sidekick-shared 0.27.6 → 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.
Files changed (269) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +58 -18
  3. package/dist/browser.d.ts +13 -2
  4. package/dist/browser.js +30 -2
  5. package/dist/claudePaths.d.ts +11 -0
  6. package/dist/claudePaths.js +61 -0
  7. package/dist/claudeProfiles.d.ts +2 -9
  8. package/dist/claudeProfiles.js +10 -25
  9. package/dist/codexPaths.d.ts +22 -0
  10. package/dist/codexPaths.js +67 -0
  11. package/dist/codexProfiles.d.ts +1 -4
  12. package/dist/codexProfiles.js +5 -43
  13. package/dist/doctor.js +2 -2
  14. package/dist/esm/accountChangeSignal.js +15 -0
  15. package/dist/esm/accountChanges.js +152 -0
  16. package/dist/esm/accountHealth.js +288 -0
  17. package/dist/esm/accountLaunch.js +145 -0
  18. package/dist/esm/accountLease.js +60 -0
  19. package/dist/esm/accountManager.js +606 -0
  20. package/dist/esm/accountRegistry.js +214 -0
  21. package/dist/esm/accountStatus.js +51 -0
  22. package/dist/esm/accountSwitch.js +74 -0
  23. package/dist/esm/accountSync.js +156 -0
  24. package/dist/esm/accountSyncTypes.js +4 -0
  25. package/dist/esm/accounts.js +1291 -0
  26. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  27. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  28. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  29. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  30. package/dist/esm/aggregation/costProvenance.js +31 -0
  31. package/dist/esm/aggregation/snapshot.js +161 -0
  32. package/dist/esm/aggregation/types.js +5 -0
  33. package/dist/esm/analytics/codeImpact.js +18 -0
  34. package/dist/esm/analytics/compactionLedger.js +37 -0
  35. package/dist/esm/analytics/qualityScore.js +112 -0
  36. package/dist/esm/autoSwitch.js +185 -0
  37. package/dist/esm/browser.js +34 -0
  38. package/dist/esm/claudeCredentialDoctor.js +239 -0
  39. package/dist/esm/claudeCredentialLedger.js +91 -0
  40. package/dist/esm/claudeCredentials.js +111 -0
  41. package/dist/esm/claudeLiveGuard.js +43 -0
  42. package/dist/esm/claudePaths.js +22 -0
  43. package/dist/esm/claudeProfiles.js +142 -0
  44. package/dist/esm/claudeRefreshLock.js +106 -0
  45. package/dist/esm/codexAuth.js +101 -0
  46. package/dist/esm/codexCredentialDoctor.js +265 -0
  47. package/dist/esm/codexCredentialStore.js +164 -0
  48. package/dist/esm/codexKeepAlive.js +112 -0
  49. package/dist/esm/codexPaths.js +172 -0
  50. package/dist/esm/codexProfiles.js +1251 -0
  51. package/dist/esm/codexQuota.js +564 -0
  52. package/dist/esm/codexQuotaWatcher.js +363 -0
  53. package/dist/esm/context/composer.js +71 -0
  54. package/dist/esm/context/sessionContext.js +546 -0
  55. package/dist/esm/credentialIO.js +283 -0
  56. package/dist/esm/credentials.js +49 -0
  57. package/dist/esm/doctor.js +305 -0
  58. package/dist/esm/ensureDefaultAccounts.js +62 -0
  59. package/dist/esm/errorHistory.js +56 -0
  60. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  61. package/dist/esm/extractors/gatherAssets.js +32 -0
  62. package/dist/esm/extractors/sessionAssets.js +176 -0
  63. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  64. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  65. package/dist/esm/extractors/tokenUsage.js +41 -0
  66. package/dist/esm/extractors/toolCall.js +142 -0
  67. package/dist/esm/failingTools.js +65 -0
  68. package/dist/esm/fileHead.js +27 -0
  69. package/dist/esm/formatters/eventHighlighter.js +332 -0
  70. package/dist/esm/formatters/noiseClassifier.js +209 -0
  71. package/dist/esm/formatters/sessionDump.js +328 -0
  72. package/dist/esm/formatters/toolSummary.js +226 -0
  73. package/dist/esm/formatting.js +82 -0
  74. package/dist/esm/handoffUrl.js +23 -0
  75. package/dist/esm/historicalStore.js +305 -0
  76. package/dist/esm/humanPrompt.js +88 -0
  77. package/dist/esm/index.js +213 -0
  78. package/dist/esm/keepAlive.js +189 -0
  79. package/dist/esm/liveCredentialGuard.js +33 -0
  80. package/dist/esm/modelAliases.js +51 -0
  81. package/dist/esm/modelCatalog.js +139 -0
  82. package/dist/esm/modelContext.js +280 -0
  83. package/dist/esm/modelInfo.js +832 -0
  84. package/dist/esm/multiProviderQuotaService.js +450 -0
  85. package/dist/esm/node.js +46 -0
  86. package/dist/esm/observedContextWindows.js +165 -0
  87. package/dist/esm/observedSessionCollector.js +939 -0
  88. package/dist/esm/parsers/changelogParser.js +71 -0
  89. package/dist/esm/parsers/codexHistory.js +89 -0
  90. package/dist/esm/parsers/codexParser.js +906 -0
  91. package/dist/esm/parsers/debugLogParser.js +126 -0
  92. package/dist/esm/parsers/jsonl.js +66 -0
  93. package/dist/esm/parsers/mcpToolName.js +28 -0
  94. package/dist/esm/parsers/openCodeParser.js +625 -0
  95. package/dist/esm/parsers/planExtractor.js +335 -0
  96. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  97. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  98. package/dist/esm/parsers/subagentScanner.js +360 -0
  99. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  100. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  101. package/dist/esm/paths.js +134 -0
  102. package/dist/esm/peakHours.js +194 -0
  103. package/dist/esm/phrases.js +1330 -0
  104. package/dist/esm/pricingCatalog.js +247 -0
  105. package/dist/esm/processDetection.js +200 -0
  106. package/dist/esm/projectMigration.js +59 -0
  107. package/dist/esm/promptHistory.js +1221 -0
  108. package/dist/esm/providerFailure.js +362 -0
  109. package/dist/esm/providerIds.js +6 -0
  110. package/dist/esm/providerQuota.js +1 -0
  111. package/dist/esm/providerServiceStatus.js +145 -0
  112. package/dist/esm/providerServiceStatusTypes.js +1 -0
  113. package/dist/esm/providerStatus.js +117 -0
  114. package/dist/esm/providers/claudeCode.js +575 -0
  115. package/dist/esm/providers/claudeListing.js +121 -0
  116. package/dist/esm/providers/codex.js +1034 -0
  117. package/dist/esm/providers/codexDatabase.js +297 -0
  118. package/dist/esm/providers/codexRollouts.js +215 -0
  119. package/dist/esm/providers/detect.js +185 -0
  120. package/dist/esm/providers/diagnostics.js +83 -0
  121. package/dist/esm/providers/directoryListingCache.js +206 -0
  122. package/dist/esm/providers/factory.js +54 -0
  123. package/dist/esm/providers/openCode.js +2008 -0
  124. package/dist/esm/providers/openCodeDatabase.js +419 -0
  125. package/dist/esm/providers/rolloutWalker.js +252 -0
  126. package/dist/esm/providers/types.js +12 -0
  127. package/dist/esm/quota.js +182 -0
  128. package/dist/esm/quotaHistory.js +364 -0
  129. package/dist/esm/quotaPoller.js +230 -0
  130. package/dist/esm/quotaPresentation.js +97 -0
  131. package/dist/esm/quotaResolver.js +231 -0
  132. package/dist/esm/quotaSnapshots.js +130 -0
  133. package/dist/esm/quotaThresholds.js +85 -0
  134. package/dist/esm/readers/decisions.js +24 -0
  135. package/dist/esm/readers/handoff.js +20 -0
  136. package/dist/esm/readers/helpers.js +27 -0
  137. package/dist/esm/readers/history.js +9 -0
  138. package/dist/esm/readers/notes.js +48 -0
  139. package/dist/esm/readers/plans.js +225 -0
  140. package/dist/esm/readers/tasks.js +19 -0
  141. package/dist/esm/report/htmlHelpers.js +280 -0
  142. package/dist/esm/report/htmlReportGenerator.js +704 -0
  143. package/dist/esm/report/index.js +7 -0
  144. package/dist/esm/report/logo.js +2 -0
  145. package/dist/esm/report/openBrowser.js +19 -0
  146. package/dist/esm/report/sessionReportInputs.js +33 -0
  147. package/dist/esm/report/transcriptParser.js +83 -0
  148. package/dist/esm/report/types.js +4 -0
  149. package/dist/esm/schemas/accountManager.js +204 -0
  150. package/dist/esm/schemas/accountStatus.js +25 -0
  151. package/dist/esm/schemas/assistantTurn.js +74 -0
  152. package/dist/esm/schemas/index.js +25 -0
  153. package/dist/esm/schemas/observedSessionV1.js +77 -0
  154. package/dist/esm/schemas/providerFailure.js +37 -0
  155. package/dist/esm/schemas/quota.js +132 -0
  156. package/dist/esm/schemas/quotaHistory.js +40 -0
  157. package/dist/esm/schemas/sessionEvent.js +216 -0
  158. package/dist/esm/schemas/stateFile.js +75 -0
  159. package/dist/esm/schemas/tokenEstimation.js +7 -0
  160. package/dist/esm/schemas/transcript.js +86 -0
  161. package/dist/esm/schemas/usageNormalization.js +38 -0
  162. package/dist/esm/search/advancedFilter.js +197 -0
  163. package/dist/esm/search/sessionSearch.js +66 -0
  164. package/dist/esm/sessionFingerprint.js +40 -0
  165. package/dist/esm/sessionMonitor.js +198 -0
  166. package/dist/esm/sessionPreviews.js +522 -0
  167. package/dist/esm/sessionPromptHistory.js +254 -0
  168. package/dist/esm/sessionPromptSignals.js +364 -0
  169. package/dist/esm/sessionStats.js +160 -0
  170. package/dist/esm/sessionTokenTotals.js +98 -0
  171. package/dist/esm/sessionTranscripts.js +49 -0
  172. package/dist/esm/stateFile.js +119 -0
  173. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  174. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  175. package/dist/esm/statusline/formatter.js +122 -0
  176. package/dist/esm/statusline/index.js +8 -0
  177. package/dist/esm/terminalSync.js +95 -0
  178. package/dist/esm/testing/credentialGuard.js +197 -0
  179. package/dist/esm/testing/index.js +12 -0
  180. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  181. package/dist/esm/tokenEstimation.js +67 -0
  182. package/dist/esm/tokenSummary.js +113 -0
  183. package/dist/esm/toolCategory.js +82 -0
  184. package/dist/esm/transcript.js +257 -0
  185. package/dist/esm/turns/assistantTurn.js +373 -0
  186. package/dist/esm/types/codex.js +23 -0
  187. package/dist/esm/types/decisionLog.js +5 -0
  188. package/dist/esm/types/historicalData.js +47 -0
  189. package/dist/esm/types/knowledgeNote.js +15 -0
  190. package/dist/esm/types/observedSessionV1.js +257 -0
  191. package/dist/esm/types/opencode.js +12 -0
  192. package/dist/esm/types/plan.js +7 -0
  193. package/dist/esm/types/sessionEvent.js +10 -0
  194. package/dist/esm/types/taskPersistence.js +12 -0
  195. package/dist/esm/usage/billingBlocks.js +148 -0
  196. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  197. package/dist/esm/usage/importSessionHistory.js +84 -0
  198. package/dist/esm/usage/subagentUsage.js +18 -0
  199. package/dist/esm/usage/usageEvents.js +402 -0
  200. package/dist/esm/usage/usageReports.js +202 -0
  201. package/dist/esm/usageNormalization.js +95 -0
  202. package/dist/esm/watchers/eventBridge.js +219 -0
  203. package/dist/esm/watchers/factory.js +63 -0
  204. package/dist/esm/watchers/jsonlTail.js +289 -0
  205. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  206. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  207. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  208. package/dist/esm/watchers/types.js +4 -0
  209. package/dist/esm/workspaceRoots.js +56 -0
  210. package/dist/esm/writers/atomic.js +375 -0
  211. package/dist/esm/writers/decisions.js +51 -0
  212. package/dist/esm/writers/notes.js +46 -0
  213. package/dist/esm/writers/tasks.js +63 -0
  214. package/dist/esm/zaiQuota.js +323 -0
  215. package/dist/esm/zaiQuotaApi.js +271 -0
  216. package/dist/esm/zaiQuotaWatcher.js +265 -0
  217. package/dist/fileHead.d.ts +22 -0
  218. package/dist/fileHead.js +33 -0
  219. package/dist/index.d.ts +16 -7
  220. package/dist/index.js +49 -15
  221. package/dist/node.d.ts +13 -1
  222. package/dist/node.js +43 -2
  223. package/dist/parsers/codexHistory.js +2 -2
  224. package/dist/parsers/codexParser.d.ts +12 -0
  225. package/dist/parsers/codexParser.js +34 -12
  226. package/dist/parsers/debugLogParser.js +2 -2
  227. package/dist/parsers/sessionPathResolver.js +5 -4
  228. package/dist/promptHistory.d.ts +32 -0
  229. package/dist/promptHistory.js +115 -59
  230. package/dist/providerFailure.d.ts +12 -6
  231. package/dist/providerFailure.js +45 -0
  232. package/dist/providers/claudeCode.js +7 -99
  233. package/dist/providers/claudeListing.d.ts +22 -0
  234. package/dist/providers/claudeListing.js +158 -0
  235. package/dist/providers/codex.d.ts +3 -1
  236. package/dist/providers/codex.js +26 -227
  237. package/dist/providers/codexRollouts.d.ts +44 -0
  238. package/dist/providers/codexRollouts.js +261 -0
  239. package/dist/providers/detect.js +4 -4
  240. package/dist/readers/plans.js +3 -3
  241. package/dist/schemas/index.d.ts +2 -0
  242. package/dist/schemas/index.js +7 -2
  243. package/dist/schemas/providerFailure.d.ts +154 -0
  244. package/dist/schemas/providerFailure.js +40 -0
  245. package/dist/sessionPromptHistory.d.ts +12 -7
  246. package/dist/sessionPromptHistory.js +15 -3
  247. package/dist/sessionPromptSignals.d.ts +9 -0
  248. package/dist/sessionPromptSignals.js +13 -0
  249. package/dist/testing/credentialGuard.d.ts +23 -0
  250. package/dist/testing/credentialGuard.js +233 -0
  251. package/dist/testing/index.d.ts +14 -0
  252. package/dist/testing/index.js +22 -0
  253. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  254. package/dist/testing/sessionLogFixtures.js +169 -0
  255. package/dist/tokenEstimation.d.ts +2 -0
  256. package/dist/tokenEstimation.js +21 -11
  257. package/dist/toolCategory.d.ts +18 -0
  258. package/dist/toolCategory.js +86 -0
  259. package/dist/types/codex.d.ts +4 -0
  260. package/dist/usage/usageEvents.d.ts +16 -0
  261. package/dist/usage/usageEvents.js +53 -9
  262. package/dist/watchers/jsonlTail.d.ts +26 -1
  263. package/dist/watchers/jsonlTail.js +136 -28
  264. package/dist/watchers/jsonlWatcher.js +7 -0
  265. package/dist/watchers/providerReaderWatcher.js +1 -0
  266. package/dist/watchers/types.d.ts +6 -0
  267. package/dist/workspaceRoots.d.ts +23 -0
  268. package/dist/workspaceRoots.js +93 -0
  269. package/package.json +22 -2
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Code session-file listing, kept apart from the provider class so a
4
+ * caller that only lists files does not bundle the provider's stats,
5
+ * context and transcript code.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.workspaceDirectoryFilter = workspaceDirectoryFilter;
42
+ exports.listClaudeSessionFilesAsync = listClaudeSessionFilesAsync;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const claudePaths_1 = require("../claudePaths");
46
+ const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
47
+ const directoryListingCache_1 = require("./directoryListingCache");
48
+ /** Matches project directory names that may hold sessions for `workspacePath`. */
49
+ function workspaceDirectoryFilter(workspacePath) {
50
+ if (!workspacePath)
51
+ return () => true;
52
+ const encodedWorkspace = (0, sessionPathResolver_1.encodeWorkspacePath)(workspacePath).toLowerCase();
53
+ const workspaceBasename = path
54
+ .basename(workspacePath)
55
+ .replace(/[^a-zA-Z0-9]/g, '-')
56
+ .toLowerCase();
57
+ return (directoryName) => {
58
+ const name = directoryName.toLowerCase();
59
+ return (name === encodedWorkspace ||
60
+ name.startsWith(`${encodedWorkspace}-`) ||
61
+ name === workspaceBasename ||
62
+ name.endsWith(`-${workspaceBasename}`));
63
+ };
64
+ }
65
+ /**
66
+ * Enumerate session files across project directories through the
67
+ * directory cache: one stat per project directory, a readdir only for
68
+ * directories whose mtime changed, and a stat only for files new to a
69
+ * changed directory (or recently modified, with `revalidateRecent`).
70
+ *
71
+ * With `limit`, directories are visited newest-mtime first and the walk
72
+ * stops once the remaining directories are older than the newest `limit`
73
+ * files found. A directory's mtime ignores appends, so a cold limited walk
74
+ * can miss a long-running session in an old directory until its next watch
75
+ * event; `since` filters files and shares the caveat.
76
+ */
77
+ async function listClaudeSessionFilesAsync(workspacePath, options = {}, cache = new directoryListingCache_1.DirectoryListingCache()) {
78
+ const projectsRoot = (0, claudePaths_1.getClaudeProjectsDir)();
79
+ if (options.stats) {
80
+ options.stats.directoriesListed ??= 0;
81
+ options.stats.directoriesStatted ??= 0;
82
+ options.stats.filesStatted ??= 0;
83
+ }
84
+ const root = await cache.listDirectory(projectsRoot, () => false, {
85
+ revalidateRecent: options.revalidateRecent,
86
+ stats: options.stats,
87
+ });
88
+ if (!root) {
89
+ return [];
90
+ }
91
+ const limit = options.limit !== undefined && Number.isFinite(options.limit)
92
+ ? Math.max(0, Math.floor(options.limit))
93
+ : null;
94
+ if (limit === 0) {
95
+ return [];
96
+ }
97
+ const since = options.since !== undefined && Number.isFinite(options.since) ? options.since : null;
98
+ const matchesWorkspace = workspaceDirectoryFilter(workspacePath);
99
+ const candidates = [];
100
+ let sinceYield = 0;
101
+ for (const name of root.subdirectories) {
102
+ if (!matchesWorkspace(name))
103
+ continue;
104
+ const directory = path.join(projectsRoot, name);
105
+ try {
106
+ const stat = await fs.promises.stat(directory);
107
+ if (options.stats) {
108
+ options.stats.directoriesStatted = (options.stats.directoriesStatted ?? 0) + 1;
109
+ }
110
+ if (stat.isDirectory())
111
+ candidates.push({ directory, stat });
112
+ }
113
+ catch {
114
+ continue;
115
+ }
116
+ if (++sinceYield >= 50) {
117
+ sinceYield = 0;
118
+ await new Promise((resolve) => setImmediate(resolve));
119
+ }
120
+ }
121
+ if (limit !== null)
122
+ candidates.sort((left, right) => right.stat.mtimeMs - left.stat.mtimeMs);
123
+ const byNewest = (left, right) => right.mtime.getTime() - left.mtime.getTime();
124
+ const results = [];
125
+ let cutoffMs = null;
126
+ for (const candidate of candidates) {
127
+ if (limit !== null && cutoffMs !== null && candidate.stat.mtimeMs < cutoffMs)
128
+ break;
129
+ const listing = await cache.listDirectory(candidate.directory, (name) => name.endsWith('.jsonl'), {
130
+ dirStat: candidate.stat,
131
+ revalidateRecent: options.revalidateRecent,
132
+ stats: options.stats,
133
+ });
134
+ if (!listing)
135
+ continue;
136
+ for (const [name, stat] of listing.files) {
137
+ if (stat.sizeBytes <= 0)
138
+ continue;
139
+ if (since !== null && stat.mtimeMs < since)
140
+ continue;
141
+ const sessionPath = path.join(candidate.directory, name);
142
+ results.push({
143
+ path: sessionPath,
144
+ mtime: new Date(stat.mtimeMs),
145
+ sizeBytes: stat.sizeBytes,
146
+ sessionId: path.basename(sessionPath, '.jsonl'),
147
+ });
148
+ }
149
+ if (limit !== null && results.length >= limit) {
150
+ results.sort(byNewest);
151
+ results.length = limit;
152
+ cutoffMs = results[limit - 1].mtime.getTime();
153
+ }
154
+ await new Promise((resolve) => setImmediate(resolve));
155
+ }
156
+ results.sort(byNewest);
157
+ return limit !== null && results.length > limit ? results.slice(0, limit) : results;
158
+ }
@@ -22,7 +22,9 @@ export interface FindCodexRolloutFileOptions {
22
22
  }
