diff --git a/cortex/orchestrator_engine.py b/cortex/orchestrator_engine.py index 67271e7..13e2c53 100644 --- a/cortex/orchestrator_engine.py +++ b/cortex/orchestrator_engine.py @@ -58,6 +58,7 @@ async def run( respond_with_claude: bool = True, gemini_api_key: str | None = None, model_name: str | None = None, + response_role: str = "chat", ) -> OrchestratorResult: """ Run the full orchestration loop for a task. @@ -176,7 +177,7 @@ async def run( response_text, backend = await complete( system_prompt=system_prompt, messages=messages, - model="claude", + role=response_role, ) else: # Cron/background tasks: return Gemini's summary directly, no Claude call diff --git a/cortex/routers/orchestrator.py b/cortex/routers/orchestrator.py index 30863c9..4e185c4 100644 --- a/cortex/routers/orchestrator.py +++ b/cortex/routers/orchestrator.py @@ -52,6 +52,7 @@ class OrchestrateRequest(BaseModel): include_short: bool = True user: str = "scott" persona: str = "inara" + chat_role: str = "chat" # role used for the final response (decoupled from tool-loop model) class OrchestrateResponse(BaseModel): @@ -184,6 +185,7 @@ async def _run_job(job_id: str, req: OrchestrateRequest, user: str) -> None: respond_with_claude=req.respond_with_claude, gemini_api_key=gemini_key, model_name=orch_model.get("model_name") if orch_model else None, + response_role=req.chat_role, ) # Save the turn to the session store so it survives a page refresh diff --git a/cortex/static/app.js b/cortex/static/app.js index 6b7e646..fdd0931 100644 --- a/cortex/static/app.js +++ b/cortex/static/app.js @@ -14,6 +14,7 @@ const mode_icon_el = document.getElementById('mode-icon'); const mode_label_el = document.getElementById('mode-label'); const note_vis_btn_el = document.getElementById('note-vis-btn'); + const tools_toggle_el = document.getElementById('tools-toggle'); const settings_btn_el = document.getElementById('settings-btn'); const settings_dd_el = document.getElementById('settings-dropdown'); const sessionsBackdrop = document.getElementById('sessions-backdrop'); @@ -151,22 +152,22 @@ // ── Input mode — dropdown select with MRU ordering ────────── const MODES = { - chat: { icon: 'message-circle', label: 'Chat' }, - note: { icon: 'pencil', label: 'Note' }, - otr: { icon: 'lock', label: 'OTR' }, - agent: { icon: 'bot', label: 'Agent' }, + chat: { icon: 'message-circle', label: 'Chat' }, + note: { icon: 'pencil', label: 'Note' }, + otr: { icon: 'lock', label: 'OTR' }, }; const send_defs = { - chat: { icon: 'arrow-up', label: 'Send' }, - note: { icon: 'pencil', label: 'Note' }, - otr: { icon: 'arrow-up', label: 'Send' }, - agent: { icon: 'zap', label: 'Run' }, + chat: { icon: 'arrow-up', label: 'Send' }, + note: { icon: 'pencil', label: 'Note' }, + otr: { icon: 'arrow-up', label: 'Send' }, }; let current_mode = localStorage.getItem('current_mode') || 'chat'; + if (!(current_mode in MODES)) current_mode = 'chat'; // migrate stored 'agent' let note_public = false; // MRU list — most recent first; used to sort dropdown options - let mode_mru = JSON.parse(localStorage.getItem('mode_mru') || '["chat","note","otr","agent"]'); + let mode_mru = JSON.parse(localStorage.getItem('mode_mru') || '["chat","note","otr"]'); + mode_mru = mode_mru.filter(m => m in MODES); // strip stale 'agent' entries function push_mru(mode) { mode_mru = [mode, ...mode_mru.filter(m => m !== mode)]; @@ -219,7 +220,7 @@ }); function update_mode_ui() { - const m = MODES[current_mode]; + const m = MODES[current_mode] || MODES.chat; const sd = send_defs[current_mode] || send_defs.chat; // Update trigger button @@ -235,13 +236,15 @@ note_vis_btn_el.classList.toggle('pub', note_public); // Textarea mode classes - inputEl.classList.toggle('mode-note', current_mode === 'note'); - inputEl.classList.toggle('public', current_mode === 'note' && note_public); - inputEl.classList.toggle('mode-otr', current_mode === 'otr'); - inputEl.classList.toggle('mode-agent', current_mode === 'agent'); + inputEl.classList.toggle('mode-note', current_mode === 'note'); + inputEl.classList.toggle('public', current_mode === 'note' && note_public); + inputEl.classList.toggle('mode-otr', current_mode === 'otr'); - // Send button label + icon - sendBtn.innerHTML = icon_html(sd.icon) + ' ' + sd.label; + // Send button label + icon (tools active → "Run", otherwise per-mode) + const effectiveSd = toolsEnabled && current_mode !== 'note' + ? { icon: 'zap', label: 'Run' } + : sd; + sendBtn.innerHTML = icon_html(effectiveSd.icon) + ' ' + effectiveSd.label; render_icons(); updateInputPlaceholder(); @@ -252,12 +255,14 @@ inputEl.placeholder = note_public ? 'Public note — LLM sees this next turn…' : 'Private note — only you see this…'; - } else if (current_mode === 'agent') { - inputEl.placeholder = ctrlEnterMode - ? `Task for ${personaLabel}… (orchestrator — Ctrl+Enter to run)` - : `Task for ${personaLabel}… (orchestrator)`; } else if (current_mode === 'otr') { - inputEl.placeholder = 'Off the record — not logged or distilled…'; + inputEl.placeholder = toolsEnabled + ? `Task for ${personaLabel}… ⚡ tools + off the record` + : 'Off the record — not logged or distilled…'; + } else if (toolsEnabled) { + inputEl.placeholder = ctrlEnterMode + ? `Task for ${personaLabel}… ⚡ tools (Ctrl+Enter to run)` + : `Task for ${personaLabel}… ⚡ tools`; } else { inputEl.placeholder = ctrlEnterMode ? `Message ${personaLabel}… (Ctrl+Enter to send)` @@ -272,6 +277,26 @@ update_mode_ui(); }); + // ── Tools toggle ───────────────────────────────────────────── + // When on: submit goes to POST /orchestrate (Gemini tool loop → active role responds). + // When off: submit goes to POST /chat (direct to active role, no tools). + let toolsEnabled = localStorage.getItem('tools-enabled') === 'true'; + + function updateToolsToggleUI() { + tools_toggle_el.classList.toggle('local-on', toolsEnabled); + tools_toggle_el.title = toolsEnabled + ? '⚡ Tools enabled — click to disable' + : 'Tools disabled — click to enable'; + update_mode_ui(); + } + + tools_toggle_el.addEventListener('click', (e) => { + e.stopPropagation(); + toolsEnabled = !toolsEnabled; + localStorage.setItem('tools-enabled', toolsEnabled); + updateToolsToggleUI(); + }); + // ── Settings dropdown ───────────────────────────────────────── settings_btn_el.addEventListener('click', (e) => { e.stopPropagation(); @@ -1098,6 +1123,7 @@ include_long: memLong, include_mid: memMid, include_short: memShort, + chat_role: activeRole()?.role || 'chat', user: CORTEX_USER, persona: CORTEX_PERSONA, }), @@ -1171,7 +1197,7 @@ function dispatchSend() { if (current_mode === 'note') addNote(); - else if (current_mode === 'agent') sendOrchestrate(); + else if (toolsEnabled) sendOrchestrate(); else sendMessage(); } @@ -1636,6 +1662,7 @@ updateTierUI(); updateMemUI(); + updateToolsToggleUI(); update_mode_ui(); // ── Init ───────────────────────────────────────────────────── diff --git a/cortex/static/index.html b/cortex/static/index.html index cb3e9d9..062f2b8 100644 --- a/cortex/static/index.html +++ b/cortex/static/index.html @@ -176,6 +176,8 @@
+ +