@inixiative/agent-session 0.6.4 → 0.6.5
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/README.md +24 -0
- package/package.json +2 -2
- package/src/claude-code-session.ts +107 -1
- package/src/codex-session.ts +88 -0
- package/src/harness-session.ts +42 -2
- package/src/turn-state.ts +5 -2
package/README.md
CHANGED
|
@@ -114,6 +114,30 @@ session.onToolRequest(async (call) => {
|
|
|
114
114
|
A turn timeout does not withdraw a pending Claude ask; `interruptNative()` does.
|
|
115
115
|
- A fork starts with a snapshot of the parent's gates.
|
|
116
116
|
|
|
117
|
+
## Subagents
|
|
118
|
+
|
|
119
|
+
A subagent's activity reaches `onEvent`, but never becomes part of a turn:
|
|
120
|
+
|
|
121
|
+
- **Outside every admission.** Every subagent event, lifecycle included,
|
|
122
|
+
carries `unattributedReason: "subagent"`. It is never in a turn's
|
|
123
|
+
`result.events` and never its `content`. This holds whichever turn is in
|
|
124
|
+
flight, because a background subagent outlives the turn that started it.
|
|
125
|
+
- **`parentToolUseId`** names the parent's tool call that spawned it. On
|
|
126
|
+
Claude this is the frame's `parent_tool_use_id`, with the native envelope
|
|
127
|
+
kept in `raw`. On Codex it is the collab `spawnAgent` item.
|
|
128
|
+
- **`kind: "subagent"`** carries its lifecycle in `subagent`:
|
|
129
|
+
- `phase`: started, progress, updated or ended;
|
|
130
|
+
- identity and shape: `taskId`, `toolUseId`, `parentTaskId`, `agentType`,
|
|
131
|
+
`depth`, `backgrounded`;
|
|
132
|
+
- outcome: `status`, `summary`, `prompt`, and running `usage`.
|
|
133
|
+
- **Claude** builds the lifecycle from its `system` task frames for
|
|
134
|
+
`task_type: "local_agent"`. Background shells, monitors and workflows are
|
|
135
|
+
tasks too, and they are not reported as subagents.
|
|
136
|
+
- **Codex** reports `started` on the spawn and `ended` on `closeAgent`, plus
|
|
137
|
+
`updated` (with the turn's status) as each child turn ends. Child threads
|
|
138
|
+
carry their final messages as text; their other items stay diagnostics.
|
|
139
|
+
Approval requests from child threads are not routed to gates yet.
|
|
140
|
+
|
|
117
141
|
## Primed decision sessions
|
|
118
142
|
|
|
119
143
|
One live, warm session per middleware role, primed once with its role
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/agent-session",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Drive coding-agent CLIs as persistent, streaming, event-captured sessions over interchangeable transports, with primed decision sessions, subscription pools, routing, limit polling and continuity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "2.5.2",
|
|
43
|
-
"@inixiative/config": "^0.5.
|
|
43
|
+
"@inixiative/config": "^0.5.83",
|
|
44
44
|
"@types/bun": "1.4.2",
|
|
45
45
|
"lefthook": "2.1.9",
|
|
46
46
|
"typescript": "6.0.3"
|
|
@@ -57,6 +57,7 @@ import type {
|
|
|
57
57
|
SessionResult,
|
|
58
58
|
SessionSendOptions,
|
|
59
59
|
SessionTokens,
|
|
60
|
+
SubagentLifecycle,
|
|
60
61
|
ToolDecision,
|
|
61
62
|
ToolGate,
|
|
62
63
|
ToolRequest,
|
|
@@ -314,6 +315,8 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
314
315
|
* are distinct compactions.
|
|
315
316
|
*/
|
|
316
317
|
private _seenBoundaries = new Set<string>();
|
|
318
|
+
/** Task ids whose task_started was a subagent (local_agent); later task frames carry no type. */
|
|
319
|
+
private _agentTasks = new Set<string>();
|
|
317
320
|
|
|
318
321
|
// -- Turn queue --
|
|
319
322
|
private _queue: QueuedTurn[] = [];
|
|
@@ -1104,6 +1107,34 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
1104
1107
|
return;
|
|
1105
1108
|
}
|
|
1106
1109
|
|
|
1110
|
+
// A subagent's frames and lifecycle are never part of an admission, whichever turn is in flight
|
|
1111
|
+
// (a background subagent outlives the turn that started it): they reach handlers tagged
|
|
1112
|
+
// `subagent`, with the spawning call in `parentToolUseId`.
|
|
1113
|
+
const parentToolUseId =
|
|
1114
|
+
typeof raw.parent_tool_use_id === 'string' && raw.parent_tool_use_id
|
|
1115
|
+
? raw.parent_tool_use_id
|
|
1116
|
+
: undefined;
|
|
1117
|
+
const lifecycle = this._subagentLifecycle(raw);
|
|
1118
|
+
if (parentToolUseId || lifecycle) {
|
|
1119
|
+
const message = raw.message as Record<string, unknown> | undefined;
|
|
1120
|
+
const events: SessionEvent[] = lifecycle
|
|
1121
|
+
? [{ kind: 'subagent', timestamp: Date.now(), subagent: lifecycle, raw }]
|
|
1122
|
+
: this._classify(raw, parentToolUseId);
|
|
1123
|
+
if (!events.length) {
|
|
1124
|
+
this._unattributed(raw, 'subagent');
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
for (const event of events)
|
|
1128
|
+
this._emit({
|
|
1129
|
+
...event,
|
|
1130
|
+
correlation: 'unknown',
|
|
1131
|
+
unattributedReason: 'subagent',
|
|
1132
|
+
nativeSessionId: typeof raw.session_id === 'string' ? raw.session_id : undefined,
|
|
1133
|
+
messageId: typeof message?.id === 'string' ? message.id : undefined,
|
|
1134
|
+
});
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1107
1138
|
const turn = this._inflight;
|
|
1108
1139
|
if (!turn) {
|
|
1109
1140
|
if (terminalKey) this._seenTerminals.add(terminalKey);
|
|
@@ -1373,7 +1404,34 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
1373
1404
|
// Private — event classification
|
|
1374
1405
|
// ---------------------------------------------------------------------------
|
|
1375
1406
|
|
|
1376
|
-
|
|
1407
|
+
/**
|
|
1408
|
+
* A Claude Code `system` task frame for a subagent (`task_type: local_agent`), as lifecycle. Other
|
|
1409
|
+
* task types (a background shell, a monitor, a workflow) are not subagents and return nothing.
|
|
1410
|
+
* Later phases carry no task type, so the session remembers which task ids are subagents.
|
|
1411
|
+
*/
|
|
1412
|
+
private _subagentLifecycle(msg: Record<string, unknown>): SubagentLifecycle | undefined {
|
|
1413
|
+
if (msg.type !== 'system' || typeof msg.subtype !== 'string') return undefined;
|
|
1414
|
+
const phase = CLAUDE_TASK_PHASES[msg.subtype];
|
|
1415
|
+
const taskId = typeof msg.task_id === 'string' && msg.task_id ? msg.task_id : undefined;
|
|
1416
|
+
if (!phase || !taskId) return undefined;
|
|
1417
|
+
if (phase === 'started') {
|
|
1418
|
+
const agent =
|
|
1419
|
+
msg.task_type === 'local_agent' ||
|
|
1420
|
+
(msg.task_type === undefined && typeof msg.subagent_type === 'string');
|
|
1421
|
+
if (!agent) return undefined;
|
|
1422
|
+
this._agentTasks.add(taskId);
|
|
1423
|
+
} else if (!this._agentTasks.has(taskId)) return undefined;
|
|
1424
|
+
if (phase === 'ended') this._agentTasks.delete(taskId);
|
|
1425
|
+
return claudeSubagentLifecycle(msg, phase, taskId);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private _classify(msg: Record<string, unknown>, parentToolUseId?: string): SessionEvent[] {
|
|
1429
|
+
return this._classifyFrame(msg).map((event) =>
|
|
1430
|
+
parentToolUseId ? { ...event, parentToolUseId } : event,
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
private _classifyFrame(msg: Record<string, unknown>): SessionEvent[] {
|
|
1377
1435
|
const events: SessionEvent[] = [];
|
|
1378
1436
|
const ts = Date.now();
|
|
1379
1437
|
|
|
@@ -1558,3 +1616,51 @@ function processAlive(pid: number): boolean {
|
|
|
1558
1616
|
return (error as NodeJS.ErrnoException).code === 'EPERM';
|
|
1559
1617
|
}
|
|
1560
1618
|
}
|
|
1619
|
+
|
|
1620
|
+
const CLAUDE_TASK_PHASES: Readonly<Record<string, SubagentLifecycle['phase']>> = {
|
|
1621
|
+
task_started: 'started',
|
|
1622
|
+
task_progress: 'progress',
|
|
1623
|
+
task_updated: 'updated',
|
|
1624
|
+
task_notification: 'ended',
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1627
|
+
function claudeSubagentLifecycle(
|
|
1628
|
+
msg: Record<string, unknown>,
|
|
1629
|
+
phase: SubagentLifecycle['phase'],
|
|
1630
|
+
taskId: string,
|
|
1631
|
+
): SubagentLifecycle {
|
|
1632
|
+
const text = (value: unknown) => (typeof value === 'string' && value ? value : undefined);
|
|
1633
|
+
const record = (value: unknown) =>
|
|
1634
|
+
value && typeof value === 'object' && !Array.isArray(value)
|
|
1635
|
+
? (value as Record<string, unknown>)
|
|
1636
|
+
: undefined;
|
|
1637
|
+
const patch = record(msg.patch);
|
|
1638
|
+
const usage = record(msg.usage);
|
|
1639
|
+
const status = text(msg.status) ?? text(patch?.status);
|
|
1640
|
+
const count = (value: unknown) =>
|
|
1641
|
+
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
1642
|
+
return {
|
|
1643
|
+
phase,
|
|
1644
|
+
taskId,
|
|
1645
|
+
...(text(msg.tool_use_id) ? { toolUseId: text(msg.tool_use_id) } : {}),
|
|
1646
|
+
...(text(msg.parent_task_id) ? { parentTaskId: text(msg.parent_task_id) } : {}),
|
|
1647
|
+
...(text(msg.subagent_type) ? { agentType: text(msg.subagent_type) } : {}),
|
|
1648
|
+
...(text(msg.description) ? { description: text(msg.description) } : {}),
|
|
1649
|
+
...(typeof msg.spawn_depth === 'number' ? { depth: msg.spawn_depth } : {}),
|
|
1650
|
+
...(typeof msg.is_backgrounded === 'boolean' ? { backgrounded: msg.is_backgrounded } : {}),
|
|
1651
|
+
...(status ? { status } : {}),
|
|
1652
|
+
...(text(msg.summary) ? { summary: text(msg.summary) } : {}),
|
|
1653
|
+
...(text(msg.prompt) ? { prompt: text(msg.prompt) } : {}),
|
|
1654
|
+
...(usage && count(usage.total_tokens) !== undefined
|
|
1655
|
+
? {
|
|
1656
|
+
usage: {
|
|
1657
|
+
totalTokens: count(usage.total_tokens)!,
|
|
1658
|
+
...(count(usage.tool_uses) !== undefined ? { toolUses: count(usage.tool_uses) } : {}),
|
|
1659
|
+
...(count(usage.duration_ms) !== undefined
|
|
1660
|
+
? { durationMs: count(usage.duration_ms) }
|
|
1661
|
+
: {}),
|
|
1662
|
+
},
|
|
1663
|
+
}
|
|
1664
|
+
: {}),
|
|
1665
|
+
};
|
|
1666
|
+
}
|
package/src/codex-session.ts
CHANGED
|
@@ -944,6 +944,8 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
944
944
|
private _approvals = new Map<string, AbortController>();
|
|
945
945
|
/** The owned turn's in-progress fileChange items' changes, by item id, for their approvals. */
|
|
946
946
|
private _fileChanges = new Map<string, readonly CodexFileChange[]>();
|
|
947
|
+
/** Child threads this session's turns spawned (collab `spawnAgent`), to the spawning item's id. */
|
|
948
|
+
private _children = new Map<string, string>();
|
|
947
949
|
private _items = new Map<
|
|
948
950
|
string,
|
|
949
951
|
{
|
|
@@ -1266,6 +1268,8 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1266
1268
|
thread.turns.some((value) => !this._turn(value)))
|
|
1267
1269
|
)
|
|
1268
1270
|
this._refuse('configuration-missing-or-incompatible');
|
|
1271
|
+
// Sub-agents belong to the thread that spawned them.
|
|
1272
|
+
if (this._externalSessionId !== id) this._children.clear();
|
|
1269
1273
|
this._externalSessionId = id;
|
|
1270
1274
|
this._nativeSessionId =
|
|
1271
1275
|
typeof thread?.sessionId === 'string' && thread.sessionId ? thread.sessionId : undefined;
|
|
@@ -1430,6 +1434,9 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1430
1434
|
},
|
|
1431
1435
|
];
|
|
1432
1436
|
const a = this._inflight?.evidence;
|
|
1437
|
+
const spawnedBy = threadId ? this._children.get(threadId) : undefined;
|
|
1438
|
+
if (threadId && spawnedBy)
|
|
1439
|
+
return this._childEvents(method, threadId, spawnedBy, turn, item, unowned);
|
|
1433
1440
|
if (!threadId || threadId !== this._externalSessionId) return unowned('foreign-session');
|
|
1434
1441
|
if (!a || this._appAdmission !== a.admissionId) return unowned('no-admission');
|
|
1435
1442
|
if (turnId && this._seenTerminals.has(turnId))
|
|
@@ -1519,6 +1526,50 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1519
1526
|
else this._fileChanges.delete(itemId);
|
|
1520
1527
|
return unowned('unrecognized-event');
|
|
1521
1528
|
}
|
|
1529
|
+
if (item.type === 'collabAgentToolCall') {
|
|
1530
|
+
// A sub-agent this turn spawned (its thread is named once known, on start or completion),
|
|
1531
|
+
// or closed. Other collab calls (send input, wait) are diagnostics.
|
|
1532
|
+
const receivers = Array.isArray(item.receiverThreadIds)
|
|
1533
|
+
? item.receiverThreadIds.filter((id): id is string => typeof id === 'string' && !!id)
|
|
1534
|
+
: [];
|
|
1535
|
+
const prompt = typeof item.prompt === 'string' && item.prompt ? item.prompt : undefined;
|
|
1536
|
+
if (item.tool === 'spawnAgent') {
|
|
1537
|
+
const fresh = receivers.filter((child) => !this._children.has(child));
|
|
1538
|
+
if (!fresh.length) return status();
|
|
1539
|
+
return fresh.map((child): SessionEvent => {
|
|
1540
|
+
this._children.set(child, itemId);
|
|
1541
|
+
return {
|
|
1542
|
+
...identity,
|
|
1543
|
+
kind: 'subagent',
|
|
1544
|
+
timestamp: Date.now(),
|
|
1545
|
+
subagent: {
|
|
1546
|
+
phase: 'started',
|
|
1547
|
+
taskId: child,
|
|
1548
|
+
toolUseId: itemId,
|
|
1549
|
+
agentType: 'spawnAgent',
|
|
1550
|
+
depth: 1,
|
|
1551
|
+
backgrounded: true,
|
|
1552
|
+
...(prompt ? { prompt } : {}),
|
|
1553
|
+
},
|
|
1554
|
+
};
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
if (item.tool === 'closeAgent' && method === 'item/completed') {
|
|
1558
|
+
const closed = receivers.filter((child) => this._children.has(child));
|
|
1559
|
+
if (!closed.length) return status();
|
|
1560
|
+
return closed.map((child): SessionEvent => {
|
|
1561
|
+
const toolUseId = this._children.get(child)!;
|
|
1562
|
+
this._children.delete(child);
|
|
1563
|
+
return {
|
|
1564
|
+
...identity,
|
|
1565
|
+
kind: 'subagent',
|
|
1566
|
+
timestamp: Date.now(),
|
|
1567
|
+
subagent: { phase: 'ended', taskId: child, toolUseId, status: 'closed' },
|
|
1568
|
+
};
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
return status();
|
|
1572
|
+
}
|
|
1522
1573
|
if (item.type === 'agentMessage') {
|
|
1523
1574
|
if (method !== 'item/completed') return status();
|
|
1524
1575
|
if (this._items.get(itemId)?.completed) return unowned('after-terminal');
|
|
@@ -1638,6 +1689,43 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1638
1689
|
}
|
|
1639
1690
|
return method === 'turn/started' ? status() : unowned('unrecognized-event');
|
|
1640
1691
|
}
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* A spawned sub-agent's thread, outside any admission of this session: its final messages as
|
|
1695
|
+
* text and its turns' ends as lifecycle, tagged with the spawning item. Anything else stays a
|
|
1696
|
+
* diagnostic, tagged the same way.
|
|
1697
|
+
*/
|
|
1698
|
+
private _childEvents(
|
|
1699
|
+
method: string,
|
|
1700
|
+
threadId: string,
|
|
1701
|
+
spawnedBy: string,
|
|
1702
|
+
turn: { id: string; status: string; error?: unknown } | undefined,
|
|
1703
|
+
item: Record<string, unknown> | undefined,
|
|
1704
|
+
unowned: (reason: NonNullable<SessionEvent['unattributedReason']>) => SessionEvent[],
|
|
1705
|
+
): SessionEvent[] {
|
|
1706
|
+
const tag = { parentToolUseId: spawnedBy, unattributedReason: 'subagent' as const };
|
|
1707
|
+
const base = { threadId, correlation: 'unknown' as const, timestamp: Date.now(), ...tag };
|
|
1708
|
+
if (
|
|
1709
|
+
method === 'item/completed' &&
|
|
1710
|
+
item?.type === 'agentMessage' &&
|
|
1711
|
+
typeof item.text === 'string'
|
|
1712
|
+
)
|
|
1713
|
+
return [{ ...base, kind: 'text', text: item.text }];
|
|
1714
|
+
if (method === 'turn/completed' && turn && turn.status !== 'inProgress')
|
|
1715
|
+
return [
|
|
1716
|
+
{
|
|
1717
|
+
...base,
|
|
1718
|
+
kind: 'subagent',
|
|
1719
|
+
subagent: {
|
|
1720
|
+
phase: 'updated',
|
|
1721
|
+
taskId: threadId,
|
|
1722
|
+
toolUseId: spawnedBy,
|
|
1723
|
+
status: turn.status,
|
|
1724
|
+
},
|
|
1725
|
+
},
|
|
1726
|
+
];
|
|
1727
|
+
return unowned('subagent').map((event) => ({ ...event, ...tag }));
|
|
1728
|
+
}
|
|
1641
1729
|
}
|
|
1642
1730
|
|
|
1643
1731
|
// ---------------------------------------------------------------------------
|
package/src/harness-session.ts
CHANGED
|
@@ -92,7 +92,38 @@ export type SessionEventKind =
|
|
|
92
92
|
| 'usage'
|
|
93
93
|
| 'rate_limit'
|
|
94
94
|
| 'result'
|
|
95
|
-
| 'error'
|
|
95
|
+
| 'error'
|
|
96
|
+
| 'subagent';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A subagent's lifecycle as the native runtime reports it (kind `subagent`). Claude Code: its
|
|
100
|
+
* `system` task frames for an Agent/Task call. Codex: a collab `spawnAgent` item and its child thread.
|
|
101
|
+
*/
|
|
102
|
+
export interface SubagentLifecycle {
|
|
103
|
+
readonly phase: 'started' | 'progress' | 'updated' | 'ended';
|
|
104
|
+
/** The native id of the subagent run (Claude task id, Codex child thread id). */
|
|
105
|
+
readonly taskId: string;
|
|
106
|
+
/** The tool call that spawned it, in its parent's stream. */
|
|
107
|
+
readonly toolUseId?: string;
|
|
108
|
+
/** The task that spawned it, when a subagent spawned it (Claude). */
|
|
109
|
+
readonly parentTaskId?: string;
|
|
110
|
+
readonly agentType?: string;
|
|
111
|
+
readonly description?: string;
|
|
112
|
+
/** 1 for a child of the main thread. */
|
|
113
|
+
readonly depth?: number;
|
|
114
|
+
readonly backgrounded?: boolean;
|
|
115
|
+
/** The native status at the end (completed, stopped, failed, killed). */
|
|
116
|
+
readonly status?: string;
|
|
117
|
+
readonly summary?: string;
|
|
118
|
+
/** The task the subagent was given. */
|
|
119
|
+
readonly prompt?: string;
|
|
120
|
+
/** Its own running totals, when the runtime reports them (Claude progress and end frames). */
|
|
121
|
+
readonly usage?: {
|
|
122
|
+
readonly totalTokens: number;
|
|
123
|
+
readonly toolUses?: number;
|
|
124
|
+
readonly durationMs?: number;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
96
127
|
|
|
97
128
|
/** Input excludes cache reads/writes when those disjoint counters are supplied.
|
|
98
129
|
* Thinking is a subset of output; TTL counters are subsets of cacheWrite.
|
|
@@ -130,7 +161,8 @@ export interface SessionEvent extends SessionIdentity {
|
|
|
130
161
|
| 'duplicate-tool'
|
|
131
162
|
| 'unmatched-tool'
|
|
132
163
|
| 'malformed-tool'
|
|
133
|
-
| 'account-status'
|
|
164
|
+
| 'account-status'
|
|
165
|
+
| 'subagent';
|
|
134
166
|
/** Local transport observation, not a native terminal acknowledgment. */
|
|
135
167
|
readonly transportOutcome?: SessionAttempt['transportOutcome'];
|
|
136
168
|
readonly nativeOutcome?: NativeOutcome;
|
|
@@ -174,6 +206,14 @@ export interface SessionEvent extends SessionIdentity {
|
|
|
174
206
|
readonly limits?: LimitSnapshot;
|
|
175
207
|
/** Owned, deeply frozen native JSON data (for Oracle introspection). */
|
|
176
208
|
readonly raw?: unknown;
|
|
209
|
+
/**
|
|
210
|
+
* The tool call (in its parent's stream) that spawned the subagent this event came from; absent
|
|
211
|
+
* for the main thread's own events. Every subagent event, its lifecycle included, is outside any
|
|
212
|
+
* admission (`unattributedReason: "subagent"`), so it never touches a turn's content or results.
|
|
213
|
+
*/
|
|
214
|
+
readonly parentToolUseId?: string;
|
|
215
|
+
/** A subagent's lifecycle (kind `subagent`). */
|
|
216
|
+
readonly subagent?: SubagentLifecycle;
|
|
177
217
|
}
|
|
178
218
|
|
|
179
219
|
/** Result of a single send() — the turn's content plus all classified events. */
|
package/src/turn-state.ts
CHANGED
|
@@ -63,8 +63,11 @@ export class TurnState {
|
|
|
63
63
|
record(event: SessionEvent): SessionEvent {
|
|
64
64
|
const e = retainEvidence({ ...this.identity, ...event, admissionId: this.admissionId });
|
|
65
65
|
this.events.push(e);
|
|
66
|
-
|
|
67
|
-
if (e.
|
|
66
|
+
// A subagent's text is its own; the parent turn's content is only the parent's.
|
|
67
|
+
if (!e.parentToolUseId) {
|
|
68
|
+
if ((e.kind === 'text' || e.kind === 'result') && e.text) this.content = e.text;
|
|
69
|
+
if (e.kind === 'text_delta' && e.text) this.content += e.text;
|
|
70
|
+
}
|
|
68
71
|
// Usage events are per-request snapshots; the terminal result carries the turn total.
|
|
69
72
|
if (e.tokens && e.kind !== 'usage') this.tokens = e.tokens;
|
|
70
73
|
return e;
|