@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/push-handler.ts
CHANGED
|
@@ -22,7 +22,6 @@ import type { InboundSource } from './inbound-source.js';
|
|
|
22
22
|
import { isSilentHeartbeatMarker, isVisiblyEmptyConversion } from './visible-content.js';
|
|
23
23
|
import { logDetail, logValue } from './log-value.js';
|
|
24
24
|
import { RefusalLines } from './refusal-lines.js';
|
|
25
|
-
import { channelNotPermitted } from './errors.js';
|
|
26
25
|
|
|
27
26
|
// ============================================================================
|
|
28
27
|
// McplPushEvent (the ProcessEvent shape pushed to the queue)
|
|
@@ -49,8 +48,13 @@ export interface McplPushEvent {
|
|
|
49
48
|
targetAgents?: string[];
|
|
50
49
|
/** RFC-006: set when the coalescer delivers; rides the wake it queues. */
|
|
51
50
|
coalescingSubject?: string;
|
|
52
|
-
/** RFC-006 assembly: materialized for this agent's turn
|
|
51
|
+
/** RFC-006 assembly: materialized for this agent's turn, stored directly
|
|
52
|
+
* while its assembly runs, after what is queued for it (the framework's
|
|
53
|
+
* addMessage, bypassDeferralFor). */
|
|
53
54
|
assemblingFor?: string;
|
|
55
|
+
/** The coalescer's mark: the agent's first view of the subject (see
|
|
56
|
+
* CoalescedOccurrence.firstView). */
|
|
57
|
+
firstView?: boolean;
|
|
54
58
|
/** Host acceptance time (epoch ms), stamped where the push is admitted. */
|
|
55
59
|
acceptedAt?: number;
|
|
56
60
|
/** Framework-owned source envelope, frozen at a coalesced occurrence's
|
|
@@ -110,9 +114,8 @@ export class PushHandler {
|
|
|
110
114
|
private emitTraceFn: (event: { type: string; [key: string]: unknown }) => void;
|
|
111
115
|
private shouldTriggerInference?: (content: string, metadata: Record<string, unknown>) => boolean;
|
|
112
116
|
private dedup = new DedupSet(1000);
|
|
113
|
-
/** Holds back repeats of the line for a push refused
|
|
114
|
-
*
|
|
115
|
-
* used (-32017), and one naming a channel another server registers. */
|
|
117
|
+
/** Holds back repeats of the line for a channel-scoped push refused because
|
|
118
|
+
* its channel isn't declared (-32023) or may not be used (-32017). */
|
|
116
119
|
private channelRefusalLines = new RefusalLines();
|
|
117
120
|
|
|
118
121
|
constructor(
|
|
@@ -125,9 +128,6 @@ export class PushHandler {
|
|
|
125
128
|
* or acknowledged: return its source envelope, frozen now
|
|
126
129
|
* (mcpl/inbound-source.ts). Coalesced work is stamped by its own path. */
|
|
127
130
|
private readonly acceptInbound?: (event: McplPushEvent) => InboundSource | undefined,
|
|
128
|
-
/** The channel a push names, and the server that registers it, when
|
|
129
|
-
* another server registers that channel id and this server doesn't. */
|
|
130
|
-
private readonly channelOfAnotherServer?: (serverId: string, params: PushEventParams) => { channelId: string; holder: string } | undefined,
|
|
131
131
|
) {
|
|
132
132
|
this.featureSetManager = featureSetManager;
|
|
133
133
|
this.pushEventFn = pushEventFn;
|
|
@@ -140,7 +140,6 @@ export class PushHandler {
|
|
|
140
140
|
*
|
|
141
141
|
* 1. Validate feature set
|
|
142
142
|
* 2. Convert content blocks; reject visibly-empty content
|
|
143
|
-
* Refuse a push naming a channel another server registers
|
|
144
143
|
* 3. Deduplicate by eventId
|
|
145
144
|
* 4. Optionally check shouldTriggerInference callback
|
|
146
145
|
* 5. Push event to queue
|
|
@@ -214,18 +213,6 @@ export class PushHandler {
|
|
|
214
213
|
return;
|
|
215
214
|
}
|
|
216
215
|
|
|
217
|
-
// A push is its own server's event, and the agent's reply to it goes to
|
|
218
|
-
// the channel the push names. A channel's id is the agent's locus, so a
|
|
219
|
-
// push naming a channel another server registers, which this server
|
|
220
|
-
// doesn't, would have the reply sent through that other server. It's
|
|
221
|
-
// refused, before dedup, so a retry once this server registers the
|
|
222
|
-
// channel isn't refused as a duplicate.
|
|
223
|
-
const elsewhere = this.channelOfAnotherServer?.(serverId, params);
|
|
224
|
-
if (elsewhere) {
|
|
225
|
-
this.refuseChannelOfAnotherServer(serverId, params, elsewhere, responder);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
216
|
// 3. Deduplicate by eventId. A coalesced occurrence is deduplicated by the
|
|
230
217
|
// coalescer's receipts instead (RFC-006 §3.1: a retry within the window
|
|
231
218
|
// gets its original result, which this set could not return).
|
|
@@ -242,12 +229,15 @@ export class PushHandler {
|
|
|
242
229
|
.filter((b): b is ContentBlock & { type: 'text' } => b.type === 'text')
|
|
243
230
|
.map((b) => b.text)
|
|
244
231
|
.join('\n');
|
|
232
|
+
// The host's own fields come after the server's origin, as a channel
|
|
233
|
+
// message's do, so an origin can't give the gate another server's id,
|
|
234
|
+
// feature set, event id or event type.
|
|
245
235
|
const metadata: Record<string, unknown> = {
|
|
236
|
+
...(params.origin ?? {}),
|
|
246
237
|
serverId,
|
|
247
238
|
featureSet: params.featureSet,
|
|
248
239
|
eventId: params.eventId,
|
|
249
240
|
eventType: 'mcpl:push-event',
|
|
250
|
-
...(params.origin ?? {}),
|
|
251
241
|
...(params.tags ? { tags: params.tags } : {}),
|
|
252
242
|
};
|
|
253
243
|
triggerInference = this.shouldTriggerInference(textContent, metadata);
|
|
@@ -353,38 +343,6 @@ export class PushHandler {
|
|
|
353
343
|
});
|
|
354
344
|
}
|
|
355
345
|
|
|
356
|
-
/** A push naming a channel another server registers: refused with -32017.
|
|
357
|
-
* The operator's line names the server that registers it; the answer to
|
|
358
|
-
* the pushing server doesn't. The line prints at most once a minute per
|
|
359
|
-
* channel, as the other channel refusals' do, and every refusal is traced. */
|
|
360
|
-
private refuseChannelOfAnotherServer(
|
|
361
|
-
serverId: string,
|
|
362
|
-
params: PushEventParams,
|
|
363
|
-
elsewhere: { channelId: string; holder: string },
|
|
364
|
-
responder: Responder | undefined,
|
|
365
|
-
): void {
|
|
366
|
-
const held = this.channelRefusalLines.admit(serverId, elsewhere.channelId);
|
|
367
|
-
if (held !== null) {
|
|
368
|
-
console.error(
|
|
369
|
-
`[push-event-rejected] server=${serverId} featureSet=${logValue(params.featureSet ?? '')} eventId=${logValue(params.eventId ?? '')} ` +
|
|
370
|
-
`channel=${logValue(elsewhere.channelId)} reason=channel-of-another-server: registered by server ${logValue(elsewhere.holder)}, not this one` +
|
|
371
|
-
(held ? ` (${held} more since the last line)` : ''),
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
this.emitTraceFn({
|
|
375
|
-
type: 'mcpl:push-event-rejected',
|
|
376
|
-
serverId,
|
|
377
|
-
eventId: typeof params.eventId === 'string' ? params.eventId : '',
|
|
378
|
-
featureSet: params.featureSet,
|
|
379
|
-
reason: 'channel-of-another-server',
|
|
380
|
-
channelId: elsewhere.channelId,
|
|
381
|
-
detail: `registered by server ${elsewhere.holder}`,
|
|
382
|
-
});
|
|
383
|
-
const error = channelNotPermitted(elsewhere.channelId);
|
|
384
|
-
if (responder?.respondError) responder.respondError(error.code, error.message, error.data);
|
|
385
|
-
else responder?.respond({ accepted: false, reason: error.message });
|
|
386
|
-
}
|
|
387
|
-
|
|
388
346
|
/** Loud, like every other push rejection: the producer's wake went nowhere.
|
|
389
347
|
* A missing eventId prints as `""` and traces as `''`, as on every push line. */
|
|
390
348
|
private traceEmptyRejection(serverId: string, params: PushEventParams): void {
|
|
@@ -224,7 +224,7 @@ export class ReferenceFetcher {
|
|
|
224
224
|
constructor(
|
|
225
225
|
private readonly resolveServer: (serverId: string) => ServerResolution | undefined,
|
|
226
226
|
/** Persist the payload; returns a mount-prefixed path or null when no
|
|
227
|
-
* writable
|
|
227
|
+
* writable mount without an include exists. File name is host-generated (§7.3). */
|
|
228
228
|
private readonly saveFile: (fileName: string, data: Buffer, mimeType: string) => Promise<string | null>,
|
|
229
229
|
) {}
|
|
230
230
|
|
|
@@ -463,9 +463,9 @@ export class ReferenceFetcher {
|
|
|
463
463
|
const observedMime = sniffMime(data) ?? headerMime ?? claimedMime ?? 'application/octet-stream';
|
|
464
464
|
const ext = EXT_BY_MIME.get(observedMime) ?? EXT_BY_MIME.get(headerMime ?? '') ?? EXT_BY_MIME.get(claimedMime ?? '') ?? 'bin';
|
|
465
465
|
// §7.3: host-generated storage name; the server's `name` is display-only.
|
|
466
|
-
// A null from the save hook covers no workspace, no writable mount
|
|
467
|
-
// a failed write, and the hook logs which. A
|
|
468
|
-
// too, not the fetch's, so it's logged here.
|
|
466
|
+
// A null from the save hook covers no workspace, no writable mount
|
|
467
|
+
// without an include and a failed write, and the hook logs which. A
|
|
468
|
+
// throw is the save's failure too, not the fetch's, so it's logged here.
|
|
469
469
|
const fileName = `${record.refId}.${ext}`;
|
|
470
470
|
let path: string | null;
|
|
471
471
|
try {
|
|
@@ -15,12 +15,15 @@ import { openTransport, STDERR_END_WAIT_MS, type McplTransport, type TransportCl
|
|
|
15
15
|
import { maskNegotiatedCapabilities } from './capability-mask.js';
|
|
16
16
|
import { CapabilityGrant, expandAdvertisementShorthand } from './capability-grant.js';
|
|
17
17
|
import { CAPABILITY_DISABLED, boundedForLog, describeCause } from './errors.js';
|
|
18
|
-
import { logDetail, logValue, logValueRedacted } from './log-value.js';
|
|
18
|
+
import { logDetail, logValue, logValueRedacted, loggedCode } from './log-value.js';
|
|
19
19
|
import {
|
|
20
20
|
LEGACY_MCP_PROTOCOL_VERSION,
|
|
21
21
|
MODERN_MCP_PROTOCOL_VERSION,
|
|
22
22
|
checkServerConfig,
|
|
23
23
|
redactServerText,
|
|
24
|
+
redactedData,
|
|
25
|
+
serverTextRedactor,
|
|
26
|
+
type ServerTextRedactor,
|
|
24
27
|
} from './protocol-family.js';
|
|
25
28
|
|
|
26
29
|
import type {
|
|
@@ -84,18 +87,30 @@ export interface McplConnectionCloseInfo {
|
|
|
84
87
|
* Spring Boot + JDA servers can take 5-10s to boot, so 30s is safe. */
|
|
85
88
|
const INITIALIZE_TIMEOUT_MS = 30_000;
|
|
86
89
|
|
|
90
|
+
/** How many errors down a `cause` chain stderrTailOf looks. */
|
|
91
|
+
const STDERR_TAIL_CAUSE_LINKS = 8;
|
|
92
|
+
|
|
87
93
|
/** A failed connect's `stderrTail`, the last of the server's stderr that its
|
|
88
|
-
* handshake's error keeps, when there is one
|
|
94
|
+
* handshake's error keeps, when there is one: the error's own, or the
|
|
95
|
+
* nearest one in its `cause` chain, since a launch that couldn't be reaped
|
|
96
|
+
* (McplUnreapedLaunchError) and the framework's refusals wrap the
|
|
97
|
+
* handshake's error. It looks at most STDERR_TAIL_CAUSE_LINKS causes down,
|
|
98
|
+
* so a chain that loops ends. */
|
|
89
99
|
export function stderrTailOf(error: unknown): string | undefined {
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
let at = error;
|
|
101
|
+
for (let link = 0; link <= STDERR_TAIL_CAUSE_LINKS && typeof at === 'object' && at !== null; link++) {
|
|
102
|
+
const { stderrTail, cause } = at as { stderrTail?: unknown; cause?: unknown };
|
|
103
|
+
if (typeof stderrTail === 'string' && stderrTail !== '') return stderrTail;
|
|
104
|
+
at = cause;
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
/** For the host's own log line about a failed connect: the server's stderr
|
|
95
|
-
* that the error keeps as `stderrTail
|
|
96
|
-
* taken out by `redact` (its
|
|
97
|
-
*
|
|
98
|
-
* none. */
|
|
110
|
+
* that the error, or the nearest error it wraps, keeps as `stderrTail`
|
|
111
|
+
* (stderrTailOf), with the server's credentials taken out by `redact` (its
|
|
112
|
+
* redactServerText), one marked line per line of it, each through
|
|
113
|
+
* log-value, since it's the server's text. '' when there's none. */
|
|
99
114
|
export function withStderr(error: unknown, redact: (text: string) => string): string {
|
|
100
115
|
const tail = stderrTailOf(error);
|
|
101
116
|
if (tail === undefined) return '';
|
|
@@ -156,11 +171,13 @@ export class McplProtocolVersionError extends Error {
|
|
|
156
171
|
constructor(
|
|
157
172
|
message: string,
|
|
158
173
|
readonly kind: 'rejected' | 'mismatch',
|
|
159
|
-
/** Revision the server answered with (`mismatch`; null when it gave
|
|
174
|
+
/** Revision the server answered with (`mismatch`; null when it gave
|
|
175
|
+
* none), redacted as the server's words are. */
|
|
160
176
|
readonly returned: string | null,
|
|
161
177
|
/** Revisions the server says it supports (`rejected`, when stated). */
|
|
162
178
|
readonly supported: readonly string[] | null,
|
|
163
|
-
/** The JSON-RPC error's code and data
|
|
179
|
+
/** The JSON-RPC error's code, and its data with each string in it
|
|
180
|
+
* redacted (`rejected`). */
|
|
164
181
|
readonly code?: number,
|
|
165
182
|
readonly data?: unknown,
|
|
166
183
|
/** The revision this client offered: the legacy engine's, unless a
|
|
@@ -172,8 +189,13 @@ export class McplProtocolVersionError extends Error {
|
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
191
|
|
|
175
|
-
function protocolVersionRejection(
|
|
176
|
-
const
|
|
192
|
+
function protocolVersionRejection(config: McplServerConfig, error: { code: number; message: string; data?: unknown }): McplProtocolVersionError {
|
|
193
|
+
const id = config.id;
|
|
194
|
+
// The server's words and data, redacted before they're bounded or kept:
|
|
195
|
+
// the error goes to the host. One build for all of them, so a
|
|
196
|
+
// configuration that can't be read is read once.
|
|
197
|
+
const redact = serverTextRedactor(config).now();
|
|
198
|
+
const data = redactedData(error.data, redact) as { supported?: unknown } | undefined;
|
|
177
199
|
const supported = Array.isArray(data?.supported) && data.supported.every((v) => typeof v === 'string')
|
|
178
200
|
? (data.supported as string[])
|
|
179
201
|
: null;
|
|
@@ -183,14 +205,15 @@ function protocolVersionRejection(id: string, error: { code: number; message: st
|
|
|
183
205
|
return new McplProtocolVersionError(
|
|
184
206
|
`MCPL server "${id}" does not support MCP ${MCP_PROTOCOL_VERSION}` +
|
|
185
207
|
// The server's message can be any JSON value, so it's read without
|
|
186
|
-
// throwing (describeCause) and bounded, as the
|
|
187
|
-
|
|
208
|
+
// throwing (describeCause), and redacted, then bounded, as the
|
|
209
|
+
// initialize error is.
|
|
210
|
+
(supported ? ` (it supports: ${boundedForLog(supported.join(', ')) || 'none listed'}).` : ` (${boundedForLog(redact(describeCause(error.message)))}).`) +
|
|
188
211
|
modernHint,
|
|
189
212
|
'rejected',
|
|
190
213
|
null,
|
|
191
214
|
supported,
|
|
192
215
|
error.code,
|
|
193
|
-
|
|
216
|
+
data,
|
|
194
217
|
);
|
|
195
218
|
}
|
|
196
219
|
|
|
@@ -218,16 +241,21 @@ class HandshakeCleanupError extends Error {
|
|
|
218
241
|
* is confirmed, so the caller can still clean up, and should start nothing
|
|
219
242
|
* else for this server until then. connect() and connectWithReconnect()
|
|
220
243
|
* throw this, and the modern engine's ModernMcpConnection.connect() too.
|
|
221
|
-
* `cause` is the connect's own failure.
|
|
244
|
+
* `cause` is the connect's own failure. `connection` isn't enumerable, so
|
|
245
|
+
* printing the error doesn't print the connection's configuration, its
|
|
246
|
+
* credentials included.
|
|
222
247
|
*/
|
|
223
248
|
export class McplUnreapedLaunchError<Connection extends { close(): Promise<void> } = McplServerConnection> extends Error {
|
|
249
|
+
declare readonly connection: Connection;
|
|
250
|
+
|
|
224
251
|
constructor(
|
|
225
|
-
|
|
252
|
+
connection: Connection,
|
|
226
253
|
failure: Error,
|
|
227
254
|
/** The cleanup's own failure: the child it couldn't confirm exited. */
|
|
228
255
|
readonly cleanup: Error,
|
|
229
256
|
) {
|
|
230
257
|
super(`${failure.message}; its cleanup also failed: ${cleanup.message}`, { cause: failure });
|
|
258
|
+
Object.defineProperty(this, 'connection', { value: connection, enumerable: false });
|
|
231
259
|
this.name = 'McplUnreapedLaunchError';
|
|
232
260
|
}
|
|
233
261
|
}
|
|
@@ -250,8 +278,9 @@ export class McplRequestError extends Error {
|
|
|
250
278
|
constructor(
|
|
251
279
|
message: string,
|
|
252
280
|
readonly outcome: 'not-sent' | 'error-response' | 'no-response',
|
|
253
|
-
/** The JSON-RPC error's `data`,
|
|
254
|
-
*
|
|
281
|
+
/** The JSON-RPC error's `data`, when the server supplied one (e.g. which
|
|
282
|
+
* parts of a multi-part send were posted), with each string in it
|
|
283
|
+
* redacted (redactedData): a host hands it on. */
|
|
255
284
|
readonly data?: unknown,
|
|
256
285
|
) {
|
|
257
286
|
super(message);
|
|
@@ -529,8 +558,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
529
558
|
|
|
530
559
|
// Event buffering: control-plane traffic can be released independently from
|
|
531
560
|
// inference-bearing data-plane traffic. This lets a server finish its own
|
|
532
|
-
// registration (and answer host tools/call requests) while
|
|
533
|
-
//
|
|
561
|
+
// registration (and answer host tools/call requests) while its data plane
|
|
562
|
+
// stays held, as through a quiesce.
|
|
534
563
|
private controlPlaneReady = false;
|
|
535
564
|
private dataPlaneReady = false;
|
|
536
565
|
private bufferedEvents: HeldItem[] = [];
|
|
@@ -568,6 +597,15 @@ export class McplServerConnection extends EventEmitter {
|
|
|
568
597
|
|
|
569
598
|
// Reconnect state (adapted from Anarchid/agent-framework@mcpl-module-proto)
|
|
570
599
|
private config: McplServerConfig | null = null;
|
|
600
|
+
/** The redactor of the server's text for `config`, made at its first error
|
|
601
|
+
* answer, by when `config` is set, and kept: a framework-launched child's
|
|
602
|
+
* `env` is a getter that reads the awareness ledger, so a redactor built
|
|
603
|
+
* per answer would read it per answer (#540's review, R1-2). An answer
|
|
604
|
+
* takes one build of it for its words, data and log line (`now()`), so
|
|
605
|
+
* it reads the ledger at most once. It builds again only after a build
|
|
606
|
+
* that failed, which withholds that answer's text, or once a dial notes
|
|
607
|
+
* something new (serverTextRedactor). */
|
|
608
|
+
private redactor: ServerTextRedactor | null = null;
|
|
571
609
|
private hostCapabilities: McplHostCapabilities | null = null;
|
|
572
610
|
private reconnectEnabled = false;
|
|
573
611
|
private reconnectIntervalMs = 5000;
|
|
@@ -666,8 +704,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
666
704
|
`(long pause backlog${dropped > 0 ? `; ${dropped} dropped at cap` : ''})`,
|
|
667
705
|
);
|
|
668
706
|
}
|
|
669
|
-
// Re-enter the gate for every buffered item. A control handler (
|
|
670
|
-
//
|
|
707
|
+
// Re-enter the gate for every buffered item. A control handler (a
|
|
708
|
+
// host/command quiesce, say) may pause the data plane while this
|
|
671
709
|
// snapshot is being flushed; later data items must observe that pause.
|
|
672
710
|
this.flushHeld(pending);
|
|
673
711
|
}
|
|
@@ -831,9 +869,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
831
869
|
|
|
832
870
|
/**
|
|
833
871
|
* Close the data plane synchronously while leaving an already-released
|
|
834
|
-
* control plane live. Used
|
|
835
|
-
*
|
|
836
|
-
* planes instead.
|
|
872
|
+
* control plane live. Used by quiesce, and by reconnectAfterFailure before
|
|
873
|
+
* its teardown. A reconnect closes both planes instead.
|
|
837
874
|
*/
|
|
838
875
|
pauseDataPlane(): void {
|
|
839
876
|
this.dataPlaneReady = false;
|
|
@@ -841,8 +878,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
841
878
|
|
|
842
879
|
/**
|
|
843
880
|
* Override emit to buffer server→host events until the corresponding plane
|
|
844
|
-
* is ready. Lifecycle events always pass through so reconnect
|
|
845
|
-
*
|
|
881
|
+
* is ready. Lifecycle events always pass through so the reconnect listener
|
|
882
|
+
* runs before the fresh transport's buffered traffic is released.
|
|
846
883
|
*/
|
|
847
884
|
override emit(event: string | symbol, ...args: unknown[]): boolean {
|
|
848
885
|
const name = typeof event === 'string' ? event : '';
|
|
@@ -1169,18 +1206,22 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1169
1206
|
},
|
|
1170
1207
|
};
|
|
1171
1208
|
|
|
1209
|
+
// When the transport fails, the end of the server's stderr often says
|
|
1210
|
+
// why, such as a missing token or a crash at startup. It goes on the
|
|
1211
|
+
// error as `stderrTail`, never into the message: an error's message
|
|
1212
|
+
// travels to traces and tool results, and a server's stderr is its own
|
|
1213
|
+
// text, which can hold a credential.
|
|
1214
|
+
const failed = (failure: string): Error & { stderrTail?: string } => {
|
|
1215
|
+
// It goes to the host too, so it's redacted, from a window of the
|
|
1216
|
+
// stream longer than the tail, so a credential the tail's cut
|
|
1217
|
+
// would split is whole when it's matched (stderrTailRedacted).
|
|
1218
|
+
const tail = transport.stderrTailRedacted(serverTextRedactor(config)).trimEnd();
|
|
1219
|
+
return Object.assign(new Error(failure), tail ? { stderrTail: tail } : {});
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1172
1222
|
// Await the initialize response, racing an early transport close/error,
|
|
1173
1223
|
// a timeout and the owner's close. Every path cleans up its listeners.
|
|
1174
1224
|
const { mcpl: rawCapabilities, mcpToolsAdvertised, protocolVersion } = await new Promise<{ mcpl: McplCapabilities | null; mcpToolsAdvertised: boolean; protocolVersion: string }>((resolve, reject) => {
|
|
1175
|
-
// When the transport fails, the end of the server's stderr often says
|
|
1176
|
-
// why, such as a missing token or a crash at startup. It goes on the
|
|
1177
|
-
// error as `stderrTail`, never into the message: an error's message
|
|
1178
|
-
// travels to traces and tool results, and a server's stderr is its
|
|
1179
|
-
// own text, which can hold a credential.
|
|
1180
|
-
const failed = (failure: string): Error & { stderrTail?: string } => {
|
|
1181
|
-
const tail = transport.stderrTail.trimEnd();
|
|
1182
|
-
return Object.assign(new Error(failure), tail ? { stderrTail: tail } : {});
|
|
1183
|
-
};
|
|
1184
1225
|
const cleanup = () => {
|
|
1185
1226
|
transport.off('line', onLine);
|
|
1186
1227
|
transport.off('close', onClose);
|
|
@@ -1222,21 +1263,25 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1222
1263
|
// listener after cleanup() has cleared the timeout: an uncaught
|
|
1223
1264
|
// exception, which ends a host that doesn't catch them, and a
|
|
1224
1265
|
// handshake left to wait forever. It's the server's text in an
|
|
1225
|
-
// error
|
|
1266
|
+
// error the host gets, so it's redacted, then bounded as
|
|
1267
|
+
// errors.ts says: a cut or a replaced character first would
|
|
1268
|
+
// leave part of a credential the redactor can't match.
|
|
1226
1269
|
reject(msg.error.code === UNSUPPORTED_PROTOCOL_VERSION_CODE
|
|
1227
|
-
? protocolVersionRejection(config
|
|
1228
|
-
: new Error(`MCPL server "${config.id}" initialize error: ${boundedForLog(describeCause(msg.error.message))}`));
|
|
1270
|
+
? protocolVersionRejection(config, msg.error)
|
|
1271
|
+
: new Error(`MCPL server "${config.id}" initialize error: ${boundedForLog(redactServerText(describeCause(msg.error.message), config))}`));
|
|
1229
1272
|
return;
|
|
1230
1273
|
}
|
|
1231
1274
|
const result = msg.result as Record<string, unknown> | undefined;
|
|
1232
1275
|
// The server's answer is the established revision. This engine
|
|
1233
1276
|
// implements exactly one, so any other answer (or none) means the
|
|
1234
1277
|
// two sides never agreed on what they're speaking.
|
|
1235
|
-
const
|
|
1236
|
-
if (
|
|
1278
|
+
const answered = typeof result?.protocolVersion === 'string' ? result.protocolVersion : null;
|
|
1279
|
+
if (answered !== MCP_PROTOCOL_VERSION) {
|
|
1280
|
+
// The server's words go to the host, so they're redacted.
|
|
1281
|
+
const returned = answered === null ? null : redactServerText(answered, config);
|
|
1237
1282
|
reject(new McplProtocolVersionError(
|
|
1238
1283
|
`MCPL server "${config.id}" answered initialize with protocolVersion ` +
|
|
1239
|
-
`${returned === null ? '(none)' : JSON.stringify(returned)}; this client's legacy engine speaks only ${MCP_PROTOCOL_VERSION}`,
|
|
1284
|
+
`${returned === null ? '(none)' : boundedForLog(JSON.stringify(returned))}; this client's legacy engine speaks only ${MCP_PROTOCOL_VERSION}`,
|
|
1240
1285
|
'mismatch',
|
|
1241
1286
|
returned,
|
|
1242
1287
|
null,
|
|
@@ -1251,7 +1296,7 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1251
1296
|
resolve({
|
|
1252
1297
|
mcpl: (experimental?.mcpl as McplCapabilities) ?? null,
|
|
1253
1298
|
mcpToolsAdvertised: caps?.tools !== undefined,
|
|
1254
|
-
protocolVersion:
|
|
1299
|
+
protocolVersion: answered,
|
|
1255
1300
|
});
|
|
1256
1301
|
};
|
|
1257
1302
|
const onClose = (info: TransportCloseInfo) => {
|
|
@@ -1281,6 +1326,22 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1281
1326
|
transport.writeLine(JSON.stringify(initRequest));
|
|
1282
1327
|
});
|
|
1283
1328
|
|
|
1329
|
+
// The transport has found the child's stdin or stdout failed before the
|
|
1330
|
+
// handshake could hand it on, such as when this host's answer to a ping
|
|
1331
|
+
// the server sent met EPIPE. Nothing more is written to the child then,
|
|
1332
|
+
// so the connection couldn't serve. It's read here, once the answer has
|
|
1333
|
+
// passed its own checks, rather than as the answer arrives: a write's
|
|
1334
|
+
// EPIPE is reported only once the callback that wrote it has returned,
|
|
1335
|
+
// so a ping and the answer read in one callback would pass a check made
|
|
1336
|
+
// there. A child that outlived SIGKILL may already have said so with
|
|
1337
|
+
// nothing listening (the transport's 'unreaped' is wired only once the
|
|
1338
|
+
// handshake hands it on). So the handshake fails, and its cleanup holds
|
|
1339
|
+
// the launch if it can't reap the child. A failure of the write below
|
|
1340
|
+
// is reported only once the caller has wired the transport, so it
|
|
1341
|
+
// reaches the connection as a loss, like any later one.
|
|
1342
|
+
const pipeFailure = transport.pipeFailure;
|
|
1343
|
+
if (pipeFailure) throw failed(`MCPL server "${config.id}" stdio pipe failed with ${pipeFailure.message} during the handshake`);
|
|
1344
|
+
|
|
1284
1345
|
// Send `initialized` notification (no id)
|
|
1285
1346
|
transport.writeLine(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }));
|
|
1286
1347
|
|
|
@@ -1485,9 +1546,13 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1485
1546
|
return this.sendRequest(McplMethod.ChannelsOpen, params as unknown as Record<string, unknown>, { timeoutMs }) as Promise<ChannelsOpenResult>;
|
|
1486
1547
|
}
|
|
1487
1548
|
|
|
1488
|
-
/** Send `channels/close` request and await result.
|
|
1549
|
+
/** Send `channels/close` request and await result. It keeps a deadline as
|
|
1550
|
+
* channels/open does: a delivery waits for a channel's queued lifecycle
|
|
1551
|
+
* work, closes included, so a close that is never answered would hold the
|
|
1552
|
+
* delivery, and the turn that waits for it, for good. */
|
|
1489
1553
|
sendChannelsClose(params: ChannelsCloseParams): Promise<ChannelsCloseResult> {
|
|
1490
|
-
|
|
1554
|
+
const timeoutMs = this.requestTimeoutMs > 0 ? this.requestTimeoutMs : this.publishDeadlineMs;
|
|
1555
|
+
return this.sendRequest(McplMethod.ChannelsClose, params as unknown as Record<string, unknown>, { timeoutMs }) as Promise<ChannelsCloseResult>;
|
|
1491
1556
|
}
|
|
1492
1557
|
|
|
1493
1558
|
/** Send `channels/acknowledge` request and await result. */
|
|
@@ -1585,10 +1650,11 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1585
1650
|
return { ...rest, tools };
|
|
1586
1651
|
}
|
|
1587
1652
|
if (typeof result.nextCursor !== 'string') {
|
|
1588
|
-
|
|
1653
|
+
// The cursor is the server's, so it's redacted, then bounded.
|
|
1654
|
+
throw malformed(`nextCursor must be a string, got ${boundedForLog(redactServerText(JSON.stringify(result.nextCursor), this.config ?? {}))}`);
|
|
1589
1655
|
}
|
|
1590
1656
|
if (seen.has(result.nextCursor)) {
|
|
1591
|
-
throw new Error(`MCPL server "${this.id}" repeated tools/list cursor ${JSON.stringify(result.nextCursor)}`);
|
|
1657
|
+
throw new Error(`MCPL server "${this.id}" repeated tools/list cursor ${boundedForLog(redactServerText(JSON.stringify(result.nextCursor), this.config ?? {}))}`);
|
|
1592
1658
|
}
|
|
1593
1659
|
seen.add(result.nextCursor);
|
|
1594
1660
|
cursor = result.nextCursor;
|
|
@@ -1895,9 +1961,8 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1895
1961
|
// boundary resets, so control traffic too (a server's channels/register
|
|
1896
1962
|
// right after initialize) must wait for the new grant rather than be
|
|
1897
1963
|
// refused against the empty one. The reconnect lifecycle event bypasses
|
|
1898
|
-
// buffering; its framework listener
|
|
1899
|
-
//
|
|
1900
|
-
// and then opens the planes.
|
|
1964
|
+
// buffering; its framework listener re-establishes the grant (§5.3
|
|
1965
|
+
// policy responses are never buffered), and then opens the planes.
|
|
1901
1966
|
this.controlPlaneReady = false;
|
|
1902
1967
|
this.dataPlaneReady = false;
|
|
1903
1968
|
this.resetPolicyForTransportBoundary();
|
|
@@ -1921,6 +1986,18 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1921
1986
|
// Private: message sending
|
|
1922
1987
|
// ==========================================================================
|
|
1923
1988
|
|
|
1989
|
+
/**
|
|
1990
|
+
* A request refused before anything is kept for it, as `error`, with its
|
|
1991
|
+
* operator's line logged after the refusal: a host logger that throws then
|
|
1992
|
+
* loses that line, and can't turn the refusal into a throw from the call.
|
|
1993
|
+
*/
|
|
1994
|
+
private static refused(error: McplRequestError, line: string): Promise<never> {
|
|
1995
|
+
return new Promise((_, reject) => {
|
|
1996
|
+
reject(error);
|
|
1997
|
+
console.error(line);
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
|
|
1924
2001
|
/**
|
|
1925
2002
|
* Send a JSON-RPC request and return a promise that resolves with the result
|
|
1926
2003
|
* or rejects with a JSON-RPC error.
|
|
@@ -1958,14 +2035,16 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1958
2035
|
// pipe's error are the operator's, so they go to the log.
|
|
1959
2036
|
const pipeFailure = this.transport?.pipeFailure;
|
|
1960
2037
|
if (pipeFailure) {
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2038
|
+
return McplServerConnection.refused(
|
|
2039
|
+
new McplRequestError(
|
|
2040
|
+
`The request wasn't sent: this host can no longer write to the "${this.id}" server, so nothing ran. ` +
|
|
2041
|
+
(this.willReconnect
|
|
2042
|
+
? 'This host is reconnecting to it; try again later.'
|
|
2043
|
+
: "This host won't reconnect to it on its own. If you need it, ask whoever runs this host."),
|
|
2044
|
+
'not-sent',
|
|
2045
|
+
),
|
|
2046
|
+
`MCPL server "${this.id}": ${method} not sent, since its stdio pipe failed: ${logDetail(pipeFailure.message)}`,
|
|
2047
|
+
);
|
|
1969
2048
|
}
|
|
1970
2049
|
|
|
1971
2050
|
const id = this.nextRequestId++;
|
|
@@ -1975,6 +2054,23 @@ export class McplServerConnection extends EventEmitter {
|
|
|
1975
2054
|
id,
|
|
1976
2055
|
params,
|
|
1977
2056
|
};
|
|
2057
|
+
// A request JSON can't write (a BigInt, a cycle) never leaves this host,
|
|
2058
|
+
// and calling again fails the same way, so it's refused before anything
|
|
2059
|
+
// is kept for it, in the modern engine's words (#556). The method, id and
|
|
2060
|
+
// the serializer's words are the operator's.
|
|
2061
|
+
let line: string;
|
|
2062
|
+
try {
|
|
2063
|
+
line = JSON.stringify(request);
|
|
2064
|
+
} catch (err) {
|
|
2065
|
+
return McplServerConnection.refused(
|
|
2066
|
+
new McplRequestError(
|
|
2067
|
+
`The request wasn't sent: this host couldn't send it to the "${this.id}" server, so nothing ran. ` +
|
|
2068
|
+
'If calling it again fails, ask whoever runs this host.',
|
|
2069
|
+
'not-sent',
|
|
2070
|
+
),
|
|
2071
|
+
`MCPL server "${this.id}": ${method} (id=${id}) not sent, since it can't be written as JSON: ${logDetail(err instanceof Error ? err.message : err)}`,
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
1978
2074
|
|
|
1979
2075
|
return new Promise((resolve, reject) => {
|
|
1980
2076
|
const pending: PendingRequest = { resolve, reject, method };
|
|
@@ -2017,20 +2113,21 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2017
2113
|
}
|
|
2018
2114
|
this.pendingRequests.set(id, pending);
|
|
2019
2115
|
try {
|
|
2020
|
-
this.transport!.writeLine(
|
|
2116
|
+
this.transport!.writeLine(line);
|
|
2021
2117
|
} catch (err) {
|
|
2022
2118
|
// A synchronous write rejection sent no request. Remove its pending
|
|
2023
2119
|
// state and watchdog before reporting that it is safe to retry. The
|
|
2024
|
-
// method, id and the transport's words are the operator's
|
|
2120
|
+
// method, id and the transport's words are the operator's, logged
|
|
2121
|
+
// after the refusal, as refused() logs.
|
|
2025
2122
|
this.pendingRequests.delete(id);
|
|
2026
2123
|
if (pending.timer) clearTimeout(pending.timer);
|
|
2027
|
-
console.error(
|
|
2028
|
-
`MCPL server "${this.id}": writing ${method} (id=${id}) failed: ${logDetail(err instanceof Error ? err.message : err)}`,
|
|
2029
|
-
);
|
|
2030
2124
|
reject(new McplRequestError(
|
|
2031
2125
|
`The request wasn't sent: this host couldn't write it to the "${this.id}" server, so nothing ran. Try again later.`,
|
|
2032
2126
|
'not-sent',
|
|
2033
2127
|
));
|
|
2128
|
+
console.error(
|
|
2129
|
+
`MCPL server "${this.id}": writing ${method} (id=${id}) failed: ${logDetail(err instanceof Error ? err.message : err)}`,
|
|
2130
|
+
);
|
|
2034
2131
|
}
|
|
2035
2132
|
});
|
|
2036
2133
|
}
|
|
@@ -2389,16 +2486,18 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2389
2486
|
if (pending.timer) clearTimeout(pending.timer);
|
|
2390
2487
|
|
|
2391
2488
|
if (response.error) {
|
|
2392
|
-
// The server's own words, after a word on whose they are,
|
|
2393
|
-
// errors.ts says, since they're the server's text
|
|
2394
|
-
//
|
|
2395
|
-
//
|
|
2396
|
-
//
|
|
2397
|
-
//
|
|
2398
|
-
//
|
|
2399
|
-
//
|
|
2400
|
-
//
|
|
2401
|
-
|
|
2489
|
+
// The server's own words, after a word on whose they are, redacted and
|
|
2490
|
+
// then bounded as errors.ts says, since they're the server's text in
|
|
2491
|
+
// an error the caller gets (redactServerText's scope), and its data's
|
|
2492
|
+
// strings redacted too. It's trimmed after it's redacted, so a
|
|
2493
|
+
// credential that ends in a space is whole when it's matched. Only a
|
|
2494
|
+
// string is words: a message of any other JSON value reads as none.
|
|
2495
|
+
// The method and id are the operator's, so they go to the log, and the
|
|
2496
|
+
// code (loggedCode) and the message, whatever JSON values they are, go
|
|
2497
|
+
// there redacted (logValueRedacted), as every other line this
|
|
2498
|
+
// connection writes is.
|
|
2499
|
+
const redact = (this.redactor ??= serverTextRedactor(this.config ?? {})).now();
|
|
2500
|
+
const said = typeof response.error.message === 'string' ? boundedForLog(redact(response.error.message).trim()) : '';
|
|
2402
2501
|
// The call is settled before its log line: its entry is gone and its
|
|
2403
2502
|
// timer cleared, so nothing else would settle it, and a host's logger
|
|
2404
2503
|
// can throw. The dispatch's containment reports such a throw. (A
|
|
@@ -2410,11 +2509,11 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2410
2509
|
: `The "${this.id}" server answered with an error without saying what went wrong, so whether the request took effect is unknown. ` +
|
|
2411
2510
|
'Check its effect before trying again.',
|
|
2412
2511
|
'error-response',
|
|
2413
|
-
(response.error as { data?: unknown }).data,
|
|
2512
|
+
redactedData((response.error as { data?: unknown }).data, redact),
|
|
2414
2513
|
));
|
|
2415
2514
|
console.error(
|
|
2416
|
-
`MCPL server "${this.id}" answered ${pending.method} (id=${response.id}) with error ${
|
|
2417
|
-
logValueRedacted(response.error.message,
|
|
2515
|
+
`MCPL server "${this.id}" answered ${pending.method} (id=${response.id}) with error ${loggedCode(response.error.code, redact)}: ` +
|
|
2516
|
+
logValueRedacted(response.error.message, redact),
|
|
2418
2517
|
);
|
|
2419
2518
|
} else {
|
|
2420
2519
|
pending.resolve(response.result);
|
|
@@ -2540,8 +2639,10 @@ export class McplServerConnection extends EventEmitter {
|
|
|
2540
2639
|
// reconnecting, and is lost as on a close, with the permanent failure
|
|
2541
2640
|
// that raises the alert and takes its tools off the list. Its id stays
|
|
2542
2641
|
// held until close() confirms the exit. A close the host has begun
|
|
2543
|
-
// reports the failure itself. During a handshake nothing listens yet:
|
|
2544
|
-
//
|
|
2642
|
+
// reports the failure itself. During a handshake nothing listens yet: a
|
|
2643
|
+
// handshake whose pipe has failed fails, when its answer comes (it's
|
|
2644
|
+
// refused then), when the child's exit closes the transport, or at its
|
|
2645
|
+
// timeout, and its own cleanup holds the launch.
|
|
2545
2646
|
transport.on('unreaped', (failure: Error) => {
|
|
2546
2647
|
if (this.closed || this.transport !== transport) return;
|
|
2547
2648
|
this.holdUnreaped(transport, failure);
|