Fix tools that could not run in Automate

This commit is contained in:
Anthony Stirling
2026-08-06 12:13:44 +01:00
parent 789be2d351
commit e7b48dbea3
3 changed files with 49 additions and 3 deletions
@@ -34,13 +34,17 @@ export default function ToolSelector({
const [shouldAutoFocus, setShouldAutoFocus] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
// Filter out excluded tools (like 'automate' itself) and tools that don't support automation
// Filter out excluded tools (like 'automate' itself), tools that don't support
// automation, and tools with no operationConfig - the executor resolves a step
// through operationConfig, so offering one without it fails only at run time.
const baseFilteredTools = useMemo(() => {
return (
Object.entries(toolRegistry) as [ToolId, ToolRegistryEntry][]
).filter(
([key, tool]) =>
!excludeTools.includes(key) && getToolSupportsAutomate(tool),
!excludeTools.includes(key) &&
getToolSupportsAutomate(tool) &&
Boolean(tool.operationConfig),
);
}, [toolRegistry, excludeTools]);
@@ -0,0 +1,33 @@
/**
* Registry invariant: the Automate picker offers a tool whenever it doesn't opt out via
* `supportsAutomate: false`, but automationExecutor resolves each step through the tool's
* `operationConfig`. A tool that is offered without one is selectable in the builder and
* only fails when the automation runs, with "Tool operation not supported: <toolId>".
*
* So a tool must either carry an operationConfig or declare supportsAutomate: false.
*/
import { describe, expect, test, vi } from "vitest";
import { renderHook } from "@testing-library/react";
import { useTranslatedToolCatalog } from "@app/data/useTranslatedToolRegistry";
import { getToolSupportsAutomate } from "@app/data/toolsTaxonomy";
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (key: string, fallback?: string) => fallback ?? key,
i18n: { changeLanguage: vi.fn(), language: "en-US" },
}),
Trans: ({ children }: { children?: unknown }) => children,
}));
describe("automatable tools", () => {
test("every tool offered to Automate can be executed as a step", () => {
const { result } = renderHook(() => useTranslatedToolCatalog());
const offeredWithoutConfig = Object.entries(result.current.regularTools)
.filter(([, entry]) => entry && getToolSupportsAutomate(entry))
.filter(([, entry]) => !entry.operationConfig)
.map(([id]) => id);
expect(offeredWithoutConfig).toEqual([]);
});
});
@@ -50,6 +50,8 @@ import { changeMetadataOperationConfig } from "@app/hooks/tools/changeMetadata/u
import { signOperationConfig } from "@app/hooks/tools/sign/useSignOperation";
import { cropOperationConfig } from "@app/hooks/tools/crop/useCropOperation";
import { removeAnnotationsOperationConfig } from "@app/hooks/tools/removeAnnotations/useRemoveAnnotationsOperation";
import { removeImageOperationConfig } from "@app/hooks/tools/removeImage/useRemoveImageOperation";
import { pageLayoutOperationConfig } from "@app/hooks/tools/pageLayout/usePageLayoutOperation";
import { extractImagesOperationConfig } from "@app/hooks/tools/extractImages/useExtractImagesOperation";
import { replaceColorOperationConfig } from "@app/hooks/tools/replaceColor/useReplaceColorOperation";
import { removePagesOperationConfig } from "@app/hooks/tools/removePages/useRemovePagesOperation";
@@ -526,6 +528,9 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
maxFiles: -1,
endpoints: ["validate-signature"],
synonyms: getSynonyms(t, "validateSignature"),
// Reports on signatures rather than transforming the PDF, and its hook is
// not on the operationConfig seam, so it cannot run as an automation step.
supportsAutomate: false,
automationSettings: null,
},
@@ -755,6 +760,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["multi-page-layout"],
operationConfig: asRegistryConfig(pageLayoutOperationConfig),
automationSettings: lazySettings(
() => import("@app/components/tools/pageLayout/PageLayoutSettings"),
),
@@ -967,7 +973,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
subcategoryId: SubcategoryId.REMOVAL,
maxFiles: -1,
endpoints: ["remove-image-pdf"],
operationConfig: undefined,
operationConfig: asRegistryConfig(removeImageOperationConfig),
synonyms: getSynonyms(t, "removeImage"),
automationSettings: null,
},
@@ -1196,6 +1202,9 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
endpoints: ["scanner-effect"],
synonyms: getSynonyms(t, "scannerEffect"),
// No frontend implementation yet (component is null), so it has no
// operationConfig to execute as an automation step.
supportsAutomate: false,
automationSettings: null,
},