UNPKG

@mastra/core

Version:
1,068 lines (1,067 loc) 61 kB
import type { Agent } from '../agent/index.js'; import type { AgentSignalAttributes, AgentSignalContents, AgentSignalInput } from '../agent/signals.js'; import type { AgentThreadSubscription, MastraBrowser, SendAgentNotificationSignalOptions, SendAgentNotificationSignalResult, ToolsetsInput } from '../agent/types.js'; import type { MastraModelGatewayInterface } from '../llm/model/gateways/index.js'; import type { MastraModelConfig } from '../llm/model/shared.types.js'; import type { SendNotificationSignalInput } from '../notifications/index.js'; import type { TracingContext, TracingOptions } from '../observability/index.js'; import type { RequestContext } from '../request-context/index.js'; import type { PublicSchema } from '../schema/index.js'; import { Workspace } from '../workspace/index.js'; import { SessionRunEngine } from './session-run-engine.js'; import type { TaskItemSnapshot } from './tools.js'; import type { AgentControllerDisplayState, AgentControllerEvent, AgentControllerEventListener, AgentControllerMessage, AgentControllerMode, AgentControllerOMConfig, AgentControllerRequestState, AgentControllerThread, ModelUseCountTracker, PermissionPolicy, PermissionRules, TokenUsage, ToolCategory } from './types.js'; /** * Minimal persistence surface the Session uses to read and write per-thread * settings (mode id, per-mode model id, …). The AgentController backs this with thread * metadata; when no storage is configured it is absent and the Session keeps * its state purely in memory. */ export interface ThreadSettingsStore { /** Read a setting for the active thread, or undefined when unset/unavailable. */ get(key: string): Promise<unknown>; /** Persist a setting for the active thread (no-op when storage is unavailable). */ set(key: string, value: unknown): Promise<void>; } /** Options for {@link Session.sendNotificationSignal}. */ export type SessionSendNotificationSignalOptions = { ifActive?: SendAgentNotificationSignalOptions['ifActive']; ifIdle?: SendAgentNotificationSignalOptions['ifIdle']; tracingContext?: TracingContext; tracingOptions?: TracingOptions; requestContext?: RequestContext; }; /** * Owns the session's identity: the memory `resourceId` and the active * `threadId` this session reads and writes under. Together they form the memory * binding (`{ thread, resource }`) every run uses. In a multi-user host one * AgentController serves many sessions, so this identity — "whose session is this, and * which thread is it on" — belongs to the Session, not the AgentController. * * `defaultResourceId` is the resourceId the session started with; switching to a * different resource (e.g. impersonation, or browsing another user's threads) * updates the current resourceId while the default is retained so the session * can return to its own identity. * * `id` is the stable identifier for this session (mirrors `SessionRecord.id` in * storage) and `ownerId` is the owner of this session (mirrors * `SessionRecord.ownerId`). Both are stable for the life of the session and do * not change when the resourceId is switched. * * The active thread the session is bound to lives on {@link SessionThread}, not * here — identity is the stable "who", the thread is the navigational "where". */ export declare class SessionIdentity { #private; constructor({ resourceId, id, ownerId }: { resourceId: string; id: string; ownerId: string; }); /** The resourceId the session currently reads/writes under. */ getResourceId(): string; /** The resourceId the session started with. */ getDefaultResourceId(): string; /** The stable session identifier for this session. */ getId(): string; /** The stable owner identifier for this session. */ getOwnerId(): string; /** Point the session at a different resourceId (the default is unchanged). */ setResourceId({ resourceId }: { resourceId: string; }): void; } /** * The shared-host storage surface the Session's thread domain leverages to read * and write threads. The AgentController backs this with its memory storage (mapping raw * storage rows to {@link AgentControllerThread}/{@link AgentControllerMessage}); when no storage * is configured the handle is absent and the data methods degrade gracefully * (empty lists, undefined settings, no-op writes). * * This is a gateway to shared infrastructure — not a callback into AgentController * orchestration. The Session owns the thread-domain logic; the host owns the DB. */ export interface ThreadDataStore { /** List threads for a resource (or all resources), already mapped + filtered of forked subagents unless asked. */ listThreads(input: { resourceId?: string; includeForkedSubagents?: boolean; metadata?: Record<string, unknown>; }): Promise<AgentControllerThread[]>; /** Fetch a single thread by id, or null when it doesn't exist. */ getById(input: { threadId: string; }): Promise<AgentControllerThread | null>; /** List messages for a thread, newest-`limit` (returned oldest-first) or all. */ listMessages(input: { threadId: string; limit?: number; }): Promise<AgentControllerMessage[]>; /** The first user message for each given thread id. */ firstUserMessages(input: { threadIds: string[]; }): Promise<Map<string, AgentControllerMessage>>; /** Read a value from a thread's metadata. */ getMetadata(input: { threadId: string; key: string; }): Promise<unknown>; /** Write a value into a thread's metadata. */ setMetadata(input: { threadId: string; key: string; value: unknown; }): Promise<void>; /** Delete a value from a thread's metadata. */ deleteMetadata(input: { threadId: string; key: string; }): Promise<void>; /** Whether the host has thread storage configured. When false, lifecycle persistence is a no-op. */ hasStorage(): boolean; /** Persist a new or updated thread row. No-op when storage is unavailable. */ saveThread(input: { thread: AgentControllerThread; }): Promise<void>; /** Delete a thread row by id. No-op when storage is unavailable. */ deleteThread(input: { threadId: string; }): Promise<void>; /** Clone a thread (and its messages) via the host's memory, returning the new thread. */ cloneThread(input: { sourceThreadId: string; resourceId: string; title?: string; metadata?: Record<string, unknown>; }): Promise<AgentControllerThread>; /** Acquire the host thread lock for a thread id. No-op when no lock is configured. */ acquireLock(threadId: string): Promise<void>; /** Release the host thread lock for a thread id. No-op when no lock is configured. */ releaseLock(threadId: string): Promise<void>; /** The host's configured mode ids, used to validate a thread's persisted mode on restore. */ getModeIds(): string[]; } /** * The AgentController-owned machinery a Session leverages to drive an agent run. In the * multi-user host one AgentController serves many sessions; the run loop, run state, and * thread stream are per-session (they cannot be shared) and so belong on the * Session. But *how* a run is produced — which agent answers, the config-backed * run/stream options, the toolset, the request context, the tool-approval * policy, usage persistence, id generation — is shared infrastructure the * AgentController owns. The AgentController injects this machinery into each Session it * constructs (via {@link Session.setMachinery}); the Session calls into it but * never reaches back into the AgentController or another session. * * This is the formalized DI boundary: the Session receives exactly the * capabilities it is allowed to use, nothing more. */ export interface SessionMachinery { /** Resolve the agent that should answer for the session's current mode/model. */ getAgent(): Agent; /** Open a fresh subscription to a thread's agent event stream. */ subscribeToThread(input: { resourceId: string; threadId: string; }): Promise<AgentThreadSubscription<any>>; /** Build the per-call stream options (instructions, memory, toolsets, abort signal, tracing). */ buildStreamOptions(input: { requestContext?: RequestContext; tracingContext?: TracingContext; tracingOptions?: TracingOptions; }): Promise<Record<string, unknown>>; /** The run budget every initial stream and resume must carry (maxSteps, provider fallbacks, …). */ buildSharedRunOptions(): Record<string, unknown>; /** Resolve the toolset (built-in controller tools + user/subagent tools) for a run. */ buildToolsets(requestContext: RequestContext): Promise<ToolsetsInput>; /** Resolve the effective request context for a run, layering controller defaults. */ buildRequestContext(requestContext?: RequestContext): Promise<RequestContext>; /** Persist the session's running token usage to thread metadata. */ persistTokenUsage(): Promise<void>; /** Generate a new id (thread ids, message ids) using the host's id strategy. */ generateId(): string; /** * Resolve the mode the session transitions to when a plan is approved: the * current mode's `transitionsTo`, else the host's default mode. Returns * `undefined` when the host has no default mode. The mode catalog is AgentController * config, so this is genuinely host-owned. */ resolveTransitionModeId(): string | undefined; /** * Persist a system-reminder message to a thread, returning the saved message * (or `null` when no storage is configured). Pure host-owned persistence * (storage handle + id strategy). */ saveSystemReminder(input: { threadId: string; resourceId: string; message: string; reminderType: string; role: 'user' | 'assistant' | 'system'; metadata?: Record<string, unknown>; }): Promise<AgentControllerMessage | null>; } /** * Owns the session's thread domain: the navigational binding (which thread the * session is currently on) plus the data reads/queries scoped to it. `null` * until the session is bound (a thread is created, switched to, or reacquired on * startup); switching/deleting updates it. * * In the multi-user model each session has its own current thread and reads its * own threads, while the AgentController host shares storage, the thread lock, and the * event bus. So the binding + data queries are per-session and live here; the * session leverages the host's storage via an injected {@link ThreadDataStore}. * Lifecycle *transitions* (create/switch/clone/delete) remain host machinery * because they drive the shared event bus and rebind the shared agent stream. */ export declare class SessionThread { #private; constructor(getResourceId: () => string); /** * Attach the shared-host storage gateway the thread domain reads/writes * through and the owning session whose subsystems lifecycle transitions * orchestrate. The AgentController calls this once during wiring; without a store the * data methods degrade gracefully. */ connect(store: ThreadDataStore | undefined, session: Session): void; /** The active thread id, or null when the session is not bound to a thread. */ getId(): string | null; /** Whether the session is currently bound to a thread. */ isSet(): boolean; /** The active thread id, throwing when the session is not bound to a thread. */ requireId(): string; /** Bind the session to a thread. */ set({ threadId }: { threadId: string; }): void; /** Clear the session's thread binding. */ clear(): void; /** Clear the session's thread binding and release its lock when one is held. */ clearAndReleaseLock(): Promise<void>; /** List this session's threads (its own resource by default, or all resources). */ list(options?: { allResources?: boolean; includeForkedSubagents?: boolean; metadata?: Record<string, unknown>; }): Promise<AgentControllerThread[]>; /** Fetch a single thread by id, or null when it doesn't exist / no storage. */ getById({ threadId }: { threadId: string; }): Promise<AgentControllerThread | null>; /** Clone a detected cross-resource project thread into this session's resource. */ cloneToCurrentResource({ threadId, expectedResourceId, expectedProjectPath, }: { threadId: string; expectedResourceId: string; expectedProjectPath: string; }): Promise<AgentControllerThread>; /** List messages for a thread (newest-`limit`, returned oldest-first), or all. */ listMessages({ threadId, limit }: { threadId: string; limit?: number; }): Promise<AgentControllerMessage[]>; /** List messages for the session's active thread (empty when not bound). */ listActiveMessages({ limit }?: { limit?: number; }): Promise<AgentControllerMessage[]>; /** The first user message for a single thread, or null. */ firstUserMessage({ threadId }: { threadId: string; }): Promise<AgentControllerMessage | null>; /** The first user message for each given thread id. */ firstUserMessages({ threadIds }: { threadIds: string[]; }): Promise<Map<string, AgentControllerMessage>>; /** Read a setting (metadata value) for the active thread. */ getSetting({ key }: { key: string; }): Promise<unknown>; /** Persist a setting (metadata value) for the active thread. */ setSetting({ key, value }: { key: string; value: unknown; }): Promise<void>; /** Delete a setting (metadata value) for the active thread. */ deleteSetting({ key }: { key: string; }): Promise<void>; /** Tear down the current agent subscription and reset the run tracker. */ cleanupSubscription(): void; /** * Ensure the session is subscribed to the given agent/thread stream, opening a * fresh subscription (and driving its run loop) when the binding changed. */ ensureSubscription(threadId: string): Promise<void>; /** Ensure a subscription for the session's active thread (no-op when unbound). */ ensureCurrentSubscription(): Promise<void>; /** Detach from the current thread: abort the run and tear down the subscription. */ detachFromCurrent(): void; /** Create a new thread, bind the session to it, and rebind the agent stream. */ create({ title }?: { title?: string; }): Promise<AgentControllerThread>; /** Rename the session's active thread. No-op when unbound or storageless. */ rename({ title }: { title: string; }): Promise<void>; /** Clone a thread (and its messages), bind the session to the clone, and rebind the stream. */ clone({ sourceThreadId, title, resourceId, }?: { sourceThreadId?: string; title?: string; resourceId?: string; }): Promise<AgentControllerThread>; /** Switch the session to an existing thread, hydrating its persisted settings and rebinding the stream. */ switch({ threadId, emitEvent }: { threadId: string; emitEvent?: boolean; }): Promise<void>; /** Delete a thread; when it's the active thread, clear the binding and tear down the run. */ delete({ threadId }: { threadId: string; }): Promise<void>; /** * Hydrate the session's per-thread settings from the active thread's metadata: * token usage, the persisted mode (restored first), the per-mode model, and * observer/reflector model ids + thresholds. Best-effort: on any failure the * token tally is reset and the rest is left at defaults. */ loadMetadata(): Promise<void>; } /** * Owns the session's live subscription to the active thread's agent event * stream. A subscription is created per `(agent, resource, thread)` and reused * while that triple is unchanged (tracked by {@link key}); switching threads or * agents tears the old one down and opens a new one. * * The Session owns the subscription *handle* and its dedup key plus the * mechanical lifecycle (reuse check, teardown, identity check, run-id read). * The AgentController still owns *how* a subscription is produced (calling the agent) * and *how* its stream is consumed, passing the resolved handle in via * {@link attach}. */ export declare class SessionStream { #private; waitForTeardown(signal: AbortSignal): Promise<void>; /** Build the dedup key identifying a subscription to `threadId` for `agent`. */ static keyFor({ agent, resourceId, threadId }: { agent: Agent; resourceId: string; threadId: string; }): string; /** Whether the open subscription already targets `key` (so it can be reused). */ matches({ key }: { key: string; }): boolean; /** Adopt `subscription` as the live one, recording its dedup `key`. */ attach({ subscription, key }: { subscription: AgentThreadSubscription<any>; key: string; }): void; /** Whether a subscription is currently open. */ isOpen(): boolean; /** Whether `subscription` is the one currently adopted (identity check). */ isCurrent({ subscription }: { subscription: AgentThreadSubscription<any>; }): boolean; /** The run id the live subscription reports as active, or null when none/idle. */ activeRunId(): string | null; /** Whether the live subscription currently has a run in flight. */ isActive(): boolean; /** Abort the live subscription's in-flight run, if any. Swallows errors. */ abort(): void; /** Detach the live subscription without aborting (e.g. on stream error). */ detach(): void; /** Fully tear down the live subscription: abort, unsubscribe, and clear. */ cleanup(): void; } /** A tool call parked awaiting a resume, keyed in {@link SessionSuspensions}. */ export interface PendingSuspension { /** The run id to resume when this tool call is answered. */ runId: string; /** The suspended tool's name (e.g. `ask_user`, `submit_plan`). */ toolName: string; } /** * Owns the session's parked tool suspensions: tool calls paused via the native * tool-suspension primitive (e.g. `ask_user` / `request_access` / `submit_plan`) * that are awaiting a resume, keyed by `toolCallId`. Each entry records the run * id to resume and the tool name. A Map (rather than single fields) lets several * tools — e.g. parallel `ask_user` calls in one step — stay suspended and be * resumed independently. * * This is the resume *data* the AgentController reads to drive a resume. The richer * per-suspension UI snapshot lives on the AgentController display state; the Session * owns only what's needed to resume. */ export declare class SessionSuspensions { #private; /** Park `toolCallId` as awaiting a resume on `runId` for `toolName`. */ register({ toolCallId, runId, toolName }: { toolCallId: string; runId: string; toolName: string; }): void; /** The parked suspension for `toolCallId`, or undefined when none. */ get({ toolCallId }: { toolCallId: string; }): PendingSuspension | undefined; /** Whether `toolCallId` is currently parked. */ has({ toolCallId }: { toolCallId: string; }): boolean; /** Drop `toolCallId` from the parked set (e.g. once resumed). */ delete({ toolCallId }: { toolCallId: string; }): void; /** Drop all parked suspensions (e.g. on abort or thread switch). */ clear(): void; /** Whether any tool calls are parked awaiting a resume. */ hasPending(): boolean; /** * Resolve which parked suspension to act on. With an explicit `toolCallId` it * must match a parked suspension; without one it returns the single parked * suspension (or undefined when there are zero or several). */ resolveToolCallId(toolCallId?: string): string | undefined; } /** A message queued to send once the active run finishes, held in {@link SessionFollowUps}. */ export interface FollowUp { /** The message text to send. */ content: string; /** Optional request context to apply when the queued message is sent. */ requestContext?: RequestContext; } /** * Owns the session's follow-up queue: messages a user submits while a run is in * progress, held FIFO until the active run finishes and the queue is drained. * * This owns the queue *data* (enqueue/dequeue/requeue/clear/count). The AgentController * still drives draining — sending each message and emitting `follow_up_queued` * as the count changes — and keeps the display-state mirror (`queuedFollowUps`). */ export declare class SessionFollowUps { #private; /** Number of messages currently queued. */ count(): number; /** Whether the queue is empty. */ isEmpty(): boolean; /** Append a follow-up to the back of the queue. */ enqueue(followUp: FollowUp): void; /** Remove and return the next follow-up, or undefined when empty. */ dequeue(): FollowUp | undefined; /** Put a follow-up back at the front (e.g. when draining it failed). */ requeue(followUp: FollowUp): void; /** Drop all queued follow-ups (e.g. on steer or thread switch). */ clear(): void; } /** The decision a user returns to resolve a parked tool-approval gate. */ export interface ApprovalDecision { /** Whether to run the gated tool or reject it. */ decision: 'approve' | 'decline'; /** Optional request context to apply when the gated tool resumes. */ requestContext?: RequestContext; /** Optional context explaining why a tool approval was declined. */ declineContext?: { reason?: string; message?: string; }; } /** * A user's response to a parked approval. `always_allow_category` approves the * tool and additionally grants its category for the rest of the session. */ export interface ApprovalResponse { decision: 'approve' | 'decline' | 'always_allow_category'; requestContext?: RequestContext; declineContext?: { reason?: string; message?: string; }; } /** * Owns the session's interactive tool-approval gate: when a tool requires user * approval, the run parks on a promise here until the UI responds approve or * decline. Holds the pending resolver and the name of the tool being gated. * * At most one approval is in flight at a time. The Session owns the gate * mechanics (arm / resolve / clear); the AgentController still maps a decision to its * effects (running vs declining the tool, and any "always allow" grant), since * those touch config-derived tool categories. */ export declare class SessionApproval { #private; /** * Park a new approval for `toolName`/`toolCallId` and return a promise that * resolves once {@link respond} is called with the user's decision. The caller * awaits this while the run is suspended on the gate. */ arm({ toolName, toolCallId }: { toolName: string; toolCallId?: string; }): Promise<ApprovalDecision>; /** Id of the tool call currently awaiting approval, or null when none. */ getToolCallId(): string | null; /** Whether an approval is currently parked awaiting a decision. */ isArmed(): boolean; /** * Apply a user's {@link ApprovalResponse} to the parked gate. A no-op when * nothing is armed. When `toolCallId` is supplied it must match the gated * call; a mismatch is ignored so a stale/delayed response cannot resolve a * different pending gate. `always_allow_category` runs `onAlwaysAllow` with the * gated tool name (so the caller can grant the tool's category — a lookup that * needs AgentController config) and then approves; `approve`/`decline` resolve as-is. */ respond({ decision, toolCallId, requestContext, declineContext, onAlwaysAllow, }: ApprovalResponse & { toolCallId?: string; onAlwaysAllow?: (toolName: string) => void; }): void; /** * Release a parked gate without a user decision — used when the run is * aborted. Resolves the awaiting producer as a `decline` so the gated tool is * rejected (not run) and the run can finalize. A no-op when nothing is armed. */ cancel(): void; /** Clear the gated tool name/call id once a parked approval has been consumed. */ clearToolName(): void; } /** * Owns the session's transient run identity and abort control: the id of the * run currently streaming on the active thread, its trace id, a monotonic * operation counter bumped each time a new operation starts, and the * AbortController/abort-requested flag governing cancellation. All of this is * per-run scratch state — it is never persisted and resets between runs. * * The live agent subscription itself lives on {@link SessionStream} * (`session.stream`); this holds the last run id observed on a chunk so callers * have a stable value once the subscription has settled. */ export declare class SessionRun { #private; waitForTeardown(signal: AbortSignal): Promise<void>; /** The current run id (null when idle). */ getRunId(): string | null; /** Set the current run id. */ setRunId({ runId }: { runId: string | null; }): void; /** The current trace id (null when unset). */ getTraceId(): string | null; /** Set the current trace id. */ setTraceId({ traceId }: { traceId: string | null; }): void; /** * Clear all run state (run id, trace id, abort controller + requested flag) * when a run ends or is reset. Does not touch the operation counter. */ reset(): void; /** Bump and return the operation counter at the start of a new operation. */ nextOperation(): number; /** * Lazily create (if needed) and return the AbortController for the current * run. Callers pass its `.signal` into the underlying stream. */ ensureAbortController(): AbortController; /** Signal for the current run's AbortController, or undefined when none is armed. */ getAbortSignal(): AbortSignal | undefined; /** * Whether a run is currently in progress. A run is armed with an * AbortController for its duration, so the presence of one is what "running" * means; this is the semantic accessor callers should use. */ isRunning(): boolean; /** * Whether an AbortController is currently armed. Equivalent to * {@link isRunning} today; kept for callers that assert on the controller's * lifecycle specifically (e.g. that it was cleared after an abort). */ hasAbortController(): boolean; /** Clear the abort-requested flag at the start of a fresh run. */ clearAbortRequested(): void; /** Whether an abort has been requested for the current run. */ isAbortRequested(): boolean; /** * Request an abort: mark the run as aborting and fire the AbortController (if * armed), then drop the controller. Leaves the requested flag set so the * run-end path can resolve its reason as 'aborted'; {@link reset} clears it. */ requestAbort(): void; } /** * Owns the session's currently-selected model. Source of truth for "which model * is active", plus the per-mode model memory persisted to the thread-settings * store (so each mode remembers the model it was last used with). */ export declare class SessionModel { #private; constructor(store: () => ThreadSettingsStore | undefined, bus: SessionBus); /** * Attach the AgentController-owned dependencies {@link switch} needs: the active-mode * accessor and the optional model-use tracker. The AgentController injects these once. */ setResolver(options: { getCurrentModeId: () => string; trackModelUse?: ModelUseCountTracker; }): void; /** The currently-selected model id ('' when none selected yet). */ get(): string; /** Whether a model is currently selected. */ hasSelection(): boolean; /** * A short display name for the selected model: the last segment of the model * id (e.g. `__GATEWAY_ANTHROPIC_MODEL_SONNET__` -> `claude-sonnet-4-6`). Returns * `'unknown'` when no model is selected. */ displayName(): string; /** Set the in-memory selected model id (no persistence). */ set({ modelId }: { modelId: string; }): void; /** Persist `modelId` as the last-used model for `modeId`. */ saveForMode({ modeId, modelId }: { modeId: string; modelId: string; }): Promise<void>; /** * Resolve the model for `modeId`: the persisted per-mode model if present, * else `defaultModelId`, else null. */ resolveForMode({ modeId, defaultModelId, }: { modeId: string; defaultModelId?: string; }): Promise<string | null>; /** * Switch to a different model at runtime. * * When `scope` is `'thread'` (the default), the model is persisted as the * per-mode model for `modeId` so it's restored when switching back. The * in-memory selection only updates when the target mode is the active mode. * Reports the selection to the model-use tracker and emits `model_changed`. */ switch({ modelId, scope, modeId, }: { modelId: string; scope?: 'global' | 'thread'; modeId?: string; }): Promise<void>; } /** * Owns the session's currently-selected mode and the logic for switching modes. * Holds the active mode id and runs the version-guarded switch sequence — * persisting the selection and coordinating the per-mode model with * {@link SessionModel}. The AgentController still owns the mode *definitions* * (`config.modes`); this owns "which mode is active" and how a switch unfolds. */ export declare class SessionMode { #private; constructor(store: () => ThreadSettingsStore | undefined, model: SessionModel, bus: SessionBus); /** * Attach the resolver that maps a mode id to its definition. The AgentController owns * the mode catalog (`config.modes`) and injects this once. */ setResolver(resolve: (modeId: string) => AgentControllerMode | null): void; /** The currently-selected mode id. */ get(): string; /** * Resolve the currently-selected mode id to its full definition against the * host's mode catalog. Throws if the selected mode id isn't in the catalog. */ resolve(): AgentControllerMode; /** Set the currently-selected mode id (on default resolution or hydration). */ set({ modeId }: { modeId: string; }): void; /** * Switch to a different mode. * * Emits `mode_changed`, then runs the version-guarded sequence: remember the * outgoing mode's model, persist the new mode, then resolve and apply the * incoming mode's model — emitting `model_changed` once applied. A newer * switch starting mid-flight supersedes this one, which then bails before * emitting `model_changed`. */ switch({ modeId }: { modeId: string; }): Promise<void>; } /** Per-role wiring + state/config keys a {@link SessionOMRole} reads and writes. */ interface SessionOMRoleConfig { /** The event `role` and `om_model_changed` discriminator for this role. */ role: 'observer' | 'reflector'; /** Session-state / thread-settings key holding this role's model id. */ modelIdKey: 'observerModelId' | 'reflectorModelId'; /** Session-state key holding this role's threshold. */ thresholdKey: 'observationThreshold' | 'reflectionThreshold'; /** Resolve this role's default model id from `omConfig`. */ defaultModelId: (omConfig: AgentControllerOMConfig | undefined) => string | undefined; /** Resolve this role's default threshold from `omConfig`. */ defaultThreshold: (omConfig: AgentControllerOMConfig | undefined) => number | undefined; } /** * One observational-memory role (observer or reflector): its model id, resolved * model instance, threshold, and model switch. Reads return the session-state * value when set, falling back to the AgentController's `omConfig` defaults. The shared * wiring is injected by {@link SessionOM.setResolver}. */ declare class SessionOMRole { #private; constructor(config: SessionOMRoleConfig, bus: SessionBus); /** @internal Injected by {@link SessionOM.setResolver}. */ setWiring(wiring: { getState: () => Record<string, unknown>; setState: (updates: Record<string, unknown>) => void; setSetting: (args: { key: string; value: unknown; }) => Promise<void>; omConfig?: AgentControllerOMConfig; gateways?: MastraModelGatewayInterface[]; }): void; /** This role's model id from session state, falling back to `omConfig`. */ modelId(): string | undefined; /** This role's threshold from session state, falling back to `omConfig`. */ threshold(): number | undefined; /** * Resolve this role's model id to a model instance via the configured * gateways, or undefined when unset. The bare model id string is routed * through {@link ModelRouterLanguageModel}, which selects the matching * gateway (or the built-in defaults) and resolves provider auth. */ resolvedModel(): MastraModelConfig | undefined; /** Switch this role's model: update session state, persist, and emit. */ switchModel({ modelId }: { modelId: string; }): Promise<void>; } /** * Owns the session's observational-memory model selection, grouped by role: * {@link SessionOM.observer} and {@link SessionOM.reflector}. The AgentController owns * `omConfig` and the model resolver, so it injects them — plus the session-state * read/write and thread-settings persistence — once via {@link setResolver}, * which fans the wiring out to both roles. */ declare class SessionOM { readonly observer: SessionOMRole; readonly reflector: SessionOMRole; constructor(bus: SessionBus); /** * Attach the session-state read/write, thread-settings persistence, and the * AgentController-owned `omConfig` defaults plus model resolver. The AgentController injects * these once; the wiring is shared by both roles. */ setResolver(options: { getState: () => Record<string, unknown>; setState: (updates: Record<string, unknown>) => void; setSetting: (args: { key: string; value: unknown; }) => Promise<void>; omConfig?: AgentControllerOMConfig; gateways?: MastraModelGatewayInterface[]; }): void; } /** * Owns the session's tool-permission rules: the per-category and per-tool * approval policies persisted in session state under `permissionRules`. The * AgentController injects the session-state read/write once via {@link setResolver}. * * These are the persisted rules consulted during tool-approval resolution; they * are distinct from the in-memory "allow for this session" grants on the * Session. */ declare class SessionPermissions { #private; /** Attach the session-state read/write. The AgentController injects these once. */ setResolver(options: { getState: () => Record<string, unknown>; setState: (updates: Record<string, unknown>) => Promise<void>; }): void; /** The current permission rules, or empty rules when none are set. */ getRules(): PermissionRules; /** Set the approval policy for a tool category. Resolves once persisted. */ setForCategory({ category, policy }: { category: ToolCategory; policy: PermissionPolicy; }): Promise<void>; /** Set the approval policy for an individual tool. Resolves once persisted. */ setForTool({ toolName, policy }: { toolName: string; policy: PermissionPolicy; }): Promise<void>; } /** * The subagent model selection. Reads prefer the per-`agentType` value and fall * back to the global subagent model; writes persist to thread settings and emit * a `subagent_model_changed` event. Wiring is injected by * {@link SessionSubagents.setResolver}. */ declare class SessionSubagentModel { #private; constructor(bus: SessionBus); /** @internal Injected by {@link SessionSubagents.setResolver}. */ setWiring(wiring: { getState: () => Record<string, unknown>; setState: (updates: Record<string, unknown>) => void; setSetting: (args: { key: string; value: unknown; }) => Promise<void>; }): void; /** * The subagent model id, preferring the `agentType`-specific value when one is * given, then the global subagent model, or `null` when neither is set. */ get({ agentType }?: { agentType?: string; }): string | null; /** * Set the subagent model id (per-`agentType` when given, otherwise global). * Persists to thread settings and emits `subagent_model_changed`. */ set({ modelId, agentType }: { modelId: string; agentType?: string; }): Promise<void>; } /** * The session's subagent configuration. Currently exposes the subagent model * selection under {@link SessionSubagents.model}; grouped under `subagents` to * leave room for additional subagent settings. The AgentController injects the * session-state read/write, thread-settings persistence, and event emitter once * via {@link setResolver}. */ declare class SessionSubagents { readonly model: SessionSubagentModel; constructor(bus: SessionBus); /** * Attach the session-state read/write and thread-settings persistence. The * AgentController injects these once. */ setResolver(options: { getState: () => Record<string, unknown>; setState: (updates: Record<string, unknown>) => void; setSetting: (args: { key: string; value: unknown; }) => Promise<void>; }): void; } interface SessionStateOptions<TState> { initialState?: Partial<TState>; stateSchema?: PublicSchema<TState, any>; } /** * A AgentController session owns the per-conversation runtime state that today lives * flattened on the {@link AgentController} instance. This class is the seam we extract * that state into, one concern at a time, so the AgentController can eventually own a * `Session` rather than the state itself. * * Currently owns: * - the live AgentController state (`session.state`): schema-validated snapshots and * serialized updates that emit `state_changed`. * - session-scoped permission grants — the "allow for this session" approvals a * user makes when a tool or tool category is gated behind the permission check. * - the live token-usage counter for the active thread. The Session holds the * in-memory running tally; the AgentController remains responsible for persisting it * to (and hydrating it from) thread metadata, because usage is thread-scoped. * - the currently-selected mode (`session.mode`) and model (`session.model`). * The Session is the source of truth for which mode/model is active and owns * the mode-switch sequence and per-mode model memory. The AgentController still owns * the mode *definitions* (`config.modes`). * - transient run identity and abort control (`session.run`): the current run * id, trace id, monotonic operation counter, and the AbortController/ * abort-requested flag. This is per-run scratch state and is never persisted. * - the live agent thread subscription (`session.stream`): the open * subscription to the active thread's event stream and its dedup key. The * AgentController still produces the subscription (calling the agent) and consumes its * stream; the Session owns the handle and its lifecycle. * - the parked tool suspensions (`session.suspensions`): tool calls paused via * the native tool-suspension primitive awaiting a resume, keyed by toolCallId. * The Session owns the resume data; the AgentController keeps the richer per-suspension * UI snapshot on its display state. * - the follow-up queue (`session.followUps`): messages a user submits while a * run is in progress, held FIFO until the run finishes. The Session owns the * queue; the AgentController drives draining and keeps the `queuedFollowUps` display * mirror. * - the interactive tool-approval gate (`session.approval`): when a tool needs * user approval, the run parks on a promise here until the UI responds. The * Session owns the gate; the AgentController maps the decision to its effects (run vs * decline, any "always allow" grant), which touch config-derived categories. * * It also exposes a couple of accessors that compose `run` and `stream`: * {@link getCurrentRunId} (the active run id, preferring the live subscription) * and {@link abortRun} (abort the live run and mark it aborting). * * Mode/model persistence is thread-scoped, so the Session writes through a * {@link ThreadSettingsStore} the AgentController backs with thread metadata; when no * storage is configured the store is absent and state stays in memory. */ /** * Owns the session's canonical display state — the projection a UI renders from * instead of folding raw events itself. The Session holds the snapshot and the * reducer ({@link apply}) that keeps it in sync with every AgentController event; the * AgentController still owns the event bus and dispatches `display_state_changed` to * listeners after applying. * * The reducer needs a few read-only host/session facts it doesn't own: the live * token-usage tally, a subagent display-name lookup (AgentController config), and the * active thread id (to decide whether a `thread_deleted` clears the view). Those * are injected at construction so the reducer stays self-contained. */ export declare class SessionDisplayState { #private; private readonly deps; constructor(deps: { /** The session's live token-usage tally, mirrored into the view on usage/thread events. */ getTokenUsage: () => TokenUsage; /** Resolve a subagent's display name from AgentController config, or undefined when unnamed. */ getSubagentDisplayName: (agentType: string) => string | undefined; /** The active thread id, used to gate `thread_deleted` resets. */ getThreadId: () => string | null; /** Clear the session's follow-up queue when thread-scoped display state resets. */ clearFollowUps: () => void; }); /** * A read-only snapshot of the canonical display state. UIs should render from * this instead of building state up from raw events. */ get(): Readonly<AgentControllerDisplayState>; /** * Drop the display mirror of every parked tool suspension. Used on abort, * which abandons the run's parked suspensions; the caller dispatches * `display_state_changed`. */ clearPendingSuspensions(): void; /** * Clear the modified-files tally without touching the rest of the snapshot. * Used after a clone, which starts the cloned thread with a clean working set * while the surrounding UI reset handles tasks/tools explicitly. */ clearModifiedFiles(): void; /** * Drop the display mirror of a single parked tool suspension once it has been * resumed, so the UI stops rendering only the resolved prompt while any other * parked suspensions stay visible. */ deletePendingSuspension(toolCallId: string): void; /** * Restore task display state after a UI replays persisted task-tool history. * Updates the snapshot without emitting a live `task_updated` event, since no * task tool just ran. The caller dispatches `display_state_changed`. */ restoreTasks(tasks: TaskItemSnapshot[]): void; /** * Reset display fields scoped to a thread. Called on thread switch/creation. * Also clears the session's follow-up queue (mirrored by `queuedFollowUps`). */ resetThread(): void; /** * Apply a display-state update based on an incoming event. The centralized * state machine that keeps {@link AgentControllerDisplayState} in sync with every * event the AgentController emits. */ apply(event: AgentControllerEvent): void; } /** * A session's event bus. Owns the listener list and the full emit pipeline: * fold the event into the canonical display state, dispatch to this session's * listeners, then fan out a synthetic `display_state_changed`. Each session * has its own bus, so events never cross between sessions. Subsystems hold a * reference to their session's bus and call {@link emit} directly. */ export declare class SessionBus { #private; /** Attach the display-state reducer the bus folds events into. Set once by the Session. */ setDisplayState(displayState: SessionDisplayState): void; subscribe(listener: AgentControllerEventListener): () => void; emit(event: AgentControllerEvent): void; } export declare class Session<TState = unknown> { #private; /** The session's currently-selected model (source of truth) + per-mode memory. */ readonly model: SessionModel; /** The session's currently-selected mode and switch sequence. */ readonly mode: SessionMode; /** The session's observational-memory model selection (observer/reflector). */ readonly om: SessionOM; /** The session's persisted tool-permission rules (per-category / per-tool). */ readonly permissions: SessionPermissions; /** The session's subagent configuration (currently the subagent model). */ readonly subagents: SessionSubagents; /** Transient run identity (run id, trace id, operation counter) for the active run. */ readonly run: SessionRun; /** Live subscription to the active thread's agent event stream. */ readonly stream: SessionStream; /** Tool calls parked awaiting a resume (the resume data, keyed by toolCallId). */ readonly suspensions: SessionSuspensions; /** Messages queued to send after the active run finishes. */ readonly followUps: SessionFollowUps; /** The interactive tool-approval gate the current run parks on. */ readonly approval: SessionApproval; /** The session's identity: the memory resourceId it reads/writes under. */ readonly identity: SessionIdentity; /** The session's thread domain: current binding + reads scoped to it. */ readonly thread: SessionThread; /** The canonical display state a UI renders, plus the reducer that maintains it. */ readonly displayState: SessionDisplayState; /** The session-owned AgentController state domain. */ readonly state: AgentControllerRequestState<TState>; browser?: MastraBrowser; constructor({ resourceId, state, id, ownerId, tags, workspace, browser, }: { resourceId: string; state?: SessionStateOptions<TState>; id: string; ownerId: string; tags?: Record<string, string>; workspace: Workspace; browser?: MastraBrowser; }); /** * This session's scoping tags (e.g. `{ projectPath }`), stamped onto every * thread it creates. Returns a copy; empty when the session is unscoped. */ getTags(): Record<string, string>; /** * Subscribe to this session's events. Returns an unsubscribe function. * Listeners are scoped to this session: a session never delivers its events * to another session's subscribers. */ subscribe(listener: AgentControllerEventListener): () => void; /** * Emit an event on this session. Delegates to this session's bus, which folds * the event into the canonical display state, dispatches to this session's * listeners, then fans out a synthetic `display_state_changed`. */ emit(event: AgentControllerEvent): void; /** * Attach the thread-settings store the Session persists mode/model through. * The AgentController calls this once storage is available; without it, mode/model * state lives purely in memory. */ setStore(store: ThreadSettingsStore | undefined): void; /** * Attach the tool→category resolver used when a user picks "always allow * category". The category map is AgentController config, so the AgentController injects this * once; without it, an "always_allow_category" decision simply approves. */ setCategoryResolver(resolveCategory: (toolName: string) => ToolCategory | null): void; /** * Attach the subagent display-name resolver the display-state reducer uses to * label active subagents. The subagent catalog is AgentController config, so the * AgentController injects this once; without it, subagents render without a name. */ setSubagentNameResolver(resolveSubagentName: (agentType: string) => string | undefined): void; /** * Attach the AgentController-owned run machinery this session leverages to drive agent * runs (resolve the agent, build run/stream options + toolsets + request * context, persist usage, generate ids). The AgentController injects this once when it * constructs the session. The run loop, run state, and thread stream live on * the session; this is the narrow set of shared capabilities it reaches back * into the host for — see {@link SessionMachinery}. */ setMachinery(machinery: SessionMachinery): void; /** * The AgentController-owned run machinery injected via {@link setMachinery}, throwing * when accessed before wiring (a run can never be driven without it). */ get machinery(): SessionMachinery; /** The per-session run engine, throwing when accessed before machinery is wired. */ get runEngine(): SessionRunEngine; /**