@agent-compose/sdk 0.8.4 → 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 +10 -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 +6 -4
  7. package/dist/index.js +1024 -39
  8. package/dist/runtimes/_cli-agent.d.ts +106 -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 +1048 -57
  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 +1 -1
  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 +118 -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 +11 -5
  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 +17 -3
  32. package/src/runtimes/_cli-agent.ts +313 -22
  33. package/src/runtimes/claude-code.ts +249 -12
  34. package/src/runtimes/openai-desktop.ts +82 -19
  35. package/src/sandbox/devbox.ts +5 -5
  36. package/src/sandbox/providers/e2b.ts +60 -16
  37. package/src/sandbox/registry.ts +19 -1
  38. package/src/sandbox/sizes.ts +11 -5
  39. package/src/sandbox.ts +1 -0
  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 +113 -1
  44. package/src/types/runtime.ts +63 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * openai-desktop runner spec — coordinate spaces (the incident pin).
3
+ *
4
+ * The model sees every screenshot downscaled to DISPLAY (1024×720) and emits
5
+ * coordinates in THAT space; the runner must map them back through EACH
6
+ * capture's REAL geometry — read from the screenshot buffer itself — before
7
+ * touching the provider. Pinned here:
8
+ *
9
+ * - captureScaledScreenshot derives scaleX/scaleY from the frame's own
10
+ * pixel dimensions, and two frames of differing geometry yield differing
11
+ * scales (the fixed-constants regression);
12
+ * - the legacy 1280×800 constants apply ONLY as a fallback when a frame's
13
+ * metadata carries no dimensions;
14
+ * - click/double/right/middle/move and BOTH drag endpoints scale; typed
15
+ * text, key chords, and scroll tick counts pass through untouched;
16
+ * - the sendMessage loop applies the CURRENT capture's scale to the calls
17
+ * the model made against that capture, and refreshes the scale with
18
+ * every new frame it sends back.
19
+ */
20
+ export {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tool-run pulse (child-telemetry contract, 2026-08-29) — the guest
3
+ * heartbeat's sample of the runner's own session, carried as the durable
4
+ * probe's trailing field. Pins:
5
+ *
6
+ * 1. the pulse token round-trips through the probe line (7th field),
7
+ * and its absence (`-`, old guests) parses evidence-absent;
8
+ * 2. parseToolPulseToken rejects everything malformed — never a guess;
9
+ * 3. the probe command with a pulsePath reads it with the char
10
+ * whitelist; without one it emits the `-` placeholder (the shape
11
+ * stays 7 fields either way);
12
+ * 4. the heartbeat fragment writes the pulse file and never breaks the
13
+ * beat on a guest without procps (`ps` failing degrades quiet);
14
+ * 5. a 5-field (pre-perf) and 6-field (pre-pulse) probe line still
15
+ * parse — old guests are evidence-absent, never errors.
16
+ */
17
+ export {};
@@ -5,13 +5,13 @@
5
5
  * This module is the SINGLE SOURCE for the machine's template identity: the
6
6
  * recipe lives in `infra/e2b-template/devbox.ts`, `infra/e2b-template/build.ts`
7
7
  * bakes it under the alias below, and the server imports the alias from here to
8
- * provision a machine (exactly how `e2bBaseTemplate` reaches the E2B provider —
9
- * a stable ALIAS, never a snapshot id, so the same string resolves in whichever
10
- * E2B account a deployment uses).
8
+ * provision a machine (exactly how `e2bAgentEnvTemplate` reaches the E2B
9
+ * provider — a stable ALIAS, never a snapshot id, so the same string resolves
10
+ * in whichever E2B account a deployment uses).
11
11
  *
12
12
  * Interactive only. Workflow RUNS never execute on a devbox (ADR-0038 §2.6):
13
- * runs boot the clean per-size `agent-compose-base-<size>` / `agent-env-<size>`
14
- * templates so reproducibility never inherits hand-configured drift.
13
+ * runs boot the clean per-size `agent-env-<size>` template (the same image
14
+ * sessions boot) so reproducibility never inherits hand-configured drift.
15
15
  */
16
16
  /** Stable E2B template ALIAS for the per-member desktop machine. Baked by
17
17
  * `infra/e2b-template/build.ts` from `infra/e2b-template/devbox.ts`; CI rebakes
@@ -9,6 +9,18 @@ import type { SandboxProvider } from "../types/sandbox.js";
9
9
  import type { SandboxCreateOpts, SandboxProviderDef, OwnedSandbox } from "./provider-def.js";
10
10
  export type SandboxProviderName = "vercel" | "e2b" | "e2b-desktop";
11
11
  export declare const SANDBOX_PROVIDERS: Record<string, SandboxProviderDef>;
12
+ /** Did a CREATE fail because its create SOURCE (template alias / snapshot id)
13
+ * does not resolve on the provider — vs any other create fault? The server's
14
+ * recovery-boot fall-through keys on this: a ring snapshot licensed on an
15
+ * INDETERMINATE existence probe that turns out to be gone must fall through
16
+ * the ring toward the base image, while every other create fault (quota,
17
+ * auth, 5xx, network) must propagate — falling through on those would trade
18
+ * a still-recoverable machine state for a base boot. E2B maps the create-
19
+ * time 404 ("template not found") to its typed `NotFoundError`; the message
20
+ * match is the fallback for untyped transports (Vercel). Lives in the SDK
21
+ * because the server holds its OWN `e2b` module instance — an `instanceof`
22
+ * against the class from the wrong copy is always false. */
23
+ export declare function isSandboxSourceNotFoundError(error: unknown): boolean;
12
24
  /** Provision a sandbox for the named provider. */
13
25
  export declare function createSandbox(provider: SandboxProviderName, opts: SandboxCreateOpts): Promise<SandboxProvider>;
14
26
  /** Reconnect to an existing sandbox by provider + provider-native sandbox ID. */
@@ -138,12 +138,18 @@ export declare function sandboxSizeLabel(size: SandboxSize): string;
138
138
  /** Stable E2B template ALIAS for the platform base at a given size
139
139
  * (`agent-compose-base-<size>`). Aliases — not snapshot ids — so the refs are
140
140
  * multi-account-clean: the same string resolves in any E2B account that built
141
- * the templates. Built by `infra/e2b-template/build.ts`; the E2B provider
142
- * boots this when a run on E2B declares no explicit `bootFrom`/template. */
141
+ * the templates. Built by `infra/e2b-template/build.ts`. An EXPLICIT
142
+ * `bootFrom`/template target only — nothing boots it by default: template-less
143
+ * E2B creates resolve `e2bAgentEnvTemplate` (the session-identical image)
144
+ * instead, so the run and session lanes can never diverge on baked tooling. */
143
145
  export declare function e2bBaseTemplate(size: SandboxSize): string;
144
- /** Stable E2B template ALIAS for the agent runtime (base + claude binary) at a
145
- * given size (`agent-env-<size>`). The agent default templates boot from this
146
- * via `bootFrom: { snapshotId: e2bAgentEnvTemplate(size) }`. Multi-account-clean
146
+ /** Stable E2B template ALIAS for the agent runtime (base + claude binary +
147
+ * dev toolbelt + session desktop) at a given size (`agent-env-<size>`).
148
+ * THE default E2B boot image, both lanes: every cloud SESSION boots it
149
+ * (server/src/sandbox/persistent.ts), and every template-less E2B workflow
150
+ * RUN boots it too (the e2b provider's size-matched default). The agent
151
+ * default templates boot from this via
152
+ * `bootFrom: { snapshotId: e2bAgentEnvTemplate(size) }`. Multi-account-clean
147
153
  * for the same reason as `e2bBaseTemplate`. */
148
154
  export declare function e2bAgentEnvTemplate(size: SandboxSize): string;
149
155
  /** Is `id` one of the platform-managed E2B template aliases — a
package/dist/sandbox.d.ts CHANGED
@@ -24,5 +24,5 @@ export { makeSandboxProvider, e2bMaxSandboxMs } from "./sandbox/providers/e2b.js
24
24
  export { makeDesktopSandboxProvider } from "./sandbox/providers/desktop.js";
25
25
  export { VERCEL_MAX_TAGS, buildVercelTags } from "./sandbox/providers/vercel.js";
26
26
  export { makeLocalSandboxProvider } from "./sandbox/providers/local.js";
27
- export { SANDBOX_PROVIDERS, createSandbox, reconnectSandbox, deleteSandboxSnapshot, snapshotResolves, getSandboxQuotas, listOwnedSandboxes, killSandboxById, killAllSandboxes, } from "./sandbox/registry.js";
27
+ export { SANDBOX_PROVIDERS, createSandbox, reconnectSandbox, deleteSandboxSnapshot, snapshotResolves, isSandboxSourceNotFoundError, getSandboxQuotas, listOwnedSandboxes, killSandboxById, killAllSandboxes, } from "./sandbox/registry.js";
28
28
  export type { SandboxProviderName, SandboxQuotaResult, OwnedSandboxResult } from "./sandbox/registry.js";
@@ -54,10 +54,10 @@ export type StepResult<TOutput = unknown> = {
54
54
  export declare const StepPauseRequestSchema: z.ZodObject<{
55
55
  reason: z.ZodString;
56
56
  kind: z.ZodEnum<{
57
+ event: "event";
57
58
  custom: "custom";
58
59
  decision: "decision";
59
60
  sleep: "sleep";
60
- event: "event";
61
61
  }>;
62
62
  payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
63
63
  ttlMs: z.ZodOptional<z.ZodNumber>;
@@ -432,6 +432,48 @@ export interface BackgroundWorkHeld {
432
432
  /** ISO timestamp the lease now runs to. */
433
433
  leaseUntil: string;
434
434
  }
