@arnilo/prism 0.0.24 → 0.0.26

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.
@@ -508,14 +508,144 @@ export interface SubscribeOptions {
508
508
  readonly overflow?: SubscriberOverflowPolicy;
509
509
  }
510
510
  export type AgentRunStatus = "succeeded" | "failed" | "aborted" | "suspended" | "denied";
511
- export type AgentRunInterruptionKind = "input_guardrail" | "tool_approval";
511
+ export type AgentRunInterruptionKind = "input_guardrail" | "tool_approval" | "elicitation";
512
+ export type ApprovalOutcome = "allow_once" | "allow_for_run" | "reject_once" | "reject_for_run";
513
+ export type PendingDecisionKind = "tool_approval" | "elicitation";
514
+ /** Redacted match scope for one pending or sticky decision; never contains raw tool arguments. */
515
+ export interface DecisionScope {
516
+ readonly toolName?: string;
517
+ readonly effectKind?: ToolEffectKind;
518
+ /** Redacted principal reference (tenant/kind/id); never a credential. */
519
+ readonly identity?: string;
520
+ /** Bounded argument-value constraints; deep-equal matched per key. */
521
+ readonly actionConstraints?: Readonly<Record<string, JsonValue>>;
522
+ /** SHA-256 of canonical JSON arguments; present instead of raw arguments. */
523
+ readonly argumentsHash?: string;
524
+ }
525
+ /** One redacted, unresolved approval request inside a suspended durable run. */
526
+ export interface PendingDecision {
527
+ /** Unique within the run; nested runs use supervisor-prefixed ids. */
528
+ readonly approvalId: string;
529
+ readonly kind: PendingDecisionKind;
530
+ readonly toolCallId?: string;
531
+ readonly scope: DecisionScope;
532
+ /** Bounded, redacted. */
533
+ readonly reason: string;
534
+ /** Typed payload contract for elicitation decisions. */
535
+ readonly elicitationSchema?: JsonObject;
536
+ /** Delegation chain, root-first; core-written, never client-supplied. */
537
+ readonly attribution?: {
538
+ readonly path: readonly string[];
539
+ };
540
+ }
512
541
  /** Redacted safe-boundary descriptor; never contains tool arguments. */
513
542
  export interface AgentRunInterruption {
514
543
  readonly kind: AgentRunInterruptionKind;
515
544
  readonly reason: string;
516
545
  readonly toolCallId?: string;
517
546
  readonly toolName?: string;
547
+ /** All unresolved approval requests of this suspension; absent for legacy single approvals. */
548
+ readonly pendingDecisions?: readonly PendingDecision[];
549
+ }
550
+ /** One host decision applied to one pending approval request. */
551
+ export interface RunDecision {
552
+ readonly approvalId: string;
553
+ readonly outcome: ApprovalOutcome;
554
+ /** Bounded to 2 KiB; redacted. */
555
+ readonly reason?: string;
556
+ /** Revalidated (schema, guardrails, policy) before dispatch; produces a new arguments hash. */
557
+ readonly modifiedArguments?: JsonObject;
558
+ /** Elicitation payload; validated against the pending decision's elicitationSchema. */
559
+ readonly elicitation?: JsonObject;
560
+ }
561
+ /** Run-scoped sticky decision; exact scope match, rechecked against policy, dropped at run end. */
562
+ export interface StickyDecision {
563
+ readonly scope: DecisionScope;
564
+ readonly outcome: "allow_for_run" | "reject_for_run";
565
+ readonly reason?: string;
566
+ readonly decidedAt: string;
567
+ /** Delegation path when the sticky was created for a nested-run decision. */
568
+ readonly attribution?: {
569
+ readonly path: readonly string[];
570
+ };
571
+ }
572
+ /** Root-visible link between one nested approval and the child-run approval id. */
573
+ export interface NestedRunApproval {
574
+ /** Root-visible approval id (hashed, non-enumerating across runs). */
575
+ readonly id: string;
576
+ /** Approval id as the nested run recorded it. */
577
+ readonly childApprovalId: string;
578
+ }
579
+ /** Root-visible link between a suspended nested run and the tool call that hosted it. */
580
+ export interface NestedRunRef {
581
+ readonly runId: string;
582
+ readonly sessionId?: string;
583
+ readonly toolCallId: string;
584
+ /** Redacted delegation path (child ids, root first). */
585
+ readonly path: readonly string[];
586
+ readonly approvals: readonly NestedRunApproval[];
587
+ /** Decisions persisted by a partial batch, keyed by root-visible approval id. */
588
+ readonly decisions?: Readonly<Record<string, RunDecision>>;
589
+ }
590
+ /** Outcome of resuming a nested run through the host-supplied hook. */
591
+ export type NestedRunOutcome = {
592
+ readonly status: "suspended";
593
+ readonly pendingDecisions: readonly PendingDecision[];
594
+ } | {
595
+ readonly status: "completed";
596
+ readonly value?: JsonValue;
597
+ } | {
598
+ readonly status: "failed";
599
+ readonly code: string;
600
+ readonly message: string;
601
+ };
602
+ /**
603
+ * Host hook that resumes a nested run (supervisor child) with child-visible decisions.
604
+ * Used both when a nested suspension first surfaces (sticky auto-apply) and when root
605
+ * decisions route back to the child on resume.
606
+ */
607
+ export type ResumeNestedRun = (nested: {
608
+ readonly ref: AgentRunRef;
609
+ readonly toolCallId: string;
610
+ readonly path: readonly string[];
611
+ }, decisions: readonly RunDecision[]) => Promise<NestedRunOutcome>;
612
+ /**
613
+ * Thrown by a delegated-run host (e.g. the supervisor) when a nested run suspends on
614
+ * pending decisions inside a tool execution. Core converts it into a root suspension
615
+ * with attributed, root-visible approval ids; the dispatching wrapper attaches `toolCall`.
616
+ */
617
+ export declare class AgentDelegationSuspendedError extends Error {
618
+ readonly ref: AgentRunRef;
619
+ readonly pendingDecisions: readonly PendingDecision[];
620
+ /** Redacted delegation path (child ids) used when decisions carry no attribution. */
621
+ readonly path?: readonly string[] | undefined;
622
+ readonly code = "ERR_PRISM_DELEGATION_SUSPENDED";
623
+ toolCall?: ToolCallContent;
624
+ constructor(ref: AgentRunRef, pendingDecisions: readonly PendingDecision[],
625
+ /** Redacted delegation path (child ids) used when decisions carry no attribution. */
626
+ path?: readonly string[] | undefined);
627
+ }
628
+ /** Shared decision-contract violations. Unknown and foreign approval ids share one non-enumerating error. */
629
+ export declare class AgentDecisionError extends Error {
630
+ readonly code: "ERR_PRISM_DECISION_STALE" | "ERR_PRISM_DECISION_UNKNOWN" | "ERR_PRISM_DECISION_DUPLICATE" | "ERR_PRISM_DECISION_SCOPE" | "ERR_PRISM_DECISION_INVALID" | "ERR_PRISM_DECISION_LIMIT";
631
+ constructor(code: "ERR_PRISM_DECISION_STALE" | "ERR_PRISM_DECISION_UNKNOWN" | "ERR_PRISM_DECISION_DUPLICATE" | "ERR_PRISM_DECISION_SCOPE" | "ERR_PRISM_DECISION_INVALID" | "ERR_PRISM_DECISION_LIMIT", message: string, options?: {
632
+ readonly cause?: unknown;
633
+ });
518
634
  }
