claude-usage-limits 1.6.1 → 1.7.0
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 +40 -0
- package/README.md +70 -7
- package/agents/openai.yaml +4 -0
- package/bin/cli.js +16 -3
- package/commands/check.md +7 -2
- package/hooks/hooks.json +12 -1
- package/package.json +5 -3
- package/skills/usage-limits/SKILL.md +113 -16
- package/skills/usage-limits/agents/openai.yaml +4 -0
- package/skills/usage-limits/scripts/brief.js +243 -52
- package/skills/usage-limits/scripts/codex.js +855 -0
- package/skills/usage-limits/scripts/host.js +96 -0
- package/skills/usage-limits/scripts/install-codex-hook.js +448 -0
- package/skills/usage-limits/scripts/pulse.js +227 -0
- package/skills/usage-limits/scripts/usage.js +506 -49
|
@@ -14,30 +14,58 @@ const os = require('os');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
|
|
16
16
|
const usage = require('./usage.js');
|
|
17
|
+
const host = require('./host.js');
|
|
17
18
|
|
|
18
19
|
const SECOND = 1000;
|
|
19
20
|
const DAY = 24 * 60 * 60 * 1000;
|
|
20
21
|
|
|
22
|
+
// There is one threshold that changes behaviour, and it is the wall.
|
|
23
|
+
//
|
|
24
|
+
// Everything below it is reported and nothing below it is discouraged. That is
|
|
25
|
+
// the whole design, and it is a correction: an earlier version escalated from
|
|
26
|
+
// 40 per cent used, or whenever the runway dropped under three quarters of an
|
|
27
|
+
// hour, and so spent its time telling a session with a third of its budget left
|
|
28
|
+
// to stop starting things. Budget left unspent at the reset is not saved, it is
|
|
29
|
+
// destroyed, so winding down early is not caution. It is waste with a
|
|
30
|
+
// respectable name.
|
|
31
|
+
//
|
|
32
|
+
// Above the wall the instruction is not "hurry" either. It is: write the plan
|
|
33
|
+
// for what is left, save the work, and stop.
|
|
21
34
|
const DEFAULTS = {
|
|
22
|
-
//
|
|
23
|
-
near:
|
|
24
|
-
//
|
|
35
|
+
// The wall. Below this, work normally at full quality.
|
|
36
|
+
near: 90,
|
|
37
|
+
// Kept for `aheadOfPace`, which is still reported. It no longer decides
|
|
38
|
+
// anything: spending a week's budget faster than the clock is information,
|
|
39
|
+
// not a reason to slow down.
|
|
25
40
|
floor: 40,
|
|
26
|
-
// Points of budget spent beyond the share of the window that has elapsed.
|
|
27
41
|
ahead: 15,
|
|
28
42
|
// How long the measured part stays good for. Prompts often arrive in
|
|
29
43
|
// bursts, and a transcript scan per prompt would be wasteful.
|
|
30
44
|
cacheSeconds: 60,
|
|
31
|
-
// Few enough turns that the count itself is the
|
|
32
|
-
fewTurns:
|
|
45
|
+
// Few enough turns that the count itself is the wall.
|
|
46
|
+
fewTurns: 10,
|
|
47
|
+
// Minutes of runway left at the current pace, below which there is no longer
|
|
48
|
+
// time to land the work and write the handoff. Not "too little time to start
|
|
49
|
+
// something ambitious" - that judgement belongs to whoever is doing the work,
|
|
50
|
+
// and it needs the number, not an instruction.
|
|
51
|
+
runwayMinutes: 10,
|
|
33
52
|
};
|
|
34
53
|
|
|
54
|
+
// The runway is worth saying long before it is worth acting on, because it is
|
|
55
|
+
// the figure that stops a turn count from flattering. Two hundred turns sounds
|
|
56
|
+
// like plenty and can be twenty minutes when three sessions are spending.
|
|
57
|
+
const RUNWAY_MENTION_MS = 2 * 60 * 60 * 1000;
|
|
58
|
+
|
|
35
59
|
function configDir() {
|
|
36
60
|
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
37
61
|
}
|
|
38
62
|
|
|
63
|
+
// One cache per host. The slots double as the count of open sessions, so mixing
|
|
64
|
+
// two agents' sessions into one file would have each of them reporting the
|
|
65
|
+
// other's windows as competition for a budget they do not share.
|
|
39
66
|
function cacheFile() {
|
|
40
|
-
|
|
67
|
+
const dir = usage.isCodex() ? require('./codex.js').homeDir() : configDir();
|
|
68
|
+
return path.join(dir, 'usage-limits-brief.json');
|
|
41
69
|
}
|
|
42
70
|
|
|
43
71
|
// One slot per session. A single shared slot meant that alternating between
|
|
@@ -75,6 +103,36 @@ function pickCached(all, sessionId, now, ttlMs) {
|
|
|
75
103
|
return now - entry.at < ttlMs ? entry : null;
|
|
76
104
|
}
|
|
77
105
|
|
|
106
|
+
// How many sessions are actually open right now.
|
|
107
|
+
//
|
|
108
|
+
// The count derived from spend is the accurate one, but it is always late: a
|
|
109
|
+
// session only appears in it once it has finished a turn and written the cost
|
|
110
|
+
// to its transcript. Three windows that all submit a prompt at the same moment
|
|
111
|
+
// each see a count of one, which is exactly when knowing about the other two
|
|
112
|
+
// would have mattered most.
|
|
113
|
+
//
|
|
114
|
+
// This cache is the earlier signal. Every session writes its own slot when the
|
|
115
|
+
// hook runs, so a slot touched in the last few minutes is a session that was
|
|
116
|
+
// being used, whether or not its spend has landed yet. It costs nothing: the
|
|
117
|
+
// file has already been read.
|
|
118
|
+
const LIVE_WINDOW_MS = 15 * 60 * 1000;
|
|
119
|
+
|
|
120
|
+
// Counts the other sessions, not this one. This session's own slot may not be
|
|
121
|
+
// written yet on its first prompt, so counting slots directly would report two
|
|
122
|
+
// when three windows are open.
|
|
123
|
+
function liveSessions(all, now, windowMs, exceptId) {
|
|
124
|
+
const within = Number.isFinite(windowMs) ? windowMs : LIVE_WINDOW_MS;
|
|
125
|
+
const mine = exceptId || '_';
|
|
126
|
+
let count = 0;
|
|
127
|
+
for (const key of Object.keys(all || {})) {
|
|
128
|
+
if (key === mine) continue;
|
|
129
|
+
const entry = all[key];
|
|
130
|
+
if (!entry || !Number.isFinite(entry.at)) continue;
|
|
131
|
+
if (now - entry.at <= within) count += 1;
|
|
132
|
+
}
|
|
133
|
+
return count;
|
|
134
|
+
}
|
|
135
|
+
|
|
78
136
|
// Keep the newest few so a machine with many sessions does not grow the file
|
|
79
137
|
// without bound.
|
|
80
138
|
function mergeCache(all, sessionId, entry, keep) {
|
|
@@ -107,6 +165,7 @@ function settings() {
|
|
|
107
165
|
ahead: number(env.USAGE_LIMITS_AHEAD, DEFAULTS.ahead),
|
|
108
166
|
cacheSeconds: number(env.USAGE_LIMITS_CACHE, DEFAULTS.cacheSeconds),
|
|
109
167
|
fewTurns: number(env.USAGE_LIMITS_FEW_TURNS, DEFAULTS.fewTurns),
|
|
168
|
+
runwayMinutes: number(env.USAGE_LIMITS_RUNWAY, DEFAULTS.runwayMinutes),
|
|
110
169
|
};
|
|
111
170
|
}
|
|
112
171
|
|
|
@@ -119,35 +178,105 @@ function aheadOfPace(window, now) {
|
|
|
119
178
|
return window.percentUsed - Math.min(100, Math.max(0, elapsed));
|
|
120
179
|
}
|
|
121
180
|
|
|
181
|
+
// Spending faster than the clock only means something for a window you have to
|
|
182
|
+
// make last. A window that comes back in hours is meant to be spent in a burst:
|
|
183
|
+
// nothing carries over, so holding budget back buys nothing at all, and the
|
|
184
|
+
// only thing an even pace achieves is getting less done for the same money.
|
|
185
|
+
const PACE_MIN_SPAN_MS = 24 * 60 * 60 * 1000;
|
|
186
|
+
const PACE_MIN_ELAPSED = 0.25;
|
|
187
|
+
|
|
188
|
+
function pacingMatters(window, now) {
|
|
189
|
+
if (!window || !window.spanMs || !Number.isFinite(window.windowStart)) return false;
|
|
190
|
+
if (window.spanMs < PACE_MIN_SPAN_MS) return false;
|
|
191
|
+
// Early on, the comparison is dominated by how little of the window has gone
|
|
192
|
+
// rather than by how much has been spent. Twenty minutes into a five hour
|
|
193
|
+
// window every working session is far "ahead of pace", which is exactly how
|
|
194
|
+
// 44 per cent used came to be reported as tight.
|
|
195
|
+
return (now - window.windowStart) / window.spanMs >= PACE_MIN_ELAPSED;
|
|
196
|
+
}
|
|
197
|
+
|
|
122
198
|
// Not whether to speak, which is always, but how hard to lean on it.
|
|
123
199
|
function pressure(window, now, config, turnsLeft) {
|
|
124
200
|
if (!window || window.percentUsed === null || window.stale) return 'unknown';
|
|
125
201
|
if (window.verdict === 'exhausted' || window.percentUsed >= 100) return 'gone';
|
|
126
202
|
if (window.verdict === 'runs-out') return 'tight';
|
|
127
203
|
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
204
|
+
// How long the budget lasts at the pace it is actually being spent at. This
|
|
205
|
+
// is the only figure here that answers "am I about to be cut off", and it was
|
|
206
|
+
// being computed and then ignored.
|
|
207
|
+
//
|
|
208
|
+
// When a reset time is known, a short runway already shows up as the
|
|
209
|
+
// 'runs-out' verdict above. When it is not - and a 5-hour window whose
|
|
210
|
+
// resets_at comes back null is exactly that case - the verdict is only
|
|
211
|
+
// 'burning', which fell through every branch below to 'roomy'. Three sessions
|
|
212
|
+
// were told the budget fitted easily while this number said forty-three
|
|
213
|
+
// minutes; nine minutes later all three were rejected.
|
|
214
|
+
//
|
|
215
|
+
// It is also the right figure when several agents share one budget: the pace
|
|
216
|
+
// it is measured from is the whole account's, not this session's, so the
|
|
217
|
+
// runway already shortens as others spend.
|
|
218
|
+
const runwayMs = Math.max(0, config.runwayMinutes) * 60 * 1000;
|
|
219
|
+
if (Number.isFinite(window.headroomMs) && window.headroomMs <= runwayMs) {
|
|
220
|
+
return 'tight';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (window.percentUsed >= config.near) return 'tight';
|
|
132
224
|
|
|
133
|
-
// Turns are the number the work is planned in, so a short
|
|
134
|
-
// whatever the percentage says.
|
|
225
|
+
// Turns are the number the work is planned in, so a count this short is the
|
|
226
|
+
// wall whatever the percentage says.
|
|
135
227
|
if (Number.isFinite(turnsLeft) && turnsLeft <= config.fewTurns) {
|
|
136
228
|
return 'tight';
|
|
137
229
|
}
|
|
138
230
|
|
|
139
|
-
//
|
|
140
|
-
// is
|
|
141
|
-
// the
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (window.percentUsed >= config.floor && lead !== null && lead >= config.ahead) {
|
|
145
|
-
return 'tight';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
231
|
+
// Being ahead of the clock is reported and is deliberately not escalated. A
|
|
232
|
+
// five hour window is meant to be spent in a burst, and even a weekly one
|
|
233
|
+
// being spent quickly is a fact about how the week is going rather than a
|
|
234
|
+
// reason to do less today. The figures are in the line; the judgement is the
|
|
235
|
+
// reader's.
|
|
148
236
|
return 'roomy';
|
|
149
237
|
}
|
|
150
238
|
|
|
239
|
+
// Everything about the binding window that has to survive the cache, because
|
|
240
|
+
// the cached copy is what every later prompt in the minute is judged against.
|
|
241
|
+
//
|
|
242
|
+
// This is a list rather than the window itself so the cache stays small, and it
|
|
243
|
+
// is a named function so it can be checked: leaving `headroomMs` off it once
|
|
244
|
+
// meant the escalation that depends on the runway was dead in production while
|
|
245
|
+
// passing every unit test, which is the quietest way for a warning to fail.
|
|
246
|
+
const CACHED_BINDING_FIELDS = [
|
|
247
|
+
'key',
|
|
248
|
+
'label',
|
|
249
|
+
'percentUsed',
|
|
250
|
+
'stale',
|
|
251
|
+
'estimated',
|
|
252
|
+
'adjusted',
|
|
253
|
+
'pointsSinceSnapshot',
|
|
254
|
+
'correctionUnreliable',
|
|
255
|
+
'resetsAt',
|
|
256
|
+
'verdict',
|
|
257
|
+
'windowStart',
|
|
258
|
+
'spanMs',
|
|
259
|
+
'headroomMs',
|
|
260
|
+
'msToReset',
|
|
261
|
+
'refusedAt',
|
|
262
|
+
'refusedResetsAt',
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
function cacheableBinding(binding) {
|
|
266
|
+
if (!binding) return null;
|
|
267
|
+
const copy = {};
|
|
268
|
+
for (const field of CACHED_BINDING_FIELDS) {
|
|
269
|
+
copy[field] = binding[field] === undefined ? null : binding[field];
|
|
270
|
+
}
|
|
271
|
+
return copy;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Every field `pressure` reads has to be one the cache keeps, or the decision
|
|
275
|
+
// it makes on a cache hit is made from missing data.
|
|
276
|
+
function pressureInputs() {
|
|
277
|
+
return ['percentUsed', 'stale', 'verdict', 'headroomMs', 'windowStart', 'spanMs', 'estimated'];
|
|
278
|
+
}
|
|
279
|
+
|
|
151
280
|
// The hook is handed JSON on stdin. The session id in it is what lets this
|
|
152
281
|
// report what the current session has cost rather than the whole window.
|
|
153
282
|
function readHookInput() {
|
|
@@ -213,6 +342,16 @@ function briefText(parts) {
|
|
|
213
342
|
' of them yours)'
|
|
214
343
|
: '';
|
|
215
344
|
bound.push('about ' + parts.turnsLeft + ' turns of headroom' + shared);
|
|
345
|
+
// A turn count is a poor sense of urgency when several agents are spending
|
|
346
|
+
// at once: two hundred turns sounds like plenty and can be gone in ten
|
|
347
|
+
// minutes. The runway is the figure that does not flatter.
|
|
348
|
+
if (parts.runsOutIn) bound.push('about ' + parts.runsOutIn + ' of that at the current pace');
|
|
349
|
+
} else if (parts.sessions > 1) {
|
|
350
|
+
// The headroom could not be worked out, but the fact that the budget is
|
|
351
|
+
// being shared is still the most important thing about it. Attaching this
|
|
352
|
+
// only to a turn count meant it went unsaid exactly when there was no
|
|
353
|
+
// reading to attach it to.
|
|
354
|
+
bound.push(parts.sessions + ' sessions active and sharing it');
|
|
216
355
|
}
|
|
217
356
|
if (parts.resetsIn) bound.push('resets in ' + parts.resetsIn);
|
|
218
357
|
|
|
@@ -241,6 +380,16 @@ function briefText(parts) {
|
|
|
241
380
|
parts.snapshotAge + ' old, so run /usage before trusting the rest.'
|
|
242
381
|
);
|
|
243
382
|
}
|
|
383
|
+
// Work having actually been stopped is the most useful thing that can be said
|
|
384
|
+
// about a budget, and the percentages stop showing it the moment the window
|
|
385
|
+
// turns over. Saying it plainly is what stops the next session opening with
|
|
386
|
+
// "plenty of room" an hour after the last one was cut off mid-edit.
|
|
387
|
+
if (parts.refusedAgo) {
|
|
388
|
+
sentences.push(
|
|
389
|
+
'This limit refused work ' + parts.refusedAgo + ' ago, so treat the room above as ' +
|
|
390
|
+
'the amount that ran out last time, not a fresh allowance.'
|
|
391
|
+
);
|
|
392
|
+
}
|
|
244
393
|
if (parts.othersSummary) sentences.push('Other windows: ' + parts.othersSummary + '.');
|
|
245
394
|
|
|
246
395
|
// A window that is not binding can still be the expensive one to exhaust.
|
|
@@ -260,15 +409,33 @@ function briefText(parts) {
|
|
|
260
409
|
);
|
|
261
410
|
}
|
|
262
411
|
|
|
412
|
+
// Three states, and only the last one stops anything.
|
|
413
|
+
//
|
|
414
|
+
// The middle one is the one that keeps being got wrong. Near the wall the
|
|
415
|
+
// instinct is to do less of what was asked, and that is the wrong trade: it
|
|
416
|
+
// spends the user's request to protect a budget that expires anyway. The job
|
|
417
|
+
// near the wall is to make being cut off cheap - order the work, save as you
|
|
418
|
+
// go, keep a note of where things stand - not to shrink the work until it is
|
|
419
|
+
// guaranteed to fit.
|
|
263
420
|
const instruction =
|
|
264
|
-
parts.pressure === '
|
|
265
|
-
? '
|
|
266
|
-
'
|
|
267
|
-
'
|
|
268
|
-
|
|
269
|
-
'
|
|
270
|
-
|
|
271
|
-
|
|
421
|
+
parts.pressure === 'gone'
|
|
422
|
+
? 'The budget is gone, so nothing further will run. Save what exists and ' +
|
|
423
|
+
'write the handoff: what is finished, what is next and in what order, ' +
|
|
424
|
+
'which files are mid-change, and when the window resets.'
|
|
425
|
+
: parts.pressure === 'tight'
|
|
426
|
+
? 'The budget is nearly gone, so make being cut off cheap rather than ' +
|
|
427
|
+
'doing less. Carry on with the whole request at full quality: this is ' +
|
|
428
|
+
'not a reason to narrow the work, drop parts of it, or stop to ask ' +
|
|
429
|
+
'whether to go on. Order it so the most valuable part lands first, ' +
|
|
430
|
+
'save at clean boundaries as you go, and keep a short running note of ' +
|
|
431
|
+
'what is done, what is next, and which files are mid-change, so that ' +
|
|
432
|
+
'stopping at any moment loses nothing. Say in one line what may not ' +
|
|
433
|
+
'land before the reset, then keep working.'
|
|
434
|
+
: 'Open your reply with one short line stating this and confirming the ' +
|
|
435
|
+
'request fits, then get on with the work. Keep it to a single line. ' +
|
|
436
|
+
'There is room, so use it: work at full quality, take on the whole ' +
|
|
437
|
+
'request, and do not hold budget back or economise, as anything left ' +
|
|
438
|
+
'unspent is lost at the reset rather than saved.';
|
|
272
439
|
|
|
273
440
|
// The mistake this guards against: quoting the roomiest window and pinning
|
|
274
441
|
// the binding window figures to it.
|
|
@@ -283,6 +450,11 @@ function briefText(parts) {
|
|
|
283
450
|
async function run(now, hookInput) {
|
|
284
451
|
if (String(process.env.USAGE_LIMITS_BRIEF || '').toLowerCase() === 'off') return '';
|
|
285
452
|
|
|
453
|
+
// Codex cannot ship a hook inside a plugin, so its hook is installed into
|
|
454
|
+
// ~/.codex/hooks.json with the host written into the command. Settle it here,
|
|
455
|
+
// before any file is read.
|
|
456
|
+
usage.setHost(host.detect(process.argv.slice(2), process.env));
|
|
457
|
+
|
|
286
458
|
const config = settings();
|
|
287
459
|
const base = usage.collect(now);
|
|
288
460
|
if (!base.utilization) return '';
|
|
@@ -312,34 +484,38 @@ async function run(now, hookInput) {
|
|
|
312
484
|
resetsIn: Number.isFinite(w.msToReset) ? usage.formatDuration(w.msToReset) : 'an unknown time',
|
|
313
485
|
})),
|
|
314
486
|
snapshotAge: usage.formatDuration(data.snapshotAgeMs),
|
|
315
|
-
binding: binding
|
|
316
|
-
? {
|
|
317
|
-
key: binding.key,
|
|
318
|
-
label: binding.label,
|
|
319
|
-
percentUsed: binding.percentUsed,
|
|
320
|
-
stale: binding.stale,
|
|
321
|
-
estimated: binding.estimated,
|
|
322
|
-
adjusted: binding.adjusted,
|
|
323
|
-
pointsSinceSnapshot: binding.pointsSinceSnapshot,
|
|
324
|
-
correctionUnreliable: binding.correctionUnreliable,
|
|
325
|
-
resetsAt: binding.resetsAt,
|
|
326
|
-
verdict: binding.verdict,
|
|
327
|
-
windowStart: binding.windowStart,
|
|
328
|
-
spanMs: binding.spanMs,
|
|
329
|
-
}
|
|
330
|
-
: null,
|
|
487
|
+
binding: cacheableBinding(binding),
|
|
331
488
|
};
|
|
332
489
|
writeCache(mergeCache(all, sessionId, view, KEEP_SESSIONS));
|
|
333
490
|
}
|
|
334
491
|
|
|
335
492
|
const binding = view.binding;
|
|
336
493
|
const sessions = view.sessions || [];
|
|
337
|
-
|
|
494
|
+
// The spend-derived count is the accurate one when it has caught up; the
|
|
495
|
+
// open-session count is the one that is right immediately. Take whichever is
|
|
496
|
+
// higher rather than the one that happens to be handy, because under-counting
|
|
497
|
+
// is what makes the headroom read as more yours than it is.
|
|
498
|
+
const active = Math.max(sessions.length, liveSessions(all, now, LIVE_WINDOW_MS, sessionId) + 1);
|
|
499
|
+
const share =
|
|
500
|
+
sessions.length > 1 ? usage.shareOf(sessions, sessionId) : active > 1 ? 1 / active : 1;
|
|
501
|
+
const yourTurnsLeft = Number.isFinite(view.turnsLeft)
|
|
502
|
+
? Math.max(1, Math.round(view.turnsLeft * share))
|
|
503
|
+
: null;
|
|
504
|
+
// Only a short runway is worth saying. Quoting it when there are hours left
|
|
505
|
+
// would make the line longer without making it more useful.
|
|
506
|
+
const shortRunway =
|
|
507
|
+
binding && Number.isFinite(binding.headroomMs) &&
|
|
508
|
+
binding.headroomMs <= RUNWAY_MENTION_MS;
|
|
338
509
|
return briefText({
|
|
339
|
-
sessions:
|
|
340
|
-
yourTurnsLeft
|
|
341
|
-
|
|
342
|
-
|
|
510
|
+
sessions: active,
|
|
511
|
+
yourTurnsLeft,
|
|
512
|
+
runsOutIn: shortRunway ? usage.formatDuration(binding.headroomMs) : null,
|
|
513
|
+
// Only while it is still the thing that just happened. A refusal from days
|
|
514
|
+
// ago says nothing about now.
|
|
515
|
+
refusedAgo:
|
|
516
|
+
binding && Number.isFinite(binding.refusedAt) && now - binding.refusedAt < 6 * 60 * 60 * 1000
|
|
517
|
+
? usage.formatDuration(now - binding.refusedAt)
|
|
518
|
+
: null,
|
|
343
519
|
binding,
|
|
344
520
|
othersSummary: view.othersSummary,
|
|
345
521
|
turnsLeft: view.turnsLeft,
|
|
@@ -353,7 +529,13 @@ async function run(now, hookInput) {
|
|
|
353
529
|
critical: view.critical || [],
|
|
354
530
|
pointsSinceSnapshot: (binding && binding.pointsSinceSnapshot) || 0,
|
|
355
531
|
snapshotAge: view.snapshotAge,
|
|
356
|
-
|
|
532
|
+
// The turn count that matters for this session is its share of a shared
|
|
533
|
+
// budget, not the whole window's. Escalating on the whole window meant a
|
|
534
|
+
// count that looked comfortable while the part actually available here was
|
|
535
|
+
// a third of it.
|
|
536
|
+
pressure: pressure(binding, now, config, Number.isFinite(yourTurnsLeft)
|
|
537
|
+
? yourTurnsLeft
|
|
538
|
+
: view.turnsLeft),
|
|
357
539
|
});
|
|
358
540
|
}
|
|
359
541
|
|
|
@@ -375,6 +557,9 @@ if (require.main === module) {
|
|
|
375
557
|
module.exports = {
|
|
376
558
|
DEFAULTS,
|
|
377
559
|
aheadOfPace,
|
|
560
|
+
pacingMatters,
|
|
561
|
+
PACE_MIN_SPAN_MS,
|
|
562
|
+
PACE_MIN_ELAPSED,
|
|
378
563
|
pressure,
|
|
379
564
|
sessionSpend,
|
|
380
565
|
describeWindow,
|
|
@@ -384,6 +569,12 @@ module.exports = {
|
|
|
384
569
|
keepSlots,
|
|
385
570
|
pickCached,
|
|
386
571
|
mergeCache,
|
|
572
|
+
liveSessions,
|
|
573
|
+
cacheableBinding,
|
|
574
|
+
pressureInputs,
|
|
575
|
+
CACHED_BINDING_FIELDS,
|
|
576
|
+
LIVE_WINDOW_MS,
|
|
577
|
+
RUNWAY_MENTION_MS,
|
|
387
578
|
KEEP_SESSIONS,
|
|
388
579
|
run,
|
|
389
580
|
cacheFile,
|