@yanlinglabs/winter-agent-sdk 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,6 +38,36 @@ files, `README.md` and `LICENSE`. It does **not** ship `src/`: the TypeScript so
38
38
  <https://github.com/yanlingLabs/winter-agent-sdk>, which is where to read them, file an issue, or send
39
39
  a patch.
40
40
 
41
+ ## Subpaths
42
+
43
+ The main entry is the wrapper surface above. Two subpaths ship beside it, for hosts that compose
44
+ Winter rather than only call it:
45
+
46
+ **`@yanlinglabs/winter-agent-sdk/messaging`** (since 0.0.2) — the cross-runtime messaging contract
47
+ and its router core: addresses, listings, delivery outcomes, the adapter interface, inbound policy,
48
+ the `notify_when_idle` subscription store, and the three orchestration functions a host drives them
49
+ with. It ships no adapter and no process-level singleton; those are host composition.
50
+
51
+ **`@yanlinglabs/winter-agent-sdk/tools`** (since 0.0.3) — Winter's default tools, declared and
52
+ implemented once:
53
+
54
+ - `WINTER_DEFAULT_TOOL_DEFINITIONS` — `send_message`, `list_agents`, `read_notifications` and
55
+ `advisor`, each a `WinterToolDefinition` carrying a BARE `toolName`, the official built-in alias
56
+ key in `builtinName`, the description, the schemas and a permission class. No registry policy
57
+ fields: a host supplies its own.
58
+ - the native schemas and their bounds (`NATIVE_SEND_MESSAGE_SCHEMA`, `SEND_MESSAGE_TO_MAX` and the
59
+ rest), plus strict acceptors — unknown arguments are refused, `summary` is truncated rather than
60
+ rejected, and a refusal is returned as data the model can correct from, never thrown.
61
+ - `MessagingToolPort` + `messagingToolPortFromRuntimeDeps`, and `createMessagingToolHandlers`, which
62
+ turn a messaging world into the three tool handlers over it.
63
+ - `createAdvisorToolHandler` and `transcriptSourceForSessionKey`, with the reviewer left to the host
64
+ to resolve.
65
+
66
+ A host BINDS these under its own names: the Winter runtime registers them as its native built-ins
67
+ plus two canonical standing-server twins, and `@yanlinglabs/winter-runtime-sdk` binds the same
68
+ definitions under Claude's built-in names. Handlers return `{ text, isError? }` for each host to
69
+ wrap in its own result shape.
70
+
41
71
  ## License
42
72
 
43
73
  MIT — see [`LICENSE`](./LICENSE), which ships in the published tarball.
