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
@@ -57,6 +57,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
57
57
  exports.__promptHistoryTesting = exports.DEFAULT_BOUNDS = void 0;
58
58
  exports.collectPromptHistory = collectPromptHistory;
59
59
  exports.emptyStats = emptyStats;
60
+ exports.isPromptHistorySessionSettled = isPromptHistorySessionSettled;
61
+ exports.parsePromptHistoryCursor = parsePromptHistoryCursor;
60
62
  exports.compareStrings = compareStrings;
61
63
  exports.resolveRoots = resolveRoots;
62
64
  exports.listCandidates = listCandidates;
@@ -65,9 +67,11 @@ const crypto_1 = require("crypto");
65
67
  const fs = __importStar(require("fs"));
66
68
  const path = __importStar(require("path"));
67
69
  const humanPrompt_1 = require("./humanPrompt");
68
- const sessionPathResolver_1 = require("./parsers/sessionPathResolver");
69
- const claudeCode_1 = require("./providers/claudeCode");
70
- const codex_1 = require("./providers/codex");
70
+ const claudeListing_1 = require("./providers/claudeListing");
71
+ const codexRollouts_1 = require("./providers/codexRollouts");
72
+ const directoryListingCache_1 = require("./providers/directoryListingCache");
73
+ const workspaceRoots_1 = require("./workspaceRoots");
74
+ const fileHead_1 = require("./fileHead");
71
75
  /** @internal */
