1use std::path::PathBuf;
4use std::sync::Arc;
5
6use claude_pool::PoolStore;
7use claude_pool::skill::{SkillScope, SkillSource};
8use claude_pool::types::SlotConfig;
9use schemars::JsonSchema;
10use serde::Deserialize;
11use tower_mcp::ToolBuilder;
12use tower_mcp::protocol::CallToolResult;
13use tower_mcp::tool::Tool;
14
15use crate::State;
16
17#[derive(Debug, Deserialize, JsonSchema)]
20pub struct RunInput {
21 pub prompt: String,
23 pub model: Option<String>,
25 pub effort: Option<String>,
27 pub mcp_servers: Option<std::collections::HashMap<String, serde_json::Value>>,
30}
31
32#[derive(Debug, Deserialize, JsonSchema)]
33pub struct SubmitInput {
34 pub prompt: String,
36 pub model: Option<String>,
38 pub effort: Option<String>,
40 pub tags: Option<Vec<String>>,
42 pub mcp_servers: Option<std::collections::HashMap<String, serde_json::Value>>,
45}
46
47#[derive(Debug, Deserialize, JsonSchema)]
48pub struct TaskIdInput {
49 pub task_id: String,
51}
52
53#[derive(Debug, Deserialize, JsonSchema)]
54pub struct FanOutInput {
55 pub prompts: Vec<String>,
57}
58
59#[derive(Debug, Deserialize, JsonSchema)]
60pub struct ContextSetInput {
61 pub key: String,
63 pub value: String,
65}
66
67#[derive(Debug, Deserialize, JsonSchema)]
68pub struct ContextKeyInput {
69 pub key: String,
71}
72
73#[derive(Debug, Deserialize, JsonSchema)]
74pub struct ConfigureSlotInput {
75 pub slot_id: String,
77 pub name: Option<String>,
79 pub role: Option<String>,
81 pub description: Option<String>,
83}
84
85#[derive(Debug, Deserialize, JsonSchema)]
86pub struct InvokeWorkflowInput {
87 pub workflow: String,
89 #[serde(default)]
91 pub arguments: std::collections::HashMap<String, String>,
92 pub tags: Option<Vec<String>>,
94}
95
96#[derive(Debug, Deserialize, JsonSchema)]
97pub struct ScalingInput {
98 pub count: usize,
100}
101
102#[derive(Debug, Deserialize, JsonSchema)]
103pub struct SetTargetSlotsInput {
104 pub target: usize,
106}
107
108#[derive(Debug, Deserialize, JsonSchema)]
112pub struct SkillListInput {
113 pub scope: Option<String>,
115 pub source: Option<String>,
117}
118
119#[derive(Debug, Deserialize, JsonSchema)]
121pub struct SkillGetInput {
122 pub name: String,
124}
125
126#[derive(Debug, Deserialize, JsonSchema)]
128pub struct SkillArgumentInput {
129 pub name: String,
131 pub description: String,
133 #[serde(default)]
135 pub required: bool,
136}
137
138#[derive(Debug, Deserialize, JsonSchema)]
140pub struct SkillAddInput {
141 pub name: String,
143 pub description: String,
145 pub prompt: String,
147 #[serde(default)]
149 pub arguments: Vec<SkillArgumentInput>,
150 pub scope: Option<String>,
152 pub config: Option<serde_json::Value>,
154}
155
156#[derive(Debug, Deserialize, JsonSchema)]
158pub struct SkillRemoveInput {
159 pub name: String,
161}
162
163#[derive(Debug, Deserialize, JsonSchema)]
165pub struct SkillSaveInput {
166 pub name: String,
168 pub dir: Option<String>,
170}
171
172#[derive(Debug, Deserialize, JsonSchema)]
174pub struct SkillEjectInput {
175 pub name: String,
177 pub dir: Option<String>,
179}
180
181#[derive(Debug, Deserialize, JsonSchema)]
185pub struct SendMessageInput {
186 pub from: String,
188 pub to: String,
190 pub content: String,
192}
193
194#[derive(Debug, Deserialize, JsonSchema)]
196pub struct ReadMessagesInput {
197 pub slot_id: String,
199}
200
201#[derive(Debug, Deserialize, JsonSchema)]
203pub struct PeekMessagesInput {
204 pub slot_id: String,
206}
207
208#[derive(Debug, Deserialize, JsonSchema)]
210pub struct BroadcastInput {
211 pub from: String,
213 pub content: String,
215}
216
217#[derive(Debug, Deserialize, JsonSchema)]
219pub struct FindSlotsInput {
220 #[serde(default)]
222 pub name: Option<String>,
223 #[serde(default)]
225 pub role: Option<String>,
226 #[serde(default)]
228 pub state: Option<String>,
229}
230
231#[derive(Debug, Deserialize, JsonSchema)]
233pub struct ClaimInput {
234 pub slot_id: String,
236}
237
238#[derive(Debug, Deserialize, JsonSchema)]
240pub struct SubmitWithReviewInput {
241 pub prompt: String,
243 pub model: Option<String>,
245 pub effort: Option<String>,
247 pub tags: Option<Vec<String>>,
249 pub max_rejections: Option<u32>,
251 pub mcp_servers: Option<std::collections::HashMap<String, serde_json::Value>>,
254}
255
256#[derive(Debug, Deserialize, JsonSchema)]
258pub struct ApproveResultInput {
259 pub task_id: String,
261}
262
263#[derive(Debug, Deserialize, JsonSchema)]
265pub struct RejectResultInput {
266 pub task_id: String,
268 pub feedback: String,
271}
272
273fn parse_effort(s: &str) -> Option<claude_pool::Effort> {
276 match s.to_lowercase().as_str() {
277 "min" | "low" => Some(claude_pool::Effort::Low),
278 "medium" => Some(claude_pool::Effort::Medium),
279 "high" => Some(claude_pool::Effort::High),
280 "max" => Some(claude_pool::Effort::Max),
281 _ => None,
282 }
283}
284
285fn task_config_from(
286 model: Option<String>,
287 effort: Option<String>,
288 mcp_servers: Option<std::collections::HashMap<String, serde_json::Value>>,
289) -> Option<SlotConfig> {
290 if model.is_none() && effort.is_none() && mcp_servers.is_none() {
291 return None;
292 }
293 Some(SlotConfig {
294 model,
295 effort: effort.and_then(|e| parse_effort(&e)),
296 mcp_servers,
297 ..Default::default()
298 })
299}
300
301fn parse_scope(s: &str) -> SkillScope {
302 match s {
303 "coordinator" => SkillScope::Coordinator,
304 "chain" => SkillScope::Chain,
305 _ => SkillScope::Task,
306 }
307}
308
309fn parse_isolation(s: Option<&str>) -> claude_pool::chain::ChainIsolation {
310 match s {
311 Some("none") => claude_pool::chain::ChainIsolation::None,
312 Some("clone") => claude_pool::chain::ChainIsolation::Clone,
313 _ => claude_pool::chain::ChainIsolation::Worktree,
314 }
315}
316
317fn parse_source(s: &str) -> Option<SkillSource> {
318 match s {
319 "builtin" => Some(SkillSource::Builtin),
320 "project" => Some(SkillSource::Project),
321 "runtime" => Some(SkillSource::Runtime),
322 _ => None,
323 }
324}
325
326pub fn pool_status_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
329 ToolBuilder::new("pool_status")
330 .title("Pool Status")
331 .description("Get pool status: slots, tasks in flight, budget, server metadata")
332 .read_only()
333 .no_params_handler(move || {
334 let state = Arc::clone(&state);
335 async move {
336 match state.pool.status().await {
337 Ok(status) => {
338 let mut response = serde_json::to_value(&status).unwrap();
339 let response_obj = response.as_object_mut().unwrap();
340 let server_obj = serde_json::to_value(&state.server_info).unwrap();
341 if let Some(server_map) = server_obj.as_object() {
342 for (key, value) in server_map.iter() {
343 response_obj.insert(format!("server_{key}"), value.clone());
344 }
345 }
346 Ok(CallToolResult::json(response))
347 }
348 Err(e) => Ok(CallToolResult::error(e.to_string())),
349 }
350 }
351 })
352 .build()
353}
354
355pub fn pool_run_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
356 ToolBuilder::new("pool_run")
357 .title("Run a Task")
358 .description(
359 "Run a task on the next available slot. Blocks until completion. \
360 Use this for single, clear actions with one clear output.",
361 )
362 .handler(move |input: RunInput| {
363 let state = Arc::clone(&state);
364 async move {
365 let config = task_config_from(input.model, input.effort, input.mcp_servers);
366 match state.pool.run_with_config(&input.prompt, config).await {
367 Ok(result) => Ok(CallToolResult::json(serde_json::to_value(&result).unwrap())),
368 Err(e) => Ok(CallToolResult::error(e.to_string())),
369 }
370 }
371 })
372 .build()
373}
374
375pub fn pool_submit_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
376 ToolBuilder::new("pool_submit")
377 .title("Fire a Task")
378 .description("Fire off a task for async execution. Returns a task_id immediately. Check on it later with pool_result.")
379 .handler(move |input: SubmitInput| {
380 let state = Arc::clone(&state);
381 async move {
382 let config = task_config_from(input.model, input.effort, input.mcp_servers);
383 let tags = input.tags.unwrap_or_default();
384 match state
385 .pool
386 .submit_with_config(&input.prompt, config, tags)
387 .await
388 {
389 Ok(task_id) => Ok(CallToolResult::json(
390 serde_json::json!({ "task_id": task_id.0 }),
391 )),
392 Err(e) => Ok(CallToolResult::error(e.to_string())),
393 }
394 }
395 })
396 .build()
397}
398
399pub fn pool_result_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
400 ToolBuilder::new("pool_result")
401 .title("Check on a Task")
402 .description(
403 "Check on a fired task. Returns the result if complete or pending_review, \
404 null if still running. Tasks with review_required=true will have \
405 state='pending_review' when done -- use pool_approve_result or \
406 pool_reject_result to finalize.",
407 )
408 .read_only()
409 .handler(move |input: TaskIdInput| {
410 let state = Arc::clone(&state);
411 async move {
412 let task_id = claude_pool::TaskId(input.task_id.clone());
413 let task = state.pool.store().get_task(&task_id).await.ok().flatten();
415
416 match state.pool.result(&task_id).await {
417 Ok(Some(r)) => {
418 let mut val = serde_json::to_value(&r).unwrap();
419 if let Some(ref t) = task
420 && let Some(obj) = val.as_object_mut()
421 {
422 obj.insert("state".to_string(), serde_json::to_value(t.state).unwrap());
423 if t.review_required {
424 obj.insert(
425 "review_required".to_string(),
426 serde_json::Value::Bool(true),
427 );
428 obj.insert(
429 "rejection_count".to_string(),
430 serde_json::json!(t.rejection_count),
431 );
432 obj.insert(
433 "max_rejections".to_string(),
434 serde_json::json!(t.max_rejections),
435 );
436 }
437 }
438 Ok(CallToolResult::json(val))
439 }
440 Ok(None) => Ok(CallToolResult::json(
441 serde_json::json!({ "status": "running" }),
442 )),
443 Err(e) => Ok(CallToolResult::error(e.to_string())),
444 }
445 }
446 })
447 .build()
448}
449
450pub fn pool_cancel_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
451 ToolBuilder::new("pool_cancel")
452 .title("Cancel a Task")
453 .description("Cancel a pending or running task.")
454 .handler(move |input: TaskIdInput| {
455 let state = Arc::clone(&state);
456 async move {
457 let task_id = claude_pool::TaskId(input.task_id);
458 match state.pool.cancel(&task_id).await {
459 Ok(()) => Ok(CallToolResult::text("cancelled")),
460 Err(e) => Ok(CallToolResult::error(e.to_string())),
461 }
462 }
463 })
464 .build()
465}
466
467pub fn pool_fan_out_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
468 ToolBuilder::new("pool_fan_out")
469 .title("Fan Out Tasks")
470 .description(
471 "Fan out multiple independent tasks in parallel across available slots. Returns all results.",
472 )
473 .handler(move |input: FanOutInput| {
474 let state = Arc::clone(&state);
475 async move {
476 let prompts: Vec<&str> = input.prompts.iter().map(|s| s.as_str()).collect();
477 match state.pool.fan_out(&prompts).await {
478 Ok(results) => Ok(CallToolResult::json(
479 serde_json::json!({ "results": results }),
480 )),
481 Err(e) => Ok(CallToolResult::error(e.to_string())),
482 }
483 }
484 })
485 .build()
486}
487
488pub fn pool_drain_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
489 ToolBuilder::new("pool_drain")
490 .title("Drain the Pool")
491 .description(
492 "Gracefully shut down the pool. Waits for in-flight tasks, then stops all slots.",
493 )
494 .destructive()
495 .no_params_handler(move || {
496 let state = Arc::clone(&state);
497 async move {
498 match state.pool.drain().await {
499 Ok(summary) => Ok(CallToolResult::json(
500 serde_json::to_value(&summary).unwrap(),
501 )),
502 Err(e) => Ok(CallToolResult::error(e.to_string())),
503 }
504 }
505 })
506 .build()
507}
508
509pub fn context_set_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
510 ToolBuilder::new("context_set")
511 .title("Set Context")
512 .description("Set a shared context value. Context is injected into slot system prompts.")
513 .handler(move |input: ContextSetInput| {
514 let state = Arc::clone(&state);
515 async move {
516 state.pool.set_context(input.key, input.value);
517 Ok(CallToolResult::text("ok"))
518 }
519 })
520 .build()
521}
522
523pub fn context_get_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
524 ToolBuilder::new("context_get")
525 .title("Get Context")
526 .description("Get a shared context value by key.")
527 .read_only()
528 .handler(move |input: ContextKeyInput| {
529 let state = Arc::clone(&state);
530 async move {
531 match state.pool.get_context(&input.key) {
532 Some(value) => Ok(CallToolResult::text(value)),
533 None => Ok(CallToolResult::error(format!(
534 "key not found: {}",
535 input.key
536 ))),
537 }
538 }
539 })
540 .build()
541}
542
543pub fn context_delete_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
544 ToolBuilder::new("context_delete")
545 .title("Delete Context")
546 .description("Delete a shared context value by key.")
547 .handler(move |input: ContextKeyInput| {
548 let state = Arc::clone(&state);
549 async move {
550 state.pool.delete_context(&input.key);
551 Ok(CallToolResult::text("ok"))
552 }
553 })
554 .build()
555}
556
557pub fn context_list_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
558 ToolBuilder::new("context_list")
559 .title("List Context")
560 .description("List all shared context keys and values.")
561 .read_only()
562 .no_params_handler(move || {
563 let state = Arc::clone(&state);
564 async move {
565 let entries = state.pool.list_context();
566 let map: serde_json::Map<String, serde_json::Value> = entries
567 .into_iter()
568 .map(|(k, v)| (k, serde_json::Value::String(v)))
569 .collect();
570 Ok(CallToolResult::json(serde_json::Value::Object(map)))
571 }
572 })
573 .build()
574}
575
576pub fn pool_configure_slot_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
577 ToolBuilder::new("pool_configure_slot")
578 .title("Configure Slot")
579 .description("Set name/role/description for a slot to give it persistent identity")
580 .handler(move |input: ConfigureSlotInput| {
581 let state = Arc::clone(&state);
582 async move {
583 let slot_id = claude_pool::SlotId(input.slot_id.clone());
584
585 match state.pool.store().get_slot(&slot_id).await {
586 Ok(Some(mut slot)) => {
587 if let Some(name) = input.name {
589 slot.config.name = Some(name);
590 }
591 if let Some(role) = input.role {
592 slot.config.role = Some(role);
593 }
594 if let Some(description) = input.description {
595 slot.config.description = Some(description);
596 }
597
598 match state.pool.store().put_slot(slot.clone()).await {
600 Ok(_) => {
601 let response = serde_json::json!({
602 "slot_id": slot_id.0,
603 "name": slot.config.name,
604 "role": slot.config.role,
605 "description": slot.config.description,
606 });
607 Ok(CallToolResult::json(response))
608 }
609 Err(e) => {
610 Ok(CallToolResult::error(format!("failed to update slot: {e}")))
611 }
612 }
613 }
614 Ok(None) => Ok(CallToolResult::error(format!(
615 "slot not found: {}",
616 input.slot_id
617 ))),
618 Err(e) => Ok(CallToolResult::error(format!("failed to fetch slot: {e}"))),
619 }
620 }
621 })
622 .build()
623}
624
625#[derive(Debug, Deserialize, JsonSchema)]
628pub struct SkillRunInput {
629 pub skill: String,
631 pub arguments: std::collections::HashMap<String, String>,
633 pub model: Option<String>,
635 pub effort: Option<String>,
637}
638
639#[derive(Debug, Deserialize, JsonSchema)]
640pub struct ChainInput {
641 pub steps: Vec<ChainStepInput>,
643}
644
645#[derive(Debug, Deserialize, JsonSchema)]
646pub struct SubmitChainInput {
647 pub steps: Vec<ChainStepInput>,
649 pub tags: Option<Vec<String>>,
651 pub isolation: Option<String>,
653}
654
655#[derive(Debug, Deserialize, JsonSchema)]
656pub struct FanOutChainsInput {
657 pub chains: Vec<Vec<ChainStepInput>>,
659 pub tags: Option<Vec<String>>,
661 pub isolation: Option<String>,
663}
664
665#[derive(Debug, Deserialize, JsonSchema)]
666pub struct ChainStepInput {
667 pub name: String,
669 #[serde(rename = "type")]
671 pub step_type: String,
672 pub value: String,
674 pub arguments: Option<std::collections::HashMap<String, String>>,
676 pub model: Option<String>,
678 pub effort: Option<String>,
680 pub retries: Option<u32>,
682 pub recovery_prompt: Option<String>,
684 pub output_vars: Option<std::collections::HashMap<String, String>>,
688}
689
690fn convert_chain_steps(steps: Vec<ChainStepInput>) -> Vec<claude_pool::ChainStep> {
691 steps
692 .into_iter()
693 .map(|s| {
694 let action = match s.step_type.as_str() {
695 "skill" => claude_pool::StepAction::Skill {
696 skill: s.value,
697 arguments: s.arguments.unwrap_or_default(),
698 },
699 _ => claude_pool::StepAction::Prompt { prompt: s.value },
700 };
701 let config = task_config_from(s.model, s.effort, None);
702 let failure_policy = claude_pool::StepFailurePolicy {
703 retries: s.retries.unwrap_or(0),
704 recovery_prompt: s.recovery_prompt,
705 };
706 claude_pool::ChainStep {
707 name: s.name,
708 action,
709 config,
710 failure_policy,
711 output_vars: s.output_vars.unwrap_or_default(),
712 }
713 })
714 .collect()
715}
716
717pub fn pool_skill_run_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
718 ToolBuilder::new("pool_skill_run")
719 .title("Run a Skill")
720 .description("Run a registered skill by name with arguments. Blocks until completion.")
721 .handler(move |input: SkillRunInput| {
722 let state = Arc::clone(&state);
723 async move {
724 let registry = state.skills.read().await;
725 let skill = match registry.get(&input.skill) {
726 Some(s) => s.clone(),
727 None => {
728 return Ok(CallToolResult::error(format!(
729 "skill not found: {}",
730 input.skill
731 )));
732 }
733 };
734 drop(registry);
735
736 let prompt = match skill.render(&input.arguments) {
737 Ok(p) => p,
738 Err(e) => return Ok(CallToolResult::error(e.to_string())),
739 };
740
741 let mut config = skill.config.unwrap_or_default();
743 if let Some(model) = input.model {
744 config.model = Some(model);
745 }
746 if let Some(effort) = input.effort {
747 config.effort = parse_effort(&effort);
748 }
749
750 match state.pool.run_with_config(&prompt, Some(config)).await {
751 Ok(result) => Ok(CallToolResult::json(serde_json::to_value(&result).unwrap())),
752 Err(e) => Ok(CallToolResult::error(e.to_string())),
753 }
754 }
755 })
756 .build()
757}
758
759pub fn pool_chain_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
760 ToolBuilder::new("pool_chain")
761 .title("Chain Steps")
762 .description(
763 "Chain a sequential pipeline of steps. Each step's output feeds the next. \
764 Steps can be inline prompts or skill references. Blocks until all steps \
765 complete. For long chains, fire a chain with pool_submit_chain instead.",
766 )
767 .handler(move |input: ChainInput| {
768 let state = Arc::clone(&state);
769 async move {
770 let steps = convert_chain_steps(input.steps);
771 let skills = state.skills.read().await;
772 match claude_pool::execute_chain(&state.pool, &skills, &steps).await {
773 Ok(result) => Ok(CallToolResult::json(serde_json::to_value(&result).unwrap())),
774 Err(e) => Ok(CallToolResult::error(e.to_string())),
775 }
776 }
777 })
778 .build()
779}
780
781pub fn pool_submit_chain_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
782 ToolBuilder::new("pool_submit_chain")
783 .title("Fire a Chain")
784 .description(
785 "Fire off a chain for async execution. Returns a task_id immediately. \
786 Check on it with pool_chain_result for per-step progress.",
787 )
788 .handler(move |input: SubmitChainInput| {
789 let state = Arc::clone(&state);
790 async move {
791 let steps = convert_chain_steps(input.steps);
792 let isolation = parse_isolation(input.isolation.as_deref());
793 let options = claude_pool::ChainOptions {
794 tags: input.tags.unwrap_or_default(),
795 isolation,
796 };
797 let skills = state.skills.read().await;
798 match state.pool.submit_chain(steps, &skills, options).await {
799 Ok(task_id) => Ok(CallToolResult::json(
800 serde_json::json!({ "task_id": task_id.0 }),
801 )),
802 Err(e) => Ok(CallToolResult::error(e.to_string())),
803 }
804 }
805 })
806 .build()
807}
808
809pub fn pool_fan_out_chains_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
810 ToolBuilder::new("pool_fan_out_chains")
811 .title("Fan Out Chains")
812 .description(
813 "Fan out multiple chains in parallel, each on its own slot. \
814 Returns all task IDs. Check on each with pool_chain_result.",
815 )
816 .handler(move |input: FanOutChainsInput| {
817 let state = Arc::clone(&state);
818 async move {
819 let chains = input.chains.into_iter().map(convert_chain_steps).collect();
820 let isolation = parse_isolation(input.isolation.as_deref());
821 let options = claude_pool::ChainOptions {
822 tags: input.tags.unwrap_or_default(),
823 isolation,
824 };
825 let skills = state.skills.read().await;
826 match state.pool.fan_out_chains(chains, &skills, options).await {
827 Ok(task_ids) => Ok(CallToolResult::json(serde_json::json!({
828 "task_ids": task_ids.iter().map(|id| &id.0).collect::<Vec<_>>()
829 }))),
830 Err(e) => Ok(CallToolResult::error(e.to_string())),
831 }
832 }
833 })
834 .build()
835}
836
837pub fn pool_chain_result_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
838 ToolBuilder::new("pool_chain_result")
839 .title("Check on a Chain")
840 .description(
841 "Check on a fired chain. Shows per-step progress: which step is running, \
842 completed steps, and overall status.",
843 )
844 .read_only()
845 .handler(move |input: TaskIdInput| {
846 let state = Arc::clone(&state);
847 async move {
848 let task_id = claude_pool::TaskId(input.task_id.clone());
849 match state.pool.chain_progress(&task_id) {
850 Some(progress) => Ok(CallToolResult::json(
851 serde_json::to_value(&progress).unwrap(),
852 )),
853 None => {
854 match state.pool.result(&task_id).await {
856 Ok(Some(r)) => {
857 Ok(CallToolResult::json(serde_json::to_value(&r).unwrap()))
858 }
859 Ok(None) => Ok(CallToolResult::error(format!(
860 "no chain found for task_id: {}",
861 input.task_id,
862 ))),
863 Err(e) => Ok(CallToolResult::error(e.to_string())),
864 }
865 }
866 }
867 }
868 })
869 .build()
870}
871
872pub fn pool_cancel_chain_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
874 ToolBuilder::new("pool_cancel_chain")
875 .title("Cancel a Chain")
876 .description(
877 "Cancel a running chain that was fired with pool_submit_chain or pool_fan_out_chains. \
878 The current step finishes before cancellation takes effect. Remaining steps are \
879 skipped. Check on the chain with pool_chain_result to confirm.",
880 )
881 .handler(move |input: TaskIdInput| {
882 let state = Arc::clone(&state);
883 async move {
884 let task_id = claude_pool::TaskId(input.task_id.clone());
885 match state.pool.cancel_chain(&task_id).await {
886 Ok(()) => Ok(CallToolResult::json(serde_json::json!({
887 "status": "cancellation_requested",
888 "task_id": input.task_id,
889 }))),
890 Err(e) => Ok(CallToolResult::error(e.to_string())),
891 }
892 }
893 })
894 .build()
895}
896
897pub fn pool_invoke_workflow_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
898 ToolBuilder::new("pool_invoke_workflow")
899 .title("Invoke Workflow")
900 .description(
901 "Submit a named workflow template with arguments. Returns a task_id immediately. \
902 Example workflows: 'issue_to_pr', 'refactor_and_test', 'review_and_fix'.",
903 )
904 .handler(move |input: InvokeWorkflowInput| {
905 let state = Arc::clone(&state);
906 async move {
907 let skills = state.skills.read().await;
908 match state
909 .pool
910 .submit_workflow(
911 &input.workflow,
912 input.arguments,
913 &skills,
914 &state.workflows,
915 input.tags.unwrap_or_default(),
916 )
917 .await
918 {
919 Ok(task_id) => Ok(CallToolResult::json(serde_json::json!({
920 "task_id": task_id.0,
921 "workflow": input.workflow,
922 }))),
923 Err(e) => Ok(CallToolResult::error(e.to_string())),
924 }
925 }
926 })
927 .build()
928}
929
930pub fn pool_scale_up_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
932 ToolBuilder::new("pool_scale_up")
933 .title("Scale Up the Pool")
934 .description("Add N new slots to the pool. Returns the new total slot count.")
935 .handler(move |input: ScalingInput| {
936 let state = Arc::clone(&state);
937 async move {
938 match state.pool.scale_up(input.count).await {
939 Ok(new_count) => Ok(CallToolResult::json(serde_json::json!({
940 "success": true,
941 "new_slot_count": new_count,
942 "details": format!("Scaled up by {} slots", input.count),
943 }))),
944 Err(e) => Ok(CallToolResult::error(e.to_string())),
945 }
946 }
947 })
948 .build()
949}
950
951pub fn pool_scale_down_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
952 ToolBuilder::new("pool_scale_down")
953 .title("Scale Down the Pool")
954 .description(
955 "Remove N slots from the pool. Removes idle slots first, \
956 then waits for busy slots to complete. Returns the new total slot count.",
957 )
958 .handler(move |input: ScalingInput| {
959 let state = Arc::clone(&state);
960 async move {
961 match state.pool.scale_down(input.count).await {
962 Ok(new_count) => Ok(CallToolResult::json(serde_json::json!({
963 "success": true,
964 "new_slot_count": new_count,
965 "details": format!("Scaled down by {} slots", input.count),
966 }))),
967 Err(e) => Ok(CallToolResult::error(e.to_string())),
968 }
969 }
970 })
971 .build()
972}
973
974pub fn pool_set_target_slots_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
975 ToolBuilder::new("pool_set_target_slots")
976 .title("Set Pool Size")
977 .description("Set the pool to a specific number of slots, scaling up or down as needed.")
978 .handler(move |input: SetTargetSlotsInput| {
979 let state = Arc::clone(&state);
980 async move {
981 match state.pool.set_target_slots(input.target).await {
982 Ok(new_count) => Ok(CallToolResult::json(serde_json::json!({
983 "success": true,
984 "new_slot_count": new_count,
985 "target": input.target,
986 }))),
987 Err(e) => Ok(CallToolResult::error(e.to_string())),
988 }
989 }
990 })
991 .build()
992}
993
994pub fn pool_skill_list_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
998 ToolBuilder::new("pool_skill_list")
999 .title("List Skills")
1000 .description("List skills available in the pool, with optional scope/source filters. Skills come from builtins, global (~/.claude-pool/skills/), or project (.claude-pool/skills/).")
1001 .read_only()
1002 .handler(move |input: SkillListInput| {
1003 let state = Arc::clone(&state);
1004 async move {
1005 let registry = state.skills.read().await;
1006 let scope_filter = input.scope.as_deref().map(parse_scope);
1007 let source_filter = input.source.as_deref().and_then(parse_source);
1008
1009 let mut results: Vec<_> = registry
1010 .list_registered()
1011 .into_iter()
1012 .filter(|rs| {
1013 if let Some(scope) = scope_filter
1014 && rs.skill.scope != scope
1015 {
1016 return false;
1017 }
1018 if let Some(source) = source_filter
1019 && rs.source != source
1020 {
1021 return false;
1022 }
1023 true
1024 })
1025 .map(|rs| {
1026 let mut entry = serde_json::json!({
1027 "name": rs.skill.name,
1028 "description": rs.skill.description,
1029 "scope": rs.skill.scope.to_string(),
1030 "source": rs.source.to_string(),
1031 });
1032 if let Some(ref hint) = rs.skill.argument_hint {
1033 entry["argument_hint"] = serde_json::json!(hint);
1034 }
1035 entry
1036 })
1037 .collect();
1038 results.sort_by(|a, b| a["name"].as_str().cmp(&b["name"].as_str()));
1039 Ok(CallToolResult::json(serde_json::json!(results)))
1040 }
1041 })
1042 .build()
1043}
1044
1045pub fn pool_skill_get_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1047 ToolBuilder::new("pool_skill_get")
1048 .title("Get Skill Details")
1049 .description("Get full details of a skill by name, including prompt template.")
1050 .read_only()
1051 .handler(move |input: SkillGetInput| {
1052 let state = Arc::clone(&state);
1053 async move {
1054 let registry = state.skills.read().await;
1055 match registry.get_registered(&input.name) {
1056 Some(rs) => {
1057 let customized = rs.source != SkillSource::Builtin
1060 && claude_pool::skill::builtin_skills()
1061 .iter()
1062 .any(|b| b.name == rs.skill.name);
1063 let response = serde_json::json!({
1064 "name": rs.skill.name,
1065 "description": rs.skill.description,
1066 "prompt": rs.skill.prompt,
1067 "arguments": rs.skill.arguments.iter().map(|a| serde_json::json!({
1068 "name": a.name,
1069 "description": a.description,
1070 "required": a.required,
1071 })).collect::<Vec<_>>(),
1072 "scope": rs.skill.scope.to_string(),
1073 "source": rs.source.to_string(),
1074 "customized": customized,
1075 "config": rs.skill.config,
1076 });
1077 Ok(CallToolResult::json(response))
1078 }
1079 None => Ok(CallToolResult::error(format!(
1080 "skill not found: {}",
1081 input.name
1082 ))),
1083 }
1084 }
1085 })
1086 .build()
1087}
1088
1089pub fn pool_skill_add_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1091 ToolBuilder::new("pool_skill_add")
1092 .title("Add a Skill")
1093 .description(
1094 "Register a skill at runtime. Ephemeral (lost on restart) unless saved \
1095 with pool_skill_save. Overwrites any existing skill with the same name.",
1096 )
1097 .handler(move |input: SkillAddInput| {
1098 let state = Arc::clone(&state);
1099 async move {
1100 let scope = input.scope.as_deref().map(parse_scope).unwrap_or_default();
1101 let arguments = input
1102 .arguments
1103 .into_iter()
1104 .map(|a| claude_pool::SkillArgument {
1105 name: a.name,
1106 description: a.description,
1107 required: a.required,
1108 })
1109 .collect();
1110 let config: Option<SlotConfig> =
1111 input.config.and_then(|v| serde_json::from_value(v).ok());
1112 let skill = claude_pool::Skill {
1113 name: input.name.clone(),
1114 description: input.description,
1115 prompt: input.prompt,
1116 arguments,
1117 config,
1118 scope,
1119 argument_hint: None,
1120 skill_dir: None,
1121 };
1122 let mut registry = state.skills.write().await;
1123 let overwritten = registry.get(&input.name).is_some();
1124 registry.register(skill, SkillSource::Runtime);
1125 Ok(CallToolResult::json(serde_json::json!({
1126 "name": input.name,
1127 "overwritten": overwritten,
1128 "source": "runtime",
1129 })))
1130 }
1131 })
1132 .build()
1133}
1134
1135pub fn pool_skill_remove_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1137 ToolBuilder::new("pool_skill_remove")
1138 .title("Remove a Skill")
1139 .description("Remove a skill by name. Runtime-only, does not delete files on disk.")
1140 .handler(move |input: SkillRemoveInput| {
1141 let state = Arc::clone(&state);
1142 async move {
1143 let mut registry = state.skills.write().await;
1144 match registry.remove(&input.name) {
1145 Some(_) => Ok(CallToolResult::json(serde_json::json!({
1146 "removed": input.name,
1147 }))),
1148 None => Ok(CallToolResult::error(format!(
1149 "skill not found: {}",
1150 input.name
1151 ))),
1152 }
1153 }
1154 })
1155 .build()
1156}
1157
1158pub fn pool_skill_save_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1160 ToolBuilder::new("pool_skill_save")
1161 .title("Save Skill to Disk")
1162 .description(
1163 "Persist a skill to the project skills directory as a SKILL.md folder \
1164 (Agent Skills standard). Creates/overwrites {dir}/{name}/SKILL.md.",
1165 )
1166 .handler(move |input: SkillSaveInput| {
1167 let state = Arc::clone(&state);
1168 async move {
1169 let skill = {
1170 let registry = state.skills.read().await;
1171 match registry.get(&input.name) {
1172 Some(s) => s.clone(),
1173 None => {
1174 return Ok(CallToolResult::error(format!(
1175 "skill not found: {}",
1176 input.name
1177 )));
1178 }
1179 }
1180 };
1181
1182 let base_dir = input
1183 .dir
1184 .map(PathBuf::from)
1185 .unwrap_or_else(|| state.skills_dir.clone());
1186
1187 let skill_dir = base_dir.join(&skill.name);
1188 if let Err(e) = std::fs::create_dir_all(&skill_dir) {
1189 return Ok(CallToolResult::error(format!(
1190 "failed to create directory {}: {e}",
1191 skill_dir.display()
1192 )));
1193 }
1194
1195 let skill_md = skill_to_skill_md(&skill);
1196 let path = skill_dir.join("SKILL.md");
1197
1198 if let Err(e) = std::fs::write(&path, &skill_md) {
1199 return Ok(CallToolResult::error(format!(
1200 "failed to write {}: {e}",
1201 path.display()
1202 )));
1203 }
1204
1205 {
1207 let mut registry = state.skills.write().await;
1208 if let Some(existing) = registry.get(&input.name).cloned() {
1209 registry.register(existing, SkillSource::Project);
1210 }
1211 }
1212
1213 Ok(CallToolResult::json(serde_json::json!({
1214 "saved": input.name,
1215 "path": path.display().to_string(),
1216 "format": "SKILL.md",
1217 })))
1218 }
1219 })
1220 .build()
1221}
1222
1223fn skill_to_skill_md(skill: &claude_pool::Skill) -> String {
1225 use std::fmt::Write;
1226
1227 let mut out = String::new();
1228 out.push_str("---\n");
1229 writeln!(out, "name: {}", skill.name).unwrap();
1230 writeln!(
1231 out,
1232 "description: \"{}\"",
1233 skill.description.replace('"', "\\\"")
1234 )
1235 .unwrap();
1236
1237 let has_metadata = skill.scope != claude_pool::SkillScope::Task
1238 || !skill.arguments.is_empty()
1239 || skill.config.is_some();
1240
1241 if has_metadata {
1242 out.push_str("metadata:\n");
1243 if skill.scope != claude_pool::SkillScope::Task {
1244 writeln!(out, " scope: {}", skill.scope).unwrap();
1245 }
1246 if !skill.arguments.is_empty() {
1247 out.push_str(" arguments:\n");
1248 for arg in &skill.arguments {
1249 writeln!(out, " - name: {}", arg.name).unwrap();
1250 writeln!(
1251 out,
1252 " description: \"{}\"",
1253 arg.description.replace('"', "\\\"")
1254 )
1255 .unwrap();
1256 writeln!(out, " required: {}", arg.required).unwrap();
1257 }
1258 }
1259 }
1260
1261 out.push_str("---\n\n");
1262 out.push_str(&skill.prompt);
1263 out.push('\n');
1264 out
1265}
1266
1267pub fn pool_skill_eject_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1269 ToolBuilder::new("pool_skill_eject")
1270 .title("Eject Builtin Skill")
1271 .description(
1272 "Write a builtin skill to disk as a SKILL.md folder for customization. \
1273 The disk version shadows the builtin. Delete the folder to restore the default.",
1274 )
1275 .handler(move |input: SkillEjectInput| {
1276 let state = Arc::clone(&state);
1277 async move {
1278 let builtin = claude_pool::skill::builtin_skills()
1280 .into_iter()
1281 .find(|s| s.name == input.name);
1282
1283 let skill = match builtin {
1284 Some(s) => s,
1285 None => {
1286 return Ok(CallToolResult::error(format!(
1287 "not a builtin skill: {} (only builtins can be ejected)",
1288 input.name
1289 )));
1290 }
1291 };
1292
1293 let base_dir = input
1294 .dir
1295 .map(PathBuf::from)
1296 .unwrap_or_else(|| state.skills_dir.clone());
1297
1298 let skill_dir = base_dir.join(&skill.name);
1299 if let Err(e) = std::fs::create_dir_all(&skill_dir) {
1300 return Ok(CallToolResult::error(format!(
1301 "failed to create directory {}: {e}",
1302 skill_dir.display()
1303 )));
1304 }
1305
1306 let skill_md = skill_to_skill_md(&skill);
1307 let path = skill_dir.join("SKILL.md");
1308
1309 if let Err(e) = std::fs::write(&path, &skill_md) {
1310 return Ok(CallToolResult::error(format!(
1311 "failed to write {}: {e}",
1312 path.display()
1313 )));
1314 }
1315
1316 {
1318 let mut registry = state.skills.write().await;
1319 registry.register(skill, SkillSource::Project);
1320 }
1321
1322 Ok(CallToolResult::json(serde_json::json!({
1323 "ejected": input.name,
1324 "path": path.display().to_string(),
1325 "hint": "edit the SKILL.md to customize, delete the folder to restore builtin",
1326 })))
1327 }
1328 })
1329 .build()
1330}
1331
1332pub fn pool_send_message_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1333 ToolBuilder::new("pool_send_message")
1334 .title("Send Message Between Slots")
1335 .description("Send a message from one slot to another. Returns the message ID.")
1336 .handler(move |input: SendMessageInput| {
1337 let state = Arc::clone(&state);
1338 async move {
1339 let from = claude_pool::types::SlotId(input.from);
1340 let to = claude_pool::types::SlotId(input.to);
1341 let message_id = state.pool.send_message(from, to, input.content);
1342 Ok(CallToolResult::json(serde_json::json!({
1343 "message_id": message_id,
1344 })))
1345 }
1346 })
1347 .build()
1348}
1349
1350pub fn pool_read_messages_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1351 ToolBuilder::new("pool_read_messages")
1352 .title("Read Messages from Slot")
1353 .description("Drain and read all messages for a slot, removing them from the inbox.")
1354 .handler(move |input: ReadMessagesInput| {
1355 let state = Arc::clone(&state);
1356 async move {
1357 let slot_id = claude_pool::types::SlotId(input.slot_id);
1358 let messages = state.pool.read_messages(&slot_id);
1359 Ok(CallToolResult::json(
1360 serde_json::to_value(&messages).unwrap(),
1361 ))
1362 }
1363 })
1364 .build()
1365}
1366
1367pub fn pool_peek_messages_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1368 ToolBuilder::new("pool_peek_messages")
1369 .title("Peek Messages from Slot")
1370 .description("Read messages from a slot's inbox without removing them.")
1371 .read_only()
1372 .handler(move |input: PeekMessagesInput| {
1373 let state = Arc::clone(&state);
1374 async move {
1375 let slot_id = claude_pool::types::SlotId(input.slot_id);
1376 let messages = state.pool.peek_messages(&slot_id);
1377 Ok(CallToolResult::json(
1378 serde_json::to_value(&messages).unwrap(),
1379 ))
1380 }
1381 })
1382 .build()
1383}
1384
1385pub fn pool_broadcast_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1386 ToolBuilder::new("pool_broadcast")
1387 .title("Broadcast Message to All Slots")
1388 .description(
1389 "Send a message from one slot to all other active slots. Returns the list of message IDs.",
1390 )
1391 .handler(move |input: BroadcastInput| {
1392 let state = Arc::clone(&state);
1393 async move {
1394 let from = claude_pool::types::SlotId(input.from);
1395 match state.pool.broadcast_message(from, input.content).await {
1396 Ok(ids) => {
1397 let count = ids.len();
1398 Ok(CallToolResult::json(serde_json::json!({
1399 "message_ids": ids,
1400 "recipients": count,
1401 })))
1402 }
1403 Err(e) => Ok(CallToolResult::error(e.to_string())),
1404 }
1405 }
1406 })
1407 .build()
1408}
1409
1410pub fn pool_find_slots_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1411 ToolBuilder::new("pool_find_slots")
1412 .title("Find Slots by Name, Role, or State")
1413 .description(
1414 "Query slots by name, role, and/or state. All filters are optional; omitted filters match everything.",
1415 )
1416 .read_only()
1417 .handler(move |input: FindSlotsInput| {
1418 let state = Arc::clone(&state);
1419 async move {
1420 let slot_state = input.state.as_deref().and_then(|s| match s {
1421 "idle" => Some(claude_pool::types::SlotState::Idle),
1422 "busy" => Some(claude_pool::types::SlotState::Busy),
1423 "stopped" => Some(claude_pool::types::SlotState::Stopped),
1424 "errored" => Some(claude_pool::types::SlotState::Errored),
1425 _ => None,
1426 });
1427 match state
1428 .pool
1429 .find_slots(input.name.as_deref(), input.role.as_deref(), slot_state)
1430 .await
1431 {
1432 Ok(slots) => {
1433 let results: Vec<_> = slots
1434 .iter()
1435 .map(|s| {
1436 serde_json::json!({
1437 "id": s.id.0,
1438 "state": s.state,
1439 "name": s.config.name,
1440 "role": s.config.role,
1441 "description": s.config.description,
1442 "current_task": s.current_task.as_ref().map(|t| &t.0),
1443 "tasks_completed": s.tasks_completed,
1444 "cost_microdollars": s.cost_microdollars,
1445 })
1446 })
1447 .collect();
1448 Ok(CallToolResult::json(serde_json::json!({
1449 "slots": results,
1450 "count": results.len(),
1451 })))
1452 }
1453 Err(e) => Ok(CallToolResult::error(e.to_string())),
1454 }
1455 }
1456 })
1457 .build()
1458}
1459
1460pub fn pool_claim_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1461 ToolBuilder::new("pool_claim")
1462 .title("Claim Next Pending Task")
1463 .description(
1464 "Self-service task claiming: an idle slot grabs the next pending task from the queue. \
1465 Returns the claimed task ID, or null if no tasks are waiting. The task executes \
1466 in the background on the claiming slot.",
1467 )
1468 .handler(move |input: ClaimInput| {
1469 let state = Arc::clone(&state);
1470 async move {
1471 let slot_id = claude_pool::types::SlotId(input.slot_id);
1472 match state.pool.claim(&slot_id).await {
1473 Ok(Some(task_id)) => Ok(CallToolResult::json(serde_json::json!({
1474 "claimed": true,
1475 "task_id": task_id.0,
1476 }))),
1477 Ok(None) => Ok(CallToolResult::json(serde_json::json!({
1478 "claimed": false,
1479 "task_id": null,
1480 "reason": "no pending tasks or slot not idle",
1481 }))),
1482 Err(e) => Ok(CallToolResult::error(e.to_string())),
1483 }
1484 }
1485 })
1486 .build()
1487}
1488
1489pub fn pool_submit_with_review_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1490 ToolBuilder::new("pool_submit_with_review")
1491 .title("Fire a Task with Review Gate")
1492 .description(
1493 "Fire off a task that requires coordinator approval before completion. \
1494 When the task finishes, it enters 'pending_review' state instead of 'completed'. \
1495 Use pool_approve_result to accept or pool_reject_result to reject with feedback.",
1496 )
1497 .handler(move |input: SubmitWithReviewInput| {
1498 let state = Arc::clone(&state);
1499 async move {
1500 let config = task_config_from(input.model, input.effort, input.mcp_servers);
1501 let tags = input.tags.unwrap_or_default();
1502 match state
1503 .pool
1504 .submit_with_review(&input.prompt, config, tags, input.max_rejections)
1505 .await
1506 {
1507 Ok(task_id) => Ok(CallToolResult::json(
1508 serde_json::json!({ "task_id": task_id.0, "review_required": true }),
1509 )),
1510 Err(e) => Ok(CallToolResult::error(e.to_string())),
1511 }
1512 }
1513 })
1514 .build()
1515}
1516
1517pub fn pool_approve_result_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1518 ToolBuilder::new("pool_approve_result")
1519 .title("Approve Task Result")
1520 .description(
1521 "Approve a task that is pending review. Transitions the task from \
1522 'pending_review' to 'completed'.",
1523 )
1524 .handler(move |input: ApproveResultInput| {
1525 let state = Arc::clone(&state);
1526 async move {
1527 let task_id = claude_pool::TaskId(input.task_id);
1528 match state.pool.approve_result(&task_id).await {
1529 Ok(()) => Ok(CallToolResult::text("approved")),
1530 Err(e) => Ok(CallToolResult::error(e.to_string())),
1531 }
1532 }
1533 })
1534 .build()
1535}
1536
1537pub fn pool_reject_result_tool<S: PoolStore + 'static>(state: Arc<State<S>>) -> Tool {
1538 ToolBuilder::new("pool_reject_result")
1539 .title("Reject Task Result")
1540 .description(
1541 "Reject a task that is pending review. The task is re-queued with the \
1542 original prompt plus rejection feedback appended. If the task has been \
1543 rejected max_rejections times, it is marked as failed.",
1544 )
1545 .handler(move |input: RejectResultInput| {
1546 let state = Arc::clone(&state);
1547 async move {
1548 let task_id = claude_pool::TaskId(input.task_id);
1549 match state.pool.reject_result(&task_id, &input.feedback).await {
1550 Ok(()) => Ok(CallToolResult::text("rejected and re-queued")),
1551 Err(e) => Ok(CallToolResult::error(e.to_string())),
1552 }
1553 }
1554 })
1555 .build()
1556}
1557
1558pub fn all_tools<S: PoolStore + 'static>(state: &Arc<State<S>>) -> Vec<Tool> {
1559 vec![
1560 pool_status_tool(Arc::clone(state)),
1561 pool_run_tool(Arc::clone(state)),
1562 pool_submit_tool(Arc::clone(state)),
1563 pool_result_tool(Arc::clone(state)),
1564 pool_cancel_tool(Arc::clone(state)),
1565 pool_fan_out_tool(Arc::clone(state)),
1566 pool_drain_tool(Arc::clone(state)),
1567 pool_skill_run_tool(Arc::clone(state)),
1568 pool_chain_tool(Arc::clone(state)),
1569 pool_submit_chain_tool(Arc::clone(state)),
1570 pool_fan_out_chains_tool(Arc::clone(state)),
1571 pool_chain_result_tool(Arc::clone(state)),
1572 pool_cancel_chain_tool(Arc::clone(state)),
1573 pool_invoke_workflow_tool(Arc::clone(state)),
1574 pool_scale_up_tool(Arc::clone(state)),
1575 pool_scale_down_tool(Arc::clone(state)),
1576 pool_set_target_slots_tool(Arc::clone(state)),
1577 context_set_tool(Arc::clone(state)),
1578 context_get_tool(Arc::clone(state)),
1579 context_delete_tool(Arc::clone(state)),
1580 context_list_tool(Arc::clone(state)),
1581 pool_send_message_tool(Arc::clone(state)),
1582 pool_read_messages_tool(Arc::clone(state)),
1583 pool_peek_messages_tool(Arc::clone(state)),
1584 pool_broadcast_tool(Arc::clone(state)),
1585 pool_find_slots_tool(Arc::clone(state)),
1586 pool_configure_slot_tool(Arc::clone(state)),
1587 pool_skill_list_tool(Arc::clone(state)),
1588 pool_skill_get_tool(Arc::clone(state)),
1589 pool_skill_add_tool(Arc::clone(state)),
1590 pool_skill_remove_tool(Arc::clone(state)),
1591 pool_skill_save_tool(Arc::clone(state)),
1592 pool_skill_eject_tool(Arc::clone(state)),
1593 pool_claim_tool(Arc::clone(state)),
1594 pool_submit_with_review_tool(Arc::clone(state)),
1595 pool_approve_result_tool(Arc::clone(state)),
1596 pool_reject_result_tool(Arc::clone(state)),
1597 ]
1598}