feat(ao): ao spawn CLI + POST /api/v1/sessions route#71
Conversation
Greptile SummaryThis PR introduces
Confidence Score: 5/5Safe to merge. The new route and CLI subcommand are well-isolated and the error classification logic is tested exhaustively, including the previously addressed opaque-internal-error contract. All three layers — daemon wiring, HTTP controller, and CLI — are implemented consistently with the existing patterns in the codebase. The two issues raised in prior review threads (client timeout too short, internal project.Error leaking) were fixed before this review. Error paths in daemon.go correctly cancel the context before returning, so session-stack goroutines are bounded by signal propagation through ctx. The narrow Spawner interface with a compile-time assertion and thorough table-driven tests on both the controller and CLI sides leave little room for hidden breakage. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CLI as ao spawn (CLI)
participant Daemon as Daemon (httpd)
participant SC as SessionsController
participant SM as session.Manager
participant WS as Workspace/Zellij
User->>CLI: ao spawn --project id --prompt task
CLI->>CLI: validate --prompt, --project (exit 2 if empty)
CLI->>CLI: read runfile to get daemon port
CLI->>CLI: context.WithTimeout(90s)
CLI->>Daemon: POST /api/v1/sessions
Daemon->>SC: spawn(w, r)
SC->>SC: nil Mgr check returns 503 sessions_disabled
SC->>SC: validate projectId and prompt returns 400
SC->>SM: Spawn(ctx, SpawnConfig)
SM->>WS: create git worktree
SM->>WS: launch zellij pane
SM->>WS: start agent process
WS-->>SM: Session with ID workspacePath runtimeHandle
SM-->>SC: domain.Session or project.Error
SC->>SC: writeSpawnError maps kind to status
SC-->>Daemon: 201 with sessionId workspacePath runtimeHandle
Daemon-->>CLI: HTTP 201
CLI-->>User: Spawned session id in path, Attach zellij attach handle
Reviews (3): Last reviewed commit: "fix(lint): lift loop condition in scm po..." | Re-trigger Greptile |
Review fixes (PR #71): - spawn CLI now uses a dedicated 90 s timeout (90 s > server's 60 s DefaultRequestTimeout) via context.WithTimeout, and stops sharing deps.HTTPClient — that client is sized for fast /healthz/shutdown probes (2 s) and was preempting the synchronous Spawn long before the daemon could finish provisioning a worktree + zellij pane + agent. - Harden writeSpawnError so a *project.Error with a non-client Kind ("internal", "not_implemented", or anything unknown) falls through to the generic 500 SPAWN_FAILED envelope instead of passing the project error's Code/Message verbatim to the client. Adds three subtests that pin down the opacity contract. Lint debt cleared (inherited from PRs #65/#70): - Add doc comments on every exported symbol in the agent / claudecode / codex / adapters-registry packages (revive: exported) - gosec G306/G301: inbox file/dir perms 0644→0600 and 0755→0750 - gosec G703 (path traversal via taint): excluded globally with the same rationale as G304 — adapter paths are daemon-config/worktree-derived, not user input - gocritic emptyStringTest: len(strings.TrimSpace(...)) > 0 → != "" - gocritic paramTypeCombine: combine adjacent same-type params - errcheck: wrap deferred os.Remove(tmpName) in a closure - prealloc: preallocate cmd slices on the resume paths
Inherited from PR #72 merging into staging after this branch opened. golangci-lint v2.12.2 → 0 issues.
…a-40) into staging
Summary
This is the user-facing CLI for the Session Manager that PR #70 wired into the daemon. After this lands, a user can run
ao spawn --project <id> --prompt "<task>"and a real agent boots in a real git worktree in a real zellij pane.POST /api/v1/sessionsroute inhttpd/controllers/sessions.go. Body{projectId, prompt, agent?}→ 201{sessionId, workspacePath, runtimeHandle}. Maps to 400 (missing/invalid fields), 404 (unknown project — unwrapped from*project.Errorthrough the linear%wchainsession.Manager.Spawnproduces), 503sessions_disabled(SM nil inAPIDeps), 500SPAWN_FAILED(other failures).session.Spawnerinterface so the controller depends on the narrow seam*session.Manageralready satisfies — symmetric with howproject.Manageris consumed by the projects controller. The controller field issession.Spawner; avar _ Spawner = (*Manager)(nil)compile-time assertion locks the satisfaction.ao spawncobra subcommand ininternal/cli/spawn.go(next to the existingstart/stop/statussubcommands —cmd/ao/main.gois the thin shim,internal/cli/is where every subcommand lives). Reads the daemon address from the runfile, POSTs to/api/v1/sessions, printsSpawned session <id> in <workspacePath>\nAttach: zellij attach <runtimeHandle>on 201. Empty prompt or missing project → usage error (exit 2). 4xx/5xx/network → exit 1 with the server'serrorsurfaced via stderr.daemon.Runnow constructs the session stack BEFOREhttpd.NewWithDepsso the SM can be plumbed intoAPIDeps.Sessions. The original code had a_ = ssplaceholder explicitly noting that γ would land routes; this PR removes it.Out of scope (kept out per the brief):
--openauto-attach,--claim-pr, positional<issue>(needs tracker integration), restore/list/get/kill HTTP routes.Prerequisite: #70 (wires SM + agent shim + RepoResolver + inbox messenger into the daemon).
Test plan
go test -race ./...green (only pre-existing failure isterminal.TestSessionStreamsRealZellijPane, a zellij-required integration test that fails identically on staging without these changes)go vet ./...cleangofmt -l backend/cleansuperpowers:code-reviewer— ship-ready, no critical/important findingssession.Spawnerrecording theSpawnConfigit receivedhttptest.NewServercovers 201/4xx/5xx/network-down and exit codes 0/1/2fmt.Errorf("spawn %s: workspace: %w", id, projectresolver-wrap)) exercised so theerrors.Asunwrap to*project.Erroris verified end-to-end🤖 Generated with Claude Code