@warble/claude-agent-sdk 0.11.0 → 0.13.0

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.
package/dist/index.d.ts CHANGED
@@ -108,6 +108,12 @@ interface LlmCall {
108
108
  * prompt text instead.
109
109
  */
110
110
  when: WhenGuard | null;
111
+ /** Same-profile component aliases this exact step may invoke. */
112
+ component_calls: ComponentCall[];
113
+ }
114
+ interface ComponentCall {
115
+ alias: string;
116
+ component: string;
111
117
  }
112
118
  /**
113
119
  * A closed-vocabulary guard on a conditional `llm_call`: `guard` is one of `on_failure` /
@@ -172,8 +178,36 @@ interface PreconditionResult {
172
178
  status: string;
173
179
  checks: PreconditionCheck[];
174
180
  }
181
+ /**
182
+ * A named position in prompt text, with the alternative wordings that may fill it (IR 0.7).
183
+ *
184
+ * `default` always names one of `variants` — compile refuses otherwise — so a consumer that has no
185
+ * opinion still has something to render. `present_when` is carried but never evaluated here: the
186
+ * spec assigns that to the host, and this back-end only asks the host for the answer.
187
+ */
188
+ interface SlotDecl {
189
+ name: string;
190
+ /** Always a key of `variants`. */
191
+ default: string;
192
+ variants: Record<string, string>;
193
+ /** Present only when the author declared a condition. Carried verbatim; not interpreted here. */
194
+ present_when?: unknown;
195
+ }
196
+ /**
197
+ * One file a component declares it needs, by identity (IR 0.7).
198
+ *
199
+ * Content is deliberately not carried — the deliberate contrast with a slot variant, whose text is.
200
+ * `hash` is `sha256:<hex>` over the bytes and neither field is authorable; both are computed at
201
+ * compile, so an author-written value cannot rot into a claim nothing keeps true.
202
+ */
203
+ interface AssetDecl {
204
+ path: string;
205
+ hash: string;
206
+ bytes: number;
207
+ }
175
208
  interface ComponentNode {
176
209
  id: string;
210
+ entrypoint: boolean;
177
211
  verb: string;
178
212
  type: ComponentType;
179
213
  realization_kind: RealizationKind;
@@ -193,6 +227,11 @@ interface ComponentNode {
193
227
  eval: EvalSpec | null;
194
228
  /** Optional free-form framing shared by every step of this component (see `docs/spec/ir-schema.md`). */
195
229
  brief?: string;
230
+ /** Slots belonging to THIS component's own prompt text (its steps and `brief`). Profile-level
231
+ * slots live on the IR root instead and are deliberately not copied here. */
232
+ slots?: SlotDecl[];
233
+ /** Files this component declares it needs, by identity. Absent when it declares none. */
234
+ assets?: AssetDecl[];
196
235
  }
197
236
  interface WarbleIr {
198
237
  warble_ir_version: string;
@@ -200,6 +239,10 @@ interface WarbleIr {
200
239
  context_binding: ContextBinding;
201
240
  config: IrConfig;
202
241
  components: ComponentNode[];
242
+ /** Profile-level slots, belonging to the profile's `system_prompt`. Names are unique across the
243
+ * whole project — compile refuses a collision with any component's — so a consumer resolves
244
+ * `{{ slot.<name> }}` against one flat namespace rather than a per-layer precedence rule. */
245
+ slots?: SlotDecl[];
203
246
  }
204
247
  /**
205
248
  * IR versions this back-end understands. Older versions (0.1, 0.2) are no longer accepted now that
@@ -215,74 +258,108 @@ declare function parseIr(json: string): WarbleIr;
215
258
  /** Distinct tier names across a node's `llm_calls`, order-preserving. */
216
259
  declare function distinctTiers(calls: readonly LlmCall[]): string[];
217
260
 
261
+ /** One statically-authorized caller-step edge in the materialized profile. */
262
+ interface ComponentDependency {
263
+ caller: string;
264
+ step: string;
265
+ alias: string;
266
+ component: string;
267
+ }
268
+ /** The deterministic transitive closure for one selectable root. */
269
+ interface EntryClosure {
270
+ root: string;
271
+ components: readonly string[];
272
+ }
218
273
  /**
219
- * Target capability profiles — the declarative side of the capability model
220
- * (`docs/spec/capability-model.md`), owned by THIS back-end in TypeScript.
274
+ * Preparation selection before target legalization. `roots` are the only nodes an operation may
275
+ * start; `callees` are addressable only through an edge in `dependencies`.
276
+ */
277
+ interface ComponentClosurePlan {
278
+ roots: readonly ComponentNode[];
279
+ callees: readonly ComponentNode[];
280
+ components: readonly ComponentNode[];
281
+ entries: readonly EntryClosure[];
282
+ dependencies: readonly ComponentDependency[];
283
+ }
284
+ /** Return all declared edges in profile/step/declaration order. */
285
+ declare function componentDependencies(ir: WarbleIr): readonly ComponentDependency[];
286
+ /**
287
+ * Resolve exactly the requested roots and their transitive callees. The reader rechecks the graph
288
+ * rather than trusting compiler provenance so a forged object IR cannot widen or cycle authority.
289
+ */
290
+ declare function resolveComponentClosure(ir: WarbleIr, requestedRoots?: readonly string[]): ComponentClosurePlan;
291
+
292
+ /**
293
+ * What a host says about each slot, keyed by slot name:
221
294
  *
222
- * A runtime target is `engine × mode`. This back-end declares one target, `claude-agent-sdk:local`:
223
- * the local `@anthropic-ai/claude-agent-sdk` `query()` loop (subscription login, compute on the
224
- * user's machine). The shared thing across back-ends is the IR + the capability-model
225
- * *semantics* (native / realize-via / degrade / fail, criticality, provided_by); the profile *data*
226
- * is target-specific and each back-end writes its own. So this file is the TS sibling of
227
- * the Rust file target's `targets.rs`, not a shared table.
295
+ * - a string — render this variant;
296
+ * - `null` — the slot's `present_when` does not hold, so remove it entirely;
297
+ * - absent — the host has no opinion, so render the declared `default`.
228
298
  *
229
- * How `local` differs from the Rust file target's `claude-code:headless` (the point of the second
230
- * back-end):
231
- * - `llm:per_step_tier` native (in-loop per-call model) ← headless: realize-via(subagents)
232
- * - `structured_output_capture` native (message stream) ← headless: native(stream-json)
233
- * - `render_contract` realize-via(warble-render) ← headless: realize-via(html-file)
234
- * The rest match headless, including the safety-critical loud-fails (human_approval, blast_radius).
299
+ * A plain table rather than a callback, so what the model was told is decided before the run rather
300
+ * than during it: it can be recorded, compared and fingerprinted. A callback could not be.
235
301
  */
236
- /** One of the four resolution outcomes a capability can take on a target. */
237
- type CapabilityOutcome = "native" | "realize-via" | "degrade" | "fail";
238
- /** Who supplies a resolved capability. */
239
- type ProvidedBy = "runtime" | "warble" | "none";
302
+ type SlotSupply = Readonly<Record<string, string | null>>;
240
303
  /**
241
- * safety-critical capabilities must never silently degrade — unsupported means the resolution pass
242
- * aborts. required/best-effort may degrade with a warning recorded in the report.
304
+ * What to do with a conditional slot the host did not answer.
305
+ *
306
+ * `"fail"` on any path whose text reaches a model: `default` covers "no opinion on the wording", and
307
+ * it cannot cover "no opinion on whether this exists at all" — defaulting there ships instructions
308
+ * for a capability that may have been withheld, which is the failure `present_when` exists to
309
+ * prevent.
310
+ *
311
+ * `"default"` on a display path. The rule above protects a model, not a reader, and a structural
312
+ * view that refuses to render a valid profile is worse than one showing the default wording. Note
313
+ * this is a real difference in meaning, not a relaxation for convenience: a display says "this is
314
+ * what the default binding would say", never "this is what will be sent".
243
315
  */
244
- type Criticality = "safety-critical" | "required" | "best-effort";
245
- interface CapabilityEntry {
246
- outcome: CapabilityOutcome;
247
- via: string | null;
248
- provided_by: ProvidedBy;
249
- criticality: Criticality;
250
- note: string | null;
251
- }
252
- type CapabilityProfile = Record<string, CapabilityEntry>;
253
- /** The one target this back-end declares (engine × mode). */
254
- type TargetId = "claude-agent-sdk:local";
255
- declare const DEFAULT_TARGET: TargetId;
256
- declare function isKnownTarget(value: string): value is TargetId;
257
- declare function knownTargetNames(): readonly string[];
258
- /** Capability profile for `claude-agent-sdk:local`. */
259
- declare function localProfile(): CapabilityProfile;
260
- /** Resolve a target id to its capability profile, or `null` if unknown. */
261
- declare function profileFor(targetId: string): CapabilityProfile | null;
262
-
263
- interface ResolvedCapability {
264
- capability: string;
265
- outcome: CapabilityOutcome;
266
- provided_by: ProvidedBy;
267
- criticality: Criticality;
268
- note?: string;
269
- }
270
- type ResolutionReport = ResolvedCapability[];
271
- /** Union of declared + implied required capabilities, de-duplicated, order-preserving. */
272
- declare function collectRequiredCapabilities(node: ComponentNode): string[];
273
- declare function resolveCapabilities(node: ComponentNode, targetId: string, profile: CapabilityProfile): ResolutionReport;
316
+ type UnansweredCondition = "fail" | "default";
274
317
  /**
275
- * Resolve one node's required capabilities against `targetId`, erroring on any `fail` outcome
276
- * (no silent degradation). Callers must not dispatch when this throws.
318
+ * Resolve each declared slot to the text that will replace its references, or to `null` when the
319
+ * host said it is not present.
320
+ *
321
+ * A slot's chosen variant may itself reference other slots — the compiler collects references from
322
+ * variant text and validates them, so this is authored-legal — hence the recursion, with a visiting
323
+ * set so a cycle is reported rather than spun on.
324
+ *
325
+ * @param decls every slot in scope for the text being resolved. That is the profile's slots PLUS
326
+ * the component's, together: compile checks the two layers separately, but it also folds the
327
+ * profile's `system_prompt` into each component's `brief` (`render_brief`), so by the time a
328
+ * consumer sees the IR one string carries references from both. Names are unique project-wide —
329
+ * compile refuses a collision — which is what makes one combined table unambiguous.
330
+ * @param supply the host's table.
331
+ * @param scope names the scope in errors ("component 'x'").
277
332
  */
278
- declare function resolveNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
333
+ declare function resolveSlots(decls: readonly SlotDecl[], supply: SlotSupply, scope: string, unanswered?: UnansweredCondition): ReadonlyMap<string, string | null>;
279
334
  /**
280
- * Read-only inspection counterpart to `resolveNodeCapabilities`. It returns
281
- * the same report, including a failed entry, but does not legalize execution.
282
- * Only display-only callers may use this; dispatch/emit/chat retain the loud
283
- * failure above.
335
+ * Apply a resolved scope to one piece of prompt text.
336
+ *
337
+ * A slot resolved to `null` leaves nothing behind — deliberately the empty string rather than any
338
+ * variant, since the point of a condition that does not hold is that the wording must not appear.
284
339
  */
285
- declare function inspectNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
340
+ declare function applySlots(raw: string, resolved: ReadonlyMap<string, string | null>, scope: string): string;
341
+ /**
342
+ * Refuse prompt text that still carries a slot reference.
343
+ *
344
+ * The guard, not a formality. Every other failure in this file is loud on its own; this one catches
345
+ * the case where a surface was never routed through {@link applySlots} at all — a new prompt-carrying
346
+ * field, a path that assembles text somewhere unexpected — which is exactly how the original defect
347
+ * arrived: the IR grew slots and nothing downstream was taught to consume them.
348
+ */
349
+ declare function assertNoSlotReferences(raw: string, owner: string): void;
350
+ /**
351
+ * Parse repeated `--slot name=variant` / `--slot name=` flags into a {@link SlotSupply}.
352
+ *
353
+ * Lives here rather than in the CLI so it is reachable by a test and by any host that wants the same
354
+ * surface; the CLI turns the thrown error into its own usage failure. Mirrors the Rust CLI's flag of
355
+ * the same name — two user-facing surfaces that disagreed about what `name=` means would be worse
356
+ * than either one alone.
357
+ *
358
+ * An empty value is **not** an empty variant name: it is the host saying the slot's condition does
359
+ * not hold and the wording must not appear at all. That distinction is why the flag takes a trailing
360
+ * `=` rather than requiring a variant.
361
+ */
362
+ declare function parseSlotFlags(flags: readonly string[]): SlotSupply;
286
363
 
287
364
  /**
288
365
  * Which provider serves a tier's model — an **open string**, opaque to warble (mirrors how the IR
@@ -356,6 +433,76 @@ declare class ModelConfig {
356
433
  validate(ir: WarbleIr): void;
357
434
  }
358
435
 
436
+ /**
437
+ * Target capability profiles — the declarative side of the capability model
438
+ * (`docs/spec/capability-model.md`), owned by THIS back-end in TypeScript.
439
+ *
440
+ * A runtime target is `engine × mode`. This back-end declares one target, `claude-agent-sdk:local`:
441
+ * the local `@anthropic-ai/claude-agent-sdk` `query()` loop (subscription login, compute on the
442
+ * user's machine). The shared thing across back-ends is the IR + the capability-model
443
+ * *semantics* (native / realize-via / degrade / fail, criticality, provided_by); the profile *data*
444
+ * is target-specific and each back-end writes its own. So this file is the TS sibling of
445
+ * the Rust file target's `targets.rs`, not a shared table.
446
+ *
447
+ * How `local` differs from the Rust file target's `claude-code:headless` (the point of the second
448
+ * back-end):
449
+ * - `llm:per_step_tier` native (in-loop per-call model) ← headless: realize-via(subagents)
450
+ * - `structured_output_capture` native (message stream) ← headless: native(stream-json)
451
+ * - `render_contract` realize-via(warble-render) ← headless: realize-via(html-file)
452
+ * The rest match headless, including the safety-critical loud-fails (human_approval, blast_radius).
453
+ */
454
+ /** One of the four resolution outcomes a capability can take on a target. */
455
+ type CapabilityOutcome = "native" | "realize-via" | "degrade" | "fail";
456
+ /** Who supplies a resolved capability. */
457
+ type ProvidedBy = "runtime" | "warble" | "none";
458
+ /**
459
+ * safety-critical capabilities must never silently degrade — unsupported means the resolution pass
460
+ * aborts. required/best-effort may degrade with a warning recorded in the report.
461
+ */
462
+ type Criticality = "safety-critical" | "required" | "best-effort";
463
+ interface CapabilityEntry {
464
+ outcome: CapabilityOutcome;
465
+ via: string | null;
466
+ provided_by: ProvidedBy;
467
+ criticality: Criticality;
468
+ note: string | null;
469
+ }
470
+ type CapabilityProfile = Record<string, CapabilityEntry>;
471
+ /** The one target this back-end declares (engine × mode). */
472
+ type TargetId = "claude-agent-sdk:local";
473
+ declare const DEFAULT_TARGET: TargetId;
474
+ declare function isKnownTarget(value: string): value is TargetId;
475
+ declare function knownTargetNames(): readonly string[];
476
+ /** Capability profile for `claude-agent-sdk:local`. */
477
+ declare function localProfile(): CapabilityProfile;
478
+ /** Resolve a target id to its capability profile, or `null` if unknown. */
479
+ declare function profileFor(targetId: string): CapabilityProfile | null;
480
+
481
+ interface ResolvedCapability {
482
+ capability: string;
483
+ outcome: CapabilityOutcome;
484
+ via?: string;
485
+ provided_by: ProvidedBy;
486
+ criticality: Criticality;
487
+ note?: string;
488
+ }
489
+ type ResolutionReport = ResolvedCapability[];
490
+ /** Union of declared + implied required capabilities, de-duplicated, order-preserving. */
491
+ declare function collectRequiredCapabilities(node: ComponentNode): string[];
492
+ declare function resolveCapabilities(node: ComponentNode, targetId: string, profile: CapabilityProfile): ResolutionReport;
493
+ /**
494
+ * Resolve one node's required capabilities against `targetId`, erroring on any `fail` outcome
495
+ * (no silent degradation). Callers must not dispatch when this throws.
496
+ */
497
+ declare function resolveNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
498
+ /**
499
+ * Read-only inspection counterpart to `resolveNodeCapabilities`. It returns
500
+ * the same report, including a failed entry, but does not legalize execution.
501
+ * Only display-only callers may use this; dispatch/emit/chat retain the loud
502
+ * failure above.
503
+ */
504
+ declare function inspectNodeCapabilities(node: ComponentNode, targetId: string): ResolutionReport;
505
+
359
506
  /**
360
507
  * Per-step provider routing — the hybrid-LLM core (see docs/spec/capability-model.md §7.2).
361
508
  *
@@ -427,52 +574,11 @@ interface StepMessage {
427
574
  }
428
575
  /**
429
576
  * Build the messages for one staged step: the step's own prompt as the system message, and the user
430
- * message = the question plus each consumed slot's value marshaled in by name (the `produces`→`consumes`
577
+ * message = the question plus each consumed artifact's value marshaled in by name (the `produces`→`consumes`
431
578
  * hand-off). This is the same isolated-invocation contract the IR already carries for the file target's
432
579
  * subagents (ir.ts `consumes`/`produces`), generalized here across providers.
433
580
  */
434
- declare function buildStepMessages(step: StagedStep, question: string, slots: Readonly<Record<string, string>>): StepMessage[];
435
-
436
- /**
437
- * Minimal OpenAI-compatible chat client for the hybrid-staged path (see docs/spec/capability-model.md §7.2).
438
- *
439
- * A local step (provider `openai_compat`, e.g. ollama's `http://localhost:11434/v1`) is executed by
440
- * calling `POST {endpoint}/chat/completions` directly — NOT through the Claude SDK, whose `agents[].model`
441
- * is a restricted alias union that loud-fails on a local model id (SDK-NOTES.md #1). ollama speaks the
442
- * OpenAI Chat Completions shape, not the Anthropic Messages shape, so this is a distinct, deliberately
443
- * tiny client — no streaming, no tools, no retries. It is the "third provider-aware back-end" embryo:
444
- * enough to prove a per-step local model can be marshaled into a cloud run, not a production LLM client.
445
- *
446
- * Live-gated: exercised only when an ollama (or other OpenAI-compat) endpoint is reachable. The request
447
- * SHAPING is unit-tested via {@link buildChatRequest} with no network.
448
- */
449
-
450
- interface ChatRequest {
451
- model: string;
452
- messages: StepMessage[];
453
- stream: false;
454
- /** Deterministic-leaning default; a local step is a bounded transform, not open-ended generation. */
455
- temperature: number;
456
- }
457
- /** Build the JSON body for an OpenAI-compatible `/chat/completions` call. Pure (no network). */
458
- declare function buildChatRequest(model: string, messages: StepMessage[]): ChatRequest;
459
- /** Extract the assistant text from an OpenAI-compatible completion response. Pure. */
460
- declare function extractCompletionText(body: unknown): string;
461
- interface CallLocalOptions {
462
- endpoint: string;
463
- model: string;
464
- messages: StepMessage[];
465
- /** Optional bearer token (ollama ignores it; other OpenAI-compat servers may require it). */
466
- apiKey?: string;
467
- /** Injectable for tests; defaults to global fetch. */
468
- fetchImpl?: typeof fetch;
469
- }
470
- /**
471
- * Call an OpenAI-compatible chat endpoint and return the assistant text. Live-gated (needs a reachable
472
- * endpoint); the request/response shaping is covered by {@link buildChatRequest} /
473
- * {@link extractCompletionText} tests, and a stubbed `fetchImpl` can drive this end-to-end offline.
474
- */
475
- declare function callOpenAiCompat(opts: CallLocalOptions): Promise<string>;
581
+ declare function buildStepMessages(step: StagedStep, question: string, artifacts: Readonly<Record<string, string>>): StepMessage[];
476
582
 
477
583
  /**
478
584
  * IR enum → `query({options})` mapping — the core of this back-end, the TS analogue of the
@@ -553,6 +659,8 @@ interface DispatchMeta {
553
659
  * beyond `wren` and Write/Edit are scoped to this root, instead of denied outright. `null` on the
554
660
  * hybrid-staged path (out of scope — see buildHybridStagedPlan). */
555
661
  setupScope: string | null;
662
+ /** True when this plan requires the prepared-registry component runtime, not plain runDispatch. */
663
+ componentInvocation?: boolean;
556
664
  }
557
665
  interface DispatchPlan {
558
666
  /** The user question (assembled prompt for `query()`). */
@@ -573,6 +681,9 @@ interface BuildConfig {
573
681
  /**
574
682
  * Build the `query({options})` for one resolved IR node. Loud-fails on any unsupported enum value
575
683
  * before producing anything (wall-hit), mirroring `emit.rs`.
684
+ *
685
+ * The assembled prompts are checked for surviving slot references on the way out — see
686
+ * {@link assertPlanHasNoSlotReferences} for why that check lives here and not only at resolution.
576
687
  */
577
688
  declare function buildDispatchPlan(node: ComponentNode, report: ResolutionReport, cfg: BuildConfig): DispatchPlan;
578
689
 
@@ -635,6 +746,97 @@ type WarbleChatEvent = {
635
746
  readonly id: string | null;
636
747
  };
637
748
 
749
+ /** Hash algorithm named in the output so a stored fingerprint stays readable if this ever changes. */
750
+ declare const ALGORITHM = "sha256";
751
+ /**
752
+ * One digest per surface, plus the total.
753
+ *
754
+ * **Per surface as well as in total, on purpose.** The total answers "did anything change"; only the
755
+ * per-surface digests answer "which prompt changed", and that is the question a trace disagreeing
756
+ * with its own hash actually raises. The cost is a few dozen bytes per run.
757
+ */
758
+ interface PromptFingerprint {
759
+ algorithm: typeof ALGORITHM;
760
+ /** Digest over every surface: name-sorted, with each name hashed alongside its text. */
761
+ digest: string;
762
+ /** Per-surface digest, keyed by a stable surface name. */
763
+ surfaces: Record<string, string>;
764
+ }
765
+ /**
766
+ * Fingerprint an arbitrary surface map. The core; both helpers below funnel through it.
767
+ *
768
+ * The total digest hashes each surface's **name and text together**, name-sorted. Including the name
769
+ * is what stops two different arrangements of the same strings — a prompt moved from one subagent to
770
+ * another, say — from colliding; sorting is what makes the result independent of key order.
771
+ */
772
+ declare function fingerprintSurfaces(surfaces: Record<string, string>): PromptFingerprint;
773
+ /**
774
+ * The prompt surfaces of the `query()` options a host is about to send.
775
+ *
776
+ * **This is the primitive, not {@link promptSurfaces}.** A host may assemble its own options from a
777
+ * plan rather than sending the plan's verbatim — the BIRD-Interact adapter does exactly that,
778
+ * replacing the system prompt with the component's `prompt_fragment` — and fingerprinting the plan
779
+ * in that case would record something other than what was sent. A fingerprint describing a prompt
780
+ * nobody received is worse than no fingerprint, because it reads as evidence.
781
+ *
782
+ * So take it as late as possible: over the options that actually reach `query()`.
783
+ */
784
+ declare function promptSurfacesOf(options: Options): Record<string, string>;
785
+ /**
786
+ * The prompt surfaces a plan **itself** determines — what the single and split paths send verbatim.
787
+ *
788
+ * Surfaces are **named, not positional**, so a plan that gains or loses a subagent changes which
789
+ * keys exist rather than silently shifting every digest after the insertion point.
790
+ *
791
+ * **What this deliberately does NOT cover, and why it would be wrong to.** A staged step is not sent
792
+ * as its `prompt`: the executor prepends a runtime preamble built from the working directory, and
793
+ * the hybrid-tool path sends a driver prompt it composes from the step list, which appears nowhere in
794
+ * the plan's options. Recording a `step.<name>` digest of the bare `prompt` here would produce a
795
+ * fingerprint that does not match the bytes sent — the exact failure this module's header calls worse
796
+ * than no fingerprint, because it reads as evidence. Reproducing the assembly here instead would
797
+ * duplicate it and drift.
798
+ *
799
+ * So those paths are fingerprinted where the bytes exist: `runDispatch` reports one fingerprint per
800
+ * turn it sends, through `RunConfig.onPromptFingerprint`. Use that for anything staged or hybrid; use
801
+ * this for a plan whose options go out as built.
802
+ *
803
+ * One surface that looks missing and is not: on the single-tier (collapse) path a component's
804
+ * `prompt_fragment` is folded into `systemPrompt`, and `llm_calls[].prompt` is not read at all. So a
805
+ * one-step component's step text is covered through the driver surface rather than a step surface.
806
+ */
807
+ declare function promptSurfaces(plan: DispatchPlan): Record<string, string>;
808
+ /** Fingerprint the prompts a plan assembled. See {@link promptSurfaces} on when this is the wrong one. */
809
+ declare function fingerprintPrompts(plan: DispatchPlan): PromptFingerprint;
810
+
811
+ /**
812
+ * Compose an embedding host's `canUseTool` with a guardrail floor's, so a host can add its own
813
+ * enforcement instead of having to choose between the two. Without this an embedder's callback is
814
+ * silently replaced, which is the same failure whichever way round it happens.
815
+ *
816
+ * **The order is load-bearing: the embedder runs first, the floor runs last.** A `canUseTool` may
817
+ * rewrite a tool's input on the way through (`updatedInput`), so a floor that ran first would be
818
+ * inspecting an input the embedder could still change afterwards — the floor has to see what the
819
+ * tool will actually receive. Running it last is what makes composition one-directional: either side
820
+ * may deny, neither can turn the other's denial into an allow, and an allowed input has passed
821
+ * through both.
822
+ *
823
+ * An embedder denial returns immediately without consulting the floor, so the floor's `denials`
824
+ * ledger stays a record of guardrail enforcement rather than a mixed log of both parties.
825
+ *
826
+ * With no embedder callback the floor's own callback is returned unchanged, so a run without an
827
+ * embedder behaves exactly as it did before this seam existed.
828
+ */
829
+ declare function composeCanUseTool(embedder: CanUseTool | undefined, floor: CanUseTool): CanUseTool;
830
+ /**
831
+ * Merge an embedding host's `hooks` with a guardrail's `PreToolUse` matchers, appending the
832
+ * guardrail's so the host's run first and neither set is dropped.
833
+ *
834
+ * This is the shape the main dispatch path already used inline; it is extracted here so the staged
835
+ * and hybrid-tool paths can stop discarding the host's hooks, and so there is one place to read
836
+ * rather than three to keep in step. `PreToolUse` is a separate control path from `canUseTool` (see
837
+ * `makeSetupReadDenyHook` below for the empirical note), which is why both have to compose.
838
+ */
839
+ declare function composeHooks(embedder: Options["hooks"], guardHooks: HookCallbackMatcher[]): Options["hooks"];
638
840
  /** A blocked tool call, recorded so the trace/report can prove enforcement actually fired. */
639
841
  interface Denial {
640
842
  tool: string;
@@ -721,6 +923,8 @@ interface Trace {
721
923
  /** Per assistant turn (per-step granularity the headless file target can't produce). */
722
924
  steps: StepUsage[];
723
925
  denials: Denial[];
926
+ /** Redacted parent/child attempt telemetry. Payloads and provider errors are never included. */
927
+ componentCalls?: ComponentCallTrace[];
724
928
  }
725
929
  /** Pure: fold the captured message stream + guardrail denials into a trace. */
726
930
  declare function aggregateTrace(messages: readonly SDKMessage[], meta: {
@@ -757,6 +961,32 @@ interface RunConfig {
757
961
  * message stream is consumed, not batched after the fact. Only wired on the main (single/split) SDK
758
962
  * loop below — the hybrid-staged executor passes through with no events. */
759
963
  onEvent?: (event: WarbleChatEvent) => void;
964
+ /**
965
+ * Called once per turn this run sends, with a fingerprint of the prompts THAT turn carried.
966
+ *
967
+ * Reported here rather than derived from the plan because the staged and hybrid paths do not send
968
+ * the plan's text as built: a step's options carry a runtime preamble ahead of its prompt, and the
969
+ * hybrid-tool driver composes a prompt that appears nowhere in the plan. A plan-derived digest
970
+ * would name bytes that were never sent, which is worse than none — it reads as evidence.
971
+ *
972
+ * Several turns therefore report several fingerprints. A consumer that wants one value per run
973
+ * should deduplicate by digest: one entry means the prompts held steady, more than one means they
974
+ * changed across the run, which is itself the finding.
975
+ */
976
+ onPromptFingerprint?: (fingerprint: PromptFingerprint) => void;
977
+ /**
978
+ * The IR file this plan came from, and the IR itself — supplied together to land the assets its
979
+ * components declare into the working directory before the agent runs.
980
+ *
981
+ * Optional because a caller that built a plan without an IR file on disk (a test, a programmatic
982
+ * IR) has nothing to land from. Omitting it on a plan whose components DO declare assets means the
983
+ * agent runs without them, which is the pre-existing behaviour this closes — so a host that can
984
+ * supply it should.
985
+ */
986
+ assets?: {
987
+ ir: WarbleIr;
988
+ irPath: string;
989
+ };
760
990
  }
761
991
  /**
762
992
  * A dispatch failure that still carries the SDK's session id, when the result message had one —
@@ -775,6 +1005,368 @@ declare class DispatchSessionError extends DispatchError {
775
1005
  */
776
1006
  declare function runDispatch(plan: DispatchPlan, cfg: RunConfig): Promise<RunResult>;
777
1007
 
1008
+ interface DispatchInput {
1009
+ /** A parsed IR or a raw JSON string. */
1010
+ ir: WarbleIr | string;
1011
+ /**
1012
+ * What this host says about each declared slot: a variant name to render, `null` to remove the
1013
+ * slot because its `present_when` does not hold, or nothing at all to take the declared default.
1014
+ *
1015
+ * The IR carries every variant and picks none — picking is the host's job, and so is answering a
1016
+ * condition. Omit this entirely for an IR that declares no slots; supplying it then is harmless
1017
+ * but pointless. An IR that DOES declare a conditional slot and gets no answer for it is a loud
1018
+ * failure rather than a silent default (see `resolveSlots`).
1019
+ */
1020
+ slots?: SlotSupply;
1021
+ /** The data question to answer (the `query()` prompt). Optional for prepare-only (dry-run/emit). */
1022
+ question?: string;
1023
+ target?: string;
1024
+ flavor?: RenderFlavor;
1025
+ models?: ModelConfig;
1026
+ maxTurns?: number;
1027
+ /** Explicit bound-project cwd (absolute or cwd-relative). Overrides `irPath`-based resolution. */
1028
+ project?: string;
1029
+ /** Resolve each node's relative `context_binding.project` against this IR file's directory. */
1030
+ irPath?: string;
1031
+ /**
1032
+ * Scope preparation to this root id and its complete transitive component-call closure. An
1033
+ * unreachable sibling is left untouched, so its model, slots, assets, capabilities, guardrails,
1034
+ * effect and trigger never enter this dispatch's preflight. Use this for `chat`, which starts one
1035
+ * root per process.
1036
+ *
1037
+ * Omit (the default) to prepare every component in the IR — the shape `manifest`, `emit`, and
1038
+ * the whole-profile `dispatch` subcommand need, since each of those actually reads or runs
1039
+ * every component and must know every component's resolution, not just one's.
1040
+ *
1041
+ * This narrows *which closure's* requirements gate a call; it does not weaken a failed
1042
+ * capability. Reachable callees are fully planned under their own authority and stored in the
1043
+ * prepared-callee registry, never appended to the root plan list or started automatically.
1044
+ */
1045
+ componentId?: string;
1046
+ }
1047
+ interface PreparedComponent {
1048
+ id: string;
1049
+ node: ComponentNode;
1050
+ report: ResolutionReport;
1051
+ plan: DispatchPlan;
1052
+ /** Every IR step with its own runtime model/provider binding, retained for trusted step execution. */
1053
+ steps: readonly StagedStep[];
1054
+ role: "entry" | "callee";
1055
+ }
1056
+ interface PreparedDispatch {
1057
+ target: string;
1058
+ profile: string;
1059
+ /** Backwards-compatible root plan list. Reachable callees never appear here merely by reachability. */
1060
+ components: PreparedComponent[];
1061
+ /** Immutable records addressable only through the authorized dependency table. */
1062
+ preparedCallees: readonly PreparedComponent[];
1063
+ entries: readonly EntryClosure[];
1064
+ dependencies: readonly ComponentDependency[];
1065
+ }
1066
+ /** Stable redacted status for a component the configured target cannot run. */
1067
+ declare const UNAVAILABLE_COMPONENT_REASON = "component is unavailable on the configured runtime";
1068
+ interface UnavailableDisplayComponent {
1069
+ id: string;
1070
+ node: ComponentNode;
1071
+ /** Read-only diagnostic resolution; never an executable capability grant. */
1072
+ inspection: ResolutionReport;
1073
+ availability: {
1074
+ status: "unavailable";
1075
+ reason: typeof UNAVAILABLE_COMPONENT_REASON;
1076
+ };
1077
+ }
1078
+ /**
1079
+ * A component in a display manifest that the target CAN run.
1080
+ *
1081
+ * Deliberately **not** a {@link PreparedComponent}: it carries no `plan`. A plan built for a display
1082
+ * is resolved under the lenient unanswered-condition policy, so handing one out would let a consumer
1083
+ * do the natural "preview it, then dispatch what was previewed" — `runDispatch(component.plan, cfg)`
1084
+ * type-checks with no cast — and send a model wording for a condition nobody ever answered. That is
1085
+ * exactly the failure the strict policy exists to prevent, arriving through the display door.
1086
+ *
1087
+ * A plan is still built during preparation, because that is what surfaces an unsupported enum as a
1088
+ * wall-hit; it is discarded rather than returned. Nothing in the manifest builders reads it.
1089
+ */
1090
+ interface AvailableDisplayComponent {
1091
+ id: string;
1092
+ node: ComponentNode;
1093
+ report: ResolutionReport;
1094
+ }
1095
+ type DisplayComponent = AvailableDisplayComponent | UnavailableDisplayComponent;
1096
+ interface PreparedDisplayManifest {
1097
+ target: string;
1098
+ profile: string;
1099
+ components: readonly DisplayComponent[];
1100
+ entries: readonly EntryClosure[];
1101
+ dependencies: readonly ComponentDependency[];
1102
+ }
1103
+ /**
1104
+ * Resolve a node's bound wren project to an absolute cwd. Relative `context_binding.project` paths
1105
+ * resolve against the IR file's directory (`irPath`) when given, else the current working directory;
1106
+ * an explicit `project` always wins.
1107
+ */
1108
+ declare function resolveProjectCwd(node: ComponentNode, opts: {
1109
+ project?: string;
1110
+ irPath?: string;
1111
+ }): string;
1112
+ /**
1113
+ * Parse + resolve + build every requested component's `query({options})`, without calling the SDK.
1114
+ *
1115
+ * By default this prepares every advertised entry and the union of their closures. Pass
1116
+ * `input.componentId` to scope preparation to one advertised root and its transitive callees;
1117
+ * components outside that closure are never consulted. See {@link DispatchInput.componentId}.
1118
+ */
1119
+ declare function prepareDispatch(input: DispatchInput): PreparedDispatch;
1120
+ /**
1121
+ * Prepare a display-only whole-profile manifest. Unsupported components are
1122
+ * represented by a closed unavailable marker; no executable plan is built
1123
+ * for them. This must never be used by emit, dispatch, or chat.
1124
+ */
1125
+ declare function prepareDisplayManifest(input: Omit<DispatchInput, "componentId" | "question">): PreparedDisplayManifest;
1126
+ interface DispatchRunConfig {
1127
+ outDir: string;
1128
+ warbleBin?: string;
1129
+ title?: string;
1130
+ /** Host-lowered root invocation limits. Defaults are fixed by the composition specification. */
1131
+ componentLimits?: ComponentInvocationLimits;
1132
+ /** Root cancellation propagated into all active child query() calls. */
1133
+ signal?: AbortSignal;
1134
+ /** Deterministic host/test seam; production omits this and uses fresh Agent SDK query() calls. */
1135
+ componentStepRunner?: ComponentStepRunner;
1136
+ /** Embedder enforcement runs before each component's own immutable guardrail floor. */
1137
+ hostCanUseTool?: Options["canUseTool"];
1138
+ hostHooks?: Options["hooks"];
1139
+ }
1140
+ interface ComponentOutcome {
1141
+ id: string;
1142
+ report: ResolutionReport;
1143
+ plan: DispatchPlan;
1144
+ result: RunResult;
1145
+ }
1146
+ interface DispatchOutcome {
1147
+ target: string;
1148
+ components: ComponentOutcome[];
1149
+ }
1150
+ /**
1151
+ * Verify every reachable mount's travelling assets without writing them. This is intentionally a
1152
+ * separate executable preflight from pure graph preparation: it reads the filesystem, but still
1153
+ * completes before a model, session, output directory, or asset target is created.
1154
+ */
1155
+ declare function preflightDispatchAssets(input: DispatchInput, prepared: PreparedDispatch): void;
1156
+ /**
1157
+ * Prepare then RUN each component against the live Agent SDK loop. Requires `input.question`.
1158
+ * Writes each run's artifacts under `runCfg.outDir` (see {@link runDispatch}).
1159
+ */
1160
+ declare function dispatch(input: DispatchInput, runCfg: DispatchRunConfig): Promise<DispatchOutcome>;
1161
+
1162
+ declare const DEFAULT_COMPONENT_INVOCATION_LIMITS: Readonly<{
1163
+ maxDepth: 8;
1164
+ maxAttempts: 32;
1165
+ maxRequestBytes: 65536;
1166
+ maxResultBytes: 1048576;
1167
+ maxTurns: 40;
1168
+ maxTurnsPerChild: 12;
1169
+ }>;
1170
+ interface ComponentInvocationLimits {
1171
+ maxDepth?: number;
1172
+ maxAttempts?: number;
1173
+ maxRequestBytes?: number;
1174
+ maxResultBytes?: number;
1175
+ maxTurns?: number;
1176
+ maxTurnsPerChild?: number;
1177
+ /** Rejected by the Agent SDK composition preflight until provider spend is reservable. */
1178
+ maxCostUsd?: number;
1179
+ deadlineAt?: number;
1180
+ }
1181
+ interface ComponentInvocationRequest {
1182
+ request: string;
1183
+ input?: Record<string, unknown>;
1184
+ }
1185
+ interface ComponentProvenance {
1186
+ verified?: boolean;
1187
+ definition?: unknown;
1188
+ }
1189
+ type ComponentInvocationResult = {
1190
+ status: "ok";
1191
+ output: {
1192
+ kind: "value";
1193
+ value: unknown;
1194
+ } | {
1195
+ kind: "render";
1196
+ blocks: Record<string, unknown>[];
1197
+ summary?: string;
1198
+ };
1199
+ provenance?: ComponentProvenance;
1200
+ } | {
1201
+ status: "refused";
1202
+ code: "callee_refused";
1203
+ message: string;
1204
+ } | {
1205
+ status: "error";
1206
+ code: "unsupported_callee" | "invalid_request" | "invalid_result" | "budget_exhausted" | "cancelled" | "transient_transport" | "callee_failed";
1207
+ message: string;
1208
+ retryable: boolean;
1209
+ };
1210
+ type ComponentCallStatus = "ok" | "refused" | "error" | "cancelled" | "late_discarded";
1211
+ interface ComponentCallTrace {
1212
+ call_id: string;
1213
+ parent_call_id: string | null;
1214
+ caller_mount: string;
1215
+ trusted_step_id: string;
1216
+ alias: string;
1217
+ callee_mount: string;
1218
+ attempt: number;
1219
+ depth: number;
1220
+ admitted_at: number;
1221
+ completed_at: number;
1222
+ status: ComponentCallStatus;
1223
+ request_bytes: number;
1224
+ result_bytes: number;
1225
+ model_turns: number;
1226
+ total_cost_usd: number;
1227
+ token_usage: {
1228
+ input_tokens: number;
1229
+ output_tokens: number;
1230
+ cache_creation_input_tokens: number;
1231
+ cache_read_input_tokens: number;
1232
+ } | null;
1233
+ degradation: string | null;
1234
+ }
1235
+ interface ComponentStepUsage {
1236
+ model: string;
1237
+ usage: unknown;
1238
+ }
1239
+ interface ComponentStepResult {
1240
+ text: string;
1241
+ turns: number;
1242
+ totalCostUsd: number;
1243
+ usage?: ComponentStepUsage[];
1244
+ degradation?: string | null;
1245
+ }
1246
+ /** A step failed after the provider exposed billable telemetry. The runtime must charge it. */
1247
+ declare class ComponentStepExecutionError extends DispatchError {
1248
+ readonly telemetry: ComponentStepResult;
1249
+ readonly retryable: boolean;
1250
+ constructor(message: string, telemetry: ComponentStepResult, retryable: boolean);
1251
+ }
1252
+ interface ComponentStepRun {
1253
+ rootRunId: string;
1254
+ callId: string | null;
1255
+ parentCallId: string | null;
1256
+ component: PreparedComponent;
1257
+ step: StagedStep;
1258
+ request: Required<ComponentInvocationRequest>;
1259
+ artifacts: Readonly<Record<string, string>>;
1260
+ aliases: readonly string[];
1261
+ signal: AbortSignal;
1262
+ maxTurns: number;
1263
+ invoke(alias: string, request: unknown): Promise<ComponentInvocationResult>;
1264
+ }
1265
+ type ComponentStepRunner = (run: ComponentStepRun) => Promise<ComponentStepResult>;
1266
+ interface ComponentRuntimeOptions {
1267
+ prepared: PreparedDispatch;
1268
+ runStep: ComponentStepRunner;
1269
+ limits?: ComponentInvocationLimits;
1270
+ signal?: AbortSignal;
1271
+ now?: () => number;
1272
+ newId?: () => string;
1273
+ }
1274
+ interface ComponentRootResult {
1275
+ finalText: string;
1276
+ turns: number;
1277
+ totalCostUsd: number;
1278
+ usage: ComponentStepUsage[];
1279
+ componentCalls: ComponentCallTrace[];
1280
+ }
1281
+ declare function normalizeComponentRequest(value: unknown, maxBytes?: number): {
1282
+ value: Required<ComponentInvocationRequest>;
1283
+ bytes: number;
1284
+ } | {
1285
+ error: ComponentInvocationResult;
1286
+ };
1287
+ declare function normalizeComponentResult(finalText: string, renderContract: readonly RenderBlock[], maxBytes?: number): {
1288
+ value: ComponentInvocationResult;
1289
+ bytes: number;
1290
+ };
1291
+ declare function assertEligibleComponentCallee(component: PreparedComponent): void;
1292
+ /** A root caller may own the final render path, but its model step stays read-only and SDK-bound. */
1293
+ declare function assertEligibleComponentCaller(component: PreparedComponent): void;
1294
+ declare class ComponentInvocationRuntime {
1295
+ private readonly options;
1296
+ readonly rootRunId: string;
1297
+ readonly componentCalls: ComponentCallTrace[];
1298
+ private readonly limits;
1299
+ private readonly abortController;
1300
+ private readonly now;
1301
+ private readonly newId;
1302
+ private readonly byId;
1303
+ private readonly edgeByStep;
1304
+ private readonly descendantDepth;
1305
+ private attempts;
1306
+ private availableTurns;
1307
+ private totalTurnsObserved;
1308
+ private totalCostUsd;
1309
+ private readonly allUsage;
1310
+ private cancelledAt;
1311
+ private deadlineTimer;
1312
+ constructor(options: ComponentRuntimeOptions);
1313
+ get signal(): AbortSignal;
1314
+ cancel(): void;
1315
+ runRoot(componentId: string, request: ComponentInvocationRequest): Promise<ComponentRootResult>;
1316
+ private throwIfCancelled;
1317
+ private aliasesFor;
1318
+ private availableFor;
1319
+ private charge;
1320
+ private maxDescendantDepth;
1321
+ private descendantDepthForStep;
1322
+ /** Reserve an active root SDK query before its synchronous child handlers may be admitted. */
1323
+ private reserveRootStep;
1324
+ private executeComponent;
1325
+ private enqueueInvocation;
1326
+ private invoke;
1327
+ }
1328
+
1329
+ /**
1330
+ * Minimal OpenAI-compatible chat client for the hybrid-staged path (see docs/spec/capability-model.md §7.2).
1331
+ *
1332
+ * A local step (provider `openai_compat`, e.g. ollama's `http://localhost:11434/v1`) is executed by
1333
+ * calling `POST {endpoint}/chat/completions` directly — NOT through the Claude SDK, whose `agents[].model`
1334
+ * is a restricted alias union that loud-fails on a local model id (SDK-NOTES.md #1). ollama speaks the
1335
+ * OpenAI Chat Completions shape, not the Anthropic Messages shape, so this is a distinct, deliberately
1336
+ * tiny client — no streaming, no tools, no retries. It is the "third provider-aware back-end" embryo:
1337
+ * enough to prove a per-step local model can be marshaled into a cloud run, not a production LLM client.
1338
+ *
1339
+ * Live-gated: exercised only when an ollama (or other OpenAI-compat) endpoint is reachable. The request
1340
+ * SHAPING is unit-tested via {@link buildChatRequest} with no network.
1341
+ */
1342
+
1343
+ interface ChatRequest {
1344
+ model: string;
1345
+ messages: StepMessage[];
1346
+ stream: false;
1347
+ /** Deterministic-leaning default; a local step is a bounded transform, not open-ended generation. */
1348
+ temperature: number;
1349
+ }
1350
+ /** Build the JSON body for an OpenAI-compatible `/chat/completions` call. Pure (no network). */
1351
+ declare function buildChatRequest(model: string, messages: StepMessage[]): ChatRequest;
1352
+ /** Extract the assistant text from an OpenAI-compatible completion response. Pure. */
1353
+ declare function extractCompletionText(body: unknown): string;
1354
+ interface CallLocalOptions {
1355
+ endpoint: string;
1356
+ model: string;
1357
+ messages: StepMessage[];
1358
+ /** Optional bearer token (ollama ignores it; other OpenAI-compat servers may require it). */
1359
+ apiKey?: string;
1360
+ /** Injectable for tests; defaults to global fetch. */
1361
+ fetchImpl?: typeof fetch;
1362
+ }
1363
+ /**
1364
+ * Call an OpenAI-compatible chat endpoint and return the assistant text. Live-gated (needs a reachable
1365
+ * endpoint); the request/response shaping is covered by {@link buildChatRequest} /
1366
+ * {@link extractCompletionText} tests, and a stubbed `fetchImpl` can drive this end-to-end offline.
1367
+ */
1368
+ declare function callOpenAiCompat(opts: CallLocalOptions): Promise<string>;
1369
+
778
1370
  /**
779
1371
  * The orchestrator's system prompt — PROVIDER-AGNOSTIC. Lists the steps in order by name and the
780
1372
  * produces→consumes marshaling; it never says which step is local vs cloud (that is the handler's job,
@@ -787,6 +1379,12 @@ declare function buildToolDriverPrompt(steps: readonly StagedStep[]): string;
787
1379
  */
788
1380
  declare function runHybridTool(plan: DispatchPlan, cfg: RunConfig): Promise<RunResult>;
789
1381
 
1382
+ /** The directory an IR's assets travel in, derived from the IR's own path. Mirrors the Rust CLI's. */
1383
+ declare function assetDirForIr(irPath: string): string;
1384
+ /** Executable-preflight probe: verify every asset before model/session/output work begins. */
1385
+ declare function validateAssets(ir: WarbleIr, irPath: string, cwd: string): void;
1386
+ declare function landAssets(ir: WarbleIr, irPath: string, cwd: string): string[];
1387
+
790
1388
  interface RenderResult {
791
1389
  outPath: string;
792
1390
  /** stderr from `warble render` (it logs "wrote … (N block(s))"). */
@@ -802,6 +1400,22 @@ declare function renderEnvelope(finalText: string, outPath: string, opts: {
802
1400
  title?: string;
803
1401
  }): RenderResult;
804
1402
 
1403
+ declare function createSdkComponentStepRunner(cfg?: Pick<ComposedRunConfig, "onPromptFingerprint" | "hostCanUseTool" | "hostHooks">): {
1404
+ runStep: ComponentStepRunner;
1405
+ denials: Denial[];
1406
+ };
1407
+ interface ComposedRunConfig extends RunConfig {
1408
+ prepared: PreparedDispatch;
1409
+ root: PreparedComponent;
1410
+ limits?: ComponentInvocationLimits;
1411
+ signal?: AbortSignal;
1412
+ runStep?: ComponentStepRunner;
1413
+ hostCanUseTool?: Options["canUseTool"];
1414
+ hostHooks?: Options["hooks"];
1415
+ }
1416
+ /** Execute one prepared root and its authorized descendants. Only this root path persists output. */
1417
+ declare function runComposedDispatch(plan: DispatchPlan, cfg: ComposedRunConfig): Promise<RunResult>;
1418
+
805
1419
  /**
806
1420
  * A minimal structured snapshot of what a turn resolved — filters/dimensions/measures/grain — used
807
1421
  * only to thread context into the NEXT turn's prompt. Nothing here is inferred by this module: a
@@ -907,111 +1521,6 @@ declare class ChatSession {
907
1521
  }
908
1522
  declare function createChatSession(plan: DispatchPlan, runCfg: RunConfig, initialResumeSessionId?: string): ChatSession;
909
1523
 
910
- interface DispatchInput {
911
- /** A parsed IR or a raw JSON string. */
912
- ir: WarbleIr | string;
913
- /** The data question to answer (the `query()` prompt). Optional for prepare-only (dry-run/emit). */
914
- question?: string;
915
- target?: string;
916
- flavor?: RenderFlavor;
917
- models?: ModelConfig;
918
- maxTurns?: number;
919
- /** Explicit bound-project cwd (absolute or cwd-relative). Overrides `irPath`-based resolution. */
920
- project?: string;
921
- /** Resolve each node's relative `context_binding.project` against this IR file's directory. */
922
- irPath?: string;
923
- /**
924
- * Scope preparation to exactly this component id: only its capabilities are resolved and only
925
- * its plan is built — every *other* component in the IR is left untouched, so its
926
- * `required_capabilities` never enter this dispatch's preflight. Use this for `chat`, which
927
- * only ever runs one component per process.
928
- *
929
- * Omit (the default) to prepare every component in the IR — the shape `manifest`, `emit`, and
930
- * the whole-profile `dispatch` subcommand need, since each of those actually reads or runs
931
- * every component and must know every component's resolution, not just one's.
932
- *
933
- * This narrows *which* component's requirements gate a given `prepareDispatch` call — it does
934
- * not change what happens when a gated capability is unmet (still a loud throw, same message,
935
- * same named capability; see `resolveNodeCapabilities`). A component that can itself invoke
936
- * another IR component at runtime would need that callee's requirements folded in here too, but
937
- * no such reachability exists yet: `borrowed_actions` names external runtime actions (notify,
938
- * ticket, …), never another component, and the one mechanism shaped for it — `orchestrating` /
939
- * `effect.outcome.kind: "dispatch"` with `routable_scope` — is parsed but not consumed by any
940
- * back-end (`docs/spec/authoring.md` marks `orchestrating` "scaffolded", not realized). If that
941
- * ever lands, this scoping must fold in the callee's requirements too.
942
- */
943
- componentId?: string;
944
- }
945
- interface PreparedComponent {
946
- id: string;
947
- node: ComponentNode;
948
- report: ResolutionReport;
949
- plan: DispatchPlan;
950
- }
951
- interface PreparedDispatch {
952
- target: string;
953
- components: PreparedComponent[];
954
- }
955
- /** Stable redacted status for a component the configured target cannot run. */
956
- declare const UNAVAILABLE_COMPONENT_REASON = "component is unavailable on the configured runtime";
957
- interface UnavailableDisplayComponent {
958
- id: string;
959
- node: ComponentNode;
960
- availability: {
961
- status: "unavailable";
962
- reason: typeof UNAVAILABLE_COMPONENT_REASON;
963
- };
964
- }
965
- type DisplayComponent = PreparedComponent | UnavailableDisplayComponent;
966
- interface PreparedDisplayManifest {
967
- target: string;
968
- components: DisplayComponent[];
969
- }
970
- /**
971
- * Resolve a node's bound wren project to an absolute cwd. Relative `context_binding.project` paths
972
- * resolve against the IR file's directory (`irPath`) when given, else the current working directory;
973
- * an explicit `project` always wins.
974
- */
975
- declare function resolveProjectCwd(node: ComponentNode, opts: {
976
- project?: string;
977
- irPath?: string;
978
- }): string;
979
- /**
980
- * Parse + resolve + build every requested component's `query({options})`, without calling the SDK.
981
- *
982
- * By default this prepares every component in the IR. Pass `input.componentId` to scope
983
- * preparation — and therefore capability resolution — to exactly that one component; every other
984
- * component's `required_capabilities` are never consulted, so a component that isn't being
985
- * dispatched can't wall-hit a dispatch it has nothing to do with. See {@link DispatchInput.componentId}.
986
- */
987
- declare function prepareDispatch(input: DispatchInput): PreparedDispatch;
988
- /**
989
- * Prepare a display-only whole-profile manifest. Unsupported components are
990
- * represented by a closed unavailable marker; no executable plan is built
991
- * for them. This must never be used by emit, dispatch, or chat.
992
- */
993
- declare function prepareDisplayManifest(input: Omit<DispatchInput, "componentId" | "question">): PreparedDisplayManifest;
994
- interface DispatchRunConfig {
995
- outDir: string;
996
- warbleBin?: string;
997
- title?: string;
998
- }
999
- interface ComponentOutcome {
1000
- id: string;
1001
- report: ResolutionReport;
1002
- plan: DispatchPlan;
1003
- result: RunResult;
1004
- }
1005
- interface DispatchOutcome {
1006
- target: string;
1007
- components: ComponentOutcome[];
1008
- }
1009
- /**
1010
- * Prepare then RUN each component against the live Agent SDK loop. Requires `input.question`.
1011
- * Writes each run's artifacts under `runCfg.outDir` (see {@link runDispatch}).
1012
- */
1013
- declare function dispatch(input: DispatchInput, runCfg: DispatchRunConfig): Promise<DispatchOutcome>;
1014
-
1015
1524
  /**
1016
1525
  * Codegen (`emit`) — freeze a prepared dispatch into an importable TS agent module.
1017
1526
  *
@@ -1091,6 +1600,7 @@ interface GuardrailManifest {
1091
1600
  }
1092
1601
  interface AvailableAgentManifest {
1093
1602
  id: string;
1603
+ entrypoint: boolean;
1094
1604
  verb: string;
1095
1605
  component_type: ComponentNode["type"];
1096
1606
  realization_kind: ComponentNode["realization_kind"];
@@ -1101,11 +1611,13 @@ interface AvailableAgentManifest {
1101
1611
  tools: ToolRef[];
1102
1612
  output_schema: unknown;
1103
1613
  capabilities: ResolutionReport;
1614
+ dependencies: ComponentDependencyManifest[];
1104
1615
  brief?: string;
1105
1616
  }
1106
1617
  /** A display-only declaration of a component that remains unavailable to this target. */
1107
1618
  interface UnavailableAgentManifest {
1108
1619
  id: string;
1620
+ entrypoint: boolean;
1109
1621
  verb: string;
1110
1622
  component_type: ComponentNode["type"];
1111
1623
  realization_kind: ComponentNode["realization_kind"];
@@ -1117,27 +1629,49 @@ interface UnavailableAgentManifest {
1117
1629
  tools: [];
1118
1630
  output_schema: Record<string, never>;
1119
1631
  capabilities: [];
1632
+ capability_inspection: ResolutionReport;
1633
+ dependencies: ComponentDependencyManifest[];
1120
1634
  availability: {
1121
1635
  status: "unavailable";
1122
1636
  reason: string;
1123
1637
  };
1124
1638
  }
1125
1639
  type AgentManifest = AvailableAgentManifest | UnavailableAgentManifest;
1640
+ type ComponentDependencyManifest = Omit<ComponentDependency, "caller">;
1641
+ interface EntryManifest {
1642
+ id: string;
1643
+ closure: string[];
1644
+ availability: {
1645
+ status: "available";
1646
+ } | {
1647
+ status: "unavailable";
1648
+ reason: string;
1649
+ };
1650
+ /** Null when the entry's closure declares no component invocation. */
1651
+ invocation_realization: ResolvedCapability | null;
1652
+ }
1126
1653
  interface Manifest {
1127
1654
  manifest_version: string;
1128
1655
  compat: CompatibilityPolicy;
1129
1656
  profile: string;
1130
1657
  target: string;
1658
+ /** Selectable roots only. Internal mounts remain visible under `agents`, never here. */
1659
+ entries: EntryManifest[];
1131
1660
  agents: AgentManifest[];
1132
1661
  }
1133
1662
  /** Port of `emit.rs::build_agent_bundle`, minus the tool-map parameter (this back-end's is fixed,
1134
1663
  * see `LOCAL_TOOL_MAP`). */
1135
- declare function buildAgentManifest(component: PreparedComponent): AvailableAgentManifest;
1664
+ /**
1665
+ * Takes the plan-less display shape deliberately. A {@link PreparedComponent} is still accepted —
1666
+ * it satisfies this structurally — but nothing here reads a plan, so nothing here should require
1667
+ * one; see `AvailableDisplayComponent` for why a display must not carry it.
1668
+ */
1669
+ declare function buildAgentManifest(component: AvailableDisplayComponent, dependencies?: readonly ComponentDependency[]): AvailableAgentManifest;
1136
1670
  /** Never derives a plan, tool, or capability grant for an unavailable component. */
1137
- declare function buildUnavailableAgentManifest(component: UnavailableDisplayComponent): UnavailableAgentManifest;
1671
+ declare function buildUnavailableAgentManifest(component: UnavailableDisplayComponent, dependencies?: readonly ComponentDependency[]): UnavailableAgentManifest;
1138
1672
  /** Build the full display manifest for a `prepareDispatch` result. `raw` is the same IR the
1139
1673
  * dispatch was prepared from — re-parsed here (a second, cheap, pure parse) just to read
1140
1674
  * `profile`, which `PreparedDispatch` does not itself carry. */
1141
1675
  declare function buildManifest(prepared: PreparedDispatch | PreparedDisplayManifest, raw: string): Manifest;
1142
1676
 
1143
- export { type AgentManifest, type AskOptions, type AvailableAgentManifest, type BuildConfig, COMPONENT_TYPES, type CallLocalOptions, type CapabilityEntry, type CapabilityOutcome, type CapabilityProfile, type ChatRequest, ChatSession, type ClarifyOutcome, type CompatibilityPolicy, type ComponentNode, type ComponentOutcome, type ComponentType, type ContextBinding, type Criticality, DEFAULT_CLARIFY_THRESHOLD, DEFAULT_RENDER_FLAVOR, DEFAULT_TARGET, DESTRUCTIVE_BASH_DENY, type Denial, type DiscoverClaudeModelsOptions, DispatchError, type DispatchInput, type DispatchMeta, type DispatchOutcome, type DispatchPlan, type DispatchRunConfig, DispatchSessionError, type DisplayComponent, type Effect, type EmitOptions, type GateFailureMode, type GateKind, type GuardConfig, type Guardrail, type GuardrailManifest, type IrConfig, type LlmCall, MODEL_CATALOG_VERSION, type Manifest, type ModelCatalogModel, type ModelCatalogResult, type ModelCatalogUnavailableCode, ModelConfig, OUTCOME_KINDS, type Outcome, type OutcomeKind, type PreconditionResult, type PreparedComponent, type PreparedDispatch, type PreparedDisplayManifest, type ProvidedBy, type Provider, REALIZATION_KINDS, type RealizationKind, type RenderBlock, type RenderFlavor, type RenderGate, type RenderResult, type ResolutionReport, type ResolvedCapability, type ResolvedIntent, type RoutingMode, type RoutingPlan, type RunConfig, type RunResult, SUPPORTED_IR_VERSIONS, type SessionState, type StagedStep, type StepManifest, type StepMessage, type StepRealization, type StepUsage, TRIGGER_KINDS, type TargetId, type TierBinding, type ToolPlan, type ToolRef, type Trace, type Trigger, type TriggerKind, type Turn, type TurnResult, UNAVAILABLE_COMPONENT_REASON, type UnavailableAgentManifest, type UnavailableDisplayComponent, type WarbleIr, type WhenGuardOut, aggregateTrace, appendTurn, buildAgentManifest, buildChatRequest, buildDispatchPlan, buildManifest, buildStepMessages, buildToolDriverPrompt, buildTurnPrompt, buildUnavailableAgentManifest, callOpenAiCompat, collectRequiredCapabilities, createChatSession, createSessionState, decideClarify, discoverClaudeModels, dispatch, distillFollowup, distinctProviders, distinctTiers, emitAgentModule, extractCompletionText, inspectNodeCapabilities, isKnownTarget, knownTargetNames, lastResolvedIntent, lastSessionId, localProfile, makeReadOnlyGuard, parseIr, parseRenderFlavor, planProviderRouting, prepareDispatch, prepareDisplayManifest, profileFor, renderEnvelope, resolveCapabilities, resolveNodeCapabilities, resolveProjectCwd, resolveStagedSteps, runDispatch, runHybridTool, shouldSplitPerStepTier, usesLocalProvider };
1677
+ export { type AgentManifest, type AskOptions, type AvailableAgentManifest, type AvailableDisplayComponent, type BuildConfig, COMPONENT_TYPES, type CallLocalOptions, type CapabilityEntry, type CapabilityOutcome, type CapabilityProfile, type ChatRequest, ChatSession, type ClarifyOutcome, type CompatibilityPolicy, type ComponentCall, type ComponentCallTrace, type ComponentClosurePlan, type ComponentDependency, type ComponentDependencyManifest, type ComponentInvocationLimits, type ComponentInvocationRequest, type ComponentInvocationResult, ComponentInvocationRuntime, type ComponentNode, type ComponentOutcome, type ComponentRootResult, ComponentStepExecutionError, type ComponentStepResult, type ComponentStepRun, type ComponentStepRunner, type ComponentType, type ComposedRunConfig, type ContextBinding, type Criticality, DEFAULT_CLARIFY_THRESHOLD, DEFAULT_COMPONENT_INVOCATION_LIMITS, DEFAULT_RENDER_FLAVOR, DEFAULT_TARGET, DESTRUCTIVE_BASH_DENY, type Denial, type DiscoverClaudeModelsOptions, DispatchError, type DispatchInput, type DispatchMeta, type DispatchOutcome, type DispatchPlan, type DispatchRunConfig, DispatchSessionError, type DisplayComponent, type Effect, type EmitOptions, type EntryClosure, type EntryManifest, type GateFailureMode, type GateKind, type GuardConfig, type Guardrail, type GuardrailManifest, type IrConfig, type LlmCall, MODEL_CATALOG_VERSION, type Manifest, type ModelCatalogModel, type ModelCatalogResult, type ModelCatalogUnavailableCode, ModelConfig, OUTCOME_KINDS, type Outcome, type OutcomeKind, type PreconditionResult, type PreparedComponent, type PreparedDispatch, type PreparedDisplayManifest, type PromptFingerprint, type ProvidedBy, type Provider, REALIZATION_KINDS, type RealizationKind, type RenderBlock, type RenderFlavor, type RenderGate, type RenderResult, type ResolutionReport, type ResolvedCapability, type ResolvedIntent, type RoutingMode, type RoutingPlan, type RunConfig, type RunResult, SUPPORTED_IR_VERSIONS, type SessionState, type SlotSupply, type StagedStep, type StepManifest, type StepMessage, type StepRealization, type StepUsage, TRIGGER_KINDS, type TargetId, type TierBinding, type ToolPlan, type ToolRef, type Trace, type Trigger, type TriggerKind, type Turn, type TurnResult, UNAVAILABLE_COMPONENT_REASON, type UnansweredCondition, type UnavailableAgentManifest, type UnavailableDisplayComponent, type WarbleIr, type WhenGuardOut, aggregateTrace, appendTurn, applySlots, assertEligibleComponentCallee, assertEligibleComponentCaller, assertNoSlotReferences, assetDirForIr, buildAgentManifest, buildChatRequest, buildDispatchPlan, buildManifest, buildStepMessages, buildToolDriverPrompt, buildTurnPrompt, buildUnavailableAgentManifest, callOpenAiCompat, collectRequiredCapabilities, componentDependencies, composeCanUseTool, composeHooks, createChatSession, createSdkComponentStepRunner, createSessionState, decideClarify, discoverClaudeModels, dispatch, distillFollowup, distinctProviders, distinctTiers, emitAgentModule, extractCompletionText, fingerprintPrompts, fingerprintSurfaces, inspectNodeCapabilities, isKnownTarget, knownTargetNames, landAssets, lastResolvedIntent, lastSessionId, localProfile, makeReadOnlyGuard, normalizeComponentRequest, normalizeComponentResult, parseIr, parseRenderFlavor, parseSlotFlags, planProviderRouting, preflightDispatchAssets, prepareDispatch, prepareDisplayManifest, profileFor, promptSurfaces, promptSurfacesOf, renderEnvelope, resolveCapabilities, resolveComponentClosure, resolveNodeCapabilities, resolveProjectCwd, resolveSlots, resolveStagedSteps, runComposedDispatch, runDispatch, runHybridTool, shouldSplitPerStepTier, usesLocalProvider, validateAssets };