@guuey/agent-client 0.13.0 → 0.14.1

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.
@@ -19,6 +19,26 @@ export interface SaturationRetryOptions {
19
19
  * real 15s timer; production uses an abort-aware `setTimeout`.
20
20
  */
21
21
  sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
22
+ /**
23
+ * Total send attempts on a saturated pod (guuey#406). Default 1 retry
24
+ * (2 attempts) — the historical behavior; capped at 5. A capacity-1 pod
25
+ * (demo fixtures, xs plans) refuses the SECOND simultaneous visitor, so
26
+ * end-user surfaces budget higher and pair it with `onSaturationWait` so
27
+ * the wait is a visible busy state, never a silent hang or a generic
28
+ * error boundary (the 2026-08-24 standalone incident).
29
+ */
30
+ attempts?: number;
31
+ /**
32
+ * Fired before each saturation wait — the surface's hook for an honest
33
+ * "the agent is helping someone else" state. Never fired for other error
34
+ * classes; the turn stays `connecting` throughout (the hook's state
35
+ * machine deliberately has no `retrying` status).
36
+ */
37
+ onSaturationWait?: (info: {
38
+ attempt: number;
39
+ totalAttempts: number;
40
+ waitMs: number;
41
+ }) => void;
22
42
  }
23
43
  /**
24
44
  * Wrap an invoke transport with ONE automatic retry on a saturated pod.
@@ -1 +1 @@
1
- {"version":3,"file":"saturation-retry.d.ts","sourceRoot":"","sources":["../src/saturation-retry.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,YAAY,CAAC;AAiBjE;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMhF;AA4BD,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAqBjB;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAkBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE,qBAA0B,GAClC,eAAe,CA0BjB"}
1
+ {"version":3,"file":"saturation-retry.d.ts","sourceRoot":"","sources":["../src/saturation-retry.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,YAAY,CAAC;AAiBjE;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAMhF;AA4BD,+CAA+C;AAC/C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC/F;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAyBjB;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAkBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,OAAO,GAAE,qBAA0B,GAClC,eAAe,CA0BjB"}
@@ -82,6 +82,8 @@ function delay(ms, signal) {
82
82
  signal.addEventListener("abort", finish, { once: true });
83
83
  });
84
84
  }
85
+ /** Hard ceiling on {@link SaturationRetryOptions.attempts} retries. */
86
+ const MAX_SATURATION_RETRIES = 5;
85
87
  /**
86
88
  * Wrap an invoke transport with ONE automatic retry on a saturated pod.
87
89
  *
@@ -119,27 +121,31 @@ function delay(ms, signal) {
119
121
  * replaying a token that may have expired during the wait.
120
122
  */
