Compare commits
2
Commits
6e8785f159
...
fd5e71ccfe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd5e71ccfe | ||
|
|
85a6038c08 |
@@ -272,6 +272,29 @@ pub async fn sync_in(container: &str, mission_id: uuid::Uuid) -> Result<(), Stri
|
|||||||
.map_err(|e| format!("create empty workspace {}: {e}", repo.display()))?;
|
.map_err(|e| format!("create empty workspace {}: {e}", repo.display()))?;
|
||||||
}
|
}
|
||||||
let docker = crate::container_exec::connect()?;
|
let docker = crate::container_exec::connect()?;
|
||||||
|
// `upload_to_container` requires the DESTINATION to exist: uploading into
|
||||||
|
// `/mission` when the container has no `/mission` fails with
|
||||||
|
// "404 Could not find the file /mission in container", which reads like a
|
||||||
|
// missing source file rather than a missing target directory. Nothing else
|
||||||
|
// creates it — not the image, not the container spec (in copy mode there is
|
||||||
|
// no `/mission` bind) — so create it here, immediately before the copy that
|
||||||
|
// depends on it.
|
||||||
|
let mkdir = [
|
||||||
|
"mkdir".to_string(),
|
||||||
|
"-p".to_string(),
|
||||||
|
CONTAINER_MISSION_DIR.to_string(),
|
||||||
|
];
|
||||||
|
if let Err(e) = crate::container_exec::exec_as_root(
|
||||||
|
&docker,
|
||||||
|
container,
|
||||||
|
None,
|
||||||
|
&mkdir,
|
||||||
|
std::time::Duration::from_secs(20),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err(format!("create {CONTAINER_MISSION_DIR} in {container}: {e}"));
|
||||||
|
}
|
||||||
copy_in(&docker, container, &repo, "repo").await
|
copy_in(&docker, container, &repo, "repo").await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -678,6 +678,73 @@ async fn launch_phase(
|
|||||||
return Err(format!("copy checkout into {name}: {e}"));
|
return Err(format!("copy checkout into {name}: {e}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Re-assert this mission's crew in the runtime config.
|
||||||
|
//
|
||||||
|
// Claws are provisioned once, at on_launch. `ensure_container`
|
||||||
|
// RECREATES a container that is not running, and recreation
|
||||||
|
// reseeds `.zeroclaw` from the seed directory — which does not
|
||||||
|
// contain this mission's claws. The agents still exist in
|
||||||
|
// Postgres, so nothing looks wrong, but the alias the turn
|
||||||
|
// dials is gone from the daemon and `/ws/chat` answers
|
||||||
|
// `400 Bad Request`. That is what a retry of mission 01a00538
|
||||||
|
// hit after its container was recreated.
|
||||||
|
//
|
||||||
|
// provision_claw is idempotent (created:false when present), so
|
||||||
|
// this costs one call per claw on the happy path and is the
|
||||||
|
// difference between a resumable mission and a dead one.
|
||||||
|
// Aim at THIS MISSION's daemon, never the global gateway. Each
|
||||||
|
// mission's turns run against its own container, which loads
|
||||||
|
// config at boot and never re-reads the file, so provisioning
|
||||||
|
// against `from_env()` writes the claws into the shared runtime
|
||||||
|
// and leaves this one with none — the failure mode
|
||||||
|
// `RuntimeProvisioner::for_gateway`'s doc comment describes, and
|
||||||
|
// the one this block exists to repair.
|
||||||
|
if let Some(rp) =
|
||||||
|
crate::runtime_provision::RuntimeProvisioner::for_gateway(ec.endpoint.clone())
|
||||||
|
{
|
||||||
|
let crew = sqlx::query(
|
||||||
|
"SELECT DISTINCT a.id, a.model_binding, t.risk_profile
|
||||||
|
FROM team_members tm
|
||||||
|
JOIN mission_teams mt ON mt.team_id = tm.team_id
|
||||||
|
JOIN teams t ON t.id = tm.team_id
|
||||||
|
JOIN agents a ON a.id = tm.claw_id
|
||||||
|
WHERE mt.mission_id = $1 AND a.deleted_at IS NULL",
|
||||||
|
)
|
||||||
|
.bind(mission_id)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut reasserted: Vec<cm_domain::AgentId> = Vec::new();
|
||||||
|
for row in &crew {
|
||||||
|
let aid: uuid::Uuid = row.get("id");
|
||||||
|
let model: Option<String> = row.get("model_binding");
|
||||||
|
let risk: Option<String> = row.get("risk_profile");
|
||||||
|
match rp
|
||||||
|
.provision_claw(
|
||||||
|
aid,
|
||||||
|
model.as_deref().unwrap_or("claude"),
|
||||||
|
risk.as_deref().unwrap_or("research_readonly"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => reasserted.push(cm_domain::AgentId::from(aid)),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
"phase_runner: re-provision claw {aid} for {mission_id} failed \
|
||||||
|
(continuing): {e}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Provisioning creates the agent; it does NOT set
|
||||||
|
// workspace.path (the prop-schema cannot). Without this the
|
||||||
|
// claws exist and every one of them runs in its own sandbox
|
||||||
|
// instead of the mission tree.
|
||||||
|
if let Err(e) = prov
|
||||||
|
.pin_agent_workspaces(mission_id, &reasserted, "/mission/repo")
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Err(format!("re-pin mission workspaces: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Err(e) = cm_db::repo::missions::set_runtime_binding(
|
if let Err(e) = cm_db::repo::missions::set_runtime_binding(
|
||||||
pool,
|
pool,
|
||||||
mission_id,
|
mission_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user