sidekick-shared 0.21.1 → 0.21.3
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 +45 -45
- package/dist/accountManager.js +9 -7
- package/dist/accountRegistry.js +18 -14
- package/dist/accountStatus.js +4 -1
- package/dist/accounts.js +60 -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.js +45 -22
- package/dist/codexQuota.js +44 -16
- package/dist/codexQuotaWatcher.js +10 -3
- 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 +25 -23
- package/dist/index.js +12 -3
- package/dist/modelContext.js +10 -8
- package/dist/modelInfo.js +11 -6
- package/dist/multiProviderQuotaService.js +10 -7
- 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.d.ts +4 -4
- package/dist/providers/openCode.js +96 -68
- 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.d.ts +1 -1
- 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.d.ts +6 -6
- package/dist/zaiQuota.js +6 -10
- package/dist/zaiQuotaApi.d.ts +33 -0
- package/dist/zaiQuotaApi.js +279 -0
- package/dist/zaiQuotaWatcher.js +10 -9
- package/package.json +5 -2
|
@@ -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
|
}
|
package/dist/codexProfiles.js
CHANGED
|
@@ -121,7 +121,9 @@ function atomicWriteJson(filePath, data, mode = 0o600) {
|
|
|
121
121
|
try {
|
|
122
122
|
fs.unlinkSync(tmp);
|
|
123
123
|
}
|
|
124
|
-
catch {
|
|
124
|
+
catch {
|
|
125
|
+
/* nothing to clean up */
|
|
126
|
+
}
|
|
125
127
|
throw err;
|
|
126
128
|
}
|
|
127
129
|
}
|
|
@@ -139,7 +141,9 @@ function atomicWriteFile(filePath, content, mode = 0o600) {
|
|
|
139
141
|
try {
|
|
140
142
|
fs.unlinkSync(tmp);
|
|
141
143
|
}
|
|
142
|
-
catch {
|
|
144
|
+
catch {
|
|
145
|
+
/* nothing to clean up */
|
|
146
|
+
}
|
|
143
147
|
throw err;
|
|
144
148
|
}
|
|
145
149
|
}
|
|
@@ -216,12 +220,8 @@ function readAuthIdentityFromRaw(raw) {
|
|
|
216
220
|
const workspaceId = typeof authClaims?.chatgpt_account_id === 'string'
|
|
217
221
|
? authClaims.chatgpt_account_id
|
|
218
222
|
: parsed.tokens?.account_id;
|
|
219
|
-
const planType = typeof authClaims?.chatgpt_plan_type === 'string'
|
|
220
|
-
|
|
221
|
-
: undefined;
|
|
222
|
-
const authMode = parsed.OPENAI_API_KEY || parsed.auth_mode === 'api_key'
|
|
223
|
-
? 'api-key'
|
|
224
|
-
: 'chatgpt';
|
|
223
|
+
const planType = typeof authClaims?.chatgpt_plan_type === 'string' ? authClaims.chatgpt_plan_type : undefined;
|
|
224
|
+
const authMode = parsed.OPENAI_API_KEY || parsed.auth_mode === 'api_key' ? 'api-key' : 'chatgpt';
|
|
225
225
|
return { email, workspaceId, planType, authMode };
|
|
226
226
|
}
|
|
227
227
|
function readLastRefresh(raw, fallbackPath) {
|
|
@@ -235,7 +235,9 @@ function readLastRefresh(raw, fallbackPath) {
|
|
|
235
235
|
try {
|
|
236
236
|
return fs.statSync(fallbackPath).mtimeMs;
|
|
237
237
|
}
|
|
238
|
-
catch {
|
|
238
|
+
catch {
|
|
239
|
+
/* fall through */
|
|
240
|
+
}
|
|
239
241
|
}
|
|
240
242
|
return null;
|
|
241
243
|
}
|
|
@@ -271,6 +273,8 @@ function getCodexLoginStatus(codexHome) {
|
|
|
271
273
|
const result = (0, child_process_1.spawnSync)('codex', ['login', 'status'], {
|
|
272
274
|
encoding: 'utf8',
|
|
273
275
|
env,
|
|
276
|
+
timeout: 4000,
|
|
277
|
+
killSignal: 'SIGKILL',
|
|
274
278
|
});
|
|
275
279
|
const stdout = String(result.stdout ?? '').trim();
|
|
276
280
|
if (result.status === 0 && /^Logged in/i.test(stdout)) {
|
|
@@ -292,7 +296,11 @@ function detectRunningCodexProcess() {
|
|
|
292
296
|
if (process.platform === 'win32')
|
|
293
297
|
return false;
|
|
294
298
|
try {
|
|
295
|
-
return (0, child_process_1.spawnSync)('pgrep', ['-x', 'codex'], {
|
|
299
|
+
return ((0, child_process_1.spawnSync)('pgrep', ['-x', 'codex'], {
|
|
300
|
+
encoding: 'utf8',
|
|
301
|
+
timeout: 4000,
|
|
302
|
+
killSignal: 'SIGKILL',
|
|
303
|
+
}).status === 0);
|
|
296
304
|
}
|
|
297
305
|
catch {
|
|
298
306
|
return false;
|
|
@@ -324,8 +332,7 @@ function isCodexProfileAuthenticated(codexHome) {
|
|
|
324
332
|
}
|
|
325
333
|
function ensureUniqueCodexLabel(label, excludeId) {
|
|
326
334
|
const normalized = label.trim().toLowerCase();
|
|
327
|
-
const conflict = listCodexAccounts().find(account => account.id !== excludeId &&
|
|
328
|
-
(account.label ?? '').trim().toLowerCase() === normalized);
|
|
335
|
+
const conflict = listCodexAccounts().find((account) => account.id !== excludeId && (account.label ?? '').trim().toLowerCase() === normalized);
|
|
329
336
|
return conflict ? `A Codex account named "${label}" already exists.` : null;
|
|
330
337
|
}
|
|
331
338
|
function listCodexAccounts() {
|
|
@@ -440,12 +447,12 @@ function stashLiveCodexAuth(liveAuthRaw, liveLegacyRaw) {
|
|
|
440
447
|
function findProfileForIdentity(identity) {
|
|
441
448
|
const profiles = listCodexAccounts();
|
|
442
449
|
if (identity?.workspaceId) {
|
|
443
|
-
const byWorkspace = profiles.find(profile => profile.metadata?.workspaceId === identity.workspaceId);
|
|
450
|
+
const byWorkspace = profiles.find((profile) => profile.metadata?.workspaceId === identity.workspaceId);
|
|
444
451
|
if (byWorkspace)
|
|
445
452
|
return byWorkspace;
|
|
446
453
|
}
|
|
447
454
|
if (identity?.email) {
|
|
448
|
-
const byEmail = profiles.find(profile => (profile.email ?? profile.metadata?.email) === identity.email);
|
|
455
|
+
const byEmail = profiles.find((profile) => (profile.email ?? profile.metadata?.email) === identity.email);
|
|
449
456
|
if (byEmail)
|
|
450
457
|
return byEmail;
|
|
451
458
|
}
|
|
@@ -488,7 +495,9 @@ function syncBackLiveCodexAuth(liveAuthRaw, liveLegacyRaw) {
|
|
|
488
495
|
metadata: { ...profile.metadata, ...metadata },
|
|
489
496
|
});
|
|
490
497
|
}
|
|
491
|
-
catch {
|
|
498
|
+
catch {
|
|
499
|
+
/* metadata refresh is best-effort */
|
|
500
|
+
}
|
|
492
501
|
return { syncedProfileId: profile.id };
|
|
493
502
|
}
|
|
494
503
|
catch (err) {
|
|
@@ -513,10 +522,16 @@ function performCodexAuthSwap(target) {
|
|
|
513
522
|
const targetLegacyRaw = readFileOrNull(path.join(profileHome, '.credentials.json'));
|
|
514
523
|
const targetName = target.label ?? target.email ?? target.id;
|
|
515
524
|
if (!targetAuthRaw && !targetLegacyRaw) {
|
|
516
|
-
return {
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
error: `No stored credentials for "${targetName}". Remove and re-add this account.`,
|
|
528
|
+
};
|
|
517
529
|
}
|
|
518
530
|
if (targetAuthRaw && !parseAuthJson(targetAuthRaw)) {
|
|
519
|
-
return {
|
|
531
|
+
return {
|
|
532
|
+
success: false,
|
|
533
|
+
error: `Stored credentials for "${targetName}" are corrupted. Remove and re-add this account.`,
|
|
534
|
+
};
|
|
520
535
|
}
|
|
521
536
|
const warnings = [];
|
|
522
537
|
if (detectRunningCodexProcess()) {
|
|
@@ -530,7 +545,9 @@ function performCodexAuthSwap(target) {
|
|
|
530
545
|
const targetIdentity = readAuthIdentityFromRaw(targetAuthRaw);
|
|
531
546
|
const targetWorkspaceId = target.metadata?.workspaceId ?? targetIdentity?.workspaceId;
|
|
532
547
|
const targetEmail = target.email ?? target.metadata?.email ?? targetIdentity?.email;
|
|
533
|
-
const liveMatchesTarget = Boolean((liveIdentity?.workspaceId &&
|
|
548
|
+
const liveMatchesTarget = Boolean((liveIdentity?.workspaceId &&
|
|
549
|
+
targetWorkspaceId &&
|
|
550
|
+
liveIdentity.workspaceId === targetWorkspaceId) ||
|
|
534
551
|
(liveIdentity?.email && targetEmail && liveIdentity.email === targetEmail) ||
|
|
535
552
|
(liveAuthRaw !== null && liveAuthRaw === targetAuthRaw) ||
|
|
536
553
|
(!liveAuthRaw && !targetAuthRaw && liveLegacyRaw !== null && liveLegacyRaw === targetLegacyRaw));
|
|
@@ -547,7 +564,9 @@ function performCodexAuthSwap(target) {
|
|
|
547
564
|
metadata: { ...target.metadata, ...metadata },
|
|
548
565
|
});
|
|
549
566
|
}
|
|
550
|
-
catch {
|
|
567
|
+
catch {
|
|
568
|
+
/* backup refresh is best-effort */
|
|
569
|
+
}
|
|
551
570
|
(0, accountRegistry_1.setActiveSavedAccount)('codex', target.id);
|
|
552
571
|
return { success: true, warning: warnings.length ? warnings.join(' ') : undefined };
|
|
553
572
|
}
|
|
@@ -569,7 +588,9 @@ function performCodexAuthSwap(target) {
|
|
|
569
588
|
else
|
|
570
589
|
fs.rmSync(liveLegacyPath, { force: true });
|
|
571
590
|
}
|
|
572
|
-
catch {
|
|
591
|
+
catch {
|
|
592
|
+
/* rollback is best-effort */
|
|
593
|
+
}
|
|
573
594
|
};
|
|
574
595
|
try {
|
|
575
596
|
if (targetAuthRaw) {
|
|
@@ -599,7 +620,7 @@ function performCodexAuthSwap(target) {
|
|
|
599
620
|
return { success: true, warning: warnings.length ? warnings.join(' ') : undefined };
|
|
600
621
|
}
|
|
601
622
|
function switchToCodexAccount(profileId) {
|
|
602
|
-
const target = listCodexAccounts().find(account => account.id === profileId);
|
|
623
|
+
const target = listCodexAccounts().find((account) => account.id === profileId);
|
|
603
624
|
if (!target) {
|
|
604
625
|
return { success: false, error: `Codex account ${profileId} not found.` };
|
|
605
626
|
}
|
|
@@ -641,7 +662,9 @@ function reconcileCodexAuthState() {
|
|
|
641
662
|
}
|
|
642
663
|
const liveIdentity = readAuthIdentityFromRaw(liveAuthRaw);
|
|
643
664
|
const profileIdentity = readAuthIdentityFromRaw(profileAuthRaw);
|
|
644
|
-
const sameIdentity = Boolean((liveIdentity?.workspaceId &&
|
|
665
|
+
const sameIdentity = Boolean((liveIdentity?.workspaceId &&
|
|
666
|
+
profileIdentity?.workspaceId &&
|
|
667
|
+
liveIdentity.workspaceId === profileIdentity.workspaceId) ||
|
|
645
668
|
(liveIdentity?.email && liveIdentity.email === profileIdentity?.email));
|
|
646
669
|
if (sameIdentity) {
|
|
647
670
|
const liveRefresh = readLastRefresh(liveAuthRaw, liveAuthPath);
|
package/dist/codexQuota.js
CHANGED
|
@@ -42,6 +42,7 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const codexProfiles_1 = require("./codexProfiles");
|
|
44
44
|
const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
45
|
+
const quota_1 = require("./quota");
|
|
45
46
|
const codex_1 = require("./providers/codex");
|
|
46
47
|
const DEFAULT_TAIL_BYTES = 2 * 1024 * 1024;
|
|
47
48
|
const DEFAULT_MAX_SESSION_FILES = 50;
|
|
@@ -79,6 +80,11 @@ function isPreferredQuotaHit(candidate, current) {
|
|
|
79
80
|
function normalizePercent(value) {
|
|
80
81
|
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
81
82
|
}
|
|
83
|
+
function windowMinutesToMs(value, fallbackMs) {
|
|
84
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0
|
|
85
|
+
? value * 60_000
|
|
86
|
+
: fallbackMs;
|
|
87
|
+
}
|
|
82
88
|
function timestampToIso(seconds) {
|
|
83
89
|
if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds <= 0) {
|
|
84
90
|
return '';
|
|
@@ -89,8 +95,9 @@ function accountEmail(account) {
|
|
|
89
95
|
return account?.email ?? account?.metadata?.email;
|
|
90
96
|
}
|
|
91
97
|
function enrichCodexQuota(state, account) {
|
|
98
|
+
const withProjections = (0, quota_1.withQuotaProjections)(state);
|
|
92
99
|
return {
|
|
93
|
-
...
|
|
100
|
+
...withProjections,
|
|
94
101
|
runtimeProvider: 'codex',
|
|
95
102
|
providerId: 'codex',
|
|
96
103
|
accountLabel: account?.label,
|
|
@@ -183,12 +190,18 @@ function quotaFromCodexRateLimits(rateLimits, source = 'session', capturedAt = n
|
|
|
183
190
|
const secondary = rateLimits?.secondary;
|
|
184
191
|
if (!primary && !secondary)
|
|
185
192
|
return null;
|
|
186
|
-
return {
|
|
193
|
+
return (0, quota_1.withQuotaProjections)({
|
|
187
194
|
fiveHour: primary
|
|
188
|
-
? {
|
|
195
|
+
? {
|
|
196
|
+
utilization: normalizePercent(primary.used_percent),
|
|
197
|
+
resetsAt: timestampToIso(primary.resets_at),
|
|
198
|
+
}
|
|
189
199
|
: { utilization: 0, resetsAt: '' },
|
|
190
200
|
sevenDay: secondary
|
|
191
|
-
? {
|
|
201
|
+
? {
|
|
202
|
+
utilization: normalizePercent(secondary.used_percent),
|
|
203
|
+
resetsAt: timestampToIso(secondary.resets_at),
|
|
204
|
+
}
|
|
192
205
|
: { utilization: 0, resetsAt: '' },
|
|
193
206
|
available: true,
|
|
194
207
|
providerId: 'codex',
|
|
@@ -202,7 +215,11 @@ function quotaFromCodexRateLimits(rateLimits, source = 'session', capturedAt = n
|
|
|
202
215
|
credits: rateLimits?.credits,
|
|
203
216
|
planType: rateLimits?.plan_type,
|
|
204
217
|
rateLimitReachedType: rateLimits?.rate_limit_reached_type,
|
|
205
|
-
}
|
|
218
|
+
}, {
|
|
219
|
+
fiveHourWindowMs: windowMinutesToMs(primary?.window_minutes, quota_1.FIVE_HOUR_WINDOW_MS),
|
|
220
|
+
sevenDayWindowMs: windowMinutesToMs(secondary?.window_minutes, quota_1.SEVEN_DAY_WINDOW_MS),
|
|
221
|
+
capturedAt,
|
|
222
|
+
});
|
|
206
223
|
}
|
|
207
224
|
function readLatestCodexQuotaFromRollouts(sessionPaths, options = {}) {
|
|
208
225
|
return readLatestCodexQuotaHitFromRollouts(sessionPaths, options)?.quota ?? null;
|
|
@@ -260,23 +277,30 @@ function resolveCodexQuotaFromLocalSources(options = {}) {
|
|
|
260
277
|
const maxTailBytes = options.maxTailBytes ?? DEFAULT_TAIL_BYTES;
|
|
261
278
|
const maxSessionFiles = options.maxSessionFiles ?? DEFAULT_MAX_SESSION_FILES;
|
|
262
279
|
let ownProvider = false;
|
|
263
|
-
const provider = options.provider ??
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
const provider = options.provider ??
|
|
281
|
+
(() => {
|
|
282
|
+
ownProvider = true;
|
|
283
|
+
return new codex_1.CodexProvider();
|
|
284
|
+
})();
|
|
267
285
|
try {
|
|
268
286
|
const candidates = [];
|
|
269
287
|
if (options.workspacePath) {
|
|
270
288
|
const workspaceSessions = provider.findAllSessions(options.workspacePath);
|
|
271
|
-
const workspaceHit = readLatestCodexQuotaHitFromRollouts(workspaceSessions, {
|
|
289
|
+
const workspaceHit = readLatestCodexQuotaHitFromRollouts(workspaceSessions, {
|
|
290
|
+
maxTailBytes,
|
|
291
|
+
maxSessionFiles,
|
|
292
|
+
});
|
|
272
293
|
if (workspaceHit)
|
|
273
294
|
candidates.push(workspaceHit);
|
|
274
295
|
}
|
|
275
296
|
const accountSessions = findAccountRolloutFiles(options.codexHome);
|
|
276
|
-
const accountHit = readLatestCodexQuotaHitFromRollouts(accountSessions, {
|
|
297
|
+
const accountHit = readLatestCodexQuotaHitFromRollouts(accountSessions, {
|
|
298
|
+
maxTailBytes,
|
|
299
|
+
maxSessionFiles,
|
|
300
|
+
});
|
|
277
301
|
if (accountHit)
|
|
278
302
|
candidates.push(accountHit);
|
|
279
|
-
const latestHit = candidates.reduce((latest, candidate) => isPreferredQuotaHit(candidate, latest) ? candidate : latest, null);
|
|
303
|
+
const latestHit = candidates.reduce((latest, candidate) => (isPreferredQuotaHit(candidate, latest) ? candidate : latest), null);
|
|
280
304
|
if (latestHit) {
|
|
281
305
|
if (account)
|
|
282
306
|
writeSnapshot('codex', account.id, latestHit.quota);
|
|
@@ -368,7 +392,9 @@ async function fetchCodexQuotaFromApi(options = {}) {
|
|
|
368
392
|
? 'server'
|
|
369
393
|
: 'unknown',
|
|
370
394
|
httpStatus: response.status,
|
|
371
|
-
retryAfterMs: response.status === 429
|
|
395
|
+
retryAfterMs: response.status === 429
|
|
396
|
+
? parseRetryAfterMs(response.headers.get('retry-after'))
|
|
397
|
+
: undefined,
|
|
372
398
|
providerId: 'codex',
|
|
373
399
|
source: 'api',
|
|
374
400
|
capturedAt,
|
|
@@ -376,7 +402,7 @@ async function fetchCodexQuotaFromApi(options = {}) {
|
|
|
376
402
|
sevenDayLabel: 'Secondary',
|
|
377
403
|
};
|
|
378
404
|
}
|
|
379
|
-
const payload = await response.json();
|
|
405
|
+
const payload = (await response.json());
|
|
380
406
|
const quota = quotaFromCodexRateLimits(rateLimitsFromUsagePayload(payload), 'api', capturedAt);
|
|
381
407
|
if (!quota) {
|
|
382
408
|
return {
|
|
@@ -464,7 +490,9 @@ function readLatestQuotaFromRollout(sessionPath, maxTailBytes, source) {
|
|
|
464
490
|
try {
|
|
465
491
|
fs.closeSync(fd);
|
|
466
492
|
}
|
|
467
|
-
catch {
|
|
493
|
+
catch {
|
|
494
|
+
/* ignore */
|
|
495
|
+
}
|
|
468
496
|
}
|
|
469
497
|
}
|
|
470
498
|
return null;
|
|
@@ -500,5 +528,5 @@ function findRolloutFiles(sessionsDir) {
|
|
|
500
528
|
}
|
|
501
529
|
visit(sessionsDir);
|
|
502
530
|
results.sort((a, b) => b.mtime - a.mtime);
|
|
503
|
-
return results.map(item => item.path);
|
|
531
|
+
return results.map((item) => item.path);
|
|
504
532
|
}
|