import type { ChannelAdapter, ChannelLogger, IngressSink } from "./adapter.js"; import type { DedupeStore } from "./dedupe.js"; import type { BindingStore, CredentialPort, IdentityPort, IngressHost, SessionPort } from "./ports.js"; import type { Binding, ChannelPlatform, ConversationKey } from "./types.js"; /** * Notice posted when `unmappedActorPolicy === "reject"` turns an inbound * message away — ephemeral where the surface supports it, a threaded regular * reply otherwise. */ export declare const REJECT_NOTICE: string; /** The five host-owned ports the engine consumes (spec §4.2). */ export type ChannelEnginePorts = { session: SessionPort; identity: IdentityPort; bindings: BindingStore; credentials: CredentialPort; ingress: IngressHost; }; /** Options for {@link ChannelEngine}. */ export type ChannelEngineOptions = { adapters: ChannelAdapter[]; ports: ChannelEnginePorts; /** Inbound eventId dedupe store. Default a 10-min {@link TtlDedupeStore}. */ dedupe?: DedupeStore; /** Post a "Thinking…" status if no agent text within this after deliver. Default 5000. */ placeholderAfterMs?: number; /** Policy for actors IdentityPort cannot map. Default "reject" (ephemeral notice). */ unmappedActorPolicy?: "reject" | "external-framed"; logger?: ChannelLogger; /** Clock override for tests. Default `Date.now`. */ now?: () => number; /** * Bindings the host restored from its store on boot. `start()` runs the * conversation preflight per binding — logging (not throwing) failures with * their actionable messages — and starts mirroring the enabled ones. */ initialBindings?: Binding[]; }; /** * Runs the channels data flow for a set of adapters against one host's ports. * * Ingress (spec §7.1): `sinkFor(platform)` hands each adapter an engine-owned * {@link IngressSink} whose `onMessage` fast-acks — loop-guard and dedupe * checks are synchronous, everything else detaches. Reactions and * conversation events are logged and dropped this milestone. * * Egress (spec §7.2): one {@link OutputMirror} + SessionPort subscription per * active binding, created by `bind()` / `initialBindings` / lazy bot-mention * bind — and self-healing on delivery to a bound conversation whose mirror is * not yet up. Every posted MessageRef lands in the egress-ref registry so its * echo can never re-ingest. */ export declare class ChannelEngine { private readonly opts; private readonly logger; private readonly dedupe; private readonly now; private readonly unmappedActorPolicy; private readonly adaptersByPlatform; private readonly sinks; /** Canonical `${platform}:${messageId}` keys of every message this engine posted (TTL-bounded). */ private readonly egressRefs; private readonly states; /** Per-conversation serial chains — ingress, bind() and unbind() all queue here (self-removing). */ private readonly conversationChains; private readonly placeholderAfterMs; private placeholderSeq; constructor(opts: ChannelEngineOptions); /** * Starts every adapter with an engine-owned IngressSink, then restores * `initialBindings`: preflight each (logging failures) and start mirroring * the enabled ones (spec §7.3 "on engine start"). */ start(): Promise; /** Disposes all mirrors, unsubscribes, and stops all adapters. */ stop(): Promise; /** * Verifies the conversation (adapter.verifyConversation), persists the * binding, and starts mirroring. Throws with the health problems on failure. * Rebinding an already-bound key replaces its mirror. The state mutation is * serialized on the conversation's chain, so a concurrent inbound lazy bind * either happens-before (and is replaced) or happens-after (and sees this * binding); the preflight stays outside the chain. */ bind(binding: Binding): Promise; /** Stops mirroring and removes the binding from the store (serialized per key). */ unbind(key: ConversationKey): Promise; /** The engine's sink for a given platform's adapter. Throws on an unknown platform. */ sinkFor(platform: ChannelPlatform): IngressSink; private adapterFor; /** Fast-ack intake (spec §7.1.3): synchronous checks only, then detach. */ private onMessage; /** * Appends `task` to the conversation's serial chain and returns its result. * Every state mutation for one key — detached ingress, `bind()`, `unbind()` * — runs through here, so an inbound lazy bind can never interleave with a * host-initiated rebind. The chain entry removes itself once drained. */ private enqueueOnKey; /** * Detaches ingress work onto the per-conversation chain: concurrent * messages for the same key run in arrival order, so the lazy-bind window * (bindings.get → createSession → bindings.put) can never race itself. */ private enqueueIngress; /** Detached ingress work: identity, routing, lazy bind, attributed delivery. */ private process; /** "Teams: " — title for a lazily-created thread session. */ private sessionTitle; /** Reject notice: ephemeral where supported; threaded regular reply otherwise. */ private postRejectNotice; private stateFor; /** Idempotent: one OutputMirror + session subscription per bound conversation. */ private ensureMirror; /** Engine-side filtering in front of the mirror: placeholder, loop guard, turn origin. */ private onAgentEvent; /** One-shot "_Thinking…_" status if the agent stays silent after a delivery (spec §7.1.7). */ private armPlaceholder; private disarmPlaceholder; /** Bind-time preflight in log-only form, for engine-start restoration. */ private verifyOrLog; /** Disarm, unsubscribe, and flush/dispose the mirror of one binding. */ private teardownState; } //# sourceMappingURL=engine.d.ts.map