hive-agent: report a failed mid-turn /compact instead of recording it as compacted
The turn-end /compact (operator button or the agent's `compact` tool) dropped the compaction error with `let _ =`, emitted no note, and returned `Ok(true)`, so the turn was recorded `result_kind = "compacted"` although nothing compacted. The idle path already printed `/compact failed: …`. Both paths now go through one `report_compact` helper that emits the done / failed note; the turn-end path returns whether the compact succeeded.
This commit is contained in:
parent
a200935216
commit
7bb1959a94
2 changed files with 56 additions and 13 deletions
|
|
@ -236,7 +236,8 @@ Slash commands today:
|
||||||
`Bus::request_compact()` flag and returns immediately. The harness
|
`Bus::request_compact()` flag and returns immediately. The harness
|
||||||
runs the `/compact` at the next turn boundary (end of the in-flight
|
runs the `/compact` at the next turn boundary (end of the in-flight
|
||||||
turn, or `turn::run_pending_compact` when idle); output streams into
|
turn, or `turn::run_pending_compact` when idle); output streams into
|
||||||
the live panel. Works mid-turn, not only when idle.
|
the live panel. Works mid-turn, not only when idle. Either way the live
|
||||||
|
panel ends with a `/compact done` or `/compact failed: …` note.
|
||||||
- `/model <name>` — `POST /api/model` flipping `Bus::set_model`.
|
- `/model <name>` — `POST /api/model` flipping `Bus::set_model`.
|
||||||
Takes effect on the next turn; persisted to
|
Takes effect on the next turn; persisted to
|
||||||
`/state/hyperhive-model` so the override survives harness
|
`/state/hyperhive-model` so the override survives harness
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ pub async fn write_system_prompt(socket: &Path, label: &str) -> Result<PathBuf>
|
||||||
/// One claude turn's outcome: `Ok(compacted)` on success, or a [`TurnError`]
|
/// One claude turn's outcome: `Ok(compacted)` on success, or a [`TurnError`]
|
||||||
/// the serve loop must act on. The `compacted` bool is `true` when a
|
/// the serve loop must act on. The `compacted` bool is `true` when a
|
||||||
/// compaction ran this turn (reactively on overflow, or proactively per the
|
/// compaction ran this turn (reactively on overflow, or proactively per the
|
||||||
/// policy — or an operator `/compact` at turn end); it's recorded as
|
/// policy — or a successful operator `/compact` at turn end); it's recorded as
|
||||||
/// `result_kind = "compacted"` in turn stats so the stats page can distinguish
|
/// `result_kind = "compacted"` in turn stats so the stats page can distinguish
|
||||||
/// those turns. Both `Ok(true)` and `Ok(false)` are ack'd; the error cases
|
/// those turns. Both `Ok(true)` and `Ok(false)` are ack'd; the error cases
|
||||||
/// each map to a distinct serve-loop action (see [`emit_turn_end`] and the
|
/// each map to a distinct serve-loop action (see [`emit_turn_end`] and the
|
||||||
|
|
@ -461,7 +461,7 @@ pub async fn drive_turn(
|
||||||
// Reflect `Compacting` in the UI like the idle path (`run_pending_compact`)
|
// Reflect `Compacting` in the UI like the idle path (`run_pending_compact`)
|
||||||
// does; the serve loop resets to `Idle` once this turn returns.
|
// does; the serve loop resets to `Idle` once this turn returns.
|
||||||
bus.set_state(crate::events::TurnState::Compacting);
|
bus.set_state(crate::events::TurnState::Compacting);
|
||||||
let _ = session.compact(&config, &sink).await;
|
let compacted = report_compact(bus, session.compact(&config, &sink).await);
|
||||||
// If the compact call asked to be woken (the agent's own `compact`
|
// If the compact call asked to be woken (the agent's own `compact`
|
||||||
// tool with a `wake_prompt`), stash it — the serve loop reads it
|
// tool with a `wake_prompt`), stash it — the serve loop reads it
|
||||||
// back after this turn returns and drives a synthetic follow-up
|
// back after this turn returns and drives a synthetic follow-up
|
||||||
|
|
@ -470,11 +470,31 @@ pub async fn drive_turn(
|
||||||
if let Some(prompt) = request.wake_prompt {
|
if let Some(prompt) = request.wake_prompt {
|
||||||
bus.set_post_compact_wake(prompt);
|
bus.set_post_compact_wake(prompt);
|
||||||
}
|
}
|
||||||
return Ok(true);
|
return Ok(compacted);
|
||||||
}
|
}
|
||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit the `/compact done` / `/compact failed: …` note for a finished
|
||||||
|
/// operator or agent `/compact` and return whether it succeeded. Shared by the
|
||||||
|
/// turn-end path in [`drive_turn`] and the idle path in [`run_pending_compact`].
|
||||||
|
fn report_compact<E: std::fmt::Display>(bus: &Bus, result: Result<(), E>) -> bool {
|
||||||
|
match result {
|
||||||
|
Ok(()) => {
|
||||||
|
bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact done".into(),
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
bus.emit(LiveEvent::Note {
|
||||||
|
text: format!("/compact failed: {e}"),
|
||||||
|
});
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pre-turn auto-reset check. If context is large AND the prompt cache has
|
/// Pre-turn auto-reset check. If context is large AND the prompt cache has
|
||||||
/// gone cold (idle time >= cache TTL), archive the current session so the
|
/// gone cold (idle time >= cache TTL), archive the current session so the
|
||||||
/// next wake-up turn's `--resume <title>` misses and self-heals into a fresh
|
/// next wake-up turn's `--resume <title>` misses and self-heals into a fresh
|
||||||
|
|
@ -600,14 +620,7 @@ pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus, session: &AgentSe
|
||||||
bus.set_state(crate::events::TurnState::Compacting);
|
bus.set_state(crate::events::TurnState::Compacting);
|
||||||
let config = claude_config(bus, files);
|
let config = claude_config(bus, files);
|
||||||
let sink = BusSink::new(bus);
|
let sink = BusSink::new(bus);
|
||||||
match session.compact(&config, &sink).await {
|
report_compact(bus, session.compact(&config, &sink).await);
|
||||||
Ok(()) => bus.emit(LiveEvent::Note {
|
|
||||||
text: "/compact done".into(),
|
|
||||||
}),
|
|
||||||
Err(e) => bus.emit(LiveEvent::Note {
|
|
||||||
text: format!("/compact failed: {e}"),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
bus.set_state(crate::events::TurnState::Idle);
|
bus.set_state(crate::events::TurnState::Idle);
|
||||||
if let Some(prompt) = request.wake_prompt {
|
if let Some(prompt) = request.wake_prompt {
|
||||||
bus.set_post_compact_wake(prompt);
|
bus.set_post_compact_wake(prompt);
|
||||||
|
|
@ -821,8 +834,10 @@ fn archive_session(bus: &Bus, session: &AgentSession) {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
PermissionAsk, TurnError, acp_permits, nonzero_or, parse_u64, runtime_error_to_turn,
|
PermissionAsk, TurnError, acp_permits, nonzero_or, parse_u64, report_compact,
|
||||||
|
runtime_error_to_turn,
|
||||||
};
|
};
|
||||||
|
use crate::events::{Bus, LiveEvent};
|
||||||
use hive_runtime::{AcpError, Error};
|
use hive_runtime::{AcpError, Error};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -959,4 +974,31 @@ mod tests {
|
||||||
Err(TurnError::Failed(_))
|
Err(TurnError::Failed(_))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compact_report(result: Result<(), &str>) -> (bool, Vec<String>) {
|
||||||
|
let bus = Bus::new();
|
||||||
|
let mut rx = bus.subscribe();
|
||||||
|
let ok = report_compact(&bus, result);
|
||||||
|
let mut notes = Vec::new();
|
||||||
|
while let Ok(ev) = rx.try_recv() {
|
||||||
|
if let LiveEvent::Note { text } = ev.event {
|
||||||
|
notes.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ok, notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_failed_compact_is_reported_and_not_counted_as_compacted() {
|
||||||
|
let (compacted, notes) = compact_report(Err("spawn failed"));
|
||||||
|
assert!(!compacted);
|
||||||
|
assert_eq!(notes, ["/compact failed: spawn failed"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_successful_compact_is_reported_and_counted_as_compacted() {
|
||||||
|
let (compacted, notes) = compact_report(Ok(()));
|
||||||
|
assert!(compacted);
|
||||||
|
assert_eq!(notes, ["/compact done"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue