Extend ephemeral active-session detection to in-review tasks

Mirror the in-progress fix for in-review: a reviewer/merger runs
ephemerally with no assignedAgentId/checkedOutBy, so an in-review task
in an active review/merge status (reviewing, merging, merging-fix,
fixing) now reads as a live session without an assignment. A null-status
in-review row is awaiting human review, not actively worked, so it stays
assignment-gated and idle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-20 19:57:14 -07:00
parent a2342cadae
commit b990872827
2 changed files with 46 additions and 11 deletions

View File

@@ -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 {

View File

@@ -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(
<TaskChatTab
task={makeTask({ column: "in-review", status, assignedAgentId: undefined, checkedOutBy: undefined })}
active
addToast={vi.fn()}
sessionLive={false}
/>,
);
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(
<TaskChatTab
task={makeTask({ column: "in-review", status: undefined, assignedAgentId: undefined, checkedOutBy: undefined })}
active
addToast={vi.fn()}
sessionLive={false}
/>,
);
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 })],