Adds `anthropic_api` model type so users can authenticate with a direct
Anthropic API key instead of (or alongside) the CLI OAuth session.
- model_registry.py: `anthropic_api` type; `save/get/remove_anthropic_api_key()`
mirroring the Google account pattern; `save_cloud_model()` now picks type
based on credential type (cli → claude_cli, api_key → anthropic_api);
`_resolve_model()` merges api_key from the credential entry
- llm_client.py: `_anthropic_api()` backend (AsyncAnthropic SDK); dispatch
and fallback wiring; usage tracking
- routers/local_llm.py: Anthropic API key management routes
(POST /settings/local/anthropic-key, /anthropic-key/{id}/remove);
`anthropic_api` badge and edit-form credential selector
- static/local_llm.html: Anthropic Cloud Provider block now shows API key
management (add/remove); Add Model → Anthropic tab has credential selector
(CLI vs API key)
- requirements.txt: enable anthropic>=0.40.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
495 lines
32 KiB
Markdown
495 lines
32 KiB
Markdown
# Cortex / Inara — Agent Task List
|
||
|
||
> Read this file before starting any work on this project.
|
||
> **Status:** Active development — ongoing.
|
||
|
||
---
|
||
|
||
## 🔴 High Priority
|
||
|
||
### [UX] User onboarding — guided model setup
|
||
New users complete password + persona setup and land directly in the chat with no working
|
||
AI model configured. This closes that gap with a guided Step 3 and a fallback for existing
|
||
users who skipped it or were onboarded before this existed.
|
||
|
||
Design spec: `documentation/ARCH__SYSTEM.md` § Onboarding Flow
|
||
|
||
- [x] **Setup Step 3 page** — new `/setup/model` GET/POST in `onboarding.py` — 2026-05-06
|
||
- Recommends OpenRouter: "one API key, access to Claude, Gemini, and dozens of other models"
|
||
- API key field + curated model dropdown (claude-3-5-haiku, claude-3-7-sonnet, gemini-2.0-flash, llama-3.3-70b)
|
||
- On submit: `save_host()` (OpenRouter) + `save_model()` + `set_role(chat, primary, model_id)` in `model_registry.py`
|
||
- Skip: `POST /setup/model/skip` reads `cx_setup_persona` cookie, redirects to chat; JS fetch on skip-link click
|
||
- Step labels updated: setup.html "1 of 3" / "2 of 3" / "3 of 3" (was "1 of 2" / "2 of 2")
|
||
- Standalone: `/setup/model` works without step labels (no `cx_setup_persona` cookie → no label)
|
||
- Persona creation now redirects to `/setup/model` instead of directly to chat
|
||
- [x] **Existing user banner** — displayed in chat if no role has a model assigned — 2026-05-06
|
||
- Checks `GET /backend` on load (uses `available_roles` — already does role-resolution)
|
||
- Dismissable amber callout strip above chat: "No AI model configured — Set up OpenRouter →"
|
||
- Dismissed via `localStorage` key `cx_no_model_banner_dismissed`; auto-removed when a model is added
|
||
- [x] **Settings quick-link** — amber card in settings Model Registry section — 2026-05-06
|
||
- Checks `GET /backend` on page load; shown if `available_roles` is empty
|
||
- Links to `/setup/model`
|
||
- [x] Update `cortex/static/HELP.md` — Getting Started section + model registry quick-connect note — 2026-05-06
|
||
- [x] Update `CLAUDE.md` — documented `/setup/model` endpoint, setup flow description, docs philosophy — 2026-05-06
|
||
|
||
### [Local] Local orchestrator — reach full parity with Gemini orchestrator
|
||
`openai_orchestrator.py` is partially built and wired into `POST /orchestrate`.
|
||
When the `orchestrator` role resolves to a `local_openai` model it routes there
|
||
automatically. Remaining work is quality/reliability parity, not ground-up design.
|
||
|
||
- [x] Tool schema conversion — Gemini FunctionDeclaration → OpenAI tools format
|
||
- [x] Context budget: `_context_budget()` uses `context_k * 1000 * 0.75`, min 16k — 2026-05-06
|
||
- [x] Context compaction: `_compact_messages()` trims old tool results before each round and before the confirmation-gate call — 2026-05-06
|
||
- [x] Error handling: malformed tool args caught + logged; tool execution errors returned as strings
|
||
- [x] Retry logic on transient API errors (connection timeout, 429, 503) — 2026-05-09
|
||
- `_chat_with_retry()` helper in `openai_orchestrator.py`; 3 attempts, exponential backoff (1s, 2s)
|
||
- Retries on `APIConnectionError` and `APIStatusError` with status 429/500/502/503/504
|
||
- [ ] Test end-to-end with Gemma 4 E4B and 26B A4B on scott_gaming
|
||
- [ ] Review `ARCH__FUTURE.md` agent architecture ideas before finalising design
|
||
- Reference: `docs/OPEN_WEBUI_API.md`, `documentation/ARCH__FUTURE.md` §1
|
||
|
||
---
|
||
|
||
## 🟡 Medium Priority
|
||
|
||
### [UI] Progressive Web App (PWA) ✅ — 2026-04-29
|
||
- manifest.json, sw.js, icon-192/512.png, SW registration in app.js
|
||
- `/manifest.json` and `/sw.js` served at root; added to `_PUBLIC` in auth_middleware
|
||
- Tested: install prompt confirmed working in Chromium
|
||
|
||
### [Tools] Orchestrator tool expansions — Round 1 ✅
|
||
- [x] **`cortex_restart`** — detached subprocess, 5s delay, admin-only, confirm-required — 2026-04-29
|
||
- [x] **`cortex_logs`** — `journalctl --user -u cortex -n N`, admin-only — 2026-04-29
|
||
- [x] **`http_fetch`** — direct URL fetch via httpx, 8192 char cap — 2026-04-29
|
||
- [x] **`file_list`** — directory listing with size, dirs first, 200 entry cap, admin-only — 2026-04-29
|
||
- [x] **`file_write`** — overwrite/append to home_root paths, admin-only, confirm-required — 2026-04-29
|
||
- [x] **`nc_talk_send`** — outbound NC Talk message via notification.py, admin-only — 2026-04-29
|
||
- [x] **`email_send`** — SMTP via email_utils, per-user regex allowlist in `home/{user}/email_allowlist.json`, managed via Settings UI textarea + Files panel raw editor — 2026-04-29
|
||
- [x] **`web_push`** — VAPID push via pywebpush; subscriptions in `home/{user}/push_subscriptions.json`; "Enable notifications" toggle in ☰ menu; sw.js push+notificationclick handlers — 2026-05-05
|
||
|
||
### [Tools] Orchestrator tool expansions — Round 2
|
||
Next additions identified 2026-05-08. See `ARCH__FUTURE.md` §2 for design notes.
|
||
|
||
**Note:** `datetime_now` is NOT needed — current date/time is already injected into every
|
||
system prompt by `context_loader.py` at all tiers.
|
||
|
||
- [x] **`session_search`** — expose existing session search to the orchestrator — 2026-05-08
|
||
- Wraps session log grep as a tool callable in `tools/files.py`
|
||
- Params: `query: str`, `limit: int = 5` (max 20)
|
||
- Returns: excerpts with session date, newest first; own sessions only via ContextVars
|
||
- User-level (no TOOL_ROLES entry needed)
|
||
- [x] **`reminders` due-date support** — make reminders time-aware — 2026-05-08
|
||
- Optional `due: YYYY-MM-DD` on `reminders_add`; stored as `due: date` first line of body
|
||
- `context_loader.py` calls `load_due_reminders()` — future-dated sections suppressed until due
|
||
- `reminders_list` shows `[OVERDUE]`, `[due TODAY]`, or `[due: YYYY-MM-DD]` per entry
|
||
- Backward compatible — existing undated reminders always surface as before
|
||
- [x] **`spawn_agent`** — spawn a synchronous sub-agent using any role's model + tools — 2026-05-08
|
||
- `cortex/tools/agents.py` — `spawn_agent(task, role, tier, timeout, max_rounds)`
|
||
- Per-host asyncio semaphore keyed by `host_id` (or model type for cloud); `max_concurrent` field in host schema
|
||
- Supports `local_openai` and `gemini_api` model types; returns error string for others
|
||
- Admin-only tool (powerful — can spawn arbitrarily long sub-tasks)
|
||
- Host UI: "Max parallel" number input in host edit/add forms
|
||
- [x] **`spawn_agent` per-call tool restrictions** — `allow_tools` and `deny_tools` params — 2026-05-12
|
||
- `allow_tools: list[str]` — intersected with role ceiling; cannot grant beyond role config
|
||
- `deny_tools: list[str]` — blocked even when role permits; falls back to `confirm_deny` gate when `tool_list` is None
|
||
- Both params documented in FunctionDeclaration for orchestrator use
|
||
- [x] **`file_diff`** — unified diff between two project-scoped files — 2026-05-12
|
||
- `cortex/tools/files.py` — `diff -u`, 50 KB output cap, project-scoped path resolution
|
||
- [x] **`git_status` / `git_log` / `git_diff`** — read-only git inspection — 2026-05-12
|
||
- `cortex/tools/git.py` — new module; all project-scoped, low risk
|
||
- `git_log(n, path, oneline)` — last N commits with optional path filter
|
||
- `git_diff(ref_a, ref_b, path, stat_only)` — any ref range; no args = unstaged vs HEAD
|
||
- [x] **`http_post`** — POST to external URLs — 2026-05-09
|
||
- Params: `url: str`, `body: str`, `headers: dict | None`, `max_chars: int`
|
||
- Per-user URL prefix allowlist in `home/{user}/http_allowlist.json` (JSON array of prefixes)
|
||
- Default: blocked if no allowlist or URL doesn't match any prefix
|
||
- Admin-only, confirm-required
|
||
- [x] **`nc_talk_history`** — read recent Talk messages — 2026-05-09
|
||
- Params: `conversation_token: str` (optional, defaults to notification_room), `limit: int = 20`
|
||
- Returns last N messages with sender + timestamp, chronological order
|
||
- Admin-only; requires `nc_username` and `nc_app_password` in channels.json under `nextcloud`
|
||
- [x] **`task_list` priority filter** — add `priority` param alongside existing `status` — 2026-05-12
|
||
- [x] **`http_fetch` max_chars** — optional param, default 8192, cap at 32768 — 2026-05-09
|
||
- [x] **`web_read(url, max_chars=16000)`** — clean article extraction via trafilatura; strips ads/nav/boilerplate, returns markdown — 2026-05-09
|
||
- [x] **`session_read(date)`** — read a full session log by YYYY-MM-DD date; lists available dates if not found — 2026-05-09
|
||
|
||
### [Channel] Proactive notifications ✅ — 2026-05-08
|
||
Inara reaches out on her own initiative via NC Talk, Google Chat, email, or browser push.
|
||
- [x] `notification.py` — `notify(username, message, channel=None)` routes to NCT / email / Google Chat / web_push
|
||
- [x] `web_push` added as a routable channel in `notification.py` (was tool-only before)
|
||
- [x] `scheduler.py` — `_run_reminder_check()` daily at 09:00: reads due reminders per persona, fires `notify()` with a summary
|
||
- [x] `cron_runner.py` — already calls `notify()` on job completion (was already wired)
|
||
- [x] `scheduler.py` — distill_mid and distill_long already call `notify()` on completion
|
||
- [x] Settings UI — "Browser Push Notification" option added to Notification Channel selector
|
||
- [x] `notification_channel` accepts `"web_push"` in `routers/settings.py`
|
||
- [x] `GET /settings/notifications` — dedicated Notifications page (channel form + test buttons); Settings page now shows a link card
|
||
- [x] `POST /api/push/test` + `POST /api/push/reminders/check` — on-demand test endpoints
|
||
- [x] `push_utils.py` — fixed `pywebpush` 2.x key deserialisation (use `Vapid.from_pem()` instead of passing PEM string)
|
||
|
||
### [Channel] Home Assistant integration — design & tools
|
||
Inara can already receive HA events via `POST /webhook/ha/{username}/{webhook_id}` and
|
||
respond via web push. Next steps are deciding what events to send and giving Inara the
|
||
ability to act on HA via the REST API.
|
||
|
||
- [ ] **Event design** — decide which HA events are worth routing to Inara (security,
|
||
climate thresholds, low battery, unexpected device state). Avoid flooding with
|
||
high-frequency sensor polling. Per-automation `"tools": true/false` to choose
|
||
notify-only vs. agentic response.
|
||
- [ ] **Richer payload template** — update `rest_command` in HA to include
|
||
`trigger.to_state.attributes`, `area_name`, and `previous_state` so Inara gets
|
||
full device context automatically.
|
||
- [x] **HA API tools** — `cortex/tools/homeassistant.py` — 2026-05-12
|
||
- `ha_get_state(entity_id)` — current state + attributes of any entity
|
||
- `ha_call_service(domain, service, data)` — turn on lights, set HVAC, lock doors, etc.
|
||
- `ha_get_states(area=None, domain=None)` — list states with optional filter
|
||
- Auth via Long-Lived Access Token stored in `channels.json` under `homeassistant.token`
|
||
- HA URL from `channels.json` under `homeassistant.url`
|
||
- [x] **Store HA config in channels.json** — `url`, `token`, `webhook_id` fields under `homeassistant`; managed via `/settings/notifications` — 2026-05-12
|
||
- [x] **`ha_call_service` confirm-required** — 2026-05-12
|
||
|
||
### [UX] Session delete confirmation
|
||
- [x] Inline "Delete this session? [Delete] [Cancel]" reveal on `×` click in `app.js` — 2026-05-12
|
||
- [x] Message-level delete: "confirm delete / cancel" inline in the actions bar — 2026-05-12
|
||
|
||
### [UI] File attachments in chat ✅ — 2026-05-12
|
||
Upload an image or document inline and have it flow into context.
|
||
- [x] Attachment button (paperclip) in input area; hidden file input
|
||
- [x] Images sent as base64 inline_data (Gemini API) or image blocks (Claude/local)
|
||
- [x] Text/code files read as UTF-8, injected as fenced code block in message
|
||
- [x] Thumbnail/filename shown above sent message in UI
|
||
|
||
### [Auth] Encrypted sessions
|
||
Allow users to opt-in to per-session encryption so session logs on disk cannot be
|
||
read without the user's key.
|
||
- [ ] Design key derivation: password-based (PBKDF2/Argon2) or separate passphrase
|
||
- [ ] Encrypt `session_logger.py` output before writing to `sessions/*.md`
|
||
- [ ] Decrypt on read in `session_store.py` (history reload, file browser)
|
||
- [ ] UI toggle in Settings to enable/disable encrypted sessions per persona
|
||
- [ ] Decide: encrypt at rest only, or also in-memory session store?
|
||
- [ ] Consider: how distillation and session search interact with encrypted files
|
||
|
||
### [Models] Model Registry V2 — Unified Provider System
|
||
See `DESIGN__Model_Registry_V2.md` for full design.
|
||
- [x] **Phase 1** — V2 schema with providers (Anthropic/Google), multi-account Gemini, auto migration, orchestrator uses account API key — 2026-04-27
|
||
- [x] **Phase 2** — Cloud provider UI: Anthropic + Google sections in `/settings/models`, account management, model entry creation for cloud models — 2026-04-27
|
||
- [x] **Phase 3** — Unified roles + toggle redesign: chat toggle cycles chat-role slot models (Primary/Backup 1/Backup 2) by label; slot sent in chat/orchestrate payload — 2026-05-12
|
||
- [ ] **Phase 4** — Polish: Claude API key, OpenRouter as named provider, catalog sync from API
|
||
|
||
### [Intelligence] Knowledge consolidation — Phase 1
|
||
See `ARCH__Intelligence_Layer.md` for full design.
|
||
- [x] Tool: `ae_journal_list` — list all journals for the account — 2026-04-28
|
||
- [x] Tool: `ae_journal_search` — search before creating to avoid duplicates
|
||
- [x] Tool: `ae_journal_entry_create` — write a new entry with source metadata
|
||
- [x] Tool: `ae_journal_entry_update` — PATCH any fields on an existing entry — 2026-04-28
|
||
- [x] Tool: `ae_journal_entry_disable` — soft-delete via enable=false — 2026-04-28
|
||
- [x] Tool: `ae_journal_entry_append` — read→append timestamped section→write (running logs) — 2026-04-28
|
||
- [x] Tool: `ae_journal_entry_prepend` — read→prepend timestamped section→write (newest-first logs) — 2026-04-28
|
||
- [x] Import script: walk a markdown directory, chunk by H2 section, create entries — 2026-05-05
|
||
- [x] Target: markdown files from `~/DgrZone_Nextcloud/` and `~/OSIT_Nextcloud/` — 2026-05-05
|
||
- [x] Tag strategy: source path, topic tags from path components — 2026-05-05
|
||
|
||
---
|
||
|
||
## 🟢 Lower Priority / Future
|
||
|
||
### [Research] Agent architecture patterns — review before building dev agent pipeline
|
||
The Claude Code system prompt was leaked April 2026. Two reimplementation repos have
|
||
useful design ideas directly applicable to the local orchestrator and dev agent work.
|
||
Read before finalising either design.
|
||
- [ ] Review https://github.com/HarnessLab/claw-code-agent (Python, targets local models)
|
||
- [ ] Review https://github.com/ultraworkers/claw-code (community port, interesting source)
|
||
- Key ideas to evaluate for Cortex:
|
||
- Tiered permission model (read-only / write / shell / unsafe) — relevant once dev
|
||
agent is writing and executing code
|
||
- Agent lineage tracking — which agent spawned which sub-agent; essential for the
|
||
orchestrator → specialist → supervisor chain
|
||
- Hard token/cost budgets per operation — local models have fixed context ceilings
|
||
- Context compaction mid-session — trim stale tool results before hitting limit
|
||
- Nested agent delegation with dependency-aware batching
|
||
- Plugin/manifest-based tool registration — worth considering before tool suite grows
|
||
|
||
### [Backend] API usage / cost tracking
|
||
Multi-user setup with real Gemini/Claude API costs. Track per-user token consumption
|
||
so Scott can see who's spending what.
|
||
- [x] Count input + output tokens — local backend (OpenAI `usage` field) + Gemini API (`usage_metadata`) — 2026-05-05
|
||
- [x] Append to `home/{user}/usage.json` — daily buckets, per-model breakdown — 2026-05-05
|
||
- [x] Expose via `/api/usage` + `/api/usage/summary` + `/api/usage/all` (admin); usage table in Settings — 2026-05-06
|
||
- [ ] Optional: soft spending limit with a warning toast when exceeded
|
||
|
||
### [Security] Tool call audit log — 2026-05-05
|
||
Every orchestrator tool invocation logged to `home/{user}/tool_audit/YYYY-MM-DD.jsonl`.
|
||
- [x] `tool_audit.py` — JSONL writer with asyncio locks; ContextVars for engine/model set by each orchestrator at run start
|
||
- [x] Hook in `call_tool()` — fire-and-forget `asyncio.create_task`; captures status ok/error/denied, 300-char result snippet, args (truncated at 500 chars)
|
||
- [x] `GET /api/audit/files` — lists available dates for current user (self-service)
|
||
- [x] `GET /api/audit/day?date=` — returns entries for one date (self-service)
|
||
- [x] `GET /api/audit/recent` + `/stats` — cross-user aggregation (admin only)
|
||
- [x] "Audit Log" group in Files panel sidebar (collapsed by default) — read-only table with time/tool/status/model/args/result columns; colour-coded status
|
||
|
||
### [Intelligence] Dev agent pipeline
|
||
See `ARCH__Intelligence_Layer.md`. Full design not yet started.
|
||
- [ ] Specialist agent: frontend (SvelteKit) code changes
|
||
- [ ] Specialist agent: backend (FastAPI) code changes
|
||
- [ ] Supervisor agent: diff review, syntax check, test runner
|
||
- [ ] Gitea webhook integration: trigger on push/PR, report back
|
||
- [ ] Human approval gate before commit
|
||
|
||
### [Intelligence] Supervisor agent
|
||
- Runs `py_compile`, `svelte-check`, unit tests after specialist agent work
|
||
- Reports pass/fail back to orchestrator
|
||
- Only commits on explicit approval
|
||
|
||
### [Channel] Gitea webhooks
|
||
- Receive push/PR/issue events → route to appropriate agent
|
||
- `cortex/routers/` already has pattern; add `gitea.py`
|
||
- Gitea Actions (CI) for "run tests on push" — simpler than custom runner
|
||
|
||
### [Local] RAG via Open WebUI
|
||
Open WebUI has a full RAG pipeline (file upload → embed → knowledge collections →
|
||
reference in chat). Could feed Nextcloud docs or session logs into a local knowledge
|
||
base accessible to local models. Endpoints documented in `docs/OPEN_WEBUI_API.md`.
|
||
- `/api/v1/files/` upload + `/api/v1/retrieval/process/web` for URLs
|
||
- Reference in chat via `"files": [{"type": "collection", "id": "..."}]`
|
||
|
||
### [Backend] Intelligent model routing — automatic task-type dispatch
|
||
Model Registry V2 (2026-04-27) added role-based routing and manual role toggle — that's
|
||
the foundation. What remains is removing the need to toggle manually.
|
||
- [ ] Classify incoming messages by task type (heuristic or lightweight classifier)
|
||
- [ ] Map task type → role → model automatically:
|
||
- User conversation → `chat` role → Claude (quality prose, persona fidelity)
|
||
- Tool/research tasks → `orchestrator` role → Gemini API or local
|
||
- Private/sensitive → `local` role → Ollama (no data leaves network)
|
||
- Long context (>50k tokens) → Gemini 2.0 (1M ctx window)
|
||
- Fast/cheap queries → local E4B (25 t/s, no API cost)
|
||
- [ ] Routing logic in `llm_client.py` or new `router.py`; expose override in UI
|
||
|
||
### [Future] Cortex Mesh — multi-instance fleet coordination
|
||
Each fleet device runs its own Cortex instance. Instances delegate tasks to each
|
||
other based on resources and specialisation. No central coordinator required.
|
||
- Concept only — no design yet. Resolve these questions before building:
|
||
- Auth between instances (shared JWT secret vs. per-instance API keys)
|
||
- Capability advertisement (model registry over HTTP? shared Syncthing file?)
|
||
- Whether `ae_send_message` / the inbox system is the right coordination layer
|
||
- Session continuity — does a conversation stay on one node or migrate?
|
||
- Natural foundation already in place: Syncthing-synced `home/` and shared
|
||
`model_registry.json` mean instances share persona memory without a central DB
|
||
|
||
---
|
||
|
||
## ✅ Completed
|
||
|
||
### [Tools] email_send tool + per-user email allowlist — 2026-04-29
|
||
- `email_send(to, subject, body)` in `cortex/tools/notify.py` — SMTP via `email_utils.py`
|
||
- Per-user regex allowlist at `home/{user}/email_allowlist.json` (JSON array of patterns)
|
||
- `re.fullmatch(..., re.IGNORECASE)` — supports wildcards like `.*@oneskyit\\.com`
|
||
- Blocked by default (no allowlist = no sends); non-matching addresses silently blocked
|
||
- Registered as admin-only tool in `TOOL_ROLES`
|
||
- **Settings UI**: `POST /settings/email-allowlist` — textarea in Account Settings, one pattern per line
|
||
- **Files panel**: `email_allowlist.json` added to `USER_FILES` in `files.py`; served from `home/{user}/`; appears in new "Settings" group in sidebar
|
||
|
||
### [Models] Edit existing model entries — 2026-04-29
|
||
- Inline edit form per model row in `local_llm.html` (`.model-row-header` + hidden `.model-edit-form`)
|
||
- "Edit" toggle shows pre-populated form; "Cancel" collapses it
|
||
- "Fetch models" button in edit form — same live-fetch flow as Add Model
|
||
- `POST /settings/local/models/{model_id}/edit` route in `local_llm.py` dispatches to `save_model` / `save_cloud_model` (upsert via `model_id`)
|
||
- Works for both `local_openai` and cloud model types
|
||
|
||
### [Sessions] Cross-session search — 2026-04-29
|
||
- `GET /sessions/search?q=&user=&persona=&limit=` in `files.py` — full-text grep across `sessions/*.md`, newest first
|
||
- Returns up to `limit` matches with 120-char excerpt and date; `total_files_searched` count
|
||
- UI: search input + results panel below Files sidebar; `Ctrl+F` / search icon shortcut; `marked.parse` highlights matches
|
||
|
||
### [Tools] Role-based access control + confirmation gate — 2026-04-29
|
||
- `TOOL_ROLES` dict maps tool names to minimum required role (`admin`/`user`)
|
||
- `CONFIRM_REQUIRED` set blocks destructive tools; orchestrator injects confirmation prompt instead
|
||
- `get_tools_for_role(role)` filters both Gemini declarations and callables
|
||
- `get_user_role(username)` added to `auth_utils.py`; passed through both orchestrators
|
||
- `manage_passwords.py role <username> admin|user` — shell-only admin promotion
|
||
- Admin-only tools: `shell_exec`, `claude_allow_dir`, `cortex_restart`, `cortex_logs`,
|
||
`file_read`, `file_list`, `file_write`, `ae_task_list`, `nc_talk_send`
|
||
- Confirm-required tools: `cortex_restart`, `file_write`, `shell_exec`, `cron_remove`, `reminders_clear`
|
||
|
||
### [UI] Admin role badge in Account settings — 2026-04-29
|
||
- `GET /settings` now injects `user_role` from `auth.json` into settings page
|
||
- Role shown as a styled pill badge (purple ADMIN, muted USER) below username field
|
||
|
||
### [Local] Unsloth Gemma 4 variants — resolved 2026-04-29
|
||
- Ollama update resolved the `500: unable to load model` issue
|
||
- Unsloth Dynamic 2.0 Q4_K_M GGUFs loading correctly
|
||
|
||
### [Distill] Distill quality review — resolved 2026-04-29
|
||
- Short/mid/long output reviewed and quality confirmed acceptable
|
||
- No prompt tuning needed at this time
|
||
|
||
### [UI] Progressive Web App (PWA) — 2026-04-29
|
||
- `manifest.json`, `sw.js`, PNG icons (192/512) generated via rsvg-convert
|
||
- `/manifest.json` and `/sw.js` served at root via ui.py; exempted in auth_middleware
|
||
- Theme-color meta tag updated dynamically on light/dark toggle
|
||
- Install prompt confirmed working in Chromium desktop; apple-touch-icon for iOS
|
||
|
||
### [UI] CodeMirror markdown editor for identity/memory files — 2026-04-28
|
||
- Replaced textarea in Files panel with CodeMirror 5 (markdown mode, CDN)
|
||
- Syntax highlighting, line wrapping, Ctrl+S to save, per-file undo history
|
||
|
||
### [UI] Input area polish — 2026-04-28
|
||
- Single cycling S/M/L button replaces 3 separate height buttons (same UX as font size)
|
||
- S size collapses mode-select to a row (compact); M/L keep vertical column layout
|
||
- Input height minimum derived from setting so empty textarea reflects selected size
|
||
- Context & Memory panel and Settings dropdown are mutually exclusive (closeAllPanels fix)
|
||
- Both panels now use consistent shadow (var(--shadow)) and z-index (200)
|
||
|
||
### [Tools] Tools toggle — decoupled from Role/Backend — 2026-04-28
|
||
- Removed "Agent" mode from the mode selector; replaced with independent ⚡ toggle
|
||
- `toolsEnabled` persists in localStorage; routes to orchestrator regardless of active mode
|
||
- Layout: column (M/L) or row (S) driven by `data-size` attribute set by JS
|
||
- chat_role flows from UI → OrchestrateRequest → orchestrator_engine.run(response_role=...)
|
||
|
||
### [Tools] shell_exec tool — 2026-04-28
|
||
- `shell_exec(command, working_dir, timeout)` in `cortex/tools/system.py`
|
||
- Runs any shell command on the Cortex host; timeout clamped 1–120s
|
||
- Use for system diagnostics: `df -h`, `ps aux`, `journalctl`, `free -h`, etc.
|
||
|
||
### [Tools] Aether Journals full toolkit — 2026-04-28
|
||
- `ae_journal_list` — list all journals + ids for the account
|
||
- `ae_journal_entry_update` — PATCH any fields (title, content, summary, tags, enable)
|
||
- `ae_journal_entry_disable` — soft-delete via enable=false
|
||
- `ae_journal_entry_append` — read→append timestamped section→write (running/data logs)
|
||
- `ae_journal_entry_prepend` — read→prepend timestamped section→write (newest-first)
|
||
- Shared `_get_entry` / `_patch_entry` helpers; OpenAI JSON Schema auto-derived from Gemini declarations
|
||
|
||
### [Local] Per-user multi-model local LLM settings — 2026-04-01
|
||
- `home/{username}/local_llm.json` — `hosts[]` + `models[]` + `active_model_id` structure
|
||
- `cortex/user_settings.py` — CRUD functions: save_host, add_model, remove_model, set_active_model, get_active_local_model
|
||
- `cortex/routers/local_llm.py` + `cortex/static/local_llm.html` — dedicated `/settings/local` page
|
||
- "Fetch models from host" button — proxied via `/api/local-llm/fetch-models`, populates dropdown
|
||
- Active model shown in UI near backend toggle button (amber hint text)
|
||
- Migrates old flat `.env`-style config automatically on first use
|
||
|
||
### [UI] Copy button for user (sent) messages — 2026-04-01
|
||
- Added matching copy-on-hover button to user messages (same pattern as assistant messages)
|
||
- `div.dataset.raw` set on send; `makeCopyBtn(div)` appended inline
|
||
|
||
### [Backend] Local model backend (Open WebUI / Ollama) — 2026-04-01
|
||
- OpenAI-compatible API via `httpx` — no CLI wrapper needed
|
||
- Configured via `LOCAL_API_URL` / `LOCAL_API_KEY` / `LOCAL_MODEL` in `.env`
|
||
- Backend toggle cycles `claude → gemini → local` (amber color in UI)
|
||
- `/auth/status` includes local reachability check (`GET /api/models`)
|
||
- Tested end-to-end: `test-agent-simple` (Qwen3-8B) on `scott-lt-i7-rtx:3000`, full persona context flowing correctly
|
||
|
||
### [Testing] Gitea SSH port 2222 — 2026-03-29
|
||
- pfSense WAN → 192.168.32.7:2222 port forward confirmed working
|
||
- `ssh -p 2222 git@git.dgrzone.com` reaches Gitea (returns "Invalid repository path" — expected, confirms connectivity)
|
||
- Clone/push via SSH: `git clone ssh://git@git.dgrzone.com:2222/<user>/<repo>.git`
|
||
|
||
### [Multi-user] Brian onboarding — 2026-03-29
|
||
- Invite sent to `memedrift@gmail.com`
|
||
- Brian completed onboarding, created `wintermute` persona
|
||
- Google OAuth registered (`google-add brian memedrift@gmail.com`)
|
||
|
||
### [Tools] Reminders tools — 2026-03-29
|
||
- `reminders_add`, `reminders_list`, `reminders_clear` added to orchestrator tool suite
|
||
- Tools live in `cortex/tools/reminders.py`
|
||
- All persona PROTOCOLS.md updated with Tools & Modes reference (direct chat vs Agent mode)
|
||
- `persona_template.py` updated so new personas get the protocol automatically
|
||
|
||
### [Auth] Token expiry — no restart needed — 2026-03-27
|
||
- `llm_client._fresh_claude_token()` reads live from `~/.claude/.credentials.json` on every call
|
||
- systemd service is a user unit (no sudo) — `systemctl --user restart cortex` is sufficient
|
||
- No manual token sync required after `claude auth login`
|
||
|
||
### [Multi-user] Per-user channel config — 2026-03-27
|
||
- Google Chat and NC Talk secrets/config moved from `.env` to `home/{username}/channels.json`
|
||
- New endpoints: `POST /channels/google-chat/{username}` and `POST /webhook/nextcloud/{username}`
|
||
- No channel access by default — each user configures their own `channels.json`
|
||
- Setup guides: `docs/GOOGLE_CHAT_BOT.md` and `docs/NEXTCLOUD_TALK_BOT.md`
|
||
|
||
### [Auth] Google OAuth sign-in — 2026-03-27
|
||
- `GET /auth/google` → Google consent → `GET /auth/google/callback` flow
|
||
- Users pre-registered via `manage_passwords.py google-add <user> <email>`
|
||
- Google sign-in button on `/login`; auth.json stores `google_sub` + `google_email`
|
||
- Active users: scott (scott.idem@oneskyit.com), holly (holly.danner@gmail.com), brian (memedrift@gmail.com)
|
||
|
||
### [Settings] Per-user Gemini API key — 2026-03-27
|
||
- Stored in `home/{username}/auth.json` as `gemini_api_key`
|
||
- Orchestrator uses user key if set, falls back to server-level `GEMINI_API_KEY`
|
||
- Manageable via `/settings` UI (add, remove, masked hint)
|
||
|
||
### [UI] Session persistence across navigation — 2026-03-26
|
||
- localStorage keyed to `cx_sid_{user}_{persona}` with 30-min inactivity TTL
|
||
- Auto-restored silently on page load; cleared on "New session" or session delete
|
||
|
||
### [UI] Persona picker page — 2026-03-26
|
||
- `GET /{username}` shows a card grid of available personas instead of 404
|
||
- Each card links directly to `/{username}/{persona}`
|
||
|
||
### [UI] Lucide icons — 2026-03-25
|
||
- Icons throughout: mode selector, send/stop buttons, edit/del/copy, save/cancel
|
||
- Loaded via UMD CDN; `icon_html()` + `render_icons()` helpers in `app.js`
|
||
|
||
### [UI] Persona-specific favicon — 2026-03-25
|
||
- Emoji SVG favicon generated from persona config at load time
|
||
|
||
### [Multi-user] Holly onboarding — 2026-03-20
|
||
- Holly's invite sent; onboarding completed via `/setup/{token}`
|
||
- `home/holly/persona/tina/` created from template
|
||
- Google OAuth registered (`holly.danner@gmail.com`)
|
||
|
||
### [Channel] Nextcloud Talk integration ✅ — 2026-03-20, updated 2026-03-27
|
||
- HMAC verification: incoming uses `random + raw_body`; outgoing reply uses `random + message_text`
|
||
- Per-user routing added 2026-03-27 (endpoint: `/webhook/nextcloud/{username}`)
|
||
- Docs: `docs/NEXTCLOUD_TALK_BOT.md`
|
||
|
||
### [Channel] Google Chat integration ✅ — 2026-03-20, updated 2026-03-27
|
||
- JWT verification via `authorizationEventObject.systemIdToken`
|
||
- Workspace Add-on format: `hostAppDataAction.chatDataAction.createMessageAction`
|
||
- Per-user routing added 2026-03-27 (endpoint: `/channels/google-chat/{username}`)
|
||
- Docs: `docs/GOOGLE_CHAT_BOT.md`
|
||
|
||
### [Intelligence] Orchestrator service — Phase 1 — 2026-03-18
|
||
- Gemini API (google-genai SDK) tool loop → Claude final response
|
||
- `POST /orchestrate` (async job), `GET /orchestrate/{job_id}` (poll)
|
||
- Tools: web search, AE API, file read, task list, scratch, reminders, cron
|
||
- Default model: `gemini-2.5-flash`
|
||
|
||
### [Auth] Session auth + persona onboarding — 2026-03-20
|
||
- bcrypt passwords in `home/{username}/auth.json`
|
||
- JWT session cookies (HS256, 30-day expiry)
|
||
- Invite tokens (72h, one-time-use) — `manage_passwords.py invite <user> [email]`
|
||
- Self-service onboarding: `/setup/{token}` → `/setup/persona`
|
||
- SMTP invite email via `noreply@oneskyit.com`
|
||
|
||
### [UI] Mobile-friendly header — 2026-03
|
||
- Backend toggle, font size, theme buttons moved into ⚙ settings panel
|
||
- Header reduced to core buttons
|
||
|
||
### [UI] Help & Reference — 2026-03-27
|
||
- Shared base at `cortex/static/HELP.md` (served to all users)
|
||
- Persona-specific additions appended from `home/{username}/persona/{name}/HELP.md` if present
|
||
- Collapsible H2 sections via `<details>` elements
|
||
|
||
### [Backend] Gemini CLI backend — 2026-03
|
||
- `gemini -p` subprocess, streaming output; auth check at `/auth/status`
|
||
|
||
### [Backend] Memory distiller — 2026-03
|
||
- APScheduler: `distill_short` (daily 03:00), `distill_mid` (weekly Sun 03:30), `distill_long` (monthly 1st 04:00)
|
||
- Writes to `MEMORY_SHORT.md`, `MEMORY_MID.md`, `MEMORY_LONG.md` per persona
|
||
|
||
### [Backend] Session logging + file browser — 2026-03
|
||
- Sessions saved to `home/{user}/persona/{name}/sessions/`
|
||
- Files panel in UI browses persona directory
|
||
|
||
### [Backend] Dispatcher core — 2026-03-04
|
||
- FastAPI service with streaming SSE response
|
||
- Claude CLI and Gemini CLI subprocess backends
|
||
- Session context management (rolling window, `MAX_HISTORY_MESSAGES`)
|
||
|
||
|
||
### [Tools] Orchestrator tool expansions — Round 3
|
||
|
||
- [x] **`spawn_agent` tool restrictions** — `allow_tools` and `deny_tools` per-call params — 2026-05-12
|
||
- Role config remains the authoritative ceiling; spawner can only restrict further
|
||
- `allow_tools`: intersected with role tool list; if role list is None, used directly (role gate still applies)
|
||
- `deny_tools`: removed from tool list; falls back to `confirm_deny` gate when tool list is unrestricted
|
||
- Design spec: `ARCH__FUTURE.md` §12
|
||
- [x] **`file_diff`** — unified diff of two project-scoped files (`diff -u`); low risk, no admin — 2026-05-12
|
||
- [x] **`git_status` / `git_log` / `git_diff`** — read-only git inspection tools, project-scoped; `git.py` module — 2026-05-12
|