deepline 0.3.237 → 0.3.238

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.js CHANGED
@@ -1267,7 +1267,7 @@ var SDK_RELEASE = {
1267
1267
  // getters keep their established compatibility behavior.
1268
1268
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1269
1269
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1270
- version: "0.3.237",
1270
+ version: "0.3.238",
1271
1271
  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.",
1272
1272
  packageCapabilities: {
1273
1273
  updatePreferences: 1
@@ -1879,19 +1879,19 @@ function withErrorCause(error, cause) {
1879
1879
  }
1880
1880
  return error;
1881
1881
  }
1882
- function mapNetworkError(baseUrl, error, targetLabel2) {
1882
+ function mapNetworkError(baseUrl, error, targetLabel) {
1883
1883
  const tagged = error;
1884
1884
  const timeoutMs = tagged?.[REQUEST_TIMEOUT_MARKER];
1885
1885
  if (typeof timeoutMs === "number") {
1886
1886
  return {
1887
- message: `Request to ${targetLabel2} timed out after ${timeoutMs}ms while waiting for a response.`,
1887
+ message: `Request to ${targetLabel} timed out after ${timeoutMs}ms while waiting for a response.`,
1888
1888
  code: "NETWORK_TIMEOUT",
1889
- details: { timeoutMs, target: targetLabel2 }
1889
+ details: { timeoutMs, target: targetLabel }
1890
1890
  };
1891
1891
  }
1892
1892
  if (tagged?.[REQUEST_ABORT_MARKER]) {
1893
1893
  return {
1894
- message: `Request to ${targetLabel2} was aborted before it completed.`,
1894
+ message: `Request to ${targetLabel} was aborted before it completed.`,
1895
1895
  code: "NETWORK_ABORTED"
1896
1896
  };
1897
1897
  }
@@ -2629,30 +2629,6 @@ var DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING = {
2629
2629
  windowMs: 36e5
2630
2630
  };
2631
2631
 
2632
- // src/stream-reconnect.ts
2633
- var STREAM_RECONNECT_BASE_DELAY_MS = 500;
2634
- var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
2635
- var STREAM_HEALTHY_CONNECTION_MS = 3e4;
2636
- function streamReconnectDelayMs(attempt) {
2637
- const cappedExponentialMs = Math.min(
2638
- STREAM_RECONNECT_MAX_DELAY_MS,
2639
- STREAM_RECONNECT_BASE_DELAY_MS * 2 ** Math.max(0, attempt)
2640
- );
2641
- return Math.max(1, Math.floor(Math.random() * (cappedExponentialMs + 1)));
2642
- }
2643
- function isTransientPlayStreamError(error) {
2644
- if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
2645
- return true;
2646
- }
2647
- if (error instanceof DeeplineError && typeof error.statusCode === "number") {
2648
- return error.statusCode >= 500 && error.statusCode < 600;
2649
- }
2650
- const text = error instanceof Error ? error.message : String(error);
2651
- 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(
2652
- text
2653
- );
2654
- }
2655
-
2656
2632
  // ../play-runtime/live-events.ts
2657
2633
  function resolveTimingWindow(input) {
2658
2634
  const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
@@ -2668,19 +2644,6 @@ function resolveTimingWindow(input) {
2668
2644
  // ../play-runtime/run-failure.ts
2669
2645
  var PUBLIC_FAILURE_TEXT_BYTE_LIMIT = 64 * 1024;
2670
2646
 
2671
- // ../play-runtime/run-terminal-source.ts
2672
- var PLAY_RUN_LEDGER_EVENT_SOURCES = [
2673
- "worker",
2674
- "convex",
2675
- "coordinator",
2676
- "system"
2677
- ];
2678
- function normalizePlayRunLedgerTerminalSource(value) {
2679
- return PLAY_RUN_LEDGER_EVENT_SOURCES.includes(
2680
- value
2681
- ) ? value : null;
2682
- }
2683
-
2684
2647
  // ../play-runtime/run-log-buffer.ts
2685
2648
  var RUNNER_LOG_MAX_BYTES = 5 * 1024 * 1024;
2686
2649
  var TERMINAL_RESERVE_BYTES = 64 * 1024;
@@ -2851,12 +2814,12 @@ function createSecretRedactionContext(initialValues = []) {
2851
2814
  }
2852
2815
  return output;
2853
2816
  }
2854
- function redactString2(value) {
2817
+ function redactString(value) {
2855
2818
  const output = redactRegisteredSecrets(value);
2856
2819
  return redactSecretLikeString(output);
2857
2820
  }
2858
2821
  function redact(value) {
2859
- if (typeof value === "string") return redactString2(value);
2822
+ if (typeof value === "string") return redactString(value);
2860
2823
  if (Array.isArray(value)) {
2861
2824
  let changed = false;
2862
2825
  const next = new Array(value.length);
@@ -2889,7 +2852,7 @@ function createSecretRedactionContext(initialValues = []) {
2889
2852
  return value;
2890
2853
  }
2891
2854
  function redactKnownSecrets(value) {
2892
- if (typeof value === "string") return redactString2(value);
2855
+ if (typeof value === "string") return redactString(value);
2893
2856
  if (Array.isArray(value)) {
2894
2857
  return value.map((entry) => redactKnownSecrets(entry));
2895
2858
  }
@@ -2908,7 +2871,7 @@ function createSecretRedactionContext(initialValues = []) {
2908
2871
  containsRegisteredSecret,
2909
2872
  matchesRegisteredSecret,
2910
2873
  redactRegisteredSecrets,
2911
- redactString: redactString2,
2874
+ redactString,
2912
2875
  redactKnownSecrets,
2913
2876
  redact
2914
2877
  };
@@ -2932,981 +2895,8 @@ var JSON_SIZE_LIMIT_REACHED = Symbol("JSON_SIZE_LIMIT_REACHED");
2932
2895
  var ledgerIngressRedactor = createSecretRedactionContext();
2933
2896
 
2934
2897
  // ../play-runtime/docflow-node-io.ts
2935
- var DOCFLOW_NODE_IO_LIMITS = {
2936
- maxPaths: 16,
2937
- maxDepth: 3,
2938
- maxObjectFields: 12,
2939
- maxArrayItems: 3,
2940
- maxStringBytes: 256,
2941
- maxPhaseBytes: 2e3,
2942
- maxErrorBytes: 512
2943
- };
2944
2898
  var utf8Encoder = new TextEncoder();
2945
2899
  var PLAY_DATASET_BRAND = Symbol.for("deepline.play.dataset");
2946
- var MAX_CONVEX_PREVIEW_KEY_LENGTH = 1024;
2947
- function ledgerSafeDocflowPreviewKey(key) {
2948
- const source = key.startsWith("$") ? key.replace(/^\$+/, "") || "output" : key || "output";
2949
- let encoded = "";
2950
- for (const character of source) {
2951
- const code = character.charCodeAt(0);
2952
- if (code >= 32 && code < 127) {
2953
- encoded += character;
2954
- continue;
2955
- }
2956
- for (const byte of utf8Encoder.encode(character)) {
2957
- encoded += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
2958
- }
2959
- }
2960
- if (encoded.length <= MAX_CONVEX_PREVIEW_KEY_LENGTH) return encoded;
2961
- let hashA = 2166136261;
2962
- let hashB = 2654435769;
2963
- for (let index = 0; index < encoded.length; index += 1) {
2964
- const code = encoded.charCodeAt(index);
2965
- hashA = Math.imul(hashA ^ code, 16777619);
2966
- hashB = Math.imul(hashB ^ code, 16777619);
2967
- }
2968
- const suffix = `~${(hashA >>> 0).toString(16).padStart(8, "0")}${(hashB >>> 0).toString(16).padStart(8, "0")}`;
2969
- return `${encoded.slice(0, MAX_CONVEX_PREVIEW_KEY_LENGTH - suffix.length)}${suffix}`;
2970
- }
2971
- function utf8Bytes(value) {
2972
- return utf8Encoder.encode(value).byteLength;
2973
- }
2974
- function truncateUtf8(value, maxBytes) {
2975
- if (utf8Bytes(value) <= maxBytes) return value;
2976
- let output = "";
2977
- for (const character of value) {
2978
- if (utf8Bytes(`${output}${character}\u2026`) > maxBytes) break;
2979
- output += character;
2980
- }
2981
- return `${output}\u2026`;
2982
- }
2983
- function redactString(redactor, value, key) {
2984
- if (!key) return redactor.redactString(value);
2985
- const wrapped = redactor.redact({ [key]: value });
2986
- return typeof wrapped[key] === "string" ? wrapped[key] : redactor.redactString(value);
2987
- }
2988
- function safeString(value, redactor, key) {
2989
- if (typeof value !== "string") return void 0;
2990
- return truncateUtf8(
2991
- redactString(redactor, value, key),
2992
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
2993
- );
2994
- }
2995
- function previewValue(value, input) {
2996
- if (value === null) return { kind: "null" };
2997
- if (value === void 0) return { kind: "unavailable", reason: "undefined" };
2998
- if (typeof value === "string") {
2999
- return {
3000
- kind: "scalar",
3001
- value: safeString(value, input.redactor, input.key) ?? ""
3002
- };
3003
- }
3004
- if (typeof value === "number") {
3005
- return Number.isFinite(value) ? { kind: "scalar", value } : { kind: "unavailable", reason: "unsupported" };
3006
- }
3007
- if (typeof value === "boolean") return { kind: "scalar", value };
3008
- if (typeof value === "bigint") {
3009
- return { kind: "unavailable", reason: "bigint" };
3010
- }
3011
- if (typeof value === "function") {
3012
- return { kind: "unavailable", reason: "function" };
3013
- }
3014
- if (typeof value === "symbol") {
3015
- return { kind: "unavailable", reason: "symbol" };
3016
- }
3017
- if (typeof value !== "object") {
3018
- return { kind: "unavailable", reason: "unsupported" };
3019
- }
3020
- try {
3021
- const brand = readDataProperty(value, PLAY_DATASET_BRAND);
3022
- const datasetKind = readDataProperty(value, "datasetKind");
3023
- const datasetId = readDataProperty(value, "datasetId");
3024
- const tableNamespace = readDataProperty(value, "tableNamespace");
3025
- if (brand.ok && brand.value === true && datasetKind.ok && (datasetKind.value === "csv" || datasetKind.value === "map") && datasetId.ok && typeof datasetId.value === "string") {
3026
- return {
3027
- kind: "dataset",
3028
- datasetId: safeString(datasetId.value, input.redactor, "datasetId") ?? "dataset",
3029
- datasetKind: datasetKind.value,
3030
- tableNamespace: tableNamespace.ok && tableNamespace.value === null ? null : tableNamespace.ok ? safeString(
3031
- tableNamespace.value,
3032
- input.redactor,
3033
- "tableNamespace"
3034
- ) : void 0
3035
- };
3036
- }
3037
- const kind = readDataProperty(value, "kind");
3038
- const count = readDataProperty(value, "count");
3039
- const preview = readDataProperty(value, "preview");
3040
- 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)) {
3041
- return {
3042
- kind: "dataset",
3043
- datasetId: safeString(datasetId.value, input.redactor, "datasetId") ?? "dataset",
3044
- datasetKind: datasetKind.value,
3045
- tableNamespace: tableNamespace.ok && tableNamespace.value === null ? null : tableNamespace.ok ? safeString(
3046
- tableNamespace.value,
3047
- input.redactor,
3048
- "tableNamespace"
3049
- ) : void 0,
3050
- rowCount: Math.max(0, Math.floor(count.value))
3051
- };
3052
- }
3053
- } catch {
3054
- return { kind: "unavailable", reason: "access_error" };
3055
- }
3056
- if (input.ancestors.has(value)) {
3057
- return { kind: "unavailable", reason: "cycle" };
3058
- }
3059
- if (input.depth >= DOCFLOW_NODE_IO_LIMITS.maxDepth) {
3060
- return { kind: "unavailable", reason: "depth" };
3061
- }
3062
- input.ancestors.add(value);
3063
- try {
3064
- if (Array.isArray(value)) {
3065
- const items = [];
3066
- for (let index = 0; index < Math.min(value.length, DOCFLOW_NODE_IO_LIMITS.maxArrayItems); index += 1) {
3067
- const descriptor = Object.getOwnPropertyDescriptor(
3068
- value,
3069
- String(index)
3070
- );
3071
- items.push(
3072
- descriptor && "value" in descriptor ? previewValue(descriptor.value, {
3073
- ...input,
3074
- depth: input.depth + 1
3075
- }) : { kind: "unavailable", reason: "access_error" }
3076
- );
3077
- }
3078
- return {
3079
- kind: "array",
3080
- items,
3081
- totalItems: value.length,
3082
- ...value.length > items.length ? { truncated: true } : {}
3083
- };
3084
- }
3085
- let descriptors;
3086
- try {
3087
- descriptors = Object.getOwnPropertyDescriptors(value);
3088
- } catch {
3089
- return { kind: "unavailable", reason: "access_error" };
3090
- }
3091
- const entries = Object.entries(descriptors).filter(
3092
- ([, descriptor]) => descriptor.enumerable
3093
- );
3094
- const selected = entries.slice(0, DOCFLOW_NODE_IO_LIMITS.maxObjectFields);
3095
- const fields = {};
3096
- for (const [key, descriptor] of selected) {
3097
- const safeKey = truncateUtf8(
3098
- ledgerSafeDocflowPreviewKey(input.redactor.redactString(key)),
3099
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
3100
- );
3101
- fields[safeKey] = "value" in descriptor ? previewValue(descriptor.value, {
3102
- ...input,
3103
- depth: input.depth + 1,
3104
- key
3105
- }) : { kind: "unavailable", reason: "access_error" };
3106
- }
3107
- return {
3108
- kind: "object",
3109
- fields,
3110
- totalFields: entries.length,
3111
- ...entries.length > selected.length ? { truncated: true } : {}
3112
- };
3113
- } catch {
3114
- return { kind: "unavailable", reason: "access_error" };
3115
- } finally {
3116
- input.ancestors.delete(value);
3117
- }
3118
- }
3119
- function readDataProperty(value, property) {
3120
- if ((typeof value !== "object" || value === null) && typeof value !== "function") {
3121
- return { ok: false };
3122
- }
3123
- try {
3124
- let owner = value;
3125
- while (owner) {
3126
- const descriptor = Object.getOwnPropertyDescriptor(owner, property);
3127
- if (descriptor) {
3128
- return "value" in descriptor ? { ok: true, value: descriptor.value } : { ok: false };
3129
- }
3130
- owner = Object.getPrototypeOf(owner);
3131
- }
3132
- } catch {
3133
- return { ok: false };
3134
- }
3135
- return { ok: false };
3136
- }
3137
- function buildPlayDocflowNodeErrorPreview(error, redactor = createSecretRedactionContext()) {
3138
- try {
3139
- const message = error instanceof Error ? error.message : String(error);
3140
- return truncateUtf8(
3141
- redactor.redactString(message),
3142
- DOCFLOW_NODE_IO_LIMITS.maxErrorBytes
3143
- );
3144
- } catch {
3145
- return "Non-Error value thrown (message unavailable)";
3146
- }
3147
- }
3148
- function isRecord4(value) {
3149
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3150
- }
3151
- function normalizePreview(value, depth) {
3152
- if (!isRecord4(value) || typeof value.kind !== "string") return null;
3153
- if (value.kind === "null") return { kind: "null" };
3154
- if (value.kind === "scalar") {
3155
- if (typeof value.value === "string" || typeof value.value === "boolean" || typeof value.value === "number" && Number.isFinite(value.value)) {
3156
- return previewValue(value.value, {
3157
- depth: 0,
3158
- ancestors: /* @__PURE__ */ new WeakSet(),
3159
- redactor: createSecretRedactionContext()
3160
- });
3161
- }
3162
- return null;
3163
- }
3164
- if (value.kind === "unavailable") {
3165
- const reason = value.reason;
3166
- return typeof reason === "string" && [
3167
- "undefined",
3168
- "function",
3169
- "symbol",
3170
- "bigint",
3171
- "cycle",
3172
- "depth",
3173
- "access_error",
3174
- "limit",
3175
- "unsupported"
3176
- ].includes(reason) ? {
3177
- kind: "unavailable",
3178
- reason
3179
- } : null;
3180
- }
3181
- if (value.kind === "dataset") {
3182
- const datasetId = safeString(
3183
- value.datasetId,
3184
- createSecretRedactionContext(),
3185
- "datasetId"
3186
- );
3187
- if (!datasetId || value.datasetKind !== "csv" && value.datasetKind !== "map") {
3188
- return null;
3189
- }
3190
- return {
3191
- kind: "dataset",
3192
- datasetId,
3193
- datasetKind: value.datasetKind,
3194
- ...value.tableNamespace === null ? { tableNamespace: null } : typeof value.tableNamespace === "string" ? {
3195
- tableNamespace: safeString(
3196
- value.tableNamespace,
3197
- createSecretRedactionContext(),
3198
- "tableNamespace"
3199
- )
3200
- } : {},
3201
- ...typeof value.rowCount === "number" && Number.isFinite(value.rowCount) ? { rowCount: Math.max(0, Math.floor(value.rowCount)) } : {}
3202
- };
3203
- }
3204
- if (depth >= DOCFLOW_NODE_IO_LIMITS.maxDepth) {
3205
- return { kind: "unavailable", reason: "depth" };
3206
- }
3207
- if (value.kind === "array" && Array.isArray(value.items)) {
3208
- const items = value.items.slice(0, DOCFLOW_NODE_IO_LIMITS.maxArrayItems).map((item) => normalizePreview(item, depth + 1)).filter((item) => item !== null);
3209
- const totalItems = typeof value.totalItems === "number" && Number.isFinite(value.totalItems) ? Math.max(items.length, Math.floor(value.totalItems)) : items.length;
3210
- return {
3211
- kind: "array",
3212
- items,
3213
- totalItems,
3214
- ...value.truncated === true || totalItems > items.length ? { truncated: true } : {}
3215
- };
3216
- }
3217
- if (value.kind === "object" && isRecord4(value.fields)) {
3218
- const fields = {};
3219
- for (const [key, field] of Object.entries(value.fields).slice(
3220
- 0,
3221
- DOCFLOW_NODE_IO_LIMITS.maxObjectFields
3222
- )) {
3223
- const normalized = normalizePreview(field, depth + 1);
3224
- if (normalized) fields[ledgerSafeDocflowPreviewKey(key)] = normalized;
3225
- }
3226
- const totalFields = typeof value.totalFields === "number" && Number.isFinite(value.totalFields) ? Math.max(Object.keys(fields).length, Math.floor(value.totalFields)) : Object.keys(fields).length;
3227
- return {
3228
- kind: "object",
3229
- fields,
3230
- totalFields,
3231
- ...value.truncated === true || totalFields > Object.keys(fields).length ? { truncated: true } : {}
3232
- };
3233
- }
3234
- return null;
3235
- }
3236
- function normalizePlayDocflowNodeIoState(value) {
3237
- if (!isRecord4(value)) return void 0;
3238
- const attempt = typeof value.attempt === "number" && Number.isFinite(value.attempt) ? Math.max(0, Math.floor(value.attempt)) : 0;
3239
- const normalizeMap = (raw) => {
3240
- if (!isRecord4(raw)) return void 0;
3241
- const output = {};
3242
- for (const [path, preview] of Object.entries(raw).slice(
3243
- 0,
3244
- DOCFLOW_NODE_IO_LIMITS.maxPaths
3245
- )) {
3246
- const normalized = normalizePreview(preview, 0);
3247
- if (!normalized) continue;
3248
- const safePath = ledgerSafeDocflowPreviewKey(path);
3249
- const candidate = { ...output, [safePath]: normalized };
3250
- if (utf8Bytes(JSON.stringify(candidate)) > DOCFLOW_NODE_IO_LIMITS.maxPhaseBytes) {
3251
- break;
3252
- }
3253
- output[safePath] = normalized;
3254
- }
3255
- return output;
3256
- };
3257
- const inputs = normalizeMap(value.inputs);
3258
- const outputs = normalizeMap(value.outputs);
3259
- const error = typeof value.error === "string" ? buildPlayDocflowNodeErrorPreview(value.error) : value.error === null ? null : void 0;
3260
- return {
3261
- attempt,
3262
- ...typeof value.invocationId === "string" && value.invocationId.trim() ? {
3263
- invocationId: truncateUtf8(
3264
- value.invocationId.trim(),
3265
- DOCFLOW_NODE_IO_LIMITS.maxStringBytes
3266
- )
3267
- } : {},
3268
- ...inputs ? { inputs } : {},
3269
- ...outputs ? { outputs } : {},
3270
- ...value.inputsTruncated === true ? { inputsTruncated: true } : {},
3271
- ...value.outputsTruncated === true ? { outputsTruncated: true } : {},
3272
- ...error !== void 0 ? { error } : {}
3273
- };
3274
- }
3275
-
3276
- // ../play-runtime/activity-observation.ts
3277
- function isRecord5(value) {
3278
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3279
- }
3280
- function isPlayActivityObservation(value) {
3281
- 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)) {
3282
- return false;
3283
- }
3284
- const targetKinds = /* @__PURE__ */ new Set([
3285
- "provider",
3286
- "dataset",
3287
- "lease",
3288
- "runner",
3289
- "step",
3290
- "capacity"
3291
- ]);
3292
- const stateKinds = /* @__PURE__ */ new Set([
3293
- "active",
3294
- "queued",
3295
- "waiting",
3296
- "retrying",
3297
- "scheduled",
3298
- "completed",
3299
- "failed"
3300
- ]);
3301
- return typeof value.target.kind === "string" && targetKinds.has(value.target.kind) && typeof value.state.kind === "string" && stateKinds.has(value.state.kind);
3302
- }
3303
- function derivePlayActivityVisibility(input) {
3304
- const { state } = input.observation;
3305
- if (state.kind === "failed") return "action";
3306
- if (state.kind === "waiting") {
3307
- return state.reason.kind === "external_event" ? "action" : "headline";
3308
- }
3309
- if (state.kind === "queued" || state.kind === "scheduled") return "headline";
3310
- if (state.kind === "retrying") {
3311
- if (state.reason === "rate_limit" && input.observation.target.kind === "capacity") {
3312
- return "headline";
3313
- }
3314
- if (state.retryAt - input.now <= 3e4) return "hidden";
3315
- return state.reason === "rate_limit" ? "headline" : "timeline";
3316
- }
3317
- if (state.kind === "active") return "headline";
3318
- return "timeline";
3319
- }
3320
- function targetLabel(target) {
3321
- switch (target.kind) {
3322
- case "provider":
3323
- return target.label ?? `${target.provider} ${target.operation}`;
3324
- case "dataset":
3325
- return target.label ?? `dataset ${target.tableNamespace}`;
3326
- case "lease":
3327
- return `${target.resourceKind} ${target.resourceLabel}`;
3328
- case "runner":
3329
- return target.label ?? `${target.backend} runner`;
3330
- case "step":
3331
- return target.label ?? target.stepId;
3332
- case "capacity":
3333
- return target.label ?? `${target.pool} capacity`;
3334
- }
3335
- }
3336
- function summarizePlayActivity(observation) {
3337
- const label = targetLabel(observation.target);
3338
- const { state } = observation;
3339
- switch (state.kind) {
3340
- case "active": {
3341
- const progress = state.progress;
3342
- const counts = typeof progress?.completed === "number" && typeof progress.total === "number" ? ` (${progress.completed}/${progress.total})` : "";
3343
- return `Active: ${label}${counts}.`;
3344
- }
3345
- case "queued":
3346
- return `Queued: waiting for ${label}.`;
3347
- case "waiting":
3348
- if (state.reason.kind === "external_event") {
3349
- const provider = state.reason.provider ? ` from ${state.reason.provider}` : "";
3350
- return `Blocked: waiting for an external event${provider}.`;
3351
- }
3352
- if (state.reason.kind === "dataset") {
3353
- return `Blocked: waiting for dataset ${state.reason.tableNamespace} to become ${state.reason.requiredPhase}.`;
3354
- }
3355
- if (state.reason.kind === "lease") {
3356
- return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
3357
- }
3358
- return `Paused: ${label}; the legacy producer did not report a typed reason.`;
3359
- case "retrying":
3360
- if (state.reason === "rate_limit") {
3361
- return `Waiting for ${label} request capacity; the run will retry automatically.`;
3362
- }
3363
- return `Delayed: ${label} will retry automatically.`;
3364
- case "scheduled":
3365
- return `Scheduled: ${label} will resume automatically.`;
3366
- case "completed":
3367
- return `Completed: ${label}.`;
3368
- case "failed":
3369
- return `Failed: ${label}.`;
3370
- }
3371
- }
3372
- function isTerminalActivityState(state) {
3373
- return state.kind === "completed" || state.kind === "failed";
3374
- }
3375
- function projection(observation, now, classified = true) {
3376
- return {
3377
- observation,
3378
- summary: summarizePlayActivity(observation),
3379
- visibility: derivePlayActivityVisibility({ observation, now }),
3380
- classified
3381
- };
3382
- }
3383
- function projectPlayRunActivity(input) {
3384
- const now = input.now ?? Date.now();
3385
- const observedAt = input.updatedAt ?? now;
3386
- const normalizedStatus = input.status.trim().toLowerCase();
3387
- if (normalizedStatus === "completed" || normalizedStatus === "failed" || normalizedStatus === "cancelled" || normalizedStatus === "terminated" || normalizedStatus === "timed_out") {
3388
- return null;
3389
- }
3390
- const latestExplicit = [...input.explicit ?? []].filter((candidate) => !isTerminalActivityState(candidate.state)).sort((left, right) => right.observedAt - left.observedAt)[0];
3391
- const explicit = latestExplicit?.target.kind === "capacity" && latestExplicit.target.pool === "provider" && latestExplicit.state.kind === "retrying" && latestExplicit.state.retryAt < now ? null : latestExplicit;
3392
- if (explicit) return projection(explicit, now);
3393
- if (input.waitKind === "detached_runner") {
3394
- return projection(
3395
- {
3396
- activityId: "runner:managed",
3397
- target: {
3398
- kind: "runner",
3399
- backend: input.runtimeBackend ?? "managed",
3400
- label: "managed play runner"
3401
- },
3402
- state: { kind: "active" },
3403
- observedAt
3404
- },
3405
- now
3406
- );
3407
- }
3408
- if (input.waitKind === "sleep") {
3409
- return projection(
3410
- {
3411
- activityId: "run:sleep",
3412
- target: {
3413
- kind: "step",
3414
- stepId: input.activeNodeId ?? "run",
3415
- label: input.playName ?? "play"
3416
- },
3417
- state: {
3418
- kind: "scheduled",
3419
- reason: "sleep",
3420
- resumeAt: input.waitUntil ?? observedAt
3421
- },
3422
- observedAt
3423
- },
3424
- now
3425
- );
3426
- }
3427
- if (input.waitKind === "integration_event" || input.waitKind === "integration_event_batch") {
3428
- return projection(
3429
- {
3430
- activityId: `event:${input.activeNodeId ?? "run"}`,
3431
- stepId: input.activeNodeId ?? void 0,
3432
- target: {
3433
- kind: "step",
3434
- stepId: input.activeNodeId ?? "run",
3435
- label: input.playName ?? "play"
3436
- },
3437
- state: {
3438
- kind: "waiting",
3439
- reason: {
3440
- kind: "external_event",
3441
- eventKey: input.eventKey ?? void 0,
3442
- deadlineAt: input.waitUntil ?? void 0
3443
- }
3444
- },
3445
- observedAt
3446
- },
3447
- now
3448
- );
3449
- }
3450
- if (normalizedStatus === "waiting") {
3451
- return projection(
3452
- {
3453
- activityId: "run:legacy-wait",
3454
- target: {
3455
- kind: "step",
3456
- stepId: input.activeNodeId ?? "run",
3457
- label: input.playName ?? "play"
3458
- },
3459
- state: { kind: "waiting", reason: { kind: "unknown" } },
3460
- observedAt
3461
- },
3462
- now,
3463
- false
3464
- );
3465
- }
3466
- const pendingDataset = [...input.datasets ?? []].filter(
3467
- (dataset) => dataset.phase === "registered" || dataset.complete !== true && dataset.phase !== "failed"
3468
- ).sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
3469
- if (pendingDataset) {
3470
- const activeDatasetStep = input.nodeStates?.find(
3471
- (candidate) => candidate.nodeId === input.activeNodeId && candidate.status === "running" && candidate.artifactTableNamespace === pendingDataset.tableNamespace
3472
- );
3473
- const datasetStep = activeDatasetStep ?? input.nodeStates?.find(
3474
- (candidate) => candidate.artifactTableNamespace === pendingDataset.tableNamespace
3475
- );
3476
- const datasetProgress = datasetStep?.progress ?? null;
3477
- return projection(
3478
- {
3479
- activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
3480
- target: {
3481
- kind: "dataset",
3482
- tableNamespace: pendingDataset.tableNamespace,
3483
- operation: "materializing"
3484
- },
3485
- state: {
3486
- kind: "active",
3487
- progress: {
3488
- completed: datasetProgress?.completed ?? pendingDataset.persistedRows,
3489
- total: datasetProgress?.total ?? (typeof pendingDataset.succeededRows === "number" || typeof pendingDataset.failedRows === "number" ? (pendingDataset.succeededRows ?? 0) + (pendingDataset.failedRows ?? 0) : void 0),
3490
- failed: datasetProgress?.failed ?? pendingDataset.failedRows
3491
- }
3492
- },
3493
- observedAt: datasetStep?.updatedAt ?? pendingDataset.updatedAt ?? observedAt
3494
- },
3495
- now
3496
- );
3497
- }
3498
- const activeStep = input.nodeStates?.find(
3499
- (candidate) => candidate.nodeId === input.activeNodeId
3500
- ) ?? input.nodeStates?.find((candidate) => candidate.status === "running") ?? null;
3501
- if (activeStep) {
3502
- const toolId = activeStep.nodeId.startsWith("tool:") ? activeStep.nodeId.split(":").at(-1)?.trim() || null : null;
3503
- const provider = toolId?.split(/[._]/)[0]?.trim() || null;
3504
- return projection(
3505
- {
3506
- activityId: `step:${activeStep.nodeId}`,
3507
- stepId: activeStep.nodeId,
3508
- target: toolId && provider ? {
3509
- kind: "provider",
3510
- provider,
3511
- operation: toolId,
3512
- label: activeStep.label ?? toolId
3513
- } : {
3514
- kind: "step",
3515
- stepId: activeStep.nodeId,
3516
- label: activeStep.label
3517
- },
3518
- state: {
3519
- kind: "active",
3520
- progress: activeStep.progress ?? void 0
3521
- },
3522
- observedAt: activeStep.updatedAt ?? observedAt
3523
- },
3524
- now
3525
- );
3526
- }
3527
- if (normalizedStatus === "queued") {
3528
- return projection(
3529
- {
3530
- activityId: "capacity:worker",
3531
- target: { kind: "capacity", pool: "worker" },
3532
- state: { kind: "queued", reason: "capacity" },
3533
- observedAt
3534
- },
3535
- now
3536
- );
3537
- }
3538
- const fallback = {
3539
- activityId: "run:legacy",
3540
- target: {
3541
- kind: "step",
3542
- stepId: input.activeNodeId ?? "run",
3543
- label: input.playName ?? "play"
3544
- },
3545
- state: normalizedStatus === "waiting" ? { kind: "waiting", reason: { kind: "unknown" } } : { kind: "active" },
3546
- observedAt
3547
- };
3548
- return projection(fallback, now, normalizedStatus !== "waiting");
3549
- }
3550
-
3551
- // ../play-runtime/cell-provenance.ts
3552
- function finiteNonNegativeInteger2(value) {
3553
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
3554
- return null;
3555
- }
3556
- return Math.trunc(value);
3557
- }
3558
- function nonEmptyString(value) {
3559
- if (typeof value !== "string") return null;
3560
- const trimmed = value.trim();
3561
- return trimmed ? trimmed : null;
3562
- }
3563
- function normalizeDatasetBornFrom(value) {
3564
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3565
- const record3 = value;
3566
- const table = nonEmptyString(record3.table);
3567
- const rowCountIn = finiteNonNegativeInteger2(record3.rowCountIn);
3568
- if (!table || rowCountIn === null) return null;
3569
- return { table, rowCountIn };
3570
- }
3571
-
3572
- // ../play-runtime/run-ledger.ts
3573
- var LOG_TAIL_LIMIT = 100;
3574
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
3575
- "completed",
3576
- "failed",
3577
- "cancelled",
3578
- "terminated",
3579
- "timed_out"
3580
- ]);
3581
- function isRecord6(value) {
3582
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3583
- }
3584
- function finiteNumber(value) {
3585
- return typeof value === "number" && Number.isFinite(value) ? value : null;
3586
- }
3587
- function nonnegativeSafeInteger(value) {
3588
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3589
- }
3590
- function isWorkCounterSegmentId(value) {
3591
- 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(
3592
- value
3593
- );
3594
- }
3595
- function normalizePlayRunLedgerTerminalWorkCounterSegment(value) {
3596
- if (!isRecord6(value)) return void 0;
3597
- if (!nonnegativeSafeInteger(value.attempt) || !nonnegativeSafeInteger(value.invocationSeq) || value.invocationSeq === 0 || !isWorkCounterSegmentId(value.segmentId)) {
3598
- return void 0;
3599
- }
3600
- return {
3601
- attempt: value.attempt,
3602
- invocationSeq: value.invocationSeq,
3603
- segmentId: value.segmentId
3604
- };
3605
- }
3606
- function hasConfirmedPlayRunLedgerWorkCounters(snapshot) {
3607
- const counters = snapshot.workCounters;
3608
- const terminal = snapshot.terminalWorkCounterSegment;
3609
- return Boolean(
3610
- 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(
3611
- counters.completedLogicalCalls + counters.logicalCalls
3612
- ) && Number.isSafeInteger(
3613
- counters.completedProviderRequests + counters.providerRequests
3614
- ) && Number.isSafeInteger(
3615
- counters.completedProvider429s + counters.provider429s
3616
- )
3617
- );
3618
- }
3619
- function normalizeWorkCounters(value) {
3620
- if (!isRecord6(value)) return void 0;
3621
- for (const field of [
3622
- "attempt",
3623
- "invocationSeq",
3624
- "logicalCalls",
3625
- "providerRequests",
3626
- "provider429s",
3627
- "completedLogicalCalls",
3628
- "completedProviderRequests",
3629
- "completedProvider429s"
3630
- ]) {
3631
- if (!nonnegativeSafeInteger(value[field])) return void 0;
3632
- }
3633
- if (value.invocationSeq === 0 || !isWorkCounterSegmentId(value.segmentId) || typeof value.final !== "boolean" || typeof value.completeThroughAttempt !== "boolean" || typeof value.consistent !== "boolean") {
3634
- return void 0;
3635
- }
3636
- return {
3637
- ...value,
3638
- attempt: value.attempt,
3639
- invocationSeq: value.invocationSeq,
3640
- segmentId: value.segmentId,
3641
- logicalCalls: value.logicalCalls,
3642
- providerRequests: value.providerRequests,
3643
- provider429s: value.provider429s,
3644
- final: value.final,
3645
- completedLogicalCalls: value.completedLogicalCalls,
3646
- completedProviderRequests: value.completedProviderRequests,
3647
- completedProvider429s: value.completedProvider429s,
3648
- completeThroughAttempt: value.completeThroughAttempt,
3649
- consistent: value.consistent
3650
- };
3651
- }
3652
- function optionalFiniteNumber(value) {
3653
- const normalized = finiteNumber(value);
3654
- return normalized === null ? void 0 : normalized;
3655
- }
3656
- function optionalString(value) {
3657
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
3658
- }
3659
- function optionalNullableString(value) {
3660
- if (value === null) return null;
3661
- return optionalString(value);
3662
- }
3663
- function normalizePlayRunLedgerStatus(value) {
3664
- const normalized = String(value ?? "").trim().toLowerCase();
3665
- switch (normalized) {
3666
- case "queued":
3667
- case "pending":
3668
- return "queued";
3669
- case "running":
3670
- case "started":
3671
- return "running";
3672
- case "waiting":
3673
- return "waiting";
3674
- case "completed":
3675
- case "complete":
3676
- case "succeeded":
3677
- return "completed";
3678
- case "failed":
3679
- case "error":
3680
- return "failed";
3681
- case "cancelled":
3682
- case "canceled":
3683
- return "cancelled";
3684
- case "terminated":
3685
- return "terminated";
3686
- case "timed_out":
3687
- case "timeout":
3688
- return "timed_out";
3689
- default:
3690
- return "unknown";
3691
- }
3692
- }
3693
- function isTerminalPlayRunLedgerStatus(status) {
3694
- return TERMINAL_STATUSES.has(status);
3695
- }
3696
- function summarizeRunRowOutcomes(snapshot) {
3697
- let completedRows = 0;
3698
- let failedRows = 0;
3699
- let totalRows = 0;
3700
- let supersededRows = 0;
3701
- for (const step of Object.values(snapshot.stepsById)) {
3702
- const progress = step.progress;
3703
- if (!progress) continue;
3704
- completedRows += Math.max(0, finiteNumber(progress.completed) ?? 0);
3705
- failedRows += Math.max(0, finiteNumber(progress.failed) ?? 0);
3706
- supersededRows += Math.max(0, finiteNumber(progress.supersededRows) ?? 0);
3707
- const stepTotal = finiteNumber(progress.total);
3708
- 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);
3709
- }
3710
- const resultSummary = isRecord6(snapshot.resultSummary) ? snapshot.resultSummary : null;
3711
- const resultRowOutcomes = isRecord6(resultSummary?.rowOutcomes) ? resultSummary.rowOutcomes : null;
3712
- const terminalCompletedRows = finiteNumber(resultRowOutcomes?.completedRows);
3713
- const terminalFailedRows = finiteNumber(resultRowOutcomes?.failedRows);
3714
- const terminalTotalRows = finiteNumber(resultRowOutcomes?.totalRows);
3715
- const terminalSupersededRows = finiteNumber(
3716
- resultRowOutcomes?.supersededRows
3717
- );
3718
- const settledCompletedRows = terminalCompletedRows ?? completedRows;
3719
- const settledFailedRows = terminalFailedRows ?? failedRows;
3720
- return {
3721
- completedRows: settledCompletedRows,
3722
- failedRows: settledFailedRows,
3723
- totalRows: terminalTotalRows ?? totalRows,
3724
- hasRowFailures: settledFailedRows > 0,
3725
- ...(terminalSupersededRows ?? supersededRows) > 0 ? { supersededRows: terminalSupersededRows ?? supersededRows } : {}
3726
- };
3727
- }
3728
- function createEmptyPlayRunLedgerSnapshot(input) {
3729
- const status = normalizePlayRunLedgerStatus(input.status ?? "unknown");
3730
- const startedAt = finiteNumber(input.startedAt) ?? null;
3731
- const finishedAt = finiteNumber(input.finishedAt) ?? null;
3732
- return {
3733
- runId: input.runId,
3734
- playName: input.playName ?? null,
3735
- status,
3736
- waitKind: null,
3737
- waitUntil: null,
3738
- error: optionalNullableString(input.error) ?? null,
3739
- createdAt: finiteNumber(input.createdAt) ?? null,
3740
- startedAt,
3741
- updatedAt: finiteNumber(input.updatedAt) ?? finishedAt ?? startedAt ?? finiteNumber(input.createdAt) ?? null,
3742
- finishedAt,
3743
- durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : null,
3744
- orderedStepIds: [],
3745
- stepsById: {},
3746
- orderedDatasetIds: [],
3747
- datasetsById: {},
3748
- orderedActivityIds: [],
3749
- activitiesById: {},
3750
- logTail: [],
3751
- totalLogCount: 0,
3752
- logsTruncated: false,
3753
- activeStepId: null,
3754
- activeActivityId: null,
3755
- activeArtifactTableNamespace: null,
3756
- resultTableNamespace: null
3757
- };
3758
- }
3759
- function normalizePlayRunLedgerSnapshot(value, fallback) {
3760
- if (!isRecord6(value)) {
3761
- return createEmptyPlayRunLedgerSnapshot(fallback);
3762
- }
3763
- const orderedStepIds = Array.isArray(value.orderedStepIds) ? value.orderedStepIds.filter(
3764
- (entry) => typeof entry === "string" && Boolean(entry.trim())
3765
- ) : [];
3766
- const rawSteps = isRecord6(value.stepsById) ? value.stepsById : {};
3767
- const stepsById = {};
3768
- for (const [stepId, rawStep] of Object.entries(rawSteps)) {
3769
- if (!stepId.trim() || !isRecord6(rawStep)) continue;
3770
- const rawStatus = normalizeStepStatus(rawStep.status);
3771
- if (!rawStatus) continue;
3772
- const completedAt = finiteNumber(rawStep.completedAt);
3773
- const status = rawStatus === "running" && completedAt !== null ? "completed" : rawStatus;
3774
- const rawProgress = isRecord6(rawStep.progress) ? rawStep.progress : null;
3775
- stepsById[stepId] = {
3776
- stepId,
3777
- label: optionalString(rawStep.label),
3778
- kind: optionalString(rawStep.kind),
3779
- status,
3780
- error: optionalNullableString(rawStep.error),
3781
- artifactTableNamespace: optionalNullableString(
3782
- rawStep.artifactTableNamespace
3783
- ),
3784
- startedAt: finiteNumber(rawStep.startedAt),
3785
- completedAt,
3786
- updatedAt: finiteNumber(rawStep.updatedAt),
3787
- progress: rawProgress ? normalizeStepProgress(rawProgress) : null
3788
- };
3789
- }
3790
- const rawDatasets = isRecord6(value.datasetsById) ? value.datasetsById : {};
3791
- const datasetsById = {};
3792
- for (const [datasetId, rawDataset] of Object.entries(rawDatasets)) {
3793
- if (!datasetId.trim() || !isRecord6(rawDataset)) continue;
3794
- const phase = rawDataset.phase === "available" || rawDataset.phase === "failed" ? rawDataset.phase : "registered";
3795
- const datasetBornFrom = normalizeDatasetBornFrom(rawDataset.bornFrom);
3796
- datasetsById[datasetId] = {
3797
- datasetId,
3798
- path: optionalString(rawDataset.path) ?? `datasets.${datasetId}`,
3799
- tableNamespace: optionalString(rawDataset.tableNamespace) ?? datasetId,
3800
- phase,
3801
- persistedRows: Math.max(0, finiteNumber(rawDataset.persistedRows) ?? 0),
3802
- persistedRowsObserved: rawDataset.persistedRowsObserved === true,
3803
- succeededRows: Math.max(0, finiteNumber(rawDataset.succeededRows) ?? 0),
3804
- succeededRowsObserved: rawDataset.succeededRowsObserved === true,
3805
- failedRows: Math.max(0, finiteNumber(rawDataset.failedRows) ?? 0),
3806
- failedRowsObserved: rawDataset.failedRowsObserved === true,
3807
- complete: rawDataset.complete === true,
3808
- completeObserved: rawDataset.completeObserved === true,
3809
- // ADR 0019 birth survives the snapshot round trip. A partial record is
3810
- // dropped rather than half-admitted, so a dataset either states where its
3811
- // rows came from or says nothing.
3812
- ...datasetBornFrom ? { bornFrom: datasetBornFrom } : {},
3813
- updatedAt: finiteNumber(rawDataset.updatedAt) ?? 0
3814
- };
3815
- }
3816
- const rawActivities = isRecord6(value.activitiesById) ? value.activitiesById : {};
3817
- const activitiesById = {};
3818
- for (const [activityId, rawActivity] of Object.entries(rawActivities)) {
3819
- if (activityId.trim() && isPlayActivityObservation(rawActivity) && rawActivity.activityId === activityId) {
3820
- activitiesById[activityId] = rawActivity;
3821
- }
3822
- }
3823
- const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
3824
- const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
3825
- const finishedAt = finiteNumber(value.finishedAt) ?? fallback.finishedAt ?? null;
3826
- const updatedAt = finiteNumber(value.updatedAt) ?? fallback.updatedAt ?? finishedAt ?? startedAt ?? createdAt ?? null;
3827
- const error = Object.prototype.hasOwnProperty.call(value, "error") ? optionalNullableString(value.error) ?? null : fallback.error ?? null;
3828
- const rawTail = Array.isArray(value.logTail) ? value.logTail : value.logs;
3829
- const logTail = Array.isArray(rawTail) ? rawTail.filter((line) => typeof line === "string") : [];
3830
- const workCounters = normalizeWorkCounters(value.workCounters);
3831
- const terminalWorkCounterSegment = normalizePlayRunLedgerTerminalWorkCounterSegment(
3832
- value.terminalWorkCounterSegment
3833
- );
3834
- return {
3835
- runId: optionalString(value.runId) ?? fallback.runId,
3836
- playName: optionalNullableString(value.playName) ?? fallback.playName ?? null,
3837
- status: normalizePlayRunLedgerStatus(value.status ?? fallback.status),
3838
- waitKind: optionalNullableString(value.waitKind),
3839
- waitUntil: finiteNumber(value.waitUntil),
3840
- error,
3841
- createdAt,
3842
- startedAt,
3843
- updatedAt,
3844
- finishedAt,
3845
- durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : finiteNumber(value.durationMs),
3846
- orderedStepIds: orderedStepIds.filter((stepId) => stepsById[stepId]),
3847
- stepsById,
3848
- orderedDatasetIds: (Array.isArray(value.orderedDatasetIds) ? value.orderedDatasetIds : Object.keys(datasetsById)).filter(
3849
- (datasetId) => typeof datasetId === "string" && Boolean(datasetsById[datasetId])
3850
- ),
3851
- datasetsById,
3852
- orderedActivityIds: (Array.isArray(value.orderedActivityIds) ? value.orderedActivityIds : Object.keys(activitiesById)).filter(
3853
- (activityId) => typeof activityId === "string" && Boolean(activitiesById[activityId])
3854
- ),
3855
- activitiesById,
3856
- ...workCounters ? { workCounters } : {},
3857
- ...terminalWorkCounterSegment ? { terminalWorkCounterSegment } : {},
3858
- logTail: logTail.slice(-LOG_TAIL_LIMIT),
3859
- // Snapshots persisted before totalLogCount existed only know the retained
3860
- // tail, so the best lower bound for the cumulative count is the tail size.
3861
- totalLogCount: Math.max(
3862
- finiteNumber(value.totalLogCount) ?? logTail.length,
3863
- logTail.length
3864
- ),
3865
- // A persisted snapshot carrying more lines than the tail keeps is
3866
- // truncated here, whatever the stored flag says. Reporting `false` while
3867
- // dropping lines is silent loss: a reader would take the tail for the
3868
- // whole log.
3869
- logsTruncated: value.logsTruncated === true || logTail.length > LOG_TAIL_LIMIT,
3870
- activeStepId: optionalNullableString(value.activeStepId),
3871
- activeActivityId: optionalNullableString(value.activeActivityId),
3872
- activeArtifactTableNamespace: optionalNullableString(
3873
- value.activeArtifactTableNamespace
3874
- ),
3875
- resultTableNamespace: optionalNullableString(value.resultTableNamespace),
3876
- resultSummary: value.resultSummary,
3877
- result: value.result,
3878
- terminalSource: normalizePlayRunLedgerTerminalSource(value.terminalSource)
3879
- };
3880
- }
3881
- function normalizeStepStatus(value) {
3882
- const normalized = String(value ?? "").trim().toLowerCase();
3883
- if (normalized === "running" || normalized === "completed" || normalized === "failed" || normalized === "skipped") {
3884
- return normalized;
3885
- }
3886
- return null;
3887
- }
3888
- function normalizeStepProgress(value) {
3889
- return {
3890
- ...optionalFiniteNumber(value.completed) !== void 0 ? { completed: optionalFiniteNumber(value.completed) } : {},
3891
- ...optionalFiniteNumber(value.total) !== void 0 ? { total: optionalFiniteNumber(value.total) } : {},
3892
- ...optionalFiniteNumber(value.failed) !== void 0 ? { failed: optionalFiniteNumber(value.failed) } : {},
3893
- ...optionalFiniteNumber(value.startedRows) !== void 0 ? { startedRows: optionalFiniteNumber(value.startedRows) } : {},
3894
- ...optionalFiniteNumber(value.activeRows) !== void 0 ? { activeRows: optionalFiniteNumber(value.activeRows) } : {},
3895
- ...optionalFiniteNumber(value.waitingRows) !== void 0 ? { waitingRows: optionalFiniteNumber(value.waitingRows) } : {},
3896
- ...optionalFiniteNumber(value.completedRows) !== void 0 ? { completedRows: optionalFiniteNumber(value.completedRows) } : {},
3897
- ...optionalFiniteNumber(value.supersededRows) !== void 0 ? { supersededRows: optionalFiniteNumber(value.supersededRows) } : {},
3898
- ...optionalString(value.message) ? { message: optionalString(value.message) } : {},
3899
- ...optionalNullableString(value.artifactTableNamespace) !== void 0 ? {
3900
- artifactTableNamespace: optionalNullableString(
3901
- value.artifactTableNamespace
3902
- )
3903
- } : {},
3904
- ...finiteNumber(value.startedAt) !== null ? { startedAt: finiteNumber(value.startedAt) } : {},
3905
- ...finiteNumber(value.completedAt) !== null ? { completedAt: finiteNumber(value.completedAt) } : {},
3906
- ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {},
3907
- ...normalizePlayDocflowNodeIoState(value.nodeIo) ? { nodeIo: normalizePlayDocflowNodeIoState(value.nodeIo) } : {}
3908
- };
3909
- }
3910
2900
 
