@inixiative/agent-session 0.6.4 → 0.6.6

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.
@@ -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
  // ---------------------------------------------------------------------------
@@ -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/index.ts CHANGED
@@ -13,9 +13,19 @@ export {
13
13
  // Claude Code adapter (persistent stream-json session, subscription auth).
14
14
  export {
15
15
  CLAUDE_TEXT_ONLY_ARGS,
16
+ type ClaudeAgentDefinition,
16
17
  ClaudeCodeSession,
17
18
  type ClaudeCodeSessionConfig,
19
+ type ClaudeSessionExtension,
20
+ type ClaudeSessionHost,
18
21
  } from './claude-code-session';
22
+ export {
23
+ type ClaudeInSessionConfig,
24
+ ClaudeInSessionDecisions,
25
+ IN_SESSION_AGENTS,
26
+ IN_SESSION_SERVER,
27
+ type InSessionSetup,
28
+ } from './claude-in-session';
19
29
  export { type ClaudePrimedConfig, ClaudePrimedSessions } from './claude-primed';
20
30
  export { type ClaudeSubagentConfig, ClaudeSubagentSessions } from './claude-subagents';
21
31
  export { parseClaudeUsage } from './claude-usage';
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
- if ((e.kind === 'text' || e.kind === 'result') && e.text) this.content = e.text;
67
- if (e.kind === 'text_delta' && e.text) this.content += e.text;
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;