sidekick-shared 0.27.6 → 0.28.0
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/CHANGELOG.md +28 -0
- package/README.md +58 -18
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +2 -9
- package/dist/claudeProfiles.js +10 -25
- package/dist/codexPaths.d.ts +22 -0
- package/dist/codexPaths.js +67 -0
- package/dist/codexProfiles.d.ts +1 -4
- package/dist/codexProfiles.js +5 -43
- package/dist/doctor.js +2 -2
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +49 -15
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in keep-alive for saved-but-inactive accounts. Sidekick never calls the
|
|
3
|
+
* providers' OAuth endpoints itself: it runs the official CLI inside the
|
|
4
|
+
* profile's isolated home and lets the CLI refresh (and rotate) the token
|
|
5
|
+
* where it belongs, then re-reads the store to record the new expiry.
|
|
6
|
+
*
|
|
7
|
+
* A refresh rotates the refresh token, so keep-alive only runs where the
|
|
8
|
+
* profile home holds the one live copy of its chain: never the live account,
|
|
9
|
+
* never a copy whose refresh token equals live's, never a copy taken from the
|
|
10
|
+
* live store that no switch has handed back yet, and never a stale copy when
|
|
11
|
+
* the backup is newer. The check and a lease are taken under the swap lock, so
|
|
12
|
+
* no switch can install that account while its CLI is refreshing.
|
|
13
|
+
*/
|
|
14
|
+
import { runCodexKeepAlive } from './codexKeepAlive';
|
|
15
|
+
import { beginCodexKeepAlive, finishCodexKeepAlive, getCodexKeepAliveBlocker, } from './codexProfiles';
|
|
16
|
+
import { getSystemCodexHome } from './codexPaths';
|
|
17
|
+
import { canonicalCodexPath, readCodexCredentialStore } from './codexCredentialStore';
|
|
18
|
+
import { compareCodexAuthFreshness, identitiesMatch, readAuthIdentityFromRaw } from './codexAuth';
|
|
19
|
+
import { execFile } from 'child_process';
|
|
20
|
+
import { getAccountHealth, listAccountsWithHealth } from './accountHealth';
|
|
21
|
+
import { getAccountLaunchEnv } from './accountLaunch';
|
|
22
|
+
import { beginClaudeKeepAlive, finishClaudeKeepAlive, getClaudeKeepAliveBlocker } from './accounts';
|
|
23
|
+
/** The command each CLI runs to load (and refresh) its stored login. */
|
|
24
|
+
export function keepAliveCommand(provider) {
|
|
25
|
+
if (provider === 'codex')
|
|
26
|
+
return { command: 'codex', args: ['app-server', '-c', 'cli_auth_credentials_store="file"'] };
|
|
27
|
+
const override = process.env.SIDEKICK_CLAUDE_KEEPALIVE_ARGS?.trim();
|
|
28
|
+
return { command: 'claude', args: override ? override.split(/\s+/) : ['auth', 'status'] };
|
|
29
|
+
}
|
|
30
|
+
/** How long a keep-alive lease outlives the CLI timeout before it lapses on its own. */
|
|
31
|
+
const LEASE_GRACE_MS = 30_000;
|
|
32
|
+
const defaultRunner = (command, args, env, timeoutMs) => command === 'codex'
|
|
33
|
+
? runCodexKeepAlive(command, args, env, timeoutMs)
|
|
34
|
+
: new Promise((resolve) => {
|
|
35
|
+
try {
|
|
36
|
+
execFile(command, args, { encoding: 'utf8', env, timeout: timeoutMs, killSignal: 'SIGKILL', windowsHide: true }, (error, stdout) => {
|
|
37
|
+
const status = error
|
|
38
|
+
? (error.code ?? null)
|
|
39
|
+
: 0;
|
|
40
|
+
resolve({
|
|
41
|
+
status: typeof status === 'number' ? status : null,
|
|
42
|
+
stdout: String(stdout ?? ''),
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
resolve({ status: null, stdout: '' });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
function eligible(view, includeFresh) {
|
|
51
|
+
if (view.health.isLive)
|
|
52
|
+
return 'live account; the CLI refreshes it in normal use';
|
|
53
|
+
if (view.health.state === 'expired')
|
|
54
|
+
return 'expired; sign in again';
|
|
55
|
+
if (view.health.state === 'missing')
|
|
56
|
+
return 'no stored credentials';
|
|
57
|
+
if (view.health.state === 'fresh' && !includeFresh)
|
|
58
|
+
return 'fresh';
|
|
59
|
+
if (view.providerId === 'codex' && view.health.state === 'unknown')
|
|
60
|
+
return 'api-key login';
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Serialised, bounded refresh of every inactive account that is `expiring`
|
|
65
|
+
* (or `unknown` for Claude). Never touches the live account or any copy of
|
|
66
|
+
* its chain.
|
|
67
|
+
*/
|
|
68
|
+
export async function refreshInactiveAccounts(options = {}) {
|
|
69
|
+
const result = { refreshed: [], skipped: [], failed: [] };
|
|
70
|
+
const timeoutMs = options.timeoutMs ?? 20_000;
|
|
71
|
+
const runner = options.runner ?? defaultRunner;
|
|
72
|
+
const views = listAccountsWithHealth(undefined, { now: options.now }).filter((view) => !options.providers || options.providers.includes(view.providerId));
|
|
73
|
+
for (const view of views) {
|
|
74
|
+
const reason = eligible(view, options.includeFresh ?? false);
|
|
75
|
+
if (reason) {
|
|
76
|
+
result.skipped.push({ id: view.id, providerId: view.providerId, reason });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const isClaude = view.providerId === 'claude-code';
|
|
80
|
+
const beginLease = isClaude ? beginClaudeKeepAlive : beginCodexKeepAlive;
|
|
81
|
+
const finishLease = isClaude ? finishClaudeKeepAlive : finishCodexKeepAlive;
|
|
82
|
+
if (options.dryRun) {
|
|
83
|
+
const blocker = (isClaude ? getClaudeKeepAliveBlocker : getCodexKeepAliveBlocker)(view.id);
|
|
84
|
+
if (blocker) {
|
|
85
|
+
result.skipped.push({ id: view.id, providerId: view.providerId, reason: blocker });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const { command, args } = keepAliveCommand(view.providerId);
|
|
90
|
+
if (options.dryRun) {
|
|
91
|
+
result.skipped.push({
|
|
92
|
+
id: view.id,
|
|
93
|
+
providerId: view.providerId,
|
|
94
|
+
reason: `dry run: would run ${command} ${args.join(' ')}`,
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
{
|
|
99
|
+
const begin = beginLease(view.id, timeoutMs + LEASE_GRACE_MS);
|
|
100
|
+
if (!begin.ok) {
|
|
101
|
+
result.skipped.push({ id: view.id, providerId: view.providerId, reason: begin.reason });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let released = false;
|
|
106
|
+
const release = () => {
|
|
107
|
+
if (!released) {
|
|
108
|
+
released = true;
|
|
109
|
+
finishLease(view.id);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
const launch = getAccountLaunchEnv(view.providerId, view.id, {
|
|
114
|
+
materialize: true,
|
|
115
|
+
restoreBackup: false,
|
|
116
|
+
});
|
|
117
|
+
if (launch.error) {
|
|
118
|
+
release();
|
|
119
|
+
result.skipped.push({ id: view.id, providerId: view.providerId, reason: launch.error });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!isClaude &&
|
|
123
|
+
canonicalCodexPath(launch.home) === canonicalCodexPath(getSystemCodexHome())) {
|
|
124
|
+
result.skipped.push({
|
|
125
|
+
id: view.id,
|
|
126
|
+
providerId: view.providerId,
|
|
127
|
+
reason: 'the account became live before refresh',
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const before = isClaude ? null : readCodexCredentialStore(launch.home);
|
|
132
|
+
const env = { ...process.env, ...launch.env };
|
|
133
|
+
for (const name of launch.envUnset)
|
|
134
|
+
delete env[name];
|
|
135
|
+
if (!isClaude)
|
|
136
|
+
for (const name of [
|
|
137
|
+
'OPENAI_API_KEY',
|
|
138
|
+
'CODEX_API_KEY',
|
|
139
|
+
'CODEX_ACCESS_TOKEN',
|
|
140
|
+
'OPENAI_FEDERATION_RULE_ID',
|
|
141
|
+
'OPENAI_IDENTITY_TOKEN_FILE',
|
|
142
|
+
'OPENAI_WORKLOAD_IDENTITY_CONTEXT',
|
|
143
|
+
])
|
|
144
|
+
delete env[name];
|
|
145
|
+
let run;
|
|
146
|
+
try {
|
|
147
|
+
run = await runner(command, args, env, timeoutMs);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
release();
|
|
151
|
+
}
|
|
152
|
+
const after = getAccountHealth(view.providerId, view.id, {
|
|
153
|
+
probe: 'store',
|
|
154
|
+
now: options.now,
|
|
155
|
+
});
|
|
156
|
+
const codexAfter = isClaude ? null : readCodexCredentialStore(launch.home);
|
|
157
|
+
const advanced = isClaude ||
|
|
158
|
+
(before?.status === 'ok' &&
|
|
159
|
+
codexAfter?.status === 'ok' &&
|
|
160
|
+
identitiesMatch(readAuthIdentityFromRaw(before.snapshot.authRaw), readAuthIdentityFromRaw(codexAfter.snapshot.authRaw)) &&
|
|
161
|
+
compareCodexAuthFreshness(codexAfter.snapshot.authRaw, before.snapshot.authRaw) > 0);
|
|
162
|
+
if (after.state === 'fresh' && advanced && (isClaude || run.status === 0)) {
|
|
163
|
+
result.refreshed.push({ id: view.id, providerId: view.providerId });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
result.failed.push({
|
|
167
|
+
id: view.id,
|
|
168
|
+
providerId: view.providerId,
|
|
169
|
+
error: run.status === 0
|
|
170
|
+
? isClaude
|
|
171
|
+
? `${command} ran but the stored credential is still ${after.state}.`
|
|
172
|
+
: `${command} ran but no verified credential advance was observed (health: ${after.state}).`
|
|
173
|
+
: `${command} exited with status ${run.status ?? 'unknown'}.${isClaude ? '' : ' Codex app-server refresh requires a compatible Codex CLI; update it and retry.'}`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
result.failed.push({
|
|
179
|
+
id: view.id,
|
|
180
|
+
providerId: view.providerId,
|
|
181
|
+
error: isClaude ? String(err) : 'Codex keep-alive could not complete safely.',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
release();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function decideLiveCredentialWrite(f) {
|
|
2
|
+
const refuse = (code, reason) => ({ allow: false, code, reason });
|
|
3
|
+
if (f.liveStatus === 'error' || (f.rejectCorrupt && f.liveStatus === 'corrupt')) {
|
|
4
|
+
return refuse('live-unreadable', `The live ${f.provider} login could not be read, so it was left untouched.`);
|
|
5
|
+
}
|
|
6
|
+
if (f.intent === 'rollback') {
|
|
7
|
+
return f.matchesWritten || (f.allowNonOkRollback && f.liveStatus !== 'ok')
|
|
8
|
+
? { allow: true }
|
|
9
|
+
: refuse('live-changed', `The live ${f.provider} login changed after the switch wrote it; it was kept.`);
|
|
10
|
+
}
|
|
11
|
+
if (!f.candidateValid)
|
|
12
|
+
return refuse('invalid-candidate', `The saved credential is not a ${f.provider} login.`);
|
|
13
|
+
if (f.intent === 'switch') {
|
|
14
|
+
if (f.liveAccountId !== null && f.liveAccountId === f.candidateAccountId) {
|
|
15
|
+
return refuse('same-account', 'That account is already the live login.');
|
|
16
|
+
}
|
|
17
|
+
if (f.sameLink)
|
|
18
|
+
return refuse('same-link', 'The saved login is the same refresh token the live login holds; sign in to that account again.');
|
|
19
|
+
if (f.liveHasCredential && !f.capturedOutgoing) {
|
|
20
|
+
return refuse('not-captured', 'The outgoing live login was not saved first, so it was left untouched.');
|
|
21
|
+
}
|
|
22
|
+
return { allow: true };
|
|
23
|
+
}
|
|
24
|
+
if (f.liveAccountId !== null && f.liveAccountId !== f.candidateAccountId) {
|
|
25
|
+
return refuse('different-account', `The live ${f.provider} login belongs to another account; switch accounts instead.`);
|
|
26
|
+
}
|
|
27
|
+
if (!f.liveHasCredential)
|
|
28
|
+
return { allow: true };
|
|
29
|
+
if (!f.strictlyNewer || (f.requireDifferentLinkForAdvance && f.sameLink)) {
|
|
30
|
+
return refuse('not-newer', `The live ${f.provider} login is already at least as new; it was kept.`);
|
|
31
|
+
}
|
|
32
|
+
return { allow: true };
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const aliases = new Map();
|
|
2
|
+
function normalizeAlias(value) {
|
|
3
|
+
return value.trim().toLowerCase();
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Register a short or host-specific model name that resolves to a canonical ID.
|
|
7
|
+
* Invalid and cyclic registrations are rejected without changing the registry.
|
|
8
|
+
*/
|
|
9
|
+
export function registerModelAlias(alias, canonicalId) {
|
|
10
|
+
const normalizedAlias = normalizeAlias(alias);
|
|
11
|
+
const normalizedCanonicalId = normalizeAlias(canonicalId);
|
|
12
|
+
if (!normalizedAlias || !normalizedCanonicalId || normalizedAlias === normalizedCanonicalId) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
let candidate = normalizedCanonicalId;
|
|
16
|
+
const visited = new Set([normalizedAlias]);
|
|
17
|
+
while (aliases.has(candidate)) {
|
|
18
|
+
if (visited.has(candidate))
|
|
19
|
+
return false;
|
|
20
|
+
visited.add(candidate);
|
|
21
|
+
candidate = aliases.get(candidate);
|
|
22
|
+
}
|
|
23
|
+
if (candidate === normalizedAlias)
|
|
24
|
+
return false;
|
|
25
|
+
aliases.set(normalizedAlias, normalizedCanonicalId);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
/** Resolve a registered alias, returning normalized input when no alias exists. */
|
|
29
|
+
export function resolveModelAlias(modelId) {
|
|
30
|
+
let current = normalizeAlias(modelId);
|
|
31
|
+
const visited = new Set();
|
|
32
|
+
while (aliases.has(current) && !visited.has(current)) {
|
|
33
|
+
visited.add(current);
|
|
34
|
+
current = aliases.get(current);
|
|
35
|
+
}
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
38
|
+
/** Internal snapshot used by the cross-realm catalog serializer. */
|
|
39
|
+
export function _getModelAliases() {
|
|
40
|
+
return Object.fromEntries(aliases);
|
|
41
|
+
}
|
|
42
|
+
/** Internal hydration hook used by the cross-realm catalog importer. */
|
|
43
|
+
export function _importModelAliases(values) {
|
|
44
|
+
for (const [alias, canonicalId] of Object.entries(values)) {
|
|
45
|
+
registerModelAlias(alias, canonicalId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Internal test hook. */
|
|
49
|
+
export function _clearModelAliases() {
|
|
50
|
+
aliases.clear();
|
|
51
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { _getModelContextIds, _setImportedResolvedContextWindows, resolveModelContextWindow, } from './modelContext';
|
|
2
|
+
import { _getModelPricingIds, _setImportedResolvedPricing, resolveModelPricing, } from './modelInfo';
|
|
3
|
+
import { _getModelAliases, _importModelAliases, registerModelAlias, resolveModelAlias, } from './modelAliases';
|
|
4
|
+
export const RESOLVED_MODEL_CATALOG_SCHEMA_VERSION = 1;
|
|
5
|
+
function normalizedIds(ids) {
|
|
6
|
+
if (ids) {
|
|
7
|
+
return Array.from(new Set(ids.map((id) => id.trim()).filter(Boolean)));
|
|
8
|
+
}
|
|
9
|
+
const aliases = _getModelAliases();
|
|
10
|
+
return Array.from(new Set([
|
|
11
|
+
..._getModelContextIds(),
|
|
12
|
+
..._getModelPricingIds(),
|
|
13
|
+
...Object.keys(aliases),
|
|
14
|
+
...Object.values(aliases),
|
|
15
|
+
])).sort();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Export an entirely serializable catalog suitable for IPC to another JS realm.
|
|
19
|
+
* Each entry records published, observed, and tier-effective context windows,
|
|
20
|
+
* plus pricing match provenance (including prefix inheritance).
|
|
21
|
+
*/
|
|
22
|
+
export function exportResolvedModelCatalog(ids) {
|
|
23
|
+
const models = {};
|
|
24
|
+
const selectedIds = normalizedIds(ids);
|
|
25
|
+
for (const id of selectedIds) {
|
|
26
|
+
const canonicalId = resolveModelAlias(id);
|
|
27
|
+
models[id] = {
|
|
28
|
+
id,
|
|
29
|
+
canonicalId,
|
|
30
|
+
contextWindow: resolveModelContextWindow(id),
|
|
31
|
+
pricing: resolveModelPricing(id),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const selectedCanonicalIds = new Set(Object.values(models).flatMap(({ id, canonicalId }) => [id, canonicalId]));
|
|
35
|
+
const aliases = Object.fromEntries(Object.entries(_getModelAliases()).filter(([alias, canonicalId]) => selectedCanonicalIds.has(alias) || selectedCanonicalIds.has(canonicalId)));
|
|
36
|
+
return { schemaVersion: RESOLVED_MODEL_CATALOG_SCHEMA_VERSION, models, aliases };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Hydrate exact model resolutions exported by another bundle realm.
|
|
40
|
+
* Invalid entries are skipped and reported; import never partially mutates an
|
|
41
|
+
* entry or throws for malformed IPC data.
|
|
42
|
+
*/
|
|
43
|
+
export function importResolvedModelCatalog(snapshot) {
|
|
44
|
+
const diagnostics = [];
|
|
45
|
+
if (!isRecord(snapshot) || snapshot.schemaVersion !== RESOLVED_MODEL_CATALOG_SCHEMA_VERSION) {
|
|
46
|
+
return { imported: 0, diagnostics: ['unsupported resolved model catalog snapshot'] };
|
|
47
|
+
}
|
|
48
|
+
const aliases = isStringRecord(snapshot.aliases) ? snapshot.aliases : {};
|
|
49
|
+
_importModelAliases(aliases);
|
|
50
|
+
const contexts = {};
|
|
51
|
+
const prices = {};
|
|
52
|
+
const models = isRecord(snapshot.models) ? snapshot.models : {};
|
|
53
|
+
let imported = 0;
|
|
54
|
+
for (const [key, rawEntry] of Object.entries(models)) {
|
|
55
|
+
if (!isResolvedEntry(rawEntry)) {
|
|
56
|
+
diagnostics.push(`ignored invalid model catalog entry: ${key}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const entry = rawEntry;
|
|
60
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
61
|
+
const canonicalId = entry.canonicalId.trim().toLowerCase();
|
|
62
|
+
const context = cloneContext(entry.contextWindow, entry.id, canonicalId);
|
|
63
|
+
const pricing = clonePricing(entry.pricing, entry.id, canonicalId);
|
|
64
|
+
// Unresolved facets are not importable facts: pinning a 'default' window
|
|
65
|
+
// or 'none' pricing would outrank data the importing realm can resolve
|
|
66
|
+
// (or later learn) on its own.
|
|
67
|
+
const contextResolved = context.provenance.source !== 'default';
|
|
68
|
+
const pricingResolved = pricing.provenance.source !== 'none';
|
|
69
|
+
if (!contextResolved && !pricingResolved) {
|
|
70
|
+
diagnostics.push(`skipped unresolved model catalog entry: ${key}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const id of new Set([normalizedKey, entry.id.trim().toLowerCase(), canonicalId])) {
|
|
74
|
+
if (!id)
|
|
75
|
+
continue;
|
|
76
|
+
if (contextResolved)
|
|
77
|
+
contexts[id] = context;
|
|
78
|
+
if (pricingResolved)
|
|
79
|
+
prices[id] = pricing;
|
|
80
|
+
}
|
|
81
|
+
imported++;
|
|
82
|
+
}
|
|
83
|
+
_setImportedResolvedContextWindows(contexts);
|
|
84
|
+
_setImportedResolvedPricing(prices);
|
|
85
|
+
return { imported, diagnostics };
|
|
86
|
+
}
|
|
87
|
+
function cloneContext(value, modelId, canonicalModelId) {
|
|
88
|
+
return {
|
|
89
|
+
...value,
|
|
90
|
+
modelId,
|
|
91
|
+
canonicalModelId,
|
|
92
|
+
provenance: { ...value.provenance },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function clonePricing(value, modelId, canonicalModelId) {
|
|
96
|
+
return {
|
|
97
|
+
...value,
|
|
98
|
+
modelId,
|
|
99
|
+
canonicalModelId,
|
|
100
|
+
pricing: value.pricing ? { ...value.pricing } : null,
|
|
101
|
+
provenance: { ...value.provenance },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function isRecord(value) {
|
|
105
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
107
|
+
function isStringRecord(value) {
|
|
108
|
+
return isRecord(value) && Object.values(value).every((item) => typeof item === 'string');
|
|
109
|
+
}
|
|
110
|
+
function validPricing(value) {
|
|
111
|
+
if (!isRecord(value))
|
|
112
|
+
return false;
|
|
113
|
+
return [
|
|
114
|
+
value.inputCostPerMillion,
|
|
115
|
+
value.outputCostPerMillion,
|
|
116
|
+
value.cacheWriteCostPerMillion,
|
|
117
|
+
value.cacheReadCostPerMillion,
|
|
118
|
+
].every((item) => typeof item === 'number' && Number.isFinite(item) && item >= 0);
|
|
119
|
+
}
|
|
120
|
+
function isResolvedEntry(value) {
|
|
121
|
+
if (!isRecord(value) || typeof value.id !== 'string' || typeof value.canonicalId !== 'string') {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const context = value.contextWindow;
|
|
125
|
+
const pricing = value.pricing;
|
|
126
|
+
if (!isRecord(context) || !isRecord(pricing))
|
|
127
|
+
return false;
|
|
128
|
+
if (typeof context.tierEffective !== 'number' ||
|
|
129
|
+
!Number.isFinite(context.tierEffective) ||
|
|
130
|
+
context.tierEffective <= 0 ||
|
|
131
|
+
(context.published !== null && typeof context.published !== 'number') ||
|
|
132
|
+
(context.observed !== null && typeof context.observed !== 'number') ||
|
|
133
|
+
!isRecord(context.provenance) ||
|
|
134
|
+
!isRecord(pricing.provenance)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return pricing.pricing === null || validPricing(pricing.pricing);
|
|
138
|
+
}
|
|
139
|
+
export { registerModelAlias, resolveModelAlias };
|