@yanlinglabs/winter-agent-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.
@@ -0,0 +1,438 @@
1
+ import {
2
+ validateToField2,
3
+ callerAddress2,
4
+ sendMessage2,
5
+ formatListing,
6
+ listAgents2
7
+ } from "../index-51ysrfm8.js";
8
+ import {
9
+ resolveWinterHome2,
10
+ WinterCompatibilitySessionStore2
11
+ } from "../index-9e98bg1r.js";
12
+
13
+ // src/tools/schemas.ts
14
+ var SEND_MESSAGE_TO_MAX = 300;
15
+ var SEND_MESSAGE_SUMMARY_MAX = 200;
16
+ var LIST_AGENTS_FIELD_MAX = 256;
17
+ var NATIVE_SEND_MESSAGE_SCHEMA = {
18
+ type: "object",
19
+ properties: {
20
+ to: { type: "string", maxLength: SEND_MESSAGE_TO_MAX, description: 'no newline, no "*" broadcast' },
21
+ message: { type: "string", description: 'required; defaults "" for pure idle subscription' },
22
+ summary: { type: "string", maxLength: SEND_MESSAGE_SUMMARY_MAX },
23
+ notify_when_idle: { type: "boolean", description: "one-shot; main conversation -> same-machine session only" }
24
+ },
25
+ required: ["to", "message"]
26
+ };
27
+ var NATIVE_LIST_AGENTS_SCHEMA = {
28
+ type: "object",
29
+ properties: {
30
+ channel: { type: "string", maxLength: LIST_AGENTS_FIELD_MAX, description: "reserved" },
31
+ q: { type: "string", maxLength: LIST_AGENTS_FIELD_MAX, description: "reserved" }
32
+ }
33
+ };
34
+ var NATIVE_LIST_AGENTS_OUTPUT_SCHEMA = {
35
+ type: "object",
36
+ properties: { listing: { type: "string" } },
37
+ required: ["listing"],
38
+ additionalProperties: false
39
+ };
40
+ var NATIVE_READ_NOTIFICATIONS_SCHEMA = {
41
+ type: "object",
42
+ properties: {},
43
+ additionalProperties: false
44
+ };
45
+ var NATIVE_READ_NOTIFICATIONS_OUTPUT_SCHEMA = {
46
+ type: "object",
47
+ properties: {
48
+ notifications: {
49
+ type: "array",
50
+ items: {
51
+ type: "object",
52
+ properties: {
53
+ notification_id: { type: "string" },
54
+ origin: { type: "string" },
55
+ queued_at: { type: "string" },
56
+ content: { type: "string" }
57
+ }
58
+ }
59
+ },
60
+ remaining: { type: "number" }
61
+ }
62
+ };
63
+ var NATIVE_ADVISOR_SCHEMA = {
64
+ type: "object",
65
+ properties: {}
66
+ };
67
+ var NATIVE_ADVISOR_OUTPUT_SCHEMA = {
68
+ type: "object",
69
+ properties: {
70
+ advice: { type: "string" },
71
+ model: { type: "string" },
72
+ truncated: { type: "boolean" }
73
+ },
74
+ required: ["advice", "model"]
75
+ };
76
+ // src/tools/definitions.ts
77
+ var SEND_MESSAGE_DEFINITION = {
78
+ toolName: "send_message",
79
+ builtinName: "SendMessage",
80
+ description: "Resolves `to` against child registry, teammates, live peer registry; steers a running child, resumes an addressable completed/stopped child, wakes an idle live peer, queues for a running peer; never cold-resumes an arbitrary exited transcript.",
81
+ searchHint: "send message agent session peer child steer resume notify idle",
82
+ inputSchema: NATIVE_SEND_MESSAGE_SCHEMA,
83
+ permissionClass: "messaging"
84
+ };
85
+ var LIST_AGENTS_DEFINITION = {
86
+ toolName: "list_agents",
87
+ builtinName: "ListAgents",
88
+ description: "Names/refs, activity/status, addressing identity for children, teammates, eligible live peers; never an enumeration of exited transcripts.",
89
+ searchHint: "list agents sessions peers children roster reachable",
90
+ inputSchema: NATIVE_LIST_AGENTS_SCHEMA,
91
+ outputSchema: NATIVE_LIST_AGENTS_OUTPUT_SCHEMA,
92
+ permissionClass: "messaging"
93
+ };
94
+ var READ_NOTIFICATIONS_DEFINITION = {
95
+ toolName: "read_notifications",
96
+ builtinName: "ReadNotifications",
97
+ description: "Drains Winter's own global-messaging notification queue ([WS-10]).",
98
+ inputSchema: NATIVE_READ_NOTIFICATIONS_SCHEMA,
99
+ outputSchema: NATIVE_READ_NOTIFICATIONS_OUTPUT_SCHEMA,
100
+ permissionClass: "messaging"
101
+ };
102
+ var ADVISOR_DEFINITION = {
103
+ toolName: "advisor",
104
+ builtinName: "advisor",
105
+ description: "Consults a stronger reviewer model over this session's own conversation/tool history (provider-opaque state such as encrypted_content is never included). Reviewer unavailable/timeout -> ordinary tool error; never blocks the turn.",
106
+ inputSchema: NATIVE_ADVISOR_SCHEMA,
107
+ outputSchema: NATIVE_ADVISOR_OUTPUT_SCHEMA,
108
+ permissionClass: "mcp"
109
+ };
110
+ var WINTER_DEFAULT_TOOL_DEFINITIONS = [
111
+ SEND_MESSAGE_DEFINITION,
112
+ LIST_AGENTS_DEFINITION,
113
+ READ_NOTIFICATIONS_DEFINITION,
114
+ ADVISOR_DEFINITION
115
+ ];
116
+ // src/tools/accept.ts
117
+ var SEND_MESSAGE_FIELDS = new Set(Object.keys(NATIVE_SEND_MESSAGE_SCHEMA.properties ?? {}));
118
+ var LIST_AGENTS_FIELDS = new Set(Object.keys(NATIVE_LIST_AGENTS_SCHEMA.properties ?? {}));
119
+ function unknownFields(record, allowed) {
120
+ return Object.keys(record).filter((key) => !allowed.has(key));
121
+ }
122
+ function asRecord(input) {
123
+ return typeof input === "object" && input !== null && !Array.isArray(input) ? input : undefined;
124
+ }
125
+ function acceptNativeSendMessageArgs(input) {
126
+ const record = asRecord(input);
127
+ if (record === undefined)
128
+ return { ok: false, reason: "expected an object of SendMessage arguments" };
129
+ const extra = unknownFields(record, SEND_MESSAGE_FIELDS);
130
+ if (extra.length > 0)
131
+ return { ok: false, reason: `unknown argument(s): ${extra.join(", ")}` };
132
+ const to = record["to"];
133
+ const validated = validateToField2(to);
134
+ if (!validated.ok)
135
+ return { ok: false, reason: validated.message };
136
+ const message = record["message"];
137
+ if (typeof message !== "string")
138
+ return { ok: false, reason: "`message` is required and must be a string (an empty string is a pure idle subscription)" };
139
+ const notify = record["notify_when_idle"];
140
+ if (notify !== undefined && typeof notify !== "boolean")
141
+ return { ok: false, reason: "`notify_when_idle` must be a boolean" };
142
+ if (message.length === 0 && notify !== true) {
143
+ return { ok: false, reason: "`message` may only be empty when `notify_when_idle` is true (a pure idle subscription, WS-10 §10.1)" };
144
+ }
145
+ const summary = record["summary"];
146
+ if (summary !== undefined && typeof summary !== "string")
147
+ return { ok: false, reason: "`summary` must be a string" };
148
+ const capped = summary === undefined ? undefined : summary.slice(0, SEND_MESSAGE_SUMMARY_MAX);
149
+ return {
150
+ ok: true,
151
+ args: {
152
+ to,
153
+ message,
154
+ ...capped === undefined ? {} : { summary: capped },
155
+ ...notify === undefined ? {} : { notify_when_idle: notify }
156
+ }
157
+ };
158
+ }
159
+ function acceptNativeListAgentsArgs(input) {
160
+ if (input === undefined || input === null)
161
+ return { ok: true, args: {} };
162
+ const record = asRecord(input);
163
+ if (record === undefined)
164
+ return { ok: false, reason: "expected an object of ListAgents arguments" };
165
+ const extra = unknownFields(record, LIST_AGENTS_FIELDS);
166
+ if (extra.length > 0)
167
+ return { ok: false, reason: `unknown argument(s): ${extra.join(", ")}` };
168
+ for (const field of ["channel", "q"]) {
169
+ const value = record[field];
170
+ if (value !== undefined && (typeof value !== "string" || value.length > LIST_AGENTS_FIELD_MAX)) {
171
+ return { ok: false, reason: `\`${field}\` must be a string of at most ${LIST_AGENTS_FIELD_MAX} characters` };
172
+ }
173
+ }
174
+ return {
175
+ ok: true,
176
+ args: {
177
+ ...typeof record["channel"] === "string" ? { channel: record["channel"] } : {},
178
+ ...typeof record["q"] === "string" ? { q: record["q"] } : {}
179
+ }
180
+ };
181
+ }
182
+ function acceptNativeReadNotificationsArgs(input) {
183
+ if (input === undefined || input === null)
184
+ return { ok: true, args: {} };
185
+ const record = asRecord(input);
186
+ if (record === undefined)
187
+ return { ok: false, reason: "expected an empty object of ReadNotifications arguments" };
188
+ const extra = Object.keys(record);
189
+ if (extra.length > 0)
190
+ return { ok: false, reason: `unknown argument(s): ${extra.join(", ")} (ReadNotifications takes no arguments)` };
191
+ return { ok: true, args: {} };
192
+ }
193
+ function deriveSendMessageSummary(rawSummary, message) {
194
+ if (rawSummary !== undefined)
195
+ return rawSummary.slice(0, SEND_MESSAGE_SUMMARY_MAX);
196
+ const firstLine = (message.split(`
197
+ `)[0] ?? "").trim();
198
+ if (firstLine.length === 0)
199
+ return;
200
+ return firstLine.slice(0, SEND_MESSAGE_SUMMARY_MAX);
201
+ }
202
+ // src/tools/port.ts
203
+ var fallbackCounter = 0;
204
+ function fallbackToolUseId() {
205
+ return `no-tool-use-id-${++fallbackCounter}-${Date.now()}`;
206
+ }
207
+ function callerFromAddress(from, originToolCallId) {
208
+ return {
209
+ sessionId: from.parentWinterSessionId ?? from.winterSessionId,
210
+ ...from.objectKind === "agent" && from.childId !== undefined ? { agentId: from.childId } : {},
211
+ toolUseId: originToolCallId ?? fallbackToolUseId()
212
+ };
213
+ }
214
+ function messagingToolPortFromRuntimeDeps(deps) {
215
+ return {
216
+ async sendDetailed(request) {
217
+ const caller = callerFromAddress(request.from, request.originToolCallId);
218
+ return sendMessage2(deps, caller, {
219
+ to: request.to,
220
+ message: request.body,
221
+ ...request.summary === undefined ? {} : { summary: request.summary },
222
+ ...request.notifyWhenIdle === undefined ? {} : { notify_when_idle: request.notifyWhenIdle }
223
+ });
224
+ },
225
+ async listReachable(scope) {
226
+ const { rows } = await listAgents2(deps, { sessionId: scope.from.parentWinterSessionId ?? scope.from.winterSessionId }, {});
227
+ return rows;
228
+ },
229
+ readNotifications(sessionId) {
230
+ return deps.notifications.drain(sessionId);
231
+ }
232
+ };
233
+ }
234
+ // src/tools/messaging-handlers.ts
235
+ var VENDOR_TOOL_USE_ID_META_KEY = "claudecode/toolUseId";
236
+ function toolUseIdFromExtra(extra) {
237
+ if (typeof extra !== "object" || extra === null)
238
+ return;
239
+ const meta = extra._meta;
240
+ if (typeof meta !== "object" || meta === null)
241
+ return;
242
+ const id = meta[VENDOR_TOOL_USE_ID_META_KEY];
243
+ return typeof id === "string" && id.length > 0 ? id : undefined;
244
+ }
245
+ var MODEL_FACING_FAILURES = new Set(["refused", "ambiguous", "not_found", "unavailable"]);
246
+ function text(body, isError = false) {
247
+ return { text: body, ...isError ? { isError: true } : {} };
248
+ }
249
+ function describe(err) {
250
+ return err instanceof Error ? err.message : String(err);
251
+ }
252
+ async function guarded(what, run) {
253
+ try {
254
+ return await run();
255
+ } catch (err) {
256
+ return text(`Error: ${what}: ${describe(err)}`, true);
257
+ }
258
+ }
259
+ function createMessagingToolHandlers(port, caller) {
260
+ const identity = () => typeof caller === "function" ? caller() : caller;
261
+ return {
262
+ async sendMessage(rawArgs, extra) {
263
+ const accepted = acceptNativeSendMessageArgs(rawArgs);
264
+ if (!accepted.ok)
265
+ return text(accepted.reason, true);
266
+ const bound = identity();
267
+ const perCall = toolUseIdFromExtra(extra);
268
+ const who = perCall === undefined ? bound : { ...bound, toolUseId: perCall };
269
+ const summary = deriveSendMessageSummary(accepted.args.summary, accepted.args.message);
270
+ return guarded("SendMessage could not reach the messaging system", async () => {
271
+ const result = await port.sendDetailed({
272
+ from: callerAddress2(who),
273
+ to: accepted.args.to,
274
+ body: accepted.args.message,
275
+ ...summary === undefined ? {} : { summary },
276
+ ...accepted.args.notify_when_idle === undefined ? {} : { notifyWhenIdle: accepted.args.notify_when_idle },
277
+ ...who.toolUseId === undefined ? {} : { originToolCallId: who.toolUseId }
278
+ });
279
+ const payload = result.notify === undefined ? result.outcome : { ...result.outcome, notify: result.notify };
280
+ return text(JSON.stringify(payload), MODEL_FACING_FAILURES.has(result.outcome.status));
281
+ });
282
+ },
283
+ async listAgents(rawArgs) {
284
+ const accepted = acceptNativeListAgentsArgs(rawArgs);
285
+ if (!accepted.ok)
286
+ return text(accepted.reason, true);
287
+ return guarded("ListAgents could not reach the messaging system", async () => {
288
+ const rows = await port.listReachable({ from: callerAddress2(identity()) });
289
+ return text(JSON.stringify({ listing: formatListing(rows) }));
290
+ });
291
+ },
292
+ async readNotifications(rawArgs) {
293
+ const accepted = acceptNativeReadNotificationsArgs(rawArgs);
294
+ if (!accepted.ok)
295
+ return text(accepted.reason, true);
296
+ return guarded("ReadNotifications could not drain the notification queue", () => {
297
+ const { notifications, remaining } = port.readNotifications(identity().sessionId);
298
+ return text(JSON.stringify({ notifications, remaining }));
299
+ });
300
+ }
301
+ };
302
+ }
303
+ // src/tools/advisor.ts
304
+ var ADVISOR_DEFAULT_MAX_CHARS = 20000;
305
+ var OPAQUE_MARKERS = ["encrypted_content", "reasoning_item", "signature", "thinking", "redacted_thinking"];
306
+ function stripOpaqueMarkers(text) {
307
+ return text.split(`
308
+ `).filter((line) => !OPAQUE_MARKERS.some((marker) => line.toLowerCase().includes(marker))).join(`
309
+ `);
310
+ }
311
+ function assembleReviewerMessages(entries, maxChars = ADVISOR_DEFAULT_MAX_CHARS) {
312
+ const cleaned = entries.map((e) => ({ role: e.role, text: stripOpaqueMarkers(e.text) }));
313
+ const kept = [];
314
+ let total = 0;
315
+ let truncated = false;
316
+ for (let i = cleaned.length - 1;i >= 0; i--) {
317
+ const entry = cleaned[i];
318
+ if (!entry)
319
+ continue;
320
+ if (total + entry.text.length > maxChars) {
321
+ if (kept.length === 0) {
322
+ kept.unshift({ role: entry.role, text: entry.text.slice(Math.max(0, entry.text.length - maxChars)) });
323
+ }
324
+ truncated = true;
325
+ break;
326
+ }
327
+ kept.unshift(entry);
328
+ total += entry.text.length;
329
+ }
330
+ return { messages: kept.map((e) => ({ role: e.role, content: e.text })), truncated };
331
+ }
332
+ function error(body) {
333
+ return { text: body, isError: true };
334
+ }
335
+ function describe2(err) {
336
+ return err instanceof Error ? err.message : String(err);
337
+ }
338
+ function createAdvisorToolHandler(deps) {
339
+ return async () => {
340
+ let reviewer;
341
+ try {
342
+ reviewer = deps.resolveReviewer();
343
+ } catch (err) {
344
+ return error(`Error: advisor failed to resolve a reviewer model: ${describe2(err)}`);
345
+ }
346
+ if (!reviewer) {
347
+ return error(`Error: advisor is unavailable -- no reviewer model is resolvable in this session's provider catalog (WS-06 §4: "Reviewer unavailable/timeout -> ordinary tool error; never blocks the turn").`);
348
+ }
349
+ let entries;
350
+ try {
351
+ entries = await deps.transcriptSource.getEntries();
352
+ } catch (err) {
353
+ return error(`Error: advisor failed to assemble the session transcript: ${describe2(err)}`);
354
+ }
355
+ const { messages, truncated } = assembleReviewerMessages(entries, deps.maxChars ?? ADVISOR_DEFAULT_MAX_CHARS);
356
+ let turn;
357
+ try {
358
+ turn = await reviewer.provider.generate({ messages });
359
+ } catch (err) {
360
+ return error(`Error: advisor's reviewer model failed: ${describe2(err)}`);
361
+ }
362
+ if (turn.kind !== "text" || typeof turn.text !== "string") {
363
+ return error(`Error: advisor's reviewer model returned a non-text response (kind: "${turn.kind}"); advisor has no tool-execution loop to act on it.`);
364
+ }
365
+ return { text: JSON.stringify({ advice: turn.text, model: reviewer.model, ...truncated ? { truncated: true } : {} }) };
366
+ };
367
+ }
368
+ function entryText(content) {
369
+ if (typeof content === "string")
370
+ return content;
371
+ if (!Array.isArray(content))
372
+ return;
373
+ const parts = content.flatMap((block) => {
374
+ if (typeof block !== "object" || block === null)
375
+ return [];
376
+ const record = block;
377
+ return record.type === "text" && typeof record.text === "string" ? [record.text] : [];
378
+ });
379
+ return parts.length === 0 ? undefined : parts.join(`
380
+ `);
381
+ }
382
+ function toTranscriptEntry(entry) {
383
+ if (entry.type !== "user" && entry.type !== "assistant")
384
+ return;
385
+ const message = entry["message"];
386
+ if (typeof message !== "object" || message === null)
387
+ return;
388
+ const text = entryText(message.content);
389
+ if (text === undefined || text.length === 0)
390
+ return;
391
+ return { role: entry.type, text };
392
+ }
393
+ function transcriptSourceForSessionKey(key, opts = {}) {
394
+ const store = opts.store ?? new WinterCompatibilitySessionStore2({ winterHome: opts.winterHome ?? resolveWinterHome2() });
395
+ return {
396
+ async getEntries() {
397
+ const entries = await store.load(key);
398
+ if (entries === null)
399
+ return [];
400
+ return entries.flatMap((entry) => {
401
+ const mapped = toTranscriptEntry(entry);
402
+ return mapped === undefined ? [] : [mapped];
403
+ });
404
+ }
405
+ };
406
+ }
407
+ export {
408
+ ADVISOR_DEFAULT_MAX_CHARS,
409
+ ADVISOR_DEFINITION,
410
+ LIST_AGENTS_DEFINITION,
411
+ LIST_AGENTS_FIELD_MAX,
412
+ NATIVE_ADVISOR_OUTPUT_SCHEMA,
413
+ NATIVE_ADVISOR_SCHEMA,
414
+ NATIVE_LIST_AGENTS_OUTPUT_SCHEMA,
415
+ NATIVE_LIST_AGENTS_SCHEMA,
416
+ NATIVE_READ_NOTIFICATIONS_OUTPUT_SCHEMA,
417
+ NATIVE_READ_NOTIFICATIONS_SCHEMA,
418
+ NATIVE_SEND_MESSAGE_SCHEMA,
419
+ OPAQUE_MARKERS,
420
+ READ_NOTIFICATIONS_DEFINITION,
421
+ SEND_MESSAGE_DEFINITION,
422
+ SEND_MESSAGE_SUMMARY_MAX,
423
+ SEND_MESSAGE_TO_MAX,
424
+ VENDOR_TOOL_USE_ID_META_KEY,
425
+ WINTER_DEFAULT_TOOL_DEFINITIONS,
426
+ acceptNativeListAgentsArgs,
427
+ acceptNativeReadNotificationsArgs,
428
+ acceptNativeSendMessageArgs,
429
+ assembleReviewerMessages,
430
+ callerAddress2 as callerAddress,
431
+ createAdvisorToolHandler,
432
+ createMessagingToolHandlers,
433
+ deriveSendMessageSummary,
434
+ messagingToolPortFromRuntimeDeps,
435
+ stripOpaqueMarkers,
436
+ toolUseIdFromExtra,
437
+ transcriptSourceForSessionKey
438
+ };
@@ -0,0 +1,52 @@
1
+ import { type MessagingToolPort } from "./port.js";
2
+ /**
3
+ * WHO IS CALLING — bound at registration, never read out of the arguments.
4
+ *
5
+ * The standing MCP server is materialized per session (WS-14 §11), so the caller is known when the
6
+ * handler is built. Taking it from the ARGUMENTS instead would make the sender's identity something
7
+ * a model could write, and every fence in the messaging core — the owning-parent rule, the
8
+ * self-target refusal, WS-10 §13's sender class, WS-15 §6.2's dedupe key — is keyed on it.
9
+ *
10
+ * `toolUseId` is the second half of WS-10 §12's retry key, and it is OPTIONAL because a host that
11
+ * cannot supply one exists: it gets no dedupe, stated at the door rather than faked with a
12
+ * stable-looking key that would make two different messages one.
13
+ */
14
+ export interface WinterToolCaller {
15
+ sessionId: string;
16
+ agentId?: string;
17
+ toolUseId?: string;
18
+ }
19
+ /** The host-neutral tool result: one text body, plus whether the model should read it as a failure. */
20
+ export interface WinterToolResult {
21
+ text: string;
22
+ isError?: boolean;
23
+ }
24
+ export type WinterToolHandler = (args: unknown, extra?: unknown) => Promise<WinterToolResult>;
25
+ export interface MessagingToolHandlers {
26
+ sendMessage: WinterToolHandler;
27
+ listAgents: WinterToolHandler;
28
+ readNotifications: WinterToolHandler;
29
+ }
30
+ /**
31
+ * WS-10 §12's RETRY KEY, on the official branch — and it exists, which was not known until it was
32
+ * measured.
33
+ *
34
+ * §12 wants a message id derived from (sender session, TOOL-CALL id) so "a retry allocates the SAME
35
+ * id" and returns the stored outcome instead of starting a second turn. On the Winter branch the
36
+ * caller binds `toolUseId` at registration. On the official branch the handler is inside the
37
+ * vendor's in-process MCP server, where the only per-call channel is the second argument the vendor
38
+ * passes — and the reasonable expectation was that it carries MCP request context (a JSON-RPC
39
+ * request id, `_meta`) rather than an Anthropic-API `tool_use_id`, which is one layer up.
40
+ *
41
+ * THE PINNED RUNTIME BRIDGES THEM. Measured on 0.3.250: `extra._meta["claudecode/toolUseId"]` is the
42
+ * exact id the model emitted. So the official branch gets a real §12 key rather than depending on
43
+ * the rapid-repeat guard, and the vendor's own namespaced `_meta` name is read rather than guessed.
44
+ *
45
+ * A VENDOR-NAMESPACED KEY IS NEVER REBRANDED (WS-01 §5): `claudecode/toolUseId` is the vendor's name
46
+ * for the vendor's field, exactly like `CLAUDE_CONFIG_DIR`. It is read defensively — an absent or
47
+ * non-string value simply falls back to the bound caller's id — because a future pin may move it,
48
+ * and losing the key must degrade to today's behaviour rather than to a crash.
49
+ */
50
+ export declare const VENDOR_TOOL_USE_ID_META_KEY = "claudecode/toolUseId";
51
+ export declare function toolUseIdFromExtra(extra: unknown): string | undefined;
52
+ export declare function createMessagingToolHandlers(port: MessagingToolPort, caller: WinterToolCaller | (() => WinterToolCaller)): MessagingToolHandlers;
@@ -0,0 +1,39 @@
1
+ import { callerAddress, type ListedRuntimeObject, type MessagingRuntimeDeps, type NotificationRecord, type RuntimeAddress, type SendMessageResult } from "../messaging/index.js";
2
+ export interface MessagingToolPort {
3
+ sendDetailed(request: {
4
+ from: RuntimeAddress;
5
+ to: string;
6
+ body: string;
7
+ summary?: string;
8
+ notifyWhenIdle?: boolean;
9
+ originToolCallId?: string;
10
+ }): Promise<SendMessageResult>;
11
+ listReachable(scope: {
12
+ from: RuntimeAddress;
13
+ }): Promise<ListedRuntimeObject[]>;
14
+ readNotifications(sessionId: string): {
15
+ notifications: NotificationRecord[];
16
+ remaining: number;
17
+ };
18
+ }
19
+ /**
20
+ * The Winter-runtime side of the port: `MessagingRuntimeDeps` in, `MessagingToolPort` out.
21
+ *
22
+ * `listReachable` goes through the core's own `listAgents` rather than straight to
23
+ * `deps.adapter.listReachable`, because the core is where the self-exclusion rule lives (WS-10
24
+ * §10.2) — so the handlers never filter again (they are the layer least able to know the caller's
25
+ * real address).
26
+ *
27
+ * WHAT THAT FILTER ACTUALLY EXCLUDES, stated precisely rather than as "never yourself" (whole-branch
28
+ * fix wave). The `from` address is resolved to its OWNING SESSION before the core is asked, and the
29
+ * core drops the row matching that session address. For a top-level caller those are the same thing
30
+ * and the rule reads as written. For a CHILD caller (`agent:<parent>:<child>`) they are not: the
31
+ * scope collapses to `session:<parent>`, so the child's OWN `agent:` row can still appear in the
32
+ * listing it gets back. The router's handle filters the same way, by the address it was scoped with.
33
+ *
34
+ * Pre-existing on both branches and left alone here deliberately — this round changed no behaviour,
35
+ * and the fix belongs where the resolution happens, not in a re-filter bolted onto the port.
36
+ * Ledgered for the 0.0.4 patch wave.
37
+ */
38
+ export declare function messagingToolPortFromRuntimeDeps(deps: MessagingRuntimeDeps): MessagingToolPort;
39
+ export { callerAddress };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The self-describing shape a tool schema is stored and served as.
3
+ *
4
+ * Deliberately structural and permissive rather than a full JSON Schema type: neither host validates
5
+ * model input against these at all (the Winter registry's own type is explicitly "self-describing…
6
+ * not a validator" — the ACCEPTORS in `accept.ts` are what enforce the contract). What matters is
7
+ * that the advertised bytes are one object, in one place.
8
+ */
9
+ export interface JsonSchemaObject {
10
+ type: "object";
11
+ properties?: Record<string, unknown>;
12
+ required?: readonly string[];
13
+ additionalProperties?: boolean;
14
+ }
15
+ /** WS-10 §10.1's bounds. `to`'s own limit is the messaging subpath's; this is the copy the SCHEMA advertises. */
16
+ export declare const SEND_MESSAGE_TO_MAX = 300;
17
+ export declare const SEND_MESSAGE_SUMMARY_MAX = 200;
18
+ /** WS-10 §10.2's bound on both reserved `ListAgents` fields. */
19
+ export declare const LIST_AGENTS_FIELD_MAX = 256;
20
+ export declare const NATIVE_SEND_MESSAGE_SCHEMA: JsonSchemaObject;
21
+ export declare const NATIVE_LIST_AGENTS_SCHEMA: JsonSchemaObject;
22
+ /**
23
+ * WS-10 §10.2: "`ListAgents` output is EXACTLY `{ listing: string }`."
24
+ *
25
+ * "Exactly" is spelled `additionalProperties: false` here, which neither former copy said out loud
26
+ * (both merely listed the one property). It is the whole content of §10.2's sentence, and it is the
27
+ * half a reader of the schema alone would otherwise have to take on trust.
28
+ */
29
+ export declare const NATIVE_LIST_AGENTS_OUTPUT_SCHEMA: JsonSchemaObject;
30
+ /** WS-06 §3.6: the ordinary call is `{}` and nothing else. */
31
+ export declare const NATIVE_READ_NOTIFICATIONS_SCHEMA: JsonSchemaObject;
32
+ /**
33
+ * The drained PAGE, not one notification: the pinned vendor shape is an ARRAY per call plus what is
34
+ * left behind, so a model that drains knows whether to drain again.
35
+ */
36
+ export declare const NATIVE_READ_NOTIFICATIONS_OUTPUT_SCHEMA: JsonSchemaObject;
37
+ /**
38
+ * WS-06 §4: input is `{}` — the runtime forwards the session's own history; no model-supplied
39
+ * parameters.
40
+ *
41
+ * NO `additionalProperties` KEY, DELIBERATELY. Neither host validates model input against a JSON
42
+ * Schema, so the keyword would be decorative — and the posture is applied uniformly across the
43
+ * parameterless tools rather than declared on one and not the others. The ACCEPTOR is where "no
44
+ * more" is actually enforced (ruling P-4), and it is enforced for `read_notifications` too, whose
45
+ * schema does carry the keyword because its pinned vendor shape does.
46
+ */
47
+ export declare const NATIVE_ADVISOR_SCHEMA: JsonSchemaObject;
48
+ export declare const NATIVE_ADVISOR_OUTPUT_SCHEMA: JsonSchemaObject;
@@ -0,0 +1 @@
1
+ export declare const SDK_VERSION: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,6 +21,14 @@
21
21
  ".": {
22
22
  "types": "./dist/index.d.ts",
23
23
  "default": "./dist/index.js"
24
+ },
25
+ "./messaging": {
26
+ "types": "./dist/messaging/index.d.ts",
27
+ "default": "./dist/messaging/index.js"
28
+ },
29
+ "./tools": {
30
+ "types": "./dist/tools/index.d.ts",
31
+ "default": "./dist/tools/index.js"
24
32
  }
25
33
  },
26
34
  "files": [
@@ -37,15 +45,15 @@
37
45
  }
38
46
  },
39
47
  "dependencies": {
40
- "@yanlinglabs/winter-provider-catalog": "0.0.1"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.3"
41
49
  },
42
50
  "optionalDependencies": {
43
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.1"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.3"
44
52
  },
45
53
  "devDependencies": {
46
54
  "@types/node": "^26.4.0",
47
- "@yanlinglabs/winter-conformance": "0.0.1",
48
- "winter-agent-runtime": "0.0.1"
55
+ "@yanlinglabs/winter-conformance": "0.0.3",
56
+ "winter-agent-runtime": "0.0.3"
49
57
  },
50
58
  "scripts": {}
51
59
  }