@animalabs/agent-framework 0.21.1 → 0.22.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 +54 -4
- package/dist/src/agent.d.ts +17 -4
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +105 -43
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +41 -4
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/code-execution/py-runner.d.ts +22 -1
- package/dist/src/code-execution/py-runner.d.ts.map +1 -1
- package/dist/src/code-execution/py-runner.js +64 -9
- package/dist/src/code-execution/py-runner.js.map +1 -1
- package/dist/src/code-execution/runtime-py.d.ts +4 -0
- package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
- package/dist/src/code-execution/runtime-py.js +215 -38
- package/dist/src/code-execution/runtime-py.js.map +1 -1
- package/dist/src/framework.d.ts +569 -59
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3091 -542
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +21 -0
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +40 -7
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +26 -3
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts +28 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +17 -0
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js +31 -7
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/index.d.ts +4 -0
- package/dist/src/mcpl/index.d.ts.map +1 -1
- package/dist/src/mcpl/index.js +1 -0
- package/dist/src/mcpl/index.js.map +1 -1
- package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
- package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
- package/dist/src/mcpl/prose-stream-router.js +85 -22
- package/dist/src/mcpl/prose-stream-router.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +32 -4
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +46 -13
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +5 -3
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js +41 -11
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
- package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
- package/dist/src/mcpl/reference-fetcher.js +20 -9
- package/dist/src/mcpl/reference-fetcher.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +29 -6
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +57 -16
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +32 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +76 -11
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/server-registry.d.ts +5 -2
- package/dist/src/mcpl/server-registry.d.ts.map +1 -1
- package/dist/src/mcpl/server-registry.js +25 -7
- package/dist/src/mcpl/server-registry.js.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
- package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
- package/dist/src/mcpl/tool-lifecycle.js +47 -1
- package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
- package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
- package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
- package/dist/src/mcpl/tool-pattern-check.js +79 -0
- package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
- package/dist/src/mcpl/types.d.ts +15 -5
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/mcpl/visible-content.d.ts +39 -0
- package/dist/src/mcpl/visible-content.d.ts.map +1 -0
- package/dist/src/mcpl/visible-content.js +54 -0
- package/dist/src/mcpl/visible-content.js.map +1 -0
- package/dist/src/module-registry.d.ts +90 -6
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +260 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
- package/dist/src/modules/workspace/branch-intent.js +83 -0
- package/dist/src/modules/workspace/branch-intent.js.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
- package/dist/src/modules/workspace/disk-agreement.js +259 -0
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
- package/dist/src/modules/workspace/effects.d.ts +84 -0
- package/dist/src/modules/workspace/effects.d.ts.map +1 -0
- package/dist/src/modules/workspace/effects.js +368 -0
- package/dist/src/modules/workspace/effects.js.map +1 -0
- package/dist/src/modules/workspace/index.d.ts +155 -17
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +1101 -424
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +288 -0
- package/dist/src/modules/workspace/observe.d.ts.map +1 -0
- package/dist/src/modules/workspace/observe.js +825 -0
- package/dist/src/modules/workspace/observe.js.map +1 -0
- package/dist/src/modules/workspace/reconcile.d.ts +238 -0
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
- package/dist/src/modules/workspace/reconcile.js +909 -0
- package/dist/src/modules/workspace/reconcile.js.map +1 -0
- package/dist/src/modules/workspace/sync.d.ts +6 -59
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +6 -257
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +77 -10
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/operator-log.d.ts +41 -0
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/provider-waits.d.ts +216 -0
- package/dist/src/provider-waits.d.ts.map +1 -0
- package/dist/src/provider-waits.js +482 -0
- package/dist/src/provider-waits.js.map +1 -0
- package/dist/src/record-journal.d.ts +127 -0
- package/dist/src/record-journal.d.ts.map +1 -0
- package/dist/src/record-journal.js +202 -0
- package/dist/src/record-journal.js.map +1 -0
- package/dist/src/refusal-reactions.d.ts +14 -12
- package/dist/src/refusal-reactions.d.ts.map +1 -1
- package/dist/src/refusal-reactions.js +14 -12
- package/dist/src/refusal-reactions.js.map +1 -1
- package/dist/src/shutdown.d.ts +13 -0
- package/dist/src/shutdown.d.ts.map +1 -0
- package/dist/src/shutdown.js +46 -0
- package/dist/src/shutdown.js.map +1 -0
- package/dist/src/silent-heartbeat.d.ts +74 -0
- package/dist/src/silent-heartbeat.d.ts.map +1 -0
- package/dist/src/silent-heartbeat.js +77 -0
- package/dist/src/silent-heartbeat.js.map +1 -0
- package/dist/src/tool-presentation.d.ts +50 -0
- package/dist/src/tool-presentation.d.ts.map +1 -0
- package/dist/src/tool-presentation.js +235 -0
- package/dist/src/tool-presentation.js.map +1 -0
- package/dist/src/tool-result-guard.d.ts +142 -6
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +357 -36
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +13 -0
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +21 -1
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/types/agent.d.ts +18 -2
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +16 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +12 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +26 -4
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +87 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/batched-wake-locus.test.d.ts +2 -0
- package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
- package/dist/test/batched-wake-locus.test.js +166 -0
- package/dist/test/batched-wake-locus.test.js.map +1 -0
- package/dist/test/code-execution.test.js +386 -2
- package/dist/test/code-execution.test.js.map +1 -1
- package/dist/test/component-defaults.test.d.ts +2 -0
- package/dist/test/component-defaults.test.d.ts.map +1 -0
- package/dist/test/component-defaults.test.js +54 -0
- package/dist/test/component-defaults.test.js.map +1 -0
- package/dist/test/conversation-routing.test.js +57 -1
- package/dist/test/conversation-routing.test.js.map +1 -1
- package/dist/test/discord-awareness-outbox.test.js +3 -0
- package/dist/test/discord-awareness-outbox.test.js.map +1 -1
- package/dist/test/ephemeral-settle.test.js +3 -0
- package/dist/test/ephemeral-settle.test.js.map +1 -1
- package/dist/test/event-gate.test.js +118 -0
- package/dist/test/event-gate.test.js.map +1 -1
- package/dist/test/explicit-prose-routing.test.js +975 -0
- package/dist/test/explicit-prose-routing.test.js.map +1 -1
- package/dist/test/framework.test.js +160 -1
- package/dist/test/framework.test.js.map +1 -1
- package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
- package/dist/test/helpers/coalescing-fixture.js +3 -2
- package/dist/test/helpers/coalescing-fixture.js.map +1 -1
- package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
- package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
- package/dist/test/helpers/pytc-stop-rows.js +143 -0
- package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
- package/dist/test/inference-exhaustion-rewind.test.js +142 -14
- package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
- package/dist/test/inference-failure-bounds.test.d.ts +2 -0
- package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
- package/dist/test/inference-failure-bounds.test.js +1201 -0
- package/dist/test/inference-failure-bounds.test.js.map +1 -0
- package/dist/test/late-events-after-stop.test.d.ts +2 -0
- package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
- package/dist/test/late-events-after-stop.test.js +122 -0
- package/dist/test/late-events-after-stop.test.js.map +1 -0
- package/dist/test/mcpl-awareness-barrier.test.js +141 -0
- package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
- package/dist/test/mcpl-coalescing.test.js +97 -22
- package/dist/test/mcpl-coalescing.test.js.map +1 -1
- package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
- package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
- package/dist/test/mcpl-config-diagnostics.test.js +452 -0
- package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
- package/dist/test/mcpl-empty-content.test.d.ts +2 -0
- package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
- package/dist/test/mcpl-empty-content.test.js +368 -0
- package/dist/test/mcpl-empty-content.test.js.map +1 -0
- package/dist/test/mcpl-manifest-changed.test.js +2 -1
- package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
- package/dist/test/mcpl-reference-fetcher.test.js +232 -4
- package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
- package/dist/test/mcpl-reference-fields.test.js +69 -2
- package/dist/test/mcpl-reference-fields.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
- package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
- package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
- package/dist/test/mcpl-ws-transport.test.js +103 -0
- package/dist/test/mcpl-ws-transport.test.js.map +1 -1
- package/dist/test/ops-alert.test.js +2 -1
- package/dist/test/ops-alert.test.js.map +1 -1
- package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
- package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
- package/dist/test/placed-reaction-baseline.test.js +312 -0
- package/dist/test/placed-reaction-baseline.test.js.map +1 -0
- package/dist/test/prose-stream-router.test.js +41 -0
- package/dist/test/prose-stream-router.test.js.map +1 -1
- package/dist/test/provider-acceleration-cooldown.test.js +5 -1
- package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
- package/dist/test/provider-waits.test.d.ts +2 -0
- package/dist/test/provider-waits.test.d.ts.map +1 -0
- package/dist/test/provider-waits.test.js +2196 -0
- package/dist/test/provider-waits.test.js.map +1 -0
- package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
- package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
- package/dist/test/reaction-baseline-platform.test.js +124 -0
- package/dist/test/reaction-baseline-platform.test.js.map +1 -0
- package/dist/test/record-journal.test.d.ts +6 -0
- package/dist/test/record-journal.test.d.ts.map +1 -0
- package/dist/test/record-journal.test.js +269 -0
- package/dist/test/record-journal.test.js.map +1 -0
- package/dist/test/refusal-reaction-baseline.test.js +10 -3
- package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
- package/dist/test/save-recent-image.test.js +240 -2
- package/dist/test/save-recent-image.test.js.map +1 -1
- package/dist/test/shutdown-errors.test.d.ts +2 -0
- package/dist/test/shutdown-errors.test.d.ts.map +1 -0
- package/dist/test/shutdown-errors.test.js +662 -0
- package/dist/test/shutdown-errors.test.js.map +1 -0
- package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
- package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
- package/dist/test/silent-heartbeat-rows.test.js +451 -0
- package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
- package/dist/test/silent-heartbeat.test.js +49 -38
- package/dist/test/silent-heartbeat.test.js.map +1 -1
- package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
- package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
- package/dist/test/stop-with-work-in-flight.test.js +672 -0
- package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
- package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
- package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
- package/dist/test/surgery-marks-receipt.test.js +540 -0
- package/dist/test/surgery-marks-receipt.test.js.map +1 -0
- package/dist/test/tool-batch-hook.test.d.ts +8 -0
- package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
- package/dist/test/tool-batch-hook.test.js +275 -0
- package/dist/test/tool-batch-hook.test.js.map +1 -0
- package/dist/test/tool-presentation.test.d.ts +2 -0
- package/dist/test/tool-presentation.test.d.ts.map +1 -0
- package/dist/test/tool-presentation.test.js +268 -0
- package/dist/test/tool-presentation.test.js.map +1 -0
- package/dist/test/tool-result-guard.test.js +2545 -19
- package/dist/test/tool-result-guard.test.js.map +1 -1
- package/dist/test/tool-result-history.test.js +2 -1
- package/dist/test/tool-result-history.test.js.map +1 -1
- package/dist/test/turn-trigger-provenance.test.js +35 -0
- package/dist/test/turn-trigger-provenance.test.js.map +1 -1
- package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
- package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
- package/dist/test/unconvertible-tool-result.test.js +358 -0
- package/dist/test/unconvertible-tool-result.test.js.map +1 -0
- package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
- package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
- package/dist/test/workspace-materialize-freshness.test.js +252 -0
- package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
- package/dist/test/workspace-three-way.test.d.ts +12 -0
- package/dist/test/workspace-three-way.test.d.ts.map +1 -0
- package/dist/test/workspace-three-way.test.js +3194 -0
- package/dist/test/workspace-three-way.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/agent.ts +124 -45
- package/src/api/server.ts +43 -4
- package/src/code-execution/py-runner.ts +66 -9
- package/src/code-execution/runtime-py.ts +215 -38
- package/src/framework.ts +3191 -544
- package/src/gate/event-gate.ts +59 -7
- package/src/index.ts +18 -2
- package/src/mcpl/channel-registry.ts +24 -3
- package/src/mcpl/conversation-router.ts +34 -1
- package/src/mcpl/feature-set-manager.ts +36 -11
- package/src/mcpl/index.ts +4 -0
- package/src/mcpl/prose-stream-router.ts +79 -23
- package/src/mcpl/push-coalescer.ts +62 -13
- package/src/mcpl/push-handler.ts +41 -12
- package/src/mcpl/reference-fetcher.ts +18 -8
- package/src/mcpl/references.ts +57 -13
- package/src/mcpl/server-connection.ts +95 -13
- package/src/mcpl/server-registry.ts +24 -7
- package/src/mcpl/tool-lifecycle.ts +77 -2
- package/src/mcpl/tool-pattern-check.ts +106 -0
- package/src/mcpl/types.ts +15 -5
- package/src/mcpl/visible-content.ts +57 -0
- package/src/module-registry.ts +269 -25
- package/src/modules/workspace/branch-intent.ts +123 -0
- package/src/modules/workspace/disk-agreement.ts +325 -0
- package/src/modules/workspace/effects.ts +362 -0
- package/src/modules/workspace/index.ts +1098 -446
- package/src/modules/workspace/observe.ts +865 -0
- package/src/modules/workspace/reconcile.ts +1157 -0
- package/src/modules/workspace/sync.ts +6 -336
- package/src/modules/workspace/types.ts +75 -10
- package/src/operator-log.ts +30 -0
- package/src/provider-waits.ts +523 -0
- package/src/record-journal.ts +246 -0
- package/src/refusal-reactions.ts +14 -12
- package/src/shutdown.ts +49 -0
- package/src/silent-heartbeat.ts +141 -0
- package/src/tool-presentation.ts +188 -0
- package/src/tool-result-guard.ts +384 -34
- package/src/tool-result-history.ts +25 -1
- package/src/types/agent.ts +18 -2
- package/src/types/events.ts +16 -0
- package/src/types/framework.ts +12 -0
- package/src/types/module.ts +27 -4
- package/src/types/trace.ts +94 -3
package/src/gate/event-gate.ts
CHANGED
|
@@ -88,6 +88,11 @@ interface PendingEvent {
|
|
|
88
88
|
* newer ambient chatter when a batched wake picks its provenance — the same
|
|
89
89
|
* rule the framework applies to the turn's speech locus. */
|
|
90
90
|
addressed: boolean;
|
|
91
|
+
/** The REGISTERED (composite) channel id the event arrived on, resolved by
|
|
92
|
+
* the host at enqueue time. Differs from `channelId` for push events,
|
|
93
|
+
* whose `channelId` is the adapter's raw id (a bare Discord snowflake for a
|
|
94
|
+
* DM) — unroutable. Absent when the host could not resolve one. */
|
|
95
|
+
routeChannelId?: string;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
/**
|
|
@@ -106,6 +111,15 @@ export interface WakeProvenance {
|
|
|
106
111
|
counterparty?: string;
|
|
107
112
|
/** True when the chosen event was `chat:addressed`. */
|
|
108
113
|
addressed?: boolean;
|
|
114
|
+
/** Speech locus for the wake: the routable channel of the chosen event,
|
|
115
|
+
* set ONLY when that event addressed the agent (mention / reply / DM) and
|
|
116
|
+
* the host resolved its registered channel. A batched wake used to carry
|
|
117
|
+
* no locus at all, so its reply fell back to the process-global
|
|
118
|
+
* most-recent-inbound channel — which any ambient message elsewhere,
|
|
119
|
+
* arriving a second after the DM that caused the wake, could retarget
|
|
120
|
+
* (2026-09-30: a DM reply published into a guild channel). Ambient-only
|
|
121
|
+
* batches still set nothing and keep the legacy fallback. */
|
|
122
|
+
routeChannelId?: string;
|
|
109
123
|
/** Timestamp (ms) of the chosen event, so a consumer coalescing several
|
|
110
124
|
* requests can order by event recency, not by flush order. */
|
|
111
125
|
at?: number;
|
|
@@ -190,7 +204,14 @@ export function wakeProvenance(events: PendingEvent[]): WakeProvenance | undefin
|
|
|
190
204
|
let newest: PendingEvent | undefined;
|
|
191
205
|
let newestAddressed: PendingEvent | undefined;
|
|
192
206
|
for (const e of events) {
|
|
193
|
-
|
|
207
|
+
// A resolved route counts as naming a channel only for an ADDRESSED
|
|
208
|
+
// event: a push whose origin declares only `mcplChannelId` (no raw
|
|
209
|
+
// channel id, no author) is still a routable addressed message, and
|
|
210
|
+
// skipping it would leave its wake on the most-recent-inbound fallback.
|
|
211
|
+
// An ambient one stays out: the route is never reported for it, so as
|
|
212
|
+
// the pick it would carry no telemetry at all and hide an earlier event
|
|
213
|
+
// that names its channel and author.
|
|
214
|
+
if (!e.channelId && !e.authorId && !(e.addressed && e.routeChannelId)) continue;
|
|
194
215
|
if (!newest || e.timestamp >= newest.timestamp) newest = e;
|
|
195
216
|
if (e.addressed && (!newestAddressed || e.timestamp >= newestAddressed.timestamp)) newestAddressed = e;
|
|
196
217
|
}
|
|
@@ -204,10 +225,20 @@ export function wakeProvenance(events: PendingEvent[]): WakeProvenance | undefin
|
|
|
204
225
|
...(channelId ? { channelId } : {}),
|
|
205
226
|
...(pick.authorId ? { counterparty: `${ns}:user:${pick.authorId}` } : {}),
|
|
206
227
|
...(pick.addressed ? { addressed: true } : {}),
|
|
228
|
+
...(pick.addressed && pick.routeChannelId ? { routeChannelId: pick.routeChannelId } : {}),
|
|
207
229
|
at: pick.timestamp,
|
|
208
230
|
};
|
|
209
231
|
}
|
|
210
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Default route-channel resolution when the host supplies none: a
|
|
235
|
+
* channel-incoming event's channel id is already the registered composite id;
|
|
236
|
+
* a push event's is the adapter's raw id, which only the host can map.
|
|
237
|
+
*/
|
|
238
|
+
function defaultRouteChannel(info: GateEventInfo): string | undefined {
|
|
239
|
+
return info.eventType === 'mcpl:channel-incoming' && info.channelId ? info.channelId : undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
211
242
|
/** Compact, log-friendly serialization of a GateBehavior. */
|
|
212
243
|
function formatBehavior(b: import('./types.js').GateBehavior): string {
|
|
213
244
|
if (typeof b === 'string') return b;
|
|
@@ -587,6 +618,7 @@ export class EventGate {
|
|
|
587
618
|
) => unknown;
|
|
588
619
|
private requestInferenceFn: (agentName: string, reason: string, source: string, provenance?: WakeProvenance) => void;
|
|
589
620
|
private getAgentNamesFn: () => string[];
|
|
621
|
+
private resolveRouteChannelFn: (info: GateEventInfo) => string | undefined;
|
|
590
622
|
/** Clock injection — keeps the new rate_limit / passive_sample paths
|
|
591
623
|
* testable without monkey-patching Date.now globally. */
|
|
592
624
|
private now: () => number;
|
|
@@ -608,6 +640,10 @@ export class EventGate {
|
|
|
608
640
|
now?: () => number;
|
|
609
641
|
/** Per-event timeout (ms) for the optional gate.js script. Default 50. */
|
|
610
642
|
scriptTimeoutMs?: number;
|
|
643
|
+
/** Map an event to the registered channel it arrived on (the host knows
|
|
644
|
+
* how push-event origins compose into channel ids). Default: a
|
|
645
|
+
* channel-incoming event's own channel id; nothing for push events. */
|
|
646
|
+
resolveRouteChannel?: (info: GateEventInfo) => string | undefined;
|
|
611
647
|
}) {
|
|
612
648
|
this.configPath = opts.configPath;
|
|
613
649
|
this.privilegedUsersPath = opts.privilegedUsersPath;
|
|
@@ -615,6 +651,7 @@ export class EventGate {
|
|
|
615
651
|
this.addMessageFn = opts.addMessage;
|
|
616
652
|
this.requestInferenceFn = opts.requestInference;
|
|
617
653
|
this.getAgentNamesFn = opts.getAgentNames;
|
|
654
|
+
this.resolveRouteChannelFn = opts.resolveRouteChannel ?? defaultRouteChannel;
|
|
618
655
|
this.now = opts.now ?? (() => Date.now());
|
|
619
656
|
this.loadPrivileged();
|
|
620
657
|
|
|
@@ -1432,6 +1469,15 @@ export class EventGate {
|
|
|
1432
1469
|
// Debounce
|
|
1433
1470
|
// =========================================================================
|
|
1434
1471
|
|
|
1472
|
+
/** Registered channel for an event; a resolver fault never breaks gating. */
|
|
1473
|
+
private routeChannelFor(info: GateEventInfo): string | undefined {
|
|
1474
|
+
try {
|
|
1475
|
+
return this.resolveRouteChannelFn(info) || undefined;
|
|
1476
|
+
} catch {
|
|
1477
|
+
return undefined;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1435
1481
|
private handleDebounce(policy: GatePolicy, info: GateEventInfo): void {
|
|
1436
1482
|
const debounceMs = (policy.behavior as { debounce: number }).debounce;
|
|
1437
1483
|
|
|
@@ -1451,6 +1497,7 @@ export class EventGate {
|
|
|
1451
1497
|
authorId: this.extractAuthorId(info.metadata) ?? undefined,
|
|
1452
1498
|
serverId: info.serverId || undefined,
|
|
1453
1499
|
addressed: Array.isArray(info.tags) && info.tags.includes('chat:addressed'),
|
|
1500
|
+
routeChannelId: this.routeChannelFor(info),
|
|
1454
1501
|
};
|
|
1455
1502
|
|
|
1456
1503
|
const existing = this.debounceTimers.get(policy.name);
|
|
@@ -1514,7 +1561,10 @@ export class EventGate {
|
|
|
1514
1561
|
if (referenced.length > 0) {
|
|
1515
1562
|
const byChannel = new Map<string, { count: number; oldest: number; label?: string }>();
|
|
1516
1563
|
for (const e of referenced) {
|
|
1517
|
-
|
|
1564
|
+
// The registered id when known, so this line names the channel the
|
|
1565
|
+
// same way routing notices and send tools do (a push event's own
|
|
1566
|
+
// channelId is the adapter's raw id).
|
|
1567
|
+
const key = e.routeChannelId ?? e.channelId ?? '(unknown channel)';
|
|
1518
1568
|
const entry = byChannel.get(key) ?? { count: 0, oldest: e.timestamp, label: e.channelLabel };
|
|
1519
1569
|
entry.count += 1;
|
|
1520
1570
|
entry.oldest = Math.min(entry.oldest, e.timestamp);
|
|
@@ -1541,11 +1591,13 @@ export class EventGate {
|
|
|
1541
1591
|
policies: policyNames,
|
|
1542
1592
|
});
|
|
1543
1593
|
|
|
1544
|
-
// Provenance of the batched wake (
|
|
1545
|
-
//
|
|
1546
|
-
//
|
|
1547
|
-
// event
|
|
1548
|
-
//
|
|
1594
|
+
// Provenance of the batched wake (who/where woke the agent): the newest
|
|
1595
|
+
// ADDRESSED event first, else the newest event naming a channel or an
|
|
1596
|
+
// author; channel + author + addressed from that ONE event. When that
|
|
1597
|
+
// event addressed the agent and its registered channel is known,
|
|
1598
|
+
// `routeChannelId` names the turn's speech locus too — the reply goes
|
|
1599
|
+
// back to whoever addressed the agent, not to whichever channel last saw
|
|
1600
|
+
// any traffic. Ambient-only batches carry telemetry only.
|
|
1549
1601
|
const provenance = wakeProvenance(events);
|
|
1550
1602
|
for (const agentName of this.getAgentNamesFn()) {
|
|
1551
1603
|
this.requestInferenceFn(agentName, 'gate:debounce', 'gate', provenance);
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { Agent } from './agent.js';
|
|
|
8
8
|
export type { StartStreamResult } from './agent.js';
|
|
9
9
|
export { ProcessQueueImpl } from './queue.js';
|
|
10
10
|
export { ModuleRegistry } from './module-registry.js';
|
|
11
|
+
export { ShutdownTimeoutError, DEFAULT_SHUTDOWN_TIMEOUT_MS } from './shutdown.js';
|
|
11
12
|
export { formatZonedDateTime, formatZonedTime, isValidTimeZone, resolveTimeZone } from './timezone.js';
|
|
12
13
|
export { REFUSAL_REACTIONS, REFUSAL_REACTION_FALLBACK, REFUSAL_REACTION_BASELINE } from './refusal-reactions.js';
|
|
13
14
|
|
|
@@ -91,7 +92,7 @@ export type { McplServerConfig } from './mcpl/index.js';
|
|
|
91
92
|
// health probes, protocol harnesses — can dial an MCPL server exactly the
|
|
92
93
|
// way the framework host does, over stdio or WebSocket)
|
|
93
94
|
export { McplServerConnection } from './mcpl/index.js';
|
|
94
|
-
export type { McplHostCapabilities } from './mcpl/index.js';
|
|
95
|
+
export type { McplHostCapabilities, McplConnectionCloseInfo } from './mcpl/index.js';
|
|
95
96
|
|
|
96
97
|
// MCPL tool lifecycle (RFC-007) and tool classes (RFC-008): the types a host
|
|
97
98
|
// needs to write McplServerConfig.toolLifecycle policy and class tables, and
|
|
@@ -107,7 +108,9 @@ export type {
|
|
|
107
108
|
} from './mcpl/index.js';
|
|
108
109
|
|
|
109
110
|
// Per-channel conversation routing
|
|
111
|
+
/** @deprecated Per-channel conversation routing is deprecated and will be removed (anima-research/agent-framework#235). */
|
|
110
112
|
export { ConversationRouter, DEFAULT_CLOSURE_PROMPT } from './mcpl/index.js';
|
|
113
|
+
/** @deprecated Per-channel conversation routing is deprecated and will be removed (anima-research/agent-framework#235). */
|
|
111
114
|
export type {
|
|
112
115
|
ConversationRouterConfig,
|
|
113
116
|
ConversationBinding,
|
|
@@ -140,9 +143,22 @@ export type {
|
|
|
140
143
|
} from './recovery/discord-awareness-outbox.js';
|
|
141
144
|
// Live operator surgery (rollback / suppress) and its durable action log
|
|
142
145
|
export { OperatorLog, OperatorActionError, defaultOperatorLogPath } from './operator-log.js';
|
|
143
|
-
export type {
|
|
146
|
+
export type {
|
|
147
|
+
OperatorLogEntry,
|
|
148
|
+
OperatorLogInput,
|
|
149
|
+
OperatorRequester,
|
|
150
|
+
SurgeryMarkerReceipt,
|
|
151
|
+
} from './operator-log.js';
|
|
144
152
|
export { createOfflineRecoveryBranch } from './recovery/offline-branch.js';
|
|
145
153
|
export type {
|
|
146
154
|
OfflineRecoveryBranchOptions,
|
|
147
155
|
OfflineRecoveryBranchResult,
|
|
148
156
|
} from './recovery/offline-branch.js';
|
|
157
|
+
|
|
158
|
+
export * from "./tool-presentation.js";
|
|
159
|
+
export {
|
|
160
|
+
SILENT_HEARTBEAT_SEPARATOR,
|
|
161
|
+
silentHeartbeatOf,
|
|
162
|
+
type SilentHeartbeatStamp,
|
|
163
|
+
type SilentHeartbeatTick,
|
|
164
|
+
} from './silent-heartbeat.js';
|
|
@@ -37,7 +37,8 @@ import type { McplServerRegistry } from './server-registry.js';
|
|
|
37
37
|
import type { FeatureSetManager } from './feature-set-manager.js';
|
|
38
38
|
import type { ToolDefinition, ToolResult, ProcessEvent } from '../types/index.js';
|
|
39
39
|
import { expandCoreTags } from './tags.js';
|
|
40
|
-
import { validateCoalescedContent } from './push-coalescer.js';
|
|
40
|
+
import { EmptyContentError, validateCoalescedContent } from './push-coalescer.js';
|
|
41
|
+
import { isVisiblyEmptyContent } from './visible-content.js';
|
|
41
42
|
import { CapabilityGrant } from './capability-grant.js';
|
|
42
43
|
|
|
43
44
|
// ============================================================================
|
|
@@ -915,18 +916,38 @@ export class ChannelRegistry {
|
|
|
915
916
|
// ACCEPTED from here down: semantic processing only for admitted
|
|
916
917
|
// messages. §16.3 core-tag closure, then content conversion.
|
|
917
918
|
const coalesced = message.coalesce !== undefined && !!this.handleCoalescedIncoming;
|
|
919
|
+
// Only a coalescing retraction may be empty (pure withdrawal, RFC-006
|
|
920
|
+
// §6; the coalescer appends nothing for it). Any other message that
|
|
921
|
+
// shows the model nothing would wake it with no visible cause.
|
|
922
|
+
const emptyAllowed = coalesced && (message.coalesce as { retract?: unknown } | null)?.retract === true;
|
|
923
|
+
const rejectEmpty = () => {
|
|
924
|
+
console.error(`[channel-incoming-rejected] server=${serverId} channel=${message.channelId} messageId=${message.messageId} reason=empty-content`);
|
|
925
|
+
this.emitTraceFn({
|
|
926
|
+
type: 'mcpl:channel-incoming-rejected',
|
|
927
|
+
serverId,
|
|
928
|
+
channelId: message.channelId,
|
|
929
|
+
messageId: message.messageId,
|
|
930
|
+
reason: 'empty-content',
|
|
931
|
+
});
|
|
932
|
+
results.push({ messageId: message.messageId, accepted: false, reason: 'empty_content' });
|
|
933
|
+
};
|
|
918
934
|
if (coalesced) {
|
|
919
935
|
// RFC-006 §13: malformed content on a coalesced item is that item's
|
|
920
936
|
// failure, not the batch's — check the shape before converting.
|
|
921
937
|
try {
|
|
922
|
-
validateCoalescedContent(message.content);
|
|
938
|
+
validateCoalescedContent(message.content, undefined, { allowEmpty: emptyAllowed });
|
|
923
939
|
} catch (error) {
|
|
924
|
-
|
|
940
|
+
if (error instanceof EmptyContentError) rejectEmpty();
|
|
941
|
+
else results.push({ messageId: message.messageId, accepted: false, reason: 'coalesce_invalid' });
|
|
925
942
|
continue;
|
|
926
943
|
}
|
|
927
944
|
}
|
|
928
945
|
if (message.tags) message.tags = expandCoreTags(message.tags);
|
|
929
946
|
const convertedContent: ContentBlock[] = message.content.map(convertBlock);
|
|
947
|
+
if (!emptyAllowed && isVisiblyEmptyContent(convertedContent)) {
|
|
948
|
+
rejectEmpty();
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
930
951
|
|
|
931
952
|
// Track default publish channel (most recent ACCEPTED incoming) —
|
|
932
953
|
// deliberately after §14.5 validation: a rejected message from an
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
* a restarted host must NOT reuse generation 1's agent name, or it would
|
|
28
28
|
* reopen the previous engagement's Chronicle namespace and re-seed the
|
|
29
29
|
* template context on top of the old history.
|
|
30
|
+
*
|
|
31
|
+
* DEPRECATED (anima-research/agent-framework#235): per-channel conversation
|
|
32
|
+
* routing is being retired. Behavior is unchanged for now; the framework
|
|
33
|
+
* logs one `[deprecated]` line when it is configured. Removal is a
|
|
34
|
+
* follow-up.
|
|
30
35
|
*/
|
|
31
36
|
|
|
32
37
|
import type { ContextStrategy } from '@animalabs/context-manager';
|
|
@@ -49,6 +54,20 @@ export type TriggerRule = 'always' | 'mention';
|
|
|
49
54
|
* inferring on every message of a four-human group DM is a firehose). */
|
|
50
55
|
export type ChannelKind = 'dm' | 'groupDm' | 'channel';
|
|
51
56
|
|
|
57
|
+
/** Logged once by the framework when `FrameworkConfig.conversations` is set. */
|
|
58
|
+
export const CONVERSATION_ROUTING_DEPRECATION_NOTICE =
|
|
59
|
+
'Per-channel conversation routing (FrameworkConfig.conversations) is deprecated and will ' +
|
|
60
|
+
'be removed in a future release. Its \'mention\' bind/trigger rule reads ' +
|
|
61
|
+
'metadata.mentioned, which not every channel server sets (discord-mcpl does not), so on ' +
|
|
62
|
+
'such channels an @-mention neither binds a fork nor triggers a bound one. Routing still ' +
|
|
63
|
+
'works for now. See anima-research/agent-framework#235.';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Per-channel conversation routing is deprecated and will be
|
|
67
|
+
* removed (anima-research/agent-framework#235). Its 'mention' bind/trigger
|
|
68
|
+
* rule reads `metadata.mentioned`, which not every channel server sets.
|
|
69
|
+
* Behavior is unchanged for now.
|
|
70
|
+
*/
|
|
52
71
|
export interface ConversationRouterConfig {
|
|
53
72
|
/** Agent whose context seeds new forks (the "trunk"/warm checkpoint). */
|
|
54
73
|
templateAgent: string;
|
|
@@ -61,7 +80,15 @@ export interface ConversationRouterConfig {
|
|
|
61
80
|
* Defaults: dm 'always', groupDm 'mention', channel 'mention'. */
|
|
62
81
|
trigger?: { dm?: TriggerRule; groupDm?: TriggerRule; channel?: TriggerRule };
|
|
63
82
|
|
|
64
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Idle time before a binding expires and the fork is closed. Default 12h.
|
|
85
|
+
*
|
|
86
|
+
* Expiry is checked at most about once a minute, so the sweep usually
|
|
87
|
+
* notices an expired binding up to ~60s after the TTL elapses. That is the
|
|
88
|
+
* usual extra delay, not a deadline for closing the fork: the closure turn
|
|
89
|
+
* can be delayed further (e.g. while the host is quiesced, it stays queued
|
|
90
|
+
* until resume).
|
|
91
|
+
*/
|
|
65
92
|
idleTtlMs?: number;
|
|
66
93
|
|
|
67
94
|
/** Final system-initiated user message sent to a fork on expiry. */
|
|
@@ -116,6 +143,12 @@ export const DEFAULT_CLOSURE_PROMPT =
|
|
|
116
143
|
// ConversationRouter
|
|
117
144
|
// ============================================================================
|
|
118
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @deprecated Per-channel conversation routing is deprecated and will be
|
|
148
|
+
* removed (anima-research/agent-framework#235). Behavior is unchanged for
|
|
149
|
+
* now; the framework logs one `[deprecated]` line when it constructs a
|
|
150
|
+
* router from `FrameworkConfig.conversations`.
|
|
151
|
+
*/
|
|
119
152
|
export class ConversationRouter {
|
|
120
153
|
private config: ConversationRouterConfig;
|
|
121
154
|
|
|
@@ -128,6 +128,16 @@ export class FeatureSetManager {
|
|
|
128
128
|
/** Per-server state, keyed by server ID. */
|
|
129
129
|
private servers = new Map<string, ServerState>();
|
|
130
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param emitTrace Receives an `mcpl:feature-set-disabled` trace beside
|
|
133
|
+
* each §6.4 disablement's console line, so hosts that keep per-server
|
|
134
|
+
* logs from the trace bus see a set that config enabled but derivation
|
|
135
|
+
* turned off. Omit for scratch derivations (nothing is installed).
|
|
136
|
+
*/
|
|
137
|
+
constructor(
|
|
138
|
+
private readonly emitTrace?: (event: { type: string; [key: string]: unknown }) => void,
|
|
139
|
+
) {}
|
|
140
|
+
|
|
131
141
|
/**
|
|
132
142
|
* Initialize a server's feature set state from its capabilities and host config.
|
|
133
143
|
*
|
|
@@ -201,28 +211,43 @@ export class FeatureSetManager {
|
|
|
201
211
|
// resolution so it can only remove: nothing a declaration or a config
|
|
202
212
|
// says can supply a capability the grant lacks.
|
|
203
213
|
if (grant) {
|
|
214
|
+
// Named only when the operator listed it: with enabledFeatureSets
|
|
215
|
+
// omitted every declared set is selected, which is no surprise.
|
|
216
|
+
const explicitSelection = config?.enabledFeatureSets !== undefined;
|
|
217
|
+
const disable = (
|
|
218
|
+
name: string,
|
|
219
|
+
reason: 'invalid_uses' | 'missing_capabilities',
|
|
220
|
+
detail: string,
|
|
221
|
+
extra: { unrecognized?: string[]; missing?: string[] },
|
|
222
|
+
): void => {
|
|
223
|
+
const selectedByConfig = state.enabled.delete(name);
|
|
224
|
+
const note = explicitSelection && selectedByConfig ? ' — overrides enabledFeatureSets' : '';
|
|
225
|
+
console.error(`[mcpl] ${serverId}/${name} disabled: ${detail}${note}`);
|
|
226
|
+
this.emitTrace?.({
|
|
227
|
+
type: 'mcpl:feature-set-disabled',
|
|
228
|
+
serverId,
|
|
229
|
+
featureSet: name,
|
|
230
|
+
reason,
|
|
231
|
+
selectedByConfig,
|
|
232
|
+
...extra,
|
|
233
|
+
});
|
|
234
|
+
};
|
|
204
235
|
for (const [name, decl] of Object.entries(declared)) {
|
|
205
236
|
const uses = (decl as { uses?: unknown }).uses;
|
|
206
237
|
if (!Array.isArray(uses) || uses.length === 0) {
|
|
207
|
-
|
|
208
|
-
console.error(`[mcpl] ${serverId}/${name} disabled: invalid_uses (§6.4 — uses absent or empty)`);
|
|
209
|
-
}
|
|
238
|
+
disable(name, 'invalid_uses', 'invalid_uses (§6.4 — uses absent or empty)', { unrecognized: [] });
|
|
210
239
|
continue;
|
|
211
240
|
}
|
|
212
241
|
const unknown = uses.filter((u) => typeof u !== 'string' || !isKnownCapabilityPath(u));
|
|
213
242
|
if (unknown.length > 0) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
243
|
+
disable(name, 'invalid_uses', `invalid_uses (§6.4 — unrecognized: ${unknown.join(', ')})`, {
|
|
244
|
+
unrecognized: unknown.map((u) => (typeof u === 'string' ? u : JSON.stringify(u))),
|
|
245
|
+
});
|
|
218
246
|
continue;
|
|
219
247
|
}
|
|
220
248
|
const missing = (uses as string[]).filter((u) => !grant.has(u));
|
|
221
249
|
if (missing.length > 0) {
|
|
222
|
-
|
|
223
|
-
console.error(
|
|
224
|
-
`[mcpl] ${serverId}/${name} disabled: missing capabilities ${missing.join(', ')} (§6.4)`,
|
|
225
|
-
);
|
|
250
|
+
disable(name, 'missing_capabilities', `missing capabilities ${missing.join(', ')} (§6.4)`, { missing });
|
|
226
251
|
}
|
|
227
252
|
}
|
|
228
253
|
}
|
package/src/mcpl/index.ts
CHANGED
|
@@ -107,6 +107,8 @@ export {
|
|
|
107
107
|
|
|
108
108
|
// Server connection and registry
|
|
109
109
|
export { McplServerConnection } from './server-connection.js';
|
|
110
|
+
export type { McplConnectionCloseInfo } from './server-connection.js';
|
|
111
|
+
export type { TransportCloseInfo } from './transport.js';
|
|
110
112
|
export { McplServerRegistry, type McplCapabilityQuery } from './server-registry.js';
|
|
111
113
|
|
|
112
114
|
// Feature set management (permission layer)
|
|
@@ -158,7 +160,9 @@ export { InferenceRouter } from './inference-router.js';
|
|
|
158
160
|
export { ChannelRegistry } from './channel-registry.js';
|
|
159
161
|
|
|
160
162
|
// Per-channel conversation routing (fork-per-channel agents)
|
|
163
|
+
/** @deprecated Per-channel conversation routing is deprecated and will be removed (anima-research/agent-framework#235). */
|
|
161
164
|
export { ConversationRouter, DEFAULT_CLOSURE_PROMPT } from './conversation-router.js';
|
|
165
|
+
/** @deprecated Per-channel conversation routing is deprecated and will be removed (anima-research/agent-framework#235). */
|
|
162
166
|
export type {
|
|
163
167
|
ConversationRouterConfig,
|
|
164
168
|
ConversationBinding,
|
|
@@ -19,8 +19,13 @@
|
|
|
19
19
|
* emits NOTHING — the delivery path will bounce/retain it; a stream consumer
|
|
20
20
|
* must never see text that delivery would refuse to send.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
* - explicit: parse `>>` prefixes
|
|
22
|
+
* Three modes, mirroring proseRouting:
|
|
23
|
+
* - explicit: parse `>>` prefixes. A fresh turn's stream starts suppressed;
|
|
24
|
+
* one that resumes a turn starts at the turn's destination, and
|
|
25
|
+
* each round's end returns the stream to it (retarget).
|
|
26
|
+
* - hybrid: parse `>>>` envelopes, with the frozen turn locus as the
|
|
27
|
+
* fallback. An envelope with no target, or `>>>skip_reply`,
|
|
28
|
+
* holds back what follows until a later `>>>` names a target.
|
|
24
29
|
* - locus: NO prefix parsing (locus mode's `>>` lines are plain text);
|
|
25
30
|
* everything streams to the frozen turn locus.
|
|
26
31
|
*/
|
|
@@ -30,7 +35,10 @@ export interface RoutedDelta {
|
|
|
30
35
|
delta: string;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
type State = 'line-start' | '
|
|
38
|
+
type State = 'line-start' | 'indent' | 'maybe-prefix' | 'prefix-gap' | 'prefix-token' | 'after-token' | 'body';
|
|
39
|
+
|
|
40
|
+
/** Whitespace as the delivery path reads it (`trimStart()`, `\s`, `\S`). */
|
|
41
|
+
const isSpace = (ch: string): boolean => /\s/.test(ch);
|
|
34
42
|
|
|
35
43
|
export class ProseStreamRouter {
|
|
36
44
|
private target: string | null;
|
|
@@ -44,7 +52,9 @@ export class ProseStreamRouter {
|
|
|
44
52
|
private opts: {
|
|
45
53
|
/** explicit parses `>>`; hybrid parses `>>>` while retaining locus fallback. */
|
|
46
54
|
mode: 'explicit' | 'hybrid' | 'locus';
|
|
47
|
-
/** Initial destination: the frozen turn locus (locus mode)
|
|
55
|
+
/** Initial destination: the frozen turn locus (locus and hybrid mode); in
|
|
56
|
+
* explicit mode, null on a fresh turn, or the turn's destination on a
|
|
57
|
+
* stream that resumes one. */
|
|
48
58
|
initialTarget: string | null;
|
|
49
59
|
/** Resolve a `>>spec` to a channelId, or null (unresolved/ambiguous). */
|
|
50
60
|
resolve: (spec: string) => string | null;
|
|
@@ -59,6 +69,17 @@ export class ProseStreamRouter {
|
|
|
59
69
|
return this.accumulated;
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Point the stream at a destination decided outside it. Explicit mode does
|
|
74
|
+
* this at each round's end, with the turn's destination: a private round's
|
|
75
|
+
* prefixes, a `>>skip_reply` envelope and a target that didn't resolve all
|
|
76
|
+
* leave that destination as it was, though the stream followed them while
|
|
77
|
+
* the round was written.
|
|
78
|
+
*/
|
|
79
|
+
retarget(target: string | null): void {
|
|
80
|
+
this.target = target;
|
|
81
|
+
}
|
|
82
|
+
|
|
62
83
|
/**
|
|
63
84
|
* Drop everything routed so far and return to the constructed state.
|
|
64
85
|
*
|
|
@@ -82,23 +103,38 @@ export class ProseStreamRouter {
|
|
|
82
103
|
return coalesce(out);
|
|
83
104
|
}
|
|
84
105
|
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The end of a segment: one round's text, which delivery parses on its
|
|
108
|
+
* own. The pending line's decision is made now. Held line-start text can
|
|
109
|
+
* no longer become a prefix, so it is body. A prefix whose target is
|
|
110
|
+
* written but has no body takes effect, as delivery reads `>>#chan` with
|
|
111
|
+
* nothing after it. The next segment starts a new line.
|
|
112
|
+
*/
|
|
113
|
+
endSegment(): RoutedDelta[] {
|
|
89
114
|
const out: RoutedDelta[] = [];
|
|
90
|
-
if (this.state === '
|
|
115
|
+
if (this.state === 'indent') this.emit(this.held, out);
|
|
91
116
|
if (this.state === 'maybe-prefix') {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
// Hybrid's `>>>` with nothing after it is an envelope with no target:
|
|
118
|
+
// delivery bounces it and holds back what follows. Anything else held
|
|
119
|
+
// is text to delivery, explicit's bare `>>` included.
|
|
120
|
+
const arrows = this.held.trimStart();
|
|
121
|
+
if (this.opts.mode === 'hybrid' && arrows === '>>>') this.target = null;
|
|
122
|
+
else this.emit(this.held, out);
|
|
95
123
|
}
|
|
96
|
-
//
|
|
124
|
+
// Hybrid's `>>>` and a gap with nothing after it: no target either.
|
|
125
|
+
if (this.state === 'prefix-gap') this.target = null;
|
|
126
|
+
if (this.state === 'prefix-token') this.applyTarget();
|
|
97
127
|
this.held = '';
|
|
98
|
-
this.
|
|
128
|
+
this.token = '';
|
|
129
|
+
if (this.opts.mode !== 'locus') this.state = 'line-start';
|
|
99
130
|
return coalesce(out);
|
|
100
131
|
}
|
|
101
132
|
|
|
133
|
+
/** End of stream: the last segment ends. */
|
|
134
|
+
finish(): RoutedDelta[] {
|
|
135
|
+
return this.endSegment();
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
private step(ch: string, out: RoutedDelta[]): void {
|
|
103
139
|
switch (this.state) {
|
|
104
140
|
case 'body':
|
|
@@ -111,8 +147,11 @@ export class ProseStreamRouter {
|
|
|
111
147
|
return;
|
|
112
148
|
|
|
113
149
|
case 'line-start':
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
// Indentation before a prefix is held until the line's decision: the
|
|
151
|
+
// delivery path reads an indented `>>` (explicit) or `>>>` (hybrid)
|
|
152
|
+
// line as a prefix too, after any leading whitespace.
|
|
153
|
+
if (ch !== '\n' && isSpace(ch)) {
|
|
154
|
+
this.state = 'indent';
|
|
116
155
|
this.held = ch;
|
|
117
156
|
return;
|
|
118
157
|
}
|
|
@@ -121,8 +160,8 @@ export class ProseStreamRouter {
|
|
|
121
160
|
this.step(ch, out);
|
|
122
161
|
return;
|
|
123
162
|
|
|
124
|
-
case '
|
|
125
|
-
if (ch
|
|
163
|
+
case 'indent':
|
|
164
|
+
if (ch !== '\n' && isSpace(ch)) { this.held += ch; return; }
|
|
126
165
|
if (ch === '>') { this.held += ch; this.state = 'maybe-prefix'; return; }
|
|
127
166
|
this.emit(this.held, out);
|
|
128
167
|
this.held = '';
|
|
@@ -132,7 +171,7 @@ export class ProseStreamRouter {
|
|
|
132
171
|
|
|
133
172
|
case 'maybe-prefix': {
|
|
134
173
|
const prefix = this.opts.mode === 'hybrid' ? '>>>' : '>>';
|
|
135
|
-
const arrows = this.
|
|
174
|
+
const arrows = this.held.trimStart();
|
|
136
175
|
if (ch === '>' && arrows.length < prefix.length) {
|
|
137
176
|
this.held += '>';
|
|
138
177
|
return;
|
|
@@ -143,11 +182,22 @@ export class ProseStreamRouter {
|
|
|
143
182
|
this.state = 'prefix-gap';
|
|
144
183
|
return;
|
|
145
184
|
}
|
|
146
|
-
if (ch
|
|
147
|
-
if (this.opts.mode === 'explicit')
|
|
185
|
+
if (isSpace(ch)) {
|
|
186
|
+
if (this.opts.mode === 'explicit') {
|
|
187
|
+
// `>>` and then whitespace is a quoted arrow: body text, since
|
|
188
|
+
// delivery's prefix needs a target right after the arrows.
|
|
189
|
+
this.emit(this.held, out);
|
|
190
|
+
} else {
|
|
191
|
+
// `>>>` and then whitespace other than a space or tab (a
|
|
192
|
+
// newline included) is an envelope with no target. Delivery
|
|
193
|
+
// bounces it and holds back what follows until a later `>>>`
|
|
194
|
+
// names a target.
|
|
195
|
+
this.target = null;
|
|
196
|
+
}
|
|
197
|
+
// The whitespace itself is body, a newline included.
|
|
148
198
|
this.held = '';
|
|
149
|
-
this.state =
|
|
150
|
-
|
|
199
|
+
this.state = 'body';
|
|
200
|
+
this.step(ch, out);
|
|
151
201
|
return;
|
|
152
202
|
}
|
|
153
203
|
this.state = 'prefix-token';
|
|
@@ -165,6 +215,9 @@ export class ProseStreamRouter {
|
|
|
165
215
|
case 'prefix-gap':
|
|
166
216
|
if (ch === ' ' || ch === '\t') return;
|
|
167
217
|
if (ch === '\n') { this.target = null; this.state = 'line-start'; return; }
|
|
218
|
+
// Other whitespace can't start hybrid's target (`[ \t]*\S+`): an
|
|
219
|
+
// envelope with no target, as above.
|
|
220
|
+
if (isSpace(ch)) { this.target = null; this.state = 'body'; return; }
|
|
168
221
|
this.state = 'prefix-token';
|
|
169
222
|
this.token = ch;
|
|
170
223
|
return;
|
|
@@ -172,6 +225,9 @@ export class ProseStreamRouter {
|
|
|
172
225
|
case 'prefix-token':
|
|
173
226
|
if (ch === ' ' || ch === '\t') { this.applyTarget(); this.state = 'after-token'; this.sawBang = false; return; }
|
|
174
227
|
if (ch === '\n') { this.applyTarget(); this.state = 'line-start'; return; }
|
|
228
|
+
// Any other whitespace ends the token too (`\S+`), and starts the
|
|
229
|
+
// body: the grammar's gap after a token is spaces and tabs only.
|
|
230
|
+
if (isSpace(ch)) { this.applyTarget(); this.state = 'body'; this.step(ch, out); return; }
|
|
175
231
|
this.token += ch;
|
|
176
232
|
return;
|
|
177
233
|
|