Commit Graph
280 Commits
Author SHA1 Message Date
Omar Sobh 1a2baee74b loops wizard: stop pulling bearer.ts into the client bundle
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 36s
ci / rust (push) Successful in 2m43s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 2m31s
Same trap the pre-existing topology import comment warned about:
importing from @/lib/api/team or @/lib/api/structure drags http.ts
→ bearer.ts (uses next/headers, server-only) into the client
bundle and Turbopack refuses to build.

Replace fetchClaws / fetchTeams / fetchOrgs with plain fetch() to
/api/team/claws, /api/teams, /api/orgs. Types are inlined where
they were only used for the shape of the JSON response.
2026-07-08 09:37:55 -07:00
Omar Sobh 16fcf8ef96 agents sidebar: fold org/company/team/agent tree into one pane
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 10s
ci / frontend (push) Successful in 26s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
The Agents tier used to render a flat list of agent cards with an
"Add to teams" button pinned to the bottom. Swap that for the same
nested tree the Visualizations tier already uses so the whole
workforce reads from one collapsible view.

- treeRoots is now worldRoots on both tiers (no more clawNode flat
  fallback); the Agents tier's selectLevel stays "claw" so the
  Wrench → select → Delete flow keeps its agent-only scope.
- Drop the "Add to teams" footer button plus its addTeamOpen state,
  AddToTeamModal render, and import.
- Header on the Agents tier now shows the full breakdown:
  N ORGS · N CO · N TEAMS · N AGENTS.
