@cohortapp/agent-sdk 2.11.4 → 2.11.5
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/lib/budget-guard.mjs +35 -4
- package/lib/budget-guard.test.mjs +25 -0
- package/package.json +1 -1
package/lib/budget-guard.mjs
CHANGED
|
@@ -663,7 +663,21 @@ export function dailyAllowance(deps) {
|
|
|
663
663
|
* @param {object} [deps] { now?, ledgerDir?, agentRoot?, capUSD?, iterationCap?,
|
|
664
664
|
* seatEnvelope?, mandateCachePath?, log? }
|
|
665
665
|
*/
|
|
666
|
+
/**
|
|
667
|
+
* Is this seat authenticated by a flat-fee Max SUBSCRIPTION (not the metered
|
|
668
|
+
* API)? A subscription seat has no dollar meter: the CLI's `total_cost_usd` is
|
|
669
|
+
* API-EQUIVALENT, not spend, and every session reports "unmeasured" by design —
|
|
670
|
+
* that is normal, not a telemetry outage. Banding such a seat on dollars
|
|
671
|
+
* suspends a seat that costs a flat fee, stops its autonomous work, and floods
|
|
672
|
+
* the log every tick. `deps.subscriptionAuth` overrides for tests.
|
|
673
|
+
*/
|
|
674
|
+
export function isSubscriptionAuth(deps) {
|
|
675
|
+
if (deps && typeof deps.subscriptionAuth === "boolean") return deps.subscriptionAuth;
|
|
676
|
+
return process.env.MAESTRO_PREFER_SUBSCRIPTION_AUTH === "1" && !process.env.ANTHROPIC_API_KEY;
|
|
677
|
+
}
|
|
678
|
+
|
|
666
679
|
export function dailyStatus(deps) {
|
|
680
|
+
const subscriptionAuth = isSubscriptionAuth(deps);
|
|
667
681
|
const iterationCap = cfgIterationCap(deps);
|
|
668
682
|
const today = sumToday(deps);
|
|
669
683
|
const { spentUSD, sessions } = today;
|
|
@@ -708,15 +722,23 @@ export function dailyStatus(deps) {
|
|
|
708
722
|
// The binding constraint is whichever is closest to (or furthest past) the cap.
|
|
709
723
|
// A null day-reading contributes nothing; the exhausted-envelope case is
|
|
710
724
|
// already ≥100 % on the month, so nothing is lost by skipping it.
|
|
711
|
-
|
|
725
|
+
// A subscription-auth seat has no dollar meter (see isSubscriptionAuth): its
|
|
726
|
+
// dollar "spend" is API-equivalent fiction, so banding on it wrongly suspends a
|
|
727
|
+
// flat-fee seat and halts its autonomous work. Band it on SESSION VOLUME only
|
|
728
|
+
// (a real limit); concurrency (resource-governor) + the 429 rate-breaker are
|
|
729
|
+
// its other guards.
|
|
730
|
+
const pct = subscriptionAuth
|
|
731
|
+
? iterPct
|
|
732
|
+
: Math.max(spendPct ?? 0, monthPct, iterPct);
|
|
712
733
|
let band = bandForPct(pct);
|
|
713
734
|
|
|
714
735
|
// ── BLIND ⇒ DEGRADE ────────────────────────────────────────────────────────
|
|
715
736
|
// `blind` (sessions ran, none measured) was computed and reported and enforced
|
|
716
737
|
// by nothing. It is now load-bearing: we do not invent a price, we lower the
|
|
717
738
|
// rung. Only when the day is TRULY unpriceable — if a month mean was available
|
|
718
|
-
// the spend was imputed from it and the ordinary band applies.
|
|
719
|
-
|
|
739
|
+
// the spend was imputed from it and the ordinary band applies. Does NOT apply
|
|
740
|
+
// to subscription auth, where "unmeasured" is the normal, expected state.
|
|
741
|
+
if (!subscriptionAuth && today.blind && today.unpriceable && band < BLIND_MIN_BAND) {
|
|
720
742
|
extraDegradations.push(
|
|
721
743
|
`band raised ${band}% → ${BLIND_MIN_BAND}%: ${today.unmeasuredSessions} session(s) ran today and NONE could be ` +
|
|
722
744
|
"measured or priced. An unmeasurable seat is degraded, not trusted."
|
|
@@ -725,7 +747,16 @@ export function dailyStatus(deps) {
|
|
|
725
747
|
}
|
|
726
748
|
const posture = postureForBand(band);
|
|
727
749
|
|
|
728
|
-
|
|
750
|
+
let degradations = [...today.degradations, ...allowance.degradations, ...extraDegradations];
|
|
751
|
+
// On a subscription seat the dollar-meter degradations (imputed / unmeasured /
|
|
752
|
+
// unfunded-envelope / telemetry) are irrelevant — there is no dollar meter —
|
|
753
|
+
// and were logged EVERY tick, burying real signal (a DM not answered) under a
|
|
754
|
+
// flood. Drop them; keep any non-dollar note (e.g. a malformed ledger line).
|
|
755
|
+
if (subscriptionAuth) {
|
|
756
|
+
degradations = degradations.filter(
|
|
757
|
+
(d) => !/imputed|unmeasured|unpriceable|UNFUNDED SEAT|envelope|per-session|telemetry|blind/i.test(String(d)),
|
|
758
|
+
);
|
|
759
|
+
}
|
|
729
760
|
// Degradations ride on the returned status AND go to the log: the daemon is
|
|
730
761
|
// usually the only thing awake when telemetry breaks, and a governor that
|
|
731
762
|
// quietly under-counts is the failure mode this module now exists to prevent.
|
|
@@ -338,3 +338,28 @@ test("a ledger of only zero-token rows sums to $0 (proves the bug is the input,
|
|
|
338
338
|
assert.equal(s.sessions, 2);
|
|
339
339
|
} finally { await rm(dir); }
|
|
340
340
|
});
|
|
341
|
+
|
|
342
|
+
test("dailyStatus: a subscription-auth seat is banded on session VOLUME, not dollars", async () => {
|
|
343
|
+
const dir = await makeLedgerDir();
|
|
344
|
+
try {
|
|
345
|
+
seedLedger(dir, [{ usd: 40 }]); // $40 of $10 = 400%
|
|
346
|
+
// A metered (API) seat: dollars bind → band 150 (refused).
|
|
347
|
+
assert.equal(dailyStatus({ ledgerDir: dir, now: clk, capUSD: 10, iterationCap: 100 }).band, 150);
|
|
348
|
+
// A flat-fee subscription seat has no dollar meter — the same API-equivalent
|
|
349
|
+
// "spend" is fiction. It lands at band 0 / normal so autonomous work keeps
|
|
350
|
+
// running; only session VOLUME can band it.
|
|
351
|
+
const sub = dailyStatus({ ledgerDir: dir, now: clk, capUSD: 10, iterationCap: 100, subscriptionAuth: true });
|
|
352
|
+
assert.equal(sub.band, 0);
|
|
353
|
+
assert.equal(sub.mode, "normal");
|
|
354
|
+
} finally { await rm(dir); }
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("dailyStatus: a subscription-auth seat STILL bands on session volume (a real limit)", async () => {
|
|
358
|
+
const dir = await makeLedgerDir();
|
|
359
|
+
try {
|
|
360
|
+
seedLedger(dir, Array.from({ length: 12 }, () => ({ usd: 0.01 }))); // 12 sessions
|
|
361
|
+
const sub = dailyStatus({ ledgerDir: dir, now: clk, capUSD: 1000, iterationCap: 10, subscriptionAuth: true });
|
|
362
|
+
assert.equal(sub.band, 100, "12 sessions / cap 10 = 120% → degrade even on subscription");
|
|
363
|
+
assert.equal(sub.mode, "degraded");
|
|
364
|
+
} finally { await rm(dir); }
|
|
365
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cohortapp/agent-sdk",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.5",
|
|
4
4
|
"description": "Cohort Agent SDK — autonomous AI colleague runtime. Deploy senior AI colleagues on dedicated Mac minis, wired to the Cohort operating surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|