style: rustfmt the four files the repo-less mission fix touched
Found while removing .github/workflows/ci.yml: three of the four files in that change were unformatted, and four of the diffs were newly introduced (the new prompt tests and the tool_preamble format! call). Formatting only the files that change already touched — a repo-wide `cargo fmt` would be 63 files of unrelated churn and belongs in its own commit. Mechanical; `cargo test -p cm-api --lib` stays at 322 passed. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
022ef98e44
commit
548f977212
@@ -280,7 +280,11 @@ async fn register_empty_marker(
|
|||||||
/// the end of the turn (`microvm_executor` writes them over
|
/// the end of the turn (`microvm_executor` writes them over
|
||||||
/// `mission_workspace::checkout_path`). Asking docker for a VM mission's files
|
/// `mission_workspace::checkout_path`). Asking docker for a VM mission's files
|
||||||
/// would query a container that never existed.
|
/// would query a container that never existed.
|
||||||
async fn collect_into(mission_id: Uuid, dest: &Path, runtime_kind: &str) -> Result<Vec<PathBuf>, String> {
|
async fn collect_into(
|
||||||
|
mission_id: Uuid,
|
||||||
|
dest: &Path,
|
||||||
|
runtime_kind: &str,
|
||||||
|
) -> Result<Vec<PathBuf>, String> {
|
||||||
// A stale copy from an earlier attempt would be registered as this pass's
|
// A stale copy from an earlier attempt would be registered as this pass's
|
||||||
// output — the same "captured a tree nobody wrote" shape capture avoids.
|
// output — the same "captured a tree nobody wrote" shape capture avoids.
|
||||||
let _ = std::fs::remove_dir_all(dest);
|
let _ = std::fs::remove_dir_all(dest);
|
||||||
@@ -308,8 +312,7 @@ async fn collect_into(mission_id: Uuid, dest: &Path, runtime_kind: &str) -> Resu
|
|||||||
/// shell-out, and this runs as the server's own uid against its own directory.
|
/// shell-out, and this runs as the server's own uid against its own directory.
|
||||||
fn copy_tree(src: &Path, dest: &Path) -> Result<(), String> {
|
fn copy_tree(src: &Path, dest: &Path) -> Result<(), String> {
|
||||||
std::fs::create_dir_all(dest).map_err(|e| format!("create {}: {e}", dest.display()))?;
|
std::fs::create_dir_all(dest).map_err(|e| format!("create {}: {e}", dest.display()))?;
|
||||||
let entries =
|
let entries = std::fs::read_dir(src).map_err(|e| format!("read {}: {e}", src.display()))?;
|
||||||
std::fs::read_dir(src).map_err(|e| format!("read {}: {e}", src.display()))?;
|
|
||||||
for entry in entries.flatten() {
|
for entry in entries.flatten() {
|
||||||
let from = entry.path();
|
let from = entry.path();
|
||||||
let to = dest.join(entry.file_name());
|
let to = dest.join(entry.file_name());
|
||||||
@@ -466,9 +469,7 @@ mod tests {
|
|||||||
|
|
||||||
// ...and the traversal shape this guards against is not, once resolved.
|
// ...and the traversal shape this guards against is not, once resolved.
|
||||||
let escaped = root.join("..").join("..").join("etc/passwd");
|
let escaped = root.join("..").join("..").join("etc/passwd");
|
||||||
let normalised: PathBuf = escaped
|
let normalised: PathBuf = escaped.components().fold(PathBuf::new(), |mut acc, c| {
|
||||||
.components()
|
|
||||||
.fold(PathBuf::new(), |mut acc, c| {
|
|
||||||
match c {
|
match c {
|
||||||
std::path::Component::ParentDir => {
|
std::path::Component::ParentDir => {
|
||||||
acc.pop();
|
acc.pop();
|
||||||
|
|||||||
@@ -361,8 +361,6 @@ use crate::container_exec::MISSION_UID;
|
|||||||
|
|
||||||
const DEFAULT_SEED_DIR: &str = "/root/clawmates-runtime/data";
|
const DEFAULT_SEED_DIR: &str = "/root/clawmates-runtime/data";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// What a mission gets its own copy of.
|
/// What a mission gets its own copy of.
|
||||||
///
|
///
|
||||||
/// An allow-list, not the whole directory. The seed dir is **1.7 GB** on
|
/// An allow-list, not the whole directory. The seed dir is **1.7 GB** on
|
||||||
@@ -1466,7 +1464,10 @@ mod tests {
|
|||||||
"GROQ_API_KEY" => Some("real".into()),
|
"GROQ_API_KEY" => Some("real".into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
assert_eq!(pairs, vec![("GROQ_API_KEY".to_string(), "real".to_string())]);
|
assert_eq!(
|
||||||
|
pairs,
|
||||||
|
vec![("GROQ_API_KEY".to_string(), "real".to_string())]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1505,8 +1506,8 @@ mod tests {
|
|||||||
fn the_two_anthropic_credentials_are_mutually_exclusive() {
|
fn the_two_anthropic_credentials_are_mutually_exclusive() {
|
||||||
for mode in [RuntimeAuth::ApiKey, RuntimeAuth::Subscription] {
|
for mode in [RuntimeAuth::ApiKey, RuntimeAuth::Subscription] {
|
||||||
let keys = forwarded_provider_keys(mode);
|
let keys = forwarded_provider_keys(mode);
|
||||||
let both = keys.contains(&"ANTHROPIC_API_KEY")
|
let both =
|
||||||
&& keys.contains(&"CLAUDE_CODE_OAUTH_TOKEN");
|
keys.contains(&"ANTHROPIC_API_KEY") && keys.contains(&"CLAUDE_CODE_OAUTH_TOKEN");
|
||||||
assert!(!both, "{mode:?} forwards both credentials: {keys:?}");
|
assert!(!both, "{mode:?} forwards both credentials: {keys:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1761,7 +1762,10 @@ allowed_tools = ["file_read", "file_edit"]
|
|||||||
let script = copy_script();
|
let script = copy_script();
|
||||||
// A missing entry is normal on a fresh deployment (no .kimi-code
|
// A missing entry is normal on a fresh deployment (no .kimi-code
|
||||||
// until Kimi is first used) and must not fail the copy.
|
// until Kimi is first used) and must not fail the copy.
|
||||||
assert!(script.contains("if [ -e "), "absent paths must be tolerated");
|
assert!(
|
||||||
|
script.contains("if [ -e "),
|
||||||
|
"absent paths must be tolerated"
|
||||||
|
);
|
||||||
assert!(script.contains("/seed/.zeroclaw"));
|
assert!(script.contains("/seed/.zeroclaw"));
|
||||||
assert!(!script.contains("/seed/.rustup"));
|
assert!(!script.contains("/seed/.rustup"));
|
||||||
for p in SEEDED_PATHS {
|
for p in SEEDED_PATHS {
|
||||||
|
|||||||
@@ -180,9 +180,8 @@ async fn capture_finished_coding_phases(pool: &PgPool) -> Result<(), String> {
|
|||||||
delivered NO files — failing it. Set config.allow_empty = true if \
|
delivered NO files — failing it. Set config.allow_empty = true if \
|
||||||
this phase is meant to verify rather than change."
|
this phase is meant to verify rather than change."
|
||||||
);
|
);
|
||||||
if let Err(e) = sqlx::query(
|
if let Err(e) =
|
||||||
"UPDATE mission_phases SET status = 'failed' WHERE id = $1",
|
sqlx::query("UPDATE mission_phases SET status = 'failed' WHERE id = $1")
|
||||||
)
|
|
||||||
.bind(phase_id)
|
.bind(phase_id)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await
|
||||||
@@ -239,7 +238,10 @@ mod repo_less_text_tests {
|
|||||||
);
|
);
|
||||||
// And it must say what DOES happen to the files, or an agent told only
|
// And it must say what DOES happen to the files, or an agent told only
|
||||||
// "there is no repo" has no reason to write them to disk at all.
|
// "there is no repo" has no reason to write them to disk at all.
|
||||||
assert!(without.contains("published as a mission artifact"), "{without}");
|
assert!(
|
||||||
|
without.contains("published as a mission artifact"),
|
||||||
|
"{without}"
|
||||||
|
);
|
||||||
assert!(without.contains("NO git repository"), "{without}");
|
assert!(without.contains("NO git repository"), "{without}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +252,10 @@ mod repo_less_text_tests {
|
|||||||
fn both_variants_still_demand_files_on_disk() {
|
fn both_variants_still_demand_files_on_disk() {
|
||||||
for has_repo in [true, false] {
|
for has_repo in [true, false] {
|
||||||
let t = phase_task_text("research", "T", None, None, has_repo);
|
let t = phase_task_text("research", "T", None, None, has_repo);
|
||||||
assert!(t.contains("REAL files with Write/Edit"), "has_repo={has_repo}: {t}");
|
assert!(
|
||||||
|
t.contains("REAL files with Write/Edit"),
|
||||||
|
"has_repo={has_repo}: {t}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +271,12 @@ mod repo_less_text_tests {
|
|||||||
for real in ["Read", "Edit", "Write", "Bash", "Glob", "Grep"] {
|
for real in ["Read", "Edit", "Write", "Bash", "Glob", "Grep"] {
|
||||||
assert!(t.contains(real), "missing {real}: {t}");
|
assert!(t.contains(real), "missing {real}: {t}");
|
||||||
}
|
}
|
||||||
for absent in ["file_edit", "content_search", "glob_search", "git_operations"] {
|
for absent in [
|
||||||
|
"file_edit",
|
||||||
|
"content_search",
|
||||||
|
"glob_search",
|
||||||
|
"git_operations",
|
||||||
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
!t.contains(absent),
|
!t.contains(absent),
|
||||||
"{absent} does not exist under claude_cli — advertising it is the bug: {t}"
|
"{absent} does not exist under claude_cli — advertising it is the bug: {t}"
|
||||||
@@ -653,8 +663,8 @@ async fn launch_phase(
|
|||||||
// and then sit idle for the life of the mission — while the pairing code and
|
// and then sit idle for the life of the mission — while the pairing code and
|
||||||
// runtime binding it writes describe a runtime nothing is using.
|
// runtime binding it writes describe a runtime nothing is using.
|
||||||
let needs_container = p.runtime_kind != "microvm";
|
let needs_container = p.runtime_kind != "microvm";
|
||||||
if let Some(prov) = crate::mission_runtime::MissionRuntimeProvisioner::from_env()
|
if let Some(prov) =
|
||||||
.filter(|_| needs_container)
|
crate::mission_runtime::MissionRuntimeProvisioner::from_env().filter(|_| needs_container)
|
||||||
{
|
{
|
||||||
match prov.ensure_container(mission_id).await {
|
match prov.ensure_container(mission_id).await {
|
||||||
Ok(ec) => {
|
Ok(ec) => {
|
||||||
@@ -813,7 +823,9 @@ async fn launch_phase(
|
|||||||
capacity_note = $2 WHERE id = $1",
|
capacity_note = $2 WHERE id = $1",
|
||||||
)
|
)
|
||||||
.bind(phase_id)
|
.bind(phase_id)
|
||||||
.bind(format!("waited {blocked_for:.0}s for fleet capacity:\n{msg}"))
|
.bind(format!(
|
||||||
|
"waited {blocked_for:.0}s for fleet capacity:\n{msg}"
|
||||||
|
))
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await;
|
.await;
|
||||||
return Err(format!("phase {phase_id} exhausted its capacity wait"));
|
return Err(format!("phase {phase_id} exhausted its capacity wait"));
|
||||||
@@ -1174,9 +1186,8 @@ async fn launch_microvm_phase(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if !has_repo {
|
if !has_repo {
|
||||||
std::fs::create_dir_all(&repo).map_err(|e| {
|
std::fs::create_dir_all(&repo)
|
||||||
format!("create empty workspace {}: {e}", repo.display())
|
.map_err(|e| format!("create empty workspace {}: {e}", repo.display()))?;
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
crate::microvm_executor::run_phase_in_vm(
|
crate::microvm_executor::run_phase_in_vm(
|
||||||
&hub,
|
&hub,
|
||||||
@@ -1212,19 +1223,28 @@ async fn launch_microvm_phase(
|
|||||||
// Counted from the guest's own per-subagent transcripts. "-" means the
|
// Counted from the guest's own per-subagent transcripts. "-" means the
|
||||||
// probe could not run, which is not the same as "it delegated to nobody".
|
// probe could not run, which is not the same as "it delegated to nobody".
|
||||||
let subagents = match &outcome {
|
let subagents = match &outcome {
|
||||||
Ok(o) => o.subagents.map(|n| n.to_string()).unwrap_or_else(|| "?".into()),
|
Ok(o) => o
|
||||||
|
.subagents
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.unwrap_or_else(|| "?".into()),
|
||||||
Err(_) => "-".into(),
|
Err(_) => "-".into(),
|
||||||
};
|
};
|
||||||
// Only meaningful for a mission that asked for a team; "-" otherwise.
|
// Only meaningful for a mission that asked for a team; "-" otherwise.
|
||||||
let teammates = match &outcome {
|
let teammates = match &outcome {
|
||||||
Ok(o) => o.teammates.map(|n| n.to_string()).unwrap_or_else(|| "-".into()),
|
Ok(o) => o
|
||||||
|
.teammates
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.unwrap_or_else(|| "-".into()),
|
||||||
Err(_) => "-".into(),
|
Err(_) => "-".into(),
|
||||||
};
|
};
|
||||||
// How often the completion gate sent the agent back inside its own turn.
|
// How often the completion gate sent the agent back inside its own turn.
|
||||||
// "-" is no gate; a number is how many second chances it took, which is
|
// "-" is no gate; a number is how many second chances it took, which is
|
||||||
// the whole measurement of whether the gate is worth its hook.
|
// the whole measurement of whether the gate is worth its hook.
|
||||||
let blocked = match &outcome {
|
let blocked = match &outcome {
|
||||||
Ok(o) => o.stop_blocks.map(|n| n.to_string()).unwrap_or_else(|| "-".into()),
|
Ok(o) => o
|
||||||
|
.stop_blocks
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.unwrap_or_else(|| "-".into()),
|
||||||
Err(_) => "-".into(),
|
Err(_) => "-".into(),
|
||||||
};
|
};
|
||||||
// What the agent touched inside the VM, recorded before the outcome is
|
// What the agent touched inside the VM, recorded before the outcome is
|
||||||
@@ -1260,7 +1280,10 @@ async fn launch_microvm_phase(
|
|||||||
// is nothing for delivery to find.
|
// is nothing for delivery to find.
|
||||||
Ok(o) if !o.collected => (
|
Ok(o) if !o.collected => (
|
||||||
"failed",
|
"failed",
|
||||||
format!("the agent's work could not be collected from the VM: {}", o.summary),
|
format!(
|
||||||
|
"the agent's work could not be collected from the VM: {}",
|
||||||
|
o.summary
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Ok(o) => ("failed", o.summary),
|
Ok(o) => ("failed", o.summary),
|
||||||
Err(e) => ("failed", e),
|
Err(e) => ("failed", e),
|
||||||
@@ -1398,9 +1421,7 @@ async fn launch_direct_session(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eprintln!(
|
eprintln!("phase_runner: mission {mission_id} phase {phase_id} launched as a DIRECT SESSION");
|
||||||
"phase_runner: mission {mission_id} phase {phase_id} launched as a DIRECT SESSION"
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1432,7 +1453,8 @@ fn phase_task_text(
|
|||||||
// ones absent, and Bash was one of the ones present. Agents answered by
|
// ones absent, and Bash was one of the ones present. Agents answered by
|
||||||
// describing the mismatch and asking what to do — five of them, on one
|
// describing the mismatch and asking what to do — five of them, on one
|
||||||
// mission, for 7.4k tokens and zero artifacts.
|
// mission, for 7.4k tokens and zero artifacts.
|
||||||
let tool_preamble = format!("\
|
let tool_preamble = format!(
|
||||||
|
"\
|
||||||
TOOLS AVAILABLE (Claude Code's standard tools — use these exact names):\n\
|
TOOLS AVAILABLE (Claude Code's standard tools — use these exact names):\n\
|
||||||
- Read — read a file\n\
|
- Read — read a file\n\
|
||||||
- Edit — modify an existing file\n\
|
- Edit — modify an existing file\n\
|
||||||
@@ -1625,7 +1647,10 @@ async fn mark_phase_running(pool: &PgPool, mission_id: Uuid, phase_id: Uuid) ->
|
|||||||
if claimed.is_some() {
|
if claimed.is_some() {
|
||||||
crate::mission_events::record(
|
crate::mission_events::record(
|
||||||
pool,
|
pool,
|
||||||
crate::mission_events::MissionEvent::new(mission_id, crate::mission_events::PHASE_STARTED)
|
crate::mission_events::MissionEvent::new(
|
||||||
|
mission_id,
|
||||||
|
crate::mission_events::PHASE_STARTED,
|
||||||
|
)
|
||||||
.phase(phase_id),
|
.phase(phase_id),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
@@ -2151,16 +2176,28 @@ mod tests {
|
|||||||
let alpha = phase_task_text("coding", "Demo", brief, Some("Create ALPHA.md"), true);
|
let alpha = phase_task_text("coding", "Demo", brief, Some("Create ALPHA.md"), true);
|
||||||
let beta = phase_task_text("coding", "Demo", brief, Some("Create BETA.md"), true);
|
let beta = phase_task_text("coding", "Demo", brief, Some("Create BETA.md"), true);
|
||||||
|
|
||||||
assert!(alpha.contains("Create ALPHA.md"), "phase task must be injected");
|
assert!(
|
||||||
|
alpha.contains("Create ALPHA.md"),
|
||||||
|
"phase task must be injected"
|
||||||
|
);
|
||||||
assert!(beta.contains("Create BETA.md"));
|
assert!(beta.contains("Create BETA.md"));
|
||||||
assert!(!alpha.contains("BETA.md"), "a phase must not see its sibling's task");
|
assert!(
|
||||||
assert_ne!(alpha, beta, "sibling phases received identical instructions");
|
!alpha.contains("BETA.md"),
|
||||||
|
"a phase must not see its sibling's task"
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
alpha, beta,
|
||||||
|
"sibling phases received identical instructions"
|
||||||
|
);
|
||||||
|
|
||||||
// A phase with no task of its own is unchanged from before the fix.
|
// A phase with no task of its own is unchanged from before the fix.
|
||||||
let bare = phase_task_text("coding", "Demo", brief, None, true);
|
let bare = phase_task_text("coding", "Demo", brief, None, true);
|
||||||
assert!(!bare.contains("THIS PHASE'S TASK"));
|
assert!(!bare.contains("THIS PHASE'S TASK"));
|
||||||
// Empty and whitespace-only configs take the same path as absent.
|
// Empty and whitespace-only configs take the same path as absent.
|
||||||
assert_eq!(bare, phase_task_text("coding", "Demo", brief, Some(" "), true));
|
assert_eq!(
|
||||||
|
bare,
|
||||||
|
phase_task_text("coding", "Demo", brief, Some(" "), true)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The marker syntax we hand the agent must be the syntax we parse back.
|
/// The marker syntax we hand the agent must be the syntax we parse back.
|
||||||
|
|||||||
Reference in New Issue
Block a user