feat(core): add stale permission entries sub-section to K3PT ST4T3
Adds a "stale permission entries" sub-section within the K3PT ST4T3 pane
on /core.html showing agents with explicit capability/tool-group JSON
entries but no live container — typically renamed or manually-deleted agents
whose entries persisted (e.g. the old "root" manager name after rename to
"ruth").
Each ghost agent gets a "✕ clear perms" button that calls
DELETE /api/permissions/{agent} (added in the prior commit). Lazy-loaded
on first K3PT ST4T3 tab activation; auto-refreshes on capabilities_changed
and tool_groups_changed SSE events.
core.js: track liveContainerNames from /api/state.containers; add
renderStalePerms + fetchAndRenderStalePerms; hook tab onShow + SSE handlers.
system-sections.css: new .tombstones-stale-* selectors for the ghost list
rows and error message.
core.html: add #tombstones-stale-perms div inside the K3PT ST4T3 pane;
expand comment to describe both sub-sections.
This commit is contained in:
parent
24cf69f72c
commit
6ab0757cc6
3 changed files with 133 additions and 1 deletions
|
|
@ -71,13 +71,19 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- K3PT ST4T3: tombstoned-agent kept state. -->
|
<!-- K3PT ST4T3: tombstoned-agent kept state + stale permission entries.
|
||||||
|
tombstones-section shows destroyed agents (revive / purge buttons).
|
||||||
|
tombstones-stale-perms is lazy-loaded on tab activation and shows
|
||||||
|
agents with explicit capability/tool-group entries but no live
|
||||||
|
container (typically renamed/deleted agents whose JSON entries
|
||||||
|
persisted). Each stale entry gets a "✕ clear perms" button. -->
|
||||||
<section class="core-pane" id="core-pane-kept" data-tab-pane="kept"
|
<section class="core-pane" id="core-pane-kept" data-tab-pane="kept"
|
||||||
role="tabpanel" aria-labelledby="core-tab-kept">
|
role="tabpanel" aria-labelledby="core-tab-kept">
|
||||||
<p class="meta">kept state from previously tombstoned agents — recreating an agent with the same name reuses it.</p>
|
<p class="meta">kept state from previously tombstoned agents — recreating an agent with the same name reuses it.</p>
|
||||||
<div id="tombstones-section">
|
<div id="tombstones-section">
|
||||||
<p class="meta">loading…</p>
|
<p class="meta">loading…</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="tombstones-stale-perms"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- C0NT41N3R L04D: live cpu + memory per agent container, from
|
<!-- C0NT41N3R L04D: live cpu + memory per agent container, from
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ let metaInputsState = [];
|
||||||
let metaUpdateRunning = false;
|
let metaUpdateRunning = false;
|
||||||
let tombstonesState = [];
|
let tombstonesState = [];
|
||||||
let rebuildQueueState = [];
|
let rebuildQueueState = [];
|
||||||
|
// Live container names — used by the stale-perms ghost filter.
|
||||||
|
let liveContainerNames = new Set();
|
||||||
|
|
||||||
function syncFromSnapshot(s) {
|
function syncFromSnapshot(s) {
|
||||||
metaInputsState = (s.meta_inputs || []).slice();
|
metaInputsState = (s.meta_inputs || []).slice();
|
||||||
metaUpdateRunning = !!s.meta_update_running;
|
metaUpdateRunning = !!s.meta_update_running;
|
||||||
tombstonesState = (s.tombstones || []).slice();
|
tombstonesState = (s.tombstones || []).slice();
|
||||||
rebuildQueueState = (s.rebuild_queue || []).slice();
|
rebuildQueueState = (s.rebuild_queue || []).slice();
|
||||||
|
liveContainerNames = new Set((s.containers || []).map((c) => c.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── meta inputs ──────────────────────────────────────────────────────────
|
// ─── meta inputs ──────────────────────────────────────────────────────────
|
||||||
|
|
@ -447,6 +450,79 @@ function renderTombstones(s) {
|
||||||
root.append(ul);
|
root.append(ul);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── stale permission entries (K3PT ST4T3 pane sub-section) ──────────────
|
||||||
|
// Agents with explicit capability / tool-group JSON entries but no live
|
||||||
|
// container (e.g. renamed agents like the old "root" manager name). Lazy-
|
||||||
|
// loaded on first "kept" tab activation; refreshed when perm data changes.
|
||||||
|
let stalePermsLoaded = false;
|
||||||
|
|
||||||
|
function renderStalePerms(root, ghosts) {
|
||||||
|
root.replaceChildren();
|
||||||
|
if (!ghosts.length) return;
|
||||||
|
root.append(el('p', { class: 'tombstones-stale-heading' }, 'stale permission entries'));
|
||||||
|
root.append(el('p', { class: 'meta' },
|
||||||
|
'agents with explicit capability or tool-group entries but no live container '
|
||||||
|
+ '(typically renamed or manually-deleted agents whose JSON entries persisted).'));
|
||||||
|
const errP = el('p', { class: 'tombstones-stale-err', hidden: true });
|
||||||
|
const ul = el('ul', { class: 'tombstones-stale-list' });
|
||||||
|
for (const name of ghosts) {
|
||||||
|
const li = el('li', { class: 'tombstones-stale-row' });
|
||||||
|
li.append(el('span', { class: 'tombstones-stale-name' }, name));
|
||||||
|
li.append(el('span', { class: 'badge badge-muted' }, 'stale perms'));
|
||||||
|
const btn = el('button', {
|
||||||
|
type: 'button',
|
||||||
|
class: 'btn btn-destroy',
|
||||||
|
title: 'remove explicit capability and tool-group entries for ' + name,
|
||||||
|
}, '✕ clear perms');
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
btn.disabled = true;
|
||||||
|
errP.hidden = true;
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/permissions/' + encodeURIComponent(name), { method: 'DELETE' });
|
||||||
|
if (!resp.ok) {
|
||||||
|
const msg = await resp.text().catch(() => String(resp.status));
|
||||||
|
errP.textContent = 'failed to clear perms for ' + name + ': ' + msg;
|
||||||
|
errP.hidden = false;
|
||||||
|
btn.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errP.textContent = 'failed to clear perms for ' + name + ': ' + err;
|
||||||
|
errP.hidden = false;
|
||||||
|
btn.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchAndRenderStalePerms();
|
||||||
|
});
|
||||||
|
li.append(btn);
|
||||||
|
ul.append(li);
|
||||||
|
}
|
||||||
|
root.append(ul, errP);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndRenderStalePerms() {
|
||||||
|
const root = $('tombstones-stale-perms');
|
||||||
|
if (!root) return;
|
||||||
|
try {
|
||||||
|
const [capsResp, tgResp] = await Promise.all([
|
||||||
|
fetch('/api/capabilities'),
|
||||||
|
fetch('/api/tool-groups'),
|
||||||
|
]);
|
||||||
|
const capsData = capsResp.ok ? await capsResp.json() : { assignments: {} };
|
||||||
|
const tgData = tgResp.ok ? await tgResp.json() : { assignments: {} };
|
||||||
|
const explicit = new Set([
|
||||||
|
...Object.keys(capsData.assignments || {}),
|
||||||
|
...Object.keys(tgData.assignments || {}),
|
||||||
|
]);
|
||||||
|
const ghosts = [...explicit].filter((n) => !liveContainerNames.has(n)).sort();
|
||||||
|
renderStalePerms(root, ghosts);
|
||||||
|
} catch (err) {
|
||||||
|
root.replaceChildren();
|
||||||
|
root.append(el('p', { class: 'meta' }, 'failed to load perm data: ' + err));
|
||||||
|
}
|
||||||
|
stalePermsLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── container load (live cgroup poll while the LOAD sub-tab is open) ──────
|
// ─── container load (live cgroup poll while the LOAD sub-tab is open) ──────
|
||||||
let containerLoadTimer = null;
|
let containerLoadTimer = null;
|
||||||
|
|
||||||
|
|
@ -602,6 +678,14 @@ const SSE_HANDLERS = {
|
||||||
tombstonesState = (ev.tombstones || []).slice();
|
tombstonesState = (ev.tombstones || []).slice();
|
||||||
renderTombstones({ tombstones: tombstonesState });
|
renderTombstones({ tombstones: tombstonesState });
|
||||||
},
|
},
|
||||||
|
// Refresh the stale-perms sub-section when perm data changes (a ghost
|
||||||
|
// was cleared, or perms were saved for an agent whose name collides).
|
||||||
|
capabilities_changed(_ev) {
|
||||||
|
if (stalePermsLoaded) fetchAndRenderStalePerms();
|
||||||
|
},
|
||||||
|
tool_groups_changed(_ev) {
|
||||||
|
if (stalePermsLoaded) fetchAndRenderStalePerms();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── boot ─────────────────────────────────────────────────────────────────
|
// ─── boot ─────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -635,6 +719,9 @@ async function init() {
|
||||||
onShow: (id) => {
|
onShow: (id) => {
|
||||||
if (id === 'load') startContainerLoadPolling();
|
if (id === 'load') startContainerLoadPolling();
|
||||||
else stopContainerLoadPolling();
|
else stopContainerLoadPolling();
|
||||||
|
// Lazy-load stale-perms on first K3PT ST4T3 activation; always
|
||||||
|
// re-fetch on subsequent visits in case perms changed.
|
||||||
|
if (id === 'kept') fetchAndRenderStalePerms();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,3 +221,42 @@
|
||||||
.cload-meter .fill.hot {
|
.cload-meter .fill.hot {
|
||||||
background: var(--red);
|
background: var(--red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── K3PT ST4T3 › stale permission entries sub-section ────────────────────
|
||||||
|
Agents with explicit capability / tool-group entries in the JSON but no
|
||||||
|
live container (renamed or manually-deleted agents). Lazy-loaded on tab
|
||||||
|
activation, refreshed on capabilities_changed / tool_groups_changed. */
|
||||||
|
.tombstones-stale-heading {
|
||||||
|
margin: 1.2em 0 0.3em;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.8em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 0.8em;
|
||||||
|
}
|
||||||
|
.tombstones-stale-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.3em;
|
||||||
|
}
|
||||||
|
.tombstones-stale-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.35em 0.6em;
|
||||||
|
border: 1px dashed var(--border);
|
||||||
|
background: color-mix(in srgb, var(--bg-elev) 35%, transparent);
|
||||||
|
}
|
||||||
|
.tombstones-stale-name {
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.tombstones-stale-err {
|
||||||
|
margin-top: 0.4em;
|
||||||
|
color: var(--red);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue