@indigoai-us/hq-cli 5.345.92 → 5.345.94

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 (48) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/command-catalog.generated.d.ts +252 -0
  3. package/dist/command-catalog.generated.js +306 -0
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/capacity.d.ts +71 -0
  7. package/dist/commands/capacity.js +666 -0
  8. package/dist/commands/integrations-connect.js +64 -5
  9. package/dist/commands/lanes-accounts.d.ts +1 -0
  10. package/dist/commands/lanes-accounts.js +6 -0
  11. package/dist/lib/bot/api.d.ts +21 -0
  12. package/dist/lib/bot/api.js +26 -0
  13. package/dist/lib/bot/prompt.js +2 -1
  14. package/dist/lib/bot/room-mentions.d.ts +26 -0
  15. package/dist/lib/bot/room-mentions.js +40 -0
  16. package/dist/lib/bot/run.js +44 -6
  17. package/dist/lib/capacity/accounts.d.ts +36 -0
  18. package/dist/lib/capacity/accounts.js +90 -0
  19. package/dist/lib/capacity/collect.d.ts +33 -0
  20. package/dist/lib/capacity/collect.js +164 -0
  21. package/dist/lib/capacity/collectors/claude-oauth.d.ts +145 -0
  22. package/dist/lib/capacity/collectors/claude-oauth.js +1211 -0
  23. package/dist/lib/capacity/collectors/claude-statusline.d.ts +72 -0
  24. package/dist/lib/capacity/collectors/claude-statusline.js +436 -0
  25. package/dist/lib/capacity/collectors/codex.d.ts +120 -0
  26. package/dist/lib/capacity/collectors/codex.js +1134 -0
  27. package/dist/lib/capacity/collectors/grok.d.ts +86 -0
  28. package/dist/lib/capacity/collectors/grok.js +588 -0
  29. package/dist/lib/capacity/lane-login.d.ts +29 -0
  30. package/dist/lib/capacity/lane-login.js +91 -0
  31. package/dist/lib/capacity/ledger.d.ts +399 -0
  32. package/dist/lib/capacity/ledger.js +1441 -0
  33. package/dist/lib/capacity/project.d.ts +12 -0
  34. package/dist/lib/capacity/project.js +264 -0
  35. package/dist/lib/capacity/read.d.ts +62 -0
  36. package/dist/lib/capacity/read.js +264 -0
  37. package/dist/lib/capacity/refusal.d.ts +30 -0
  38. package/dist/lib/capacity/refusal.js +183 -0
  39. package/dist/lib/capacity/scheduler.d.ts +76 -0
  40. package/dist/lib/capacity/scheduler.js +800 -0
  41. package/dist/lib/capacity/sign-in.d.ts +103 -0
  42. package/dist/lib/capacity/sign-in.js +517 -0
  43. package/dist/lib/capacity/widget.d.ts +22 -0
  44. package/dist/lib/capacity/widget.js +418 -0
  45. package/dist/lib/lanes/spawn.js +12 -0
  46. package/dist/lib/mesh/live/daemon/run.d.ts +5 -0
  47. package/dist/lib/mesh/live/daemon/run.js +29 -1
  48. package/package.json +1 -1
