From: Pascal Date: Sat, 8 Aug 2026 14:36:21 +0000 (+0200) Subject: server, ui: only offer a working directory when a tool reads it (#26762) X-Git-Tag: upstream/0.0.10438~109 X-Git-Url: https://git.djapps.eu/?a=commitdiff_plain;h=18f7ad7fc912444acc0f51995a4b8e45fd9a0cd4;p=pkg%2Fggml%2Fsources%2Fllama.cpp server, ui: only offer a working directory when a tool reads it (#26762) The working directory chip showed up as soon as the server exposed any builtin tool, so a server started with just get_datetime, or a user who turned every filesystem tool off in the settings, still got a control that nothing would read. Tools now declare whether they resolve their paths and run against the working directory, next to the write permission they already publish in the /tools listing. The WebUI shows the chip and enables the /cwd command only when at least one such tool is both served and left enabled. --- diff --git a/tools/server/server-tools.cpp b/tools/server/server-tools.cpp index eacfbf0f7..d5e696434 100644 --- a/tools/server/server-tools.cpp +++ b/tools/server/server-tools.cpp @@ -74,6 +74,7 @@ json server_tool::to_json() const { {"permissions", json{ {"write", permission_write} }}, + {"uses_cwd", uses_cwd}, {"definition", get_definition()}, }; } @@ -763,6 +764,7 @@ struct server_tool_read_file : server_tool { server_tool_read_file() { name = "read_file"; display_name = "Read file"; + uses_cwd = true; permission_write = false; } @@ -851,6 +853,7 @@ struct server_tool_file_glob_search : server_tool { server_tool_file_glob_search() { name = "file_glob_search"; display_name = "File search"; + uses_cwd = true; permission_write = false; } @@ -965,6 +968,7 @@ struct server_tool_grep_search : server_tool { server_tool_grep_search() { name = "grep_search"; display_name = "Grep search"; + uses_cwd = true; permission_write = false; } @@ -1117,6 +1121,7 @@ struct server_tool_exec_shell_command : server_tool { server_tool_exec_shell_command() { name = "exec_shell_command"; display_name = "Execute shell command"; + uses_cwd = true; permission_write = true; support_stream = true; } @@ -1195,6 +1200,7 @@ struct server_tool_write_file : server_tool { server_tool_write_file() { name = "write_file"; display_name = "Write file"; + uses_cwd = true; permission_write = true; } @@ -1237,6 +1243,7 @@ struct server_tool_edit_file : server_tool { server_tool_edit_file() { name = "edit_file"; display_name = "Edit file"; + uses_cwd = true; permission_write = true; } @@ -1625,6 +1632,7 @@ struct server_tool_get_info : server_tool { server_tool_get_info() { name = "get_info"; display_name = "Get Runtime Info"; + uses_cwd = true; permission_write = false; } diff --git a/tools/server/server-tools.h b/tools/server/server-tools.h index 7f70e6767..ede303181 100644 --- a/tools/server/server-tools.h +++ b/tools/server/server-tools.h @@ -14,6 +14,7 @@ struct server_tool { std::string display_name; bool permission_write = false; bool support_stream = false; // if true, output can be streamed + bool uses_cwd = false; // if true, the tool resolves paths and runs against the working directory virtual ~server_tool() = default; virtual json get_definition() const = 0; diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte index 1df125708..2d70c302c 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte @@ -156,7 +156,7 @@ focusInput: refocusInput, getShowModelSelector: () => showModelSelector, hasPrompts: () => mcpStore.hasPromptsCapability(conversationsStore.getAllMcpServerOverrides()), - hasBuiltinTools: () => toolsStore.builtinTools.length > 0, + hasCwdTools: () => toolsStore.hasEnabledCwdTools, getCwd: () => cwd, getServerHome: () => toolsStore.serverHome ?? null, openModelSelector: () => chatFormActionsRef?.openModelSelector(), @@ -651,7 +651,7 @@ - {#if toolsStore.builtinTools.length > 0} + {#if toolsStore.hasEnabledCwdTools} boolean; /** Gates `/cwd`. */ - hasBuiltinTools: () => boolean; + hasCwdTools: () => boolean; } /** @@ -32,7 +32,7 @@ export function getChatCommands(options: ChatCommandsOptions): ChatFormCommand[] description: SET_WORKING_DIRECTORY_LABEL, keywords: ['current working directory'], action: ChatFormCommandAction.CWD, - disabled: !options.hasBuiltinTools() + disabled: !options.hasCwdTools() }, { name: 'model', diff --git a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts index f3bc5f732..4860f16fb 100644 --- a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts +++ b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts @@ -24,7 +24,7 @@ export interface UseChatFormPickersOptions { /** Gates `/prompt`. */ hasPrompts: () => boolean; /** Gates `/cwd`. */ - hasBuiltinTools: () => boolean; + hasCwdTools: () => boolean; getCwd: () => string | null; /** Mention search fallback scope. */ getServerHome: () => string | null; @@ -63,7 +63,7 @@ export function useChatFormPickers(opts: UseChatFormPickersOptions) { getChatCommands({ showModelSelector: opts.getShowModelSelector(), hasPrompts: opts.hasPrompts, - hasBuiltinTools: opts.hasBuiltinTools + hasCwdTools: opts.hasCwdTools }) ); diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index 5136101e7..4114ef756 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -27,6 +27,9 @@ class ToolsStore { private _loading = $state(false); private _error = $state(null); private _disabledTools = $state(new SvelteSet()); + // builtin tools that resolve their paths against the working directory, + // as declared by the server in its `/tools` listing + private _cwdAwareTools = $state(new SvelteSet()); private _toolsEndpointUnreachable = $state(false); private _serverHome = $state(undefined); @@ -476,6 +479,21 @@ class ToolsStore { return this.getEnabledToolsForLLM().length > 0; } + /** + * Check if a working directory is worth setting: at least one builtin tool + * that reads it is both served and left enabled by the user. + */ + get hasEnabledCwdTools(): boolean { + return this._builtinTools.some((def) => { + const name = def.function.name; + + return ( + this._cwdAwareTools.has(name) && + !this._disabledTools.has(this.toolKey(ToolSource.BUILTIN, name)) + ); + }); + } + async fetchBuiltinTools(): Promise { if (this._loading) return; @@ -486,6 +504,9 @@ class ToolsStore { try { const toolInfos = await ToolsService.list(); this._builtinTools = toolInfos.map((info) => info.definition); + this._cwdAwareTools = new SvelteSet( + toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool) + ); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); this._error = errorMessage; diff --git a/tools/ui/src/lib/types/mcp.d.ts b/tools/ui/src/lib/types/mcp.d.ts index b567c20c9..2b5937913 100644 --- a/tools/ui/src/lib/types/mcp.d.ts +++ b/tools/ui/src/lib/types/mcp.d.ts @@ -292,6 +292,7 @@ export interface ServerBuiltinToolInfo { permissions: { write: boolean; }; + uses_cwd: boolean; definition: OpenAIToolDefinition; } diff --git a/tools/ui/tests/client/components/ChatFormPickersHarness.svelte b/tools/ui/tests/client/components/ChatFormPickersHarness.svelte index 76b2e9fe3..8e5e56c20 100644 --- a/tools/ui/tests/client/components/ChatFormPickersHarness.svelte +++ b/tools/ui/tests/client/components/ChatFormPickersHarness.svelte @@ -21,7 +21,7 @@ focusInput: () => {}, getShowModelSelector: () => true, hasPrompts: () => true, - hasBuiltinTools: () => true, + hasCwdTools: () => true, getCwd: () => null, getServerHome: () => null, openModelSelector: () => {