@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
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* - Everything else is left unrouted and dropped by the framework — the
|
|
17
17
|
* trunk is a dormant template, not a listener.
|
|
18
18
|
*
|
|
19
|
-
* Bindings expire after an idle TTL. Expiry
|
|
20
|
-
* fork (see framework wiring)
|
|
21
|
-
* that channel spawns a *fresh* fork (generation + 1) from the
|
|
22
|
-
* trunk, so handbook/template updates propagate between engagements.
|
|
19
|
+
* Bindings expire after an idle TTL. Expiry unbinds the channel and runs a
|
|
20
|
+
* final closure turn on the fork (see framework wiring); the next qualifying
|
|
21
|
+
* message on that channel spawns a *fresh* fork (generation + 1) from the
|
|
22
|
+
* current trunk, so handbook/template updates propagate between engagements.
|
|
23
23
|
*
|
|
24
24
|
* The binding table is in-memory — bindings are re-established by the next
|
|
25
25
|
* qualifying message after a restart. The generation counters, by contrast,
|
|
@@ -108,10 +108,6 @@ export interface ConversationRouterConfig {
|
|
|
108
108
|
|
|
109
109
|
export interface ConversationBinding {
|
|
110
110
|
channelId: string;
|
|
111
|
-
/** The channel's MCPL server, where the binding was made with one: channel
|
|
112
|
-
* ids are unique only within one server's connection (MCPL §14.2), so each
|
|
113
|
-
* server's channel has its own binding. */
|
|
114
|
-
serverId?: string;
|
|
115
111
|
agentName: string;
|
|
116
112
|
/** Engagement counter per channel — fresh forks get a new generation. */
|
|
117
113
|
generation: number;
|
|
@@ -161,15 +157,11 @@ export const DEFAULT_CLOSURE_PROMPT =
|
|
|
161
157
|
export class ConversationRouter {
|
|
162
158
|
private config: ConversationRouterConfig;
|
|
163
159
|
|
|
164
|
-
/** Active bindings, keyed by
|
|
165
|
-
* two servers' channels with one id are two conversations. */
|
|
160
|
+
/** Active bindings, keyed by channelId. */
|
|
166
161
|
private bindings = new Map<string, ConversationBinding>();
|
|
167
162
|
|
|
168
|
-
/** Engagement counter per channel
|
|
169
|
-
*
|
|
170
|
-
* persisted: two servers sharing an id share a counter, which still never
|
|
171
|
-
* repeats a name, and their forks' names differ anyway, since each hashes
|
|
172
|
-
* its server (forkAgentName). */
|
|
163
|
+
/** Engagement counter per channel — survives unbind, so a re-bound channel
|
|
164
|
+
* gets a distinct fork agent name. */
|
|
173
165
|
private generations = new Map<string, number>();
|
|
174
166
|
|
|
175
167
|
constructor(config: ConversationRouterConfig) {
|
|
@@ -199,7 +191,7 @@ export class ConversationRouter {
|
|
|
199
191
|
* unchanged; a failed spawn doesn't leave a dangling route.
|
|
200
192
|
*/
|
|
201
193
|
route(facts: IncomingMessageFacts): RoutingDecision {
|
|
202
|
-
const existing = this.bindings.get(
|
|
194
|
+
const existing = this.bindings.get(facts.channelId);
|
|
203
195
|
|
|
204
196
|
if (existing) {
|
|
205
197
|
return {
|
|
@@ -231,57 +223,33 @@ export class ConversationRouter {
|
|
|
231
223
|
* Record a binding after the framework successfully spawned the fork.
|
|
232
224
|
* Returns the new binding.
|
|
233
225
|
*/
|
|
234
|
-
bind(
|
|
235
|
-
|
|
236
|
-
// The counter is kept by bare id, and two servers' spawns on one id can
|
|
237
|
-
// run beside each other, so a bind can finish after a later spawn's: the
|
|
238
|
-
// counter keeps the higher, or the next spawn would propose a fork name
|
|
239
|
-
// already used.
|
|
240
|
-
this.generations.set(channelId, Math.max(this.generations.get(channelId) ?? 0, generation));
|
|
226
|
+
bind(channelId: string, agentName: string, generation: number, now = Date.now()): ConversationBinding {
|
|
227
|
+
this.generations.set(channelId, generation);
|
|
241
228
|
const binding: ConversationBinding = {
|
|
242
229
|
channelId,
|
|
243
|
-
...(serverId !== undefined ? { serverId } : {}),
|
|
244
230
|
agentName,
|
|
245
231
|
generation,
|
|
246
232
|
boundAt: now,
|
|
247
233
|
lastActivity: now,
|
|
248
234
|
};
|
|
249
|
-
this.bindings.set(
|
|
235
|
+
this.bindings.set(channelId, binding);
|
|
250
236
|
return binding;
|
|
251
237
|
}
|
|
252
238
|
|
|
253
239
|
/** Record delivered activity on an existing binding. Does not bind a
|
|
254
240
|
* channel or advance its generation. Call after successful delivery, not
|
|
255
241
|
* when previewing a route. The optional clock supports deterministic use. */
|
|
256
|
-
touch(
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
unbind(channel: string | ConversationChannel): void {
|
|
262
|
-
const found = this.findBinding(channel);
|
|
263
|
-
if (found) this.bindings.delete(found[0]);
|
|
242
|
+
touch(channelId: string, now = Date.now()): void {
|
|
243
|
+
const binding = this.bindings.get(channelId);
|
|
244
|
+
if (binding) binding.lastActivity = now;
|
|
264
245
|
}
|
|
265
246
|
|
|
266
|
-
|
|
267
|
-
|
|
247
|
+
unbind(channelId: string): void {
|
|
248
|
+
this.bindings.delete(channelId);
|
|
268
249
|
}
|
|
269
250
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
* names that server's binding only. A bare id, as callers gave it before
|
|
273
|
-
* bindings had servers, names the binding made with no server, else the one
|
|
274
|
-
* binding for that id; where several servers' channels with that id are
|
|
275
|
-
* bound, it names none.
|
|
276
|
-
*/
|
|
277
|
-
private findBinding(channel: string | ConversationChannel): [string, ConversationBinding] | undefined {
|
|
278
|
-
const named = asChannel(channel);
|
|
279
|
-
const key = bindingKey(named);
|
|
280
|
-
const exact = this.bindings.get(key);
|
|
281
|
-
if (exact) return [key, exact];
|
|
282
|
-
if (named.serverId !== undefined) return undefined;
|
|
283
|
-
const matches = [...this.bindings.entries()].filter(([, b]) => b.channelId === named.channelId);
|
|
284
|
-
return matches.length === 1 ? matches[0] : undefined;
|
|
251
|
+
getBinding(channelId: string): ConversationBinding | undefined {
|
|
252
|
+
return this.bindings.get(channelId);
|
|
285
253
|
}
|
|
286
254
|
|
|
287
255
|
/** Reverse lookup: the channel an agent is bound to, if any. */
|
|
@@ -296,8 +264,10 @@ export class ConversationRouter {
|
|
|
296
264
|
return Array.from(this.bindings.values());
|
|
297
265
|
}
|
|
298
266
|
|
|
299
|
-
/** Bindings whose idle TTL has elapsed. Does not unbind
|
|
300
|
-
*
|
|
267
|
+
/** Bindings whose idle TTL has elapsed. Does not unbind: the framework
|
|
268
|
+
* unbinds each one first, so the channel's next message goes to a newer
|
|
269
|
+
* fork, or nowhere, and then runs the old fork's closure turn
|
|
270
|
+
* (sweepExpiredConversations). */
|
|
301
271
|
expired(now = Date.now()): ConversationBinding[] {
|
|
302
272
|
const ttl = this.idleTtlMs;
|
|
303
273
|
return this.getBindings().filter((b) => now - b.lastActivity >= ttl);
|
|
@@ -333,7 +303,10 @@ export class ConversationRouter {
|
|
|
333
303
|
return Object.fromEntries(this.generations);
|
|
334
304
|
}
|
|
335
305
|
|
|
336
|
-
/**
|
|
306
|
+
/** Raise each channel's engagement counter to at least the given
|
|
307
|
+
* generation, never lowering one: the persisted counters at startup (see
|
|
308
|
+
* exportGenerations), or a spawn's generation whose fork ended before the
|
|
309
|
+
* spawn could bind it. */
|
|
337
310
|
hydrateGenerations(generations: Record<string, number>): void {
|
|
338
311
|
for (const [channelId, generation] of Object.entries(generations)) {
|
|
339
312
|
if (typeof generation === 'number' && generation > (this.generations.get(channelId) ?? 0)) {
|
|
@@ -349,25 +322,6 @@ export class ConversationRouter {
|
|
|
349
322
|
}
|
|
350
323
|
}
|
|
351
324
|
|
|
352
|
-
/**
|
|
353
|
-
* A channel as the router's bindings take it: its id, with the MCPL server it
|
|
354
|
-
* belongs to where the caller has one. A bare id, as callers gave it before,
|
|
355
|
-
* is the channel with no server.
|
|
356
|
-
*/
|
|
357
|
-
export interface ConversationChannel {
|
|
358
|
-
channelId: string;
|
|
359
|
-
serverId?: string;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function asChannel(channel: string | ConversationChannel): ConversationChannel {
|
|
363
|
-
return typeof channel === 'string' ? { channelId: channel } : channel;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/** A binding's key: its channel's server and id, unambiguous whatever either holds. */
|
|
367
|
-
function bindingKey(channel: ConversationChannel): string {
|
|
368
|
-
return JSON.stringify([channel.serverId ?? null, channel.channelId]);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
325
|
/** The readable part of a fork's name stops here: uniqueness rides on the hash. */
|
|
372
326
|
const FORK_NAME_READABLE_MAX = 64;
|
|
373
327
|
|
|
@@ -43,9 +43,12 @@ export function convertBlock(block: McplContentBlock, provenance: string, server
|
|
|
43
43
|
// closed, withhold the data (checked BEFORE the data branch).
|
|
44
44
|
return { type: 'text', text: INLINE_WITHHELD_TEXT };
|
|
45
45
|
}
|
|
46
|
-
if (isFilledString(block.uri) && block.disposition) {
|
|
46
|
+
if (isFilledString(block.uri) && block.disposition !== undefined) {
|
|
47
47
|
// RFC-005 uri-form media with a disposition claim: stub, do not
|
|
48
|
-
// hand the URI to the provider or inline it.
|
|
48
|
+
// hand the URI to the provider or inline it. Any disposition given
|
|
49
|
+
// is a claim, one the reference rejects included, as classifyBlock
|
|
50
|
+
// and isInlineContradiction read it: a falsy one such as 0, false
|
|
51
|
+
// or '' stubs too, rather than sending the URI as the image's source.
|
|
49
52
|
return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
|
|
50
53
|
}
|
|
51
54
|
if (isFilledString(block.data) && isFilledString(block.mimeType)) {
|
|
@@ -68,9 +71,10 @@ export function convertBlock(block: McplContentBlock, provenance: string, server
|
|
|
68
71
|
// closed, withhold the data (checked BEFORE the data branch).
|
|
69
72
|
return { type: 'text', text: INLINE_WITHHELD_TEXT };
|
|
70
73
|
}
|
|
71
|
-
if (isFilledString(block.uri) && block.disposition) {
|
|
72
|
-
// RFC-005 uri-form media with a disposition claim
|
|
73
|
-
//
|
|
74
|
+
if (isFilledString(block.uri) && block.disposition !== undefined) {
|
|
75
|
+
// RFC-005 uri-form media with a disposition claim, a falsy one
|
|
76
|
+
// included, as for an image (above): stub, do not hand the URI to
|
|
77
|
+
// the provider or inline it.
|
|
74
78
|
return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
|
|
75
79
|
}
|
|
76
80
|
if (isFilledString(block.data) && isFilledString(block.mimeType)) {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* key: `eventId` is present only when the producer supplied one.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
|
|
24
26
|
/** The MCPL admission lane that accepted an item. Its contract decides what
|
|
25
27
|
* an `eventId` is worth: `push/event` deduplicates by eventId, and RFC-006
|
|
26
28
|
* coalesced admission (`coalesced: true`) guarantees stable retries and
|
|
@@ -154,3 +156,132 @@ export function conversationKey(source: InboundSource): string | undefined {
|
|
|
154
156
|
if (source.kind === 'surface') return JSON.stringify(['surface', source.surface]);
|
|
155
157
|
return undefined;
|
|
156
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* JSON with object keys sorted at every level, written as the store's append
|
|
162
|
+
* keeps a value (its native UTF-8 write, which the real-store digest table
|
|
163
|
+
* checks): an `undefined` object entry is dropped; a string is well-formed
|
|
164
|
+
* (each lone surrogate comes back as U+FFFD); and an entry whose key isn't
|
|
165
|
+
* well-formed is dropped (the store doesn't keep it). An `undefined` array
|
|
166
|
+
* element is written as `null`, as JSON writes it: the store's append
|
|
167
|
+
* refuses a body holding one, so no stored copy does. So a value
|
|
168
|
+
* hashes alike however its keys were ordered (content read back from the
|
|
169
|
+
* store has them in a different order than it was written), and alike
|
|
170
|
+
* before and after the store. editMessage writes JSON instead, so an edited
|
|
171
|
+
* copy can keep what an append wouldn't; it has changed anyway.
|
|
172
|
+
*
|
|
173
|
+
* It expects JSON-shaped values, as the MCPL lanes deliver them (parsed from
|
|
174
|
+
* JSON). Any other object is written by its own enumerable entries, unlike
|
|
175
|
+
* JSON.stringify: a Date becomes `{}`, as the store's own write keeps it, but
|
|
176
|
+
* a deferred write recovered from its JSON file brings it back as a string.
|
|
177
|
+
*
|
|
178
|
+
* sourceBodyDigest's serializer, kept out of the package API: serialization
|
|
179
|
+
* and framing both stay inside sourceBodyDigest, so consumers don't
|
|
180
|
+
* duplicate either (`canonicalJson(blocks)` alone hashes differently).
|
|
181
|
+
*/
|
|
182
|
+
export function canonicalJson(value: unknown): string {
|
|
183
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
184
|
+
if (value && typeof value === 'object') {
|
|
185
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
186
|
+
.filter(([k, v]) => v !== undefined && k.isWellFormed())
|
|
187
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
188
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === 'string') return JSON.stringify(value.toWellFormed());
|
|
191
|
+
return JSON.stringify(value) ?? 'null';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A block as the store hands it back. Context-manager keeps inline media as
|
|
196
|
+
* blobs (blob-manager.ts): an image whose source isn't a URL, and every
|
|
197
|
+
* document, audio or video block, comes back as `{ type, source: { type:
|
|
198
|
+
* 'base64', data, mediaType } }` and nothing else, its data re-encoded from
|
|
199
|
+
* the decoded bytes, and an image's media type taken from its bytes'
|
|
200
|
+
* signature where they have one. Any other block comes back as it was given,
|
|
201
|
+
* except a store-internal `blob_ref` from an unresolved read, which resolves
|
|
202
|
+
* to inline media by default. It is digested as given here, so an unresolved
|
|
203
|
+
* read and its resolved form differ. Context-manager refuses caller-supplied
|
|
204
|
+
* blob_ref writes; the MCPL lanes never store one.
|
|
205
|
+
*/
|
|
206
|
+
function storedBlock(block: unknown): unknown {
|
|
207
|
+
const media = block as { type?: unknown; source?: { type?: unknown; data?: unknown; mediaType?: unknown } } | null;
|
|
208
|
+
const blob = media?.type === 'image'
|
|
209
|
+
? media.source?.type !== 'url'
|
|
210
|
+
: media?.type === 'document' || media?.type === 'audio' || media?.type === 'video';
|
|
211
|
+
const source = media?.source;
|
|
212
|
+
if (!blob || typeof source?.data !== 'string') return block;
|
|
213
|
+
const data = canonicalBase64(source.data);
|
|
214
|
+
const mediaType = media!.type === 'image'
|
|
215
|
+
? sniffRasterImageMediaType(Buffer.from(data.slice(0, 32), 'base64')) ?? source.mediaType
|
|
216
|
+
: source.mediaType;
|
|
217
|
+
return { type: media!.type, source: { type: 'base64', data, mediaType } };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Base64 as the store writes it back from the decoded bytes: padded, with no
|
|
222
|
+
* whitespace, the standard alphabet and zero unused bits. Data already in
|
|
223
|
+
* that form, as every stored copy's is, is returned without decoding it all.
|
|
224
|
+
*/
|
|
225
|
+
function canonicalBase64(data: string): string {
|
|
226
|
+
if (data.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
|
|
227
|
+
const last = data.slice(-4);
|
|
228
|
+
if (Buffer.from(last, 'base64').toString('base64') === last) return data;
|
|
229
|
+
}
|
|
230
|
+
return Buffer.from(data, 'base64').toString('base64');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The store's own test for raster image types with an unambiguous byte
|
|
235
|
+
* signature, copied from context-manager's blob-manager.ts (it isn't
|
|
236
|
+
* exported): the store relabels an image whose bytes match one of these.
|
|
237
|
+
*/
|
|
238
|
+
function sniffRasterImageMediaType(bytes: Uint8Array): string | null {
|
|
239
|
+
if (bytes.length >= 8 &&
|
|
240
|
+
bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 &&
|
|
241
|
+
bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a) {
|
|
242
|
+
return 'image/png';
|
|
243
|
+
}
|
|
244
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
245
|
+
return 'image/jpeg';
|
|
246
|
+
}
|
|
247
|
+
if (bytes.length >= 6) {
|
|
248
|
+
const signature = Buffer.from(bytes.subarray(0, 6)).toString('ascii');
|
|
249
|
+
if (signature === 'GIF87a' || signature === 'GIF89a') return 'image/gif';
|
|
250
|
+
}
|
|
251
|
+
if (bytes.length >= 12 &&
|
|
252
|
+
Buffer.from(bytes.subarray(0, 4)).toString('ascii') === 'RIFF' &&
|
|
253
|
+
Buffer.from(bytes.subarray(8, 12)).toString('ascii') === 'WEBP') {
|
|
254
|
+
return 'image/webp';
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* The version identity of a delivered body (agreed with the receipts lane,
|
|
261
|
+
* room-220 #46752–#47210): SHA-256 hex of `canonicalJson([blocks])` over the
|
|
262
|
+
* ContentBlock[] the framework stores for the body — after MCPL conversion,
|
|
263
|
+
* before any host decoration is added and before storage shards it — with
|
|
264
|
+
* each block as the store keeps it (storedBlock, canonicalJson). So a body
|
|
265
|
+
* hashes alike as delivered, as handed to storage, and as read back from
|
|
266
|
+
* the store. The one-element array keeps the framing an undecorated,
|
|
267
|
+
* unsharded stored copy has always hashed to, so a stamped copy and an
|
|
268
|
+
* older copy of the same body share a version.
|
|
269
|
+
*
|
|
270
|
+
* Ingestion stamps it as `metadata.sourceBodyDigest`, and the same function
|
|
271
|
+
* over exactly the blocks handed to storage (decorations included) as
|
|
272
|
+
* `metadata.storedBodyDigest`, on both MCPL lanes. Both live outside the
|
|
273
|
+
* frozen admission envelope: they describe the body actually delivered (a
|
|
274
|
+
* materialization, a correction), not the admission. Neither proves later
|
|
275
|
+
* presence or completeness; an unsharded stored copy that no longer hashes
|
|
276
|
+
* to its storedBodyDigest was changed after delivery (editMessage keeps
|
|
277
|
+
* metadata). A body the store shards carries the whole body's two digests
|
|
278
|
+
* on every shard, and no shard hashes to them. Check a copy read back with
|
|
279
|
+
* its blobs resolved (getAllMessages, getMessage, or getMessageWindow
|
|
280
|
+
* without `resolveBlobs: false`): a blob reference never matches.
|
|
281
|
+
*
|
|
282
|
+
* Exported from the package root, so a consumer checks a copy against either
|
|
283
|
+
* field with this same function rather than a copy of it.
|
|
284
|
+
*/
|
|
285
|
+
export function sourceBodyDigest(blocks: readonly unknown[]): string {
|
|
286
|
+
return createHash('sha256').update(canonicalJson([blocks.map(storedBlock)])).digest('hex');
|
|
287
|
+
}
|
package/src/mcpl/index.ts
CHANGED
|
@@ -171,9 +171,11 @@ export { InferenceRouter } from './inference-router.js';
|
|
|
171
171
|
|
|
172
172
|
// Channel registry (channel lifecycle, incoming messages, synthesized tools)
|
|
173
173
|
export { ChannelRegistry } from './channel-registry.js';
|
|
174
|
+
export type { PublishRefusalReason } from './channel-registry.js';
|
|
174
175
|
|
|
175
|
-
// Inbound source envelope: where each accepted item came from, stamped once
|
|
176
|
-
|
|
176
|
+
// Inbound source envelope: where each accepted item came from, stamped once;
|
|
177
|
+
// and the digest of a delivered or stored body
|
|
178
|
+
export { INBOUND_SOURCE_KEY, readInboundSource, conversationKey, sourceBodyDigest } from './inbound-source.js';
|
|
177
179
|
export type {
|
|
178
180
|
InboundSource,
|
|
179
181
|
InboundChannelSource,
|
package/src/mcpl/log-value.ts
CHANGED
|
@@ -55,6 +55,15 @@ export function logValueRedacted(value: unknown, redact: (text: string) => strin
|
|
|
55
55
|
return printed(redact(textOf(value)), FIELD);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A JSON-RPC error answer's code as a log line prints it: a number, as
|
|
60
|
+
* JSON-RPC says a code is, as logValue prints it; any other JSON value a
|
|
61
|
+
* server sends there redacted (logValueRedacted), since the server wrote it.
|
|
62
|
+
*/
|
|
63
|
+
export function loggedCode(code: unknown, redact: (text: string) => string): string {
|
|
64
|
+
return typeof code === 'number' ? logValue(code) : logValueRedacted(code, redact);
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
/**
|
|
59
68
|
* The host's own message, as the free text that ends a log line after its
|
|
60
69
|
* fields: `reason=coalesce-invalid field=payload.content: "content must be an
|