DFGAdapter — TUI Bridge
DFGAdapter is the current glue layer between the Bubble Tea shell and Zeus. It owns command dispatch, event streaming, and the slash command registry. This page explains what it does, how it is wired, and why it is a temporary structure that should be absorbed into Zeus (issue #62).
What DFGAdapter owns
How input is dispatched
DFGAdapter.Submit(input) is the single entry point for all user input.
It serialises concurrent submissions (only one invocation runs at a time) and routes
to one of two paths:
func (a *DFGAdapter) Submit(input string) error {
trimmed := strings.TrimSpace(input)
if trimmed == "" { return ErrEmptyInput }
a.mu.Lock()
if a.status == StatusRunning { return ErrBusy }
ctx, cancel := context.WithCancel(context.Background())
a.status = StatusRunning
a.mu.Unlock()
go func() {
if strings.HasPrefix(trimmed, "/") {
a.dispatchCommand(ctx, trimmed) // fast, synchronous
} else {
a.dispatchPrompt(ctx, trimmed) // streaming, may take seconds
}
}()
return nil
}
Cancel() signals the in-flight context. Subsequent calls to
Submit() return ErrBusy while running — callers must cancel
first or wait for the terminal TokenEvent{Done: true}.
ExecutionEvent — what the TUI receives
All output flows over a typed event channel. The TUI subscribes via
DFGAdapter.Events() and type-switches on arrival:
| Event type | Fields | Meaning |
|---|---|---|
TokenEvent |
Token string, Done bool, Err error |
A fragment of the LLM stream. When Done == true, the stream is over.
Err may be set on cancellation or provider error. |
StatusEvent |
Message string, Phase string |
A human-readable notification (slash command result, lifecycle update).
When Phase == "prompt" the TUI re-submits the message to the LLM. |
Each call to Events() returns a new subscription channel (buffer size 256).
Multiple TUI components can subscribe independently. If a subscriber's buffer fills, events
are dropped for that subscriber rather than blocking the goroutine.
Slash command dispatch
CommandRegistry maps slash command names and aliases to handler functions.
Every handler receives the command arguments and a *StateStore, and returns
an ExecutionEvent.
Commands registered in shell/zeus_commands.go:
| Command | Aliases | What it does |
|---|---|---|
/help | Lists all registered commands | |
/providers | /llms | Shows provider status table |
/stats | Token usage, cost, local/cloud split | |
/context | History size and compression state | |
/auth | Auth status for all providers | |
/route | Active routing configuration | |
/llm-order | Provider waterfall in priority order | |
/fix | Phase="prompt" → streams LLM fix for last error | |
/review | Phase="prompt" → streams code review | |
/security | Phase="prompt" → streams security audit | |
/govern | Phase="prompt" → streams governance check | |
/startup | /init, /triage | Gathers git/gh env, builds preflight prompt, streams triage via LLM |
Commands that return StatusEvent{Phase: "prompt"} cause the TUI to immediately
re-submit the message content as a free-text prompt. This is how slash commands like
/fix chain into the LLM stream without requiring the user to type anything.
The case for absorbing DFGAdapter into Zeus
DFGAdapter exists because the TUI needed an async event model and Zeus was not wired for it. The result is two orchestrators: Zeus owns the LLM calls and history; DFGAdapter owns the command dispatch and event fan-out. Neither is a complete control plane.
| Problem | Consequence |
|---|---|
Zeus's slash command list in buildDynamicContext() is hardcoded |
Adding a command requires editing two files — the registry and the context builder |
Intent classification happens by "/" prefix only |
Zeus can't distinguish "explain this code" (chat) from "refactor this file" (agentic) — both go straight to LLM |
| StateStore is separate from Zeus's history | TUI display state and LLM conversation state are in different structs with no shared lifecycle |
| EventBus is owned by DFGAdapter, not Zeus | The agentic loop (loopdriver) can't emit events to the TUI without routing through DFGAdapter |
Issue #62 documents a 4-phase migration: add Submit/Events/Cancel to Zeus, port the CommandRegistry, port the EventBus, then delete DFGAdapter.