- Schedules web UI (/settings/crons): list, add, edit, pause/resume, delete jobs
- cron task type: full orchestrator tool loop on a schedule, result → notification channel
- parse_schedule: monthly/yearly formats (monthly:DD:HH:MM, yearly:MM:DD:HH:MM)
- HA inbound webhook tools toggle: orchestrator loop vs. direct LLM, configurable in UI
- ae_db_query/describe/show_view: SELECT-only Aether MariaDB access (admin, per-user creds)
- /settings/integrations: admin-only page for Aether DB credentials
- Schedules nav link added to all settings pages
- pymysql added to requirements
- Docs updated: HELP.md, MASTER.md, CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text files (.md, .py, .js, .json, etc.): read client-side and injected
into the message body as a fenced code block — works with all backends
with zero model capability requirements.
Images (PNG/JPG/WebP/GIF, max 5 MB): encoded as base64 data URL on the
client and sent as a separate attachment field. Backend formats them as
OpenAI multimodal content (text + image_url) for local_openai backends.
Claude CLI and Gemini CLI see the text message with a "📎 filename.png"
note; image data is never written to session history.
- index.html: 📎 button + hidden file input in mode-select row;
attachment-row preview area with thumbnail (images) or filename chip
- app.js: _resolveAttachment(), file reader, clearAttachment();
sendMessage/sendOrchestrate updated to allow no-text sends when a
file is pending; attachment spread into chat payload for images
- chat.py: Attachment model; attachment field on ChatRequest;
llm_attachment extracted in _stream_chat and passed to complete()
- llm_client.py: attachment param through complete()/_dispatch()/_local();
_local() builds multimodal content array for vision calls
- style.css: #attach-btn, #attachment-row, #attachment-preview, thumb
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the role-cycle toggle with a slot model toggle in the Context &
Memory panel. The active model label is shown on the button; clicking cycles
through Primary → Backup 1 → Backup 2 slots configured for the Chat role.
- app.js: remove activeRole()/availableRoles role-cycling; add
activeChatModel()/chatModels slot cycling; update send/orchestrate
payloads to send slot + chat_role:"chat"; fix updateSendBtnTitle and
startRunTimer to use activeChatModel()
- chat.py: add slot field to ChatRequest; pass slot= to complete();
resolve backend_label from slot config; add _chat_slot_models() helper;
include chat_models in GET /backend response
- HELP.md: update Model toggle description, tool count (62/16),
Backends section, API chat payload example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking del now shows 'confirm delete / cancel' inline in the action
bar. Cancel rebuilds the original buttons; confirm proceeds as before.
Matches the session delete pattern added in the prior commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- task_list: add priority param ('low'/'normal'/'high') alongside existing status filter
- Session delete: inline confirm row (Delete / Cancel) instead of immediate delete
- spawn_agent: allow_tools and deny_tools per-call params; role config remains ceiling;
deny_tools falls back to confirm_deny gate when no explicit tool_list is set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove Tool Permissions form from settings.html; replace with a
"Tool Settings →" link that redirects to /settings/tools
- Add Confirmation Gate section to tools_settings.html (allow/deny
textareas) inside the same form as risk policy — one save covers all
- tools_settings.py save handler now writes allow/deny alongside
max_risk/whitelist/blacklist into tool_policy.json
- Remove /settings/tool-policy POST route from settings.py (no longer needed)
- Remove get_tool_policy, save_tool_policy, CONFIRM_REQUIRED imports
from settings.py (now owned by tools_settings.py)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /settings/tools page: max_risk selector (low/medium/high) + per-tool
override dropdowns (Default / Force include / Force exclude) for all 58 tools
grouped by category with color-coded risk badges; JS updates Auto status live
- get_tools_for_role() + get_openai_tools_for_role() now accept max_risk,
whitelist, blacklist; _apply_risk_policy() handles the filtering logic
- get_risk_policy() helper in auth_utils reads from tool_policy.json
- Risk policy wired through orchestrator.py, openai_orchestrator.py,
orchestrator_engine.py, nextcloud_talk.py, homeassistant.py
- Tools nav link added to settings.html and notifications.html
- CLAUDE.md and ARCH__SYSTEM.md updated: tool count 50→58, risk system docs,
tool access control three-layer model documented
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Register three HA orchestrator tools so Inara can read device states and
control devices via the HA REST API. ha_call_service requires admin role
and user confirmation. Also includes accumulated UI fixes (setProcessing
helper, wasNewSession flag cleanup).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notifications.html: add Home Assistant section with two collapsible
blocks — Connection (HA URL + Long-Lived Access Token) and Inbound
webhook (webhook ID with endpoint URL hint showing the username).
Token field uses keep-existing pattern (blank = no change).
settings.py: wire ha_url, ha_token, ha_webhook_id through
_notifications_page() template substitution and save_notifications()
POST handler. Preserves existing HA config fields (persona, tier,
role, tools) on save.
TODO__Agents.md: add Home Assistant integration planning section
(event design, richer payload template, HA API tools).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace free-form reasoning_budget_tokens number input with a 5-level
select in both the edit form (local_llm.py) and add-model form
(local_llm.html). Values: 0 / 1024 / 4096 / 8192 / 32768 tokens.
Edit form pre-selects the stored value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- reasoning_budget_tokens: optional int field on local_openai models;
when set, injects {"reasoning": {"budget_tokens": N}} via extra_body
into every OpenRouter API call (both tool-loop and confirmation-gate
rounds). Field exposed in the model edit form in Settings.
- session name moved from standalone full-row div between #messages
and #input-area into the persona-switcher block in the header, as a
third dim line under "Cortex · Local". Collapses when empty via
:empty CSS. No JS changes required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- inject_mode: per-role toggle (parallel to inject_datetime) gates the
"Current mode: Off The Record" line in the system prompt; wired through
model_registry, context_loader, chat router, orchestrator router, and
local_llm settings UI
- OTR orchestrator fix: OrchestrateRequest now carries off_record;
_finalize_job stores it per message and gates log_turn on it; JS
orchestrate payload sends off_record correctly
- Per-message hover metadata: removed always-visible .model-tag; replaced
with .msg-meta strip in the action bar (hover-only); shows model label,
host, fallback indicator, and OTR badge; stored in session JSON
- Send/stop button tooltip: shows role + model and (when tools on)
separate orchestrator model + engine label; live elapsed timer on stop
button via startRunTimer/stopRunTimer
- OrchestratorResult.backend_label: new field; openai_orchestrator fills
it; finalize_job propagates it to job dict and session messages
- GET /backend: exposes orchestrator_model label so the frontend tooltip
can show both models separately
- TODO: session delete confirmation added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Notifications page:
- NC Talk section expanded: url, bot_secret, notification_room,
nc_username, nc_app_password — all fields from channels.json now editable
- Per-channel sections use <details>/<summary> collapsibles; auto-open
when values are present
- Secrets use type=password with "leave blank to keep" semantics
- Google Chat outbound webhook in its own collapsible section
Account settings:
- HTTP POST Allowlist section added (same textarea pattern as email allowlist)
- POST /settings/http-allowlist route saves home/{user}/http_allowlist.json
- Example placeholder shows ha.dgrzone.com and n8n patterns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract orchestrator inner loop into _doOrchestrate() so the retry button
can re-run without re-adding the user message to DOM or history — same
pattern as the existing chat retry.
Also set AsyncOpenAI(timeout=settings.timeout_local) so slow remote models
(OpenRouter/DeepSeek) get the same 300s budget as local chat calls instead
of the SDK default which varies by connection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CLAUDE.md: date → 2026-05-08, add Proactive notifications row to channel table
- HELP.md: update Notifications settings entry, expand Push Notifications section
with channel config link, add test API endpoints to reference table
- TODO__Agents.md: mark notifications dedicated page and pywebpush fix as done
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds GET /settings/notifications (dedicated page with channel form + two
test buttons) and updates POST /settings/notifications to render that page.
Settings page now shows a compact link card instead of the full form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Routes web_push through notification.py alongside NCT/email/Google Chat,
and fires daily reminder summaries via the scheduler.
- notification.py: _notify_web_push() + "web_push" case in notify();
all four channels (web_push/email/nextcloud/google_chat) now routable
- scheduler.py: _run_reminder_check() daily at 09:00 — reads due reminders
per persona via set_context(), formats up to 3 entries, calls notify()
- routers/settings.py: "web_push" added to valid notification_channel values
- static/settings.html: "Browser Push Notification" option in channel selector
- TODO__Agents.md: proactive notifications section marked complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a synchronous sub-agent spawning tool that lets the orchestrator
delegate tasks to a specific role's model and tool set.
- cortex/tools/agents.py: spawn_agent(task, role, tier, timeout, max_rounds)
- Supports local_openai and gemini_api model types
- Per-host asyncio semaphore (keyed by host_id or model type)
- asyncio.wait_for() enforces timeout; admin-only tool
- cortex/model_registry.py: max_concurrent field in host schema (default 3,
clamped 1-20); backfilled on _normalize() for existing hosts
- cortex/routers/local_llm.py + local_llm.html: "Max parallel" number input
in host add/edit forms
- cortex/tools/__init__.py: spawn_agent registered in TOOL_CATEGORIES["Agents"],
_CALLABLES, TOOL_ROLES (admin), and _ALL_DECLARATIONS
- Docs: TOOLS.md count 44→45, spawn_agent section; HELP.md tool table updated;
ARCH__FUTURE.md Round 2 completed items; TODO__Agents.md spawn_agent checked;
CLAUDE.md tool count and list updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each role can now disable the current date/time header injected into the
system prompt. Default is true (all existing roles unchanged). Useful for
pure processing roles (summarizer, classifier, translator) where temporal
context is irrelevant or could cause unexpected model behavior.
Changes:
- model_registry: set_role_config/get_role_config gain inject_datetime field
- context_loader: load_context gains inject_datetime param (default True)
- orchestrator router: passes inject_datetime from role_cfg to load_context
- local_llm router: reads inject_datetime from POST body, passes to registry;
role_config_data_js includes the field
- local_llm.html: checkbox in role config panel; populate on open, save on submit
Session logs still timestamp every turn (HH:MM header in YYYY-MM-DD.md files)
regardless of this setting — the toggle only affects the system prompt header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tool_audit: ContextVars (engine, model) set at orchestrator run start; fields added to every entry
- orchestrator_engine: tool_audit.set_context("gemini", model_name) at run() start
- openai_orchestrator: tool_audit.set_context("openai", model label) at run() start
- audit table: Model column between Status and Args
- HELP.md: push notifications section, audit log in Files section, tool count 30→40, new API endpoints
- TODO__Agents.md: web_push and audit log marked complete with full detail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an "Audit Log" section (collapsed by default) at the bottom of the Files
panel showing tool_audit/YYYY-MM-DD.jsonl files for the current user.
- GET /api/audit/files — lists available dates (newest first, any auth user)
- GET /api/audit/day — returns entries for one date as JSON (any auth user)
- tool_audit.read_day() — reads a single day's JSONL file chronologically
- Clicking a date renders a read-only table: time / tool / status / args / result
- Status cells are colour-coded (green ok, red error, amber denied)
- Edit/Raw/Preview/Save buttons are hidden in audit view, restored on file switch
- Audit group starts collapsed; expands on click like other file groups
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/history/{session_id} now returns a 'name' field alongside messages.
resumeSession() uses data.name first, then the sessionNames map, then
raw ID as fallback — so named sessions display correctly even on page
load before the sessions panel has been opened.
'Resumed session X' message also now shows the friendly name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: 300px panel minus edit btn (28px) + meta (~130px) + delete
btn (28px) + gaps/padding left only ~70px (~7 chars) for the session name.
- Panel: 300px → 420px desktop, 300px → 380px mobile drawer
- Max-height: 340px → 400px
- Session item: name and meta now in a .session-body flex column, so the
name gets full body width (panel minus two buttons) — meta lives below
- Edit mode: hides .session-body + delete, input takes the full body slot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Edit button (✎) moved to left of row, separated from delete (×)
- Clicking ✎ hides name/meta/delete and expands input to full row width
- Button changes to ✓ (accent color) while editing
- Enter or ✓ click = save; Escape = cancel without saving
- Removed accidental-save-on-blur behavior
- Edit button: 30% opacity at rest, 75% on row hover, 100% on direct hover
- Touch devices: edit button always at 60% opacity (no hover to reveal it)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Distillation was silently operating on scott/inara for all users due to
ContextVar defaults. All three distill endpoints now require ?user=&persona=
query params and validate them via persona.validate(). Memory distiller
signatures changed from Optional to required positional args — no more
global settings fallback. Scheduler now iterates all users/personas instead
of hardcoding the primary user.
- context_loader: inject current date/time as first system prompt section
- session_logger: use get_user()/get_persona() from context instead of
settings globals so Holly/Brian sessions show correct speaker labels
- memory_distiller: system prompts now reference u.title()/p.title()
instead of settings.user_name/settings.agent_name
- distill router: Query(...) enforces params; _resolve() validates persona
- scheduler: _all_personas() helper iterates every user/persona for distill
- app.js: runDistill() now appends ?user=&persona= via _fileParams
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- cortex/static/TOOLS.md — tool reference extracted from HELP.md; uses ##
headers so each category is collapsible. All 30 tools with descriptions.
- cortex/static/HELP.md — UI guide only; tools section replaced with a
one-line pointer to the Tools tab.
- help.html — three tabs (UI Guide / Tools / Persona); tab choice persists
in localStorage. Tools tab defaults all sections open. Persona tab shows
home/{user}/persona/{name}/HELP.md with an empty-state message if unset.
- context_loader.py — loads cortex/static/TOOLS.md into context at tier 2+
(replaces the previously empty persona HELP.md load). Persona HELP.md
still loaded if non-empty, as persona-specific additions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces single flat table with category sections, each with a description
column. Footnotes moved to the top of the section for clarity. Covers all
30 tools including the new cortex_status, cortex_update, reminders_remove,
ae_journal_entry_read, ae_journal_entries_list, and email_send.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cortex_status: git branch/commit/ahead-behind + systemctl state — read-only
cortex_update: git pull + syntax check all .py files + report; does NOT auto-restart.
If syntax errors are found after pull, warns and blocks restart suggestion.
Call cortex_restart separately to apply a clean update.
Both are admin-only. cortex_update is confirm-required (modifies files on disk).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add reminders_remove (targeted single-reminder removal, no confirm needed)
- Add ae_journal_entry_read, ae_journal_entries_list to AE Journals row
- Add email_send (admin-only) to Notifications row
- Remove TASKS.json from Files table (not in the Files panel)
- Add email_allowlist.json to Files table (Settings group in Files panel)
- Update last-updated date
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes the broken confirmation gate where users had no way to approve
or deny a blocked tool call in the web UI.
Changes:
- orchestrator_engine.py: add OrchestrateCheckpoint dataclass, extract
loop into _run_from_contents(), add resume() function
- openai_orchestrator.py: same treatment — _run_from_messages(), resume()
- routers/orchestrator.py: POST /{job_id}/confirm and /deny endpoints,
separate _checkpoints store, _resume_job() + _finalize_job() helpers,
"awaiting_confirmation" job status with pending_confirmation payload
- auth_utils.py: get_tool_policy() and save_tool_policy() helpers reading
home/{user}/tool_policy.json (allow/deny lists)
- routers/orchestrator.py: loads tool_policy per user and passes
confirm_allow/confirm_deny to both engines
- app.js: poll loop handles awaiting_confirmation — shows Confirm/Deny
buttons inline, resumes polling after user action
- settings.html + settings.py: Tool Permissions section with allow/deny
textareas, POST /settings/tool-policy route
- style.css: .confirm-gate, .confirm-btn, .deny-btn styles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notification.py now handles all three outbound channels. Email defaults
to the user's login address (google_email from auth.json); an optional
override can be set in channels.json. Google Chat uses an incoming
webhook URL. NC Talk was already wired, just needs notification_room set.
Settings page gains a Notifications section: channel dropdown, optional
email override, NC room token, and Google Chat webhook URL. All stored
in per-user channels.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings page gets an editable textarea (POST /settings/email-allowlist)
so users can view and update their per-user regex allowlist without
touching the raw JSON file.
Files panel gains a "Settings" group containing email_allowlist.json as
a raw JSON editor backup — served from home/{user}/ via files.py USER_FILES.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline edit form per model row (label, model name/ID, host/account, context, tags)
- Fetch models button in edit form for local models — same live-picker UX as Add Model
- POST /settings/local/models/{id}/edit route in local_llm.py
- Admin role badge (ADMIN/USER pill) in Account Settings page
- HELP.md updated: new tools table with admin/confirm markers, PWA install section
- TODO updated: tool expansions marked done, distill review and Unsloth resolved,
role-based access and admin badge added to completed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace plain textarea with CodeMirror 5 + markdown mode loaded from
jsDelivr CDN. Editor fills the modal body via flex layout, theme-aware
via CSS vars (cursor, selection, headings, bold/em/links/code all mapped
to Cortex dark/light palette). Lazy init on first file open; history
cleared per-file so undo doesn't bleed across files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- cx_last_persona cookie set on serve_ui; root/login/help/settings
redirects use preferred persona from cookie instead of alphabetically first
- /api/personas returns [{name, emoji}] objects; persona switcher dropdown
renders emoji + name with flex layout and .pd-emoji span
- Help, Settings, Model Registry pages apply localStorage theme on load
(no flash); CSS variables for dark/light replacing all hardcoded hex values
- Claude CLI auth status moved from prominent chat banner to Anthropic
provider block in Model Registry — live dot indicator (ok/warn/err)
- Auth banner removed from main chat UI (index.html, app.js, style.css)
- Add Model collapsed into Models section as <details> to shorten page
- Light-mode overrides for provider icons, model badges, ctx-badge, tags
(Anthropic/Google/local colors now readable in both themes)
- Help page gains table, pre/code, hr styles for HELP.md rendered content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When height is set to S, mode-select collapses to a row (mode button +
compact tools toggle side by side). M and L keep the vertical column
layout where each control gets its own full-width row. Driven by
data-size attribute set in JS so the switch is instant on click, not
reliant on a viewport media query. Removed the redundant max-height
landscape query.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stack Chat/Note/OTR button and tools toggle vertically (flex-direction:
column, align-items: stretch) on desktop so they share a tidy left column.
Mobile (≤520px) restores row layout; landscape phone (≤400px height) also
reverts to row to avoid crowding a short viewport.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach used a 600ms preview animation + syncHeight() which
collapsed the textarea back to 1 line (empty scrollHeight). Now syncHeight
enforces minHeight = maxHeight/3, so each setting (S/M/L) has a visibly
distinct resting height even when the input is empty.
S (120px): min ~40px ≈ 1-2 lines at rest
M (240px): min ~80px ≈ 3 lines at rest
L (480px): min ~160px ≈ 5-6 lines at rest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace 3 S/M/L height buttons with one cycling button (like font size)
- Fix closeAllPanels() to include ctx-panel so Context and Settings menus
cannot be open simultaneously
- Fix ctxOpenBtn handler to use the same toggle-via-closeAllPanels pattern
as the settings button
- Align .hdr-dropdown shadow to var(--shadow) instead of hardcoded rgba
- Align #ctx-panel z-index to 200 (match .hdr-dropdown)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace height <select> with S/M/L buttons (data-height); active class shows
current setting; clicking an empty textarea briefly expands it as a preview
so the effect is immediately visible, then auto-shrinks back
- Add --amber/--amber-border/--amber-glow CSS vars to all 4 theme blocks:
dark=#f59e0b (bright), light=#b45309 (deep, 4:1 contrast on light bg)
Fixes local-on/tools-toggle/backend-hint being nearly invisible in light mode
- Rename "Backend" ctx-section to "Role" (matches the role-cycle toggle)
- Update backend-toggle title from stale "primary backend" to "Active role"
- Capitalize distill buttons (Short/Mid/Long/All) to match Memory layer style
- Improve all ctx-panel button titles for clarity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #mode-select changed from flex column to flex row (desktop + mobile unified)
- Chat/⚡ buttons now sit side-by-side at the same height as the textarea
- Removed stale mode-agent CSS rules (mode removed in prior commit)
- Mobile: simplified override — flex:1 only, direction/align already desktop default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>