@agent-compose/sdk 0.8.1 → 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 (48) 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 +189 -15
  9. package/dist/display.d.ts +17 -0
  10. package/dist/index.d.ts +14 -5
  11. package/dist/index.js +1625 -120
  12. package/dist/runtimes/_cli-agent.d.ts +372 -2
  13. package/dist/runtimes/claude-code.d.ts +12 -0
  14. package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
  15. package/dist/runtimes/codex.d.ts +8 -0
  16. package/dist/runtimes/openai-desktop.js +1555 -120
  17. package/dist/runtimes/session-env.test.d.ts +14 -0
  18. package/dist/sandbox/sizes.d.ts +120 -30
  19. package/dist/sandbox.d.ts +1 -1
  20. package/dist/types/api-conversations.d.ts +476 -1
  21. package/dist/types/api-factory.d.ts +164 -7
  22. package/dist/types/api-runs.d.ts +23 -1
  23. package/dist/types/protocol.d.ts +32 -1
  24. package/dist/types/runtime.d.ts +120 -0
  25. package/dist/types/workflow-metadata.d.ts +6 -5
  26. package/package.json +1 -1
  27. package/src/agent/agent-context.ts +128 -28
  28. package/src/agent/agent-loop.ts +10 -3
  29. package/src/agent/desktop-open.ts +418 -0
  30. package/src/agent/perf-sampler.ts +202 -0
  31. package/src/agent/services-manifest.ts +356 -0
  32. package/src/agent/services-restore.ts +195 -0
  33. package/src/client.ts +384 -32
  34. package/src/display.ts +44 -1
  35. package/src/index.ts +74 -7
  36. package/src/runtimes/_cli-agent.ts +1160 -67
  37. package/src/runtimes/claude-code.ts +187 -12
  38. package/src/runtimes/codex.ts +65 -2
  39. package/src/sandbox/providers/e2b.ts +8 -4
  40. package/src/sandbox/providers/local.ts +16 -4
  41. package/src/sandbox/sizes.ts +127 -44
  42. package/src/sandbox.ts +8 -0
  43. package/src/types/api-conversations.ts +461 -2
  44. package/src/types/api-factory.ts +165 -7
  45. package/src/types/api-runs.ts +25 -1
  46. package/src/types/protocol.ts +30 -1
  47. package/src/types/runtime.ts +122 -0
  48. package/src/types/workflow-metadata.ts +6 -5
@@ -192,6 +192,48 @@ export interface FactoryFileSearchResult {
192
192
  folders_truncated?: boolean;
193
193
  }
194
194
 
