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/codexProfiles.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountIdentityMetadata, SavedAccountProfile } from './accountRegistry';
|
|
1
|
+
import type { AccountIdentityMetadata, ResolvedActiveAccount, SavedAccountProfile } from './accountRegistry';
|
|
2
2
|
import type { AccountManagerResult } from './accounts';
|
|
3
3
|
export interface CodexAccountManagerResult extends AccountManagerResult {
|
|
4
4
|
needsLogin?: boolean;
|
|
@@ -14,6 +14,20 @@ export declare function isCodexProfileAuthenticated(codexHome: string): boolean;
|
|
|
14
14
|
export declare function listCodexAccounts(): SavedAccountProfile[];
|
|
15
15
|
export declare function getActiveCodexAccount(): SavedAccountProfile | null;
|
|
16
16
|
export declare function resolveSidekickCodexHome(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the *currently logged-in* Codex account for display, preferring the
|
|
19
|
+
* live `auth.json` identity over the saved registry pointer (which only sidekick's
|
|
20
|
+
* own switch flow updates and therefore goes stale after a native `codex login`).
|
|
21
|
+
*
|
|
22
|
+
* Uses the cheap JWT decode from `auth.json` directly — NOT `readCodexAccountMetadata`,
|
|
23
|
+
* whose fallback spawns `codex login status` (a multi-second subprocess) unsuitable
|
|
24
|
+
* for a render path.
|
|
25
|
+
*
|
|
26
|
+
* Safe self-heal: when the live identity unambiguously matches a saved profile that
|
|
27
|
+
* isn't the current active pointer, the pointer is re-pointed so registry-keyed data
|
|
28
|
+
* (quota history, auto-switch) tracks reality too. Never creates or deletes profiles.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveActiveCodexAccount(): ResolvedActiveAccount;
|
|
17
31
|
export declare function getCodexExecutionEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
18
32
|
export declare function prepareCodexAccount(label: string): CodexAccountManagerResult;
|
|
19
33
|
export declare function finalizeCodexAccount(profileId: string, opts?: {
|
package/dist/codexProfiles.js
CHANGED
|
@@ -42,6 +42,7 @@ exports.isCodexProfileAuthenticated = isCodexProfileAuthenticated;
|
|
|
42
42
|
exports.listCodexAccounts = listCodexAccounts;
|
|
43
43
|
exports.getActiveCodexAccount = getActiveCodexAccount;
|
|
44
44
|
exports.resolveSidekickCodexHome = resolveSidekickCodexHome;
|
|
45
|
+
exports.resolveActiveCodexAccount = resolveActiveCodexAccount;
|
|
45
46
|
exports.getCodexExecutionEnv = getCodexExecutionEnv;
|
|
46
47
|
exports.prepareCodexAccount = prepareCodexAccount;
|
|
47
48
|
exports.finalizeCodexAccount = finalizeCodexAccount;
|
|
@@ -121,7 +122,9 @@ function atomicWriteJson(filePath, data, mode = 0o600) {
|
|
|
121
122
|
try {
|
|
122
123
|
fs.unlinkSync(tmp);
|
|
123
124
|
}
|
|
124
|
-
catch {
|
|
125
|
+
catch {
|
|
126
|
+
/* nothing to clean up */
|
|
127
|
+
}
|
|
125
128
|
throw err;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
@@ -139,7 +142,9 @@ function atomicWriteFile(filePath, content, mode = 0o600) {
|
|
|
139
142
|
try {
|
|
140
143
|
fs.unlinkSync(tmp);
|
|
141
144
|
}
|
|
142
|
-
catch {
|
|
145
|
+
catch {
|
|
146
|
+
/* nothing to clean up */
|
|
147
|
+
}
|
|
143
148
|
throw err;
|
|
144
149
|
}
|
|
145
150
|
}
|
|
@@ -216,12 +221,8 @@ function readAuthIdentityFromRaw(raw) {
|
|
|
216
221
|
const workspaceId = typeof authClaims?.chatgpt_account_id === 'string'
|
|
217
222
|
? authClaims.chatgpt_account_id
|
|
218
223
|
: 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';
|
|
224
|
+
const planType = typeof authClaims?.chatgpt_plan_type === 'string' ? authClaims.chatgpt_plan_type : undefined;
|
|
225
|
+
const authMode = parsed.OPENAI_API_KEY || parsed.auth_mode === 'api_key' ? 'api-key' : 'chatgpt';
|
|
225
226
|
return { email, workspaceId, planType, authMode };
|
|
226
227
|
}
|
|
227
228
|
function readLastRefresh(raw, fallbackPath) {
|
|
@@ -235,7 +236,9 @@ function readLastRefresh(raw, fallbackPath) {
|
|
|
235
236
|
try {
|
|
236
237
|
return fs.statSync(fallbackPath).mtimeMs;
|
|
237
238
|
}
|
|
238
|
-
catch {
|
|
239
|
+
catch {
|
|
240
|
+
/* fall through */
|
|
241
|
+
}
|
|
239
242
|
}
|
|
240
243
|
return null;
|
|
241
244
|
}
|
|
@@ -271,6 +274,8 @@ function getCodexLoginStatus(codexHome) {
|
|
|
271
274
|
const result = (0, child_process_1.spawnSync)('codex', ['login', 'status'], {
|
|
272
275
|
encoding: 'utf8',
|
|
273
276
|
env,
|
|
277
|
+
timeout: 4000,
|
|
278
|
+
killSignal: 'SIGKILL',
|
|
274
279
|
});
|
|
275
280
|
const stdout = String(result.stdout ?? '').trim();
|
|
276
281
|
if (result.status === 0 && /^Logged in/i.test(stdout)) {
|
|
@@ -292,7 +297,11 @@ function detectRunningCodexProcess() {
|
|
|
292
297
|
if (process.platform === 'win32')
|
|
293
298
|
return false;
|
|
294
299
|
try {
|
|
295
|
-
return (0, child_process_1.spawnSync)('pgrep', ['-x', 'codex'], {
|
|
300
|
+
return ((0, child_process_1.spawnSync)('pgrep', ['-x', 'codex'], {
|
|
301
|
+
encoding: 'utf8',
|
|
302
|
+
timeout: 4000,
|
|
303
|
+
killSignal: 'SIGKILL',
|
|
304
|
+
}).status === 0);
|
|
296
305
|
}
|
|
297
306
|
catch {
|
|
298
307
|
return false;
|
|
@@ -324,8 +333,7 @@ function isCodexProfileAuthenticated(codexHome) {
|
|
|
324
333
|
}
|
|
325
334
|
function ensureUniqueCodexLabel(label, excludeId) {
|
|
326
335
|
const normalized = label.trim().toLowerCase();
|
|
327
|
-
const conflict = listCodexAccounts().find(account => account.id !== excludeId &&
|
|
328
|
-
(account.label ?? '').trim().toLowerCase() === normalized);
|
|
336
|
+
const conflict = listCodexAccounts().find((account) => account.id !== excludeId && (account.label ?? '').trim().toLowerCase() === normalized);
|
|
329
337
|
return conflict ? `A Codex account named "${label}" already exists.` : null;
|
|
330
338
|
}
|
|
331
339
|
function listCodexAccounts() {
|
|
@@ -339,6 +347,56 @@ function resolveSidekickCodexHome() {
|
|
|
339
347
|
// home (or an explicit CODEX_HOME) is always the single live home.
|
|
340
348
|
return getSystemCodexHome();
|
|
341
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Resolves the *currently logged-in* Codex account for display, preferring the
|
|
352
|
+
* live `auth.json` identity over the saved registry pointer (which only sidekick's
|
|
353
|
+
* own switch flow updates and therefore goes stale after a native `codex login`).
|
|
354
|
+
*
|
|
355
|
+
* Uses the cheap JWT decode from `auth.json` directly — NOT `readCodexAccountMetadata`,
|
|
356
|
+
* whose fallback spawns `codex login status` (a multi-second subprocess) unsuitable
|
|
357
|
+
* for a render path.
|
|
358
|
+
*
|
|
359
|
+
* Safe self-heal: when the live identity unambiguously matches a saved profile that
|
|
360
|
+
* isn't the current active pointer, the pointer is re-pointed so registry-keyed data
|
|
361
|
+
* (quota history, auto-switch) tracks reality too. Never creates or deletes profiles.
|
|
362
|
+
*/
|
|
363
|
+
function resolveActiveCodexAccount() {
|
|
364
|
+
const identity = readAuthIdentityFromRaw(readFileOrNull(path.join(resolveSidekickCodexHome(), 'auth.json')));
|
|
365
|
+
if (identity && (identity.email || identity.workspaceId)) {
|
|
366
|
+
const match = findProfileForIdentity(identity);
|
|
367
|
+
if (match) {
|
|
368
|
+
const active = (0, accountRegistry_1.getActiveSavedAccount)('codex');
|
|
369
|
+
if (!active || active.id !== match.id) {
|
|
370
|
+
// Self-heal is best-effort: a registry write failure (read-only/full
|
|
371
|
+
// disk) must never break display, extension activation, or the quota
|
|
372
|
+
// watcher's hot path. We still return the correct live identity below.
|
|
373
|
+
try {
|
|
374
|
+
(0, accountRegistry_1.setActiveSavedAccount)('codex', match.id);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
/* keep going with the live identity */
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
email: identity.email ?? match?.email ?? match?.metadata?.email,
|
|
383
|
+
label: match?.label,
|
|
384
|
+
providerAccountId: match?.providerAccountId ?? identity.workspaceId,
|
|
385
|
+
source: 'live',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// api-key auth, unparseable token, or logged out → fall back to the registry.
|
|
389
|
+
const active = getActiveCodexAccount();
|
|
390
|
+
if (active) {
|
|
391
|
+
return {
|
|
392
|
+
email: active.email ?? active.metadata?.email,
|
|
393
|
+
label: active.label,
|
|
394
|
+
providerAccountId: active.providerAccountId,
|
|
395
|
+
source: 'registry',
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return { source: 'none' };
|
|
399
|
+
}
|
|
342
400
|
function getCodexExecutionEnv(baseEnv = process.env) {
|
|
343
401
|
return {
|
|
344
402
|
...baseEnv,
|
|
@@ -440,12 +498,12 @@ function stashLiveCodexAuth(liveAuthRaw, liveLegacyRaw) {
|
|
|
440
498
|
function findProfileForIdentity(identity) {
|
|
441
499
|
const profiles = listCodexAccounts();
|
|
442
500
|
if (identity?.workspaceId) {
|
|
443
|
-
const byWorkspace = profiles.find(profile => profile.metadata?.workspaceId === identity.workspaceId);
|
|
501
|
+
const byWorkspace = profiles.find((profile) => profile.metadata?.workspaceId === identity.workspaceId);
|
|
444
502
|
if (byWorkspace)
|
|
445
503
|
return byWorkspace;
|
|
446
504
|
}
|
|
447
505
|
if (identity?.email) {
|
|
448
|
-
const byEmail = profiles.find(profile => (profile.email ?? profile.metadata?.email) === identity.email);
|
|
506
|
+
const byEmail = profiles.find((profile) => (profile.email ?? profile.metadata?.email) === identity.email);
|
|
449
507
|
if (byEmail)
|
|
450
508
|
return byEmail;
|
|
451
509
|
}
|
|
@@ -488,7 +546,9 @@ function syncBackLiveCodexAuth(liveAuthRaw, liveLegacyRaw) {
|
|
|
488
546
|
metadata: { ...profile.metadata, ...metadata },
|
|
489
547
|
});
|
|
490
548
|
}
|
|
491
|
-
catch {
|
|
549
|
+
catch {
|
|
550
|
+
/* metadata refresh is best-effort */
|
|
551
|
+
}
|
|
492
552
|
return { syncedProfileId: profile.id };
|
|
493
553
|
}
|
|
494
554
|
catch (err) {
|
|
@@ -513,10 +573,16 @@ function performCodexAuthSwap(target) {
|
|
|
513
573
|
const targetLegacyRaw = readFileOrNull(path.join(profileHome, '.credentials.json'));
|
|
514
574
|
const targetName = target.label ?? target.email ?? target.id;
|
|
515
575
|
if (!targetAuthRaw && !targetLegacyRaw) {
|
|
516
|
-
return {
|
|
576
|
+
return {
|
|
577
|
+
success: false,
|
|
578
|
+
error: `No stored credentials for "${targetName}". Remove and re-add this account.`,
|
|
579
|
+
};
|
|
517
580
|
}
|
|
518
581
|
if (targetAuthRaw && !parseAuthJson(targetAuthRaw)) {
|
|
519
|
-
return {
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
error: `Stored credentials for "${targetName}" are corrupted. Remove and re-add this account.`,
|
|
585
|
+
};
|
|
520
586
|
}
|
|
521
587
|
const warnings = [];
|
|
522
588
|
if (detectRunningCodexProcess()) {
|
|
@@ -530,7 +596,9 @@ function performCodexAuthSwap(target) {
|
|
|
530
596
|
const targetIdentity = readAuthIdentityFromRaw(targetAuthRaw);
|
|
531
597
|
const targetWorkspaceId = target.metadata?.workspaceId ?? targetIdentity?.workspaceId;
|
|
532
598
|
const targetEmail = target.email ?? target.metadata?.email ?? targetIdentity?.email;
|
|
533
|
-
const liveMatchesTarget = Boolean((liveIdentity?.workspaceId &&
|
|
599
|
+
const liveMatchesTarget = Boolean((liveIdentity?.workspaceId &&
|
|
600
|
+
targetWorkspaceId &&
|
|
601
|
+
liveIdentity.workspaceId === targetWorkspaceId) ||
|
|
534
602
|
(liveIdentity?.email && targetEmail && liveIdentity.email === targetEmail) ||
|
|
535
603
|
(liveAuthRaw !== null && liveAuthRaw === targetAuthRaw) ||
|
|
536
604
|
(!liveAuthRaw && !targetAuthRaw && liveLegacyRaw !== null && liveLegacyRaw === targetLegacyRaw));
|
|
@@ -547,7 +615,9 @@ function performCodexAuthSwap(target) {
|
|
|
547
615
|
metadata: { ...target.metadata, ...metadata },
|
|
548
616
|
});
|
|
549
617
|
}
|
|
550
|
-
catch {
|
|
618
|
+
catch {
|
|
619
|
+
/* backup refresh is best-effort */
|
|
620
|
+
}
|
|
551
621
|
(0, accountRegistry_1.setActiveSavedAccount)('codex', target.id);
|
|
552
622
|
return { success: true, warning: warnings.length ? warnings.join(' ') : undefined };
|
|
553
623
|
}
|
|
@@ -569,7 +639,9 @@ function performCodexAuthSwap(target) {
|
|
|
569
639
|
else
|
|
570
640
|
fs.rmSync(liveLegacyPath, { force: true });
|
|
571
641
|
}
|
|
572
|
-
catch {
|
|
642
|
+
catch {
|
|
643
|
+
/* rollback is best-effort */
|
|
644
|
+
}
|
|
573
645
|
};
|
|
574
646
|
try {
|
|
575
647
|
if (targetAuthRaw) {
|
|
@@ -599,7 +671,7 @@ function performCodexAuthSwap(target) {
|
|
|
599
671
|
return { success: true, warning: warnings.length ? warnings.join(' ') : undefined };
|
|
600
672
|
}
|
|
601
673
|
function switchToCodexAccount(profileId) {
|
|
602
|
-
const target = listCodexAccounts().find(account => account.id === profileId);
|
|
674
|
+
const target = listCodexAccounts().find((account) => account.id === profileId);
|
|
603
675
|
if (!target) {
|
|
604
676
|
return { success: false, error: `Codex account ${profileId} not found.` };
|
|
605
677
|
}
|
|
@@ -641,7 +713,9 @@ function reconcileCodexAuthState() {
|
|
|
641
713
|
}
|
|
642
714
|
const liveIdentity = readAuthIdentityFromRaw(liveAuthRaw);
|
|
643
715
|
const profileIdentity = readAuthIdentityFromRaw(profileAuthRaw);
|
|
644
|
-
const sameIdentity = Boolean((liveIdentity?.workspaceId &&
|
|
716
|
+
const sameIdentity = Boolean((liveIdentity?.workspaceId &&
|
|
717
|
+
profileIdentity?.workspaceId &&
|
|
718
|
+
liveIdentity.workspaceId === profileIdentity.workspaceId) ||
|
|
645
719
|
(liveIdentity?.email && liveIdentity.email === profileIdentity?.email));
|
|
646
720
|
if (sameIdentity) {
|
|
647
721
|
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
|
}
|
|
@@ -93,9 +93,18 @@ class CodexQuotaWatcher {
|
|
|
93
93
|
running = false;
|
|
94
94
|
constructor(workspacePath, options = {}) {
|
|
95
95
|
this.workspacePath = workspacePath;
|
|
96
|
-
this.discoveryPollIntervalMs =
|
|
96
|
+
this.discoveryPollIntervalMs =
|
|
97
|
+
options.discoveryPollIntervalMs ?? DEFAULT_DISCOVERY_POLL_INTERVAL_MS;
|
|
97
98
|
this.providerFactory = options.providerFactory ?? (() => new codex_1.CodexProvider());
|
|
98
|
-
|
|
99
|
+
// Self-heal the registry pointer to the live login before reading it, so the
|
|
100
|
+
// history key (account.id) and the display label track the currently
|
|
101
|
+
// logged-in account even after a native `codex login`. Overridable for tests.
|
|
102
|
+
this.getActiveAccount =
|
|
103
|
+
options.getActiveAccount ??
|
|
104
|
+
(() => {
|
|
105
|
+
(0, codexProfiles_1.resolveActiveCodexAccount)();
|
|
106
|
+
return (0, codexProfiles_1.getActiveCodexAccount)();
|
|
107
|
+
});
|
|
99
108
|
this.readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
100
109
|
this.writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
101
110
|
this.watchFile = options.watchFile ?? fs.watch;
|
|
@@ -207,8 +216,14 @@ class CodexQuotaWatcher {
|
|
|
207
216
|
runtimeProvider: 'codex',
|
|
208
217
|
providerId: account.id,
|
|
209
218
|
workspaceId: this.workspaceId,
|
|
210
|
-
fiveHour: {
|
|
211
|
-
|
|
219
|
+
fiveHour: {
|
|
220
|
+
utilization: liveQuota.fiveHour.utilization,
|
|
221
|
+
resetsAt: liveQuota.fiveHour.resetsAt,
|
|
222
|
+
},
|
|
223
|
+
sevenDay: {
|
|
224
|
+
utilization: liveQuota.sevenDay.utilization,
|
|
225
|
+
resetsAt: liveQuota.sevenDay.resetsAt,
|
|
226
|
+
},
|
|
212
227
|
available: liveQuota.available,
|
|
213
228
|
error: liveQuota.error,
|
|
214
229
|
source: liveQuota.source,
|
package/dist/context/composer.js
CHANGED
|
@@ -29,7 +29,9 @@ async function composeContext(slug, fidelity, provider, workspacePath) {
|
|
|
29
29
|
try {
|
|
30
30
|
sessionSummaries.push(provider.readSessionStats(sp));
|
|
31
31
|
}
|
|
32
|
-
catch {
|
|
32
|
+
catch {
|
|
33
|
+
/* skip */
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
36
|
// Apply fidelity filtering
|
|
35
37
|
let tasks;
|
|
@@ -40,23 +42,23 @@ async function composeContext(slug, fidelity, provider, workspacePath) {
|
|
|
40
42
|
case 'full':
|
|
41
43
|
tasks = allTasks;
|
|
42
44
|
decisions = allDecisions;
|
|
43
|
-
notes = allNotes.filter(n => n.status === 'active' || n.status === 'needs_review');
|
|
45
|
+
notes = allNotes.filter((n) => n.status === 'active' || n.status === 'needs_review');
|
|
44
46
|
if (history) {
|
|
45
47
|
stats = { tokens: { ...history.allTime.tokens }, cost: history.allTime.totalCost };
|
|
46
48
|
}
|
|
47
49
|
break;
|
|
48
50
|
case 'compact':
|
|
49
|
-
tasks = allTasks.filter(t => t.status === 'pending');
|
|
51
|
+
tasks = allTasks.filter((t) => t.status === 'pending');
|
|
50
52
|
decisions = allDecisions.slice(0, 10);
|
|
51
|
-
notes = allNotes.filter(n => n.status === 'active');
|
|
53
|
+
notes = allNotes.filter((n) => n.status === 'active');
|
|
52
54
|
if (history) {
|
|
53
55
|
stats = { tokens: { ...history.allTime.tokens }, cost: history.allTime.totalCost };
|
|
54
56
|
}
|
|
55
57
|
break;
|
|
56
58
|
case 'brief':
|
|
57
|
-
tasks = allTasks.filter(t => t.status === 'pending').slice(0, 3);
|
|
59
|
+
tasks = allTasks.filter((t) => t.status === 'pending').slice(0, 3);
|
|
58
60
|
decisions = []; // Count only
|
|
59
|
-
notes = allNotes.filter(n => n.importance === 'critical');
|
|
61
|
+
notes = allNotes.filter((n) => n.importance === 'critical');
|
|
60
62
|
stats = null;
|
|
61
63
|
break;
|
|
62
64
|
}
|
|
@@ -65,7 +65,7 @@ function buildSessionContextSnapshot(events, options = {}) {
|
|
|
65
65
|
const allSources = sourceState.sources;
|
|
66
66
|
const limitedSources = limitSources(allSources, options.sourceLimit ?? DEFAULT_CONTEXT_SOURCE_LIMIT);
|
|
67
67
|
const breakdown = buildLayerBreakdown(limitedSources);
|
|
68
|
-
const layers = breakdown.map(row => row.layer);
|
|
68
|
+
const layers = breakdown.map((row) => row.layer);
|
|
69
69
|
const sourceTokenTotal = allSources.reduce((sum, source) => sum + source.tokenEstimate, 0);
|
|
70
70
|
const contextTokens = metrics.currentContextSize > 0 ? metrics.currentContextSize : sourceTokenTotal;
|
|
71
71
|
const { pressure, ratio } = calculateSessionContextPressure(contextTokens, contextWindow);
|
|
@@ -381,16 +381,16 @@ function limitSources(sources, limit) {
|
|
|
381
381
|
return [];
|
|
382
382
|
if (sources.length <= limit)
|
|
383
383
|
return [...sources];
|
|
384
|
-
const pinned = sources.filter(source => source.sourceType === 'system' ||
|
|
384
|
+
const pinned = sources.filter((source) => source.sourceType === 'system' ||
|
|
385
385
|
source.sourceType === 'runtime' ||
|
|
386
386
|
source.sourceType === 'rate_limit');
|
|
387
387
|
const pinnedLimit = Math.min(pinned.length, Math.max(1, Math.floor(limit / 4)));
|
|
388
388
|
const pinnedKept = pinned.slice(0, pinnedLimit);
|
|
389
|
-
const pinnedIds = new Set(pinnedKept.map(source => source.id));
|
|
390
|
-
const remaining = sources.filter(source => !pinnedIds.has(source.id));
|
|
389
|
+
const pinnedIds = new Set(pinnedKept.map((source) => source.id));
|
|
390
|
+
const remaining = sources.filter((source) => !pinnedIds.has(source.id));
|
|
391
391
|
const latest = remaining.slice(-(limit - pinnedKept.length));
|
|
392
|
-
const keepIds = new Set([...pinnedKept, ...latest].map(source => source.id));
|
|
393
|
-
return sources.filter(source => keepIds.has(source.id));
|
|
392
|
+
const keepIds = new Set([...pinnedKept, ...latest].map((source) => source.id));
|
|
393
|
+
return sources.filter((source) => keepIds.has(source.id));
|
|
394
394
|
}
|
|
395
395
|
function latestModel(events) {
|
|
396
396
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
@@ -440,7 +440,7 @@ function inferSourceFile(input) {
|
|
|
440
440
|
}
|
|
441
441
|
const files = input.files;
|
|
442
442
|
if (Array.isArray(files)) {
|
|
443
|
-
const first = files.find(value => typeof value === 'string' && value.trim());
|
|
443
|
+
const first = files.find((value) => typeof value === 'string' && value.trim());
|
|
444
444
|
if (typeof first === 'string')
|
|
445
445
|
return first.trim();
|
|
446
446
|
}
|
|
@@ -451,7 +451,10 @@ function inferCommand(input) {
|
|
|
451
451
|
if (typeof command === 'string' && command.trim())
|
|
452
452
|
return command.trim();
|
|
453
453
|
if (Array.isArray(command)) {
|
|
454
|
-
return command
|
|
454
|
+
return (command
|
|
455
|
+
.map((part) => String(part))
|
|
456
|
+
.join(' ')
|
|
457
|
+
.trim() || undefined);
|
|
455
458
|
}
|
|
456
459
|
return undefined;
|
|
457
460
|
}
|
|
@@ -469,7 +472,7 @@ function extractText(content) {
|
|
|
469
472
|
return content;
|
|
470
473
|
if (Array.isArray(content)) {
|
|
471
474
|
return content
|
|
472
|
-
.map(block => isRecord(block) ? blockText(block) : '')
|
|
475
|
+
.map((block) => (isRecord(block) ? blockText(block) : ''))
|
|
473
476
|
.filter(Boolean)
|
|
474
477
|
.join('\n');
|
|
475
478
|
}
|
package/dist/credentialIO.js
CHANGED
|
@@ -66,9 +66,7 @@ function getCredentialsFilePath(configDir) {
|
|
|
66
66
|
function readActiveCredentials(configDir) {
|
|
67
67
|
if (process.platform === 'darwin') {
|
|
68
68
|
try {
|
|
69
|
-
const raw = (0, child_process_1.execFileSync)('security', [
|
|
70
|
-
'find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w',
|
|
71
|
-
], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
69
|
+
const raw = (0, child_process_1.execFileSync)('security', ['find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 4000, killSignal: 'SIGKILL' });
|
|
72
70
|
return JSON.parse(raw.trim());
|
|
73
71
|
}
|
|
74
72
|
catch {
|
|
@@ -96,11 +94,15 @@ function writeActiveCredentials(credentials, configDir) {
|
|
|
96
94
|
JSON.parse(json); // validate round-trip
|
|
97
95
|
if (process.platform === 'darwin') {
|
|
98
96
|
(0, child_process_1.execFileSync)('security', [
|
|
99
|
-
'add-generic-password',
|
|
100
|
-
'-
|
|
101
|
-
'-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
'add-generic-password',
|
|
98
|
+
'-U',
|
|
99
|
+
'-s',
|
|
100
|
+
(0, claudeProfiles_1.claudeKeychainService)(configDir),
|
|
101
|
+
'-a',
|
|
102
|
+
process.env.USER || 'user',
|
|
103
|
+
'-w',
|
|
104
|
+
json,
|
|
105
|
+
], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 4000, killSignal: 'SIGKILL' });
|
|
104
106
|
return;
|
|
105
107
|
}
|
|
106
108
|
// Linux / WSL / Windows — file-based
|
|
@@ -62,7 +62,15 @@ const os = __importStar(require("node:os"));
|
|
|
62
62
|
const node_path_1 = require("node:path");
|
|
63
63
|
const URL_RE = /(?:https?|file):\/\/[^\s"'<>)\]`\\]+/g;
|
|
64
64
|
const PATH_RE = /(?:~|\.{0,2}\/)?[A-Za-z0-9_@.-]*(?:\/[A-Za-z0-9_@.-]+)+(?::\d+){0,2}|[A-Za-z0-9_@-]+\.[A-Za-z0-9]{1,8}(?::\d+){0,2}/g;
|
|
65
|
-
const SHELL_TAGS = new Set([
|
|
65
|
+
const SHELL_TAGS = new Set([
|
|
66
|
+
'sh',
|
|
67
|
+
'bash',
|
|
68
|
+
'shell',
|
|
69
|
+
'zsh',
|
|
70
|
+
'console',
|
|
71
|
+
'shellscript',
|
|
72
|
+
'shell-session',
|
|
73
|
+
]);
|
|
66
74
|
exports.DEFAULT_CAPS = {
|
|
67
75
|
command: 60,
|
|
68
76
|
path: 60,
|
|
@@ -149,7 +149,13 @@ function accumClaude(filePath, cwd, acc) {
|
|
|
149
149
|
}
|
|
150
150
|
function readClaudeAssets(cwd, limit = 3) {
|
|
151
151
|
const sessions = claudeSessions(cwd, limit);
|
|
152
|
-
const acc = {
|
|
152
|
+
const acc = {
|
|
153
|
+
urls: [],
|
|
154
|
+
paths: [],
|
|
155
|
+
commands: [],
|
|
156
|
+
plans: [],
|
|
157
|
+
hadSession: sessions.length > 0,
|
|
158
|
+
};
|
|
153
159
|
for (const session of sessions)
|
|
154
160
|
accumClaude(session, cwd, acc);
|
|
155
161
|
return acc;
|
|
@@ -99,7 +99,9 @@ function rolloutFiles(limit = 150) {
|
|
|
99
99
|
if (entry.isDirectory()) {
|
|
100
100
|
walk(fullPath);
|
|
101
101
|
}
|
|
102
|
-
else if (entry.isFile() &&
|
|
102
|
+
else if (entry.isFile() &&
|
|
103
|
+
entry.name.startsWith('rollout-') &&
|
|
104
|
+
entry.name.endsWith('.jsonl')) {
|
|
103
105
|
try {
|
|
104
106
|
if (seen.has(fullPath))
|
|
105
107
|
continue;
|
|
@@ -117,7 +119,10 @@ function rolloutFiles(limit = 150) {
|
|
|
117
119
|
if (dirExists(sessionsDir))
|
|
118
120
|
walk(sessionsDir);
|
|
119
121
|
}
|
|
120
|
-
return entries
|
|
122
|
+
return entries
|
|
123
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
124
|
+
.slice(0, limit)
|
|
125
|
+
.map((entry) => entry.path);
|
|
121
126
|
}
|
|
122
127
|
function codexSessions(cwd, limit = 3) {
|
|
123
128
|
const exactCwd = (0, node_path_1.resolve)(cwd);
|
|
@@ -182,7 +187,13 @@ function addExecutedCommandAssets(acc, command, cwd, timestamp, meta) {
|
|
|
182
187
|
function readCodexAssets(cwd, limit = 3) {
|
|
183
188
|
const exactCwd = (0, node_path_1.resolve)(cwd);
|
|
184
189
|
const files = codexSessions(exactCwd, limit);
|
|
185
|
-
const acc = {
|
|
190
|
+
const acc = {
|
|
191
|
+
urls: [],
|
|
192
|
+
paths: [],
|
|
193
|
+
commands: [],
|
|
194
|
+
plans: [],
|
|
195
|
+
hadSession: files.length > 0,
|
|
196
|
+
};
|
|
186
197
|
const skip = (line) => line.includes('"type":"function_call_output"') ||
|
|
187
198
|
line.includes('"type":"reasoning"') ||
|
|
188
199
|
line.includes('"type":"token_count"');
|
|
@@ -37,10 +37,7 @@ function extractToolCalls(event) {
|
|
|
37
37
|
const timestamp = new Date(event.timestamp);
|
|
38
38
|
const calls = [];
|
|
39
39
|
for (const block of content) {
|
|
40
|
-
if (block &&
|
|
41
|
-
typeof block === 'object' &&
|
|
42
|
-
'type' in block &&
|
|
43
|
-
block.type === 'tool_use') {
|
|
40
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'tool_use') {
|
|
44
41
|
const toolBlock = block;
|
|
45
42
|
if (toolBlock.name) {
|
|
46
43
|
calls.push({
|