deepline 0.1.235 → 0.1.236
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/bundling-sources/apps/play-runner-workers/src/entry.ts +3 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +18 -1
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +23 -6
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +15 -0
- package/dist/cli/index.js +49 -67
- package/dist/cli/index.mjs +49 -67
- package/dist/index.js +3 -2
- package/dist/index.mjs +3 -2
- package/package.json +1 -1
|
@@ -3828,6 +3828,9 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
|
|
|
3828
3828
|
if (builtin) {
|
|
3829
3829
|
return Promise.resolve({
|
|
3830
3830
|
...builtin,
|
|
3831
|
+
// Builtin pacing has no per-org coordinator rate scope. Keep the
|
|
3832
|
+
// required resolver contract explicit so the Governor treats it as
|
|
3833
|
+
// intentionally unscoped rather than receiving a malformed policy.
|
|
3831
3834
|
r: null,
|
|
3832
3835
|
retrySafeTransientHttp: false,
|
|
3833
3836
|
});
|
|
@@ -108,10 +108,10 @@ export const SDK_RELEASE = {
|
|
|
108
108
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
109
109
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
110
110
|
// automatically without blocking their current command.
|
|
111
|
-
version: '0.1.
|
|
111
|
+
version: '0.1.236',
|
|
112
112
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
113
113
|
supportPolicy: {
|
|
114
|
-
latest: '0.1.
|
|
114
|
+
latest: '0.1.236',
|
|
115
115
|
minimumSupported: '0.1.53',
|
|
116
116
|
deprecatedBelow: '0.1.219',
|
|
117
117
|
commandMinimumSupported: [
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
isPlayRowExecutionSuspendedError,
|
|
13
13
|
} from './suspension';
|
|
14
14
|
import { getToolHttpErrorReceiptFailureKind } from './tool-http-errors';
|
|
15
|
+
import { isLegacyRepairableWorkReceiptError } from './work-receipt-state-machine';
|
|
15
16
|
import type { WorkReceiptFailureKind } from './work-receipts';
|
|
16
17
|
import {
|
|
17
18
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
@@ -63,8 +64,24 @@ export function runtimeReceiptFailureKindForError(
|
|
|
63
64
|
const toolFailureKind = getToolHttpErrorReceiptFailureKind(error);
|
|
64
65
|
if (toolFailureKind) return toolFailureKind;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
if (
|
|
68
|
+
isPlayExecutionSuspendedError(error) ||
|
|
67
69
|
isPlayRowExecutionSuspendedError(error)
|
|
70
|
+
) {
|
|
71
|
+
return 'repairable';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Transport/timeout failures never reach a `ToolHttpError` (there was no HTTP
|
|
75
|
+
// response to classify), so they would otherwise default to `terminal` and
|
|
76
|
+
// poison the durable receipt: the next run would replay the cached transport
|
|
77
|
+
// failure instead of re-executing. These are transient infra failures, so
|
|
78
|
+
// mark them repairable. `isLegacyRepairableWorkReceiptError` already encodes
|
|
79
|
+
// the exact transient-string contract (5xx, "transport failed calling",
|
|
80
|
+
// "runtime api call timed out") while excluding hard billing caps, and the
|
|
81
|
+
// read-side SQL predicate uses the same rules, so write and read stay aligned.
|
|
82
|
+
const message =
|
|
83
|
+
error instanceof Error ? error.message : error != null ? String(error) : '';
|
|
84
|
+
return isLegacyRepairableWorkReceiptError(message)
|
|
68
85
|
? 'repairable'
|
|
69
86
|
: 'terminal';
|
|
70
87
|
}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
const encoder = typeof TextEncoder === 'undefined' ? null : new TextEncoder();
|
|
2
2
|
|
|
3
3
|
export const TERMINAL_RUN_RESULT_MAX_BYTES = 2_500_000;
|
|
4
|
+
/**
|
|
5
|
+
* Convex hard-caps a single document at 1 MiB and rejects nesting deeper than
|
|
6
|
+
* 16 levels. A run-ledger `run.completed` / `run.failed` event embeds its
|
|
7
|
+
* `result` inside one `playRunEvents` document, so the ledger-bound result must
|
|
8
|
+
* stay comfortably under that ceiling (the event also carries seq/type/
|
|
9
|
+
* timestamps and the surrounding doc envelope). Keep well below 1 MiB so no
|
|
10
|
+
* single event can wedge the append mutation with `Value is too large (>1MiB)`.
|
|
11
|
+
* Historically a failed run embedded its full `PlayRunnerResult` — including a
|
|
12
|
+
* `checkpoint` whose `completedToolBatches[*].toolResponse.raw` held megabytes
|
|
13
|
+
* of provider payload — which is durable in scheduler Postgres and never read
|
|
14
|
+
* back from Convex, yet deterministically wedged the append.
|
|
15
|
+
*/
|
|
16
|
+
export const LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
|
|
4
17
|
export const CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
|
|
5
18
|
export const CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
|
|
6
19
|
/** Inline Postgres receipt contract: one serialized output is at most 10 MiB. */
|
|
@@ -86,6 +99,81 @@ export function persistableTerminalRunResult(value: unknown): unknown {
|
|
|
86
99
|
return valueWithinJsonByteLimit(value, TERMINAL_RUN_RESULT_MAX_BYTES);
|
|
87
100
|
}
|
|
88
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Replay-only keys a terminal `PlayRunnerResult` carries for the scheduler
|
|
104
|
+
* (Postgres) data plane. These are NEVER read back from the Convex run ledger:
|
|
105
|
+
* resume rehydrates the checkpoint/suspension from scheduler Postgres
|
|
106
|
+
* (`work_runs.checkpoint_json` / `suspension_json`), and customer reads use the
|
|
107
|
+
* run's `output`. Embedding them in a `playRunEvents` document only bloats the
|
|
108
|
+
* write and can exceed Convex's 1 MiB / 16-level document limits.
|
|
109
|
+
*/
|
|
110
|
+
const LEDGER_STRIPPED_TERMINAL_RESULT_KEYS = [
|
|
111
|
+
'checkpoint',
|
|
112
|
+
'suspension',
|
|
113
|
+
] as const;
|
|
114
|
+
|
|
115
|
+
/** A tiny reference descriptor left in place of a dropped over-limit result. */
|
|
116
|
+
export type LedgerTerminalResultRef = {
|
|
117
|
+
__kind: 'deepline.ledger_terminal_result_ref.v1';
|
|
118
|
+
/** Where the full terminal result is durably stored. */
|
|
119
|
+
store: 'scheduler_postgres';
|
|
120
|
+
/** Column on the scheduler `work_runs` row that holds the full result. */
|
|
121
|
+
key: 'terminal_result_json';
|
|
122
|
+
/** Serialized byte size of the result that was elided from the ledger. */
|
|
123
|
+
bytes: number;
|
|
124
|
+
reason: 'terminal_result_exceeds_ledger_limit';
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
128
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Shape a terminal run result for the Convex run ledger. Control-plane only:
|
|
133
|
+
*
|
|
134
|
+
* 1. Strip replay-only keys ({@link LEDGER_STRIPPED_TERMINAL_RESULT_KEYS}) from
|
|
135
|
+
* the top-level result object — the scheduler Postgres data plane owns them.
|
|
136
|
+
* 2. If what remains still exceeds {@link LEDGER_TERMINAL_RESULT_MAX_BYTES},
|
|
137
|
+
* replace the whole result with a {@link LedgerTerminalResultRef} pointing at
|
|
138
|
+
* the durable scheduler copy, so the ledger event stays small and the
|
|
139
|
+
* Convex append cannot wedge on an oversized/over-nested document.
|
|
140
|
+
*
|
|
141
|
+
* The full result (including its checkpoint) remains durable in scheduler
|
|
142
|
+
* Postgres regardless; this only governs what the ledger embeds.
|
|
143
|
+
*/
|
|
144
|
+
export function terminalRunResultForLedger(value: unknown): unknown {
|
|
145
|
+
if (value === undefined || value === null) {
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
let candidate = value;
|
|
149
|
+
if (isPlainObject(value)) {
|
|
150
|
+
const stripped: Record<string, unknown> = {};
|
|
151
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
152
|
+
if (
|
|
153
|
+
(LEDGER_STRIPPED_TERMINAL_RESULT_KEYS as readonly string[]).includes(
|
|
154
|
+
key,
|
|
155
|
+
)
|
|
156
|
+
) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
stripped[key] = entry;
|
|
160
|
+
}
|
|
161
|
+
candidate = stripped;
|
|
162
|
+
}
|
|
163
|
+
const bytes = jsonByteLength(candidate);
|
|
164
|
+
if (bytes <= LEDGER_TERMINAL_RESULT_MAX_BYTES) {
|
|
165
|
+
return candidate;
|
|
166
|
+
}
|
|
167
|
+
const ref: LedgerTerminalResultRef = {
|
|
168
|
+
__kind: 'deepline.ledger_terminal_result_ref.v1',
|
|
169
|
+
store: 'scheduler_postgres',
|
|
170
|
+
key: 'terminal_result_json',
|
|
171
|
+
bytes,
|
|
172
|
+
reason: 'terminal_result_exceeds_ledger_limit',
|
|
173
|
+
};
|
|
174
|
+
return ref;
|
|
175
|
+
}
|
|
176
|
+
|
|
89
177
|
export function assertCustomerOutputValueWithinLimit(input: {
|
|
90
178
|
value: unknown;
|
|
91
179
|
path: string;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { RECEIPT_STATUS_CODE } from './receipt-status';
|
|
2
2
|
import { workReceiptFailureKindCode } from './work-receipts';
|
|
3
3
|
|
|
4
|
+
// This predicate MUST stay behaviorally identical to
|
|
5
|
+
// `isLegacyRepairableWorkReceiptError` in ./work-receipt-state-machine.ts.
|
|
6
|
+
// Write-side classification and this read-side reclaim gate share one contract:
|
|
7
|
+
// a receipt is repairable if its failure_kind is repairable, OR it is a legacy
|
|
8
|
+
// terminal receipt whose error text proves a transient-class failure (5xx,
|
|
9
|
+
// transport, timeout, or a transient billing-plane outage). The
|
|
10
|
+
// billing_unavailable clause recovers the poisoned backlog written during a
|
|
11
|
+
// billing-plane incident, where transient 503s were formatted with the hard
|
|
12
|
+
// "billing cap" template — it is checked BEFORE the hard-billing exclusions so
|
|
13
|
+
// those receipts re-execute on the next run instead of replaying the failure.
|
|
4
14
|
export function workReceiptRepairableFailurePredicateSql(
|
|
5
15
|
receiptTable: string,
|
|
6
16
|
): string {
|
|
@@ -9,13 +19,20 @@ export function workReceiptRepairableFailurePredicateSql(
|
|
|
9
19
|
OR (
|
|
10
20
|
${receiptTable}.failure_kind = ${workReceiptFailureKindCode('terminal')}::smallint
|
|
11
21
|
AND ${receiptTable}.error IS NOT NULL
|
|
12
|
-
AND lower(${receiptTable}.error) NOT LIKE '%billing cap%'
|
|
13
|
-
AND lower(${receiptTable}.error) NOT LIKE '%insufficient credits%'
|
|
14
|
-
AND lower(${receiptTable}.error) NOT LIKE '%monthly billing limit%'
|
|
15
22
|
AND (
|
|
16
|
-
${receiptTable}.error
|
|
17
|
-
OR lower(${receiptTable}.error) LIKE '%
|
|
18
|
-
OR lower(${receiptTable}.error) LIKE '%
|
|
23
|
+
lower(${receiptTable}.error) LIKE '%billing_unavailable%'
|
|
24
|
+
OR lower(${receiptTable}.error) LIKE '%billing is temporarily unavailable%'
|
|
25
|
+
OR lower(${receiptTable}.error) LIKE '%billing temporarily unavailable%'
|
|
26
|
+
OR (
|
|
27
|
+
lower(${receiptTable}.error) NOT LIKE '%billing cap%'
|
|
28
|
+
AND lower(${receiptTable}.error) NOT LIKE '%insufficient credits%'
|
|
29
|
+
AND lower(${receiptTable}.error) NOT LIKE '%monthly billing limit%'
|
|
30
|
+
AND (
|
|
31
|
+
${receiptTable}.error ~* '(^|[^0-9])5[0-9]{2}([^0-9]|$)'
|
|
32
|
+
OR lower(${receiptTable}.error) LIKE '%transport failed calling%'
|
|
33
|
+
OR lower(${receiptTable}.error) LIKE '%runtime api call timed out%'
|
|
34
|
+
)
|
|
35
|
+
)
|
|
19
36
|
)
|
|
20
37
|
)
|
|
21
38
|
)`;
|
|
@@ -52,10 +52,40 @@ function isInsufficientCreditsBilling(
|
|
|
52
52
|
return billing?.kind === 'insufficient_credits';
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A transient failure of the Deepline billing PLANE (Convex outage, OCC
|
|
57
|
+
* exhaustion) surfaced as HTTP 503 `BILLING_UNAVAILABLE` with `retryable: true`
|
|
58
|
+
* and `failure_description` stating no credits were charged. This is NOT a hard
|
|
59
|
+
* credit/cap denial: a funded org is temporarily blocked because our billing
|
|
60
|
+
* store hiccuped, so it must be repairable on the next run rather than cached as
|
|
61
|
+
* a terminal failure. See `billingUnavailableResponse` in
|
|
62
|
+
* src/lib/integrations/execute-billing.ts. This class is the exact incident
|
|
63
|
+
* poison closed by issues #2561/#2706: without this carve-out a billing-plane
|
|
64
|
+
* outage classifies every affected tool call as `hard_billing_error` ->
|
|
65
|
+
* `terminal` and the next run replays the cached failure instead of retrying.
|
|
66
|
+
*/
|
|
67
|
+
export function isTransientBillingFailurePayload(
|
|
68
|
+
payload: Record<string, unknown> | null,
|
|
69
|
+
): boolean {
|
|
70
|
+
if (!payload) return false;
|
|
71
|
+
const code = String(payload.code ?? payload.error_code ?? '').toUpperCase();
|
|
72
|
+
if (code === 'BILLING_UNAVAILABLE') return true;
|
|
73
|
+
// Any billing-origin payload that explicitly declares itself retryable is a
|
|
74
|
+
// billing-infra hiccup, never a hard denial (denials are never retryable).
|
|
75
|
+
const category = String(
|
|
76
|
+
payload.error_category ?? payload.errorCategory ?? '',
|
|
77
|
+
).toLowerCase();
|
|
78
|
+
return category === 'billing' && payload.retryable === true;
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
function isHardBillingFailurePayload(
|
|
56
82
|
payload: Record<string, unknown> | null,
|
|
57
83
|
): payload is Record<string, unknown> {
|
|
58
84
|
if (!payload) return false;
|
|
85
|
+
// Billing-plane outages (503 BILLING_UNAVAILABLE) are transient infra, not
|
|
86
|
+
// hard denials. Never let them reach the terminal, run-fatal hard-billing
|
|
87
|
+
// path — they must stay repairable so the next run retries.
|
|
88
|
+
if (isTransientBillingFailurePayload(payload)) return false;
|
|
59
89
|
const category = String(
|
|
60
90
|
payload.error_category ?? payload.errorCategory ?? '',
|
|
61
91
|
).toLowerCase();
|
|
@@ -100,6 +130,10 @@ function normalizeHardBillingPayload(
|
|
|
100
130
|
function receiptFailureKindForToolErrorPayload(
|
|
101
131
|
payload: Record<string, unknown> | null,
|
|
102
132
|
): WorkReceiptFailureKind {
|
|
133
|
+
// A transient billing-plane outage (503 BILLING_UNAVAILABLE) charged nothing
|
|
134
|
+
// and is explicitly retryable. It must be repairable so the next run
|
|
135
|
+
// re-executes instead of replaying the cached billing-infra failure.
|
|
136
|
+
if (isTransientBillingFailurePayload(payload)) return 'repairable';
|
|
103
137
|
const code = getStringField(payload, 'code')?.toUpperCase();
|
|
104
138
|
// A credential connection can be added or replaced between play runs. Keep
|
|
105
139
|
// the failure terminal within its owning run, but let a later run reclaim
|
|
@@ -22,6 +22,21 @@ export function isLegacyRepairableWorkReceiptError(
|
|
|
22
22
|
): boolean {
|
|
23
23
|
const message = error?.trim().toLowerCase() ?? '';
|
|
24
24
|
if (!message) return false;
|
|
25
|
+
// Transient billing-PLANE outages (503 BILLING_UNAVAILABLE) were historically
|
|
26
|
+
// formatted with the hard "billing cap exceeded" template (see
|
|
27
|
+
// formatHardBillingFailureMessage), so the receipts written during a
|
|
28
|
+
// billing-plane incident carry a "billing cap" string even though they are
|
|
29
|
+
// retryable infra failures that charged nothing. Recognize them BEFORE the
|
|
30
|
+
// hard-billing exclusion below so the poisoned backlog re-executes on the next
|
|
31
|
+
// run instead of replaying the cached failure. Real hard denials never carry a
|
|
32
|
+
// BILLING_UNAVAILABLE code or "temporarily unavailable" text.
|
|
33
|
+
if (
|
|
34
|
+
message.includes('billing_unavailable') ||
|
|
35
|
+
message.includes('billing is temporarily unavailable') ||
|
|
36
|
+
message.includes('billing temporarily unavailable')
|
|
37
|
+
) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
25
40
|
if (
|
|
26
41
|
message.includes('billing cap') ||
|
|
27
42
|
message.includes('insufficient credits') ||
|
package/dist/cli/index.js
CHANGED
|
@@ -625,10 +625,10 @@ var SDK_RELEASE = {
|
|
|
625
625
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
626
626
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
627
627
|
// automatically without blocking their current command.
|
|
628
|
-
version: "0.1.
|
|
628
|
+
version: "0.1.236",
|
|
629
629
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
630
630
|
supportPolicy: {
|
|
631
|
-
latest: "0.1.
|
|
631
|
+
latest: "0.1.236",
|
|
632
632
|
minimumSupported: "0.1.53",
|
|
633
633
|
deprecatedBelow: "0.1.219",
|
|
634
634
|
commandMinimumSupported: [
|
|
@@ -1456,6 +1456,7 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
1456
1456
|
|
|
1457
1457
|
// ../shared_libs/play-runtime/output-size-limits.ts
|
|
1458
1458
|
var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
|
|
1459
|
+
var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
|
|
1459
1460
|
var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
|
|
1460
1461
|
var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
|
|
1461
1462
|
var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -12080,6 +12081,9 @@ function getRunIdFromLiveEvent(event) {
|
|
|
12080
12081
|
const packageRunId = getRunRecordFromPackage(payload)?.id;
|
|
12081
12082
|
return typeof packageRunId === "string" && packageRunId.trim() ? packageRunId : null;
|
|
12082
12083
|
}
|
|
12084
|
+
function eventConfirmsPlayRunLaunch(event) {
|
|
12085
|
+
return event.type === "play.run.status" && getEventPayload(event).launchConfirmed === true;
|
|
12086
|
+
}
|
|
12083
12087
|
function getStatusFromLiveEvent(event) {
|
|
12084
12088
|
if (event.type !== "play.run.status" && event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
|
|
12085
12089
|
return null;
|
|
@@ -12506,70 +12510,6 @@ async function waitForPlayCompletionByStream(input2) {
|
|
|
12506
12510
|
}
|
|
12507
12511
|
return null;
|
|
12508
12512
|
};
|
|
12509
|
-
const watchViaObserveTransport = async () => {
|
|
12510
|
-
const controller = new AbortController();
|
|
12511
|
-
let timedOut = false;
|
|
12512
|
-
const remaining = remainingWaitMs();
|
|
12513
|
-
const timeout = remaining === null ? null : setTimeout(
|
|
12514
|
-
() => {
|
|
12515
|
-
timedOut = true;
|
|
12516
|
-
controller.abort();
|
|
12517
|
-
},
|
|
12518
|
-
Math.max(1, remaining)
|
|
12519
|
-
);
|
|
12520
|
-
try {
|
|
12521
|
-
for await (const event of input2.client.observeRunEvents(
|
|
12522
|
-
input2.workflowId,
|
|
12523
|
-
{
|
|
12524
|
-
signal: controller.signal,
|
|
12525
|
-
fallback: "none",
|
|
12526
|
-
onNotice: input2.jsonOutput ? void 0 : (message) => input2.progress.writeLine(message)
|
|
12527
|
-
}
|
|
12528
|
-
)) {
|
|
12529
|
-
const terminal2 = await handleLiveEvent(event);
|
|
12530
|
-
if (terminal2) {
|
|
12531
|
-
return terminal2;
|
|
12532
|
-
}
|
|
12533
|
-
}
|
|
12534
|
-
} finally {
|
|
12535
|
-
if (timeout) {
|
|
12536
|
-
clearTimeout(timeout);
|
|
12537
|
-
}
|
|
12538
|
-
}
|
|
12539
|
-
const terminal = await fetchTerminalStatus();
|
|
12540
|
-
if (terminal) {
|
|
12541
|
-
return terminal;
|
|
12542
|
-
}
|
|
12543
|
-
if (timedOut) {
|
|
12544
|
-
assertPlayWaitNotTimedOut({ ...input2, lastPhase });
|
|
12545
|
-
}
|
|
12546
|
-
throw new DeeplineError(
|
|
12547
|
-
`Run observation for ${input2.workflowId} ended before a terminal status.`,
|
|
12548
|
-
void 0,
|
|
12549
|
-
"PLAY_LIVE_STREAM_ENDED",
|
|
12550
|
-
{ runId: input2.workflowId, workflowId: input2.workflowId }
|
|
12551
|
-
);
|
|
12552
|
-
};
|
|
12553
|
-
try {
|
|
12554
|
-
return await watchViaObserveTransport();
|
|
12555
|
-
} catch (error) {
|
|
12556
|
-
if (!(error instanceof RunObserveTransportUnavailableError)) {
|
|
12557
|
-
throw error;
|
|
12558
|
-
}
|
|
12559
|
-
if (!input2.jsonOutput) {
|
|
12560
|
-
input2.progress.writeLine(
|
|
12561
|
-
`[play watch] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
|
|
12562
|
-
);
|
|
12563
|
-
}
|
|
12564
|
-
recordCliTrace({
|
|
12565
|
-
phase: "cli.play_observe_transport_fallback",
|
|
12566
|
-
ms: Date.now() - input2.startedAt,
|
|
12567
|
-
ok: true,
|
|
12568
|
-
playName: input2.playName,
|
|
12569
|
-
workflowId: input2.workflowId,
|
|
12570
|
-
reason: error.reason
|
|
12571
|
-
});
|
|
12572
|
-
}
|
|
12573
12513
|
const streamOneWindow = async () => {
|
|
12574
12514
|
const controller = new AbortController();
|
|
12575
12515
|
let timedOut = false;
|
|
@@ -12702,6 +12642,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12702
12642
|
let launchedRevisionId = input2.request.revisionId?.trim() || null;
|
|
12703
12643
|
let eventCount = 0;
|
|
12704
12644
|
let firstRunIdMs = null;
|
|
12645
|
+
let launchConfirmed = false;
|
|
12705
12646
|
let lastPhase = null;
|
|
12706
12647
|
const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
|
|
12707
12648
|
() => {
|
|
@@ -12752,6 +12693,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12752
12693
|
input2.onRunStarted?.(eventRunId);
|
|
12753
12694
|
}
|
|
12754
12695
|
}
|
|
12696
|
+
if (eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
|
|
12697
|
+
launchConfirmed = true;
|
|
12698
|
+
}
|
|
12755
12699
|
const workflowId = lastKnownWorkflowId || "pending";
|
|
12756
12700
|
if (workflowId !== "pending" && !emittedDashboardUrl) {
|
|
12757
12701
|
if (!input2.jsonOutput) {
|
|
@@ -12828,6 +12772,16 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12828
12772
|
const finalStatus = getFinalStatusFromLiveEvent(event);
|
|
12829
12773
|
if (finalStatus) {
|
|
12830
12774
|
lastKnownWorkflowId ||= finalStatus.runId ?? "";
|
|
12775
|
+
if (finalStatus.status === "failed" && !finalStatus.runId) {
|
|
12776
|
+
return {
|
|
12777
|
+
...finalStatus,
|
|
12778
|
+
runId: "pending",
|
|
12779
|
+
name: input2.playName,
|
|
12780
|
+
playName: input2.playName,
|
|
12781
|
+
dashboardUrl,
|
|
12782
|
+
...launchedRevisionId ? { revisionId: launchedRevisionId } : {}
|
|
12783
|
+
};
|
|
12784
|
+
}
|
|
12831
12785
|
const canonicalTerminal = input2.request.profile === "absurd" ? await fetchKnownTerminalStatus() : {
|
|
12832
12786
|
...finalStatus,
|
|
12833
12787
|
dashboardUrl,
|
|
@@ -12859,6 +12813,34 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12859
12813
|
});
|
|
12860
12814
|
return canonicalTerminal;
|
|
12861
12815
|
}
|
|
12816
|
+
if (lastKnownWorkflowId && launchConfirmed) {
|
|
12817
|
+
if (timeout) {
|
|
12818
|
+
clearTimeout(timeout);
|
|
12819
|
+
}
|
|
12820
|
+
controller.abort();
|
|
12821
|
+
recordCliTrace({
|
|
12822
|
+
phase: "cli.play_start_stream_handoff_to_tail",
|
|
12823
|
+
ms: Date.now() - startedAt,
|
|
12824
|
+
ok: true,
|
|
12825
|
+
playName: input2.playName,
|
|
12826
|
+
workflowId: lastKnownWorkflowId,
|
|
12827
|
+
eventCount,
|
|
12828
|
+
firstRunIdMs,
|
|
12829
|
+
lastPhase
|
|
12830
|
+
});
|
|
12831
|
+
return waitForPlayCompletionByStream({
|
|
12832
|
+
client: input2.client,
|
|
12833
|
+
playName: input2.playName,
|
|
12834
|
+
workflowId: lastKnownWorkflowId,
|
|
12835
|
+
dashboardUrl,
|
|
12836
|
+
jsonOutput: input2.jsonOutput,
|
|
12837
|
+
emitLogs: input2.emitLogs,
|
|
12838
|
+
waitTimeoutMs: input2.waitTimeoutMs,
|
|
12839
|
+
startedAt,
|
|
12840
|
+
state,
|
|
12841
|
+
progress: input2.progress
|
|
12842
|
+
});
|
|
12843
|
+
}
|
|
12862
12844
|
}
|
|
12863
12845
|
} catch (error) {
|
|
12864
12846
|
if (timedOut) {
|
|
@@ -14031,7 +14013,7 @@ function playRunPackageTextLedgerIncomplete(pkg) {
|
|
|
14031
14013
|
}
|
|
14032
14014
|
async function resolvePlayRunOutputStatus(input2) {
|
|
14033
14015
|
const runId = input2.status.runId;
|
|
14034
|
-
if (!runId) {
|
|
14016
|
+
if (!runId || runId === "pending") {
|
|
14035
14017
|
return input2.status;
|
|
14036
14018
|
}
|
|
14037
14019
|
const streamedPackage = getPlayRunPackage(input2.status);
|
package/dist/cli/index.mjs
CHANGED
|
@@ -610,10 +610,10 @@ var SDK_RELEASE = {
|
|
|
610
610
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
611
611
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
612
612
|
// automatically without blocking their current command.
|
|
613
|
-
version: "0.1.
|
|
613
|
+
version: "0.1.236",
|
|
614
614
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
615
615
|
supportPolicy: {
|
|
616
|
-
latest: "0.1.
|
|
616
|
+
latest: "0.1.236",
|
|
617
617
|
minimumSupported: "0.1.53",
|
|
618
618
|
deprecatedBelow: "0.1.219",
|
|
619
619
|
commandMinimumSupported: [
|
|
@@ -1441,6 +1441,7 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
1441
1441
|
|
|
1442
1442
|
// ../shared_libs/play-runtime/output-size-limits.ts
|
|
1443
1443
|
var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
|
|
1444
|
+
var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
|
|
1444
1445
|
var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
|
|
1445
1446
|
var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
|
|
1446
1447
|
var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -12109,6 +12110,9 @@ function getRunIdFromLiveEvent(event) {
|
|
|
12109
12110
|
const packageRunId = getRunRecordFromPackage(payload)?.id;
|
|
12110
12111
|
return typeof packageRunId === "string" && packageRunId.trim() ? packageRunId : null;
|
|
12111
12112
|
}
|
|
12113
|
+
function eventConfirmsPlayRunLaunch(event) {
|
|
12114
|
+
return event.type === "play.run.status" && getEventPayload(event).launchConfirmed === true;
|
|
12115
|
+
}
|
|
12112
12116
|
function getStatusFromLiveEvent(event) {
|
|
12113
12117
|
if (event.type !== "play.run.status" && event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
|
|
12114
12118
|
return null;
|
|
@@ -12535,70 +12539,6 @@ async function waitForPlayCompletionByStream(input2) {
|
|
|
12535
12539
|
}
|
|
12536
12540
|
return null;
|
|
12537
12541
|
};
|
|
12538
|
-
const watchViaObserveTransport = async () => {
|
|
12539
|
-
const controller = new AbortController();
|
|
12540
|
-
let timedOut = false;
|
|
12541
|
-
const remaining = remainingWaitMs();
|
|
12542
|
-
const timeout = remaining === null ? null : setTimeout(
|
|
12543
|
-
() => {
|
|
12544
|
-
timedOut = true;
|
|
12545
|
-
controller.abort();
|
|
12546
|
-
},
|
|
12547
|
-
Math.max(1, remaining)
|
|
12548
|
-
);
|
|
12549
|
-
try {
|
|
12550
|
-
for await (const event of input2.client.observeRunEvents(
|
|
12551
|
-
input2.workflowId,
|
|
12552
|
-
{
|
|
12553
|
-
signal: controller.signal,
|
|
12554
|
-
fallback: "none",
|
|
12555
|
-
onNotice: input2.jsonOutput ? void 0 : (message) => input2.progress.writeLine(message)
|
|
12556
|
-
}
|
|
12557
|
-
)) {
|
|
12558
|
-
const terminal2 = await handleLiveEvent(event);
|
|
12559
|
-
if (terminal2) {
|
|
12560
|
-
return terminal2;
|
|
12561
|
-
}
|
|
12562
|
-
}
|
|
12563
|
-
} finally {
|
|
12564
|
-
if (timeout) {
|
|
12565
|
-
clearTimeout(timeout);
|
|
12566
|
-
}
|
|
12567
|
-
}
|
|
12568
|
-
const terminal = await fetchTerminalStatus();
|
|
12569
|
-
if (terminal) {
|
|
12570
|
-
return terminal;
|
|
12571
|
-
}
|
|
12572
|
-
if (timedOut) {
|
|
12573
|
-
assertPlayWaitNotTimedOut({ ...input2, lastPhase });
|
|
12574
|
-
}
|
|
12575
|
-
throw new DeeplineError(
|
|
12576
|
-
`Run observation for ${input2.workflowId} ended before a terminal status.`,
|
|
12577
|
-
void 0,
|
|
12578
|
-
"PLAY_LIVE_STREAM_ENDED",
|
|
12579
|
-
{ runId: input2.workflowId, workflowId: input2.workflowId }
|
|
12580
|
-
);
|
|
12581
|
-
};
|
|
12582
|
-
try {
|
|
12583
|
-
return await watchViaObserveTransport();
|
|
12584
|
-
} catch (error) {
|
|
12585
|
-
if (!(error instanceof RunObserveTransportUnavailableError)) {
|
|
12586
|
-
throw error;
|
|
12587
|
-
}
|
|
12588
|
-
if (!input2.jsonOutput) {
|
|
12589
|
-
input2.progress.writeLine(
|
|
12590
|
-
`[play watch] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
|
|
12591
|
-
);
|
|
12592
|
-
}
|
|
12593
|
-
recordCliTrace({
|
|
12594
|
-
phase: "cli.play_observe_transport_fallback",
|
|
12595
|
-
ms: Date.now() - input2.startedAt,
|
|
12596
|
-
ok: true,
|
|
12597
|
-
playName: input2.playName,
|
|
12598
|
-
workflowId: input2.workflowId,
|
|
12599
|
-
reason: error.reason
|
|
12600
|
-
});
|
|
12601
|
-
}
|
|
12602
12542
|
const streamOneWindow = async () => {
|
|
12603
12543
|
const controller = new AbortController();
|
|
12604
12544
|
let timedOut = false;
|
|
@@ -12731,6 +12671,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12731
12671
|
let launchedRevisionId = input2.request.revisionId?.trim() || null;
|
|
12732
12672
|
let eventCount = 0;
|
|
12733
12673
|
let firstRunIdMs = null;
|
|
12674
|
+
let launchConfirmed = false;
|
|
12734
12675
|
let lastPhase = null;
|
|
12735
12676
|
const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
|
|
12736
12677
|
() => {
|
|
@@ -12781,6 +12722,9 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12781
12722
|
input2.onRunStarted?.(eventRunId);
|
|
12782
12723
|
}
|
|
12783
12724
|
}
|
|
12725
|
+
if (eventConfirmsPlayRunLaunch(event) && eventRunId === lastKnownWorkflowId) {
|
|
12726
|
+
launchConfirmed = true;
|
|
12727
|
+
}
|
|
12784
12728
|
const workflowId = lastKnownWorkflowId || "pending";
|
|
12785
12729
|
if (workflowId !== "pending" && !emittedDashboardUrl) {
|
|
12786
12730
|
if (!input2.jsonOutput) {
|
|
@@ -12857,6 +12801,16 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12857
12801
|
const finalStatus = getFinalStatusFromLiveEvent(event);
|
|
12858
12802
|
if (finalStatus) {
|
|
12859
12803
|
lastKnownWorkflowId ||= finalStatus.runId ?? "";
|
|
12804
|
+
if (finalStatus.status === "failed" && !finalStatus.runId) {
|
|
12805
|
+
return {
|
|
12806
|
+
...finalStatus,
|
|
12807
|
+
runId: "pending",
|
|
12808
|
+
name: input2.playName,
|
|
12809
|
+
playName: input2.playName,
|
|
12810
|
+
dashboardUrl,
|
|
12811
|
+
...launchedRevisionId ? { revisionId: launchedRevisionId } : {}
|
|
12812
|
+
};
|
|
12813
|
+
}
|
|
12860
12814
|
const canonicalTerminal = input2.request.profile === "absurd" ? await fetchKnownTerminalStatus() : {
|
|
12861
12815
|
...finalStatus,
|
|
12862
12816
|
dashboardUrl,
|
|
@@ -12888,6 +12842,34 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
12888
12842
|
});
|
|
12889
12843
|
return canonicalTerminal;
|
|
12890
12844
|
}
|
|
12845
|
+
if (lastKnownWorkflowId && launchConfirmed) {
|
|
12846
|
+
if (timeout) {
|
|
12847
|
+
clearTimeout(timeout);
|
|
12848
|
+
}
|
|
12849
|
+
controller.abort();
|
|
12850
|
+
recordCliTrace({
|
|
12851
|
+
phase: "cli.play_start_stream_handoff_to_tail",
|
|
12852
|
+
ms: Date.now() - startedAt,
|
|
12853
|
+
ok: true,
|
|
12854
|
+
playName: input2.playName,
|
|
12855
|
+
workflowId: lastKnownWorkflowId,
|
|
12856
|
+
eventCount,
|
|
12857
|
+
firstRunIdMs,
|
|
12858
|
+
lastPhase
|
|
12859
|
+
});
|
|
12860
|
+
return waitForPlayCompletionByStream({
|
|
12861
|
+
client: input2.client,
|
|
12862
|
+
playName: input2.playName,
|
|
12863
|
+
workflowId: lastKnownWorkflowId,
|
|
12864
|
+
dashboardUrl,
|
|
12865
|
+
jsonOutput: input2.jsonOutput,
|
|
12866
|
+
emitLogs: input2.emitLogs,
|
|
12867
|
+
waitTimeoutMs: input2.waitTimeoutMs,
|
|
12868
|
+
startedAt,
|
|
12869
|
+
state,
|
|
12870
|
+
progress: input2.progress
|
|
12871
|
+
});
|
|
12872
|
+
}
|
|
12891
12873
|
}
|
|
12892
12874
|
} catch (error) {
|
|
12893
12875
|
if (timedOut) {
|
|
@@ -14060,7 +14042,7 @@ function playRunPackageTextLedgerIncomplete(pkg) {
|
|
|
14060
14042
|
}
|
|
14061
14043
|
async function resolvePlayRunOutputStatus(input2) {
|
|
14062
14044
|
const runId = input2.status.runId;
|
|
14063
|
-
if (!runId) {
|
|
14045
|
+
if (!runId || runId === "pending") {
|
|
14064
14046
|
return input2.status;
|
|
14065
14047
|
}
|
|
14066
14048
|
const streamedPackage = getPlayRunPackage(input2.status);
|
package/dist/index.js
CHANGED
|
@@ -424,10 +424,10 @@ var SDK_RELEASE = {
|
|
|
424
424
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
425
425
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
426
426
|
// automatically without blocking their current command.
|
|
427
|
-
version: "0.1.
|
|
427
|
+
version: "0.1.236",
|
|
428
428
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
429
429
|
supportPolicy: {
|
|
430
|
-
latest: "0.1.
|
|
430
|
+
latest: "0.1.236",
|
|
431
431
|
minimumSupported: "0.1.53",
|
|
432
432
|
deprecatedBelow: "0.1.219",
|
|
433
433
|
commandMinimumSupported: [
|
|
@@ -1255,6 +1255,7 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
1255
1255
|
|
|
1256
1256
|
// ../shared_libs/play-runtime/output-size-limits.ts
|
|
1257
1257
|
var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
|
|
1258
|
+
var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
|
|
1258
1259
|
var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
|
|
1259
1260
|
var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
|
|
1260
1261
|
var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
|
package/dist/index.mjs
CHANGED
|
@@ -354,10 +354,10 @@ var SDK_RELEASE = {
|
|
|
354
354
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
355
355
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
356
356
|
// automatically without blocking their current command.
|
|
357
|
-
version: "0.1.
|
|
357
|
+
version: "0.1.236",
|
|
358
358
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
359
359
|
supportPolicy: {
|
|
360
|
-
latest: "0.1.
|
|
360
|
+
latest: "0.1.236",
|
|
361
361
|
minimumSupported: "0.1.53",
|
|
362
362
|
deprecatedBelow: "0.1.219",
|
|
363
363
|
commandMinimumSupported: [
|
|
@@ -1185,6 +1185,7 @@ function createSecretRedactionContext(initialValues = []) {
|
|
|
1185
1185
|
|
|
1186
1186
|
// ../shared_libs/play-runtime/output-size-limits.ts
|
|
1187
1187
|
var encoder = typeof TextEncoder === "undefined" ? null : new TextEncoder();
|
|
1188
|
+
var LEDGER_TERMINAL_RESULT_MAX_BYTES = 768 * 1024;
|
|
1188
1189
|
var CUSTOMER_OUTPUT_VALUE_MAX_BYTES = 1 * 1024 * 1024;
|
|
1189
1190
|
var CUSTOMER_OUTPUT_TOTAL_MAX_BYTES = 2 * 1024 * 1024;
|
|
1190
1191
|
var RUNTIME_RECEIPT_OUTPUT_MAX_BYTES = 10 * 1024 * 1024;
|