feat: provider seam v2 — generalize the agent-CLI contract (#51) #53
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!53
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "lab/20260708-1652"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Evolve the
AgentProviderseam so lab's chat surface works with any agent CLI (Codex, Gemini, …), not only Claude Code — without implementing a second provider. Every claude assumption is lifted into provider-declared metadata or a documented seam obligation; claude-code is brought up to the generalized contract and proven provider-neutral by construction.What changed
Seam (
internal/provider)DialoggainsKind(question|plan|approval) andQuestions[];DialogAnswergains per-questionAnswers[](positional:answers[i]↔questions[i]).Commands(ctx, worktree)slash-command catalog;DisplayName, anAuthFlowdescriptor (oauth-code|oauth-redirect|api-key|external— api-key schema-only), andSeedMetajoin the interface.DialogHooker→ provider-neutralLiveSignals(same contract). Sentinel errors + the auth-changed event move to the provider package; the SSE event is nowprovider.auth.changedcarrying the provider id.claude-code adapter +
internal/compat/clearstuck composer).AskUserQuestionandExitPlanModeplan approval are now answerable; synthesized option rows are write-side pinned constants (never a pane scrape).HTTP/SSE, seeder, frontend
/providers/{id}/auth/*; newGET /runs/{id}/commands(chat-safe, briefly cached); the answer endpoint acceptsanswers[].httpapino longer importsclaudecode.SeedMeta; seeded worktree contents are byte-identical for claude-code (golden-pinned both sides).role=clear"New conversation"; multi-question form and plan approval render with real content; descriptor-drivenProviderAuthCard; provider display metadata drives all copy (grep-guarded — zero hardcoded "Claude"/"claude.ai" in components).Live verification (Claude Code 2.1.198, 2026-07-08)
The recipes were driven end to end through the production
Reply/AnswerDialogpath against the real binary under tmux (TestCompat_Live_*,LAB_COMPAT_LIVE=1) — the ADR-0020 process. This found and fixed four defects snapshot tests could not:Upwraps, it does not clamp — the normalize-to-top climb corrupted answers; on the review screen[Up][Enter]would land on "Cancel" and discard the whole form. Removed the climb (pickers open on their top row; walk downward only; review is a bare[Enter]).Enteron the empty free-text row declines the whole dialog — the Other recipe is now type-first.Enteron a multi-select option toggles, never commits — commit is via a dedicated unnumbered "Submit" row.Enterraced the paste and was dropped — the submitEnteris now paced bykeyDelay.Two non-bug findings shaped the design: recorded multi-select label order is not option-index order (the backstop compares as a set), and the plan picker's row-0 label drifts with session state under the same spawn flag (lab surfaces its own stable semantic labels, coupling only to the index).
Review
Three parallel adversarial reviews (seam/adapter, HTTP/seeder/wiring, frontend) ran over the diff. Two independently found the same real defect — an empty-
SeedMetapre-push hook renderedfor … do; donewith an empty body (a/bin/shsyntax error that would reject every push in a degraded/no-registry boot). Fixed with a:no-op body + ansh -nregression test across all four hook variants. A frontend review found "New conversation" stayed clickable mid-turn (bypassing the ADR-0022 send-block); fixed by gating onstate() !== 'working'+ a regression test. All other areas verified correct.Gates
gofmt/go vetclean;go test ./...green exceptinternal/tmuxx(pre-existing sandbox-environmental — tmux sessions exit immediately here; fails identically on cleanmain). Web: 452 vitest tests, build, lint, prettier all clean. Live recipe tests pass against 2.1.198.Docs
ADR-0026 (amends ADR-0016 and ADR-0020);
compat.md§5/§6/§7 rewritten + new §10 builtin catalog; CONTEXT.md gains the "Slash-command catalog" vocabulary.Out of scope (per the issue)
No Codex/Gemini adapter, no protocol-mode/push adapter, no lab-owned message store, no TUI pane scraping.
Closes #51
🤖 Generated with Claude Code
https://claude.ai/code/session_012x6iHssVua15JWfas1tppV
Evolve the AgentProvider seam so lab's chat surface works with any agent CLI, not only Claude Code — without implementing a second provider. Every claude assumption is lifted into provider-declared metadata or a documented seam obligation; claude-code is brought up to the generalized contract. Seam (internal/provider): - Dialog gains Kind (question|plan|approval) and Questions[] for multi-question forms; DialogAnswer gains per-question Answers[]. - New mandatory Commands(ctx, worktree) slash-command catalog; DisplayName, AuthFlow descriptor (oauth-code|oauth-redirect|api-key|external — api-key schema-only), and SeedMeta join the interface. - DialogHooker renamed to the provider-neutral LiveSignals (same contract). - Sentinel errors + the auth-changed event move to the provider package; the SSE event becomes provider.auth.changed carrying the provider id. - Contract comments pin the clear/epoch obligation, reply-while-working best-effort, and hazard-checked interrupt recipes. claude-code adapter + compat: - Sent slash commands render as plain user text; command echoes stay excluded from state derivation (regression test for the post-/clear stuck composer). - Multi-question AskUserQuestion and ExitPlanMode plan approval are now answerable; synthesized option rows are write-side pinned constants. - A post-resolve verification backstop compares recorded answers against intent and warns on a blind-sequence desync. - Real command catalog: pinned builtin table (bundle-extracted 2.1.198) + project/user/skill scans, curated to chat-safe entries. HTTP/SSE, seeder, frontend: - Auth routes generalize to /providers/{id}/auth/*; new /runs/{id}/commands (chat-safe, briefly cached); the answer endpoint accepts answers[]. httpapi no longer imports claudecode. - One generic seeder consumes provider-declared SeedMeta; seeded worktree contents are byte-identical for claude-code (golden-pinned). - Composer slash autocomplete + role=clear "New conversation"; multi- question form and plan approval render with real content; descriptor- driven ProviderAuthCard; provider display metadata drives all copy (grep-guarded — no hardcoded Claude strings in components). Live verification (2.1.198, 2026-07-08): the recipes were driven end to end through the production Reply/AnswerDialog path against the real binary, finding and fixing four defects snapshot tests could not: the picker's Up wraps (dropped the normalize-to-top climb; review is a bare Enter), Enter on the empty free-text row declines the dialog (type-first), Enter on a multi- select option toggles instead of committing (dedicated Submit row), and a multi-line reply's Enter raced the paste (paced now). Recorded multi-select label order is not option order (backstop compares as a set); the plan picker's row-0 label drifts with session state (lab uses stable semantic labels, coupling only to index). Docs: ADR-0026 (amends 0016/0020), compat.md §5/§6/§7 + §10, CONTEXT.md slash-command catalog vocabulary. Closes #51 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012x6iHssVua15JWfas1tppV