wip: unfinished review fix-up (interrupted by the session usage limit)

Uncommitted edits from the fix-up stage, saved so nothing is lost. Not
compiled or tested after these edits; the next pass reviews and finishes them.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-09-25 10:42:25 -07:00
1 parent 3bedc41a91
commit 74cf2c043c
4 files changed
+224 -22

No files matched your search

+6 -1
View File
@@ -10099,7 +10099,12 @@ impl ToolSpec for AgentTool {
"runtime": {
"type": "string",
"enum": ["local", "cloud"],
"description": "For action=start. local (default) runs the agent here. cloud only proposes a long job for a cloud sandbox that raises a branch and opens a PR: it starts nothing and spends nothing, and returns a job the person confirms with /dispatch confirm <id>. Never confirm it yourself. Pass only prompt, plus remote (github, cnb or gitee) when the repository has several forges."
"description": "For action=start. local (default) runs the agent here. cloud only proposes a long job for a cloud sandbox that raises a branch and opens a PR: it starts nothing and spends nothing, and returns a job the person confirms with /dispatch confirm <id>. Never confirm it yourself. Pass only prompt, plus remote when the repository has several forges."
},
"remote": {
"type": "string",
"enum": ["github", "cnb", "gitee"],
"description": "For runtime=cloud only: which forge gets the branch and PR when the repository has several."
},
"detached": {
"type": "boolean",
+1
View File
@@ -5844,6 +5844,7 @@ fn subagent_tool_schemas_advertise_real_type_and_role_vocabulary() {
"offset",
"profile",
"prompt",
"remote",
"resume_from",
"runtime",
"thinking",
+160 -7
View File
@@ -342,7 +342,11 @@ fn finish_workflow_controller(state: &WorkflowWorkspaceState, record: &WorkflowR
if let Some(report) = &report {
receipt["report"] = json!(report);
}
let summary = workflow_receipt_summary(record, report.as_deref());
let summary = workflow_receipt_summary(
record,
&controller.driver.task_records_snapshot(),
report.as_deref(),
);
let payload =
format!("{summary}\n<codewhale:subagent.done>{receipt}</codewhale:subagent.done>");
debug_assert!(payload.len() <= WORKFLOW_COMPLETION_MAX_BYTES);
@@ -358,8 +362,14 @@ fn finish_workflow_controller(state: &WorkflowWorkspaceState, record: &WorkflowR
/// The plain-language half of a workflow's parent receipt: what ended, how
/// many agents finished and why the others stopped, where the report is, and
/// a bounded result preview. Event names and the JSON receipt carry the
/// machine-readable status; this is for the reader.
fn workflow_receipt_summary(record: &WorkflowRunRecord, report: Option<&str>) -> String {
/// machine-readable status; this is for the reader. `tasks` is the driver's
/// per-task ledger, which counts the agents once the run's retained event
/// tail has been trimmed.
fn workflow_receipt_summary(
record: &WorkflowRunRecord,
tasks: &[RuntimeTaskRecord],
report: Option<&str>,
) -> String {
let name = record
.workflow_goal
.as_deref()
@@ -367,7 +377,13 @@ fn workflow_receipt_summary(record: &WorkflowRunRecord, report: Option<&str>) ->
.filter(|goal| !goal.is_empty())
.map(|goal| format!("Workflow \"{}\"", truncate_chars(goal, 160)))
.unwrap_or_else(|| format!("Workflow {}", truncate_chars(&record.run_id, 64)));
let tally = WorkflowTally::from_events(&record.events);
let mut tally = WorkflowTally::from_events(&record.events);
if record.events_dropped > 0 {
tally.count_from_ledger(
tasks.iter().map(|task| task.status),
record.dispatch_failure_count,
);
}
let mut summary = format!(
"{name} {}: {}.",
view::run_outcome_phrase(record.status),
@@ -1706,6 +1722,9 @@ fn cancel_workflow_run(
state.mark_owner_missing(run_id);
let mut failed = snapshot;
failed.error = Some(format!("workflow cancellation journal failed: {err}"));
if let Some(controller) = controller.as_ref() {
write_run_report_artifact(&controller.driver.workspace, &failed);
}
finish_workflow_controller(&state, &failed);
return Err(ToolError::execution_failed(format!(
"workflow cancellation journal failed: {err}"
@@ -1717,6 +1736,9 @@ fn cancel_workflow_run(
// the live panel finalizes running rows and cannot remain visually failed.
if let Some(controller) = controller {
controller.driver.emit_ui_event(&cancelled_event);
// The receipt links the report, so write it first; the VM's own
// terminal path may rewrite it moments later.
write_run_report_artifact(&controller.driver.workspace, &snapshot);
}
finish_workflow_controller(&state, &snapshot);
workflow_result_for(run_id, state, owner_session_id)
@@ -12325,8 +12347,11 @@ FINAL RECEIPT
));
record.result = Some(json!({"a": "ok", "b": null}));
let summary =
workflow_receipt_summary(&record, Some(".codewhale/reports/workflow_d08d912f.md"));
let summary = workflow_receipt_summary(
&record,
&[],
Some(".codewhale/reports/workflow_d08d912f.md"),
);
let first_line = summary.lines().next().expect("sentence");
assert_eq!(
first_line,
@@ -12336,13 +12361,86 @@ FINAL RECEIPT
assert!(summary.contains("\nReport: .codewhale/reports/workflow_d08d912f.md"));
assert!(!summary.contains("Degraded") && !summary.contains(" ended "));
let unreported = workflow_receipt_summary(&record, None);
let unreported = workflow_receipt_summary(&record, &[], None);
assert!(
unreported.contains("No report file was written; `workflow status workflow_d08d912f`"),
"{unreported}"
);
}
#[test]
fn receipt_counts_from_the_task_ledger_once_events_were_trimmed() {
let mut record = WorkflowRunRecord::new(
"workflow_trimmed".to_string(),
Some("session-test".to_string()),
None,
None,
None,
);
record.status = WorkflowRunStatus::Degraded;
record.workflow_goal = Some("Survey".to_string());
// Chatty run: logs push every task_started out of the retained tail.
for index in 0..(WORKFLOW_RUN_EVENTS_MAX_RETAINED + 50) {
record.push_event(WorkflowUiEvent::at(
1,
"session-test",
WorkflowUiEventKind::Log {
message: format!("line {index}"),
},
));
}
let budget = TaskCompletion::BudgetExhausted {
message: "child step budget exhausted".to_string(),
};
let (reason, kind) = view::task_stop(&budget);
record.push_event(WorkflowUiEvent::at(
2,
"session-test",
WorkflowUiEventKind::TaskCompleted {
task_id: "agent_mid".to_string(),
status: IrWorkflowRunStatus::BudgetExceeded,
reason,
kind,
usage: None,
},
));
record.dispatch_failure_count = 1;
assert!(record.events_dropped > 0);
// Events alone still count the agent they name as stopped.
let events_only = workflow_receipt_summary(&record, &[], None);
assert!(
events_only.starts_with(
"Workflow \"Survey\" finished with gaps: 0 of 1 agent finished, 1 failed \
(agent_mid: stopped at the step limit)."
),
"{events_only}"
);
let task = |index: usize, status| RuntimeTaskRecord {
agent_id: format!("agent_{index}"),
label: None,
role: None,
status,
output: None,
schema_error: None,
usage: None,
};
let mut ledger: Vec<RuntimeTaskRecord> = (0..598)
.map(|index| task(index, IrWorkflowRunStatus::Succeeded))
.collect();
ledger.push(task(598, IrWorkflowRunStatus::BudgetExceeded));
ledger.push(task(599, IrWorkflowRunStatus::Failed));
let summary = workflow_receipt_summary(&record, &ledger, None);
assert!(
summary.starts_with(
"Workflow \"Survey\" finished with gaps: 598 of 601 agents finished, 2 failed, \
1 could not start (agent_mid: stopped at the step limit; 2 more)."
),
"{summary}"
);
}
#[tokio::test]
async fn detached_receipt_links_the_written_report_and_keeps_event_names() {
let tmp = tempfile::tempdir().expect("tempdir");
@@ -12844,6 +12942,53 @@ FINAL RECEIPT
assert_eq!(tool.manager.read().await.active_count(), 0);
}
#[tokio::test]
async fn explicit_cancel_receipt_links_the_report_it_wrote() {
let tmp = tempfile::tempdir().expect("tempdir");
let (mut tool, context, mut completion_rx) = native_lifecycle_tool(tmp.path());
let (event_tx, mut event_rx) = mpsc::channel(32);
tool.runtime.event_tx = Some(event_tx);
let started = tool
.execute(
json!({"action":"start", "script":"phase('spin'); while (true) {}"}),
&context,
)
.await
.expect("detached start");
let started: Value = serde_json::from_str(&started.content).expect("start json");
let run_id = started["run_id"].as_str().expect("run id").to_string();
tokio::time::timeout(std::time::Duration::from_secs(5), async {
loop {
if let Event::WorkflowUi { event, .. } =
event_rx.recv().await.expect("event channel")
&& event["type"] == "phase_started"
{
break;
}
}
})
.await
.expect("VM entered its phase");
tool.execute(json!({"action":"cancel", "run_id":run_id}), &context)
.await
.expect("cancel");
let completion =
tokio::time::timeout(std::time::Duration::from_secs(5), completion_rx.recv())
.await
.expect("cancel receipt arrives")
.expect("parent inbox open");
let receipt = terminal_workflow_receipt(&completion.payload);
assert_eq!(receipt["status"], "cancelled");
let report = format!(".codewhale/reports/{run_id}.md");
assert_eq!(receipt["report"], report.as_str());
assert!(
completion.payload.contains(&format!("\nReport: {report}")),
"{}",
completion.payload
);
assert!(!completion.payload.contains("No report file was written"));
}
#[tokio::test]
async fn native_cancel_closes_queued_admission_without_cancelling_parent() {
let tmp = tempfile::tempdir().expect("tempdir");
@@ -13034,6 +13179,14 @@ FINAL RECEIPT
.contains("workflow.max_children limit (1)"),
"{second}"
);
// Both attempts found the one slot free, so neither was announced as
// waiting for it.
assert!(
driver.state.runs.lock().expect("runs")[&driver.run_id]
.events
.iter()
.all(|event| event.event_type() != "task_queued")
);
assert_eq!(driver.child_counter.load(Ordering::SeqCst), 1);
assert!(driver.child_ids.lock().expect("children").is_empty());
for name in ["max_children", "max_concurrent", "max_depth"] {
+57 -14
View File
@@ -132,16 +132,7 @@ impl WorkflowTally {
kind,
..
} => {
let state = match status {
IrWorkflowRunStatus::Succeeded => RowState::Finished,
IrWorkflowRunStatus::Cancelled => RowState::Cancelled,
IrWorkflowRunStatus::Pending | IrWorkflowRunStatus::Running => {
RowState::Running
}
IrWorkflowRunStatus::Failed
| IrWorkflowRunStatus::BudgetExceeded
| IrWorkflowRunStatus::ReplayDiverged => RowState::Failed,
};
let state = row_state(*status);
set_row(&mut rows, task_id, state);
if matches!(state, RowState::Failed | RowState::Cancelled) {
tally.stopped.push(StoppedAgent {
@@ -201,6 +192,36 @@ impl WorkflowTally {
tally
}
/// Recount agents from the driver's own per-task ledger. A run keeps only
/// its newest events, so once older ones were trimmed the events alone
/// under-count; the ledger and the exact dispatch-failure total do not.
/// `stopped` stays the named sample the retained events carry.
pub(super) fn count_from_ledger(
&mut self,
statuses: impl IntoIterator<Item = IrWorkflowRunStatus>,
dispatch_failures: u64,
) {
let (mut running, mut finished, mut failed, mut cancelled) = (0, 0, 0, 0);
for status in statuses {
match row_state(status) {
RowState::Running => running += 1,
RowState::Finished => finished += 1,
RowState::Failed => failed += 1,
RowState::Cancelled => cancelled += 1,
}
}
if running + finished + failed + cancelled == 0 {
return;
}
self.running = running;
self.finished = finished;
self.failed = failed;
self.cancelled = cancelled;
self.not_started = self
.not_started
.max(usize::try_from(dispatch_failures).unwrap_or(usize::MAX));
}
/// Agents that started, plus tasks refused before they could.
pub(super) fn total(&self) -> usize {
self.running + self.finished + self.failed + self.cancelled + self.not_started
@@ -249,8 +270,14 @@ impl WorkflowTally {
}
})
.collect();
if self.stopped.len() > SHOWN {
details.push(format!("{} more", self.stopped.len() - SHOWN));
// The named list can be a sample of a trimmed run; the counts
// are the whole run, so "more" is measured against them.
let stopped_total = self
.stopped
.len()
.max(self.failed + self.cancelled + self.not_started);
if stopped_total > details.len() {
details.push(format!("{} more", stopped_total - details.len()));
}
sentence.push_str(&format!(" ({})", details.join("; ")));
}
@@ -258,14 +285,30 @@ impl WorkflowTally {
}
}
fn set_row(rows: &mut [(&str, RowState)], task_id: &str, state: RowState) -> bool {
fn row_state(status: IrWorkflowRunStatus) -> RowState {
match status {
IrWorkflowRunStatus::Succeeded => RowState::Finished,
IrWorkflowRunStatus::Cancelled => RowState::Cancelled,
IrWorkflowRunStatus::Pending | IrWorkflowRunStatus::Running => RowState::Running,
IrWorkflowRunStatus::Failed
| IrWorkflowRunStatus::BudgetExceeded
| IrWorkflowRunStatus::ReplayDiverged => RowState::Failed,
}
}
/// Set a row's state, adding the row when its `task_started` event was
/// trimmed from the retained tail. True when the row is new or changed.
fn set_row<'a>(rows: &mut Vec<(&'a str, RowState)>, task_id: &'a str, state: RowState) -> bool {
match rows.iter_mut().find(|(id, _)| *id == task_id) {
Some(row) => {
let changed = row.1 != state;
row.1 = state;
changed
}
None => false,
None => {
rows.push((task_id, state));
true
}
}
}