use anyhow::Result;
fn launch_job(
content: String,
workspace_name: String,
launch: crate::Launch,
) -> crate::agent::message_router::AgentJob {
crate::agent::message_router::AgentJob {
content,
workspace_name,
user_name: crate::users::ADMIN_USER_NAME.to_string(),
channel: "gui".to_string(),
kind: crate::agent::message_router::MessageKind::UserMessage,
role: launch.role,
reply_target: None,
pending_job_id: None,
originating_workspace: None,
custom_role: Some(launch),
reply_to_custom: None,
}
}
pub(crate) async fn custom_role(
name: &str,
message: &str,
branch: Option<&str>,
place: Option<&str>,
answered_for: crate::agent::message_router::AnsweredFor,
) -> Result<String> {
anyhow::ensure!(
!message.trim().is_empty(),
"usage: the launch message must not be empty"
);
let role = crate::custom_role::get(name)
.await?
.ok_or_else(|| anyhow::anyhow!("no custom role named '{name}'"))?;
let identifier = branch.map(str::trim).filter(|id| !id.is_empty());
if let Some(id) = identifier {
crate::custom_role::validate_identifier(id)?;
}
let workspace_name = match place.map(str::trim).filter(|w| !w.is_empty()) {
None => crate::users::personal_workspace_name(crate::users::ADMIN_USER_NAME),
Some(raw) => resolve_launch_workspace(raw).await?,
};
let agent_id = match identifier {
Some(id) => crate::session::custom_role_agent_id(name, Some(id)),
None => match role.behavior {
crate::custom_role::CustomRoleBehavior::Continuous => {
crate::session::custom_role_agent_id(name, None)
}
crate::custom_role::CustomRoleBehavior::OneOff => {
crate::session::custom_role_run_agent_id(name)
}
},
};
let launch = crate::Launch {
role: crate::Role::Custom,
name: name.to_string(),
identifier: identifier.map(str::to_string),
prompt: role.prompt,
tools: role.tools,
agent_id: agent_id.clone(),
reply_to: None,
};
let job = launch_job(message.to_string(), workspace_name.clone(), launch);
crate::agent::message_router::hand_over(job, &agent_id, "custom role launch", answered_for)
.await?;
Ok(format!(
"custom role '{name}' launched on branch {agent_id} in {workspace_name}"
))
}
pub(crate) async fn worker_branch(
caller_ws: &crate::Workspace,
branch: &str,
message: &str,
place: Option<&crate::Workspace>,
asker: Option<crate::Launch>,
answered_for: crate::agent::message_router::AnsweredFor,
) -> Result<String> {
anyhow::ensure!(
!message.trim().is_empty(),
"usage: the message must not be empty"
);
let branch = branch.trim();
crate::custom_role::validate_worker_branch(branch)?;
let workspace_name = place.map_or_else(|| caller_ws.name.clone(), |ws| ws.path.clone());
let agent_id = crate::session::worker_branch_agent_id(branch);
let reply_to = asker.map(|launch| crate::agent::message_router::Asker {
launch,
workspace: caller_ws.name.clone(),
});
let launch = crate::Launch {
role: crate::Role::Worker,
name: String::new(),
identifier: Some(branch.to_string()),
prompt: String::new(),
tools: Vec::new(),
agent_id: agent_id.clone(),
reply_to: reply_to.map(Box::new),
};
let job = launch_job(message.to_string(), workspace_name.clone(), launch);
crate::agent::message_router::hand_over(job, &agent_id, "worker branch launch", answered_for)
.await?;
let folder = place.map_or_else(|| caller_ws.path.clone(), |ws| ws.path.clone());
let folder = folder.trim_end_matches(std::path::MAIN_SEPARATOR);
Ok(format!(
"Worker branch '{branch}' is on it in {folder}; its report arrives as a <worker-report> message."
))
}
#[must_use]
pub(crate) fn display_label(launch: &crate::Launch) -> String {
let identifier = launch.identifier.as_deref();
match launch.role {
crate::Role::Worker => format!("Worker branch \"{}\"", identifier.unwrap_or_default()),
_ => match identifier {
Some(id) => format!("Custom role \"{}\" — branch \"{id}\"", launch.name),
None => format!("Custom role \"{}\"", launch.name),
},
}
}
pub(crate) async fn stop(agent_id: &str) -> Result<()> {
crate::agent::registry::AGENT_REGISTRY.cancel_by_parent_key(
&crate::agent::registry::ParentKey::Launch(agent_id.to_string()),
);
crate::session::store()
.set_launch_stopped(agent_id, true)
.await?;
crate::jobs::delete_pending_jobs_for(agent_id).await?;
if let Some(workspace) = crate::session::store()
.session_workspace_name(agent_id)
.await
{
crate::search_engine::release_path_named_engine(&workspace);
}
Ok(())
}
pub(crate) async fn resolve_launch_workspace(raw: &str) -> Result<String> {
let ws = crate::users::resolve_workspace(raw).await?.ok_or_else(|| {
anyhow::anyhow!(
"usage: the place must be a registered workspace name or an absolute path \
to an existing directory"
)
})?;
Ok(ws.name)
}