23
23
  /**
24
24
  * Locates the rollout file for a Codex session id by walking
25
- * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`.
25
+ * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
26
+ * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
27
+ * threads.
26
28
  *
27
29
  * Filename-only match, case-insensitive on the id — no file contents are read
28
30
  * and files are only stat'ed once their name matches, so the walk stays cheap
@@ -53,8 +53,9 @@ const sessionStats_1 = require("../sessionStats");
53
53
  const codexParser_1 = require("../parsers/codexParser");
54
54
  const codexDatabase_1 = require("./codexDatabase");
55
55
  const subagentStatsCache_1 = require("../parsers/subagentStatsCache");
56
- const codexProfiles_1 = require("../codexProfiles");
57
56
  const rolloutWalker_1 = require("./rolloutWalker");
57
+ const codexPaths_1 = require("../codexPaths");
58
+ const codexRollouts_1 = require("./codexRollouts");
58
59
  const directoryListingCache_1 = require("./directoryListingCache");
59
60
  const diagnostics_1 = require("./diagnostics");
60
61
  const modelContext_1 = require("../modelContext");
@@ -64,19 +65,6 @@ const subagentUsage_1 = require("../usage/subagentUsage");
64
65
  // ---------------------------------------------------------------------------
65
66
  // Helper functions
66
67
  // ---------------------------------------------------------------------------
67
- /**
68
- * Gets all candidate Codex homes to monitor.
69
- *
70
- * Managed profiles stay first so Sidekick-launched sessions keep working, but
71
- * we also fall back to the system ~/.codex home when no explicit CODEX_HOME is set.
72
- */
73
- function getCodexHomes() {
74
- return (0, codexProfiles_1.getCodexMonitoringHomes)();
75
- }
76
- /** Get every sessions base directory that may contain live Codex rollouts. */
77
- function getSessionsDirs() {
78
- return getCodexHomes().map((home) => path.join(home, 'sessions'));
79
- }
80
68
  /** Truncate a string to maxLen with ellipsis. */
