@animalabs/agent-framework 0.32.0 → 0.33.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 +204 -14
- package/dist/src/admission/admit.d.ts +18 -0
- package/dist/src/admission/admit.d.ts.map +1 -1
- package/dist/src/admission/admit.js +16 -3
- package/dist/src/admission/admit.js.map +1 -1
- package/dist/src/agent.d.ts +185 -78
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +279 -102
- 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 +50 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +4 -20
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +24 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/config-checks.d.ts +7 -0
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +14 -0
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/context-manager-errors.d.ts +25 -0
- package/dist/src/context-manager-errors.d.ts.map +1 -0
- package/dist/src/context-manager-errors.js +31 -0
- package/dist/src/context-manager-errors.js.map +1 -0
- package/dist/src/context-options.d.ts +67 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +85 -0
- package/dist/src/context-options.js.map +1 -0
- package/dist/src/deferred-import.d.ts +93 -0
- package/dist/src/deferred-import.d.ts.map +1 -0
- package/dist/src/deferred-import.js +177 -0
- package/dist/src/deferred-import.js.map +1 -0
- package/dist/src/dendrite/audience.d.ts +223 -0
- package/dist/src/dendrite/audience.d.ts.map +1 -0
- package/dist/src/dendrite/audience.js +202 -0
- package/dist/src/dendrite/audience.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +5 -3
- package/dist/src/dendrite/index.d.ts.map +1 -1
- package/dist/src/dendrite/index.js +2 -1
- package/dist/src/dendrite/index.js.map +1 -1
- package/dist/src/dendrite/presets.d.ts +10 -5
- package/dist/src/dendrite/presets.d.ts.map +1 -1
- package/dist/src/dendrite/presets.js +17 -5
- package/dist/src/dendrite/presets.js.map +1 -1
- package/dist/src/dendrite/registry.d.ts +62 -7
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +179 -10
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/dendrite/types.d.ts +91 -8
- package/dist/src/dendrite/types.d.ts.map +1 -1
- package/dist/src/foreign-rounds.d.ts +69 -0
- package/dist/src/foreign-rounds.d.ts.map +1 -0
- package/dist/src/foreign-rounds.js +152 -0
- package/dist/src/foreign-rounds.js.map +1 -0
- package/dist/src/framework.d.ts +1976 -818
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +7566 -3422
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +85 -15
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +421 -47
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/rule-scope.d.ts +30 -0
- package/dist/src/gate/rule-scope.d.ts.map +1 -0
- package/dist/src/gate/rule-scope.js +41 -0
- package/dist/src/gate/rule-scope.js.map +1 -0
- package/dist/src/gate/suppress-script-worker.d.ts +2 -0
- package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
- package/dist/src/gate/suppress-script-worker.js +103 -0
- package/dist/src/gate/suppress-script-worker.js.map +1 -0
- package/dist/src/gate/suppress-scripts.d.ts +105 -0
- package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
- package/dist/src/gate/suppress-scripts.js +403 -0
- package/dist/src/gate/suppress-scripts.js.map +1 -0
- package/dist/src/gate/suppression-review.d.ts +143 -0
- package/dist/src/gate/suppression-review.d.ts.map +1 -0
- package/dist/src/gate/suppression-review.js +463 -0
- package/dist/src/gate/suppression-review.js.map +1 -0
- package/dist/src/gate/suppression-stats.d.ts +125 -0
- package/dist/src/gate/suppression-stats.d.ts.map +1 -0
- package/dist/src/gate/suppression-stats.js +214 -0
- package/dist/src/gate/suppression-stats.js.map +1 -0
- package/dist/src/gate/types.d.ts +135 -7
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/gate/wake-onboarding.d.ts +119 -0
- package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
- package/dist/src/gate/wake-onboarding.js +277 -0
- package/dist/src/gate/wake-onboarding.js.map +1 -0
- package/dist/src/held-writes.d.ts +31 -23
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +44 -59
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/jsonrpc-envelope.d.ts +69 -0
- package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
- package/dist/src/jsonrpc-envelope.js +66 -0
- package/dist/src/jsonrpc-envelope.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +131 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +290 -46
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +2 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +6 -6
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -22
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +105 -15
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -7
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +13 -10
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +134 -56
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +8 -2
- 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 +24 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +45 -8
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +237 -86
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +59 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +432 -67
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +6 -5
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +15 -22
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +1 -1
- package/dist/src/modules/workspace/index.d.ts +28 -7
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +76 -22
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/notice-tags.d.ts +10 -1
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +10 -1
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/operator-log.d.ts +36 -3
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js +2 -2
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/presentation.d.ts +746 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1435 -0
- package/dist/src/presentation.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +15 -11
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +16 -12
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/read-marks.d.ts +192 -0
- package/dist/src/read-marks.d.ts.map +1 -0
- package/dist/src/read-marks.js +242 -0
- package/dist/src/read-marks.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +91 -6
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +273 -29
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +22 -3
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +5 -9
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +40 -7
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/stored-injections.d.ts +2 -2
- package/dist/src/stored-injections.js +2 -2
- package/dist/src/stored-removal.d.ts +17 -4
- package/dist/src/stored-removal.d.ts.map +1 -1
- package/dist/src/stored-removal.js +29 -5
- package/dist/src/stored-removal.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +42 -8
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +1159 -79
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3709 -363
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +114 -17
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +87 -27
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/tune-out/window-anchor.d.ts +143 -0
- package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
- package/dist/src/tune-out/window-anchor.js +202 -0
- package/dist/src/tune-out/window-anchor.js.map +1 -0
- package/dist/src/types/agent.d.ts +82 -20
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +2 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +32 -12
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +180 -69
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +510 -36
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +323 -120
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- package/src/code-execution/isolate/scripts.ts +28 -3
- package/src/config-checks.ts +14 -0
- package/src/context-manager-errors.ts +37 -0
- package/src/context-options.ts +96 -0
- package/src/deferred-import.ts +239 -0
- package/src/dendrite/audience.ts +315 -0
- package/src/dendrite/index.ts +14 -2
- package/src/dendrite/presets.ts +18 -6
- package/src/dendrite/registry.ts +209 -12
- package/src/dendrite/types.ts +92 -8
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +7804 -3546
- package/src/gate/event-gate.ts +428 -65
- package/src/gate/index.ts +3 -0
- package/src/gate/rule-scope.ts +45 -0
- package/src/gate/suppress-script-worker.ts +89 -0
- package/src/gate/suppress-scripts.ts +414 -0
- package/src/gate/suppression-review.ts +517 -0
- package/src/gate/suppression-stats.ts +314 -0
- package/src/gate/types.ts +141 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/held-writes.ts +49 -60
- package/src/index.ts +9 -2
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +353 -85
- package/src/mcpl/checkpoint-manager.ts +3 -2
- package/src/mcpl/feature-set-manager.ts +2 -1
- package/src/mcpl/inbound-source.ts +2 -1
- package/src/mcpl/inference-router.ts +7 -4
- package/src/mcpl/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +141 -60
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/types.ts +8 -2
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +229 -81
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +15 -21
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +85 -23
- package/src/notice-tags.ts +10 -1
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1676 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/recovery/offline-branch.ts +346 -32
- package/src/recovery/recover-cli.ts +21 -3
- package/src/send-tools.ts +40 -7
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4089 -457
- package/src/tune-out/tools.ts +160 -43
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +86 -20
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +32 -12
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +542 -40
|
@@ -26,6 +26,7 @@ import { CapabilityGrant } from './capability-grant.js';
|
|
|
26
26
|
import { McplRequestError } from './server-connection.js';
|
|
27
27
|
import { readSkipReplyWake, skipReplyNote } from './skip-reply-wake.js';
|
|
28
28
|
import { isProseTargetToken } from './prose-grammar.js';
|
|
29
|
+
import { persistentReaderName, tuneOutHeldWords, tuneOutNoReaderAnswer, tuneOutReachWords } from '../tune-out/tools.js';
|
|
29
30
|
// ============================================================================
|
|
30
31
|
// Typing indicator interval (Discord typing lasts ~10s, so 7s keeps it alive)
|
|
31
32
|
// ============================================================================
|
|
@@ -146,8 +147,8 @@ function desiredStateRecord(item) {
|
|
|
146
147
|
}
|
|
147
148
|
/**
|
|
148
149
|
* The desired state `store`'s lifecycle log records for `channelId` on
|
|
149
|
-
* `serverId`, for a reader with no registry yet, as
|
|
150
|
-
*
|
|
150
|
+
* `serverId`, for a reader with no registry yet, as the boot's import of an
|
|
151
|
+
* earlier release's queued writes is, before initializeMcpl: the last decision wins, as
|
|
151
152
|
* the registry's replay reads it (getDesiredState answers once there is a
|
|
152
153
|
* registry). undefined when no decision names it.
|
|
153
154
|
*/
|
|
@@ -478,6 +479,32 @@ function withEmbeddedTextMarked(block) {
|
|
|
478
479
|
function channelOnServer(ref) {
|
|
479
480
|
return `${quotedValue(ref.channelId)} on server ${quotedValue(ref.serverId)}`;
|
|
480
481
|
}
|
|
482
|
+
/** A channel_close's lead where its channel's server no longer registers it. */
|
|
483
|
+
function noLongerRegistered(ref) {
|
|
484
|
+
return `Server ${quotedValue(ref.serverId)} no longer registers channel ${quotedValue(ref.channelId)}, so no close was recorded or sent.`;
|
|
485
|
+
}
|
|
486
|
+
/** What a channel_close's bare name reached besides the channel it closed
|
|
487
|
+
* (ChannelRegistry.focusNamedBare): each by its id and server, which the
|
|
488
|
+
* bare name leaves out, and the channelId and serverId a call closes it
|
|
489
|
+
* with: its own id, which needn't be the name the call gave (a focus on
|
|
490
|
+
* `b:room` beside server b's `room`), so naming the server alone, the
|
|
491
|
+
* name kept, is no channel there. */
|
|
492
|
+
function notClosedText(refs) {
|
|
493
|
+
const names = refs.map(channelOnServer);
|
|
494
|
+
const call = (ref) => `channelId ${quotedValue(ref.channelId)} and serverId ${quotedValue(ref.serverId)}`;
|
|
495
|
+
return refs.length === 1
|
|
496
|
+
? `Channel ${names[0]} was not closed: to close it, call again with ${call(refs[0])}.`
|
|
497
|
+
: `Channels ${names.slice(0, -1).join(', ')} and ${names.at(-1)} were not closed: to close one, call again with ` +
|
|
498
|
+
`${refs.slice(0, -1).map(call).join(', with ')}, or with ${call(refs.at(-1))}.`;
|
|
499
|
+
}
|
|
500
|
+
/** A tool result with a sentence added: a success's after its note (or as
|
|
501
|
+
* its note), a failure's after its error. */
|
|
502
|
+
function withSentence(result, sentence) {
|
|
503
|
+
if (!result.success)
|
|
504
|
+
return { ...result, error: result.error ? `${result.error} ${sentence}` : sentence };
|
|
505
|
+
const data = (result.data ?? {});
|
|
506
|
+
return { ...result, data: { ...data, note: typeof data.note === 'string' ? `${data.note} ${sentence}` : sentence } };
|
|
507
|
+
}
|
|
481
508
|
const CHANNEL_ON_SERVER_AT_END = /("(?:[^"\\]|\\.)*") on server ("(?:[^"\\]|\\.)*")$/;
|
|
482
509
|
/**
|
|
483
510
|
* A failed request's words, bounded, for a reason that a caller composes.
|
|
@@ -680,10 +707,12 @@ export class ChannelRegistry {
|
|
|
680
707
|
latestInboundLocus;
|
|
681
708
|
onToolClose;
|
|
682
709
|
focusChannelResolver;
|
|
710
|
+
tuneOutReaderFn;
|
|
683
711
|
onRouteFailure;
|
|
684
712
|
onChannelAutoOpened;
|
|
685
713
|
homeChannelResolver;
|
|
686
714
|
activeChannelResolver;
|
|
715
|
+
channellessTurn;
|
|
687
716
|
takesGlobalFallback;
|
|
688
717
|
serverConnecting;
|
|
689
718
|
store;
|
|
@@ -934,17 +963,17 @@ export class ChannelRegistry {
|
|
|
934
963
|
(reach === 'gone' ? ' If you need the server back, ask whoever runs this host.' : '') +
|
|
935
964
|
(failure === undefined ? ` ${withStop(lifecycleClause(err))}` : '');
|
|
936
965
|
}
|
|
937
|
-
// A tune-out
|
|
966
|
+
// A tune-out holds the channel's messages for its reader, if any, and
|
|
938
967
|
// channel_open refuses a tuned-out channel.
|
|
939
968
|
if (action === 'open' ? now !== 'open' : now !== 'closed') {
|
|
940
969
|
if (action === 'open' && now === 'tuned-out') {
|
|
941
|
-
return `Channel ${channel} was tuned out meanwhile: its messages
|
|
970
|
+
return `Channel ${channel} was tuned out meanwhile: its messages ${this.tuneOutHeld()}, and channel_open doesn't ` +
|
|
942
971
|
`change that. ${this.tuneOutEndText(current)}`;
|
|
943
972
|
}
|
|
944
973
|
// channel_close refuses a tuned-out channel too, so calling it again
|
|
945
974
|
// isn't the way: the tune-out's cancel is, and it delivers the backlog.
|
|
946
975
|
if (action === 'close' && now === 'tuned-out') {
|
|
947
|
-
return `Channel ${channel} was tuned out meanwhile: its messages
|
|
976
|
+
return `Channel ${channel} was tuned out meanwhile: its messages ${this.tuneOutHeld()}, and channel_close doesn't ` +
|
|
948
977
|
`change that. ${this.tuneOutEndText(current, 'channel_close')}`;
|
|
949
978
|
}
|
|
950
979
|
// The later decision's own work settles the transport, and its call
|
|
@@ -1053,10 +1082,12 @@ export class ChannelRegistry {
|
|
|
1053
1082
|
this.latestInboundLocus = options?.latestInboundLocus === true;
|
|
1054
1083
|
this.onToolClose = options?.onToolClose;
|
|
1055
1084
|
this.focusChannelResolver = options?.focusChannelResolver;
|
|
1085
|
+
this.tuneOutReaderFn = options?.tuneOutReader;
|
|
1056
1086
|
this.onRouteFailure = options?.onRouteFailure;
|
|
1057
1087
|
this.onChannelAutoOpened = options?.onChannelAutoOpened;
|
|
1058
1088
|
this.homeChannelResolver = options?.homeChannelResolver;
|
|
1059
1089
|
this.activeChannelResolver = options?.activeChannelResolver;
|
|
1090
|
+
this.channellessTurn = options?.channellessTurn;
|
|
1060
1091
|
this.takesGlobalFallback = options?.takesGlobalFallback;
|
|
1061
1092
|
this.serverConnecting = options?.serverConnecting;
|
|
1062
1093
|
this.store = options?.store;
|
|
@@ -1686,8 +1717,10 @@ export class ChannelRegistry {
|
|
|
1686
1717
|
}
|
|
1687
1718
|
}
|
|
1688
1719
|
/** The explicit 'stop' for a channel whose typing ended, while its
|
|
1689
|
-
* server still registers it. */
|
|
1720
|
+
* server still registers it and holds the grant its start needed. */
|
|
1690
1721
|
sendTypingStop(ref) {
|
|
1722
|
+
if (!this.typingGranted(ref))
|
|
1723
|
+
return;
|
|
1691
1724
|
if (this.sendTypingFn && this.entryAt(ref)) {
|
|
1692
1725
|
this.sendTypingFn(ref.serverId, ref.channelId, this.typingMetadata.get(destinationKey(ref)), 'stop');
|
|
1693
1726
|
}
|
|
@@ -1858,7 +1891,9 @@ export class ChannelRegistry {
|
|
|
1858
1891
|
* its home.
|
|
1859
1892
|
*/
|
|
1860
1893
|
incomingFor(agentName) {
|
|
1861
|
-
|
|
1894
|
+
const channelless = agentName === undefined ? false
|
|
1895
|
+
: this.channellessTurn ? this.channellessTurn(agentName) : this.activeChannelResolver?.(agentName) === null;
|
|
1896
|
+
if (channelless && !this.homeChannelResolver?.(agentName)) {
|
|
1862
1897
|
return undefined;
|
|
1863
1898
|
}
|
|
1864
1899
|
// The latest delivered inbound itself, not the speech fallback
|
|
@@ -2466,7 +2501,7 @@ export class ChannelRegistry {
|
|
|
2466
2501
|
}
|
|
2467
2502
|
/**
|
|
2468
2503
|
* Make a tuned-out channel's transport match its tune-out, which keeps it
|
|
2469
|
-
* open so its traffic reaches
|
|
2504
|
+
* open so its traffic reaches its reader, or is held (TuneOutParams). The
|
|
2470
2505
|
* tune-out is recorded first (enterTuneOut), and this writes no decision,
|
|
2471
2506
|
* so the tune-out stands whatever becomes of the open. Lifecycle work
|
|
2472
2507
|
* queued for the channel goes first, until none is left, since each
|
|
@@ -2478,8 +2513,8 @@ export class ChannelRegistry {
|
|
|
2478
2513
|
* tune-out as open), and the answer waits for the work queued after it
|
|
2479
2514
|
* too, such as a registration that came back, and comes from the state
|
|
2480
2515
|
* that work leaves. Returns nothing where the transport is open, or, for
|
|
2481
|
-
* tune_out's answer, why the channel's messages don't or may not reach
|
|
2482
|
-
*
|
|
2516
|
+
* tune_out's answer, why the channel's messages don't or may not reach its
|
|
2517
|
+
* reader yet, or be received, and when they will, or that the tune-out isn't in
|
|
2483
2518
|
* effect any more. A call while this tune-out's open is in flight, as a
|
|
2484
2519
|
* repeated tune_out makes, gets that open's answer rather than send
|
|
2485
2520
|
* another.
|
|
@@ -2545,8 +2580,9 @@ export class ChannelRegistry {
|
|
|
2545
2580
|
// that took effect would have its traffic diverted. `again` where calling
|
|
2546
2581
|
// tune_out again tries the open now: the channel is registered here and
|
|
2547
2582
|
// its server up.
|
|
2548
|
-
|
|
2549
|
-
|
|
2583
|
+
// Where no reader runs, each answer is that alone (tuneOutNoReaderAnswer).
|
|
2584
|
+
const notYet = (later, why, again = false) => tuneOutNoReaderAnswer(this.tuneOutReaderNow()) ?? withStop(`The tune-out is recorded, but its messages ${this.tuneOutReach('may not reach')} until this host opens the ` +
|
|
2585
|
+
`channel, which it does ${later}${again ? ', or now if you call tune_out again' : ''}; ${why}`);
|
|
2550
2586
|
const nextRegistration = `when the "${serverId}" server next registers this channel, if this host may open and ` +
|
|
2551
2587
|
'close its channels then';
|
|
2552
2588
|
// The work queued for the channel as it stands: its tail, waited for,
|
|
@@ -2580,16 +2616,16 @@ export class ChannelRegistry {
|
|
|
2580
2616
|
// established.
|
|
2581
2617
|
const reach = this.serverReach(serverId);
|
|
2582
2618
|
if (reach !== 'up') {
|
|
2583
|
-
return withStop(`The tune-out is recorded, but its messages don't reach
|
|
2584
|
-
`connected and the channel is open; it can't be opened now: ${unreachableClause(serverId, reach)}` +
|
|
2619
|
+
return tuneOutNoReaderAnswer(this.tuneOutReaderNow()) ?? withStop(`The tune-out is recorded, but its messages ${this.tuneOutReach("don't reach")} until the "${serverId}" server ` +
|
|
2620
|
+
`is connected and the channel is open; it can't be opened now: ${unreachableClause(serverId, reach)}` +
|
|
2585
2621
|
(reach === 'gone' ? unreachableRemedy(reach) : ''));
|
|
2586
2622
|
}
|
|
2587
2623
|
const transportOpen = this.tuneOutReadsOpen(entry);
|
|
2588
2624
|
if (!this.directsLifecycle(serverId)) {
|
|
2589
2625
|
return transportOpen ? undefined
|
|
2590
|
-
: `The tune-out is recorded, but ${this.lifecycleRefusal(serverId).message}, so its messages
|
|
2591
|
-
'
|
|
2592
|
-
'this host to open it, ask whoever runs this host.';
|
|
2626
|
+
: tuneOutNoReaderAnswer(this.tuneOutReaderNow()) ?? `The tune-out is recorded, but ${this.lifecycleRefusal(serverId).message}, so its messages ` +
|
|
2627
|
+
`${this.tuneOutReach('reach')} only as that server sends them, and calling tune_out again won't change ` +
|
|
2628
|
+
'that. If you need this host to open it, ask whoever runs this host.';
|
|
2593
2629
|
}
|
|
2594
2630
|
if (!entry)
|
|
2595
2631
|
return missing;
|
|
@@ -3222,10 +3258,9 @@ export class ChannelRegistry {
|
|
|
3222
3258
|
* (typing timer lifecycle is still managed for when the callback is wired).
|
|
3223
3259
|
*/
|
|
3224
3260
|
sendTypingNotification(ref) {
|
|
3225
|
-
//
|
|
3226
|
-
//
|
|
3227
|
-
|
|
3228
|
-
if (!CapabilityGrant.of(this.serverRegistry.getServer(ref.serverId)).has('channels.typing'))
|
|
3261
|
+
// Silent skip — a typing indicator is cosmetic and per-7s, so a
|
|
3262
|
+
// diagnostic per tick would be noise.
|
|
3263
|
+
if (!this.typingGranted(ref))
|
|
3229
3264
|
return;
|
|
3230
3265
|
if (this.sendTypingFn) {
|
|
3231
3266
|
this.sendTypingFn(ref.serverId, ref.channelId, this.typingMetadata.get(destinationKey(ref)));
|
|
@@ -3233,6 +3268,10 @@ export class ChannelRegistry {
|
|
|
3233
3268
|
// TODO: When server-connection exposes a public sendNotification or
|
|
3234
3269
|
// sendTyping method, wire it here directly instead of using a callback.
|
|
3235
3270
|
}
|
|
3271
|
+
/** §14.1: channels/typing, start and stop alike, requires channels.typing in the grant. */
|
|
3272
|
+
typingGranted(ref) {
|
|
3273
|
+
return CapabilityGrant.of(this.serverRegistry.getServer(ref.serverId)).has('channels.typing');
|
|
3274
|
+
}
|
|
3236
3275
|
// ==========================================================================
|
|
3237
3276
|
// Private: Channel Lookup
|
|
3238
3277
|
// ==========================================================================
|
|
@@ -3305,6 +3344,18 @@ export class ChannelRegistry {
|
|
|
3305
3344
|
}
|
|
3306
3345
|
return channelOnServer(ref);
|
|
3307
3346
|
}
|
|
3347
|
+
/** The reader of a tuned-out channel's traffic, as the host says (tuneOutReader), or the default persistent reader. */
|
|
3348
|
+
tuneOutReaderNow() {
|
|
3349
|
+
return this.tuneOutReaderFn?.() ?? { kind: 'persistent', name: persistentReaderName({}) };
|
|
3350
|
+
}
|
|
3351
|
+
/** What a tuned-out channel's messages are, after "its messages" (tuneOutHeldWords). */
|
|
3352
|
+
tuneOutHeld() {
|
|
3353
|
+
return tuneOutHeldWords(this.tuneOutReaderNow());
|
|
3354
|
+
}
|
|
3355
|
+
/** Whether a tuned-out channel's messages get to its reader, after "its messages" (tuneOutReachWords). */
|
|
3356
|
+
tuneOutReach(form) {
|
|
3357
|
+
return tuneOutReachWords(this.tuneOutReaderNow(), form);
|
|
3358
|
+
}
|
|
3308
3359
|
/**
|
|
3309
3360
|
* A tool's refusal of a tuned-out channel, whose decision is the tune-out:
|
|
3310
3361
|
* its cancel is what ends it (tuneOutEndText), and a decision recorded over
|
|
@@ -3315,7 +3366,7 @@ export class ChannelRegistry {
|
|
|
3315
3366
|
return {
|
|
3316
3367
|
success: false,
|
|
3317
3368
|
isError: true,
|
|
3318
|
-
error: `Channel ${this.quotedShown(refOf(entry))} is tuned out: its messages
|
|
3369
|
+
error: `Channel ${this.quotedShown(refOf(entry))} is tuned out: its messages ${this.tuneOutHeld()}, and ${tool} ` +
|
|
3319
3370
|
`doesn't change that. ${this.tuneOutEndText(entry, tool)}`,
|
|
3320
3371
|
data: { refusal: 'tuned-out', channelId: entry.descriptor.id, serverId: entry.serverId },
|
|
3321
3372
|
};
|
|
@@ -3334,7 +3385,7 @@ export class ChannelRegistry {
|
|
|
3334
3385
|
const entry = this.entryAt(ref);
|
|
3335
3386
|
return entry ? this.tuneOutEndText(entry) : undefined;
|
|
3336
3387
|
}
|
|
3337
|
-
tuneOutEndText(entry, tool) {
|
|
3388
|
+
tuneOutEndText(entry, tool, closeWith) {
|
|
3338
3389
|
const close = tool === 'channel_close';
|
|
3339
3390
|
// A name is written as a literal tune_out's channelId takes back
|
|
3340
3391
|
// (quotedValue, main's #852); one no name picks alone, by its id and
|
|
@@ -3344,7 +3395,7 @@ export class ChannelRegistry {
|
|
|
3344
3395
|
? `channelId ${quotedValue(name)}`
|
|
3345
3396
|
: `channelId ${quotedValue(entry.descriptor.id)}, serverId ${JSON.stringify(entry.serverId)}`;
|
|
3346
3397
|
return `Call tune_out with ${names} and mode "cancel" for it to resume normal ` +
|
|
3347
|
-
`attention; cancelling delivers the diverted backlog${close ?
|
|
3398
|
+
`attention; cancelling delivers the diverted backlog${close ? `, and channel_close${closeWith ? ` with ${closeWith}` : ''} closes it after that.` : '.'}`;
|
|
3348
3399
|
}
|
|
3349
3400
|
/**
|
|
3350
3401
|
* A channel's name as nameShown gives it, quoted for a sentence: the name
|
|
@@ -4336,15 +4387,38 @@ export class ChannelRegistry {
|
|
|
4336
4387
|
}
|
|
4337
4388
|
}
|
|
4338
4389
|
async handleToolClose(input, origin) {
|
|
4339
|
-
const
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4390
|
+
const own = origin?.kind === 'agent' ? origin.own : undefined;
|
|
4391
|
+
// An agent's bare name for the channel the all-except hold excepts means
|
|
4392
|
+
// that channel (focusNamedBare), whether its server still registers it
|
|
4393
|
+
// or not: another server's channel with the id, which the name would
|
|
4394
|
+
// reach otherwise, isn't closed, and the answer says so. A module's
|
|
4395
|
+
// close resolves its name as any other: housekeeping doesn't end the
|
|
4396
|
+
// resident's focus.
|
|
4397
|
+
const focused = origin?.kind === 'module' ? undefined : this.focusNamedBare(input.channelId, input.serverId, own);
|
|
4398
|
+
if (focused && 'refusal' in focused)
|
|
4399
|
+
return { success: false, error: focused.refusal, isError: true };
|
|
4400
|
+
if (focused) {
|
|
4401
|
+
const entry = this.entryAt(focused.focus);
|
|
4402
|
+
const result = entry
|
|
4403
|
+
? await this.closeToolEntry(entry, input, origin)
|
|
4404
|
+
: this.unregisteredFocusClose(focused.focus) ?? { success: false, error: noLongerRegistered(focused.focus), isError: true };
|
|
4405
|
+
return focused.notClosed ? withSentence(result, focused.notClosed) : result;
|
|
4347
4406
|
}
|
|
4407
|
+
const resolved = this.resolveToolChannelEntry(input.channelId, input.serverId, own);
|
|
4408
|
+
if (resolved.entry)
|
|
4409
|
+
return this.closeToolEntry(resolved.entry, input, origin);
|
|
4410
|
+
// The name, with its serverId or as the turn's own channel, means the
|
|
4411
|
+
// channel the all-except hold excepts, which its server no longer
|
|
4412
|
+
// registers (unregisteredFocusNamed). A module's close of such a name is
|
|
4413
|
+
// refused as any name no channel answers to.
|
|
4414
|
+
const gone = origin?.kind === 'module' ? undefined : this.unregisteredFocusNamed(input.channelId, input.serverId, own);
|
|
4415
|
+
return (gone && this.unregisteredFocusClose(gone)) || { success: false, error: resolved.error, isError: true };
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* A tool close of the channel `entry`, which its name resolved to
|
|
4419
|
+
* (handleToolClose).
|
|
4420
|
+
*/
|
|
4421
|
+
async closeToolEntry(entry, input, origin) {
|
|
4348
4422
|
// From here, the channel's own id, whichever name the call gave for it
|
|
4349
4423
|
// (a server-qualified one included).
|
|
4350
4424
|
input = { ...input, channelId: entry.descriptor.id };
|
|
@@ -4375,20 +4449,23 @@ export class ChannelRegistry {
|
|
|
4375
4449
|
// Closed already, perhaps only by default: the agent's close is still
|
|
4376
4450
|
// its decision, and is recorded as one (setDesiredState), so a later
|
|
4377
4451
|
// registration's lift of a default leaves the channel closed.
|
|
4378
|
-
const
|
|
4452
|
+
const hook = this.toolCloseHook(refOf(entry));
|
|
4453
|
+
if ('error' in hook)
|
|
4454
|
+
return { success: false, error: hook.error, isError: true };
|
|
4455
|
+
const ended = hook.text;
|
|
4379
4456
|
try {
|
|
4380
4457
|
this.setDesiredState(entry.serverId, input.channelId, 'closed', closeSource);
|
|
4381
4458
|
}
|
|
4382
4459
|
catch (err) {
|
|
4383
4460
|
return {
|
|
4384
4461
|
success: false,
|
|
4385
|
-
error: `Channel ${shown} is already closed, but recording the decision to close it failed: ${err.message}${ended ? ` ${ended}` : ''}`,
|
|
4462
|
+
error: `Channel ${shown} is already closed, but recording the decision to close it failed: ${err.message}${ended ? ` ${ended()}` : ''}`,
|
|
4386
4463
|
isError: true,
|
|
4387
4464
|
};
|
|
4388
4465
|
}
|
|
4389
4466
|
return {
|
|
4390
4467
|
success: true,
|
|
4391
|
-
data: { channelId: input.channelId, serverId: entry.serverId, status: 'already closed' },
|
|
4468
|
+
data: { channelId: input.channelId, serverId: entry.serverId, status: 'already closed', ...(ended ? { note: ended() } : {}) },
|
|
4392
4469
|
};
|
|
4393
4470
|
}
|
|
4394
4471
|
// Housekeeping must not override stated intent (issue #5: GC closes wore
|
|
@@ -4425,15 +4502,23 @@ export class ChannelRegistry {
|
|
|
4425
4502
|
// A hold around this channel — the all-except scope it is the excepted
|
|
4426
4503
|
// channel of — ends through the cancel flow: backlog released, end
|
|
4427
4504
|
// recorded, never by overwriting state (#232 gap 9).
|
|
4428
|
-
const
|
|
4505
|
+
const hook = this.toolCloseHook(refOf(entry));
|
|
4506
|
+
if ('error' in hook)
|
|
4507
|
+
return { success: false, error: hook.error, isError: true };
|
|
4508
|
+
const ended = hook.text;
|
|
4429
4509
|
// A decision the log refused changed nothing for the channel, and the
|
|
4430
|
-
// answer says so, and what the hook ended all the same.
|
|
4510
|
+
// answer says so, and what the hook ended all the same. Every answer
|
|
4511
|
+
// carries the hook's sentence, a success's as `note`: whether the close
|
|
4512
|
+
// went through says nothing of the focus, whose end may wait (an
|
|
4513
|
+
// operator's change to the store in progress). The sentence is read as
|
|
4514
|
+
// the answer is built, after the transport's round-trip, during which a
|
|
4515
|
+
// release that waited may have run.
|
|
4516
|
+
const withEnded = (text) => (ended ? `${text} ${ended()}` : text);
|
|
4431
4517
|
try {
|
|
4432
4518
|
this.setDesiredState(entry.serverId, input.channelId, 'closed', closeSource);
|
|
4433
4519
|
}
|
|
4434
4520
|
catch (err) {
|
|
4435
|
-
|
|
4436
|
-
return { success: false, error: ended ? `${text} ${ended}` : text, isError: true };
|
|
4521
|
+
return { success: false, error: withEnded(this.lifecycleFailureText('close', entry, unrecordedDecision(err))), isError: true };
|
|
4437
4522
|
}
|
|
4438
4523
|
// This channel's typing, not every channel with its id (stopTyping's
|
|
4439
4524
|
// fan-out of a bare name).
|
|
@@ -4441,7 +4526,7 @@ export class ChannelRegistry {
|
|
|
4441
4526
|
// Refused now, the close stays recorded for the server's next
|
|
4442
4527
|
// registration, and the text says so.
|
|
4443
4528
|
if (!this.directsLifecycle(entry.serverId)) {
|
|
4444
|
-
return { success: false, error: this.lifecycleFailureText('close', entry, this.lifecycleRefusal(entry.serverId)), isError: true };
|
|
4529
|
+
return { success: false, error: withEnded(this.lifecycleFailureText('close', entry, this.lifecycleRefusal(entry.serverId))), isError: true };
|
|
4445
4530
|
}
|
|
4446
4531
|
try {
|
|
4447
4532
|
const applied = await this.applyDesiredChannelState(entry.serverId, input.channelId);
|
|
@@ -4450,12 +4535,166 @@ export class ChannelRegistry {
|
|
|
4450
4535
|
throw new ChannelLifecycleError(supersededClause('close', now), 'superseded');
|
|
4451
4536
|
return {
|
|
4452
4537
|
success: true,
|
|
4453
|
-
data: { channelId: input.channelId, serverId: entry.serverId, status: 'closed' },
|
|
4538
|
+
data: { channelId: input.channelId, serverId: entry.serverId, status: 'closed', ...(ended ? { note: ended() } : {}) },
|
|
4454
4539
|
};
|
|
4455
4540
|
}
|
|
4456
4541
|
catch (err) {
|
|
4457
|
-
return { success: false, error: this.lifecycleFailureText('close', entry, err), isError: true };
|
|
4542
|
+
return { success: false, error: withEnded(this.lifecycleFailureText('close', entry, err)), isError: true };
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
/**
|
|
4546
|
+
* The tool-close hook (onToolClose) for a channel, its throw taken as the
|
|
4547
|
+
* answer's text: a release of the hold that failed is the close's answer,
|
|
4548
|
+
* as the registry's other failures are, never a rejected call.
|
|
4549
|
+
*/
|
|
4550
|
+
toolCloseHook(ref, opts) {
|
|
4551
|
+
try {
|
|
4552
|
+
return { text: this.onToolClose?.(ref.serverId, ref.channelId, opts) };
|
|
4553
|
+
}
|
|
4554
|
+
catch (err) {
|
|
4555
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
/**
|
|
4559
|
+
* A close naming the channel the all-except hold excepts, which its server
|
|
4560
|
+
* no longer registers: the close has nothing left to close, and the hold
|
|
4561
|
+
* nothing left to speak into, so it ends as a close of the registered
|
|
4562
|
+
* channel ends it, what it held released, and the answer says so, as a
|
|
4563
|
+
* failure, since no close ran. Undefined where no hold excepts the channel
|
|
4564
|
+
* (onToolClose answers nothing).
|
|
4565
|
+
*/
|
|
4566
|
+
unregisteredFocusClose(gone) {
|
|
4567
|
+
const lead = noLongerRegistered(gone);
|
|
4568
|
+
const ended = this.toolCloseHook(gone, { unregistered: true });
|
|
4569
|
+
if ('error' in ended)
|
|
4570
|
+
return { success: false, error: `${lead} ${ended.error}`, isError: true };
|
|
4571
|
+
if (ended.text)
|
|
4572
|
+
return { success: false, error: `${lead} ${ended.text()}`, isError: true };
|
|
4573
|
+
return undefined;
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* The channel the all-except hold excepts (focusChannelResolver), where a
|
|
4577
|
+
* channel_close names it by its id or its server-qualified name alone: no
|
|
4578
|
+
* serverId, and no channel of the calling turn's own answering to the name
|
|
4579
|
+
* (ownChannelNamed, which a turn's name means first). Such a name means
|
|
4580
|
+
* the hold's channel, registered or not, and not another server's channel
|
|
4581
|
+
* with the id, as when the hold's channel's server stopped registering it
|
|
4582
|
+
* and another server registered the id: that one is closed only by a close
|
|
4583
|
+
* that names its server. With it, where other registered channels answer
|
|
4584
|
+
* to the name, the sentence saying they weren't closed (`notClosed`).
|
|
4585
|
+
*
|
|
4586
|
+
* Where the name is the hold channel's and a channel of the calling turn's
|
|
4587
|
+
* own, another channel, answers to it too, the name means neither: a
|
|
4588
|
+
* resident repeating a close that failed before its focus ended, in a turn
|
|
4589
|
+
* that has since opened another server's channel with the id, would
|
|
4590
|
+
* otherwise close that channel and leave the focus in force. That close is
|
|
4591
|
+
* refused, naming both (`refusal`, focusOrOwnRefusal), and a close naming
|
|
4592
|
+
* either's server reaches it. A turn's name for the hold's channel itself,
|
|
4593
|
+
* and a name the turn's channels on more than one server answer to
|
|
4594
|
+
* (ownNeitherRefusal), resolve as any other.
|
|
4595
|
+
*
|
|
4596
|
+
* Undefined where no hold is in force, where the name isn't the hold
|
|
4597
|
+
* channel's, and where the hold's record can't be read, which leaves the
|
|
4598
|
+
* name to resolve as any other (resolveToolChannelEntry).
|
|
4599
|
+
*
|
|
4600
|
+
* Another channel the name reaches that has a tune-out of its own is
|
|
4601
|
+
* refused by a close until that tune-out is cancelled (tunedOutRefusal),
|
|
4602
|
+
* so its sentence says it is tuned out, and names the cancel first, then
|
|
4603
|
+
* the close by its id and server.
|
|
4604
|
+
*/
|
|
4605
|
+
focusNamedBare(name, serverId, own) {
|
|
4606
|
+
if (serverId)
|
|
4607
|
+
return undefined;
|
|
4608
|
+
const mine = this.ownChannelNamed(name, own);
|
|
4609
|
+
if (mine && 'neither' in mine)
|
|
4610
|
+
return undefined;
|
|
4611
|
+
let focus;
|
|
4612
|
+
try {
|
|
4613
|
+
focus = this.focusChannelResolver?.();
|
|
4614
|
+
}
|
|
4615
|
+
catch {
|
|
4616
|
+
return undefined;
|
|
4617
|
+
}
|
|
4618
|
+
if (!focus || (name !== focus.channelId && name !== qualifiedChannelName(focus)))
|
|
4619
|
+
return undefined;
|
|
4620
|
+
if (mine)
|
|
4621
|
+
return sameChannel(mine.channel, focus) ? undefined : { refusal: this.focusOrOwnRefusal(name, focus, mine.channel, own) };
|
|
4622
|
+
const held = focus;
|
|
4623
|
+
const others = this.entriesNamed(name).filter((entry) => !sameChannel(refOf(entry), held));
|
|
4624
|
+
const tunedOut = others.filter((entry) => this.getDesiredState(entry.serverId, entry.descriptor.id) === 'tuned-out');
|
|
4625
|
+
const closable = others.filter((entry) => !tunedOut.includes(entry)).map(refOf);
|
|
4626
|
+
const sentences = [
|
|
4627
|
+
...(closable.length > 0 ? [notClosedText(closable)] : []),
|
|
4628
|
+
...tunedOut.map((entry) => {
|
|
4629
|
+
const ref = refOf(entry);
|
|
4630
|
+
return `Channel ${channelOnServer(ref)} was not closed: it is tuned out, and a close doesn't change that. ` +
|
|
4631
|
+
this.tuneOutEndText(entry, 'channel_close', `channelId ${quotedValue(ref.channelId)} and serverId ${quotedValue(ref.serverId)}`);
|
|
4632
|
+
}),
|
|
4633
|
+
];
|
|
4634
|
+
return { focus: held, ...(sentences.length > 0 ? { notClosed: sentences.join(' ') } : {}) };
|
|
4635
|
+
}
|
|
4636
|
+
/**
|
|
4637
|
+
* The refusal of a channel_close's bare name (focusNamedBare) that both the
|
|
4638
|
+
* channel the all-except hold excepts (`focus`) and another channel of the
|
|
4639
|
+
* calling turn's own (`mine`, ownChannelNamed) answer to, each named by
|
|
4640
|
+
* what reaches it alone. The hold's channel is reached by its
|
|
4641
|
+
* server-qualified name, registered or not (focusNamedBare), unless a
|
|
4642
|
+
* channel of the turn's answers to that too; the turn's channel as a
|
|
4643
|
+
* refusal of the turn's names shows it (ownCandidates), unless another
|
|
4644
|
+
* channel of the turn's has its qualified name for its id. A serverId goes
|
|
4645
|
+
* with the channel's own id, so it's offered only where the name is the id
|
|
4646
|
+
* of both, for the hold's server (registered or not, the close reaches its
|
|
4647
|
+
* channel: unregisteredFocusNamed) and for the turn's channel's where that
|
|
4648
|
+
* server still registers it. Where the name is the hold channel's
|
|
4649
|
+
* server-qualified name, which another of the turn's channels has for its
|
|
4650
|
+
* id, the hold's channel is reached by its id and server.
|
|
4651
|
+
*/
|
|
4652
|
+
focusOrOwnRefusal(name, focus, mine, own) {
|
|
4653
|
+
const qualified = qualifiedChannelName(focus);
|
|
4654
|
+
const focusReaches = this.ownChannelNamed(qualified, own) === undefined;
|
|
4655
|
+
const [theirs] = this.ownCandidates([mine]);
|
|
4656
|
+
// The turn's channel's name, as the focus's: a bare id answers to the
|
|
4657
|
+
// turn's own channels first, so one of them with that id catches it.
|
|
4658
|
+
const theirsReaches = theirs.reaches && this.ownChannelNamed(theirs.text, own) === undefined;
|
|
4659
|
+
const parts = [
|
|
4660
|
+
{ text: focusReaches ? quotedValue(qualified) : channelOnServer(focus), reaches: focusReaches },
|
|
4661
|
+
{ text: theirsReaches ? quotedValue(theirs.text) : theirs.reaches ? channelOnServer(mine) : theirs.text, reaches: theirsReaches },
|
|
4662
|
+
];
|
|
4663
|
+
const named = parts.filter((p) => p.reaches).map((p) => p.text);
|
|
4664
|
+
const byName = named.length === 2 ? 'channelId set to one of them' : named.length === 1 ? `channelId ${named[0]}` : '';
|
|
4665
|
+
const servers = name === focus.channelId ? [focus.serverId, ...(this.entryAt(mine) ? [mine.serverId] : [])] : [];
|
|
4666
|
+
const byServer = servers.length > 0 ? `serverId set to ${servers.map((id) => JSON.stringify(id)).join(' or ')}` : '';
|
|
4667
|
+
const how = [byName, byServer].filter((h) => h !== '').join(', or with ');
|
|
4668
|
+
const focusRoute = name !== focus.channelId && !focusReaches
|
|
4669
|
+
? ` For the channel the focus excepts, call again with channelId ${quotedValue(focus.channelId)} and serverId ${JSON.stringify(focus.serverId)}.`
|
|
4670
|
+
: '';
|
|
4671
|
+
return `Channel ${JSON.stringify(name)} names both the channel the all-except focus excepts (${parts[0].text}) ` +
|
|
4672
|
+
`and this turn's channel (${parts[1].text}), so nothing ran.${how ? ` Call again with ${how}.` : ''}${focusRoute}`;
|
|
4673
|
+
}
|
|
4674
|
+
/**
|
|
4675
|
+
* The channel the all-except hold excepts (focusChannelResolver), where a
|
|
4676
|
+
* channel tool's name with a serverId, or the calling turn's own channel's
|
|
4677
|
+
* name, means it though its server no longer registers it: with a
|
|
4678
|
+
* serverId, that server's channel with the id; inside the calling turn,
|
|
4679
|
+
* the turn's own channel the name answers to by its id (ownChannelNamed).
|
|
4680
|
+
* A name given alone is focusNamedBare's. Undefined where no hold is in
|
|
4681
|
+
* force, where its channel is still registered, and where the hold's
|
|
4682
|
+
* record can't be read, which leaves the name's refusal as it was.
|
|
4683
|
+
*/
|
|
4684
|
+
unregisteredFocusNamed(name, serverId, own) {
|
|
4685
|
+
let focus;
|
|
4686
|
+
try {
|
|
4687
|
+
focus = this.focusChannelResolver?.();
|
|
4458
4688
|
}
|
|
4689
|
+
catch {
|
|
4690
|
+
return undefined;
|
|
4691
|
+
}
|
|
4692
|
+
if (!focus || this.entryAt(focus))
|
|
4693
|
+
return undefined;
|
|
4694
|
+
if (serverId)
|
|
4695
|
+
return sameChannel(focus, { serverId, channelId: name }) ? focus : undefined;
|
|
4696
|
+
const mine = this.ownChannelNamed(name, own);
|
|
4697
|
+
return mine && 'channel' in mine && sameChannel(mine.channel, focus) ? focus : undefined;
|
|
4459
4698
|
}
|
|
4460
4699
|
async handleToolDecline(input, own) {
|
|
4461
4700
|
const resolved = this.resolveToolChannelEntry(input.channelId, input.serverId, own);
|
|
@@ -4651,8 +4890,8 @@ export class ChannelRegistry {
|
|
|
4651
4890
|
`diverted, and then call channel_close.${serverBack}`
|
|
4652
4891
|
: `. If you still want it closed, call channel_close.${serverBack}`;
|
|
4653
4892
|
closeError = decided === 'tuned-out'
|
|
4654
|
-
? `The decline is recorded, but the channel was tuned out meanwhile, and that decision stands: its messages
|
|
4655
|
-
|
|
4893
|
+
? `The decline is recorded, but the channel was tuned out meanwhile, and that decision stands: its messages ` +
|
|
4894
|
+
`${this.tuneOutHeld()}${step}`
|
|
4656
4895
|
: `The decline is recorded, but a later decision to open the channel replaced it, and that decision stands${step}`;
|
|
4657
4896
|
}
|
|
4658
4897
|
else if (closed) {
|
|
@@ -5358,8 +5597,13 @@ export class ChannelRegistry {
|
|
|
5358
5597
|
? 'Nothing was posted: name the channel with channelId. A subagent has no default channel, since its plain '
|
|
5359
5598
|
+ 'speech is returned to whoever called it, so channel_publish sends only to a channel it names.'
|
|
5360
5599
|
: caller !== undefined && inTurn && this.activeChannelResolver?.(caller) === null
|
|
5361
|
-
?
|
|
5362
|
-
|
|
5600
|
+
? (this.channellessTurn && !this.channellessTurn(caller)
|
|
5601
|
+
// The turn came from a channel, but its speech goes to none
|
|
5602
|
+
// (a locked turn held as drafts).
|
|
5603
|
+
? `Nothing was posted: name the channel with channelId. Your plain speech goes to no channel now, so `
|
|
5604
|
+
+ `channel_publish sends only to a channel it names. ${findIds}`
|
|
5605
|
+
: `Nothing was posted: name the channel with channelId. This turn has no channel of its own, so its plain speech `
|
|
5606
|
+
+ `goes to none and channel_publish sends only to a channel it names. ${findIds}`)
|
|
5363
5607
|
: `Nothing was posted: channel_publish needs a \`channelId\`, and there's no channel to default to yet. ${findIds}`,
|
|
5364
5608
|
isError: true,
|
|
5365
5609
|
};
|