sidekick-shared 0.21.1 → 0.21.3

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 (77) hide show
  1. package/README.md +45 -45
  2. package/dist/accountManager.js +9 -7
  3. package/dist/accountRegistry.js +18 -14
  4. package/dist/accountStatus.js +4 -1
  5. package/dist/accounts.js +60 -25
  6. package/dist/aggregation/EventAggregator.d.ts +14 -11
  7. package/dist/aggregation/EventAggregator.js +68 -31
  8. package/dist/aggregation/PatternExtractor.js +5 -4
  9. package/dist/autoSwitch.js +6 -12
  10. package/dist/claudeProfiles.js +3 -3
  11. package/dist/codexProfiles.js +45 -22
  12. package/dist/codexQuota.js +44 -16
  13. package/dist/codexQuotaWatcher.js +10 -3
  14. package/dist/context/composer.js +8 -6
  15. package/dist/context/sessionContext.js +12 -9
  16. package/dist/credentialIO.js +10 -8
  17. package/dist/extractors/sessionAssets.js +9 -1
  18. package/dist/extractors/sources/claudeAssets.js +7 -1
  19. package/dist/extractors/sources/codexAssets.js +14 -3
  20. package/dist/extractors/toolCall.js +1 -4
  21. package/dist/formatters/eventHighlighter.js +148 -28
  22. package/dist/formatters/noiseClassifier.js +3 -11
  23. package/dist/formatters/sessionDump.js +22 -13
  24. package/dist/formatters/toolSummary.js +1 -3
  25. package/dist/index.d.ts +25 -23
  26. package/dist/index.js +12 -3
  27. package/dist/modelContext.js +10 -8
  28. package/dist/modelInfo.js +11 -6
  29. package/dist/multiProviderQuotaService.js +10 -7
  30. package/dist/parsers/codexParser.js +170 -103
  31. package/dist/parsers/debugLogParser.js +5 -4
  32. package/dist/parsers/openCodeParser.js +66 -48
  33. package/dist/parsers/planExtractor.js +3 -3
  34. package/dist/parsers/sessionActivityDetector.js +10 -6
  35. package/dist/parsers/sessionPathResolver.js +29 -34
  36. package/dist/parsers/subagentScanner.js +21 -20
  37. package/dist/parsers/subagentTraceParser.js +7 -4
  38. package/dist/phrases.js +11 -11
  39. package/dist/providerStatus.js +3 -4
  40. package/dist/providers/claudeCode.js +3 -1
  41. package/dist/providers/codex.js +35 -14
  42. package/dist/providers/codexDatabase.js +14 -5
  43. package/dist/providers/detect.js +10 -6
  44. package/dist/providers/openCode.d.ts +4 -4
  45. package/dist/providers/openCode.js +96 -68
  46. package/dist/providers/openCodeDatabase.js +44 -17
  47. package/dist/quota.d.ts +19 -0
  48. package/dist/quota.js +50 -8
  49. package/dist/quotaHistory.js +10 -3
  50. package/dist/quotaSnapshots.d.ts +1 -1
  51. package/dist/quotaSnapshots.js +2 -2
  52. package/dist/readers/decisions.js +1 -1
  53. package/dist/readers/notes.js +5 -3
  54. package/dist/readers/plans.js +43 -23
  55. package/dist/readers/tasks.js +1 -1
  56. package/dist/report/htmlHelpers.js +135 -16
  57. package/dist/report/htmlReportGenerator.js +38 -12
  58. package/dist/report/transcriptParser.js +8 -2
  59. package/dist/schemas/index.d.ts +4 -4
  60. package/dist/schemas/quota.js +2 -10
  61. package/dist/schemas/sessionEvent.js +20 -10
  62. package/dist/search/advancedFilter.js +3 -3
  63. package/dist/search/sessionSearch.js +10 -4
  64. package/dist/terminalSync.js +4 -2
  65. package/dist/turns/assistantTurn.js +5 -1
  66. package/dist/watchers/eventBridge.js +56 -26
  67. package/dist/watchers/factory.js +5 -2
  68. package/dist/watchers/jsonlTail.js +3 -1
  69. package/dist/watchers/jsonlWatcher.js +128 -52
  70. package/dist/watchers/providerReaderWatcher.js +3 -1
  71. package/dist/watchers/sqliteWatcher.js +52 -22
  72. package/dist/zaiQuota.d.ts +6 -6
  73. package/dist/zaiQuota.js +6 -10
  74. package/dist/zaiQuotaApi.d.ts +33 -0
  75. package/dist/zaiQuotaApi.js +279 -0
  76. package/dist/zaiQuotaWatcher.js +10 -9
  77. package/package.json +5 -2
