use super::*;
pub(crate) fn finalize_output(
agent: &dyn Agent,
task_id: &TaskId,
store: &Arc<Store>,
output_path: Option<&str>,
log_path: &std::path::Path,
streaming: bool,
exit_status: &portable_pty::ExitStatus,
state: &mut MonitorState,
) -> Result<()> {
append_terminal_sentinel(task_id, log_path, exit_status, state);
if streaming {
return finalize_streaming(agent, task_id, store, exit_status, state);
}
finalize_buffered(agent, task_id, store, output_path, exit_status, state)
}
pub(super) fn finalize_streaming(
agent: &dyn Agent,
task_id: &TaskId,
store: &Arc<Store>,
exit_status: &portable_pty::ExitStatus,
state: &mut MonitorState,
) -> Result<()> {
state.full_output.push_str(&terminal_sentinel(task_id, exit_status, state));
persist_transcript(task_id, &state.full_output);
let mut status = if state.info.status == TaskStatus::Failed {
TaskStatus::Failed
} else if exit_status.success() {
TaskStatus::Done
} else {
TaskStatus::Failed
};
let delivery_outcome = state.delivery_evidence.validate();
let delivered = if agent.kind() == AgentKind::Codex {
matches!(&delivery_outcome, DeliveryOutcome::Delivered)
} else {
state.saw_completion_event
};
if agent.kind() == AgentKind::Codex {
status = watcher::apply_codex_delivery_guard(
store,
task_id,
status,
delivery_outcome,
i32::try_from(exit_status.exit_code()).ok(),
);
}
if status == TaskStatus::Done {
state.info.status = status;
let parsed = agent.parse_completion(&state.full_output);
crate::agent::stream_completion::merge_parsed_completion(&mut state.info, parsed);
status = state.info.status;
}
let dispatched_model = store
.get_task(task_id.as_str())
.ok()
.flatten()
.and_then(|task| task.requested_model);
if crate::agent::stream_completion::record_quota_exhaustion_with_delivery(
&state.full_output,
agent.kind(),
agent.rate_limit_name(),
state.info.model.as_deref().or(dispatched_model.as_deref()),
delivered,
)
.should_fail()
{
status = TaskStatus::Failed;
}
state.info.status = status;
state.info.exit_code = i32::try_from(exit_status.exit_code()).ok();
store.insert_agent_event(&TaskEvent {
task_id: task_id.clone(),
timestamp: Local::now(),
event_kind: if status == TaskStatus::Done {
EventKind::Completion
} else {
EventKind::Error
},
detail: format!(
"{} — {} events, exit code {}",
status.label(),
state.event_count,
exit_status.exit_code()
),
metadata: None,
})?;
Ok(())
}
fn finalize_buffered(
agent: &dyn Agent,
task_id: &TaskId,
store: &Arc<Store>,
output_path: Option<&str>,
exit_status: &portable_pty::ExitStatus,
state: &mut MonitorState,
) -> Result<()> {
state.full_output.push_str(&terminal_sentinel(task_id, exit_status, state));
persist_transcript(task_id, &state.full_output);
if let Some(path) = output_path {
write_output_file(agent.kind(), path, &state.full_output)?;
}
state.info = if state.info.status == TaskStatus::Failed {
CompletionInfo {
tokens: None,
status: TaskStatus::Failed,
model: None,
cost_usd: None,
exit_code: None,
}
} else if exit_status.success() {
agent.parse_completion(&state.full_output)
} else {
CompletionInfo {
tokens: None,
status: TaskStatus::Failed,
model: None,
cost_usd: None,
exit_code: None,
}
};
state.info.exit_code = i32::try_from(exit_status.exit_code()).ok();
let mut event = crate::agent::gemini::make_completion_event(task_id, &state.info);
if state.info.status == TaskStatus::Failed {
event.event_kind = EventKind::Error;
}
store.insert_agent_event(&event)?;
Ok(())
}
fn append_terminal_sentinel(
task_id: &TaskId,
log_path: &std::path::Path,
exit_status: &portable_pty::ExitStatus,
state: &MonitorState,
) {
let sentinel = terminal_sentinel(task_id, exit_status, state);
append_terminal_sentinel_line(log_path, &sentinel);
}
pub(crate) fn append_failed_terminal_sentinel(
task_id: &TaskId,
log_path: &std::path::Path,
reason: &str,
) {
let sentinel = format!("\n=== AID TASK {} FAILED ({}) ===\n", task_id, reason);
append_terminal_sentinel_line(log_path, &sentinel);
append_terminal_sentinel_line(&crate::paths::transcript_path(task_id.as_str()), &sentinel);
}
pub(crate) fn append_stopped_terminal_sentinel(
task_id: &TaskId,
log_path: &std::path::Path,
reason: &str,
) {
let sentinel = format!("\n=== AID TASK {} STOPPED ({}) ===\n", task_id, reason);
append_terminal_sentinel_line(log_path, &sentinel);
append_terminal_sentinel_line(&crate::paths::transcript_path(task_id.as_str()), &sentinel);
}
fn append_terminal_sentinel_line(log_path: &std::path::Path, sentinel: &str) {
let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(log_path) else {
return;
};
let _ = file.write_all(sentinel.as_bytes());
}
fn terminal_sentinel(
task_id: &TaskId,
exit_status: &portable_pty::ExitStatus,
state: &MonitorState,
) -> String {
let status = if state.info.status == TaskStatus::Failed || !exit_status.success() {
"FAILED"
} else {
"DONE"
};
format!(
"\n=== AID TASK {} {} (exit {}) ===\n",
task_id,
status,
exit_status.exit_code()
)
}