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:
co-authored by
Claude Opus 4.8
parent
9f266d5806
commit
34f744734b
@@ -256,3 +256,89 @@ pub async fn soft_delete(pool: &PgPool, agent_id: AgentId) -> Result<(), DbError
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Counts of the rows reaped by [`hard_purge`], for the progress summary.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct PurgeCounts {
|
||||
pub sessions: u64,
|
||||
pub approvals: u64,
|
||||
pub connections: u64,
|
||||
pub files: u64,
|
||||
}
|
||||
|
||||
/// Hard-delete an agent and everything that references it, in FK-dependency
|
||||
/// order, in one transaction. Auto-CASCADE handles access_policies,
|
||||
/// installed_skills, routines(+routine_runs) and team_members; the non-cascade
|
||||
/// references (chat history, approvals, threads, connections, queued mail, file
|
||||
/// drives, usage) are cleared first so the final `DELETE FROM agents` succeeds.
|
||||
/// Only the immutable `audit_log` survives. Returns NotFound if the agent is gone.
|
||||
pub async fn hard_purge(pool: &PgPool, agent_id: AgentId) -> Result<PurgeCounts, DbError> {
|
||||
let aid = agent_id.as_uuid();
|
||||
let mut tx = pool.begin().await?;
|
||||
let mut c = PurgeCounts::default();
|
||||
|
||||
// Chat history: steps → messages → agent_runs → sessions (none cascade).
|
||||
sqlx::query(
|
||||
"DELETE FROM steps WHERE message_id IN \
|
||||
(SELECT m.id FROM messages m JOIN sessions s ON m.session_id = s.id WHERE s.agent_id = $1)",
|
||||
)
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query("DELETE FROM messages WHERE session_id IN (SELECT id FROM sessions WHERE agent_id = $1)")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query("DELETE FROM agent_runs WHERE session_id IN (SELECT id FROM sessions WHERE agent_id = $1)")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
c.sessions = sqlx::query("DELETE FROM sessions WHERE agent_id = $1")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
// Approvals: execution_grants → approvals.
|
||||
sqlx::query(
|
||||
"DELETE FROM execution_grants WHERE approval_id IN \
|
||||
(SELECT id FROM approvals WHERE requested_by_agent = $1)",
|
||||
)
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
c.approvals = sqlx::query("DELETE FROM approvals WHERE requested_by_agent = $1")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
// Inter-agent threads, queued mail, oauth flows.
|
||||
sqlx::query("DELETE FROM thread_messages WHERE from_agent = $1").bind(aid).execute(&mut *tx).await?;
|
||||
sqlx::query("DELETE FROM thread_participants WHERE agent_id = $1").bind(aid).execute(&mut *tx).await?;
|
||||
sqlx::query("DELETE FROM outbox WHERE agent_id = $1").bind(aid).execute(&mut *tx).await?;
|
||||
sqlx::query("DELETE FROM oauth_states WHERE agent_id = $1").bind(aid).execute(&mut *tx).await?;
|
||||
c.connections = sqlx::query("DELETE FROM app_connections WHERE agent_id = $1")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
c.files = sqlx::query("DELETE FROM file_nodes WHERE agent_id = $1")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
sqlx::query("DELETE FROM usage_events WHERE agent_id = $1").bind(aid).execute(&mut *tx).await?;
|
||||
|
||||
// Finally the agent itself (cascades the rest).
|
||||
let n = sqlx::query("DELETE FROM agents WHERE id = $1")
|
||||
.bind(aid)
|
||||
.execute(&mut *tx)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if n == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
tx.commit().await?;
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,45 @@ pub async fn insert_company(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rebuild a company's topology (kind + graph) in place; node→team bindings
|
||||
/// (keyed off stable node ids `n0..`) are left untouched. Non-macro query.
|
||||
pub async fn set_topology(
|
||||
pool: &PgPool,
|
||||
id: Uuid,
|
||||
workspace_id: WorkspaceId,
|
||||
kind: &str,
|
||||
graph: &Value,
|
||||
) -> Result<(), DbError> {
|
||||
let res = sqlx::query("UPDATE companies SET kind = $3, graph = $4 WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(workspace_id.as_uuid())
|
||||
.bind(kind)
|
||||
.bind(graph)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if res.rows_affected() == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a company and its node→team bindings (teams themselves remain).
|
||||
pub async fn delete_company(pool: &PgPool, id: Uuid, workspace_id: WorkspaceId) -> Result<(), DbError> {
|
||||
sqlx::query("DELETE FROM company_teams WHERE company_id = $1")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
let res = sqlx::query("DELETE FROM companies WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(workspace_id.as_uuid())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if res.rows_affected() == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bind a team to a topology node within a company.
|
||||
pub async fn add_team(
|
||||
pool: &PgPool,
|
||||
|
||||
@@ -146,3 +146,21 @@ pub async fn companies_for_org(pool: &PgPool, org_id: Uuid) -> Result<Vec<OrgCom
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Delete an org. Structural only: the `org_companies` links are removed
|
||||
/// (cascade) so the companies survive, just ungrouped from this org.
|
||||
pub async fn delete_org(pool: &PgPool, id: Uuid, workspace_id: WorkspaceId) -> Result<(), DbError> {
|
||||
sqlx::query("DELETE FROM org_companies WHERE org_id = $1")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
let res = sqlx::query("DELETE FROM orgs WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(workspace_id.as_uuid())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if res.rows_affected() == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -59,6 +59,46 @@ pub async fn insert_team(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rebuild a team's topology (kind + graph) in place; node→claw bindings (which
|
||||
/// key off stable node ids `n0..`) are left untouched. Non-macro query so it
|
||||
/// needs no offline sqlx cache entry.
|
||||
pub async fn set_topology(
|
||||
pool: &PgPool,
|
||||
id: Uuid,
|
||||
workspace_id: WorkspaceId,
|
||||
kind: &str,
|
||||
graph: &Value,
|
||||
) -> Result<(), DbError> {
|
||||
let res = sqlx::query("UPDATE teams SET kind = $3, graph = $4 WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(workspace_id.as_uuid())
|
||||
.bind(kind)
|
||||
.bind(graph)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if res.rows_affected() == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a team and its node→claw bindings.
|
||||
pub async fn delete_team(pool: &PgPool, id: Uuid, workspace_id: WorkspaceId) -> Result<(), DbError> {
|
||||
sqlx::query("DELETE FROM team_members WHERE team_id = $1")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
let res = sqlx::query("DELETE FROM teams WHERE id = $1 AND workspace_id = $2")
|
||||
.bind(id)
|
||||
.bind(workspace_id.as_uuid())
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if res.rows_affected() == 0 {
|
||||
return Err(DbError::NotFound);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bind a claw to a topology node within a team.
|
||||
pub async fn add_member(
|
||||
pool: &PgPool,
|
||||
|
||||
Reference in New Issue
Block a user