@@ -93,7 +93,8 @@ class CodexQuotaWatcher {
93
93
  running = false;
94
94
  constructor(workspacePath, options = {}) {
95
95
  this.workspacePath = workspacePath;
96
- this.discoveryPollIntervalMs = options.discoveryPollIntervalMs ?? DEFAULT_DISCOVERY_POLL_INTERVAL_MS;
96
+ this.discoveryPollIntervalMs =
97
+ options.discoveryPollIntervalMs ?? DEFAULT_DISCOVERY_POLL_INTERVAL_MS;
97
98
  this.providerFactory = options.providerFactory ?? (() => new codex_1.CodexProvider());
98
99
  this.getActiveAccount = options.getActiveAccount ?? codexProfiles_1.getActiveCodexAccount;
99
100
  this.readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
@@ -207,8 +208,14 @@ class CodexQuotaWatcher {
207
208
  runtimeProvider: 'codex',
208
209
  providerId: account.id,
209
210
  workspaceId: this.workspaceId,
210
- fiveHour: { utilization: liveQuota.fiveHour.utilization, resetsAt: liveQuota.fiveHour.resetsAt },
211
- sevenDay: { utilization: liveQuota.sevenDay.utilization, resetsAt: liveQuota.sevenDay.resetsAt },
211
+ fiveHour: {
212
+ utilization: liveQuota.fiveHour.utilization,
213
+ resetsAt: liveQuota.fiveHour.resetsAt,
214
+ },
215
+ sevenDay: {
216
+ utilization: liveQuota.sevenDay.utilization,
217
+ resetsAt: liveQuota.sevenDay.resetsAt,
218
+ },
212
219
  available: liveQuota.available,
213
220
  error: liveQuota.error,
214
221
  source: liveQuota.source,
@@ -29,7 +29,9 @@ async function composeContext(slug, fidelity, provider, workspacePath) {
29
29
  try {
30
30
  sessionSummaries.push(provider.readSessionStats(sp));
31
31
  }
32
- catch { /* skip */ }
32
+ catch {
33
+ /* skip */
34
+ }
33
35
  }
34
36
  // Apply fidelity filtering
35
37
  let tasks;
@@ -40,23 +42,23 @@ async function composeContext(slug, fidelity, provider, workspacePath) {
40
42
  case 'full':
41
43
  tasks = allTasks;
42
44
  decisions = allDecisions;
43
- notes = allNotes.filter(n => n.status === 'active' || n.status === 'needs_review');
45
+ notes = allNotes.filter((n) => n.status === 'active' || n.status === 'needs_review');
44
46
  if (history) {
45
47
  stats = { tokens: { ...history.allTime.tokens }, cost: history.allTime.totalCost };
46
48
  }
47
49
  break;
48
50
  case 'compact':
49
- tasks = allTasks.filter(t => t.status === 'pending');
51
+ tasks = allTasks.filter((t) => t.status === 'pending');
50
52
  decisions = allDecisions.slice(0, 10);
51
- notes = allNotes.filter(n => n.status === 'active');
53
+ notes = allNotes.filter((n) => n.status === 'active');
52
54
  if (history) {
53
55
  stats = { tokens: { ...history.allTime.tokens }, cost: history.allTime.totalCost };
54
56
  }
55
57
  break;
56
58
  case 'brief':
57
- tasks = allTasks.filter(t => t.status === 'pending').slice(0, 3);
59
+ tasks = allTasks.filter((t) => t.status === 'pending').slice(0, 3);
58
60
  decisions = []; // Count only
59
- notes = allNotes.filter(n => n.importance === 'critical');
61
+ notes = allNotes.filter((n) => n.importance === 'critical');
60
62
  stats = null;
61
63
  break;
62
64
  }
@@ -65,7 +65,7 @@ function buildSessionContextSnapshot(events, options = {}) {
65
65
  const allSources = sourceState.sources;
66
66
  const limitedSources = limitSources(allSources, options.sourceLimit ?? DEFAULT_CONTEXT_SOURCE_LIMIT);
67
67
  const breakdown = buildLayerBreakdown(limitedSources);
68
- const layers = breakdown.map(row => row.layer);
68
+ const layers = breakdown.map((row) => row.layer);
69
69
  const sourceTokenTotal = allSources.reduce((sum, source) => sum + source.tokenEstimate, 0);
70
70
  const contextTokens = metrics.currentContextSize > 0 ? metrics.currentContextSize : sourceTokenTotal;
71
71
  const { pressure, ratio } = calculateSessionContextPressure(contextTokens, contextWindow);
@@ -381,16 +381,16 @@ function limitSources(sources, limit) {
381
381
  return [];
382
382
  if (sources.length <= limit)
383
383
  return [...sources];
384
- const pinned = sources.filter(source => source.sourceType === 'system' ||
384
+ const pinned = sources.filter((source) => source.sourceType === 'system' ||
385
385
  source.sourceType === 'runtime' ||
386
386
  source.sourceType === 'rate_limit');
387
387
  const pinnedLimit = Math.min(pinned.length, Math.max(1, Math.floor(limit / 4)));
388
388
  const pinnedKept = pinned.slice(0, pinnedLimit);
389
- const pinnedIds = new Set(pinnedKept.map(source => source.id));
390
- const remaining = sources.filter(source => !pinnedIds.has(source.id));
389
+ const pinnedIds = new Set(pinnedKept.map((source) => source.id));
390
+ const remaining = sources.filter((source) => !pinnedIds.has(source.id));
391
391
  const latest = remaining.slice(-(limit - pinnedKept.length));
392
- const keepIds = new Set([...pinnedKept, ...latest].map(source => source.id));
393
- return sources.filter(source => keepIds.has(source.id));
392
+ const keepIds = new Set([...pinnedKept, ...latest].map((source) => source.id));
393
+ return sources.filter((source) => keepIds.has(source.id));
394
394
  }
395
395
  function latestModel(events) {
396
396
  for (let i = events.length - 1; i >= 0; i--) {
@@ -440,7 +440,7 @@ function inferSourceFile(input) {
440
440
  }
441
441
  const files = input.files;
442
442
  if (Array.isArray(files)) {
443
- const first = files.find(value => typeof value === 'string' && value.trim());
443
+ const first = files.find((value) => typeof value === 'string' && value.trim());
444
444
  if (typeof first === 'string')
445
445
  return first.trim();
446
446
  }
@@ -451,7 +451,10 @@ function inferCommand(input) {
451
451
  if (typeof command === 'string' && command.trim())
452
452
  return command.trim();
453
453
  if (Array.isArray(command)) {
454
- return command.map(part => String(part)).join(' ').trim() || undefined;
454
+ return (command
455
+ .map((part) => String(part))
456
+ .join(' ')
457
+ .trim() || undefined);
455
458
  }
456
459
  return undefined;
457
460
  }
@@ -469,7 +472,7 @@ function extractText(content) {
469
472
  return content;
470
473
  if (Array.isArray(content)) {
471
474
  return content
472
- .map(block => isRecord(block) ? blockText(block) : '')
475
+ .map((block) => (isRecord(block) ? blockText(block) : ''))
473
476
  .filter(Boolean)
474
477
  .join('\n');
475
478
  }
@@ -66,9 +66,7 @@ function getCredentialsFilePath(configDir) {
66
66
  function readActiveCredentials(configDir) {
67
67
  if (process.platform === 'darwin') {
68
68
  try {
69
- const raw = (0, child_process_1.execFileSync)('security', [
70
- 'find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w',
71
- ], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
69
+ const raw = (0, child_process_1.execFileSync)('security', ['find-generic-password', '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir), '-w'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 4000, killSignal: 'SIGKILL' });
72
70
  return JSON.parse(raw.trim());
73
71
  }
74
72
  catch {
@@ -96,11 +94,15 @@ function writeActiveCredentials(credentials, configDir) {
96
94
  JSON.parse(json); // validate round-trip
97
95
  if (process.platform === 'darwin') {
98
96
  (0, child_process_1.execFileSync)('security', [
99
- 'add-generic-password', '-U',
100
- '-s', (0, claudeProfiles_1.claudeKeychainService)(configDir),
101
- '-a', process.env.USER || 'user',
102
- '-w', json,
103
- ], { stdio: ['pipe', 'pipe', 'pipe'] });
97
+ 'add-generic-password',
98
+ '-U',
99
+ '-s',
100
+ (0, claudeProfiles_1.claudeKeychainService)(configDir),
101
+ '-a',
102
+ process.env.USER || 'user',
103
+ '-w',
104
+ json,
105
+ ], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 4000, killSignal: 'SIGKILL' });
104
106
  return;
105
107
  }
106
108
  // Linux / WSL / Windows — file-based
@@ -62,7 +62,15 @@ const os = __importStar(require("node:os"));
62
62
  const node_path_1 = require("node:path");
63
63
  const URL_RE = /(?:https?|file):\/\/[^\s"'<>)\]`\\]+/g;
64
64
  const PATH_RE = /(?:~|\.{0,2}\/)?[A-Za-z0-9_@.-]*(?:\/[A-Za-z0-9_@.-]+)+(?::\d+){0,2}|[A-Za-z0-9_@-]+\.[A-Za-z0-9]{1,8}(?::\d+){0,2}/g;
65
- const SHELL_TAGS = new Set(['sh', 'bash', 'shell', 'zsh', 'console', 'shellscript', 'shell-session']);
65
+ const SHELL_TAGS = new Set([
66
+ 'sh',
67
+ 'bash',
68
+ 'shell',
69
+ 'zsh',
70
+ 'console',
71
+ 'shellscript',
72
+ 'shell-session',
73
+ ]);
66
74
  exports.DEFAULT_CAPS = {
67
75
  command: 60,
68
76
  path: 60,
@@ -149,7 +149,13 @@ function accumClaude(filePath, cwd, acc) {
149
149
  }
150
150
  function readClaudeAssets(cwd, limit = 3) {
151
151
  const sessions = claudeSessions(cwd, limit);
152
- const acc = { urls: [], paths: [], commands: [], plans: [], hadSession: sessions.length > 0 };
152
+ const acc = {
153
+ urls: [],
154
+ paths: [],
155
+ commands: [],
156
+ plans: [],
157
+ hadSession: sessions.length > 0,
158
+ };
153
159
  for (const session of sessions)
154
160
  accumClaude(session, cwd, acc);
155
161
  return acc;
@@ -99,7 +99,9 @@ function rolloutFiles(limit = 150) {
99
99
  if (entry.isDirectory()) {
100
100
  walk(fullPath);
101
101
  }
102
- else if (entry.isFile() && entry.name.startsWith('rollout-') && entry.name.endsWith('.jsonl')) {
102
+ else if (entry.isFile() &&
103
+ entry.name.startsWith('rollout-') &&
104
+ entry.name.endsWith('.jsonl')) {
103
105
  try {
104
106
  if (seen.has(fullPath))
105
107
  continue;
@@ -117,7 +119,10 @@ function rolloutFiles(limit = 150) {
117
119
  if (dirExists(sessionsDir))
118
120
  walk(sessionsDir);
119
121
  }
120
- return entries.sort((a, b) => b.mtime - a.mtime).slice(0, limit).map((entry) => entry.path);
122
+ return entries
123
+ .sort((a, b) => b.mtime - a.mtime)
124
+ .slice(0, limit)
125
+ .map((entry) => entry.path);
121
126
  }
122
127
  function codexSessions(cwd, limit = 3) {
123
128
  const exactCwd = (0, node_path_1.resolve)(cwd);
@@ -182,7 +187,13 @@ function addExecutedCommandAssets(acc, command, cwd, timestamp, meta) {
182
187
  function readCodexAssets(cwd, limit = 3) {
183
188
  const exactCwd = (0, node_path_1.resolve)(cwd);
184
189
  const files = codexSessions(exactCwd, limit);
185
- const acc = { urls: [], paths: [], commands: [], plans: [], hadSession: files.length > 0 };
190
+ const acc = {
191
+ urls: [],
192
+ paths: [],
193
+ commands: [],
194
+ plans: [],
195
+ hadSession: files.length > 0,
196
+ };
186
197
  const skip = (line) => line.includes('"type":"function_call_output"') ||
187
198
  line.includes('"type":"reasoning"') ||
188
199
  line.includes('"type":"token_count"');
@@ -37,10 +37,7 @@ function extractToolCalls(event) {
37
37
  const timestamp = new Date(event.timestamp);
38
38
  const calls = [];
39
39
  for (const block of content) {
40
- if (block &&
41
- typeof block === 'object' &&
42
- 'type' in block &&
43
- block.type === 'tool_use') {
40
+ if (block && typeof block === 'object' && 'type' in block && block.type === 'tool_use') {
44
41
  const toolBlock = block;
45
42
  if (toolBlock.name) {
46
43
  calls.push({
@@ -46,43 +46,163 @@ const HTML_COLORS = {
46
46
  };
47
47
  function getColors(format) {
48
48
  switch (format) {
49
- case 'blessed': return BLESSED_COLORS;
50
- case 'ansi': return ANSI_COLORS;
51
- case 'html': return HTML_COLORS;
49
+ case 'blessed':
50
+ return BLESSED_COLORS;
51
+ case 'ansi':
52
+ return ANSI_COLORS;
53
+ case 'html':
54
+ return HTML_COLORS;
52
55
  }
53
56
  }
54
57
  const ERROR_KEYWORDS = new Set([
55
- 'error', 'errors', 'err', 'fail', 'failed', 'failure', 'failures',
56
- 'fatal', 'panic', 'crash', 'crashed', 'exception', 'exceptions',
57
- 'critical', 'alert', 'abort', 'aborted', 'denied', 'forbidden',
58
- 'rejected', 'invalid', 'illegal', 'corrupt', 'corrupted',
59
- 'broken', 'bug', 'segfault', 'timeout', 'timedout', 'timed-out',
60
- 'deadlock', 'overflow', 'underflow', 'null', 'undefined', 'nan',
61
- 'unhandled', 'uncaught', 'unreachable', 'missing', 'notfound',
62
- 'not-found', 'unavailable', 'disconnected', 'dropped',
58
+ 'error',
59
+ 'errors',
60
+ 'err',
61
+ 'fail',
62
+ 'failed',
63
+ 'failure',
64
+ 'failures',
65
+ 'fatal',
66
+ 'panic',
67
+ 'crash',
68
+ 'crashed',
69
+ 'exception',
70
+ 'exceptions',
71
+ 'critical',
72
+ 'alert',
73
+ 'abort',
74
+ 'aborted',
75
+ 'denied',
76
+ 'forbidden',
77
+ 'rejected',
78
+ 'invalid',
79
+ 'illegal',
80
+ 'corrupt',
81
+ 'corrupted',
82
+ 'broken',
83
+ 'bug',
84
+ 'segfault',
85
+ 'timeout',
86
+ 'timedout',
87
+ 'timed-out',
88
+ 'deadlock',
89
+ 'overflow',
90
+ 'underflow',
91
+ 'null',
92
+ 'undefined',
93
+ 'nan',
94
+ 'unhandled',
95
+ 'uncaught',
96
+ 'unreachable',
97
+ 'missing',
98
+ 'notfound',
99
+ 'not-found',
100
+ 'unavailable',
101
+ 'disconnected',
102
+ 'dropped',
63
103
  ]);
64
104
  const SUCCESS_KEYWORDS = new Set([
65
- 'success', 'successful', 'succeeded', 'ok', 'done', 'complete',
66
- 'completed', 'passed', 'pass', 'resolved', 'fixed', 'ready',
67
- 'active', 'running', 'started', 'created', 'connected',
68
- 'established', 'accepted', 'approved', 'merged', 'deployed',
69
- 'installed', 'loaded', 'enabled', 'healthy', 'valid', 'verified',
105
+ 'success',
106
+ 'successful',
107
+ 'succeeded',
108
+ 'ok',
109
+ 'done',
110
+ 'complete',
111
+ 'completed',
112
+ 'passed',
113
+ 'pass',
114
+ 'resolved',
115
+ 'fixed',
116
+ 'ready',
117
+ 'active',
118
+ 'running',
119
+ 'started',
120
+ 'created',
121
+ 'connected',
122
+ 'established',
123
+ 'accepted',
124
+ 'approved',
125
+ 'merged',
126
+ 'deployed',
127
+ 'installed',
128
+ 'loaded',
129
+ 'enabled',
130
+ 'healthy',
131
+ 'valid',
132
+ 'verified',
70
133
  ]);
71
134
  const WARNING_KEYWORDS = new Set([
72
- 'warn', 'warning', 'warnings', 'caution', 'deprecated',
73
- 'slow', 'retry', 'retrying', 'retries', 'pending', 'waiting',
74
- 'queued', 'stale', 'expired', 'expiring', 'limited',
75
- 'throttled', 'throttling', 'degraded', 'unstable', 'flaky',
76
- 'skipped', 'skip', 'ignored', 'unknown', 'unrecognized',
135
+ 'warn',
136
+ 'warning',
137
+ 'warnings',
138
+ 'caution',
139
+ 'deprecated',
140
+ 'slow',
141
+ 'retry',
142
+ 'retrying',
143
+ 'retries',
144
+ 'pending',
145
+ 'waiting',
146
+ 'queued',
147
+ 'stale',
148
+ 'expired',
149
+ 'expiring',
150
+ 'limited',
151
+ 'throttled',
152
+ 'throttling',
153
+ 'degraded',
154
+ 'unstable',
155
+ 'flaky',
156
+ 'skipped',
157
+ 'skip',
158
+ 'ignored',
159
+ 'unknown',
160
+ 'unrecognized',
77
161
  ]);
78
162
  const ACTION_KEYWORDS = new Set([
79
- 'read', 'write', 'edit', 'delete', 'create', 'update', 'insert',
80
- 'remove', 'add', 'modify', 'patch', 'merge', 'push', 'pull',
81
- 'fetch', 'send', 'receive', 'request', 'response', 'query',
82
- 'search', 'find', 'list', 'get', 'set', 'put', 'post',
83
- 'build', 'compile', 'test', 'deploy', 'install', 'run',
84
- 'execute', 'start', 'stop', 'restart', 'init', 'initialize',
85
- 'configure', 'spawn', 'call', 'invoke',
163
+ 'read',
164
+ 'write',
165
+ 'edit',
166
+ 'delete',
167
+ 'create',
168
+ 'update',
169
+ 'insert',
170
+ 'remove',
171
+ 'add',
172
+ 'modify',
173
+ 'patch',
174
+ 'merge',
175
+ 'push',
176
+ 'pull',
177
+ 'fetch',
178
+ 'send',
179
+ 'receive',
180
+ 'request',
181
+ 'response',
182
+ 'query',
183
+ 'search',
184
+ 'find',
185
+ 'list',
186
+ 'get',
187
+ 'set',
188
+ 'put',
189
+ 'post',
190
+ 'build',
191
+ 'compile',
192
+ 'test',
193
+ 'deploy',
194
+ 'install',
195
+ 'run',
196
+ 'execute',
197
+ 'start',
198
+ 'stop',
199
+ 'restart',
200
+ 'init',
201
+ 'initialize',
202
+ 'configure',
203
+ 'spawn',
204
+ 'call',
205
+ 'invoke',
86
206
  ]);
87
207
  const HTTP_METHODS = new Map([
88
208
  ['GET', 'green'],
@@ -18,11 +18,7 @@ exports.shouldMergeWithPrevious = shouldMergeWithPrevious;
18
18
  exports.classifyNoise = classifyNoise;
19
19
  // ── Hard Noise Detection ──
20
20
  /** Hard noise types that should be dropped entirely from display. */
21
- const HARD_NOISE_EVENT_TYPES = new Set([
22
- 'file-history-snapshot',
23
- 'queue-operation',
24
- 'progress',
25
- ]);
21
+ const HARD_NOISE_EVENT_TYPES = new Set(['file-history-snapshot', 'queue-operation', 'progress']);
26
22
  const SYNTHETIC_MODEL_PREFIX = '<synthetic>';
27
23
  /**
28
24
  * Determines if an event is "hard noise" and should be dropped entirely.
@@ -70,11 +66,7 @@ const COMMAND_CAVEAT_PATTERNS = [
70
66
  /IMPORTANT:.*?(?:never|always|must|should)/i,
71
67
  /Note:.*?(?:do not|don't|avoid)/i,
72
68
  ];
73
- const INTERRUPTION_MARKERS = [
74
- 'interrupted by user',
75
- 'operation cancelled',
76
- 'aborted',
77
- ];
69
+ const INTERRUPTION_MARKERS = ['interrupted by user', 'operation cancelled', 'aborted'];
78
70
  /**
79
71
  * Checks for soft noise that can be hidden but not dropped.
80
72
  *
@@ -222,5 +214,5 @@ function extractContent(event) {
222
214
  function hasToolUseBlocks(content) {
223
215
  if (!Array.isArray(content))
224
216
  return false;
225
- return content.some(b => b.type === 'tool_use');
217
+ return content.some((b) => b.type === 'tool_use');
226
218
  }
@@ -51,7 +51,7 @@ function formatSessionText(metrics, options = {}) {
51
51
  // Tool stats
52
52
  if (metrics.toolStats.length > 0) {
53
53
  lines.push('Tools:');
54
- const sorted = [...metrics.toolStats].sort((a, b) => (b.successCount + b.failureCount) - (a.successCount + a.failureCount));
54
+ const sorted = [...metrics.toolStats].sort((a, b) => b.successCount + b.failureCount - (a.successCount + a.failureCount));
55
55
  for (const t of sorted) {
56
56
  const total = t.successCount + t.failureCount;
57
57
  const failStr = t.failureCount > 0 ? ` (${t.failureCount} failed)` : '';
@@ -77,7 +77,7 @@ function formatSessionText(metrics, options = {}) {
77
77
  lines.push(hr);
78
78
  const filteredEvents = expand
79
79
  ? metrics.timeline
80
- : metrics.timeline.filter(e => e.noiseLevel !== 'noise');
80
+ : metrics.timeline.filter((e) => e.noiseLevel !== 'noise');
81
81
  if (filteredEvents.length === 0) {
82
82
  lines.push(' (no events)');
83
83
  }
@@ -150,7 +150,7 @@ function formatSessionMarkdown(metrics, options = {}) {
150
150
  lines.push('');
151
151
  lines.push(`| Tool | Total | Failed | Avg Duration |`);
152
152
  lines.push(`|------|-------|--------|--------------|`);
153
- const sorted = [...metrics.toolStats].sort((a, b) => (b.successCount + b.failureCount) - (a.successCount + a.failureCount));
153
+ const sorted = [...metrics.toolStats].sort((a, b) => b.successCount + b.failureCount - (a.successCount + a.failureCount));
154
154
  for (const t of sorted) {
155
155
  const total = t.successCount + t.failureCount;
156
156
  const avgMs = t.completedCount > 0 ? Math.round(t.totalDuration / t.completedCount) : 0;
@@ -190,7 +190,7 @@ function formatSessionMarkdown(metrics, options = {}) {
190
190
  lines.push('');
191
191
  const filteredEvents = expand
192
192
  ? metrics.timeline
193
- : metrics.timeline.filter(e => e.noiseLevel !== 'noise');
193
+ : metrics.timeline.filter((e) => e.noiseLevel !== 'noise');
194
194
  if (filteredEvents.length === 0) {
195
195
  lines.push('_(no events)_');
196
196
  }
@@ -279,15 +279,24 @@ function formatDuration(start, end) {
279
279
  /** Map TimelineEvent.type to a display icon. */
280
280
  function getTimelineEventIcon(type) {
281
281
  switch (type) {
282
- case 'user_prompt': return '\u25b6'; // right-pointing triangle
283
- case 'assistant_response': return '\u2726'; // four-pointed star
284
- case 'tool_call': return '\u2699'; // gear
285
- case 'tool_result': return '\u2190'; // left arrow
286
- case 'compaction': return '\u21bb'; // clockwise arrow
287
- case 'error': return '\u2718'; // heavy ballot X
288
- case 'session_start': return '\u25cf'; // black circle
289
- case 'session_end': return '\u25cb'; // white circle
290
- default: return ' ';
282
+ case 'user_prompt':
283
+ return '\u25b6'; // right-pointing triangle
284
+ case 'assistant_response':
285
+ return '\u2726'; // four-pointed star
286
+ case 'tool_call':
287
+ return '\u2699'; // gear
288
+ case 'tool_result':
289
+ return '\u2190'; // left arrow
290
+ case 'compaction':
291
+ return '\u21bb'; // clockwise arrow
292
+ case 'error':
293
+ return '\u2718'; // heavy ballot X
294
+ case 'session_start':
295
+ return '\u25cf'; // black circle
296
+ case 'session_end':
297
+ return '\u25cb'; // white circle
298
+ default:
299
+ return ' ';
291
300
  }
292
301
  }
293
302
  function truncateToWidth(text, maxLen) {
@@ -217,9 +217,7 @@ function genericFallback(input) {
217
217
  */
218
218
  function formatToolSummary(toolName, input) {
219
219
  // Strip MCP prefixes: "mcp__server__tool" → use "tool" for matching
220
- const baseName = toolName.includes('__')
221
- ? toolName.split('__').pop() ?? toolName
222
- : toolName;
220
+ const baseName = toolName.includes('__') ? (toolName.split('__').pop() ?? toolName) : toolName;
223
221
  const formatter = TOOL_FORMATTERS[toolName] ?? TOOL_FORMATTERS[baseName];
224
222
  if (formatter) {
225
223
  const result = formatter(input);