@agent-compose/sdk 0.8.3 → 0.8.5

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 +9 -1
  2. package/dist/agent/agent-loop.d.ts +14 -1
  3. package/dist/client.d.ts +171 -33
  4. package/dist/directives.d.ts +14 -0
  5. package/dist/generated/verb-synopsis.d.ts +34 -0
  6. package/dist/index.d.ts +7 -5
  7. package/dist/index.js +1043 -41
  8. package/dist/runtimes/_cli-agent.d.ts +118 -0
  9. package/dist/runtimes/claude-code.d.ts +31 -1
  10. package/dist/runtimes/openai-desktop.d.ts +50 -0
  11. package/dist/runtimes/openai-desktop.js +1065 -59
  12. package/dist/runtimes/openai-desktop.test.d.ts +20 -0
  13. package/dist/runtimes/tool-pulse.test.d.ts +17 -0
  14. package/dist/sandbox/devbox.d.ts +5 -5
  15. package/dist/sandbox/registry.d.ts +12 -0
  16. package/dist/sandbox/sizes.d.ts +11 -5
  17. package/dist/sandbox.d.ts +2 -2
  18. package/dist/step-invocation/types.d.ts +1 -1
  19. package/dist/types/api-conversations.d.ts +85 -12
  20. package/dist/types/api-factory.d.ts +111 -1
  21. package/dist/types/conversation-stream.d.ts +22 -1
  22. package/dist/types/protocol.d.ts +130 -1
  23. package/dist/types/runtime.d.ts +71 -0
  24. package/package.json +1 -1
  25. package/src/agent/agent-context.ts +43 -9
  26. package/src/agent/agent-loop.ts +14 -3
  27. package/src/agent/desktop-open.ts +13 -1
  28. package/src/client.ts +256 -38
  29. package/src/directives.ts +21 -1
  30. package/src/generated/verb-synopsis.ts +544 -0
  31. package/src/index.ts +20 -5
  32. package/src/runtimes/_cli-agent.ts +333 -22
  33. package/src/runtimes/claude-code.ts +260 -14
  34. package/src/runtimes/openai-desktop.ts +82 -19
  35. package/src/sandbox/devbox.ts +5 -5
  36. package/src/sandbox/providers/e2b.ts +89 -17
  37. package/src/sandbox/registry.ts +19 -1
  38. package/src/sandbox/sizes.ts +11 -5
  39. package/src/sandbox.ts +2 -1
  40. package/src/types/api-conversations.ts +65 -13
  41. package/src/types/api-factory.ts +121 -1
  42. package/src/types/conversation-stream.ts +24 -1
  43. package/src/types/protocol.ts +127 -1
  44. package/src/types/runtime.ts +63 -0
@@ -5,7 +5,7 @@
5
5
  * registry entry.
6
6
  */
7
7
 
8
- import { Sandbox } from "e2b";
8
+ import { Sandbox, Template } from "e2b";
9
9
  import type { CommandHandle } from "e2b";
10
10
  import type { Sandbox as Desktop } from "@e2b/desktop";
11
11
  import pRetry from "p-retry";
@@ -13,7 +13,7 @@ import type {
13
13
  SandboxProvider, SandboxCommandRunOptions, SandboxBackgroundProcess, SandboxPtyHandle,
14
14
  } from "../../types/sandbox.js";
15
15
  import { toE2bNetwork } from "../network-policy.js";
16
- import { DEFAULT_SANDBOX_SIZE, e2bBaseTemplate, isE2bSupportedSize } from "../sizes.js";
16
+ import { DEFAULT_SANDBOX_SIZE, e2bAgentEnvTemplate, isE2bSupportedSize } from "../sizes.js";
17
17
  import { AGENT_COMPOSE_TAG } from "../provider-def.js";
18
18
  import type { OwnedSandbox, SandboxProviderDef } from "../provider-def.js";
19
19
 
@@ -104,6 +104,9 @@ export function e2bMaxSandboxMs(): number {
104
104
  return Number(process.env.E2B_MAX_SANDBOX_MS) || 60 * 60 * 1000;
105
105
  }
106
106
 
