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
@@ -1,4 +1,6 @@
use crate::ai::agent::{AIAgentActionResultType, AIAgentActionType};
use crate::ai::blocklist::orchestration_events::OrchestrationEventService;
use crate::ai::blocklist::BlocklistAIHistoryModel;
use crate::ai::blocklist::BlocklistAIPermissions;
use ai::agent::action_result::{AskUserQuestionAnswerItem, AskUserQuestionResult};
use futures::{future::BoxFuture, FutureExt};
@@ -51,6 +53,52 @@ impl AskUserQuestionExecutor {
}
};
// For child agent conversations, route the question to the parent
// for silent auto-answer instead of presenting UI to the user.
if let Some(parent_conversation_id) = self.parent_conversation_id(input.conversation_id, ctx)
{
let question_text = questions
.iter()
.map(|q| q.question.clone())
.collect::<Vec<_>>()
.join("\n");
let options: Vec<String> = questions
.iter()
.filter_map(|q| q.multiple_choice_options())
.flatten()
.map(|o| o.label.clone())
.collect();
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
service.route_subagent_question_to_parent(
input.conversation_id,
parent_conversation_id,
question_text,
options,
0,
ctx,
);
});
// Wait for the parent's answer to arrive via the same channel
let receiver = self.result_rx.1.clone();
return ActionExecution::new_async(
async move { receiver.recv().await },
|result, _ctx| match result {
Ok(AskUserQuestionDecision::Completed(answers)) => {
AIAgentActionResultType::AskUserQuestion(
AskUserQuestionResult::Success { answers },
)
}
Ok(AskUserQuestionDecision::Cancelled) | Err(_) => {
AIAgentActionResultType::AskUserQuestion(
AskUserQuestionResult::Cancelled,
)
}
},
);
}
if self.should_autoexecute(input, ctx) {
let question_ids = questions
.iter()
@@ -80,6 +128,16 @@ impl AskUserQuestionExecutor {
)
}
fn parent_conversation_id(
&self,
conversation_id: crate::ai::agent::conversation::AIConversationId,
ctx: &ModelContext<Self>,
) -> Option<crate::ai::agent::conversation::AIConversationId> {
let history_model = BlocklistAIHistoryModel::as_ref(ctx);
let conversation = history_model.conversation(&conversation_id)?;
conversation.parent_conversation_id()
}
pub(super) fn preprocess_action(
&mut self,
_action: PreprocessActionInput,