> ## Documentation Index
> Fetch the complete documentation index at: https://agent.minimax.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent API reference

> Complete signatures, data types, events, permissions, and cancellation for Mini App Agent calls from the page and Node.

This reference describes MiniMax Code v3.1.6. The page entry is `window.mcode?.miniapp?.agent`; the Node entry is `context.agent`. For usage and interaction examples, see [Working with the Agent](https://agent.minimax.io/docs/miniapp/authors/agent.md). The types below describe injected host interfaces; no runtime SDK is required. Other host interfaces are covered by [Client API](https://agent.minimax.io/docs/miniapp/reference/client-api.md) and [Node API](https://agent.minimax.io/docs/miniapp/reference/node-api.md).

## Access and session ownership

Declare the required entries in `miniapp/miniapp.json`, such as `agentAccess: ["session.run", "session.read"]`. The two grants are independent and users can switch each off in plugin details. Node injects `context.agent` when at least one entry is declared. A page object's presence does not establish permission to call its methods.

| Operation | Required access |
| - | - |
| `run`, `reply`, `answer`, `open`, `models`, `agents`, `create` | Declared and enabled `session.run` |
| `sessions()`, `observe({ all: true })` | Declared and enabled `session.read` |
| `session(id)`, `observe({ sessionId: id })` | Either grant for this Mini App's own sessions; `session.read` for other visible user sessions |

`run` and `answer` only continue root sessions created by this Mini App. `reply` may answer approvals from delegated child sessions, using the child's IDs from the event. `session.read` does not permit continuing user sessions, approving their tools, or answering their questionnaires. Other Mini Apps' sessions and sub-Agent sessions are outside the readable scope.

A new session takes its agent, workspace, and model as follows. Continuing preserves its agent and workspace; only an explicit `model` switches its model.

| Caller | Agent | Workspace | Initial model |
| - | - | - | - |
| Page opened in a chat | The chat's agent, or an explicitly selected agent | The chat's project directory, otherwise the plugin's Agent directory | The chat's model and thinking level, otherwise runtime defaults |
| Page without a chat, or a new Node session | Primary agent, or an explicitly selected agent | The plugin's Agent directory | Runtime defaults |

## run(input, options?)

Run one turn. The page can supply a cancellation ID; Node uses an `AbortSignal` in the second argument.

```typescript theme={null}
interface MiniAppAgentRunInput {
  readonly text: string;
  readonly sessionId?: string;
  readonly model?: MiniAppAgentModelSelection;
  readonly agent?: string;
  readonly whenBusy?: 'queue' | 'steer';
  readonly requestId?: string;
}
type NodeAgentRunInput = Omit<MiniAppAgentRunInput, 'requestId'>;

interface PageAgentRunOptions {
  readonly onEvent?: (event: MiniAppAgentRunEvent) => void;
}
interface NodeAgentRunOptions extends PageAgentRunOptions {
  readonly signal?: AbortSignal;
}

type PageRun = (
  input: MiniAppAgentRunInput, options?: PageAgentRunOptions,
) => Promise<MiniAppAgentRunResult>;
type NodeRun = (
  input: NodeAgentRunInput, options?: NodeAgentRunOptions,
) => Promise<MiniAppAgentRunResult>;
```

| Parameter | Meaning |
| - | - |
| `text` | Required, nonempty and not whitespace-only; at most 100,000 UTF-16 code units |
| `sessionId` | Omit to create a session; pass to continue a session this Mini App owns |
| `model` | Selection from [`models()`](#models); the session retains the new selection |
| `agent` | Accepted only for a new session; use a `name` from [`agents()`](#agents), never together with `sessionId` |
| `whenBusy` | Only with `sessionId`; `queue` or `steer`, described below |
| Page `requestId` | Optional, 1–128 characters without control characters, unique among the page's active runs and subscriptions. The bridge generates one when omitted, so it is unavailable for explicit cancellation |
| `onEvent` | Receives [run events](#events). If supplied, handle approval events; subscribing without answering may leave a call waiting |
| Node `signal` | Cancels this call; the page API does not accept an `AbortSignal` |

A busy session rejects a call without `whenBusy`. `queue` runs the text as a later, separate turn and returns that turn's result. `steer` hands it to the running turn; once read, the call resolves with `status: 'steered'`, while the original call receives the reply. If the running turn answers before reading it, the text becomes the next turn. Both modes start a turn immediately when the session is idle.

After the user stops a session, its message queue may be paused. A follow-up can wait for the user to resume it while retaining a run slot. Cancelling a queued call removes its text. A steer already handed over cannot be withdrawn; cancelling it before it is read may yield an unknown outcome without stopping the original turn.

### Run result

```typescript theme={null}
interface MiniAppAgentRunResult {
  readonly sessionId: string;
  readonly turnId?: string;
  readonly model?: MiniAppAgentModelSelection;
  readonly status: 'finished' | 'error' | 'aborted' | 'steered';
  readonly text?: string;
  readonly error?: { readonly code: string; readonly message: string };
  readonly pendingQuestion?: {
    readonly requestId: string;
    readonly mode: MiniAppAgentQuestionMode;
    readonly request: MiniAppAgentQuestionnaire;
  };
}
```

`sessionId` is always present. `turnId` can be absent when an `answer` did not start a continuation turn; `model` can be absent when no turn ran or the result is `steered`. `text` is the final reply and is not guaranteed. `status: 'error'` is a turn execution failure, separate from a rejected Promise. `aborted` is returned only after the host confirms the abort. A questionnaire pause supplies `pendingQuestion.request` to render and `pendingQuestion.requestId` to pass to [`answer`](#answer).

## reply(input)

The page and Node share this signature. Success resolves with `undefined`:

```typescript theme={null}
type Reply = (input: {
  readonly sessionId: string;
  readonly requestId: string;
  readonly decision: 'allowOnce' | 'deny';
}) => Promise<void>;
```

`requestId` comes from `permission.requested`, not the page's cancellation ID. Only a one-time allow or deny is accepted; this method cannot write global rules. Unknown or settled requests reject. An approval can arrive before the first `run()` returns and can be answered immediately. For delegated asks, preserve the child's `sessionId` and `requestId` from the event.

## answer(input, options?)

Answer a questionnaire and observe its continuation. The two surfaces use different fields for the questionnaire ID:

```typescript theme={null}
interface PageAgentAnswerInput {
  readonly sessionId: string;
  readonly questionId: string;
  readonly answers: readonly MiniAppAgentQuestionAnswer[];
  readonly model?: MiniAppAgentModelSelection;
  readonly requestId?: string;
}
interface NodeAgentAnswerInput {
  readonly sessionId: string;
  readonly requestId: string;
  readonly answers: readonly MiniAppAgentQuestionAnswer[];
  readonly model?: MiniAppAgentModelSelection;
}
type PageAnswer = (
  input: PageAgentAnswerInput, options?: PageAgentRunOptions,
) => Promise<MiniAppAgentRunResult>;
type NodeAnswer = (
  input: NodeAgentAnswerInput, options?: NodeAgentRunOptions,
) => Promise<MiniAppAgentRunResult>;
```

Page `questionId` and Node `requestId` both take `question.requested.requestId` or `result.pendingQuestion.requestId`. The page may additionally pass its own `requestId` to cancel this answer/continuation call. Supply one [answer](#questionnaire) per questionnaire step.

Wait for the paused `run` to resolve before calling `answer`; it still occupies the session until then, so an earlier answer can reject as busy. The result is the same as [`run`](#run-result). If the answer was recorded but the continuation was not observed, the outcome is unknown; do not automatically answer again. An explicit `model` switches before questionnaire lookup, so even a questionnaire-not-found failure may have changed the model.

## cancel(requestId)

Page only. Node cancels [`run`](#run), [`answer`](#answer), or [`observe`](#observe) by aborting the supplied `signal`.

```typescript theme={null}
type PageCancel = (requestId: string) => void;
```

This returns `undefined` without waiting for confirmation. Unknown or completed IDs are ignored. A call waiting for a run slot or in the session queue rejects after cancellation. An executing call resolves `aborted` only after a confirmed abort; otherwise its outcome can be unknown. Cancelling an observer ends its subscription normally. Reloading or closing the page also cancels its active calls and subscriptions.

## open(sessionId)

Page only. Show one of this Mini App's own root sessions in the host chat's side panel:

```typescript theme={null}
type PageOpen = (sessionId: string) => Promise<void>;
```

Any page or Node call may have created the target, but this Mini App must own it. A page with no associated chat rejects. While the side-panel tab remains open, users can read the session and answer its approvals and questionnaires in the host. After it closes, handle approvals in the call's `onEvent` or open the tab again. Reopen after a host-window reload, runtime restart, or deletion of the original chat. Opening is idempotent, so retrying an open after an unknown result is safe.

## models() and model selection

```typescript theme={null}
interface MiniAppAgentModelSelection {
  readonly providerId: string;
  readonly modelId: string;
  readonly variant?: string;
  readonly thinking?: { readonly effort: string };
}
interface MiniAppAgentModelOption {
  readonly providerId: string;
  readonly modelId: string;
  readonly displayName: string;
  readonly variants?: readonly string[];
  readonly thinking?: { readonly efforts: readonly string[]; readonly default?: string };
}
interface MiniAppAgentModels {
  readonly models: readonly MiniAppAgentModelOption[];
  readonly default: MiniAppAgentModelSelection;
  readonly host?: MiniAppAgentModelSelection;
}
type PageModels = () => Promise<MiniAppAgentModels>;
type NodeModels = () => Promise<Omit<MiniAppAgentModels, 'host'>>;
```

`models` is the user's available catalog. `default` can be passed directly as `model` to `run` or `answer`. A page associated with a chat can also receive `host`, the chat's current selection or the default if it has none. Node has no `host` field.

For a catalog entry, copy `providerId` and `modelId`; optionally select a `variant` from `variants` or a `thinking.effort` from `thinking.efforts`. Preserve `variant: ''`: it explicitly turns thinking off. Catalog `thinking.default` describes the default effort; input takes one `thinking.effort`. The method rejects when no usable default exists. An invalid model selection also rejects, while an offered model with missing BYOK credentials can end a turn with `status: 'error'`.

## agents()

```typescript theme={null}
interface MiniAppAgentAgentOption {
  readonly name: string;
  readonly displayName: string;
  readonly primary: boolean;
}
interface MiniAppAgentAgents {
  readonly agents: readonly MiniAppAgentAgentOption[];
}
type Agents = () => Promise<MiniAppAgentAgents>;
```

`name` is the canonical `agent` value for a new session, `displayName` is for display, and `primary` identifies the default agent. Use catalog names rather than the `main` alias. A session cannot change agent midway; create another session instead. Selecting an agent does not change the page's inherited project directory or model.

## create(input?)

The page and Node share this signature:

```typescript theme={null}
type Create = (
  input?: { readonly agent?: string },
) => Promise<{ readonly sessionId: string }>;
```

Create a session without running a turn or taking a host run slot. Omitting the argument or passing an empty object uses the defaults under [Access and ownership](#access). A page can pass its created session ID to Node for continuation. A page call reaching the host's 60-second deadline can reject with `runtime_unavailable` even though the session may already exist; retrying may create another session.

## observe(target, options)

```typescript theme={null}
type MiniAppAgentObserveTarget =
  | { readonly sessionId: string }
  | { readonly all: true };
type PageObserve = (
  target: MiniAppAgentObserveTarget & { readonly requestId?: string },
  options: { readonly onEvent: (event: MiniAppAgentObservedEvent) => void },
) => Promise<void>;
type NodeObserve = (
  target: MiniAppAgentObserveTarget,
  options: {
    readonly onEvent: (event: MiniAppAgentObservedEvent) => void;
    readonly signal?: AbortSignal;
  },
) => Promise<void>;
```

Pass exactly one of `sessionId` or `all: true`; `onEvent` is required. An ID follows that session's progress across turns. `all: true` receives only starts, final replies, and finishes across user sessions, as described under [Observed events](#observed-events). Observing grants no right to answer approvals or questionnaires.

On the page, pass your own `requestId` to end the subscription with `cancel(requestId)`; it shares the unique-ID scope with active `run` and `answer` calls. Node uses `signal` in the second argument. There is no fixed subscription timeout. Cancellation, session deletion, page end, service stop/replacement, or read-access revocation ends the subscription. A start or runtime failure rejects; a normal end resolves with `undefined`. At most 4 observers are allowed per Mini App, apart from run slots and the start budget.

## sessions()

```typescript theme={null}
interface MiniAppAgentSessions {
  readonly sessions: readonly MiniAppAgentSessionSummary[];
}
type Sessions = () => Promise<MiniAppAgentSessions>;
```

Return up to 50 user sessions, ordered by most recently updated. Mini App, scheduled-task, and sub-Agent sessions are excluded. Requires `session.read`; there is no pagination or custom count argument. Fields are defined under [Session types](#session-types).

## session(sessionId?)

```typescript theme={null}
interface PageSession {
  (): Promise<MiniAppAgentSessionDetail | undefined>;
  (sessionId: string): Promise<MiniAppAgentSessionDetail>;
}
type NodeSession = (sessionId: string) => Promise<MiniAppAgentSessionDetail>;
```

An explicit ID returns that session's details or rejects when missing or outside the readable scope. On the page, omitting the ID reads the user chat that opened this page and needs `session.read`; outside a chat it resolves `undefined`. Node serves multiple pages and has no current chat, so it requires an ID, which the page can obtain with `session()` and pass along. This API does not return full chat history: only details and the latest available final reply.

## Data types

### Session summary and detail

```typescript theme={null}
interface MiniAppAgentSessionSummary {
  readonly sessionId: string;
  readonly title?: string;
  readonly agent: string;
  readonly status: 'idle' | 'running' | 'error' | 'aborted';
  readonly workspaceDir: string;
  readonly isDefaultWorkspace: boolean;
  readonly createdAtMs: number;
  readonly updatedAtMs: number;
}
interface MiniAppAgentSessionDetail extends MiniAppAgentSessionSummary {
  readonly model?: MiniAppAgentModelSelection;
  readonly error?: { readonly message: string };
  readonly lastAssistantText?: string;
}
```

`workspaceDir` is the recorded absolute project directory and may no longer exist. `isDefaultWorkspace` means the session has no project and uses the default directory. `createdAtMs` and `updatedAtMs` are Unix milliseconds. An unrecorded `model` is omitted; `error` is present only for an `error` status with a recorded message; `lastAssistantText` is absent without an available final reply. List entries do not include detail-only fields.

### Questionnaires and answers

These rules apply to questionnaires returned through `question.requested.request` or `pendingQuestion.request` on the page and Node. Optional host fields may be absent. Submit answers using the separate `MiniAppAgentQuestionAnswer` type.

#### Render the questionnaire

* Render every entry in `steps`. Render question and option strings as plain text.
* `selectionMode` selects single or multiple choice. Chosen IDs must come from the current step's `options`.
* `allowOther` and `required` are always `true`; `otherPlaceholder` is always `Others...`.
* `image.src` supports HTTPS URLs or app attachment paths beginning with `/mavis/api/`, rather than local `file://` URLs.
* `recommended` is a display marker. For v2 requests, it does not automatically select or submit an answer.
* `presentation` supplies composer-replacement, progress, and back-navigation hints.

| `mode` | Content to display |
| - | - |
| Absent or `questionnaire` | A standard questionnaire |
| `feature-enable` | The feature identified by `modePayload.featureKey` |
| `plan` | The review Markdown and path in `modePayload.planReview` |

#### Submit answers

Each answer contains only `stepId`, `selectedOptionIds`, and `selectedOther`, plus optional `otherText` and `skipped`. Answer the supplied steps and options without adding other fields.

* Free text uses `selectedOther` and `otherText`.
* `skipped: true` explicitly skips a step even though it is required.
* Returned `prefillAnswers` can prefill the form. Do not copy its `contextImages` or other returned metadata into `answer()`.

| Limit | Page and Node |
| - | - |
| `answers` per submission | At most 64 entries |
| `selectedOptionIds` per entry | At most 64 IDs |
| Each `stepId`, option ID, and `otherText` | At most 4,096 characters |

> [accordion] Complete type definitions
>
> ```typescript theme={null}
> type MiniAppAgentQuestionMode = 'questionnaire' | 'feature-enable' | 'plan';
> type MiniAppAgentQuestionStatus =
>   | 'pending' | 'answered' | 'expired' | 'superseded' | 'dismissed';
> type MiniAppAgentQuestionResponseMode = 'sync' | 'async';
>
> interface MiniAppAgentQuestionImage {
>   readonly src: string;
>   readonly alt?: string;
>   readonly caption?: string;
>   readonly width?: number;
>   readonly height?: number;
> }
> interface MiniAppAgentQuestionOption {
>   readonly id: string;
>   readonly label: string;
>   readonly description?: string;
>   readonly image?: MiniAppAgentQuestionImage;
>   readonly recommended?: true;
> }
> interface MiniAppAgentQuestionStep {
>   readonly id: string;
>   readonly header?: string;
>   readonly question: string;
>   readonly description?: string;
>   readonly image?: MiniAppAgentQuestionImage;
>   readonly selectionMode: 'single' | 'multiple';
>   readonly options: readonly MiniAppAgentQuestionOption[];
>   readonly allowOther: true;
>   readonly otherPlaceholder: 'Others...';
>   readonly required: true;
> }
> interface MiniAppAgentQuestionPresentation {
>   readonly replaceComposer: boolean;
>   readonly showProgress: boolean;
>   readonly allowBackNavigation: boolean;
> }
> interface MiniAppAgentQuestionnaire {
>   readonly schemaVersion: 2;
>   readonly id: string;
>   readonly purpose?: 'goal';
>   readonly goalId?: string;
>   readonly title?: string;
>   readonly tool?: { readonly message_id: string; readonly call_id: string };
>   readonly requester?: {
>     readonly sessionId: string;
>     readonly runId?: string;
>     readonly toolCallId?: string;
>     readonly agentName?: string;
>   };
>   readonly presentation: MiniAppAgentQuestionPresentation;
>   readonly steps: readonly MiniAppAgentQuestionStep[];
>   readonly expiresAt?: number;
>   readonly status?: MiniAppAgentQuestionStatus;
>   readonly createdAt?: number;
>   readonly responseMode?: MiniAppAgentQuestionResponseMode;
>   readonly countdownSeconds?: number;
>   readonly syncWaiting?: boolean;
>   readonly originTurnRunning?: boolean;
>   readonly answeredAt?: number;
>   readonly injectedAt?: number;
>   readonly mode?: MiniAppAgentQuestionMode;
>   readonly modePayload?: {
>     readonly featureKey?: string;
>     readonly planReview?: { readonly markdown: string; readonly path: string };
>   };
>   readonly reopenReason?: 'content_recall';
>   readonly prefillAnswers?: readonly MiniAppAgentQuestionPrefillAnswer[];
> }
> interface MiniAppAgentQuestionAnswer {
>   readonly stepId: string;
>   readonly selectedOptionIds: readonly string[];
>   readonly selectedOther: boolean;
>   readonly otherText?: string;
>   readonly skipped?: boolean;
> }
> interface MiniAppAgentQuestionPrefillAnswer extends MiniAppAgentQuestionAnswer {
>   readonly contextImages?: readonly {
>     readonly contextId: string;
>     readonly annotationId: string;
>     readonly attachment: {
>       readonly meta?: {
>         readonly attachmentType?: string;
>         readonly fileName?: string;
>         readonly mimeType?: string;
>         readonly sizeBytes?: number;
>       };
>       readonly local?: {
>         readonly assetId?: string;
>         readonly filePath?: string;
>         readonly dataUrl?: string;
>         readonly desktopPath?: string;
>       };
>       readonly cloud?: {
>         readonly uploadId?: string;
>         readonly driveNodeId?: string;
>         readonly url?: string;
>         readonly dataUrl?: string;
>         readonly objectKey?: string;
>       };
>     };
>     readonly sha256?: string;
>   }[];
> }
> ```

> [accordion] Returned state, timing, and legacy compatibility fields
>
> The host supplies these fields when available for display or compatibility with existing questionnaires. They are not `answer()` inputs and do not establish additional Mini App business APIs, such as a Goal API.
>
> | Field | Meaning |
> | - | - |
> | `tool`, `requester` | The originating model message, tool call, and session, plus optional run and agent identifiers |
> | `status` | `pending`, `answered`, `expired`, `superseded`, or `dismissed` |
> | `responseMode` | `sync` means the originating `ask_user` tool waited for an answer; `async` means it returned immediately. Absent means a legacy request with the turn-ending lifecycle |
> | `syncWaiting`, `originTurnRunning` | The current wait and originating turn state; absent means unknown |
> | `createdAt`, `answeredAt`, `injectedAt` | Unix milliseconds. Host views fill the latter two when available; they can be absent from the original request |
> | `countdownSeconds` | A hint for the initial async panel. Together with `createdAt`, it defines an absolute countdown; reopening a historical request does not reset it. The field does not schedule a host timer |
> | `purpose: 'goal'`, `goalId` | Origin markers retained as returned metadata from the shared host questionnaire type |
> | `expiresAt` | A legacy Goal questionnaire compatibility field: the Unix-millisecond time when the host automatically adopts each step's recommended option, otherwise its first option, or skips a step without options. V2 requests with `responseMode` do not set it; it is not a general questionnaire-withdrawal deadline |
> | `reopenReason: 'content_recall'` | The host recreated the questionnaire after an answer was recalled |
> | `prefillAnswers` | The previous answer for prefilling, possibly including session-owned `contextImages`; the runtime computes its `sha256` |
>
> These state fields are snapshots. The Agent API has no `questionnaire.state` event.

## Events

`run` and `answer` pass this discriminated union to `onEvent`. Ignore unrecognized `type` values.

```typescript theme={null}
type AgentTurnEvent = { readonly sessionId: string; readonly turnId: string };
type MiniAppAgentRunEvent =
  | { readonly type: 'run.queued'; readonly sessionId?: string; readonly position: number }
  | (AgentTurnEvent & { readonly type: 'message.pending' | 'message.steered' })
  | (AgentTurnEvent & {
      readonly type: 'turn.started'; readonly model: MiniAppAgentModelSelection;
    })
  | (AgentTurnEvent & {
      readonly type: 'text.delta' | 'message.committed'; readonly text: string;
    })
  | (AgentTurnEvent & { readonly type: 'tool.started'; readonly name: string })
  | (AgentTurnEvent & {
      readonly type: 'tool.finished'; readonly name: string; readonly ok: boolean;
    })
  | {
      readonly type: 'permission.requested'; readonly sessionId: string;
      readonly turnId?: string; readonly requestId: string; readonly toolName: string;
      readonly toolInput?: string; readonly reason: string;
    }
  | {
      readonly type: 'question.requested'; readonly sessionId: string;
      readonly turnId?: string; readonly requestId: string;
      readonly mode: MiniAppAgentQuestionMode; readonly request: MiniAppAgentQuestionnaire;
    }
  | (AgentTurnEvent & {
      readonly type: 'turn.finished'; readonly status: MiniAppAgentRunResult['status'];
      readonly text?: string;
      readonly error?: { readonly code: string; readonly message: string };
    });
```

| Event | Timing and meaning |
| - | - |
| `run.queued` | Waiting for a run slot or the `whenBusy` session queue; `position` is 1-based. No `turnId`; `sessionId` is present only for continuation. A call may receive it twice for the two kinds of wait |
| `message.pending`, `message.steered` | Steer text handed to the running turn and then read; `message.steered` is the follow-up call's last event |
| `turn.started` | The turn started with the dispatched `model` |
| `text.delta` | The next reply fragment |
| `message.committed` | One complete assistant message; a turn can contain several |
| `tool.started`, `tool.finished` | Tool `name` and completion `ok`; these are tool events inside the Agent turn |
| `permission.requested` | Answer with `reply`; delegated child asks carry the child's IDs and must not be filtered by the root turn |
| `question.requested` | A questionnaire pause, sent before `turn.finished`; answer after the original call resolves |
| `turn.finished` | The turn result, normally last; a `steered` follow-up has no independent finish event |

### Observed events

```typescript theme={null}
type MiniAppAgentObservedEvent =
  | (AgentTurnEvent & {
      readonly type: 'turn.started'; readonly model?: MiniAppAgentModelSelection;
      readonly title?: string;
    })
  | (AgentTurnEvent & { readonly type: 'text.delta'; readonly text: string })
  | (AgentTurnEvent & { readonly type: 'tool.started'; readonly name: string })
  | (AgentTurnEvent & {
      readonly type: 'tool.finished'; readonly name: string; readonly ok: boolean;
    })
  | (AgentTurnEvent & {
      readonly type: 'message.committed'; readonly text: string; readonly title?: string;
    })
  | (AgentTurnEvent & {
      readonly type: 'turn.finished'; readonly status: MiniAppAgentRunResult['status'];
      readonly text?: string; readonly title?: string;
      readonly error?: { readonly code: string; readonly message: string };
    });
```

A single-session observer can receive all of the progress events above. `all: true` receives only `turn.started`, the final reply's `message.committed`, and `turn.finished`, with optional session `title`. An observed `turn.started.model` is optional. If the host cannot read the final reply, it sends no `message.committed` and omits `text` from `turn.finished`. Observers receive no queue, steer, approval, or questionnaire events. Backpressure can drop text fragments and tool progress; do not treat every progress fragment as a reliable persistent record.

## Concurrency and cancellation limits

* `agentConcurrency` is 1–32, default 4; each session has one turn at a time. Extra `run` / `answer` calls wait in order, in a queue of at most four times the concurrency.
* A `whenBusy` follow-up shares a slot already held for that session by this plugin. Such extra in-flight follow-ups are also capped at four times the concurrency. The start budget per 60 seconds is `max(12, 5 × agentConcurrency)` and survives Mini App restarts; waiting for a slot or a refused call does not count.
* Approvals wait only while the call supplied `onEvent` or the session remains open in a chat side-panel tab. Otherwise they are denied; a questionnaire can be returned as `pendingQuestion`. Approval waiting has no timeout, and ignoring an approval event retains the run slot.
* Per Node process, up to 16 ordinary requests can be pending across Agent `run`, `answer`, `models`, `agents`, `create`, `sessions`, and `session`, together with all Tool and Connector methods. Agent `reply` has a separate limit of 16 pending calls; `observe` has its own limit of 4. An Agent call reaching the ordinary or reply limit rejects with retryable `AGENT_UNAVAILABLE` before dispatch. These process limits also apply while calls wait for run slots, queued turns, or approval.
* Revoking access aborts affected running turns, refuses waiting calls, or ends read subscriptions. Objects may remain present; new calls work after access is enabled again. After a Node `signal` aborts, no further event callbacks are delivered, including events already in transit.

## Errors

Page rejections are `Error` objects; rely on the code in `message`. Custom properties such as `busy.reason` are not guaranteed to survive the host bridge. Node rejects with `MiniAppAgentError`, carrying `code`, `retryable`, and optional `reason`. See [Agent error codes](https://agent.minimax.io/docs/miniapp/reference/errors.md#agent) for exact mappings and handling.

A rejected Promise alone does not justify retrying: `result_unknown` / `AGENT_OUTCOME_UNKNOWN` means an operation may have applied. `observe`, `sessions`, and `session` do not change a session, and lost read/subscription settlement reports unavailability. The page's `models`, `agents`, and `create` share a catalog call path whose transport failure can still report `result_unknown`. Handle a normal `run` result with `status: 'error'` separately from these call rejections.
