loops: repo picker + agent/team/org staffing + sidebar edit/delete
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 12s
ci / frontend (push) Successful in 25s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped

Adds the missing pieces the wizard needed and the sidebar controls
around it:

- LoopsWizard is now a 6-step flow (identity → repo → task/topology
  → triggers → repeat → assign agents) plus the existing secrets
  card. ResearchWizard picks up the same repo step and a hard gate
  when the workspace has zero agents.
- New LoopStaffingStep with three tabs — Individual / Team /
  Organization — that mix freely per loop; selections persist via
  new loop_agents / loop_teams / loop_orgs join tables (0035
  migration), each cascading on loop_id so hard-delete stays a
  single-row DELETE.
- Backend CreateLoopRequest / UpdateLoopRequest accept the three
  lists and apply_staffing does a transactional replace-all;
  list_loops / get_loop hydrate the lists via a flattened
  LoopWithStaffing response.
- LoopsList sidebar gains per-row enable/disable, edit (reopens the
  wizard prefilled with the current loop, PATCHes on submit), and
  delete with an inline confirm.
- NoAgentsGate blocks launching a loop or research topic from a
  workspace with no roster; the sidebar `+` buttons also disable
  with a tooltip pointing at the TEAM tier.

Not yet wired: the run driver still fills role slots from the
workspace-wide pool; teaching enqueue_iteration to prefer
loop_agents/loop_teams/loop_orgs is a follow-up.
This commit is contained in:
Omar Sobh
2026-07-07 22:09:06 -07:00
parent 2562541f5b
commit a6da19430f
25 changed files with 1781 additions and 156 deletions
@@ -99,17 +99,25 @@ export function ResearchList({
<button
type="button"
onClick={() => setWizardOpen(true)}
title="New research topic"
disabled={agents.length === 0}
title={
agents.length === 0
? "Add an agent to your roster first"
: "New research topic"
}
aria-label="New research topic"
style={{
flex: "none",
width: 34,
height: 34,
borderRadius: 9,
border: "1px dashed rgba(255,111,97,.4)",
background: "rgba(255,111,97,.06)",
color: "#ff6f61",
cursor: "pointer",
border: `1px dashed ${agents.length === 0 ? "rgba(255,255,255,.14)" : "rgba(255,111,97,.4)"}`,
background:
agents.length === 0
? "rgba(255,255,255,.02)"
: "rgba(255,111,97,.06)",
color: agents.length === 0 ? "#5a5a62" : "#ff6f61",
cursor: agents.length === 0 ? "not-allowed" : "pointer",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",