2026-07-08 05:30:34 -07:00
Omar Sobh a6da19430f 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.
2026-07-07 22:09:06 -07:00
Omar Sobh 2562541f5b repos sidebar: fold repos under their org
ci / frontend (push) Successful in 27s
ci / rust (push) Successful in 3m53s
ci / e2e (push) Has been skipped
ci / gates (push) Successful in 7s
ci / publish (push) Successful in 2m40s
Within a provider connection, repos now group under a foldable org card
(the repo's owner login). Each org shows chevron + owner + count, with
repos indented under a subtle left rail so the tree reads visually.

Sorting: orgs alphabetical, repos within an org alphabetical — makes
scanning stable when a re-sync reorders provider output.

Collapsed state lives per (connection_id, owner) so the same org name
appearing under two providers folds independently. Default is expanded
so the first pass after connecting shows everything.
2026-07-07 18:37:48 -07:00
Omar Sobh 637e1bdd69 repos: sidebar actions (sync/edit/remove) + edit modal
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 35s
ci / publish (push) Successful in 4m7s
ci / rust (push) Successful in 2m43s
ci / e2e (push) Has been skipped
Sidebar:
- Each connection header now has three inline icon buttons: Sync now
  (spins while in flight), Edit (opens the modal), Remove (opens an
  inline confirm strip). Removes cascade repos via ON DELETE CASCADE.
- The connection's last_sync_error surfaces as a red inline banner
  under the header — no more 'error status with nowhere to see why'.
- Sync is POST /api/repos/connections/:id/sync (already existed);
  after either sync or delete the sidebar re-fetches so state stays
  consistent.

Edit modal (RepoConnectionEditModal):
- Loads GET /api/repos/connections/:id, pre-fills owner/base_url/label
- PATCHes only the fields that actually changed; empty string on a
  Some(&str) field sends explicit null so the backend clears it
- Sync-now + Remove reachable from inside the modal too
- Rotating the token is out of scope: the modal says as much and
  points the user at delete + re-create through the wizard (the
  broker doesn't expose an update path, and rotating in place would
  require duplicating the whole broker->store_secret flow here)

Backend:
- GET /api/repos/connections/:id — same ConnectionSummary shape
- PATCH /api/repos/connections/:id — owner/base_url use Option<Option<T>>
  double-nesting so 'omit = leave alone' and 'null = clear' round-trip
  distinctly through serde
- repo_connections::update with COALESCE-per-field so the SQL matches
  the double-Option semantics without an OR-chain per field
2026-07-07 17:55:20 -07:00
Omar Sobh e858a7f92f repos: Gitea provider (first-class) — sync + wizard default
ci / e2e (push) Has been skipped
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 42s
ci / rust (push) Successful in 2m47s
ci / publish (push) Successful in 2m30s
Fleet's Gitea (git.redclaw.dev) hosts most of this workspace's repos,
so Gitea gets the same inline sync treatment GitHub already had.

Backend sync_gitea:
- base_url is required — Gitea has no shared 'gitea.com'; we accept
  either the instance root (auto-appends /api/v1) or the fully-formed
  API base if the user already included the suffix
- /orgs/{owner}/repos when owner set, /repos/search when not (with the
  {data: [...], ok: bool} envelope Gitea wraps that endpoint in)
- 404 with an owner surfaces as 'org not found or PAT lacks access',
  same UX as GitHub
- 50/page, capped at 20 pages (~1000 repos); short page terminates
- upsert_gitea_repo tolerates the small field-name differences
  (stars_count vs stargazers_count, owner.login vs owner.username on
  older versions)

Frontend wizard:
- Gitea listed first — matches the workspace's actual usage
- Default provider selection is now gitea
- Token-input placeholder tailored per provider (Gitea's is
  'Settings → Applications → Generate New Token (repo)')

GitLab still returns 'not yet supported' — that's the next follow-up.
2026-07-07 15:11:30 -07:00
Omar Sobh 9cb14ddd89 repos: real provider-connection wizard
Replaces the earlier placeholder inside RepoConnectionWizardStub with the
actual flow (kept the filename so the Dashboard import doesn't churn).

- Provider picker (github / gitea / gitlab) as three inline cards
- PAT input (password field, never rendered back)
- Optional owner override (org or user)
- Optional label (defaults to <provider>/<owner>)
- Optional base URL — shown only for Gitea / GitLab, hidden for GitHub
- POST /api/repos/connections + immediate result card:
  green when the initial sync succeeded (shows # repos synced), red
  when the connection persisted but the sync failed (shows the message
  the backend recorded on repo_connections.last_sync_error). The
  sidebar refresh fires on both paths so the new row appears either way.

Sidebar and detail view already fetch the right endpoints from task
#11 — end-to-end works locally on this build.
2026-07-07 14:54:39 -07:00
Omar Sobh 076f7724ca dashboard: REPOS tier tab + Repos page shell
Inserts a 6th tier tab between AGENT and INFRA (Tier type + TIER_TABS +
rail icon). Wires two new sidebar/canvas components with the same
list+detail pattern as loops/research:

- RepoList: header (provider count · repo count), + button opens the
  connection wizard, groups repos by provider connection (empty state
  prompts the user to connect the first). Fetches /api/repos/connections
  and /api/repos — those routes land in tasks #12-14.
- RepoCanvas: repo detail (name, owner, private badge, description,
  stars/forks/branch/updated, clone URL with copy, open-on-provider
  link, last-synced footer). Empty + loading + error placeholders.
- RepoConnectionWizardStub: minimal 'coming next' modal so the + button
  is wired end-to-end; real wizard replaces it in task #15.

Sidebar header branch updated so the tier renders its own header. Build
is clean; the sidebar is fully functional once the backend endpoints
respond.
2026-07-07 14:44:50 -07:00
Omar Sobh 806ba869e5 teams: ephemeral lifecycle for Scheduled + Triggered planner modes
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 38s
ci / rust (push) Successful in 3m6s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 2m23s
Migration 0033: adds teams.lifecycle ('permanent' | 'ephemeral') and a
topology_runs.team_id back-ref with a partial index for the sibling-in-
flight check.

cm-db repo:
- teams::insert_team_with_lifecycle (insert_team keeps the permanent default)
- topology_runs::enqueue_run_for_team (populates team_id)
- topology_runs::check_ephemeral_teardown — atomic SELECT that only
  returns Some when the team is ephemeral AND no siblings are still
  queued/running; carries the workspace + bound claw ids for cleanup.

cm-api:
- topology_worker post-terminal hook maybe_teardown_ephemeral_team
  runs deprovision_claw on each bound claw (best-effort; failures log
  but don't block Postgres deletion), then hard_purge each agent row,
  then delete_team.
- routes::teams::build_team_with_lifecycle (build_team keeps default);
  run_team enqueues with team_id.
- planner ScaffoldRequest gains mode; lifecycle_for(mode) sets the team
  to ephemeral for scheduled + triggered, permanent otherwise.

Frontend MasterPlannerModal passes mode in the scaffold payload so the
backend can derive lifecycle without duplicating the mode taxonomy.

Tests: 3 new (returns claws when no siblings, holds when siblings queued,
ignores permanent teams). 10/10 topology_jobs green; workspace clippy
--tests clean.
2026-07-07 04:30:07 -07:00
Omar Sobh b0acdfd987 master planner: wire user-locked topology_kind through chat + scaffold
Frontend: send topologyKind to /api/planner/chat so the planner's user
prompt gets a USER-LOCKED TOPOLOGY block telling Opus to use it verbatim.
On buildTeam, override proposal.topology_kind with the user's pick
(belt-and-braces — if the planner ignored the lock, we still ship the
right shape). Proposal chip renders the effective kind in a lavender
tint when it was overridden, with a hover title showing what was
replaced.

Backend PlannerChatRequest gains an optional topology_kind. Empty /
absent = planner picks. Not honored for 'swarm' mode (swarm planner
doesn't take a topology kind).
2026-07-07 04:23:00 -07:00
Omar Sobh 5e49086af7 master planner: topology info panel under the gallery
Selecting a card in the strip expands into an info panel below with:
- Coordinator badge (green when the topology has a lead, gray when
  leaderless — mirrors what cm-orchestrator's planner reads off the graph)
- Communication pattern one-liner
- 'When to use' guidance
- Auto-staffed role distribution chips (from the catalog's
  role_distribution — same numbers TeamWizard apportions)
- ASCII sketch of the shape

Cheat sheet is client-side (TOPO_DETAIL) so the expansion is instant on
selection — mirrors the exec-plan semantics baked into cm-orchestrator.
2026-07-07 04:21:32 -07:00
Omar Sobh ab4a62ef7d master planner: topology gallery strip for Team + Swarm modes
New horizontally-scrollable card strip appears between the mode selector
and the split pane when the mode is 'team' or 'swarm'. Cards are fetched
from GET /api/topologies (the full 12-kind catalog), rendered as pill
cards with name + description. Clicking selects; 'planner picks' clears.
Selection persists per-mode via the ModeSlice snapshot, so switching
modes doesn't forget the choice. Scaffold wire-through comes next
(task #9).
2026-07-07 04:20:12 -07:00
Omar Sobh 987f4f0e84 master planner: add 'Team' mode + size bands per mode
Modes now: specialists (2–3 domain experts, deep prompts) · team (4–8
balanced roles, coordinator + complements) · swarm (10+ workers, self-
verifying loop) · scheduled (ephemeral, cron/one-shot) · triggered
(ephemeral, webhook). Backend planner_system_for() gains a TEAM_NOTE
using PLANNER_SYSTEM; specialists / scheduled / triggered notes are
rewritten to bake in the size + ephemeral guidance. Swarm's system
prompt now targets task_count>=10 explicitly.

Frontend MODES / INTRO copy match. Chat-preserving switchMode from the
prior commit handles the new mode transparently — no state-plumbing
changes needed.
2026-07-07 04:18:29 -07:00
Omar Sobh 9056937434 master planner: preserve state across mode switches
switchMode was resetting messages/proposal/swarm/runSteps/etc. every
time. Now it snapshots the current mode's slice into a ref before
loading the target mode's snapshot (or a fresh slice if the target
was never visited). Switching specialists → swarm → specialists keeps
the specialists chat intact.

Also route async /planner/chat responses to the mode the message was
sent in, not the currently-active mode — user can switch modes while
the reply is in flight without the assistant response landing in the
wrong chat.
2026-07-07 04:16:36 -07:00
Omar Sobh 18a99a970d loops wizard: fix client-bundle break from topology import
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 4m3s
ci / rust (push) Successful in 7m3s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 4m46s
@/lib/api/topology's apiFetch pulls in bearer.ts which imports
@clerk/nextjs/server — the whole chain gets tagged as client-side
by Next when LoopsWizard imports it, and 'server-only' breaks the
production build. Switch to plain fetch(/api/topologies) with
type-only imports (mirrors what TeamWizard does). Local pnpm build
now compiles clean; typecheck/lint already pass. This unblocks the
publish job on the topology-builder push.
2026-07-06 13:03:24 -07:00
Omar Sobh e5820ce927 loops wizard: topology builder in step 2
ci / gates (push) Successful in 13s
ci / frontend (push) Successful in 4m21s
ci / rust (push) Successful in 8m10s
ci / e2e (push) Has been skipped
ci / publish (push) Failing after 6m22s
Step 2 now defaults to a Builder pane: kind picker (from /api/topologies)
+ team size + role distribution preview, with /api/topologies/build
rendering the canonical graph and node/edge counts. Advanced JSON stays
as a toggle for hand-crafted graphs — same shape lands in the payload
either way, so downstream code is unchanged.

Debounced build effect wraps the async load in an inner function to
avoid the setState-in-effect cascading-renders lint. Uses the same
largest-remainder role apportionment as TeamWizard so builder output
matches team-wizard output for the same kind + size.
2026-07-06 12:36:18 -07:00
Omar Sobh 9079184bb2 loops wizard: expose 'until' repeat policy
Third radio option on the repeat step. Two inputs: event name (defaults
to 'ok') and a within_iters cap (defaults to 20). Ships the full
{kind: 'until', event, within_iters} payload the schema already accepts.
LoopsCanvas repeat summary now formats iters/until/infinite via one
helper — the previous inline expression was rendering 'until' as a bare
label with no event context.
2026-07-06 12:33:42 -07:00
Omar Sobh 6d1dda6197 loops: iteration timeline — GET /api/topology-runs?loop_id=X
Extend the topology-runs list route with an optional loop_id filter that
returns iterations for a single loop, newest-iteration-first. Adds the
iteration and finished_at columns to the summary (skip-null on the JSON
so compares stay compact). Backed by list_by_loop in the repo, which uses
the existing topology_runs_loop_idx partial index.

LoopsCanvas fetches the runs in parallel with the loop detail and renders
an iteration timeline card (iteration #, status pill, start time, duration,
run id prefix) between the graph section and the actions row.
2026-07-06 12:28:34 -07:00
Omar Sobh d0adc78de4 loops UI: real list + canvas + 4-step wizard with webhook secrets card
ci / rust (push) Successful in 2m38s
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 34s
ci / publish (push) Successful in 40s
ci / e2e (push) Failing after 29m28s
Seventh and final commit of the Research + Loops arc. Replaces the
placeholder Loops surface from commit 5 with the full working UI.

New:
- lib/api/loops.ts — thin TypeScript client for every /api/loops
  endpoint (list, get, create, patch, delete, run-now, enable, disable).
- dashboard/LoopsWizard.tsx — 4-step modal:
    1. title + description
    2. task template + topology graph JSON (defaults to empty graph; a
       visual builder is future work)
    3. triggers — any combination of cron / on-completion / webhook,
       with the cron pattern input revealed inline when cron is on
    4. repeat policy — infinite or fixed-iterations
  When the webhook trigger is enabled, submit lands on a fifth SECRETS
  screen showing webhook_token + signing_key exactly once (never shown
  again by the backend), a copy-to-clipboard row for each, and the
  X-Loop-Signature: sha256=<HMAC-SHA256(key, body)> usage snippet.
- dashboard/LoopsList.tsx — replaces the stub. Loop cards with an
  enabled/disabled dot + a next-fire chip. Re-fetches on refreshKey.
- dashboard/LoopsCanvas.tsx — replaces the stub. Selected loop detail:
  header (title, enabled state, next-fire), triggers card, repeat
  policy, task template preview, topology graph JSON, and an action row
  (Run now / Enable-or-Disable / Delete with confirm).

Wired into Dashboard.tsx via a `loopsSel` / `loopsRefresh` state pair
matching the Research pattern. onDeleted clears the selection and bumps
the refresh key so the list drops the deleted card and the canvas
returns to its placeholder.

Closes the arc: all 5 tiers active, all state machines driveable from
the UI, and the loop scheduler + cron + webhook plumbing wired end-to-
end. Future work per the roadmap: visual topology builder, iteration
timeline in the canvas (via GET /api/topology-runs?loop_id=X), and the
"until" repeat policy UI.
2026-07-06 07:22:39 -07:00
Omar Sobh 568d3c4b78 research canvas: move the setTopic(null) reset inside the async load
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 2m58s
ci / publish (push) Successful in 2m42s
ci / e2e (push) Failing after 29m54s
Second lint pass caught the standalone setTopic(null) in the early-return
branch of the effect — still synchronous at the top of the useEffect. Fold
that guard into the async load() so every setState landing on this hook
runs inside the async wrapper.
2026-07-06 07:04:49 -07:00
Omar Sobh 1466f8aadc research UI: fix 3 lint errors — async wrapper + escape apostrophe
ci / gates (push) Successful in 7s
ci / frontend (push) Failing after 19s
ci / rust (push) Successful in 3m46s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
Push right after ec1ddc6 tripped three eslint errors on the same-commit
lint pass. All three are cosmetic in behavior:

- ResearchList.tsx:48 + ResearchCanvas.tsx:64 — setState was called
  synchronously at the top of a useEffect. Wrap the body in an inner
  async load() and call setState inside it; effect still returns the
  cleanup for the alive-flag guard.
- ResearchWizard.tsx:207 — bare apostrophe in JSX text ("workspace's").
  Replace with &apos; to satisfy react/no-unescaped-entities.
2026-07-06 07:03:11 -07:00
Omar Sobh ec1ddc634d research UI: real list + canvas + 4-step wizard, wired to the backend
ci / gates (push) Successful in 6s
ci / frontend (push) Failing after 19s
ci / rust (push) Successful in 2m49s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped
Sixth commit of the Research + Loops arc. Replaces the placeholder
Research surface from commit 5 with the full working UI.

New:
- lib/api/research.ts — thin TypeScript client for every /api/research
  endpoint (list, get, create, attach/detach agent, start, submit-review,
  request-publish, list/approve/reject publish approvals, wizard refine).
- dashboard/ResearchWizard.tsx — 4-step modal (topic prompt → LLM refine
  → outcome kind → agents). The refine step calls
  POST /api/research/wizard/refine which streams the workspace's default
  LLM and returns {title, description}. Users can accept, edit, or refine
  again. Manual fallback if the LLM call errors.
- dashboard/ResearchList.tsx — replaces the stub. Real topic cards with
  status pill (standby / processing / reviewing / publishing / published),
  outcome-kind chip, and the + button that opens the wizard. Re-fetches
  when the parent bumps refreshKey.
- dashboard/ResearchCanvas.tsx — replaces the stub. Selected topic
  detail: title, status header, outcome + published_at meta, assigned
  agents grid (matched to workspace claws by id), the full description
  in a monospace preformatted block, and a state-appropriate primary
  action button (Start research → Submit for review → Request publish).

Wired into Dashboard.tsx via a `researchSel` / `researchRefresh` state
pair: selecting a card sets the id, mutations bump the counter so both
list + canvas re-fetch.

All API calls go through the existing /api/[...path] catch-all Next
proxy — no new server routes needed.
2026-07-06 07:01:15 -07:00
Omar Sobh 2ff00934b2 dashboard: expand to 5 peer tiers — add Research + Loops with placeholder canvases
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 35s
ci / rust (push) Successful in 3m4s
ci / publish (push) Successful in 36s
ci / e2e (push) Failing after 29m22s
Fifth commit of the Research + Loops arc. Lights up the tier rail (both
the chip column and the top-bar crumbs) with two new peer surfaces:

  [VIZ] [RESEARCH] [LOOPS] [AGENT] [INFRA]

The stubs (ResearchList / ResearchCanvas / LoopsList / LoopsCanvas) are
self-contained placeholder components — each renders a header with a
"0 topics" / "0 loops" chip and a "coming soon" body plus a canvas hero
with the tier motif. Real functionality (topic cards, wizard, canvas
timelines, publish gate UI) arrives in the next two commits, and slots
in by replacing the stub files without touching Dashboard.tsx again.

Dashboard.tsx changes:
- `Tier` type widened to include `"research" | "loops"`.
- railIcon record grew two SVG entries — book+lens for Research, orbit
  arrows for Loops. Matches the placeholder canvas hero.
- TIER_TABS reordered as Viz → Research → Loops → Agent → Infra to
  match the mental grouping (conceptual work first, machinery second).
- Top-bar crumbs mirror the rail order.
- New isResearch / isLoops selectors used everywhere the existing tiers
  were tested; the Agents-header conditional now also skips for the new
  tiers so ResearchList and LoopsList can supply their own headers.
- Sidebar swap short-circuits to ResearchList / LoopsList; canvas swap
  short-circuits to ResearchCanvas / LoopsCanvas.
2026-07-06 06:47:37 -07:00
Omar Sobh 2527a888a2 e2e: replace stale post-login heading assertion with a URL wait
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 25s
ci / rust (push) Successful in 3m41s
ci / publish (push) Successful in 46s
ci / e2e (push) Failing after 29m36s
The shared signIn helper in every spec asserted
`getByRole("heading", { name: "Clawmates" })` after clicking Sign in, but
the dashboard's "Clawmates" is a decorative <span> in the top-bar logo,
not a heading — so all authenticated tests died on the same helper. Swap
that for `page.waitForURL((url) => !url.pathname.endsWith("/login"))`
with a 15s timeout. Robust across UI redesigns and doesn't couple the
signIn helper to a specific product surface. Should convert ~27 of the
remaining 30 failures to passes.
2026-07-05 21:46:21 -07:00
Omar Sobh c417097db2 LoginForm: restore proper <label> elements + update e2e tests to the single-step flow
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 40s
ci / rust (push) Successful in 3m2s
ci / publish (push) Successful in 42s
ci / e2e (push) Failing after 8m53s
The local-mode LoginForm had drifted to using styled <div> elements as
labels. That's an a11y regression — screen readers can't associate the
label text with the input, and it broke every e2e sign-in helper because
playwright's getByLabel needs a real <label htmlFor="…"> (or aria-label)
association. Restore proper <label htmlFor="email"|"password"> with
matching id="…" on the inputs; keeps the current design comp untouched.

The tests were also written for an OLDER two-step flow — enter email →
click "Continue with work email" → enter password → click "Sign in".
The current form is single-step (both fields, one Sign in click). Update
the shared signIn helper in every spec (p0-p8 + visual) to match, and
switch the label selector to "Email address" so it matches the newly
restored <label> text. Drop the stale a11y assertion in p6 that expected
the two-step button.

Also refresh the marketing landing check in p0-shell.spec.ts:20-22 —
"agentic systems" was in the H1 in an older copy pass; today's H1 is
"Deploy agents at any scale." Update the selector.

Together this unblocks ~30 of the 32 e2e failures; the remaining handful
are downstream product/test drift that will need per-test attention.
2026-07-05 21:27:34 -07:00
Omar Sobh 7784edb772 frontend: bump @playwright/test 1.60.0 → 1.61.1 for Ubuntu 26.04 chromium support
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 23s
ci / rust (push) Successful in 3m39s
ci / publish (push) Successful in 47s
ci / e2e (push) Failing after 31m2s
The e2e job was failing at `npx playwright install --with-deps chromium`
with "Playwright does not support chromium on ubuntu26.04-x64" — the
fleet's Gitea Actions runners (morpheus/tank/architect) run 26.04 which
is newer than 1.60's supported distro set. 1.61.1 adds the platform
detection for 26.04.
2026-07-05 20:31:32 -07:00
Omar Sobh 298eb8e20e e2e: move backend/dex/frontend from 8080 to 18080 so runners don't collide
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 23s
ci / rust (push) Successful in 3m43s
ci / e2e (push) Failing after 14s
ci / publish (push) Successful in 36s
Two of the fleet's Gitea Actions runners (morpheus, architect) already
had 8080 permanently bound by unrelated services (nginx on morpheus,
envio-hasura on architect) — every e2e run scheduled there died at
playwright's webServer preflight with "http://127.0.0.1:8080/healthz is
already used". 18080 is unused across morpheus/tank/architect.

Swap 8080 → 18080 in the eight e2e-scoped sites: clawmates.e2e.toml
(listen_addr + slack base_url + oauth redirect_base), dex.yaml (client
redirect URIs must match backend), playwright.config.ts + tests
(p4-slack, p6-oauth), the http.ts dev-fallback origin, and the two
shell scripts (e2e-backend safety check, rehearse-install healthz probe).

Prod compose (/opt/clawmates/docker-compose.yml on gw-04) is untouched;
prod continues to expose the server on 8080 internally on the compose
network (that's per-network, not host-shared).
2026-07-05 20:25:27 -07:00
Omar Sobh c5400e1007 dashboard: rename Large World → Visualizations, narrow canvas on select
ci / gates (pull_request) Successful in 5s
ci / rust (pull_request) Failing after 12s
ci / frontend (pull_request) Successful in 24s
ci / e2e (pull_request) Has been skipped
ci / publish (pull_request) Has been skipped
Three surfaces show the tier name: the rail chip, the top-bar crumb, and
the sidebar header. Rename rail "WORLD" → "VIZ", crumb + sidebar "Large
World" → "Visualizations".

Canvas narrowing: the left StructureTree keeps the full org forest so you
can still browse everything, but the right-side WorldCanvas now prunes to
just the branch containing the current selection. Clicking a company shows
only that company's teams; clicking an org shows only that org. Nothing
selected still shows the full forest.
2026-07-05 18:49:31 -07:00
Omar Sobh 1e9e930425 frontend: use-chat — auto-reconnect on SSE transport failure
Cloudflare/Traefik culls idle SSE streams periodically and users see a
"connection lost" state even though the run is still going. Add a bounded
retry loop (5 attempts, 500ms→8s exponential backoff) that reattaches
with resumeFrom. The transcript reducer already dedupes by seq, so
replaying is safe even if the server re-emits events we already saw.

Aborts (component unmount, user navigation) cut through the backoff via
sleepUnlessAborted so we don't hold the socket open through a page
transition. Terminal outcomes (run_completed/error) skip the retry.
2026-07-05 18:49:24 -07:00
Omar SobhandClaude Opus 4.8 85b0e1ac33 feat(observe): surface delegation + A2A in the live world feed via audit poll
Edge-initiated inter-agent events (gated delegation, A2A ingress) bypass the
run loop, so the world SSE now polls the append-only audit log (cursor on the
BIGINT id, seeded to max on first pass) and emits:
  - delegation.invoked -> agent.delegate {fromAgentId,toAgentId,toName,task}
  - a2a.invoked        -> a2a.invoked   {agentId}
TeamObserver renders agent.delegate as an A->B handoff in the team timeline;
adds the agent.delegate taxonomy type. Reliable live (the synthesized-run path
never streamed — active_runs + first-sight cursor jump skip it).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-29 05:55:49 -07:00
Omar SobhandClaude Opus 4.8 7352ed47ab feat(observe): team observation mode in the agent chat surface
Adds a "This agent | Team" scope toggle to the chat card's Observe view. Team
mode (TeamObserver) resolves the open agent's team (members + names via
/api/teams + /api/team/claws), lists the team's group rooms, and renders a live
timeline of agent.message / room.message / a2a.invoked among team members —
read-only, reusing the workspace SSE feed. No backend changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-28 21:46:47 -07:00
Omar SobhandClaude Opus 4.8 7589f62aca feat(a2a): External access panel + GET /api/a2a/settings
Adds GET /api/a2a/settings (enabled + publicBaseUrl) and an A2ASection in the
Fleet overview: enable/disable A2A for the workspace, mint/list/revoke external
bearer tokens (token shown once), and the discovery URL. Claw/skill publishing
is configured per-claw (follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-28 16:14:22 -07:00
Omar SobhandClaude Opus 4.8 cbfa0ff24f feat: agent-to-agent platform on ZeroClaw 0.8.2 — rooms, delegation, A2A ingress
Builds on the v0.8.2 runtime. Four workstreams, all behind the §15 MCP door:

- Group rooms (Phase 1): migration 0026; N-way threads repo with a DM/room
  count-guard; chat.send {room} + room.create/invite/leave tools; RoomMessage
  -> room.message SSE; /api/claw-chat/rooms* APIs; Observer room badge.
- Per-claw door identity: door caller_agent resolves the X-ZeroClaw-Agent
  header (set by the fork) to the specific claw, falling back to roster[0].
- Gated delegation bridge (Phase 3): clawmates__delegate door tool drives a
  sibling via the existing /ws/chat ZeroClawDriveExecutor (not A2A); self-deny,
  per-workspace hourly budget, audit trail, untrusted-banner result. Native
  in-daemon delegation stays off (it would bypass the door).
- A2A tenant ingress (Phase 2): migration 0027 (workspace_a2a + a2a_tokens);
  runtime_provision enable_a2a_server/publish_claw; routes/a2a.rs tenant-aware
  proxy (per-workspace tokens, injected internal bearer, daemon stays internal,
  cards URL-rewritten to the cm-api edge); a2a.invoked taxonomy.

Tests: cm-db room repos, cm-runtime chat tools, door units. sqlx cache updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-28 16:11:01 -07:00
Omar SobhandClaude Opus 4.8 8b06ca11e8 Agents .brain: wrap anatomy cards in a .brain container (brain icon) + 4×2 grid
ci / gates (push) Successful in 16s
ci / rust (push) Failing after 9s
ci / frontend (push) Successful in 42s
ci / e2e (push) Has been skipped
The 8 section icon cards now sit inside a labeled .brain panel (Brain icon + '.brain'
header) arranged 4 per row × 2 rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 11:49:54 -07:00
Omar SobhandClaude Opus 4.8 d9ee9f5328 Agents page: collapse anatomy into icon cards → pretty editable modal
ci / rust (push) Failing after 8s
ci / frontend (push) Successful in 25s
ci / e2e (push) Has been skipped
ci / gates (push) Successful in 7s
The agent command center's BRAIN+SURFACE columns are now a grid of compact icon
cards (system prompt / how it operates / personality / skills / capabilities /
tools / memory / safety). Clicking a card opens a modal with the full content,
nicely formatted (markdown/persona/tags), editable for the four brain text files
(system_prompt, agent_md, persona, skills_md) and saved back to the .brain via
PATCH /api/claws/{id}/brain. Capabilities/tools/memory/safety render read-only
(tools keeps its Add affordance). LIVE column unchanged.

- frontend: new AnatomyGrid (icon cards + SectionModal); ClawCommandCenter swaps the
  two verbose columns for it; skills_md added to RawBrain types.
- backend: cm-brain skills_md() getter + skills_md in ClawBrainResponse so the skills
  editor pre-fills (avoids blank-overwrite).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 11:25:20 -07:00
Omar SobhandClaude Opus 4.8 66c4a80985 Agents: read-only agent-to-agent observer in the chat card + live agent.message
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 12s
ci / frontend (push) Successful in 26s
ci / e2e (push) Has been skipped
A new "Observe" button (Eye) sits next to "+ New" in the agent chat header; clicking
flips the card into a read-only observer of that agent's conversations with other
agents, updating live as messages happen.

- frontend: AgentObserver (history from /api/claw-chat/* + live agent.message overlay
  filtered to the agent, read-only banner, no composer); ClawChatSection toggle + flip.
- backend: emit a live agent.message run-event when chat.send succeeds — events.rs
  AgentMessage variant, chat.send returns to_id, runtime emits in both tool paths,
  world.rs normalizes agent_message → agent.message SSE. No migration, no new table.

Roadmap (not built): group/multi-party rooms; A2A protocol (a2a-rs) adoption.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 09:57:29 -07:00
Omar SobhandClaude Opus 4.8 f1f1f4f862 Fleet card: fill the GPU/temp slot with daemon vitals (swap, load5/15) on non-GPU nodes
ci / rust (push) Failing after 8s
ci / e2e (push) Has been skipped
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 33s
Nodes without an onboard GPU + not on Beszel (e.g. smith.lan) left that row empty.
Show the daemon's already-collected swap + 5m/15m load there instead, so every card
is full. (Temp still comes from Beszel; a non-Beszel Mac has no temp source.)

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 07:56:06 -07:00
Omar SobhandClaude Opus 4.8 b82a71d4ea Fleet tools: 8s probe cap + post-update refresh retries (updates now reflect)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 7s
ci / frontend (push) Successful in 24s
ci / e2e (push) Has been skipped
After 'claude update' replaces the binary, the daemon's re-probe ran the fresh
binary which macOS Gatekeeper re-verifies (>2s) — the 2s probe cap missed the new
version, so the UI didn't refresh (update worked but looked stale). Bump the cap to
8s; frontend polls the tools endpoint a few times post-update to catch the re-probe.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 06:55:25 -07:00
Omar SobhandClaude Opus 4.8 ed339c2121 Fleet tools: one-click per-node update (Phase 2)
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 7s
ci / frontend (push) Successful in 23s
ci / e2e (push) Has been skipped
The ↑ badge on each tool card is now a button: confirm → POST
/api/nodes/{id}/tools/{tool}/update → daemon runs the tool's own updater + re-probes.

- daemon: tool_update op (spawned task so the 170s update can't stall the WS loop;
  re-probes + re-sends node_tools after). Fixed command allow-list (no arbitrary
  shell): claude/glm → `claude update`; kimi → `uv tool upgrade kimi-cli`; ollama →
  brew upgrade (mac) / install.sh (linux); else unsupported. 4KB output cap.
- cm-api: call_timeout/request_timeout (long ops); POST .../tools/{tool}/update
  (workspace-scoped, allow-list) → {ok,output}.
- frontend: ↑latest becomes an Update button → confirm → spinner → refresh/err.

Note: claude/kimi/glm are user-space (no sudo); ollama on Linux uses install.sh
(needs sudo — works on passwordless nodes, returns an error otherwise; surfaced in UI).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 06:25:30 -07:00
Omar SobhandClaude Opus 4.8 9263418fcb Fleet: per-node dev-tool version cards + nightly latest-check (Phase 1, read-only)
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 7s
ci / frontend (push) Successful in 23s
ci / e2e (push) Has been skipped
Each node card now shows installed versions of Docker / Claude Code / Kimi / GLM /
Ollama (conditional per node) under the ssh card, with an "update available" badge.

- daemon: probe_tools() finds docker/claude/kimi-cli/ollama across candidate bin dirs,
  extracts semver from --version, reports {"t":"node_tools",...} on connect + every 15m.
- migration node_tools + tool_latest; cm-db repo node_tools (upsert/list/latest).
- cm-api: fleet.rs NodeTools uplink → upsert; tool_versions.rs spawn_latest_checker
  (24h, npm/pypi/github; docker display-only); GET /api/nodes/{id}/tools (glm mirrors
  claude). Spawned in clawmates-server.
- frontend: NodeTools cards on each HostCard with the ↑latest badge.

Phase 2 (one-click update execution) intentionally deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-27 06:01:51 -07:00
Omar SobhandClaude Opus 4.8 d766742871 User menu: keep only Profile + Credits (drop Infrastructure/Brains/Tools)
ci / gates (push) Failing after 27s
ci / rust (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Infrastructure is the breadcrumb tier and the brain registry opens from the agents
sidebar, so the top-right menu becomes account-only. Relabel the section ACCOUNT.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 22:30:57 -07:00
Omar SobhandClaude Opus 4.8 3554a3aaf2 CI: remove k8s stages, fix the Docker-level pipeline green
ci / frontend (push) Successful in 23s
ci / rust (push) Failing after 27s
ci / e2e (push) Has been skipped
ci / gates (push) Successful in 5s
Survey + fixes so the pipeline passes at the Docker level (no k8s).

- Remove k8s: drop the `sandbox-k8s` job (kind/Calico/--features k8s-tests) and the
  "Helm chart lints" gate step. release.yml was already k8s-clean.
- Rust job:
  - `cargo fmt --all` — fix pre-existing formatting drift (fmt --check was failing).
  - clippy -D warnings: fix 3 lib warnings (cm-brain sort_by_key→Reverse, cm-api
    fleet.rs doc list indentation, node_rules map_or→is_none_or).
  - Regenerate the .sqlx offline cache (was missing the cm-runtime run_loop test
    query → offline compile failed). DB-backed tests use testcontainers at runtime.
  - Set SQLX_OFFLINE=true on the rust + e2e jobs so query! macros compile against
    the committed cache deterministically (no DB needed at compile time).
- Frontend job:
  - Fix the 1 ESLint error (useAgentTelemetry: no setState-synchronously-in-effect;
    tag the slice with agentId + derive null on mismatch).
  - Fix 2 stale panel-params tests (`terminal` is a valid app id now; assert the
    current APP_IDS + use a genuinely-unknown id for the reject case).

Verified locally: fmt clean, clippy --all-targets -D warnings clean (offline),
frontend lint 0 errors, tsc clean, 86/86 frontend tests pass, build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 18:15:31 -07:00
Omar SobhandClaude Opus 4.8 a36b2c87ac Infra fleet: Tailscale + Beszel side by side (2-column responsive grid)
ci / rust (push) Failing after 8s
ci / frontend (push) Failing after 19s
ci / e2e (push) Has been skipped
ci / sandbox-k8s (push) Failing after 52s
ci / gates (push) Successful in 6s
Wrap the Tailscale network card + Beszel monitoring in a repeat(auto-fit,minmax(340px,1fr))
grid so they share one row as two columns (stacking when the region is narrow);
moved the bottom spacing to the grid wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 17:23:09 -07:00
Omar SobhandClaude Opus 4.8 43432289fd Connect-host wizard: set everConnected in the poll callback (no setState-in-effect lint)
ci / gates (push) Successful in 7s
ci / sandbox-k8s (push) Failing after 9s
ci / rust (push) Failing after 30s
ci / e2e (push) Has been skipped
ci / frontend (push) Failing after 50s
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 15:34:29 -07:00
Omar SobhandClaude Opus 4.8 447bf04932 Connect-host wizard: delete the minted pending node on cancel (no orphan 'waiting for daemon' card)
ci / gates (push) Successful in 6s
ci / frontend (push) Failing after 36s
ci / rust (push) Failing after 7s
ci / sandbox-k8s (push) Failing after 27s
ci / e2e (push) Has been skipped
The wizard mints a pending node on open (POST /api/nodes/pair). Cancelling left it
behind as a 'waiting for daemon to dial home' card. handleClose now DELETEs the node
on close unless it actually connected (everConnected) — so cancel leaves no card,
while a node that already dialed home is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 15:30:54 -07:00
Omar SobhandClaude Opus 4.8 fadb1a084c Computer Chat app: render the user↔agent native chat (ClawChatSection), not the inter-agent inbox
ci / gates (push) Successful in 6s
ci / frontend (push) Failing after 20s
ci / e2e (push) Has been skipped
ci / rust (push) Failing after 8s
ci / sandbox-k8s (push) Failing after 1m19s
The chat launcher was repointed to the computer's 'chat' app, which rendered the
inter-agent inbox (ClawChatApp, §7.2) — empty for most agents. Point it at the
original ClawChatSection (sessions + streaming composer) so launching Chat shows
the user's actual conversation with the agent.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 14:38:33 -07:00
Omar SobhandClaude Opus 4.8 c84f3bd386 Command center: remove the Settings gear from the identity header
ci / gates (push) Successful in 7s
ci / rust (push) Failing after 8s
ci / e2e (push) Has been skipped
ci / sandbox-k8s (push) Failing after 8s
ci / frontend (push) Failing after 20s
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 14:23:36 -07:00
Omar SobhandClaude Opus 4.8 b600cb6ffb Command center: remove the model chip + 'Edit brain →' from the identity header
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 28s
ci / e2e (push) Has been skipped
ci / sandbox-k8s (push) Failing after 21s
ci / frontend (push) Failing after 21s
Inline card editing + the sidebar brain registry cover editing now; drop the
redundant top-right model chip and Edit-brain button (and the unused onEditBrain prop).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 13:43:02 -07:00
Omar SobhandClaude Opus 4.8 6b91da6022 Command center: source the System Prompt card from the brain so inline edits reflect on save
ci / gates (push) Successful in 7s
ci / frontend (push) Failing after 47s
ci / rust (push) Failing after 26s
ci / sandbox-k8s (push) Failing after 24s
ci / e2e (push) Has been skipped
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 13:35:23 -07:00
Omar SobhandClaude Opus 4.8 89c147b742 Command center: edit brain sections inline from the cards
ci / gates (push) Failing after 11s
ci / rust (push) Has been skipped
ci / sandbox-k8s (push) Has been skipped
ci / frontend (push) Has been skipped
ci / e2e (push) Has been skipped
Add PATCH /api/claws/{id}/brain (edit_brain): writes any of system_prompt / agent_md
/ persona / skills_md into the claw's .brain (best-effort) + persists system_prompt
to Postgres (authoritative) + commits a ClawSync revision.

Frontend: a reusable EditableSection (pencil → textarea → Save/Cancel → PATCH →
re-fetch brain). The SYSTEM PROMPT, HOW I OPERATE (AGENTS.md), and PERSONALITY cards
in the command center are now editable inline; saving writes back to the mapped
brain section. AGENTS.md card now always shows (so it can be authored when empty).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-26 13:30:58 -07:00