feat(TASK-049): classify idle-prompt ctrl-c + ctrl-d as a completed exit

Ctrl-C at the reedline prompt no longer latches the abort signal
unconditionally: latch_prompt_interrupt only marks an interruption when
the supervisor had live agents/jobs to cancel (new
Supervisor::has_active_tasks), so the advertised exit path (ctrl-c at an
idle prompt, then ctrl-d) fires agent.completed at teardown instead of
agent.interrupted. Mid-turn ctrl-c still latches directly via
wait_user_interrupt/poll_abort_signal, preserving the
interrupted-then-exit classification.

Also: probe-lane tests for escalation.answered single-fire,
escalation question env truncation, agent.failed inverse gate, and
hooks probe Scenario I (session.started vs session.resumed); comment
notes on escalation.answered semantics and the agent__cancel blind
spot.
This commit is contained in:
Dark-Alex-17 committed 2026-09-16 19:55:52 -06:00
1 parent 739c1be8bb
commit a3b71a6889
7 files changed
+369 -4

No files matched your search

+44
View File
@@ -8619,6 +8619,50 @@ mod tests {
}));
}
/// The REPL's advertised exit path -- ctrl-c at an idle prompt (which
/// prints "To exit, press Ctrl+D"), then ctrl-d -- is a deliberate exit:
/// with no active tasks to cancel the prompt latch stays clear, so
/// teardown fires agent.completed, never agent.interrupted.
#[test]
#[serial]
fn idle_prompt_ctrlc_then_ctrld_exit_fires_agent_completed() {
let _guard = TestConfigDirGuard::new();
let _sink = test_sink::install();
let marker = "agent-top-idle-cc-w2b";
let agent_name = unique_name("hook_agent");
seed_agent_with_hooks(&agent_name, marker);
let mut ctx = create_test_ctx();
let app = ctx.app.config.clone();
let abort = utils::create_abort_signal();
run_async(ctx.use_agent(&app, &agent_name, None, abort.clone())).unwrap();
ctx.top_level_agent_started();
// Ctrl-c at the idle prompt: nothing to cancel, so no latch.
let supervisor = Arc::new(RwLock::new(Supervisor::new(4, 3)));
crate::repl::latch_prompt_interrupt(&abort, &supervisor);
assert!(!abort.aborted_ctrlc());
// Ctrl-d exits; teardown sees only the ctrl-d flag.
abort.set_ctrld();
ctx.top_level_agent_finished(None, Some(&abort));
let captures = marker_captures(marker);
assert_eq!(
captures
.iter()
.filter(|capture| capture.hook_name == format!("{marker}-agent.completed"))
.count(),
1
);
assert!(
captures
.iter()
.all(|capture| capture.hook_name != format!("{marker}-agent.interrupted")),
"a deliberate exit is never an interruption"
);
}
#[test]
#[serial]
fn top_level_agent_helpers_noop_without_agent() {