Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
efdecf9
🤖 fix: detect pending ask_user_question within latest turn
jaaydenh Mar 19, 2026
5696c31
🤖 fix: suppress interruption UI for pending question turns
jaaydenh Mar 19, 2026
cff3aa7
🤖 fix: preserve error retry states for failed question turns
jaaydenh Mar 19, 2026
2d0fca8
🤖 fix: use authoritative awaiting-question signal in retry gating
jaaydenh Mar 19, 2026
f28e8a4
🤖 fix: suppress interrupted rows with authoritative awaiting state
jaaydenh Mar 19, 2026
a66fd6c
🤖 fix: ignore ephemeral plan-display rows for awaiting detection
jaaydenh Mar 19, 2026
e2703d5
🤖 fix: treat plan-display rows as decorative in interruption checks
jaaydenh Mar 19, 2026
ed9943f
🤖 fix: scope plan-display skipping to interrupted-row turn resolution
jaaydenh Mar 19, 2026
6105ed2
🤖 fix: ignore plan-display previews in ask-user waiting fallback
jaaydenh Mar 19, 2026
aaa6e52
🤖 fix: require latest unfinished part to be ask_user_question for awa…
jaaydenh Mar 19, 2026
bd89e87
🤖 fix: keep interruption visible when output continues after question
jaaydenh Mar 19, 2026
34d8778
🤖 fix: prefer later tool failures over ask-user awaiting state
jaaydenh Mar 19, 2026
9969383
🤖 fix: align retry suppression with ask-user question tail state
jaaydenh Mar 19, 2026
b929481
🤖 fix: keep pending ask-user turns out of startup auto-retry
jaaydenh Mar 19, 2026
74cb31e
🤖 fix: treat failed redacted tools as interruption tails
jaaydenh Mar 19, 2026
8e00324
🤖 fix: keep stale ask-user tool rows from showing as executing
jaaydenh Mar 19, 2026
38c4428
🤖 fix: keep failed-tail ask-user prompts answerable
jaaydenh Mar 19, 2026
9b3e527
🤖 fix: preserve pending ask-user recovery when streams error
jaaydenh Mar 19, 2026
1a4dbb8
🤖 fix: only suppress retry UI when awaiting question is visible
jaaydenh Mar 19, 2026
f039f0b
Use authoritative awaiting flag for interruption barriers
jaaydenh Mar 19, 2026
bc8bc13
Clear awaiting state when question row is truncated
jaaydenh Mar 19, 2026
8ea39e8
Treat post-question tool output as interrupted tail state
jaaydenh Mar 19, 2026
f6fcd1e
Suppress interrupted marker for answerable question rows
jaaydenh Mar 19, 2026
0de1cde
Preserve persisted ask_user_question recovery semantics
jaaydenh Mar 19, 2026
e5dec82
Handle redacted and completed tool tails in retry recovery
jaaydenh Mar 19, 2026
2a9d63a
Keep sibling pending-question recovery paths intact
jaaydenh Mar 19, 2026
a739e6d
Keep pending question state across truncation and restart
jaaydenh Mar 19, 2026
0b562f3
Clear awaiting flag for interrupted ask_user_question tails
jaaydenh Mar 19, 2026
42b18f6
Avoid awaiting fallback on errored truncated turns
jaaydenh Mar 19, 2026
989783c
Keep all pending ask_user_question tool calls answerable
jaaydenh Mar 19, 2026
43b3950
Keep awaiting question rows visible and sibling tools pending
jaaydenh Mar 19, 2026
87ac791
Pin only latest answerable ask_user_question rows
jaaydenh Mar 19, 2026
36840c1
Align startup ask_user_question pending detection with interrupted tails
jaaydenh Mar 19, 2026
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
Expand Up @@ -93,6 +93,102 @@ describe("ask_user_question waiting state", () => {

expect(aggregator.hasAwaitingUserQuestion()).toBe(true);
});