72
76
  exports.DEFAULT_BOUNDS = {
73
77
  maxSessions: 1000,
@@ -78,7 +82,6 @@ exports.DEFAULT_BOUNDS = {
78
82
  deadlineMs: 30_000,
79
83
  };
80
84
  const DEFAULT_READ_CHUNK_BYTES = 1024 * 1024;
81
- const HEAD_BYTES = 4096;
82
85
  const MAX_EXCLUSIONS = 1000;
83
86
  const CHECK_EVERY_LINES = 500;
84
87
  const ALL_PROVIDERS = ['claude-code', 'codex'];
@@ -99,7 +102,17 @@ async function collectPromptHistory(options) {
99
102
  const cursor = { version: 3, sessions: { ...previous } };
100
103
  const entries = [];
101
104
  const exclusions = [];
105
+ const changed = new Map();
102
106
  let hasMore = false;
107
+ let progressed = false;
108
+ let scannedRoots = [];
109
+ const markChanged = (candidate) => {
110
+ changed.set(candidate.key, {
111
+ provider: candidate.provider,
112
+ sessionId: candidate.sessionId,
113
+ sessionKey: candidate.key,
114
+ });
115
+ };
103
116
  const shouldStop = () => {
104
117
  if (options.signal?.aborted)
105
118
  return 'aborted';
@@ -114,14 +127,19 @@ async function collectPromptHistory(options) {
114
127
  boundsHit: [...boundsHit],
115
128
  exclusions,
116
129
  hasMore,
130
+ changed: [...changed.values()],
131
+ progressed,
132
+ roots: scannedRoots,
117
133
  });
118
134
  const realpaths = new Map();
119
135
  const { roots, listingRoots } = resolveRoots(options.workspacePaths, options.includeWorktrees !== false, realpaths);
136
+ scannedRoots = roots;
120
137
  if (roots.length === 0)
121
138
  return finish();
122
139
  const sinceMs = options.since ? options.since.getTime() : null;
123
140
  const providers = options.providers ?? ALL_PROVIDERS;
124
141
  const candidates = rotate(await listCandidates(providers, listingRoots, sinceMs), previousNext);
142
+ const moved = movedSessionKeys(previous, candidates);
125
143
  const context = {
126
144
  roots,
127
145
  sinceMs,
@@ -160,7 +178,21 @@ async function collectPromptHistory(options) {
160
178
  catch {
161
179
  continue;
162
180
  }
163
- const prior = validSession(previous[candidate.key]);
181
+ let prior = validSession(previous[candidate.key]);
182
+ const sessionRef = `${candidate.provider}:${candidate.sessionId}`;
183
+ const movedFrom = prior ? undefined : moved.get(sessionRef);
184
+ const carried = movedFrom ? validSession(previous[movedFrom]) : undefined;
185
+ // The same session under a new path (Codex archives a thread by moving its
186
+ // rollout): carry the entry over, but only when the new file starts with
187
+ // the recorded head. Another file that merely shares the id (an unlisted
188
+ // file is not necessarily gone) keeps its own entry and is read as new.
189
+ if (movedFrom && carried?.head && (await fileStartsWith(candidate.filePath, carried.head))) {
190
+ moved.delete(sessionRef);
191
+ prior = carried;
192
+ delete cursor.sessions[movedFrom];
193
+ cursor.sessions[candidate.key] = carried;
194
+ markChanged(candidate);
195
+ }
164
196
  if (prior && isSettled(prior) && prior.size === stat.size && prior.mtimeMs === stat.mtimeMs) {
165
197
  stats.sessionsSkippedUnchanged++;
166
198
  continue;
@@ -191,7 +223,15 @@ async function collectPromptHistory(options) {
191
223
  continue;
192
224
  }
193
225
  totalBytes += outcome.bytesRead;
226
+ for (const entry of outcome.entries)
227
+ entry.sessionKey = candidate.key;
194
228
  entries.push(...outcome.entries);
229
+ if (outcome.progressed)
230
+ progressed = true;
231
+ if (!outcome.session ||
232
+ JSON.stringify(outcome.session) !== JSON.stringify(previous[candidate.key])) {
233
+ markChanged(candidate);
234
+ }
195
235
  if (outcome.notInteractive)
196
236
  stats.sessionsNotInteractive++;
197
237
  else if (outcome.inScope === 0 && outcome.outOfScope > 0)
@@ -276,6 +316,33 @@ function validSession(session) {
276
316
  typeof session.mtimeMs === 'number';
277
317
  return valid ? session : undefined;
278
318
  }
319
+ /**
320
+ * True when a cursor entry's last pass reached the end of its file with no
321
+ * prompt still waiting for its answer: an unchanged file needs no read.
322
+ */
323
+ function isPromptHistorySessionSettled(session) {
324
+ return validSession(session) !== undefined && isSettled(session);
325
+ }
326
+ /**
327
+ * Validates a stored cursor (versions 1 to 3). Returns it as a version-3
328
+ * cursor with malformed session entries dropped, or undefined when it is
329
+ * not a cursor at all.
330
+ */
331
+ function parsePromptHistoryCursor(value) {
332
+ if (!value || typeof value !== 'object')
333
+ return undefined;
334
+ const candidate = value;
335
+ if (typeof candidate.version !== 'number' || ![1, 2, 3].includes(candidate.version)) {
336
+ return undefined;
337
+ }
338
+ const { sessions, next } = acceptedCursor(value);
339
+ const valid = {};
340
+ for (const [key, session] of Object.entries(sessions)) {
341
+ if (validSession(session))
342
+ valid[key] = session;
343
+ }
344
+ return { version: 3, sessions: valid, ...(next !== undefined ? { next } : {}) };
345
+ }
279
346
  /** A session whose last pass reached the end and needs no migration. */
280
347
  function isSettled(session) {
281
348
  if (session.backlog || session.skipping)
@@ -339,53 +406,15 @@ function rotate(candidates, nextKey) {
339
406
  const index = nextKey ? candidates.findIndex((candidate) => candidate.key === nextKey) : -1;
340
407
  return index <= 0 ? candidates : [...candidates.slice(index), ...candidates.slice(0, index)];
341
408
  }
342
- function realpathOrNull(input, cache) {
343
- const cached = cache.get(input);
344
- if (cached !== undefined)
345
- return cached;
346
- let resolved = null;
347
- try {
348
- resolved = path.isAbsolute(input) ? fs.realpathSync.native(input) : null;
349
- }
350
- catch {
351
- resolved = null;
352
- }
353
- cache.set(input, resolved);
354
- return resolved;
355
- }
356
409
  /** @internal */
357
410
  function resolveRoots(workspacePaths, includeWorktrees, realpaths) {
358
- const roots = new Set();
359
- const listingRoots = new Set();
360
- const add = (candidate) => {
361
- const real = realpathOrNull(candidate, realpaths);
362
- if (!real)
363
- return;
364
- roots.add(real);
365
- listingRoots.add(real);
366
- listingRoots.add(path.resolve(candidate));
367
- };
368
- for (const workspacePath of workspacePaths) {
369
- if (typeof workspacePath !== 'string' || !path.isAbsolute(workspacePath))
370
- continue;
371
- add(workspacePath);
372
- if (!includeWorktrees)
373
- continue;
374
- const real = realpathOrNull(workspacePath, realpaths);
375
- if (!real)
376
- continue;
377
- const mainRepo = (0, sessionPathResolver_1.resolveWorktreeMainRepo)(real) ?? real;
378
- add(mainRepo);
379
- for (const sibling of (0, sessionPathResolver_1.discoverWorktreeSiblings)(mainRepo))
380
- add(sibling);
381
- }
382
- return { roots: [...roots], listingRoots: [...listingRoots] };
411
+ return (0, workspaceRoots_1.resolveWorkspaceRoots)(workspacePaths, { includeWorktrees }, realpaths);
383
412
  }
384
413
  /** Fail-closed scope check on a recorded working directory. */
385
414
  function scopedCwd(cwd, roots, realpaths) {
386
415
  if (typeof cwd !== 'string' || cwd.length === 0)
387
416
  return null;
388
- const real = realpathOrNull(cwd, realpaths);
417
+ const real = (0, workspaceRoots_1.realpathOrNull)(cwd, realpaths);
389
418
  if (!real)
390
419
  return null;
391
420
  for (const root of roots) {
@@ -405,11 +434,12 @@ async function listCandidates(providers, listingRoots, sinceMs) {
405
434
  if (!ALL_PROVIDERS.includes(provider))
406
435
  continue;
407
436
  // Listings are only candidate supersets; scope is re-checked per prompt.
408
- const source = provider === 'claude-code' ? new claudeCode_1.ClaudeCodeProvider() : new codex_1.CodexProvider();
437
+ const list = provider === 'claude-code' ? claudeListing_1.listClaudeSessionFilesAsync : codexRollouts_1.listCodexSessionFilesAsync;
438
+ const cache = new directoryListingCache_1.DirectoryListingCache();
409
439
  for (const root of listingRoots) {
410
440
  let files;
411
441
  try {
412
- files = (await source.listSessionFilesAsync?.(root, listOptions)) ?? [];
442
+ files = await list(root, listOptions, cache);
413
443
  }
414
444
  catch {
415
445
  files = [];
@@ -426,18 +456,48 @@ async function listCandidates(providers, listingRoots, sinceMs) {
426
456
  });
427
457
  }
428
458
  }
429
- source.dispose();
430
459
  }
431
460
  return [...byPath.values()].sort((left, right) => compareStrings(left.provider, right.provider) ||
432
461
  compareStrings(left.sessionId, right.sessionId) ||
433
462
  compareStrings(left.key, right.key));
434
463
  }
464
+ /** True when the file's first bytes still match a recorded head. */
465
+ async function fileStartsWith(filePath, head) {
466
+ let handle;
467
+ try {
468
+ handle = await fs.promises.open(filePath, 'r');
469
+ return (0, fileHead_1.headMatches)(head, await readAt(handle, 0, Math.min(fileHead_1.HEAD_BYTES, head.bytes)));
470
+ }
471
+ catch {
472
+ return false;
473
+ }
474
+ finally {
475
+ await handle?.close();
476
+ }
477
+ }
478
+ /**
479
+ * Cursor entries whose file is no longer listed, by `provider:sessionId`, for
480
+ * sessions that are listed again under another path.
481
+ */
482
+ function movedSessionKeys(previous, candidates) {
483
+ const listedKeys = new Set(candidates.map((candidate) => candidate.key));
484
+ const listedIds = new Set(candidates.map((candidate) => `${candidate.provider}:${candidate.sessionId}`));
485
+ const moved = new Map();
486
+ for (const [key, value] of Object.entries(previous)) {
487
+ if (listedKeys.has(key))
488
+ continue;
489
+ const session = validSession(value);
490
+ if (!session)
491
+ continue;
492
+ const id = `${session.provider}:${session.sessionId}`;
493
+ if (listedIds.has(id) && !moved.has(id))
494
+ moved.set(id, key);
495
+ }
496
+ return moved;
497
+ }
435
498
  function hashPath(filePath) {
436
499
  return (0, crypto_1.createHash)('sha256').update(filePath).digest('hex').slice(0, 12);
437
500
  }
438
- function hashBytes(buffer) {
439
- return (0, crypto_1.createHash)('sha256').update(buffer).digest('hex').slice(0, 32);
440
- }
441
501
  async function readAt(handle, offset, length) {
442
502
  const buffer = Buffer.alloc(length);
443
503
  let read = 0;
@@ -457,7 +517,7 @@ function yieldToEventLoop() {
457
517
  * Returns the head to store, or null when the file was truncated or rewritten.
458
518
  */
459
519
  async function verifyFile(handle, size, session) {
460
- const headBytes = Math.min(HEAD_BYTES, size);
520
+ const headBytes = Math.min(fileHead_1.HEAD_BYTES, size);
461
521
  const head = await readAt(handle, 0, headBytes);
462
522
  let bytesRead = head.length;
463
523
  if (head.length !== headBytes)
@@ -466,12 +526,8 @@ async function verifyFile(handle, size, session) {
466
526
  if (session.offset > size)
467
527
  return null;
468
528
  const known = session.head;
469
- if (known) {
470
- if (!isOffset(known.bytes) || known.bytes > head.length)
471
- return null;
472
- if (hashBytes(head.subarray(0, known.bytes)) !== known.hash)
473
- return null;
474
- }
529
+ if (known && !(0, fileHead_1.headMatches)(known, head))
530
+ return null;
475
531
  if (session.offset > 0 && !session.skipping) {
476
532
  const before = await readAt(handle, session.offset - 1, 1);
477
533
  bytesRead += before.length;
@@ -479,7 +535,7 @@ async function verifyFile(handle, size, session) {
479
535
  return null;
480
536
  }
481
537
  }
482
- return { head: { bytes: head.length, hash: hashBytes(head) }, bytesRead };
538
+ return { head: (0, fileHead_1.fileHeadOf)(head), bytesRead };
483
539
  }
484
540
  /** @internal */
485
541
  async function scanFile(candidate, stat, prior, budget, context) {
@@ -1,10 +1,13 @@
1
1
  import type { AccountProviderId } from './providerIds';
2
- export type ProviderCredentialKind = 'oauth' | 'api-key' | 'unknown';
2
+ export declare const PROVIDER_CREDENTIAL_KINDS: readonly ["oauth", "api-key", "unknown"];
3
+ export type ProviderCredentialKind = (typeof PROVIDER_CREDENTIAL_KINDS)[number];
4
+ export declare const PROVIDER_AUTHENTICATION_STATES: readonly ["authenticated", "missing", "signed-out", "expired", "rejected", "unknown"];
5
+ export declare const PROVIDER_AUTHENTICATION_SOURCES: readonly ["local-check", "provider-response"];
3
6
  export interface ProviderAuthenticationEvidence {
4
7
  credentialKind: ProviderCredentialKind;
5
- state: 'authenticated' | 'missing' | 'signed-out' | 'expired' | 'rejected' | 'unknown';
8
+ state: (typeof PROVIDER_AUTHENTICATION_STATES)[number];
6
9
  checkedAt: string;
7
- source: 'local-check' | 'provider-response';
10
+ source: (typeof PROVIDER_AUTHENTICATION_SOURCES)[number];
8
11
  }
9
12
  export interface ProviderFailureInput {
10
13
  provider: AccountProviderId;
@@ -14,8 +17,10 @@ export interface ProviderFailureInput {
14
17
  /** The caller owns freshness and relevance of this separate observation. */
15
18
  authentication?: ProviderAuthenticationEvidence;
16
19
  }
17
- export type ProviderFailureCode = 'missing_credentials' | 'oauth_reauthentication_required' | 'api_credentials_rejected' | 'authentication_rejected' | 'invalid_provider_session' | 'service_unavailable' | 'connection_failed' | 'timeout' | 'rate_limited' | 'overloaded' | 'execution_policy_denied' | 'runtime_unavailable' | 'context_overflow' | 'unknown';
18
- export type ProviderRecoveryAction = 'sign_in' | 'update_credentials' | 'check_authentication' | 'start_new_session' | 'retry_later' | 'check_connection' | 'review_execution_policy' | 'repair_runtime' | 'reduce_context' | 'inspect_error';
20
+ export declare const PROVIDER_FAILURE_CODES: readonly ["missing_credentials", "oauth_reauthentication_required", "api_credentials_rejected", "authentication_rejected", "invalid_provider_session", "service_unavailable", "connection_failed", "timeout", "rate_limited", "overloaded", "execution_policy_denied", "runtime_unavailable", "context_overflow", "unknown"];
21
+ export type ProviderFailureCode = (typeof PROVIDER_FAILURE_CODES)[number];
22
+ export declare const PROVIDER_RECOVERY_ACTIONS: readonly ["sign_in", "update_credentials", "check_authentication", "start_new_session", "retry_later", "check_connection", "review_execution_policy", "repair_runtime", "reduce_context", "inspect_error"];
23
+ export type ProviderRecoveryAction = (typeof PROVIDER_RECOVERY_ACTIONS)[number];
19
24
  export type ProviderRetryAfter = {
20
25
  kind: 'delay';
21
26
  delayMs: number;
@@ -23,8 +28,9 @@ export type ProviderRetryAfter = {
23
28
  kind: 'date';
24
29
  at: string;
25
30
  };
31
+ export declare const PROVIDER_FAILURE_EVIDENCE_SOURCES: readonly ["structured-error", "http-status", "message", "authentication-check"];
26
32
  export interface ProviderFailureEvidence {
27
- source: 'structured-error' | 'http-status' | 'message' | 'authentication-check';
33
+ source: (typeof PROVIDER_FAILURE_EVIDENCE_SOURCES)[number];
28
34
  rule: ProviderFailureCode;
29
35
  checkedAt?: string;
30
36
  }
@@ -1,6 +1,51 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROVIDER_FAILURE_EVIDENCE_SOURCES = exports.PROVIDER_RECOVERY_ACTIONS = exports.PROVIDER_FAILURE_CODES = exports.PROVIDER_AUTHENTICATION_SOURCES = exports.PROVIDER_AUTHENTICATION_STATES = exports.PROVIDER_CREDENTIAL_KINDS = void 0;
3
4
  exports.diagnoseProviderFailure = diagnoseProviderFailure;
5
+ exports.PROVIDER_CREDENTIAL_KINDS = ['oauth', 'api-key', 'unknown'];
6
+ exports.PROVIDER_AUTHENTICATION_STATES = [
7
+ 'authenticated',
8
+ 'missing',
9
+ 'signed-out',
10
+ 'expired',
11
+ 'rejected',
12
+ 'unknown',
13
+ ];
14
+ exports.PROVIDER_AUTHENTICATION_SOURCES = ['local-check', 'provider-response'];
15
+ exports.PROVIDER_FAILURE_CODES = [
16
+ 'missing_credentials',
17
+ 'oauth_reauthentication_required',
18
+ 'api_credentials_rejected',
19
+ 'authentication_rejected',
20
+ 'invalid_provider_session',
21
+ 'service_unavailable',
22
+ 'connection_failed',
23
+ 'timeout',
24
+ 'rate_limited',
25
+ 'overloaded',
26
+ 'execution_policy_denied',
27
+ 'runtime_unavailable',
28
+ 'context_overflow',
29
+ 'unknown',
30
+ ];
31
+ exports.PROVIDER_RECOVERY_ACTIONS = [
32
+ 'sign_in',
33
+ 'update_credentials',
34
+ 'check_authentication',
35
+ 'start_new_session',
36
+ 'retry_later',
37
+ 'check_connection',
38
+ 'review_execution_policy',
39
+ 'repair_runtime',
40
+ 'reduce_context',
41
+ 'inspect_error',
42
+ ];
43
+ exports.PROVIDER_FAILURE_EVIDENCE_SOURCES = [
44
+ 'structured-error',
45
+ 'http-status',
46
+ 'message',
47
+ 'authentication-check',
48
+ ];
4
49
  function read(value, key) {
5
50
  try {
6
51
  return value && typeof value === 'object' ? value[key] : undefined;
@@ -44,7 +44,6 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.ClaudeCodeProvider = void 0;
46
46
  const fs = __importStar(require("fs"));
47
- const os = __importStar(require("os"));
48
47
  const path = __importStar(require("path"));
49
48
  const string_decoder_1 = require("string_decoder");
50
49
  const sessionContext_1 = require("../context/sessionContext");
@@ -57,6 +56,8 @@ const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
57
56
  const subagentScanner_1 = require("../parsers/subagentScanner");
58
57
  const modelContext_1 = require("../modelContext");
59
58
  const sessionEvent_1 = require("../schemas/sessionEvent");
59
+ const claudePaths_1 = require("../claudePaths");
60
+ const claudeListing_1 = require("./claudeListing");
60
61
  /** Type guard for content blocks with a `type` string property */
61
62
  function isTypedBlock(block) {
62
63
  return (block !== null &&
@@ -96,22 +97,6 @@ function extractSearchableText(event) {
96
97
  * worktree suffix of it, or a directory ending in the workspace's basename.
97
98
  * Without a workspace every directory matches.
98
99
  */
99
- function workspaceDirectoryFilter(workspacePath) {
100
- if (!workspacePath)
101
- return () => true;
102
- const encodedWorkspace = (0, sessionPathResolver_1.encodeWorkspacePath)(workspacePath).toLowerCase();
103
- const workspaceBasename = path
104
- .basename(workspacePath)
105
- .replace(/[^a-zA-Z0-9]/g, '-')
106
- .toLowerCase();
107
- return (directoryName) => {
108
- const name = directoryName.toLowerCase();
109
- return (name === encodedWorkspace ||
110
- name.startsWith(`${encodedWorkspace}-`) ||
111
- name === workspaceBasename ||
112
- name.endsWith(`-${workspaceBasename}`));
113
- };
114
- }
115
100
  function extractClaudeLabelFromPrefix(chunk) {
116
101
  for (const line of chunk.split('\n')) {
117
102
  const trimmed = line.trim();
@@ -368,89 +353,12 @@ class ClaudeCodeProvider {
368
353
  * event; `since` filters files and shares the caveat.
369
354
  */
370
355
  async listSessionFilesAsync(workspacePath, options = {}) {
371
- const projectsRoot = this.getProjectsBaseDir();
372
- if (options.stats) {
373
- options.stats.directoriesListed ??= 0;
374
- options.stats.directoriesStatted ??= 0;
375
- options.stats.filesStatted ??= 0;
376
- }
377
- const root = await this.directoryCache.listDirectory(projectsRoot, () => false, {
378
- revalidateRecent: options.revalidateRecent,
379
- stats: options.stats,
380
- });
381
- if (!root) {
382
- this.recordHomeStatus('listSessionFilesAsync');
383
- return [];
356
+ try {
357
+ return await (0, claudeListing_1.listClaudeSessionFilesAsync)(workspacePath, options, this.directoryCache);
384
358
  }
385
- const limit = options.limit !== undefined && Number.isFinite(options.limit)
386
- ? Math.max(0, Math.floor(options.limit))
387
- : null;
388
- if (limit === 0) {
359
+ finally {
389
360
  this.recordHomeStatus('listSessionFilesAsync');
390
- return [];
391
- }
392
- const since = options.since !== undefined && Number.isFinite(options.since) ? options.since : null;
393
- const matchesWorkspace = workspaceDirectoryFilter(workspacePath);
394
- const candidates = [];
395
- let sinceYield = 0;
396
- for (const name of root.subdirectories) {
397
- if (!matchesWorkspace(name))
398
- continue;
399
- const directory = path.join(projectsRoot, name);
400
- try {
401
- const stat = await fs.promises.stat(directory);
402
- if (options.stats) {
403
- options.stats.directoriesStatted = (options.stats.directoriesStatted ?? 0) + 1;
404
- }
405
- if (stat.isDirectory())
406
- candidates.push({ directory, stat });
407
- }
408
- catch {
409
- continue;
410
- }
411
- if (++sinceYield >= 50) {
412
- sinceYield = 0;
413
- await new Promise((resolve) => setImmediate(resolve));
414
- }
415
- }
416
- if (limit !== null)
417
- candidates.sort((left, right) => right.stat.mtimeMs - left.stat.mtimeMs);
418
- const byNewest = (left, right) => right.mtime.getTime() - left.mtime.getTime();
419
- const results = [];
420
- let cutoffMs = null;
421
- for (const candidate of candidates) {
422
- if (limit !== null && cutoffMs !== null && candidate.stat.mtimeMs < cutoffMs)
423
- break;
424
- const listing = await this.directoryCache.listDirectory(candidate.directory, (name) => this.isSessionFile(name), {
425
- dirStat: candidate.stat,
426
- revalidateRecent: options.revalidateRecent,
427
- stats: options.stats,
428
- });
429
- if (!listing)
430
- continue;
431
- for (const [name, stat] of listing.files) {
432
- if (stat.sizeBytes <= 0)
433
- continue;
434
- if (since !== null && stat.mtimeMs < since)
435
- continue;
436
- const sessionPath = path.join(candidate.directory, name);
437
- results.push({
438
- path: sessionPath,
439
- mtime: new Date(stat.mtimeMs),
440
- sizeBytes: stat.sizeBytes,
441
- sessionId: this.getSessionId(sessionPath),
442
- });
443
- }
444
- if (limit !== null && results.length >= limit) {
445
- results.sort(byNewest);
446
- results.length = limit;
447
- cutoffMs = results[limit - 1].mtime.getTime();
448
- }
449
- await new Promise((resolve) => setImmediate(resolve));
450
361
  }
451
- this.recordHomeStatus('listSessionFilesAsync');
452
- results.sort(byNewest);
453
- return limit !== null && results.length > limit ? results.slice(0, limit) : results;
454
362
  }
455
363
  /**
456
364
  * Resolve a recursive-watch event under the projects root with one stat.
@@ -478,7 +386,7 @@ class ClaudeCodeProvider {
478
386
  const [directoryName, fileName] = segments;
479
387
  if (!this.isSessionFile(fileName))
480
388
  return { status: 'ignored' };
481
- if (!workspaceDirectoryFilter(workspacePath)(directoryName))
389
+ if (!(0, claudeListing_1.workspaceDirectoryFilter)(workspacePath)(directoryName))
482
390
  return { status: 'ignored' };
483
391
  const sessionId = this.getSessionId(fullPath);
484
392
  const stat = await this.directoryCache.refreshFile(fullPath);
@@ -593,7 +501,7 @@ class ClaudeCodeProvider {
593
501
  return results;
594
502
  }
595
503
  getProjectsBaseDir() {
596
- return path.join(os.homedir(), '.claude', 'projects');
504
+ return (0, claudePaths_1.getClaudeProjectsDir)();
597
505
  }
598
506
  getWatchRoots() {
599
507
  return [this.getProjectsBaseDir()];
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Claude Code session-file listing, kept apart from the provider class so a
3
+ * caller that only lists files does not bundle the provider's stats,
4
+ * context and transcript code.
5
+ */
6
+ import { DirectoryListingCache } from './directoryListingCache';
7
+ import type { ListSessionFilesOptions, SessionFileInfo } from './types';
8
+ /** Matches project directory names that may hold sessions for `workspacePath`. */
9
+ export declare function workspaceDirectoryFilter(workspacePath?: string): (directoryName: string) => boolean;
10
+ /**
11
+ * Enumerate session files across project directories through the
12
+ * directory cache: one stat per project directory, a readdir only for
13
+ * directories whose mtime changed, and a stat only for files new to a
14
+ * changed directory (or recently modified, with `revalidateRecent`).
15
+ *
16
+ * With `limit`, directories are visited newest-mtime first and the walk
17
+ * stops once the remaining directories are older than the newest `limit`
18
+ * files found. A directory's mtime ignores appends, so a cold limited walk
19
+ * can miss a long-running session in an old directory until its next watch
20
+ * event; `since` filters files and shares the caveat.
21
+ */
22
+ export declare function listClaudeSessionFilesAsync(workspacePath?: string, options?: ListSessionFilesOptions, cache?: DirectoryListingCache): Promise<SessionFileInfo[]>;