@yanlinglabs/winter-agent-sdk 0.0.36 → 0.0.39

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 CHANGED
@@ -128,6 +128,37 @@ runtime's own — same descriptions, same input schemas, same inner-call prompts
128
128
  Withdraw either with `disallowedTools`. `WebSearch` can also be switched off at the backend with
129
129
  `web.search.enabled: false`.
130
130
 
131
+ - **`Search`** (opt-in, unreleased) takes `{query}` and returns Exa's answer mode: a written answer
132
+ and the pages it came from, in one call. It is offered only when `Options.tools` names it and
133
+ `web.search.authRef` names a key (the answer endpoint has no anonymous tier). The cited urls pass
134
+ the same `blockedDomains` floor, and a withheld source is counted in the result.
135
+
136
+ All three report the icon of each site they name to the HOST only, as `winter_site_icons:
137
+ [{url, icon_url}]` on the host-facing `tool_result` block. The model never sees it.
138
+
139
+ ### Shaping the tool surface (0.0.38)
140
+
141
+ - **`Options.tools`** — claude's own option: the built-in tool set by name, or the `claude_code`
142
+ preset (the same as leaving it out). A visibility list: a built-in left out is not advertised, not
143
+ searchable, and refused at dispatch. MCP servers' tools are never filtered by it. Without `ToolSearch`
144
+ in it, nothing is deferred.
145
+ - **`Options.deferTools`** — tools that start deferred while Tool Search is active (`toolSearchEnabled`),
146
+ loaded through `ToolSearch` on first use. This is the only way a built-in defers.
147
+ - **`McpSdkServerConfig.toolNames`** — plain names for an in-process server's tools (`{ browser:
148
+ "Browser" }`). The model, the transcript, hooks and `canUseTool` all see the plain name. The call still
149
+ reaches the host as `sdk_mcp_call` with the server's own tool name, and the tool still defers like an
150
+ MCP tool. The `mcp__<server>__<tool>` spelling stays an equivalent identity for permission rules,
151
+ `disallowedTools` and hook matchers; when one of those names it, the call is evaluated under that
152
+ spelling, as for an alias. A plain name that collides with another tool refuses the session. The old
153
+ spelling also selects the tool in a model call, `ToolSearch`'s `select:` and an agent definition's
154
+ `tools`.
155
+ - **`Options.legacyToolNames`** — `{ <old name>: <current tool name> }` for a host's own renamed tool:
156
+ the old name keeps working in calls, `select:`, agent definitions, rules, `disallowedTools` and hook
157
+ matchers.
158
+ - **`Options.reservedMcpServerNames`** — server names only the host's own `type: "sdk"` servers may
159
+ take; any other server under one (settings, project, plugin, explicit non-sdk, `mcp_set_servers`) is
160
+ refused, and an agent definition's inline server is renamed.
161
+
131
162
  - **`Options.web`** — `search.enabled`, `search.authRef` (the backend key, used only once the
132
163
  anonymous tier is exhausted), `search.maxSearchesPerCall` / `search.anonymousMaxSearchesPerCall`,
133
164
  `fetch.digestModel` / `fetch.authRef` (the page-digest model and its own credential),
@@ -145,6 +176,34 @@ Withdraw either with `disallowedTools`. `WebSearch` can also be switched off at
145
176
 
146
177
  Subagents inherit both.
147
178
 
179
+ ### Messaging a host's other sessions (0.0.39)
180
+
181
+ **`Options.hostMessaging`** — `{ send, list }`, for a host that runs many sessions (one process or one
182
+ Worker each) and wants `SendMessage` and `ListAgents` to reach the others. With it set, `query()` puts
183
+ `hostMessaging: true` on the wire and answers two runtime → host control requests,
184
+ `host_message_send` and `host_message_list` (`HOST_MESSAGE_SEND_SUBTYPE` / `HOST_MESSAGE_LIST_SUBTYPE`),
185
+ the same way for a spawned `winter` process and an embedded Worker:
186
+
187
+ - `SendMessage` resolves `to` in-process first — the session's own subagents, then the in-process
188
+ peers. Only a `not_found` there goes to `send`, with the model's raw `to`, the message, its summary,
189
+ `notifyWhenIdle`, the runtime's message id and (for a subagent's call) `fromAgentId`. The host's
190
+ answer (`delivered`, `queued`, `resumed_and_delivered`, `refused`, `not_found`, `unavailable`,
191
+ `delivery_uncertain`, plus an optional `notify` fact and a one-sentence `note`) is the tool's
192
+ result under the runtime's message id, so a retry of the same tool call is answered from the
193
+ ledger and never asks the host twice. A throwing or malformed `send` is `delivery_uncertain`.
194
+ - `ListAgents` lists the subagents, then the sessions `list` returns as `session` rows, ending with
195
+ a count of the ones the host left out (`omitted`) — a listing is never silently cut. A failing
196
+ `list` lists nothing more.
197
+ - `TaskStop` with a `task_id` that names no task of this session goes to the optional `stop`
198
+ (`host_session_stop`): the host interrupts that session's running turn and answers `stopped`,
199
+ `not_running`, `refused`, `not_found` or `unavailable`.
200
+ - The calling tool's abort signal travels with the request: an interrupted call cancels it
201
+ (`control_cancel_request`), and the handler's own `signal` aborts so the host can skip the delivery.
202
+
203
+ The handler is per session and never told who is sending: it knows its caller by construction. The
204
+ router core reaches the same seam through `MessagingRuntimeDeps.hostMessaging` (a `HostMessagingPort`
205
+ per owning session, on the `/messaging` subpath). Without the option, nothing changes.
206
+
148
207
  ### The 0.0.16 background-default change
149
208
 
150
209
  Before 0.0.16, an Agent tool call with no `run_in_background` ran in the **foreground** (this call
@@ -419,6 +419,138 @@ function facetNotificationKey2(sessionId) {
419
419
  function isReservedNotificationKey2(key) {
420
420
  return key.startsWith(RESERVED_NOTIFICATION_KEY_PREFIX2);
421
421
  }
422
+ // src/messaging/host.ts
423
+ var SEND_STATUSES = new Set(["delivered", "queued", "resumed_and_delivered", "refused", "not_found", "unavailable", "delivery_uncertain"]);
424
+ var NEEDS_REASON = new Set(["refused", "not_found", "unavailable", "delivery_uncertain"]);
425
+ var LISTED_STATUSES = new Set(["starting", "running", "idle", "exited", "unavailable", "archived"]);
426
+ var HOST_MESSAGE_NOTE_MAX2 = 500;
427
+ var HOST_MESSAGE_LIST_MAX2 = 200;
428
+ function isRecord(v) {
429
+ return typeof v === "object" && v !== null && !Array.isArray(v);
430
+ }
431
+ function isHostMessageSendRequest2(payload) {
432
+ if (!isRecord(payload))
433
+ return false;
434
+ return typeof payload.to === "string" && typeof payload.message === "string" && typeof payload.messageId === "string" && (payload.summary === undefined || typeof payload.summary === "string") && (payload.notifyWhenIdle === undefined || typeof payload.notifyWhenIdle === "boolean") && (payload.fromAgentId === undefined || typeof payload.fromAgentId === "string");
435
+ }
436
+ function isHostMessageListRequest2(payload) {
437
+ if (payload === undefined)
438
+ return true;
439
+ return isRecord(payload) && (payload.fromAgentId === undefined || typeof payload.fromAgentId === "string");
440
+ }
441
+ function isHostSessionStopRequest2(payload) {
442
+ return isRecord(payload) && typeof payload.id === "string" && (payload.fromAgentId === undefined || typeof payload.fromAgentId === "string");
443
+ }
444
+ var STOP_STATUSES = new Set(["stopped", "not_running", "refused", "not_found", "unavailable"]);
445
+ var STOP_NEEDS_REASON = new Set(["refused", "not_found", "unavailable"]);
446
+ function isHostSessionStopAnswer2(value) {
447
+ if (!isRecord(value))
448
+ return false;
449
+ if (typeof value.status !== "string" || !STOP_STATUSES.has(value.status))
450
+ return false;
451
+ if (value.reason !== undefined && typeof value.reason !== "string")
452
+ return false;
453
+ if (STOP_NEEDS_REASON.has(value.status) && (typeof value.reason !== "string" || value.reason.length === 0))
454
+ return false;
455
+ if (value.note !== undefined && typeof value.note !== "string")
456
+ return false;
457
+ return true;
458
+ }
459
+ function isHostMessageSendAnswer2(value) {
460
+ if (!isRecord(value))
461
+ return false;
462
+ if (typeof value.status !== "string" || !SEND_STATUSES.has(value.status))
463
+ return false;
464
+ if (value.reason !== undefined && typeof value.reason !== "string")
465
+ return false;
466
+ if (NEEDS_REASON.has(value.status) && (typeof value.reason !== "string" || value.reason.length === 0))
467
+ return false;
468
+ if (value.retryable !== undefined && typeof value.retryable !== "boolean")
469
+ return false;
470
+ if (value.note !== undefined && typeof value.note !== "string")
471
+ return false;
472
+ if (value.notify !== undefined) {
473
+ if (!isRecord(value.notify))
474
+ return false;
475
+ if (value.notify.subscribed !== undefined && value.notify.subscribed !== true)
476
+ return false;
477
+ if (value.notify.refused !== undefined && typeof value.notify.refused !== "string")
478
+ return false;
479
+ }
480
+ return true;
481
+ }
482
+ function isHostReachableSession(value) {
483
+ if (!isRecord(value))
484
+ return false;
485
+ if (typeof value.address !== "string" || !validateToField2(value.address).ok)
486
+ return false;
487
+ if (value.name !== undefined && (typeof value.name !== "string" || value.name.includes(`
488
+ `)))
489
+ return false;
490
+ if (typeof value.status !== "string" || !LISTED_STATUSES.has(value.status))
491
+ return false;
492
+ if (typeof value.mode !== "string")
493
+ return false;
494
+ if (value.cwd !== undefined && typeof value.cwd !== "string")
495
+ return false;
496
+ return true;
497
+ }
498
+ function normaliseHostMessageListAnswer2(value) {
499
+ if (!isRecord(value) || !Array.isArray(value.sessions))
500
+ return;
501
+ const valid = value.sessions.filter(isHostReachableSession);
502
+ const sessions = valid.slice(0, HOST_MESSAGE_LIST_MAX2);
503
+ const hostOmitted = typeof value.omitted === "number" && Number.isInteger(value.omitted) && value.omitted > 0 ? value.omitted : 0;
504
+ const omitted = hostOmitted + (valid.length - sessions.length);
505
+ return {
506
+ ...omitted > 0 ? { omitted } : {},
507
+ sessions: sessions.map((s) => ({
508
+ address: s.address,
509
+ ...s.name !== undefined ? { name: s.name } : {},
510
+ status: s.status,
511
+ mode: s.mode,
512
+ ...s.cwd !== undefined ? { cwd: s.cwd } : {}
513
+ }))
514
+ };
515
+ }
516
+ function hostSessionToListed2(row) {
517
+ const reachable = row.status !== "archived" && row.status !== "unavailable";
518
+ return {
519
+ address: row.address,
520
+ ...row.name !== undefined ? { name: row.name } : {},
521
+ objectKind: "session",
522
+ runtimeKind: "winter-agent",
523
+ status: row.status,
524
+ mode: row.mode,
525
+ ...row.cwd !== undefined ? { cwd: row.cwd } : {},
526
+ capabilities: { message: reachable, resume: reachable, notifyWhenIdle: false, reply: reachable }
527
+ };
528
+ }
529
+ function hostAnswerToOutcome2(messageId, answer) {
530
+ const reason = answer.reason ?? answer.status;
531
+ switch (answer.status) {
532
+ case "delivered":
533
+ case "queued":
534
+ case "resumed_and_delivered":
535
+ return { status: answer.status, messageId };
536
+ case "refused":
537
+ return { status: "refused", messageId, reason };
538
+ case "not_found":
539
+ return { status: "not_found", messageId, reason };
540
+ case "unavailable":
541
+ return { status: "unavailable", messageId, retryable: answer.retryable === true, reason };
542
+ case "delivery_uncertain":
543
+ return { status: "delivery_uncertain", messageId, deliveryMayHaveOccurred: true, reason };
544
+ }
545
+ }
546
+ function boundedHostNote2(note) {
547
+ if (note === undefined)
548
+ return;
549
+ const trimmed = note.trim();
550
+ if (trimmed.length === 0)
551
+ return;
552
+ return trimmed.length > HOST_MESSAGE_NOTE_MAX2 ? `${trimmed.slice(0, HOST_MESSAGE_NOTE_MAX2 - 1)}…` : trimmed;
553
+ }
422
554
  // src/messaging/router.ts
423
555
  var MAX_TRACKED_MESSAGE_IDS2 = 1e4;
424
556
  function rememberBounded2(map, key, value, cap = MAX_TRACKED_MESSAGE_IDS2) {
@@ -484,15 +616,15 @@ var SUCCESS_CLASS_STATUSES = new Set(["delivered", "queued", "resumed_and_delive
484
616
  function outcomeReason(outcome) {
485
617
  return "reason" in outcome ? outcome.reason : outcome.status;
486
618
  }
487
- async function sendMessage2(deps, caller, input) {
619
+ async function sendMessage2(deps, caller, input, opts = {}) {
488
620
  const now = deps.now();
489
621
  const messageId = deps.seam.allocateMessageId(caller.sessionId, caller.toolUseId);
490
622
  const existing = deps.seam.lookupOutcome(messageId);
491
623
  if (existing !== undefined)
492
624
  return { outcome: existing };
493
- function settle(outcome, notify) {
625
+ function settle(outcome, notify, note) {
494
626
  deps.seam.recordOutcome(messageId, outcome);
495
- return notify !== undefined ? { outcome, notify } : { outcome };
627
+ return { outcome, ...notify !== undefined ? { notify } : {}, ...note !== undefined ? { note } : {} };
496
628
  }
497
629
  const from = callerAddress2(caller);
498
630
  const fromKey = serializeRuntimeAddress2(from);
@@ -512,8 +644,12 @@ async function sendMessage2(deps, caller, input) {
512
644
  const reachable = await deps.adapter.listReachable({ parent: buildSessionAddress2(caller.sessionId) });
513
645
  const peers = reachable.filter((r) => r.objectKind === "session");
514
646
  const resolved = resolveTarget2({ to: input.to, callerParentSessionId: caller.sessionId, children: deps.seam.children(), peers });
515
- if (resolved.kind === "not_found")
516
- return settle(notFound2(messageId, resolved.message));
647
+ if (resolved.kind === "not_found") {
648
+ const host = deps.hostMessaging?.(caller.sessionId);
649
+ if (host === undefined)
650
+ return settle(notFound2(messageId, resolved.message));
651
+ return settle(...await askHost(host, messageId, caller, input, opts.signal));
652
+ }
517
653
  if (resolved.kind === "stale")
518
654
  return settle(refused2(messageId, resolved.message));
519
655
  if (resolved.kind === "ambiguous")
@@ -560,6 +696,25 @@ async function sendMessage2(deps, caller, input) {
560
696
  const idleOutcome = await deps.adapter.subscribeIdle(to, { messageId });
561
697
  return settle(bodyOutcome, idleOutcome.status === "subscribed" ? { subscribed: true } : { refused: outcomeReason(idleOutcome) });
562
698
  }
699
+ async function askHost(host, messageId, caller, input, signal) {
700
+ let answer;
701
+ try {
702
+ answer = await host.send({
703
+ to: input.to,
704
+ message: input.message,
705
+ ...input.summary !== undefined ? { summary: input.summary } : {},
706
+ ...input.notify_when_idle !== undefined ? { notifyWhenIdle: input.notify_when_idle } : {},
707
+ messageId,
708
+ ...caller.agentId !== undefined ? { fromAgentId: caller.agentId } : {}
709
+ }, signal !== undefined ? { signal } : {});
710
+ } catch (err) {
711
+ return [deliveryUncertain2(messageId, `the host did not answer the delivery: ${describeThrow(err)}`)];
712
+ }
713
+ if (!isHostMessageSendAnswer2(answer))
714
+ return [deliveryUncertain2(messageId, "the host answered the delivery with a malformed outcome")];
715
+ const notify = answer.notify === undefined ? undefined : { ...answer.notify.subscribed === true ? { subscribed: true } : {}, ...answer.notify.refused !== undefined ? { refused: answer.notify.refused } : {} };
716
+ return [hostAnswerToOutcome2(messageId, answer), notify, boundedHostNote2(answer.note)];
717
+ }
563
718
  function describeThrow(err) {
564
719
  return err instanceof Error ? err.message : String(err);
565
720
  }
@@ -593,12 +748,32 @@ function formatListing(rows) {
593
748
  }).join(`
