@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 +59 -0
- package/dist/{index-51ysrfm8.js → index-3zhkc4nb.js} +183 -8
- package/dist/index.d.ts +3 -2
- package/dist/index.js +99 -3
- package/dist/messaging/host.d.ts +42 -0
- package/dist/messaging/index.d.ts +3 -0
- package/dist/messaging/index.js +23 -1
- package/dist/messaging/router.d.ts +20 -2
- package/dist/options.d.ts +74 -1
- package/dist/protocol/config.d.ts +161 -0
- package/dist/protocol/frames.d.ts +10 -0
- package/dist/tools/index.js +17 -6
- package/dist/tools/messaging-handlers.d.ts +2 -0
- package/dist/tools/port.d.ts +10 -0
- package/package.json +5 -5
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 ? {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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";
|
package/dist/messaging/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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";
|
package/dist/tools/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
callerAddress2,
|
|
4
4
|
sendMessage2,
|
|
5
5
|
formatListing,
|
|
6
|
+
omittedLine,
|
|
6
7
|
listAgents2
|
|
7
|
-
} from "../index-
|
|
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 ?
|
|
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
|
|
289
|
-
|
|
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 {
|
package/dist/tools/port.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
48
|
+
"@yanlinglabs/winter-provider-catalog": "0.0.39"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.
|
|
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-
|
|
56
|
-
"@yanlinglabs/winter-
|
|
55
|
+
"@yanlinglabs/winter-agent-runtime": "0.0.39",
|
|
56
|
+
"@yanlinglabs/winter-conformance": "0.0.39"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {}
|
|
59
59
|
}
|