@yanlinglabs/winter-runtime-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/dist/directory/directory.d.ts +55 -0
  4. package/dist/directory/entries.d.ts +82 -0
  5. package/dist/directory/recovery.d.ts +49 -0
  6. package/dist/door.d.ts +247 -0
  7. package/dist/errors.d.ts +104 -0
  8. package/dist/index.d.ts +25 -0
  9. package/dist/index.js +6679 -0
  10. package/dist/messaging/attribution.d.ts +47 -0
  11. package/dist/messaging/dispatch.d.ts +78 -0
  12. package/dist/messaging/handlers.d.ts +56 -0
  13. package/dist/messaging/inbound.d.ts +110 -0
  14. package/dist/messaging/index.d.ts +39 -0
  15. package/dist/messaging/official-adapter.d.ts +36 -0
  16. package/dist/messaging/router.d.ts +101 -0
  17. package/dist/messaging/sessions.d.ts +49 -0
  18. package/dist/messaging/winter-adapter.d.ts +47 -0
  19. package/dist/native-args.d.ts +76 -0
  20. package/dist/official/adapter.d.ts +88 -0
  21. package/dist/official/aliases.d.ts +48 -0
  22. package/dist/official/auth.d.ts +117 -0
  23. package/dist/official/branding.d.ts +31 -0
  24. package/dist/official/callbacks.d.ts +143 -0
  25. package/dist/official/containment.d.ts +130 -0
  26. package/dist/official/env-allowlist.d.ts +237 -0
  27. package/dist/official/env-registry-rule.d.ts +12 -0
  28. package/dist/official/env-registry.d.ts +3 -0
  29. package/dist/official/errors.d.ts +250 -0
  30. package/dist/official/index.d.ts +31 -0
  31. package/dist/official/mcp-descriptors.d.ts +130 -0
  32. package/dist/official/options-template.d.ts +110 -0
  33. package/dist/official/spawn-proxy.d.ts +144 -0
  34. package/dist/official/spool.d.ts +80 -0
  35. package/dist/official/supervision.d.ts +49 -0
  36. package/dist/official/sweep.d.ts +65 -0
  37. package/dist/sdk.d.ts +214 -0
  38. package/dist/seams/context.d.ts +26 -0
  39. package/dist/seams/directory-store.d.ts +274 -0
  40. package/dist/seams/directory.d.ts +46 -0
  41. package/dist/seams/global-messaging.d.ts +30 -0
  42. package/dist/seams/handoff.d.ts +77 -0
  43. package/dist/seams/index.d.ts +11 -0
  44. package/dist/seams/keychain.d.ts +11 -0
  45. package/dist/seams/materialized-resume.d.ts +46 -0
  46. package/dist/seams/messaging-contract.d.ts +29 -0
  47. package/dist/seams/official-adapter.d.ts +125 -0
  48. package/dist/seams/official-sdk-shapes.d.ts +126 -0
  49. package/dist/seams/stubs.d.ts +34 -0
  50. package/dist/selection/child-runtime.d.ts +81 -0
  51. package/dist/selection/runtime-selection.d.ts +217 -0
  52. package/dist/selection/select-runtime.d.ts +213 -0
  53. package/dist/store/handoff-barrier.d.ts +238 -0
  54. package/dist/store/index.d.ts +11 -0
  55. package/dist/store/materialized-resume.d.ts +100 -0
  56. package/dist/store/pinned-probes.d.ts +17 -0
  57. package/dist/store/reconcile.d.ts +157 -0
  58. package/dist/store/temp-continuity.d.ts +92 -0
  59. package/dist/store/wiring.d.ts +250 -0
  60. package/dist/vendor-paths.d.ts +21 -0
  61. package/dist/version-matrix.d.ts +84 -0
  62. package/docs/conformance-rows.md +195 -0
  63. package/package.json +65 -0