635
+ export declare const DEFAULT_MAX_PENDING_DECISIONS = 32;
636
+ export declare const HARD_MAX_PENDING_DECISIONS = 128;
637
+ export declare const DEFAULT_MAX_STICKY_DECISIONS = 64;
638
+ export declare const HARD_MAX_STICKY_DECISIONS = 256;
639
+ export declare const MAX_DECISION_REASON_BYTES: number;
640
+ export declare const HARD_MAX_DECISION_REASON_BYTES: number;
641
+ export declare const MAX_ELICITATION_BYTES: number;
642
+ export declare const HARD_MAX_ELICITATION_BYTES: number;
643
+ export declare const MAX_ACTION_CONSTRAINTS = 32;
644
+ export declare const HARD_MAX_ACTION_CONSTRAINTS = 64;
645
+ /** Maximum delegation attribution depth for surfaced nested pending decisions. */
646
+ export declare const MAX_ATTRIBUTION_DEPTH = 8;
647
+ export declare const MAX_ACTION_CONSTRAINT_BYTES: number;
648
+ export declare const HARD_MAX_ACTION_CONSTRAINT_BYTES: number;
519
649
  export interface AgentRunStateOptions {
520
650
  readonly checkpoints: CheckpointStore;
521
651
  /** Host-authored immutable revision required for durable runs. */
@@ -524,6 +654,8 @@ export interface AgentRunStateOptions {
524
654
  readonly interruptBeforeTool?: boolean;
525
655
  readonly maxStateBytes?: number;
526
656
  readonly fencingToken?: number;
657
+ /** Enables sticky auto-apply when a nested suspension first surfaces during this run. */
658
+ readonly resumeNestedRun?: ResumeNestedRun;
527
659
  }
528
660
  /** Versioned, redacted checkpoint payload. Treat as opaque except status/version/interruption. */
529
661
  export interface AgentRunState {
@@ -540,8 +672,11 @@ export interface AgentRunState {
540
672
  readonly version?: number;
541
673
  }
542
674
  export interface AgentRunResume {
543
- readonly decision: "approve" | "deny";
544
675
  readonly expectedVersion: number;
676
+ /** Legacy single-approval path; `approve` allows all pending once, `deny` terminates the run denied. */
677
+ readonly decision?: "approve" | "deny";
678
+ /** Batch decision path; exactly one of decision/decisions. Applied as one atomic CAS transition. */
679
+ readonly decisions?: readonly RunDecision[];
545
680
  }
546
681
  export interface AgentRunResumeOptions {
547
682
  readonly checkpoints: CheckpointStore;
@@ -549,6 +684,8 @@ export interface AgentRunResumeOptions {
549
684
  readonly definitionRevision: string;
550
685
  readonly ownership?: OwnershipScope;
551
686
  readonly fencingToken?: number;
687
+ /** Routes root decisions for nested-run approvals back to the child (e.g. supervisor). */
688
+ readonly resumeNestedRun?: ResumeNestedRun;
552
689
  }
553
690
  /** Bounded, abortable options for `resumeAgentRunStream()`. */
554
691
  export interface AgentRunResumeStreamOptions extends AgentRunResumeOptions, SubscribeOptions {
@@ -566,6 +703,13 @@ export declare class AgentRunStateError extends Error {
566
703
  readonly code = "ERR_PRISM_AGENT_RUN_STATE";
567
704
  constructor(message: string);
568
705
  }
706
+ /** Durable-loop contract violations: hook-less custom strategy on a durable run, invalid snapshot, or revision drift. */
707
+ export declare class AgentLoopStateError extends Error {
708
+ readonly code: "ERR_PRISM_LOOP_NOT_DURABLE" | "ERR_PRISM_LOOP_SNAPSHOT" | "ERR_PRISM_LOOP_REVISION";
709
+ constructor(code: "ERR_PRISM_LOOP_NOT_DURABLE" | "ERR_PRISM_LOOP_SNAPSHOT" | "ERR_PRISM_LOOP_REVISION", message: string, options?: {
710
+ readonly cause?: unknown;
711
+ });
712
+ }
569
713
  /** Terminal result of `session.run()` / `session.prompt()`. Failed and aborted runs throw {@link AgentRunError} with this shape attached. */
570
714
  export interface AgentRunResult {
571
715
  readonly sessionId: string;
@@ -847,6 +991,19 @@ export interface ToolEffectDeclaration {
847
991
  }
848
992
  /** Runs after argument validation. It must be synchronous, deterministic, bounded, and side-effect-free. */
849
993
  export type ToolEffectClassifier = (args: JsonObject, context: ToolExecutionContext) => ToolEffectDeclaration;
994
+ /**
995
+ * Elicitation contract declared by a tool. When a durable gated run suspends on this tool,
996
+ * the pending decision has kind `elicitation` and carries this schema as its payload contract;
997
+ * the resume decision's `elicitation` payload resolves the call without executing it.
998
+ */
999
+ export interface ToolElicitationRequest {
1000
+ /** Typed payload contract; bounded to HARD_MAX_ELICITATION_BYTES when serialized. */
1001
+ readonly schema: JsonObject;
1002
+ /** Human-facing reason (e.g. the question); bounded to MAX_DECISION_REASON_BYTES. */
1003
+ readonly reason?: string;
1004
+ /** Answer-shape validation beyond structural schema checks; throw to reject the payload. */
1005
+ readonly validate?: (payload: JsonObject) => void;
1006
+ }
850
1007
  export interface ToolDefinition {
851
1008
  readonly name: string;
852
1009
  readonly description?: string;
@@ -855,6 +1012,8 @@ export interface ToolDefinition {
855
1012
  readonly exclusive?: boolean;
856
1013
  /** Optional side-effect declaration. Omitted tools retain legacy unmanaged dispatch. */
857
1014
  readonly effect?: ToolEffectDeclaration | ToolEffectClassifier;
1015
+ /** Optional elicitation contract for durable gating; return undefined to fall back to plain tool approval. */
1016
+ readonly elicitation?: (args: JsonObject, context: ToolExecutionContext) => ToolElicitationRequest | undefined;
858
1017
  execute(args: JsonObject, context: ToolExecutionContext): Promise<ToolResult> | ToolResult;
859
1018
  }
860
1019
  export interface ToolRegistry {
@@ -2022,8 +2181,13 @@ export interface LoopContext {
2022
2181
  /** Maximum independent tool calls dispatched concurrently per provider turn. Default `1`. */
2023
2182
  readonly toolConcurrency: number;
2024
2183
  assemble(nextInput: AgentInput, toolResults?: readonly ToolResult[], turn?: number): Promise<ProviderRequest>;
2025
- /** Charges a complete tool round before any call in it can start. */
2026
- chargeToolRound?(calls: readonly ToolCallContent[]): void;
2184
+ /**
2185
+ * Charges a complete tool round before any call in it can start. On durable interrupt
2186
+ * runs this is also the round-level approval gate: it collects every gated call of the
2187
+ * round into one suspension. Loops must await it; dispatch without it falls back to
2188
+ * per-call single-decision suspensions.
2189
+ */
2190
+ chargeToolRound?(calls: readonly ToolCallContent[]): void | Promise<void>;
2027
2191
  generate(request: ProviderRequest): Promise<ProviderTurnResult>;
2028
2192
  dispatchToolCall(call: ToolCallContent): Promise<ToolResult>;
2029
2193
  isToolCallExclusive?(call: ToolCallContent): boolean;
@@ -2033,10 +2197,23 @@ export interface LoopContext {
2033
2197
  hasPendingSteers?(): boolean;
2034
2198
  /** Drain pending steers into history/session. Returns true when any were applied. */
2035
2199
  applyPendingSteers?(): Promise<boolean>;
2200
+ /** Snapshot captured at the last suspension when the strategy declared snapshot/restore. Present only on resume. */
2201
+ readonly restoredLoopState?: JsonValue;
2036
2202
  }
2037
2203
  export interface AgentLoopStrategy {
2038
2204
  readonly name: string;
2205
+ /** Host-authored loop revision. Joins the durable-run fingerprint when snapshot hooks are present. */
2206
+ readonly revision?: string;
2039
2207
  run(ctx: LoopContext): Promise<Usage | undefined>;
2208
+ /**
2209
+ * Capture loop-local resumable state at suspension. Must return a JSON-compatible value;
2210
+ * core bounds and redacts it inside the durable run-state envelope. Declare together with
2211
+ * `restore`; a custom strategy without both hooks is rejected before any provider call on
2212
+ * durable runs (`AgentLoopStateError` / `ERR_PRISM_LOOP_NOT_DURABLE`).
2213
+ */
2214
+ snapshot?(): JsonValue;
2215
+ /** Rehydrate from a previously captured snapshot; must throw on drift. Called before `run` on resume. */
2216
+ restore?(snapshot: JsonValue): void;
2040
2217
  }
2041
2218
  export type AgentLoopOptions = {
2042
2219
  readonly strategy: "single-shot";
package/dist/contracts.js CHANGED
@@ -1,3 +1,47 @@
1
+ /**
2
+ * Thrown by a delegated-run host (e.g. the supervisor) when a nested run suspends on
3
+ * pending decisions inside a tool execution. Core converts it into a root suspension
4
+ * with attributed, root-visible approval ids; the dispatching wrapper attaches `toolCall`.
5
+ */
6
+ export class AgentDelegationSuspendedError extends Error {
7
+ ref;
8
+ pendingDecisions;
9
+ path;
10
+ code = "ERR_PRISM_DELEGATION_SUSPENDED";
11
+ toolCall;
12
+ constructor(ref, pendingDecisions,
13
+ /** Redacted delegation path (child ids) used when decisions carry no attribution. */
14
+ path) {
15
+ super("Delegated run suspended");
16
+ this.ref = ref;
17
+ this.pendingDecisions = pendingDecisions;
18
+ this.path = path;
19
+ this.name = "AgentDelegationSuspendedError";
20
+ }
21
+ }
22
+ /** Shared decision-contract violations. Unknown and foreign approval ids share one non-enumerating error. */
23
+ export class AgentDecisionError extends Error {
24
+ code;
25
+ constructor(code, message, options) {
26
+ super(message, options);
27
+ this.code = code;
28
+ this.name = "AgentDecisionError";
29
+ }
30
+ }
31
+ export const DEFAULT_MAX_PENDING_DECISIONS = 32;
32
+ export const HARD_MAX_PENDING_DECISIONS = 128;
33
+ export const DEFAULT_MAX_STICKY_DECISIONS = 64;
34
+ export const HARD_MAX_STICKY_DECISIONS = 256;
35
+ export const MAX_DECISION_REASON_BYTES = 2 * 1024;
36
+ export const HARD_MAX_DECISION_REASON_BYTES = 8 * 1024;
37
+ export const MAX_ELICITATION_BYTES = 16 * 1024;
38
+ export const HARD_MAX_ELICITATION_BYTES = 64 * 1024;
39
+ export const MAX_ACTION_CONSTRAINTS = 32;
40
+ export const HARD_MAX_ACTION_CONSTRAINTS = 64;
41
+ /** Maximum delegation attribution depth for surfaced nested pending decisions. */
42
+ export const MAX_ATTRIBUTION_DEPTH = 8;
43
+ export const MAX_ACTION_CONSTRAINT_BYTES = 4 * 1024;
44
+ export const HARD_MAX_ACTION_CONSTRAINT_BYTES = 16 * 1024;
1
45
  export class AgentRunStateError extends Error {
2
46
  code = "ERR_PRISM_AGENT_RUN_STATE";
3
47
  constructor(message) {
@@ -5,6 +49,15 @@ export class AgentRunStateError extends Error {
5
49
  this.name = "AgentRunStateError";
6
50
  }
7
51
  }
52
+ /** Durable-loop contract violations: hook-less custom strategy on a durable run, invalid snapshot, or revision drift. */
53
+ export class AgentLoopStateError extends Error {
54
+ code;
55
+ constructor(code, message, options) {
56
+ super(message, options);
57
+ this.code = code;
58
+ this.name = "AgentLoopStateError";
59
+ }
60
+ }
8
61
  export class AgentRunError extends Error {
9
62
  result;
10
63
  constructor(result, options) {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { resolveAgentDefinition } from "./agent-definitions.js";
2
2
  export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop, } from "./agent-loops.js";
3
3
  export type { AgentRunLifecycle, AgentRunLifecycleAgent, AgentRunLifecycleOptions, AgentRunLifecycleRequest, AgentRunLifecycleStreamRequest, } from "./agent-run-lifecycle.js";
4
4
  export { createAgentRunLifecycle } from "./agent-run-lifecycle.js";
5
- export type { StoredAgentRunState } from "./agent-run-state.js";
5
+ export type { PendingToolCall, StoredAgentRunState } from "./agent-run-state.js";
6
6
  export { AGENT_RUN_STATE_NAMESPACE, AGENT_RUN_STATE_SCHEMA_VERSION, agentFingerprint, DEFAULT_MAX_AGENT_RUN_STATE_BYTES, HARD_MAX_AGENT_RUN_STATE_BYTES, loadAgentRunState, } from "./agent-run-state.js";
7
7
  export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
8
8
  export type { ArtifactApproval, ArtifactApprovalState, ArtifactCitation, ArtifactDecisionState, ArtifactDeliveryToken, ArtifactRecord, ArtifactRevision, } from "./artifacts.js";
@@ -20,8 +20,8 @@ export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMe
20
20
  export type { ContextBudget, ContextBudgetMessageGroups, ContextBudgetOmission, ContextBudgetOmissionKind, ContextBudgetReport, } from "./context-budget.js";
21
21
  export { applyContextBudget, CONTEXT_BUDGET_ERROR_CODE, CONTEXT_BUDGET_REPORT_METADATA_KEY, ContextBudgetError, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS, estimateAssemblyTokens, estimateMessageBytes, estimateMessageTokens, estimateTextBytes, estimateTextTokens, getContextBudgetReport, HARD_MAX_CONTEXT_BUDGET_BYTES, HARD_MAX_CONTEXT_BUDGET_OMISSIONS, HARD_MAX_CONTEXT_BUDGET_TOKENS, isContextBudgetError, resolveContextBudget, } from "./context-budget.js";
22
22
  export type * from "./contracts.js";
23
- export type { ProviderResolver, RealtimeCaps, RealtimeEvent, RealtimeSession, RealtimeSessionFactory, RealtimeSessionOptions, RunLimitCounters, RunLimitName, SecureAgentOptions, ToolCallAuthority, ToolEffectClassifier, ToolEffectDeclaration, ToolEffectIdempotency, ToolEffectKey, ToolEffectKind, ToolEffectRecord, ToolEffectStatus, ToolEffectStore, ToolEffectTransition, } from "./contracts.js";
24
- export { AgentRunError, AgentRunStateError, assertSessionMetadataKey, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEERS, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, isSessionAppendConflict, isSessionEntryKind, isSessionSearchUnsupported, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SESSION_SEARCH_UNSUPPORTED_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
23
+ export type { ApprovalOutcome, DecisionScope, NestedRunApproval, NestedRunOutcome, NestedRunRef, PendingDecision, PendingDecisionKind, ProviderResolver, ResumeNestedRun, RunDecision, StickyDecision, RealtimeCaps, RealtimeEvent, RealtimeSession, RealtimeSessionFactory, RealtimeSessionOptions, RunLimitCounters, RunLimitName, SecureAgentOptions, ToolCallAuthority, ToolEffectClassifier, ToolEffectDeclaration, ToolEffectIdempotency, ToolEffectKey, ToolEffectKind, ToolEffectRecord, ToolEffectStatus, ToolEffectStore, ToolEffectTransition, ToolElicitationRequest, } from "./contracts.js";
24
+ export { AgentDecisionError, AgentDelegationSuspendedError, AgentLoopStateError, MAX_ATTRIBUTION_DEPTH, AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_DECISIONS, DEFAULT_MAX_STICKY_DECISIONS, HARD_MAX_PENDING_DECISIONS, HARD_MAX_STICKY_DECISIONS, MAX_ACTION_CONSTRAINT_BYTES, MAX_ACTION_CONSTRAINTS, MAX_DECISION_REASON_BYTES, MAX_ELICITATION_BYTES, HARD_MAX_ACTION_CONSTRAINT_BYTES, HARD_MAX_ACTION_CONSTRAINTS, HARD_MAX_DECISION_REASON_BYTES, HARD_MAX_ELICITATION_BYTES, assertSessionMetadataKey, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEERS, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, isSessionAppendConflict, isSessionEntryKind, isSessionSearchUnsupported, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SESSION_SEARCH_UNSUPPORTED_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
25
25
  export { parseAgentFile, parseSkillFile } from "./contribution-parsing.js";
26
26
  export type { ContributionRegistries, ContributionRegistriesOptions, ContributionRegistry, ContributionRegistryOptions, } from "./contributions.js";
27
27
  export { createContributionRegistries, createContributionRegistry, registerDiscoveredContributions } from "./contributions.js";
@@ -105,5 +105,5 @@ export type { ToolEffectErrorCode } from "./tool-effects.js";
105
105
  export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
106
106
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
107
107
  export declare const name = "prism";
108
- export declare const version = "0.0.24";
108
+ export declare const version = "0.0.26";
109
109
  export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ export { createDefaultCompactionStrategy, isCompactionEntryData } from "./compac
10
10
  export { assertJsonObject, isJsonObject, loadConfigLayers, mergeConfigLayers } from "./config.js";
11
11
  export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, resolveMediaContentBlocks, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
12
12
  export { applyContextBudget, CONTEXT_BUDGET_ERROR_CODE, CONTEXT_BUDGET_REPORT_METADATA_KEY, ContextBudgetError, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS, estimateAssemblyTokens, estimateMessageBytes, estimateMessageTokens, estimateTextBytes, estimateTextTokens, getContextBudgetReport, HARD_MAX_CONTEXT_BUDGET_BYTES, HARD_MAX_CONTEXT_BUDGET_OMISSIONS, HARD_MAX_CONTEXT_BUDGET_TOKENS, isContextBudgetError, resolveContextBudget, } from "./context-budget.js";
13
- export { AgentRunError, AgentRunStateError, assertSessionMetadataKey, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEERS, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, isSessionAppendConflict, isSessionEntryKind, isSessionSearchUnsupported, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SESSION_SEARCH_UNSUPPORTED_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
13
+ export { AgentDecisionError, AgentDelegationSuspendedError, AgentLoopStateError, MAX_ATTRIBUTION_DEPTH, AgentRunError, AgentRunStateError, DEFAULT_MAX_PENDING_DECISIONS, DEFAULT_MAX_STICKY_DECISIONS, HARD_MAX_PENDING_DECISIONS, HARD_MAX_STICKY_DECISIONS, MAX_ACTION_CONSTRAINT_BYTES, MAX_ACTION_CONSTRAINTS, MAX_DECISION_REASON_BYTES, MAX_ELICITATION_BYTES, HARD_MAX_ACTION_CONSTRAINT_BYTES, HARD_MAX_ACTION_CONSTRAINTS, HARD_MAX_DECISION_REASON_BYTES, HARD_MAX_ELICITATION_BYTES, assertSessionMetadataKey, DEFAULT_MAX_PENDING_STEER_BYTES, DEFAULT_MAX_PENDING_STEERS, DEFAULT_MAX_SESSION_SEARCH_CURSOR_BYTES, DEFAULT_MAX_SESSION_SEARCH_FTS_CANDIDATES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_QUERY_BYTES, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, DEFAULT_SESSION_SEARCH_LIMIT, HARD_MAX_PENDING_STEER_BYTES, HARD_MAX_PENDING_STEERS, HARD_MAX_SESSION_SEARCH_CURSOR_BYTES, HARD_MAX_SESSION_SEARCH_FTS_CANDIDATES, HARD_MAX_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_LINEAR_BYTES, HARD_MAX_SESSION_SEARCH_LINEAR_ENTRIES, HARD_MAX_SESSION_SEARCH_LINEAR_SESSIONS, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, HARD_MAX_SESSION_SEARCH_SNIPPET_BYTES, isSessionAppendConflict, isSessionEntryKind, isSessionSearchUnsupported, resolveSessionSearchQuery, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SESSION_SEARCH_UNSUPPORTED_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, } from "./contracts.js";
14
14
  export { parseAgentFile, parseSkillFile } from "./contribution-parsing.js";
15
15
  export { createContributionRegistries, createContributionRegistry, registerDiscoveredContributions } from "./contributions.js";
16
16
  export { CONVERSATION_METADATA_KEY, ConversationError, conversationMarkerMetadata, conversationThreadFromRecord, DEFAULT_MAX_CONVERSATION_CURSOR_BYTES, decodeConversationReplayCursor, encodeConversationReplayCursor, HARD_MAX_CONVERSATION_CURSOR_BYTES, } from "./conversations.js";
@@ -57,6 +57,6 @@ export { createToolParameterValidator, createToolRegistry, dispatchToolCall, fil
57
57
  export { createMemoryToolEffectStore, ToolEffectError } from "./tool-effects.js";
58
58
  export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
59
59
  export const name = "prism";
60
- export const version = "0.0.24";
60
+ export const version = "0.0.26";
61
61
  export const description = "Agent harness for AI providers, agents, sessions, and tools.";
62
62
  //# sourceMappingURL=index.js.map
package/dist/tools.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentEvent, ErrorInfo, Guardrails, JsonObject, OwnershipScope, RunLedger, ToolCallContent, ToolDefinition, ToolExecutionContext, ToolEffectStore, ToolRegistry, ToolResult } from "./contracts.js";
1
+ import type { AgentEvent, ErrorInfo, Guardrails, JsonObject, OwnershipScope, RunLedger, ToolCallContent, ToolDefinition, ToolExecutionContext, ToolEffectDeclaration, ToolEffectStore, ToolRegistry, ToolResult } from "./contracts.js";
2
2
  import type { MiddlewareRegistry } from "./middleware.js";
3
3
  import { type SecretRedactor } from "./redaction.js";
4
4
  import { type DuplicateRegistrationOptions } from "./registry-options.js";
@@ -57,3 +57,4 @@ export interface ToolRegistryOptions extends DuplicateRegistrationOptions {
57
57
  export declare function createToolRegistry(tools?: readonly ToolDefinition[], options?: ToolRegistryOptions): ToolRegistry;
58
58
  export declare function filterTools(tools: readonly ToolDefinition[], filter?: ToolFilterInput): readonly ToolDefinition[];
59
59
  export declare function dispatchToolCall(options: DispatchToolCallOptions): Promise<ToolResult>;
60
+ export declare function resolveToolEffectDeclaration(tool: ToolDefinition, args: JsonObject, context: ToolExecutionContext): ToolEffectDeclaration | undefined;
package/dist/tools.js CHANGED
@@ -153,7 +153,8 @@ export async function dispatchToolCall(options) {
153
153
  }
154
154
  catch (error) {
155
155
  await failBeforeEffect(effect, isSuspended(error) ? "failed_retryable" : "failed_terminal");
156
- if (isSuspended(error))
156
+ // Loop-state contract errors (snapshot capture) are terminal run errors, not tool errors.
157
+ if (isSuspended(error) || isLoopStateError(error) || isDelegationSuspended(error))
157
158
  throw error;
158
159
  return blocked(mediatedCall, context, "execution_denied", errorToErrorInfo(error, secrets), options, startedAt);
159
160
  }
@@ -222,6 +223,14 @@ export async function dispatchToolCall(options) {
222
223
  catch (error) {
223
224
  if (completedResult)
224
225
  return completedResult;
226
+ // Nested-run suspensions must propagate to the run loop, never become tool errors.
227
+ if (isDelegationSuspended(error)) {
228
+ if (effect && (effect.dispatched || dispatchAttempted))
229
+ await unknownEffectResult(effect, mediatedCall);
230
+ else
231
+ await failBeforeEffect(effect, "failed_retryable");
232
+ throw error;
233
+ }
225
234
  if (effect && (effect.dispatched || dispatchAttempted))
226
235
  return finishUnknownEffect(effect, mediatedCall, context, options, startedAt);
227
236
  await failBeforeEffect(effect, "failed_terminal");
@@ -298,7 +307,7 @@ async function prepareToolEffect(tool, call, context, options) {
298
307
  },
299
308
  };
300
309
  }
301
- function resolveToolEffectDeclaration(tool, args, context) {
310
+ export function resolveToolEffectDeclaration(tool, args, context) {
302
311
  const classifierContext = Object.freeze({
303
312
  sessionId: context.sessionId,
304
313
  runId: context.runId,
@@ -389,6 +398,12 @@ function effectErrorResult(call, code, message) {
389
398
  function isSuspended(error) {
390
399
  return error?.code === "ERR_PRISM_AGENT_RUN_SUSPENDED";
391
400
  }
401
+ function isLoopStateError(error) {
402
+ return typeof error?.code === "string" && error.code.startsWith("ERR_PRISM_LOOP_");
403
+ }
404
+ function isDelegationSuspended(error) {
405
+ return error?.code === "ERR_PRISM_DELEGATION_SUSPENDED";
406
+ }
392
407
  async function checkCall(call, options, startedAt) {
393
408
  const context = options.context;
394
409
  const tool = options.registry.get(call.name);
@@ -1,12 +1,12 @@
1
1
  # 0.1.0 / 1.0 Readiness Gates
2
2
 
3
- Status: **0.0.24** is the current release line (Phase 7 distributed events and recoverable tool effects); **1.0** readiness remains operator-gated, not automatic.
3
+ Status: **0.0.26** is the current release line (Phase 9 coding intelligence, managed processes, forge, and safe egress); **1.0** readiness remains operator-gated, not automatic.
4
4
 
5
5
  This page distills runnable readiness gates into one command-per-gate table. The
6
6
  **Last evidence** column records the 2026-07-26 **0.0.16** baseline snapshot
7
7
  (Phase 11, Node v24.18.0, Linux x86_64). Treat it as historical floor evidence,
8
8
  not the current release tag. Re-run each gate on the target release tree before
9
- cutting 0.0.24 / 1.0. The decision to cut 1.0 stays with the operator after
9
+ cutting 0.0.26 / 1.0. The decision to cut 1.0 stays with the operator after
10
10
  operator-gated legs run in a protected environment and Phase 12 demand evidence
11
11
  exists.
12
12
 
@@ -14,15 +14,16 @@ Evidence trail: [`docs/review-coverage-2026-07-26-phase-11.md`](./review-coverag
14
14
  (addenda 0–9), [`docs/release-and-install.md`](./release-and-install.md),
15
15
  [`docs/migration.md`](./migration.md), [`docs/performance.md`](./performance.md).
16
16
 
17
- ## Current line (0.0.24)
17
+ ## Current line (0.0.26)
18
18
 
19
19
  | Item | Status |
20
20
  |---|---|
21
- | Published graph | **47** publishable manifests at **0.0.24** (`docs/release-and-install.md`) |
22
- | Phase 7 distributed events/effects | `AgentEventSource`, `ToolEffectStore`, AG-UI MCP/A2A fronting; enterprise `toolEffects`; schema v6/v7 + enterprise migration 002 |
23
- | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.23 → 0.0.24 distributed events and recoverable tool effects` |
24
- | Protected database evidence | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; `benchmark-0.0.24.json` under Task 0 ceilings |
25
- | Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.24 database evidence is recorded separately |
21
+ | Published graph | **48** publishable manifests at **0.0.26** (`docs/release-and-install.md`) |
22
+ | Phase 9 coding intelligence / processes / forge / egress | Git-aware enumeration, LSP language intelligence, managed process sessions, GitHub forge with idempotent handoff, allow-list egress proxy with rebinding defense |
23
+ | Docs tripwires | `node --test dist/__tests__/docs.test.js` — migration section `0.0.25 → 0.0.26 coding intelligence, processes, forge, and egress` |
24
+ | Network-free Phase 9 evidence | `scripts/phase9-conformance.test.mjs`; `benchmark-0.0.26.json` under Task 0 ceilings |
25
+ | Protected database evidence (Phase 7) | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres`; `benchmark-0.0.24.json` under prior ceilings |
26
+ | Readiness table below | **0.0.16 measured values** remain historical network-free baseline; 0.0.26 Phase 9 evidence is recorded separately |
26
27
 
27
28
  ## Gate table
28
29
 
package/docs/a2a.md CHANGED
@@ -39,6 +39,30 @@ const handler = createA2AHandler({
39
39
 
40
40
  Parts, messages, artifacts, histories, metadata, and aggregate responses are untrusted. Rich content remains in A2A task/message/artifact contracts for host mapping; it is never promoted to system instructions or automatically loaded as a Prism resource.
41
41
 
42
+ ## AG-UI server-side exposure (Task 13, 0.0.26)
43
+
44
+ `createAgUiA2AServer()` in `@arnilo/prism-ag-ui` fronts one host-selected **local AG-UI agent** as an A2A 1.0 server, the reverse direction of `createAgUiA2AAdapter()`: remote A2A clients start and stream local runs through the same AG-UI input allow-list and event mapper as the AG-UI SSE path (same projection, redaction, and byte caps). It reuses this package's `createA2AHandler` transport/lifecycle; it creates no second runtime, task store, or worker. Requires the optional `@arnilo/prism-supervisor` peer (imported lazily; plain `@arnilo/prism-ag-ui` imports keep working without it).
45
+
46
+ ```ts
47
+ import { createAgentEventSourceAgUiReplay, createAgUiA2AServer } from "@arnilo/prism-ag-ui";
48
+
49
+ const server = await createAgUiA2AServer({
50
+ card: agentCard, // A2A agent card (streaming: true)
51
+ authorize: (input) => authorizeA2A(input), // A2A auth → { ownership } (also the AG-UI authorization)
52
+ sessionFactory: ({ threadId, authorization, signal, input }) =>
53
+ createAgUiSession(authorization, input), // same shape as createAgUiHandler
54
+ input: { project: projectAgUiInput }, // AG-UI full-input allow-list
55
+ projection, redactor, a2ui, limits, // AG-UI mapper options
56
+ durable: { // optional: GetTask/SubscribeToTask after a run finishes
57
+ source: persistence.events, // durable AgentEventSource
58
+ resolveTask: async ({ id, authorization }) => ({ task, run }), // host-owned task→run correlation
59
+ },
60
+ });
61
+ // host mounts: new Request(url, init) → server(request)
62
+ ```
63
+
64
+ Semantics: `SendMessage` runs the local agent to completion and returns a terminal task with collected text artifacts; `SendStreamingMessage` (client `returnImmediately: true`) streams text/activity/state as bounded A2A artifact updates, then a terminal task. `agent_suspended` closes the stream with `TASK_STATE_INPUT_REQUIRED`; continuation stays host-owned (AG-UI resume). `GetTask`/`ListTasks`/`CancelTask` cover a bounded in-memory registry of tasks started on this instance; with `durable`, `SubscribeToTask`/`GetTask` also resolve host-correlated runs and replay the durable source with cursor event ids (at-least-once; clients dedupe by `eventId`). Text parts become the AG-UI user message; raw/data/url parts stay disabled unless `parts` selects them, and then arrive only in `forwardedProps.a2a` for `input.project`. Task ids default to `task-<uuid>`; hosts may own them via `selectTaskId`. `tasks` may be supplied to replace the built-in lifecycle entirely. A2A remains separately mounted — no route is added to `createPrismHandler()`.
65
+
42
66
  ## Implementation example
43
67
 
44
68
  ```ts
@@ -7,7 +7,7 @@ This page records Prism's compatibility review against official AG-UI `@ag-ui/co
7
7
  Official material reviewed:
8
8
 
9
9
  - [Events](https://docs.ag-ui.com/concepts/events), [messages](https://docs.ag-ui.com/concepts/messages), [tools](https://docs.ag-ui.com/concepts/tools), [state](https://docs.ag-ui.com/concepts/state), [reasoning](https://docs.ag-ui.com/concepts/reasoning), [interrupts](https://docs.ag-ui.com/concepts/interrupts), [capabilities](https://docs.ag-ui.com/concepts/capabilities), [serialization](https://docs.ag-ui.com/concepts/serialization), [server quickstart](https://docs.ag-ui.com/quickstart/server), and [protocol architecture](https://docs.ag-ui.com/concepts/architecture).
10
- - Official [MCP/A2A/AG-UI relationship](https://docs.ag-ui.com/agentic-protocols), [integrations](https://docs.ag-ui.com/integrations), [`@ag-ui/mcp-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-middleware), [`@ag-ui/mcp-apps-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-apps-middleware), [`@ag-ui/a2a`](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/a2a/typescript), and [`@ag-ui/a2a-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/a2a-middleware).
10
+ - Official [MCP/A2A/AG-UI relationship](https://docs.ag-ui.com/agentic-protocols), [integrations](https://docs.ag-ui.com/integrations), [`@ag-ui/mcp-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-middleware), [`@ag-ui/mcp-apps-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/mcp-apps-middleware), [`@ag-ui/a2ui-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/a2ui-middleware) (Prism ships an in-package opt-in painter with frozen caps; no runtime dependency), [`@ag-ui/a2a`](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/a2a/typescript), and [`@ag-ui/a2a-middleware`](https://github.com/ag-ui-protocol/ag-ui/tree/main/middlewares/a2a-middleware).
11
11
  - A2A [current specification](https://a2a-protocol.org/latest/specification/) and [streaming rules](https://a2a-protocol.org/latest/topics/streaming-and-async/).
12
12
  - MCP Apps [SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) and the [`io.modelcontextprotocol/ui` draft specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx).
13
13