435
+ /** One background CHILD declared beside the busy lease (task #63): a
436
+ * detached process, by pid, with its durable journal/log file — what lets
437
+ * the platform VERIFY the work (`/proc/<pid>` + journal mtime) and
438
+ * reattach it after a park instead of losing it. */
439
+ export interface BackgroundWorkChildDecl {
440
+ pid: number;
441
+ /** Absolute guest-side path to the child's own durable output/journal
442
+ * file — its mtime is the progress evidence. */
443
+ journalPath?: string;
444
+ label?: string;
445
+ }
446
+ /** Outcome of requesting one machine size up
447
+ * (`POST /conversations/:id/machine/request-upsize` — task #110, the
448
+ * auto-resize policy's agent door). The platform arbitrates: within the
449
+ * team's daily cap the upsize is auto-granted (and lands immediately when
450
+ * no turn/background work holds the machine); past it a human approval
451
+ * card is posted. Wire shape mirrors
452
+ * server/src/sandbox/session-auto-resize.ts `UpsizeRequestOutcome`. */
453
+ export interface MachineUpsizeOutcome {
454
+ /** "executed" (landed now), "granted" (lands when the work settles),
455
+ * "pending_approval" (a card awaits the owner). Refusals arrive as HTTP
456
+ * errors carrying `code`. */
457
+ outcome: "executed" | "granted" | "pending_approval";
458
+ /** The size the grant/card names (e.g. "4vcpu-8gb"). */
459
+ size: string;
460
+ approvalId: string;
461
+ /** One human-readable line the CLI can print verbatim. */
462
+ message: string;
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 & {
471
+ declaredAt: string;
472
+ }>;
473
+ /** Non-null when a mid-work park froze declared children and the session
474
+ * still owes its conversation a status report (ISO park instant). */
475
+ frozenAt: string | null;
476
+ }
435
477
  /** One changed file on a session's drive branch relative to `main`. */
