diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 19e88f62..0e77d39b 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -42,4 +42,4 @@ runs: - name: Create workspace shell: bash run: | - printf 'go 1.26.5\n\nuse .\n\nreplace (\n\tgithub.com/GrayCodeAI/hawk-core-contracts => ./external/hawk-core-contracts\n\tgithub.com/GrayCodeAI/eyrie => ./external/eyrie\n\tgithub.com/GrayCodeAI/inspect => ./external/inspect\n\tgithub.com/GrayCodeAI/sight => ./external/sight\n\tgithub.com/GrayCodeAI/tok => ./external/tok\n\tgithub.com/GrayCodeAI/trace => ./external/trace\n\tgithub.com/GrayCodeAI/yaad => ./external/yaad\n)\n' > go.work + printf 'go 1.26.5\n\nuse .\n\nreplace (\n\tgithub.com/GrayCodeAI/hawk-core-contracts => ./external/hawk-core-contracts\n\tgithub.com/GrayCodeAI/eyrie => ./external/eyrie\n\tgithub.com/GrayCodeAI/inspect => ./external/inspect\n\tgithub.com/GrayCodeAI/sight => ./external/sight\n\tgithub.com/GrayCodeAI/tok => ./external/tok\n\tgithub.com/GrayCodeAI/trace => ./external/trace\n\tgithub.com/GrayCodeAI/yaad => ./external/yaad\n\tgithub.com/GrayCodeAI/hawk-mcpkit => ./external/hawk-mcpkit\n)\n' > go.work diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 104ede8b..bf6b2f99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,13 +106,22 @@ jobs: run: go mod verify - name: workspace points at external checkouts run: | - for module in hawk-core-contracts eyrie inspect sight tok trace yaad; do + for module in hawk-core-contracts eyrie inspect sight tok trace yaad hawk-mcpkit; do if ! grep -q "./external/${module}" go.work; then echo "::error::go.work must include ./external/${module}." cat go.work exit 1 fi done + - name: Validate every Go module independently + run: | + while IFS= read -r modfile; do + dir=$(dirname "$modfile") + echo "==> validating $dir" + (cd "$dir" && GOWORK=off go mod tidy -diff) + (cd "$dir" && GOWORK=off go mod verify) + (cd "$dir" && GOWORK=off go test ./... -count=1 -timeout=300s) + done < <(find . -name go.mod -not -path './.git/*' -not -path './external/*' -print | sort) public-modules: name: public module graph diff --git a/Makefile b/Makefile index d3d9b133..ae2328df 100644 --- a/Makefile +++ b/Makefile @@ -177,7 +177,7 @@ clean: ## Remove build artefacts. # Setup — bootstrap local development environment. # --------------------------------------------------------------------------- FOUNDATION_REPOS := hawk-core-contracts -ECO_REPOS := eyrie inspect sight tok trace yaad +ECO_REPOS := eyrie inspect sight tok trace yaad hawk-mcpkit WORKSPACE_REPOS := $(FOUNDATION_REPOS) $(ECO_REPOS) setup: ## Set up local development environment (go.work + external repos). @@ -185,7 +185,7 @@ setup: ## Set up local development environment (go.work + external repos). @mkdir -p external @for repo in $(WORKSPACE_REPOS); do \ dest="external/$$repo"; \ - if [ -d "$$dest/.git" ]; then \ + if git -C "$$dest" rev-parse --git-dir >/dev/null 2>&1; then \ echo "✓ $$dest already exists"; \ else \ commit=$$(git ls-tree HEAD "$$dest" 2>/dev/null | awk '{print $$3}' || true); \ diff --git a/README.md b/README.md index 2ee9ea31..f959d418 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ hawk is an AI-powered coding agent that lives in your terminal. It reads your co **Developer path:** one machine, keychain credentials, local memory. Run `hawk path` to check readiness. -- **Model-agnostic** — supports 23 first-class providers through [eyrie](https://github.com/GrayCodeAI/eyrie), including Anthropic, OpenAI, Gemini, Concentrate AI (pay-as-you-go), DeepSeek, and Ollama +- **Model-agnostic** — supports 28 first-class providers through [eyrie](https://github.com/GrayCodeAI/eyrie), including Anthropic, OpenAI, Gemini, Fireworks AI, Concentrate AI (pay-as-you-go), DeepSeek, and Ollama - **Zero CGO** — single static binary, cross-compiled for linux/darwin/windows on amd64/arm64 - **Privacy-first** — your code never leaves your machine except to the LLM API you choose - **Docker-only execution** — agent commands run in an isolated container and @@ -345,6 +345,7 @@ hawk works with any LLM provider. **Developer path:** paste keys in `/config` (s | OpenAI | `openai` | `OPENAI_API_KEY` | | Google Gemini | `gemini` | `GEMINI_API_KEY` | | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | +| Fireworks AI | `fireworks` | `FIREWORKS_API_KEY` | | xAI (Grok) | `grok` | `XAI_API_KEY` | | Z.AI | `z-ai` | `ZAI_API_KEY` | | CanopyWave | `canopywave` | `CANOPYWAVE_API_KEY` | diff --git a/docs/ECOSYSTEM-CONFIG.md b/docs/ECOSYSTEM-CONFIG.md index 72419383..b66c1efe 100644 --- a/docs/ECOSYSTEM-CONFIG.md +++ b/docs/ECOSYSTEM-CONFIG.md @@ -135,8 +135,8 @@ setting; the unified key is rendered down to it. |---------------------------------|-------------|--------------------------------------------------| | `model.default` | hawk | `HAWK_MODEL` env / `config.json` | | `model.small_fast` | hawk | `GRAYCODE_SMALL_FAST_MODEL` env | -| `providers[].api_key_env` | eyrie/hawk | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`, `ZAI_API_KEY`, `CANOPYWAVE_API_KEY` | -| `providers[].base_url_env` | eyrie | `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL` / `OPENAI_API_BASE`, `OLLAMA_BASE_URL` | +| `providers[].api_key_env` | eyrie/hawk | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `OPENROUTER_API_KEY`, `XAI_API_KEY`, `ZAI_API_KEY`, `CANOPYWAVE_API_KEY`, `FIREWORKS_API_KEY` | +| `providers[].base_url_env` | eyrie | `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL` / `OPENAI_API_BASE`, `OLLAMA_BASE_URL`, `FIREWORKS_BASE_URL` | | `providers[].model` (openai) | eyrie | `OPENAI_MODEL` env | | `providers[].model` (gemini) | eyrie | `GEMINI_MODEL` env | | `providers[].model` (anthropic) | eyrie | `ANTHROPIC_MODEL` env | diff --git a/docs/user-guide/02-authentication.md b/docs/user-guide/02-authentication.md index 2ef5e50d..2c6139ef 100644 --- a/docs/user-guide/02-authentication.md +++ b/docs/user-guide/02-authentication.md @@ -17,6 +17,7 @@ Hawk supports multiple providers: | OpenAI | `openai` | `OPENAI_API_KEY` | | Google Gemini | `gemini` | `GEMINI_API_KEY` | | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | +| Fireworks AI | `fireworks` | `FIREWORKS_API_KEY` | | Ollama (local) | `ollama` | `OLLAMA_BASE_URL` (no API key) | ### Setting Up Credentials @@ -38,6 +39,12 @@ export XAI_API_KEY="xai-..." hawk ``` +Fireworks uses its OpenAI-compatible API. Set `FIREWORKS_API_KEY`; the default +base URL is `https://api.fireworks.ai/inference/v1`. See the official +[quickstart](https://docs.fireworks.ai/getting-started/quickstart), +[API reference](https://docs.fireworks.ai/api-reference/introduction), and +[models overview](https://docs.fireworks.ai/models/overview). + --- ## Provider Configuration @@ -228,4 +235,4 @@ Any provider can be set as default in your settings: --- -© 2026 GrayCode AI. All rights reserved. \ No newline at end of file +© 2026 GrayCode AI. All rights reserved. diff --git a/external/eyrie b/external/eyrie index 68fb1224..afeda44e 160000 --- a/external/eyrie +++ b/external/eyrie @@ -1 +1 @@ -Subproject commit 68fb122446739ee5056725262558478bde24b894 +Subproject commit afeda44e0c6e9554aef0bb10dc131a925b605389 diff --git a/go.mod b/go.mod index 7d5ef635..ce460228 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( charm.land/bubbles/v2 v2.1.1 charm.land/bubbletea/v2 v2.0.8 charm.land/lipgloss/v2 v2.0.5 - github.com/GrayCodeAI/eyrie v0.2.3-0.20260811030832-68fb12244673 + github.com/GrayCodeAI/eyrie v0.2.3-0.20260813030854-afeda44e0c6e github.com/GrayCodeAI/hawk-core-contracts v0.1.12 github.com/GrayCodeAI/inspect v0.0.0-20260726091806-08f3151d5738 github.com/GrayCodeAI/sight v0.0.0-20260726091804-84c96edfc589 diff --git a/go.sum b/go.sum index b7855e6b..f217206a 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8 github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE= github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/GrayCodeAI/eyrie v0.2.3-0.20260811030832-68fb12244673 h1:Ox90gGE+fNzsH0S9AxCa7w6W/GqyuSnGAYIfJZ8vOE4= -github.com/GrayCodeAI/eyrie v0.2.3-0.20260811030832-68fb12244673/go.mod h1:AW/UPuj+EWxMibiD+/Cy0TWd6RmTvth+KeGXSxU3t6I= +github.com/GrayCodeAI/eyrie v0.2.3-0.20260813030854-afeda44e0c6e h1:62MKYG0zyi+Wud7a7T3wgQgc+XMvLq4XCbcACltVxms= +github.com/GrayCodeAI/eyrie v0.2.3-0.20260813030854-afeda44e0c6e/go.mod h1:AW/UPuj+EWxMibiD+/Cy0TWd6RmTvth+KeGXSxU3t6I= github.com/GrayCodeAI/hawk-core-contracts v0.1.12 h1:percfsd771JLmO9gMkrQtENEPBA9ZN3dG1Nc1moN3ZQ= github.com/GrayCodeAI/hawk-core-contracts v0.1.12/go.mod h1:BXbh68YrCf+s9HVqND5F8DAvl2MnE5NcOwZZZB56HGA= github.com/GrayCodeAI/hawk-mcpkit v0.1.6-0.20260729083555-85ac53f3ec84 h1:HzoXUYNNyt88IccaPBxSvOQ/5PZzJOcSHbvBjX3l2mQ= diff --git a/internal/multiagent/watchdog_test.go b/internal/multiagent/watchdog_test.go index 2f265cf9..48690430 100644 --- a/internal/multiagent/watchdog_test.go +++ b/internal/multiagent/watchdog_test.go @@ -8,12 +8,12 @@ import ( ) func TestWatchdog_NoStall(t *testing.T) { - stallCalled := false + stallCalled := make(chan struct{}, 1) w := NewWatchdog(WatchdogConfig{ StallTimeout: 100 * time.Millisecond, CheckInterval: 20 * time.Millisecond, }, func(featureID string) { - stallCalled = true + stallCalled <- struct{}{} }) w.Register("feat-1") @@ -25,8 +25,10 @@ func TestWatchdog_NoStall(t *testing.T) { w.Touch("feat-1") <-ctx.Done() - if stallCalled { + select { + case <-stallCalled: t.Fatal("expected no stall callback when feature is touched within timeout") + default: } } @@ -62,12 +64,12 @@ func TestWatchdog_StallDetected(t *testing.T) { } func TestWatchdog_Unregister_StopsMonitoring(t *testing.T) { - stallCalled := false + stallCalled := make(chan struct{}, 1) w := NewWatchdog(WatchdogConfig{ StallTimeout: 50 * time.Millisecond, CheckInterval: 20 * time.Millisecond, }, func(featureID string) { - stallCalled = true + stallCalled <- struct{}{} }) w.Register("feat-unreg") @@ -79,8 +81,10 @@ func TestWatchdog_Unregister_StopsMonitoring(t *testing.T) { go w.Run(ctx) <-ctx.Done() - if stallCalled { + select { + case <-stallCalled: t.Fatal("expected no stall callback after Unregister") + default: } } diff --git a/testdata/golden/help_root.txt b/testdata/golden/help_root.txt index 577109df..55a82abb 100644 --- a/testdata/golden/help_root.txt +++ b/testdata/golden/help_root.txt @@ -1,6 +1,6 @@ hawk is an AI coding agent that reads, writes, and runs code in your terminal. -It connects to 27 first-class LLM providers through eyrie, executes tools (file I/O, shell, +It connects to 28 first-class LLM providers through eyrie, executes tools (file I/O, shell, git, web search), and manages sessions — all from a keyboard-driven TUI or headless mode for scripts and CI.