instar 1.3.394 → 1.3.396
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +17 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/QuotaPoller.d.ts +120 -0
- package/dist/core/QuotaPoller.d.ts.map +1 -0
- package/dist/core/QuotaPoller.js +286 -0
- package/dist/core/QuotaPoller.js.map +1 -0
- package/dist/lifeline/MessageQueue.d.ts +25 -1
- package/dist/lifeline/MessageQueue.d.ts.map +1 -1
- package/dist/lifeline/MessageQueue.js +30 -0
- package/dist/lifeline/MessageQueue.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +12 -2
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +83 -70
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/lifeline/replayPolicy.d.ts +66 -0
- package/dist/lifeline/replayPolicy.d.ts.map +1 -0
- package/dist/lifeline/replayPolicy.js +86 -0
- package/dist/lifeline/replayPolicy.js.map +1 -0
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +36 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-no-direct-llm-http.js +6 -0
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/1.3.395.md +57 -0
- package/upgrades/1.3.396.md +31 -0
- package/upgrades/side-effects/lifeline-replay-budget-classification.md +97 -0
- package/upgrades/side-effects/subscription-quota-poller.md +71 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuotaPoller — per-account live quota reader (P1.2 of the Subscription & Auth
|
|
3
|
+
* Standard, decision C hybrid read).
|
|
4
|
+
*
|
|
5
|
+
* Produces an AccountQuotaSnapshot per SubscriptionPool account: 5-hour and
|
|
6
|
+
* 7-day utilization + reset dates, per-model breakdown, and extra-usage credit
|
|
7
|
+
* state — exactly what the QuotaAwareScheduler (P1.3) needs to drain each
|
|
8
|
+
* account optimally before its reset and swap before a limit.
|
|
9
|
+
*
|
|
10
|
+
* ── Read mechanism (decision C, grounded by hands-on finding) ──
|
|
11
|
+
* Justin chose C: drive Claude Code's own /usage surface by default, the
|
|
12
|
+
* `GET /api/oauth/usage` endpoint as a bounded fallback. FINDING (2026-06-06):
|
|
13
|
+
* Claude Code does NOT persist usage to disk and exposes no non-interactive
|
|
14
|
+
* usage command, so a truly "read what the client cached" primary does not
|
|
15
|
+
* exist. The only viable Claude mechanism is the OAuth usage endpoint — the
|
|
16
|
+
* same endpoint the client's /usage screen calls internally. So the poller:
|
|
17
|
+
* - resolves each account's OAuth access token TRANSIENTLY from that account's
|
|
18
|
+
* own config-home credential store (never persisted, never logged),
|
|
19
|
+
* - calls the read-only usage endpoint at LOW frequency, and
|
|
20
|
+
* - stamps the snapshot `source: 'oauth-usage-endpoint-fallback'` for honesty.
|
|
21
|
+
* This is read-only TELEMETRY, not inference — distinct from the inference-
|
|
22
|
+
* spoofing Anthropic enforces against. It stays within decision C's accepted
|
|
23
|
+
* bounds (subscription-only, no API keys, official-client login reused).
|
|
24
|
+
*
|
|
25
|
+
* ── Burn rate, not call count ──
|
|
26
|
+
* The scheduler must decide on MEASURED utilization deltas over time, never raw
|
|
27
|
+
* call volume (lesson: call counts overstate real burn ~100× because the
|
|
28
|
+
* LlmQueue shed layer absorbs most background traffic). The poller exposes a
|
|
29
|
+
* per-account burn rate (utilization %/hour) computed from consecutive reads.
|
|
30
|
+
*
|
|
31
|
+
* Testability: `fetchImpl` and `tokenResolver` are injectable so the whole
|
|
32
|
+
* poller runs hermetically with zero credentials and zero network in tests.
|
|
33
|
+
*/
|
|
34
|
+
import type { SubscriptionPool, SubscriptionAccount, AccountQuotaSnapshot } from './SubscriptionPool.js';
|
|
35
|
+
/** Injectable token resolver — returns an account's OAuth access token or null. */
|
|
36
|
+
export type TokenResolver = (account: SubscriptionAccount) => string | null;
|
|
37
|
+
/** Minimal fetch surface so tests inject a stub (no global fetch dependency). */
|
|
38
|
+
export type FetchImpl = (url: string, init: {
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
}) => Promise<{
|
|
41
|
+
ok: boolean;
|
|
42
|
+
status: number;
|
|
43
|
+
json: () => Promise<unknown>;
|
|
44
|
+
}>;
|
|
45
|
+
export interface QuotaPollerConfig {
|
|
46
|
+
pool: SubscriptionPool;
|
|
47
|
+
/** Poll cadence. Default 15 min — low frequency by design (telemetry, not hot path). */
|
|
48
|
+
pollIntervalMs?: number;
|
|
49
|
+
/** Injected for tests; defaults to global fetch. */
|
|
50
|
+
fetchImpl?: FetchImpl;
|
|
51
|
+
/** Injected for tests; defaults to the config-home credential resolver. */
|
|
52
|
+
tokenResolver?: TokenResolver;
|
|
53
|
+
/** Logger (defaults to console). */
|
|
54
|
+
logger?: {
|
|
55
|
+
log: (m: string) => void;
|
|
56
|
+
warn: (m: string) => void;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export interface BurnRate {
|
|
60
|
+
/** Utilization points per hour on the binding (7-day) window. */
|
|
61
|
+
sevenDayPctPerHour: number | null;
|
|
62
|
+
/** Utilization points per hour on the 5-hour window. */
|
|
63
|
+
fiveHourPctPerHour: number | null;
|
|
64
|
+
/** Wall-clock ms between the two samples the rate was computed from. */
|
|
65
|
+
spanMs: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a claude-code account's OAuth access token from its config home,
|
|
69
|
+
* TRANSIENTLY. Never persisted, never logged. macOS: the per-config-home
|
|
70
|
+
* keychain entry `Claude Code-credentials-<sha256(configHome)[0:8]>` (verified
|
|
71
|
+
* empirically). Linux/other: `<configHome>/.credentials.json`. The default
|
|
72
|
+
* (`~/.claude`) keychain entry has no hash suffix.
|
|
73
|
+
*/
|
|
74
|
+
export declare function defaultTokenResolver(account: SubscriptionAccount): string | null;
|
|
75
|
+
/**
|
|
76
|
+
* Map the REAL /api/oauth/usage response (verified live 2026-06-06) into an
|
|
77
|
+
* AccountQuotaSnapshot. The live shape is `five_hour: {utilization, resets_at}`,
|
|
78
|
+
* `seven_day: {utilization, resets_at}`, `seven_day_sonnet`, `seven_day_opus`,
|
|
79
|
+
* `extra_usage: {is_enabled, used_credits, monthly_limit}`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function mapUsageResponse(body: Record<string, unknown>, source: AccountQuotaSnapshot['source'], nowIso: string): AccountQuotaSnapshot;
|
|
82
|
+
export declare class QuotaPoller {
|
|
83
|
+
private readonly pool;
|
|
84
|
+
private readonly pollIntervalMs;
|
|
85
|
+
private readonly fetchImpl;
|
|
86
|
+
private readonly tokenResolver;
|
|
87
|
+
private readonly logger;
|
|
88
|
+
private interval;
|
|
89
|
+
/** Most-recent snapshot per account id. */
|
|
90
|
+
private readonly lastByAccount;
|
|
91
|
+
/** The snapshot BEFORE the most recent, per account — the burn-rate baseline. */
|
|
92
|
+
private readonly prevByAccount;
|
|
93
|
+
constructor(config: QuotaPollerConfig);
|
|
94
|
+
start(): void;
|
|
95
|
+
stop(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Poll one account: resolve token transiently, read usage, map to snapshot.
|
|
98
|
+
* Returns null when the token is unresolvable or the read fails. NEVER logs
|
|
99
|
+
* or returns the token.
|
|
100
|
+
*/
|
|
101
|
+
pollAccount(account: SubscriptionAccount): Promise<AccountQuotaSnapshot | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Poll every claude-code/anthropic account in the pool and persist each
|
|
104
|
+
* account's latest snapshot (and a recovered status when a prior needs-reauth
|
|
105
|
+
* account now reads cleanly).
|
|
106
|
+
*/
|
|
107
|
+
pollAll(): Promise<{
|
|
108
|
+
polled: number;
|
|
109
|
+
failed: number;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Burn rate for an account, computed from the two most recent reads. Returns
|
|
113
|
+
* null until at least two distinct reads exist. Uses MEASURED utilization
|
|
114
|
+
* deltas — never call counts. The caller (P1.3 scheduler) decides on these.
|
|
115
|
+
*/
|
|
116
|
+
burnRate(accountId: string): BurnRate | null;
|
|
117
|
+
/** Expose the last in-memory snapshot for an account (test/diagnostic). */
|
|
118
|
+
lastSnapshot(accountId: string): AccountQuotaSnapshot | null;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=QuotaPoller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuotaPoller.d.ts","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAE/B,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;AAE5E,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KACtC,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,gBAAgB,CAAC;IACvB,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oCAAoC;IACpC,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAClE;AAED,MAAM,WAAW,QAAQ;IACvB,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wDAAwD;IACxD,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAyChF;AASD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,EACtC,MAAM,EAAE,MAAM,GACb,oBAAoB,CA0CtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0D;IACjF,OAAO,CAAC,QAAQ,CAA+C;IAC/D,2CAA2C;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;IACzE,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;gBAE7D,MAAM,EAAE,iBAAiB;IAUrC,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAOZ;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA6CrF;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAwB5D;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAqB5C,2EAA2E;IAC3E,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI;CAG7D"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuotaPoller — per-account live quota reader (P1.2 of the Subscription & Auth
|
|
3
|
+
* Standard, decision C hybrid read).
|
|
4
|
+
*
|
|
5
|
+
* Produces an AccountQuotaSnapshot per SubscriptionPool account: 5-hour and
|
|
6
|
+
* 7-day utilization + reset dates, per-model breakdown, and extra-usage credit
|
|
7
|
+
* state — exactly what the QuotaAwareScheduler (P1.3) needs to drain each
|
|
8
|
+
* account optimally before its reset and swap before a limit.
|
|
9
|
+
*
|
|
10
|
+
* ── Read mechanism (decision C, grounded by hands-on finding) ──
|
|
11
|
+
* Justin chose C: drive Claude Code's own /usage surface by default, the
|
|
12
|
+
* `GET /api/oauth/usage` endpoint as a bounded fallback. FINDING (2026-06-06):
|
|
13
|
+
* Claude Code does NOT persist usage to disk and exposes no non-interactive
|
|
14
|
+
* usage command, so a truly "read what the client cached" primary does not
|
|
15
|
+
* exist. The only viable Claude mechanism is the OAuth usage endpoint — the
|
|
16
|
+
* same endpoint the client's /usage screen calls internally. So the poller:
|
|
17
|
+
* - resolves each account's OAuth access token TRANSIENTLY from that account's
|
|
18
|
+
* own config-home credential store (never persisted, never logged),
|
|
19
|
+
* - calls the read-only usage endpoint at LOW frequency, and
|
|
20
|
+
* - stamps the snapshot `source: 'oauth-usage-endpoint-fallback'` for honesty.
|
|
21
|
+
* This is read-only TELEMETRY, not inference — distinct from the inference-
|
|
22
|
+
* spoofing Anthropic enforces against. It stays within decision C's accepted
|
|
23
|
+
* bounds (subscription-only, no API keys, official-client login reused).
|
|
24
|
+
*
|
|
25
|
+
* ── Burn rate, not call count ──
|
|
26
|
+
* The scheduler must decide on MEASURED utilization deltas over time, never raw
|
|
27
|
+
* call volume (lesson: call counts overstate real burn ~100× because the
|
|
28
|
+
* LlmQueue shed layer absorbs most background traffic). The poller exposes a
|
|
29
|
+
* per-account burn rate (utilization %/hour) computed from consecutive reads.
|
|
30
|
+
*
|
|
31
|
+
* Testability: `fetchImpl` and `tokenResolver` are injectable so the whole
|
|
32
|
+
* poller runs hermetically with zero credentials and zero network in tests.
|
|
33
|
+
*/
|
|
34
|
+
import { execFileSync } from 'node:child_process';
|
|
35
|
+
import crypto from 'node:crypto';
|
|
36
|
+
import fs from 'node:fs';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a claude-code account's OAuth access token from its config home,
|
|
41
|
+
* TRANSIENTLY. Never persisted, never logged. macOS: the per-config-home
|
|
42
|
+
* keychain entry `Claude Code-credentials-<sha256(configHome)[0:8]>` (verified
|
|
43
|
+
* empirically). Linux/other: `<configHome>/.credentials.json`. The default
|
|
44
|
+
* (`~/.claude`) keychain entry has no hash suffix.
|
|
45
|
+
*/
|
|
46
|
+
export function defaultTokenResolver(account) {
|
|
47
|
+
if (account.provider !== 'anthropic' || account.framework !== 'claude-code') {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const configHome = expandHome(account.configHome);
|
|
51
|
+
// Linux / non-darwin: per-config-home credentials file.
|
|
52
|
+
if (process.platform !== 'darwin') {
|
|
53
|
+
try {
|
|
54
|
+
const credPath = path.join(configHome, '.credentials.json');
|
|
55
|
+
if (fs.existsSync(credPath)) {
|
|
56
|
+
const data = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
|
|
57
|
+
const tok = data?.claudeAiOauth?.accessToken;
|
|
58
|
+
return typeof tok === 'string' && tok.startsWith('sk-ant-oat') ? tok : null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// @silent-fallback-ok: missing/unreadable creds → unresolvable token (null)
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// macOS: keychain service name, hash-suffixed by config-home path.
|
|
67
|
+
const defaultHome = expandHome('~/.claude');
|
|
68
|
+
const service = configHome === defaultHome
|
|
69
|
+
? 'Claude Code-credentials'
|
|
70
|
+
: `Claude Code-credentials-${crypto.createHash('sha256').update(configHome).digest('hex').slice(0, 8)}`;
|
|
71
|
+
try {
|
|
72
|
+
const raw = execFileSync('security', ['find-generic-password', '-s', service, '-w'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
73
|
+
if (!raw)
|
|
74
|
+
return null;
|
|
75
|
+
const data = JSON.parse(raw);
|
|
76
|
+
const tok = data?.claudeAiOauth?.accessToken;
|
|
77
|
+
return typeof tok === 'string' && tok.startsWith('sk-ant-oat') ? tok : null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// @silent-fallback-ok: no keychain entry for this config home → null
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function expandHome(p) {
|
|
85
|
+
if (p === '~' || p.startsWith('~/')) {
|
|
86
|
+
return path.join(process.env.HOME ?? '', p.slice(1));
|
|
87
|
+
}
|
|
88
|
+
return p;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Map the REAL /api/oauth/usage response (verified live 2026-06-06) into an
|
|
92
|
+
* AccountQuotaSnapshot. The live shape is `five_hour: {utilization, resets_at}`,
|
|
93
|
+
* `seven_day: {utilization, resets_at}`, `seven_day_sonnet`, `seven_day_opus`,
|
|
94
|
+
* `extra_usage: {is_enabled, used_credits, monthly_limit}`.
|
|
95
|
+
*/
|
|
96
|
+
export function mapUsageResponse(body, source, nowIso) {
|
|
97
|
+
const snap = { source, measuredAt: nowIso };
|
|
98
|
+
const win = (v) => {
|
|
99
|
+
if (!v || typeof v !== 'object')
|
|
100
|
+
return undefined;
|
|
101
|
+
const o = v;
|
|
102
|
+
if (o.utilization === undefined && o.resets_at === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
return {
|
|
105
|
+
utilizationPct: Number(o.utilization ?? 0),
|
|
106
|
+
resetsAt: String(o.resets_at ?? ''),
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const five = win(body['five_hour']);
|
|
110
|
+
if (five)
|
|
111
|
+
snap.fiveHour = five;
|
|
112
|
+
const seven = win(body['seven_day']);
|
|
113
|
+
if (seven)
|
|
114
|
+
snap.sevenDay = seven;
|
|
115
|
+
const perModel = {};
|
|
116
|
+
for (const [key, label] of [
|
|
117
|
+
['seven_day_sonnet', 'sonnet'],
|
|
118
|
+
['seven_day_opus', 'opus'],
|
|
119
|
+
]) {
|
|
120
|
+
const v = body[key];
|
|
121
|
+
if (v && typeof v === 'object') {
|
|
122
|
+
const u = v.utilization;
|
|
123
|
+
perModel[label] = u === undefined || u === null ? null : Number(u);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (Object.keys(perModel).length > 0)
|
|
127
|
+
snap.perModel = perModel;
|
|
128
|
+
const extra = body['extra_usage'];
|
|
129
|
+
if (extra && typeof extra === 'object') {
|
|
130
|
+
const e = extra;
|
|
131
|
+
snap.extraUsage = {
|
|
132
|
+
isEnabled: Boolean(e.is_enabled),
|
|
133
|
+
usedCredits: Number(e.used_credits ?? 0),
|
|
134
|
+
monthlyLimit: Number(e.monthly_limit ?? 0),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return snap;
|
|
138
|
+
}
|
|
139
|
+
export class QuotaPoller {
|
|
140
|
+
pool;
|
|
141
|
+
pollIntervalMs;
|
|
142
|
+
fetchImpl;
|
|
143
|
+
tokenResolver;
|
|
144
|
+
logger;
|
|
145
|
+
interval = null;
|
|
146
|
+
/** Most-recent snapshot per account id. */
|
|
147
|
+
lastByAccount = new Map();
|
|
148
|
+
/** The snapshot BEFORE the most recent, per account — the burn-rate baseline. */
|
|
149
|
+
prevByAccount = new Map();
|
|
150
|
+
constructor(config) {
|
|
151
|
+
this.pool = config.pool;
|
|
152
|
+
this.pollIntervalMs = config.pollIntervalMs ?? 15 * 60_000;
|
|
153
|
+
this.fetchImpl =
|
|
154
|
+
config.fetchImpl ??
|
|
155
|
+
((url, init) => fetch(url, init));
|
|
156
|
+
this.tokenResolver = config.tokenResolver ?? defaultTokenResolver;
|
|
157
|
+
this.logger = config.logger ?? { log: () => { }, warn: () => { } };
|
|
158
|
+
}
|
|
159
|
+
start() {
|
|
160
|
+
if (this.interval)
|
|
161
|
+
return;
|
|
162
|
+
this.interval = setInterval(() => {
|
|
163
|
+
void this.pollAll();
|
|
164
|
+
}, this.pollIntervalMs);
|
|
165
|
+
this.interval.unref?.();
|
|
166
|
+
}
|
|
167
|
+
stop() {
|
|
168
|
+
if (this.interval) {
|
|
169
|
+
clearInterval(this.interval);
|
|
170
|
+
this.interval = null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Poll one account: resolve token transiently, read usage, map to snapshot.
|
|
175
|
+
* Returns null when the token is unresolvable or the read fails. NEVER logs
|
|
176
|
+
* or returns the token.
|
|
177
|
+
*/
|
|
178
|
+
async pollAccount(account) {
|
|
179
|
+
const token = this.tokenResolver(account);
|
|
180
|
+
if (!token) {
|
|
181
|
+
this.logger.warn(`[QuotaPoller] no resolvable token for account ${account.id} — skipping`);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
let body = null;
|
|
185
|
+
let authFailed = false;
|
|
186
|
+
try {
|
|
187
|
+
const res = await this.fetchImpl(USAGE_URL, {
|
|
188
|
+
headers: {
|
|
189
|
+
Authorization: `Bearer ${token}`,
|
|
190
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
191
|
+
'anthropic-version': '2023-06-01',
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
if (res.ok) {
|
|
195
|
+
body = (await res.json());
|
|
196
|
+
}
|
|
197
|
+
else if (res.status === 401 || res.status === 403) {
|
|
198
|
+
authFailed = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// @silent-fallback-ok: network failure → no snapshot this cycle (retry next)
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (authFailed) {
|
|
206
|
+
// Genuine auth failure (revoked / password change) — surface for re-auth.
|
|
207
|
+
try {
|
|
208
|
+
this.pool.update(account.id, { status: 'needs-reauth' });
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// @silent-fallback-ok: pool update best-effort; status reflects next read
|
|
212
|
+
}
|
|
213
|
+
this.logger.warn(`[QuotaPoller] account ${account.id} usage read returned auth error → needs-reauth`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (!body)
|
|
217
|
+
return null;
|
|
218
|
+
const snap = mapUsageResponse(body, 'oauth-usage-endpoint-fallback', new Date().toISOString());
|
|
219
|
+
// Shift the prior "last" down to "prev" so burnRate has a distinct baseline.
|
|
220
|
+
const priorLast = this.lastByAccount.get(account.id);
|
|
221
|
+
if (priorLast)
|
|
222
|
+
this.prevByAccount.set(account.id, priorLast);
|
|
223
|
+
this.lastByAccount.set(account.id, snap);
|
|
224
|
+
return snap;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Poll every claude-code/anthropic account in the pool and persist each
|
|
228
|
+
* account's latest snapshot (and a recovered status when a prior needs-reauth
|
|
229
|
+
* account now reads cleanly).
|
|
230
|
+
*/
|
|
231
|
+
async pollAll() {
|
|
232
|
+
let polled = 0;
|
|
233
|
+
let failed = 0;
|
|
234
|
+
for (const account of this.pool.list()) {
|
|
235
|
+
if (account.provider !== 'anthropic' || account.framework !== 'claude-code')
|
|
236
|
+
continue;
|
|
237
|
+
if (account.status === 'disabled')
|
|
238
|
+
continue;
|
|
239
|
+
const snap = await this.pollAccount(account);
|
|
240
|
+
if (!snap) {
|
|
241
|
+
failed++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
polled++;
|
|
245
|
+
const patch = { lastQuota: snap };
|
|
246
|
+
// A clean read on an account previously flagged needs-reauth restores it.
|
|
247
|
+
if (account.status === 'needs-reauth')
|
|
248
|
+
patch.status = 'active';
|
|
249
|
+
try {
|
|
250
|
+
this.pool.update(account.id, patch);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// @silent-fallback-ok: persistence best-effort; snapshot retained in memory
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { polled, failed };
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Burn rate for an account, computed from the two most recent reads. Returns
|
|
260
|
+
* null until at least two distinct reads exist. Uses MEASURED utilization
|
|
261
|
+
* deltas — never call counts. The caller (P1.3 scheduler) decides on these.
|
|
262
|
+
*/
|
|
263
|
+
burnRate(accountId) {
|
|
264
|
+
const prev = this.prevByAccount.get(accountId);
|
|
265
|
+
const current = this.lastByAccount.get(accountId);
|
|
266
|
+
if (!prev || !current || prev.measuredAt === current.measuredAt)
|
|
267
|
+
return null;
|
|
268
|
+
const t0 = Date.parse(prev.measuredAt ?? '');
|
|
269
|
+
const t1 = Date.parse(current.measuredAt ?? '');
|
|
270
|
+
if (!Number.isFinite(t0) || !Number.isFinite(t1) || t1 <= t0)
|
|
271
|
+
return null;
|
|
272
|
+
const spanMs = t1 - t0;
|
|
273
|
+
const hours = spanMs / 3_600_000;
|
|
274
|
+
const delta = (a, b) => a && b ? (b.utilizationPct - a.utilizationPct) / hours : null;
|
|
275
|
+
return {
|
|
276
|
+
sevenDayPctPerHour: delta(prev.sevenDay, current.sevenDay),
|
|
277
|
+
fiveHourPctPerHour: delta(prev.fiveHour, current.fiveHour),
|
|
278
|
+
spanMs,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/** Expose the last in-memory snapshot for an account (test/diagnostic). */
|
|
282
|
+
lastSnapshot(accountId) {
|
|
283
|
+
return this.lastByAccount.get(accountId) ?? null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=QuotaPoller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuotaPoller.js","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,8 +16,14 @@ export interface QueuedMessage {
|
|
|
16
16
|
photoPath?: string;
|
|
17
17
|
documentPath?: string;
|
|
18
18
|
documentName?: string;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Strikes from genuine HTTP-400 rejections (message-specific / "poison").
|
|
21
|
+
* Named `replayFailures` for on-disk back-compat with queues written before
|
|
22
|
+
* the transient/poison split (2026-06-06); semantically the poison counter.
|
|
23
|
+
*/
|
|
20
24
|
replayFailures?: number;
|
|
25
|
+
/** Strikes from transient capacity/availability failures (timeout/5xx/down). */
|
|
26
|
+
transientReplayFailures?: number;
|
|
21
27
|
}
|
|
22
28
|
export declare class MessageQueue {
|
|
23
29
|
private queuePath;
|
|
@@ -35,6 +41,24 @@ export declare class MessageQueue {
|
|
|
35
41
|
* Peek at the queue without draining.
|
|
36
42
|
*/
|
|
37
43
|
peek(): QueuedMessage[];
|
|
44
|
+
/**
|
|
45
|
+
* Remove a single message by id and persist. Used by durable replay: a
|
|
46
|
+
* message is removed from the persisted queue ONLY after it has been
|
|
47
|
+
* delivered or deliberately dropped — so a process exit mid-replay can never
|
|
48
|
+
* lose an undelivered message (the 2026-06-06 topic-21487 untracked-loss bug,
|
|
49
|
+
* where drain() emptied the disk queue before delivery confirmed).
|
|
50
|
+
* Returns true if a message was removed.
|
|
51
|
+
*/
|
|
52
|
+
remove(id: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Patch the replay-strike counters of a queued message in place and persist.
|
|
55
|
+
* Leaves the message ON DISK (durable) — used when a forward fails and the
|
|
56
|
+
* message must be retried on the next replay tick. No-op if the id is gone.
|
|
57
|
+
*/
|
|
58
|
+
updateReplayCounters(id: string, counters: {
|
|
59
|
+
replayFailures: number;
|
|
60
|
+
transientReplayFailures: number;
|
|
61
|
+
}): void;
|
|
38
62
|
get length(): number;
|
|
39
63
|
private load;
|
|
40
64
|
private save;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageQueue.d.ts","sourceRoot":"","sources":["../../src/lifeline/MessageQueue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB
|
|
1
|
+
{"version":3,"file":"MessageQueue.d.ts","sourceRoot":"","sources":["../../src/lifeline/MessageQueue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAuB;gBAExB,QAAQ,EAAE,MAAM;IAK5B;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAKjC;;OAEG;IACH,KAAK,IAAI,aAAa,EAAE;IAOxB;;OAEG;IACH,IAAI,IAAI,aAAa,EAAE;IAIvB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU3B;;;;OAIG;IACH,oBAAoB,CAClB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,uBAAuB,EAAE,MAAM,CAAA;KAAE,GACpE,IAAI;IAQP,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,IAAI;CASb"}
|
|
@@ -35,6 +35,36 @@ export class MessageQueue {
|
|
|
35
35
|
peek() {
|
|
36
36
|
return [...this.queue];
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Remove a single message by id and persist. Used by durable replay: a
|
|
40
|
+
* message is removed from the persisted queue ONLY after it has been
|
|
41
|
+
* delivered or deliberately dropped — so a process exit mid-replay can never
|
|
42
|
+
* lose an undelivered message (the 2026-06-06 topic-21487 untracked-loss bug,
|
|
43
|
+
* where drain() emptied the disk queue before delivery confirmed).
|
|
44
|
+
* Returns true if a message was removed.
|
|
45
|
+
*/
|
|
46
|
+
remove(id) {
|
|
47
|
+
const before = this.queue.length;
|
|
48
|
+
this.queue = this.queue.filter(m => m.id !== id);
|
|
49
|
+
if (this.queue.length !== before) {
|
|
50
|
+
this.save();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Patch the replay-strike counters of a queued message in place and persist.
|
|
57
|
+
* Leaves the message ON DISK (durable) — used when a forward fails and the
|
|
58
|
+
* message must be retried on the next replay tick. No-op if the id is gone.
|
|
59
|
+
*/
|
|
60
|
+
updateReplayCounters(id, counters) {
|
|
61
|
+
const msg = this.queue.find(m => m.id === id);
|
|
62
|
+
if (!msg)
|
|
63
|
+
return;
|
|
64
|
+
msg.replayFailures = counters.replayFailures;
|
|
65
|
+
msg.transientReplayFailures = counters.transientReplayFailures;
|
|
66
|
+
this.save();
|
|
67
|
+
}
|
|
38
68
|
get length() {
|
|
39
69
|
return this.queue.length;
|
|
40
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageQueue.js","sourceRoot":"","sources":["../../src/lifeline/MessageQueue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"MessageQueue.js","sourceRoot":"","sources":["../../src/lifeline/MessageQueue.ts"],"names":[],"mappings":"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"}
|
|
@@ -172,7 +172,19 @@ export declare class TelegramLifeline {
|
|
|
172
172
|
* (awake) this is a single `existsSync` no-op per forward.
|
|
173
173
|
*/
|
|
174
174
|
private requestWakeIfSlept;
|
|
175
|
+
/**
|
|
176
|
+
* Boolean façade kept for the inbound-handler callers that only care whether
|
|
177
|
+
* the forward landed. Replay uses {@link forwardToServerClassified} so it can
|
|
178
|
+
* tell a message-specific rejection (poison) from a transient outage.
|
|
179
|
+
*/
|
|
175
180
|
private forwardToServer;
|
|
181
|
+
/**
|
|
182
|
+
* Forward a message to the server and classify the result so replay can
|
|
183
|
+
* budget correctly: only a genuine HTTP-400 rejection ('poison') burns the
|
|
184
|
+
* drop budget; timeout / 5xx / 503-boot / network refusal are 'transient'
|
|
185
|
+
* (never drop a real message), and 426 is 'skew' (coordinated restart).
|
|
186
|
+
*/
|
|
187
|
+
private forwardToServerClassified;
|
|
176
188
|
/**
|
|
177
189
|
* Resolve the destination topic for update-class alerts (version-skew,
|
|
178
190
|
* delivery-health). Reads `agent-updates-topic` from server state. If
|
|
@@ -232,8 +244,6 @@ export declare class TelegramLifeline {
|
|
|
232
244
|
private driftPromoter;
|
|
233
245
|
private watchdog;
|
|
234
246
|
private handleLifelineCommand;
|
|
235
|
-
/** Max times a message can fail replay before being dropped. */
|
|
236
|
-
private static readonly MAX_REPLAY_FAILURES;
|
|
237
247
|
private replayQueue;
|
|
238
248
|
/** Whether we've already notified for the current outage. Reset on recovery. */
|
|
239
249
|
private hasNotifiedServerDown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"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"}
|