@@ -0,0 +1,614 @@
1
+ // src/messaging/adapter.ts
2
+ function serializeRuntimeAddress2(addr) {
3
+ if (addr.objectKind === "session")
4
+ return `session:${addr.winterSessionId}`;
5
+ if (addr.childId === undefined) {
6
+ throw new Error("serializeRuntimeAddress: objectKind 'agent' requires childId");
7
+ }
8
+ const parent = addr.parentWinterSessionId ?? addr.winterSessionId;
9
+ return `agent:${parent}:${addr.childId}`;
10
+ }
11
+ function createFakeMessagingRouterSeam2() {
12
+ const outcomes = new Map;
13
+ const idsBySenderAndTool = new Map;
14
+ let children = [];
15
+ let counter = 0;
16
+ return {
17
+ outcomes,
18
+ setChildren(next) {
19
+ children = next;
20
+ },
21
+ allocateMessageId(senderSessionId, toolUseId) {
22
+ const key = `${senderSessionId}:${toolUseId}`;
23
+ const existing = idsBySenderAndTool.get(key);
24
+ if (existing !== undefined)
25
+ return existing;
26
+ const id = `msg-${++counter}`;
27
+ idsBySenderAndTool.set(key, id);
28
+ return id;
29
+ },
30
+ recordOutcome(messageId, outcome) {
31
+ outcomes.set(messageId, outcome);
32
+ },
33
+ lookupOutcome(messageId) {
34
+ return outcomes.get(messageId);
35
+ },
36
+ children() {
37
+ return children;
38
+ }
39
+ };
40
+ }
41
+ // src/messaging/addressing.ts
42
+ var REFERENCE_RUNTIME_KIND2 = "winter-agent";
43
+ var MAX_TO_LENGTH = 300;
44
+ function validateToField2(to) {
45
+ if (typeof to !== "string" || to.length === 0)
46
+ return { ok: false, message: "to must be a non-empty string" };
47
+ if (to.length > MAX_TO_LENGTH)
48
+ return { ok: false, message: `to must be at most ${MAX_TO_LENGTH} characters (got ${to.length})` };
49
+ if (to.includes(`
50
+ `) || to.includes("\r"))
51
+ return { ok: false, message: "to must not contain a newline" };
52
+ if (to.includes("*"))
53
+ return { ok: false, message: 'to must not contain "*" ("*" broadcast is forbidden, WS-10 §10.1)' };
54
+ return { ok: true };
55
+ }
56
+ function buildSessionAddress2(winterSessionId) {
57
+ return { objectKind: "session", runtimeKind: REFERENCE_RUNTIME_KIND2, winterSessionId };
58
+ }
59
+ function buildChildAddress2(parentWinterSessionId, childId) {
60
+ return { objectKind: "agent", runtimeKind: REFERENCE_RUNTIME_KIND2, winterSessionId: parentWinterSessionId, parentWinterSessionId, childId };
61
+ }
62
+ function parseRuntimeAddress2(serialized) {
63
+ if (serialized.startsWith("session:")) {
64
+ const id = serialized.slice("session:".length);
65
+ if (id.length === 0)
66
+ return;
67
+ return buildSessionAddress2(id);
68
+ }
69
+ if (serialized.startsWith("agent:")) {
70
+ const rest = serialized.slice("agent:".length);
71
+ const sep = rest.indexOf(":");
72
+ if (sep <= 0 || sep === rest.length - 1)
73
+ return;
74
+ const parent = rest.slice(0, sep);
75
+ const childId = rest.slice(sep + 1);
76
+ return buildChildAddress2(parent, childId);
77
+ }
78
+ return;
79
+ }
80
+ function sameAddress2(a, b) {
81
+ try {
82
+ return serializeRuntimeAddress2(a) === serializeRuntimeAddress2(b);
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ // src/messaging/outcomes.ts
88
+ var MAX_GLOBAL_MESSAGE_SIZE2 = 1e6;
89
+ var DEFAULT_MESSAGE_TTL_MS2 = 24 * 60 * 60 * 1000;
90
+ var MAX_HOP_COUNT2 = 10;
91
+ var RAPID_REPEAT_WINDOW_MS2 = 5000;
92
+ var HELD_INBOX_CAP2 = 100;
93
+ var ACCEPTED_QUEUE_CAP2 = 50;
94
+ var DEFAULT_HOLD_EXPIRY_MS2 = 5 * 60 * 1000;
95
+ var NOTIFY_IDLE_EXPIRY_MS2 = 12 * 60 * 60 * 1000;
96
+ function messageExceedsMaxSize2(body) {
97
+ return body.length > MAX_GLOBAL_MESSAGE_SIZE2;
98
+ }
99
+ function hopCountExceeded2(hopCount) {
100
+ return hopCount >= MAX_HOP_COUNT2;
101
+ }
102
+ function delivered2(messageId) {
103
+ return { status: "delivered", messageId };
104
+ }
105
+ function queued2(messageId) {
106
+ return { status: "queued", messageId };
107
+ }
108
+ function resumedAndDelivered2(messageId) {
109
+ return { status: "resumed_and_delivered", messageId };
110
+ }
111
+ function held2(messageId, reason) {
112
+ return { status: "held", messageId, reason };
113
+ }
114
+ function subscribed2(messageId) {
115
+ return { status: "subscribed", messageId };
116
+ }
117
+ function deliveryUncertain2(messageId, reason) {
118
+ return { status: "delivery_uncertain", messageId, deliveryMayHaveOccurred: true, reason };
119
+ }
120
+ function refused2(messageId, reason) {
121
+ return { status: "refused", messageId, reason };
122
+ }
123
+ function ambiguous2(messageId, candidates) {
124
+ return { status: "ambiguous", messageId, candidates };
125
+ }
126
+ function notFound2(messageId, reason) {
127
+ return { status: "not_found", messageId, reason };
128
+ }
129
+ function unavailable2(messageId, retryable, reason) {
130
+ return { status: "unavailable", messageId, retryable, reason };
131
+ }
132
+ function createLoopGuard2() {
133
+ const lastSeen = new Map;
134
+ return {
135
+ check(from, to, body, now) {
136
+ const key = JSON.stringify([from, to, body]);
137
+ const prev = lastSeen.get(key);
138
+ lastSeen.set(key, now);
139
+ if (prev !== undefined && now - prev < RAPID_REPEAT_WINDOW_MS2)
140
+ return "duplicate";
141
+ return "ok";
142
+ }
143
+ };
144
+ }
145
+ // src/messaging/resolution.ts
146
+ function childToListedRuntimeObject2(parentSessionId, child) {
147
+ const running = child.status() === "running";
148
+ return {
149
+ address: serializeRuntimeAddress2(buildChildAddress2(parentSessionId, child.record.id)),
150
+ ...child.record.name !== undefined ? { name: child.record.name } : {},
151
+ objectKind: "agent",
152
+ runtimeKind: "winter-agent",
153
+ status: running ? "running" : "exited",
154
+ mode: child.record.permission.effectiveMode,
155
+ capabilities: {
156
+ message: running,
157
+ resume: !running,
158
+ notifyWhenIdle: false,
159
+ reply: running
160
+ }
161
+ };
162
+ }
163
+ function resolveTarget2(input) {
164
+ const ownChildren = input.children.filter((c) => c.record.parentSessionId === input.callerParentSessionId);
165
+ const parsed = parseRuntimeAddress2(input.to);
166
+ if (parsed !== undefined) {
167
+ if (parsed.objectKind === "agent") {
168
+ const owningParent = parsed.parentWinterSessionId ?? parsed.winterSessionId;
169
+ if (owningParent !== input.callerParentSessionId) {
170
+ return { kind: "not_found", message: `"${input.to}" is not reachable from this session (a child agent is only addressable within its owning parent)` };
171
+ }
172
+ const child = ownChildren.find((c) => c.record.id === parsed.childId);
173
+ if (child === undefined)
174
+ return { kind: "not_found", message: `no child at canonical address "${input.to}"` };
175
+ return { kind: "resolved", address: parsed, child };
176
+ }
177
+ const peer = input.peers.find((p) => p.address === input.to);
178
+ if (peer === undefined)
179
+ return { kind: "not_found", message: `no live session at canonical address "${input.to}"` };
180
+ return { kind: "resolved", address: { ...parsed, runtimeKind: peer.runtimeKind } };
181
+ }
182
+ const byId = ownChildren.find((c) => c.record.id === input.to);
183
+ if (byId !== undefined) {
184
+ return { kind: "resolved", address: buildChildAddress2(input.callerParentSessionId, byId.record.id), child: byId };
185
+ }
186
+ const childrenNamed = ownChildren.filter((c) => c.record.name === input.to);
187
+ const distinctChildIds = new Set(childrenNamed.map((c) => c.record.id));
188
+ if (distinctChildIds.size > 1) {
189
+ return {
190
+ kind: "stale",
191
+ message: `"${input.to}" has been used by more than one agent in this conversation; address the one you mean by its canonical address from ListAgents`
192
+ };
193
+ }
194
+ const peersNamed = input.peers.filter((p) => p.name === input.to);
195
+ const uniqueChild = distinctChildIds.size === 1 ? childrenNamed[0] : undefined;
196
+ const totalCandidates = (uniqueChild !== undefined ? 1 : 0) + peersNamed.length;
197
+ if (totalCandidates === 0) {
198
+ return { kind: "not_found", message: `no agent or session named "${input.to}" is currently reachable` };
199
+ }
200
+ if (totalCandidates > 1) {
201
+ const candidates = [...uniqueChild !== undefined ? [childToListedRuntimeObject2(input.callerParentSessionId, uniqueChild)] : [], ...peersNamed];
202
+ return { kind: "ambiguous", candidates };
203
+ }
204
+ if (uniqueChild !== undefined) {
205
+ return { kind: "resolved", address: buildChildAddress2(input.callerParentSessionId, uniqueChild.record.id), child: uniqueChild };
206
+ }
207
+ const peer = peersNamed[0];
208
+ if (peer === undefined) {
209
+ return { kind: "not_found", message: `no agent or session named "${input.to}" is currently reachable` };
210
+ }
211
+ const peerAddr = parseRuntimeAddress2(peer.address);
212
+ if (peerAddr === undefined) {
213
+ return { kind: "not_found", message: `internal: malformed peer address for "${input.to}"` };
214
+ }
215
+ return { kind: "resolved", address: { ...peerAddr, runtimeKind: peer.runtimeKind } };
216
+ }
217
+ // src/messaging/inbound.ts
218
+ var PROMPTING_MODES = new Set(["default", "acceptEdits", "dontAsk", "auto"]);
219
+ function classifyPermissionMode2(mode, opts) {
220
+ if (mode === "bypassPermissions")
221
+ return "bypasses";
222
+ if (mode === "plan")
223
+ return opts.bypassAvailable ? "bypasses" : "prompts";
224
+ if (PROMPTING_MODES.has(mode))
225
+ return "prompts";
226
+ return "prompts";
227
+ }
228
+ function mapFromModeToPermissionClass2(fromMode) {
229
+ if (fromMode === "bypass")
230
+ return "bypasses";
231
+ if (fromMode === "prompting")
232
+ return "prompts";
233
+ return "unknown";
234
+ }
235
+ function defaultInboundResult2(receiverClass, senderClass) {
236
+ if (receiverClass === "prompts")
237
+ return senderClass === "bypasses" ? "hold" : "accept";
238
+ return senderClass === "bypasses" ? "accept" : "hold";
239
+ }
240
+ function resolveInboundDecision2(params) {
241
+ if (!params.authenticated)
242
+ return "refuse";
243
+ if (params.explicitSetting !== undefined)
244
+ return params.explicitSetting;
245
+ return defaultInboundResult2(params.receiverClass, params.senderClass);
246
+ }
247
+ function createMailbox2() {
248
+ const boxes = new Map;
249
+ function boxFor(key) {
250
+ let box = boxes.get(key);
251
+ if (box === undefined) {
252
+ box = { held: [], acceptedCount: 0 };
253
+ boxes.set(key, box);
254
+ }
255
+ return box;
256
+ }
257
+ return {
258
+ hold(receiverKey, entry) {
259
+ const box = boxFor(receiverKey);
260
+ if (box.held.length >= HELD_INBOX_CAP2)
261
+ return false;
262
+ box.held.push(entry);
263
+ return true;
264
+ },
265
+ accept(receiverKey) {
266
+ const box = boxFor(receiverKey);
267
+ if (box.acceptedCount >= ACCEPTED_QUEUE_CAP2)
268
+ return false;
269
+ box.acceptedCount += 1;
270
+ return true;
271
+ },
272
+ releaseAccepted(receiverKey, n = 1) {
273
+ const box = boxFor(receiverKey);
274
+ box.acceptedCount = Math.max(0, box.acceptedCount - n);
275
+ },
276
+ heldCount(receiverKey) {
277
+ return boxes.get(receiverKey)?.held.length ?? 0;
278
+ },
279
+ acceptedCount(receiverKey) {
280
+ return boxes.get(receiverKey)?.acceptedCount ?? 0;
281
+ },
282
+ listHeld(receiverKey) {
283
+ return boxes.get(receiverKey)?.held ?? [];
284
+ },
285
+ takeHeld(receiverKey, messageId) {
286
+ const box = boxes.get(receiverKey);
287
+ if (box === undefined)
288
+ return;
289
+ const idx = box.held.findIndex((e) => e.messageId === messageId);
290
+ if (idx === -1)
291
+ return;
292
+ const [removed] = box.held.splice(idx, 1);
293
+ return removed;
294
+ },
295
+ reevaluate(receiverKey, decide) {
296
+ const box = boxes.get(receiverKey);
297
+ if (box === undefined)
298
+ return [];
299
+ const promoted = [];
300
+ box.held = box.held.filter((entry) => {
301
+ if (entry.kind !== "default")
302
+ return true;
303
+ const next = decide(entry);
304
+ if (next === "hold")
305
+ return true;
306
+ promoted.push({ entry, next });
307
+ return false;
308
+ });
309
+ return promoted;
310
+ },
311
+ sweepExpired(receiverKey, now) {
312
+ const box = boxes.get(receiverKey);
313
+ if (box === undefined)
314
+ return [];
315
+ const expired = [];
316
+ box.held = box.held.filter((entry) => {
317
+ if (entry.kind === "default" && entry.expiresAt !== undefined && entry.expiresAt <= now) {
318
+ expired.push(entry);
319
+ return false;
320
+ }
321
+ return true;
322
+ });
323
+ return expired;
324
+ }
325
+ };
326
+ }
327
+ function buildDefaultHoldEntry2(messageId, reason, now) {
328
+ return { messageId, reason, kind: "default", heldAt: now, expiresAt: now + DEFAULT_HOLD_EXPIRY_MS2 };
329
+ }
330
+ function buildExplicitHoldEntry2(messageId, reason, now) {
331
+ return { messageId, reason, kind: "explicit", heldAt: now };
332
+ }
333
+ // src/messaging/idle.ts
334
+ function isIdleSubscribeSenderAllowed2(sender) {
335
+ return !sender.isChild;
336
+ }
337
+ function isIdleSubscribeTargetAllowed2(target) {
338
+ return target.objectKind === "session" && target.hasReliableIdleSignal;
339
+ }
340
+ function createNotificationQueue2() {
341
+ const byOwner = new Map;
342
+ let listeners = [];
343
+ let counter = 0;
344
+ return {
345
+ subscribe(listener) {
346
+ listeners.push(listener);
347
+ return () => {
348
+ listeners = listeners.filter((l) => l !== listener);
349
+ };
350
+ },
351
+ push(ownerKey, rec) {
352
+ const list = byOwner.get(ownerKey) ?? [];
353
+ const record = {
354
+ notification_id: `note-${++counter}`,
355
+ origin: rec.origin,
356
+ queued_at: new Date(rec.queuedAtMs).toISOString(),
357
+ content: rec.content
358
+ };
359
+ list.push(record);
360
+ byOwner.set(ownerKey, list);
361
+ for (const listener of listeners)
362
+ listener(ownerKey, record);
363
+ },
364
+ drain(ownerKey, max) {
365
+ const list = byOwner.get(ownerKey) ?? [];
366
+ const take = max === undefined ? list.length : Math.max(0, Math.min(max, list.length));
367
+ const notifications = list.splice(0, take);
368
+ const remaining = list.length;
369
+ if (list.length === 0)
370
+ byOwner.delete(ownerKey);
371
+ else
372
+ byOwner.set(ownerKey, list);
373
+ return { notifications, remaining };
374
+ },
375
+ pendingCount(ownerKey) {
376
+ return byOwner.get(ownerKey)?.length ?? 0;
377
+ }
378
+ };
379
+ }
380
+ function createIdleSubscriptionStore2() {
381
+ let pending = [];
382
+ return {
383
+ subscribe(sub, now) {
384
+ pending.push({ ...sub, createdAt: now, expiresAt: now + NOTIFY_IDLE_EXPIRY_MS2 });
385
+ },
386
+ fireIdle(targetKey, now, computeReducedStatus, queue, originLabel) {
387
+ const matches = pending.filter((p) => p.targetKey === targetKey && p.expiresAt > now);
388
+ pending = pending.filter((p) => p.targetKey !== targetKey);
389
+ for (const m of matches) {
390
+ const reducedStatus = computeReducedStatus(m.subscriberKey);
391
+ queue.push(m.subscriberKey, {
392
+ origin: originLabel,
393
+ content: reducedStatus ? `${originLabel} changed state (reduced-status notice: the subscribing session is currently holding cross-session messages from this sender's class)` : `${originLabel} is now idle`,
394
+ queuedAtMs: now
395
+ });
396
+ }
397
+ return matches.length;
398
+ },
399
+ sweepExpired(now) {
400
+ pending = pending.filter((p) => p.expiresAt > now);
401
+ },
402
+ pendingCount(targetKey) {
403
+ return pending.filter((p) => p.targetKey === targetKey).length;
404
+ }
405
+ };
406
+ }
407
+ // src/messaging/attribution.ts
408
+ var AGENT_MESSAGE_TAG2 = "agent-message";
409
+ function escapeAttributionText2(value) {
410
+ return value.split(`</${AGENT_MESSAGE_TAG2}`).join(`&lt;/${AGENT_MESSAGE_TAG2}`).split(`<${AGENT_MESSAGE_TAG2}`).join(`&lt;${AGENT_MESSAGE_TAG2}`);
411
+ }
412
+ function escapeAttributionAttribute2(value) {
413
+ return escapeAttributionText2(value).split('"').join("&quot;");
414
+ }
415
+ var RESERVED_NOTIFICATION_KEY_PREFIX2 = "host:";
416
+ function facetNotificationKey2(sessionId) {
417
+ return `${RESERVED_NOTIFICATION_KEY_PREFIX2}${sessionId}`;
418
+ }
419
+ function isReservedNotificationKey2(key) {
420
+ return key.startsWith(RESERVED_NOTIFICATION_KEY_PREFIX2);
421
+ }
422
+ // src/messaging/router.ts
423
+ var MAX_TRACKED_MESSAGE_IDS2 = 1e4;
424
+ function rememberBounded2(map, key, value, cap = MAX_TRACKED_MESSAGE_IDS2) {
425
+ if (map.has(key))
426
+ map.delete(key);
427
+ map.set(key, value);
428
+ while (map.size > cap) {
429
+ const oldest = map.keys().next();
430
+ if (oldest.done === true)
431
+ break;
432
+ map.delete(oldest.value);
433
+ }
434
+ }
435
+ function createMessagingRouterSeam2() {
436
+ const outcomes = new Map;
437
+ const idsBySenderAndTool = new Map;
438
+ let sources = [];
439
+ let counter = 0;
440
+ return {
441
+ allocateMessageId(senderSessionId, toolUseId) {
442
+ const key = JSON.stringify([senderSessionId, toolUseId]);
443
+ const existing = idsBySenderAndTool.get(key);
444
+ if (existing !== undefined)
445
+ return existing;
446
+ const id = `msg-${++counter}`;
447
+ rememberBounded2(idsBySenderAndTool, key, id);
448
+ return id;
449
+ },
450
+ recordOutcome(messageId, outcome) {
451
+ rememberBounded2(outcomes, messageId, outcome);
452
+ },
453
+ lookupOutcome(messageId) {
454
+ return outcomes.get(messageId);
455
+ },
456
+ addChildRosterSource(getChildren) {
457
+ sources.push(getChildren);
458
+ return () => {
459
+ sources = sources.filter((s) => s !== getChildren);
460
+ };
461
+ },
462
+ children() {
463
+ return sources.flatMap((getChildren) => getChildren());
464
+ }
465
+ };
466
+ }
467
+ function createSubscriberDirectory2() {
468
+ const map = new Map;
469
+ return {
470
+ remember(messageId, subscriberSessionId) {
471
+ rememberBounded2(map, messageId, subscriberSessionId);
472
+ },
473
+ lookup(messageId) {
474
+ return map.get(messageId);
475
+ }
476
+ };
477
+ }
478
+ function callerAddress2(caller) {
479
+ if (caller.agentId !== undefined)
480
+ return buildChildAddress2(caller.sessionId, caller.agentId);
481
+ return buildSessionAddress2(caller.sessionId);
482
+ }
483
+ var SUCCESS_CLASS_STATUSES = new Set(["delivered", "queued", "resumed_and_delivered"]);
484
+ function outcomeReason(outcome) {
485
+ return "reason" in outcome ? outcome.reason : outcome.status;
486
+ }
487
+ async function sendMessage2(deps, caller, input) {
488
+ const now = deps.now();
489
+ const messageId = deps.seam.allocateMessageId(caller.sessionId, caller.toolUseId);
490
+ const existing = deps.seam.lookupOutcome(messageId);
491
+ if (existing !== undefined)
492
+ return { outcome: existing };
493
+ function settle(outcome, notify) {
494
+ deps.seam.recordOutcome(messageId, outcome);
495
+ return notify !== undefined ? { outcome, notify } : { outcome };
496
+ }
497
+ const from = callerAddress2(caller);
498
+ const fromKey = serializeRuntimeAddress2(from);
499
+ const wantsIdle = input.notify_when_idle === true;
500
+ const isPureSubscription = input.message.length === 0;
501
+ if (wantsIdle && !isIdleSubscribeSenderAllowed2({ isChild: caller.agentId !== undefined })) {
502
+ return settle(refused2(messageId, "notify_when_idle: only a main conversation may subscribe (WS-10 §14)"));
503
+ }
504
+ if (!isPureSubscription) {
505
+ if (messageExceedsMaxSize2(input.message)) {
506
+ return settle(refused2(messageId, `message exceeds MAX_GLOBAL_MESSAGE_SIZE (${MAX_GLOBAL_MESSAGE_SIZE2} chars)`));
507
+ }
508
+ if (deps.loopGuard.check(fromKey, input.to, input.message, now) === "duplicate") {
509
+ return settle(refused2(messageId, "identical message to the same target was sent moments ago (rapid repeat suppressed, WS-10 §12)"));
510
+ }
511
+ }
512
+ const reachable = await deps.adapter.listReachable({ parent: buildSessionAddress2(caller.sessionId) });
513
+ const peers = reachable.filter((r) => r.objectKind === "session");
514
+ 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));
517
+ if (resolved.kind === "stale")
518
+ return settle(refused2(messageId, resolved.message));
519
+ if (resolved.kind === "ambiguous")
520
+ return settle(ambiguous2(messageId, resolved.candidates));
521
+ const to = resolved.address;
522
+ if (sameAddress2(to, from)) {
523
+ return settle(refused2(messageId, "cannot SendMessage to your own session (self-target, WS-10 §16)"));
524
+ }
525
+ const targetRow = to.objectKind === "agent" && resolved.child !== undefined ? childToListedRuntimeObject2(caller.sessionId, resolved.child) : reachable.find((r) => r.address === serializeRuntimeAddress2(to));
526
+ if (wantsIdle && (targetRow === undefined || !targetRow.capabilities.notifyWhenIdle)) {
527
+ return settle(refused2(messageId, "notify_when_idle: target does not support idle notification (subagents, teammates, remote peers, and adapters without a reliable idle signal refuse the WHOLE call, WS-10 §14)"));
528
+ }
529
+ if (wantsIdle) {
530
+ deps.subscribers.remember(messageId, caller.sessionId);
531
+ }
532
+ if (isPureSubscription) {
533
+ return settle(await deps.adapter.subscribeIdle(to, { messageId }));
534
+ }
535
+ const hopCount = 0;
536
+ if (hopCountExceeded2(hopCount))
537
+ return settle(refused2(messageId, "message exceeds MAX_HOP_COUNT"));
538
+ const senderPermissionClass = await deps.adapter.senderPermissionClass(from);
539
+ const envelope = {
540
+ messageId,
541
+ from,
542
+ fromGeneration: 0,
543
+ to,
544
+ toGeneration: 0,
545
+ body: input.message,
546
+ ...input.summary !== undefined ? { summary: input.summary } : {},
547
+ notifyWhenIdle: wantsIdle,
548
+ createdAt: now,
549
+ expiresAt: now + DEFAULT_MESSAGE_TTL_MS2,
550
+ hopCount,
551
+ originToolCallId: caller.toolUseId,
552
+ senderPermissionClass
553
+ };
554
+ const bodyOutcome = await deliverEnvelope(deps, to, resolved.child, envelope, messageId);
555
+ if (!wantsIdle)
556
+ return settle(bodyOutcome);
557
+ if (!SUCCESS_CLASS_STATUSES.has(bodyOutcome.status)) {
558
+ return settle(bodyOutcome, { refused: `message was not delivered (status: ${bodyOutcome.status}); notify_when_idle was not attempted` });
559
+ }
560
+ const idleOutcome = await deps.adapter.subscribeIdle(to, { messageId });
561
+ return settle(bodyOutcome, idleOutcome.status === "subscribed" ? { subscribed: true } : { refused: outcomeReason(idleOutcome) });
562
+ }
563
+ function describeThrow(err) {
564
+ return err instanceof Error ? err.message : String(err);
565
+ }
566
+ function outcomeForThrow(deps, messageId, err) {
567
+ if (deps.classifyDeliveryError?.(err) === "refused")
568
+ return refused2(messageId, describeThrow(err));
569
+ return deliveryUncertain2(messageId, `unexpected error during delivery: ${describeThrow(err)}`);
570
+ }
571
+ async function deliverEnvelope(deps, to, child, envelope, messageId) {
572
+ if (to.objectKind === "agent") {
573
+ if (child === undefined)
574
+ return notFound2(messageId, "child no longer reachable");
575
+ try {
576
+ return child.status() === "running" ? await deps.adapter.steerChild(to, envelope) : await deps.adapter.resumeChild(to, envelope);
577
+ } catch (err) {
578
+ return outcomeForThrow(deps, messageId, err);
579
+ }
580
+ }
581
+ try {
582
+ return await deps.adapter.deliverToSession(to, envelope);
583
+ } catch (err) {
584
+ return outcomeForThrow(deps, messageId, err);
585
+ }
586
+ }
587
+ function formatListing(rows) {
588
+ if (rows.length === 0)
589
+ return "No agents or sessions are currently reachable.";
590
+ return rows.map((r) => {
591
+ const label = r.name !== undefined ? `${r.name} (${r.address})` : r.address;
592
+ return `- ${label} [${r.objectKind}/${r.runtimeKind}] status=${r.status} mode=${r.mode}`;
593
+ }).join(`
594
+ `);
595
+ }
596
+ async function listAgents2(deps, caller, _input) {
597
+ const selfAddr = buildSessionAddress2(caller.sessionId);
598
+ const selfKey = serializeRuntimeAddress2(selfAddr);
599
+ const reachable = await deps.adapter.listReachable({ parent: selfAddr });
600
+ const rows = reachable.filter((r) => r.address !== selfKey);
601
+ return { listing: formatListing(rows), rows };
602
+ }
603
+ function readNotifications2(deps, caller) {
604
+ return deps.notifications.drain(caller.sessionId);
605
+ }
606
+ function createMessagingRouter2(deps) {
607
+ return {
608
+ deps,
609
+ sendMessage: (caller, input) => sendMessage2(deps, caller, input),
610
+ listAgents: (caller, input) => listAgents2(deps, caller, input),
611
+ readNotifications: (caller) => readNotifications2(deps, caller)
612
+ };
613
+ }
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 };