deepline 0.3.237 → 0.3.239

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/index.mjs CHANGED
@@ -1171,7 +1171,7 @@ var SDK_RELEASE = {
1171
1171
  // getters keep their established compatibility behavior.
1172
1172
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1173
1173
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1174
- version: "0.3.237",
1174
+ version: "0.3.239",
1175
1175
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
1176
1176
  packageCapabilities: {
1177
1177
  updatePreferences: 1
@@ -1783,19 +1783,19 @@ function withErrorCause(error, cause) {
1783
1783
  }
1784
1784
  return error;
1785
1785
  }
1786
- function mapNetworkError(baseUrl, error, targetLabel2) {
1786
+ function mapNetworkError(baseUrl, error, targetLabel) {
1787
1787
  const tagged = error;
1788
1788
  const timeoutMs = tagged?.[REQUEST_TIMEOUT_MARKER];
1789
1789
  if (typeof timeoutMs === "number") {
1790
1790
  return {
1791
- message: `Request to ${targetLabel2} timed out after ${timeoutMs}ms while waiting for a response.`,
1791
+ message: `Request to ${targetLabel} timed out after ${timeoutMs}ms while waiting for a response.`,
1792
1792
  code: "NETWORK_TIMEOUT",
1793
- details: { timeoutMs, target: targetLabel2 }
1793
+ details: { timeoutMs, target: targetLabel }
1794
1794
  };
1795
1795
  }
1796
1796
  if (tagged?.[REQUEST_ABORT_MARKER]) {
1797
1797
  return {
1798
- message: `Request to ${targetLabel2} was aborted before it completed.`,
1798
+ message: `Request to ${targetLabel} was aborted before it completed.`,
1799
1799
  code: "NETWORK_ABORTED"
1800
1800
  };
1801
1801
  }
@@ -2533,30 +2533,6 @@ var DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING = {
2533
2533
  windowMs: 36e5
2534
2534
  };
2535
2535
 
2536
- // src/stream-reconnect.ts
2537
- var STREAM_RECONNECT_BASE_DELAY_MS = 500;
2538
- var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
2539
- var STREAM_HEALTHY_CONNECTION_MS = 3e4;
2540
- function streamReconnectDelayMs(attempt) {
2541
- const cappedExponentialMs = Math.min(
2542
- STREAM_RECONNECT_MAX_DELAY_MS,
2543
- STREAM_RECONNECT_BASE_DELAY_MS * 2 ** Math.max(0, attempt)
2544
- );
2545
- return Math.max(1, Math.floor(Math.random() * (cappedExponentialMs + 1)));
2546
- }
2547
- function isTransientPlayStreamError(error) {
2548
- if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
2549
- return true;
2550
- }
2551
- if (error instanceof DeeplineError && typeof error.statusCode === "number") {
2552
- return error.statusCode >= 500 && error.statusCode < 600;
2553
- }
2554
- const text = error instanceof Error ? error.message : String(error);
2555
- return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
2556
- text
2557
- );
2558
- }
2559
-
2560
2536
  // ../play-runtime/live-events.ts
2561
2537
  function resolveTimingWindow(input) {
2562
2538
  const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
@@ -2572,19 +2548,6 @@ function resolveTimingWindow(input) {
2572
2548
  // ../play-runtime/run-failure.ts
2573
2549
  var PUBLIC_FAILURE_TEXT_BYTE_LIMIT = 64 * 1024;
2574
2550
 
2575
- // ../play-runtime/run-terminal-source.ts
2576
- var PLAY_RUN_LEDGER_EVENT_SOURCES = [
2577
- "worker",
2578
- "convex",
2579
- "coordinator",
2580
- "system"
2581
- ];
2582
- function normalizePlayRunLedgerTerminalSource(value) {
2583
- return PLAY_RUN_LEDGER_EVENT_SOURCES.includes(
2584
- value
2585
- ) ? value : null;
2586
- }
2587
-
2588
2551
  // ../play-runtime/run-log-buffer.ts
2589
2552
  var RUNNER_LOG_MAX_BYTES = 5 * 1024 * 1024;
2590
2553
  var TERMINAL_RESERVE_BYTES = 64 * 1024;
@@ -2755,12 +2718,12 @@ function createSecretRedactionContext(initialValues = []) {
2755
2718
  }
2756
2719
  return output;
2757
2720
  }
2758
- function redactString2(value) {
2721
+ function redactString(value) {
2759
2722
  const output = redactRegisteredSecrets(value);
2760
2723
  return redactSecretLikeString(output);
2761
2724
  }
2762
2725
  function redact(value) {
2763
- if (typeof value === "string") return redactString2(value);
2726
+ if (typeof value === "string") return redactString(value);
2764
2727
  if (Array.isArray(value)) {
2765
2728
  let changed = false;
2766
2729
  const next = new Array(value.length);
@@ -2793,7 +2756,7 @@ function createSecretRedactionContext(initialValues = []) {
2793
2756
  return value;
2794
2757
  }
2795
2758
  function redactKnownSecrets(value) {
2796
- if (typeof value === "string") return redactString2(value);
2759
+ if (typeof value === "string") return redactString(value);
2797
2760
  if (Array.isArray(value)) {
2798
2761
  return value.map((entry) => redactKnownSecrets(entry));
2799
2762
  }
@@ -2812,7 +2775,7 @@ function createSecretRedactionContext(initialValues = []) {
2812
2775
  containsRegisteredSecret,
2813
2776
  matchesRegisteredSecret,
2814
2777
  redactRegisteredSecrets,
2815
- redactString: redactString2,
2778
+ redactString,
2816
2779
  redactKnownSecrets,
2817
2780
  redact
2818
2781
  };
@@ -2836,981 +2799,8 @@ var JSON_SIZE_LIMIT_REACHED = Symbol("JSON_SIZE_LIMIT_REACHED");
2836
2799
  var ledgerIngressRedactor = createSecretRedactionContext();
2837
2800
 
2838
2801
  // ../play-runtime/docflow-node-io.ts
2839
- var DOCFLOW_NODE_IO_LIMITS = {
2840
- maxPaths: 16,
2841
- maxDepth: 3,
2842
- maxObjectFields: 12,
2843
- maxArrayItems: 3,
2844
- maxStringBytes: 256,
2845
- maxPhaseBytes: 2e3,
2846
- maxErrorBytes: 512
2847
- };
2848
2802
  var utf8Encoder = new TextEncoder();
2849
2803
  var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
2850
- var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
2851
- function ledgerSafeDocflowPreviewKey(key) {
2852
- const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
2853
- let encoded = "";
2854
- for (const character of source) {
2855
- const code = character.charCodeAt(0);
2856
- if (code >= 32 && code < 127) {
2857
- encoded += character;
2858
- continue;
2859
- }
2860
- for (const byte of utf8Encoder.encode(character)) {
2861
- encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
2862
- }
2863
- }
2864
- if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
2865
- let hashA = 2166136261;
2866
- let hashB = 2654435769;
2867
- for (let index = 0; index < encoded.length; index += 1) {
2868
- const code = encoded.charCodeAt(index);
2869
- hashA = Math.imul(hashA ^ code, 16777619);
2870
- hashB = Math.imul(hashB ^ code, 16777619);
2871
- }
2872
- const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
2873
- return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
2874
- }
2875
- function utf8Bytes(value) {
2876
- return utf8Encoder.encode(value).byteLength;
2877
- }
2878
- function truncateUtf8(value, maxBytes) {
2879
- if (utf8Bytes(value) <= maxBytes) return value;
2880
- let output = "";
2881
- for (const character of value) {
2882
- if (utf8Bytes(`${output}${character}\u2026`) > maxBytes) break;
2883
- output += character;
2884
- }
2885
- return `${output}\u2026`;
2886
- }
2887
- function redactString(redactor, value, key) {
2888
- if (!key) return redactor.redactString(value);
2889
- const wrapped = redactor.redact({ [key]: value });
2890
- return typeof wrapped[key] === "string" ? wrapped[key] : redactor.redactString(value);
2891
- }
2892
- function safeString(value, redactor, key) {
2893
- if (typeof value !== "string") return void 0;
2894
- return truncateUtf8(
2895
- redactString(redactor, value, key),
2896
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
2897
- );
2898
- }
2899
- function previewValue(value, input) {
2900
- if (value === null) return { kind: "null" };
2901
- if (value === void 0) return { kind: "unavailable", reason: "undefined" };
2902
- if (typeof value === "string") {
2903
- return {
2904
- kind: "scalar",
2905
- value: safeString(value, input.redactor, input.key) ?? ""
2906
- };
2907
- }
2908
- if (typeof value === "number") {
2909
- return Number.isFinite(value) ? { kind: "scalar", value } : { kind: "unavailable", reason: "unsupported" };
2910
- }
2911
- if (typeof value === "boolean") return { kind: "scalar", value };
2912
- if (typeof value === "bigint") {
2913
- return { kind: "unavailable", reason: "bigint" };
2914
- }
2915
- if (typeof value === "function") {
2916
- return { kind: "unavailable", reason: "function" };
2917
- }
2918
- if (typeof value === "symbol") {
2919
- return { kind: "unavailable", reason: "symbol" };
2920
- }
2921
- if (typeof value !== "object") {
2922
- return { kind: "unavailable", reason: "unsupported" };
2923
- }
2924
- try {
2925
- const brand = readDataProperty(value, PLAY_DATASET_BRAND);
2926
- const datasetKind = readDataProperty(value, "datasetKind");
2927
- const datasetId = readDataProperty(value, "datasetId");
2928
- const tableNamespace = readDataProperty(value, "tableNamespace");
2929
- if (brand.ok && brand.value === true && datasetKind.ok && (datasetKind.value === "csv" || datasetKind.value === "map") && datasetId.ok && typeof datasetId.value === "string") {
2930
- return {
2931
- kind: "dataset",
2932
- datasetId: safeString(datasetId.value, input.redactor, "datasetId") ?? "dataset",
2933
- datasetKind: datasetKind.value,
2934
- tableNamespace: tableNamespace.ok && tableNamespace.value === null ? null : tableNamespace.ok ? safeString(
2935
- tableNamespace.value,
2936
- input.redactor,
2937
- "tableNamespace"
2938
- ) : void 0
2939
- };
2940
- }
2941
- const kind = readDataProperty(value, "kind");
2942
- const count = readDataProperty(value, "count");
2943
- const preview = readDataProperty(value, "preview");
2944
- if (kind.ok && kind.value === "dataset" && datasetKind.ok && (datasetKind.value === "csv" || datasetKind.value === "map") && datasetId.ok && typeof datasetId.value === "string" && count.ok && typeof count.value === "number" && preview.ok && Array.isArray(preview.value)) {
2945
- return {
2946
- kind: "dataset",
2947
- datasetId: safeString(datasetId.value, input.redactor, "datasetId") ?? "dataset",
2948
- datasetKind: datasetKind.value,
2949
- tableNamespace: tableNamespace.ok && tableNamespace.value === null ? null : tableNamespace.ok ? safeString(
2950
- tableNamespace.value,
2951
- input.redactor,
2952
- "tableNamespace"
2953
- ) : void 0,
2954
- rowCount: Math.max(0, Math.floor(count.value))
2955
- };
2956
- }
2957
- } catch {
2958
- return { kind: "unavailable", reason: "access_error" };
2959
- }
2960
- if (input.ancestors.has(value)) {
2961
- return { kind: "unavailable", reason: "cycle" };
2962
- }
2963
- if (input.depth >= DOCFLOW_NODE_IO_LIMITS.maxDepth) {
2964
- return { kind: "unavailable", reason: "depth" };
2965
- }
2966
- input.ancestors.add(value);
2967
- try {
2968
- if (Array.isArray(value)) {
2969
- const items = [];
2970
- for (let index = 0; index < Math.min(value.length, DOCFLOW_NODE_IO_LIMITS.maxArrayItems); index += 1) {
2971
- const descriptor = Object.getOwnPropertyDescriptor(
2972
- value,
2973
- String(index)
2974
- );
2975
- items.push(
2976
- descriptor && "value" in descriptor ? previewValue(descriptor.value, {
2977
- ...input,
2978
- depth: input.depth + 1
2979
- }) : { kind: "unavailable", reason: "access_error" }
2980
- );
2981
- }
2982
- return {
2983
- kind: "array",
2984
- items,
2985
- totalItems: value.length,
2986
- ...value.length > items.length ? { truncated: true } : {}
2987
- };
2988
- }
2989
- let descriptors;
2990
- try {
2991
- descriptors = Object.getOwnPropertyDescriptors(value);
2992
- } catch {
2993
- return { kind: "unavailable", reason: "access_error" };
2994
- }
2995
- const entries = Object.entries(descriptors).filter(
2996
- ([, descriptor]) => descriptor.enumerable
2997
- );
2998
- const selected = entries.slice(0, DOCFLOW_NODE_IO_LIMITS.maxObjectFields);
2999
- const fields = {};
3000
- for (const [key, descriptor] of selected) {
3001
- const safeKey = truncateUtf8(
3002
- ledgerSafeDocflowPreviewKey(input.redactor.redactString(key)),
3003
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
3004
- );
3005
- fields[safeKey] = "value" in descriptor ? previewValue(descriptor.value, {
3006
- ...input,
3007
- depth: input.depth + 1,
3008
- key
3009
- }) : { kind: "unavailable", reason: "access_error" };
3010
- }
3011
- return {
3012
- kind: "object",
3013
- fields,
3014
- totalFields: entries.length,
3015
- ...entries.length > selected.length ? { truncated: true } : {}
3016
- };
3017
- } catch {
3018
- return { kind: "unavailable", reason: "access_error" };
3019
- } finally {
3020
- input.ancestors.delete(value);
3021
- }
3022
- }
3023
- function readDataProperty(value, property) {
3024
- if ((typeof value !== "object" || value === null) && typeof value !== "function") {
3025
- return { ok: false };
3026
- }
3027
- try {
3028
- let owner = value;
3029
- while (owner) {
3030
- const descriptor = Object.getOwnPropertyDescriptor(owner, property);
3031
- if (descriptor) {
3032
- return "value" in descriptor ? { ok: true, value: descriptor.value } : { ok: false };
3033
- }
3034
- owner = Object.getPrototypeOf(owner);
3035
- }
3036
- } catch {
3037
- return { ok: false };
3038
- }
3039
- return { ok: false };
3040
- }
3041
- function buildPlayDocflowNodeErrorPreview(error, redactor = createSecretRedactionContext()) {
3042
- try {
3043
- const message = error instanceof Error ? error.message : String(error);
3044
- return truncateUtf8(
3045
- redactor.redactString(message),
3046
- DOCFLOW_NODE_IO_LIMITS.maxErrorBytes
3047
- );
3048
- } catch {
3049
- return "Non-Error value thrown (message unavailable)";
3050
- }
3051
- }
3052
- function isRecord4(value) {
3053
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3054
- }
3055
- function normalizePreview(value, depth) {
3056
- if (!isRecord4(value) || typeof value.kind !== "string") return null;
3057
- if (value.kind === "null") return { kind: "null" };
3058
- if (value.kind === "scalar") {
3059
- if (typeof value.value === "string" || typeof value.value === "boolean" || typeof value.value === "number" && Number.isFinite(value.value)) {
3060
- return previewValue(value.value, {
3061
- depth: 0,
3062
- ancestors: /* @__PURE__ */ new WeakSet(),
3063
- redactor: createSecretRedactionContext()
3064
- });
3065
- }
3066
- return null;
3067
- }
3068
- if (value.kind === "unavailable") {
3069
- const reason = value.reason;
3070
- return typeof reason === "string" && [
3071
- "undefined",
3072
- "function",
3073
- "symbol",
3074
- "bigint",
3075
- "cycle",
3076
- "depth",
3077
- "access_error",
3078
- "limit",
3079
- "unsupported"
3080
- ].includes(reason) ? {
3081
- kind: "unavailable",
3082
- reason
3083
- } : null;
3084
- }
3085
- if (value.kind === "dataset") {
3086
- const datasetId = safeString(
3087
- value.datasetId,
3088
- createSecretRedactionContext(),
3089
- "datasetId"
3090
- );
3091
- if (!datasetId || value.datasetKind !== "csv" && value.datasetKind !== "map") {
3092
- return null;
3093
- }
3094
- return {
3095
- kind: "dataset",
3096
- datasetId,
3097
- datasetKind: value.datasetKind,
3098
- ...value.tableNamespace === null ? { tableNamespace: null } : typeof value.tableNamespace === "string" ? {
3099
- tableNamespace: safeString(
3100
- value.tableNamespace,
3101
- createSecretRedactionContext(),
3102
- "tableNamespace"
3103
- )
3104
- } : {},
3105
- ...typeof value.rowCount === "number" && Number.isFinite(value.rowCount) ? { rowCount: Math.max(0, Math.floor(value.rowCount)) } : {}
3106
- };
3107
- }
3108
- if (depth >= DOCFLOW_NODE_IO_LIMITS.maxDepth) {
3109
- return { kind: "unavailable", reason: "depth" };
3110
- }
3111
- if (value.kind === "array" && Array.isArray(value.items)) {
3112
- const items = value.items.slice(0, DOCFLOW_NODE_IO_LIMITS.maxArrayItems).map((item) => normalizePreview(item, depth + 1)).filter((item) => item !== null);
3113
- const totalItems = typeof value.totalItems === "number" && Number.isFinite(value.totalItems) ? Math.max(items.length, Math.floor(value.totalItems)) : items.length;
3114
- return {
3115
- kind: "array",
3116
- items,
3117
- totalItems,
3118
- ...value.truncated === true || totalItems > items.length ? { truncated: true } : {}
3119
- };
3120
- }
3121
- if (value.kind === "object" && isRecord4(value.fields)) {
3122
- const fields = {};
3123
- for (const [key, field] of Object.entries(value.fields).slice(
3124
- 0,
3125
- DOCFLOW_NODE_IO_LIMITS.maxObjectFields
3126
- )) {
3127
- const normalized = normalizePreview(field, depth + 1);
3128
- if (normalized) fields[ledgerSafeDocflowPreviewKey(key)] = normalized;
3129
- }
3130
- const totalFields = typeof value.totalFields === "number" && Number.isFinite(value.totalFields) ? Math.max(Object.keys(fields).length, Math.floor(value.totalFields)) : Object.keys(fields).length;
3131
- return {
3132
- kind: "object",
3133
- fields,
3134
- totalFields,
3135
- ...value.truncated === true || totalFields > Object.keys(fields).length ? { truncated: true } : {}
3136
- };
3137
- }
3138
- return null;
3139
- }
3140
- function normalizePlayDocflowNodeIoState(value) {
3141
- if (!isRecord4(value)) return void 0;
3142
- const attempt = typeof value.attempt === "number" && Number.isFinite(value.attempt) ? Math.max(0, Math.floor(value.attempt)) : 0;
3143
- const normalizeMap = (raw) => {
3144
- if (!isRecord4(raw)) return void 0;
3145
- const output = {};
3146
- for (const [path, preview] of Object.entries(raw).slice(
3147
- 0,
3148
- DOCFLOW_NODE_IO_LIMITS.maxPaths
3149
- )) {
3150
- const normalized = normalizePreview(preview, 0);
3151
- if (!normalized) continue;
3152
- const safePath = ledgerSafeDocflowPreviewKey(path);
3153
- const candidate = { ...output, [safePath]: normalized };
3154
- if (utf8Bytes(JSON.stringify(candidate)) > DOCFLOW_NODE_IO_LIMITS.maxPhaseBytes) {
3155
- break;
3156
- }
3157
- output[safePath] = normalized;
3158
- }
3159
- return output;
3160
- };
3161
- const inputs = normalizeMap(value.inputs);
3162
- const outputs = normalizeMap(value.outputs);
3163
- const error = typeof value.error === "string" ? buildPlayDocflowNodeErrorPreview(value.error) : value.error === null ? null : void 0;
3164
- return {
3165
- attempt,
3166
- ...typeof value.invocationId === "string" && value.invocationId.trim() ? {
3167
- invocationId: truncateUtf8(
3168
- value.invocationId.trim(),
3169
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
3170
- )
3171
- } : {},
3172
- ...inputs ? { inputs } : {},
3173
- ...outputs ? { outputs } : {},
3174
- ...value.inputsTruncated === true ? { inputsTruncated: true } : {},
3175
- ...value.outputsTruncated === true ? { outputsTruncated: true } : {},
3176
- ...error !== void 0 ? { error } : {}
3177
- };
3178
- }
3179
-
3180
- // ../play-runtime/activity-observation.ts
3181
- function isRecord5(value) {
3182
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3183
- }
3184
- function isPlayActivityObservation(value) {
3185
- if (!isRecord5(value) || value.schemaVersion !== void 0 && value.schemaVersion !== 1 || typeof value.activityId !== "string" || !value.activityId.trim() || typeof value.observedAt !== "number" || !Number.isFinite(value.observedAt) || !isRecord5(value.target) || !isRecord5(value.state)) {
3186
- return false;
3187
- }
3188
- const targetKinds = /* @__PURE__ */ new Set([
3189
- "provider",
3190
- "dataset",
3191
- "lease",
3192
- "runner",
3193
- "step",
3194
- "capacity"
3195
- ]);
3196
- const stateKinds = /* @__PURE__ */ new Set([
3197
- "active",
3198
- "queued",
3199
- "waiting",
3200
- "retrying",
3201
- "scheduled",
3202
- "completed",
3203
- "failed"
3204
- ]);
3205
- return typeof value.target.kind === "string" && targetKinds.has(value.target.kind) && typeof value.state.kind === "string" && stateKinds.has(value.state.kind);
3206
- }
3207
- function derivePlayActivityVisibility(input) {
3208
- const { state } = input.observation;
3209
- if (state.kind === "failed") return "action";
3210
- if (state.kind === "waiting") {
3211
- return state.reason.kind === "external_event" ? "action" : "headline";
3212
- }
3213
- if (state.kind === "queued" || state.kind === "scheduled") return "headline";
3214
- if (state.kind === "retrying") {
3215
- if (state.reason === "rate_limit" && input.observation.target.kind === "capacity") {
3216
- return "headline";
3217
- }
3218
- if (state.retryAt - input.now <= 3e4) return "hidden";
3219
- return state.reason === "rate_limit" ? "headline" : "timeline";
3220
- }
3221
- if (state.kind === "active") return "headline";
3222
- return "timeline";
3223
- }
3224
- function targetLabel(target) {
3225
- switch (target.kind) {
3226
- case "provider":
3227
- return target.label ?? `${target.provider} ${target.operation}`;
3228
- case "dataset":
3229
- return target.label ?? `dataset ${target.tableNamespace}`;
3230
- case "lease":
3231
- return `${target.resourceKind} ${target.resourceLabel}`;
3232
- case "runner":
3233
- return target.label ?? `${target.backend} runner`;
3234
- case "step":
3235
- return target.label ?? target.stepId;
3236
- case "capacity":
3237
- return target.label ?? `${target.pool} capacity`;
3238
- }
3239
- }
3240
- function summarizePlayActivity(observation) {
3241
- const label = targetLabel(observation.target);
3242
- const { state } = observation;
3243
- switch (state.kind) {
3244
- case "active": {
3245
- const progress = state.progress;
3246
- const counts = typeof progress?.completed === "number" && typeof progress.total === "number" ? ` (${progress.completed}/${progress.total})` : "";
3247
- return `Active: ${label}${counts}.`;
3248
- }
3249
- case "queued":
3250
- return `Queued: waiting for ${label}.`;
3251
- case "waiting":
3252
- if (state.reason.kind === "external_event") {
3253
- const provider = state.reason.provider ? ` from ${state.reason.provider}` : "";
3254
- return `Blocked: waiting for an external event${provider}.`;
3255
- }
3256
- if (state.reason.kind === "dataset") {
3257
- return `Blocked: waiting for dataset ${state.reason.tableNamespace} to become ${state.reason.requiredPhase}.`;
3258
- }
3259
- if (state.reason.kind === "lease") {
3260
- return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
3261
- }
3262
- return `Paused: ${label}; the legacy producer did not report a typed reason.`;
3263
- case "retrying":
3264
- if (state.reason === "rate_limit") {
3265
- return `Waiting for ${label} request capacity; the run will retry automatically.`;
3266
- }
3267
- return `Delayed: ${label} will retry automatically.`;
3268
- case "scheduled":
3269
- return `Scheduled: ${label} will resume automatically.`;
3270
- case "completed":
3271
- return `Completed: ${label}.`;
3272
- case "failed":
3273
- return `Failed: ${label}.`;
3274
- }
3275
- }
3276
- function isTerminalActivityState(state) {
3277
- return state.kind === "completed" || state.kind === "failed";
3278
- }
3279
- function projection(observation, now, classified = true) {
3280
- return {
3281
- observation,
3282
- summary: summarizePlayActivity(observation),
3283
- visibility: derivePlayActivityVisibility({ observation, now }),
3284
- classified
3285
- };
3286
- }
3287
- function projectPlayRunActivity(input) {
3288
- const now = input.now ?? Date.now();
3289
- const observedAt = input.updatedAt ?? now;
3290
- const normalizedStatus = input.status.trim().toLowerCase();
3291
- if (normalizedStatus === "completed" || normalizedStatus === "failed" || normalizedStatus === "cancelled" || normalizedStatus === "terminated" || normalizedStatus === "timed_out") {
3292
- return null;
3293
- }
3294
- const latestExplicit = [...input.explicit ?? []].filter((candidate) => !isTerminalActivityState(candidate.state)).sort((left, right) => right.observedAt - left.observedAt)[0];
3295
- const explicit = latestExplicit?.target.kind === "capacity" && latestExplicit.target.pool === "provider" && latestExplicit.state.kind === "retrying" && latestExplicit.state.retryAt < now ? null : latestExplicit;
3296
- if (explicit) return projection(explicit, now);
3297
- if (input.waitKind === "detached_runner") {
3298
- return projection(
3299
- {
3300
- activityId: "runner:managed",
3301
- target: {
3302
- kind: "runner",
3303
- backend: input.runtimeBackend ?? "managed",
3304
- label: "managed play runner"
3305
- },
3306
- state: { kind: "active" },
3307
- observedAt
3308
- },
3309
- now
3310
- );
3311
- }
3312
- if (input.waitKind === "sleep") {
3313
- return projection(
3314
- {
3315
- activityId: "run:sleep",
3316
- target: {
3317
- kind: "step",
3318
- stepId: input.activeNodeId ?? "run",
3319
- label: input.playName ?? "play"
3320
- },
3321
- state: {
3322
- kind: "scheduled",
3323
- reason: "sleep",
3324
- resumeAt: input.waitUntil ?? observedAt
3325
- },
3326
- observedAt
3327
- },
3328
- now
3329
- );
3330
- }
3331
- if (input.waitKind === "integration_event" || input.waitKind === "integration_event_batch") {
3332
- return projection(
3333
- {
3334
- activityId: `event:${input.activeNodeId ?? "run"}`,
3335
- stepId: input.activeNodeId ?? void 0,
3336
- target: {
3337
- kind: "step",
3338
- stepId: input.activeNodeId ?? "run",
3339
- label: input.playName ?? "play"
3340
- },
3341
- state: {
3342
- kind: "waiting",
3343
- reason: {
3344
- kind: "external_event",
3345
- eventKey: input.eventKey ?? void 0,
3346
- deadlineAt: input.waitUntil ?? void 0
3347
- }
3348
- },
3349
- observedAt
3350
- },
3351
- now
3352
- );
3353
- }
3354
- if (normalizedStatus === "waiting") {
3355
- return projection(
3356
- {
3357
- activityId: "run:legacy-wait",
3358
- target: {
3359
- kind: "step",
3360
- stepId: input.activeNodeId ?? "run",
3361
- label: input.playName ?? "play"
3362
- },
3363
- state: { kind: "waiting", reason: { kind: "unknown" } },
3364
- observedAt
3365
- },
3366
- now,
3367
- false
3368
- );
3369
- }
3370
- const pendingDataset = [...input.datasets ?? []].filter(
3371
- (dataset) => dataset.phase === "registered" || dataset.complete !== true && dataset.phase !== "failed"
3372
- ).sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
3373
- if (pendingDataset) {
3374
- const activeDatasetStep = input.nodeStates?.find(
3375
- (candidate) => candidate.nodeId === input.activeNodeId && candidate.status === "running" && candidate.artifactTableNamespace === pendingDataset.tableNamespace
3376
- );
3377
- const datasetStep = activeDatasetStep ?? input.nodeStates?.find(
3378
- (candidate) => candidate.artifactTableNamespace === pendingDataset.tableNamespace
3379
- );
3380
- const datasetProgress = datasetStep?.progress ?? null;
3381
- return projection(
3382
- {
3383
- activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
3384
- target: {
3385
- kind: "dataset",
3386
- tableNamespace: pendingDataset.tableNamespace,
3387
- operation: "materializing"
3388
- },
3389
- state: {
3390
- kind: "active",
3391
- progress: {
3392
- completed: datasetProgress?.completed ?? pendingDataset.persistedRows,
3393
- total: datasetProgress?.total ?? (typeof pendingDataset.succeededRows === "number" || typeof pendingDataset.failedRows === "number" ? (pendingDataset.succeededRows ?? 0) + (pendingDataset.failedRows ?? 0) : void 0),
3394
- failed: datasetProgress?.failed ?? pendingDataset.failedRows
3395
- }
3396
- },
3397
- observedAt: datasetStep?.updatedAt ?? pendingDataset.updatedAt ?? observedAt
3398
- },
3399
- now
3400
- );
3401
- }
3402
- const activeStep = input.nodeStates?.find(
3403
- (candidate) => candidate.nodeId === input.activeNodeId
3404
- ) ?? input.nodeStates?.find((candidate) => candidate.status === "running") ?? null;
3405
- if (activeStep) {
3406
- const toolId = activeStep.nodeId.startsWith("tool:") ? activeStep.nodeId.split(":").at(-1)?.trim() || null : null;
3407
- const provider = toolId?.split(/[._]/)[0]?.trim() || null;
3408
- return projection(
3409
- {
3410
- activityId: `step:${activeStep.nodeId}`,
3411
- stepId: activeStep.nodeId,
3412
- target: toolId && provider ? {
3413
- kind: "provider",
3414
- provider,
3415
- operation: toolId,
3416
- label: activeStep.label ?? toolId
3417
- } : {
3418
- kind: "step",
3419
- stepId: activeStep.nodeId,
3420
- label: activeStep.label
3421
- },
3422
- state: {
3423
- kind: "active",
3424
- progress: activeStep.progress ?? void 0
3425
- },
3426
- observedAt: activeStep.updatedAt ?? observedAt
3427
- },
3428
- now
3429
- );
3430
- }
3431
- if (normalizedStatus === "queued") {
3432
- return projection(
3433
- {
3434
- activityId: "capacity:worker",
3435
- target: { kind: "capacity", pool: "worker" },
3436
- state: { kind: "queued", reason: "capacity" },
3437
- observedAt
3438
- },
3439
- now
3440
- );
3441
- }
3442
- const fallback = {
3443
- activityId: "run:legacy",
3444
- target: {
3445
- kind: "step",
3446
- stepId: input.activeNodeId ?? "run",
3447
- label: input.playName ?? "play"
3448
- },
3449
- state: normalizedStatus === "waiting" ? { kind: "waiting", reason: { kind: "unknown" } } : { kind: "active" },
3450
- observedAt
3451
- };
3452
- return projection(fallback, now, normalizedStatus !== "waiting");
3453
- }
3454
-
3455
- // ../play-runtime/cell-provenance.ts
3456
- function finiteNonNegativeInteger2(value) {
3457
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
3458
- return null;
3459
- }
3460
- return Math.trunc(value);
3461
- }
3462
- function nonEmptyString(value) {
3463
- if (typeof value !== "string") return null;
3464
- const trimmed = value.trim();
3465
- return trimmed ? trimmed : null;
3466
- }
3467
- function normalizeDatasetBornFrom(value) {
3468
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3469
- const record3 = value;
3470
- const table = nonEmptyString(record3.table);
3471
- const rowCountIn = finiteNonNegativeInteger2(record3.rowCountIn);
3472
- if (!table || rowCountIn === null) return null;
3473
- return { table, rowCountIn };
3474
- }
3475
-
3476
- // ../play-runtime/run-ledger.ts
3477
- var LOG_TAIL_LIMIT = 100;
3478
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
3479
- "completed",
3480
- "failed",
3481
- "cancelled",
3482
- "terminated",
3483
- "timed_out"
3484
- ]);
3485
- function isRecord6(value) {
3486
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3487
- }
3488
- function finiteNumber(value) {
3489
- return typeof value === "number" && Number.isFinite(value) ? value : null;
3490
- }
3491
- function nonnegativeSafeInteger(value) {
3492
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3493
- }
3494
- function isWorkCounterSegmentId(value) {
3495
- return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
3496
- value
3497
- );
3498
- }
3499
- function normalizePlayRunLedgerTerminalWorkCounterSegment(value) {
3500
- if (!isRecord6(value)) return void 0;
3501
- if (!nonnegativeSafeInteger(value.attempt) || !nonnegativeSafeInteger(value.invocationSeq) || value.invocationSeq === 0 || !isWorkCounterSegmentId(value.segmentId)) {
3502
- return void 0;
3503
- }
3504
- return {
3505
- attempt: value.attempt,
3506
- invocationSeq: value.invocationSeq,
3507
- segmentId: value.segmentId
3508
- };
3509
- }
3510
- function hasConfirmedPlayRunLedgerWorkCounters(snapshot) {
3511
- const counters = snapshot.workCounters;
3512
- const terminal = snapshot.terminalWorkCounterSegment;
3513
- return Boolean(
3514
- isTerminalPlayRunLedgerStatus(snapshot.status) && (snapshot.terminalSource === "worker" || snapshot.terminalSource === "system") && counters?.final && counters.completeThroughAttempt && counters.consistent && terminal && counters.attempt === terminal.attempt && counters.invocationSeq === terminal.invocationSeq && counters.segmentId === terminal.segmentId && Number.isSafeInteger(
3515
- counters.completedLogicalCalls + counters.logicalCalls
3516
- ) && Number.isSafeInteger(
3517
- counters.completedProviderRequests + counters.providerRequests
3518
- ) && Number.isSafeInteger(
3519
- counters.completedProvider429s + counters.provider429s
3520
- )
3521
- );
3522
- }
3523
- function normalizeWorkCounters(value) {
3524
- if (!isRecord6(value)) return void 0;
3525
- for (const field of [
3526
- "attempt",
3527
- "invocationSeq",
3528
- "logicalCalls",
3529
- "providerRequests",
3530
- "provider429s",
3531
- "completedLogicalCalls",
3532
- "completedProviderRequests",
3533
- "completedProvider429s"
3534
- ]) {
3535
- if (!nonnegativeSafeInteger(value[field])) return void 0;
3536
- }
3537
- if (value.invocationSeq === 0 || !isWorkCounterSegmentId(value.segmentId) || typeof value.final !== "boolean" || typeof value.completeThroughAttempt !== "boolean" || typeof value.consistent !== "boolean") {
3538
- return void 0;
3539
- }
3540
- return {
3541
- ...value,
3542
- attempt: value.attempt,
3543
- invocationSeq: value.invocationSeq,
3544
- segmentId: value.segmentId,
3545
- logicalCalls: value.logicalCalls,
3546
- providerRequests: value.providerRequests,
3547
- provider429s: value.provider429s,
3548
- final: value.final,
3549
- completedLogicalCalls: value.completedLogicalCalls,
3550
- completedProviderRequests: value.completedProviderRequests,
3551
- completedProvider429s: value.completedProvider429s,
3552
- completeThroughAttempt: value.completeThroughAttempt,
3553
- consistent: value.consistent
3554
- };
3555
- }
3556
- function optionalFiniteNumber(value) {
3557
- const normalized = finiteNumber(value);
3558
- return normalized === null ? void 0 : normalized;
3559
- }
3560
- function optionalString(value) {
3561
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
3562
- }
3563
- function optionalNullableString(value) {
3564
- if (value === null) return null;
3565
- return optionalString(value);
3566
- }
3567
- function normalizePlayRunLedgerStatus(value) {
3568
- const normalized = String(value ?? "").trim().toLowerCase();
3569
- switch (normalized) {
3570
- case "queued":
3571
- case "pending":
3572
- return "queued";
3573
- case "running":
3574
- case "started":
3575
- return "running";
3576
- case "waiting":
3577
- return "waiting";
3578
- case "completed":
3579
- case "complete":
3580
- case "succeeded":
3581
- return "completed";
3582
- case "failed":
3583
- case "error":
3584
- return "failed";
3585
- case "cancelled":
3586
- case "canceled":
3587
- return "cancelled";
3588
- case "terminated":
3589
- return "terminated";
3590
- case "timed_out":
3591
- case "timeout":
3592
- return "timed_out";
3593
- default:
3594
- return "unknown";
3595
- }
3596
- }
3597
- function isTerminalPlayRunLedgerStatus(status) {
3598
- return TERMINAL_STATUSES.has(status);
3599
- }
3600
- function summarizeRunRowOutcomes(snapshot) {
3601
- let completedRows = 0;
3602
- let failedRows = 0;
3603
- let totalRows = 0;
3604
- let supersededRows = 0;
3605
- for (const step of Object.values(snapshot.stepsById)) {
3606
- const progress = step.progress;
3607
- if (!progress) continue;
3608
- completedRows += Math.max(0, finiteNumber(progress.completed) ?? 0);
3609
- failedRows += Math.max(0, finiteNumber(progress.failed) ?? 0);
3610
- supersededRows += Math.max(0, finiteNumber(progress.supersededRows) ?? 0);
3611
- const stepTotal = finiteNumber(progress.total);
3612
- totalRows += stepTotal !== null && stepTotal >= 0 ? stepTotal : Math.max(0, finiteNumber(progress.completed) ?? 0) + Math.max(0, finiteNumber(progress.failed) ?? 0) + Math.max(0, finiteNumber(progress.supersededRows) ?? 0);
3613
- }
3614
- const resultSummary = isRecord6(snapshot.resultSummary) ? snapshot.resultSummary : null;
3615
- const resultRowOutcomes = isRecord6(resultSummary?.rowOutcomes) ? resultSummary.rowOutcomes : null;
3616
- const terminalCompletedRows = finiteNumber(resultRowOutcomes?.completedRows);
3617
- const terminalFailedRows = finiteNumber(resultRowOutcomes?.failedRows);
3618
- const terminalTotalRows = finiteNumber(resultRowOutcomes?.totalRows);
3619
- const terminalSupersededRows = finiteNumber(
3620
- resultRowOutcomes?.supersededRows
3621
- );
3622
- const settledCompletedRows = terminalCompletedRows ?? completedRows;
3623
- const settledFailedRows = terminalFailedRows ?? failedRows;
3624
- return {
3625
- completedRows: settledCompletedRows,
3626
- failedRows: settledFailedRows,
3627
- totalRows: terminalTotalRows ?? totalRows,
3628
- hasRowFailures: settledFailedRows > 0,
3629
- ...(terminalSupersededRows ?? supersededRows) > 0 ? { supersededRows: terminalSupersededRows ?? supersededRows } : {}
3630
- };
3631
- }
3632
- function createEmptyPlayRunLedgerSnapshot(input) {
3633
- const status = normalizePlayRunLedgerStatus(input.status ?? "unknown");
3634
- const startedAt = finiteNumber(input.startedAt) ?? null;
3635
- const finishedAt = finiteNumber(input.finishedAt) ?? null;
3636
- return {
3637
- runId: input.runId,
3638
- playName: input.playName ?? null,
3639
- status,
3640
- waitKind: null,
3641
- waitUntil: null,
3642
- error: optionalNullableString(input.error) ?? null,
3643
- createdAt: finiteNumber(input.createdAt) ?? null,
3644
- startedAt,
3645
- updatedAt: finiteNumber(input.updatedAt) ?? finishedAt ?? startedAt ?? finiteNumber(input.createdAt) ?? null,
3646
- finishedAt,
3647
- durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : null,
3648
- orderedStepIds: [],
3649
- stepsById: {},
3650
- orderedDatasetIds: [],
3651
- datasetsById: {},
3652
- orderedActivityIds: [],
3653
- activitiesById: {},
3654
- logTail: [],
3655
- totalLogCount: 0,
3656
- logsTruncated: false,
3657
- activeStepId: null,
3658
- activeActivityId: null,
3659
- activeArtifactTableNamespace: null,
3660
- resultTableNamespace: null
3661
- };
3662
- }
3663
- function normalizePlayRunLedgerSnapshot(value, fallback) {
3664
- if (!isRecord6(value)) {
3665
- return createEmptyPlayRunLedgerSnapshot(fallback);
3666
- }
3667
- const orderedStepIds = Array.isArray(value.orderedStepIds) ? value.orderedStepIds.filter(
3668
- (entry) => typeof entry === "string" && Boolean(entry.trim())
3669
- ) : [];
3670
- const rawSteps = isRecord6(value.stepsById) ? value.stepsById : {};
3671
- const stepsById = {};
3672
- for (const [stepId, rawStep] of Object.entries(rawSteps)) {
3673
- if (!stepId.trim() || !isRecord6(rawStep)) continue;
3674
- const rawStatus = normalizeStepStatus(rawStep.status);
3675
- if (!rawStatus) continue;
3676
- const completedAt = finiteNumber(rawStep.completedAt);
3677
- const status = rawStatus === "running" && completedAt !== null ? "completed" : rawStatus;
3678
- const rawProgress = isRecord6(rawStep.progress) ? rawStep.progress : null;
3679
- stepsById[stepId] = {
3680
- stepId,
3681
- label: optionalString(rawStep.label),
3682
- kind: optionalString(rawStep.kind),
3683
- status,
3684
- error: optionalNullableString(rawStep.error),
3685
- artifactTableNamespace: optionalNullableString(
3686
- rawStep.artifactTableNamespace
3687
- ),
3688
- startedAt: finiteNumber(rawStep.startedAt),
3689
- completedAt,
3690
- updatedAt: finiteNumber(rawStep.updatedAt),
3691
- progress: rawProgress ? normalizeStepProgress(rawProgress) : null
3692
- };
3693
- }
3694
- const rawDatasets = isRecord6(value.datasetsById) ? value.datasetsById : {};
3695
- const datasetsById = {};
3696
- for (const [datasetId, rawDataset] of Object.entries(rawDatasets)) {
3697
- if (!datasetId.trim() || !isRecord6(rawDataset)) continue;
3698
- const phase = rawDataset.phase === "available" || rawDataset.phase === "failed" ? rawDataset.phase : "registered";
3699
- const datasetBornFrom = normalizeDatasetBornFrom(rawDataset.bornFrom);
3700
- datasetsById[datasetId] = {
3701
- datasetId,
3702
- path: optionalString(rawDataset.path) ?? `datasets.${datasetId}`,
3703
- tableNamespace: optionalString(rawDataset.tableNamespace) ?? datasetId,
3704
- phase,
3705
- persistedRows: Math.max(0, finiteNumber(rawDataset.persistedRows) ?? 0),
3706
- persistedRowsObserved: rawDataset.persistedRowsObserved === true,
3707
- succeededRows: Math.max(0, finiteNumber(rawDataset.succeededRows) ?? 0),
3708
- succeededRowsObserved: rawDataset.succeededRowsObserved === true,
3709
- failedRows: Math.max(0, finiteNumber(rawDataset.failedRows) ?? 0),
3710
- failedRowsObserved: rawDataset.failedRowsObserved === true,
3711
- complete: rawDataset.complete === true,
3712
- completeObserved: rawDataset.completeObserved === true,
3713
- // ADR 0019 birth survives the snapshot round trip. A partial record is
3714
- // dropped rather than half-admitted, so a dataset either states where its
3715
- // rows came from or says nothing.
3716
- ...datasetBornFrom ? { bornFrom: datasetBornFrom } : {},
3717
- updatedAt: finiteNumber(rawDataset.updatedAt) ?? 0
3718
- };
3719
- }
3720
- const rawActivities = isRecord6(value.activitiesById) ? value.activitiesById : {};
3721
- const activitiesById = {};
3722
- for (const [activityId, rawActivity] of Object.entries(rawActivities)) {
3723
- if (activityId.trim() && isPlayActivityObservation(rawActivity) && rawActivity.activityId === activityId) {
3724
- activitiesById[activityId] = rawActivity;
3725
- }
3726
- }
3727
- const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
3728
- const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
3729
- const finishedAt = finiteNumber(value.finishedAt) ?? fallback.finishedAt ?? null;
3730
- const updatedAt = finiteNumber(value.updatedAt) ?? fallback.updatedAt ?? finishedAt ?? startedAt ?? createdAt ?? null;
3731
- const error = Object.prototype.hasOwnProperty.call(value, "error") ? optionalNullableString(value.error) ?? null : fallback.error ?? null;
3732
- const rawTail = Array.isArray(value.logTail) ? value.logTail : value.logs;
3733
- const logTail = Array.isArray(rawTail) ? rawTail.filter((line) => typeof line === "string") : [];
3734
- const workCounters = normalizeWorkCounters(value.workCounters);
3735
- const terminalWorkCounterSegment = normalizePlayRunLedgerTerminalWorkCounterSegment(
3736
- value.terminalWorkCounterSegment
3737
- );
3738
- return {
3739
- runId: optionalString(value.runId) ?? fallback.runId,
3740
- playName: optionalNullableString(value.playName) ?? fallback.playName ?? null,
3741
- status: normalizePlayRunLedgerStatus(value.status ?? fallback.status),
3742
- waitKind: optionalNullableString(value.waitKind),
3743
- waitUntil: finiteNumber(value.waitUntil),
3744
- error,
3745
- createdAt,
3746
- startedAt,
3747
- updatedAt,
3748
- finishedAt,
3749
- durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : finiteNumber(value.durationMs),
3750
- orderedStepIds: orderedStepIds.filter((stepId) => stepsById[stepId]),
3751
- stepsById,
3752
- orderedDatasetIds: (Array.isArray(value.orderedDatasetIds) ? value.orderedDatasetIds : Object.keys(datasetsById)).filter(
3753
- (datasetId) => typeof datasetId === "string" && Boolean(datasetsById[datasetId])
3754
- ),
3755
- datasetsById,
3756
- orderedActivityIds: (Array.isArray(value.orderedActivityIds) ? value.orderedActivityIds : Object.keys(activitiesById)).filter(
3757
- (activityId) => typeof activityId === "string" && Boolean(activitiesById[activityId])
3758
- ),
3759
- activitiesById,
3760
- ...workCounters ? { workCounters } : {},
3761
- ...terminalWorkCounterSegment ? { terminalWorkCounterSegment } : {},
3762
- logTail: logTail.slice(-LOG_TAIL_LIMIT),
3763
- // Snapshots persisted before totalLogCount existed only know the retained
3764
- // tail, so the best lower bound for the cumulative count is the tail size.
3765
- totalLogCount: Math.max(
3766
- finiteNumber(value.totalLogCount) ?? logTail.length,
3767
- logTail.length
3768
- ),
3769
- // A persisted snapshot carrying more lines than the tail keeps is
3770
- // truncated here, whatever the stored flag says. Reporting `false` while
3771
- // dropping lines is silent loss: a reader would take the tail for the
3772
- // whole log.
3773
- logsTruncated: value.logsTruncated === true || logTail.length > LOG_TAIL_LIMIT,
3774
- activeStepId: optionalNullableString(value.activeStepId),
3775
- activeActivityId: optionalNullableString(value.activeActivityId),
3776
- activeArtifactTableNamespace: optionalNullableString(
3777
- value.activeArtifactTableNamespace
3778
- ),
3779
- resultTableNamespace: optionalNullableString(value.resultTableNamespace),
3780
- resultSummary: value.resultSummary,
3781
- result: value.result,
3782
- terminalSource: normalizePlayRunLedgerTerminalSource(value.terminalSource)
3783
- };
3784
- }
3785
- function normalizeStepStatus(value) {
3786
- const normalized = String(value ?? "").trim().toLowerCase();
3787
- if (normalized === "running" || normalized === "completed" || normalized === "failed" || normalized === "skipped") {
3788
- return normalized;
3789
- }
3790
- return null;
3791
- }
3792
- function normalizeStepProgress(value) {
3793
- return {
3794
- ...optionalFiniteNumber(value.completed) !== void 0 ? { completed: optionalFiniteNumber(value.completed) } : {},
3795
- ...optionalFiniteNumber(value.total) !== void 0 ? { total: optionalFiniteNumber(value.total) } : {},
3796
- ...optionalFiniteNumber(value.failed) !== void 0 ? { failed: optionalFiniteNumber(value.failed) } : {},
3797
- ...optionalFiniteNumber(value.startedRows) !== void 0 ? { startedRows: optionalFiniteNumber(value.startedRows) } : {},
3798
- ...optionalFiniteNumber(value.activeRows) !== void 0 ? { activeRows: optionalFiniteNumber(value.activeRows) } : {},
3799
- ...optionalFiniteNumber(value.waitingRows) !== void 0 ? { waitingRows: optionalFiniteNumber(value.waitingRows) } : {},
3800
- ...optionalFiniteNumber(value.completedRows) !== void 0 ? { completedRows: optionalFiniteNumber(value.completedRows) } : {},
3801
- ...optionalFiniteNumber(value.supersededRows) !== void 0 ? { supersededRows: optionalFiniteNumber(value.supersededRows) } : {},
3802
- ...optionalString(value.message) ? { message: optionalString(value.message) } : {},
3803
- ...optionalNullableString(value.artifactTableNamespace) !== void 0 ? {
3804
- artifactTableNamespace: optionalNullableString(
3805
- value.artifactTableNamespace
3806
- )
3807
- } : {},
3808
- ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
3809
- ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
3810
- ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {},
3811
- ...normalizePlayDocflowNodeIoState(value.nodeIo) ? { nodeIo: normalizePlayDocflowNodeIoState(value.nodeIo) } : {}
3812
- };
3813
- }
3814
2804
 
3815
2805
  // ../play-runtime/run-lifecycle-policy.ts
3816
2806
  var TERMINAL_PLAY_RUN_STATUSES = /* @__PURE__ */ new Set([
@@ -3874,170 +2864,13 @@ function decodePlayRunPublicStatus(value) {
3874
2864
  }
3875
2865
 
3876
2866
  // ../play-runtime/log-provenance.ts
3877
- var LOG_PROVENANCE_CLASSES = [
3878
- "user",
3879
- "lifecycle",
3880
- "replay",
3881
- "infra",
3882
- "diagnostic",
3883
- "receipt"
3884
- ];
3885
2867
  var PROVENANCE_SENTINEL = "";
3886
2868
  var PROVENANCE_PREFIX = `${PROVENANCE_SENTINEL}prov:`;
3887
- function readProvenanceTag(line) {
3888
- if (!line.startsWith(PROVENANCE_PREFIX)) {
3889
- return { provenance: null, line };
3890
- }
3891
- const end = line.indexOf(PROVENANCE_SENTINEL, PROVENANCE_PREFIX.length);
3892
- if (end === -1) {
3893
- return { provenance: null, line };
3894
- }
3895
- const candidate = line.slice(PROVENANCE_PREFIX.length, end);
3896
- const provenance = LOG_PROVENANCE_CLASSES.includes(candidate) ? candidate : null;
3897
- return { provenance, line: line.slice(end + 1) };
3898
- }
3899
- function stripProvenanceTag(line) {
3900
- return readProvenanceTag(line).line;
3901
- }
3902
2869
 
3903
2870
  // ../play-runtime/run-snapshot-stream.ts
3904
- function normalizePlayRunLiveStatus(value) {
3905
- return normalizePlayRunLifecycleStatus(value);
3906
- }
3907
2871
  function isTerminalPlayRunLiveStatus(status) {
3908
2872
  return isTerminalPlayRunLifecycleStatus(status);
3909
2873
  }
3910
- function isRecord7(value) {
3911
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3912
- }
3913
- function finiteNumber2(value) {
3914
- return typeof value === "number" && Number.isFinite(value) ? value : null;
3915
- }
3916
- function extractTerminalRunLogTail(result) {
3917
- if (!isRecord7(result) || !isRecord7(result._metadata)) {
3918
- return null;
3919
- }
3920
- const runLogTail = result._metadata.runLogTail;
3921
- if (!isRecord7(runLogTail) || !Array.isArray(runLogTail.tail)) {
3922
- return null;
3923
- }
3924
- const logTail = runLogTail.tail.filter(
3925
- (line) => typeof line === "string" && line.trim().length > 0
3926
- );
3927
- if (logTail.length === 0) {
3928
- return null;
3929
- }
3930
- const totalLogCount = Math.max(
3931
- finiteNumber2(runLogTail.totalLogCount) ?? logTail.length,
3932
- logTail.length
3933
- );
3934
- return {
3935
- logTail: logTail.slice(-LOG_TAIL_LIMIT),
3936
- totalLogCount
3937
- };
3938
- }
3939
- function buildSnapshotFromLedger(snapshot) {
3940
- const nodeStates = snapshot.orderedStepIds.map((stepId) => snapshot.stepsById[stepId]).filter((step) => Boolean(step)).map((step) => ({
3941
- nodeId: step.stepId,
3942
- status: step.status,
3943
- error: step.error ?? null,
3944
- artifactTableNamespace: step.artifactTableNamespace ?? null,
3945
- progress: step.progress ? {
3946
- completed: step.progress.completed,
3947
- total: step.progress.total,
3948
- failed: step.progress.failed,
3949
- startedRows: step.progress.startedRows,
3950
- activeRows: step.progress.activeRows,
3951
- waitingRows: step.progress.waitingRows,
3952
- completedRows: step.progress.completedRows,
3953
- supersededRows: step.progress.supersededRows,
3954
- message: step.progress.message,
3955
- artifactTableNamespace: step.progress.artifactTableNamespace ?? step.artifactTableNamespace ?? null,
3956
- startedAt: step.startedAt ?? null,
3957
- completedAt: step.completedAt ?? null,
3958
- updatedAt: step.progress.updatedAt ?? step.updatedAt ?? null,
3959
- nodeIo: step.progress.nodeIo
3960
- } : null,
3961
- startedAt: step.startedAt ?? null,
3962
- completedAt: step.completedAt ?? null,
3963
- updatedAt: step.updatedAt ?? null,
3964
- ...step.progress?.nodeIo ? { nodeIo: step.progress.nodeIo } : {}
3965
- }));
3966
- const liveStatus = normalizePlayRunLiveStatus(snapshot.status);
3967
- const terminalRowOutcomes = isTerminalPlayRunLiveStatus(liveStatus) ? summarizeRunRowOutcomes(snapshot) : null;
3968
- const rowOutcomes = terminalRowOutcomes && (Object.keys(snapshot.stepsById).length > 0 || terminalRowOutcomes.totalRows > 0 || (terminalRowOutcomes.supersededRows ?? 0) > 0) ? terminalRowOutcomes : null;
3969
- const workCounters = snapshot.workCounters;
3970
- const calls = workCounters ? {
3971
- logicalCalls: workCounters.completedLogicalCalls + workCounters.logicalCalls,
3972
- providerRequests: workCounters.completedProviderRequests + workCounters.providerRequests,
3973
- provider429s: workCounters.completedProvider429s + workCounters.provider429s,
3974
- confirmed: isTerminalPlayRunLiveStatus(liveStatus) && hasConfirmedPlayRunLedgerWorkCounters(snapshot)
3975
- } : null;
3976
- return {
3977
- runId: snapshot.runId,
3978
- status: liveStatus,
3979
- createdAt: snapshot.createdAt ?? null,
3980
- startedAt: snapshot.startedAt ?? null,
3981
- finishedAt: snapshot.finishedAt ?? null,
3982
- durationMs: snapshot.durationMs ?? null,
3983
- updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
3984
- // This snapshot is public SDK/API transport. Keep the historical plain
3985
- // timestamp-prefixed text while provenance remains runtime metadata.
3986
- logs: snapshot.logTail.map(stripProvenanceTag),
3987
- totalLogCount: snapshot.totalLogCount,
3988
- ...snapshot.logsTruncated ? { logsTruncated: true } : {},
3989
- activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
3990
- resultTableNamespace: snapshot.resultTableNamespace ?? null,
3991
- datasets: snapshot.orderedDatasetIds.map((datasetId) => snapshot.datasetsById[datasetId]).filter(
3992
- (dataset) => Boolean(dataset)
3993
- ),
3994
- activities: snapshot.orderedActivityIds.map((activityId) => snapshot.activitiesById[activityId]).filter(
3995
- (activity) => Boolean(activity)
3996
- ),
3997
- activeActivity: snapshot.activeActivityId ? snapshot.activitiesById[snapshot.activeActivityId] ?? null : null,
3998
- nodeStates,
3999
- activeNodeId: snapshot.activeStepId ?? null,
4000
- ...rowOutcomes ? { rowOutcomes } : {},
4001
- ...calls && Object.values(calls).every(
4002
- (value) => typeof value === "boolean" || Number.isSafeInteger(value)
4003
- ) ? { calls } : {}
4004
- };
4005
- }
4006
- function buildPlayRunStatusSnapshot(input) {
4007
- const ledgerSnapshot = normalizePlayRunLedgerSnapshot(input.run.runSnapshot, {
4008
- runId: input.run.workflowId,
4009
- playName: input.run.name ?? null,
4010
- status: input.run.status,
4011
- createdAt: input.run.createdAt ?? null,
4012
- startedAt: input.run.startedAt ?? null,
4013
- updatedAt: input.run.updatedAt ?? null,
4014
- finishedAt: input.run.finishedAt ?? null
4015
- });
4016
- const terminalRunLogTail = extractTerminalRunLogTail(input.run.result);
4017
- const projectedSnapshot = terminalRunLogTail && terminalRunLogTail.totalLogCount > ledgerSnapshot.totalLogCount ? buildSnapshotFromLedger({
4018
- ...ledgerSnapshot,
4019
- logTail: terminalRunLogTail.logTail,
4020
- totalLogCount: terminalRunLogTail.totalLogCount
4021
- }) : buildSnapshotFromLedger(ledgerSnapshot);
4022
- const activity = projectPlayRunActivity({
4023
- runId: input.run.workflowId,
4024
- playName: input.run.name,
4025
- status: projectedSnapshot.status,
4026
- updatedAt: projectedSnapshot.updatedAt,
4027
- waitKind: input.run.waitKind ?? input.run.wait?.kind ?? null,
4028
- waitUntil: input.run.waitUntil,
4029
- eventKey: input.run.wait?.eventKey ?? null,
4030
- runtimeBackend: input.run.runtimeBackend,
4031
- activeNodeId: input.run.activeBoundaryId ?? projectedSnapshot.activeNodeId,
4032
- nodeStates: projectedSnapshot.nodeStates,
4033
- datasets: projectedSnapshot.datasets,
4034
- explicit: projectedSnapshot.activities
4035
- });
4036
- return {
4037
- ...projectedSnapshot,
4038
- activity
4039
- };
4040
- }
4041
2874
  function makeRunStreamEvent(input) {
4042
2875
  return {
4043
2876
  ...input,
@@ -4120,7 +2953,7 @@ function diffLogLines(input) {
4120
2953
  const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
4121
2954
  return {
4122
2955
  lines: [
4123
- `[stream] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
2956
+ `[watch] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
4124
2957
  ...logs
4125
2958
  ],
4126
2959
  lastLogSeq: totalLogCount,
@@ -4266,310 +3099,156 @@ function diffPlayRunStreamEvents(input) {
4266
3099
  return { events, next };
4267
3100
  }
4268
3101
 
4269
- // src/runs/observe-transport.ts
4270
- var RunObserveTransportUnavailableError = class extends Error {
4271
- constructor(message, reason) {
4272
- super(message);
4273
- this.reason = reason;
4274
- this.name = "RunObserveTransportUnavailableError";
3102
+ // src/stream-reconnect.ts
3103
+ function isTransientPlayRunReadError(error) {
3104
+ if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
3105
+ return true;
4275
3106
  }
4276
- };
4277
- var OBSERVE_BOOTSTRAP_TIMEOUT_MS = 1e4;
4278
- var OBSERVE_RECONNECT_NOTICE_MS = 1e4;
4279
- var OBSERVE_STALE_WARNING_MS = 12e4;
4280
- var OBSERVE_WATCHDOG_TICK_MS = 5e3;
4281
- var GRANT_REFRESH_MARGIN_MS = 5 * 6e4;
4282
- var BACKFILL_PAGE_LIMIT = 1e3;
4283
- var BACKFILL_MAX_PAGES = 30;
4284
- var OBSERVER_SNAPSHOT_QUERY = "runObservers:getPlayRunSnapshotForObserver";
4285
- var OBSERVER_LOG_PAGE_QUERY = "runObservers:getRunLogPageForObserver";
4286
- function errorText(error) {
4287
- return error instanceof Error ? error.message : String(error);
4288
- }
4289
- async function mintRunObserveGrant(http, runId) {
4290
- let response;
4291
- try {
4292
- response = await http.post(
4293
- `/api/v2/runs/${encodeURIComponent(runId)}/observe-grant`,
4294
- {}
4295
- );
4296
- } catch (error) {
4297
- if (error instanceof DeeplineError) {
4298
- if (error.statusCode === 401 || error.statusCode === 403) {
4299
- throw error;
4300
- }
4301
- throw new RunObserveTransportUnavailableError(
4302
- `observe-grant endpoint unavailable (${error.statusCode ?? "network"}): ${error.message}`,
4303
- "grant_endpoint_unavailable"
4304
- );
4305
- }
4306
- throw new RunObserveTransportUnavailableError(
4307
- `observe-grant request failed: ${errorText(error)}`,
4308
- "grant_request_failed"
4309
- );
3107
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
3108
+ return error.statusCode >= 500 && error.statusCode < 600;
3109
+ }
3110
+ const text = error instanceof Error ? error.message : String(error);
3111
+ return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
3112
+ text
3113
+ );
3114
+ }
3115
+
3116
+ // src/runs/watch-polling.ts
3117
+ var RUN_WATCH_POLL_INTERVAL_MS = 2e3;
3118
+ function waitForNextPoll(ms, signal) {
3119
+ if (signal?.aborted) {
3120
+ return Promise.resolve(false);
3121
+ }
3122
+ return new Promise((resolve2) => {
3123
+ const finish = (elapsed) => {
3124
+ clearTimeout(timeout);
3125
+ signal?.removeEventListener("abort", onAbort);
3126
+ resolve2(elapsed);
3127
+ };
3128
+ const onAbort = () => finish(false);
3129
+ const timeout = setTimeout(() => finish(true), ms);
3130
+ signal?.addEventListener("abort", onAbort, { once: true });
3131
+ });
3132
+ }
3133
+ function readSnapshotResponse(response, runId) {
3134
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
3135
+ throw new Error("Run watch returned an invalid snapshot response.");
4310
3136
  }
4311
- const grant = response;
4312
- if (!grant || typeof grant.convexUrl !== "string" || !grant.convexUrl.trim() || typeof grant.token !== "string" || !grant.token.trim() || typeof grant.expiresAt !== "number") {
4313
- throw new RunObserveTransportUnavailableError(
4314
- "observe-grant endpoint returned an invalid grant payload.",
4315
- "grant_payload_invalid"
4316
- );
3137
+ const record3 = response;
3138
+ const snapshot = record3.snapshot;
3139
+ if (record3.runId !== runId || !snapshot || typeof snapshot !== "object" || Array.isArray(snapshot) || snapshot.runId !== runId || typeof snapshot.status !== "string" || !Array.isArray(snapshot.logs) || !snapshot.logs.every(
3140
+ (line) => typeof line === "string"
3141
+ ) || typeof snapshot.totalLogCount !== "number" || !Array.isArray(snapshot.nodeStates)) {
3142
+ throw new Error(`Run watch returned an invalid snapshot for ${runId}.`);
4317
3143
  }
4318
- return grant;
3144
+ return { runId, snapshot };
4319
3145
  }
4320
- async function backfillLogGap(input) {
3146
+ async function backfillRotatedLogTail(input) {
3147
+ if (!input.previous.runSignature) {
3148
+ return input.snapshot;
3149
+ }
3150
+ const gap = resolvePlayRunLogGap(input.snapshot, input.previous.lastLogSeq);
3151
+ if (!gap) {
3152
+ return input.snapshot;
3153
+ }
4321
3154
  const lines = [];
4322
- let cursor = input.lastLogSeq;
4323
- for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
4324
- if (cursor >= input.tailFirstSeq - 1) {
4325
- break;
4326
- }
4327
- let logPage;
4328
- try {
4329
- logPage = await input.queryLogPage(
4330
- cursor,
4331
- Math.min(BACKFILL_PAGE_LIMIT, input.tailFirstSeq - 1 - cursor)
4332
- );
4333
- } catch {
4334
- return null;
4335
- }
4336
- const entries = (logPage?.entries ?? []).filter(
4337
- (entry) => entry.seq > cursor && entry.seq < input.tailFirstSeq
3155
+ let cursor = input.previous.lastLogSeq;
3156
+ const targetSeq = gap.tailFirstSeq - 1;
3157
+ while (cursor < targetSeq) {
3158
+ const limit = Math.min(1e3, targetSeq - cursor);
3159
+ const page = await input.http.get(
3160
+ `/api/v2/runs/${encodeURIComponent(input.runId)}/logs?afterSeq=${cursor}&limit=${limit}`,
3161
+ { signal: input.signal }
4338
3162
  );
4339
- if (entries.length === 0) {
4340
- break;
3163
+ if (!page || !Array.isArray(page.entries) || page.entries.length === 0) {
3164
+ return input.snapshot;
4341
3165
  }
4342
- for (const entry of entries) {
4343
- if (entry.seq !== cursor + 1) {
4344
- return null;
3166
+ for (const entry of page.entries) {
3167
+ if (!entry || entry.seq !== cursor + 1 || typeof entry.line !== "string") {
3168
+ return input.snapshot;
4345
3169
  }
4346
3170
  lines.push(entry.line);
4347
3171
  cursor = entry.seq;
4348
3172
  }
4349
3173
  }
4350
- if (cursor < input.tailFirstSeq - 1) {
4351
- return null;
3174
+ if (cursor !== targetSeq) {
3175
+ return input.snapshot;
4352
3176
  }
4353
- return lines;
3177
+ return { ...input.snapshot, logs: [...lines, ...input.snapshot.logs] };
4354
3178
  }
4355
- async function* observeRunEvents(options) {
4356
- const { http, runId } = options;
4357
- let grant = await mintRunObserveGrant(http, runId);
4358
- let convexBrowser;
4359
- let convexServer;
4360
- try {
4361
- convexBrowser = await import("convex/browser");
4362
- convexServer = await import("convex/server");
4363
- } catch (error) {
4364
- throw new RunObserveTransportUnavailableError(
4365
- `convex client module unavailable: ${errorText(error)}`,
4366
- "convex_module_unavailable"
4367
- );
4368
- }
4369
- let webSocketConstructor;
4370
- if (typeof WebSocket === "undefined") {
3179
+ async function* watchRunEvents(options) {
3180
+ const { http, runId, signal } = options;
3181
+ const path = `/api/v2/runs/${encodeURIComponent(runId)}?watchSnapshot=true`;
3182
+ const streamId = `play-run-watch:${runId}`;
3183
+ const pollIntervalMs = options.pollIntervalMs ?? RUN_WATCH_POLL_INTERVAL_MS;
3184
+ let previous = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
3185
+ let consecutiveFailures = 0;
3186
+ let outageNoticeSent = false;
3187
+ while (!signal?.aborted) {
3188
+ const pollStartedAt = Date.now();
3189
+ let snapshot;
4371
3190
  try {
4372
- const wsModuleName = "ws";
4373
- const ws = await import(wsModuleName);
4374
- webSocketConstructor = ws.default;
4375
- } catch (error) {
4376
- throw new RunObserveTransportUnavailableError(
4377
- `no WebSocket implementation available: ${errorText(error)}`,
4378
- "websocket_unavailable"
4379
- );
4380
- }
4381
- }
4382
- const snapshotQuery = convexServer.makeFunctionReference(
4383
- OBSERVER_SNAPSHOT_QUERY
4384
- );
4385
- const logPageQuery = convexServer.makeFunctionReference(
4386
- OBSERVER_LOG_PAGE_QUERY
4387
- );
4388
- const client = new convexBrowser.ConvexClient(grant.convexUrl, {
4389
- ...webSocketConstructor ? { webSocketConstructor } : {},
4390
- unsavedChangesWarning: false
4391
- });
4392
- const queue = [];
4393
- let wake = null;
4394
- const push = (item) => {
4395
- queue.push(item);
4396
- wake?.();
4397
- wake = null;
4398
- };
4399
- let lastForcedRefreshAt = 0;
4400
- client.setAuth(async ({ forceRefreshToken }) => {
4401
- const now = Date.now();
4402
- if (!forceRefreshToken && grant.expiresAt - now > GRANT_REFRESH_MARGIN_MS) {
4403
- return grant.token;
4404
- }
4405
- if (forceRefreshToken && now - lastForcedRefreshAt < 5e3) {
4406
- push({
4407
- kind: "error",
4408
- error: new DeeplineError(
4409
- `Run observe grant for ${runId} was rejected after a re-mint. The server and Convex deployment disagree on the grant issuer/JWKS.`,
4410
- 401,
4411
- "RUN_OBSERVE_GRANT_REJECTED"
4412
- )
3191
+ const response = await http.get(path, { signal });
3192
+ snapshot = await backfillRotatedLogTail({
3193
+ http,
3194
+ runId,
3195
+ snapshot: readSnapshotResponse(response, runId).snapshot,
3196
+ previous,
3197
+ signal
4413
3198
  });
4414
- return null;
4415
- }
4416
- if (forceRefreshToken) {
4417
- lastForcedRefreshAt = now;
4418
- }
4419
- try {
4420
- grant = await mintRunObserveGrant(http, runId);
4421
- return grant.token;
4422
3199
  } catch (error) {
4423
- push({ kind: "error", error });
4424
- return null;
4425
- }
4426
- });
4427
- const unsubscribe = client.onUpdate(
4428
- snapshotQuery,
4429
- { workflowId: runId },
4430
- (run) => push({ kind: "run", run: run ?? null }),
4431
- (error) => push({ kind: "error", error })
4432
- );
4433
- let lastUpdateAt = Date.now();
4434
- let lastStatusTerminal = false;
4435
- let disconnectedSince = null;
4436
- let warnedReconnecting = false;
4437
- let warnedStale = false;
4438
- const watchdog = setInterval(() => {
4439
- const now = Date.now();
4440
- try {
4441
- const connectionState = client.connectionState();
4442
- if (connectionState.isWebSocketConnected) {
4443
- disconnectedSince = null;
4444
- warnedReconnecting = false;
4445
- } else {
4446
- disconnectedSince ??= now;
4447
- if (!warnedReconnecting && now - disconnectedSince >= OBSERVE_RECONNECT_NOTICE_MS) {
4448
- warnedReconnecting = true;
4449
- options.onNotice?.(
4450
- `[observe] connection lost; reconnecting to live run ${runId}\u2026`
4451
- );
4452
- }
4453
- }
4454
- } catch {
4455
- }
4456
- if (!lastStatusTerminal && !warnedStale && now - lastUpdateAt >= OBSERVE_STALE_WARNING_MS) {
4457
- warnedStale = true;
4458
- options.onNotice?.(
4459
- `[observe] no live updates for ${Math.round((now - lastUpdateAt) / 1e3)}s; run ${runId} may be stalled (status checks continue)`
4460
- );
4461
- }
4462
- }, OBSERVE_WATCHDOG_TICK_MS);
4463
- watchdog.unref?.();
4464
- const abortListener = () => push({
4465
- kind: "error",
4466
- error: new DeeplineError(
4467
- "Run observation aborted.",
4468
- void 0,
4469
- "ABORTED"
4470
- )
4471
- });
4472
- options.signal?.addEventListener("abort", abortListener, { once: true });
4473
- let diffState = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
4474
- const streamId = ["observe", runId].join(":");
4475
- let sawFirstSnapshot = false;
4476
- try {
4477
- for (; ; ) {
4478
- if (queue.length === 0) {
4479
- const waitForItem = new Promise((resolve2) => {
4480
- wake = resolve2;
4481
- });
4482
- if (!sawFirstSnapshot) {
4483
- const timedOut = await Promise.race([
4484
- waitForItem.then(() => false),
4485
- new Promise(
4486
- (resolve2) => setTimeout(() => resolve2(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
4487
- )
4488
- ]);
4489
- if (timedOut && queue.length === 0) {
4490
- throw new RunObserveTransportUnavailableError(
4491
- `no snapshot from Convex at ${grant.convexUrl} within ${OBSERVE_BOOTSTRAP_TIMEOUT_MS}ms`,
4492
- "convex_unreachable"
4493
- );
4494
- }
4495
- } else {
4496
- await waitForItem;
4497
- }
4498
- continue;
4499
- }
4500
- const item = queue.shift();
4501
- if (item.kind === "error") {
4502
- if (options.signal?.aborted) {
4503
- return;
4504
- }
4505
- throw item.error;
3200
+ if (signal?.aborted) {
3201
+ return;
4506
3202
  }
4507
- sawFirstSnapshot = true;
4508
- lastUpdateAt = Date.now();
4509
- warnedStale = false;
4510
- if (item.run === null) {
4511
- throw new DeeplineError(
4512
- `Run ${runId} was not found (or is not visible to this grant).`,
4513
- 404,
4514
- "RUN_NOT_FOUND"
4515
- );
3203
+ if (!isTransientPlayRunReadError(error)) {
3204
+ throw error;
4516
3205
  }
4517
- const snapshot = buildPlayRunStatusSnapshot({ run: item.run });
4518
- lastStatusTerminal = isTerminalPlayRunLiveStatus(snapshot.status);
4519
- const gap = resolvePlayRunLogGap(snapshot, diffState.lastLogSeq);
4520
- if (gap && diffState.lastLogSeq > 0) {
4521
- const backfilled = await backfillLogGap({
4522
- queryLogPage: (afterSeq, limit) => client.query(logPageQuery, {
4523
- workflowId: runId,
4524
- afterSeq,
4525
- limit
4526
- }),
4527
- lastLogSeq: diffState.lastLogSeq,
4528
- tailFirstSeq: gap.tailFirstSeq
3206
+ consecutiveFailures += 1;
3207
+ const reason = error instanceof Error ? error.message : String(error);
3208
+ if (!outageNoticeSent) {
3209
+ options.onRetry?.({
3210
+ attempt: consecutiveFailures,
3211
+ delayMs: pollIntervalMs,
3212
+ reason
4529
3213
  });
4530
- if (backfilled && backfilled.length > 0) {
4531
- yield {
4532
- cursor: String(snapshot.updatedAt ?? Date.now()),
4533
- streamId,
4534
- scope: "play",
4535
- type: "play.run.log",
4536
- at: (/* @__PURE__ */ new Date()).toISOString(),
4537
- payload: {
4538
- runId: snapshot.runId,
4539
- lines: backfilled,
4540
- source: "worker",
4541
- firstSeq: diffState.lastLogSeq + 1,
4542
- totalLogCount: snapshot.totalLogCount
4543
- }
4544
- };
4545
- diffState = { ...diffState, lastLogSeq: gap.tailFirstSeq - 1 };
4546
- }
4547
- }
4548
- const { events, next } = diffPlayRunStreamEvents({
4549
- streamId,
4550
- snapshot,
4551
- previous: diffState
4552
- });
4553
- diffState = next;
4554
- const ordered = [
4555
- ...events.filter((event) => event.type === "play.run.snapshot"),
4556
- ...events.filter((event) => event.type !== "play.run.snapshot")
4557
- ];
4558
- for (const event of ordered) {
4559
- yield event;
3214
+ options.onNotice?.(
3215
+ `[watch] run snapshot request failed; retrying every ${pollIntervalMs / 1e3}s (${reason})`
3216
+ );
3217
+ outageNoticeSent = true;
4560
3218
  }
4561
- if (lastStatusTerminal) {
3219
+ const remaining2 = Math.max(
3220
+ 0,
3221
+ pollIntervalMs - (Date.now() - pollStartedAt)
3222
+ );
3223
+ if (!await waitForNextPoll(remaining2, signal)) {
4562
3224
  return;
4563
3225
  }
3226
+ continue;
4564
3227
  }
4565
- } finally {
4566
- clearInterval(watchdog);
4567
- options.signal?.removeEventListener("abort", abortListener);
4568
- try {
4569
- unsubscribe();
4570
- } catch {
3228
+ if (outageNoticeSent) {
3229
+ options.onNotice?.("[watch] run snapshot polling recovered");
3230
+ outageNoticeSent = false;
3231
+ }
3232
+ consecutiveFailures = 0;
3233
+ const diff = diffPlayRunStreamEvents({
3234
+ streamId,
3235
+ snapshot,
3236
+ previous
3237
+ });
3238
+ previous = diff.next;
3239
+ for (const event of diff.events) {
3240
+ yield event;
3241
+ }
3242
+ if (isTerminalPlayRunLiveStatus(snapshot.status)) {
3243
+ return;
3244
+ }
3245
+ const remaining = Math.max(
3246
+ 0,
3247
+ pollIntervalMs - (Date.now() - pollStartedAt)
3248
+ );
3249
+ if (!await waitForNextPoll(remaining, signal)) {
3250
+ return;
4571
3251
  }
4572
- await client.close().catch(() => void 0);
4573
3252
  }
4574
3253
  }
4575
3254
 
@@ -4921,7 +3600,7 @@ function requireTargetBillingIdempotencyKey(value) {
4921
3600
  }
4922
3601
  return normalized;
4923
3602
  }
4924
- function isRecord8(value) {
3603
+ function isRecord4(value) {
4925
3604
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4926
3605
  }
4927
3606
  function isPrebuiltPlayDescription(play) {
@@ -4953,10 +3632,10 @@ function normalizePlayStatus(raw) {
4953
3632
  return {
4954
3633
  ...raw,
4955
3634
  runId,
4956
- ...isRecord8(legacyOverview) ? {
3635
+ ...isRecord4(legacyOverview) ? {
4957
3636
  overview: legacyOverview
4958
3637
  } : {},
4959
- ...isRecord8(runStatus) ? { runStatus } : {},
3638
+ ...isRecord4(runStatus) ? { runStatus } : {},
4960
3639
  ...runPackage ? {
4961
3640
  package: runPackage,
4962
3641
  outputs: runPackage.outputs
@@ -5095,7 +3774,7 @@ function updatePlayLiveStatusState(state, event) {
5095
3774
  }
5096
3775
  const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : isPlayRunPackage(payload) ? payload.run.id : state.runId;
5097
3776
  const status = normalizeLiveStatus(payload.status) ?? (isPlayRunPackage(payload) ? normalizeLiveStatus(payload.run.status) : null) ?? state.status;
5098
- const progressPayload = isRecord8(payload.progress) ? payload.progress : {};
3777
+ const progressPayload = isRecord4(payload.progress) ? payload.progress : {};
5099
3778
  if (event.type === "play.run.final_status" && state.logs.length === 0 && state.lastLogSeq === 0) {
5100
3779
  const payloadLogs = readStringArray(payload.logs);
5101
3780
  const progressLogs = readStringArray(progressPayload.logs);
@@ -5174,6 +3853,7 @@ var DeeplineClient = class _DeeplineClient {
5174
3853
  status: (runId) => this.getRunLifecycleStatus(runId),
5175
3854
  list: (options2) => this.listRuns(options2),
5176
3855
  listPage: (options2) => this.listRunsPage(options2),
3856
+ watch: (runId, options2) => this.watchRunToTerminal(runId, options2),
5177
3857
  tail: (runId, options2) => this.tailRun(runId, options2),
5178
3858
  logs: (runId, options2) => this.getRunLogs(runId, options2),
5179
3859
  input: (runId) => this.getRunInput(runId),
@@ -5296,9 +3976,9 @@ var DeeplineClient = class _DeeplineClient {
5296
3976
  return fields.length > 0 ? { fields } : schema;
5297
3977
  }
5298
3978
  schemaMetadata(schema, key) {
5299
- if (!isRecord8(schema)) return null;
3979
+ if (!isRecord4(schema)) return null;
5300
3980
  const value = schema[key];
5301
- return isRecord8(value) ? value : null;
3981
+ return isRecord4(value) ? value : null;
5302
3982
  }
5303
3983
  playRunCommand(play, options) {
5304
3984
  const target = play.reference || play.name;
@@ -5353,7 +4033,7 @@ var DeeplineClient = class _DeeplineClient {
5353
4033
  aliases,
5354
4034
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
5355
4035
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
5356
- staticPipeline: isRecord8(play.staticPipeline) ? play.staticPipeline : isRecord8(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord8(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
4036
+ staticPipeline: isRecord4(play.staticPipeline) ? play.staticPipeline : isRecord4(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord4(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
5357
4037
  ...csvInput ? { csvInput } : {},
5358
4038
  ...rowOutputSchema ? { rowOutputSchema } : {},
5359
4039
  runCommand,
@@ -5713,15 +4393,17 @@ var DeeplineClient = class _DeeplineClient {
5713
4393
  return normalizePlayRunStart(response);
5714
4394
  }
5715
4395
  /**
5716
- * Start a play run and stream live runtime events from the same request.
4396
+ * Submit a play run through the legacy event response.
4397
+ *
4398
+ * This reports admission from the start request; it does not watch an
4399
+ * existing run. Use `startPlayRun()` and `watchRunEvents(runId)` separately.
5717
4400
  *
5718
- * Use this when a caller wants low-level event handling instead of submitting
5719
- * first and then connecting to `streamPlayRunEvents(runId)`.
4401
+ * @deprecated Use the separate start and watch methods.
5720
4402
  *
5721
4403
  * @param request - Play run configuration.
5722
- * @param options - Optional streaming options.
5723
- * @param options.signal - Optional abort signal for the streaming request.
5724
- * @returns Async stream of play-scoped live events.
4404
+ * @param options - Optional compatibility response options.
4405
+ * @param options.signal - Abort the start response.
4406
+ * @returns Async generator of play-scoped live events.
5725
4407
  */
5726
4408
  async *startPlayRunStream(request, options) {
5727
4409
  const integrationMode = resolvePlayRunIntegrationMode(request);
@@ -6203,11 +4885,23 @@ var DeeplineClient = class _DeeplineClient {
6203
4885
  );
6204
4886
  return normalizePlayStatus(response);
6205
4887
  }
4888
+ /** Poll authenticated run snapshots every two seconds and emit changed events. */
4889
+ async *watchRunEvents(workflowId, options) {
4890
+ for await (const event of watchRunEvents({
4891
+ http: this.http,
4892
+ runId: workflowId,
4893
+ signal: options?.signal,
4894
+ onNotice: options?.onNotice,
4895
+ onRetry: options?.onRetry
4896
+ })) {
4897
+ yield event;
4898
+ }
4899
+ }
6206
4900
  /**
6207
- * Stream semantic play-run events using the same SSE feed as the dashboard.
4901
+ * Read raw legacy SSE events for callers that explicitly require that
4902
+ * compatibility endpoint. Ordinary run watching uses REST snapshot polling.
6208
4903
  *
6209
- * The server emits a canonical `play.run.snapshot` event first for every
6210
- * connection, then incremental live events until terminal state or reconnect.
4904
+ * @deprecated Use `watchRunEvents` for ordinary run watching.
6211
4905
  */
6212
4906
  async *streamPlayRunEvents(workflowId, options) {
6213
4907
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -6222,6 +4916,68 @@ var DeeplineClient = class _DeeplineClient {
6222
4916
  }
6223
4917
  }
6224
4918
  }
4919
+ /**
4920
+ * Compatibility alias for polling canonical run snapshots and emitting
4921
+ * their changed events.
4922
+ *
4923
+ * @deprecated Use `watchRunEvents`; kept as a source-compatible alias.
4924
+ * The `fallback` option is ignored because watching uses REST polling.
4925
+ */
4926
+ async *observeRunEvents(runId, options) {
4927
+ yield* this.watchRunEvents(runId, {
4928
+ signal: options?.signal,
4929
+ onNotice: options?.onNotice
4930
+ });
4931
+ }
4932
+ /** Poll snapshots to terminal status and return the durable package. */
4933
+ async watchRunToTerminal(runId, options) {
4934
+ const state = {
4935
+ runId,
4936
+ status: "running",
4937
+ logs: [],
4938
+ lastLogSeq: 0,
4939
+ latest: null
4940
+ };
4941
+ let terminalObserved = false;
4942
+ for await (const event of this.watchRunEvents(runId, {
4943
+ signal: options?.signal,
4944
+ onNotice: options?.onNotice,
4945
+ onRetry: options?.onRetry ?? options?.onReconnect
4946
+ })) {
4947
+ options?.onEvent?.(event);
4948
+ const status = updatePlayLiveStatusState(state, event);
4949
+ if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
4950
+ terminalObserved = true;
4951
+ }
4952
+ }
4953
+ if (options?.signal?.aborted) {
4954
+ throw new DeeplineError("Run watch aborted.", void 0, "ABORTED");
4955
+ }
4956
+ if (terminalObserved) {
4957
+ return await this.getRunStatus(runId);
4958
+ }
4959
+ const refreshed = await this.getRunStatus(runId);
4960
+ if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) return refreshed;
4961
+ throw new DeeplineError(
4962
+ `Run watch for ${runId} ended before a terminal status.`,
4963
+ void 0,
4964
+ "PLAY_WATCH_ENDED"
4965
+ );
4966
+ }
4967
+ /**
4968
+ * Wait for a run and return its terminal status.
4969
+ *
4970
+ * This source-compatible alias uses the same authenticated two-second REST
4971
+ * snapshot polling as `client.runs.watch`.
4972
+ *
4973
+ * @deprecated Use `client.runs.watch`; retained for SDK compatibility.
4974
+ * @param runId - Public Deepline play-run id.
4975
+ * @param options - Optional callbacks and cancellation signal.
4976
+ * @returns The final durable run status.
4977
+ */
4978
+ async tailRun(runId, options) {
4979
+ return this.watchRunToTerminal(runId, options);
4980
+ }
6225
4981
  /**
6226
4982
  * Cancel a running play execution.
6227
4983
  *
@@ -6385,10 +5141,10 @@ var DeeplineClient = class _DeeplineClient {
6385
5141
  const responseRunStatus = isPlayRunPackage(response) ? response.runStatus : isPlayRunPackage(response.package) ? response.package.runStatus : response.runStatus;
6386
5142
  const responseOverview = isPlayRunPackage(response) ? response.overview : isPlayRunPackage(response.package) ? response.package.overview : response.overview;
6387
5143
  const status = normalizePlayStatus(response);
6388
- if (isRecord8(responseRunStatus)) {
5144
+ if (isRecord4(responseRunStatus)) {
6389
5145
  status.runStatus = responseRunStatus;
6390
5146
  }
6391
- if (isRecord8(responseOverview)) {
5147
+ if (isRecord4(responseOverview)) {
6392
5148
  status.overview = responseOverview;
6393
5149
  }
6394
5150
  if (options?.failedLogs !== true || status.status !== "failed") {
@@ -6487,222 +5243,6 @@ var DeeplineClient = class _DeeplineClient {
6487
5243
  );
6488
5244
  return { ...response, runs: response.runs ?? [] };
6489
5245
  }
6490
- /**
6491
- * Observe one run's live events. Uses the Convex Run Snapshot subscription
6492
- * transport first (ADR-0008), then falls back to the canonical SSE stream
6493
- * when the subscription transport or its optional client modules are not
6494
- * available. Pass `fallback: 'none'` to receive
6495
- * {@link RunObserveTransportUnavailableError} instead.
6496
- */
6497
- async *observeRunEvents(runId, options) {
6498
- let yieldedObserveEvent = false;
6499
- try {
6500
- for await (const event of observeRunEvents({
6501
- http: this.http,
6502
- runId,
6503
- signal: options?.signal,
6504
- onNotice: options?.onNotice
6505
- })) {
6506
- yieldedObserveEvent = true;
6507
- yield event;
6508
- }
6509
- } catch (error) {
6510
- if (!(error instanceof RunObserveTransportUnavailableError) || yieldedObserveEvent || options?.fallback === "none") {
6511
- throw error;
6512
- }
6513
- options?.onNotice?.(
6514
- `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
6515
- );
6516
- yield* this.streamPlayRunEventsUntilTerminal(runId, options);
6517
- }
6518
- }
6519
- async *streamPlayRunEventsUntilTerminal(runId, options) {
6520
- const state = {
6521
- runId,
6522
- status: "running",
6523
- logs: [],
6524
- lastLogSeq: 0,
6525
- latest: null
6526
- };
6527
- let lastEventId;
6528
- let reconnectAttempt = 0;
6529
- for (; ; ) {
6530
- if (options?.signal?.aborted) {
6531
- return;
6532
- }
6533
- const connectedAt = Date.now();
6534
- let sawEvent = false;
6535
- let endedReason = "stream window ended before a terminal event";
6536
- try {
6537
- for await (const event of this.streamPlayRunEvents(runId, {
6538
- mode: "cli",
6539
- signal: options?.signal,
6540
- ...lastEventId ? { lastEventId } : {}
6541
- })) {
6542
- sawEvent = true;
6543
- if (event.cursor?.trim()) {
6544
- lastEventId = event.cursor;
6545
- }
6546
- yield event;
6547
- const status = updatePlayLiveStatusState(state, event);
6548
- if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
6549
- return;
6550
- }
6551
- }
6552
- } catch (error) {
6553
- if (options?.signal?.aborted) {
6554
- return;
6555
- }
6556
- if (!isTransientPlayStreamError(error)) {
6557
- throw error;
6558
- }
6559
- endedReason = error instanceof Error ? error.message : String(error);
6560
- }
6561
- let refreshed = null;
6562
- try {
6563
- refreshed = await this.getRunStatus(runId);
6564
- } catch (error) {
6565
- if (!isTransientPlayStreamError(error)) {
6566
- throw error;
6567
- }
6568
- }
6569
- if (refreshed && TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6570
- yield {
6571
- cursor: String(Date.now()),
6572
- streamId: `sse-fallback:${runId}`,
6573
- scope: "play",
6574
- type: "play.run.status",
6575
- at: (/* @__PURE__ */ new Date()).toISOString(),
6576
- payload: refreshed
6577
- };
6578
- return;
6579
- }
6580
- if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
6581
- reconnectAttempt = 0;
6582
- }
6583
- const delayMs = streamReconnectDelayMs(reconnectAttempt);
6584
- reconnectAttempt += 1;
6585
- options?.onNotice?.(
6586
- `[observe] SSE tail window ended before terminal status (${endedReason}); reconnecting to run ${runId}`
6587
- );
6588
- await sleep2(delayMs);
6589
- }
6590
- }
6591
- /**
6592
- * Tail one run through the subscription transport until terminal, then
6593
- * return one durable REST status read (the final Run Response Package).
6594
- */
6595
- async tailRunViaObserveTransport(runId, options) {
6596
- const state = {
6597
- runId,
6598
- status: "running",
6599
- logs: [],
6600
- lastLogSeq: 0,
6601
- latest: null
6602
- };
6603
- for await (const event of this.observeRunEvents(runId, {
6604
- signal: options?.signal,
6605
- onNotice: options?.onNotice,
6606
- fallback: "none"
6607
- })) {
6608
- options?.onEvent?.(event);
6609
- const status = updatePlayLiveStatusState(state, event);
6610
- if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
6611
- continue;
6612
- }
6613
- return await this.getRunStatus(status.runId || runId);
6614
- }
6615
- if (options?.signal?.aborted) {
6616
- throw new DeeplineError("Run observation aborted.", void 0, "ABORTED");
6617
- }
6618
- const refreshed = await this.getRunStatus(runId);
6619
- if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6620
- return refreshed;
6621
- }
6622
- throw new DeeplineError(
6623
- `Run observation for ${runId} ended before a terminal status.`,
6624
- void 0,
6625
- "PLAY_LIVE_STREAM_ENDED"
6626
- );
6627
- }
6628
- /**
6629
- * Read the canonical run stream until a terminal run status is observed.
6630
- *
6631
- * Tries the Convex Run Snapshot subscription transport first (ADR-0008);
6632
- * when the server cannot serve it (grant endpoint missing/unconfigured or
6633
- * Convex unreachable) it falls back — with one `onNotice` message — to the
6634
- * support-window SSE stream below.
6635
- *
6636
- * Server stream windows are finite: they end cleanly at the function
6637
- * ceiling even while the run keeps executing. A window that ends (cleanly
6638
- * or via transient network error) without a terminal event triggers one
6639
- * durable-status re-check followed by a backed-off reconnect, so long runs
6640
- * tail to completion. Abort via `options.signal` to stop waiting.
6641
- */
6642
- async tailRun(runId, options) {
6643
- try {
6644
- return await this.tailRunViaObserveTransport(runId, options);
6645
- } catch (error) {
6646
- if (!(error instanceof RunObserveTransportUnavailableError)) {
6647
- throw error;
6648
- }
6649
- options?.onNotice?.(
6650
- `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
6651
- );
6652
- }
6653
- const state = {
6654
- runId,
6655
- status: "running",
6656
- logs: [],
6657
- lastLogSeq: 0,
6658
- latest: null
6659
- };
6660
- let reconnectAttempt = 0;
6661
- for (; ; ) {
6662
- const connectedAt = Date.now();
6663
- let sawEvent = false;
6664
- let terminalRunId = null;
6665
- let endedReason = "stream window ended before a terminal event";
6666
- try {
6667
- for await (const event of this.streamPlayRunEvents(runId, {
6668
- mode: "cli",
6669
- signal: options?.signal
6670
- })) {
6671
- options?.onEvent?.(event);
6672
- sawEvent = true;
6673
- const status = updatePlayLiveStatusState(state, event);
6674
- if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
6675
- continue;
6676
- }
6677
- terminalRunId = status.runId || runId;
6678
- break;
6679
- }
6680
- } catch (error) {
6681
- if (options?.signal?.aborted || !isTransientPlayStreamError(error)) {
6682
- throw error;
6683
- }
6684
- endedReason = error instanceof Error ? error.message : String(error);
6685
- }
6686
- if (terminalRunId) {
6687
- return await this.getRunStatus(terminalRunId);
6688
- }
6689
- const refreshed = await this.getRunStatus(runId);
6690
- if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6691
- return refreshed;
6692
- }
6693
- if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
6694
- reconnectAttempt = 0;
6695
- }
6696
- const delayMs = streamReconnectDelayMs(reconnectAttempt);
6697
- reconnectAttempt += 1;
6698
- options?.onReconnect?.({
6699
- attempt: reconnectAttempt,
6700
- delayMs,
6701
- reason: endedReason
6702
- });
6703
- await sleep2(delayMs);
6704
- }
6705
- }
6706
5246
  /** Get the exact original input retained for a run. This is intentionally separate from status. */
6707
5247
  async getRunInput(runId) {
6708
5248
  return this.http.get(`/api/v2/runs/${encodeURIComponent(runId)}/input`);
@@ -7493,10 +6033,10 @@ var DeeplineClient = class _DeeplineClient {
7493
6033
  // Plays — high-level orchestration
7494
6034
  // ——————————————————————————————————————————————————————————
7495
6035
  /**
7496
- * Run a play end-to-end: submit, stream until terminal, return result.
6036
+ * Run a play end-to-end: submit, poll progress until terminal, return result.
7497
6037
  *
7498
6038
  * This is the highest-level play execution method. It submits the play,
7499
- * reads the canonical run stream for status updates, and returns a structured
6039
+ * reads the canonical run snapshot for status updates, and returns a structured
7500
6040
  * result with logs and timing. Supports cancellation via `AbortSignal`.
7501
6041
  *
7502
6042
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
@@ -7552,6 +6092,7 @@ var DeeplineClient = class _DeeplineClient {
7552
6092
  lastLogSeq: 0,
7553
6093
  latest: null
7554
6094
  };
6095
+ let terminalObserved = null;
7555
6096
  if (options?.signal?.aborted) {
7556
6097
  await this.cancelPlay(workflowId);
7557
6098
  return {
@@ -7562,8 +6103,7 @@ var DeeplineClient = class _DeeplineClient {
7562
6103
  error: "Cancelled by user"
7563
6104
  };
7564
6105
  }
7565
- for await (const event of this.streamPlayRunEvents(workflowId, {
7566
- mode: "cli",
6106
+ for await (const event of this.watchRunEvents(workflowId, {
7567
6107
  signal: options?.signal
7568
6108
  })) {
7569
6109
  if (options?.signal?.aborted) {
@@ -7576,20 +6116,36 @@ var DeeplineClient = class _DeeplineClient {
7576
6116
  error: "Cancelled by user"
7577
6117
  };
7578
6118
  }
6119
+ const previousStatus = state.latest?.status;
7579
6120
  const status = updatePlayLiveStatusState(state, event);
7580
6121
  if (!status) {
7581
6122
  continue;
7582
6123
  }
7583
- options?.onProgress?.(status);
6124
+ if (event.type !== "play.run.status" || status.status !== previousStatus) {
6125
+ options?.onProgress?.(status);
6126
+ }
7584
6127
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
7585
- const finalStatus = await this.getPlayStatus(
7586
- status.runId || workflowId
7587
- ).catch(() => status);
7588
- return playRunResultFromStatus(finalStatus, start, workflowId);
6128
+ terminalObserved = status;
7589
6129
  }
7590
6130
  }
6131
+ if (options?.signal?.aborted) {
6132
+ await this.cancelPlay(workflowId);
6133
+ return {
6134
+ success: false,
6135
+ runId: workflowId,
6136
+ logs: state.logs,
6137
+ durationMs: Date.now() - start,
6138
+ error: "Cancelled by user"
6139
+ };
6140
+ }
6141
+ if (terminalObserved) {
6142
+ const finalStatus = await this.getPlayStatus(
6143
+ terminalObserved.runId || workflowId
6144
+ ).catch(() => terminalObserved);
6145
+ return playRunResultFromStatus(finalStatus, start, workflowId);
6146
+ }
7591
6147
  throw new DeeplineError(
7592
- `Run stream for ${workflowId} ended before the run reached a terminal state.`,
6148
+ `Run watch for ${workflowId} ended before the run reached a terminal state.`,
7593
6149
  void 0,
7594
6150
  "PLAY_RUN_STREAM_ENDED",
7595
6151
  { runId: workflowId, workflowId }
@@ -9227,7 +7783,7 @@ var TARGET_FALLBACK_KEYS = {
9227
7783
  status: { match: [/^email_status$/i, /^status$/i] },
9228
7784
  email_status: { match: [/^email_status$/i, /^status$/i] }
9229
7785
  };
9230
- function isRecord9(value) {
7786
+ function isRecord5(value) {
9231
7787
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9232
7788
  }
9233
7789
  function toV2RawToolOutputPath(path) {
@@ -9287,7 +7843,7 @@ function valuesAtSegments(current, segments, path = []) {
9287
7843
  if (!Array.isArray(current)) return [];
9288
7844
  return valuesAtSegments(current[segment], rest, [...path, segment]);
9289
7845
  }
9290
- if (!isRecord9(current)) return [];
7846
+ if (!isRecord5(current)) return [];
9291
7847
  const directMatches = valuesAtSegments(current[segment], rest, [
9292
7848
  ...path,
9293
7849
  segment
@@ -9317,7 +7873,7 @@ function getValuesAtPath(root, path) {
9317
7873
  return valuesAtSegments(root, parsePath(path)).map((entry) => entry.value);
9318
7874
  }
9319
7875
  function toResultEnvelope(value) {
9320
- if (isRecord9(value) && "data" in value) {
7876
+ if (isRecord5(value) && "data" in value) {
9321
7877
  const envelope = { data: value.data };
9322
7878
  if ("meta" in value) envelope.meta = value.meta;
9323
7879
  return envelope;
@@ -9370,7 +7926,7 @@ function getAtPath(root, path) {
9370
7926
  current = current[segment];
9371
7927
  continue;
9372
7928
  }
9373
- if (!isRecord9(current)) return void 0;
7929
+ if (!isRecord5(current)) return void 0;
9374
7930
  current = current[segment];
9375
7931
  }
9376
7932
  return current;
@@ -9387,7 +7943,7 @@ function normalizeString(value) {
9387
7943
  }
9388
7944
  function normalizeRows(value) {
9389
7945
  if (!Array.isArray(value)) return null;
9390
- return value.map((entry) => isRecord9(entry) ? entry : { value: entry });
7946
+ return value.map((entry) => isRecord5(entry) ? entry : { value: entry });
9391
7947
  }
9392
7948
  function findFirstTargetByPath(result, paths) {
9393
7949
  for (const path of paths ?? []) {
@@ -9457,7 +8013,7 @@ function findFirstTargetByKey(result, target, depth = 0, path = []) {
9457
8013
  }
9458
8014
  return null;
9459
8015
  }
9460
- if (!isRecord9(result)) return null;
8016
+ if (!isRecord5(result)) return null;
9461
8017
  const rule = TARGET_FALLBACK_KEYS[target] ?? {
9462
8018
  match: [new RegExp(`^${target}$`, "i")]
9463
8019
  };
@@ -9518,7 +8074,7 @@ function normalizeJobChangeStatus(value) {
9518
8074
  function firstExperienceDate(value) {
9519
8075
  if (!Array.isArray(value)) return null;
9520
8076
  for (const entry of value) {
9521
- if (!isRecord9(entry)) continue;
8077
+ if (!isRecord5(entry)) continue;
9522
8078
  const date = normalizeString(
9523
8079
  entry.start_date ?? entry.started_at ?? entry.startDate
9524
8080
  );
@@ -9527,10 +8083,10 @@ function firstExperienceDate(value) {
9527
8083
  return null;
9528
8084
  }
9529
8085
  function normalizeJobChange(value) {
9530
- const record3 = isRecord9(value) ? value : {};
9531
- const nested = isRecord9(record3.job_change) ? record3.job_change : record3;
9532
- const output = isRecord9(nested.output) ? nested.output : nested;
9533
- const person = isRecord9(output.person) ? output.person : {};
8086
+ const record3 = isRecord5(value) ? value : {};
8087
+ const nested = isRecord5(record3.job_change) ? record3.job_change : record3;
8088
+ const output = isRecord5(nested.output) ? nested.output : nested;
8089
+ const person = isRecord5(output.person) ? output.person : {};
9534
8090
  const status = normalizeJobChangeStatus(
9535
8091
  output.status ?? output.job_change_status ?? output.job_changed ?? output.changed
9536
8092
  );
@@ -9941,7 +8497,7 @@ function attachToolResultListDataset(result, input) {
9941
8497
  root = replaceAtPath(root, candidate, serialized.preview);
9942
8498
  break;
9943
8499
  }
9944
- if (isRecord9(root) && isRecord9(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, "raw")) {
8500
+ if (isRecord5(root) && isRecord5(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, "raw")) {
9945
8501
  result.toolResponse.raw = root.toolResponse.raw;
9946
8502
  result.toolOutput.raw = root.toolResponse.raw;
9947
8503
  }
@@ -9959,7 +8515,7 @@ function replaceAtPath(root, path, replacement) {
9959
8515
  copy[segment] = replace(copy[segment], index + 1);
9960
8516
  return copy;
9961
8517
  }
9962
- if (!isRecord9(current)) return current;
8518
+ if (!isRecord5(current)) return current;
9963
8519
  return {
9964
8520
  ...current,
9965
8521
  [segment]: replace(current[segment], index + 1)
@@ -11929,11 +10485,11 @@ var Deepline = class {
11929
10485
  return new DeeplineContext(options);
11930
10486
  }
11931
10487
  };
11932
- function isRecord10(value) {
10488
+ function isRecord6(value) {
11933
10489
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
11934
10490
  }
11935
10491
  function stringArrayRecord(value) {
11936
- if (!isRecord10(value)) return {};
10492
+ if (!isRecord6(value)) return {};
11937
10493
  return Object.fromEntries(
11938
10494
  Object.entries(value).map(([key, paths]) => [
11939
10495
  key,
@@ -11945,7 +10501,7 @@ function stringArray(value) {
11945
10501
  return Array.isArray(value) ? value.map(String) : [];
11946
10502
  }
11947
10503
  function emailStatusExtractorConfig(value) {
11948
- if (!isRecord10(value)) return void 0;
10504
+ if (!isRecord6(value)) return void 0;
11949
10505
  const readPaths = (key) => {
11950
10506
  const paths = stringArray(value[key]).map((path) => path.trim()).filter(Boolean);
11951
10507
  return paths.length > 0 ? paths : void 0;
@@ -11972,7 +10528,7 @@ function emailStatusExtractorConfig(value) {
11972
10528
  const paths = readPaths(key);
11973
10529
  if (paths) config[key] = paths;
11974
10530
  }
11975
- if (isRecord10(value.statusMap)) {
10531
+ if (isRecord6(value.statusMap)) {
11976
10532
  config.statusMap = value.statusMap;
11977
10533
  }
11978
10534
  if (Array.isArray(value.rules)) {
@@ -11981,10 +10537,10 @@ function emailStatusExtractorConfig(value) {
11981
10537
  return config;
11982
10538
  }
11983
10539
  function extractorDescriptorRecord(value) {
11984
- if (!isRecord10(value)) return {};
10540
+ if (!isRecord6(value)) return {};
11985
10541
  return Object.fromEntries(
11986
10542
  Object.entries(value).flatMap(([key, descriptor]) => {
11987
- if (!isRecord10(descriptor)) return [];
10543
+ if (!isRecord6(descriptor)) return [];
11988
10544
  const paths = stringArray(descriptor.paths).map((path) => path.trim()).filter(Boolean);
11989
10545
  if (paths.length === 0) return [];
11990
10546
  const transforms = stringArray(descriptor.transforms).map((transform) => transform.trim()).filter(Boolean);
@@ -12006,16 +10562,16 @@ function extractorDescriptorRecord(value) {
12006
10562
  }
12007
10563
  function rowsFromUnknown(value) {
12008
10564
  if (!Array.isArray(value)) return [];
12009
- return value.map((row) => isRecord10(row) ? row : { value: row });
10565
+ return value.map((row) => isRecord6(row) ? row : { value: row });
12010
10566
  }
12011
- function finiteNonNegativeInteger3(value) {
10567
+ function finiteNonNegativeInteger2(value) {
12012
10568
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
12013
10569
  return null;
12014
10570
  }
12015
10571
  return Math.floor(value);
12016
10572
  }
12017
10573
  function finitePositiveInteger(value) {
12018
- const integer = finiteNonNegativeInteger3(value);
10574
+ const integer = finiteNonNegativeInteger2(value);
12019
10575
  return integer !== null && integer > 0 ? integer : null;
12020
10576
  }
12021
10577
  function stableHash(value) {
@@ -12028,9 +10584,9 @@ function stableHash(value) {
12028
10584
  }
12029
10585
  function attachSdkQueryResultDatasetResult(toolId, result, options) {
12030
10586
  if (!options || !isQueryResultDatasetTool(toolId)) return result;
12031
- const raw = isRecord10(result.toolResponse.raw) ? result.toolResponse.raw : null;
12032
- const dataset = isRecord10(raw?.dataset) ? raw.dataset : null;
12033
- const totalRows = finiteNonNegativeInteger3(dataset?.total_rows);
10587
+ const raw = isRecord6(result.toolResponse.raw) ? result.toolResponse.raw : null;
10588
+ const dataset = isRecord6(raw?.dataset) ? raw.dataset : null;
10589
+ const totalRows = finiteNonNegativeInteger2(dataset?.total_rows);
12034
10590
  const sql = typeof options.input.sql === "string" ? options.input.sql : typeof options.input.query === "string" ? options.input.query : typeof raw?.sql === "string" ? raw.sql : null;
12035
10591
  if (!dataset || totalRows === null || !sql) {
12036
10592
  return result;
@@ -12061,7 +10617,7 @@ function attachSdkQueryResultDatasetResult(toolId, result, options) {
12061
10617
  } : {}
12062
10618
  }
12063
10619
  });
12064
- const pageRaw = isRecord10(response.toolResponse?.raw) ? response.toolResponse.raw : null;
10620
+ const pageRaw = isRecord6(response.toolResponse?.raw) ? response.toolResponse.raw : null;
12065
10621
  return rowsFromUnknown(pageRaw?.rows);
12066
10622
  };
12067
10623
  const collectRows = async (limit) => {
@@ -12118,8 +10674,8 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12118
10674
  const rawV2 = response.toolResponse?.rawV2;
12119
10675
  const view = response.toolResponse?.view;
12120
10676
  const meta = response.toolResponse?.meta;
12121
- const metadata = isRecord10(response._metadata) ? response._metadata.tool : null;
12122
- const toolMetadata = isRecord10(metadata) ? metadata : {};
10677
+ const metadata = isRecord6(response._metadata) ? response._metadata.tool : null;
10678
+ const toolMetadata = isRecord6(metadata) ? metadata : {};
12123
10679
  const result = attachSdkQueryResultDatasetResult(
12124
10680
  fallbackToolId,
12125
10681
  createToolExecuteResult({
@@ -12127,12 +10683,12 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12127
10683
  jobId: typeof response.job_id === "string" ? response.job_id : void 0,
12128
10684
  result: {
12129
10685
  data: raw,
12130
- ...isRecord10(meta) ? { meta } : {}
10686
+ ...isRecord6(meta) ? { meta } : {}
12131
10687
  },
12132
10688
  response: {
12133
10689
  ...rawV2 !== void 0 ? { rawV2 } : {},
12134
10690
  ...view === "data" || view === "rawV2" ? { view } : {},
12135
- ...isRecord10(meta) ? { meta } : {}
10691
+ ...isRecord6(meta) ? { meta } : {}
12136
10692
  },
12137
10693
  metadata: {
12138
10694
  toolId: typeof toolMetadata.toolId === "string" ? toolMetadata.toolId : fallbackToolId,
@@ -12148,12 +10704,12 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12148
10704
  cached: false,
12149
10705
  source: "live"
12150
10706
  },
12151
- meta: isRecord10(response.meta) ? response.meta : void 0,
10707
+ meta: isRecord6(response.meta) ? response.meta : void 0,
12152
10708
  billing: response.billing
12153
10709
  }),
12154
10710
  options
12155
10711
  );
12156
- const executionRecovery = isRecord10(response.executionRecovery) ? response.executionRecovery : void 0;
10712
+ const executionRecovery = isRecord6(response.executionRecovery) ? response.executionRecovery : void 0;
12157
10713
  if (typeof response.idempotencyKey === "string") {
12158
10714
  result.idempotencyKey = response.idempotencyKey;
12159
10715
  }
@@ -12290,6 +10846,15 @@ function getDefinedPlayMetadata(value) {
12290
10846
  return candidate;
12291
10847
  }
12292
10848
 
10849
+ // src/runs/observe-transport.ts
10850
+ var RunObserveTransportUnavailableError = class extends Error {
10851
+ constructor(message, reason) {
10852
+ super(message);
10853
+ this.reason = reason;
10854
+ this.name = "RunObserveTransportUnavailableError";
10855
+ }
10856
+ };
10857
+
12293
10858
  // src/monitors.ts
12294
10859
  function defineMonitor(definition) {
12295
10860
  if (!definition || typeof definition !== "object") {