/**
 * Injection Engine — subflow builder.
 *
 * Pattern: Subflow Builder (returns a FlowChart mountable via
 *          `addSubFlowChartNext`). Each subflow stands alone.
 * Role:    Layer-3 context-engineering primitive. Sits BEFORE the
 *          three slot subflows in any primitive (Agent, LLMCall) that
 *          uses Injections. Evaluates every Injection's trigger once
 *          per iteration.
 *
 * Four small, readable stages (was one monolithic `evaluate`):
 *   1. Gather   — snapshot the turn's inputs (iteration, history size,
 *                 last tool, LLM-activated count). Observability only.
 *   2. Evaluate — run every trigger → `activeInjections` (the REAL output
 *                 the slot subflows read). Logic is UNCHANGED from the old
 *                 single stage: `activeInjections` is byte-identical, so the
 *                 slots are 100% unaffected. (Safety invariant.)
 *   3. Route    — partition `activeInjections` into per-slot buckets
 *                 (`activeByslot`), mirroring how the slots filter. Pure
 *                 annotation — the slots still do their own filtering.
 *   4. Delta    — diff this turn's buckets vs last turn's (`slotDelta`):
 *                 per slot, what activated / deactivated / stayed. The
 *                 explainability win ("tools +skill X, system-prompt
 *                 unchanged"). Reads last turn via `priorActiveByslot`
 *                 carried by the mount's input/output mappers.
 *
 * Nothing here SKIPS a slot — Route/Delta only annotate. See
 * docs (injection-algorithm blog) + memory agentfootprint_slot_plan_review
 * for why per-turn skip was deferred.
 *
 * Emits:   `agentfootprint.context.evaluated` at the Evaluate stage, with
 *          aggregate metadata. The per-slot route/delta ride visible stage
 *          STATE (`activeByslot` / `slotDelta`) so the lens reads them from
 *          the commit log without a new event-type contract.
 *
 * Mount with:
 *   builder.addSubFlowChartNext(
 *     SUBFLOW_IDS.INJECTION_ENGINE,
 *     buildInjectionEngineSubflow({ injections }),
 *     'Injection Engine',
 *     {
 *       inputMapper: (parent) => ({
 *         iteration: parent.iteration,
 *         userMessage: parent.userMessage,
 *         history: parent.history,
 *         lastToolResult: parent.lastToolResult,
 *         toolResults: parent.toolResults, // the whole batch, in call order
 *         activatedInjectionIds: parent.activatedInjectionIds ?? [],
 *         priorActiveByslot: parent.activeByslot ?? EMPTY_ACTIVE_BY_SLOT,
 *       }),
 *       outputMapper: (sf) => ({
 *         activeInjections: sf.activeInjections,
 *         activeByslot: sf.activeByslot, // carried so next turn's Delta can diff
 *       }),
 *     },
 *   )
 */
