use super::{get_bool, get_string, make_tool_with_prompts};
use crate::config::{AppConfig, Prompts, StatesConfig};
use crate::db::Database;
use crate::error::ToolError;
use crate::prompts::PromptContext;
use anyhow::Result;
use rmcp::model::Tool;
use serde_json::{Value, json};
pub fn get_tools(prompts: &Prompts, _states_config: &StatesConfig) -> Vec<Tool> {
vec![make_tool_with_prompts(
"claim",
"Commit to working on a task (like adding to a changelist). Fails if: already claimed, deps unsatisfied, or worker lacks required tags. Sets status to timed (working) status.",
json!({
"worker_id": {
"type": "string",
"description": "Worker ID claiming the task"
},
"task": {
"type": "string",
"description": "Task ID to claim"
},
"force": {
"type": "boolean",
"description": "Force claim even if owned by another agent (default: false)"
}
}),
vec!["worker_id", "task"],
prompts,
)]
}
pub fn claim(
db: &Database,
config: &AppConfig,
workflows: &crate::config::workflows::WorkflowsConfig,
args: Value,
) -> Result<Value> {
let states_config = &config.states;
let phases_config = &config.phases;
let deps_config = &config.deps;
let auto_advance = &config.auto_advance;
let worker_id =
get_string(&args, "worker_id").ok_or_else(|| ToolError::missing_field("worker_id"))?;
let task_id = get_string(&args, "task").ok_or_else(|| ToolError::missing_field("task"))?;
let force = get_bool(&args, "force").unwrap_or(false);
let claim_status = states_config
.definitions
.iter()
.find(|(_, def)| def.timed)
.map(|(name, _)| name.clone())
.unwrap_or_else(|| "working".to_string());
let (task, _unblocked, _auto_advanced) = match db.update_task_unified(
&task_id,
&worker_id,
None, None, None, Some(claim_status), None, None, None, None, None, None, None, None, force,
states_config,
deps_config,
auto_advance,
) {
Ok(result) => result,
Err(e) => {
let err_msg = e.to_string();
if err_msg.contains("unsatisfied dependencies") {
let blockers = db
.get_start_blockers(&task_id, deps_config)
.unwrap_or_default();
if !blockers.is_empty() {
return Err(ToolError::deps_not_satisfied(&blockers).into());
}
}
return Err(e);
}
};
let worker_info = db.get_worker(&worker_id).ok().flatten();
let worker_role = worker_info
.as_ref()
.map(|w| workflows.match_role(&w.tags))
.unwrap_or(None);
let mut transition_prompt_list: Vec<String> = {
match db.update_worker_state(&worker_id, Some(&task.status), task.phase.as_deref()) {
Ok((old_status, old_phase)) => {
let mut ctx = PromptContext::new(
&task.status,
task.phase.as_deref(),
states_config,
phases_config,
)
.with_task(&task.id, &task.title, task.priority, &task.tags);
if let Some(ref worker) = worker_info {
ctx = ctx.with_agent(&worker_id, worker_role.as_deref(), &worker.tags);
}
crate::prompts::get_transition_prompts_with_context(
old_status.as_deref().unwrap_or(""),
old_phase.as_deref(),
&task.status,
task.phase.as_deref(),
workflows,
&ctx,
)
}
Err(_) => vec![],
}
};
let mut response = json!({
"success": true,
"task": {
"id": &task.id,
"title": task.title,
"status": task.status,
"worker_id": task.worker_id,
"claimed_at": task.claimed_at
}
});
if let Some(ref role_name) = worker_role {
if let Some(claiming_prompt) = workflows.get_role_prompt(role_name, "claiming") {
transition_prompt_list.push(claiming_prompt.to_string());
}
if let Some(reporting_prompt) = workflows.get_role_prompt(role_name, "reporting") {
transition_prompt_list.push(reporting_prompt.to_string());
}
}
if !transition_prompt_list.is_empty()
&& let Value::Object(ref mut map) = response
{
map.insert("prompts".to_string(), json!(transition_prompt_list));
}
Ok(response)
}