sidekick-shared 0.23.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +24 -20
  2. package/dist/accountManager.js +10 -0
  3. package/dist/aggregation/EventAggregator.d.ts +4 -1
  4. package/dist/aggregation/EventAggregator.js +22 -9
  5. package/dist/aggregation/snapshot.d.ts +1 -1
  6. package/dist/aggregation/snapshot.js +2 -1
  7. package/dist/aggregation/types.d.ts +2 -0
  8. package/dist/autoSwitch.d.ts +1 -1
  9. package/dist/autoSwitch.js +14 -3
  10. package/dist/codexQuota.d.ts +2 -0
  11. package/dist/codexQuota.js +27 -0
  12. package/dist/codexQuotaWatcher.d.ts +7 -0
  13. package/dist/codexQuotaWatcher.js +37 -19
  14. package/dist/context/sessionContext.js +35 -9
  15. package/dist/credentialIO.js +19 -5
  16. package/dist/errorHistory.d.ts +1 -0
  17. package/dist/errorHistory.js +3 -2
  18. package/dist/extractors/toolCall.js +2 -2
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +9 -7
  21. package/dist/modelInfo.js +6 -0
  22. package/dist/multiProviderQuotaService.js +13 -2
  23. package/dist/parsers/codexParser.js +4 -1
  24. package/dist/parsers/sessionActivityDetector.js +2 -0
  25. package/dist/parsers/sessionPathResolver.js +19 -16
  26. package/dist/parsers/subagentScanner.d.ts +1 -1
  27. package/dist/parsers/subagentScanner.js +4 -5
  28. package/dist/parsers/subagentTraceParser.js +4 -7
  29. package/dist/providerStatus.js +13 -2
  30. package/dist/providers/claudeCode.js +18 -4
  31. package/dist/providers/codex.js +42 -13
  32. package/dist/providers/detect.js +32 -3
  33. package/dist/providers/openCodeDatabase.js +12 -3
  34. package/dist/quota.js +8 -0
  35. package/dist/quotaPoller.js +17 -6
  36. package/dist/quotaSnapshots.js +80 -26
  37. package/dist/readers/decisions.js +3 -1
  38. package/dist/readers/handoff.js +1 -1
  39. package/dist/readers/notes.js +9 -4
  40. package/dist/readers/plans.js +2 -4
  41. package/dist/readers/tasks.js +3 -1
  42. package/dist/report/htmlHelpers.js +6 -1
  43. package/dist/report/openBrowser.js +3 -3
  44. package/dist/statusline/formatter.js +8 -1
  45. package/dist/terminalSync.js +16 -7
  46. package/dist/types/historicalData.d.ts +14 -0
  47. package/dist/types/historicalData.js +22 -0
  48. package/dist/types/observedSessionV1.js +6 -5
  49. package/dist/types/sessionEvent.d.ts +1 -0
  50. package/dist/watchers/factory.js +14 -3
  51. package/dist/watchers/jsonlTail.js +34 -14
  52. package/dist/watchers/jsonlWatcher.js +5 -3
  53. package/dist/watchers/providerReaderWatcher.js +4 -2
  54. package/dist/watchers/sqliteWatcher.d.ts +2 -0
  55. package/dist/watchers/sqliteWatcher.js +27 -14
  56. package/dist/writers/atomic.js +100 -13
  57. package/dist/zaiQuotaWatcher.js +6 -0
  58. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { ErrorRollup, ErrorRollupEntry } from './aggregation/types';
2
2
  export declare const ERROR_HISTORY_SCHEMA_VERSION: 1;
