@animalabs/agent-framework 0.28.0 → 0.29.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 +88 -25
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -1
- 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 +3 -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 +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +164 -36
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1120 -479
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +34 -10
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +118 -48
- 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 +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +131 -53
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +180 -50
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +28 -12
- 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 +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.js +8 -6
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +23 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent.ts +4 -1
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/framework.ts +1132 -492
- package/src/gate/event-gate.ts +128 -47
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +2 -2
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +137 -52
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +142 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +84 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +25 -27
- package/src/types/agent.ts +23 -9
- package/src/types/framework.ts +44 -0
- package/src/types/module.ts +23 -11
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* - Expose synthesized tools: channel_list, channel_open, channel_close, channel_publish
|
|
13
13
|
* - Build channel context for beforeInference params
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
16
|
+
import { convertBlocks } from './convert-block.js';
|
|
17
|
+
import { cutToFit, markedText, markNoticeOpenings, nameToTypeBack, quotedValue, showsAsItself, suppliedValue } from '../supplied-text.js';
|
|
16
18
|
import { isDeepStrictEqual } from 'node:util';
|
|
17
19
|
import { ensureStateRegistered } from '../module-registry.js';
|
|
18
20
|
import { expandCoreTags, isMarkerTagged } from './tags.js';
|
|
@@ -22,7 +24,7 @@ import { logDetail, logValue } from './log-value.js';
|
|
|
22
24
|
import { RefusalLines } from './refusal-lines.js';
|
|
23
25
|
import { CapabilityGrant } from './capability-grant.js';
|
|
24
26
|
import { McplRequestError } from './server-connection.js';
|
|
25
|
-
import { readSkipReplyWake } from './skip-reply-wake.js';
|
|
27
|
+
import { readSkipReplyWake, skipReplyNote } from './skip-reply-wake.js';
|
|
26
28
|
import { isProseTargetToken } from './prose-grammar.js';
|
|
27
29
|
// ============================================================================
|
|
28
30
|
// Typing indicator interval (Discord typing lasts ~10s, so 7s keeps it alive)
|
|
@@ -410,20 +412,22 @@ const CHANNEL_TOOL_DEFINITIONS = [
|
|
|
410
412
|
// ============================================================================
|
|
411
413
|
// Constructor Options
|
|
412
414
|
// ============================================================================
|
|
413
|
-
/** Bound connector values for notices, logs and an outcome's reason
|
|
414
|
-
*
|
|
415
|
-
*
|
|
415
|
+
/** Bound connector values for notices, logs and an outcome's reason, on one
|
|
416
|
+
* line with their notice openings marked (suppliedValue). A PublishOutcome
|
|
417
|
+
* keeps the connector's structured data (`detail`) as the connection gives
|
|
418
|
+
* it, its strings redacted (redactedData). */
|
|
416
419
|
export function connectorText(value) {
|
|
417
420
|
let text;
|
|
418
421
|
try {
|
|
419
422
|
text = typeof value === 'string' ? value : JSON.stringify(value) ?? String(value);
|
|
420
423
|
}
|
|
421
424
|
catch {
|
|
422
|
-
return
|
|
425
|
+
return `[${NOTICE_TAG.unserializableConnectorValue}]`;
|
|
423
426
|
}
|
|
424
427
|
const limit = 2000;
|
|
425
|
-
const suffix =
|
|
426
|
-
|
|
428
|
+
const suffix = `… [${NOTICE_TAG.truncated}]`;
|
|
429
|
+
const shown = suppliedValue(text);
|
|
430
|
+
return shown.length > limit ? cutToFit(shown, limit - suffix.length) + suffix : shown;
|
|
427
431
|
}
|
|
428
432
|
/** One key per channel, its server and id: unlike joining the two with a
|
|
429
433
|
* separator, no two channels share one, whatever their ids hold. */
|
|
@@ -437,6 +441,8 @@ export function sameChannel(a, b) {
|
|
|
437
441
|
return !a && !b;
|
|
438
442
|
return a.serverId === b.serverId && a.channelId === b.channelId;
|
|
439
443
|
}
|
|
444
|
+
export const NO_CHANNEL_EXAMPLE = '#<channel>';
|
|
445
|
+
export const NO_PERSON_EXAMPLE = '@<person>';
|
|
440
446
|
/**
|
|
441
447
|
* A channel's server-qualified name, `serverId:channelId`. A name given as a
|
|
442
448
|
* string (a tool's channelId, a `>>` target, a host's call) may take this
|
|
@@ -447,11 +453,32 @@ export function sameChannel(a, b) {
|
|
|
447
453
|
export function qualifiedChannelName(ref) {
|
|
448
454
|
return `${ref.serverId}:${ref.channelId}`;
|
|
449
455
|
}
|
|
456
|
+
function markedHistory(history) {
|
|
457
|
+
return history.map((item) => {
|
|
458
|
+
if (item === null || typeof item !== 'object' || Array.isArray(item) || !('content' in item))
|
|
459
|
+
return item;
|
|
460
|
+
const { content } = item;
|
|
461
|
+
if (Array.isArray(content))
|
|
462
|
+
return { ...item, content: markNoticeOpenings(content.map(withEmbeddedTextMarked)) };
|
|
463
|
+
return typeof content === 'string' ? { ...item, content: markedText(content) } : item;
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function withEmbeddedTextMarked(block) {
|
|
467
|
+
if (block === null || typeof block !== 'object' || block.type !== 'resource')
|
|
468
|
+
return block;
|
|
469
|
+
const { resource } = block;
|
|
470
|
+
if (resource === null || typeof resource !== 'object' || typeof resource.text !== 'string')
|
|
471
|
+
return block;
|
|
472
|
+
return { ...block, resource: { ...resource, text: markedText(resource.text) } };
|
|
473
|
+
}
|
|
450
474
|
/** A channel told apart by its id and its server, for one no string picks
|
|
451
|
-
* alone: `"b:room" on server "a"
|
|
475
|
+
* alone or whose name wouldn't show as itself: `"b:room" on server "a"`,
|
|
476
|
+
* each a JSON literal a channel tool's channelId and serverId take back
|
|
477
|
+
* (quotedValue). No input takes the whole form for a name. */
|
|
452
478
|
function channelOnServer(ref) {
|
|
453
|
-
return `${
|
|
479
|
+
return `${quotedValue(ref.channelId)} on server ${quotedValue(ref.serverId)}`;
|
|
454
480
|
}
|
|
481
|
+
const CHANNEL_ON_SERVER_AT_END = /("(?:[^"\\]|\\.)*") on server ("(?:[^"\\]|\\.)*")$/;
|
|
455
482
|
/**
|
|
456
483
|
* A failed request's words, bounded, for a reason that a caller composes.
|
|
457
484
|
* This host's own words, for a request that wasn't sent or wasn't answered
|
|
@@ -1328,7 +1355,7 @@ export class ChannelRegistry {
|
|
|
1328
1355
|
}
|
|
1329
1356
|
if (message.tags)
|
|
1330
1357
|
message.tags = expandCoreTags(message.tags);
|
|
1331
|
-
const convertedContent = message.content
|
|
1358
|
+
const convertedContent = convertBlocks(message.content, 'attachment on channel message', serverId);
|
|
1332
1359
|
if (!emptyAllowed && isVisiblyEmptyConversion(message.content, convertedContent)) {
|
|
1333
1360
|
rejectEmpty();
|
|
1334
1361
|
continue;
|
|
@@ -1764,7 +1791,7 @@ export class ChannelRegistry {
|
|
|
1764
1791
|
case 'journal':
|
|
1765
1792
|
return this.handleToolJournal(input);
|
|
1766
1793
|
case 'skip_reply':
|
|
1767
|
-
return this.handleToolSkipReply(input);
|
|
1794
|
+
return this.handleToolSkipReply(input, origin?.kind === 'agent' ? origin.earlierWake : undefined);
|
|
1768
1795
|
default:
|
|
1769
1796
|
// Not every agent is given every channel tool, so the refusal points
|
|
1770
1797
|
// to the ones this agent has rather than listing them all.
|
|
@@ -1817,7 +1844,8 @@ export class ChannelRegistry {
|
|
|
1817
1844
|
// declared it inbound-only, since speech isn't posted there): a conversation
|
|
1818
1845
|
// fork's home channel, else this turn's channel (single trunk agent — item-3
|
|
1819
1846
|
// redux: its locus, else its triggering channel), else the global default
|
|
1820
|
-
// (
|
|
1847
|
+
// (a no-trigger turn's, but not a heartbeat's, whose turn takes none).
|
|
1848
|
+
// Without this, a fork
|
|
1821
1849
|
// was advertised the global locus but published somewhere else; and a trunk
|
|
1822
1850
|
// agent was told the wrong channel under concurrency.
|
|
1823
1851
|
// Each lookup is keyed on whether a name was given, not its truthiness:
|
|
@@ -3215,13 +3243,20 @@ export class ChannelRegistry {
|
|
|
3215
3243
|
* name, only where no registered channel takes that name, as a channel
|
|
3216
3244
|
* tool reads it or a `>>` target does (by label or raw id too), so the
|
|
3217
3245
|
* name can't send anything to another channel; else by its id and server.
|
|
3246
|
+
* A name that wouldn't show as itself (showsAsItself: one holding a line
|
|
3247
|
+
* break, a notice opening, a quote, a backslash or a lone surrogate, or
|
|
3248
|
+
* whitespace at its edges) is shown by its id and its server too, which
|
|
3249
|
+
* the channel tools take exactly and the prose resolver doesn't, so no name
|
|
3250
|
+
* shown can be another channel's.
|
|
3218
3251
|
*/
|
|
3219
3252
|
nameShown(ref) {
|
|
3220
3253
|
const entry = this.entryAt(ref);
|
|
3221
|
-
if (entry)
|
|
3222
|
-
|
|
3254
|
+
if (entry) {
|
|
3255
|
+
const name = this.nameFor(entry);
|
|
3256
|
+
return name !== undefined && showsAsItself(name) ? name : channelOnServer(ref);
|
|
3257
|
+
}
|
|
3223
3258
|
for (const name of [ref.channelId, qualifiedChannelName(ref)]) {
|
|
3224
|
-
if (this.entriesNamed(name).length === 0 && !('channelId' in this.resolveProseTarget(name)))
|
|
3259
|
+
if (showsAsItself(name) && this.entriesNamed(name).length === 0 && !('channelId' in this.resolveProseTarget(name)))
|
|
3225
3260
|
return name;
|
|
3226
3261
|
}
|
|
3227
3262
|
return channelOnServer(ref);
|
|
@@ -3258,7 +3293,7 @@ export class ChannelRegistry {
|
|
|
3258
3293
|
tuneOutEndText(entry, tool) {
|
|
3259
3294
|
const close = tool === 'channel_close';
|
|
3260
3295
|
return this.nameFor(entry) !== undefined
|
|
3261
|
-
? `Call tune_out with channelId ${this.
|
|
3296
|
+
? `Call tune_out with channelId ${quotedValue(this.nameFor(entry))} and mode "cancel" for it to resume normal ` +
|
|
3262
3297
|
`attention; cancelling delivers the diverted backlog${close ? ', and channel_close closes it after that.' : '.'}`
|
|
3263
3298
|
: 'No channelId names it alone, so tune_out can\'t cancel it: its tune-out ends by itself when its duration ' +
|
|
3264
3299
|
'runs out, or at the first wake past its wake budget, and its backlog is delivered then.' +
|
|
@@ -3270,7 +3305,7 @@ export class ChannelRegistry {
|
|
|
3270
3305
|
*/
|
|
3271
3306
|
quotedShown(ref) {
|
|
3272
3307
|
const shown = this.nameShown(ref);
|
|
3273
|
-
return shown === channelOnServer(ref) ? shown :
|
|
3308
|
+
return shown === channelOnServer(ref) ? shown : quotedValue(shown);
|
|
3274
3309
|
}
|
|
3275
3310
|
/**
|
|
3276
3311
|
* The string that names exactly this entry by entriesNamed: its id when no
|
|
@@ -3303,7 +3338,7 @@ export class ChannelRegistry {
|
|
|
3303
3338
|
const nothing = opts.nothing ?? 'nothing ran';
|
|
3304
3339
|
const names = entries.map((e) => this.nameFor(e));
|
|
3305
3340
|
if (names.every((n) => n !== undefined)) {
|
|
3306
|
-
const listed = names.map((n) =>
|
|
3341
|
+
const listed = names.map((n) => quotedValue(n)).join(', ');
|
|
3307
3342
|
const servers = [...new Set(entries.map((e) => JSON.stringify(e.serverId)))];
|
|
3308
3343
|
// One server's two channels can answer to one name too, as `s`'s
|
|
3309
3344
|
// `s:room` and `room` both do to `s:room`; its server picks neither.
|
|
@@ -3325,8 +3360,8 @@ export class ChannelRegistry {
|
|
|
3325
3360
|
if (own === undefined && !opts.takesServerId)
|
|
3326
3361
|
return `- ${where}: no channelId reaches it alone.`;
|
|
3327
3362
|
return own !== undefined
|
|
3328
|
-
? `- ${where}: call again with channelId ${
|
|
3329
|
-
: `- ${where}: call again with channelId ${
|
|
3363
|
+
? `- ${where}: call again with channelId ${quotedValue(own)}.`
|
|
3364
|
+
: `- ${where}: call again with channelId ${quotedValue(e.descriptor.id)} and serverId ${JSON.stringify(e.serverId)}.`;
|
|
3330
3365
|
});
|
|
3331
3366
|
const count = entries.length === 2 ? 'either of two channels' : `any of ${entries.length} channels`;
|
|
3332
3367
|
return `Channel ${JSON.stringify(name)} could mean ${count}, so ${nothing}:\n${lines.join('\n')}`;
|
|
@@ -3364,7 +3399,7 @@ export class ChannelRegistry {
|
|
|
3364
3399
|
if (!alone)
|
|
3365
3400
|
return 'channel_list shows the channels and their servers.';
|
|
3366
3401
|
const one = named[0];
|
|
3367
|
-
const names = `${JSON.stringify(channelId)} alone names channel ${
|
|
3402
|
+
const names = `${JSON.stringify(channelId)} alone names channel ${quotedValue(one.descriptor.id)} on server `
|
|
3368
3403
|
+ JSON.stringify(one.serverId);
|
|
3369
3404
|
return takesServerId ? `${names}: for that one, call again with serverId left out.` : `${names}.`;
|
|
3370
3405
|
}
|
|
@@ -3376,7 +3411,7 @@ export class ChannelRegistry {
|
|
|
3376
3411
|
const places = elsewhere.map((e) => {
|
|
3377
3412
|
const at = `on server ${JSON.stringify(e.serverId)}`;
|
|
3378
3413
|
const name = this.nameFor(e);
|
|
3379
|
-
return name === undefined ? `${at} (no channelId reaches it alone)` : `${at} as ${
|
|
3414
|
+
return name === undefined ? `${at} (no channelId reaches it alone)` : `${at} as ${quotedValue(name)}`;
|
|
3380
3415
|
});
|
|
3381
3416
|
return `It's ${places.length === 1 ? places[0] : `${places.slice(0, -1).join(', ')} and ${places.at(-1)}`}.`;
|
|
3382
3417
|
}
|
|
@@ -3639,19 +3674,22 @@ export class ChannelRegistry {
|
|
|
3639
3674
|
* before any other spelling (ownChannelNamed).
|
|
3640
3675
|
*/
|
|
3641
3676
|
resolveProseTarget(spec, opts) {
|
|
3642
|
-
const
|
|
3677
|
+
const name = spec.trim();
|
|
3678
|
+
if (name === NO_CHANNEL_EXAMPLE || name === NO_PERSON_EXAMPLE)
|
|
3679
|
+
return { error: `${name} is the example's placeholder: write the name it stands for in its place` };
|
|
3680
|
+
const own = this.ownChannelNamed(name, opts?.own);
|
|
3643
3681
|
let resolved;
|
|
3644
3682
|
if (own === undefined) {
|
|
3645
|
-
resolved = this.resolveTargetByName(
|
|
3683
|
+
resolved = this.resolveTargetByName(name);
|
|
3646
3684
|
}
|
|
3647
3685
|
else if ('neither' in own) {
|
|
3648
|
-
resolved = { error: `"${
|
|
3686
|
+
resolved = { error: `"${name}" is the id of this turn's channels on more than one server`, candidates: this.ownCandidates(own.neither).map((c) => c.prose) };
|
|
3649
3687
|
}
|
|
3650
3688
|
else {
|
|
3651
3689
|
const entry = this.entryAt(own.channel);
|
|
3652
3690
|
resolved = entry
|
|
3653
3691
|
? { channelId: entry.descriptor.id, serverId: entry.serverId, label: entry.descriptor.label }
|
|
3654
|
-
: { error: `${this.ownChannelGoneText(
|
|
3692
|
+
: { error: `${this.ownChannelGoneText(name, own.channel)}, and nothing can be sent there until that server registers it again` };
|
|
3655
3693
|
}
|
|
3656
3694
|
if (!opts?.forSpeech || 'error' in resolved)
|
|
3657
3695
|
return resolved;
|
|
@@ -3704,7 +3742,7 @@ export class ChannelRegistry {
|
|
|
3704
3742
|
return undefined;
|
|
3705
3743
|
if ('neither' in mine) {
|
|
3706
3744
|
const done = tool === 'publish' ? 'was posted' : tool === 'deliver' ? 'was delivered' : 'ran';
|
|
3707
|
-
return { error: this.ownNeitherRefusal(name, mine.neither, done) };
|
|
3745
|
+
return { error: this.ownNeitherRefusal(name, mine.neither, done, false) };
|
|
3708
3746
|
}
|
|
3709
3747
|
return this.entryAt(mine.channel) ? { channel: mine.channel } : { error: this.ownChannelGoneRefusal(name, mine.channel, tool) };
|
|
3710
3748
|
}
|
|
@@ -3721,32 +3759,73 @@ export class ChannelRegistry {
|
|
|
3721
3759
|
/**
|
|
3722
3760
|
* The turn's channels a name means neither of (ownChannelNamed), each by a
|
|
3723
3761
|
* string that reaches it outside the turn's rule: its server-qualified
|
|
3724
|
-
* name, where that picks it alone, since its id is the name refused.
|
|
3725
|
-
*
|
|
3726
|
-
*
|
|
3762
|
+
* name, where that picks it alone, since its id is the name refused. `text`
|
|
3763
|
+
* is that name, which a refusal writes as JSON; `prose`, which a `>>`
|
|
3764
|
+
* target reads, is that name where it shows as itself, else its id and
|
|
3765
|
+
* server. One no string picks alone, and one its server no longer
|
|
3766
|
+
* registers, are named by id and server in both, saying so. `reaches` says
|
|
3767
|
+
* which a name reaches.
|
|
3727
3768
|
*/
|
|
3728
3769
|
ownCandidates(channels) {
|
|
3729
3770
|
return channels.map((c) => {
|
|
3730
3771
|
const entry = this.entryAt(c);
|
|
3731
|
-
if (!entry)
|
|
3732
|
-
|
|
3772
|
+
if (!entry) {
|
|
3773
|
+
const text = `${channelOnServer(c)} (its server no longer registers it)`;
|
|
3774
|
+
return { text, prose: text, reaches: false, byServer: false };
|
|
3775
|
+
}
|
|
3733
3776
|
const qualified = qualifiedChannelName(c);
|
|
3734
3777
|
const picked = this.entriesNamed(qualified);
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3778
|
+
if (picked.length !== 1 || picked[0] !== entry) {
|
|
3779
|
+
const text = `${channelOnServer(c)} (no channelId reaches it alone)`;
|
|
3780
|
+
return { text, prose: text, reaches: false, byServer: true };
|
|
3781
|
+
}
|
|
3782
|
+
return { text: qualified, prose: showsAsItself(qualified) ? qualified : channelOnServer(c), reaches: true, byServer: false };
|
|
3738
3783
|
});
|
|
3739
3784
|
}
|
|
3785
|
+
/**
|
|
3786
|
+
* What a refusal listing these candidates says of each one that is a
|
|
3787
|
+
* channel shown by its id and server: the channelId channel_publish reaches
|
|
3788
|
+
* it by, or that no channelId reaches it alone. That is its server-qualified
|
|
3789
|
+
* name where that picks it alone, since a bare id is what a turn's own
|
|
3790
|
+
* channels answer to first, whether or not their server still registers
|
|
3791
|
+
* them (ownChannelNamed), and else its id where that picks it alone and
|
|
3792
|
+
* isn't empty (channel_publish refuses an empty one). It leaves out a channel no
|
|
3793
|
+
* longer registered and a candidate that already says no channelId reaches
|
|
3794
|
+
* it alone, and is empty when nothing is left.
|
|
3795
|
+
*/
|
|
3796
|
+
publishRouteFor(candidates) {
|
|
3797
|
+
const parts = new Set();
|
|
3798
|
+
for (const candidate of candidates) {
|
|
3799
|
+
const shown = CHANNEL_ON_SERVER_AT_END.exec(candidate);
|
|
3800
|
+
if (!shown)
|
|
3801
|
+
continue;
|
|
3802
|
+
const ref = { channelId: JSON.parse(shown[1]), serverId: JSON.parse(shown[2]) };
|
|
3803
|
+
const entry = this.entryAt(ref);
|
|
3804
|
+
if (!entry)
|
|
3805
|
+
continue;
|
|
3806
|
+
const name = [qualifiedChannelName(ref), ref.channelId].find((n) => {
|
|
3807
|
+
const named = n === '' ? [] : this.entriesNamed(n);
|
|
3808
|
+
return named.length === 1 && named[0] === entry;
|
|
3809
|
+
});
|
|
3810
|
+
parts.add(name !== undefined
|
|
3811
|
+
? `channel_publish reaches ${channelOnServer(ref)} with channelId ${quotedValue(name)}`
|
|
3812
|
+
: `no channelId reaches ${channelOnServer(ref)} alone`);
|
|
3813
|
+
}
|
|
3814
|
+
return parts.size ? ` For a channel shown by its id and server: ${[...parts].join('; ')}.` : '';
|
|
3815
|
+
}
|
|
3740
3816
|
/** A tool's refusal of a name the turn's channels on more than one server
|
|
3741
3817
|
* answer to by their id (ownChannelNamed's neither). */
|
|
3742
|
-
ownNeitherRefusal(name, channels, done) {
|
|
3818
|
+
ownNeitherRefusal(name, channels, done, takesServer = true) {
|
|
3743
3819
|
const candidates = this.ownCandidates(channels);
|
|
3744
|
-
const named = candidates.filter((c) => c.reaches).map((c) =>
|
|
3820
|
+
const named = candidates.filter((c) => c.reaches).map((c) => quotedValue(c.text));
|
|
3745
3821
|
const pick = named.length === candidates.length ? ' Call again with channelId set to one of them.'
|
|
3746
3822
|
: named.length === 1 ? ` Call again with channelId ${named[0]}.`
|
|
3747
3823
|
: named.length > 1 ? ` Call again with channelId set to one of ${named.join(', ')}.` : '';
|
|
3748
|
-
const listed = candidates.map((c) => (c.reaches ?
|
|
3749
|
-
|
|
3824
|
+
const listed = candidates.map((c) => (c.reaches ? quotedValue(c.text) : c.text)).join(', ');
|
|
3825
|
+
const byServer = takesServer && candidates.some((c) => c.byServer)
|
|
3826
|
+
? ' For a channel shown by its id and server, call again with that channelId and that serverId.'
|
|
3827
|
+
: '';
|
|
3828
|
+
return `"${name}" is the id of this turn's channels on more than one server (${listed}), so nothing ${done}.${pick}${byServer}`;
|
|
3750
3829
|
}
|
|
3751
3830
|
resolveTargetByName(spec) {
|
|
3752
3831
|
const entries = [...this.channels.values()];
|
|
@@ -3757,7 +3836,7 @@ export class ChannelRegistry {
|
|
|
3757
3836
|
// A candidate is named by the token that picks it alone (nameFor), so
|
|
3758
3837
|
// two servers' channels with one id or one label can be told apart, or,
|
|
3759
3838
|
// where no string picks it alone, by its id and its server.
|
|
3760
|
-
const token = (e) => this.
|
|
3839
|
+
const token = (e) => this.nameShown(refOf(e));
|
|
3761
3840
|
// A DM is shown by its label, the person, where that label, given as a
|
|
3762
3841
|
// target, picks that DM. It doesn't where another channel answers to that
|
|
3763
3842
|
// label too, as two servers' DMs with one person do (a label is matched
|
|
@@ -3774,7 +3853,7 @@ export class ChannelRegistry {
|
|
|
3774
3853
|
};
|
|
3775
3854
|
const dmShown = (list) => list.map((e) => {
|
|
3776
3855
|
const label = e.descriptor.label ?? e.descriptor.id;
|
|
3777
|
-
return labelPicks(e, label) ? label : token(e);
|
|
3856
|
+
return showsAsItself(label) && labelPicks(e, label) ? label : token(e);
|
|
3778
3857
|
});
|
|
3779
3858
|
// A channel id, or its server-qualified name (entriesNamed).
|
|
3780
3859
|
const exact = this.entriesNamed(trimmed);
|
|
@@ -3879,7 +3958,7 @@ export class ChannelRegistry {
|
|
|
3879
3958
|
if (byName.length > 1) {
|
|
3880
3959
|
return {
|
|
3881
3960
|
error: `"${trimmed}" matches several channels`,
|
|
3882
|
-
candidates: byName.map((e) => `${e.descriptor.label} = ${token(e)}`),
|
|
3961
|
+
candidates: byName.map((e) => `${nameToTypeBack(e.descriptor.label ?? '')} = ${token(e)}`),
|
|
3883
3962
|
};
|
|
3884
3963
|
}
|
|
3885
3964
|
// No match: offer near-candidates (labels containing the name) so the
|
|
@@ -3887,7 +3966,7 @@ export class ChannelRegistry {
|
|
|
3887
3966
|
const near = entries
|
|
3888
3967
|
.filter((e) => norm(e.descriptor.label ?? '').includes(norm(trimmed)))
|
|
3889
3968
|
.slice(0, 5)
|
|
3890
|
-
.map((e) => `${e.descriptor.label} = ${token(e)}`);
|
|
3969
|
+
.map((e) => `${nameToTypeBack(e.descriptor.label ?? '')} = ${token(e)}`);
|
|
3891
3970
|
return { error: `no channel matches "${trimmed}"`, ...(near.length ? { candidates: near } : {}) };
|
|
3892
3971
|
}
|
|
3893
3972
|
/**
|
|
@@ -3924,7 +4003,7 @@ export class ChannelRegistry {
|
|
|
3924
4003
|
qualifiedChannelName(ref),
|
|
3925
4004
|
];
|
|
3926
4005
|
for (const c of candidates) {
|
|
3927
|
-
if (!isProseTargetToken(c) || c === '#')
|
|
4006
|
+
if (!isProseTargetToken(c) || c === '#' || !showsAsItself(c))
|
|
3928
4007
|
continue;
|
|
3929
4008
|
const r = this.resolveProseTarget(c, { forSpeech: true });
|
|
3930
4009
|
if ('channelId' in r && sameChannel(r, ref))
|
|
@@ -4192,7 +4271,7 @@ export class ChannelRegistry {
|
|
|
4192
4271
|
channelId: input.channelId,
|
|
4193
4272
|
serverId: entry.serverId,
|
|
4194
4273
|
status: alreadyDesiredOpen ? 'reconciled' : 'opened',
|
|
4195
|
-
...(result.history ? { history: result.history } : {}),
|
|
4274
|
+
...(result.history ? { history: markedHistory(result.history) } : {}),
|
|
4196
4275
|
...(result.historyTruncated ? { historyTruncated: true } : {}),
|
|
4197
4276
|
},
|
|
4198
4277
|
};
|
|
@@ -4649,13 +4728,14 @@ export class ChannelRegistry {
|
|
|
4649
4728
|
* tool, so any trailing prose this turn is NOT posted. Replaces the old
|
|
4650
4729
|
* overloaded use of `think` for staying silent.
|
|
4651
4730
|
*/
|
|
4652
|
-
handleToolSkipReply(input) {
|
|
4731
|
+
handleToolSkipReply(input, earlierWake) {
|
|
4653
4732
|
// The self-wake itself is armed by the framework (which owns the
|
|
4654
4733
|
// EventGate) BEFORE its dispatch reaches this handler, from the same
|
|
4655
4734
|
// reading, and the framework refuses a wake the host can't set before
|
|
4656
4735
|
// dispatching. A caller of this registry's handleChannelToolCall
|
|
4657
4736
|
// (AgentFramework.channels) goes past both, so its note names a wake
|
|
4658
|
-
// nothing armed.
|
|
4737
|
+
// nothing armed. The framework, which also owns the heartbeat, says
|
|
4738
|
+
// which of its wakes is due before the self-wake (`earlierWake`).
|
|
4659
4739
|
const wake = readSkipReplyWake(input);
|
|
4660
4740
|
const selfWake = wake.kind === 'wake' ? wake.seconds : undefined;
|
|
4661
4741
|
return {
|
|
@@ -4671,9 +4751,7 @@ export class ChannelRegistry {
|
|
|
4671
4751
|
// on Fable: ~60% of skip_reply result bytes were verbatim input echo).
|
|
4672
4752
|
data: {
|
|
4673
4753
|
skipped: true,
|
|
4674
|
-
note: selfWake !== undefined
|
|
4675
|
-
? `Turn ended; nothing sent. Self-wake in ~${selfWake}s unless something wakes you first.`
|
|
4676
|
-
: 'Turn ended; nothing sent.',
|
|
4754
|
+
note: skipReplyNote(selfWake, selfWake !== undefined ? earlierWake : undefined),
|
|
4677
4755
|
},
|
|
4678
4756
|
};
|
|
4679
4757
|
}
|
|
@@ -5228,7 +5306,7 @@ export class ChannelRegistry {
|
|
|
5228
5306
|
else if (own && 'neither' in own) {
|
|
5229
5307
|
if (seen)
|
|
5230
5308
|
seen.refusal = 'shared-id';
|
|
5231
|
-
return { success: false, error: this.ownNeitherRefusal(input.channelId, own.neither, 'was posted'), isError: true };
|
|
5309
|
+
return { success: false, error: this.ownNeitherRefusal(input.channelId, own.neither, 'was posted', false), isError: true };
|
|
5232
5310
|
}
|
|
5233
5311
|
else if (own) {
|
|
5234
5312
|
entry = this.entryAt(own.channel);
|
|
@@ -5265,7 +5343,7 @@ export class ChannelRegistry {
|
|
|
5265
5343
|
// channel with its id isn't where its message came from.
|
|
5266
5344
|
error: input.channelId === undefined
|
|
5267
5345
|
? serverGiven
|
|
5268
|
-
? `Server "${input.serverId}" has no channel
|
|
5346
|
+
? `Server "${input.serverId}" has no channel ${quotedValue(fallback.channelId)}, the id of the channel this call would default to, so nothing was posted.`
|
|
5269
5347
|
: `The channel this would default to, ${this.quotedShown(fallback)}, isn't registered now, so nothing was posted.`
|
|
5270
5348
|
// This tool's schema has no serverId, so one given here is a host's
|
|
5271
5349
|
// ref (publishForAgent), or one an agent added unasked, and the
|