sidekick-shared 0.21.2 → 0.21.4

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 (77) hide show
  1. package/README.md +60 -45
  2. package/dist/accountManager.js +9 -7
  3. package/dist/accountRegistry.d.ts +17 -0
  4. package/dist/accountRegistry.js +18 -14
  5. package/dist/accountStatus.js +7 -7
  6. package/dist/accounts.d.ts +13 -0
  7. package/dist/accounts.js +110 -25
  8. package/dist/aggregation/EventAggregator.d.ts +14 -11
  9. package/dist/aggregation/EventAggregator.js +68 -31
  10. package/dist/aggregation/PatternExtractor.js +5 -4
  11. package/dist/autoSwitch.js +6 -12
  12. package/dist/claudeProfiles.js +3 -3
  13. package/dist/codexProfiles.d.ts +15 -1
  14. package/dist/codexProfiles.js +96 -22
  15. package/dist/codexQuota.js +44 -16
  16. package/dist/codexQuotaWatcher.js +19 -4
  17. package/dist/context/composer.js +8 -6
  18. package/dist/context/sessionContext.js +12 -9
  19. package/dist/credentialIO.js +10 -8
  20. package/dist/extractors/sessionAssets.js +9 -1
  21. package/dist/extractors/sources/claudeAssets.js +7 -1
  22. package/dist/extractors/sources/codexAssets.js +14 -3
  23. package/dist/extractors/toolCall.js +1 -4
  24. package/dist/formatters/eventHighlighter.js +148 -28
  25. package/dist/formatters/noiseClassifier.js +3 -11
  26. package/dist/formatters/sessionDump.js +22 -13
  27. package/dist/formatters/toolSummary.js +1 -3
  28. package/dist/index.d.ts +26 -26
  29. package/dist/index.js +10 -4
  30. package/dist/modelContext.js +10 -8
  31. package/dist/modelInfo.js +11 -6
  32. package/dist/multiProviderQuotaService.d.ts +2 -2
  33. package/dist/multiProviderQuotaService.js +14 -10
  34. package/dist/parsers/codexParser.js +170 -103
  35. package/dist/parsers/debugLogParser.js +5 -4
  36. package/dist/parsers/openCodeParser.js +66 -48
  37. package/dist/parsers/planExtractor.js +3 -3
  38. package/dist/parsers/sessionActivityDetector.js +10 -6
  39. package/dist/parsers/sessionPathResolver.js +29 -34
  40. package/dist/parsers/subagentScanner.js +21 -20
  41. package/dist/parsers/subagentTraceParser.js +7 -4
  42. package/dist/phrases.js +11 -11
  43. package/dist/providerStatus.js +3 -4
  44. package/dist/providers/claudeCode.js +3 -1
  45. package/dist/providers/codex.js +35 -14
  46. package/dist/providers/codexDatabase.js +14 -5
  47. package/dist/providers/detect.js +10 -6
  48. package/dist/providers/openCode.js +91 -63
  49. package/dist/providers/openCodeDatabase.js +44 -17
  50. package/dist/quota.d.ts +19 -0
  51. package/dist/quota.js +50 -8
  52. package/dist/quotaHistory.js +10 -3
  53. package/dist/quotaSnapshots.js +2 -2
  54. package/dist/readers/decisions.js +1 -1
  55. package/dist/readers/notes.js +5 -3
  56. package/dist/readers/plans.js +43 -23
  57. package/dist/readers/tasks.js +1 -1
  58. package/dist/report/htmlHelpers.js +135 -16
  59. package/dist/report/htmlReportGenerator.js +38 -12
  60. package/dist/report/transcriptParser.js +8 -2
  61. package/dist/schemas/index.d.ts +4 -4
  62. package/dist/schemas/quota.js +2 -10
  63. package/dist/schemas/sessionEvent.js +20 -10
  64. package/dist/search/advancedFilter.js +3 -3
  65. package/dist/search/sessionSearch.js +10 -4
  66. package/dist/terminalSync.js +4 -2
  67. package/dist/turns/assistantTurn.js +5 -1
  68. package/dist/watchers/eventBridge.js +56 -26
  69. package/dist/watchers/factory.js +5 -2
  70. package/dist/watchers/jsonlTail.js +3 -1
  71. package/dist/watchers/jsonlWatcher.js +128 -52
  72. package/dist/watchers/providerReaderWatcher.js +3 -1
  73. package/dist/watchers/sqliteWatcher.js +52 -22
  74. package/dist/zaiQuota.js +6 -10
  75. package/dist/zaiQuotaApi.js +13 -5
  76. package/dist/zaiQuotaWatcher.js +1 -1
  77. package/package.json +5 -2
