feat: SSH dev routing, model registry UX, chat input toolbar, doc sync

Backend / infrastructure:
- cortex/tools/_projects.py (new): shared project alias registry with ssh_host
  for workstation projects (aether_api, aether_frontend, aether_container)
- cortex/tools/git.py: all git tools route to workstation via SSH when ssh_host set
- cortex/tools/aider.py: aider_run SSH-routes to workstation using bash -l -c
- cortex/routers/local_llm.py: POST /api/models/{id}/edit AJAX endpoint — save
  model edits without page reload or tab reset; returns JSON {ok, label, model_name}
- cortex/llm_client.py: remove Gemini CLI and Claude CLI backends; clean up
  fallback chain and process group tracking (continuation of Gemini CLI removal)
- cortex/routers/auth.py: strip Claude/Gemini CLI auth status checks (CLI removed)
- cortex/routers/chat.py: remove legacy claude/gemini backend fields
- cortex/config.py: clean up CLI-related settings
- cortex/main.py: remove CLI lifecycle hooks

UI:
- cortex/static/local_llm.html: model edit forms now save via fetch() + toast;
  stay on Models tab; update row header label in place on success
- cortex/static/index.html: restructure input area to column layout — textarea
  above, compact toolbar below (Chat/Tools/Attach + Send); fixes dead space at
  M/L/XL sizes; context panel "Role" → "Model" section label
- cortex/static/style.css: column input-area layout; #input-toolbar; flex:1 →
  width:100% on textarea (fixes scrollHeight in column flex context); compact
  send/stop button padding
- cortex/static/app.js: add XL (720px) to height cycle; default M (240px)

Docs:
- cortex/static/HELP.md: S/M/L → S/M/L/XL; add Rebuild to distill table; fix
  "Role selector" references (no such UI); fix "your active role" → Chat role;
  fix  toggle description; Model Registry section cleanup
- documentation/ARCH__BACKENDS.md: reflect CLI removal, current backend state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-18 22:14:07 -04:00
parent 85223326b0
commit b144d8385f
15 changed files with 378 additions and 586 deletions

View File

@@ -16,25 +16,16 @@ background=True runs the subprocess asynchronously and returns an agent_id immed
import asyncio
import logging
import os
import shlex
from pathlib import Path
from google.genai import types
import agent_manager
from ._projects import PROJECT_ALIASES
logger = logging.getLogger(__name__)
_CORTEX_DIR = Path(__file__).parent # .../Cortex_and_Inara_dev/cortex/
_PROJECT_ROOT = _CORTEX_DIR.parent # .../Cortex_and_Inara_dev/
# Known project aliases — expand before passing to subprocess
_PROJECT_ALIASES: dict[str, str] = {
"cortex": str(_PROJECT_ROOT),
"aether_api": "~/OSIT_dev/aether_api_fastapi",
"aether_frontend": "~/OSIT_dev/aether_app_sveltekit",
"aether_container": "~/OSIT_dev/aether_container_env",
}
_MAX_OUTPUT_CHARS = 12_000
# Maps URL fragments → Aider --api-key provider slug.
@@ -192,11 +183,16 @@ async def aider_run(
immediately. Use agent_status(agent_id) to check progress; set notify=True to
receive a push/Talk notification on completion.
"""
resolved = _PROJECT_ALIASES.get(project, project)
cwd = Path(os.path.expanduser(resolved))
proj_def = PROJECT_ALIASES.get(project)
if proj_def is not None:
cwd = Path(os.path.expanduser(proj_def.path))
ssh_host = proj_def.ssh_host
else:
cwd = Path(os.path.expanduser(project))
ssh_host = ""
if not cwd.is_dir():
return f"Error: project directory '{resolved}' does not exist."
if not ssh_host and not cwd.is_dir():
return f"Error: project directory '{cwd}' does not exist."
timeout = min(max(int(timeout), 10), 600)
@@ -232,17 +228,28 @@ async def aider_run(
cmd += ["--file", f]
logger.info(
"aider_run: project=%s model=%s host_label=%s auto_commit=%s background=%s task=%.120s",
project, model, host_label, auto_commit, background, task,
"aider_run: project=%s ssh_host=%s model=%s host_label=%s auto_commit=%s background=%s task=%.120s",
project, ssh_host or "local", model, host_label, auto_commit, background, task,
)
async def _run() -> str:
proc = await asyncio.create_subprocess_exec(
*cmd,
cwd=str(cwd),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
if ssh_host:
# Run aider natively on the remote host via a login shell so PATH
# includes ~/.local/bin where aider is typically installed.
inner_cmd = "cd " + shlex.quote(str(cwd)) + " && " + shlex.join(cmd)
ssh_cmd = f"bash -l -c {shlex.quote(inner_cmd)}"
proc = await asyncio.create_subprocess_exec(
"ssh", ssh_host, ssh_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
else:
proc = await asyncio.create_subprocess_exec(
*cmd,
cwd=str(cwd),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=float(timeout))
out = stdout.decode(errors="replace").strip()
@@ -323,6 +330,8 @@ DECLARATIONS = [
"Credentials are resolved automatically from the Cortex model registry — "
"OpenRouter, local Open WebUI/Ollama, Anthropic API, and other configured hosts "
"are all supported. Use host_label to pick a specific host. "
"aether_api, aether_frontend, and aether_container run aider natively on the "
"workstation (scott-wks-main-i7) via SSH — aider must be installed there. "
"Set background=True for long tasks — returns an agent_id immediately and sends "
"a notification when done. ADMIN ONLY. Requires confirmation."
),