@zibby/agent-workflow 2.0.4 → 2.0.6
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/README.md +1 -0
- package/dist/failure-class.d.ts +193 -0
- package/dist/failure-class.js +1 -0
- package/dist/fetch-deadline.d.ts +78 -0
- package/dist/fetch-deadline.js +1 -0
- package/dist/graph-compiler.js +24 -24
- package/dist/graph.js +20 -20
- package/dist/in-process-subgraph.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +33 -33
- package/dist/node.js +18 -18
- package/dist/sub-graph-executor.d.ts +2 -0
- package/dist/sub-graph-executor.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,6 +167,7 @@ If you want to compose Claude Code + Codex + Gemini into one pipeline with struc
|
|
|
167
167
|
| `Graph` | The DAG. `addNode`, `addEdge`, `addConditionalEdges`, `setEntryPoint`. |
|
|
168
168
|
| Fan-out | Call `addEdge` more than once from the same node and **every** branch runs, each carrying on through its own children. Branches run sequentially in declaration order (depth-first: a branch finishes before the next starts). Where branches converge, the shared node waits for all of them and runs **once** — see [Fan-out](#fan-out) below. |
|
|
169
169
|
| `Node` | One agent invocation. Config: `prompt`, `outputSchema` (Zod), optional `agent`, `retries`, `skills`. |
|
|
170
|
+
| Retries | `retries: N` = N retries on ANY failure (N+1 attempts), no delay — declared per node. **On top of that**, a failure the engine can POSITIVELY identify as a transient provider failure (a dead stream, a 429, an upstream 5xx) buys up to `AGENT_TRANSIENT_RETRIES` extra attempts (default 2) with exponential backoff (`AGENT_TRANSIENT_BACKOFF_MS`, default 5000, 0 = no wait). One loop, one decider — `classifyFailure` in `failure-class.ts`. A failure it cannot positively identify is DETERMINISTIC and is never retried, because retrying a bug is an infinite loop that burns money. |
|
|
170
171
|
| Sub-graph node | `addNode(name, { workflow: 'other-name', ... })` — dispatches another deployed workflow as a child. Sync (poll + merge) or async (`async: true`, fire-and-forget). See [Sub-graphs](#sub-graphs) below. |
|
|
171
172
|
| `AgentStrategy` | Abstract base. Implement `canHandle(ctx)` and `invoke(prompt, opts)`. |
|
|
172
173
|
| `registerStrategy()` | Tells the engine what agents are available. Selected by node `agent` field → `config.agents[name]` → `state.agentType`. |
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAILURE CLASSIFICATION — the ONE authority on "is this failure the provider
|
|
3
|
+
* having a bad second, or is it a real bug?"
|
|
4
|
+
*
|
|
5
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
* WHY THIS FILE EXISTS (execution 42b920ae, 2026-08-25, the founder's own box)
|
|
7
|
+
*
|
|
8
|
+
* A `frontend-specialist` member had ALREADY built its feature, driven a real
|
|
9
|
+
* browser at it, and taken the screenshot ("Recently viewed … — newest-first
|
|
10
|
+
* ✓"). It was writing its handoff file when the provider's stream died:
|
|
11
|
+
*
|
|
12
|
+
* API Error: Stream idle timeout - partial response received
|
|
13
|
+
*
|
|
14
|
+
* Sixteen minutes of finished work were thrown away, no PR was pushed, and the
|
|
15
|
+
* ticket spent one of its two attempts. It later hit the cap and parked — so a
|
|
16
|
+
* network blip masqueraded as "the fleet cannot do this ticket".
|
|
17
|
+
*
|
|
18
|
+
* The stream dying is the provider's SDK and cannot be prevented. What happens
|
|
19
|
+
* NEXT is entirely ours, and it needs exactly one thing this codebase did not
|
|
20
|
+
* have: a way to tell a bad second apart from a bug. That is this file.
|
|
21
|
+
*
|
|
22
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
* TWO RULES THIS FILE KEEPS, BOTH LEARNED THE EXPENSIVE WAY
|
|
24
|
+
*
|
|
25
|
+
* 1. **"Retry on anything I can't explain" is an infinite money pump.** A
|
|
26
|
+
* deterministic failure — a bad prompt, a schema violation, a missing
|
|
27
|
+
* credential, a node returning `{success:false}` — fails identically on
|
|
28
|
+
* every attempt, so a catch-all classifier turns one wasted turn into N.
|
|
29
|
+
* Therefore the default is DETERMINISTIC: a failure is retried only when
|
|
30
|
+
* something POSITIVELY identifies it as transient. Nothing about "I do not
|
|
31
|
+
* recognise this" is evidence of transience.
|
|
32
|
+
*
|
|
33
|
+
* 2. **The provider's own classification is a KIND, not a MESSAGE — and its
|
|
34
|
+
* catch-all is not a verdict.** The Claude Agent SDK stamps an error kind on
|
|
35
|
+
* the message (`authentication_failed` … `server_error` | `unknown`), and
|
|
36
|
+
* `unknown` is what a dead stream arrives as. Reading `unknown` as "transient"
|
|
37
|
+
* would smuggle rule 1's catch-all back in through the provider's front door;
|
|
38
|
+
* reading it as "deterministic" would leave the original bug unfixed. So an
|
|
39
|
+
* inconclusive kind DECIDES NOTHING and hands over to the text patterns —
|
|
40
|
+
* which name real network shapes and nothing else. A kind that IS conclusive
|
|
41
|
+
* is final: an `authentication_failed` whose text happens to say "timed out"
|
|
42
|
+
* must never be retried.
|
|
43
|
+
*
|
|
44
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
* ONE DECIDER, THREE CALLERS. `classifyFailure` is consulted by:
|
|
46
|
+
* • `node.ts` — the ONE retry loop (there is no second one; see the long note
|
|
47
|
+
* in node.ts about the graph-level gate that was deleted for being one);
|
|
48
|
+
* • the strategies — via `formatProviderError`, so the message a failure
|
|
49
|
+
* carries and the classifier that reads it back are the SAME module and can
|
|
50
|
+
* never drift (🔗 TWO-PLACES);
|
|
51
|
+
* • the fleet's reconcile step — which reads a FINISHED run's `error` STRING
|
|
52
|
+
* off the execution record and must reach the same verdict the engine
|
|
53
|
+
* reached in-process. Hence: `classifyFailure` accepts an Error OR a string.
|
|
54
|
+
*/
|
|
55
|
+
/** The failure classes. `inconclusive` is a verdict about the EVIDENCE, never about the failure. */
|
|
56
|
+
export type FailureClass = 'transient' | 'deterministic';
|
|
57
|
+
/**
|
|
58
|
+
* The Claude Agent SDK's own error-kind enum, each mapped to what it PROVES.
|
|
59
|
+
*
|
|
60
|
+
* Source of truth for the key set: the SDK's `assistant.error` / `api_retry.error`
|
|
61
|
+
* schema (one shared enum). Verified against the shipped CLI, 2026-08-25:
|
|
62
|
+
* ["authentication_failed","oauth_org_not_allowed","billing_error",
|
|
63
|
+
* "rate_limit","invalid_request","server_error","unknown","max_output_tokens"]
|
|
64
|
+
*
|
|
65
|
+
* A kind we have never seen is deliberately absent, and an absent kind is
|
|
66
|
+
* INCONCLUSIVE (never "transient") — a new provider kind must be classified by
|
|
67
|
+
* a human reading its meaning, not adopted into the retry budget by default.
|
|
68
|
+
*/
|
|
69
|
+
export declare const PROVIDER_ERROR_KIND_CLASS: Readonly<Record<string, FailureClass | 'inconclusive'>>;
|
|
70
|
+
/**
|
|
71
|
+
* TRANSIENT TEXT SHAPES — an allowlist, not a heuristic.
|
|
72
|
+
*
|
|
73
|
+
* Every entry is a wire/stream failure that a second attempt can genuinely
|
|
74
|
+
* succeed at, and every entry was taken from a real error string (the CLI's own
|
|
75
|
+
* strings, undici/node network errors, HTTP status lines). Deliberately NOT
|
|
76
|
+
* here, because they repeat identically: prompt/context-length errors, credit
|
|
77
|
+
* balance, invalid API key, "No session token", zod validation output, a node
|
|
78
|
+
* returning `{success:false}`, and the engine's own `API stuck in loop` (a turn
|
|
79
|
+
* that looped once will loop again, and it is the most expensive thing to redo).
|
|
80
|
+
*/
|
|
81
|
+
export declare const TRANSIENT_MESSAGE_PATTERNS: readonly RegExp[];
|
|
82
|
+
/**
|
|
83
|
+
* The message a strategy throws when the PROVIDER reported a failure.
|
|
84
|
+
*
|
|
85
|
+
* This exists because the alternative shipped, and it read — in the founder's
|
|
86
|
+
* execution record, as the entire explanation of a 16-minute loss —
|
|
87
|
+
*
|
|
88
|
+
* Node 'develop' failed after 1 attempt(s): unknown
|
|
89
|
+
*
|
|
90
|
+
* `unknown` was the SDK's error KIND. The strategy took it for the error
|
|
91
|
+
* MESSAGE and discarded the assistant text sitting in the same object, which
|
|
92
|
+
* said `API Error: Stream idle timeout - partial response received`. So this
|
|
93
|
+
* function keeps ALL THREE facts, and keeps the kind in a bracket so a later
|
|
94
|
+
* reader (the fleet's reconcile, which only ever sees the persisted string) can
|
|
95
|
+
* recover it without re-guessing. Formatter and parser in ONE module, on
|
|
96
|
+
* purpose.
|
|
97
|
+
*/
|
|
98
|
+
export declare function formatProviderError({ kind, status, text }: {
|
|
99
|
+
kind?: string | null;
|
|
100
|
+
status?: number | string | null;
|
|
101
|
+
text?: string | null;
|
|
102
|
+
}): string;
|
|
103
|
+
/** The provider's error kind, from a structured field or from a formatted message. */
|
|
104
|
+
export declare function providerErrorKindOf(x: unknown): string | null;
|
|
105
|
+
/** The provider's HTTP status, from a structured field or from a formatted message. */
|
|
106
|
+
export declare function providerErrorStatusOf(x: unknown): number | null;
|
|
107
|
+
/**
|
|
108
|
+
* Is this failure worth one more attempt?
|
|
109
|
+
*
|
|
110
|
+
* Accepts an Error (in-process, from a strategy) OR a string (out-of-process:
|
|
111
|
+
* a finished run's `error` field off the execution record). Same verdict either
|
|
112
|
+
* way — that is the whole point of accepting both.
|
|
113
|
+
*
|
|
114
|
+
* ORDER IS THE DESIGN:
|
|
115
|
+
* 1. cancellation → deterministic, always, first. Never bill a stop twice.
|
|
116
|
+
* 2. conclusive provider kind → final. A conclusive kind OUTRANKS the text,
|
|
117
|
+
* so an auth failure that mentions a timeout is not retried.
|
|
118
|
+
* 3. provider HTTP status (429 / 5xx) → transient. Structured, unambiguous.
|
|
119
|
+
* 4. the text allowlist → transient only on a named network shape.
|
|
120
|
+
* 5. otherwise → deterministic. "I don't recognise this" is not evidence.
|
|
121
|
+
*/
|
|
122
|
+
export declare function classifyFailure(x: unknown): FailureClass;
|
|
123
|
+
/** Convenience predicate over {@link classifyFailure}. */
|
|
124
|
+
export declare function isTransientFailure(x: unknown): boolean;
|
|
125
|
+
/** Default extra attempts a TRANSIENT failure may buy, on top of a node's declared `retries`. */
|
|
126
|
+
export declare const DEFAULT_TRANSIENT_RETRIES = 2;
|
|
127
|
+
/** Hard ceiling on the knob — a typo in an env var must not multiply the bill without limit. */
|
|
128
|
+
export declare const MAX_TRANSIENT_RETRIES = 5;
|
|
129
|
+
/**
|
|
130
|
+
* How many extra attempts a transient failure may buy.
|
|
131
|
+
*
|
|
132
|
+
* Bounded on purpose: a genuinely broken provider costs at most
|
|
133
|
+
* (1 + budget) turns per node instead of an unbounded stream of them. Brand
|
|
134
|
+
* neutral (CLAUDE.md § NEW identifiers) and `0` is a legal, honest "off".
|
|
135
|
+
*/
|
|
136
|
+
export declare function transientRetryBudget(env?: Record<string, any>): number;
|
|
137
|
+
/** First-retry delay, doubling per retry. Overridable; `0` means "no wait". */
|
|
138
|
+
export declare const DEFAULT_TRANSIENT_BACKOFF_MS = 5000;
|
|
139
|
+
/** Ceiling on one wait, so a doubling series can never park a run for minutes. */
|
|
140
|
+
export declare const MAX_TRANSIENT_BACKOFF_MS = 30000;
|
|
141
|
+
/**
|
|
142
|
+
* Base delay for the backoff series. Brand-neutral knob; `0` disables the wait
|
|
143
|
+
* entirely (what a test wants, and what an operator on a private endpoint with
|
|
144
|
+
* no rate limit might legitimately want too).
|
|
145
|
+
*/
|
|
146
|
+
export declare function transientBackoffBaseMs(env?: Record<string, any>): number;
|
|
147
|
+
/**
|
|
148
|
+
* Backoff before transient attempt `n` (1-based): 5s, 10s, 20s, capped at 30s,
|
|
149
|
+
* ±20% jitter so a fleet of members that all failed on the same upstream blip
|
|
150
|
+
* does not come back in lockstep.
|
|
151
|
+
*/
|
|
152
|
+
export declare function transientBackoffMs(n: number, rand?: () => number, baseMs?: number): number;
|
|
153
|
+
/** What the budget says to do after one failed attempt. */
|
|
154
|
+
export interface AttemptDecision {
|
|
155
|
+
/** Run the node again? */
|
|
156
|
+
retry: boolean;
|
|
157
|
+
/** Which budget paid for it — `declared` = the node's own `retries`, `transient` = this file's. */
|
|
158
|
+
paidBy?: 'declared' | 'transient';
|
|
159
|
+
/** Sleep this long first (only ever non-zero for a TRANSIENT failure). */
|
|
160
|
+
delayMs: number;
|
|
161
|
+
/** The classification that produced this decision. */
|
|
162
|
+
failureClass: FailureClass;
|
|
163
|
+
/** 1-based index within `paidBy`'s budget, for the log line. */
|
|
164
|
+
index?: number;
|
|
165
|
+
/** Size of `paidBy`'s budget, for the log line. */
|
|
166
|
+
of?: number;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* THE attempt budget — ONE implementation, shared by both of `node.ts`'s retry
|
|
170
|
+
* paths (custom-execute and LLM) so they can never drift into two policies.
|
|
171
|
+
*
|
|
172
|
+
* TWO budgets, spent in this order, and the order is the back-compat guarantee:
|
|
173
|
+
*
|
|
174
|
+
* 1. `retries` — the node's OWN declaration, honoured for ANY failure exactly
|
|
175
|
+
* as it always was. A node declaring `retries: 2` still gets 2 retries on a
|
|
176
|
+
* schema violation, with no delay. Nothing here changes that.
|
|
177
|
+
* 2. `transient` — extra attempts, available ONLY when `classifyFailure` says
|
|
178
|
+
* transient. This is the new capacity, and it is why a dead stream no
|
|
179
|
+
* longer destroys a member's finished work.
|
|
180
|
+
*
|
|
181
|
+
* A TRANSIENT failure gets the backoff sleep whichever budget pays for it —
|
|
182
|
+
* hammering an overloaded upstream three times in 200ms is not a retry policy.
|
|
183
|
+
*/
|
|
184
|
+
export declare function createAttemptBudget(retries: number, { env, rand }?: {
|
|
185
|
+
env?: Record<string, any>;
|
|
186
|
+
rand?: () => number;
|
|
187
|
+
}): {
|
|
188
|
+
readonly attemptsMade: number;
|
|
189
|
+
readonly transientUsed: number;
|
|
190
|
+
readonly transientMax: number;
|
|
191
|
+
/** Record one failure and say whether to run again. */
|
|
192
|
+
next(failure: unknown): AttemptDecision;
|
|
193
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var N=Object.freeze({authentication_failed:"deterministic",oauth_org_not_allowed:"deterministic",billing_error:"deterministic",invalid_request:"deterministic",max_output_tokens:"deterministic",rate_limit:"transient",server_error:"transient",unknown:"inconclusive"}),y=Object.freeze([/stream idle timeout/i,/partial response received/i,/no chunks received/i,/stream ended without receiving any events/i,/stream completed without receiving/i,/socket hang up/i,/premature close/i,/\b(?:ECONNRESET|ECONNABORTED|ECONNREFUSED|EPIPE|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|ENETRESET|EHOSTUNREACH)\b/,/\bfetch failed\b/i,/\bnetwork (?:error|timeout)\b/i,/request timed out/i,/\brequest timeout\b/i,/\boverloaded(?:_error)?\b/i,/\brate[_ ]limit(?:_error|ed)?\b/i,/\b(?:HTTP|http_status|status(?:\s*code)?)[:= ]\s*(?:429|5\d\d)\b/i,/\b(?:429|500|502|503|504|529)\s+(?:too many requests|internal server error|bad gateway|service unavailable|gateway time-?out|overloaded)\b/i]),_=Object.freeze([/\bAbortError\b/,/\bAPIUserAbortError\b/,/\boperation was aborted\b/i,/\bcanceled by parent abort\b/i,/\bstopped by operator\b/i]),m=/provider error \[([a-z_]+)(?:\s+http\s+(\d{3}))?\]/i;function v({kind:t,status:e,text:r}){let n=typeof t=="string"&&t.trim()?t.trim():"unclassified",i=Number(e),s=Number.isInteger(i)&&i>=100&&i<=599?` http ${i}`:"",o=(typeof r=="string"?r.trim():"")||"the provider reported a failure but gave no message";return`provider error [${n}${s}]: ${o}`}function d(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t!="object")return String(t);let e=t;return[e.message,e.name,e.code,e.providerErrorText,e.cause?.message??(typeof e.cause=="string"?e.cause:void 0),e.cause?.code].filter(n=>typeof n=="string"||typeof n=="number").join(" | ")}function g(t){if(t&&typeof t=="object"){let r=t.providerErrorKind;if(typeof r=="string"&&r.trim())return r.trim()}let e=m.exec(d(t));return e?e[1].toLowerCase():null}function T(t){if(t&&typeof t=="object"){let n=Number(t.providerErrorStatus);if(Number.isInteger(n)&&n>=100&&n<=599)return n}let e=m.exec(d(t)),r=e&&e[2]?Number(e[2]):NaN;return Number.isInteger(r)?r:null}function b(t){let e=d(t);if(!e)return"deterministic";for(let i of _)if(i.test(e))return"deterministic";let r=g(t);if(r){let i=N[r];if(i==="transient"||i==="deterministic")return i}let n=T(t);if(n===429||n>=500&&n<=599)return"transient";for(let i of y)if(i.test(e))return"transient";return"deterministic"}function S(t){return b(t)==="transient"}var l=2,R=5;function A(t=process.env){let e=t?.AGENT_TRANSIENT_RETRIES;if(e==null||String(e).trim()==="")return l;let r=Number(e);return!Number.isFinite(r)||r<0?l:Math.min(R,Math.floor(r))}var c=5e3,p=3e4;function M(t=process.env){let e=t?.AGENT_TRANSIENT_BACKOFF_MS;if(e==null||String(e).trim()==="")return c;let r=Number(e);return!Number.isFinite(r)||r<0?c:Math.min(p,Math.floor(r))}function f(t,e=Math.random,r=c){if(!(r>0))return 0;let n=Math.max(1,Math.floor(Number(t)||1)),i=Math.min(p,r*2**(n-1)),s=1+(e()-.5)*.4;return Math.max(1,Math.round(i*s))}function h(t,{env:e=process.env,rand:r=Math.random}={}){let n=Math.max(0,Math.floor(Number(t)||0)),i=A(e),s=M(e),a=0,o=0;return{get attemptsMade(){return 1+a+o},get transientUsed(){return o},get transientMax(){return i},next(E){let u=b(E);return a<n?(a+=1,{retry:!0,paidBy:"declared",delayMs:u==="transient"?f(a,r,s):0,failureClass:u,index:a,of:n}):u==="transient"&&o<i?(o+=1,{retry:!0,paidBy:"transient",delayMs:f(o,r,s),failureClass:u,index:o,of:i}):{retry:!1,delayMs:0,failureClass:u}}}}export{c as DEFAULT_TRANSIENT_BACKOFF_MS,l as DEFAULT_TRANSIENT_RETRIES,p as MAX_TRANSIENT_BACKOFF_MS,R as MAX_TRANSIENT_RETRIES,N as PROVIDER_ERROR_KIND_CLASS,y as TRANSIENT_MESSAGE_PATTERNS,b as classifyFailure,h as createAttemptBudget,v as formatProviderError,S as isTransientFailure,g as providerErrorKindOf,T as providerErrorStatusOf,M as transientBackoffBaseMs,f as transientBackoffMs,A as transientRetryBudget};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch deadlines — the ONE declaration, so the engine's HTTP doors cannot
|
|
3
|
+
* drift apart.
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS FILE EXISTS AT ALL. Node's global fetch has NO default timeout, and
|
|
7
|
+
* A HANG IS NOT A THROW: a connection that is accepted and then never answered
|
|
8
|
+
* is not an error any `catch` can see, it is a process that stops. Every HTTP
|
|
9
|
+
* door in this engine is already written for failure — a trigger rejection is
|
|
10
|
+
* booked per-child by the caller's `Promise.allSettled`, a poll transport throw
|
|
11
|
+
* is retried, a `begin` failure falls back to the HTTP path — and NONE of that
|
|
12
|
+
* code can run for the one failure mode that actually costs a run.
|
|
13
|
+
*
|
|
14
|
+
* MEASURED, not hypothetical: board-runner run 4b49371e (2026-08-24) sat 7m33s
|
|
15
|
+
* inside the identical unbounded shape until the container watchdog killed it,
|
|
16
|
+
* and a tick that had already done all of its work recorded nothing. The same
|
|
17
|
+
* class has been closed in workflow-templates' lib/kb.js (d7e3184),
|
|
18
|
+
* lib/platform-api.js (7a355cc), _shared/tracker.js (539483e) and
|
|
19
|
+
* @zibby/core's backend-client.js.
|
|
20
|
+
*
|
|
21
|
+
* WHY THE HELPERS LIVE HERE AND NOT NEXT TO THEIR CALL SITES. The engine has
|
|
22
|
+
* two files that dispatch a child — `sub-graph-executor.ts` (HTTP) and
|
|
23
|
+
* `in-process-subgraph.ts` (in-process, which FALLS BACK to the other) — and
|
|
24
|
+
* they are two halves of one dispatch. Copying a clamp, a `TimeoutError` check
|
|
25
|
+
* and a budget into both is precisely the TWO-PLACES shape that produced every
|
|
26
|
+
* incident this rule was written for: a pair that must agree with nothing to
|
|
27
|
+
* scream when it drifts. One declaration, N consumers, no tripwire needed
|
|
28
|
+
* because there is nothing to keep in sync.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ Deliberately DEPENDENCY-FREE (not even the logger) so any module can
|
|
31
|
+
* import it without a cycle — `in-process-subgraph.ts` is imported BY
|
|
32
|
+
* `sub-graph-executor.ts`, so the budgets could not have lived in the latter.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SUBGRAPH_TRIGGER_TIMEOUT_MS = 30000;
|
|
35
|
+
export declare const SUBGRAPH_POLL_TIMEOUT_MS = 15000;
|
|
36
|
+
export declare const SUBGRAPH_BUNDLE_TIMEOUT_MS = 60000;
|
|
37
|
+
/** curl's connect phase only — a stalled DNS/TCP handshake, distinct from a
|
|
38
|
+
* slow but progressing transfer. Not separately overridable: it is a fixed
|
|
39
|
+
* fraction of the class, and one more knob would be one more thing to drift. */
|
|
40
|
+
export declare const SUBGRAPH_CONNECT_TIMEOUT_MS = 10000;
|
|
41
|
+
export declare const TIMEOUT_FLOOR_MS = 1000;
|
|
42
|
+
export declare const TIMEOUT_CEILING_MS = 120000;
|
|
43
|
+
/**
|
|
44
|
+
* Read a budget from the environment, or fall back. Clamped to
|
|
45
|
+
* [TIMEOUT_FLOOR_MS, TIMEOUT_CEILING_MS]; anything unparseable or non-positive
|
|
46
|
+
* (`0`, `-1`, `''`, `'soon'`) falls back rather than disabling the bound.
|
|
47
|
+
*/
|
|
48
|
+
export declare function timeoutMsFrom(knob: string, fallback: number, env?: any): number;
|
|
49
|
+
/**
|
|
50
|
+
* ONE deadline for ONE call: the `signal` the request AND its body reads share
|
|
51
|
+
* — a response whose headers arrive and whose body then stalls is the same
|
|
52
|
+
* hang, and bounding only the first half leaves the door open — plus the
|
|
53
|
+
* `label` a timeout reports itself with.
|
|
54
|
+
*
|
|
55
|
+
* The label names the budget AND its knob because whoever reads the run log has
|
|
56
|
+
* to tell a SLOW control plane (raise the knob, or accept the failure) from a
|
|
57
|
+
* BROKEN one (an ordinary transport error, which keeps its existing wording).
|
|
58
|
+
* One spelling for both is how a hang stays invisible for as long as this one
|
|
59
|
+
* did.
|
|
60
|
+
*/
|
|
61
|
+
export declare function makeDeadline(ms: number, knob: string): {
|
|
62
|
+
signal: AbortSignal;
|
|
63
|
+
label: string;
|
|
64
|
+
};
|
|
65
|
+
/** Build a deadline straight from a knob + default. */
|
|
66
|
+
export declare function deadlineFor(knob: string, fallback: number): {
|
|
67
|
+
signal: AbortSignal;
|
|
68
|
+
label: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* `AbortSignal.timeout` aborts with a `TimeoutError` DOMException (undici
|
|
72
|
+
* rejects the fetch — and any in-flight body read — with that same reason); a
|
|
73
|
+
* caller-cancelled signal aborts with `AbortError`. Both mean "we stopped
|
|
74
|
+
* waiting"; NEITHER means "the far end said no", which is why every call site
|
|
75
|
+
* branches on this before deciding whether to reword an error or rethrow it
|
|
76
|
+
* unchanged.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isTimeoutError(err: any): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i=3e4,M=15e3,T=6e4,u=1e4,s=1e3,E=12e4;function o(t,r,e=process.env){let n=Number(e[t]);return Number.isFinite(n)&&n>0?Math.min(12e4,Math.max(1e3,Math.floor(n))):r}function _(t,r){return{signal:AbortSignal.timeout(t),label:`after ${t}ms (${r})`}}function a(t,r){return _(o(t,r),t)}function m(t){return t?.name==="TimeoutError"||t?.name==="AbortError"}export{T as SUBGRAPH_BUNDLE_TIMEOUT_MS,u as SUBGRAPH_CONNECT_TIMEOUT_MS,M as SUBGRAPH_POLL_TIMEOUT_MS,i as SUBGRAPH_TRIGGER_TIMEOUT_MS,E as TIMEOUT_CEILING_MS,s as TIMEOUT_FLOOR_MS,a as deadlineFor,m as isTimeoutError,_ as makeDeadline,o as timeoutMsFrom};
|