@teamvibe/poller 0.8.2-rc.1 → 0.8.3-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.
Files changed (43) hide show
  1. package/dist/classify-failed-iteration.d.ts +7 -0
  2. package/dist/classify-failed-iteration.js +8 -0
  3. package/dist/claude-quota.d.ts +15 -0
  4. package/dist/claude-quota.js +188 -0
  5. package/dist/claude-rate-limit.d.ts +19 -0
  6. package/dist/claude-rate-limit.js +44 -0
  7. package/dist/claude-spawner.d.ts +15 -0
  8. package/dist/claude-spawner.js +307 -26
  9. package/dist/config-client.d.ts +2 -0
  10. package/dist/config-client.js +3 -0
  11. package/dist/config.d.ts +4 -0
  12. package/dist/config.js +1 -0
  13. package/dist/inbox-manager.d.ts +22 -1
  14. package/dist/inbox-manager.js +57 -3
  15. package/dist/inbox-watcher.d.ts +1 -1
  16. package/dist/inbox-watcher.js +42 -20
  17. package/dist/poller.js +262 -51
  18. package/dist/pool-defer.d.ts +13 -0
  19. package/dist/pool-defer.js +81 -0
  20. package/dist/pool-telemetry.d.ts +44 -0
  21. package/dist/pool-telemetry.js +107 -0
  22. package/dist/quota-pause.d.ts +16 -0
  23. package/dist/quota-pause.js +96 -0
  24. package/dist/session-store.d.ts +2 -2
  25. package/dist/session-store.js +6 -3
  26. package/dist/slack-client.d.ts +4 -1
  27. package/dist/slack-client.js +100 -11
  28. package/dist/subscription-pool-client.d.ts +32 -0
  29. package/dist/subscription-pool-client.js +139 -0
  30. package/dist/subscription-pool.d.ts +18 -0
  31. package/dist/subscription-pool.js +24 -0
  32. package/dist/subscription-runtime.d.ts +53 -0
  33. package/dist/subscription-runtime.js +74 -0
  34. package/dist/subscription-selection.d.ts +75 -0
  35. package/dist/subscription-selection.js +142 -0
  36. package/dist/subscription-usage-probe.d.ts +16 -0
  37. package/dist/subscription-usage-probe.js +106 -0
  38. package/dist/subscription-usage-store.d.ts +47 -0
  39. package/dist/subscription-usage-store.js +206 -0
  40. package/dist/types.d.ts +25 -9
  41. package/dist/worktree-manager.js +4 -0
  42. package/npm-shrinkwrap.json +78 -77
  43. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
+ import type { ClaudeQuotaFailure } from './claude-quota.js';
1
2
  export type KilledByPoller = 'timeout' | 'idle' | 'zombie' | 'network' | 'shutdown';
2
3
  export interface FailedIterationInput {
3
4
  error?: string;
5
+ quota?: ClaudeQuotaFailure;
4
6
  exitCode: number | null;
5
7
  killedByPoller?: KilledByPoller;
6
8
  }
@@ -12,5 +14,10 @@ export type FailedIterationDecision = {
12
14
  kind: 'error';
13
15
  text: string;
14
16
  reaction: string;
17
+ } | {
18
+ kind: 'quota';
19
+ quota: ClaudeQuotaFailure;
20
+ text: string;
21
+ reaction: string;
15
22
  };
16
23
  export declare function classifyFailedIteration(result: FailedIterationInput): FailedIterationDecision;
@@ -67,6 +67,14 @@ export function classifyFailedIteration(result) {
67
67
  };
68
68
  case 'timeout':
