@animalabs/agent-framework 0.27.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 +145 -41
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +73 -12
- 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 +11 -5
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +5 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +9 -5
- 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/cut-off-round.d.ts +24 -0
- package/dist/src/cut-off-round.d.ts.map +1 -0
- package/dist/src/cut-off-round.js +87 -0
- package/dist/src/cut-off-round.js.map +1 -0
- package/dist/src/framework.d.ts +468 -125
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3651 -1111
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +133 -13
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +654 -59
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +3 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +2 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +116 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -0
- package/dist/src/gate/reply-judge.js +467 -0
- package/dist/src/gate/reply-judge.js.map +1 -0
- package/dist/src/gate/types.d.ts +173 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +202 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +778 -0
- package/dist/src/heartbeat/heartbeat.js.map +1 -0
- package/dist/src/heartbeat/reminder-time.d.ts +5 -0
- package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
- package/dist/src/heartbeat/reminder-time.js +107 -0
- package/dist/src/heartbeat/reminder-time.js.map +1 -0
- 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 +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -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/capability-mask.d.ts.map +1 -1
- package/dist/src/mcpl/capability-mask.js +3 -2
- package/dist/src/mcpl/capability-mask.js.map +1 -1
- 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 +133 -54
- 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 +29 -11
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +43 -70
- 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 +18 -8
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +70 -30
- 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/tags.d.ts +9 -2
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +12 -2
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +66 -14
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +150 -80
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +10 -3
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +60 -30
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -55
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +220 -111
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +27 -35
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +313 -91
- 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 +31 -13
- 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 +10 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +15 -9
- 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.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +30 -17
- 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 +26 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +138 -0
- package/dist/src/modules/workspace/answers.js.map +1 -0
- package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +6 -2
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +3 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +59 -24
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +48 -17
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/edit-text.d.ts +2 -1
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +4 -2
- package/dist/src/modules/workspace/edit-text.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 +67 -50
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +63 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +980 -547
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +125 -82
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +465 -241
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
- package/dist/src/modules/workspace/read-page.js +7 -1
- package/dist/src/modules/workspace/read-page.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 +156 -50
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +1 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/result-text.js.map +1 -1
- package/dist/src/modules/workspace/sync.d.ts +9 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +14 -0
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +96 -43
- 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.map +1 -1
- package/dist/src/modules/workspace/watcher.js +8 -3
- package/dist/src/modules/workspace/watcher.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/prose-drafts.d.ts +4 -3
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +3 -2
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/reasoning-loss-notice.d.ts +23 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
- package/dist/src/reasoning-loss-notice.js +52 -0
- package/dist/src/reasoning-loss-notice.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +24 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +102 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/send-tools.d.ts +3 -2
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +3 -2
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +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/thinking-drops.d.ts +4 -11
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js +7 -14
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-args.d.ts +12 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +39 -4
- 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/tool-result-guard.d.ts +8 -4
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +23 -11
- package/dist/src/tool-result-guard.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/tune-out/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +63 -18
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +18 -8
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +58 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +27 -13
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +34 -1
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +76 -12
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +11 -5
- package/src/auto-continue.ts +12 -5
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +3772 -1138
- package/src/gate/event-gate.ts +705 -60
- package/src/gate/index.ts +9 -1
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +176 -3
- package/src/heartbeat/heartbeat.ts +843 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +5 -1
- package/src/line-reader.ts +58 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +139 -53
- 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 +45 -70
- 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 +76 -34
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +149 -82
- package/src/mcpl/tool-signature.ts +64 -32
- package/src/mcpl/toolset-host.ts +212 -110
- package/src/mcpl/toolsets-module.ts +277 -91
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +32 -13
- package/src/module-registry.ts +14 -9
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +30 -18
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +151 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +59 -23
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +7 -5
- package/src/modules/workspace/effects.ts +69 -50
- package/src/modules/workspace/index.ts +966 -556
- package/src/modules/workspace/observe.ts +497 -242
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +184 -49
- package/src/modules/workspace/result-text.ts +2 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/types.ts +96 -43
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/notice-tags.ts +84 -0
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +64 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +38 -4
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/coordinator.ts +25 -27
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +63 -18
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +59 -0
- package/src/types/module.ts +28 -13
- package/src/types/trace.ts +36 -1
package/src/mcpl/transport.ts
CHANGED
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
22
|
-
import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
|
|
23
22
|
import { EventEmitter } from 'node:events';
|
|
23
|
+
import type { Readable } from 'node:stream';
|
|
24
24
|
import { StringDecoder } from 'node:string_decoder';
|
|
25
|
+
import { escapeLineSeparators, readLines } from '../line-reader.js';
|
|
25
26
|
import WebSocket from 'ws';
|
|
26
27
|
|
|
27
28
|
import type { McplServerConfig } from './types.js';
|
|
@@ -325,16 +326,18 @@ export class StdioTransport extends McplTransport {
|
|
|
325
326
|
});
|
|
326
327
|
}
|
|
327
328
|
|
|
329
|
+
private readonly stopLines: () => void;
|
|
330
|
+
|
|
328
331
|
private constructor(
|
|
329
332
|
private readonly child: ChildProcess,
|
|
330
|
-
|
|
333
|
+
stdout: Readable,
|
|
331
334
|
/** The server this child serves, named in its stderr lines and in what
|
|
332
335
|
* its reaping reports. */
|
|
333
336
|
serverId: string,
|
|
334
337
|
) {
|
|
335
338
|
super(serverId);
|
|
336
339
|
|
|
337
|
-
this.
|
|
340
|
+
this.stopLines = readLines(stdout, (line) => this.pushLine(line));
|
|
338
341
|
|
|
339
342
|
// Stitch stderr chunks into whole lines (a chunk can split a line, and a
|
|
340
343
|
// character's bytes), and keep the stream's last characters for
|
|
@@ -383,12 +386,7 @@ export class StdioTransport extends McplTransport {
|
|
|
383
386
|
});
|
|
384
387
|
};
|
|
385
388
|
this.child.stdin?.on('error', endOnPipeError);
|
|
386
|
-
// On the stream itself, since close() closes readline, which takes its own
|
|
387
|
-
// listener off the stream. While readline is open, its listener, which
|
|
388
|
-
// came first, re-emits the error on the interface, where an empty
|
|
389
|
-
// listener absorbs that copy.
|
|
390
389
|
this.child.stdout?.on('error', endOnPipeError);
|
|
391
|
-
this.rl.on('error', () => { /* readline's copy of a stdout error, handled above */ });
|
|
392
390
|
// stderr carries diagnostics only: a read error on it is reported, to the
|
|
393
391
|
// listeners or else in the log, and never thrown. It ends nothing once
|
|
394
392
|
// the connection is wired; during a handshake the listener is the
|
|
@@ -453,8 +451,7 @@ export class StdioTransport extends McplTransport {
|
|
|
453
451
|
resolve();
|
|
454
452
|
});
|
|
455
453
|
});
|
|
456
|
-
|
|
457
|
-
return new StdioTransport(child, rl, config.id);
|
|
454
|
+
return new StdioTransport(child, child.stdout!, config.id);
|
|
458
455
|
}
|
|
459
456
|
|
|
460
457
|
writeLine(json: string): void {
|
|
@@ -462,7 +459,7 @@ export class StdioTransport extends McplTransport {
|
|
|
462
459
|
// transport is ending it.
|
|
463
460
|
if (this.pipeError) return;
|
|
464
461
|
// stdin may be gone if the child died between the closed-check and here.
|
|
465
|
-
this.child.stdin?.write(json + '\n');
|
|
462
|
+
this.child.stdin?.write(escapeLineSeparators(json) + '\n');
|
|
466
463
|
}
|
|
467
464
|
|
|
468
465
|
private closing: Promise<void> | null = null;
|
|
@@ -492,7 +489,7 @@ export class StdioTransport extends McplTransport {
|
|
|
492
489
|
|
|
493
490
|
private async reap(): Promise<void> {
|
|
494
491
|
this.markClosed({ reason: 'closed by host' });
|
|
495
|
-
this.
|
|
492
|
+
this.stopLines();
|
|
496
493
|
await this.terminate();
|
|
497
494
|
}
|
|
498
495
|
|
|
@@ -696,7 +693,7 @@ export class WebSocketTransport extends McplTransport {
|
|
|
696
693
|
|
|
697
694
|
writeLine(json: string): void {
|
|
698
695
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
699
|
-
this.ws.send(json);
|
|
696
|
+
this.ws.send(escapeLineSeparators(json));
|
|
700
697
|
}
|
|
701
698
|
}
|
|
702
699
|
|
|
@@ -830,7 +827,7 @@ const SERVER_CONFIG_BOOLEANS: Record<BooleanSetting, string> = {
|
|
|
830
827
|
inheritEnv: 'true passes the server the host\'s whole environment, and false or leaving it out passes only the allowlisted and LC_* host variables',
|
|
831
828
|
reconnect: 'true retries in the background when the server can\'t be reached or its connection drops, and false or leaving it out doesn\'t',
|
|
832
829
|
allowHostCommands: 'true lets the server run the host\'s admin commands, such as undo, hide, unstick and quiesce, and false or leaving it out refuses them',
|
|
833
|
-
toolHints: 'true advertises MCPL tool hints (RFC-014), so
|
|
830
|
+
toolHints: 'true advertises MCPL tool hints (RFC-014) to an MCPL server, so its dynamic tools are announced in the conversation and called through toolsets--act; leaving it out does the same wherever the name toolsets is free; false lists every tool as before and advertises none',
|
|
834
831
|
};
|
|
835
832
|
|
|
836
833
|
/**
|
|
@@ -849,6 +846,13 @@ export function assertServerConfigBooleans(config: McplServerConfig): void {
|
|
|
849
846
|
if (setting === 'inheritEnv' && dialsNetwork(config)) continue;
|
|
850
847
|
assertBooleanSetting(`MCPL server "${config.id}" ${setting}`, config[setting], whatTrueDoes);
|
|
851
848
|
}
|
|
849
|
+
// Not a boolean, but read the same way: a value that isn't one of the
|
|
850
|
+
// three would otherwise be taken for the default.
|
|
851
|
+
const presentation = (config as { presentation?: unknown }).presentation;
|
|
852
|
+
if (presentation !== undefined && presentation !== null && presentation !== 'full' && presentation !== 'tab' && presentation !== 'closed-tab') {
|
|
853
|
+
const kind = typeof presentation === 'string' ? 'another string' : Array.isArray(presentation) ? 'a list' : typeof presentation;
|
|
854
|
+
throw new Error(`MCPL server "${config.id}" presentation must be "full", "tab" or "closed-tab" (or left out for full), not ${kind}`);
|
|
855
|
+
}
|
|
852
856
|
}
|
|
853
857
|
|
|
854
858
|
/**
|
package/src/mcpl/types.ts
CHANGED
|
@@ -193,8 +193,8 @@ export interface McplHostCapabilities {
|
|
|
193
193
|
modelInfo?: boolean;
|
|
194
194
|
featureSets?: boolean;
|
|
195
195
|
channels?: boolean | McplChannelCapabilities;
|
|
196
|
-
/** RFC-014 §6.3: this host presents tool hints. Sent
|
|
197
|
-
*
|
|
196
|
+
/** RFC-014 §6.3: this host presents tool hints. Sent to every server
|
|
197
|
+
* with hints (McplServerConfig.toolHints: on unless false). */
|
|
198
198
|
toolHints?: boolean;
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -384,7 +384,9 @@ export interface McplServerConfig {
|
|
|
384
384
|
*
|
|
385
385
|
* `disabledCapabilities` takes precedence on conflict. `version` and
|
|
386
386
|
* `featureSets` are not maskable here — feature sets have their own
|
|
387
|
-
* enable/disable knobs above
|
|
387
|
+
* enable/disable knobs above — and neither are RFC-014's `toolHints`,
|
|
388
|
+
* `toolsets` and `surface`, which a server's own `toolHints` config turns
|
|
389
|
+
* off.
|
|
388
390
|
*/
|
|
389
391
|
enabledCapabilities?: string[];
|
|
390
392
|
|
|
@@ -506,18 +508,35 @@ export interface McplServerConfig {
|
|
|
506
508
|
toolLifecycle?: import('./tool-lifecycle.js').ToolLifecycleConfig;
|
|
507
509
|
|
|
508
510
|
/**
|
|
509
|
-
* MCPL RFC-014 (tool presence, toolsets and surfaces).
|
|
510
|
-
* host advertises
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* `toolsets--
|
|
518
|
-
*
|
|
511
|
+
* MCPL RFC-014 (tool presence, toolsets and surfaces). On unless set to
|
|
512
|
+
* `false`: the host advertises `experimental.mcpl.toolHints` and honours
|
|
513
|
+
* hints (through the `toolsets` meta-tools) only from a server that
|
|
514
|
+
* advertised `toolHints` too. Such a server's static tools stay in the tool
|
|
515
|
+
* list as before, and its dynamic tools (toolset tools, `mcpl/presence:
|
|
516
|
+
* "dynamic"`) leave it: they are announced in the conversation as they
|
|
517
|
+
* become visible and invisible, and called through `toolsets--act`, opened
|
|
518
|
+
* with `toolsets--open`, inspected with `toolsets--look` and closed with
|
|
519
|
+
* `toolsets--close`. A server that doesn't advertise `toolHints` lists every
|
|
520
|
+
* tool as before. `false` advertises nothing and treats every tool as
|
|
521
|
+
* static. Omitted and null are on unless a module given to `create()`
|
|
522
|
+
* holds the name `toolsets` (or a restart is moving a server to that
|
|
523
|
+
* prefix, which is refused); `true` with such a module is refused.
|
|
519
524
|
*/
|
|
520
525
|
toolHints?: boolean;
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* How this server is presented to agents at first (the host's own
|
|
529
|
+
* toolsets, src/host-presentation.ts): `'full'` (the default) puts its
|
|
530
|
+
* tools in the tool list, as before; `'tab'` keeps it connected, its events
|
|
531
|
+
* still arriving, with none of its tools in the tool list until an agent
|
|
532
|
+
* opens its tab (`toolsets--open {server}`) and calls them through
|
|
533
|
+
* `toolsets--act`; `'closed-tab'` leaves it disconnected until an agent
|
|
534
|
+
* opens it. Each agent can switch it later (`toolsets--close {server}`,
|
|
535
|
+
* with `disconnect: true` for a closed tab; `toolsets--open {server,
|
|
536
|
+
* full: true}` back into the tool list), and that choice is kept across
|
|
537
|
+
* restarts beside the store, over this default.
|
|
538
|
+
*/
|
|
539
|
+
presentation?: 'full' | 'tab' | 'closed-tab';
|
|
521
540
|
}
|
|
522
541
|
|
|
523
542
|
// ============================================================================
|
package/src/module-registry.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { heldWriteId } from './held-writes.js';
|
|
|
28
28
|
import type { Agent } from './agent.js';
|
|
29
29
|
import { DEFAULT_SHUTDOWN_TIMEOUT_MS, validateShutdownTimeout, waitForShutdown } from './shutdown.js';
|
|
30
30
|
import { logValue } from './mcpl/log-value.js';
|
|
31
|
-
import { toolArgsProblems } from './tool-args.js';
|
|
31
|
+
import { asCalled, markedInputRefusal, toolArgsProblems } from './tool-args.js';
|
|
32
32
|
|
|
33
33
|
/** How the registry queues a module's event: its push names the module. A
|
|
34
34
|
* `ProcessQueue` serves too, ignoring the name. */
|
|
@@ -282,8 +282,8 @@ export class ModuleRegistry {
|
|
|
282
282
|
/**
|
|
283
283
|
* All utilities from all modules, namespaced exactly like tools
|
|
284
284
|
* (`module--name`). See Module.getUtilities — these are deliberately kept
|
|
285
|
-
* out of the model-facing tool list and
|
|
286
|
-
*
|
|
285
|
+
* out of the model-facing tool list and presented in the host's built-in
|
|
286
|
+
* toolset; dispatch goes through the same handleToolCall.
|
|
287
287
|
*/
|
|
288
288
|
getAllUtilities(): ToolDefinition[] {
|
|
289
289
|
const utilities: ToolDefinition[] = [];
|
|
@@ -300,8 +300,8 @@ export class ModuleRegistry {
|
|
|
300
300
|
|
|
301
301
|
/**
|
|
302
302
|
* A module tool's call for a path that stores or sends its result: the
|
|
303
|
-
* model's dispatch
|
|
304
|
-
*
|
|
303
|
+
* model's dispatch (a `toolsets--act` call of it included), a script's
|
|
304
|
+
* inner call. The result is checked and read once, in its JSON form
|
|
305
305
|
* ({@link storableToolResult}).
|
|
306
306
|
*/
|
|
307
307
|
async storableToolCall(call: ToolCall, options: { definition?: ToolDefinition } = {}): Promise<ToolResult> {
|
|
@@ -317,11 +317,16 @@ export class ModuleRegistry {
|
|
|
317
317
|
* A call whose input isn't an object, or lacks a key the tool's schema
|
|
318
318
|
* requires, is answered before the module runs, with an error result that
|
|
319
319
|
* opens "Nothing was done:" and names what to send (toolArgsProblems).
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
320
|
+
* So is a call membrane marked `unparseableInput`, unless its text is JSON
|
|
321
|
+
* `null`, which reaches the module as a call with no arguments
|
|
322
|
+
* (markedInputRefusal).
|
|
323
|
+
* A caller that has already resolved the tool's definition passes it as
|
|
324
|
+
* `definition`, and it's checked against that one.
|
|
323
325
|
*/
|
|
324
|
-
async handleToolCall(
|
|
326
|
+
async handleToolCall(given: ToolCall, options: { definition?: ToolDefinition } = {}): Promise<ToolResult> {
|
|
327
|
+
const marked = markedInputRefusal(given);
|
|
328
|
+
if (marked !== undefined) return { success: false, error: marked, isError: true };
|
|
329
|
+
const call = asCalled(given);
|
|
325
330
|
// Parse module--tool name
|
|
326
331
|
const sepIndex = call.name.indexOf('--');
|
|
327
332
|
if (sepIndex === -1) {
|
package/src/modules/api/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { unknownToolMessage } from '../unknown-tool.js';
|
|
9
|
+
import { outsideSenderName } from '../sender-name.js';
|
|
9
10
|
import type {
|
|
10
11
|
Module,
|
|
11
12
|
ModuleContext,
|
|
@@ -62,12 +63,13 @@ export class ApiModule implements Module {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
private handleMessage(event: ApiMessageEvent): EventResponse {
|
|
66
|
+
const carried = event.metadata?.author;
|
|
65
67
|
const response: EventResponse = {
|
|
66
68
|
addMessages: [
|
|
67
69
|
{
|
|
68
|
-
participant: event.participant,
|
|
70
|
+
participant: outsideSenderName(event.participant, 'API'),
|
|
69
71
|
content: [{ type: 'text', text: event.content }],
|
|
70
|
-
metadata: event.metadata,
|
|
72
|
+
metadata: { ...event.metadata, author: typeof carried === 'object' && carried !== null ? carried : { name: event.participant } },
|
|
71
73
|
},
|
|
72
74
|
],
|
|
73
75
|
};
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* - Tools for explicit Discord operations (send, DM, react, etc.)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { NOTICE_TAG } from '../../notice-tags.js';
|
|
11
|
+
import { markedText, suppliedValue } from '../../supplied-text.js';
|
|
10
12
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
11
13
|
import { continuesBody, type StoredMessage } from '@animalabs/context-manager';
|
|
12
14
|
import type {
|
|
@@ -47,6 +49,7 @@ import type {
|
|
|
47
49
|
import { formatZonedDateTime, resolveTimeZone } from '../../timezone.js';
|
|
48
50
|
import { logValue } from '../../mcpl/log-value.js';
|
|
49
51
|
import { unknownToolMessage } from '../unknown-tool.js';
|
|
52
|
+
import { outsideSenderName } from '../sender-name.js';
|
|
50
53
|
import { DiscordRefusal } from './refusal.js';
|
|
51
54
|
|
|
52
55
|
// Re-export event types for consumers who want to handle them
|
|
@@ -119,10 +122,11 @@ const NOT_RUNNING = "Speech handling wasn't changed: the Discord module hasn't s
|
|
|
119
122
|
/** What the agent reads for a text attachment whose contents couldn't be
|
|
120
123
|
* fetched: by an error, or by an answer that wasn't OK. */
|
|
121
124
|
function attachmentNotFetched(filename: string): { type: 'text'; text: string } {
|
|
122
|
-
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
return { type: 'text', text: notFetchedText(suppliedValue(filename)) };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function notFetchedText(filename: string): string {
|
|
129
|
+
return `[${NOTICE_TAG.attachment}: ${filename} couldn't be fetched, so its text isn't in your context. If you need it, ask the sender in the channel to paste it.]`;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/**
|
|
@@ -165,7 +169,7 @@ function seconds(n: number): string {
|
|
|
165
169
|
|
|
166
170
|
/** What a stored message reads when it has no text of its own and nothing the
|
|
167
171
|
* host added, so the agent still sees that a message was there. */
|
|
168
|
-
const EMPTY_MESSAGE =
|
|
172
|
+
const EMPTY_MESSAGE = `[${NOTICE_TAG.emptyMessage}]`;
|
|
169
173
|
|
|
170
174
|
/**
|
|
171
175
|
* Metadata on a stored Discord message whose first block is its own text,
|
|
@@ -184,6 +188,14 @@ function ownText(content: string): string {
|
|
|
184
188
|
return content.trim() ? content : '';
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
function storedOwnText(content: string): string {
|
|
192
|
+
return markedText(ownText(content));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function holdsOwnText(text: string, own: string): boolean {
|
|
196
|
+
return text === own || text === markedText(own);
|
|
197
|
+
}
|
|
198
|
+
|
|
187
199
|
const isEmptyMarker = (block: ContentBlock): boolean => block.type === 'text' && block.text === EMPTY_MESSAGE;
|
|
188
200
|
const isBlankText = (block: ContentBlock): boolean => block.type === 'text' && !block.text.trim();
|
|
189
201
|
|
|
@@ -198,7 +210,7 @@ function textFirst(text: string, after: ContentBlock[] = []): ContentBlock[] {
|
|
|
198
210
|
/** A text-first row's content with `text` as the message's own text, in
|
|
199
211
|
* place of its first block, keeping what the host added after it. */
|
|
200
212
|
function withOwnText(content: ContentBlock[], text: string): ContentBlock[] {
|
|
201
|
-
return textFirst(
|
|
213
|
+
return textFirst(storedOwnText(text), content.slice(1).filter((block) => !isEmptyMarker(block)));
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
/**
|
|
@@ -221,11 +233,11 @@ function contentForText(stored: StoredMessage, discordText: string): ContentBloc
|
|
|
221
233
|
const text = ownText(discordText);
|
|
222
234
|
const [first, ...rest] = stored.content;
|
|
223
235
|
if (stored.metadata?.[TEXT_FIRST] === true) {
|
|
224
|
-
if (first?.type === 'text' && first.text
|
|
236
|
+
if (first?.type === 'text' && holdsOwnText(first.text, text)) return null;
|
|
225
237
|
return withOwnText(stored.content, text);
|
|
226
238
|
}
|
|
227
|
-
if (!text || (first?.type === 'text' && first.text
|
|
228
|
-
return [{ type: 'text', text: discordText }, ...(first?.type === 'text' ? rest : stored.content).filter((block) => !isEmptyMarker(block))];
|
|
239
|
+
if (!text || (first?.type === 'text' && holdsOwnText(first.text, discordText))) return null;
|
|
240
|
+
return [{ type: 'text', text: markedText(discordText) }, ...(first?.type === 'text' ? rest : stored.content).filter((block) => !isEmptyMarker(block))];
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
/**
|
|
@@ -255,7 +267,7 @@ function needsRebuild(stored: StoredMessage, message: HistoryMessage): boolean {
|
|
|
255
267
|
const [first, ...rest] = stored.content;
|
|
256
268
|
if (!own && first?.type === 'text' && !isBlankText(first) && !isEmptyMarker(first)) return true;
|
|
257
269
|
const expected = (own ? 1 : 0) + attachments;
|
|
258
|
-
const counted = own && first?.type === 'text' && first.text
|
|
270
|
+
const counted = own && first?.type === 'text' && holdsOwnText(first.text, own) ? 1 + held(rest) : held(stored.content);
|
|
259
271
|
return counted !== expected || (expected === 0 && !stored.content.some(isEmptyMarker));
|
|
260
272
|
}
|
|
261
273
|
|
|
@@ -268,14 +280,14 @@ function needsRebuild(stored: StoredMessage, message: HistoryMessage): boolean {
|
|
|
268
280
|
function holdsOtherText(stored: StoredMessage, message: HistoryMessage): boolean {
|
|
269
281
|
const own = ownText(message.content);
|
|
270
282
|
const [first, ...rest] = stored.content;
|
|
271
|
-
const others = own && first?.type === 'text' && first.text
|
|
283
|
+
const others = own && first?.type === 'text' && holdsOwnText(first.text, own) ? rest : stored.content;
|
|
272
284
|
return others.some((block) => block.type === 'text' && !isBlankText(block) && !isEmptyMarker(block));
|
|
273
285
|
}
|
|
274
286
|
|
|
275
287
|
/** Whether a message's conversion holds, after the message's own text, the
|
|
276
288
|
* note for an attachment of its whose text wasn't fetched. */
|
|
277
289
|
function holdsNotFetched(content: ContentBlock[], message: HistoryMessage): boolean {
|
|
278
|
-
const notes = new Set((message.attachments ?? []).
|
|
290
|
+
const notes = new Set((message.attachments ?? []).flatMap((attachment) => [attachmentNotFetched(attachment.filename).text, notFetchedText(attachment.filename)]));
|
|
279
291
|
return content.slice(1).some((block) => block.type === 'text' && notes.has(block.text));
|
|
280
292
|
}
|
|
281
293
|
|
|
@@ -302,9 +314,10 @@ function forGoneAttachment(block: ContentBlock, attachments: readonly DiscordAtt
|
|
|
302
314
|
if (block.type !== 'text') return false;
|
|
303
315
|
const { text } = block;
|
|
304
316
|
const opening = text.startsWith('\n--- File: ') ? (filename: string) => `\n--- File: ${filename} ---\n`
|
|
305
|
-
: text.startsWith(
|
|
317
|
+
: text.startsWith(`[${NOTICE_TAG.attachment}: `) ? (filename: string) => `[${NOTICE_TAG.attachment}: ${filename} `
|
|
306
318
|
: null;
|
|
307
|
-
return opening !== null && !attachments.some((attachment) =>
|
|
319
|
+
return opening !== null && !attachments.some((attachment) =>
|
|
320
|
+
text.startsWith(opening(suppliedValue(attachment.filename))) || text.startsWith(opening(attachment.filename)));
|
|
308
321
|
}
|
|
309
322
|
|
|
310
323
|
/**
|
|
@@ -818,7 +831,7 @@ export class DiscordModule implements Module {
|
|
|
818
831
|
|
|
819
832
|
// Built from the message and its attachments, or from its text alone
|
|
820
833
|
// when an event carries no blocks; text first, as TEXT_FIRST marks.
|
|
821
|
-
const contentBlocks = msg.contentBlocks ?? textFirst(
|
|
834
|
+
const contentBlocks = msg.contentBlocks ?? textFirst(storedOwnText(msg.content));
|
|
822
835
|
this.noteLive(msg.discordMessageId);
|
|
823
836
|
|
|
824
837
|
// A message a history sync wrote first, stored or held, keeps that row:
|
|
@@ -833,9 +846,10 @@ export class DiscordModule implements Module {
|
|
|
833
846
|
...(stored ? {} : {
|
|
834
847
|
addMessages: [
|
|
835
848
|
{
|
|
836
|
-
participant: msg.authorName,
|
|
849
|
+
participant: outsideSenderName(msg.authorName, 'Discord'),
|
|
837
850
|
content: contentBlocks,
|
|
838
851
|
metadata: {
|
|
852
|
+
author: { id: msg.authorId, name: msg.authorName },
|
|
839
853
|
external: { source: 'discord', id: msg.discordMessageId },
|
|
840
854
|
channelId: msg.channelId,
|
|
841
855
|
timestamp: msg.timestamp,
|
|
@@ -1144,14 +1158,12 @@ export class DiscordModule implements Module {
|
|
|
1144
1158
|
// For bot's own messages (from previous sessions), use the agent name
|
|
1145
1159
|
// This ensures continuity of conversation history
|
|
1146
1160
|
const isBotMessage = discordMsg.authorId === this.state.botUserId;
|
|
1147
|
-
const agentName = ctx.getAgents()[0]?.name;
|
|
1148
|
-
const participantName = isBotMessage
|
|
1149
|
-
? (agentName ?? discordMsg.authorName)
|
|
1150
|
-
: discordMsg.authorName;
|
|
1161
|
+
const agentName = isBotMessage ? ctx.getAgents()[0]?.name : undefined;
|
|
1151
1162
|
ctx.addMessage(
|
|
1152
|
-
|
|
1163
|
+
agentName ?? outsideSenderName(discordMsg.authorName, 'Discord'),
|
|
1153
1164
|
content,
|
|
1154
1165
|
{
|
|
1166
|
+
...(agentName === undefined ? { author: { id: discordMsg.authorId, name: discordMsg.authorName } } : {}),
|
|
1155
1167
|
external: { source: 'discord', id: discordMsg.id },
|
|
1156
1168
|
channelId,
|
|
1157
1169
|
timestamp: discordMsg.timestamp.getTime(),
|
|
@@ -1371,7 +1383,7 @@ export class DiscordModule implements Module {
|
|
|
1371
1383
|
if (isTextFile && attachment.size >= TEXT_FILE_LIMIT_BYTES) {
|
|
1372
1384
|
blocks.push({
|
|
1373
1385
|
type: 'text',
|
|
1374
|
-
text: `[
|
|
1386
|
+
text: `[${NOTICE_TAG.attachment}: ${suppliedValue(attachment.filename)} (${attachment.size.toLocaleString('en-US')} bytes): this host reads text files under ${TEXT_FILE_LIMIT_BYTES.toLocaleString('en-US')} bytes into your context, so its text isn't here. If you need it, ask the sender to paste the part that matters.]`,
|
|
1375
1387
|
});
|
|
1376
1388
|
continue;
|
|
1377
1389
|
}
|
|
@@ -1383,7 +1395,7 @@ export class DiscordModule implements Module {
|
|
|
1383
1395
|
const textContent = await response.text();
|
|
1384
1396
|
blocks.push({
|
|
1385
1397
|
type: 'text',
|
|
1386
|
-
text: `\n--- File: ${attachment.filename} ---\n${textContent}\n--- End of ${attachment.filename} ---\n`,
|
|
1398
|
+
text: `\n--- File: ${suppliedValue(attachment.filename)} ---\n${markedText(textContent)}\n--- End of ${suppliedValue(attachment.filename)} ---\n`,
|
|
1387
1399
|
});
|
|
1388
1400
|
} else {
|
|
1389
1401
|
// An answer whose body is never read holds its connection.
|
|
@@ -1401,11 +1413,11 @@ export class DiscordModule implements Module {
|
|
|
1401
1413
|
// For unsupported attachments, add a note
|
|
1402
1414
|
blocks.push({
|
|
1403
1415
|
type: 'text',
|
|
1404
|
-
text: `[
|
|
1416
|
+
text: `[${NOTICE_TAG.attachment}: ${suppliedValue(attachment.filename)} (${contentType ? suppliedValue(contentType) : 'unknown type'}): this host didn't read its contents in, so they aren't in your context. If you need them, ask the sender for them in a form you can read, such as pasted text.]`,
|
|
1405
1417
|
});
|
|
1406
1418
|
}
|
|
1407
1419
|
|
|
1408
|
-
return textFirst(
|
|
1420
|
+
return textFirst(storedOwnText(message.content), blocks);
|
|
1409
1421
|
}
|
|
1410
1422
|
|
|
1411
1423
|
/**
|
|
@@ -535,7 +535,7 @@ export class HistoryModule implements Module {
|
|
|
535
535
|
channelId?: string;
|
|
536
536
|
n: number;
|
|
537
537
|
side: 'newest' | 'oldest';
|
|
538
|
-
}): { messages: StoredMessage[]; more: boolean } {
|
|
538
|
+
}): { messages: StoredMessage[]; more: boolean; total?: number } {
|
|
539
539
|
const cm = this.cm as ContextManager;
|
|
540
540
|
const { fromMs, toMs, channelId, n, side } = opts;
|
|
541
541
|
const newest = side === 'newest';
|
|
@@ -553,8 +553,8 @@ export class HistoryModule implements Module {
|
|
|
553
553
|
const fetch = (f: number | undefined, t: number | undefined, limit: number, offset = 0) =>
|
|
554
554
|
limit <= 0 ? [] : cm.queryMessagesByTimeAndChannel({ fromMs: f, toMs: t, channelId, limit, offset }).messages;
|
|
555
555
|
const total = count(fromMs, toMs);
|
|
556
|
-
if (n === 0) return { messages: [], more: total > 0 };
|
|
557
|
-
if (total <= n) return { messages: dirSort(fetch(fromMs, toMs, total)), more: false };
|
|
556
|
+
if (n === 0) return { messages: [], more: total > 0, total };
|
|
557
|
+
if (total <= n) return { messages: dirSort(fetch(fromMs, toMs, total)), more: false, total };
|
|
558
558
|
|
|
559
559
|
// The window is parameterised by its FAR bound `e`: [e, toMs] for the
|
|
560
560
|
// newest side, [fromMs, e] for the oldest; its count is monotone in e.
|
|
@@ -580,7 +580,7 @@ export class HistoryModule implements Module {
|
|
|
580
580
|
}
|
|
581
581
|
const fetchCap = Math.max(4 * n, n + 1000);
|
|
582
582
|
if (inCount <= fetchCap) {
|
|
583
|
-
return { messages: dirSort(fetch(...win(inside), inCount)).slice(0, n), more: true };
|
|
583
|
+
return { messages: dirSort(fetch(...win(inside), inCount)).slice(0, n), more: true, total };
|
|
584
584
|
}
|
|
585
585
|
// Overshoot: bisect the far bound. The empty side starts one past the
|
|
586
586
|
// range's near end (newest: past toMs / any date; oldest: before lo).
|
|
@@ -595,7 +595,7 @@ export class HistoryModule implements Module {
|
|
|
595
595
|
inside = mid;
|
|
596
596
|
inCount = c;
|
|
597
597
|
if (c <= fetchCap) {
|
|
598
|
-
return { messages: dirSort(fetch(...win(mid), c)).slice(0, n), more: true };
|
|
598
|
+
return { messages: dirSort(fetch(...win(mid), c)).slice(0, n), more: true, total };
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
}
|
|
@@ -607,7 +607,7 @@ export class HistoryModule implements Module {
|
|
|
607
607
|
const need = n - near.length;
|
|
608
608
|
const ties = count(inside, inside);
|
|
609
609
|
const edgeMs = fetch(inside, inside, need, newest ? Math.max(0, ties - need) : 0);
|
|
610
|
-
return { messages: dirSort([...near, ...edgeMs]).slice(0, n), more: true };
|
|
610
|
+
return { messages: dirSort([...near, ...edgeMs]).slice(0, n), more: true, total };
|
|
611
611
|
}
|
|
612
612
|
|
|
613
613
|
async start(ctx: ModuleContext): Promise<void> {
|
|
@@ -1150,7 +1150,8 @@ export class HistoryModule implements Module {
|
|
|
1150
1150
|
hint:
|
|
1151
1151
|
(pageFull
|
|
1152
1152
|
? 'More matching messages exist. '
|
|
1153
|
-
: `Stopped after scanning ${scanned} messages of the window without reaching its end
|
|
1153
|
+
: `Stopped after scanning ${scanned} messages of the window without reaching its end` +
|
|
1154
|
+
(page.length === 0 ? ', so returning none is not a negative result for the window. ' : '. ')) +
|
|
1154
1155
|
`Continue by repeating this call with the same from/to/channelId/author plus ` +
|
|
1155
1156
|
`the fields of \`resume\` (windowOffset:${resume.windowOffset}, offset:${resume.offset}, afterId).`,
|
|
1156
1157
|
};
|
|
@@ -1373,6 +1374,7 @@ export class HistoryModule implements Module {
|
|
|
1373
1374
|
!!skipRange && m.timestamp.getTime() === edgeMs && m.sequence >= skipRange[0] && m.sequence <= skipRange[1];
|
|
1374
1375
|
let windowMessages: StoredMessage[];
|
|
1375
1376
|
let truncated: boolean;
|
|
1377
|
+
let unscanned: number | undefined;
|
|
1376
1378
|
{
|
|
1377
1379
|
// Over-fetch by the number of messages at the edge millisecond (the
|
|
1378
1380
|
// only ones that can be skipped), then drop the skipped ones.
|
|
@@ -1381,6 +1383,7 @@ export class HistoryModule implements Module {
|
|
|
1381
1383
|
const pool = skipRange ? w.messages.filter((m) => !isSkipped(m)) : w.messages;
|
|
1382
1384
|
truncated = w.more || pool.length > maxScan;
|
|
1383
1385
|
windowMessages = pool.slice(0, maxScan);
|
|
1386
|
+
if (w.total !== undefined) unscanned = w.total - (w.messages.length - pool.length) - windowMessages.length;
|
|
1384
1387
|
}
|
|
1385
1388
|
const candidatePoolSize = windowMessages.length;
|
|
1386
1389
|
const candidates = authorFilter ? windowMessages.filter(authorFilter) : windowMessages;
|
|
@@ -1394,7 +1397,7 @@ export class HistoryModule implements Module {
|
|
|
1394
1397
|
// the pool was never looked at) or the pool itself was truncated. The
|
|
1395
1398
|
// timestamp is the continuation point — from: (oldest) / to: (newest).
|
|
1396
1399
|
// Boundary is inclusive, so a message at exactly that instant may repeat.
|
|
1397
|
-
const resumeInfo = (scanned: number): Record<string, unknown> => {
|
|
1400
|
+
const resumeInfo = (scanned: number, matched: number): Record<string, unknown> => {
|
|
1398
1401
|
const stoppedEarly = scanned < candidates.length;
|
|
1399
1402
|
if (!stoppedEarly && !truncated) return {};
|
|
1400
1403
|
// Stopped at limit: the last candidate actually looked at. Scanned the
|
|
@@ -1432,9 +1435,16 @@ export class HistoryModule implements Module {
|
|
|
1432
1435
|
hint:
|
|
1433
1436
|
(stoppedEarly
|
|
1434
1437
|
? 'Stopped at limit; more candidates remain. '
|
|
1435
|
-
:
|
|
1438
|
+
: matched === 0
|
|
1439
|
+
? `No match among the ${order} ${candidatePoolSize} ${candidatePoolSize === 1 ? 'message' : 'messages'} ` +
|
|
1440
|
+
'scanned (maxScan); ' +
|
|
1441
|
+
(unscanned === undefined
|
|
1442
|
+
? 'the rest of this range was not searched'
|
|
1443
|
+
: `${unscanned} more in this range ${unscanned === 1 ? 'was' : 'were'} not searched`) +
|
|
1444
|
+
', so this is not a negative result for the range. '
|
|
1445
|
+
: `Only the ${order} ${candidatePoolSize} messages of this range were scanned (maxScan). `) +
|
|
1436
1446
|
`Continue by repeating the call with the fields of \`resume\` (${bound}:"${at}" and skipSequences), ` +
|
|
1437
|
-
|
|
1447
|
+
`or raise limit/maxScan, or narrow with ${[channelId === undefined ? 'channelId' : null, stoppedEarly && input.author === undefined ? 'author' : null, 'dates'].filter((w) => w !== null).join('/')}.`,
|
|
1438
1448
|
};
|
|
1439
1449
|
};
|
|
1440
1450
|
|
|
@@ -1449,7 +1459,7 @@ export class HistoryModule implements Module {
|
|
|
1449
1459
|
// on a deadline. See search-regex-worker.ts's header for the full
|
|
1450
1460
|
// rationale.
|
|
1451
1461
|
const { matches, scanned } = await this.searchWithRegexWorker(candidates, query, flags, limit);
|
|
1452
|
-
return { success: true, data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned), matches } };
|
|
1462
|
+
return { success: true, data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned, matches.length), matches } };
|
|
1453
1463
|
}
|
|
1454
1464
|
|
|
1455
1465
|
// Plain substring search (String.prototype.indexOf) is inherently
|
|
@@ -1481,7 +1491,7 @@ export class HistoryModule implements Module {
|
|
|
1481
1491
|
|
|
1482
1492
|
return {
|
|
1483
1493
|
success: true,
|
|
1484
|
-
data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned), matches },
|
|
1494
|
+
data: { scanned, ...poolInfo, truncated, ...resumeInfo(scanned, matches.length), matches },
|
|
1485
1495
|
};
|
|
1486
1496
|
}
|
|
1487
1497
|
|
|
@@ -2578,9 +2588,11 @@ function inTextUnits(text: string, hit: MatchHit): MatchHit {
|
|
|
2578
2588
|
// author filtering
|
|
2579
2589
|
// ============================================================================
|
|
2580
2590
|
|
|
2581
|
-
/** metadata.author as
|
|
2582
|
-
*
|
|
2583
|
-
*
|
|
2591
|
+
/** metadata.author as MCPL channel ingestion and DiscordModule write it
|
|
2592
|
+
* ({id, name}), and ApiModule ({name}, or the author object an api:message
|
|
2593
|
+
* event carries). Their participant is "user", or the sender's name marked
|
|
2594
|
+
* with its source unless it's the generic "user", so the plain author
|
|
2595
|
+
* name is read here. */
|
|
2584
2596
|
function authorOf(msg: StoredMessage): { id?: string; name?: string } | undefined {
|
|
2585
2597
|
const a = (msg.metadata as { author?: unknown } | undefined)?.author;
|
|
2586
2598
|
if (!a || typeof a !== 'object') return undefined;
|
|
@@ -2647,9 +2659,9 @@ function toAuthorSet(spec: AuthorSpec | undefined, field: string): Set<string> |
|
|
|
2647
2659
|
|
|
2648
2660
|
/**
|
|
2649
2661
|
* Predicate for `author`/`excludeAuthor`, or null when neither is given.
|
|
2650
|
-
* A message's identities are its metadata.author name and id
|
|
2651
|
-
* stored participant
|
|
2652
|
-
* (and anything else ingested without author metadata). Exact match after
|
|
2662
|
+
* A message's identities are its metadata.author name and id when it has
|
|
2663
|
+
* one, and otherwise its stored participant, which is what identifies the
|
|
2664
|
+
* agent's own turns (and anything else ingested without author metadata). Exact match after
|
|
2653
2665
|
* normalization, never substring: "ann" must not pull in "joanne".
|
|
2654
2666
|
*/
|
|
2655
2667
|
function buildAuthorFilter(include: AuthorSpec | undefined, exclude: AuthorSpec | undefined): ((m: StoredMessage) => boolean) | null {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The participant a module stores for a sender's name from outside the
|
|
2
|
+
* framework: on one line, and marked with its source, so it isn't an
|
|
3
|
+
* agent's name unless a host names an agent in that form. The generic
|
|
4
|
+
* `user` stays as given, since context-manager places injections by the
|
|
5
|
+
* last message whose participant is `user`, in any capitalization. */
|
|
6
|
+
export function outsideSenderName(name: string, source: string): string {
|
|
7
|
+
const line = name.replace(/[\p{Cc}\u2028\u2029]+/gu, ' ');
|
|
8
|
+
return line.toLowerCase() === 'user' ? line : `${line} (${source})`;
|
|
9
|
+
}
|