Files
zeekayandHanzo Dev d5ce906725 ci: restore the image build under .hanzo/workflows
Every workflow in this repo was deleted and nothing replaced them, so the sole
producer of ghcr.io/hanzoai/agent no longer existed anywhere and there was no CI
at all. That is silent staleness: no failing run, just an artifact that quietly
stops being rebuilt.

.github/workflows now holds sync.yml and nothing else, copied verbatim from
universe infra/ci/sync.yml. It replaces a mirror-nudge file that POSTed
/api/v1/repos/.../mirror-sync and described git.hanzo.ai as a downstream pull
mirror; the forge is canonical and refs are pushed to it. Without it a
.hanzo/workflows file has nothing to trigger it.

deploy.yml restores docker-publish.yml. Two things could not come across as-is:

- Its body was one `uses: hanzoai/.github/.github/workflows/docker-build.yml@main`.
  A private cross-repo reusable workflow does not resolve on the forge, so the
  amd64 build/push is vendored inline, as hanzoai/bot already does.
- It built `context: control-plane`, and no control-plane directory exists on
  main; the orchestrator was hoisted to the root module in v0.1.1 and the
  directory survives only on the unmerged consolidate/held-work branch.
  Restoring that context restores a build that cannot run. It builds
  Dockerfile.sdk — the one Dockerfile on main whose context exists, previously
  referenced by nothing despite sdk-* tags being live on the image — and tags it
  sdk-* so the two Dockerfiles cannot collide in one tag namespace.

The tag rule is shell rather than docker/metadata-action, so it is two readable
lines with no action semantics to guess at, and it was tested standalone:
v* -> :sdk-X.Y.Z + :sdk-latest, main -> :sdk-sha-<sha7> + :sdk-latest.

ci.yml replaces tests.yml and test.yml, which checked the same things twice, the
second with `|| true` on most steps so it could not fail. It gates what is
verifiable on main today — gofmt/build/vet/test for the Go module, and the
locked install plus `import agents` for Python, which is what Dockerfile.sdk's
builder stage and HEALTHCHECK do. It deliberately does not run ruff, mypy or
pytest: `make sync` cannot resolve (requires-python >=3.9, a transitive numpy
needs >=3.10), ruff reports 2337 errors, and pytest INTERNALERRORs at collection
because a test module sys.exit(1)s at import. tests.yml ran `make sync` first in
all five jobs, so none of them could have gone green. Those four are written down
in LLM.md as work rather than shipped as a permanently red check, which gets read
as broken CI instead of broken code.

gofmt over eight files so that gate is real instead of omitted; build, vet and
test re-run green afterwards. Alignment whitespace only.

Not restored, with reasons in LLM.md: docs.yml (mkdocs gh-deploy is GitHub Pages,
never published anything, and Pages is not how we serve sites), publish.yml
(pointed at the PyPI package hanzoai/python-sdk owns; deliberately removed
earlier), issues.yml (upstream's stale-issue bot).

LLM.md documents the chain, records the four broken Python checks, and states
the open question this repo actually has: nothing in hanzoai/universe references
ghcr.io/hanzoai/agent, so the image has no consumer and no App CR.

.gitignore ignored .hanzo/ under an "# IDE" heading, which silently dropped
both new workflows from the commit — the same silent-staleness shape this is
meant to remove, one level down. Narrowed to .hanzo/* with .hanzo/workflows/
re-included.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 09:01:00 -07:00

123 lines
3.6 KiB
Go

package agent
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDiscoverJSON(t *testing.T) {
body := `{
"discovered_at": "2025-01-01T00:00:00Z",
"total_agents": 1,
"total_reasoners": 1,
"total_skills": 0,
"pagination": {"limit": 50, "offset": 0, "has_more": false},
"capabilities": [{
"agent_id": "agent-1",
"base_url": "http://agent",
"version": "1.0.0",
"health_status": "active",
"deployment_type": "long_running",
"last_heartbeat": "2025-01-01T00:00:00Z",
"reasoners": [{"id": "deep_research", "invocation_target": "agent-1:deep_research"}],
"skills": []
}]
}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
assert.Equal(t, "agent-1", query.Get("agent"))
assert.Equal(t, "true", query.Get("include_input_schema"))
assert.Equal(t, "json", query.Get("format"))
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, body)
}))
defer server.Close()
a, err := New(Config{
NodeID: "node-1",
Version: "1.0.0",
HanzoAgentsURL: server.URL,
})
require.NoError(t, err)
result, err := a.Discover(context.Background(), WithAgent("agent-1"), WithDiscoveryInputSchema(true), WithLimit(50))
require.NoError(t, err)
require.NotNil(t, result)
require.NotNil(t, result.JSON)
assert.Equal(t, "json", result.Format)
assert.Equal(t, 1, result.JSON.TotalAgents)
assert.Equal(t, "agent-1", result.JSON.Capabilities[0].AgentID)
assert.Equal(t, "agent-1:deep_research", result.JSON.Capabilities[0].Reasoners[0].InvocationTarget)
assert.Equal(t, body, result.Raw)
}
func TestDiscoverCompactAndXML(t *testing.T) {
compact := `{"discovered_at":"2025-01-01T00:00:00Z","reasoners":[{"id":"r1","agent_id":"a1","target":"a1:r1"}],"skills":[]}`
xml := "<discovery><summary total_agents=\"0\" /></discovery>"
compactServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "compact", r.URL.Query().Get("format"))
fmt.Fprint(w, compact)
}))
defer compactServer.Close()
xmlServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "xml", r.URL.Query().Get("format"))
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, xml)
}))
defer xmlServer.Close()
aCompact, err := New(Config{
NodeID: "node-1",
Version: "1.0.0",
HanzoAgentsURL: compactServer.URL,
})
require.NoError(t, err)
compactResult, err := aCompact.Discover(context.Background(), WithFormat("compact"))
require.NoError(t, err)
require.NotNil(t, compactResult.Compact)
assert.Equal(t, "compact", compactResult.Format)
assert.Equal(t, "a1", compactResult.Compact.Reasoners[0].AgentID)
aXML, err := New(Config{
NodeID: "node-1",
Version: "1.0.0",
HanzoAgentsURL: xmlServer.URL,
})
require.NoError(t, err)
xmlResult, err := aXML.Discover(context.Background(), WithFormat("xml"), WithTags([]string{"ml"}))
require.NoError(t, err)
assert.Equal(t, "xml", xmlResult.Format)
assert.Equal(t, xml, xmlResult.XML)
assert.Nil(t, xmlResult.JSON)
assert.Nil(t, xmlResult.Compact)
}
func TestDiscoverRejectsInvalidFormat(t *testing.T) {
a, err := New(Config{
NodeID: "node-1",
Version: "1.0.0",
HanzoAgentsURL: "http://localhost:8080",
})
require.NoError(t, err)
_, err = a.Discover(context.Background(), WithFormat("yaml"))
require.Error(t, err)
}
func TestDedupeHelper(t *testing.T) {
values := []string{"a", "b", "a", "", "c"}
assert.Equal(t, []string{"a", "b", "c"}, dedupe(values))
}