mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
Open email context for agent, email search across All Mail, cookbook serve polish
- Agent: pass the open email reader (uid/folder/account/from/subject/body
preview) on every chat submit so 'reply to this' / 'write email saying
hi' route to ui_control open_email_reply with the right UID instead of
inventing a new .md draft. Code-level enforcement (chat_routes strips
create_document + send_email when active_email is set); cross-session
active_doc_id is now trusted instead of being silently dropped.
set_active_email/clear_active_email tool-layer helpers in
tool_implementations.
- ui_control open_email_reply: optional body argument so the agent can
open-and-write in one call; envelope now forwards uid/folder/account/
body/panel through tool_output. Tool description sharpened and the
parser rejects empty bodies on reply/reply-all (forces the agent to
write rather than open an empty draft).
- Email library: search now runs against [Gmail]/All Mail when the
current folder is INBOX (archived emails surface). Whirlpool spinner
+ 'Searching…' placeholder while in flight. Each search result is
stamped with its source folder so clicks open the right email instead
of whatever shares its UID in INBOX. Search no longer re-applies the
same text pill locally (which only checks subject/from/snippet, never
body) so body-only matches don't get dropped after IMAP returns them.
Initial inbox load bumped 100→500.
- Email favorites: 'Favorite (pin to top)' / 'Unfavorite' in both the
card menu and the open-reader more menu, backed by a new
/api/email/flag/{uid}?on=true|false endpoint. Flagged emails always
bubble to the top of the grid regardless of active sort.
- AI reply in doc editor: never overwrites existing draft text or the
quoted history. AI suggestion is prepended; AI-generated 'On …
wrote:' re-quotes are stripped so the original quote isn't visually
edited.
- Cookbook serve: pre-launch GPU driver / has_gpu / install / version-
floor checks (vllm minimax_m2 needs 0.10.0+, deepseek_r1 needs 0.7.0
etc.) before the launch chain starts. Detect 'another model already
running on this host' and offer Stop & launch (with graceful then
force tmux kill helpers, port release wait). Per-vendor deep-link
buttons (vLLM recipe / SGLang cookbook) with hardware hash. Backend
picker is now a custom dropdown with accent-coloured logos for vLLM,
SGLang, llama.cpp, Ollama, Diffusers; same glyphs added next to
package names in Dependencies. Runtime-readiness note moved inside
the panel (green when ready, red when missing) with an × dismiss.
Esc collapses the expanded card; expanded card scrolls when it
overflows; Trust Remote / Auto Tool / Reasoning Parser / Enforce
Eager / Prefix Caching / Expert Parallel / Speculative / MoE Env on
one row (Reasoning Parser auto-detected per model family).
Dtype→Row 1, GPUs→Row 2 (rightmost). Removed redundant GPU 'auto'
input — command builders read from the GPU button strip. Default
cookbook open is Download tab.
- Cookbook hwfit: 'Model (latest)' / 'Model (oldest)' header sorts by
release_date; release dates can be backfilled with the new
scripts/backfill_model_release_dates.py and recipe metadata pulled
with scripts/import_from_vllm_recipes.py against the upstream
vllm-project/recipes catalog (vllm_recipe + min_vllm_version stamped
on entries).
- Calendar: Quick add hint cycles a random Odysseus-themed example per
open (wooden horse Friday, crew muster 10am daily, council on
Ithaca, …). Typing a time like '11pm' in the event title updates
the hero clock live.
- Doc editor: email-mode Reply button (sparkle icon, accent) opens the
same Fast/Full + context popover the email reader uses; Ctrl+Alt+M
toggles markdown preview.
- Memories panel: custom sort picker with per-option icons, default
'Latest', visible Enabled/Disabled toggle text matching the section
description style.
This commit is contained in:
@@ -807,7 +807,7 @@ function _winSessionCmd(task, tmuxArgs) {
|
||||
return host ? `ssh ${pf}${host} 'tmux ${tmuxArgs}' 2>/dev/null` : `tmux ${tmuxArgs} 2>/dev/null`;
|
||||
}
|
||||
|
||||
function _tmuxGracefulKill(task) {
|
||||
export function _tmuxGracefulKill(task) {
|
||||
if (_isWindows(task)) {
|
||||
const host = task.remoteHost;
|
||||
const sd = host ? '$env:TEMP\\odysseus-sessions' : '$env:TEMP\\odysseus-tmux';
|
||||
@@ -824,6 +824,48 @@ function _tmuxGracefulKill(task) {
|
||||
return `tmux send-keys -t ${task.sessionId} C-c 2>/dev/null; sleep 2; tmux kill-session -t ${task.sessionId} 2>/dev/null`;
|
||||
}
|
||||
|
||||
// Force-kill escalation: SIGKILL the tmux pane's owning PID and any children,
|
||||
// then nuke the session. Use AFTER the graceful kill when the process is
|
||||
// still detected — vLLM sometimes ignores SIGINT during model init, and a
|
||||
// stuck CUDA context can survive `tmux kill-session` alone.
|
||||
export function _tmuxForceKill(task) {
|
||||
if (_isWindows(task)) {
|
||||
// Windows graceful path already does Stop-Process -Force, so the same
|
||||
// command serves as the "force" variant.
|
||||
return _tmuxGracefulKill(task);
|
||||
}
|
||||
const sid = task.sessionId;
|
||||
const inner =
|
||||
`PIDS=$(tmux list-panes -t ${sid} -F "#{pane_pid}" 2>/dev/null); ` +
|
||||
`if [ -n "$PIDS" ]; then ` +
|
||||
` for P in $PIDS; do ` +
|
||||
` pkill -KILL -P "$P" 2>/dev/null; ` +
|
||||
` kill -9 "$P" 2>/dev/null; ` +
|
||||
` done; ` +
|
||||
`fi; ` +
|
||||
`tmux kill-session -t ${sid} 2>/dev/null`;
|
||||
if (task.remoteHost) {
|
||||
return `ssh ${_sshPrefix(_getPort(task))}${task.remoteHost} ${_shQuote(inner)}`;
|
||||
}
|
||||
return inner;
|
||||
}
|
||||
|
||||
// Returns a shell snippet that prints "ALIVE" if the tmux session still
|
||||
// exists (or its main PID is still listed in /proc), "DEAD" otherwise.
|
||||
// Used by the Stop-all escalation to decide whether to force-kill.
|
||||
export function _tmuxIsAliveCheck(task) {
|
||||
if (_isWindows(task)) {
|
||||
// Skip the check on Windows — the graceful path already force-kills.
|
||||
return null;
|
||||
}
|
||||
const sid = task.sessionId;
|
||||
const inner = `if tmux has-session -t ${sid} 2>/dev/null; then echo ALIVE; else echo DEAD; fi`;
|
||||
if (task.remoteHost) {
|
||||
return `ssh ${_sshPrefix(_getPort(task))}${task.remoteHost} ${_shQuote(inner)}`;
|
||||
}
|
||||
return inner;
|
||||
}
|
||||
|
||||
function _shQuote(value) {
|
||||
return "'" + String(value ?? '').replace(/'/g, "'\\''") + "'";
|
||||
}
|
||||
@@ -1668,7 +1710,11 @@ export function _renderRunningTab() {
|
||||
group = document.createElement('div');
|
||||
group.className = 'cookbook-group hidden';
|
||||
group.dataset.backendGroup = 'Running';
|
||||
group.innerHTML = '<div class="admin-card" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">' +
|
||||
// No `flex:1` on the card — with overflow:visible (forced via #cookbook-modal
|
||||
// .cookbook-group > .admin-card), flex:1 collapsed the card to body height
|
||||
// and the body's scrollHeight stopped tracking the overflowing children.
|
||||
// Sized-to-content means cookbook-body's overflow-y:auto kicks in naturally.
|
||||
group.innerHTML = '<div class="admin-card" style="display:flex;flex-direction:column;">' +
|
||||
'<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px;">' +
|
||||
'<h2 style="margin:0;padding:0;line-height:1;">Active <span id="running-count" class="memory-count" style="font-size:0.6em;opacity:0.6;font-weight:normal">' + activeCount + '</span></h2>' +
|
||||
'</div>' +
|
||||
@@ -1761,9 +1807,21 @@ export function _renderRunningTab() {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation(); // don't toggle the section collapse (was an inline onclick, blocked by CSP)
|
||||
const host = btn.dataset.clearServer;
|
||||
if (!await window.styledConfirm(`Clear finished tasks on ${_serverName(host)}?`, { confirmText: 'Clear' })) return;
|
||||
const allTasks = _loadTasks();
|
||||
const toRemove = allTasks.filter(t => (t.remoteHost || '') === host && _canClearTask(t));
|
||||
// Bail with a clear message instead of silently doing nothing when
|
||||
// every task on this server is still running (nothing finished to
|
||||
// clear yet) — the previous behavior looked like the button was dead.
|
||||
if (!toRemove.length) {
|
||||
const stillRunning = allTasks.filter(t => (t.remoteHost || '') === host && t.status === 'running').length;
|
||||
const _msg = stillRunning
|
||||
? `No finished tasks on ${_serverName(host)} — ${stillRunning} still running. Stop them first to clear.`
|
||||
: `No finished tasks on ${_serverName(host)}.`;
|
||||
if (window.uiModule?.showToast) window.uiModule.showToast(_msg);
|
||||
else alert(_msg);
|
||||
return;
|
||||
}
|
||||
if (!await window.styledConfirm(`Clear ${toRemove.length} finished task${toRemove.length === 1 ? '' : 's'} on ${_serverName(host)}?`, { confirmText: 'Clear' })) return;
|
||||
const remaining = allTasks.filter(t => (t.remoteHost || '') !== host || !_canClearTask(t));
|
||||
_saveTasks(remaining);
|
||||
// Fade/slide each finished card out (same exit as the per-card clear)
|
||||
|
||||
Reference in New Issue
Block a user