feat: replace Agent mode with independent Tools toggle
- Remove 'agent' from mode dropdown; Chat/Note/OTR remain - Add ⚡ tools toggle button in input bar (persisted in localStorage) When on: routes to POST /orchestrate (Gemini tool loop); send btn → "Run" When off: routes to POST /chat (direct to active role); no change - Role selector and tools toggle are now fully independent: active chat_role sent in orchestrate payload → used for final response - orchestrator_engine.run() accepts response_role param; passes it to complete(role=...) instead of hardcoded model="claude" - OrchestrateRequest gains chat_role field (default "chat") - Migrate stored 'agent' mode/MRU entries to 'chat' on load Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ async def run(
|
|||||||
respond_with_claude: bool = True,
|
respond_with_claude: bool = True,
|
||||||
gemini_api_key: str | None = None,
|
gemini_api_key: str | None = None,
|
||||||
model_name: str | None = None,
|
model_name: str | None = None,
|
||||||
|
response_role: str = "chat",
|
||||||
) -> OrchestratorResult:
|
) -> OrchestratorResult:
|
||||||
"""
|
"""
|
||||||
Run the full orchestration loop for a task.
|
Run the full orchestration loop for a task.
|
||||||
@@ -176,7 +177,7 @@ async def run(
|
|||||||
response_text, backend = await complete(
|
response_text, backend = await complete(
|
||||||
system_prompt=system_prompt,
|
system_prompt=system_prompt,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
model="claude",
|
role=response_role,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Cron/background tasks: return Gemini's summary directly, no Claude call
|
# Cron/background tasks: return Gemini's summary directly, no Claude call
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class OrchestrateRequest(BaseModel):
|
|||||||
include_short: bool = True
|
include_short: bool = True
|
||||||
user: str = "scott"
|
user: str = "scott"
|
||||||
persona: str = "inara"
|
persona: str = "inara"
|
||||||
|
chat_role: str = "chat" # role used for the final response (decoupled from tool-loop model)
|
||||||
|
|
||||||
|
|
||||||
class OrchestrateResponse(BaseModel):
|
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,
|
respond_with_claude=req.respond_with_claude,
|
||||||
gemini_api_key=gemini_key,
|
gemini_api_key=gemini_key,
|
||||||
model_name=orch_model.get("model_name") if orch_model else None,
|
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
|
# Save the turn to the session store so it survives a page refresh
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
const mode_icon_el = document.getElementById('mode-icon');
|
const mode_icon_el = document.getElementById('mode-icon');
|
||||||
const mode_label_el = document.getElementById('mode-label');
|
const mode_label_el = document.getElementById('mode-label');
|
||||||
const note_vis_btn_el = document.getElementById('note-vis-btn');
|
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_btn_el = document.getElementById('settings-btn');
|
||||||
const settings_dd_el = document.getElementById('settings-dropdown');
|
const settings_dd_el = document.getElementById('settings-dropdown');
|
||||||
const sessionsBackdrop = document.getElementById('sessions-backdrop');
|
const sessionsBackdrop = document.getElementById('sessions-backdrop');
|
||||||
@@ -154,19 +155,19 @@
|
|||||||
chat: { icon: 'message-circle', label: 'Chat' },
|
chat: { icon: 'message-circle', label: 'Chat' },
|
||||||
note: { icon: 'pencil', label: 'Note' },
|
note: { icon: 'pencil', label: 'Note' },
|
||||||
otr: { icon: 'lock', label: 'OTR' },
|
otr: { icon: 'lock', label: 'OTR' },
|
||||||
agent: { icon: 'bot', label: 'Agent' },
|
|
||||||
};
|
};
|
||||||
const send_defs = {
|
const send_defs = {
|
||||||
chat: { icon: 'arrow-up', label: 'Send' },
|
chat: { icon: 'arrow-up', label: 'Send' },
|
||||||
note: { icon: 'pencil', label: 'Note' },
|
note: { icon: 'pencil', label: 'Note' },
|
||||||
otr: { icon: 'arrow-up', label: 'Send' },
|
otr: { icon: 'arrow-up', label: 'Send' },
|
||||||
agent: { icon: 'zap', label: 'Run' },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_mode = localStorage.getItem('current_mode') || 'chat';
|
let current_mode = localStorage.getItem('current_mode') || 'chat';
|
||||||
|
if (!(current_mode in MODES)) current_mode = 'chat'; // migrate stored 'agent'
|
||||||
let note_public = false;
|
let note_public = false;
|
||||||
// MRU list — most recent first; used to sort dropdown options
|
// 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) {
|
function push_mru(mode) {
|
||||||
mode_mru = [mode, ...mode_mru.filter(m => m !== mode)];
|
mode_mru = [mode, ...mode_mru.filter(m => m !== mode)];
|
||||||
@@ -219,7 +220,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function update_mode_ui() {
|
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;
|
const sd = send_defs[current_mode] || send_defs.chat;
|
||||||
|
|
||||||
// Update trigger button
|
// Update trigger button
|
||||||
@@ -238,10 +239,12 @@
|
|||||||
inputEl.classList.toggle('mode-note', current_mode === 'note');
|
inputEl.classList.toggle('mode-note', current_mode === 'note');
|
||||||
inputEl.classList.toggle('public', current_mode === 'note' && note_public);
|
inputEl.classList.toggle('public', current_mode === 'note' && note_public);
|
||||||
inputEl.classList.toggle('mode-otr', current_mode === 'otr');
|
inputEl.classList.toggle('mode-otr', current_mode === 'otr');
|
||||||
inputEl.classList.toggle('mode-agent', current_mode === 'agent');
|
|
||||||
|
|
||||||
// Send button label + icon
|
// Send button label + icon (tools active → "Run", otherwise per-mode)
|
||||||
sendBtn.innerHTML = icon_html(sd.icon) + ' ' + sd.label;
|
const effectiveSd = toolsEnabled && current_mode !== 'note'
|
||||||
|
? { icon: 'zap', label: 'Run' }
|
||||||
|
: sd;
|
||||||
|
sendBtn.innerHTML = icon_html(effectiveSd.icon) + ' ' + effectiveSd.label;
|
||||||
|
|
||||||
render_icons();
|
render_icons();
|
||||||
updateInputPlaceholder();
|
updateInputPlaceholder();
|
||||||
@@ -252,12 +255,14 @@
|
|||||||
inputEl.placeholder = note_public
|
inputEl.placeholder = note_public
|
||||||
? 'Public note — LLM sees this next turn…'
|
? 'Public note — LLM sees this next turn…'
|
||||||
: 'Private note — only you see this…';
|
: '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') {
|
} 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 {
|
} else {
|
||||||
inputEl.placeholder = ctrlEnterMode
|
inputEl.placeholder = ctrlEnterMode
|
||||||
? `Message ${personaLabel}… (Ctrl+Enter to send)`
|
? `Message ${personaLabel}… (Ctrl+Enter to send)`
|
||||||
@@ -272,6 +277,26 @@
|
|||||||
update_mode_ui();
|
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 dropdown ─────────────────────────────────────────
|
||||||
settings_btn_el.addEventListener('click', (e) => {
|
settings_btn_el.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -1098,6 +1123,7 @@
|
|||||||
include_long: memLong,
|
include_long: memLong,
|
||||||
include_mid: memMid,
|
include_mid: memMid,
|
||||||
include_short: memShort,
|
include_short: memShort,
|
||||||
|
chat_role: activeRole()?.role || 'chat',
|
||||||
user: CORTEX_USER,
|
user: CORTEX_USER,
|
||||||
persona: CORTEX_PERSONA,
|
persona: CORTEX_PERSONA,
|
||||||
}),
|
}),
|
||||||
@@ -1171,7 +1197,7 @@
|
|||||||
|
|
||||||
function dispatchSend() {
|
function dispatchSend() {
|
||||||
if (current_mode === 'note') addNote();
|
if (current_mode === 'note') addNote();
|
||||||
else if (current_mode === 'agent') sendOrchestrate();
|
else if (toolsEnabled) sendOrchestrate();
|
||||||
else sendMessage();
|
else sendMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1636,6 +1662,7 @@
|
|||||||
|
|
||||||
updateTierUI();
|
updateTierUI();
|
||||||
updateMemUI();
|
updateMemUI();
|
||||||
|
updateToolsToggleUI();
|
||||||
update_mode_ui();
|
update_mode_ui();
|
||||||
|
|
||||||
// ── Init ─────────────────────────────────────────────────────
|
// ── Init ─────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -176,6 +176,8 @@
|
|||||||
<div id="mode-dropdown"></div>
|
<div id="mode-dropdown"></div>
|
||||||
<!-- Note visibility sub-toggle — only shown when note mode is active -->
|
<!-- Note visibility sub-toggle — only shown when note mode is active -->
|
||||||
<button id="note-vis-btn" title="Toggle note visibility (private / public)">prv</button>
|
<button id="note-vis-btn" title="Toggle note visibility (private / public)">prv</button>
|
||||||
|
<!-- Tools toggle — routes through the orchestrator tool loop when active -->
|
||||||
|
<button id="tools-toggle" title="Tools disabled — click to enable">⚡</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="input" rows="1" placeholder="Message…" autofocus></textarea>
|
<textarea id="input" rows="1" placeholder="Message…" autofocus></textarea>
|
||||||
<div id="send-col">
|
<div id="send-col">
|
||||||
|
|||||||
Reference in New Issue
Block a user