claude-usage-limits 1.40.2 → 1.40.4
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +29 -16
- package/package.json +1 -1
- package/skills/usage-limits/SKILL.md +5 -4
- package/skills/usage-limits/scripts/brief.js +79 -25
- package/skills/usage-limits/scripts/defer.js +38 -6
- package/skills/usage-limits/scripts/lowpri.js +108 -21
- package/skills/usage-limits/scripts/mode.js +38 -5
- package/skills/usage-limits/scripts/relay.js +20 -1
- package/skills/usage-limits/scripts/wake.js +11 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
3
|
"displayName": "Usage Limits",
|
|
4
|
-
"version": "1.40.
|
|
4
|
+
"version": "1.40.4",
|
|
5
5
|
"description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Ridelink",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usage-limits",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.4",
|
|
4
4
|
"description": "Reports how much of your Codex usage limit is left as turns of work rather than a percentage, prices a job before you start it, and counts the other agents sharing the same budget.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ridelink",
|
package/README.md
CHANGED
|
@@ -393,20 +393,20 @@ relayed run either.
|
|
|
393
393
|
At the wall with weekly headroom, the line looks like this:
|
|
394
394
|
|
|
395
395
|
```
|
|
396
|
-
If the wall offers it, /low-priority
|
|
397
|
-
at lower priority instead of stopping: it spends the weekly limit, which
|
|
398
|
-
45% and so has room, and replies may pause while it waits for spare
|
|
399
|
-
the wait and its ceiling are set by the server per request. It is a
|
|
400
|
-
|
|
401
|
-
|
|
396
|
+
If the wall offers it, /low-priority can carry this session past the 5-hour
|
|
397
|
+
limit at lower priority instead of stopping: it spends the weekly limit, which
|
|
398
|
+
is at 45% and so has room, and replies may pause while it waits for spare
|
|
399
|
+
capacity - the wait and its ceiling are set by the server per request. It is a
|
|
400
|
+
toggle only the user can type, and type again to stop: you cannot run a slash
|
|
401
|
+
command, and nothing here can switch it on.
|
|
402
402
|
```
|
|
403
403
|
|
|
404
404
|
**The recommendation has a number behind it.** It is offered only while the
|
|
405
405
|
weekly is at or below **80 per cent** and the 5-hour window is the binding wall.
|
|
406
406
|
Above that the brief actively says not to, and cites the figure: low-priority
|
|
407
407
|
spends the weekly *and* draws on a weekly allowance whose size is exposed to no
|
|
408
|
-
hook and no file, and
|
|
409
|
-
couple of hours. No wait time is ever printed, because the retry and the ceiling
|
|
408
|
+
hook and no file, and one user has reported it burning through almost a week of
|
|
409
|
+
usage in a couple of hours (anthropics/claude-code#92544). No wait time is ever printed, because the retry and the ceiling
|
|
410
410
|
come from `lowPriorityRetryAfterSeconds` and `lowPriorityMaxWaitSeconds` on each
|
|
411
411
|
response — any fixed "20 seconds, 20 minutes" would be invented.
|
|
412
412
|
|
|
@@ -418,6 +418,13 @@ would fire into a session that never stopped. A weekly wall still arms. The
|
|
|
418
418
|
acknowledgement lapses by itself when the 5-hour window it was made against
|
|
419
419
|
resets.
|
|
420
420
|
|
|
421
|
+
A deferral is different from a wake, and is treated differently. `defer` is a
|
|
422
|
+
time **you** named, so it is always scheduled — but if you defer to `reset` while
|
|
423
|
+
low-priority is acknowledged, the confirmation says that this session carries
|
|
424
|
+
past the 5-hour reset and waiting for it buys nothing. The mid-turn pulse follows
|
|
425
|
+
the brief onto the weekly too, out of the same function, so the two can never
|
|
426
|
+
report different windows.
|
|
427
|
+
|
|
421
428
|
**The graceful wrap-up note.** Claude Code can inject its own "finish up"
|
|
422
429
|
instruction at the wall. The mechanism is real, and the gate is
|
|
423
430
|
`tengu_lantern_wick_mode` — the bundle's own normalizer keeps only `"wrap-up"`
|
|
@@ -435,13 +442,19 @@ wake. So when a resume relay is armed and this is on, the brief says so: the
|
|
|
435
442
|
wake is the route for a session that will be **closed** at the reset, and both
|
|
436
443
|
firing for the same reset would start the work twice and spend the weekly twice.
|
|
437
444
|
|
|
438
|
-
**`/limit-reset
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
+
**`/limit-reset`** is one command behind two different server flags, and the
|
|
446
|
+
brief words each in its own copy's terms. `tengu_nifty_lemur` is the
|
|
447
|
+
once-a-week reset of the 5-hour session limit ("uses weekly limit · 1/week",
|
|
448
|
+
"your weekly limit still applies"); `tengu_cedar_ember` is a counted grant with
|
|
449
|
+
a use-by date that "refills your limits". The account this was built on carries
|
|
450
|
+
`tengu_nifty_lemur` enabled and no `tengu_cedar_ember`. When the budget is tight
|
|
451
|
+
and the 5-hour window is the wall, the brief names the weekly reset, says the
|
|
452
|
+
work it unlocks still counts toward the weekly, and says only the user can type
|
|
453
|
+
it; at a weekly wall it says nothing, because resetting the 5-hour limit cannot
|
|
454
|
+
help there. It never reports whether this week's reset is used or how many
|
|
455
|
+
grants are left — both come from a live endpoint and appear in no file a hook
|
|
456
|
+
can read. A flag counts only when it is a plain object with `enabled: true`,
|
|
457
|
+
the way the CLI reads it.
|
|
445
458
|
|
|
446
459
|
**Grace is not weekly spend.** The allowance the wall gives you is metered in
|
|
447
460
|
its own per-window meters (`anthropic-ratelimit-unified-grace-5h-utilization`
|
|
@@ -1295,7 +1308,7 @@ test/ node --test, no dependencies
|
|
|
1295
1308
|
node --test
|
|
1296
1309
|
```
|
|
1297
1310
|
|
|
1298
|
-
|
|
1311
|
+
901 tests over the pricing, the window arithmetic, plan and credit detection,
|
|
1299
1312
|
the status line, the before-prompt line, the mid-turn pulse, the after-reply tally and the session history, job forecasting,
|
|
1300
1313
|
per-project attribution, the Codex reader and its installer, the CLI,
|
|
1301
1314
|
packaging, the settings save/restore, and what Claude Code's own wall-time
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-usage-limits",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.4",
|
|
4
4
|
"description": "Puts your remaining Claude Code usage limit into Claude's context before every prompt, so it opens with what fits in the budget instead of starting work that gets cut off. Reports headroom as turns rather than percentages, prices a job before you start it, and detects your plan tier.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -625,10 +625,11 @@ longer stops at. **Record it only when they said so.** Whether it is running is
|
|
|
625
625
|
in the CLI's process memory and readable nowhere, so nothing here may infer it,
|
|
626
626
|
and `mode --low-priority off` is how it is taken back.
|
|
627
627
|
|
|
628
|
-
The same applies to `/limit-reset`, the
|
|
629
|
-
line names it only
|
|
630
|
-
|
|
631
|
-
|
|
628
|
+
The same applies to `/limit-reset`, the manual reset: only the user can type
|
|
629
|
+
it. The line names it only when the account's flags say it is set up. The
|
|
630
|
+
once-a-week kind resets the 5-hour limit and its work still counts toward the
|
|
631
|
+
weekly, so it is named only at a 5-hour wall; whether this week's is already
|
|
632
|
+
used, and how many of the counted kind are left, is not knowable from here.
|
|
632
633
|
|
|
633
634
|
And if the budget line says Claude Code is injecting its own wrap-up note at this
|
|
634
635
|
wall, follow that note: it is more specific than anything here, and two
|
|
@@ -1028,53 +1028,99 @@ function briefText(input) {
|
|
|
1028
1028
|
// style drops only what reads the same every turn. This does not.
|
|
1029
1029
|
const lp = parts.lowPriority || null;
|
|
1030
1030
|
let lowPrioritySentence = null;
|
|
1031
|
-
|
|
1031
|
+
// The acknowledged sentence describes the SWAP, so it may only be said when
|
|
1032
|
+
// the swap really happened. wallFeatures refuses to swap unless there is a
|
|
1033
|
+
// weekly window with a live percentage and a known reset, and this branch
|
|
1034
|
+
// used to fire on the acknowledgement alone - so on an account with no
|
|
1035
|
+
// readable weekly the brief said "binding window is 5-hour 99% used" in one
|
|
1036
|
+
// clause and "the figures above are the weekly" in the next. A line that
|
|
1037
|
+
// contradicts the numbers printed beside it is worse than no line: it tells
|
|
1038
|
+
// the model to ignore a wall that is still there.
|
|
1039
|
+
if (lp && lp.state === 'acknowledged' && lp.weeklyBinding) {
|
|
1032
1040
|
lowPrioritySentence = (
|
|
1033
|
-
'
|
|
1041
|
+
'The user has said /low-priority is on, so the brake is the weekly window and not the 5-hour one: ' +
|
|
1034
1042
|
(Number.isFinite(lp.fiveHourPercent)
|
|
1035
1043
|
? 'the 5-hour limit is at ' + lp.fiveHourPercent + '% and no longer stops this session, '
|
|
1036
1044
|
: 'the 5-hour limit no longer stops this session, ') +
|
|
1037
1045
|
'the figures above are the weekly, and replies may pause while lower priority waits for spare ' +
|
|
1038
|
-
'capacity. Whether it is still on cannot be read from here, so if it has ended
|
|
1039
|
-
'usage-mode --low-priority off and the 5-hour wall counts again. It also draws on a separate ' +
|
|
1046
|
+
'capacity. Whether it is still on cannot be read from here, so if the user says it has ended, ' +
|
|
1047
|
+
'record that with usage-mode --low-priority off and the 5-hour wall counts again. It also draws on a separate ' +
|
|
1040
1048
|
'weekly lower-priority allowance that is exposed to no hook and no file, so nothing here can ' +
|
|
1041
1049
|
'track how much of that is left.'
|
|
1042
1050
|
);
|
|
1051
|
+
} else if (lp && lp.state === 'acknowledged') {
|
|
1052
|
+
// Acknowledged, but there is no weekly reading to brake on, so the figures
|
|
1053
|
+
// above are still the window they say they are and the wall is still real.
|
|
1054
|
+
lowPrioritySentence = (
|
|
1055
|
+
'The user has said /low-priority is on, which spends the weekly limit instead of stopping at the ' +
|
|
1056
|
+
'5-hour one - but there is no usable weekly reading here to brake on, so the figures above are ' +
|
|
1057
|
+
'the window named beside them and that window is still the one to plan against. /usage (the ' +
|
|
1058
|
+
'user types it) refreshes the account snapshot; until it has a weekly, treat the limit above as real.'
|
|
1059
|
+
);
|
|
1043
1060
|
} else if (lp && lp.advise && lp.advise.kind === 'offer') {
|
|
1044
1061
|
lowPrioritySentence = (
|
|
1045
|
-
'If the wall offers it, /low-priority
|
|
1062
|
+
'If the wall offers it, /low-priority can carry this session past the 5-hour limit at lower ' +
|
|
1046
1063
|
'priority instead of stopping: it spends the weekly limit, which is at ' +
|
|
1047
1064
|
lp.advise.weeklyPercent + '% and so has room, and replies may pause while it waits for spare ' +
|
|
1048
|
-
'capacity - the wait and its ceiling are set by the server per request. It is a toggle
|
|
1049
|
-
'
|
|
1050
|
-
'in one line that it is there; if it is taken,
|
|
1051
|
-
'this line brake on the weekly instead.'
|
|
1065
|
+
'capacity - the wait and its ceiling are set by the server per request. It is a toggle only ' +
|
|
1066
|
+
'the user can type, and type again to stop: you cannot run a slash command, and nothing here ' +
|
|
1067
|
+
'can switch it on. Tell the user in one line that it is there; if the user says it is taken, ' +
|
|
1068
|
+
'usage-mode --low-priority on is what makes this line brake on the weekly instead.'
|
|
1052
1069
|
);
|
|
1053
1070
|
} else if (lp && lp.advise && lp.advise.kind === 'hold') {
|
|
1054
1071
|
lowPrioritySentence = (
|
|
1055
1072
|
'The wall may offer /low-priority here, and it is not worth taking: it spends the weekly limit, ' +
|
|
1056
1073
|
'which is already at ' + lp.advise.weeklyPercent + '% - past the ' + lp.advise.threshold +
|
|
1057
1074
|
' per cent this plugin will recommend it at - and it draws on a weekly lower-priority ' +
|
|
1058
|
-
'allowance as well, which has
|
|
1059
|
-
'
|
|
1075
|
+
'allowance as well, which one user has reported burning through almost a week of usage in a ' +
|
|
1076
|
+
'couple of hours. The choice is the user\'s, and only the user can type it: say in one line ' +
|
|
1077
|
+
'that this plugin advises against it and why; waiting out the 5-hour reset is the cheaper move.'
|
|
1060
1078
|
);
|
|
1061
1079
|
}
|
|
1062
1080
|
if (lowPrioritySentence) sentences.push(lowPrioritySentence);
|
|
1063
|
-
// A manual
|
|
1081
|
+
// A manual reset behind /limit-reset, when this account holds one.
|
|
1082
|
+
//
|
|
1083
|
+
// Two server flags sit behind the one command and they are different offers,
|
|
1084
|
+
// so each is worded in its own copy's terms and nothing is borrowed across
|
|
1085
|
+
// (lowpri.sessionReset has the bundle strings):
|
|
1086
|
+
//
|
|
1087
|
+
// 'weekly' (tengu_nifty_lemur, which this account has): resets the 5-hour
|
|
1088
|
+
// session limit, once a week, and the work still counts toward the weekly.
|
|
1089
|
+
// So it only helps when the 5-hour window is the wall - said at a weekly
|
|
1090
|
+
// wall it would be advice that cannot work - and the first build, reading
|
|
1091
|
+
// only the other flag, never said it at all.
|
|
1064
1092
|
//
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
|
|
1093
|
+
// 'grant' (tengu_cedar_ember): a counted reset with a use-by date that
|
|
1094
|
+
// "refills your limits". Its copy does not say it spends the weekly, and it
|
|
1095
|
+
// has an early-use path, so neither "once a week" nor "only at a limit" is
|
|
1096
|
+
// claimed for it.
|
|
1097
|
+
//
|
|
1098
|
+
// Neither is counted: whether this week's reset is already used, and how
|
|
1099
|
+
// many grants are left, are served by the API and appear in no file. And the
|
|
1100
|
+
// CLI refuses a reset while lower priority runs ("Resets can't be used while
|
|
1101
|
+
// you continue at lower priority"), which is why the weekly variant stays
|
|
1102
|
+
// silent once the brief has swapped to the weekly, and the grant says so.
|
|
1103
|
+
const resetVariant = lp && lp.resetGrant ? lp.resetVariant || 'grant' : null;
|
|
1104
|
+
const resetPressure = parts.pressure === 'tight' || parts.pressure === 'gone';
|
|
1105
|
+
const fiveHourWall = Boolean(parts.binding && parts.binding.key === 'five_hour');
|
|
1106
|
+
// Acknowledged is checked on its own as well as through the binding: with no
|
|
1107
|
+
// weekly reading the brief cannot swap, the 5-hour window stays binding, and
|
|
1108
|
+
// the reset would be offered to a session the CLI will not reset.
|
|
1109
|
+
if (resetVariant === 'weekly' && resetPressure && fiveHourWall && lp.state !== 'acknowledged') {
|
|
1110
|
+
sentences.push(
|
|
1111
|
+
'This account is set up for a manual session reset (/limit-reset): once a week it resets the ' +
|
|
1112
|
+
'5-hour limit, and the work it unlocks still counts toward the weekly, so it moves the 5-hour ' +
|
|
1113
|
+
'wall rather than adding budget. The server decides whether it can be used right now, and ' +
|
|
1114
|
+
"whether this week's is already used is not readable from here. Like /low-priority, only the user can type " +
|
|
1115
|
+
'it; you cannot run a slash command.'
|
|
1116
|
+
);
|
|
1117
|
+
} else if (resetVariant === 'grant' && resetPressure) {
|
|
1073
1118
|
sentences.push(
|
|
1074
|
-
'
|
|
1075
|
-
'
|
|
1076
|
-
'
|
|
1077
|
-
'
|
|
1119
|
+
'This account appears to hold a limit reset (/limit-reset), which the CLI describes as refilling ' +
|
|
1120
|
+
'your limits while the weekly reset day stays put. How many are left and until when is not ' +
|
|
1121
|
+
'readable from here - the CLI asks the server for that.' +
|
|
1122
|
+
(lp.state === 'acknowledged' ? ' The CLI will not use one while lower priority is on.' : '') +
|
|
1123
|
+
' Like /low-priority, only the user can type it; you cannot run a slash command.'
|
|
1078
1124
|
);
|
|
1079
1125
|
}
|
|
1080
1126
|
if (parts.session) {
|
|
@@ -1524,9 +1570,17 @@ function wallFeatures(now, binding, windows, hostName) {
|
|
|
1524
1570
|
if (weekly && Number.isFinite(weekly.percentUsed) && !weekly.stale && Number.isFinite(weekly.resetsAt)) {
|
|
1525
1571
|
out.binding = weekly;
|
|
1526
1572
|
out.swapped = true;
|
|
1527
|
-
out.lowPriority = Object.assign({}, info, { weeklyBinding: true });
|
|
1528
1573
|
}
|
|
1529
1574
|
}
|
|
1575
|
+
// Whether the weekly really is the window the figures describe, which is
|
|
1576
|
+
// what the acknowledged sentence claims. True when the swap just happened
|
|
1577
|
+
// AND when the weekly was already binding on its own - the 5-hour window
|
|
1578
|
+
// reading low is the ordinary case for a session that has been running at
|
|
1579
|
+
// lower priority for a while. False when there was no weekly to brake on,
|
|
1580
|
+
// and then the sentence has to say so instead of claiming otherwise.
|
|
1581
|
+
out.lowPriority = Object.assign({}, info, {
|
|
1582
|
+
weeklyBinding: Boolean(out.binding && out.binding.key === 'seven_day'),
|
|
1583
|
+
});
|
|
1530
1584
|
} catch (err) {
|
|
1531
1585
|
// Nothing about these features is worth a failed prompt.
|
|
1532
1586
|
}
|
|
@@ -140,7 +140,10 @@ function confirmation(parts) {
|
|
|
140
140
|
const tail = [];
|
|
141
141
|
tail.push('Nothing has been started');
|
|
142
142
|
if (parts.items) tail.push(parts.items + ' saved');
|
|
143
|
-
|
|
143
|
+
// Every note, not only the first: two can apply at once.
|
|
144
|
+
for (const note of parts.notes && parts.notes.length ? parts.notes : [parts.resetNote]) {
|
|
145
|
+
if (note) tail.push(note);
|
|
146
|
+
}
|
|
144
147
|
return line + tail.join('; ') + '. Run "defer cancel" to call it off.';
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -154,7 +157,7 @@ function bindingReset(now) {
|
|
|
154
157
|
usage.setHost(host.detect(process.argv.slice(2), process.env));
|
|
155
158
|
const collected = usage.collect(now);
|
|
156
159
|
const utilization = collected && collected.utilization;
|
|
157
|
-
if (!utilization) return { resetsAt: null, percent: null };
|
|
160
|
+
if (!utilization) return { resetsAt: null, percent: null, windowKey: null };
|
|
158
161
|
let worst = null;
|
|
159
162
|
for (const key of Object.keys(utilization)) {
|
|
160
163
|
const window = utilization[key];
|
|
@@ -163,12 +166,24 @@ function bindingReset(now) {
|
|
|
163
166
|
if (!Number.isFinite(percent)) continue;
|
|
164
167
|
const resets = Date.parse(window.resets_at);
|
|
165
168
|
if (!worst || percent > worst.percent) {
|
|
166
|
-
worst = { percent, resetsAt: Number.isFinite(resets) ? resets : null };
|
|
169
|
+
worst = { percent, resetsAt: Number.isFinite(resets) ? resets : null, windowKey: key };
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
return worst || { resetsAt: null, percent: null };
|
|
170
173
|
} catch (err) {
|
|
171
|
-
return { resetsAt: null, percent: null };
|
|
174
|
+
return { resetsAt: null, percent: null, windowKey: null };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Claude Code only, like every other reader of this fact: /low-priority is its
|
|
179
|
+
// slash command against its account, and Codex files a five-hour window under the
|
|
180
|
+
// same key.
|
|
181
|
+
function lowPriorityAcknowledged(now) {
|
|
182
|
+
try {
|
|
183
|
+
if (host.detect(process.argv.slice(2), process.env) !== host.CLAUDE) return false;
|
|
184
|
+
return Boolean(require('./lowpri.js').readAck(now));
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return false;
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
189
|
|
|
@@ -198,13 +213,29 @@ function plan(options) {
|
|
|
198
213
|
notes.push('note: that is before the window resets at ' + formatClock(binding.resetsAt));
|
|
199
214
|
}
|
|
200
215
|
}
|
|
216
|
+
// A deferral is a time the user named, so it is honoured whatever else is
|
|
217
|
+
// true - unlike an automatic relay wake, which the acknowledgement refuses.
|
|
218
|
+
// But waiting for a 5-hour reset this session carries straight past is
|
|
219
|
+
// waiting for nothing, and that is worth one line before the task is
|
|
220
|
+
// registered rather than after it fires.
|
|
221
|
+
if (options.lowPriorityAck && binding.windowKey === 'five_hour') {
|
|
222
|
+
notes.push(
|
|
223
|
+
'note: you have said /low-priority is on, so this session carries past the 5-hour reset ' +
|
|
224
|
+
'and waiting for it buys nothing - the weekly is the window that still stops work'
|
|
225
|
+
);
|
|
226
|
+
}
|
|
201
227
|
return {
|
|
202
228
|
ok: true,
|
|
203
229
|
at: when.at,
|
|
204
230
|
label: when.label,
|
|
205
231
|
clock: formatClock(when.at),
|
|
206
232
|
in: when.at - now,
|
|
233
|
+
// resetNote is the first of them, kept because the confirmation and the
|
|
234
|
+
// tests have always read that field. `notes` is all of them, because there
|
|
235
|
+
// can now be two and dropping the second would have hidden the one fact
|
|
236
|
+
// worth printing: that waiting for this reset buys nothing.
|
|
207
237
|
resetNote: notes[0] || null,
|
|
238
|
+
notes,
|
|
208
239
|
};
|
|
209
240
|
}
|
|
210
241
|
|
|
@@ -256,7 +287,7 @@ function main(argv, now) {
|
|
|
256
287
|
}
|
|
257
288
|
|
|
258
289
|
const binding = bindingReset(at);
|
|
259
|
-
const decided = plan({ now: at, when: first, binding });
|
|
290
|
+
const decided = plan({ now: at, when: first, binding, lowPriorityAck: lowPriorityAcknowledged(at) });
|
|
260
291
|
if (!decided.ok) return decided.error;
|
|
261
292
|
|
|
262
293
|
const id = sessionId(args, process.env);
|
|
@@ -304,6 +335,7 @@ function main(argv, now) {
|
|
|
304
335
|
in: decided.in,
|
|
305
336
|
items,
|
|
306
337
|
resetNote: decided.resetNote,
|
|
338
|
+
notes: decided.notes,
|
|
307
339
|
});
|
|
308
340
|
}
|
|
309
341
|
|
|
@@ -317,4 +349,4 @@ if (require.main === module) {
|
|
|
317
349
|
}
|
|
318
350
|
}
|
|
319
351
|
|
|
320
|
-
module.exports = { parseWhen, formatClock, formatSpan, confirmation, plan, status, cancel, main, MINUTE, HOUR };
|
|
352
|
+
module.exports = { lowPriorityAcknowledged, parseWhen, formatClock, formatSpan, confirmation, plan, status, cancel, main, MINUTE, HOUR };
|
|
@@ -39,13 +39,14 @@
|
|
|
39
39
|
// 30-minute freshness cutoff on cached window readings, nothing to do with
|
|
40
40
|
// this.)
|
|
41
41
|
//
|
|
42
|
-
// /limit-reset - a
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
42
|
+
// /limit-reset - a manual reset, behind two different server flags (see
|
|
43
|
+
// sessionReset below). tengu_nifty_lemur is the once-a-week reset of the
|
|
44
|
+
// 5-hour session limit whose work still counts toward the weekly, and THIS
|
|
45
|
+
// ACCOUNT HAS IT (enabled:true). tengu_cedar_ember is a counted grant with a
|
|
46
|
+
// use-by date, absent here, with cachedUsageUtilization.utilization.cedar_ember
|
|
47
|
+
// null. Nothing is spent by the plugin - only the user can type the command -
|
|
48
|
+
// and whether this week's reset is used, or how many grants are left, is
|
|
49
|
+
// served by the API and never a file, so neither is ever claimed.
|
|
49
50
|
//
|
|
50
51
|
// The graceful wrap-up note - the CLI injecting "finish up" at the wall. The
|
|
51
52
|
// mechanism is real and the treatment TEXT is provisioned on this machine
|
|
@@ -81,6 +82,7 @@ const atomic = require('./atomic.js');
|
|
|
81
82
|
|
|
82
83
|
const FLAG = 'tengu_toasty_breeze';
|
|
83
84
|
const RESET_FLAG = 'tengu_cedar_ember';
|
|
85
|
+
const SESSION_RESET_FLAG = 'tengu_nifty_lemur';
|
|
84
86
|
const WRAPUP_MODE_FLAG = 'tengu_lantern_wick_mode';
|
|
85
87
|
const WRAPUP_TEXT_FLAG = 'tengu_lantern_wick_text';
|
|
86
88
|
const NEAR_WALL_FLAG = 'tengu_vellum_anchor';
|
|
@@ -95,7 +97,7 @@ const MAX_COOLOFF_MINUTES = 1440;
|
|
|
95
97
|
// The recommendation rule, in one number so it can be argued with.
|
|
96
98
|
//
|
|
97
99
|
// /low-priority spends the weekly and draws on a weekly allowance whose size is
|
|
98
|
-
// exposed nowhere a hook can read, and
|
|
100
|
+
// exposed nowhere a hook can read, and one user has reported it burning "almost
|
|
99
101
|
// a week of usage in a couple hours"
|
|
100
102
|
// (github.com/anthropics/claude-code/issues/92544). So it is worth naming only
|
|
101
103
|
// while the weekly still has real room. At or below this it is offered; above
|
|
@@ -106,6 +108,22 @@ const WEEKLY_HEADROOM_MAX = 80;
|
|
|
106
108
|
// offers the toggle at all.
|
|
107
109
|
const WALL_PERCENT = 90;
|
|
108
110
|
|
|
111
|
+
// How long an acknowledgement with no readable reset time is allowed to live.
|
|
112
|
+
//
|
|
113
|
+
// The record is meant to lapse at the reset of the window it belongs to. When
|
|
114
|
+
// that reset could not be read - a session with no account snapshot, or a
|
|
115
|
+
// snapshot with no five_hour entry - the record used to be written with
|
|
116
|
+
// resetsAt null, and readAck only lapsed a FINITE resetsAt. So it never lapsed:
|
|
117
|
+
// a record written once went on redirecting the headroom maths at the weekly
|
|
118
|
+
// for ever. Probed on 2026-09-26 with an acknowledgement three days old and the
|
|
119
|
+
// 5-hour window reading 0 per cent, and the brief still said the 5-hour limit
|
|
120
|
+
// "no longer stops this session".
|
|
121
|
+
//
|
|
122
|
+
// The window the toggle is offered at is five hours long, so an
|
|
123
|
+
// acknowledgement made against it cannot honestly outlive five hours from when
|
|
124
|
+
// it was made. That is the ceiling, not a guess at when it really ended.
|
|
125
|
+
const ACK_MAX_MS = 5 * 60 * 60 * 1000;
|
|
126
|
+
|
|
109
127
|
function configDir() {
|
|
110
128
|
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
111
129
|
}
|
|
@@ -203,16 +221,53 @@ function offer(account) {
|
|
|
203
221
|
};
|
|
204
222
|
}
|
|
205
223
|
|
|
206
|
-
//
|
|
207
|
-
//
|
|
224
|
+
// A flag object the way the CLI reads one: a plain object whose `enabled` is
|
|
225
|
+
// exactly true. The bundle's own readers are
|
|
226
|
+
// function qae(){let e=x("tengu_nifty_lemur",{});return typeof e==="object"&&e!==null&&!Array.isArray(e)?e:{}}
|
|
227
|
+
// function pme(){return qae().enabled===!0}
|
|
228
|
+
// and the same shape for tengu_cedar_ember ($Z()). Truthiness is not enough:
|
|
229
|
+
// `{ enabled: false }` is a truthy object, and the first build counted it as a
|
|
230
|
+
// reset on offer.
|
|
231
|
+
function flagEnabled(gb, key) {
|
|
232
|
+
if (!gb || !Object.prototype.hasOwnProperty.call(gb, key)) return false;
|
|
233
|
+
const value = gb[key];
|
|
234
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value) && value.enabled === true);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The manual reset behind /limit-reset. Detected, never spent.
|
|
238
|
+
//
|
|
239
|
+
// There are TWO server flags behind the one command, and they describe
|
|
240
|
+
// different things, so the brief must not word one in the other's terms:
|
|
241
|
+
//
|
|
242
|
+
// tengu_nifty_lemur - "Reset your session limit now and keep working; once a
|
|
243
|
+
// week, still counts toward your weekly limit" (the command's description
|
|
244
|
+
// when this is the variant). Its notice: "/limit-reset to reset your session
|
|
245
|
+
// limit now · uses weekly limit · 1/week". THIS ACCOUNT HAS IT: enabled:true,
|
|
246
|
+
// version 1, read from ~/.claude.json on 2026-09-25. The first build looked
|
|
247
|
+
// only at tengu_cedar_ember, called the account grant-less, and so never said
|
|
248
|
+
// a word about a reset the account actually holds.
|
|
249
|
+
//
|
|
250
|
+
// tengu_cedar_ember - "Use an available limit reset and keep working". A
|
|
251
|
+
// counted grant: "Refills your {limits} now · your weekly reset day stays
|
|
252
|
+
// {week}", "{resets} left · use by {deadline}", and an early-use path ("You
|
|
253
|
+
// haven't reached a limit yet - use your reset anyway?"). Not once a week,
|
|
254
|
+
// not only at a limit, and whether it spends the weekly is not in its copy.
|
|
255
|
+
//
|
|
256
|
+
// The CLI prefers cedar_ember when both are on (its description reads
|
|
257
|
+
// `$Z()?"Use an available limit reset...":"Reset your session limit now..."`),
|
|
258
|
+
// so this does too. Whether this week's reset is already spent, and how many
|
|
259
|
+
// grants are left, come from the server and appear in no file.
|
|
208
260
|
function sessionReset(account) {
|
|
209
261
|
const gb = features(account);
|
|
210
262
|
const util = utilization(account);
|
|
211
|
-
const
|
|
263
|
+
const grantFlag = flagEnabled(gb, RESET_FLAG);
|
|
264
|
+
const weeklyFlag = flagEnabled(gb, SESSION_RESET_FLAG);
|
|
212
265
|
const grant = util && util.cedar_ember ? util.cedar_ember : null;
|
|
266
|
+
const variant = grantFlag || grant ? 'grant' : weeklyFlag ? 'weekly' : null;
|
|
213
267
|
return {
|
|
214
268
|
known: Boolean(gb || util),
|
|
215
|
-
present:
|
|
269
|
+
present: variant !== null,
|
|
270
|
+
variant,
|
|
216
271
|
// resets_left is served from /api/organizations/<uuid>/reset_rate_limits,
|
|
217
272
|
// not from any file a hook can read, so it stays unreported.
|
|
218
273
|
resetsLeft: null,
|
|
@@ -276,9 +331,13 @@ function autoContinue() {
|
|
|
276
331
|
// session where the 5-hour wall is real again.
|
|
277
332
|
// ---------------------------------------------------------------------------
|
|
278
333
|
|
|
334
|
+
// An array is an object too, and JSON.stringify drops a named property set on
|
|
335
|
+
// one: a state file holding `[]` made acknowledge() write `[]` straight back,
|
|
336
|
+
// report saved:true, and usage-mode print "Recorded" for a statement that the
|
|
337
|
+
// next read could not find. Only a plain object is a state.
|
|
279
338
|
function readState() {
|
|
280
339
|
const parsed = readJson(stateFile());
|
|
281
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
340
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
282
341
|
}
|
|
283
342
|
|
|
284
343
|
function writeState(state) {
|
|
@@ -290,14 +349,20 @@ function writeState(state) {
|
|
|
290
349
|
}
|
|
291
350
|
}
|
|
292
351
|
|
|
352
|
+
// `saved` is reported, never assumed. A state path that cannot be written -
|
|
353
|
+
// a directory sitting where the file goes, a read-only home - used to return
|
|
354
|
+
// the record anyway, so usage-mode printed "Recorded: you have switched
|
|
355
|
+
// /low-priority on" and then the very next brief behaved as though nothing had
|
|
356
|
+
// been said. Telling somebody their statement was recorded when it was not is
|
|
357
|
+
// the one thing this module must not do.
|
|
293
358
|
function acknowledge(input) {
|
|
294
359
|
const options = input || {};
|
|
295
360
|
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
296
361
|
if (!options.on) {
|
|
297
362
|
const state = readState();
|
|
298
363
|
delete state.ack;
|
|
299
|
-
writeState(state);
|
|
300
|
-
return { on: false, at: now };
|
|
364
|
+
const saved = writeState(state);
|
|
365
|
+
return { on: false, at: now, saved, file: stateFile() };
|
|
301
366
|
}
|
|
302
367
|
const record = {
|
|
303
368
|
on: true,
|
|
@@ -307,18 +372,35 @@ function acknowledge(input) {
|
|
|
307
372
|
};
|
|
308
373
|
const state = readState();
|
|
309
374
|
state.ack = record;
|
|
310
|
-
writeState(state);
|
|
311
|
-
return record;
|
|
375
|
+
const saved = writeState(state);
|
|
376
|
+
return Object.assign({}, record, { saved, file: stateFile() });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// When a record stops being true: the reset of the window it was stamped
|
|
380
|
+
// against, or - when that could not be read - ACK_MAX_MS after it was made.
|
|
381
|
+
// Both are returned as `expiresAt` so a caller never has to work it out twice
|
|
382
|
+
// and get a different answer.
|
|
383
|
+
function ackExpiry(ack) {
|
|
384
|
+
if (!ack) return null;
|
|
385
|
+
if (Number.isFinite(ack.resetsAt)) return ack.resetsAt;
|
|
386
|
+
if (Number.isFinite(ack.at)) return ack.at + ACK_MAX_MS;
|
|
387
|
+
return null;
|
|
312
388
|
}
|
|
313
389
|
|
|
314
390
|
function readAck(now) {
|
|
315
391
|
const at = Number.isFinite(now) ? now : Date.now();
|
|
316
392
|
const state = readState();
|
|
317
393
|
const ack = state.ack;
|
|
318
|
-
if (!ack || ack.on !== true) return null;
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
394
|
+
if (!ack || typeof ack !== 'object' || ack.on !== true) return null;
|
|
395
|
+
// A record with no usable timestamp is not a record. It printed as
|
|
396
|
+
// "acknowledged ... at Invalid Date" before this, off a hand-edited or
|
|
397
|
+
// half-written file, which is the plugin quoting garbage back as fact.
|
|
398
|
+
if (!Number.isFinite(ack.at)) return null;
|
|
399
|
+
// Past the reset of the window it was recorded against - or past the length
|
|
400
|
+
// of that window, when no reset was readable - the fact is spent.
|
|
401
|
+
const expiresAt = ackExpiry(ack);
|
|
402
|
+
if (Number.isFinite(expiresAt) && at >= expiresAt) return null;
|
|
403
|
+
return Object.assign({}, ack, { expiresAt, expiryKnown: Number.isFinite(ack.resetsAt) });
|
|
322
404
|
}
|
|
323
405
|
|
|
324
406
|
// Exactly three states, and activeKnown is false in every one of them.
|
|
@@ -388,6 +470,7 @@ function forBrief(input) {
|
|
|
388
470
|
weeklyPercent: weekly && Number.isFinite(weekly.percentUsed) ? Math.round(weekly.percentUsed) : null,
|
|
389
471
|
fiveHourPercent: five && Number.isFinite(five.percentUsed) ? Math.round(five.percentUsed) : null,
|
|
390
472
|
resetGrant: sessionReset(account).present,
|
|
473
|
+
resetVariant: sessionReset(account).variant,
|
|
391
474
|
credits: credits(account),
|
|
392
475
|
autoContinue: autoContinue(),
|
|
393
476
|
};
|
|
@@ -396,6 +479,7 @@ function forBrief(input) {
|
|
|
396
479
|
module.exports = {
|
|
397
480
|
FLAG,
|
|
398
481
|
RESET_FLAG,
|
|
482
|
+
SESSION_RESET_FLAG,
|
|
399
483
|
WRAPUP_MODE_FLAG,
|
|
400
484
|
WRAPUP_TEXT_FLAG,
|
|
401
485
|
NEAR_WALL_FLAG,
|
|
@@ -404,12 +488,15 @@ module.exports = {
|
|
|
404
488
|
MAX_COOLOFF_MINUTES,
|
|
405
489
|
WEEKLY_HEADROOM_MAX,
|
|
406
490
|
WALL_PERCENT,
|
|
491
|
+
ACK_MAX_MS,
|
|
492
|
+
ackExpiry,
|
|
407
493
|
configDir,
|
|
408
494
|
stateFile,
|
|
409
495
|
accountFiles,
|
|
410
496
|
snapshot,
|
|
411
497
|
offer,
|
|
412
498
|
sessionReset,
|
|
499
|
+
flagEnabled,
|
|
413
500
|
wrapUp,
|
|
414
501
|
credits,
|
|
415
502
|
autoContinue,
|
|
@@ -1505,7 +1505,12 @@ function main(argv) {
|
|
|
1505
1505
|
const usage = require('./usage.js');
|
|
1506
1506
|
const windows = usage.snapshotWindows(usage.collect(now), now, null) || [];
|
|
1507
1507
|
const five = windows.find((w) => w && w.key === 'five_hour');
|
|
1508
|
-
|
|
1508
|
+
// Only a reset still ahead. A stale snapshot carries the reset of a
|
|
1509
|
+
// window that has already rolled over, and stamping that on the record
|
|
1510
|
+
// printed "Recorded ... lapses at" a time already gone - and the very
|
|
1511
|
+
// next read found it spent, so the statement vanished. The next reset
|
|
1512
|
+
// is unknown then, and the record takes the five-hour ceiling instead.
|
|
1513
|
+
if (five && Number.isFinite(five.resetsAt) && five.resetsAt > now) {
|
|
1509
1514
|
windowKey = 'five_hour';
|
|
1510
1515
|
resetsAt = five.resetsAt;
|
|
1511
1516
|
}
|
|
@@ -1513,7 +1518,18 @@ function main(argv) {
|
|
|
1513
1518
|
// No reading is not a reason to refuse the user's own statement; the
|
|
1514
1519
|
// record simply has no expiry to hang on.
|
|
1515
1520
|
}
|
|
1516
|
-
lowpri.acknowledge({ on: true, windowKey, resetsAt, now });
|
|
1521
|
+
const written = lowpri.acknowledge({ on: true, windowKey, resetsAt, now });
|
|
1522
|
+
// A write that did not happen is never reported as one. With a directory
|
|
1523
|
+
// sitting where the state file goes this said "Recorded" and the next
|
|
1524
|
+
// brief behaved as though nothing had been said.
|
|
1525
|
+
if (!written.saved) {
|
|
1526
|
+
return [
|
|
1527
|
+
'NOT recorded: ' + written.file + ' could not be written, so nothing was saved and the ' +
|
|
1528
|
+
'brief will go on treating the 5-hour window as a real wall.',
|
|
1529
|
+
'',
|
|
1530
|
+
'Check that the path is a writable file and not a directory, then run this again.',
|
|
1531
|
+
].join('\n');
|
|
1532
|
+
}
|
|
1517
1533
|
logChange({ plane: 'mode', key: 'low-priority', from: 'unknown', to: 'on', by: 'user', reason: null }, now);
|
|
1518
1534
|
return [
|
|
1519
1535
|
'Recorded: you have switched /low-priority on.',
|
|
@@ -1530,14 +1546,25 @@ function main(argv) {
|
|
|
1530
1546
|
'',
|
|
1531
1547
|
resetsAt
|
|
1532
1548
|
? 'This lapses on its own at ' + new Date(resetsAt).toLocaleString() + ', when the 5-hour window resets.'
|
|
1533
|
-
: 'No 5-hour reset time was readable, so this
|
|
1549
|
+
: 'No 5-hour reset time was readable, so this lapses at ' +
|
|
1550
|
+
new Date(now + lowpri.ACK_MAX_MS).toLocaleString() + ' instead - five hours, the length of ' +
|
|
1551
|
+
'the window the toggle belongs to, which is the longest it could honestly still be true. ' +
|
|
1552
|
+
'Run /usage and set it again if it is still on then.',
|
|
1534
1553
|
'',
|
|
1535
1554
|
detail,
|
|
1536
1555
|
].join('\n');
|
|
1537
1556
|
}
|
|
1538
1557
|
if (asked === 'off' || asked === 'no' || asked === 'false') {
|
|
1539
1558
|
const had = lowpri.readAck(now);
|
|
1540
|
-
lowpri.acknowledge({ on: false, now });
|
|
1559
|
+
const cleared = lowpri.acknowledge({ on: false, now });
|
|
1560
|
+
// The same rule as "on": a clear that did not reach the file is not
|
|
1561
|
+
// reported as one, because the record it failed to remove still steers
|
|
1562
|
+
// the brief at the weekly.
|
|
1563
|
+
if (had && !cleared.saved) {
|
|
1564
|
+
return 'NOT cleared: ' + cleared.file + ' could not be written, so the acknowledgement is still ' +
|
|
1565
|
+
'there and the brief will go on braking on the weekly until it lapses at ' +
|
|
1566
|
+
new Date(had.expiresAt).toLocaleString() + '. Check that the file is writable, then run this again.';
|
|
1567
|
+
}
|
|
1541
1568
|
logChange({ plane: 'mode', key: 'low-priority', from: had ? 'on' : 'unknown', to: 'off', by: 'user', reason: null }, now);
|
|
1542
1569
|
return had
|
|
1543
1570
|
? 'Cleared: low-priority is off again, so the 5-hour window counts as a wall from the next prompt.'
|
|
@@ -1550,9 +1577,15 @@ function main(argv) {
|
|
|
1550
1577
|
const wrap = lowpri.wrapUp(account);
|
|
1551
1578
|
const auto = lowpri.autoContinue();
|
|
1552
1579
|
return [
|
|
1580
|
+
// readAck drops a record with no usable timestamp, so there is no longer
|
|
1581
|
+
// a path that prints "at Invalid Date" here, and the expiry is always a
|
|
1582
|
+
// real time: the window's reset, or five hours from the statement.
|
|
1553
1583
|
ack
|
|
1554
1584
|
? 'You have acknowledged low-priority ON, at ' + new Date(ack.at).toLocaleString() +
|
|
1555
|
-
(Number.isFinite(ack.
|
|
1585
|
+
(Number.isFinite(ack.expiresAt)
|
|
1586
|
+
? ', lapsing at ' + new Date(ack.expiresAt).toLocaleString() +
|
|
1587
|
+
(ack.expiryKnown ? ' when the 5-hour window resets' : ' (no reset time was readable, so this is five hours from the statement)')
|
|
1588
|
+
: '') + '.'
|
|
1556
1589
|
: 'Low-priority has not been acknowledged, so the plugin treats the 5-hour window as a real wall.',
|
|
1557
1590
|
'',
|
|
1558
1591
|
'It is a toggle you type yourself. The model cannot run a slash command, and /low-priority is ' +
|
|
@@ -1496,11 +1496,30 @@ async function armByHand(rest) {
|
|
|
1496
1496
|
});
|
|
1497
1497
|
if (!result.ok) return 'Could not arm: ' + result.error;
|
|
1498
1498
|
if (text) saveContinuation(sessionId, text);
|
|
1499
|
+
// armable() refuses a 5-hour wake while low-priority is acknowledged, and
|
|
1500
|
+
// this path never went through it: `relay arm --session <id>` booked the wake
|
|
1501
|
+
// anyway and said nothing, which is the double-start the whole gate exists to
|
|
1502
|
+
// prevent. An explicit command is still honoured - it is what somebody typed
|
|
1503
|
+
// - but it says what `defer reset` says, so the person deciding has the fact.
|
|
1504
|
+
let lowPriorityNote = '';
|
|
1505
|
+
if (binding.key === 'five_hour' && usage.currentHost() === host.CLAUDE) {
|
|
1506
|
+
try {
|
|
1507
|
+
if (lowpri.readAck(now)) {
|
|
1508
|
+
lowPriorityNote =
|
|
1509
|
+
' Note: you have said /low-priority is on, so this session carries past the 5-hour reset ' +
|
|
1510
|
+
'on its own and this wake would start the work a second time. Arm against the weekly ' +
|
|
1511
|
+
'instead, or say usage-mode --low-priority off if it has ended; "relay cancel" calls this one off.';
|
|
1512
|
+
}
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
// A missing or unreadable record is not a reason to refuse a command.
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1499
1517
|
return (
|
|
1500
1518
|
'Armed by hand for session ' + sessionId.slice(0, 8) + ': wake at ' +
|
|
1501
1519
|
new Date(result.record.wakeAt).toLocaleString() + ' via ' + result.record.how + '.' +
|
|
1502
1520
|
(result.record.preflight && result.record.preflight.length ? ' Pre-answered: ' + result.record.preflight.join(', ') + '.' : '') +
|
|
1503
|
-
(text ? ' Continuation saved.' : ' No continuation yet - add one with: relay note "<text>"')
|
|
1521
|
+
(text ? ' Continuation saved.' : ' No continuation yet - add one with: relay note "<text>"') +
|
|
1522
|
+
lowPriorityNote
|
|
1504
1523
|
);
|
|
1505
1524
|
}
|
|
1506
1525
|
|
|
@@ -559,8 +559,17 @@ function mine(held, record) {
|
|
|
559
559
|
}
|
|
560
560
|
|
|
561
561
|
async function run(now, argv, overrides) {
|
|
562
|
+
// `reachable` belongs in here with the rest. Left out, the preflight below
|
|
563
|
+
// called the real network on every run of the retry tests, so the whole
|
|
564
|
+
// launch-failure suite passed only on a machine that could reach
|
|
565
|
+
// api.anthropic.com - and went red, in the offline branch, on a laptop with
|
|
566
|
+
// its wifi off or behind a TLS-inspecting proxy. That is the exact condition
|
|
567
|
+
// the relay is built for, so it is the last thing its tests should need.
|
|
562
568
|
const deps = Object.assign(
|
|
563
|
-
{
|
|
569
|
+
{
|
|
570
|
+
windowReopened, deliverClaude, deliverCodex, toast, userIsPresent,
|
|
571
|
+
arm: relay.arm, capabilities: relay.capabilities, reachable: net.reachable,
|
|
572
|
+
},
|
|
564
573
|
overrides || null
|
|
565
574
|
);
|
|
566
575
|
const id = argOf(argv, '--id');
|
|
@@ -618,7 +627,7 @@ async function run(now, argv, overrides) {
|
|
|
618
627
|
// So: ask first, before spending a launch on it, and give being offline its
|
|
619
628
|
// own much longer budget. A machine that cannot reach the API has not failed.
|
|
620
629
|
// It is waiting, and waiting is free.
|
|
621
|
-
const link = await
|
|
630
|
+
const link = await deps.reachable({ timeoutMs: 8000 });
|
|
622
631
|
if (!link.online) {
|
|
623
632
|
const offlineAttempt = (record.offlineAttempt || 0) + 1;
|
|
624
633
|
if (offlineAttempt <= config.offlineAttempts) {
|