wolfpack-mcp 1.0.96 → 1.0.98
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/dist/browserTools.js +146 -0
- package/dist/client.js +19 -0
- package/dist/index.js +136 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function text(data) {
|
|
3
|
+
return JSON.stringify(data, null, 2);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Browser control for chatbot agents (#2288): see and drive the page the chat
|
|
7
|
+
* widget is embedded in.
|
|
8
|
+
*
|
|
9
|
+
* Gated by the `browser_control` capability, which the backend grants only to a
|
|
10
|
+
* key bound to a chat session — a scheduled run or a coder never sees these.
|
|
11
|
+
* The backend gates every call on the visitor's own grant as well, so a tool
|
|
12
|
+
* called out of turn is refused there rather than here.
|
|
13
|
+
*/
|
|
14
|
+
export const BROWSER_TOOLS = [
|
|
15
|
+
{
|
|
16
|
+
name: 'browser_request_control',
|
|
17
|
+
description: 'Ask the person you are chatting with for permission to drive their browser. ' +
|
|
18
|
+
'They see your reason in the conversation with Allow and Deny buttons, and this call waits for their answer. ' +
|
|
19
|
+
'You MUST call this and be granted control before any other browser tool will work — there is no way around it, ' +
|
|
20
|
+
'and asking repeatedly after a refusal is not acceptable. ' +
|
|
21
|
+
'Ask only when driving the page is genuinely the best way to help; explaining what to click is usually better.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
reason: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'What you want to do on their page, in one plain sentence — this is what they read before deciding (e.g. "Fill in the booking form with the dates we agreed").',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['reason'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'browser_snapshot',
|
|
35
|
+
description: 'See the page: its URL, title, visible text, and the interactive elements you can act on. ' +
|
|
36
|
+
'Each element comes back with a "ref" — pass that ref to browser_click or browser_type. ' +
|
|
37
|
+
'Refs are only valid until the page changes, so take a fresh snapshot after every action.',
|
|
38
|
+
inputSchema: { type: 'object', properties: {} },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'browser_click',
|
|
42
|
+
description: 'Click an element on the page. The visitor sees the pointer travel to it before it is clicked.',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
ref: { type: 'string', description: 'The ref of the element, from browser_snapshot' },
|
|
47
|
+
},
|
|
48
|
+
required: ['ref'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'browser_type',
|
|
53
|
+
description: 'Type text into an input, textarea or select on the page, replacing whatever it holds.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
ref: { type: 'string', description: 'The ref of the field, from browser_snapshot' },
|
|
58
|
+
text: { type: 'string', description: 'The text to enter' },
|
|
59
|
+
},
|
|
60
|
+
required: ['ref', 'text'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'browser_scroll',
|
|
65
|
+
description: 'Scroll the page vertically to bring more of it into view.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
deltaY: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
description: 'Pixels to scroll by — positive scrolls down, negative up',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ['deltaY'],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'browser_release_control',
|
|
79
|
+
description: 'Hand the browser back when you are done, so the visitor stops seeing the "agent in control" frame on their screen. ' +
|
|
80
|
+
'Always do this once the task is finished. They can also stop you themselves at any time.',
|
|
81
|
+
inputSchema: { type: 'object', properties: {} },
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const RequestControlSchema = z.object({ reason: z.string() });
|
|
85
|
+
const ClickSchema = z.object({ ref: z.string() });
|
|
86
|
+
const TypeSchema = z.object({ ref: z.string(), text: z.string() });
|
|
87
|
+
const ScrollSchema = z.object({ deltaY: z.coerce.number() });
|
|
88
|
+
/** A refused or failed command is reported as a tool error, so the agent reads
|
|
89
|
+
* it as something that did not happen rather than as a result. */
|
|
90
|
+
function commandOutcome(result) {
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: result.error ?? 'The command failed' }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { content: [{ type: 'text', text: text(result.result ?? { done: true }) }] };
|
|
98
|
+
}
|
|
99
|
+
export async function handleBrowserTool(name, args, client) {
|
|
100
|
+
switch (name) {
|
|
101
|
+
case 'browser_request_control': {
|
|
102
|
+
const { reason } = RequestControlSchema.parse(args);
|
|
103
|
+
const { outcome } = await client.requestBrowserControl(reason);
|
|
104
|
+
if (outcome === 'granted') {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: 'Granted. Take a browser_snapshot to see the page, and call browser_release_control when you are done.',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: outcome === 'denied'
|
|
119
|
+
? 'The visitor declined. Do not ask again unless they bring it up — help them another way.'
|
|
120
|
+
: 'The visitor did not answer. Carry on without the browser.',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
case 'browser_snapshot':
|
|
127
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'snapshot' }));
|
|
128
|
+
case 'browser_click': {
|
|
129
|
+
const { ref } = ClickSchema.parse(args);
|
|
130
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'click', ref }));
|
|
131
|
+
}
|
|
132
|
+
case 'browser_type': {
|
|
133
|
+
const parsed = TypeSchema.parse(args);
|
|
134
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'type', ...parsed }));
|
|
135
|
+
}
|
|
136
|
+
case 'browser_scroll': {
|
|
137
|
+
const { deltaY } = ScrollSchema.parse(args);
|
|
138
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'scroll', deltaY }));
|
|
139
|
+
}
|
|
140
|
+
case 'browser_release_control':
|
|
141
|
+
await client.releaseBrowserControl();
|
|
142
|
+
return { content: [{ type: 'text', text: 'Browser control handed back to the visitor.' }] };
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unknown browser tool: ${name}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -240,6 +240,13 @@ export class WolfpackClient {
|
|
|
240
240
|
throw error;
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
// Dependency methods (#2331)
|
|
244
|
+
async setWorkItemDependencies(workItemId, dependsOnRefIds, teamSlug) {
|
|
245
|
+
return this.api.put(this.withTeamSlug(`/work-items/${workItemId}/dependencies`, teamSlug), { dependsOnRefIds });
|
|
246
|
+
}
|
|
247
|
+
async getWorkItemDependencyGraph(workItemId, teamSlug) {
|
|
248
|
+
return this.api.get(this.withTeamSlug(`/work-items/${workItemId}/dependency-graph`, teamSlug));
|
|
249
|
+
}
|
|
243
250
|
// Review check methods
|
|
244
251
|
async listWorkItemChecks(filter, teamSlug) {
|
|
245
252
|
const params = new URLSearchParams();
|
|
@@ -981,6 +988,18 @@ export class WolfpackClient {
|
|
|
981
988
|
async saveMemory(key, content) {
|
|
982
989
|
return this.api.put(`/self/memories/${encodeURIComponent(key)}`, { content });
|
|
983
990
|
}
|
|
991
|
+
// ─── Browser control (#2288) ───────────────────────────────────────────────
|
|
992
|
+
// No chat is named on any of these: the backend derives it from the session
|
|
993
|
+
// this key is bound to, so the agent cannot address another visitor's page.
|
|
994
|
+
async requestBrowserControl(reason) {
|
|
995
|
+
return this.api.post('/browser/request', { reason });
|
|
996
|
+
}
|
|
997
|
+
async runBrowserCommand(command) {
|
|
998
|
+
return this.api.post('/browser/command', command);
|
|
999
|
+
}
|
|
1000
|
+
async releaseBrowserControl() {
|
|
1001
|
+
await this.api.post('/browser/release', {});
|
|
1002
|
+
}
|
|
984
1003
|
close() {
|
|
985
1004
|
// No cleanup needed for API client
|
|
986
1005
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { validateConfig, config } from './config.js';
|
|
|
12
12
|
import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
13
13
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
14
14
|
import { AGENT_SELF_TOOLS, AGENT_MEMORY_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
15
|
+
import { BROWSER_TOOLS, handleBrowserTool } from './browserTools.js';
|
|
15
16
|
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
16
17
|
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
17
18
|
import { fetch as proxyFetch } from './proxyFetch.js';
|
|
@@ -215,6 +216,29 @@ const SubmitWorkItemFormSchema = z.object({
|
|
|
215
216
|
.optional()
|
|
216
217
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
217
218
|
});
|
|
219
|
+
// #2331 — reference numbers, accepted as numbers or as "#2322" the way agents
|
|
220
|
+
// write them, so an ordered plan can be transcribed without reformatting.
|
|
221
|
+
const dependsOnRefIds = () => z
|
|
222
|
+
.array(z.union([z.number(), z.string()]))
|
|
223
|
+
.transform((refs) => refs.map((ref) => Number(String(ref).replace(/^#/, ''))))
|
|
224
|
+
.refine((refs) => refs.every(Number.isInteger), {
|
|
225
|
+
message: 'depends_on must be work item reference numbers, e.g. [2322] or ["#2322"]',
|
|
226
|
+
});
|
|
227
|
+
const SetWorkItemDependenciesSchema = z.object({
|
|
228
|
+
work_item_id: refIdString().describe('The refId of the work item that follows the others'),
|
|
229
|
+
depends_on: dependsOnRefIds().describe('Reference numbers of the prerequisites. Replaces the whole set; pass [] to clear.'),
|
|
230
|
+
project_slug: z
|
|
231
|
+
.string()
|
|
232
|
+
.optional()
|
|
233
|
+
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
234
|
+
});
|
|
235
|
+
const GetWorkItemDependencyGraphSchema = z.object({
|
|
236
|
+
work_item_id: refIdString().describe('The refId of the work item'),
|
|
237
|
+
project_slug: z
|
|
238
|
+
.string()
|
|
239
|
+
.optional()
|
|
240
|
+
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
241
|
+
});
|
|
218
242
|
const WorkPoolMemberSchema = z.object({
|
|
219
243
|
work_pool: z.string().describe('Work pool id or name'),
|
|
220
244
|
user_id: z.string().describe('The user id to add or remove'),
|
|
@@ -368,6 +392,9 @@ const CreateWorkItemSchema = z.object({
|
|
|
368
392
|
.string()
|
|
369
393
|
.optional()
|
|
370
394
|
.describe('Link to a radar/initiative item. Accepts refId ("5" or "#r5") or UUID.'),
|
|
395
|
+
depends_on: dependsOnRefIds()
|
|
396
|
+
.optional()
|
|
397
|
+
.describe('Reference numbers of work items that must finish before this one starts'),
|
|
371
398
|
});
|
|
372
399
|
const CreateIssueSchema = z.object({
|
|
373
400
|
project_slug: z
|
|
@@ -1268,6 +1295,56 @@ class WolfpackMCPServer {
|
|
|
1268
1295
|
required: ['work_item_id', 'check_id', 'status'],
|
|
1269
1296
|
},
|
|
1270
1297
|
},
|
|
1298
|
+
{
|
|
1299
|
+
name: 'set_work_item_dependencies',
|
|
1300
|
+
description: 'Record which work items must finish before this one can start, so the board knows the order ' +
|
|
1301
|
+
'and the pickup gate enforces it. Use this when you file or plan a set of ordered items, instead ' +
|
|
1302
|
+
'of writing "Follows #2322" into a description — a description is prose nothing enforces. ' +
|
|
1303
|
+
'A bare #N is a WORK ITEM refId, and depends_on takes those reference numbers. ' +
|
|
1304
|
+
'REPLACES the whole set: pass every prerequisite you want the item to have, and [] to clear it. ' +
|
|
1305
|
+
'An item may not depend on itself, nor on anything that already depends on it — either would leave ' +
|
|
1306
|
+
'it permanently blocked, and both are refused with 400 naming the item. ' +
|
|
1307
|
+
'Requires the mcp:work_items:manage_dependencies permission, which is separate from ' +
|
|
1308
|
+
"mcp:work_items:update: updating your own item does not let you order other people's work. " +
|
|
1309
|
+
'Returns what the item now depends on, what follows it, and which prerequisites are still unfinished.',
|
|
1310
|
+
inputSchema: {
|
|
1311
|
+
type: 'object',
|
|
1312
|
+
properties: {
|
|
1313
|
+
work_item_id: {
|
|
1314
|
+
type: 'string',
|
|
1315
|
+
description: 'The refId of the work item that follows the others',
|
|
1316
|
+
},
|
|
1317
|
+
depends_on: {
|
|
1318
|
+
type: 'array',
|
|
1319
|
+
items: { type: ['number', 'string'] },
|
|
1320
|
+
description: 'Reference numbers of the prerequisites, as 2322 or "#2322". Replaces the whole set; pass [] to clear.',
|
|
1321
|
+
},
|
|
1322
|
+
project_slug: {
|
|
1323
|
+
type: 'string',
|
|
1324
|
+
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1325
|
+
},
|
|
1326
|
+
},
|
|
1327
|
+
required: ['work_item_id', 'depends_on'],
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
name: 'get_work_item_dependency_graph',
|
|
1332
|
+
description: 'The whole chain of ordered work around one work item — the prerequisites above it and the work ' +
|
|
1333
|
+
'that follows below — as nodes and edges, each item appearing exactly once. An edge runs from the ' +
|
|
1334
|
+
"item that must finish to the item that waits on it. Use it to see a plan's shape; " +
|
|
1335
|
+
"get_work_item already gives one item's own dependsOn, nextItems and blockedBy.",
|
|
1336
|
+
inputSchema: {
|
|
1337
|
+
type: 'object',
|
|
1338
|
+
properties: {
|
|
1339
|
+
work_item_id: { type: 'string', description: 'The refId of the work item' },
|
|
1340
|
+
project_slug: {
|
|
1341
|
+
type: 'string',
|
|
1342
|
+
description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
|
|
1343
|
+
},
|
|
1344
|
+
},
|
|
1345
|
+
required: ['work_item_id'],
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1271
1348
|
// Radar Item (Initiative/Roadmap) tools
|
|
1272
1349
|
{
|
|
1273
1350
|
name: 'list_radar_items',
|
|
@@ -1458,6 +1535,9 @@ class WolfpackMCPServer {
|
|
|
1458
1535
|
name: 'create_work_item',
|
|
1459
1536
|
description: 'Create a new work item in your current project (auto-selected for single-project users, or use list_projects first for multi-project). Requires mcp:work_items:create permission. ' +
|
|
1460
1537
|
'Agents: work you file lands unassigned in the backlog, whatever status you ask for, and waits there for the Delivery Lead to approve and assign it. ' +
|
|
1538
|
+
'ORDERED WORK: when you file a set of items that must happen in order, give each one depends_on ' +
|
|
1539
|
+
'naming the reference numbers it follows, rather than writing "Follows #2322" into the description — ' +
|
|
1540
|
+
'that way the board knows the order and the pickup gate enforces it. ' +
|
|
1461
1541
|
CONTENT_LINKING_HELP,
|
|
1462
1542
|
inputSchema: {
|
|
1463
1543
|
type: 'object',
|
|
@@ -1507,6 +1587,11 @@ class WolfpackMCPServer {
|
|
|
1507
1587
|
type: 'string',
|
|
1508
1588
|
description: 'Link to a radar/initiative item. Accepts refId (e.g. "5" or "#r5") or UUID.',
|
|
1509
1589
|
},
|
|
1590
|
+
depends_on: {
|
|
1591
|
+
type: 'array',
|
|
1592
|
+
items: { type: ['number', 'string'] },
|
|
1593
|
+
description: 'Reference numbers of work items that must finish before this one starts, as 2322 or "#2322". Filing an ordered set one call each needs mcp:work_items:manage_dependencies.',
|
|
1594
|
+
},
|
|
1510
1595
|
},
|
|
1511
1596
|
required: ['title'],
|
|
1512
1597
|
},
|
|
@@ -2467,6 +2552,7 @@ class WolfpackMCPServer {
|
|
|
2467
2552
|
...(this.capabilities.includes('agent_self') ? AGENT_SELF_TOOLS : []),
|
|
2468
2553
|
...(this.capabilities.includes('agent_memory') ? AGENT_MEMORY_TOOLS : []),
|
|
2469
2554
|
...(this.capabilities.includes('agent_builder') ? AGENT_BUILDER_TOOLS : []),
|
|
2555
|
+
...(this.capabilities.includes('browser_control') ? BROWSER_TOOLS : []),
|
|
2470
2556
|
],
|
|
2471
2557
|
};
|
|
2472
2558
|
});
|
|
@@ -2785,6 +2871,48 @@ class WolfpackMCPServer {
|
|
|
2785
2871
|
],
|
|
2786
2872
|
};
|
|
2787
2873
|
}
|
|
2874
|
+
case 'set_work_item_dependencies': {
|
|
2875
|
+
const parsed = SetWorkItemDependenciesSchema.parse(args);
|
|
2876
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2877
|
+
const dependencies = await this.client.setWorkItemDependencies(parsed.work_item_id, parsed.depends_on, teamSlug);
|
|
2878
|
+
const name = (items) => items.map((item) => `#${item.refId} "${item.title}"`).join(', ');
|
|
2879
|
+
const summary = dependencies.dependsOn.length === 0
|
|
2880
|
+
? `#${parsed.work_item_id} now depends on nothing`
|
|
2881
|
+
: `#${parsed.work_item_id} now depends on ${name(dependencies.dependsOn)}` +
|
|
2882
|
+
(dependencies.blockedBy.length > 0
|
|
2883
|
+
? `, and cannot start until ${name(dependencies.blockedBy)} finishes`
|
|
2884
|
+
: ', all of which have finished');
|
|
2885
|
+
return {
|
|
2886
|
+
content: [
|
|
2887
|
+
{
|
|
2888
|
+
type: 'text',
|
|
2889
|
+
text: `${summary}\n\n${JSON.stringify(stripUuids(dependencies), null, 2)}`,
|
|
2890
|
+
},
|
|
2891
|
+
],
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
case 'get_work_item_dependency_graph': {
|
|
2895
|
+
const parsed = GetWorkItemDependencyGraphSchema.parse(args);
|
|
2896
|
+
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2897
|
+
const graph = await this.client.getWorkItemDependencyGraph(parsed.work_item_id, teamSlug);
|
|
2898
|
+
// Edges name nodes by UUID, which agents never see, so they are
|
|
2899
|
+
// rewritten as reference numbers before the UUIDs are stripped —
|
|
2900
|
+
// dropping them would leave a list of items and no order at all
|
|
2901
|
+
const byId = new Map(graph.nodes.map((node) => [node.id, node.refId]));
|
|
2902
|
+
return {
|
|
2903
|
+
content: [
|
|
2904
|
+
{
|
|
2905
|
+
type: 'text',
|
|
2906
|
+
text: JSON.stringify({
|
|
2907
|
+
root: `#${byId.get(graph.rootId)}`,
|
|
2908
|
+
items: stripUuids(graph.nodes),
|
|
2909
|
+
// Each entry reads "A must finish before B can start"
|
|
2910
|
+
mustFinishBefore: graph.edges.map((edge) => `#${byId.get(edge.from)} → #${byId.get(edge.to)}`),
|
|
2911
|
+
}, null, 2),
|
|
2912
|
+
},
|
|
2913
|
+
],
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2788
2916
|
// Radar Item handlers
|
|
2789
2917
|
case 'list_radar_items': {
|
|
2790
2918
|
const parsed = ListRadarItemsSchema.parse(args);
|
|
@@ -2904,6 +3032,7 @@ class WolfpackMCPServer {
|
|
|
2904
3032
|
leadingUserId: parsed.leading_user_id,
|
|
2905
3033
|
categoryId: parsed.category_id,
|
|
2906
3034
|
radarItemId,
|
|
3035
|
+
dependsOnRefIds: parsed.depends_on,
|
|
2907
3036
|
teamSlug,
|
|
2908
3037
|
});
|
|
2909
3038
|
return {
|
|
@@ -3537,6 +3666,13 @@ class WolfpackMCPServer {
|
|
|
3537
3666
|
return handleAgentSelfTool(name, args, this.client);
|
|
3538
3667
|
}
|
|
3539
3668
|
}
|
|
3669
|
+
// Check browser control tools (#2288 — chat sessions only)
|
|
3670
|
+
if (this.capabilities.includes('browser_control')) {
|
|
3671
|
+
const browserToolNames = BROWSER_TOOLS.map((t) => t.name);
|
|
3672
|
+
if (browserToolNames.includes(name)) {
|
|
3673
|
+
return handleBrowserTool(name, args, this.client);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3540
3676
|
// Check agent builder tools
|
|
3541
3677
|
if (this.capabilities.includes('agent_builder')) {
|
|
3542
3678
|
return handleAgentBuilderTool(name, args, this.client);
|