fix(renderer): preserve desktop result errors

This commit is contained in:
ZacharyZhang-NY committed 2026-04-22 19:00:53 -04:00
1 parent 7793f2da35
commit bbf7ab91e2
3 files changed
+47 -4

No files matched your search

+4 -3
View File
@@ -15,7 +15,7 @@ import { useCompanyController } from "./hooks/useCompanyController";
import { useCommandPaletteHotkeys } from "./hooks/useCommandPaletteHotkeys";
import { AppActionsContext, type AppActions } from "./hooks/useAppActions";
import { SectionContext } from "./hooks/useSectionContext";
import { createAppErrorNotice, describeDesktopError, formatDesktopResultError, unwrap } from "./lib/desktop";
import { createAppErrorNotice, describeDesktopError, formatDesktopResultNotice } from "./lib/desktop";
function Shell({
locale,
@@ -64,7 +64,7 @@ export function App() {
return;
}
const result = await window.agentCompany.startTaskRun({ companyId: company.currentCompany.id, taskId });
if (!result.ok) { setError(formatDesktopResultError(result.error)); return; }
if (!result.ok) { setError(formatDesktopResultNotice(result.error)); return; }
setSelectedRunId(result.data);
await company.refresh();
// eslint-disable-next-line react-hooks/exhaustive-deps -- Zustand setters and company.refresh are stable
@@ -74,7 +74,8 @@ export function App() {
try {
const run = company.runs.find((entry) => entry.id === runId);
if (!run) throw new Error("Selected run is no longer available.");
unwrap(await window.agentCompany.cancelRun({ companyId: run.companyId, runId }));
const result = await window.agentCompany.cancelRun({ companyId: run.companyId, runId });
if (!result.ok) { setError(formatDesktopResultNotice(result.error)); return; }
await company.refresh();
setError(null);
} catch (err) { setError(describeDesktopError(err)); }
+13 -1
View File
@@ -21,6 +21,7 @@ export type AppErrorInput =
const GENERIC_ERROR_CODE = "APP_ERROR";
const ACTION_REQUIRED_CODE = "ACTION_REQUIRED";
const DEFAULT_ACTION_HINT = "Use this code when reporting the issue.";
const SILENT_ERROR_CODES = new Set(["APP_SHUTTING_DOWN"]);
const ACTION_HINTS_BY_CODE: Record<string, string> = {
ACTION_REQUIRED: "Complete the required selection, then retry the action.",
@@ -29,7 +30,7 @@ const ACTION_HINTS_BY_CODE: Record<string, string> = {
AGENT_NOT_RUNNABLE: "Open Agents, activate the assigned agent, then retry the task.",
APP_SHUTTING_DOWN: "Restart AgentCompany after shutdown completes.",
BUDGET_POLICY_BLOCKED: "Open Budget, update the blocking policy, then retry the run.",
CHECKOUT_RACE_LOST: "Refresh tasks, review the current assignee, then retry the run.",
CHECKOUT_RACE_LOST: "Refresh tasks, review the current assignee, then try again.",
COMPANY_NOT_ACTIVE: "Select the active company, then retry the action.",
COMPANY_NOT_FOUND: "Refresh companies, select an existing company, then retry the action.",
CONNECTOR_MODEL_NOT_ALLOWED: "Open Connectors, enable an allowed model, then retry the run.",
@@ -76,6 +77,13 @@ export function formatDesktopResultError(error: DesktopErrorEnvelope): AppErrorN
return createAppErrorNotice(error.message, error.code);
}
export function formatDesktopResultNotice(error: DesktopErrorEnvelope): AppErrorNotice | null {
if (isSilentDesktopErrorCode(error.code)) {
return null;
}
return formatDesktopResultError(error);
}
export function describeDesktopError(error: unknown): AppErrorNotice {
if (isAppErrorNotice(error)) {
return error;
@@ -150,6 +158,10 @@ function actionHintForCode(code: string): string {
return DEFAULT_ACTION_HINT;
}
function isSilentDesktopErrorCode(code: string): boolean {
return SILENT_ERROR_CODES.has(normalizeCode(code).toUpperCase());
}
function isDesktopErrorEnvelope(error: unknown): error is DesktopErrorEnvelope {
return Boolean(
error &&
+30
View File
@@ -1,3 +1,5 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type { DesktopResult } from "@shared/contracts";
import { describe, expect, it } from "vitest";
import {
@@ -5,6 +7,7 @@ import {
describeDesktopError,
DesktopError,
formatDesktopResultError,
formatDesktopResultNotice,
normalizeAppErrorNotice,
unwrap,
} from "../../src/renderer/lib/desktop";
@@ -44,6 +47,33 @@ describe("renderer desktop error notices", () => {
});
});
it("keeps checkout races actionable for retry", () => {
const notice = formatDesktopResultNotice({
code: "CHECKOUT_RACE_LOST",
message: "Another run claimed this task first.",
});
expect(notice).toEqual({
code: "CHECKOUT_RACE_LOST",
message: "Another run claimed this task first.",
actionHint: "Refresh tasks, review the current assignee, then try again.",
});
});
it("suppresses shutdown noise from DesktopResult notices", () => {
expect(formatDesktopResultNotice({
code: "APP_SHUTTING_DOWN",
message: "The app is shutting down.",
})).toBeNull();
});
it("keeps App cancelRun on the DesktopResult envelope path", () => {
const source = readFileSync(join(process.cwd(), "src/renderer/App.tsx"), "utf8");
expect(source).not.toContain("unwrap(await window.agentCompany.cancelRun");
expect(source).toContain("formatDesktopResultNotice(result.error)");
});
it("describes thrown DesktopError instances with their original code", () => {
const notice = describeDesktopError(new DesktopError("TASK_UNASSIGNED", "Assign an agent before starting a run."));