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.
- package/README.md +60 -45
- package/dist/accountManager.js +9 -7
- package/dist/accountRegistry.d.ts +17 -0
- package/dist/accountRegistry.js +18 -14
- package/dist/accountStatus.js +7 -7
- package/dist/accounts.d.ts +13 -0
- package/dist/accounts.js +110 -25
- package/dist/aggregation/EventAggregator.d.ts +14 -11
- package/dist/aggregation/EventAggregator.js +68 -31
- package/dist/aggregation/PatternExtractor.js +5 -4
- package/dist/autoSwitch.js +6 -12
- package/dist/claudeProfiles.js +3 -3
- package/dist/codexProfiles.d.ts +15 -1
- package/dist/codexProfiles.js +96 -22
- package/dist/codexQuota.js +44 -16
- package/dist/codexQuotaWatcher.js +19 -4
- package/dist/context/composer.js +8 -6
- package/dist/context/sessionContext.js +12 -9
- package/dist/credentialIO.js +10 -8
- package/dist/extractors/sessionAssets.js +9 -1
- package/dist/extractors/sources/claudeAssets.js +7 -1
- package/dist/extractors/sources/codexAssets.js +14 -3
- package/dist/extractors/toolCall.js +1 -4
- package/dist/formatters/eventHighlighter.js +148 -28
- package/dist/formatters/noiseClassifier.js +3 -11
- package/dist/formatters/sessionDump.js +22 -13
- package/dist/formatters/toolSummary.js +1 -3
- package/dist/index.d.ts +26 -26
- package/dist/index.js +10 -4
- package/dist/modelContext.js +10 -8
- package/dist/modelInfo.js +11 -6
- package/dist/multiProviderQuotaService.d.ts +2 -2
- package/dist/multiProviderQuotaService.js +14 -10
- package/dist/parsers/codexParser.js +170 -103
- package/dist/parsers/debugLogParser.js +5 -4
- package/dist/parsers/openCodeParser.js +66 -48
- package/dist/parsers/planExtractor.js +3 -3
- package/dist/parsers/sessionActivityDetector.js +10 -6
- package/dist/parsers/sessionPathResolver.js +29 -34
- package/dist/parsers/subagentScanner.js +21 -20
- package/dist/parsers/subagentTraceParser.js +7 -4
- package/dist/phrases.js +11 -11
- package/dist/providerStatus.js +3 -4
- package/dist/providers/claudeCode.js +3 -1
- package/dist/providers/codex.js +35 -14
- package/dist/providers/codexDatabase.js +14 -5
- package/dist/providers/detect.js +10 -6
- package/dist/providers/openCode.js +91 -63
- package/dist/providers/openCodeDatabase.js +44 -17
- package/dist/quota.d.ts +19 -0
- package/dist/quota.js +50 -8
- package/dist/quotaHistory.js +10 -3
- package/dist/quotaSnapshots.js +2 -2
- package/dist/readers/decisions.js +1 -1
- package/dist/readers/notes.js +5 -3
- package/dist/readers/plans.js +43 -23
- package/dist/readers/tasks.js +1 -1
- package/dist/report/htmlHelpers.js +135 -16
- package/dist/report/htmlReportGenerator.js +38 -12
- package/dist/report/transcriptParser.js +8 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/quota.js +2 -10
- package/dist/schemas/sessionEvent.js +20 -10
- package/dist/search/advancedFilter.js +3 -3
- package/dist/search/sessionSearch.js +10 -4
- package/dist/terminalSync.js +4 -2
- package/dist/turns/assistantTurn.js +5 -1
- package/dist/watchers/eventBridge.js +56 -26
- package/dist/watchers/factory.js +5 -2
- package/dist/watchers/jsonlTail.js +3 -1
- package/dist/watchers/jsonlWatcher.js +128 -52
- package/dist/watchers/providerReaderWatcher.js +3 -1
- package/dist/watchers/sqliteWatcher.js +52 -22
- package/dist/zaiQuota.js +6 -10
- package/dist/zaiQuotaApi.js +13 -5
- package/dist/zaiQuotaWatcher.js +1 -1
- 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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
326
|
+
catch {
|
|
327
|
+
/* no existing config to back up */
|
|
328
|
+
}
|
|
317
329
|
try {
|
|
318
330
|
backupCurrentClaudeLiveHome();
|
|
319
331
|
}
|
|
320
|
-
catch {
|
|
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 {
|
|
346
|
+
catch {
|
|
347
|
+
/* rollback failed */
|
|
348
|
+
}
|
|
333
349
|
if (originalCreds) {
|
|
334
350
|
try {
|
|
335
351
|
(0, credentialIO_1.writeActiveCredentials)(originalCreds);
|
|
336
352
|
}
|
|
337
|
-
catch {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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)) &&
|
|
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', {
|
|
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 {
|
|
480
|
+
catch {
|
|
481
|
+
/* ok */
|
|
482
|
+
}
|
|
449
483
|
try {
|
|
450
484
|
fs.unlinkSync(path.join(getConfigsDir(), `${uuid}.config.json`));
|
|
451
485
|
}
|
|
452
|
-
catch {
|
|
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<[
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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]
|
|
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 &&
|
|
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,
|
|
267
|
-
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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,
|
|
663
|
-
|
|
664
|
-
|
|
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 =
|
|
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,
|
|
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,
|
|
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'
|
|
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'
|
|
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'
|
|
1525
|
-
|
|
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 =
|
|
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
|
|
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;
|
package/dist/autoSwitch.js
CHANGED
|
@@ -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);
|
package/dist/claudeProfiles.js
CHANGED
|
@@ -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
|
}
|