@inixiative/agent-session 0.6.8 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/agent-session",
3
- "version": "0.6.8",
3
+ "version": "0.7.0",
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",
@@ -36,18 +36,18 @@
36
36
  ],
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
- "@inixiative/json-rules": "^3.5.4"
39
+ "@inixiative/json-rules": "^3.16.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@biomejs/biome": "2.5.2",
43
- "@inixiative/config": "^0.5.118",
44
- "@types/bun": "1.4.2",
43
+ "@inixiative/config": "^0.5.166",
44
+ "@types/bun": "1.4.4",
45
45
  "lefthook": "2.1.9",
46
46
  "typescript": "6.0.3"
47
47
  },
48
- "packageManager": "bun@1.4.2",
48
+ "packageManager": "bun@1.4.4",
49
49
  "engines": {
50
- "bun": ">=1.4.2"
50
+ "bun": ">=1.4.4"
51
51
  },
52
52
  "repository": {
53
53
  "type": "git",
@@ -59,6 +59,7 @@ import type {
59
59
  SessionSendOptions,
60
60
  SessionTokens,
61
61
  SubagentLifecycle,
62
+ TaskLifecycle,
62
63
  ToolDecision,
63
64
  ToolGate,
64
65
  ToolRequest,
@@ -385,6 +386,8 @@ export class ClaudeCodeSession implements HarnessSession {
385
386
  private _seenBoundaries = new Set<string>();
386
387
  /** Task ids whose task_started was a subagent (local_agent); later task frames carry no type. */
387
388
  private _agentTasks = new Set<string>();
389
+ /** Task ids whose task_started was another task type (a background shell, a monitor): id → type. */
390
+ private _otherTasks = new Map<string, string>();
388
391
 
389
392
  // -- Turn queue --
390
393
  private _queue: QueuedTurn[] = [];
@@ -829,6 +832,11 @@ export class ClaudeCodeSession implements HarnessSession {
829
832
  return turn.evidence.nativeOutcome === 'unknown' ? 'unacknowledged' : 'acknowledged';
830
833
  }
831
834
 
835
+ /** Stop a background task via the CLI control protocol (`stop_task`); its end comes as lifecycle. */
836
+ async stopTask(taskId: string, opts?: { timeoutMs?: number }): Promise<void> {
837
+ await this._controlRequest({ subtype: 'stop_task', task_id: taskId }, opts?.timeoutMs ?? 5_000);
838
+ }
839
+
832
840
  /** Account limits via the CLI control protocol (`get_usage`); no model turn. Requires a started session. */
833
841
  async readLimits(opts?: { timeoutMs?: number }): Promise<LimitSnapshot | undefined> {
834
842
  const response = await this._controlRequest(
@@ -1297,6 +1305,20 @@ export class ClaudeCodeSession implements HarnessSession {
1297
1305
  ? raw.parent_tool_use_id
1298
1306
  : undefined;
1299
1307
  const lifecycle = this._subagentLifecycle(raw);
1308
+ // A background task (a shell, a monitor) outlives its turn like a subagent: never an admission's.
1309
+ const task = lifecycle || parentToolUseId ? undefined : this._taskLifecycle(raw);
1310
+ if (task) {
1311
+ this._emit({
1312
+ kind: 'task',
1313
+ timestamp: Date.now(),
1314
+ task,
1315
+ raw,
1316
+ correlation: 'unknown',
1317
+ unattributedReason: 'task',
1318
+ nativeSessionId: typeof raw.session_id === 'string' ? raw.session_id : undefined,
1319
+ });
1320
+ return;
1321
+ }
1300
1322
  if (parentToolUseId || lifecycle) {
1301
1323
  const message = raw.message as Record<string, unknown> | undefined;
1302
1324
  const events: SessionEvent[] = lifecycle
@@ -1669,6 +1691,40 @@ export class ClaudeCodeSession implements HarnessSession {
1669
1691
  return claudeSubagentLifecycle(msg, phase, taskId);
1670
1692
  }
1671
1693
 
1694
+ /**
1695
+ * A Claude Code `system` task frame for a task that is not a subagent (`task_type` other than
1696
+ * `local_agent`), as lifecycle. Later phases carry no task type: the session remembers it.
1697
+ */
1698
+ private _taskLifecycle(msg: Record<string, unknown>): TaskLifecycle | undefined {
1699
+ if (msg.type !== 'system' || typeof msg.subtype !== 'string') return undefined;
1700
+ const phase = CLAUDE_TASK_PHASES[msg.subtype];
1701
+ const taskId = typeof msg.task_id === 'string' && msg.task_id ? msg.task_id : undefined;
1702
+ if (!phase || !taskId) return undefined;
1703
+ if (phase === 'started') {
1704
+ if (typeof msg.task_type !== 'string' || msg.task_type === 'local_agent') return undefined;
1705
+ this._otherTasks.set(taskId, msg.task_type);
1706
+ }
1707
+ const taskType = this._otherTasks.get(taskId);
1708
+ if (!taskType) return undefined;
1709
+ if (phase === 'ended') this._otherTasks.delete(taskId);
1710
+ const text = (value: unknown) => (typeof value === 'string' && value ? value : undefined);
1711
+ const patch =
1712
+ msg.patch && typeof msg.patch === 'object' && !Array.isArray(msg.patch)
1713
+ ? (msg.patch as Record<string, unknown>)
1714
+ : undefined;
1715
+ const status = text(msg.status) ?? text(patch?.status);
1716
+ return {
1717
+ phase,
1718
+ taskId,
1719
+ taskType,
1720
+ ...(text(msg.tool_use_id) ? { toolUseId: text(msg.tool_use_id) } : {}),
1721
+ ...(text(msg.description) ? { description: text(msg.description) } : {}),
1722
+ ...(status ? { status } : {}),
1723
+ ...(text(msg.reason) ? { reason: text(msg.reason) } : {}),
1724
+ ...(text(msg.summary) ? { summary: text(msg.summary) } : {}),
1725
+ };
1726
+ }
1727
+
1672
1728
  private _classify(msg: Record<string, unknown>, parentToolUseId?: string): SessionEvent[] {
1673
1729
  return this._classifyFrame(msg).map((event) =>
1674
1730
  parentToolUseId ? { ...event, parentToolUseId } : event,
@@ -122,7 +122,8 @@ export type SessionEventKind =
122
122
  | 'rate_limit'
123
123
  | 'result'
124
124
  | 'error'
125
- | 'subagent';
125
+ | 'subagent'
126
+ | 'task';
126
127
 
127
128
  /**
128
129
  * A subagent's lifecycle as the native runtime reports it (kind `subagent`). Claude Code: its
@@ -154,6 +155,26 @@ export interface SubagentLifecycle {
154
155
  };
155
156
  }
156
157
 
158
+ /**
159
+ * A background task that is not a subagent (kind `task`): Claude Code's `system` task frames for a
160
+ * background shell (`local_bash`), a monitor or a workflow. Like a subagent it can outlive the turn
161
+ * that started it, so its events belong to no admission (`unattributedReason: "task"`).
162
+ */
163
+ export interface TaskLifecycle {
164
+ readonly phase: 'started' | 'progress' | 'updated' | 'ended';
165
+ /** The native task id (the id the agent's own task tools name). */
166
+ readonly taskId: string;
167
+ /** The runtime's task type, e.g. `local_bash`. */
168
+ readonly taskType: string;
169
+ /** The tool call that started it, in the session's stream (its command is that call's input). */
170
+ readonly toolUseId?: string;
171
+ readonly description?: string;
172
+ /** The native status at the end (completed, failed, stopped, killed). */
173
+ readonly status?: string;
174
+ readonly reason?: string;
175
+ readonly summary?: string;
176
+ }
177
+
157
178
  /** Input excludes cache reads/writes when those disjoint counters are supplied.
158
179
  * Thinking is a subset of output; TTL counters are subsets of cacheWrite.
159
180
  * Missing optional counters mean the runtime did not report them.
@@ -191,7 +212,8 @@ export interface SessionEvent extends SessionIdentity {
191
212
  | 'unmatched-tool'
192
213
  | 'malformed-tool'
193
214
  | 'account-status'
194
- | 'subagent';
215
+ | 'subagent'
216
+ | 'task';
195
217
  /** Local transport observation, not a native terminal acknowledgment. */
196
218
  readonly transportOutcome?: SessionAttempt['transportOutcome'];
197
219
  readonly nativeOutcome?: NativeOutcome;
@@ -243,6 +265,8 @@ export interface SessionEvent extends SessionIdentity {
243
265
  readonly parentToolUseId?: string;
244
266
  /** A subagent's lifecycle (kind `subagent`). */
245
267
  readonly subagent?: SubagentLifecycle;
268
+ /** A background task's lifecycle (kind `task`). */
269
+ readonly task?: TaskLifecycle;
246
270
  }
247
271
 
248
272
  /** Result of a single send() — the turn's content plus all classified events. */
@@ -398,6 +422,11 @@ export interface HarnessSession {
398
422
  interruptNative?(opts?: { timeoutMs?: number }): Promise<NativeInterruptOutcome>;
399
423
  /** Poll account usage limits without a model turn. Present where the transport declares `limits.poll`. */
400
424
  readLimits?(opts?: { timeoutMs?: number }): Promise<LimitSnapshot | undefined>;
425
+ /**
426
+ * Stop one background task (a `task` lifecycle's `taskId`) through the runtime's own control
427
+ * channel. Its end arrives as the task's `ended` lifecycle. Claude Code: the `stop_task` request.
428
+ */
429
+ stopTask?(taskId: string, opts?: { timeoutMs?: number }): Promise<void>;
401
430
  /** One immutable admission snapshot, without copying unrelated session history. */
402
431
  inspectAttempt?(admissionId: string): SessionAttempt | undefined;
403
432
  readonly admissionProtocol?: 'prewrite-v1';