Skip to content

Dashboard: pick a per-task agent CLI in the n new-task flow - #399

Draft
tildesrc wants to merge 3 commits into
mainfrom
panopticon/task-cli-override
Draft

Dashboard: pick a per-task agent CLI in the n new-task flow#399
tildesrc wants to merge 3 commits into
mainfrom
panopticon/task-cli-override

Conversation

@tildesrc

@tildesrc tildesrc commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

The per-task agent_cli override has had a full data path since #384/#385 (model, store, service, REST, client, MCP) but no way to set it interactively — the dashboard's n flow went repo → workflow → memo and always inherited the repo default.

The memo modal now carries an agent CLI dropdown that starts on the repo's own default. Leaving it alone sends agent_cli=None rather than a redundant explicit value, matching resolve_agent_cli's contract, so task creation stays repo → workflow → type → Enter and the CLI is there when you want it.

What's here

  • KNOWN_AGENT_CLIS in core/models.py. The CLI name set is a control-plane concept (it drives the base-image variant + config mount), and terminal/taskservice can't import container/cli at runtime without breaking the determinism invariant — so the list is restated in the LLM-free core and guarded by a test.
  • registered_agent_clis() on the adapter registry. _REGISTRY is populated lazily by _load_builtin_adapters(), so a drift test comparing against it directly would have passed vacuously or depended on import order. The new guard also pins the two long-duplicated DEFAULT_AGENT_CLI constants (core/models.py and container/cli/base.py) together — a pre-existing hole.
  • MemoScreen takes agent_clis + repo_default and dismisses a 4-tuple whose added member is the picked CLI. Both params default to empty, so the screen still stands alone with no dropdown.
  • Focus handling. Tab reaches the dropdown (TextArea's default tab_behavior is "focus"); picking hands focus back to the memo so the next Enter submits rather than reopening the overlay. That hand-back is deliberately unconditional — while the overlay is open, focus sits on the overlay rather than the Select, so a has_focus guard never fires on the path that matters.
  • _selected_cli() falls back to the first known CLI when a repo names one this build doesn't offer, since allow_blank=False needs a value that's genuinely in the options.
  • Repo default read from the payload in hand. action_new_task already fetches the repo dicts and threw everything but the id away; it now keeps the agent_cli too. self._repo_clis was the tempting source but it's only filled on a refresh pass, so it can be empty or stale at this point.
  • Four hardcoded "claude" literals in dashboard.py swapped for DEFAULT_AGENT_CLI.

The fake-adapter registration test now cleans up after itself — it was leaking fake-cli into the module-global registry, which would have made the drift guard order-dependent.

Dropdown coverage: the repo default is shown and sent as no-override, an explicit override, every known CLI is on offer, an unknown one is rejected, an unofferable repo default degrades sanely, and the full tab → pick → focus-returns interaction.

Out of scope: a post-creation set_agent_cli operation, and validating Repo.agent_cli against KNOWN_AGENT_CLIS in the repo form.

Plan: the task's plan.md artifact.

🤖 Generated with Claude Code

claude added 3 commits August 24, 2026 19:24
The per-task `agent_cli` override has had a full data path since #384/#385 (model,
store, service, REST, client, MCP) but no way to set it interactively — the dashboard's
`n` flow went repo -> workflow -> memo and always inherited the repo default.

Insert a CLI step between workflow and memo, pre-selected to the repo's own default so
the common path is still Enter/Enter/Enter-and-type. Picking the default sends
`agent_cli=None` rather than a redundant explicit value, matching `resolve_agent_cli`'s
contract.

- `KNOWN_AGENT_CLIS` in `core/models.py`. The CLI name set is a control-plane concept
  (it drives the base-image variant + config mount), and `terminal`/`taskservice` can't
  import `container/cli` at runtime without breaking the determinism invariant, so the
  list is restated in the LLM-free core and guarded by a test.
- `registered_agent_clis()` on the adapter registry — the registry is populated lazily,
  so a drift test comparing against `_REGISTRY` directly would pass vacuously. The new
  guard also pins the two long-duplicated `DEFAULT_AGENT_CLI` constants together.
- `_OptionListModal` grows an `initial=` param; it had no way to set the starting
  highlight, which the pre-select needs. The repo default is named in the picker title
  rather than baked into an option label, since `ChoiceScreen` dismisses the option text.
- Read the repo default from the `list_repos()` payload `action_new_task` already
  fetches, not `self._repo_clis` — that cache is only filled on a refresh pass and can
  be empty or stale at this point.
- Swap four hardcoded `"claude"` literals in `dashboard.py` for `DEFAULT_AGENT_CLI`.

The fake-adapter registration test now cleans up after itself; it was leaking `fake-cli`
into the module-global registry, which would have made the drift guard order-dependent.

Co-Authored-By: Claude <noreply@anthropic.com>
The separate ChoiceScreen step made task creation four screens deep and cost a
keystroke even when the operator didn't care which CLI ran. Fold it into the memo
modal as a labelled Select that starts on the repo's default, so the common path is
back to repo -> workflow -> type -> enter and the CLI is right there when wanted.

- MemoScreen takes `agent_clis` + `repo_default` and dismisses a 4-tuple, the added
  member being the picked CLI. Both default to empty, so the screen still stands alone
  with no dropdown.
- Tab reaches the dropdown (TextArea's default tab_behavior is "focus"); picking hands
  focus back to the memo, so the next Enter submits rather than reopening the overlay.
  The refocus is deliberately unconditional — while the overlay is open, focus is on the
  overlay rather than the Select, so a has_focus guard never fires on the path that
  matters.
- `_selected_cli` falls back to the first known CLI when a repo names one this build
  doesn't offer, since allow_blank=False needs a value that's really in the options.
- Reverts the `initial=` param added to _OptionListModal last commit; with no CLI
  ChoiceScreen there is nothing left that pre-selects.

The 12 flow tests lose the extra `enter` they grew last commit. New coverage for the
dropdown: repo default shown and sent as no-override, an explicit override, every known
CLI on offer, an unknown one rejected, an unofferable repo default, and the full
tab -> pick -> focus-returns interaction (which caught the focus bug above).

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants