fix(renderer): preserve desktop result errors
This commit is contained in:
1 parent
7793f2da35
commit
bbf7ab91e2
3 files changed
+47
-4
No files matched your search
@@ -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)); }
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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."));
|
||||
|
||||
|
||||
Reference in new issue
Block a user