@agent-compose/sdk 0.8.2 → 0.8.3

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.
Files changed (40) hide show
  1. package/dist/agent/__tests__/perf-sampler.test.d.ts +10 -0
  2. package/dist/agent/agent-context.d.ts +1 -1
  3. package/dist/agent/agent-loop.d.ts +5 -1
  4. package/dist/agent/desktop-open.d.ts +184 -0
  5. package/dist/agent/perf-sampler.d.ts +99 -0
  6. package/dist/agent/services-manifest.d.ts +88 -0
  7. package/dist/agent/services-restore.d.ts +58 -0
  8. package/dist/client.d.ts +164 -8
  9. package/dist/display.d.ts +17 -0
  10. package/dist/index.d.ts +13 -4
  11. package/dist/index.js +1374 -51
  12. package/dist/runtimes/_cli-agent.d.ts +347 -2
  13. package/dist/runtimes/claude-code.d.ts +12 -0
  14. package/dist/runtimes/codex.d.ts +8 -0
  15. package/dist/runtimes/openai-desktop.js +1312 -51
  16. package/dist/runtimes/session-env.test.d.ts +14 -0
  17. package/dist/types/api-conversations.d.ts +309 -1
  18. package/dist/types/api-factory.d.ts +115 -10
  19. package/dist/types/api-runs.d.ts +21 -0
  20. package/dist/types/protocol.d.ts +32 -1
  21. package/dist/types/runtime.d.ts +120 -0
  22. package/package.json +1 -1
  23. package/src/agent/agent-context.ts +100 -11
  24. package/src/agent/agent-loop.ts +10 -3
  25. package/src/agent/desktop-open.ts +418 -0
  26. package/src/agent/perf-sampler.ts +202 -0
  27. package/src/agent/services-manifest.ts +356 -0
  28. package/src/agent/services-restore.ts +195 -0
  29. package/src/client.ts +328 -12
  30. package/src/display.ts +44 -1
  31. package/src/index.ts +63 -1
  32. package/src/runtimes/_cli-agent.ts +891 -35
  33. package/src/runtimes/claude-code.ts +187 -12
  34. package/src/runtimes/codex.ts +58 -1
  35. package/src/sandbox/providers/local.ts +16 -4
  36. package/src/types/api-conversations.ts +307 -3
  37. package/src/types/api-factory.ts +118 -10
  38. package/src/types/api-runs.ts +23 -0
  39. package/src/types/protocol.ts +30 -1
  40. package/src/types/runtime.ts +122 -0
