feat: add DelegateTool with multi-turn agentic loop (18 tools total)

- DelegateTool.js: multi-turn sub-agent (max 10 turns), feeds tool results back
- Moved TOOL_DEFS to startBot scope so delegate handler can access tool schemas
- Fixed scoping: delegate handler resolves model from svc.config instead of chatWithAI local
- Wired into tools/index.js, TOOL_DEFS, and toolHandlers
This commit is contained in:
admin
2026-05-05 16:59:59 +00:00
Unverified
parent e92e9f5b9d
commit 092fefbc52
3 changed files with 176 additions and 10 deletions

View File

@@ -198,14 +198,9 @@ export async function initBot(config, api, tools, skills, agents) {
toolMap: new Map((tools || []).map(t => [t.name, t])),
};
// ── AI chat with function calling ──
async function chatWithAI(messages, opts = {}) {
const model = opts.model || svc.config?.api?.models?.default || 'glm-5.1';
const tools = [];
const toolMap = svc.toolMap;
// ── Tool definitions for the AI API (OpenAI function-calling format) ──
const TOOL_DEFS = {
// ── Tool definitions for the AI API (OpenAI function-calling format) ──
// Defined at startBot scope so delegate handler can access them
const TOOL_DEFS = {
bash: {
description: 'Execute a shell command',
parameters: { type: 'object', properties: {
@@ -333,6 +328,15 @@ export async function initBot(config, api, tools, skills, agents) {
selector: { type: 'string', description: 'CSS selector for content extraction (optional, auto-detects article/main)' },
}, required: ['url'] },
},
delegate: {
description: 'Spawn a sub-agent to autonomously complete a complex multi-step task. The sub-agent runs in isolation with its own conversation history and has access to tools. It will use tools, reason through problems, and return a final answer. Use for tasks that require multiple tool calls in sequence.',
parameters: { type: 'object', properties: {
goal: { type: 'string', description: 'The task for the sub-agent to accomplish' },
context: { type: 'string', description: 'Additional context or background information' },
tools: { type: 'array', items: { type: 'string' }, description: 'Specific tools to enable (optional, defaults to all available tools)' },
role: { type: 'string', description: 'Role description for the sub-agent (optional)' },
}, required: ['goal'] },
},
delegate_agent: {
description: 'Delegate to a specialized agent role',
parameters: { type: 'object', properties: {
@@ -349,11 +353,18 @@ export async function initBot(config, api, tools, skills, agents) {
},
};
// ── AI chat with function calling ──
async function chatWithAI(messages, opts = {}) {
const model = opts.model || svc.config?.api?.models?.default || 'glm-5.1';
const tools = [];
const toolMap = svc.toolMap;
// Register all tools that have a matching class loaded
for (const [name, def] of Object.entries(TOOL_DEFS)) {
if (name === 'delegate_agent' && !svc.agents.length) continue;
if (name === 'run_skill' && !svc.skills.length) continue;
if (!toolMap.has(name) && name !== 'delegate_agent' && name !== 'run_skill') continue;
// delegate is special — dynamically created, always available
if (!toolMap.has(name) && name !== 'delegate_agent' && name !== 'run_skill' && name !== 'delegate') continue;
tools.push({ type: 'function', function: { name, ...def } });
}
@@ -615,6 +626,26 @@ export async function initBot(config, api, tools, skills, agents) {
if (!tool) return '❌ Browser tool unavailable.';
try { return await tool.execute(args); } catch (e) { return `${e.message}`; }
},
delegate: async (args) => {
// Dynamically create a DelegateTool with current context
try {
const { DelegateTool } = await import('../tools/DelegateTool.js');
// Build tool defs from the currently available toolHandlers
const subToolDefs = {};
for (const [name, handler] of Object.entries(toolHandlers)) {
subToolDefs[name] = TOOL_DEFS[name] || { description: `Tool: ${name}` };
}
const subAgent = new DelegateTool({
apiClient: svc.api.client,
model: svc.config?.api?.models?.default || 'glm-5.1',
toolHandlers, // pass all current tool handlers
toolDefs: subToolDefs, // pass tool definitions
});
return await subAgent.execute(args);
} catch (e) {
return `❌ Delegate error: ${e.message}`;
}
},
delegate_agent: async (args) => {
const agent = svc.agents.find(a => a.id === args.agent_id);
if (!agent) return `❌ Agent not found: ${args.agent_id}`;