Commit Graph
268 Commits
Author SHA1 Message Date
Omar Sobh eb1df6acde launch: accept config.phase_teams as a valid team source
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 10s
ci / frontend (push) Successful in 26s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Missions created via the new multi-team wizard have neither team_id
nor team_template_id set — they carry config.phase_teams. Both the
frontend Launch button gate and the backend set_status precondition
were checking only the old two fields, disabling launch for every
new wizard-created mission with a "No team" tooltip.

  - MissionCanvas: hasTeam now also returns true when
    mission.config.phase_teams has at least one non-empty list.
  - routes::missions::set_status: same check on the server so a
    direct API caller with only config.phase_teams also gets past
    the gate.

Directly unblocks the "we just finished the wizard, Launch is greyed
out" report. Agents materialize AFTER Launch — the button is the
trigger, not a post-condition of creation.
2026-07-21 05:26:27 -07:00
Omar Sobh f0dd0147f6 templates: 5 research team templates + category filtering
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m28s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m45s
Adds the operator's five categorized research team archetypes:

  1. codebase_research — code archeologist, architecture mapper,
     flow tracer, vault scribe. Produces Obsidian vault entries
     under Codebases/<repo>/ that make future missions faster.
  2. papers_research — domain scout, paper reader, library curator.
     Pulls arXiv / Semantic Scholar / conference proceedings, keeps
     a structured local library under Papers/<topic>/.
  3. insight_research — implementation tracker, novelty hunter,
     publication drafter. Bidirectional loop that spots
     publication-worthy novelty in our own implementations of
     external papers.
  4. continuous_research — signal harvester, ranker, digest writer.
     Standing sweep of RSS + arXiv daily + GitHub trending; produces
     a rolling ContinuousResearch/<date>/digest.md.
  5. continuous_improvement — brain inspector, improvement proposer,
     improvement evaluator. Standing self-audit that files level-up
     proposals for the operator to review + measures the outcome.

Each template ships with role system_prompts + brain_seeds authored
in the same voice as the existing backend/frontend/etc templates —
evidence-first, redlines called out, no invention.

Schema + code:
  - 0057_team_templates_category.sql — new column with
    CHECK (research | development | security | ops). Existing rows
    default to 'development'.
  - team_templates::UpsertBuiltin + TeamTemplate carry category
    (with default_category = 'development' fallback for
    Serialize/Deserialize compatibility).
  - team_template_loader reads `category = "..."` from the TOML;
    absent defaults to 'development' so old templates keep working.
  - Wizard step 3 filters:
      Research teams panel → templates.filter(t.category==='research')
      Development teams panel → templates.filter(t.category==='development')
    Operator can no longer accidentally pick backend as their
    "research team".

Test fixture updated with category="development".

The templates ship in the server image via the existing
`COPY templates /etc/clawmates/templates` line — no Dockerfile
change needed.
2026-07-21 04:54:52 -07:00
Omar Sobh b8b8cb452e missions: multi-team model — pick research + development teams
ci / frontend (push) Successful in 37s
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 1m41s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Directly addresses "we want to pick one or more teams to assign to a
mission, first screen research teams, next screen dev teams." A
mission now materializes N teams, each tagged with a phase purpose.

Backend:
  - 0056_mission_teams.sql — new join table
    mission_teams(mission_id, team_id, purpose). team_id PK because a
    team belongs to one mission-purpose. missions.team_id kept as
    legacy pointer to the first minted team for single-team surfaces.
  - mission_orchestrator::on_launch — reads mission.config.phase_teams
    (JSONB shape { research: [tid,...], coding: [tid,...] }), mints
    one team per (purpose, template) pair, records each in
    mission_teams, binds the first to mission.team_id. Legacy fallback:
    if config.phase_teams is absent, uses missions.team_template_id.
    Hard error if both are absent.
  - GET /api/missions/{id}/teams — returns
    [{ team_id, purpose, team_name }], sorted by created_at asc.

Frontend wizard (step 3 rewrite):
  - researchTeamIds / devTeamIds — Set<string> multi-selects
  - Reusable TeamMultiSelect component (checkbox-style cards)
  - Panels rendered conditionally by preset:
    hasResearchPhase → "Research teams" panel
    hasCodingPhase → "Development teams" panel
    neither → "Teams" panel (bench/security-only missions)
  - canNext enforces at least one pick in every visible panel
  - submit builds config.phase_teams and passes it via CreateMissionRequest
  - Review step shows both selections by name

MissionTeamTab:
  - Fetches /api/missions/{id}/teams and groups by purpose
  - Each purpose renders a section with per-team cards
  - Falls back to a single "mission" pseudo-row for legacy missions
    that only have missions.team_id (no mission_teams rows)

CreateMissionRequest no longer sends team_template_id from the wizard
— the multi-team config.phase_teams path supersedes it. The backend
still accepts team_template_id for API callers.

