dsh-agent-toolkit 0.1.0 → 0.2.1
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 +2 -2
- package/lib/client.js +260 -113
- package/lib/client.js.map +1 -1
- package/lib/index.js +181 -52
- package/lib/index.js.map +1 -1
- package/package.json +5 -3
package/lib/index.js
CHANGED
|
@@ -60,7 +60,42 @@ function migrateAgentRecord(record) {
|
|
|
60
60
|
} : rest;
|
|
61
61
|
}
|
|
62
62
|
//#endregion
|
|
63
|
+
//#region src/channels/basic-tools.ts
|
|
64
|
+
const BASIC_TOOLS = [
|
|
65
|
+
{
|
|
66
|
+
id: "@deepseek-ai/dsh-persona",
|
|
67
|
+
config: { text: "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}." }
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "@deepseek-ai/dsh-agent-instructions",
|
|
71
|
+
config: { maxBytes: 65536 }
|
|
72
|
+
},
|
|
73
|
+
...process.platform === "win32" ? [{ id: "@deepseek-ai/dsh-tool-pwsh" }] : [{ id: "@deepseek-ai/dsh-tool-bash" }],
|
|
74
|
+
{ id: "@deepseek-ai/dsh-tool-fs" },
|
|
75
|
+
{
|
|
76
|
+
id: "@deepseek-ai/dsh-tool-fs-search",
|
|
77
|
+
config: { sampleOverCapGlobResults: false }
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
/** 原生工具名(白名单 UI 与存量迁移用):与 BASIC_TOOLS 挂载插件注册的工具名一一对应。
|
|
81
|
+
* 名字来源(摘自 deepseek-harness 源码):dsh-tool-pwsh/dsh-tool-bash → 'pwsh'/'bash'(平台互斥);
|
|
82
|
+
* dsh-tool-fs → 'read'/'write'/'edit'/'read_image';dsh-tool-fs-search → 'glob'/'grep'。
|
|
83
|
+
* 这些工具 scoped 挂载在 agentCtx,不出现在顶层 ctx.tools.schemas(),故需显式常量。 */
|
|
84
|
+
const NATIVE_TOOL_NAMES = [
|
|
85
|
+
process.platform === "win32" ? "pwsh" : "bash",
|
|
86
|
+
"read",
|
|
87
|
+
"write",
|
|
88
|
+
"edit",
|
|
89
|
+
"read_image",
|
|
90
|
+
"glob",
|
|
91
|
+
"grep"
|
|
92
|
+
];
|
|
93
|
+
//#endregion
|
|
63
94
|
//#region src/agents/builtin.ts
|
|
95
|
+
/** 内置保底 Agent 记录:main + explorer(只读白名单)/ general(不限制)。 */
|
|
96
|
+
/** explorer 默认白名单:原生工具去掉写类(write/edit)。shell 名平台互斥(win32=pwsh、其余=bash),
|
|
97
|
+
* 必须从 NATIVE_TOOL_NAMES 派生不可写死——宿主 tools.restrict 对未知名响亮失败。 */
|
|
98
|
+
const EXPLORER_READONLY_ALLOW = NATIVE_TOOL_NAMES.filter((n) => n !== "write" && n !== "edit");
|
|
64
99
|
const BUILTIN_AGENTS = [
|
|
65
100
|
{
|
|
66
101
|
id: "main",
|
|
@@ -74,7 +109,8 @@ const BUILTIN_AGENTS = [
|
|
|
74
109
|
persona: `你是代码库探索员。快速定位与任务相关的文件与符号,回答关于代码结构、
|
|
75
110
|
调用关系、实现位置的问题。你只读不写:不修改任何文件、不运行有副作用的命令。
|
|
76
111
|
输出结论清单,每条附文件路径与行号;信息不足时说明缺口,不要猜测。`,
|
|
77
|
-
builtin: true
|
|
112
|
+
builtin: true,
|
|
113
|
+
tools: { allow: [...EXPLORER_READONLY_ALLOW] }
|
|
78
114
|
},
|
|
79
115
|
{
|
|
80
116
|
id: "general",
|
|
@@ -97,20 +133,16 @@ const RoleYamlSchema = z$1.object({
|
|
|
97
133
|
persona: z$1.string().min(1),
|
|
98
134
|
provider: z$1.string().optional(),
|
|
99
135
|
model: z$1.string().optional(),
|
|
100
|
-
tools: z$1.object({
|
|
101
|
-
allow: z$1.array(z$1.string()).optional(),
|
|
102
|
-
deny: z$1.array(z$1.string()).optional()
|
|
103
|
-
}).optional()
|
|
136
|
+
tools: z$1.object({ allow: z$1.array(z$1.string()).optional() }).optional()
|
|
104
137
|
});
|
|
105
138
|
/**
|
|
106
139
|
* 解析校验单个角色 YAML 文件并转成 AgentRecord。
|
|
107
140
|
* @param text - 文件内容。
|
|
108
141
|
* @param source - 用于错误信息的来源名(通常是文件路径)。
|
|
109
142
|
* @param fileName - 文件名(去 .yml),name 省略时的取值;显式 name 须与它一致。
|
|
110
|
-
* @param warn - 非致命丢弃(如 tools.deny)的通知通道。
|
|
111
143
|
* @throws YAML 语法错误、结构非法、name 与文件名不一致、id 非法、tools 空。
|
|
112
144
|
*/
|
|
113
|
-
function parseRoleYaml(text, source, fileName
|
|
145
|
+
function parseRoleYaml(text, source, fileName) {
|
|
114
146
|
let parsed;
|
|
115
147
|
try {
|
|
116
148
|
parsed = yaml.load(text);
|
|
@@ -127,8 +159,7 @@ function parseRoleYaml(text, source, fileName, warn) {
|
|
|
127
159
|
const id = raw.name ?? fileName;
|
|
128
160
|
if (raw.name !== void 0 && raw.name !== fileName) throw new Error(`dsh-agent-toolkit: 角色文件 ${source} 的 name "${raw.name}" 与文件名 "${fileName}" 不一致(省略 name 即取文件名)`);
|
|
129
161
|
if (!AGENT_ID_RE.test(id)) throw new Error(`dsh-agent-toolkit: 角色 id "${id}" 非法(${source}):只允许小写字母、数字、-,且以小写字母开头`);
|
|
130
|
-
if (hasTools && raw.tools !== void 0 && (raw.tools.allow?.length ?? 0) === 0
|
|
131
|
-
if (raw.tools?.deny !== void 0 && raw.tools.deny.length > 0) warn?.(`dsh-agent-toolkit: 角色文件 ${source} 的 tools.deny 已忽略(注册表仅支持 allow 白名单)`);
|
|
162
|
+
if (hasTools && raw.tools !== void 0 && (raw.tools.allow?.length ?? 0) === 0) throw new Error(`dsh-agent-toolkit: 角色文件 ${source} 的 tools 为空:allow 至少配一个(不需要限制请整段省略 tools)`);
|
|
132
163
|
const model = raw.provider !== void 0 && raw.model !== void 0 ? {
|
|
133
164
|
provider: raw.provider,
|
|
134
165
|
model: raw.model
|
|
@@ -186,7 +217,7 @@ async function importRolesYaml(ctx, rolesDir) {
|
|
|
186
217
|
return result;
|
|
187
218
|
}
|
|
188
219
|
for (const ref of refs) try {
|
|
189
|
-
const record = parseRoleYaml(await readFile(ref.path, "utf8"), ref.path, ref.fileName
|
|
220
|
+
const record = parseRoleYaml(await readFile(ref.path, "utf8"), ref.path, ref.fileName);
|
|
190
221
|
await ctx.agents.put(record.id, record);
|
|
191
222
|
result.imported++;
|
|
192
223
|
} catch (error) {
|
|
@@ -200,47 +231,17 @@ async function markImported(ctx) {
|
|
|
200
231
|
await ctx.meta.put(ROLES_YAML_IMPORTED_KEY, { value: "1" });
|
|
201
232
|
}
|
|
202
233
|
//#endregion
|
|
203
|
-
//#region src/channels/basic-tools.ts
|
|
204
|
-
const BASIC_TOOLS = [
|
|
205
|
-
{
|
|
206
|
-
id: "@deepseek-ai/dsh-persona",
|
|
207
|
-
config: { text: "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}." }
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
id: "@deepseek-ai/dsh-agent-instructions",
|
|
211
|
-
config: { maxBytes: 65536 }
|
|
212
|
-
},
|
|
213
|
-
...process.platform === "win32" ? [{ id: "@deepseek-ai/dsh-tool-pwsh" }] : [{ id: "@deepseek-ai/dsh-tool-bash" }],
|
|
214
|
-
{ id: "@deepseek-ai/dsh-tool-fs" },
|
|
215
|
-
{
|
|
216
|
-
id: "@deepseek-ai/dsh-tool-fs-search",
|
|
217
|
-
config: { sampleOverCapGlobResults: false }
|
|
218
|
-
}
|
|
219
|
-
];
|
|
220
|
-
/** 原生工具名(白名单 UI 与存量迁移用):与 BASIC_TOOLS 挂载插件注册的工具名一一对应。
|
|
221
|
-
* 名字来源(摘自 deepseek-harness 源码):dsh-tool-pwsh/dsh-tool-bash → 'pwsh'/'bash'(平台互斥);
|
|
222
|
-
* dsh-tool-fs → 'read'/'write'/'edit'/'read_image';dsh-tool-fs-search → 'glob'/'grep'。
|
|
223
|
-
* 这些工具 scoped 挂载在 agentCtx,不出现在顶层 ctx.tools.schemas(),故需显式常量。 */
|
|
224
|
-
const NATIVE_TOOL_NAMES = [
|
|
225
|
-
process.platform === "win32" ? "pwsh" : "bash",
|
|
226
|
-
"read",
|
|
227
|
-
"write",
|
|
228
|
-
"edit",
|
|
229
|
-
"read_image",
|
|
230
|
-
"glob",
|
|
231
|
-
"grep"
|
|
232
|
-
];
|
|
233
|
-
//#endregion
|
|
234
234
|
//#region src/agents/registry.ts
|
|
235
235
|
/** tools.allow 一次性并入原生工具名的 meta 表标记键。 */
|
|
236
236
|
const TOOLS_NATIVE_MIGRATED_KEY = "tools_native_migrated";
|
|
237
|
+
/** explorer 只读白名单一次性并入的 meta 表标记键。 */
|
|
238
|
+
const EXPLORER_READONLY_MIGRATED_KEY = "explorer_readonly_migrated";
|
|
237
239
|
/**
|
|
238
|
-
* 打开 dsh_agent_toolkit 域 →
|
|
239
|
-
* 构建内存缓存。域由 apply 统一 open(storage-domain 同名单开),此处只消费表句柄。
|
|
240
|
+
* 打开 dsh_agent_toolkit 域 → 首启 YAML 导入 → 旧记录迁移(promptLayers/原生并入/explorer 只读)→
|
|
241
|
+
* 缺 main/explorer/general 时种入内置 → 构建内存缓存。域由 apply 统一 open(storage-domain 同名单开),此处只消费表句柄。
|
|
240
242
|
*/
|
|
241
243
|
async function createRegistry(warn, tables) {
|
|
242
244
|
const { agents, meta } = tables;
|
|
243
|
-
await seedBuiltins(agents);
|
|
244
245
|
await importRolesYaml({
|
|
245
246
|
agents,
|
|
246
247
|
meta,
|
|
@@ -260,6 +261,15 @@ async function createRegistry(warn, tables) {
|
|
|
260
261
|
if (next !== record) await agents.put(id, next);
|
|
261
262
|
}
|
|
262
263
|
if (!nativeMigrated) await meta.put(TOOLS_NATIVE_MIGRATED_KEY, { value: "1" });
|
|
264
|
+
if (!(meta.get("explorer_readonly_migrated") !== void 0)) {
|
|
265
|
+
const explorer = agents.get("explorer");
|
|
266
|
+
if (explorer !== void 0 && explorer.tools === void 0) await agents.put("explorer", {
|
|
267
|
+
...explorer,
|
|
268
|
+
tools: { allow: [...EXPLORER_READONLY_ALLOW] }
|
|
269
|
+
});
|
|
270
|
+
await meta.put(EXPLORER_READONLY_MIGRATED_KEY, { value: "1" });
|
|
271
|
+
}
|
|
272
|
+
await seedBuiltins(agents);
|
|
263
273
|
const cache = /* @__PURE__ */ new Map();
|
|
264
274
|
for (const [id, record] of agents.entries()) cache.set(id, record);
|
|
265
275
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1192,7 +1202,7 @@ function withPartialText(error, output) {
|
|
|
1192
1202
|
return text.length === 0 ? error : `${error}\n成员中断前的部分产出:\n${text}`;
|
|
1193
1203
|
}
|
|
1194
1204
|
/** 收集并释放一次前台运行;dispose 失败不掩盖独立的结果失败。 */
|
|
1195
|
-
async function settleForegroundRun(run, roleId) {
|
|
1205
|
+
async function settleForegroundRun(run, roleId, route) {
|
|
1196
1206
|
const childSessionId = String(run.id);
|
|
1197
1207
|
const [execution] = await Promise.allSettled([run.result.then((result) => {
|
|
1198
1208
|
const error = stopReasonError(result);
|
|
@@ -1202,7 +1212,11 @@ async function settleForegroundRun(run, roleId) {
|
|
|
1202
1212
|
role: roleId,
|
|
1203
1213
|
runId: String(run.id),
|
|
1204
1214
|
childSessionId,
|
|
1205
|
-
output: result.output
|
|
1215
|
+
output: result.output,
|
|
1216
|
+
...route !== void 0 ? {
|
|
1217
|
+
provider: route.provider,
|
|
1218
|
+
model: route.model
|
|
1219
|
+
} : {}
|
|
1206
1220
|
};
|
|
1207
1221
|
})]);
|
|
1208
1222
|
const [disposal] = await Promise.allSettled([Promise.resolve().then(() => run.dispose())]);
|
|
@@ -1269,7 +1283,9 @@ function createDelegateTool(toolName, deps) {
|
|
|
1269
1283
|
type: "array",
|
|
1270
1284
|
required: true,
|
|
1271
1285
|
items: { type: "json" }
|
|
1272
|
-
}
|
|
1286
|
+
},
|
|
1287
|
+
provider: { type: "string" },
|
|
1288
|
+
model: { type: "string" }
|
|
1273
1289
|
}
|
|
1274
1290
|
},
|
|
1275
1291
|
render: (_args, value) => [{
|
|
@@ -1279,7 +1295,11 @@ function createDelegateTool(toolName, deps) {
|
|
|
1279
1295
|
presentationMeta: (_args, value) => ({
|
|
1280
1296
|
role: value.role,
|
|
1281
1297
|
runId: value.runId,
|
|
1282
|
-
childSessionId: value.childSessionId
|
|
1298
|
+
childSessionId: value.childSessionId,
|
|
1299
|
+
...typeof value.provider === "string" && typeof value.model === "string" ? {
|
|
1300
|
+
provider: value.provider,
|
|
1301
|
+
model: value.model
|
|
1302
|
+
} : {}
|
|
1283
1303
|
})
|
|
1284
1304
|
},
|
|
1285
1305
|
isConcurrencySafe: () => true,
|
|
@@ -1312,7 +1332,19 @@ function createDelegateTool(toolName, deps) {
|
|
|
1312
1332
|
} } : {},
|
|
1313
1333
|
...role.tools !== void 0 ? { toolFilter: { allow: [...role.tools.allow] } } : {}
|
|
1314
1334
|
};
|
|
1315
|
-
|
|
1335
|
+
const route = (role.model !== void 0 && role.model.provider !== "" && role.model.model !== "" ? role.model : void 0) ?? (typeof parent.options.provider === "string" && parent.options.provider !== "" && typeof parent.options.model === "string" && parent.options.model !== "" ? {
|
|
1336
|
+
provider: parent.options.provider,
|
|
1337
|
+
model: parent.options.model
|
|
1338
|
+
} : void 0);
|
|
1339
|
+
const parentSessionId = String(parent.session.id);
|
|
1340
|
+
if (route !== void 0) deps.active.set(parentSessionId, role.id, route);
|
|
1341
|
+
try {
|
|
1342
|
+
const run = await deps.startRun(deps.provider, request);
|
|
1343
|
+
if (route !== void 0) await deps.recordRoute(String(run.id), route).catch(() => void 0);
|
|
1344
|
+
return await settleForegroundRun(run, role.id, route);
|
|
1345
|
+
} finally {
|
|
1346
|
+
if (route !== void 0) deps.active.delete(parentSessionId, role.id);
|
|
1347
|
+
}
|
|
1316
1348
|
}
|
|
1317
1349
|
});
|
|
1318
1350
|
}
|
|
@@ -1326,7 +1358,7 @@ const TEAM_SECTION_ORDER = 116.6;
|
|
|
1326
1358
|
* 工具随 provider 在场与否挂载/摘除。名册来自注册表(main 排除),工具与提示段都经
|
|
1327
1359
|
* 闭包读取 registry,UI 改角色后新会话即生效。
|
|
1328
1360
|
*/
|
|
1329
|
-
function setupDelegate(ctx, config, registry) {
|
|
1361
|
+
function setupDelegate(ctx, config, registry, channels) {
|
|
1330
1362
|
const { provider, toolName } = config;
|
|
1331
1363
|
let disposeTool;
|
|
1332
1364
|
let providerFailed = false;
|
|
@@ -1339,7 +1371,9 @@ function setupDelegate(ctx, config, registry) {
|
|
|
1339
1371
|
roster: () => registry.list(),
|
|
1340
1372
|
provider,
|
|
1341
1373
|
buildPersona: (role) => buildAgentPersona({ rules: config.rules }, role, role.model),
|
|
1342
|
-
startRun: (pr, request) => ctx.subagents.start(pr, request)
|
|
1374
|
+
startRun: (pr, request) => ctx.subagents.start(pr, request),
|
|
1375
|
+
active: channels.active,
|
|
1376
|
+
recordRoute: channels.recordRoute
|
|
1343
1377
|
}));
|
|
1344
1378
|
};
|
|
1345
1379
|
ctx.on("subagent/provider-added", (p) => {
|
|
@@ -1372,6 +1406,86 @@ function setupDelegate(ctx, config, registry) {
|
|
|
1372
1406
|
});
|
|
1373
1407
|
}
|
|
1374
1408
|
//#endregion
|
|
1409
|
+
//#region src/delegate/active.ts
|
|
1410
|
+
function createActiveRoutes() {
|
|
1411
|
+
const map = /* @__PURE__ */ new Map();
|
|
1412
|
+
const key = (sessionId, roleId) => `${sessionId}:${roleId}`;
|
|
1413
|
+
return {
|
|
1414
|
+
set: (sessionId, roleId, route) => {
|
|
1415
|
+
map.set(key(sessionId, roleId), route);
|
|
1416
|
+
},
|
|
1417
|
+
get: (sessionId, roleId) => map.get(key(sessionId, roleId)),
|
|
1418
|
+
delete: (sessionId, roleId) => {
|
|
1419
|
+
map.delete(key(sessionId, roleId));
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/delegate/routes.ts
|
|
1425
|
+
/** 委派路由持久存储域:子会话头部 chip 的数据源(schema 与 domain 布局的单一来源在本文件)。 */
|
|
1426
|
+
const DelegationRouteRecordSchema = z$1.object({
|
|
1427
|
+
provider: z$1.string(),
|
|
1428
|
+
model: z$1.string(),
|
|
1429
|
+
at: z$1.number()
|
|
1430
|
+
});
|
|
1431
|
+
const delegationRoutesDomain = defineDomain({
|
|
1432
|
+
name: "dsh_agent_toolkit_routes",
|
|
1433
|
+
version: 1,
|
|
1434
|
+
tables: { routes: domainTable(DelegationRouteRecordSchema) }
|
|
1435
|
+
});
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/delegate/api.ts
|
|
1438
|
+
function createDelegateApiHandler(deps) {
|
|
1439
|
+
return async (req, res) => {
|
|
1440
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1441
|
+
const sub = url.pathname.replace(/^\/dsh-agent-toolkit\/api/, "") || "/";
|
|
1442
|
+
if (req.method !== "GET") {
|
|
1443
|
+
json$1(res, 405, { error: "method not allowed" });
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (sub === "/delegate/active") {
|
|
1447
|
+
const route = deps.active.get(url.searchParams.get("session") ?? "", url.searchParams.get("role") ?? "");
|
|
1448
|
+
if (route === void 0) {
|
|
1449
|
+
json$1(res, 404, { error: "not found" });
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
json$1(res, 200, {
|
|
1453
|
+
provider: route.provider,
|
|
1454
|
+
model: route.model
|
|
1455
|
+
});
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
if (sub === "/delegate/route") {
|
|
1459
|
+
const record = deps.routes.get(url.searchParams.get("session") ?? "");
|
|
1460
|
+
if (record === void 0) {
|
|
1461
|
+
json$1(res, 404, { error: "not found" });
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
json$1(res, 200, {
|
|
1465
|
+
provider: record.provider,
|
|
1466
|
+
model: record.model
|
|
1467
|
+
});
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
json$1(res, 404, { error: "not found" });
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* 注册 delegate 路由(恒启用,与 agents 同策略)。webServer 为可选服务:
|
|
1475
|
+
* 缺席时经 registerOptionalRoutes 惰性不注册。prefix 先于 /api 兜底前缀命中。
|
|
1476
|
+
*/
|
|
1477
|
+
function setupDelegateApi(ctx, deps) {
|
|
1478
|
+
const handler = createDelegateApiHandler(deps);
|
|
1479
|
+
registerOptionalRoutes(ctx, (webCtx) => {
|
|
1480
|
+
const unregister = webCtx.webServer.register({
|
|
1481
|
+
kind: "prefix",
|
|
1482
|
+
path: "/dsh-agent-toolkit/api/delegate",
|
|
1483
|
+
handler
|
|
1484
|
+
});
|
|
1485
|
+
return () => unregister();
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
//#endregion
|
|
1375
1489
|
//#region src/agents/api.ts
|
|
1376
1490
|
function createAgentsApiHandler(deps) {
|
|
1377
1491
|
return async (req, res) => {
|
|
@@ -3107,11 +3221,26 @@ async function apply(ctx, config) {
|
|
|
3107
3221
|
source: layerSource,
|
|
3108
3222
|
rules: config.rules
|
|
3109
3223
|
});
|
|
3224
|
+
const routesTable = (await openDomainSafely(ctx, delegationRoutesDomain, warn)).table("routes");
|
|
3225
|
+
const activeRoutes = createActiveRoutes();
|
|
3110
3226
|
setupDelegate(ctx, {
|
|
3111
3227
|
provider: config.provider,
|
|
3112
3228
|
toolName: config.toolName,
|
|
3113
3229
|
rules: config.rules
|
|
3114
|
-
}, registry
|
|
3230
|
+
}, registry, {
|
|
3231
|
+
active: activeRoutes,
|
|
3232
|
+
recordRoute: async (childSessionId, route) => {
|
|
3233
|
+
await routesTable.put(childSessionId, {
|
|
3234
|
+
provider: route.provider,
|
|
3235
|
+
model: route.model,
|
|
3236
|
+
at: Date.now()
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
});
|
|
3240
|
+
setupDelegateApi(ctx, {
|
|
3241
|
+
active: activeRoutes,
|
|
3242
|
+
routes: routesTable
|
|
3243
|
+
});
|
|
3115
3244
|
setupAgentsApi(ctx, {
|
|
3116
3245
|
registry,
|
|
3117
3246
|
listTools: () => ctx.tools.schemas().map((s) => s.name),
|