it("keeps awaiting state when ask_user_question is followed by other parts in the same turn", () => {
const aggregator = new StreamingMessageAggregator("2024-01-01T00:00:00.000Z");

aggregator.loadHistoricalMessages([
{
id: "assistant-1",
role: "assistant" as const,
parts: [
{
type: "dynamic-tool" as const,
toolCallId: "call-ask-1",
toolName: "ask_user_question",
state: "input-available" as const,
input: {
questions: [
{
header: "Approach",
question: "Which approach should we take?",
options: [
{ label: "A", description: "Approach A" },
{ label: "B", description: "Approach B" },
],
multiSelect: false,
},
],
},
},
{
type: "dynamic-tool" as const,
toolCallId: "call-todo-1",
toolName: "todo_write",
state: "output-available" as const,
input: { todos: [{ content: "Waiting for answers", status: "in_progress" }] },
output: { success: true },
},
{ type: "text" as const, text: "Please answer the question above." },
],
metadata: {
timestamp: 1000,
historySequence: 1,
partial: true,
},
},
]);

expect(aggregator.hasAwaitingUserQuestion()).toBe(true);
});

it("does not treat older question turns as awaiting after chat moves on", () => {
const aggregator = new StreamingMessageAggregator("2024-01-01T00:00:00.000Z");

aggregator.loadHistoricalMessages([
{
id: "assistant-1",
role: "assistant" as const,
parts: [
{
type: "dynamic-tool" as const,
toolCallId: "call-ask-1",
toolName: "ask_user_question",
state: "input-available" as const,
input: {
questions: [
{
header: "Approach",
question: "Which approach should we take?",
options: [
{ label: "A", description: "Approach A" },
{ label: "B", description: "Approach B" },
],
multiSelect: false,
},
],
},
},
],
metadata: {
timestamp: 1000,
historySequence: 1,
partial: true,
},
},
{
id: "user-2",
role: "user" as const,
parts: [{ type: "text" as const, text: "Skipping this and moving on" }],
metadata: {
timestamp: 2000,
historySequence: 2,
},
},
]);

expect(aggregator.hasAwaitingUserQuestion()).toBe(false);
});
});

describe("StreamingMessageAggregator - Agent Status", () => {
Expand Down
32 changes: 25 additions & 7 deletions src/browser/utils/messages/StreamingMessageAggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,19 +716,37 @@ export class StreamingMessageAggregator {
* Used to show "Awaiting your input" instead of "streaming..." in the UI.
*/
hasAwaitingUserQuestion(): boolean {
// Only treat the workspace as "awaiting input" when the *latest* displayed
// message is an executing ask_user_question tool.
//
// This avoids false positives from stale historical partials if the user
// continued the chat after skipping/canceling the questions.
const displayed = this.getDisplayedMessages();
Comment thread
jaaydenh marked this conversation as resolved.
Outdated
const last = displayed[displayed.length - 1];

if (last?.type !== "tool") {
if (!last || !("historyId" in last)) {
return false;
}

return last.toolName === "ask_user_question" && last.status === "executing";
// Treat the latest assistant turn as "awaiting input" when it contains an
// executing ask_user_question tool, even if later parts in the same turn
// (text or other tool calls) were emitted after the question.
//
// We intentionally scope to the latest historyId only so stale historical
// questions do not keep the workspace in an awaiting state once the chat has
// moved on to a newer message.
const latestHistoryId = last.historyId;
for (let i = displayed.length - 1; i >= 0; i--) {
const message = displayed[i];
if (!("historyId" in message) || message.historyId !== latestHistoryId) {
break;
Comment thread
jaaydenh marked this conversation as resolved.
Outdated
}

if (
message.type === "tool" &&
message.toolName === "ask_user_question" &&
message.status === "executing"
) {
return true;
}
}

return false;
}

/**
Expand Down
Loading