81
69
  function truncate(text, maxLen) {
82
70
  const trimmed = text.trim().replace(/\s+/g, ' ');
@@ -94,11 +82,13 @@ function hasRolloutFile(dir) {
94
82
  }
95
83
  /** Every rollout across the monitored homes, newest first, deduplicated by path. */
96
84
  function findRolloutFilesInConfiguredHomes(options = {}) {
97
- return (0, rolloutWalker_1.walkRolloutFiles)(getSessionsDirs(), options);
85
+ return (0, rolloutWalker_1.walkRolloutFiles)((0, codexRollouts_1.getSessionsDirs)(), options);
98
86
  }
99
87
  /**
100
88
  * Locates the rollout file for a Codex session id by walking
101
- * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`.
89
+ * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
90
+ * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
91
+ * threads.
102
92
  *
103
93
  * Filename-only match, case-insensitive on the id — no file contents are read
104
94
  * and files are only stat'ed once their name matches, so the walk stays cheap
@@ -112,14 +102,14 @@ function findCodexRolloutFile(sessionId, options = {}) {
112
102
  if (!normalizedId)
113
103
  return null;
114
104
  const sessionsDirs = options.codexHome
115
- ? [path.join(options.codexHome, 'sessions')]
116
- : getSessionsDirs();
105
+ ? (0, codexPaths_1.getCodexSessionsDirs)([options.codexHome])
106
+ : (0, codexRollouts_1.getSessionsDirs)();
117
107
  // Name-only filter, one walk across the homes, newest modification first.
118
108
  return (0, rolloutWalker_1.walkRolloutFiles)(sessionsDirs, { sessionId: normalizedId })[0]?.path ?? null;
119
109
  }
120
110
  /** Pick the best sessions directory for UI/search entrypoints. */
121
111
  function getSessionsDir() {
122
- const dirs = getSessionsDirs();
112
+ const dirs = (0, codexPaths_1.getCodexSessionsDirs)(undefined, { includeArchived: false });
123
113
  for (const dir of dirs) {
124
114
  if (hasRolloutFile(dir)) {
125
115
  return dir;
@@ -140,174 +130,6 @@ function isSystemInjection(text) {
140
130
  const t = text.trimStart();
141
131
  return t.startsWith('<') || t.startsWith('#');
142
132
  }
143
- /**
144
- * Read the first N complete lines from a file using chunked reads.
145
- * Handles arbitrarily long lines (Codex session_meta can be 10KB+).
146
- */
147
- function readFirstLines(filePath, maxLines, maxBytes = 256 * 1024) {
148
- const lines = [];
149
- let fd;
150
- try {
151
- fd = fs.openSync(filePath, 'r');
152
- }
153
- catch {
154
- return [];
155
- }
156
- let pending = '';
157
- let totalRead = 0;
158
- const chunkSize = 16384;
159
- try {
160
- while (totalRead < maxBytes && lines.length < maxLines) {
161
- const buf = Buffer.alloc(Math.min(chunkSize, maxBytes - totalRead));
162
- const bytesRead = fs.readSync(fd, buf, 0, buf.length, totalRead);
163
- if (bytesRead === 0)
164
- break;
165
- totalRead += bytesRead;
166
- pending += buf.toString('utf-8', 0, bytesRead);
167
- let nlIdx;
168
- while ((nlIdx = pending.indexOf('\n')) !== -1 && lines.length < maxLines) {
169
- lines.push(pending.substring(0, nlIdx));
170
- pending = pending.substring(nlIdx + 1);
171
- }
172
- }
173
- if (pending.trim() && lines.length < maxLines) {
174
- lines.push(pending);
175
- }
176
- }
177
- finally {
178
- fs.closeSync(fd);
179
- }
180
- return lines;
181
- }
182
- /** Read session_meta from the beginning of a rollout file. */
183
- function readSessionMeta(rolloutPath) {
184
- try {
185
- const lines = readFirstLines(rolloutPath, 1);
186
- const firstLine = lines[0]?.trim();
187
- if (!firstLine)
188
- return null;
189
- const parsed = JSON.parse(firstLine);
190
- if (parsed.type === 'session_meta') {
191
- return parsed.payload;
192
- }
193
- }
194
- catch {
195
- // Skip
196
- }
197
- return null;
198
- }
199
- /**
200
- * First line of a rollout, chunk-read under the same 256 KiB cap as
201
- * readFirstLines(). `complete` is false while the line has no newline yet
202
- * (a rollout that was just created) or the file could not be read, so
203
- * callers know not to cache the answer.
204
- */
205
- async function readFirstLineAsync(rolloutPath) {
206
- // Mirrors readSessionMeta()/readFirstLines(): chunk-read until the first
207
- // line is complete under the same 256 KiB cap — session_meta carries
208
- // `instructions` and can far exceed a single chunk.
209
- const maxBytes = 256 * 1024;
210
- const chunkSize = 64 * 1024;
211
- let handle = null;
212
- try {
213
- handle = await fs.promises.open(rolloutPath, 'r');
214
- const chunks = [];
215
- let totalRead = 0;
216
- let newlineFound = false;
217
- while (totalRead < maxBytes && !newlineFound) {
218
- const buffer = Buffer.alloc(Math.min(chunkSize, maxBytes - totalRead));
219
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, totalRead);
220
- if (bytesRead === 0)
221
- break;
222
- const chunk = buffer.subarray(0, bytesRead);
223
- chunks.push(chunk);
224
- totalRead += bytesRead;
225
- newlineFound = chunk.includes(0x0a);
226
- }
227
- const firstLine = Buffer.concat(chunks).toString('utf8').split('\n', 1)[0]?.trim();
228
- return { line: firstLine || null, complete: newlineFound || totalRead >= maxBytes };
229
- }
230
- catch {
231
- return { line: null, complete: false };
232
- }
233
- finally {
234
- await handle?.close().catch(() => undefined);
235
- }
236
- }
237
- function parseSessionMetaLine(line) {
238
- if (!line)
239
- return null;
240
- try {
241
- const parsed = JSON.parse(line);
242
- return parsed.type === 'session_meta' ? parsed.payload : null;
243
- }
244
- catch {
245
- return null;
246
- }
247
- }
248
- /**
249
- * Read the CWD from a rollout file's session_meta. Cached per path, bounded
250
- * so a long-lived host watching a large history cannot grow it without limit
251
- * (insertion order doubles as an eviction order).
252
- */
253
- const CWD_CACHE_LIMIT = 5000;
254
- const cwdCache = new Map();
255
- function rememberCwd(rolloutPath, cwd) {
256
- if (!cwdCache.has(rolloutPath) && cwdCache.size >= CWD_CACHE_LIMIT) {
257
- const oldest = cwdCache.keys().next().value;
258
- if (oldest !== undefined)
259
- cwdCache.delete(oldest);
260
- }
261
- cwdCache.set(rolloutPath, cwd);
262
- }
263
- function readSessionCwd(rolloutPath) {
264
- const cached = cwdCache.get(rolloutPath);
265
- if (cached !== undefined)
266
- return cached;
267
- const meta = readSessionMeta(rolloutPath);
268
- const cwd = meta?.cwd || null;
269
- rememberCwd(rolloutPath, cwd);
270
- return cwd;
271
- }
272
- /** Async twin of readSessionCwd, sharing its cache so repeat listings do no content reads. */
273
- async function readSessionCwdAsync(rolloutPath) {
274
- const cached = cwdCache.get(rolloutPath);
275
- if (cached !== undefined)
276
- return cached;
277
- const { line, complete } = await readFirstLineAsync(rolloutPath);
278
- const cwd = parseSessionMetaLine(line)?.cwd || null;
279
- // A rollout observed before its session_meta line is flushed has no cwd
280
- // yet; caching that null would hide the session from its workspace until
281
- // the entry aged out, so only conclusive answers are remembered.
282
- if (cwd !== null || complete)
283
- rememberCwd(rolloutPath, cwd);
284
- return cwd;
285
- }
286
- /** Built-in enumerators report explicit zeros rather than leaving a counter unset. */
287
- function initializeCounters(stats) {
288
- if (!stats)
289
- return;
290
- stats.directoriesListed ??= 0;
291
- stats.directoriesStatted ??= 0;
292
- stats.filesStatted ??= 0;
293
- }
294
- /** Check if a session's CWD matches a workspace path. */
295
- function cwdMatches(sessionCwd, workspacePath) {
296
- const normalizedSession = normalizePath(sessionCwd);
297
- const normalizedWorkspace = normalizePath(workspacePath);
298
- return (normalizedSession === normalizedWorkspace ||
299
- normalizedWorkspace.startsWith(normalizedSession + path.sep) ||
300
- normalizedSession.startsWith(normalizedWorkspace + path.sep));
301
- }
302
- /** Normalize a path using realpathSync. */
303
- function normalizePath(input) {
304
- try {
305
- return fs.realpathSync(input);
306
- }
307
- catch {
308
- return path.resolve(input);
309
- }
310
- }
311
133
  /**
312
134
  * Read the first user message text from a rollout file.
313
135
  * Reads up to 20 lines to find a real user message (lines can be 10KB+ each).
@@ -315,7 +137,7 @@ function normalizePath(input) {
315
137
  */
316
138
  function extractFirstUserMessage(rolloutPath) {
317
139
  try {
318
- return extractFirstUserMessageFromLines(readFirstLines(rolloutPath, 20));
140
+ return extractFirstUserMessageFromLines((0, codexRollouts_1.readFirstLines)(rolloutPath, 20));
319
141
  }
320
142
  catch {
321
143
  return null;
@@ -754,8 +576,8 @@ class CodexProvider {
754
576
  return null;
755
577
  }
756
578
  for (const file of files) {
757
- const cwd = readSessionCwd(file.path);
758
- if (cwd && cwdMatches(cwd, workspacePath)) {
579
+ const cwd = (0, codexRollouts_1.readSessionCwd)(file.path);
580
+ if (cwd && (0, codexRollouts_1.cwdMatches)(cwd, workspacePath)) {
759
581
  this.recordDatabaseFallback('discoverSessionDirectory', true);
760
582
  return path.dirname(file.path);
761
583
  }
@@ -778,8 +600,8 @@ class CodexProvider {
778
600
  }
779
601
  // File scan fallback (newest first; cwd reads are cached per path)
780
602
  for (const file of findRolloutFilesInConfiguredHomes()) {
781
- const cwd = readSessionCwd(file.path);
782
- if (cwd && cwdMatches(cwd, workspacePath)) {
603
+ const cwd = (0, codexRollouts_1.readSessionCwd)(file.path);
604
+ if (cwd && (0, codexRollouts_1.cwdMatches)(cwd, workspacePath)) {
783
605
  this.recordDatabaseFallback('findActiveSession', true);
784
606
  return file.path;
785
607
  }
@@ -803,8 +625,8 @@ class CodexProvider {
803
625
  // File scan (newest first; cwd reads are cached per path)
804
626
  const sessions = findRolloutFilesInConfiguredHomes()
805
627
  .filter((f) => {
806
- const cwd = readSessionCwd(f.path);
807
- return cwd !== null && cwdMatches(cwd, workspacePath);
628
+ const cwd = (0, codexRollouts_1.readSessionCwd)(f.path);
629
+ return cwd !== null && (0, codexRollouts_1.cwdMatches)(cwd, workspacePath);
808
630
  })
809
631
  .map((f) => f.path);
810
632
  this.recordDatabaseFallback('findAllSessions', true);
@@ -834,30 +656,7 @@ class CodexProvider {
834
656
  return files;
835
657
  }
836
658
  async listSessionFilesAsync(workspacePath, options = {}) {
837
- const results = [];
838
- initializeCounters(options.stats);
839
- // One capped walk across the homes (deduplicated, newest first) through
840
- // the directory cache. With a workspace filter the limit applies after
841
- // filtering, so the walk itself is bounded only by its file cap.
842
- const files = await (0, rolloutWalker_1.walkRolloutFilesAsync)(getSessionsDirs(), {
843
- limit: workspacePath ? undefined : options.limit,
844
- since: options.since,
845
- cache: this.directoryCache,
846
- revalidateRecent: options.revalidateRecent,
847
- stats: options.stats,
848
- });
849
- for (const file of files) {
850
- if (options.limit !== undefined && results.length >= options.limit)
851
- break;
852
- const info = { ...file };
853
- if (workspacePath) {
854
- const cwd = await readSessionCwdAsync(file.path);
855
- if (!cwd || !cwdMatches(cwd, workspacePath))
856
- continue;
857
- info.workspacePath = cwd;
858
- }
859
- results.push(info);
860
- }
659
+ const results = await (0, codexRollouts_1.listCodexSessionFilesAsync)(workspacePath, options, this.directoryCache);
861
660
  // Filesystem enumeration bypasses the database, so record that here as
862
661
  // the sync listing paths do; otherwise `getLastOperationStatus()` keeps
863
662
  // describing whichever operation ran before this one.
@@ -898,8 +697,8 @@ class CodexProvider {
898
697
  sessionId,
899
698
  };
900
699
  if (workspacePath) {
901
- const cwd = await readSessionCwdAsync(fullPath);
902
- if (!cwd || !cwdMatches(cwd, workspacePath))
700
+ const cwd = await (0, codexRollouts_1.readSessionCwdAsync)(fullPath);
701
+ if (!cwd || !(0, codexRollouts_1.cwdMatches)(cwd, workspacePath))
903
702
  return { status: 'ignored' };
904
703
  file.workspacePath = cwd;
905
704
  }
@@ -930,7 +729,7 @@ class CodexProvider {
930
729
  for (const file of findRolloutFilesInConfiguredHomes()) {
931
730
  if (db && indexedUpTo > 0 && file.mtime.getTime() <= indexedUpTo)
932
731
  continue;
933
- const cwd = readSessionCwd(file.path);
732
+ const cwd = (0, codexRollouts_1.readSessionCwd)(file.path);
934
733
  if (!cwd)
935
734
  continue;
936
735
  const existing = seenCwds.get(cwd);
@@ -952,11 +751,11 @@ class CodexProvider {
952
751
  }
953
752
  folders.push(...seenCwds.values());
954
753
  // Sort: current workspace first, then by recency
955
- const normalizedWorkspace = workspacePath ? normalizePath(workspacePath) : null;
754
+ const normalizedWorkspace = workspacePath ? (0, codexRollouts_1.normalizePath)(workspacePath) : null;
956
755
  folders.sort((a, b) => {
957
756
  if (normalizedWorkspace) {
958
- const aMatch = cwdMatches(a.name, normalizedWorkspace);
959
- const bMatch = cwdMatches(b.name, normalizedWorkspace);
757
+ const aMatch = (0, codexRollouts_1.cwdMatches)(a.name, normalizedWorkspace);
758
+ const bMatch = (0, codexRollouts_1.cwdMatches)(b.name, normalizedWorkspace);
960
759
  if (aMatch && !bMatch)
961
760
  return -1;
962
761
  if (!aMatch && bMatch)
@@ -1074,7 +873,7 @@ class CodexProvider {
1074
873
  // forked_from_id. The walk stops after the 50 newest-dated files instead
1075
874
  // of enumerating the whole history and slicing afterwards.
1076
875
  for (const file of findRolloutFilesInConfiguredHomes({ limit: 50 })) {
1077
- const meta = readSessionMeta(file.path);
876
+ const meta = (0, codexRollouts_1.readSessionMeta)(file.path);
1078
877
  if (meta &&
1079
878
  (meta.forked_from_id === sessionId || (0, codexDatabase_1.spawnParentFromSource)(meta.source) === sessionId)) {
1080
879
  const childId = (0, rolloutWalker_1.extractRolloutSessionId)(path.basename(file.path));
@@ -1187,7 +986,7 @@ class CodexProvider {
1187
986
  line: snippet.replace(/\n/g, ' '),
1188
987
  eventType: parsed.type,
1189
988
  timestamp: parsed.timestamp || '',
1190
- projectPath: readSessionCwd(sessionPath) || sessionPath,
989
+ projectPath: (0, codexRollouts_1.readSessionCwd)(sessionPath) || sessionPath,
1191
990
  });
1192
991
  }
1193
992
  catch {
@@ -1204,7 +1003,7 @@ class CodexProvider {
1204
1003
  return getSessionsDir();
1205
1004
  }
1206
1005
  getWatchRoots() {
1207
- return getSessionsDirs();
1006
+ return (0, codexRollouts_1.getSessionsDirs)();
1208
1007
  }
1209
1008
  // --- Stats ---
1210
1009
  readSessionStats(sessionPath) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Codex rollout-file helpers shared by the provider and by callers that only
3
+ * list rollouts: bounded first-line reads, session_meta and cwd lookups, and
4
+ * the async session-file listing. Kept apart from the provider class so a
5
+ * listing does not bundle the provider's stats, context and reader code.
6
+ */
7
+ import type { CodexSessionMeta } from '../types/codex';
8
+ import { DirectoryListingCache } from './directoryListingCache';
9
+ import type { ListSessionFilesOptions, SessionFileInfo } from './types';
10
+ /** Every directory across the monitored homes that may hold rollouts, archived ones included. */
11
+ export declare function getSessionsDirs(): string[];
12
+ /**
13
+ * Read the first N complete lines from a file using chunked reads.
14
+ * Handles arbitrarily long lines (Codex session_meta can be 10KB+).
15
+ */
16
+ export declare function readFirstLines(filePath: string, maxLines: number, maxBytes?: number): string[];
17
+ /** Read session_meta from the beginning of a rollout file. */
18
+ export declare function readSessionMeta(rolloutPath: string): CodexSessionMeta | null;
19
+ /**
20
+ * First line of a rollout, chunk-read under the same 256 KiB cap as
21
+ * readFirstLines(). `complete` is false while the line has no newline yet
22
+ * (a rollout that was just created) or the file could not be read, so
23
+ * callers know not to cache the answer.
24
+ */
25
+ export declare function readFirstLineAsync(rolloutPath: string): Promise<{
26
+ line: string | null;
27
+ complete: boolean;
28
+ }>;
29
+ export declare function parseSessionMetaLine(line: string | null): CodexSessionMeta | null;
30
+ export declare function readSessionCwd(rolloutPath: string): string | null;
31
+ /** Async twin of readSessionCwd, sharing its cache so repeat listings do no content reads. */
32
+ export declare function readSessionCwdAsync(rolloutPath: string): Promise<string | null>;
33
+ /** Built-in enumerators report explicit zeros rather than leaving a counter unset. */
34
+ export declare function initializeCounters(stats: ListSessionFilesOptions['stats']): void;
35
+ /** Check if a session's CWD matches a workspace path. */
36
+ export declare function cwdMatches(sessionCwd: string, workspacePath: string): boolean;
37
+ /** Normalize a path using realpathSync. */
38
+ export declare function normalizePath(input: string): string;
39
+ /**
40
+ * List rollout files across the monitored Codex homes, newest first. With
41
+ * `workspacePath`, keeps rollouts whose session_meta cwd equals, contains, or
42
+ * sits inside it.
43
+ */
44
+ export declare function listCodexSessionFilesAsync(workspacePath?: string, options?: ListSessionFilesOptions, cache?: DirectoryListingCache): Promise<SessionFileInfo[]>;