@teamvibe/poller 0.8.7-rc.1 → 0.8.8
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/claude-spawner.d.ts +2 -0
- package/dist/claude-spawner.js +42 -12
- package/dist/event-queue.js +11 -0
- package/dist/inbox-manager.d.ts +4 -0
- package/dist/inbox-manager.js +29 -0
- package/dist/inbox-watcher.js +16 -3
- package/dist/poller.js +33 -9
- package/dist/pool-defer.d.ts +24 -1
- package/dist/pool-defer.js +101 -5
- package/dist/subscription-runtime.d.ts +2 -0
- package/dist/subscription-runtime.js +8 -2
- package/dist/subscription-selection.d.ts +21 -2
- package/dist/subscription-selection.js +91 -14
- package/dist/subscription-usage-store.d.ts +6 -1
- package/dist/subscription-usage-store.js +246 -13
- package/dist/types.d.ts +2 -0
- package/dist/usage-limit-messages.d.ts +3 -0
- package/dist/usage-limit-messages.js +22 -0
- package/dist/usage-probe-job.d.ts +2 -0
- package/dist/usage-probe-job.js +36 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UsageWindow } from './subscription-selection.js';
|
|
1
2
|
/** Owner-facing account policy location. */
|
|
2
3
|
export declare const FULL_QUOTA_SETTINGS_PATH = "Connections (account menu \u2192 Use until exhausted)";
|
|
3
4
|
export interface UsageResetTiming {
|
|
@@ -18,6 +19,8 @@ export interface PoolUsageLimitNotice extends UsageResetTiming {
|
|
|
18
19
|
cause: PoolDeferCause;
|
|
19
20
|
thresholdDeferred?: boolean;
|
|
20
21
|
accounts?: readonly UsageLimitAccount[];
|
|
22
|
+
eligibleAt?: number;
|
|
23
|
+
blockedWindows?: readonly Pick<UsageWindow, 'window' | 'scope'>[];
|
|
21
24
|
}
|
|
22
25
|
export declare function formatUsageReset(input: UsageResetTiming): string | undefined;
|
|
23
26
|
export declare function formatPoolUsageLimitMessage(input: PoolUsageLimitNotice): string;
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { USAGE_THRESHOLD_PERCENT } from './subscription-selection.js';
|
|
2
2
|
/** Owner-facing account policy location. */
|
|
3
3
|
export const FULL_QUOTA_SETTINGS_PATH = 'Connections (account menu → Use until exhausted)';
|
|
4
|
+
/** Quota notices include a calendar date and zone across multi-day holds. */
|
|
5
|
+
function formatQuotaTime(at, timeZone) {
|
|
6
|
+
try {
|
|
7
|
+
const parts = new Intl.DateTimeFormat('en-GB', { timeZone: timeZone || 'UTC', weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', timeZoneName: 'short' }).formatToParts(at);
|
|
8
|
+
const part = (type) => parts.find(p => p.type === type)?.value;
|
|
9
|
+
return `${part('weekday')} ${part('day')} ${part('month')} ${part('hour')}:${part('minute')} ${part('timeZoneName')}`;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return formatQuotaTime(at, 'UTC');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
export function formatUsageReset(input) {
|
|
5
16
|
if (input.resetAt === undefined || !Number.isFinite(input.resetAt))
|
|
6
17
|
return undefined;
|
|
@@ -43,6 +54,17 @@ export function formatPoolUsageLimitMessage(input) {
|
|
|
43
54
|
const labels = headroom.filter(a => a.label && a.remaining > 0).map(a => `${a.label} ${Number(a.remaining.toFixed(2))} %`).join(', ');
|
|
44
55
|
return `⏳ All Claude accounts in this workspace are above ${USAGE_THRESHOLD_PERCENT} % of their usage limit, so I'm waiting for the next reset${reset ? ` at ${reset}` : ''}. Your message is saved and will run automatically.\n💡 There is still some capacity left${labels ? ` (${labels})` : ''}. A workspace owner can allow the full quota per account in ${FULL_QUOTA_SETTINGS_PATH}.`;
|
|
45
56
|
}
|
|
57
|
+
if (input.blockedWindows?.length) {
|
|
58
|
+
const windows = [...new Map(input.blockedWindows.map(w => [`${w.window}:${w.scope}`, w])).values()];
|
|
59
|
+
const names = windows.map(w => `${w.scope === 'global' ? 'global' : w.scope === 'uncertain' ? 'Claude' : w.scope[0].toUpperCase() + w.scope.slice(1)} ${w.window === 'seven_day' ? 'weekly' : 'five-hour'} quota`);
|
|
60
|
+
const global = windows.some(w => w.scope === 'global' || w.scope === 'uncertain');
|
|
61
|
+
const resets = input.resetAt !== undefined ? `resets ${formatQuotaTime(input.resetAt, input.timeZone)}` : 'reset time unavailable';
|
|
62
|
+
const retry = input.eligibleAt !== undefined ? `I'll retry automatically at ${formatQuotaTime(input.eligibleAt, input.timeZone)}` : "I'll retry automatically after the next reset";
|
|
63
|
+
const blocked = windows.length === 1 && !global
|
|
64
|
+
? `${names[0]} is exhausted on every account in this workspace (${resets}).`
|
|
65
|
+
: `All Claude accounts in this workspace have hit their usage limit (${names.join(', ')}; ${resets}).`;
|
|
66
|
+
return `⏳ ${blocked} Your message is saved. ${retry}${global ? '.' : '; other models keep working.'}`;
|
|
67
|
+
}
|
|
46
68
|
return `⏳ All Claude accounts in this workspace have hit their usage limit. Your message is saved and will run automatically after ${reset ? `the earliest reset at ${reset}` : 'the next reset'}.`;
|
|
47
69
|
}
|
|
48
70
|
export function formatLegacyUsageLimitMessage(input) {
|
|
@@ -6,6 +6,8 @@ import { type SubscriptionUsageStore } from './subscription-usage-store.js';
|
|
|
6
6
|
import type { UsageProbeFailureReason as Reason } from './usage-probe-contract.js';
|
|
7
7
|
type ProbeResult = Reason | 'ok' | 'cancelled';
|
|
8
8
|
export interface UsageProbeOptions {
|
|
9
|
+
/** Poller-owned wake-up; keeps shared probe imports independent of poller configuration. */
|
|
10
|
+
onHeadroom?: (workspaceId: string) => void;
|
|
9
11
|
store: SubscriptionUsageStore;
|
|
10
12
|
cliPath: string;
|
|
11
13
|
/** Use the session's resolved model for local probes; platform jobs default to CLI behavior. */
|
package/dist/usage-probe-job.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { mkdtemp, rm, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { normalizePassiveUsage } from './subscription-selection.js';
|
|
5
|
+
import { selectSubscriptionAccount, normalizePassiveUsage, reactiveUsageWindow } from './subscription-selection.js';
|
|
6
6
|
import { safeRateLimitInfo, usageStateForSelection, } from './subscription-usage-store.js';
|
|
7
7
|
import { parseClaudeRateLimitInfo, } from './claude-rate-limit.js';
|
|
8
8
|
import { formatPoolUsageWindows, poolInfo, poolLabel } from './pool-logging.js';
|
|
@@ -210,17 +210,51 @@ export async function runUsageProbe(account, options, context = {}) {
|
|
|
210
210
|
return result;
|
|
211
211
|
const now = Date.now();
|
|
212
212
|
const safe = safeRateLimitInfo(info);
|
|
213
|
+
const normalized = normalizePassiveUsage(safe, now);
|
|
213
214
|
const usage = {
|
|
214
|
-
...
|
|
215
|
+
...normalized,
|
|
215
216
|
source: 'probe',
|
|
217
|
+
windows: normalized.windows.map(w => ({ ...w,
|
|
218
|
+
...(w.status === undefined && w.percent !== undefined && (safe.status === 'allowed' || safe.status === 'allowed_warning') ? { status: safe.status } : {}),
|
|
219
|
+
})),
|
|
216
220
|
};
|
|
217
221
|
measuredWindows = formatPoolUsageWindows(usage.windows);
|
|
222
|
+
const before = usageStateForSelection(await options.store.read(account), now);
|
|
218
223
|
await options.store.finishProbe(account, {
|
|
219
224
|
status: 'ok',
|
|
220
225
|
observedAt: now,
|
|
221
226
|
usage,
|
|
222
227
|
});
|
|
223
228
|
const stored = await options.store.read(account);
|
|
229
|
+
const after = usageStateForSelection(stored, now);
|
|
230
|
+
// Keep the combined comparison, and compare each rejected scope/window
|
|
231
|
+
// independently so an unrelated, later hold cannot hide an improvement.
|
|
232
|
+
const pairs = new Map();
|
|
233
|
+
for (const state of [before, after])
|
|
234
|
+
for (const w of [...(state.usage?.windows ?? []), ...(state.reactive ?? []).map(reactiveUsageWindow)]) {
|
|
235
|
+
if (w.status === 'rejected')
|
|
236
|
+
pairs.set(`${w.scope}.${w.window}`, w);
|
|
237
|
+
}
|
|
238
|
+
const eligibility = (state, pair) => {
|
|
239
|
+
const matches = (w) => !pair || w.scope === pair.scope && w.window === pair.window;
|
|
240
|
+
const scoped = pair ? {
|
|
241
|
+
usage: state.usage ? { ...state.usage, windows: state.usage.windows.filter(matches) } : undefined,
|
|
242
|
+
reactive: state.reactive?.filter(r => matches(reactiveUsageWindow(r))),
|
|
243
|
+
} : state;
|
|
244
|
+
return selectSubscriptionAccount({ pool: { kind: 'ready', accounts: [account] }, states: { [account.accountId]: scoped }, model: pair?.scope, now });
|
|
245
|
+
};
|
|
246
|
+
const improved = [undefined, ...pairs.values()].some(pair => {
|
|
247
|
+
const previous = eligibility(before, pair);
|
|
248
|
+
const current = eligibility(after, pair);
|
|
249
|
+
return current.kind === 'selected' && (!pair || previous.kind === 'deferred') ||
|
|
250
|
+
current.kind === 'deferred' && previous.kind === 'deferred' && current.eligibleAt < previous.eligibleAt;
|
|
251
|
+
});
|
|
252
|
+
if (improved) {
|
|
253
|
+
try {
|
|
254
|
+
options.onHeadroom?.(account.workspaceId);
|
|
255
|
+
}
|
|
256
|
+
catch { /* Wake-up failures must not discard a measured observation. */ }
|
|
257
|
+
}
|
|
224
258
|
// Requestless local observations use real channel authorization; the
|
|
225
259
|
// workspace-job namespace is reserved for platform request IDs.
|
|
226
260
|
options.emit('pool.usage_observed', context.channelId ?? 'usage_probe', {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@teamvibe/poller",
|
|
9
|
-
"version": "0.8.
|
|
9
|
+
"version": "0.8.8",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@aws-sdk/client-dynamodb": "3.1108.0",
|
|
12
12
|
"@aws-sdk/client-sqs": "3.1108.0",
|