436
478
  export interface SessionFileChange {
437
479
  /** Factory-relative path. */
@@ -510,11 +552,9 @@ export interface SessionChangeSet {
510
552
  * (hard cap 2; a manual re-review resets it). */
511
553
  reviewAutoFollowup?: boolean;
512
554
  reviewAutoRounds?: number;
513
- /** The OPT-IN main-advance auto-rebase reflex (additive — absent on
514
- * older servers; default OFF): armed ⇒ when main moves and a preflight
515
- * proves the fold clean, the platform rebases this session's branch
516
- * from main headlessly. Toggled via `setSessionAutoRebase`. */
517
- autoRebaseFromMain?: boolean;
555
+ /** The session's selectable cap on automatic follow-up rounds (additive
556
+ * — absent on older servers; default 2, range 1..5). */
557
+ reviewAutoRoundsMax?: number;
518
558
  /** STRUCTURED review suggestions beside the notes (additive — absent on
519
559
  * older servers): individually actionable {id, path, title, rationale,
520
560
  * patch} entries the review session wrote back, status-stamped
@@ -801,13 +841,6 @@ export interface SessionRebaseReport {
801
841
  * skipped whole (an unlisted conflicted path must never be overwritten). */
802
842
  foldSkipped?: boolean;
803
843
  }
804
- /** Result of `POST /conversations/:id/changes/autorebase` — the opt-in
805
- * main-advance auto-rebase reflex's new state. */
806
- export interface SessionAutoRebaseState {
807
- object: "session_autorebase";
808
- conversationId: string;
809
- autoRebaseFromMain: boolean;
810
- }
811
844
  /** The sender's page stamp (HUD bar sends) — persisted server-side, never
812
845
  * echoed back on the wire. Mirrors the server's `PageContext` schema. */
813
846
  export interface ConversationPageContext {
@@ -996,3 +1029,43 @@ export interface StreamConversationOptions {
996
1029
  lastEventId?: number;
997
1030
  signal?: AbortSignal;
998
1031
  }
1032
+ /** Shared chat creation includes its own Ivy unless explicitly disabled. */
1033
+ export interface CreateChatInput {
1034
+ title?: string;
1035
+ visibility: "shared";
1036
+ access?: "public" | "private";
1037
+ memberIds?: string[];
1038
+ includeIvy?: boolean;
1039
+ }
1040
+ export interface ChannelIvyState {
1041
+ projects: Array<{
1042
+ id: string;
1043
+ name: string;
1044
+ }>;
1045
+ agent: AgentListRow | null;
1046
+ enabled: boolean;
1047
+ agentId: string | null;
1048
+ canManage: boolean;
1049
+ nextReviewAt: string | null;
1050
+ work: Array<{
1051
+ id: string;
1052
+ title: string;
1053
+ status: string;
1054
+ evidence: string;
1055
+ nextAction: string;
1056
+ waitingOn: string | null;
1057
+ reviewAt: string | null;
1058
+ }>;
1059
+ }
1060
+ export interface ProjectIvyConnection {
1061
+ id: string;
1062
+ teamId: string;
1063
+ projectId: string;
1064
+ provider: "github";
1065
+ installationId: string;
1066
+ accountLogin: string;
1067
+ repositories: string[];
1068
+ allowWrites: boolean;
1069
+ configuredBy: string | null;
1070
+ updatedAt: string;
1071
+ }
@@ -100,9 +100,15 @@ export interface RegisterWorkflowInput {
100
100
  export interface TemplateRow {
101
101
  name: string;
102
102
  version: string;
103
- factorySlug: string;
103
+ /** Owning factory's slug. `null` on platform-published rows — a published
104
+ * default belongs to no single factory and is invokable via any slug. */
105
+ factorySlug: string | null;
104
106
  /** Share scope (ADR-0045) — `null` = unscoped/grandfathered team tier. */
105
107
  scope: ArtifactScope | null;
108
+ /** `factory` = registered in a factory of the caller's team; `published` =
109
+ * platform default (team-wide read+invoke in EVERY factory, never write;
110
+ * fork it to edit under a new name). Optional: older servers omit it. */
111
+ origin?: "factory" | "published";
106
112
  }
107
113
  /** Template detail (`GET /factories/:slug/templates/:name`) — the typed
108
114
  * subset the SDK pins; the route returns additional registration fields
@@ -122,6 +128,16 @@ export interface TemplateDetail {
122
128
  export interface ListTemplatesOptions {
123
129
  factorySlug?: string;
124
130
  }
131
+ /** One team connector grant, as `GET /api/v1/connectors` returns it (wire
132
+ * shape verbatim — snake_case). The typed subset the SDK pins; additional
133
+ * fields flow through untyped. */
134
+ export interface ConnectorGrantSummary {
135
+ id: string;
136
+ provider: string;
137
+ external_account_label: string | null;
138
+ status: "active" | "revoked" | "reauth_required";
139
+ connected_by_user_id: string;
140
+ }
125
141
  /** Public "anyone with the link" state — the additive `public` field on file
126
142
  * rows (list/ls/search). The token is the whole capability, so an enabled
127
143
  * row carries it ONLY for viewers holding the document's share authority
@@ -352,6 +368,24 @@ export interface SessionSecretRequestCreated {
352
368
  requestId: string;
353
369
  url: string;
354
370
  expiresAt: string;
371
+ /** True = a standing auto-approve set already filled the request at the
372
+ * mint — nothing is pending; source the session env now. */
373
+ autoApproved?: boolean;
374
+ }
375
+ /** What KIND of credential a vault request asks for (advisory): the vault
376
+ * page leads with the user's MATCHING standing entries. */
377
+ export type VaultRequestKind = "login" | "password" | "api_key" | "payment_card" | "env_file" | "note" | "other";
378
+ /** One STANDING vault entry usable for a session (the catalog read,
379
+ * 2026-08-31) — labels, kinds, and field NAMES only; values are write-only
380
+ * and never on this wire. */
381
+ export interface VaultCatalogEntry {
382
+ label: string;
383
+ kind: string;
384
+ fieldKeys: string[];
385
+ via: "owner" | "member" | "project";
386
+ ownerName: string | null;
387
+ projectName: string | null;
388
+ lastUsedAt: string | null;
355
389
  }
356
390
  /** A vault link's polled status. */
357
391
  export interface SessionSecretRequestStatus {
@@ -359,6 +393,25 @@ export interface SessionSecretRequestStatus {
359
393
  status: "pending" | "fulfilled" | "cancelled" | "expired";
360
394
  keys: string[];
361
395
  expiresAt: string;
396
+ /** Cancellation attribution (task #111) — present on cancelled rows:
397
+ * who ended the ask ('human' = denied; 'agent' = withdrawn) and why. */
398
+ cancelledVia?: "human" | "agent";
399
+ cancelReason?: string | null;
400
+ /** The denying human's display name (best-effort; 'human' via only). */
401
+ deniedByName?: string | null;
402
+ /** Newest USER message in the session SINCE the mint (pending rows only)
403
+ * — the chat-interrupt probe: the human may be answering in chat while
404
+ * the agent blocks on --wait. */
405
+ userMessageAt?: string;
406
+ }
407
+ /** One OPEN vault request, as the list endpoint returns it — receipts
408
+ * only (keys + reason + clocks), never values. */
409
+ export interface SessionSecretRequestSummary {
410
+ requestId: string;
411
+ keys: string[];
412
+ reason: string | null;
413
+ expiresAt: string;
414
+ createdAt: string;
362
415
  }
363
416
  export interface CreateApiKeyInput {
364
417
  name?: string;
@@ -480,6 +533,21 @@ export interface DriveRepoLink {
480
533
  * auto-unlink; capped at 100 branches). Explicit `trackedBranches`:
481
534
  * index 0 is the PRIMARY and keeps the plain placement; every additional
482
535
  * branch lands at the sibling `dirPrefix@<sanitized-branch>` placement. */
536
+ /** POST /factories/:slug/repo-links/native — a repo whose origin IS the
537
+ * platform git plane (no GitHub side). `name` is ONE segment of GitHub's
538
+ * repo alphabet; `dirPrefix` defaults to `repos/<name>`. */
539
+ export interface CreateNativeRepoInput {
540
+ name: string;
541
+ dirPrefix?: string;
542
+ }
543
+ export interface CreateNativeRepoResult {
544
+ link: DriveRepoLink;
545
+ /** Stock-git clone/push URL on the platform origin (credentials are
546
+ * minted per operation by `agentc repos git-credential --link`). */
547
+ repoUrl: string;
548
+ /** The CLI one-liner: `agentc repos clone <id> --via origin`. */
549
+ cloneCommand: string;
550
+ }
483
551
  export interface CreateDriveRepoLinkInput {
484
552
  dirPrefix: string;
485
553
  repoFullName: string;
@@ -489,3 +557,45 @@ export interface CreateDriveRepoLinkInput {
489
557
  /** Two-way sync ("push-out") — defaults to TRUE for new links. */
490
558
  pushOut?: boolean;
491
559
  }
560
+ /** A retained merge conflict. Resolution edits a live session branch;
561
+ * publication still goes through its normal merge and approval gates. */
562
+ /** A project-scoped secret's METADATA (scoped-secrets spec §4.1) — names
563
+ * only; values are write-only. Reaches SESSIONS linked into the project whose
564
+ * owner is a live member; workflow runs never receive project secrets. */
565
+ export interface ProjectSecretMeta {
566
+ id: string;
567
+ projectId: string;
568
+ secretKey: string;
569
+ createdBy: string | null;
570
+ delivery: "injected" | "brokered";
571
+ brokerHost: string | null;
572
+ brokerHeaderName: string | null;
573
+ brokerHeaderScheme: string | null;
574
+ createdAt: string | null;
575
+ updatedAt: string | null;
576
+ }
577
+ export interface ProjectSecretList {
578
+ secrets: ProjectSecretMeta[];
579
+ tier: "project";
580
+ audience: "sessions";
581
+ }
582
+ export interface FactoryFileConflict {
583
+ id: string;
584
+ path: string;
585
+ conversationId: string | null;
586
+ runId: string | null;
587
+ runLabel: string | null;
588
+ reason: "same_region" | "binary" | "delete_vs_edit";
589
+ oursHash: string | null;
590
+ theirsHash: string | null;
591
+ baseHash: string | null;
592
+ status: string;
593
+ createdAt: string;
594
+ resolvesTo: "branch" | "main";
595
+ }
596
+ export interface FactoryFileConflictList {
597
+ object: "list";
598
+ data: FactoryFileConflict[];
599
+ has_more: boolean;
600
+ next_cursor: string | null;
601
+ }
@@ -119,6 +119,15 @@ export interface ConversationTurnStateEvent {
119
119
  pendingCount: number;
120
120
  at: number;
121
121
  partial: true;
122
+ /** When the open turn started (ms) — carried by the connect-time
123
+ * snapshot frame only; absent on live transition frames. */
124
+ startedAt?: number | null;
125
+ /** The session holds a live background-work lease (snapshot frames
126
+ * only). */
127
+ leaseHeld?: boolean;
128
+ /** The open turn is blocked on a HUMAN ask — an open vault/secret
129
+ * request or approval (snapshot frames only). */
130
+ blockedOnAsk?: "secrets" | "approval" | null;
122
131
  }
123
132
  /** One attached surface (a dashboard tab, an `agentc session` TUI process)
124
133
  * as carried on a `presence` beat. Clients key the roster on `clientId` —
@@ -169,7 +178,19 @@ export interface ConversationSessionStatusEvent {
169
178
  state: "running" | "idle";
170
179
  at: number;
171
180
  }
172
- export type ConversationStreamEvent = ConversationPartEvent | ConversationPartPartialEvent | ConversationMessageDoneEvent | ConversationErrorEvent | ConversationReactionEvent | ConversationMessageDeletedEvent | ConversationReplayContinueEvent | ConversationTurnStateEvent | ConversationPresenceEvent | ConversationSessionStatusEvent;
181
+ /** Live-only tool-run pulse (2026-08-29): the running turn's guest session
182
+ * PROVED progress (CPU/output counters advanced between heartbeat pulse
183
+ * samples) while the stream was otherwise silent — a long foreground tool
184
+ * (`bun install`) is working. Emitted at the executor's evidence-tick
185
+ * cadence, only on proof; carries no output content. Same contract as
186
+ * `turn_state`: never persisted, never advances `Last-Event-ID`. */
187
+ export interface ConversationToolPulseEvent {
188
+ event: "tool_pulse";
189
+ conversationId: string;
190
+ advancing: true;
191
+ at: number;
192
+ }
193
+ export type ConversationStreamEvent = ConversationPartEvent | ConversationPartPartialEvent | ConversationMessageDoneEvent | ConversationErrorEvent | ConversationReactionEvent | ConversationMessageDeletedEvent | ConversationReplayContinueEvent | ConversationTurnStateEvent | ConversationPresenceEvent | ConversationSessionStatusEvent | ConversationToolPulseEvent;
173
194
  /**
174
195
  * Fold one parsed SSE frame into a `ConversationStreamEvent`.
175
196
  *
@@ -144,6 +144,71 @@ export interface AgentMessageTaskNotification extends AgentMessageBase {
144
144
  durationMs?: number;
145
145
  };
146
146
  }
147
+ /** One entry of a harness workflow's live progress feed — the
148
+ * `workflow_progress` array claude-code's `system`/`task_progress` events
149
+ * carry for its in-harness Workflow tool (the dynamic-workflow
150
+ * orchestrator). `phase` entries are the script's declared phases (seeded
151
+ * up front, 1-based `index`); `agent` entries are the spawned workflow
152
+ * agents, updated in place as they queue → run → settle. Preview text the
153
+ * harness includes (prompt/result previews) is internal plumbing and is
154
+ * deliberately NOT forwarded — same rule as task notifications. */
155
+ export type WorkflowProgressEntry = {
156
+ kind: "phase";
157
+ index: number;
158
+ title: string;
159
+ } | {
160
+ kind: "agent";
161
+ index: number;
162
+ label: string;
163
+ /** Lifecycle: `start` (spawned) / `progress` (heartbeat) are live;
164
+ * `done` / `error` are settled. Verbatim from the harness. */
165
+ state: "start" | "progress" | "done" | "error";
166
+ phaseIndex?: number;
167
+ phaseTitle?: string;
168
+ model?: string;
169
+ agentId?: string;
170
+ /** Self-reported usage so far (cumulative for this agent). */
171
+ tokens?: number;
172
+ toolCalls?: number;
173
+ durationMs?: number;
174
+ /** Epoch ms the agent actually started (for live elapsed). */
175
+ startedAt?: number;
176
+ /** The failure message when `state: "error"`, clamped. */
177
+ error?: string;
178
+ /** Replayed from a resume cache — settled instantly, no fresh spend. */
179
+ cached?: true;
180
+ /** Skipped by the user (workflow dialog) — an error state that is
181
+ * not a failure. */
182
+ skipped?: true;
183
+ };
184
+ /** LIVE progress of a harness BACKGROUND task (claude-code
185
+ * `system`/`task_progress`). For the in-harness Workflow tool the message
186
+ * carries the CUMULATIVE `workflow_progress` entry array (the harness
187
+ * re-sends the whole picture: state changes immediately, heartbeats
188
+ * throttled ~10s), so a consumer treats the latest message as
189
+ * authoritative per entry `(kind, index)`. A plain background AGENT task
190
+ * (Task tool, run_in_background) heartbeats on the same event with NO
191
+ * workflow entries — forwarded with `workflow: []` as liveness evidence
192
+ * so the platform can declare the running task as session background
193
+ * work; its completion evidence rides `task_notification`. `toolUseId`
194
+ * names the spawning call — the correlation key to its card. Additive
195
+ * kind: existing producers never emit it. */
196
+ export interface AgentMessageTaskProgress extends AgentMessageBase {
197
+ type: "task_progress";
198
+ /** The harness's background task id. */
199
+ taskId: string;
200
+ /** The SPAWNING Workflow/Task call's tool_use id, when carried. */
201
+ toolUseId?: string;
202
+ /** The task's cumulative usage totals so far. */
203
+ usage?: {
204
+ tokens?: number;
205
+ toolUses?: number;
206
+ durationMs?: number;
207
+ };
208
+ /** The cumulative workflow progress entries — EMPTY for a plain
209
+ * background Agent-task heartbeat (only Workflow tasks carry entries). */
210
+ workflow: WorkflowProgressEntry[];
211
+ }
147
212
  /** HARNESS-authored notice text — content the CLI composed itself rather
148
213
  * than the model speaking: slash-command stdout, model/skills advisories,
149
214
  * queued-input notes. claude-code marks these structurally (assistant
@@ -156,7 +221,59 @@ export interface AgentMessageHarnessNotice extends AgentMessageBase {
156
221
  type: "harness_notice";
157
222
  text: string;
158
223
  }
159
- export type AgentMessage = AgentMessageInit | AgentMessageText | AgentMessageTextDelta | AgentMessageThinking | AgentMessageToolUse | AgentMessageToolResult | AgentMessageDone | AgentMessageError | AgentMessageUsage | AgentMessageUsageDelta | AgentMessagePlan | AgentMessageTaskNotification | AgentMessageHarnessNotice;
224
+ /** Context-compaction lifecycle — the harness summarizing its own
225
+ * conversation to reclaim context. Mapped 1:1 from claude-code's wire
226
+ * (verified live on 2.1.212, the baked sandbox pin, and 2.1.241 — both
227
+ * emit the identical shapes, `/compact` and auto alike):
228
+ *
229
+ * `system`/`status` `{status:"compacting"}` → phase "start"
230
+ * `system`/`status` `{status:null, compact_result, → phase "settled"
231
+ * compact_error?}`
232
+ * `system`/`compact_boundary` `{compact_metadata: → phase "boundary"
233
+ * {trigger, pre_tokens, post_tokens,
234
+ * cumulative_dropped_tokens, duration_ms, …}}`
235
+ *
236
+ * Order on the wire: start → settled → (fresh init) → boundary → the
237
+ * continuation summary as a SYNTHETIC user message (never forwarded — it
238
+ * quotes conversation content verbatim). "boundary" only follows a
239
+ * successful settle and only on the turn the compaction ran (verified: it
240
+ * does NOT replay on later resumes). A compaction can span MINUTES of
241
+ * otherwise-silent stream — the whole point of forwarding it is that
242
+ * downstream can show the silence as work (the 2026-08-23 dead-air
243
+ * incident: 94% auto-compact read as a dead session). Additive kind:
244
+ * existing producers never emit it. */
245
+ export interface AgentMessageCompaction extends AgentMessageBase {
246
+ type: "compaction";
247
+ phase: "start" | "settled" | "boundary";
248
+ /** settled: how it ended. Absent on start/boundary (a boundary IS a
249
+ * success by construction — the harness only emits it after one). */
250
+ result?: "success" | "failed";
251
+ /** settled+failed: the harness's own reason, clamped. */
252
+ error?: string;
253
+ /** boundary: what initiated the compaction. */
254
+ trigger?: "auto" | "manual";
255
+ /** boundary: context tokens before / after, dropped total, wall time. */
256
+ preTokens?: number;
257
+ postTokens?: number;
258
+ droppedTokens?: number;
259
+ durationMs?: number;
260
+ }
261
+ /** A user-role message landing INSIDE a subagent's thread — the delivered
262
+ * form of a steer (the parent's `SendMessage` to a RUNNING child, queued
263
+ * "for delivery at its next tool round") or any other message the harness
264
+ * folds into a child's conversation mid-flight. Emitted ONLY with sidechain
265
+ * attribution: `parentToolUseId` (the spawning Agent/Task call's tool_use
266
+ * id) is REQUIRED — an unattributed user event is the parent's own prompt
267
+ * echo, which stays unmapped as before. Lets renderers show the steer as a
268
+ * user-role message inside the child's mini-session instead of leaving it
269
+ * an opaque SendMessage tool call on the parent only (task #97, owner
270
+ * directive 2026-08-27). Additive kind: existing producers never emit it. */
271
+ export interface AgentMessageSubagentUserMessage extends AgentMessageBase {
272
+ type: "subagent_user_message";
273
+ text: string;
274
+ parentToolUseId: string;
275
+ }
276
+ export type AgentMessage = AgentMessageInit | AgentMessageText | AgentMessageTextDelta | AgentMessageThinking | AgentMessageToolUse | AgentMessageToolResult | AgentMessageDone | AgentMessageError | AgentMessageUsage | AgentMessageUsageDelta | AgentMessagePlan | AgentMessageTaskNotification | AgentMessageTaskProgress | AgentMessageHarnessNotice | AgentMessageCompaction | AgentMessageSubagentUserMessage;
160
277
  /** Status block the agent emits to signal iteration completion or blockers. */
161
278
  export interface AgentStatus {
162
279
  summary: string;
@@ -93,6 +93,20 @@ export interface RuntimeOptions {
93
93
  * Absent ⇒ nothing extra is sourced. Must be a plain absolute path — no
94
94
  * quotes, no `..`; the runtime validates and drops anything else. */
95
95
  credEnvFile?: string;
96
+ /** Durable launch report (boot-time turn adoption): called by the durable
97
+ * detached transport the moment its in-guest runner exists — with the
98
+ * guest prompt path (every durable file derives from it), the exit
99
+ * sentinel string, and the detached wrapper's pid. The caller stamps
100
+ * these on the turn row so a SUCCESSOR process (a deploy roll's new
101
+ * server) can re-attach to the runner's durable `.out` without this
102
+ * process's memory. Fired once per detached launch (a retry with a fresh
103
+ * runner fires again with the new paths); never on transports without
104
+ * durable files. Must not throw — the transport calls it inline. */
105
+ onDetachedLaunch?: (info: {
106
+ promptPath: string;
107
+ sentinel: string;
108
+ pid: number;
109
+ }) => void;
96
110
  }
97
111
  /** Three-valued liveness verdict for a runtime's CURRENT turn, read from
98
112
  * DURABLE guest state (heartbeat file, stdout file, exit sentinel, pid) over
@@ -184,6 +198,25 @@ export interface ModelExecutionContract {
184
198
  * fallback probe it already had; null is never a verdict.
185
199
  */
186
200
  probeTurnLiveness?(): Promise<RunnerLivenessVerdict | null>;
201
+ /**
202
+ * TELEMETRY, NEVER A VERDICT (tool-run pulse, 2026-08-29): the newest
203
+ * tool-run pulse the durable liveness probe carried — the guest
204
+ * heartbeat's sample of the runner's own session (aggregate CPU jiffies,
205
+ * written bytes, live process count) plus the guest clock it was read
206
+ * against. The executor's evidence ticker peeks it AFTER its liveness
207
+ * check and compares successive samples: counters ADVANCING is proof a
208
+ * long silent foreground tool is working, fanned to clients as a live
209
+ * `tool_pulse` frame. Never probes on its own; null before any probe or
210
+ * on a transport without the pulse file. No liveness decision may ever
211
+ * read it.
212
+ */
213
+ peekTurnPulse?(): {
214
+ atMs: number;
215
+ cpuJiffies: number;
216
+ ioBytes: number;
217
+ procs: number;
218
+ guestNowMs: number;
219
+ } | null;
187
220
  /**
188
221
  * DOORBELL, NEVER A VERDICT (exit-event push, v0.10.43): wake the current
189
222
  * turn's durable watchdog NOW so it runs its normal verification pass —
@@ -234,6 +267,44 @@ export interface ModelExecutionContract {
234
267
  * Calls are serialized per turn; never throws.
235
268
  */
236
269
  injectUserMessage?(text: string): Promise<"delivered" | "pending" | "closed" | "unsupported">;
270
+ /**
271
+ * Request an in-band STEP INTERRUPT of the currently running turn — the
272
+ * ESC equivalent. Where `injectUserMessage` queues content for the turn
273
+ * loop's next boundary, this rides the same durable inbox but carries a
274
+ * control line the CLI handles immediately, mid-step included: the
275
+ * running tool call aborts, the run ends within ~100ms, and the guest
276
+ * session stays resumable with the whole turn context (verified live
277
+ * against claude 2.1.236). Verdicts mirror `injectUserMessage`; only
278
+ * "delivered" means the CLI got the control line — callers escalate
279
+ * anything else (and "unsupported": no stream-input turn, or a runtime
280
+ * with no in-band interrupt, e.g. codex) to kill semantics, which stay
281
+ * honest because thread stores are durable and the successor turn
282
+ * resumes them. Never throws.
283
+ */
284
+ interruptTurn?(): Promise<"delivered" | "pending" | "closed" | "unsupported">;
285
+ /**
286
+ * Guest pid of the CURRENT turn's detached runner wrapper (the setsid
287
+ * process-group leader recorded at launch), or null when no detached
288
+ * durable-transport runner is live (boot phase, ACP path, single-exec
289
+ * transports). Advisory identity, NEVER a liveness verdict: the platform
290
+ * reads it to DECLARE harness-reported background work (an in-harness
291
+ * Workflow task) against the process tree that hosts it, so the park
292
+ * machinery can verify the tree from `/proc/<pid>` later. Runtimes
293
+ * without a detached guest simply omit the method.
294
+ */
295
+ currentRunnerPid?(): number | null;
296
+ /**
297
+ * Durable byte offset of the CURRENT turn's `.out` file just past the
298
+ * last line whose messages have ALL been yielded to the consumer — the
299
+ * safe harvest watermark for boot-time turn adoption. Null when no
300
+ * durable-transport turn is live, or before the first line completes.
301
+ * The contract is deliberately one line BEHIND the parse cursor: a
302
+ * caller that persists parts after each yielded message may stamp this
303
+ * offset at any time and a successor re-parses AT MOST the line whose
304
+ * parts were mid-persist (the same crash window the workflow tailer's
305
+ * flush-before-advance ordering accepts). Advisory, never a verdict.
306
+ */
307
+ currentTurnDurableOffset?(): number | null;
237
308
  sendMessage(opts: {
238
309
  prompt: string;
239
310
  sessionId?: string;