feat(tools): wire TaskRegistry into task tool dispatch

Replace all 6 task tool stubs (TaskCreate/Get/List/Stop/Update/Output)
with real TaskRegistry-backed implementations:

- TaskCreate: creates task in global registry, returns real task_id
- TaskGet: retrieves full task state (status, messages, timestamps)
- TaskList: lists all tasks with metadata
- TaskStop: transitions task to stopped state with validation
- TaskUpdate: appends user messages to task message history
- TaskOutput: returns accumulated task output

Global registry uses OnceLock<TaskRegistry> singleton per process.
All existing tests pass (37 tools, 149 runtime, 102 CLI).
This commit is contained in:
Jobdori
2026-04-03 17:26:26 +09:00
parent 21a1e1d479
commit e8692e45c4

View File

@@ -11,14 +11,22 @@ use api::{
use plugins::PluginTool;
use reqwest::blocking::Client;
use runtime::{
edit_file, execute_bash, glob_search, grep_search, load_system_prompt, read_file, write_file,
ApiClient, ApiRequest, AssistantEvent, BashCommandInput, ContentBlock, ConversationMessage,
ConversationRuntime, GrepSearchInput, MessageRole, PermissionMode, PermissionPolicy,
PromptCacheEvent, RuntimeError, Session, ToolError, ToolExecutor,
edit_file, execute_bash, glob_search, grep_search, load_system_prompt, read_file,
task_registry::TaskRegistry, write_file, ApiClient, ApiRequest, AssistantEvent,
BashCommandInput, ContentBlock, ConversationMessage, ConversationRuntime, GrepSearchInput,
MessageRole, PermissionMode, PermissionPolicy, PromptCacheEvent, RuntimeError, Session,
ToolError, ToolExecutor,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
/// Global task registry shared across tool invocations within a session.
fn global_task_registry() -> &'static TaskRegistry {
use std::sync::OnceLock;
static REGISTRY: OnceLock<TaskRegistry> = OnceLock::new();
REGISTRY.get_or_init(TaskRegistry::new)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolManifestEntry {
pub name: String,
@@ -905,60 +913,96 @@ fn run_ask_user_question(input: AskUserQuestionInput) -> Result<String, String>
#[allow(clippy::needless_pass_by_value)]
fn run_task_create(input: TaskCreateInput) -> Result<String, String> {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let task_id = format!("task_{secs:08x}");
let registry = global_task_registry();
let task = registry.create(&input.prompt, input.description.as_deref());
to_pretty_json(json!({
"task_id": task_id,
"status": "created",
"prompt": input.prompt,
"description": input.description
"task_id": task.task_id,
"status": task.status,
"prompt": task.prompt,
"description": task.description,
"created_at": task.created_at
}))
}
#[allow(clippy::needless_pass_by_value)]
fn run_task_get(input: TaskIdInput) -> Result<String, String> {
to_pretty_json(json!({
"task_id": input.task_id,
"status": "unknown",
"message": "Task runtime not yet implemented"
}))
let registry = global_task_registry();
match registry.get(&input.task_id) {
Some(task) => to_pretty_json(json!({
"task_id": task.task_id,
"status": task.status,
"prompt": task.prompt,
"description": task.description,
"created_at": task.created_at,
"updated_at": task.updated_at,
"messages": task.messages,
"team_id": task.team_id
})),
None => Err(format!("task not found: {}", input.task_id)),
}
}
fn run_task_list(_input: Value) -> Result<String, String> {
let registry = global_task_registry();
let tasks: Vec<_> = registry
.list(None)
.into_iter()
.map(|t| {
json!({
"task_id": t.task_id,
"status": t.status,
"prompt": t.prompt,
"description": t.description,
"created_at": t.created_at,
"updated_at": t.updated_at,
"team_id": t.team_id
})
})
.collect();
to_pretty_json(json!({
"tasks": [],
"message": "No tasks found"
"tasks": tasks,
"count": tasks.len()
}))
}
#[allow(clippy::needless_pass_by_value)]
fn run_task_stop(input: TaskIdInput) -> Result<String, String> {
to_pretty_json(json!({
"task_id": input.task_id,
"status": "stopped",
"message": "Task stop requested"
}))
let registry = global_task_registry();
match registry.stop(&input.task_id) {
Ok(task) => to_pretty_json(json!({
"task_id": task.task_id,
"status": task.status,
"message": "Task stopped"
})),
Err(e) => Err(e),
}
}
#[allow(clippy::needless_pass_by_value)]
fn run_task_update(input: TaskUpdateInput) -> Result<String, String> {
to_pretty_json(json!({
"task_id": input.task_id,
"status": "updated",
"message": input.message
}))
let registry = global_task_registry();
match registry.update(&input.task_id, &input.message) {
Ok(task) => to_pretty_json(json!({
"task_id": task.task_id,
"status": task.status,
"message_count": task.messages.len(),
"last_message": input.message
})),
Err(e) => Err(e),
}
}
#[allow(clippy::needless_pass_by_value)]
fn run_task_output(input: TaskIdInput) -> Result<String, String> {
to_pretty_json(json!({
"task_id": input.task_id,
"output": "",
"message": "No output available"
}))
let registry = global_task_registry();
match registry.output(&input.task_id) {
Ok(output) => to_pretty_json(json!({
"task_id": input.task_id,
"output": output,
"has_output": !output.is_empty()
})),
Err(e) => Err(e),
}
}
#[allow(clippy::needless_pass_by_value)]