@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.
- package/README.md +77 -15
- package/dist/door.d.ts +62 -1
- package/dist/errors.d.ts +8 -0
- package/dist/index-mfd2rg7x.js +1426 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +421 -1611
- package/dist/messaging/index.d.ts +1 -3
- package/dist/messaging/router.d.ts +1 -12
- package/dist/official/aliases.d.ts +6 -2
- package/dist/official/index.d.ts +2 -2
- package/dist/official/mcp-descriptors.d.ts +59 -47
- package/dist/sdk.d.ts +90 -5
- package/dist/testing/capture-env.d.ts +41 -0
- package/dist/testing/hermetic.d.ts +33 -0
- package/dist/testing/host.d.ts +5 -0
- package/dist/testing/host.js +139 -0
- package/dist/testing/peers.d.ts +45 -0
- package/dist/version-matrix.d.ts +44 -4
- package/package.json +17 -5
- package/dist/messaging/handlers.d.ts +0 -56
- package/dist/native-args.d.ts +0 -76
|
@@ -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,
|
|
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,
|
|
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
|
*
|
package/dist/official/index.d.ts
CHANGED
|
@@ -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,
|
|
21
|
-
export type { InputShapeFactory, JsonSchemaObject,
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
20
|
-
*
|
|
21
|
-
* key
|
|
22
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
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
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
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
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
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
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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
|
-
|
|
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
|
|
204
|
-
*
|
|
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
|
|
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"]>;
|