fix(permissions): mark stale agents and allow removing their explicit entries
The P3RM1SS10NS tab showed agents that no longer exist in the live
container roster — e.g. an agent named 'root' that was renamed or
destroyed but still had explicit entries in tool-groups.json and/or
capabilities.json. The roster-union behaviour is intentional for
temporarily-stopped agents, but stale entries from renamed/destroyed
agents are confusing.
Backend (dashboard/permissions.rs):
- New DELETE /api/permissions/{agent} handler that bypasses the live-
roster guard (intentionally — that's the point). Calls
tool_groups::remove_agent + capabilities::remove_agent to clear both
JSON files, then emits live SSE snapshots so the tab updates without
a page reload. Format-checks the agent name but does not require it to
be in the containers snapshot.
Frontend (permissions.js):
- renderCapabilities / renderToolGroups now cross-reference agentNames
against containersState (the live roster, already imported). Agents
not in the live roster get an isStale flag.
- Stale rows get a '(not running)' label and a '✕ remove' button that
calls clearStaleAgent() — a new async helper that DELETEs the stale
entry and re-fetches both perm tables.
- Non-stale agents without explicit assignments still get '(default)'.
CSS (dashboard.css):
- .perm-row-stale (reduced opacity), .perm-stale-label (muted small
text), .perm-remove-btn (small red-bordered button) + disabled state.
This commit is contained in:
parent
f060456860
commit
a8fb33e2ee
4 changed files with 116 additions and 6 deletions
|
|
@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use problem_details::ProblemDetails;
|
||||
|
||||
use super::{AppState, guard_agent_name, strip_container_prefix};
|
||||
use super::{AppState, guard_agent_name, strip_container_prefix, validate_agent_name};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct ToolGroupsSnapshot {
|
||||
|
|
@ -312,6 +312,39 @@ pub(super) async fn post_permissions(
|
|||
Ok((StatusCode::OK, "ok").into_response())
|
||||
}
|
||||
|
||||
/// Clear all explicit permission entries for a named agent without
|
||||
/// requiring it to exist in the live roster. Used by the P3RM1SS10NS
|
||||
/// tab's "remove" button for agents that have stale explicit entries
|
||||
/// in `tool-groups.json` / `capabilities.json` but are no longer
|
||||
/// running (e.g. an agent that was renamed or destroyed while its
|
||||
/// JSON entries persisted).
|
||||
///
|
||||
/// Bypasses `guard_agent_name`'s live-roster check intentionally —
|
||||
/// the whole point is to remove entries for non-roster agents. Only
|
||||
/// the format check (`validate_agent_name`) is applied. No rebuild is
|
||||
/// enqueued (the agent doesn't exist to rebuild); the SSE snapshots
|
||||
/// update the P3RM1SS10NS tab live.
|
||||
pub(super) async fn delete_agent_permissions(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(name): AxumPath<String>,
|
||||
) -> Response {
|
||||
let logical = strip_container_prefix(&name);
|
||||
if let Some(reason) = validate_agent_name(&logical) {
|
||||
return (StatusCode::BAD_REQUEST, format!("bad agent name: {reason}")).into_response();
|
||||
}
|
||||
if let Err(e) = crate::tool_groups::remove_agent(&logical) {
|
||||
tracing::warn!(agent = %logical, error = ?e, "failed to remove tool-groups entry");
|
||||
}
|
||||
if let Err(e) = crate::capabilities::remove_agent(&logical) {
|
||||
tracing::warn!(agent = %logical, error = ?e, "failed to remove capabilities entry");
|
||||
}
|
||||
// Emit live snapshots so the P3RM1SS10NS tab updates immediately.
|
||||
state.coord.emit_tool_groups_snapshot();
|
||||
state.coord.emit_capabilities_snapshot();
|
||||
tracing::info!(agent = %logical, "operator: cleared stale permission entries via dashboard");
|
||||
(StatusCode::OK, "ok").into_response()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::roster_and_effective;
|
||||
|
|
|
|||
Loading…
Reference in a new issue