ctrlcode 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ctrlcode/__init__.py +8 -0
- ctrlcode/agents/__init__.py +29 -0
- ctrlcode/agents/cleanup.py +388 -0
- ctrlcode/agents/communication.py +439 -0
- ctrlcode/agents/observability.py +421 -0
- ctrlcode/agents/react_loop.py +297 -0
- ctrlcode/agents/registry.py +211 -0
- ctrlcode/agents/result_parser.py +242 -0
- ctrlcode/agents/workflow.py +723 -0
- ctrlcode/analysis/__init__.py +28 -0
- ctrlcode/analysis/ast_diff.py +163 -0
- ctrlcode/analysis/bug_detector.py +149 -0
- ctrlcode/analysis/code_graphs.py +329 -0
- ctrlcode/analysis/semantic.py +205 -0
- ctrlcode/analysis/static.py +183 -0
- ctrlcode/analysis/synthesizer.py +281 -0
- ctrlcode/analysis/tests.py +189 -0
- ctrlcode/cleanup/__init__.py +16 -0
- ctrlcode/cleanup/auto_merge.py +350 -0
- ctrlcode/cleanup/doc_gardening.py +388 -0
- ctrlcode/cleanup/pr_automation.py +330 -0
- ctrlcode/cleanup/scheduler.py +356 -0
- ctrlcode/config.py +380 -0
- ctrlcode/embeddings/__init__.py +6 -0
- ctrlcode/embeddings/embedder.py +192 -0
- ctrlcode/embeddings/vector_store.py +213 -0
- ctrlcode/fuzzing/__init__.py +24 -0
- ctrlcode/fuzzing/analyzer.py +280 -0
- ctrlcode/fuzzing/budget.py +112 -0
- ctrlcode/fuzzing/context.py +665 -0
- ctrlcode/fuzzing/context_fuzzer.py +506 -0
- ctrlcode/fuzzing/derived_orchestrator.py +732 -0
- ctrlcode/fuzzing/oracle_adapter.py +135 -0
- ctrlcode/linters/__init__.py +11 -0
- ctrlcode/linters/hand_rolled_utils.py +221 -0
- ctrlcode/linters/yolo_parsing.py +217 -0
- ctrlcode/metrics/__init__.py +6 -0
- ctrlcode/metrics/dashboard.py +283 -0
- ctrlcode/metrics/tech_debt.py +663 -0
- ctrlcode/paths.py +68 -0
- ctrlcode/permissions.py +179 -0
- ctrlcode/providers/__init__.py +15 -0
- ctrlcode/providers/anthropic.py +138 -0
- ctrlcode/providers/base.py +77 -0
- ctrlcode/providers/openai.py +197 -0
- ctrlcode/providers/parallel.py +104 -0
- ctrlcode/server.py +871 -0
- ctrlcode/session/__init__.py +6 -0
- ctrlcode/session/baseline.py +57 -0
- ctrlcode/session/manager.py +967 -0
- ctrlcode/skills/__init__.py +10 -0
- ctrlcode/skills/builtin/commit.toml +29 -0
- ctrlcode/skills/builtin/docs.toml +25 -0
- ctrlcode/skills/builtin/refactor.toml +33 -0
- ctrlcode/skills/builtin/review.toml +28 -0
- ctrlcode/skills/builtin/test.toml +28 -0
- ctrlcode/skills/loader.py +111 -0
- ctrlcode/skills/registry.py +139 -0
- ctrlcode/storage/__init__.py +19 -0
- ctrlcode/storage/history_db.py +708 -0
- ctrlcode/tools/__init__.py +220 -0
- ctrlcode/tools/bash.py +112 -0
- ctrlcode/tools/browser.py +352 -0
- ctrlcode/tools/executor.py +153 -0
- ctrlcode/tools/explore.py +486 -0
- ctrlcode/tools/mcp.py +108 -0
- ctrlcode/tools/observability.py +561 -0
- ctrlcode/tools/registry.py +193 -0
- ctrlcode/tools/todo.py +291 -0
- ctrlcode/tools/update.py +266 -0
- ctrlcode/tools/webfetch.py +147 -0
- ctrlcode-0.1.0.dist-info/METADATA +93 -0
- ctrlcode-0.1.0.dist-info/RECORD +75 -0
- ctrlcode-0.1.0.dist-info/WHEEL +4 -0
- ctrlcode-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Tool registry for managing MCP servers and tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional, Callable
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .mcp import MCPClient, MCPTool
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BuiltinTool:
|
|
14
|
+
"""Built-in tool definition."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
description: str
|
|
18
|
+
input_schema: dict[str, Any]
|
|
19
|
+
function: Callable
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ToolRegistry:
|
|
23
|
+
"""Registry for MCP and built-in tools."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize tool registry."""
|
|
27
|
+
self.clients: dict[str, MCPClient] = {}
|
|
28
|
+
self.mcp_tools: dict[str, MCPTool] = {} # tool_name -> MCPTool
|
|
29
|
+
self.builtin_tools: dict[str, BuiltinTool] = {} # tool_name -> BuiltinTool
|
|
30
|
+
|
|
31
|
+
async def add_server(self, server_config: dict[str, Any]) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Add and start an MCP server.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
server_config: Server configuration
|
|
37
|
+
"""
|
|
38
|
+
server_name = server_config.get("name", "unknown")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
client = MCPClient(server_config)
|
|
42
|
+
await client.start()
|
|
43
|
+
|
|
44
|
+
# List tools
|
|
45
|
+
tools = await client.list_tools()
|
|
46
|
+
logger.info(f"Server '{server_name}' provides {len(tools)} tools")
|
|
47
|
+
|
|
48
|
+
# Register tools
|
|
49
|
+
for tool in tools:
|
|
50
|
+
if tool.name in self.mcp_tools or tool.name in self.builtin_tools:
|
|
51
|
+
logger.warning(f"Tool '{tool.name}' already registered, skipping")
|
|
52
|
+
else:
|
|
53
|
+
self.mcp_tools[tool.name] = tool
|
|
54
|
+
|
|
55
|
+
# Store client
|
|
56
|
+
self.clients[server_name] = client
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Failed to start server '{server_name}': {e}")
|
|
60
|
+
|
|
61
|
+
def register_builtin(
|
|
62
|
+
self,
|
|
63
|
+
name: str,
|
|
64
|
+
description: str,
|
|
65
|
+
input_schema: dict[str, Any],
|
|
66
|
+
function: Callable,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Register a built-in tool.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
name: Tool name
|
|
73
|
+
description: Tool description
|
|
74
|
+
input_schema: JSON schema for tool inputs
|
|
75
|
+
function: Callable that implements the tool
|
|
76
|
+
"""
|
|
77
|
+
if name in self.mcp_tools or name in self.builtin_tools:
|
|
78
|
+
logger.warning(f"Tool '{name}' already registered, skipping")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
tool = BuiltinTool(
|
|
82
|
+
name=name,
|
|
83
|
+
description=description,
|
|
84
|
+
input_schema=input_schema,
|
|
85
|
+
function=function,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
self.builtin_tools[name] = tool
|
|
89
|
+
logger.info(f"Registered built-in tool: {name}")
|
|
90
|
+
|
|
91
|
+
async def remove_server(self, server_name: str) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Remove and stop an MCP server.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
server_name: Name of server to remove
|
|
97
|
+
"""
|
|
98
|
+
if server_name in self.clients:
|
|
99
|
+
client = self.clients[server_name]
|
|
100
|
+
await client.stop()
|
|
101
|
+
del self.clients[server_name]
|
|
102
|
+
|
|
103
|
+
# Remove tools from this server
|
|
104
|
+
self.mcp_tools = {
|
|
105
|
+
name: tool
|
|
106
|
+
for name, tool in self.mcp_tools.items()
|
|
107
|
+
if tool.server_name != server_name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
def get_tool(self, tool_name: str) -> Optional[MCPTool | BuiltinTool]:
|
|
111
|
+
"""
|
|
112
|
+
Get tool by name (MCP or built-in).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
tool_name: Name of tool
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Tool or None if not found
|
|
119
|
+
"""
|
|
120
|
+
return self.mcp_tools.get(tool_name) or self.builtin_tools.get(tool_name)
|
|
121
|
+
|
|
122
|
+
def is_builtin(self, tool_name: str) -> bool:
|
|
123
|
+
"""Check if tool is built-in."""
|
|
124
|
+
return tool_name in self.builtin_tools
|
|
125
|
+
|
|
126
|
+
def list_tools(self) -> list[MCPTool | BuiltinTool]:
|
|
127
|
+
"""
|
|
128
|
+
List all available tools (MCP and built-in).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of all registered tools
|
|
132
|
+
"""
|
|
133
|
+
return list(self.mcp_tools.values()) + list(self.builtin_tools.values())
|
|
134
|
+
|
|
135
|
+
def get_client(self, server_name: str) -> Optional[MCPClient]:
|
|
136
|
+
"""
|
|
137
|
+
Get MCP client by server name.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
server_name: Server name
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Client or None if not found
|
|
144
|
+
"""
|
|
145
|
+
return self.clients.get(server_name)
|
|
146
|
+
|
|
147
|
+
def get_tool_definitions(self) -> list[dict[str, Any]]:
|
|
148
|
+
"""
|
|
149
|
+
Get tool definitions in provider format (both MCP and built-in).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of tool definitions for LLM providers
|
|
153
|
+
"""
|
|
154
|
+
definitions = []
|
|
155
|
+
|
|
156
|
+
# MCP tools
|
|
157
|
+
for tool in self.mcp_tools.values():
|
|
158
|
+
definitions.append({
|
|
159
|
+
"name": tool.name,
|
|
160
|
+
"description": tool.description,
|
|
161
|
+
"input_schema": tool.input_schema,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
# Built-in tools
|
|
165
|
+
for tool in self.builtin_tools.values():
|
|
166
|
+
definitions.append({
|
|
167
|
+
"name": tool.name,
|
|
168
|
+
"description": tool.description,
|
|
169
|
+
"input_schema": tool.input_schema,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
return definitions
|
|
173
|
+
|
|
174
|
+
def get_tool_definitions_filtered(self, allowed_tools: list[str]) -> list[dict[str, Any]]:
|
|
175
|
+
"""
|
|
176
|
+
Get tool definitions filtered by allowed tool names.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
allowed_tools: List of tool names to include
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Filtered list of tool definitions
|
|
183
|
+
"""
|
|
184
|
+
all_tools = self.get_tool_definitions()
|
|
185
|
+
return [tool for tool in all_tools if tool["name"] in allowed_tools]
|
|
186
|
+
|
|
187
|
+
async def close_all(self) -> None:
|
|
188
|
+
"""Close all MCP clients."""
|
|
189
|
+
for client in self.clients.values():
|
|
190
|
+
await client.stop()
|
|
191
|
+
self.clients.clear()
|
|
192
|
+
self.mcp_tools.clear()
|
|
193
|
+
self.builtin_tools.clear()
|
ctrlcode/tools/todo.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Built-in todo list tools for task management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import fcntl
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from time import time
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TodoTools:
|
|
12
|
+
"""Built-in tools for managing todo lists."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, data_dir: str | Path):
|
|
15
|
+
"""
|
|
16
|
+
Initialize todo tools.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
data_dir: Data directory for storing todos
|
|
20
|
+
"""
|
|
21
|
+
self.data_dir = Path(data_dir)
|
|
22
|
+
self.todos_dir = self.data_dir / "todos"
|
|
23
|
+
self.todos_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
def _get_todo_file(self, session_id: str | None) -> Path:
|
|
26
|
+
"""Get path to todo file for session."""
|
|
27
|
+
if session_id:
|
|
28
|
+
return self.todos_dir / f"{session_id}.json"
|
|
29
|
+
return self.todos_dir / "global.json"
|
|
30
|
+
|
|
31
|
+
def _read_todos(self, file_path: Path) -> list[dict]:
|
|
32
|
+
"""Read todos from file with locking."""
|
|
33
|
+
if not file_path.exists():
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with open(file_path, "r") as f:
|
|
38
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
|
|
39
|
+
try:
|
|
40
|
+
data = json.load(f)
|
|
41
|
+
return data if isinstance(data, list) else []
|
|
42
|
+
finally:
|
|
43
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
44
|
+
except (json.JSONDecodeError, IOError):
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
def _write_todos(self, file_path: Path, todos: list[dict]) -> None:
|
|
48
|
+
"""Write todos to file atomically with locking."""
|
|
49
|
+
temp_path = file_path.with_suffix(".tmp")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
# Write to temp file with exclusive lock
|
|
53
|
+
with open(temp_path, "w") as f:
|
|
54
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
|
55
|
+
try:
|
|
56
|
+
json.dump(todos, f, indent=2)
|
|
57
|
+
f.flush()
|
|
58
|
+
finally:
|
|
59
|
+
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
|
60
|
+
|
|
61
|
+
# Atomic rename
|
|
62
|
+
temp_path.replace(file_path)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
if temp_path.exists():
|
|
65
|
+
temp_path.unlink()
|
|
66
|
+
raise e
|
|
67
|
+
|
|
68
|
+
def todo_write(
|
|
69
|
+
self, items: list[dict[str, Any]], session_id: str | None = None
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Create or append todo items.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
items: List of todo items with 'content' and optional 'activeForm', 'priority', 'status'
|
|
76
|
+
session_id: Optional session ID for session-specific todos
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Dict with success status and created todos
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
file_path = self._get_todo_file(session_id)
|
|
83
|
+
todos = self._read_todos(file_path)
|
|
84
|
+
current_time = time()
|
|
85
|
+
|
|
86
|
+
created = []
|
|
87
|
+
for item in items:
|
|
88
|
+
if not item.get("content"):
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
todo = {
|
|
92
|
+
"id": str(uuid.uuid4()),
|
|
93
|
+
"content": item["content"],
|
|
94
|
+
"status": item.get("status", "pending"),
|
|
95
|
+
"activeForm": item.get("activeForm", item["content"]),
|
|
96
|
+
"priority": item.get("priority", "medium"),
|
|
97
|
+
"created_at": current_time,
|
|
98
|
+
"updated_at": current_time,
|
|
99
|
+
}
|
|
100
|
+
todos.append(todo)
|
|
101
|
+
created.append(todo)
|
|
102
|
+
|
|
103
|
+
self._write_todos(file_path, todos)
|
|
104
|
+
return {"success": True, "todos": created, "error": None}
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return {"success": False, "todos": [], "error": str(e)}
|
|
108
|
+
|
|
109
|
+
def todo_list(
|
|
110
|
+
self, session_id: str | None = None, status: str | None = None
|
|
111
|
+
) -> dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
List todo items with optional filtering.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
session_id: Optional session ID for session-specific todos
|
|
117
|
+
status: Optional status filter (pending, in_progress, completed)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dict with success status and todos list
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
file_path = self._get_todo_file(session_id)
|
|
124
|
+
todos = self._read_todos(file_path)
|
|
125
|
+
|
|
126
|
+
if status:
|
|
127
|
+
todos = [t for t in todos if t.get("status") == status]
|
|
128
|
+
|
|
129
|
+
return {"success": True, "todos": todos, "error": None}
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return {"success": False, "todos": [], "error": str(e)}
|
|
133
|
+
|
|
134
|
+
def todo_update(
|
|
135
|
+
self,
|
|
136
|
+
id: str,
|
|
137
|
+
status: str | None = None,
|
|
138
|
+
content: str | None = None,
|
|
139
|
+
activeForm: str | None = None,
|
|
140
|
+
priority: str | None = None,
|
|
141
|
+
session_id: str | None = None,
|
|
142
|
+
) -> dict[str, Any]:
|
|
143
|
+
"""
|
|
144
|
+
Update an existing todo item.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
id: UUID of the todo to update
|
|
148
|
+
status: New status (pending, in_progress, completed)
|
|
149
|
+
content: New content
|
|
150
|
+
activeForm: Present continuous form shown when in progress
|
|
151
|
+
priority: New priority (high, medium, low)
|
|
152
|
+
session_id: Optional session ID for session-specific todos
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Dict with success status and updated todo
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
file_path = self._get_todo_file(session_id)
|
|
159
|
+
todos = self._read_todos(file_path)
|
|
160
|
+
|
|
161
|
+
todo_found = None
|
|
162
|
+
for todo in todos:
|
|
163
|
+
if todo.get("id") == id:
|
|
164
|
+
todo_found = todo
|
|
165
|
+
if status is not None:
|
|
166
|
+
todo["status"] = status
|
|
167
|
+
if content is not None:
|
|
168
|
+
todo["content"] = content
|
|
169
|
+
if activeForm is not None:
|
|
170
|
+
todo["activeForm"] = activeForm
|
|
171
|
+
if priority is not None:
|
|
172
|
+
todo["priority"] = priority
|
|
173
|
+
todo["updated_at"] = time()
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
if not todo_found:
|
|
177
|
+
return {
|
|
178
|
+
"success": False,
|
|
179
|
+
"todos": [],
|
|
180
|
+
"error": f"Todo with id '{id}' not found",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
self._write_todos(file_path, todos)
|
|
184
|
+
return {"success": True, "todos": [todo_found], "error": None}
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return {"success": False, "todos": [], "error": str(e)}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Tool schemas for LLM providers (Anthropic/OpenAI format)
|
|
191
|
+
TODO_TOOL_SCHEMAS = [
|
|
192
|
+
{
|
|
193
|
+
"name": "todo_write",
|
|
194
|
+
"description": "Create or append todo items for task tracking. Use this when planning work or breaking down complex tasks.",
|
|
195
|
+
"input_schema": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"properties": {
|
|
198
|
+
"items": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"description": "List of todo items to create",
|
|
201
|
+
"items": {
|
|
202
|
+
"type": "object",
|
|
203
|
+
"properties": {
|
|
204
|
+
"content": {
|
|
205
|
+
"type": "string",
|
|
206
|
+
"description": "Task description (brief, actionable)",
|
|
207
|
+
},
|
|
208
|
+
"activeForm": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Present continuous form shown when in progress (e.g., 'Running tests')",
|
|
211
|
+
},
|
|
212
|
+
"priority": {
|
|
213
|
+
"type": "string",
|
|
214
|
+
"description": "Task priority",
|
|
215
|
+
"enum": ["high", "medium", "low"],
|
|
216
|
+
"default": "medium",
|
|
217
|
+
},
|
|
218
|
+
"status": {
|
|
219
|
+
"type": "string",
|
|
220
|
+
"description": "Initial status",
|
|
221
|
+
"enum": ["pending", "in_progress", "completed"],
|
|
222
|
+
"default": "pending",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
"required": ["content"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
"session_id": {
|
|
229
|
+
"type": "string",
|
|
230
|
+
"description": "Optional session ID for session-specific todos",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
"required": ["items"],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"name": "todo_list",
|
|
238
|
+
"description": "List todos with optional filtering by status. Use this to check what tasks are pending or track progress.",
|
|
239
|
+
"input_schema": {
|
|
240
|
+
"type": "object",
|
|
241
|
+
"properties": {
|
|
242
|
+
"session_id": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"description": "Optional session ID for session-specific todos",
|
|
245
|
+
},
|
|
246
|
+
"status": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"description": "Filter by status",
|
|
249
|
+
"enum": ["pending", "in_progress", "completed"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
"required": [],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "todo_update",
|
|
257
|
+
"description": "Update an existing todo item. Use this to mark tasks as in progress or completed, or modify task details.",
|
|
258
|
+
"input_schema": {
|
|
259
|
+
"type": "object",
|
|
260
|
+
"properties": {
|
|
261
|
+
"id": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"description": "UUID of the todo to update",
|
|
264
|
+
},
|
|
265
|
+
"status": {
|
|
266
|
+
"type": "string",
|
|
267
|
+
"description": "New status",
|
|
268
|
+
"enum": ["pending", "in_progress", "completed"],
|
|
269
|
+
},
|
|
270
|
+
"content": {
|
|
271
|
+
"type": "string",
|
|
272
|
+
"description": "New task description",
|
|
273
|
+
},
|
|
274
|
+
"activeForm": {
|
|
275
|
+
"type": "string",
|
|
276
|
+
"description": "Present continuous form shown when in progress (e.g., 'Running tests')",
|
|
277
|
+
},
|
|
278
|
+
"priority": {
|
|
279
|
+
"type": "string",
|
|
280
|
+
"description": "New priority",
|
|
281
|
+
"enum": ["high", "medium", "low"],
|
|
282
|
+
},
|
|
283
|
+
"session_id": {
|
|
284
|
+
"type": "string",
|
|
285
|
+
"description": "Optional session ID for session-specific todos",
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
"required": ["id"],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
]
|