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:
@@ -109,13 +109,7 @@ function formatChatMessages(messages: MessageInRow[]): string {
|
||||
|
||||
const lines = ['<messages>'];
|
||||
for (const msg of messages) {
|
||||
const content = parseContent(msg.content);
|
||||
const sender = content.sender || content.author?.fullName || content.author?.userName || 'Unknown';
|
||||
const time = formatTime(msg.timestamp);
|
||||
const text = content.text || '';
|
||||
const idAttr = msg.seq != null ? ` id="${msg.seq}"` : '';
|
||||
const attachmentsSuffix = formatAttachments(content.attachments);
|
||||
lines.push(`<message${idAttr} sender="${escapeXml(sender)}" time="${time}">${escapeXml(text)}${attachmentsSuffix}</message>`);
|
||||
lines.push(formatSingleChat(msg));
|
||||
}
|
||||
lines.push('</messages>');
|
||||
return lines.join('\n');
|
||||
@@ -127,8 +121,9 @@ function formatSingleChat(msg: MessageInRow): string {
|
||||
const time = formatTime(msg.timestamp);
|
||||
const text = content.text || '';
|
||||
const idAttr = msg.seq != null ? ` id="${msg.seq}"` : '';
|
||||
const replyPrefix = formatReplyContext(content.replyTo);
|
||||
const attachmentsSuffix = formatAttachments(content.attachments);
|
||||
return `<message${idAttr} sender="${escapeXml(sender)}" time="${time}">${escapeXml(text)}${attachmentsSuffix}</message>`;
|
||||
return `<message${idAttr} sender="${escapeXml(sender)}" time="${time}">${replyPrefix}${escapeXml(text)}${attachmentsSuffix}</message>`;
|
||||
}
|
||||
|
||||
function formatTaskMessage(msg: MessageInRow): string {
|
||||
@@ -153,13 +148,26 @@ function formatSystemMessage(msg: MessageInRow): string {
|
||||
return `[SYSTEM RESPONSE]\n\nAction: ${content.action || 'unknown'}\nStatus: ${content.status || 'unknown'}\nResult: ${JSON.stringify(content.result || null)}`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function formatReplyContext(replyTo: any): string {
|
||||
if (!replyTo) return '';
|
||||
const sender = replyTo.sender || 'Unknown';
|
||||
const text = replyTo.text || '';
|
||||
const preview = text.length > 100 ? text.slice(0, 100) + '…' : text;
|
||||
return `\n<reply-to sender="${escapeXml(sender)}">${escapeXml(preview)}</reply-to>\n`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function formatAttachments(attachments: any[] | undefined): string {
|
||||
if (!Array.isArray(attachments) || attachments.length === 0) return '';
|
||||
const parts = attachments.map((a) => {
|
||||
const name = a.name || a.filename || 'attachment';
|
||||
const type = a.type || 'file';
|
||||
const localPath = a.localPath ? `/workspace/${a.localPath}` : '';
|
||||
const url = a.url || '';
|
||||
if (localPath) {
|
||||
return `[${type}: ${escapeXml(name)} — saved to ${escapeXml(localPath)}]`;
|
||||
}
|
||||
return url ? `[${type}: ${escapeXml(name)} (${escapeXml(url)})]` : `[${type}: ${escapeXml(name)}]`;
|
||||
});
|
||||
return '\n' + parts.join('\n');
|
||||
|
||||
Reference in New Issue
Block a user