CI: remove k8s stages, fix the Docker-level pipeline green
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:
co-authored by
Claude Opus 4.8
parent
a36b2c87ac
commit
3554a3aaf2
@@ -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 raw‑API 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(
|
||||
|
||||
Reference in New Issue
Block a user