69
69
  case undefined: {
70
+ if (!result.killedByPoller && result.quota) {
71
+ const reset = result.quota.resetAt ? `Resets at ${result.quota.resetAt}.` : 'Reset time unavailable.';
72
+ return {
73
+ kind: 'quota', quota: result.quota,
74
+ text: `Claude subscription usage limit reached. ${reset} Your message is saved. After the quota pause ends, the next inbox sweep will retry the saved request.`,
75
+ reaction: 'hourglass',
76
+ };
77
+ }
70
78
  // 'timeout' keeps the legacy ":warning: Session timed out after N
71
79
  // minutes..." surface unchanged (the error string is set by the
72
80
  // spawner). undefined = genuine failure → keep current behavior.
@@ -0,0 +1,15 @@
1
+ import type { ClaudeQuotaWindow, ClaudeRateLimitInfo } from './claude-rate-limit.js';
2
+ /** Safe metadata only; never carries CLI output or credential-bearing payloads. */
3
+ export interface ClaudeQuotaFailure {
4
+ reason: 'usage_limit' | 'weekly_limit' | 'session_limit' | 'extra_usage';
5
+ window?: ClaudeQuotaWindow;
6
+ modelScope?: string;
7
+ resetAt?: string;
8
+ }
9
+ export interface ClaudeQuotaInput {
10
+ exitCode: number | null;
11
+ stderr?: string;
12
+ events?: readonly unknown[];
13
+ rateLimitInfo?: ClaudeRateLimitInfo;
14
+ }
15
+ export declare function parseClaudeQuotaFailure(input: ClaudeQuotaInput, now: Date): ClaudeQuotaFailure | undefined;
@@ -0,0 +1,188 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2025 Paperclip AI
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ */
24
+ // Selectively adapted from Paperclip AI (MIT), Copyright (c) 2025 Paperclip AI.
25
+ // https://github.com/paperclipai/paperclip/blob/cc88b61c0918f45f6d3215f5d2d2368c88297046/packages/adapters/claude-local/src/server/parse.ts#L24-L30
26
+ // Excludes generic rate limits, extra-usage suggestions and AWS service quotas.
27
+ const QUOTA_RE = /(?:you(?:'|’)ve\s+hit\s+your\s+(?:(?:usage|weekly|session|sonnet|opus|haiku)\s+)?limit\b(?!\s+of\b)|session\s+limit\s+(?:reached|exceeded)|out\s+of\s+extra\s+usage|(?:claude\s+)?usage\s+(?:limit|cap)\s+reached|5[-\s]?hour\s+limit\s+reached|weekly\s+limit\s+reached)/i;
28
+ function record(value) {
29
+ return value !== null && typeof value === 'object' ? value : {};
30
+ }
31
+ function errorText(value) {
32
+ if (typeof value === 'string')
33
+ return [value];
34
+ if (Array.isArray(value))
35
+ return value.flatMap(errorText);
36
+ if (value === null || typeof value !== 'object')
37
+ return [];
38
+ const obj = record(value);
39
+ // Read only known error fields; do not serialize arbitrary payloads.
40
+ return [...errorText(obj['message']), ...errorText(obj['error'])];
41
+ }
42
+ export function parseClaudeQuotaFailure(input, now) {
43
+ // stderr is CLI diagnostic evidence even when an error is followed by exit 0.
44
+ // Ordinary assistant prose is read only when explicitly marked as an error.
45
+ const texts = [input.stderr ?? ''];
46
+ for (const raw of input.events ?? []) {
47
+ const event = record(raw);
48
+ const subtype = typeof event['subtype'] === 'string' ? event['subtype'] : '';
49
+ if (event['type'] === 'result' && /^error_max_(?:turns|budget)/.test(subtype))
50
+ return undefined;
51
+ if (event['type'] === 'error' ||
52
+ (event['type'] === 'result' && (event['is_error'] === true || subtype.startsWith('error_')))) {
53
+ texts.push(...errorText(event['error']), ...errorText(event['errors']), ...errorText(event['result']), ...errorText(event['message']));
54
+ }
55
+ else if (event['type'] === 'assistant' && event['error']) {
56
+ texts.push(...errorText(event['error']));
57
+ const content = record(event['message'])['content'];
58
+ if (Array.isArray(content)) {
59
+ for (const block of content) {
60
+ const item = record(block);
61
+ if (item['type'] === 'text')
62
+ texts.push(...errorText(item['text']));
63
+ }
64
+ }
65
+ }
66
+ }
67
+ const text = texts.join('\n');
68
+ if (/(?:login required|not logged in|please (?:log in|run (?:`?claude login`?|\/login))|invalid api key|failed to authenticate|invalid bearer token|authentication[_ -](?:failed|required|error)|unauthorized|(?:oauth|access|bearer) token (?:expired|invalid|revoked)|unknown session|no conversation found|reached max turns|max[_ -]turns)/i.test(text))
69
+ return undefined;
70
+ // Each candidate owns its reason, scope, window and reset. Split lines and
71
+ // repeated quota phrases so one diagnostic cannot donate another's reset.
72
+ const candidates = texts.flatMap(value => value.split('\n').flatMap(line => {
73
+ const matches = [...line.matchAll(new RegExp(QUOTA_RE.source, 'gi'))];
74
+ return matches.flatMap((match, index) => {
75
+ const diagnostic = line.slice(match.index, matches[index + 1]?.index);
76
+ // Request/token rates are transient even if the CLI calls them "usage".
77
+ if (/\b(?:requests?|tokens?)\s*(?:per\s+|a\s+|each\s+|\/)\s*(?:second|minute|hour|day|s\b|m\b)/i.test(diagnostic))
78
+ return [];
79
+ const evidence = match[0];
80
+ const reason = /weekly/i.test(evidence) ? 'weekly_limit'
81
+ : /session\s+limit/i.test(evidence) ? 'session_limit'
82
+ : /out\s+of\s+extra\s+usage/i.test(evidence) ? 'extra_usage'
83
+ : 'usage_limit';
84
+ // Scope is adjacent to this exhausted limit, never unrelated advice.
85
+ const model = diagnostic.slice(evidence.length).match(/^\s+(?:for|on)\s+(?:model\s+)?((?:claude-)?(?:sonnet|opus|haiku)(?:-\d+)*)(?![\w-])/i)?.[1]?.toLowerCase()
86
+ ?? evidence.match(/your\s+((?:claude-)?(?:sonnet|opus|haiku)(?:-\d+)*)\s+limit/i)?.[1]?.toLowerCase();
87
+ const window = reason === 'weekly_limit' ? 'seven_day'
88
+ : reason === 'session_limit' || /5[-\s]?hour/i.test(evidence) ? 'five_hour' : undefined;
89
+ const resetPrefix = diagnostic.slice(evidence.length).split(/\bresets?\b/i)[0] ?? '';
90
+ // Another named window/model before "resets" makes that reset unrelated
91
+ // or ambiguous. Strip only the directly attached model scope first.
92
+ const scopePrefix = resetPrefix.replace(/^\s+(?:for|on)\s+(?:model\s+)?(?:claude-)?(?:sonnet|opus|haiku)(?:-\d+)*(?![\w-])/i, '');
93
+ const unrelatedAllowance = /\b(?:5[-\s]?hour|five[-\s]?hour|weekly|seven[-\s]?day|session|(?:claude-)?(?:sonnet|opus|haiku)(?:-\d+)*)\b/i.test(scopePrefix);
94
+ const resetAt = unrelatedAllowance ? undefined : parseResetAt(diagnostic, now);
95
+ return [{ reason, ...(window ? { window } : {}), ...(model ? { modelScope: model } : {}), ...(resetAt ? { resetAt } : {}) }];
96
+ });
97
+ }));
98
+ // Detailed weekly evidence beats a generic message, but its unknown reset
99
+ // stays unknown; no search through the other candidates for a timestamp.
100
+ const quota = candidates.find(candidate => candidate.reason === 'weekly_limit') ?? candidates[0];
101
+ if (!quota)
102
+ return undefined;
103
+ // Global windows cannot establish the reset of a model-specific allowance.
104
+ // Prefer structured metadata for the exact text-classified window only.
105
+ const info = input.rateLimitInfo;
106
+ if (quota.window && !quota.modelScope && info) {
107
+ const matching = info.unifiedWindows?.[quota.window];
108
+ // An explicitly present window with an unknown reset stays unknown;
109
+ // never borrow the top-level reset in that case.
110
+ const seconds = matching ? matching.resetsAt
111
+ : info.rateLimitType === quota.window ? info.resetsAt : undefined;
112
+ if (seconds !== undefined && seconds * 1000 > now.getTime()) {
113
+ return { ...quota, resetAt: new Date(seconds * 1000).toISOString() };
114
+ }
115
+ }
116
+ return quota;
117
+ }
118
+ // Inspired by Paperclip's timezone helpers and reset extraction (MIT above):
119
+ // https://github.com/paperclipai/paperclip/blob/cc88b61c0918f45f6d3215f5d2d2368c88297046/packages/adapters/claude-local/src/server/parse.ts#L547-L595 (and adjacent timezone helpers)
120
+ // Unlike its local-clock fallback, unknown zones and DST folds stay unknown.
121
+ function parseResetAt(text, now) {
122
+ if (!Number.isFinite(now.getTime()))
123
+ return undefined;
124
+ const reset = text.match(/\bresets?\s+(?:at\s+)?([^\n]+)/i)?.[1]?.trim();
125
+ if (!reset)
126
+ return undefined;
127
+ const iso = reset.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|[+-]\d{2}:\d{2})[.!]?$/i);
128
+ if (iso) {
129
+ const [, year, month, day, hour, minute, second, , zone] = iso;
130
+ const wall = Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second));
131
+ const date = new Date(wall);
132
+ if (date.getUTCFullYear() !== Number(year) || date.getUTCMonth() + 1 !== Number(month) ||
133
+ date.getUTCDate() !== Number(day) || Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59)
134
+ return undefined;
135
+ if (zone !== 'Z' && zone !== 'z' && (Number(zone.slice(1, 3)) > 23 || Number(zone.slice(4)) > 59))
136
+ return undefined;
137
+ const timestamp = Date.parse(reset.replace(/[.!]$/, ''));
138
+ return timestamp > now.getTime() ? new Date(timestamp).toISOString() : undefined;
139
+ }
140
+ const clock = reset.match(/^(\d{1,2})(?::(\d{2}))?\s*([ap])m\s*\(([^)]+)\)[.!]?$/i);
141
+ if (!clock)
142
+ return undefined;
143
+ const hour12 = Number(clock[1]);
144
+ const minute = Number(clock[2] ?? 0);
145
+ if (hour12 < 1 || hour12 > 12 || minute > 59)
146
+ return undefined;
147
+ const hour = hour12 % 12 + (clock[3].toLowerCase() === 'p' ? 12 : 0);
148
+ const hint = clock[4].trim();
149
+ // Abbreviations such as CST/EST are ambiguous, even if Intl accepts them.
150
+ const timeZone = /^(utc|gmt)$/i.test(hint) ? 'UTC' : hint.includes('/') ? hint : undefined;
151
+ if (!timeZone)
152
+ return undefined;
153
+ try {
154
+ const formatter = new Intl.DateTimeFormat('en-US', {
155
+ timeZone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit',
156
+ hour: '2-digit', minute: '2-digit', second: '2-digit',
157
+ });
158
+ const wallTime = (timestamp) => {
159
+ const parts = Object.fromEntries(formatter.formatToParts(new Date(timestamp)).map(p => [p.type, p.value]));
160
+ return Date.UTC(Number(parts['year']), Number(parts['month']) - 1, Number(parts['day']), Number(parts['hour']), Number(parts['minute']), Number(parts['second']));
161
+ };
162
+ const localNow = new Date(wallTime(now.getTime()));
163
+ let target = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate(), hour, minute);
164
+ const resolve = (wall) => {
165
+ const candidates = new Set();
166
+ // Sample offsets on both sides of any DST transition, then verify each
167
+ // UTC candidate against the exact wall clock. Zero/multiple = unknown.
168
+ for (let hours = -36; hours <= 36; hours += 6) {
169
+ const sample = wall + hours * 3600000;
170
+ const candidate = wall - (wallTime(sample) - sample);
171
+ if (wallTime(candidate) === wall)
172
+ candidates.add(candidate);
173
+ }
174
+ return candidates.size === 1 ? [...candidates][0] : undefined;
175
+ };
176
+ let timestamp = resolve(target);
177
+ if (timestamp === undefined)
178
+ return undefined;
179
+ if (timestamp <= now.getTime()) {
180
+ target += 86400000; // next calendar day in wall-clock coordinates
181
+ timestamp = resolve(target);
182
+ }
183
+ return timestamp !== undefined && timestamp > now.getTime() ? new Date(timestamp).toISOString() : undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
@@ -0,0 +1,19 @@
1
+ /** Passive Claude Code stream metadata; status never establishes exhaustion. */
2
+ export type ClaudeQuotaWindow = 'five_hour' | 'seven_day';
3
+ export interface ClaudeRateLimitWindow {
4
+ /** Fraction from 0 to 1, not a percentage. */
5
+ utilization?: number;
6
+ /** Unix epoch seconds. */
7
+ resetsAt?: number;
8
+ }
9
+ export interface ClaudeRateLimitInfo extends ClaudeRateLimitWindow {
10
+ status?: string;
11
+ rateLimitType?: string;
12
+ unifiedWindows?: Partial<Record<ClaudeQuotaWindow, ClaudeRateLimitWindow>>;
13
+ overageStatus?: string;
14
+ overageDisabledReason?: string;
15
+ isUsingOverage?: boolean;
16
+ surpassedThreshold?: number;
17
+ }
18
+ /** Copies only recognized fields of the last rate_limit_info; never raw JSON. */
19
+ export declare function parseClaudeRateLimitInfo(value: unknown): ClaudeRateLimitInfo | undefined;
@@ -0,0 +1,44 @@
1
+ function record(value) {
2
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
3
+ ? value : undefined;
4
+ }
5
+ function fraction(value) {
6
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
7
+ }
8
+ function epochSeconds(value) {
9
+ // Bound to supported calendar dates; reject milliseconds/string coercions.
10
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 253402300799;
11
+ }
12
+ function windowInfo(obj) {
13
+ return {
14
+ ...(fraction(obj['utilization']) ? { utilization: obj['utilization'] } : {}),
15
+ ...(epochSeconds(obj['resetsAt']) ? { resetsAt: obj['resetsAt'] } : {}),
16
+ };
17
+ }
18
+ /** Copies only recognized fields of the last rate_limit_info; never raw JSON. */
19
+ export function parseClaudeRateLimitInfo(value) {
20
+ const obj = record(value);
21
+ if (!obj)
22
+ return undefined;
23
+ const result = windowInfo(obj);
24
+ for (const key of ['status', 'rateLimitType', 'overageStatus', 'overageDisabledReason']) {
25
+ if (typeof obj[key] === 'string')
26
+ result[key] = obj[key];
27
+ }
28
+ if (typeof obj['isUsingOverage'] === 'boolean')
29
+ result.isUsingOverage = obj['isUsingOverage'];
30
+ if (fraction(obj['surpassedThreshold']))
31
+ result.surpassedThreshold = obj['surpassedThreshold'];
32
+ const windows = record(obj['unifiedWindows']);
33
+ if (windows) {
34
+ const parsed = {};
35
+ for (const key of ['five_hour', 'seven_day']) {
36
+ const data = record(windows[key]);
37
+ if (data)
38
+ parsed[key] = windowInfo(data);
39
+ }
40
+ if (Object.keys(parsed).length)
41
+ result.unifiedWindows = parsed;
42
+ }
43
+ return result;
44
+ }
@@ -4,6 +4,10 @@ import { type SessionLogger } from './logger.js';
4
4
  import { type TypingIndicator } from './slack-client.js';
