@yanlinglabs/winter-runtime-sdk 0.0.1 → 0.0.3

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.
@@ -2,7 +2,7 @@ export { createRuntimeDirectory } from "../directory/directory.js";
2
2
  export type { DirectorySnapshot, RuntimeDirectoryHandle, RuntimeDirectoryOptions } from "../directory/directory.js";
3
3
  export type { RuntimeDirectoryRecoveryHooks, RuntimeDirectoryRetention } from "../directory/recovery.js";
4
4
  export { entryToChildLike, entryToListedRuntimeObject, entryToListedRuntimeObjectList, isListableFrom, isLiveStatus, isResolvableFrom, mergeAdapterOwnedFields, owningSessionIdOf, parentAddressOf, sessionAddressOf } from "../directory/entries.js";
5
- export { createGlobalMessaging, callerAddressOf, deriveMessageId } from "./router.js";
5
+ export { createGlobalMessaging, deriveMessageId } from "./router.js";
6
6
  export type { GlobalMessagingContext, GlobalMessagingHandle, GlobalMessagingOptions, ReplyRequest } from "./router.js";
7
7
  export { createWinterMessagingAdapter } from "./winter-adapter.js";
8
8
  export type { WinterMessagingAdapter, WinterMessagingAdapterDeps } from "./winter-adapter.js";
