@teamvibe/poller 0.8.6-rc.4 → 0.8.6
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/pool-logging.js
CHANGED
|
@@ -42,6 +42,20 @@ export function logPoolFetch(log, pool) {
|
|
|
42
42
|
const configured = pool.kind === 'ready' || pool.kind === 'fetch_unavailable' && pool.reason === 'no_usable_accounts' ? 'true' : 'unknown';
|
|
43
43
|
poolInfo(log, `fetch poolConfigured=${configured} enabled=${enabled} usable=${enabled} result=${pool.kind}${pool.kind === 'fetch_unavailable' ? ` reason=${pool.reason}` : ''}${pool.cause ? ` cause=${pool.cause}` : ''} attempts=${pool.attempts ?? 1}`);
|
|
44
44
|
}
|
|
45
|
+
function skippedSelectionReason(candidate, selected) {
|
|
46
|
+
if (candidate.provenance === 'unknown' && selected.provenance === 'known')
|
|
47
|
+
return 'known observation preferred';
|
|
48
|
+
if (candidate.provenance === 'known' && selected.provenance === 'known' && candidate.score < selected.score)
|
|
49
|
+
return 'lower headroom per hour';
|
|
50
|
+
if (candidate.provenance === selected.provenance && selected.pinned)
|
|
51
|
+
return 'pinned thread kept (within tolerance)';
|
|
52
|
+
if (candidate.provenance === 'known' && selected.provenance === 'known' && candidate.score === selected.score)
|
|
53
|
+
return 'equal headroom, account id order';
|
|
54
|
+
if (candidate.provenance === 'unknown' && selected.provenance === 'unknown')
|
|
55
|
+
return 'no measurements, account id order';
|
|
56
|
+
// Should be unreachable: the policy explains every available skipped candidate above.
|
|
57
|
+
return 'deterministic account order';
|
|
58
|
+
}
|
|
45
59
|
/** The log and transport share a single explanation of the same selection snapshot. */
|
|
46
60
|
export function poolSelectionCandidates(pool, states, model, now, selected) {
|
|
47
61
|
if (pool.kind !== 'ready')
|
|
@@ -61,7 +75,7 @@ export function poolSelectionCandidates(pool, states, model, now, selected) {
|
|
|
61
75
|
if (selected.kind === 'selected' && account.accountId === selected.account.accountId)
|
|
62
76
|
return { ...base, ...metrics, outcome: 'selected', reason: 'selected' };
|
|
63
77
|
const reason = candidate.kind === 'selected' && selected.kind === 'selected'
|
|
64
|
-
? candidate
|
|
78
|
+
? skippedSelectionReason(candidate, selected)
|
|
65
79
|
: 'unavailable';
|
|
66
80
|
return { ...base, ...metrics, reason };
|
|
67
81
|
});
|
|
@@ -88,6 +88,7 @@ export declare function preparePool(msg: TeamVibeQueueMessage, resolved: Resolve
|
|
|
88
88
|
account: SubscriptionAccount;
|
|
89
89
|
provenance: "known";
|
|
90
90
|
source: "passive" | "probe";
|
|
91
|
+
pinned?: true;
|
|
91
92
|
} & HeadroomScore & {
|
|
92
93
|
model: string | undefined;
|
|
93
94
|
modelSource: import("./claude-spawner.js").ModelSource;
|
|
@@ -102,6 +103,7 @@ export declare function preparePool(msg: TeamVibeQueueMessage, resolved: Resolve
|
|
|
102
103
|
account: SubscriptionAccount;
|
|
103
104
|
provenance: "unknown";
|
|
104
105
|
uncertainApplicability: boolean;
|
|
106
|
+
pinned?: true;
|
|
105
107
|
} & {
|
|
106
108
|
model: string | undefined;
|
|
107
109
|
modelSource: import("./claude-spawner.js").ModelSource;
|
|
@@ -63,6 +63,10 @@ export declare function isLegacyReporter(version: string | undefined): boolean;
|
|
|
63
63
|
* for modern reports, duplicate unknown windows, or an identical global copy.
|
|
64
64
|
*/
|
|
65
65
|
export declare function projectLegacyFableUsage<T extends UsageObservation | undefined>(usage: T, version?: string): T;
|
|
66
|
+
/** Genuine Fable data replaces raw uncertain windows observed at or before it.
|
|
67
|
+
* Deduplicate uncertain windows before splitting measurements from rejections.
|
|
68
|
+
*/
|
|
69
|
+
export declare function supersedeLegacyFableProjections(observations: readonly (UsageObservation | undefined)[], now: number): (UsageObservation | undefined)[];
|
|
66
70
|
/** Latest measurements plus durable genuine rejections; omission or success never clears a genuine hold. */
|
|
67
71
|
export declare function mergeUsageObservations(observations: readonly (UsageObservation | undefined)[], now: number): UsageObservation | undefined;
|
|
68
72
|
/** Preserve last-known meters, projecting legacy identity before its global companion is replaced. */
|
|
@@ -85,11 +89,13 @@ export type SelectionOutcome<A extends AccountIdentity = AccountIdentity> = ({
|
|
|
85
89
|
account: A;
|
|
86
90
|
provenance: 'known';
|
|
87
91
|
source: 'passive' | 'probe';
|
|
92
|
+
pinned?: true;
|
|
88
93
|
} & HeadroomScore) | {
|
|
89
94
|
kind: 'selected';
|
|
90
95
|
account: A;
|
|
91
96
|
provenance: 'unknown';
|
|
92
97
|
uncertainApplicability: boolean;
|
|
98
|
+
pinned?: true;
|
|
93
99
|
} | {
|
|
94
100
|
kind: 'deferred';
|
|
95
101
|
eligibleAt: number;
|
|
@@ -59,24 +59,61 @@ export function projectLegacyFableUsage(usage, version) {
|
|
|
59
59
|
}),
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
/** Genuine Fable data
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/** Genuine Fable data replaces raw uncertain windows observed at or before it.
|
|
63
|
+
* Deduplicate uncertain windows before splitting measurements from rejections.
|
|
64
|
+
*/
|
|
65
|
+
export function supersedeLegacyFableProjections(observations, now) {
|
|
66
|
+
// Preserve explicit-scope compatibility for already projected legacy Fable data.
|
|
67
|
+
let newestFable = -Infinity;
|
|
65
68
|
let newestGenuine = -Infinity;
|
|
66
|
-
for (const usage of
|
|
69
|
+
for (const usage of observations) {
|
|
67
70
|
if (!usage || usage.observedAt > now)
|
|
68
71
|
continue;
|
|
69
72
|
for (const w of usage.windows) {
|
|
70
73
|
const observedAt = w.observedAt ?? usage.observedAt;
|
|
71
74
|
if (w.window === 'seven_day' && w.scope === 'fable' &&
|
|
72
75
|
w.kind !== 'legacy-uncertain-seven-day' && observedAt <= now)
|
|
76
|
+
newestFable = Math.max(newestFable, observedAt);
|
|
77
|
+
if (w.window === 'seven_day' && w.scope === 'fable' &&
|
|
78
|
+
w.kind === 'seven_day_overage_included' && observedAt <= now)
|
|
73
79
|
newestGenuine = Math.max(newestGenuine, observedAt);
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
|
-
|
|
82
|
+
// Reporter versions and missing kinds cannot date raw uncertain windows.
|
|
83
|
+
// Keep newer evidence unprojected so its rejection still holds every family.
|
|
84
|
+
const projected = observations.map(usage => {
|
|
85
|
+
const filtered = usage && ({
|
|
86
|
+
...usage,
|
|
87
|
+
windows: usage.windows.filter(w => !(newestGenuine > -Infinity &&
|
|
88
|
+
w.window === 'seven_day' && w.scope === 'uncertain' &&
|
|
89
|
+
(w.observedAt ?? usage.observedAt) <= newestGenuine)),
|
|
90
|
+
});
|
|
91
|
+
return newestGenuine > -Infinity ? filtered : projectLegacyFableUsage(filtered);
|
|
92
|
+
});
|
|
93
|
+
const newestUncertain = new Map();
|
|
94
|
+
projected.forEach((usage, observation) => {
|
|
95
|
+
if (!usage || usage.observedAt > now)
|
|
96
|
+
return;
|
|
97
|
+
usage.windows.forEach((w, window) => {
|
|
98
|
+
const observedAt = w.observedAt ?? usage.observedAt;
|
|
99
|
+
if (w.scope !== 'uncertain' || observedAt > now)
|
|
100
|
+
return;
|
|
101
|
+
const previous = newestUncertain.get(w.window);
|
|
102
|
+
if (!previous || observedAt >= previous.observedAt)
|
|
103
|
+
newestUncertain.set(w.window, { observation, window, observedAt });
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
return projected.map((usage, observation) => usage && ({
|
|
77
107
|
...usage,
|
|
78
|
-
windows: usage.windows.filter(w
|
|
79
|
-
w.
|
|
108
|
+
windows: usage.windows.filter((w, window) => {
|
|
109
|
+
if (w.window === 'seven_day' && newestFable > -Infinity && (w.scope === 'fable' && w.kind === 'legacy-uncertain-seven-day' &&
|
|
110
|
+
(w.observedAt ?? usage.observedAt) < newestFable))
|
|
111
|
+
return false;
|
|
112
|
+
if (w.scope !== 'uncertain')
|
|
113
|
+
return true;
|
|
114
|
+
const newest = newestUncertain.get(w.window);
|
|
115
|
+
return newest?.observation === observation && newest.window === window;
|
|
116
|
+
}),
|
|
80
117
|
}));
|
|
81
118
|
}
|
|
82
119
|
/** Latest measurements plus durable genuine rejections; omission or success never clears a genuine hold. */
|
|
@@ -316,7 +353,7 @@ export function selectSubscriptionAccount(input) {
|
|
|
316
353
|
if (input.now < until)
|
|
317
354
|
blockers.push(until);
|
|
318
355
|
}
|
|
319
|
-
const usage =
|
|
356
|
+
const usage = supersedeLegacyFableProjections([state?.usage], input.now)[0];
|
|
320
357
|
// A provider rejection outlives snapshot freshness. Never score it as headroom.
|
|
321
358
|
if (usage && usage.observedAt <= input.now) {
|
|
322
359
|
for (const w of usage.windows) {
|
|
@@ -359,19 +396,22 @@ export function selectSubscriptionAccount(input) {
|
|
|
359
396
|
const current = known.find(candidate => candidate.account.accountId === input.currentAccountId);
|
|
360
397
|
if (current && known[0] && input.currentAccountTolerance !== undefined &&
|
|
361
398
|
known[0].score < current.score * (1 + input.currentAccountTolerance))
|
|
362
|
-
return { kind: 'selected', provenance: 'known', ...current };
|
|
399
|
+
return { kind: 'selected', provenance: 'known', ...current, pinned: true };
|
|
363
400
|
// Known-first is only this spawn's transitional fallback: background CLI
|
|
364
401
|
// probes measure non-selected unknowns so the next spawn can score them.
|
|
402
|
+
// Preserve pin provenance for equal scores (including zero) and unknowns too.
|
|
365
403
|
if (known[0])
|
|
366
|
-
return { kind: 'selected', provenance: 'known', ...known[0]
|
|
404
|
+
return { kind: 'selected', provenance: 'known', ...known[0],
|
|
405
|
+
...(known[0].account.accountId === input.currentAccountId ? { pinned: true } : {}) };
|
|
367
406
|
if (unknown[0])
|
|
368
|
-
return { kind: 'selected', provenance: 'unknown', ...unknown[0]
|
|
407
|
+
return { kind: 'selected', provenance: 'unknown', ...unknown[0],
|
|
408
|
+
...(input.currentAccountTolerance !== undefined && unknown[0].account.accountId === input.currentAccountId ? { pinned: true } : {}) };
|
|
369
409
|
return { kind: 'deferred', eligibleAt: Math.min(...deadlines) };
|
|
370
410
|
}
|
|
371
411
|
/** Explain actual blockers with the same scope, clocks and thresholds as selection. */
|
|
372
412
|
export function blockingUsageWindows(account, state, model, now) {
|
|
373
413
|
const pool = { kind: 'ready', accounts: [account] };
|
|
374
|
-
const usage =
|
|
414
|
+
const usage = supersedeLegacyFableProjections([state?.usage], now)[0];
|
|
375
415
|
const windows = [];
|
|
376
416
|
for (const window of usage?.windows ?? []) {
|
|
377
417
|
const result = selectSubscriptionAccount({ pool, states: { [account.accountId]: { usage: { ...usage, windows: [window] } } }, model, now });
|
|
@@ -3,7 +3,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { parseClaudeRateLimitInfo } from './claude-rate-limit.js';
|
|
6
|
-
import { normalizePassiveUsage, mergeUsageObservations, record, reactiveEligibleAt, family } from './subscription-selection.js';
|
|
6
|
+
import { normalizePassiveUsage, mergePersistedUsageObservations, mergeUsageObservations, record, reactiveEligibleAt, family } from './subscription-selection.js';
|
|
7
7
|
export function usageStateForSelection(state, now) {
|
|
8
8
|
return {
|
|
9
9
|
usage: mergeUsageObservations([
|
|
@@ -186,9 +186,10 @@ export class FileSubscriptionUsageStore {
|
|
|
186
186
|
const state = safeVersion(await this.#load(path, {}));
|
|
187
187
|
if (state.passive && state.passive.observedAt > observedAt && !options?.expireFutureObservations)
|
|
188
188
|
return;
|
|
189
|
-
|
|
189
|
+
// Retain Fable observation clocks even when the latest report omits its meter.
|
|
190
|
+
const previous = state.usage ?? usageStateForSelection({ ...state, reactive: [] }, observedAt).usage;
|
|
190
191
|
state.passive = { rateLimitInfo: safeRateLimitInfo(rateLimitInfo), observedAt };
|
|
191
|
-
state.usage =
|
|
192
|
+
state.usage = mergePersistedUsageObservations(previous, normalizePassiveUsage(state.passive.rateLimitInfo, observedAt), observedAt);
|
|
192
193
|
await this.#save(path, state);
|
|
193
194
|
});
|
|
194
195
|
}
|
|
@@ -262,9 +263,10 @@ export class FileSubscriptionUsageStore {
|
|
|
262
263
|
const state = safeVersion(await this.#load(path, {}));
|
|
263
264
|
// Network completion order must not discard a newer stored snapshot.
|
|
264
265
|
const mergeAt = Math.max(observation.observedAt, state.usage?.observedAt ?? 0, state.passive?.observedAt ?? 0);
|
|
265
|
-
|
|
266
|
+
// Retain Fable observation clocks even when the latest report omits its meter.
|
|
267
|
+
const previous = state.usage ?? usageStateForSelection({ ...state, reactive: [] }, mergeAt).usage;
|
|
266
268
|
state.probe = safeProbe(observation);
|
|
267
|
-
state.usage =
|
|
269
|
+
state.usage = mergePersistedUsageObservations(previous, state.probe.usage, mergeAt);
|
|
268
270
|
await this.#save(path, state);
|
|
269
271
|
});
|
|
270
272
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.6
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@teamvibe/poller",
|
|
9
|
-
"version": "0.8.6
|
|
9
|
+
"version": "0.8.6",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@aws-sdk/client-dynamodb": "3.1108.0",
|
|
12
12
|
"@aws-sdk/client-sqs": "3.1108.0",
|