feat: agent-to-agent communication, dynamic agent creation, self-modification tools

Agent-to-agent: host routes messages with channel_type='agent' to target
agent's inbound.db, enriches with sender info, wakes target container.
Bidirectional routing works via inherited routing context.

Dynamic agents: create_agent MCP tool + system action handler creates
agent groups, folders, and optional CLAUDE.md on the fly.

Self-modification: install_packages (apt/npm, requires admin approval),
add_mcp_server (no approval), request_rebuild (builds per-agent-group
Docker image with approved packages). Approval flow reuses interactive
card infrastructure with pending_approvals table.

Also includes fixes from prior session: attachment download, reply context
extraction, message editing (platform message ID tracking), delivery retry
limits, and card update on button click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-10 01:10:34 +03:00
parent 9af9bc947a
commit d8fbd3b239
24 changed files with 1025 additions and 78 deletions

View File

@@ -1,6 +1,7 @@
/**
* Agent-to-agent MCP tools: send_to_agent.
* Agent-to-agent MCP tools: send_to_agent, create_agent.
*/
import { findQuestionResponse, markCompleted } from '../db/messages-in.js';
import { writeMessageOut } from '../db/messages-out.js';
import type { McpToolDefinition } from './types.js';
@@ -20,6 +21,10 @@ function err(text: string) {
return { content: [{ type: 'text' as const, text: `Error: ${text}` }], isError: true };
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export const sendToAgent: McpToolDefinition = {
tool: {
name: 'send_to_agent',
@@ -55,4 +60,56 @@ export const sendToAgent: McpToolDefinition = {
},
};
export const agentTools: McpToolDefinition[] = [sendToAgent];
export const createAgent: McpToolDefinition = {
tool: {
name: 'create_agent',
description: 'Create a new agent group dynamically. Returns the new agent group ID.',
inputSchema: {
type: 'object' as const,
properties: {
name: { type: 'string', description: 'Agent display name' },
instructions: { type: 'string', description: 'CLAUDE.md content (agent instructions/personality)' },
folder: { type: 'string', description: 'Folder name (default: auto-generated from name)' },
},
required: ['name'],
},
},
async handler(args) {
const name = args.name as string;
if (!name) return err('name is required');
const requestId = generateId();
writeMessageOut({
id: requestId,
kind: 'system',
content: JSON.stringify({
action: 'create_agent',
requestId,
name,
instructions: (args.instructions as string) || null,
folder: (args.folder as string) || null,
}),
});
log(`create_agent: ${requestId} → "${name}"`);
// Poll for host response
const deadline = Date.now() + 30_000;
while (Date.now() < deadline) {
const response = findQuestionResponse(requestId);
if (response) {
const parsed = JSON.parse(response.content);
markCompleted([response.id]);
if (parsed.status === 'success') {
return ok(`Agent created: ${parsed.result.agentGroupId} (name: ${parsed.result.name}, folder: ${parsed.result.folder})`);
}
return err(parsed.result?.error || 'Failed to create agent');
}
await sleep(1000);
}
return err('Timed out waiting for agent creation response');
},
};
export const agentTools: McpToolDefinition[] = [sendToAgent, createAgent];