121
123
  export function withSaturationRetry(transport, options = {}) {
124
+ const retries = Math.min(Math.max(options.attempts ?? 1, 1), MAX_SATURATION_RETRIES);
122
125
  return async function* retrying(req) {
123
- let yielded = false;
124
- try {
125
- for await (const chunk of transport(req)) {
126
- yielded = true;
127
- yield chunk;
126
+ for (let attempt = 1;; attempt += 1) {
127
+ let yielded = false;
128
+ try {
129
+ for await (const chunk of transport(req)) {
130
+ yielded = true;
131
+ yield chunk;
132
+ }
133
+ return;
134
+ }
135
+ catch (err) {
136
+ const saturated = err instanceof AgentResponseError && err.code === AGENT_ERROR_CODES.POD_SATURATED;
137
+ if (!saturated || yielded || attempt > retries)
138
+ throw err;
139
+ const waitMs = saturationDelayMs(err.retryAfterSeconds);
140
+ options.onSaturationWait?.({ attempt, totalAttempts: retries + 1, waitMs });
141
+ await (options.sleep ?? delay)(waitMs, req.signal);
142
+ // Aborted mid-wait: the user is done with this turn. Surface the
143
+ // refusal that caused the wait rather than spending a request that
144
+ // `fetch` would reject on the signal anyway.
145
+ if (req.signal.aborted)
146
+ throw err;
128
147
  }
129
- return;
130
- }
131
- catch (err) {
132
- const saturated = err instanceof AgentResponseError && err.code === AGENT_ERROR_CODES.POD_SATURATED;
133
- if (!saturated || yielded)
134
- throw err;
135
- await (options.sleep ?? delay)(saturationDelayMs(err.retryAfterSeconds), req.signal);
136
- // Aborted mid-wait: the user is done with this turn. Surface the refusal
137
- // that caused the wait rather than spending a request that `fetch` would
138
- // reject on the signal anyway.
139
- if (req.signal.aborted)
140
- throw err;
141
148
  }
142
- yield* transport(req);
143
149
  };
144
150
  }
145
151
  const COLD_START_DEFAULT_ATTEMPTS = 3;
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAIL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAgB5C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAEpF;AAoHD,gDAAgD;AAChD,MAAM,WAAW,2BAA4B,SAAQ,sBAAsB;IACzE;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAC/C;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,GAAE,2BAAgC,GACxC,aAAa,CAAC,MAAM,CAAC,CAcvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,MAAM,IAAI,GAClB,eAAe,CAOjB"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAIL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAgB5C;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAEpF;AAoHD,gDAAgD;AAChD,MAAM,WAAW,2BAA4B,SAAQ,sBAAsB;IACzE;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,qBAAqB,GAAG,KAAK,CAAC;IAC/C;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,aAAa,EAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,GAAE,2BAAgC,GACxC,aAAa,CAAC,MAAM,CAAC,CAoBvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,OAAO,EAAE,MAAM,IAAI,GAClB,eAAe,CAOjB"}
package/dist/transport.js CHANGED
@@ -160,7 +160,13 @@ export function fetchStreamTransport(req, accessToken, guestSecret, options = {}
160
160
  const bearer = getBearer ? await getBearer() : accessToken;
161
161
  yield* streamInvokeOnce(attempt, bearer, guestSecret);
162
162
  };
163
- const saturated = withSaturationRetry(once, { sleep: options.sleep });
163
+ const saturated = withSaturationRetry(once, {
164
+ sleep: options.sleep,
165
+ ...(options.attempts !== undefined ? { attempts: options.attempts } : {}),
166
+ ...(options.onSaturationWait !== undefined
167
+ ? { onSaturationWait: options.onSaturationWait }
168
+ : {}),
169
+ });
164
170
  if (options.coldStartRetry === false)
165
171
  return saturated(req);
166
172
  return withColdStartRetry(saturated, {
package/dist/types.d.ts CHANGED
@@ -116,12 +116,20 @@ export interface AgentInvokeAdapters {
116
116
  /** Tuning for the guuey#192 stall watchdog — see {@link UseAgentInvokeOptions.stallRecovery}. */
117
117
  export interface StallRecoveryOptions {
118
118
  /**
119
- * Byte-inactivity window (ms) before a history probe fires. Armed only
120
- * AFTER the first byte of the turn pre-first-byte silence is a legitimate
121
- * cold start and never triggers. Every received chunk resets it.
122
- * Default 25000.
119
+ * Byte-inactivity window (ms) before a history probe fires. Armed with
120
+ * {@link preFirstByteWindowMs} at turn start, re-armed with THIS window by
121
+ * every received chunk. Default 25000.
123
122
  */
124
123
  windowMs?: number;
124
+ /**
125
+ * The PRE-first-byte window (ms) — guuey#409: a turn that never receives
126
+ * ANY byte (an invoke-rail death upstream of the pod, or an in-pod
127
+ * pre-spawn hang) previously showed "Thinking…" forever with no error
128
+ * item; this window bounds it. Much longer than `windowMs` on purpose —
129
+ * the transport's cold-start retries legitimately spend up to ~90s before
130
+ * the first byte. Default 120000.
131
+ */
132
+ preFirstByteWindowMs?: number;
125
133
  /**
126
134
  * Fruitless probes (history shows the turn still in flight, or no probe is
127
135
  * possible) before the turn fails with `CLIENT_ERROR_CODES.STREAM_STALLED`.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,15 @@ export declare function applyHistoryResult(result: HistoryLoadResult, currentMes
28
28
  export declare const STALL_RECOVERY_DEFAULTS: {
29
29
  readonly windowMs: 25000;
30
30
  readonly probeAttempts: 4;
31
+ /**
32
+ * guuey#409: the PRE-first-byte watchdog window. The #192 clock arms only
33
+ * on the first chunk (so a silent cold start never trips it) — which left
34
+ * a turn that never receives ANY byte with no watchdog at all: the
35
+ * eternal-"Thinking…" face of the 2026-08-22 invoke-rail turn death.
36
+ * Deliberately much longer than `windowMs`: the transport's cold-start
37
+ * retries legitimately spend up to ~90s before the first byte.
38
+ */
39
+ readonly preFirstByteWindowMs: 120000;
31
40
  };
32
41
  /**
33
42
  * Pure decision seam for the guuey#192 stall probe: does a freshly-loaded
@@ -1 +1 @@
1
- {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAW,KAAK,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAK/F,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,EAAE,oBAEnD,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,eAAe,EAAE,YAAY,EAAE,GAC9B,kBAAkB,CAIpB;AAED,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;CAAkD,CAAC;AAYvF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,WAAW,CAOvB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CA6dhF"}
1
+ {"version":3,"file":"useAgentInvoke.d.ts","sourceRoot":"","sources":["../src/useAgentInvoke.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAW,KAAK,oBAAoB,EAAuB,MAAM,sBAAsB,CAAC;AAK/F,OAAO,KAAK,EAGV,YAAY,EAEZ,iBAAiB,EAGjB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,EAAE,oBAEnD,CAAC;AAEF,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,iBAAiB,EACzB,eAAe,EAAE,YAAY,EAAE,GAC9B,kBAAkB,CAIpB;AAED,uFAAuF;AACvF,eAAO,MAAM,uBAAuB;;;IAGlC;;;;;;;OAOG;;CAEK,CAAC;AAcX;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EAAE,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,WAAW,CAOvB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,CAoehF"}
@@ -54,13 +54,26 @@ export function applyHistoryResult(result, currentMessages) {
54
54
  return { kind: "seed", messages: result.messages };
55
55
  }
56
56
  /** The guuey#192 stall watchdog's resolved tuning (see {@link stallProbeDecision}). */
57
- export const STALL_RECOVERY_DEFAULTS = { windowMs: 25_000, probeAttempts: 4 };
57
+ export const STALL_RECOVERY_DEFAULTS = {
58
+ windowMs: 25_000,
59
+ probeAttempts: 4,
60
+ /**
61
+ * guuey#409: the PRE-first-byte watchdog window. The #192 clock arms only
62
+ * on the first chunk (so a silent cold start never trips it) — which left
63
+ * a turn that never receives ANY byte with no watchdog at all: the
64
+ * eternal-"Thinking…" face of the 2026-08-22 invoke-rail turn death.
65
+ * Deliberately much longer than `windowMs`: the transport's cold-start
66
+ * retries legitimately spend up to ~90s before the first byte.
67
+ */
68
+ preFirstByteWindowMs: 120_000,
69
+ };
58
70
  function resolveStallRecovery(option) {
59
71
  if (option === false)
60
72
  return null;
61
73
  return {
62
74
  windowMs: option?.windowMs ?? STALL_RECOVERY_DEFAULTS.windowMs,
63
75
  probeAttempts: option?.probeAttempts ?? STALL_RECOVERY_DEFAULTS.probeAttempts,
76
+ preFirstByteWindowMs: option?.preFirstByteWindowMs ?? STALL_RECOVERY_DEFAULTS.preFirstByteWindowMs,
64
77
  };
65
78
  }
66
79
  /**
@@ -370,14 +383,21 @@ export function useAgentInvoke(opts) {
370
383
  stallTimer = null;
371
384
  }
372
385
  };
373
- const armStallTimer = () => {
386
+ const armStallTimer = (windowMs) => {
374
387
  if (!stall || turnEnded || controller.signal.aborted)
375
388
  return;
376
389
  clearStallTimer();
377
390
  stallTimer = setTimeout(() => {
378
391
  void onStallWindow();
379
- }, stall.windowMs);
392
+ }, windowMs ?? stall.windowMs);
380
393
  };
394
+ // guuey#409: arm BEFORE the first byte — a turn that never receives
395
+ // any chunk (rail death upstream of the pod, or an in-pod pre-spawn
396
+ // hang) otherwise shows "Thinking…" forever with no error item. The
397
+ // long window keeps silent cold starts un-tripped; expiry runs the
398
+ // SAME probe-then-adopt-or-STREAM_STALLED machinery as mid-stream
399
+ // stalls — one face fix for both mechanisms.
400
+ armStallTimer(stall?.preFirstByteWindowMs);
381
401
  const endTurnWith = (apply) => {
382
402
  turnEnded = true;
383
403
  clearStallTimer();
@@ -9,6 +9,7 @@
9
9
  import { type McpToolCallResult, type ResolvedViewMount, type UiActionRequest } from "@guuey/mcp-apps-host";
10
10
  import type { AgHitlAnswer } from "@silverprotocol/core";
11
11
  import type { AgentInvokeAdapters, ThreadIdStore } from "./types.js";
12
+ import type { SaturationRetryOptions } from "./saturation-retry.js";
12
13
  /** Persists the threadId in `window.localStorage` (synchronously). */
13
14
  export declare const localStorageThreadStore: ThreadIdStore;
14
15
  /** Crypto-strong client-message id, with a non-crypto fallback. */
@@ -19,6 +20,16 @@ export interface CreateWebAdaptersOptions {
19
20
  * omitted, no history adapter is installed and reloads start empty.
20
21
  */
21
22
  apiBaseUrl?: string;
23
+ /**
24
+ * Total send attempts on a saturated pod (guuey#406) — forwarded to
25
+ * {@link SaturationRetryOptions.attempts}. End-user surfaces facing
26
+ * capacity-1 pods (demo fixtures, xs plans) budget higher than the
27
+ * 2-attempt default and pair it with {@link onSaturationWait} so the
28
+ * wait is a visible busy state.
29
+ */
30
+ saturationAttempts?: number;
31
+ /** Forwarded to {@link SaturationRetryOptions.onSaturationWait}. */
32
+ onSaturationWait?: SaturationRetryOptions["onSaturationWait"];
22
33
  /**
23
34
  * Resolve the caller's Cognito access token (fresh), or `null` when signed
24
35
  * out. When a token is present the chat transport AND the history read
@@ -1 +1 @@
1
- {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web-adapters.d.ts","sourceRoot":"","sources":["../src/web-adapters.ts"],"names":[],"mappings":"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"}
@@ -59,6 +59,10 @@ export function createWebAdapters(opts = {}) {
59
59
  // resolves inside its generator).
60
60
  yield* fetchStreamTransport(req, null, getGuestSecret ? getGuestSecret() : null, {
61
61
  getBearer: getAccessToken,
62
+ ...(opts.saturationAttempts !== undefined ? { attempts: opts.saturationAttempts } : {}),
63
+ ...(opts.onSaturationWait !== undefined
64
+ ? { onSaturationWait: opts.onSaturationWait }
65
+ : {}),
62
66
  });
63
67
  };
64
68
  const adapters = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/agent-client",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Client SDK for Guuey's agent runtime: the `useAgentInvoke` React hook + pure SSE helpers that speak the /agent/invoke streaming contract, plus the paginated thread-history read plane. Host adapters (storage / id / transport) are injected, so it runs on web (Next) and React Native alike.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@silverprotocol/core": "0.5.1",
38
- "@guuey/mcp-apps-host": "0.13.0"
38
+ "@guuey/mcp-apps-host": "0.14.1"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18"
@@ -48,7 +48,7 @@
48
48
  "react": "^19.0.0",
49
49
  "react-dom": "^19.0.0",
50
50
  "typescript": "^5.0.0",
51
- "vitest": "^3.0.0"
51
+ "vitest": "^3.2.7"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public"
@@ -94,8 +94,27 @@ export interface SaturationRetryOptions {
94
94
  * real 15s timer; production uses an abort-aware `setTimeout`.
95
95
  */
96
96
  sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
97
+ /**
98
+ * Total send attempts on a saturated pod (guuey#406). Default 1 retry
99
+ * (2 attempts) — the historical behavior; capped at 5. A capacity-1 pod
100
+ * (demo fixtures, xs plans) refuses the SECOND simultaneous visitor, so
101
+ * end-user surfaces budget higher and pair it with `onSaturationWait` so
102
+ * the wait is a visible busy state, never a silent hang or a generic
103
+ * error boundary (the 2026-08-24 standalone incident).
104
+ */
105
+ attempts?: number;
106
+ /**
107
+ * Fired before each saturation wait — the surface's hook for an honest
108
+ * "the agent is helping someone else" state. Never fired for other error
109
+ * classes; the turn stays `connecting` throughout (the hook's state
110
+ * machine deliberately has no `retrying` status).
111
+ */
112
+ onSaturationWait?: (info: { attempt: number; totalAttempts: number; waitMs: number }) => void;
97
113
  }
98
114
 
115
+ /** Hard ceiling on {@link SaturationRetryOptions.attempts} retries. */
116
+ const MAX_SATURATION_RETRIES = 5;
117
+
99
118
  /**
100
119
  * Wrap an invoke transport with ONE automatic retry on a saturated pod.
101
120
  *
@@ -136,25 +155,29 @@ export function withSaturationRetry(
136
155
  transport: InvokeTransport,
137
156
  options: SaturationRetryOptions = {},
138
157
  ): InvokeTransport {
158
+ const retries = Math.min(Math.max(options.attempts ?? 1, 1), MAX_SATURATION_RETRIES);
139
159
  return async function* retrying(req: InvokeRequest): AsyncGenerator<string> {
140
- let yielded = false;
141
- try {
142
- for await (const chunk of transport(req)) {
143
- yielded = true;
144
- yield chunk;
160
+ for (let attempt = 1; ; attempt += 1) {
161
+ let yielded = false;
162
+ try {
163
+ for await (const chunk of transport(req)) {
164
+ yielded = true;
165
+ yield chunk;
166
+ }
167
+ return;
168
+ } catch (err) {
169
+ const saturated =
170
+ err instanceof AgentResponseError && err.code === AGENT_ERROR_CODES.POD_SATURATED;
171
+ if (!saturated || yielded || attempt > retries) throw err;
172
+ const waitMs = saturationDelayMs(err.retryAfterSeconds);
173
+ options.onSaturationWait?.({ attempt, totalAttempts: retries + 1, waitMs });
174
+ await (options.sleep ?? delay)(waitMs, req.signal);
175
+ // Aborted mid-wait: the user is done with this turn. Surface the
176
+ // refusal that caused the wait rather than spending a request that
177
+ // `fetch` would reject on the signal anyway.
178
+ if (req.signal.aborted) throw err;
145
179
  }
146
- return;
147
- } catch (err) {
148
- const saturated =
149
- err instanceof AgentResponseError && err.code === AGENT_ERROR_CODES.POD_SATURATED;
150
- if (!saturated || yielded) throw err;
151
- await (options.sleep ?? delay)(saturationDelayMs(err.retryAfterSeconds), req.signal);
152
- // Aborted mid-wait: the user is done with this turn. Surface the refusal
153
- // that caused the wait rather than spending a request that `fetch` would
154
- // reject on the signal anyway.
155
- if (req.signal.aborted) throw err;
156
180
  }
157
- yield* transport(req);
158
181
  };
159
182
  }
160
183
 
package/src/transport.ts CHANGED
@@ -228,7 +228,13 @@ export function fetchStreamTransport(
228
228
  const bearer = getBearer ? await getBearer() : accessToken;
229
229
  yield* streamInvokeOnce(attempt, bearer, guestSecret);
230
230
  };
231
- const saturated = withSaturationRetry(once, { sleep: options.sleep });
231
+ const saturated = withSaturationRetry(once, {
232
+ sleep: options.sleep,
233
+ ...(options.attempts !== undefined ? { attempts: options.attempts } : {}),
234
+ ...(options.onSaturationWait !== undefined
235
+ ? { onSaturationWait: options.onSaturationWait }
236
+ : {}),
237
+ });
232
238
  if (options.coldStartRetry === false) return saturated(req);
233
239
  return withColdStartRetry(saturated, {
234
240
  sleep: options.sleep,
package/src/types.ts CHANGED
@@ -125,12 +125,20 @@ export interface AgentInvokeAdapters {
125
125
  /** Tuning for the guuey#192 stall watchdog — see {@link UseAgentInvokeOptions.stallRecovery}. */
126
126
  export interface StallRecoveryOptions {
127
127
  /**
128
- * Byte-inactivity window (ms) before a history probe fires. Armed only
129
- * AFTER the first byte of the turn pre-first-byte silence is a legitimate
130
- * cold start and never triggers. Every received chunk resets it.
131
- * Default 25000.
128
+ * Byte-inactivity window (ms) before a history probe fires. Armed with
129
+ * {@link preFirstByteWindowMs} at turn start, re-armed with THIS window by
130
+ * every received chunk. Default 25000.
132
131
  */
133
132
  windowMs?: number;
133
+ /**
134
+ * The PRE-first-byte window (ms) — guuey#409: a turn that never receives
135
+ * ANY byte (an invoke-rail death upstream of the pod, or an in-pod
136
+ * pre-spawn hang) previously showed "Thinking…" forever with no error
137
+ * item; this window bounds it. Much longer than `windowMs` on purpose —
138
+ * the transport's cold-start retries legitimately spend up to ~90s before
139
+ * the first byte. Default 120000.
140
+ */
141
+ preFirstByteWindowMs?: number;
134
142
  /**
135
143
  * Fruitless probes (history shows the turn still in flight, or no probe is
136
144
  * possible) before the turn fails with `CLIENT_ERROR_CODES.STREAM_STALLED`.
@@ -76,15 +76,29 @@ export function applyHistoryResult(
76
76
  }
77
77
 
78
78
  /** The guuey#192 stall watchdog's resolved tuning (see {@link stallProbeDecision}). */
79
- export const STALL_RECOVERY_DEFAULTS = { windowMs: 25_000, probeAttempts: 4 } as const;
79
+ export const STALL_RECOVERY_DEFAULTS = {
80
+ windowMs: 25_000,
81
+ probeAttempts: 4,
82
+ /**
83
+ * guuey#409: the PRE-first-byte watchdog window. The #192 clock arms only
84
+ * on the first chunk (so a silent cold start never trips it) — which left
85
+ * a turn that never receives ANY byte with no watchdog at all: the
86
+ * eternal-"Thinking…" face of the 2026-08-22 invoke-rail turn death.
87
+ * Deliberately much longer than `windowMs`: the transport's cold-start
88
+ * retries legitimately spend up to ~90s before the first byte.
89
+ */
90
+ preFirstByteWindowMs: 120_000,
91
+ } as const;
80
92
 
81
93
  function resolveStallRecovery(
82
94
  option: false | StallRecoveryOptions | undefined,
83
- ): { windowMs: number; probeAttempts: number } | null {
95
+ ): { windowMs: number; probeAttempts: number; preFirstByteWindowMs: number } | null {
84
96
  if (option === false) return null;
85
97
  return {
86
98
  windowMs: option?.windowMs ?? STALL_RECOVERY_DEFAULTS.windowMs,
87
99
  probeAttempts: option?.probeAttempts ?? STALL_RECOVERY_DEFAULTS.probeAttempts,
100
+ preFirstByteWindowMs:
101
+ option?.preFirstByteWindowMs ?? STALL_RECOVERY_DEFAULTS.preFirstByteWindowMs,
88
102
  };
89
103
  }
90
104
 
@@ -399,13 +413,20 @@ export function useAgentInvoke(opts: UseAgentInvokeOptions): UseAgentInvokeRetur
399
413
  stallTimer = null;
400
414
  }
401
415
  };
402
- const armStallTimer = (): void => {
416
+ const armStallTimer = (windowMs?: number): void => {
403
417
  if (!stall || turnEnded || controller.signal.aborted) return;
404
418
  clearStallTimer();
405
419
  stallTimer = setTimeout(() => {
406
420
  void onStallWindow();
407
- }, stall.windowMs);
421
+ }, windowMs ?? stall.windowMs);
408
422
  };
423
+ // guuey#409: arm BEFORE the first byte — a turn that never receives
424
+ // any chunk (rail death upstream of the pod, or an in-pod pre-spawn
425
+ // hang) otherwise shows "Thinking…" forever with no error item. The
426
+ // long window keeps silent cold starts un-tripped; expiry runs the
427
+ // SAME probe-then-adopt-or-STREAM_STALLED machinery as mid-stream
428
+ // stalls — one face fix for both mechanisms.
429
+ armStallTimer(stall?.preFirstByteWindowMs);
409
430
  const endTurnWith = (apply: () => void): void => {
410
431
  turnEnded = true;
411
432
  clearStallTimer();
@@ -18,7 +18,9 @@ import {
18
18
  import type { AgHitlAnswer } from "@silverprotocol/core";
19
19
  import type { AgentInvokeAdapters, InvokeTransport, ThreadIdStore } from "./types.js";
20
20
  import { fetchThreadHistory, HistoryUnauthorizedError } from "./history.js";
21
- import { fetchStreamTransport, sendableGuestSecret, GUEST_HEADER } from "./transport.js";
21
+ import {
22
+ fetchStreamTransport, sendableGuestSecret, GUEST_HEADER } from "./transport.js";
23
+ import type { SaturationRetryOptions } from "./saturation-retry.js";
22
24
  import { toInvokeUrl } from "./invoke-turn.js";
23
25
 
24
26
  /** Persists the threadId in `window.localStorage` (synchronously). */
@@ -55,6 +57,16 @@ export interface CreateWebAdaptersOptions {
55
57
  * omitted, no history adapter is installed and reloads start empty.
56
58
  */
57
59
  apiBaseUrl?: string;
60
+ /**
61
+ * Total send attempts on a saturated pod (guuey#406) — forwarded to
62
+ * {@link SaturationRetryOptions.attempts}. End-user surfaces facing
63
+ * capacity-1 pods (demo fixtures, xs plans) budget higher than the
64
+ * 2-attempt default and pair it with {@link onSaturationWait} so the
65
+ * wait is a visible busy state.
66
+ */
67
+ saturationAttempts?: number;
68
+ /** Forwarded to {@link SaturationRetryOptions.onSaturationWait}. */
69
+ onSaturationWait?: SaturationRetryOptions["onSaturationWait"];
58
70
  /**
59
71
  * Resolve the caller's Cognito access token (fresh), or `null` when signed
60
72
  * out. When a token is present the chat transport AND the history read
@@ -139,6 +151,10 @@ export function createWebAdapters(
139
151
  // resolves inside its generator).
140
152
  yield* fetchStreamTransport(req, null, getGuestSecret ? getGuestSecret() : null, {
141
153
  getBearer: getAccessToken,
154
+ ...(opts.saturationAttempts !== undefined ? { attempts: opts.saturationAttempts } : {}),
155
+ ...(opts.onSaturationWait !== undefined
156
+ ? { onSaturationWait: opts.onSaturationWait }
157
+ : {}),
142
158
  });
143
159
  };
144
160