@@ -0,0 +1,47 @@
1
+ import type { GlobalAgentMessage, RuntimeAddress } from "../seams/messaging-contract.js";
2
+ /** A sender whose `from` cannot be turned into a canonical address cannot be attributed at all. */
3
+ export declare class UnattributableSenderError extends Error {
4
+ constructor(reason: string);
5
+ }
6
+ /**
7
+ * THE OWNER CHECK ON ITS OWN — the reason this envelope cannot be attributed here, or `undefined`.
8
+ *
9
+ * SEPARATED FROM THE RENDERING (review r1, D1) because it is a DECISION and rendering is an effect,
10
+ * and the three delivery paths need the decision at a different moment than the string: before any
11
+ * push, outside every `try`, so that one envelope gets one answer whichever handle shape a host
12
+ * attached. Evaluated inside a `try` whose `catch` maps to `delivery_uncertain`, the same refusal
13
+ * became "the delivery may have occurred" for a message that provably never left the router — and
14
+ * WS-10 §12's recovery contract then forbids retrying it, so a clean side-effect-free "no" turned into
15
+ * a permanently ambiguous record.
16
+ *
17
+ * `owner` is the session whose input stream this is going into. A claimed CHILD sender is only
18
+ * attributable by the session that owns that child (WS-10 §10.3), so an `agent:` origin from somewhere
19
+ * else is refused rather than rendered with an address this side cannot vouch for. Pass `undefined` to
20
+ * skip the check — for the official branch's owner-qualified relay, where the message is deliberately
21
+ * being handed to a parent that does NOT own the sender.
22
+ */
23
+ export declare function unattributableReason(message: GlobalAgentMessage, owner?: {
24
+ winterSessionId: string;
25
+ }): string | undefined;
26
+ /**
27
+ * The attributed turn, byte-for-byte as the Winter runtime renders it.
28
+ *
29
+ * Throws `UnattributableSenderError` on the same condition `unattributableReason` names, so a caller
30
+ * that renders without checking still cannot produce an unattributable frame — but every caller in
31
+ * this package checks FIRST, because a throw at this point is an effect-free refusal and must be
32
+ * reported as one.
33
+ */
34
+ export declare function renderAttributedTurn(message: GlobalAgentMessage, owner?: {
35
+ winterSessionId: string;
36
+ }): string;
37
+ /**
38
+ * The owner-qualified form: the same frame, plus the address the message was actually FOR.
39
+ *
40
+ * WS-15 §6.2's official-child rows are the only user: "Native child messaging through the active
41
+ * owning parent session", and "the public result stays owner-qualified" — the official runtime has no
42
+ * out-of-band door into a child, so a message for one is delivered to the owning parent, which is a
43
+ * different fact from a message TO the parent and must not read like one. The `for` attribute is
44
+ * router-authored (it is the resolved canonical address, not anything a sender wrote), so it is
45
+ * escaped for the same defensive reason `from` is and can no more be forged than `from` can.
46
+ */
47
+ export declare function renderOwnerQualifiedTurn(message: GlobalAgentMessage, target: RuntimeAddress): string;
@@ -0,0 +1,78 @@
1
+ import type { RuntimeMessagingAdapter } from "@yanlinglabs/winter-agent-sdk/messaging";
2
+ import type { DirectorySnapshot } from "../directory/directory.js";
3
+ import type { RuntimeDirectoryStore } from "../seams/directory-store.js";
4
+ import type { DeliveryOutcome, RuntimeAddress, RuntimeKind } from "../seams/messaging-contract.js";
5
+ import type { InboundPolicy } from "./inbound.js";
6
+ /**
7
+ * WS-10 §15's adapter, plus the ONE fact the router must know about a runtime that the interface has
8
+ * nowhere to put: whether it has a reliable idle signal at all.
9
+ *
10
+ * WS-10 §14 requires that "subagents, teammates, cloud/remote targets, AND ADAPTERS WITHOUT A RELIABLE
11
+ * IDLE SIGNAL MUST refuse the ENTIRE call (including any attached message)". The shared core enforces
12
+ * that from the target row's `capabilities.notifyWhenIdle` flag — which is a DIRECTORY field, written
13
+ * by a host. A host that sets it optimistically on an official-runtime row would get the body
14
+ * delivered and only the subscription refused, which is precisely the "delivered and then
15
+ * un-delivered" shape §14 exists to prevent. So the adapter declares it, and the dispatcher clears the
16
+ * flag on every row belonging to a runtime that cannot back it. Absent means "yes" — the honest
17
+ * default for a host-registered adapter this package knows nothing about.
18
+ */
19
+ export interface RouterMessagingAdapter extends RuntimeMessagingAdapter {
20
+ readonly supportsIdleSubscriptions?: boolean;
21
+ /**
22
+ * PER ADDRESS, where `supportsIdleSubscriptions` is per RUNTIME (review r2, NEW-2).
23
+ *
24
+ * The runtime-wide flag is not fine enough: the Winter branch genuinely has a reliable idle signal
25
+ * (its engine is the session-status event source), but only for a session this process holds a
26
+ * FACET for — its own `subscribeIdle` refuses a facet-less one by address. So a row whose
27
+ * `capabilities.notifyWhenIdle` a host wrote `true` (the ordinary case) attached through a plain
28
+ * input-stream writer passed the core's eligibility check, the body was DELIVERED, and only the
29
+ * subscription came back refused — which is exactly what WS-10 §14's "refuse the ENTIRE call
30
+ * (including any attached message) so the sender can retry without the flag" forbids.
31
+ *
32
+ * Absent means "the runtime-wide flag is the whole answer", which is the honest default for a
33
+ * host-registered adapter this package cannot see inside.
34
+ */
35
+ canSubscribeIdle?(address: RuntimeAddress): Promise<boolean> | boolean;
36
+ }
37
+ export interface DispatchDeps {
38
+ snapshot: DirectorySnapshot;
39
+ adapters: ReadonlyMap<RuntimeKind, RouterMessagingAdapter>;
40
+ policy: InboundPolicy;
41
+ store: RuntimeDirectoryStore;
42
+ now(): number;
43
+ /**
44
+ * Which rows the core is allowed to see through `listReachable`.
45
+ *
46
+ * `resolve` is every object this caller may ADDRESS — including an exited session, which WS-15 §6.2
47
+ * has a routing row for. `list` is WS-10 §10.2's narrower listing eligibility. They are two views of
48
+ * the same snapshot rather than two reads, so a listing and a resolution can never disagree about a
49
+ * row that changed in between.
50
+ */
51
+ view: "resolve" | "list";
52
+ /**
53
+ * WHERE A SUBSCRIBE GOES (review r1, M1). Absent = straight to the owner adapter.
54
+ *
55
+ * The shared core calls `adapter.subscribeIdle` for both the pure-subscription and the combined
56
+ * `SendMessage{notify_when_idle}` call — the MODEL-facing path. Delegating that to the owner adapter
57
+ * subscribes at the RUNTIME and writes nothing durable, so the router's own `store.subscriptions` —
58
+ * the only thing `noteIdle()` reads — stayed empty and the one notice WS-10 §14 promises could never
59
+ * be routed to anyone. The model was told `subscribed: true` and nothing ever arrived.
60
+ *
61
+ * So the core's dispatcher is given the ROUTER's own subscription door here, and only the router's
62
+ * door talks to the owner adapter (with this field absent, which is also what stops the two from
63
+ * recursing into each other).
64
+ */
65
+ subscribeIdleVia?: (target: RuntimeAddress, request: {
66
+ messageId: string;
67
+ }) => Promise<DeliveryOutcome>;
68
+ /**
69
+ * The inbound decision has ALREADY been made for this envelope (review r1, low 2).
70
+ *
71
+ * Set when a HELD message is released: `InboundPolicy.reevaluate` decided it and removed its durable
72
+ * record, so re-running `decide` on the way out could produce a second, divergent answer that holds
73
+ * a message whose record is gone — losing it. The accepted-queue slot is still reserved, because
74
+ * that is the half a release genuinely changes.
75
+ */
76
+ inboundDecided?: boolean;
77
+ }
78
+ export declare function createDispatchingAdapter(deps: DispatchDeps): RuntimeMessagingAdapter;
@@ -0,0 +1,56 @@
1
+ import { type GlobalMessagingHandle } from "./router.js";
2
+ /** The MCP result shape both branches return — structurally the descriptor's own (WS-14 §11). */
3
+ export interface MessagingToolResult {
4
+ content: Array<{
5
+ type: "text";
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }
10
+ export type MessagingToolHandler = (args: unknown, extra?: unknown) => Promise<MessagingToolResult>;
11
+ /**
12
+ * WS-10 §12's RETRY KEY, on the official branch — and it exists, which was not known until it was
13
+ * measured (item 15; `test/official/runtime-aliases.test.ts` is the measurement).
14
+ *
15
+ * §12 wants a message id derived from (sender session, TOOL-CALL id) so "a retry allocates the SAME
16
+ * id" and returns the stored outcome instead of starting a second turn. On the Winter branch the
17
+ * caller binds `toolUseId` at registration. On the official branch the handler is inside the vendor's
18
+ * in-process MCP server, where the only per-call channel is the second argument the vendor passes —
19
+ * and the reasonable expectation was that it carries MCP request context (a JSON-RPC request id,
20
+ * `_meta`) rather than an Anthropic-API `tool_use_id`, which is one layer up.
21
+ *
22
+ * THE PINNED RUNTIME BRIDGES THEM. Measured on 0.3.250: `extra._meta["claudecode/toolUseId"]` is the
23
+ * exact id the model emitted. So the official branch gets a real §12 key rather than depending on the
24
+ * rapid-repeat guard, and the vendor's own namespaced `_meta` name is read rather than guessed at.
25
+ *
26
+ * A VENDOR-NAMESPACED KEY IS NEVER REBRANDED (WS-01 §5): `claudecode/toolUseId` is the vendor's name
27
+ * for the vendor's field, exactly like `CLAUDE_CONFIG_DIR`. It is read defensively — an absent or
28
+ * non-string value simply falls back to the bound caller's id — because a future pin may move it, and
29
+ * losing the key must degrade to today's behaviour rather than to a crash.
30
+ */
31
+ export declare const VENDOR_TOOL_USE_ID_META_KEY = "claudecode/toolUseId";
32
+ export declare function toolUseIdFromExtra(extra: unknown): string | undefined;
33
+ export interface MessagingToolHandlers {
34
+ sendMessage: MessagingToolHandler;
35
+ listAgents: MessagingToolHandler;
36
+ }
37
+ /**
38
+ * WHO IS CALLING — bound at registration, never read out of the arguments.
39
+ *
40
+ * The standing MCP server is materialized per session (WS-14 §11), so the caller is known when the
41
+ * handler is built. Taking it from the ARGUMENTS instead would make the sender's identity something a
42
+ * model could write, and every fence in this package — the owning-parent rule, the self-target
43
+ * refusal, WS-10 §13's sender class, WS-15 §6.2's dedupe key — is keyed on it.
44
+ *
45
+ * `toolUseId` is the second half of WS-10 §12's retry key. It is OPTIONAL because on the official
46
+ * branch it is not available: the in-process MCP server hands a tool handler its arguments, and the
47
+ * router's own materialization does not forward the vendor's `extra` (which is where a request id
48
+ * would be). A caller that cannot supply one gets no dedupe — stated at the door rather than faked
49
+ * with a stable-looking key that would make two different messages one.
50
+ */
51
+ export interface MessagingToolCaller {
52
+ sessionId: string;
53
+ agentId?: string;
54
+ toolUseId?: string;
55
+ }
56
+ export declare function createMessagingToolHandlers(messaging: GlobalMessagingHandle, caller: MessagingToolCaller | (() => MessagingToolCaller)): MessagingToolHandlers;
@@ -0,0 +1,110 @@
1
+ import type { CrossSessionInbound, HeldEntry, Mailbox, PermissionClassLabel } from "@yanlinglabs/winter-agent-sdk/messaging";
2
+ import type { HeldMessageRecord, RuntimeDirectoryEntry, RuntimeDirectoryStore } from "../seams/directory-store.js";
3
+ import type { DeliveryOutcome, GlobalAgentMessage, SerializedRuntimeAddress } from "../seams/messaging-contract.js";
4
+ export interface InboundPolicyHooks {
5
+ /**
6
+ * The receiver's own explicit `crossSessionInbound` setting, when the host has one.
7
+ *
8
+ * WS-10 §13: an explicit setting ALWAYS WINS over the class matrix. It is a host fact (the settings
9
+ * hierarchy is the host's), so it is asked for rather than assumed; absent means "no explicit
10
+ * setting", which is what makes the matrix the default rather than the only rule.
11
+ */
12
+ explicitSetting?: (receiver: RuntimeDirectoryEntry) => Promise<CrossSessionInbound | undefined> | CrossSessionInbound | undefined;
13
+ /**
14
+ * WS-10 §13: "an unauthenticated route is refused BEFORE the matrix."
15
+ *
16
+ * The router's own default answer is "authenticated exactly when the sender is an object this
17
+ * directory authored" — a canonical address the router itself issued and can still resolve. That is
18
+ * the strongest claim a library can make on its own; a cross-machine or phone-originated route
19
+ * "requires an authenticated Winter transport plus its own policy gate", which is the host's, and
20
+ * this hook is where the host says so.
21
+ */
22
+ authenticatedRoute?: (input: {
23
+ message: GlobalAgentMessage;
24
+ receiver: RuntimeDirectoryEntry;
25
+ senderKnown: boolean;
26
+ }) => Promise<boolean> | boolean;
27
+ /**
28
+ * Called when a held message LEAVES THE MAILBOX WITHOUT BEING DELIVERED — swept by the five-minute
29
+ * dialog expiry, or re-evaluated to `refuse` (review r3, NEW-10).
30
+ *
31
+ * The router supplies it and writes the delivery receipt through, because the ledger is the router's.
32
+ * Without it a sender's durable receipt reads `held` forever for a message that no longer exists —
33
+ * and "held" reads as "waiting for you".
34
+ */
35
+ onHoldTerminal?: (message: GlobalAgentMessage, outcome: DeliveryOutcome) => Promise<void> | void;
36
+ /** An explicit hold is the receiver's own choice to queue rather than to take; the kind is its own. */
37
+ holdKind?: (input: {
38
+ message: GlobalAgentMessage;
39
+ receiver: RuntimeDirectoryEntry;
40
+ decision: CrossSessionInbound;
41
+ }) => "default" | "explicit";
42
+ }
43
+ export interface InboundPolicyDeps extends InboundPolicyHooks {
44
+ store: RuntimeDirectoryStore;
45
+ now(): number;
46
+ /** The receiver's permission class, asked of the runtime that actually holds it (WS-10 §13). */
47
+ receiverClass(receiver: RuntimeDirectoryEntry): Promise<PermissionClassLabel>;
48
+ }
49
+ /** The two receiver-scoped facts a decision needs, read once per sweep (NEW-9). */
50
+ export interface ReceiverFacts {
51
+ explicitSetting: CrossSessionInbound | undefined;
52
+ receiverClass: PermissionClassLabel;
53
+ }
54
+ export type InboundVerdict =
55
+ /** Deliver. `release()` MUST be called with the eventual outcome so the accepted-queue slot is freed. */
56
+ {
57
+ kind: "accept";
58
+ release(outcome: DeliveryOutcome): void;
59
+ } | {
60
+ kind: "settled";
61
+ outcome: DeliveryOutcome;
62
+ };
63
+ export interface InboundPolicy {
64
+ /** Runs the whole of WS-10 §13 for one envelope against one receiver. */
65
+ decide(receiver: RuntimeDirectoryEntry, message: GlobalAgentMessage, senderKnown: boolean): Promise<InboundVerdict>;
66
+ /**
67
+ * The DECISION alone, with no mailbox effect — the matrix, the explicit setting and the
68
+ * authentication gate, and nothing else.
69
+ *
70
+ * WS-10 §14's returning idle notice is the caller that needs this: "inbound policy applies to the
71
+ * returning notice", and a held subscription "delivers a REDUCED-STATUS notice, not treated as
72
+ * ordinary delivered text". A notice is therefore never held in the mailbox and never occupies an
73
+ * accepted slot — it is reduced or dropped — so it needs the verdict without the bookkeeping that
74
+ * ordinary mail earns.
75
+ */
76
+ classify(receiver: RuntimeDirectoryEntry, message: GlobalAgentMessage, senderKnown: boolean): Promise<CrossSessionInbound>;
77
+ /**
78
+ * WS-10 §13's "held messages are re-evaluated when the receiver's mode or settings change".
79
+ *
80
+ * Sweeps the 5-minute expiry first, then re-runs the decision for every DEFAULT-class hold (an
81
+ * explicit hold "persists until a later accept/refusal/session end", so it is never auto-promoted),
82
+ * and hands back the envelopes that are now deliverable. Delivering them is the router's job — this
83
+ * module never calls an adapter.
84
+ */
85
+ reevaluate(receiver: RuntimeDirectoryEntry): Promise<{
86
+ released: GlobalAgentMessage[];
87
+ expired: HeldEntry[];
88
+ }>;
89
+ /**
90
+ * Take an accepted-queue slot WITHOUT re-running the decision (review r1, low 2).
91
+ *
92
+ * A RELEASED HOLD HAS ALREADY BEEN DECIDED, by `reevaluate`, which removed its durable record when
93
+ * it promoted it. Re-running `decide` on the way out would let a second, divergent answer hold a
94
+ * message whose record is gone — losing it — while still costing the host's hooks another call. The
95
+ * cap is still honoured, because that is the half a release genuinely changes: a held message
96
+ * occupies no accepted slot, and a delivered one does.
97
+ */
98
+ reserveAccepted(receiverKey: SerializedRuntimeAddress, messageId: string): InboundVerdict;
99
+ /** WS-10 §13's 5-minute dialog expiry, applied on its own. Returns what it swept. */
100
+ sweepExpired(receiverKey: SerializedRuntimeAddress): Promise<HeldEntry[]>;
101
+ /** What is held for a receiver right now, durably. */
102
+ listHeld(receiverKey: SerializedRuntimeAddress): Promise<HeldMessageRecord[]>;
103
+ readonly caps: {
104
+ held: number;
105
+ accepted: number;
106
+ };
107
+ /** Exposed for the router's own counters; the mailbox is this module's, not the router's. */
108
+ readonly mailbox: Mailbox;
109
+ }
110
+ export declare function createInboundPolicy(deps: InboundPolicyDeps): InboundPolicy;
@@ -0,0 +1,39 @@
1
+ export { createRuntimeDirectory } from "../directory/directory.js";
2
+ export type { DirectorySnapshot, RuntimeDirectoryHandle, RuntimeDirectoryOptions } from "../directory/directory.js";
3
+ export type { RuntimeDirectoryRecoveryHooks, RuntimeDirectoryRetention } from "../directory/recovery.js";
4
+ export { entryToChildLike, entryToListedRuntimeObject, entryToListedRuntimeObjectList, isListableFrom, isLiveStatus, isResolvableFrom, mergeAdapterOwnedFields, owningSessionIdOf, parentAddressOf, sessionAddressOf } from "../directory/entries.js";
5
+ export { createGlobalMessaging, callerAddressOf, deriveMessageId } from "./router.js";
6
+ export type { GlobalMessagingContext, GlobalMessagingHandle, GlobalMessagingOptions, ReplyRequest } from "./router.js";
7
+ export { createWinterMessagingAdapter } from "./winter-adapter.js";
8
+ export type { WinterMessagingAdapter, WinterMessagingAdapterDeps } from "./winter-adapter.js";
9
+ export { createOfficialMessagingAdapter } from "./official-adapter.js";
10
+ export type { OfficialMessagingAdapter, OfficialMessagingAdapterDeps } from "./official-adapter.js";
11
+ export { createDispatchingAdapter } from "./dispatch.js";
12
+ export type { DispatchDeps, RouterMessagingAdapter } from "./dispatch.js";
13
+ export { createInboundPolicy } from "./inbound.js";
14
+ export type { InboundPolicy, InboundPolicyDeps, InboundPolicyHooks, InboundVerdict } from "./inbound.js";
15
+ export { createAttachedSessionRegistry } from "./sessions.js";
16
+ export type { AttachedOfficialSession, AttachedSession, AttachedSessionRegistry, AttachedWinterSession, LiveSessionStatus } from "./sessions.js";
17
+ export { renderAttributedTurn, renderOwnerQualifiedTurn, UnattributableSenderError } from "./attribution.js";
18
+ export { createMessagingToolHandlers, toolUseIdFromExtra, VENDOR_TOOL_USE_ID_META_KEY } from "./handlers.js";
19
+ export type { MessagingToolCaller, MessagingToolHandler, MessagingToolHandlers, MessagingToolResult } from "./handlers.js";
20
+ import { type RuntimeDirectoryHandle, type RuntimeDirectoryOptions } from "../directory/directory.js";
21
+ import type { SeamContext } from "../seams/context.js";
22
+ import { type GlobalMessagingHandle, type GlobalMessagingOptions } from "./router.js";
23
+ export interface RuntimeMessaging {
24
+ directory: RuntimeDirectoryHandle;
25
+ messaging: GlobalMessagingHandle;
26
+ }
27
+ /**
28
+ * The directory and the router, built together and wired to each other.
29
+ *
30
+ * THE ONE WIRE THAT IS EASY TO MISS: the directory's `ChildLike` view delivers through the ROUTER, so
31
+ * a caller that used `ChildLike.steer`/`resume` (the router core never does — see `entryToChildLike`)
32
+ * reaches the child's own runtime adapter rather than a stub. It is a late binding on purpose: the
33
+ * router does not exist yet when the directory is constructed, so the hook closes over a variable
34
+ * that is assigned one line later.
35
+ */
36
+ export declare function createRuntimeMessaging(context: SeamContext, options?: {
37
+ directory?: RuntimeDirectoryOptions;
38
+ messaging?: GlobalMessagingOptions;
39
+ }): RuntimeMessaging;
@@ -0,0 +1,36 @@
1
+ import type { RuntimeDirectory } from "../seams/directory.js";
2
+ import type { RuntimeDirectoryEntry } from "../seams/directory-store.js";
3
+ import type { PermissionClassLabel } from "../seams/messaging-contract.js";
4
+ import type { RouterMessagingAdapter } from "./dispatch.js";
5
+ import type { AttachedOfficialSession, AttachedSessionRegistry } from "./sessions.js";
6
+ export interface OfficialMessagingAdapterDeps {
7
+ directory: RuntimeDirectory;
8
+ sessions: AttachedSessionRegistry<AttachedOfficialSession>;
9
+ /**
10
+ * WS-15 §6.2's "exited official Claude session → explicitly resume by `backendSessionId`,
11
+ * re-establish the adapter, then deliver".
12
+ *
13
+ * INJECTED, because everything that resume needs — WS-14 §2's Options template, §1's launch profile
14
+ * and observed config dir, §3's child environment, §12's credential fetch — belongs to the official
15
+ * ADAPTER lane, and duplicating any of it here would be a second, drifting copy of the branch's
16
+ * hardest-won configuration. This lane owns only the delivery half: it hands back a live handle, and
17
+ * this module pushes into it.
18
+ *
19
+ * Absent: an exited official session is `unavailable`, non-retryable, with the reason naming what is
20
+ * missing — never a silent `not_found`, because the session exists and is resumable by a host that
21
+ * wires this.
22
+ */
23
+ resumeExited?: (entry: RuntimeDirectoryEntry) => Promise<AttachedOfficialSession | undefined>;
24
+ /**
25
+ * The receiver/sender permission class for an official object, when the host tracks one.
26
+ *
27
+ * The pinned runtime exposes no live permission mode to read, so the honest default is `unknown` —
28
+ * which WS-10 §13's matrix handles explicitly rather than by guessing (`prompts × unknown → accept`,
29
+ * `bypasses × unknown → hold`). A host that knows the mode it launched a session with can say so.
30
+ */
31
+ permissionClass?: (entry: RuntimeDirectoryEntry) => Promise<PermissionClassLabel> | PermissionClassLabel;
32
+ }
33
+ export interface OfficialMessagingAdapter extends RouterMessagingAdapter {
34
+ readonly sessions: AttachedSessionRegistry<AttachedOfficialSession>;
35
+ }
36
+ export declare function createOfficialMessagingAdapter(deps: OfficialMessagingAdapterDeps): OfficialMessagingAdapter;
@@ -0,0 +1,101 @@
1
+ import type { NotificationRecord, SendMessageResult } from "@yanlinglabs/winter-agent-sdk/messaging";
2
+ import type { RuntimeDirectoryHandle } from "../directory/directory.js";
3
+ import type { SeamContext } from "../seams/context.js";
4
+ import type { GlobalMessaging, SendMessageRequest } from "../seams/global-messaging.js";
5
+ import type { DeliveryOutcome, GlobalAgentMessage, RuntimeAddress, SerializedRuntimeAddress } from "../seams/messaging-contract.js";
6
+ import { type InboundPolicyHooks } from "./inbound.js";
7
+ import { type OfficialMessagingAdapter, type OfficialMessagingAdapterDeps } from "./official-adapter.js";
8
+ import { type AttachedOfficialSession, type AttachedWinterSession } from "./sessions.js";
9
+ import { type WinterMessagingAdapter, type WinterMessagingAdapterDeps } from "./winter-adapter.js";
10
+ /** The context this factory needs: the seam's, with the directory as the concrete handle. */
11
+ export interface GlobalMessagingContext extends SeamContext {
12
+ directory: RuntimeDirectoryHandle;
13
+ }
14
+ export interface GlobalMessagingOptions extends InboundPolicyHooks {
15
+ now?: () => number;
16
+ /** Passed to the Winter adapter this factory builds (cold-resume options, child resume context). */
17
+ winter?: Omit<WinterMessagingAdapterDeps, "peers" | "directory" | "sessions">;
18
+ /** Passed to the official adapter this factory builds (the resume collaborator, the class hook). */
19
+ official?: Omit<OfficialMessagingAdapterDeps, "directory" | "sessions">;
20
+ /**
21
+ * R-7b-4's one behavioural seam, forwarded to the core: how a THROW out of an adapter is classified.
22
+ *
23
+ * ABSENT MEANS EVERYTHING IS UNCERTAIN, which is the conservative reading and the core's own default
24
+ * — `refused` asserts the effect did NOT happen, and from this side "the call failed" and "the
25
+ * effect happened and then the call failed" are indistinguishable (WS-10 §12).
26
+ */
27
+ classifyDeliveryError?: (error: unknown) => "refused" | "uncertain";
28
+ }
29
+ export interface ReplyRequest {
30
+ /** The envelope being replied to. Its `to` becomes the reply's `from`, and its `from` the target. */
31
+ original: GlobalAgentMessage;
32
+ body: string;
33
+ summary?: string;
34
+ }
35
+ /** The seam, plus the doors a host and this package's own tool handlers need. */
36
+ export interface GlobalMessagingHandle extends GlobalMessaging {
37
+ readonly winterAdapter: WinterMessagingAdapter;
38
+ readonly officialAdapter: OfficialMessagingAdapter;
39
+ /** Register a live Winter session AND bridge its idle notices. Returns the detach function. */
40
+ attachWinterSession(address: SerializedRuntimeAddress, handle: AttachedWinterSession): () => void;
41
+ attachOfficialSession(address: SerializedRuntimeAddress, handle: AttachedOfficialSession): () => void;
42
+ /**
43
+ * `send()`, keeping the shared core's SUPPLEMENTARY fact about a combined call.
44
+ *
45
+ * The seam pins `send(): Promise<DeliveryOutcome>`, and the delivery outcome is rightly primary —
46
+ * but a call that carried both a message AND `notify_when_idle` has a second, separate result (was
47
+ * the subscription honoured?), and WS-10 §10.1's own result text allows for it. The core reports it
48
+ * beside the outcome rather than inventing an eleventh status; this door is where that survives, and
49
+ * `send()` is exactly `(await sendDetailed(...)).outcome`.
50
+ */
51
+ sendDetailed(request: SendMessageRequest): Promise<SendMessageResult>;
52
+ /** WS-10 §13's re-evaluation door: sweep, re-decide, deliver what is now acceptable. */
53
+ releaseHeld(receiver: SerializedRuntimeAddress): Promise<DeliveryOutcome[]>;
54
+ /** Reply routing — the one path on which `hopCount` grows, and therefore the one the bound binds. */
55
+ reply(request: ReplyRequest): Promise<DeliveryOutcome>;
56
+ /** WS-06 §3.6's `ReadNotifications` page for one session's own queue. */
57
+ readNotifications(sessionId: string): {
58
+ notifications: NotificationRecord[];
59
+ remaining: number;
60
+ };
61
+ /**
62
+ * "This target has gone idle" — WS-10 §14's firing edge, from whatever noticed it.
63
+ *
64
+ * Called by the Winter facet bridge for every live notice and every drained one; a host that detects
65
+ * idleness some other way calls it too. Returns how many notices were pushed.
66
+ */
67
+ noteIdle(target: SerializedRuntimeAddress, notice?: {
68
+ notificationId?: string;
69
+ content?: string;
70
+ }): Promise<number>;
71
+ /** The caps and windows this router enforces, so a host can render them without re-deriving them. */
72
+ readonly bounds: {
73
+ heldCap: number;
74
+ acceptedCap: number;
75
+ holdExpiryMs: number;
76
+ idleSubscriptionMs: number;
77
+ messageTtlMs: number;
78
+ };
79
+ }
80
+ /**
81
+ * WS-10 §12's message id: "derived/persisted from the sender session plus tool-call ID", so that "a
82
+ * retry with the same ID returns the stored outcome".
83
+ *
84
+ * DERIVED, NOT COUNTED, and that is the whole point: the shared core allocates `msg-1`, `msg-2` from
85
+ * an in-process counter, which is stable within one process and COLLIDES across a restart — two
86
+ * different messages would share an id, and the second would be answered with the first's stored
87
+ * outcome. Both components are percent-encoded so the pair can never be ambiguous.
88
+ */
89
+ export declare function deriveMessageId(senderSessionId: string, toolUseId: string): string;
90
+ export declare function createGlobalMessaging(context: GlobalMessagingContext, options?: GlobalMessagingOptions): GlobalMessagingHandle;
91
+ /**
92
+ * The canonical address a `SendMessage`/`ListAgents` handler runs AS (see `handlers.ts`).
93
+ *
94
+ * The subpath's own `callerAddress` builds `agent:<parent>:<child>` for a child caller and
95
+ * `session:<id>` for a top-level one — the same rule the resolution fence reads, so the handler and
96
+ * the router can never disagree about who is asking.
97
+ */
98
+ export declare function callerAddressOf(caller: {
99
+ sessionId: string;
100
+ agentId?: string;
101
+ }): RuntimeAddress;
@@ -0,0 +1,49 @@
1
+ import type { SessionMessagingFacet } from "@yanlinglabs/winter-agent-sdk";
2
+ import type { ListedRuntimeObject, SerializedRuntimeAddress } from "../seams/messaging-contract.js";
3
+ /** The live status a handle can report, when it can. Same vocabulary as the directory's own. */
4
+ export type LiveSessionStatus = ListedRuntimeObject["status"];
5
+ export interface AttachedSession {
6
+ /**
7
+ * Push one turn into this session's input stream.
8
+ *
9
+ * The text is ALREADY RENDERED (attributed, escaped) by the time it gets here — a handle is a pipe,
10
+ * not a policy. Absent on a Winter handle that only carries a facet: the facet's own `deliver` is
11
+ * the runtime-side push, and it renders on the far side.
12
+ */
13
+ push?(text: string): void | Promise<void>;
14
+ /** The live status, when the host tracks one. Absent falls back to the durable row's `status`. */
15
+ status?(): LiveSessionStatus;
16
+ }
17
+ export interface AttachedWinterSession extends AttachedSession {
18
+ /**
19
+ * `Query.messaging` (R-7b-4). The ONLY door into a spawned Winter session's children — a child
20
+ * engine has no facet surface of its own, so `steer_child`/`resume_child` on the PARENT's facet is
21
+ * how a child is reached, never a session address (Task 0 fix r2, concern 4).
22
+ */
23
+ readonly messaging?: SessionMessagingFacet;
24
+ }
25
+ export interface AttachedOfficialSession extends AttachedSession {
26
+ /**
27
+ * REQUIRED on this branch, because there is no alternative. The pinned official SDK exposes no
28
+ * messaging surface of any kind (its `Query` declares none), so an input-stream push is the whole
29
+ * mechanism — for the session itself and, owner-qualified, for its children.
30
+ */
31
+ push(text: string): void | Promise<void>;
32
+ }
33
+ export interface AttachedSessionRegistry<T extends AttachedSession> {
34
+ /** Register a handle for a canonical address. Returns the detach function. */
35
+ attach(address: SerializedRuntimeAddress, handle: T): () => void;
36
+ detach(address: SerializedRuntimeAddress): void;
37
+ get(address: SerializedRuntimeAddress): T | undefined;
38
+ addresses(): SerializedRuntimeAddress[];
39
+ }
40
+ /**
41
+ * A registry, and deliberately nothing more: no liveness inference, no reaping, no ordering.
42
+ *
43
+ * ATTACHING IS THE HOST'S CALL, not something this package can discover. `RuntimeSdk.query()` is
44
+ * where a session is created, and until that door routes to both branches (spine-owned, Task 6) a
45
+ * host attaches what it launched. `attach` returning its own detach — rather than the caller
46
+ * remembering the address — is what keeps a handle from outliving the session in the one case that
47
+ * matters, a re-launch of the same address: the OLD detach then removes only the handle it added.
48
+ */
49
+ export declare function createAttachedSessionRegistry<T extends AttachedSession>(): AttachedSessionRegistry<T>;
@@ -0,0 +1,47 @@
1
+ import type { Options } from "@yanlinglabs/winter-agent-sdk";
2
+ import type { RuntimeSdkPeers } from "../sdk.js";
3
+ import type { RuntimeDirectory } from "../seams/directory.js";
4
+ import type { RuntimeDirectoryEntry } from "../seams/directory-store.js";
5
+ import type { PermissionClassLabel } from "../seams/messaging-contract.js";
6
+ import type { RouterMessagingAdapter } from "./dispatch.js";
7
+ import type { ChildSelectionInput } from "../selection/runtime-selection.js";
8
+ import type { AttachedSessionRegistry, AttachedWinterSession } from "./sessions.js";
9
+ export interface WinterMessagingAdapterDeps {
10
+ peers: RuntimeSdkPeers;
11
+ directory: RuntimeDirectory;
12
+ sessions: AttachedSessionRegistry<AttachedWinterSession>;
13
+ /**
14
+ * The `Options` a COLD RESUME is opened with, beside the `resume` the adapter supplies itself.
15
+ *
16
+ * A resume needs a cwd, a model, a permission mode — session facts the messaging layer does not
17
+ * hold and must not invent. Absent means an empty options object, which is a legitimate resume (the
18
+ * transcript carries the session's own configuration) and is what the hermetic tests use.
19
+ */
20
+ resumeOptions?: (entry: RuntimeDirectoryEntry) => Options | Promise<Options>;
21
+ /**
22
+ * WS-13c §8 / WS-10's Phase 6.6 amendment: the catalogue a RESUMED CHILD re-resolves against.
23
+ *
24
+ * `resumeChildSelection` answers "is the row this child is RECORDED on still servable?" — and a
25
+ * refusal is `{ status: "unavailable", retryable: false, reason: "child-provider-unavailable: …" }`
26
+ * WITH NO GENERATION STARTED, which is why the check runs before the parent's facet is touched.
27
+ * Absent: nothing re-resolves, and the child's own persisted record stands — which is the correct
28
+ * fallback rather than a silent skip, because WS-13c §8 makes that record authoritative anyway; the
29
+ * check only detects DRIFT, and a host with no catalogue has nothing to detect it against.
30
+ */
31
+ childResumeContext?: (entry: RuntimeDirectoryEntry) => Omit<ChildSelectionInput, "slot" | "model" | "provider"> | undefined;
32
+ /**
33
+ * The permission class of a session this process holds no FACET for — the host's own declaration.
34
+ *
35
+ * A live facet answers from the session's live permission mode and always wins. This is the fallback
36
+ * for a session the router drives through a plain input-stream writer, or one it has a record of and
37
+ * no handle at all: without it every such receiver is `unknown`, and since review r1's D2 made an
38
+ * unknown receiver FAIL CLOSED (its mail is held until the class is known), a host would have no way
39
+ * to say what it launched the session with.
40
+ */
41
+ permissionClass?: (entry: RuntimeDirectoryEntry) => Promise<PermissionClassLabel> | PermissionClassLabel;
42
+ }
43
+ /** The adapter plus the one thing the router attaches to it: the live-session registry. */
44
+ export interface WinterMessagingAdapter extends RouterMessagingAdapter {
45
+ readonly sessions: AttachedSessionRegistry<AttachedWinterSession>;
46
+ }
47
+ export declare function createWinterMessagingAdapter(deps: WinterMessagingAdapterDeps): WinterMessagingAdapter;
@@ -0,0 +1,76 @@
1
+ /** WS-10 §10.1's bounds. `to`'s own limit is the subpath's; this is the copy the SCHEMA advertises. */
2
+ export declare const SEND_MESSAGE_TO_MAX = 300;
3
+ export declare const SEND_MESSAGE_SUMMARY_MAX = 200;
4
+ /** WS-10 §10.2's bound on both reserved `ListAgents` fields. */
5
+ export declare const LIST_AGENTS_FIELD_MAX = 256;
6
+ export declare const NATIVE_SEND_MESSAGE_SCHEMA: {
7
+ readonly type: "object";
8
+ readonly properties: {
9
+ readonly to: {
10
+ readonly type: "string";
11
+ readonly maxLength: 300;
12
+ readonly description: "no newline, no \"*\" broadcast";
13
+ };
14
+ readonly message: {
15
+ readonly type: "string";
16
+ readonly description: "required; defaults \"\" for pure idle subscription";
17
+ };
18
+ readonly summary: {
19
+ readonly type: "string";
20
+ readonly maxLength: 200;
21
+ };
22
+ readonly notify_when_idle: {
23
+ readonly type: "boolean";
24
+ readonly description: "one-shot; main conversation -> same-machine session only";
25
+ };
26
+ };
27
+ readonly required: readonly ["to", "message"];
28
+ };
29
+ export declare const NATIVE_LIST_AGENTS_SCHEMA: {
30
+ readonly type: "object";
31
+ readonly properties: {
32
+ readonly channel: {
33
+ readonly type: "string";
34
+ readonly maxLength: 256;
35
+ readonly description: "reserved";
36
+ };
37
+ readonly q: {
38
+ readonly type: "string";
39
+ readonly maxLength: 256;
40
+ readonly description: "reserved";
41
+ };
42
+ };
43
+ };
44
+ /** WS-10 §10.2: "`ListAgents` output is EXACTLY `{ listing: string }`." */
45
+ export declare const NATIVE_LIST_AGENTS_OUTPUT_SCHEMA: {
46
+ readonly type: "object";
47
+ readonly properties: {
48
+ readonly listing: {
49
+ readonly type: "string";
50
+ };
51
+ };
52
+ readonly required: readonly ["listing"];
53
+ };
54
+ /** The native `SendMessage` arguments (WS-10 §10.1), after validation. */
55
+ export interface NativeSendMessageArgs {
56
+ to: string;
57
+ message: string;
58
+ summary?: string;
59
+ notify_when_idle?: boolean;
60
+ }
61
+ /** The native `ListAgents` arguments (WS-10 §10.2). Both fields are reserved in the pinned build. */
62
+ export interface NativeListAgentsArgs {
63
+ channel?: string;
64
+ q?: string;
65
+ }
66
+ export type NativeArgsResult<T> = {
67
+ ok: true;
68
+ args: T;
69
+ } | {
70
+ ok: false;
71
+ reason: string;
72
+ };
73
+ /** Accepts the native `SendMessage` arguments EXACTLY — no more, no less. */
74
+ export declare function acceptNativeSendMessageArgs(input: unknown): NativeArgsResult<NativeSendMessageArgs>;
75
+ /** The same treatment for `ListAgents`: two reserved optional fields, both capped, nothing else. */
76
+ export declare function acceptNativeListAgentsArgs(input: unknown): NativeArgsResult<NativeListAgentsArgs>;