diff --git a/hive-ag3nt/assets/agent.css b/hive-ag3nt/assets/agent.css index 249daa5..013fc82 100644 --- a/hive-ag3nt/assets/agent.css +++ b/hive-ag3nt/assets/agent.css @@ -126,6 +126,26 @@ pre.diff { } #state-row { margin: 0.4em 0 0.2em; + display: flex; + align-items: center; + gap: 0.6em; +} +.btn-cancel-turn { + font-family: inherit; + font-size: 0.8em; + letter-spacing: 0.08em; + background: transparent; + color: var(--red); + border: 1px solid var(--red); + border-radius: 999px; + padding: 0.2em 0.8em; + cursor: pointer; + text-shadow: 0 0 4px currentColor; + transition: box-shadow 0.15s ease, background 0.15s ease; +} +.btn-cancel-turn:hover { + background: rgba(243, 139, 168, 0.1); + box-shadow: 0 0 10px -2px currentColor; } .state-badge { display: inline-block; diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index 6183e93..a2f620f 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -161,10 +161,24 @@ let termAPI = null; const SLASH_COMMANDS = [ - { name: '/help', desc: 'list slash commands' }, - { name: '/clear', desc: 'wipe the terminal panel (local-only)' }, + { name: '/help', desc: 'list slash commands' }, + { name: '/clear', desc: 'wipe the terminal panel (local-only)' }, + { name: '/cancel', desc: 'SIGINT the in-flight claude turn' }, ]; + async function postCancelTurn() { + try { + const resp = await fetch('/api/cancel', { method: 'POST', redirect: 'manual' }); + const ok = resp.ok || resp.type === 'opaqueredirect' + || (resp.status >= 200 && resp.status < 400); + if (!ok && termAPI) { + termAPI.row('turn-end-fail', '✗ /cancel failed: http ' + resp.status); + } + } catch (err) { + if (termAPI) termAPI.row('turn-end-fail', '✗ /cancel failed: ' + err); + } + } + function handleSlashCommand(line) { if (!termAPI) return false; const trimmed = line.trim(); @@ -181,6 +195,9 @@ termAPI.clear(); termAPI.row('note', '· terminal cleared (local view only — server history kept)'); return true; + case '/cancel': + postCancelTurn(); + return true; default: termAPI.row('turn-end-fail', '✗ unknown slash command: ' + cmd + ' — try /help'); return true; @@ -282,6 +299,8 @@ const age = fmtAge(Date.now() - stateSince); badge.textContent = def.glyph + ' ' + def.text + ' · ' + age; badge.className = 'state-badge state-' + stateName; + const cancelBtn = $('cancel-btn'); + if (cancelBtn) cancelBtn.hidden = stateName !== 'thinking'; } function setState(next) { if (next === stateName) return; @@ -302,6 +321,16 @@ } startStateTicker(); + // Wire the cancel-turn button (visible only while state === thinking). + (() => { + const btn = $('cancel-btn'); + if (!btn) return; + btn.addEventListener('click', () => { + btn.disabled = true; + postCancelTurn().finally(() => { btn.disabled = false; }); + }); + })(); + // Track banner activity by reference-counting in-flight turns. A turn // can begin while the previous turn_end is still in the pipeline (rare // but happens on tight wake cycles), so we count rather than toggle. diff --git a/hive-ag3nt/assets/index.html b/hive-ag3nt/assets/index.html index 219f8d9..397fd32 100644 --- a/hive-ag3nt/assets/index.html +++ b/hive-ag3nt/assets/index.html @@ -15,6 +15,7 @@