Verified: cargo check --workspace + tsc + eslint --quiet all green.
2026-07-20 19:25:07 -07:00
Omar Sobh 0ee689f590 missions: hard-require team template — block empty-team launches
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 3m6s
ci / e2e (push) Skipped
ci / publish (push) Successful in 4m4s
Root-cause fix for the "mission runs with zero agents" bug. Three
enforcement layers now guarantee a launched mission has a team:

  1. mission_orchestrator::on_launch — the previous
     \`return Ok(None)\` when both team_id and team_template_id are
     None is now \`return Err(...)\`. That branch was never a real
     "auto-provision later" path; it was a silent no-op that let
     the mission flip to running with nothing to run.
  2. routes::missions::set_status — the draft→running transition
     now (a) rejects with 400 when team_id + team_template_id are
     both null, and (b) runs on_launch BEFORE flipping status +
     returns 500 on failure. No more orphan "running" missions
     with no materialization.
  3. MissionWizard step 3 — removed the misleading "LLM
     auto-provision" tile (fake code path). First real template is
     pre-selected on mount; canNext requires teamTemplateId set;
     empty state surfaces a red warning if no templates loaded.
  4. MissionCanvas Launch button — disabled with a "No team" label
     and explanatory tooltip when the mission has neither team_id
     nor team_template_id (defense-in-depth for legacy rows or
     direct-API missions).

Also flipped the mission_orchestrator test that expected
Ok(None) → now expects a specific error message.

Prod cleanup: reset the stuck mission
019f814c-d36f-7d60-8915-1ce100683133 (running with team_id=NULL) back
to draft so the operator can delete or attach a template.

Verified: cargo check --workspace + tsc + eslint all green;
mission_orchestrator test updated to match new contract.
2026-07-20 15:50:24 -07:00
Omar Sobh 3ba0485e7d mission progress UI: auto-refresh + Team tab + Live events tab
ci / rust (push) Successful in 2m59s
ci / e2e (push) Skipped
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 36s
ci / publish (push) Successful in 3m9s
Fills the biggest UX gap surfaced during the deploy walk: hosted
missions had no live-progress surface at all. Now they do.

Auto-refresh:
  - MissionCanvas grows a second useEffect that polls getMission
    every 3s while mission.status === 'running'. Stops immediately
    on terminal state (completed / failed / cancelled). Phases,
    Tasks, Artifacts, Benchmarks all update without a manual click.

Team tab (new):
  - MissionTeamTab.tsx — fetches /api/teams/{id} + /api/team/claws,
    shows a card per member with role slot + an "Open" pill that
    calls onOpenClaw(clawId) → Dashboard flips to AGENT tier with
    that claw selected, dropping the operator into the existing
    ClawCommandCenter surface (WorkingOnNow, ReasoningStream, etc).

Live events tab (new):
  - MissionLiveEvents.tsx — polls /api/missions/{id}/runs every 5s
    for the topology_runs bound to this mission, opens one
    EventSource per active run against /api/topology-runs/{id}/events,
    renders as a chronological scrolling feed with per-event kind
    pills + per-run short-id badges. Auto-scrolls unless the
    operator scrolled up. New runs auto-attach; terminal runs
    close cleanly.

Backend:
  - cm-db::repo::topology_runs::list_by_mission — SELECT ... FROM
    topology_runs WHERE mission_id = $1 ORDER BY created_at DESC.
    Uses runtime sqlx::query (not the macro) to avoid a sqlx cache
    regen just for this route.
  - TopologyRunSummary gains #[derive(Serialize)] + rfc3339 codecs.
  - GET /api/missions/{id}/runs — workspace-scoped, returns
    { runs: [...] }.

Dashboard wires onOpenClaw on MissionCanvas → setAgentId + setTier("claw").

Verified: cargo check --workspace + tsc --noEmit + eslint --quiet
all green.
2026-07-20 15:31:41 -07:00
Omar Sobh cf735312f8 mission canvas: cap description height with own scroll
ci / gates (push) Successful in 6s
ci / rust (push) Successful in 3m2s
ci / e2e (push) Skipped
ci / frontend (push) Successful in 36s
ci / publish (push) Successful in 4m5s
Long refined descriptions (Opus tends to emit full section spines)
pushed the tabs + toolbar past the viewport with no way to reach
them. Cap the description block at 38vh with its own overflow-y so
the header stays reachable no matter how long the brief gets.
2026-07-20 15:21:25 -07:00
Omar Sobh d8c8793c4a ci fixes: cargo fmt, eslint entities, max-lines split
ci / gates (push) Successful in 8s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m25s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m46s
CI on 6ffbe97 failed on two auto-fixable gates. Both fixed:

  * cargo fmt --all — rustfmt applied across the surface touched
    by the last ~20 commits (world.rs, security_scan.rs,
    routes/{missions,nodes,terminal}.rs, fleet_herdr.rs,
    mission_workspace.rs, benchmark_runner.rs, mission_refiner.rs,
    lib.rs, tests/mission_orchestrator.rs, cm-db/repo/{missions,teams}.rs,
    bins/clawmates-node/src/main.rs)
  * eslint apostrophe escapes in HerdrSessions + MissionWizard
  * eslint max-lines: extracted EditMissionModal + RefineDiffModal
    (each ~200 LoC) into their own files. MissionCanvas drops from
    1424 to 1026, comfortably under both the 1250 eslint cap and the
    1500 CI budget.

New files:
  frontend/src/components/dashboard/EditMissionModal.tsx  (211 LoC)
  frontend/src/components/dashboard/RefineDiffModal.tsx   (208 LoC)

Verified locally: cargo fmt --check clean, cargo check clean,
mission_orchestrator test 3/3 pass, tsc + eslint --quiet both silent.
2026-07-20 12:03:43 -07:00
Omar Sobh 6ffbe978b2 missions: extract MissionLivePane to stay under 1500-LoC CI budget
ci / rust (push) Failing after 10s
ci / gates (push) Successful in 6s
ci / frontend (push) Failing after 19s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Previous push hit the gates job's file-size gate — MissionCanvas.tsx
was 1517 lines (17 over). The Live Pane xterm subcomponent is
cleanly separable (no shared state with the parent, just takes
nodeId + visible props), so it lifts into its own file at zero
behavior cost.

  frontend/src/components/dashboard/MissionLivePane.tsx  (new, 106 LoC)
  frontend/src/components/dashboard/MissionCanvas.tsx    (1517 → 1424)

Also drops the xterm.css + useResilientTerminal imports from
MissionCanvas since only MissionLivePane needs them now.
2026-07-20 11:55:47 -07:00
Omar Sobh d4efb0dba2 missions sidebar: wrench toggle + multi-select bulk delete
ci / gates (push) Failing after 5s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped
Matches the AGENT tier's manage-mode pattern. Three states on the
sidebar toolbar:

  - default: [Wrench] [Refresh] [+]
  - select mode active: [Wrench] (highlighted) [Refresh] [+] + rows
    grow a checkbox on the left and click-toggles selection instead
    of opening the mission
  - selection made: [Wrench] [Trash · N] [Refresh] [+] where the
    trash pill shows the count and fires window.confirm → bulk
    DELETE /api/missions/{id} loop

On success: exits select mode, calls onDeleted with the ids, parent
Dashboard clears missionsSel if it was in the batch. Failures stay
selected + a red error line surfaces the count.

Reuses the CRUD backend added earlier (a1c… PATCH/DELETE) — no new
server work.
2026-07-20 11:46:24 -07:00
Omar Sobh bf4af48c80 herdr phase 3: INFRA tier Herdr sessions surface
New INFRA category "Herdr sessions" (purple sparkles icon between
Fleet and Local hardware). Shows a card per online fleet node with:

  - Node name + hostname + IP
  - Per-workspace agent state pills (working / blocked / done /
    idle / unknown), colored dots + pane count
  - "Open" button → renders that node's full Herdr TUI inline via
    xterm.js (same nodeHerdrConnector + WebRTC-with-fallback the
    MissionCanvas Live Pane uses)

Backend:
  - node daemon: herdr_workspaces + herdr_snapshot ops
    (`herdr workspace list`, `herdr api snapshot`)
  - fleet_herdr::snapshot helper on top of hub.call_timeout
  - GET /api/nodes/{id}/herdr/session route

Fetch flow: /api/nodes filtered to status='online' → for each,
/api/nodes/{id}/herdr/session in parallel. Snapshot errors surface
per-card without failing the whole grid.

The "Open" xterm is separate from the MissionCanvas Live Pane —
this one is scoped to the whole node's Herdr TUI (any workspace),
not a specific mission's pane. Operator toggles between nodes via
the buttons.

Verified: cargo check --workspace + tsc --noEmit both green.
2026-07-20 11:30:52 -07:00
Omar Sobh a5588b0289 herdr phase 2: Live Pane tab (xterm.js → node's herdr TUI)
The killer UX feature: click a mission's Live Pane tab and watch the
actual Herdr TUI on the target node in the browser — cursor, colors,
tool output, all live. WebRTC DataChannel direct where the browser
can reach the node peer-to-peer, WS-relayed fallback otherwise
(same auto-negotiation the INFRA node terminal already uses).

Zero new deployment infra — reuses the existing terminal_ticket +
terminal_ws + PTY-over-control-channel machinery. The one primitive
we grew: PtyTarget::Command variant so the node can spawn an
arbitrary program (\`herdr\`) in the PTY instead of the login shell.

Node daemon (clawmates-node):
  - PtyTarget grows a Command { argv } variant
  - spawn_command_pty resolves bare names against user + system bin
    dirs (matches how tool_update finds claude/kimi)
  - PtyTarget::from_frame reads the `command` array from the pty_open
    frame; precedence Command > Container > Host

cm-api:
  - NodeHub::open_pty grows an optional command argv; when set, the
    frame carries it and the daemon spawns the program directly.
  - routes::nodes::TermCtrl gains a `command: Vec<String>`; the
    fallback branch threads it through.

Frontend:
  - core.ts::webrtcConnector takes an optional commandOverride
    that ships inside the fallback frame
  - nodeHerdrConnector(nodeId) — mints the standard ticket + WS URL
    but overrides command to ["herdr"]
  - MissionCanvas grows a "pane" tab, visible only when
    runtime_kind='local_herdr'. LivePane subcomponent uses xterm.js
    (already a workspace dep) via useResilientTerminal, shows a
    connecting/relayed/direct pill in the corner.

To watch a mission live: pick "On a fleet node (Herdr)" + target
node in the wizard, launch, click Pane tab → node's Herdr TUI
appears. Navigate to the mission workspace in the Herdr sidebar
(mouse or prefix+w) to zoom into the mission's pane.

Focus-a-specific-pane-directly is a later enhancement — Herdr has
no CLI arg for it yet, so operator navigates the sidebar for now.

Verified: cargo check --workspace + tsc --noEmit both green.
2026-07-20 10:58:45 -07:00
Omar Sobh 2b3ec27757 herdr phase 1c: wizard runtime picker + on_launch auto-dispatch
Closes the operator loop for the second-runtime path. Missions
created with runtime='local_herdr' now spawn a Herdr pane on their
target_node automatically on draft→running.

Frontend (MissionWizard):
  - Step 4 grows a "Runtime" section above Schedule
  - Radio: "Hosted (ZeroClaw)" default | "On a fleet node (Herdr)"
  - Local-Herdr shows a dropdown of ONLINE nodes only (from
    /api/nodes filtered by status='online')
  - canNext blocks Next when local_herdr picked without a node
  - Review step shows "Runtime: Herdr on <node-name>" or "Hosted"
  - Empty-online-nodes state hints "Connect one from INFRA first"

Backend:
  - mission_orchestrator::on_launch grows a NodeHub param; when
    mission.runtime_kind='local_herdr' + target_node_id set +
    hub present → calls fleet_herdr::dispatch(). Non-fatal:
    logs and continues so a research_only mission with a Herdr
    runtime chosen accidentally still boots the team.
  - routes::missions::set_status passes state.node_hub through.
  - Test call sites updated to pass None for the new param
    (integration tests don't drive real fleet nodes).

CLI stub: on_launch currently hard-codes cli="claude" for the
Herdr pane. Phase 4 will read that from the team template so a
research team → kimi, gpu team → claude, etc.

Verified: cargo check --workspace + cargo test
-p cm-api --test mission_orchestrator + tsc --noEmit all green.
2026-07-20 10:02:40 -07:00
Omar Sobh d6dbd044c8 herdr phase 1a: missions runtime_kind + target_node schema
First slice of the second-runtime path. Missions now carry
runtime_kind ('zeroclaw' | 'local_herdr') + target_node_id (FK to
nodes) so the mission_orchestrator + phase executors can dispatch
differently depending on where the operator wants execution.

Migration:
  - 0055_missions_runtime_kind.sql — adds runtime_kind (NOT NULL
    DEFAULT 'zeroclaw' + CHECK), target_node_id (nullable FK ON
    DELETE SET NULL). All existing missions backfill to 'zeroclaw'
    so behavior is unchanged.
  - topology_runs also grows herdr_workspace_id / herdr_tab_id /
    herdr_pane_id text columns so a resumed run can reattach to the
    same Herdr pane instead of spawning a duplicate.

Code:
  - cm-db::repo::missions — Mission + NewMission carry the two new
    fields; all SELECTs updated; INSERT COALESCE-defaults
    runtime_kind to 'zeroclaw' when unspecified.
  - routes::missions::create — validates runtime_kind and requires
    target_node_id when kind='local_herdr' (400 otherwise).
  - lib/api/missions.ts — RuntimeKind type; Mission carries both;
    CreateMissionRequest optional fields.

Behavior is opt-in: no path exists yet to actually create a
local_herdr mission — that lands in Phase 1c (wizard picker). This
commit just makes the schema + validation in place so Phase 1b's
fleet_herdr dispatch module can key on it.

Tests: mission_orchestrator integration test still green.
2026-07-20 09:45:15 -07:00
Omar Sobh 1f0117e35a mission canvas: add / edit / delete toolbar controls
Top-right toolbar grows three CRUD controls per your request:

  - Plus (always visible) — opens MissionWizard, selects the new
    mission on create
  - Pencil (draft-only) — opens EditMissionModal for title +
    description; PATCHes /api/missions/{id}
  - Trash (always visible) — window.confirm then DELETEs; sidebar
    selection clears via new onDeleted callback

Backend:
  - cm-db::repo::missions::update_meta(id, ws, title?, description?)
    — COALESCE-based partial patch
  - cm-db::repo::missions::delete(id, ws) — hard delete, cascades
    via FKs on phases/tasks/artifacts/benchmark_snapshots
  - PATCH /api/missions/{id} (draft-only) + DELETE /api/missions/{id}

Frontend:
  - lib/api/missions — updateMission + deleteMission clients
  - MissionCanvas — three toolbar buttons, EditMissionModal
    (title + textarea for description), local wizard state
  - Dashboard — passes onSelect + onDeleted so sidebar reacts to
    create + delete without stale selection

Edit is draft-only (backend enforces + button hidden past draft) so
in-flight missions can't have their brief mutated out from under
running agents. Delete is unconditional — operator responsibility to
Cancel first if a run is live.
2026-07-20 08:32:52 -07:00
Omar Sobh 5b7cb55d21 mount LevelUpInbox + document Gemini env vars
1. Dashboard.tsx — mount LevelUpInbox as a bottom panel on the
   MISSIONS tier sidebar (below MissionsList, above the tier rail).
   Capped at 38% height so it never crowds the mission list; scrolls
   independently when the proposal count grows.

2. deploy/compose/.env.example — document GEMINI_API_KEY +
   CLAWMATES_REFINER_MODEL + CLAWMATES_LEVEL_UP_MODEL. Refine + Level-
   Up silently 500 without the key; the model overrides default to
   gemini-2.5-flash so setting the key is the only required step.

Closes the two loose ends I called out last turn (inbox not mounted,
env vars undocumented). The full flow — Refine → mission launch →
security scan / benchmark → level-up review — is now wireable
end-to-end on a fresh deploy just by copying .env.example → .env
and setting POSTGRES_PASSWORD + GEMINI_API_KEY.
2026-07-19 19:14:25 -07:00
Omar Sobh ad1cee0b08 refine polish: before/after diff view + accept/cancel/restore
Refine no longer clobbers the mission description on click. Flow:
  1. Click Refine → server generates the rewrite, returns
     { original, refined } WITHOUT persisting
  2. RefineDiffModal shows a side-by-side pane (raw before,
     Markdown-rendered after)
  3. User picks:
     - Accept → PATCH /api/missions/{id}/description commits refined
     - Cancel → discards the proposal, description unchanged
     - Restore original → forces a write of `original` (undo path
       for accidentally-accepted refines, since Accept+Cancel is
       still a two-step confirmation)

Backend:
  - mission_refiner::refine returns a RefineResult { original, refined }
    struct instead of persisting + returning the text
  - routes::missions::refine now returns { original, refined }
  - routes::missions::set_description added on PATCH
    /api/missions/{id}/description (draft-only)

Frontend:
  - lib/api/missions — refineMission return type is now RefineResult;
    added setMissionDescription
  - MissionCanvas — RefineDiffModal + DiffPane subcomponents;
    accept / cancel / restore handlers wired to state

Closes task #20.
2026-07-19 18:46:14 -07:00
Omar Sobh 278cbf90b7 artifacts tab: inline PDF preview via iframe (task #19) 2026-07-19 18:43:46 -07:00
Omar Sobh 267b28a762 mission canvas: security-scan + benchmark trigger buttons
Fills the frontend gap after slices 7 + 8 shipped the backend runners
without triggers. Each phase card in the Phases tab now grows an
action row when the mission is running or completed:
  - security_scan phase → "Run scan" button (POSTs /security-scan)
  - benchmark phase → "Baseline" + "After" buttons, iteration auto-
    derived from the current benchmark_snapshots count

Findings from security_scan already surface in the Tasks tab via
the task-card parser (external_id = cargo_audit:RUSTSEC-... etc);
the tool prefix in the badge is enough to distinguish sources
without a separate grouped view.

Closes tasks #17 + #18.
2026-07-19 18:42:40 -07:00
Omar Sobh a3d5a5a96d level-up UI: inbox + review drawer + per-claw/per-team triggers
Fills the frontend gap left after slice 8.5 shipped the level-up
backend without any UI. Reviewers can now:
  - See pending proposals across the workspace (LevelUpInbox)
  - Trigger a proposal from any claw's ClawCommandCenter header
  - Trigger a team-scoped proposal from TeamObserver's header
  - Review one proposal item-by-item and apply the approved subset
    (or reject all) via LevelUpDrawer

The drawer preselects auto-applicable kinds (identity_refinement,
skill_add, skill_candidate, brain_consolidation) and disables the
manual-only kinds (roster_change, mcp_bundle_change) with an
inline "manual — needs team wizard" hint, matching what the
backend applier does per commit 9b5e63c.

New files:
  - frontend/src/lib/api/level-up.ts — typed client for the 6 endpoints
  - frontend/src/components/dashboard/LevelUpDrawer.tsx — review pane
  - frontend/src/components/dashboard/LevelUpInbox.tsx — pending list

Wired:
  - ClawCommandCenter identity header — "Level up" pill (purple)
  - TeamObserver header — "Level up team" pill (purple)

The inbox is deliberately not yet mounted anywhere; it's a
composable component ready to drop into the missions or agent tier
(follow-up decision on which surface hosts the global list).
2026-07-19 18:41:19 -07:00
Omar Sobh 56201a6985 mission canvas: add Refine button + markdown-rendered description
Adds a Refine button to the left of Refresh + Launch on the mission
detail toolbar (draft-only). Clicking it POSTs to a new endpoint that
calls Gemini 2.5 Flash to rewrite the user's freeform description into
a coherent, sectioned Markdown brief (Objective / Context / Scope /
Constraints / Acceptance Criteria / Open Questions) ready for the
research + coding agents to ingest cleanly.

Backend:
  - crates/cm-api/src/mission_refiner.rs — Gemini call with a
    system prompt that preserves user-provided facts, avoids
    invention, and emits raw markdown (not JSON).
  - POST /api/missions/{id}/refine — draft-only, 400 on empty
    description or non-draft state.
  - cm-db::repo::missions::set_description helper.

Frontend:
  - MarkdownBlock — tiny zero-dep renderer for h1/h2/h3, bullet +
    numbered lists, **bold**, `code`, paragraphs. Deliberately
    small; the refiner emits a bounded subset.
  - MissionCanvas — Refine button (Sparkles icon, secondary style)
    to the left of Refresh; description now renders through
    MarkdownBlock instead of a single <p>. Disabled while
    description is empty or a refine is in flight.
  - lib/api/missions — refineMission client.
2026-07-19 17:20:34 -07:00
Omar SobhandClaude Opus 4.7 4663348a0e slice 9: big-bang cutover — delete legacy research + loops UI
ci / rust (push) Successful in 3m34s
ci / e2e (push) Skipped
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 36s
ci / publish (push) Successful in 38s
Missions has been the unified surface for a while (Slices 1-8.5).
This slice makes it the ONLY surface by deleting the legacy
research + loops UI:

Removed frontend files:
  - dashboard/ResearchList.tsx
  - dashboard/ResearchCanvas.tsx
  - dashboard/ResearchWizard.tsx
  - dashboard/LoopsList.tsx
  - dashboard/LoopsCanvas.tsx
  - dashboard/LoopsWizard.tsx
  - dashboard/LoopStaffingStep.tsx
  - dashboard/NoAgentsGate.tsx
  - dashboard/ResearchArtifactPicker.tsx
  - dashboard/AutoProvisionCard.tsx
  - dashboard/LiveRunLogs.tsx
  - lib/api/research.ts
  - lib/api/loops.ts

Dashboard.tsx cleanup:
  - Tier enum collapses to world | missions | claw | repos | infra
  - Dropped researchSel / researchRefresh / loopsSel / loopsRefresh
    state slots
  - Dropped the Research + Loops crumbs from the tab bar
  - Dropped the two rail icons; missions rail icon retitled
  - TIER_TABS lists missions in the second slot (was research)
  - Combined isInfra || isMissions || isRepos guard replaces the
    six-way branch

What INTENTIONALLY stayed (soft cutover on backend):
  - /api/research/* + /api/loops/* routes still respond — no UI
    hits them but external integrations (webhooks, prior curl
    scripts) don't hard-break on this deploy
  - research_topics / loops / research_outcomes / research_topic_agents
    / research_publish_approvals tables remain — the missions
    backfill from 0047 references these rows via config.legacy_*
    keys, and dropping the tables now would cascade FK deletes
    into topology_runs.research_topic_id / .loop_id nullouts
  - cm_db::repo::{research_topics, research_outcomes, loops} +
    cm_api::routes::{research, research_setup, research_pipeline,
    loops} + cm_runtime::loops kept — they're internal-only now,
    scheduled for a follow-up cleanup PR

Follow-up PR (dedicated cleanup):
  - Drop the 5 legacy tables + null out topology_runs FKs
  - Delete the ~4k LoC of backend routes/repos + their tests
  - Remove research/loops crumbs from URL history

Frontend TS check: clean (16 pre-existing unused warnings in
Dashboard.tsx are unrelated).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 16:53:21 -07:00
Omar SobhandClaude Opus 4.7 58963d5083 slice 8: security scan runner + trigger endpoint
ci / frontend (push) Successful in 27s
ci / rust (push) Successful in 4m28s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m47s
ci / gates (push) Successful in 3s
Runs the security template's tool set (cargo-audit / gitleaks /
trivy fs / semgrep) inside the mission's team container and
materializes each finding as a mission_task keyed on the tool's
canonical id. The subsequent coding phase picks up the tasks and
applies remediations; the committer closes them by emitting
`COMPLETED: <external_id>` (Slice 5's task-card parser handles it).

Rust surface:
  - cm_api::security_scan::run(mission_id, phase_id)
  - Per-tool runners with JSON output parsing:
      cargo audit --json          → vulnerabilities[].advisory.id
      gitleaks detect --report-format=json → [{ Fingerprint, RuleID }]
      trivy fs --format=json      → Results[].Vulnerabilities[].VulnerabilityID
      semgrep --config=auto --json → results[] w/ rule+path+line fingerprint
  - Tool errors surface as a `warning` task instead of failing the
    scan — operator sees which need installing/fixing without a
    silent no-op.
  - Findings map to mission_tasks with external_id = "<tool>:<id>"
    (e.g. cargo_audit:RUSTSEC-2024-0001, gitleaks:<sha>,
    trivy_fs:CVE-2024-1234, semgrep:<rule>@<file>:<line>).

API:
  - POST /api/missions/{id}/security-scan { phase_id }
    → { findings, tasks[] } — full task list after upsert so the
    canvas can render immediately.

Frontend:
  - triggerSecurityScan helper in lib/api/missions.ts. Findings
    show up in the existing Tasks tab (Slice 5's UPSERT path).

Container requirements (opt-in):
  - Runs inside the team container via `docker exec`, so the tools
    must be present in that image. Missing = warning task, not fail.
  - `-w /workspace/repo` so scanners see the mounted repo. Reads
    teams.zeroclaw_container (populated on first phase run).

Follow-ups:
  - MCP bundle wrapping the same tools as agent-callable functions
    (currently agents scan by shelling out to `cargo audit` etc.
    directly; a typed MCP wrap lands with clean audit trail).
  - Auto-fire from the security_hardening workflow template on
    phase transition (currently manual via API trigger).
  - Bundle the four tools into the runtime image (or a dedicated
    security-tools image) so operators don't have to install them.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 16:19:29 -07:00
Omar SobhandClaude Opus 4.7 f843c9ddb1 slice 7: before/after benchmark runner
ci / frontend (push) Successful in 26s
ci / publish (push) Successful in 4m52s
ci / gates (push) Successful in 4s
ci / rust (push) Successful in 4m19s
ci / e2e (push) Skipped
Executes a benchmark harness inside the mission's team container
and records the resulting metrics as a benchmark_snapshots row keyed
on (phase_id, iteration). Baseline pass (iteration=0) captures
before_metrics; each post-iteration call captures after_metrics +
computes delta vs baseline.

Rust surface:
  - cm_db::repo::missions::upsert_benchmark_snapshot / benchmark_snapshots_for
  - cm_api::benchmark_runner::{baseline, after_iteration, run}
  - Harness enum: Auto | Criterion | CargoBench | VitestBench |
    PytestBench | Shell (each with a command() vector)
  - Auto detection peeks at the repo layout inside the container
    (Cargo.toml → CargoBench, package.json → VitestBench, pyproject
    → PytestBench). Falls back to a Shell echo when nothing
    identifiable.
  - Bencher-format line parser extracts (name, ns_per_iter,
    plusminus) so criterion + `cargo bench` output become structured
    samples the canvas can diff.
  - compute_delta pairs samples by name, emits {before_ns, after_ns,
    delta_pct, direction: improved|regressed}.

API:
  - POST /api/missions/{id}/benchmark { phase_id, slot, iteration? }
    triggers baseline or after run and returns the mission's full
    snapshot list.
  - GET /api/missions/{id} now includes `benchmarks[]` in the detail
    payload.

Frontend:
  - New Benchmarks tab on MissionCanvas with iteration + driver
    header, plus a 4-column grid (bench / before / after / Δ%) when
    delta samples are present. Improved deltas render green,
    regressions red.
  - TS types + triggerBenchmark() helper in lib/api/missions.ts.

Wiring notes:
  - team_container_for_mission reads teams.zeroclaw_container — that's
    populated by topology_worker::try_team_gateway_url on first run,
    so trigger baseline AFTER the mission's first phase spawns the
    container.
  - Not auto-fired yet by phase execution; that's the "template phase
    executor" work that spans Slices 4-8. Manual API trigger works
    today; automated hook is a follow-up.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 15:32:47 -07:00
Omar SobhandClaude Opus 4.7 9ba5c06a1a slice 3: 6 team templates seeded from TOML recipes
ci / rust (push) Failing after 11s
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 28s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Team templates are the canonical rosters + tool bundles that mint
concrete teams for a mission. Every builtin ships as a TOML recipe
under templates/teams/*.toml, loaded into the DB at server boot.

Migration 0048 adds:
  - team_templates    (id, key, name, stack, default_topology,
                       risk_profile, mcp_bundles, version, source,
                       workspace_id)
  - template_roles    (m2m: template_id + slot; system_prompt,
                       skills[], brain_seed)
  - teams gets template_id + template_version for level-up lineage

Ships 6 builtins:
  - rust_sdlc  — planner/coder/tester/reviewer/committer for Rust
  - backend    — api_designer/db_engineer/coder/tester/committer
                 (Postgres, DuckDB, graph DBs, wire protocols)
  - frontend   — designer/coder/tester/committer (React + Tailwind + ShadCN)
  - mobile     — designer/coder/tester/committer (Expo, RN, iOS, Android)
  - gpu        — arch_analyst/kernel_author/bench_engineer/coder/committer
                 (CUDA, Metal, ROCm from Rust)
  - threejs    — scene_designer/coder/shader_author/perf_engineer/
                 committer (three.js, WebGL, WebGPU)

Each role has a versioned system_prompt + skill list + brain_seed
markdown. Skills column is a name array today; Slice 3.5a promotes it
to a typed m2m join with the real skills catalog.

Server boot:
  - team_template_loader::load_builtins reads TOML from
    /etc/clawmates/templates/teams (container) or templates/teams (dev),
    upserts idempotently. Deterministic uuid per template key (sha256
    of a fixed namespace + key) so ids are stable across boots.
  - Dockerfile copies templates/ to /etc/clawmates/templates.

Read API:
  - GET /api/team-templates       — list all
  - GET /api/team-templates/{id}  — detail with roles

Wizard:
  - Step 3 rewired from a raw team_id text field to a template picker
    with "LLM auto-provision" as the default option + one card per
    builtin, showing stack, topology, risk profile, and description.
  - Mission create now passes team_template_id (not team_id) so phase
    execution knows which template to mint from.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 12:41:15 -07:00
Omar SobhandClaude Opus 4.7 fc67936e33 slice 2: MissionWizard + MissionCanvas + MissionsList frontend
ci / gates (push) Successful in 4s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m10s
ci / e2e (push) Skipped
ci / publish (push) Successful in 4m10s
Adds the unified missions tier to the dashboard. Runs in parallel
with Research + Loops tabs until Slice 9's big-bang cutover.

New files:
  - lib/api/missions.ts       — typed API client + 5 template presets
                                (research_only, research_and_code,
                                security_hardening, refactor, benchmark)
  - dashboard/MissionsList.tsx — sidebar list with status pills +
                                template-kind badges + new-mission CTA
  - dashboard/MissionWizard.tsx — 5-step adaptive wizard:
      1) template picker (5 cards)
      2) title/description + repo (when required)
      3) team (placeholder — Slice 3 wires templates + auto-provision)
      4) schedule (one-shot or cron)
      5) review + launch
  - dashboard/MissionCanvas.tsx — 4-tab detail view (overview/phases/
                                tasks/artifacts), draft→running launch

Dashboard.tsx gets a new "missions" tier + crumb + rail icon (flag).

Slice 2 ships a minimal implementation that creates missions with the
template's canned phase composition. Slice 4 replaces the preset table
with real TOML-recipe dispatch on the server; the client's fallback
presets keep offline preview working.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-19 12:19:19 -07:00
Omar SobhandClaude Opus 4.7 82b5cf4385 research canvas: refresh claws + make Description collapsible
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 39s
ci / rust (push) Successful in 3m16s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m35s
Two UX cleanups on the research canvas:

1. Agent cards were showing "(missing agent)" for every slot after an
   in-wizard auto-provision because the Dashboard's `claws` prop is SSR-
   rendered and doesn't include agents created during the client session.
   Refetch /api/team/claws on canvas mount + refreshKey change, merge
   with the prop (fresh wins) so newly-provisioned agents resolve
   correctly without a page reload.

2. The Description block was always fully expanded. Wrap it in <details
   open> so the user can collapse it once they've read it, matching the
   existing "Original prompt" pattern.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-18 18:21:37 -07:00
Omar Sobh efc13f2904 trim: drop stale roster-empty comment (LoopsWizard)
ci / frontend (push) Successful in 36s
ci / gates (push) Successful in 4s
ci / rust (push) Successful in 2m57s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m43s
2026-07-18 16:26:15 -07:00
Omar Sobh c9dc96c44b fix: use TopologyKind alias to keep LoopsWizard under 1250 lines
ci / gates (push) Successful in 5s
ci / frontend (push) Failing after 1m6s
ci / rust (push) Successful in 3m33s
ci / e2e (push) Skipped
ci / publish (push) Skipped
2026-07-18 16:26:02 -07:00
Omar SobhandClaude Opus 4.7 c3e8f3ee01 fix: match AutoProvisionedAgent shape + valid OutcomeKind for loops
ci / gates (push) Successful in 15s
ci / rust (push) Successful in 4m29s
ci / frontend (push) Failing after 20s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-18 16:24:40 -07:00
Omar SobhandClaude Opus 4.7 279d785f5b loops wizard: extract AutoProvisionCard to stay under 1250 lines
ci / gates (push) Successful in 6s
ci / frontend (push) Failing after 33s
ci / rust (push) Successful in 2m52s
ci / e2e (push) Skipped
ci / publish (push) Skipped
Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-18 16:23:13 -07:00
Omar SobhandClaude Opus 4.7 6f1515b1b0 team runs modal: expose runtime posture (risk_profile + mcp_bundles)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 9s
ci / frontend (push) Failing after 42s
ci / e2e (push) Skipped
ci / publish (push) Skipped
PATCH /api/teams/{id}/runtime-config existed since the team-wizard work
but had no UI — you needed curl to swap a team's risk_profile or add
gitea_forge to its bundles. Add compact pickers to TeamRunsModal above
"Run now": a Risk profile <select> and a Bundle checkbox list.

Fetches current settings from GET /api/teams/{id} on open and PATCHes
inline on every change. No save button — the debounce is the user's
next click. Optimistic state; next-open resync corrects any drift.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-18 16:19:12 -07:00
Omar SobhandClaude Opus 4.7 9cab32c354 loops wizard: auto-provision team in-wizard (parity with research)
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 9s
ci / frontend (push) Failing after 17s
ci / e2e (push) Skipped
ci / publish (push) Skipped
The LoopsWizard hard-blocked on an empty roster via NoAgentsGate — new
users had to bounce to the Agents page first. Match ResearchWizard's
flow: remove the top-level gate, add an auto-provision panel to the
staffing step (6) that derives 3-5 roles from the loop's task via
Claude Sonnet 5 and preselects the returned agents into
selectedAgents so the existing submit path publishes them onto the
loop with zero extra clicks.

Existing rosters still work — the hand-pick panel now sits below the
auto-provision card and only renders when the workspace has agents and
no team was auto-provisioned this session.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-07-18 16:17:57 -07:00
Omar Sobh e95e251c9c empty state: primary CTA button instead of hunt-for-plus text
ci / gates (push) Successful in 6s
ci / rust (push) Successful in 4m4s
ci / e2e (push) Skipped
ci / frontend (push) Successful in 28s
ci / publish (push) Successful in 35s
The '+ new topic' button (commit 47a4242 unlocked it when the
workspace roster was empty) is a tiny 34x34 corner icon that
disappears against the header on a truly empty page. Users kept
asking why they can't start a research topic when in fact they
could — the target was just invisible.

Replace the 'Hit the + button to launch the wizard' hint with a
proper primary CTA: coral pill 'Start a research topic' with the
Plus icon, centered in the empty sidebar. Same treatment for
LoopsList → 'Start a loop'.

The wizard flow's auto-provisioning behavior means the empty-
workspace path is now the happy path — one click and you get
everything (topic, team, agents, container). The empty state
should invite that click, not require a hunt.
2026-07-17 21:33:30 -07:00
Omar Sobh f8589471bc AgentComputer: clickable model picker (dropdown → PATCH /api/claws/:id/model)
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 27s
ci / rust (push) Successful in 4m20s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m45s
The '● <model>' text on the per-agent Computer slide-out was
read-only — swapping a claw's model required curl. Now it's a
button that opens a small dropdown of the curated fleet models:

- Sonnet 5 / Opus 4.8 / Haiku 4.5 / Sonnet 4.6 (Anthropic)
- GLM 4.6 / 5.2 (Z.AI)
- Kimi K2 (Moonshot)
- Gemini 2.0 Flash (Google)
- Llama 3.3 70B (Groq)

Click a model → PATCH /api/claws/{id}/model → picker closes, the
button relabels to the new selection. The endpoint persists the DB
binding + best-effort re-provisions the runtime; the swap applies
on the claw's next turn. Long-tail models still work via curl with
any string.

Adds an optional onModelChanged callback so callers (Dashboard's
runtime-config enrich cache) can refresh their state without a
full re-render.
2026-07-17 21:18:29 -07:00
Omar Sobh ac8c689f50 logs + team parity: pretty step/container renderers; quota + audit on team creation
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 37s
ci / rust (push) Successful in 2m58s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m53s
Two bundled changes:

── LiveRunLogs prettification ──────────────────────────────────
The Steps + Container tabs were plain mono lines with a single
color per event. Now they get structured layout:

Steps:
- Color-hashed actor pill (stable palette so [Distiller] and
  [Novelty Analyst] each get their own hue across the session).
- Phase pill (plan=cyan, work=green, synth=amber, aggregate=purple).
- Token count pill formatted 1.2k / 14.3k / etc.
- Gated-action warning pill in amber when > 0.
- Left-border color strip keyed to the actor for at-a-glance
  visual grouping.
- Long outputs collapse to their first 300 chars with a '+ N more'
  toggle to expand the full text.
- 'done' events get a green (or red for error) border strip +
  pill instead of blending into the stream.

Container:
- Splits '[actor] action (outcome) · msg' into colored spans —
  actor pill (deterministic color), action in dim, outcome pill
  green/red/dim by state.
- Non-line events (info/error/done) get their own left-border
  strip so bash echoes and stack traces don't drown in the daemon
  chatter.
- Timestamps switch to HH:MM:SS.mmm — dense but scannable.

Small palette (LOG constants) keeps the color budget bounded — no
new UI vocabulary, just cleaner reads of what was already there.

── Team-wizard governance parity ───────────────────────────────
build_team_with_lifecycle now matches POST /api/claws' governance:
- enforce_new_agent quota check per member (previously bypassed
  workspace agent quotas entirely for team/auto-provision paths).
- audit::append('agent.created', ..., {source: 'team_wizard'}) per
  member so team-created claws appear in the same audit trail as
  individually-created ones. Adding a 'source' key distinguishes
  provenance without changing consumers.

.brain (h5) handling was already consistent between the two paths —
both use the lazy on-first-access load_brain hook seeded from
agents.system_prompt. No change there.
2026-07-17 18:20:56 -07:00
Omar Sobh 956be2cf4f wizard: in-place auto-provision team from topic (LLM-derived, sonnet-5)
ci / gates (push) Successful in 17s
ci / frontend (push) Successful in 36s
ci / rust (push) Successful in 4m17s
ci / e2e (push) Skipped
ci / publish (push) Successful in 4m9s
Step 5 of ResearchWizard was 'assign agents from workspace roster'.
When the roster was empty, the wizard body was hard-swapped for
NoAgentsGate — you couldn't reach step 5 at all.

Now step 5 shows a 'Team' panel:
- Big cyan card: 'Auto-provision team from this topic'. One click
  runs an LLM plan pass, gets 3-5 role slots + system prompts back,
  materializes claws via the existing build_team pipeline, stamps
  runtime posture, returns a shape that drops straight into the
  submit body's agents[]. Card flips green with the derived roster.
- Below that: the classic roster picker, but only when the workspace
  actually has ≥1 claw AND auto-provision hasn't landed. Otherwise
  hidden — no dead empty-state affordance.

Every gate that required agents.length > 0 to render the wizard body
or the footer is gone. canNext gains a step-5 clause: allow Next when
EITHER auto-team is ready OR the user handpicked from a non-empty
roster.

Backend
- POST /api/teams/auto-provision — accepts {title, description,
  outcome_kind, topology_kind?, model?, risk_profile?, mcp_bundles?}.
  Derives topology from outcome_kind (integrations → pipeline; else
  hub_spoke). LLM plan pass yields a JSON roster of 3-5 roles
  (role_slot, name, system_prompt). Materializes team + claws via
  build_team, stamps risk_profile (default research_web_readonly) +
  mcp_bundles (default [clawmates_door, gitea_forge]). Response
  carries team_id + agents[] in the shape /api/research already
  expects.
- Every provisioned claw runs on claude-sonnet-5 by default;
  overridable via the model field.

Follow-ups (not in this slice):
- Same picker in LoopsWizard (slice C — parallel change, same API).
- Post-create 'Team' section on ResearchCanvas / LoopsCanvas so
  users can rebind after the fact (slice D).
- Full Teams tier UI + Agents-page deprecation (slice E).
2026-07-17 16:08:07 -07:00
Omar Sobh 47a42423e3 wizards: unlock '+' buttons when roster is empty
ci / frontend (push) Successful in 49s
ci / publish (push) Successful in 2m42s
ci / gates (push) Successful in 7s
ci / rust (push) Successful in 4m6s
ci / e2e (push) Skipped
The 'no agents → button disabled' guard hard-locked the wizard
behind an already-populated roster, forcing users to detour to the
Agents page and come back. NoAgentsGate already handles the empty
state gracefully inside the wizard body; the button just needed to
open it. Applied to both ResearchList and LoopsList.

Follow-up (design in progress): let the wizard auto-provision a
team from the topic/loop itself so users never have to leave to
create agents.
2026-07-17 13:27:08 -07:00
Omar Sobh eb06e91ac3 dashboard(Agents): drop counts caption, align toolbar with title
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 45s
ci / e2e (push) Skipped
ci / rust (push) Successful in 2m51s
ci / publish (push) Successful in 2m39s
Previously the Agents header wrapped a 'N ORGS · N CO · N TEAMS ·
N AGENTS' small-caps caption above the title, with the toolbar
(select/history/brain) hanging off the right. Dense, noisy, and the
World tier already surfaces those counts at higher fidelity.

Now: single-line header — 'Agents' title on the left, toolbar flush
right, both center-aligned. Comment left in place noting where the
caption used to live in case we ever want it back.
2026-07-17 12:51:53 -07:00
Omar Sobh 0b7f247b0e wizard: 'fresh coding team' picker for paired coding loop
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m4s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m36s
Second slice of the per-loop-team arc. The paired-coding-loop checkbox
in ResearchWizard step 6 now exposes a two-option picker:

  ⦿ Provision a dedicated coding team (default when the loop is on)
     — fresh 'Coding · <topic>' team row, risk_profile =
       coding_readwrite, clawmates_door in mcp_bundles. Loop's
       team_id is bound at wizard-submit time.
  ○ Reuse the research team (legacy) — no team_id bound; coding
     iterations spawn against the research topic's container.

Frontend
- New codingTeamMode state, radio picker rendered under the checkbox.
- research.ts createTopic body gains paired_coding_team_mode?: 'fresh'|
  'reuse'.

Backend
- CreateTopicRequest gains paired_coding_team_mode: Option<String>.
- materialize_topic_loops takes it through and, when 'fresh', calls
  the new provision_fresh_coding_team helper — inserts a teams row
  via the existing insert_team_with_lifecycle (pipeline kind, same
  graph as the loop), sets its runtime-config via
  set_team_runtime_config, then binds loop.team_id.
- All operations best-effort with stderr logging — a team-provision
  failure leaves the loop functional under the legacy fallback.

Not shipped in this slice (deferred to runtime hookup slice):
- research_container::spawn keyed on team_id → per-team container
- Config template rewrite injecting the team's risk_profile
- Migration of existing paired loops onto their own teams

The plumbing lands now so the wizard's intent is recorded; the
runtime honors it in the next PR.
2026-07-16 20:49:54 -07:00
Omar Sobh b2a38da3f9 loops: 'View full output' modal for a completed iteration
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 4m6s
ci / e2e (push) Skipped
ci / publish (push) Successful in 35s
The expanded iteration panel now exposes the full topology_run.result
in a proper viewer instead of leaving it stranded in Postgres. Reads
/api/topology-runs/:id (existing endpoint, no backend change), prefers
result.final_output (the produced markdown/code), falls back to a
per-step transcript, last-resort a raw JSON dump.

Modal renders as a fixed overlay — Escape or backdrop-click to close.
Header carries kind/status/steps/tokens metadata, Copy button hits
navigator.clipboard, Download button emits a .md file named
run-<id-slice>.md. The button on the row is disabled until the
iteration terminates (completed | failed) — running iterations
already have LiveRunLogs surfacing the tail.
2026-07-16 18:05:57 -07:00
Omar Sobh 2ba40b03b7 loops: per-iteration live logs (Steps + Container tabs), collapse graph JSON
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 38s
ci / rust (push) Successful in 3m15s
ci / e2e (push) Skipped
ci / publish (push) Successful in 2m35s
The Loops canvas' IterationsTimeline was a flat status-pill list —
no way to see WHAT an iteration was actually doing. Meanwhile the
Research canvas had full LiveRunLogs with Steps + Container tabs
against the same underlying topology_run SSE endpoints. This
factors LiveRunLogs so both canvases share it.

Frontend
- LiveRunLogs gains a directRunId?: string prop. In this mode it
  skips the topic-scoped active-runs + pipeline-state polls and
  pins activeRun to the given id. topicId stays optional (topic
  mode unchanged from ResearchCanvas' perspective).
- LoopsCanvas IterationsTimeline: each row is now a click-to-
  expand card. On expand, renders <LiveRunLogs directRunId={...}/>
  right below the header — Steps + Container tabs, full SSE tail,
  same 320px terminal.
- Auto-opens the newest running/queued iteration so a click on
  'Run now' immediately exposes the live pane.
- New CollapsibleSection helper wraps the graph JSON block so it
  starts closed. Reference material stays one click away without
  cluttering the canvas.

Backend
- run_container_log_sse now resolves the tail target via
  loop.source_research_topic_id when the run itself has no
  research_topic_id. Paired coding loops (kind=exec with a
  source_research_topic_id) reuse the paired topic's team
  container, so we tail its docker logs. Pure loop runs still
  error with a clearer message.
2026-07-16 17:30:07 -07:00
Omar Sobh ae113dd319 live-run-logs: add Container tab that tails filtered daemon logs
ci / rust (push) Successful in 2m49s
ci / e2e (push) Skipped
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 36s
ci / publish (push) Successful in 3m58s
Steps summaries only fire AFTER each topology step completes — so a
stalled first turn was completely dark. Add a second tab that
streams the team runtime container's daemon log live via a new SSE
endpoint.

Backend
- GET /api/topology-runs/:id/container-log — workspace-scoped SSE
  around bollard's docker.logs(follow=true, tail=200). Buffers on
  newline so partial mux chunks don't truncate a log line.
- compact_container_log: parse a zeroclaw daemon line
  ('[actor] ... zc_action=X zc_outcome=Y ... msg') into
  '[actor] action (outcome) · msg'. Framing-only continuations are
  dropped; non-zc lines (bash echoes, backtraces) pass through as-is
  so nothing interesting is lost. ANSI escapes stripped.
- Everything funnels through one async_stream! so early exits
  (workspace check / docker connect / no bound topic) yield an
  'error' event and return without breaking Sse::new's single stream
  type.

Frontend
- LiveRunLogs gets a sub-tabs strip: Steps · Container.
- New useContainerLog(runId, active) hook — gated by tab so we don't
  hold two open SSE streams when the operator isn't looking.
- Same terminal widget renders each container line with a level
  color (info/done grey, line default, error red). Sub-tab pill
  shows count + status live.
2026-07-15 18:42:03 -07:00
Omar Sobh f70f6c679e research: errored-state card + one-click rerun (no wizard re-entry)
ci / publish (push) Skipped
ci / gates (push) Successful in 22s
ci / frontend (push) Successful in 27s
ci / rust (push) Failing after 58s
ci / e2e (push) Skipped
When a topic ends up parked in 'processing' with all runs failed and
nothing in flight, the sidebar card was still spinning as if
progress were happening. Now:

Backend
- topology_runs::run_counts_by_research_topic — batch query that
  returns (in_flight, failed-since-last-success) per topic. Used by
  the list endpoint; dynamic sqlx::query() so no prepare needed.
- TopicListItem DTO gains runs_in_flight + runs_failed.
- start_topic status guard relaxed: allow (standby) OR (processing
  AND runs_in_flight == 0). Blocks accidental double-fires on a
  live pipeline; permits rerun on a failed one. Same request body,
  same behavior once accepted, so the frontend just POSTs
  /research/:id/start on the RotateCw click.

Frontend
- ResearchList detects errored: status===processing && !in_flight
  && failed>0. Swaps the MiniSpinner for a red AlertTriangle and
  changes the status text to 'error · N failed'.
- New RotateCw icon button next to the delete Trash — same button
  cluster, one click, no wizard re-entry required. Disables while
  a request is in flight; error surfaces in the sidebar's shared
  error banner.
2026-07-15 16:27:43 -07:00
Omar Sobh 43f7880327 agent cards: disk-LED glow on live topology step
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 40s
ci / rust (push) Successful in 3m13s
ci / e2e (push) Skipped
ci / publish (push) Successful in 58s
Wire SSE step events from LiveRunLogs up to ResearchCanvas via an
optional onStep callback (StepPulse: {role, phase, node_id, ts}).
ResearchCanvas keeps a role -> {phase, expiresAt} map; a 250ms tick
clears expired entries so the glow fades naturally.

Each agent card:
- 8px LED dot next to the name (green idle-off / colored when live)
- outer glow via two-layer box-shadow when the agent's role_slot
  matches the last step's role, within a 2.5s window
- inline 'reading' / 'writing' hint below the role

Color mapping (disk-LED metaphor):
- Plan phase   -> cyan  #5ec8d8 (reading / decomposing)
- Work/Synth/Aggregate -> green #5fd08a (writing / producing)

Overlapping steps reset the timer so back-to-back activity on the
same role holds the glow. When no run is active nothing pulses —
LiveRunLogs is silent, no callback fires.
2026-07-15 14:42:57 -07:00
Omar Sobh 45292bf5fb sidebar polish: tighter chevron + '+' drops to Research line
ci / gates (push) Successful in 1m17s
ci / rust (push) Successful in 2m48s
ci / frontend (push) Successful in 1m4s
ci / e2e (push) Skipped
ci / publish (push) Successful in 46s
- Dashboard chevron: 24x24 → 22x22, top:10 right:8 → top:4 right:4
  so it sits in the true corner of the panel.
- ResearchList header: alignItems flex-start → flex-end so the '+'
  button lands on the 'Research' title baseline instead of hugging
  the 'N TOPICS' caption line. Reserved 34px right padding on the
  header so the '+' clears the corner chevron.
2026-07-15 12:59:07 -07:00
Omar Sobh 1876641415 dashboard: collapsible context sidebar (research/loops/repos/infra)
ci / publish (push) Successful in 4m55s
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 43s
ci / rust (push) Successful in 3m33s
ci / e2e (push) Skipped
The earlier PR added a chevron to RosterColumn but the research /
loops / repos / infra tiers render a completely different sidebar
(ResearchList/LoopsList/…) inside a fixed 252px div in Dashboard.tsx
— the RosterColumn toggle never appeared on those pages.

Add a proper collapse on the Dashboard's own context-list wrapper:
- Overlaid PanelLeftClose chevron top-right (top: 10, right: 8) so
  it doesn't fight the per-tier header content.
- When collapsed, wrapper shrinks to 32px with a PanelLeftOpen
  button; canvas gets the reclaimed ~220px.
- State via useSyncExternalStore on localStorage
  ('cm.dashboard.sidebarCollapsed'), same pattern I used on
  RosterColumn to satisfy react-hooks/set-state-in-effect.
2026-07-15 12:06:35 -07:00
Omar Sobh 573956e840 live-run-logs: fix step-record parser + surface pre-first-step phases
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 29s
ci / rust (push) Successful in 3m3s
ci / e2e (push) Skipped
ci / publish (push) Successful in 3m1s
Two fixes based on live smoke test:

- StepRecord parser: the SSE step payload is {node_id, role, phase,
  output, gated, tokens} (orchestrator::StepRecord), not the made-up
  shape my first pass looked for. Render as '[role] phase · <first
  line of output> · Nt · N gated'.

- Pre-first-step visibility: the checkpoint only journals AFTER each
  turn completes, so the container-startup + agent-boot window (often
  30-90s for the first step) was completely dark ('waiting for first
  step…'). Poll pipeline-state every 2s and render its stages as
  pseudo-log lines (setup 🟢 staffing · 3 agents assigned / setup 🔵
  container · starting…) until real step events arrive.
2026-07-15 10:37:33 -07:00
Omar Sobh 2ef50bb30b lint(research): satisfy react-hooks/set-state-in-effect
ci / publish (push) Successful in 4m6s
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 26s
ci / rust (push) Successful in 2m49s
ci / e2e (push) Skipped
- RosterColumn: useSyncExternalStore on localStorage instead of
  hydrate-in-effect setState. Same-tab writes nudge subscribers via
  a synthetic storage event.
- LiveRunLogs: reset-on-runId-change via the React-sanctioned
  set-state-during-render pattern (prevRunId ref state), so the
  effect body no longer calls setState synchronously.
2026-07-15 10:20:29 -07:00
osobh 1d69866bcf research canvas: collapsible sidebar + live topology-run logs (#6)
ci / frontend (push) Failing after 21s
ci / rust (push) Successful in 4m12s
ci / e2e (push) Skipped
ci / publish (push) Skipped
ci / gates (push) Successful in 6s
2026-07-15 17:01:48 +00:00
osobh 149ad992bb wizard: materialize picked repo across clawstor fleet at step-2-next (#5)
ci / frontend (push) Successful in 29s
ci / gates (push) Successful in 5s
ci / rust (push) Failing after 11s
ci / e2e (push) Skipped
ci / publish (push) Skipped
2026-07-15 11:33:19 +00:00