use bamboo_agent_core::tools::{
handle_tool_result_with_agentic_support_and_persistence, ToolHandlingOutcome,
};
use bamboo_agent_core::AgentEvent;
use super::super::{clarification, events, task, tool_error_collector};
use super::{goal, workspace, SuccessPathContext};
async fn refresh_workflow_tool_side_effects(ctx: &mut SuccessPathContext<'_>) {
if !ctx.result.success {
return;
}
if ctx.tool_call.function.name == "load_skill" {
crate::runtime::runner::session_setup::legacy_instruction::refresh_load_side_effects(
ctx.session,
ctx.config,
ctx.session_id,
&ctx.tool_call.function.name,
ctx.result.success,
)
.await;
return;
}
let keys: &[&str] = match ctx.tool_call.function.name.as_str() {
"workflow_run" => &[bamboo_skills::WORKFLOW_RUN_IDS_METADATA_KEY],
_ => return,
};
let Some(persistence) = ctx.config.persistence.as_ref() else {
return;
};
match persistence.load_runtime_session(ctx.session_id).await {
Ok(Some(latest)) => {
for key in keys {
if let Some(value) = latest.metadata.get(*key) {
ctx.session
.metadata
.insert((*key).to_string(), value.clone());
} else {
ctx.session.metadata.remove(*key);
}
}
}
Ok(None) => tracing::warn!(
"[{}] load_skill completed but no repository session was available for activation refresh",
ctx.session_id
),
Err(error) => tracing::warn!(
"[{}] load_skill activation refresh failed: {}",
ctx.session_id,
error
),
}
}
pub(super) async fn handle_successful_tool_result(mut ctx: SuccessPathContext<'_>) -> bool {
let ticket_result =
task::maybe_apply_ticket_task(ctx.tool_call, ctx.result, ctx.session, ctx.config).await;
let result = ticket_result.as_ref().unwrap_or(ctx.result);
refresh_workflow_tool_side_effects(&mut ctx).await;
if let Some(progress_result) = ctx.task_progress_result.result(result) {
task::track_task_progress(
ctx.task_context,
ctx.event_tx,
ctx.session_id,
ctx.tool_call,
progress_result,
ctx.round,
)
.await;
}
task::maybe_handle_taskwrite(
ctx.tool_call,
result,
ctx.session,
ctx.session_id,
ctx.event_tx,
ctx.config,
ctx.task_context,
)
.await;
workspace::maybe_apply_workspace_update(
ctx.session,
ctx.tool_call,
result,
ctx.session_id,
ctx.config.project_context_resolver.as_deref(),
ctx.event_tx,
)
.await;
goal::maybe_apply_goal_update(ctx.session, ctx.tool_call, result, ctx.config, ctx.round);
if clarification::maybe_handle_user_question_tool(clarification::UserQuestionToolContext {
tool_call: ctx.tool_call,
permission_replay_origin: ctx.permission_replay_origin,
result,
session: ctx.session,
event_tx: ctx.event_tx,
metrics_collector: ctx.metrics_collector,
session_id: ctx.session_id,
round_id: ctx.round_id,
config: ctx.config,
})
.await
{
ctx.state.mark_awaiting_clarification();
return true;
}
events::send_event_with_metrics(
ctx.event_tx,
ctx.metrics_collector,
ctx.session_id,
ctx.round_id,
AgentEvent::ToolComplete {
tool_call_id: ctx.tool_call.id.clone(),
result: result.clone(),
},
)
.await;
if !result.success {
ctx.state
.mark_unsuccessful_tool(&ctx.tool_call.function.name);
tool_error_collector::append_tool_error(tool_error_collector::soft_failure_record(
ctx.session_id,
ctx.round,
&ctx.tool_call.function.name,
&ctx.tool_call.id,
&ctx.tool_call.function.arguments,
&result.result,
))
.await;
}
tracing::debug!(
"[{}] tool_complete: {}",
ctx.session_id,
serde_json::json!({
"tool_name": ctx.tool_call.function.name,
"tool_call_id": ctx.tool_call.id,
"duration_ms": ctx.tool_duration.as_millis(),
"success": result.success,
})
);
if ctx.loop_result_checkpointed {
return false;
}
let outcome = handle_tool_result_with_agentic_support_and_persistence(
result,
ctx.tool_call,
ctx.event_tx,
ctx.session,
ctx.tools.as_ref(),
ctx.session_flags,
None,
ctx.config.persistence.as_ref(),
)
.await;
match outcome {
ToolHandlingOutcome::AwaitingClarification => {
ctx.state.mark_awaiting_clarification();
true
}
ToolHandlingOutcome::WaitingForChildren => {
ctx.state.mark_waiting_for_children();
false
}
ToolHandlingOutcome::Continue => {
ctx.config.ticket_worker_plan.is_some()
&& ctx
.session
.metadata
.contains_key(crate::ticket_worker_plan::TICKET_QUESTION_YIELD_KEY)
}
}
}