sidekick-shared 0.27.6 → 0.28.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/CHANGELOG.md +34 -0
- package/README.md +59 -18
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +2 -9
- package/dist/claudeProfiles.js +10 -25
- package/dist/codexPaths.d.ts +22 -0
- package/dist/codexPaths.js +67 -0
- package/dist/codexProfiles.d.ts +1 -4
- package/dist/codexProfiles.js +5 -43
- package/dist/doctor.js +2 -2
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +222 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +49 -15
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +258 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI rollout reader for actionable asset extraction.
|
|
3
|
+
*
|
|
4
|
+
* Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
|
|
5
|
+
* https://github.com/B33pBeeps/trawl
|
|
6
|
+
*
|
|
7
|
+
* @module extractors/sources/codexAssets
|
|
8
|
+
*/
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from 'node:fs';
|
|
11
|
+
import { getCodexMonitoringHomes } from '../../codexProfiles';
|
|
12
|
+
import { commandAsset, extractCommands, extractFilePaths, extractUrls, pathAsset, planAsset, urlAsset, } from '../sessionAssets';
|
|
13
|
+
const EXEC_NAMES = new Set(['exec_command', 'shell', 'local_shell', 'container.exec']);
|
|
14
|
+
function dirExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
return statSync(path).isDirectory();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function firstJsonLine(filePath) {
|
|
23
|
+
let fd;
|
|
24
|
+
try {
|
|
25
|
+
fd = openSync(filePath, 'r');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const buffer = Buffer.alloc(65536);
|
|
31
|
+
let data = '';
|
|
32
|
+
try {
|
|
33
|
+
let bytesRead;
|
|
34
|
+
while ((bytesRead = readSync(fd, buffer, 0, buffer.length, null)) > 0) {
|
|
35
|
+
data += buffer.toString('utf8', 0, bytesRead);
|
|
36
|
+
const newlineIndex = data.indexOf('\n');
|
|
37
|
+
if (newlineIndex >= 0) {
|
|
38
|
+
data = data.slice(0, newlineIndex);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (data.length > 1_000_000)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
closeSync(fd);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(data);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function readJsonl(filePath, skip) {
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = readFileSync(filePath, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const lines = [];
|
|
67
|
+
for (const line of raw.split('\n')) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed)
|
|
70
|
+
continue;
|
|
71
|
+
if (skip?.(trimmed))
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
lines.push(JSON.parse(trimmed));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Ignore partial or malformed JSONL lines.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return lines;
|
|
81
|
+
}
|
|
82
|
+
function rolloutFiles(limit = 150) {
|
|
83
|
+
const entries = [];
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
const walk = (dir) => {
|
|
86
|
+
let dirEntries;
|
|
87
|
+
try {
|
|
88
|
+
dirEntries = readdirSync(dir, { withFileTypes: true });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const entry of dirEntries) {
|
|
94
|
+
const fullPath = join(dir, entry.name);
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
walk(fullPath);
|
|
97
|
+
}
|
|
98
|
+
else if (entry.isFile() &&
|
|
99
|
+
entry.name.startsWith('rollout-') &&
|
|
100
|
+
entry.name.endsWith('.jsonl')) {
|
|
101
|
+
try {
|
|
102
|
+
if (seen.has(fullPath))
|
|
103
|
+
continue;
|
|
104
|
+
seen.add(fullPath);
|
|
105
|
+
entries.push({ path: fullPath, mtime: statSync(fullPath).mtimeMs });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Ignore inaccessible files.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
for (const home of getCodexMonitoringHomes()) {
|
|
114
|
+
const sessionsDir = join(home, 'sessions');
|
|
115
|
+
if (dirExists(sessionsDir))
|
|
116
|
+
walk(sessionsDir);
|
|
117
|
+
}
|
|
118
|
+
return entries
|
|
119
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
120
|
+
.slice(0, limit)
|
|
121
|
+
.map((entry) => entry.path);
|
|
122
|
+
}
|
|
123
|
+
export function codexSessions(cwd, limit = 3) {
|
|
124
|
+
const exactCwd = resolve(cwd);
|
|
125
|
+
const sessions = [];
|
|
126
|
+
for (const filePath of rolloutFiles()) {
|
|
127
|
+
const meta = firstJsonLine(filePath);
|
|
128
|
+
const payload = meta?.payload;
|
|
129
|
+
if (payload?.cwd === exactCwd) {
|
|
130
|
+
sessions.push(filePath);
|
|
131
|
+
if (sessions.length >= limit)
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return sessions;
|
|
136
|
+
}
|
|
137
|
+
function parseArgs(value) {
|
|
138
|
+
if (typeof value !== 'string')
|
|
139
|
+
return value && typeof value === 'object' ? value : {};
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(value);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function asString(value) {
|
|
148
|
+
return typeof value === 'string' ? value : undefined;
|
|
149
|
+
}
|
|
150
|
+
function provenance(sessionPath, source) {
|
|
151
|
+
return { agent: 'codex', sessionPath, source };
|
|
152
|
+
}
|
|
153
|
+
function commandFromLocalShell(item) {
|
|
154
|
+
const action = item.action;
|
|
155
|
+
const command = action?.command;
|
|
156
|
+
if (Array.isArray(command))
|
|
157
|
+
return command.map(String).join(' ');
|
|
158
|
+
return asString(command);
|
|
159
|
+
}
|
|
160
|
+
function patchFiles(patch, cwd) {
|
|
161
|
+
const paths = [];
|
|
162
|
+
for (const match of String(patch).matchAll(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/gm)) {
|
|
163
|
+
for (const filePath of extractFilePaths(match[1].trim(), cwd)) {
|
|
164
|
+
paths.push(filePath);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return paths;
|
|
168
|
+
}
|
|
169
|
+
function addMessageAssets(acc, text, cwd, timestamp, meta) {
|
|
170
|
+
for (const url of extractUrls(text))
|
|
171
|
+
acc.urls.push(urlAsset(url, timestamp, meta));
|
|
172
|
+
for (const filePath of extractFilePaths(text, cwd))
|
|
173
|
+
acc.paths.push(pathAsset(filePath, timestamp, meta));
|
|
174
|
+
for (const command of extractCommands(text))
|
|
175
|
+
acc.commands.push(commandAsset(command, timestamp, meta));
|
|
176
|
+
}
|
|
177
|
+
function addExecutedCommandAssets(acc, command, cwd, timestamp, meta) {
|
|
178
|
+
for (const url of extractUrls(command))
|
|
179
|
+
acc.urls.push(urlAsset(url, timestamp, meta));
|
|
180
|
+
for (const filePath of extractFilePaths(command, cwd))
|
|
181
|
+
acc.paths.push(pathAsset(filePath, timestamp, meta));
|
|
182
|
+
}
|
|
183
|
+
export function readCodexAssets(cwd, limit = 3) {
|
|
184
|
+
const exactCwd = resolve(cwd);
|
|
185
|
+
const files = codexSessions(exactCwd, limit);
|
|
186
|
+
const acc = {
|
|
187
|
+
urls: [],
|
|
188
|
+
paths: [],
|
|
189
|
+
commands: [],
|
|
190
|
+
plans: [],
|
|
191
|
+
hadSession: files.length > 0,
|
|
192
|
+
};
|
|
193
|
+
const skip = (line) => line.includes('"type":"function_call_output"') ||
|
|
194
|
+
line.includes('"type":"reasoning"') ||
|
|
195
|
+
line.includes('"type":"token_count"');
|
|
196
|
+
for (const filePath of files) {
|
|
197
|
+
for (const line of readJsonl(filePath, skip)) {
|
|
198
|
+
const payload = line.payload;
|
|
199
|
+
if (!payload)
|
|
200
|
+
continue;
|
|
201
|
+
const timestamp = asString(line.timestamp);
|
|
202
|
+
if (payload.type === 'function_call') {
|
|
203
|
+
if (EXEC_NAMES.has(payload.name)) {
|
|
204
|
+
const args = parseArgs(payload.arguments);
|
|
205
|
+
addExecutedCommandAssets(acc, args.cmd ?? args.command, exactCwd, timestamp, provenance(filePath, `tool:${String(payload.name)}`));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (payload.type === 'local_shell_call') {
|
|
209
|
+
addExecutedCommandAssets(acc, commandFromLocalShell(payload), exactCwd, timestamp, provenance(filePath, 'tool:local_shell'));
|
|
210
|
+
}
|
|
211
|
+
else if (payload.type === 'item_completed') {
|
|
212
|
+
const item = payload.item;
|
|
213
|
+
const text = asString(item?.text);
|
|
214
|
+
if (item?.type === 'Plan' && text?.trim())
|
|
215
|
+
acc.plans.push(planAsset(text, timestamp, provenance(filePath, 'plan')));
|
|
216
|
+
}
|
|
217
|
+
else if (payload.type === 'custom_tool_call' && payload.name === 'apply_patch') {
|
|
218
|
+
const meta = provenance(filePath, 'tool:apply_patch');
|
|
219
|
+
for (const file of patchFiles(payload.input, exactCwd))
|
|
220
|
+
acc.paths.push(pathAsset(file, timestamp, meta));
|
|
221
|
+
}
|
|
222
|
+
else if (payload.type === 'message' && Array.isArray(payload.content)) {
|
|
223
|
+
for (const block of payload.content) {
|
|
224
|
+
const typedBlock = block;
|
|
225
|
+
addMessageAssets(acc, typedBlock.text, exactCwd, timestamp, provenance(filePath, 'message'));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (payload.type === 'agent_message') {
|
|
229
|
+
addMessageAssets(acc, payload.message, exactCwd, timestamp, provenance(filePath, 'message'));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return acc;
|
|
234
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure extraction of normalized TokenUsage from a SessionEvent.
|
|
3
|
+
*
|
|
4
|
+
* Provides a standalone function to extract camelCase TokenUsage from
|
|
5
|
+
* a raw session event without requiring an EventAggregator instance.
|
|
6
|
+
*
|
|
7
|
+
* @module extractors/tokenUsage
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Extracts normalized TokenUsage from a SessionEvent.
|
|
11
|
+
*
|
|
12
|
+
* Looks for `message.usage` on assistant events and normalizes
|
|
13
|
+
* the snake_case API fields to the camelCase TokenUsage shape.
|
|
14
|
+
*
|
|
15
|
+
* @param event - A parsed session event
|
|
16
|
+
* @returns TokenUsage if the event contains usage data, null otherwise
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const usage = extractTokenUsage(event);
|
|
21
|
+
* if (usage) {
|
|
22
|
+
* console.log(`Input: ${usage.inputTokens}, Output: ${usage.outputTokens}`);
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function extractTokenUsage(event) {
|
|
27
|
+
const usage = event.message?.usage;
|
|
28
|
+
if (!usage)
|
|
29
|
+
return null;
|
|
30
|
+
return {
|
|
31
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
32
|
+
outputTokens: usage.output_tokens ?? 0,
|
|
33
|
+
cacheWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
34
|
+
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
35
|
+
model: event.message?.model ?? 'unknown',
|
|
36
|
+
timestamp: new Date(event.timestamp),
|
|
37
|
+
reportedCost: usage.reported_cost,
|
|
38
|
+
reasoningTokens: usage.reasoning_tokens,
|
|
39
|
+
...(event.message?.usageKind ? { usageKind: event.message.usageKind } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure extraction of ToolCall objects from a SessionEvent.
|
|
3
|
+
*
|
|
4
|
+
* Provides a standalone function to extract tool calls from assistant
|
|
5
|
+
* message content blocks without requiring an EventAggregator instance.
|
|
6
|
+
*
|
|
7
|
+
* @module extractors/toolCall
|
|
8
|
+
*/
|
|
9
|
+
import { categorizeError, extractErrorMessage } from './errorTaxonomy';
|
|
10
|
+
function outputString(value) {
|
|
11
|
+
if (value == null)
|
|
12
|
+
return undefined;
|
|
13
|
+
const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
14
|
+
return text.length > 5000 ? `${text.slice(0, 5000)}\n...(truncated)` : text;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Extracts ToolCall objects from a SessionEvent.
|
|
18
|
+
*
|
|
19
|
+
* Scans the message content blocks for `tool_use` entries and
|
|
20
|
+
* returns them as normalized ToolCall objects.
|
|
21
|
+
*
|
|
22
|
+
* @param event - A parsed session event
|
|
23
|
+
* @returns Array of ToolCall objects (empty if none found)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const calls = extractToolCalls(event);
|
|
28
|
+
* for (const call of calls) {
|
|
29
|
+
* console.log(`${call.name}: ${JSON.stringify(call.input)}`);
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function extractToolCalls(event) {
|
|
34
|
+
// Tool calls live in assistant message content blocks
|
|
35
|
+
if (event.type !== 'assistant')
|
|
36
|
+
return [];
|
|
37
|
+
const content = event.message?.content;
|
|
38
|
+
if (!Array.isArray(content))
|
|
39
|
+
return [];
|
|
40
|
+
const timestamp = new Date(event.timestamp);
|
|
41
|
+
const calls = [];
|
|
42
|
+
for (const block of content) {
|
|
43
|
+
if (block && typeof block === 'object' && 'type' in block && block.type === 'tool_use') {
|
|
44
|
+
const toolBlock = block;
|
|
45
|
+
if (toolBlock.name) {
|
|
46
|
+
const isError = toolBlock.is_error === true || toolBlock.error != null;
|
|
47
|
+
const errorOutput = toolBlock.error ?? toolBlock.output;
|
|
48
|
+
calls.push({
|
|
49
|
+
name: toolBlock.name,
|
|
50
|
+
input: toolBlock.input ?? {},
|
|
51
|
+
timestamp,
|
|
52
|
+
toolUseId: toolBlock.id,
|
|
53
|
+
...(isError
|
|
54
|
+
? {
|
|
55
|
+
isError: true,
|
|
56
|
+
output: outputString(errorOutput),
|
|
57
|
+
errorMessage: extractErrorMessage(errorOutput, toolBlock.name),
|
|
58
|
+
errorCategory: categorizeError(errorOutput),
|
|
59
|
+
}
|
|
60
|
+
: {}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return calls;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stateful shared extractor that correlates provider-normalized tool uses with
|
|
69
|
+
* their later results. Completed calls always carry the canonical taxonomy.
|
|
70
|
+
*/
|
|
71
|
+
export class ToolCallTracker {
|
|
72
|
+
pending = new Map();
|
|
73
|
+
process(event) {
|
|
74
|
+
const completed = [];
|
|
75
|
+
for (const call of extractToolCalls(event)) {
|
|
76
|
+
if (call.toolUseId)
|
|
77
|
+
this.pending.set(call.toolUseId, call);
|
|
78
|
+
else
|
|
79
|
+
completed.push(call);
|
|
80
|
+
}
|
|
81
|
+
const topLevel = extractToolCall(event);
|
|
82
|
+
if (topLevel) {
|
|
83
|
+
const id = event.message?.id;
|
|
84
|
+
if (id) {
|
|
85
|
+
topLevel.toolUseId = id;
|
|
86
|
+
this.pending.set(id, topLevel);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
completed.push(topLevel);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const content = event.message?.content;
|
|
93
|
+
if (!Array.isArray(content))
|
|
94
|
+
return completed;
|
|
95
|
+
for (const block of content) {
|
|
96
|
+
if (!block ||
|
|
97
|
+
typeof block !== 'object' ||
|
|
98
|
+
!('type' in block) ||
|
|
99
|
+
block.type !== 'tool_result') {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const result = block;
|
|
103
|
+
if (!result.tool_use_id)
|
|
104
|
+
continue;
|
|
105
|
+
const call = this.pending.get(result.tool_use_id);
|
|
106
|
+
if (!call)
|
|
107
|
+
continue;
|
|
108
|
+
this.pending.delete(result.tool_use_id);
|
|
109
|
+
call.duration =
|
|
110
|
+
typeof result.duration === 'number'
|
|
111
|
+
? result.duration
|
|
112
|
+
: Math.max(0, new Date(event.timestamp).getTime() - call.timestamp.getTime());
|
|
113
|
+
call.isError = result.is_error === true;
|
|
114
|
+
call.output = outputString(result.content);
|
|
115
|
+
if (call.isError) {
|
|
116
|
+
call.errorMessage = extractErrorMessage(result.content, call.name);
|
|
117
|
+
call.errorCategory = categorizeError(result.content);
|
|
118
|
+
}
|
|
119
|
+
completed.push(call);
|
|
120
|
+
}
|
|
121
|
+
return completed;
|
|
122
|
+
}
|
|
123
|
+
reset() {
|
|
124
|
+
this.pending.clear();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extracts a single top-level `tool_use` event.
|
|
129
|
+
*
|
|
130
|
+
* This complements `extractToolCalls`, which scans assistant message content
|
|
131
|
+
* blocks. Some providers normalize tool calls as their own event with
|
|
132
|
+
* `event.type === 'tool_use'` and `event.tool` populated.
|
|
133
|
+
*/
|
|
134
|
+
export function extractToolCall(event) {
|
|
135
|
+
if (event.type !== 'tool_use' || !event.tool?.name)
|
|
136
|
+
return null;
|
|
137
|
+
return {
|
|
138
|
+
name: event.tool.name,
|
|
139
|
+
input: event.tool.input ?? {},
|
|
140
|
+
timestamp: new Date(event.timestamp),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failing-tool trend rows shared by the CLI `stats` block and the VS Code
|
|
3
|
+
* Health tab. Browser-safe: no filesystem access; callers pass the two
|
|
4
|
+
* windows they read with `getTopFailingTools()`.
|
|
5
|
+
*
|
|
6
|
+
* @module failingTools
|
|
7
|
+
*/
|
|
8
|
+
const TREND_TOLERANCE = 0.1;
|
|
9
|
+
/**
|
|
10
|
+
* Compare the last week against the 30-day weekly average: more than 10%
|
|
11
|
+
* above is `up`, more than 10% below is `down`, otherwise `flat`. A tool
|
|
12
|
+
* with no 30-day history but failures this week is `up`.
|
|
13
|
+
*/
|
|
14
|
+
export function failingToolTrend(last7, last30) {
|
|
15
|
+
const weeklyAverage = last30 / (30 / 7);
|
|
16
|
+
if (weeklyAverage <= 0)
|
|
17
|
+
return last7 > 0 ? 'up' : 'flat';
|
|
18
|
+
const ratio = last7 / weeklyAverage;
|
|
19
|
+
if (ratio > 1 + TREND_TOLERANCE)
|
|
20
|
+
return 'up';
|
|
21
|
+
if (ratio < 1 - TREND_TOLERANCE)
|
|
22
|
+
return 'down';
|
|
23
|
+
return 'flat';
|
|
24
|
+
}
|
|
25
|
+
export const FAILING_TOOL_TREND_ARROWS = {
|
|
26
|
+
up: '↑',
|
|
27
|
+
down: '↓',
|
|
28
|
+
flat: '→',
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Merge the 7-day and 30-day windows into one row per tool, ordered by
|
|
32
|
+
* 7-day failures, then 30-day failures, then name.
|
|
33
|
+
*/
|
|
34
|
+
export function mergeFailingToolWindows(last7, last30) {
|
|
35
|
+
const rows = new Map();
|
|
36
|
+
for (const tool of last30) {
|
|
37
|
+
rows.set(tool.tool, {
|
|
38
|
+
tool: tool.tool,
|
|
39
|
+
last7: 0,
|
|
40
|
+
last30: tool.failures,
|
|
41
|
+
categories: { ...(tool.categories ?? {}) },
|
|
42
|
+
trend: 'flat',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
for (const tool of last7) {
|
|
46
|
+
const row = rows.get(tool.tool);
|
|
47
|
+
if (row) {
|
|
48
|
+
row.last7 = tool.failures;
|
|
49
|
+
// A 7-day count cannot exceed the 30-day one; tolerate windows read at different instants.
|
|
50
|
+
row.last30 = Math.max(row.last30, tool.failures);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
rows.set(tool.tool, {
|
|
54
|
+
tool: tool.tool,
|
|
55
|
+
last7: tool.failures,
|
|
56
|
+
last30: tool.failures,
|
|
57
|
+
categories: { ...(tool.categories ?? {}) },
|
|
58
|
+
trend: 'flat',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const row of rows.values())
|
|
63
|
+
row.trend = failingToolTrend(row.last7, row.last30);
|
|
64
|
+
return [...rows.values()].sort((a, b) => b.last7 - a.last7 || b.last30 - a.last30 || a.tool.localeCompare(b.tool));
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite detection shared by the incremental JSONL readers: a file whose
|
|
3
|
+
* first bytes changed, or whose resume point no longer follows a newline,
|
|
4
|
+
* was rewritten rather than appended to.
|
|
5
|
+
*
|
|
6
|
+
* @module fileHead
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
/** How many leading bytes identify a file's content. */
|
|
10
|
+
export const HEAD_BYTES = 4096;
|
|
11
|
+
export function hashHead(buffer) {
|
|
12
|
+
return createHash('sha256').update(buffer).digest('hex').slice(0, 32);
|
|
13
|
+
}
|
|
14
|
+
/** Records the prefix just read (at most `HEAD_BYTES`). */
|
|
15
|
+
export function fileHeadOf(prefix) {
|
|
16
|
+
return { bytes: prefix.length, hash: hashHead(prefix) };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* True when `prefix` (the file's current first bytes) still starts with the
|
|
20
|
+
* recorded head. A file that grew past a short head still matches.
|
|
21
|
+
*/
|
|
22
|
+
export function headMatches(known, prefix) {
|
|
23
|
+
if (!Number.isInteger(known.bytes) || known.bytes < 0 || known.bytes > prefix.length) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return hashHead(prefix.subarray(0, known.bytes)) === known.hash;
|
|
27
|
+
}
|