diff --git a/ui/src/components/ActivityRow.tsx b/ui/src/components/ActivityRow.tsx
index 8516ffc7..bf8258e6 100644
--- a/ui/src/components/ActivityRow.tsx
+++ b/ui/src/components/ActivityRow.tsx
@@ -108,7 +108,7 @@ export function ActivityRow({ event, agentMap, entityNameMap, entityTitleMap, cl
const inner = (
-
+
(null);
+ const shouldPreventCloseAutoFocusRef = useRef(false);
const allOptions = useMemo(
() => [{ id: "", label: noneLabel, searchText: noneLabel }, ...options],
@@ -70,6 +71,7 @@ export const InlineEntitySelector = forwardRef {
const option = filteredOptions[index] ?? filteredOptions[0];
if (option) onChange(option.id);
+ shouldPreventCloseAutoFocusRef.current = moveNext;
setOpen(false);
setQuery("");
if (moveNext && onConfirm) {
@@ -109,6 +111,11 @@ export const InlineEntitySelector = forwardRef {
+ if (!shouldPreventCloseAutoFocusRef.current) return;
+ event.preventDefault();
+ shouldPreventCloseAutoFocusRef.current = false;
+ }}
>
| null {
return value as Record;
}
+function asNonEmptyString(value: unknown): string | null {
+ if (typeof value !== "string") return null;
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : null;
+}
+
export function AgentDetail() {
const { agentId, tab: urlTab, runId: urlRunId } = useParams<{ agentId: string; tab?: string; runId?: string }>();
const { selectedCompanyId } = useCompany();
@@ -1509,6 +1515,7 @@ function RunsTab({ runs, companyId, agentId, selectedRunId, adapterType }: { run
function RunDetail({ run, adapterType }: { run: HeartbeatRun; adapterType: string }) {
const queryClient = useQueryClient();
+ const navigate = useNavigate();
const metrics = runMetrics(run);
const [sessionOpen, setSessionOpen] = useState(false);
const [claudeLoginResult, setClaudeLoginResult] = useState(null);
@@ -1523,6 +1530,41 @@ function RunDetail({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(run.companyId, run.agentId) });
},
});
+ const canResumeLostRun = run.errorCode === "process_lost" && run.status === "failed";
+ const resumePayload = useMemo(() => {
+ const payload: Record = {
+ resumeFromRunId: run.id,
+ };
+ const context = asRecord(run.contextSnapshot);
+ if (!context) return payload;
+ const issueId = asNonEmptyString(context.issueId);
+ const taskId = asNonEmptyString(context.taskId);
+ const taskKey = asNonEmptyString(context.taskKey);
+ const commentId = asNonEmptyString(context.wakeCommentId) ?? asNonEmptyString(context.commentId);
+ if (issueId) payload.issueId = issueId;
+ if (taskId) payload.taskId = taskId;
+ if (taskKey) payload.taskKey = taskKey;
+ if (commentId) payload.commentId = commentId;
+ return payload;
+ }, [run.contextSnapshot, run.id]);
+ const resumeRun = useMutation({
+ mutationFn: async () => {
+ const result = await agentsApi.wakeup(run.agentId, {
+ source: "on_demand",
+ triggerDetail: "manual",
+ reason: "resume_process_lost_run",
+ payload: resumePayload,
+ });
+ if (!("id" in result)) {
+ throw new Error("Resume request was skipped because the agent is not currently invokable.");
+ }
+ return result;
+ },
+ onSuccess: (resumedRun) => {
+ queryClient.invalidateQueries({ queryKey: queryKeys.heartbeats(run.companyId, run.agentId) });
+ navigate(`/agents/${run.agentId}/runs/${resumedRun.id}`);
+ },
+ });
const { data: touchedIssues } = useQuery({
queryKey: queryKeys.runIssues(run.id),
@@ -1602,7 +1644,24 @@ function RunDetail({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
{cancelRun.isPending ? "Cancelling..." : "Cancel"}
)}
+ {canResumeLostRun && (
+
+ )}
+ {resumeRun.isError && (
+
+ {resumeRun.error instanceof Error ? resumeRun.error.message : "Failed to resume run"}
+
+ )}
{startTime && (
diff --git a/ui/src/pages/Dashboard.tsx b/ui/src/pages/Dashboard.tsx
index e89185c7..5d15de87 100644
--- a/ui/src/pages/Dashboard.tsx
+++ b/ui/src/pages/Dashboard.tsx
@@ -142,6 +142,12 @@ export function Dashboard() {
return map;
}, [issues, agents, projects]);
+ const entityTitleMap = useMemo(() => {
+ const map = new Map();
+ for (const i of issues ?? []) map.set(`issue:${i.id}`, i.title);
+ return map;
+ }, [issues]);
+
const agentName = (id: string | null) => {
if (!id || !agents) return null;
return agents.find((a) => a.id === id)?.name ?? null;
@@ -240,6 +246,7 @@ export function Dashboard() {
event={event}
agentMap={agentMap}
entityNameMap={entityNameMap}
+ entityTitleMap={entityTitleMap}
className={animatedActivityIds.has(event.id) ? "activity-row-enter" : undefined}
/>
))}