@animalabs/agent-framework 0.28.0 → 0.30.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 +88 -25
- package/dist/src/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +21 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +47 -7
- 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 +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- 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/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +846 -67
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +4213 -924
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +46 -14
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +143 -53
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +174 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +565 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +7 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- 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/channel-registry.d.ts +118 -15
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +266 -110
- 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 +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- 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-coalescer.d.ts +9 -0
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.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.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- 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/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +187 -50
- 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 +28 -12
- 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 +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- 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.js +8 -6
- 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 +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.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 +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.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 +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +81 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +85 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +5 -55
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +63 -7
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.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/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- 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/tune-out/coordinator.d.ts +382 -11
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1100 -119
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +101 -5
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +48 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +142 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +136 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +52 -7
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/author-presentation.ts +81 -0
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +4362 -852
- package/src/gate/event-gate.ts +159 -52
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +612 -0
- package/src/index.ts +17 -4
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +306 -111
- 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 +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-coalescer.ts +19 -0
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +149 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +86 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +11 -7
- package/src/stored-injections.ts +7 -0
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +1252 -123
- package/src/tune-out/tools.ts +152 -11
- package/src/types/agent.ts +51 -9
- package/src/types/framework.ts +146 -0
- package/src/types/module.ts +23 -11
- package/src/types/trace.ts +150 -0
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* reference host of RFC-014. Dependency-free.
|
|
11
11
|
*/
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
+
import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
|
|
13
14
|
|
|
14
15
|
export type JsonSchema = Record<string, unknown>;
|
|
15
16
|
|
|
@@ -34,7 +35,7 @@ function nestsWithin(value: unknown, depth = 0): boolean {
|
|
|
34
35
|
|
|
35
36
|
/** A schema's literal value (an enum's, a const or a default) as JSON, or … where it's nested too deeply to write. */
|
|
36
37
|
function literal(value: unknown): string {
|
|
37
|
-
return nestsWithin(value) ?
|
|
38
|
+
return nestsWithin(value) ? quotedValue(value) : '…';
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function typeSig(schema: JsonSchema | undefined, depth = 0): string {
|
|
@@ -80,12 +81,12 @@ function typeSig(schema: JsonSchema | undefined, depth = 0): string {
|
|
|
80
81
|
if (!isObj(props) || Object.keys(props).length === 0) return 'object';
|
|
81
82
|
if (depth >= 3) return '{…}';
|
|
82
83
|
const req = new Set(Array.isArray(schema.required) ? (schema.required as string[]) : []);
|
|
83
|
-
return `{${Object.entries(props).map(([k, v]) => `${k}${req.has(k) ? '' : '?'}: ${typeSig(v, depth + 1)}`).join(', ')}}`;
|
|
84
|
+
return `{${Object.entries(props).map(([k, v]) => `${nameToTypeBack(k)}${req.has(k) ? '' : '?'}: ${typeSig(v, depth + 1)}`).join(', ')}}`;
|
|
84
85
|
}
|
|
85
86
|
case undefined:
|
|
86
87
|
return 'any';
|
|
87
88
|
default:
|
|
88
|
-
return t;
|
|
89
|
+
return suppliedValue(t);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -98,9 +99,9 @@ export function signature(tool: HintedTool): string {
|
|
|
98
99
|
const req = new Set(Array.isArray(s.required) ? (s.required as string[]) : []);
|
|
99
100
|
const params = Object.entries(props).map(([k, v]) => {
|
|
100
101
|
const def = isObj(v) && v.default !== undefined ? ` =${literal(v.default)}` : '';
|
|
101
|
-
return `${k}${req.has(k) ? '' : '?'}: ${typeSig(v, 1)}${def}`;
|
|
102
|
+
return `${nameToTypeBack(k)}${req.has(k) ? '' : '?'}: ${typeSig(v, 1)}${def}`;
|
|
102
103
|
});
|
|
103
|
-
return `${tool.name}(${params.join(', ')})`;
|
|
104
|
+
return `${nameToTypeBack(String(tool.name))}(${params.join(', ')})`;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/** An options schema as `{size?: 9|13|19 =9, …}` (a toolset's options). */
|
|
@@ -153,7 +154,7 @@ export function renderToolLines(tools: HintedTool[], shown: Shown, opts: { descr
|
|
|
153
154
|
const describe = opts.describeAll || !shown.has(d);
|
|
154
155
|
shown.add(d);
|
|
155
156
|
const sentence = describe ? firstSentence(t.description) : '';
|
|
156
|
-
return `${indent}${signature(t)}${sentence ? ` — ${sentence}` : ''}`;
|
|
157
|
+
return `${indent}${signature(t)}${sentence ? ` — ${suppliedValue(sentence)}` : ''}`;
|
|
157
158
|
});
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -161,7 +162,7 @@ export function renderToolLines(tools: HintedTool[], shown: Shown, opts: { descr
|
|
|
161
162
|
export function jsonText(value: unknown, space?: number): string {
|
|
162
163
|
if (!nestsWithin(value)) return '(nested too deeply to show)';
|
|
163
164
|
try {
|
|
164
|
-
return JSON.stringify(value
|
|
165
|
+
return JSON.stringify(value) === undefined ? '' : quotedValue(value, space);
|
|
165
166
|
} catch {
|
|
166
167
|
return '(too long to show)';
|
|
167
168
|
}
|
|
@@ -173,8 +174,8 @@ export function renderToolDefinition(tool: HintedTool): string {
|
|
|
173
174
|
const classes = Array.isArray(cls) ? cls.filter((c): c is string => typeof c === 'string') : [];
|
|
174
175
|
return [
|
|
175
176
|
signature(tool),
|
|
176
|
-
typeof tool.description === 'string' ? tool.description : '',
|
|
177
|
-
...(classes.length ? [`class: ${classes.join(', ')}`] : []),
|
|
177
|
+
typeof tool.description === 'string' ? suppliedValue(tool.description) : '',
|
|
178
|
+
...(classes.length ? [`class: ${classes.map(suppliedValue).join(', ')}`] : []),
|
|
178
179
|
'inputSchema:',
|
|
179
180
|
jsonText(tool.inputSchema ?? {}, 2),
|
|
180
181
|
]
|
|
@@ -252,15 +253,15 @@ function check(schema: unknown, value: unknown, at: string, depth: number, budge
|
|
|
252
253
|
const s = schema;
|
|
253
254
|
if (s.type !== undefined) {
|
|
254
255
|
const types = Array.isArray(s.type) ? (s.type as string[]) : [s.type as string];
|
|
255
|
-
if (!types.some((t) => typeOk(t, value))) return `${at}: must be ${types.join(' or ')}`;
|
|
256
|
+
if (!types.some((t) => typeOk(t, value))) return `${at}: must be ${types.map(suppliedValue).join(' or ')}`;
|
|
256
257
|
}
|
|
257
258
|
if (Array.isArray(s.enum) || s.const !== undefined) {
|
|
258
259
|
const own = canonicalJson(value);
|
|
259
260
|
if (Array.isArray(s.enum)) {
|
|
260
261
|
spend(budget, s.enum.length);
|
|
261
|
-
if (!s.enum.some((e) => canonicalJson(e) === own)) return `${at}: must be one of ${s.enum.map((e) =>
|
|
262
|
+
if (!s.enum.some((e) => canonicalJson(e) === own)) return `${at}: must be one of ${s.enum.map((e) => quotedValue(e)).join('|')}`;
|
|
262
263
|
}
|
|
263
|
-
if (s.const !== undefined && canonicalJson(s.const) !== own) return `${at}: must be ${
|
|
264
|
+
if (s.const !== undefined && canonicalJson(s.const) !== own) return `${at}: must be ${quotedValue(s.const)}`;
|
|
264
265
|
}
|
|
265
266
|
if (typeof value === 'string') {
|
|
266
267
|
const n = [...value].length;
|
|
@@ -286,16 +287,16 @@ function check(schema: unknown, value: unknown, at: string, depth: number, budge
|
|
|
286
287
|
if (isObj(value)) {
|
|
287
288
|
const props = isObj(s.properties) ? s.properties : {};
|
|
288
289
|
for (const r of Array.isArray(s.required) ? (s.required as unknown[]) : []) {
|
|
289
|
-
if (typeof r === 'string' && !Object.hasOwn(value, r)) return `${at}.${r}: required`;
|
|
290
|
+
if (typeof r === 'string' && !Object.hasOwn(value, r)) return `${at}.${nameToTypeBack(r)}: required`;
|
|
290
291
|
}
|
|
291
292
|
for (const [k, v] of Object.entries(value)) {
|
|
292
293
|
if (Object.hasOwn(props, k)) {
|
|
293
|
-
const e = check(props[k], v, `${at}.${k}`, depth + 1, budget);
|
|
294
|
+
const e = check(props[k], v, `${at}.${nameToTypeBack(k)}`, depth + 1, budget);
|
|
294
295
|
if (e) return e;
|
|
295
296
|
} else if (s.additionalProperties === false) {
|
|
296
|
-
return `${at}.${k}: not an argument of this tool`;
|
|
297
|
+
return `${at}.${nameToTypeBack(k)}: not an argument of this tool`;
|
|
297
298
|
} else if (isObj(s.additionalProperties)) {
|
|
298
|
-
const e = check(s.additionalProperties, v, `${at}.${k}`, depth + 1, budget);
|
|
299
|
+
const e = check(s.additionalProperties, v, `${at}.${nameToTypeBack(k)}`, depth + 1, budget);
|
|
299
300
|
if (e) return e;
|
|
300
301
|
}
|
|
301
302
|
}
|
package/src/mcpl/toolset-host.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* which the toolsets module gathers at its next start as a stored
|
|
25
25
|
* `afterUser` injection.
|
|
26
26
|
*/
|
|
27
|
+
import { nameToTypeBack, quotedValue } from '../supplied-text.js';
|
|
27
28
|
import { McplRequestError, type McplServerConnection, type HintRead } from './server-connection.js';
|
|
28
29
|
import type { McplServerConfig } from './types.js';
|
|
29
30
|
import { isToolAllowed } from './tool-policy.js';
|
|
@@ -73,6 +74,10 @@ export interface ToolsetHostDeps {
|
|
|
73
74
|
* where it isn't published. RFC §4.2: until its move applies, a tool
|
|
74
75
|
* keeps working where it is. */
|
|
75
76
|
published?(serverId: string, tool: string): Presence | undefined;
|
|
77
|
+
/** The host's own toolsets an agent has open (the built-in one, server
|
|
78
|
+
* tabs): each one's marker and reminder, for RFC §8.3 reminders beside the
|
|
79
|
+
* servers' toolsets. */
|
|
80
|
+
hostOpen?(agentName: string): Array<{ marker: string; reminder: () => string; dueWhenAbsent?: boolean }>;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
/** One server's RFC-014 state and traffic. Lives as long as the server's
|
|
@@ -381,8 +386,8 @@ export class ToolsetController {
|
|
|
381
386
|
*/
|
|
382
387
|
dynamicCallRefusal(tool: string, calledAs: string): string | null {
|
|
383
388
|
if (!this.presence.active || !this.presence.tool(tool) || this.presence.shownAs(tool, 'static')) return null;
|
|
384
|
-
return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${tool} is a dynamic tool of ${this.serverId}, ` +
|
|
385
|
-
`called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool
|
|
389
|
+
return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${nameToTypeBack(tool)} is a dynamic tool of ${this.serverId}, ` +
|
|
390
|
+
`called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:${quotedValue(tool)}, args}. ${this.presence.toolStatus(tool)}.`;
|
|
386
391
|
}
|
|
387
392
|
|
|
388
393
|
/**
|
|
@@ -621,8 +626,8 @@ export class ToolsetController {
|
|
|
621
626
|
if (!moved.length) return;
|
|
622
627
|
const cs = emptyChange();
|
|
623
628
|
cs.notes.push(...moved.map(({ name, to }) => to === 'dynamic'
|
|
624
|
-
? `${name} left your tool list: it's announced when available, and called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool
|
|
625
|
-
: `${name} is in your tool list now, as ${this.deps.listedName(this.serverId, name)}.`));
|
|
629
|
+
? `${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}.`
|
|
630
|
+
: `${nameToTypeBack(name)} is in your tool list now, as ${nameToTypeBack(this.deps.listedName(this.serverId, name))}.`));
|
|
626
631
|
this.deliver(cs);
|
|
627
632
|
}
|
|
628
633
|
}
|
|
@@ -681,8 +686,9 @@ export class ToolsetHub {
|
|
|
681
686
|
* reminder due.
|
|
682
687
|
*/
|
|
683
688
|
observeCompile(agentName: string, messages: readonly unknown[]): void {
|
|
689
|
+
const host = this.deps.hostOpen?.(agentName) ?? [];
|
|
684
690
|
const open = this.openMarkers();
|
|
685
|
-
if (open.length === 0) {
|
|
691
|
+
if (open.length === 0 && host.length === 0) {
|
|
686
692
|
this.seen.delete(agentName);
|
|
687
693
|
this.due.delete(agentName);
|
|
688
694
|
return;
|
|
@@ -696,6 +702,9 @@ export class ToolsetHub {
|
|
|
696
702
|
}
|
|
697
703
|
for (const marker of [...seen]) if (!open.includes(marker)) seen.delete(marker);
|
|
698
704
|
this.seen.set(agentName, seen);
|
|
705
|
+
// A host toolset open across a restart was never seen here: it's due
|
|
706
|
+
// whenever a request doesn't carry its marker.
|
|
707
|
+
for (const h of host) if (h.dueWhenAbsent && !text.includes(h.marker)) missing = true;
|
|
699
708
|
// Due until a request carries every marker the agent has seen again: a
|
|
700
709
|
// gather that doesn't send (a preview) leaves it due for the next start.
|
|
701
710
|
if (missing) this.due.add(agentName);
|
|
@@ -705,7 +714,10 @@ export class ToolsetHub {
|
|
|
705
714
|
/** The reminder due for `agentName`, or null when none is. */
|
|
706
715
|
reminderFor(agentName: string): string | null {
|
|
707
716
|
if (!this.due.has(agentName)) return null;
|
|
708
|
-
const parts =
|
|
717
|
+
const parts = [
|
|
718
|
+
...(this.deps.hostOpen?.(agentName) ?? []).map((h) => h.reminder()),
|
|
719
|
+
...this.presenting().filter((c) => this.deps.canNotify(c.serverId)).map((c) => c.presence.reminder()).filter((r): r is string => !!r),
|
|
720
|
+
];
|
|
709
721
|
if (parts.length === 0) return null;
|
|
710
722
|
return `Open toolsets (a reminder: their earlier announcements have left your context):\n${parts.join('\n')}`;
|
|
711
723
|
}
|
|
@@ -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,6 +23,7 @@
|
|
|
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';
|
|
@@ -29,6 +31,7 @@ import { META_TOOL_NAMES, TOOLSETS_MODULE, type ToolsetController, type ToolsetH
|
|
|
29
31
|
import { keyOf, type ChangeSet, type ToolPresence } from './tool-presence.js';
|
|
30
32
|
import { checkArgs, jsonText, renderToolDefinition, type HintedTool } from './tool-signature.js';
|
|
31
33
|
import { McplRequestError } from './server-connection.js';
|
|
34
|
+
import { BUILTIN_TOOLSET, type HostPresentation } from '../host-presentation.js';
|
|
32
35
|
|
|
33
36
|
/** The namespace a reminder's stored injection carries. */
|
|
34
37
|
export const TOOLSETS_REMINDER_NAMESPACE = 'mcpl-toolsets';
|
|
@@ -37,24 +40,26 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
37
40
|
{
|
|
38
41
|
name: 'open',
|
|
39
42
|
description:
|
|
40
|
-
'Open a toolset
|
|
41
|
-
'
|
|
42
|
-
'
|
|
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; ' +
|
|
43
47
|
'options as toolsets--look lists them.',
|
|
44
48
|
inputSchema: {
|
|
45
49
|
type: 'object',
|
|
46
50
|
properties: {
|
|
47
51
|
server: { type: 'string', description: 'the MCPL server id' },
|
|
48
|
-
toolset: { type: 'string' },
|
|
52
|
+
toolset: { type: 'string', description: '"settings" for the host\'s own; a server\'s toolset with server' },
|
|
49
53
|
instance: { type: 'string', description: 'a name for this copy (toolsets with instances only)' },
|
|
50
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' },
|
|
51
56
|
},
|
|
52
57
|
},
|
|
53
58
|
},
|
|
54
59
|
{
|
|
55
60
|
name: 'act',
|
|
56
61
|
description:
|
|
57
|
-
'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. ' +
|
|
58
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. ' +
|
|
59
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.',
|
|
60
65
|
inputSchema: {
|
|
@@ -71,7 +76,7 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
71
76
|
{
|
|
72
77
|
name: 'look',
|
|
73
78
|
description:
|
|
74
|
-
'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. ' +
|
|
75
80
|
'With tool: that tool\'s full description and input schema. With toolset or instance: just that one.',
|
|
76
81
|
inputSchema: {
|
|
77
82
|
type: 'object',
|
|
@@ -85,13 +90,15 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
85
90
|
},
|
|
86
91
|
{
|
|
87
92
|
name: 'close',
|
|
88
|
-
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.'
|
|
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.',
|
|
89
95
|
inputSchema: {
|
|
90
96
|
type: 'object',
|
|
91
97
|
properties: {
|
|
92
98
|
server: { type: 'string', description: 'the MCPL server id (may be left out when one server has the toolset open)' },
|
|
93
|
-
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' },
|
|
94
100
|
instance: { type: 'string' },
|
|
101
|
+
disconnect: { type: 'boolean', description: 'with server alone: disconnect it as a closed tab until it is opened again' },
|
|
95
102
|
},
|
|
96
103
|
},
|
|
97
104
|
},
|
|
@@ -99,6 +106,21 @@ const META_TOOLS: ToolDefinition[] = [
|
|
|
99
106
|
|
|
100
107
|
const str = (v: unknown): string | undefined => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
|
101
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
|
+
|
|
102
124
|
const SELECTORS = ['server', 'toolset', 'instance', 'tool'] as const;
|
|
103
125
|
|
|
104
126
|
function checkSelectors(args: Record<string, unknown>): void {
|
|
@@ -117,7 +139,7 @@ export function withLookHint(serverId: string, text: string): string {
|
|
|
117
139
|
}
|
|
118
140
|
|
|
119
141
|
function requestFailure(serverId: string, did: string, unconfirmed: string, error: unknown): never {
|
|
120
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
142
|
+
const message = suppliedValue(error instanceof Error ? error.message : String(error));
|
|
121
143
|
if (error instanceof McplRequestError && error.outcome === 'not-sent') return fail(`${serverId} ${did}: ${message}`);
|
|
122
144
|
return fail(withLookHint(serverId, `${serverId} ${unconfirmed}: ${message}`));
|
|
123
145
|
}
|
|
@@ -194,7 +216,7 @@ const NONE_PRESENTS = 'no MCPL server presents toolsets now: MCPL tools are call
|
|
|
194
216
|
|
|
195
217
|
function noToolset(c: ToolsetController, toolset: string): string {
|
|
196
218
|
const names = [...Object.keys(c.presence.declared), ...c.presence.undeclaredGroups];
|
|
197
|
-
return `${c.serverId} has no toolset "${toolset}"; ${names.length ? `it has ${names.join(', ')}` : 'it has none'}`;
|
|
219
|
+
return `${c.serverId} has no toolset "${toolset}"; ${names.length ? `it has ${names.map(nameToTypeBack).join(', ')}` : 'it has none'}`;
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
/** What a `toolsets--act` call resolves to: the server's tool, its instance and arguments. */
|
|
@@ -221,35 +243,35 @@ export function resolveAct(hub: ToolsetHub, input: unknown): ActTarget {
|
|
|
221
243
|
try {
|
|
222
244
|
const args = isObj(input) ? input : {};
|
|
223
245
|
checkSelectors(args);
|
|
224
|
-
const tool =
|
|
246
|
+
const tool = toolNamed(hub, args.server, args.tool) ?? fail('name the tool');
|
|
225
247
|
const c = owner(hub, args.server, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
|
|
226
248
|
const p = c.presence;
|
|
227
249
|
if (!p.tool(tool)) fail(`${c.serverId} has no tool "${tool}": ${META_TOOL_NAMES.look} {server:"${c.serverId}"} shows what's available`);
|
|
228
|
-
if (!p.shownAs(tool, 'dynamic')) fail(`${tool} is not a dynamic tool: ${p.toolStatus(tool)}. Call it there.`);
|
|
250
|
+
if (!p.shownAs(tool, 'dynamic')) fail(`${nameToTypeBack(tool)} is not a dynamic tool: ${p.toolStatus(tool)}. Call it there.`);
|
|
229
251
|
if (args.args !== undefined && !isObj(args.args)) fail('args must be an object of the tool\'s arguments');
|
|
230
252
|
const callArgs = (args.args ?? {}) as Record<string, unknown>;
|
|
231
|
-
let instance = str(args.instance);
|
|
232
253
|
const ts = p.toolsetOf(tool);
|
|
254
|
+
let instance = instanceNamed([c], ts, args.instance);
|
|
233
255
|
const declared = ts !== undefined && p.isDeclared(ts);
|
|
234
256
|
const withInstances = hasInstances(p, ts);
|
|
235
257
|
if (withInstances && !instance) {
|
|
236
258
|
// RFC §5.5: the instance rides in _meta; with exactly one open, it's that one.
|
|
237
259
|
const open = p.openRecords(ts);
|
|
238
260
|
if (open.length === 1) instance = open[0]!.instance;
|
|
239
|
-
else if (!open.length) fail(`${tool} belongs to ${ts}, which isn't open: ${p.openHint(ts!)}`);
|
|
240
|
-
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`);
|
|
241
263
|
}
|
|
242
|
-
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`);
|
|
243
265
|
// RFC §5.4: the host gates calls into an undeclared group it has closed.
|
|
244
|
-
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)}`);
|
|
245
267
|
// RFC §6.1: arguments are checked against the definition in force for
|
|
246
268
|
// that instance: its own from hints (the legal moves of one game), else
|
|
247
269
|
// the toolset's, else the listed one.
|
|
248
270
|
const key = actKey(p, tool, instance);
|
|
249
271
|
const problem = checkArgs(p.defOf(key, tool).inputSchema, callArgs);
|
|
250
272
|
if (problem) {
|
|
251
|
-
fail(`Invalid arguments for ${tool} in ${p.label(key)}: ${problem}. Nothing was sent; ` +
|
|
252
|
-
`${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.`);
|
|
253
275
|
}
|
|
254
276
|
return { ok: true, serverId: c.serverId, tool, ...(instance ? { instance } : {}), args: callArgs };
|
|
255
277
|
} catch (error) {
|
|
@@ -267,6 +289,8 @@ export class ToolsetsModule implements Module {
|
|
|
267
289
|
constructor(
|
|
268
290
|
private readonly hub: ToolsetHub,
|
|
269
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,
|
|
270
294
|
) {}
|
|
271
295
|
|
|
272
296
|
async start(_ctx: ModuleContext): Promise<void> {}
|
|
@@ -291,7 +315,23 @@ export class ToolsetsModule implements Module {
|
|
|
291
315
|
const args = isObj(call.input) ? call.input : {};
|
|
292
316
|
try {
|
|
293
317
|
checkSelectors(args);
|
|
318
|
+
// An agent shown another's tool list can't switch it: what it opened
|
|
319
|
+
// or closed would change its owner's list, or nothing it is shown.
|
|
320
|
+
const caller = call.callerAgentName;
|
|
321
|
+
const owner = caller && this.host ? this.host.ownerOf(caller) : caller;
|
|
322
|
+
if ((call.name === 'open' || call.name === 'close') && caller && owner !== caller) {
|
|
323
|
+
fail(`${caller} is shown ${owner}'s tools and can't ${call.name} a toolset; it reaches the toolsets ${owner} has open. Nothing changed.`);
|
|
324
|
+
}
|
|
325
|
+
const hosted = await this.hostCall(call.name, args, call.callerAgentName);
|
|
326
|
+
if (hosted) return hosted;
|
|
294
327
|
const take = this.takerFor(call);
|
|
328
|
+
const lead = call.name === 'look' ? this.hostLookLead(args, call.callerAgentName) : null;
|
|
329
|
+
if (lead !== null) {
|
|
330
|
+
if (!this.hub.presenting().length) return textResult(lead);
|
|
331
|
+
const theirs = await this.look(args, take);
|
|
332
|
+
if (theirs.isError || typeof theirs.data !== 'string') return theirs;
|
|
333
|
+
return textResult(`${lead}\n\n${theirs.data}`);
|
|
334
|
+
}
|
|
295
335
|
switch (call.name) {
|
|
296
336
|
case 'open': return await this.open(args, take);
|
|
297
337
|
case 'act': return this.act(args);
|
|
@@ -307,6 +347,62 @@ export class ToolsetsModule implements Module {
|
|
|
307
347
|
}
|
|
308
348
|
}
|
|
309
349
|
|
|
350
|
+
/**
|
|
351
|
+
* The host's own toolsets: `toolset: "settings"` alone, or `server` alone
|
|
352
|
+
* (a server's tab), for open, close and look; a tool a host toolset has, for
|
|
353
|
+
* look. Null for everything else, which the servers' toolsets answer.
|
|
354
|
+
*/
|
|
355
|
+
private async hostCall(name: string, args: Record<string, unknown>, agentName: string | undefined): Promise<ToolResult | null> {
|
|
356
|
+
const host = this.host;
|
|
357
|
+
if (!host || !agentName || !host.active(agentName)) return null;
|
|
358
|
+
const server = str(args.server);
|
|
359
|
+
const toolset = str(args.toolset);
|
|
360
|
+
const instance = str(args.instance);
|
|
361
|
+
const tool = str(args.tool);
|
|
362
|
+
const builtin = toolset === BUILTIN_TOOLSET && !server && !instance;
|
|
363
|
+
const tab = !!server && !toolset && !instance && args.options === undefined;
|
|
364
|
+
if (args.full !== undefined && typeof args.full !== 'boolean') fail('full must be true or false');
|
|
365
|
+
if (args.disconnect !== undefined && typeof args.disconnect !== 'boolean') fail('disconnect must be true or false');
|
|
366
|
+
if (tab && !host.knows(server!) && !this.hub.get(server!)) fail(`no MCPL server "${server}" is configured here`);
|
|
367
|
+
switch (name) {
|
|
368
|
+
case 'open':
|
|
369
|
+
if (builtin) return textResult(host.openBuiltin(agentName));
|
|
370
|
+
if (tab) return textResult(await host.openServer(agentName, server!, { full: args.full === true }));
|
|
371
|
+
if (args.full !== undefined) fail('full goes with server alone');
|
|
372
|
+
return null;
|
|
373
|
+
case 'close':
|
|
374
|
+
if (builtin) return textResult(host.closeBuiltin(agentName));
|
|
375
|
+
if (tab) return textResult(await host.closeServer(agentName, server!, { disconnect: args.disconnect === true }));
|
|
376
|
+
if (args.disconnect !== undefined) fail('disconnect goes with server alone');
|
|
377
|
+
return null;
|
|
378
|
+
case 'look': {
|
|
379
|
+
// A host toolset's tool, with or without its server named (as act takes it).
|
|
380
|
+
if (tool && !toolset && !instance) {
|
|
381
|
+
const shown = host.lookTool(agentName, tool, server);
|
|
382
|
+
if (shown) return textResult(shown);
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (builtin) return textResult(host.lookToolset(agentName, { builtin: true }));
|
|
386
|
+
// A server's tab, and every host toolset with no selector, go first in
|
|
387
|
+
// what the servers' own look says (handleToolCall adds them).
|
|
388
|
+
if (tab && !tool && !this.hub.get(server!)?.presents) return textResult(host.lookToolset(agentName, { serverId: server! }));
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** What the host's own toolsets add ahead of the servers' look: everything, or one presenting server's tab. */
|
|
397
|
+
private hostLookLead(args: Record<string, unknown>, agentName: string | undefined): string | null {
|
|
398
|
+
const host = this.host;
|
|
399
|
+
if (!host || !agentName || !host.active(agentName)) return null;
|
|
400
|
+
const server = str(args.server);
|
|
401
|
+
if (str(args.toolset) || str(args.instance) || str(args.tool)) return null;
|
|
402
|
+
if (!server) return host.catalog(agentName).join('\n');
|
|
403
|
+
return host.knows(server) ? host.lookToolset(agentName, { serverId: server }) : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
310
406
|
private toolsAt(c: ToolsetController, key: string): HintedTool[] {
|
|
311
407
|
return [...(c.presence.view().get(key)?.tools.values() ?? [])];
|
|
312
408
|
}
|
|
@@ -320,36 +416,36 @@ export class ToolsetsModule implements Module {
|
|
|
320
416
|
|
|
321
417
|
private async open(args: Record<string, unknown>, take: ((taken: TakenChange) => void) | null): Promise<ToolResult> {
|
|
322
418
|
const shown = take !== null;
|
|
323
|
-
let toolset =
|
|
419
|
+
let toolset = toolsetNamed(this.hub, args.server, args.toolset);
|
|
324
420
|
const c = toolset && !str(args.server)
|
|
325
421
|
? owner(this.hub, undefined, (x) => x.presence.isDeclared(toolset!) || x.presence.undeclaredGroups.includes(toolset!), `a toolset "${toolset}"`)
|
|
326
422
|
: pick(this.hub, args.server);
|
|
327
423
|
const p = c.presence;
|
|
328
424
|
const openable = [...Object.keys(p.declared), ...p.undeclaredGroups];
|
|
329
425
|
if (!toolset) {
|
|
330
|
-
if (openable.length !== 1) fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.join(', ')}` : 'no toolsets'}`);
|
|
426
|
+
if (openable.length !== 1) fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.map(nameToTypeBack).join(', ')}` : 'no toolsets'}`);
|
|
331
427
|
toolset = openable[0]!;
|
|
332
428
|
}
|
|
333
|
-
const instance =
|
|
429
|
+
const instance = instanceNamed([c], toolset, args.instance);
|
|
334
430
|
if (args.options !== undefined && !isObj(args.options)) fail('options must be an object');
|
|
335
431
|
const options = args.options as Record<string, unknown> | undefined;
|
|
336
432
|
|
|
337
433
|
if (p.isDeclared(toolset)) {
|
|
338
|
-
if (instance && !p.declared[toolset]!.instances) fail(`${c.serverId}/${toolset} has no instances: open it without one`);
|
|
434
|
+
if (instance && !p.declared[toolset]!.instances) fail(`${c.serverId}/${nameToTypeBack(toolset)} has no instances: open it without one`);
|
|
339
435
|
let opened: Awaited<ReturnType<ToolsetController['open']>>;
|
|
340
436
|
try {
|
|
341
437
|
opened = await c.open(toolset, instance, options, { shown });
|
|
342
438
|
} catch (error) {
|
|
343
|
-
return requestFailure(c.serverId, `did not open ${toolset}`, `didn't confirm opening ${toolset}`, error);
|
|
439
|
+
return requestFailure(c.serverId, `did not open ${nameToTypeBack(toolset)}`, `didn't confirm opening ${nameToTypeBack(toolset)}`, error);
|
|
344
440
|
}
|
|
345
441
|
// RFC §5.2: opening what is open adopts it; its content says which options weren't applied.
|
|
346
442
|
const blocks: Block[] = [
|
|
347
|
-
{ type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${opened.title}` : ''}${opened.adopted ? ' (already open)' : ''}` },
|
|
443
|
+
{ type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${suppliedValue(opened.title)}` : ''}${opened.adopted ? ' (already open)' : ''}` },
|
|
348
444
|
...asBlocks(opened.content),
|
|
349
445
|
];
|
|
350
446
|
const tail = [...p.toolBlock(opened.key, this.toolsAt(c, opened.key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set<string>() })];
|
|
351
447
|
const gone = opened.change.changes.find((ch) => ch.key === opened.key)?.gone ?? [];
|
|
352
|
-
if (gone.length) tail.push(`now unavailable: ${gone.join(', ')}`);
|
|
448
|
+
if (gone.length) tail.push(`now unavailable: ${gone.map(suppliedValue).join(', ')}`);
|
|
353
449
|
const others = this.renderOthers(c, opened.change, opened.key, shown);
|
|
354
450
|
if (others) tail.push(others);
|
|
355
451
|
const close = p.closeHint(opened.key);
|
|
@@ -360,7 +456,7 @@ export class ToolsetsModule implements Module {
|
|
|
360
456
|
}
|
|
361
457
|
if (p.undeclaredGroups.includes(toolset)) {
|
|
362
458
|
// RFC §5.4: a grouping the server offers but doesn't track: the host opens it, the server isn't told.
|
|
363
|
-
if (instance || options) fail(`${c.serverId}/${toolset} takes no instance or options (the server doesn't declare it)`);
|
|
459
|
+
if (instance || options) fail(`${c.serverId}/${nameToTypeBack(toolset)} takes no instance or options (the server doesn't declare it)`);
|
|
364
460
|
const cs = c.setGroupOpen(toolset, true);
|
|
365
461
|
const key = keyOf(toolset);
|
|
366
462
|
const lines = [`${p.label(key)} open.`, ...p.toolBlock(key, this.toolsAt(c, key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set<string>() })];
|
|
@@ -400,7 +496,7 @@ export class ToolsetsModule implements Module {
|
|
|
400
496
|
};
|
|
401
497
|
let result: ToolResult;
|
|
402
498
|
try {
|
|
403
|
-
result = this.lookNow(args, server,
|
|
499
|
+
result = this.lookNow(args, server, toolNamed(this.hub, server, args.tool), shown);
|
|
404
500
|
} catch (error) {
|
|
405
501
|
giveBack();
|
|
406
502
|
if (error instanceof MetaError && unread.length) fail([error.message, ...unread].join('\n'));
|
|
@@ -426,13 +522,16 @@ export class ToolsetsModule implements Module {
|
|
|
426
522
|
|
|
427
523
|
private lookNow(args: Record<string, unknown>, server: string | undefined, tool: string | undefined, mark: boolean): ToolResult {
|
|
428
524
|
if (!server) {
|
|
429
|
-
if (tool)
|
|
430
|
-
|
|
431
|
-
|
|
525
|
+
if (tool) {
|
|
526
|
+
const c = owner(this.hub, undefined, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
|
|
527
|
+
return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
|
|
528
|
+
}
|
|
529
|
+
const ts = toolsetNamed(this.hub, undefined, args.toolset);
|
|
530
|
+
const inst = instanceNamed(this.hub.presenting(), ts, args.instance);
|
|
432
531
|
if (ts || inst) {
|
|
433
532
|
const c = owner(this.hub,
|
|
434
533
|
undefined,
|
|
435
|
-
(x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords().some((o) => o.instance === inst)),
|
|
534
|
+
(x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords(ts).some((o) => o.instance === inst)),
|
|
436
535
|
ts ? `a toolset "${ts}"${inst ? ` with instance "${inst}"` : ''}` : `an open instance "${inst}"`,
|
|
437
536
|
);
|
|
438
537
|
return this.lookNow(args, c.serverId, tool, mark);
|
|
@@ -442,14 +541,14 @@ export class ToolsetsModule implements Module {
|
|
|
442
541
|
return textResult(all.map((c) => c.presence.catalog({ mark })).join('\n\n'));
|
|
443
542
|
}
|
|
444
543
|
const c = pick(this.hub, server);
|
|
445
|
-
if (tool) return this.lookTool(c, tool,
|
|
544
|
+
if (tool) return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
|
|
446
545
|
const p = c.presence;
|
|
447
|
-
let toolset =
|
|
448
|
-
const instance =
|
|
546
|
+
let toolset = toolsetNamed(this.hub, server, args.toolset);
|
|
547
|
+
const instance = instanceNamed([c], toolset, args.instance);
|
|
449
548
|
if (!toolset && instance) {
|
|
450
549
|
const open = p.openRecords().filter((o) => o.instance !== undefined);
|
|
451
550
|
toolset = open.find((o) => o.instance === instance)?.toolset
|
|
452
|
-
?? fail(`no open instance "${instance}" on ${c.serverId}; ${open.length ? `open: ${open.map((o) => `${o.toolset}:${o.instance}`).join(', ')}` : 'no instance is open there'}`);
|
|
551
|
+
?? 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'}`);
|
|
453
552
|
}
|
|
454
553
|
if (!toolset) return textResult(p.catalog({ mark }));
|
|
455
554
|
if (p.isOpen(toolset, instance) || (!instance && p.openRecords(toolset).length === 1)) {
|
|
@@ -464,8 +563,8 @@ export class ToolsetsModule implements Module {
|
|
|
464
563
|
if (d) {
|
|
465
564
|
const open = p.openRecords(toolset);
|
|
466
565
|
return textResult([
|
|
467
|
-
`${c.serverId}/${toolset}${d.title ? ` — ${d.title}` : ''}${d.summary ? `: ${d.summary}` : ''}`,
|
|
468
|
-
open.length ? `open: ${open.map((o) => o.instance ?? toolset).join(', ')}` : 'not open',
|
|
566
|
+
`${c.serverId}/${nameToTypeBack(toolset)}${d.title ? ` — ${suppliedValue(d.title)}` : ''}${d.summary ? `: ${suppliedValue(d.summary)}` : ''}`,
|
|
567
|
+
open.length ? `open: ${open.map((o) => nameToTypeBack(o.instance ?? toolset)).join(', ')}` : 'not open',
|
|
469
568
|
...(d.instances ? ['several can be open at once; name each with instance'] : []),
|
|
470
569
|
...(d.options ? [`options: ${jsonText(d.options)}`] : []),
|
|
471
570
|
`${p.openHint(toolset)} opens it.`,
|
|
@@ -473,7 +572,7 @@ export class ToolsetsModule implements Module {
|
|
|
473
572
|
}
|
|
474
573
|
if (p.undeclaredGroups.includes(toolset)) {
|
|
475
574
|
const names = p.allTools.filter((t) => p.toolsetOf(t.name) === toolset).map((t) => t.name);
|
|
476
|
-
return textResult(`${c.serverId}/${toolset}: ${names.join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
|
|
575
|
+
return textResult(`${c.serverId}/${nameToTypeBack(toolset)}: ${names.map(nameToTypeBack).join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
|
|
477
576
|
}
|
|
478
577
|
return fail(noToolset(c, toolset!));
|
|
479
578
|
}
|
|
@@ -491,13 +590,13 @@ export class ToolsetsModule implements Module {
|
|
|
491
590
|
|
|
492
591
|
private async close(args: Record<string, unknown>, take: ((taken: TakenChange) => void) | null): Promise<ToolResult> {
|
|
493
592
|
const shown = take !== null;
|
|
494
|
-
const named =
|
|
593
|
+
const named = toolsetNamed(this.hub, args.server, args.toolset);
|
|
495
594
|
const c = named && !str(args.server)
|
|
496
|
-
? owner(this.hub, undefined, (x) => x.presence.isOpen(named,
|
|
595
|
+
? owner(this.hub, undefined, (x) => x.presence.isOpen(named, instanceNamed([x], named, args.instance)) || x.presence.openRecords(named).length > 0, `an open toolset "${named}"`)
|
|
497
596
|
: pick(this.hub, args.server);
|
|
498
597
|
const p = c.presence;
|
|
499
598
|
let toolset = named;
|
|
500
|
-
let instance =
|
|
599
|
+
let instance = instanceNamed([c], named, args.instance);
|
|
501
600
|
if (!toolset) {
|
|
502
601
|
const open = [
|
|
503
602
|
...p.openRecords().filter((o) => !instance || o.instance === instance),
|
|
@@ -505,7 +604,7 @@ export class ToolsetsModule implements Module {
|
|
|
505
604
|
];
|
|
506
605
|
if (open.length !== 1) {
|
|
507
606
|
fail(open.length
|
|
508
|
-
? `several are open on ${c.serverId}: name one of ${open.map((o) => `${o.toolset}${o.instance ? `:${o.instance}` : ''}`).join(', ')}`
|
|
607
|
+
? `several are open on ${c.serverId}: name one of ${open.map((o) => `${nameToTypeBack(o.toolset)}${o.instance ? `:${nameToTypeBack(o.instance)}` : ''}`).join(', ')}`
|
|
509
608
|
: `nothing is open on ${c.serverId}`);
|
|
510
609
|
}
|
|
511
610
|
toolset = open[0]!.toolset;
|
|
@@ -513,17 +612,17 @@ export class ToolsetsModule implements Module {
|
|
|
513
612
|
}
|
|
514
613
|
if (p.isDeclared(toolset!)) {
|
|
515
614
|
// As open refuses one: a toolset without instances is closed without one.
|
|
516
|
-
if (instance && !p.declared[toolset!]!.instances) fail(`${c.serverId}/${toolset} has no instances: close it without one`);
|
|
615
|
+
if (instance && !p.declared[toolset!]!.instances) fail(`${c.serverId}/${nameToTypeBack(toolset!)} has no instances: close it without one`);
|
|
517
616
|
if (p.declared[toolset!]!.instances && !instance) {
|
|
518
617
|
const open = p.openRecords(toolset);
|
|
519
|
-
if (open.length !== 1) fail(open.length ? `several ${toolset} instances are open (${open.map((o) => o.instance).join(', ')}): say which` : `${toolset} is not open`);
|
|
618
|
+
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`);
|
|
520
619
|
instance = open[0]!.instance;
|
|
521
620
|
}
|
|
522
621
|
let closed: Awaited<ReturnType<ToolsetController['close']>>;
|
|
523
622
|
try {
|
|
524
623
|
closed = await c.close(toolset!, instance, { shown });
|
|
525
624
|
} catch (error) {
|
|
526
|
-
const what = `${toolset}${instance ? `:${instance}` : ''}`;
|
|
625
|
+
const what = `${nameToTypeBack(toolset!)}${instance ? `:${nameToTypeBack(instance)}` : ''}`;
|
|
527
626
|
return requestFailure(c.serverId, `did not close ${what}`, `didn't confirm closing ${what}`, error);
|
|
528
627
|
}
|
|
529
628
|
const key = keyOf(toolset!, instance);
|
|
@@ -532,8 +631,8 @@ export class ToolsetsModule implements Module {
|
|
|
532
631
|
return blocksResult([...asBlocks(closed.content), { type: 'text', text: change }]);
|
|
533
632
|
}
|
|
534
633
|
if (p.undeclaredGroups.includes(toolset!)) {
|
|
535
|
-
if (instance) fail(`${c.serverId}/${toolset} takes no instance (the server doesn't declare it)`);
|
|
536
|
-
if (!p.isOpen(toolset!)) fail(`${c.serverId}/${toolset} is not open`);
|
|
634
|
+
if (instance) fail(`${c.serverId}/${nameToTypeBack(toolset!)} takes no instance (the server doesn't declare it)`);
|
|
635
|
+
if (!p.isOpen(toolset!)) fail(`${c.serverId}/${nameToTypeBack(toolset!)} is not open`);
|
|
537
636
|
const cs = c.setGroupOpen(toolset!, false);
|
|
538
637
|
const text = p.render(cs, { mark: shown, describeAll: !shown }) || `${p.label(keyOf(toolset!))} closed.`;
|
|
539
638
|
if (take) take({ toolsets: c, cs });
|