@animalabs/agent-framework 0.23.3 → 0.24.1
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 +212 -42
- package/dist/src/agent.d.ts +65 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +98 -12
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +14 -12
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/clock-rows.d.ts +115 -0
- package/dist/src/clock-rows.d.ts.map +1 -0
- package/dist/src/clock-rows.js +222 -0
- package/dist/src/clock-rows.js.map +1 -0
- package/dist/src/code-execution/engine.d.ts +177 -0
- package/dist/src/code-execution/engine.d.ts.map +1 -0
- package/dist/src/code-execution/engine.js +494 -0
- package/dist/src/code-execution/engine.js.map +1 -0
- package/dist/src/code-execution/index.d.ts +5 -10
- package/dist/src/code-execution/index.d.ts.map +1 -1
- package/dist/src/code-execution/index.js +5 -9
- package/dist/src/code-execution/index.js.map +1 -1
- package/dist/src/code-execution/isolate/calls.d.ts +114 -0
- package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/calls.js +145 -0
- package/dist/src/code-execution/isolate/calls.js.map +1 -0
- package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
- package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child-entry.js +5 -0
- package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
- package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
- package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child-main.js +132 -0
- package/dist/src/code-execution/isolate/child-main.js.map +1 -0
- package/dist/src/code-execution/isolate/child.d.ts +44 -0
- package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/child.js +315 -0
- package/dist/src/code-execution/isolate/child.js.map +1 -0
- package/dist/src/code-execution/isolate/frames.d.ts +80 -0
- package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/frames.js +210 -0
- package/dist/src/code-execution/isolate/frames.js.map +1 -0
- package/dist/src/code-execution/isolate/output.d.ts +68 -0
- package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/output.js +143 -0
- package/dist/src/code-execution/isolate/output.js.map +1 -0
- package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
- package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/prelude.js +401 -0
- package/dist/src/code-execution/isolate/prelude.js.map +1 -0
- package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
- package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/protocol.js +150 -0
- package/dist/src/code-execution/isolate/protocol.js.map +1 -0
- package/dist/src/code-execution/isolate/render.d.ts +57 -0
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/render.js +214 -0
- package/dist/src/code-execution/isolate/render.js.map +1 -0
- package/dist/src/code-execution/isolate/runner.d.ts +250 -0
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/runner.js +854 -0
- package/dist/src/code-execution/isolate/runner.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
- package/dist/src/code-execution/isolate/scripts.js +357 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -0
- package/dist/src/code-execution/quickjs.wasm +0 -0
- package/dist/src/code-execution/tool-definition.d.ts +93 -43
- package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
- package/dist/src/code-execution/tool-definition.js +176 -142
- package/dist/src/code-execution/tool-definition.js.map +1 -1
- package/dist/src/deadline-timer.d.ts +3 -0
- package/dist/src/deadline-timer.d.ts.map +1 -1
- package/dist/src/deadline-timer.js +1 -1
- package/dist/src/deadline-timer.js.map +1 -1
- package/dist/src/framework.d.ts +1120 -457
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4635 -2505
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +28 -4
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +89 -13
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/held-writes.d.ts +16 -3
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +16 -3
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +8 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +483 -115
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +1735 -425
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +19 -39
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +27 -59
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +9 -5
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +50 -0
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +130 -0
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +2 -1
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +3 -2
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/log-value.d.ts +6 -0
- package/dist/src/mcpl/log-value.d.ts.map +1 -1
- package/dist/src/mcpl/log-value.js +8 -0
- package/dist/src/mcpl/log-value.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +15 -6
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +168 -53
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/prose-grammar.d.ts +4 -5
- package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
- package/dist/src/mcpl/prose-grammar.js +4 -5
- package/dist/src/mcpl/prose-grammar.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +97 -10
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +174 -17
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -14
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +227 -50
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -20
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +7 -49
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +4 -4
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +50 -21
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +158 -74
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +24 -0
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +33 -0
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
- package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
- package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +55 -7
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +163 -34
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +22 -6
- 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 +4 -2
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +100 -13
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +8 -13
- 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 +27 -4
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +15 -3
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +27 -5
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/index.d.ts +1 -1
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/unknown-tool.d.ts +4 -1
- package/dist/src/modules/unknown-tool.d.ts.map +1 -1
- package/dist/src/modules/unknown-tool.js +4 -1
- package/dist/src/modules/unknown-tool.js.map +1 -1
- package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +3 -1
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
- package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
- package/dist/src/modules/workspace/edit-plan.js +794 -0
- package/dist/src/modules/workspace/edit-plan.js.map +1 -0
- package/dist/src/modules/workspace/edit-text.d.ts +117 -0
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
- package/dist/src/modules/workspace/edit-text.js +584 -0
- package/dist/src/modules/workspace/edit-text.js.map +1 -0
- package/dist/src/modules/workspace/effects.d.ts +5 -4
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +78 -25
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +493 -62
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +2216 -652
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +213 -38
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +416 -36
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +15 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/text.d.ts +32 -0
- package/dist/src/modules/workspace/text.d.ts.map +1 -0
- package/dist/src/modules/workspace/text.js +67 -0
- package/dist/src/modules/workspace/text.js.map +1 -0
- package/dist/src/modules/workspace/types.d.ts +210 -6
- 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 +3 -2
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/operator-log.d.ts +52 -24
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/recovery/offline-branch.d.ts +26 -1
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +164 -39
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +23 -4
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +11 -0
- package/dist/src/send-tools.d.ts.map +1 -0
- package/dist/src/send-tools.js +11 -0
- package/dist/src/send-tools.js.map +1 -0
- package/dist/src/timezone.d.ts +4 -2
- package/dist/src/timezone.d.ts.map +1 -1
- package/dist/src/timezone.js +4 -2
- package/dist/src/timezone.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts +46 -7
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +60 -7
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +14 -11
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +17 -14
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +3 -2
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +3 -2
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +21 -0
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +142 -40
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +31 -6
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +66 -9
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +69 -62
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +40 -8
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +111 -10
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/src/usage/types.d.ts +16 -0
- package/dist/src/usage/types.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.d.ts +14 -1
- package/dist/src/usage/usage-tracker.d.ts.map +1 -1
- package/dist/src/usage/usage-tracker.js +81 -41
- package/dist/src/usage/usage-tracker.js.map +1 -1
- package/package.json +6 -8
- package/src/agent.ts +99 -12
- package/src/api/server.ts +14 -12
- package/src/clock-rows.ts +302 -0
- package/src/code-execution/engine.ts +595 -0
- package/src/code-execution/index.ts +5 -17
- package/src/code-execution/isolate/calls.ts +199 -0
- package/src/code-execution/isolate/child-entry.ts +5 -0
- package/src/code-execution/isolate/child-main.ts +132 -0
- package/src/code-execution/isolate/child.ts +327 -0
- package/src/code-execution/isolate/frames.ts +209 -0
- package/src/code-execution/isolate/output.ts +161 -0
- package/src/code-execution/isolate/prelude.ts +409 -0
- package/src/code-execution/isolate/protocol.ts +240 -0
- package/src/code-execution/isolate/render.ts +230 -0
- package/src/code-execution/isolate/runner.ts +911 -0
- package/src/code-execution/isolate/scripts.ts +479 -0
- package/src/code-execution/quickjs.wasm +0 -0
- package/src/code-execution/quickjs.wasm.LICENSES +149 -0
- package/src/code-execution/tool-definition.ts +236 -167
- package/src/deadline-timer.ts +1 -1
- package/src/framework.ts +4978 -2671
- package/src/gate/event-gate.ts +79 -17
- package/src/held-writes.ts +17 -3
- package/src/index.ts +32 -22
- package/src/mcpl/channel-registry.ts +1892 -471
- package/src/mcpl/conversation-router.ts +26 -72
- package/src/mcpl/convert-block.ts +9 -5
- package/src/mcpl/inbound-source.ts +131 -0
- package/src/mcpl/index.ts +4 -2
- package/src/mcpl/log-value.ts +9 -0
- package/src/mcpl/modern-connection.ts +162 -53
- package/src/mcpl/prose-grammar.ts +4 -5
- package/src/mcpl/protocol-family.ts +200 -22
- package/src/mcpl/push-coalescer.ts +306 -62
- package/src/mcpl/push-handler.ts +12 -54
- package/src/mcpl/reference-fetcher.ts +4 -4
- package/src/mcpl/server-connection.ts +180 -79
- package/src/mcpl/tags.ts +36 -0
- package/src/mcpl/tool-result-normalize.ts +2 -1
- package/src/mcpl/transport.ts +162 -33
- package/src/mcpl/types.ts +23 -6
- package/src/module-registry.ts +92 -13
- package/src/modules/discord/index.ts +8 -13
- package/src/modules/history/index.ts +25 -3
- package/src/modules/history/semantic.ts +29 -6
- package/src/modules/index.ts +1 -0
- package/src/modules/unknown-tool.ts +4 -1
- package/src/modules/workspace/branch-intent.ts +3 -1
- package/src/modules/workspace/edit-plan.ts +954 -0
- package/src/modules/workspace/edit-text.ts +618 -0
- package/src/modules/workspace/effects.ts +70 -19
- package/src/modules/workspace/index.ts +2293 -650
- package/src/modules/workspace/observe.ts +465 -56
- package/src/modules/workspace/reconcile.ts +14 -7
- package/src/modules/workspace/text.ts +68 -0
- package/src/modules/workspace/types.ts +211 -6
- package/src/modules/workspace/watcher.ts +3 -2
- package/src/operator-log.ts +47 -19
- package/src/recovery/discord-awareness-outbox.ts +1500 -356
- package/src/recovery/offline-branch.ts +189 -55
- package/src/recovery/recover-cli.ts +25 -4
- package/src/send-tools.ts +11 -0
- package/src/timezone.ts +4 -2
- package/src/tool-image-ledger.ts +62 -8
- package/src/tool-result-guard.ts +18 -15
- package/src/tool-result-history.ts +3 -2
- package/src/tune-out/coordinator.ts +166 -45
- package/src/types/agent.ts +32 -6
- package/src/types/events.ts +66 -9
- package/src/types/framework.ts +69 -62
- package/src/types/module.ts +40 -8
- package/src/types/trace.ts +121 -15
- package/src/usage/types.ts +16 -0
- package/src/usage/usage-tracker.ts +86 -43
- package/dist/src/code-execution/py-runner.d.ts +0 -191
- package/dist/src/code-execution/py-runner.d.ts.map +0 -1
- package/dist/src/code-execution/py-runner.js +0 -655
- package/dist/src/code-execution/py-runner.js.map +0 -1
- package/dist/src/code-execution/runtime-py.d.ts +0 -50
- package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
- package/dist/src/code-execution/runtime-py.js +0 -677
- package/dist/src/code-execution/runtime-py.js.map +0 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
- package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
- package/dist/src/mcpl/prose-stream-router.js +0 -291
- package/dist/src/mcpl/prose-stream-router.js.map +0 -1
- package/src/code-execution/py-runner.ts +0 -791
- package/src/code-execution/runtime-py.ts +0 -677
- package/src/mcpl/prose-stream-router.ts +0 -268
package/src/mcpl/tags.ts
CHANGED
|
@@ -18,6 +18,42 @@ const CORE_IMPLIES: Readonly<Record<string, readonly string[]>> = {
|
|
|
18
18
|
'chat:dm': ['chat:addressed', 'chat:private'],
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* RFC-001 core tags that mark a message as being ABOUT the conversation
|
|
23
|
+
* rather than part of it: reactions (added or removed), edits and deletions
|
|
24
|
+
* of an existing message. These are markers, not turns — they must not clear
|
|
25
|
+
* explicit-send suppression, count as a new conversational round, or
|
|
26
|
+
* retarget the default publish channel. An edit can also be the first
|
|
27
|
+
* version of a message the agent sees, and then counts as that message:
|
|
28
|
+
* isMarkerTagged reads the host's mark for the case the coalescer knows
|
|
29
|
+
* (`firstView`), for isConversationalInjection and for the channel
|
|
30
|
+
* registry's publish channel and reply anchor. Tags are
|
|
31
|
+
* still never authority (§16.6): this set only ever WITHHOLDS default
|
|
32
|
+
* conversational treatment, it never grants anything.
|
|
33
|
+
*/
|
|
34
|
+
export const NON_CONVERSATIONAL_TAGS: ReadonlySet<string> = new Set([
|
|
35
|
+
'chat:reaction',
|
|
36
|
+
'chat:reaction-remove',
|
|
37
|
+
'chat:edited',
|
|
38
|
+
'chat:deleted',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/** True when a (possibly untyped) tag list carries a non-conversational marker tag. */
|
|
42
|
+
export function hasNonConversationalTag(tags: unknown): boolean {
|
|
43
|
+
return Array.isArray(tags) && tags.some((t) => typeof t === 'string' && NON_CONVERSATIONAL_TAGS.has(t));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True when a message is a marker by its tags. `firstView` is the host's own
|
|
48
|
+
* mark that the coalescer delivered the message as the agent's first view of
|
|
49
|
+
* it (a stored message's metadata `firstView`, or in the channel registry the
|
|
50
|
+
* admission's receipt), never a server's claim: an edit so delivered counts
|
|
51
|
+
* as the message it replaced, while a reaction or deletion stays a notice.
|
|
52
|
+
*/
|
|
53
|
+
export function isMarkerTagged(tags: unknown, firstView: boolean): boolean {
|
|
54
|
+
return hasNonConversationalTag(firstView && Array.isArray(tags) ? tags.filter((t) => t !== 'chat:edited') : tags);
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
/**
|
|
22
58
|
* Expand a raw tag list over the normative core closure, then resolve the
|
|
23
59
|
* §16.2 mutual exclusion: `chat:addressed` and `chat:ambient` cannot both
|
|
@@ -36,7 +36,8 @@ export const MALFORMED_CONTENT_NOTICE =
|
|
|
36
36
|
'[malformed result: its content is not a list of blocks; the tool may already have completed]';
|
|
37
37
|
|
|
38
38
|
/** Saves a payload under a host-chosen name; resolves to its workspace path,
|
|
39
|
-
* or null when there is
|
|
39
|
+
* or null when there is no writable workspace mount without an include to
|
|
40
|
+
* put it in. */
|
|
40
41
|
export type SaveToolPayload = (fileName: string, bytes: Buffer, mimeType: string) => Promise<string | null>;
|
|
41
42
|
|
|
42
43
|
/** Stub fields are bounded independently of anything the server sends. */
|
package/src/mcpl/transport.ts
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* - StdioTransport — spawns `config.command` and speaks over stdin/stdout;
|
|
13
13
|
* child stderr is surfaced as 'stderr' lines.
|
|
14
14
|
* - WebSocketTransport — dials `config.url` (ws:// or wss://) with the `ws`
|
|
15
|
-
* library,
|
|
15
|
+
* library, sending the credential as `?token=` (or
|
|
16
|
+
* as an Authorization header, `tokenTransport:
|
|
17
|
+
* 'header'`) when `config.token` is
|
|
16
18
|
* set; each WS text frame is one JSON-RPC line.
|
|
17
19
|
*/
|
|
18
20
|
|
|
@@ -23,7 +25,7 @@ import { StringDecoder } from 'node:string_decoder';
|
|
|
23
25
|
import WebSocket from 'ws';
|
|
24
26
|
|
|
25
27
|
import type { McplServerConfig } from './types.js';
|
|
26
|
-
import { describeCredentialError, noteDialSent, redactUrl, resolveServerBinding, urlAsDialed } from './protocol-family.js';
|
|
28
|
+
import { WITHHELD_TEXT, describeCredentialError, headerCarries, maskedError, noteDialSent, redactServerText, redactUrl, resolveServerBinding, serverTextRedactor, tokenTransportProblem, urlAsDialed, type Redaction } from './protocol-family.js';
|
|
27
29
|
import { assertBooleanSetting } from '../config-checks.js';
|
|
28
30
|
import { logDetail } from './log-value.js';
|
|
29
31
|
|
|
@@ -94,6 +96,21 @@ export abstract class McplTransport extends EventEmitter {
|
|
|
94
96
|
return '';
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* {@link stderrTail} as an error a host gets carries it: the same
|
|
101
|
+
* characters, redacted with `redact`. The stream's last
|
|
102
|
+
* {@link STDERR_WINDOW_CHARS} are redacted together and the tail is taken
|
|
103
|
+
* from where it begins in them, so a credential that begins before the
|
|
104
|
+
* tail and reaches into it is matched whole and replaced whole
|
|
105
|
+
* ({@link Redaction}'s `from`). That holds for a credential of up to
|
|
106
|
+
* 14,336 characters, the window less the tail: a longer one that began
|
|
107
|
+
* before the window can't be matched, and the tail keeps what it holds of
|
|
108
|
+
* it. '' for a transport that has no diagnostic stream.
|
|
109
|
+
*/
|
|
110
|
+
stderrTailRedacted(_redact: Redaction): string {
|
|
111
|
+
return '';
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
/**
|
|
98
115
|
* The error a stdio child's stdin or stdout failed with, once one has. The
|
|
99
116
|
* transport is ending the child then, and stays open until it exits, but
|
|
@@ -230,9 +247,19 @@ export function buildChildEnv(
|
|
|
230
247
|
return merged;
|
|
231
248
|
}
|
|
232
249
|
|
|
233
|
-
/** The most of a stdio child's stderr
|
|
250
|
+
/** The most of a stdio child's stderr that {@link McplTransport.stderrTail} gives. */
|
|
234
251
|
export const STDERR_TAIL_CHARS = 2_048;
|
|
235
252
|
|
|
253
|
+
/** The most of a stdio child's stderr a transport keeps, for
|
|
254
|
+
* {@link McplTransport.stderrTail} and
|
|
255
|
+
* {@link McplTransport.stderrTailRedacted}: eight tails, so a credential of
|
|
256
|
+
* up to 14,336 characters that reaches into the tail, a private key's PEM
|
|
257
|
+
* of 8,192 bits (about 6,500 characters) included, is whole in it. */
|
|
258
|
+
export const STDERR_WINDOW_CHARS = 8 * STDERR_TAIL_CHARS;
|
|
259
|
+
|
|
260
|
+
/** A redaction that replaces nothing: the raw tail. */
|
|
261
|
+
const unredacted: Redaction = (text, from = 0) => String(text).slice(from);
|
|
262
|
+
|
|
236
263
|
/**
|
|
237
264
|
* How long a refusal at a stdio child's exit waits for the child's stderr to
|
|
238
265
|
* end, so that its `stderrTail` holds what the child wrote. Node can emit a
|
|
@@ -251,13 +278,28 @@ export class StdioTransport extends McplTransport {
|
|
|
251
278
|
readonly kind = 'stdio' as const;
|
|
252
279
|
/** The first error on the child's stdin or stdout, named in the close reason. */
|
|
253
280
|
private pipeError: Error | undefined;
|
|
254
|
-
|
|
255
|
-
private
|
|
281
|
+
/** The stream's last STDERR_WINDOW_CHARS, raw. */
|
|
282
|
+
private stderrWindow = '';
|
|
256
283
|
/** True once the child's stderr has ended or closed. */
|
|
257
284
|
private stderrDone = false;
|
|
258
285
|
|
|
259
286
|
override get stderrTail(): string {
|
|
260
|
-
return
|
|
287
|
+
return this.stderrTailRedacted(unredacted);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
override stderrTailRedacted(redact: Redaction): string {
|
|
291
|
+
// A child that wrote nothing has no tail, whatever its redaction.
|
|
292
|
+
if (this.stderrWindow === '') return '';
|
|
293
|
+
// The tail begins at its raw place in the window, so a redaction that
|
|
294
|
+
// shortens the window can't move its edge back toward the window's own
|
|
295
|
+
// cut. Something before the tail was dropped whenever the window is
|
|
296
|
+
// longer than the tail, which it always is once the window was cut.
|
|
297
|
+
const from = Math.max(0, this.stderrWindow.length - STDERR_TAIL_CHARS);
|
|
298
|
+
const cut = from > 0;
|
|
299
|
+
let kept = redact(this.stderrWindow, from);
|
|
300
|
+
// A cut between a surrogate pair's halves drops the low half too.
|
|
301
|
+
if (cut && /^[\uDC00-\uDFFF]/.test(kept)) kept = kept.slice(1);
|
|
302
|
+
return (cut ? '…' : '') + kept;
|
|
261
303
|
}
|
|
262
304
|
|
|
263
305
|
override get pipeFailure(): Error | undefined {
|
|
@@ -300,12 +342,8 @@ export class StdioTransport extends McplTransport {
|
|
|
300
342
|
let carry = '';
|
|
301
343
|
const decoder = new StringDecoder('utf8');
|
|
302
344
|
const received = (text: string) => {
|
|
303
|
-
const
|
|
304
|
-
this.
|
|
305
|
-
let kept = end.slice(-STDERR_TAIL_CHARS);
|
|
306
|
-
// A cut between a surrogate pair's halves drops the low half too.
|
|
307
|
-
if (/^[\uDC00-\uDFFF]/.test(kept) && end.length > STDERR_TAIL_CHARS) kept = kept.slice(1);
|
|
308
|
-
this.stderrEnd = kept;
|
|
345
|
+
const window = this.stderrWindow + text;
|
|
346
|
+
this.stderrWindow = window.slice(-STDERR_WINDOW_CHARS);
|
|
309
347
|
const lines = (carry + text).split('\n');
|
|
310
348
|
carry = lines.pop() ?? '';
|
|
311
349
|
for (const l of lines) if (l.length > 0) this.pushStderr(l);
|
|
@@ -374,10 +412,33 @@ export class StdioTransport extends McplTransport {
|
|
|
374
412
|
`MCPL server "${config.id}": stdio transport requires "command" (none provided)`,
|
|
375
413
|
);
|
|
376
414
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
415
|
+
// Node's error for a launch that failed reaches the host, and its words
|
|
416
|
+
// and its other properties (`spawnargs`, `path`) can repeat a credential
|
|
417
|
+
// the configuration holds. So it's handed on as its name and redacted
|
|
418
|
+
// words (maskedError), with its code, which says what failed. What
|
|
419
|
+
// spawn() refuses at once is one of the configuration's own arguments or
|
|
420
|
+
// options, which Node quotes after " Received", cut to 128 characters: a
|
|
421
|
+
// credential across that cut couldn't be matched, so the words stop
|
|
422
|
+
// before the quote, still saying which value Node refused and why.
|
|
423
|
+
const failed = (err: NodeJS.ErrnoException, refusedAtOnce: boolean): Error => {
|
|
424
|
+
const redact = serverTextRedactor(config).now();
|
|
425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
426
|
+
const words = refusedAtOnce ? message.split(' Received')[0] : message;
|
|
427
|
+
const shown = Object.assign(new Error(words), { name: err instanceof Error ? err.name : 'Error' });
|
|
428
|
+
const cause = Object.assign(maskedError(shown, redact), err.code === undefined ? {} : { code: err.code });
|
|
429
|
+
return new Error(`MCPL server "${config.id}" could not be started: ${redact(words)}`, { cause });
|
|
430
|
+
};
|
|
431
|
+
// The child's environment is read first, outside that: a configuration
|
|
432
|
+
// whose `env` can't be read throws the framework's own error, such as
|
|
433
|
+
// its awareness ledger's, which the framework acts on: its startup ends
|
|
434
|
+
// on it, and a reconnect reports it.
|
|
435
|
+
const env = buildChildEnv(config);
|
|
436
|
+
let child: ChildProcess;
|
|
437
|
+
try {
|
|
438
|
+
child = spawn(config.command, config.args ?? [], { stdio: ['pipe', 'pipe', 'pipe'], env });
|
|
439
|
+
} catch (error) {
|
|
440
|
+
throw failed(error as NodeJS.ErrnoException, true);
|
|
441
|
+
}
|
|
381
442
|
// Node reports a failed spawn (a command that doesn't exist, say) as an
|
|
382
443
|
// 'error' on a tick after spawn() returns, and no 'exit' follows. It is
|
|
383
444
|
// settled here, where it is listened for. A caller that listens only once
|
|
@@ -385,12 +446,10 @@ export class StdioTransport extends McplTransport {
|
|
|
385
446
|
// from a timer callback, as a reconnect is: Node runs that tick before the
|
|
386
447
|
// caller's promise continuation, and throws an 'error' nothing listens for.
|
|
387
448
|
await new Promise<void>((resolve, reject) => {
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
};
|
|
391
|
-
child.once('error', failed);
|
|
449
|
+
const fail = (err: NodeJS.ErrnoException) => reject(failed(err, false));
|
|
450
|
+
child.once('error', fail);
|
|
392
451
|
child.once('spawn', () => {
|
|
393
|
-
child.off('error',
|
|
452
|
+
child.off('error', fail);
|
|
394
453
|
resolve();
|
|
395
454
|
});
|
|
396
455
|
});
|
|
@@ -504,7 +563,8 @@ function raceAbort<T>(work: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
|
504
563
|
export class WebSocketTransport extends McplTransport {
|
|
505
564
|
readonly kind = 'websocket' as const;
|
|
506
565
|
|
|
507
|
-
private constructor(private readonly ws: WebSocket,
|
|
566
|
+
private constructor(private readonly ws: WebSocket, config: McplServerConfig, dialed: string) {
|
|
567
|
+
const serverId = config.id;
|
|
508
568
|
super(serverId);
|
|
509
569
|
|
|
510
570
|
this.ws.on('message', (data: WebSocket.RawData, isBinary: boolean) => {
|
|
@@ -517,7 +577,9 @@ export class WebSocketTransport extends McplTransport {
|
|
|
517
577
|
}
|
|
518
578
|
});
|
|
519
579
|
|
|
520
|
-
|
|
580
|
+
// The client's text can quote the url as dialed (Bun's does): reported
|
|
581
|
+
// masked, as a new error with no cause (clientErrorText).
|
|
582
|
+
this.ws.on('error', (err: Error) => this.reportError(new Error(clientErrorText(err, config, dialed))));
|
|
521
583
|
this.ws.on('close', (code: number, reason: Buffer) => {
|
|
522
584
|
const text = reason?.toString('utf8');
|
|
523
585
|
this.markClosed(text ? { code, reason: text, reasonFromPeer: true } : { code, reason: 'websocket closed' });
|
|
@@ -525,8 +587,11 @@ export class WebSocketTransport extends McplTransport {
|
|
|
525
587
|
}
|
|
526
588
|
|
|
527
589
|
/**
|
|
528
|
-
* Dial `config.url` and resolve once the socket is OPEN.
|
|
529
|
-
*
|
|
590
|
+
* Dial `config.url` and resolve once the socket is OPEN. Sends the
|
|
591
|
+
* credential (`config.token`, or the access provider's) as a `?token=`
|
|
592
|
+
* query parameter, or with `tokenTransport: 'header'` as an
|
|
593
|
+
* `Authorization: Bearer` header on the upgrade, refusing before the dial
|
|
594
|
+
* one a header can't carry as configured. Rejects on connect error / timeout (and tears
|
|
530
595
|
* the socket down) so a failed dial never leaks an open handle. `signal`
|
|
531
596
|
* ends a dial whose connection is closed meanwhile, at once, rather than
|
|
532
597
|
* at the open timeout.
|
|
@@ -557,14 +622,32 @@ export class WebSocketTransport extends McplTransport {
|
|
|
557
622
|
}
|
|
558
623
|
}
|
|
559
624
|
const url = buildWebSocketUrl(config, token);
|
|
560
|
-
// A provider's token is in this url
|
|
561
|
-
//
|
|
562
|
-
//
|
|
625
|
+
// A provider's token is in this url when the dial sends it in the query,
|
|
626
|
+
// which may then be written again, and in the header alone in header
|
|
627
|
+
// mode: the server's text is recorded with what the dial sent, the token
|
|
628
|
+
// in each form a token is replaced in.
|
|
563
629
|
noteDialSent(config, { url, token: token ?? undefined });
|
|
564
630
|
// Error strings travel: traces, logs, tool results the model reads. They
|
|
565
631
|
// get the endpoint, never the credential.
|
|
566
632
|
const redacted = redactUrl(config.url);
|
|
567
|
-
|
|
633
|
+
// In header mode the url carries no credential (buildWebSocketUrl): the
|
|
634
|
+
// upgrade request does, if a header can carry it as configured
|
|
635
|
+
// (headerCarries). A static token is refused earlier, by
|
|
636
|
+
// serverConfigProblems; this refuses an access provider's, and a static
|
|
637
|
+
// one dialed past that check. A client that throws as the socket is
|
|
638
|
+
// constructed can quote the url, so its text is masked (clientErrorText).
|
|
639
|
+
const headerMode = config.tokenTransport === 'header' && !!token;
|
|
640
|
+
if (headerMode && token && !headerCarries(token)) {
|
|
641
|
+
throw new Error(`MCPL server "${config.id}" WebSocket connect failed: its credential isn't printable ASCII with no space at either end, which is all a header carries as configured (${redacted})`);
|
|
642
|
+
}
|
|
643
|
+
let ws: WebSocket;
|
|
644
|
+
try {
|
|
645
|
+
ws = headerMode
|
|
646
|
+
? new WebSocket(url, { headers: { Authorization: `Bearer ${token}` } })
|
|
647
|
+
: new WebSocket(url);
|
|
648
|
+
} catch (err) {
|
|
649
|
+
throw new Error(`MCPL server "${config.id}" WebSocket connect failed: ${clientErrorText(err, config, url)} (${redacted})`);
|
|
650
|
+
}
|
|
568
651
|
|
|
569
652
|
// A failed dial can emit 'error' MORE THAN ONCE on the raw socket (seen
|
|
570
653
|
// live 2026-08-03: an HTTP 502 upgrade-reject emits the "Expected 101"
|
|
@@ -602,11 +685,11 @@ export class WebSocketTransport extends McplTransport {
|
|
|
602
685
|
clearTimeout(timer);
|
|
603
686
|
signal?.removeEventListener('abort', onAbort);
|
|
604
687
|
ws.removeAllListeners('error'); // the instance re-attaches its own handlers
|
|
605
|
-
resolve(new WebSocketTransport(ws, config
|
|
688
|
+
resolve(new WebSocketTransport(ws, config, url));
|
|
606
689
|
});
|
|
607
690
|
|
|
608
691
|
ws.once('error', (err: Error) => {
|
|
609
|
-
abandon(new Error(`MCPL server "${config.id}" WebSocket connect failed: ${err
|
|
692
|
+
abandon(new Error(`MCPL server "${config.id}" WebSocket connect failed: ${clientErrorText(err, config, url)} (${redacted})`));
|
|
610
693
|
});
|
|
611
694
|
});
|
|
612
695
|
}
|
|
@@ -635,10 +718,53 @@ export class WebSocketTransport extends McplTransport {
|
|
|
635
718
|
}
|
|
636
719
|
}
|
|
637
720
|
|
|
721
|
+
/** A ws:// or wss:// url in a WebSocket client's text, to the next space or
|
|
722
|
+
* quote: brackets (an IPv6 host) and parentheses can be part of one. */
|
|
723
|
+
const WS_URL_IN_TEXT = /\bwss?:\/\/[^\s"'<>`]+/gi;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* A WebSocket client's error text (an Error under Node's ws, an ErrorEvent
|
|
727
|
+
* under Bun's), as the transport's own errors carry it: a failed dial's, and
|
|
728
|
+
* one after the socket opened. A client can quote the url as dialed: under
|
|
729
|
+
* Bun 1.3.13 and 1.4.3 a refused or unreachable dial's message holds it
|
|
730
|
+
* whole, its user, password, path and query, and the token, a static one or
|
|
731
|
+
* an access provider's. In order, so a credential is replaced while it's
|
|
732
|
+
* whole: the url as dialed and as configured, wherever they occur, are
|
|
733
|
+
* shown as {@link redactUrl} shows them; then the strings the configuration
|
|
734
|
+
* and its dials know to be credentials (the dial has noted what it sent by
|
|
735
|
+
* now) are replaced, in their encoded and decoded forms
|
|
736
|
+
* ({@link redactServerText}); then any other ws(s) url left is shown as
|
|
737
|
+
* {@link redactUrl} shows it. The caller makes a new error of it with no
|
|
738
|
+
* `cause`, so the client's own error, message and stack, isn't reachable.
|
|
739
|
+
* A configuration that can't be read withholds the text
|
|
740
|
+
* ({@link WITHHELD_TEXT}) rather than throw: this runs in a socket's
|
|
741
|
+
* 'error' listener, where a throw has nowhere to go.
|
|
742
|
+
*/
|
|
743
|
+
export function clientErrorText(err: unknown, config: McplServerConfig, dialed: string): string {
|
|
744
|
+
// Bun's ws emits an ErrorEvent, not an Error, its text in `message` too.
|
|
745
|
+
try {
|
|
746
|
+
const said = (err as { message?: unknown } | null)?.message;
|
|
747
|
+
let text = typeof said === 'string' ? said : String(err);
|
|
748
|
+
const configured = config.url;
|
|
749
|
+
const shown = redactUrl(configured);
|
|
750
|
+
for (const url of [dialed, configured]) {
|
|
751
|
+
if (url) text = text.split(url).join(shown);
|
|
752
|
+
}
|
|
753
|
+
text = redactServerText(text, config);
|
|
754
|
+
return text.replace(WS_URL_IN_TEXT, (found) => redactUrl(found));
|
|
755
|
+
} catch {
|
|
756
|
+
return WITHHELD_TEXT;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
638
760
|
/**
|
|
639
761
|
* Build the dial URL: validate it's a ws(s):// URL the ws library will dial,
|
|
640
762
|
* which refuses one with a fragment, and append the auth token as a `token`
|
|
641
|
-
* query param when provided (preserving any existing query).
|
|
763
|
+
* query param when provided (preserving any existing query). With
|
|
764
|
+
* `tokenTransport: 'header'` no token is appended: the dial sends it as a
|
|
765
|
+
* header, and a url that carries a `token` parameter of its own is refused,
|
|
766
|
+
* as is a `tokenTransport` that is neither mode, so a dial that skipped
|
|
767
|
+
* serverConfigProblems still can't fall back to the query.
|
|
642
768
|
*/
|
|
643
769
|
export function buildWebSocketUrl(config: McplServerConfig, tokenOverride?: string | null): string {
|
|
644
770
|
if (!config.url) {
|
|
@@ -659,7 +785,10 @@ export function buildWebSocketUrl(config: McplServerConfig, tokenOverride?: stri
|
|
|
659
785
|
if (parsed.hash) {
|
|
660
786
|
throw new Error(`MCPL server "${config.id}": websocket url must not have a fragment, got "${parsed.hash}"`);
|
|
661
787
|
}
|
|
662
|
-
const
|
|
788
|
+
const problem = tokenTransportProblem(config, parsed);
|
|
789
|
+
if (problem) throw new Error(problem);
|
|
790
|
+
const token = config.tokenTransport === 'header' ? null
|
|
791
|
+
: tokenOverride !== undefined ? tokenOverride : config.token;
|
|
663
792
|
return urlAsDialed(parsed, token).toString();
|
|
664
793
|
}
|
|
665
794
|
|
package/src/mcpl/types.ts
CHANGED
|
@@ -285,12 +285,28 @@ export interface McplServerConfig {
|
|
|
285
285
|
/**
|
|
286
286
|
* Bearer token for a network server. Where it travels depends on the
|
|
287
287
|
* transport: on a ws(s):// url it is appended as a `token` query parameter
|
|
288
|
-
* (`?token=…`) on connect
|
|
289
|
-
* `Authorization: Bearer` header and
|
|
290
|
-
* server ignores it, and a modern
|
|
288
|
+
* (`?token=…`) on connect, unless `tokenTransport` is `'header'`; to an
|
|
289
|
+
* http(s):// server it is sent as an `Authorization: Bearer` header and
|
|
290
|
+
* never put in the url. A legacy stdio server ignores it, and a modern
|
|
291
|
+
* stdio server refuses it.
|
|
291
292
|
*/
|
|
292
293
|
token?: string;
|
|
293
294
|
|
|
295
|
+
/**
|
|
296
|
+
* How a ws(s):// server's credential, `token` or what `accessProvider`
|
|
297
|
+
* returns for the dial, is sent on the WebSocket upgrade. `'query'`, the
|
|
298
|
+
* default: as a `token` query parameter (`?token=…`). `'header'`: as an
|
|
299
|
+
* `Authorization: Bearer …` header, with no query parameter, so the
|
|
300
|
+
* credential stays out of the url and of the access logs that record it.
|
|
301
|
+
* A header carries a credential as configured only if it is printable
|
|
302
|
+
* ASCII (U+0020 to U+007E) with no space at either end, so header mode
|
|
303
|
+
* refuses any other: a static `token` when the server is added, an access
|
|
304
|
+
* provider's at the dial. Set only on a WebSocket server; an http(s)://
|
|
305
|
+
* server always sends the header, and a stdio server sends no credential.
|
|
306
|
+
* `null` counts as not set.
|
|
307
|
+
*/
|
|
308
|
+
tokenTransport?: 'query' | 'header';
|
|
309
|
+
|
|
294
310
|
/**
|
|
295
311
|
* Lazily resolves the connection credential at EVERY dial — first connect
|
|
296
312
|
* and each background reconnect — overriding `token` when it returns a
|
|
@@ -414,9 +430,10 @@ export interface McplServerConfig {
|
|
|
414
430
|
* are also the only bound for one that closes its stdin, since Bun emits no
|
|
415
431
|
* 'error' event for the writes to it. requestTimeoutMs is the timeout of
|
|
416
432
|
* every request that names none; the handshake's initialize has its own.
|
|
417
|
-
* Set 0 to disable, except for channels/publish
|
|
418
|
-
*
|
|
419
|
-
*
|
|
433
|
+
* Set 0 to disable, except for channels/publish, channels/open and
|
|
434
|
+
* channels/close, which keep the 60 s default: a publish's outcome is
|
|
435
|
+
* reported to the resident and recorded for its draft, so it must end, and
|
|
436
|
+
* a delivery waits for a channel's queued opens and closes.
|
|
420
437
|
* Default: 60000 (60 seconds). A value above 2^31−1 is refused: Node runs a
|
|
421
438
|
* longer timer after about 1 ms, so every request would time out at once.
|
|
422
439
|
*
|
package/src/module-registry.ts
CHANGED
|
@@ -310,7 +310,8 @@ export class ModuleRegistry {
|
|
|
310
310
|
* Handle a tool call by routing to the appropriate module: the module's own
|
|
311
311
|
* result, unchecked, as it returned it, for a caller that uses the result
|
|
312
312
|
* in process (`ModuleContext.callTool`, `executeToolCall`, or a host
|
|
313
|
-
* calling the registry)
|
|
313
|
+
* calling the registry), less a refusal it claims (withoutRefusal). A
|
|
314
|
+
* module's throw is answered as an error result.
|
|
314
315
|
* A call whose input isn't an object, or lacks a key the tool's schema
|
|
315
316
|
* requires, is answered before the module runs, with an error result that
|
|
316
317
|
* opens "Nothing was done:" and names what to send (toolArgsProblems).
|
|
@@ -324,7 +325,7 @@ export class ModuleRegistry {
|
|
|
324
325
|
if (sepIndex === -1) {
|
|
325
326
|
return {
|
|
326
327
|
success: false,
|
|
327
|
-
error: `Invalid tool name format: ${call.name}`,
|
|
328
|
+
error: `Invalid tool name format: ${call.name}, so nothing was done`,
|
|
328
329
|
isError: true,
|
|
329
330
|
};
|
|
330
331
|
}
|
|
@@ -336,19 +337,14 @@ export class ModuleRegistry {
|
|
|
336
337
|
if (!module) {
|
|
337
338
|
return {
|
|
338
339
|
success: false,
|
|
339
|
-
error: `Module not found: ${moduleName}`,
|
|
340
|
+
error: `Module not found: ${moduleName}, so nothing was done`,
|
|
340
341
|
isError: true,
|
|
341
342
|
};
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
name: toolName,
|
|
348
|
-
input: call.input,
|
|
349
|
-
callerAgentName: call.callerAgentName,
|
|
350
|
-
...(call.callerTurnId !== undefined ? { callerTurnId: call.callerTurnId } : {}),
|
|
351
|
-
};
|
|
345
|
+
// The call as it came, with the un-prefixed name: the caller's identity
|
|
346
|
+
// and everything else the dispatch set on it reach the module.
|
|
347
|
+
const moduleCall: ToolCall = { ...call, name: toolName };
|
|
352
348
|
|
|
353
349
|
try {
|
|
354
350
|
// The call against the tool's declared schema, before the module runs:
|
|
@@ -372,7 +368,9 @@ export class ModuleRegistry {
|
|
|
372
368
|
isError: true,
|
|
373
369
|
};
|
|
374
370
|
}
|
|
375
|
-
|
|
371
|
+
// A refusal is the dispatcher's to say (RFC-007), so a module's result
|
|
372
|
+
// can't claim one on this route either.
|
|
373
|
+
return withoutRefusal(call, await module.handleToolCall(moduleCall));
|
|
376
374
|
} catch (error) {
|
|
377
375
|
return thrownResult(error);
|
|
378
376
|
}
|
|
@@ -900,6 +898,83 @@ class ProcessStateImpl implements ProcessState {
|
|
|
900
898
|
}
|
|
901
899
|
}
|
|
902
900
|
|
|
901
|
+
/** The fields ToolResult declares, read by name, so a result class's getters count: endTurn first and the error next, so a failure keeps them. */
|
|
902
|
+
const TOOL_RESULT_FIELDS = ['endTurn', 'error', 'success', 'isError', 'data', 'structured', 'keepWhenWithheld'] as const;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* A module's result for an in-process caller: the module's own object, unless
|
|
906
|
+
* it claims a refusal, which is the dispatcher's to say (RFC-007). Then it's a
|
|
907
|
+
* copy, less the claim: a plain object of every field ToolResult declares,
|
|
908
|
+
* read by name, so a result class's getters count, each read once, and every
|
|
909
|
+
* other own enumerable field, string or symbol, as a spread takes them, so
|
|
910
|
+
* non-enumerable own fields and inherited undeclared ones are left out. A
|
|
911
|
+
* result that can't be read, its claim included, is a failure saying so, as
|
|
912
|
+
* on the stored route (checkedToolResult),
|
|
913
|
+
* keeping the turn's end and the module's error if those could be read: the
|
|
914
|
+
* module's call completed, and the claim never reaches the caller.
|
|
915
|
+
*/
|
|
916
|
+
function withoutRefusal(call: ToolCall, result: ToolResult): ToolResult {
|
|
917
|
+
// Not an object: nothing to claim, and the caller gets it as it came.
|
|
918
|
+
if (!result || (typeof result !== 'object' && typeof result !== 'function')) return result;
|
|
919
|
+
const source = result as unknown as Record<PropertyKey, unknown>;
|
|
920
|
+
const copy: Record<PropertyKey, unknown> = {};
|
|
921
|
+
// Each field is read once: one whose getter threw isn't asked again.
|
|
922
|
+
const read = new Set<PropertyKey>();
|
|
923
|
+
// Defined, not assigned, so a field named __proto__ is a field.
|
|
924
|
+
const keep = (key: PropertyKey): void => {
|
|
925
|
+
read.add(key);
|
|
926
|
+
Object.defineProperty(copy, key, { value: source[key], enumerable: true, writable: true, configurable: true });
|
|
927
|
+
};
|
|
928
|
+
const once = (key: string): unknown => {
|
|
929
|
+
if (key in copy) return copy[key];
|
|
930
|
+
if (read.has(key)) return undefined;
|
|
931
|
+
return source[key];
|
|
932
|
+
};
|
|
933
|
+
// A failure keeps what could be read of the turn's end and the module's
|
|
934
|
+
// error, each read on its own, whatever else couldn't be.
|
|
935
|
+
const unreadable = (error: unknown): ToolResult => {
|
|
936
|
+
let endTurn: unknown;
|
|
937
|
+
let own: string | undefined;
|
|
938
|
+
try {
|
|
939
|
+
endTurn = once('endTurn');
|
|
940
|
+
} catch {
|
|
941
|
+
// Not readable: the failure doesn't end the turn.
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
own = errorText(once('error'));
|
|
945
|
+
} catch {
|
|
946
|
+
// An error whose own text can't be read: the failure says only why.
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
success: false,
|
|
950
|
+
isError: true,
|
|
951
|
+
error: `Tool '${call.name}' returned a result that couldn't be read (${thrownText(error).split('\n')[0] || 'no reason given'}). ` +
|
|
952
|
+
`The call itself completed${own ? `, with this error: ${own}` : '.'}`,
|
|
953
|
+
...(endTurn ? { endTurn: true } : {}),
|
|
954
|
+
};
|
|
955
|
+
};
|
|
956
|
+
let claim: unknown;
|
|
957
|
+
try {
|
|
958
|
+
claim = source.refused;
|
|
959
|
+
} catch (error) {
|
|
960
|
+
return unreadable(error);
|
|
961
|
+
}
|
|
962
|
+
if (!claim) return result;
|
|
963
|
+
try {
|
|
964
|
+
const copied = new Set<PropertyKey>(['refused']);
|
|
965
|
+
for (const key of TOOL_RESULT_FIELDS) {
|
|
966
|
+
copied.add(key);
|
|
967
|
+
if (key in source) keep(key);
|
|
968
|
+
}
|
|
969
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
970
|
+
if (!copied.has(key) && Object.prototype.propertyIsEnumerable.call(source, key)) keep(key);
|
|
971
|
+
}
|
|
972
|
+
return copy as unknown as ToolResult;
|
|
973
|
+
} catch (error) {
|
|
974
|
+
return unreadable(error);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
903
978
|
/** A result's flags, which consumers read for their truth: booleans here. */
|
|
904
979
|
const RESULT_FLAGS = ['success', 'isError', 'endTurn'] as const;
|
|
905
980
|
|
|
@@ -921,7 +996,8 @@ const RESULT_FLAGS = ['success', 'isError', 'endTurn'] as const;
|
|
|
921
996
|
* runs again. It's taken where the framework stores or sends a result
|
|
922
997
|
* ({@link ModuleRegistry.storableToolCall}). A caller that uses the result
|
|
923
998
|
* in process (`ModuleContext.callTool`, `executeToolCall`) gets the module's
|
|
924
|
-
* own result, as the module returned it
|
|
999
|
+
* own result, as the module returned it, less a refusal it claims
|
|
1000
|
+
* ({@link ModuleRegistry.handleToolCall}).
|
|
925
1001
|
* The flags are booleans, read for their truth as consumers read them.
|
|
926
1002
|
* `error` is its text form, which is all a consumer reads of it, so an error
|
|
927
1003
|
* value JSON can't hold costs nothing. An error that says nothing (null,
|
|
@@ -977,6 +1053,9 @@ export function checkedToolResult(call: ToolCall, result: unknown): { result: To
|
|
|
977
1053
|
}
|
|
978
1054
|
// The error is kept as its text, which consumers read; its value isn't.
|
|
979
1055
|
delete fields.error;
|
|
1056
|
+
// A refusal is the dispatcher's to say, before the call opens (RFC-007):
|
|
1057
|
+
// a module answers after that, so its result can't claim one.
|
|
1058
|
+
delete fields.refused;
|
|
980
1059
|
// The flags are kept as their truth, which is all consumers read of them,
|
|
981
1060
|
// before anything is serialized, so a flag's own value (a BigInt, a
|
|
982
1061
|
// function, an object with a toJSON) neither breaks nor runs anything.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
11
|
-
import type
|
|
11
|
+
import { continuesBody, type StoredMessage } from '@animalabs/context-manager';
|
|
12
12
|
import type {
|
|
13
13
|
Module,
|
|
14
14
|
ModuleContext,
|
|
@@ -322,19 +322,14 @@ function withoutGoneAttachments(content: ContentBlock[], attachments: readonly D
|
|
|
322
322
|
return textFirst(first.text, kept);
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
/** The ids to remove for a message's rows: each row once,
|
|
326
|
-
* in shards once,
|
|
325
|
+
/** The ids to remove for a message's rows, in store order: each row once,
|
|
326
|
+
* and a body stored in shards once, by its first shard, since removing one
|
|
327
|
+
* shard takes out its whole body. Which rows make up a body is
|
|
328
|
+
* context-manager's rule (continuesBody), read pairwise: a group id names
|
|
329
|
+
* the text, so a message stored twice in shards is two bodies under one id,
|
|
330
|
+
* and each is removed. */
|
|
327
331
|
function oncePerBody(rows: readonly StoredMessage[]): string[] {
|
|
328
|
-
|
|
329
|
-
const ids: string[] = [];
|
|
330
|
-
for (const row of rows) {
|
|
331
|
-
if (row.bodyGroupId) {
|
|
332
|
-
if (bodies.has(row.bodyGroupId)) continue;
|
|
333
|
-
bodies.add(row.bodyGroupId);
|
|
334
|
-
}
|
|
335
|
-
ids.push(row.id);
|
|
336
|
-
}
|
|
337
|
-
return ids;
|
|
332
|
+
return rows.filter((row, i) => !continuesBody(rows[i - 1], row)).map((row) => row.id);
|
|
338
333
|
}
|
|
339
334
|
|
|
340
335
|
/** A live create on its way to the store (DiscordModule.creating). */
|