195
+ // ── Factory-file listing (`GET /files` — live index or browse-at-head) ─────
196
+
197
+ /** One file row of `GET /factories/:slug/files` — the flat recursive
198
+ * listing. Same row shape live and at a pinned head; rows arrive
199
+ * camelCase on the wire (mirrors `FactoryFileSearchRow`). */
200
+ export interface FactoryFileListRow {
201
+ id: string;
202
+ path: string;
203
+ sizeBytes: number;
204
+ contentType: string | null;
205
+ contentHash: string;
206
+ deletedAt: string | null;
207
+ createdAt: string;
208
+ updatedAt: string;
209
+ }
210
+
211
+ export interface ListFactoryFilesOptions {
212
+ factorySlug?: string;
213
+ /** Only paths under this prefix. */
214
+ prefix?: string;
215
+ /** Path cursor from a prior page's `nextCursor`. */
216
+ cursor?: string;
217
+ limit?: number;
218
+ /** List AS OF a drive branch (e.g. a session's `session-<uuid>`). */
219
+ branch?: string;
220
+ /** Browse-at-head: a retained 64-hex commit of the MAIN drive (or, with
221
+ * `branch`, a pin of that graph branch). The server answers 410
222
+ * `at_unavailable` for a garbage-collected/unknown head. */
223
+ at?: string;
224
+ }
225
+
226
+ /** One page of `GET /factories/:slug/files`. */
227
+ export interface FactoryFileListPage {
228
+ data: FactoryFileListRow[];
229
+ hasMore: boolean;
230
+ /** Pass as `cursor` to fetch the next page; null on the last page. */
231
+ nextCursor: string | null;
232
+ /** The pinned head this page was read at — non-null exactly when the
233
+ * request carried `at`. `committedAt` is RFC3339 or null (unknown). */
234
+ at: { head: string; committedAt: string | null } | null;
235
+ }
236
+
195
237
  export interface FactoryFileWriteResult {
196
238
  path: string;
197
239
  contentHash: string;
@@ -237,6 +279,28 @@ export interface FactoryRow {
237
279
  updatedAt: string;
238
280
  }
239
281
 
282
+ /** GET /factories/:slug/perf-summary — team-level sandbox perf aggregates
283
+ * over a window (session_perf_rollups). Aggregate-only by design: sessions
284
+ * are member-gated, so no conversation ids appear here; per-session detail
285
+ * is the member-gated perf-history surface. */
286
+ export interface FactoryPerfSummary {
287
+ /** The aggregated window, ISO 8601. */
288
+ from: string;
289
+ to: string;
290
+ /** Distinct sessions with any rollup bucket in the window. */
291
+ sessions: number;
292
+ /** Rollup buckets (15-min) in the window. */
293
+ buckets: number;
294
+ /** Guest samples folded into those buckets. */
295
+ samples: number;
296
+ cpu_busy_p95_max: number | null;
297
+ mem_used_p95_max: number | null;
298
+ disk_used_p95_max: number | null;
299
+ load1_max: number | null;
300
+ /** Total seconds any session sat ≥90% on a resource, summed. */
301
+ saturated_seconds: number;
302
+ }
303
+
240
304
  export interface CreateFactoryInput {
241
305
  slug: string;
242
306
  name: string;
@@ -285,6 +349,48 @@ export interface SecretListEntry {
285
349
  updatedAt: string;
286
350
  }
287
351
 
352
+ /** One SESSION secret on the wire (metadata only — values are write-only).
353
+ * `kind` 'value' = a session-own secret; 'factory' = an attachment by name
354
+ * to the session factory's secret tier. `ownerUserId` is the user who set
355
+ * it — only the owner or a team admin may replace/remove it. */
356
+ export interface SessionSecretEntry {
357
+ secretKey: string;
358
+ kind: "value" | "factory";
359
+ ownerUserId: string;
360
+ /** Delivery mode (vault v2): 'injected' = the session env file;
361
+ * 'brokered' = an egress-ruleset header — the value never enters the
362
+ * sandbox (call the host WITHOUT auth headers; the platform adds them). */
363
+ delivery: "injected" | "brokered";
364
+ /** Brokered entries: the API host the header rides on. */
365
+ brokerHost: string | null;
366
+ /** Non-null = an ephemeral use-and-scrub copy, and when its TTL lapses. */
367
+ ephemeralExpiresAt: string | null;
368
+ createdAt: string;
369
+ updatedAt: string;
370
+ }
371
+
372
+ /** One entry of a session-secret set: a literal value, or a factory
373
+ * attachment by name (`source: "factory"`). */
374
+ export type SessionSecretInput =
375
+ | { key: string; value: string }
376
+ | { key: string; source: "factory" };
377
+
378
+ /** A freshly minted vault link (session secret request). `url` is the
379
+ * single-use page a session writer opens to provide the named values. */
380
+ export interface SessionSecretRequestCreated {
381
+ requestId: string;
382
+ url: string;
383
+ expiresAt: string;
384
+ }
385
+
386
+ /** A vault link's polled status. */
387
+ export interface SessionSecretRequestStatus {
388
+ requestId: string;
389
+ status: "pending" | "fulfilled" | "cancelled" | "expired";
390
+ keys: string[];
391
+ expiresAt: string;
392
+ }
393
+
288
394
  export interface CreateApiKeyInput {
289
395
  name?: string;
290
396
  scopes?: string[];
@@ -313,6 +419,27 @@ export interface ApiKey {
313
419
  * shown once at creation, never retrievable again. */
314
420
  export interface ApiKeyCreated extends ApiKey { key: string }
315
421
 
422
+ /** Options for `GET /api-keys` — the list is bounded and keyset-paginated
423
+ * (every run step / session boot mints a key row, so "all keys ever" is
424
+ * unbounded by construction). */
425
+ export interface ListApiKeysOptions {
426
+ /** Page size, 1–200 (server default 100). */
427
+ limit?: number;
428
+ /** `active` = unrevoked + unexpired only; `all` (default) includes
429
+ * revoked/expired history. */
430
+ status?: "active" | "all";
431
+ /** Opaque cursor from a previous page's `nextCursor`. */
432
+ cursor?: string;
433
+ }
434
+
435
+ /** One page of `GET /api-keys`. */
436
+ export interface ApiKeyPage {
437
+ data: ApiKey[];
438
+ hasMore: boolean;
439
+ /** Pass as `cursor` to fetch the next page; null on the last page. */
440
+ nextCursor: string | null;
441
+ }
442
+
316
443
  /** Single rollup row from `GET /api/v1/usage`. */
317
444
  export interface UsageRollupRow {
318
445
  eventType: string;
@@ -333,7 +460,10 @@ export interface UsageResponse {
333
460
  // ── GitHub-linked drive directories (ADR-0030 P1) ───────────────────────────
334
461
 
335
462
  /** One drive-directory ⇄ GitHub-repo link (`/factories/:slug/repo-links`).
336
- * The webhook secret ref is operator plumbing and never on the wire. */
463
+ * Multi-branch model: ONE link row per (repo, branch); sibling branches
464
+ * of a repo occupy sibling `base@<branch>` placements. Event delivery is
465
+ * app-level (the platform GitHub App's own webhook) or polling — there
466
+ * is no per-repo webhook setup (retired 2026-08-18). */
337
467
  export interface DriveRepoLink {
338
468
  id: string;
339
469
  factoryId: string;
@@ -343,26 +473,54 @@ export interface DriveRepoLink {
343
473
  provider: string;
344
474
  /** "org/repo". */
345
475
  repoFullName: string;
346
- /** The GitHub branch drive `main` syncs with. */
476
+ /** The GitHub branch this link's placement syncs with. */
347
477
  trackedBranch: string;
348
478
  connectorGrantId: string | null;
349
479
  /** v1 links are always 'write' (the round trip is the feature). */
350
480
  access: "read" | "write";
481
+ /** HOW events reach this link. "app": the platform GitHub App's own
482
+ * registered webhook delivers every installed repo's events (zero
483
+ * per-repo setup). "polling": no app webhook configured — the sweep
484
+ * alone. */
485
+ eventDelivery: "app" | "polling";
486
+ /** When an app-signed GitHub delivery last PROVED event delivery
487
+ * reaches this link; null = unproven. */
488
+ webhookVerifiedAt: string | null;
489
+ /** Two-way sync: drive edits under the prefix push back to the tracked
490
+ * branch. ON by default for new links. */
491
+ pushOutEnabled: boolean;
492
+ /** Follow-all mode (stamped identically on every sibling link of one
493
+ * repo): pushes to NEW branches auto-link them; GitHub branch deletes
494
+ * auto-unlink. False = explicit selection. */
495
+ followAllBranches: boolean;
496
+ /** Branches follow-all could NOT link (with the reason) — stamped on
497
+ * the repo's PRIMARY link row only; empty everywhere else. */
498
+ autoLinkSkips: Array<{ branch: string; reason: string; at: string }>;
351
499
  lastSyncedGitSha: string | null;
352
500
  lastSyncedAcgCommit: string | null;
353
- syncState: "idle" | "syncing" | "diverged" | "reauth_required";
354
- visibility: "team" | "restricted";
501
+ syncState: "idle" | "syncing" | "pushing" | "error" | "conflict" | "diverged" | "reauth_required";
502
+ /** Human-readable detail when syncState is 'error' or 'conflict'. */
503
+ syncError: string | null;
355
504
  createdBy: string | null;
356
505
  lastSyncedAt: string | null;
357
506
  createdAt: string;
358
507
  }
359
508
 
360
509
  /** Input for `POST /factories/:slug/repo-links`. Requires the drive to be
361
- * graph-authoritative (ADR-0058 Phase 4 promoted) — 409 otherwise. */
510
+ * graph-authoritative (ADR-0058 Phase 4 promoted) — 409 otherwise.
511
+ * At most one of `followAllBranches` / `trackedBranches`; omitting BOTH
512
+ * is the follow-all default. Follow-all links EVERY
513
+ * branch — default branch primary at the plain `dirPrefix` — and keeps
514
+ * following live (new branches auto-link on push, deleted branches
515
+ * auto-unlink; capped at 100 branches). Explicit `trackedBranches`:
516
+ * index 0 is the PRIMARY and keeps the plain placement; every additional
517
+ * branch lands at the sibling `dirPrefix@<sanitized-branch>` placement. */
362
518
  export interface CreateDriveRepoLinkInput {
363
519
  dirPrefix: string;
364
520
  repoFullName: string;
365
- trackedBranch: string;
521
+ trackedBranches?: string[];
522
+ followAllBranches?: boolean;
366
523
  connectorGrantId: string;
367
- visibility?: "team" | "restricted";
524
+ /** Two-way sync ("push-out") — defaults to TRUE for new links. */
525
+ pushOut?: boolean;
368
526
  }
@@ -281,6 +281,29 @@ export interface RunFundingResponse {
281
281
  };
282
282
  }
283
283
 
284
+ /** One step's attested platform token totals — `GET
285
+ * /workflows/:id/step-usage`. Derived server-side from the spend ledger
286
+ * by step time window (steps run serially, so every gateway call falls in
287
+ * exactly one). Steps with no ledger rows are absent from the list. */
288
+ export interface RunStepUsage {
289
+ stepIndex: number;
290
+ costUsd: number;
291
+ promptTokens: number;
292
+ completionTokens: number;
293
+ cacheReadTokens: number;
294
+ cacheCreationTokens: number;
295
+ calls: number;
296
+ }
297
+
298
+ /** `GET /workflows/:id/step-usage`. Empty `steps` is an honest answer: a
299
+ * legacy run with no ledger rows, or a run funded outside the platform
300
+ * lane (that money is never metered — ADR-0048). */
301
+ export interface RunStepUsageResponse {
302
+ object: "run.step_usage";
303
+ runId: string;
304
+ steps: RunStepUsage[];
305
+ }
306
+
284
307
  /** One row from the factory runs list (`GET /factories/:slug/runs`) — the
285
308
  * summary shape, a strict subset of what the route returns. */
286
309
  export interface RunListEntry {
@@ -303,7 +326,8 @@ export interface ListRunsOptions {
303
326
  factorySlug?: string;
304
327
  /** Substring match on the registered workflow name (`metadata._workflow`). */
305
328
  workflow?: string;
306
- /** Substring match across title / task title / branch / run id. */
329
+ /** Substring match across title / task title / branch — or an exact run
330
+ * id (full UUID). */
307
331
  search?: string;
308
332
  outcome?: string;
309
333
  sort?: "newest" | "oldest" | "fastest" | "slowest";
@@ -119,6 +119,34 @@ export interface AgentMessagePlan extends AgentMessageBase {
119
119
  }[];
120
120
  }
121
121
 
122
+ /** A harness `<task-notification>` announcing a BACKGROUND task stopped —
123
+ * an async Agent spawn finishing, a background command exiting, a workflow
124
+ * completing. claude-code injects these as user-role text blocks; the
125
+ * normaliser parses them into structure so downstream can complete the
126
+ * spawning call's card (status, final report, usage) instead of dropping
127
+ * the only completion evidence a background subagent ever emits. Internal
128
+ * plumbing in the raw block (output-file paths, resume hints) is
129
+ * deliberately NOT forwarded — renderers must never see it. Additive
130
+ * kind: existing producers never emit it. */
131
+ export interface AgentMessageTaskNotification extends AgentMessageBase {
132
+ type: "task_notification";
133
+ /** The harness's background task id (`<task-id>`). */
134
+ taskId: string;
135
+ /** The SPAWNING tool_use id (`<tool-use-id>`) when the notification names
136
+ * one — the correlation key back to the Agent/Task call. */
137
+ toolUseId?: string;
138
+ /** Terminal status word — "completed" | "failed" | "stopped" | "killed"
139
+ * (verbatim from the harness; "finished" when the block carried none). */
140
+ status: string;
141
+ /** One-line what-happened ("Agent \"…\" finished"), collapsed + clamped. */
142
+ summary: string;
143
+ /** The task's final report (`<result>`), entity-unescaped and clamped.
144
+ * Absent when the notification carried none. */
145
+ report?: string;
146
+ /** The completion's usage block, when carried. */
147
+ usage?: { tokens?: number; toolUses?: number; durationMs?: number };
148
+ }
149
+
122
150
  export type AgentMessage =
123
151
  | AgentMessageInit
124
152
  | AgentMessageText
@@ -130,7 +158,8 @@ export type AgentMessage =
130
158
  | AgentMessageError
131
159
  | AgentMessageUsage
132
160
  | AgentMessageUsageDelta
133
- | AgentMessagePlan;
161
+ | AgentMessagePlan
162
+ | AgentMessageTaskNotification;
134
163
 
135
164
  /** Status block the agent emits to signal iteration completion or blockers. */
136
165
  export interface AgentStatus {
@@ -15,6 +15,34 @@ export interface McpServerConfig {
15
15
  env?: Record<string, string>;
16
16
  }
17
17
 
18
+ /**
19
+ * Exit-event push (the completion DOORBELL, v0.10.43). When set, the durable
20
+ * detached transport's in-guest wrapper fires ONE best-effort HTTP POST
21
+ * announcing `{ turnId, exitCode }` immediately AFTER the exit sentinel is
22
+ * durably written — so the server can verify-and-harvest at event latency
23
+ * instead of the watchdog's poll cadence.
24
+ *
25
+ * THE PUSH IS A DOORBELL, NEVER A VERDICT: the durable file stays the only
26
+ * truth; the push's arrival triggers verification against it, its absence
27
+ * means nothing (the poll ladder is the unchanged backstop), and a lost /
28
+ * duplicate / spoofed push must be harmless. Accordingly the wrapper never
29
+ * blocks sentinel-writing on the push (sentinel first, push after; failures
30
+ * are invisible to the runner lifecycle).
31
+ *
32
+ * Auth: `tokenEnv` NAMES a guest env var (e.g. the cloud session's
33
+ * `AGENT_COMPOSE_API_KEY`) — the wrapper reads it at push time, so the
34
+ * credential never appears in the generated script text or any log.
35
+ */
36
+ export interface TurnExitNotify {
37
+ /** Absolute URL of the server's turn-exit-event endpoint. */
38
+ url: string;
39
+ /** The bridge turn id this runner executes (rides the POST body). */
40
+ turnId: string;
41
+ /** Guest env var holding the bearer credential. A name that is not a
42
+ * plain env identifier disables the push (never risks shell injection). */
43
+ tokenEnv: string;
44
+ }
45
+
18
46
  /** Options passed to a runtime when creating a ModelExecutionContract. */
19
47
  export interface RuntimeOptions {
20
48
  allowedTools?: string[];
@@ -47,8 +75,35 @@ export interface RuntimeOptions {
47
75
  pause?: BoundaryPauseFn;
48
76
  /** Optional JSON schema for runtimes with native structured-output support. */
49
77
  outputFormat?: { type: "json_schema"; schema: Record<string, unknown> };
78
+ /** Exit-event doorbell config (cloud sessions) — see `TurnExitNotify`.
79
+ * Absent ⇒ no push; the durable transport behaves exactly as before. */
80
+ turnExitNotify?: TurnExitNotify;
81
+ /** $HOME-relative path of a shell env file the CLI process sources at
82
+ * launch (cloud sessions: the platform-managed session-secrets file,
83
+ * `SESSION_ENV_FILE_RELPATH`). Sourced fresh at EVERY turn launch, so a
84
+ * rewrite between turns lands on the next turn without a VM recycle.
85
+ * Absent ⇒ nothing is sourced (local/BYOM runs never read a user's own
86
+ * dotfiles by surprise). Must be a plain relative path — no quotes, no
87
+ * `..`; the runtime validates and drops anything else. */
88
+ sessionEnvFile?: string;
89
+ /** ABSOLUTE guest path of a per-turn model-credential shell fragment
90
+ * (cloud subscription sessions: the server ships the TURN ACTOR's own
91
+ * subscription credential there before dispatching the turn). Sourced at
92
+ * launch AFTER `sessionEnvFile`, so the turn's credential always wins —
93
+ * each turn runs on its actor's plan, never a baked or session-wide one.
94
+ * Absent ⇒ nothing extra is sourced. Must be a plain absolute path — no
95
+ * quotes, no `..`; the runtime validates and drops anything else. */
96
+ credEnvFile?: string;
50
97
  }
51
98
 
99
+ /** Three-valued liveness verdict for a runtime's CURRENT turn, read from
100
+ * DURABLE guest state (heartbeat file, stdout file, exit sentinel, pid) over
101
+ * a fresh short exec — never from the health of any long-lived stream.
102
+ * `probe-failed` (exec timeout, transport fault, unparseable output) is
103
+ * NEVER evidence of death: the caller tracks it separately and only many
104
+ * consecutive failures escalate to a sandbox-unreachable verdict. */
105
+ export type RunnerLivenessVerdict = "alive" | "dead" | "probe-failed";
106
+
52
107
  /** Runtime-normalized result of running pre-tool processors. */
53
108
  export type ToolCallGateResult =
54
109
  | { kind: "allow"; call: ToolCall }
@@ -110,6 +165,73 @@ export interface ModelExecutionContract {
110
165
  * `captureCheckpoint` is omitted, this is never called.
111
166
  */
112
167
  restoreCheckpoint?(blob: unknown): void;
168
+ /**
169
+ * Durable liveness probe for the CURRENT turn (2026-08-15 incident, turn
170
+ * 18dc5261: a silently wedged tail stream blinded the executor's
171
+ * process-grep probe to a turn that had FINISHED into its durable file —
172
+ * ten minutes of "no evidence" over a completed answer). Runtimes that run
173
+ * the detached durable transport implement this by reading the guest's
174
+ * durable trio — heartbeat file, stdout size, exit sentinel, pid — over a
175
+ * fresh short exec. The executor's evidence ticker prefers this over its
176
+ * generic process-grep probe.
177
+ *
178
+ * Contract: resolves fast (the implementation carries its own explicit
179
+ * exec timeout) and never throws — faults map to "probe-failed". Null
180
+ * means NO durable probe exists right now (boot phase before the runner
181
+ * launched, a transport without durable files): the caller keeps whatever
182
+ * fallback probe it already had; null is never a verdict.
183
+ */
184
+ probeTurnLiveness?(): Promise<RunnerLivenessVerdict | null>;
185
+ /**
186
+ * DOORBELL, NEVER A VERDICT (exit-event push, v0.10.43): wake the current
187
+ * turn's durable watchdog NOW so it runs its normal verification pass —
188
+ * durable probe, then harvest-on-sentinel / honest no-sentinel death —
189
+ * immediately instead of at the next poll interval. Carries NO information
190
+ * of its own: a nudge for a live turn verifies alive and is a no-op; a
191
+ * spurious / duplicate / stale nudge is harmless; the poll ladder is the
192
+ * unchanged backstop when no nudge arrives. Never throws; a nudge while no
193
+ * watchdog is armed is remembered for the next arm (or dropped at turn
194
+ * start — a new turn owes nothing to the previous turn's doorbell).
195
+ */
196
+ nudgeTurnProbe?(): void;
197
+ /**
198
+ * RESUME HANDOFF, NEVER A KILL (redispatch-carries-session, 2026-08-15
199
+ * forensics): mark the CURRENT turn's in-guest runner as handed off — the
200
+ * caller intends a successor turn to RESUME the same guest session, so
201
+ * abort/early-exit must unwind the transport WITHOUT reaping the detached
202
+ * process tree or deleting its durable files (heartbeat included — the
203
+ * park path's busy signal keeps reading it). Without this, "stop
204
+ * consuming" and "kill the guest tree" are fused on the abort seam, and a
205
+ * supersede-then-resume would destroy the very session it resumes.
206
+ * One-way for the runner instance; a runtime without a detachable guest
207
+ * (single-exec transports, ACP) simply omits the method — its abort
208
+ * semantics are unchanged and the caller falls back to kill semantics.
209
+ */
210
+ detachGuest?(): void;
211
+ /**
212
+ * Deliver ONE user message INTO the currently running turn (the cloud
213
+ * analogue of `inboxStream` for runtimes whose agent loop lives in a
214
+ * detached in-guest CLI). The message rides a durable per-turn inbox
215
+ * file; the guest-side feeder forwards it to the CLI's stdin, where a
216
+ * steering-capable harness folds it into the live turn at the next safe
217
+ * boundary. Four-valued and honest:
218
+ * - "delivered" — the guest feeder forwarded the message to the CLI's
219
+ * stdin BEFORE the turn's terminal result: the running turn saw it.
220
+ * Only this verdict may advance any answered watermark.
221
+ * - "pending" — the append landed in the durable inbox but the ack
222
+ * window exhausted before the feeder forwarded it (guest exit 5: the
223
+ * CLI is not draining stdin mid-step — a long single tool call — or
224
+ * the guest is crawling). NOT seen yet; leave it owed. The line stays
225
+ * appended, so the CLI may still read it when the current step
226
+ * finishes — callers may narrate that bound but must never advance a
227
+ * watermark on it.
228
+ * - "closed" — the turn ended (guest exit 4 / exec fault) before
229
+ * the message was consumed: it was NOT seen; leave it owed.
230
+ * - "unsupported" — no live stream-input turn exists right now (boot
231
+ * phase, a spec/transport without stream input, ACP path).
232
+ * Calls are serialized per turn; never throws.
233
+ */
234
+ injectUserMessage?(text: string): Promise<"delivered" | "pending" | "closed" | "unsupported">;
113
235
  sendMessage(opts: {
114
236
  prompt: string;
115
237
  sessionId?: string;
@@ -154,11 +154,12 @@ export interface InvokePolicy {
154
154
  * today; kept as its own object so finer controls (disk, gpu, …) can be
155
155
  * added later without reshaping `WorkflowMetadata`. */
156
156
  export interface SandboxResources {
157
- /** Machine hardware SKU — one of the `SandboxSize` vCPU strings
158
- * (`2vcpu-4gb` | `4vcpu-8gb` | `8vcpu-16gb` | `32vcpu-64gb`). Maps to
159
- * provider specs at create time (Vercel: 2 / 4 / 8 / 32 vCPU, 2048 MB RAM
160
- * per vCPU). Omit → the smallest SKU. E2B sizing is template-defined and
161
- * ignores this. */
157
+ /** Machine hardware SKU. The vocabulary is `SANDBOX_SIZES` in
158
+ * `sandbox/sizes.ts` — the single source; do not restate it here or
159
+ * anywhere else. Maps to provider specs at create time: Vercel takes the
160
+ * vCPU count and allocates RAM at 2048 MB/vCPU; E2B has no create-time
161
+ * cpu/mem knob at all, so the size selects a PRE-BUILT per-size template
162
+ * (`E2B_TEMPLATE_SIZES`). Omit → `DEFAULT_SANDBOX_SIZE`. */
162
163
  size?: SandboxSize;
163
164
  /** Sandbox provider this workflow's runs execute on — `"vercel"` or
164
165
  * `"e2b"`. Optional and additive: omit and the run resolves to the