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,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionMonitor = void 0;
|
|
4
|
+
const EventAggregator_1 = require("./aggregation/EventAggregator");
|
|
5
|
+
/**
|
|
6
|
+
* UI-independent session monitor for library consumers. It owns one provider
|
|
7
|
+
* reader and the canonical aggregator; hosts decide how often to call `poll()`.
|
|
8
|
+
*/
|
|
9
|
+
class SessionMonitor {
|
|
10
|
+
provider;
|
|
11
|
+
aggregator;
|
|
12
|
+
reader = null;
|
|
13
|
+
sessionPath = null;
|
|
14
|
+
listeners = new Set();
|
|
15
|
+
replayOnAttach;
|
|
16
|
+
constructor(provider, options = {}) {
|
|
17
|
+
this.provider = provider;
|
|
18
|
+
this.replayOnAttach = options.replayOnAttach ?? true;
|
|
19
|
+
this.aggregator = new EventAggregator_1.EventAggregator({ ...options, providerId: provider.id });
|
|
20
|
+
}
|
|
21
|
+
attach(sessionPath, replay = this.replayOnAttach) {
|
|
22
|
+
this.aggregator.reset();
|
|
23
|
+
this.reader = this.provider.createReader(sessionPath);
|
|
24
|
+
this.sessionPath = sessionPath;
|
|
25
|
+
if (replay)
|
|
26
|
+
this.consume(this.reader.readAll());
|
|
27
|
+
return this.getMetrics();
|
|
28
|
+
}
|
|
29
|
+
attachLatest(workspacePath, replay = this.replayOnAttach) {
|
|
30
|
+
const sessionPath = this.provider.findActiveSession(workspacePath);
|
|
31
|
+
return sessionPath ? this.attach(sessionPath, replay) : null;
|
|
32
|
+
}
|
|
33
|
+
poll() {
|
|
34
|
+
if (!this.reader)
|
|
35
|
+
return [];
|
|
36
|
+
if (this.reader.wasTruncated())
|
|
37
|
+
this.aggregator.reset();
|
|
38
|
+
const events = this.reader.readNew();
|
|
39
|
+
this.consume(events);
|
|
40
|
+
return events;
|
|
41
|
+
}
|
|
42
|
+
process(event) {
|
|
43
|
+
this.aggregator.processEvent(event);
|
|
44
|
+
for (const listener of this.listeners)
|
|
45
|
+
listener(event);
|
|
46
|
+
}
|
|
47
|
+
onEvent(listener) {
|
|
48
|
+
this.listeners.add(listener);
|
|
49
|
+
return () => this.listeners.delete(listener);
|
|
50
|
+
}
|
|
51
|
+
getMetrics() {
|
|
52
|
+
return this.aggregator.getMetrics();
|
|
53
|
+
}
|
|
54
|
+
getSessionPath() {
|
|
55
|
+
return this.sessionPath;
|
|
56
|
+
}
|
|
57
|
+
reset() {
|
|
58
|
+
this.reader?.reset();
|
|
59
|
+
this.aggregator.reset();
|
|
60
|
+
}
|
|
61
|
+
dispose() {
|
|
62
|
+
this.listeners.clear();
|
|
63
|
+
this.reader = null;
|
|
64
|
+
this.provider.dispose();
|
|
65
|
+
}
|
|
66
|
+
consume(events) {
|
|
67
|
+
for (const event of events)
|
|
68
|
+
this.process(event);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.SessionMonitor = SessionMonitor;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Sliding-window burn-rate calculator shared by every Sidekick surface. */
|
|
2
|
+
export declare class BurnRateCalculator {
|
|
3
|
+
private events;
|
|
4
|
+
private readonly windowMs;
|
|
5
|
+
constructor(windowMinutes?: number);
|
|
6
|
+
addEvent(tokens: number, timestamp?: Date): void;
|
|
7
|
+
calculateBurnRate(now?: Date): number;
|
|
8
|
+
estimateTimeToQuota(currentTokens: number, quotaLimit: number, now?: Date): number | null;
|
|
9
|
+
getEventCount(): number;
|
|
10
|
+
reset(): void;
|
|
11
|
+
private pruneOldEvents;
|
|
12
|
+
}
|
|
13
|
+
/** Pure ETA helper for cached or externally computed burn rates. */
|
|
14
|
+
export declare function estimateTimeToQuota(currentUsage: number, quotaLimit: number, burnRatePerMinute: number): number | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BurnRateCalculator = void 0;
|
|
4
|
+
exports.estimateTimeToQuota = estimateTimeToQuota;
|
|
5
|
+
/** Sliding-window burn-rate calculator shared by every Sidekick surface. */
|
|
6
|
+
class BurnRateCalculator {
|
|
7
|
+
events = [];
|
|
8
|
+
windowMs;
|
|
9
|
+
constructor(windowMinutes = 5) {
|
|
10
|
+
this.windowMs = windowMinutes * 60_000;
|
|
11
|
+
}
|
|
12
|
+
addEvent(tokens, timestamp = new Date()) {
|
|
13
|
+
this.events.push({ tokens, timestamp });
|
|
14
|
+
this.pruneOldEvents(timestamp);
|
|
15
|
+
}
|
|
16
|
+
calculateBurnRate(now = new Date()) {
|
|
17
|
+
this.pruneOldEvents(now);
|
|
18
|
+
if (this.events.length === 0)
|
|
19
|
+
return 0;
|
|
20
|
+
const totalTokens = this.events.reduce((sum, event) => sum + event.tokens, 0);
|
|
21
|
+
const elapsedMinutes = Math.max((now.getTime() - this.events[0].timestamp.getTime()) / 60_000, 1);
|
|
22
|
+
return totalTokens / elapsedMinutes;
|
|
23
|
+
}
|
|
24
|
+
estimateTimeToQuota(currentTokens, quotaLimit, now = new Date()) {
|
|
25
|
+
return estimateTimeToQuota(currentTokens, quotaLimit, this.calculateBurnRate(now));
|
|
26
|
+
}
|
|
27
|
+
getEventCount() {
|
|
28
|
+
return this.events.length;
|
|
29
|
+
}
|
|
30
|
+
reset() {
|
|
31
|
+
this.events = [];
|
|
32
|
+
}
|
|
33
|
+
pruneOldEvents(now) {
|
|
34
|
+
const cutoff = now.getTime() - this.windowMs;
|
|
35
|
+
this.events = this.events.filter((event) => event.timestamp.getTime() >= cutoff);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.BurnRateCalculator = BurnRateCalculator;
|
|
39
|
+
/** Pure ETA helper for cached or externally computed burn rates. */
|
|
40
|
+
function estimateTimeToQuota(currentUsage, quotaLimit, burnRatePerMinute) {
|
|
41
|
+
if (burnRatePerMinute <= 0)
|
|
42
|
+
return null;
|
|
43
|
+
const remaining = quotaLimit - currentUsage;
|
|
44
|
+
return remaining <= 0 ? 0 : remaining / burnRatePerMinute;
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ActiveAccountStatus } from '../accountStatus';
|
|
2
|
+
import type { QuotaState } from '../quota';
|
|
3
|
+
export interface StatuslineInput {
|
|
4
|
+
accounts: ActiveAccountStatus;
|
|
5
|
+
claudeQuota?: QuotaState | null;
|
|
6
|
+
codexQuota?: QuotaState | null;
|
|
7
|
+
now?: Date;
|
|
8
|
+
}
|
|
9
|
+
export interface StatuslineSelection {
|
|
10
|
+
providerId: 'claude-code' | 'codex';
|
|
11
|
+
accountId?: string;
|
|
12
|
+
accountLabel: string;
|
|
13
|
+
quota: QuotaState | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function selectStatuslineAccount(input: StatuslineInput): StatuslineSelection | null;
|
|
16
|
+
export declare function formatStatusline(input: StatuslineInput): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectStatuslineAccount = selectStatuslineAccount;
|
|
4
|
+
exports.formatStatusline = formatStatusline;
|
|
5
|
+
const quota_1 = require("../quota");
|
|
6
|
+
const BurnRateCalculator_1 = require("./BurnRateCalculator");
|
|
7
|
+
function selectStatuslineAccount(input) {
|
|
8
|
+
if (input.accounts.claude.present) {
|
|
9
|
+
return {
|
|
10
|
+
providerId: 'claude-code',
|
|
11
|
+
accountId: input.accounts.claude.accountId,
|
|
12
|
+
accountLabel: input.accounts.claude.label ?? input.accounts.claude.email ?? 'claude',
|
|
13
|
+
quota: input.claudeQuota ?? null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (input.accounts.codex.present) {
|
|
17
|
+
return {
|
|
18
|
+
providerId: 'codex',
|
|
19
|
+
accountId: input.accounts.codex.accountId,
|
|
20
|
+
accountLabel: input.accounts.codex.label ?? input.accounts.codex.email ?? 'codex',
|
|
21
|
+
quota: input.codexQuota ?? null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function formatStatusline(input) {
|
|
27
|
+
const selected = selectStatuslineAccount(input);
|
|
28
|
+
if (!selected)
|
|
29
|
+
return 'acct:none · quota unavailable';
|
|
30
|
+
const account = compactLabel(selected.accountLabel);
|
|
31
|
+
const quota = selected.quota;
|
|
32
|
+
if (!quota?.available)
|
|
33
|
+
return `acct:${account} · quota unavailable`;
|
|
34
|
+
const now = input.now ?? new Date();
|
|
35
|
+
const utilization = clampPercent(quota.fiveHour.utilization);
|
|
36
|
+
const reset = formatReset(quota.fiveHour.resetsAt, now);
|
|
37
|
+
const eta = estimateWindowEta(quota, now);
|
|
38
|
+
const etaText = eta == null ? '' : ` · ~${formatMinutes(eta)} left`;
|
|
39
|
+
return `acct:${account} · 5h ${Math.round(utilization)}% resets ${reset}${etaText}`;
|
|
40
|
+
}
|
|
41
|
+
function estimateWindowEta(quota, now) {
|
|
42
|
+
const resetAt = Date.parse(quota.fiveHour.resetsAt);
|
|
43
|
+
if (!Number.isFinite(resetAt))
|
|
44
|
+
return null;
|
|
45
|
+
const nowMs = now.getTime();
|
|
46
|
+
if (resetAt <= nowMs)
|
|
47
|
+
return null;
|
|
48
|
+
const capturedAt = quota.capturedAt ? Date.parse(quota.capturedAt) : now.getTime();
|
|
49
|
+
const sampleAt = Number.isFinite(capturedAt) ? capturedAt : now.getTime();
|
|
50
|
+
if (sampleAt >= resetAt)
|
|
51
|
+
return null;
|
|
52
|
+
const elapsedMinutes = (sampleAt - (resetAt - quota_1.FIVE_HOUR_WINDOW_MS)) / 60_000;
|
|
53
|
+
if (elapsedMinutes <= 0 || quota.fiveHour.utilization <= 0)
|
|
54
|
+
return null;
|
|
55
|
+
const burnRate = quota.fiveHour.utilization / elapsedMinutes;
|
|
56
|
+
const eta = (0, BurnRateCalculator_1.estimateTimeToQuota)(quota.fiveHour.utilization, 100, burnRate);
|
|
57
|
+
const minutesUntilReset = (resetAt - nowMs) / 60_000;
|
|
58
|
+
return eta !== null && eta < minutesUntilReset ? eta : null;
|
|
59
|
+
}
|
|
60
|
+
function compactLabel(value) {
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
const localPart = trimmed.includes('@') ? trimmed.split('@')[0] : trimmed;
|
|
63
|
+
return localPart.replace(/\s+/g, '-').slice(0, 24) || 'unknown';
|
|
64
|
+
}
|
|
65
|
+
function clampPercent(value) {
|
|
66
|
+
return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;
|
|
67
|
+
}
|
|
68
|
+
function formatReset(value, fallback) {
|
|
69
|
+
const reset = new Date(value);
|
|
70
|
+
const date = Number.isNaN(reset.getTime()) ? fallback : reset;
|
|
71
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
72
|
+
hour: '2-digit',
|
|
73
|
+
minute: '2-digit',
|
|
74
|
+
hour12: false,
|
|
75
|
+
}).format(date);
|
|
76
|
+
}
|
|
77
|
+
function formatMinutes(value) {
|
|
78
|
+
const minutes = Math.max(0, Math.round(value));
|
|
79
|
+
if (minutes < 60)
|
|
80
|
+
return `${minutes}min`;
|
|
81
|
+
const hours = Math.floor(minutes / 60);
|
|
82
|
+
const remainder = minutes % 60;
|
|
83
|
+
return remainder ? `${hours}h${remainder}m` : `${hours}h`;
|
|
84
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { getActiveAccountStatus } from '../accountStatus';
|
|
2
|
+
export type { ActiveAccountStatus, ActiveProviderAccountStatus } from '../accountStatus';
|
|
3
|
+
export { readQuotaSnapshot } from '../quotaSnapshots';
|
|
4
|
+
export type { QuotaSnapshotProviderId } from '../quotaSnapshots';
|
|
5
|
+
export { BurnRateCalculator, estimateTimeToQuota } from './BurnRateCalculator';
|
|
6
|
+
export { formatStatusline, selectStatuslineAccount } from './formatter';
|
|
7
|
+
export type { StatuslineInput, StatuslineSelection } from './formatter';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.readQuotaSnapshot = exports.getActiveAccountStatus = void 0;
|
|
4
|
+
var accountStatus_1 = require("../accountStatus");
|
|
5
|
+
Object.defineProperty(exports, "getActiveAccountStatus", { enumerable: true, get: function () { return accountStatus_1.getActiveAccountStatus; } });
|
|
6
|
+
var quotaSnapshots_1 = require("../quotaSnapshots");
|
|
7
|
+
Object.defineProperty(exports, "readQuotaSnapshot", { enumerable: true, get: function () { return quotaSnapshots_1.readQuotaSnapshot; } });
|
|
8
|
+
var BurnRateCalculator_1 = require("./BurnRateCalculator");
|
|
9
|
+
Object.defineProperty(exports, "BurnRateCalculator", { enumerable: true, get: function () { return BurnRateCalculator_1.BurnRateCalculator; } });
|
|
10
|
+
Object.defineProperty(exports, "estimateTimeToQuota", { enumerable: true, get: function () { return BurnRateCalculator_1.estimateTimeToQuota; } });
|
|
11
|
+
var formatter_1 = require("./formatter");
|
|
12
|
+
Object.defineProperty(exports, "formatStatusline", { enumerable: true, get: function () { return formatter_1.formatStatusline; } });
|
|
13
|
+
Object.defineProperty(exports, "selectStatuslineAccount", { enumerable: true, get: function () { return formatter_1.selectStatuslineAccount; } });
|
package/dist/terminalSync.js
CHANGED
|
@@ -107,20 +107,28 @@ function buildShellHookBlock() {
|
|
|
107
107
|
function getShellRcPaths() {
|
|
108
108
|
const zshrc = path.join(os.homedir(), '.zshrc');
|
|
109
109
|
const bashrc = path.join(os.homedir(), '.bashrc');
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const existing = [zshrc, bashrc].filter((filePath) => fs.existsSync(filePath));
|
|
111
|
+
if (existing.length > 0)
|
|
112
|
+
return existing;
|
|
113
|
+
const shell = path.basename(process.env.SHELL ?? 'zsh');
|
|
114
|
+
return shell === 'bash' ? [bashrc] : [zshrc];
|
|
115
|
+
}
|
|
116
|
+
function resolveShellRcTarget(filePath) {
|
|
117
|
+
if (!fs.existsSync(filePath))
|
|
118
|
+
return { filePath, mode: 0o600 };
|
|
119
|
+
const realPath = fs.realpathSync(filePath);
|
|
120
|
+
return { filePath: realPath, mode: fs.statSync(realPath).mode & 0o777 };
|
|
114
121
|
}
|
|
115
122
|
function stripShellHook(content) {
|
|
116
123
|
const pattern = new RegExp(`${HOOK_START}[\\s\\S]*?${HOOK_END}\\n?`, 'g');
|
|
117
124
|
return content.replace(pattern, '').replace(/\n{3,}/g, '\n\n');
|
|
118
125
|
}
|
|
119
126
|
function installHookInFile(filePath) {
|
|
120
|
-
const
|
|
127
|
+
const target = resolveShellRcTarget(filePath);
|
|
128
|
+
const existing = fs.existsSync(target.filePath) ? fs.readFileSync(target.filePath, 'utf8') : '';
|
|
121
129
|
const cleaned = stripShellHook(existing).replace(/\s*$/, '');
|
|
122
130
|
const next = cleaned ? `${cleaned}\n\n${buildShellHookBlock()}` : buildShellHookBlock();
|
|
123
|
-
atomicWriteFile(filePath, next,
|
|
131
|
+
atomicWriteFile(target.filePath, next, target.mode);
|
|
124
132
|
}
|
|
125
133
|
function installShellHook() {
|
|
126
134
|
for (const rcPath of getShellRcPaths()) {
|
|
@@ -130,7 +138,8 @@ function installShellHook() {
|
|
|
130
138
|
function uninstallHookInFile(filePath) {
|
|
131
139
|
if (!fs.existsSync(filePath))
|
|
132
140
|
return;
|
|
133
|
-
|
|
141
|
+
const target = resolveShellRcTarget(filePath);
|
|
142
|
+
atomicWriteFile(target.filePath, stripShellHook(fs.readFileSync(target.filePath, 'utf8')), target.mode);
|
|
134
143
|
}
|
|
135
144
|
function uninstallShellHook() {
|
|
136
145
|
for (const rcPath of getShellRcPaths()) {
|
package/dist/types/codex.d.ts
CHANGED
|
@@ -124,6 +124,8 @@ export interface CodexCustomToolCallOutputItem {
|
|
|
124
124
|
/** Context compaction marker. */
|
|
125
125
|
export interface CodexCompacted {
|
|
126
126
|
summary?: string;
|
|
127
|
+
tokens_before?: number;
|
|
128
|
+
tokens_after?: number;
|
|
127
129
|
}
|
|
128
130
|
/** Turn context providing model and policy info. */
|
|
129
131
|
export interface CodexTurnContext {
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
* $0 because pricing was unknown — surfaced in the dashboard footer.
|
|
11
11
|
* Legacy records (schemaVersion 1 or missing) have neither field and should
|
|
12
12
|
* be treated as fully priced.
|
|
13
|
+
*
|
|
14
|
+
* Schema version 3 (2026-07-18) adds a capped per-session record list with
|
|
15
|
+
* provider/project dimensions, beta quality factors, churn, and impact cost.
|
|
13
16
|
*/
|
|
14
|
-
export declare const HISTORICAL_DATA_SCHEMA_VERSION =
|
|
17
|
+
export declare const HISTORICAL_DATA_SCHEMA_VERSION = 3;
|
|
18
|
+
export declare const HISTORICAL_SESSION_RETENTION_LIMIT = 500;
|
|
15
19
|
export interface TokenTotals {
|
|
16
20
|
inputTokens: number;
|
|
17
21
|
outputTokens: number;
|
|
@@ -47,6 +51,13 @@ export interface DailyData {
|
|
|
47
51
|
toolUsage: ToolUsageRecord[];
|
|
48
52
|
updatedAt: string;
|
|
49
53
|
}
|
|
54
|
+
export interface HourlyData {
|
|
55
|
+
hour: number;
|
|
56
|
+
tokens: TokenTotals;
|
|
57
|
+
totalCost: number;
|
|
58
|
+
messageCount: number;
|
|
59
|
+
sessionCount: number;
|
|
60
|
+
}
|
|
50
61
|
export interface MonthlyData {
|
|
51
62
|
month: string;
|
|
52
63
|
tokens: TokenTotals;
|
|
@@ -71,12 +82,41 @@ export interface AllTimeStats {
|
|
|
71
82
|
export interface HistoricalDataStore {
|
|
72
83
|
schemaVersion: number;
|
|
73
84
|
daily: Record<string, DailyData>;
|
|
85
|
+
/** Hourly buckets keyed by local YYYY-MM-DD. */
|
|
86
|
+
hourly?: Record<string, HourlyData[]>;
|
|
74
87
|
monthly: Record<string, MonthlyData>;
|
|
75
88
|
allTime: AllTimeStats;
|
|
76
89
|
lastSaved: string;
|
|
90
|
+
/** Capped durable per-session records, newest last (schema v3). */
|
|
91
|
+
sessions?: SessionHistoryRecord[];
|
|
77
92
|
importedFiles?: string[];
|
|
78
93
|
lastImportTimestamp?: string;
|
|
79
94
|
}
|
|
95
|
+
export interface SessionHistoryRecord {
|
|
96
|
+
sessionId: string;
|
|
97
|
+
provider: string;
|
|
98
|
+
project: string;
|
|
99
|
+
startTime: string;
|
|
100
|
+
endTime: string;
|
|
101
|
+
tokens: TokenTotals;
|
|
102
|
+
totalCost: number;
|
|
103
|
+
messageCount: number;
|
|
104
|
+
/** Schema v3 additive fields used to make aggregate replacement idempotent. */
|
|
105
|
+
modelUsage?: ModelUsageRecord[];
|
|
106
|
+
toolUsage?: ToolUsageRecord[];
|
|
107
|
+
unpricedModelIds?: string[];
|
|
108
|
+
qualityScore: number;
|
|
109
|
+
qualityFactors: Array<{
|
|
110
|
+
id: string;
|
|
111
|
+
label: string;
|
|
112
|
+
contribution: number;
|
|
113
|
+
maximum: number;
|
|
114
|
+
detail: string;
|
|
115
|
+
}>;
|
|
116
|
+
additions: number;
|
|
117
|
+
deletions: number;
|
|
118
|
+
costPerChangedLine: number | null;
|
|
119
|
+
}
|
|
80
120
|
export interface SessionSummary {
|
|
81
121
|
sessionId: string;
|
|
82
122
|
startTime: string;
|
|
@@ -96,5 +136,13 @@ export interface SessionSummary {
|
|
|
96
136
|
* Omitted on v1 records or when every model was priced.
|
|
97
137
|
*/
|
|
98
138
|
unpricedModelIds?: string[];
|
|
139
|
+
provider?: string;
|
|
140
|
+
project?: string;
|
|
141
|
+
qualityScore?: number;
|
|
142
|
+
qualityFactors?: SessionHistoryRecord['qualityFactors'];
|
|
143
|
+
additions?: number;
|
|
144
|
+
deletions?: number;
|
|
145
|
+
costPerChangedLine?: number | null;
|
|
99
146
|
}
|
|
100
147
|
export declare function createEmptyTokenTotals(): TokenTotals;
|
|
148
|
+
export declare function createEmptyDataStore(): HistoricalDataStore;
|
|
@@ -11,11 +11,16 @@
|
|
|
11
11
|
* $0 because pricing was unknown — surfaced in the dashboard footer.
|
|
12
12
|
* Legacy records (schemaVersion 1 or missing) have neither field and should
|
|
13
13
|
* be treated as fully priced.
|
|
14
|
+
*
|
|
15
|
+
* Schema version 3 (2026-07-18) adds a capped per-session record list with
|
|
16
|
+
* provider/project dimensions, beta quality factors, churn, and impact cost.
|
|
14
17
|
*/
|
|
15
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.HISTORICAL_DATA_SCHEMA_VERSION = void 0;
|
|
19
|
+
exports.HISTORICAL_SESSION_RETENTION_LIMIT = exports.HISTORICAL_DATA_SCHEMA_VERSION = void 0;
|
|
17
20
|
exports.createEmptyTokenTotals = createEmptyTokenTotals;
|
|
18
|
-
exports.
|
|
21
|
+
exports.createEmptyDataStore = createEmptyDataStore;
|
|
22
|
+
exports.HISTORICAL_DATA_SCHEMA_VERSION = 3;
|
|
23
|
+
exports.HISTORICAL_SESSION_RETENTION_LIMIT = 500;
|
|
19
24
|
function createEmptyTokenTotals() {
|
|
20
25
|
return {
|
|
21
26
|
inputTokens: 0,
|
|
@@ -24,3 +29,24 @@ function createEmptyTokenTotals() {
|
|
|
24
29
|
cacheReadTokens: 0,
|
|
25
30
|
};
|
|
26
31
|
}
|
|
32
|
+
function createEmptyDataStore() {
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
return {
|
|
35
|
+
schemaVersion: exports.HISTORICAL_DATA_SCHEMA_VERSION,
|
|
36
|
+
daily: {},
|
|
37
|
+
monthly: {},
|
|
38
|
+
sessions: [],
|
|
39
|
+
allTime: {
|
|
40
|
+
tokens: createEmptyTokenTotals(),
|
|
41
|
+
totalCost: 0,
|
|
42
|
+
messageCount: 0,
|
|
43
|
+
sessionCount: 0,
|
|
44
|
+
firstDate: '',
|
|
45
|
+
lastDate: '',
|
|
46
|
+
modelUsage: [],
|
|
47
|
+
toolUsage: [],
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
},
|
|
50
|
+
lastSaved: now,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { SessionEvent } from './sessionEvent';
|
|
2
|
+
import type { ProviderId, SessionProviderBase } from '../providers/types';
|
|
3
|
+
export type ObservationProvenanceV1 = 'reported' | 'estimated' | 'inferred';
|
|
4
|
+
export interface SessionEvidenceRefV1 {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
provider: ProviderId;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
sourcePath?: string;
|
|
9
|
+
eventIndex?: number;
|
|
10
|
+
eventId?: string;
|
|
11
|
+
timestamp?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ObservedValueV1<T> {
|
|
14
|
+
value: T;
|
|
15
|
+
provenance: ObservationProvenanceV1;
|
|
16
|
+
confidence: number;
|
|
17
|
+
evidence: SessionEvidenceRefV1[];
|
|
18
|
+
}
|
|
19
|
+
export interface PendingUserRequestV1 {
|
|
20
|
+
schemaVersion: 1;
|
|
21
|
+
id: string;
|
|
22
|
+
provider: ProviderId;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
kind: ObservedValueV1<'prompt_response' | 'tool_approval' | 'question'>;
|
|
25
|
+
requestedAt: ObservedValueV1<string>;
|
|
26
|
+
prompt: ObservedValueV1<string | null>;
|
|
27
|
+
}
|
|
28
|
+
export interface ObservedAgentSessionV1 {
|
|
29
|
+
schemaVersion: 1;
|
|
30
|
+
identity: {
|
|
31
|
+
provider: ProviderId;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
sourcePath: string;
|
|
34
|
+
};
|
|
35
|
+
cwd: ObservedValueV1<string>;
|
|
36
|
+
model: ObservedValueV1<string | null>;
|
|
37
|
+
activity: ObservedValueV1<'active' | 'idle' | 'ended' | 'unknown'>;
|
|
38
|
+
usage: {
|
|
39
|
+
inputTokens: ObservedValueV1<number>;
|
|
40
|
+
outputTokens: ObservedValueV1<number>;
|
|
41
|
+
cacheReadTokens: ObservedValueV1<number>;
|
|
42
|
+
cacheWriteTokens: ObservedValueV1<number>;
|
|
43
|
+
costUsd: ObservedValueV1<number>;
|
|
44
|
+
};
|
|
45
|
+
pendingUserRequest: PendingUserRequestV1 | null;
|
|
46
|
+
observedAt: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ProviderCapabilitiesV1 {
|
|
49
|
+
schemaVersion: 1;
|
|
50
|
+
provider: ProviderId;
|
|
51
|
+
resume: ObservedValueV1<boolean>;
|
|
52
|
+
forkLineage: ObservedValueV1<boolean>;
|
|
53
|
+
quotaSource: ObservedValueV1<'api' | 'session' | 'mixed' | 'none'>;
|
|
54
|
+
assetExtraction: ObservedValueV1<boolean>;
|
|
55
|
+
}
|
|
56
|
+
export interface ProviderSessionAdapterV1 {
|
|
57
|
+
readonly schemaVersion: 1;
|
|
58
|
+
readonly provider: ProviderId;
|
|
59
|
+
readonly capabilities: ProviderCapabilitiesV1;
|
|
60
|
+
discover(cwd: string): Promise<ObservedAgentSessionV1[]>;
|
|
61
|
+
read(sessionPath: string, cwd?: string): Promise<ObservedAgentSessionV1>;
|
|
62
|
+
watch(sessionPath: string, listener: (session: ObservedAgentSessionV1) => void, cwd?: string): {
|
|
63
|
+
dispose(): void;
|
|
64
|
+
};
|
|
65
|
+
dispose(): void;
|
|
66
|
+
}
|
|
67
|
+
export declare function derivePendingUserRequestV1(provider: ProviderId, sessionId: string, sourcePath: string, events: SessionEvent[]): PendingUserRequestV1 | null;
|
|
68
|
+
export declare function createProviderSessionAdapterV1(sessionProvider: SessionProviderBase, options?: {
|
|
69
|
+
pollIntervalMs?: number;
|
|
70
|
+
}): ProviderSessionAdapterV1;
|