turn: propagate all three flags out of compact_session
This commit is contained in:
parent
c8120f9edc
commit
b647df3db8
2 changed files with 60 additions and 34 deletions
|
|
@ -306,15 +306,18 @@ pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutco
|
||||||
maybe_auto_reset(bus);
|
maybe_auto_reset(bus);
|
||||||
let outcome = match run_turn(prompt, files, bus).await {
|
let outcome = match run_turn(prompt, files, bus).await {
|
||||||
TurnOutcome::PromptTooLong => {
|
TurnOutcome::PromptTooLong => {
|
||||||
|
// Compact has its own three-flag surface (it's the same claude
|
||||||
|
// binary). Treat any non-Ok outcome the same as if `run_turn`
|
||||||
|
// had returned it — the serve loop already knows what to do
|
||||||
|
// with each variant, no point re-wrapping. PromptTooLong from
|
||||||
|
// /compact itself would be absurd recursion; bubble it up as
|
||||||
|
// a normal failure path.
|
||||||
match compact_session(files, bus).await {
|
match compact_session(files, bus).await {
|
||||||
Ok(true) => return TurnOutcome::AuthFailed,
|
TurnOutcome::Ok | TurnOutcome::Compacted => {
|
||||||
Ok(false) => {}
|
run_turn(prompt, files, bus).await
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!(error = %format!("{e:#}"), "compact failed");
|
|
||||||
return TurnOutcome::Failed(e);
|
|
||||||
}
|
}
|
||||||
|
other => return other,
|
||||||
}
|
}
|
||||||
run_turn(prompt, files, bus).await
|
|
||||||
}
|
}
|
||||||
// Rate-limited: no point retrying immediately — bubble up so the
|
// Rate-limited: no point retrying immediately — bubble up so the
|
||||||
// serve loop can park + emit status before the next attempt.
|
// serve loop can park + emit status before the next attempt.
|
||||||
|
|
@ -374,12 +377,23 @@ async fn maybe_checkpoint_and_compact(files: &TurnFiles, bus: &Bus) -> bool {
|
||||||
text: format!("checkpoint turn failed ({e:#}) — compacting anyway"),
|
text: format!("checkpoint turn failed ({e:#}) — compacting anyway"),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
// Best-effort: never changes the outcome of the turn that already
|
||||||
|
// succeeded. Mirror the checkpoint-turn handling above — emit a Note
|
||||||
|
// for each failure mode and move on; the next real turn will surface
|
||||||
|
// the underlying issue (rate-limit / 401 / etc.) through the normal
|
||||||
|
// path anyway.
|
||||||
match compact_session(files, bus).await {
|
match compact_session(files, bus).await {
|
||||||
Ok(true) => bus.emit(LiveEvent::Note {
|
TurnOutcome::Ok | TurnOutcome::Compacted => {}
|
||||||
|
TurnOutcome::PromptTooLong => bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact unexpectedly returned PromptTooLong — next turn will retry".into(),
|
||||||
|
}),
|
||||||
|
TurnOutcome::RateLimited => bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact was rate-limited — next turn will park + retry".into(),
|
||||||
|
}),
|
||||||
|
TurnOutcome::AuthFailed => bus.emit(LiveEvent::Note {
|
||||||
text: "/compact hit 401 — next turn will trigger the re-login flow".into(),
|
text: "/compact hit 401 — next turn will trigger the re-login flow".into(),
|
||||||
}),
|
}),
|
||||||
Ok(false) => {}
|
TurnOutcome::Failed(e) => {
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!(error = %format!("{e:#}"), "post-checkpoint compact failed");
|
tracing::warn!(error = %format!("{e:#}"), "post-checkpoint compact failed");
|
||||||
bus.emit(LiveEvent::Note {
|
bus.emit(LiveEvent::Note {
|
||||||
text: format!("/compact after checkpoint failed: {e:#}"),
|
text: format!("/compact after checkpoint failed: {e:#}"),
|
||||||
|
|
@ -516,31 +530,42 @@ pub async fn run_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutcome
|
||||||
/// compact state matches a normal turn's. Only the prompt over stdin
|
/// compact state matches a normal turn's. Only the prompt over stdin
|
||||||
/// differs (`/compact` vs the wake-up payload).
|
/// differs (`/compact` vs the wake-up payload).
|
||||||
///
|
///
|
||||||
/// Returns `true` if claude reported a 401 mid-compact — the caller
|
/// Returns the same `TurnOutcome` shape as `run_turn` so callers can
|
||||||
/// should bail into the re-login flow instead of treating compaction
|
/// react identically to all three failure flags (`prompt_too_long`,
|
||||||
/// as succeeded. (`prompt_too_long` / `rate_limited` are unlikely on
|
/// `rate_limited`, `auth_failed`). The reactive caller bubbles any
|
||||||
/// a slash command and are folded into the boolean by being ignored
|
/// non-Ok outcome up so the serve loop's normal handling (park +
|
||||||
/// — the next real turn surfaces them through the normal path.)
|
/// retry on rate-limit, flip to `needs_login` on 401, etc.) kicks in;
|
||||||
///
|
/// the proactive post-checkpoint caller stays best-effort and only
|
||||||
/// # Errors
|
/// emits a Note for each failure mode.
|
||||||
///
|
pub async fn compact_session(files: &TurnFiles, bus: &Bus) -> TurnOutcome {
|
||||||
/// Returns an error if the `claude --print /compact` invocation fails
|
|
||||||
/// (non-zero exit or I/O error).
|
|
||||||
pub async fn compact_session(files: &TurnFiles, bus: &Bus) -> Result<bool> {
|
|
||||||
bus.emit(LiveEvent::Note {
|
bus.emit(LiveEvent::Note {
|
||||||
text: "context overflow — running /compact on the persistent session".into(),
|
text: "context overflow — running /compact on the persistent session".into(),
|
||||||
});
|
});
|
||||||
let (_, _, auth_failed) = run_claude("/compact", files, bus).await?;
|
let outcome = match run_claude("/compact", files, bus).await {
|
||||||
if auth_failed {
|
Ok((true, _, _)) => TurnOutcome::PromptTooLong,
|
||||||
bus.emit(LiveEvent::Note {
|
Ok((_, true, _)) => TurnOutcome::RateLimited,
|
||||||
text: "/compact hit 401 — bubbling up for re-login flow".into(),
|
Ok((_, _, true)) => TurnOutcome::AuthFailed,
|
||||||
});
|
Ok(_) => TurnOutcome::Ok,
|
||||||
} else {
|
Err(e) => TurnOutcome::Failed(e),
|
||||||
bus.emit(LiveEvent::Note {
|
};
|
||||||
|
match &outcome {
|
||||||
|
TurnOutcome::Ok | TurnOutcome::Compacted => bus.emit(LiveEvent::Note {
|
||||||
text: "/compact done".into(),
|
text: "/compact done".into(),
|
||||||
});
|
}),
|
||||||
|
TurnOutcome::PromptTooLong => bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact reported PromptTooLong — bubbling up".into(),
|
||||||
|
}),
|
||||||
|
TurnOutcome::RateLimited => bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact was rate-limited — bubbling up".into(),
|
||||||
|
}),
|
||||||
|
TurnOutcome::AuthFailed => bus.emit(LiveEvent::Note {
|
||||||
|
text: "/compact hit 401 — bubbling up for re-login flow".into(),
|
||||||
|
}),
|
||||||
|
TurnOutcome::Failed(e) => bus.emit(LiveEvent::Note {
|
||||||
|
text: format!("/compact failed: {e:#}"),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
Ok(auth_failed)
|
outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
|
|
|
||||||
|
|
@ -851,12 +851,13 @@ async fn post_compact(State(state): State<AppState>) -> Response {
|
||||||
text: "operator: /compact — running on persistent session".into(),
|
text: "operator: /compact — running on persistent session".into(),
|
||||||
});
|
});
|
||||||
bus.set_state(crate::events::TurnState::Compacting);
|
bus.set_state(crate::events::TurnState::Compacting);
|
||||||
let r = crate::turn::compact_session(&files, &bus).await;
|
let outcome = crate::turn::compact_session(&files, &bus).await;
|
||||||
bus.set_state(crate::events::TurnState::Idle);
|
bus.set_state(crate::events::TurnState::Idle);
|
||||||
if let Err(e) = r {
|
// Best-effort manual /compact from the operator: compact_session
|
||||||
bus.emit(crate::events::LiveEvent::Note {
|
// already emits a Note per outcome, so we don't need to re-emit
|
||||||
text: format!("/compact failed: {e:#}"),
|
// here — just record any underlying error to the harness log.
|
||||||
});
|
if let crate::turn::TurnOutcome::Failed(e) = outcome {
|
||||||
|
tracing::warn!(error = %format!("{e:#}"), "operator /compact failed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
(axum::http::StatusCode::OK, "ok").into_response()
|
(axum::http::StatusCode::OK, "ok").into_response()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue