@agent-compose/sdk 0.8.0 → 0.8.2

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 (44) hide show
  1. package/dist/agent/agent-context.d.ts +1 -1
  2. package/dist/agent/agent-loop.d.ts +8 -0
  3. package/dist/agent/run-agent.d.ts +4 -0
  4. package/dist/client.d.ts +77 -15
  5. package/dist/display.d.ts +16 -0
  6. package/dist/index.d.ts +6 -6
  7. package/dist/index.js +522 -123
  8. package/dist/runtimes/_cli-agent.d.ts +34 -7
  9. package/dist/runtimes/claude-code.d.ts +10 -8
  10. package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
  11. package/dist/runtimes/codex.d.ts +4 -1
  12. package/dist/runtimes/openai-desktop.js +507 -122
  13. package/dist/sandbox/sizes.d.ts +120 -30
  14. package/dist/sandbox.d.ts +1 -1
  15. package/dist/types/api-conversations.d.ts +198 -0
  16. package/dist/types/api-factory.d.ts +84 -7
  17. package/dist/types/api-runs.d.ts +48 -2
  18. package/dist/types/protocol.d.ts +8 -0
  19. package/dist/types/workflow-metadata.d.ts +14 -5
  20. package/dist/utils/bundler.d.ts +56 -0
  21. package/dist/workflow-steps/workflow.d.ts +7 -0
  22. package/dist/workflows/invoke-child.d.ts +18 -0
  23. package/dist/workflows/invoke-child.test.d.ts +9 -0
  24. package/package.json +2 -2
  25. package/src/agent/agent-context.ts +28 -17
  26. package/src/agent/agent-loop.ts +9 -0
  27. package/src/agent/run-agent.ts +5 -0
  28. package/src/client.ts +201 -30
  29. package/src/display.ts +61 -15
  30. package/src/index.ts +22 -9
  31. package/src/runtimes/_cli-agent.ts +302 -63
  32. package/src/runtimes/claude-code.ts +25 -15
  33. package/src/runtimes/codex.ts +19 -5
  34. package/src/sandbox/providers/e2b.ts +8 -4
  35. package/src/sandbox/sizes.ts +127 -44
  36. package/src/sandbox.ts +8 -0
  37. package/src/types/api-conversations.ts +180 -0
  38. package/src/types/api-factory.ts +89 -7
  39. package/src/types/api-runs.ts +50 -2
  40. package/src/types/protocol.ts +8 -0
  41. package/src/types/workflow-metadata.ts +15 -5
  42. package/src/utils/bundler.ts +213 -3
  43. package/src/workflow-steps/workflow.ts +7 -0
  44. package/src/workflows/invoke-child.ts +47 -11
@@ -1,19 +1,78 @@
1
1
  /**
2
- * Sandbox machine sizes + the E2B template aliases derived from them.
2
+ * Sandbox machine sizes — THE single source of the size vocabulary.
3
3
  *
4
- * A coarse hardware knob that maps to provider machine specs at create time:
5
- * Vercel honours it natively via `resources.vcpus`; E2B sizing is baked into
6
- * the template, so on E2B a size resolves to a pre-built per-size template.
4
+ * Everything that names a size (the server's `SANDBOX_DEFAULT_SIZE` env enum,
5
+ * the register/invoke zod schemas, the run + session row types, the CLI's
6
+ * `--size` flag, the dashboard pickers via `GET /v1/sandbox-sizes`) derives
7
+ * from `SANDBOX_SIZES` / `SandboxSize` here. Adding a size is a ONE-LINE edit
8
+ * to `SANDBOX_MACHINES`: the type, the enums, the E2B build matrix and the
9
+ * pickers all follow. Do NOT re-declare the union inline anywhere.
10
+ *
11
+ * A size is a coarse hardware knob that maps to provider machine specs:
12
+ * Vercel honours it natively via `resources.vcpus`; E2B sizing is BAKED INTO
13
+ * THE TEMPLATE (e2b 2.30.5 has no create-time cpu/mem knob — `NewSandbox`
14
+ * carries only `templateID`), so on E2B a size resolves to a pre-built
15
+ * per-size template.
7
16
  */
8
- /** Sandbox hardware SKU. Named for the actual machine spec (vCPU + RAM) rather
9
- * than abstract t-shirt sizes. Memory is always 2048 MB per vCPU:
10
- * 2vcpu-4gb = 2 vCPU / 4 GiB (Vercel's own default machine)
11
- * 4vcpu-8gb = 4 vCPU / 8 GiB
12
- * 8vcpu-16gb = 8 vCPU / 16 GiB (per-sandbox ceiling on STANDARD accounts —
13
- * probed live: 16 & 32 vCPU 400 on dev)
14
- * 32vcpu-64gb = 32 vCPU / 64 GiB (ENTERPRISE ONLY — standard accounts reject >8 vCPU) */
15
- export type SandboxSize = "2vcpu-4gb" | "4vcpu-8gb" | "8vcpu-16gb" | "32vcpu-64gb";
16
- /** SKU → Vercel vCPU count (RAM follows at 2048 MB/vCPU). */
17
+ /** Every sandbox hardware SKU, with its real machine spec. Named for the
18
+ * machine (vCPU + RAM) rather than abstract t-shirt sizes, so a size can
19
+ * never quietly mean something different than it says.
20
+ *
21
+ * RAM is 2048 MB/vCPU everywhere EXCEPT `8vcpu-8gb`, which exists because
22
+ * E2B caps a sandbox at 8 vCPU / 8192 MB (e2b.dev/docs/billing: Hobby and
23
+ * Pro both "8 vCPU / 8 GB", raised only by arrangement): 8 vCPU at the 2 GB
24
+ * rule would need 16 GiB and cannot be built. `8vcpu-8gb` is the CPU ceiling
25
+ * at the memory ceiling — the only way to get 8 cores on E2B today, and a
26
+ * spec already proven bakeable by the devbox (`E2B_DEVBOX_SPEC`).
27
+ *
28
+ * Adding an entry here automatically: widens `SandboxSize`, widens every
29
+ * derived enum, and — if it fits under the E2B caps — adds it to
30
+ * `E2B_TEMPLATE_SIZES`, which is what `infra/e2b-template/build.ts` and the
31
+ * `sandbox-images` CI job loop over. Two templates get baked per size, so
32
+ * the matrix is not free; see that workflow's header. */
33
+ export declare const SANDBOX_MACHINES: {
34
+ /** 1 vCPU / 2 GiB — the cheap floor. Plenty for a terminal session or a
35
+ * shell-shaped agent; tight for a big `bun install` or a browser. */
36
+ readonly "1vcpu-2gb": {
37
+ readonly vcpus: 1;
38
+ readonly memoryMB: 2048;
39
+ };
40
+ /** 2 vCPU / 4 GiB — the default (Vercel's own default machine too). */
41
+ readonly "2vcpu-4gb": {
42
+ readonly vcpus: 2;
43
+ readonly memoryMB: 4096;
44
+ };
45
+ /** 4 vCPU / 8 GiB — comfortable for builds and multi-tool agent turns. */
46
+ readonly "4vcpu-8gb": {
47
+ readonly vcpus: 4;
48
+ readonly memoryMB: 8192;
49
+ };
50
+ /** 8 vCPU / 8 GiB — E2B's per-sandbox CEILING (cores maxed at the memory
51
+ * cap). NOT expressible on Vercel, whose RAM follows vCPUs at 2 GB each. */
52
+ readonly "8vcpu-8gb": {
53
+ readonly vcpus: 8;
54
+ readonly memoryMB: 8192;
55
+ };
56
+ /** 8 vCPU / 16 GiB — Vercel only; exceeds E2B's 8 GiB memory cap. */
57
+ readonly "8vcpu-16gb": {
58
+ readonly vcpus: 8;
59
+ readonly memoryMB: 16384;
60
+ };
61
+ /** 32 vCPU / 64 GiB — Vercel Enterprise only; far past every E2B cap. */
62
+ readonly "32vcpu-64gb": {
63
+ readonly vcpus: 32;
64
+ readonly memoryMB: 65536;
65
+ };
66
+ };
67
+ /** Sandbox hardware SKU. Derived from `SANDBOX_MACHINES` — never re-spelled
68
+ * as an inline union. */
69
+ export type SandboxSize = keyof typeof SANDBOX_MACHINES;
70
+ /** The vocabulary as an ordered, smallest-first array — the shape zod
71
+ * (`z.enum`), the CLI's `--size` validation, and the wire catalogue want.
72
+ * Ordering is the pickers' display order, so keep it ascending. */
73
+ export declare const SANDBOX_SIZES: readonly [SandboxSize, ...SandboxSize[]];
74
+ /** SKU → Vercel vCPU count (Vercel's RAM follows automatically at 2048
75
+ * MB/vCPU — which is why `isVercelSupportedSize` exists). */
17
76
  export declare const SANDBOX_VCPUS: Record<SandboxSize, number>;
18
77
  /** SDK fallback size when neither the caller nor the deployment specifies one.
19
78
  * Deliberately conservative — the OPERATIONAL default is the server's
@@ -21,30 +80,61 @@ export declare const SANDBOX_VCPUS: Record<SandboxSize, number>;
21
80
  * small matters because Vercel rate-limits creation by vCPUs-per-window
22
81
  * (`api-sandboxes-vcpus-creation`); a large default 429s bursty/simultaneous
23
82
  * creates. Workloads that need more RAM/CPU declare `resources.size` on the
24
- * workflow rather than inflating the default for everyone. */
83
+ * workflow rather than inflating the default for everyone.
84
+ *
85
+ * NOT `1vcpu-2gb`: the floor is an opt-IN for cheap sessions, not a quiet
86
+ * downgrade of every existing run's machine. */
25
87
  export declare const DEFAULT_SANDBOX_SIZE: SandboxSize;
26
- /** The E2B sizes we pre-build a template for. E2B sizing is template-baked
27
- * (no per-create cpu/mem knob), so honouring `resources.size` on E2B means
28
- * ONE pre-built template per size. `32vcpu-64gb` is absent (E2B has no
29
- * >8-vCPU equivalent). `8vcpu-16gb` is also absent: it needs 16 GiB RAM, but
30
- * the E2B account caps memory at 8 GiB (`Template.build` 400s with
31
- * "Memory can't be higher than 8192 MiB"). Add it back here (and rebuild the
32
- * templates) only once the account's memory limit is raised. The register/
33
- * invoke guards reject an unsupported E2B size before it can reach here. */
88
+ /** SESSION default — deliberately one size up from the run default
89
+ * (2026-08-13): a session's sandbox carries the full desktop toolbelt
90
+ * (VS Code + Chromium + dockerd) plus the KasmVNC encoder at the 60fps
91
+ * cap, and that stack swap-thrashes on 4 GiB while the encoder starves on
92
+ * 2 shared vCPUs. Workflow runs keep DEFAULT_SANDBOX_SIZE — no desktop,
93
+ * no toolbelt weight. Sessions bill active time only (parked = storage),
94
+ * so the delta applies to active hours, not the fleet. */
95
+ export declare const SESSION_DEFAULT_SANDBOX_SIZE: SandboxSize;
96
+ /** E2B's per-sandbox ceiling on the plans we run (e2b.dev/docs/billing —
97
+ * Hobby: "8 vCPU / 8 GB"; Pro: the same, "8+" only by arrangement with
98
+ * support). Recorded live too: `Template.build` 400s with "Memory can't be
99
+ * higher than 8192 MiB" past the memory cap.
100
+ *
101
+ * These two numbers are the ONLY knob for which sizes get an E2B template —
102
+ * raise them after E2B raises the account limit and the build matrix (and
103
+ * therefore the session picker) widens on its own. */
104
+ export declare const E2B_MAX_VCPUS = 8;
105
+ export declare const E2B_MAX_MEMORY_MB = 8192;
106
+ /** The E2B sizes we pre-build a template for — DERIVED from the caps, not
107
+ * hand-listed, so a new `SANDBOX_MACHINES` entry can never be offered
108
+ * without a template or omitted despite fitting. E2B sizing is
109
+ * template-baked (no per-create cpu/mem knob), so honouring `resources.size`
110
+ * on E2B means ONE pre-built template per size; `infra/e2b-template/build.ts`
111
+ * loops exactly this list. The register / invoke / session-spawn / resize
112
+ * guards all reject an unsupported E2B size before it can reach a create. */
34
113
  export declare const E2B_TEMPLATE_SIZES: readonly SandboxSize[];
35
- /** Is `size` one E2B can be built/booted at? `32vcpu-64gb` (no >8-vCPU E2B
36
- * equivalent) and `8vcpu-16gb` (exceeds the account's 8 GiB memory cap) are
37
- * not — the guards lean on this so the "no E2B equivalent" decision lives in
38
- * exactly one place. */
114
+ /** Is `size` one E2B can be built/booted at? False for the sizes past E2B's
115
+ * 8 vCPU / 8 GiB ceiling (`8vcpu-16gb`, `32vcpu-64gb`) — those run on Vercel.
116
+ * The "no E2B equivalent" decision lives in exactly one place: the caps. */
39
117
  export declare function isE2bSupportedSize(size: SandboxSize): boolean;
40
- /** Machine spec for a SandboxSize, in the shape `Template.build` wants. RAM is
41
- * always 2048 MB/vCPU, matching the size name + Vercel parity
42
- * (`SANDBOX_VCPUS` × 2048). Used by `infra/e2b-template/build.ts` to stamp the
43
- * per-size base + agent-env templates. */
118
+ /** Vercel's fixed memory-per-vCPU ratio. Vercel takes `resources.vcpus` and
119
+ * allocates RAM itself at this rate — there is no independent memory knob. */
120
+ export declare const VERCEL_MEMORY_MB_PER_VCPU = 2048;
121
+ /** Is `size` expressible on Vercel? Only when its RAM matches what Vercel
122
+ * would allocate for that vCPU count — otherwise asking for it would hand
123
+ * the caller a machine that does not match the name (`8vcpu-8gb` would come
124
+ * back with 16 GiB). Vercel's own ceiling (32 vCPU, Enterprise) is a plan
125
+ * matter, not a shape matter, so it is not encoded here. */
126
+ export declare function isVercelSupportedSize(size: SandboxSize): boolean;
127
+ /** Machine spec for a SandboxSize, in the shape `Template.build` wants. Used
128
+ * by `infra/e2b-template/build.ts` to stamp the per-size base + agent-env
129
+ * templates. Reads the explicit table rather than deriving RAM from vCPUs —
130
+ * `8vcpu-8gb` is deliberately off the 2048 MB/vCPU line. */
44
131
  export declare function e2bMachineSpec(size: SandboxSize): {
45
132
  cpuCount: number;
46
133
  memoryMB: number;
47
134
  };
