destroy --purge: also wipe agent state dirs
new --purge flag on the destroy verb (cli + admin socket + dashboard).
default destroy still keeps /var/lib/hyperhive/{agents,applied}/<name>/
so recreating with the same name reuses prior config + creds.
with --purge, both dirs go too (config history, claude creds, /state/
notes). no undo. dashboard adds a separate PURG3 button with an
explicit confirmation copy; the existing DESTR0Y button keeps the
soft semantics.
claude.md dashboard-action-surface section updated; todo entry
dropped.
This commit is contained in:
parent
8d3df656de
commit
48ebfefd1a
8 changed files with 78 additions and 28 deletions
|
|
@ -337,7 +337,9 @@ loops over every stale container.
|
|||
Container row buttons (rendered per-state by `assets/app.js`):
|
||||
|
||||
- Always: `↻ R3BU1LD` (calls `lifecycle::rebuild`), and for sub-agents
|
||||
`DESTR0Y` (container removed, state + creds kept).
|
||||
`DESTR0Y` (container removed, state + creds kept) + `PURG3`
|
||||
(DESTR0Y plus wipes `/var/lib/hyperhive/{agents,applied}/<name>/`;
|
||||
no undo).
|
||||
- Running: `↺ R3ST4RT` + (sub-agents only) `■ ST0P`.
|
||||
- Stopped: `▶ ST4RT`.
|
||||
- Stale marker: clickable `needs update ↻` badge (same target as rebuild
|
||||
|
|
|
|||
23
TODO.md
23
TODO.md
|
|
@ -42,11 +42,18 @@ Pick anything from here when relevant. Cross-cutting design notes live in
|
|||
channel from the harness: idle when waiting on the inbox, thinking
|
||||
while claude's stream is open, compacting when `/compact` is in
|
||||
flight. Replaces the binary "harness alive — turn loop running" line.
|
||||
- **Terminal: slash commands + tab-completion.** Operator-facing
|
||||
in-terminal commands: `/help`, `/model`, `/compact`, `/clear`. Tab
|
||||
completes command names + model names (cf. bitburner-agent's pattern).
|
||||
- **Terminal: multi-line input.** Replace the single-line `<input>` with
|
||||
an auto-growing textarea; Enter sends, Shift+Enter newlines.
|
||||
- **Terminal: slash commands beyond /help and /clear.** Operator-facing
|
||||
in-terminal commands still to add: `/model`, `/compact`, `/cancel`.
|
||||
Each needs harness-side support (model override, force compaction,
|
||||
cancel current claude turn).
|
||||
- **Terminal: bigger.** The 32em max-height is cramped on a 1080p+
|
||||
screen. Grow it (e.g. `min(70vh, 60em)`) so the live tail is the
|
||||
main visual element of the page rather than a strip.
|
||||
- **Terminal: sticky-bottom auto-scroll.** Today every appended row
|
||||
scrolls to bottom, so the view shifts while the operator is reading
|
||||
scrolled-up. Track whether the user is *already* at the bottom
|
||||
(within a small threshold), and only auto-scroll when that's true.
|
||||
Show a small "↓ N new" indicator when not at bottom; click to jump.
|
||||
- **Terminal: cancel-current-turn button.** Explicit "kill claude
|
||||
process for this turn" control. Harness needs to track the
|
||||
in-flight claude child PID and offer a `/cancel` endpoint that sends
|
||||
|
|
@ -57,9 +64,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in
|
|||
same session id. Surfaces as a slash command in the terminal + a
|
||||
toolbar button while the state badge is `idle`. Sets state to
|
||||
`compacting` during the run.
|
||||
- **Visuals.** Frosted-glass backdrop blur on the terminal wrap,
|
||||
per-event fade-in slide-up animation on new rows, badge pulse
|
||||
animation on state-badge transitions.
|
||||
- **xterm.js terminal** embedded per-agent, attached to a PTY exposed by
|
||||
the harness. Pairs well with the unprivileged-container work — would let
|
||||
the operator drop into the container without `nixos-container root-login`.
|
||||
|
|
@ -115,9 +119,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in
|
|||
- **Container crash events.** Watch `container@*.service` via D-Bus, push
|
||||
`HelperEvent::ContainerCrash` to the manager's inbox so the manager can
|
||||
react (restart, escalate, etc.).
|
||||
- **`destroy --purge`.** Today `destroy` keeps state by design; add an
|
||||
opt-in flag (CLI + dashboard) to also wipe `/var/lib/hyperhive/agents/<name>/`
|
||||
and `/var/lib/hyperhive/applied/<name>/`.
|
||||
|
||||
## Cleanup / docs
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,10 @@
|
|||
' ',
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'DESTR0Y',
|
||||
'destroy ' + c.name + '? container is removed; state + creds kept.'),
|
||||
' ',
|
||||
form('/destroy/' + c.name, 'btn-destroy', 'PURG3',
|
||||
'PURGE ' + c.name + '? container, config history, claude creds, '
|
||||
+ 'and /state/ notes are all WIPED. no undo.', { purge: 'on' }),
|
||||
);
|
||||
}
|
||||
ul.append(li);
|
||||
|
|
|
|||
|
|
@ -132,22 +132,40 @@ fn finish_approval(
|
|||
/// kept, so recreating an agent of the same name reuses prior config + creds
|
||||
/// (no re-login). The ephemeral runtime dir under `/run/hyperhive/agents/`
|
||||
/// is cleared because its contents (the mcp socket) don't survive restarts
|
||||
/// anyway. A future `--purge` path can wipe state when the operator opts in.
|
||||
/// anyway. With `purge=true` the persistent trees are also wiped — config
|
||||
/// history, claude creds, notes — there is no undo.
|
||||
/// Refuses the manager (declarative; would fight with the host's nixos config).
|
||||
pub async fn destroy(coord: &Coordinator, name: &str) -> Result<()> {
|
||||
pub async fn destroy(coord: &Coordinator, name: &str, purge: bool) -> Result<()> {
|
||||
if name == MANAGER_NAME || name == MANAGER_AGENT {
|
||||
bail!("refusing to destroy the manager ({name})");
|
||||
}
|
||||
tracing::info!(%name, "destroy");
|
||||
tracing::info!(%name, purge, "destroy");
|
||||
lifecycle::destroy(name).await?;
|
||||
coord.unregister_agent(name);
|
||||
let runtime = Coordinator::agent_dir(name);
|
||||
if runtime.exists() {
|
||||
let _ = std::fs::remove_dir_all(&runtime);
|
||||
}
|
||||
let _ = coord
|
||||
.approvals
|
||||
.fail_pending_for_agent(name, "agent destroyed");
|
||||
if purge {
|
||||
for dir in [
|
||||
Coordinator::agent_state_root(name),
|
||||
Coordinator::agent_applied_dir(name),
|
||||
] {
|
||||
if dir.exists()
|
||||
&& let Err(e) = std::fs::remove_dir_all(&dir)
|
||||
{
|
||||
tracing::warn!(error = ?e, dir = %dir.display(), "purge: remove failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = coord.approvals.fail_pending_for_agent(
|
||||
name,
|
||||
if purge {
|
||||
"agent purged"
|
||||
} else {
|
||||
"agent destroyed"
|
||||
},
|
||||
);
|
||||
coord.notify_manager(&HelperEvent::Destroyed {
|
||||
agent: name.to_owned(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -424,8 +424,20 @@ fn strip_container_prefix(name: &str) -> String {
|
|||
.to_owned()
|
||||
}
|
||||
|
||||
async fn post_destroy(State(state): State<AppState>, AxumPath(name): AxumPath<String>) -> Response {
|
||||
match actions::destroy(&state.coord, &name).await {
|
||||
#[derive(Deserialize, Default)]
|
||||
struct DestroyForm {
|
||||
#[serde(default)]
|
||||
purge: Option<String>,
|
||||
}
|
||||
|
||||
async fn post_destroy(
|
||||
State(state): State<AppState>,
|
||||
AxumPath(name): AxumPath<String>,
|
||||
Form(form): Form<DestroyForm>,
|
||||
) -> Response {
|
||||
// Checkbox semantics: any non-empty value (axum sends "on") = purge.
|
||||
let purge = form.purge.as_deref().is_some_and(|v| !v.is_empty());
|
||||
match actions::destroy(&state.coord, &name, purge).await {
|
||||
Ok(()) => Redirect::to("/").into_response(),
|
||||
Err(e) => error_response(&format!("destroy {name} failed: {e:#}")),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,14 @@ enum Cmd {
|
|||
/// Stop a managed container (graceful).
|
||||
Kill { name: String },
|
||||
/// Tear down a sub-agent container. Container is removed; persistent
|
||||
/// state (config repos + Claude credentials) is kept by default.
|
||||
Destroy { name: String },
|
||||
/// state (config repos + Claude credentials) is kept by default. Pass
|
||||
/// `--purge` to also wipe the agent's state dirs (config + creds +
|
||||
/// notes). No undo.
|
||||
Destroy {
|
||||
name: String,
|
||||
#[arg(long)]
|
||||
purge: bool,
|
||||
},
|
||||
/// Apply pending config to a managed container.
|
||||
Rebuild { name: String },
|
||||
/// List managed containers.
|
||||
|
|
@ -121,8 +127,8 @@ async fn main() -> Result<()> {
|
|||
Cmd::Kill { name } => {
|
||||
render(client::request(&cli.socket, HostRequest::Kill { name }).await?)
|
||||
}
|
||||
Cmd::Destroy { name } => {
|
||||
render(client::request(&cli.socket, HostRequest::Destroy { name }).await?)
|
||||
Cmd::Destroy { name, purge } => {
|
||||
render(client::request(&cli.socket, HostRequest::Destroy { name, purge }).await?)
|
||||
}
|
||||
Cmd::Rebuild { name } => {
|
||||
render(client::request(&cli.socket, HostRequest::Rebuild { name }).await?)
|
||||
|
|
|
|||
|
|
@ -115,8 +115,8 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
|||
});
|
||||
HostResponse::success()
|
||||
}
|
||||
HostRequest::Destroy { name } => {
|
||||
actions::destroy(&coord, name).await?;
|
||||
HostRequest::Destroy { name, purge } => {
|
||||
actions::destroy(&coord, name, *purge).await?;
|
||||
HostResponse::success()
|
||||
}
|
||||
HostRequest::Rebuild { name } => {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,15 @@ pub enum HostRequest {
|
|||
/// Tear down a sub-agent container: stop + remove + drop the systemd
|
||||
/// drop-in, purge pending approvals. Persistent state (proposed/applied
|
||||
/// repos, Claude credentials) is KEPT by default — recreating the agent
|
||||
/// with the same name reuses prior config + login. Manager not destroyable.
|
||||
Destroy { name: String },
|
||||
/// with the same name reuses prior config + login. With `purge=true`
|
||||
/// the agent's `/var/lib/hyperhive/{agents,applied}/<name>/` trees are
|
||||
/// also wiped (config history + creds + notes gone forever). Manager
|
||||
/// not destroyable.
|
||||
Destroy {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
purge: bool,
|
||||
},
|
||||
/// Apply pending config to a managed container.
|
||||
Rebuild { name: String },
|
||||
/// List managed containers.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue