sidekick-shared 0.29.0 → 0.30.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.
- package/CHANGELOG.md +48 -0
- package/README.md +198 -56
- package/dist/aggregation/EventAggregator.d.ts +96 -2
- package/dist/aggregation/EventAggregator.js +535 -42
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +38 -1
- package/dist/browser.d.ts +9 -1
- package/dist/browser.js +29 -3
- package/dist/context/sessionContext.js +8 -0
- package/dist/esm/aggregation/EventAggregator.js +535 -42
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/browser.js +3 -0
- package/dist/esm/context/sessionContext.js +8 -0
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +3 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +372 -7
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +37 -12
- package/dist/esm/parsers/subagentTraceParser.js +103 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +60 -29
- package/dist/esm/providers/codex.js +235 -201
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +63 -14
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +363 -0
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +8 -0
- package/dist/esm/turns/assistantTurn.js +6 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +45 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +82 -48
- package/dist/esm/watchers/jsonlWatcher.js +61 -4
- package/dist/esm/watchers/providerReaderWatcher.js +18 -4
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.js +48 -10
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +5 -1
- package/dist/node.js +18 -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 +107 -0
- package/dist/parsers/codexParser.js +373 -7
- package/dist/parsers/openCodeParser.js +2 -1
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +37 -12
- package/dist/parsers/subagentTraceParser.d.ts +33 -5
- package/dist/parsers/subagentTraceParser.js +103 -37
- package/dist/pricingCatalog.js +3 -2
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +59 -28
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +232 -198
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +36 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +51 -10
- package/dist/schemas/sessionEvent.js +63 -14
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +175 -0
- package/dist/subagentSpawn.js +385 -0
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +8 -0
- package/dist/turns/assistantTurn.d.ts +4 -0
- package/dist/turns/assistantTurn.js +6 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +45 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +27 -4
- package/dist/watchers/jsonlTail.js +84 -48
- package/dist/watchers/jsonlWatcher.d.ts +22 -1
- package/dist/watchers/jsonlWatcher.js +60 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +18 -4
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ const HeatmapTracker_1 = require("./HeatmapTracker");
|
|
|
22
22
|
const PatternExtractor_1 = require("./PatternExtractor");
|
|
23
23
|
const errorTaxonomy_1 = require("../extractors/errorTaxonomy");
|
|
24
24
|
const tokenEstimation_1 = require("../tokenEstimation");
|
|
25
|
+
const subagentActivity_1 = require("../subagentActivity");
|
|
26
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
25
27
|
const usageNormalization_1 = require("../usageNormalization");
|
|
26
28
|
// ── Defaults ──
|
|
27
29
|
const DEFAULT_TIMELINE_CAP = 200;
|
|
@@ -30,10 +32,100 @@ const DEFAULT_CONTEXT_TIMELINE_CAP = 2_000;
|
|
|
30
32
|
const DEFAULT_BURN_WINDOW_MS = 5 * 60_000;
|
|
31
33
|
const DEFAULT_BURN_SAMPLE_MS = 10_000;
|
|
32
34
|
const COMPACTION_DROP_THRESHOLD = 0.8; // >20% drop
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Schema version for serialized snapshots. v6: token totals count each Claude
|
|
37
|
+
* response and Codex call once. v7: subagent lifecycles track Codex spawns and
|
|
38
|
+
* record what settled a background agent (`settledBy`).
|
|
39
|
+
*/
|
|
40
|
+
exports.SNAPSHOT_SCHEMA_VERSION = 7;
|
|
41
|
+
// Checked without the schema module so this file stays free of Zod.
|
|
42
|
+
const PERMISSION_MODES = new Set([
|
|
43
|
+
'default',
|
|
44
|
+
'acceptEdits',
|
|
45
|
+
'bypassPermissions',
|
|
46
|
+
'plan',
|
|
47
|
+
'auto',
|
|
48
|
+
'dontAsk',
|
|
49
|
+
]);
|
|
50
|
+
function asPermissionMode(value) {
|
|
51
|
+
return typeof value === 'string' && PERMISSION_MODES.has(value)
|
|
52
|
+
? value
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
35
55
|
// ── Goal gate detection regex ──
|
|
36
56
|
const GOAL_GATE_REGEX = /\b(CRITICAL|MUST|blocker|required|must.?complete|goal.?gate|essential|do.?not.?skip|blocking)\b/i;
|
|
57
|
+
// ── Task notifications delivered as attachments ──
|
|
58
|
+
/**
|
|
59
|
+
* The notification a `system` FollowEvent carries when it stands for a
|
|
60
|
+
* mid-turn task-notification attachment (its `raw` is the attachment line or
|
|
61
|
+
* the SessionEvent `extractSessionEvents` made from it).
|
|
62
|
+
*/
|
|
63
|
+
function taskNotificationFromAttachmentFollowEvent(event) {
|
|
64
|
+
const raw = event.raw;
|
|
65
|
+
if (!raw || typeof raw !== 'object')
|
|
66
|
+
return null;
|
|
67
|
+
const prompt = (0, subagentSpawn_1.taskNotificationPromptFromAttachment)(raw);
|
|
68
|
+
if (prompt !== null)
|
|
69
|
+
return (0, subagentSpawn_1.parseTaskNotification)(prompt);
|
|
70
|
+
const asEvent = raw;
|
|
71
|
+
return (0, subagentSpawn_1.isTaskNotificationSystemEvent)(asEvent) ? (0, subagentSpawn_1.taskNotificationFromEvent)(asEvent) : null;
|
|
72
|
+
}
|
|
73
|
+
function subagentRecordFromFollowEvent(event) {
|
|
74
|
+
const raw = event.raw;
|
|
75
|
+
if (!raw || typeof raw !== 'object')
|
|
76
|
+
return null;
|
|
77
|
+
const notification = taskNotificationFromAttachmentFollowEvent(event);
|
|
78
|
+
if (notification)
|
|
79
|
+
return { kind: 'notification', notification };
|
|
80
|
+
const asEvent = raw;
|
|
81
|
+
const prompt = (0, subagentSpawn_1.taskNotificationPromptFromAttachment)(raw);
|
|
82
|
+
const notice = prompt !== null ? (0, subagentSpawn_1.parseAgentsStoppedNotice)(prompt) : (0, subagentSpawn_1.agentsStoppedNoticeFromEvent)(asEvent);
|
|
83
|
+
if (notice)
|
|
84
|
+
return { kind: 'stopped', notice };
|
|
85
|
+
if (prompt !== null || (0, subagentSpawn_1.isTaskNotificationSystemEvent)(asEvent)) {
|
|
86
|
+
// A notification this release cannot read: still bookkeeping.
|
|
87
|
+
return { kind: 'unreadable' };
|
|
88
|
+
}
|
|
89
|
+
const status = (0, subagentSpawn_1.taskStatusFromAttachment)(raw) ?? (0, subagentSpawn_1.taskStatusFromEvent)(asEvent);
|
|
90
|
+
if (status)
|
|
91
|
+
return { kind: 'task-status', status };
|
|
92
|
+
if ((0, subagentActivity_1.isSubagentActivitySystemEvent)(asEvent)) {
|
|
93
|
+
const activity = (0, subagentActivity_1.subagentActivityFromEvent)(asEvent);
|
|
94
|
+
return activity ? { kind: 'activity', activity } : { kind: 'unreadable' };
|
|
95
|
+
}
|
|
96
|
+
if ((0, subagentSpawn_1.isAgentsKilledSystemEvent)(raw))
|
|
97
|
+
return { kind: 'agents-killed' };
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
// ── Subagent spawn input ──
|
|
101
|
+
/**
|
|
102
|
+
* Description and type of a spawn: Claude Code's `description` /
|
|
103
|
+
* `subagent_type`, or Codex `spawn_agent`'s `task_name` / `agent_type`.
|
|
104
|
+
*/
|
|
105
|
+
function spawnFields(input) {
|
|
106
|
+
const text = (value) => typeof value === 'string' && value ? value : undefined;
|
|
107
|
+
const description = text(input.description) ?? text(input.task_name);
|
|
108
|
+
const subagentType = text(input.subagent_type) ?? text(input.subagentType) ?? text(input.agent_type);
|
|
109
|
+
return {
|
|
110
|
+
...(description ? { description } : {}),
|
|
111
|
+
...(subagentType ? { subagentType } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Whether a spawn's tool result launched the agent in the background or
|
|
116
|
+
* ended it. A Codex `spawn_agent` result is always a launch unless it failed
|
|
117
|
+
* ("collab spawn failed: …"); its `{"task_name": …}` output is a launch even
|
|
118
|
+
* when the output's text looked like an error to the parser (an agent path
|
|
119
|
+
* such as `/root/fix_failed_tests`).
|
|
120
|
+
*/
|
|
121
|
+
function spawnResultKind(codex, content, isError) {
|
|
122
|
+
// Read first: the `codex` hint does not survive a snapshot boundary.
|
|
123
|
+
if ((0, subagentSpawn_1.parseCodexSpawnLaunch)(content))
|
|
124
|
+
return 'launch';
|
|
125
|
+
if (codex)
|
|
126
|
+
return isError ? 'end' : 'launch';
|
|
127
|
+
return !isError && (0, subagentSpawn_1.parseAsyncAgentLaunch)(content) ? 'launch' : 'end';
|
|
128
|
+
}
|
|
37
129
|
// ── Todo dependency parsing ──
|
|
38
130
|
/**
|
|
39
131
|
* Parses dependency references from a todo item's content text.
|
|
@@ -119,6 +211,8 @@ class EventAggregator {
|
|
|
119
211
|
// Subagent lifecycle
|
|
120
212
|
subagents = [];
|
|
121
213
|
pendingSubagents = new Map(); // toolUseId -> index
|
|
214
|
+
/** Codex `spawn_agent` calls still waiting for their result (always a background launch). */
|
|
215
|
+
pendingCodexSpawns = new Set();
|
|
122
216
|
// Plan state
|
|
123
217
|
planExtractor;
|
|
124
218
|
planState = null;
|
|
@@ -172,6 +266,41 @@ class EventAggregator {
|
|
|
172
266
|
// processEvent — main entry for SessionEvent
|
|
173
267
|
// ═══════════════════════════════════════════════════════════════════════
|
|
174
268
|
processEvent(event) {
|
|
269
|
+
// Opt-in mid-turn task notifications (`extractSessionEvents` with
|
|
270
|
+
// `includeTaskNotifications`) only finish background subagents and mark
|
|
271
|
+
// the timeline: they are not messages, prompts, context, or activity.
|
|
272
|
+
if ((0, subagentSpawn_1.isTaskNotificationSystemEvent)(event)) {
|
|
273
|
+
const notification = (0, subagentSpawn_1.taskNotificationFromEvent)(event);
|
|
274
|
+
if (notification) {
|
|
275
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
276
|
+
this.addTaskNotificationTimeline(event.timestamp, notification);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// The plain-text "background agents were stopped by the user" notice.
|
|
280
|
+
const notice = (0, subagentSpawn_1.agentsStoppedNoticeFromEvent)(event);
|
|
281
|
+
if (notice)
|
|
282
|
+
this.applyAgentsStopped(notice, event.timestamp);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// So are a background agent's final `task_status` (same opt-in) and the
|
|
287
|
+
// opt-in Codex subagent lifecycle records (`subagentActivityFromEvent`).
|
|
288
|
+
if ((0, subagentSpawn_1.isTaskStatusSystemEvent)(event)) {
|
|
289
|
+
const status = (0, subagentSpawn_1.taskStatusFromEvent)(event);
|
|
290
|
+
if (status)
|
|
291
|
+
this.applyTaskStatus(status, event.timestamp);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if ((0, subagentActivity_1.isSubagentActivitySystemEvent)(event)) {
|
|
295
|
+
const activity = (0, subagentActivity_1.subagentActivityFromEvent)(event);
|
|
296
|
+
if (activity)
|
|
297
|
+
this.applySubagentActivity(activity, event.timestamp);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// The user killed every running background agent. The line itself is
|
|
301
|
+
// counted below as before.
|
|
302
|
+
if ((0, subagentSpawn_1.isAgentsKilledSystemEvent)(event))
|
|
303
|
+
this.applyAgentsKilled(event.timestamp);
|
|
175
304
|
// 1. Increment counters, capture timestamps
|
|
176
305
|
this.eventCount++;
|
|
177
306
|
if (!this.sessionStartTime) {
|
|
@@ -237,12 +366,24 @@ class EventAggregator {
|
|
|
237
366
|
// processFollowEvent — adapter for CLI FollowEvent format
|
|
238
367
|
// ═══════════════════════════════════════════════════════════════════════
|
|
239
368
|
processFollowEvent(event) {
|
|
369
|
+
// Subagent bookkeeping (a mid-turn task-notification or task-status
|
|
370
|
+
// attachment, a stop notice or `agents_killed` line, a Codex lifecycle
|
|
371
|
+
// record), like its SessionEvent form above: not counted as activity.
|
|
372
|
+
if (event.type === 'system') {
|
|
373
|
+
const record = subagentRecordFromFollowEvent(event);
|
|
374
|
+
if (record) {
|
|
375
|
+
this.applySubagentFollowRecord(record, event.timestamp);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
240
379
|
// 1. Increment counters, capture timestamps
|
|
241
380
|
this.eventCount++;
|
|
242
381
|
if (!this.sessionStartTime) {
|
|
243
382
|
this.sessionStartTime = event.timestamp;
|
|
244
383
|
}
|
|
245
384
|
this.lastEventTime = event.timestamp;
|
|
385
|
+
// Followed events carry the mode as a plain string.
|
|
386
|
+
this.trackPermissionMode(event);
|
|
246
387
|
// Provider
|
|
247
388
|
if (!this._providerId && event.providerId) {
|
|
248
389
|
this._providerId = event.providerId;
|
|
@@ -374,6 +515,10 @@ class EventAggregator {
|
|
|
374
515
|
getSubagents() {
|
|
375
516
|
return [...this.subagents];
|
|
376
517
|
}
|
|
518
|
+
/** Timestamp of the last counted event (`getMetrics().lastEventTime`), without building the metrics. */
|
|
519
|
+
getLastEventTime() {
|
|
520
|
+
return this.lastEventTime;
|
|
521
|
+
}
|
|
377
522
|
getPlan() {
|
|
378
523
|
return this.planState
|
|
379
524
|
? { ...this.planState, steps: this.planState.steps.map((step) => ({ ...step })) }
|
|
@@ -470,6 +615,7 @@ class EventAggregator {
|
|
|
470
615
|
this.activeTaskId = null;
|
|
471
616
|
this.subagents = [];
|
|
472
617
|
this.pendingSubagents.clear();
|
|
618
|
+
this.pendingCodexSpawns.clear();
|
|
473
619
|
this.planExtractor.reset();
|
|
474
620
|
this.planState = null;
|
|
475
621
|
this.planEnteredAt = undefined;
|
|
@@ -603,7 +749,7 @@ class EventAggregator {
|
|
|
603
749
|
this.tasks = new Map(state.tasks);
|
|
604
750
|
this.activeTaskId = state.activeTaskId;
|
|
605
751
|
this.subagents = [...state.subagents];
|
|
606
|
-
this.currentPermissionMode = state.permissionMode ?? null;
|
|
752
|
+
this.currentPermissionMode = asPermissionMode(state.permissionMode) ?? null;
|
|
607
753
|
this.permissionModeHistory = state.permissionModeHistory
|
|
608
754
|
? [...state.permissionModeHistory]
|
|
609
755
|
: [];
|
|
@@ -630,6 +776,15 @@ class EventAggregator {
|
|
|
630
776
|
this.pendingToolCalls.clear();
|
|
631
777
|
this.pendingTaskCreates.clear();
|
|
632
778
|
this.pendingSubagents.clear();
|
|
779
|
+
this.pendingCodexSpawns.clear();
|
|
780
|
+
// A spawn still waiting for its tool_result is resolved by that result,
|
|
781
|
+
// which can land after the snapshot boundary. Background agents need no
|
|
782
|
+
// entry: their notifications are matched against every subagent.
|
|
783
|
+
this.subagents.forEach((agent, idx) => {
|
|
784
|
+
if (agent.status === 'running' && !agent.background && agent.id) {
|
|
785
|
+
this.pendingSubagents.set(agent.id, idx);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
633
788
|
this.pendingUserRequest = null;
|
|
634
789
|
this.planExtractor.reset();
|
|
635
790
|
return true;
|
|
@@ -1108,7 +1263,7 @@ class EventAggregator {
|
|
|
1108
1263
|
else if (name === 'UpdatePlan') {
|
|
1109
1264
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1110
1265
|
}
|
|
1111
|
-
else if ((
|
|
1266
|
+
else if ((0, subagentSpawn_1.isSubagentSpawnTool)(name, input) && toolUseId) {
|
|
1112
1267
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1113
1268
|
}
|
|
1114
1269
|
}
|
|
@@ -1118,7 +1273,7 @@ class EventAggregator {
|
|
|
1118
1273
|
continue;
|
|
1119
1274
|
const isError = block.is_error ?? false;
|
|
1120
1275
|
this.resolveTaskCreate(toolUseId, block.content ?? block.output, event.timestamp, isError);
|
|
1121
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1276
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, block.content ?? block.output);
|
|
1122
1277
|
}
|
|
1123
1278
|
}
|
|
1124
1279
|
}
|
|
@@ -1152,7 +1307,7 @@ class EventAggregator {
|
|
|
1152
1307
|
else if (event.toolName === 'UpdatePlan') {
|
|
1153
1308
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1154
1309
|
}
|
|
1155
|
-
else if ((
|
|
1310
|
+
else if ((0, subagentSpawn_1.isSubagentSpawnTool)(event.toolName, input) && toolUseId) {
|
|
1156
1311
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1157
1312
|
}
|
|
1158
1313
|
}
|
|
@@ -1164,7 +1319,7 @@ class EventAggregator {
|
|
|
1164
1319
|
return;
|
|
1165
1320
|
const isError = raw.is_error ?? false;
|
|
1166
1321
|
this.resolveTaskCreate(toolUseId, raw.content, event.timestamp, isError);
|
|
1167
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1322
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, raw.content);
|
|
1168
1323
|
}
|
|
1169
1324
|
}
|
|
1170
1325
|
applyTaskUpdate(input) {
|
|
@@ -1451,8 +1606,9 @@ class EventAggregator {
|
|
|
1451
1606
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1452
1607
|
handleAgentSpawn(toolUseId, input, now) {
|
|
1453
1608
|
const agentTaskId = `agent-${toolUseId}`;
|
|
1454
|
-
const
|
|
1455
|
-
const
|
|
1609
|
+
const spawn = spawnFields(input);
|
|
1610
|
+
const description = spawn.description ?? 'Subagent';
|
|
1611
|
+
const subagentType = spawn.subagentType;
|
|
1456
1612
|
const task = {
|
|
1457
1613
|
taskId: agentTaskId,
|
|
1458
1614
|
subject: description,
|
|
@@ -1469,11 +1625,15 @@ class EventAggregator {
|
|
|
1469
1625
|
};
|
|
1470
1626
|
this.tasks.set(agentTaskId, task);
|
|
1471
1627
|
}
|
|
1472
|
-
resolveAgentResult(toolUseId, isError, timestamp) {
|
|
1628
|
+
resolveAgentResult(toolUseId, isError, timestamp, content) {
|
|
1473
1629
|
const agentTaskId = `agent-${toolUseId}`;
|
|
1474
1630
|
const agentTask = this.tasks.get(agentTaskId);
|
|
1475
1631
|
if (!agentTask)
|
|
1476
1632
|
return;
|
|
1633
|
+
// A background launch returns at once; its task notification finishes it.
|
|
1634
|
+
if (spawnResultKind(this.pendingCodexSpawns.has(toolUseId), content, isError) === 'launch') {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1477
1637
|
agentTask.status = isError ? 'deleted' : 'completed';
|
|
1478
1638
|
agentTask.updatedAt = new Date(timestamp);
|
|
1479
1639
|
}
|
|
@@ -1493,31 +1653,25 @@ class EventAggregator {
|
|
|
1493
1653
|
// Private: Subagent Tracking from SessionEvent
|
|
1494
1654
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1495
1655
|
extractSubagentFromEvent(event) {
|
|
1656
|
+
const notification = (0, subagentSpawn_1.taskNotificationFromEvent)(event);
|
|
1657
|
+
if (notification)
|
|
1658
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1496
1659
|
const content = event.message?.content;
|
|
1497
1660
|
if (!Array.isArray(content))
|
|
1498
1661
|
return;
|
|
1499
1662
|
for (const block of content) {
|
|
1500
|
-
if (block.type === 'tool_use' && block.name
|
|
1663
|
+
if (block.type === 'tool_use' && (0, subagentSpawn_1.isSubagentSpawnTool)(block.name, block.input)) {
|
|
1501
1664
|
const input = block.input;
|
|
1502
1665
|
const toolUseId = block.id;
|
|
1503
1666
|
if (!input || !toolUseId)
|
|
1504
1667
|
continue;
|
|
1505
|
-
|
|
1506
|
-
id: toolUseId,
|
|
1507
|
-
description: input.description || 'Unknown task',
|
|
1508
|
-
subagentType: input.subagent_type || input.subagentType || 'general',
|
|
1509
|
-
spawnTime: event.timestamp,
|
|
1510
|
-
status: 'running',
|
|
1511
|
-
};
|
|
1512
|
-
const idx = this.subagents.length;
|
|
1513
|
-
this.subagents.push(info);
|
|
1514
|
-
this.pendingSubagents.set(toolUseId, idx);
|
|
1668
|
+
this.trackSubagentSpawn(toolUseId, input, event.timestamp, (0, subagentSpawn_1.isCodexSubagentSpawnTool)(block.name, input));
|
|
1515
1669
|
}
|
|
1516
1670
|
else if (block.type === 'tool_result') {
|
|
1517
1671
|
const toolUseId = block.tool_use_id;
|
|
1518
1672
|
if (!toolUseId)
|
|
1519
1673
|
continue;
|
|
1520
|
-
this.
|
|
1674
|
+
this.resolveSubagentResult(toolUseId, block.content ?? block.output, block.is_error === true, event.timestamp);
|
|
1521
1675
|
}
|
|
1522
1676
|
}
|
|
1523
1677
|
}
|
|
@@ -1526,23 +1680,10 @@ class EventAggregator {
|
|
|
1526
1680
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1527
1681
|
extractSubagentFromFollowEvent(event) {
|
|
1528
1682
|
const raw = event.raw;
|
|
1529
|
-
if (event.type === 'tool_use' && event.toolName
|
|
1683
|
+
if (event.type === 'tool_use' && (0, subagentSpawn_1.isSubagentSpawnTool)(event.toolName, raw?.input)) {
|
|
1530
1684
|
if (!raw?.input)
|
|
1531
1685
|
return;
|
|
1532
|
-
|
|
1533
|
-
const toolUseId = raw.id || '';
|
|
1534
|
-
const info = {
|
|
1535
|
-
id: toolUseId,
|
|
1536
|
-
description: input.description || 'Unknown task',
|
|
1537
|
-
subagentType: input.subagent_type || input.subagentType || 'general',
|
|
1538
|
-
spawnTime: event.timestamp,
|
|
1539
|
-
status: 'running',
|
|
1540
|
-
};
|
|
1541
|
-
const idx = this.subagents.length;
|
|
1542
|
-
this.subagents.push(info);
|
|
1543
|
-
if (toolUseId) {
|
|
1544
|
-
this.pendingSubagents.set(toolUseId, idx);
|
|
1545
|
-
}
|
|
1686
|
+
this.trackSubagentSpawn(raw.id || '', raw.input, event.timestamp, (0, subagentSpawn_1.isCodexSubagentSpawnTool)(event.toolName, raw.input));
|
|
1546
1687
|
}
|
|
1547
1688
|
else if (event.type === 'tool_result') {
|
|
1548
1689
|
if (!raw)
|
|
@@ -1550,22 +1691,374 @@ class EventAggregator {
|
|
|
1550
1691
|
const toolUseId = raw.tool_use_id;
|
|
1551
1692
|
if (!toolUseId)
|
|
1552
1693
|
return;
|
|
1553
|
-
this.
|
|
1694
|
+
this.resolveSubagentResult(toolUseId, raw.content, raw.is_error === true, event.timestamp);
|
|
1695
|
+
}
|
|
1696
|
+
else if (event.type === 'user' && raw?.type === 'user') {
|
|
1697
|
+
// Claude Code delivers most task notifications as their own user line.
|
|
1698
|
+
const notification = (0, subagentSpawn_1.taskNotificationFromEvent)(raw);
|
|
1699
|
+
if (notification)
|
|
1700
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
trackSubagentSpawn(toolUseId, input, timestamp, codex = false) {
|
|
1704
|
+
const spawn = spawnFields(input);
|
|
1705
|
+
const info = {
|
|
1706
|
+
id: toolUseId,
|
|
1707
|
+
description: spawn.description ?? 'Unknown task',
|
|
1708
|
+
subagentType: spawn.subagentType ?? (codex ? 'default' : 'general'),
|
|
1709
|
+
spawnTime: timestamp,
|
|
1710
|
+
status: 'running',
|
|
1711
|
+
};
|
|
1712
|
+
const idx = this.subagents.length;
|
|
1713
|
+
this.subagents.push(info);
|
|
1714
|
+
if (toolUseId) {
|
|
1715
|
+
this.pendingSubagents.set(toolUseId, idx);
|
|
1716
|
+
if (codex)
|
|
1717
|
+
this.pendingCodexSpawns.add(toolUseId);
|
|
1554
1718
|
}
|
|
1555
1719
|
}
|
|
1556
|
-
|
|
1720
|
+
/**
|
|
1721
|
+
* The spawning tool call's result. A foreground agent ends here; a
|
|
1722
|
+
* background launch only reports the agent id (a Codex spawn, its agent
|
|
1723
|
+
* path), and the agent keeps running until a notification or another
|
|
1724
|
+
* record says it ended.
|
|
1725
|
+
*/
|
|
1726
|
+
resolveSubagentResult(toolUseId, content, isError, timestamp) {
|
|
1557
1727
|
const idx = this.pendingSubagents.get(toolUseId);
|
|
1558
1728
|
if (idx === undefined)
|
|
1559
1729
|
return;
|
|
1560
|
-
this.
|
|
1730
|
+
const codex = this.pendingCodexSpawns.delete(toolUseId);
|
|
1731
|
+
if (spawnResultKind(codex, content, isError) === 'launch') {
|
|
1732
|
+
const launch = (0, subagentSpawn_1.parseCodexSpawnLaunch)(content) ?? (0, subagentSpawn_1.parseAsyncAgentLaunch)(content) ?? {};
|
|
1733
|
+
this.markBackgroundLaunch(idx, launch);
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
this.completeSubagent(toolUseId, timestamp, isError ? 'failed' : 'completed');
|
|
1737
|
+
}
|
|
1738
|
+
markBackgroundLaunch(idx, launch) {
|
|
1561
1739
|
const agent = this.subagents[idx];
|
|
1740
|
+
this.pendingSubagents.delete(agent.id);
|
|
1741
|
+
agent.background = true;
|
|
1742
|
+
if (launch.agentId)
|
|
1743
|
+
agent.agentId = launch.agentId;
|
|
1744
|
+
if (launch.agentPath && !agent.agentPath)
|
|
1745
|
+
agent.agentPath = launch.agentPath;
|
|
1746
|
+
}
|
|
1747
|
+
completeSubagent(toolUseId, timestamp, outcome = 'completed') {
|
|
1748
|
+
const idx = this.pendingSubagents.get(toolUseId);
|
|
1749
|
+
if (idx === undefined)
|
|
1750
|
+
return;
|
|
1751
|
+
this.finishSubagent(this.subagents[idx], timestamp, outcome);
|
|
1752
|
+
}
|
|
1753
|
+
finishSubagent(agent, timestamp, outcome, settledBy) {
|
|
1754
|
+
this.pendingSubagents.delete(agent.id);
|
|
1755
|
+
this.pendingCodexSpawns.delete(agent.id);
|
|
1562
1756
|
agent.status = 'completed';
|
|
1563
1757
|
agent.completionTime = timestamp;
|
|
1758
|
+
agent.outcome = outcome;
|
|
1759
|
+
if (settledBy)
|
|
1760
|
+
agent.settledBy = settledBy;
|
|
1761
|
+
else
|
|
1762
|
+
delete agent.settledBy;
|
|
1564
1763
|
const start = new Date(agent.spawnTime).getTime();
|
|
1565
1764
|
const end = new Date(timestamp).getTime();
|
|
1566
1765
|
if (!isNaN(start) && !isNaN(end) && end >= start) {
|
|
1567
1766
|
agent.durationMs = end - start;
|
|
1568
1767
|
}
|
|
1768
|
+
else {
|
|
1769
|
+
delete agent.durationMs;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
/** Puts a settled agent back to running (it resumed). */
|
|
1773
|
+
reopenSubagent(agent, timestamp) {
|
|
1774
|
+
this.pendingSubagents.delete(agent.id);
|
|
1775
|
+
agent.status = 'running';
|
|
1776
|
+
delete agent.completionTime;
|
|
1777
|
+
delete agent.durationMs;
|
|
1778
|
+
delete agent.outcome;
|
|
1779
|
+
delete agent.settledBy;
|
|
1780
|
+
this.settleAgentTask(agent.id, null, timestamp);
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Settles a lifecycle that is still running (and its task card). Agents
|
|
1784
|
+
* already settled are left alone: a stop notice or status can name agents
|
|
1785
|
+
* that finished earlier.
|
|
1786
|
+
*/
|
|
1787
|
+
settleRunningSubagent(agent, timestamp, outcome, settledBy) {
|
|
1788
|
+
if (agent.status !== 'running')
|
|
1789
|
+
return false;
|
|
1790
|
+
this.finishSubagent(agent, timestamp, outcome, settledBy);
|
|
1791
|
+
this.settleAgentTask(agent.id, outcome, timestamp);
|
|
1792
|
+
return true;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Moves an agent's task card with its lifecycle: `null` back to in
|
|
1796
|
+
* progress, a failure to deleted, any other outcome to completed.
|
|
1797
|
+
*/
|
|
1798
|
+
settleAgentTask(toolUseId, outcome, timestamp) {
|
|
1799
|
+
const agentTask = toolUseId ? this.tasks.get(`agent-${toolUseId}`) : undefined;
|
|
1800
|
+
if (!agentTask?.isSubagent)
|
|
1801
|
+
return;
|
|
1802
|
+
agentTask.status =
|
|
1803
|
+
outcome === null ? 'in_progress' : outcome === 'failed' ? 'deleted' : 'completed';
|
|
1804
|
+
agentTask.updatedAt = new Date(timestamp);
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Applies a background task's notification to the subagent it reports on,
|
|
1808
|
+
* matched by the spawning tool_use id (or by agent id when the notification
|
|
1809
|
+
* has none). The latest notification wins: an interim one ("waiting on its
|
|
1810
|
+
* own background work") keeps or puts the agent back to running, a final one
|
|
1811
|
+
* completes it. Notifications for background shell tasks match nothing.
|
|
1812
|
+
*/
|
|
1813
|
+
applyTaskNotification(notification, timestamp) {
|
|
1814
|
+
const agent = this.findNotifiedSubagent(notification);
|
|
1815
|
+
const toolUseId = agent?.id || notification.toolUseId;
|
|
1816
|
+
const outcome = (0, subagentSpawn_1.taskNotificationOutcome)(notification.status);
|
|
1817
|
+
if (agent) {
|
|
1818
|
+
agent.background = true;
|
|
1819
|
+
if (!agent.agentId)
|
|
1820
|
+
agent.agentId = notification.taskId;
|
|
1821
|
+
if (notification.interim) {
|
|
1822
|
+
this.pendingSubagents.delete(agent.id);
|
|
1823
|
+
agent.status = 'running';
|
|
1824
|
+
delete agent.completionTime;
|
|
1825
|
+
delete agent.durationMs;
|
|
1826
|
+
delete agent.outcome;
|
|
1827
|
+
delete agent.settledBy;
|
|
1828
|
+
}
|
|
1829
|
+
else {
|
|
1830
|
+
this.finishSubagent(agent, timestamp, outcome, 'notification');
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
this.settleAgentTask(toolUseId, notification.interim ? null : outcome, timestamp);
|
|
1834
|
+
}
|
|
1835
|
+
findNotifiedSubagent(notification) {
|
|
1836
|
+
const { toolUseId, taskId } = notification;
|
|
1837
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1838
|
+
if (toolUseId && this.subagents[i].id === toolUseId)
|
|
1839
|
+
return this.subagents[i];
|
|
1840
|
+
}
|
|
1841
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1842
|
+
if (this.subagents[i].agentId === taskId)
|
|
1843
|
+
return this.subagents[i];
|
|
1844
|
+
}
|
|
1845
|
+
return undefined;
|
|
1846
|
+
}
|
|
1847
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1848
|
+
// Subagent settling: evidence, stop markers, task statuses, Codex records
|
|
1849
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1850
|
+
/**
|
|
1851
|
+
* Applies host evidence about a subagent (Claude Code's meta flags):
|
|
1852
|
+
* `stoppedByUser` settles a running agent as `stopped`, `finished` as
|
|
1853
|
+
* `completed` (`settledBy: 'meta'`). Agents already settled some other way
|
|
1854
|
+
* keep their outcome; `finished: false` reopens an agent that only meta
|
|
1855
|
+
* settled. Matches by `toolUseId`, then `agentId` (the newest such agent),
|
|
1856
|
+
* and records the agent id when matched by tool_use id.
|
|
1857
|
+
*
|
|
1858
|
+
* @param timestamp ISO time to record as the completion (the agent's last
|
|
1859
|
+
* activity, when the host knows it)
|
|
1860
|
+
* @returns Whether a lifecycle changed
|
|
1861
|
+
*/
|
|
1862
|
+
applySubagentEvidence(evidence, timestamp) {
|
|
1863
|
+
const agent = this.findSubagent(evidence.toolUseId, evidence.agentId);
|
|
1864
|
+
if (!agent)
|
|
1865
|
+
return false;
|
|
1866
|
+
let changed = false;
|
|
1867
|
+
if (evidence.agentId && !agent.agentId) {
|
|
1868
|
+
agent.agentId = evidence.agentId;
|
|
1869
|
+
changed = true;
|
|
1870
|
+
}
|
|
1871
|
+
if (agent.status === 'running') {
|
|
1872
|
+
const outcome = evidence.stoppedByUser
|
|
1873
|
+
? 'stopped'
|
|
1874
|
+
: evidence.finished
|
|
1875
|
+
? 'completed'
|
|
1876
|
+
: null;
|
|
1877
|
+
if (outcome)
|
|
1878
|
+
changed = this.settleRunningSubagent(agent, timestamp, outcome, 'meta') || changed;
|
|
1879
|
+
}
|
|
1880
|
+
else if (agent.settledBy === 'meta' &&
|
|
1881
|
+
evidence.finished === false &&
|
|
1882
|
+
evidence.stoppedByUser !== true) {
|
|
1883
|
+
this.reopenSubagent(agent, timestamp);
|
|
1884
|
+
changed = true;
|
|
1885
|
+
}
|
|
1886
|
+
return changed;
|
|
1887
|
+
}
|
|
1888
|
+
/** The newest lifecycle with this spawning tool_use id, else with this agent id. */
|
|
1889
|
+
findSubagent(toolUseId, agentId) {
|
|
1890
|
+
if (toolUseId) {
|
|
1891
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1892
|
+
if (this.subagents[i].id === toolUseId)
|
|
1893
|
+
return this.subagents[i];
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (agentId) {
|
|
1897
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1898
|
+
if (this.subagents[i].agentId === agentId)
|
|
1899
|
+
return this.subagents[i];
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return undefined;
|
|
1903
|
+
}
|
|
1904
|
+
/** `agents_killed`: the user killed every running background agent. */
|
|
1905
|
+
applyAgentsKilled(timestamp) {
|
|
1906
|
+
let settled = 0;
|
|
1907
|
+
for (const agent of this.subagents) {
|
|
1908
|
+
if (!agent.background)
|
|
1909
|
+
continue;
|
|
1910
|
+
if (this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker'))
|
|
1911
|
+
settled++;
|
|
1912
|
+
}
|
|
1913
|
+
if (settled > 0)
|
|
1914
|
+
this.addSubagentTimeline(timestamp, (0, subagentSpawn_1.describeAgentsStopped)(null));
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* "N background agents were stopped by the user: "A", "B"": settles the
|
|
1918
|
+
* running agents with those descriptions, the newest first for repeats.
|
|
1919
|
+
*/
|
|
1920
|
+
applyAgentsStopped(notice, timestamp) {
|
|
1921
|
+
const taken = new Set();
|
|
1922
|
+
for (const description of notice.descriptions) {
|
|
1923
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1924
|
+
const agent = this.subagents[i];
|
|
1925
|
+
if (taken.has(agent) || agent.status !== 'running')
|
|
1926
|
+
continue;
|
|
1927
|
+
if (agent.description.trim() !== description)
|
|
1928
|
+
continue;
|
|
1929
|
+
taken.add(agent);
|
|
1930
|
+
this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker');
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
this.addSubagentTimeline(timestamp, (0, subagentSpawn_1.describeAgentsStopped)(notice));
|
|
1935
|
+
}
|
|
1936
|
+
/** A background agent's final `task_status` attachment, matched by agent id. */
|
|
1937
|
+
applyTaskStatus(status, timestamp) {
|
|
1938
|
+
if (!(0, subagentSpawn_1.isFinalTaskStatus)(status.status))
|
|
1939
|
+
return;
|
|
1940
|
+
const agent = this.findSubagent(undefined, status.taskId);
|
|
1941
|
+
if (!agent)
|
|
1942
|
+
return;
|
|
1943
|
+
if (this.settleRunningSubagent(agent, timestamp, (0, subagentSpawn_1.taskNotificationOutcome)(status.status), 'task-status')) {
|
|
1944
|
+
this.addSubagentTimeline(timestamp, (0, subagentSpawn_1.describeTaskStatus)(status), agent.id);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* A Codex subagent lifecycle record: `started` links the spawn to its child
|
|
1949
|
+
* thread; `completed` (a child turn finished) and `final-answer` (the
|
|
1950
|
+
* child's report, matched by its agent path) complete it; `interacted`
|
|
1951
|
+
* after `followup_task` puts it back to running (a `send_message` does
|
|
1952
|
+
* not: it does not start a turn); `interrupted` (`interrupt_agent`) settles
|
|
1953
|
+
* it as `stopped`, since the child keeps its thread and a follow-up can
|
|
1954
|
+
* run it again, like a user stop rather than a kill.
|
|
1955
|
+
*/
|
|
1956
|
+
applySubagentActivity(activity, timestamp) {
|
|
1957
|
+
const byThread = () => this.findSubagent(undefined, activity.agentThreadId) ??
|
|
1958
|
+
this.findSubagentByPath(activity.agentPath);
|
|
1959
|
+
let agent;
|
|
1960
|
+
switch (activity.kind) {
|
|
1961
|
+
case 'started': {
|
|
1962
|
+
agent = this.findSubagent(activity.callId, undefined);
|
|
1963
|
+
if (!agent)
|
|
1964
|
+
break;
|
|
1965
|
+
agent.background = true;
|
|
1966
|
+
if (activity.agentThreadId)
|
|
1967
|
+
agent.agentId = activity.agentThreadId;
|
|
1968
|
+
if (activity.agentPath)
|
|
1969
|
+
agent.agentPath = activity.agentPath;
|
|
1970
|
+
break;
|
|
1971
|
+
}
|
|
1972
|
+
case 'completed':
|
|
1973
|
+
agent = byThread();
|
|
1974
|
+
if (agent)
|
|
1975
|
+
this.settleRunningSubagent(agent, timestamp, 'completed', 'notification');
|
|
1976
|
+
break;
|
|
1977
|
+
case 'final-answer':
|
|
1978
|
+
agent = this.findSubagentByPath(activity.agentPath, true);
|
|
1979
|
+
if (agent)
|
|
1980
|
+
this.settleRunningSubagent(agent, timestamp, 'completed', 'notification');
|
|
1981
|
+
break;
|
|
1982
|
+
case 'interacted':
|
|
1983
|
+
agent = byThread();
|
|
1984
|
+
if (agent && activity.tool === 'followup_task' && agent.status === 'completed') {
|
|
1985
|
+
this.reopenSubagent(agent, timestamp);
|
|
1986
|
+
}
|
|
1987
|
+
break;
|
|
1988
|
+
case 'interrupted':
|
|
1989
|
+
agent = byThread();
|
|
1990
|
+
if (agent)
|
|
1991
|
+
this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker');
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
this.addSubagentTimeline(timestamp, (0, subagentActivity_1.describeSubagentActivity)(activity), agent?.id);
|
|
1995
|
+
}
|
|
1996
|
+
/** The newest lifecycle with this Codex agent path (with `runningFirst`, a running one first). */
|
|
1997
|
+
findSubagentByPath(agentPath, runningFirst = false) {
|
|
1998
|
+
if (!agentPath)
|
|
1999
|
+
return undefined;
|
|
2000
|
+
let fallback;
|
|
2001
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
2002
|
+
const agent = this.subagents[i];
|
|
2003
|
+
if (agent.agentPath !== agentPath)
|
|
2004
|
+
continue;
|
|
2005
|
+
if (!runningFirst || agent.status === 'running')
|
|
2006
|
+
return agent;
|
|
2007
|
+
fallback ??= agent;
|
|
2008
|
+
}
|
|
2009
|
+
return fallback;
|
|
2010
|
+
}
|
|
2011
|
+
applySubagentFollowRecord(record, timestamp) {
|
|
2012
|
+
switch (record.kind) {
|
|
2013
|
+
case 'notification':
|
|
2014
|
+
this.applyTaskNotification(record.notification, timestamp);
|
|
2015
|
+
this.addTaskNotificationTimeline(timestamp, record.notification);
|
|
2016
|
+
break;
|
|
2017
|
+
case 'stopped':
|
|
2018
|
+
this.applyAgentsStopped(record.notice, timestamp);
|
|
2019
|
+
break;
|
|
2020
|
+
case 'task-status':
|
|
2021
|
+
this.applyTaskStatus(record.status, timestamp);
|
|
2022
|
+
break;
|
|
2023
|
+
case 'activity':
|
|
2024
|
+
this.applySubagentActivity(record.activity, timestamp);
|
|
2025
|
+
break;
|
|
2026
|
+
case 'agents-killed':
|
|
2027
|
+
this.applyAgentsKilled(timestamp);
|
|
2028
|
+
break;
|
|
2029
|
+
case 'unreadable':
|
|
2030
|
+
break;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
addSubagentTimeline(timestamp, description, toolUseId) {
|
|
2034
|
+
const text = description.length > 200 ? description.substring(0, 197) + '...' : description;
|
|
2035
|
+
this.timeline.push({
|
|
2036
|
+
type: 'tool_result',
|
|
2037
|
+
timestamp,
|
|
2038
|
+
description: text,
|
|
2039
|
+
noiseLevel: 'system',
|
|
2040
|
+
messageClassification: 'system',
|
|
2041
|
+
...(toolUseId ? { metadata: { toolUseId } } : {}),
|
|
2042
|
+
});
|
|
2043
|
+
if (this.timeline.length > this.timelineCap) {
|
|
2044
|
+
this.timeline.shift();
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
addTaskNotificationTimeline(timestamp, notification) {
|
|
2048
|
+
let description = notification.summary ?? `Background task ${notification.status ?? 'finished'}`;
|
|
2049
|
+
if (description.length > 200)
|
|
2050
|
+
description = description.substring(0, 197) + '...';
|
|
2051
|
+
this.timeline.push({
|
|
2052
|
+
type: 'tool_result',
|
|
2053
|
+
timestamp,
|
|
2054
|
+
description,
|
|
2055
|
+
noiseLevel: 'system',
|
|
2056
|
+
messageClassification: 'system',
|
|
2057
|
+
...(notification.toolUseId ? { metadata: { toolUseId: notification.toolUseId } } : {}),
|
|
2058
|
+
});
|
|
2059
|
+
if (this.timeline.length > this.timelineCap) {
|
|
2060
|
+
this.timeline.shift();
|
|
2061
|
+
}
|
|
1569
2062
|
}
|
|
1570
2063
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1571
2064
|
// Private: Plan Extraction from SessionEvent
|
|
@@ -1801,9 +2294,9 @@ class EventAggregator {
|
|
|
1801
2294
|
// Private: Permission Mode
|
|
1802
2295
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1803
2296
|
trackPermissionMode(event) {
|
|
1804
|
-
|
|
2297
|
+
const mode = asPermissionMode(event.permissionMode);
|
|
2298
|
+
if (!mode)
|
|
1805
2299
|
return;
|
|
1806
|
-
const mode = event.permissionMode;
|
|
1807
2300
|
if (mode !== this.currentPermissionMode) {
|
|
1808
2301
|
this.permissionModeHistory.push({
|
|
1809
2302
|
timestamp: event.timestamp,
|