IDE-интеграция (ACP)

Creo работает как AI-ассистент внутри IDE через Agent Client Protocol (ACP) — открытый стандарт для интеграции AI-агентов с редакторами кода. ACP — альтернативный фронтенд к ядру агента (наряду с TUI), не содержащий собственной агентской логики.

graph TB
    IDE["IDE-клиент<br/>(редактор)"]
    ACP["ACP-сервер<br/>creo acp (stdio)"]
    AgentImpl["AgentImpl<br/>ACP ↔ agent bridge"]
    Agent["agent.Agent<br/>LLM цикл"]
    Bus[("Bus<br/>in-process pub/sub")]
    Manager["Session Manager"]
    DB[("SQLite")]
    AcpTools["ACP-proxied tools<br/>read, write, terminal"]

    IDE <-->|"stdin/stdout<br/>JSON-RPC"| ACP
    ACP --> AgentImpl
    AgentImpl -->|"ensureAgent → Run"| Agent
    Agent -->|"events"| Bus
    AgentImpl -->|"subscribeSession"| Bus
    Bus -->|"StreamingToken<br/>ToolCall/Status<br/>TurnEnd"| AgentImpl
    AgentImpl -->|"SendText/SendThought<br/>StartToolCall/CompleteToolCall"| IDE
    Agent --> AcpTools
    AcpTools -->|"fs/read_text_file<br/>fs/write_text_file<br/>terminal/create"| IDE
    AgentImpl --> Manager
    Manager --> DB

Команда creo acp запускает ACP-сервер на stdin/stdout:

1// cmd/creo — упрощённо
2agentImpl := NewAgent(deps)
3conn := acp.NewAgentSideConnection(agentImpl, os.Stdin, os.Stdout)
4agentImpl.SetClient(conn.Client())
5conn.Start(ctx)

IDE подключается через stdin/stdout. Диагностика пишется в stderr.

Deps передаётся при создании AgentImpl и живёт всё время жизни ACP-сервера:

 1type Deps struct {
 2    Bus      bus.Bus
 3    Cfg      *config.Config
 4    LLM      llm.Provider
 5    Provider string
 6    ModelID  string
 7    Sessions *session.Manager
 8    Repo     *storage.SessionRepo
 9    CPS      func(cwd string) *checkpoint.Store
10}

Эти зависимости разделяются между TUI и ACP — один и тот же агентский движок.

ACP-сессии создаются клиентом (session/new) и лениво разрешаются в Creo-сессии внутри агента.

Метод ACP Что делает
NewSession Создаёт ACP-сессию сгенерированным ID, запоминает cwd
LoadSession Открывает существующую Creo-сессию, replay истории через SendUserMessage
ListSessions Список сессий, опционально отфильтрованный по cwd
Cancel Отмена текущего хода: AbortRequestEvent в шину + context.Cancel

ensureAgent вызывается при первом Prompt для сессии:

  1. Sessions.Open(sessionID) — попытка открыть существующую сессию
  2. Если не существует — Sessions.CreateWithID(cwd, sessionID)
  3. Создание tool registry через BuildACPRegistry
  4. Генерация system message
  5. Создание agent.Agent с WithAutoApprove() и WithCheckpoints
1func (a *AgentImpl) ensureAgent(sessionID, cwd string) (*agent.Agent, error) {
2    // ... open or create session ...
3    cps := a.deps.CPS(cwd)
4    registry := BuildACPRegistry(cwd, sess, ...)
5    agent.GenerateSystemMessage(sess, registry, cwd)
6    ag := agent.New(...).WithAutoApprove().WithCheckpoints(cps, cwd)
7    a.agents[sessionID] = ag
8    return ag, nil
9}

В ACP-транспорте файловые операции делегируются IDE-клиенту через ACP RPC. Вместо локальных read, write, terminal инструментов регистрируются ACP-proxied аналоги:

Локальный инструмент ACP-аналог ACP RPC
read AcpReadTool fs/read_text_file
write AcpWriteTool fs/write_text_file
terminal AcpTerminalTool terminal/create
1func BuildACPRegistry(...) *tool.Registry {
2    r := agent.BuildBaseRegistry(...)  // calculate, lua, memory, agent, mcp, skills, ...
3    r.Register(acp.NewAcpRead(cwd))    // замена read
4    r.Register(acp.NewAcpWrite(cwd))   // замена write
5    r.Register(acp.NewAcpTerminal(cwd)) // замена terminal
6    return r
7}

