@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
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* results, in the results of calls that changed them, in push events and in
|
|
14
14
|
* notices) and called through `toolsets--act`, which this module only
|
|
15
15
|
* resolves (resolveAct): the framework dispatches the call it resolves to
|
|
16
|
-
* down the same path as a direct call of the server's tool. The module is registered
|
|
17
|
-
*
|
|
16
|
+
* down the same path as a direct call of the server's tool. The module is registered at create(),
|
|
17
|
+
* for every agent (the host's own toolsets use it too), unless a module given to create() holds
|
|
18
|
+
* the name; its tools never change after that.
|
|
18
19
|
*
|
|
19
20
|
* Its gatherContext publishes the RFC §8.3 reminder, as a stored `afterUser`
|
|
20
21
|
* injection, when an agent's earlier announcements of an open toolset have
|
|
@@ -22,12 +23,15 @@
|
|
|
22
23
|
*
|
|
23
24
|
* Ported from mcpl-cc-bridge's meta-tools.ts, the reference host.
|
|
24
25
|
*/
|
|
26
|
+
import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
|
|
25
27
|
import type { EventResponse, Module, ModuleContext, ModuleContextInjection, ProcessState } from '../types/module.js';
|
|
26
28
|
import type { ProcessEvent, ToolCall, ToolDefinition, ToolResult } from '../types/events.js';
|
|
27
29
|
import { unknownToolMessage } from '../modules/unknown-tool.js';
|
|
28
30
|
import { META_TOOL_NAMES, TOOLSETS_MODULE, type ToolsetController, type ToolsetHub } from './toolset-host.js';
|
|
29
|
-
import { keyOf, type ChangeSet } from './tool-presence.js';
|
|
30
|
-
import { checkArgs, renderToolDefinition, type HintedTool } from './tool-signature.js';
|
|
31
|
+
import { keyOf, type ChangeSet, type ToolPresence } from './tool-presence.js';
|
|
32
|
+
import { checkArgs, jsonText, renderToolDefinition, type HintedTool } from './tool-signature.js';
|
|
33
|
+
import { McplRequestError } from './server-connection.js';
|
|
34
|
+
import { BUILTIN_TOOLSET, type HostPresentation } from '../host-presentation.js';
|
|
31
35
|
|
|
32
36
|
/** The namespace a reminder's stored injection carries. */
|
|
33
37
|
export const TOOLSETS_REMINDER_NAMESPACE = 'mcpl-toolsets';
|
|
@@ -36,26 +40,28 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
36
40
|
{
|
|
37
41
|
name: 'open',
|
|
38
42
|
description:
|
|
39
|
-
'Open a toolset
|
|
40
|
-
'
|
|
41
|
-
'
|
|
43
|
+
'Open a toolset so its tools can be called with toolsets--act: the host\'s own settings and controls (toolset "settings"), ' +
|
|
44
|
+
'an MCPL server\'s tab (server alone: its tools without putting them in your tool list; a closed tab is connected again; ' +
|
|
45
|
+
'full: true puts the server back in your tool list), or a toolset an MCPL server declares (a game, a building mode, a card table: ' +
|
|
46
|
+
'server and toolset). Returns the tools available now. instance names a separate copy where a toolset allows several; ' +
|
|
42
47
|
'options as toolsets--look lists them.',
|
|
43
48
|
inputSchema: {
|
|
44
49
|
type: 'object',
|
|
45
50
|
properties: {
|
|
46
51
|
server: { type: 'string', description: 'the MCPL server id' },
|
|
47
|
-
toolset: { type: 'string' },
|
|
52
|
+
toolset: { type: 'string', description: '"settings" for the host\'s own; a server\'s toolset with server' },
|
|
48
53
|
instance: { type: 'string', description: 'a name for this copy (toolsets with instances only)' },
|
|
49
54
|
options: { type: 'object', description: 'toolset options, as toolsets--look shows them' },
|
|
55
|
+
full: { type: 'boolean', description: 'with server alone: put the server\'s tools back in your tool list' },
|
|
50
56
|
},
|
|
51
57
|
},
|
|
52
58
|
},
|
|
53
59
|
{
|
|
54
60
|
name: 'act',
|
|
55
61
|
description:
|
|
56
|
-
'Call a tool
|
|
62
|
+
'Call a tool of an open toolset (the host\'s "settings", a server\'s tab, or a server\'s own toolset) or another dynamic tool, as announced in the conversation; they are not in your tool list. ' +
|
|
57
63
|
'args are the arguments its signature shows; instance picks the open copy when several are open; server is needed only when two servers have the tool. ' +
|
|
58
|
-
'Returns the server\'s result, then any change in which tools are available.',
|
|
64
|
+
'Returns the server\'s result, then any change it made in which tools are available, unless that change already went out as a notice. toolsets--look shows what is available now.',
|
|
59
65
|
inputSchema: {
|
|
60
66
|
type: 'object',
|
|
61
67
|
properties: {
|
|
@@ -70,7 +76,7 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
70
76
|
{
|
|
71
77
|
name: 'look',
|
|
72
78
|
description:
|
|
73
|
-
'Show what is open and what can be opened
|
|
79
|
+
'Show what is open and what can be opened (the host\'s "settings", each MCPL server\'s tab, servers\' own toolsets), with the tools usable now: announcements earlier in the conversation may be out of date. ' +
|
|
74
80
|
'With tool: that tool\'s full description and input schema. With toolset or instance: just that one.',
|
|
75
81
|
inputSchema: {
|
|
76
82
|
type: 'object',
|
|
@@ -84,19 +90,63 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
84
90
|
},
|
|
85
91
|
{
|
|
86
92
|
name: 'close',
|
|
87
|
-
description: 'Close an open toolset or instance: its tools and its events
|
|
93
|
+
description: 'Close an open toolset or instance: its tools stop, and for a toolset the server declares, the server is told and stops its events too. Closing a game in progress may count as leaving it. ' +
|
|
94
|
+
'server alone moves a server out of your tool list into a tab (connected, its events still come), or hides its open tab; with disconnect: true it is disconnected until you open it again.',
|
|
88
95
|
inputSchema: {
|
|
89
96
|
type: 'object',
|
|
90
97
|
properties: {
|
|
91
98
|
server: { type: 'string', description: 'the MCPL server id (may be left out when one server has the toolset open)' },
|
|
92
|
-
toolset: { type: 'string', description: 'may be left out when only one thing is open on the server' },
|
|
99
|
+
toolset: { type: 'string', description: '"settings" for the host\'s own; may be left out when only one thing is open on the server' },
|
|
93
100
|
instance: { type: 'string' },
|
|
101
|
+
disconnect: { type: 'boolean', description: 'with server alone: disconnect it as a closed tab until it is opened again' },
|
|
94
102
|
},
|
|
95
103
|
},
|
|
96
104
|
},
|
|
97
105
|
];
|
|
98
106
|
|
|
99
107
|
const str = (v: unknown): string | undefined => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
|
108
|
+
|
|
109
|
+
const selector = (v: unknown, has: (name: string) => boolean): string | undefined =>
|
|
110
|
+
typeof v === 'string' && v !== v.trim() && v.trim() !== '' && has(v) ? v : str(v);
|
|
111
|
+
const presentersOf = (hub: ToolsetHub, server: unknown): ToolsetController[] => {
|
|
112
|
+
const id = str(server);
|
|
113
|
+
if (!id) return hub.presenting();
|
|
114
|
+
const c = hub.get(id);
|
|
115
|
+
return c ? [c] : [];
|
|
116
|
+
};
|
|
117
|
+
const toolNamed = (hub: ToolsetHub, server: unknown, v: unknown): string | undefined =>
|
|
118
|
+
selector(v, (name) => presentersOf(hub, server).some((c) => !!c.presence.tool(name)));
|
|
119
|
+
const toolsetNamed = (hub: ToolsetHub, server: unknown, v: unknown): string | undefined =>
|
|
120
|
+
selector(v, (name) => presentersOf(hub, server).some((c) => c.presence.isDeclared(name) || c.presence.undeclaredGroups.includes(name)));
|
|
121
|
+
const instanceNamed = (presenters: readonly ToolsetController[], toolset: string | undefined, v: unknown): string | undefined =>
|
|
122
|
+
selector(v, (name) => presenters.some((c) => c.presence.openRecords(toolset).some((o) => o.instance === name)));
|
|
123
|
+
|
|
124
|
+
const SELECTORS = ['server', 'toolset', 'instance', 'tool'] as const;
|
|
125
|
+
|
|
126
|
+
function checkSelectors(args: Record<string, unknown>): void {
|
|
127
|
+
for (const key of SELECTORS) {
|
|
128
|
+
const v = args[key];
|
|
129
|
+
if (v === undefined || v === null || str(v) !== undefined) continue;
|
|
130
|
+
const got = typeof v === 'string' ? 'a blank string' : Array.isArray(v) ? 'a list' : typeof v === 'object' ? 'an object' : `a ${typeof v}`;
|
|
131
|
+
fail(`${key} must be a name, or left out; got ${got}, so nothing was done`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A failure whose outcome is unknown, with how to find it out. */
|
|
136
|
+
export function withLookHint(serverId: string, text: string): string {
|
|
137
|
+
const said = text.trimEnd();
|
|
138
|
+
return `${said}${/[.!?]$/.test(said) ? '' : '.'} ${META_TOOL_NAMES.look} {server:"${serverId}"} asks it what's open, or says it couldn't.`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function requestFailure(serverId: string, did: string, unconfirmed: string, error: unknown): never {
|
|
142
|
+
const message = suppliedValue(error instanceof Error ? error.message : String(error));
|
|
143
|
+
if (error instanceof McplRequestError && error.outcome === 'not-sent') return fail(`${serverId} ${did}: ${message}`);
|
|
144
|
+
return fail(withLookHint(serverId, `${serverId} ${unconfirmed}: ${message}`));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** A result's tool change, for whoever shows it with the result (ToolsetsModule's takerFor). */
|
|
148
|
+
export type TakenChange = { toolsets: ToolsetController; cs: ChangeSet };
|
|
149
|
+
|
|
100
150
|
const isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
101
151
|
|
|
102
152
|
type Block = { type: string; text?: string; [k: string]: unknown };
|
|
@@ -142,11 +192,11 @@ function pick(hub: ToolsetHub, server: unknown): ToolsetController {
|
|
|
142
192
|
const ids = hub.presenting().map((c) => c.serverId);
|
|
143
193
|
if (!id) {
|
|
144
194
|
if (ids.length === 1) return hub.get(ids[0]!)!;
|
|
145
|
-
return fail(`name a server: ${ids.join(', ')
|
|
195
|
+
return fail(ids.length ? `name a server: ${ids.join(', ')}` : NONE_PRESENTS);
|
|
146
196
|
}
|
|
147
197
|
const c = hub.get(id);
|
|
148
198
|
if (!c) return fail(`no server "${id}" presents toolsets; the ones that do: ${ids.join(', ') || 'none'}`);
|
|
149
|
-
if (!c.presence.active) return fail(`${id} sends no tool hints now: its tools are
|
|
199
|
+
if (!c.presence.active) return fail(`${id} sends no tool hints now: its tools are called directly, by name, not through ${META_TOOL_NAMES.act}`);
|
|
150
200
|
return c;
|
|
151
201
|
}
|
|
152
202
|
|
|
@@ -158,9 +208,15 @@ function owner(hub: ToolsetHub, server: unknown, has: (c: ToolsetController) =>
|
|
|
158
208
|
if (str(server)) return pick(hub, server);
|
|
159
209
|
const owners = hub.presenting().filter(has);
|
|
160
210
|
if (owners.length === 1) return owners[0]!;
|
|
161
|
-
return fail(owners.
|
|
162
|
-
|
|
163
|
-
|
|
211
|
+
if (owners.length) return fail(`${what} is on several servers (${owners.map((c) => c.serverId).join(', ')}): add server`);
|
|
212
|
+
return fail(hub.presenting().length ? `no server that presents toolsets has ${what}: ${META_TOOL_NAMES.look} shows what they have` : NONE_PRESENTS);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const NONE_PRESENTS = 'no MCPL server presents toolsets now: MCPL tools are called directly, by name';
|
|
216
|
+
|
|
217
|
+
function noToolset(c: ToolsetController, toolset: string): string {
|
|
218
|
+
const names = [...Object.keys(c.presence.declared), ...c.presence.undeclaredGroups];
|
|
219
|
+
return `${c.serverId} has no toolset "${toolset}"; ${names.length ? `it has ${names.map(nameToTypeBack).join(', ')}` : 'it has none'}`;
|
|
164
220
|
}
|
|
165
221
|
|
|
166
222
|
/** What a `toolsets--act` call resolves to: the server's tool, its instance and arguments. */
|
|
@@ -171,42 +227,51 @@ export type ActTarget = { ok: true; serverId: string; tool: string; instance?: s
|
|
|
171
227
|
* or the reason it can't be sent. The framework then dispatches it exactly as
|
|
172
228
|
* a direct call of that tool, with `_meta["mcpl/instance"]` added (RFC §5.5).
|
|
173
229
|
* Refused here, before anything is sent: a tool no presenting server has, a
|
|
174
|
-
*
|
|
230
|
+
* tool neither the published list nor the server's latest tools/list puts as dynamic (RFC §4.2), an instance that isn't named where
|
|
175
231
|
* several are open, a group the host has closed (RFC §5.4), and arguments
|
|
176
232
|
* that don't fit the definition in force for that instance (RFC §6.1).
|
|
177
233
|
*/
|
|
234
|
+
const hasInstances = (p: ToolPresence, ts: string | undefined): boolean => ts !== undefined && p.isDeclared(ts) && !!p.declared[ts]!.instances;
|
|
235
|
+
|
|
236
|
+
/** The key whose definition act checks a call of `tool` against (RFC §6.1): an instance's own, else the toolset's, else the listed one. */
|
|
237
|
+
function actKey(p: ToolPresence, tool: string, instance: string | undefined): string {
|
|
238
|
+
const ts = p.toolsetOf(tool);
|
|
239
|
+
return ts ? keyOf(ts, hasInstances(p, ts) ? instance : undefined) : keyOf('');
|
|
240
|
+
}
|
|
241
|
+
|
|
178
242
|
export function resolveAct(hub: ToolsetHub, input: unknown): ActTarget {
|
|
179
243
|
try {
|
|
180
244
|
const args = isObj(input) ? input : {};
|
|
181
|
-
|
|
245
|
+
checkSelectors(args);
|
|
246
|
+
const tool = toolNamed(hub, args.server, args.tool) ?? fail('name the tool');
|
|
182
247
|
const c = owner(hub, args.server, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
|
|
183
248
|
const p = c.presence;
|
|
184
249
|
if (!p.tool(tool)) fail(`${c.serverId} has no tool "${tool}": ${META_TOOL_NAMES.look} {server:"${c.serverId}"} shows what's available`);
|
|
185
|
-
if (p.
|
|
250
|
+
if (!p.shownAs(tool, 'dynamic')) fail(`${nameToTypeBack(tool)} is not a dynamic tool: ${p.toolStatus(tool)}. Call it there.`);
|
|
186
251
|
if (args.args !== undefined && !isObj(args.args)) fail('args must be an object of the tool\'s arguments');
|
|
187
252
|
const callArgs = (args.args ?? {}) as Record<string, unknown>;
|
|
188
|
-
let instance = str(args.instance);
|
|
189
253
|
const ts = p.toolsetOf(tool);
|
|
254
|
+
let instance = instanceNamed([c], ts, args.instance);
|
|
190
255
|
const declared = ts !== undefined && p.isDeclared(ts);
|
|
191
|
-
const withInstances =
|
|
256
|
+
const withInstances = hasInstances(p, ts);
|
|
192
257
|
if (withInstances && !instance) {
|
|
193
258
|
// RFC §5.5: the instance rides in _meta; with exactly one open, it's that one.
|
|
194
259
|
const open = p.openRecords(ts);
|
|
195
260
|
if (open.length === 1) instance = open[0]!.instance;
|
|
196
|
-
else if (!open.length) fail(`${tool} belongs to ${ts}, which isn't open: ${p.openHint(ts!)}`);
|
|
197
|
-
else fail(`several ${ts} instances are open (${open.map((o) => o.instance).join(', ')}): say which with instance`);
|
|
261
|
+
else if (!open.length) fail(`${nameToTypeBack(tool)} belongs to ${nameToTypeBack(ts!)}, which isn't open: ${p.openHint(ts!)}`);
|
|
262
|
+
else fail(`several ${nameToTypeBack(ts!)} instances are open (${open.map((o) => nameToTypeBack(o.instance!)).join(', ')}): say which with instance`);
|
|
198
263
|
}
|
|
199
|
-
if (instance && !withInstances) fail(`${tool} ${ts ? `belongs to ${ts}, which has no instances` : 'belongs to no toolset'}: call it without instance`);
|
|
264
|
+
if (instance && !withInstances) fail(`${nameToTypeBack(tool)} ${ts ? `belongs to ${nameToTypeBack(ts)}, which has no instances` : 'belongs to no toolset'}: call it without instance`);
|
|
200
265
|
// RFC §5.4: the host gates calls into an undeclared group it has closed.
|
|
201
|
-
if (ts && !declared && !p.isOpen(ts)) fail(`${tool} is in ${ts}, which isn't open: ${p.openHint(ts)}`);
|
|
266
|
+
if (ts && !declared && !p.isOpen(ts)) fail(`${nameToTypeBack(tool)} is in ${nameToTypeBack(ts)}, which isn't open: ${p.openHint(ts)}`);
|
|
202
267
|
// RFC §6.1: arguments are checked against the definition in force for
|
|
203
268
|
// that instance: its own from hints (the legal moves of one game), else
|
|
204
269
|
// the toolset's, else the listed one.
|
|
205
|
-
const key =
|
|
270
|
+
const key = actKey(p, tool, instance);
|
|
206
271
|
const problem = checkArgs(p.defOf(key, tool).inputSchema, callArgs);
|
|
207
272
|
if (problem) {
|
|
208
|
-
fail(`Invalid arguments for ${tool} in ${p.label(key)}: ${problem}. Nothing was sent; ` +
|
|
209
|
-
`${META_TOOL_NAMES.look} {server:"${c.serverId}", tool
|
|
273
|
+
fail(`Invalid arguments for ${nameToTypeBack(tool)} in ${p.label(key)}: ${problem}. Nothing was sent; ` +
|
|
274
|
+
`${META_TOOL_NAMES.look} {server:"${c.serverId}", tool:${quotedValue(tool)}${instance ? `, instance:${quotedValue(instance)}` : ''}} shows its current definition.`);
|
|
210
275
|
}
|
|
211
276
|
return { ok: true, serverId: c.serverId, tool, ...(instance ? { instance } : {}), args: callArgs };
|
|
212
277
|
} catch (error) {
|
|
@@ -218,7 +283,15 @@ export function resolveAct(hub: ToolsetHub, input: unknown): ActTarget {
|
|
|
218
283
|
export class ToolsetsModule implements Module {
|
|
219
284
|
readonly name = TOOLSETS_MODULE;
|
|
220
285
|
|
|
221
|
-
|
|
286
|
+
/** `takerFor`: who shows a call's result to an agent, taking the tool
|
|
287
|
+
* change rendered into it; null when the result may reach none, and its
|
|
288
|
+
* change is left to a notice (RFC-014 §6.1: told once). */
|
|
289
|
+
constructor(
|
|
290
|
+
private readonly hub: ToolsetHub,
|
|
291
|
+
private readonly takerFor: (call: ToolCall) => ((taken: TakenChange) => void) | null,
|
|
292
|
+
/** The host's own toolsets (the built-in "settings" and server tabs), when the framework presents them. */
|
|
293
|
+
private readonly host: HostPresentation | null = null,
|
|
294
|
+
) {}
|
|
222
295
|
|
|
223
296
|
async start(_ctx: ModuleContext): Promise<void> {}
|
|
224
297
|
async stop(): Promise<void> {}
|
|
@@ -241,11 +314,22 @@ export class ToolsetsModule implements Module {
|
|
|
241
314
|
async handleToolCall(call: ToolCall): Promise<ToolResult> {
|
|
242
315
|
const args = isObj(call.input) ? call.input : {};
|
|
243
316
|
try {
|
|
317
|
+
checkSelectors(args);
|
|
318
|
+
const hosted = await this.hostCall(call.name, args, call.callerAgentName);
|
|
319
|
+
if (hosted) return hosted;
|
|
320
|
+
const take = this.takerFor(call);
|
|
321
|
+
const lead = call.name === 'look' ? this.hostLookLead(args, call.callerAgentName) : null;
|
|
322
|
+
if (lead !== null) {
|
|
323
|
+
if (!this.hub.presenting().length) return textResult(lead);
|
|
324
|
+
const theirs = await this.look(args, take);
|
|
325
|
+
if (theirs.isError || typeof theirs.data !== 'string') return theirs;
|
|
326
|
+
return textResult(`${lead}\n\n${theirs.data}`);
|
|
327
|
+
}
|
|
244
328
|
switch (call.name) {
|
|
245
|
-
case 'open': return await this.open(args);
|
|
329
|
+
case 'open': return await this.open(args, take);
|
|
246
330
|
case 'act': return this.act(args);
|
|
247
|
-
case 'look': return await this.look(args);
|
|
248
|
-
case 'close': return await this.close(args);
|
|
331
|
+
case 'look': return await this.look(args, take);
|
|
332
|
+
case 'close': return await this.close(args, take);
|
|
249
333
|
default: return { success: false, isError: true, error: unknownToolMessage(this.name, call.name, META_TOOLS) };
|
|
250
334
|
}
|
|
251
335
|
} catch (error) {
|
|
@@ -256,67 +340,127 @@ export class ToolsetsModule implements Module {
|
|
|
256
340
|
}
|
|
257
341
|
}
|
|
258
342
|
|
|
343
|
+
/**
|
|
344
|
+
* The host's own toolsets: `toolset: "settings"` alone, or `server` alone
|
|
345
|
+
* (a server's tab), for open, close and look; a tool a host toolset has, for
|
|
346
|
+
* look. Null for everything else, which the servers' toolsets answer.
|
|
347
|
+
*/
|
|
348
|
+
private async hostCall(name: string, args: Record<string, unknown>, agentName: string | undefined): Promise<ToolResult | null> {
|
|
349
|
+
const host = this.host;
|
|
350
|
+
if (!host || !agentName || !host.active(agentName)) return null;
|
|
351
|
+
const server = str(args.server);
|
|
352
|
+
const toolset = str(args.toolset);
|
|
353
|
+
const instance = str(args.instance);
|
|
354
|
+
const tool = str(args.tool);
|
|
355
|
+
const builtin = toolset === BUILTIN_TOOLSET && !server && !instance;
|
|
356
|
+
const tab = !!server && !toolset && !instance && args.options === undefined;
|
|
357
|
+
if (args.full !== undefined && typeof args.full !== 'boolean') fail('full must be true or false');
|
|
358
|
+
if (args.disconnect !== undefined && typeof args.disconnect !== 'boolean') fail('disconnect must be true or false');
|
|
359
|
+
if (tab && !host.knows(server!) && !this.hub.get(server!)) fail(`no MCPL server "${server}" is configured here`);
|
|
360
|
+
switch (name) {
|
|
361
|
+
case 'open':
|
|
362
|
+
if (builtin) return textResult(host.openBuiltin(agentName));
|
|
363
|
+
if (tab) return textResult(await host.openServer(agentName, server!, { full: args.full === true }));
|
|
364
|
+
if (args.full !== undefined) fail('full goes with server alone');
|
|
365
|
+
return null;
|
|
366
|
+
case 'close':
|
|
367
|
+
if (builtin) return textResult(host.closeBuiltin(agentName));
|
|
368
|
+
if (tab) return textResult(await host.closeServer(agentName, server!, { disconnect: args.disconnect === true }));
|
|
369
|
+
if (args.disconnect !== undefined) fail('disconnect goes with server alone');
|
|
370
|
+
return null;
|
|
371
|
+
case 'look': {
|
|
372
|
+
// A host toolset's tool, with or without its server named (as act takes it).
|
|
373
|
+
if (tool && !toolset && !instance) {
|
|
374
|
+
const shown = host.lookTool(agentName, tool, server);
|
|
375
|
+
if (shown) return textResult(shown);
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (builtin) return textResult(host.lookToolset(agentName, { builtin: true }));
|
|
379
|
+
// A server's tab, and every host toolset with no selector, go first in
|
|
380
|
+
// what the servers' own look says (handleToolCall adds them).
|
|
381
|
+
if (tab && !tool && !this.hub.get(server!)?.presents) return textResult(host.lookToolset(agentName, { serverId: server! }));
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
default:
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** What the host's own toolsets add ahead of the servers' look: everything, or one presenting server's tab. */
|
|
390
|
+
private hostLookLead(args: Record<string, unknown>, agentName: string | undefined): string | null {
|
|
391
|
+
const host = this.host;
|
|
392
|
+
if (!host || !agentName || !host.active(agentName)) return null;
|
|
393
|
+
const server = str(args.server);
|
|
394
|
+
if (str(args.toolset) || str(args.instance) || str(args.tool)) return null;
|
|
395
|
+
if (!server) return host.catalog(agentName).join('\n');
|
|
396
|
+
return host.knows(server) ? host.lookToolset(agentName, { serverId: server }) : null;
|
|
397
|
+
}
|
|
398
|
+
|
|
259
399
|
private toolsAt(c: ToolsetController, key: string): HintedTool[] {
|
|
260
400
|
return [...(c.presence.view().get(key)?.tools.values() ?? [])];
|
|
261
401
|
}
|
|
262
402
|
|
|
263
403
|
/** A change set rendered without the key the caller already showed whole. */
|
|
264
|
-
private renderOthers(c: ToolsetController, cs: ChangeSet, key: string): string {
|
|
265
|
-
return c.presence.render({ ...cs, changes: cs.changes.filter((ch) => ch.key !== key) });
|
|
404
|
+
private renderOthers(c: ToolsetController, cs: ChangeSet, key: string, shown: boolean): string {
|
|
405
|
+
return c.presence.render({ ...cs, changes: cs.changes.filter((ch) => ch.key !== key) }, { mark: shown, describeAll: !shown });
|
|
266
406
|
}
|
|
267
407
|
|
|
268
408
|
// ── the tools ──
|
|
269
409
|
|
|
270
|
-
private async open(args: Record<string, unknown
|
|
271
|
-
|
|
410
|
+
private async open(args: Record<string, unknown>, take: ((taken: TakenChange) => void) | null): Promise<ToolResult> {
|
|
411
|
+
const shown = take !== null;
|
|
412
|
+
let toolset = toolsetNamed(this.hub, args.server, args.toolset);
|
|
272
413
|
const c = toolset && !str(args.server)
|
|
273
414
|
? owner(this.hub, undefined, (x) => x.presence.isDeclared(toolset!) || x.presence.undeclaredGroups.includes(toolset!), `a toolset "${toolset}"`)
|
|
274
415
|
: pick(this.hub, args.server);
|
|
275
416
|
const p = c.presence;
|
|
276
417
|
const openable = [...Object.keys(p.declared), ...p.undeclaredGroups];
|
|
277
418
|
if (!toolset) {
|
|
278
|
-
if (openable.length !== 1) fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.join(', ')}` : 'no toolsets'}`);
|
|
419
|
+
if (openable.length !== 1) fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.map(nameToTypeBack).join(', ')}` : 'no toolsets'}`);
|
|
279
420
|
toolset = openable[0]!;
|
|
280
421
|
}
|
|
281
|
-
const instance =
|
|
422
|
+
const instance = instanceNamed([c], toolset, args.instance);
|
|
282
423
|
if (args.options !== undefined && !isObj(args.options)) fail('options must be an object');
|
|
283
424
|
const options = args.options as Record<string, unknown> | undefined;
|
|
284
425
|
|
|
285
426
|
if (p.isDeclared(toolset)) {
|
|
286
|
-
if (instance && !p.declared[toolset]!.instances) fail(`${c.serverId}/${toolset} has no instances: open it without one`);
|
|
427
|
+
if (instance && !p.declared[toolset]!.instances) fail(`${c.serverId}/${nameToTypeBack(toolset)} has no instances: open it without one`);
|
|
287
428
|
let opened: Awaited<ReturnType<ToolsetController['open']>>;
|
|
288
429
|
try {
|
|
289
|
-
opened = await c.open(toolset, instance, options);
|
|
430
|
+
opened = await c.open(toolset, instance, options, { shown });
|
|
290
431
|
} catch (error) {
|
|
291
|
-
return
|
|
432
|
+
return requestFailure(c.serverId, `did not open ${nameToTypeBack(toolset)}`, `didn't confirm opening ${nameToTypeBack(toolset)}`, error);
|
|
292
433
|
}
|
|
293
434
|
// RFC §5.2: opening what is open adopts it; its content says which options weren't applied.
|
|
294
435
|
const blocks: Block[] = [
|
|
295
|
-
{ type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${opened.title}` : ''}${opened.adopted ? ' (already open)' : ''}` },
|
|
436
|
+
{ type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${suppliedValue(opened.title)}` : ''}${opened.adopted ? ' (already open)' : ''}` },
|
|
296
437
|
...asBlocks(opened.content),
|
|
297
438
|
];
|
|
298
|
-
const tail = [...p.toolBlock(opened.key, this.toolsAt(c, opened.key), { headed: false })];
|
|
439
|
+
const tail = [...p.toolBlock(opened.key, this.toolsAt(c, opened.key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set<string>() })];
|
|
299
440
|
const gone = opened.change.changes.find((ch) => ch.key === opened.key)?.gone ?? [];
|
|
300
|
-
if (gone.length) tail.push(`now unavailable: ${gone.join(', ')}`);
|
|
301
|
-
const others = this.renderOthers(c, opened.change, opened.key);
|
|
441
|
+
if (gone.length) tail.push(`now unavailable: ${gone.map(suppliedValue).join(', ')}`);
|
|
442
|
+
const others = this.renderOthers(c, opened.change, opened.key, shown);
|
|
302
443
|
if (others) tail.push(others);
|
|
303
444
|
const close = p.closeHint(opened.key);
|
|
304
445
|
if (close) tail.push(`${close} closes it.`);
|
|
305
446
|
blocks.push({ type: 'text', text: tail.join('\n') });
|
|
447
|
+
take?.({ toolsets: c, cs: opened.change });
|
|
306
448
|
return blocksResult(blocks);
|
|
307
449
|
}
|
|
308
450
|
if (p.undeclaredGroups.includes(toolset)) {
|
|
309
451
|
// RFC §5.4: a grouping the server offers but doesn't track: the host opens it, the server isn't told.
|
|
310
|
-
if (instance || options) fail(`${c.serverId}/${toolset} takes no instance or options (the server doesn't declare it)`);
|
|
452
|
+
if (instance || options) fail(`${c.serverId}/${nameToTypeBack(toolset)} takes no instance or options (the server doesn't declare it)`);
|
|
311
453
|
const cs = c.setGroupOpen(toolset, true);
|
|
312
454
|
const key = keyOf(toolset);
|
|
313
|
-
const lines = [`${p.label(key)} open.`, ...p.toolBlock(key, this.toolsAt(c, key), { headed: false })];
|
|
314
|
-
const others = this.renderOthers(c, cs, key);
|
|
455
|
+
const lines = [`${p.label(key)} open.`, ...p.toolBlock(key, this.toolsAt(c, key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set<string>() })];
|
|
456
|
+
const others = this.renderOthers(c, cs, key, shown);
|
|
315
457
|
if (others) lines.push(others);
|
|
316
458
|
lines.push(`${p.closeHint(key)} closes it.`);
|
|
459
|
+
if (take) take({ toolsets: c, cs });
|
|
460
|
+
else c.tell(cs);
|
|
317
461
|
return textResult(lines.join('\n'));
|
|
318
462
|
}
|
|
319
|
-
return fail(
|
|
463
|
+
return fail(noToolset(c, toolset));
|
|
320
464
|
}
|
|
321
465
|
|
|
322
466
|
/**
|
|
@@ -333,42 +477,77 @@ export class ToolsetsModule implements Module {
|
|
|
333
477
|
}
|
|
334
478
|
|
|
335
479
|
/** look restates the present: titles and visibility are re-read from the servers first (toolsets/list). */
|
|
336
|
-
private async look(args: Record<string, unknown
|
|
480
|
+
private async look(args: Record<string, unknown>, take: ((taken: TakenChange) => void) | null): Promise<ToolResult> {
|
|
337
481
|
const server = str(args.server);
|
|
338
482
|
const targets = server ? [this.hub.get(server)].filter((c): c is ToolsetController => !!c && c.presents) : this.hub.presenting();
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
|
|
483
|
+
const shown = take !== null;
|
|
484
|
+
const reads = await Promise.all(targets.map(async (c) => ({ c, cs: await c.refreshOpen({ shown }).catch(() => null) })));
|
|
485
|
+
const read = reads.filter((r): r is { c: ToolsetController; cs: ChangeSet } => r.cs !== null);
|
|
486
|
+
const unread = reads.filter((r) => r.cs === null).map(({ c }) => `${c.serverId} couldn't be asked just now: this is what the host last knew of it.`);
|
|
487
|
+
const giveBack = () => {
|
|
488
|
+
if (shown) for (const { c, cs } of read) c.tell(cs);
|
|
489
|
+
};
|
|
490
|
+
let result: ToolResult;
|
|
491
|
+
try {
|
|
492
|
+
result = this.lookNow(args, server, toolNamed(this.hub, server, args.tool), shown);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
giveBack();
|
|
495
|
+
if (error instanceof MetaError && unread.length) fail([error.message, ...unread].join('\n'));
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
498
|
+
if (result.isError || typeof result.data !== 'string') {
|
|
499
|
+
giveBack();
|
|
500
|
+
return result;
|
|
501
|
+
}
|
|
502
|
+
const restates = !str(args.tool) && !str(args.toolset) && !str(args.instance);
|
|
503
|
+
if (!restates) giveBack();
|
|
504
|
+
const lines = restates
|
|
505
|
+
? read.flatMap(({ c, cs }) => {
|
|
506
|
+
const said = [...cs.suggest.map((s) => c.presence.renderSuggestion(s)), ...c.presence.untoldNotes(cs), ...cs.notes];
|
|
507
|
+
// The catalog above counted what it showed; the change's own blocks weren't shown, but stay for a notice if its taker can't show the result.
|
|
508
|
+
take?.({ toolsets: c, cs: { ...cs, blocksShown: false } });
|
|
509
|
+
return said;
|
|
510
|
+
})
|
|
511
|
+
: [];
|
|
512
|
+
lines.push(...unread);
|
|
513
|
+
return lines.length ? textResult(`${result.data}\n${lines.join('\n')}`) : result;
|
|
342
514
|
}
|
|
343
515
|
|
|
344
|
-
private lookNow(args: Record<string, unknown>, server: string | undefined, tool: string | undefined): ToolResult {
|
|
516
|
+
private lookNow(args: Record<string, unknown>, server: string | undefined, tool: string | undefined, mark: boolean): ToolResult {
|
|
345
517
|
if (!server) {
|
|
346
|
-
if (tool)
|
|
347
|
-
|
|
348
|
-
|
|
518
|
+
if (tool) {
|
|
519
|
+
const c = owner(this.hub, undefined, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
|
|
520
|
+
return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
|
|
521
|
+
}
|
|
522
|
+
const ts = toolsetNamed(this.hub, undefined, args.toolset);
|
|
523
|
+
const inst = instanceNamed(this.hub.presenting(), ts, args.instance);
|
|
349
524
|
if (ts || inst) {
|
|
350
525
|
const c = owner(this.hub,
|
|
351
526
|
undefined,
|
|
352
|
-
(x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords().some((o) => o.instance === inst)),
|
|
527
|
+
(x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords(ts).some((o) => o.instance === inst)),
|
|
353
528
|
ts ? `a toolset "${ts}"${inst ? ` with instance "${inst}"` : ''}` : `an open instance "${inst}"`,
|
|
354
529
|
);
|
|
355
|
-
return this.lookNow(args, c.serverId, tool);
|
|
530
|
+
return this.lookNow(args, c.serverId, tool, mark);
|
|
356
531
|
}
|
|
357
532
|
const all = this.hub.presenting();
|
|
358
|
-
if (!all.length) return textResult('No MCPL server presents toolsets or dynamic tools now;
|
|
359
|
-
return textResult(all.map((c) => c.presence.catalog()).join('\n\n'));
|
|
533
|
+
if (!all.length) return textResult('No MCPL server presents toolsets or dynamic tools now; MCPL tools are called directly, by name.');
|
|
534
|
+
return textResult(all.map((c) => c.presence.catalog({ mark })).join('\n\n'));
|
|
360
535
|
}
|
|
361
536
|
const c = pick(this.hub, server);
|
|
362
|
-
if (tool) return this.lookTool(c, tool,
|
|
537
|
+
if (tool) return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
|
|
363
538
|
const p = c.presence;
|
|
364
|
-
let toolset =
|
|
365
|
-
const instance =
|
|
366
|
-
if (!toolset && instance)
|
|
367
|
-
|
|
539
|
+
let toolset = toolsetNamed(this.hub, server, args.toolset);
|
|
540
|
+
const instance = instanceNamed([c], toolset, args.instance);
|
|
541
|
+
if (!toolset && instance) {
|
|
542
|
+
const open = p.openRecords().filter((o) => o.instance !== undefined);
|
|
543
|
+
toolset = open.find((o) => o.instance === instance)?.toolset
|
|
544
|
+
?? fail(`no open instance "${instance}" on ${c.serverId}; ${open.length ? `open: ${open.map((o) => `${nameToTypeBack(o.toolset)}:${nameToTypeBack(String(o.instance))}`).join(', ')}` : 'no instance is open there'}`);
|
|
545
|
+
}
|
|
546
|
+
if (!toolset) return textResult(p.catalog({ mark }));
|
|
368
547
|
if (p.isOpen(toolset, instance) || (!instance && p.openRecords(toolset).length === 1)) {
|
|
369
548
|
const rec = p.isDeclared(toolset) ? (instance ? p.openRecords(toolset).find((o) => o.instance === instance) : p.openRecords(toolset)[0]) : undefined;
|
|
370
549
|
const key = keyOf(toolset, rec?.instance);
|
|
371
|
-
const lines = p.toolBlock(key, this.toolsAt(c, key), { describeAll: true, title: rec?.title });
|
|
550
|
+
const lines = p.toolBlock(key, this.toolsAt(c, key), { describeAll: true, title: rec?.title, seen: mark ? undefined : new Set<string>() });
|
|
372
551
|
const close = p.closeHint(key);
|
|
373
552
|
if (close) lines.push(`${close} closes it.`);
|
|
374
553
|
return textResult(lines.join('\n'));
|
|
@@ -377,38 +556,40 @@ export class ToolsetsModule implements Module {
|
|
|
377
556
|
if (d) {
|
|
378
557
|
const open = p.openRecords(toolset);
|
|
379
558
|
return textResult([
|
|
380
|
-
`${c.serverId}/${toolset}${d.title ? ` — ${d.title}` : ''}${d.summary ? `: ${d.summary}` : ''}`,
|
|
381
|
-
open.length ? `open: ${open.map((o) => o.instance ?? toolset).join(', ')}` : 'not open',
|
|
559
|
+
`${c.serverId}/${nameToTypeBack(toolset)}${d.title ? ` — ${suppliedValue(d.title)}` : ''}${d.summary ? `: ${suppliedValue(d.summary)}` : ''}`,
|
|
560
|
+
open.length ? `open: ${open.map((o) => nameToTypeBack(o.instance ?? toolset)).join(', ')}` : 'not open',
|
|
382
561
|
...(d.instances ? ['several can be open at once; name each with instance'] : []),
|
|
383
|
-
...(d.options ? [`options: ${
|
|
562
|
+
...(d.options ? [`options: ${jsonText(d.options)}`] : []),
|
|
384
563
|
`${p.openHint(toolset)} opens it.`,
|
|
385
564
|
].join('\n'));
|
|
386
565
|
}
|
|
387
566
|
if (p.undeclaredGroups.includes(toolset)) {
|
|
388
567
|
const names = p.allTools.filter((t) => p.toolsetOf(t.name) === toolset).map((t) => t.name);
|
|
389
|
-
return textResult(`${c.serverId}/${toolset}: ${names.join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
|
|
568
|
+
return textResult(`${c.serverId}/${nameToTypeBack(toolset)}: ${names.map(nameToTypeBack).join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
|
|
390
569
|
}
|
|
391
|
-
return fail(
|
|
570
|
+
return fail(noToolset(c, toolset!));
|
|
392
571
|
}
|
|
393
572
|
|
|
394
573
|
private lookTool(c: ToolsetController, tool: string, instance: string | undefined): ToolResult {
|
|
395
574
|
const p = c.presence;
|
|
396
|
-
if (!p.tool(tool)) fail(`${c.serverId} has no tool "${tool}"`);
|
|
575
|
+
if (!p.tool(tool)) fail(`${c.serverId} has no tool "${tool}": ${META_TOOL_NAMES.look} {server:"${c.serverId}"} shows what's available`);
|
|
397
576
|
const ts = p.toolsetOf(tool);
|
|
398
|
-
|
|
399
|
-
const
|
|
577
|
+
// The definition act would check, wherever act would take the tool.
|
|
578
|
+
const open = !instance && hasInstances(p, ts) ? p.openRecords(ts) : [];
|
|
579
|
+
const key = !p.shownAs(tool, 'dynamic') ? undefined : actKey(p, tool, instance ?? (open.length === 1 ? open[0]!.instance : undefined));
|
|
400
580
|
const def = key ? p.defOf(key, tool) : p.tool(tool)!;
|
|
401
581
|
return textResult(`${renderToolDefinition(def)}\n${p.toolStatus(tool)}`);
|
|
402
582
|
}
|
|
403
583
|
|
|
404
|
-
private async close(args: Record<string, unknown
|
|
405
|
-
const
|
|
584
|
+
private async close(args: Record<string, unknown>, take: ((taken: TakenChange) => void) | null): Promise<ToolResult> {
|
|
585
|
+
const shown = take !== null;
|
|
586
|
+
const named = toolsetNamed(this.hub, args.server, args.toolset);
|
|
406
587
|
const c = named && !str(args.server)
|
|
407
|
-
? owner(this.hub, undefined, (x) => x.presence.isOpen(named,
|
|
588
|
+
? owner(this.hub, undefined, (x) => x.presence.isOpen(named, instanceNamed([x], named, args.instance)) || x.presence.openRecords(named).length > 0, `an open toolset "${named}"`)
|
|
408
589
|
: pick(this.hub, args.server);
|
|
409
590
|
const p = c.presence;
|
|
410
591
|
let toolset = named;
|
|
411
|
-
let instance =
|
|
592
|
+
let instance = instanceNamed([c], named, args.instance);
|
|
412
593
|
if (!toolset) {
|
|
413
594
|
const open = [
|
|
414
595
|
...p.openRecords().filter((o) => !instance || o.instance === instance),
|
|
@@ -416,7 +597,7 @@ export class ToolsetsModule implements Module {
|
|
|
416
597
|
];
|
|
417
598
|
if (open.length !== 1) {
|
|
418
599
|
fail(open.length
|
|
419
|
-
? `several are open on ${c.serverId}: name one of ${open.map((o) => `${o.toolset}${o.instance ? `:${o.instance}` : ''}`).join(', ')}`
|
|
600
|
+
? `several are open on ${c.serverId}: name one of ${open.map((o) => `${nameToTypeBack(o.toolset)}${o.instance ? `:${nameToTypeBack(o.instance)}` : ''}`).join(', ')}`
|
|
420
601
|
: `nothing is open on ${c.serverId}`);
|
|
421
602
|
}
|
|
422
603
|
toolset = open[0]!.toolset;
|
|
@@ -424,28 +605,33 @@ export class ToolsetsModule implements Module {
|
|
|
424
605
|
}
|
|
425
606
|
if (p.isDeclared(toolset!)) {
|
|
426
607
|
// As open refuses one: a toolset without instances is closed without one.
|
|
427
|
-
if (instance && !p.declared[toolset!]!.instances) fail(`${c.serverId}/${toolset} has no instances: close it without one`);
|
|
608
|
+
if (instance && !p.declared[toolset!]!.instances) fail(`${c.serverId}/${nameToTypeBack(toolset!)} has no instances: close it without one`);
|
|
428
609
|
if (p.declared[toolset!]!.instances && !instance) {
|
|
429
610
|
const open = p.openRecords(toolset);
|
|
430
|
-
if (open.length !== 1) fail(open.length ? `several ${toolset} instances are open (${open.map((o) => o.instance).join(', ')}): say which` : `${toolset} is not open`);
|
|
611
|
+
if (open.length !== 1) fail(open.length ? `several ${nameToTypeBack(toolset!)} instances are open (${open.map((o) => nameToTypeBack(String(o.instance))).join(', ')}): say which` : `${nameToTypeBack(toolset!)} is not open`);
|
|
431
612
|
instance = open[0]!.instance;
|
|
432
613
|
}
|
|
433
614
|
let closed: Awaited<ReturnType<ToolsetController['close']>>;
|
|
434
615
|
try {
|
|
435
|
-
closed = await c.close(toolset!, instance);
|
|
616
|
+
closed = await c.close(toolset!, instance, { shown });
|
|
436
617
|
} catch (error) {
|
|
437
|
-
|
|
618
|
+
const what = `${nameToTypeBack(toolset!)}${instance ? `:${nameToTypeBack(instance)}` : ''}`;
|
|
619
|
+
return requestFailure(c.serverId, `did not close ${what}`, `didn't confirm closing ${what}`, error);
|
|
438
620
|
}
|
|
439
621
|
const key = keyOf(toolset!, instance);
|
|
440
|
-
const change = p.render(closed.change) || `${p.label(key)} closed.`;
|
|
622
|
+
const change = p.render(closed.change, { mark: shown, describeAll: !shown }) || `${p.label(key)} closed.`;
|
|
623
|
+
take?.({ toolsets: c, cs: closed.change });
|
|
441
624
|
return blocksResult([...asBlocks(closed.content), { type: 'text', text: change }]);
|
|
442
625
|
}
|
|
443
626
|
if (p.undeclaredGroups.includes(toolset!)) {
|
|
444
|
-
if (instance) fail(`${c.serverId}/${toolset} takes no instance (the server doesn't declare it)`);
|
|
445
|
-
if (!p.isOpen(toolset!)) fail(`${c.serverId}/${toolset} is not open`);
|
|
627
|
+
if (instance) fail(`${c.serverId}/${nameToTypeBack(toolset!)} takes no instance (the server doesn't declare it)`);
|
|
628
|
+
if (!p.isOpen(toolset!)) fail(`${c.serverId}/${nameToTypeBack(toolset!)} is not open`);
|
|
446
629
|
const cs = c.setGroupOpen(toolset!, false);
|
|
447
|
-
|
|
630
|
+
const text = p.render(cs, { mark: shown, describeAll: !shown }) || `${p.label(keyOf(toolset!))} closed.`;
|
|
631
|
+
if (take) take({ toolsets: c, cs });
|
|
632
|
+
else c.tell(cs);
|
|
633
|
+
return textResult(text);
|
|
448
634
|
}
|
|
449
|
-
return fail(
|
|
635
|
+
return fail(noToolset(c, toolset!));
|
|
450
636
|
}
|
|
451
637
|
}
|