CI: remove k8s stages, fix the Docker-level pipeline green
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 23s
ci / rust (push) Failing after 27s
ci / e2e (push) Has been skipped

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]>
This commit is contained in:
Omar Sobh
2026-06-26 18:15:31 -07:00
co-authored by Claude Opus 4.8
parent a36b2c87ac
commit 3554a3aaf2
47 changed files with 1264 additions and 446 deletions
+131 -40
View File
@@ -2,11 +2,11 @@ use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::Json;
use std::convert::Infallible;
use cm_db::repo::audit::Actor;
use cm_domain::{AccessPolicy, Agent, AgentId, AgentStatus};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::convert::Infallible;
use crate::runtime_provision::provider_alias_for;
use crate::{ApiError, AppState, Authed};
@@ -241,7 +241,14 @@ pub async fn edit_brain(
// system_prompt stays authoritative in Postgres.
if let Some(sp) = req.system_prompt.as_ref() {
let _ = cm_db::repo::agents::update_profile(
&state.pool, agent.id, None, None, Some(sp.trim()), None, None, None,
&state.pool,
agent.id,
None,
None,
Some(sp.trim()),
None,
None,
None,
)
.await;
}
@@ -292,7 +299,10 @@ pub struct EnhanceRequest {
/// fences, and trailing commentary (balanced-brace scan from the first `{`).
pub(crate) fn extract_json(s: &str) -> Option<Value> {
let t = s.trim();
let t = t.strip_prefix("```json").or_else(|| t.strip_prefix("```")).unwrap_or(t);
let t = t
.strip_prefix("```json")
.or_else(|| t.strip_prefix("```"))
.unwrap_or(t);
let t = t.strip_suffix("```").unwrap_or(t).trim();
if let Ok(v) = serde_json::from_str::<Value>(t) {
return Some(v);
@@ -448,54 +458,110 @@ pub(crate) async fn enhance_and_publish(
reference: &str,
role_context: &str,
) -> Result<String, String> {
let safe: String = reference.chars().map(|c| if c == '/' || c == ':' { '_' } else { c }).collect();
let safe: String = reference
.chars()
.map(|c| if c == '/' || c == ':' { '_' } else { c })
.collect();
let path = brain_dir().join(format!("scaffold_{safe}.h5"));
let _ = std::fs::remove_file(&path);
let pulled = cm_brain::hub::pull(reference, &path).await.map_err(|e| e.to_string())?;
let pulled = cm_brain::hub::pull(reference, &path)
.await
.map_err(|e| e.to_string())?;
let (sp, agent_md, persona, skills) = {
let b = cm_brain::ClawBrain::open_or_create(&path, reference).map_err(|e| e.to_string())?;
(
b.system_prompt().unwrap_or_default(),
b.agent_md().unwrap_or_default(),
b.personality().unwrap_or_default(),
b.skills().into_iter().map(|(n, bd)| format!("## {n}\n{bd}")).collect::<Vec<_>>().join("\n\n"),
b.skills()
.into_iter()
.map(|(n, bd)| format!("## {n}\n{bd}"))
.collect::<Vec<_>>()
.join("\n\n"),
)
};
let user_prompt = format!(
"ROLE CONTEXT: {role_context}\n\nBRAIN: {reference}\n\n=== SYSTEM PROMPT ===\n{sp}\n\n=== AGENTS.md ===\n{agent_md}\n\n=== PERSONA ===\n{persona}\n\n=== SKILLS ===\n{skills}"
);
let raw = runtime.complete(ENHANCE_SYSTEM, &user_prompt, "claude-opus-4-8", 16000, true).await?;
let raw = runtime
.complete(ENHANCE_SYSTEM, &user_prompt, "claude-opus-4-8", 16000, true)
.await?;
let v = extract_json(&raw).ok_or_else(|| "unparseable enhance output".to_string())?;
let enh = v.get("enhanced").cloned().unwrap_or(Value::Null);
let field = |k: &str| enh.get(k).and_then(|x| x.as_str()).unwrap_or("").to_string();
{
let mut b = cm_brain::ClawBrain::open_or_create(&path, reference).map_err(|e| e.to_string())?;
if !field("system_prompt").trim().is_empty() { let _ = b.set_system_prompt(&field("system_prompt")); }
if !field("agent_md").trim().is_empty() { let _ = b.set_agent_md(&field("agent_md")); }
if !field("persona").trim().is_empty() { let _ = b.set_personality(&field("persona")); }
if !field("skills_md").trim().is_empty() { let _ = b.set_skills_md(&field("skills_md")); }
}
let owner = cm_brain::hub::whoami().await.unwrap_or_else(|_| "me".to_string());
let on = pulled.meta.reference.rsplit_once(':').map(|(o, _)| o).unwrap_or(&pulled.meta.reference);
let name = on.rsplit_once('/').map(|(_, n)| n.to_string()).unwrap_or_else(|| on.to_string());
let cur_ver = pulled.meta.reference.rsplit_once(':').map(|(_, vv)| vv.to_string()).unwrap_or_else(|| "1.0.0".to_string());
let new_ref = format!("{owner}/{name}:{}", bump_version(&cur_ver));
let result = match cm_brain::hub::push(&new_ref, &path, "Refined by Master Planner (Opus 4.8)", &[]).await {
Ok(()) => new_ref,
Err(_) => reference.to_string(),
let field = |k: &str| {
enh.get(k)
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string()
};
{
let mut b =
cm_brain::ClawBrain::open_or_create(&path, reference).map_err(|e| e.to_string())?;
if !field("system_prompt").trim().is_empty() {
let _ = b.set_system_prompt(&field("system_prompt"));
}
if !field("agent_md").trim().is_empty() {
let _ = b.set_agent_md(&field("agent_md"));
}
if !field("persona").trim().is_empty() {
let _ = b.set_personality(&field("persona"));
}
if !field("skills_md").trim().is_empty() {
let _ = b.set_skills_md(&field("skills_md"));
}
}
let owner = cm_brain::hub::whoami()
.await
.unwrap_or_else(|_| "me".to_string());
let on = pulled
.meta
.reference
.rsplit_once(':')
.map(|(o, _)| o)
.unwrap_or(&pulled.meta.reference);
let name = on
.rsplit_once('/')
.map(|(_, n)| n.to_string())
.unwrap_or_else(|| on.to_string());
let cur_ver = pulled
.meta
.reference
.rsplit_once(':')
.map(|(_, vv)| vv.to_string())
.unwrap_or_else(|| "1.0.0".to_string());
let new_ref = format!("{owner}/{name}:{}", bump_version(&cur_ver));
let result =
match cm_brain::hub::push(&new_ref, &path, "Refined by Master Planner (Opus 4.8)", &[])
.await
{
Ok(()) => new_ref,
Err(_) => reference.to_string(),
};
let _ = std::fs::remove_file(&path);
Ok(result)
}
/// Attach a brain reference to a freshly-created claw (merge + set its
/// authoritative system prompt). Used by the Master Planner scaffold.
pub(crate) async fn apply_reference_to_claw(state: &AppState, id: AgentId, reference: &str) -> Result<(), String> {
pub(crate) async fn apply_reference_to_claw(
state: &AppState,
id: AgentId,
reference: &str,
) -> Result<(), String> {
let path = brain_dir().join(format!("claw_{id}.h5"));
let pulled = cm_brain::hub::pull_merge(reference, &path).await.map_err(|e| e.to_string())?;
let pulled = cm_brain::hub::pull_merge(reference, &path)
.await
.map_err(|e| e.to_string())?;
if !pulled.system_prompt.trim().is_empty() {
let _ = cm_db::repo::agents::update_profile(
&state.pool, id, None, None, Some(pulled.system_prompt.trim()), None, None, None,
&state.pool,
id,
None,
None,
Some(pulled.system_prompt.trim()),
None,
None,
None,
)
.await;
}
@@ -539,14 +605,20 @@ pub async fn pull_brain(
let agent = Agent {
id,
workspace_id: user.workspace_id,
name: body.name.filter(|s| !s.trim().is_empty()).unwrap_or(pulled.name),
name: body
.name
.filter(|s| !s.trim().is_empty())
.unwrap_or(pulled.name),
job_title: body
.job_title
.filter(|s| !s.trim().is_empty())
.unwrap_or_else(|| "Pulled from ClawBrainHub".into()),
system_prompt: pulled.system_prompt,
avatar: String::new(),
accent: body.accent.filter(|s| !s.trim().is_empty()).unwrap_or_else(|| "#ff6f61".into()),
accent: body
.accent
.filter(|s| !s.trim().is_empty())
.unwrap_or_else(|| "#ff6f61".into()),
wallpaper: String::new(),
managed_by: user.user_id,
status: AgentStatus::Online,
@@ -627,7 +699,9 @@ pub async fn brainhub_search(
Authed(_user): Authed,
Query(query): Query<BrainSearchQuery>,
) -> Result<Json<Vec<cm_brain::hub::BrainListing>>, ApiError> {
Ok(Json(cm_brain::hub::list(&query.q).await.unwrap_or_default()))
Ok(Json(
cm_brain::hub::list(&query.q).await.unwrap_or_default(),
))
}
/// `GET /api/brainhub/preview?ref=owner/name` — overview of a brain's contents
@@ -647,10 +721,13 @@ pub async fn brainhub_preview(
if reference.is_empty() || !reference.contains('/') {
return Err(ApiError::BadRequest);
}
cm_brain::hub::preview(reference).await.map(Json).map_err(|e| {
eprintln!("cm-api: brain preview failed for {reference}: {e}");
ApiError::BadRequest
})
cm_brain::hub::preview(reference)
.await
.map(Json)
.map_err(|e| {
eprintln!("cm-api: brain preview failed for {reference}: {e}");
ApiError::BadRequest
})
}
/// `POST /api/claws/{id}/brain/apply` — pull a brain and inject its contents
@@ -673,10 +750,12 @@ pub async fn apply_brain(
return Err(ApiError::BadRequest);
}
let path = brain_dir().join(format!("claw_{id}.h5"));
let pulled = cm_brain::hub::pull_merge(reference, &path).await.map_err(|e| {
eprintln!("cm-api: brain apply failed for {reference}: {e}");
ApiError::BadRequest
})?;
let pulled = cm_brain::hub::pull_merge(reference, &path)
.await
.map_err(|e| {
eprintln!("cm-api: brain apply failed for {reference}: {e}");
ApiError::BadRequest
})?;
// The assembled identity becomes the agent's authoritative system prompt
// (safe replace — the chat path is rawAPI for every provider).
if !pulled.system_prompt.trim().is_empty() {
@@ -749,13 +828,25 @@ pub async fn brain_rollback(
) -> Result<Json<Value>, ApiError> {
workspace_agent(&state, &user, id).await?;
let path = brain_dir().join(format!("claw_{id}.h5"));
let b = cm_brain::ClawBrain::open_or_create(&path, &id.to_string()).map_err(|_| ApiError::Internal)?;
b.rollback(body.revision).map_err(|_| ApiError::BadRequest)?;
let b = cm_brain::ClawBrain::open_or_create(&path, &id.to_string())
.map_err(|_| ApiError::Internal)?;
b.rollback(body.revision)
.map_err(|_| ApiError::BadRequest)?;
// Re-open the rolled-back brain and restore its identity as the live prompt.
if let Ok(reb) = cm_brain::ClawBrain::open_or_create(&path, &id.to_string()) {
let sp = reb.assembled_identity();
if !sp.trim().is_empty() {
let _ = cm_db::repo::agents::update_profile(&state.pool, id, None, None, Some(sp.trim()), None, None, None).await;
let _ = cm_db::repo::agents::update_profile(
&state.pool,
id,
None,
None,
Some(sp.trim()),
None,
None,
None,
)
.await;
}
}
cm_db::repo::audit::append(