135
+ /** Human label for a size — "2 vCPU · 4 GB". The wire catalogue carries it so
136
+ * the dashboard never has to parse the id back into numbers. */
137
+ export declare function sandboxSizeLabel(size: SandboxSize): string;
48
138
  /** Stable E2B template ALIAS for the platform base at a given size
49
139
  * (`agent-compose-base-<size>`). Aliases — not snapshot ids — so the refs are
50
140
  * multi-account-clean: the same string resolves in any E2B account that built
package/dist/sandbox.d.ts CHANGED
@@ -14,7 +14,7 @@ export type { SandboxProvider, DesktopSandboxProvider, SandboxCommandRunOptions,
14
14
  export type { SandboxNetworkHeaderTransform, SandboxNetworkAllowRule, SandboxNetworkSubnetPolicy, SandboxNetworkPolicy, } from "./sandbox/network-policy.js";
15
15
  export { DOT_SEGMENT_PATH_RE2, toVercelNetworkPolicy, toE2bNetwork } from "./sandbox/network-policy.js";
16
16
  export type { SandboxSize } from "./sandbox/sizes.js";
17
- export { SANDBOX_VCPUS, DEFAULT_SANDBOX_SIZE, E2B_TEMPLATE_SIZES, isE2bSupportedSize, e2bMachineSpec, e2bBaseTemplate, e2bAgentEnvTemplate, isPlatformE2bTemplateAlias, } from "./sandbox/sizes.js";
17
+ export { SANDBOX_SIZES, SANDBOX_MACHINES, SANDBOX_VCPUS, DEFAULT_SANDBOX_SIZE, SESSION_DEFAULT_SANDBOX_SIZE, E2B_TEMPLATE_SIZES, E2B_MAX_VCPUS, E2B_MAX_MEMORY_MB, VERCEL_MEMORY_MB_PER_VCPU, isE2bSupportedSize, isVercelSupportedSize, sandboxSizeLabel, e2bMachineSpec, e2bBaseTemplate, e2bAgentEnvTemplate, isPlatformE2bTemplateAlias, } from "./sandbox/sizes.js";
18
18
  export { E2B_DEVBOX_TEMPLATE, E2B_DEVBOX_SPEC, E2B_DEVBOX_RECIPE_VERSION, e2bDevboxTemplateRef, } from "./sandbox/devbox.js";
19
19
  export { AGENT_COMPOSE_TAG } from "./sandbox/provider-def.js";
20
20
  export type { SandboxCreateOpts, OwnedSandbox } from "./sandbox/provider-def.js";
@@ -101,6 +101,9 @@ export interface ConversationMessageRow {
101
101
  reactions: Record<string, string[]>;
102
102
  replyCount: number;
103
103
  lastReplyAt: string | null;
104
+ /** When the author last edited the content; null/absent = never edited
105
+ * (absent on older servers). */
106
+ editedAt?: string | null;
104
107
  createdAt: string;
105
108
  /** Thread-root facepile (≤3) — present only on roots with replies. */
106
109
  replyAuthors?: Array<{
@@ -157,11 +160,31 @@ export interface ConversationDetail {
157
160
  name: string;
158
161
  description: string;
159
162
  }> | null;
163
+ /** Where this conversation's UNMERGED work lives: a cloud session writes
164
+ * its own private drive branch, and every drive read a client makes for
165
+ * THAT factory must carry `?branch=` or it resolves against `main`,
166
+ * where session-born files do not exist. Non-null only for a session
167
+ * with a drive branch and a resolvable factory; null/absent = main
168
+ * only (read loosely — older servers omit it). */
169
+ sessionDrive?: {
170
+ factorySlug: string;
171
+ branch: string;
172
+ } | null;
160
173
  viewerLastReadAt: string | null;
161
174
  /** The caller's role in this conversation (ADR-0045) — drives client
162
175
  * affordances only; the server remains the authority on every action.
163
176
  * Public channels report implicit `write` for non-member teammates. */
164
177
  viewerRole: ConversationMemberRole;
178
+ /** Provenance when `viewerRole` is DERIVED rather than held: the project
179
+ * (ADR-0045 read floor) or attached channel (ADR-0057) the viewer
180
+ * follows this conversation through. Null/absent for real members —
181
+ * clients use it to explain read-only honestly ("You follow this
182
+ * session through <project>…"). Absent on older servers. */
183
+ viewerRoleVia?: {
184
+ kind: "project" | "channel";
185
+ id: string;
186
+ name: string | null;
187
+ } | null;
165
188
  /** SSE replay watermark: the conversation's highest durable stream-event
166
189
  * id at hydrate time — everything at or below it is already folded into
167
190
  * `messages`. Seed the stream's first `Last-Event-ID` from it instead of
@@ -226,6 +249,36 @@ export interface CreateCloudSessionInput {
226
249
  * profile ids, or "none" for a bare session. Omitted = the owner's
227
250
  * default profile. */
228
251
  connectorProfileId?: string;
252
+ /** Idempotent dependency-setup command run by every fresh sandbox acquire
253
+ * after the drive mounts (`bun install`, `npm ci`, …). Strict charset —
254
+ * no shell metacharacters (400). Mutually exclusive with `templateId`
255
+ * (a snapshot carries its own installed state). */
256
+ setupCommand?: string;
257
+ /** Provenance of a session born by `agentc session import`: where the
258
+ * imported local session lived and which harness thread seeded it. */
259
+ handoffOrigin?: {
260
+ cwd: string;
261
+ sourceAcpSessionId: string | null;
262
+ agentKind: string;
263
+ mirrorConversationId: string | null;
264
+ };
265
+ /** Imported claude memories (session-import spec §memories): guest-home
266
+ * seeds re-applied on every fresh acquire. Paths under .claude/ only;
267
+ * strict base64; 256KB decoded total (server-capped). */
268
+ seedFiles?: Array<{
269
+ path: string;
270
+ contentB64: string;
271
+ mode: "write" | "append";
272
+ }>;
273
+ /** DIFF REVIEW spawn: the conversation whose proposed changes this
274
+ * session is born to review. Server-resolved: on a graph-plane drive
275
+ * the new session's branch is minted as a FORK of the reviewed branch
276
+ * (its working dir IS the proposal, `.review/` comparison materials
277
+ * included); on the legacy plane the spawn degrades to an ordinary
278
+ * session. Requires a human caller and a chat session on the reviewed
279
+ * session's factory; 409 `review_of_review` when the target is itself
280
+ * a review session. */
281
+ reviewOfConversationId?: string;
229
282
  }
230
283
  export interface CloudSessionCreated {
231
284
  conversation: ConversationRow;
@@ -295,6 +348,14 @@ export interface SessionForked {
295
348
  /** The new child conversation to switch to. */
296
349
  conversationId: string;
297
350
  }
351
+ /** Result of holding a session's background-work busy lease
352
+ * (`POST /conversations/:id/background-work`). While the lease is live the
353
+ * between-turns park/suspend leaves the session's VM running; it lapses on
354
+ * its own — re-hold to extend. */
355
+ export interface BackgroundWorkHeld {
356
+ /** ISO timestamp the lease now runs to. */
357
+ leaseUntil: string;
358
+ }
298
359
  /** One changed file on a session's drive branch relative to `main`. */
299
360
  export interface SessionFileChange {
300
361
  /** Factory-relative path. */
@@ -326,6 +387,131 @@ export interface SessionChangeSet {
326
387
  truncated: boolean;
327
388
  /** Open `factory_file_conflicts` rows from this session's prior merges. */
328
389
  openConflicts: number;
390
+ /** MERGE GATE (merge-gate spec, additive — absent on older servers):
391
+ * null = ungated. Present: whether THIS caller's merge lands directly
392
+ * (`canMerge` — an owner or named approver) or stages a kind='merge'
393
+ * approval instead (`mergeSessionChanges` answers 202
394
+ * `SessionMergeGated`), plus the resolved approver set. */
395
+ mergeGate?: {
396
+ enabled: true;
397
+ canMerge: boolean;
398
+ approvers: Array<{
399
+ userId: string;
400
+ label: string | null;
401
+ }>;
402
+ } | null;
403
+ /** The OPEN kind='merge' approval already waiting on this session's
404
+ * branch, if any (additive — absent on older servers). */
405
+ pendingApprovalId?: string | null;
406
+ /** DIFF REVIEW (additive — absent on older servers): notes written back
407
+ * by a spawned review session, whether they predate the served diff
408
+ * (`reviewStale` — the changed-file signature no longer matches), the
409
+ * bound review session's conversation + whether it is still live, and
410
+ * whether the deployment offers reviews at all (`reviewEnabled`; false
411
+ * hides the affordance). Reviews are USER-TRIGGERED only — binding
412
+ * rides `POST /conversations/:id/changes/review`, and notes arrive
413
+ * through the review session's own gated write-back. */
414
+ reviewNotes?: string | null;
415
+ reviewStale?: boolean;
416
+ reviewEnabled?: boolean;
417
+ reviewSessionConversationId?: string | null;
418
+ reviewSessionActive?: boolean;
419
+ /** STRUCTURED review suggestions beside the notes (additive — absent on
420
+ * older servers): individually actionable {id, path, title, rationale,
421
+ * patch} entries the review session wrote back, status-stamped
422
+ * `"proposed"` by the server (`"accepted"`/`"rejected"` are reserved for
423
+ * the human decision pass). */
424
+ reviewSuggestions?: ReviewSuggestion[];
425
+ /** THE REVIEWER MARKER (additive — absent on older servers): non-null ⇒
426
+ * THIS session was born to review that conversation's diff. Its own
427
+ * branch is a fork of the reviewed branch and can never merge
428
+ * (`mergeable` false, merge 409s `fork_branch_unmergeable`), and it is
429
+ * refused as a review target (409 `review_of_review`). */
430
+ reviewOfConversationId?: string | null;
431
+ }
432
+ /** One STRUCTURED, individually actionable suggestion a review session
433
+ * wrote back beside its notes (`SessionChangeSet.reviewSuggestions`). */
434
+ export interface ReviewSuggestion {
435
+ /** Reviewer-minted stable id — survives full-replace re-posts. */
436
+ id: string;
437
+ /** The changed file the suggestion targets (always in the change set —
438
+ * the writeback refuses paths outside it). */
439
+ path: string;
440
+ title: string;
441
+ rationale: string;
442
+ /** Unified-diff hunk targeting the PROPOSED side of `path`. */
443
+ patch: string;
444
+ /** Server-stamped `"proposed"` at writeback; the HUMAN decision routes
445
+ * (`POST …/changes/suggestions/:suggestionId/decide` / `…/decide-all`)
446
+ * are the only writers of the rest — `"accepted"` (the patch landed on
447
+ * the session branch), `"rejected"`, or `"stale"` (an accept found the
448
+ * file drifted since review; nothing was written). */
449
+ status: "proposed" | "accepted" | "rejected" | "stale";
450
+ }
451
+ /** `POST /conversations/:id/changes/suggestions/:suggestionId/decide` —
452
+ * the post-call truth for that suggestion (an accept that found drift
453
+ * answers `"stale"`; repeats on a settled row are no-ops). */
454
+ export interface ReviewSuggestionDecision {
455
+ object: "review_suggestion_decision";
456
+ conversationId: string;
457
+ id: string;
458
+ status: ReviewSuggestion["status"];
459
+ }
460
+ /** `POST /conversations/:id/changes/suggestions/decide-all` — every stored
461
+ * suggestion's post-call status (only `"proposed"` rows flip). */
462
+ export interface ReviewSuggestionDecisions {
463
+ object: "review_suggestion_decisions";
464
+ conversationId: string;
465
+ results: Array<{
466
+ id: string;
467
+ status: ReviewSuggestion["status"];
468
+ }>;
469
+ }
470
+ /** `GET /conversations/:id/changes/stats` — the changes chip's aggregate
471
+ * +added/−removed line counts, cached server-side per (base, theirsHead).
472
+ * `unavailable` = no cheap head anchors (legacy plane, unbranched) — the
473
+ * chip degrades to its file count, never fake zeros. */
474
+ export type SessionChangeStats = {
475
+ object: "session_change_stats";
476
+ conversationId: string;
477
+ state: "unavailable";
478
+ } | {
479
+ object: "session_change_stats";
480
+ conversationId: string;
481
+ state: "ok";
482
+ additions: number;
483
+ deletions: number;
484
+ files: number;
485
+ /** True when the count is partial (list truncated / file cap). */
486
+ truncated: boolean;
487
+ base: string;
488
+ theirsHead: string;
489
+ };
490
+ /** `POST /conversations/:id/changes/review` — binds a just-spawned review
491
+ * session to the reviewed session and stamps the diff signature the
492
+ * review covers. */
493
+ export interface SessionDiffReviewBound {
494
+ object: "session_diff_review";
495
+ conversationId: string;
496
+ reviewSessionConversationId: string;
497
+ reviewDiffSignature: string;
498
+ }
499
+ /** 202 from `POST /conversations/:id/changes/merge` on a merge-GATED
500
+ * session when the caller is not an approver: nothing merged — the ask
501
+ * froze into (`approval_required`) or converged on (`approval_pending`) a
502
+ * kind='merge' approval routed to the approvers. */
503
+ export interface SessionMergeGated {
504
+ object: "session_merge_gated";
505
+ conversationId: string;
506
+ code: "approval_required" | "approval_pending";
507
+ approvalId: string;
508
+ branch?: string;
509
+ changeCount?: number;
510
+ openConflicts?: number;
511
+ approvers: Array<{
512
+ userId: string;
513
+ label: string | null;
514
+ }>;
329
515
  }
330
516
  /** Per-file accounting of one session branch merge — the merge core's
331
517
  * honest report, returned verbatim. */
@@ -394,6 +580,14 @@ export interface SendConversationMessageInput {
394
580
  * streaming; `queued` = a turn is already in flight — the message is owed
395
581
  * work and a coalesced follow-up turn will answer it. NOT an error. */
396
582
  export type ConversationTurnState = "none" | "started" | "queued";
583
+ /** One @-mentioned user the server DROPPED as a non-member — the mention
584
+ * ping went to nobody, and the response says so instead of staying
585
+ * silent. `name` is the label the sender's own text carried for the
586
+ * mention (picked in the typeahead — no new information). */
587
+ export interface UnnotifiedMention {
588
+ userId: string;
589
+ name: string;
590
+ }
397
591
  export interface SendConversationMessageResult {
398
592
  /** The persisted user message's id. */
399
593
  messageId: string;
@@ -406,6 +600,10 @@ export interface SendConversationMessageResult {
406
600
  * each runs its own turn in its own conversation. Absent on older
407
601
  * servers and non-channel sends. */
408
602
  relayedSessionIds?: string[];
603
+ /** Mentioned users whose ping was dropped by the member filter (a
604
+ * private channel pings members only; a DM pings only the pair) —
605
+ * present only when non-empty. Absent on older servers. */
606
+ unnotifiedMentions?: UnnotifiedMention[];
409
607
  }
410
608
  /** Response of the presence heartbeat (ADR-0037 §6): a fresh agent-liveness
411
609
  * snapshot + the roster TTL. The attach roster itself is NOT returned —
@@ -89,6 +89,11 @@ export interface RegisterWorkflowInput {
89
89
  * server skips the /factory mount for its runs (#13). See
90
90
  * `WorkflowMetadata.environmentBuild`. */
91
91
  environmentBuild?: boolean;
92
+ /** Drive requirement declared via `defineWorkflow({ factoryDrive })`.
93
+ * Omitted ⇒ `"required"` on the server (a failed /factory mount fails
94
+ * the run); `"none"` = explicit no-drive opt-out. See
95
+ * `WorkflowMetadata.factoryDrive`. */
96
+ factoryDrive?: "required" | "none";
92
97
  /** Factory slug. Defaults to `"default"`. */
93
98
  factorySlug?: string;
94
99
  }
@@ -181,6 +186,26 @@ export interface FactoryFileWriteResult {
181
186
  sizeBytes: number;
182
187
  created: boolean;
183
188
  }
189
+ /** A minted local-mount grant: the user branch, the signed gateway token, and
190
+ * where to dial. The backing `conversationId` is the mount's review surface
191
+ * (its branch changes list + merge ride the session-changes routes). */
192
+ export interface DriveMountSession {
193
+ conversationId: string;
194
+ branch: string;
195
+ /** Signed gateway mount token (user principal, exclusive mode). Treat as a
196
+ * secret: write it to a 0600 token file, never argv/env of children. */
197
+ token: string;
198
+ gatewayWsUrl: string;
199
+ /** Token expiry, unix seconds — re-mint (same `conversationId`) before it. */
200
+ expiresAtS: number;
201
+ diskId: string;
202
+ }
203
+ export interface CreateDriveMountSessionInput {
204
+ /** Re-mint for an existing mount session (remount / token refresh). */
205
+ conversationId?: string;
206
+ /** The mounting machine's hostname — carried in the mount's title. */
207
+ host?: string;
208
+ }
184
209
  /** A factory: a project-level grouping of workflows inside a team. */
185
210
  export interface FactoryRow {
186
211
  id: string;
@@ -259,6 +284,25 @@ export interface ApiKey {
259
284
  export interface ApiKeyCreated extends ApiKey {
260
285
  key: string;
261
286
  }
287
+ /** Options for `GET /api-keys` — the list is bounded and keyset-paginated
288
+ * (every run step / session boot mints a key row, so "all keys ever" is
289
+ * unbounded by construction). */
290
+ export interface ListApiKeysOptions {
291
+ /** Page size, 1–200 (server default 100). */
292
+ limit?: number;
293
+ /** `active` = unrevoked + unexpired only; `all` (default) includes
294
+ * revoked/expired history. */
295
+ status?: "active" | "all";
296
+ /** Opaque cursor from a previous page's `nextCursor`. */
297
+ cursor?: string;
298
+ }
299
+ /** One page of `GET /api-keys`. */
300
+ export interface ApiKeyPage {
301
+ data: ApiKey[];
302
+ hasMore: boolean;
303
+ /** Pass as `cursor` to fetch the next page; null on the last page. */
304
+ nextCursor: string | null;
305
+ }
262
306
  /** Single rollup row from `GET /api/v1/usage`. */
263
307
  export interface UsageRollupRow {
264
308
  eventType: string;
@@ -275,7 +319,10 @@ export interface UsageResponse {
275
319
  to: string | null;
276
320
  }
277
321
  /** One drive-directory ⇄ GitHub-repo link (`/factories/:slug/repo-links`).
278
- * The webhook secret ref is operator plumbing and never on the wire. */
322
+ * Multi-branch model: ONE link row per (repo, branch); sibling branches
323
+ * of a repo occupy sibling `base@<branch>` placements. The webhook secret
324
+ * ref is operator plumbing and never on the wire — `webhookConfigured` /
325
+ * `webhookVerifiedAt` carry the honest connect status instead. */
279
326
  export interface DriveRepoLink {
280
327
  id: string;
281
328
  factoryId: string;
@@ -285,25 +332,55 @@ export interface DriveRepoLink {
285
332
  provider: string;
286
333
  /** "org/repo". */
287
334
  repoFullName: string;
288
- /** The GitHub branch drive `main` syncs with. */
335
+ /** The GitHub branch this link's placement syncs with. */
289
336
  trackedBranch: string;
290
337
  connectorGrantId: string | null;
291
338
  /** v1 links are always 'write' (the round trip is the feature). */
292
339
  access: "read" | "write";
340
+ /** A webhook secret exists for this link (registration done). NOT proof
341
+ * of delivery — that is `webhookVerifiedAt`. */
342
+ webhookConfigured: boolean;
343
+ /** When a signed GitHub delivery last PROVED the webhook delivers; null
344
+ * = unproven (the link syncs by poll). */
345
+ webhookVerifiedAt: string | null;
346
+ /** Two-way sync: drive edits under the prefix push back to the tracked
347
+ * branch. ON by default for new links. */
348
+ pushOutEnabled: boolean;
349
+ /** Follow-all mode (stamped identically on every sibling link of one
350
+ * repo): pushes to NEW branches auto-link them; GitHub branch deletes
351
+ * auto-unlink. False = explicit selection. */
352
+ followAllBranches: boolean;
353
+ /** Branches follow-all could NOT link (with the reason) — stamped on
354
+ * the repo's PRIMARY link row only; empty everywhere else. */
355
+ autoLinkSkips: Array<{
356
+ branch: string;
357
+ reason: string;
358
+ at: string;
359
+ }>;
293
360
  lastSyncedGitSha: string | null;
294
361
  lastSyncedAcgCommit: string | null;
295
- syncState: "idle" | "syncing" | "diverged" | "reauth_required";
296
- visibility: "team" | "restricted";
362
+ syncState: "idle" | "syncing" | "pushing" | "error" | "conflict" | "diverged" | "reauth_required";
363
+ /** Human-readable detail when syncState is 'error' or 'conflict'. */
364
+ syncError: string | null;
297
365
  createdBy: string | null;
298
366
  lastSyncedAt: string | null;
299
367
  createdAt: string;
300
368
  }
301
369
  /** Input for `POST /factories/:slug/repo-links`. Requires the drive to be
302
- * graph-authoritative (ADR-0058 Phase 4 promoted) — 409 otherwise. */
370
+ * graph-authoritative (ADR-0058 Phase 4 promoted) — 409 otherwise.
371
+ * Exactly ONE of `followAllBranches` / `trackedBranches`:
372
+ * `followAllBranches: true` (the default connect mode) links EVERY
373
+ * branch — default branch primary at the plain `dirPrefix` — and keeps
374
+ * following live (new branches auto-link on push, deleted branches
375
+ * auto-unlink; capped at 100 branches). Explicit `trackedBranches`:
376
+ * index 0 is the PRIMARY and keeps the plain placement; every additional
377
+ * branch lands at the sibling `dirPrefix@<sanitized-branch>` placement. */
303
378
  export interface CreateDriveRepoLinkInput {
304
379
  dirPrefix: string;
305
380
  repoFullName: string;
306
- trackedBranch: string;
381
+ trackedBranches?: string[];
382
+ followAllBranches?: boolean;
307
383
  connectorGrantId: string;
308
- visibility?: "team" | "restricted";
384
+ /** Two-way sync ("push-out") — defaults to TRUE for new links. */
385
+ pushOut?: boolean;
309
386
  }
@@ -9,7 +9,18 @@
9
9
  import type { SandboxNetworkPolicy, SandboxSize } from "../sandbox.js";
10
10
  import type { SnapshotConfig } from "./workflow-metadata.js";
11
11
  import type { RunContext } from "./api-scopes.js";
12
+ import type { BundledWorkflow } from "../utils/bundler.js";
12
13
  export type RunState = "running" | "success" | "failed" | "abandoned" | "canceled";
14
+ /** INLINE invoke payload (`POST /factories/:slug/invoke`): everything
15
+ * `bundleWorkflow` produced — source, the REQUIRED manifest binding the
16
+ * bytes, the workflow plan, and the build fields — plus the name the run
17
+ * reports as its workflow. The server runs it through the same
18
+ * validate/build core as registration but writes NO registry row: the run
19
+ * snapshots the validated source (auditable, replayable) and the name
20
+ * stays free for real registrations. */
21
+ export type InlineWorkflowPayload = BundledWorkflow & {
22
+ name: string;
23
+ };
13
24
  export interface InvokeWorkflowOptions {
14
25
  /** Per-invocation snapshot config override. `snapshots.bootFrom`
15
26
  * replaces the template's boot source; `snapshots.saveLatest` and
@@ -38,11 +49,45 @@ export interface InvokeWorkflowOptions {
38
49
  * with the same key inside the server's dedup window returns the original
39
50
  * run instead of starting a new one (matches `resumePause`'s pattern). */
40
51
  idempotencyKey?: string;
41
- }
52
+ /** Who pays for this run's model calls:
53
+ *
54
+ * - `"default"` (Auto) — the initiating human's connected subscription
55
+ * when one is enabled for workflows, else platform credits;
56
+ * - `"platform"` — always platform credits (metered);
57
+ * - `"subscription"` — REQUIRE the initiating human's plan. If it
58
+ * cannot be honored the invoke FAILS (412) rather than quietly
59
+ * spending credits;
60
+ * - `"byok"` — a factory secret named by `fundingSecret`, injected as
61
+ * the runtime's raw provider key. Never metered.
62
+ *
63
+ * Omitted → the workflow's own default (its `settings.funding`), else
64
+ * Auto. */
65
+ funding?: FundingChoice;
66
+ /** `funding: "byok"` only — the FACTORY SECRET NAME whose value funds the
67
+ * run. Must be a provider key variable (ANTHROPIC_API_KEY,
68
+ * OPENAI_API_KEY, CODEX_API_KEY, OPENROUTER_API_KEY) — that is where the
69
+ * sandbox's runtimes read it. The name only; the value never leaves the
70
+ * server's secret store. */
71
+ fundingSecret?: string;
72
+ }
73
+ /** Inference-funding choice for a run — a lane the caller PINS, or
74
+ * `"default"` (Auto, the automatic ladder). Mirrors the same vocabulary
75
+ * cloud sessions use. */
76
+ export type FundingChoice = "default" | "platform" | "subscription" | "byok";
42
77
  export interface InvokeAndWaitOptions extends InvokeWorkflowOptions {
43
78
  timeoutMs?: number;
44
79
  pollIntervalMs?: number;
45
80
  }
81
+ /** Options for `invokeInline` — the named-invoke options plus the run-title
82
+ * override (an inline run has no registered template to inherit one from). */
83
+ export interface InvokeInlineOptions extends InvokeWorkflowOptions {
84
+ /** Run title override — defaults to the workflow name. */
85
+ title?: string;
86
+ }
87
+ export interface InvokeInlineAndWaitOptions extends InvokeInlineOptions {
88
+ timeoutMs?: number;
89
+ pollIntervalMs?: number;
90
+ }
46
91
  export interface InvokeResult {
47
92
  id: string;
48
93
  }
@@ -238,7 +283,8 @@ export interface ListRunsOptions {
238
283
  factorySlug?: string;
239
284
  /** Substring match on the registered workflow name (`metadata._workflow`). */
240
285
  workflow?: string;
241
- /** Substring match across title / task title / branch / run id. */
286
+ /** Substring match across title / task title / branch — or an exact run
287
+ * id (full UUID). */
242
288
  search?: string;
243
289
  outcome?: string;
244
290
  sort?: "newest" | "oldest" | "fastest" | "slowest";