@animalabs/agent-framework 0.29.0 → 0.31.0
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 +71 -8
- package/dist/src/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +50 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +125 -23
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +10 -3
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/render.js +7 -6
- package/dist/src/code-execution/isolate/render.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -4
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +806 -118
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4180 -1121
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +36 -21
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +223 -149
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -1
- package/dist/src/gate/recipe-reconcile.js +6 -5
- package/dist/src/gate/recipe-reconcile.js.map +1 -1
- package/dist/src/host-presentation.d.ts +4 -0
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +12 -10
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/inference-bounds.d.ts +37 -0
- package/dist/src/inference-bounds.d.ts.map +1 -0
- package/dist/src/inference-bounds.js +84 -0
- package/dist/src/inference-bounds.js.map +1 -0
- package/dist/src/line-reader.d.ts +7 -1
- package/dist/src/line-reader.d.ts.map +1 -1
- package/dist/src/line-reader.js +17 -6
- package/dist/src/line-reader.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +84 -9
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +136 -58
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/errors.d.ts +1 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +3 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -2
- package/dist/src/mcpl/inbound-source.js +2 -2
- package/dist/src/mcpl/inference-router.d.ts +11 -1
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +65 -9
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +10 -7
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +85 -74
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +45 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +11 -7
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +1 -2
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +3 -3
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +14 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +26 -22
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +1 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +16 -11
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +7 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +44 -21
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +283 -64
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +17 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +32 -4
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +189 -25
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +24 -16
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +5 -4
- package/dist/src/modules/sender-name.d.ts.map +1 -1
- package/dist/src/modules/sender-name.js +7 -5
- package/dist/src/modules/sender-name.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +16 -8
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +33 -12
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +6 -3
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +61 -15
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +9 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +50 -10
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +11 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +19 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +8 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +7 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +7 -5
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/notice-tags.d.ts +2 -0
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +2 -0
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1 -9
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +4 -2
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +7 -4
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.js.map +1 -1
- package/dist/src/supplied-text.d.ts +2 -0
- package/dist/src/supplied-text.d.ts.map +1 -1
- package/dist/src/supplied-text.js +16 -7
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/tool-result-guard.js +1 -1
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +368 -16
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1224 -136
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +115 -7
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +27 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +153 -15
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +42 -3
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +140 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +138 -31
- package/src/api/mcp-server-core.ts +10 -3
- package/src/author-presentation.ts +81 -0
- package/src/code-execution/isolate/render.ts +7 -6
- package/src/code-execution/isolate/scripts.ts +5 -4
- package/src/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +4344 -1052
- package/src/gate/event-gate.ts +251 -150
- package/src/gate/recipe-reconcile.ts +7 -5
- package/src/host-presentation.ts +14 -10
- package/src/index.ts +16 -2
- package/src/inference-bounds.ts +111 -0
- package/src/line-reader.ts +22 -5
- package/src/mcpl/channel-registry.ts +170 -60
- package/src/mcpl/errors.ts +4 -0
- package/src/mcpl/inbound-source.ts +2 -2
- package/src/mcpl/inference-router.ts +67 -10
- package/src/mcpl/modern-connection.ts +83 -68
- package/src/mcpl/protocol-family.ts +37 -4
- package/src/mcpl/push-coalescer.ts +21 -7
- package/src/mcpl/references.ts +3 -4
- package/src/mcpl/server-connection.ts +30 -21
- package/src/mcpl/tool-signature.ts +18 -11
- package/src/mcpl/toolsets-module.ts +7 -0
- package/src/mcpl/transport.ts +261 -60
- package/src/mcpl/types.ts +18 -1
- package/src/module-registry.ts +178 -25
- package/src/modules/discord/discord-js-client.ts +33 -29
- package/src/modules/sender-name.ts +8 -5
- package/src/modules/workspace/disk-agreement.ts +39 -15
- package/src/modules/workspace/index.ts +65 -14
- package/src/modules/workspace/observe.ts +51 -10
- package/src/modules/workspace/reconcile.ts +22 -8
- package/src/modules/workspace/types.ts +5 -0
- package/src/modules/workspace/watcher.ts +8 -6
- package/src/notice-tags.ts +2 -0
- package/src/recovery/discord-awareness-outbox.ts +1 -11
- package/src/silent-heartbeat.ts +7 -4
- package/src/stored-injections.ts +7 -0
- package/src/supplied-text.ts +17 -6
- package/src/tool-result-guard.ts +1 -1
- package/src/tune-out/coordinator.ts +1374 -141
- package/src/tune-out/tools.ts +166 -13
- package/src/types/agent.ts +30 -1
- package/src/types/framework.ts +157 -15
- package/src/types/module.ts +42 -3
- package/src/types/trace.ts +154 -3
|
@@ -339,11 +339,11 @@ const CHANNEL_TOOL_DEFINITIONS = [
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
name: 'channel_publish',
|
|
342
|
-
description: "Publish a message to a channel. If channelId is omitted, publishes
|
|
342
|
+
description: "Publish a message to a channel. If channelId is omitted, publishes where your plain speech goes this turn: your conversation's channel if you are bound to one; otherwise the channel of the message that woke this turn, or one you opened during it; otherwise, while a focus holds, the channel it keeps. Where none of those names a channel, nothing is posted, unless this host is set to use the channel of the latest incoming message instead. A turn with no channel of its own, such as a heartbeat's, has no default, so it names one. A subagent has no default, so it must name a channel.",
|
|
343
343
|
inputSchema: {
|
|
344
344
|
type: 'object',
|
|
345
345
|
properties: {
|
|
346
|
-
channelId: { type: 'string', description: "ID of the channel to publish to (defaults to your
|
|
346
|
+
channelId: { type: 'string', description: "ID of the channel to publish to (defaults to where your plain speech goes this turn, as the tool's description says; required of a subagent)" },
|
|
347
347
|
content: { type: 'string', description: 'Text content to publish' },
|
|
348
348
|
text: { type: 'string', description: 'Alias for content' },
|
|
349
349
|
},
|
|
@@ -677,6 +677,9 @@ export class ChannelRegistry {
|
|
|
677
677
|
emitTraceFn;
|
|
678
678
|
sendTypingFn;
|
|
679
679
|
shouldTriggerInference;
|
|
680
|
+
latestInboundLocus;
|
|
681
|
+
onToolClose;
|
|
682
|
+
focusChannelResolver;
|
|
680
683
|
onRouteFailure;
|
|
681
684
|
onChannelAutoOpened;
|
|
682
685
|
homeChannelResolver;
|
|
@@ -1047,6 +1050,9 @@ export class ChannelRegistry {
|
|
|
1047
1050
|
this.emitTraceFn = emitTraceFn;
|
|
1048
1051
|
this.sendTypingFn = options?.sendTypingFn;
|
|
1049
1052
|
this.shouldTriggerInference = options?.shouldTriggerInference;
|
|
1053
|
+
this.latestInboundLocus = options?.latestInboundLocus === true;
|
|
1054
|
+
this.onToolClose = options?.onToolClose;
|
|
1055
|
+
this.focusChannelResolver = options?.focusChannelResolver;
|
|
1050
1056
|
this.onRouteFailure = options?.onRouteFailure;
|
|
1051
1057
|
this.onChannelAutoOpened = options?.onChannelAutoOpened;
|
|
1052
1058
|
this.homeChannelResolver = options?.homeChannelResolver;
|
|
@@ -1255,9 +1261,6 @@ export class ChannelRegistry {
|
|
|
1255
1261
|
// The batch came on the transport open now: a close after this point
|
|
1256
1262
|
// (transportClosed) leaves its traffic nothing to confirm.
|
|
1257
1263
|
const closesAtArrival = this.transportCloses.get(serverId) ?? 0;
|
|
1258
|
-
// And came from a server registered now: one removed since (removeServer)
|
|
1259
|
-
// gives no message the agent replies to (markAccepted).
|
|
1260
|
-
const removalsAtArrival = this.serverRemovals.get(serverId) ?? 0;
|
|
1261
1264
|
for (const message of params.messages) {
|
|
1262
1265
|
if (!message || typeof message.channelId !== 'string' || !message.channelId
|
|
1263
1266
|
|| typeof message.messageId !== 'string' || !message.messageId) {
|
|
@@ -1360,28 +1363,19 @@ export class ChannelRegistry {
|
|
|
1360
1363
|
rejectEmpty();
|
|
1361
1364
|
continue;
|
|
1362
1365
|
}
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1367
|
-
//
|
|
1368
|
-
//
|
|
1366
|
+
// A server sending channels/incoming is evidence that the transport
|
|
1367
|
+
// is open. This repairs transient status only; durable desired state
|
|
1368
|
+
// still changes exclusively through lifecycle operations. Nothing else
|
|
1369
|
+
// is decided here: whether this message is delivered, wakes anyone or
|
|
1370
|
+
// counts as the latest inbound is the framework's admission decision,
|
|
1371
|
+
// taken once the hold is known (see `noteDelivered`; issue #232 root
|
|
1372
|
+
// cause A). A coalesced item is "accepted" only once the coalescer
|
|
1373
|
+
// admits it, so for those this runs after the hook (below).
|
|
1369
1374
|
const incomingEntry = this.channels.get(incomingKey);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
// must not retarget the default publish locus or become
|
|
1375
|
-
// `context.incoming`'s reply anchor. An edit the coalescer delivered
|
|
1376
|
-
// as the agent's first view counts as the message it replaced.
|
|
1377
|
-
// A message the coalescer admits after its server was removed sets no
|
|
1378
|
-
// default: the removal cleared that server's message, and a server
|
|
1379
|
-
// that's gone mustn't displace a live one's default.
|
|
1380
|
-
if (!isMarkerTagged(message.tags, firstView) && (this.serverRemovals.get(serverId) ?? 0) === removalsAtArrival) {
|
|
1381
|
-
this.defaultPublishChannel = { serverId, channelId: message.channelId };
|
|
1382
|
-
this.defaultPublishMessageId = message.messageId;
|
|
1383
|
-
this.defaultPublishThreadId = message.threadId;
|
|
1384
|
-
}
|
|
1375
|
+
// `firstView` is the coalescer's mark that an edit was the agent's first
|
|
1376
|
+
// view of its message; the registry no longer records the publish locus
|
|
1377
|
+
// here (admission does, through noteDelivered, with the same marker rule).
|
|
1378
|
+
const markAccepted = (_firstView = false) => {
|
|
1385
1379
|
// Where the host directs this server's lifecycle, traffic shows the
|
|
1386
1380
|
// transport open only for a channel still decided open or tuned out,
|
|
1387
1381
|
// and, but for one exception, only for a confirmed target: after a
|
|
@@ -1419,7 +1413,7 @@ export class ChannelRegistry {
|
|
|
1419
1413
|
};
|
|
1420
1414
|
if (!coalesced)
|
|
1421
1415
|
markAccepted();
|
|
1422
|
-
//
|
|
1416
|
+
// The server-level veto only; the wake gate runs at admission.
|
|
1423
1417
|
let triggerInference = true;
|
|
1424
1418
|
if (this.shouldTriggerInference) {
|
|
1425
1419
|
// The text the agent's context gets, as the push lane's filter reads
|
|
@@ -1701,15 +1695,58 @@ export class ChannelRegistry {
|
|
|
1701
1695
|
// ==========================================================================
|
|
1702
1696
|
// Accessors
|
|
1703
1697
|
// ==========================================================================
|
|
1698
|
+
/**
|
|
1699
|
+
* The most recent DELIVERED inbound message (channel, message, thread).
|
|
1700
|
+
* Recorded by the framework after admission, so a held message never
|
|
1701
|
+
* becomes "the latest inbound" (issue #232 gap 2), and never for a marker
|
|
1702
|
+
* (a reaction, reaction removal, edit or deletion, apart from an edit the
|
|
1703
|
+
* coalescer delivered as the agent's first view: isMarkerTagged). Read as
|
|
1704
|
+
* the `incoming` reference in the channel context and, under
|
|
1705
|
+
* `speech.locus: 'latest-inbound'`, as the fallback speech locus. The
|
|
1706
|
+
* channel is on the server the message came from. A message whose server
|
|
1707
|
+
* no longer registers its channel sets nothing, as one the coalescer admits
|
|
1708
|
+
* after its server was removed: the removal cleared that server's message,
|
|
1709
|
+
* and a server that's gone mustn't displace a live one's anchor. That's
|
|
1710
|
+
* read at admission, where main's guard compared the server's removal
|
|
1711
|
+
* count at arrival and at acceptance (markAccepted): a message that
|
|
1712
|
+
* arrived before its server's removal and is admitted once the server
|
|
1713
|
+
* registers the channel again sets the anchor here, as a message delivered
|
|
1714
|
+
* on a live channel, where main's would set nothing.
|
|
1715
|
+
*/
|
|
1716
|
+
noteDelivered(channel, messageId, threadId, tags, firstView = false) {
|
|
1717
|
+
// A marker is ABOUT a message, not one the agent replies to: it leaves
|
|
1718
|
+
// the anchor where it was (#157), and so does a chat:system notice (a
|
|
1719
|
+
// join, the weather), which is about the place (isMarkerTagged). An edit
|
|
1720
|
+
// the coalescer delivered as the agent's first view of its message counts
|
|
1721
|
+
// as that message.
|
|
1722
|
+
if (isMarkerTagged(tags, firstView))
|
|
1723
|
+
return;
|
|
1724
|
+
if (!this.entryAt(channel))
|
|
1725
|
+
return;
|
|
1726
|
+
this.defaultPublishChannel = { serverId: channel.serverId, channelId: channel.channelId };
|
|
1727
|
+
this.defaultPublishMessageId = messageId;
|
|
1728
|
+
this.defaultPublishThreadId = threadId;
|
|
1729
|
+
}
|
|
1704
1730
|
/**
|
|
1705
1731
|
* Get the default publish channel ID: the channel of the most recent
|
|
1706
|
-
* incoming message that isn't a reaction, reaction removal, edit
|
|
1707
|
-
* deletion marker, apart from an edit the coalescer delivers on
|
|
1708
|
-
* as the agent's first view.
|
|
1732
|
+
* delivered incoming message that isn't a reaction, reaction removal, edit
|
|
1733
|
+
* or deletion marker, apart from an edit the coalescer delivers on
|
|
1734
|
+
* acceptance as the agent's first view.
|
|
1709
1735
|
*/
|
|
1710
1736
|
getDefaultPublishChannel() {
|
|
1711
1737
|
return this.defaultPublishChannel;
|
|
1712
1738
|
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Public form of the tool-input channel resolution (resolveToolChannelEntry):
|
|
1741
|
+
* optional server, the calling turn's own channels (`own`), ambiguity is an
|
|
1742
|
+
* error.
|
|
1743
|
+
*/
|
|
1744
|
+
resolveChannel(channelId, serverId, own) {
|
|
1745
|
+
const resolved = this.resolveToolChannelEntry(channelId, serverId, own);
|
|
1746
|
+
if (!resolved.entry)
|
|
1747
|
+
return { error: resolved.error ?? `Channel not found: ${channelId}` };
|
|
1748
|
+
return { serverId: resolved.entry.serverId, channelId: resolved.entry.descriptor.id };
|
|
1749
|
+
}
|
|
1713
1750
|
/**
|
|
1714
1751
|
* RFC-006 §3.2: is `channelId` registered BY `serverId` through a server
|
|
1715
1752
|
* declaration (channels/register, channels/changed) or an authorized open?
|
|
@@ -1807,7 +1844,7 @@ export class ChannelRegistry {
|
|
|
1807
1844
|
// ==========================================================================
|
|
1808
1845
|
/**
|
|
1809
1846
|
* The message the agent is replying to, with its channel: the most recent
|
|
1810
|
-
* inbound message that isn't a reaction, reaction removal, edit or deletion
|
|
1847
|
+
* delivered inbound message (noteDelivered) that isn't a reaction, reaction removal, edit or deletion
|
|
1811
1848
|
* marker, apart from an edit the coalescer delivers on acceptance as the
|
|
1812
1849
|
* agent's first view. Left process-global: per-channel inbound tracking
|
|
1813
1850
|
* (the right messageId for a fork's own channel) is a separate concern from
|
|
@@ -1824,10 +1861,14 @@ export class ChannelRegistry {
|
|
|
1824
1861
|
if (agentName !== undefined && this.activeChannelResolver?.(agentName) === null && !this.homeChannelResolver?.(agentName)) {
|
|
1825
1862
|
return undefined;
|
|
1826
1863
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1864
|
+
// The latest delivered inbound itself, not the speech fallback
|
|
1865
|
+
// (globalFallbackFor), which under `speech.locus: 'delivery'` is the
|
|
1866
|
+
// all-except hold's excepted channel while one is open, and none
|
|
1867
|
+
// otherwise.
|
|
1868
|
+
const anchor = agentName !== undefined && this.takesGlobalFallback?.(agentName) === false ? null : this.defaultPublishChannel;
|
|
1869
|
+
if (!anchor || !this.defaultPublishMessageId)
|
|
1829
1870
|
return undefined;
|
|
1830
|
-
return { channel:
|
|
1871
|
+
return { channel: anchor, messageId: this.defaultPublishMessageId, threadId: this.defaultPublishThreadId };
|
|
1831
1872
|
}
|
|
1832
1873
|
/**
|
|
1833
1874
|
* The channel context one server is told for an agent's inference
|
|
@@ -1852,14 +1893,17 @@ export class ChannelRegistry {
|
|
|
1852
1893
|
// '' is a name an ephemeral run can have, and resolveLocus reads it too.
|
|
1853
1894
|
const home = agentName !== undefined ? this.homeChannelResolver?.(agentName) : undefined;
|
|
1854
1895
|
const active = agentName !== undefined ? this.activeChannelResolver?.(agentName) : undefined;
|
|
1855
|
-
// An ephemeral run takes no global fallback (takesGlobalFallback):
|
|
1856
|
-
// the latest inbound message's channel as
|
|
1857
|
-
// message as the one it is replying to.
|
|
1858
|
-
const fallback = agentName !== undefined ? this.globalFallbackFor(agentName) : this.
|
|
1896
|
+
// An ephemeral run takes no global fallback (takesGlobalFallback): not the
|
|
1897
|
+
// all-except hold's channel, not the latest inbound message's channel as
|
|
1898
|
+
// its outgoing one, nor that message as the one it is replying to.
|
|
1899
|
+
const fallback = agentName !== undefined ? this.globalFallbackFor(agentName) : this.sharedFallback();
|
|
1859
1900
|
// A turn that deliberately has no channel (null) takes no fallback.
|
|
1860
1901
|
const outgoing = home ?? (active !== undefined ? active : fallback);
|
|
1861
1902
|
const context = {};
|
|
1862
|
-
// Incoming: the message the agent is replying to (incomingFor).
|
|
1903
|
+
// Incoming: the message the agent is replying to (incomingFor). The
|
|
1904
|
+
// anchor is independent of the speech rule: under `speech.locus:
|
|
1905
|
+
// 'delivery'` the latest inbound is no fallback for where prose lands,
|
|
1906
|
+
// but it is still the message the agent is replying to.
|
|
1863
1907
|
const incoming = this.incomingFor(agentName);
|
|
1864
1908
|
if (incoming && incoming.channel.serverId === serverId) {
|
|
1865
1909
|
context.incoming = {
|
|
@@ -3279,8 +3323,8 @@ export class ChannelRegistry {
|
|
|
3279
3323
|
/**
|
|
3280
3324
|
* How a tuned-out channel's tune-out ends, for the texts that tell an agent
|
|
3281
3325
|
* channel_open doesn't change it: a tune_out cancel, by the name that
|
|
3282
|
-
* picks the channel (nameFor), or, where no name picks it alone, its
|
|
3283
|
-
*
|
|
3326
|
+
* picks the channel (nameFor), or, where no name picks it alone, by its id
|
|
3327
|
+
* and server, since tune_out takes a serverId.
|
|
3284
3328
|
* For channel_close, which refuses a tuned-out channel too, it adds that
|
|
3285
3329
|
* the close comes after that.
|
|
3286
3330
|
*/
|
|
@@ -3292,12 +3336,15 @@ export class ChannelRegistry {
|
|
|
3292
3336
|
}
|
|
3293
3337
|
tuneOutEndText(entry, tool) {
|
|
3294
3338
|
const close = tool === 'channel_close';
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3339
|
+
// A name is written as a literal tune_out's channelId takes back
|
|
3340
|
+
// (quotedValue, main's #852); one no name picks alone, by its id and
|
|
3341
|
+
// server, since this branch's tune_out takes a serverId.
|
|
3342
|
+
const name = this.nameFor(entry);
|
|
3343
|
+
const names = name !== undefined
|
|
3344
|
+
? `channelId ${quotedValue(name)}`
|
|
3345
|
+
: `channelId ${quotedValue(entry.descriptor.id)}, serverId ${JSON.stringify(entry.serverId)}`;
|
|
3346
|
+
return `Call tune_out with ${names} and mode "cancel" for it to resume normal ` +
|
|
3347
|
+
`attention; cancelling delivers the diverted backlog${close ? ', and channel_close closes it after that.' : '.'}`;
|
|
3301
3348
|
}
|
|
3302
3349
|
/**
|
|
3303
3350
|
* A channel's name as nameShown gives it, quoted for a sentence: the name
|
|
@@ -4328,13 +4375,14 @@ export class ChannelRegistry {
|
|
|
4328
4375
|
// Closed already, perhaps only by default: the agent's close is still
|
|
4329
4376
|
// its decision, and is recorded as one (setDesiredState), so a later
|
|
4330
4377
|
// registration's lift of a default leaves the channel closed.
|
|
4378
|
+
const ended = this.onToolClose?.(entry.serverId, input.channelId);
|
|
4331
4379
|
try {
|
|
4332
4380
|
this.setDesiredState(entry.serverId, input.channelId, 'closed', closeSource);
|
|
4333
4381
|
}
|
|
4334
4382
|
catch (err) {
|
|
4335
4383
|
return {
|
|
4336
4384
|
success: false,
|
|
4337
|
-
error: `Channel ${shown} is already closed, but recording the decision to close it failed: ${err.message}`,
|
|
4385
|
+
error: `Channel ${shown} is already closed, but recording the decision to close it failed: ${err.message}${ended ? ` ${ended}` : ''}`,
|
|
4338
4386
|
isError: true,
|
|
4339
4387
|
};
|
|
4340
4388
|
}
|
|
@@ -4374,12 +4422,18 @@ export class ChannelRegistry {
|
|
|
4374
4422
|
const refusal = this.tunedOutRefusal('channel_close', entry);
|
|
4375
4423
|
return isModuleOrigin ? { ...refusal, isError: false } : refusal;
|
|
4376
4424
|
}
|
|
4377
|
-
// A
|
|
4425
|
+
// A hold around this channel — the all-except scope it is the excepted
|
|
4426
|
+
// channel of — ends through the cancel flow: backlog released, end
|
|
4427
|
+
// recorded, never by overwriting state (#232 gap 9).
|
|
4428
|
+
const ended = this.onToolClose?.(entry.serverId, input.channelId);
|
|
4429
|
+
// A decision the log refused changed nothing for the channel, and the
|
|
4430
|
+
// answer says so, and what the hook ended all the same.
|
|
4378
4431
|
try {
|
|
4379
4432
|
this.setDesiredState(entry.serverId, input.channelId, 'closed', closeSource);
|
|
4380
4433
|
}
|
|
4381
4434
|
catch (err) {
|
|
4382
|
-
|
|
4435
|
+
const text = this.lifecycleFailureText('close', entry, unrecordedDecision(err));
|
|
4436
|
+
return { success: false, error: ended ? `${text} ${ended}` : text, isError: true };
|
|
4383
4437
|
}
|
|
4384
4438
|
// This channel's typing, not every channel with its id (stopTyping's
|
|
4385
4439
|
// fan-out of a bare name).
|
|
@@ -4843,9 +4897,27 @@ export class ChannelRegistry {
|
|
|
4843
4897
|
const active = this.activeChannelResolver?.(conversationId);
|
|
4844
4898
|
return active !== undefined ? active : this.globalFallbackFor(conversationId);
|
|
4845
4899
|
}
|
|
4846
|
-
/**
|
|
4900
|
+
/**
|
|
4901
|
+
* The process-global fallback for this conversation, or null when it takes
|
|
4902
|
+
* none (takesGlobalFallback): an ephemeral run's prose is its caller's
|
|
4903
|
+
* result, so neither the all-except hold's channel nor the latest inbound
|
|
4904
|
+
* message's is ever its destination.
|
|
4905
|
+
*/
|
|
4847
4906
|
globalFallbackFor(conversationId) {
|
|
4848
|
-
return this.takesGlobalFallback?.(conversationId) === false ? null : this.
|
|
4907
|
+
return this.takesGlobalFallback?.(conversationId) === false ? null : this.sharedFallback();
|
|
4908
|
+
}
|
|
4909
|
+
/**
|
|
4910
|
+
* What every conversation that takes the global fallback falls back to when
|
|
4911
|
+
* neither a home channel nor its own delivery names one: the all-except
|
|
4912
|
+
* hold's channel while one is open, else the latest delivered inbound
|
|
4913
|
+
* channel under the legacy locus rule, else none — the prose stays private
|
|
4914
|
+
* (architecture channel §3 carve-out, 2026-10-06).
|
|
4915
|
+
*/
|
|
4916
|
+
sharedFallback() {
|
|
4917
|
+
return this.focusChannelResolver?.() ?? this.fallbackLocus();
|
|
4918
|
+
}
|
|
4919
|
+
fallbackLocus() {
|
|
4920
|
+
return this.latestInboundLocus ? this.defaultPublishChannel : null;
|
|
4849
4921
|
}
|
|
4850
4922
|
/**
|
|
4851
4923
|
* Resolve a publish target to exactly one registered channel: `serverId`
|
|
@@ -5263,17 +5335,20 @@ export class ChannelRegistry {
|
|
|
5263
5335
|
if (input.channelId === '' && tool === 'channel_publish') {
|
|
5264
5336
|
return { success: false, error: `Nothing was posted: channelId is empty. ${findIds}`, isError: true };
|
|
5265
5337
|
}
|
|
5266
|
-
// Resolve channelId:
|
|
5267
|
-
//
|
|
5268
|
-
//
|
|
5269
|
-
//
|
|
5338
|
+
// Resolve channelId: a call made inside the agent's turn (inTurn) has
|
|
5339
|
+
// the turn's default applied already, where its plain speech goes, and
|
|
5340
|
+
// takes none here. Any other defaults to what the caller falls back to:
|
|
5341
|
+
// the all-except hold's channel, else the latest delivered inbound
|
|
5342
|
+
// channel under the legacy locus rule (a marker — reaction, edit,
|
|
5343
|
+
// deletion — is never that channel), which an ephemeral run never takes
|
|
5270
5344
|
// (takesGlobalFallback), so it names one.
|
|
5271
5345
|
// A channel named by a string means what it means to every channel tool
|
|
5272
5346
|
// (entriesNamed): one channel, by its id or its server-qualified name,
|
|
5273
5347
|
// and a name two servers' channels answer to is refused with them as
|
|
5274
5348
|
// candidates, never sent to the first.
|
|
5275
5349
|
const caller = origin?.kind === 'agent' ? origin.agentName : undefined;
|
|
5276
|
-
const
|
|
5350
|
+
const inTurn = origin?.kind === 'agent' && origin.inTurn === true;
|
|
5351
|
+
const fallback = caller === undefined ? this.sharedFallback() : inTurn ? null : this.globalFallbackFor(caller);
|
|
5277
5352
|
if (!(input.channelId ?? fallback)) {
|
|
5278
5353
|
return {
|
|
5279
5354
|
success: false,
|
|
@@ -5282,7 +5357,10 @@ export class ChannelRegistry {
|
|
|
5282
5357
|
: caller !== undefined && this.takesGlobalFallback?.(caller) === false
|
|
5283
5358
|
? 'Nothing was posted: name the channel with channelId. A subagent has no default channel, since its plain '
|
|
5284
5359
|
+ 'speech is returned to whoever called it, so channel_publish sends only to a channel it names.'
|
|
5285
|
-
:
|
|
5360
|
+
: caller !== undefined && inTurn && this.activeChannelResolver?.(caller) === null
|
|
5361
|
+
? `Nothing was posted: name the channel with channelId. This turn has no channel of its own, so its plain speech `
|
|
5362
|
+
+ `goes to none and channel_publish sends only to a channel it names. ${findIds}`
|
|
5363
|
+
: `Nothing was posted: channel_publish needs a \`channelId\`, and there's no channel to default to yet. ${findIds}`,
|
|
5286
5364
|
isError: true,
|
|
5287
5365
|
};
|
|
5288
5366
|
}
|