sidekick-shared 0.27.5 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +93 -52
- package/dist/accountHealth.d.ts +14 -0
- package/dist/accountHealth.js +61 -14
- package/dist/accountLaunch.d.ts +6 -0
- package/dist/accountLaunch.js +61 -4
- package/dist/accountLease.d.ts +5 -0
- package/dist/accountLease.js +98 -0
- package/dist/accountManager.d.ts +7 -0
- package/dist/accountManager.js +226 -89
- package/dist/accountSync.d.ts +1 -5
- package/dist/accountSync.js +22 -6
- package/dist/accountSyncTypes.d.ts +9 -0
- package/dist/accounts.d.ts +75 -18
- package/dist/accounts.js +712 -189
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudeCredentialDoctor.d.ts +50 -0
- package/dist/claudeCredentialDoctor.js +277 -0
- package/dist/claudeCredentialLedger.d.ts +15 -0
- package/dist/claudeCredentialLedger.js +130 -0
- package/dist/claudeCredentials.d.ts +20 -3
- package/dist/claudeCredentials.js +50 -3
- package/dist/claudeLiveGuard.d.ts +33 -0
- package/dist/claudeLiveGuard.js +46 -0
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +15 -8
- package/dist/claudeProfiles.js +30 -23
- package/dist/claudeRefreshLock.d.ts +15 -0
- package/dist/claudeRefreshLock.js +145 -0
- package/dist/codexAuth.d.ts +4 -0
- package/dist/codexAuth.js +18 -0
- package/dist/codexCredentialDoctor.d.ts +33 -0
- package/dist/codexCredentialDoctor.js +302 -0
- package/dist/codexCredentialStore.d.ts +29 -0
- package/dist/codexCredentialStore.js +213 -0
- package/dist/codexKeepAlive.d.ts +2 -0
- package/dist/codexKeepAlive.js +116 -0
- package/dist/codexPaths.d.ts +30 -2
- package/dist/codexPaths.js +118 -7
- package/dist/codexProfiles.d.ts +38 -35
- package/dist/codexProfiles.js +848 -715
- package/dist/credentialIO.d.ts +67 -7
- package/dist/credentialIO.js +211 -47
- package/dist/doctor.js +2 -2
- package/dist/ensureDefaultAccounts.js +1 -8
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +30 -11
- package/dist/index.js +72 -15
- package/dist/keepAlive.d.ts +2 -1
- package/dist/keepAlive.js +116 -29
- package/dist/liveCredentialGuard.d.ts +28 -0
- package/dist/liveCredentialGuard.js +36 -0
- 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/processDetection.d.ts +10 -3
- package/dist/processDetection.js +44 -11
- 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/accountManager.d.ts +22 -0
- package/dist/schemas/accountManager.js +4 -0
- package/dist/schemas/assistantTurn.d.ts +2 -2
- 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/terminalSync.d.ts +12 -2
- package/dist/terminalSync.js +27 -2
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { reconcileClaudeAuthState } from './accounts';
|
|
2
|
+
import { cleanupAbandonedLoginsAsync, syncLiveAccountState } from './accountSync';
|
|
3
|
+
function logFailure(options, message, error) {
|
|
4
|
+
try {
|
|
5
|
+
options?.logger?.(message, error);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// Logging must never make account bootstrap fail.
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function statusFromReport(report) {
|
|
12
|
+
if (report.registered)
|
|
13
|
+
return 'registered';
|
|
14
|
+
// A keyring-mode Codex login is a steady state sidekick cannot act on, not a failure.
|
|
15
|
+
if (report.skipped === 'keyring')
|
|
16
|
+
return 'skipped';
|
|
17
|
+
if (report.warnings.length > 0)
|
|
18
|
+
return 'error';
|
|
19
|
+
return 'skipped';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Host startup hook: run the one-time on-disk migrations, drop abandoned
|
|
23
|
+
* isolated logins, then reconcile the live logins with the saved profiles
|
|
24
|
+
* (registering any login sidekick has never seen).
|
|
25
|
+
*/
|
|
26
|
+
export async function ensureDefaultAccounts(options) {
|
|
27
|
+
try {
|
|
28
|
+
reconcileClaudeAuthState();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logFailure(options, 'Claude auth reconciliation failed.', error);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await cleanupAbandonedLoginsAsync();
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logFailure(options, 'Abandoned login cleanup failed.', error);
|
|
38
|
+
}
|
|
39
|
+
let sync;
|
|
40
|
+
try {
|
|
41
|
+
sync = await syncLiveAccountState({ reason: 'startup' });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logFailure(options, 'Account sync failed.', error);
|
|
45
|
+
return { claude: 'error', codex: 'error' };
|
|
46
|
+
}
|
|
47
|
+
for (const [provider, report] of [
|
|
48
|
+
['Claude', sync.claude],
|
|
49
|
+
['Codex', sync.codex],
|
|
50
|
+
]) {
|
|
51
|
+
if (report.skipped === 'keyring')
|
|
52
|
+
continue;
|
|
53
|
+
for (const warning of report.warnings) {
|
|
54
|
+
logFailure(options, `${provider} account sync: ${warning}`, undefined);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
claude: statusFromReport(sync.claude),
|
|
59
|
+
codex: statusFromReport(sync.codex),
|
|
60
|
+
sync,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { getGlobalDataPath } from './paths';
|
|
3
|
+
import { updateJsonStoreAtomic } from './writers/atomic';
|
|
4
|
+
export const ERROR_HISTORY_SCHEMA_VERSION = 1;
|
|
5
|
+
export const MAX_ERROR_HISTORY_SESSIONS = 500;
|
|
6
|
+
function emptyStore() {
|
|
7
|
+
return {
|
|
8
|
+
schemaVersion: ERROR_HISTORY_SCHEMA_VERSION,
|
|
9
|
+
sessions: [],
|
|
10
|
+
lastSaved: new Date().toISOString(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export async function appendErrorHistory(identity, rollup, filePath = getGlobalDataPath('error-history.json')) {
|
|
14
|
+
const record = {
|
|
15
|
+
...identity,
|
|
16
|
+
endedAt: identity.endedAt ?? new Date().toISOString(),
|
|
17
|
+
failures: rollup.byToolCategory,
|
|
18
|
+
retryAttempts: rollup.retryAttempts,
|
|
19
|
+
finishReasons: rollup.finishReasons,
|
|
20
|
+
};
|
|
21
|
+
await updateJsonStoreAtomic(filePath, emptyStore, (store) => ({
|
|
22
|
+
...store,
|
|
23
|
+
schemaVersion: ERROR_HISTORY_SCHEMA_VERSION,
|
|
24
|
+
sessions: [...(Array.isArray(store.sessions) ? store.sessions : []), record].slice(-MAX_ERROR_HISTORY_SESSIONS),
|
|
25
|
+
lastSaved: record.endedAt,
|
|
26
|
+
}));
|
|
27
|
+
return record;
|
|
28
|
+
}
|
|
29
|
+
export async function readErrorHistory(filePath = getGlobalDataPath('error-history.json')) {
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
|
|
32
|
+
if (parsed.schemaVersion === ERROR_HISTORY_SCHEMA_VERSION && Array.isArray(parsed.sessions))
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Missing and malformed history are represented as an empty store.
|
|
37
|
+
}
|
|
38
|
+
return emptyStore();
|
|
39
|
+
}
|
|
40
|
+
export async function getTopFailingTools(days = 7, now = new Date(), filePath = getGlobalDataPath('error-history.json')) {
|
|
41
|
+
const cutoff = now.getTime() - Math.max(1, days) * 24 * 60 * 60 * 1000;
|
|
42
|
+
const store = await readErrorHistory(filePath);
|
|
43
|
+
const tools = new Map();
|
|
44
|
+
for (const session of store.sessions) {
|
|
45
|
+
const endedAt = Date.parse(session.endedAt);
|
|
46
|
+
if (!Number.isFinite(endedAt) || endedAt < cutoff)
|
|
47
|
+
continue;
|
|
48
|
+
for (const failure of session.failures) {
|
|
49
|
+
const item = tools.get(failure.tool) ?? { tool: failure.tool, failures: 0, categories: {} };
|
|
50
|
+
item.failures += failure.count;
|
|
51
|
+
item.categories[failure.category] = (item.categories[failure.category] ?? 0) + failure.count;
|
|
52
|
+
tools.set(failure.tool, item);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return Array.from(tools.values()).sort((left, right) => right.failures - left.failures);
|
|
56
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function stringifyError(value) {
|
|
2
|
+
if (typeof value === 'string')
|
|
3
|
+
return value;
|
|
4
|
+
if (value == null)
|
|
5
|
+
return '';
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Classifies provider and tool failures into the stable shared taxonomy. */
|
|
14
|
+
export function categorizeError(output, providerErrorType) {
|
|
15
|
+
const type = (providerErrorType ?? '').toLowerCase();
|
|
16
|
+
const text = `${type} ${stringifyError(output)}`.toLowerCase();
|
|
17
|
+
if (type === 'autherror' ||
|
|
18
|
+
/permission denied|not permitted|unauthori[sz]ed|forbidden|authentication|access denied/.test(text)) {
|
|
19
|
+
return 'permission';
|
|
20
|
+
}
|
|
21
|
+
if (/not found|no such file|enoent|does not exist|unknown command/.test(text))
|
|
22
|
+
return 'not_found';
|
|
23
|
+
if (/timeout|timed out|deadline exceeded|etimedout/.test(text))
|
|
24
|
+
return 'timeout';
|
|
25
|
+
if (/syntax error|parse error|invalid syntax/.test(text))
|
|
26
|
+
return 'syntax';
|
|
27
|
+
if (/exit[ _-]?code|non[- ]zero exit|process exited/.test(text))
|
|
28
|
+
return 'exit_code';
|
|
29
|
+
if (type === 'apierror' ||
|
|
30
|
+
type === 'outputlengtherror' ||
|
|
31
|
+
/tool_use_error|tool error|output length|rate limit|retry attempt/.test(text)) {
|
|
32
|
+
return 'tool_error';
|
|
33
|
+
}
|
|
34
|
+
return 'other';
|
|
35
|
+
}
|
|
36
|
+
/** Produces the compact error label used by the extension and CLI. */
|
|
37
|
+
export function extractErrorMessage(content, toolName) {
|
|
38
|
+
let message = stringifyError(content) || 'Unknown error';
|
|
39
|
+
message = message.replace(/<\/?tool_use_error>/g, '').trim();
|
|
40
|
+
if (message.length > 150)
|
|
41
|
+
message = `${message.slice(0, 147)}...`;
|
|
42
|
+
return `${toolName}: ${message}`;
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate actionable assets for exactly one working directory.
|
|
3
|
+
*
|
|
4
|
+
* Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
|
|
5
|
+
* https://github.com/B33pBeeps/trawl
|
|
6
|
+
*
|
|
7
|
+
* @module extractors/gatherAssets
|
|
8
|
+
*/
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { readClaudeAssets } from './sources/claudeAssets';
|
|
11
|
+
import { readCodexAssets } from './sources/codexAssets';
|
|
12
|
+
import { byTimestampDesc, capped, dedupeAssets, dedupePlansByTitle, DEFAULT_CAPS, } from './sessionAssets';
|
|
13
|
+
const ALL_AGENTS = ['claude', 'codex'];
|
|
14
|
+
export function gatherAssetsForCwd(options = {}) {
|
|
15
|
+
const cwd = resolve(options.cwd || process.cwd());
|
|
16
|
+
const sessionLimit = options.sessionsPerAgent ?? 3;
|
|
17
|
+
const agents = options.agents ?? ALL_AGENTS;
|
|
18
|
+
const caps = { ...DEFAULT_CAPS, ...(options.caps || {}) };
|
|
19
|
+
const sources = [];
|
|
20
|
+
if (agents.includes('claude'))
|
|
21
|
+
sources.push(readClaudeAssets(cwd, sessionLimit));
|
|
22
|
+
if (agents.includes('codex'))
|
|
23
|
+
sources.push(readCodexAssets(cwd, sessionLimit));
|
|
24
|
+
const merge = (key, cap) => capped(dedupeAssets(sources.flatMap((source) => source[key]).sort(byTimestampDesc)), cap);
|
|
25
|
+
return {
|
|
26
|
+
urls: merge('urls', caps.url),
|
|
27
|
+
paths: merge('paths', caps.path),
|
|
28
|
+
commands: merge('commands', caps.command),
|
|
29
|
+
plans: capped(dedupePlansByTitle(sources.flatMap((source) => source.plans).sort(byTimestampDesc)), caps.plan),
|
|
30
|
+
inChat: sources.some((source) => source.hadSession),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract actionable assets from agent session transcript text.
|
|
3
|
+
*
|
|
4
|
+
* Portions adapted from `trawl` by Juan Fourie (B33pBeeps), MIT licensed:
|
|
5
|
+
* https://github.com/B33pBeeps/trawl
|
|
6
|
+
*
|
|
7
|
+
* @module extractors/sessionAssets
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
12
|
+
const URL_RE = /(?:https?|file):\/\/[^\s"'<>)\]`\\]+/g;
|
|
13
|
+
const PATH_RE = /(?:~|\.{0,2}\/)?[A-Za-z0-9_@.-]*(?:\/[A-Za-z0-9_@.-]+)+(?::\d+){0,2}|[A-Za-z0-9_@-]+\.[A-Za-z0-9]{1,8}(?::\d+){0,2}/g;
|
|
14
|
+
const SHELL_TAGS = new Set([
|
|
15
|
+
'sh',
|
|
16
|
+
'bash',
|
|
17
|
+
'shell',
|
|
18
|
+
'zsh',
|
|
19
|
+
'console',
|
|
20
|
+
'shellscript',
|
|
21
|
+
'shell-session',
|
|
22
|
+
]);
|
|
23
|
+
export const DEFAULT_CAPS = {
|
|
24
|
+
command: 60,
|
|
25
|
+
path: 60,
|
|
26
|
+
url: 40,
|
|
27
|
+
plan: 25,
|
|
28
|
+
};
|
|
29
|
+
export function extractUrls(text) {
|
|
30
|
+
if (!text)
|
|
31
|
+
return [];
|
|
32
|
+
const urls = [];
|
|
33
|
+
for (const match of String(text).matchAll(URL_RE)) {
|
|
34
|
+
urls.push(match[0].replace(/[.,;:!?'"`]+$/, ''));
|
|
35
|
+
}
|
|
36
|
+
return urls;
|
|
37
|
+
}
|
|
38
|
+
export function extractFilePaths(text, cwd) {
|
|
39
|
+
if (!text)
|
|
40
|
+
return [];
|
|
41
|
+
const home = os.homedir();
|
|
42
|
+
const base = cwd ? resolve(cwd) : process.cwd();
|
|
43
|
+
const paths = [];
|
|
44
|
+
for (const match of String(text).matchAll(PATH_RE)) {
|
|
45
|
+
let token = match[0].replace(/[.,;:)>"]+$/, '');
|
|
46
|
+
let line;
|
|
47
|
+
const lineMatch = /^(.+?):(\d+)(?::\d+)?$/.exec(token);
|
|
48
|
+
if (lineMatch) {
|
|
49
|
+
token = lineMatch[1];
|
|
50
|
+
line = Number(lineMatch[2]);
|
|
51
|
+
}
|
|
52
|
+
let file = token.startsWith('~') ? token.replace(/^~/, home) : token;
|
|
53
|
+
if (!isAbsolute(file))
|
|
54
|
+
file = resolve(base, file);
|
|
55
|
+
if (isExistingFile(file)) {
|
|
56
|
+
paths.push(line !== undefined ? { file, line } : { file });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return paths;
|
|
60
|
+
}
|
|
61
|
+
export function extractCommands(text) {
|
|
62
|
+
if (!text)
|
|
63
|
+
return [];
|
|
64
|
+
const commands = [];
|
|
65
|
+
const source = String(text);
|
|
66
|
+
const fence = /```([\w.-]*)[ \t]*\r?\n([\s\S]*?)```/g;
|
|
67
|
+
const fencedSpans = [];
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = fence.exec(source))) {
|
|
70
|
+
fencedSpans.push([match.index, fence.lastIndex]);
|
|
71
|
+
const shellTagged = SHELL_TAGS.has(match[1].toLowerCase());
|
|
72
|
+
let continuation = '';
|
|
73
|
+
for (const rawLine of match[2].split('\n')) {
|
|
74
|
+
const promptLine = /^\s*\$\s+(.+)$/.exec(rawLine);
|
|
75
|
+
if (promptLine) {
|
|
76
|
+
commands.push(promptLine[1].trim());
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!shellTagged)
|
|
80
|
+
continue;
|
|
81
|
+
let line = rawLine.trim();
|
|
82
|
+
if (!line || line.startsWith('#'))
|
|
83
|
+
continue;
|
|
84
|
+
if (continuation) {
|
|
85
|
+
line = `${continuation} ${line}`;
|
|
86
|
+
continuation = '';
|
|
87
|
+
}
|
|
88
|
+
if (line.endsWith('\\')) {
|
|
89
|
+
continuation = line.slice(0, -1).trim();
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
commands.push(line);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const lineMatch of source.matchAll(/^[ \t]*\$[ \t]+(.+)$/gm)) {
|
|
96
|
+
const index = lineMatch.index ?? 0;
|
|
97
|
+
if (fencedSpans.some(([start, end]) => index >= start && index < end))
|
|
98
|
+
continue;
|
|
99
|
+
commands.push(lineMatch[1].trim());
|
|
100
|
+
}
|
|
101
|
+
return commands;
|
|
102
|
+
}
|
|
103
|
+
export function urlAsset(url, timestamp, provenance = {}) {
|
|
104
|
+
return { type: 'url', text: url, display: url, timestamp, ...provenance };
|
|
105
|
+
}
|
|
106
|
+
export function commandAsset(command, timestamp, provenance = {}) {
|
|
107
|
+
return { type: 'command', text: command, display: flat(command), timestamp, ...provenance };
|
|
108
|
+
}
|
|
109
|
+
export function pathAsset(path, timestamp, provenance = {}) {
|
|
110
|
+
const text = path.line !== undefined ? `${path.file}:${path.line}` : path.file;
|
|
111
|
+
return { type: 'path', text, display: text, timestamp, ...provenance };
|
|
112
|
+
}
|
|
113
|
+
export function planAsset(markdown, timestamp, provenance = {}) {
|
|
114
|
+
return { type: 'plan', text: markdown, display: planTitle(markdown), timestamp, ...provenance };
|
|
115
|
+
}
|
|
116
|
+
export function flat(value) {
|
|
117
|
+
return value.replace(/\s*\n\s*/g, ' ').trim();
|
|
118
|
+
}
|
|
119
|
+
export function planTitle(markdown) {
|
|
120
|
+
for (const rawLine of markdown.split('\n')) {
|
|
121
|
+
const line = rawLine.trim();
|
|
122
|
+
if (line)
|
|
123
|
+
return line.replace(/^#{1,6}\s*/, '');
|
|
124
|
+
}
|
|
125
|
+
return 'Plan';
|
|
126
|
+
}
|
|
127
|
+
export function isExistingFile(filePath) {
|
|
128
|
+
try {
|
|
129
|
+
return fs.statSync(filePath).isFile();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export function readPlanFile(filePath) {
|
|
136
|
+
if (!filePath)
|
|
137
|
+
return undefined;
|
|
138
|
+
try {
|
|
139
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function byTimestampDesc(a, b) {
|
|
146
|
+
return (b.timestamp || '').localeCompare(a.timestamp || '');
|
|
147
|
+
}
|
|
148
|
+
export function dedupeAssets(assets) {
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
const deduped = [];
|
|
151
|
+
for (const asset of assets) {
|
|
152
|
+
if (!asset.text)
|
|
153
|
+
continue;
|
|
154
|
+
const key = `${asset.type}\0${asset.text}`;
|
|
155
|
+
if (seen.has(key))
|
|
156
|
+
continue;
|
|
157
|
+
seen.add(key);
|
|
158
|
+
deduped.push(asset);
|
|
159
|
+
}
|
|
160
|
+
return deduped;
|
|
161
|
+
}
|
|
162
|
+
export function dedupePlansByTitle(plans) {
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
const deduped = [];
|
|
165
|
+
for (const plan of plans) {
|
|
166
|
+
const key = (plan.display || plan.text).trim().toLowerCase();
|
|
167
|
+
if (!key || seen.has(key))
|
|
168
|
+
continue;
|
|
169
|
+
seen.add(key);
|
|
170
|
+
deduped.push(plan);
|
|
171
|
+
}
|
|
172
|
+
return deduped;
|
|
173
|
+
}
|
|
174
|
+
export function capped(assets, cap) {
|
|
175
|
+
return cap > 0 ? assets.slice(0, cap) : assets;
|
|
176
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code transcript 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/claudeAssets
|
|
8
|
+
*/
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
11
|
+
import { getSessionDirectory } from '../../parsers/sessionPathResolver';
|
|
12
|
+
import { commandAsset, extractCommands, extractFilePaths, extractUrls, isExistingFile, pathAsset, planAsset, readPlanFile, urlAsset, } from '../sessionAssets';
|
|
13
|
+
const PATH_TOOLS = new Set(['Read', 'Write', 'Edit', 'NotebookEdit']);
|
|
14
|
+
function dirExists(path) {
|
|
15
|
+
try {
|
|
16
|
+
return statSync(path).isDirectory();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function filesByMtimeDesc(dir, filter) {
|
|
23
|
+
let names;
|
|
24
|
+
try {
|
|
25
|
+
names = readdirSync(dir);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
return names
|
|
31
|
+
.filter(filter)
|
|
32
|
+
.map((name) => join(dir, name))
|
|
33
|
+
.map((filePath) => {
|
|
34
|
+
try {
|
|
35
|
+
return { path: filePath, mtime: statSync(filePath).mtimeMs };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.filter((entry) => entry !== null)
|
|
42
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
43
|
+
.map((entry) => entry.path);
|
|
44
|
+
}
|
|
45
|
+
function readJsonl(filePath) {
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = readFileSync(filePath, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const lines = [];
|
|
54
|
+
for (const line of raw.split('\n')) {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
continue;
|
|
58
|
+
try {
|
|
59
|
+
lines.push(JSON.parse(trimmed));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Ignore partial or malformed JSONL lines.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
export function claudeSessions(cwd, limit = 3) {
|
|
68
|
+
const dir = getSessionDirectory(cwd);
|
|
69
|
+
if (!dirExists(dir))
|
|
70
|
+
return [];
|
|
71
|
+
return filesByMtimeDesc(dir, (name) => name.endsWith('.jsonl')).slice(0, limit);
|
|
72
|
+
}
|
|
73
|
+
function asString(value) {
|
|
74
|
+
return typeof value === 'string' ? value : undefined;
|
|
75
|
+
}
|
|
76
|
+
function provenance(sessionPath, source) {
|
|
77
|
+
return { agent: 'claude', sessionPath, source };
|
|
78
|
+
}
|
|
79
|
+
function addTextAssets(acc, text, cwd, timestamp, meta) {
|
|
80
|
+
for (const url of extractUrls(text))
|
|
81
|
+
acc.urls.push(urlAsset(url, timestamp, meta));
|
|
82
|
+
for (const filePath of extractFilePaths(text, cwd))
|
|
83
|
+
acc.paths.push(pathAsset(filePath, timestamp, meta));
|
|
84
|
+
for (const command of extractCommands(text))
|
|
85
|
+
acc.commands.push(commandAsset(command, timestamp, meta));
|
|
86
|
+
}
|
|
87
|
+
function addToolPathAssets(acc, value, cwd, timestamp, meta) {
|
|
88
|
+
for (const filePath of extractFilePaths(value, cwd)) {
|
|
89
|
+
acc.paths.push(pathAsset(filePath, timestamp, meta));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function accumClaude(filePath, cwd, acc) {
|
|
93
|
+
for (const line of readJsonl(filePath)) {
|
|
94
|
+
const timestamp = asString(line.timestamp);
|
|
95
|
+
if (line.type === 'attachment') {
|
|
96
|
+
const attachment = line.attachment;
|
|
97
|
+
const planFilePath = asString(attachment?.planFilePath);
|
|
98
|
+
if (planFilePath && isExistingFile(planFilePath)) {
|
|
99
|
+
const text = readPlanFile(planFilePath);
|
|
100
|
+
if (text?.trim())
|
|
101
|
+
acc.plans.push(planAsset(text, timestamp, provenance(filePath, 'attachment:plan')));
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const message = line.message;
|
|
106
|
+
const content = message?.content;
|
|
107
|
+
if (typeof content === 'string') {
|
|
108
|
+
addTextAssets(acc, content, cwd, timestamp, provenance(filePath, 'message'));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!Array.isArray(content))
|
|
112
|
+
continue;
|
|
113
|
+
for (const block of content) {
|
|
114
|
+
const typedBlock = block;
|
|
115
|
+
if (typedBlock.type === 'text') {
|
|
116
|
+
addTextAssets(acc, typedBlock.text, cwd, timestamp, provenance(filePath, 'message'));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typedBlock.type !== 'tool_use')
|
|
120
|
+
continue;
|
|
121
|
+
const name = asString(typedBlock.name);
|
|
122
|
+
const input = typedBlock.input || {};
|
|
123
|
+
const toolSource = name ? `tool:${name}` : 'tool';
|
|
124
|
+
const toolMeta = provenance(filePath, toolSource);
|
|
125
|
+
if (name === 'Bash') {
|
|
126
|
+
for (const url of extractUrls(input.command))
|
|
127
|
+
acc.urls.push(urlAsset(url, timestamp, toolMeta));
|
|
128
|
+
addToolPathAssets(acc, input.command, cwd, timestamp, toolMeta);
|
|
129
|
+
}
|
|
130
|
+
else if (name && PATH_TOOLS.has(name)) {
|
|
131
|
+
addToolPathAssets(acc, input.file_path, cwd, timestamp, toolMeta);
|
|
132
|
+
}
|
|
133
|
+
else if (name === 'WebFetch' || name === 'WebSearch') {
|
|
134
|
+
for (const url of extractUrls(JSON.stringify(input)))
|
|
135
|
+
acc.urls.push(urlAsset(url, timestamp, toolMeta));
|
|
136
|
+
}
|
|
137
|
+
else if (name === 'ExitPlanMode') {
|
|
138
|
+
const inline = asString(input.plan);
|
|
139
|
+
const markdown = inline?.trim() ? inline : readPlanFile(asString(input.planFilePath));
|
|
140
|
+
if (markdown?.trim())
|
|
141
|
+
acc.plans.push(planAsset(markdown, timestamp, toolMeta));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export function readClaudeAssets(cwd, limit = 3) {
|
|
147
|
+
const sessions = claudeSessions(cwd, limit);
|
|
148
|
+
const acc = {
|
|
149
|
+
urls: [],
|
|
150
|
+
paths: [],
|
|
151
|
+
commands: [],
|
|
152
|
+
plans: [],
|
|
153
|
+
hadSession: sessions.length > 0,
|
|
154
|
+
};
|
|
155
|
+
for (const session of sessions)
|
|
156
|
+
accumClaude(session, cwd, acc);
|
|
157
|
+
return acc;
|
|
158
|
+
}
|