594
749
  `);
595
750
  }
596
- async function listAgents2(deps, caller, _input) {
751
+ function omittedLine(omitted) {
752
+ return `(${omitted} more reachable session${omitted === 1 ? "" : "s"} not listed)`;
753
+ }
754
+ async function listAgents2(deps, caller, _input, opts = {}) {
597
755
  const selfAddr = buildSessionAddress2(caller.sessionId);
598
756
  const selfKey = serializeRuntimeAddress2(selfAddr);
599
757
  const reachable = await deps.adapter.listReachable({ parent: selfAddr });
600
758
  const rows = reachable.filter((r) => r.address !== selfKey);
601
- return { listing: formatListing(rows), rows };
759
+ const host = deps.hostMessaging?.(caller.sessionId);
760
+ let omitted = 0;
761
+ if (host !== undefined) {
762
+ const answer = normaliseHostMessageListAnswer2(await host.list({}, opts.signal !== undefined ? { signal: opts.signal } : {}).catch(() => {
763
+ return;
764
+ }));
765
+ omitted = answer?.omitted ?? 0;
766
+ const seen = new Set(rows.map((r) => r.address));
767
+ for (const row of answer?.sessions ?? []) {
768
+ if (seen.has(row.address) || row.address === selfKey)
769
+ continue;
770
+ seen.add(row.address);
771
+ rows.push(hostSessionToListed2(row));
772
+ }
773
+ }
774
+ const listing = omitted > 0 ? `${formatListing(rows)}
775
+ ${omittedLine(omitted)}` : formatListing(rows);
776
+ return { listing, rows, ...omitted > 0 ? { omitted } : {} };
602
777
  }
603
778
  function readNotifications2(deps, caller) {
604
779
  return deps.notifications.drain(caller.sessionId);
@@ -611,4 +786,4 @@ function createMessagingRouter2(deps) {
611
786
  readNotifications: (caller) => readNotifications2(deps, caller)
612
787
  };
613
788
  }
614
- export { serializeRuntimeAddress2, createFakeMessagingRouterSeam2, REFERENCE_RUNTIME_KIND2, validateToField2, buildSessionAddress2, buildChildAddress2, parseRuntimeAddress2, sameAddress2, MAX_GLOBAL_MESSAGE_SIZE2, DEFAULT_MESSAGE_TTL_MS2, MAX_HOP_COUNT2, RAPID_REPEAT_WINDOW_MS2, HELD_INBOX_CAP2, ACCEPTED_QUEUE_CAP2, DEFAULT_HOLD_EXPIRY_MS2, NOTIFY_IDLE_EXPIRY_MS2, messageExceedsMaxSize2, hopCountExceeded2, delivered2, queued2, resumedAndDelivered2, held2, subscribed2, deliveryUncertain2, refused2, ambiguous2, notFound2, unavailable2, createLoopGuard2, childToListedRuntimeObject2, resolveTarget2, classifyPermissionMode2, mapFromModeToPermissionClass2, defaultInboundResult2, resolveInboundDecision2, createMailbox2, buildDefaultHoldEntry2, buildExplicitHoldEntry2, isIdleSubscribeSenderAllowed2, isIdleSubscribeTargetAllowed2, createNotificationQueue2, createIdleSubscriptionStore2, AGENT_MESSAGE_TAG2, escapeAttributionText2, escapeAttributionAttribute2, RESERVED_NOTIFICATION_KEY_PREFIX2, facetNotificationKey2, isReservedNotificationKey2, MAX_TRACKED_MESSAGE_IDS2, rememberBounded2, createMessagingRouterSeam2, createSubscriberDirectory2, callerAddress2, sendMessage2, formatListing, listAgents2, readNotifications2, createMessagingRouter2 };
789
+ export { serializeRuntimeAddress2, createFakeMessagingRouterSeam2, REFERENCE_RUNTIME_KIND2, validateToField2, buildSessionAddress2, buildChildAddress2, parseRuntimeAddress2, sameAddress2, HOST_MESSAGE_NOTE_MAX2, HOST_MESSAGE_LIST_MAX2, isHostMessageSendRequest2, isHostMessageListRequest2, isHostSessionStopRequest2, isHostSessionStopAnswer2, isHostMessageSendAnswer2, normaliseHostMessageListAnswer2, hostSessionToListed2, hostAnswerToOutcome2, boundedHostNote2, MAX_GLOBAL_MESSAGE_SIZE2, DEFAULT_MESSAGE_TTL_MS2, MAX_HOP_COUNT2, RAPID_REPEAT_WINDOW_MS2, HELD_INBOX_CAP2, ACCEPTED_QUEUE_CAP2, DEFAULT_HOLD_EXPIRY_MS2, NOTIFY_IDLE_EXPIRY_MS2, messageExceedsMaxSize2, hopCountExceeded2, delivered2, queued2, resumedAndDelivered2, held2, subscribed2, deliveryUncertain2, refused2, ambiguous2, notFound2, unavailable2, createLoopGuard2, childToListedRuntimeObject2, resolveTarget2, classifyPermissionMode2, mapFromModeToPermissionClass2, defaultInboundResult2, resolveInboundDecision2, createMailbox2, buildDefaultHoldEntry2, buildExplicitHoldEntry2, isIdleSubscribeSenderAllowed2, isIdleSubscribeTargetAllowed2, createNotificationQueue2, createIdleSubscriptionStore2, AGENT_MESSAGE_TAG2, escapeAttributionText2, escapeAttributionAttribute2, RESERVED_NOTIFICATION_KEY_PREFIX2, facetNotificationKey2, isReservedNotificationKey2, MAX_TRACKED_MESSAGE_IDS2, rememberBounded2, createMessagingRouterSeam2, createSubscriberDirectory2, callerAddress2, sendMessage2, formatListing, omittedLine, listAgents2, readNotifications2, createMessagingRouter2 };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export type { Query, SdkMessage, SessionMessagingFacet } from "./query.js";
3
3
  export type { QueryInternal, ControlRequestHandler, ControlRequestHandlerResult } from "./query.js";
4
4
  export type { Options } from "./options.js";
5
5
  export { SYSTEM_PROMPT_DYNAMIC_BOUNDARY, DEFAULT_CONTEXT_WINDOW_TOKENS, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_PLANS_DIRECTORY, DEFAULT_OUTPUT_STYLE } from "./options.js";
6
- export { DEFAULT_PROVIDER_STALL_TIMEOUT_MS, DEFAULT_KEYCHAIN_SERVICE, TEST_KEYCHAIN_ENV, TEST_KEYCHAIN_MEMORY, MCP_OAUTH_REFRESH_SUBTYPE, CREDENTIAL_RESOLVE_SUBTYPE } from "./options.js";
6
+ export { DEFAULT_PROVIDER_STALL_TIMEOUT_MS, DEFAULT_KEYCHAIN_SERVICE, TEST_KEYCHAIN_ENV, TEST_KEYCHAIN_MEMORY, MCP_OAUTH_REFRESH_SUBTYPE, CREDENTIAL_RESOLVE_SUBTYPE, HOST_MESSAGE_SEND_SUBTYPE, HOST_MESSAGE_LIST_SUBTYPE, HOST_SESSION_STOP_SUBTYPE } from "./options.js";
7
+ export type { HostMessagingHandler } from "./options.js";
7
8
  export { WEB_TOOLS_DEFAULTS, resolveWebToolsConfig } from "./options.js";
8
9
  export type { ResolvedWebToolsConfig } from "./options.js";
9
10
  export type { WebToolsConfig, WebSearchConfig, WebFetchConfig, WebPrivateAddressPolicy, AutoMemoryConfig } from "./protocol/config.js";
@@ -22,7 +23,7 @@ export type { SpawnedRuntimeProcess, SpawnRuntimeOptions, SpawnClaudeCodeProcess
22
23
  export type { RuntimeConfig, RuntimeHooksConfig, RuntimeHookMatcherGroup, SandboxSettingsConfig } from "./protocol/config.js";
23
24
  export type { McpServerConfigForProcessTransport, McpVersionNegotiation, AgentMcpServerSpec, RuntimeAgentDefinition } from "./protocol/config.js";
24
25
  export type { McpOAuthConfig, McpOAuthSecretRef, McpOAuthRefreshRequest, McpOAuthRefreshAnswer } from "./protocol/config.js";
25
- export type { CredentialResolveRequest, CredentialResolveAnswer } from "./protocol/config.js";
26
+ export type { CredentialResolveRequest, CredentialResolveAnswer, HostMessageSendRequest, HostMessageSendAnswer, HostMessageListRequest, HostMessageListAnswer, HostReachableSession, HostSessionStopRequest, HostSessionStopAnswer } from "./protocol/config.js";
26
27
  export { encodeFrame, decodeFrame, splitFrames, ProtocolError } from "./protocol/codec.js";
27
28
  export { PROTOCOL_VERSION } from "./protocol/frames.js";
28
29
  export { SDK_VERSION } from "./version.js";
package/dist/index.js CHANGED
@@ -16,10 +16,16 @@ import {
16
16
  WinterCompatibilitySessionStore2
17
17
  } from "./index-0xks1t4r.js";
18
18
  import {
19
+ isHostMessageSendRequest2,
20
+ isHostMessageListRequest2,
21
+ isHostSessionStopRequest2,
22
+ isHostSessionStopAnswer2,
23
+ isHostMessageSendAnswer2,
24
+ normaliseHostMessageListAnswer2,
19
25
  deliveryUncertain2,
20
26
  refused2,
21
27
  unavailable2
22
- } from "./index-51ysrfm8.js";
28
+ } from "./index-3zhkc4nb.js";
23
29
 
24
30
  // src/query.ts
25
31
  import { randomUUID } from "node:crypto";
@@ -70,6 +76,9 @@ var TEST_KEYCHAIN_ENV = "WINTER_TEST_KEYCHAIN";
70
76
  var TEST_KEYCHAIN_MEMORY = "memory";
71
77
  var MCP_OAUTH_REFRESH_SUBTYPE = "mcp_oauth_refresh";
72
78
  var CREDENTIAL_RESOLVE_SUBTYPE = "credential_resolve";
79
+ var HOST_MESSAGE_SEND_SUBTYPE = "host_message_send";
80
+ var HOST_MESSAGE_LIST_SUBTYPE = "host_message_list";
81
+ var HOST_SESSION_STOP_SUBTYPE = "host_session_stop";
73
82
  var WEB_TOOLS_DEFAULTS = {
74
83
  searchEnabled: true,
75
84
  maxSearchesPerCall: 8,
@@ -419,7 +428,8 @@ function toWireMcpServers(servers) {
419
428
  type: "sdk",
420
429
  name: cfg.name,
421
430
  ...cfg.timeout !== undefined ? { timeout: cfg.timeout } : {},
422
- ...isWinterMcpServerInstance(cfg.instance) ? { tools: cfg.instance.listTools() } : {}
431
+ ...isWinterMcpServerInstance(cfg.instance) ? { tools: cfg.instance.listTools() } : {},
432
+ ...cfg.toolNames !== undefined ? { toolNames: { ...cfg.toolNames } } : {}
423
433
  } : cfg;
424
434
  }
425
435
  return out;
@@ -550,6 +560,79 @@ function makeCredentialResolveHandler(onCredentialResolve, abortController) {
550
560
  }
551
561
  };
552
562
  }
563
+ function abortableFor(abortController, handlerCtx) {
564
+ const controller = new AbortController;
565
+ const onAbort = () => controller.abort();
566
+ if (abortController?.signal.aborted === true || handlerCtx?.signal.aborted === true)
567
+ controller.abort();
568
+ abortController?.signal.addEventListener("abort", onAbort, { once: true });
569
+ handlerCtx?.signal.addEventListener("abort", onAbort, { once: true });
570
+ return {
571
+ controller,
572
+ dispose: () => {
573
+ abortController?.signal.removeEventListener("abort", onAbort);
574
+ handlerCtx?.signal.removeEventListener("abort", onAbort);
575
+ }
576
+ };
577
+ }
578
+ function makeHostMessageSendHandler(host, abortController) {
579
+ return async (payload, handlerCtx) => {
580
+ if (!isHostMessageSendRequest2(payload))
581
+ return { ok: false, error: { code: "invalid_payload", message: "host_message_send expects { to, message, messageId, summary?, notifyWhenIdle?, fromAgentId? }" } };
582
+ const { controller, dispose } = abortableFor(abortController, handlerCtx);
583
+ const request = {
584
+ to: payload.to,
585
+ message: payload.message,
586
+ messageId: payload.messageId,
587
+ ...payload.summary !== undefined ? { summary: payload.summary } : {},
588
+ ...payload.notifyWhenIdle !== undefined ? { notifyWhenIdle: payload.notifyWhenIdle } : {},
589
+ ...payload.fromAgentId !== undefined ? { fromAgentId: payload.fromAgentId } : {}
590
+ };
591
+ try {
592
+ const answer = await host.send(request, { signal: controller.signal });
593
+ return { ok: true, payload: isHostMessageSendAnswer2(answer) ? answer : { status: "delivery_uncertain", reason: "the host's message handler returned a malformed answer" } };
594
+ } catch (err) {
595
+ console.error(`winter: hostMessaging.send threw (${err instanceof Error ? err.name : "error"}) -- answering delivery_uncertain`);
596
+ return { ok: true, payload: { status: "delivery_uncertain", reason: "the host's message handler failed" } };
597
+ } finally {
598
+ dispose();
599
+ }
600
+ };
601
+ }
602
+ function makeHostMessageListHandler(host, abortController) {
603
+ return async (payload, handlerCtx) => {
604
+ if (!isHostMessageListRequest2(payload))
605
+ return { ok: false, error: { code: "invalid_payload", message: "host_message_list expects { fromAgentId? }" } };
606
+ const { controller, dispose } = abortableFor(abortController, handlerCtx);
607
+ try {
608
+ const answer = await host.list({ ...payload?.fromAgentId !== undefined ? { fromAgentId: payload.fromAgentId } : {} }, { signal: controller.signal });
609
+ return { ok: true, payload: normaliseHostMessageListAnswer2(answer) ?? { sessions: [] } };
610
+ } catch (err) {
611
+ console.error(`winter: hostMessaging.list threw (${err instanceof Error ? err.name : "error"}) -- answering an empty listing`);
612
+ return { ok: true, payload: { sessions: [] } };
613
+ } finally {
614
+ dispose();
615
+ }
616
+ };
617
+ }
618
+ function makeHostSessionStopHandler(host, abortController) {
619
+ return async (payload, handlerCtx) => {
620
+ if (!isHostSessionStopRequest2(payload))
621
+ return { ok: false, error: { code: "invalid_payload", message: "host_session_stop expects { id, fromAgentId? }" } };
622
+ if (host.stop === undefined)
623
+ return { ok: true, payload: { status: "not_found", reason: `no task or session "${payload.id}" is known` } };
624
+ const { controller, dispose } = abortableFor(abortController, handlerCtx);
625
+ try {
626
+ const answer = await host.stop({ id: payload.id, ...payload.fromAgentId !== undefined ? { fromAgentId: payload.fromAgentId } : {} }, { signal: controller.signal });
627
+ return { ok: true, payload: isHostSessionStopAnswer2(answer) ? answer : { status: "unavailable", reason: "the host's stop handler returned a malformed answer" } };
628
+ } catch (err) {
629
+ console.error(`winter: hostMessaging.stop threw (${err instanceof Error ? err.name : "error"}) -- answering unavailable`);
630
+ return { ok: true, payload: { status: "unavailable", reason: "the host's stop handler failed" } };
631
+ } finally {
632
+ dispose();
633
+ }
634
+ };
635
+ }
553
636
  function buildRuntimeHooksConfig(hooks) {
554
637
  if (!hooks)
555
638
  return;
@@ -698,6 +781,10 @@ function query(args) {
698
781
  ...options.toolSearchEnabled !== undefined ? { toolSearchEnabled: options.toolSearchEnabled } : {},
699
782
  ...options.insideSubagent !== undefined ? { insideSubagent: options.insideSubagent } : {},
700
783
  ...options.familyMetadata !== undefined ? { familyMetadata: options.familyMetadata } : {},
784
+ ...Array.isArray(options.tools) ? { tools: [...options.tools] } : {},
785
+ ...options.deferTools !== undefined ? { deferTools: [...options.deferTools] } : {},
786
+ ...options.legacyToolNames !== undefined ? { legacyToolNames: { ...options.legacyToolNames } } : {},
787
+ ...options.reservedMcpServerNames !== undefined ? { reservedMcpServerNames: [...options.reservedMcpServerNames] } : {},
701
788
  ...options.allowDangerouslySkipPermissions !== undefined ? { allowDangerouslySkipPermissions: options.allowDangerouslySkipPermissions } : {},
702
789
  ...runtimeHooksConfig !== undefined ? { hooks: runtimeHooksConfig } : {},
703
790
  ...options.includeHookEvents !== undefined ? { includeHookEvents: options.includeHookEvents } : {},
@@ -730,6 +817,7 @@ function query(args) {
730
817
  ...options.maxOutputTokens !== undefined ? { maxOutputTokens: options.maxOutputTokens } : {},
731
818
  ...brand.keychainService !== WINTER_BRAND2.keychainService || options.keychainService !== undefined ? { keychainService: brand.keychainService } : {},
732
819
  ...options.onCredentialResolve !== undefined ? { hostCredentials: true } : {},
820
+ ...options.hostMessaging !== undefined ? { hostMessaging: true } : {},
733
821
  ...options.autoClassifier !== undefined ? { autoClassifier: options.autoClassifier } : {},
734
822
  ...options.advisor !== undefined ? { advisor: options.advisor } : {},
735
823
  ...options.web !== undefined ? { web: options.web } : {},
@@ -892,6 +980,11 @@ function query(args) {
892
980
  if (options.onCredentialResolve) {
893
981
  controlRequestHandlers.set(CREDENTIAL_RESOLVE_SUBTYPE, makeCredentialResolveHandler(options.onCredentialResolve, options.abortController));
894
982
  }
983
+ if (options.hostMessaging) {
984
+ controlRequestHandlers.set(HOST_MESSAGE_SEND_SUBTYPE, makeHostMessageSendHandler(options.hostMessaging, options.abortController));
985
+ controlRequestHandlers.set(HOST_MESSAGE_LIST_SUBTYPE, makeHostMessageListHandler(options.hostMessaging, options.abortController));
986
+ controlRequestHandlers.set(HOST_SESSION_STOP_SUBTYPE, makeHostSessionStopHandler(options.hostMessaging, options.abortController));
987
+ }
895
988
  if (options.onMcpOAuthRefresh) {
896
989
  controlRequestHandlers.set(MCP_OAUTH_REFRESH_SUBTYPE, makeMcpOAuthRefreshHandler(options.onMcpOAuthRefresh, options.abortController));
897
990
  }
@@ -1175,7 +1268,7 @@ function withTestKeychainRedirect(env) {
1175
1268
  return { ...env, [TEST_KEYCHAIN_ENV]: redirect };
1176
1269
  }
1177
1270
  // src/version.ts
1178
- var SDK_VERSION = "0.0.36";
1271
+ var SDK_VERSION = "0.0.39";
1179
1272
  // src/paths/project-key.ts
1180
1273
  var TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
1181
1274
  var VENDOR_PROJECT_KEY_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
@@ -2351,6 +2444,9 @@ export {
2351
2444
  ESCALATING_PERMISSION_MODES,
2352
2445
  FIRST_PARTY_ORIGINATORS2 as FIRST_PARTY_ORIGINATORS,
2353
2446
  HOOK_EVENTS,
2447
+ HOST_MESSAGE_LIST_SUBTYPE,
2448
+ HOST_MESSAGE_SEND_SUBTYPE,
2449
+ HOST_SESSION_STOP_SUBTYPE,
2354
2450
  InvalidBrandError,
2355
2451
  MCP_OAUTH_REFRESH_SUBTYPE,
2356
2452
  MESSAGING_CONTROL_SUBTYPES,
@@ -0,0 +1,42 @@
1
+ import type { HostMessageListAnswer, HostMessageListRequest, HostMessageSendAnswer, HostMessageSendRequest, HostReachableSession, HostSessionStopAnswer, HostSessionStopRequest } from "../protocol/config.js";
2
+ import type { DeliveryOutcome, ListedRuntimeObject } from "./adapter.js";
3
+ /**
4
+ * What the router core (and `TaskStop`) need from a host: `Options.hostMessaging`, already bound to one
5
+ * session. `signal` is the calling tool's own: an interrupted sender cancels the request, and the host
6
+ * is told (its handler's `signal` aborts) so it can avoid delivering.
7
+ */
8
+ export interface HostMessagingPort {
9
+ send(request: HostMessageSendRequest, opts?: {
10
+ signal?: AbortSignal;
11
+ }): Promise<HostMessageSendAnswer>;
12
+ list(request: HostMessageListRequest, opts?: {
13
+ signal?: AbortSignal;
14
+ }): Promise<HostMessageListAnswer>;
15
+ stop(request: HostSessionStopRequest, opts?: {
16
+ signal?: AbortSignal;
17
+ }): Promise<HostSessionStopAnswer>;
18
+ }
19
+ /** A host's sentence for the model is bounded: it is rendered into a tool result. */
20
+ export declare const HOST_MESSAGE_NOTE_MAX = 500;
21
+ /** A host listing is bounded: it is rendered into a tool result, one line per row. */
22
+ export declare const HOST_MESSAGE_LIST_MAX = 200;
23
+ /** The request shape, checked on the WRAPPER side before the host's callback sees it. */
24
+ export declare function isHostMessageSendRequest(payload: unknown): payload is HostMessageSendRequest;
25
+ export declare function isHostMessageListRequest(payload: unknown): payload is HostMessageListRequest;
26
+ export declare function isHostSessionStopRequest(payload: unknown): payload is HostSessionStopRequest;
27
+ /** A well-formed stop answer: a known status, a non-empty reason wherever one is required. */
28
+ export declare function isHostSessionStopAnswer(value: unknown): value is HostSessionStopAnswer;
29
+ /** A well-formed send answer: a known status, a reason wherever one is required, the optional facts typed. */
30
+ export declare function isHostMessageSendAnswer(value: unknown): value is HostMessageSendAnswer;
31
+ /**
32
+ * A list answer, normalised: every malformed row is DROPPED (one bad row must not hide the others, and
33
+ * an unaddressable row would advertise a target the model cannot name), and the listing is capped.
34
+ * `undefined` for an answer that is not a listing at all.
35
+ */
36
+ export declare function normaliseHostMessageListAnswer(value: unknown): HostMessageListAnswer | undefined;
37
+ /** A host's session row as a ListAgents row: always a `session` of the Winter runtime, never resumable "for free" or idle-notifiable unless the host says so through SendMessage itself. */
38
+ export declare function hostSessionToListed(row: HostReachableSession): ListedRuntimeObject;
39
+ /** The host's answer as the router's outcome, under the RUNTIME's message id. Assumes `isHostMessageSendAnswer`. */
40
+ export declare function hostAnswerToOutcome(messageId: string, answer: HostMessageSendAnswer): DeliveryOutcome;
41
+ /** A host note, cut to its bound (it is model-visible). Empty and absent read the same. */
42
+ export declare function boundedHostNote(note: string | undefined): string | undefined;
@@ -11,5 +11,8 @@ export type { PermissionClassLabel, CrossSessionInbound, InboundDecisionParams,
11
11
  export { isIdleSubscribeSenderAllowed, isIdleSubscribeTargetAllowed, createNotificationQueue, createIdleSubscriptionStore, } from "./idle.js";
12
12
  export type { NotificationRecord, NotificationQueue, PendingIdleSubscription, IdleSubscriptionStore } from "./idle.js";
13
13
  export { AGENT_MESSAGE_TAG, RESERVED_NOTIFICATION_KEY_PREFIX, escapeAttributionText, escapeAttributionAttribute, facetNotificationKey, isReservedNotificationKey } from "./attribution.js";
14
+ export { HOST_MESSAGE_NOTE_MAX, HOST_MESSAGE_LIST_MAX, isHostMessageSendRequest, isHostMessageListRequest, isHostMessageSendAnswer, isHostSessionStopRequest, isHostSessionStopAnswer, normaliseHostMessageListAnswer, hostSessionToListed, hostAnswerToOutcome, boundedHostNote, } from "./host.js";
15
+ export type { HostMessagingPort } from "./host.js";
16
+ export type { HostMessageSendRequest, HostMessageSendAnswer, HostMessageListRequest, HostMessageListAnswer, HostReachableSession, HostSessionStopRequest, HostSessionStopAnswer } from "../protocol/config.js";
14
17
  export { MAX_TRACKED_MESSAGE_IDS, rememberBounded, createMessagingRouterSeam, createSubscriberDirectory, callerAddress, sendMessage, listAgents, readNotifications, createMessagingRouter, } from "./router.js";
15
18
  export type { MessagingRouterSeamWithRoster, SubscriberDirectory, MessagingRuntimeDeps, CallerContext, SessionCallerContext, SendMessageInput, NotifyOutcome, SendMessageResult, ListAgentsInput, MessagingRouter, } from "./router.js";
@@ -7,6 +7,17 @@ import {
7
7
  buildChildAddress2,
8
8
  parseRuntimeAddress2,
9
9
  sameAddress2,
10
+ HOST_MESSAGE_NOTE_MAX2,
11
+ HOST_MESSAGE_LIST_MAX2,
12
+ isHostMessageSendRequest2,
13
+ isHostMessageListRequest2,
14
+ isHostSessionStopRequest2,
15
+ isHostSessionStopAnswer2,
16
+ isHostMessageSendAnswer2,
17
+ normaliseHostMessageListAnswer2,
18
+ hostSessionToListed2,
19
+ hostAnswerToOutcome2,
20
+ boundedHostNote2,
10
21
  MAX_GLOBAL_MESSAGE_SIZE2,
11
22
  DEFAULT_MESSAGE_TTL_MS2,
12
23
  MAX_HOP_COUNT2,
@@ -56,13 +67,15 @@ import {
56
67
  listAgents2,
57
68
  readNotifications2,
58
69
  createMessagingRouter2
59
- } from "../index-51ysrfm8.js";
70
+ } from "../index-3zhkc4nb.js";
60
71
  export {
61
72
  ACCEPTED_QUEUE_CAP2 as ACCEPTED_QUEUE_CAP,
62
73
  AGENT_MESSAGE_TAG2 as AGENT_MESSAGE_TAG,
63
74
  DEFAULT_HOLD_EXPIRY_MS2 as DEFAULT_HOLD_EXPIRY_MS,
64
75
  DEFAULT_MESSAGE_TTL_MS2 as DEFAULT_MESSAGE_TTL_MS,
65
76
  HELD_INBOX_CAP2 as HELD_INBOX_CAP,
77
+ HOST_MESSAGE_LIST_MAX2 as HOST_MESSAGE_LIST_MAX,
78
+ HOST_MESSAGE_NOTE_MAX2 as HOST_MESSAGE_NOTE_MAX,
66
79
  MAX_GLOBAL_MESSAGE_SIZE2 as MAX_GLOBAL_MESSAGE_SIZE,
67
80
  MAX_HOP_COUNT2 as MAX_HOP_COUNT,
68
81
  MAX_TRACKED_MESSAGE_IDS2 as MAX_TRACKED_MESSAGE_IDS,
@@ -71,6 +84,7 @@ export {
71
84
  REFERENCE_RUNTIME_KIND2 as REFERENCE_RUNTIME_KIND,
72
85
  RESERVED_NOTIFICATION_KEY_PREFIX2 as RESERVED_NOTIFICATION_KEY_PREFIX,
73
86
  ambiguous2 as ambiguous,
87
+ boundedHostNote2 as boundedHostNote,
74
88
  buildChildAddress2 as buildChildAddress,
75
89
  buildDefaultHoldEntry2 as buildDefaultHoldEntry,
76
90
  buildExplicitHoldEntry2 as buildExplicitHoldEntry,
@@ -94,12 +108,20 @@ export {
94
108
  facetNotificationKey2 as facetNotificationKey,
95
109
  held2 as held,
96
110
  hopCountExceeded2 as hopCountExceeded,
111
+ hostAnswerToOutcome2 as hostAnswerToOutcome,
112
+ hostSessionToListed2 as hostSessionToListed,
113
+ isHostMessageListRequest2 as isHostMessageListRequest,
114
+ isHostMessageSendAnswer2 as isHostMessageSendAnswer,
115
+ isHostMessageSendRequest2 as isHostMessageSendRequest,
116
+ isHostSessionStopAnswer2 as isHostSessionStopAnswer,
117
+ isHostSessionStopRequest2 as isHostSessionStopRequest,
97
118
  isIdleSubscribeSenderAllowed2 as isIdleSubscribeSenderAllowed,
98
119
  isIdleSubscribeTargetAllowed2 as isIdleSubscribeTargetAllowed,
99
120
  isReservedNotificationKey2 as isReservedNotificationKey,
100
121
  listAgents2 as listAgents,
101
122
  mapFromModeToPermissionClass2 as mapFromModeToPermissionClass,
102
123
  messageExceedsMaxSize2 as messageExceedsMaxSize,
124
+ normaliseHostMessageListAnswer2 as normaliseHostMessageListAnswer,
103
125
  notFound2 as notFound,
104
126
  parseRuntimeAddress2 as parseRuntimeAddress,
105
127
  queued2 as queued,
@@ -1,4 +1,5 @@
1
1
  import { type RuntimeAddress, type ListedRuntimeObject, type DeliveryOutcome, type RuntimeMessagingAdapter, type MessagingRouterSeam, type ChildLike } from "./adapter.js";
2
+ import { type HostMessagingPort } from "./host.js";
2
3
  import type { NotificationQueue, NotificationRecord } from "./idle.js";
3
4
  import { type LoopGuard } from "./outcomes.js";
4
5
  export interface MessagingRouterSeamWithRoster extends MessagingRouterSeam {
@@ -34,6 +35,14 @@ export interface MessagingRuntimeDeps {
34
35
  * "everything is uncertain", which is exactly the conservative reading.
35
36
  */
36
37
  classifyDeliveryError?(err: unknown): "refused" | "uncertain";
38
+ /**
39
+ * Host messaging (`Options.hostMessaging`): the port to the HOST's other sessions for the OWNING
40
+ * session `owningSessionId` (a subagent's calls use its parent's, since they share the session id),
41
+ * or `undefined` when that session has none -- which is every session of a standalone SDK user, and
42
+ * every session of a host that composes this core itself (the router package). Consulted only after
43
+ * in-process resolution answered `not_found` (SendMessage) and to add rows (ListAgents).
44
+ */
45
+ hostMessaging?(owningSessionId: string): HostMessagingPort | undefined;
37
46
  }
38
47
  export interface CallerContext {
39
48
  sessionId: string;
@@ -57,8 +66,11 @@ export interface NotifyOutcome {
57
66
  export interface SendMessageResult {
58
67
  outcome: DeliveryOutcome;
59
68
  notify?: NotifyOutcome;
69
+ note?: string;
60
70
  }
61
- export declare function sendMessage(deps: MessagingRuntimeDeps, caller: CallerContext, input: SendMessageInput): Promise<SendMessageResult>;
71
+ export declare function sendMessage(deps: MessagingRuntimeDeps, caller: CallerContext, input: SendMessageInput, opts?: {
72
+ signal?: AbortSignal;
73
+ }): Promise<SendMessageResult>;
62
74
  export interface ListAgentsInput {
63
75
  channel?: string;
64
76
  q?: string;
@@ -76,9 +88,14 @@ export declare function formatListing(rows: readonly ListedRuntimeObject[]): str
76
88
  export interface SessionCallerContext {
77
89
  sessionId: string;
78
90
  }
79
- export declare function listAgents(deps: MessagingRuntimeDeps, caller: SessionCallerContext, _input: ListAgentsInput): Promise<{
91
+ /** The line a listing ends with when the host left reachable sessions out (its cap), so nothing is silently cut. */
92
+ export declare function omittedLine(omitted: number): string;
93
+ export declare function listAgents(deps: MessagingRuntimeDeps, caller: SessionCallerContext, _input: ListAgentsInput, opts?: {
94
+ signal?: AbortSignal;
95
+ }): Promise<{
80
96
  listing: string;
81
97
  rows: ListedRuntimeObject[];
98
+ omitted?: number;
82
99
  }>;
83
100
  export declare function readNotifications(deps: MessagingRuntimeDeps, caller: SessionCallerContext): {
84
101
  notifications: NotificationRecord[];
@@ -90,6 +107,7 @@ export interface MessagingRouter {
90
107
  listAgents(caller: SessionCallerContext, input: ListAgentsInput): Promise<{
91
108
  listing: string;
92
109
  rows: ListedRuntimeObject[];
110
+ omitted?: number;
93
111
  }>;
94
112
  readNotifications(caller: SessionCallerContext): {
95
113
  notifications: NotificationRecord[];
package/dist/options.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { SpawnClaudeCodeProcess } from "./transport.js";
2
2
  import type { PermissionMode, CanUseTool, HookEvent, HookCallbackMatcher } from "./permissions/types.js";
3
- import type { SandboxSettingsConfig, McpServerToolPolicy, McpStdioServerConfig, McpHttpServerConfig, McpSSEServerConfig, McpSdkServerConfig, RuntimeAgentDefinition, SdkPluginConfig, SystemPromptOption, OutputFormat, SkillsOption, ProviderSelection, ThinkingConfig, EffortLevel, AutoClassifierConfig, AdvisorConfig, WebToolsConfig, WebPrivateAddressPolicy, AutoMemoryConfig, CredentialRef, McpOAuthRefreshRequest, McpOAuthRefreshAnswer, CredentialResolveRequest, CredentialResolveAnswer } from "./protocol/config.js";
3
+ import type { SandboxSettingsConfig, McpServerToolPolicy, McpStdioServerConfig, McpHttpServerConfig, McpSSEServerConfig, McpSdkServerConfig, RuntimeAgentDefinition, SdkPluginConfig, SystemPromptOption, OutputFormat, SkillsOption, ProviderSelection, ThinkingConfig, EffortLevel, AutoClassifierConfig, AdvisorConfig, WebToolsConfig, WebPrivateAddressPolicy, AutoMemoryConfig, CredentialRef, McpOAuthRefreshRequest, McpOAuthRefreshAnswer, CredentialResolveRequest, CredentialResolveAnswer, HostMessageSendRequest, HostMessageSendAnswer, HostMessageListRequest, HostMessageListAnswer, HostSessionStopRequest, HostSessionStopAnswer } from "./protocol/config.js";
4
4
  import type { SettingSource } from "./settings/types.js";
5
5
  import type { SessionStore } from "./store/session-store.js";
6
6
  import { type BrandProfile } from "./brand.js";
@@ -45,6 +45,33 @@ export declare const TEST_KEYCHAIN_MEMORY = "memory";
45
45
  export declare const MCP_OAUTH_REFRESH_SUBTYPE = "mcp_oauth_refresh";
46
46
  /** WS-25 §7: the runtime -> host control subtype a host-brokered session resolves a Keychain credential with (`CredentialResolveRequest` -> `CredentialResolveAnswer`). */
47
47
  export declare const CREDENTIAL_RESOLVE_SUBTYPE = "credential_resolve";
48
+ /** Host messaging: the runtime -> host control subtype `SendMessage` asks its host to deliver with (`HostMessageSendRequest` -> `HostMessageSendAnswer`). */
49
+ export declare const HOST_MESSAGE_SEND_SUBTYPE = "host_message_send";
50
+ /** Host messaging: the runtime -> host control subtype `ListAgents` asks its host what it can reach with (`HostMessageListRequest` -> `HostMessageListAnswer`). */
51
+ export declare const HOST_MESSAGE_LIST_SUBTYPE = "host_message_list";
52
+ /** Host messaging: the runtime -> host control subtype `TaskStop` asks its host to stop one of the host's sessions with (`HostSessionStopRequest` -> `HostSessionStopAnswer`). */
53
+ export declare const HOST_SESSION_STOP_SUBTYPE = "host_session_stop";
54
+ /**
55
+ * Host messaging, WINTER-ONLY: the handler a host that runs MANY sessions gives one session so its
56
+ * `SendMessage` and `ListAgents` reach the host's other sessions. See `Options.hostMessaging`.
57
+ */
58
+ export interface HostMessagingHandler {
59
+ /** Deliver one message this session's `SendMessage` could not resolve in-process. Never throws for a policy answer -- refuse with a typed status. */
60
+ send(request: HostMessageSendRequest, options: {
61
+ signal: AbortSignal;
62
+ }): Promise<HostMessageSendAnswer>;
63
+ /** The sessions this session can reach through the host, for `ListAgents` (beside its own subagents). */
64
+ list(request: HostMessageListRequest, options: {
65
+ signal: AbortSignal;
66
+ }): Promise<HostMessageListAnswer>;
67
+ /**
68
+ * Stop (interrupt the running turn of) one of the host's sessions, for a `TaskStop` whose `task_id`
69
+ * names no task of this session. Optional: a host without it answers every such request `not_found`.
70
+ */
71
+ stop?(request: HostSessionStopRequest, options: {
72
+ signal: AbortSignal;
73
+ }): Promise<HostSessionStopAnswer>;
74
+ }
48
75
  /**
49
76
  * Every web-tool default, spelled ONCE. A reader function resolves an absent field against this --
50
77
  * no consumer writes a literal of its own.
@@ -155,6 +182,29 @@ export interface Options {
155
182
  familyMetadata?: {
156
183
  taskNative?: boolean;
157
184
  };
185
+ /**
186
+ * claude's own `tools` option: the BUILT-IN tool set, by name -- an array, or the `claude_code` preset
187
+ * (every built-in, the same as leaving it out). It is a VISIBILITY list, unlike `allowedTools` (which
188
+ * pre-approves): a built-in left out is not advertised, not searchable, and refused at dispatch as a
189
+ * tool the model was not offered. MCP servers' tools (including an in-process server's plain-named
190
+ * ones, `McpSdkServerConfig.toolNames`) are never filtered by it -- name only built-ins here. Leaving
191
+ * `ToolSearch` out switches deferral off, as in claude. Opt-in built-ins (`Search`) are advertised only
192
+ * when this list names them. See `RuntimeConfig.tools`.
193
+ */
194
+ tools?: string[] | {
195
+ type: "preset";
196
+ preset: "claude_code";
197
+ };
198
+ /**
199
+ * DISCLOSED WINTER option: tools that start DEFERRED (loaded through `ToolSearch` on first use) while
200
+ * Tool Search is active -- the host's way to defer a BUILT-IN (`CronList`), which otherwise never
201
+ * defers. See `RuntimeConfig.deferTools`.
202
+ */
203
+ deferTools?: string[];
204
+ /** DISCLOSED WINTER option: a tool's old names, `{ <old>: <current> }`. See `RuntimeConfig.legacyToolNames`. */
205
+ legacyToolNames?: Record<string, string>;
206
+ /** DISCLOSED WINTER option: MCP server names only the host's own in-process servers may use. See `RuntimeConfig.reservedMcpServerNames`. */
207
+ reservedMcpServerNames?: string[];
158
208
  allowDangerouslySkipPermissions?: boolean;
159
209
  canUseTool?: CanUseTool;
160
210
  hooks?: Partial<Record<HookEvent, HookCallbackMatcher[]>>;
@@ -233,6 +283,29 @@ export interface Options {
233
283
  onCredentialResolve?: (request: CredentialResolveRequest, options: {
234
284
  signal: AbortSignal;
235
285
  }) => Promise<CredentialResolveAnswer>;
286
+ /**
287
+ * Host messaging, WINTER-ONLY: this session's line to the host's OTHER sessions. When set, `query()`
288
+ * puts `hostMessaging: true` on the wire and answers the runtime's `host_message_send` /
289
+ * `host_message_list` control requests with it -- identically for a spawned `winter` process and an
290
+ * embedded Worker, since both speak the same frame stream to this wrapper.
291
+ *
292
+ * - `SendMessage` resolves `to` in-process first (this session's subagents, then the in-process peer
293
+ * directory). Only a `not_found` there is handed to `send`; a subagent, a self-target, a stale or an
294
+ * ambiguous name never reaches the host. The host's typed answer is the tool's result, under the
295
+ * runtime's own message id (so a retry of the same tool call short-circuits on the stored outcome).
296
+ * - `ListAgents` lists this session's subagents and then whatever `list` returns, as `session` rows
297
+ * (with the host's `omitted` count, so nothing is silently cut).
298
+ * - `TaskStop` with a `task_id` that names no task of this session is handed to `stop` (when given):
299
+ * the host interrupts that session's running turn.
300
+ *
301
+ * The handler is per session: it knows its caller by construction, and the request never names the
302
+ * sender (only `fromAgentId`, information about which subagent of THIS session asked). A throwing
303
+ * `send` answers `delivery_uncertain`; a throwing `list` lists nothing.
304
+ *
305
+ * Absent: `SendMessage` and `ListAgents` reach exactly what the session's own process holds, as before.
306
+ * Never serialized (a JS object of functions).
307
+ */
308
+ hostMessaging?: HostMessagingHandler;
236
309
  systemPrompt?: SystemPromptOption;
237
310
  plugins?: SdkPluginConfig[];
238
311
  skills?: SkillsOption;
@@ -231,6 +231,100 @@ export type CredentialResolveAnswer = {
231
231
  ok: false;
232
232
  reason: "not_found" | "not_allowed" | "stale" | "unavailable";
233
233
  };
234
+ /**
235
+ * Host messaging, WINTER-ONLY: the runtime -> host `host_message_send` control request (subtype
236
+ * `HOST_MESSAGE_SEND_SUBTYPE`), sent only when `RuntimeConfig.hostMessaging` is set.
237
+ *
238
+ * WHY. A session's `SendMessage` resolves `to` against what its own process can reach: the session's
239
+ * subagents and the in-process peer directory, which holds the calling session alone. A HOST that runs
240
+ * many sessions (the Winter daemon: one process or one Worker per session) owns every other one, so the
241
+ * runtime asks it when -- and only when -- local resolution finds nothing (`not_found`). Subagents,
242
+ * self-target, a stale or ambiguous name are all still answered in-process and never reach the host.
243
+ *
244
+ * - `to` -- EXACTLY what the model wrote (a host session id, a `session:<id>` address from ListAgents,
245
+ * a name the host recognises). Resolution is the host's.
246
+ * - `messageId` -- the runtime's own id for this call, stable across a retry of the same tool call (the
247
+ * runtime's ledger short-circuits a retry before it ever asks again; the host MAY also dedupe on it).
248
+ * - `fromAgentId` -- set when one of this session's SUBAGENTS is the sender. Information only: the host
249
+ * knows which session is asking from which session's handler answered, and must never take the sender
250
+ * from the payload.
251
+ */
252
+ export interface HostMessageSendRequest {
253
+ to: string;
254
+ message: string;
255
+ summary?: string;
256
+ notifyWhenIdle?: boolean;
257
+ messageId: string;
258
+ fromAgentId?: string;
259
+ }
260
+ /**
261
+ * The host's answer. `status` is one of the delivery outcomes a host can honestly report; the runtime
262
+ * stamps its own `messageId` on it and records it in its ledger.
263
+ *
264
+ * - `delivered` (the target is reading it now), `queued` (behind a turn already running),
265
+ * `resumed_and_delivered` (the target was not running and the host resumed it for this message);
266
+ * - `refused` / `not_found` -- `reason` required; `unavailable` -- `reason` required, `retryable`
267
+ * optional (default `false`); `delivery_uncertain` -- `reason` required (the effect may have happened).
268
+ *
269
+ * `notify` is the separate fact of whether `notifyWhenIdle` was honoured, rendered beside the outcome
270
+ * exactly like the in-process router's combined call. `note` is ONE short sentence for the model beside
271
+ * the outcome (e.g. whether it will be told when the target finishes) -- never a second outcome.
272
+ */
273
+ export interface HostMessageSendAnswer {
274
+ status: "delivered" | "queued" | "resumed_and_delivered" | "refused" | "not_found" | "unavailable" | "delivery_uncertain";
275
+ reason?: string;
276
+ retryable?: boolean;
277
+ notify?: {
278
+ subscribed?: true;
279
+ refused?: string;
280
+ };
281
+ note?: string;
282
+ }
283
+ /** `host_message_list` (subtype `HOST_MESSAGE_LIST_SUBTYPE`): what this session can reach through its host, for `ListAgents`. */
284
+ export interface HostMessageListRequest {
285
+ /** As on `HostMessageSendRequest`: set when a subagent is asking. Information only. */
286
+ fromAgentId?: string;
287
+ }
288
+ /**
289
+ * One session the host lists. `address` is what the model passes back as `to` (it must satisfy
290
+ * `SendMessage`'s own `to` rules: non-empty, at most 300 characters, no newline, no `*`); `name` is a
291
+ * display name (a title). The runtime renders each as an ordinary `session` row of ListAgents.
292
+ */
293
+ export interface HostReachableSession {
294
+ address: string;
295
+ name?: string;
296
+ status: "starting" | "running" | "idle" | "exited" | "unavailable" | "archived";
297
+ /** The session's product mode (`code`, `dispatch`, …). */
298
+ mode: string;
299
+ cwd?: string;
300
+ }
301
+ export interface HostMessageListAnswer {
302
+ sessions: HostReachableSession[];
303
+ /** How many MORE reachable sessions the host did not list (its own cap). Rendered as a count, so a listing is never silently truncated. */
304
+ omitted?: number;
305
+ }
306
+ /**
307
+ * Host messaging: the runtime -> host `host_session_stop` control request (subtype
308
+ * `HOST_SESSION_STOP_SUBTYPE`). `TaskStop` sends it when its `task_id` names no task of this session (a
309
+ * background command, a subagent, a workflow) -- i.e. when the id may be one of the HOST's sessions.
310
+ * Stopping a session interrupts the turn it is running; it never deletes or archives anything.
311
+ * `fromAgentId` as on `HostMessageSendRequest`: information only.
312
+ */
313
+ export interface HostSessionStopRequest {
314
+ id: string;
315
+ fromAgentId?: string;
316
+ }
317
+ /**
318
+ * `stopped` (a running turn was interrupted) and `not_running` (nothing to stop) are ordinary answers;
319
+ * `refused`, `not_found` and `unavailable` carry a required `reason` and reach the model as errors.
320
+ */
321
+ export interface HostSessionStopAnswer {
322
+ status: "stopped" | "not_running" | "refused" | "not_found" | "unavailable";
323
+ reason?: string;
324
+ /** One sentence for the model beside a `stopped` / `not_running` result — e.g. what happens to messages
325
+ * that were queued behind the interrupted turn. Rendered into TaskStop's `message`. */
326
+ note?: string;
327
+ }
234
328
  export interface McpStdioServerConfig {
235
329
  type?: "stdio";
236
330
  command: string;
@@ -279,6 +373,28 @@ export interface McpSdkServerConfig {
279
373
  name: string;
280
374
  timeout?: number;
281
375
  tools?: WireMcpToolDefinition[];
376
+ /**
377
+ * WINTER-OWNED EXTENSION: advertise some of this in-process server's tools under a HOST-CHOSEN plain
378
+ * name instead of `mcp__<server>__<tool>` -- `{ <tool as listTools() names it>: <plain name> }`.
379
+ *
380
+ * A renamed tool is an ordinary-looking tool to the model (`Browser`, never `mcp__…`) and that ONE name
381
+ * is its identity everywhere a name is carried: the provider request, the transcript, the host's frames,
382
+ * hook inputs' `tool_name`, `canUseTool`, `permission_denials`. Underneath it is still this server's MCP
383
+ * tool: it defers like one (unless `_meta["anthropic/alwaysLoad"]`), the call still arrives at the host
384
+ * as `sdk_mcp_call {server, tool}` with the ORIGINAL tool name, and `winter_mcp_server` / `canUseTool`'s
385
+ * `mcpServer` still name the server. The old `mcp__<server>__<tool>` spelling stays an EQUIVALENT
386
+ * identity: a permission rule (`mcp__<server>__<tool>`, `mcp__<server>__*`), a bare `disallowedTools`
387
+ * entry or a hook matcher written against it governs the renamed tool too (strictest-of, as for an
388
+ * alias) -- and, as for an alias, the call is then evaluated UNDER that spelling: the hook it selected
389
+ * and the prompt its ask rule raised carry `mcp__<server>__<tool>` as `tool_name`. A model call under the
390
+ * old spelling (a resumed history) runs as the renamed tool.
391
+ *
392
+ * A plain name must look like a tool name (`[A-Za-z][A-Za-z0-9_-]*`, at most 64 characters), must not
393
+ * start with `mcp__`, and must not collide with any other registered tool -- a collision refuses the
394
+ * session at startup rather than shadowing a tool. Only an in-process (`sdk`) server may rename; the
395
+ * host owns it.
396
+ */
397
+ toolNames?: Record<string, string>;
282
398
  }
283
399
  export type McpServerConfigForProcessTransport = McpStdioServerConfig | McpHttpServerConfig | McpSSEServerConfig | McpSdkServerConfig;
284
400
  export type AgentMcpServerSpec = string | Record<string, McpServerConfigForProcessTransport>;
@@ -389,6 +505,44 @@ export interface RuntimeConfig {
389
505
  familyMetadata?: {
390
506
  taskNative?: boolean;
391
507
  };
508
+ /**
509
+ * `Options.tools` (claude's own option): the BUILT-IN tool set this session is offered, by name.
510
+ * Absent = every built-in (claude's `claude_code` preset, which the wrapper serializes as absent).
511
+ * Filters built-ins only -- an MCP server's tools, a host's plain-named in-process tools and
512
+ * host-generated tools (`StructuredOutput`) are never named here and never filtered by it. A built-in
513
+ * left out is not advertised, not in ToolSearch's pool and refused at dispatch as a tool the model
514
+ * was not offered. `ToolSearch` left out switches deferral off (claude's rule: no search tool, no
515
+ * deferred tools). A few built-ins (`Search`) are OPT-IN: advertised only when this list names them.
516
+ */
517
+ tools?: string[];
518
+ /**
519
+ * Winter extension: tools that START DEFERRED -- loaded through `ToolSearch` on first use -- when Tool
520
+ * Search is active. A built-in otherwise never defers; an MCP tool already does (unless its
521
+ * `_meta["anthropic/alwaysLoad"]`, which still wins). `ToolSearch` itself is never deferred.
522
+ * Inert while deferral is inactive (full injection).
523
+ */
524
+ deferTools?: string[];
525
+ /**
526
+ * Winter extension: a tool's OLD names -- `{ <old name>: <current tool name> }` -- for a host whose
527
+ * own tool became another one (a host's in-process `mcp__<server>__Search` became the `Search`
528
+ * built-in). The old name keeps working everywhere a name is read: a model call under it (a resumed
529
+ * history taught it) runs the current tool, `ToolSearch`'s `select:` finds it, an agent definition's
530
+ * `tools` list keeps it, a rule, bare `disallowedTools` entry or hook matcher naming it governs the
531
+ * current tool (strictest-of, as for an alias). A plain-named in-process tool's own
532
+ * `mcp__<server>__<tool>` spelling needs no entry here: it is resolved the same way by itself. A key
533
+ * that names a registered tool is ignored (the live tool wins).
534
+ */
535
+ legacyToolNames?: Record<string, string>;
536
+ /**
537
+ * Winter extension: MCP server NAMES no server may take in this session except the host's own
538
+ * in-process (`type: "sdk"`) servers in `mcpServers` -- whatever its origin: a settings scope, a plugin's
539
+ * `.mcp.json`, an explicit non-sdk entry, the live `mcp_set_servers` door (each refused typed
540
+ * `reserved_name` and never connected), and an agent definition's inline server (connected under a
541
+ * renamed `<name>_<n>`, as for any clash; an inline in-process one under a reserved name is not
542
+ * connected). For a host whose own tools are classified by their server (trust keyed on
543
+ * `mcp__<server>__*`), so no foreign server can wear that spelling.
544
+ */
545
+ reservedMcpServerNames?: string[];
392
546
  mcpServers?: Record<string, McpServerConfigForProcessTransport>;
393
547
  strictMcpConfig?: boolean;
394
548
  toolAliases?: Record<string, string>;
@@ -478,6 +632,13 @@ export interface RuntimeConfig {
478
632
  * runtime's own Keychain store, as before (a standalone SDK user). A flag, never material.
479
633
  */
480
634
  hostCredentials?: boolean;
635
+ /**
636
+ * Host messaging, WINTER-ONLY: `true` when the host answers `host_message_send` / `host_message_list`
637
+ * (`Options.hostMessaging`). `SendMessage` then asks the host to deliver whatever it cannot resolve
638
+ * in-process, and `ListAgents` adds the sessions the host lists. Absent: in-process only, as before.
639
+ * A flag, never a handler.
640
+ */
641
+ hostMessaging?: boolean;
481
642
  autoClassifier?: AutoClassifierConfig;
482
643
  advisor?: AdvisorConfig;
483
644
  /** The wire twin of `Options.web` -- see `WebToolsConfig`. Pure passthrough; absent means every default in `WEB_TOOLS_DEFAULTS`. */
@@ -306,6 +306,16 @@ export type WireContentBlock = {
306
306
  tool_use_id: string;
307
307
  content: string | WireContentBlock[];
308
308
  is_error?: boolean;
309
+ /**
310
+ * Winter-only, HOST-facing frame only (never model-visible, never in a transcript): the sites a web
311
+ * tool's result names and the icon the tool knows for each -- `WebFetch`'s page icon (the page's
312
+ * own declared `<link rel=icon>`, else its origin's `/favicon.ico`), `WebSearch`'s Exa `favicon`.
313
+ * At most 10 entries; every url https. Absent on every other result.
314
+ */
315
+ winter_site_icons?: Array<{
316
+ url: string;
317
+ icon_url: string;
318
+ }>;
309
319
  [k: string]: unknown;
310
320
  } | {
311
321
  type: "image";
@@ -3,8 +3,9 @@ import {
3
3
  callerAddress2,
4
4
  sendMessage2,
5
5
  formatListing,
6
+ omittedLine,
6
7
  listAgents2
7
- } from "../index-51ysrfm8.js";
8
+ } from "../index-3zhkc4nb.js";
8
9
  import {
9
10
  resolveWinterHome2,
10
11
  WinterCompatibilitySessionStore2
@@ -220,12 +221,16 @@ function messagingToolPortFromRuntimeDeps(deps) {
220
221
  message: request.body,
221
222
  ...request.summary === undefined ? {} : { summary: request.summary },
222
223
  ...request.notifyWhenIdle === undefined ? {} : { notify_when_idle: request.notifyWhenIdle }
223
- });
224
+ }, request.signal !== undefined ? { signal: request.signal } : {});
224
225
  },
225
226
  async listReachable(scope) {
226
227
  const { rows } = await listAgents2(deps, { sessionId: scope.from.parentWinterSessionId ?? scope.from.winterSessionId }, {});
227
228
  return rows;
228
229
  },
230
+ async listReachableDetailed(scope) {
231
+ const { rows, omitted } = await listAgents2(deps, { sessionId: scope.from.parentWinterSessionId ?? scope.from.winterSessionId }, {}, scope.signal !== undefined ? { signal: scope.signal } : {});
232
+ return { rows, ...omitted !== undefined ? { omitted } : {} };
233
+ },
229
234
  readNotifications(sessionId) {
230
235
  return deps.notifications.drain(sessionId);
231
236
  }
@@ -274,9 +279,10 @@ function createMessagingToolHandlers(port, caller) {
274
279
  body: accepted.args.message,
275
280
  ...summary === undefined ? {} : { summary },
276
281
  ...accepted.args.notify_when_idle === undefined ? {} : { notifyWhenIdle: accepted.args.notify_when_idle },
277
- ...who.toolUseId === undefined ? {} : { originToolCallId: who.toolUseId }
282
+ ...who.toolUseId === undefined ? {} : { originToolCallId: who.toolUseId },
283
+ ...who.signal === undefined ? {} : { signal: who.signal }
278
284
  });
279
- const payload = result.notify === undefined ? result.outcome : { ...result.outcome, notify: result.notify };
285
+ const payload = { ...result.outcome, ...result.notify === undefined ? {} : { notify: result.notify }, ...result.note === undefined ? {} : { note: result.note } };
280
286
  return text(JSON.stringify(payload), MODEL_FACING_FAILURES.has(result.outcome.status));
281
287
  });
282
288
  },
@@ -285,8 +291,13 @@ function createMessagingToolHandlers(port, caller) {
285
291
  if (!accepted.ok)
286
292
  return text(accepted.reason, true);
287
293
  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) }));
294
+ const who = identity();
295
+ const from = callerAddress2(who);
296
+ const detailed = port.listReachableDetailed !== undefined ? await port.listReachableDetailed({ from, ...who.signal !== undefined ? { signal: who.signal } : {} }) : { rows: await port.listReachable({ from }) };
297
+ const rows = detailed.rows;
298
+ const omitted = detailed.omitted ?? 0;
299
+ return text(JSON.stringify({ listing: omitted > 0 ? `${formatListing(rows)}
300
+ ${omittedLine(omitted)}` : formatListing(rows) }));
290
301
  });
291
302
  },
292
303
  async readNotifications(rawArgs) {
@@ -15,6 +15,8 @@ export interface WinterToolCaller {
15
15
  sessionId: string;
16
16
  agentId?: string;
17
17
  toolUseId?: string;
18
+ /** The calling tool's own abort signal, when the host binds the caller per call (host messaging cancels a pending delivery with it). */
19
+ signal?: AbortSignal;
18
20
  }
19
21
  /** The host-neutral tool result: one text body, plus whether the model should read it as a failure. */
20
22
  export interface WinterToolResult {
@@ -1,5 +1,6 @@
1
1
  import { callerAddress, type ListedRuntimeObject, type MessagingRuntimeDeps, type NotificationRecord, type RuntimeAddress, type SendMessageResult } from "../messaging/index.js";
2
2
  export interface MessagingToolPort {
3
+ /** `signal`: the calling tool's own (host messaging cancels a pending host delivery with it). */
3
4
  sendDetailed(request: {
4
5
  from: RuntimeAddress;
5
6
  to: string;
@@ -7,10 +8,19 @@ export interface MessagingToolPort {
7
8
  summary?: string;
8
9
  notifyWhenIdle?: boolean;
9
10
  originToolCallId?: string;
11
+ signal?: AbortSignal;
10
12
  }): Promise<SendMessageResult>;
11
13
  listReachable(scope: {
12
14
  from: RuntimeAddress;
13
15
  }): Promise<ListedRuntimeObject[]>;
16
+ /** Optional: the rows plus how many reachable ones were NOT listed (a host's cap). Preferred by the ListAgents handler when present. */
17
+ listReachableDetailed?(scope: {
18
+ from: RuntimeAddress;
19
+ signal?: AbortSignal;
20
+ }): Promise<{
21
+ rows: ListedRuntimeObject[];
22
+ omitted?: number;
23
+ }>;
14
24
  readNotifications(sessionId: string): {
15
25
  notifications: NotificationRecord[];
16
26
  remaining: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.36",
3
+ "version": "0.0.39",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,15 +45,15 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@yanlinglabs/winter-provider-catalog": "0.0.36"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.39"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.36"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.39"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^26.4.0",
55
- "@yanlinglabs/winter-conformance": "0.0.36",
56
- "@yanlinglabs/winter-agent-runtime": "0.0.36"
55
+ "@yanlinglabs/winter-agent-runtime": "0.0.39",
56
+ "@yanlinglabs/winter-conformance": "0.0.39"
57
57
  },
58
58
  "scripts": {}
59
59
  }