@@ -0,0 +1,800 @@
1
+ /**
2
+ * Resident capacity collector scheduler (US-008).
3
+ *
4
+ * Runs inside hq mesh daemon on the daemon's timer host. A disabled or
5
+ * missing collector mark does nothing except re-check every 60s. One
6
+ * in-flight run per source; a hung run is abandoned and recorded as a
7
+ * parser_failure. Sentry gets provider, source, and CLI version only.
8
+ */
9
+ import { execFile } from "node:child_process";
10
+ import * as fs from "node:fs";
11
+ import * as os from "node:os";
12
+ import * as path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { appendParserFailure, DIR_MODE, FILE_MODE, LedgerBusy, readCollectorMark, rebuildNow, } from "./ledger.js";
15
+ import { ingestCodexResult, isCodexProcessRunning, latestCodexSessionWriteAt, mapCodexAppServerBody, readCodexAppServer, readInstalledCodexVersion, shouldReadAppServer, tailCodexSessions, } from "./collectors/codex.js";
16
+ import { CLAUDE_SPOOL_DIR, consumeClaudeStatuslineSpool, parseStatuslinePayload, } from "./collectors/claude-statusline.js";
17
+ import { mapClaudeOAuthUsage, readInstalledClaudeVersionAsync, collectClaudeOAuth, } from "./collectors/claude-oauth.js";
18
+ import { collectGrok, mapGrokBillingBody, readInstalledGrokVersionAsync, } from "./collectors/grok.js";
19
+ import { listCapacityAccounts } from "./accounts.js";
20
+ export const ENABLE_TICK_MS = 60_000;
21
+ export const REBUILD_TICK_MS = 60_000;
22
+ export const POLL_TICK_MS = 5 * 60_000;
23
+ export const CODEX_DIR_POLL_MS = 15_000;
24
+ export const SPOOL_POLL_MS = 15_000;
25
+ const RUN_TIMEOUT_MS = {
26
+ "codex-tail": 15_000,
27
+ "codex-app-server": 30_000,
28
+ "claude-statusline": 10_000,
29
+ "claude-oauth": 30_000,
30
+ grok: 30_000,
31
+ rebuild: 10_000,
32
+ };
33
+ const SOURCE_EVENT = {
34
+ "codex-tail": { provider: "codex", source: "codex-jsonl" },
35
+ "codex-app-server": { provider: "codex", source: "codex-app-server" },
36
+ "claude-statusline": { provider: "claude", source: "statusline" },
37
+ "claude-oauth": { provider: "claude", source: "oauth-usage" },
38
+ grok: { provider: "grok", source: "grok-billing" },
39
+ };
40
+ const TAIL_STATE_FILE = "codex-tail-state.json";
41
+ const APP_SERVER_BACKOFF_FILE = "codex-app-server-backoff.json";
42
+ const SEEN_VERSIONS_FILE = "seen-cli-versions.json";
43
+ const SELF_TEST_AT = "2026-10-11T00:00:00.000Z";
44
+ export function defaultCodexSessionsDir(home = os.homedir()) {
45
+ return path.join(home, ".codex", "sessions");
46
+ }
47
+ export function capacityFixtureDir() {
48
+ return fileURLToPath(new URL("../../../test/fixtures/capacity/", import.meta.url));
49
+ }
50
+ /** Parse the pinned fixtures once. Missing fixtures are a skip, not a failure. */
51
+ export function runParserSelfTest(provider, version, fixturesDir = capacityFixtureDir()) {
52
+ const checks = [];
53
+ if (provider === "codex") {
54
+ checks.push(() => {
55
+ const body = readFixture(fixturesDir, "codex-app-server-rate-limits.json");
56
+ if (!body)
57
+ return { readings: [true], failures: [] };
58
+ const mapped = mapCodexAppServerBody(body, { cliVersion: version, now: new Date(SELF_TEST_AT) });
59
+ return { readings: mapped.readings, failures: mapped.failures };
60
+ });
61
+ }
62
+ else if (provider === "claude") {
63
+ checks.push(() => {
64
+ const body = readFixture(fixturesDir, "claude-statusline.json");
65
+ if (!body)
66
+ return { readings: [true], failures: [] };
67
+ const parsed = parseStatuslinePayload(body, { observedAt: SELF_TEST_AT, cliVersion: version });
68
+ return { readings: parsed.readings, failures: parsed.failures };
69
+ });
70
+ checks.push(() => {
71
+ const body = readFixture(fixturesDir, "claude-oauth-usage.json");
72
+ if (!body)
73
+ return { readings: [true], failures: [] };
74
+ const mapped = mapClaudeOAuthUsage(body, {
75
+ observedAt: SELF_TEST_AT,
76
+ cliVersion: version,
77
+ accountId: "acct_self_test",
78
+ planType: null,
79
+ });
80
+ return { readings: mapped.readings, failures: mapped.failures };
81
+ });
82
+ }
83
+ else if (provider === "grok") {
84
+ checks.push(() => {
85
+ const body = readFixture(fixturesDir, "grok-billing.json");
86
+ if (!body)
87
+ return { readings: [true], failures: [] };
88
+ const mapped = mapGrokBillingBody(body, {
89
+ observedAt: SELF_TEST_AT,
90
+ cliVersion: version,
91
+ accountId: "acct_self_test",
92
+ });
93
+ return { readings: mapped.readings, failures: mapped.failures };
94
+ });
95
+ }
96
+ if (checks.length === 0)
97
+ return { ok: true };
98
+ for (const check of checks) {
99
+ const result = check();
100
+ if (result.failures.length > 0 || result.readings.length === 0)
101
+ return { ok: false };
102
+ }
103
+ return { ok: true };
104
+ }
105
+ export function startCapacityScheduler(options) {
106
+ const timers = options.timers;
107
+ const root = options.root;
108
+ const now = options.now ?? (() => new Date());
109
+ const log = options.log ?? (() => undefined);
110
+ const home = options.home ?? os.homedir();
111
+ const sessionsDir = options.sessionsDir ?? defaultCodexSessionsDir(home);
112
+ const watchFn = options.watch ?? defaultWatch;
113
+ const recursive = options.recursive ?? (process.platform === "darwin" || process.platform === "win32");
114
+ const sentry = options.sentry ?? defaultSentry;
115
+ const selfTest = options.selfTest ?? ((provider, version) => runParserSelfTest(provider, version));
116
+ const enumerateAccounts = options.enumerateAccounts === true;
117
+ const collectors = {
118
+ tailCodexSessions,
119
+ ingestCodexResult,
120
+ shouldReadAppServer,
121
+ isCodexProcessRunning,
122
+ readCodexAppServer,
123
+ latestCodexSessionWriteAt,
124
+ readInstalledCodexVersion,
125
+ consumeClaudeStatuslineSpool,
126
+ collectClaudeOAuth,
127
+ collectGrok,
128
+ readInstalledClaudeVersion: readInstalledClaudeVersionAsync,
129
+ readInstalledGrokVersion: readInstalledGrokVersionAsync,
130
+ ...options.collectors,
131
+ };
132
+ const aborts = new Set();
133
+ const versionCache = new Map();
134
+ let backoffWrite = Promise.resolve();
135
+ const VERSION_TTL_MS = 60 * 60 * 1000;
136
+ let stopped = false;
137
+ let enabled = false;
138
+ let tickCount = 0;
139
+ let slowHandle = null;
140
+ const intervals = new Map();
141
+ const watches = [];
142
+ const inflight = new Map();
143
+ const pending = new Set();
144
+ const jobs = new Map();
145
+ const runTimeouts = new Map();
146
+ const seenVersions = new Map();
147
+ let generation = 0;
148
+ let codexWatching = false;
149
+ let spoolWatching = false;
150
+ const ledger = () => ({ root, now });
151
+ function armSlow() {
152
+ if (stopped)
153
+ return;
154
+ slowHandle = timers.setTimeout(() => {
155
+ slowHandle = null;
156
+ if (stopped)
157
+ return;
158
+ try {
159
+ syncEnabled();
160
+ }
161
+ catch (err) {
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ log(`collector mark check failed: ${message.slice(0, 120)}`);
164
+ }
165
+ if (!stopped)
166
+ armSlow();
167
+ }, ENABLE_TICK_MS);
168
+ }
169
+ function every(name, ms, fn) {
170
+ if (stopped || !enabled)
171
+ return;
172
+ const tick = () => {
173
+ if (stopped || !enabled)
174
+ return;
175
+ fn();
176
+ if (stopped || !enabled)
177
+ return;
178
+ intervals.set(name, timers.setTimeout(tick, ms));
179
+ };
180
+ intervals.set(name, timers.setTimeout(tick, ms));
181
+ }
182
+ function clearFast() {
183
+ for (const handle of intervals.values())
184
+ timers.clearTimeout(handle);
185
+ intervals.clear();
186
+ for (const tracked of watches.splice(0)) {
187
+ try {
188
+ tracked.watcher.close();
189
+ }
190
+ catch {
191
+ // The watcher may already be closed.
192
+ }
193
+ }
194
+ codexWatching = false;
195
+ spoolWatching = false;
196
+ }
197
+ function syncEnabled() {
198
+ const on = readCollectorMark(ledger())?.enabled === true;
199
+ if (on && !enabled)
200
+ activate();
201
+ else if (!on && enabled)
202
+ deactivate();
203
+ }
204
+ function activate() {
205
+ enabled = true;
206
+ loadSeenVersions();
207
+ openWatches();
208
+ armFast();
209
+ kick("codex-tail", runCodexTail);
210
+ kick("codex-app-server", runCodexAppServer);
211
+ kick("claude-statusline", runStatusline);
212
+ kick("claude-oauth", runClaudeOAuth);
213
+ kick("grok", runGrok);
214
+ kick("rebuild", runRebuild);
215
+ }
216
+ function deactivate() {
217
+ enabled = false;
218
+ clearFast();
219
+ }
220
+ function armFast() {
221
+ every("rebuild", REBUILD_TICK_MS, () => kick("rebuild", runRebuild));
222
+ every("oauth", POLL_TICK_MS, () => kick("claude-oauth", runClaudeOAuth));
223
+ every("grok", POLL_TICK_MS, () => kick("grok", runGrok));
224
+ every("app-server", POLL_TICK_MS, () => kick("codex-app-server", runCodexAppServer));
225
+ if (!codexWatching || enumerateAccounts)
226
+ every("codex-poll", CODEX_DIR_POLL_MS, () => kick("codex-tail", runCodexTail));
227
+ if (!spoolWatching)
228
+ every("spool-poll", SPOOL_POLL_MS, () => kick("claude-statusline", runStatusline));
229
+ }
230
+ function openWatches() {
231
+ if (recursive && fs.existsSync(sessionsDir)) {
232
+ codexWatching = openWatch(sessionsDir, true, "codex", () => kick("codex-tail", runCodexTail));
233
+ }
234
+ const spoolDir = path.join(root, CLAUDE_SPOOL_DIR);
235
+ try {
236
+ fs.mkdirSync(spoolDir, { recursive: true, mode: DIR_MODE });
237
+ }
238
+ catch {
239
+ spoolWatching = false;
240
+ }
241
+ if (fs.existsSync(spoolDir)) {
242
+ spoolWatching = openWatch(spoolDir, false, "spool", () => kick("claude-statusline", runStatusline));
243
+ }
244
+ }
245
+ function openWatch(target, recursiveWatch, kind, onFire) {
246
+ try {
247
+ const watchOptions = { persistent: true };
248
+ if (recursiveWatch)
249
+ watchOptions.recursive = true;
250
+ const watcher = watchFn(target, watchOptions, () => onFire());
251
+ const tracked = { kind, watcher };
252
+ watches.push(tracked);
253
+ watcher.on?.("error", () => {
254
+ const index = watches.indexOf(tracked);
255
+ if (index >= 0)
256
+ watches.splice(index, 1);
257
+ try {
258
+ watcher.close();
259
+ }
260
+ catch {
261
+ // Already closed.
262
+ }
263
+ if (kind === "codex") {
264
+ codexWatching = false;
265
+ if (!intervals.has("codex-poll")) {
266
+ every("codex-poll", CODEX_DIR_POLL_MS, () => kick("codex-tail", runCodexTail));
267
+ }
268
+ }
269
+ else {
270
+ spoolWatching = false;
271
+ if (!intervals.has("spool-poll")) {
272
+ every("spool-poll", SPOOL_POLL_MS, () => kick("claude-statusline", runStatusline));
273
+ }
274
+ }
275
+ log(`${kind} watch error`);
276
+ });
277
+ return true;
278
+ }
279
+ catch {
280
+ log(`${kind} watch unavailable`);
281
+ return false;
282
+ }
283
+ }
284
+ function kick(source, work) {
285
+ if (stopped || !enabled)
286
+ return;
287
+ jobs.set(source, work);
288
+ if (inflight.has(source)) {
289
+ pending.add(source);
290
+ return;
291
+ }
292
+ startJob(source);
293
+ }
294
+ function startJob(source) {
295
+ const work = jobs.get(source);
296
+ if (!work || stopped || !enabled || inflight.has(source))
297
+ return;
298
+ const token = ++generation;
299
+ inflight.set(source, token);
300
+ tickCount += 1;
301
+ const timeout = timers.setTimeout(() => {
302
+ if (inflight.get(source) !== token)
303
+ return;
304
+ inflight.delete(source);
305
+ runTimeouts.delete(source);
306
+ recordFailure(source, "collector run timed out");
307
+ resumePending(source);
308
+ }, RUN_TIMEOUT_MS[source]);
309
+ runTimeouts.set(source, timeout);
310
+ void Promise.resolve()
311
+ .then(work)
312
+ .catch(() => {
313
+ if (inflight.get(source) !== token)
314
+ return;
315
+ recordFailure(source, "collector run failed");
316
+ })
317
+ .finally(() => {
318
+ if (inflight.get(source) === token) {
319
+ inflight.delete(source);
320
+ clearRunTimeout(source);
321
+ }
322
+ resumePending(source);
323
+ });
324
+ }
325
+ function resumePending(source) {
326
+ if (inflight.has(source) || stopped || !enabled)
327
+ return;
328
+ if (!pending.has(source))
329
+ return;
330
+ pending.delete(source);
331
+ startJob(source);
332
+ }
333
+ function clearRunTimeout(source) {
334
+ const handle = runTimeouts.get(source);
335
+ if (handle === undefined)
336
+ return;
337
+ runTimeouts.delete(source);
338
+ timers.clearTimeout(handle);
339
+ }
340
+ function recordFailure(source, error) {
341
+ if (source === "rebuild") {
342
+ log(error === "collector run timed out" ? "rebuild timed out" : `rebuild skipped: ${error}`);
343
+ return;
344
+ }
345
+ const meta = SOURCE_EVENT[source];
346
+ const cliVersion = seenVersions.get(meta.provider) ?? "unknown";
347
+ const observedAt = now().toISOString();
348
+ const event = {
349
+ event: "parser_failure",
350
+ observedAt,
351
+ provider: meta.provider,
352
+ source: meta.source,
353
+ error,
354
+ cliVersion,
355
+ parserVersion: "capacity-scheduler.1",
356
+ };
357
+ try {
358
+ appendParserFailure(event, ledger());
359
+ }
360
+ catch {
361
+ log(`${source} failure was not recorded`);
362
+ }
363
+ reportFailures([event]);
364
+ }
365
+ function reportFailures(failures) {
366
+ for (const failure of failures) {
367
+ try {
368
+ sentry({
369
+ provider: failure.provider,
370
+ source: failure.source,
371
+ cliVersion: failure.cliVersion ?? "unknown",
372
+ });
373
+ }
374
+ catch {
375
+ // Sentry must not stop collection.
376
+ }
377
+ }
378
+ }
379
+ function noteVersion(provider, version) {
380
+ if (!version || version === "unknown")
381
+ return;
382
+ if (seenVersions.get(provider) === version)
383
+ return;
384
+ seenVersions.set(provider, version);
385
+ saveSeenVersions();
386
+ log(`cli ${provider} ${version}`);
387
+ let ok = true;
388
+ try {
389
+ const result = selfTest(provider, version);
390
+ if (result && typeof result === "object" && result.ok === false)
391
+ ok = false;
392
+ }
393
+ catch {
394
+ ok = false;
395
+ }
396
+ log(`self-test ${provider} ${version} ${ok ? "pass" : "fail"}`);
397
+ }
398
+ function noteResultVersions(provider, readings, failures) {
399
+ const version = readings.find((row) => row.cliVersion)?.cliVersion
400
+ ?? failures.find((row) => row.cliVersion)?.cliVersion;
401
+ if (version)
402
+ noteVersion(provider, version);
403
+ }
404
+ function accountGroups(provider) {
405
+ let rows;
406
+ try {
407
+ rows = listCapacityAccounts({ home, hqRoot: options.hqRoot }).filter((account) => account.provider === provider);
408
+ }
409
+ catch (err) {
410
+ const message = err instanceof Error ? err.message : String(err);
411
+ log(`capacity accounts ${provider} failed: ${message.slice(0, 120)}`);
412
+ rows = [];
413
+ }
414
+ if (rows.length === 0) {
415
+ const dir = provider === "claude" ? ".claude" : provider === "codex" ? ".codex" : ".grok";
416
+ rows = [{ label: "default", home: path.join(home, dir) }];
417
+ }
418
+ const grouped = new Map();
419
+ for (const row of rows) {
420
+ const key = path.resolve(row.home);
421
+ const group = grouped.get(key) ?? { home: row.home, labels: [] };
422
+ if (!group.labels.includes(row.label))
423
+ group.labels.push(row.label);
424
+ grouped.set(key, group);
425
+ }
426
+ return [...grouped.values()].map((group) => ({
427
+ home: group.home,
428
+ labels: group.labels,
429
+ label: group.labels.find((label) => label !== "default") ?? group.labels[0] ?? "default",
430
+ }));
431
+ }
432
+ function flatAccounts(provider) {
433
+ return accountGroups(provider).flatMap((group) => group.labels.map((label) => ({ label, home: group.home })));
434
+ }
435
+ function codexHome() {
436
+ return path.join(home, ".codex");
437
+ }
438
+ function beginAbort() {
439
+ const controller = new AbortController();
440
+ aborts.add(controller);
441
+ return controller.signal;
442
+ }
443
+ async function processSnapshot() {
444
+ if (options.ps)
445
+ return options.ps();
446
+ return listProcesses();
447
+ }
448
+ async function cachedVersion(provider, read) {
449
+ const hit = versionCache.get(provider);
450
+ if (hit && Date.now() - hit.at < VERSION_TTL_MS)
451
+ return hit.version;
452
+ let version;
453
+ try {
454
+ version = await read();
455
+ }
456
+ catch {
457
+ version = "unknown";
458
+ }
459
+ versionCache.set(provider, { version, at: Date.now() });
460
+ return version;
461
+ }
462
+ function enqueueBackoff(apply) {
463
+ backoffWrite = backoffWrite.then(() => {
464
+ apply();
465
+ }).catch(() => undefined);
466
+ }
467
+ async function runCodexTail() {
468
+ const cliVersion = await cachedVersion("codex", () => collectors.readInstalledCodexVersion());
469
+ noteVersion("codex", cliVersion);
470
+ if (!enumerateAccounts) {
471
+ const accountHome = codexHome();
472
+ const result = collectors.tailCodexSessions({
473
+ sessionsDir,
474
+ state: readTailState(),
475
+ cliVersion,
476
+ now: now(),
477
+ recentDays: 2,
478
+ accountId: `home:${path.resolve(accountHome)}`,
479
+ accountHome,
480
+ });
481
+ writeTailState(result.state);
482
+ collectors.ingestCodexResult(result, ledger());
483
+ reportFailures(result.failures);
484
+ return;
485
+ }
486
+ let state = readTailState();
487
+ for (const group of accountGroups("codex")) {
488
+ const dir = path.join(group.home, "sessions");
489
+ const result = collectors.tailCodexSessions({
490
+ sessionsDir: dir,
491
+ state,
492
+ cliVersion,
493
+ now: now(),
494
+ recentDays: 2,
495
+ accountId: `home:${path.resolve(group.home)}`,
496
+ accountLabel: group.label,
497
+ accountLabels: group.labels,
498
+ accountHome: group.home,
499
+ });
500
+ const prefix = path.resolve(dir);
501
+ const kept = {};
502
+ for (const [file, cursor] of Object.entries(state.files)) {
503
+ const resolved = path.resolve(file);
504
+ if (resolved !== prefix && !resolved.startsWith(`${prefix}${path.sep}`))
505
+ kept[file] = cursor;
506
+ }
507
+ state = { files: { ...kept, ...result.state.files } };
508
+ collectors.ingestCodexResult(result, ledger());
509
+ reportFailures(result.failures);
510
+ }
511
+ writeTailState(state);
512
+ }
513
+ async function runCodexAppServer() {
514
+ const clock = now();
515
+ const signal = beginAbort();
516
+ const processes = await processSnapshot();
517
+ const running = collectors.isCodexProcessRunning(processes);
518
+ if (!enumerateAccounts) {
519
+ if (backoffActive(clock.getTime()))
520
+ return;
521
+ const lastWrite = collectors.latestCodexSessionWriteAt(sessionsDir, 2);
522
+ if (!collectors.shouldReadAppServer({
523
+ lastSessionWriteAt: lastWrite,
524
+ codexProcessRunning: running,
525
+ now: clock.getTime(),
526
+ }))
527
+ return;
528
+ const cliVersion = await cachedVersion("codex", () => collectors.readInstalledCodexVersion());
529
+ noteVersion("codex", cliVersion);
530
+ const accountHome = codexHome();
531
+ const result = await collectors.readCodexAppServer({
532
+ cliVersion,
533
+ now: clock,
534
+ signal,
535
+ accountId: `home:${path.resolve(accountHome)}`,
536
+ accountHome,
537
+ timeoutMs: 20_000,
538
+ });
539
+ if (!result.ok && result.backoffMs)
540
+ enqueueBackoff(() => writeBackoff(clock.getTime() + (result.backoffMs ?? 0)));
541
+ if (result.ok)
542
+ enqueueBackoff(() => clearBackoff());
543
+ collectors.ingestCodexResult(result, ledger());
544
+ reportFailures(result.failures);
545
+ await backoffWrite;
546
+ return;
547
+ }
548
+ const cliVersion = await cachedVersion("codex", () => collectors.readInstalledCodexVersion());
549
+ noteVersion("codex", cliVersion);
550
+ await Promise.all(accountGroups("codex").map(async (group) => {
551
+ if (signal.aborted || backoffActiveFor(group.label, clock.getTime()))
552
+ return;
553
+ const dir = path.join(group.home, "sessions");
554
+ const lastWrite = collectors.latestCodexSessionWriteAt(dir, 2);
555
+ if (!collectors.shouldReadAppServer({
556
+ lastSessionWriteAt: lastWrite,
557
+ codexProcessRunning: running,
558
+ now: clock.getTime(),
559
+ }))
560
+ return;
561
+ const result = await collectors.readCodexAppServer({
562
+ cliVersion,
563
+ now: clock,
564
+ signal,
565
+ home: group.home,
566
+ accountId: `home:${path.resolve(group.home)}`,
567
+ accountLabel: group.label,
568
+ accountLabels: group.labels,
569
+ accountHome: group.home,
570
+ timeoutMs: 20_000,
571
+ });
572
+ if (!result.ok && result.backoffMs) {
573
+ enqueueBackoff(() => writeBackoffFor(group.label, clock.getTime() + (result.backoffMs ?? 0)));
574
+ }
575
+ if (result.ok)
576
+ enqueueBackoff(() => clearBackoffFor(group.label));
577
+ collectors.ingestCodexResult(result, ledger());
578
+ reportFailures(result.failures);
579
+ }));
580
+ await backoffWrite;
581
+ }
582
+ async function runStatusline() {
583
+ const installed = await cachedVersion("claude", () => collectors.readInstalledClaudeVersion());
584
+ noteVersion("claude", installed);
585
+ const result = collectors.consumeClaudeStatuslineSpool({
586
+ root,
587
+ now: now(),
588
+ ...(enumerateAccounts ? { accounts: flatAccounts("claude") } : {}),
589
+ });
590
+ noteResultVersions("claude", result.readings, result.failures);
591
+ reportFailures(result.failures);
592
+ }
593
+ async function runClaudeOAuth() {
594
+ const installed = await cachedVersion("claude", () => collectors.readInstalledClaudeVersion());
595
+ noteVersion("claude", installed);
596
+ const result = await collectors.collectClaudeOAuth({
597
+ ledger: ledger(),
598
+ now,
599
+ cliVersion: installed,
600
+ ...(enumerateAccounts ? { accounts: flatAccounts("claude") } : {}),
601
+ });
602
+ noteResultVersions("claude", result.readings, result.failures);
603
+ reportFailures(result.failures);
604
+ }
605
+ async function runGrok() {
606
+ const installed = await cachedVersion("grok", () => collectors.readInstalledGrokVersion());
607
+ noteVersion("grok", installed);
608
+ const result = await collectors.collectGrok({
609
+ ledger: ledger(),
610
+ now,
611
+ cliVersion: installed,
612
+ ...(enumerateAccounts ? { accounts: flatAccounts("grok") } : {}),
613
+ });
614
+ noteResultVersions("grok", result.readings, result.failures);
615
+ reportFailures(result.failures);
616
+ }
617
+ async function runRebuild() {
618
+ try {
619
+ rebuildNow(ledger());
620
+ }
621
+ catch (err) {
622
+ if (err instanceof LedgerBusy) {
623
+ log("rebuild skipped: ledger busy");
624
+ return;
625
+ }
626
+ throw err;
627
+ }
628
+ }
629
+ function loadSeenVersions() {
630
+ const parsed = readJson(path.join(root, SEEN_VERSIONS_FILE));
631
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
632
+ return;
633
+ for (const [key, value] of Object.entries(parsed)) {
634
+ if (typeof value === "string" && value)
635
+ seenVersions.set(key, value);
636
+ }
637
+ }
638
+ function saveSeenVersions() {
639
+ writeJson(path.join(root, SEEN_VERSIONS_FILE), Object.fromEntries(seenVersions));
640
+ }
641
+ function readTailState() {
642
+ const parsed = readJson(path.join(root, TAIL_STATE_FILE));
643
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
644
+ return { files: {} };
645
+ const files = parsed.files;
646
+ if (!files || typeof files !== "object" || Array.isArray(files))
647
+ return { files: {} };
648
+ return { files: files };
649
+ }
650
+ function writeTailState(state) {
651
+ writeJson(path.join(root, TAIL_STATE_FILE), state);
652
+ }
653
+ function backoffActive(nowMs) {
654
+ const parsed = readJson(path.join(root, APP_SERVER_BACKOFF_FILE));
655
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
656
+ return false;
657
+ const until = parsed.until;
658
+ return typeof until === "number" && until > nowMs;
659
+ }
660
+ function writeBackoff(until) {
661
+ writeJson(path.join(root, APP_SERVER_BACKOFF_FILE), { until });
662
+ }
663
+ function clearBackoff() {
664
+ const dest = path.join(root, APP_SERVER_BACKOFF_FILE);
665
+ try {
666
+ if (fs.existsSync(dest))
667
+ fs.rmSync(dest);
668
+ }
669
+ catch {
670
+ // The next read treats a bad file as no backoff.
671
+ }
672
+ }
673
+ function backoffRecord() {
674
+ const parsed = readJson(path.join(root, APP_SERVER_BACKOFF_FILE));
675
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
676
+ return null;
677
+ return parsed;
678
+ }
679
+ function backoffActiveFor(label, nowMs) {
680
+ const record = backoffRecord();
681
+ if (!record)
682
+ return false;
683
+ if (label === "default" && typeof record.until === "number" && record.until > nowMs)
684
+ return true;
685
+ const accounts = record.accounts;
686
+ if (!accounts || typeof accounts !== "object" || Array.isArray(accounts))
687
+ return false;
688
+ const until = accounts[label];
689
+ return typeof until === "number" && until > nowMs;
690
+ }
691
+ function writeBackoffFor(label, until) {
692
+ const record = backoffRecord() ?? {};
693
+ const accounts = record.accounts && typeof record.accounts === "object" && !Array.isArray(record.accounts)
694
+ ? { ...record.accounts }
695
+ : {};
696
+ accounts[label] = until;
697
+ writeJson(path.join(root, APP_SERVER_BACKOFF_FILE), { ...record, accounts });
698
+ }
699
+ function clearBackoffFor(label) {
700
+ const record = backoffRecord();
701
+ if (!record)
702
+ return;
703
+ const next = { ...record };
704
+ if (label === "default")
705
+ delete next.until;
706
+ if (next.accounts && typeof next.accounts === "object" && !Array.isArray(next.accounts)) {
707
+ const accounts = { ...next.accounts };
708
+ delete accounts[label];
709
+ next.accounts = accounts;
710
+ }
711
+ writeJson(path.join(root, APP_SERVER_BACKOFF_FILE), next);
712
+ }
713
+ function stop() {
714
+ if (stopped)
715
+ return;
716
+ stopped = true;
717
+ enabled = false;
718
+ for (const controller of aborts)
719
+ controller.abort();
720
+ aborts.clear();
721
+ if (slowHandle !== null)
722
+ timers.clearTimeout(slowHandle);
723
+ slowHandle = null;
724
+ clearFast();
725
+ for (const handle of runTimeouts.values())
726
+ timers.clearTimeout(handle);
727
+ runTimeouts.clear();
728
+ pending.clear();
729
+ }
730
+ try {
731
+ syncEnabled();
732
+ }
733
+ catch {
734
+ enabled = false;
735
+ }
736
+ armSlow();
737
+ return { stop, ticks: () => tickCount };
738
+ }
739
+ function readFixture(dir, name) {
740
+ const dest = path.join(dir, name);
741
+ if (!fs.existsSync(dest))
742
+ return null;
743
+ try {
744
+ return JSON.parse(fs.readFileSync(dest, "utf8"));
745
+ }
746
+ catch {
747
+ return null;
748
+ }
749
+ }
750
+ function readJson(dest) {
751
+ try {
752
+ return JSON.parse(fs.readFileSync(dest, "utf8"));
753
+ }
754
+ catch {
755
+ return null;
756
+ }
757
+ }
758
+ function writeJson(dest, value) {
759
+ const tmp = `${dest}.${process.pid}.${Date.now()}.tmp`;
760
+ try {
761
+ fs.mkdirSync(path.dirname(dest), { recursive: true, mode: DIR_MODE });
762
+ fs.writeFileSync(tmp, `${JSON.stringify(value)}\n`, { mode: FILE_MODE });
763
+ fs.renameSync(tmp, dest);
764
+ }
765
+ catch {
766
+ try {
767
+ if (fs.existsSync(tmp))
768
+ fs.rmSync(tmp);
769
+ }
770
+ catch {
771
+ // The next tick retries the state write.
772
+ }
773
+ }
774
+ }
775
+ function defaultWatch(target, options, listener) {
776
+ return fs.watch(target, options, listener);
777
+ }
778
+ function listProcesses() {
779
+ return new Promise((resolve) => {
780
+ execFile("ps", ["-ax", "-o", "command="], { timeout: 5_000, encoding: "utf8", windowsHide: true }, (error, stdout, stderr) => {
781
+ if (error && !stdout && !stderr) {
782
+ resolve("");
783
+ return;
784
+ }
785
+ resolve(`${stdout ?? ""}\n${stderr ?? ""}`);
786
+ });
787
+ });
788
+ }
789
+ function defaultSentry(event) {
790
+ void import("../../sentry.js").then(({ Sentry }) => {
791
+ Sentry.withScope((scope) => {
792
+ scope.setTag("provider", event.provider);
793
+ scope.setTag("source", event.source);
794
+ scope.setTag("cliVersion", event.cliVersion);
795
+ scope.setFingerprint(["capacity-parser-failure", event.provider, event.source, event.cliVersion]);
796
+ Sentry.captureMessage("capacity parser_failure");
797
+ });
798
+ }).catch(() => undefined);
799
+ }
800
+ //# sourceMappingURL=scheduler.js.map