v1.5.0: Inline subagent panels, Bedrock compaction fixes, context window debug view

New features:
- Inline subagent panels with expand/collapse and click-to-toggle
- /context slash command to inspect bedrock_message_history
- Child-to-parent question routing with auto-answer for subagents
- Subagent token usage and cost merging into parent conversation
- Randomized session-colored user avatar silhouettes

Bug fixes:
- Bedrock: remove orphaned tool_results after compaction
- Bedrock: self-healing exchange lookup for out-of-order streaming
- Bedrock: append continuation prompt when conversation ends with assistant
- Duration sanity check rejects epoch-time artifacts from session restore
- Cache hit rate calculation uses actual total_input_tokens
- Hide "Time to first token" when value is zero

Improvements:
- Demote verbose bedrock-debug logs to debug/trace levels
- Bedrock tool usage counting falls back to action counting
- Remove logout menu item from workspace menu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-05-21 14:30:04 -05:00
co-authored by Claude Opus 4.6
parent 6f54e2cb30
commit f278e53b7e
22 changed files with 914 additions and 113 deletions
@@ -552,6 +552,57 @@ impl OrchestrationEventService {
LifecycleEventType::Idle,
result,
);
// Emit completion summary to parent and merge costs
let (parent_id, summary) = {
let history_model = BlocklistAIHistoryModel::as_ref(ctx);
let parent_id = history_model
.conversation(&conversation_id)
.and_then(|c| c.parent_conversation_id());
let summary = parent_id.and_then(|_| {
let conv = history_model.conversation(&conversation_id)?;
let messages = conv.all_linearized_messages();
messages.iter().rev().find_map(|msg| {
let message_content = msg.message.as_ref()?;
match message_content {
warp_multi_agent_api::message::Message::AgentOutput(output)
if !output.text.is_empty() =>
{
let text = if output.text.len() > 500 {
format!("{}...", &output.text[..497])
} else {
output.text.clone()
};
Some(text)
}
_ => None,
}
})
});
(parent_id, summary)
};
if let Some(parent_id) = parent_id {
// Merge child's token usage and costs into parent
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, _ctx| {
let child_usage: Option<(HashMap<String, api::response_event::stream_finished::TokenUsage>, crate::ai::agent::RequestCost)> = history_model
.conversation(&conversation_id)
.map(|c| (c.total_token_usage_by_model().clone(), c.total_request_cost()));
if let Some((token_usage, request_cost)) = child_usage {
if let Some(parent) = history_model.conversation_mut(&parent_id) {
parent.merge_child_usage_raw(&token_usage, request_cost);
}
}
});
if let Some(summary_text) = summary {
self.route_subagent_completion_summary(
conversation_id,
parent_id,
summary_text,
ctx,
);
}
}
}
(Some(ConversationStatus::InProgress), ConversationStatus::Error) => {
let result = self.dispatch_lifecycle_event(