feat: orchestrator Agent mode UI + claude_allow_dir tool + fix DDG search
- Add Agent mode toggle to web UI input row — routes through POST /orchestrate instead of /chat; polls for result with live tool-call count in thinking bubble - Add cortex/tools/system.py with claude_allow_dir tool; registers in tool registry - Fix web search: duckduckgo_search renamed to ddgs, update import + requirements.txt - Allow WebSearch and WebFetch in ~/.claude/settings.json for Claude CLI fallback - Add claude-allow-dir script docs and security note to CLAUDE.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ from tools.ae_knowledge import journal_search as _ae_journal_search
|
||||
from tools.ae_knowledge import journal_entry_create as _ae_journal_entry_create
|
||||
from tools.ae_tasks import task_list as _ae_task_list
|
||||
from tools.files import file_read as _file_read
|
||||
from tools.system import claude_allow_dir as _claude_allow_dir
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -171,8 +172,36 @@ _CALLABLES: dict[str, callable] = {
|
||||
"ae_journal_entry_create": _ae_journal_entry_create,
|
||||
"ae_task_list": _ae_task_list,
|
||||
"file_read": _file_read,
|
||||
"claude_allow_dir": _claude_allow_dir,
|
||||
}
|
||||
|
||||
_claude_allow_dir_declaration = types.FunctionDeclaration(
|
||||
name="claude_allow_dir",
|
||||
description=(
|
||||
"Add a directory to Claude Code's auto-allow list so Claude can read or write "
|
||||
"files there without prompting. Edits ~/.claude/settings.json on the local machine. "
|
||||
"Use this when Claude is silently hanging or being blocked from accessing a directory. "
|
||||
"Changes take effect in the next Claude Code session."
|
||||
),
|
||||
parameters=types.Schema(
|
||||
type=types.Type.OBJECT,
|
||||
properties={
|
||||
"path": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description=(
|
||||
"Absolute or home-relative path to the directory "
|
||||
"(e.g. ~/OSIT_dev/aether_api_fastapi or /home/scott/agents_sync)"
|
||||
),
|
||||
),
|
||||
"mode": types.Schema(
|
||||
type=types.Type.STRING,
|
||||
description="Permission mode: 'r' (read-only), 'w' (write-only), or 'rw' (both). Default: rw",
|
||||
),
|
||||
},
|
||||
required=["path"],
|
||||
),
|
||||
)
|
||||
|
||||
# Gemini Tool object — pass this to GenerateContentConfig
|
||||
TOOL_DECLARATIONS = [
|
||||
types.Tool(function_declarations=[
|
||||
@@ -181,6 +210,7 @@ TOOL_DECLARATIONS = [
|
||||
_ae_journal_entry_create_declaration,
|
||||
_ae_task_list_declaration,
|
||||
_file_read_declaration,
|
||||
_claude_allow_dir_declaration,
|
||||
])
|
||||
]
|
||||
|
||||
|
||||
44
cortex/tools/system.py
Normal file
44
cortex/tools/system.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
System tools — local machine operations.
|
||||
|
||||
These tools affect the host system directly. Use with care.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ALLOW_SCRIPT = "/home/scott/.local/bin/claude-allow-dir"
|
||||
|
||||
|
||||
async def claude_allow_dir(path: str, mode: str = "rw") -> str:
|
||||
"""Add Read/Edit allow rules to ~/.claude/settings.json for a directory.
|
||||
|
||||
Calls the claude-allow-dir script, which edits settings.json directly.
|
||||
Changes take effect in the next Claude Code session (or after /hooks reload).
|
||||
"""
|
||||
if mode not in ("r", "w", "rw"):
|
||||
return f"Error: mode must be r, w, or rw (got '{mode}')"
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"python3", ALLOW_SCRIPT, path, mode,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=10)
|
||||
output = stdout.decode().strip()
|
||||
err = stderr.decode().strip()
|
||||
|
||||
if proc.returncode != 0:
|
||||
logger.warning("claude-allow-dir failed (rc=%d): %s", proc.returncode, err)
|
||||
return f"Failed (exit {proc.returncode}): {err or output}"
|
||||
|
||||
return output or "Done."
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
return "Error: script timed out"
|
||||
except Exception as e:
|
||||
logger.error("claude_allow_dir error: %s", e)
|
||||
return f"Error: {e}"
|
||||
@@ -35,7 +35,7 @@ async def search(query: str, max_results: int | None = None) -> str:
|
||||
|
||||
def _sync_search(query: str, max_results: int) -> list[dict]:
|
||||
"""Synchronous DuckDuckGo search — run via asyncio.to_thread."""
|
||||
from duckduckgo_search import DDGS
|
||||
from ddgs import DDGS
|
||||
|
||||
kwargs = {}
|
||||
if settings.ddg_api_key:
|
||||
|
||||
Reference in New Issue
Block a user