Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- An agent whose turn stopped mid-command now learns the command is still running on its next call to any tool, not only on its next shell call.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"slice_class": "non_ranking",
"targeted_mechanism": "none",
"kind": "harness",
"fixtures": [
"harness-goldens"
],
"slices": [
"cancelled-call-notice-any-tool"
],
"note": "Cancelled-call notices ride the next agent-visible reply of any tool. No changed path matches RANKING_FENCE_PREFIXES."
}
14 changes: 2 additions & 12 deletions crates/aft/src/commands/bash_kill_scope_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ fn bash_kill_scope_completion_is_an_expected_owner_stop() {
}

#[test]
fn bash_kill_scope_next_bash_family_reply_names_cancellation_once() {
fn bash_kill_scope_next_agent_reply_names_cancellation_once() {
let dir = tempfile::tempdir().unwrap();
let mut config = crate::config::Config::default();
config.project_root = Some(dir.path().into());
Expand All @@ -301,23 +301,13 @@ fn bash_kill_scope_next_bash_family_reply_names_cancellation_once() {
let task = tasks.start(&stamp, "session", "sleep 30", true);
assert!(tasks.cancel(&stamp).success);
let mut response = Response::success("next", json!({}));
let mut unrelated = String::new();
crate::response_finalize::finalize_tool_response(
&mut response,
&mut unrelated,
&ctx,
"session",
"read",
false,
);
assert!(!unrelated.contains("cancelled by owner"));
let mut next = String::new();
crate::response_finalize::finalize_tool_response(
&mut response,
&mut next,
&ctx,
"session",
"bash_status",
"read",
false,
);
assert!(
Expand Down
14 changes: 13 additions & 1 deletion crates/aft/src/commands/tool_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,23 @@ fn handle_on_route(req: &RawRequest, ctx: &AppContext, dispatch: &DispatchFn<'_>
};

let sanitized_arguments = strip_agent_preview_arg_owned(arguments);
let agent_visible =
!preview && !crate::subc::is_native_plumbing_call(name, &sanitized_arguments);
let format_context = crate::subc_format::FormatContext::from_tool_call(
name,
&sanitized_arguments,
tool_ctx.project_root.as_path(),
);
let finalizer = |response: &mut Response, text: &mut String| {
if agent_visible {
crate::response_finalize::append_cancelled_call_notices(
response,
text,
ctx,
req.session(),
);
}
};

match run_tool_call(
name,
Expand All @@ -132,7 +144,7 @@ fn handle_on_route(req: &RawRequest, ctx: &AppContext, dispatch: &DispatchFn<'_>
&tool_ctx,
ctx,
dispatch,
None,
Some(&finalizer),
None,
) {
ToolCallOutcome::Unary(result) => response_with_text(result.response, result.text),
Expand Down
41 changes: 33 additions & 8 deletions crates/aft/src/response_finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,29 @@ pub fn finalize_tool_response(
session_id: &str,
attach_command: &str,
allow_bg_completions: bool,
) {
finalize_tool_response_with_visibility(
response,
text,
ctx,
session_id,
attach_command,
allow_bg_completions,
true,
);
}

/// Native plugin calls share tool dispatch with agent calls, but their replies are not
/// shown to the agent. They must leave cancelled-call notices pending for a visible reply.
#[allow(clippy::too_many_arguments)]
pub(crate) fn finalize_tool_response_with_visibility(
response: &mut Response,
text: &mut String,
ctx: &AppContext,
session_id: &str,
attach_command: &str,
allow_bg_completions: bool,
agent_visible: bool,
) {
let (publish, counts) = finalize_prefix(
response,
Expand All @@ -158,7 +181,9 @@ pub fn finalize_tool_response(
attach_command,
allow_bg_completions,
);
append_cancelled_call_notices(text, ctx, session_id, attach_command);
if agent_visible {
append_cancelled_call_notices(response, text, ctx, session_id);
}
if let Some(line) = status_bar_line(ctx, publish, attach_command, session_id, counts) {
append_trailing_line(text, &line);
}
Expand Down Expand Up @@ -209,19 +234,19 @@ fn acknowledge_terminal_bash_reply(
}
}

/// A bash-family reply names tasks the session has not heard about: commands
/// An agent-visible reply names tasks the session has not heard about: commands
/// still running after their waiting call was cancelled, and commands stopped
/// by an owner cancellation. The registry supplies the appropriate status for each.
fn append_cancelled_call_notices(
pub(crate) fn append_cancelled_call_notices(
response: &Response,
text: &mut String,
ctx: &AppContext,
session_id: &str,
command: &str,
) {
if !matches!(
command,
"bash" | "powershell" | "bash_status" | "bash_watch" | "bash_kill" | "bash_write"
) {
// An internal access ask is held for consent and replaced by a later tool reply.
if response.data.get("code").and_then(Value::as_str)
== Some(crate::access_policy::ACCESS_ASK_REQUIRED)
{
return;
}
for line in ctx
Expand Down
Loading
Loading