107
+ /** Once-per-process latch for the extendLifetime clamp warning below. */
108
+ let e2bExtendClampWarned = false;
109
+
107
110
  /** Wrap an E2B `CommandHandle` as a provider-agnostic background process.
108
111
  * `wait()` is normalised NOT to throw on a non-zero exit (mirroring the
109
112
  * `commands.run` contract in `makeSandboxProvider`) so callers branch on
@@ -245,7 +248,20 @@ function makeE2bSandboxProvider(sb: Sandbox): SandboxProvider {
245
248
  // the create timeout — an over-cap push would 400 and leave the OLD
246
249
  // (possibly short) deadline standing.
247
250
  async extendLifetime(ms) {
248
- await sb.setTimeout(Math.min(ms, e2bMaxSandboxMs()));
251
+ const maxMs = e2bMaxSandboxMs();
252
+ if (ms > maxMs && !e2bExtendClampWarned) {
253
+ // Once per process, not per extension: every far-horizon push clamps
254
+ // identically, and the fact worth surfacing is the CONFIGURATION —
255
+ // the server believes it bought `ms` of kill-clock slack but the
256
+ // plan cap silently shortens it, so the "orphan backstop" can reap
257
+ // a machine the platform still considers covered.
258
+ e2bExtendClampWarned = true;
259
+ console.warn(
260
+ `[sandbox] e2b lifetime extension clamped: requested ${ms}ms exceeds the plan cap ${maxMs}ms — ` +
261
+ `the kill-clock backstop fires at the cap, not the requested horizon. Raise E2B_MAX_SANDBOX_MS on plans that allow more.`,
262
+ );
263
+ }
264
+ await sb.setTimeout(Math.min(ms, maxMs));
249
265
  },
250
266
  // Push a freshly-resolved egress policy onto the live sandbox via E2B's
251
267
  // native `updateNetwork` — the E2B analogue of Vercel's `update({
@@ -281,18 +297,24 @@ export const e2bProviderDef: SandboxProviderDef = {
281
297
  // E2B has no create-time resource knob (specs are baked into the
282
298
  // template/snapshot), so honouring `size` on E2B = picking a PRE-SIZED
283
299
  // template, not passing the field through. We resolve the boot template
284
- // from `size` below (`e2bBaseTemplate(size)`) when the caller gave no
300
+ // from `size` below (`e2bAgentEnvTemplate(size)`) when the caller gave no
285
301
  // explicit template/bootFrom; the field itself is never forwarded to E2B.
286
302
  create: async ({ template, timeoutMs, networkPolicy, size, ...rest }) => {
287
303
  // `template` is an E2B template id/alias, a snapshot id (a valid create
288
304
  // source that persists beyond its origin sandbox — bootFrom parity), or
289
- // absent. When absent we pick the SIZE-MATCHED platform base alias
290
- // (`agent-compose-base-<size>`) so `resources.size` gives the same
291
- // machine spec on E2B as on Vercel — the cross-provider parity this whole
292
- // change exists for. Only when no size is resolvable at all do we fall
293
- // back to E2B_DEFAULT_TEMPLATE if set (a prebuilt base — e.g. one with the claude CLI
294
- // + chromium baked in and more RAM than the stock 482MB base), the
295
- // per-deployment analogue of Vercel's node24; else E2B's stock base.
305
+ // absent. When absent we pick the SIZE-MATCHED platform agent-env alias
306
+ // (`agent-env-<size>`) — the SAME image every cloud session boots
307
+ // (server/src/sandbox/persistent.ts resolves the identical
308
+ // `e2bAgentEnvTemplate(size)`), so a template-less workflow run gets the
309
+ // full session toolchain (claude runtime, dev toolbelt, agentc, fsgw
310
+ // client) instead of a thinner image. One seam, both lanes: the
311
+ // session/run divergence that caused fleet-wide `exit 127`s (missing
312
+ // harness CLIs, fixed by on-demand install in v0.10.73) is structurally
313
+ // gone — there is no separate run-lane template to drift. The per-size
314
+ // aliasing also keeps `resources.size` giving the same machine spec on
315
+ // E2B as on Vercel (cross-provider parity). Only when no size is
316
+ // resolvable at all do we fall back to E2B_DEFAULT_TEMPLATE if set (the
317
+ // per-deployment analogue of Vercel's node24); else E2B's stock base.
296
318
  // Self-provisioning runtimes (claude/codex/amp via bootFrom:"reuse") install
297
319
  // their CLI on the base and cache it in the captured snapshot, so a
298
320
  // template-less first run boots, installs, snapshots. Clamp the lifetime to
@@ -317,28 +339,41 @@ export const e2bProviderDef: SandboxProviderDef = {
317
339
  const sandboxOpts = {
318
340
  ...rest,
319
341
  timeoutMs: Math.min(timeoutMs, maxMs),
342
+ // Lifetime-deadline expiry PAUSES instead of killing, and the next
343
+ // incoming connection auto-resumes (recycle forensics 2026-08-23:
344
+ // uncontrolled kill-clock loss was the dominant machine-end cause
345
+ // ~60:1 — under this policy a missed extension becomes a resumable
346
+ // park our redial paths revive). CREATE-time policy, persistent for
347
+ // the sandbox's life — connect() takes no lifecycle. NOTE: this is
348
+ // e2b's `lifecycle` contract, NOT the deprecated `autoPause` flag
349
+ // (which the create body would silently drop).
350
+ lifecycle: { onTimeout: "pause" as const, autoResume: true },
320
351
  ...(networkPolicy ? { network: toE2bNetwork(networkPolicy) } : {}),
321
352
  };
322
353
  // Boot template resolution, in priority order:
323
354
  // 1. explicit `template`/bootFrom (a pinned snapshot or alias),
324
- // 2. else the SIZE-MATCHED base alias `agent-compose-base-<size>`
325
- // (`size` resolved to DEFAULT_SANDBOX_SIZE when unset) — this is the
326
- // cross-provider parity path,
355
+ // 2. else the SIZE-MATCHED agent-env alias `agent-env-<size>` — the
356
+ // session-identical image (`size` resolved to DEFAULT_SANDBOX_SIZE
357
+ // when unset). NEVER the legacy `agent-compose-base-<size>` — that
358
+ // family remains a valid EXPLICIT bootFrom target only,
327
359
  // 3. else E2B_DEFAULT_TEMPLATE as an ultimate per-deployment fallback,
328
360
  // 4. else E2B's stock base.
329
- // `32vcpu-64gb` has no base-<size> template (Pro caps ~8 vCPU); the
361
+ // `32vcpu-64gb` has no per-size template (Pro caps ~8 vCPU); the
330
362
  // register + invoke guards reject it before a run reaches here, so we
331
- // never synthesize a non-existent `agent-compose-base-32vcpu-64gb` alias.
363
+ // never synthesize a non-existent `agent-env-32vcpu-64gb` alias.
332
364
  const resolvedSize = size ?? DEFAULT_SANDBOX_SIZE;
333
365
  const tmpl =
334
366
  template ??
335
- (isE2bSupportedSize(resolvedSize) ? e2bBaseTemplate(resolvedSize) : undefined) ??
367
+ (isE2bSupportedSize(resolvedSize) ? e2bAgentEnvTemplate(resolvedSize) : undefined) ??
336
368
  process.env.E2B_DEFAULT_TEMPLATE;
337
369
  return makeE2bSandboxProvider(
338
370
  await (tmpl ? Sandbox.create(tmpl, sandboxOpts) : Sandbox.create(sandboxOpts)),
339
371
  );
340
372
  },
341
373
  reconnect: async (sandboxId) => makeE2bSandboxProvider(
374
+ // The pause-on-timeout policy is create-time and persistent — connect
375
+ // takes no lifecycle options (the old per-connect autoPause flag is
376
+ // deprecated API-side).
342
377
  await Sandbox.connect(sandboxId, { apiKey: process.env.E2B_API_KEY ?? "", timeoutMs: 60 * 60 * 1000 }),
343
378
  ),
344
379
  killAll: async () => {
@@ -355,4 +390,41 @@ export const e2bProviderDef: SandboxProviderDef = {
355
390
  // E2B snapshots are team-scoped; delete by id. Best-effort like Vercel's.
356
391
  await Sandbox.deleteSnapshot(snapshotId, { apiKey: env.E2B_API_KEY });
357
392
  },
393
+ snapshotExists: async (snapshotId, env) => {
394
+ // Existence probe for a CREATE SOURCE (the `snapshotResolves` contract:
395
+ // `true` = resolves, `false` = the provider definitively says it does not
396
+ // exist, anything indeterminate PROPAGATES — never reported as missing).
397
+ //
398
+ // e2b 2.30.5 has no GET-snapshot-by-id, so this composes the two
399
+ // documented lookups, cheapest first:
400
+ // 1. `Template.exists` — ONE round-trip to the template-existence
401
+ // endpoint (`GET /templates/aliases/{alias}`), with DOCUMENTED
402
+ // not-found semantics: 404 → false, 403 → exists but owned by
403
+ // another team → true (the SDK's own mapping). Snapshots are
404
+ // templates provider-side, and this probe also resolves the
405
+ // `agent-env-*` template ALIASES that E2B-pinned default templates
406
+ // register as their bootFrom — boot-time platform validation
407
+ // (validatePlatformSnapshots) probes those through this same seam,
408
+ // so a snapshots-only lookup would falsely alert "unresolvable" on
409
+ // every alias.
410
+ // 2. A paged scan of the team's snapshot list (`Sandbox.listSnapshots`)
411
+ // — authoritative for `createSnapshot` artifacts whatever their id
412
+ // shape, reached only when the template probe answered not-found.
413
+ // Bounded by the team's snapshot count (session rings are GC'd to a
414
+ // fixed retain depth), and this is rare-path code: machine-loss
415
+ // recovery and boot validation, never a hot loop.
416
+ // `false` therefore means BOTH documented lookups answered not-found.
417
+ // (`Sandbox.listSnapshots({ sandboxId })` — source-filtered — was
418
+ // rejected as the primary: callers hold only the snapshot id, and a
419
+ // filtered miss would still need the full scan before "absent" is
420
+ // honest.) Transport/auth faults from either call throw — indeterminate.
421
+ const opts = { apiKey: env.E2B_API_KEY };
422
+ if (await Template.exists(snapshotId, opts)) return true;
423
+ const paginator = Sandbox.listSnapshots(opts);
424
+ while (paginator.hasNext) {
425
+ const items = await pRetry(() => paginator.nextItems(), { retries: 3, minTimeout: 500, factor: 2 });
426
+ if (items.some((s) => s.snapshotId === snapshotId || s.names.includes(snapshotId))) return true;
427
+ }
428
+ return false;
429
+ },
358
430
  };
@@ -6,7 +6,7 @@
6
6
  * at creation time.
7
7
  */
