framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
69 lines • 3.62 kB
JavaScript
import { QuotaPoller } from './quota-poller.js';
import { quotaBoundaryStatus } from './quota-boundary.js';
import { DEFAULT_SPEND_OFFSET } from './preference-defaults.js';
/**
* Wire the quota boundary up for one run (#879): poll the agent's quota, and
* hand back the gate a run consults between turns.
*
* The boundary is derived from the account's own week, so there is nothing to
* configure and nothing to remember between restarts: it is a comparison of two
* numbers the agent reports, not a total we accumulate.
*
* Resolves `undefined` when there is nothing to guard with — the agent can't
* report a quota at all (the fake driver, or a second agent that has no such
* command). That is the fail-open Rom confirmed on #519: no reading means the
* work carries on, with the per-run budget cap still underneath it. The gate
* itself fails open for the same reason, which is the opposite of the auto-PM
* gate: this one guards work the user asked for.
*
* The first read is deliberately not awaited. It takes ~5s (it spawns the whole
* agent CLI), and making every run wait that long to *maybe* find out it has
* budget would be a poor trade. It lands a moment into the run instead.
*/
export function startConsumptionGuard(opts) {
const readQuota = opts.driver.readQuota?.bind(opts.driver);
if (!readQuota)
return undefined;
const now = opts.now ?? (() => Date.now());
const poller = new QuotaPoller({ read: () => readQuota(), now });
poller.start();
// The slider's latest resolved value. The gate stays synchronous — it answers from cached
// readings — so the offset is refreshed in the background around each check and the check
// uses the last value that landed: one turn behind at worst, which is also how fresh the
// quota reading itself is.
let userOffset = DEFAULT_SPEND_OFFSET;
const refreshOffset = () => {
if (!opts.limitOffset)
return;
void Promise.resolve()
.then(opts.limitOffset)
.then(value => {
if (Number.isFinite(value))
userOffset = value;
})
.catch(() => { }); // an unreadable registry means the default policy
};
refreshOffset();
return {
poller,
stop: () => poller.stop(),
gate: () => {
refreshOffset();
const windows = poller.current().lastGood?.windows;
if (!windows)
return null;
// The same half-day cushion unattended work gets by default (#960 Edit). The continuous
// boundary starts the week at zero, so without one the first integer percent the agent
// reports outruns it and the user's own first run of the week is paused over ordinary
// rounding — the stepped line this replaces always kept the current day's seventh in hand.
// The user's slider joins the gate only when it LOOSENS it (#1490): raising the limit must
// unblock the runs the Usage bar says have room — the bar and this gate disagreeing is the
// exact thing #960 forbids — but lowering it sets where *unattended* work stands down, and
// holding it back must never tighten the gate on work the user asked for.
const limitOffset = Math.max(DEFAULT_SPEND_OFFSET, userOffset);
const status = quotaBoundaryStatus({ windows, now: now(), limitOffset, ...(opts.model ? { model: opts.model } : {}) });
return status?.reached?.label ?? null;
},
};
}
//# sourceMappingURL=consumption-guard.js.map