sidekick-shared 0.22.0 → 0.23.1
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 +75 -63
- package/dist/accountManager.js +10 -0
- package/dist/accountRegistry.d.ts +2 -0
- package/dist/accountStatus.d.ts +1 -0
- package/dist/accountStatus.js +6 -0
- package/dist/accounts.js +2 -0
- package/dist/aggregation/EventAggregator.d.ts +36 -2
- package/dist/aggregation/EventAggregator.js +259 -33
- package/dist/aggregation/snapshot.d.ts +1 -1
- package/dist/aggregation/snapshot.js +2 -1
- package/dist/aggregation/types.d.ts +25 -1
- package/dist/analytics/codeImpact.d.ts +20 -0
- package/dist/analytics/codeImpact.js +21 -0
- package/dist/analytics/compactionLedger.d.ts +11 -0
- package/dist/analytics/compactionLedger.js +31 -0
- package/dist/analytics/qualityScore.d.ts +26 -0
- package/dist/analytics/qualityScore.js +115 -0
- package/dist/autoSwitch.d.ts +1 -1
- package/dist/autoSwitch.js +14 -3
- package/dist/codexProfiles.js +2 -0
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +27 -0
- package/dist/codexQuotaWatcher.d.ts +7 -0
- package/dist/codexQuotaWatcher.js +37 -19
- package/dist/context/composer.d.ts +3 -2
- package/dist/context/composer.js +5 -5
- package/dist/context/sessionContext.js +35 -9
- package/dist/credentialIO.js +19 -5
- package/dist/doctor.d.ts +55 -0
- package/dist/doctor.js +244 -0
- package/dist/errorHistory.d.ts +33 -0
- package/dist/errorHistory.js +95 -0
- package/dist/extractors/errorTaxonomy.d.ts +6 -0
- package/dist/extractors/errorTaxonomy.js +47 -0
- package/dist/extractors/toolCall.d.ts +9 -0
- package/dist/extractors/toolCall.js +79 -0
- package/dist/handoffUrl.d.ts +7 -0
- package/dist/handoffUrl.js +26 -0
- package/dist/index.d.ts +32 -9
- package/dist/index.js +64 -24
- package/dist/modelInfo.js +6 -0
- package/dist/multiProviderQuotaService.js +13 -2
- package/dist/parsers/codexParser.js +17 -1
- package/dist/parsers/openCodeParser.js +13 -0
- package/dist/parsers/planExtractor.js +17 -6
- package/dist/parsers/sessionActivityDetector.js +2 -0
- package/dist/parsers/sessionPathResolver.js +19 -16
- package/dist/parsers/subagentScanner.d.ts +1 -1
- package/dist/parsers/subagentScanner.js +4 -5
- package/dist/parsers/subagentTraceParser.js +4 -7
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +18 -5
- package/dist/projectMigration.d.ts +23 -0
- package/dist/projectMigration.js +95 -0
- package/dist/providerStatus.js +13 -2
- package/dist/providers/claudeCode.js +18 -4
- package/dist/providers/codex.js +42 -13
- package/dist/providers/codexDatabase.d.ts +1 -11
- package/dist/providers/detect.js +32 -3
- package/dist/providers/openCodeDatabase.d.ts +1 -31
- package/dist/providers/openCodeDatabase.js +21 -11
- package/dist/providers/types.d.ts +1 -0
- package/dist/quota.js +8 -0
- package/dist/quotaPoller.js +17 -6
- package/dist/quotaSnapshots.js +80 -26
- package/dist/readers/decisions.d.ts +2 -1
- package/dist/readers/decisions.js +5 -5
- package/dist/readers/handoff.d.ts +2 -1
- package/dist/readers/handoff.js +13 -8
- package/dist/readers/helpers.d.ts +3 -0
- package/dist/readers/helpers.js +12 -0
- package/dist/readers/notes.d.ts +2 -1
- package/dist/readers/notes.js +11 -8
- package/dist/readers/plans.js +3 -5
- package/dist/readers/tasks.d.ts +2 -1
- package/dist/readers/tasks.js +5 -5
- package/dist/report/htmlHelpers.js +6 -1
- package/dist/report/openBrowser.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +10 -1
- package/dist/schemas/observedSessionV1.d.ts +499 -0
- package/dist/schemas/observedSessionV1.js +69 -0
- package/dist/schemas/sessionEvent.d.ts +13 -0
- package/dist/schemas/sessionEvent.js +19 -0
- package/dist/search/sessionSearch.d.ts +2 -2
- package/dist/search/sessionSearch.js +2 -3
- package/dist/sessionMonitor.d.ts +29 -0
- package/dist/sessionMonitor.js +71 -0
- package/dist/statusline/BurnRateCalculator.d.ts +14 -0
- package/dist/statusline/BurnRateCalculator.js +45 -0
- package/dist/statusline/formatter.d.ts +16 -0
- package/dist/statusline/formatter.js +84 -0
- package/dist/statusline/index.d.ts +7 -0
- package/dist/statusline/index.js +13 -0
- package/dist/terminalSync.js +16 -7
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/historicalData.d.ts +49 -1
- package/dist/types/historicalData.js +28 -2
- package/dist/types/observedSessionV1.d.ts +70 -0
- package/dist/types/observedSessionV1.js +186 -0
- package/dist/types/opencode.d.ts +1 -0
- package/dist/types/sessionEvent.d.ts +19 -1
- package/dist/watchers/eventBridge.js +1 -0
- package/dist/watchers/factory.js +14 -3
- package/dist/watchers/jsonlTail.js +34 -14
- package/dist/watchers/jsonlWatcher.js +5 -3
- package/dist/watchers/providerReaderWatcher.js +4 -2
- package/dist/watchers/sqliteWatcher.d.ts +2 -0
- package/dist/watchers/sqliteWatcher.js +27 -14
- package/dist/watchers/types.d.ts +5 -0
- package/dist/writers/atomic.d.ts +4 -0
- package/dist/writers/atomic.js +200 -0
- package/dist/writers/decisions.d.ts +12 -0
- package/dist/writers/decisions.js +88 -0
- package/dist/writers/notes.d.ts +8 -0
- package/dist/writers/notes.js +82 -0
- package/dist/writers/tasks.d.ts +6 -0
- package/dist/writers/tasks.js +100 -0
- package/dist/zaiQuotaWatcher.js +6 -0
- package/package.json +5 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ModelCostInput {
|
|
2
|
+
model: string;
|
|
3
|
+
cost: number;
|
|
4
|
+
priced?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface CodeImpact {
|
|
7
|
+
changedLines: number;
|
|
8
|
+
additions: number;
|
|
9
|
+
deletions: number;
|
|
10
|
+
totalCost: number;
|
|
11
|
+
costPerChangedLine: number | null;
|
|
12
|
+
byModel: Array<{
|
|
13
|
+
model: string;
|
|
14
|
+
cost: number;
|
|
15
|
+
priced: boolean;
|
|
16
|
+
changedLines: number;
|
|
17
|
+
costPerChangedLine: number | null;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export declare function calculateCodeImpact(totalCost: number, additions: number, deletions: number, models?: ModelCostInput[]): CodeImpact;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateCodeImpact = calculateCodeImpact;
|
|
4
|
+
function calculateCodeImpact(totalCost, additions, deletions, models = []) {
|
|
5
|
+
const safeAdditions = Math.max(0, additions);
|
|
6
|
+
const safeDeletions = Math.max(0, deletions);
|
|
7
|
+
const changedLines = safeAdditions + safeDeletions;
|
|
8
|
+
return {
|
|
9
|
+
changedLines,
|
|
10
|
+
additions: safeAdditions,
|
|
11
|
+
deletions: safeDeletions,
|
|
12
|
+
totalCost,
|
|
13
|
+
costPerChangedLine: changedLines > 0 ? totalCost / changedLines : null,
|
|
14
|
+
byModel: models.map((model) => ({
|
|
15
|
+
...model,
|
|
16
|
+
priced: model.priced !== false,
|
|
17
|
+
changedLines,
|
|
18
|
+
costPerChangedLine: changedLines > 0 && model.priced !== false ? model.cost / changedLines : null,
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CompactionEvent } from '../types/sessionEvent';
|
|
2
|
+
export interface CompactionLedger {
|
|
3
|
+
count: number;
|
|
4
|
+
tokensEvicted: number;
|
|
5
|
+
reestablishmentCostUsd: number | null;
|
|
6
|
+
source: 'reported' | 'heuristic' | 'mixed';
|
|
7
|
+
reportedCount: number;
|
|
8
|
+
heuristicCount: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function calculateCompactionLedger(events: CompactionEvent[], model?: string | null): CompactionLedger;
|
|
11
|
+
export declare function formatCompactionLedger(ledger: CompactionLedger): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateCompactionLedger = calculateCompactionLedger;
|
|
4
|
+
exports.formatCompactionLedger = formatCompactionLedger;
|
|
5
|
+
const modelInfo_1 = require("../modelInfo");
|
|
6
|
+
function calculateCompactionLedger(events, model) {
|
|
7
|
+
const tokensEvicted = events.reduce((sum, event) => sum + Math.max(0, event.tokensReclaimed), 0);
|
|
8
|
+
const reportedCount = events.filter((event) => event.source === 'reported').length;
|
|
9
|
+
const heuristicCount = events.length - reportedCount;
|
|
10
|
+
const pricing = model ? (0, modelInfo_1.getModelPricing)(model) : null;
|
|
11
|
+
return {
|
|
12
|
+
count: events.length,
|
|
13
|
+
tokensEvicted,
|
|
14
|
+
reestablishmentCostUsd: pricing
|
|
15
|
+
? (0, modelInfo_1.calculateCostWithPricing)({ inputTokens: tokensEvicted, outputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 }, pricing)
|
|
16
|
+
: null,
|
|
17
|
+
source: reportedCount > 0 && heuristicCount > 0
|
|
18
|
+
? 'mixed'
|
|
19
|
+
: reportedCount > 0
|
|
20
|
+
? 'reported'
|
|
21
|
+
: 'heuristic',
|
|
22
|
+
reportedCount,
|
|
23
|
+
heuristicCount,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function formatCompactionLedger(ledger) {
|
|
27
|
+
const cost = ledger.reestablishmentCostUsd == null
|
|
28
|
+
? 'cost unavailable'
|
|
29
|
+
: `~$${ledger.reestablishmentCostUsd.toFixed(2)} re-establishing context`;
|
|
30
|
+
return `${ledger.count} compaction${ledger.count === 1 ? '' : 's'} · ${ledger.tokensEvicted.toLocaleString()} tokens evicted · ${cost} · ${ledger.source}`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AggregatedMetrics } from '../aggregation/types';
|
|
2
|
+
import type { SessionHistoryRecord } from '../types/historicalData';
|
|
3
|
+
export type QualityFactorId = 'latency' | 'reliability' | 'context' | 'permission_safety' | 'outcomes';
|
|
4
|
+
export interface QualityFactorContribution {
|
|
5
|
+
id: QualityFactorId;
|
|
6
|
+
label: string;
|
|
7
|
+
contribution: number;
|
|
8
|
+
maximum: number;
|
|
9
|
+
detail: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionQualityScore {
|
|
12
|
+
/** Composite score. Beta until weights are calibrated against durable history. */
|
|
13
|
+
score: number;
|
|
14
|
+
beta: true;
|
|
15
|
+
factors: QualityFactorContribution[];
|
|
16
|
+
}
|
|
17
|
+
export interface QualityTrend {
|
|
18
|
+
currentWeekAverage: number | null;
|
|
19
|
+
previousWeekAverage: number | null;
|
|
20
|
+
delta: number | null;
|
|
21
|
+
direction: 'improving' | 'declining' | 'flat' | 'insufficient';
|
|
22
|
+
currentWeekSessions: number;
|
|
23
|
+
previousWeekSessions: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function scoreSessionQuality(metrics: AggregatedMetrics): SessionQualityScore;
|
|
26
|
+
export declare function calculateQualityTrend(records: SessionHistoryRecord[], now?: Date): QualityTrend;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scoreSessionQuality = scoreSessionQuality;
|
|
4
|
+
exports.calculateQualityTrend = calculateQualityTrend;
|
|
5
|
+
function awarded(maximum, ratio) {
|
|
6
|
+
return Math.round(maximum * Math.max(0, Math.min(1, ratio)) * 10) / 10;
|
|
7
|
+
}
|
|
8
|
+
function scoreSessionQuality(metrics) {
|
|
9
|
+
const latencyMs = metrics.latencyStats?.avgFirstTokenLatencyMs ?? 0;
|
|
10
|
+
const latencyRatio = latencyMs > 0 ? 1 - Math.min(1, latencyMs / 15_000) : 1;
|
|
11
|
+
const completedCalls = metrics.toolStats.reduce((sum, tool) => sum + tool.completedCount, 0);
|
|
12
|
+
const errorRate = completedCalls > 0 ? metrics.errorRollup.totalFailures / completedCalls : 0;
|
|
13
|
+
const contextTokens = Math.max(1, metrics.tokens.inputTokens + metrics.tokens.cacheReadTokens + metrics.tokens.cacheWriteTokens);
|
|
14
|
+
const evicted = metrics.compactionEvents.reduce((sum, event) => sum + event.tokensReclaimed, 0);
|
|
15
|
+
const contextRatio = 1 - Math.min(1, evicted / contextTokens);
|
|
16
|
+
const start = metrics.sessionStartTime ? Date.parse(metrics.sessionStartTime) : NaN;
|
|
17
|
+
const end = metrics.lastEventTime ? Date.parse(metrics.lastEventTime) : NaN;
|
|
18
|
+
let bypassMs = 0;
|
|
19
|
+
for (let index = 0; index < metrics.permissionModeHistory.length; index++) {
|
|
20
|
+
const change = metrics.permissionModeHistory[index];
|
|
21
|
+
if (change.mode !== 'bypassPermissions')
|
|
22
|
+
continue;
|
|
23
|
+
const from = Date.parse(change.timestamp);
|
|
24
|
+
const to = Date.parse(metrics.permissionModeHistory[index + 1]?.timestamp ?? metrics.lastEventTime ?? '');
|
|
25
|
+
if (Number.isFinite(from) && Number.isFinite(to))
|
|
26
|
+
bypassMs += Math.max(0, to - from);
|
|
27
|
+
}
|
|
28
|
+
const sessionMs = Number.isFinite(start) && Number.isFinite(end) ? Math.max(1, end - start) : 1;
|
|
29
|
+
const bypassRatio = Math.min(1, bypassMs / sessionMs);
|
|
30
|
+
const tasks = Array.from(metrics.taskState.tasks.values()).filter((task) => task.status !== 'deleted');
|
|
31
|
+
const completedTasks = tasks.filter((task) => task.status === 'completed').length;
|
|
32
|
+
const goalGates = tasks.filter((task) => task.isGoalGate);
|
|
33
|
+
const completedGates = goalGates.filter((task) => task.status === 'completed').length;
|
|
34
|
+
const taskRatio = tasks.length > 0 ? completedTasks / tasks.length : 1;
|
|
35
|
+
const goalRatio = goalGates.length > 0 ? completedGates / goalGates.length : taskRatio;
|
|
36
|
+
const outcomeRatio = taskRatio * 0.6 + goalRatio * 0.4;
|
|
37
|
+
const factors = [
|
|
38
|
+
{
|
|
39
|
+
id: 'latency',
|
|
40
|
+
label: 'Response latency',
|
|
41
|
+
contribution: awarded(15, latencyRatio),
|
|
42
|
+
maximum: 15,
|
|
43
|
+
detail: latencyMs > 0 ? `${Math.round(latencyMs)}ms average first token` : 'No latency penalty',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'reliability',
|
|
47
|
+
label: 'Tool reliability',
|
|
48
|
+
contribution: awarded(30, 1 - Math.min(1, errorRate * 2)),
|
|
49
|
+
maximum: 30,
|
|
50
|
+
detail: `${metrics.errorRollup.totalFailures}/${completedCalls} completed tool calls failed`,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'context',
|
|
54
|
+
label: 'Context stability',
|
|
55
|
+
contribution: awarded(15, contextRatio),
|
|
56
|
+
maximum: 15,
|
|
57
|
+
detail: `${metrics.compactionCount} compactions, ${evicted} tokens evicted`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'permission_safety',
|
|
61
|
+
label: 'Permission safety',
|
|
62
|
+
contribution: awarded(10, 1 - bypassRatio),
|
|
63
|
+
maximum: 10,
|
|
64
|
+
detail: `${Math.round(bypassRatio * 100)}% of observed time bypassing permissions`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'outcomes',
|
|
68
|
+
label: 'Task outcomes',
|
|
69
|
+
contribution: awarded(30, outcomeRatio),
|
|
70
|
+
maximum: 30,
|
|
71
|
+
detail: `${completedTasks}/${tasks.length} tasks and ${completedGates}/${goalGates.length} goal gates completed`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
return {
|
|
75
|
+
score: Math.round(factors.reduce((sum, factor) => sum + factor.contribution, 0)),
|
|
76
|
+
beta: true,
|
|
77
|
+
factors,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function calculateQualityTrend(records, now = new Date()) {
|
|
81
|
+
const end = now.getTime();
|
|
82
|
+
const week = 7 * 24 * 60 * 60 * 1000;
|
|
83
|
+
const eligible = records.filter((record) => record.qualityScore > 0 && Number.isFinite(Date.parse(record.endTime)));
|
|
84
|
+
const current = eligible.filter((record) => {
|
|
85
|
+
const at = Date.parse(record.endTime);
|
|
86
|
+
return at >= end - week && at <= end;
|
|
87
|
+
});
|
|
88
|
+
const previous = eligible.filter((record) => {
|
|
89
|
+
const at = Date.parse(record.endTime);
|
|
90
|
+
return at >= end - 2 * week && at < end - week;
|
|
91
|
+
});
|
|
92
|
+
const average = (items) => items.length > 0
|
|
93
|
+
? Math.round((items.reduce((sum, item) => sum + item.qualityScore, 0) / items.length) * 10) /
|
|
94
|
+
10
|
|
95
|
+
: null;
|
|
96
|
+
const currentWeekAverage = average(current);
|
|
97
|
+
const previousWeekAverage = average(previous);
|
|
98
|
+
const delta = currentWeekAverage != null && previousWeekAverage != null
|
|
99
|
+
? Math.round((currentWeekAverage - previousWeekAverage) * 10) / 10
|
|
100
|
+
: null;
|
|
101
|
+
return {
|
|
102
|
+
currentWeekAverage,
|
|
103
|
+
previousWeekAverage,
|
|
104
|
+
delta,
|
|
105
|
+
direction: delta == null
|
|
106
|
+
? 'insufficient'
|
|
107
|
+
: delta > 0.5
|
|
108
|
+
? 'improving'
|
|
109
|
+
: delta < -0.5
|
|
110
|
+
? 'declining'
|
|
111
|
+
: 'flat',
|
|
112
|
+
currentWeekSessions: current.length,
|
|
113
|
+
previousWeekSessions: previous.length,
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/autoSwitch.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface AutoSwitchControllerOptions {
|
|
|
42
42
|
cooldownMs?: number;
|
|
43
43
|
now?: () => number;
|
|
44
44
|
}
|
|
45
|
-
export declare function decideAutoSwitch(_provider: AccountProviderId, active: AutoSwitchActiveAccount, candidates: AutoSwitchCandidate[], config?: AutoSwitchConfig): AutoSwitchDecision | null;
|
|
45
|
+
export declare function decideAutoSwitch(_provider: AccountProviderId, active: AutoSwitchActiveAccount, candidates: AutoSwitchCandidate[], config?: AutoSwitchConfig, nowMs?: number): AutoSwitchDecision | null;
|
|
46
46
|
export declare class AutoSwitchController implements Disposable {
|
|
47
47
|
private readonly quotaService;
|
|
48
48
|
private readonly getClaudeAccounts;
|
package/dist/autoSwitch.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.DEFAULT_AUTO_SWITCH_CONFIG = {
|
|
|
11
11
|
thresholdPct: 90,
|
|
12
12
|
};
|
|
13
13
|
const MATERIAL_REMAINING_IMPROVEMENT_PCT = 5;
|
|
14
|
+
const MAX_STALE_SNAPSHOT_AGE_MS = 5 * 60 * 60 * 1000;
|
|
14
15
|
function mergedConfig(config) {
|
|
15
16
|
return {
|
|
16
17
|
...exports.DEFAULT_AUTO_SWITCH_CONFIG,
|
|
@@ -27,7 +28,17 @@ function quotaRemaining(quota) {
|
|
|
27
28
|
return 0;
|
|
28
29
|
return Math.max(0, 100 - quotaUtilization(quota));
|
|
29
30
|
}
|
|
30
|
-
function
|
|
31
|
+
function isUsableCandidateQuota(quota, nowMs) {
|
|
32
|
+
if (!quota?.available)
|
|
33
|
+
return false;
|
|
34
|
+
if (quota.stale !== true)
|
|
35
|
+
return true;
|
|
36
|
+
const capturedAt = Date.parse(quota.capturedAt ?? '');
|
|
37
|
+
if (!Number.isFinite(capturedAt) || capturedAt > nowMs)
|
|
38
|
+
return false;
|
|
39
|
+
return nowMs - capturedAt <= MAX_STALE_SNAPSHOT_AGE_MS;
|
|
40
|
+
}
|
|
41
|
+
function decideAutoSwitch(_provider, active, candidates, config, nowMs = Date.now()) {
|
|
31
42
|
const cfg = mergedConfig(config);
|
|
32
43
|
if (!cfg.enabled)
|
|
33
44
|
return null;
|
|
@@ -40,7 +51,7 @@ function decideAutoSwitch(_provider, active, candidates, config) {
|
|
|
40
51
|
const best = candidates
|
|
41
52
|
.filter((candidate) => candidate.accountId !== active.accountId &&
|
|
42
53
|
candidate.switchable !== false &&
|
|
43
|
-
candidate.quota
|
|
54
|
+
isUsableCandidateQuota(candidate.quota, nowMs))
|
|
44
55
|
.sort((a, b) => quotaRemaining(b.quota) - quotaRemaining(a.quota))[0];
|
|
45
56
|
if (!best)
|
|
46
57
|
return null;
|
|
@@ -129,7 +140,7 @@ class AutoSwitchController {
|
|
|
129
140
|
quota: accountId === activeAccountId ? activeQuota : this.readSnapshot(providerId, accountId),
|
|
130
141
|
};
|
|
131
142
|
});
|
|
132
|
-
const decision = decideAutoSwitch(providerId, { accountId: activeAccountId, quota: activeQuota }, candidates, this.config);
|
|
143
|
+
const decision = decideAutoSwitch(providerId, { accountId: activeAccountId, quota: activeQuota }, candidates, this.config, this.now());
|
|
133
144
|
if (!decision)
|
|
134
145
|
return;
|
|
135
146
|
if (this.switchedDuringCrossing.has(providerId))
|
package/dist/codexProfiles.js
CHANGED
|
@@ -382,6 +382,7 @@ function resolveActiveCodexAccount() {
|
|
|
382
382
|
email: identity.email ?? match?.email ?? match?.metadata?.email,
|
|
383
383
|
label: match?.label,
|
|
384
384
|
providerAccountId: match?.providerAccountId ?? identity.workspaceId,
|
|
385
|
+
registryAccountId: match?.id,
|
|
385
386
|
source: 'live',
|
|
386
387
|
};
|
|
387
388
|
}
|
|
@@ -392,6 +393,7 @@ function resolveActiveCodexAccount() {
|
|
|
392
393
|
email: active.email ?? active.metadata?.email,
|
|
393
394
|
label: active.label,
|
|
394
395
|
providerAccountId: active.providerAccountId,
|
|
396
|
+
registryAccountId: active.id,
|
|
395
397
|
source: 'registry',
|
|
396
398
|
};
|
|
397
399
|
}
|
package/dist/codexQuota.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface CodexQuotaApiOptions {
|
|
|
32
32
|
usageUrl?: string;
|
|
33
33
|
resetCreditsUrl?: string;
|
|
34
34
|
capturedAt?: string;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
timeoutMs?: number;
|
|
35
37
|
}
|
|
36
38
|
export declare function quotaFromCodexRateLimits(rateLimits: CodexRateLimits | null | undefined, source?: 'api' | 'session' | 'cache', capturedAt?: string): QuotaState | null;
|
|
37
39
|
export declare function readLatestCodexQuotaFromRollouts(sessionPaths: string[], options?: {
|
package/dist/codexQuota.js
CHANGED
|
@@ -414,10 +414,19 @@ async function fetchCodexQuotaFromApi(options = {}) {
|
|
|
414
414
|
sevenDayLabel: 'Secondary',
|
|
415
415
|
};
|
|
416
416
|
}
|
|
417
|
+
const controller = new AbortController();
|
|
418
|
+
const onAbort = () => controller.abort();
|
|
419
|
+
if (options.signal?.aborted)
|
|
420
|
+
controller.abort();
|
|
421
|
+
else
|
|
422
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
423
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 10_000);
|
|
424
|
+
timeout.unref();
|
|
417
425
|
try {
|
|
418
426
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
419
427
|
const response = await fetchImpl(options.usageUrl ?? CHATGPT_USAGE_URL, {
|
|
420
428
|
method: 'GET',
|
|
429
|
+
signal: controller.signal,
|
|
421
430
|
headers: {
|
|
422
431
|
Authorization: `Bearer ${accessToken}`,
|
|
423
432
|
Accept: 'application/json',
|
|
@@ -468,6 +477,7 @@ async function fetchCodexQuotaFromApi(options = {}) {
|
|
|
468
477
|
accessToken,
|
|
469
478
|
accountId: auth?.accountId,
|
|
470
479
|
capturedAt,
|
|
480
|
+
signal: controller.signal,
|
|
471
481
|
});
|
|
472
482
|
if (resetCredits) {
|
|
473
483
|
quota.resetCredits = resetCredits;
|
|
@@ -488,6 +498,10 @@ async function fetchCodexQuotaFromApi(options = {}) {
|
|
|
488
498
|
sevenDayLabel: 'Secondary',
|
|
489
499
|
};
|
|
490
500
|
}
|
|
501
|
+
finally {
|
|
502
|
+
clearTimeout(timeout);
|
|
503
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
504
|
+
}
|
|
491
505
|
}
|
|
492
506
|
async function fetchCodexResetCreditsFromApi(options = {}) {
|
|
493
507
|
const capturedAt = options.capturedAt ?? new Date().toISOString();
|
|
@@ -497,6 +511,14 @@ async function fetchCodexResetCreditsFromApi(options = {}) {
|
|
|
497
511
|
const accessToken = auth?.accessToken;
|
|
498
512
|
if (!accessToken)
|
|
499
513
|
return undefined;
|
|
514
|
+
const controller = new AbortController();
|
|
515
|
+
const onAbort = () => controller.abort();
|
|
516
|
+
if (options.signal?.aborted)
|
|
517
|
+
controller.abort();
|
|
518
|
+
else
|
|
519
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
520
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 10_000);
|
|
521
|
+
timeout.unref();
|
|
500
522
|
try {
|
|
501
523
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
502
524
|
const headers = {
|
|
@@ -511,6 +533,7 @@ async function fetchCodexResetCreditsFromApi(options = {}) {
|
|
|
511
533
|
const response = await fetchImpl(options.resetCreditsUrl ?? CHATGPT_RESET_CREDITS_URL, {
|
|
512
534
|
method: 'GET',
|
|
513
535
|
headers,
|
|
536
|
+
signal: controller.signal,
|
|
514
537
|
});
|
|
515
538
|
if (!response.ok)
|
|
516
539
|
return undefined;
|
|
@@ -519,6 +542,10 @@ async function fetchCodexResetCreditsFromApi(options = {}) {
|
|
|
519
542
|
catch {
|
|
520
543
|
return undefined;
|
|
521
544
|
}
|
|
545
|
+
finally {
|
|
546
|
+
clearTimeout(timeout);
|
|
547
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
548
|
+
}
|
|
522
549
|
}
|
|
523
550
|
function readCodexAuth(codexHome) {
|
|
524
551
|
try {
|
|
@@ -15,6 +15,9 @@ export interface CodexQuotaWatcherOptions {
|
|
|
15
15
|
discoveryPollIntervalMs?: number;
|
|
16
16
|
maxTailBytes?: number;
|
|
17
17
|
maxSessionFiles?: number;
|
|
18
|
+
/** Minimum interval between expensive rollout-tail fallback scans. */
|
|
19
|
+
localScanCacheMs?: number;
|
|
20
|
+
now?: () => number;
|
|
18
21
|
providerFactory?: () => CodexProvider;
|
|
19
22
|
getActiveAccount?: CodexAccountReader;
|
|
20
23
|
readSnapshot?: SnapshotReader;
|
|
@@ -39,6 +42,8 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
39
42
|
private readonly watchFile;
|
|
40
43
|
private readonly maxTailBytes;
|
|
41
44
|
private readonly maxSessionFiles;
|
|
45
|
+
private readonly localScanCacheMs;
|
|
46
|
+
private readonly now;
|
|
42
47
|
private readonly workspaceId;
|
|
43
48
|
private readonly appendHistorySample;
|
|
44
49
|
private readonly listeners;
|
|
@@ -48,6 +53,8 @@ export declare class CodexQuotaWatcher implements Disposable {
|
|
|
48
53
|
private fileWatcher;
|
|
49
54
|
private sessionPath;
|
|
50
55
|
private lastEmissionKey;
|
|
56
|
+
private lastLocalScanAt;
|
|
57
|
+
private lastLocalScanState;
|
|
51
58
|
private running;
|
|
52
59
|
constructor(workspacePath: string, options?: CodexQuotaWatcherOptions);
|
|
53
60
|
start(): void;
|
|
@@ -41,6 +41,7 @@ const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
|
41
41
|
const quotaHistory_1 = require("./quotaHistory");
|
|
42
42
|
const codex_1 = require("./providers/codex");
|
|
43
43
|
const DEFAULT_DISCOVERY_POLL_INTERVAL_MS = 30_000;
|
|
44
|
+
const DEFAULT_LOCAL_SCAN_CACHE_MS = 5 * 60_000;
|
|
44
45
|
function accountEmail(account) {
|
|
45
46
|
return account?.email ?? account?.metadata?.email;
|
|
46
47
|
}
|
|
@@ -81,6 +82,8 @@ class CodexQuotaWatcher {
|
|
|
81
82
|
watchFile;
|
|
82
83
|
maxTailBytes;
|
|
83
84
|
maxSessionFiles;
|
|
85
|
+
localScanCacheMs;
|
|
86
|
+
now;
|
|
84
87
|
workspaceId;
|
|
85
88
|
appendHistorySample;
|
|
86
89
|
listeners = [];
|
|
@@ -90,6 +93,8 @@ class CodexQuotaWatcher {
|
|
|
90
93
|
fileWatcher = null;
|
|
91
94
|
sessionPath = null;
|
|
92
95
|
lastEmissionKey = null;
|
|
96
|
+
lastLocalScanAt = Number.NEGATIVE_INFINITY;
|
|
97
|
+
lastLocalScanState = null;
|
|
93
98
|
running = false;
|
|
94
99
|
constructor(workspacePath, options = {}) {
|
|
95
100
|
this.workspacePath = workspacePath;
|
|
@@ -110,6 +115,8 @@ class CodexQuotaWatcher {
|
|
|
110
115
|
this.watchFile = options.watchFile ?? fs.watch;
|
|
111
116
|
this.maxTailBytes = options.maxTailBytes;
|
|
112
117
|
this.maxSessionFiles = options.maxSessionFiles;
|
|
118
|
+
this.localScanCacheMs = options.localScanCacheMs ?? DEFAULT_LOCAL_SCAN_CACHE_MS;
|
|
119
|
+
this.now = options.now ?? Date.now;
|
|
113
120
|
this.workspaceId = options.workspaceId;
|
|
114
121
|
this.appendHistorySample = options.appendHistorySample ?? quotaHistory_1.appendQuotaHistorySample;
|
|
115
122
|
}
|
|
@@ -255,28 +262,39 @@ class CodexQuotaWatcher {
|
|
|
255
262
|
}
|
|
256
263
|
emitCachedOrUnavailable() {
|
|
257
264
|
const account = this.getActiveAccount();
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
workspacePath: this.workspacePath,
|
|
263
|
-
activeAccount: account,
|
|
264
|
-
readSnapshot: this.readSnapshot,
|
|
265
|
-
writeSnapshot: this.writeSnapshot,
|
|
266
|
-
provider: localProvider,
|
|
267
|
-
maxTailBytes: this.maxTailBytes,
|
|
268
|
-
maxSessionFiles: this.maxSessionFiles,
|
|
269
|
-
});
|
|
270
|
-
if (local) {
|
|
271
|
-
this.emitState(local);
|
|
265
|
+
const scanNow = this.now();
|
|
266
|
+
if (scanNow - this.lastLocalScanAt < this.localScanCacheMs) {
|
|
267
|
+
if (this.lastLocalScanState) {
|
|
268
|
+
this.emitState(this.lastLocalScanState);
|
|
272
269
|
return;
|
|
273
270
|
}
|
|
274
271
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
272
|
+
else {
|
|
273
|
+
let localProvider = null;
|
|
274
|
+
this.lastLocalScanAt = scanNow;
|
|
275
|
+
this.lastLocalScanState = null;
|
|
276
|
+
try {
|
|
277
|
+
localProvider = this.providerFactory();
|
|
278
|
+
this.lastLocalScanState = (0, codexQuota_1.resolveCodexQuotaFromLocalSources)({
|
|
279
|
+
workspacePath: this.workspacePath,
|
|
280
|
+
activeAccount: account,
|
|
281
|
+
readSnapshot: this.readSnapshot,
|
|
282
|
+
writeSnapshot: this.writeSnapshot,
|
|
283
|
+
provider: localProvider,
|
|
284
|
+
maxTailBytes: this.maxTailBytes,
|
|
285
|
+
maxSessionFiles: this.maxSessionFiles,
|
|
286
|
+
});
|
|
287
|
+
if (this.lastLocalScanState) {
|
|
288
|
+
this.emitState(this.lastLocalScanState);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Fall through to account-scoped cache or unavailable state.
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
localProvider?.dispose();
|
|
297
|
+
}
|
|
280
298
|
}
|
|
281
299
|
const cached = account ? this.readSnapshot('codex', account.id) : null;
|
|
282
300
|
if (cached) {
|
|
@@ -5,7 +5,8 @@ import type { PersistedTask } from '../types/taskPersistence';
|
|
|
5
5
|
import type { DecisionEntry } from '../types/decisionLog';
|
|
6
6
|
import type { KnowledgeNote } from '../types/knowledgeNote';
|
|
7
7
|
import type { TokenTotals } from '../types/historicalData';
|
|
8
|
-
import type {
|
|
8
|
+
import type { SessionProviderBase, SessionFileStats, ProviderId } from '../providers/types';
|
|
9
|
+
import type { ProjectIdentity } from '../paths';
|
|
9
10
|
export type Fidelity = 'full' | 'compact' | 'brief';
|
|
10
11
|
export interface ContextResult {
|
|
11
12
|
provider: ProviderId;
|
|
@@ -28,4 +29,4 @@ export interface ContextResult {
|
|
|
28
29
|
} | null;
|
|
29
30
|
sessionSummaries: SessionFileStats[];
|
|
30
31
|
}
|
|
31
|
-
export declare function composeContext(
|
|
32
|
+
export declare function composeContext(project: string | ProjectIdentity, fidelity: Fidelity, provider: SessionProviderBase, workspacePath?: string): Promise<ContextResult>;
|
package/dist/context/composer.js
CHANGED
|
@@ -9,14 +9,14 @@ const decisions_1 = require("../readers/decisions");
|
|
|
9
9
|
const notes_1 = require("../readers/notes");
|
|
10
10
|
const history_1 = require("../readers/history");
|
|
11
11
|
const handoff_1 = require("../readers/handoff");
|
|
12
|
-
async function composeContext(
|
|
12
|
+
async function composeContext(project, fidelity, provider, workspacePath) {
|
|
13
13
|
// Fetch all data in parallel
|
|
14
14
|
const [allTasks, allDecisions, allNotes, history, handoff] = await Promise.all([
|
|
15
|
-
(0, tasks_1.readTasks)(
|
|
16
|
-
(0, decisions_1.readDecisions)(
|
|
17
|
-
(0, notes_1.readNotes)(
|
|
15
|
+
(0, tasks_1.readTasks)(project),
|
|
16
|
+
(0, decisions_1.readDecisions)(project),
|
|
17
|
+
(0, notes_1.readNotes)(project),
|
|
18
18
|
(0, history_1.readHistory)(),
|
|
19
|
-
(0, handoff_1.readLatestHandoff)(
|
|
19
|
+
(0, handoff_1.readLatestHandoff)(project),
|
|
20
20
|
]);
|
|
21
21
|
// Get session summaries
|
|
22
22
|
const sessionFiles = workspacePath ? provider.findAllSessions(workspacePath) : [];
|
|
@@ -57,6 +57,10 @@ function buildSessionContextSnapshot(events, options = {}) {
|
|
|
57
57
|
if (event.rateLimits)
|
|
58
58
|
lastRateLimits = event.rateLimits;
|
|
59
59
|
}
|
|
60
|
+
return assembleSessionContextSnapshot(aggregator, sourceState, lastPermissionMode, lastRateLimits, options);
|
|
61
|
+
}
|
|
62
|
+
function assembleSessionContextSnapshot(aggregator, sourceState, lastPermissionMode, lastRateLimits, options) {
|
|
63
|
+
const providerId = options.providerId;
|
|
60
64
|
const metrics = aggregator.getMetrics();
|
|
61
65
|
const model = options.model ?? metrics.currentModel ?? metrics.modelStats[0]?.model;
|
|
62
66
|
const contextWindow = options.contextWindow ??
|
|
@@ -100,24 +104,46 @@ function buildSessionContextSnapshot(events, options = {}) {
|
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
106
|
function createSessionContextProjector(options = {}) {
|
|
103
|
-
let
|
|
107
|
+
let aggregator = createProjectorAggregator(options);
|
|
108
|
+
let sourceState = createSourceExtractionState(options);
|
|
109
|
+
let lastPermissionMode;
|
|
110
|
+
let lastRateLimits;
|
|
111
|
+
const process = (event) => {
|
|
112
|
+
aggregator.processEvent(event);
|
|
113
|
+
extractSourcesFromEvent(event, sourceState);
|
|
114
|
+
if (event.permissionMode)
|
|
115
|
+
lastPermissionMode = event.permissionMode;
|
|
116
|
+
if (event.rateLimits)
|
|
117
|
+
lastRateLimits = event.rateLimits;
|
|
118
|
+
};
|
|
119
|
+
const snapshot = () => assembleSessionContextSnapshot(aggregator, sourceState, lastPermissionMode, lastRateLimits, options);
|
|
104
120
|
return {
|
|
105
121
|
processEvent(event) {
|
|
106
|
-
|
|
107
|
-
return
|
|
122
|
+
process(event);
|
|
123
|
+
return snapshot();
|
|
108
124
|
},
|
|
109
125
|
processEvents(newEvents) {
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
for (const event of newEvents)
|
|
127
|
+
process(event);
|
|
128
|
+
return snapshot();
|
|
112
129
|
},
|
|
113
130
|
getSnapshot() {
|
|
114
|
-
return
|
|
131
|
+
return snapshot();
|
|
115
132
|
},
|
|
116
133
|
reset() {
|
|
117
|
-
|
|
134
|
+
aggregator = createProjectorAggregator(options);
|
|
135
|
+
sourceState = createSourceExtractionState(options);
|
|
136
|
+
lastPermissionMode = undefined;
|
|
137
|
+
lastRateLimits = undefined;
|
|
118
138
|
},
|
|
119
139
|
};
|
|
120
140
|
}
|
|
141
|
+
function createProjectorAggregator(options) {
|
|
142
|
+
return new EventAggregator_1.EventAggregator({
|
|
143
|
+
providerId: options.providerId,
|
|
144
|
+
computeContextSize: options.computeContextSize,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
121
147
|
function readSessionContextSnapshot(provider, sessionPath, options = {}) {
|
|
122
148
|
const reader = provider.createReader(sessionPath);
|
|
123
149
|
const events = reader.readAll();
|
|
@@ -177,7 +203,7 @@ function extractSourcesFromEvent(event, state) {
|
|
|
177
203
|
layer: 'summary',
|
|
178
204
|
sourceType: 'summary',
|
|
179
205
|
title: 'Context summary',
|
|
180
|
-
text: extractText(event.message
|
|
206
|
+
text: extractText(event.message?.content) || 'Context compacted',
|
|
181
207
|
});
|
|
182
208
|
break;
|
|
183
209
|
}
|
|
@@ -394,7 +420,7 @@ function limitSources(sources, limit) {
|
|
|
394
420
|
}
|
|
395
421
|
function latestModel(events) {
|
|
396
422
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
397
|
-
const model = events[i].message
|
|
423
|
+
const model = events[i].message?.model;
|
|
398
424
|
if (model)
|
|
399
425
|
return model;
|
|
400
426
|
}
|