sidekick-shared 0.28.3 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +188 -27
- package/dist/aggregation/EventAggregator.d.ts +19 -0
- package/dist/aggregation/EventAggregator.js +174 -35
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +13 -0
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +16 -6
- package/dist/browser.js +24 -2
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/context/sessionContext.js +4 -0
- package/dist/esm/aggregation/EventAggregator.js +174 -35
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +5 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +11 -6
- package/dist/esm/modelCatalog.js +7 -5
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +425 -47
- package/dist/esm/node.js +9 -3
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +351 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +85 -22
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +17 -9
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +84 -62
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +46 -3
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +442 -58
- package/dist/esm/watchers/jsonlWatcher.js +51 -4
- package/dist/esm/watchers/providerReaderWatcher.js +13 -0
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.js +44 -12
- package/dist/modelCatalog.js +7 -5
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +434 -49
- package/dist/node.d.ts +16 -5
- package/dist/node.js +33 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +118 -0
- package/dist/parsers/codexParser.js +352 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +88 -22
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +33 -12
- package/dist/parsers/subagentTraceParser.d.ts +25 -5
- package/dist/parsers/subagentTraceParser.js +97 -37
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +16 -8
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +85 -64
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/providers/types.d.ts +14 -0
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +46 -3
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/eventBridge.js +15 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +124 -4
- package/dist/watchers/jsonlTail.js +446 -59
- package/dist/watchers/jsonlWatcher.d.ts +18 -1
- package/dist/watchers/jsonlWatcher.js +50 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +13 -0
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +41 -1
|
@@ -18,6 +18,7 @@ import { HeatmapTracker } from './HeatmapTracker';
|
|
|
18
18
|
import { PatternExtractor } from './PatternExtractor';
|
|
19
19
|
import { categorizeError } from '../extractors/errorTaxonomy';
|
|
20
20
|
import { estimateTextTokens } from '../tokenEstimation';
|
|
21
|
+
import { isSubagentSpawnTool, isTaskNotificationSystemEvent, parseAsyncAgentLaunch, parseTaskNotification, taskNotificationFromEvent, taskNotificationOutcome, taskNotificationPromptFromAttachment, } from '../subagentSpawn';
|
|
21
22
|
import { calculateNormalizedUsageCost, extractNormalizedUsage, normalizeProviderUsage, } from '../usageNormalization';
|
|
22
23
|
// ── Defaults ──
|
|
23
24
|
const DEFAULT_TIMELINE_CAP = 200;
|
|
@@ -30,6 +31,22 @@ const COMPACTION_DROP_THRESHOLD = 0.8; // >20% drop
|
|
|
30
31
|
export const SNAPSHOT_SCHEMA_VERSION = 6;
|
|
31
32
|
// ── Goal gate detection regex ──
|
|
32
33
|
const GOAL_GATE_REGEX = /\b(CRITICAL|MUST|blocker|required|must.?complete|goal.?gate|essential|do.?not.?skip|blocking)\b/i;
|
|
34
|
+
// ── Task notifications delivered as attachments ──
|
|
35
|
+
/**
|
|
36
|
+
* The notification a `system` FollowEvent carries when it stands for a
|
|
37
|
+
* mid-turn task-notification attachment (its `raw` is the attachment line or
|
|
38
|
+
* the SessionEvent `extractSessionEvents` made from it).
|
|
39
|
+
*/
|
|
40
|
+
function taskNotificationFromAttachmentFollowEvent(event) {
|
|
41
|
+
const raw = event.raw;
|
|
42
|
+
if (!raw || typeof raw !== 'object')
|
|
43
|
+
return null;
|
|
44
|
+
const prompt = taskNotificationPromptFromAttachment(raw);
|
|
45
|
+
if (prompt !== null)
|
|
46
|
+
return parseTaskNotification(prompt);
|
|
47
|
+
const asEvent = raw;
|
|
48
|
+
return isTaskNotificationSystemEvent(asEvent) ? taskNotificationFromEvent(asEvent) : null;
|
|
49
|
+
}
|
|
33
50
|
// ── Todo dependency parsing ──
|
|
34
51
|
/**
|
|
35
52
|
* Parses dependency references from a todo item's content text.
|
|
@@ -168,6 +185,17 @@ export class EventAggregator {
|
|
|
168
185
|
// processEvent — main entry for SessionEvent
|
|
169
186
|
// ═══════════════════════════════════════════════════════════════════════
|
|
170
187
|
processEvent(event) {
|
|
188
|
+
// Opt-in mid-turn task notifications (`extractSessionEvents` with
|
|
189
|
+
// `includeTaskNotifications`) only finish background subagents and mark
|
|
190
|
+
// the timeline: they are not messages, prompts, context, or activity.
|
|
191
|
+
if (isTaskNotificationSystemEvent(event)) {
|
|
192
|
+
const notification = taskNotificationFromEvent(event);
|
|
193
|
+
if (notification) {
|
|
194
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
195
|
+
this.addTaskNotificationTimeline(event.timestamp, notification);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
171
199
|
// 1. Increment counters, capture timestamps
|
|
172
200
|
this.eventCount++;
|
|
173
201
|
if (!this.sessionStartTime) {
|
|
@@ -233,6 +261,15 @@ export class EventAggregator {
|
|
|
233
261
|
// processFollowEvent — adapter for CLI FollowEvent format
|
|
234
262
|
// ═══════════════════════════════════════════════════════════════════════
|
|
235
263
|
processFollowEvent(event) {
|
|
264
|
+
// A mid-turn task-notification attachment, like its SessionEvent form above.
|
|
265
|
+
if (event.type === 'system') {
|
|
266
|
+
const notification = taskNotificationFromAttachmentFollowEvent(event);
|
|
267
|
+
if (notification) {
|
|
268
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
269
|
+
this.addTaskNotificationTimeline(event.timestamp, notification);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
236
273
|
// 1. Increment counters, capture timestamps
|
|
237
274
|
this.eventCount++;
|
|
238
275
|
if (!this.sessionStartTime) {
|
|
@@ -626,6 +663,14 @@ export class EventAggregator {
|
|
|
626
663
|
this.pendingToolCalls.clear();
|
|
627
664
|
this.pendingTaskCreates.clear();
|
|
628
665
|
this.pendingSubagents.clear();
|
|
666
|
+
// A spawn still waiting for its tool_result is resolved by that result,
|
|
667
|
+
// which can land after the snapshot boundary. Background agents need no
|
|
668
|
+
// entry: their notifications are matched against every subagent.
|
|
669
|
+
this.subagents.forEach((agent, idx) => {
|
|
670
|
+
if (agent.status === 'running' && !agent.background && agent.id) {
|
|
671
|
+
this.pendingSubagents.set(agent.id, idx);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
629
674
|
this.pendingUserRequest = null;
|
|
630
675
|
this.planExtractor.reset();
|
|
631
676
|
return true;
|
|
@@ -1104,7 +1149,7 @@ export class EventAggregator {
|
|
|
1104
1149
|
else if (name === 'UpdatePlan') {
|
|
1105
1150
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1106
1151
|
}
|
|
1107
|
-
else if ((name
|
|
1152
|
+
else if (isSubagentSpawnTool(name) && toolUseId) {
|
|
1108
1153
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1109
1154
|
}
|
|
1110
1155
|
}
|
|
@@ -1114,7 +1159,7 @@ export class EventAggregator {
|
|
|
1114
1159
|
continue;
|
|
1115
1160
|
const isError = block.is_error ?? false;
|
|
1116
1161
|
this.resolveTaskCreate(toolUseId, block.content ?? block.output, event.timestamp, isError);
|
|
1117
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1162
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, block.content ?? block.output);
|
|
1118
1163
|
}
|
|
1119
1164
|
}
|
|
1120
1165
|
}
|
|
@@ -1148,7 +1193,7 @@ export class EventAggregator {
|
|
|
1148
1193
|
else if (event.toolName === 'UpdatePlan') {
|
|
1149
1194
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1150
1195
|
}
|
|
1151
|
-
else if ((event.toolName
|
|
1196
|
+
else if (isSubagentSpawnTool(event.toolName) && toolUseId) {
|
|
1152
1197
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1153
1198
|
}
|
|
1154
1199
|
}
|
|
@@ -1160,7 +1205,7 @@ export class EventAggregator {
|
|
|
1160
1205
|
return;
|
|
1161
1206
|
const isError = raw.is_error ?? false;
|
|
1162
1207
|
this.resolveTaskCreate(toolUseId, raw.content, event.timestamp, isError);
|
|
1163
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1208
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, raw.content);
|
|
1164
1209
|
}
|
|
1165
1210
|
}
|
|
1166
1211
|
applyTaskUpdate(input) {
|
|
@@ -1465,11 +1510,14 @@ export class EventAggregator {
|
|
|
1465
1510
|
};
|
|
1466
1511
|
this.tasks.set(agentTaskId, task);
|
|
1467
1512
|
}
|
|
1468
|
-
resolveAgentResult(toolUseId, isError, timestamp) {
|
|
1513
|
+
resolveAgentResult(toolUseId, isError, timestamp, content) {
|
|
1469
1514
|
const agentTaskId = `agent-${toolUseId}`;
|
|
1470
1515
|
const agentTask = this.tasks.get(agentTaskId);
|
|
1471
1516
|
if (!agentTask)
|
|
1472
1517
|
return;
|
|
1518
|
+
// A background launch returns at once; its task notification finishes it.
|
|
1519
|
+
if (!isError && parseAsyncAgentLaunch(content))
|
|
1520
|
+
return;
|
|
1473
1521
|
agentTask.status = isError ? 'deleted' : 'completed';
|
|
1474
1522
|
agentTask.updatedAt = new Date(timestamp);
|
|
1475
1523
|
}
|
|
@@ -1489,31 +1537,25 @@ export class EventAggregator {
|
|
|
1489
1537
|
// Private: Subagent Tracking from SessionEvent
|
|
1490
1538
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1491
1539
|
extractSubagentFromEvent(event) {
|
|
1540
|
+
const notification = taskNotificationFromEvent(event);
|
|
1541
|
+
if (notification)
|
|
1542
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1492
1543
|
const content = event.message?.content;
|
|
1493
1544
|
if (!Array.isArray(content))
|
|
1494
1545
|
return;
|
|
1495
1546
|
for (const block of content) {
|
|
1496
|
-
if (block.type === 'tool_use' && block.name
|
|
1547
|
+
if (block.type === 'tool_use' && isSubagentSpawnTool(block.name)) {
|
|
1497
1548
|
const input = block.input;
|
|
1498
1549
|
const toolUseId = block.id;
|
|
1499
1550
|
if (!input || !toolUseId)
|
|
1500
1551
|
continue;
|
|
1501
|
-
|
|
1502
|
-
id: toolUseId,
|
|
1503
|
-
description: input.description || 'Unknown task',
|
|
1504
|
-
subagentType: input.subagent_type || input.subagentType || 'general',
|
|
1505
|
-
spawnTime: event.timestamp,
|
|
1506
|
-
status: 'running',
|
|
1507
|
-
};
|
|
1508
|
-
const idx = this.subagents.length;
|
|
1509
|
-
this.subagents.push(info);
|
|
1510
|
-
this.pendingSubagents.set(toolUseId, idx);
|
|
1552
|
+
this.trackSubagentSpawn(toolUseId, input, event.timestamp);
|
|
1511
1553
|
}
|
|
1512
1554
|
else if (block.type === 'tool_result') {
|
|
1513
1555
|
const toolUseId = block.tool_use_id;
|
|
1514
1556
|
if (!toolUseId)
|
|
1515
1557
|
continue;
|
|
1516
|
-
this.
|
|
1558
|
+
this.resolveSubagentResult(toolUseId, block.content ?? block.output, block.is_error === true, event.timestamp);
|
|
1517
1559
|
}
|
|
1518
1560
|
}
|
|
1519
1561
|
}
|
|
@@ -1522,23 +1564,10 @@ export class EventAggregator {
|
|
|
1522
1564
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1523
1565
|
extractSubagentFromFollowEvent(event) {
|
|
1524
1566
|
const raw = event.raw;
|
|
1525
|
-
if (event.type === 'tool_use' && event.toolName
|
|
1567
|
+
if (event.type === 'tool_use' && isSubagentSpawnTool(event.toolName)) {
|
|
1526
1568
|
if (!raw?.input)
|
|
1527
1569
|
return;
|
|
1528
|
-
|
|
1529
|
-
const toolUseId = raw.id || '';
|
|
1530
|
-
const info = {
|
|
1531
|
-
id: toolUseId,
|
|
1532
|
-
description: input.description || 'Unknown task',
|
|
1533
|
-
subagentType: input.subagent_type || input.subagentType || 'general',
|
|
1534
|
-
spawnTime: event.timestamp,
|
|
1535
|
-
status: 'running',
|
|
1536
|
-
};
|
|
1537
|
-
const idx = this.subagents.length;
|
|
1538
|
-
this.subagents.push(info);
|
|
1539
|
-
if (toolUseId) {
|
|
1540
|
-
this.pendingSubagents.set(toolUseId, idx);
|
|
1541
|
-
}
|
|
1570
|
+
this.trackSubagentSpawn(raw.id || '', raw.input, event.timestamp);
|
|
1542
1571
|
}
|
|
1543
1572
|
else if (event.type === 'tool_result') {
|
|
1544
1573
|
if (!raw)
|
|
@@ -1546,23 +1575,133 @@ export class EventAggregator {
|
|
|
1546
1575
|
const toolUseId = raw.tool_use_id;
|
|
1547
1576
|
if (!toolUseId)
|
|
1548
1577
|
return;
|
|
1549
|
-
this.
|
|
1578
|
+
this.resolveSubagentResult(toolUseId, raw.content, raw.is_error === true, event.timestamp);
|
|
1579
|
+
}
|
|
1580
|
+
else if (event.type === 'user' && raw?.type === 'user') {
|
|
1581
|
+
// Claude Code delivers most task notifications as their own user line.
|
|
1582
|
+
const notification = taskNotificationFromEvent(raw);
|
|
1583
|
+
if (notification)
|
|
1584
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
trackSubagentSpawn(toolUseId, input, timestamp) {
|
|
1588
|
+
const info = {
|
|
1589
|
+
id: toolUseId,
|
|
1590
|
+
description: input.description || 'Unknown task',
|
|
1591
|
+
subagentType: input.subagent_type || input.subagentType || 'general',
|
|
1592
|
+
spawnTime: timestamp,
|
|
1593
|
+
status: 'running',
|
|
1594
|
+
};
|
|
1595
|
+
const idx = this.subagents.length;
|
|
1596
|
+
this.subagents.push(info);
|
|
1597
|
+
if (toolUseId) {
|
|
1598
|
+
this.pendingSubagents.set(toolUseId, idx);
|
|
1550
1599
|
}
|
|
1551
1600
|
}
|
|
1552
|
-
|
|
1601
|
+
/**
|
|
1602
|
+
* The spawning tool call's result. A foreground agent ends here; a
|
|
1603
|
+
* background launch only reports the agent id, and the agent keeps running
|
|
1604
|
+
* until its task notification arrives.
|
|
1605
|
+
*/
|
|
1606
|
+
resolveSubagentResult(toolUseId, content, isError, timestamp) {
|
|
1553
1607
|
const idx = this.pendingSubagents.get(toolUseId);
|
|
1554
1608
|
if (idx === undefined)
|
|
1555
1609
|
return;
|
|
1556
|
-
|
|
1610
|
+
const launch = isError ? null : parseAsyncAgentLaunch(content);
|
|
1611
|
+
if (launch) {
|
|
1612
|
+
this.markBackgroundLaunch(idx, launch);
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
this.completeSubagent(toolUseId, timestamp, isError ? 'failed' : 'completed');
|
|
1616
|
+
}
|
|
1617
|
+
markBackgroundLaunch(idx, launch) {
|
|
1557
1618
|
const agent = this.subagents[idx];
|
|
1619
|
+
this.pendingSubagents.delete(agent.id);
|
|
1620
|
+
agent.background = true;
|
|
1621
|
+
if (launch.agentId)
|
|
1622
|
+
agent.agentId = launch.agentId;
|
|
1623
|
+
}
|
|
1624
|
+
completeSubagent(toolUseId, timestamp, outcome = 'completed') {
|
|
1625
|
+
const idx = this.pendingSubagents.get(toolUseId);
|
|
1626
|
+
if (idx === undefined)
|
|
1627
|
+
return;
|
|
1628
|
+
this.finishSubagent(this.subagents[idx], timestamp, outcome);
|
|
1629
|
+
}
|
|
1630
|
+
finishSubagent(agent, timestamp, outcome) {
|
|
1631
|
+
this.pendingSubagents.delete(agent.id);
|
|
1558
1632
|
agent.status = 'completed';
|
|
1559
1633
|
agent.completionTime = timestamp;
|
|
1634
|
+
agent.outcome = outcome;
|
|
1560
1635
|
const start = new Date(agent.spawnTime).getTime();
|
|
1561
1636
|
const end = new Date(timestamp).getTime();
|
|
1562
1637
|
if (!isNaN(start) && !isNaN(end) && end >= start) {
|
|
1563
1638
|
agent.durationMs = end - start;
|
|
1564
1639
|
}
|
|
1565
1640
|
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Applies a background task's notification to the subagent it reports on,
|
|
1643
|
+
* matched by the spawning tool_use id (or by agent id when the notification
|
|
1644
|
+
* has none). The latest notification wins: an interim one ("waiting on its
|
|
1645
|
+
* own background work") keeps or puts the agent back to running, a final one
|
|
1646
|
+
* completes it. Notifications for background shell tasks match nothing.
|
|
1647
|
+
*/
|
|
1648
|
+
applyTaskNotification(notification, timestamp) {
|
|
1649
|
+
const agent = this.findNotifiedSubagent(notification);
|
|
1650
|
+
const toolUseId = agent?.id || notification.toolUseId;
|
|
1651
|
+
const outcome = taskNotificationOutcome(notification.status);
|
|
1652
|
+
if (agent) {
|
|
1653
|
+
agent.background = true;
|
|
1654
|
+
if (!agent.agentId)
|
|
1655
|
+
agent.agentId = notification.taskId;
|
|
1656
|
+
if (notification.interim) {
|
|
1657
|
+
this.pendingSubagents.delete(agent.id);
|
|
1658
|
+
agent.status = 'running';
|
|
1659
|
+
delete agent.completionTime;
|
|
1660
|
+
delete agent.durationMs;
|
|
1661
|
+
delete agent.outcome;
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
this.finishSubagent(agent, timestamp, outcome);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const agentTask = toolUseId ? this.tasks.get(`agent-${toolUseId}`) : undefined;
|
|
1668
|
+
if (agentTask?.isSubagent) {
|
|
1669
|
+
agentTask.status = notification.interim
|
|
1670
|
+
? 'in_progress'
|
|
1671
|
+
: outcome === 'failed'
|
|
1672
|
+
? 'deleted'
|
|
1673
|
+
: 'completed';
|
|
1674
|
+
agentTask.updatedAt = new Date(timestamp);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
findNotifiedSubagent(notification) {
|
|
1678
|
+
const { toolUseId, taskId } = notification;
|
|
1679
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1680
|
+
if (toolUseId && this.subagents[i].id === toolUseId)
|
|
1681
|
+
return this.subagents[i];
|
|
1682
|
+
}
|
|
1683
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1684
|
+
if (this.subagents[i].agentId === taskId)
|
|
1685
|
+
return this.subagents[i];
|
|
1686
|
+
}
|
|
1687
|
+
return undefined;
|
|
1688
|
+
}
|
|
1689
|
+
addTaskNotificationTimeline(timestamp, notification) {
|
|
1690
|
+
let description = notification.summary ?? `Background task ${notification.status ?? 'finished'}`;
|
|
1691
|
+
if (description.length > 200)
|
|
1692
|
+
description = description.substring(0, 197) + '...';
|
|
1693
|
+
this.timeline.push({
|
|
1694
|
+
type: 'tool_result',
|
|
1695
|
+
timestamp,
|
|
1696
|
+
description,
|
|
1697
|
+
noiseLevel: 'system',
|
|
1698
|
+
messageClassification: 'system',
|
|
1699
|
+
...(notification.toolUseId ? { metadata: { toolUseId: notification.toolUseId } } : {}),
|
|
1700
|
+
});
|
|
1701
|
+
if (this.timeline.length > this.timelineCap) {
|
|
1702
|
+
this.timeline.shift();
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1566
1705
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1567
1706
|
// Private: Plan Extraction from SessionEvent
|
|
1568
1707
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Session snapshot sidecar — persists aggregator state + reader position
|
|
3
3
|
* so that re-attaching to a session skips full replay.
|
|
4
4
|
*
|
|
5
|
-
* Snapshots are stored in
|
|
6
|
-
* They are invalidated when the source file
|
|
5
|
+
* Snapshots are stored in `<config dir>/snapshots/{sessionId}.json` (see
|
|
6
|
+
* `getConfigDir()`). They are invalidated when the source file shrank below
|
|
7
|
+
* the snapshot position or, for file-backed sessions that recorded a
|
|
8
|
+
* {@link SessionSnapshot.sourceCheckpoint}, when the file is no longer the
|
|
9
|
+
* same append-only file.
|
|
7
10
|
*
|
|
8
11
|
* @module aggregation/snapshot
|
|
9
12
|
*/
|
|
10
13
|
import * as fs from 'fs';
|
|
11
14
|
import * as path from 'path';
|
|
12
15
|
import { getConfigDir } from '../paths';
|
|
16
|
+
import { verifyJsonlCheckpoint } from '../watchers/jsonlTail';
|
|
13
17
|
import { atomicWriteJsonSync } from '../writers/atomic';
|
|
14
18
|
import { SNAPSHOT_SCHEMA_VERSION } from './EventAggregator';
|
|
15
19
|
/** Current schema version — bump when format changes. */
|
|
@@ -101,8 +105,8 @@ export function pruneSnapshots(keep = MAX_SNAPSHOT_FILES) {
|
|
|
101
105
|
* - Schema version doesn't match
|
|
102
106
|
* - JSON parse fails
|
|
103
107
|
*
|
|
104
|
-
* The caller is responsible for staleness checks
|
|
105
|
-
*
|
|
108
|
+
* The caller is responsible for staleness checks against the actual file:
|
|
109
|
+
* `isSnapshotValid(snapshot, currentSize, sourcePath)`.
|
|
106
110
|
*/
|
|
107
111
|
export function loadSnapshot(sessionId) {
|
|
108
112
|
try {
|
|
@@ -142,13 +146,21 @@ export function deleteSnapshot(sessionId) {
|
|
|
142
146
|
/**
|
|
143
147
|
* Validates whether a snapshot is still usable for a given source file.
|
|
144
148
|
*
|
|
145
|
-
* For JSONL providers: checks that the file hasn't been truncated
|
|
146
|
-
* (current size >= snapshot position) and that the size matches or has grown.
|
|
147
|
-
*
|
|
148
149
|
* For DB-backed providers: sourceSize is 0, so we always consider valid
|
|
149
150
|
* (the DB reader's timestamp-based cursor handles staleness naturally).
|
|
151
|
+
*
|
|
152
|
+
* For JSONL providers: the file must not have shrunk below the snapshot
|
|
153
|
+
* position. With `sourcePath` the check is also content-aware: the snapshot
|
|
154
|
+
* must carry a {@link SessionSnapshot.sourceCheckpoint} for exactly
|
|
155
|
+
* `readerPosition`, and that checkpoint must still match the file at
|
|
156
|
+
* `sourcePath` (same device and inode, same first bytes, position just after
|
|
157
|
+
* a newline), so a file replaced by another one or rewritten to the same or
|
|
158
|
+
* a larger size is not resumed mid-content. A snapshot without a checkpoint
|
|
159
|
+
* (written before 0.29.1) is invalid when `sourcePath` is given, which costs
|
|
160
|
+
* one full replay. Without `sourcePath` only the size check applies, as it
|
|
161
|
+
* did before.
|
|
150
162
|
*/
|
|
151
|
-
export function isSnapshotValid(snapshot, currentSourceSize) {
|
|
163
|
+
export function isSnapshotValid(snapshot, currentSourceSize, sourcePath) {
|
|
152
164
|
// DB-backed providers store sourceSize=0 — always valid (cursor is a timestamp)
|
|
153
165
|
if (snapshot.sourceSize === 0) {
|
|
154
166
|
return true;
|
|
@@ -157,5 +169,15 @@ export function isSnapshotValid(snapshot, currentSourceSize) {
|
|
|
157
169
|
if (currentSourceSize < snapshot.readerPosition) {
|
|
158
170
|
return false;
|
|
159
171
|
}
|
|
160
|
-
|
|
172
|
+
if (typeof sourcePath !== 'string') {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
// Snapshot content comes from disk: guard the shape before trusting it.
|
|
176
|
+
const checkpoint = snapshot.sourceCheckpoint;
|
|
177
|
+
if (!checkpoint ||
|
|
178
|
+
typeof checkpoint !== 'object' ||
|
|
179
|
+
checkpoint.offset !== snapshot.readerPosition) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return verifyJsonlCheckpoint(sourcePath, checkpoint);
|
|
161
183
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsers for the provider CLIs' read-only sign-in status commands,
|
|
3
|
+
* `claude auth status --json` and `codex login status`. They do no I/O. The
|
|
4
|
+
* caller runs the command with its own environment, home, and timeout, and
|
|
5
|
+
* passes the output here.
|
|
6
|
+
*
|
|
7
|
+
* A status command reports which credential the CLI would use. It does not
|
|
8
|
+
* prove that a request will authenticate: an expired or revoked token can
|
|
9
|
+
* still read as logged in.
|
|
10
|
+
*
|
|
11
|
+
* @module authStatus
|
|
12
|
+
*/
|
|
13
|
+
/** Larger outputs are not a status report; reading stops there. */
|
|
14
|
+
const MAX_STATUS_OUTPUT = 256 * 1024;
|
|
15
|
+
const MAX_DETAIL = 200;
|
|
16
|
+
function stringField(record, key) {
|
|
17
|
+
const value = record[key];
|
|
18
|
+
if (typeof value !== 'string')
|
|
19
|
+
return undefined;
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
return trimmed && trimmed.length <= 512 ? trimmed : undefined;
|
|
22
|
+
}
|
|
23
|
+
function parseJsonObject(output) {
|
|
24
|
+
const trimmed = output.trim();
|
|
25
|
+
const candidates = [trimmed];
|
|
26
|
+
// Tolerate warning lines printed ahead of the JSON document, which must
|
|
27
|
+
// then start a line and run to the end of the output.
|
|
28
|
+
const start = trimmed.search(/\n[ \t]*\{/);
|
|
29
|
+
if (start >= 0)
|
|
30
|
+
candidates.push(trimmed.slice(start).trim());
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
if (!candidate.startsWith('{') || !candidate.endsWith('}'))
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(candidate);
|
|
36
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* Try the next candidate. */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse the stdout of `claude auth status --json`. Claude Code exits 1 while
|
|
47
|
+
* printing valid signed-out JSON, so parse stdout whatever the exit status
|
|
48
|
+
* was. Only a JSON `"loggedIn": true` counts as logged in.
|
|
49
|
+
*/
|
|
50
|
+
export function parseClaudeAuthStatus(stdout) {
|
|
51
|
+
const raw = typeof stdout === 'string' && stdout.length <= MAX_STATUS_OUTPUT
|
|
52
|
+
? parseJsonObject(stdout)
|
|
53
|
+
: undefined;
|
|
54
|
+
if (!raw)
|
|
55
|
+
return { loggedIn: false };
|
|
56
|
+
const method = stringField(raw, 'authMethod');
|
|
57
|
+
const email = stringField(raw, 'email');
|
|
58
|
+
const subscriptionType = stringField(raw, 'subscriptionType');
|
|
59
|
+
return {
|
|
60
|
+
loggedIn: raw.loggedIn === true,
|
|
61
|
+
...(method ? { method } : {}),
|
|
62
|
+
...(email ? { email } : {}),
|
|
63
|
+
...(subscriptionType ? { subscriptionType } : {}),
|
|
64
|
+
raw,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse the output of `codex login status`. Codex prints its status line to
|
|
69
|
+
* stderr (`Logged in using ChatGPT`, `Logged in using an API key - sk-…`,
|
|
70
|
+
* `Not logged in`), so pass both streams. The exit status is not consulted:
|
|
71
|
+
* callers that require exit 0 check it themselves.
|
|
72
|
+
*/
|
|
73
|
+
export function parseCodexLoginStatus(stdout, stderr) {
|
|
74
|
+
const combined = `${typeof stdout === 'string' ? stdout : ''}\n${typeof stderr === 'string' ? stderr : ''}`.trim();
|
|
75
|
+
const firstLine = combined.split(/\r?\n/, 1)[0]?.trim() ?? '';
|
|
76
|
+
const detail = (/^(Logged in using .*?API key)\s+-\s.*$/i.exec(firstLine)?.[1] ?? firstLine)
|
|
77
|
+
.slice(0, MAX_DETAIL)
|
|
78
|
+
.trim();
|
|
79
|
+
const withDetail = detail ? { detail } : {};
|
|
80
|
+
if (!/^Logged in/i.test(combined))
|
|
81
|
+
return { loggedIn: false, ...withDetail };
|
|
82
|
+
if (/API key/i.test(combined))
|
|
83
|
+
return { loggedIn: true, method: 'api-key', ...withDetail };
|
|
84
|
+
if (/ChatGPT/i.test(combined))
|
|
85
|
+
return { loggedIn: true, method: 'chatgpt', ...withDetail };
|
|
86
|
+
return { loggedIn: true, ...withDetail };
|
|
87
|
+
}
|
package/dist/esm/browser.js
CHANGED
|
@@ -17,17 +17,20 @@ export { addLocalDays, formatDurationMs, formatLocalDateKey, formatTokenCount, p
|
|
|
17
17
|
export { parseMcpToolName } from './parsers/mcpToolName';
|
|
18
18
|
export { buildSessionContextSnapshot, calculateSessionContextPressure, createSessionContextProjector, } from './context/sessionContext';
|
|
19
19
|
export { ACCOUNT_PROVIDER_IDS, MODEL_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from './providerIds';
|
|
20
|
+
export { SUBAGENT_SPAWN_TOOL_NAMES, isSubagentSpawnTool, isTaskNotificationSystemEvent, parseTaskNotification, taskNotificationFromEvent, } from './subagentSpawn';
|
|
20
21
|
export { assistantTurnEventsFromSessionEvents, extractTurnSubagents, isAssistantTurnSubagentTool, reasoningSummary, segmentAssistantTurn, } from './turns/assistantTurn';
|
|
21
|
-
export { parseModelId, getModelPricing, resolveModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, } from './modelInfo';
|
|
22
|
+
export { parseModelId, getModelPricing, resolveModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, canonicalModelId, isMovingModelAlias, registerModelPricing, clearRegisteredModelPricing, getPricingCatalogInfo, costSourceFromPricingProvenance, mergeCostComponents, } from './modelInfo';
|
|
22
23
|
export { exportResolvedModelCatalog, importResolvedModelCatalog, registerModelAlias, resolveModelAlias, RESOLVED_MODEL_CATALOG_SCHEMA_VERSION, } from './modelCatalog';
|
|
23
24
|
export { BILLING_BLOCK_DURATION_MS, computeBillingBlocks, findActiveBillingBlock, } from './usage/billingBlocks';
|
|
24
25
|
export { bucketUsage, summarizeUsageRows, usageBucketKey, weekKey } from './usage/usageReports';
|
|
25
26
|
export { applySessionSummary, isFileImported, markFileImported, removeSessionSummary, sessionSummaryFromStats, } from './historicalStore';
|
|
26
27
|
export { FAILING_TOOL_TREND_ARROWS, failingToolTrend, mergeFailingToolWindows, } from './failingTools';
|
|
27
|
-
export { diagnoseProviderFailure, PROVIDER_AUTHENTICATION_SOURCES, PROVIDER_AUTHENTICATION_STATES, PROVIDER_CREDENTIAL_KINDS, PROVIDER_FAILURE_CODES, PROVIDER_FAILURE_EVIDENCE_SOURCES, PROVIDER_RECOVERY_ACTIONS, } from './providerFailure';
|
|
28
|
+
export { diagnoseProviderFailure, PROVIDER_AUTHENTICATION_SOURCES, PROVIDER_AUTHENTICATION_STATES, PROVIDER_CREDENTIAL_KINDS, PROVIDER_FAILURE_CODES, PROVIDER_FAILURE_EVIDENCE_SOURCES, PROVIDER_RECOVERY_ACTIONS, CODEX_ERROR_INFO_KINDS, decodeCodexErrorInfo, } from './providerFailure';
|
|
29
|
+
export { parseClaudeAuthStatus, parseCodexLoginStatus } from './authStatus';
|
|
28
30
|
export { isAggregateCodexLimit } from './types/codex';
|
|
29
31
|
// Codex rollout parsing (pure: no filesystem access)
|
|
30
32
|
export { CodexRolloutParser, DEFAULT_MAX_TRACKED_TOOL_CALLS, extractPatchFilePaths, normalizeCodexToolName, normalizeCodexToolInput, resolveCodexCallUsage, } from './parsers/codexParser';
|
|
33
|
+
export { codexRolloutOrigin, codexSpawnParentFromSource } from './parsers/codexRolloutOrigin';
|
|
31
34
|
export { HUMAN_CLAUDE_ENTRYPOINTS, HUMAN_CODEX_SOURCES, humanPromptText, isHumanPrompt, } from './humanPrompt';
|
|
32
35
|
export { renderHandoffUrlTemplate } from './handoffUrl';
|
|
33
36
|
// Tool categories across providers
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sidekick-shared/codex` — a small Node-only entry for Codex homes, the
|
|
3
|
+
* child-process environment, `auth.json` helpers, credential-store mode,
|
|
4
|
+
* rollout lookup, and the atomic JSON writers those helpers rely on.
|
|
5
|
+
*
|
|
6
|
+
* Loads a handful of modules and no Zod, so a host that only launches or
|
|
7
|
+
* locates Codex does not pay for the full `sidekick-shared/node` surface.
|
|
8
|
+
* Everything here is also exported from the package root and from
|
|
9
|
+
* `sidekick-shared/node`.
|
|
10
|
+
*
|
|
11
|
+
* @module codex
|
|
12
|
+
*/
|
|
13
|
+
export { getSystemCodexHome, resolveSidekickCodexHome, getCodexExecutionEnv, getCodexMonitoringHomes, getCodexSessionsDirs, getCodexCredentialStoreMode, forceFileCredentialStore, CODEX_SESSIONS_SUBDIR, CODEX_ARCHIVED_SESSIONS_SUBDIR, } from './codexPaths';
|
|
14
|
+
export { parseAuthJson as parseCodexAuthJson, readAuthIdentityFromRaw as readCodexAuthIdentity, readLastRefresh as readCodexLastRefresh, readAccessTokenExpiry as readCodexAccessTokenExpiry, identitiesMatch as codexIdentitiesMatch, codexRefreshFingerprint, compareCodexAuthFreshness, } from './codexAuth';
|
|
15
|
+
export { findCodexRolloutFile } from './providers/codexRollouts';
|
|
16
|
+
export { atomicWriteJson, atomicWriteJsonSync } from './writers/atomic';
|
|
17
|
+
export { parseClaudeAuthStatus, parseCodexLoginStatus } from './authStatus';
|
|
18
|
+
export { codexRolloutOrigin, codexSpawnParentFromSource } from './parsers/codexRolloutOrigin';
|
|
@@ -7,6 +7,7 @@ import { getAccountsDir, getActiveSavedAccount, listSavedAccountProfiles, remove
|
|
|
7
7
|
// re-serializing would drop fields added by newer codex versions, and the
|
|
8
8
|
// rotated refresh token inside is only valid in its freshest form.
|
|
9
9
|
import { atomicWriteFileSync as atomicWriteFile, atomicWriteJsonSync as atomicWriteJson, withFileLockSync, } from './writers/atomic';
|
|
10
|
+
import { parseCodexLoginStatus } from './authStatus';
|
|
10
11
|
import { identitiesMatch, codexRefreshFingerprint, compareCodexAuthFreshness, readAuthIdentityFromRaw, readLastRefresh, STALE_AUTH_THRESHOLD_MS, } from './codexAuth';
|
|
11
12
|
import { ensureCodexProfileDirs, forceFileCredentialStore, getCodexCredentialStoreMode, getCodexProfileDir, getCodexProfileHome, getCodexProfileStatePath, getCodexBackupPath, getCodexLoginDir, getCodexLoginsDir, getCodexLoginHome, getCodexLoginStatePath, getCodexProfilesDir, getSystemCodexHome, readFileOrNull, resolveSidekickCodexHome, } from './codexPaths';
|
|
12
13
|
import { getAccountHealth, readHealthSidecar, writeCodexHealthSidecar, } from './accountHealth';
|
|
@@ -206,18 +207,14 @@ function readMetadataFromLegacyCredentials(codexHome) {
|
|
|
206
207
|
return {};
|
|
207
208
|
}
|
|
208
209
|
const PROBE_TIMEOUT_MS = 4000;
|
|
210
|
+
/** `codex login status` counts only on exit 0; the text is read by {@link parseCodexLoginStatus}. */
|
|
209
211
|
export function parseCodexLoginStatusOutput(status, stdout) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { loggedIn: true, authMode: 'chatgpt' };
|
|
217
|
-
}
|
|
218
|
-
return { loggedIn: true };
|
|
219
|
-
}
|
|
220
|
-
return { loggedIn: false };
|
|
212
|
+
if (status !== 0)
|
|
213
|
+
return { loggedIn: false };
|
|
214
|
+
const parsed = parseCodexLoginStatus(stdout);
|
|
215
|
+
if (!parsed.loggedIn)
|
|
216
|
+
return { loggedIn: false };
|
|
217
|
+
return parsed.method ? { loggedIn: true, authMode: parsed.method } : { loggedIn: true };
|
|
221
218
|
}
|
|
222
219
|
/**
|
|
223
220
|
* Blocks the caller's event loop for up to {@link PROBE_TIMEOUT_MS}. Async code
|
|
@@ -8,6 +8,7 @@ import { EventAggregator } from '../aggregation/EventAggregator';
|
|
|
8
8
|
import { getModelContextWindowSize } from '../modelContext';
|
|
9
9
|
import { parseMcpToolName } from '../parsers/mcpToolName';
|
|
10
10
|
import { estimateTextTokens } from '../tokenEstimation';
|
|
11
|
+
import { isTaskNotificationSystemEvent } from '../subagentSpawn';
|
|
11
12
|
const DEFAULT_CONTEXT_SOURCE_LIMIT = 200;
|
|
12
13
|
const DEFAULT_SNIPPET_MAX_CHARS = 240;
|
|
13
14
|
const DEFAULT_BODY_MAX_CHARS = 5000;
|
|
@@ -205,6 +206,9 @@ function extractSourcesFromEvent(event, state) {
|
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
function extractSystemSource(event, state) {
|
|
209
|
+
// Background-task notices only update subagent lifecycles (like the aggregator).
|
|
210
|
+
if (isTaskNotificationSystemEvent(event))
|
|
211
|
+
return;
|
|
208
212
|
const message = event.message;
|
|
209
213
|
const text = extractText(message?.content);
|
|
210
214
|
const label = message?.sourceLabel ?? message?.role ?? 'system';
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module formatters/toolSummary
|
|
9
9
|
*/
|
|
10
|
+
import { ownValue } from '../ownKey';
|
|
10
11
|
// ── Helpers ──
|
|
11
12
|
function truncate(text, maxLen) {
|
|
12
13
|
const clean = text.replace(/\s+/g, ' ').trim();
|
|
@@ -171,6 +172,8 @@ const TOOL_FORMATTERS = {
|
|
|
171
172
|
Bash: formatBash,
|
|
172
173
|
Grep: formatGrep,
|
|
173
174
|
Glob: formatGlob,
|
|
175
|
+
// Claude Code's subagent tool; `Task` is its earlier name.
|
|
176
|
+
Agent: formatTask,
|
|
174
177
|
Task: formatTask,
|
|
175
178
|
WebFetch: formatWebFetch,
|
|
176
179
|
WebSearch: formatWebSearch,
|
|
@@ -214,7 +217,7 @@ function genericFallback(input) {
|
|
|
214
217
|
export function formatToolSummary(toolName, input) {
|
|
215
218
|
// Strip MCP prefixes: "mcp__server__tool" → use "tool" for matching
|
|
216
219
|
const baseName = toolName.includes('__') ? (toolName.split('__').pop() ?? toolName) : toolName;
|
|
217
|
-
const formatter = TOOL_FORMATTERS
|
|
220
|
+
const formatter = ownValue(TOOL_FORMATTERS, toolName) ?? ownValue(TOOL_FORMATTERS, baseName);
|
|
218
221
|
if (formatter) {
|
|
219
222
|
const result = formatter(input);
|
|
220
223
|
if (result)
|
package/dist/esm/formatting.js
CHANGED
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
* Pure display formatting helpers shared by CLI, extension host, webviews,
|
|
3
3
|
* and downstream apps.
|
|
4
4
|
*
|
|
5
|
-
* This module intentionally has no Node-only imports
|
|
5
|
+
* This module intentionally has no Node-only imports, and no runtime imports
|
|
6
|
+
* at all: bundles that only format values must not retain the pricing tables
|
|
7
|
+
* in `modelInfo.ts`. Type-only imports are fine.
|
|
6
8
|
*
|
|
7
9
|
* @module formatting
|
|
8
10
|
*/
|
|
11
|
+
export function formatCost(cost, options) {
|
|
12
|
+
const source = options?.source;
|
|
13
|
+
if (cost === null || cost === undefined || source === 'unpriced')
|
|
14
|
+
return '—';
|
|
15
|
+
const formatted = cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`;
|
|
16
|
+
return source === 'estimated' ? `${formatted} (est.)` : formatted;
|
|
17
|
+
}
|
|
9
18
|
/** Format a token count with compact `k` / `M` suffixes. */
|
|
10
19
|
export function formatTokenCount(value, options = {}) {
|
|
11
20
|
if (!Number.isFinite(value))
|