diff --git a/packages/dashboard/app/components/TaskChatTab.tsx b/packages/dashboard/app/components/TaskChatTab.tsx
index 3d8421e85f..cdd039702a 100644
--- a/packages/dashboard/app/components/TaskChatTab.tsx
+++ b/packages/dashboard/app/components/TaskChatTab.tsx
@@ -175,17 +175,20 @@ function isActiveAgentSession(task: Task | TaskDetail, opts: { sessionLive?: boo
// In the default ephemeral-agents mode the scheduler never writes
// `assignedAgentId`/`checkedOutBy` — those are only set when
// `ephemeralAgentsEnabled === false` (scheduler.ts). An actively-executing
- // in-progress task therefore has no assignment field yet IS being worked, so
- // requiring `hasAssignedAgent` made the chat always show "no agent is working"
- // for default-mode tasks. Treat assignment as sufficient-but-not-necessary:
- // a non-blocked, non-`queued` in-progress task is a live session on its own.
- // `queued` is the documented waiting/blocked marker (self-healing.ts), so it
- // stays assignment-gated; in-review keeps requiring an assignment.
- const inProgressExecuting = task.column === "in-progress"
- && statusAllowsProgressSteering
- && task.status !== "queued";
+ // task therefore has no assignment field yet IS being worked, so requiring
+ // `hasAssignedAgent` made the chat always show "no agent is working" for
+ // default-mode tasks. Treat assignment as sufficient-but-not-necessary:
+ // - in-progress with a non-blocked, non-`queued` status is an executing run
+ // (`queued` is the documented waiting marker, self-healing.ts — it stays
+ // assignment-gated);
+ // - in-review with an active review/merge status (REVIEW_STEERABLE_STATUSES)
+ // has a reviewer/merger running. A null-status in-review row is awaiting
+ // human review, not actively worked, so it stays assignment-gated and idle.
+ const executionImpliesActiveAgent =
+ (task.column === "in-progress" && statusAllowsProgressSteering && task.status !== "queued")
+ || (task.column === "in-review" && task.status != null && REVIEW_STEERABLE_STATUSES.has(task.status));
return columnAllowsSteering
- && (hasAssignedAgent || inProgressExecuting);
+ && (hasAssignedAgent || executionImpliesActiveAgent);
}
function isToolLikeEntry(entry: AgentLogEntry): boolean {
diff --git a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx
index ea046c0cba..92e246addd 100644
--- a/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskChatTab.test.tsx
@@ -1976,7 +1976,6 @@ describe("TaskChatTab", () => {
["in-progress task without an assigned or checked-out agent", makeTask({ column: "in-progress", status: "queued", assignedAgentId: undefined, checkedOutBy: undefined })],
["paused in-progress task", makeTask({ column: "in-progress", status: "queued", paused: true })],
["user-paused in-progress task", makeTask({ column: "in-progress", status: "queued", userPaused: true })],
- ["in-review task without an assigned or checked-out agent", makeTask({ column: "in-review", status: "reviewing", assignedAgentId: undefined, checkedOutBy: undefined })],
["paused in-review task", makeTask({ column: "in-review", status: "reviewing", paused: true })],
["user-paused in-review task", makeTask({ column: "in-review", status: "reviewing", userPaused: true })],
])("keeps the composer sendable with idle guidance for %s", (_label, task) => {
@@ -1986,6 +1985,39 @@ describe("TaskChatTab", () => {
expectComposerSendableAfterDraft();
});
+ it.each(["reviewing", "merging", "merging-fix", "fixing"])(
+ "treats an actively-reviewing in-review task as an active session even without an assignment (ephemeral mode): %s status",
+ (status) => {
+ // A reviewer/merger runs ephemerally with no assignedAgentId/checkedOutBy,
+ // so an in-review task in an active review/merge status must NOT show the
+ // idle "no agent is working" hint.
+ render(
+ ,
+ );
+
+ expectActiveSessionCopy();
+ expect(screen.getByLabelText("Message active agent session")).not.toBeDisabled();
+ },
+ );
+
+ it("keeps a null-status in-review task (awaiting human review) idle without an assignment", () => {
+ render(
+ ,
+ );
+
+ expectIdleSessionHint();
+ });
+
it.each([
["paused in-progress task with a live session", makeTask({ column: "in-progress", status: "queued", paused: true })],
["user-paused in-progress task with a live session", makeTask({ column: "in-progress", status: "queued", userPaused: true })],