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,83 @@
|
|
|
1
|
+
const AVAILABLE_STATUS = { available: true, kind: 'available' };
|
|
2
|
+
/** Small per-provider diagnostic state; it never stores transcript or environment contents. */
|
|
3
|
+
export class ProviderDiagnosticTracker {
|
|
4
|
+
providerId;
|
|
5
|
+
options;
|
|
6
|
+
lastEmittedSignature = null;
|
|
7
|
+
lastStatus = {
|
|
8
|
+
operation: 'construct',
|
|
9
|
+
usable: true,
|
|
10
|
+
degraded: false,
|
|
11
|
+
runtimeStatus: AVAILABLE_STATUS,
|
|
12
|
+
diagnostics: [],
|
|
13
|
+
};
|
|
14
|
+
constructor(providerId, options = {}) {
|
|
15
|
+
this.providerId = providerId;
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
available(operation) {
|
|
19
|
+
this.lastEmittedSignature = null;
|
|
20
|
+
this.lastStatus = {
|
|
21
|
+
operation,
|
|
22
|
+
usable: true,
|
|
23
|
+
degraded: false,
|
|
24
|
+
runtimeStatus: AVAILABLE_STATUS,
|
|
25
|
+
diagnostics: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
degraded(operation, runtimeStatus, diagnostic, usable = true) {
|
|
29
|
+
const value = { providerId: this.providerId, ...diagnostic };
|
|
30
|
+
this.lastStatus = {
|
|
31
|
+
operation,
|
|
32
|
+
usable,
|
|
33
|
+
degraded: true,
|
|
34
|
+
runtimeStatus,
|
|
35
|
+
diagnostics: [value],
|
|
36
|
+
};
|
|
37
|
+
const signature = `${value.kind}\0${value.severity}\0${value.phase}\0${value.message}`;
|
|
38
|
+
if (signature === this.lastEmittedSignature)
|
|
39
|
+
return;
|
|
40
|
+
this.lastEmittedSignature = signature;
|
|
41
|
+
try {
|
|
42
|
+
this.options.onDiagnostic?.(value);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Diagnostics are observational and cannot break provider operations.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
getLastOperationStatus() {
|
|
49
|
+
return {
|
|
50
|
+
...this.lastStatus,
|
|
51
|
+
runtimeStatus: { ...this.lastStatus.runtimeStatus },
|
|
52
|
+
diagnostics: [...this.lastStatus.diagnostics],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function diagnosticFromRuntimeStatus(providerId, phase, status) {
|
|
57
|
+
if (status.kind === 'available')
|
|
58
|
+
return null;
|
|
59
|
+
const kind = status.kind;
|
|
60
|
+
return {
|
|
61
|
+
providerId,
|
|
62
|
+
kind,
|
|
63
|
+
severity: status.kind === 'query_failed' ? 'error' : 'warning',
|
|
64
|
+
phase,
|
|
65
|
+
message: status.message ?? defaultRuntimeMessage(providerId, status.kind),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function defaultRuntimeMessage(providerId, kind) {
|
|
69
|
+
switch (kind) {
|
|
70
|
+
case 'home_unavailable':
|
|
71
|
+
return `${providerId} data home is unavailable.`;
|
|
72
|
+
case 'config_unreadable':
|
|
73
|
+
return `${providerId} configuration is unreadable.`;
|
|
74
|
+
case 'db_missing':
|
|
75
|
+
return `${providerId} database is unavailable; file fallback is active.`;
|
|
76
|
+
case 'sqlite_missing':
|
|
77
|
+
return 'The sqlite3 executable is unavailable; file fallback is active.';
|
|
78
|
+
case 'sqlite_blocked':
|
|
79
|
+
return 'The sqlite3 executable could not be started; file fallback is active.';
|
|
80
|
+
case 'query_failed':
|
|
81
|
+
return `${providerId} database query failed; fallback data may be incomplete.`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory listings keyed by path and validated by the directory's own
|
|
3
|
+
* mtime and inode.
|
|
4
|
+
*
|
|
5
|
+
* A create, delete, or rename inside a directory bumps that directory's
|
|
6
|
+
* mtime; an append to an existing file does not. So a listing can be reused
|
|
7
|
+
* for as long as the directory's mtime and inode are unchanged, and the per
|
|
8
|
+
* file size/mtime it carries is only refreshed by `refreshFile()` (the watch
|
|
9
|
+
* event path) or by `revalidateRecent` on catch-up passes. Full discovery is
|
|
10
|
+
* then one stat per directory instead of one readdir per directory plus one
|
|
11
|
+
* stat per file.
|
|
12
|
+
*
|
|
13
|
+
* @module providers/directoryListingCache
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
export const DEFAULT_DIRECTORY_CACHE_MAX_DIRECTORIES = 10_000;
|
|
18
|
+
export const DEFAULT_DIRECTORY_CACHE_RECENT_WINDOW_MS = 30 * 60_000;
|
|
19
|
+
/**
|
|
20
|
+
* A directory whose mtime is within this many milliseconds of the moment it
|
|
21
|
+
* was listed may still receive entries that land on the same coarse mtime
|
|
22
|
+
* (HFS+, exFAT, SMB), so it is re-listed rather than trusted.
|
|
23
|
+
*/
|
|
24
|
+
const COARSE_MTIME_GUARD_MS = 2_000;
|
|
25
|
+
export class DirectoryListingCache {
|
|
26
|
+
listings = new Map();
|
|
27
|
+
maxDirectories;
|
|
28
|
+
recentWindowMs;
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.maxDirectories = Math.max(1, Math.floor(options.maxDirectories ?? DEFAULT_DIRECTORY_CACHE_MAX_DIRECTORIES));
|
|
31
|
+
this.recentWindowMs = Math.max(0, options.recentWindowMs ?? DEFAULT_DIRECTORY_CACHE_RECENT_WINDOW_MS);
|
|
32
|
+
}
|
|
33
|
+
get size() {
|
|
34
|
+
return this.listings.size;
|
|
35
|
+
}
|
|
36
|
+
/** The cached listing without touching the filesystem, if any. */
|
|
37
|
+
peek(dir) {
|
|
38
|
+
return this.listings.get(dir);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* List `dir`, reusing the cached listing when the directory is unchanged.
|
|
42
|
+
* Returns null when `dir` is missing, unreadable, or not a directory.
|
|
43
|
+
*/
|
|
44
|
+
async listDirectory(dir, accept, options = {}) {
|
|
45
|
+
let dirStat = options.dirStat;
|
|
46
|
+
if (!dirStat) {
|
|
47
|
+
try {
|
|
48
|
+
dirStat = await fs.promises.stat(dir);
|
|
49
|
+
count(options.stats, 'directoriesStatted');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
this.listings.delete(dir);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!dirStat.isDirectory()) {
|
|
57
|
+
this.listings.delete(dir);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const cached = this.listings.get(dir);
|
|
61
|
+
const unchanged = cached !== undefined &&
|
|
62
|
+
cached.mtimeMs === dirStat.mtimeMs &&
|
|
63
|
+
cached.ino === dirStat.ino &&
|
|
64
|
+
dirStat.mtimeMs < cached.listedAtMs - COARSE_MTIME_GUARD_MS;
|
|
65
|
+
if (cached && unchanged) {
|
|
66
|
+
this.touch(dir, cached);
|
|
67
|
+
if (options.revalidateRecent)
|
|
68
|
+
await this.revalidateRecent(dir, cached, options.stats);
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
71
|
+
let entries;
|
|
72
|
+
try {
|
|
73
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
74
|
+
count(options.stats, 'directoriesListed');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
this.listings.delete(dir);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const listedAtMs = Date.now();
|
|
81
|
+
const files = new Map();
|
|
82
|
+
const subdirectories = [];
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
subdirectories.push(entry.name);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const isCandidateFile = entry.isFile() && accept(entry.name);
|
|
89
|
+
if (!isCandidateFile && !entry.isSymbolicLink())
|
|
90
|
+
continue;
|
|
91
|
+
const fullPath = path.join(dir, entry.name);
|
|
92
|
+
// A file already in the listing keeps its cached stat unless it is
|
|
93
|
+
// recent enough to be live; a directory change is a create, delete, or
|
|
94
|
+
// rename, and only the new names need a fresh stat.
|
|
95
|
+
const previous = cached?.files.get(entry.name);
|
|
96
|
+
if (isCandidateFile && previous && !this.isRecent(previous, listedAtMs)) {
|
|
97
|
+
files.set(entry.name, previous);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (entry.isSymbolicLink() && !accept(entry.name)) {
|
|
101
|
+
// Symlinked directories are followed so linked project folders count.
|
|
102
|
+
try {
|
|
103
|
+
const target = await fs.promises.stat(fullPath);
|
|
104
|
+
count(options.stats, 'directoriesStatted');
|
|
105
|
+
if (target.isDirectory())
|
|
106
|
+
subdirectories.push(entry.name);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Dangling link.
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const stat = await fs.promises.stat(fullPath);
|
|
115
|
+
count(options.stats, 'filesStatted');
|
|
116
|
+
if (stat.isFile())
|
|
117
|
+
files.set(entry.name, { sizeBytes: stat.size, mtimeMs: stat.mtimeMs });
|
|
118
|
+
else if (stat.isDirectory())
|
|
119
|
+
subdirectories.push(entry.name);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Vanished between readdir and stat.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const listing = {
|
|
126
|
+
mtimeMs: dirStat.mtimeMs,
|
|
127
|
+
ino: dirStat.ino,
|
|
128
|
+
listedAtMs,
|
|
129
|
+
files,
|
|
130
|
+
subdirectories,
|
|
131
|
+
};
|
|
132
|
+
this.touch(dir, listing);
|
|
133
|
+
return listing;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Stat one file and reflect the result in its parent's cached listing when
|
|
137
|
+
* that listing exists. Returns null when the path is missing or not a file.
|
|
138
|
+
* Never creates a listing: a partial listing must not pass for a full one.
|
|
139
|
+
*/
|
|
140
|
+
async refreshFile(fullPath, stats) {
|
|
141
|
+
const dir = path.dirname(fullPath);
|
|
142
|
+
const name = path.basename(fullPath);
|
|
143
|
+
const listing = this.listings.get(dir);
|
|
144
|
+
try {
|
|
145
|
+
const stat = await fs.promises.stat(fullPath);
|
|
146
|
+
count(stats, 'filesStatted');
|
|
147
|
+
if (!stat.isFile()) {
|
|
148
|
+
listing?.files.delete(name);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const fileStat = { sizeBytes: stat.size, mtimeMs: stat.mtimeMs };
|
|
152
|
+
listing?.files.set(name, fileStat);
|
|
153
|
+
return fileStat;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
listing?.files.delete(name);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Drop one directory's listing, or every listing. */
|
|
161
|
+
invalidate(dir) {
|
|
162
|
+
if (dir === undefined)
|
|
163
|
+
this.listings.clear();
|
|
164
|
+
else
|
|
165
|
+
this.listings.delete(dir);
|
|
166
|
+
}
|
|
167
|
+
async revalidateRecent(dir, listing, stats) {
|
|
168
|
+
const now = Date.now();
|
|
169
|
+
for (const [name, cached] of [...listing.files]) {
|
|
170
|
+
if (!this.isRecent(cached, now))
|
|
171
|
+
continue;
|
|
172
|
+
await this.refreshFile(path.join(dir, name), stats);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
isRecent(stat, now) {
|
|
176
|
+
return now - stat.mtimeMs <= this.recentWindowMs;
|
|
177
|
+
}
|
|
178
|
+
touch(dir, listing) {
|
|
179
|
+
this.listings.delete(dir);
|
|
180
|
+
this.listings.set(dir, listing);
|
|
181
|
+
while (this.listings.size > this.maxDirectories) {
|
|
182
|
+
const oldest = this.listings.keys().next().value;
|
|
183
|
+
if (oldest === undefined)
|
|
184
|
+
break;
|
|
185
|
+
this.listings.delete(oldest);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Split a watcher-reported root-relative path into segments. Both separators
|
|
191
|
+
* are accepted (Windows watchers report backslashes). Returns null for an
|
|
192
|
+
* empty path or one that escapes the root.
|
|
193
|
+
*/
|
|
194
|
+
export function splitWatchedPath(relativePath) {
|
|
195
|
+
const segments = relativePath.split(/[\\/]+/).filter((segment) => segment.length > 0);
|
|
196
|
+
if (segments.length === 0)
|
|
197
|
+
return null;
|
|
198
|
+
if (segments.some((segment) => segment === '.' || segment === '..'))
|
|
199
|
+
return null;
|
|
200
|
+
return segments;
|
|
201
|
+
}
|
|
202
|
+
function count(stats, field) {
|
|
203
|
+
if (!stats)
|
|
204
|
+
return;
|
|
205
|
+
stats[field] = (stats[field] ?? 0) + 1;
|
|
206
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ClaudeCodeProvider } from './claudeCode';
|
|
2
|
+
import { CodexProvider } from './codex';
|
|
3
|
+
import { OpenCodeProvider } from './openCode';
|
|
4
|
+
import { SESSION_PROVIDER_IDS } from './types';
|
|
5
|
+
/**
|
|
6
|
+
* Constructs built-in providers without probing the filesystem or binaries.
|
|
7
|
+
* A broken provider is isolated and reported instead of aborting host startup.
|
|
8
|
+
*/
|
|
9
|
+
export function createSessionProviders(options = {}) {
|
|
10
|
+
const diagnostics = [];
|
|
11
|
+
const diagnosticIndexes = new Map();
|
|
12
|
+
const providers = [];
|
|
13
|
+
const selected = new Set(options.providerIds ?? SESSION_PROVIDER_IDS);
|
|
14
|
+
const emit = (diagnostic) => {
|
|
15
|
+
const key = `${diagnostic.providerId}\0${diagnostic.kind}\0${diagnostic.phase}`;
|
|
16
|
+
const existingIndex = diagnosticIndexes.get(key);
|
|
17
|
+
if (existingIndex === undefined) {
|
|
18
|
+
diagnosticIndexes.set(key, diagnostics.length);
|
|
19
|
+
diagnostics.push(diagnostic);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
diagnostics[existingIndex] = diagnostic;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
options.onDiagnostic?.(diagnostic);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// A host diagnostic callback cannot break provider setup.
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
for (const providerId of SESSION_PROVIDER_IDS) {
|
|
32
|
+
if (!selected.has(providerId))
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
const providerOptions = { onDiagnostic: emit };
|
|
36
|
+
providers.push(providerId === 'claude-code'
|
|
37
|
+
? new ClaudeCodeProvider(providerOptions)
|
|
38
|
+
: providerId === 'opencode'
|
|
39
|
+
? new OpenCodeProvider(providerOptions)
|
|
40
|
+
: new CodexProvider(providerOptions));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
const diagnostic = {
|
|
44
|
+
providerId,
|
|
45
|
+
kind: 'construction_failed',
|
|
46
|
+
severity: 'error',
|
|
47
|
+
phase: 'construct',
|
|
48
|
+
message: `${providerId} could not be constructed and was omitted.`,
|
|
49
|
+
};
|
|
50
|
+
emit(diagnostic);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { providers, diagnostics };
|
|
54
|
+
}
|