5
5
  import { type ResolvedModelConfig } from './config-client.js';
6
6
  import type { KilledByPoller } from './classify-failed-iteration.js';
7
+ import { type ClaudeRateLimitInfo } from './claude-rate-limit.js';
8
+ import { type ClaudeQuotaFailure } from './claude-quota.js';
9
+ import { type PoolRuntimeOptions, type PoolAttempt } from './subscription-runtime.js';
10
+ import type { SubscriptionAccount } from './subscription-pool-client.js';
7
11
  export declare function progressNotificationMarks(estimateMinutes: number): number[];
8
12
  export declare function computeCompactionKillDeadline(previousActivityTime: number, now: number, idleKillMs: number, graceMs: number): number;
9
13
  export interface ContextHintParams {
@@ -19,6 +23,11 @@ export interface ContextHintParams {
19
23
  }
20
24
  export declare function buildContextHint(p: ContextHintParams): Promise<string | undefined>;
21
25
  export interface SpawnResult {
26
+ poolAttempts?: readonly PoolAttempt[];
27
+ quota?: ClaudeQuotaFailure;
28
+ rateLimitInfo?: ClaudeRateLimitInfo;
29
+ /** Actual ID passed to this CLI attempt, including a failed first turn. */
30
+ attemptedSessionId?: string;
22
31
  success: boolean;
23
32
  output: string;
24
33
  exitCode: number | null;
@@ -165,6 +174,7 @@ export interface BuildSpawnEnvParams {
165
174
  teamvibePollerId: string;
166
175
  inboxThreadId: string;
167
176
  brainPath: string;
177
+ subscriptionAccount?: SubscriptionAccount;
168
178
  }
169
179
  /**
170
180
  * Pure construction of the env object passed to the spawned `claude` child
@@ -225,6 +235,8 @@ export declare function resolveTypingStatus(event: any, assistantText?: string):
225
235
  * createSession() placeholder forever and the thread's next message does
226
236
  * `--resume <id-claude-code-never-used>`, which fails 100% of the time.
227
237
  * (teamvibe.ai#77, DevGuru review round)
238
+ * Quota failures also report the attempted ID, even on the first turn, so
239
+ * later pool orchestration can continue that same session (#401).
228
240
  */
229
241
  export declare function finalizeSpawnResult(retryResult: SpawnResult, isFirstMessage: boolean, effectiveSessionId: string | undefined): SpawnResult & {
230
242
  newSessionId?: string;
@@ -232,6 +244,9 @@ export declare function finalizeSpawnResult(retryResult: SpawnResult, isFirstMes
232
244
  export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void, typingIndicator?: TypingIndicator): Promise<SpawnResult & {
233
245
  newSessionId?: string;
234
246
  }>;
247
+ export declare function spawnPoolClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void, typingIndicator?: TypingIndicator, poolRuntime?: PoolRuntimeOptions): Promise<SpawnResult & {
248
+ newSessionId?: string;
249
+ }>;
235
250
  export declare function getActiveProcessCount(): number;
236
251
  export declare function getLastSessionEndTs(): number;
237
252
  /**