import type { RuntimeMessagingAdapter } from "@yanlinglabs/winter-agent-sdk/messaging"; import type { DirectorySnapshot } from "../directory/directory.js"; import type { RuntimeDirectoryStore } from "../seams/directory-store.js"; import type { DeliveryOutcome, RuntimeAddress, RuntimeKind } from "../seams/messaging-contract.js"; import type { InboundPolicy } from "./inbound.js"; /** * WS-10 §15's adapter, plus the ONE fact the router must know about a runtime that the interface has * nowhere to put: whether it has a reliable idle signal at all. * * WS-10 §14 requires that "subagents, teammates, cloud/remote targets, AND ADAPTERS WITHOUT A RELIABLE * IDLE SIGNAL MUST refuse the ENTIRE call (including any attached message)". The shared core enforces * that from the target row's `capabilities.notifyWhenIdle` flag — which is a DIRECTORY field, written * by a host. A host that sets it optimistically on an official-runtime row would get the body * delivered and only the subscription refused, which is precisely the "delivered and then * un-delivered" shape §14 exists to prevent. So the adapter declares it, and the dispatcher clears the * flag on every row belonging to a runtime that cannot back it. Absent means "yes" — the honest * default for a host-registered adapter this package knows nothing about. */ export interface RouterMessagingAdapter extends RuntimeMessagingAdapter { readonly supportsIdleSubscriptions?: boolean; /** * PER ADDRESS, where `supportsIdleSubscriptions` is per RUNTIME (review r2, NEW-2). * * The runtime-wide flag is not fine enough: the Winter branch genuinely has a reliable idle signal * (its engine is the session-status event source), but only for a session this process holds a * FACET for — its own `subscribeIdle` refuses a facet-less one by address. So a row whose * `capabilities.notifyWhenIdle` a host wrote `true` (the ordinary case) attached through a plain * input-stream writer passed the core's eligibility check, the body was DELIVERED, and only the * subscription came back refused — which is exactly what WS-10 §14's "refuse the ENTIRE call * (including any attached message) so the sender can retry without the flag" forbids. * * Absent means "the runtime-wide flag is the whole answer", which is the honest default for a * host-registered adapter this package cannot see inside. */ canSubscribeIdle?(address: RuntimeAddress): Promise | boolean; } export interface DispatchDeps { snapshot: DirectorySnapshot; adapters: ReadonlyMap; policy: InboundPolicy; store: RuntimeDirectoryStore; now(): number; /** * Which rows the core is allowed to see through `listReachable`. * * `resolve` is every object this caller may ADDRESS — including an exited session, which WS-15 §6.2 * has a routing row for. `list` is WS-10 §10.2's narrower listing eligibility. They are two views of * the same snapshot rather than two reads, so a listing and a resolution can never disagree about a * row that changed in between. */ view: "resolve" | "list"; /** * WHERE A SUBSCRIBE GOES (review r1, M1). Absent = straight to the owner adapter. * * The shared core calls `adapter.subscribeIdle` for both the pure-subscription and the combined * `SendMessage{notify_when_idle}` call — the MODEL-facing path. Delegating that to the owner adapter * subscribes at the RUNTIME and writes nothing durable, so the router's own `store.subscriptions` — * the only thing `noteIdle()` reads — stayed empty and the one notice WS-10 §14 promises could never * be routed to anyone. The model was told `subscribed: true` and nothing ever arrived. * * So the core's dispatcher is given the ROUTER's own subscription door here, and only the router's * door talks to the owner adapter (with this field absent, which is also what stops the two from * recursing into each other). */ subscribeIdleVia?: (target: RuntimeAddress, request: { messageId: string; }) => Promise; /** * The inbound decision has ALREADY been made for this envelope (review r1, low 2). * * Set when a HELD message is released: `InboundPolicy.reevaluate` decided it and removed its durable * record, so re-running `decide` on the way out could produce a second, divergent answer that holds * a message whose record is gone — losing it. The accepted-queue slot is still reserved, because * that is the half a release genuinely changes. */ inboundDecided?: boolean; } export declare function createDispatchingAdapter(deps: DispatchDeps): RuntimeMessagingAdapter;