@animalabs/agent-framework 0.27.0 → 0.29.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 +145 -41
- package/dist/src/agent.d.ts +29 -2
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +73 -12
- 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 +3 -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 +11 -5
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +5 -2
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +9 -5
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/cut-off-round.d.ts +24 -0
- package/dist/src/cut-off-round.d.ts.map +1 -0
- package/dist/src/cut-off-round.js +87 -0
- package/dist/src/cut-off-round.js.map +1 -0
- package/dist/src/framework.d.ts +468 -125
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +3651 -1111
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +133 -13
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +654 -59
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +3 -2
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +2 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/reply-judge.d.ts +116 -0
- package/dist/src/gate/reply-judge.d.ts.map +1 -0
- package/dist/src/gate/reply-judge.js +467 -0
- package/dist/src/gate/reply-judge.js.map +1 -0
- package/dist/src/gate/types.d.ts +173 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +202 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
- package/dist/src/heartbeat/heartbeat.js +778 -0
- package/dist/src/heartbeat/heartbeat.js.map +1 -0
- package/dist/src/heartbeat/reminder-time.d.ts +5 -0
- package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
- package/dist/src/heartbeat/reminder-time.js +107 -0
- package/dist/src/heartbeat/reminder-time.js.map +1 -0
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
- package/dist/src/mcpl/capability-mask.js +3 -2
- package/dist/src/mcpl/capability-mask.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +133 -54
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +29 -11
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +43 -70
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +18 -8
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +70 -30
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tags.d.ts +9 -2
- package/dist/src/mcpl/tags.d.ts.map +1 -1
- package/dist/src/mcpl/tags.js +12 -2
- package/dist/src/mcpl/tags.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts +66 -14
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +150 -80
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +10 -3
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +60 -30
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +88 -55
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +220 -111
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +27 -35
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +313 -91
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +31 -13
- 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 +10 -8
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +15 -9
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.d.ts.map +1 -1
- package/dist/src/modules/history/index.js +30 -17
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +26 -0
- package/dist/src/modules/workspace/answers.d.ts.map +1 -0
- package/dist/src/modules/workspace/answers.js +138 -0
- package/dist/src/modules/workspace/answers.js.map +1 -0
- package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
- package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
- package/dist/src/modules/workspace/branch-intent.js +6 -2
- package/dist/src/modules/workspace/branch-intent.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts +3 -0
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +59 -24
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +48 -17
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/edit-text.d.ts +2 -1
- package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/edit-text.js +4 -2
- package/dist/src/modules/workspace/edit-text.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +67 -50
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +63 -51
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +980 -547
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +125 -82
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +465 -241
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
- package/dist/src/modules/workspace/read-page.js +7 -1
- package/dist/src/modules/workspace/read-page.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +156 -50
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/result-text.d.ts +1 -0
- package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
- package/dist/src/modules/workspace/result-text.js.map +1 -1
- package/dist/src/modules/workspace/sync.d.ts +9 -0
- package/dist/src/modules/workspace/sync.d.ts.map +1 -1
- package/dist/src/modules/workspace/sync.js +14 -0
- package/dist/src/modules/workspace/sync.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +96 -43
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +8 -3
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/prose-drafts.d.ts +4 -3
- package/dist/src/prose-drafts.d.ts.map +1 -1
- package/dist/src/prose-drafts.js +3 -2
- package/dist/src/prose-drafts.js.map +1 -1
- package/dist/src/reasoning-loss-notice.d.ts +23 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
- package/dist/src/reasoning-loss-notice.js +52 -0
- package/dist/src/reasoning-loss-notice.js.map +1 -0
- package/dist/src/reasoning-loss.d.ts +24 -0
- package/dist/src/reasoning-loss.d.ts.map +1 -0
- package/dist/src/reasoning-loss.js +102 -0
- package/dist/src/reasoning-loss.js.map +1 -0
- package/dist/src/send-tools.d.ts +3 -2
- package/dist/src/send-tools.d.ts.map +1 -1
- package/dist/src/send-tools.js +3 -2
- package/dist/src/send-tools.js.map +1 -1
- package/dist/src/sent-rows.js +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/thinking-drops.d.ts +4 -11
- package/dist/src/thinking-drops.d.ts.map +1 -1
- package/dist/src/thinking-drops.js +7 -14
- package/dist/src/thinking-drops.js.map +1 -1
- package/dist/src/tool-args.d.ts +12 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +39 -4
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +8 -4
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +23 -11
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +4 -1
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/types/agent.d.ts +63 -18
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +18 -8
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +58 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +27 -13
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +34 -1
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +76 -12
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +11 -5
- package/src/auto-continue.ts +12 -5
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/cut-off-round.ts +98 -0
- package/src/framework.ts +3772 -1138
- package/src/gate/event-gate.ts +705 -60
- package/src/gate/index.ts +9 -1
- package/src/gate/reply-judge.ts +512 -0
- package/src/gate/types.ts +176 -3
- package/src/heartbeat/heartbeat.ts +843 -0
- package/src/heartbeat/reminder-time.ts +107 -0
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +5 -1
- package/src/line-reader.ts +58 -0
- package/src/mcpl/capability-mask.ts +3 -2
- package/src/mcpl/channel-registry.ts +139 -53
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +45 -70
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +76 -34
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tags.ts +13 -2
- package/src/mcpl/tool-presence.ts +149 -82
- package/src/mcpl/tool-signature.ts +64 -32
- package/src/mcpl/toolset-host.ts +212 -110
- package/src/mcpl/toolsets-module.ts +277 -91
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +32 -13
- package/src/module-registry.ts +14 -9
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +30 -18
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +151 -0
- package/src/modules/workspace/branch-intent.ts +10 -5
- package/src/modules/workspace/direct-write.ts +59 -23
- package/src/modules/workspace/disk-agreement.ts +52 -21
- package/src/modules/workspace/edit-text.ts +7 -5
- package/src/modules/workspace/effects.ts +69 -50
- package/src/modules/workspace/index.ts +966 -556
- package/src/modules/workspace/observe.ts +497 -242
- package/src/modules/workspace/read-page.ts +7 -1
- package/src/modules/workspace/reconcile.ts +184 -49
- package/src/modules/workspace/result-text.ts +2 -0
- package/src/modules/workspace/sync.ts +16 -0
- package/src/modules/workspace/types.ts +96 -43
- package/src/modules/workspace/watcher.ts +8 -3
- package/src/notice-tags.ts +84 -0
- package/src/prose-drafts.ts +4 -3
- package/src/reasoning-loss-notice.ts +64 -0
- package/src/reasoning-loss.ts +120 -0
- package/src/send-tools.ts +3 -2
- package/src/sent-rows.ts +1 -1
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/thinking-drops.ts +10 -16
- package/src/tool-args.ts +38 -4
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tool-result-guard.ts +26 -11
- package/src/tune-out/coordinator.ts +25 -27
- package/src/tune-out/tools.ts +4 -1
- package/src/types/agent.ts +63 -18
- package/src/types/events.ts +18 -8
- package/src/types/framework.ts +59 -0
- package/src/types/module.ts +28 -13
- package/src/types/trace.ts +36 -1
|
@@ -1,5 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPL RFC-014, host side: per server, the controller that feeds the
|
|
3
|
+
* server's tool presence (tool-presence.ts) from what its connection reads,
|
|
4
|
+
* sends toolsets/open, close and list, recovers from a lost rev, and renders
|
|
5
|
+
* what changed; and the hub that holds every server's controller for the
|
|
6
|
+
* meta-tools (toolsets-module.ts) and for reminders.
|
|
7
|
+
*
|
|
8
|
+
* Hint changes apply as their message is read (RFC §6.1); a static↔dynamic
|
|
9
|
+
* move takes effect when a tools/list states it (§4.1), and the model's tool
|
|
10
|
+
* list shows it at its next rebuild; until then the tool is called at either
|
|
11
|
+
* place (§4.2). The connection's read tap hands
|
|
12
|
+
* the controller every result of a hinted request (awaited or not, while no
|
|
13
|
+
* more than 1,000 hinted requests await an answer), each tools/list whole, every push/event and every tools/hints, of the
|
|
14
|
+
* current transport, synchronously and in arrival order, before anything
|
|
15
|
+
* routes the message or anything read after it is handled.
|
|
16
|
+
* What a message's changes did to the model's view is kept by the object
|
|
17
|
+
* that carried them (`readChanges`), so whoever later handles that object
|
|
18
|
+
* (the tool result, the push, the meta-tool) renders it next to the content.
|
|
19
|
+
*
|
|
20
|
+
* Reminders (RFC §8.3): after each compile built to send, the hub reads
|
|
21
|
+
* which open toolsets' markers (`[server/toolset:instance]`) the request
|
|
22
|
+
* carried. A marker an agent has had in view, gone from a later request
|
|
23
|
+
* (compressed, or out of the window), makes a reminder due for that agent,
|
|
24
|
+
* which the toolsets module gathers at its next start as a stored
|
|
25
|
+
* `afterUser` injection.
|
|
26
|
+
*/
|
|
27
|
+
import { nameToTypeBack, quotedValue } from '../supplied-text.js';
|
|
28
|
+
import { McplRequestError } from './server-connection.js';
|
|
1
29
|
import { isToolAllowed } from './tool-policy.js';
|
|
2
|
-
import {
|
|
30
|
+
import { redactServerText } from './protocol-family.js';
|
|
31
|
+
import { logDetail, logValueRedacted } from './log-value.js';
|
|
32
|
+
import { ToolPresence, emptyChange, isEmptyChange, keyOf, } from './tool-presence.js';
|
|
3
33
|
/** The meta-tools' module, whose tools are `toolsets--open` and so on. */
|
|
4
34
|
export const TOOLSETS_MODULE = 'toolsets';
|
|
5
35
|
export const META_TOOL_NAMES = {
|
|
@@ -8,7 +38,7 @@ export const META_TOOL_NAMES = {
|
|
|
8
38
|
look: `${TOOLSETS_MODULE}--look`,
|
|
9
39
|
close: `${TOOLSETS_MODULE}--close`,
|
|
10
40
|
};
|
|
11
|
-
/** How long an open
|
|
41
|
+
/** How long an open or close is waited for. A late result still applies when it's read. */
|
|
12
42
|
export const TOOLSET_REQUEST_TIMEOUT_MS = 60_000;
|
|
13
43
|
/** A toolsets/list after a (re)connect or in a recovery. */
|
|
14
44
|
const LIST_TIMEOUT_MS = 15_000;
|
|
@@ -19,8 +49,8 @@ const RECOVERY_DEBOUNCE_MS = 200;
|
|
|
19
49
|
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
20
50
|
const metaHints = (res) => (isObj(res._meta) ? res._meta['mcpl/hints'] : undefined);
|
|
21
51
|
/** One server's RFC-014 state and traffic. Lives as long as the server's
|
|
22
|
-
* configuration, across reconnects and restarts (its `ended`
|
|
23
|
-
* the model was shown carry over). */
|
|
52
|
+
* configuration, across reconnects and the server's restarts (its `ended`
|
|
53
|
+
* ids and what the model was shown carry over), in memory only. */
|
|
24
54
|
export class ToolsetController {
|
|
25
55
|
serverId;
|
|
26
56
|
config;
|
|
@@ -31,16 +61,10 @@ export class ToolsetController {
|
|
|
31
61
|
epoch = -1;
|
|
32
62
|
/** What applying a message's hints changed, by the object that carried them. */
|
|
33
63
|
readChanges = new WeakMap();
|
|
34
|
-
/** Results whose change no one has
|
|
35
|
-
unclaimed = new
|
|
64
|
+
/** Results whose change no one has taken yet, in the order they were read, with their method (tellUnlessClaimed). */
|
|
65
|
+
unclaimed = new Map();
|
|
36
66
|
/** eventIds of pushes read on this transport (bounded): a repeat's hints are ignored (RFC §6.1). */
|
|
37
67
|
seenEvents = new Set();
|
|
38
|
-
/** A paged tools/list being read. */
|
|
39
|
-
listPages = null;
|
|
40
|
-
/** Lists this controller asked for itself, whose change it delivers. */
|
|
41
|
-
ownLists = 0;
|
|
42
|
-
/** What lists read while one of its own was out changed, for it to tell. */
|
|
43
|
-
ownListChange = null;
|
|
44
68
|
recoveryTimer = null;
|
|
45
69
|
recovering = false;
|
|
46
70
|
recoverAgain = false;
|
|
@@ -48,11 +72,17 @@ export class ToolsetController {
|
|
|
48
72
|
this.serverId = serverId;
|
|
49
73
|
this.config = config;
|
|
50
74
|
this.deps = deps;
|
|
51
|
-
this.presence = new ToolPresence(serverId, META_TOOL_NAMES, (tool) => isToolAllowed(tool, this.config), (tool) => deps.listedName(serverId, tool));
|
|
75
|
+
this.presence = new ToolPresence(serverId, META_TOOL_NAMES, (tool) => isToolAllowed(tool, this.config), (tool) => deps.listedName(serverId, tool), (tool) => deps.published?.(serverId, tool));
|
|
52
76
|
}
|
|
53
77
|
log(line) {
|
|
54
78
|
this.deps.log(`${this.serverId}: ${line}`);
|
|
55
79
|
}
|
|
80
|
+
field(value) {
|
|
81
|
+
return logValueRedacted(value, (text) => redactServerText(text, this.config));
|
|
82
|
+
}
|
|
83
|
+
failure(error) {
|
|
84
|
+
return logDetail(redactServerText(error instanceof Error ? error.message : String(error), this.config));
|
|
85
|
+
}
|
|
56
86
|
/** A restart can bring a new configuration under the same id. */
|
|
57
87
|
setConfig(config) {
|
|
58
88
|
this.config = config;
|
|
@@ -67,6 +97,10 @@ export class ToolsetController {
|
|
|
67
97
|
this.connection = connection;
|
|
68
98
|
connection.setReadTap((read, epoch) => this.onRead(connection, read, epoch));
|
|
69
99
|
}
|
|
100
|
+
release() {
|
|
101
|
+
if (this.connection)
|
|
102
|
+
this.detach(this.connection);
|
|
103
|
+
}
|
|
70
104
|
/** The server's connection is gone for good (disconnected, removed). */
|
|
71
105
|
detach(connection) {
|
|
72
106
|
if (this.connection !== connection)
|
|
@@ -89,7 +123,7 @@ export class ToolsetController {
|
|
|
89
123
|
live() {
|
|
90
124
|
const c = this.connection;
|
|
91
125
|
if (!c || !c.isConnected)
|
|
92
|
-
throw new
|
|
126
|
+
throw new McplRequestError(`${this.serverId} is not connected right now; try again later`, 'not-sent');
|
|
93
127
|
this.sync(c.transportEpoch, c);
|
|
94
128
|
return c;
|
|
95
129
|
}
|
|
@@ -103,7 +137,6 @@ export class ToolsetController {
|
|
|
103
137
|
return;
|
|
104
138
|
this.epoch = epoch;
|
|
105
139
|
this.seenEvents = new Set();
|
|
106
|
-
this.listPages = null;
|
|
107
140
|
this.presence.beginConnection(connection.toolsetDeclarations, true);
|
|
108
141
|
}
|
|
109
142
|
// ── RFC §6.1: apply on arrival ──
|
|
@@ -112,7 +145,7 @@ export class ToolsetController {
|
|
|
112
145
|
return;
|
|
113
146
|
this.sync(epoch, connection);
|
|
114
147
|
if (read.kind === 'result') {
|
|
115
|
-
this.readResult(read.method, read.params, read.result, read.awaited);
|
|
148
|
+
this.readResult(read.method, read.params, read.result, read.awaited, read.straddled === true);
|
|
116
149
|
return;
|
|
117
150
|
}
|
|
118
151
|
if (!this.presence.active || !isObj(read.params))
|
|
@@ -129,7 +162,7 @@ export class ToolsetController {
|
|
|
129
162
|
// RFC §7: a push carrying hints is never coalesced; when its changes
|
|
130
163
|
// would apply is undefined, so they're ignored and the state re-read.
|
|
131
164
|
if (p.coalesce !== undefined && p.coalesce !== null) {
|
|
132
|
-
this.log(`push ${
|
|
165
|
+
this.log(`push ${this.field(p.eventId ?? '')} carries hints and coalesce (RFC-014 §7 forbids it): its hints are ignored; recovering`);
|
|
133
166
|
this.scheduleRecovery();
|
|
134
167
|
return;
|
|
135
168
|
}
|
|
@@ -137,7 +170,7 @@ export class ToolsetController {
|
|
|
137
170
|
const eventId = typeof p.eventId === 'string' || typeof p.eventId === 'number' ? String(p.eventId) : '';
|
|
138
171
|
if (eventId) {
|
|
139
172
|
if (this.seenEvents.has(eventId)) {
|
|
140
|
-
this.log(`push ${eventId} was read before: its hints are ignored`);
|
|
173
|
+
this.log(`push ${this.field(eventId)} was read before: its hints are ignored`);
|
|
141
174
|
return;
|
|
142
175
|
}
|
|
143
176
|
this.seenEvents.add(eventId);
|
|
@@ -145,28 +178,22 @@ export class ToolsetController {
|
|
|
145
178
|
this.seenEvents.delete(this.seenEvents.values().next().value);
|
|
146
179
|
}
|
|
147
180
|
// Applied now, before (and whatever) the host decides about the push.
|
|
148
|
-
|
|
181
|
+
const cs = this.applyHints(p.hints, 'push');
|
|
182
|
+
this.readChanges.set(p, cs);
|
|
183
|
+
this.tellUnlessClaimed(p, cs, 'push/event');
|
|
149
184
|
}
|
|
150
|
-
readResult(method, params, res, awaited) {
|
|
185
|
+
readResult(method, params, res, awaited, straddled) {
|
|
151
186
|
let cs;
|
|
152
187
|
switch (method) {
|
|
153
188
|
case 'tools/list': {
|
|
154
|
-
|
|
155
|
-
// A first page (no cursor) starts the list; a page with no next cursor ends it.
|
|
156
|
-
if (params.cursor === undefined || this.listPages === null)
|
|
157
|
-
this.listPages = [];
|
|
158
|
-
this.listPages.push(...page);
|
|
159
|
-
if (res.nextCursor !== undefined && res.nextCursor !== null)
|
|
189
|
+
if (!Array.isArray(res.tools))
|
|
160
190
|
return;
|
|
161
|
-
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.deliver(cs);
|
|
168
|
-
else
|
|
169
|
-
this.ownListChange = joinChanges(this.ownListChange ?? emptyChange(), cs);
|
|
191
|
+
cs = this.readToolList(res.tools, res);
|
|
192
|
+
if (straddled && !this.recovering) {
|
|
193
|
+
this.log('tools/list pages carried different revs, so its earlier pages are older than changes read between them: recovering');
|
|
194
|
+
this.scheduleRecovery();
|
|
195
|
+
}
|
|
196
|
+
this.deliver(cs);
|
|
170
197
|
return;
|
|
171
198
|
}
|
|
172
199
|
case 'toolsets/list':
|
|
@@ -218,9 +245,9 @@ export class ToolsetController {
|
|
|
218
245
|
if (h === undefined || !this.presence.active)
|
|
219
246
|
return emptyChange();
|
|
220
247
|
const before = this.presence.view();
|
|
221
|
-
const last = this.presence.rev;
|
|
248
|
+
const last = this.field(this.presence.rev);
|
|
222
249
|
const r = this.presence.apply(h);
|
|
223
|
-
const rev =
|
|
250
|
+
const rev = this.field(h?.rev);
|
|
224
251
|
if (r.behind) {
|
|
225
252
|
this.log(`hint change rev ${rev} (${source}) is not after rev ${last}: ignored; recovering from tools/list and toolsets/list`);
|
|
226
253
|
this.scheduleRecovery();
|
|
@@ -232,6 +259,10 @@ export class ToolsetController {
|
|
|
232
259
|
else if (!r.applied) {
|
|
233
260
|
this.log(`a malformed hint change (${source}) was ignored`);
|
|
234
261
|
}
|
|
262
|
+
if (r.unlisted) {
|
|
263
|
+
this.log(`hint change rev ${rev} (${source}) names a tool tools/list hasn't listed: recovering from tools/list and toolsets/list`);
|
|
264
|
+
this.scheduleRecovery();
|
|
265
|
+
}
|
|
235
266
|
const cs = this.presence.diff(before, this.presence.view());
|
|
236
267
|
cs.suggest.push(...r.suggest);
|
|
237
268
|
return cs;
|
|
@@ -241,7 +272,7 @@ export class ToolsetController {
|
|
|
241
272
|
const before = this.presence.view();
|
|
242
273
|
this.presence.setTools(tools);
|
|
243
274
|
if (this.presence.active && this.presence.noteListRev(isObj(last._meta) ? last._meta['mcpl/rev'] : undefined)) {
|
|
244
|
-
this.log(`tools/list was written after rev ${
|
|
275
|
+
this.log(`tools/list was written after rev ${this.field(isObj(last._meta) ? last._meta['mcpl/rev'] : '')}, past the last rev read: a message was lost; recovering`);
|
|
245
276
|
this.scheduleRecovery();
|
|
246
277
|
}
|
|
247
278
|
return this.presence.diff(before, this.presence.view());
|
|
@@ -250,40 +281,82 @@ export class ToolsetController {
|
|
|
250
281
|
readToolsetList(res) {
|
|
251
282
|
if (!this.presence.active)
|
|
252
283
|
return emptyChange();
|
|
284
|
+
if (!Array.isArray(res.open)) {
|
|
285
|
+
this.log('a toolsets/list result with no open list was ignored');
|
|
286
|
+
return emptyChange();
|
|
287
|
+
}
|
|
253
288
|
const before = this.presence.view();
|
|
254
|
-
const { lost, ended, suggest, skipped } = this.presence.adopt(res);
|
|
289
|
+
const { lost, ended, suggest, skipped, unlisted } = this.presence.adopt(res);
|
|
255
290
|
if (skipped && !this.recovering) {
|
|
256
291
|
// Per-instance state is restated in full; what else was lost (presence) comes from tools/list.
|
|
257
292
|
this.log('toolsets/list skipped a rev: re-reading tools/list');
|
|
258
293
|
this.scheduleRecovery();
|
|
259
294
|
}
|
|
295
|
+
if (unlisted && !this.recovering) {
|
|
296
|
+
this.log('toolsets/list names a tool tools/list hasn\'t listed: re-reading tools/list');
|
|
297
|
+
this.scheduleRecovery();
|
|
298
|
+
}
|
|
260
299
|
const cs = this.presence.diff(before, this.presence.view());
|
|
261
300
|
cs.suggest.push(...suggest);
|
|
262
301
|
// A lost toolset gets its note, not also a "closed" line.
|
|
263
302
|
cs.changes = cs.changes.filter((c) => !lost.some((o) => keyOf(o.toolset, o.instance) === c.key));
|
|
264
|
-
|
|
303
|
+
const told = this.presence.adoptionNotes(lost, ended);
|
|
304
|
+
cs.notes.push(...told.notes);
|
|
305
|
+
if (told.ended.length)
|
|
306
|
+
cs.ended = [...(cs.ended ?? []), ...told.ended];
|
|
265
307
|
return cs;
|
|
266
308
|
}
|
|
267
|
-
/** A change with nothing else to ride on, told as a notice. */
|
|
309
|
+
/** A change with nothing else to ride on, told as a notice, after every result's change still unclaimed. */
|
|
268
310
|
deliver(cs) {
|
|
311
|
+
this.tellUnclaimed();
|
|
312
|
+
this.notice(cs);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Unclaimed carriers' changes, told as notices in the order they were read,
|
|
316
|
+
* through `upTo` when given. When no notice can be written, a push keeps its
|
|
317
|
+
* change for the framework's push path, which takes it when the push gets
|
|
318
|
+
* there; a result's is dropped, whether or not its caller is still to
|
|
319
|
+
* resume.
|
|
320
|
+
*/
|
|
321
|
+
tellUnclaimed(upTo) {
|
|
322
|
+
for (const [res, method] of [...this.unclaimed]) {
|
|
323
|
+
this.unclaimed.delete(res);
|
|
324
|
+
const cs = this.readChanges.get(res) ?? emptyChange();
|
|
325
|
+
const sent = this.notice(cs);
|
|
326
|
+
if (sent !== 'refused' || method !== 'push/event')
|
|
327
|
+
this.readChanges.delete(res);
|
|
328
|
+
if (sent === 'told')
|
|
329
|
+
this.log(`${method}'s tool change wasn't shown with it: told as a notice`);
|
|
330
|
+
if (res === upTo)
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Whether a notice was written, there was nothing left to tell (`empty`), or one couldn't be written (`refused`). */
|
|
335
|
+
notice(cs) {
|
|
269
336
|
if (isEmptyChange(cs))
|
|
270
|
-
return;
|
|
271
|
-
const text = this.presence.render(cs);
|
|
337
|
+
return 'empty';
|
|
338
|
+
const text = this.presence.render(cs, { mark: false });
|
|
272
339
|
if (!text)
|
|
273
|
-
return;
|
|
274
|
-
this.deps.notify(this.serverId, text, { invite: cs.suggest.some((s) => s.action === 'open') })
|
|
340
|
+
return 'empty';
|
|
341
|
+
if (!this.deps.notify(this.serverId, text, { invite: cs.suggest.some((s) => s.action === 'open') }))
|
|
342
|
+
return 'refused';
|
|
343
|
+
this.presence.markTold(cs.ended);
|
|
344
|
+
this.presence.markShown(cs);
|
|
345
|
+
return 'told';
|
|
275
346
|
}
|
|
276
347
|
/**
|
|
277
348
|
* A dynamic tool called by its namespaced name, as if it were listed (a
|
|
278
349
|
* model imitating an earlier call, a module): refused, so every call into
|
|
279
350
|
* a toolset goes through act, which knows its instance and gates closed
|
|
280
|
-
* groups (RFC §5.4, §5.5). Null for any other tool
|
|
351
|
+
* groups (RFC §5.4, §5.5). Null for any other tool, including one the tool
|
|
352
|
+
* list last published or the server's latest tools/list puts as static
|
|
353
|
+
* (shownAs: RFC §4.2, until its move applies).
|
|
281
354
|
*/
|
|
282
355
|
dynamicCallRefusal(tool, calledAs) {
|
|
283
|
-
if (!this.presence.active || !this.presence.tool(tool) || this.presence.
|
|
356
|
+
if (!this.presence.active || !this.presence.tool(tool) || this.presence.shownAs(tool, 'static'))
|
|
284
357
|
return null;
|
|
285
|
-
return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${tool} is a dynamic tool of ${this.serverId}, ` +
|
|
286
|
-
`called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool
|
|
358
|
+
return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${nameToTypeBack(tool)} is a dynamic tool of ${this.serverId}, ` +
|
|
359
|
+
`called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:${quotedValue(tool)}, args}. ${this.presence.toolStatus(tool)}.`;
|
|
287
360
|
}
|
|
288
361
|
/**
|
|
289
362
|
* An awaited result's change is told by whoever renders that result, when
|
|
@@ -292,34 +365,54 @@ export class ToolsetController {
|
|
|
292
365
|
* run (the next macrotask), such as the result of a call the framework makes
|
|
293
366
|
* itself (a reaction, a send, an accounted side effect), is told as a notice
|
|
294
367
|
* instead, as a standalone tools/hints is (RFC §6.1: every change reaches the
|
|
295
|
-
* agent once).
|
|
368
|
+
* agent once), or dropped as tellUnclaimed says when none can be written.
|
|
296
369
|
*/
|
|
297
370
|
tellUnlessClaimed(res, cs, method) {
|
|
298
371
|
if (isEmptyChange(cs))
|
|
299
372
|
return;
|
|
300
|
-
this.unclaimed.
|
|
373
|
+
this.unclaimed.set(res, method);
|
|
301
374
|
setImmediate(() => {
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
this.log(`${method} result's tool change wasn't shown with it: told as a notice`);
|
|
305
|
-
this.deliver(cs);
|
|
375
|
+
if (this.unclaimed.has(res))
|
|
376
|
+
this.tellUnclaimed(res);
|
|
306
377
|
});
|
|
307
378
|
}
|
|
308
|
-
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
|
|
379
|
+
/**
|
|
380
|
+
* The change a carrier's hints made (a tool result, a push's params), taken
|
|
381
|
+
* by a caller that shows it to an agent with the carrier: it isn't told as a
|
|
382
|
+
* notice, and the caller says when it has reached an agent (told). Call it
|
|
383
|
+
* synchronously as the carrier is read, before the next macrotask, when an
|
|
384
|
+
* unclaimed carrier's change goes out as a notice instead (tellUnclaimed
|
|
385
|
+
* says what happens when none can be written). A change already gone that
|
|
386
|
+
* way is empty here. Unclaimed carriers read before this one
|
|
387
|
+
* are told first, as notices, so no later change is shown before them.
|
|
388
|
+
*/
|
|
389
|
+
takeChange(carrier) {
|
|
312
390
|
if (!isObj(carrier))
|
|
313
|
-
return
|
|
391
|
+
return emptyChange();
|
|
392
|
+
if (this.unclaimed.has(carrier)) {
|
|
393
|
+
let before;
|
|
394
|
+
for (const res of this.unclaimed.keys()) {
|
|
395
|
+
if (res === carrier)
|
|
396
|
+
break;
|
|
397
|
+
before = res;
|
|
398
|
+
}
|
|
399
|
+
if (before)
|
|
400
|
+
this.tellUnclaimed(before);
|
|
401
|
+
}
|
|
314
402
|
this.unclaimed.delete(carrier);
|
|
315
|
-
|
|
316
|
-
|
|
403
|
+
return this.readChanges.get(carrier) ?? emptyChange();
|
|
404
|
+
}
|
|
405
|
+
/** A change its taker showed to an agent: its ended records count as told, and the definitions it describes as shown, unless the taker showed it without its blocks (`blocksShown: false`). */
|
|
406
|
+
told(cs) {
|
|
407
|
+
this.presence.markTold(cs.ended);
|
|
408
|
+
if (cs.blocksShown !== false)
|
|
409
|
+
this.presence.markShown(cs);
|
|
410
|
+
}
|
|
411
|
+
/** A change its taker couldn't show, told as a notice instead, or dropped when none can be written. */
|
|
412
|
+
tell(cs) {
|
|
413
|
+
this.notice(cs);
|
|
317
414
|
}
|
|
318
|
-
/** The change a carrier's hints made, or an empty one; taken as changeText's is. */
|
|
319
415
|
changeOf(carrier) {
|
|
320
|
-
if (!isObj(carrier))
|
|
321
|
-
return emptyChange();
|
|
322
|
-
this.unclaimed.delete(carrier);
|
|
323
416
|
return this.readChanges.get(carrier) ?? emptyChange();
|
|
324
417
|
}
|
|
325
418
|
// ── recovery (RFC §6.1) ──
|
|
@@ -341,8 +434,11 @@ export class ToolsetController {
|
|
|
341
434
|
/**
|
|
342
435
|
* Recovering: re-read tools/list (presence, declared definitions), then
|
|
343
436
|
* toolsets/list (dynamic-tool state in full), so every tool the statement
|
|
344
|
-
* names is already known
|
|
345
|
-
*
|
|
437
|
+
* names is already known, unless tools/list failed: a tool the last list
|
|
438
|
+
* lacks then stays unknown until a later change or list starts a recovery.
|
|
439
|
+
* Each applies where it's read. What
|
|
440
|
+
* changed is told. (stdio and WebSocket deliver in order, so no early rev is
|
|
441
|
+
* held.)
|
|
346
442
|
*/
|
|
347
443
|
async recover() {
|
|
348
444
|
const connection = this.connection;
|
|
@@ -350,13 +446,14 @@ export class ToolsetController {
|
|
|
350
446
|
return;
|
|
351
447
|
this.recovering = true;
|
|
352
448
|
try {
|
|
353
|
-
|
|
449
|
+
await this.ownToolList(connection);
|
|
450
|
+
// RFC §6.1: toolsets/list after tools/list, so every tool it names is known, unless tools/list failed.
|
|
354
451
|
if (connection !== this.connection)
|
|
355
452
|
return;
|
|
356
453
|
const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'recovery');
|
|
357
454
|
if (connection !== this.connection)
|
|
358
455
|
return;
|
|
359
|
-
this.deliver(
|
|
456
|
+
this.deliver(lists);
|
|
360
457
|
}
|
|
361
458
|
finally {
|
|
362
459
|
this.recovering = false;
|
|
@@ -366,34 +463,22 @@ export class ToolsetController {
|
|
|
366
463
|
}
|
|
367
464
|
}
|
|
368
465
|
}
|
|
466
|
+
/** The tap applies the list and tells its change as it's read. */
|
|
369
467
|
async ownToolList(connection) {
|
|
370
|
-
if (this.ownLists++ === 0)
|
|
371
|
-
this.ownListChange = null;
|
|
372
468
|
try {
|
|
373
|
-
// The tap applied each page as it was read, and kept what the whole
|
|
374
|
-
// list changed (ownListChange).
|
|
375
469
|
await connection.sendToolsList();
|
|
376
470
|
}
|
|
377
471
|
catch (error) {
|
|
378
|
-
this.log(`tools/list failed: ${
|
|
379
|
-
}
|
|
380
|
-
finally {
|
|
381
|
-
this.ownLists--;
|
|
472
|
+
this.log(`tools/list failed: ${this.failure(error)}`);
|
|
382
473
|
}
|
|
383
|
-
const collected = this.ownListChange ?? emptyChange();
|
|
384
|
-
if (this.ownLists === 0)
|
|
385
|
-
this.ownListChange = null;
|
|
386
|
-
else
|
|
387
|
-
this.ownListChange = emptyChange();
|
|
388
|
-
return collected;
|
|
389
474
|
}
|
|
390
475
|
async ownToolsetList(connection, timeoutMs, why) {
|
|
391
476
|
try {
|
|
392
477
|
const r = await connection.sendToolsetsList(timeoutMs);
|
|
393
|
-
return this.
|
|
478
|
+
return this.takeChange(r);
|
|
394
479
|
}
|
|
395
480
|
catch (error) {
|
|
396
|
-
this.log(`toolsets/list (${why}) failed: ${
|
|
481
|
+
this.log(`toolsets/list (${why}) failed: ${this.failure(error)}`);
|
|
397
482
|
return emptyChange();
|
|
398
483
|
}
|
|
399
484
|
}
|
|
@@ -411,60 +496,67 @@ export class ToolsetController {
|
|
|
411
496
|
if (!this.presence.active)
|
|
412
497
|
return;
|
|
413
498
|
const epoch = connection.transportEpoch;
|
|
414
|
-
|
|
499
|
+
await this.ownToolList(connection);
|
|
415
500
|
if (connection !== this.connection || connection.transportEpoch !== epoch)
|
|
416
501
|
return;
|
|
417
502
|
const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'after connecting');
|
|
418
503
|
if (connection !== this.connection || connection.transportEpoch !== epoch)
|
|
419
504
|
return;
|
|
420
|
-
this.deliver(
|
|
505
|
+
this.deliver(lists);
|
|
421
506
|
}
|
|
422
507
|
/**
|
|
423
508
|
* Re-read what the server holds open (titles, visibility) without telling
|
|
424
|
-
* it
|
|
425
|
-
*
|
|
509
|
+
* it, for look. Returns what the re-read changed, taken from the notice it
|
|
510
|
+
* would otherwise get when `shown` (its result reaches an agent): look then
|
|
511
|
+
* either shows it (told) or hands it back (tell). Otherwise the change is
|
|
512
|
+
* left to its notice, and returned to be shown to the caller alone. Null
|
|
513
|
+
* when nothing was read.
|
|
426
514
|
*/
|
|
427
|
-
async refreshOpen() {
|
|
515
|
+
async refreshOpen(opts) {
|
|
428
516
|
if (!this.presence.active)
|
|
429
|
-
return
|
|
517
|
+
return null;
|
|
430
518
|
const connection = this.connection;
|
|
431
519
|
if (!connection || !connection.isConnected)
|
|
432
|
-
return
|
|
520
|
+
return null;
|
|
433
521
|
try {
|
|
434
522
|
const r = await connection.sendToolsetsList(REFRESH_TIMEOUT_MS);
|
|
435
|
-
|
|
523
|
+
if (!isObj(r))
|
|
524
|
+
return null;
|
|
525
|
+
return opts.shown ? this.takeChange(r) : this.changeOf(r);
|
|
436
526
|
}
|
|
437
527
|
catch (error) {
|
|
438
|
-
this.log(`toolsets/list (look) failed: ${
|
|
439
|
-
return
|
|
528
|
+
this.log(`toolsets/list (look) failed: ${this.failure(error)}`);
|
|
529
|
+
return null;
|
|
440
530
|
}
|
|
441
531
|
}
|
|
442
532
|
// ── the host's acts ──
|
|
443
|
-
/** toolsets/open (RFC §5.2). The server's refusal rejects.
|
|
444
|
-
|
|
533
|
+
/** toolsets/open (RFC §5.2). The server's refusal rejects. Its change is
|
|
534
|
+
* taken when `shown` (its result reaches an agent), else left to its
|
|
535
|
+
* notice and returned for the caller alone; close's is the same. */
|
|
536
|
+
async open(toolset, instance, options, opts) {
|
|
445
537
|
const connection = this.live();
|
|
446
538
|
const r = await connection.sendToolsetsOpen({ toolset, ...(instance ? { instance } : {}), ...(options ? { options } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
|
|
447
539
|
const res = isObj(r) ? r : {};
|
|
448
540
|
const inst = typeof res.instance === 'string' && res.instance ? res.instance : instance;
|
|
449
541
|
// RFC §5.2: a proposed name is used as given or the open refused.
|
|
450
542
|
if (instance && inst !== instance)
|
|
451
|
-
this.log(`proposed instance
|
|
543
|
+
this.log(`proposed instance ${this.field(instance)} came back as ${this.field(inst)} (RFC-014 §5.2 forbids renaming): using the server's`);
|
|
452
544
|
const key = keyOf(toolset, this.presence.declared[toolset]?.instances ? inst : undefined);
|
|
453
545
|
const title = typeof res.title === 'string' && res.title ? res.title : this.presence.openRecords().find((o) => keyOf(o.toolset, o.instance) === key)?.title;
|
|
454
546
|
return {
|
|
455
547
|
key,
|
|
456
548
|
...(title ? { title } : {}),
|
|
457
549
|
content: Array.isArray(res.content) ? res.content : [],
|
|
458
|
-
change: this.changeOf(res),
|
|
550
|
+
change: opts.shown ? this.takeChange(res) : this.changeOf(res),
|
|
459
551
|
adopted: res.adopted === true,
|
|
460
552
|
};
|
|
461
553
|
}
|
|
462
554
|
/** toolsets/close (RFC §5.2). */
|
|
463
|
-
async close(toolset, instance) {
|
|
555
|
+
async close(toolset, instance, opts) {
|
|
464
556
|
const connection = this.live();
|
|
465
557
|
const r = await connection.sendToolsetsClose({ toolset, ...(instance ? { instance } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
|
|
466
558
|
const res = isObj(r) ? r : {};
|
|
467
|
-
return { content: Array.isArray(res.content) ? res.content : [], change: this.changeOf(res) };
|
|
559
|
+
return { content: Array.isArray(res.content) ? res.content : [], change: opts.shown ? this.takeChange(res) : this.changeOf(res) };
|
|
468
560
|
}
|
|
469
561
|
/** Open or close an undeclared group (RFC §5.4): the host's own act; the server isn't told. */
|
|
470
562
|
setGroupOpen(group, open) {
|
|
@@ -489,7 +581,7 @@ export class ToolsetController {
|
|
|
489
581
|
this.presence.deactivate();
|
|
490
582
|
this.log('the server withdrew toolHints: every tool is static from now on');
|
|
491
583
|
const cs = this.presence.diff(before, this.presence.view());
|
|
492
|
-
cs.notes.push(`${this.serverId} no longer presents toolsets: its tools are
|
|
584
|
+
cs.notes.push(`${this.serverId} no longer presents toolsets: its tools are called directly now, by name, not through ${META_TOOL_NAMES.act}.`);
|
|
493
585
|
this.deliver(cs);
|
|
494
586
|
return;
|
|
495
587
|
}
|
|
@@ -502,11 +594,16 @@ export class ToolsetController {
|
|
|
502
594
|
this.presence.setManifest(declarations);
|
|
503
595
|
this.deliver(this.presence.diff(before, this.presence.view()));
|
|
504
596
|
}
|
|
505
|
-
/**
|
|
506
|
-
applyMoves() {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
597
|
+
/** At a rebuild of the model's tool list (RFC §4.2), tell where the tools it moved went: `moves`, those still pending, or every one pending. A move a list has undone since the rebuild listed the server isn't told; the next rebuild publishes the undoing. */
|
|
598
|
+
applyMoves(moves) {
|
|
599
|
+
const moved = this.presence.applyMoves(moves);
|
|
600
|
+
if (!moved.length)
|
|
601
|
+
return;
|
|
602
|
+
const cs = emptyChange();
|
|
603
|
+
cs.notes.push(...moved.map(({ name, to }) => to === 'dynamic'
|
|
604
|
+
? `${nameToTypeBack(name)} left your tool list: it's announced when available, and called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:${quotedValue(name)}, args}.`
|
|
605
|
+
: `${nameToTypeBack(name)} is in your tool list now, as ${nameToTypeBack(this.deps.listedName(this.serverId, name))}.`));
|
|
606
|
+
this.deliver(cs);
|
|
510
607
|
}
|
|
511
608
|
}
|
|
512
609
|
/** Every server's controller, for the meta-tools and reminders. */
|
|
@@ -534,8 +631,11 @@ export class ToolsetHub {
|
|
|
534
631
|
get(serverId) {
|
|
535
632
|
return this.controllers.get(serverId);
|
|
536
633
|
}
|
|
537
|
-
/** A server's configuration went (removed):
|
|
634
|
+
/** A server's configuration went (removed): its controller lets go of the
|
|
635
|
+
* connection it holds, and is forgotten, so a server added later under
|
|
636
|
+
* the same id starts from nothing. */
|
|
538
637
|
forget(serverId) {
|
|
638
|
+
this.controllers.get(serverId)?.release();
|
|
539
639
|
this.controllers.delete(serverId);
|
|
540
640
|
}
|
|
541
641
|
all() {
|
|
@@ -556,8 +656,9 @@ export class ToolsetHub {
|
|
|
556
656
|
* reminder due.
|
|
557
657
|
*/
|
|
558
658
|
observeCompile(agentName, messages) {
|
|
659
|
+
const host = this.deps.hostOpen?.(agentName) ?? [];
|
|
559
660
|
const open = this.openMarkers();
|
|
560
|
-
if (open.length === 0) {
|
|
661
|
+
if (open.length === 0 && host.length === 0) {
|
|
561
662
|
this.seen.delete(agentName);
|
|
562
663
|
this.due.delete(agentName);
|
|
563
664
|
return;
|
|
@@ -575,6 +676,11 @@ export class ToolsetHub {
|
|
|
575
676
|
if (!open.includes(marker))
|
|
576
677
|
seen.delete(marker);
|
|
577
678
|
this.seen.set(agentName, seen);
|
|
679
|
+
// A host toolset open across a restart was never seen here: it's due
|
|
680
|
+
// whenever a request doesn't carry its marker.
|
|
681
|
+
for (const h of host)
|
|
682
|
+
if (h.dueWhenAbsent && !text.includes(h.marker))
|
|
683
|
+
missing = true;
|
|
578
684
|
// Due until a request carries every marker the agent has seen again: a
|
|
579
685
|
// gather that doesn't send (a preview) leaves it due for the next start.
|
|
580
686
|
if (missing)
|
|
@@ -586,7 +692,10 @@ export class ToolsetHub {
|
|
|
586
692
|
reminderFor(agentName) {
|
|
587
693
|
if (!this.due.has(agentName))
|
|
588
694
|
return null;
|
|
589
|
-
const parts =
|
|
695
|
+
const parts = [
|
|
696
|
+
...(this.deps.hostOpen?.(agentName) ?? []).map((h) => h.reminder()),
|
|
697
|
+
...this.presenting().filter((c) => this.deps.canNotify(c.serverId)).map((c) => c.presence.reminder()).filter((r) => !!r),
|
|
698
|
+
];
|
|
590
699
|
if (parts.length === 0)
|
|
591
700
|
return null;
|
|
592
701
|
return `Open toolsets (a reminder: their earlier announcements have left your context):\n${parts.join('\n')}`;
|