@skaile/workspaces 3.7.3 → 3.8.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.
- package/CHANGELOG.md +123 -0
- package/dist/app-sdk/index.js +1 -1
- package/dist/{asset-feeds-6XDLVHQJ.js → asset-feeds-6A2KVBQZ.js} +4 -4
- package/dist/{asset-feeds-6XDLVHQJ.js.map → asset-feeds-6A2KVBQZ.js.map} +1 -1
- package/dist/asset-manager/index.js +1 -1
- package/dist/bridge/drivers/claude-sdk.js +351 -30
- package/dist/bridge/drivers/claude-sdk.js.map +1 -1
- package/dist/bridge/drivers/codex.js +1 -1
- package/dist/bridge/drivers/continue.js +2 -2
- package/dist/bridge/drivers/echo.js +1 -1
- package/dist/bridge/drivers/gemini.js +3 -3
- package/dist/bridge/drivers/goose.js +2 -2
- package/dist/bridge/drivers/omp.js +1 -1
- package/dist/bridge/drivers/opencode.js +2 -2
- package/dist/bridge/drivers/qwen.js +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/bridge/src/drivers/claude-sdk.d.ts +148 -2
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/bridge/src/types.d.ts +103 -0
- package/dist/bridge/src/types.d.ts.map +1 -1
- package/dist/{chunk-YICH6VQB.js → chunk-3ZL3GHON.js} +2 -2
- package/dist/{chunk-YICH6VQB.js.map → chunk-3ZL3GHON.js.map} +1 -1
- package/dist/{chunk-5Z4JLURL.js → chunk-4P2P5KA5.js} +3 -3
- package/dist/{chunk-5Z4JLURL.js.map → chunk-4P2P5KA5.js.map} +1 -1
- package/dist/{chunk-V7CKJGT4.js → chunk-5MIXT2MV.js} +4 -4
- package/dist/{chunk-V7CKJGT4.js.map → chunk-5MIXT2MV.js.map} +1 -1
- package/dist/{chunk-ML6OHA3S.js → chunk-5YIGODSZ.js} +2 -2
- package/dist/chunk-5YIGODSZ.js.map +1 -0
- package/dist/{chunk-6GTPLCMO.js → chunk-7SOLXVES.js} +142 -13
- package/dist/chunk-7SOLXVES.js.map +1 -0
- package/dist/{chunk-OOUQWDD5.js → chunk-D5X2IXY7.js} +6 -6
- package/dist/{chunk-OOUQWDD5.js.map → chunk-D5X2IXY7.js.map} +1 -1
- package/dist/{chunk-26L2B6P6.js → chunk-FUCYTGAE.js} +4 -4
- package/dist/chunk-FUCYTGAE.js.map +1 -0
- package/dist/{chunk-MGPHEYW2.js → chunk-HCGVNO7F.js} +2 -2
- package/dist/{chunk-MGPHEYW2.js.map → chunk-HCGVNO7F.js.map} +1 -1
- package/dist/{chunk-FSWYUSCP.js → chunk-IWHPJFQ2.js} +3 -3
- package/dist/{chunk-FSWYUSCP.js.map → chunk-IWHPJFQ2.js.map} +1 -1
- package/dist/{chunk-UGBN7MQZ.js → chunk-KLATRNFG.js} +3 -3
- package/dist/{chunk-UGBN7MQZ.js.map → chunk-KLATRNFG.js.map} +1 -1
- package/dist/{chunk-BCXI27BH.js → chunk-NVYEK4FI.js} +3 -3
- package/dist/{chunk-BCXI27BH.js.map → chunk-NVYEK4FI.js.map} +1 -1
- package/dist/{chunk-D4VHBAJC.js → chunk-O3LEV6KB.js} +4 -4
- package/dist/{chunk-D4VHBAJC.js.map → chunk-O3LEV6KB.js.map} +1 -1
- package/dist/{chunk-WHOM6NF3.js → chunk-OI5VPP4P.js} +3 -3
- package/dist/{chunk-WHOM6NF3.js.map → chunk-OI5VPP4P.js.map} +1 -1
- package/dist/{chunk-BSQGQMGA.js → chunk-OIOJMSW2.js} +3 -3
- package/dist/{chunk-BSQGQMGA.js.map → chunk-OIOJMSW2.js.map} +1 -1
- package/dist/{chunk-DGWJAPRC.js → chunk-Q57ZGD4V.js} +447 -95
- package/dist/chunk-Q57ZGD4V.js.map +1 -0
- package/dist/{chunk-ZMCBWZ77.js → chunk-SO3BKY2Y.js} +6 -6
- package/dist/{chunk-ZMCBWZ77.js.map → chunk-SO3BKY2Y.js.map} +1 -1
- package/dist/{chunk-765ZGKVC.js → chunk-XNAY6FBK.js} +3 -3
- package/dist/{chunk-765ZGKVC.js.map → chunk-XNAY6FBK.js.map} +1 -1
- package/dist/cli/index.js +30 -30
- package/dist/connectors/index.js +3 -3
- package/dist/connectors/src/connector-manager.d.ts +34 -2
- package/dist/connectors/src/connector-manager.d.ts.map +1 -1
- package/dist/connectors/src/connector-prompt.d.ts.map +1 -1
- package/dist/connectors/src/connector-types.d.ts +25 -0
- package/dist/connectors/src/connector-types.d.ts.map +1 -1
- package/dist/connectors/src/credential-helper-script.d.ts +48 -0
- package/dist/connectors/src/credential-helper-script.d.ts.map +1 -1
- package/dist/connectors/src/index.d.ts +3 -3
- package/dist/connectors/src/index.d.ts.map +1 -1
- package/dist/connectors/src/managed-gitconfig.d.ts +80 -0
- package/dist/connectors/src/managed-gitconfig.d.ts.map +1 -1
- package/dist/{connectors-DY7UBBV5.js → connectors-2OHRZDJZ.js} +3 -3
- package/dist/{connectors-DY7UBBV5.js.map → connectors-2OHRZDJZ.js.map} +1 -1
- package/dist/{ensure-sources-TSHLAVNU.js → ensure-sources-7DUTA24P.js} +4 -4
- package/dist/{ensure-sources-TSHLAVNU.js.map → ensure-sources-7DUTA24P.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +3 -3
- package/dist/factory-assets/connectors/deploy.js +3 -3
- package/dist/factory-assets/connectors/devserver.js +3 -3
- package/dist/factory-assets/connectors/flow/adapter.js +3 -3
- package/dist/factory-assets/connectors/flow/run-flow.js +4 -4
- package/dist/factory-assets/connectors/flow.js +3 -3
- package/dist/factory-assets/connectors/git/driver.d.ts +28 -0
- package/dist/factory-assets/connectors/git/driver.d.ts.map +1 -1
- package/dist/factory-assets/connectors/git.js +3 -3
- package/dist/factory-assets/connectors/gmail.js +3 -3
- package/dist/factory-assets/connectors/googledrive.js +3 -3
- package/dist/factory-assets/connectors/local.js +3 -3
- package/dist/factory-assets/connectors/mattermost.js +3 -3
- package/dist/factory-assets/connectors/memory.js +3 -3
- package/dist/factory-assets/connectors/minio.js +3 -3
- package/dist/factory-assets/connectors/postgres.js +3 -3
- package/dist/factory-assets/connectors/s3.js +3 -3
- package/dist/factory-assets/connectors/sharepoint.js +3 -3
- package/dist/factory-assets/connectors/sqlite.js +3 -3
- package/dist/factory-assets/connectors/static-server.js +3 -3
- package/dist/factory-assets/connectors/tunnel.js +3 -3
- package/dist/factory-assets/connectors/webdav.js +3 -3
- package/dist/factory-assets/connectors/xstate-store.js +3 -3
- package/dist/factory-assets/connectors/xstate.js +3 -3
- package/dist/{helpers-PVM4MQV2.js → helpers-IA562IEW.js} +4 -4
- package/dist/{helpers-PVM4MQV2.js.map → helpers-IA562IEW.js.map} +1 -1
- package/dist/{library-publish-P7ZVEY6I.js → library-publish-RQUQXZW6.js} +6 -6
- package/dist/{library-publish-P7ZVEY6I.js.map → library-publish-RQUQXZW6.js.map} +1 -1
- package/dist/{open-library-QG4LHFQQ.js → open-library-74EQADJ4.js} +5 -5
- package/dist/{open-library-QG4LHFQQ.js.map → open-library-74EQADJ4.js.map} +1 -1
- package/dist/{project-YE6GKQTZ.js → project-OKXPPH2C.js} +5 -5
- package/dist/{project-YE6GKQTZ.js.map → project-OKXPPH2C.js.map} +1 -1
- package/dist/runner/index.js +7 -7
- package/dist/runner/src/ai-provider-switch.d.ts +95 -0
- package/dist/runner/src/ai-provider-switch.d.ts.map +1 -0
- package/dist/runner/src/credential-rotated.d.ts +23 -4
- package/dist/runner/src/credential-rotated.d.ts.map +1 -1
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/runner/src/session-builder.d.ts +19 -0
- package/dist/runner/src/session-builder.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +1 -1
- package/dist/sdk/bridge.js +2 -2
- package/dist/sdk/index.js +7 -7
- package/dist/sdk/runner.js +7 -7
- package/dist/sdk/session.js +2 -2
- package/dist/sdk/types.js +1 -1
- package/dist/session/index.js +2 -2
- package/dist/{setup-UYLZFES6.js → setup-Q6ANIFZE.js} +4 -4
- package/dist/{setup-UYLZFES6.js.map → setup-Q6ANIFZE.js.map} +1 -1
- package/dist/{source-5GTHOFT6.js → source-P27ND6I4.js} +6 -6
- package/dist/{source-5GTHOFT6.js.map → source-P27ND6I4.js.map} +1 -1
- package/dist/{source-manifest-publish-DK3CPBSI.js → source-manifest-publish-PKYHNVGG.js} +7 -7
- package/dist/{source-manifest-publish-DK3CPBSI.js.map → source-manifest-publish-PKYHNVGG.js.map} +1 -1
- package/dist/tui/index.js +7 -7
- package/dist/types/index.js +1 -1
- package/dist/types/src/events.d.ts +34 -1
- package/dist/types/src/events.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/lifecycle.d.ts +13 -0
- package/dist/types/src/lifecycle.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +22 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/dist/workspace-plugin/adapters/mcp.js +2 -2
- package/dist/workspace-plugin/adapters/mcp.js.map +1 -1
- package/dist/workspace-plugin/adapters/omp.js +8 -8
- package/dist/workspace-plugin/adapters/omp.js.map +1 -1
- package/dist/workspace-plugin/index.js +1 -1
- package/dist/workspace-plugin/src/adapters/mcp.d.ts.map +1 -1
- package/dist/workspace-plugin/src/adapters/omp.d.ts.map +1 -1
- package/dist/workspace-plugin/src/tools/connectors.d.ts +7 -3
- package/dist/workspace-plugin/src/tools/connectors.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-26L2B6P6.js.map +0 -1
- package/dist/chunk-6GTPLCMO.js.map +0 -1
- package/dist/chunk-DGWJAPRC.js.map +0 -1
- package/dist/chunk-ML6OHA3S.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { scrubPoisonedTranscript, resolveClaudeConfigDir, SUPPORTED_CLOUDS, buil
|
|
|
2
2
|
import { dispatchCapability } from '../../chunk-RRVQAE5D.js';
|
|
3
3
|
import { fetchProviderModels } from '../../chunk-V34ZEXSE.js';
|
|
4
4
|
import { AuthError, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-GAH4YRCI.js';
|
|
5
|
-
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-
|
|
5
|
+
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-5YIGODSZ.js';
|
|
6
6
|
import '../../chunk-6E6PKKAD.js';
|
|
7
7
|
import '../../chunk-AZPXCHDK.js';
|
|
8
8
|
import '../../chunk-F3HNW354.js';
|
|
@@ -159,7 +159,12 @@ if (typeof globalThis.require === "undefined") {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
function freshRetryBudget() {
|
|
162
|
-
return { staleResume: false, poison: false, auth: false, queryEnded: false };
|
|
162
|
+
return { staleResume: false, poison: false, auth: false, queryEnded: false, limit: false };
|
|
163
|
+
}
|
|
164
|
+
function isSeatCapacityFailure(detail, subtype) {
|
|
165
|
+
if (detail.category !== "rate_limit") return false;
|
|
166
|
+
if (subtype === "error_max_budget_usd") return false;
|
|
167
|
+
return detail.statusCode !== 529;
|
|
163
168
|
}
|
|
164
169
|
var QueryEndedBeforeTurnError = class extends Error {
|
|
165
170
|
constructor(message) {
|
|
@@ -178,6 +183,23 @@ function firstMessageStallMs() {
|
|
|
178
183
|
const value = Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_FIRST_MESSAGE_STALL_MS;
|
|
179
184
|
return Math.min(value, turnStallMs());
|
|
180
185
|
}
|
|
186
|
+
var CLI_AUTH_RENDER_MARKERS = [
|
|
187
|
+
"failed to authenticate",
|
|
188
|
+
"api error:",
|
|
189
|
+
"authentication_error",
|
|
190
|
+
"not logged in",
|
|
191
|
+
"please run /login",
|
|
192
|
+
"oauth authentication is currently not supported"
|
|
193
|
+
];
|
|
194
|
+
function isCliAuthRenderText(text) {
|
|
195
|
+
const lower = text.toLowerCase();
|
|
196
|
+
return CLI_AUTH_RENDER_MARKERS.some((marker) => lower.includes(marker));
|
|
197
|
+
}
|
|
198
|
+
var DEFAULT_ERROR_RESULT_GRACE_MS = 5e3;
|
|
199
|
+
function errorResultGraceMs() {
|
|
200
|
+
const raw = Number(process.env.SKAILE_ERROR_RESULT_GRACE_MS);
|
|
201
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_ERROR_RESULT_GRACE_MS;
|
|
202
|
+
}
|
|
181
203
|
var RECYCLE_TURNS_FRACTION = 0.8;
|
|
182
204
|
var MIN_RECYCLE_HEADROOM = 5;
|
|
183
205
|
var PushableInputStream = class {
|
|
@@ -241,6 +263,16 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
241
263
|
emittedSanitizedLen = 0;
|
|
242
264
|
/** Set once the streaming text of the current message classifies as a self-healable auth render. */
|
|
243
265
|
suppressingAuthStream = false;
|
|
266
|
+
/**
|
|
267
|
+
* The auth render this attempt dropped on the promise that a self-heal would
|
|
268
|
+
* retry — the evidence {@link handleResultErrorPayload} otherwise lacks when
|
|
269
|
+
* the SDK reports the same 401 as an `is_error` result with an empty
|
|
270
|
+
* `errors[]`. Written only through {@link stashSuppressedAuthRender}, which
|
|
271
|
+
* applies a stricter test than suppression does; see its docblock. Cleared at
|
|
272
|
+
* both ends of {@link runPrompt}, so it can only ever describe the turn that
|
|
273
|
+
* is failing.
|
|
274
|
+
*/
|
|
275
|
+
suppressedAuthRenderText = null;
|
|
244
276
|
/** Active query — stays alive between turns (streaming input mode). */
|
|
245
277
|
query = null;
|
|
246
278
|
/**
|
|
@@ -282,6 +314,12 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
282
314
|
lastSdkMessageAt = 0;
|
|
283
315
|
/** Armed while a turn is in flight; trips when the SDK goes silent for too long. */
|
|
284
316
|
turnStallTimer = null;
|
|
317
|
+
/**
|
|
318
|
+
* Armed only while an `is_error` result waits for its follow-up iterator
|
|
319
|
+
* throw; fails the turn if that throw never lands. Cleared at every turn
|
|
320
|
+
* start and settle site so it can never fire into a later turn.
|
|
321
|
+
*/
|
|
322
|
+
errorResultGraceTimer = null;
|
|
285
323
|
/**
|
|
286
324
|
* Mirror of the in-flight {@link prompt} call's auth retry budget: `false`
|
|
287
325
|
* while the `onAuthError` self-heal is still available, `true` once it has
|
|
@@ -292,6 +330,52 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
292
330
|
* reset elsewhere — the next call's set is the only legitimate transition.
|
|
293
331
|
*/
|
|
294
332
|
authSelfHealUsed = false;
|
|
333
|
+
/**
|
|
334
|
+
* Mirror of the in-flight turn's seat-failover budget — same contract as
|
|
335
|
+
* {@link authSelfHealUsed}, for the `onLimitBlocked` path.
|
|
336
|
+
*/
|
|
337
|
+
limitFailoverUsed = false;
|
|
338
|
+
/**
|
|
339
|
+
* Set when THIS attempt saw an upstream usage-limit rejection: either the
|
|
340
|
+
* SDK's `rate_limit_event` telemetry with `status: "rejected"`, or an error
|
|
341
|
+
* result the classifier calls `rate_limit`.
|
|
342
|
+
*
|
|
343
|
+
* The telemetry is the load-bearing half. A subscription ceiling arrives as
|
|
344
|
+
* `{"status":"rejected","type":"five_hour"}` followed by an error result
|
|
345
|
+
* whose text ("You've hit your session limit · resets 9:50am") matches none
|
|
346
|
+
* of the classifier's rate-limit keywords — so without this flag the
|
|
347
|
+
* rejection classifies as `unknown` and no failover is reachable at all.
|
|
348
|
+
*
|
|
349
|
+
* Reset at the top of every {@link runPrompt} attempt, so the replay after a
|
|
350
|
+
* failover observes its own outcome rather than inheriting the first one's.
|
|
351
|
+
*/
|
|
352
|
+
limitRejectionSeen = false;
|
|
353
|
+
/**
|
|
354
|
+
* True when {@link handleResultErrorPayload} rejected this turn ONLY so the
|
|
355
|
+
* seat-failover branch could run — a turn that, before this existed, would
|
|
356
|
+
* have emitted its error and resolved. If the failover then declines, the
|
|
357
|
+
* rejection is undone so a limit block with no peer available looks exactly
|
|
358
|
+
* as it always has downstream (one error event, one `agent_end`, a resolved
|
|
359
|
+
* promise) rather than also drawing the runner's fatal "prompt failed".
|
|
360
|
+
*/
|
|
361
|
+
limitTurnRejectedForFailover = false;
|
|
362
|
+
/**
|
|
363
|
+
* Whether this attempt already reported a provider rejection. One upstream
|
|
364
|
+
* failure routinely reaches the driver twice — `rejected` telemetry followed
|
|
365
|
+
* by the error result, or an `is_error` result followed by the iterator throw
|
|
366
|
+
* carrying the real text — and these observations feed counters, so counting
|
|
367
|
+
* one rejection twice would overstate the seat's pressure.
|
|
368
|
+
*/
|
|
369
|
+
providerResponseReported = false;
|
|
370
|
+
/** Limit-window metadata from this attempt's rejection telemetry, if any. */
|
|
371
|
+
lastLimitInfo = null;
|
|
372
|
+
/**
|
|
373
|
+
* The `agent-event: error` {@link failTurn} withheld because a recovery
|
|
374
|
+
* (auth self-heal or seat failover) was about to run. The recovery emits it
|
|
375
|
+
* if it declines, and drops it if it retries — so a recovered turn shows the
|
|
376
|
+
* user nothing and a failed one shows exactly one error.
|
|
377
|
+
*/
|
|
378
|
+
deferredTurnError = null;
|
|
295
379
|
/** Tracks whether a session has been started (for continue: true). */
|
|
296
380
|
hasSession = false;
|
|
297
381
|
/** Session ID from the SDK — used for streamInput messages. */
|
|
@@ -422,8 +506,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
422
506
|
async runPrompt(message, _budget) {
|
|
423
507
|
if (!this.sdk) await this.start();
|
|
424
508
|
this.authSelfHealUsed = _budget.auth;
|
|
509
|
+
this.limitFailoverUsed = _budget.limit;
|
|
510
|
+
this.limitRejectionSeen = false;
|
|
511
|
+
this.limitTurnRejectedForFailover = false;
|
|
512
|
+
this.providerResponseReported = false;
|
|
513
|
+
this.lastLimitInfo = null;
|
|
514
|
+
this.deferredTurnError = null;
|
|
425
515
|
let retrying = false;
|
|
426
|
-
const isFirstAttempt = !_budget.staleResume && !_budget.poison && !_budget.auth && !_budget.queryEnded;
|
|
516
|
+
const isFirstAttempt = !_budget.staleResume && !_budget.poison && !_budget.auth && !_budget.queryEnded && !_budget.limit;
|
|
427
517
|
if (isFirstAttempt) {
|
|
428
518
|
this._lastTokens = null;
|
|
429
519
|
this._lastContextTokens = null;
|
|
@@ -432,7 +522,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
432
522
|
this.prevText = "";
|
|
433
523
|
this.emittedSanitizedLen = 0;
|
|
434
524
|
this.suppressingAuthStream = false;
|
|
525
|
+
this.suppressedAuthRenderText = null;
|
|
435
526
|
this.turnCompleted = false;
|
|
527
|
+
this.disarmErrorResultGrace();
|
|
436
528
|
if (this.query && (this.inputStream == null || this.inputStream.ended || this.shouldRecycleQuery())) {
|
|
437
529
|
this.recycleQueryForNextTurn();
|
|
438
530
|
}
|
|
@@ -447,6 +539,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
447
539
|
await this.startQuery(message);
|
|
448
540
|
}
|
|
449
541
|
} catch (err) {
|
|
542
|
+
this.disarmErrorResultGrace();
|
|
450
543
|
this.turnResolve = null;
|
|
451
544
|
this.turnReject = null;
|
|
452
545
|
this.running = false;
|
|
@@ -476,20 +569,40 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
476
569
|
return this.runPrompt(message, { ..._budget, poison: true });
|
|
477
570
|
}
|
|
478
571
|
}
|
|
479
|
-
const
|
|
480
|
-
if (
|
|
572
|
+
const authOwns = err instanceof AuthError && !_budget.auth && this.config.onAuthError != null;
|
|
573
|
+
if (authOwns) {
|
|
481
574
|
if (await this.attemptAuthSelfHeal(err)) {
|
|
482
575
|
retrying = true;
|
|
483
576
|
return this.runPrompt(message, { ..._budget, auth: true });
|
|
484
577
|
}
|
|
578
|
+
} else if (!_budget.limit && this.limitRejectionSeen && this.config.onLimitBlocked) {
|
|
579
|
+
if (await this.attemptLimitFailover(err)) {
|
|
580
|
+
if (this.abortController?.signal.aborted) {
|
|
581
|
+
this.log.info("aborted during the seat failover; not replaying the prompt");
|
|
582
|
+
this.emitAgentEnd();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
retrying = true;
|
|
586
|
+
return this.runPrompt(message, { ..._budget, limit: true });
|
|
587
|
+
}
|
|
588
|
+
if (this.limitTurnRejectedForFailover) {
|
|
589
|
+
this.emitAgentEnd();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
485
592
|
}
|
|
486
593
|
throw err;
|
|
487
594
|
} finally {
|
|
488
595
|
if (!retrying) {
|
|
489
596
|
this.disarmTurnStallWatchdog();
|
|
597
|
+
this.disarmErrorResultGrace();
|
|
490
598
|
this.turnResolve = null;
|
|
491
599
|
this.turnReject = null;
|
|
492
600
|
this.running = false;
|
|
601
|
+
this.limitRejectionSeen = false;
|
|
602
|
+
this.limitTurnRejectedForFailover = false;
|
|
603
|
+
this.lastLimitInfo = null;
|
|
604
|
+
this.deferredTurnError = null;
|
|
605
|
+
this.suppressedAuthRenderText = null;
|
|
493
606
|
}
|
|
494
607
|
}
|
|
495
608
|
}
|
|
@@ -536,6 +649,42 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
536
649
|
this.turnStallTimer = null;
|
|
537
650
|
}
|
|
538
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Bound the wait for the follow-up throw an `is_error` result implies. The
|
|
654
|
+
* throw carries the real, richly-classified error, so the deferral stays —
|
|
655
|
+
* this only caps it, turning a permanently pending turn into a bounded
|
|
656
|
+
* failure. Never arms onto an already-settled turn: an auth / account / limit
|
|
657
|
+
* payload has run `failTurn` by the time this is reached, and the same guard
|
|
658
|
+
* is re-checked inside the callback because the throw normally lands in
|
|
659
|
+
* between. Note 2026-06-08-claude-sdk-error-result-handling.
|
|
660
|
+
*/
|
|
661
|
+
armErrorResultGrace(detail, context) {
|
|
662
|
+
this.disarmErrorResultGrace();
|
|
663
|
+
if (this.turnCompleted || this.turnReject == null) return;
|
|
664
|
+
this.errorResultGraceTimer = setTimeout(() => {
|
|
665
|
+
this.errorResultGraceTimer = null;
|
|
666
|
+
if (this.turnCompleted || this.turnReject == null) return;
|
|
667
|
+
this.log.error("is_error result never produced its follow-up throw; failing the turn", {
|
|
668
|
+
subtype: context.subtype,
|
|
669
|
+
terminalReason: context.terminalReason,
|
|
670
|
+
category: detail.category
|
|
671
|
+
});
|
|
672
|
+
const message = "The model reported an error but sent no details. The conversation is intact \u2014 send your message again.";
|
|
673
|
+
this.failTurn(new Error(message), { ...detail, message });
|
|
674
|
+
}, errorResultGraceMs());
|
|
675
|
+
this.errorResultGraceTimer.unref?.();
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* The retry helpers that null `turnResolve`/`turnReject` before recursing
|
|
679
|
+
* need no call: they run only from `runPrompt`'s catch, which is reached only
|
|
680
|
+
* because `failTurn` or `rejectTurnSilently` rejected — and both disarm first.
|
|
681
|
+
*/
|
|
682
|
+
disarmErrorResultGrace() {
|
|
683
|
+
if (this.errorResultGraceTimer) {
|
|
684
|
+
clearTimeout(this.errorResultGraceTimer);
|
|
685
|
+
this.errorResultGraceTimer = null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
539
688
|
/**
|
|
540
689
|
* The SDK went silent mid-turn. Tear the query down so the *next* prompt
|
|
541
690
|
* starts a fresh one (a live-but-deaf query would otherwise swallow every
|
|
@@ -803,18 +952,60 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
803
952
|
this.running = false;
|
|
804
953
|
return true;
|
|
805
954
|
}
|
|
955
|
+
this.emitDeferredTurnError({ type: "error", error: err.message, detail: err.detail });
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Side-effects of the seat-failover path: ask the host to re-resolve the AI
|
|
960
|
+
* provider config behind this session. On `switched: true` drop the query and
|
|
961
|
+
* return `true` (the caller recurses onto a fresh CLI); otherwise emit the
|
|
962
|
+
* deferred error and return `false`. Mirrors {@link attemptAuthSelfHeal} —
|
|
963
|
+
* this performs the state mutation, the caller owns the control flow.
|
|
964
|
+
*
|
|
965
|
+
* Dropping `this.query` is the whole point of the retry, not bookkeeping: a
|
|
966
|
+
* resident CLI caches "blocked until <reset>" and short-circuits the next
|
|
967
|
+
* prompt locally with no API attempt at all (skaile-ai/workspaces#566), so a
|
|
968
|
+
* rewritten credentials file alone would change nothing. Only a fresh spawn
|
|
969
|
+
* clears it. `sessionId` / `hasSession` are preserved so the new query
|
|
970
|
+
* resumes the same conversation.
|
|
971
|
+
*/
|
|
972
|
+
async attemptLimitFailover(err) {
|
|
973
|
+
this.log.warn("turn blocked by an upstream usage limit; invoking onLimitBlocked", {
|
|
974
|
+
configId: this.config.aiProviderConfigId,
|
|
975
|
+
limitType: this.lastLimitInfo?.limitType,
|
|
976
|
+
resetsAt: this.lastLimitInfo?.resetsAt
|
|
977
|
+
});
|
|
978
|
+
let outcome;
|
|
806
979
|
try {
|
|
807
|
-
this.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
980
|
+
outcome = await this.config.onLimitBlocked({
|
|
981
|
+
configId: this.config.aiProviderConfigId ?? "",
|
|
982
|
+
...this.lastLimitInfo ?? {}
|
|
983
|
+
});
|
|
984
|
+
} catch (failoverErr) {
|
|
985
|
+
this.log.warn("onLimitBlocked callback threw; surfacing the original limit error", {
|
|
986
|
+
error: failoverErr instanceof Error ? failoverErr.message : String(failoverErr)
|
|
811
987
|
});
|
|
812
|
-
} catch (emitErr) {
|
|
813
|
-
this.log.warn(
|
|
814
|
-
"agent-event listener threw during deferred auth-error surfacing; suppressing",
|
|
815
|
-
{ error: emitErr instanceof Error ? emitErr.message : String(emitErr) }
|
|
816
|
-
);
|
|
817
988
|
}
|
|
989
|
+
if (outcome?.switched === true) {
|
|
990
|
+
this.log.info("switched AI provider config; respawning the CLI and replaying the prompt", {
|
|
991
|
+
configId: outcome.configId
|
|
992
|
+
});
|
|
993
|
+
this.query = null;
|
|
994
|
+
this.endInputStream();
|
|
995
|
+
this.turnResolve = null;
|
|
996
|
+
this.turnReject = null;
|
|
997
|
+
this.running = false;
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
this.log.info("no alternative AI provider config available; surfacing the limit error", {
|
|
1001
|
+
reason: outcome?.reason
|
|
1002
|
+
});
|
|
1003
|
+
const detail = err?.detail;
|
|
1004
|
+
this.emitDeferredTurnError({
|
|
1005
|
+
type: "error",
|
|
1006
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1007
|
+
...detail ? { detail } : {}
|
|
1008
|
+
});
|
|
818
1009
|
return false;
|
|
819
1010
|
}
|
|
820
1011
|
/**
|
|
@@ -1261,6 +1452,12 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1261
1452
|
category: detail.category,
|
|
1262
1453
|
retryable: detail.retryable
|
|
1263
1454
|
});
|
|
1455
|
+
if (detail.category === "auth") {
|
|
1456
|
+
this.reportProviderResponse({ status: 401 });
|
|
1457
|
+
} else if (isSeatCapacityFailure(detail)) {
|
|
1458
|
+
this.limitRejectionSeen = true;
|
|
1459
|
+
this.reportProviderResponse({ status: 429, ...this.lastLimitInfo ?? {} });
|
|
1460
|
+
}
|
|
1264
1461
|
const surfaced = detail.category === "auth" ? new AuthError(detail) : detail.category === "account" ? new AccountUnavailableError(detail) : error;
|
|
1265
1462
|
this.failTurn(surfaced, detail);
|
|
1266
1463
|
}
|
|
@@ -1276,15 +1473,20 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1276
1473
|
if (this.turnCompleted) return;
|
|
1277
1474
|
this.turnCompleted = true;
|
|
1278
1475
|
this.disarmTurnStallWatchdog();
|
|
1476
|
+
this.disarmErrorResultGrace();
|
|
1279
1477
|
if (this.turnResolve) {
|
|
1280
1478
|
this.turnResolve();
|
|
1281
1479
|
this.turnResolve = null;
|
|
1282
1480
|
this.turnReject = null;
|
|
1283
1481
|
}
|
|
1482
|
+
this.emitAgentEnd();
|
|
1483
|
+
}
|
|
1484
|
+
/** Emit the turn-end signal, swallowing a throwing listener. */
|
|
1485
|
+
emitAgentEnd() {
|
|
1284
1486
|
try {
|
|
1285
1487
|
this.emit("agent-event", { type: "agent_end" });
|
|
1286
1488
|
} catch (emitErr) {
|
|
1287
|
-
this.log.warn("agent-event listener threw
|
|
1489
|
+
this.log.warn("agent-event listener threw while ending the turn; suppressing", {
|
|
1288
1490
|
error: emitErr instanceof Error ? emitErr.message : String(emitErr)
|
|
1289
1491
|
});
|
|
1290
1492
|
}
|
|
@@ -1302,6 +1504,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1302
1504
|
*/
|
|
1303
1505
|
rejectTurnSilently(err) {
|
|
1304
1506
|
this.disarmTurnStallWatchdog();
|
|
1507
|
+
this.disarmErrorResultGrace();
|
|
1305
1508
|
this.turnCompleted = true;
|
|
1306
1509
|
if (this.turnReject) {
|
|
1307
1510
|
const reject = this.turnReject;
|
|
@@ -1344,29 +1547,63 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1344
1547
|
*/
|
|
1345
1548
|
failTurn(err, detail) {
|
|
1346
1549
|
this.disarmTurnStallWatchdog();
|
|
1550
|
+
this.disarmErrorResultGrace();
|
|
1551
|
+
const hadPendingTurn = this.turnReject != null;
|
|
1347
1552
|
if (this.turnReject) {
|
|
1348
1553
|
this.turnReject(err);
|
|
1349
1554
|
this.turnResolve = null;
|
|
1350
1555
|
this.turnReject = null;
|
|
1351
1556
|
}
|
|
1352
1557
|
this.turnCompleted = true;
|
|
1558
|
+
const event = {
|
|
1559
|
+
type: "error",
|
|
1560
|
+
error: err.message,
|
|
1561
|
+
...detail ? { detail } : {}
|
|
1562
|
+
};
|
|
1353
1563
|
const willSelfHeal = err instanceof AuthError && !this.authSelfHealUsed && this.config.onAuthError != null;
|
|
1354
|
-
if (willSelfHeal) {
|
|
1564
|
+
if (hadPendingTurn && willSelfHeal) {
|
|
1355
1565
|
this.log.info("deferring auth-error event; self-heal will attempt refresh");
|
|
1566
|
+
this.deferredTurnError = event;
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (hadPendingTurn && this.willFailoverOnLimit()) {
|
|
1570
|
+
this.log.info("deferring limit error event; seat failover will be attempted");
|
|
1571
|
+
this.deferredTurnError = event;
|
|
1356
1572
|
return;
|
|
1357
1573
|
}
|
|
1574
|
+
this.emitTurnErrorEvent(event);
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Whether a usage-limit block on the current attempt still has a seat
|
|
1578
|
+
* failover available. Evaluated identically by {@link failTurn} (to decide
|
|
1579
|
+
* whether to withhold the error event) and by {@link handleResultErrorPayload}
|
|
1580
|
+
* (to decide whether to reject the turn at all) — one predicate so the two
|
|
1581
|
+
* can never disagree and strand a deferred event.
|
|
1582
|
+
*/
|
|
1583
|
+
willFailoverOnLimit() {
|
|
1584
|
+
return this.limitRejectionSeen && !this.limitFailoverUsed && this.config.onLimitBlocked != null;
|
|
1585
|
+
}
|
|
1586
|
+
/** Emit one `agent-event: error`, swallowing a throwing listener. */
|
|
1587
|
+
emitTurnErrorEvent(event) {
|
|
1358
1588
|
try {
|
|
1359
|
-
this.emit("agent-event",
|
|
1360
|
-
type: "error",
|
|
1361
|
-
error: err.message,
|
|
1362
|
-
...detail ? { detail } : {}
|
|
1363
|
-
});
|
|
1589
|
+
this.emit("agent-event", event);
|
|
1364
1590
|
} catch (emitErr) {
|
|
1365
|
-
this.log.warn("agent-event listener threw
|
|
1591
|
+
this.log.warn("agent-event listener threw while surfacing a turn error; suppressing", {
|
|
1366
1592
|
error: emitErr instanceof Error ? emitErr.message : String(emitErr)
|
|
1367
1593
|
});
|
|
1368
1594
|
}
|
|
1369
1595
|
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Emit the error {@link failTurn} withheld, exactly once. Called by a
|
|
1598
|
+
* recovery path that decided NOT to retry, so every failure produces one
|
|
1599
|
+
* downstream error event and a recovered one produces none. `fallback` covers
|
|
1600
|
+
* the paths that reject without going through `failTurn`.
|
|
1601
|
+
*/
|
|
1602
|
+
emitDeferredTurnError(fallback) {
|
|
1603
|
+
const event = this.deferredTurnError ?? fallback;
|
|
1604
|
+
this.deferredTurnError = null;
|
|
1605
|
+
this.emitTurnErrorEvent(event);
|
|
1606
|
+
}
|
|
1370
1607
|
// ---------------------------------------------------------------------------
|
|
1371
1608
|
// Message handling
|
|
1372
1609
|
// ---------------------------------------------------------------------------
|
|
@@ -1383,9 +1620,17 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1383
1620
|
}
|
|
1384
1621
|
}
|
|
1385
1622
|
/**
|
|
1386
|
-
* Surface subscription rate-limit telemetry as a structured warning log
|
|
1623
|
+
* Surface subscription rate-limit telemetry as a structured warning log, and
|
|
1624
|
+
* — on `status: "rejected"` — arm the seat failover and report the rejection
|
|
1625
|
+
* to the host for per-seat health attribution.
|
|
1626
|
+
*
|
|
1387
1627
|
* Returns `true` when the message was a rate-limit event (and fully handled),
|
|
1388
1628
|
* so the caller can stop dispatching.
|
|
1629
|
+
*
|
|
1630
|
+
* This used to only log. That is what left a session pinned to a parked seat:
|
|
1631
|
+
* the rejection was fully handled here and nothing propagated, so the
|
|
1632
|
+
* platform's re-resolution — which was ready and working — was reachable only
|
|
1633
|
+
* through a token refresh that happened to be due (skaile-ai/platform#2976).
|
|
1389
1634
|
*/
|
|
1390
1635
|
handleRateLimitEvent(msg) {
|
|
1391
1636
|
const maybeRateLimit = msg;
|
|
@@ -1397,15 +1642,45 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1397
1642
|
utilization: info.utilization,
|
|
1398
1643
|
resetsAt: info.resetsAt
|
|
1399
1644
|
});
|
|
1645
|
+
if (info.status === "rejected") {
|
|
1646
|
+
this.limitRejectionSeen = true;
|
|
1647
|
+
this.lastLimitInfo = {
|
|
1648
|
+
...info.rateLimitType ? { limitType: info.rateLimitType } : {},
|
|
1649
|
+
...typeof info.resetsAt === "number" ? { resetsAt: info.resetsAt } : {},
|
|
1650
|
+
...typeof info.utilization === "number" ? { utilization: info.utilization } : {}
|
|
1651
|
+
};
|
|
1652
|
+
this.reportProviderResponse({ status: 429, ...this.lastLimitInfo });
|
|
1653
|
+
}
|
|
1400
1654
|
return true;
|
|
1401
1655
|
}
|
|
1402
1656
|
return false;
|
|
1403
1657
|
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Report an observed 401 / 429 to the host so the owning AI provider config's
|
|
1660
|
+
* live health counters can be bumped (skaile-ai/workspaces#368). Strictly
|
|
1661
|
+
* fire-and-forget: a throwing host callback must never fail the turn that
|
|
1662
|
+
* produced the observation.
|
|
1663
|
+
*/
|
|
1664
|
+
reportProviderResponse(observation) {
|
|
1665
|
+
if (!this.config.onProviderResponse || this.providerResponseReported) return;
|
|
1666
|
+
this.providerResponseReported = true;
|
|
1667
|
+
try {
|
|
1668
|
+
this.config.onProviderResponse({
|
|
1669
|
+
...observation,
|
|
1670
|
+
...this.config.aiProviderConfigId ? { configId: this.config.aiProviderConfigId } : {}
|
|
1671
|
+
});
|
|
1672
|
+
} catch (err) {
|
|
1673
|
+
this.log.warn("onProviderResponse threw; suppressing", {
|
|
1674
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1404
1678
|
handleAssistantMessage(msg) {
|
|
1405
1679
|
this.captureAssistantContext(msg);
|
|
1406
1680
|
const agentMsg = this.mapAssistantMessage(msg);
|
|
1407
1681
|
if (this.isSuppressibleAuthRender(agentMsg)) {
|
|
1408
1682
|
this.log.info("suppressing SDK auth-error assistant text; self-heal will retry");
|
|
1683
|
+
this.stashSuppressedAuthRender(this.assistantTextOf(agentMsg));
|
|
1409
1684
|
this.prevText = "";
|
|
1410
1685
|
this.emittedSanitizedLen = 0;
|
|
1411
1686
|
this.suppressingAuthStream = false;
|
|
@@ -1425,14 +1700,37 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1425
1700
|
this.prevText = "";
|
|
1426
1701
|
this.emittedSanitizedLen = 0;
|
|
1427
1702
|
this.suppressingAuthStream = false;
|
|
1703
|
+
this.suppressedAuthRenderText = null;
|
|
1428
1704
|
this.emit("agent-event", { type: "message_end", message: agentMsg });
|
|
1429
1705
|
}
|
|
1430
1706
|
// True when this assistant text is the SDK's render of a 401 about to be self-healed.
|
|
1431
1707
|
isSuppressibleAuthRender(msg) {
|
|
1432
|
-
if (this.
|
|
1708
|
+
if (!this.authSelfHealAvailable()) return false;
|
|
1433
1709
|
if (msg.toolCalls && msg.toolCalls.length > 0) return false;
|
|
1434
|
-
|
|
1435
|
-
|
|
1710
|
+
return this.textIsAuthRender(this.assistantTextOf(msg));
|
|
1711
|
+
}
|
|
1712
|
+
/** Whether a 401 on this attempt can still be self-healed and retried. */
|
|
1713
|
+
authSelfHealAvailable() {
|
|
1714
|
+
return !this.authSelfHealUsed && this.config.onAuthError != null;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Stash a dropped auth render as evidence for {@link handleResultErrorPayload}.
|
|
1718
|
+
*
|
|
1719
|
+
* Deliberately stricter than the suppression predicate. Suppression only hides
|
|
1720
|
+
* a message, but this text can reach the result path and construct an
|
|
1721
|
+
* `AuthError` — which refreshes a credential, bumps that seat's health
|
|
1722
|
+
* counters, and replays the turn. `classifyClaudeSdkError` buckets a bare
|
|
1723
|
+
* "401" or "unauthorized" as auth, so ordinary prose about authentication
|
|
1724
|
+
* qualifies; requiring a marker only the CLI itself emits keeps that prose out.
|
|
1725
|
+
*/
|
|
1726
|
+
stashSuppressedAuthRender(text) {
|
|
1727
|
+
if (!this.authSelfHealAvailable()) return;
|
|
1728
|
+
if (!isCliAuthRenderText(text)) return;
|
|
1729
|
+
this.suppressedAuthRenderText = text;
|
|
1730
|
+
}
|
|
1731
|
+
/** Flatten an assistant message's text blocks; non-text blocks contribute nothing. */
|
|
1732
|
+
assistantTextOf(msg) {
|
|
1733
|
+
return typeof msg.content === "string" ? msg.content : msg.content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
1436
1734
|
}
|
|
1437
1735
|
// Cheap substring pre-filter keeps the full classifier off the hot path of
|
|
1438
1736
|
// every assistant message / stream delta; only auth-shaped text pays for it.
|
|
@@ -1453,7 +1751,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1453
1751
|
this.suppressingAuthStream = true;
|
|
1454
1752
|
this.log.info("suppressing SDK auth-error stream; self-heal will retry");
|
|
1455
1753
|
}
|
|
1456
|
-
if (this.suppressingAuthStream)
|
|
1754
|
+
if (this.suppressingAuthStream) {
|
|
1755
|
+
this.stashSuppressedAuthRender(this.prevText);
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1457
1758
|
const safe = streamingSafeSanitize(this.prevText);
|
|
1458
1759
|
const sanitizedDelta = safe.slice(this.emittedSanitizedLen);
|
|
1459
1760
|
this.emittedSanitizedLen = safe.length;
|
|
@@ -1577,11 +1878,16 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1577
1878
|
/**
|
|
1578
1879
|
* Classify + route an `is_error` result payload: auth-shaped errors throw
|
|
1579
1880
|
* via `failTurn`; populated non-auth errors surface a normal `error` event;
|
|
1580
|
-
* empty errors defer to the consumer-error catch.
|
|
1881
|
+
* empty errors defer to the consumer-error catch under a bounded grace timer.
|
|
1882
|
+
*
|
|
1883
|
+
* An empty `errors[]` falls back to the auth render this attempt suppressed:
|
|
1884
|
+
* handed nothing, the classifier can only answer `unknown`, stranding a 401
|
|
1885
|
+
* the render path already recognised on the same text.
|
|
1581
1886
|
*/
|
|
1582
1887
|
handleResultErrorPayload(msg, errors) {
|
|
1888
|
+
const effectiveErrors = errors.length === 0 && this.suppressedAuthRenderText != null ? [this.suppressedAuthRenderText] : errors;
|
|
1583
1889
|
const rawDetail = classifyClaudeSdkError({
|
|
1584
|
-
errors,
|
|
1890
|
+
errors: effectiveErrors,
|
|
1585
1891
|
terminalReason: msg.terminal_reason,
|
|
1586
1892
|
subtype: msg.subtype,
|
|
1587
1893
|
oauthCredential: this.usingOauthCredential
|
|
@@ -1591,20 +1897,35 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1591
1897
|
subtype: msg.subtype,
|
|
1592
1898
|
terminalReason: msg.terminal_reason,
|
|
1593
1899
|
errorsCount: errors.length,
|
|
1900
|
+
fromSuppressedAuthRender: effectiveErrors !== errors,
|
|
1594
1901
|
category: detail.category,
|
|
1595
1902
|
retryable: detail.retryable
|
|
1596
1903
|
});
|
|
1597
|
-
|
|
1904
|
+
if (detail.category === "auth") {
|
|
1905
|
+
this.reportProviderResponse({ status: 401 });
|
|
1906
|
+
} else if (isSeatCapacityFailure(detail, msg.subtype)) {
|
|
1907
|
+
this.limitRejectionSeen = true;
|
|
1908
|
+
this.reportProviderResponse({ status: 429, ...this.lastLimitInfo ?? {} });
|
|
1909
|
+
}
|
|
1910
|
+
const errorText = effectiveErrors.join("; ") || `Agent stopped: ${msg.subtype}`;
|
|
1598
1911
|
if (detail.category === "auth") {
|
|
1599
1912
|
this.failTurn(new AuthError({ ...detail, message: errorText }), detail);
|
|
1600
1913
|
} else if (detail.category === "account") {
|
|
1601
1914
|
this.failTurn(new AccountUnavailableError({ ...detail, message: errorText }), detail);
|
|
1915
|
+
} else if (this.willFailoverOnLimit()) {
|
|
1916
|
+
this.limitTurnRejectedForFailover = true;
|
|
1917
|
+
this.failTurn(new Error(errorText), detail);
|
|
1602
1918
|
} else if (errors.length > 0) {
|
|
1603
1919
|
this.emit("agent-event", {
|
|
1604
1920
|
type: "error",
|
|
1605
1921
|
error: errorText,
|
|
1606
1922
|
detail
|
|
1607
1923
|
});
|
|
1924
|
+
} else {
|
|
1925
|
+
this.armErrorResultGrace(detail, {
|
|
1926
|
+
subtype: msg.subtype,
|
|
1927
|
+
terminalReason: msg.terminal_reason
|
|
1928
|
+
});
|
|
1608
1929
|
}
|
|
1609
1930
|
}
|
|
1610
1931
|
// ---------------------------------------------------------------------------
|