diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 18eb3ebe..c162e187 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -607,6 +607,9 @@ function isTrackedLocalChildProcessAdapter(adapterType: string) { return SESSIONED_LOCAL_ADAPTERS.has(adapterType); } +// A positive liveness check means some process currently owns the PID. +// On Linux, PIDs can be recycled, so this is a best-effort signal rather +// than proof that the original child is still alive. function isProcessAlive(pid: number | null | undefined) { if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return false; try { diff --git a/ui/src/lib/assignees.test.ts b/ui/src/lib/assignees.test.ts index 59822ef6..225f7133 100644 --- a/ui/src/lib/assignees.test.ts +++ b/ui/src/lib/assignees.test.ts @@ -74,4 +74,19 @@ describe("assignee selection helpers", () => { ), ).toBe("user:board-user"); }); + + it("skips the current agent when choosing a suggested commenter assignee", () => { + expect( + suggestedCommentAssigneeValue( + { assigneeUserId: "board-user" }, + [ + { authorUserId: "board-user" }, + { authorAgentId: "agent-self" }, + { authorAgentId: "agent-123" }, + ], + null, + "agent-self", + ), + ).toBe("agent:agent-123"); + }); }); diff --git a/ui/src/lib/assignees.ts b/ui/src/lib/assignees.ts index c4df80a3..0dc57e96 100644 --- a/ui/src/lib/assignees.ts +++ b/ui/src/lib/assignees.ts @@ -29,11 +29,14 @@ export function suggestedCommentAssigneeValue( issue: CommentAssigneeSuggestionInput, comments: CommentAssigneeSuggestionComment[] | null | undefined, currentUserId: string | null | undefined, + currentAgentId?: string | null | undefined, ): string { - if (comments && comments.length > 0 && currentUserId) { + if (comments && comments.length > 0 && (currentUserId || currentAgentId)) { for (let i = comments.length - 1; i >= 0; i--) { const comment = comments[i]; - if (comment.authorAgentId) return assigneeValueFromSelection({ assigneeAgentId: comment.authorAgentId }); + if (comment.authorAgentId && comment.authorAgentId !== currentAgentId) { + return assigneeValueFromSelection({ assigneeAgentId: comment.authorAgentId }); + } if (comment.authorUserId && comment.authorUserId !== currentUserId) { return assigneeValueFromSelection({ assigneeUserId: comment.authorUserId }); } diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index c75fdeab..d2a6ce20 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -497,6 +497,8 @@ export function Inbox() { }, }); + const [retryingRunIds, setRetryingRunIds] = useState>(new Set()); + const retryRunMutation = useMutation({ mutationFn: async (run: HeartbeatRun) => { const payload: Record = {}; @@ -517,11 +519,22 @@ export function Inbox() { } return { newRun: result, originalRun: run }; }, + onMutate: (run) => { + setRetryingRunIds((prev) => new Set(prev).add(run.id)); + }, onSuccess: ({ newRun, originalRun }) => { queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId) }); queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(originalRun.companyId, originalRun.agentId) }); navigate(`/agents/${originalRun.agentId}/runs/${newRun.id}`); }, + onSettled: (_data, _error, run) => { + if (!run) return; + setRetryingRunIds((prev) => { + const next = new Set(prev); + next.delete(run.id); + return next; + }); + }, }); const [fadingOutIssues, setFadingOutIssues] = useState>(new Set()); @@ -739,7 +752,7 @@ export function Inbox() { issueLinkState={issueLinkState} onDismiss={() => dismiss(`run:${item.run.id}`)} onRetry={() => retryRunMutation.mutate(item.run)} - isRetrying={retryRunMutation.isPending} + isRetrying={retryingRunIds.has(item.run.id)} /> ); }