3
+ export declare const MAX_ERROR_HISTORY_SESSIONS = 500;
3
4
  export interface ErrorHistorySessionRecord {
4
5
  sessionId: string;
5
6
  providerId: string;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ERROR_HISTORY_SCHEMA_VERSION = void 0;
36
+ exports.MAX_ERROR_HISTORY_SESSIONS = exports.ERROR_HISTORY_SCHEMA_VERSION = void 0;
37
37
  exports.appendErrorHistory = appendErrorHistory;
38
38
  exports.readErrorHistory = readErrorHistory;
39
39
  exports.getTopFailingTools = getTopFailingTools;
@@ -41,6 +41,7 @@ const fs = __importStar(require("fs"));
41
41
  const paths_1 = require("./paths");
42
42
  const atomic_1 = require("./writers/atomic");
43
43
  exports.ERROR_HISTORY_SCHEMA_VERSION = 1;
44
+ exports.MAX_ERROR_HISTORY_SESSIONS = 500;
44
45
  function emptyStore() {
45
46
  return {
46
47
  schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
@@ -59,7 +60,7 @@ async function appendErrorHistory(identity, rollup, filePath = (0, paths_1.getGl
59
60
  await (0, atomic_1.updateJsonStoreAtomic)(filePath, emptyStore, (store) => ({
60
61
  ...store,
61
62
  schemaVersion: exports.ERROR_HISTORY_SCHEMA_VERSION,
62
- sessions: [...store.sessions, record],
63
+ sessions: [...(Array.isArray(store.sessions) ? store.sessions : []), record].slice(-exports.MAX_ERROR_HISTORY_SESSIONS),
63
64
  lastSaved: record.endedAt,
64
65
  }));
65
66
  return record;
@@ -85,7 +85,7 @@ class ToolCallTracker {
85
85
  }
86
86
  const topLevel = extractToolCall(event);
87
87
  if (topLevel) {
88
- const id = event.message.id;
88
+ const id = event.message?.id;
89
89
  if (id) {
90
90
  topLevel.toolUseId = id;
91
91
  this.pending.set(id, topLevel);
@@ -94,7 +94,7 @@ class ToolCallTracker {
94
94
  completed.push(topLevel);
95
95
  }
96
96
  }
97
- const content = event.message.content;
97
+ const content = event.message?.content;
98
98
  if (!Array.isArray(content))
99
99
  return completed;
100
100
  for (const block of content) {
package/dist/index.d.ts CHANGED
@@ -11,8 +11,8 @@ export type { DecisionEntry, DecisionLogStore, DecisionSource } from './types/de
11
11
  export { DECISION_LOG_SCHEMA_VERSION } from './types/decisionLog';
12
12
  export type { KnowledgeNote, KnowledgeNoteStore, KnowledgeNoteType, KnowledgeNoteSource, KnowledgeNoteStatus, KnowledgeNoteImportance, } from './types/knowledgeNote';
13
13
  export { KNOWLEDGE_NOTE_SCHEMA_VERSION, IMPORTANCE_DECAY_FACTORS, STALENESS_THRESHOLDS, } from './types/knowledgeNote';
14
- export type { HistoricalDataStore, DailyData, MonthlyData, AllTimeStats, TokenTotals, ModelUsageRecord, ToolUsageRecord, SessionSummary, SessionHistoryRecord, } from './types/historicalData';
15
- export { HISTORICAL_DATA_SCHEMA_VERSION, HISTORICAL_SESSION_RETENTION_LIMIT, createEmptyTokenTotals, } from './types/historicalData';
14
+ export type { HistoricalDataStore, DailyData, HourlyData, MonthlyData, AllTimeStats, TokenTotals, ModelUsageRecord, ToolUsageRecord, SessionSummary, SessionHistoryRecord, } from './types/historicalData';
15
+ export { HISTORICAL_DATA_SCHEMA_VERSION, HISTORICAL_SESSION_RETENTION_LIMIT, createEmptyTokenTotals, createEmptyDataStore, } from './types/historicalData';
16
16
  export type { PersistedPlan, PersistedPlanStep, PlanHistoryStore, PlanStepStatus, PlanStatus, PlanSource, PlanStepComplexity, } from './types/plan';
17
17
  export { PLAN_SCHEMA_VERSION, MAX_PLANS_PER_PROJECT } from './types/plan';
18
18
  export { getConfigDir, getProjectDataPath, getGlobalDataPath, encodeWorkspacePath, getProjectSlug, getProjectSlugRaw, resolveProjectIdentity, } from './paths';
@@ -93,7 +93,7 @@ export type { SerializedAggregatorState } from './aggregation/EventAggregator';
93
93
  export { saveSnapshot, loadSnapshot, deleteSnapshot, isSnapshotValid, getSnapshotPath, } from './aggregation/snapshot';
94
94
  export type { SessionSnapshot } from './aggregation/snapshot';
95
95
  export type { EventAggregatorOptions, AggregatedTokens, ModelUsageStats, BurnRateInfo, SubagentLifecycle, AggregatedMetrics, FrequencyMetric, PatternMetric, HeatmapBucketMetric, ErrorRollup, ErrorRollupEntry, ErrorBucketMetric, } from './aggregation/types';
96
- export { ERROR_HISTORY_SCHEMA_VERSION, appendErrorHistory, getTopFailingTools, readErrorHistory, } from './errorHistory';
96
+ export { ERROR_HISTORY_SCHEMA_VERSION, MAX_ERROR_HISTORY_SESSIONS, appendErrorHistory, getTopFailingTools, readErrorHistory, } from './errorHistory';
97
97
  export type { ErrorHistorySessionRecord, ErrorHistoryStore, TopFailingTool } from './errorHistory';
98
98
  export { FrequencyTracker } from './aggregation/FrequencyTracker';
99
99
  export type { FrequencyEntry, SerializedFrequencyState } from './aggregation/FrequencyTracker';
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@
3
3
  * Public API for sidekick-shared.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.getOpenCodeDataDir = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.derivePendingUserRequestV1 = exports.createProviderSessionAdapterV1 = exports.renderHandoffUrlTemplate = exports.addNote = exports.decisionFingerprint = exports.addDecision = exports.completeTask = exports.addTask = exports.updateJsonStoreAtomic = exports.atomicWriteJsonSync = exports.atomicWriteJson = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.migrateLegacyProjectStores = exports.resolveProjectIdentity = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_SESSION_RETENTION_LIMIT = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = exports.isAggregateCodexLimit = void 0;
7
- exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.readSessionContextSnapshot = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = void 0;
8
- exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscriptFromEvents = exports.parseTranscript = exports.generateHtmlReport = exports.formatCompactionLedger = exports.calculateCompactionLedger = exports.calculateCodeImpact = exports.scoreSessionQuality = exports.calculateQualityTrend = exports.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.readErrorHistory = exports.getTopFailingTools = exports.appendErrorHistory = exports.ERROR_HISTORY_SCHEMA_VERSION = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.SessionMonitor = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = void 0;
9
- exports.installShellHook = exports.setTerminalActiveProfile = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.resolveActiveCodexAccount = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.readClaudeProfileIdentity = exports.isClaudeProfileAuthenticated = exports.claudeKeychainService = exports.claudeKeychainSuffix = exports.getClaudeProfileHome = exports.getClaudeProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.resolveClaudeLoginCommand = exports.listAllAccounts = exports.switchAccount = exports.spawnAccountLogin = exports.finalizeAccountLogin = exports.getAccountLoginStatus = exports.beginAccountLogin = exports.isMultiAccountEnabled = exports.resolveActiveClaudeAccount = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.reconcileClaudeAuthState = exports.applyActiveClaudeToLiveHome = exports.resolveActiveClaudeHome = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = void 0;
10
- exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.MultiProviderQuotaService = exports.resolveZaiQuota = exports.readZaiCredentials = exports.quotaStateFromZaiQuotaLimitPayload = exports.fetchZaiQuotaFromApi = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.fetchCodexResetCreditsFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.withQuotaProjections = exports.projectQuotaWindow = exports.fetchQuota = exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = exports.AutoSwitchController = exports.decideAutoSwitch = exports.DEFAULT_AUTO_SWITCH_CONFIG = exports.removeLauncher = exports.writeLauncher = exports.isShellHookInstalled = exports.uninstallShellHook = void 0;
11
- exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.ToolCallTracker = exports.extractToolCalls = exports.extractToolCall = exports.extractErrorMessage = exports.categorizeError = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = void 0;
12
- exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.runDoctor = exports.getSessionDiagnostics = exports.formatHealthReport = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = void 0;
6
+ exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.getOpenCodeDataDir = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.derivePendingUserRequestV1 = exports.createProviderSessionAdapterV1 = exports.renderHandoffUrlTemplate = exports.addNote = exports.decisionFingerprint = exports.addDecision = exports.completeTask = exports.addTask = exports.updateJsonStoreAtomic = exports.atomicWriteJsonSync = exports.atomicWriteJson = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.migrateLegacyProjectStores = exports.resolveProjectIdentity = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyDataStore = exports.createEmptyTokenTotals = exports.HISTORICAL_SESSION_RETENTION_LIMIT = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = exports.isAggregateCodexLimit = void 0;
7
+ exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.readSessionContextSnapshot = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = void 0;
8
+ exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscriptFromEvents = exports.parseTranscript = exports.generateHtmlReport = exports.formatCompactionLedger = exports.calculateCompactionLedger = exports.calculateCodeImpact = exports.scoreSessionQuality = exports.calculateQualityTrend = exports.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.readErrorHistory = exports.getTopFailingTools = exports.appendErrorHistory = exports.MAX_ERROR_HISTORY_SESSIONS = exports.ERROR_HISTORY_SCHEMA_VERSION = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.SessionMonitor = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = exports.toFollowEvents = void 0;
9
+ exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.resolveActiveCodexAccount = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.readClaudeProfileIdentity = exports.isClaudeProfileAuthenticated = exports.claudeKeychainService = exports.claudeKeychainSuffix = exports.getClaudeProfileHome = exports.getClaudeProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.resolveClaudeLoginCommand = exports.listAllAccounts = exports.switchAccount = exports.spawnAccountLogin = exports.finalizeAccountLogin = exports.getAccountLoginStatus = exports.beginAccountLogin = exports.isMultiAccountEnabled = exports.resolveActiveClaudeAccount = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.reconcileClaudeAuthState = exports.applyActiveClaudeToLiveHome = exports.resolveActiveClaudeHome = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = void 0;
10
+ exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.MultiProviderQuotaService = exports.resolveZaiQuota = exports.readZaiCredentials = exports.quotaStateFromZaiQuotaLimitPayload = exports.fetchZaiQuotaFromApi = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.fetchCodexResetCreditsFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.withQuotaProjections = exports.projectQuotaWindow = exports.fetchQuota = exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = exports.AutoSwitchController = exports.decideAutoSwitch = exports.DEFAULT_AUTO_SWITCH_CONFIG = exports.removeLauncher = exports.writeLauncher = exports.isShellHookInstalled = exports.uninstallShellHook = exports.installShellHook = exports.setTerminalActiveProfile = void 0;
11
+ exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.ToolCallTracker = exports.extractToolCalls = exports.extractToolCall = exports.extractErrorMessage = exports.categorizeError = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = void 0;
12
+ exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.runDoctor = exports.getSessionDiagnostics = exports.formatHealthReport = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = void 0;
13
13
  var codex_1 = require("./types/codex");
14
14
  Object.defineProperty(exports, "isAggregateCodexLimit", { enumerable: true, get: function () { return codex_1.isAggregateCodexLimit; } });
15
15
  var taskPersistence_1 = require("./types/taskPersistence");
@@ -25,6 +25,7 @@ var historicalData_1 = require("./types/historicalData");
25
25
  Object.defineProperty(exports, "HISTORICAL_DATA_SCHEMA_VERSION", { enumerable: true, get: function () { return historicalData_1.HISTORICAL_DATA_SCHEMA_VERSION; } });
26
26
  Object.defineProperty(exports, "HISTORICAL_SESSION_RETENTION_LIMIT", { enumerable: true, get: function () { return historicalData_1.HISTORICAL_SESSION_RETENTION_LIMIT; } });
27
27
  Object.defineProperty(exports, "createEmptyTokenTotals", { enumerable: true, get: function () { return historicalData_1.createEmptyTokenTotals; } });
28
+ Object.defineProperty(exports, "createEmptyDataStore", { enumerable: true, get: function () { return historicalData_1.createEmptyDataStore; } });
28
29
  var plan_1 = require("./types/plan");
29
30
  Object.defineProperty(exports, "PLAN_SCHEMA_VERSION", { enumerable: true, get: function () { return plan_1.PLAN_SCHEMA_VERSION; } });
30
31
  Object.defineProperty(exports, "MAX_PLANS_PER_PROJECT", { enumerable: true, get: function () { return plan_1.MAX_PLANS_PER_PROJECT; } });
@@ -213,6 +214,7 @@ Object.defineProperty(exports, "isSnapshotValid", { enumerable: true, get: funct
213
214
  Object.defineProperty(exports, "getSnapshotPath", { enumerable: true, get: function () { return snapshot_1.getSnapshotPath; } });
214
215
  var errorHistory_1 = require("./errorHistory");
215
216
  Object.defineProperty(exports, "ERROR_HISTORY_SCHEMA_VERSION", { enumerable: true, get: function () { return errorHistory_1.ERROR_HISTORY_SCHEMA_VERSION; } });
217
+ Object.defineProperty(exports, "MAX_ERROR_HISTORY_SESSIONS", { enumerable: true, get: function () { return errorHistory_1.MAX_ERROR_HISTORY_SESSIONS; } });
216
218
  Object.defineProperty(exports, "appendErrorHistory", { enumerable: true, get: function () { return errorHistory_1.appendErrorHistory; } });
217
219
  Object.defineProperty(exports, "getTopFailingTools", { enumerable: true, get: function () { return errorHistory_1.getTopFailingTools; } });
218
220
  Object.defineProperty(exports, "readErrorHistory", { enumerable: true, get: function () { return errorHistory_1.readErrorHistory; } });
package/dist/modelInfo.js CHANGED
@@ -142,6 +142,12 @@ const PRICING_TABLE = {
142
142
  cacheWriteCostPerMillion: 6.25,
143
143
  cacheReadCostPerMillion: 0.5,
144
144
  },
145
+ 'claude-opus-4-5': {
146
+ inputCostPerMillion: 5.0,
147
+ outputCostPerMillion: 25.0,
148
+ cacheWriteCostPerMillion: 6.25,
149
+ cacheReadCostPerMillion: 0.5,
150
+ },
145
151
  'claude-opus-4.5': {
146
152
  inputCostPerMillion: 5.0,
147
153
  outputCostPerMillion: 25.0,
@@ -174,7 +174,7 @@ class MultiProviderQuotaService {
174
174
  providerId: 'claude-code',
175
175
  });
176
176
  this.lastEmittedClaudeState = nextState;
177
- if (nextState.available) {
177
+ if (nextState.available && nextState.stale !== true) {
178
178
  this.lastSuccessfulClaudeState = nextState;
179
179
  }
180
180
  this.quotaByProvider = {
@@ -323,7 +323,18 @@ class MultiProviderQuotaService {
323
323
  }
324
324
  }
325
325
  this.cachedFallbackActive = hasVisibleCachedSuccess;
326
- if (!hasVisibleCachedSuccess) {
326
+ if (hasVisibleCachedSuccess) {
327
+ this.emitClaudeState({
328
+ ...this.lastSuccessfulClaudeState,
329
+ error: state.error,
330
+ failureKind: state.failureKind,
331
+ httpStatus: state.httpStatus,
332
+ retryAfterMs: state.retryAfterMs,
333
+ source: 'cache',
334
+ stale: true,
335
+ });
336
+ }
337
+ else {
327
338
  this.emitClaudeState(state);
328
339
  }
329
340
  }
@@ -782,7 +782,10 @@ class CodexRolloutParser {
782
782
  return [];
783
783
  const mappedUsage = usage
784
784
  ? {
785
- input_tokens: usage.input_tokens || 0,
785
+ // Codex reports cached_input_tokens as a subset of input_tokens,
786
+ // while MessageUsage categories are disjoint. Normalize once here
787
+ // so aggregation and pricing do not count cached input twice.
788
+ input_tokens: Math.max(0, (usage.input_tokens || 0) - (usage.cached_input_tokens || 0)),
786
789
  output_tokens: usage.output_tokens || 0,
787
790
  cache_read_input_tokens: usage.cached_input_tokens || 0,
788
791
  cache_creation_input_tokens: 0,
@@ -126,6 +126,8 @@ function detectSessionActivity(sessionPath) {
126
126
  }
127
127
  if (!hasTerminal) {
128
128
  for (const pattern of ENDING_PATTERNS) {
129
+ if (pattern === '"type":"user"' && line.includes('"type":"tool_result"'))
130
+ continue;
129
131
  if (line.includes(pattern)) {
130
132
  lastEndingIndex = i;
131
133
  break;
@@ -81,10 +81,9 @@ const os = __importStar(require("os"));
81
81
  function encodeWorkspacePath(workspacePath) {
82
82
  // Normalize path separators to forward slash
83
83
  const normalized = workspacePath.replace(/\\/g, '/');
84
- // Replace colons, slashes, and underscores with hyphens
85
- // Windows: C:\Users\foo_bar -> C:/Users/foo_bar -> C--Users-foo-bar
86
- // Unix: /home/user/foo_bar -> -home-user-foo-bar
87
- return normalized.replace(/[:/_]/g, '-');
84
+ // Claude Code replaces every non-alphanumeric path character, including
85
+ // dots and spaces, with a hyphen.
86
+ return normalized.replace(/[^a-zA-Z0-9]/g, '-');
88
87
  }
89
88
  /**
90
89
  * Gets the session directory path for a workspace.
@@ -249,12 +248,7 @@ function discoverSessionDirectory(workspacePath) {
249
248
  });
250
249
  // Try to match by workspace path components
251
250
  // Normalize workspace path for comparison
252
- const normalizedWorkspace = workspacePath
253
- .replace(/\\/g, '/')
254
- .replace(/:/g, '-')
255
- .replace(/_/g, '-')
256
- .replace(/\//g, '-')
257
- .toLowerCase();
251
+ const normalizedWorkspace = encodeWorkspacePath(workspacePath).toLowerCase();
258
252
  for (const dir of existingDirs) {
259
253
  // Check if the directory name matches (case-insensitive)
260
254
  if (dir.toLowerCase() === normalizedWorkspace) {
@@ -262,7 +256,10 @@ function discoverSessionDirectory(workspacePath) {
262
256
  }
263
257
  }
264
258
  // Fallback: match by final path component (project name)
265
- const workspaceBasename = path.basename(workspacePath).replace(/_/g, '-').toLowerCase();
259
+ const workspaceBasename = path
260
+ .basename(workspacePath)
261
+ .replace(/[^a-zA-Z0-9]/g, '-')
262
+ .toLowerCase();
266
263
  for (const dir of existingDirs) {
267
264
  const dirLower = dir.toLowerCase();
268
265
  // Check if dir ends with the project name
@@ -406,12 +403,18 @@ function findAllSessions(workspacePath) {
406
403
  .filter((file) => file.endsWith('.jsonl'))
407
404
  .map((file) => {
408
405
  const fullPath = path.join(sessionDir, file);
409
- const stats = fs.statSync(fullPath);
410
- return {
411
- path: fullPath,
412
- mtime: stats.mtime.getTime(),
413
- };
406
+ try {
407
+ const stats = fs.statSync(fullPath);
408
+ return {
409
+ path: fullPath,
410
+ mtime: stats.mtime.getTime(),
411
+ };
412
+ }
413
+ catch {
414
+ return null;
415
+ }
414
416
  })
417
+ .filter((file) => file !== null)
415
418
  .sort((a, b) => b.mtime - a.mtime)
416
419
  .map((file) => file.path);
417
420
  return files;
@@ -26,7 +26,7 @@ import type { SubagentStats, TaskState } from '../types/sessionEvent';
26
26
  * }
27
27
  * ```
28
28
  */
29
- export declare function scanSubagentDir(sessionDir: string, sessionId: string, logger?: (msg: string) => void): SubagentStats[];
29
+ export declare function scanSubagentDir(sessionDir: string, sessionId: string, logger?: (msg: string) => void, skipFiles?: ReadonlySet<string>): SubagentStats[];
30
30
  /**
31
31
  * Handles TaskUpdate tool input and updates task state.
32
32
  */
@@ -93,7 +93,7 @@ const reportedMissing = new Set();
93
93
  * }
94
94
  * ```
95
95
  */
96
- function scanSubagentDir(sessionDir, sessionId, logger) {
96
+ function scanSubagentDir(sessionDir, sessionId, logger, skipFiles = new Set()) {
97
97
  const log = logger || (() => { });
98
98
  const subagentsDir = path.join(sessionDir, sessionId, 'subagents');
99
99
  const results = [];
@@ -110,6 +110,8 @@ function scanSubagentDir(sessionDir, sessionId, logger) {
110
110
  const files = fs.readdirSync(subagentsDir);
111
111
  log(`[SubagentScanner] Found ${files.length} files: ${files.join(', ')}`);
112
112
  for (const file of files) {
113
+ if (skipFiles.has(file))
114
+ continue;
113
115
  const match = file.match(AGENT_FILE_PATTERN);
114
116
  if (!match) {
115
117
  continue;
@@ -169,10 +171,7 @@ function parseAgentFile(filePath, agentId, log) {
169
171
  // Extract token usage from assistant messages
170
172
  if (event.type === 'assistant' && event.message?.usage) {
171
173
  const usage = event.message.usage;
172
- inputTokens +=
173
- (usage.input_tokens || 0) +
174
- (usage.cache_creation_input_tokens || 0) +
175
- (usage.cache_read_input_tokens || 0);
174
+ inputTokens += usage.input_tokens || 0;
176
175
  outputTokens += usage.output_tokens || 0;
177
176
  }
178
177
  // Extract agent type and description from Task tool invocation
@@ -141,7 +141,7 @@ function parseAgentTrace(filePath, agentId) {
141
141
  }
142
142
  // Format tool summary
143
143
  let toolSummary;
144
- if (sessionEvent.type === 'tool_use' && sessionEvent.tool) {
144
+ if (sessionEvent.tool) {
145
145
  toolSummary = (0, toolSummary_1.formatToolSummary)(sessionEvent.tool.name, sessionEvent.tool.input);
146
146
  }
147
147
  events.push({
@@ -155,10 +155,7 @@ function parseAgentTrace(filePath, agentId) {
155
155
  // Extract token usage
156
156
  if (raw.type === 'assistant' && raw.message?.usage) {
157
157
  const usage = raw.message.usage;
158
- inputTokens +=
159
- (usage.input_tokens || 0) +
160
- (usage.cache_creation_input_tokens || 0) +
161
- (usage.cache_read_input_tokens || 0);
158
+ inputTokens += usage.input_tokens || 0;
162
159
  outputTokens += usage.output_tokens || 0;
163
160
  }
164
161
  // Extract tool calls
@@ -251,7 +248,7 @@ function linkTraces(traces) {
251
248
  for (const match of matches) {
252
249
  const childId = match[1];
253
250
  const child = traceById.get(childId);
254
- if (child && child !== trace) {
251
+ if (child && child !== trace && !child.parentToolUseId) {
255
252
  child.parentToolUseId = trace.agentId;
256
253
  trace.children.push(child);
257
254
  }
@@ -274,7 +271,7 @@ function linkTraces(traces) {
274
271
  continue; // Already linked
275
272
  for (const traceEvent of potential.events) {
276
273
  const evt = traceEvent.event;
277
- if (evt.type === 'tool_use' && evt.tool?.name === 'Task') {
274
+ if (evt.type === 'assistant' && evt.tool?.name === 'Task') {
278
275
  const taskTime = new Date(evt.timestamp).getTime();
279
276
  // Within 2 seconds before child start
280
277
  if (taskTime <= childStart && childStart - taskTime < 2000) {
@@ -24,7 +24,7 @@ function fallbackState() {
24
24
  */
25
25
  async function fetchStatusPage(baseUrl) {
26
26
  try {
27
- const statusRes = await fetch(`${baseUrl}/api/v2/status.json`);
27
+ const statusRes = await fetchWithTimeout(`${baseUrl}/api/v2/status.json`);
28
28
  if (!statusRes.ok)
29
29
  return fallbackState();
30
30
  const statusData = await statusRes.json();
@@ -36,7 +36,7 @@ async function fetchStatusPage(baseUrl) {
36
36
  return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
37
37
  }
38
38
  // Degraded — fetch summary for components + incidents
39
- const summaryRes = await fetch(`${baseUrl}/api/v2/summary.json`);
39
+ const summaryRes = await fetchWithTimeout(`${baseUrl}/api/v2/summary.json`);
40
40
  if (!summaryRes.ok) {
41
41
  return { indicator, description, affectedComponents: [], activeIncident: null, updatedAt };
42
42
  }
@@ -61,6 +61,17 @@ async function fetchStatusPage(baseUrl) {
61
61
  return fallbackState();
62
62
  }
63
63
  }
64
+ async function fetchWithTimeout(url) {
65
+ const controller = new AbortController();
66
+ const timeout = setTimeout(() => controller.abort(), 10_000);
67
+ timeout.unref();
68
+ try {
69
+ return await fetch(url, { signal: controller.signal });
70
+ }
71
+ finally {
72
+ clearTimeout(timeout);
73
+ }
74
+ }
64
75
  /**
65
76
  * Fetch current Claude API status from status.claude.com.
66
77
  *
@@ -46,6 +46,7 @@ exports.ClaudeCodeProvider = void 0;
46
46
  const fs = __importStar(require("fs"));
47
47
  const os = __importStar(require("os"));
48
48
  const path = __importStar(require("path"));
49
+ const string_decoder_1 = require("string_decoder");
49
50
  const sessionContext_1 = require("../context/sessionContext");
50
51
  const jsonl_1 = require("../parsers/jsonl");
51
52
  const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
@@ -97,6 +98,7 @@ class ClaudeCodeReader {
97
98
  filePosition = 0;
98
99
  events = [];
99
100
  _wasTruncated = false;
101
+ decoder = new string_decoder_1.StringDecoder('utf8');
100
102
  constructor(sessionPath) {
101
103
  this.sessionPath = sessionPath;
102
104
  this.parser = new jsonl_1.JsonlParser({
@@ -120,6 +122,7 @@ class ClaudeCodeReader {
120
122
  this._wasTruncated = true;
121
123
  this.filePosition = 0;
122
124
  this.parser.reset();
125
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
123
126
  }
124
127
  // No new content
125
128
  if (currentSize <= this.filePosition) {
@@ -129,11 +132,16 @@ class ClaudeCodeReader {
129
132
  const fd = fs.openSync(this.sessionPath, 'r');
130
133
  const bufferSize = currentSize - this.filePosition;
131
134
  const buffer = Buffer.alloc(bufferSize);
132
- fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
133
- fs.closeSync(fd);
134
- const chunk = buffer.toString('utf-8');
135
+ let bytesRead = 0;
136
+ try {
137
+ bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
138
+ }
139
+ finally {
140
+ fs.closeSync(fd);
141
+ }
142
+ const chunk = this.decoder.write(buffer.subarray(0, bytesRead));
135
143
  this.parser.processChunk(chunk);
136
- this.filePosition = currentSize;
144
+ this.filePosition += bytesRead;
137
145
  }
138
146
  catch (error) {
139
147
  console.error(`ClaudeCodeReader: error reading ${this.sessionPath}: ${error}`);
@@ -147,13 +155,18 @@ class ClaudeCodeReader {
147
155
  reset() {
148
156
  this.filePosition = 0;
149
157
  this.parser.reset();
158
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
150
159
  this._wasTruncated = false;
151
160
  }
152
161
  exists() {
153
162
  return fs.existsSync(this.sessionPath);
154
163
  }
155
164
  flush() {
165
+ const finalChunk = this.decoder.end();
166
+ if (finalChunk)
167
+ this.parser.processChunk(finalChunk);
156
168
  this.parser.flush();
169
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
157
170
  }
158
171
  getPosition() {
159
172
  return this.filePosition;
@@ -161,6 +174,7 @@ class ClaudeCodeReader {
161
174
  seekTo(position) {
162
175
  this.filePosition = position;
163
176
  this.parser.reset();
177
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
164
178
  }
165
179
  wasTruncated() {
166
180
  return this._wasTruncated;
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.CodexProvider = void 0;
47
47
  const fs = __importStar(require("fs"));
48
48
  const path = __importStar(require("path"));
49
+ const string_decoder_1 = require("string_decoder");
49
50
  const EventAggregator_1 = require("../aggregation/EventAggregator");
50
51
  const sessionContext_1 = require("../context/sessionContext");
51
52
  const codexParser_1 = require("../parsers/codexParser");
@@ -131,6 +132,23 @@ function findRolloutFiles(dir) {
131
132
  }
132
133
  return results;
133
134
  }
135
+ function hasRolloutFile(dir) {
136
+ try {
137
+ if (!fs.existsSync(dir))
138
+ return false;
139
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
140
+ const fullPath = path.join(dir, entry.name);
141
+ if (entry.isDirectory() && hasRolloutFile(fullPath))
142
+ return true;
143
+ if (entry.isFile() && isRolloutFile(entry.name))
144
+ return true;
145
+ }
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ return false;
151
+ }
134
152
  function findRolloutFilesInConfiguredHomes() {
135
153
  const results = [];
136
154
  const seen = new Set();
@@ -148,7 +166,7 @@ function findRolloutFilesInConfiguredHomes() {
148
166
  function getSessionsDir() {
149
167
  const dirs = getSessionsDirs();
150
168
  for (const dir of dirs) {
151
- if (findRolloutFiles(dir).length > 0) {
169
+ if (hasRolloutFile(dir)) {
152
170
  return dir;
153
171
  }
154
172
  }
@@ -430,6 +448,7 @@ class CodexReader {
430
448
  filePosition = 0;
431
449
  lineBuffer = '';
432
450
  _wasTruncated = false;
451
+ decoder = new string_decoder_1.StringDecoder('utf8');
433
452
  constructor(rolloutPath, onContextWindowLimit, onRateLimits, onTokenUsage) {
434
453
  this.rolloutPath = rolloutPath;
435
454
  this.onContextWindowLimit = onContextWindowLimit;
@@ -455,6 +474,7 @@ class CodexReader {
455
474
  this.filePosition = 0;
456
475
  this.lineBuffer = '';
457
476
  this.parser.reset();
477
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
458
478
  }
459
479
  // No new content
460
480
  if (currentSize <= this.filePosition)
@@ -463,10 +483,15 @@ class CodexReader {
463
483
  const fd = fs.openSync(this.rolloutPath, 'r');
464
484
  const bufferSize = currentSize - this.filePosition;
465
485
  const buffer = Buffer.alloc(bufferSize);
466
- fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
467
- fs.closeSync(fd);
468
- const chunk = buffer.toString('utf-8');
469
- this.filePosition = currentSize;
486
+ let bytesRead = 0;
487
+ try {
488
+ bytesRead = fs.readSync(fd, buffer, 0, bufferSize, this.filePosition);
489
+ }
490
+ finally {
491
+ fs.closeSync(fd);
492
+ }
493
+ const chunk = this.decoder.write(buffer.subarray(0, bytesRead));
494
+ this.filePosition += bytesRead;
470
495
  // Process lines
471
496
  const text = this.lineBuffer + chunk;
472
497
  const lines = text.split('\n');
@@ -497,11 +522,12 @@ class CodexReader {
497
522
  // Propagate last token usage snapshot to the provider
498
523
  const lastUsage = this.parser.getLastTokenUsage();
499
524
  if (lastUsage && this.onTokenUsage) {
525
+ const cacheReadTokens = lastUsage.cached_input_tokens || 0;
500
526
  this.onTokenUsage({
501
- inputTokens: lastUsage.input_tokens || 0,
527
+ inputTokens: Math.max(0, (lastUsage.input_tokens || 0) - cacheReadTokens),
502
528
  outputTokens: lastUsage.output_tokens || 0,
503
529
  cacheWriteTokens: 0,
504
- cacheReadTokens: lastUsage.cached_input_tokens || 0,
530
+ cacheReadTokens,
505
531
  reasoningTokens: lastUsage.reasoning_output_tokens || 0,
506
532
  model: this.parser.getCurrentModel() || 'unknown',
507
533
  timestamp: new Date(),
@@ -521,12 +547,14 @@ class CodexReader {
521
547
  this.filePosition = 0;
522
548
  this.lineBuffer = '';
523
549
  this.parser.reset();
550
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
524
551
  this._wasTruncated = false;
525
552
  }
526
553
  exists() {
527
554
  return fs.existsSync(this.rolloutPath);
528
555
  }
529
556
  flush() {
557
+ this.lineBuffer += this.decoder.end();
530
558
  // Process any remaining data in the line buffer
531
559
  if (this.lineBuffer.trim()) {
532
560
  try {
@@ -537,6 +565,7 @@ class CodexReader {
537
565
  // Not a complete line yet
538
566
  }
539
567
  }
568
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
540
569
  }
541
570
  getPosition() {
542
571
  return this.filePosition;
@@ -544,6 +573,7 @@ class CodexReader {
544
573
  seekTo(position) {
545
574
  this.filePosition = position;
546
575
  this.lineBuffer = '';
576
+ this.decoder = new string_decoder_1.StringDecoder('utf8');
547
577
  }
548
578
  wasTruncated() {
549
579
  return this._wasTruncated;
@@ -675,9 +705,10 @@ class CodexProvider {
675
705
  const db = this.ensureDb();
676
706
  if (db) {
677
707
  const cwdStats = db.getAllDistinctCwds();
708
+ const sessionsDir = getSessionsDir();
678
709
  for (const stat of cwdStats) {
679
710
  seenCwds.set(stat.cwd, {
680
- dir: getSessionsDir(),
711
+ dir: sessionsDir,
681
712
  name: stat.cwd,
682
713
  encodedName: stat.cwd,
683
714
  sessionCount: stat.count,
@@ -971,11 +1002,9 @@ class CodexProvider {
971
1002
  return this.lastTokenUsageData;
972
1003
  }
973
1004
  computeContextSize(usage) {
974
- // OpenAI's input_tokens already includes cached_input_tokens (it's a subset,
975
- // not additive like Anthropic's model). So inputTokens alone represents the
976
- // full context window usage. This differs from OpenCode's formula which adds
977
- // cacheWrite + cacheRead because Anthropic reports them separately.
978
- return usage.inputTokens;
1005
+ // Codex usage is normalized to the disjoint TokenUsage shape at the reader
1006
+ // boundary, so reconstruct the full prompt/context from uncached + cached.
1007
+ return usage.inputTokens + usage.cacheReadTokens;
979
1008
  }
980
1009
  // --- Rate limits (for vscode wrapper to build QuotaState) ---
981
1010
  /** Get the last rate limits data received from token_count events. */