Как расширять Agent Engine
В этой статье приведены практические руководства для трех распространенных сценариев расширения: добавление новых инструментов, добавление новых движков и создание файлов конфигурации Agent.
Руководство 1: добавление новых инструментов
Добавьте новый инструмент в движок TinyElf.
Шаги
1. Реализуйте интерфейс ITool
Создайте новый файл в packages/desktop/app/main/services/agent-core/engine/tinyelf/tools/:
// MyNewTool.ts
import type { ITool } from './ToolInterface';
export class MyNewTool implements ITool {
readonly name = 'MyNewTool';
readonly description = 'Tool functionality description (LLM reads this to decide whether to use)';
readonly parameters = {
type: 'object',
properties: {
input: {
type: 'string',
description: 'Description of input parameter',
},
optional_param: {
type: 'number',
description: 'Optional parameter',
},
},
required: ['input'],
};
constructor(private workspacePath: string) {}
async execute(params: Record<string, unknown>): Promise<string> {
const input = String(params.input);
// Implement tool logic
return `Execution result: ${input}`;
}
}
Ключевые требования:
nameдолжно быть уникальнымdescriptionдолжно помогать LLM понять, когда использовать этот инструментparametersиспользуют стандартный формат JSON Schemaexecuteвозвращает строковый результат (свыше 50 КБ будет автоматически обрезано)- При ошибке верните строку, начинающуюся с
Error, или выбросьте исключение
2. Зарегистрируйте в ToolRegistryBuilder
Отредактируйте TinyElfToolRegistryBuilder.ts:
import { MyNewTool } from './tools/MyNewTool';
export async function buildToolRegistry(...): Promise<ToolRegistryBuildResult> {
// ...existing tool registration code...
// Register new tool
toolRegistry.register(new MyNewTool(projectPath));
// ...
}
3. Определите классификацию чувствительности
Если инструмент является безопасным инструментом только для чтения, добавьте его в набор безопасных инструментов в TinyElfAgentLoop.ts:
private static readonly SAFE_TOOLS = new Set([
'Read', 'ListDir', 'Glob', 'Grep',
'WebSearch', 'WebFetch',
'list_skills', 'read_skill',
'Notify', 'SessionsYield', 'SessionsHistory',
'MyNewTool', // Add to safe set
]);
Если не добавить, по умолчанию инструмент считается чувствительным (требуется подтверждение пользователя).
4. Настройте наследование инструмента (необязательно)
Если дочерние Agents должны наследовать этот инструмент, в TinyElfToolRegistryBuilder.ts:
const inheritableToolNames = new Set([
'list_skills', 'read_skill', 'slash_command',
'MyNewTool', // Add to inheritable set
]);
Контрольный список изменений
| Файл | Действие | Описание |
|---|---|---|
tinyelf/tools/MyNewTool.ts | Создать | Реализация инструмента |
tinyelf/TinyElfToolRegistryBuilder.ts | Изменить | Зарегистрировать инструмент |
tinyelf/TinyElfAgentLoop.ts | Изменить | Классификация чувствительности (необязательно) |
tinyelf/TinyElfToolRegistryBuilder.ts | Изменить | Наследование инструмента (необязательно) |
Руководство 2: добавление нового движка
Добавьте новый тип движка Agent в Elftia.
Шаги
1. Добавьте EngineType
В packages/desktop/app/shared/contracts/elftia-agent-types.ts:
export type EngineType =
| 'tinyelf'
| 'claude-sdk'
| 'cli'
| 'chat'
| 'st-chat'
| 'api'
| 'my-engine'; // New
2. Реализуйте интерфейс IEngine
Создайте новый файл в packages/desktop/app/main/services/agent-core/engine/:
// MyEngine.ts
import type { EngineType } from '@shared/contracts/elftia-agent-types';
import type { EngineSessionContext, IEngine } from './types';
export class MyEngine implements IEngine {
readonly engineType: EngineType = 'my-engine';
private activeSessions = new Map<string, { cancel: () => void }>();
async startSession(ctx: EngineSessionContext): Promise<void> {
const { dbSessionId, sender, prompt } = ctx;
// 1. Save user message
// 2. Send IPC event
sender.send('agent:event', {
type: 'userMessage',
sessionId: dbSessionId,
message: { id: '...', role: 'user', content: prompt },
});
// 3. Execute engine logic (usually async)
const controller = new AbortController();
this.activeSessions.set(dbSessionId, {
cancel: () => controller.abort(),
});
try {
// ...engine core logic...
sender.send('agent:event', {
type: 'complete',
sessionId: dbSessionId,
});
} finally {
this.activeSessions.delete(dbSessionId);
}
}
async resumeSession(ctx: EngineSessionContext): Promise<void> {
// Similar to startSession, but load history
await this.startSession(ctx);
}
async interrupt(dbSessionId: string): Promise<boolean> {
const session = this.activeSessions.get(dbSessionId);
if (session) {
session.cancel();
this.activeSessions.delete(dbSessionId);
return true;
}
return false;
}
isActive(dbSessionId: string): boolean {
return this.activeSessions.has(dbSessionId);
}
}
3. Зарегистрируйте в EngineDispatcher
В packages/desktop/app/main/index.ts:
import { MyEngine } from './services/agent-core/engine/MyEngine';
const dispatcher = new EngineDispatcher();
// ...existing engine registration...
dispatcher.registerEngine(new MyEngine());
4. Экспортируйте в index.ts
В packages/desktop/app/main/services/agent-core/engine/index.ts:
export { MyEngine } from './MyEngine';
5. Добавьте ключи i18n (необязательно)
Если нужно отображать имя движка в UI, добавьте ключи интернационализации:
{
"backendMyEngine": "My Engine",
"backendMyEngineDesc": "My custom engine description"
}
Контрольный список изменений
| Файл | Действие | Описание |
|---|---|---|
@shared/contracts/elftia-agent-types.ts | Изменить | Добавить EngineType |
services/agent-core/engine/MyEngine.ts | Создать | Реализация движка |
services/agent-core/engine/index.ts | Изменить | Экспортировать новый движок |
main/index.ts | Изменить | Зарегистрировать в EngineDispatcher |
| Файлы i18n | Изменить | Добавить отображаемое имя (необязательно) |
Руководство 3: создание конфигурации Agent
Создайте файл конфигурации Agent для использования в движке TinyElf.
Формат конфигурации Agent
---
name: Agent Name
description: Agent Description
model: main
permissionMode: default
tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
skills:
- code-standards
---
System prompt body (Markdown format)
## Instructions
1. First step
2. Second step
Тип AgentConfig
interface AgentConfig {
name: string;
description: string;
tools?: string[]; // Tool whitelist
model?: ModelAlias | string; // Model selection
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
skills?: string[]; // Skill list
systemPrompt: string; // Body content
}
type ModelAlias = 'main' | 'background' | 'inherit' | 'sonnet' | 'opus' | 'haiku';
Разрешение псевдонимов моделей
function resolveModelAlias(
alias: string,
parentModel: string,
backgroundModel?: string,
): string;
| Псевдоним | Разрешение |
|---|---|
main / inherit | Возвращает parentModel |
background | Возвращает backgroundModel (с откатом к parentModel) |
sonnet | Заменяет opus/haiku в parentModel на sonnet |
opus | Заменяет на opus |
haiku | Заменяет на haiku |
| Другое | Использует как конкретный ID модели |
Обнаружение Agent
AgentsLoader обнаруживает конфигурации Agent из:
class AgentsLoader {
constructor(
projectPath: string, // .claude/agents/*.md
personalDir: string, // ~/.claude/agents/*.md
);
listAgents(): AgentInfo[];
loadAgent(name: string): AgentConfig | null;
}
Приоритет обнаружения: проектные > личные > встроенные
Сценарии использования Agent
Как запущенный дочерний Agent
spawn_agent(prompt: "Review this code", agent: "code-reviewer")
SpawnTool загружает конфигурацию Agent через AgentsLoader.loadAgent(), задавая системный prompt, ограничения инструментов и режим разрешений.
Выбор на панели Agent
Frontend выводит список всех доступных Agents через AgentsLoader.listAgents(), пользователь выбирает Agent и передает agentId в движок.
Контрольный список изменений
| Файл | Действие | Описание |
|---|---|---|
.claude/agents/<name>.md | Создать | Файл конфигурации Agent |
Изменения кода не нужны. После размещения файла конфигурации по правильному пути AgentsLoader обнаружит его автоматически.
Общие примечания
Тестирование
Для всех инструментов и движков должны быть соответствующие тесты:
- Тесты инструментов:
tinyelf/tools/__tests__/ - Тесты движков:
agent-core/engine/__tests__/ - Тесты безопасности:
platform/security/__tests__/
Обновление индекса
После добавления новых модулей обновите файловый индекс в .claude/skills/architecture-index/SKILL.md.
Соглашение об IPC-событиях
IPC-события всех движков должны следовать единому формату:
sender.send('agent:event', {
type: string, // event type
sessionId: string, // session ID
// ...event-specific payload
});
Соглашение об именовании инструментов
| Тип инструмента | Формат именования | Пример |
|---|---|---|
| Файловая система | PascalCase | Read, Write, Edit |
| Shell | PascalCase | Bash |
| Функциональный | snake_case | spawn_agent, list_skills |
| MCP | mcp__<server>__<tool> | mcp__github__list_repos |
| Сессия | Префикс PascalCase | SessionsSpawn, SessionsList |
Ключевые файлы
| Файл | Путь | Описание |
|---|---|---|
| Интерфейс ITool | tinyelf/tools/ToolInterface.ts | Базовый интерфейс инструмента |
| Интерфейс IEngine | agent-core/engine/types.ts | Базовый интерфейс движка |
| EngineDispatcher | agent-core/engine/EngineDispatcher.ts | Центр регистрации движков |
| ToolRegistryBuilder | tinyelf/TinyElfToolRegistryBuilder.ts | Сборка регистрации инструментов |
| AgentsLoader | tinyelf/agents/AgentsLoader.ts | Загрузчик конфигураций Agent |
| SkillsLoader | tinyelf/skills/SkillsLoader.ts | Загрузчик конфигураций Skill |
| Определение EngineType | @shared/contracts/elftia-agent-types.ts | Определение типа |
| Основная точка входа | main/index.ts | Регистрация движков |
Все пути указаны относительно packages/desktop/app/main/services/.
Связанные модули
| Модуль | Описание | Ссылка |
|---|---|---|
| TinyElf Agent Loop | Цикл выполнения инструментов | Глубокий разбор TinyElf |
| Tool System | Регистрация и выполнение инструментов | Система инструментов |
| Security Layers | Трехуровневый конвейер безопасности | Уровни безопасности |
| ClaudeSdkEngine | Движок SDK | ClaudeSdkEngine |
| CliRunnerEngine | Движок CLI | CliRunnerEngine |