sidekick-shared 0.22.0 → 0.23.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/README.md +51 -43
- 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 +32 -1
- package/dist/aggregation/EventAggregator.js +238 -25
- package/dist/aggregation/snapshot.js +1 -1
- package/dist/aggregation/types.d.ts +23 -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/codexProfiles.js +2 -0
- package/dist/context/composer.d.ts +3 -2
- package/dist/context/composer.js +5 -5
- package/dist/doctor.d.ts +55 -0
- package/dist/doctor.js +244 -0
- package/dist/errorHistory.d.ts +32 -0
- package/dist/errorHistory.js +94 -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 +62 -24
- package/dist/parsers/codexParser.js +13 -0
- package/dist/parsers/openCodeParser.js +13 -0
- package/dist/parsers/planExtractor.js +17 -6
- 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/providers/codexDatabase.d.ts +1 -11
- package/dist/providers/openCodeDatabase.d.ts +1 -31
- package/dist/providers/openCodeDatabase.js +9 -8
- package/dist/providers/types.d.ts +1 -0
- package/dist/readers/decisions.d.ts +2 -1
- package/dist/readers/decisions.js +2 -4
- 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 +2 -4
- package/dist/readers/plans.js +1 -1
- package/dist/readers/tasks.d.ts +2 -1
- package/dist/readers/tasks.js +2 -4
- 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 +77 -0
- package/dist/statusline/index.d.ts +7 -0
- package/dist/statusline/index.js +13 -0
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/historicalData.d.ts +35 -1
- package/dist/types/historicalData.js +6 -2
- package/dist/types/observedSessionV1.d.ts +70 -0
- package/dist/types/observedSessionV1.js +185 -0
- package/dist/types/opencode.d.ts +1 -0
- package/dist/types/sessionEvent.d.ts +18 -1
- package/dist/watchers/eventBridge.js +1 -0
- package/dist/watchers/types.d.ts +5 -0
- package/dist/writers/atomic.d.ts +4 -0
- package/dist/writers/atomic.js +113 -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/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,77 @@
|
|
|
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 capturedAt = quota.capturedAt ? Date.parse(quota.capturedAt) : now.getTime();
|
|
46
|
+
const sampleAt = Number.isFinite(capturedAt) ? capturedAt : now.getTime();
|
|
47
|
+
const elapsedMinutes = (sampleAt - (resetAt - quota_1.FIVE_HOUR_WINDOW_MS)) / 60_000;
|
|
48
|
+
if (elapsedMinutes <= 0 || quota.fiveHour.utilization <= 0)
|
|
49
|
+
return null;
|
|
50
|
+
const burnRate = quota.fiveHour.utilization / elapsedMinutes;
|
|
51
|
+
return (0, BurnRateCalculator_1.estimateTimeToQuota)(quota.fiveHour.utilization, 100, burnRate);
|
|
52
|
+
}
|
|
53
|
+
function compactLabel(value) {
|
|
54
|
+
const trimmed = value.trim();
|
|
55
|
+
const localPart = trimmed.includes('@') ? trimmed.split('@')[0] : trimmed;
|
|
56
|
+
return localPart.replace(/\s+/g, '-').slice(0, 24) || 'unknown';
|
|
57
|
+
}
|
|
58
|
+
function clampPercent(value) {
|
|
59
|
+
return Number.isFinite(value) ? Math.min(100, Math.max(0, value)) : 0;
|
|
60
|
+
}
|
|
61
|
+
function formatReset(value, fallback) {
|
|
62
|
+
const reset = new Date(value);
|
|
63
|
+
const date = Number.isNaN(reset.getTime()) ? fallback : reset;
|
|
64
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
65
|
+
hour: '2-digit',
|
|
66
|
+
minute: '2-digit',
|
|
67
|
+
hour12: false,
|
|
68
|
+
}).format(date);
|
|
69
|
+
}
|
|
70
|
+
function formatMinutes(value) {
|
|
71
|
+
const minutes = Math.max(0, Math.round(value));
|
|
72
|
+
if (minutes < 60)
|
|
73
|
+
return `${minutes}min`;
|
|
74
|
+
const hours = Math.floor(minutes / 60);
|
|
75
|
+
const remainder = minutes % 60;
|
|
76
|
+
return remainder ? `${hours}h${remainder}m` : `${hours}h`;
|
|
77
|
+
}
|
|
@@ -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/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;
|
|
@@ -74,9 +78,32 @@ export interface HistoricalDataStore {
|
|
|
74
78
|
monthly: Record<string, MonthlyData>;
|
|
75
79
|
allTime: AllTimeStats;
|
|
76
80
|
lastSaved: string;
|
|
81
|
+
/** Capped durable per-session records, newest last (schema v3). */
|
|
82
|
+
sessions?: SessionHistoryRecord[];
|
|
77
83
|
importedFiles?: string[];
|
|
78
84
|
lastImportTimestamp?: string;
|
|
79
85
|
}
|
|
86
|
+
export interface SessionHistoryRecord {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
provider: string;
|
|
89
|
+
project: string;
|
|
90
|
+
startTime: string;
|
|
91
|
+
endTime: string;
|
|
92
|
+
tokens: TokenTotals;
|
|
93
|
+
totalCost: number;
|
|
94
|
+
messageCount: number;
|
|
95
|
+
qualityScore: number;
|
|
96
|
+
qualityFactors: Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
label: string;
|
|
99
|
+
contribution: number;
|
|
100
|
+
maximum: number;
|
|
101
|
+
detail: string;
|
|
102
|
+
}>;
|
|
103
|
+
additions: number;
|
|
104
|
+
deletions: number;
|
|
105
|
+
costPerChangedLine: number | null;
|
|
106
|
+
}
|
|
80
107
|
export interface SessionSummary {
|
|
81
108
|
sessionId: string;
|
|
82
109
|
startTime: string;
|
|
@@ -96,5 +123,12 @@ export interface SessionSummary {
|
|
|
96
123
|
* Omitted on v1 records or when every model was priced.
|
|
97
124
|
*/
|
|
98
125
|
unpricedModelIds?: string[];
|
|
126
|
+
provider?: string;
|
|
127
|
+
project?: string;
|
|
128
|
+
qualityScore?: number;
|
|
129
|
+
qualityFactors?: SessionHistoryRecord['qualityFactors'];
|
|
130
|
+
additions?: number;
|
|
131
|
+
deletions?: number;
|
|
132
|
+
costPerChangedLine?: number | null;
|
|
99
133
|
}
|
|
100
134
|
export declare function createEmptyTokenTotals(): TokenTotals;
|
|
@@ -11,11 +11,15 @@
|
|
|
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.HISTORICAL_DATA_SCHEMA_VERSION =
|
|
21
|
+
exports.HISTORICAL_DATA_SCHEMA_VERSION = 3;
|
|
22
|
+
exports.HISTORICAL_SESSION_RETENTION_LIMIT = 500;
|
|
19
23
|
function createEmptyTokenTotals() {
|
|
20
24
|
return {
|
|
21
25
|
inputTokens: 0,
|
|
@@ -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;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.derivePendingUserRequestV1 = derivePendingUserRequestV1;
|
|
4
|
+
exports.createProviderSessionAdapterV1 = createProviderSessionAdapterV1;
|
|
5
|
+
function evidence(provider, sessionId, sourcePath, event, eventIndex) {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
schemaVersion: 1,
|
|
9
|
+
provider,
|
|
10
|
+
sessionId,
|
|
11
|
+
sourcePath,
|
|
12
|
+
eventIndex,
|
|
13
|
+
eventId: event?.message.id,
|
|
14
|
+
timestamp: event?.timestamp,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
function observed(value, provenance, confidence, refs) {
|
|
19
|
+
return { value, provenance, confidence, evidence: refs };
|
|
20
|
+
}
|
|
21
|
+
function derivePendingUserRequestV1(provider, sessionId, sourcePath, events) {
|
|
22
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
23
|
+
const event = events[index];
|
|
24
|
+
// A user/tool-result event after a request means the human has responded.
|
|
25
|
+
if (event.type === 'user')
|
|
26
|
+
return null;
|
|
27
|
+
const request = waitingToolRequest(event);
|
|
28
|
+
if (request) {
|
|
29
|
+
const refs = evidence(provider, sessionId, sourcePath, event, index);
|
|
30
|
+
return {
|
|
31
|
+
schemaVersion: 1,
|
|
32
|
+
id: `${provider}:${sessionId}:${request.id ?? event.message.id ?? index}`,
|
|
33
|
+
provider,
|
|
34
|
+
sessionId,
|
|
35
|
+
kind: observed(request.kind, 'inferred', request.kind === 'question' ? 0.95 : 0.9, refs),
|
|
36
|
+
requestedAt: observed(event.timestamp, 'reported', 1, refs),
|
|
37
|
+
prompt: observed(request.prompt, request.prompt ? 'reported' : 'inferred', request.prompt ? 0.95 : 0.4, refs),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// A later assistant turn proves any older request was already resolved.
|
|
41
|
+
if (event.type === 'assistant' || event.type === 'tool_use')
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function waitingToolRequest(event) {
|
|
47
|
+
const calls = [];
|
|
48
|
+
if (event.type === 'tool_use') {
|
|
49
|
+
calls.push({ id: event.message.id, name: event.tool?.name, input: event.tool?.input });
|
|
50
|
+
}
|
|
51
|
+
if (Array.isArray(event.message.content)) {
|
|
52
|
+
for (const block of event.message.content) {
|
|
53
|
+
if (!block || typeof block !== 'object')
|
|
54
|
+
continue;
|
|
55
|
+
const value = block;
|
|
56
|
+
if (value.type !== 'tool_use')
|
|
57
|
+
continue;
|
|
58
|
+
calls.push({
|
|
59
|
+
id: typeof value.id === 'string' ? value.id : undefined,
|
|
60
|
+
name: typeof value.name === 'string' ? value.name : undefined,
|
|
61
|
+
input: value.input,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (let index = calls.length - 1; index >= 0; index--) {
|
|
66
|
+
const call = calls[index];
|
|
67
|
+
if (call.name !== 'AskUserQuestion' && call.name !== 'ExitPlanMode')
|
|
68
|
+
continue;
|
|
69
|
+
return {
|
|
70
|
+
id: call.id,
|
|
71
|
+
kind: call.name === 'AskUserQuestion' ? 'question' : 'tool_approval',
|
|
72
|
+
prompt: waitingToolPrompt(call.input, call.name),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function waitingToolPrompt(input, toolName) {
|
|
78
|
+
if (!input || typeof input !== 'object') {
|
|
79
|
+
return toolName === 'ExitPlanMode' ? 'Approve the proposed plan?' : null;
|
|
80
|
+
}
|
|
81
|
+
const record = input;
|
|
82
|
+
if (typeof record.question === 'string')
|
|
83
|
+
return record.question;
|
|
84
|
+
if (typeof record.prompt === 'string')
|
|
85
|
+
return record.prompt;
|
|
86
|
+
if (Array.isArray(record.questions)) {
|
|
87
|
+
const questions = record.questions
|
|
88
|
+
.map((question) => question && typeof question === 'object' && 'question' in question
|
|
89
|
+
? String(question.question ?? '')
|
|
90
|
+
: '')
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
if (questions.length > 0)
|
|
93
|
+
return questions.join('\n');
|
|
94
|
+
}
|
|
95
|
+
if (toolName === 'ExitPlanMode')
|
|
96
|
+
return 'Approve the proposed plan?';
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function providerCapabilities(provider) {
|
|
100
|
+
const refs = [];
|
|
101
|
+
const known = (value) => observed(value, 'inferred', 1, refs);
|
|
102
|
+
return {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
provider,
|
|
105
|
+
resume: known(true),
|
|
106
|
+
forkLineage: known(provider !== 'opencode'),
|
|
107
|
+
quotaSource: known(provider === 'claude-code' ? 'api' : provider === 'codex' ? 'mixed' : 'none'),
|
|
108
|
+
assetExtraction: known(true),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createProviderSessionAdapterV1(sessionProvider, options = {}) {
|
|
112
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
|
|
113
|
+
const adapter = {
|
|
114
|
+
schemaVersion: 1,
|
|
115
|
+
provider: sessionProvider.id,
|
|
116
|
+
capabilities: providerCapabilities(sessionProvider.id),
|
|
117
|
+
async discover(cwd) {
|
|
118
|
+
return Promise.all(sessionProvider.findAllSessions(cwd).map((sessionPath) => adapter.read(sessionPath, cwd)));
|
|
119
|
+
},
|
|
120
|
+
async read(sessionPath, cwd = '') {
|
|
121
|
+
const stats = sessionProvider.readSessionStats(sessionPath);
|
|
122
|
+
const events = sessionProvider.createReader(sessionPath).readAll();
|
|
123
|
+
const refs = evidence(sessionProvider.id, stats.sessionId, sessionPath);
|
|
124
|
+
const model = Object.entries(stats.modelUsage).sort((left, right) => right[1].calls - left[1].calls)[0]?.[0] ?? null;
|
|
125
|
+
const metadata = sessionProvider.getSessionMetadata?.(sessionPath);
|
|
126
|
+
const modifiedAt = metadata?.mtime.getTime() ?? Date.parse(stats.endTime);
|
|
127
|
+
const recent = Number.isFinite(modifiedAt) && Date.now() - modifiedAt < 2 * 60_000;
|
|
128
|
+
return {
|
|
129
|
+
schemaVersion: 1,
|
|
130
|
+
identity: {
|
|
131
|
+
provider: sessionProvider.id,
|
|
132
|
+
sessionId: stats.sessionId,
|
|
133
|
+
sourcePath: sessionPath,
|
|
134
|
+
},
|
|
135
|
+
cwd: observed(cwd, 'inferred', cwd ? 0.95 : 0.4, refs),
|
|
136
|
+
model: observed(model, model ? 'reported' : 'inferred', model ? 1 : 0.2, refs),
|
|
137
|
+
activity: observed(recent ? 'active' : 'idle', 'inferred', 0.8, refs),
|
|
138
|
+
usage: {
|
|
139
|
+
inputTokens: observed(stats.tokens.input, 'reported', 1, refs),
|
|
140
|
+
outputTokens: observed(stats.tokens.output, 'reported', 1, refs),
|
|
141
|
+
cacheReadTokens: observed(stats.tokens.cacheRead, 'reported', 1, refs),
|
|
142
|
+
cacheWriteTokens: observed(stats.tokens.cacheWrite, 'reported', 1, refs),
|
|
143
|
+
costUsd: observed(stats.reportedCost, stats.reportedCost > 0 ? 'reported' : 'estimated', stats.reportedCost > 0 ? 1 : 0.5, refs),
|
|
144
|
+
},
|
|
145
|
+
pendingUserRequest: derivePendingUserRequestV1(sessionProvider.id, stats.sessionId, sessionPath, events),
|
|
146
|
+
observedAt: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
watch(sessionPath, listener, cwd) {
|
|
150
|
+
let disposed = false;
|
|
151
|
+
let lastSignature = '';
|
|
152
|
+
const update = async () => {
|
|
153
|
+
if (disposed)
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
const session = await adapter.read(sessionPath, cwd);
|
|
157
|
+
const signature = JSON.stringify({
|
|
158
|
+
activity: session.activity,
|
|
159
|
+
model: session.model,
|
|
160
|
+
usage: session.usage,
|
|
161
|
+
pendingUserRequest: session.pendingUserRequest,
|
|
162
|
+
});
|
|
163
|
+
if (signature !== lastSignature) {
|
|
164
|
+
lastSignature = signature;
|
|
165
|
+
listener(session);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Observed sources can disappear between polling iterations.
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
void update();
|
|
173
|
+
const timer = setInterval(update, pollIntervalMs);
|
|
174
|
+
timer.unref?.();
|
|
175
|
+
return {
|
|
176
|
+
dispose: () => {
|
|
177
|
+
disposed = true;
|
|
178
|
+
clearInterval(timer);
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
dispose: () => sessionProvider.dispose(),
|
|
183
|
+
};
|
|
184
|
+
return adapter;
|
|
185
|
+
}
|
package/dist/types/opencode.d.ts
CHANGED
|
@@ -76,6 +76,21 @@ export interface SessionEvent {
|
|
|
76
76
|
resetsAt: number;
|
|
77
77
|
};
|
|
78
78
|
};
|
|
79
|
+
/** Exact context compaction counts when reported by the provider. */
|
|
80
|
+
compaction?: {
|
|
81
|
+
tokensBefore: number;
|
|
82
|
+
tokensAfter: number;
|
|
83
|
+
};
|
|
84
|
+
/** Provider-specific signals normalized for shared analytics. */
|
|
85
|
+
providerMetadata?: {
|
|
86
|
+
retryAttempts?: number[];
|
|
87
|
+
finishReasons?: string[];
|
|
88
|
+
providerError?: {
|
|
89
|
+
type?: string;
|
|
90
|
+
message?: string;
|
|
91
|
+
code?: string | number;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
79
94
|
/** Tool use details (when type is 'tool_use') */
|
|
80
95
|
tool?: {
|
|
81
96
|
name: string;
|
|
@@ -156,7 +171,7 @@ export interface ToolCall {
|
|
|
156
171
|
/** Truncated error message if the call failed */
|
|
157
172
|
errorMessage?: string;
|
|
158
173
|
/** Error category (permission, not_found, timeout, syntax, exit_code, tool_error, other) */
|
|
159
|
-
errorCategory?:
|
|
174
|
+
errorCategory?: import('../extractors/errorTaxonomy').ErrorCategory;
|
|
160
175
|
/** Tool use ID for correlation with tool_result */
|
|
161
176
|
toolUseId?: string;
|
|
162
177
|
/** Truncated tool output/result content (max 5000 chars) */
|
|
@@ -506,6 +521,8 @@ export interface CompactionEvent {
|
|
|
506
521
|
contextAfter: number;
|
|
507
522
|
/** Tokens reclaimed (contextBefore - contextAfter) */
|
|
508
523
|
tokensReclaimed: number;
|
|
524
|
+
/** Whether the provider reported exact counts or Sidekick inferred them. */
|
|
525
|
+
source: 'reported' | 'heuristic';
|
|
509
526
|
}
|
|
510
527
|
/**
|
|
511
528
|
* Record of a tool output truncation event.
|
package/dist/watchers/types.d.ts
CHANGED
|
@@ -36,6 +36,11 @@ export interface FollowEvent {
|
|
|
36
36
|
resetsAt: number;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
/** Exact compaction counts when supplied by the provider. */
|
|
40
|
+
compaction?: {
|
|
41
|
+
tokensBefore: number;
|
|
42
|
+
tokensAfter: number;
|
|
43
|
+
};
|
|
39
44
|
/** Original raw event for JSON output. */
|
|
40
45
|
raw?: unknown;
|
|
41
46
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function atomicWriteJson(filePath: string, value: unknown): Promise<void>;
|
|
2
|
+
export declare function atomicWriteJsonSync(filePath: string, value: unknown): void;
|
|
3
|
+
/** Serializes read-modify-write cycles across Sidekick processes. */
|
|
4
|
+
export declare function updateJsonStoreAtomic<T>(filePath: string, createEmpty: () => T, update: (latest: T) => T | Promise<T>): Promise<T>;
|