@@ -23,13 +23,231 @@
23
23
  * (see each spec's `authEnv`); `commands.run` inherits the sandbox env, so
24
24
  * values set via `agentc secrets set` are visible to the CLI.
25
25
  */
26
- import type { AgentMessage, ModelExecutionContract, RuntimeOptions, SandboxProvider, ToolCallGateResult } from "../index.js";
26
+ import type { AgentMessage, ModelExecutionContract, RunnerLivenessVerdict, RuntimeOptions, SandboxProvider, ToolCallGateResult, TurnExitNotify } from "../index.js";
27
+ import type { GuestPerfSample } from "../agent/perf-sampler.js";
27
28
  import type { ProcessorContext, ToolCall } from "../processors/processor.js";
28
29
  /** Backoff between tail re-attach attempts after a mid-turn stream fault
29
30
  * (durable detached transport). Short: the runner is alive and producing,
30
31
  * and each retry costs one exec; the executor's evidence machinery — not
31
32
  * this loop — bounds a truly dead sandbox. Exported for tests. */
32
33
  export declare const TAIL_REATTACH_DELAY_MS = 2000;
34
+ /** Cadence of the in-guest heartbeat writer (seconds — it is a shell loop). */
35
+ export declare const RUNNER_HEARTBEAT_INTERVAL_SECONDS = 10;
36
+ /** How recently a file under the CLI's own task state dir (~/.claude/tasks)
37
+ * must have been touched to count as a LIVE background task in the busy
38
+ * sentinel (minutes — it is `find -mmin`). A running task's state/output
39
+ * files are written continuously, so two minutes is generous; a finished
40
+ * task's files go quiet and age out on their own. */
41
+ export declare const RUNNER_BUSY_TASK_FRESH_MINUTES = 2;
42
+ /** Cap on the task-id list the busy sentinel publishes — the ids are for
43
+ * server-side log attribution, not an inventory. */
44
+ export declare const RUNNER_BUSY_TASK_ID_LIMIT = 16;
45
+ /** The sh fragment the heartbeat subshell runs each beat to maintain the
46
+ * busy sentinel — the guest half of the background-work marker contract:
47
+ * - `<promptPath>.busy` — the COUNT of recently-active files under the
48
+ * CLI's task state dir (`$ac_busy` stays set in-shell for the loop's
49
+ * own exit decision);
50
+ * - `<promptPath>.tasks` — the task IDS those files belong to (first
51
+ * path segment under tasks/, deduped, capped).
52
+ * The marker's freshness stamp is the files' own mtime (each beat rewrites
53
+ * them; the server probe gates on `-mmin -1`). Cheap (one bounded find
54
+ * over a small dir), and read by the server's park path so a VM hosting
55
+ * live background work is never frozen mid-flight (2026-08-15 forensics:
56
+ * a 3-min idle park suspended a VM with three research agents mid-flight).
57
+ * A missing tasks dir counts 0. Exported for tests. */
58
+ export declare function runnerBusySentinelFragment(busyPath: string, tasksPath: string): string;
59
+ /** A heartbeat older than this (by the GUEST's own clock — the probe reads
60
+ * `date +%s%3N` in the same exec, so host clock skew is irrelevant) is
61
+ * stale: three missed writes. */
62
+ export declare const RUNNER_HEARTBEAT_STALE_MS: number;
63
+ /** Watchdog cadence while a tail stream is attached. Each tick is one fresh
64
+ * short exec; a healthy stream makes every tick a no-op. */
65
+ export declare const TAIL_WATCHDOG_INTERVAL_MS = 15000;
66
+ /** Explicit ceiling on one durable-trio probe exec. A probe that can't
67
+ * answer is "probe-failed" — never a verdict. */
68
+ export declare const TURN_PROBE_TIMEOUT_MS = 10000;
69
+ /** After this many stream deaths in ONE turn, stop re-attaching tails and
70
+ * finish the turn on durable-file polling alone (the GHA permanent-
71
+ * fallback pattern): a wire that killed three streams will kill the
72
+ * fourth, and the durable plane already delivers everything. */
73
+ export declare const TAIL_ABANDON_AFTER_STREAM_DEATHS = 3;
74
+ /** Poll cadence once streaming is abandoned — a full durable read per poll
75
+ * (envd HTTP, ~65ms RTT), consumed from the byte offset. Latency-tuned:
76
+ * this is the degraded path, correctness never depends on it being fast. */
77
+ export declare const DURABLE_POLL_INTERVAL_MS = 1000;
78
+ /** The claude CLI's stderr complaint when `--resume <id>` names a thread
79
+ * that does not exist on this machine (the 2026-08-18 split-brain
80
+ * forensic). When a nonzero exit's stderr carries it, the transport
81
+ * surfaces the stderr as the turn's LAST error event even though a bare
82
+ * result error already streamed — the consumer's classifier needs the
83
+ * specific complaint, not the generic `error_during_execution` token. */
84
+ export declare const RESUME_TARGET_MISSING_STDERR: RegExp;
85
+ /** One durable-trio reading, parsed from the probe exec's single line. */
86
+ export interface TurnProbeReading {
87
+ /** Bytes currently in the durable stdout file (-1: unreadable/absent). */
88
+ size: number;
89
+ /** Is the detached runner's process group leader alive (kill -0)? */
90
+ alive: boolean;
91
+ /** Last heartbeat value (epoch ms, guest clock; 0 = no heartbeat yet). */
92
+ hbMs: number;
93
+ /** The guest's own clock at probe time (epoch ms). */
94
+ nowMs: number;
95
+ /** Is the exit sentinel already in the durable file (turn FINISHED)? */
96
+ sentinelSeen: boolean;
97
+ /** Latest guest perf sample (the trailing `.perf` token field), when the
98
+ * guest wrote one AND it parsed clean. Absent = older guest image, no
99
+ * sample yet, or a garbled token — evidence-absent, never an error.
100
+ * Telemetry-only: no liveness verdict may ever read it. */
101
+ perf?: GuestPerfSample;
102
+ }
103
+ /** The probe exec: one line, six fields, always exit 0 — faults surface as
104
+ * parse failures, not exec failures. Reads the guest's own clock in the
105
+ * same exec so staleness math never involves the host clock. The sixth
106
+ * field is the perf token (perf-sampler.ts), `-` when absent; the char
107
+ * whitelist (`tr -cd`) collapses any garbled write to at most one token —
108
+ * it can never add whitespace and desync the field positions. */
109
+ export declare function turnLivenessProbeCommand(paths: {
110
+ outPath: string;
111
+ hbPath: string;
112
+ perfPath: string;
113
+ pid: number;
114
+ sentinel: string;
115
+ }): string;
116
+ /** Parse the probe's line. Null = unusable output (a wedged or faulted exec)
117
+ * — the caller treats that as "probe-failed", never as a verdict. The
118
+ * sixth field (perf token) is OPTIONAL both ways: a five-field line from
119
+ * an old guest parses fine, and a token that fails its own clamp simply
120
+ * leaves `perf` absent — perf can never fail a liveness reading. */
121
+ export declare function parseTurnProbeOutput(raw: string): TurnProbeReading | null;
122
+ /** The three-valued verdict from one durable reading. A FINISHED turn
123
+ * (sentinel durable in the file) is ALIVE — the transport's watchdog
124
+ * harvests it within one interval; reporting it dead is exactly the
125
+ * incident's ten-minute blindness. */
126
+ export declare function livenessVerdictFromReading(reading: TurnProbeReading | null): RunnerLivenessVerdict;
127
+ /** Feeder poll cadence (seconds — it is a shell `sleep`; GNU sleep accepts
128
+ * fractions and the durable transport only runs on the GNU guest image). */
129
+ export declare const INJECT_FEEDER_POLL_SECONDS = "0.3";
130
+ /** How much of the durable stdout tail the feeder greps for the terminal
131
+ * result line each poll — bounded so a long transcript never turns the
132
+ * poll into a full-file scan. Comfortably above the JSONL guard's line cap,
133
+ * so a capped result line still fits whole. */
134
+ export declare const INJECT_FEEDER_RESULT_TAIL_BYTES = 262144;
135
+ /** One in-guest delivery-ack exec: append the line, then poll the delivered
136
+ * counter until it covers this append (delivered), the runner dies, or the
137
+ * attempts run out (both: not delivered — the message stays owed). */
138
+ export declare const INJECT_ACK_POLL_ATTEMPTS = 30;
139
+ export declare const INJECT_ACK_POLL_SECONDS = "0.5";
140
+ export declare const INJECT_ACK_EXEC_TIMEOUT_MS = 25000;
141
+ /** The in-guest feeder fragment, prepended to the detached wrapper script.
142
+ * Runs inside the setsid session (group-kill reaps it) and keys its loop to
143
+ * the wrapper's own pid (`$$`), like the heartbeat subshell. Exported for
144
+ * tests. */
145
+ export declare function streamInputFeederFragment(paths: {
146
+ promptPath: string;
147
+ fifoPath: string;
148
+ inboxPath: string;
149
+ deliveredPath: string;
150
+ outPath: string;
151
+ }): string;
152
+ /** The one delivery-ack exec `injectUserMessage` runs: append the message
153
+ * line to the durable inbox, then wait for the feeder's delivered counter
154
+ * to cover it. Exit 0 = delivered into the CLI's stdin pre-result; 4 = the
155
+ * runner died first; 5 = not delivered within the ack window (feeder
156
+ * stopped on the result, or the guest is crawling) — both non-zero exits
157
+ * mean "leave the message owed". Exported for tests. */
158
+ export declare function injectAppendAndAckCommand(args: {
159
+ line: string;
160
+ target: number;
161
+ pid: number;
162
+ inboxPath: string;
163
+ deliveredPath: string;
164
+ }): string;
165
+ /** SIGTERM grace before escalation: attempts × sleep = 5s. */
166
+ export declare const REAP_TERM_WAIT_ATTEMPTS = 20;
167
+ /** Post-SIGKILL confirm window: attempts × sleep = 2s (a KILLed process only
168
+ * lingers as an unreaped zombie, which `kill -0` still sees — brief). */
169
+ export declare const REAP_KILL_WAIT_ATTEMPTS = 8;
170
+ export declare const REAP_POLL_SECONDS = "0.25";
171
+ /** The confirm exec's "still alive after SIGKILL" exit code (surfaced as a
172
+ * warn — the launch preflight is the backstop for whatever survived). */
173
+ export declare const REAP_STILL_ALIVE_EXIT = 7;
174
+ /** Ceiling on the whole kill-and-confirm exec (TERM 5s + KILL 2s + slack). */
175
+ export declare const REAP_EXEC_TIMEOUT_MS = 15000;
176
+ /** The one kill-and-confirm exec: TERM the group (setsid made `pid` the
177
+ * leader) and the leader itself, poll for exit, escalate to KILL, poll
178
+ * again. Exit 0 = confirmed dead; REAP_STILL_ALIVE_EXIT = it survived
179
+ * SIGKILL's confirm window. Exported for tests. */
180
+ export declare function reapKillAndConfirmCommand(pid: number): string;
181
+ /** Guest idle TTL for a resident with no new inbox line: hot window (180s)
182
+ * + margin, so the park gate's graceful `.end` normally wins and the
183
+ * watchdog only answers a dead server / lost record. */
184
+ export declare const RESIDENT_IDLE_TTL_S = 240;
185
+ /** Watchdog poll cadence (seconds). */
186
+ export declare const RESIDENT_WATCHDOG_POLL_S = 15;
187
+ /** Anchored prefix of the CLI's terminal result event line. Anchoring keeps
188
+ * the guest-side counts honest against tool output that merely CONTAINS
189
+ * the substring; a serialization-order change fails SAFE (parity check
190
+ * refuses adoption → cold launch — a latency cost, never correctness). */
191
+ export declare const RESIDENT_RESULT_LINE_PREFIX = "{\"type\":\"result\"";
192
+ /** One bounded exec printing the count of result lines at or past
193
+ * `fromByte` (0-based) in the durable .out. Always exits 0; stdout is the
194
+ * count (a missing file reads as 0). Hint-grade by design. */
195
+ export declare function residentResultCountCommand(outPath: string, fromByte?: number): string;
196
+ /** The resident feeder: `streamInputFeederFragment` with the result-break
197
+ * REPLACED by the end-file break — the ONLY guest-contract change the
198
+ * resident shape needs (verified live by the spec's probe: two messages,
199
+ * one process, one session file, graceful exit on `.end`). */
200
+ export declare function residentFeederFragment(paths: {
201
+ promptPath: string;
202
+ fifoPath: string;
203
+ inboxPath: string;
204
+ deliveredPath: string;
205
+ endPath: string;
206
+ }): string;
207
+ /** Sets `ac_idle` (1 = between turns: every delivered turn has its result
208
+ * and the inbox is fully fed). Embedded by the watchdog and the resident
209
+ * heartbeat gate — one idle predicate, stated once. */
210
+ export declare function residentIdleCheckFragment(paths: {
211
+ outPath: string;
212
+ inboxPath: string;
213
+ deliveredPath: string;
214
+ servedPath: string;
215
+ }): string;
216
+ /** Guest idle watchdog: reap the whole process group after
217
+ * RESIDENT_IDLE_TTL_S of CONTINUOUS idleness (any busy reading resets the
218
+ * clock — a long-running TURN is never reaped; the parity check reads
219
+ * busy mid-turn). The park gate's graceful `.end` normally wins; this is
220
+ * the orphan backstop for a dead server or a lost durable record.
221
+ *
222
+ * BACKGROUND TASKS ARE NOT IDLE (2026-08-19 forensics: a user's
223
+ * between-turns background search died to a harness teardown while its
224
+ * journal was still advancing). The turn-parity predicate alone reads a
225
+ * resident BETWEEN turns as idle even while harness background tasks it
226
+ * tracks are hard at work — and this watchdog's group-kill would take
227
+ * those children with the tree. So idle additionally requires the CLI's
228
+ * own task journal (`$HOME/.claude/tasks`) to be QUIET: any file touched
229
+ * within RUNNER_BUSY_TASK_FRESH_MINUTES resets the clock — the exact
230
+ * journal-freshness license the server's park gate honors
231
+ * (guestBusyProbeCommand). A stalled task goes stale within the window
232
+ * and the TTL resumes on its own; liveness alone never counts (the
233
+ * 2026-08-17 wake-loop damper is a SERVER rule about waking — this is a
234
+ * guest rule about not killing, and journal advance is required, not mere
235
+ * process existence). */
236
+ export declare function residentIdleWatchdogFragment(paths: {
237
+ outPath: string;
238
+ inboxPath: string;
239
+ deliveredPath: string;
240
+ servedPath: string;
241
+ }): string;
242
+ /** Exit-event doorbell with the turnId read from a guest FILE at push time
243
+ * (a resident serves many turns; each adoption rewrites `<prompt>.turnid`
244
+ * so a mid-turn death rings the turn actually being served). Same
245
+ * validation posture as `exitEventPushCommand`; an empty turnid file
246
+ * pushes nothing. */
247
+ export declare function deferredExitEventPushCommand(notify: {
248
+ url: string;
249
+ tokenEnv: string;
250
+ }, turnIdFile: string): string;
33
251
  /** Provider deadline on the detached LAUNCH exec. The launch shell only
34
252
  * truncates the durable files, forks the detached runner (stdio fully
35
253
  * redirected — see the launch command), writes the pidfile, and echoes the
@@ -50,8 +268,50 @@ export declare const LAUNCH_PID_RECOVERY_DELAY_MS = 1000;
50
268
  * call in the turn path carries an explicit timeout so no SDK default can
51
269
  * decide a turn's fate. Exported for tests. */
52
270
  export declare const INSTALL_PROBE_TIMEOUT_MS = 30000;
271
+ /** Deadline on the on-demand CLI install itself (`spec.install`). Shared by
272
+ * this transport's ensureInstalled and the server's CloudTurnWorkflow launch
273
+ * path (turn-workflow/tailer.ts), which provisions the same way before a
274
+ * detached launch — the session images bake only `claude`, so codex/
275
+ * opencode/cursor/droid MUST be installable at launch on a fresh machine. */
276
+ export declare const CLI_INSTALL_TIMEOUT_MS = 300000;
53
277
  /** Single-quote a value for safe interpolation into a `sh -c` command line. */
54
278
  export declare function shellQuote(value: string): string;
279
+ /** $HOME-relative path of the platform-managed session env file. The server
280
+ * writes it at sandbox acquire (session secrets → `export KEY='…'` lines);
281
+ * the detached turn launch sources it fresh EVERY turn, so a secret set or
282
+ * rotated between turns lands on the next turn with no VM recycle. Shared
283
+ * constant so the writer (server) and the reader (this runtime) can never
284
+ * drift. */
285
+ export declare const SESSION_ENV_FILE_RELPATH = ".agent-compose/session-env.sh";
286
+ /** The `. $HOME/<file>;` fragment sourced ahead of the CLI command, or ""
287
+ * when no env file is configured. Errors are swallowed — a missing or
288
+ * unreadable file must never fail a turn. */
289
+ export declare function sessionEnvSourceFragment(relPath: string | undefined): string;
290
+ /** The per-turn model-credential source fragment (`RuntimeOptions.
291
+ * credEnvFile` — an ABSOLUTE guest path), or "" when none is configured.
292
+ * Sourced AFTER the session env file so the turn actor's credential wins.
293
+ * Same swallow-errors posture: a missing file must never fail a turn. */
294
+ export declare function credEnvSourceFragment(absPath: string | undefined): string;
295
+ /** Ceiling on ONE curl attempt (seconds — it is `curl -m`). Short: the file
296
+ * is the truth and a slow push must not keep the wrapper alive for long. */
297
+ export declare const EXIT_PUSH_ATTEMPT_TIMEOUT_SECONDS = 5;
298
+ /** Pause before the single retry. */
299
+ export declare const EXIT_PUSH_RETRY_DELAY_SECONDS = 1;
300
+ /**
301
+ * The sh fragment appended to the detached wrapper AFTER the sentinel
302
+ * `printf` — never before it, and never in a way that can fail the wrapper:
303
+ * - reads the credential from the guest env at push time (`$<tokenEnv>`;
304
+ * the token never appears in the script text, argv-visible command
305
+ * lines the server logs, or any log line);
306
+ * - silently skips when the credential or curl is absent;
307
+ * - two attempts max (`-m 5`, one retry after 1s), then gives up silently
308
+ * — all output discarded, exit status swallowed (`|| true`).
309
+ * `$ac_rc` is the wrapper-local capture of the guarded pipeline's exit code
310
+ * (the same value the sentinel line carries). Returns "" — push disabled —
311
+ * for any `tokenEnv`/`turnId` that is not a plain identifier/uuid shape, so
312
+ * hostile config can never become shell injection.
313
+ */
314
+ export declare function exitEventPushCommand(notify: TurnExitNotify): string;
55
315
  /**
56
316
  * A UNIQUE per-turn prompt path. Uniqueness is load-bearing, not cosmetic:
57
317
  * E2B's envd cannot overwrite an existing non-root file in /tmp — it opens
@@ -129,13 +389,18 @@ export interface CliAgentSpec {
129
389
  /** Build the one-shot shell command for a turn. `promptPath` is a file in the
130
390
  * sandbox holding `promptPayload(prompt)`; `sessionId` continues a thread.
131
391
  * `effort` is present only when the caller configured a reasoning effort
132
- * AND the spec has a real knob for it (see `CliReasoningEffort`). */
392
+ * AND the spec has a real knob for it (see `CliReasoningEffort`).
393
+ * `streamInput` is true when the durable transport is feeding stdin as a
394
+ * live message stream (see `CliAgentSpec.streamInput`): `promptPath` is
395
+ * then the FIFO the feeder writes, and the CLI must be invoked in its
396
+ * stream-input mode (claude: `--input-format stream-json`). */
133
397
  buildCommand(args: {
134
398
  promptPath: string;
135
399
  sessionId?: string;
136
400
  model?: string;
137
401
  cwd?: string;
138
402
  effort?: CliReasoningEffort;
403
+ streamInput?: boolean;
139
404
  }): string;
140
405
  /** Map one parsed JSONL stdout event to `AgentMessage`s. The base emits
141
406
  * `init`/`done`/`error` lifecycle itself, so a spec maps only content +
@@ -150,6 +415,16 @@ export interface CliAgentSpec {
150
415
  * resume it (codex `thread.started.thread_id`, amp `session_id`).
151
416
  * LEGACY FALLBACK PATH (see `mapEvent`). */
152
417
  extractSessionId(parsed: Record<string, unknown>): string | undefined;
418
+ /** The runtime's thread persistence admits only ONE live writer per thread
419
+ * (codex: an OS advisory flock per thread id, held for the holding
420
+ * process's whole lifetime — codex-rs thread-store writer_lock.rs). A
421
+ * platform that wants a SUCCESSOR turn to resume the same thread must
422
+ * therefore KILL the predecessor's process tree first — a merely-detached
423
+ * live predecessor blocks every `thread/resume` with "already has an
424
+ * active writer" (the 2026-08-22 superseded-codex-turn incident). Absent
425
+ * ⇒ concurrent resume is safe (claude-code: append-only session JSONL)
426
+ * and a superseded runner may be detached alive as an asset. */
427
+ exclusiveSessionWriter?: boolean;
153
428
  /** ACP-mode invocation (the Zed `agent_servers` shape). When present, the
154
429
  * runner spawns the CLI in ACP agent mode and delegates the whole wire
155
430
  * protocol to `AcpClientPeer`; the legacy `promptPayload`/`buildCommand`/
@@ -160,6 +435,27 @@ export interface CliAgentSpec {
160
435
  args: string[];
161
436
  env?: Record<string, string>;
162
437
  };
438
+ /** Mid-turn STREAM-INPUT support (the delivery half of
439
+ * `injectUserMessage`). When present AND the durable detached transport
440
+ * is in play, the CLI's stdin is fed from a FIFO by an in-guest feeder:
441
+ * the prompt line first, then any lines appended to the turn's durable
442
+ * inbox file — so a steering-capable CLI (claude: queued user input is
443
+ * folded into the RUNNING turn at the next tool boundary; verified live
444
+ * against claude 2.1.233) receives user messages while the turn runs.
445
+ * The feeder stops at the CLI's terminal result line (a message that
446
+ * races the result is NOT forwarded — it stays owed) and closes the
447
+ * FIFO, which is what ends the CLI process (stream-input CLIs exit on
448
+ * stdin EOF, not after a result). Absent → the prompt file is the whole
449
+ * stdin, exactly as before. */
450
+ streamInput?: {
451
+ /** Serialise the opening prompt into ONE stream-input stdin line
452
+ * (claude: a stream-json user message). No trailing newline — the
453
+ * transport owns line framing. */
454
+ promptLine(prompt: string): string;
455
+ /** Serialise one mid-turn user message into ONE stdin line. No
456
+ * trailing newline. */
457
+ messageLine(text: string): string;
458
+ };
163
459
  }