import type { FlowChart } from 'footprintjs';
import { type ActiveInjection, type Injection, type InjectionContext } from './types.js';
import { type CursorMove } from './skillGraph.js';
import { type StepPlanFor } from './skillSteps.js';
import type { EngagementPlan } from '../maps/engagement/types.js';
export interface InjectionEngineConfig {
    /**
     * The Injection list. Frozen at build time. To change at runtime,
     * rebuild the agent / chart — the primitive is intentionally
     * declarative.
     */
    readonly injections: readonly Injection[];
    /**
     * The skill-graph CURSOR resolver (`graph.nextSkill`), present only when the
     * agent was built with `.skillGraph()`. The Evaluate stage advances the cursor
     * with the SAME `ctx` the triggers gate on, so trigger ↔ cursor never diverge
     * (the keystone). Absent → `currentSkillId` is never written (no graph routing).
     */
    readonly nextSkill?: (ctx: InjectionContext) => string | undefined;
    /**
     * The same resolver, reporting the clause that WON (`graph.explainNextSkill`,
     * 8.5.0). Preferred over `nextSkill` when present — the stage takes the cursor
     * from `.to`, so the graph is still consulted exactly once per iteration, and
     * stamps the cause on `context.evaluated` as `cursorMove`.
     *
     * Optional for forward-compat with graphs built before it existed; without it
     * the stage falls back to `nextSkill` and emits no `cursorMove` (an observer
     * then sees exactly what it saw in 8.4.0).
     */
    readonly explainNextSkill?: (ctx: InjectionContext) => CursorMove;
    /**
     * The graph's suppression reporter (`graph.supersededEntries`, 8.15.0) — the
     * conditional entries whose own `when` matched this iteration while the cursor was
     * elsewhere, so the cursor law kept them off the wire. Stamped on
     * `context.evaluated` as `supersededIds`.
     *
     * Optional for forward-compat with graphs built before it existed; absent → the
     * field is omitted and an observer sees exactly what it saw in 8.14.0.
     */
    readonly supersededEntries?: (ctx: InjectionContext) => readonly string[];
    /**
     * The gate's admissible set, for the record (9.50.0) — given the cursor a
     * move landed on, the skill ids the `read_skill` gate will admit this
     * iteration (declared hops out of that cursor plus the open skills). The
     * Agent composes it from the SAME two resolvers the `read_skill` offer and
     * the refusal messages use, so the recorded set can never drift from the
     * verdicts. Stamped on `context.evaluated.cursorMove` as `reachable`.
     *
     * Optional for forward-compat with graphs built before `reachableSkills`
     * existed; absent → the field is omitted and an observer sees exactly what
     * it saw in 9.49.0.
     */
    readonly reachableSkills?: (currentSkillId?: string) => readonly string[];
    /**
     * The frozen step plans, keyed by skill id (9.18.0) — present only when
     * ≥1 registered skill declares `steps`. The Evaluate stage owns the
     * pointer's TENURE RE-KEY with it, at the same stage the cursor truth
     * lives: tenant changed → fresh pointer (or cleared, when the new tenant
     * has no steps); unchanged → pass-through. Absent → no pointer key is
     * ever written (zero-cost-when-unused).
     */
    readonly stepPlanFor?: StepPlanFor;
    /**
     * The mount kernel's plan (9.58.0) — present only when the agent was built
     * with `.maps()`. The Evaluate stage advances every mounted map's
     * engagement with the SAME ctx the triggers gate on, and hands the parked
     * maps' member ids to the evaluator as the one framework-tier suppression
     * (`ctx.parkedIds`, the `leaseActiveIds` admission's mirror). Absent →
     * nothing here runs and the evaluation is byte-identical.
     */
    readonly engagement?: EngagementPlan;
    /**
     * Which skills the caller's ROLE may not see (`skill_read`), asked once per
     * iteration — present only when the agent's permission checker governs
     * `skill_read`. The Gather stage resolves it FIRST, before anything on
     * this pass decides, and publishes it as `hiddenSkillIds`: the Evaluate
     * stage hands it to the cursor resolver and the evaluator
     * (`ctx.hiddenSkillIds`, read through `isHiddenSkill`), and the mount
     * carries it to the parent for every model-facing composer after it.
     * Absent → nothing is resolved and the evaluation is byte-identical.
     */
    readonly hiddenSkillIds?: (scope: object) => Promise<readonly string[]> | readonly string[];
}
/** One routed entry per (active injection × slot it contributes to). */
export interface RoutedInjection {
    readonly id: string;
    readonly source: ActiveInjection['flavor'];
    readonly reason: string;
}
/** Active injections partitioned by the slot they contribute to. */
export interface ActiveBySlot {
    readonly systemPrompt: readonly RoutedInjection[];
    readonly messages: readonly RoutedInjection[];
    readonly tools: readonly RoutedInjection[];
}
/** Per-slot change since last turn. */
export interface SlotDeltaEntry {
    readonly added: readonly string[];
    readonly removed: readonly string[];
    readonly kept: readonly string[];
}
/** Per-slot delta across the whole context. */
export interface SlotDelta {
    readonly systemPrompt: SlotDeltaEntry;
    readonly messages: SlotDeltaEntry;
    readonly tools: SlotDeltaEntry;
}
/** Empty buckets — turn-1 prior, and the safe default for the mappers. */
export declare const EMPTY_ACTIVE_BY_SLOT: ActiveBySlot;
/**
 * Build the Injection Engine subflow — Gather → Evaluate → Route → Delta.
 */
export declare function buildInjectionEngineSubflow(config: InjectionEngineConfig): FlowChart;
/** Partition active injections by the slot(s) each contributes to. Mirrors
 *  the slot subflows' own filters so this view matches what they compose.
 *  Pure — exported for unit tests + reuse (e.g. the lens). */
export declare function routeActiveInjections(active: readonly ActiveInjection[]): ActiveBySlot;
/** Diff two per-slot snapshots into a per-slot delta. Pure — exported for
 *  unit tests + reuse. */
export declare function diffActiveBySlot(prior: ActiveBySlot, current: ActiveBySlot): SlotDelta;
//# sourceMappingURL=buildInjectionEngineSubflow.d.ts.map