@onepointfour-packs/flaredeck-agent 0.2.38 → 0.2.40

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.
Files changed (87) hide show
  1. package/dist/agent.d.ts +42 -3
  2. package/dist/agent.js +84 -6
  3. package/dist/agent.js.map +1 -1
  4. package/dist/automode.js +43 -3
  5. package/dist/automode.js.map +1 -1
  6. package/dist/branches.d.ts +12 -1
  7. package/dist/branches.js +13 -3
  8. package/dist/branches.js.map +1 -1
  9. package/dist/browser.d.ts +52 -0
  10. package/dist/browser.js +185 -17
  11. package/dist/browser.js.map +1 -1
  12. package/dist/config.d.ts +6 -0
  13. package/dist/config.js +36 -2
  14. package/dist/config.js.map +1 -1
  15. package/dist/credentials.d.ts +217 -0
  16. package/dist/credentials.js +332 -0
  17. package/dist/credentials.js.map +1 -0
  18. package/dist/engine-antigravity.js +2 -0
  19. package/dist/engine-antigravity.js.map +1 -1
  20. package/dist/engine-claude.js +14 -2
  21. package/dist/engine-claude.js.map +1 -1
  22. package/dist/engine.d.ts +19 -0
  23. package/dist/engine.js.map +1 -1
  24. package/dist/environment.d.ts +13 -1
  25. package/dist/environment.js +35 -2
  26. package/dist/environment.js.map +1 -1
  27. package/dist/envtools.d.ts +58 -0
  28. package/dist/envtools.js +151 -0
  29. package/dist/envtools.js.map +1 -0
  30. package/dist/flaredeck.d.ts +24 -0
  31. package/dist/flaredeck.js +20 -0
  32. package/dist/flaredeck.js.map +1 -1
  33. package/dist/github.d.ts +2 -0
  34. package/dist/github.js +4 -0
  35. package/dist/github.js.map +1 -1
  36. package/dist/githubauth.d.ts +11 -1
  37. package/dist/githubauth.js +82 -16
  38. package/dist/githubauth.js.map +1 -1
  39. package/dist/hydrate.d.ts +114 -0
  40. package/dist/hydrate.js +79 -0
  41. package/dist/hydrate.js.map +1 -0
  42. package/dist/limits.d.ts +184 -0
  43. package/dist/limits.js +251 -0
  44. package/dist/limits.js.map +1 -0
  45. package/dist/main.js +178 -96
  46. package/dist/main.js.map +1 -1
  47. package/dist/picker.js +50 -4
  48. package/dist/picker.js.map +1 -1
  49. package/dist/preflight.d.ts +43 -0
  50. package/dist/preflight.js +133 -19
  51. package/dist/preflight.js.map +1 -1
  52. package/dist/providers.d.ts +10 -0
  53. package/dist/providers.js +23 -0
  54. package/dist/providers.js.map +1 -1
  55. package/dist/runs.d.ts +11 -3
  56. package/dist/runs.js +41 -4
  57. package/dist/runs.js.map +1 -1
  58. package/dist/serve.js +1061 -114
  59. package/dist/serve.js.map +1 -1
  60. package/dist/session.d.ts +51 -0
  61. package/dist/session.js +145 -0
  62. package/dist/session.js.map +1 -0
  63. package/dist/settle.d.ts +18 -0
  64. package/dist/settle.js +39 -0
  65. package/dist/settle.js.map +1 -0
  66. package/dist/signin.d.ts +47 -0
  67. package/dist/signin.js +331 -0
  68. package/dist/signin.js.map +1 -0
  69. package/dist/stream.d.ts +6 -0
  70. package/dist/stream.js +44 -3
  71. package/dist/stream.js.map +1 -1
  72. package/dist/subscriptions.d.ts +115 -0
  73. package/dist/subscriptions.js +140 -0
  74. package/dist/subscriptions.js.map +1 -0
  75. package/dist/ui.d.ts +11 -0
  76. package/dist/ui.js +6 -1
  77. package/dist/ui.js.map +1 -1
  78. package/dist/usage.d.ts +105 -0
  79. package/dist/usage.js +348 -0
  80. package/dist/usage.js.map +1 -0
  81. package/dist/wall.d.ts +35 -0
  82. package/dist/wall.js +94 -0
  83. package/dist/wall.js.map +1 -0
  84. package/dist/worktree.d.ts +19 -0
  85. package/dist/worktree.js +179 -4
  86. package/dist/worktree.js.map +1 -1
  87. package/package.json +1 -1
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Noticing that a Claude subscription has hit its cap.
3
+ *
4
+ * This had to exist before subscription routing could: the harness had no usage-limit detection
5
+ * at all. The only rate-limit handling anywhere was in intake.ts, for its own Haiku call, which
6
+ * is a different account and a different failure.
7
+ *
8
+ * NOTHING HERE IS GUESSED. Every string and every field below comes from the installed SDK's own
9
+ * declarations, which was the one thing worth checking before writing a line of routing: a matcher
10
+ * built on an invented error shape never fires, and a rotation that never fires is worse than no
11
+ * rotation, because it looks like it works. What the SDK actually offers, verified in
12
+ * node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts:
13
+ *
14
+ * 1. `SDKRateLimitEvent` — `{ type: 'rate_limit_event', rate_limit_info: SDKRateLimitInfo }`,
15
+ * where `status` is `'allowed' | 'allowed_warning' | 'rejected'`, `rateLimitType` is one of
16
+ * `five_hour | seven_day | seven_day_opus | seven_day_sonnet | seven_day_overage_included |
17
+ * overage`, and `resetsAt` is an epoch. It is part of the `SDKMessage` union, so it arrives
18
+ * in the ordinary stream. This is the good one: it names the WINDOW and the RESET TIME, which
19
+ * is exactly what a ledger needs and exactly what text parsing cannot reliably give.
20
+ * 2. `SDKAssistantMessage.error?: SDKAssistantMessageError`, which includes `'rate_limit'`.
21
+ * 3. `USAGE_LIMIT_ERROR_PREFIXES` — exported from the SDK's `/core` entry point and verified
22
+ * importable at runtime (12 entries). It is the CLI's own list of "a usage limit was
23
+ * genuinely reached" messages, so the text fallback is the SDK's list rather than my reading
24
+ * of one error I happened to see.
25
+ *
26
+ * And the trap, which is why (3) matters more than it looks: the SDK also exports
27
+ * `USAGE_WARNING_PREFIXES` = ["You've used", "You're close to"] and `USAGE_TRANSITION_PREFIXES`
28
+ * ("You're now using usage credits", …). Those are footers and toasts for a subscription that is
29
+ * still working. A hand-written matcher for "You've…" would rotate away a perfectly good
30
+ * subscription every time it crossed 50%, and the symptom — subscriptions marked spent that
31
+ * aren't — would look like the ledger being broken rather than the matcher. They are checked
32
+ * against explicitly, before the limit list, so the ordering cannot be lost in a later edit.
33
+ */
34
+ /**
35
+ * How much of each window a subscription has already used.
36
+ *
37
+ * This is what makes PREDICTIVE routing possible: send work where there is room, rather than
38
+ * driving one plan to exhaustion before touching the others.
39
+ *
40
+ * WHERE IT COMES FROM, and this took a correction. The numbers live on
41
+ * `SDKControlGetUsageResponse`, which is the answer to an explicit control request — NOT on the
42
+ * system/init message, where an earlier version of this file looked for them. That version would
43
+ * have parsed every init message, found nothing, reported nothing, and left routing permanently
44
+ * falling back to configured order while appearing to work. The only honest way to find that out
45
+ * was to read which TYPE owns the fields, so: `rate_limits`, `rate_limits_available` and
46
+ * `subscription_type` are declared on `SDKControlGetUsageResponse` (sdk.d.ts), obtained by calling
47
+ *
48
+ * usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({ skipBehaviors: true })
49
+ *
50
+ * on the query handle. `skipBehaviors` matters: without it the call scans every local transcript
51
+ * touched in the last seven days, which on a box running agents all day is a lot of disk for a
52
+ * number we use to break ties.
53
+ *
54
+ * The method name is the SDK telling us not to depend on it, and it says the name will change when
55
+ * the API stabilises. So the caller must feature-detect it and swallow any failure — a rename must
56
+ * cost predictive routing and nothing else. `usageFrom` below never throws and returns null for
57
+ * anything it does not recognise, which is the shape that degrades to reactive on its own.
58
+ *
59
+ * Within the response, `utilization` is documented "Percentage of the window used, 0-100" on every
60
+ * window type and `resets_at` is ISO 8601. The streamed `SDKRateLimitEvent` also carries a
61
+ * `utilization`, but with NO documented unit — `0.8` could be 80% or 0.8% — so the stream is used
62
+ * for the reactive signal only, where `status: 'rejected'` needs no units at all.
63
+ */
64
+ export type UsageReading = {
65
+ /** 'pro' | 'max' | 'team' | 'enterprise', or null on an API-key/3P session. */
66
+ subscriptionType: string | null;
67
+ /**
68
+ * False for API key, Bedrock, Vertex or a missing profile scope.
69
+ *
70
+ * It must never be confused with "plenty of headroom". A profile with no plan limits cannot be
71
+ * SCHEDULED by utilisation at all, which is a different thing from being empty, and treating
72
+ * absent data as 0% used would send every run to the one plan nobody can measure.
73
+ */
74
+ rateLimitsAvailable: boolean;
75
+ /**
76
+ * Per window, keyed by the SDK's own names. Whatever windows the response reported are carried
77
+ * through — `seven_day_opus`, `seven_day_oauth_apps` and the rest included — rather than a
78
+ * hardcoded two, so a window the SDK adds arrives as data.
79
+ */
80
+ windows: Record<string, {
81
+ utilization: number | null;
82
+ resetsAt: number | null;
83
+ }>;
84
+ };
85
+ /** ISO 8601 → epoch ms, or null. Never throws on a malformed string. */
86
+ /**
87
+ * Both governing windows from a `rate_limit_event`, whatever its status.
88
+ *
89
+ * Every model response carries the account's rate-limit state, and the CLI passes it on as
90
+ * `rate_limit_info.unifiedWindows` — five_hour and seven_day, each with utilisation and reset.
91
+ * This is the ONLY usage source a setup-token has: the usage endpoint needs the user:profile
92
+ * scope, which `claude setup-token` does not grant (rate_limits_available=false). Measured on a
93
+ * real account: `{"five_hour":{"utilization":0.43,"resetsAt":1791663000},"seven_day":{...0.58}}`.
94
+ *
95
+ * Units, both checked against that reading: utilisation is a 0-1 FRACTION (×100 here, so 0.43 is
96
+ * 43%, not 0.43%), and resetsAt is epoch SECONDS.
97
+ */
98
+ export declare function windowsFromRateLimit(message: unknown): {
99
+ window: string;
100
+ utilization: number;
101
+ resetsAt?: number;
102
+ }[];
103
+ /** An epoch in ms, whichever unit it came in: anything below 1e12 is seconds (before 2001 in ms). */
104
+ export declare function epochMs(t: number): number;
105
+ /**
106
+ * A utilisation reading from whatever the usage getter returned, or null.
107
+ *
108
+ * Deliberately shape-driven rather than tied to a message type: it accepts the response object
109
+ * itself, or anything carrying it as a `usage` / `data` property, because the getter is marked
110
+ * unstable and the wrapper around it is the likeliest thing to move. The gate is the presence of
111
+ * a boolean `rate_limits_available` — the field that says whether any of this applies at all.
112
+ *
113
+ * `utilization` passes through only when it is a finite number in 0-100. A null, a missing field
114
+ * or a nonsense value stays null, and null means UNKNOWN everywhere downstream, never zero. That
115
+ * distinction is the whole difference between "this plan is empty" and "nobody can tell", and
116
+ * conflating them is the one way predictive routing could be worse than reactive.
117
+ */
118
+ export declare function usageFrom(source: unknown): UsageReading | null;
119
+ /**
120
+ * Kept so existing callers compile, but it will not find anything in a STREAM message.
121
+ *
122
+ * `rate_limits` is declared on `SDKControlGetUsageResponse` — the answer to an explicit control
123
+ * request — and on no message in the `SDKMessage` union. Calling this per streamed message
124
+ * therefore returns null every time, which is exactly the "looks like it works" failure worth
125
+ * avoiding: routing would fall back to configured order for ever and nothing would say why.
126
+ *
127
+ * Use `readUsage(handle)` once per run instead. This remains only because it is shape-driven, so
128
+ * it costs nothing and would start working if the fields ever do appear on a message.
129
+ *
130
+ * @deprecated prefer `readUsage`
131
+ */
132
+ export declare const usageFromMessage: typeof usageFrom;
133
+ /**
134
+ * Ask a live query handle for the usage data, defensively.
135
+ *
136
+ * Everything about this call is guarded, because the method's own name says it may vanish: the
137
+ * handle may not have it, it may throw, it may answer something unrecognisable. Each of those
138
+ * returns null, which the ledger reads as "this plan cannot be measured right now" and routes
139
+ * reactively instead. Nothing here can fail a card.
140
+ */
141
+ export declare function readUsage(handle: unknown): Promise<UsageReading | null>;
142
+ /** The SDK's own vocabulary for the window that tripped. `unknown` for the text-only paths. */
143
+ export type LimitWindow = 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet' | 'seven_day_overage_included' | 'overage' | 'unknown';
144
+ export declare const LIMIT_WINDOWS: readonly LimitWindow[];
145
+ export type LimitHit = {
146
+ window: LimitWindow;
147
+ /** Epoch ms, when the SDK told us. Absent means the caller applies a default — see below. */
148
+ resetsAt?: number;
149
+ /** 0–1 where the SDK reported it. Only ever for information; routing uses `status`. */
150
+ utilization?: number;
151
+ /** Which of the three signals saw it, so a surprising rotation can be traced to its evidence. */
152
+ source: 'rate_limit_event' | 'assistant_error' | 'result_text' | 'assistant_text';
153
+ /** A short line for the card and the log. Never contains a token — see note at the bottom. */
154
+ why: string;
155
+ };
156
+ /**
157
+ * Whether a piece of text means "this subscription is done for now".
158
+ *
159
+ * Warnings and overage transitions are ruled out FIRST. "You've used 80% of your limit" and
160
+ * "You've hit your limit" differ by one word and mean opposite things, and the SDK ships both
161
+ * lists precisely because the distinction is not something to re-derive.
162
+ */
163
+ export declare function isUsageLimitText(text: string | null | undefined): boolean;
164
+ /**
165
+ * A usage-limit hit in an SDK message, or null.
166
+ *
167
+ * Takes `unknown` and reads defensively rather than taking a typed `SDKMessage`: this runs
168
+ * against whatever the installed CLI emits, and a field that moves should make the detector miss
169
+ * one signal, not throw in the middle of a card. The three signals are independent, so losing one
170
+ * degrades rather than breaks.
171
+ */
172
+ export declare function limitFromMessage(message: unknown): LimitHit | null;
173
+ /**
174
+ * A usage-limit hit in a bare string, or null.
175
+ *
176
+ * The degraded path, and it exists so this is useful TODAY. `runTask` in agent.ts flattens the
177
+ * SDK stream into `AgentEvent`s and drops `rate_limit_event` entirely, so without a change there
178
+ * the only thing a consumer can see is the `done` event's `result` text. That is enough to
179
+ * rotate on; it just cannot say which window tripped, so the ledger applies a default.
180
+ */
181
+ export declare function limitFromText(text: string | null | undefined): LimitHit | null;
182
+ /** When a window that tripped now should be assumed to free up, if the SDK did not say. */
183
+ export declare function defaultResetFor(window: LimitWindow, now?: number): number;
184
+ export declare function describeWindow(window: LimitWindow): string;
package/dist/limits.js ADDED
@@ -0,0 +1,251 @@
1
+ import { USAGE_LIMIT_ERROR_PREFIXES, USAGE_TRANSITION_PREFIXES, USAGE_WARNING_PREFIXES, } from '@anthropic-ai/claude-agent-sdk/core';
2
+ /** ISO 8601 → epoch ms, or null. Never throws on a malformed string. */
3
+ /**
4
+ * Both governing windows from a `rate_limit_event`, whatever its status.
5
+ *
6
+ * Every model response carries the account's rate-limit state, and the CLI passes it on as
7
+ * `rate_limit_info.unifiedWindows` — five_hour and seven_day, each with utilisation and reset.
8
+ * This is the ONLY usage source a setup-token has: the usage endpoint needs the user:profile
9
+ * scope, which `claude setup-token` does not grant (rate_limits_available=false). Measured on a
10
+ * real account: `{"five_hour":{"utilization":0.43,"resetsAt":1791663000},"seven_day":{...0.58}}`.
11
+ *
12
+ * Units, both checked against that reading: utilisation is a 0-1 FRACTION (×100 here, so 0.43 is
13
+ * 43%, not 0.43%), and resetsAt is epoch SECONDS.
14
+ */
15
+ export function windowsFromRateLimit(message) {
16
+ const m = message;
17
+ if (!m || m['type'] !== 'rate_limit_event')
18
+ return [];
19
+ const info = m['rate_limit_info'] ?? {};
20
+ const pct = (u) => typeof u === 'number' && Number.isFinite(u) && u >= 0 ? Math.min(100, Math.round((u <= 1 ? u * 100 : u) * 10) / 10) : null;
21
+ const out = [];
22
+ const unified = info['unifiedWindows'];
23
+ if (unified && typeof unified === 'object') {
24
+ for (const name of ['five_hour', 'seven_day']) {
25
+ const w = unified[name];
26
+ const u = pct(w?.utilization);
27
+ if (u === null)
28
+ continue;
29
+ out.push({ window: name, utilization: u, ...(typeof w?.resetsAt === 'number' ? { resetsAt: epochMs(w.resetsAt) } : {}) });
30
+ }
31
+ return out;
32
+ }
33
+ // An older CLI: only the window that is currently binding.
34
+ const u = pct(info['utilization']);
35
+ if (u !== null && (info['rateLimitType'] === 'five_hour' || info['rateLimitType'] === 'seven_day')) {
36
+ out.push({ window: info['rateLimitType'], utilization: u, ...(typeof info['resetsAt'] === 'number' ? { resetsAt: epochMs(info['resetsAt']) } : {}) });
37
+ }
38
+ return out;
39
+ }
40
+ /** An epoch in ms, whichever unit it came in: anything below 1e12 is seconds (before 2001 in ms). */
41
+ export function epochMs(t) {
42
+ return t > 0 && t < 1e12 ? t * 1000 : t;
43
+ }
44
+ function epochOf(iso) {
45
+ if (typeof iso !== 'string' || !iso)
46
+ return null;
47
+ const t = Date.parse(iso);
48
+ return Number.isFinite(t) ? t : null;
49
+ }
50
+ /**
51
+ * A utilisation reading from whatever the usage getter returned, or null.
52
+ *
53
+ * Deliberately shape-driven rather than tied to a message type: it accepts the response object
54
+ * itself, or anything carrying it as a `usage` / `data` property, because the getter is marked
55
+ * unstable and the wrapper around it is the likeliest thing to move. The gate is the presence of
56
+ * a boolean `rate_limits_available` — the field that says whether any of this applies at all.
57
+ *
58
+ * `utilization` passes through only when it is a finite number in 0-100. A null, a missing field
59
+ * or a nonsense value stays null, and null means UNKNOWN everywhere downstream, never zero. That
60
+ * distinction is the whole difference between "this plan is empty" and "nobody can tell", and
61
+ * conflating them is the one way predictive routing could be worse than reactive.
62
+ */
63
+ export function usageFrom(source) {
64
+ const candidates = [source, source?.usage, source?.data, source?.response];
65
+ for (const c of candidates) {
66
+ const r = c;
67
+ if (!r || typeof r !== 'object')
68
+ continue;
69
+ if (typeof r['rate_limits_available'] !== 'boolean')
70
+ continue;
71
+ const windows = {};
72
+ for (const [name, raw] of Object.entries((r['rate_limits'] ?? {}))) {
73
+ if (!raw || typeof raw !== 'object')
74
+ continue;
75
+ const u = raw['utilization'];
76
+ windows[name] = {
77
+ utilization: typeof u === 'number' && Number.isFinite(u) && u >= 0 && u <= 100 ? u : null,
78
+ resetsAt: epochOf(raw['resets_at']),
79
+ };
80
+ }
81
+ return {
82
+ subscriptionType: typeof r['subscription_type'] === 'string' ? r['subscription_type'] : null,
83
+ rateLimitsAvailable: r['rate_limits_available'],
84
+ windows,
85
+ };
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Kept so existing callers compile, but it will not find anything in a STREAM message.
91
+ *
92
+ * `rate_limits` is declared on `SDKControlGetUsageResponse` — the answer to an explicit control
93
+ * request — and on no message in the `SDKMessage` union. Calling this per streamed message
94
+ * therefore returns null every time, which is exactly the "looks like it works" failure worth
95
+ * avoiding: routing would fall back to configured order for ever and nothing would say why.
96
+ *
97
+ * Use `readUsage(handle)` once per run instead. This remains only because it is shape-driven, so
98
+ * it costs nothing and would start working if the fields ever do appear on a message.
99
+ *
100
+ * @deprecated prefer `readUsage`
101
+ */
102
+ export const usageFromMessage = usageFrom;
103
+ /**
104
+ * Ask a live query handle for the usage data, defensively.
105
+ *
106
+ * Everything about this call is guarded, because the method's own name says it may vanish: the
107
+ * handle may not have it, it may throw, it may answer something unrecognisable. Each of those
108
+ * returns null, which the ledger reads as "this plan cannot be measured right now" and routes
109
+ * reactively instead. Nothing here can fail a card.
110
+ */
111
+ export async function readUsage(handle) {
112
+ const name = 'usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET';
113
+ const fn = handle?.[name];
114
+ if (typeof fn !== 'function')
115
+ return null;
116
+ try {
117
+ // skipBehaviors: the behaviours scan reads every transcript touched in the last seven days,
118
+ // and we want two percentages.
119
+ return usageFrom(await fn.call(handle, { skipBehaviors: true }));
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ export const LIMIT_WINDOWS = [
126
+ 'five_hour', 'seven_day', 'seven_day_opus', 'seven_day_sonnet', 'seven_day_overage_included', 'overage', 'unknown',
127
+ ];
128
+ const startsWithAny = (text, prefixes) => {
129
+ const t = text.trimStart();
130
+ return prefixes.some((p) => t.startsWith(p));
131
+ };
132
+ /**
133
+ * Whether a piece of text means "this subscription is done for now".
134
+ *
135
+ * Warnings and overage transitions are ruled out FIRST. "You've used 80% of your limit" and
136
+ * "You've hit your limit" differ by one word and mean opposite things, and the SDK ships both
137
+ * lists precisely because the distinction is not something to re-derive.
138
+ */
139
+ export function isUsageLimitText(text) {
140
+ if (!text)
141
+ return false;
142
+ if (startsWithAny(text, USAGE_WARNING_PREFIXES))
143
+ return false;
144
+ if (startsWithAny(text, USAGE_TRANSITION_PREFIXES))
145
+ return false;
146
+ return startsWithAny(text, USAGE_LIMIT_ERROR_PREFIXES);
147
+ }
148
+ /** The first line, trimmed — enough to say WHY a subscription was parked, short enough to log. */
149
+ const firstLine = (text, cap = 160) => (text.trim().split('\n')[0] ?? '').slice(0, cap);
150
+ /**
151
+ * A usage-limit hit in an SDK message, or null.
152
+ *
153
+ * Takes `unknown` and reads defensively rather than taking a typed `SDKMessage`: this runs
154
+ * against whatever the installed CLI emits, and a field that moves should make the detector miss
155
+ * one signal, not throw in the middle of a card. The three signals are independent, so losing one
156
+ * degrades rather than breaks.
157
+ */
158
+ export function limitFromMessage(message) {
159
+ const m = message;
160
+ if (!m || typeof m !== 'object')
161
+ return null;
162
+ // (1) The structured event. The only path that knows the window and the reset time.
163
+ if (m['type'] === 'rate_limit_event') {
164
+ const info = m['rate_limit_info'] ?? {};
165
+ // 'allowed_warning' is a subscription that still works. Routing on it would retire all three
166
+ // early and leave the agents with nothing while every account still had capacity.
167
+ const rejected = info['status'] === 'rejected';
168
+ if (!rejected)
169
+ return null;
170
+ const window = LIMIT_WINDOWS.includes(info['rateLimitType'])
171
+ ? info['rateLimitType']
172
+ : 'unknown';
173
+ return {
174
+ window,
175
+ /* Epoch SECONDS from the CLI (the rate-limit header's unit), not milliseconds. Passed on
176
+ raw, the ledger read it as a time in 1970, refused it as "already past", and fell back
177
+ to a full window — so a weekly cap that reset at 21:00 blocked the plan for seven days. */
178
+ ...(typeof info['resetsAt'] === 'number' ? { resetsAt: epochMs(info['resetsAt']) } : {}),
179
+ ...(typeof info['utilization'] === 'number' ? { utilization: info['utilization'] } : {}),
180
+ source: 'rate_limit_event',
181
+ why: `the ${describeWindow(window)} limit is spent`,
182
+ };
183
+ }
184
+ // (2) The typed error on an assistant message. No window, no reset time.
185
+ if (m['type'] === 'assistant' && m['error'] === 'rate_limit') {
186
+ return { window: 'unknown', source: 'assistant_error', why: 'the model reported a rate limit' };
187
+ }
188
+ // (3) The text paths, against the SDK's own prefix lists. A usage limit ends the turn with a
189
+ // non-success result, and the limit text also arrives as a synthetic assistant message.
190
+ if (m['type'] === 'result' && m['subtype'] !== 'success' && isUsageLimitText(m['result'])) {
191
+ return { window: 'unknown', source: 'result_text', why: firstLine(String(m['result'])) };
192
+ }
193
+ if (m['type'] === 'assistant') {
194
+ for (const block of m['message']?.content ?? []) {
195
+ if (block?.type === 'text' && isUsageLimitText(block.text)) {
196
+ return { window: 'unknown', source: 'assistant_text', why: firstLine(String(block.text)) };
197
+ }
198
+ }
199
+ }
200
+ return null;
201
+ }
202
+ /**
203
+ * A usage-limit hit in a bare string, or null.
204
+ *
205
+ * The degraded path, and it exists so this is useful TODAY. `runTask` in agent.ts flattens the
206
+ * SDK stream into `AgentEvent`s and drops `rate_limit_event` entirely, so without a change there
207
+ * the only thing a consumer can see is the `done` event's `result` text. That is enough to
208
+ * rotate on; it just cannot say which window tripped, so the ledger applies a default.
209
+ */
210
+ export function limitFromText(text) {
211
+ if (!isUsageLimitText(text))
212
+ return null;
213
+ return { window: 'unknown', source: 'result_text', why: firstLine(String(text)) };
214
+ }
215
+ /** How long each window lasts, for when the SDK did not say when it resets. */
216
+ const WINDOW_MS = {
217
+ five_hour: 5 * 60 * 60 * 1000,
218
+ seven_day: 7 * 24 * 60 * 60 * 1000,
219
+ seven_day_opus: 7 * 24 * 60 * 60 * 1000,
220
+ seven_day_sonnet: 7 * 24 * 60 * 60 * 1000,
221
+ seven_day_overage_included: 7 * 24 * 60 * 60 * 1000,
222
+ overage: 5 * 60 * 60 * 1000,
223
+ // Deliberately the SHORTEST window, not the longest.
224
+ //
225
+ // Guessing long wastes a subscription for a week over what may have been a five-hour cap, and
226
+ // with three subscriptions that is most of the capacity gone on one bad guess. Guessing short
227
+ // costs one wasted run: the agent tries again in five hours, hits the same wall, and the ledger
228
+ // re-marks it — self-correcting, where the other direction is not.
229
+ unknown: 5 * 60 * 60 * 1000,
230
+ };
231
+ /** When a window that tripped now should be assumed to free up, if the SDK did not say. */
232
+ export function defaultResetFor(window, now = Date.now()) {
233
+ return now + WINDOW_MS[window];
234
+ }
235
+ export function describeWindow(window) {
236
+ switch (window) {
237
+ case 'five_hour': return '5-hour';
238
+ case 'seven_day': return 'weekly';
239
+ case 'seven_day_opus': return 'weekly Opus';
240
+ case 'seven_day_sonnet': return 'weekly Sonnet';
241
+ case 'seven_day_overage_included': return 'weekly (overage included)';
242
+ case 'overage': return 'overage';
243
+ default: return 'usage';
244
+ }
245
+ }
246
+ /*
247
+ * On `why`: it is built only from the SDK's own limit text and from window names, never from the
248
+ * environment or the credential. It goes onto a card and into an audit row, both of which people
249
+ * read, so it must stay a description of a LIMIT and never a description of a TOKEN.
250
+ */
251
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"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"}