feat: add kit-kit meta-agent extension example

Port of Pi Pi (meta-agent with parallel expert subprocesses) to Kit's
extension system. Includes expert grid widget, query_experts tool,
custom footer, tool renderer, and orchestrator system prompt injection.

Also updates AGENTS.md with Yaegi gotchas, BubbleTea patterns, testing
recipes, and extension architecture notes.

Fixes golangci-lint issues: modernize min/max in overlay.go, replace
deprecated GetExtRunner() with new GetExtensionContext() SDK method,
remove broken --model flag from expert subprocess.
This commit is contained in:
Ed Zynda
2026-02-28 18:59:18 +03:00
parent 7747fc2033
commit eeecd5a843
9 changed files with 1039 additions and 12 deletions
+47
View File
@@ -39,6 +39,53 @@ Keep this managed block so 'openspec update' can refresh the instructions.
- Multi-provider LLM support via `llm.Provider` interface
- MCP client-server for tool integration
- Builtin servers: bash, fetch, todo, fs
- **Extension system** (`internal/extensions/`): Yaegi-interpreted Go, 13 lifecycle events, custom tools/commands/widgets/overlays/editor interceptors
- **TUI** (`internal/ui/`): Bubble Tea v2 parent-child model (`AppModel``InputComponent`, `StreamComponent`, etc.)
- **Decoupling pattern**: `cmd/root.go` has converter functions (e.g. `widgetProviderForUI()`) that bridge `internal/extensions/` types to `internal/ui/` types — the UI never imports extensions directly
## Key Patterns
### Yaegi (Extension Interpreter) Gotchas
- **No interfaces across boundary**: All extension-facing API types must be concrete structs, never interfaces. Yaegi crashes on interface wrapper generation.
- **Function field bug**: Named function references assigned to struct fields return zero values across the interpreter boundary. Always use anonymous closure literals:
```go
// WRONG: ctx.SetEditor(ext.EditorConfig{HandleKey: myHandler})
// RIGHT: ctx.SetEditor(ext.EditorConfig{HandleKey: func(k, t string) ext.EditorKeyAction { return myHandler(k, t) }})
```
- **Symbol exports**: Every new type exposed to extensions must be added to `internal/extensions/symbols.go`
### BubbleTea Integration
- **No `prog.Send()` from inside `Update()`**: Calling `prog.Send()` synchronously within a BubbleTea `Update()` handler deadlocks the event loop. Use `go appInstance.NotifyWidgetUpdate()` (async goroutine) instead.
- **Height measurement**: `distributeHeight()` in `model.go` must measure using the same render path as `View()`. If an interceptor wraps rendering, measure with the wrapper too, or layout will mismatch.
- **Channel-based prompts**: Extension prompt calls (PromptSelect, etc.) block on a `chan PromptResponse`. Extension slash commands run in dedicated goroutines (not `tea.Cmd`) to avoid stalling BubbleTea's Cmd scheduler.
### Extension State Management
- **Thread-safe maps on Runner**: Widget/header/footer/editor state lives on the Runner with `sync.RWMutex`, queried by UI via callbacks
- **Context function fields**: The `Context` struct uses function fields (`Print func(string)`, `SetWidget func(WidgetConfig)`) wired by closures in `cmd/root.go`
- **Package-level vars in extensions**: Yaegi supports package-level variables captured in closures — this is how extensions maintain state across event callbacks
### Unicode in Widget Text
- Widget content renders through `lipgloss.Style.Render()` which preserves ANSI escape codes
- Use rune-based width calculations (`len([]rune(s))`) not byte length (`len(s)`) when aligning box-drawing characters or multi-byte symbols
## Testing
### Interactive TUI Testing with tmux
Use tmux to test Kit interactively without blocking the agent:
```bash
tmux new-session -d -s kittest -x 120 -y 40 "output/kit -e examples/extensions/my-ext.go --no-session 2>kit_stderr.log"
sleep 3
tmux capture-pane -t kittest -p # read screen
tmux send-keys -t kittest '/command' Enter # send input
tmux kill-session -t kittest # cleanup
```
### Non-Interactive Kit (Subprocess Spawning)
Extensions can spawn Kit as a subprocess for sub-agent patterns:
```bash
kit --prompt "question" --quiet --no-session --no-extensions --system-prompt /path/to/prompt.txt --model provider/model
```
Key flags: `--quiet` (stdout only, no TUI), `--no-session` (ephemeral), `--no-extensions` (prevent recursive loading), `--system-prompt` (string or file path).
## External Repo Research
- **ALWAYS use `btca`** to search external repos (e.g. iteratr, other reference codebases)