Large World graph, agent platform, brain stack & dashboard rebuild

Frontend
- Large World: collapse org/company/team tiers into one expandable React Flow
  hierarchy (WorldFlow) with per-click expand, persisted node positions, a
  compact tree sidebar, wrench multi-select delete across levels, and a sized
  right slide-out (phone/tablet/full) showing an agent summary + drill button.
- Agent page: GitHub-style animated contribution grid (VitalsCard), collapsible
  System Prompt + Personality cards, restructured anatomy cards, bigger avatar
  with name/title header row, Markdown/JSON-aware rendering, brain registry +
  history, avatar generate/upload.
- User-icon menu (Infrastructure/Brains/Tools/Profile/Credits) + ToolPanel;
  Master Planner deploy wizard (Specialists/Swarm/Scheduled/Triggered);
  Team Runs view; reap-progress modal; dashboard is the single live interface.

Backend
- cm-brain crate (.brain as the agent definition) + brain apply/history.
- Hard-purge reap (FK-ordered) + sandbox release + SSE batch-delete.
- Swarm self-verifying loop, mode-aware planner, web.search tool, webhooks
  (migration 0013), org/company/team delete endpoints, scheduler sweeps.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-22 23:21:54 -07:00
co-authored by Claude Opus 4.8
parent 9f266d5806
commit 34f744734b
123 changed files with 9591 additions and 1098 deletions
+122
View File
@@ -0,0 +1,122 @@
//! Inbound webhook triggers (Triggered deploy mode). A team gets a token whose
//! public URL, when POSTed, enqueues the team's topology run — reusing the same
//! durable-run path as the scheduler and the UI "Run team" button.
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use uuid::Uuid;
use crate::{ApiError, AppState, Authed};
/// `POST /api/teams/{id}/webhooks` — mint a webhook token for the team.
pub async fn create_webhook(
State(state): State<AppState>,
Authed(user): Authed,
Path(team_id): Path<Uuid>,
body: Option<Json<CreateBody>>,
) -> Result<Json<Value>, ApiError> {
// Scope check: the team must belong to the caller's workspace.
cm_db::repo::teams::get_team(&state.pool, team_id, user.workspace_id).await?;
let default_task = body.map(|b| b.0.task).unwrap_or_default();
let id = Uuid::now_v7();
let token = Uuid::now_v7();
sqlx::query("INSERT INTO webhook_tokens (id, workspace_id, team_id, token, task) VALUES ($1, $2, $3, $4, $5)")
.bind(id)
.bind(user.workspace_id.as_uuid())
.bind(team_id)
.bind(token)
.bind(&default_task)
.execute(&state.pool)
.await
.map_err(|_| ApiError::Internal)?;
Ok(Json(json!({ "token": token.to_string(), "url": format!("/api/hooks/{token}") })))
}
#[derive(Deserialize, Default)]
pub struct CreateBody {
#[serde(default)]
pub task: String,
}
/// `GET /api/teams/{id}/webhooks` — list the team's webhook tokens.
pub async fn list_webhooks(
State(state): State<AppState>,
Authed(user): Authed,
Path(team_id): Path<Uuid>,
) -> Result<Json<Value>, ApiError> {
cm_db::repo::teams::get_team(&state.pool, team_id, user.workspace_id).await?;
let rows = sqlx::query_as::<_, (Uuid,)>(
"SELECT token FROM webhook_tokens WHERE team_id = $1 ORDER BY created_at DESC",
)
.bind(team_id)
.fetch_all(&state.pool)
.await
.map_err(|_| ApiError::Internal)?;
let hooks: Vec<Value> = rows
.into_iter()
.map(|(t,)| json!({ "token": t.to_string(), "url": format!("/api/hooks/{t}") }))
.collect();
Ok(Json(json!({ "webhooks": hooks })))
}
#[derive(Deserialize, Default)]
struct HookBody {
#[serde(default)]
task: String,
}
/// `POST /api/hooks/{token}` — PUBLIC. Fire the bound team's topology run. Body
/// `{task?}` overrides the team's default task. Mirrors the Slack/Stripe inbound
/// pattern (no session auth; the unguessable token is the credential).
pub async fn trigger_hook(
State(state): State<AppState>,
Path(token): Path<Uuid>,
body: axum::body::Bytes,
) -> StatusCode {
let row = sqlx::query_as::<_, (Uuid, Uuid, String)>(
"SELECT workspace_id, team_id, task FROM webhook_tokens WHERE token = $1",
)
.bind(token)
.fetch_optional(&state.pool)
.await
.ok()
.flatten();
let Some((ws_id, team_id, default_task)) = row else {
return StatusCode::NOT_FOUND;
};
let ws = cm_domain::WorkspaceId::from(ws_id);
let Ok(team) = cm_db::repo::teams::get_team(&state.pool, team_id, ws).await else {
return StatusCode::NOT_FOUND;
};
let task = serde_json::from_slice::<HookBody>(&body)
.ok()
.map(|b| b.task)
.filter(|t| !t.trim().is_empty())
.or_else(|| (!default_task.trim().is_empty()).then_some(default_task))
.unwrap_or_else(|| "webhook trigger".to_string());
let run_id = Uuid::now_v7();
if cm_db::repo::topology_runs::enqueue_run(&state.pool, run_id, ws, &task, &team.graph)
.await
.is_err()
{
return StatusCode::INTERNAL_SERVER_ERROR;
}
let _ = sqlx::query("UPDATE webhook_tokens SET last_fired_at = now() WHERE token = $1")
.bind(token)
.execute(&state.pool)
.await;
let _ = cm_db::repo::audit::append(
&state.pool,
ws,
cm_db::repo::audit::Actor::System,
"topology.webhook_triggered",
"webhook",
&token.to_string(),
json!({ "team_id": team_id, "run_id": run_id }),
)
.await;
StatusCode::ACCEPTED
}