@inixiative/agent-session 0.6.7 → 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/README.md +6 -0
- package/package.json +6 -6
- package/src/claude-code-session.ts +104 -11
- package/src/codex-session.ts +25 -7
- package/src/harness-session.ts +60 -2
package/README.md
CHANGED
|
@@ -50,6 +50,12 @@ session.kill();
|
|
|
50
50
|
refused before a byte is written.
|
|
51
51
|
- **fork / resume / interrupt** and pre-send hooks (rewrite the outgoing
|
|
52
52
|
message — e.g. inject just-in-time context per turn).
|
|
53
|
+
- **A resume whose native conversation is not found is typed.** Claude finding no transcript
|
|
54
|
+
("No conversation found with session ID": cleaned up, deleted, or under another project
|
|
55
|
+
directory or config home) and Codex finding no rollout ("no rollout found for thread id") both
|
|
56
|
+
raise `NativeBindingMissingError` (`nativeBindingMissing(error)` finds it behind a turn error,
|
|
57
|
+
with the `cwd` the lookup ran in). No turn ran, so the attempt settles as a no-send instead of
|
|
58
|
+
uncertain native work, and the caller can start fresh from its own record.
|
|
53
59
|
- **Transports with declared capabilities.** How a model is reached is a
|
|
54
60
|
pluggable transport; switching it is a routing change, not a rewrite.
|
|
55
61
|
- **Zero dependencies.** Just Bun + the agent CLI you're driving. The Agent SDK
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inixiative/agent-session",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
39
|
+
"@inixiative/json-rules": "^3.16.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@biomejs/biome": "2.5.2",
|
|
43
|
-
"@inixiative/config": "^0.5.
|
|
44
|
-
"@types/bun": "1.4.
|
|
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.
|
|
48
|
+
"packageManager": "bun@1.4.4",
|
|
49
49
|
"engines": {
|
|
50
|
-
"bun": ">=1.4.
|
|
50
|
+
"bun": ">=1.4.4"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|
|
@@ -59,11 +59,12 @@ import type {
|
|
|
59
59
|
SessionSendOptions,
|
|
60
60
|
SessionTokens,
|
|
61
61
|
SubagentLifecycle,
|
|
62
|
+
TaskLifecycle,
|
|
62
63
|
ToolDecision,
|
|
63
64
|
ToolGate,
|
|
64
65
|
ToolRequest,
|
|
65
66
|
} from './harness-session';
|
|
66
|
-
import { sendableImages } from './harness-session';
|
|
67
|
+
import { NativeBindingMissingError, sendableImages } from './harness-session';
|
|
67
68
|
|
|
68
69
|
// Re-export types so existing import paths keep working
|
|
69
70
|
export type {
|
|
@@ -354,6 +355,8 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
354
355
|
// so we know the concrete types at runtime.
|
|
355
356
|
private _proc: PipedSubprocess | null = null;
|
|
356
357
|
private _stderr = '';
|
|
358
|
+
/** The stderr reader of the running process; awaited (capped) before stderr is interpreted. */
|
|
359
|
+
private _stderrDone: Promise<void> = Promise.resolve();
|
|
357
360
|
|
|
358
361
|
// -- Session state --
|
|
359
362
|
/**
|
|
@@ -383,6 +386,8 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
383
386
|
private _seenBoundaries = new Set<string>();
|
|
384
387
|
/** Task ids whose task_started was a subagent (local_agent); later task frames carry no type. */
|
|
385
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>();
|
|
386
391
|
|
|
387
392
|
// -- Turn queue --
|
|
388
393
|
private _queue: QueuedTurn[] = [];
|
|
@@ -560,7 +565,7 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
560
565
|
// Background readers — run for session lifetime (don't await)
|
|
561
566
|
const proc = this._proc;
|
|
562
567
|
const stdoutDone = this._readStdout(proc);
|
|
563
|
-
this._readStderr();
|
|
568
|
+
this._stderrDone = this._readStderr();
|
|
564
569
|
|
|
565
570
|
// Monitor process exit for cleanup
|
|
566
571
|
// The process read its base context file at startup; it goes when the process does.
|
|
@@ -577,7 +582,7 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
577
582
|
const errMsg = this._stderr.trim()
|
|
578
583
|
? `Process exited (code ${code}): ${this._stderr.trim().slice(0, 500)}`
|
|
579
584
|
: `Process exited with code ${code}`;
|
|
580
|
-
this._rejectInflight(new Error(errMsg));
|
|
585
|
+
this._rejectInflight((await this._settleMissing(errMsg)) ?? new Error(errMsg));
|
|
581
586
|
this._rejectQueue(new Error('Session ended'));
|
|
582
587
|
this._rejectControl(new Error('Session ended'));
|
|
583
588
|
this._emit({ kind: 'session_end', timestamp: Date.now(), transportOutcome: 'failed' });
|
|
@@ -616,6 +621,8 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
616
621
|
);
|
|
617
622
|
} catch (error) {
|
|
618
623
|
this.kill();
|
|
624
|
+
const missing = this._missingBinding((error as Error).message);
|
|
625
|
+
if (missing) throw missing;
|
|
619
626
|
throw new Error(
|
|
620
627
|
`${this._toolGate ? 'Tool gate' : 'Extension'} registration failed: ${(error as Error).message}`,
|
|
621
628
|
);
|
|
@@ -630,6 +637,41 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
630
637
|
}
|
|
631
638
|
}
|
|
632
639
|
|
|
640
|
+
/**
|
|
641
|
+
* The CLI refused to resume because the transcript is gone (Claude Code's cleanup of
|
|
642
|
+
* `~/.claude/projects`, or a deleted file): it says so and exits before reading a turn.
|
|
643
|
+
*/
|
|
644
|
+
private _missingBinding(detail: string): NativeBindingMissingError | undefined {
|
|
645
|
+
const binding = this._externalSessionId;
|
|
646
|
+
if (!binding) return undefined;
|
|
647
|
+
const marker = `No conversation found with session ID: ${binding}`;
|
|
648
|
+
return this._stderr.includes(marker) || detail.includes(marker)
|
|
649
|
+
? new NativeBindingMissingError('claude-code', binding, marker, this._cwd)
|
|
650
|
+
: undefined;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* After the process ended: was the binding missing? stderr is read first (capped), since the
|
|
655
|
+
* marker is there. A turn written to a CLI that never loaded the conversation (no `system/init`,
|
|
656
|
+
* no terminal) could not run: it settles as a no-send rather than uncertain native work.
|
|
657
|
+
*/
|
|
658
|
+
private async _settleMissing(detail: string): Promise<NativeBindingMissingError | undefined> {
|
|
659
|
+
await Promise.race([this._stderrDone, Bun.sleep(250)]);
|
|
660
|
+
const missing = this._missingBinding(detail);
|
|
661
|
+
const a = this._inflight?.evidence;
|
|
662
|
+
if (
|
|
663
|
+
missing &&
|
|
664
|
+
a &&
|
|
665
|
+
a.nativeOutcome === 'unknown' &&
|
|
666
|
+
!a.events.some((event) => {
|
|
667
|
+
const raw = event.raw as { type?: unknown; subtype?: unknown } | undefined;
|
|
668
|
+
return raw?.type === 'system' && raw.subtype === 'init';
|
|
669
|
+
})
|
|
670
|
+
)
|
|
671
|
+
a.dispatch = 'not-dispatched';
|
|
672
|
+
return missing;
|
|
673
|
+
}
|
|
674
|
+
|
|
633
675
|
private _detachExtension(reason: string): void {
|
|
634
676
|
try {
|
|
635
677
|
this._extension?.detach(reason);
|
|
@@ -790,6 +832,11 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
790
832
|
return turn.evidence.nativeOutcome === 'unknown' ? 'unacknowledged' : 'acknowledged';
|
|
791
833
|
}
|
|
792
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
|
+
|
|
793
840
|
/** Account limits via the CLI control protocol (`get_usage`); no model turn. Requires a started session. */
|
|
794
841
|
async readLimits(opts?: { timeoutMs?: number }): Promise<LimitSnapshot | undefined> {
|
|
795
842
|
const response = await this._controlRequest(
|
|
@@ -1065,14 +1112,12 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
1065
1112
|
if (buffer.trim() && (this._proc === proc || !this._proc)) {
|
|
1066
1113
|
this._processLine(buffer);
|
|
1067
1114
|
}
|
|
1068
|
-
if (this._proc === proc && this._alive)
|
|
1069
|
-
this.
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
),
|
|
1075
|
-
);
|
|
1115
|
+
if (this._proc === proc && this._alive) {
|
|
1116
|
+
const detail = this._stderr.trim()
|
|
1117
|
+
? `Native stdout closed: ${this._stderr.trim().slice(0, 500)}`
|
|
1118
|
+
: 'Native stdout closed';
|
|
1119
|
+
this._transportFailure((await this._settleMissing(detail)) ?? new Error(detail));
|
|
1120
|
+
}
|
|
1076
1121
|
} catch (err) {
|
|
1077
1122
|
if (this._proc === proc) this._transportFailure(err as Error);
|
|
1078
1123
|
}
|
|
@@ -1260,6 +1305,20 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
1260
1305
|
? raw.parent_tool_use_id
|
|
1261
1306
|
: undefined;
|
|
1262
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
|
+
}
|
|
1263
1322
|
if (parentToolUseId || lifecycle) {
|
|
1264
1323
|
const message = raw.message as Record<string, unknown> | undefined;
|
|
1265
1324
|
const events: SessionEvent[] = lifecycle
|
|
@@ -1632,6 +1691,40 @@ export class ClaudeCodeSession implements HarnessSession {
|
|
|
1632
1691
|
return claudeSubagentLifecycle(msg, phase, taskId);
|
|
1633
1692
|
}
|
|
1634
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
|
+
|
|
1635
1728
|
private _classify(msg: Record<string, unknown>, parentToolUseId?: string): SessionEvent[] {
|
|
1636
1729
|
return this._classifyFrame(msg).map((event) =>
|
|
1637
1730
|
parentToolUseId ? { ...event, parentToolUseId } : event,
|
package/src/codex-session.ts
CHANGED
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
SessionResult,
|
|
28
28
|
ToolGate,
|
|
29
29
|
} from './harness-session';
|
|
30
|
-
import { sendableImages } from './harness-session';
|
|
30
|
+
import { NativeBindingMissingError, sendableImages } from './harness-session';
|
|
31
31
|
|
|
32
32
|
// Re-export types so importers can stay on one path (mirrors claude-code-session).
|
|
33
33
|
export type {
|
|
@@ -592,8 +592,11 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
592
592
|
|
|
593
593
|
private _releaseKnownTurn(): void {
|
|
594
594
|
const a = this._inflight?.evidence;
|
|
595
|
-
if (!a
|
|
596
|
-
|
|
595
|
+
if (!a) return;
|
|
596
|
+
// Refused before `turn/start` was written: nothing native is outstanding.
|
|
597
|
+
const noSend = a.dispatch === 'not-dispatched' && a.localOutcome === 'rejected';
|
|
598
|
+
if (!noSend && (!this._canReleaseAttempt(a) || a.nativeOutcome === 'unknown')) return;
|
|
599
|
+
if (!noSend) this._turns++;
|
|
597
600
|
this._inflight = null;
|
|
598
601
|
this._processNextTurn();
|
|
599
602
|
}
|
|
@@ -1222,8 +1225,9 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1222
1225
|
if (!this._threadReady) {
|
|
1223
1226
|
const binding = this._externalSessionId;
|
|
1224
1227
|
const method = binding ? 'thread/resume' : 'thread/start';
|
|
1225
|
-
|
|
1226
|
-
|
|
1228
|
+
let resumed: unknown;
|
|
1229
|
+
try {
|
|
1230
|
+
resumed = await this._rpcRequest(method, {
|
|
1227
1231
|
...(binding ? { threadId: binding } : {}),
|
|
1228
1232
|
model: this._model,
|
|
1229
1233
|
cwd: this._cwd,
|
|
@@ -1232,8 +1236,22 @@ export class CodexAppServerSession implements HarnessSession {
|
|
|
1232
1236
|
...(this._gated ? { approvalsReviewer: 'user' } : {}),
|
|
1233
1237
|
sandbox: this._sandbox,
|
|
1234
1238
|
...(this._options.config ? { config: this._options.config } : {}),
|
|
1235
|
-
})
|
|
1236
|
-
)
|
|
1239
|
+
});
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
// The rollout is gone (Codex's own cleanup, or deleted). Codex answered that no thread
|
|
1242
|
+
// exists, so no turn could run and none was written: a settled no-send, not uncertain
|
|
1243
|
+
// native work. Any other failure here keeps its outcome unknown.
|
|
1244
|
+
if (binding && /^no rollout found for thread id\b/i.test((error as Error).message ?? '')) {
|
|
1245
|
+
a.dispatch = 'not-dispatched';
|
|
1246
|
+
throw new NativeBindingMissingError(
|
|
1247
|
+
'codex-app-server',
|
|
1248
|
+
binding,
|
|
1249
|
+
(error as Error).message,
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1254
|
+
const response = object(resumed);
|
|
1237
1255
|
const thread = object(response?.thread),
|
|
1238
1256
|
id = thread?.id;
|
|
1239
1257
|
if (typeof id !== 'string' || !id || (binding && id !== binding))
|
package/src/harness-session.ts
CHANGED
|
@@ -79,6 +79,35 @@ export class SessionTurnError extends Error {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* The native binding a session was told to resume was not found by the runtime, from this
|
|
84
|
+
* session's cwd and config home: Claude found no transcript ("No conversation found with session
|
|
85
|
+
* ID": cleaned up, deleted, or kept under another project directory or `CLAUDE_CONFIG_DIR`), or
|
|
86
|
+
* Codex found no rollout ("no rollout found for thread id": deleted, or under another
|
|
87
|
+
* `CODEX_HOME`). No turn ran. The caller decides what comes next, typically a fresh session seeded
|
|
88
|
+
* from its own record of the conversation; `cwd` says where the lookup was made.
|
|
89
|
+
*/
|
|
90
|
+
export class NativeBindingMissingError extends Error {
|
|
91
|
+
constructor(
|
|
92
|
+
readonly runtime: 'claude-code' | 'codex-app-server',
|
|
93
|
+
readonly binding: string,
|
|
94
|
+
message: string,
|
|
95
|
+
readonly cwd?: string,
|
|
96
|
+
) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = 'NativeBindingMissingError';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The missing-binding error behind a session error, if any (it may ride as a turn error's cause). */
|
|
103
|
+
export function nativeBindingMissing(error: unknown): NativeBindingMissingError | undefined {
|
|
104
|
+
for (let current = error, depth = 0; current && depth < 5; depth++) {
|
|
105
|
+
if (current instanceof NativeBindingMissingError) return current;
|
|
106
|
+
current = (current as { cause?: unknown }).cause;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
export type SessionEventKind =
|
|
83
112
|
| 'text_delta'
|
|
84
113
|
| 'native_status'
|
|
@@ -93,7 +122,8 @@ export type SessionEventKind =
|
|
|
93
122
|
| 'rate_limit'
|
|
94
123
|
| 'result'
|
|
95
124
|
| 'error'
|
|
96
|
-
| 'subagent'
|
|
125
|
+
| 'subagent'
|
|
126
|
+
| 'task';
|
|
97
127
|
|
|
98
128
|
/**
|
|
99
129
|
* A subagent's lifecycle as the native runtime reports it (kind `subagent`). Claude Code: its
|
|
@@ -125,6 +155,26 @@ export interface SubagentLifecycle {
|
|
|
125
155
|
};
|
|
126
156
|
}
|
|
127
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
|
+
|
|
128
178
|
/** Input excludes cache reads/writes when those disjoint counters are supplied.
|
|
129
179
|
* Thinking is a subset of output; TTL counters are subsets of cacheWrite.
|
|
130
180
|
* Missing optional counters mean the runtime did not report them.
|
|
@@ -162,7 +212,8 @@ export interface SessionEvent extends SessionIdentity {
|
|
|
162
212
|
| 'unmatched-tool'
|
|
163
213
|
| 'malformed-tool'
|
|
164
214
|
| 'account-status'
|
|
165
|
-
| 'subagent'
|
|
215
|
+
| 'subagent'
|
|
216
|
+
| 'task';
|
|
166
217
|
/** Local transport observation, not a native terminal acknowledgment. */
|
|
167
218
|
readonly transportOutcome?: SessionAttempt['transportOutcome'];
|
|
168
219
|
readonly nativeOutcome?: NativeOutcome;
|
|
@@ -214,6 +265,8 @@ export interface SessionEvent extends SessionIdentity {
|
|
|
214
265
|
readonly parentToolUseId?: string;
|
|
215
266
|
/** A subagent's lifecycle (kind `subagent`). */
|
|
216
267
|
readonly subagent?: SubagentLifecycle;
|
|
268
|
+
/** A background task's lifecycle (kind `task`). */
|
|
269
|
+
readonly task?: TaskLifecycle;
|
|
217
270
|
}
|
|
218
271
|
|
|
219
272
|
/** Result of a single send() — the turn's content plus all classified events. */
|
|
@@ -369,6 +422,11 @@ export interface HarnessSession {
|
|
|
369
422
|
interruptNative?(opts?: { timeoutMs?: number }): Promise<NativeInterruptOutcome>;
|
|
370
423
|
/** Poll account usage limits without a model turn. Present where the transport declares `limits.poll`. */
|
|
371
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>;
|
|
372
430
|
/** One immutable admission snapshot, without copying unrelated session history. */
|
|
373
431
|
inspectAttempt?(admissionId: string): SessionAttempt | undefined;
|
|
374
432
|
readonly admissionProtocol?: 'prewrite-v1';
|