@teamvibe/poller 0.8.5 → 0.8.6-rc.2
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 +1 -0
- package/dist/claude-spawner.js +15 -2
- package/dist/pool-logging.d.ts +2 -1
- package/dist/pool-logging.js +5 -2
- package/dist/subscription-pool.d.ts +1 -0
- package/dist/subscription-pool.js +1 -1
- package/dist/subscription-runtime.d.ts +10 -1
- package/dist/subscription-runtime.js +97 -20
- package/dist/subscription-selection.d.ts +6 -2
- package/dist/subscription-selection.js +14 -3
- package/dist/subscription-usage-store.d.ts +3 -0
- package/dist/subscription-usage-store.js +18 -0
- package/dist/usage-probe-job.d.ts +19 -2
- package/dist/usage-probe-job.js +71 -17
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/claude-spawner.d.ts
CHANGED
|
@@ -248,6 +248,7 @@ export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: S
|
|
|
248
248
|
export declare function spawnPoolClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, selectedDirectory: BrainDirectory | string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void, typingIndicator?: TypingIndicator, poolRuntime?: PoolRuntimeOptions): Promise<SpawnResult & {
|
|
249
249
|
newSessionId?: string;
|
|
250
250
|
}>;
|
|
251
|
+
export declare function isSubscriptionAccountRunning(account: Pick<SubscriptionAccount, 'workspaceId' | 'accountId'>): boolean;
|
|
251
252
|
export declare function getActiveProcessCount(): number;
|
|
252
253
|
export declare function getLastSessionEndTs(): number;
|
|
253
254
|
/**
|
package/dist/claude-spawner.js
CHANGED
|
@@ -16,7 +16,7 @@ import { fetchSecretsForSpawn, buildSecretsReminder } from './secrets-client.js'
|
|
|
16
16
|
import { fetchConfigForSpawn } from './config-client.js';
|
|
17
17
|
import { parseClaudeRateLimitInfo } from './claude-rate-limit.js';
|
|
18
18
|
import { parseClaudeQuotaFailure } from './claude-quota.js';
|
|
19
|
-
import { accountPin, preparePool, fetchRuntimePool, validateAccount, poolDeferredError, PoolDeferredError } from './subscription-runtime.js';
|
|
19
|
+
import { abortLocalProbe, accountPin, preparePool, fetchRuntimePool, validateAccount, poolDeferredError, PoolDeferredError } from './subscription-runtime.js';
|
|
20
20
|
import { selectPoolForResolvedModel } from './subscription-pool.js';
|
|
21
21
|
import { isQuotaPaused, QuotaPausedError } from './quota-pause.js';
|
|
22
22
|
import { redactStartupText } from './startup-auth.js';
|
|
@@ -1023,6 +1023,10 @@ async function runClaudeCode(msg, sessionLog, cwd, directory, sessionId, isFirst
|
|
|
1023
1023
|
]));
|
|
1024
1024
|
};
|
|
1025
1025
|
warnTokenAliasSettings(spawnEnv['CLAUDE_CONFIG_DIR'], cwd, directory.path, sessionLog);
|
|
1026
|
+
// Preparation awaits remote calls: cancel again at the actual launch site.
|
|
1027
|
+
// No await between cancellation, spawn and running-account registration.
|
|
1028
|
+
if (runtime?.account)
|
|
1029
|
+
abortLocalProbe(runtime.account);
|
|
1026
1030
|
const proc = spawn(config.CLAUDE_CLI_PATH, args, {
|
|
1027
1031
|
cwd,
|
|
1028
1032
|
env: spawnEnv,
|
|
@@ -1040,6 +1044,8 @@ async function runClaudeCode(msg, sessionLog, cwd, directory, sessionId, isFirst
|
|
|
1040
1044
|
// Track active process by message ID and thread ID
|
|
1041
1045
|
activeProcesses.set(msg.id, proc);
|
|
1042
1046
|
activeProcessesByThread.set(msg.thread_id, proc);
|
|
1047
|
+
if (runtime?.account)
|
|
1048
|
+
activeSubscriptionAccounts.set(proc, runtime.account);
|
|
1043
1049
|
for (const chunk of spawnStdinPlan.stdinChunks) {
|
|
1044
1050
|
proc.stdin?.write(chunk);
|
|
1045
1051
|
}
|
|
@@ -1310,6 +1316,7 @@ async function runClaudeCode(msg, sessionLog, cwd, directory, sessionId, isFirst
|
|
|
1310
1316
|
clearNotificationTimers();
|
|
1311
1317
|
activeProcesses.delete(msg.id);
|
|
1312
1318
|
activeProcessesByThread.delete(msg.thread_id);
|
|
1319
|
+
activeSubscriptionAccounts.delete(proc);
|
|
1313
1320
|
lastSessionEndTs = Date.now();
|
|
1314
1321
|
// #73 gap 2 (flagged independently by two reviews): `close` firing means
|
|
1315
1322
|
// `claude` itself has exited, but its MCP server children — spawned
|
|
@@ -1401,6 +1408,7 @@ async function runClaudeCode(msg, sessionLog, cwd, directory, sessionId, isFirst
|
|
|
1401
1408
|
clearInterval(idleWatchdog);
|
|
1402
1409
|
activeProcesses.delete(msg.id);
|
|
1403
1410
|
activeProcessesByThread.delete(msg.thread_id);
|
|
1411
|
+
activeSubscriptionAccounts.delete(proc);
|
|
1404
1412
|
lastSessionEndTs = Date.now();
|
|
1405
1413
|
emitError('session', `Claude Code process spawn error: ${redactOutput(error.message)}`, {
|
|
1406
1414
|
channelId: msg.teamvibe.channelId,
|
|
@@ -1568,7 +1576,7 @@ export async function spawnPoolClaudeCode(msg, sessionLog, selectedDirectory, se
|
|
|
1568
1576
|
const resolved = fetchedPool?.kind !== 'no_accounts_configured' && poolRuntime?.pin?.workspaceId === msg.teamvibe.workspaceId
|
|
1569
1577
|
? { model: poolRuntime.pin.model, modelSource: poolRuntime.pin.modelSource }
|
|
1570
1578
|
: resolveModel(typeof settings['model'] === 'string' ? settings['model'] : undefined, scoped ?? {}, config.CLAUDE_MODEL);
|
|
1571
|
-
const prepared = poolRuntime ? await preparePool(msg, resolved, poolRuntime.pin, fetchedPool, sessionLog) : undefined;
|
|
1579
|
+
const prepared = poolRuntime ? await preparePool(msg, resolved, poolRuntime.pin, fetchedPool, sessionLog, { cliPath: config.CLAUDE_CLI_PATH, isAtCapacity, emit: emitEvent, log: line => sessionLog.info(line) }) : undefined;
|
|
1572
1580
|
const modelNormalizedFrom = scoped?.model === resolved.model && scoped?.modelSource === resolved.modelSource
|
|
1573
1581
|
? scoped?.modelNormalizedFrom : undefined;
|
|
1574
1582
|
const runtime = poolRuntime ? { resolved, modelNormalizedFrom, claudeConfigDir, account: prepared?.account, shouldStop: poolRuntime.shouldStop, resumePending: fetchedPool?.kind !== 'no_accounts_configured' && poolRuntime.pin?.resumePending === true } : undefined;
|
|
@@ -1678,6 +1686,7 @@ export async function spawnPoolClaudeCode(msg, sessionLog, selectedDirectory, se
|
|
|
1678
1686
|
if (!effectiveSessionId)
|
|
1679
1687
|
throw new PoolDeferredError('infrastructure');
|
|
1680
1688
|
const fromAccount = prepared.account;
|
|
1689
|
+
abortLocalProbe(selected.account);
|
|
1681
1690
|
poolInfo(sessionLog, `switch from=${poolLabel(fromAccount)} to=${poolLabel(selected.account)} session=${JSON.stringify(effectiveSessionId)}`);
|
|
1682
1691
|
prepared.account = selected.account;
|
|
1683
1692
|
prepared.selection = selected;
|
|
@@ -1733,6 +1742,10 @@ export async function spawnPoolClaudeCode(msg, sessionLog, selectedDirectory, se
|
|
|
1733
1742
|
}
|
|
1734
1743
|
const activeProcesses = new Map();
|
|
1735
1744
|
const activeProcessesByThread = new Map();
|
|
1745
|
+
const activeSubscriptionAccounts = new Map();
|
|
1746
|
+
export function isSubscriptionAccountRunning(account) {
|
|
1747
|
+
return [...activeSubscriptionAccounts.values()].some(active => active.workspaceId === account.workspaceId && active.accountId === account.accountId);
|
|
1748
|
+
}
|
|
1736
1749
|
// tv#317: when the last `claude` process ended, so the idle-restart gate can require
|
|
1737
1750
|
// a quiet period since real work last happened. Starts at module load (poller boot) so
|
|
1738
1751
|
// a poller that never runs a session still becomes eligible once IDLE_RESTART_MIN_UPTIME_MS
|
package/dist/pool-logging.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PoolFetchDiagnostics } from './subscription-pool-client.js';
|
|
2
2
|
import type { SessionLogger } from './logger.js';
|
|
3
3
|
import type { ClaudeRateLimitInfo } from './claude-rate-limit.js';
|
|
4
|
-
import { type AccountIdentity, type AccountUsageState, type PoolFetchResult, type SelectionOutcome } from './subscription-selection.js';
|
|
4
|
+
import { type AccountIdentity, type AccountUsageState, type PoolFetchResult, type SelectionOutcome, type UsageWindow } from './subscription-selection.js';
|
|
5
5
|
export type PoolInfoLogger = Pick<SessionLogger, 'info'>;
|
|
6
6
|
type LabeledAccount = AccountIdentity & {
|
|
7
7
|
label?: string;
|
|
@@ -15,6 +15,7 @@ export declare function poolErrorClass(error: unknown): string;
|
|
|
15
15
|
export declare function poolErrorDiagnostic(error: unknown): string;
|
|
16
16
|
export declare function logPoolFetch(log: PoolInfoLogger, pool: PoolFetchResult & PoolFetchDiagnostics): void;
|
|
17
17
|
export declare function logPoolSelection<A extends LabeledAccount>(log: PoolInfoLogger, pool: PoolFetchResult<A>, states: Readonly<Record<string, AccountUsageState>>, model: string | undefined, now: number, selected: SelectionOutcome<A>, logAllExhausted?: boolean): void;
|
|
18
|
+
export declare function formatPoolUsageWindows(windows: readonly UsageWindow[], includeScope?: boolean): string;
|
|
18
19
|
/** Log the real store promise without changing its rejection or retry behavior. */
|
|
19
20
|
export declare function logPoolUsageWrite(log: PoolInfoLogger, account: {
|
|
20
21
|
label?: string;
|
package/dist/pool-logging.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { redactStartupText } from './
|
|
1
|
+
import { redactStartupText } from './redact-text.js';
|
|
2
2
|
import { normalizePassiveUsage, selectSubscriptionAccount } from './subscription-selection.js';
|
|
3
3
|
import { safeRateLimitInfo } from './subscription-usage-store.js';
|
|
4
4
|
// Never serialize accounts, credentials, provider strings or caught errors.
|
|
@@ -87,9 +87,12 @@ export function logPoolSelection(log, pool, states, model, now, selected, logAll
|
|
|
87
87
|
poolInfo(log, `selected account=${poolLabel(selected.account)} model=${JSON.stringify(model ?? 'CLI default')} score=${score}`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
export function formatPoolUsageWindows(windows, includeScope = true) {
|
|
91
|
+
return windows.map(w => `${w.window}${includeScope ? `/${w.scope}` : ''}:${w.percent === undefined ? 'unknown' : `${Number(w.percent.toFixed(2))}%`}`).join(',') || 'none';
|
|
92
|
+
}
|
|
90
93
|
/** Log the real store promise without changing its rejection or retry behavior. */
|
|
91
94
|
export async function logPoolUsageWrite(log, account, info, write, source = 'passive') {
|
|
92
|
-
const windows = info ? normalizePassiveUsage(safeRateLimitInfo(info), Date.now()).windows
|
|
95
|
+
const windows = formatPoolUsageWindows(info ? normalizePassiveUsage(safeRateLimitInfo(info), Date.now()).windows : [], source === 'passive');
|
|
93
96
|
try {
|
|
94
97
|
await write();
|
|
95
98
|
poolInfo(log, `usage write account=${poolLabel(account)} result=ok windows=${windows || 'none'} source=${source}`);
|
|
@@ -20,6 +20,7 @@ export declare function selectPoolForResolvedModel<A extends AccountIdentity & {
|
|
|
20
20
|
store: SubscriptionUsageStore;
|
|
21
21
|
now: number;
|
|
22
22
|
currentAccountId?: string;
|
|
23
|
+
currentAccountTolerance?: number;
|
|
23
24
|
log?: PoolInfoLogger;
|
|
24
25
|
logAllExhausted?: boolean;
|
|
25
26
|
}): Promise<ResolvedPoolSelection<A>>;
|
|
@@ -30,7 +30,7 @@ export async function selectPoolForResolvedModel(resolvedModel, input) {
|
|
|
30
30
|
catch (error) {
|
|
31
31
|
return { kind: 'usage_unavailable', error, ...resolvedModel };
|
|
32
32
|
}
|
|
33
|
-
const selected = selectSubscriptionAccount({ pool: input.pool, states, model: resolvedModel.model, now: input.now, currentAccountId: input.currentAccountId });
|
|
33
|
+
const selected = selectSubscriptionAccount({ pool: input.pool, states, model: resolvedModel.model, now: input.now, currentAccountId: input.currentAccountId, currentAccountTolerance: input.currentAccountTolerance });
|
|
34
34
|
if (input.log)
|
|
35
35
|
logPoolSelection(input.log, input.pool, states, resolvedModel.model, input.now, selected, input.logAllExhausted);
|
|
36
36
|
return { ...selected, ...resolvedModel };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type UsageProbeOptions } from './usage-probe-job.js';
|
|
1
2
|
import { type PoolInfoLogger } from './pool-logging.js';
|
|
2
3
|
import { type PoolFetchDiagnostics, type SubscriptionAccount } from './subscription-pool-client.js';
|
|
3
4
|
import { type ResolvedPoolModel } from './subscription-pool.js';
|
|
@@ -51,7 +52,15 @@ export interface PoolRuntimeOptions {
|
|
|
51
52
|
}
|
|
52
53
|
export declare function subscriptionUsageStore(): FileSubscriptionUsageStore;
|
|
53
54
|
export declare function fetchRuntimePool(msg: TeamVibeQueueMessage): Promise<PoolFetchResult<SubscriptionAccount> & PoolFetchDiagnostics>;
|
|
54
|
-
|
|
55
|
+
/** Minimum gap between local measurements of one workspace/account/credential. */
|
|
56
|
+
export declare const LOCAL_PROBE_MIN_INTERVAL_MS = 60000;
|
|
57
|
+
/** Bound background CLI load across every workspace in this poller process. */
|
|
58
|
+
export declare const LOCAL_PROBE_MAX_CONCURRENT = 3;
|
|
59
|
+
/** A local measurement has its own deadline, independent of session lifetime. */
|
|
60
|
+
export declare const LOCAL_PROBE_TIMEOUT_MS = 30000;
|
|
61
|
+
/** Synchronous: kill the isolated child and free its slot before session spawn. */
|
|
62
|
+
export declare function abortLocalProbe(account: SubscriptionAccount): void;
|
|
63
|
+
export declare function preparePool(msg: TeamVibeQueueMessage, resolved: ResolvedPoolModel, pin?: SubscriptionPin, fetched?: PoolFetchResult<SubscriptionAccount>, log?: PoolInfoLogger, probeOptions?: Omit<UsageProbeOptions, 'store'>): Promise<{
|
|
55
64
|
account: SubscriptionAccount;
|
|
56
65
|
store: FileSubscriptionUsageStore;
|
|
57
66
|
pool: {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { isAtCapacity, isSubscriptionAccountRunning } from './claude-spawner.js';
|
|
2
|
+
import { emitEvent } from './event-queue.js';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
import { runUsageProbe } from './usage-probe-job.js';
|
|
1
5
|
import { poolInfo, poolLabel, poolErrorDiagnostic } from './pool-logging.js';
|
|
2
6
|
import { emitCandidateUsage } from './pool-telemetry.js';
|
|
3
7
|
import { join } from 'node:path';
|
|
@@ -6,7 +10,7 @@ import { getPollerId } from './auth-provider.js';
|
|
|
6
10
|
import { fetchSubscriptionPool } from './subscription-pool-client.js';
|
|
7
11
|
import { selectPoolForResolvedModel } from './subscription-pool.js';
|
|
8
12
|
import { FileSubscriptionUsageStore, usageStateForSelection } from './subscription-usage-store.js';
|
|
9
|
-
import { normalizePassiveUsage, selectSubscriptionAccount, usageWindowExpiresAt, reactiveEligibleAt, USAGE_THRESHOLD_PERCENT, POOL_RECHECK_MS } from './subscription-selection.js';
|
|
13
|
+
import { normalizePassiveUsage, selectSubscriptionAccount, usageWindowExpiresAt, reactiveEligibleAt, PIN_HEADROOM_SWITCH_RELATIVE, USAGE_THRESHOLD_PERCENT, POOL_RECHECK_MS } from './subscription-selection.js';
|
|
10
14
|
export const POOL_UNAVAILABLE_RECHECK_MS = 60_000;
|
|
11
15
|
export class PoolDeferredError extends Error {
|
|
12
16
|
cause;
|
|
@@ -31,31 +35,104 @@ export function subscriptionUsageStore() {
|
|
|
31
35
|
export async function fetchRuntimePool(msg) {
|
|
32
36
|
return fetchSubscriptionPool({ pollerId: getPollerId() || '', workspaceId: msg.teamvibe.workspaceId, channelId: msg.teamvibe.channelId }, { apiUrl: config.TEAMVIBE_API_URL, pollerToken: config.TEAMVIBE_POLLER_TOKEN || '' });
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
/** Minimum gap between local measurements of one workspace/account/credential. */
|
|
39
|
+
export const LOCAL_PROBE_MIN_INTERVAL_MS = 60_000;
|
|
40
|
+
/** Bound background CLI load across every workspace in this poller process. */
|
|
41
|
+
export const LOCAL_PROBE_MAX_CONCURRENT = 3;
|
|
42
|
+
/** A local measurement has its own deadline, independent of session lifetime. */
|
|
43
|
+
export const LOCAL_PROBE_TIMEOUT_MS = 30_000;
|
|
44
|
+
const localProbesInFlight = new Map();
|
|
45
|
+
/** Synchronous: kill the isolated child and free its slot before session spawn. */
|
|
46
|
+
export function abortLocalProbe(account) {
|
|
47
|
+
for (const [key, probe] of localProbesInFlight) {
|
|
48
|
+
// A credential rotation must not leave an older probe on the same account.
|
|
49
|
+
if (probe.account.workspaceId !== account.workspaceId || probe.account.accountId !== account.accountId)
|
|
50
|
+
continue;
|
|
51
|
+
localProbesInFlight.delete(key);
|
|
52
|
+
probe.controller.abort();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function probeUnknownAccounts(accounts, selectedAccountId, resolved, store, options, channelId) {
|
|
56
|
+
for (const account of accounts) {
|
|
57
|
+
if (!account.enabled || account.accountId === selectedAccountId || isSubscriptionAccountRunning(account))
|
|
58
|
+
continue;
|
|
59
|
+
// Fire-and-forget ALL probe work, including disk claims and cleanup. No
|
|
60
|
+
// session signal/shouldStop is passed: the observation outlives its trigger.
|
|
61
|
+
void (async () => {
|
|
62
|
+
const key = JSON.stringify([account.workspaceId, account.accountId, account.credentialVersion]);
|
|
63
|
+
let probe;
|
|
64
|
+
try {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
const state = usageStateForSelection(await store.read(account), now);
|
|
67
|
+
const candidate = selectSubscriptionAccount({ pool: { kind: 'ready', accounts: [account] }, states: { [account.accountId]: state }, model: resolved.model, now });
|
|
68
|
+
if (candidate.kind !== 'selected' || candidate.provenance !== 'unknown')
|
|
69
|
+
return;
|
|
70
|
+
if (isSubscriptionAccountRunning(account))
|
|
71
|
+
return;
|
|
72
|
+
if (localProbesInFlight.has(key)) {
|
|
73
|
+
poolInfo({ info: options.log }, `local probe account=${poolLabel(account)} result=skipped reason=claimed`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (options.isAtCapacity() || localProbesInFlight.size >= LOCAL_PROBE_MAX_CONCURRENT)
|
|
77
|
+
return;
|
|
78
|
+
// Reserve before asynchronous claiming, so concurrent preparePool calls
|
|
79
|
+
// cannot exceed the process-wide limit while claims are being written.
|
|
80
|
+
probe = { account, controller: new AbortController() };
|
|
81
|
+
localProbesInFlight.set(key, probe);
|
|
82
|
+
const claimed = await store.claimLocalProbe(account, now, LOCAL_PROBE_MIN_INTERVAL_MS);
|
|
83
|
+
if (probe.controller.signal.aborted) {
|
|
84
|
+
poolInfo({ info: options.log }, `local probe account=${poolLabel(account)} result=cancelled reason=account selected`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!claimed) {
|
|
88
|
+
poolInfo({ info: options.log }, `local probe account=${poolLabel(account)} result=skipped reason=claimed`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Capacity may change while the durable claim is written.
|
|
92
|
+
if (options.isAtCapacity()) {
|
|
93
|
+
poolInfo({ info: options.log }, `local probe account=${poolLabel(account)} result=cli_at_capacity`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await runUsageProbe(account, { ...options, model: resolved.model, timeoutMs: LOCAL_PROBE_TIMEOUT_MS }, {
|
|
97
|
+
channelId, signal: probe.controller.signal,
|
|
98
|
+
onMeasurementComplete: () => {
|
|
99
|
+
if (localProbesInFlight.get(key) === probe)
|
|
100
|
+
localProbesInFlight.delete(key);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
poolInfo({ info: options.log }, `local probe account=${poolLabel(account)} result=${probe?.controller.signal.aborted ? 'cancelled reason=account selected' : 'unknown'}`);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
// An aborted run must not delete a newer reservation for the same key.
|
|
109
|
+
if (probe && localProbesInFlight.get(key) === probe)
|
|
110
|
+
localProbesInFlight.delete(key);
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export async function preparePool(msg, resolved, pin, fetched, log, probeOptions) {
|
|
35
116
|
const pool = fetched ?? await fetchRuntimePool(msg);
|
|
36
117
|
const store = subscriptionUsageStore();
|
|
37
118
|
if (pool.kind === 'ready')
|
|
38
119
|
await emitCandidateUsage(msg, pool.accounts, store);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (selected.kind === 'selected') {
|
|
44
|
-
if (log)
|
|
45
|
-
for (const other of pool.accounts) {
|
|
46
|
-
if (other.accountId !== account.accountId)
|
|
47
|
-
poolInfo(log, `skipped account=${poolLabel(other)} reason=session pinned elsewhere`);
|
|
48
|
-
}
|
|
49
|
-
return { account, store, pool, selection: selected };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const selected = await selectPoolForResolvedModel(resolved, { workspaceId: msg.teamvibe.workspaceId, pool, store, now: Date.now(), log, currentAccountId: pin?.workspaceId === msg.teamvibe.workspaceId ? pin.accountId : undefined });
|
|
120
|
+
const currentAccountId = pin?.workspaceId === msg.teamvibe.workspaceId ? pin.accountId : undefined;
|
|
121
|
+
// A pin is a spawn-boundary preference. Reactive same-session switches keep
|
|
122
|
+
// their existing strict ranking and do not receive this tolerance.
|
|
123
|
+
const selected = await selectPoolForResolvedModel(resolved, { workspaceId: msg.teamvibe.workspaceId, pool, store, now: Date.now(), log, currentAccountId, currentAccountTolerance: PIN_HEADROOM_SWITCH_RELATIVE });
|
|
54
124
|
if (selected.kind === 'selected') {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
125
|
+
abortLocalProbe(selected.account);
|
|
126
|
+
if (log && pool.kind === 'ready' && currentAccountId && currentAccountId !== selected.account.accountId) {
|
|
127
|
+
const previous = pool.accounts.find(a => a.accountId === currentAccountId);
|
|
128
|
+
const previousSelection = previous ? await selectPoolForResolvedModel(resolved, { workspaceId: msg.teamvibe.workspaceId, pool: { kind: 'ready', accounts: [previous] }, store, now: Date.now() }) : undefined;
|
|
129
|
+
poolInfo(log, `switch from=${poolLabel(previous ?? {})} to=${poolLabel(selected.account)} reason=${previousSelection?.kind === 'selected' ? 'better headroom' : 'pinned account unavailable'}`);
|
|
58
130
|
}
|
|
131
|
+
if (pool.kind === 'ready')
|
|
132
|
+
probeUnknownAccounts(pool.accounts, selected.account.accountId, resolved, store, {
|
|
133
|
+
cliPath: config.CLAUDE_CLI_PATH, isAtCapacity, emit: emitEvent,
|
|
134
|
+
log: line => (log ?? logger).info(line), ...probeOptions, store,
|
|
135
|
+
}, msg.teamvibe.channelId);
|
|
59
136
|
return { account: selected.account, store, pool, selection: selected };
|
|
60
137
|
}
|
|
61
138
|
if (selected.kind === 'no_accounts_configured')
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { AccountProbeFailureReason } from './usage-probe-contract.js';
|
|
2
2
|
import type { ClaudeQuotaFailure } from './claude-quota.js';
|
|
3
|
+
/** Switch a spawn-boundary pin when the best score is at least 25% higher. */
|
|
4
|
+
export declare const PIN_HEADROOM_SWITCH_RELATIVE = 0.25;
|
|
3
5
|
export declare const USAGE_THRESHOLD_PERCENT = 90;
|
|
4
6
|
/** Fallback freshness for passive windows without a reset time. */
|
|
5
7
|
export declare const PASSIVE_USAGE_TTL_MS: number;
|
|
8
|
+
/** Fallback freshness for probe windows without a reset time. */
|
|
6
9
|
export declare const PROBE_USAGE_TTL_MS = 60000;
|
|
7
10
|
export declare const POOL_RECHECK_MS: number;
|
|
8
11
|
/** Safety hold from the rejection observation when the provider gives no reset. */
|
|
@@ -49,7 +52,7 @@ export interface UsageObservation {
|
|
|
49
52
|
observedAt: number;
|
|
50
53
|
windows: readonly UsageWindow[];
|
|
51
54
|
}
|
|
52
|
-
/** Passive percentages remain lower bounds until
|
|
55
|
+
/** Passive and probe percentages remain lower bounds until each window resets. */
|
|
53
56
|
export declare function usageWindowExpiresAt(usage: UsageObservation, window: UsageWindow): number;
|
|
54
57
|
/** Latest measurements plus durable rejections; omission or success never clears a hold. */
|
|
55
58
|
export declare function mergeUsageObservations(observations: readonly (UsageObservation | undefined)[], now: number): UsageObservation | undefined;
|
|
@@ -92,7 +95,8 @@ export declare function selectSubscriptionAccount<A extends AccountIdentity>(inp
|
|
|
92
95
|
/** Stable account ID within the pool's single workspace. */
|
|
93
96
|
states: Readonly<Record<string, AccountUsageState | undefined>>;
|
|
94
97
|
model: string | undefined;
|
|
95
|
-
/** Tie preference
|
|
98
|
+
/** Tie preference by default; runtime pins can also supply a relative tolerance. */
|
|
96
99
|
currentAccountId?: string;
|
|
100
|
+
currentAccountTolerance?: number;
|
|
97
101
|
now: number;
|
|
98
102
|
}): SelectionOutcome<A>;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
/** Switch a spawn-boundary pin when the best score is at least 25% higher. */
|
|
2
|
+
export const PIN_HEADROOM_SWITCH_RELATIVE = 0.25;
|
|
1
3
|
export const USAGE_THRESHOLD_PERCENT = 90;
|
|
2
4
|
/** Fallback freshness for passive windows without a reset time. */
|
|
3
5
|
export const PASSIVE_USAGE_TTL_MS = 5 * 60_000;
|
|
6
|
+
/** Fallback freshness for probe windows without a reset time. */
|
|
4
7
|
export const PROBE_USAGE_TTL_MS = 60_000;
|
|
5
8
|
export const POOL_RECHECK_MS = 15 * 60_000;
|
|
6
9
|
/** Safety hold from the rejection observation when the provider gives no reset. */
|
|
7
10
|
export const REJECTED_BLOCK_MS = 15 * 60_000;
|
|
8
|
-
/** Passive percentages remain lower bounds until
|
|
11
|
+
/** Passive and probe percentages remain lower bounds until each window resets. */
|
|
9
12
|
export function usageWindowExpiresAt(usage, window) {
|
|
10
13
|
const ttlExpiry = usage.observedAt + (usage.source === 'passive' ? PASSIVE_USAGE_TTL_MS : PROBE_USAGE_TTL_MS);
|
|
11
|
-
return
|
|
14
|
+
return window.resetAt ?? ttlExpiry;
|
|
12
15
|
}
|
|
13
16
|
/** Latest measurements plus durable rejections; omission or success never clears a hold. */
|
|
14
17
|
export function mergeUsageObservations(observations, now) {
|
|
@@ -223,7 +226,15 @@ export function selectSubscriptionAccount(input) {
|
|
|
223
226
|
}
|
|
224
227
|
const compare = (a, b) => a.accountId < b.accountId ? -1 : a.accountId > b.accountId ? 1 : 0;
|
|
225
228
|
known.sort((a, b) => b.score - a.score || Number(b.account.accountId === input.currentAccountId) - Number(a.account.accountId === input.currentAccountId) || compare(a.account, b.account));
|
|
226
|
-
|
|
229
|
+
// Unknown pin preference is opt-in for preparePool; preserve the default
|
|
230
|
+
// policy's stable ID order (also used by the server and reactive selection).
|
|
231
|
+
unknown.sort((a, b) => (input.currentAccountTolerance === undefined ? 0 : Number(b.account.accountId === input.currentAccountId) - Number(a.account.accountId === input.currentAccountId)) || compare(a.account, b.account));
|
|
232
|
+
const current = known.find(candidate => candidate.account.accountId === input.currentAccountId);
|
|
233
|
+
if (current && known[0] && input.currentAccountTolerance !== undefined &&
|
|
234
|
+
known[0].score < current.score * (1 + input.currentAccountTolerance))
|
|
235
|
+
return { kind: 'selected', provenance: 'known', ...current };
|
|
236
|
+
// Known-first is only this spawn's transitional fallback: background CLI
|
|
237
|
+
// probes measure non-selected unknowns so the next spawn can score them.
|
|
227
238
|
if (known[0])
|
|
228
239
|
return { kind: 'selected', provenance: 'known', ...known[0] };
|
|
229
240
|
if (unknown[0])
|
|
@@ -29,6 +29,7 @@ export interface SubscriptionUsageStore {
|
|
|
29
29
|
recordQuota(key: UsageKey, quota: ClaudeQuotaFailure, observedAt: number, options?: UsageWriteOptions): Promise<void>;
|
|
30
30
|
/** Persist BEFORE network I/O; one attempt per credential version, including restart. */
|
|
31
31
|
beginProbe(key: UsageKey, observedAt: number): Promise<boolean>;
|
|
32
|
+
claimLocalProbe(key: UsageKey, observedAt: number, minIntervalMs: number): Promise<boolean>;
|
|
32
33
|
claimProbeRequest(requestId: string, expiresAt: number): Promise<boolean>;
|
|
33
34
|
finishProbe(key: UsageKey, observation: ProbeObservation): Promise<void>;
|
|
34
35
|
}
|
|
@@ -46,6 +47,8 @@ export declare class FileSubscriptionUsageStore implements SubscriptionUsageStor
|
|
|
46
47
|
recordPassive(key: UsageKey, rateLimitInfo: ClaudeRateLimitInfo, observedAt: number, options?: UsageWriteOptions): Promise<void>;
|
|
47
48
|
recordQuota(key: UsageKey, quota: ClaudeQuotaFailure, observedAt: number, options?: UsageWriteOptions): Promise<void>;
|
|
48
49
|
beginProbe(key: UsageKey, observedAt: number): Promise<boolean>;
|
|
50
|
+
/** Atomic across store instances; separate from optional HTTP and platform claims. */
|
|
51
|
+
claimLocalProbe(key: UsageKey, observedAt: number, minIntervalMs: number): Promise<boolean>;
|
|
49
52
|
/** Request claims live in the local data directory, alongside usage, never in a brain. */
|
|
50
53
|
claimProbeRequest(requestId: string, expiresAt: number): Promise<boolean>;
|
|
51
54
|
finishProbe(key: UsageKey, observation: ProbeObservation): Promise<void>;
|
|
@@ -103,6 +103,10 @@ function safeProbe(value) {
|
|
|
103
103
|
}
|
|
104
104
|
function safeVersion(value) {
|
|
105
105
|
const state = {};
|
|
106
|
+
if (value.localProbeClaimedAt !== undefined) {
|
|
107
|
+
validTime(value.localProbeClaimedAt);
|
|
108
|
+
state.localProbeClaimedAt = value.localProbeClaimedAt;
|
|
109
|
+
}
|
|
106
110
|
if (value.passive) {
|
|
107
111
|
validTime(value.passive.observedAt);
|
|
108
112
|
state.passive = { observedAt: value.passive.observedAt, rateLimitInfo: safeRateLimitInfo(value.passive.rateLimitInfo) };
|
|
@@ -220,6 +224,20 @@ export class FileSubscriptionUsageStore {
|
|
|
220
224
|
return true;
|
|
221
225
|
});
|
|
222
226
|
}
|
|
227
|
+
/** Atomic across store instances; separate from optional HTTP and platform claims. */
|
|
228
|
+
async claimLocalProbe(key, observedAt, minIntervalMs) {
|
|
229
|
+
validTime(observedAt);
|
|
230
|
+
const path = this.#path(key, true);
|
|
231
|
+
return serial(path, async () => {
|
|
232
|
+
const state = safeVersion(await this.#load(path, {}));
|
|
233
|
+
if (state.localProbeClaimedAt !== undefined && observedAt >= state.localProbeClaimedAt &&
|
|
234
|
+
observedAt - state.localProbeClaimedAt < minIntervalMs)
|
|
235
|
+
return false;
|
|
236
|
+
state.localProbeClaimedAt = observedAt;
|
|
237
|
+
await this.#save(path, state);
|
|
238
|
+
return true;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
223
241
|
/** Request claims live in the local data directory, alongside usage, never in a brain. */
|
|
224
242
|
async claimProbeRequest(requestId, expiresAt) {
|
|
225
243
|
validTime(expiresAt);
|
|
@@ -1,17 +1,34 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import type { UsageProbeMessage } from './types.js';
|
|
2
3
|
import type { PoolFetchResult } from './subscription-selection.js';
|
|
3
4
|
import type { SubscriptionAccount } from './subscription-pool-client.js';
|
|
4
5
|
import { type SubscriptionUsageStore } from './subscription-usage-store.js';
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import type { UsageProbeFailureReason as Reason } from './usage-probe-contract.js';
|
|
7
|
+
type ProbeResult = Reason | 'ok' | 'cancelled';
|
|
8
|
+
export interface UsageProbeOptions {
|
|
7
9
|
store: SubscriptionUsageStore;
|
|
8
10
|
cliPath: string;
|
|
11
|
+
/** Use the session's resolved model for local probes; platform jobs default to CLI behavior. */
|
|
12
|
+
model?: string;
|
|
9
13
|
timeoutMs?: number;
|
|
14
|
+
/** Injectable child for deterministic tests; production uses node spawn. */
|
|
15
|
+
spawn?: typeof spawn;
|
|
10
16
|
isAtCapacity: () => boolean;
|
|
11
17
|
emit: (type: string, channelId: string, metadata: Record<string, unknown>) => void;
|
|
12
18
|
log: (line: string) => void;
|
|
13
19
|
}
|
|
20
|
+
interface Options extends UsageProbeOptions {
|
|
21
|
+
fetchPool: () => Promise<PoolFetchResult<SubscriptionAccount>>;
|
|
22
|
+
}
|
|
14
23
|
export declare function isUsageProbe(value: unknown): value is UsageProbeMessage;
|
|
15
24
|
/** This run deliberately has no message/session/brain context or normal spawn bookkeeping. */
|
|
16
25
|
export declare function handleUsageProbe(job: UsageProbeMessage, options: Options): Promise<void>;
|
|
26
|
+
/** Shared isolated CLI measurement; independent of any triggering session. */
|
|
27
|
+
export declare function runUsageProbe(account: SubscriptionAccount, options: UsageProbeOptions, context?: {
|
|
28
|
+
requestId?: string;
|
|
29
|
+
expiresAt?: number;
|
|
30
|
+
channelId?: string;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
onMeasurementComplete?: () => void;
|
|
33
|
+
}): Promise<ProbeResult>;
|
|
17
34
|
export {};
|
package/dist/usage-probe-job.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { normalizePassiveUsage } from './subscription-selection.js';
|
|
6
6
|
import { safeRateLimitInfo, usageStateForSelection, } from './subscription-usage-store.js';
|
|
7
7
|
import { parseClaudeRateLimitInfo, } from './claude-rate-limit.js';
|
|
8
|
+
import { formatPoolUsageWindows, poolInfo, poolLabel } from './pool-logging.js';
|
|
8
9
|
export function isUsageProbe(value) {
|
|
9
10
|
return (!!value &&
|
|
10
11
|
typeof value === 'object' &&
|
|
@@ -14,7 +15,7 @@ export function isUsageProbe(value) {
|
|
|
14
15
|
export async function handleUsageProbe(job, options) {
|
|
15
16
|
let result = 'unknown';
|
|
16
17
|
let duplicate = false;
|
|
17
|
-
let
|
|
18
|
+
let probed = false;
|
|
18
19
|
if (!['workspaceId', 'accountId', 'credentialVersion', 'requestId'].every((k) => {
|
|
19
20
|
const value = job[k];
|
|
20
21
|
return (typeof value === 'string' &&
|
|
@@ -63,9 +64,36 @@ export async function handleUsageProbe(job, options) {
|
|
|
63
64
|
result = 'cli_at_capacity';
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
67
|
+
probed = true;
|
|
68
|
+
result = await runUsageProbe(account, options, { requestId: job.requestId, expiresAt: job.expiresAt });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
result = 'unknown';
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (!duplicate && result !== 'ok' && result !== 'cancelled')
|
|
75
|
+
options.emit('pool.probe_failed', 'usage_probe', {
|
|
76
|
+
...metadata,
|
|
77
|
+
reason: result,
|
|
78
|
+
});
|
|
79
|
+
if (!duplicate && !probed)
|
|
80
|
+
options.log(`[Pool] usage probe request=${job.requestId} result=${result}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Shared isolated CLI measurement; independent of any triggering session. */
|
|
84
|
+
export async function runUsageProbe(account, options, context = {}) {
|
|
85
|
+
let result = 'unknown';
|
|
86
|
+
let cwd;
|
|
87
|
+
let measuredWindows;
|
|
88
|
+
const cancelled = () => context.signal?.aborted === true;
|
|
89
|
+
try {
|
|
90
|
+
if (cancelled())
|
|
91
|
+
return result = 'cancelled';
|
|
66
92
|
cwd = await mkdtemp(join(tmpdir(), 'teamvibe-usage-'));
|
|
67
93
|
const configDir = join(cwd, 'config');
|
|
68
94
|
await mkdir(configDir);
|
|
95
|
+
if (cancelled())
|
|
96
|
+
return result = 'cancelled';
|
|
69
97
|
// Allow only OS execution essentials. No ambient credentials, Slack, API,
|
|
70
98
|
// brain, hooks, or user's Claude settings reach this isolated child.
|
|
71
99
|
const env = {
|
|
@@ -79,9 +107,10 @@ export async function handleUsageProbe(job, options) {
|
|
|
79
107
|
let info;
|
|
80
108
|
const outcome = await new Promise((resolve) => {
|
|
81
109
|
let done = false, buffer = '', errorText = '';
|
|
82
|
-
const child = spawn(options.cliPath, [
|
|
110
|
+
const child = (options.spawn ?? spawn)(options.cliPath, [
|
|
83
111
|
'-p',
|
|
84
112
|
'Reply OK.',
|
|
113
|
+
...(options.model ? ['--model', options.model] : []),
|
|
85
114
|
'--output-format',
|
|
86
115
|
'stream-json',
|
|
87
116
|
'--verbose',
|
|
@@ -105,18 +134,25 @@ export async function handleUsageProbe(job, options) {
|
|
|
105
134
|
child.kill('SIGKILL');
|
|
106
135
|
}
|
|
107
136
|
}
|
|
137
|
+
else
|
|
138
|
+
child.kill('SIGKILL');
|
|
108
139
|
};
|
|
109
140
|
const finish = (reason) => {
|
|
110
141
|
if (done)
|
|
111
142
|
return;
|
|
112
143
|
done = true;
|
|
113
144
|
clearTimeout(timer);
|
|
145
|
+
context.signal?.removeEventListener('abort', onAbort);
|
|
114
146
|
resolve(reason);
|
|
115
147
|
};
|
|
116
|
-
const
|
|
148
|
+
const onAbort = () => {
|
|
149
|
+
finish('cancelled');
|
|
117
150
|
kill();
|
|
151
|
+
};
|
|
152
|
+
const timer = setTimeout(() => {
|
|
118
153
|
finish('timeout');
|
|
119
|
-
|
|
154
|
+
kill();
|
|
155
|
+
}, Math.min(options.timeoutMs ?? 60_000, Math.max(1, (context.expiresAt ?? Infinity) - Date.now())));
|
|
120
156
|
const line = (text) => {
|
|
121
157
|
try {
|
|
122
158
|
const event = JSON.parse(text);
|
|
@@ -128,6 +164,8 @@ export async function handleUsageProbe(job, options) {
|
|
|
128
164
|
catch { }
|
|
129
165
|
};
|
|
130
166
|
child.stdout.on('data', (chunk) => {
|
|
167
|
+
if (done)
|
|
168
|
+
return;
|
|
131
169
|
buffer += chunk.toString();
|
|
132
170
|
if (buffer.length > 1024 * 1024) {
|
|
133
171
|
kill();
|
|
@@ -147,6 +185,8 @@ export async function handleUsageProbe(job, options) {
|
|
|
147
185
|
// Quota rejection is still a successful observation: it reveals reset
|
|
148
186
|
// time without requiring the account to complete an assistant turn.
|
|
149
187
|
child.once('close', (code) => {
|
|
188
|
+
if (done)
|
|
189
|
+
return;
|
|
150
190
|
if (buffer)
|
|
151
191
|
line(buffer);
|
|
152
192
|
finish(/401|unauthorized|invalid.*token|invalid api key|authentication.*(?:fail|error)/i.test(errorText)
|
|
@@ -157,43 +197,57 @@ export async function handleUsageProbe(job, options) {
|
|
|
157
197
|
? 'ok'
|
|
158
198
|
: 'unknown');
|
|
159
199
|
});
|
|
200
|
+
context.signal?.addEventListener('abort', onAbort, { once: true });
|
|
201
|
+
if (cancelled())
|
|
202
|
+
onAbort();
|
|
160
203
|
});
|
|
161
|
-
result = outcome;
|
|
204
|
+
result = cancelled() ? 'cancelled' : outcome;
|
|
205
|
+
// The CLI has finished. Stop treating this as an in-flight measurement
|
|
206
|
+
// before publishing its result, so a later selection cannot retroactively
|
|
207
|
+
// cancel an observation whose store write has already started.
|
|
208
|
+
context.onMeasurementComplete?.();
|
|
162
209
|
if (result !== 'ok' || !info)
|
|
163
|
-
return;
|
|
210
|
+
return result;
|
|
164
211
|
const now = Date.now();
|
|
165
212
|
const safe = safeRateLimitInfo(info);
|
|
166
213
|
const usage = {
|
|
167
214
|
...normalizePassiveUsage(safe, now),
|
|
168
215
|
source: 'probe',
|
|
169
216
|
};
|
|
217
|
+
measuredWindows = formatPoolUsageWindows(usage.windows);
|
|
170
218
|
await options.store.finishProbe(account, {
|
|
171
219
|
status: 'ok',
|
|
172
220
|
observedAt: now,
|
|
173
221
|
usage,
|
|
174
222
|
});
|
|
175
|
-
options.
|
|
176
|
-
|
|
223
|
+
const stored = await options.store.read(account);
|
|
224
|
+
// Requestless local observations use real channel authorization; the
|
|
225
|
+
// workspace-job namespace is reserved for platform request IDs.
|
|
226
|
+
options.emit('pool.usage_observed', context.channelId ?? 'usage_probe', {
|
|
227
|
+
workspaceId: account.workspaceId,
|
|
228
|
+
accountId: account.accountId,
|
|
229
|
+
credentialVersion: account.credentialVersion,
|
|
230
|
+
...(context.requestId ? { requestId: context.requestId } : {}),
|
|
231
|
+
source: 'probe',
|
|
177
232
|
accountLabel: account.label ?? account.accountId,
|
|
178
233
|
reportedAt: now,
|
|
179
|
-
usage: usageStateForSelection(
|
|
234
|
+
usage: usageStateForSelection(stored, now).usage,
|
|
180
235
|
lastRateLimitInfo: safe,
|
|
181
236
|
probeStatus: 'ok',
|
|
182
237
|
probeObservedAt: now,
|
|
183
238
|
});
|
|
239
|
+
return result;
|
|
184
240
|
}
|
|
185
241
|
catch {
|
|
186
|
-
result = 'unknown';
|
|
242
|
+
result = cancelled() ? 'cancelled' : 'unknown';
|
|
243
|
+
return result;
|
|
187
244
|
}
|
|
188
245
|
finally {
|
|
189
246
|
if (cwd)
|
|
190
247
|
await rm(cwd, { recursive: true, force: true }).catch(() => { });
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
});
|
|
196
|
-
if (!duplicate)
|
|
197
|
-
options.log(`[Pool] usage probe request=${job.requestId} result=${result}`);
|
|
248
|
+
const description = context.requestId
|
|
249
|
+
? `usage probe request=${context.requestId}`
|
|
250
|
+
: `local probe account=${poolLabel(account)}`;
|
|
251
|
+
poolInfo({ info: options.log }, `${description} result=${result}${result === 'ok' && !context.requestId ? ` windows=${measuredWindows}` : ''}${result === 'cancelled' ? ' reason=account selected' : ''}`);
|
|
198
252
|
}
|
|
199
253
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6-rc.2",
|
|
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.6-rc.2",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@aws-sdk/client-dynamodb": "3.1108.0",
|
|
12
12
|
"@aws-sdk/client-sqs": "3.1108.0",
|