import type { StateDiff } from '../../state-diff.js'; import { type ValidationError } from './validate-payload.js'; import type { MsgSchemaShape } from '../factory.js'; import type { MessageAnnotations } from '../../protocol.js'; /** * Predict the result of dispatching `msg` without actually applying * it. Runs the reducer in isolation against the current state, * returns the would-be diff and the would-fire effects, but doesn't * commit or run anything. Lets the agent reason about a candidate * action before pulling the trigger: * * - "If I dispatch X, what will change?" — read `stateDiff`. * - "Will it fire effects? Which ones?" — read `effects`. * - "Should I batch?" — predict each, see whether the diffs * compose without conflict. * * The contract is bounded by TEA's purity assumption: the reducer * must be a pure function `(state, msg) → [newState, effects]`. LLui * reducers are pure by convention (the runtime never re-runs them * speculatively for any other reason, so impurity would already be * a latent bug). Apps whose reducers branch on `Date.now()` or read * `localStorage` will see prediction drift from real dispatch by * exactly that amount of impurity — usually negligible, sometimes * surprising; document at the call site. * * **No effects fire.** The returned `effects` array is the literal * effect descriptors the reducer produced — what `onEffect` would * have received. The agent reads them; the runtime ignores them. * This is the entire reason the tool exists separately from * `send_message`: a real dispatch hits the cloud / analytics / * persistence; a predicted one doesn't. */ export type WouldDispatchHost = { getState(): unknown; /** * Run the reducer in isolation. `[newState, effects]` shape. * Implemented by the AppHandle as a thin wrapper around * `inst.def.update(state, msg)` — no flush, no subscribe, no * commit. Implementations that can't run the reducer (e.g. * test harnesses with no live instance) return null and the * tool reports unsupported. */ runReducer(msg: { type: string; [k: string]: unknown; }): { state: unknown; effects: unknown[]; } | null; /** * The compiler-emitted Msg schema, when available. Used to * validate the candidate payload structurally before running the * reducer — wrong enum values, missing discriminants, primitive * type mismatches surface as structured errors the agent can fix * in one round trip instead of probing field by field. Hosts * without schema metadata return null and the validator skips, * keeping the tool permissive (the reducer still runs). */ getMsgSchema?(): MsgSchemaShape | null; /** * The compiler-emitted Msg annotations, when available. Used to apply * the SAME dispatch gate `send_message` enforces — `human-only` and * unknown variants are rejected before the reducer runs, so an agent * can't use prediction to probe a transition it's forbidden to * dispatch. Hosts without annotation metadata return null and the gate * is skipped (the reducer still runs), matching `send_message`. */ getMsgAnnotations?(): Record | null; }; export type WouldDispatchArgs = { msg: { type: string; [k: string]: unknown; }; }; export type WouldDispatchResult = { status: 'predicted'; /** Diff from current state to the predicted post-reducer state. */ stateDiff: StateDiff; /** Effects the reducer would emit. Order matches the reducer's return. */ effects: unknown[]; } | { status: 'rejected'; reason: 'invalid' | 'unsupported' | 'human-only'; detail?: string; } | { /** * The candidate Msg failed schema validation BEFORE the reducer * ran. `errors` lists every structural mismatch with a path- * keyed description. The agent reads this as "fix these fields * and retry" — no reducer side-effects to roll back, no state * change to predict around. */ status: 'rejected'; reason: 'schema-mismatch'; errors: ValidationError[]; } | { /** * The reducer threw while running against the candidate Msg. * State is poisoned (or the reducer has a latent bug); a real * `send_message` would land state change + an error in * `drain.errors`. `would_dispatch` mirrors that contract: the * "diff" is empty (we couldn't compute it), and the throw text * is surfaced so the agent knows to back off rather than * retrying the same payload. * * Distinct from `'rejected'` because the agent learned something * different: the reducer DOES accept this Msg shape but errors * downstream. Often that means earlier state needs fixing first * (a previously-dispatched bad Msg poisoned a derived path), * not that this candidate is malformed. */ status: 'reducer-threw'; message: string; stack?: string; }; export declare function handleWouldDispatch(host: WouldDispatchHost, args: WouldDispatchArgs): WouldDispatchResult; //# sourceMappingURL=would-dispatch.d.ts.map