164
460
  export declare class CliAgentRunner implements ModelExecutionContract {
165
461
  private readonly sandbox;
@@ -208,6 +504,55 @@ export declare class CliAgentRunner implements ModelExecutionContract {
208
504
  * exercising the real `sendMessageAcp` even while the production default is
209
505
  * dormant. Not a public API. */
210
506
  private acpTransportReady;
507
+ /** The CURRENT turn's durable-trio reader (set once the detached runner is
508
+ * launched, replaced by the next launch). Null = no durable transport is
509
+ * active for this runner (boot phase, ACP path, single-exec providers) —
510
+ * `probeTurnLiveness` then answers null and the executor keeps its own
511
+ * fallback probe. */
512
+ private turnProbe;
513
+ /** The CURRENT turn's mid-turn injection state (durable stream-input
514
+ * transport only). Null = no live stream-input turn — `injectUserMessage`
515
+ * answers "unsupported" and the caller leaves the message owed. `chain`
516
+ * serializes appends so each delivery targets a deterministic line count. */
517
+ private turnInject;
518
+ /** Deliver one user message INTO the live turn — see the contract doc
519
+ * (types/runtime.ts). Appends a stream-input line to the turn's durable
520
+ * inbox and waits for the in-guest feeder's delivered-counter ack; only
521
+ * an acked forward (pre-result, into the CLI's stdin) reports
522
+ * "delivered". Guest exit 5 (ack window exhausted with the runner still
523
+ * alive — a CLI that is not draining stdin mid-step) reports "pending":
524
+ * the appended line may still be read when the current step finishes,
525
+ * but it was NOT seen yet. Never throws. */
526
+ injectUserMessage(text: string): Promise<"delivered" | "pending" | "closed" | "unsupported">;
527
+ /** Durable three-valued liveness for the current turn — see the contract
528
+ * doc (types/runtime.ts). Never throws; never blocks past the probe's
529
+ * explicit exec timeout. */
530
+ probeTurnLiveness(): Promise<RunnerLivenessVerdict | null>;
531
+ /** Wakes the CURRENT tail-watchdog interval early. Armed only while the
532
+ * watchdog's wake timer is pending; null between arms / between turns. */
533
+ private wakeTailWatchdog;
534
+ /** A doorbell that rang while no watchdog wake was armed — consumed at the
535
+ * next arm (immediate wake), dropped at the start of a new turn. */
536
+ private nudgePending;
537
+ /** DOORBELL, NEVER A VERDICT (contract doc: types/runtime.ts). Wake the
538
+ * tail watchdog NOW so its normal verification pass — durable probe, then
539
+ * harvest-on-sentinel / honest no-sentinel death — runs immediately
540
+ * instead of at the next TAIL_WATCHDOG_INTERVAL_MS tick. Carries no
541
+ * information: a nudge for a live mid-turn runner probes alive and is a
542
+ * no-op; a spurious/duplicate nudge costs at most one probe exec. No-op
543
+ * when no durable watchdog is armed (boot phase, ACP path, single-exec
544
+ * transports, abandoned-streaming polling) — those phases already carry
545
+ * their own bounds. */
546
+ nudgeTurnProbe(): void;
547
+ /** RESUME HANDOFF, NEVER A KILL (contract doc: types/runtime.ts). Once
548
+ * set, abort/early-exit unwinds the transport WITHOUT reaping the
549
+ * detached guest tree and WITHOUT the opportunistic durable-file cleanup
550
+ * — the successor turn resumes the same guest session, and the park
551
+ * path's busy signal keeps reading the surviving heartbeat/busy files.
552
+ * One-way for this runner instance (a runner is per-turn on the cloud
553
+ * path). */
554
+ private guestDetached;
555
+ detachGuest(): void;
211
556
  constructor(sandbox: SandboxProvider, options: RuntimeOptions, spec: CliAgentSpec, configModel?: string | undefined, configEffort?: CliReasoningEffort | undefined);
212
557
  /** TEST-ONLY: flip the live-ACP readiness gate on for this runner instance so
213
558
  * the ACP lifecycle / fallback tests can exercise `sendMessageAcp` while the
@@ -33,6 +33,7 @@
33
33
  * sessions this is the gateway-minted session virtual key pointed at the
34
34
  * token-metering gateway's Anthropic passthrough (ADR-0039).
35
35
  */
36
+ import type { AgentMessageTaskNotification } from "../index.js";
36
37
  import { type CliAgentSpec, type CliReasoningEffort } from "./_cli-agent.js";
37
38
  /** Pinned Claude Code ACP adapter (Zed's npm shim — `claude` has no native
38
39
  * ACP mode). Pinned EXACT, not a range: the adapter's README warns of
@@ -40,6 +41,17 @@ import { type CliAgentSpec, type CliReasoningEffort } from "./_cli-agent.js";
40
41
  * bridge daemon (cli/src/bridge/acp.ts) imports this same pin so both
41
42
  * executors launch the identical adapter. Verified against 0.16.2. */
42
43
  export declare const CLAUDE_CODE_ACP_ADAPTER = "@zed-industries/claude-code-acp@0.16.2";
44
+ /** Parse every `<task-notification>` block out of one user-role text blob.
45
+ * Pure and tolerant over untrusted harness text: a block without a task id
46
+ * is skipped, absent fields stay absent, everything is clamped. Exported
47
+ * for tests. */
48
+ export declare function parseTaskNotifications(text: string, timestamp: string): AgentMessageTaskNotification[];
49
+ /** One `system`/`task_notification` stream-json event mapped onto the same
50
+ * structured message the XML parse produces, or null when the event names
51
+ * no task id. Pure and tolerant over untrusted harness JSON: absent fields
52
+ * stay absent, everything is clamped; `output_file` (internal plumbing) is
53
+ * deliberately not forwarded. Exported for tests. */
54
+ export declare function parseSystemTaskNotification(p: Record<string, unknown>, timestamp: string): AgentMessageTaskNotification | null;
43
55
  /** Claude Code's real reasoning knob is its own `--effort <level>` flag
44
56
  * (low|medium|high|xhigh|max — verified against `claude -p --help`). The
45
57
  * CliReasoningEffort union IS the CLI's vocabulary, so the level rides the
@@ -16,6 +16,14 @@ import { type CliAgentSpec, type CliReasoningEffort } from "./_cli-agent.js";
16
16
  /** True when an error-item message is a known codex advisory (log-level
17
17
  * noise), not a real error. Exported for tests. */
18
18
  export declare function isCodexAdvisoryNoise(text: string): boolean;
19
+ /** Bounded wait for a dying previous writer: attempts × sleep = 5s, matching
20
+ * the teardown's SIGTERM grace (sdk _cli-agent.ts REAP_TERM_WAIT_ATTEMPTS). */
21
+ export declare const CODEX_LOCK_WAIT_ATTEMPTS = 20;
22
+ export declare const CODEX_LOCK_WAIT_SECONDS = "0.25";
23
+ /** The sh fragment prepended to a resume launch. `waitAttempts` is a test
24
+ * seam (the live-holder test must not sleep 5s); production callers take
25
+ * the default. Exported for tests. */
26
+ export declare function codexWriterLockPreflight(threadId: string, waitAttempts?: number): string;
19
27
  /** Exported for the fixture-based parity tests (ADR-0020): the legacy JSONL
20
28
  * `mapEvent` is the golden the ACP normaliser is asserted equal to. Not part
21
29
  * of the public runtime surface — `createCodexRuntime` stays the entry point. */