Базовые инструменты (не работающие с файлами) остаются неизменными: calculate, lua, memory, agent, agent_manage, agent_abort, ask_user, mcp_register, skill, skillhub, checkpoint_*, search_sessions, sessions, schedule, todo, kv, update_config.

ACP-клиент передаётся через context для proxied tools:

1toolCtx := tool.WithAcpClient(promptCtx, a.client, params.SessionID)

requireAcpClient извлекает клиент из контекста. Если клиент отсутствует — инструмент возвращает ошибку (не ACP-транспорт).

Параметр TUI read ACP read
path
offset ❌ — IDE возвращает полный файл
limit
pages (PDF)

relayEvents подписывается на session-scoped bus events и транслирует их в ACP session/update уведомления через SessionStream:

Bus event ACP stream method
StreamingTokenEvent (kind="text") SendText
StreamingTokenEvent (kind="thinking") SendThought
ToolCallEvent StartToolCall
ToolStatusEvent (done) CompleteToolCall
ToolStatusEvent (error) FailToolCall
AskUserEvent SendText (текст с вариантами)
ErrorEvent сохранение ошибки
TurnEndEvent завершение цикла
 1func (a *AgentImpl) relayEvents(ctx context.Context, done <-chan struct{}, sessionID string, stream *acp.SessionStream) error {
 2    events := agent.SubscribeSession(ctx, a.deps.Bus, sessionID)
 3    for {
 4        select {
 5        case ev := <-events:
 6            switch ev := ev.(type) {
 7            case bus.StreamingTokenEvent:
 8                if ev.Kind == "thinking" {
 9                    stream.SendThought(ctx, ev.TextDelta)
10                } else {
11                    stream.SendText(ctx, ev.TextDelta)
12                }
13            case bus.ToolCallEvent:
14                stream.StartToolCall(ctx, acp.ToolCallID(ev.ToolCallID), ev.Display, acp.ToolKindOther)
15            // ...
16            case bus.TurnEndEvent:
17                break consume
18            }
19        case <-done:
20            break consume
21        case <-ctx.Done():
22            break consume
23        }
24    }
25}

При LoadSession история сессии replay'ится через stream:

  • UserMessageSendUserMessage
  • SkillMessage / CommandMessage (если есть Display) → SendUserMessage
  • AssistantMessageSendText (text content) + SendThought (thinking content)

SendUserMessage используется только при replay, никогда при live-промптах.

Следующие инструменты недоступны через ACP, так как файловые операции делегируются IDE, а остальные требуют локального git:

Инструмент Причина
edit Файловые операции делегируются IDE (только read + write)
glob Локальный поиск по файловой системе
grep Локальный поиск по содержимому
git_* (все) Требуют локальный .git репозиторий
  • Терминал синхронный — без живого стриминга вывода команд.
  • TUI-специфичные фичи недоступны — слэш-команды, мини-режим, темы, сайдбар, интерактивный ask_user-пикер.
  • ask_user работает, но как текстовый вывод с вариантами (не интерактивный пикер).
  • AutoApprove — ACP-агент всегда работает в режиме авто-аппрува (WithAutoApprove()), так как IDE-клиент управляет подтверждениями.
Файл Назначение
internal/acp/run.go Run — запуск ACP-сервера на stdin/stdout
internal/acp/agent.go AgentImpl, Deps, NewAgent, SetClient, ACP capabilities
internal/acp/agent_handlers.go Prompt, Cancel, NewSession, LoadSession, ListSessions, ensureAgent
internal/acp/agent_relay.go relayEvents — трансляция bus events → ACP stream, replaySession
internal/acp/registry.go BuildACPRegistry — tool registry для ACP-транспорта
internal/tool/acp/read.go AcpReadToolread через fs/read_text_file
internal/tool/acp/write.go AcpWriteToolwrite через fs/write_text_file
internal/tool/acp/terminal.go AcpTerminalToolterminal через terminal/create
internal/tool/acp/shared.go requireAcpClient — извлечение ACP-клиента из context