harness-tui + engine: jobs pane, subtask drill-in, reminders (M4)

TUI: Ctrl+J (or /jobs) opens a jobs pane listing the current session's board —
alias, agent, state, objective, files read — populated on load and kept live via
JobUpdated events; Enter drills into a subtask's child session. Snapshot test added.

Engine: optional orchestration reminders (off by default) injected as synthetic,
non-persisted turn-start blocks and, after a file tool runs, an after-file-tool
block on the following turn. Processor reports file-tool usage via StepOutcome.

This completes M4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 06:46:31 +02:00
co-authored by Claude Opus 4.8
parent 54f91b7e4c
commit 7738ed55b9
15 changed files with 426 additions and 28 deletions
@@ -33,6 +33,9 @@ pub struct StepOutcome {
/// Dollar cost of this step's usage (0.0 when no pricing is available).
pub cost: f64,
pub aborted: bool,
/// Whether a file-mutating tool (`edit`/`write`) ran this step — drives the optional
/// `after_file_tool` reminder injection on the next turn.
pub used_file_tool: bool,
}
pub struct StepError {
@@ -98,9 +101,13 @@ impl FlushTracker {
}
}
/// Tool names that mutate files — after one runs, the optional `after_file_tool` reminder fires.
const FILE_TOOLS: &[&str] = &["edit", "write"];
struct Run<'a> {
ctx: &'a StepContext,
assistant: Option<Message>,
used_file_tool: bool,
next_idx: u32,
active_text: Option<PartId>,
active_reasoning: Option<PartId>,
@@ -117,6 +124,7 @@ impl<'a> Run<'a> {
Self {
ctx,
assistant: None,
used_file_tool: false,
next_idx: 0,
active_text: None,
active_reasoning: None,
@@ -386,6 +394,9 @@ impl<'a> Run<'a> {
input: serde_json::Value,
doomloop: &mut DoomLoopGuard,
) -> Result<(), ProviderError> {
if FILE_TOOLS.contains(&name.as_str()) {
self.used_file_tool = true;
}
let part_id = self.pending_tools.remove(&call_id).unwrap_or_default();
let running = Part {
id: part_id.clone(),
@@ -607,6 +618,7 @@ pub async fn process_step(
usage,
cost: step_cost,
aborted: true,
used_file_tool: run.used_file_tool,
});
}
};
@@ -679,5 +691,6 @@ pub async fn process_step(
usage,
cost: step_cost,
aborted: false,
used_file_tool: run.used_file_tool,
})
}