8
8
 
9
- import { SandboxNotFoundError, RateLimitError } from "e2b";
9
+ import { SandboxNotFoundError, RateLimitError, NotFoundError } from "e2b";
10
10
  import pRetry from "p-retry";
11
11
  import type { FailedAttemptError } from "p-retry";
12
12
  import type { SandboxProvider } from "../types/sandbox.js";
@@ -45,6 +45,24 @@ function isTransientSandboxError(error: unknown): boolean {
45
45
  .test(message);
46
46
  }
47
47
 
48
+ /** Did a CREATE fail because its create SOURCE (template alias / snapshot id)
49
+ * does not resolve on the provider — vs any other create fault? The server's
50
+ * recovery-boot fall-through keys on this: a ring snapshot licensed on an
51
+ * INDETERMINATE existence probe that turns out to be gone must fall through
52
+ * the ring toward the base image, while every other create fault (quota,
53
+ * auth, 5xx, network) must propagate — falling through on those would trade
54
+ * a still-recoverable machine state for a base boot. E2B maps the create-
55
+ * time 404 ("template not found") to its typed `NotFoundError`; the message
56
+ * match is the fallback for untyped transports (Vercel). Lives in the SDK
57
+ * because the server holds its OWN `e2b` module instance — an `instanceof`
58
+ * against the class from the wrong copy is always false. */
59
+ export function isSandboxSourceNotFoundError(error: unknown): boolean {
60
+ if (error instanceof NotFoundError) return true;
61
+ const message = error instanceof Error ? error.message : String(error ?? "");
62
+ return /(?:template|snapshot)[^\n]{0,80}not[\s_-]?found|not[\s_-]?found[^\n]{0,80}(?:template|snapshot)/i
63
+ .test(message);
64
+ }
65
+
48
66
  /** The ONE place the sandbox retry policy lives. Provisioning, reconnecting, and
49
67
  * snapshotting all race with the sandbox being paused/reclaimed; this runs the call
50
68
  * through p-retry's generic backoff, retrying ONLY the transient race (and failing
@@ -148,15 +148,21 @@ export function sandboxSizeLabel(size: SandboxSize): string {
148
148
  /** Stable E2B template ALIAS for the platform base at a given size
149
149
  * (`agent-compose-base-<size>`). Aliases — not snapshot ids — so the refs are
150
150
  * multi-account-clean: the same string resolves in any E2B account that built
151
- * the templates. Built by `infra/e2b-template/build.ts`; the E2B provider
152
- * boots this when a run on E2B declares no explicit `bootFrom`/template. */
151
+ * the templates. Built by `infra/e2b-template/build.ts`. An EXPLICIT
152
+ * `bootFrom`/template target only — nothing boots it by default: template-less
153
+ * E2B creates resolve `e2bAgentEnvTemplate` (the session-identical image)
154
+ * instead, so the run and session lanes can never diverge on baked tooling. */
153
155
  export function e2bBaseTemplate(size: SandboxSize): string {
154
156
  return `agent-compose-base-${size}`;
155
157
  }
156
158
 
157
- /** Stable E2B template ALIAS for the agent runtime (base + claude binary) at a
158
- * given size (`agent-env-<size>`). The agent default templates boot from this
159
- * via `bootFrom: { snapshotId: e2bAgentEnvTemplate(size) }`. Multi-account-clean
159
+ /** Stable E2B template ALIAS for the agent runtime (base + claude binary +
160
+ * dev toolbelt + session desktop) at a given size (`agent-env-<size>`).
161
+ * THE default E2B boot image, both lanes: every cloud SESSION boots it
162
+ * (server/src/sandbox/persistent.ts), and every template-less E2B workflow
163
+ * RUN boots it too (the e2b provider's size-matched default). The agent
164
+ * default templates boot from this via
165
+ * `bootFrom: { snapshotId: e2bAgentEnvTemplate(size) }`. Multi-account-clean
160
166
  * for the same reason as `e2bBaseTemplate`. */
161
167
  export function e2bAgentEnvTemplate(size: SandboxSize): string {
162
168
  return `agent-env-${size}`;
package/src/sandbox.ts CHANGED
@@ -54,7 +54,7 @@ export type { SandboxCreateOpts, OwnedSandbox } from "./sandbox/provider-def.js"
54
54
  export { parseSseExecStream } from "./sandbox/exec-stream.js";
55
55
  export type { ParseSseExecStreamOptions } from "./sandbox/exec-stream.js";
56
56
 
57
- export { makeSandboxProvider } from "./sandbox/providers/e2b.js";
57
+ export { makeSandboxProvider, e2bMaxSandboxMs } from "./sandbox/providers/e2b.js";
58
58
  export { makeDesktopSandboxProvider } from "./sandbox/providers/desktop.js";
59
59
  export { VERCEL_MAX_TAGS, buildVercelTags } from "./sandbox/providers/vercel.js";
60
60
  export { makeLocalSandboxProvider } from "./sandbox/providers/local.js";
@@ -65,6 +65,7 @@ export {
65
65
  reconnectSandbox,
66
66
  deleteSandboxSnapshot,
67
67
  snapshotResolves,
68
+ isSandboxSourceNotFoundError,
68
69
  getSandboxQuotas,
69
70
  listOwnedSandboxes,
70
71
  killSandboxById,
@@ -430,6 +430,49 @@ export interface BackgroundWorkHeld {
430
430
  leaseUntil: string;
431
431
  }
432
432
 
433
+ /** One background CHILD declared beside the busy lease (task #63): a
434
+ * detached process, by pid, with its durable journal/log file — what lets
435
+ * the platform VERIFY the work (`/proc/<pid>` + journal mtime) and
436
+ * reattach it after a park instead of losing it. */
437
+ export interface BackgroundWorkChildDecl {
438
+ pid: number;
439
+ /** Absolute guest-side path to the child's own durable output/journal
440
+ * file — its mtime is the progress evidence. */
441
+ journalPath?: string;
442
+ label?: string;
443
+ }
444
+
445
+ /** Outcome of requesting one machine size up
446
+ * (`POST /conversations/:id/machine/request-upsize` — task #110, the
447
+ * auto-resize policy's agent door). The platform arbitrates: within the
448
+ * team's daily cap the upsize is auto-granted (and lands immediately when
449
+ * no turn/background work holds the machine); past it a human approval
450
+ * card is posted. Wire shape mirrors
451
+ * server/src/sandbox/session-auto-resize.ts `UpsizeRequestOutcome`. */
452
+ export interface MachineUpsizeOutcome {
453
+ /** "executed" (landed now), "granted" (lands when the work settles),
454
+ * "pending_approval" (a card awaits the owner). Refusals arrive as HTTP
455
+ * errors carrying `code`. */
456
+ outcome: "executed" | "granted" | "pending_approval";
457
+ /** The size the grant/card names (e.g. "4vcpu-8gb"). */
458
+ size: string;
459
+ approvalId: string;
460
+ /** One human-readable line the CLI can print verbatim. */
461
+ message: string;
462
+ }
463
+
464
+ /** The session's background-work status
465
+ * (`GET /conversations/:id/background-work`). */
466
+ export interface BackgroundWorkStatus {
467
+ /** ISO deadline of the live lease, or null (no lease held). */
468
+ leaseUntil: string | null;
469
+ /** The declared children (each with its server-stamped `declaredAt`). */
470
+ children: Array<BackgroundWorkChildDecl & { declaredAt: string }>;
471
+ /** Non-null when a mid-work park froze declared children and the session
472
+ * still owes its conversation a status report (ISO park instant). */
473
+ frozenAt: string | null;
474
+ }
475
+
433
476
  // ── Session branch proposals (ADR-0053) ─────────────────────────────────────
434
477
  // Every cloud session works on its own factory-drive branch; the whole branch
435
478
  // is the unit of review, like a PR. Wire shapes mirror
@@ -511,11 +554,9 @@ export interface SessionChangeSet {
511
554
  * (hard cap 2; a manual re-review resets it). */
512
555
  reviewAutoFollowup?: boolean;
513
556
  reviewAutoRounds?: number;
514
- /** The OPT-IN main-advance auto-rebase reflex (additive — absent on
515
- * older servers; default OFF): armed ⇒ when main moves and a preflight
516
- * proves the fold clean, the platform rebases this session's branch
517
- * from main headlessly. Toggled via `setSessionAutoRebase`. */
518
- autoRebaseFromMain?: boolean;
557
+ /** The session's selectable cap on automatic follow-up rounds (additive
558
+ * — absent on older servers; default 2, range 1..5). */
559
+ reviewAutoRoundsMax?: number;
519
560
  /** STRUCTURED review suggestions beside the notes (additive — absent on
520
561
  * older servers): individually actionable {id, path, title, rationale,
521
562
  * patch} entries the review session wrote back, status-stamped
@@ -793,14 +834,6 @@ export interface SessionRebaseReport {
793
834
  foldSkipped?: boolean;
794
835
  }
795
836
 
796
- /** Result of `POST /conversations/:id/changes/autorebase` — the opt-in
797
- * main-advance auto-rebase reflex's new state. */
798
- export interface SessionAutoRebaseState {
799
- object: "session_autorebase";
800
- conversationId: string;
801
- autoRebaseFromMain: boolean;
802
- }
803
-
804
837
  /** The sender's page stamp (HUD bar sends) — persisted server-side, never
805
838
  * echoed back on the wire. Mirrors the server's `PageContext` schema. */
806
839
  export interface ConversationPageContext {
@@ -1004,3 +1037,22 @@ export interface StreamConversationOptions {
1004
1037
  lastEventId?: number;
1005
1038
  signal?: AbortSignal;
1006
1039
  }
1040
+
1041
+ /** Shared chat creation includes its own Ivy unless explicitly disabled. */
1042
+ export interface CreateChatInput {
1043
+ title?: string;
1044
+ visibility: "shared";
1045
+ access?: "public" | "private";
1046
+ memberIds?: string[];
1047
+ includeIvy?: boolean;
1048
+ }
1049
+ export interface ChannelIvyState {
1050
+ projects: Array<{ id: string; name: string }>;
1051
+ agent: AgentListRow | null;
1052
+ enabled: boolean; agentId: string | null; canManage: boolean; nextReviewAt: string | null;
1053
+ work: Array<{ id: string; title: string; status: string; evidence: string; nextAction: string; waitingOn: string | null; reviewAt: string | null }>;
1054
+ }
1055
+ export interface ProjectIvyConnection {
1056
+ id: string; teamId: string; projectId: string; provider: "github"; installationId: string;
1057
+ accountLogin: string; repositories: string[]; allowWrites: boolean; configuredBy: string | null; updatedAt: string;
1058
+ }
@@ -107,9 +107,15 @@ export interface RegisterWorkflowInput {
107
107
  export interface TemplateRow {
108
108
  name: string;
109
109
  version: string;
110
- factorySlug: string;
110
+ /** Owning factory's slug. `null` on platform-published rows — a published
111
+ * default belongs to no single factory and is invokable via any slug. */
112
+ factorySlug: string | null;
111
113
  /** Share scope (ADR-0045) — `null` = unscoped/grandfathered team tier. */
112
114
  scope: ArtifactScope | null;
115
+ /** `factory` = registered in a factory of the caller's team; `published` =
116
+ * platform default (team-wide read+invoke in EVERY factory, never write;
117
+ * fork it to edit under a new name). Optional: older servers omit it. */
118
+ origin?: "factory" | "published";
113
119
  }
114
120
 
115
121
  /** Template detail (`GET /factories/:slug/templates/:name`) — the typed
@@ -132,6 +138,17 @@ export interface ListTemplatesOptions {
132
138
  factorySlug?: string;
133
139
  }
134
140
 
141
+ /** One team connector grant, as `GET /api/v1/connectors` returns it (wire
142
+ * shape verbatim — snake_case). The typed subset the SDK pins; additional
143
+ * fields flow through untyped. */
144
+ export interface ConnectorGrantSummary {
145
+ id: string;
146
+ provider: string;
147
+ external_account_label: string | null;
148
+ status: "active" | "revoked" | "reauth_required";
149
+ connected_by_user_id: string;
150
+ }
151
+
135
152
  // ── Factory-file search (wire shape mirrors routes/factory-files.ts) ────────
136
153
 
137
154
  /** Public "anyone with the link" state — the additive `public` field on file
@@ -381,6 +398,27 @@ export interface SessionSecretRequestCreated {
381
398
  requestId: string;
382
399
  url: string;
383
400
  expiresAt: string;
401
+ /** True = a standing auto-approve set already filled the request at the
402
+ * mint — nothing is pending; source the session env now. */
403
+ autoApproved?: boolean;
404
+ }
405
+
406
+ /** What KIND of credential a vault request asks for (advisory): the vault
407
+ * page leads with the user's MATCHING standing entries. */
408
+ export type VaultRequestKind =
409
+ | "login" | "password" | "api_key" | "payment_card" | "env_file" | "note" | "other";
410
+
411
+ /** One STANDING vault entry usable for a session (the catalog read,
412
+ * 2026-08-31) — labels, kinds, and field NAMES only; values are write-only
413
+ * and never on this wire. */
414
+ export interface VaultCatalogEntry {
415
+ label: string;
416
+ kind: string;
417
+ fieldKeys: string[];
418
+ via: "owner" | "member" | "project";
419
+ ownerName: string | null;
420
+ projectName: string | null;
421
+ lastUsedAt: string | null;
384
422
  }
385
423
 
386
424
  /** A vault link's polled status. */
@@ -389,6 +427,26 @@ export interface SessionSecretRequestStatus {
389
427
  status: "pending" | "fulfilled" | "cancelled" | "expired";
390
428
  keys: string[];
391
429
  expiresAt: string;
430
+ /** Cancellation attribution (task #111) — present on cancelled rows:
431
+ * who ended the ask ('human' = denied; 'agent' = withdrawn) and why. */
432
+ cancelledVia?: "human" | "agent";
433
+ cancelReason?: string | null;
434
+ /** The denying human's display name (best-effort; 'human' via only). */
435
+ deniedByName?: string | null;
436
+ /** Newest USER message in the session SINCE the mint (pending rows only)
437
+ * — the chat-interrupt probe: the human may be answering in chat while
438
+ * the agent blocks on --wait. */
439
+ userMessageAt?: string;
440
+ }
441
+
442
+ /** One OPEN vault request, as the list endpoint returns it — receipts
443
+ * only (keys + reason + clocks), never values. */
444
+ export interface SessionSecretRequestSummary {
445
+ requestId: string;
446
+ keys: string[];
447
+ reason: string | null;
448
+ expiresAt: string;
449
+ createdAt: string;
392
450
  }
393
451
 
394
452
  export interface CreateApiKeyInput {
@@ -515,6 +573,23 @@ export interface DriveRepoLink {
515
573
  * auto-unlink; capped at 100 branches). Explicit `trackedBranches`:
516
574
  * index 0 is the PRIMARY and keeps the plain placement; every additional
517
575
  * branch lands at the sibling `dirPrefix@<sanitized-branch>` placement. */
576
+ /** POST /factories/:slug/repo-links/native — a repo whose origin IS the
577
+ * platform git plane (no GitHub side). `name` is ONE segment of GitHub's
578
+ * repo alphabet; `dirPrefix` defaults to `repos/<name>`. */
579
+ export interface CreateNativeRepoInput {
580
+ name: string;
581
+ dirPrefix?: string;
582
+ }
583
+
584
+ export interface CreateNativeRepoResult {
585
+ link: DriveRepoLink;
586
+ /** Stock-git clone/push URL on the platform origin (credentials are
587
+ * minted per operation by `agentc repos git-credential --link`). */
588
+ repoUrl: string;
589
+ /** The CLI one-liner: `agentc repos clone <id> --via origin`. */
590
+ cloneCommand: string;
591
+ }
592
+
518
593
  export interface CreateDriveRepoLinkInput {
519
594
  dirPrefix: string;
520
595
  repoFullName: string;
@@ -524,3 +599,48 @@ export interface CreateDriveRepoLinkInput {
524
599
  /** Two-way sync ("push-out") — defaults to TRUE for new links. */
525
600
  pushOut?: boolean;
526
601
  }
602
+
603
+ /** A retained merge conflict. Resolution edits a live session branch;
604
+ * publication still goes through its normal merge and approval gates. */
605
+ /** A project-scoped secret's METADATA (scoped-secrets spec §4.1) — names
606
+ * only; values are write-only. Reaches SESSIONS linked into the project whose
607
+ * owner is a live member; workflow runs never receive project secrets. */
608
+ export interface ProjectSecretMeta {
609
+ id: string;
610
+ projectId: string;
611
+ secretKey: string;
612
+ createdBy: string | null;
613
+ delivery: "injected" | "brokered";
614
+ brokerHost: string | null;
615
+ brokerHeaderName: string | null;
616
+ brokerHeaderScheme: string | null;
617
+ createdAt: string | null;
618
+ updatedAt: string | null;
619
+ }
620
+
621
+ export interface ProjectSecretList {
622
+ secrets: ProjectSecretMeta[];
623
+ tier: "project";
624
+ audience: "sessions";
625
+ }
626
+
627
+ export interface FactoryFileConflict {
628
+ id: string;
629
+ path: string;
630
+ conversationId: string | null;
631
+ runId: string | null;
632
+ runLabel: string | null;
633
+ reason: "same_region" | "binary" | "delete_vs_edit";
634
+ oursHash: string | null;
635
+ theirsHash: string | null;
636
+ baseHash: string | null;
637
+ status: string;
638
+ createdAt: string;
639
+ resolvesTo: "branch" | "main";
640
+ }
641
+ export interface FactoryFileConflictList {
642
+ object: "list";
643
+ data: FactoryFileConflict[];
644
+ has_more: boolean;
645
+ next_cursor: string | null;
646
+ }
@@ -130,6 +130,15 @@ export interface ConversationTurnStateEvent {
130
130
  pendingCount: number;
131
131
  at: number;
132
132
  partial: true;
133
+ /** When the open turn started (ms) — carried by the connect-time
134
+ * snapshot frame only; absent on live transition frames. */
135
+ startedAt?: number | null;
136
+ /** The session holds a live background-work lease (snapshot frames
137
+ * only). */
138
+ leaseHeld?: boolean;
139
+ /** The open turn is blocked on a HUMAN ask — an open vault/secret
140
+ * request or approval (snapshot frames only). */
141
+ blockedOnAsk?: "secrets" | "approval" | null;
133
142
  }
134
143
 
135
144
  /** One attached surface (a dashboard tab, an `agentc session` TUI process)
@@ -185,6 +194,19 @@ export interface ConversationSessionStatusEvent {
185
194
  at: number;
186
195
  }
187
196
 
197
+ /** Live-only tool-run pulse (2026-08-29): the running turn's guest session
198
+ * PROVED progress (CPU/output counters advanced between heartbeat pulse
199
+ * samples) while the stream was otherwise silent — a long foreground tool
200
+ * (`bun install`) is working. Emitted at the executor's evidence-tick
201
+ * cadence, only on proof; carries no output content. Same contract as
202
+ * `turn_state`: never persisted, never advances `Last-Event-ID`. */
203
+ export interface ConversationToolPulseEvent {
204
+ event: "tool_pulse";
205
+ conversationId: string;
206
+ advancing: true;
207
+ at: number;
208
+ }
209
+
188
210
  export type ConversationStreamEvent =
189
211
  | ConversationPartEvent
190
212
  | ConversationPartPartialEvent
@@ -195,7 +217,8 @@ export type ConversationStreamEvent =
195
217
  | ConversationReplayContinueEvent
196
218
  | ConversationTurnStateEvent
197
219
  | ConversationPresenceEvent
198
- | ConversationSessionStatusEvent;
220
+ | ConversationSessionStatusEvent
221
+ | ConversationToolPulseEvent;
199
222
 
200
223
  /**
201
224
  * Fold one parsed SSE frame into a `ConversationStreamEvent`.