@@ -15,8 +15,6 @@ export type { InboundPolicy, InboundPolicyDeps, InboundPolicyHooks, InboundVerdi
15
15
  export { createAttachedSessionRegistry } from "./sessions.js";
16
16
  export type { AttachedOfficialSession, AttachedSession, AttachedSessionRegistry, AttachedWinterSession, LiveSessionStatus } from "./sessions.js";
17
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
18
  import { type RuntimeDirectoryHandle, type RuntimeDirectoryOptions } from "../directory/directory.js";
21
19
  import type { SeamContext } from "../seams/context.js";
22
20
  import { type GlobalMessagingHandle, type GlobalMessagingOptions } from "./router.js";
@@ -2,7 +2,7 @@ import type { NotificationRecord, SendMessageResult } from "@yanlinglabs/winter-
2
2
  import type { RuntimeDirectoryHandle } from "../directory/directory.js";
3
3
  import type { SeamContext } from "../seams/context.js";
4
4
  import type { GlobalMessaging, SendMessageRequest } from "../seams/global-messaging.js";
5
- import type { DeliveryOutcome, GlobalAgentMessage, RuntimeAddress, SerializedRuntimeAddress } from "../seams/messaging-contract.js";
5
+ import type { DeliveryOutcome, GlobalAgentMessage, SerializedRuntimeAddress } from "../seams/messaging-contract.js";
6
6
  import { type InboundPolicyHooks } from "./inbound.js";
7
7
  import { type OfficialMessagingAdapter, type OfficialMessagingAdapterDeps } from "./official-adapter.js";
8
8
  import { type AttachedOfficialSession, type AttachedWinterSession } from "./sessions.js";
@@ -88,14 +88,3 @@ export interface GlobalMessagingHandle extends GlobalMessaging {
88
88
  */
89
89
  export declare function deriveMessageId(senderSessionId: string, toolUseId: string): string;
90
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;
@@ -6,14 +6,18 @@ export declare const ALIASED_BUILTINS: readonly [{
6
6
  }, {
7
7
  readonly builtin: "ListAgents";
8
8
  readonly tool: "list_agents";
9
+ }, {
10
+ readonly builtin: "ReadNotifications";
11
+ readonly tool: "read_notifications";
12
+ }, {
13
+ readonly builtin: "advisor";
14
+ readonly tool: "advisor";
9
15
  }];
10
16
  export type AliasedBuiltin = (typeof ALIASED_BUILTINS)[number]["builtin"];
11
17
  /** §2's `toolAliases` value: `{ SendMessage: "mcp__<server>__send_message", … }`. */
12
18
  export declare function officialToolAliases(brand: Pick<BrandProfile, "mcpServerName">): Record<string, string>;
13
19
  /** The canonical name one aliased built-in resolves to (single hop — never a chain). */
14
20
  export declare function aliasTargetFor(builtin: AliasedBuiltin, brand: Pick<BrandProfile, "mcpServerName">): string;
15
- export { LIST_AGENTS_FIELD_MAX, NATIVE_LIST_AGENTS_OUTPUT_SCHEMA, NATIVE_LIST_AGENTS_SCHEMA, NATIVE_SEND_MESSAGE_SCHEMA, SEND_MESSAGE_SUMMARY_MAX, SEND_MESSAGE_TO_MAX, acceptNativeListAgentsArgs, acceptNativeSendMessageArgs, } from "../native-args.js";
16
- export type { NativeArgsResult, NativeListAgentsArgs, NativeSendMessageArgs } from "../native-args.js";
17
21
  /**
18
22
  * How the canonical duplicates are exposed (§7's "SHOULD be deferred/hidden").
19
23
  *
@@ -17,8 +17,8 @@ export { EXECUTION_INDIRECTION_ENV_NAMES, EXECUTION_INDIRECTION_ENV_PREFIXES, is
17
17
  export type { EnvAllowlistSnapshot, OfficialEnvInput, OfficialEnvPolicy } from "./env-allowlist.js";
18
18
  export { OFFICIAL_ERROR_CODES, OfficialStdoutUnterminatedError, OfficialAgentResultError, OfficialApiError, OfficialBranchError, OfficialConfigurationError, OfficialConnectionError, OfficialExecutableNotFoundError, OfficialInterruptedError, OfficialInvalidResumeError, OfficialKilledError, OfficialMcpError, OfficialNonzeroExitError, OfficialPermissionDeniedError, OfficialProtocolError, OfficialSessionNotFoundError, OfficialSessionStoreError, OfficialToolError, isOfficialBranchError, } from "./errors.js";
19
19
  export type { OfficialCrashClass, OfficialErrorCode, WinterErrorClassName } from "./errors.js";
20
- export { OFFICIAL_MATERIALIZATION_DROPS, assertNoAdvisor, canonicalToolNames, materializeOfficialMcpServer, messagingToolDescriptors, officialMcpServers, winterMcpServerDescriptor } from "./mcp-descriptors.js";
21
- export type { InputShapeFactory, JsonSchemaObject, MessagingHandlers, OfficialMcpModule, WinterMcpHandler, WinterMcpServerDescriptor, WinterMcpToolDescriptor, WinterMcpToolResult } from "./mcp-descriptors.js";
20
+ export { OFFICIAL_MATERIALIZATION_DROPS, canonicalToolNames, materializeOfficialMcpServer, officialMcpServers, winterMcpServerDescriptor } from "./mcp-descriptors.js";
21
+ export type { InputShapeFactory, JsonSchemaObject, OfficialMcpModule, WinterMcpHandler, WinterMcpServerDescriptor, WinterMcpToolDescriptor, WinterMcpToolResult } from "./mcp-descriptors.js";
22
22
  export { AUTO_MEMORY_LOAD_CAP, DEFAULT_EXCLUDE_DYNAMIC_SECTIONS, PINNED_SYSTEM_PROMPT_PRESET, assertOptionsInvariants, brandedFlagSettings, buildOfficialOptions, captureOptions, mergeHooks } from "./options-template.js";
23
23
  export type { OptionsTemplatePolicy } from "./options-template.js";
24
24
  export { SPOOL_SEGMENTS, classifyLocalWriteRoot, officialSpoolRoot, validateObservedConfigDir, vendorTempRootReport } from "./spool.js";
@@ -1,10 +1,15 @@
1
- import { type BrandProfile } from "@yanlinglabs/winter-agent-sdk";
2
- /** A JSON-Schema object, in the subset the descriptors use. */
3
- export interface JsonSchemaObject {
4
- type: "object";
5
- properties: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
6
- required?: readonly string[];
7
- }
1
+ import { type BrandProfile, type McpSdkServerConfigWithInstance } from "@yanlinglabs/winter-agent-sdk";
2
+ import { type AdvisorToolDeps, type JsonSchemaObject, type MessagingToolPort, type WinterToolCaller } from "@yanlinglabs/winter-agent-sdk/tools";
3
+ import { RuntimeLaunchInputError } from "../errors.js";
4
+ /**
5
+ * ONE `JsonSchemaObject`, AND IT IS THE SDK'S (interim review I-3).
6
+ *
7
+ * The router used to declare its own — `properties` required, no `additionalProperties` — and every
8
+ * composition of an SDK schema into a descriptor went through `as unknown as JsonSchemaObject`. Two
9
+ * types for one contract is how the two copies of the native schemas drifted in the first place
10
+ * (barrel-exports.test.ts's own header), so the type travels with the definitions it describes.
11
+ */
12
+ export type { JsonSchemaObject };
8
13
  /** What a handler answers with — the MCP content shape, identical on both branches. */
9
14
  export interface WinterMcpToolResult {
10
15
  content: Array<{
@@ -16,20 +21,20 @@ export interface WinterMcpToolResult {
16
21
  /**
17
22
  * A tool handler, and the vendor's own second argument (item 15).
18
23
  *
19
- * `extra` IS FORWARDED, NOT DROPPED but what it CARRIES is a measurement, not an assumption. The
20
- * carry that produced this change asked for it so the official branch could derive WS-10 §12's retry
21
- * key (the (session, tool-call id) pair a retry must allocate the SAME message id from), and the
22
- * whole-branch review was right to say that must be checked first: the in-process server's `extra` is
23
- * the MCP REQUEST CONTEXT — the JSON-RPC request id and `_meta` — which is not the model's
24
- * `tool_use_id`. `test/official/runtime-aliases.test.ts` records what the pinned runtime actually
25
- * puts there; see that test and this module's own note below for the answer.
26
- *
27
- * Forwarding it is worth doing either way: it is the only channel the vendor gives a tool for request
28
- * context, dropping it is unrecoverable at the handler, and a handler that does not want it simply
29
- * declares one parameter.
24
+ * `extra` IS FORWARDED, NOT DROPPED, and what it carries is measured rather than assumed: on 0.3.250
25
+ * `extra._meta["claudecode/toolUseId"]` is the id the model emitted, which is the half of WS-10 §12's
26
+ * retry key the official branch could not otherwise have. The SDK's `toolUseIdFromExtra` reads it (and
27
+ * falls back to the bound caller's id), so the router forwards the argument and decides nothing.
30
28
  */
31
29
  export type WinterMcpHandler = (args: unknown, extra?: unknown) => Promise<WinterMcpToolResult>;
32
- /** One tool on the standing server. Everything a branch needs to register it, and nothing branch-specific. */
30
+ /**
31
+ * One tool on the standing server — the ROUTER'S OWN COMPOSITION TYPE, and nothing more (R-8-1).
32
+ *
33
+ * It is a `WinterToolDefinition` (the SDK's: bare name, description, schemas, annotations, permission
34
+ * class) plus the two things only a HOST can supply: the handler that runs it and how this branch
35
+ * exposes it. The router declares no tool of its own — every field but those two is read off the
36
+ * definition the SDK owns.
37
+ */
33
38
  export interface WinterMcpToolDescriptor {
34
39
  /** The bare tool name (`send_message`); the canonical name is derived from it and the brand. */
35
40
  tool: string;
@@ -49,43 +54,51 @@ export interface WinterMcpServerDescriptor {
49
54
  version: string;
50
55
  tools: readonly WinterMcpToolDescriptor[];
51
56
  }
52
- /** The two messaging handlers §7's aliases resolve to. Their implementations are the router's. */
53
- export interface MessagingHandlers {
54
- sendMessage: WinterMcpHandler;
55
- listAgents: WinterMcpHandler;
56
- }
57
57
  /**
58
- * The messaging tools, with the NATIVE schemas mirrored (§7: "handlers MUST accept the native
59
- * argument schemas exactly").
58
+ * THE STANDING SERVER, COMPOSED FROM THE SDK'S DEFINITIONS AND THE SDK'S HANDLER FACTORIES (R-8-1).
60
59
  *
61
- * `deferred` on both: they are the canonical twins of aliased built-ins, and §7 wants the model to
62
- * see one `SendMessage`, not two.
63
- */
64
- export declare function messagingToolDescriptors(handlers: MessagingHandlers): readonly WinterMcpToolDescriptor[];
65
- /**
66
- * §11's refusal: no advisor on this server, on this branch.
60
+ * THE ROUTER OWNS NO TOOL. It used to declare `send_message` and `list_agents` here their
61
+ * descriptions, their native schemas, their acceptors — beside a second copy of the same declarations
62
+ * inside the Winter runtime, and the two had already drifted on what `to` may contain and on what an
63
+ * over-long `summary` does. `@yanlinglabs/winter-agent-sdk/tools` is now the single declaration, and
64
+ * this function is what BINDS it: `WINTER_DEFAULT_TOOL_DEFINITIONS` × the SDK's handler factories,
65
+ * under the brand's server name.
67
66
  *
68
- * Thrown rather than filtered. A silent filter would leave a host believing its advisor was
69
- * registered and wondering why the model never calls it; the whole point of D29's split is that each
70
- * branch's advisor has a DIFFERENT backing, and a host that tried to register one here has a
71
- * misunderstanding worth surfacing.
72
- */
73
- export declare function assertNoAdvisor(tools: readonly WinterMcpToolDescriptor[], branchLabel: string): void;
74
- /**
75
- * Builds the standing server descriptor for a session.
67
+ * THE PORT IS PASSED STRAIGHT THROUGH (ruling P-3). `GlobalMessagingHandle` satisfies
68
+ * `MessagingToolPort` structurally `sendDetailed`, `listReachable({from})`, `readNotifications(id)`
69
+ * so the router needs no adapter of its own and `messagingToolPortFromRuntimeDeps` (which adapts the
70
+ * RUNTIME's deps) is never used here.
76
71
  *
77
- * The capability plugins (browser, computer, office) are passed IN rather than declared here: WS-06
78
- * owns their exact names and schemas, and a copy of them in the router would be a second declaration
79
- * to drift. What the router owns is that they are registered under the same canonical names, with the
80
- * same identity, on both branches which is what `canonicalToolNames` below makes checkable.
72
+ * THE ADVISOR IS ALWAYS REGISTERED (interim review I-5). The Winter runtime always advertises
73
+ * `advisor`; gating the official branch's copy on a host option would make the two legs' advertised
74
+ * sets differ by a setting that changes nothing on the other leg. `advisor.resolveReviewer` supplies
75
+ * the reviewer its default answers `undefined`, which is WS-06 §4's ordinary tool error, not a throw.
81
76
  */
82
77
  export declare function winterMcpServerDescriptor(args: {
83
78
  brand: Pick<BrandProfile, "mcpServerName">;
84
- messaging: MessagingHandlers;
79
+ port: MessagingToolPort;
80
+ caller: WinterToolCaller | (() => WinterToolCaller);
81
+ advisor: Omit<AdvisorToolDeps, "resolveReviewer"> & Partial<Pick<AdvisorToolDeps, "resolveReviewer">>;
85
82
  capabilities?: readonly WinterMcpToolDescriptor[];
86
83
  version?: string;
87
- branchLabel: string;
88
84
  }): WinterMcpServerDescriptor;
85
+ /** One capability server, as a descriptor the official branch can register. */
86
+ export declare function capabilityServerDescriptor(server: McpSdkServerConfigWithInstance, version?: string): WinterMcpServerDescriptor;
87
+ /**
88
+ * The ONE refusal both legs raise when a caller's own `mcpServers` key names a forwarded capability.
89
+ *
90
+ * IT IS SHARED BECAUSE THE TWO LEGS MUST SAY THE SAME THING (review r1). The Winter leg refuses in
91
+ * `forwardableOptions`, the official leg before its launch; the same mistake producing a typed error
92
+ * on one leg and a silent per-key override on the other would leave the two branches advertising
93
+ * different tools under one name, with nothing to read about it. Two copies of this sentence is how
94
+ * that starts being true again, so there is one.
95
+ */
96
+ export declare function capabilityNameCollisionError(args: {
97
+ field: string;
98
+ name: string;
99
+ }): RuntimeLaunchInputError;
100
+ /** Every capability server, in the order the host declared them. */
101
+ export declare function capabilityServerDescriptors(servers: readonly McpSdkServerConfigWithInstance[]): readonly WinterMcpServerDescriptor[];
89
102
  /** The canonical names a descriptor registers — the identity both branches must agree on. */
90
103
  export declare function canonicalToolNames(descriptor: WinterMcpServerDescriptor, brand: Pick<BrandProfile, "mcpServerName">): readonly string[];
91
104
  /** The subset of the injected official module this needs. Duck-typed: the seam declares only `query`. */
@@ -119,7 +132,6 @@ export declare function materializeOfficialMcpServer(args: {
119
132
  toInputShape: InputShapeFactory;
120
133
  branchLabel: string;
121
134
  }): unknown;
122
- /** `Options.mcpServers` for this branch: one entry, keyed by the brand's own server name. */
123
135
  /** The descriptor fields the official branch's registration cannot carry (review r1, n3). */
124
136
  export declare const OFFICIAL_MATERIALIZATION_DROPS: readonly string[];
125
137
  export declare function officialMcpServers(args: {
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BrandProfile, Options, Query, SessionKey } from "@yanlinglabs/winter-agent-sdk";
1
+ import type { BrandProfile, McpSdkServerConfigWithInstance, Options, Query, SessionKey } from "@yanlinglabs/winter-agent-sdk";
2
2
  import { type RouterOfficialInput, type RouterOfficialPolicy, type RouterQuery } from "./door.js";
3
3
  import type { SeamContextWithDirectory } from "./seams/context.js";
4
4
  import type { OfficialSdkModule } from "./seams/official-sdk-shapes.js";
@@ -11,6 +11,8 @@ import type { RuntimeDirectory } from "./seams/directory.js";
11
11
  import type { RuntimeDirectoryStore } from "./seams/directory-store.js";
12
12
  import type { GlobalMessagingOptions, RuntimeDirectoryOptions } from "./messaging/index.js";
13
13
  import type { HandoffBarrierDeps } from "./store/index.js";
14
+ import type { ReviewerResolver } from "@yanlinglabs/winter-agent-sdk/tools";
15
+ import { type InputShapeFactory } from "./official/mcp-descriptors.js";
14
16
  import type { RuntimeKind, RuntimeSelection, SelectionInput } from "./selection/runtime-selection.js";
15
17
  import { type VersionMatrixReport } from "./version-matrix.js";
16
18
  /**
@@ -38,6 +40,21 @@ export interface RuntimeSdkPeers {
38
40
  }
39
41
  export interface RuntimeSdkOptions {
40
42
  peers: RuntimeSdkPeers;
43
+ /**
44
+ * Host-declared peer package versions (D19a/R2, WS-02 §7.1) — THE ONLY DOOR inside a compiled
45
+ * binary. The version matrix's probe 2 (`resolved-manifest`) resolves a peer's `package.json` via
46
+ * `createRequire(...).resolve()`, which cannot see outside a compiled binary's own bundle
47
+ * (`file:///$bunfs/...`); a host that self-spawns its own compiled artifact and whose injected peer
48
+ * exports no version identity of its own (probe 1) has NOTHING for the matrix to read unless it
49
+ * declares one here, from its own vendored `VERSIONS.json`. Declaring a version wins over both
50
+ * probes when it parses — it does not bypass the matrix's range/exact-pin checks, only replaces how
51
+ * the identity was DISCOVERED; an unparseable declared value falls through to probe 1 rather than
52
+ * refusing on its own. Absent, behaviour is unchanged: probes 1 and 2, in that order.
53
+ */
54
+ peerVersions?: {
55
+ winterAgentSdk?: string;
56
+ claudeAgentSdk?: string;
57
+ };
41
58
  /** R-7b-2's seam; default = in-memory (which is also what every hermetic test uses). */
42
59
  directoryStore?: RuntimeDirectoryStore;
43
60
  /** Host-provided credential reads (WS-14 §12) — never disk, never this package's own keychain. */
@@ -57,6 +74,61 @@ export interface RuntimeSdkOptions {
57
74
  * fill in when neither does.
58
75
  */
59
76
  brand?: Partial<BrandProfile>;
77
+ /**
78
+ * WS-09 §1.3's capability servers, as the Winter SDK's own in-process shape — FORWARDED ON BOTH
79
+ * LEGS, never rewritten into anything else (R-8, and the user's tool-ownership ruling R-8-1).
80
+ *
81
+ * THE ROUTER OWNS NO TOOL. The daemon owns the capability tools — computer, browser, office — and
82
+ * hands them over as MCP SERVERS; this is the door they come through, and the router's whole job is
83
+ * to put the same servers in front of both runtimes. The Winter leg receives each entry BY
84
+ * REFERENCE under its own `name`, merged into `Options.mcpServers`. The official leg cannot take the
85
+ * object itself (its runtime registers in-process servers through its own constructor, over its own
86
+ * validator's schema shape), so the same tools are REGISTERED there from the server's own
87
+ * declaration — see `capabilityServerDescriptor`. Identical names, identical schemas, identical
88
+ * handlers, two registrations.
89
+ *
90
+ * THE BRAND'S OWN SERVER NAME IS RESERVED. `brand.mcpServerName` is the standing server's key on the
91
+ * official branch (§7's aliases resolve to `mcp__<mcpServerName>__<tool>`), so a capability server
92
+ * that claimed it would shadow the messaging tools on one branch and not the other. That is a typed
93
+ * refusal at construction, not a silent overwrite.
94
+ *
95
+ * WITHOUT `toInputShape` THIS IS A WINTER-LEG-ONLY DOOR: the official leg refuses rather than open a
96
+ * session whose capability tools exist on one branch only.
97
+ *
98
+ * A CALLER'S OWN `Options.mcpServers` REACHES THE WINTER LEG ONLY, and always has: the official leg
99
+ * builds its servers from this constructor and `runtime.official.mcpServers`, and never reads the
100
+ * pinned `Options` field at all. A caller key that collides with a capability name is therefore
101
+ * refused for BOTH legs at the door (interim review I-6) rather than refused on one and silently
102
+ * dropped on the other.
103
+ */
104
+ capabilities?: readonly McpSdkServerConfigWithInstance[];
105
+ /**
106
+ * The host's JSON-Schema → validator-shape bridge, for the official branch's in-process servers.
107
+ *
108
+ * INJECTED BECAUSE THE ROUTER DEPENDS ON NO VALIDATOR (see `official/mcp-descriptors.ts`'s header):
109
+ * the official runtime's own server constructor takes schemas in its peer validator's shape, and a
110
+ * package whose entire design is "two injected peers and nothing else" will not grow a third
111
+ * dependency to produce them. A host that has the official SDK already has that validator, and
112
+ * writes this in one line.
113
+ */
114
+ toInputShape?: InputShapeFactory;
115
+ /**
116
+ * The REVIEWER behind the standing advisor (R-8-1(3), ruling P-6; interim review I-5).
117
+ *
118
+ * The advisor tool itself is not optional and is not configured here: the official leg registers it
119
+ * on the standing server unconditionally, because the Winter runtime always advertises `advisor` and
120
+ * two legs whose advertised sets differ by a host option is the divergence WS-14 §11 exists to
121
+ * prevent. This supplies WHO REVIEWS — a resolver the host owns, because reviewer RESOLUTION is the
122
+ * runtime's provider-layer concern (D30) and the router has no provider layer. With no resolver the
123
+ * tool answers WS-06 §4's ordinary error.
124
+ *
125
+ * The transcript is NOT a field here: the source is this session's own, built by the leg over the one
126
+ * shared store, keyed by the backend session id the runtime reports at `system/init`.
127
+ */
128
+ advisor?: {
129
+ resolveReviewer: ReviewerResolver;
130
+ maxChars?: number;
131
+ };
60
132
  /**
61
133
  * The handoff barrier's collaborators (whole-branch review, F-3).
62
134
  *
@@ -197,14 +269,27 @@ export declare function runtimeSdkInternals(sdk: RuntimeSdk): RuntimeSdkInternal
197
269
  /**
198
270
  * Builds the object forwarded to a peer's `query()`.
199
271
  *
272
+ * THE INVARIANT, IN ONE SENTENCE: **the forwarded options are the caller's options minus
273
+ * `ROUTER_ONLY_OPTION_KEYS`, plus the brand's capability-server entries under `mcpServers`, and
274
+ * nothing else is rewritten.**
275
+ *
200
276
  * THE COMMON CASE FORWARDS THE CALLER'S OWN OBJECT, by reference. "Passes through verbatim" is a
201
277
  * property a test can only really check by identity, and a router that copied unconditionally would
202
278
  * be quietly deciding which of `Options`' members it knows about — the exact drift D19b's "never a
203
- * translation layer" rules out. A copy is made ONLY when a router-owned key is present, because that
204
- * key must not reach an SDK that would not recognise it; every other member keeps its own value
205
- * identity through the copy.
279
+ * translation layer" rules out. A copy is made ONLY when there is something to remove or something to
280
+ * add, because that is the only time the caller's own object would be the wrong thing to hand over;
281
+ * every other member keeps its own value identity through the copy.
282
+ *
283
+ * SO R-8 DID NOT WEAKEN THE PROPERTY, IT RESTATED IT. A host that configures no capabilities is
284
+ * unaffected — the identity return below still fires, and the test that pins it is unchanged. A host
285
+ * that configures them gets a copy whose every member except `mcpServers` is `Object.is`-identical to
286
+ * its own, and whose `mcpServers` is its own entries plus the capability entries, by reference.
287
+ *
288
+ * A CAPABILITY KEY THE CALLER ALSO USES IS A TYPED REFUSAL, never a silent overwrite in either
289
+ * direction: one of the two servers would simply not be there, and the party who would find out is
290
+ * the model, at the one moment the tool matters.
206
291
  */
207
- export declare function forwardableOptions(options: RouterOptions, brand?: Partial<BrandProfile>): Options;
292
+ export declare function forwardableOptions(options: RouterOptions, brand?: Partial<BrandProfile>, capabilityServers?: Readonly<Record<string, unknown>>): Options;
208
293
  /**
209
294
  * Constructs the router. Throws `RuntimeSdkVersionError` on a version-matrix miss (D19a).
210
295
  *
@@ -0,0 +1,41 @@
1
+ /**
2
+ * THE FOUR TRAFFIC OPT-OUTS THAT MAKE A CHILD RUNTIME ACTUALLY HERMETIC (whole-branch review, F-1).
3
+ *
4
+ * HERMETICITY IS A PROPERTY OF THE CHILD ENVIRONMENT, NOT OF THE FAKE. The loopback fake captures the
5
+ * MODEL endpoint and nothing else; the pinned artifact also fetches REMOTE FEATURE CONFIGURATION
6
+ * (`cdn.growthbook.io`), telemetry, error reports and update checks, none of which pass through
7
+ * `ANTHROPIC_BASE_URL`. Measured on this pin, same binary, same options, same fake: with these four
8
+ * unset the request carries 25 tools; with them set, 21 — `DesignSync`, `Monitor`,
9
+ * `PushNotification` and `advisor_20260301:advisor` appear ONLY when the remote flag fetch succeeds.
10
+ *
11
+ * SO A TEST WITHOUT THESE IS NOT MEASURING THE PIN. It is measuring the pin plus whatever a CDN said
12
+ * this minute, which is (a) a different answer on a different day, (b) a suite that goes red when the
13
+ * fetch times out — the "flake seen twice in ~20 runs" — and (c) a `docs/probes/` record whose "no
14
+ * network" line is false.
15
+ *
16
+ * THE SAME OBJECT THE PRODUCTION ENV BUILDER SETS (R-7b-11). It used to be a test-only copy handed to
17
+ * `configuredExtras`, and a copy is exactly how a test bed and a shipped session end up measuring two
18
+ * different artifacts: this re-export is what makes "the beds run what a host runs" checkable by
19
+ * identity rather than by reading two lists.
20
+ */
21
+ export { TRAFFIC_OPT_OUT_VARIABLES as HERMETIC_TRAFFIC_OPT_OUTS } from "../official/env-allowlist.js";
22
+ /**
23
+ * The minimal environment that points an official-SDK session at a loopback fake (R-7b-6).
24
+ *
25
+ * A REPLACEMENT, never a spread of `process.env` — WS-14 §3's own rule for the child environment, and
26
+ * the same reason the SDK repository's capture harness sets `HOME`: a throwaway is required because
27
+ * `os.homedir()` falls back to the OS user database and would otherwise reach the real `~/.claude`
28
+ * regardless of `CLAUDE_CONFIG_DIR`.
29
+ *
30
+ * PLUS THE FOUR OPT-OUTS ABOVE, unconditionally, because "hermetic" has to mean the whole child and
31
+ * not just its model endpoint (F-1). `allowRemoteConfig` is the ONE deliberate escape hatch: the D29
32
+ * probe's non-hermetic leg uses it to observe what remote configuration adds, and it is spelled at
33
+ * the call site so a reader can see which legs are which.
34
+ */
35
+ export declare function officialCaptureEnv(input: {
36
+ baseUrl: string;
37
+ apiKey?: string;
38
+ claudeConfigDir: string;
39
+ home: string;
40
+ allowRemoteConfig?: boolean;
41
+ }): Record<string, string>;
@@ -0,0 +1,33 @@
1
+ import type { CredentialRef } from "@yanlinglabs/winter-agent-sdk";
2
+ import type { KeychainSeam } from "../seams/keychain.js";
3
+ export interface FakeKeychain extends KeychainSeam {
4
+ /** Stores material for a ref, keyed the same way `read` looks it up. */
5
+ set(ref: CredentialRef, material: string): void;
6
+ /** Every `read` this double served, in order — so a test can prove the fetch happened at spawn. */
7
+ readonly reads: CredentialRef[];
8
+ }
9
+ /**
10
+ * An in-memory `KeychainSeam`. NEVER `Bun.secrets`, never the OS keychain.
11
+ *
12
+ * An unset ref reads `undefined`, which is the seam's own documented "the host has none" answer —
13
+ * so the missing-credential path is testable without arranging for a real keychain to be empty.
14
+ */
15
+ export declare function createFakeKeychain(initial?: Array<{
16
+ ref: CredentialRef;
17
+ material: string;
18
+ }>): FakeKeychain;
19
+ /**
20
+ * A fresh temp directory that is removed whatever the body does.
21
+ *
22
+ * `mkdtemp` under the OS temp root, never a path built from a home directory: the point is a
23
+ * directory this process created and this process owns.
24
+ */
25
+ export declare function withTempDir<T>(prefix: string, fn: (dir: string) => Promise<T>): Promise<T>;
26
+ /**
27
+ * The home directories a hermetic official-branch test needs: a throwaway `HOME` and a throwaway
28
+ * `CLAUDE_CONFIG_DIR`, both under one temp root that is removed in a `finally`.
29
+ */
30
+ export declare function withHermeticHomes<T>(fn: (homes: {
31
+ home: string;
32
+ claudeConfigDir: string;
33
+ }) => Promise<T>): Promise<T>;
@@ -0,0 +1,5 @@
1
+ export { createFakeKeychain, withHermeticHomes, withTempDir } from "./hermetic.js";
2
+ export type { FakeKeychain } from "./hermetic.js";
3
+ export { createFakeClaudePeer, createFakeWinterPeer } from "./peers.js";
4
+ export type { FakeClaudePeerOptions, FakeWinterPeer, FakeWinterPeerOptions, RecordedQueryCall } from "./peers.js";
5
+ export { HERMETIC_TRAFFIC_OPT_OUTS, officialCaptureEnv } from "./capture-env.js";
@@ -0,0 +1,139 @@
1
+ import {
2
+ TRAFFIC_OPT_OUT_VARIABLES
3
+ } from "../index-mfd2rg7x.js";
4
+
5
+ // src/testing/hermetic.ts
6
+ import { mkdtempSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ function keyOf(ref) {
10
+ switch (ref.kind) {
11
+ case "keychain":
12
+ return `keychain:${ref.service ?? ""}:${ref.account}`;
13
+ case "env":
14
+ return `env:${ref.name}`;
15
+ case "file":
16
+ return `file:${ref.path}:${ref.format}:${ref.profile ?? ""}`;
17
+ case "inline":
18
+ return `inline:${ref.value}`;
19
+ default:
20
+ return ref.kind;
21
+ }
22
+ }
23
+ function createFakeKeychain(initial = []) {
24
+ const store = new Map;
25
+ const reads = [];
26
+ for (const entry of initial)
27
+ store.set(keyOf(entry.ref), entry.material);
28
+ return {
29
+ reads,
30
+ set(ref, material) {
31
+ store.set(keyOf(ref), material);
32
+ },
33
+ async read(ref) {
34
+ reads.push(ref);
35
+ return store.get(keyOf(ref));
36
+ }
37
+ };
38
+ }
39
+ async function withTempDir(prefix, fn) {
40
+ const dir = mkdtempSync(join(tmpdir(), `winter-runtime-sdk-${prefix}-`));
41
+ try {
42
+ return await fn(dir);
43
+ } finally {
44
+ rmSync(dir, { recursive: true, force: true });
45
+ }
46
+ }
47
+ async function withHermeticHomes(fn) {
48
+ return withTempDir("homes", async (root) => {
49
+ const home = mkdtempSync(join(root, "home-"));
50
+ const claudeConfigDir = mkdtempSync(join(root, "claude-config-"));
51
+ return fn({ home, claudeConfigDir });
52
+ });
53
+ }
54
+ // src/testing/peers.ts
55
+ import { InvalidBrandError, resolveBrand, transcriptProjectKey } from "@yanlinglabs/winter-agent-sdk";
56
+ var defaultMessage = () => ({
57
+ type: "result",
58
+ subtype: "success",
59
+ is_error: false,
60
+ result: "ok"
61
+ });
62
+ function scriptedQuery(messages) {
63
+ const unsupported = (name) => async () => {
64
+ throw new Error(`fake winter peer: Query.${name}() is not scripted`);
65
+ };
66
+ const generator = async function* () {
67
+ for (const message of messages)
68
+ yield message;
69
+ }();
70
+ const query = {
71
+ next: (...args) => generator.next(...args),
72
+ return: (value) => generator.return(value),
73
+ throw: (error) => generator.throw(error),
74
+ [Symbol.asyncIterator]() {
75
+ return query;
76
+ },
77
+ async[Symbol.asyncDispose]() {
78
+ await generator.return(undefined);
79
+ },
80
+ interrupt: unsupported("interrupt"),
81
+ setModel: unsupported("setModel"),
82
+ supportedModels: unsupported("supportedModels"),
83
+ listModelFamilies: unsupported("listModelFamilies"),
84
+ accountInfo: unsupported("accountInfo"),
85
+ rewindFiles: unsupported("rewindFiles"),
86
+ setPermissionMode: unsupported("setPermissionMode"),
87
+ messaging: {
88
+ listReachable: unsupported("messaging.listReachable"),
89
+ deliver: unsupported("messaging.deliver"),
90
+ steerChild: unsupported("messaging.steerChild"),
91
+ resumeChild: unsupported("messaging.resumeChild"),
92
+ subscribeIdle: unsupported("messaging.subscribeIdle"),
93
+ senderClass: unsupported("messaging.senderClass"),
94
+ readNotifications: unsupported("messaging.readNotifications"),
95
+ onIdleNotice: () => {
96
+ throw new Error("fake winter peer: Query.messaging.onIdleNotice() is not scripted");
97
+ }
98
+ }
99
+ };
100
+ return query;
101
+ }
102
+ function createFakeWinterPeer(options = {}) {
103
+ const calls = [];
104
+ const scripted = options.messages ?? [defaultMessage()];
105
+ const namespace = {
106
+ SDK_VERSION: options.packageVersion ?? "0.0.3",
107
+ PROTOCOL_VERSION: options.protocolVersion ?? "1.0",
108
+ resolveBrand,
109
+ InvalidBrandError,
110
+ transcriptProjectKey,
111
+ query: (args) => {
112
+ calls.push({ prompt: args.prompt, options: args.options });
113
+ return options.query === undefined ? scriptedQuery(scripted) : options.query(args);
114
+ }
115
+ };
116
+ return { peer: namespace, calls, scripted };
117
+ }
118
+ function createFakeClaudePeer(options = {}) {
119
+ return { version: options.packageVersion ?? "0.3.250" };
120
+ }
121
+ // src/testing/capture-env.ts
122
+ function officialCaptureEnv(input) {
123
+ return {
124
+ ANTHROPIC_BASE_URL: input.baseUrl,
125
+ ANTHROPIC_API_KEY: input.apiKey ?? "sk-ant-fake-hermetic-key",
126
+ CLAUDE_CONFIG_DIR: input.claudeConfigDir,
127
+ HOME: input.home,
128
+ ...input.allowRemoteConfig === true ? {} : TRAFFIC_OPT_OUT_VARIABLES
129
+ };
130
+ }
131
+ export {
132
+ withTempDir,
133
+ withHermeticHomes,
134
+ officialCaptureEnv,
135
+ createFakeWinterPeer,
136
+ createFakeKeychain,
137
+ createFakeClaudePeer,
138
+ TRAFFIC_OPT_OUT_VARIABLES as HERMETIC_TRAFFIC_OPT_OUTS
139
+ };
@@ -0,0 +1,45 @@
1
+ import type { Options, Query, SdkMessage } from "@yanlinglabs/winter-agent-sdk";
2
+ import type { RuntimeSdkPeers } from "../sdk.js";
3
+ export interface RecordedQueryCall {
4
+ prompt: string | AsyncIterable<string>;
5
+ options: Options;
6
+ }
7
+ export interface FakeWinterPeer {
8
+ /** Pass this as `peers.winter`. */
9
+ peer: RuntimeSdkPeers["winter"];
10
+ /** Every `query()` the router made, in order, with the values it forwarded. */
11
+ calls: RecordedQueryCall[];
12
+ /** The exact message objects the fake yields — for identity assertions on the stream. */
13
+ scripted: SdkMessage[];
14
+ }
15
+ export interface FakeWinterPeerOptions {
16
+ /**
17
+ * The package version the fake reports as its own identity (probe step 1, `peer-export`).
18
+ * Defaults to a version INSIDE the matrix so a test that does not care about versions constructs
19
+ * cleanly; pass an out-of-range value to exercise the refusal.
20
+ */
21
+ packageVersion?: string;
22
+ /** Defaults to the one protocol version this router is tested against. */
23
+ protocolVersion?: string;
24
+ /** What the returned `Query` yields, in order. Defaults to one result-shaped message. */
25
+ messages?: SdkMessage[];
26
+ /** Called instead of the default generator, when a test needs the Query itself to misbehave. */
27
+ query?: (args: {
28
+ prompt: string | AsyncIterable<string>;
29
+ options: Options;
30
+ }) => Query;
31
+ }
32
+ /** A Winter peer that records what the router forwarded. See this module's header for the cast. */
33
+ export declare function createFakeWinterPeer(options?: FakeWinterPeerOptions): FakeWinterPeer;
34
+ export interface FakeClaudePeerOptions {
35
+ /** Defaults to the exact pin in the matrix. */
36
+ packageVersion?: string;
37
+ }
38
+ /**
39
+ * An official peer, for matrix tests only.
40
+ *
41
+ * It exports a version identity ON PURPOSE: without one, the matrix's second probe would resolve the
42
+ * REAL `@anthropic-ai/claude-agent-sdk@0.3.250` that this repository installs as a dev dependency,
43
+ * and an "out of range" test would pass for the wrong reason.
44
+ */
45
+ export declare function createFakeClaudePeer(options?: FakeClaudePeerOptions): NonNullable<RuntimeSdkPeers["claude"]>;