3911
2901
  // ../play-runtime/run-lifecycle-policy.ts
3912
2902
  var TERMINAL_PLAY_RUN_STATUSES = /* @__PURE__ */ new Set([
@@ -3970,170 +2960,13 @@ function decodePlayRunPublicStatus(value) {
3970
2960
  }
3971
2961
 
3972
2962
  // ../play-runtime/log-provenance.ts
3973
- var LOG_PROVENANCE_CLASSES = [
3974
- "user",
3975
- "lifecycle",
3976
- "replay",
3977
- "infra",
3978
- "diagnostic",
3979
- "receipt"
3980
- ];
3981
2963
  var PROVENANCE_SENTINEL = "";
3982
2964
  var PROVENANCE_PREFIX = `${PROVENANCE_SENTINEL}prov:`;
3983
- function readProvenanceTag(line) {
3984
- if (!line.startsWith(PROVENANCE_PREFIX)) {
3985
- return { provenance: null, line };
3986
- }
3987
- const end = line.indexOf(PROVENANCE_SENTINEL, PROVENANCE_PREFIX.length);
3988
- if (end === -1) {
3989
- return { provenance: null, line };
3990
- }
3991
- const candidate = line.slice(PROVENANCE_PREFIX.length, end);
3992
- const provenance = LOG_PROVENANCE_CLASSES.includes(candidate) ? candidate : null;
3993
- return { provenance, line: line.slice(end + 1) };
3994
- }
3995
- function stripProvenanceTag(line) {
3996
- return readProvenanceTag(line).line;
3997
- }
3998
2965
 
3999
2966
  // ../play-runtime/run-snapshot-stream.ts
4000
- function normalizePlayRunLiveStatus(value) {
4001
- return normalizePlayRunLifecycleStatus(value);
4002
- }
4003
2967
  function isTerminalPlayRunLiveStatus(status) {
4004
2968
  return isTerminalPlayRunLifecycleStatus(status);
4005
2969
  }
4006
- function isRecord7(value) {
4007
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
4008
- }
4009
- function finiteNumber2(value) {
4010
- return typeof value === "number" && Number.isFinite(value) ? value : null;
4011
- }
4012
- function extractTerminalRunLogTail(result) {
4013
- if (!isRecord7(result) || !isRecord7(result._metadata)) {
4014
- return null;
4015
- }
4016
- const runLogTail = result._metadata.runLogTail;
4017
- if (!isRecord7(runLogTail) || !Array.isArray(runLogTail.tail)) {
4018
- return null;
4019
- }
4020
- const logTail = runLogTail.tail.filter(
4021
- (line) => typeof line === "string" && line.trim().length > 0
4022
- );
4023
- if (logTail.length === 0) {
4024
- return null;
4025
- }
4026
- const totalLogCount = Math.max(
4027
- finiteNumber2(runLogTail.totalLogCount) ?? logTail.length,
4028
- logTail.length
4029
- );
4030
- return {
4031
- logTail: logTail.slice(-LOG_TAIL_LIMIT),
4032
- totalLogCount
4033
- };
4034
- }
4035
- function buildSnapshotFromLedger(snapshot) {
4036
- const nodeStates = snapshot.orderedStepIds.map((stepId) => snapshot.stepsById[stepId]).filter((step) => Boolean(step)).map((step) => ({
4037
- nodeId: step.stepId,
4038
- status: step.status,
4039
- error: step.error ?? null,
4040
- artifactTableNamespace: step.artifactTableNamespace ?? null,
4041
- progress: step.progress ? {
4042
- completed: step.progress.completed,
4043
- total: step.progress.total,
4044
- failed: step.progress.failed,
4045
- startedRows: step.progress.startedRows,
4046
- activeRows: step.progress.activeRows,
4047
- waitingRows: step.progress.waitingRows,
4048
- completedRows: step.progress.completedRows,
4049
- supersededRows: step.progress.supersededRows,
4050
- message: step.progress.message,
4051
- artifactTableNamespace: step.progress.artifactTableNamespace ?? step.artifactTableNamespace ?? null,
4052
- startedAt: step.startedAt ?? null,
4053
- completedAt: step.completedAt ?? null,
4054
- updatedAt: step.progress.updatedAt ?? step.updatedAt ?? null,
4055
- nodeIo: step.progress.nodeIo
4056
- } : null,
4057
- startedAt: step.startedAt ?? null,
4058
- completedAt: step.completedAt ?? null,
4059
- updatedAt: step.updatedAt ?? null,
4060
- ...step.progress?.nodeIo ? { nodeIo: step.progress.nodeIo } : {}
4061
- }));
4062
- const liveStatus = normalizePlayRunLiveStatus(snapshot.status);
4063
- const terminalRowOutcomes = isTerminalPlayRunLiveStatus(liveStatus) ? summarizeRunRowOutcomes(snapshot) : null;
4064
- const rowOutcomes = terminalRowOutcomes && (Object.keys(snapshot.stepsById).length > 0 || terminalRowOutcomes.totalRows > 0 || (terminalRowOutcomes.supersededRows ?? 0) > 0) ? terminalRowOutcomes : null;
4065
- const workCounters = snapshot.workCounters;
4066
- const calls = workCounters ? {
4067
- logicalCalls: workCounters.completedLogicalCalls + workCounters.logicalCalls,
4068
- providerRequests: workCounters.completedProviderRequests + workCounters.providerRequests,
4069
- provider429s: workCounters.completedProvider429s + workCounters.provider429s,
4070
- confirmed: isTerminalPlayRunLiveStatus(liveStatus) && hasConfirmedPlayRunLedgerWorkCounters(snapshot)
4071
- } : null;
4072
- return {
4073
- runId: snapshot.runId,
4074
- status: liveStatus,
4075
- createdAt: snapshot.createdAt ?? null,
4076
- startedAt: snapshot.startedAt ?? null,
4077
- finishedAt: snapshot.finishedAt ?? null,
4078
- durationMs: snapshot.durationMs ?? null,
4079
- updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
4080
- // This snapshot is public SDK/API transport. Keep the historical plain
4081
- // timestamp-prefixed text while provenance remains runtime metadata.
4082
- logs: snapshot.logTail.map(stripProvenanceTag),
4083
- totalLogCount: snapshot.totalLogCount,
4084
- ...snapshot.logsTruncated ? { logsTruncated: true } : {},
4085
- activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
4086
- resultTableNamespace: snapshot.resultTableNamespace ?? null,
4087
- datasets: snapshot.orderedDatasetIds.map((datasetId) => snapshot.datasetsById[datasetId]).filter(
4088
- (dataset) => Boolean(dataset)
4089
- ),
4090
- activities: snapshot.orderedActivityIds.map((activityId) => snapshot.activitiesById[activityId]).filter(
4091
- (activity) => Boolean(activity)
4092
- ),
4093
- activeActivity: snapshot.activeActivityId ? snapshot.activitiesById[snapshot.activeActivityId] ?? null : null,
4094
- nodeStates,
4095
- activeNodeId: snapshot.activeStepId ?? null,
4096
- ...rowOutcomes ? { rowOutcomes } : {},
4097
- ...calls && Object.values(calls).every(
4098
- (value) => typeof value === "boolean" || Number.isSafeInteger(value)
4099
- ) ? { calls } : {}
4100
- };
4101
- }
4102
- function buildPlayRunStatusSnapshot(input) {
4103
- const ledgerSnapshot = normalizePlayRunLedgerSnapshot(input.run.runSnapshot, {
4104
- runId: input.run.workflowId,
4105
- playName: input.run.name ?? null,
4106
- status: input.run.status,
4107
- createdAt: input.run.createdAt ?? null,
4108
- startedAt: input.run.startedAt ?? null,
4109
- updatedAt: input.run.updatedAt ?? null,
4110
- finishedAt: input.run.finishedAt ?? null
4111
- });
4112
- const terminalRunLogTail = extractTerminalRunLogTail(input.run.result);
4113
- const projectedSnapshot = terminalRunLogTail && terminalRunLogTail.totalLogCount > ledgerSnapshot.totalLogCount ? buildSnapshotFromLedger({
4114
- ...ledgerSnapshot,
4115
- logTail: terminalRunLogTail.logTail,
4116
- totalLogCount: terminalRunLogTail.totalLogCount
4117
- }) : buildSnapshotFromLedger(ledgerSnapshot);
4118
- const activity = projectPlayRunActivity({
4119
- runId: input.run.workflowId,
4120
- playName: input.run.name,
4121
- status: projectedSnapshot.status,
4122
- updatedAt: projectedSnapshot.updatedAt,
4123
- waitKind: input.run.waitKind ?? input.run.wait?.kind ?? null,
4124
- waitUntil: input.run.waitUntil,
4125
- eventKey: input.run.wait?.eventKey ?? null,
4126
- runtimeBackend: input.run.runtimeBackend,
4127
- activeNodeId: input.run.activeBoundaryId ?? projectedSnapshot.activeNodeId,
4128
- nodeStates: projectedSnapshot.nodeStates,
4129
- datasets: projectedSnapshot.datasets,
4130
- explicit: projectedSnapshot.activities
4131
- });
4132
- return {
4133
- ...projectedSnapshot,
4134
- activity
4135
- };
4136
- }
4137
2970
  function makeRunStreamEvent(input) {
4138
2971
  return {
4139
2972
  ...input,
@@ -4216,7 +3049,7 @@ function diffLogLines(input) {
4216
3049
  const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
4217
3050
  return {
4218
3051
  lines: [
4219
- `[stream] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
3052
+ `[watch] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
4220
3053
  ...logs
4221
3054
  ],
4222
3055
  lastLogSeq: totalLogCount,
@@ -4362,310 +3195,156 @@ function diffPlayRunStreamEvents(input) {
4362
3195
  return { events, next };
4363
3196
  }
4364
3197
 
4365
- // src/runs/observe-transport.ts
4366
- var RunObserveTransportUnavailableError = class extends Error {
4367
- constructor(message, reason) {
4368
- super(message);
4369
- this.reason = reason;
4370
- this.name = "RunObserveTransportUnavailableError";
3198
+ // src/stream-reconnect.ts
3199
+ function isTransientPlayRunReadError(error) {
3200
+ if (error instanceof DeeplineError && error.code === "PLAY_STREAM_NETWORK_ABORTED") {
3201
+ return true;
4371
3202
  }
4372
- };
4373
- var OBSERVE_BOOTSTRAP_TIMEOUT_MS = 1e4;
4374
- var OBSERVE_RECONNECT_NOTICE_MS = 1e4;
4375
- var OBSERVE_STALE_WARNING_MS = 12e4;
4376
- var OBSERVE_WATCHDOG_TICK_MS = 5e3;
4377
- var GRANT_REFRESH_MARGIN_MS = 5 * 6e4;
4378
- var BACKFILL_PAGE_LIMIT = 1e3;
4379
- var BACKFILL_MAX_PAGES = 30;
4380
- var OBSERVER_SNAPSHOT_QUERY = "runObservers:getPlayRunSnapshotForObserver";
4381
- var OBSERVER_LOG_PAGE_QUERY = "runObservers:getRunLogPageForObserver";
4382
- function errorText(error) {
4383
- return error instanceof Error ? error.message : String(error);
4384
- }
4385
- async function mintRunObserveGrant(http, runId) {
4386
- let response;
4387
- try {
4388
- response = await http.post(
4389
- `/api/v2/runs/${encodeURIComponent(runId)}/observe-grant`,
4390
- {}
4391
- );
4392
- } catch (error) {
4393
- if (error instanceof DeeplineError) {
4394
- if (error.statusCode === 401 || error.statusCode === 403) {
4395
- throw error;
4396
- }
4397
- throw new RunObserveTransportUnavailableError(
4398
- `observe-grant endpoint unavailable (${error.statusCode ?? "network"}): ${error.message}`,
4399
- "grant_endpoint_unavailable"
4400
- );
4401
- }
4402
- throw new RunObserveTransportUnavailableError(
4403
- `observe-grant request failed: ${errorText(error)}`,
4404
- "grant_request_failed"
4405
- );
3203
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
3204
+ return error.statusCode >= 500 && error.statusCode < 600;
3205
+ }
3206
+ const text = error instanceof Error ? error.message : String(error);
3207
+ 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(
3208
+ text
3209
+ );
3210
+ }
3211
+
3212
+ // src/runs/watch-polling.ts
3213
+ var RUN_WATCH_POLL_INTERVAL_MS = 2e3;
3214
+ function waitForNextPoll(ms, signal) {
3215
+ if (signal?.aborted) {
3216
+ return Promise.resolve(false);
3217
+ }
3218
+ return new Promise((resolve2) => {
3219
+ const finish = (elapsed) => {
3220
+ clearTimeout(timeout);
3221
+ signal?.removeEventListener("abort", onAbort);
3222
+ resolve2(elapsed);
3223
+ };
3224
+ const onAbort = () => finish(false);
3225
+ const timeout = setTimeout(() => finish(true), ms);
3226
+ signal?.addEventListener("abort", onAbort, { once: true });
3227
+ });
3228
+ }
3229
+ function readSnapshotResponse(response, runId) {
3230
+ if (!response || typeof response !== "object" || Array.isArray(response)) {
3231
+ throw new Error("Run watch returned an invalid snapshot response.");
4406
3232
  }
4407
- const grant = response;
4408
- if (!grant || typeof grant.convexUrl !== "string" || !grant.convexUrl.trim() || typeof grant.token !== "string" || !grant.token.trim() || typeof grant.expiresAt !== "number") {
4409
- throw new RunObserveTransportUnavailableError(
4410
- "observe-grant endpoint returned an invalid grant payload.",
4411
- "grant_payload_invalid"
4412
- );
3233
+ const record3 = response;
3234
+ const snapshot = record3.snapshot;
3235
+ 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(
3236
+ (line) => typeof line === "string"
3237
+ ) || typeof snapshot.totalLogCount !== "number" || !Array.isArray(snapshot.nodeStates)) {
3238
+ throw new Error(`Run watch returned an invalid snapshot for ${runId}.`);
4413
3239
  }
4414
- return grant;
3240
+ return { runId, snapshot };
4415
3241
  }
4416
- async function backfillLogGap(input) {
3242
+ async function backfillRotatedLogTail(input) {
3243
+ if (!input.previous.runSignature) {
3244
+ return input.snapshot;
3245
+ }
3246
+ const gap = resolvePlayRunLogGap(input.snapshot, input.previous.lastLogSeq);
3247
+ if (!gap) {
3248
+ return input.snapshot;
3249
+ }
4417
3250
  const lines = [];
4418
- let cursor = input.lastLogSeq;
4419
- for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
4420
- if (cursor >= input.tailFirstSeq - 1) {
4421
- break;
4422
- }
4423
- let logPage;
4424
- try {
4425
- logPage = await input.queryLogPage(
4426
- cursor,
4427
- Math.min(BACKFILL_PAGE_LIMIT, input.tailFirstSeq - 1 - cursor)
4428
- );
4429
- } catch {
4430
- return null;
4431
- }
4432
- const entries = (logPage?.entries ?? []).filter(
4433
- (entry) => entry.seq > cursor && entry.seq < input.tailFirstSeq
3251
+ let cursor = input.previous.lastLogSeq;
3252
+ const targetSeq = gap.tailFirstSeq - 1;
3253
+ while (cursor < targetSeq) {
3254
+ const limit = Math.min(1e3, targetSeq - cursor);
3255
+ const page = await input.http.get(
3256
+ `/api/v2/runs/${encodeURIComponent(input.runId)}/logs?afterSeq=${cursor}&limit=${limit}`,
3257
+ { signal: input.signal }
4434
3258
  );
4435
- if (entries.length === 0) {
4436
- break;
3259
+ if (!page || !Array.isArray(page.entries) || page.entries.length === 0) {
3260
+ return input.snapshot;
4437
3261
  }
4438
- for (const entry of entries) {
4439
- if (entry.seq !== cursor + 1) {
4440
- return null;
3262
+ for (const entry of page.entries) {
3263
+ if (!entry || entry.seq !== cursor + 1 || typeof entry.line !== "string") {
3264
+ return input.snapshot;
4441
3265
  }
4442
3266
  lines.push(entry.line);
4443
3267
  cursor = entry.seq;
4444
3268
  }
4445
3269
  }
4446
- if (cursor < input.tailFirstSeq - 1) {
4447
- return null;
3270
+ if (cursor !== targetSeq) {
3271
+ return input.snapshot;
4448
3272
  }
4449
- return lines;
3273
+ return { ...input.snapshot, logs: [...lines, ...input.snapshot.logs] };
4450
3274
  }
4451
- async function* observeRunEvents(options) {
4452
- const { http, runId } = options;
4453
- let grant = await mintRunObserveGrant(http, runId);
4454
- let convexBrowser;
4455
- let convexServer;
4456
- try {
4457
- convexBrowser = await import("convex/browser");
4458
- convexServer = await import("convex/server");
4459
- } catch (error) {
4460
- throw new RunObserveTransportUnavailableError(
4461
- `convex client module unavailable: ${errorText(error)}`,
4462
- "convex_module_unavailable"
4463
- );
4464
- }
4465
- let webSocketConstructor;
4466
- if (typeof WebSocket === "undefined") {
3275
+ async function* watchRunEvents(options) {
3276
+ const { http, runId, signal } = options;
3277
+ const path = `/api/v2/runs/${encodeURIComponent(runId)}?watchSnapshot=true`;
3278
+ const streamId = `play-run-watch:${runId}`;
3279
+ const pollIntervalMs = options.pollIntervalMs ?? RUN_WATCH_POLL_INTERVAL_MS;
3280
+ let previous = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
3281
+ let consecutiveFailures = 0;
3282
+ let outageNoticeSent = false;
3283
+ while (!signal?.aborted) {
3284
+ const pollStartedAt = Date.now();
3285
+ let snapshot;
4467
3286
  try {
4468
- const wsModuleName = "ws";
4469
- const ws = await import(wsModuleName);
4470
- webSocketConstructor = ws.default;
4471
- } catch (error) {
4472
- throw new RunObserveTransportUnavailableError(
4473
- `no WebSocket implementation available: ${errorText(error)}`,
4474
- "websocket_unavailable"
4475
- );
4476
- }
4477
- }
4478
- const snapshotQuery = convexServer.makeFunctionReference(
4479
- OBSERVER_SNAPSHOT_QUERY
4480
- );
4481
- const logPageQuery = convexServer.makeFunctionReference(
4482
- OBSERVER_LOG_PAGE_QUERY
4483
- );
4484
- const client = new convexBrowser.ConvexClient(grant.convexUrl, {
4485
- ...webSocketConstructor ? { webSocketConstructor } : {},
4486
- unsavedChangesWarning: false
4487
- });
4488
- const queue = [];
4489
- let wake = null;
4490
- const push = (item) => {
4491
- queue.push(item);
4492
- wake?.();
4493
- wake = null;
4494
- };
4495
- let lastForcedRefreshAt = 0;
4496
- client.setAuth(async ({ forceRefreshToken }) => {
4497
- const now = Date.now();
4498
- if (!forceRefreshToken && grant.expiresAt - now > GRANT_REFRESH_MARGIN_MS) {
4499
- return grant.token;
4500
- }
4501
- if (forceRefreshToken && now - lastForcedRefreshAt < 5e3) {
4502
- push({
4503
- kind: "error",
4504
- error: new DeeplineError(
4505
- `Run observe grant for ${runId} was rejected after a re-mint. The server and Convex deployment disagree on the grant issuer/JWKS.`,
4506
- 401,
4507
- "RUN_OBSERVE_GRANT_REJECTED"
4508
- )
3287
+ const response = await http.get(path, { signal });
3288
+ snapshot = await backfillRotatedLogTail({
3289
+ http,
3290
+ runId,
3291
+ snapshot: readSnapshotResponse(response, runId).snapshot,
3292
+ previous,
3293
+ signal
4509
3294
  });
4510
- return null;
4511
- }
4512
- if (forceRefreshToken) {
4513
- lastForcedRefreshAt = now;
4514
- }
4515
- try {
4516
- grant = await mintRunObserveGrant(http, runId);
4517
- return grant.token;
4518
3295
  } catch (error) {
4519
- push({ kind: "error", error });
4520
- return null;
4521
- }
4522
- });
4523
- const unsubscribe = client.onUpdate(
4524
- snapshotQuery,
4525
- { workflowId: runId },
4526
- (run) => push({ kind: "run", run: run ?? null }),
4527
- (error) => push({ kind: "error", error })
4528
- );
4529
- let lastUpdateAt = Date.now();
4530
- let lastStatusTerminal = false;
4531
- let disconnectedSince = null;
4532
- let warnedReconnecting = false;
4533
- let warnedStale = false;
4534
- const watchdog = setInterval(() => {
4535
- const now = Date.now();
4536
- try {
4537
- const connectionState = client.connectionState();
4538
- if (connectionState.isWebSocketConnected) {
4539
- disconnectedSince = null;
4540
- warnedReconnecting = false;
4541
- } else {
4542
- disconnectedSince ??= now;
4543
- if (!warnedReconnecting && now - disconnectedSince >= OBSERVE_RECONNECT_NOTICE_MS) {
4544
- warnedReconnecting = true;
4545
- options.onNotice?.(
4546
- `[observe] connection lost; reconnecting to live run ${runId}\u2026`
4547
- );
4548
- }
4549
- }
4550
- } catch {
4551
- }
4552
- if (!lastStatusTerminal && !warnedStale && now - lastUpdateAt >= OBSERVE_STALE_WARNING_MS) {
4553
- warnedStale = true;
4554
- options.onNotice?.(
4555
- `[observe] no live updates for ${Math.round((now - lastUpdateAt) / 1e3)}s; run ${runId} may be stalled (status checks continue)`
4556
- );
4557
- }
4558
- }, OBSERVE_WATCHDOG_TICK_MS);
4559
- watchdog.unref?.();
4560
- const abortListener = () => push({
4561
- kind: "error",
4562
- error: new DeeplineError(
4563
- "Run observation aborted.",
4564
- void 0,
4565
- "ABORTED"
4566
- )
4567
- });
4568
- options.signal?.addEventListener("abort", abortListener, { once: true });
4569
- let diffState = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
4570
- const streamId = ["observe", runId].join(":");
4571
- let sawFirstSnapshot = false;
4572
- try {
4573
- for (; ; ) {
4574
- if (queue.length === 0) {
4575
- const waitForItem = new Promise((resolve2) => {
4576
- wake = resolve2;
4577
- });
4578
- if (!sawFirstSnapshot) {
4579
- const timedOut = await Promise.race([
4580
- waitForItem.then(() => false),
4581
- new Promise(
4582
- (resolve2) => setTimeout(() => resolve2(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
4583
- )
4584
- ]);
4585
- if (timedOut && queue.length === 0) {
4586
- throw new RunObserveTransportUnavailableError(
4587
- `no snapshot from Convex at ${grant.convexUrl} within ${OBSERVE_BOOTSTRAP_TIMEOUT_MS}ms`,
4588
- "convex_unreachable"
4589
- );
4590
- }
4591
- } else {
4592
- await waitForItem;
4593
- }
4594
- continue;
4595
- }
4596
- const item = queue.shift();
4597
- if (item.kind === "error") {
4598
- if (options.signal?.aborted) {
4599
- return;
4600
- }
4601
- throw item.error;
3296
+ if (signal?.aborted) {
3297
+ return;
4602
3298
  }
4603
- sawFirstSnapshot = true;
4604
- lastUpdateAt = Date.now();
4605
- warnedStale = false;
4606
- if (item.run === null) {
4607
- throw new DeeplineError(
4608
- `Run ${runId} was not found (or is not visible to this grant).`,
4609
- 404,
4610
- "RUN_NOT_FOUND"
4611
- );
3299
+ if (!isTransientPlayRunReadError(error)) {
3300
+ throw error;
4612
3301
  }
4613
- const snapshot = buildPlayRunStatusSnapshot({ run: item.run });
4614
- lastStatusTerminal = isTerminalPlayRunLiveStatus(snapshot.status);
4615
- const gap = resolvePlayRunLogGap(snapshot, diffState.lastLogSeq);
4616
- if (gap && diffState.lastLogSeq > 0) {
4617
- const backfilled = await backfillLogGap({
4618
- queryLogPage: (afterSeq, limit) => client.query(logPageQuery, {
4619
- workflowId: runId,
4620
- afterSeq,
4621
- limit
4622
- }),
4623
- lastLogSeq: diffState.lastLogSeq,
4624
- tailFirstSeq: gap.tailFirstSeq
3302
+ consecutiveFailures += 1;
3303
+ const reason = error instanceof Error ? error.message : String(error);
3304
+ if (!outageNoticeSent) {
3305
+ options.onRetry?.({
3306
+ attempt: consecutiveFailures,
3307
+ delayMs: pollIntervalMs,
3308
+ reason
4625
3309
  });
4626
- if (backfilled && backfilled.length > 0) {
4627
- yield {
4628
- cursor: String(snapshot.updatedAt ?? Date.now()),
4629
- streamId,
4630
- scope: "play",
4631
- type: "play.run.log",
4632
- at: (/* @__PURE__ */ new Date()).toISOString(),
4633
- payload: {
4634
- runId: snapshot.runId,
4635
- lines: backfilled,
4636
- source: "worker",
4637
- firstSeq: diffState.lastLogSeq + 1,
4638
- totalLogCount: snapshot.totalLogCount
4639
- }
4640
- };
4641
- diffState = { ...diffState, lastLogSeq: gap.tailFirstSeq - 1 };
4642
- }
4643
- }
4644
- const { events, next } = diffPlayRunStreamEvents({
4645
- streamId,
4646
- snapshot,
4647
- previous: diffState
4648
- });
4649
- diffState = next;
4650
- const ordered = [
4651
- ...events.filter((event) => event.type === "play.run.snapshot"),
4652
- ...events.filter((event) => event.type !== "play.run.snapshot")
4653
- ];
4654
- for (const event of ordered) {
4655
- yield event;
3310
+ options.onNotice?.(
3311
+ `[watch] run snapshot request failed; retrying every ${pollIntervalMs / 1e3}s (${reason})`
3312
+ );
3313
+ outageNoticeSent = true;
4656
3314
  }
4657
- if (lastStatusTerminal) {
3315
+ const remaining2 = Math.max(
3316
+ 0,
3317
+ pollIntervalMs - (Date.now() - pollStartedAt)
3318
+ );
3319
+ if (!await waitForNextPoll(remaining2, signal)) {
4658
3320
  return;
4659
3321
  }
3322
+ continue;
4660
3323
  }
4661
- } finally {
4662
- clearInterval(watchdog);
4663
- options.signal?.removeEventListener("abort", abortListener);
4664
- try {
4665
- unsubscribe();
4666
- } catch {
3324
+ if (outageNoticeSent) {
3325
+ options.onNotice?.("[watch] run snapshot polling recovered");
3326
+ outageNoticeSent = false;
3327
+ }
3328
+ consecutiveFailures = 0;
3329
+ const diff = diffPlayRunStreamEvents({
3330
+ streamId,
3331
+ snapshot,
3332
+ previous
3333
+ });
3334
+ previous = diff.next;
3335
+ for (const event of diff.events) {
3336
+ yield event;
3337
+ }
3338
+ if (isTerminalPlayRunLiveStatus(snapshot.status)) {
3339
+ return;
3340
+ }
3341
+ const remaining = Math.max(
3342
+ 0,
3343
+ pollIntervalMs - (Date.now() - pollStartedAt)
3344
+ );
3345
+ if (!await waitForNextPoll(remaining, signal)) {
3346
+ return;
4667
3347
  }
4668
- await client.close().catch(() => void 0);
4669
3348
  }
4670
3349
  }
4671
3350
 
@@ -5017,7 +3696,7 @@ function requireTargetBillingIdempotencyKey(value) {
5017
3696
  }
5018
3697
  return normalized;
5019
3698
  }
5020
- function isRecord8(value) {
3699
+ function isRecord4(value) {
5021
3700
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
5022
3701
  }
5023
3702
  function isPrebuiltPlayDescription(play) {
@@ -5049,10 +3728,10 @@ function normalizePlayStatus(raw) {
5049
3728
  return {
5050
3729
  ...raw,
5051
3730
  runId,
5052
- ...isRecord8(legacyOverview) ? {
3731
+ ...isRecord4(legacyOverview) ? {
5053
3732
  overview: legacyOverview
5054
3733
  } : {},
5055
- ...isRecord8(runStatus) ? { runStatus } : {},
3734
+ ...isRecord4(runStatus) ? { runStatus } : {},
5056
3735
  ...runPackage ? {
5057
3736
  package: runPackage,
5058
3737
  outputs: runPackage.outputs
@@ -5191,7 +3870,7 @@ function updatePlayLiveStatusState(state, event) {
5191
3870
  }
5192
3871
  const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : isPlayRunPackage(payload) ? payload.run.id : state.runId;
5193
3872
  const status = normalizeLiveStatus(payload.status) ?? (isPlayRunPackage(payload) ? normalizeLiveStatus(payload.run.status) : null) ?? state.status;
5194
- const progressPayload = isRecord8(payload.progress) ? payload.progress : {};
3873
+ const progressPayload = isRecord4(payload.progress) ? payload.progress : {};
5195
3874
  if (event.type === "play.run.final_status" && state.logs.length === 0 && state.lastLogSeq === 0) {
5196
3875
  const payloadLogs = readStringArray(payload.logs);
5197
3876
  const progressLogs = readStringArray(progressPayload.logs);
@@ -5270,6 +3949,7 @@ var DeeplineClient = class _DeeplineClient {
5270
3949
  status: (runId) => this.getRunLifecycleStatus(runId),
5271
3950
  list: (options2) => this.listRuns(options2),
5272
3951
  listPage: (options2) => this.listRunsPage(options2),
3952
+ watch: (runId, options2) => this.watchRunToTerminal(runId, options2),
5273
3953
  tail: (runId, options2) => this.tailRun(runId, options2),
5274
3954
  logs: (runId, options2) => this.getRunLogs(runId, options2),
5275
3955
  input: (runId) => this.getRunInput(runId),
@@ -5392,9 +4072,9 @@ var DeeplineClient = class _DeeplineClient {
5392
4072
  return fields.length > 0 ? { fields } : schema;
5393
4073
  }
5394
4074
  schemaMetadata(schema, key) {
5395
- if (!isRecord8(schema)) return null;
4075
+ if (!isRecord4(schema)) return null;
5396
4076
  const value = schema[key];
5397
- return isRecord8(value) ? value : null;
4077
+ return isRecord4(value) ? value : null;
5398
4078
  }
5399
4079
  playRunCommand(play, options) {
5400
4080
  const target = play.reference || play.name;
@@ -5449,7 +4129,7 @@ var DeeplineClient = class _DeeplineClient {
5449
4129
  aliases,
5450
4130
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
5451
4131
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
5452
- staticPipeline: isRecord8(play.staticPipeline) ? play.staticPipeline : isRecord8(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord8(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
4132
+ staticPipeline: isRecord4(play.staticPipeline) ? play.staticPipeline : isRecord4(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord4(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
5453
4133
  ...csvInput ? { csvInput } : {},
5454
4134
  ...rowOutputSchema ? { rowOutputSchema } : {},
5455
4135
  runCommand,
@@ -5809,15 +4489,17 @@ var DeeplineClient = class _DeeplineClient {
5809
4489
  return normalizePlayRunStart(response);
5810
4490
  }
5811
4491
  /**
5812
- * Start a play run and stream live runtime events from the same request.
4492
+ * Submit a play run through the legacy event response.
4493
+ *
4494
+ * This reports admission from the start request; it does not watch an
4495
+ * existing run. Use `startPlayRun()` and `watchRunEvents(runId)` separately.
5813
4496
  *
5814
- * Use this when a caller wants low-level event handling instead of submitting
5815
- * first and then connecting to `streamPlayRunEvents(runId)`.
4497
+ * @deprecated Use the separate start and watch methods.
5816
4498
  *
5817
4499
  * @param request - Play run configuration.
5818
- * @param options - Optional streaming options.
5819
- * @param options.signal - Optional abort signal for the streaming request.
5820
- * @returns Async stream of play-scoped live events.
4500
+ * @param options - Optional compatibility response options.
4501
+ * @param options.signal - Abort the start response.
4502
+ * @returns Async generator of play-scoped live events.
5821
4503
  */
5822
4504
  async *startPlayRunStream(request, options) {
5823
4505
  const integrationMode = resolvePlayRunIntegrationMode(request);
@@ -6299,11 +4981,23 @@ var DeeplineClient = class _DeeplineClient {
6299
4981
  );
6300
4982
  return normalizePlayStatus(response);
6301
4983
  }
4984
+ /** Poll authenticated run snapshots every two seconds and emit changed events. */
4985
+ async *watchRunEvents(workflowId, options) {
4986
+ for await (const event of watchRunEvents({
4987
+ http: this.http,
4988
+ runId: workflowId,
4989
+ signal: options?.signal,
4990
+ onNotice: options?.onNotice,
4991
+ onRetry: options?.onRetry
4992
+ })) {
4993
+ yield event;
4994
+ }
4995
+ }
6302
4996
  /**
6303
- * Stream semantic play-run events using the same SSE feed as the dashboard.
4997
+ * Read raw legacy SSE events for callers that explicitly require that
4998
+ * compatibility endpoint. Ordinary run watching uses REST snapshot polling.
6304
4999
  *
6305
- * The server emits a canonical `play.run.snapshot` event first for every
6306
- * connection, then incremental live events until terminal state or reconnect.
5000
+ * @deprecated Use `watchRunEvents` for ordinary run watching.
6307
5001
  */
6308
5002
  async *streamPlayRunEvents(workflowId, options) {
6309
5003
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -6318,6 +5012,68 @@ var DeeplineClient = class _DeeplineClient {
6318
5012
  }
6319
5013
  }
6320
5014
  }
5015
+ /**
5016
+ * Compatibility alias for polling canonical run snapshots and emitting
5017
+ * their changed events.
5018
+ *
5019
+ * @deprecated Use `watchRunEvents`; kept as a source-compatible alias.
5020
+ * The `fallback` option is ignored because watching uses REST polling.
5021
+ */
5022
+ async *observeRunEvents(runId, options) {
5023
+ yield* this.watchRunEvents(runId, {
5024
+ signal: options?.signal,
5025
+ onNotice: options?.onNotice
5026
+ });
5027
+ }
5028
+ /** Poll snapshots to terminal status and return the durable package. */
5029
+ async watchRunToTerminal(runId, options) {
5030
+ const state = {
5031
+ runId,
5032
+ status: "running",
5033
+ logs: [],
5034
+ lastLogSeq: 0,
5035
+ latest: null
5036
+ };
5037
+ let terminalObserved = false;
5038
+ for await (const event of this.watchRunEvents(runId, {
5039
+ signal: options?.signal,
5040
+ onNotice: options?.onNotice,
5041
+ onRetry: options?.onRetry ?? options?.onReconnect
5042
+ })) {
5043
+ options?.onEvent?.(event);
5044
+ const status = updatePlayLiveStatusState(state, event);
5045
+ if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
5046
+ terminalObserved = true;
5047
+ }
5048
+ }
5049
+ if (options?.signal?.aborted) {
5050
+ throw new DeeplineError("Run watch aborted.", void 0, "ABORTED");
5051
+ }
5052
+ if (terminalObserved) {
5053
+ return await this.getRunStatus(runId);
5054
+ }
5055
+ const refreshed = await this.getRunStatus(runId);
5056
+ if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) return refreshed;
5057
+ throw new DeeplineError(
5058
+ `Run watch for ${runId} ended before a terminal status.`,
5059
+ void 0,
5060
+ "PLAY_WATCH_ENDED"
5061
+ );
5062
+ }
5063
+ /**
5064
+ * Wait for a run and return its terminal status.
5065
+ *
5066
+ * This source-compatible alias uses the same authenticated two-second REST
5067
+ * snapshot polling as `client.runs.watch`.
5068
+ *
5069
+ * @deprecated Use `client.runs.watch`; retained for SDK compatibility.
5070
+ * @param runId - Public Deepline play-run id.
5071
+ * @param options - Optional callbacks and cancellation signal.
5072
+ * @returns The final durable run status.
5073
+ */
5074
+ async tailRun(runId, options) {
5075
+ return this.watchRunToTerminal(runId, options);
5076
+ }
6321
5077
  /**
6322
5078
  * Cancel a running play execution.
6323
5079
  *
@@ -6481,10 +5237,10 @@ var DeeplineClient = class _DeeplineClient {
6481
5237
  const responseRunStatus = isPlayRunPackage(response) ? response.runStatus : isPlayRunPackage(response.package) ? response.package.runStatus : response.runStatus;
6482
5238
  const responseOverview = isPlayRunPackage(response) ? response.overview : isPlayRunPackage(response.package) ? response.package.overview : response.overview;
6483
5239
  const status = normalizePlayStatus(response);
6484
- if (isRecord8(responseRunStatus)) {
5240
+ if (isRecord4(responseRunStatus)) {
6485
5241
  status.runStatus = responseRunStatus;
6486
5242
  }
6487
- if (isRecord8(responseOverview)) {
5243
+ if (isRecord4(responseOverview)) {
6488
5244
  status.overview = responseOverview;
6489
5245
  }
6490
5246
  if (options?.failedLogs !== true || status.status !== "failed") {
@@ -6583,222 +5339,6 @@ var DeeplineClient = class _DeeplineClient {
6583
5339
  );
6584
5340
  return { ...response, runs: response.runs ?? [] };
6585
5341
  }
6586
- /**
6587
- * Observe one run's live events. Uses the Convex Run Snapshot subscription
6588
- * transport first (ADR-0008), then falls back to the canonical SSE stream
6589
- * when the subscription transport or its optional client modules are not
6590
- * available. Pass `fallback: 'none'` to receive
6591
- * {@link RunObserveTransportUnavailableError} instead.
6592
- */
6593
- async *observeRunEvents(runId, options) {
6594
- let yieldedObserveEvent = false;
6595
- try {
6596
- for await (const event of observeRunEvents({
6597
- http: this.http,
6598
- runId,
6599
- signal: options?.signal,
6600
- onNotice: options?.onNotice
6601
- })) {
6602
- yieldedObserveEvent = true;
6603
- yield event;
6604
- }
6605
- } catch (error) {
6606
- if (!(error instanceof RunObserveTransportUnavailableError) || yieldedObserveEvent || options?.fallback === "none") {
6607
- throw error;
6608
- }
6609
- options?.onNotice?.(
6610
- `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
6611
- );
6612
- yield* this.streamPlayRunEventsUntilTerminal(runId, options);
6613
- }
6614
- }
6615
- async *streamPlayRunEventsUntilTerminal(runId, options) {
6616
- const state = {
6617
- runId,
6618
- status: "running",
6619
- logs: [],
6620
- lastLogSeq: 0,
6621
- latest: null
6622
- };
6623
- let lastEventId;
6624
- let reconnectAttempt = 0;
6625
- for (; ; ) {
6626
- if (options?.signal?.aborted) {
6627
- return;
6628
- }
6629
- const connectedAt = Date.now();
6630
- let sawEvent = false;
6631
- let endedReason = "stream window ended before a terminal event";
6632
- try {
6633
- for await (const event of this.streamPlayRunEvents(runId, {
6634
- mode: "cli",
6635
- signal: options?.signal,
6636
- ...lastEventId ? { lastEventId } : {}
6637
- })) {
6638
- sawEvent = true;
6639
- if (event.cursor?.trim()) {
6640
- lastEventId = event.cursor;
6641
- }
6642
- yield event;
6643
- const status = updatePlayLiveStatusState(state, event);
6644
- if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
6645
- return;
6646
- }
6647
- }
6648
- } catch (error) {
6649
- if (options?.signal?.aborted) {
6650
- return;
6651
- }
6652
- if (!isTransientPlayStreamError(error)) {
6653
- throw error;
6654
- }
6655
- endedReason = error instanceof Error ? error.message : String(error);
6656
- }
6657
- let refreshed = null;
6658
- try {
6659
- refreshed = await this.getRunStatus(runId);
6660
- } catch (error) {
6661
- if (!isTransientPlayStreamError(error)) {
6662
- throw error;
6663
- }
6664
- }
6665
- if (refreshed && TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6666
- yield {
6667
- cursor: String(Date.now()),
6668
- streamId: `sse-fallback:${runId}`,
6669
- scope: "play",
6670
- type: "play.run.status",
6671
- at: (/* @__PURE__ */ new Date()).toISOString(),
6672
- payload: refreshed
6673
- };
6674
- return;
6675
- }
6676
- if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
6677
- reconnectAttempt = 0;
6678
- }
6679
- const delayMs = streamReconnectDelayMs(reconnectAttempt);
6680
- reconnectAttempt += 1;
6681
- options?.onNotice?.(
6682
- `[observe] SSE tail window ended before terminal status (${endedReason}); reconnecting to run ${runId}`
6683
- );
6684
- await sleep2(delayMs);
6685
- }
6686
- }
6687
- /**
6688
- * Tail one run through the subscription transport until terminal, then
6689
- * return one durable REST status read (the final Run Response Package).
6690
- */
6691
- async tailRunViaObserveTransport(runId, options) {
6692
- const state = {
6693
- runId,
6694
- status: "running",
6695
- logs: [],
6696
- lastLogSeq: 0,
6697
- latest: null
6698
- };
6699
- for await (const event of this.observeRunEvents(runId, {
6700
- signal: options?.signal,
6701
- onNotice: options?.onNotice,
6702
- fallback: "none"
6703
- })) {
6704
- options?.onEvent?.(event);
6705
- const status = updatePlayLiveStatusState(state, event);
6706
- if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
6707
- continue;
6708
- }
6709
- return await this.getRunStatus(status.runId || runId);
6710
- }
6711
- if (options?.signal?.aborted) {
6712
- throw new DeeplineError("Run observation aborted.", void 0, "ABORTED");
6713
- }
6714
- const refreshed = await this.getRunStatus(runId);
6715
- if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6716
- return refreshed;
6717
- }
6718
- throw new DeeplineError(
6719
- `Run observation for ${runId} ended before a terminal status.`,
6720
- void 0,
6721
- "PLAY_LIVE_STREAM_ENDED"
6722
- );
6723
- }
6724
- /**
6725
- * Read the canonical run stream until a terminal run status is observed.
6726
- *
6727
- * Tries the Convex Run Snapshot subscription transport first (ADR-0008);
6728
- * when the server cannot serve it (grant endpoint missing/unconfigured or
6729
- * Convex unreachable) it falls back — with one `onNotice` message — to the
6730
- * support-window SSE stream below.
6731
- *
6732
- * Server stream windows are finite: they end cleanly at the function
6733
- * ceiling even while the run keeps executing. A window that ends (cleanly
6734
- * or via transient network error) without a terminal event triggers one
6735
- * durable-status re-check followed by a backed-off reconnect, so long runs
6736
- * tail to completion. Abort via `options.signal` to stop waiting.
6737
- */
6738
- async tailRun(runId, options) {
6739
- try {
6740
- return await this.tailRunViaObserveTransport(runId, options);
6741
- } catch (error) {
6742
- if (!(error instanceof RunObserveTransportUnavailableError)) {
6743
- throw error;
6744
- }
6745
- options?.onNotice?.(
6746
- `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
6747
- );
6748
- }
6749
- const state = {
6750
- runId,
6751
- status: "running",
6752
- logs: [],
6753
- lastLogSeq: 0,
6754
- latest: null
6755
- };
6756
- let reconnectAttempt = 0;
6757
- for (; ; ) {
6758
- const connectedAt = Date.now();
6759
- let sawEvent = false;
6760
- let terminalRunId = null;
6761
- let endedReason = "stream window ended before a terminal event";
6762
- try {
6763
- for await (const event of this.streamPlayRunEvents(runId, {
6764
- mode: "cli",
6765
- signal: options?.signal
6766
- })) {
6767
- options?.onEvent?.(event);
6768
- sawEvent = true;
6769
- const status = updatePlayLiveStatusState(state, event);
6770
- if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
6771
- continue;
6772
- }
6773
- terminalRunId = status.runId || runId;
6774
- break;
6775
- }
6776
- } catch (error) {
6777
- if (options?.signal?.aborted || !isTransientPlayStreamError(error)) {
6778
- throw error;
6779
- }
6780
- endedReason = error instanceof Error ? error.message : String(error);
6781
- }
6782
- if (terminalRunId) {
6783
- return await this.getRunStatus(terminalRunId);
6784
- }
6785
- const refreshed = await this.getRunStatus(runId);
6786
- if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
6787
- return refreshed;
6788
- }
6789
- if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
6790
- reconnectAttempt = 0;
6791
- }
6792
- const delayMs = streamReconnectDelayMs(reconnectAttempt);
6793
- reconnectAttempt += 1;
6794
- options?.onReconnect?.({
6795
- attempt: reconnectAttempt,
6796
- delayMs,
6797
- reason: endedReason
6798
- });
6799
- await sleep2(delayMs);
6800
- }
6801
- }
6802
5342
  /** Get the exact original input retained for a run. This is intentionally separate from status. */
6803
5343
  async getRunInput(runId) {
6804
5344
  return this.http.get(`/api/v2/runs/${encodeURIComponent(runId)}/input`);
@@ -7589,10 +6129,10 @@ var DeeplineClient = class _DeeplineClient {
7589
6129
  // Plays — high-level orchestration
7590
6130
  // ——————————————————————————————————————————————————————————
7591
6131
  /**
7592
- * Run a play end-to-end: submit, stream until terminal, return result.
6132
+ * Run a play end-to-end: submit, poll progress until terminal, return result.
7593
6133
  *
7594
6134
  * This is the highest-level play execution method. It submits the play,
7595
- * reads the canonical run stream for status updates, and returns a structured
6135
+ * reads the canonical run snapshot for status updates, and returns a structured
7596
6136
  * result with logs and timing. Supports cancellation via `AbortSignal`.
7597
6137
  *
7598
6138
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
@@ -7648,6 +6188,7 @@ var DeeplineClient = class _DeeplineClient {
7648
6188
  lastLogSeq: 0,
7649
6189
  latest: null
7650
6190
  };
6191
+ let terminalObserved = null;
7651
6192
  if (options?.signal?.aborted) {
7652
6193
  await this.cancelPlay(workflowId);
7653
6194
  return {
@@ -7658,8 +6199,7 @@ var DeeplineClient = class _DeeplineClient {
7658
6199
  error: "Cancelled by user"
7659
6200
  };
7660
6201
  }
7661
- for await (const event of this.streamPlayRunEvents(workflowId, {
7662
- mode: "cli",
6202
+ for await (const event of this.watchRunEvents(workflowId, {
7663
6203
  signal: options?.signal
7664
6204
  })) {
7665
6205
  if (options?.signal?.aborted) {
@@ -7672,20 +6212,36 @@ var DeeplineClient = class _DeeplineClient {
7672
6212
  error: "Cancelled by user"
7673
6213
  };
7674
6214
  }
6215
+ const previousStatus = state.latest?.status;
7675
6216
  const status = updatePlayLiveStatusState(state, event);
7676
6217
  if (!status) {
7677
6218
  continue;
7678
6219
  }
7679
- options?.onProgress?.(status);
6220
+ if (event.type !== "play.run.status" || status.status !== previousStatus) {
6221
+ options?.onProgress?.(status);
6222
+ }
7680
6223
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
7681
- const finalStatus = await this.getPlayStatus(
7682
- status.runId || workflowId
7683
- ).catch(() => status);
7684
- return playRunResultFromStatus(finalStatus, start, workflowId);
6224
+ terminalObserved = status;
7685
6225
  }
7686
6226
  }
6227
+ if (options?.signal?.aborted) {
6228
+ await this.cancelPlay(workflowId);
6229
+ return {
6230
+ success: false,
6231
+ runId: workflowId,
6232
+ logs: state.logs,
6233
+ durationMs: Date.now() - start,
6234
+ error: "Cancelled by user"
6235
+ };
6236
+ }
6237
+ if (terminalObserved) {
6238
+ const finalStatus = await this.getPlayStatus(
6239
+ terminalObserved.runId || workflowId
6240
+ ).catch(() => terminalObserved);
6241
+ return playRunResultFromStatus(finalStatus, start, workflowId);
6242
+ }
7687
6243
  throw new DeeplineError(
7688
- `Run stream for ${workflowId} ended before the run reached a terminal state.`,
6244
+ `Run watch for ${workflowId} ended before the run reached a terminal state.`,
7689
6245
  void 0,
7690
6246
  "PLAY_RUN_STREAM_ENDED",
7691
6247
  { runId: workflowId, workflowId }
@@ -9323,7 +7879,7 @@ var TARGET_FALLBACK_KEYS = {
9323
7879
  status: { match: [/^email_status$/i, /^status$/i] },
9324
7880
  email_status: { match: [/^email_status$/i, /^status$/i] }
9325
7881
  };
9326
- function isRecord9(value) {
7882
+ function isRecord5(value) {
9327
7883
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9328
7884
  }
9329
7885
  function toV2RawToolOutputPath(path) {
@@ -9383,7 +7939,7 @@ function valuesAtSegments(current, segments, path = []) {
9383
7939
  if (!Array.isArray(current)) return [];
9384
7940
  return valuesAtSegments(current[segment], rest, [...path, segment]);
9385
7941
  }
9386
- if (!isRecord9(current)) return [];
7942
+ if (!isRecord5(current)) return [];
9387
7943
  const directMatches = valuesAtSegments(current[segment], rest, [
9388
7944
  ...path,
9389
7945
  segment
@@ -9413,7 +7969,7 @@ function getValuesAtPath(root, path) {
9413
7969
  return valuesAtSegments(root, parsePath(path)).map((entry) => entry.value);
9414
7970
  }
9415
7971
  function toResultEnvelope(value) {
9416
- if (isRecord9(value) && "data" in value) {
7972
+ if (isRecord5(value) && "data" in value) {
9417
7973
  const envelope = { data: value.data };
9418
7974
  if ("meta" in value) envelope.meta = value.meta;
9419
7975
  return envelope;
@@ -9466,7 +8022,7 @@ function getAtPath(root, path) {
9466
8022
  current = current[segment];
9467
8023
  continue;
9468
8024
  }
9469
- if (!isRecord9(current)) return void 0;
8025
+ if (!isRecord5(current)) return void 0;
9470
8026
  current = current[segment];
9471
8027
  }
9472
8028
  return current;
@@ -9483,7 +8039,7 @@ function normalizeString(value) {
9483
8039
  }
9484
8040
  function normalizeRows(value) {
9485
8041
  if (!Array.isArray(value)) return null;
9486
- return value.map((entry) => isRecord9(entry) ? entry : { value: entry });
8042
+ return value.map((entry) => isRecord5(entry) ? entry : { value: entry });
9487
8043
  }
9488
8044
  function findFirstTargetByPath(result, paths) {
9489
8045
  for (const path of paths ?? []) {
@@ -9553,7 +8109,7 @@ function findFirstTargetByKey(result, target, depth = 0, path = []) {
9553
8109
  }
9554
8110
  return null;
9555
8111
  }
9556
- if (!isRecord9(result)) return null;
8112
+ if (!isRecord5(result)) return null;
9557
8113
  const rule = TARGET_FALLBACK_KEYS[target] ?? {
9558
8114
  match: [new RegExp(`^${target}$`, "i")]
9559
8115
  };
@@ -9614,7 +8170,7 @@ function normalizeJobChangeStatus(value) {
9614
8170
  function firstExperienceDate(value) {
9615
8171
  if (!Array.isArray(value)) return null;
9616
8172
  for (const entry of value) {
9617
- if (!isRecord9(entry)) continue;
8173
+ if (!isRecord5(entry)) continue;
9618
8174
  const date = normalizeString(
9619
8175
  entry.start_date ?? entry.started_at ?? entry.startDate
9620
8176
  );
@@ -9623,10 +8179,10 @@ function firstExperienceDate(value) {
9623
8179
  return null;
9624
8180
  }
9625
8181
  function normalizeJobChange(value) {
9626
- const record3 = isRecord9(value) ? value : {};
9627
- const nested = isRecord9(record3.job_change) ? record3.job_change : record3;
9628
- const output = isRecord9(nested.output) ? nested.output : nested;
9629
- const person = isRecord9(output.person) ? output.person : {};
8182
+ const record3 = isRecord5(value) ? value : {};
8183
+ const nested = isRecord5(record3.job_change) ? record3.job_change : record3;
8184
+ const output = isRecord5(nested.output) ? nested.output : nested;
8185
+ const person = isRecord5(output.person) ? output.person : {};
9630
8186
  const status = normalizeJobChangeStatus(
9631
8187
  output.status ?? output.job_change_status ?? output.job_changed ?? output.changed
9632
8188
  );
@@ -10037,7 +8593,7 @@ function attachToolResultListDataset(result, input) {
10037
8593
  root = replaceAtPath(root, candidate, serialized.preview);
10038
8594
  break;
10039
8595
  }
10040
- if (isRecord9(root) && isRecord9(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, "raw")) {
8596
+ if (isRecord5(root) && isRecord5(root.toolResponse) && Object.prototype.hasOwnProperty.call(root.toolResponse, "raw")) {
10041
8597
  result.toolResponse.raw = root.toolResponse.raw;
10042
8598
  result.toolOutput.raw = root.toolResponse.raw;
10043
8599
  }
@@ -10055,7 +8611,7 @@ function replaceAtPath(root, path, replacement) {
10055
8611
  copy[segment] = replace(copy[segment], index + 1);
10056
8612
  return copy;
10057
8613
  }
10058
- if (!isRecord9(current)) return current;
8614
+ if (!isRecord5(current)) return current;
10059
8615
  return {
10060
8616
  ...current,
10061
8617
  [segment]: replace(current[segment], index + 1)
@@ -12025,11 +10581,11 @@ var Deepline = class {
12025
10581
  return new DeeplineContext(options);
12026
10582
  }
12027
10583
  };
12028
- function isRecord10(value) {
10584
+ function isRecord6(value) {
12029
10585
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
12030
10586
  }
12031
10587
  function stringArrayRecord(value) {
12032
- if (!isRecord10(value)) return {};
10588
+ if (!isRecord6(value)) return {};
12033
10589
  return Object.fromEntries(
12034
10590
  Object.entries(value).map(([key, paths]) => [
12035
10591
  key,
@@ -12041,7 +10597,7 @@ function stringArray(value) {
12041
10597
  return Array.isArray(value) ? value.map(String) : [];
12042
10598
  }
12043
10599
  function emailStatusExtractorConfig(value) {
12044
- if (!isRecord10(value)) return void 0;
10600
+ if (!isRecord6(value)) return void 0;
12045
10601
  const readPaths = (key) => {
12046
10602
  const paths = stringArray(value[key]).map((path) => path.trim()).filter(Boolean);
12047
10603
  return paths.length > 0 ? paths : void 0;
@@ -12068,7 +10624,7 @@ function emailStatusExtractorConfig(value) {
12068
10624
  const paths = readPaths(key);
12069
10625
  if (paths) config[key] = paths;
12070
10626
  }
12071
- if (isRecord10(value.statusMap)) {
10627
+ if (isRecord6(value.statusMap)) {
12072
10628
  config.statusMap = value.statusMap;
12073
10629
  }
12074
10630
  if (Array.isArray(value.rules)) {
@@ -12077,10 +10633,10 @@ function emailStatusExtractorConfig(value) {
12077
10633
  return config;
12078
10634
  }
12079
10635
  function extractorDescriptorRecord(value) {
12080
- if (!isRecord10(value)) return {};
10636
+ if (!isRecord6(value)) return {};
12081
10637
  return Object.fromEntries(
12082
10638
  Object.entries(value).flatMap(([key, descriptor]) => {
12083
- if (!isRecord10(descriptor)) return [];
10639
+ if (!isRecord6(descriptor)) return [];
12084
10640
  const paths = stringArray(descriptor.paths).map((path) => path.trim()).filter(Boolean);
12085
10641
  if (paths.length === 0) return [];
12086
10642
  const transforms = stringArray(descriptor.transforms).map((transform) => transform.trim()).filter(Boolean);
@@ -12102,16 +10658,16 @@ function extractorDescriptorRecord(value) {
12102
10658
  }
12103
10659
  function rowsFromUnknown(value) {
12104
10660
  if (!Array.isArray(value)) return [];
12105
- return value.map((row) => isRecord10(row) ? row : { value: row });
10661
+ return value.map((row) => isRecord6(row) ? row : { value: row });
12106
10662
  }
12107
- function finiteNonNegativeInteger3(value) {
10663
+ function finiteNonNegativeInteger2(value) {
12108
10664
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
12109
10665
  return null;
12110
10666
  }
12111
10667
  return Math.floor(value);
12112
10668
  }
12113
10669
  function finitePositiveInteger(value) {
12114
- const integer = finiteNonNegativeInteger3(value);
10670
+ const integer = finiteNonNegativeInteger2(value);
12115
10671
  return integer !== null && integer > 0 ? integer : null;
12116
10672
  }
12117
10673
  function stableHash(value) {
@@ -12124,9 +10680,9 @@ function stableHash(value) {
12124
10680
  }
12125
10681
  function attachSdkQueryResultDatasetResult(toolId, result, options) {
12126
10682
  if (!options || !isQueryResultDatasetTool(toolId)) return result;
12127
- const raw = isRecord10(result.toolResponse.raw) ? result.toolResponse.raw : null;
12128
- const dataset = isRecord10(raw?.dataset) ? raw.dataset : null;
12129
- const totalRows = finiteNonNegativeInteger3(dataset?.total_rows);
10683
+ const raw = isRecord6(result.toolResponse.raw) ? result.toolResponse.raw : null;
10684
+ const dataset = isRecord6(raw?.dataset) ? raw.dataset : null;
10685
+ const totalRows = finiteNonNegativeInteger2(dataset?.total_rows);
12130
10686
  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;
12131
10687
  if (!dataset || totalRows === null || !sql) {
12132
10688
  return result;
@@ -12157,7 +10713,7 @@ function attachSdkQueryResultDatasetResult(toolId, result, options) {
12157
10713
  } : {}
12158
10714
  }
12159
10715
  });
12160
- const pageRaw = isRecord10(response.toolResponse?.raw) ? response.toolResponse.raw : null;
10716
+ const pageRaw = isRecord6(response.toolResponse?.raw) ? response.toolResponse.raw : null;
12161
10717
  return rowsFromUnknown(pageRaw?.rows);
12162
10718
  };
12163
10719
  const collectRows = async (limit) => {
@@ -12214,8 +10770,8 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12214
10770
  const rawV2 = response.toolResponse?.rawV2;
12215
10771
  const view = response.toolResponse?.view;
12216
10772
  const meta = response.toolResponse?.meta;
12217
- const metadata = isRecord10(response._metadata) ? response._metadata.tool : null;
12218
- const toolMetadata = isRecord10(metadata) ? metadata : {};
10773
+ const metadata = isRecord6(response._metadata) ? response._metadata.tool : null;
10774
+ const toolMetadata = isRecord6(metadata) ? metadata : {};
12219
10775
  const result = attachSdkQueryResultDatasetResult(
12220
10776
  fallbackToolId,
12221
10777
  createToolExecuteResult({
@@ -12223,12 +10779,12 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12223
10779
  jobId: typeof response.job_id === "string" ? response.job_id : void 0,
12224
10780
  result: {
12225
10781
  data: raw,
12226
- ...isRecord10(meta) ? { meta } : {}
10782
+ ...isRecord6(meta) ? { meta } : {}
12227
10783
  },
12228
10784
  response: {
12229
10785
  ...rawV2 !== void 0 ? { rawV2 } : {},
12230
10786
  ...view === "data" || view === "rawV2" ? { view } : {},
12231
- ...isRecord10(meta) ? { meta } : {}
10787
+ ...isRecord6(meta) ? { meta } : {}
12232
10788
  },
12233
10789
  metadata: {
12234
10790
  toolId: typeof toolMetadata.toolId === "string" ? toolMetadata.toolId : fallbackToolId,
@@ -12244,12 +10800,12 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response, options) {
12244
10800
  cached: false,
12245
10801
  source: "live"
12246
10802
  },
12247
- meta: isRecord10(response.meta) ? response.meta : void 0,
10803
+ meta: isRecord6(response.meta) ? response.meta : void 0,
12248
10804
  billing: response.billing
12249
10805
  }),
12250
10806
  options
12251
10807
  );
12252
- const executionRecovery = isRecord10(response.executionRecovery) ? response.executionRecovery : void 0;
10808
+ const executionRecovery = isRecord6(response.executionRecovery) ? response.executionRecovery : void 0;
12253
10809
  if (typeof response.idempotencyKey === "string") {
12254
10810
  result.idempotencyKey = response.idempotencyKey;
12255
10811
  }
@@ -12386,6 +10942,15 @@ function getDefinedPlayMetadata(value) {
12386
10942
  return candidate;
12387
10943
  }
12388
10944
 
10945
+ // src/runs/observe-transport.ts
10946
+ var RunObserveTransportUnavailableError = class extends Error {
10947
+ constructor(message, reason) {
10948
+ super(message);
10949
+ this.reason = reason;
10950
+ this.name = "RunObserveTransportUnavailableError";
10951
+ }
10952
+ };
10953
+
12389
10954
  // src/monitors.ts
12390
10955
  function defineMonitor(definition) {
12391
10956
  if (!definition || typeof definition !== "object") {