/** * Reactor — typed reactive signals + priority-scheduled rule dispatch. * * Mirrors `python/src/adk_fluent/_reactor/`. A `Signal` holds a single * value with a monotonic version; mutations fan out to synchronous * subscribers, an optional `EventBus`, and (via `SignalPredicate`) to * declarative `.when(...)` handlers scheduled by a `Reactor`. * * Design decisions * ---------------- * - **Equality guard by default.** `signal.set(v)` is a no-op when * `v === current` (strict equality, plus `NaN`-safe handling via * `Object.is`). Pass `{ force: true }` to emit anyway. * - **Version is monotonic.** Even when equality-guarded emissions are * skipped, the version never rewinds. Consumers can memoize on it. * - **Observer isolation.** A failing subscriber never blocks other * subscribers or the mutation itself. * - **Predicate composition.** `&`, `|`, `~` operators aren't available * in TS, so we expose `.and()`, `.or()`, `.not()` methods. * - **Priority preemption.** Lower numeric priority wins. When a rule * fires while one is running, the running task is cancelled and the * new one takes over; the current rule's token gets `resume_cursor` * set to the tape's head. */ import type { EventBus } from "./harness/events.js"; import { AgentToken, TokenRegistry } from "./harness/lifecycle.js"; export type SignalSubscriber = (value: T, previous: T) => void; export interface SignalOptions { bus?: EventBus; } /** * Typed reactive state cell. */ export declare class Signal { readonly name: string; private _value; private _version; private _bus?; private readonly _subs; constructor(name: string, initial: T, opts?: SignalOptions); /** Monotonic mutation counter. Survives equality-guarded no-ops. */ get version(): number; get value(): T; get(): T; /** * Set the signal's value. Emits `SignalChangedEvent` unless unchanged. * Returns true if an emission happened, false if skipped. */ set(value: T, opts?: { force?: boolean; }): boolean; /** Apply `fn(current) -> new` atomically. Returns whether emission occurred. */ update(fn: (current: T) => T): boolean; /** Register a sync observer. Returns an unsubscribe callable. */ subscribe(fn: SignalSubscriber): () => void; /** Wire this signal to a bus. Returns self for chaining. */ attach(bus: EventBus): this; /** Predicate that fires on every change. */ get changed(): SignalPredicate; /** Predicate that fires when value rises (new > prev). */ get rising(): SignalPredicate; /** Predicate that fires when value falls (new < prev). */ get falling(): SignalPredicate; /** Predicate that fires when value equals `expected` after a change. */ is(expected: T): SignalPredicate; toString(): string; } export type PredicateFn = (current: unknown, previous: unknown) => boolean; /** * Declarative predicate over one or more signals. Use `.and()` / `.or()` * / `.not()` to compose, `.where(fn)` to add an extra guard, and * `.debounce(ms)` / `.throttle(ms)` for rate control. */ export declare class SignalPredicate { readonly deps: ReadonlyArray>; private readonly check; private _debounceMs; private _throttleMs; private _lastFired; private _debounceTimer; constructor(deps: ReadonlyArray>, check: PredicateFn); static onChanged(signal: Signal): SignalPredicate; static onRising(signal: Signal): SignalPredicate; static onFalling(signal: Signal): SignalPredicate; static onEquals(signal: Signal, expected: U): SignalPredicate; and(other: SignalPredicate): SignalPredicate; or(other: SignalPredicate): SignalPredicate; not(): SignalPredicate; where(fn: PredicateFn): SignalPredicate; /** * Return a fresh predicate with the given debounce window. * * Immutable: the base predicate is unchanged and can be reused. * Preserves any throttle window already attached. */ debounce(ms: number): SignalPredicate; /** * Return a fresh predicate with the given throttle window. * * Immutable: the base predicate is unchanged and can be reused. * Preserves any debounce window already attached. */ throttle(ms: number): SignalPredicate; /** @internal — used by the R namespace to inspect wiring. */ get _debounceMsValue(): number; /** @internal — used by the R namespace to inspect wiring. */ get _throttleMsValue(): number; /** * Evaluate the predicate for a (current, previous) pair. Returns: * - false / "throttled": don't fire * - true: fire immediately * - "debounce": fire after `debounceMs` elapses without another hit * * When the predicate returns "debounce", the caller passes a `fire` * callback invoked once after the debounce window lapses. */ evaluate(current: unknown, previous: unknown, fire: () => void): "fired" | "skipped"; } export type ReactorHandler = (ctx: ReactorContext) => Promise | void; export interface ReactorContext { /** Name of the agent this rule is bound to, if any. */ agentName: string | null; /** Per-agent token if the reactor is registry-aware. */ token: AgentToken | null; /** Previous value that triggered the rule. */ previous: unknown; /** Current value that triggered the rule. */ current: unknown; /** Tape head cursor at the moment the rule fired. */ cursor: number; } export interface ReactorRuleOptions { /** Stable agent name for targeted preemption via `TokenRegistry`. */ agentName?: string; /** Lower number = higher priority. Default 100. */ priority?: number; /** If true, interrupt any currently-running rule when this one fires. */ preemptive?: boolean; } export interface ReactorRule { readonly predicate: SignalPredicate; readonly handler: ReactorHandler; readonly agentName: string | null; readonly priority: number; readonly preemptive: boolean; } export interface ReactorOptions { registry?: TokenRegistry; /** Called with cursor head when a rule preempts another. Used for * emitting an `interrupt` event on a tape or bus. */ onPreempt?: (victim: ReactorRule, cursor: number) => void; /** Optional hook that returns the current tape head cursor, used to * stamp `ReactorContext.cursor` and feed preemption resume. */ cursor?: () => number; } export declare class Reactor { private readonly rules; private readonly unsubs; private _current; private readonly queue; private readonly registry; private readonly onPreempt?; private readonly cursor; private _started; constructor(opts?: ReactorOptions); /** Register a rule. */ when(predicate: SignalPredicate, handler: ReactorHandler, opts?: ReactorRuleOptions): this; /** Subscribe to every signal referenced by the registered rules. */ start(): void; /** Tear down subscriptions. Does not cancel in-flight tasks. */ stop(): void; private _onChange; private _dispatch; private _finish; /** Expose the token registry so callers can cancel specific agents. */ tokens(): TokenRegistry; /** The rules registered against this reactor (read-only copy). */ getRules(): readonly ReactorRule[]; } /** @internal used by Signal.get() to record reads during tracking. */ export declare function _recordRead(sig: Signal): void; /** * Create a derived signal that re-runs `fn` whenever a dependency changes. * * Dependencies are auto-tracked: every ``Signal.get()`` called during * ``fn`` is subscribed. The first invocation seeds the value; later * changes to any tracked dep trigger a recompute and emission. */ export declare function computed(name: string, fn: () => T, opts?: SignalOptions): Signal; /** * A declarative reactor rule attached to a builder via ``.on()``. * * Immutable. Stored on builders in the ``_reactor_rules`` list and * materialized into a ``ReactorRule`` by ``R.compile()``. */ export interface RuleSpec { readonly predicate: SignalPredicate; readonly handler: ReactorHandler | null; readonly name: string; readonly priority: number; readonly preemptive: boolean; } export interface RuleSpecOptions { name?: string; priority?: number; preemptive?: boolean; } export declare function makeRuleSpec(predicate: SignalPredicate, handler: ReactorHandler | null, opts?: RuleSpecOptions): RuleSpec; /** * Thread-safe-style (single-threaded in Node) name→signal registry * backing the ``R`` facade. * * One registry per logical session. The module-level ``defaultRegistry`` * is the default scope used by ``R.*``; tests and isolated workflows can * create a dedicated instance and use it directly. * * Every signal created through the registry shares the registry's bus, * so mutations flow through a single event stream the reactor observes. */ export declare class SignalRegistry { private _signals; private _rules; private _bus?; constructor(opts?: { bus?: EventBus; }); get bus(): EventBus | undefined; /** Attach a bus. Existing signals are re-wired to it. */ attach(bus: EventBus): this; /** * Get-or-create the named signal. Re-calling with the same name * returns the same instance. ``initial`` is only used on first * creation. */ signal(name: string, initial?: T): Signal; /** Return an existing signal or throw. */ get(name: string): Signal; has(name: string): boolean; names(): string[]; /** Drop every signal and standalone rule. Primarily for tests. */ clear(): void; /** Register a standalone rule (not tied to a specific builder). */ rule(predicate: SignalPredicate, handler: ReactorHandler, opts?: RuleSpecOptions): RuleSpec; rules(): readonly RuleSpec[]; /** @internal */ _internalRegister(name: string, sig: Signal): void; } /** Module-level default registry backing the ``R`` facade. */ export declare let defaultRegistry: SignalRegistry; /** * Options for ``R.compile()``. */ export interface RCompileOptions { /** Optional bus to attach to the registry and reactor. */ bus?: EventBus; /** Override the registry used for rule discovery. */ registry?: SignalRegistry; /** Passed through to the constructed ``Reactor``. */ cursor?: () => number; /** Passed through to the constructed ``Reactor``. */ onPreempt?: (victim: ReactorRule, cursor: number) => void; /** Passed through to the constructed ``Reactor``. */ tokenRegistry?: TokenRegistry; } /** * The reactive namespace — first-class signals, predicates, and rules. * * Mirrors the ergonomics of ``S`` (state), ``C`` (context), ``M`` * (middleware): name-addressed factories instead of manual object * construction. Every call delegates to ``defaultRegistry`` unless a * dedicated registry is supplied via ``R.scope()``. * * Example:: * * import { Agent, R } from "adk-fluent-ts"; * * const temp = R.signal("temp", 72); * const cooler = new Agent("cooler", "gemini-2.5-flash") * .instruct("Plan a cool-down.") * .on(R.rising("temp").where((v) => (v as number) > 90)); * * const reactor = R.compile([cooler]); * reactor.start(); * temp.set(92); */ export declare const R: { /** The active default registry. */ registry(): SignalRegistry; /** Swap the module-level default registry. Primarily for tests. */ setRegistry(reg: SignalRegistry): void; /** Return a fresh, isolated registry. */ scope(opts?: { bus?: EventBus; }): SignalRegistry; /** Drop every signal and standalone rule from the default registry. */ clear(): void; /** Attach a bus to the default registry. Returns it for chaining. */ attach(bus: EventBus): SignalRegistry; /** Get-or-create a named signal in the default registry. */ signal(name: string, initial?: T): Signal; /** Return an existing signal by name, or throw. */ get(name: string): Signal; /** Names of registered signals (insertion order). */ names(): string[]; /** Predicate that fires on every change of the named signal. */ changed(name: string): SignalPredicate; /** Predicate that fires when the named signal rises (new > prev). */ rising(name: string): SignalPredicate; /** Predicate that fires when the named signal falls (new < prev). */ falling(name: string): SignalPredicate; /** Predicate that fires when the named signal equals `expected`. */ is(name: string, expected: T): SignalPredicate; /** Fire when any of the passed predicates match. */ any(...preds: SignalPredicate[]): SignalPredicate; /** Fire only when all of the passed predicates match. */ all(...preds: SignalPredicate[]): SignalPredicate; /** * Create a derived signal driven by `fn`. Dependencies are auto-tracked * via `Signal.get()`. Registered on the default registry so it can be * referenced by name via `R.get(name)` / `R.changed(name)`. */ computed(name: string, fn: () => T): Signal; /** Register a standalone rule against the default registry. */ rule(predicate: SignalPredicate, handler: ReactorHandler, opts?: RuleSpecOptions): RuleSpec; /** * Build a `Reactor` with every rule discovered on the given builders * plus any standalone rules on the registry. * * Walks each builder's ``_reactor_rules`` (attached via ``.on()``) and * recurses into nested Pipelines / FanOuts / Loops. The bus attached * to the registry is reused unless one is supplied explicitly. */ compile(builders?: readonly unknown[], opts?: RCompileOptions): Reactor; }; /** * Lifecycle plugin that starts/stops a ``Reactor`` from session callbacks. * * Mirrors ``python/src/adk_fluent/_reactor/_plugin.py``. Duck-typed * against the ADK plugin protocol — exposes ``onSessionStart`` and * ``onSessionEnd`` hooks that the runtime invokes around each session. */ export declare class ReactorPlugin { readonly name = "reactor"; private readonly reactor; constructor(reactor: Reactor); /** Start the reactor. */ start(): void; /** Stop the reactor. */ stop(): void; onSessionStart(): Promise; onSessionEnd(): Promise; } //# sourceMappingURL=reactor.d.ts.map