import type { AgentEffect } from './effects.js'; import type { LogEntry } from '../protocol.js'; /** * Visual attention layer. Companion to `agentLog` — same `Append { entry }` * input shape, different output. While `agentLog` ring-buffers the full * activity history, `agentAttention` tracks only the most recent * dispatched entry's effective metadata so the host's view can flash * highlight classes onto DOM regions whose state paths just changed. * * Why a separate slice: the activity log is a passive timeline (read-only, * no time-bounded expiry, no per-region accessors). The attention layer * is a transient projection — the "current dispatch's spotlight" — that * has to clear itself after a configurable window, decay across renders, * and drive per-path accessors (one per highlightable region in the * host's layout). Mixing these into one slice would conflate "I want * to read past actions" with "I want to point at the current one." * * Composition contract: the host appends the SAME `LogEntry` payload * to both slices on every `log-append` from the ws-client (typically * via a single `agent/log/Append` Msg routed by `sliceHandler`). The * attention reducer ignores entries whose `kind` isn't `'dispatched'` * — proposed, blocked, error, and read entries don't update the * attention focus, since they don't represent a state mutation. * * Auto-clear: the reducer fires an `AgentAttentionFlashTimeout` effect * keyed by `entryId`. After `flashDurationMs`, the effect handler * dispatches a `Clear { entryId }` Msg back into the slice. The * conditional clear (only-if-current) means a fast follow-up dispatch * cleanly replaces the spotlight without the timer racing to wipe the * new one. Hosts that don't wire `wrapAttentionMsg` in the factory * still see the spotlight set; it just won't auto-clear (the next * dispatch overwrites it instead). */ export type AgentAttentionState = { /** * The current dispatch's spotlight, or null when no dispatch has * landed yet (or the auto-clear timer fired and `latestDispatch.entryId` * matched). */ latestDispatch: { entryId: string; /** * Top-level state paths the dispatch touched, derived from the * entry's JSON-Patch `stateDiff`. A whole-state replace (path * `/`) collapses to the wildcard `'*'` so callers can match every * region without enumerating their own state keys. */ paths: string[]; variant?: string; intent?: string; at: number; } | null; /** Configurable: how long the spotlight persists before auto-clear. */ flashDurationMs: number; }; export type AgentAttentionInitOpts = { /** Default 600ms — long enough to read, short enough not to obscure. */ flashDurationMs?: number; }; export type AgentAttentionMsg = { /** * Same shape as `agentLog`'s `Append` so the host can route a * single incoming Msg to both slices via `sliceHandler` without * a translation layer. */ type: 'Append'; entry: LogEntry; } | { /** * Fired by the auto-clear timer effect. Guarded by `entryId` — * the reducer only clears when `latestDispatch.entryId` matches, * so a fast follow-up dispatch isn't wiped by the previous * dispatch's pending timer. */ type: 'Clear'; entryId: string; } | { /** * Adjust the flash duration at runtime. Persists in state so * subsequent timeouts use the new value. Existing in-flight * timers are not cancelled — they'll fire at their original * delay, and the conditional clear handles the race. */ type: 'SetFlashDuration'; ms: number; }; export declare function init(opts?: AgentAttentionInitOpts): [AgentAttentionState, AgentEffect[]]; export declare function update(state: AgentAttentionState, msg: AgentAttentionMsg): [AgentAttentionState, AgentEffect[]]; import { type Send, type Signal } from '@llui/dom'; type RegionAction = { entryId: string; variant?: string; intent?: string; at: number; } | null; export type ConnectBag = { root: { 'data-scope': 'agent-attention'; }; /** * Reactive boolean signal: true while the spotlight covers `path`. * Use as the predicate for a conditional class binding in the host's * own element bag. Cached by `path` so each `flashing(path)` call * returns the same handle across renders, keeping the underlying * binding's short-circuit valid. */ flashing: (path: string) => Signal; /** * Convenience signal: resolves to `className` (default `'agent-flash'`) * while flashing, otherwise `undefined`. Spread into element bags * via `class: bag.flashClass('items')`. Cached per `(path, className)` * pair. */ flashClass: (path: string, className?: string) => Signal; /** * Metadata about the action that touched this path, or null when * the spotlight isn't on this path. Useful for tooltips or aria-live * narration: "agent → SelectAlternative just changed alternatives." * Cached per `path`. */ regionAction: (path: string) => Signal; /** * Direct signal on the latest dispatch envelope. Useful for a * single panel-level "now flashing: X" indicator outside the * per-region instrumentation. */ latestDispatch: Signal; }; export declare function connect(state: Signal, _send: Send): ConnectBag; export {}; //# sourceMappingURL=agentAttention.d.ts.map