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
|
@@ -18,6 +18,8 @@ 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 { describeSubagentActivity, isSubagentActivitySystemEvent, subagentActivityFromEvent, } from '../subagentActivity';
|
|
22
|
+
import { agentsStoppedNoticeFromEvent, describeAgentsStopped, describeTaskStatus, isAgentsKilledSystemEvent, isCodexSubagentSpawnTool, isFinalTaskStatus, isSubagentSpawnTool, isTaskNotificationSystemEvent, isTaskStatusSystemEvent, parseAgentsStoppedNotice, parseAsyncAgentLaunch, parseCodexSpawnLaunch, parseTaskNotification, taskNotificationFromEvent, taskNotificationOutcome, taskNotificationPromptFromAttachment, taskStatusFromAttachment, taskStatusFromEvent, } from '../subagentSpawn';
|
|
21
23
|
import { calculateNormalizedUsageCost, extractNormalizedUsage, normalizeProviderUsage, } from '../usageNormalization';
|
|
22
24
|
// ── Defaults ──
|
|
23
25
|
const DEFAULT_TIMELINE_CAP = 200;
|
|
@@ -26,10 +28,100 @@ const DEFAULT_CONTEXT_TIMELINE_CAP = 2_000;
|
|
|
26
28
|
const DEFAULT_BURN_WINDOW_MS = 5 * 60_000;
|
|
27
29
|
const DEFAULT_BURN_SAMPLE_MS = 10_000;
|
|
28
30
|
const COMPACTION_DROP_THRESHOLD = 0.8; // >20% drop
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Schema version for serialized snapshots. v6: token totals count each Claude
|
|
33
|
+
* response and Codex call once. v7: subagent lifecycles track Codex spawns and
|
|
34
|
+
* record what settled a background agent (`settledBy`).
|
|
35
|
+
*/
|
|
36
|
+
export const SNAPSHOT_SCHEMA_VERSION = 7;
|
|
37
|
+
// Checked without the schema module so this file stays free of Zod.
|
|
38
|
+
const PERMISSION_MODES = new Set([
|
|
39
|
+
'default',
|
|
40
|
+
'acceptEdits',
|
|
41
|
+
'bypassPermissions',
|
|
42
|
+
'plan',
|
|
43
|
+
'auto',
|
|
44
|
+
'dontAsk',
|
|
45
|
+
]);
|
|
46
|
+
function asPermissionMode(value) {
|
|
47
|
+
return typeof value === 'string' && PERMISSION_MODES.has(value)
|
|
48
|
+
? value
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
31
51
|
// ── Goal gate detection regex ──
|
|
32
52
|
const GOAL_GATE_REGEX = /\b(CRITICAL|MUST|blocker|required|must.?complete|goal.?gate|essential|do.?not.?skip|blocking)\b/i;
|
|
53
|
+
// ── Task notifications delivered as attachments ──
|
|
54
|
+
/**
|
|
55
|
+
* The notification a `system` FollowEvent carries when it stands for a
|
|
56
|
+
* mid-turn task-notification attachment (its `raw` is the attachment line or
|
|
57
|
+
* the SessionEvent `extractSessionEvents` made from it).
|
|
58
|
+
*/
|
|
59
|
+
function taskNotificationFromAttachmentFollowEvent(event) {
|
|
60
|
+
const raw = event.raw;
|
|
61
|
+
if (!raw || typeof raw !== 'object')
|
|
62
|
+
return null;
|
|
63
|
+
const prompt = taskNotificationPromptFromAttachment(raw);
|
|
64
|
+
if (prompt !== null)
|
|
65
|
+
return parseTaskNotification(prompt);
|
|
66
|
+
const asEvent = raw;
|
|
67
|
+
return isTaskNotificationSystemEvent(asEvent) ? taskNotificationFromEvent(asEvent) : null;
|
|
68
|
+
}
|
|
69
|
+
function subagentRecordFromFollowEvent(event) {
|
|
70
|
+
const raw = event.raw;
|
|
71
|
+
if (!raw || typeof raw !== 'object')
|
|
72
|
+
return null;
|
|
73
|
+
const notification = taskNotificationFromAttachmentFollowEvent(event);
|
|
74
|
+
if (notification)
|
|
75
|
+
return { kind: 'notification', notification };
|
|
76
|
+
const asEvent = raw;
|
|
77
|
+
const prompt = taskNotificationPromptFromAttachment(raw);
|
|
78
|
+
const notice = prompt !== null ? parseAgentsStoppedNotice(prompt) : agentsStoppedNoticeFromEvent(asEvent);
|
|
79
|
+
if (notice)
|
|
80
|
+
return { kind: 'stopped', notice };
|
|
81
|
+
if (prompt !== null || isTaskNotificationSystemEvent(asEvent)) {
|
|
82
|
+
// A notification this release cannot read: still bookkeeping.
|
|
83
|
+
return { kind: 'unreadable' };
|
|
84
|
+
}
|
|
85
|
+
const status = taskStatusFromAttachment(raw) ?? taskStatusFromEvent(asEvent);
|
|
86
|
+
if (status)
|
|
87
|
+
return { kind: 'task-status', status };
|
|
88
|
+
if (isSubagentActivitySystemEvent(asEvent)) {
|
|
89
|
+
const activity = subagentActivityFromEvent(asEvent);
|
|
90
|
+
return activity ? { kind: 'activity', activity } : { kind: 'unreadable' };
|
|
91
|
+
}
|
|
92
|
+
if (isAgentsKilledSystemEvent(raw))
|
|
93
|
+
return { kind: 'agents-killed' };
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// ── Subagent spawn input ──
|
|
97
|
+
/**
|
|
98
|
+
* Description and type of a spawn: Claude Code's `description` /
|
|
99
|
+
* `subagent_type`, or Codex `spawn_agent`'s `task_name` / `agent_type`.
|
|
100
|
+
*/
|
|
101
|
+
function spawnFields(input) {
|
|
102
|
+
const text = (value) => typeof value === 'string' && value ? value : undefined;
|
|
103
|
+
const description = text(input.description) ?? text(input.task_name);
|
|
104
|
+
const subagentType = text(input.subagent_type) ?? text(input.subagentType) ?? text(input.agent_type);
|
|
105
|
+
return {
|
|
106
|
+
...(description ? { description } : {}),
|
|
107
|
+
...(subagentType ? { subagentType } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether a spawn's tool result launched the agent in the background or
|
|
112
|
+
* ended it. A Codex `spawn_agent` result is always a launch unless it failed
|
|
113
|
+
* ("collab spawn failed: …"); its `{"task_name": …}` output is a launch even
|
|
114
|
+
* when the output's text looked like an error to the parser (an agent path
|
|
115
|
+
* such as `/root/fix_failed_tests`).
|
|
116
|
+
*/
|
|
117
|
+
function spawnResultKind(codex, content, isError) {
|
|
118
|
+
// Read first: the `codex` hint does not survive a snapshot boundary.
|
|
119
|
+
if (parseCodexSpawnLaunch(content))
|
|
120
|
+
return 'launch';
|
|
121
|
+
if (codex)
|
|
122
|
+
return isError ? 'end' : 'launch';
|
|
123
|
+
return !isError && parseAsyncAgentLaunch(content) ? 'launch' : 'end';
|
|
124
|
+
}
|
|
33
125
|
// ── Todo dependency parsing ──
|
|
34
126
|
/**
|
|
35
127
|
* Parses dependency references from a todo item's content text.
|
|
@@ -115,6 +207,8 @@ export class EventAggregator {
|
|
|
115
207
|
// Subagent lifecycle
|
|
116
208
|
subagents = [];
|
|
117
209
|
pendingSubagents = new Map(); // toolUseId -> index
|
|
210
|
+
/** Codex `spawn_agent` calls still waiting for their result (always a background launch). */
|
|
211
|
+
pendingCodexSpawns = new Set();
|
|
118
212
|
// Plan state
|
|
119
213
|
planExtractor;
|
|
120
214
|
planState = null;
|
|
@@ -168,6 +262,41 @@ export class EventAggregator {
|
|
|
168
262
|
// processEvent — main entry for SessionEvent
|
|
169
263
|
// ═══════════════════════════════════════════════════════════════════════
|
|
170
264
|
processEvent(event) {
|
|
265
|
+
// Opt-in mid-turn task notifications (`extractSessionEvents` with
|
|
266
|
+
// `includeTaskNotifications`) only finish background subagents and mark
|
|
267
|
+
// the timeline: they are not messages, prompts, context, or activity.
|
|
268
|
+
if (isTaskNotificationSystemEvent(event)) {
|
|
269
|
+
const notification = taskNotificationFromEvent(event);
|
|
270
|
+
if (notification) {
|
|
271
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
272
|
+
this.addTaskNotificationTimeline(event.timestamp, notification);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// The plain-text "background agents were stopped by the user" notice.
|
|
276
|
+
const notice = agentsStoppedNoticeFromEvent(event);
|
|
277
|
+
if (notice)
|
|
278
|
+
this.applyAgentsStopped(notice, event.timestamp);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// So are a background agent's final `task_status` (same opt-in) and the
|
|
283
|
+
// opt-in Codex subagent lifecycle records (`subagentActivityFromEvent`).
|
|
284
|
+
if (isTaskStatusSystemEvent(event)) {
|
|
285
|
+
const status = taskStatusFromEvent(event);
|
|
286
|
+
if (status)
|
|
287
|
+
this.applyTaskStatus(status, event.timestamp);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (isSubagentActivitySystemEvent(event)) {
|
|
291
|
+
const activity = subagentActivityFromEvent(event);
|
|
292
|
+
if (activity)
|
|
293
|
+
this.applySubagentActivity(activity, event.timestamp);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// The user killed every running background agent. The line itself is
|
|
297
|
+
// counted below as before.
|
|
298
|
+
if (isAgentsKilledSystemEvent(event))
|
|
299
|
+
this.applyAgentsKilled(event.timestamp);
|
|
171
300
|
// 1. Increment counters, capture timestamps
|
|
172
301
|
this.eventCount++;
|
|
173
302
|
if (!this.sessionStartTime) {
|
|
@@ -233,12 +362,24 @@ export class EventAggregator {
|
|
|
233
362
|
// processFollowEvent — adapter for CLI FollowEvent format
|
|
234
363
|
// ═══════════════════════════════════════════════════════════════════════
|
|
235
364
|
processFollowEvent(event) {
|
|
365
|
+
// Subagent bookkeeping (a mid-turn task-notification or task-status
|
|
366
|
+
// attachment, a stop notice or `agents_killed` line, a Codex lifecycle
|
|
367
|
+
// record), like its SessionEvent form above: not counted as activity.
|
|
368
|
+
if (event.type === 'system') {
|
|
369
|
+
const record = subagentRecordFromFollowEvent(event);
|
|
370
|
+
if (record) {
|
|
371
|
+
this.applySubagentFollowRecord(record, event.timestamp);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
236
375
|
// 1. Increment counters, capture timestamps
|
|
237
376
|
this.eventCount++;
|
|
238
377
|
if (!this.sessionStartTime) {
|
|
239
378
|
this.sessionStartTime = event.timestamp;
|
|
240
379
|
}
|
|
241
380
|
this.lastEventTime = event.timestamp;
|
|
381
|
+
// Followed events carry the mode as a plain string.
|
|
382
|
+
this.trackPermissionMode(event);
|
|
242
383
|
// Provider
|
|
243
384
|
if (!this._providerId && event.providerId) {
|
|
244
385
|
this._providerId = event.providerId;
|
|
@@ -370,6 +511,10 @@ export class EventAggregator {
|
|
|
370
511
|
getSubagents() {
|
|
371
512
|
return [...this.subagents];
|
|
372
513
|
}
|
|
514
|
+
/** Timestamp of the last counted event (`getMetrics().lastEventTime`), without building the metrics. */
|
|
515
|
+
getLastEventTime() {
|
|
516
|
+
return this.lastEventTime;
|
|
517
|
+
}
|
|
373
518
|
getPlan() {
|
|
374
519
|
return this.planState
|
|
375
520
|
? { ...this.planState, steps: this.planState.steps.map((step) => ({ ...step })) }
|
|
@@ -466,6 +611,7 @@ export class EventAggregator {
|
|
|
466
611
|
this.activeTaskId = null;
|
|
467
612
|
this.subagents = [];
|
|
468
613
|
this.pendingSubagents.clear();
|
|
614
|
+
this.pendingCodexSpawns.clear();
|
|
469
615
|
this.planExtractor.reset();
|
|
470
616
|
this.planState = null;
|
|
471
617
|
this.planEnteredAt = undefined;
|
|
@@ -599,7 +745,7 @@ export class EventAggregator {
|
|
|
599
745
|
this.tasks = new Map(state.tasks);
|
|
600
746
|
this.activeTaskId = state.activeTaskId;
|
|
601
747
|
this.subagents = [...state.subagents];
|
|
602
|
-
this.currentPermissionMode = state.permissionMode ?? null;
|
|
748
|
+
this.currentPermissionMode = asPermissionMode(state.permissionMode) ?? null;
|
|
603
749
|
this.permissionModeHistory = state.permissionModeHistory
|
|
604
750
|
? [...state.permissionModeHistory]
|
|
605
751
|
: [];
|
|
@@ -626,6 +772,15 @@ export class EventAggregator {
|
|
|
626
772
|
this.pendingToolCalls.clear();
|
|
627
773
|
this.pendingTaskCreates.clear();
|
|
628
774
|
this.pendingSubagents.clear();
|
|
775
|
+
this.pendingCodexSpawns.clear();
|
|
776
|
+
// A spawn still waiting for its tool_result is resolved by that result,
|
|
777
|
+
// which can land after the snapshot boundary. Background agents need no
|
|
778
|
+
// entry: their notifications are matched against every subagent.
|
|
779
|
+
this.subagents.forEach((agent, idx) => {
|
|
780
|
+
if (agent.status === 'running' && !agent.background && agent.id) {
|
|
781
|
+
this.pendingSubagents.set(agent.id, idx);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
629
784
|
this.pendingUserRequest = null;
|
|
630
785
|
this.planExtractor.reset();
|
|
631
786
|
return true;
|
|
@@ -1104,7 +1259,7 @@ export class EventAggregator {
|
|
|
1104
1259
|
else if (name === 'UpdatePlan') {
|
|
1105
1260
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1106
1261
|
}
|
|
1107
|
-
else if ((name
|
|
1262
|
+
else if (isSubagentSpawnTool(name, input) && toolUseId) {
|
|
1108
1263
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1109
1264
|
}
|
|
1110
1265
|
}
|
|
@@ -1114,7 +1269,7 @@ export class EventAggregator {
|
|
|
1114
1269
|
continue;
|
|
1115
1270
|
const isError = block.is_error ?? false;
|
|
1116
1271
|
this.resolveTaskCreate(toolUseId, block.content ?? block.output, event.timestamp, isError);
|
|
1117
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1272
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, block.content ?? block.output);
|
|
1118
1273
|
}
|
|
1119
1274
|
}
|
|
1120
1275
|
}
|
|
@@ -1148,7 +1303,7 @@ export class EventAggregator {
|
|
|
1148
1303
|
else if (event.toolName === 'UpdatePlan') {
|
|
1149
1304
|
this.handleUpdatePlan(input, new Date(event.timestamp));
|
|
1150
1305
|
}
|
|
1151
|
-
else if ((event.toolName
|
|
1306
|
+
else if (isSubagentSpawnTool(event.toolName, input) && toolUseId) {
|
|
1152
1307
|
this.handleAgentSpawn(toolUseId, input, new Date(event.timestamp));
|
|
1153
1308
|
}
|
|
1154
1309
|
}
|
|
@@ -1160,7 +1315,7 @@ export class EventAggregator {
|
|
|
1160
1315
|
return;
|
|
1161
1316
|
const isError = raw.is_error ?? false;
|
|
1162
1317
|
this.resolveTaskCreate(toolUseId, raw.content, event.timestamp, isError);
|
|
1163
|
-
this.resolveAgentResult(toolUseId, isError, event.timestamp);
|
|
1318
|
+
this.resolveAgentResult(toolUseId, isError, event.timestamp, raw.content);
|
|
1164
1319
|
}
|
|
1165
1320
|
}
|
|
1166
1321
|
applyTaskUpdate(input) {
|
|
@@ -1447,8 +1602,9 @@ export class EventAggregator {
|
|
|
1447
1602
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1448
1603
|
handleAgentSpawn(toolUseId, input, now) {
|
|
1449
1604
|
const agentTaskId = `agent-${toolUseId}`;
|
|
1450
|
-
const
|
|
1451
|
-
const
|
|
1605
|
+
const spawn = spawnFields(input);
|
|
1606
|
+
const description = spawn.description ?? 'Subagent';
|
|
1607
|
+
const subagentType = spawn.subagentType;
|
|
1452
1608
|
const task = {
|
|
1453
1609
|
taskId: agentTaskId,
|
|
1454
1610
|
subject: description,
|
|
@@ -1465,11 +1621,15 @@ export class EventAggregator {
|
|
|
1465
1621
|
};
|
|
1466
1622
|
this.tasks.set(agentTaskId, task);
|
|
1467
1623
|
}
|
|
1468
|
-
resolveAgentResult(toolUseId, isError, timestamp) {
|
|
1624
|
+
resolveAgentResult(toolUseId, isError, timestamp, content) {
|
|
1469
1625
|
const agentTaskId = `agent-${toolUseId}`;
|
|
1470
1626
|
const agentTask = this.tasks.get(agentTaskId);
|
|
1471
1627
|
if (!agentTask)
|
|
1472
1628
|
return;
|
|
1629
|
+
// A background launch returns at once; its task notification finishes it.
|
|
1630
|
+
if (spawnResultKind(this.pendingCodexSpawns.has(toolUseId), content, isError) === 'launch') {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1473
1633
|
agentTask.status = isError ? 'deleted' : 'completed';
|
|
1474
1634
|
agentTask.updatedAt = new Date(timestamp);
|
|
1475
1635
|
}
|
|
@@ -1489,31 +1649,25 @@ export class EventAggregator {
|
|
|
1489
1649
|
// Private: Subagent Tracking from SessionEvent
|
|
1490
1650
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1491
1651
|
extractSubagentFromEvent(event) {
|
|
1652
|
+
const notification = taskNotificationFromEvent(event);
|
|
1653
|
+
if (notification)
|
|
1654
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1492
1655
|
const content = event.message?.content;
|
|
1493
1656
|
if (!Array.isArray(content))
|
|
1494
1657
|
return;
|
|
1495
1658
|
for (const block of content) {
|
|
1496
|
-
if (block.type === 'tool_use' && block.name
|
|
1659
|
+
if (block.type === 'tool_use' && isSubagentSpawnTool(block.name, block.input)) {
|
|
1497
1660
|
const input = block.input;
|
|
1498
1661
|
const toolUseId = block.id;
|
|
1499
1662
|
if (!input || !toolUseId)
|
|
1500
1663
|
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);
|
|
1664
|
+
this.trackSubagentSpawn(toolUseId, input, event.timestamp, isCodexSubagentSpawnTool(block.name, input));
|
|
1511
1665
|
}
|
|
1512
1666
|
else if (block.type === 'tool_result') {
|
|
1513
1667
|
const toolUseId = block.tool_use_id;
|
|
1514
1668
|
if (!toolUseId)
|
|
1515
1669
|
continue;
|
|
1516
|
-
this.
|
|
1670
|
+
this.resolveSubagentResult(toolUseId, block.content ?? block.output, block.is_error === true, event.timestamp);
|
|
1517
1671
|
}
|
|
1518
1672
|
}
|
|
1519
1673
|
}
|
|
@@ -1522,23 +1676,10 @@ export class EventAggregator {
|
|
|
1522
1676
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1523
1677
|
extractSubagentFromFollowEvent(event) {
|
|
1524
1678
|
const raw = event.raw;
|
|
1525
|
-
if (event.type === 'tool_use' && event.toolName
|
|
1679
|
+
if (event.type === 'tool_use' && isSubagentSpawnTool(event.toolName, raw?.input)) {
|
|
1526
1680
|
if (!raw?.input)
|
|
1527
1681
|
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
|
-
}
|
|
1682
|
+
this.trackSubagentSpawn(raw.id || '', raw.input, event.timestamp, isCodexSubagentSpawnTool(event.toolName, raw.input));
|
|
1542
1683
|
}
|
|
1543
1684
|
else if (event.type === 'tool_result') {
|
|
1544
1685
|
if (!raw)
|
|
@@ -1546,22 +1687,374 @@ export class EventAggregator {
|
|
|
1546
1687
|
const toolUseId = raw.tool_use_id;
|
|
1547
1688
|
if (!toolUseId)
|
|
1548
1689
|
return;
|
|
1549
|
-
this.
|
|
1690
|
+
this.resolveSubagentResult(toolUseId, raw.content, raw.is_error === true, event.timestamp);
|
|
1691
|
+
}
|
|
1692
|
+
else if (event.type === 'user' && raw?.type === 'user') {
|
|
1693
|
+
// Claude Code delivers most task notifications as their own user line.
|
|
1694
|
+
const notification = taskNotificationFromEvent(raw);
|
|
1695
|
+
if (notification)
|
|
1696
|
+
this.applyTaskNotification(notification, event.timestamp);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
trackSubagentSpawn(toolUseId, input, timestamp, codex = false) {
|
|
1700
|
+
const spawn = spawnFields(input);
|
|
1701
|
+
const info = {
|
|
1702
|
+
id: toolUseId,
|
|
1703
|
+
description: spawn.description ?? 'Unknown task',
|
|
1704
|
+
subagentType: spawn.subagentType ?? (codex ? 'default' : 'general'),
|
|
1705
|
+
spawnTime: timestamp,
|
|
1706
|
+
status: 'running',
|
|
1707
|
+
};
|
|
1708
|
+
const idx = this.subagents.length;
|
|
1709
|
+
this.subagents.push(info);
|
|
1710
|
+
if (toolUseId) {
|
|
1711
|
+
this.pendingSubagents.set(toolUseId, idx);
|
|
1712
|
+
if (codex)
|
|
1713
|
+
this.pendingCodexSpawns.add(toolUseId);
|
|
1550
1714
|
}
|
|
1551
1715
|
}
|
|
1552
|
-
|
|
1716
|
+
/**
|
|
1717
|
+
* The spawning tool call's result. A foreground agent ends here; a
|
|
1718
|
+
* background launch only reports the agent id (a Codex spawn, its agent
|
|
1719
|
+
* path), and the agent keeps running until a notification or another
|
|
1720
|
+
* record says it ended.
|
|
1721
|
+
*/
|
|
1722
|
+
resolveSubagentResult(toolUseId, content, isError, timestamp) {
|
|
1553
1723
|
const idx = this.pendingSubagents.get(toolUseId);
|
|
1554
1724
|
if (idx === undefined)
|
|
1555
1725
|
return;
|
|
1556
|
-
this.
|
|
1726
|
+
const codex = this.pendingCodexSpawns.delete(toolUseId);
|
|
1727
|
+
if (spawnResultKind(codex, content, isError) === 'launch') {
|
|
1728
|
+
const launch = parseCodexSpawnLaunch(content) ?? parseAsyncAgentLaunch(content) ?? {};
|
|
1729
|
+
this.markBackgroundLaunch(idx, launch);
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
this.completeSubagent(toolUseId, timestamp, isError ? 'failed' : 'completed');
|
|
1733
|
+
}
|
|
1734
|
+
markBackgroundLaunch(idx, launch) {
|
|
1557
1735
|
const agent = this.subagents[idx];
|
|
1736
|
+
this.pendingSubagents.delete(agent.id);
|
|
1737
|
+
agent.background = true;
|
|
1738
|
+
if (launch.agentId)
|
|
1739
|
+
agent.agentId = launch.agentId;
|
|
1740
|
+
if (launch.agentPath && !agent.agentPath)
|
|
1741
|
+
agent.agentPath = launch.agentPath;
|
|
1742
|
+
}
|
|
1743
|
+
completeSubagent(toolUseId, timestamp, outcome = 'completed') {
|
|
1744
|
+
const idx = this.pendingSubagents.get(toolUseId);
|
|
1745
|
+
if (idx === undefined)
|
|
1746
|
+
return;
|
|
1747
|
+
this.finishSubagent(this.subagents[idx], timestamp, outcome);
|
|
1748
|
+
}
|
|
1749
|
+
finishSubagent(agent, timestamp, outcome, settledBy) {
|
|
1750
|
+
this.pendingSubagents.delete(agent.id);
|
|
1751
|
+
this.pendingCodexSpawns.delete(agent.id);
|
|
1558
1752
|
agent.status = 'completed';
|
|
1559
1753
|
agent.completionTime = timestamp;
|
|
1754
|
+
agent.outcome = outcome;
|
|
1755
|
+
if (settledBy)
|
|
1756
|
+
agent.settledBy = settledBy;
|
|
1757
|
+
else
|
|
1758
|
+
delete agent.settledBy;
|
|
1560
1759
|
const start = new Date(agent.spawnTime).getTime();
|
|
1561
1760
|
const end = new Date(timestamp).getTime();
|
|
1562
1761
|
if (!isNaN(start) && !isNaN(end) && end >= start) {
|
|
1563
1762
|
agent.durationMs = end - start;
|
|
1564
1763
|
}
|
|
1764
|
+
else {
|
|
1765
|
+
delete agent.durationMs;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/** Puts a settled agent back to running (it resumed). */
|
|
1769
|
+
reopenSubagent(agent, timestamp) {
|
|
1770
|
+
this.pendingSubagents.delete(agent.id);
|
|
1771
|
+
agent.status = 'running';
|
|
1772
|
+
delete agent.completionTime;
|
|
1773
|
+
delete agent.durationMs;
|
|
1774
|
+
delete agent.outcome;
|
|
1775
|
+
delete agent.settledBy;
|
|
1776
|
+
this.settleAgentTask(agent.id, null, timestamp);
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Settles a lifecycle that is still running (and its task card). Agents
|
|
1780
|
+
* already settled are left alone: a stop notice or status can name agents
|
|
1781
|
+
* that finished earlier.
|
|
1782
|
+
*/
|
|
1783
|
+
settleRunningSubagent(agent, timestamp, outcome, settledBy) {
|
|
1784
|
+
if (agent.status !== 'running')
|
|
1785
|
+
return false;
|
|
1786
|
+
this.finishSubagent(agent, timestamp, outcome, settledBy);
|
|
1787
|
+
this.settleAgentTask(agent.id, outcome, timestamp);
|
|
1788
|
+
return true;
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Moves an agent's task card with its lifecycle: `null` back to in
|
|
1792
|
+
* progress, a failure to deleted, any other outcome to completed.
|
|
1793
|
+
*/
|
|
1794
|
+
settleAgentTask(toolUseId, outcome, timestamp) {
|
|
1795
|
+
const agentTask = toolUseId ? this.tasks.get(`agent-${toolUseId}`) : undefined;
|
|
1796
|
+
if (!agentTask?.isSubagent)
|
|
1797
|
+
return;
|
|
1798
|
+
agentTask.status =
|
|
1799
|
+
outcome === null ? 'in_progress' : outcome === 'failed' ? 'deleted' : 'completed';
|
|
1800
|
+
agentTask.updatedAt = new Date(timestamp);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Applies a background task's notification to the subagent it reports on,
|
|
1804
|
+
* matched by the spawning tool_use id (or by agent id when the notification
|
|
1805
|
+
* has none). The latest notification wins: an interim one ("waiting on its
|
|
1806
|
+
* own background work") keeps or puts the agent back to running, a final one
|
|
1807
|
+
* completes it. Notifications for background shell tasks match nothing.
|
|
1808
|
+
*/
|
|
1809
|
+
applyTaskNotification(notification, timestamp) {
|
|
1810
|
+
const agent = this.findNotifiedSubagent(notification);
|
|
1811
|
+
const toolUseId = agent?.id || notification.toolUseId;
|
|
1812
|
+
const outcome = taskNotificationOutcome(notification.status);
|
|
1813
|
+
if (agent) {
|
|
1814
|
+
agent.background = true;
|
|
1815
|
+
if (!agent.agentId)
|
|
1816
|
+
agent.agentId = notification.taskId;
|
|
1817
|
+
if (notification.interim) {
|
|
1818
|
+
this.pendingSubagents.delete(agent.id);
|
|
1819
|
+
agent.status = 'running';
|
|
1820
|
+
delete agent.completionTime;
|
|
1821
|
+
delete agent.durationMs;
|
|
1822
|
+
delete agent.outcome;
|
|
1823
|
+
delete agent.settledBy;
|
|
1824
|
+
}
|
|
1825
|
+
else {
|
|
1826
|
+
this.finishSubagent(agent, timestamp, outcome, 'notification');
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
this.settleAgentTask(toolUseId, notification.interim ? null : outcome, timestamp);
|
|
1830
|
+
}
|
|
1831
|
+
findNotifiedSubagent(notification) {
|
|
1832
|
+
const { toolUseId, taskId } = notification;
|
|
1833
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1834
|
+
if (toolUseId && this.subagents[i].id === toolUseId)
|
|
1835
|
+
return this.subagents[i];
|
|
1836
|
+
}
|
|
1837
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1838
|
+
if (this.subagents[i].agentId === taskId)
|
|
1839
|
+
return this.subagents[i];
|
|
1840
|
+
}
|
|
1841
|
+
return undefined;
|
|
1842
|
+
}
|
|
1843
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1844
|
+
// Subagent settling: evidence, stop markers, task statuses, Codex records
|
|
1845
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
1846
|
+
/**
|
|
1847
|
+
* Applies host evidence about a subagent (Claude Code's meta flags):
|
|
1848
|
+
* `stoppedByUser` settles a running agent as `stopped`, `finished` as
|
|
1849
|
+
* `completed` (`settledBy: 'meta'`). Agents already settled some other way
|
|
1850
|
+
* keep their outcome; `finished: false` reopens an agent that only meta
|
|
1851
|
+
* settled. Matches by `toolUseId`, then `agentId` (the newest such agent),
|
|
1852
|
+
* and records the agent id when matched by tool_use id.
|
|
1853
|
+
*
|
|
1854
|
+
* @param timestamp ISO time to record as the completion (the agent's last
|
|
1855
|
+
* activity, when the host knows it)
|
|
1856
|
+
* @returns Whether a lifecycle changed
|
|
1857
|
+
*/
|
|
1858
|
+
applySubagentEvidence(evidence, timestamp) {
|
|
1859
|
+
const agent = this.findSubagent(evidence.toolUseId, evidence.agentId);
|
|
1860
|
+
if (!agent)
|
|
1861
|
+
return false;
|
|
1862
|
+
let changed = false;
|
|
1863
|
+
if (evidence.agentId && !agent.agentId) {
|
|
1864
|
+
agent.agentId = evidence.agentId;
|
|
1865
|
+
changed = true;
|
|
1866
|
+
}
|
|
1867
|
+
if (agent.status === 'running') {
|
|
1868
|
+
const outcome = evidence.stoppedByUser
|
|
1869
|
+
? 'stopped'
|
|
1870
|
+
: evidence.finished
|
|
1871
|
+
? 'completed'
|
|
1872
|
+
: null;
|
|
1873
|
+
if (outcome)
|
|
1874
|
+
changed = this.settleRunningSubagent(agent, timestamp, outcome, 'meta') || changed;
|
|
1875
|
+
}
|
|
1876
|
+
else if (agent.settledBy === 'meta' &&
|
|
1877
|
+
evidence.finished === false &&
|
|
1878
|
+
evidence.stoppedByUser !== true) {
|
|
1879
|
+
this.reopenSubagent(agent, timestamp);
|
|
1880
|
+
changed = true;
|
|
1881
|
+
}
|
|
1882
|
+
return changed;
|
|
1883
|
+
}
|
|
1884
|
+
/** The newest lifecycle with this spawning tool_use id, else with this agent id. */
|
|
1885
|
+
findSubagent(toolUseId, agentId) {
|
|
1886
|
+
if (toolUseId) {
|
|
1887
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1888
|
+
if (this.subagents[i].id === toolUseId)
|
|
1889
|
+
return this.subagents[i];
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
if (agentId) {
|
|
1893
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1894
|
+
if (this.subagents[i].agentId === agentId)
|
|
1895
|
+
return this.subagents[i];
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return undefined;
|
|
1899
|
+
}
|
|
1900
|
+
/** `agents_killed`: the user killed every running background agent. */
|
|
1901
|
+
applyAgentsKilled(timestamp) {
|
|
1902
|
+
let settled = 0;
|
|
1903
|
+
for (const agent of this.subagents) {
|
|
1904
|
+
if (!agent.background)
|
|
1905
|
+
continue;
|
|
1906
|
+
if (this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker'))
|
|
1907
|
+
settled++;
|
|
1908
|
+
}
|
|
1909
|
+
if (settled > 0)
|
|
1910
|
+
this.addSubagentTimeline(timestamp, describeAgentsStopped(null));
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* "N background agents were stopped by the user: "A", "B"": settles the
|
|
1914
|
+
* running agents with those descriptions, the newest first for repeats.
|
|
1915
|
+
*/
|
|
1916
|
+
applyAgentsStopped(notice, timestamp) {
|
|
1917
|
+
const taken = new Set();
|
|
1918
|
+
for (const description of notice.descriptions) {
|
|
1919
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1920
|
+
const agent = this.subagents[i];
|
|
1921
|
+
if (taken.has(agent) || agent.status !== 'running')
|
|
1922
|
+
continue;
|
|
1923
|
+
if (agent.description.trim() !== description)
|
|
1924
|
+
continue;
|
|
1925
|
+
taken.add(agent);
|
|
1926
|
+
this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker');
|
|
1927
|
+
break;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
this.addSubagentTimeline(timestamp, describeAgentsStopped(notice));
|
|
1931
|
+
}
|
|
1932
|
+
/** A background agent's final `task_status` attachment, matched by agent id. */
|
|
1933
|
+
applyTaskStatus(status, timestamp) {
|
|
1934
|
+
if (!isFinalTaskStatus(status.status))
|
|
1935
|
+
return;
|
|
1936
|
+
const agent = this.findSubagent(undefined, status.taskId);
|
|
1937
|
+
if (!agent)
|
|
1938
|
+
return;
|
|
1939
|
+
if (this.settleRunningSubagent(agent, timestamp, taskNotificationOutcome(status.status), 'task-status')) {
|
|
1940
|
+
this.addSubagentTimeline(timestamp, describeTaskStatus(status), agent.id);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* A Codex subagent lifecycle record: `started` links the spawn to its child
|
|
1945
|
+
* thread; `completed` (a child turn finished) and `final-answer` (the
|
|
1946
|
+
* child's report, matched by its agent path) complete it; `interacted`
|
|
1947
|
+
* after `followup_task` puts it back to running (a `send_message` does
|
|
1948
|
+
* not: it does not start a turn); `interrupted` (`interrupt_agent`) settles
|
|
1949
|
+
* it as `stopped`, since the child keeps its thread and a follow-up can
|
|
1950
|
+
* run it again, like a user stop rather than a kill.
|
|
1951
|
+
*/
|
|
1952
|
+
applySubagentActivity(activity, timestamp) {
|
|
1953
|
+
const byThread = () => this.findSubagent(undefined, activity.agentThreadId) ??
|
|
1954
|
+
this.findSubagentByPath(activity.agentPath);
|
|
1955
|
+
let agent;
|
|
1956
|
+
switch (activity.kind) {
|
|
1957
|
+
case 'started': {
|
|
1958
|
+
agent = this.findSubagent(activity.callId, undefined);
|
|
1959
|
+
if (!agent)
|
|
1960
|
+
break;
|
|
1961
|
+
agent.background = true;
|
|
1962
|
+
if (activity.agentThreadId)
|
|
1963
|
+
agent.agentId = activity.agentThreadId;
|
|
1964
|
+
if (activity.agentPath)
|
|
1965
|
+
agent.agentPath = activity.agentPath;
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
case 'completed':
|
|
1969
|
+
agent = byThread();
|
|
1970
|
+
if (agent)
|
|
1971
|
+
this.settleRunningSubagent(agent, timestamp, 'completed', 'notification');
|
|
1972
|
+
break;
|
|
1973
|
+
case 'final-answer':
|
|
1974
|
+
agent = this.findSubagentByPath(activity.agentPath, true);
|
|
1975
|
+
if (agent)
|
|
1976
|
+
this.settleRunningSubagent(agent, timestamp, 'completed', 'notification');
|
|
1977
|
+
break;
|
|
1978
|
+
case 'interacted':
|
|
1979
|
+
agent = byThread();
|
|
1980
|
+
if (agent && activity.tool === 'followup_task' && agent.status === 'completed') {
|
|
1981
|
+
this.reopenSubagent(agent, timestamp);
|
|
1982
|
+
}
|
|
1983
|
+
break;
|
|
1984
|
+
case 'interrupted':
|
|
1985
|
+
agent = byThread();
|
|
1986
|
+
if (agent)
|
|
1987
|
+
this.settleRunningSubagent(agent, timestamp, 'stopped', 'stop-marker');
|
|
1988
|
+
break;
|
|
1989
|
+
}
|
|
1990
|
+
this.addSubagentTimeline(timestamp, describeSubagentActivity(activity), agent?.id);
|
|
1991
|
+
}
|
|
1992
|
+
/** The newest lifecycle with this Codex agent path (with `runningFirst`, a running one first). */
|
|
1993
|
+
findSubagentByPath(agentPath, runningFirst = false) {
|
|
1994
|
+
if (!agentPath)
|
|
1995
|
+
return undefined;
|
|
1996
|
+
let fallback;
|
|
1997
|
+
for (let i = this.subagents.length - 1; i >= 0; i--) {
|
|
1998
|
+
const agent = this.subagents[i];
|
|
1999
|
+
if (agent.agentPath !== agentPath)
|
|
2000
|
+
continue;
|
|
2001
|
+
if (!runningFirst || agent.status === 'running')
|
|
2002
|
+
return agent;
|
|
2003
|
+
fallback ??= agent;
|
|
2004
|
+
}
|
|
2005
|
+
return fallback;
|
|
2006
|
+
}
|
|
2007
|
+
applySubagentFollowRecord(record, timestamp) {
|
|
2008
|
+
switch (record.kind) {
|
|
2009
|
+
case 'notification':
|
|
2010
|
+
this.applyTaskNotification(record.notification, timestamp);
|
|
2011
|
+
this.addTaskNotificationTimeline(timestamp, record.notification);
|
|
2012
|
+
break;
|
|
2013
|
+
case 'stopped':
|
|
2014
|
+
this.applyAgentsStopped(record.notice, timestamp);
|
|
2015
|
+
break;
|
|
2016
|
+
case 'task-status':
|
|
2017
|
+
this.applyTaskStatus(record.status, timestamp);
|
|
2018
|
+
break;
|
|
2019
|
+
case 'activity':
|
|
2020
|
+
this.applySubagentActivity(record.activity, timestamp);
|
|
2021
|
+
break;
|
|
2022
|
+
case 'agents-killed':
|
|
2023
|
+
this.applyAgentsKilled(timestamp);
|
|
2024
|
+
break;
|
|
2025
|
+
case 'unreadable':
|
|
2026
|
+
break;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
addSubagentTimeline(timestamp, description, toolUseId) {
|
|
2030
|
+
const text = description.length > 200 ? description.substring(0, 197) + '...' : description;
|
|
2031
|
+
this.timeline.push({
|
|
2032
|
+
type: 'tool_result',
|
|
2033
|
+
timestamp,
|
|
2034
|
+
description: text,
|
|
2035
|
+
noiseLevel: 'system',
|
|
2036
|
+
messageClassification: 'system',
|
|
2037
|
+
...(toolUseId ? { metadata: { toolUseId } } : {}),
|
|
2038
|
+
});
|
|
2039
|
+
if (this.timeline.length > this.timelineCap) {
|
|
2040
|
+
this.timeline.shift();
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
addTaskNotificationTimeline(timestamp, notification) {
|
|
2044
|
+
let description = notification.summary ?? `Background task ${notification.status ?? 'finished'}`;
|
|
2045
|
+
if (description.length > 200)
|
|
2046
|
+
description = description.substring(0, 197) + '...';
|
|
2047
|
+
this.timeline.push({
|
|
2048
|
+
type: 'tool_result',
|
|
2049
|
+
timestamp,
|
|
2050
|
+
description,
|
|
2051
|
+
noiseLevel: 'system',
|
|
2052
|
+
messageClassification: 'system',
|
|
2053
|
+
...(notification.toolUseId ? { metadata: { toolUseId: notification.toolUseId } } : {}),
|
|
2054
|
+
});
|
|
2055
|
+
if (this.timeline.length > this.timelineCap) {
|
|
2056
|
+
this.timeline.shift();
|
|
2057
|
+
}
|
|
1565
2058
|
}
|
|
1566
2059
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1567
2060
|
// Private: Plan Extraction from SessionEvent
|
|
@@ -1797,9 +2290,9 @@ export class EventAggregator {
|
|
|
1797
2290
|
// Private: Permission Mode
|
|
1798
2291
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1799
2292
|
trackPermissionMode(event) {
|
|
1800
|
-
|
|
2293
|
+
const mode = asPermissionMode(event.permissionMode);
|
|
2294
|
+
if (!mode)
|
|
1801
2295
|
return;
|
|
1802
|
-
const mode = event.permissionMode;
|
|
1803
2296
|
if (mode !== this.currentPermissionMode) {
|
|
1804
2297
|
this.permissionModeHistory.push({
|
|
1805
2298
|
timestamp: event.timestamp,
|