package/dist/accounts.js CHANGED
@@ -50,6 +50,7 @@ exports.reconcileClaudeAuthState = reconcileClaudeAuthState;
50
50
  exports.removeAccount = removeAccount;
51
51
  exports.listAccounts = listAccounts;
52
52
  exports.getActiveAccount = getActiveAccount;
53
+ exports.resolveActiveClaudeAccount = resolveActiveClaudeAccount;
53
54
  exports.isMultiAccountEnabled = isMultiAccountEnabled;
54
55
  const fs = __importStar(require("fs"));
55
56
  const path = __importStar(require("path"));
@@ -69,7 +70,9 @@ function getClaudeConfigPath() {
69
70
  if (data?.oauthAccount)
70
71
  return primary;
71
72
  }
72
- catch { /* fall through */ }
73
+ catch {
74
+ /* fall through */
75
+ }
73
76
  // Fallback: ~/.claude.json (some Claude Code versions store config here)
74
77
  return path.join(os.homedir(), '.claude.json');
75
78
  }
@@ -193,8 +196,8 @@ function readAccountRegistry() {
193
196
  if (!registry)
194
197
  return null;
195
198
  const claudeAccounts = registry.accounts
196
- .filter(account => account.providerId === 'claude-code')
197
- .map(account => ({
199
+ .filter((account) => account.providerId === 'claude-code')
200
+ .map((account) => ({
198
201
  uuid: account.providerAccountId ?? account.id,
199
202
  email: account.email ?? account.metadata?.email ?? 'unknown',
200
203
  label: account.label,
@@ -207,7 +210,7 @@ function readAccountRegistry() {
207
210
  };
208
211
  }
209
212
  function writeAccountRegistry(registry) {
210
- const mappedClaudeProfiles = registry.accounts.map(account => ({
213
+ const mappedClaudeProfiles = registry.accounts.map((account) => ({
211
214
  id: account.uuid,
212
215
  providerId: 'claude-code',
213
216
  providerAccountId: account.uuid,
@@ -240,7 +243,10 @@ function addCurrentAccount(label) {
240
243
  // 1. Read current Claude identity
241
244
  const active = readActiveClaudeAccount();
242
245
  if (!active) {
243
- return { success: false, error: 'No active Claude account found. Sign in with `claude` first.' };
246
+ return {
247
+ success: false,
248
+ error: 'No active Claude account found. Sign in with `claude` first.',
249
+ };
244
250
  }
245
251
  // 2. Read current credentials (platform-aware: Keychain on macOS, file on Linux)
246
252
  const credBlob = (0, credentialIO_1.readActiveCredentials)();
@@ -264,7 +270,7 @@ function addCurrentAccount(label) {
264
270
  accounts: [],
265
271
  };
266
272
  // 5. Check if account already exists — update credentials + label
267
- const existing = registry.accounts.find(a => a.uuid === active.uuid);
273
+ const existing = registry.accounts.find((a) => a.uuid === active.uuid);
268
274
  if (existing) {
269
275
  if (label !== undefined)
270
276
  existing.label = label;
@@ -273,7 +279,9 @@ function addCurrentAccount(label) {
273
279
  try {
274
280
  writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
275
281
  }
276
- catch { /* flat backups remain available */ }
282
+ catch {
283
+ /* flat backups remain available */
284
+ }
277
285
  registry.activeAccountUuid = active.uuid;
278
286
  writeAccountRegistry(registry);
279
287
  return { success: true };
@@ -284,7 +292,9 @@ function addCurrentAccount(label) {
284
292
  try {
285
293
  writeClaudeProfileMirror(active.uuid, credBlob, configBlob);
286
294
  }
287
- catch { /* flat backups remain available */ }
295
+ catch {
296
+ /* flat backups remain available */
297
+ }
288
298
  // 7. Add entry
289
299
  registry.accounts.push({
290
300
  uuid: active.uuid,
@@ -302,7 +312,7 @@ function switchToAccount(uuid) {
302
312
  if (!registry) {
303
313
  return { success: false, error: 'No account registry found. Save an account first.' };
304
314
  }
305
- const target = registry.accounts.find(a => a.uuid === uuid);
315
+ const target = registry.accounts.find((a) => a.uuid === uuid);
306
316
  if (!target) {
307
317
  return { success: false, error: `Account ${uuid} not found in registry.` };
308
318
  }
@@ -313,11 +323,15 @@ function switchToAccount(uuid) {
313
323
  try {
314
324
  originalConfig = fs.readFileSync(getClaudeConfigPath(), 'utf8');
315
325
  }
316
- catch { /* no existing config to back up */ }
326
+ catch {
327
+ /* no existing config to back up */
328
+ }
317
329
  try {
318
330
  backupCurrentClaudeLiveHome();
319
331
  }
320
- catch { /* best effort */ }
332
+ catch {
333
+ /* best effort */
334
+ }
321
335
  try {
322
336
  (0, accountRegistry_1.setActiveSavedAccount)('claude-code', uuid);
323
337
  }
@@ -329,18 +343,24 @@ function switchToAccount(uuid) {
329
343
  try {
330
344
  (0, accountRegistry_1.setActiveSavedAccount)('claude-code', registry.activeAccountUuid);
331
345
  }
332
- catch { /* rollback failed */ }
346
+ catch {
347
+ /* rollback failed */
348
+ }
333
349
  if (originalCreds) {
334
350
  try {
335
351
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
336
352
  }
337
- catch { /* rollback failed */ }
353
+ catch {
354
+ /* rollback failed */
355
+ }
338
356
  }
339
357
  if (originalConfig) {
340
358
  try {
341
359
  fs.writeFileSync(getClaudeConfigPath(), originalConfig);
342
360
  }
343
- catch { /* rollback failed */ }
361
+ catch {
362
+ /* rollback failed */
363
+ }
344
364
  }
345
365
  return applied;
346
366
  }
@@ -367,7 +387,9 @@ function applyActiveClaudeToLiveHome() {
367
387
  try {
368
388
  originalConfig = fs.readFileSync(getClaudeConfigPath(), 'utf8');
369
389
  }
370
- catch { /* no existing config to back up */ }
390
+ catch {
391
+ /* no existing config to back up */
392
+ }
371
393
  try {
372
394
  (0, credentialIO_1.writeActiveCredentials)(stored.credentials);
373
395
  }
@@ -376,7 +398,9 @@ function applyActiveClaudeToLiveHome() {
376
398
  try {
377
399
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
378
400
  }
379
- catch { /* rollback failed */ }
401
+ catch {
402
+ /* rollback failed */
403
+ }
380
404
  }
381
405
  return { success: false, error: `Failed to write credentials: ${err}` };
382
406
  }
@@ -388,13 +412,17 @@ function applyActiveClaudeToLiveHome() {
388
412
  try {
389
413
  (0, credentialIO_1.writeActiveCredentials)(originalCreds);
390
414
  }
391
- catch { /* rollback failed */ }
415
+ catch {
416
+ /* rollback failed */
417
+ }
392
418
  }
393
419
  if (originalConfig) {
394
420
  try {
395
421
  fs.writeFileSync(getClaudeConfigPath(), originalConfig);
396
422
  }
397
- catch { /* rollback failed */ }
423
+ catch {
424
+ /* rollback failed */
425
+ }
398
426
  }
399
427
  return { success: false, error: `Failed to write config: ${err}` };
400
428
  }
@@ -407,10 +435,11 @@ function reconcileClaudeAuthState() {
407
435
  return;
408
436
  const registry = (0, accountRegistry_1.readSavedAccountRegistry)();
409
437
  if (registry) {
410
- for (const profile of registry.accounts.filter(account => account.providerId === 'claude-code')) {
438
+ for (const profile of registry.accounts.filter((account) => account.providerId === 'claude-code')) {
411
439
  try {
412
440
  const uuid = profile.providerAccountId ?? profile.id;
413
- if ((0, credentialIO_1.readActiveCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(uuid)) && readProfileClaudeOauthAccount(uuid)) {
441
+ if ((0, credentialIO_1.readActiveCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(uuid)) &&
442
+ readProfileClaudeOauthAccount(uuid)) {
414
443
  continue;
415
444
  }
416
445
  const credentials = readFlatClaudeCredentials(uuid);
@@ -425,7 +454,10 @@ function reconcileClaudeAuthState() {
425
454
  }
426
455
  }
427
456
  fs.mkdirSync(path.dirname(markerPath), { recursive: true, mode: 0o700 });
428
- fs.writeFileSync(markerPath, new Date().toISOString() + '\n', { encoding: 'utf8', mode: 0o600 });
457
+ fs.writeFileSync(markerPath, new Date().toISOString() + '\n', {
458
+ encoding: 'utf8',
459
+ mode: 0o600,
460
+ });
429
461
  }
430
462
  catch {
431
463
  // Reconciliation must never break startup.
@@ -437,7 +469,7 @@ function removeAccount(uuid) {
437
469
  if (!registry) {
438
470
  return { success: false, error: 'No account registry found.' };
439
471
  }
440
- const idx = registry.accounts.findIndex(a => a.uuid === uuid);
472
+ const idx = registry.accounts.findIndex((a) => a.uuid === uuid);
441
473
  if (idx === -1) {
442
474
  return { success: false, error: `Account ${uuid} not found.` };
443
475
  }
@@ -445,11 +477,15 @@ function removeAccount(uuid) {
445
477
  try {
446
478
  fs.unlinkSync(path.join(getCredentialsDir(), `${uuid}.credentials.json`));
447
479
  }
448
- catch { /* ok */ }
480
+ catch {
481
+ /* ok */
482
+ }
449
483
  try {
450
484
  fs.unlinkSync(path.join(getConfigsDir(), `${uuid}.config.json`));
451
485
  }
452
- catch { /* ok */ }
486
+ catch {
487
+ /* ok */
488
+ }
453
489
  // Remove from registry
454
490
  registry.accounts.splice(idx, 1);
455
491
  if (registry.activeAccountUuid === uuid) {
@@ -460,7 +496,7 @@ function removeAccount(uuid) {
460
496
  }
461
497
  // ── Query helpers ────────────────────────────────────────────────────────
462
498
  function listAccounts() {
463
- return (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map(account => ({
499
+ return (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').map((account) => ({
464
500
  uuid: account.providerAccountId ?? account.id,
465
501
  email: account.email ?? account.metadata?.email ?? 'unknown',
466
502
  label: account.label,
@@ -478,6 +514,55 @@ function getActiveAccount() {
478
514
  addedAt: active.addedAt,
479
515
  };
480
516
  }
517
+ /**
518
+ * Resolves the *currently logged-in* Claude account for display, preferring the
519
+ * live `~/.claude/.claude.json` identity over the saved registry pointer (which
520
+ * only sidekick's own switch flow updates and therefore goes stale after a native
521
+ * `claude /login`).
522
+ *
523
+ * Safe self-heal: when the live account matches a saved profile (by account UUID,
524
+ * email as fallback) that isn't the current active pointer, the pointer is
525
+ * re-pointed so registry-keyed data tracks reality too. Never creates or deletes
526
+ * profiles; an unknown live account is shown as-is with no label and no write.
527
+ */
528
+ function resolveActiveClaudeAccount() {
529
+ const live = readActiveClaudeAccount();
530
+ if (live) {
531
+ const profiles = (0, accountRegistry_1.listSavedAccountProfiles)('claude-code');
532
+ const match = profiles.find((p) => (p.providerAccountId ?? p.id) === live.uuid) ??
533
+ profiles.find((p) => (p.email ?? p.metadata?.email) === live.email);
534
+ if (match) {
535
+ const active = (0, accountRegistry_1.getActiveSavedAccount)('claude-code');
536
+ if (!active || active.id !== match.id) {
537
+ // Self-heal is best-effort: a registry write failure (read-only/full
538
+ // disk) must never break display or extension activation. We still
539
+ // return the correct live identity below.
540
+ try {
541
+ (0, accountRegistry_1.setActiveSavedAccount)('claude-code', match.id);
542
+ }
543
+ catch {
544
+ /* keep going with the live identity */
545
+ }
546
+ }
547
+ }
548
+ return {
549
+ email: live.email,
550
+ label: match?.label,
551
+ providerAccountId: live.uuid,
552
+ source: 'live',
553
+ };
554
+ }
555
+ const active = getActiveAccount();
556
+ if (active) {
557
+ return {
558
+ email: active.email,
559
+ label: active.label,
560
+ providerAccountId: active.uuid,
561
+ source: 'registry',
562
+ };
563
+ }
564
+ return { source: 'none' };
565
+ }
481
566
  function isMultiAccountEnabled() {
482
567
  return (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').length >= 1;
483
568
  }
@@ -26,17 +26,20 @@ export interface SerializedAggregatorState {
26
26
  cacheRead: number;
27
27
  reportedCost: number;
28
28
  };
29
- modelUsage: Array<[string, {
30
- calls: number;
31
- tokens: number;
32
- inputTokens: number;
33
- outputTokens: number;
34
- cacheWriteTokens: number;
35
- cacheReadTokens: number;
36
- cost: number;
37
- reasoningTokens?: number;
38
- priced?: boolean;
39
- }]>;
29
+ modelUsage: Array<[
30
+ string,
31
+ {
32
+ calls: number;
33
+ tokens: number;
34
+ inputTokens: number;
35
+ outputTokens: number;
36
+ cacheWriteTokens: number;
37
+ cacheReadTokens: number;
38
+ cost: number;
39
+ reasoningTokens?: number;
40
+ priced?: boolean;
41
+ }
42
+ ]>;
40
43
  contextSize: number;
41
44
  previousContextSize: number;
42
45
  compactionEvents: CompactionEvent[];
@@ -42,7 +42,10 @@ function parseTodoDependencies(content, allTodos) {
42
42
  const match = content.match(depPattern);
43
43
  if (!match)
44
44
  return [];
45
- const refs = match[1].split(/\s+and\s+|,\s*|&\s*/).map(r => r.trim()).filter(Boolean);
45
+ const refs = match[1]
46
+ .split(/\s+and\s+|,\s*|&\s*/)
47
+ .map((r) => r.trim())
48
+ .filter(Boolean);
46
49
  const result = [];
47
50
  for (const ref of refs) {
48
51
  const refLower = ref.toLowerCase();
@@ -235,7 +238,8 @@ class EventAggregator {
235
238
  contextSize = inputTok + cacheWrite + cacheRead;
236
239
  }
237
240
  // Compaction detection
238
- if (this.previousContextSize > 0 && contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
241
+ if (this.previousContextSize > 0 &&
242
+ contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
239
243
  this.compactionEvents.push({
240
244
  timestamp: new Date(event.timestamp),
241
245
  contextBefore: this.previousContextSize,
@@ -263,8 +267,14 @@ class EventAggregator {
263
267
  : 0;
264
268
  const evPriced = event.cost != null || pricing != null;
265
269
  const acc = this.modelUsage.get(model) ?? {
266
- calls: 0, tokens: 0, inputTokens: 0, outputTokens: 0,
267
- cacheWriteTokens: 0, cacheReadTokens: 0, cost: 0, priced: true,
270
+ calls: 0,
271
+ tokens: 0,
272
+ inputTokens: 0,
273
+ outputTokens: 0,
274
+ cacheWriteTokens: 0,
275
+ cacheReadTokens: 0,
276
+ cost: 0,
277
+ priced: true,
268
278
  };
269
279
  acc.calls++;
270
280
  acc.tokens += inputTok + outputTok;
@@ -339,8 +349,10 @@ class EventAggregator {
339
349
  .sort((a, b) => b.calls - a.calls);
340
350
  }
341
351
  getToolStats() {
342
- return Array.from(this.toolAnalytics.values())
343
- .sort((a, b) => (b.successCount + b.failureCount + b.pendingCount) - (a.successCount + a.failureCount + a.pendingCount));
352
+ return Array.from(this.toolAnalytics.values()).sort((a, b) => b.successCount +
353
+ b.failureCount +
354
+ b.pendingCount -
355
+ (a.successCount + a.failureCount + a.pendingCount));
344
356
  }
345
357
  getCompactionEvents() {
346
358
  return [...this.compactionEvents];
@@ -349,7 +361,7 @@ class EventAggregator {
349
361
  return [...this.truncationEvents];
350
362
  }
351
363
  getBurnRate() {
352
- const points = this.burnSamples.map(s => s.tokens);
364
+ const points = this.burnSamples.map((s) => s.tokens);
353
365
  const tokensPerMinute = points.length > 0 ? points[points.length - 1] : 0;
354
366
  return {
355
367
  tokensPerMinute,
@@ -384,7 +396,7 @@ class EventAggregator {
384
396
  const records = this.latencyRecords;
385
397
  const totalFirstToken = records.reduce((sum, r) => sum + r.firstTokenLatencyMs, 0);
386
398
  const totalResponse = records.reduce((sum, r) => sum + r.totalResponseTimeMs, 0);
387
- const maxFirstToken = Math.max(...records.map(r => r.firstTokenLatencyMs));
399
+ const maxFirstToken = Math.max(...records.map((r) => r.firstTokenLatencyMs));
388
400
  const last = records[records.length - 1];
389
401
  return {
390
402
  recentLatencies: [...records],
@@ -421,8 +433,8 @@ class EventAggregator {
421
433
  contextTimeline: [...this.contextTimeline],
422
434
  timeline: this.getTimeline(),
423
435
  latencyStats: this.getLatencyStats(),
424
- toolFrequency: this.toolFrequency.getTopN(20).map(e => ({ name: e.key, count: e.count })),
425
- wordFrequency: this.wordFrequency.getTopN(20).map(e => ({ name: e.key, count: e.count })),
436
+ toolFrequency: this.toolFrequency.getTopN(20).map((e) => ({ name: e.key, count: e.count })),
437
+ wordFrequency: this.wordFrequency.getTopN(20).map((e) => ({ name: e.key, count: e.count })),
426
438
  patterns: this.patternExtractor.getPatterns().slice(0, 20),
427
439
  heatmapBuckets: this.heatmapTracker.getBuckets(),
428
440
  };
@@ -560,7 +572,9 @@ class EventAggregator {
560
572
  this.activeTaskId = state.activeTaskId;
561
573
  this.subagents = [...state.subagents];
562
574
  this.currentPermissionMode = state.permissionMode ?? null;
563
- this.permissionModeHistory = state.permissionModeHistory ? [...state.permissionModeHistory] : [];
575
+ this.permissionModeHistory = state.permissionModeHistory
576
+ ? [...state.permissionModeHistory]
577
+ : [];
564
578
  this.contextTimeline = state.contextTimeline ? [...state.contextTimeline] : [];
565
579
  this.contextTurnIndex = state.contextTurnIndex ?? 0;
566
580
  this.timeline = [...state.timeline];
@@ -639,7 +653,8 @@ class EventAggregator {
639
653
  contextSize = inputTok + cacheWrite + cacheRead;
640
654
  }
641
655
  // Compaction detection
642
- if (this.previousContextSize > 0 && contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
656
+ if (this.previousContextSize > 0 &&
657
+ contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
643
658
  this.compactionEvents.push({
644
659
  timestamp: new Date(timestamp),
645
660
  contextBefore: this.previousContextSize,
@@ -659,9 +674,15 @@ class EventAggregator {
659
674
  // `priced` flag so one unpriced event taints the aggregate (UI renders "—").
660
675
  const perModelKey = model ?? this.currentModel ?? 'unknown';
661
676
  const acc = this.modelUsage.get(perModelKey) ?? {
662
- calls: 0, tokens: 0, inputTokens: 0, outputTokens: 0,
663
- cacheWriteTokens: 0, cacheReadTokens: 0, reasoningTokens: 0,
664
- cost: 0, priced: true,
677
+ calls: 0,
678
+ tokens: 0,
679
+ inputTokens: 0,
680
+ outputTokens: 0,
681
+ cacheWriteTokens: 0,
682
+ cacheReadTokens: 0,
683
+ reasoningTokens: 0,
684
+ cost: 0,
685
+ priced: true,
665
686
  };
666
687
  acc.calls++;
667
688
  acc.tokens += inputTok + outputTok;
@@ -696,7 +717,7 @@ class EventAggregator {
696
717
  this.lastBurnSampleTime = now;
697
718
  // Trim to window
698
719
  const cutoff = now - this.burnWindowMs;
699
- this.burnSamples = this.burnSamples.filter(s => s.time >= cutoff);
720
+ this.burnSamples = this.burnSamples.filter((s) => s.time >= cutoff);
700
721
  }
701
722
  }
702
723
  // ═══════════════════════════════════════════════════════════════════════
@@ -716,7 +737,8 @@ class EventAggregator {
716
737
  // First assistant response with text
717
738
  this.pendingUserRequest.firstResponseReceived = true;
718
739
  this.pendingUserRequest.firstResponseTimestamp = now;
719
- this.pendingUserRequest.firstTokenLatencyMs = now.getTime() - this.pendingUserRequest.timestamp.getTime();
740
+ this.pendingUserRequest.firstTokenLatencyMs =
741
+ now.getTime() - this.pendingUserRequest.timestamp.getTime();
720
742
  }
721
743
  if (event.message.usage && this.pendingUserRequest.firstResponseReceived) {
722
744
  // Assistant with usage -> complete the cycle
@@ -751,7 +773,12 @@ class EventAggregator {
751
773
  continue;
752
774
  // Record in toolAnalytics
753
775
  const analytics = this.toolAnalytics.get(name) ?? {
754
- name, successCount: 0, failureCount: 0, totalDuration: 0, completedCount: 0, pendingCount: 0,
776
+ name,
777
+ successCount: 0,
778
+ failureCount: 0,
779
+ totalDuration: 0,
780
+ completedCount: 0,
781
+ pendingCount: 0,
755
782
  };
756
783
  analytics.pendingCount++;
757
784
  this.toolAnalytics.set(name, analytics);
@@ -825,7 +852,12 @@ class EventAggregator {
825
852
  const name = event.toolName;
826
853
  // Update analytics
827
854
  const analytics = this.toolAnalytics.get(name) ?? {
828
- name, successCount: 0, failureCount: 0, totalDuration: 0, completedCount: 0, pendingCount: 0,
855
+ name,
856
+ successCount: 0,
857
+ failureCount: 0,
858
+ totalDuration: 0,
859
+ completedCount: 0,
860
+ pendingCount: 0,
829
861
  };
830
862
  analytics.pendingCount++;
831
863
  this.toolAnalytics.set(name, analytics);
@@ -1380,11 +1412,11 @@ class EventAggregator {
1380
1412
  }
1381
1413
  }
1382
1414
  convertExtractedPlanToPlanState(extracted) {
1383
- const completed = extracted.steps.filter(s => s.status === 'completed').length;
1415
+ const completed = extracted.steps.filter((s) => s.status === 'completed').length;
1384
1416
  const total = extracted.steps.length;
1385
1417
  return {
1386
1418
  active: true,
1387
- steps: extracted.steps.map(s => ({
1419
+ steps: extracted.steps.map((s) => ({
1388
1420
  id: s.id,
1389
1421
  description: s.description,
1390
1422
  status: s.status,
@@ -1406,7 +1438,9 @@ class EventAggregator {
1406
1438
  if (Array.isArray(content)) {
1407
1439
  for (const block of content) {
1408
1440
  if (block.type === 'tool_result') {
1409
- const text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content || '');
1441
+ const text = typeof block.content === 'string'
1442
+ ? block.content
1443
+ : JSON.stringify(block.content || '');
1410
1444
  this.contextAttribution.toolOutputs += this.estimateTokens(text);
1411
1445
  }
1412
1446
  else if (block.type === 'text') {
@@ -1470,7 +1504,9 @@ class EventAggregator {
1470
1504
  if (Array.isArray(content)) {
1471
1505
  for (const block of content) {
1472
1506
  if (block.type === 'tool_result') {
1473
- const text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content || '');
1507
+ const text = typeof block.content === 'string'
1508
+ ? block.content
1509
+ : JSON.stringify(block.content || '');
1474
1510
  this.contextAttribution.toolOutputs += this.estimateTokens(text);
1475
1511
  }
1476
1512
  else if (block.type === 'text') {
@@ -1521,8 +1557,10 @@ class EventAggregator {
1521
1557
  }
1522
1558
  else if (event.type === 'tool_result') {
1523
1559
  const rawContent = raw?.content;
1524
- const text = typeof rawContent === 'string' ? rawContent
1525
- : rawContent ? JSON.stringify(rawContent)
1560
+ const text = typeof rawContent === 'string'
1561
+ ? rawContent
1562
+ : rawContent
1563
+ ? JSON.stringify(rawContent)
1526
1564
  : event.summary || '';
1527
1565
  if (text)
1528
1566
  this.contextAttribution.toolOutputs += this.estimateTokens(text);
@@ -1573,16 +1611,15 @@ class EventAggregator {
1573
1611
  if (event.message.model)
1574
1612
  metadata.model = event.message.model;
1575
1613
  if (event.message.usage) {
1576
- metadata.tokenCount = event.message.usage.input_tokens + event.message.usage.output_tokens;
1614
+ metadata.tokenCount =
1615
+ event.message.usage.input_tokens + event.message.usage.output_tokens;
1577
1616
  }
1578
1617
  break;
1579
1618
  case 'tool_use': {
1580
1619
  tlType = 'tool_call';
1581
1620
  // Use rich tool summary formatter
1582
1621
  const toolName = event.tool?.name ?? 'unknown';
1583
- const toolSummary = event.tool?.input
1584
- ? (0, toolSummary_1.formatToolSummary)(toolName, event.tool.input)
1585
- : '';
1622
+ const toolSummary = event.tool?.input ? (0, toolSummary_1.formatToolSummary)(toolName, event.tool.input) : '';
1586
1623
  description = toolSummary ? `${toolName}: ${toolSummary}` : toolName;
1587
1624
  noiseLevel = 'system';
1588
1625
  if (event.tool)
@@ -1702,7 +1739,7 @@ class EventAggregator {
1702
1739
  if (typeof content === 'string')
1703
1740
  return content.length > 0;
1704
1741
  if (Array.isArray(content)) {
1705
- return content.some(b => b.type === 'text' && typeof b.text === 'string' && b.text.length > 0);
1742
+ return content.some((b) => b.type === 'text' && typeof b.text === 'string' && b.text.length > 0);
1706
1743
  }
1707
1744
  return false;
1708
1745
  }
@@ -1756,7 +1793,7 @@ class EventAggregator {
1756
1793
  }
1757
1794
  // Word frequency: extract significant words from summary
1758
1795
  if (summary) {
1759
- const words = summary.split(/\s+/).filter(w => w.length > 2);
1796
+ const words = summary.split(/\s+/).filter((w) => w.length > 2);
1760
1797
  for (const word of words) {
1761
1798
  this.wordFrequency.increment(word.toLowerCase(), timestamp);
1762
1799
  }
@@ -126,7 +126,10 @@ class PatternExtractor {
126
126
  }
127
127
  // ── Private ──
128
128
  tokenize(text) {
129
- return text.split(/\s+/).filter(t => t.length > 0).slice(0, this.maxDepth * 4);
129
+ return text
130
+ .split(/\s+/)
131
+ .filter((t) => t.length > 0)
132
+ .slice(0, this.maxDepth * 4);
130
133
  }
131
134
  similarity(tokens, cluster) {
132
135
  const len = Math.min(tokens.length, this.maxDepth);
@@ -139,9 +142,7 @@ class PatternExtractor {
139
142
  return matches / len;
140
143
  }
141
144
  buildTemplate(cluster) {
142
- return cluster.tokens
143
- .map((tok, i) => (cluster.mask[i] ? '<*>' : tok))
144
- .join(' ');
145
+ return cluster.tokens.map((tok, i) => (cluster.mask[i] ? '<*>' : tok)).join(' ');
145
146
  }
146
147
  totalClusters() {
147
148
  let total = 0;
@@ -38,7 +38,7 @@ function decideAutoSwitch(_provider, active, candidates, config) {
38
38
  return null;
39
39
  const activeRemaining = quotaRemaining(active.quota);
40
40
  const best = candidates
41
- .filter(candidate => candidate.accountId !== active.accountId &&
41
+ .filter((candidate) => candidate.accountId !== active.accountId &&
42
42
  candidate.switchable !== false &&
43
43
  candidate.quota?.available)
44
44
  .sort((a, b) => quotaRemaining(b.quota) - quotaRemaining(a.quota))[0];
@@ -89,7 +89,7 @@ class AutoSwitchController {
89
89
  start() {
90
90
  if (this.subscription)
91
91
  return;
92
- this.subscription = this.quotaService.onUpdate(state => this.handleUpdate(state));
92
+ this.subscription = this.quotaService.onUpdate((state) => this.handleUpdate(state));
93
93
  }
94
94
  stop() {
95
95
  this.subscription?.dispose();
@@ -112,27 +112,21 @@ class AutoSwitchController {
112
112
  }
113
113
  handleProviderUpdate(runtimeProvider, activeQuota) {
114
114
  const providerId = runtimeStateToProvider(runtimeProvider);
115
- const activeAccount = providerId === 'claude-code'
116
- ? this.getActiveClaudeAccount()
117
- : this.getActiveCodexAccount();
115
+ const activeAccount = providerId === 'claude-code' ? this.getActiveClaudeAccount() : this.getActiveCodexAccount();
118
116
  if (!activeAccount)
119
117
  return;
120
118
  const activeAccountId = accountIdFor(providerId, activeAccount);
121
119
  if (quotaUtilization(activeQuota) < this.config.thresholdPct) {
122
120
  this.switchedDuringCrossing.delete(providerId);
123
121
  }
124
- const accounts = providerId === 'claude-code'
125
- ? this.getClaudeAccounts()
126
- : this.getCodexAccounts();
122
+ const accounts = providerId === 'claude-code' ? this.getClaudeAccounts() : this.getCodexAccounts();
127
123
  if (accounts.length <= 1)
128
124
  return;
129
- const candidates = accounts.map(account => {
125
+ const candidates = accounts.map((account) => {
130
126
  const accountId = accountIdFor(providerId, account);
131
127
  return {
132
128
  accountId,
133
- quota: accountId === activeAccountId
134
- ? activeQuota
135
- : this.readSnapshot(providerId, accountId),
129
+ quota: accountId === activeAccountId ? activeQuota : this.readSnapshot(providerId, accountId),
136
130
  };
137
131
  });
138
132
  const decision = decideAutoSwitch(providerId, { accountId: activeAccountId, quota: activeQuota }, candidates, this.config);
@@ -69,9 +69,7 @@ function readClaudeProfileIdentity(home) {
69
69
  const raw = JSON.parse(fs.readFileSync(path.join(home, '.claude.json'), 'utf8'));
70
70
  const email = raw?.oauthAccount?.emailAddress;
71
71
  const uuid = raw?.oauthAccount?.accountUuid;
72
- return typeof email === 'string' && typeof uuid === 'string'
73
- ? { email, uuid }
74
- : null;
72
+ return typeof email === 'string' && typeof uuid === 'string' ? { email, uuid } : null;
75
73
  }
76
74
  catch {
77
75
  return null;
@@ -81,6 +79,8 @@ function keychainServiceExists(service) {
81
79
  try {
82
80
  (0, child_process_1.execFileSync)('security', ['find-generic-password', '-s', service], {
83
81
  stdio: ['ignore', 'ignore', 'ignore'],
82
+ timeout: 4000,
83
+ killSignal: 'SIGKILL',
84
84
  });
85
85
  return true;
86
86
  }