agent-relay-runner 0.91.1 → 0.91.2
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/package.json
CHANGED
|
@@ -10,15 +10,27 @@ error="$(relay_json_string_field error_type "$payload")"
|
|
|
10
10
|
[ -z "$error" ] && error="$(relay_json_string_field error "$payload")"
|
|
11
11
|
reset_time="$(relay_json_number_field reset_time "$payload")"
|
|
12
12
|
error_message="$(relay_json_string_field error_message "$payload")"
|
|
13
|
+
[ -z "$error_message" ] && error_message="$(relay_json_string_field error_details "$payload")"
|
|
14
|
+
[ -z "$error_message" ] && error_message="$(relay_json_string_field last_assistant_message "$payload")"
|
|
13
15
|
|
|
14
16
|
# #286: a usage/rate-limit stall is transient and time-bounded — hold the agent
|
|
15
17
|
# (providerState blocked) and auto-resume at reset, instead of silently going idle.
|
|
16
18
|
# The disambiguator for billing_error is reset_time: a subscription rolling-window
|
|
17
19
|
# limit carries one (and is auto-resumable); true credit exhaustion does not (fatal).
|
|
18
20
|
case "$error" in
|
|
19
|
-
rate_limit|overloaded)
|
|
21
|
+
rate_limit|overloaded|server_error|api_error)
|
|
20
22
|
relay_post_rate_limit "$error" "$reset_time" "$error_message"
|
|
21
23
|
;;
|
|
24
|
+
unknown)
|
|
25
|
+
case "$error_message" in
|
|
26
|
+
*"529"*|*"Overloaded"*|*"overloaded"*|*"temporar"*|*"server-side"*|*"server error"*)
|
|
27
|
+
relay_post_rate_limit "$error" "$reset_time" "$error_message"
|
|
28
|
+
;;
|
|
29
|
+
*)
|
|
30
|
+
relay_post_status_clearing_subagents idle
|
|
31
|
+
;;
|
|
32
|
+
esac
|
|
33
|
+
;;
|
|
22
34
|
billing_error)
|
|
23
35
|
case "$reset_time" in
|
|
24
36
|
''|*[!0-9]*) relay_post_status_clearing_subagents error ;;
|
package/src/adapters/claude.ts
CHANGED
|
@@ -559,7 +559,7 @@ export function claudeRateLimitStatus(text: string, sessionName?: string): Provi
|
|
|
559
559
|
status: "idle",
|
|
560
560
|
clear: ["subagent"],
|
|
561
561
|
providerState: buildRateLimitProviderState({
|
|
562
|
-
errorType: "session_limit",
|
|
562
|
+
errorType: parsed.errorType ?? "session_limit", kind: parsed.kind ?? "usage_limit",
|
|
563
563
|
...(parsed.resetAt ? { resetAt: parsed.resetAt } : {}),
|
|
564
564
|
message: parsed.message,
|
|
565
565
|
source: sessionName ? `claude-pane:${sessionName}` : "claude-pane",
|
package/src/rate-limit.ts
CHANGED
|
@@ -17,6 +17,7 @@ const MAX_RESET_AHEAD_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
17
17
|
|
|
18
18
|
interface RateLimitHoldInput {
|
|
19
19
|
errorType?: string;
|
|
20
|
+
kind?: "usage_limit" | "transient_overload";
|
|
20
21
|
/** Unix ms the limit window resets, when known. */
|
|
21
22
|
resetAt?: number;
|
|
22
23
|
message?: string;
|
|
@@ -27,12 +28,18 @@ interface RateLimitHoldInput {
|
|
|
27
28
|
export function buildRateLimitProviderState(input: RateLimitHoldInput): Record<string, unknown> {
|
|
28
29
|
const now = Date.now();
|
|
29
30
|
const resetAt = typeof input.resetAt === "number" && Number.isFinite(input.resetAt) ? input.resetAt : undefined;
|
|
30
|
-
const
|
|
31
|
+
const kind = input.kind ?? rateLimitKindForError(input.errorType);
|
|
32
|
+
const label = kind === "transient_overload"
|
|
33
|
+
? "provider overloaded"
|
|
34
|
+
: resetAt ? `usage limit · resets ${formatResetClock(resetAt)}` : "usage limit reached";
|
|
31
35
|
return {
|
|
32
36
|
state: "blocked",
|
|
33
37
|
reason: RATE_LIMIT_BLOCK_REASON,
|
|
34
38
|
label,
|
|
35
|
-
recommendedAction:
|
|
39
|
+
recommendedAction: kind === "transient_overload"
|
|
40
|
+
? "Holding briefly for provider overload; agent-relay will retry with bounded backoff."
|
|
41
|
+
: "Holding until the usage window resets; agent-relay auto-resumes the agent.",
|
|
42
|
+
kind,
|
|
36
43
|
source: input.source ?? "claude",
|
|
37
44
|
errorType: input.errorType ?? RATE_LIMIT_BLOCK_REASON,
|
|
38
45
|
enteredAt: now,
|
|
@@ -42,6 +49,10 @@ export function buildRateLimitProviderState(input: RateLimitHoldInput): Record<s
|
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
function rateLimitKindForError(errorType: string | undefined): "usage_limit" | "transient_overload" {
|
|
53
|
+
return /^(overloaded|server_error|api_error|unknown|529|5\d\d)$/i.test(errorType ?? "") ? "transient_overload" : "usage_limit";
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
/** Host-local HH:MM for the chat notice / badge label (matches how the CLI shows "resets 3:45pm"). */
|
|
46
57
|
function formatResetClock(resetAtMs: number): string {
|
|
47
58
|
try {
|
|
@@ -64,6 +75,9 @@ interface PaneRateLimit {
|
|
|
64
75
|
message: string;
|
|
65
76
|
/** Unix ms reset, when parseable from the pane (else undefined → resume falls back to poll). */
|
|
66
77
|
resetAt?: number;
|
|
78
|
+
/** Usage limits wait for reset; transient provider overloads retry quickly. */
|
|
79
|
+
kind?: "usage_limit" | "transient_overload";
|
|
80
|
+
errorType?: string;
|
|
67
81
|
}
|
|
68
82
|
|
|
69
83
|
/**
|
|
@@ -75,6 +89,8 @@ interface PaneRateLimit {
|
|
|
75
89
|
*/
|
|
76
90
|
export function parseClaudeRateLimitPane(text: string, nowMs: number = Date.now()): PaneRateLimit | null {
|
|
77
91
|
if (!text) return null;
|
|
92
|
+
const overload = parseClaudeTransientOverloadPane(text);
|
|
93
|
+
if (overload) return overload;
|
|
78
94
|
const limit = LIMIT_PHRASE_RE.exec(text);
|
|
79
95
|
if (!limit) return null;
|
|
80
96
|
const message = limitLineAt(text, limit.index);
|
|
@@ -95,6 +111,21 @@ export function parseClaudeRateLimitPane(text: string, nowMs: number = Date.now(
|
|
|
95
111
|
return null;
|
|
96
112
|
}
|
|
97
113
|
|
|
114
|
+
const TRANSIENT_API_ERROR_RE = /\bAPI Error:\s*((?:5\d\d)|529)\b[^\n]*(?:overload|server-side|temporar|try again|status\.claude\.com)?[^\n]*/i;
|
|
115
|
+
const TRANSIENT_5XX_RE = /\b(?:5(?:00|02|03|04|20|29)|529)\b[^\n]*(?:overload|server-side|temporar|try again|status\.claude\.com)\b[^\n]*/i;
|
|
116
|
+
|
|
117
|
+
function parseClaudeTransientOverloadPane(text: string): PaneRateLimit | null {
|
|
118
|
+
const match = TRANSIENT_API_ERROR_RE.exec(text) ?? TRANSIENT_5XX_RE.exec(text);
|
|
119
|
+
if (!match) return null;
|
|
120
|
+
const message = limitLineAt(text, match.index);
|
|
121
|
+
const code = match[1] ?? /\b(5\d\d|529)\b/.exec(match[0])?.[1];
|
|
122
|
+
return {
|
|
123
|
+
message: message || match[0].trim(),
|
|
124
|
+
kind: "transient_overload",
|
|
125
|
+
errorType: code === "529" ? "overloaded" : "server_error",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
export interface ClaudeRateLimitPaneGateState {
|
|
99
130
|
consecutiveDetections: number;
|
|
100
131
|
reported: boolean;
|
|
@@ -116,7 +147,8 @@ export function gatedClaudeRateLimitStatus(
|
|
|
116
147
|
status: "idle",
|
|
117
148
|
clear: ["subagent"],
|
|
118
149
|
providerState: buildRateLimitProviderState({
|
|
119
|
-
errorType: "session_limit",
|
|
150
|
+
errorType: parsed.errorType ?? "session_limit",
|
|
151
|
+
kind: parsed.kind ?? "usage_limit",
|
|
120
152
|
...(parsed.resetAt ? { resetAt: parsed.resetAt } : {}),
|
|
121
153
|
message: parsed.message,
|
|
122
154
|
source: options.sessionName ? `claude-pane:${options.sessionName}` : "claude-pane",
|