Files
Cortex-Inara/cortex/tools/ae_tasks.py
Scott Idem eab92d876d refactor: split tool declarations into domain files + role config UI
tools/__init__.py shrinks from 1,137 → 250 lines. Each domain file now
owns both its callables and its FunctionDeclarations (DECLARATIONS list),
so adding a new tool only touches one file.

New TOOL_CATEGORIES dict exported from __init__ — used by the UI for
grouped tool checkboxes.

Role config UI (Settings → Model Registry → Role Assignments):
- ⚙ button per role expands an inline configure panel
- Textarea for system_append (injected into system prompt for this role)
- Grouped checkboxes for tool allow-list (all checked = no restriction)
- POST /api/models/role-config saves both fields; updates ROLE_CONFIG_DATA
  in-page so re-open reflects current state without a page reload

Backend:
- model_registry.set_role_config() writes system_append + tools to registry
- TOOL_CATEGORIES exported from tools/__init__ for UI rendering
- TOOLS.md header updated: 30 → 39 tools (ae_journal_* and cortex_* additions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 20:40:50 -04:00

120 lines
3.6 KiB
Python

"""
Aether task list tool — reads the agents_sync Kanban board.
Reads task JSON files directly from the agents_sync filesystem rather than
making an HTTP call, since the tasks directory is always locally available
(synced via Syncthing). This avoids needing a separate API endpoint for tasks.
Structure:
agents_sync/tasks/01_todo/ — pending tasks
agents_sync/tasks/02_in_progress/ — active tasks
agents_sync/tasks/03_done/ — completed tasks (not included by default)
"""
import asyncio
import json
import logging
from pathlib import Path
from google.genai import types
logger = logging.getLogger(__name__)
# Resolved at import time — agents_sync is always at ~/agents_sync on this machine.
# If the path doesn't exist the tool returns a helpful error rather than crashing.
_AGENTS_SYNC = Path.home() / "agents_sync"
_TASKS_ROOT = _AGENTS_SYNC / "tasks"
async def task_list(include_done: bool = False) -> str:
"""List tasks from the agents_sync Kanban board.
Reads the todo and in_progress buckets (and optionally done).
Returns a markdown summary grouped by status.
Args:
include_done: If True, also include completed tasks (can be noisy).
"""
return await asyncio.to_thread(_sync_task_list, include_done)
def _sync_task_list(include_done: bool) -> str:
if not _TASKS_ROOT.exists():
return f"Task directory not found: {_TASKS_ROOT}"
buckets = [
("01_todo", "Todo"),
("02_in_progress", "In Progress"),
]
if include_done:
buckets.append(("03_done", "Done"))
sections: list[str] = []
total = 0
for dir_name, label in buckets:
bucket_dir = _TASKS_ROOT / dir_name
if not bucket_dir.exists():
continue
tasks = _read_bucket(bucket_dir)
total += len(tasks)
if not tasks:
continue
lines = [f"## {label} ({len(tasks)})\n"]
for task in tasks:
title = task.get("title") or task.get("name") or "(untitled)"
assigned = task.get("assigned_to") or ""
task_id = task.get("id") or ""
desc = task.get("description") or ""
header = f"- **{title}**"
if assigned:
header += f" (assigned: {assigned})"
if task_id:
header += f" — `{task_id}`"
lines.append(header)
if desc:
# First sentence / 120 chars of description
short = desc.split(".")[0][:120]
lines.append(f" {short}")
sections.append("\n".join(lines))
if not sections:
return "No tasks found on the Kanban board."
header_line = f"# Kanban Board — {total} task(s)\n"
return header_line + "\n\n".join(sections)
def _read_bucket(bucket_dir: Path) -> list[dict]:
"""Read and parse all JSON task files in a bucket directory."""
tasks = []
for path in sorted(bucket_dir.glob("*.json")):
try:
data = json.loads(path.read_text())
tasks.append(data)
except Exception as e:
logger.warning("Failed to read task file %s: %s", path, e)
return tasks
DECLARATIONS = [
types.FunctionDeclaration(
name="ae_task_list",
description=(
"List tasks from the agents_sync Kanban board (todo and in-progress). "
"Use this when asked about current work, pending tasks, or project status."
),
parameters=types.Schema(
type=types.Type.OBJECT,
properties={
"include_done": types.Schema(type=types.Type.BOOLEAN, description="If true, also include completed tasks (default false)"),
},
),
),
]