@animalabs/agent-framework 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +340 -22
- package/dist/src/admission/admit.d.ts +18 -0
- package/dist/src/admission/admit.d.ts.map +1 -1
- package/dist/src/admission/admit.js +16 -3
- package/dist/src/admission/admit.js.map +1 -1
- package/dist/src/agent.d.ts +208 -80
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +345 -113
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +50 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +4 -20
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/append-all.d.ts +5 -0
- package/dist/src/append-all.d.ts.map +1 -0
- package/dist/src/append-all.js +8 -0
- package/dist/src/append-all.js.map +1 -0
- package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +24 -0
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/config-checks.d.ts +7 -0
- package/dist/src/config-checks.d.ts.map +1 -1
- package/dist/src/config-checks.js +14 -0
- package/dist/src/config-checks.js.map +1 -1
- package/dist/src/context-manager-errors.d.ts +25 -0
- package/dist/src/context-manager-errors.d.ts.map +1 -0
- package/dist/src/context-manager-errors.js +31 -0
- package/dist/src/context-manager-errors.js.map +1 -0
- package/dist/src/context-options.d.ts +70 -0
- package/dist/src/context-options.d.ts.map +1 -0
- package/dist/src/context-options.js +90 -0
- package/dist/src/context-options.js.map +1 -0
- package/dist/src/cut-off-round.js +4 -4
- package/dist/src/deferred-import.d.ts +93 -0
- package/dist/src/deferred-import.d.ts.map +1 -0
- package/dist/src/deferred-import.js +177 -0
- package/dist/src/deferred-import.js.map +1 -0
- package/dist/src/dendrite/audience.d.ts +223 -0
- package/dist/src/dendrite/audience.d.ts.map +1 -0
- package/dist/src/dendrite/audience.js +202 -0
- package/dist/src/dendrite/audience.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +5 -3
- package/dist/src/dendrite/index.d.ts.map +1 -1
- package/dist/src/dendrite/index.js +2 -1
- package/dist/src/dendrite/index.js.map +1 -1
- package/dist/src/dendrite/presets.d.ts +10 -5
- package/dist/src/dendrite/presets.d.ts.map +1 -1
- package/dist/src/dendrite/presets.js +17 -5
- package/dist/src/dendrite/presets.js.map +1 -1
- package/dist/src/dendrite/registry.d.ts +62 -7
- package/dist/src/dendrite/registry.d.ts.map +1 -1
- package/dist/src/dendrite/registry.js +183 -12
- package/dist/src/dendrite/registry.js.map +1 -1
- package/dist/src/dendrite/types.d.ts +91 -8
- package/dist/src/dendrite/types.d.ts.map +1 -1
- package/dist/src/failure-text.d.ts +36 -0
- package/dist/src/failure-text.d.ts.map +1 -0
- package/dist/src/failure-text.js +89 -0
- package/dist/src/failure-text.js.map +1 -0
- package/dist/src/foreign-rounds.d.ts +69 -0
- package/dist/src/foreign-rounds.d.ts.map +1 -0
- package/dist/src/foreign-rounds.js +152 -0
- package/dist/src/foreign-rounds.js.map +1 -0
- package/dist/src/framework.d.ts +2064 -842
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +8586 -3739
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +125 -21
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +487 -72
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +4 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -1
- package/dist/src/gate/reply-judge.js +12 -3
- package/dist/src/gate/reply-judge.js.map +1 -1
- package/dist/src/gate/rule-scope.d.ts +30 -0
- package/dist/src/gate/rule-scope.d.ts.map +1 -0
- package/dist/src/gate/rule-scope.js +41 -0
- package/dist/src/gate/rule-scope.js.map +1 -0
- package/dist/src/gate/suppress-script-worker.d.ts +2 -0
- package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
- package/dist/src/gate/suppress-script-worker.js +103 -0
- package/dist/src/gate/suppress-script-worker.js.map +1 -0
- package/dist/src/gate/suppress-scripts.d.ts +105 -0
- package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
- package/dist/src/gate/suppress-scripts.js +403 -0
- package/dist/src/gate/suppress-scripts.js.map +1 -0
- package/dist/src/gate/suppression-review.d.ts +143 -0
- package/dist/src/gate/suppression-review.d.ts.map +1 -0
- package/dist/src/gate/suppression-review.js +463 -0
- package/dist/src/gate/suppression-review.js.map +1 -0
- package/dist/src/gate/suppression-stats.d.ts +125 -0
- package/dist/src/gate/suppression-stats.d.ts.map +1 -0
- package/dist/src/gate/suppression-stats.js +214 -0
- package/dist/src/gate/suppression-stats.js.map +1 -0
- package/dist/src/gate/types.d.ts +142 -7
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/gate/wake-onboarding.d.ts +119 -0
- package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
- package/dist/src/gate/wake-onboarding.js +277 -0
- package/dist/src/gate/wake-onboarding.js.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +1 -1
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +31 -23
- package/dist/src/held-writes.d.ts.map +1 -1
- package/dist/src/held-writes.js +44 -59
- package/dist/src/held-writes.js.map +1 -1
- package/dist/src/index.d.ts +6 -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/inference-bounds.d.ts +2 -1
- package/dist/src/inference-bounds.d.ts.map +1 -1
- package/dist/src/inference-bounds.js +9 -6
- package/dist/src/inference-bounds.js.map +1 -1
- package/dist/src/jsonrpc-envelope.d.ts +69 -0
- package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
- package/dist/src/jsonrpc-envelope.js +66 -0
- package/dist/src/jsonrpc-envelope.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +188 -28
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +426 -70
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
- package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
- package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
- package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
- package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
- package/dist/src/mcpl/feature-set-manager.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +2 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/inference-router.d.ts +6 -6
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +109 -15
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +593 -86
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +15 -9
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +132 -22
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +105 -15
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/push-handler.d.ts +9 -7
- package/dist/src/mcpl/push-handler.d.ts.map +1 -1
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +50 -28
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +165 -72
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
- package/dist/src/mcpl/tool-classes.js +3 -0
- package/dist/src/mcpl/tool-classes.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +10 -6
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +7 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +2 -1
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +33 -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 +24 -1
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +68 -18
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts +45 -8
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +238 -87
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/health/index.js +2 -2
- package/dist/src/modules/health/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +59 -0
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +432 -67
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/history/semantic.d.ts +6 -5
- package/dist/src/modules/history/semantic.d.ts.map +1 -1
- package/dist/src/modules/history/semantic.js +18 -25
- package/dist/src/modules/history/semantic.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +1 -1
- package/dist/src/modules/workspace/index.d.ts +28 -7
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +80 -25
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +2 -1
- 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 +3 -2
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +20 -9
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +20 -9
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/operator-log.d.ts +36 -3
- package/dist/src/operator-log.d.ts.map +1 -1
- package/dist/src/operator-log.js +2 -2
- package/dist/src/operator-log.js.map +1 -1
- package/dist/src/presentation.d.ts +747 -0
- package/dist/src/presentation.d.ts.map +1 -0
- package/dist/src/presentation.js +1437 -0
- package/dist/src/presentation.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +15 -11
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +16 -12
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/read-marks.d.ts +192 -0
- package/dist/src/read-marks.d.ts.map +1 -0
- package/dist/src/read-marks.js +242 -0
- package/dist/src/read-marks.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +5 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +8 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/recovery/offline-branch.d.ts +91 -6
- package/dist/src/recovery/offline-branch.d.ts.map +1 -1
- package/dist/src/recovery/offline-branch.js +277 -32
- package/dist/src/recovery/offline-branch.js.map +1 -1
- package/dist/src/recovery/recover-cli.js +24 -4
- package/dist/src/recovery/recover-cli.js.map +1 -1
- package/dist/src/send-tools.d.ts +5 -9
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +40 -7
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/stored-injections.d.ts +2 -2
- package/dist/src/stored-injections.js +2 -2
- package/dist/src/stored-removal.d.ts +17 -4
- package/dist/src/stored-removal.d.ts.map +1 -1
- package/dist/src/stored-removal.js +29 -5
- package/dist/src/stored-removal.js.map +1 -1
- package/dist/src/supplied-text.d.ts +1 -1
- package/dist/src/supplied-text.js +3 -3
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/thinking-drops.d.ts +2 -0
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +38 -9
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +12 -1
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +42 -8
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +1170 -81
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +3732 -369
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +114 -17
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +89 -28
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/tune-out/window-anchor.d.ts +143 -0
- package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
- package/dist/src/tune-out/window-anchor.js +202 -0
- package/dist/src/tune-out/window-anchor.js.map +1 -0
- package/dist/src/types/agent.d.ts +121 -25
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +2 -0
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +72 -17
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/inference-log.d.ts +1 -0
- package/dist/src/types/inference-log.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +180 -69
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +573 -39
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/admission/admit.ts +32 -3
- package/src/agent.ts +392 -132
- package/src/api/mcp-server-core.ts +54 -17
- package/src/api/server.ts +4 -23
- package/src/append-all.ts +6 -0
- package/src/code-execution/isolate/scripts.ts +28 -3
- package/src/config-checks.ts +14 -0
- package/src/context-manager-errors.ts +37 -0
- package/src/context-options.ts +106 -0
- package/src/cut-off-round.ts +4 -4
- package/src/deferred-import.ts +239 -0
- package/src/dendrite/audience.ts +315 -0
- package/src/dendrite/index.ts +14 -2
- package/src/dendrite/presets.ts +18 -6
- package/src/dendrite/registry.ts +213 -14
- package/src/dendrite/types.ts +92 -8
- package/src/failure-text.ts +88 -0
- package/src/foreign-rounds.ts +199 -0
- package/src/framework.ts +8796 -3813
- package/src/gate/event-gate.ts +518 -91
- package/src/gate/index.ts +3 -0
- package/src/gate/reply-judge.ts +12 -3
- package/src/gate/rule-scope.ts +45 -0
- package/src/gate/suppress-script-worker.ts +89 -0
- package/src/gate/suppress-scripts.ts +414 -0
- package/src/gate/suppression-review.ts +517 -0
- package/src/gate/suppression-stats.ts +314 -0
- package/src/gate/types.ts +148 -7
- package/src/gate/wake-onboarding.ts +309 -0
- package/src/heartbeat/heartbeat.ts +1 -1
- package/src/held-writes.ts +49 -60
- package/src/index.ts +10 -2
- package/src/inference-bounds.ts +11 -6
- package/src/jsonrpc-envelope.ts +81 -0
- package/src/mcpl/channel-registry.ts +513 -116
- package/src/mcpl/checkpoint-manager.ts +3 -2
- package/src/mcpl/feature-set-manager.ts +2 -1
- package/src/mcpl/inbound-source.ts +2 -1
- package/src/mcpl/inference-router.ts +7 -4
- package/src/mcpl/modern-connection.ts +629 -91
- package/src/mcpl/protocol-family.ts +15 -9
- package/src/mcpl/push-coalescer.ts +193 -34
- package/src/mcpl/push-handler.ts +10 -5
- package/src/mcpl/server-connection.ts +169 -75
- package/src/mcpl/tool-classes.ts +3 -0
- package/src/mcpl/tool-presence.ts +10 -5
- package/src/mcpl/toolset-host.ts +7 -6
- package/src/mcpl/toolsets-module.ts +2 -1
- package/src/mcpl/types.ts +33 -6
- package/src/module-registry.ts +73 -17
- package/src/modules/discord/index.ts +230 -82
- package/src/modules/health/index.ts +2 -2
- package/src/modules/history/index.ts +439 -70
- package/src/modules/history/semantic.ts +18 -24
- package/src/modules/workspace/disk-agreement.ts +1 -1
- package/src/modules/workspace/index.ts +89 -26
- package/src/modules/workspace/observe.ts +2 -1
- package/src/modules/workspace/reconcile.ts +3 -2
- package/src/notice-tags.ts +21 -9
- package/src/operator-log.ts +36 -3
- package/src/presentation.ts +1678 -0
- package/src/prose-drafts.ts +16 -12
- package/src/read-marks.ts +328 -0
- package/src/reasoning-loss.ts +7 -0
- package/src/recovery/offline-branch.ts +350 -35
- package/src/recovery/recover-cli.ts +23 -4
- package/src/send-tools.ts +40 -7
- package/src/sent-rows.ts +1 -1
- package/src/stored-injections.ts +2 -2
- package/src/stored-removal.ts +31 -7
- package/src/supplied-text.ts +3 -3
- package/src/thinking-drops.ts +2 -0
- package/src/tool-presentation.ts +34 -9
- package/src/tool-result-guard.ts +41 -7
- package/src/tune-out/coordinator.ts +4121 -472
- package/src/tune-out/tools.ts +162 -44
- package/src/tune-out/window-anchor.ts +277 -0
- package/src/types/agent.ts +122 -25
- package/src/types/events.ts +2 -0
- package/src/types/framework.ts +73 -17
- package/src/types/inference-log.ts +1 -0
- package/src/types/module.ts +181 -68
- package/src/types/trace.ts +607 -43
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wake onboarding: an MCPL server whose events the agent's wake rules never
|
|
3
|
+
* decided about.
|
|
4
|
+
*
|
|
5
|
+
* A server's events are often skipped by a rule written for something else —
|
|
6
|
+
* a catch-all with no `source` (a Discord-era `discord-ambient`) or the gate's
|
|
7
|
+
* default — so a game that waits on the agent's move, or a message addressed
|
|
8
|
+
* to it, stalls without anyone having chosen that. MCPL RFC-001 §5 lets a
|
|
9
|
+
* server suggest how its tagged events should be treated, and §5.2 forbids a
|
|
10
|
+
* host from applying suggestions it was not told to apply. This module is the
|
|
11
|
+
* missing step between the two: the host notices the first undecided event
|
|
12
|
+
* from a server, offers the agent that server's suggestions once, and records
|
|
13
|
+
* the agent's answer — accepted (all of them or some) or declined — keyed to
|
|
14
|
+
* the server's declared ontology, so it asks again only when the ontology
|
|
15
|
+
* changes.
|
|
16
|
+
*
|
|
17
|
+
* An event is *undecided* when the gate let it go by without waking
|
|
18
|
+
* (`defer`/`skip`) by its default, or by a rule that names neither the event's
|
|
19
|
+
* source nor its channel nor any of its tags. A rule that does — Linn's
|
|
20
|
+
* `touche-quiet`, a channel's debounce — is a decision, and this module leaves
|
|
21
|
+
* it alone. So does the gate's sleep, gate.js, and every rule that wakes later
|
|
22
|
+
* (debounce, judge, a rate limit or sampler).
|
|
23
|
+
*
|
|
24
|
+
* Pure functions here; the framework wires the notice, the wake and the tools.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { dirname } from 'node:path';
|
|
28
|
+
import { stableHash } from '../stable-hash.js';
|
|
29
|
+
import { firstCatchAll, ruleDecides } from './rule-scope.js';
|
|
30
|
+
import type { GateBehavior, GateDecision, GatePolicy } from './types.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tags that say an event is addressed to the agent or waiting on it (MCPL
|
|
34
|
+
* RFC-001 `chat:addressed`, RFC-014 `surface:turn`/`surface:alert`; not
|
|
35
|
+
* `surface:invite`, which only suggests). The first undecided event carrying
|
|
36
|
+
* one wakes the agent to decide; any other undecided event leaves its offer
|
|
37
|
+
* for the agent's next turn.
|
|
38
|
+
*/
|
|
39
|
+
export const ATTENTION_TAGS: readonly string[] = ['chat:addressed', 'surface:turn', 'surface:alert'];
|
|
40
|
+
|
|
41
|
+
/** Accepted rules are named `suggested:<server>:<n>`: the name is the attribution. */
|
|
42
|
+
export const SUGGESTED_RULE_PREFIX = 'suggested:';
|
|
43
|
+
export const suggestedRuleName = (server: string, n: number): string => `${SUGGESTED_RULE_PREFIX}${server}:${n}`;
|
|
44
|
+
/** Exactly `suggested:<server>:<n>`: a server id that another extends (`games`, `games:beta`) never claims the other's rules. */
|
|
45
|
+
export const isSuggestedRuleOf = (name: string, server: string): boolean =>
|
|
46
|
+
name.startsWith(`${SUGGESTED_RULE_PREFIX}${server}:`) && /^\d+$/.test(name.slice(SUGGESTED_RULE_PREFIX.length + server.length + 1));
|
|
47
|
+
|
|
48
|
+
/** One of a server's suggestions as the host offers it: the server's words and the rule it becomes. */
|
|
49
|
+
export interface OfferedRule {
|
|
50
|
+
/** 1-based: the number `wake_accept_suggestions` takes it by. */
|
|
51
|
+
n: number;
|
|
52
|
+
/** The feature set that declared it; empty for the host's own fallback. */
|
|
53
|
+
featureSet: string;
|
|
54
|
+
match: { tagsAny?: string[]; tagsAll?: string[]; tagsNone?: string[] };
|
|
55
|
+
/** The gate behavior the suggestion becomes. */
|
|
56
|
+
behavior: GateBehavior;
|
|
57
|
+
/** The server's own word for it (`immediate`, `{ debounce: 60000 }`, …). */
|
|
58
|
+
said: unknown;
|
|
59
|
+
/** The server's account of what the rule is for and what declining costs. */
|
|
60
|
+
rationale?: string;
|
|
61
|
+
/** The host wrote it: the server suggested nothing it could use. */
|
|
62
|
+
fallback?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ServerOffer {
|
|
66
|
+
server: string;
|
|
67
|
+
/** What the decision is about: a digest of the server's declared ontologies ('none' without one). */
|
|
68
|
+
digest: string;
|
|
69
|
+
rules: OfferedRule[];
|
|
70
|
+
/** Descriptions the server gave of the tags its rules name. */
|
|
71
|
+
tagDescriptions: Record<string, string>;
|
|
72
|
+
/** Suggestions in the declaration the host could not translate, as given. */
|
|
73
|
+
untranslated: unknown[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type Ontology = {
|
|
77
|
+
tags?: Record<string, unknown>;
|
|
78
|
+
suggestedTreatment?: unknown;
|
|
79
|
+
/** RFC-001 revision 1's name for suggestedTreatment; still accepted. */
|
|
80
|
+
defaultTreatment?: unknown;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const DEBOUNCE_MIN_MS = 100;
|
|
84
|
+
const DEBOUNCE_MAX_MS = 300_000;
|
|
85
|
+
const THROTTLE_DEFAULT_MS = 60_000;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A suggested behavior (RFC-001 §6's vocabulary, or the gate's own names) as a
|
|
89
|
+
* gate behavior; null for one with no faithful translation.
|
|
90
|
+
*/
|
|
91
|
+
export function translateBehavior(said: unknown): GateBehavior | null {
|
|
92
|
+
if (said === 'immediate' || said === 'always') return 'always';
|
|
93
|
+
if (said === 'mute' || said === 'defer' || said === 'skip') return 'defer';
|
|
94
|
+
if (said === 'throttle') return { rate_limit: { tokens: 1, refillIntervalMs: THROTTLE_DEFAULT_MS } };
|
|
95
|
+
if (said && typeof said === 'object' && !Array.isArray(said)) {
|
|
96
|
+
const o = said as Record<string, unknown>;
|
|
97
|
+
if (typeof o.debounce === 'number' && Number.isFinite(o.debounce)) {
|
|
98
|
+
return { debounce: Math.min(DEBOUNCE_MAX_MS, Math.max(DEBOUNCE_MIN_MS, Math.round(o.debounce))) };
|
|
99
|
+
}
|
|
100
|
+
const perMs = (o.throttle as Record<string, unknown> | undefined)?.perMs;
|
|
101
|
+
if (typeof perMs === 'number' && perMs > 0) return { rate_limit: { tokens: 1, refillIntervalMs: Math.round(perMs) } };
|
|
102
|
+
const every = (o.sample as Record<string, unknown> | undefined)?.every;
|
|
103
|
+
if (typeof every === 'number' && Number.isInteger(every) && every > 0) return { passive_sample: { every } };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const tagList = (v: unknown): string[] | undefined => {
|
|
109
|
+
if (!Array.isArray(v)) return undefined;
|
|
110
|
+
const tags = v.filter((t): t is string => typeof t === 'string' && t.length > 0);
|
|
111
|
+
return tags.length > 0 ? tags : undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A server's suggestions from its declared feature sets (null: none declared).
|
|
116
|
+
* Feature sets in name order; in each, its ordered rules as declared, then
|
|
117
|
+
* per-tag suggestions no ordered rule already makes, by tag. A server that suggests nothing the host can
|
|
118
|
+
* use gets one host-written rule: wake for its events tagged as addressed to
|
|
119
|
+
* the agent or waiting on it, nothing else.
|
|
120
|
+
*/
|
|
121
|
+
export function offerFor(server: string, declared: Record<string, { tagOntology?: unknown }> | null): ServerOffer {
|
|
122
|
+
const rules: OfferedRule[] = [];
|
|
123
|
+
const tagDescriptions: Record<string, string> = {};
|
|
124
|
+
const untranslated: unknown[] = [];
|
|
125
|
+
const ontologies: Record<string, unknown> = {};
|
|
126
|
+
const seen = new Set<string>();
|
|
127
|
+
const add = (featureSet: string, match: OfferedRule['match'], said: unknown, rationale?: unknown) => {
|
|
128
|
+
const behavior = translateBehavior(said);
|
|
129
|
+
if (!behavior) { untranslated.push({ featureSet, ...match, behavior: said }); return; }
|
|
130
|
+
const key = stableHash({ match, behavior });
|
|
131
|
+
if (seen.has(key)) return;
|
|
132
|
+
seen.add(key);
|
|
133
|
+
rules.push({
|
|
134
|
+
n: rules.length + 1, featureSet, match, behavior, said,
|
|
135
|
+
...(typeof rationale === 'string' && rationale.trim() ? { rationale: rationale.trim() } : {}),
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
for (const featureSet of Object.keys(declared ?? {}).sort()) {
|
|
140
|
+
const ontology = declared![featureSet]?.tagOntology as Ontology | undefined;
|
|
141
|
+
if (!ontology || typeof ontology !== 'object') continue;
|
|
142
|
+
ontologies[featureSet] = ontology;
|
|
143
|
+
const tags = ontology.tags && typeof ontology.tags === 'object' ? ontology.tags : {};
|
|
144
|
+
const ordered = Array.isArray(ontology.suggestedTreatment) ? ontology.suggestedTreatment
|
|
145
|
+
: Array.isArray(ontology.defaultTreatment) ? ontology.defaultTreatment : [];
|
|
146
|
+
for (const raw of ordered) {
|
|
147
|
+
if (!raw || typeof raw !== 'object') continue;
|
|
148
|
+
const r = raw as Record<string, unknown>;
|
|
149
|
+
const match = {
|
|
150
|
+
...(tagList(r.tagsAny) ? { tagsAny: tagList(r.tagsAny) } : {}),
|
|
151
|
+
...(tagList(r.tagsAll) ? { tagsAll: tagList(r.tagsAll) } : {}),
|
|
152
|
+
...(tagList(r.tagsNone) ? { tagsNone: tagList(r.tagsNone) } : {}),
|
|
153
|
+
};
|
|
154
|
+
// A rule that names no tag would take every event the server sends: that is the agent's call to write, not an offer.
|
|
155
|
+
if (!match.tagsAny && !match.tagsAll) { untranslated.push({ featureSet, ...r }); continue; }
|
|
156
|
+
add(featureSet, match, r.behavior, r.rationale);
|
|
157
|
+
}
|
|
158
|
+
// Per-tag suggestions in tag order (UTF-8 bytes, as MCPL RFC-001 §5.2 and SPEC §16.5 order them):
|
|
159
|
+
// a JSON object's key order is no order at all.
|
|
160
|
+
const byTag = Object.keys(tags).sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
|
|
161
|
+
for (const tag of byTag) {
|
|
162
|
+
const descriptor = tags[tag];
|
|
163
|
+
const d = descriptor as Record<string, unknown> | null;
|
|
164
|
+
if (!d || typeof d !== 'object') continue;
|
|
165
|
+
if (typeof d.desc === 'string') tagDescriptions[tag] = d.desc;
|
|
166
|
+
if (d.suggestedTreatment !== undefined) add(featureSet, { tagsAny: [tag] }, d.suggestedTreatment, d.rationale);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (rules.length === 0) {
|
|
171
|
+
rules.push({
|
|
172
|
+
n: 1, featureSet: '', match: { tagsAny: [...ATTENTION_TAGS] }, behavior: 'always', said: 'immediate', fallback: true,
|
|
173
|
+
rationale: 'This server suggests nothing the host can use. This rule wakes you for its events marked as addressed to you or waiting on you, and leaves the rest as they are.',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const named = new Set(rules.flatMap((r) => [...(r.match.tagsAny ?? []), ...(r.match.tagsAll ?? [])]));
|
|
177
|
+
for (const tag of Object.keys(tagDescriptions)) if (!named.has(tag)) delete tagDescriptions[tag];
|
|
178
|
+
const digest = Object.keys(ontologies).length > 0 ? stableHash({ server, ontologies }) : 'none';
|
|
179
|
+
return { server, digest, rules, tagDescriptions, untranslated };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { ruleDecides } from './rule-scope.js';
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Whether the gate let an event go by without anyone having decided to: it did
|
|
186
|
+
* not wake and will not (`defer`/`skip`), by the default or by a rule that
|
|
187
|
+
* doesn't decide about it (ruleDecides). `policy` is the matched rule, as in
|
|
188
|
+
* force; undefined when the decision named none or it can't be found.
|
|
189
|
+
*/
|
|
190
|
+
export function isUndecided(decision: GateDecision, policy: GatePolicy | undefined): boolean {
|
|
191
|
+
if (decision.trigger) return false;
|
|
192
|
+
if (decision.behavior !== 'skip' && decision.behavior !== 'defer') return false;
|
|
193
|
+
if (decision.policyName === null) return true;
|
|
194
|
+
if (decision.policyName === 'asleep' || decision.policyName === 'gate.js') return false;
|
|
195
|
+
return policy !== undefined && !ruleDecides(policy);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const carriesAttention = (tags: readonly string[] | undefined): boolean => (tags ?? []).some((t) => ATTENTION_TAGS.includes(t));
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Where an accepted rule goes: ahead of the first rule that would let the
|
|
202
|
+
* server's events by without deciding about them (a catch-all that defers or
|
|
203
|
+
* skips), so the rules the agent wrote for this server, or for a channel or
|
|
204
|
+
* tags, keep their place before it, and so do catch-alls that wake (a mention
|
|
205
|
+
* rule on metadata): a muted suggestion can't swallow an explicit mention.
|
|
206
|
+
* Null: there is none; the rule goes last, ahead of the default. (Accepted
|
|
207
|
+
* rules name their source, so they are never the catch-all.)
|
|
208
|
+
*/
|
|
209
|
+
export const placementFor = (policies: readonly GatePolicy[]): string | null => firstCatchAll(policies, { lettingBy: true });
|
|
210
|
+
|
|
211
|
+
/** The gate rules accepting `rules` of `offer` writes. */
|
|
212
|
+
export function rulesToWrite(offer: ServerOffer, accepted: readonly OfferedRule[]): GatePolicy[] {
|
|
213
|
+
return accepted.map((r) => ({
|
|
214
|
+
name: suggestedRuleName(offer.server, r.n),
|
|
215
|
+
match: { source: offer.server, ...r.match },
|
|
216
|
+
behavior: r.behavior,
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------- decisions
|
|
221
|
+
|
|
222
|
+
/** The least time between two offers for one server (WakeDecisions.mayOffer). */
|
|
223
|
+
export const OFFER_FLOOR_MS = 24 * 60 * 60 * 1000;
|
|
224
|
+
|
|
225
|
+
export type DecisionState = 'offered' | 'accepted' | 'declined';
|
|
226
|
+
export interface DecisionRecord {
|
|
227
|
+
digest: string;
|
|
228
|
+
state: DecisionState;
|
|
229
|
+
/** The rule names an acceptance wrote (kept while a newer ontology is being asked about). */
|
|
230
|
+
rules?: string[];
|
|
231
|
+
/** The digest whose numbered list the agent was last shown, when newer than `digest` (a drift refusal re-showed it). */
|
|
232
|
+
shown?: string;
|
|
233
|
+
at: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The agent's decisions per server, beside gate.json (`wake-decisions.json`).
|
|
238
|
+
* Written whole, through a temporary file; one that can't be read is set aside
|
|
239
|
+
* and starts empty, so a damaged file costs a repeated offer, never a crash.
|
|
240
|
+
*/
|
|
241
|
+
export class WakeDecisions {
|
|
242
|
+
private records: Record<string, DecisionRecord> = {};
|
|
243
|
+
|
|
244
|
+
constructor(private readonly path: string, private readonly log: (message: string) => void = () => undefined) {
|
|
245
|
+
if (!existsSync(path)) return;
|
|
246
|
+
try {
|
|
247
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as { servers?: Record<string, DecisionRecord> };
|
|
248
|
+
for (const [server, r] of Object.entries(parsed.servers ?? {})) {
|
|
249
|
+
if (r && typeof r.digest === 'string' && (r.state === 'offered' || r.state === 'accepted' || r.state === 'declined')) this.records[server] = r;
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const aside = `${path}.unreadable-${Date.now()}`;
|
|
253
|
+
try { renameSync(path, aside); } catch { /* leave it */ }
|
|
254
|
+
this.log(`wake decisions: ${path} could not be read (${(error as Error).message}); set aside as ${aside}, starting empty`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
get(server: string): DecisionRecord | undefined { return this.records[server]; }
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Whether an offer for `server` may go out now: never twice for the same
|
|
262
|
+
* digest, answered or not, and not within OFFER_FLOOR_MS of the server's
|
|
263
|
+
* last offer or decision even when its ontology changed — a server can't
|
|
264
|
+
* buy itself a stream of questions by editing what it declares.
|
|
265
|
+
*/
|
|
266
|
+
/** Whether an offer for `server` could go out at all, before its digest is known: not within the floor of the last one. */
|
|
267
|
+
mayOfferSoon(server: string, now = Date.now()): boolean {
|
|
268
|
+
const r = this.records[server];
|
|
269
|
+
if (!r) return true;
|
|
270
|
+
const at = Date.parse(r.at);
|
|
271
|
+
return !Number.isFinite(at) || now - at >= OFFER_FLOOR_MS;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
mayOffer(server: string, digest: string, now = Date.now()): boolean {
|
|
275
|
+
const r = this.records[server];
|
|
276
|
+
if (!r) return true;
|
|
277
|
+
if (r.digest === digest || r.shown === digest) return false;
|
|
278
|
+
const at = Date.parse(r.at);
|
|
279
|
+
return !Number.isFinite(at) || now - at >= OFFER_FLOOR_MS;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
set(server: string, record: Omit<DecisionRecord, 'at'>, now = Date.now()): DecisionRecord {
|
|
283
|
+
const r: DecisionRecord = { ...record, at: new Date(now).toISOString() };
|
|
284
|
+
this.records[server] = r;
|
|
285
|
+
this.write();
|
|
286
|
+
return r;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Record that the agent was shown `digest`'s list, leaving the decision and its time as they are (the floor doesn't restart). */
|
|
290
|
+
markShown(server: string, digest: string): void {
|
|
291
|
+
const r = this.records[server];
|
|
292
|
+
if (!r) return;
|
|
293
|
+
this.records[server] = { ...r, shown: digest };
|
|
294
|
+
this.write();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
all(): Readonly<Record<string, DecisionRecord>> { return this.records; }
|
|
298
|
+
|
|
299
|
+
private write() {
|
|
300
|
+
try {
|
|
301
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
302
|
+
const tmp = `${this.path}.tmp-${process.pid}`;
|
|
303
|
+
writeFileSync(tmp, JSON.stringify({ servers: this.records }, null, 2) + '\n');
|
|
304
|
+
renameSync(tmp, this.path);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
this.log(`wake decisions: could not write ${this.path}: ${(error as Error).message}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -603,7 +603,7 @@ export class Heartbeat {
|
|
|
603
603
|
this.cancelReminders?.();
|
|
604
604
|
this.cancelReminders = null;
|
|
605
605
|
if (!this.started || this.state.reminders.length === 0) return;
|
|
606
|
-
const due =
|
|
606
|
+
const due = this.state.reminders.reduce((min, rem) => Math.min(min, dueAt(rem)), Infinity);
|
|
607
607
|
this.cancelReminders = callAt(Math.max(due, this.notBefore), this.now, () => {
|
|
608
608
|
this.cancelReminders = null;
|
|
609
609
|
this.fireDueReminders();
|
package/src/held-writes.ts
CHANGED
|
@@ -1,73 +1,62 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { suppressionRecordOf } from './gate/suppression-stats.js';
|
|
2
|
+
import type { ContextManager, MessageId, StoredMessage } from '@animalabs/context-manager';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* The id a module
|
|
5
|
-
* write
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* The id a release before #291 gave a module for a write it held while the
|
|
6
|
+
* write's agent's turn was alive: `held:` and the write's id in its deferred
|
|
7
|
+
* queue, which the stored row carries as `metadata.deferredWriteId` once it
|
|
8
|
+
* lands. This release stores every write at once and answers its stored id,
|
|
9
|
+
* unless a surgery's hold or a context manager refusing its work keeps the
|
|
10
|
+
* write in memory, where it answers '', so no write gets a handle any more.
|
|
11
|
+
* A module may have kept one across the upgrade: for one release the
|
|
12
|
+
* context's getMessage, editMessage and removeMessage still take it, and
|
|
13
|
+
* reach the row its write landed as (AgentFramework.landedRow), which the
|
|
14
|
+
* importer stores at boot for a write that release left queued.
|
|
12
15
|
*/
|
|
13
16
|
const HELD = 'held:';
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** The deferred write a handle names, or undefined for any other id. A value
|
|
20
|
-
* that isn't a string, which only an untyped caller passes, is no handle,
|
|
21
|
-
* and goes on to the store as it did before handles. */
|
|
18
|
+
/** The queued write a `held:` handle names, or undefined for any other id. A
|
|
19
|
+
* value that isn't a string, which only an untyped caller passes, is no
|
|
20
|
+
* handle, and goes on to the store as it did before handles. */
|
|
22
21
|
export function heldWriteId(id: MessageId): string | undefined {
|
|
23
22
|
return typeof id === 'string' && id.startsWith(HELD) ? id.slice(HELD.length) : undefined;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
* stored one. That's its MessageStore's private matchesFilter, the same at
|
|
29
|
-
* 0.14.1, 0.16.0, 0.17.1 and 0.18.0, which the package doesn't export, so the rule is copied
|
|
30
|
-
* here, and a test compares the two on the same rows. A row matches the external source, one of the
|
|
31
|
-
* external ids (an empty list filters nothing), the participant, and each
|
|
32
|
-
* metadata path, dot-separated, by strict equality.
|
|
33
|
-
*/
|
|
34
|
-
export function matchesMessageQuery(row: Pick<StoredMessage, 'participant' | 'metadata'>, filter: MessageQuery): boolean {
|
|
35
|
-
const external = row.metadata?.external as { source?: unknown; id?: unknown } | undefined;
|
|
36
|
-
if (filter.source !== undefined && external?.source !== filter.source) return false;
|
|
37
|
-
if (
|
|
38
|
-
filter.externalIds !== undefined &&
|
|
39
|
-
filter.externalIds.length > 0 &&
|
|
40
|
-
(!external?.id || !filter.externalIds.includes(external.id as string))
|
|
41
|
-
) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (filter.participant !== undefined && row.participant !== filter.participant) return false;
|
|
45
|
-
if (filter.metadata !== undefined) {
|
|
46
|
-
for (const [path, value] of Object.entries(filter.metadata)) {
|
|
47
|
-
if (valueAt(row.metadata, path) !== value) return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function valueAt(value: unknown, path: string): unknown {
|
|
54
|
-
for (const part of path.split('.')) {
|
|
55
|
-
if (value === undefined || value === null || typeof value !== 'object') return undefined;
|
|
56
|
-
value = (value as Record<string, unknown>)[part];
|
|
57
|
-
}
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
25
|
+
/** What asModuleRow reads of the window of the agent a read is for. */
|
|
26
|
+
export type ModuleRowWindow = Pick<ContextManager, 'windowMode' | 'ordinalOf'>;
|
|
60
27
|
|
|
61
28
|
/**
|
|
62
|
-
* A row as a module's context answers it
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* a
|
|
68
|
-
*
|
|
69
|
-
*
|
|
29
|
+
* A row as a module's context answers it, for the agent whose store it was
|
|
30
|
+
* read from:
|
|
31
|
+
*
|
|
32
|
+
* - `heldFromResidents` when it carries the stamp a tune-out puts on a
|
|
33
|
+
* message it holds from the residents (`metadata.tuneOut`). Every
|
|
34
|
+
* resident's viewFilter leaves a stamped row out of its compiled view
|
|
35
|
+
* (framework.ts, createAgent), and a tool boundary never presents one to a
|
|
36
|
+
* resident, so a marked row is one the store keeps and no resident is
|
|
37
|
+
* shown (ModuleMessage.heldFromResidents says where its text can still
|
|
38
|
+
* reach one). So is a row a suppress rule hid (`metadata.suppressed`),
|
|
39
|
+
* which no view shows either, nor any window presents (one stored before
|
|
40
|
+
* audience stamps stays in a window written down from the record; the
|
|
41
|
+
* view still leaves it out).
|
|
42
|
+
* - `held` when that agent's window (`window`) presents rows and doesn't
|
|
43
|
+
* hold it (#291): stored and not yet presented to the agent, which its
|
|
44
|
+
* next presentation point does, or taken out of it as an undone turn's,
|
|
45
|
+
* or one it is never presented, as a held row is for a resident. Never in
|
|
46
|
+
* legacy mode, where the window is the record. A resident's window
|
|
47
|
+
* written down from the record holds the held rows an earlier release
|
|
48
|
+
* stored, unstamped, so those read without `held`: `heldFromResidents`
|
|
49
|
+
* marks every held row.
|
|
50
|
+
*
|
|
51
|
+
* Any other row comes back as it is.
|
|
70
52
|
*/
|
|
71
|
-
export function asModuleRow<T extends Pick<StoredMessage, 'metadata'>>(
|
|
72
|
-
|
|
53
|
+
export function asModuleRow<T extends Pick<StoredMessage, 'id' | 'metadata'>>(
|
|
54
|
+
row: T,
|
|
55
|
+
window?: ModuleRowWindow,
|
|
56
|
+
): T & { heldFromResidents?: true; held?: true } {
|
|
57
|
+
const metadata = row.metadata as Record<string, unknown> | undefined;
|
|
58
|
+
const fromResidents = !!metadata?.tuneOut || suppressionRecordOf(metadata) !== undefined;
|
|
59
|
+
const held = window !== undefined && window.windowMode() === 'presented' && window.ordinalOf(row.id) === undefined;
|
|
60
|
+
if (!fromResidents && !held) return row;
|
|
61
|
+
return { ...row, ...(fromResidents ? { heldFromResidents: true as const } : {}), ...(held ? { held: true as const } : {}) };
|
|
73
62
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,11 +3,19 @@ export * from './types/index.js';
|
|
|
3
3
|
|
|
4
4
|
// Core classes
|
|
5
5
|
export { AgentFramework, AgentStoppedError, BudgetPreflightError, DefaultErrorPolicy, ProviderWaitNotSleptError, QuiesceBlockedError, ResumeBlockedError } from './framework.js';
|
|
6
|
+
export { isReasoningLoss } from './reasoning-loss.js';
|
|
6
7
|
export type { RuntimeSettingsPreview, HostModeStatus, DeriveAgentOptions } from './framework.js';
|
|
7
8
|
export { Agent } from './agent.js';
|
|
8
9
|
export { FLAT_MINIMUM_MS, type InferenceBoundsOptions } from './inference-bounds.js';
|
|
9
10
|
export { storedInjectionMarkOf, type StoredInjectionMark } from './stored-injections.js';
|
|
10
11
|
export type { StartStreamResult } from './agent.js';
|
|
12
|
+
export type { PresentationOccasion } from './presentation.js';
|
|
13
|
+
export { covers, coverTest, describeReadMark, markAtOrAfter, type Cover, type MarkCm, type MarkedRow, type ReadMark } from './read-marks.js';
|
|
14
|
+
// How a resident's context and the subconscious's are opened (#291), for a host that opens one itself.
|
|
15
|
+
export {
|
|
16
|
+
residentContextOptions, residentViewFilter, subconsciousContextOptions, subconsciousViewFilter,
|
|
17
|
+
type ContextManagerOpenOptions,
|
|
18
|
+
} from './context-options.js';
|
|
11
19
|
export { ProcessQueueImpl } from './queue.js';
|
|
12
20
|
export { ModuleRegistry } from './module-registry.js';
|
|
13
21
|
export { ShutdownTimeoutError, DEFAULT_SHUTDOWN_TIMEOUT_MS } from './shutdown.js';
|
|
@@ -73,9 +81,9 @@ export type { SessionUsage, AgentUsage, SessionUsageSnapshot, UsageUpdatedEvent
|
|
|
73
81
|
|
|
74
82
|
// EventGate
|
|
75
83
|
export { EventGate, MCPL_GATE_EVENT_TYPES, wakeLineText, type EventTypesFrom, type WakeProvenance, type WritePlacement } from './gate/index.js';
|
|
76
|
-
export type { GateConfig, GateOptions, GatePolicy, GatePolicyMatch, GateBehavior, WakeLine } from './gate/index.js';
|
|
84
|
+
export type { GateConfig, GateOptions, GatePolicy, GatePolicyMatch, GateBehavior, WakeLine, SuppressConfig, SuppressionReviewOptions } from './gate/index.js';
|
|
77
85
|
export { ReplyJudge, DEFAULT_JUDGE_MODEL, DEFAULT_JUDGE_MAX_INPUT_TOKENS, MIN_JUDGE_INPUT_TOKENS } from './gate/index.js';
|
|
78
|
-
export type { JudgeConfig, JudgeEventSummary, JudgeFn, JudgeRequest, JudgeVerdict, ReplyJudgeOptions, ReplyJudgeDeps, UpcomingWake } from './gate/index.js';
|
|
86
|
+
export type { JudgeConfig, JudgeEventSummary, JudgeFn, JudgeRequest, JudgeVerdict, ReplyJudgeOptions, ReplyJudgeDeps, UpcomingWake, UpcomingWakeKind } from './gate/index.js';
|
|
79
87
|
export { Heartbeat, DEFAULT_HEARTBEAT, HEARTBEAT_TOOL_PREFIX } from './heartbeat/heartbeat.js';
|
|
80
88
|
export type { HeartbeatOptions, HeartbeatSettings, HeartbeatWake, HeartbeatDeliveryMode, HeartbeatScheduledWake, Reminder } from './heartbeat/heartbeat.js';
|
|
81
89
|
|
package/src/inference-bounds.ts
CHANGED
|
@@ -72,11 +72,9 @@ export function wholeCallMs(bounds: InferenceBounds, maxTokens: number | undefin
|
|
|
72
72
|
return setupMs(bounds.progressBounds) + allowanceMs(bounds.progressBounds, maxTokens);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// membrane 0.8.0
|
|
76
|
-
// symbol (on a sent request, or the Anthropic SDK's timeout, which
|
|
77
|
-
// connect timeout too) and
|
|
78
|
-
// the deadline class here and the transient-failure check of an exhausted
|
|
79
|
-
// turn's re-arm both use it.
|
|
75
|
+
// membrane 0.8.0 and 0.9.0 mark a deadline they report as run out with this
|
|
76
|
+
// registered symbol (on a sent request, or the Anthropic SDK's timeout, which
|
|
77
|
+
// covers its connect timeout too) and export no reader or marker for it.
|
|
80
78
|
const DEADLINE_EXPIRY = Symbol.for('membrane.deadlineExpiry');
|
|
81
79
|
|
|
82
80
|
export function isDeadlineExpiry(error: unknown): boolean {
|
|
@@ -85,7 +83,7 @@ export function isDeadlineExpiry(error: unknown): boolean {
|
|
|
85
83
|
|
|
86
84
|
/**
|
|
87
85
|
* Names the host setting for the bound a stream cut hit, read from the opening
|
|
88
|
-
* of membrane 0.8.0's message for it (
|
|
86
|
+
* of membrane 0.8.0's and 0.9.0's message for it (their progress-bounds.ts and
|
|
89
87
|
* providers/utils.ts), so the reason an operator and the agent see names the
|
|
90
88
|
* host's setting for that bound, as the README's table maps them. A message it
|
|
91
89
|
* doesn't know is left as it is.
|
|
@@ -99,6 +97,8 @@ export function withHostSetting(message: string): string {
|
|
|
99
97
|
return setting === undefined ? message : `${message} (the host's setting for this bound: \`inferenceBounds.${setting}\`)`;
|
|
100
98
|
}
|
|
101
99
|
|
|
100
|
+
const CUTS = new WeakSet<object>();
|
|
101
|
+
|
|
102
102
|
export function deadlineCut(message: string | undefined): MembraneError {
|
|
103
103
|
const error = new MembraneError({
|
|
104
104
|
type: 'timeout',
|
|
@@ -107,5 +107,10 @@ export function deadlineCut(message: string | undefined): MembraneError {
|
|
|
107
107
|
rawError: undefined,
|
|
108
108
|
});
|
|
109
109
|
Object.defineProperty(error, DEADLINE_EXPIRY, { value: true });
|
|
110
|
+
CUTS.add(error);
|
|
110
111
|
return error;
|
|
111
112
|
}
|
|
113
|
+
|
|
114
|
+
export function isDeadlineCut(error: unknown): boolean {
|
|
115
|
+
return typeof error === 'object' && error !== null && CUTS.has(error);
|
|
116
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON-RPC 2.0 message's envelope, read once where a message arrives, by
|
|
3
|
+
* each of the framework's endpoints that answers requests: the MCPL
|
|
4
|
+
* connection, for its servers' requests, and the MCP server
|
|
5
|
+
* (src/api/mcp-server-core.ts), for its client's.
|
|
6
|
+
*
|
|
7
|
+
* A message with an id member and no method is a response, whatever the
|
|
8
|
+
* id's value: a peer answers a request it couldn't read with id null (§5),
|
|
9
|
+
* and taking that answer for a request would answer it in turn. So is an
|
|
10
|
+
* error with no id, which MCP lets a peer send for a request it couldn't
|
|
11
|
+
* read (MCP 2025-11-25, Basic, Error Responses), but only from a message
|
|
12
|
+
* whose jsonrpc member is "2.0": a peer's own JSON log lines, which an MCPL
|
|
13
|
+
* server's stdout can carry, can have an error key. Any other object with
|
|
14
|
+
* neither a method nor an id isn't JSON-RPC: the MCP server answers it
|
|
15
|
+
* -32600 with id null, as it does a value that isn't an object, and the MCPL
|
|
16
|
+
* connection, whose server's stdout carries other output, ignores it. A
|
|
17
|
+
* message with a method is a request when it has an id member, whatever its
|
|
18
|
+
* value, and a notification when it has none (§4.1), so `"id": null` is a
|
|
19
|
+
* request. MCP says a request's id MUST NOT be null (2025-11-25, Basic); one
|
|
20
|
+
* is served anyway, as JSON-RPC allows, since its sender waits for the
|
|
21
|
+
* answer. A request's method must be a string and its id a string, a number
|
|
22
|
+
* or null (§4). A message that breaks either isn't a Request object, and so
|
|
23
|
+
* isn't a notification either, with or without an id: the endpoints answer
|
|
24
|
+
* it once with -32600, carrying its id where that's a string or a number and
|
|
25
|
+
* null otherwise (§5, and the spec's own example of an invalid Request
|
|
26
|
+
* object, which has no id), and don't dispatch it. The MCPL handshake, which
|
|
27
|
+
* answers only pings, ignores it, as it ignores every other request then.
|
|
28
|
+
* Apart from the error with no id, the `jsonrpc` member isn't checked: a
|
|
29
|
+
* peer that leaves it out is served, as before.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** A JSON value's kind, as a log line or an error names what a peer sent
|
|
33
|
+
* without repeating it: "null", "an array", "an object", "a number". */
|
|
34
|
+
export function jsonKind(value: unknown): string {
|
|
35
|
+
if (value === null) return 'null';
|
|
36
|
+
const kind = Array.isArray(value) ? 'array' : typeof value;
|
|
37
|
+
return `${/^[aeiou]/.test(kind) ? 'an' : 'a'} ${kind}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type JsonRpcEnvelope =
|
|
41
|
+
/** An id member and no method, or a JSON-RPC 2.0 error with neither: an
|
|
42
|
+
* answer, which the endpoint matches to one of its own requests by the
|
|
43
|
+
* id where it can. */
|
|
44
|
+
| { kind: 'response' }
|
|
45
|
+
/** Neither a method nor an id member, and not such an error: not a
|
|
46
|
+
* JSON-RPC message. */
|
|
47
|
+
| { kind: 'neither' }
|
|
48
|
+
| { kind: 'request'; method: string; id: string | number | null }
|
|
49
|
+
| { kind: 'notification'; method: string }
|
|
50
|
+
/** Not a Request object, with or without an id: answered with `error`,
|
|
51
|
+
* carrying `id`, and not dispatched. `method` is its method where that's
|
|
52
|
+
* a string, for a log line. */
|
|
53
|
+
| { kind: 'invalid'; id: string | number | null; error: { code: -32600; message: string }; method?: string; problems: string };
|
|
54
|
+
|
|
55
|
+
/** Read a parsed message's envelope. What to do with a value that isn't an
|
|
56
|
+
* object is the endpoint's own call. */
|
|
57
|
+
export function readEnvelope(msg: Record<string, unknown>): JsonRpcEnvelope {
|
|
58
|
+
if (!Object.hasOwn(msg, 'method')) {
|
|
59
|
+
const answers = Object.hasOwn(msg, 'id') || (msg.jsonrpc === '2.0' && Object.hasOwn(msg, 'error'));
|
|
60
|
+
return { kind: answers ? 'response' : 'neither' };
|
|
61
|
+
}
|
|
62
|
+
const isRequest = Object.hasOwn(msg, 'id');
|
|
63
|
+
const { method, id } = msg;
|
|
64
|
+
const problems = [
|
|
65
|
+
...(typeof method === 'string' ? [] : [`the method must be a string, not ${jsonKind(method)}`]),
|
|
66
|
+
...(!isRequest || typeof id === 'string' || typeof id === 'number' || id === null
|
|
67
|
+
? [] : [`the id must be a string, a number or null, not ${jsonKind(id)}`]),
|
|
68
|
+
].join('; ');
|
|
69
|
+
if (problems) {
|
|
70
|
+
return {
|
|
71
|
+
kind: 'invalid',
|
|
72
|
+
id: typeof id === 'string' || typeof id === 'number' ? id : null,
|
|
73
|
+
error: { code: -32600, message: `Invalid Request: ${problems}` },
|
|
74
|
+
...(typeof method === 'string' ? { method } : {}),
|
|
75
|
+
problems,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return isRequest
|
|
79
|
+
? { kind: 'request', method: method as string, id: id as string | number | null }
|
|
80
|
+
: { kind: 'notification', method: method as string };
|
|
81
|
+
}
|