From fc7897f380fdf6abaa868d17bbf07fefb84723d9 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Tue, 3 Feb 2026 07:41:38 -0700 Subject: [PATCH 01/13] fix(client): fix dev mode hanging on Windows (#1051) The `stdin: 'ignore'` option was not working because spawn-rx expects an Observable for its stdin option, not a string. This caused tsx watch and vite dev server to hang waiting for stdin on Windows. Use Node's native `stdio: ['ignore', 'pipe', 'pipe']` option instead, which spawn-rx passes through correctly to child_process.spawn. Fixes #550 Co-authored-by: Claude Opus 4.5 --- client/bin/start.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client/bin/start.js b/client/bin/start.js index f67301de..7e7e013a 100755 --- a/client/bin/start.js +++ b/client/bin/start.js @@ -56,9 +56,11 @@ async function startDevServer(serverOptions) { echoOutput: true, }; - // For Windows, we need to use stdin: 'ignore' to simulate < NUL + // For Windows, we need to ignore stdin to simulate < NUL + // spawn-rx's 'stdin' option expects an Observable, not 'ignore' + // Use Node's stdio option instead if (isWindows) { - spawnOptions.stdin = "ignore"; + spawnOptions.stdio = ["ignore", "pipe", "pipe"]; } const server = spawn(serverCommand, serverArgs, spawnOptions); @@ -140,13 +142,21 @@ async function startDevClient(clientOptions) { const clientCommand = "npx"; const host = process.env.HOST || "localhost"; const clientArgs = ["vite", "--port", CLIENT_PORT, "--host", host]; + const isWindows = process.platform === "win32"; - const client = spawn(clientCommand, clientArgs, { + const spawnOptions = { cwd: resolve(__dirname, ".."), env: { ...process.env, CLIENT_PORT }, signal: abort.signal, echoOutput: true, - }); + }; + + // For Windows, we need to ignore stdin to prevent hanging + if (isWindows) { + spawnOptions.stdio = ["ignore", "pipe", "pipe"]; + } + + const client = spawn(clientCommand, clientArgs, spawnOptions); const url = getClientUrl( CLIENT_PORT, From 885c8bc732334ede5226b8ea40b26fe247c0c742 Mon Sep 17 00:00:00 2001 From: Murat Dzhulkuttiev Date: Tue, 3 Feb 2026 20:50:50 +0300 Subject: [PATCH 02/13] fix(client): circular function calls leads to memory leak (#1049) * fix(client): circular function call leads to memory leak * allow children validation --- client/src/components/DynamicJsonForm.tsx | 6 ++++++ client/src/components/ToolsTab.tsx | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 2f38da29..ecd150f2 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -28,6 +28,7 @@ interface DynamicJsonFormProps { export interface DynamicJsonFormRef { validateJson: () => { isValid: boolean; error: string | null }; + hasJsonError: () => boolean; } const isTypeSupported = ( @@ -129,6 +130,10 @@ const DynamicJsonForm = forwardRef( // on every keystroke which would be inefficient and error-prone const timeoutRef = useRef>(); + const hasJsonError = () => { + return !!jsonError; + }; + // Debounce JSON parsing and parent updates to handle typing gracefully const debouncedUpdateParent = useCallback( (jsonString: string) => { @@ -253,6 +258,7 @@ const DynamicJsonForm = forwardRef( useImperativeHandle(ref, () => ({ validateJson, + hasJsonError, })); const renderFormFields = ( diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index d872e629..9e1b5d56 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -102,9 +102,11 @@ const ToolsTab = ({ const { copied, setCopied } = useCopy(); // Function to check if any form has validation errors - const checkValidationErrors = () => { + const checkValidationErrors = (validateChildren: boolean = false) => { const errors = Object.values(formRefs.current).some( - (ref) => ref && !ref.validateJson().isValid, + (ref) => + ref && + (validateChildren ? !ref.validateJson().isValid : ref.hasJsonError()), ); setHasValidationErrors(errors); return errors; @@ -675,7 +677,7 @@ const ToolsTab = ({ + )} + {selectedTool && ( + + )} + + + + +
+ {error && ( + + + {error} + + )} + + {selectedTool ? ( + (() => { + const hasFields = + selectedTool.inputSchema.properties && + Object.keys(selectedTool.inputSchema.properties).length > 0; + + return ( +
+ {!isAppOpen ? ( +
+ {selectedTool.description && ( +

+ {selectedTool.description} +

+ )} + +
+

App Input

+ {Object.entries( + selectedTool.inputSchema.properties ?? [], + ).map(([key, value]) => { + // First resolve any $ref references + const resolvedValue = resolveRef( + value as JsonSchemaType, + selectedTool.inputSchema as JsonSchemaType, + ); + const prop = normalizeUnionType(resolvedValue); + const inputSchema = + selectedTool.inputSchema as JsonSchemaType; + const required = isPropertyRequired( + key, + inputSchema, + ); + + return ( +
+
+ + {prop.nullable ? ( +
+ + setParams({ + ...params, + [key]: checked + ? null + : prop.type === "array" + ? undefined + : prop.default !== null + ? prop.default + : prop.type === "boolean" + ? false + : prop.type === "string" + ? "" + : undefined, + }) + } + /> + +
+ ) : null} +
+ +
+ {prop.type === "boolean" ? ( +
+ + setParams({ + ...params, + [key]: checked, + }) + } + /> + +
+ ) : prop.type === "string" && prop.enum ? ( + + ) : prop.type === "string" ? ( +