sidekick-shared 0.27.5 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +93 -52
  3. package/dist/accountHealth.d.ts +14 -0
  4. package/dist/accountHealth.js +61 -14
  5. package/dist/accountLaunch.d.ts +6 -0
  6. package/dist/accountLaunch.js +61 -4
  7. package/dist/accountLease.d.ts +5 -0
  8. package/dist/accountLease.js +98 -0
  9. package/dist/accountManager.d.ts +7 -0
  10. package/dist/accountManager.js +226 -89
  11. package/dist/accountSync.d.ts +1 -5
  12. package/dist/accountSync.js +22 -6
  13. package/dist/accountSyncTypes.d.ts +9 -0
  14. package/dist/accounts.d.ts +75 -18
  15. package/dist/accounts.js +712 -189
  16. package/dist/browser.d.ts +13 -2
  17. package/dist/browser.js +30 -2
  18. package/dist/claudeCredentialDoctor.d.ts +50 -0
  19. package/dist/claudeCredentialDoctor.js +277 -0
  20. package/dist/claudeCredentialLedger.d.ts +15 -0
  21. package/dist/claudeCredentialLedger.js +130 -0
  22. package/dist/claudeCredentials.d.ts +20 -3
  23. package/dist/claudeCredentials.js +50 -3
  24. package/dist/claudeLiveGuard.d.ts +33 -0
  25. package/dist/claudeLiveGuard.js +46 -0
  26. package/dist/claudePaths.d.ts +11 -0
  27. package/dist/claudePaths.js +61 -0
  28. package/dist/claudeProfiles.d.ts +15 -8
  29. package/dist/claudeProfiles.js +30 -23
  30. package/dist/claudeRefreshLock.d.ts +15 -0
  31. package/dist/claudeRefreshLock.js +145 -0
  32. package/dist/codexAuth.d.ts +4 -0
  33. package/dist/codexAuth.js +18 -0
  34. package/dist/codexCredentialDoctor.d.ts +33 -0
  35. package/dist/codexCredentialDoctor.js +302 -0
  36. package/dist/codexCredentialStore.d.ts +29 -0
  37. package/dist/codexCredentialStore.js +213 -0
  38. package/dist/codexKeepAlive.d.ts +2 -0
  39. package/dist/codexKeepAlive.js +116 -0
  40. package/dist/codexPaths.d.ts +30 -2
  41. package/dist/codexPaths.js +118 -7
  42. package/dist/codexProfiles.d.ts +38 -35
  43. package/dist/codexProfiles.js +848 -715
  44. package/dist/credentialIO.d.ts +67 -7
  45. package/dist/credentialIO.js +211 -47
  46. package/dist/doctor.js +2 -2
  47. package/dist/ensureDefaultAccounts.js +1 -8
  48. package/dist/esm/accountChangeSignal.js +15 -0
  49. package/dist/esm/accountChanges.js +152 -0
  50. package/dist/esm/accountHealth.js +288 -0
  51. package/dist/esm/accountLaunch.js +145 -0
  52. package/dist/esm/accountLease.js +60 -0
  53. package/dist/esm/accountManager.js +606 -0
  54. package/dist/esm/accountRegistry.js +214 -0
  55. package/dist/esm/accountStatus.js +51 -0
  56. package/dist/esm/accountSwitch.js +74 -0
  57. package/dist/esm/accountSync.js +156 -0
  58. package/dist/esm/accountSyncTypes.js +4 -0
  59. package/dist/esm/accounts.js +1291 -0
  60. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  61. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  62. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  63. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  64. package/dist/esm/aggregation/costProvenance.js +31 -0
  65. package/dist/esm/aggregation/snapshot.js +161 -0
  66. package/dist/esm/aggregation/types.js +5 -0
  67. package/dist/esm/analytics/codeImpact.js +18 -0
  68. package/dist/esm/analytics/compactionLedger.js +37 -0
  69. package/dist/esm/analytics/qualityScore.js +112 -0
  70. package/dist/esm/autoSwitch.js +185 -0
  71. package/dist/esm/browser.js +34 -0
  72. package/dist/esm/claudeCredentialDoctor.js +239 -0
  73. package/dist/esm/claudeCredentialLedger.js +91 -0
  74. package/dist/esm/claudeCredentials.js +111 -0
  75. package/dist/esm/claudeLiveGuard.js +43 -0
  76. package/dist/esm/claudePaths.js +22 -0
  77. package/dist/esm/claudeProfiles.js +142 -0
  78. package/dist/esm/claudeRefreshLock.js +106 -0
  79. package/dist/esm/codexAuth.js +101 -0
  80. package/dist/esm/codexCredentialDoctor.js +265 -0
  81. package/dist/esm/codexCredentialStore.js +164 -0
  82. package/dist/esm/codexKeepAlive.js +112 -0
  83. package/dist/esm/codexPaths.js +172 -0
  84. package/dist/esm/codexProfiles.js +1251 -0
  85. package/dist/esm/codexQuota.js +564 -0
  86. package/dist/esm/codexQuotaWatcher.js +363 -0
  87. package/dist/esm/context/composer.js +71 -0
  88. package/dist/esm/context/sessionContext.js +546 -0
  89. package/dist/esm/credentialIO.js +283 -0
  90. package/dist/esm/credentials.js +49 -0
  91. package/dist/esm/doctor.js +305 -0
  92. package/dist/esm/ensureDefaultAccounts.js +62 -0
  93. package/dist/esm/errorHistory.js +56 -0
  94. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  95. package/dist/esm/extractors/gatherAssets.js +32 -0
  96. package/dist/esm/extractors/sessionAssets.js +176 -0
  97. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  98. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  99. package/dist/esm/extractors/tokenUsage.js +41 -0
  100. package/dist/esm/extractors/toolCall.js +142 -0
  101. package/dist/esm/failingTools.js +65 -0
  102. package/dist/esm/fileHead.js +27 -0
  103. package/dist/esm/formatters/eventHighlighter.js +332 -0
  104. package/dist/esm/formatters/noiseClassifier.js +209 -0
  105. package/dist/esm/formatters/sessionDump.js +328 -0
  106. package/dist/esm/formatters/toolSummary.js +226 -0
  107. package/dist/esm/formatting.js +82 -0
  108. package/dist/esm/handoffUrl.js +23 -0
  109. package/dist/esm/historicalStore.js +305 -0
  110. package/dist/esm/humanPrompt.js +88 -0
  111. package/dist/esm/index.js +213 -0
  112. package/dist/esm/keepAlive.js +189 -0
  113. package/dist/esm/liveCredentialGuard.js +33 -0
  114. package/dist/esm/modelAliases.js +51 -0
  115. package/dist/esm/modelCatalog.js +139 -0
  116. package/dist/esm/modelContext.js +280 -0
  117. package/dist/esm/modelInfo.js +832 -0
  118. package/dist/esm/multiProviderQuotaService.js +450 -0
  119. package/dist/esm/node.js +46 -0
  120. package/dist/esm/observedContextWindows.js +165 -0
  121. package/dist/esm/observedSessionCollector.js +939 -0
  122. package/dist/esm/parsers/changelogParser.js +71 -0
  123. package/dist/esm/parsers/codexHistory.js +89 -0
  124. package/dist/esm/parsers/codexParser.js +906 -0
  125. package/dist/esm/parsers/debugLogParser.js +126 -0
  126. package/dist/esm/parsers/jsonl.js +66 -0
  127. package/dist/esm/parsers/mcpToolName.js +28 -0
  128. package/dist/esm/parsers/openCodeParser.js +625 -0
  129. package/dist/esm/parsers/planExtractor.js +335 -0
  130. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  131. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  132. package/dist/esm/parsers/subagentScanner.js +360 -0
  133. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  134. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  135. package/dist/esm/paths.js +134 -0
  136. package/dist/esm/peakHours.js +194 -0
  137. package/dist/esm/phrases.js +1330 -0
  138. package/dist/esm/pricingCatalog.js +247 -0
  139. package/dist/esm/processDetection.js +200 -0
  140. package/dist/esm/projectMigration.js +59 -0
  141. package/dist/esm/promptHistory.js +1221 -0
  142. package/dist/esm/providerFailure.js +362 -0
  143. package/dist/esm/providerIds.js +6 -0
  144. package/dist/esm/providerQuota.js +1 -0
  145. package/dist/esm/providerServiceStatus.js +145 -0
  146. package/dist/esm/providerServiceStatusTypes.js +1 -0
  147. package/dist/esm/providerStatus.js +117 -0
  148. package/dist/esm/providers/claudeCode.js +575 -0
  149. package/dist/esm/providers/claudeListing.js +121 -0
  150. package/dist/esm/providers/codex.js +1034 -0
  151. package/dist/esm/providers/codexDatabase.js +297 -0
  152. package/dist/esm/providers/codexRollouts.js +215 -0
  153. package/dist/esm/providers/detect.js +185 -0
  154. package/dist/esm/providers/diagnostics.js +83 -0
  155. package/dist/esm/providers/directoryListingCache.js +206 -0
  156. package/dist/esm/providers/factory.js +54 -0
  157. package/dist/esm/providers/openCode.js +2008 -0
  158. package/dist/esm/providers/openCodeDatabase.js +419 -0
  159. package/dist/esm/providers/rolloutWalker.js +252 -0
  160. package/dist/esm/providers/types.js +12 -0
  161. package/dist/esm/quota.js +182 -0
  162. package/dist/esm/quotaHistory.js +364 -0
  163. package/dist/esm/quotaPoller.js +230 -0
  164. package/dist/esm/quotaPresentation.js +97 -0
  165. package/dist/esm/quotaResolver.js +231 -0
  166. package/dist/esm/quotaSnapshots.js +130 -0
  167. package/dist/esm/quotaThresholds.js +85 -0
  168. package/dist/esm/readers/decisions.js +24 -0
  169. package/dist/esm/readers/handoff.js +20 -0
  170. package/dist/esm/readers/helpers.js +27 -0
  171. package/dist/esm/readers/history.js +9 -0
  172. package/dist/esm/readers/notes.js +48 -0
  173. package/dist/esm/readers/plans.js +225 -0
  174. package/dist/esm/readers/tasks.js +19 -0
  175. package/dist/esm/report/htmlHelpers.js +280 -0
  176. package/dist/esm/report/htmlReportGenerator.js +704 -0
  177. package/dist/esm/report/index.js +7 -0
  178. package/dist/esm/report/logo.js +2 -0
  179. package/dist/esm/report/openBrowser.js +19 -0
  180. package/dist/esm/report/sessionReportInputs.js +33 -0
  181. package/dist/esm/report/transcriptParser.js +83 -0
  182. package/dist/esm/report/types.js +4 -0
  183. package/dist/esm/schemas/accountManager.js +204 -0
  184. package/dist/esm/schemas/accountStatus.js +25 -0
  185. package/dist/esm/schemas/assistantTurn.js +74 -0
  186. package/dist/esm/schemas/index.js +25 -0
  187. package/dist/esm/schemas/observedSessionV1.js +77 -0
  188. package/dist/esm/schemas/providerFailure.js +37 -0
  189. package/dist/esm/schemas/quota.js +132 -0
  190. package/dist/esm/schemas/quotaHistory.js +40 -0
  191. package/dist/esm/schemas/sessionEvent.js +216 -0
  192. package/dist/esm/schemas/stateFile.js +75 -0
  193. package/dist/esm/schemas/tokenEstimation.js +7 -0
  194. package/dist/esm/schemas/transcript.js +86 -0
  195. package/dist/esm/schemas/usageNormalization.js +38 -0
  196. package/dist/esm/search/advancedFilter.js +197 -0
  197. package/dist/esm/search/sessionSearch.js +66 -0
  198. package/dist/esm/sessionFingerprint.js +40 -0
  199. package/dist/esm/sessionMonitor.js +198 -0
  200. package/dist/esm/sessionPreviews.js +522 -0
  201. package/dist/esm/sessionPromptHistory.js +254 -0
  202. package/dist/esm/sessionPromptSignals.js +364 -0
  203. package/dist/esm/sessionStats.js +160 -0
  204. package/dist/esm/sessionTokenTotals.js +98 -0
  205. package/dist/esm/sessionTranscripts.js +49 -0
  206. package/dist/esm/stateFile.js +119 -0
  207. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  208. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  209. package/dist/esm/statusline/formatter.js +122 -0
  210. package/dist/esm/statusline/index.js +8 -0
  211. package/dist/esm/terminalSync.js +95 -0
  212. package/dist/esm/testing/credentialGuard.js +197 -0
  213. package/dist/esm/testing/index.js +12 -0
  214. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  215. package/dist/esm/tokenEstimation.js +67 -0
  216. package/dist/esm/tokenSummary.js +113 -0
  217. package/dist/esm/toolCategory.js +82 -0
  218. package/dist/esm/transcript.js +257 -0
  219. package/dist/esm/turns/assistantTurn.js +373 -0
  220. package/dist/esm/types/codex.js +23 -0
  221. package/dist/esm/types/decisionLog.js +5 -0
  222. package/dist/esm/types/historicalData.js +47 -0
  223. package/dist/esm/types/knowledgeNote.js +15 -0
  224. package/dist/esm/types/observedSessionV1.js +257 -0
  225. package/dist/esm/types/opencode.js +12 -0
  226. package/dist/esm/types/plan.js +7 -0
  227. package/dist/esm/types/sessionEvent.js +10 -0
  228. package/dist/esm/types/taskPersistence.js +12 -0
  229. package/dist/esm/usage/billingBlocks.js +148 -0
  230. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  231. package/dist/esm/usage/importSessionHistory.js +84 -0
  232. package/dist/esm/usage/subagentUsage.js +18 -0
  233. package/dist/esm/usage/usageEvents.js +402 -0
  234. package/dist/esm/usage/usageReports.js +202 -0
  235. package/dist/esm/usageNormalization.js +95 -0
  236. package/dist/esm/watchers/eventBridge.js +219 -0
  237. package/dist/esm/watchers/factory.js +63 -0
  238. package/dist/esm/watchers/jsonlTail.js +289 -0
  239. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  240. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  241. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  242. package/dist/esm/watchers/types.js +4 -0
  243. package/dist/esm/workspaceRoots.js +56 -0
  244. package/dist/esm/writers/atomic.js +375 -0
  245. package/dist/esm/writers/decisions.js +51 -0
  246. package/dist/esm/writers/notes.js +46 -0
  247. package/dist/esm/writers/tasks.js +63 -0
  248. package/dist/esm/zaiQuota.js +323 -0
  249. package/dist/esm/zaiQuotaApi.js +271 -0
  250. package/dist/esm/zaiQuotaWatcher.js +265 -0
  251. package/dist/fileHead.d.ts +22 -0
  252. package/dist/fileHead.js +33 -0
  253. package/dist/index.d.ts +30 -11
  254. package/dist/index.js +72 -15
  255. package/dist/keepAlive.d.ts +2 -1
  256. package/dist/keepAlive.js +116 -29
  257. package/dist/liveCredentialGuard.d.ts +28 -0
  258. package/dist/liveCredentialGuard.js +36 -0
  259. package/dist/node.d.ts +13 -1
  260. package/dist/node.js +43 -2
  261. package/dist/parsers/codexHistory.js +2 -2
  262. package/dist/parsers/codexParser.d.ts +12 -0
  263. package/dist/parsers/codexParser.js +34 -12
  264. package/dist/parsers/debugLogParser.js +2 -2
  265. package/dist/parsers/sessionPathResolver.js +5 -4
  266. package/dist/processDetection.d.ts +10 -3
  267. package/dist/processDetection.js +44 -11
  268. package/dist/promptHistory.d.ts +32 -0
  269. package/dist/promptHistory.js +115 -59
  270. package/dist/providerFailure.d.ts +12 -6
  271. package/dist/providerFailure.js +45 -0
  272. package/dist/providers/claudeCode.js +7 -99
  273. package/dist/providers/claudeListing.d.ts +22 -0
  274. package/dist/providers/claudeListing.js +158 -0
  275. package/dist/providers/codex.d.ts +3 -1
  276. package/dist/providers/codex.js +26 -227
  277. package/dist/providers/codexRollouts.d.ts +44 -0
  278. package/dist/providers/codexRollouts.js +261 -0
  279. package/dist/providers/detect.js +4 -4
  280. package/dist/readers/plans.js +3 -3
  281. package/dist/schemas/accountManager.d.ts +22 -0
  282. package/dist/schemas/accountManager.js +4 -0
  283. package/dist/schemas/assistantTurn.d.ts +2 -2
  284. package/dist/schemas/index.d.ts +2 -0
  285. package/dist/schemas/index.js +7 -2
  286. package/dist/schemas/providerFailure.d.ts +154 -0
  287. package/dist/schemas/providerFailure.js +40 -0
  288. package/dist/sessionPromptHistory.d.ts +12 -7
  289. package/dist/sessionPromptHistory.js +15 -3
  290. package/dist/sessionPromptSignals.d.ts +9 -0
  291. package/dist/sessionPromptSignals.js +13 -0
  292. package/dist/terminalSync.d.ts +12 -2
  293. package/dist/terminalSync.js +27 -2
  294. package/dist/testing/credentialGuard.d.ts +23 -0
  295. package/dist/testing/credentialGuard.js +233 -0
  296. package/dist/testing/index.d.ts +14 -0
  297. package/dist/testing/index.js +22 -0
  298. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  299. package/dist/testing/sessionLogFixtures.js +169 -0
  300. package/dist/tokenEstimation.d.ts +2 -0
  301. package/dist/tokenEstimation.js +21 -11
  302. package/dist/toolCategory.d.ts +18 -0
  303. package/dist/toolCategory.js +86 -0
  304. package/dist/types/codex.d.ts +4 -0
  305. package/dist/usage/usageEvents.d.ts +16 -0
  306. package/dist/usage/usageEvents.js +53 -9
  307. package/dist/watchers/jsonlTail.d.ts +26 -1
  308. package/dist/watchers/jsonlTail.js +136 -28
  309. package/dist/watchers/jsonlWatcher.js +7 -0
  310. package/dist/watchers/providerReaderWatcher.js +1 -0
  311. package/dist/watchers/types.d.ts +6 -0
  312. package/dist/workspaceRoots.d.ts +23 -0
  313. package/dist/workspaceRoots.js +93 -0
  314. package/package.json +22 -2
