From 6e23d087d2cef8f82e4c18bd3732e7e83cfcd368 Mon Sep 17 00:00:00 2001 From: damocles Date: Mon, 18 May 2026 18:22:49 +0200 Subject: [PATCH] =?UTF-8?q?rename:=20open=5Fthreads=20=E2=86=92=20loose=5F?= =?UTF-8?q?ends=20+=20cancel=5Fthread=20=E2=86=92=20cancel=5Floose=5Fend?= =?UTF-8?q?=20across=20wire=20/=20tools=20/=20web=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 11 ++- TODO.md | 2 +- hive-ag3nt/assets/app.js | 48 ++++++---- hive-ag3nt/assets/index.html | 6 +- hive-ag3nt/prompts/agent.md | 4 +- hive-ag3nt/prompts/manager.md | 4 +- hive-ag3nt/src/bin/hive-ag3nt.rs | 6 +- hive-ag3nt/src/bin/hive-m1nd.rs | 6 +- hive-ag3nt/src/mcp.rs | 96 +++++++++---------- hive-ag3nt/src/web_ui.rs | 26 ++--- hive-c0re/src/agent_server.rs | 6 +- .../src/{open_threads.rs => loose_ends.rs} | 22 ++--- hive-c0re/src/main.rs | 2 +- hive-c0re/src/manager_server.rs | 6 +- hive-c0re/src/questions.rs | 10 +- hive-sh4re/src/lib.rs | 36 +++---- 16 files changed, 152 insertions(+), 139 deletions(-) rename hive-c0re/src/{open_threads.rs => loose_ends.rs} (90%) diff --git a/CLAUDE.md b/CLAUDE.md index bba3919..b135dea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,11 +53,12 @@ hive-c0re/ host daemon + CLI (one binary, subcommand-dispatched) transient/sockets) + tombstone enumeration + kick_agent + notify_agent (helper-event push) + last_containers cache + rescan_and_emit diff helper - src/open_threads.rs loose-ends aggregator (pending approvals + - unanswered questions) — for_agent (filtered) and - hive_wide (manager surface). Backs - AgentRequest::GetOpenThreads + ManagerRequest:: - GetOpenThreads (the get_open_threads MCP tool). + src/loose_ends.rs loose-ends aggregator (pending approvals + + unanswered questions + pending reminders) — + for_agent (filtered) and hive_wide (manager + surface). Backs AgentRequest::GetLooseEnds + + ManagerRequest::GetLooseEnds (the + get_loose_ends MCP tool). src/actions.rs approve/deny/destroy (transient-aware) src/auto_update.rs startup rebuild scan + ensure_manager + meta::lock_update_hyperhive bump diff --git a/TODO.md b/TODO.md index e9046d6..c4c2b86 100644 --- a/TODO.md +++ b/TODO.md @@ -40,7 +40,7 @@ how often the friction bites in normal use. into the prompt builder in `hive-ag3nt::turn.rs`. Even better: add a one-shot `recv_batch(max: u32)` MCP tool that returns up to `max` pending messages in a single round-trip. -- ~~**Self-management of own asks + reminders**~~ ✓ landed — unified with `get_open_threads` rather than a separate listing surface. `OpenThread` gained a `Reminder { id, owner, message, due_at, age_seconds }` variant (sub-agent flavour filters by `owner == self`; manager unfiltered). New `mcp__hyperhive__cancel_thread(kind, id)` on both surfaces — `kind` is `"question"` (asker gets `[cancelled by ]` answer, unblocks) or `"reminder"` (hard-deleted before fire). Auth: sub-agent must own the row; manager bypasses for hive-wide cleanup. New helpers `OperatorQuestions::cancel` + `Broker::cancel_reminder_as` push the auth check down so both flavours stay aligned. Shared dispatch in `hive-c0re/src/questions.rs::handle_cancel_thread`. +- ~~**Self-management of own asks + reminders**~~ ✓ landed — unified with `get_loose_ends` (renamed from `get_open_threads` per the naming pass). `LooseEnd` enum (renamed from `OpenThread`) gained a `Reminder { id, owner, message, due_at, age_seconds }` variant (sub-agent flavour filters by `owner == self`; manager unfiltered). New `mcp__hyperhive__cancel_loose_end(kind, id)` on both surfaces — `kind` is `"question"` (asker gets `[cancelled by ]` answer, unblocks) or `"reminder"` (hard-deleted before fire). Auth: sub-agent must own the row; manager bypasses for hive-wide cleanup. New helpers `OperatorQuestions::cancel` + `Broker::cancel_reminder_as` push the auth check down so both flavours stay aligned. Shared dispatch in `hive-c0re/src/questions.rs::handle_cancel_loose_end`. Per-agent web UI's `/api/open-threads` → `/api/loose-ends` too, with reminder-row rendering added. - **Optional `in_reply_to: ` on send** — pure wire addition; no behavioural change. The dashboard could render conversation threads (already wants this for the agent-to-agent question UI in the diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index 709d9bd..82b6c80 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -393,43 +393,43 @@ } renderStateBadge(); } - // Open-threads section: same data the get_open_threads MCP tool + // Loose-ends section: same data the get_loose_ends MCP tool // returns. Best-effort fetch on cold load + after every turn_end // (a turn likely answered or asked something). Silent failure // keeps the section hidden rather than surfacing an empty banner. - let lastOpenThreadsCount = 0; - async function refreshOpenThreads() { + let lastLooseEndsCount = 0; + async function refreshLooseEnds() { try { - const resp = await fetch('/api/open-threads'); + const resp = await fetch('/api/loose-ends'); if (!resp.ok) { - renderOpenThreads([]); + renderLooseEnds([]); return; } const data = await resp.json(); - renderOpenThreads(data.threads || []); + renderLooseEnds(data.loose_ends || []); } catch (err) { - console.warn('open-threads fetch failed', err); - renderOpenThreads([]); + console.warn('loose-ends fetch failed', err); + renderLooseEnds([]); } } - function renderOpenThreads(threads) { - const root = $('open-threads-section'); - const list = $('open-threads-list'); - const summary = $('open-threads-summary'); + function renderLooseEnds(threads) { + const root = $('loose-ends-section'); + const list = $('loose-ends-list'); + const summary = $('loose-ends-summary'); if (!root || !list || !summary) return; if (!threads.length) { root.hidden = true; - lastOpenThreadsCount = 0; + lastLooseEndsCount = 0; return; } root.hidden = false; - summary.textContent = 'open threads · ' + threads.length; + summary.textContent = 'loose ends · ' + threads.length; list.innerHTML = ''; // Auto-expand on first appearance of any open thread so the // operator notices new loose ends; collapse only on operator // click (sticky after that). - if (lastOpenThreadsCount === 0) root.open = true; - lastOpenThreadsCount = threads.length; + if (lastLooseEndsCount === 0) root.open = true; + lastLooseEndsCount = threads.length; const fmtAge = (s) => { if (s < 60) return s + 's'; if (s < 3600) return Math.floor(s / 60) + 'm'; @@ -455,6 +455,18 @@ el('span', { class: 'inbox-ts' }, fmtAge(t.age_seconds || 0) + ' ago'), el('div', { class: 'inbox-body' }, t.question || ''), ); + } else if (t.kind === 'reminder') { + // due_at is an absolute unix-seconds value; show time-until-fire + // (negative when overdue, fmtAge handles 0/positive case here). + const now = Math.floor(Date.now() / 1000); + const dueIn = (t.due_at || 0) - now; + const dueLabel = dueIn >= 0 ? 'in ' + fmtAge(dueIn) : fmtAge(-dueIn) + ' overdue'; + li.append( + el('span', { class: 'inbox-from' }, '⏰ reminder #' + t.id), ' ', + el('span', { class: 'inbox-sep' }, t.owner + ' · due ' + dueLabel), ' ', + el('span', { class: 'inbox-ts' }, 'scheduled ' + fmtAge(t.age_seconds || 0) + ' ago'), + el('div', { class: 'inbox-body' }, t.message || ''), + ); } else { li.append(el('span', { class: 'inbox-body' }, JSON.stringify(t))); } @@ -614,7 +626,7 @@ // Open-threads aren't part of /api/state (kept on the broker // db, fetched via the per-agent socket). Cold-load fetches // it here; turn_end refreshes it via the renderer below. - refreshOpenThreads(); + refreshLooseEnds(); // Skip the re-render if nothing structurally changed. The most // common case is `online` polling itself — without this guard, the // operator's gets clobbered every cycle. @@ -956,7 +968,7 @@ } else { setBannerActive(false); setState('idle'); // Likely answered/asked/scheduled something — refresh. - refreshOpenThreads(); + refreshLooseEnds(); } const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail'; api.row(cls, diff --git a/hive-ag3nt/assets/index.html b/hive-ag3nt/assets/index.html index 8635a32..7e8dac8 100644 --- a/hive-ag3nt/assets/index.html +++ b/hive-ag3nt/assets/index.html @@ -29,9 +29,9 @@
    -