fix: skip 'Requiring Attention' bucket for auto-approved threads (still pin on ask_user) (#24)
Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
This commit is contained in:
@@ -41,6 +41,7 @@ import {
|
||||
exportThread,
|
||||
} from "@/app/hooks/useThreads";
|
||||
import { useMemoryActivity } from "@/app/hooks/useMemoryActivity";
|
||||
import { getThreadAutoApprove } from "@/lib/autoApprove";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -235,7 +236,20 @@ export function ThreadList({
|
||||
// Pinned threads live in the "Research" section only, not the time groups.
|
||||
if (thread.pinned) return;
|
||||
|
||||
if (thread.status === "interrupted") {
|
||||
// Bucket into "Requiring Attention" when the interrupt actually needs
|
||||
// the user. Two cases qualify:
|
||||
// - `needsUserInput` (an `ask_user` interrupt is active) - auto-approve
|
||||
// can NOT handle these, so the row must surface no matter what.
|
||||
// - Plain interrupt with auto-approve off - the user has to approve
|
||||
// the tool call themselves.
|
||||
// With auto-approve on AND no `ask_user`, the WebUI resumes the run on
|
||||
// its own; lifting the row out of its time group would make it jump
|
||||
// around in the sidebar for a behaviour the user has explicitly opted
|
||||
// out of seeing.
|
||||
if (
|
||||
thread.needsUserInput ||
|
||||
(thread.status === "interrupted" && !getThreadAutoApprove(thread.id))
|
||||
) {
|
||||
groups.interrupted.push(thread);
|
||||
return;
|
||||
}
|
||||
@@ -258,7 +272,14 @@ export function ThreadList({
|
||||
}, [filtered, now]);
|
||||
|
||||
const interruptedCount = useMemo(() => {
|
||||
return flattened.filter((t) => t.status === "interrupted").length;
|
||||
// Mirrors the `grouped` logic: count threads that actually need the user
|
||||
// either via an ask_user interrupt OR a plain interrupt with auto-approve
|
||||
// off. Auto-approve threads with tool-call interrupts skip the badge.
|
||||
return flattened.filter(
|
||||
(t) =>
|
||||
t.needsUserInput ||
|
||||
(t.status === "interrupted" && !getThreadAutoApprove(t.id))
|
||||
).length;
|
||||
}, [flattened]);
|
||||
|
||||
// Expose thread list revalidation to parent component
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface ThreadItem {
|
||||
description: string;
|
||||
assistantId?: string;
|
||||
pinned: boolean;
|
||||
/** True when any of the thread's pending interrupts is an `ask_user` —
|
||||
* i.e. the agent is asking the user a question that auto-approve can't
|
||||
* resolve. Used by the sidebar to keep these threads in "Requiring
|
||||
* Attention" even when auto-approve is on. */
|
||||
needsUserInput: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
@@ -163,6 +168,28 @@ export function useThreads(props: {
|
||||
(thread.metadata as Record<string, unknown> | undefined)?.pinned ===
|
||||
true;
|
||||
|
||||
// Walk `thread.interrupts` (Record<task_id, Interrupt[]>) and flag any
|
||||
// value with `type: "ask_user"`. The auto-approver can't resolve those,
|
||||
// so the sidebar should keep the row in "Requiring Attention" even when
|
||||
// auto-approve is on for the thread.
|
||||
let needsUserInput = false;
|
||||
const interrupts = thread.interrupts as
|
||||
| Record<string, Array<{ value?: unknown }>>
|
||||
| undefined;
|
||||
if (interrupts && typeof interrupts === "object") {
|
||||
for (const list of Object.values(interrupts)) {
|
||||
if (!Array.isArray(list)) continue;
|
||||
for (const ir of list) {
|
||||
const value = ir?.value as { type?: unknown } | undefined;
|
||||
if (value && value.type === "ask_user") {
|
||||
needsUserInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needsUserInput) break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: thread.thread_id,
|
||||
updatedAt: new Date(thread.updated_at),
|
||||
@@ -171,6 +198,7 @@ export function useThreads(props: {
|
||||
description,
|
||||
assistantId,
|
||||
pinned,
|
||||
needsUserInput,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user