@@ -0,0 +1,360 @@
1
+ /**
2
+ * @fileoverview Scanner for subagent JSONL files (sidekick-shared).
3
+ *
4
+ * This module provides functionality to scan Claude Code subagent session files
5
+ * located in <sessionDir>/<sessionId>/subagents/agent-*.jsonl and extract
6
+ * tool calls made by subagents.
7
+ *
8
+ * @module parsers/subagentScanner
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { ClaudeUsageDeduper, dedupedRawClaudeUsage } from '../usage/claudeUsageDedupe';
13
+ import { addUsageToSubagent } from '../usage/subagentUsage';
14
+ import { cachedSubagentStats } from './subagentStatsCache';
15
+ /**
16
+ * Pattern for matching subagent JSONL files.
17
+ * Files are named like: agent-<hash>.jsonl
18
+ */
19
+ const AGENT_FILE_PATTERN = /^agent-(.+)\.jsonl$/;
20
+ /**
21
+ * Reads `agent-<id>.meta.json` (written by Claude Code next to the transcript)
22
+ * for the spawn's agent type and description. Missing or malformed files
23
+ * yield an empty object.
24
+ */
25
+ export function readAgentMeta(agentFilePath) {
26
+ try {
27
+ const metaPath = agentFilePath.replace(/\.jsonl$/, '.meta.json');
28
+ const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
29
+ return {
30
+ ...(typeof parsed.agentType === 'string' && parsed.agentType
31
+ ? { agentType: parsed.agentType }
32
+ : {}),
33
+ ...(typeof parsed.description === 'string' && parsed.description
34
+ ? { description: parsed.description }
35
+ : {}),
36
+ };
37
+ }
38
+ catch {
39
+ return {};
40
+ }
41
+ }
42
+ /** Task-related tool names */
43
+ const TASK_TOOLS = ['TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList'];
44
+ /**
45
+ * Extracts task ID from TaskCreate result content.
46
+ *
47
+ * Looks for "Task #N" or JSON taskId patterns in the result string.
48
+ */
49
+ function extractTaskIdFromResult(resultContent) {
50
+ const resultStr = typeof resultContent === 'string' ? resultContent : JSON.stringify(resultContent || '');
51
+ // Try to match "Task #N" pattern
52
+ const taskIdMatch = resultStr.match(/Task #(\d+)/i);
53
+ if (taskIdMatch) {
54
+ return taskIdMatch[1];
55
+ }
56
+ // Try to match taskId in JSON-like content
57
+ const jsonIdMatch = resultStr.match(/"taskId"\s*:\s*"?(\d+)"?/i);
58
+ if (jsonIdMatch) {
59
+ return jsonIdMatch[1];
60
+ }
61
+ return null;
62
+ }
63
+ /** Tracks directories already reported as missing to avoid log spam */
64
+ const reportedMissing = new Set();
65
+ /**
66
+ * Scans subagent directory for agent JSONL files and extracts tool calls.
67
+ *
68
+ * Subagent files are located at: <sessionDir>/<sessionId>/subagents/agent-*.jsonl
69
+ *
70
+ * @param sessionDir - Directory containing the session file
71
+ * @param sessionId - Session ID (filename without .jsonl extension)
72
+ * @param logger - Optional logging function; messages are silently discarded if not provided
73
+ * @returns Array of SubagentStats with tool calls, empty array if no subagents
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const stats = scanSubagentDir('/home/user/.claude/projects/myproject', 'abc123');
78
+ * for (const agent of stats) {
79
+ * console.log(`Agent ${agent.agentId}: ${agent.toolCalls.length} tool calls`);
80
+ * }
81
+ * ```
82
+ */
83
+ export function scanSubagentDir(sessionDir, sessionId, logger, skipFiles = new Set()) {
84
+ const log = logger || (() => { });
85
+ const subagentsDir = path.join(sessionDir, sessionId, 'subagents');
86
+ const results = [];
87
+ try {
88
+ if (!fs.existsSync(subagentsDir)) {
89
+ if (!reportedMissing.has(subagentsDir)) {
90
+ reportedMissing.add(subagentsDir);
91
+ log(`[SubagentScanner] No subagents directory: ${subagentsDir}`);
92
+ }
93
+ return results;
94
+ }
95
+ // Directory appeared — clear the missing flag so future disappearances get logged
96
+ reportedMissing.delete(subagentsDir);
97
+ const files = fs.readdirSync(subagentsDir);
98
+ log(`[SubagentScanner] Found ${files.length} files: ${files.join(', ')}`);
99
+ for (const file of files) {
100
+ if (skipFiles.has(file))
101
+ continue;
102
+ const match = file.match(AGENT_FILE_PATTERN);
103
+ if (!match) {
104
+ continue;
105
+ }
106
+ const agentId = match[1];
107
+ const filePath = path.join(subagentsDir, file);
108
+ const agentStats = cachedSubagentStats(filePath, () => parseAgentFile(filePath, agentId, log));
109
+ if (agentStats) {
110
+ log(`[SubagentScanner] Agent ${agentId}: ${agentStats.toolCalls.length} tool calls`);
111
+ results.push(agentStats);
112
+ }
113
+ else {
114
+ log(`[SubagentScanner] Agent ${agentId}: no stats returned`);
115
+ }
116
+ }
117
+ }
118
+ catch {
119
+ // Directory read failed - subagents dir may not exist yet
120
+ log(`Failed to scan subagents directory: ${subagentsDir}`);
121
+ }
122
+ log(`[SubagentScanner] Returning ${results.length} subagent stats`);
123
+ return results;
124
+ }
125
+ /**
126
+ * Parses a single subagent JSONL file and extracts tool calls and task state.
127
+ *
128
+ * @param filePath - Path to the agent JSONL file
129
+ * @param agentId - Agent ID extracted from filename
130
+ * @param log - Logging function
131
+ * @returns SubagentStats or null if parsing fails
132
+ */
133
+ function parseAgentFile(filePath, agentId, log) {
134
+ try {
135
+ const content = fs.readFileSync(filePath, 'utf-8');
136
+ const lines = content.split('\n').filter((line) => line.trim());
137
+ const toolCalls = [];
138
+ let agentType;
139
+ let description;
140
+ const tokens = { agentId, toolCalls: [], inputTokens: 0, outputTokens: 0 };
141
+ const usageDeduper = new ClaudeUsageDeduper();
142
+ let startTime;
143
+ let endTime;
144
+ // Task tracking state
145
+ const taskState = {
146
+ tasks: new Map(),
147
+ activeTaskId: null,
148
+ };
149
+ const pendingTaskCreates = new Map();
150
+ for (const line of lines) {
151
+ try {
152
+ const event = JSON.parse(line);
153
+ const eventTimestamp = new Date(event.timestamp || Date.now());
154
+ // Track start/end times
155
+ if (!startTime)
156
+ startTime = eventTimestamp;
157
+ endTime = eventTimestamp;
158
+ // Extract token usage from assistant messages. Split lines of one
159
+ // response repeat its usage, so count each response once.
160
+ const usage = dedupedRawClaudeUsage(event, usageDeduper);
161
+ if (usage)
162
+ addUsageToSubagent(tokens, usage);
163
+ // Extract agent type and description from Task tool invocation
164
+ // This appears in the parent session, but we can also look for it
165
+ // in the first system message or context
166
+ if (event.type === 'system' && event.message?.content) {
167
+ const contentStr = typeof event.message.content === 'string'
168
+ ? event.message.content
169
+ : JSON.stringify(event.message.content);
170
+ // Try to extract agent type from system message
171
+ const typeMatch = contentStr.match(/subagent_type['":\s]+(\w+)/i);
172
+ if (typeMatch) {
173
+ agentType = typeMatch[1];
174
+ }
175
+ }
176
+ // Look for tool_use events in assistant messages
177
+ if (event.type === 'assistant' && event.message?.content) {
178
+ const contentArray = Array.isArray(event.message.content) ? event.message.content : [];
179
+ for (const block of contentArray) {
180
+ if (block && typeof block === 'object' && block.type === 'tool_use') {
181
+ const toolUse = block;
182
+ const toolCall = {
183
+ name: toolUse.name,
184
+ input: toolUse.input || {},
185
+ timestamp: eventTimestamp,
186
+ };
187
+ // Handle task tools
188
+ if (toolUse.name === 'TaskCreate') {
189
+ pendingTaskCreates.set(toolUse.id, {
190
+ subject: String(toolUse.input.subject || ''),
191
+ description: toolUse.input.description
192
+ ? String(toolUse.input.description)
193
+ : undefined,
194
+ activeForm: toolUse.input.activeForm
195
+ ? String(toolUse.input.activeForm)
196
+ : undefined,
197
+ timestamp: eventTimestamp,
198
+ });
199
+ }
200
+ else if (toolUse.name === 'TaskUpdate') {
201
+ handleTaskUpdate(taskState, toolUse.input, eventTimestamp);
202
+ }
203
+ // Associate non-task tool calls with active task
204
+ if (!TASK_TOOLS.includes(toolUse.name) && taskState.activeTaskId) {
205
+ const activeTask = taskState.tasks.get(taskState.activeTaskId);
206
+ if (activeTask) {
207
+ activeTask.associatedToolCalls.push(toolCall);
208
+ }
209
+ }
210
+ toolCalls.push(toolCall);
211
+ // If this is a Task tool call (spawning another subagent), extract info
212
+ if (toolUse.name === 'Task' && toolUse.input) {
213
+ if (toolUse.input.subagent_type && !agentType) {
214
+ agentType = String(toolUse.input.subagent_type);
215
+ }
216
+ if (toolUse.input.description && !description) {
217
+ description = String(toolUse.input.description);
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+ // Look for tool_result events in user messages
224
+ if (event.type === 'user' && event.message?.content) {
225
+ const contentArray = Array.isArray(event.message.content) ? event.message.content : [];
226
+ for (const block of contentArray) {
227
+ if (block && typeof block === 'object' && block.type === 'tool_result') {
228
+ const toolResult = block;
229
+ // Handle TaskCreate results
230
+ const pendingCreate = pendingTaskCreates.get(toolResult.tool_use_id);
231
+ if (pendingCreate && !toolResult.is_error) {
232
+ const taskId = extractTaskIdFromResult(toolResult.content);
233
+ if (taskId) {
234
+ const task = {
235
+ taskId,
236
+ subject: pendingCreate.subject,
237
+ description: pendingCreate.description,
238
+ status: 'pending',
239
+ createdAt: pendingCreate.timestamp,
240
+ updatedAt: eventTimestamp,
241
+ activeForm: pendingCreate.activeForm,
242
+ blockedBy: [],
243
+ blocks: [],
244
+ associatedToolCalls: [],
245
+ };
246
+ taskState.tasks.set(taskId, task);
247
+ }
248
+ pendingTaskCreates.delete(toolResult.tool_use_id);
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ catch {
255
+ // Skip malformed lines
256
+ }
257
+ }
258
+ // Claude Code writes the spawn's type and description beside the transcript.
259
+ const meta = readAgentMeta(filePath);
260
+ agentType = meta.agentType ?? agentType;
261
+ description = meta.description ?? description;
262
+ // If we found any tool calls, return the stats
263
+ if (toolCalls.length > 0 || agentType || description || (tokens.totalTokens ?? 0) > 0) {
264
+ const durationMs = startTime && endTime ? endTime.getTime() - startTime.getTime() : undefined;
265
+ return {
266
+ agentId,
267
+ agentType,
268
+ description,
269
+ toolCalls,
270
+ taskState: taskState.tasks.size > 0 ? taskState : undefined,
271
+ inputTokens: tokens.inputTokens,
272
+ outputTokens: tokens.outputTokens,
273
+ cacheReadTokens: tokens.cacheReadTokens ?? 0,
274
+ cacheWriteTokens: tokens.cacheWriteTokens ?? 0,
275
+ reasoningTokens: tokens.reasoningTokens ?? 0,
276
+ totalTokens: tokens.totalTokens ?? 0,
277
+ startTime,
278
+ endTime,
279
+ durationMs,
280
+ };
281
+ }
282
+ return null;
283
+ }
284
+ catch {
285
+ log(`Failed to parse agent file: ${filePath}`);
286
+ return null;
287
+ }
288
+ }
289
+ /**
290
+ * Handles TaskUpdate tool input and updates task state.
291
+ */
292
+ export function handleTaskUpdate(taskState, input, timestamp) {
293
+ const taskId = String(input.taskId || '');
294
+ let task = taskState.tasks.get(taskId);
295
+ if (!task) {
296
+ // Create placeholder task for unknown TaskUpdate
297
+ task = {
298
+ taskId,
299
+ subject: input.subject ? String(input.subject) : `Task ${taskId}`,
300
+ description: input.description ? String(input.description) : undefined,
301
+ status: 'pending',
302
+ createdAt: timestamp,
303
+ updatedAt: timestamp,
304
+ blockedBy: [],
305
+ blocks: [],
306
+ associatedToolCalls: [],
307
+ };
308
+ taskState.tasks.set(taskId, task);
309
+ }
310
+ // Update task fields
311
+ if (input.status) {
312
+ const newStatus = input.status;
313
+ const oldStatus = task.status;
314
+ task.status = newStatus;
315
+ // Track active task transitions
316
+ if (newStatus === 'in_progress' && oldStatus !== 'in_progress') {
317
+ taskState.activeTaskId = taskId;
318
+ }
319
+ else if (oldStatus === 'in_progress' && newStatus !== 'in_progress') {
320
+ if (taskState.activeTaskId === taskId) {
321
+ taskState.activeTaskId = null;
322
+ }
323
+ }
324
+ }
325
+ if (input.subject)
326
+ task.subject = String(input.subject);
327
+ if (input.description)
328
+ task.description = String(input.description);
329
+ if (input.activeForm)
330
+ task.activeForm = String(input.activeForm);
331
+ if (Array.isArray(input.addBlockedBy)) {
332
+ for (const id of input.addBlockedBy) {
333
+ const idStr = String(id);
334
+ if (!task.blockedBy.includes(idStr)) {
335
+ task.blockedBy.push(idStr);
336
+ }
337
+ }
338
+ }
339
+ if (Array.isArray(input.addBlocks)) {
340
+ for (const id of input.addBlocks) {
341
+ const idStr = String(id);
342
+ if (!task.blocks.includes(idStr)) {
343
+ task.blocks.push(idStr);
344
+ }
345
+ }
346
+ }
347
+ task.updatedAt = timestamp;
348
+ }
349
+ /**
350
+ * Extracts agent type and description from a Task tool call input.
351
+ *
352
+ * @param input - Task tool input parameters
353
+ * @returns Object with agentType and description if found
354
+ */
355
+ export function extractTaskInfo(input) {
356
+ return {
357
+ agentType: input.subagent_type ? String(input.subagent_type) : undefined,
358
+ description: input.description ? String(input.description) : undefined,
359
+ };
360
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Memoizes per-transcript subagent stats by file size and mtime.
3
+ *
4
+ * Dashboards rescan a session's subagents on a timer, and one parent can own
5
+ * a hundred subagent transcripts of several MB each. An unchanged transcript
6
+ * now costs one `stat` instead of a full parse. Callers treat the returned
7
+ * stats as read-only.
8
+ *
9
+ * Node-only: stats files.
10
+ *
11
+ * @module parsers/subagentStatsCache
12
+ */
13
+ import * as fs from 'fs';
14
+ const MAX_ENTRIES = 1024;
15
+ const cache = new Map();
16
+ /** Stats for one subagent transcript, parsed again only when the file changed. */
17
+ export function cachedSubagentStats(filePath, compute) {
18
+ let stat;
19
+ try {
20
+ stat = fs.statSync(filePath);
21
+ }
22
+ catch {
23
+ cache.delete(filePath);
24
+ return compute();
25
+ }
26
+ const hit = cache.get(filePath);
27
+ if (hit && hit.size === stat.size && hit.mtimeMs === stat.mtimeMs) {
28
+ cache.delete(filePath);
29
+ cache.set(filePath, hit);
30
+ return hit.stats;
31
+ }
32
+ const stats = compute();
33
+ cache.delete(filePath);
34
+ cache.set(filePath, { size: stat.size, mtimeMs: stat.mtimeMs, stats });
35
+ if (cache.size > MAX_ENTRIES) {
36
+ const oldest = cache.keys().next().value;
37
+ if (oldest !== undefined)
38
+ cache.delete(oldest);
39
+ }
40
+ return stats;
41
+ }
42
+ /** @internal Test hook. */
43
+ export function clearSubagentStatsCache() {
44
+ cache.clear();
45
+ }