import type { FacadeResponse } from "./facade-envelope.js"; import { WEB_MODULE_REGISTRY, getWebModuleWorkerForCommand } from "./module-registry.generated.js"; import { WebModuleRouter } from "./module-router.js"; import { FEM_NATIVE_BOUNDARY } from "./fem-boundary.js"; import { nativeSourceDecision } from "./native-source-policy.js"; export interface WebCommandDescriptor { id: string; module: string; command: string; worker: string | null; category: "create" | "inspect" | "python" | "persistence" | "format" | "boundary"; runnable: boolean; reason: string; } function category(command: string): WebCommandDescriptor["category"] { if (command.includes("save") || command.includes("reopen")) return "persistence"; if (command.includes("inspect")) return "inspect"; if (command.includes("python")) return "python"; if (command.includes("import") || command.includes("export")) return "format"; if (command.includes("boundary") || command.includes("server")) return "boundary"; return "create"; } export function listWebCommands(query = ""): WebCommandDescriptor[] { const normalized = query.trim().toLowerCase(); return WEB_MODULE_REGISTRY.flatMap((route) => route.commands.map((command) => { const worker = getWebModuleWorkerForCommand(route.module, command) ?? route.primaryWorker; const boundary = route.guiDependency && category(command) === "boundary"; const sourceDecision = nativeSourceDecision(route.module, command); const nativeBlocked = route.module === "Fem"; return { id: `${route.module}:${command}`, module: route.module, command, worker: worker ?? null, category: category(command), runnable: !boundary && !sourceDecision, reason: sourceDecision?.message ?? (nativeBlocked ? FEM_NATIVE_BOUNDARY.message : boundary ? "GUI/external capability boundary" : "registered Worker route"), }; })).filter((descriptor) => !normalized || `${descriptor.module} ${descriptor.command} ${descriptor.category}`.toLowerCase().includes(normalized)); } export class WebCommandRuntime { readonly commands = listWebCommands(); constructor(private readonly router: WebModuleRouter) {} async execute(descriptor: WebCommandDescriptor, payload: unknown, documentId = "workbench-document", signal?: AbortSignal): Promise { if (!descriptor.runnable) return { schema: "freecad-web-facade/v1", version: 1, kind: "error", requestId: `command-${descriptor.id}`, error: { code: "restricted", message: descriptor.reason, diagnostic: { module: descriptor.module, command: descriptor.command } } }; return this.router.request(descriptor.module, descriptor.command, payload, documentId, signal); } }