ui: CWD for agent (#26518)
* server : extend file_glob_search for UI pickers * ui : add per-conversation working directory with picker * ui : add path navigation and search scope to cwd picker Treat path-like queries (starting with / or ~) as directory navigation instead of glob-matching the whole query: search the parent for the last segment, and descend into an exactly-typed directory by listing its children. Show the effective search scope in the footer and auto-search on open so the current directory and its siblings appear immediately. Assisted-by: Claude * db : persist per-call tool cwd on tool result messages * ui : abbreviate tool paths under home with a tilde * ui : show the per-call cwd on exec shell rows * ui : clarify the synthetic cwd message for the model * ui : reuse the trailing cwd row on a repeated pick * ui : don't jump when a cwd row is injected mid-chat * chore: Formatting * refactor: Cleanup comments * ui : unify working directory naming and add a synthetic-message flag * ui : render synthetic cwd rows without a scroll jump * ui : decouple the working directory picker into utils and sub-components * ui : add get_info tool call block * chore: Formatting * refactor: Cleanup * refactor: Cleanup * refactor: Cleanup * fix: UI * server : harden file_glob_search listing (kind enum, timeout, symlink guard, absolute base) * ui : use persisted isSynthetic flag for cwd rows, drop legacy formats * ui : cache picker search, fail visibly on native resolve * ui : escape glob metacharacters in picker search glob * ui : simplify auto-scroll pin * chore: Format * fix: Use `SvelteMap` * refactor: Post-review fixes * ui: accept Windows roots in the working directory picker recognize a drive root (C:) and a UNC share (//host/share) as path navigation, alongside the POSIX root and ~, so a query like D:\repos lists that directory instead of glob-matching it under the home dir split below the root, so a bare drive resolves to its root rather than to a drive-relative prefix rewrite backslashes into forward slashes only when the query carries a Windows root, since a backslash is a legal POSIX filename character paths keep travelling with forward slashes, which is what the server returns and what Windows accepts --------- Co-authored-by: Pascal <admin@serveurperso.com>
This commit is contained in:
co-authored by
Pascal
parent
0713275082
commit
2f56fc3431
@@ -86,6 +86,15 @@ class ConversationsStore {
|
||||
/** Global (non-conversation-specific) reasoning effort default */
|
||||
pendingReasoningEffort = $state<ReasoningEffort>(ConversationsStore.loadReasoningEffortDefault());
|
||||
|
||||
/**
|
||||
* Working directory picked on the empty new-chat screen, before any
|
||||
* conversation exists. Consumed by `chatStore.sendMessage()`, which
|
||||
* records it into chat history as a synthetic message on first send.
|
||||
* Cleared by `loadConversation` and `clearActiveConversation` so a
|
||||
* stale pick can't bleed onto an unrelated chat.
|
||||
*/
|
||||
pendingCwd = $state<string | null>(null);
|
||||
|
||||
/** Load reasoning effort default from localStorage, DEFAULT defers to the server */
|
||||
private static loadReasoningEffortDefault(): ReasoningEffort {
|
||||
if (typeof globalThis.localStorage === 'undefined') return ReasoningEffort.DEFAULT;
|
||||
@@ -250,9 +259,13 @@ class ConversationsStore {
|
||||
// No MCP override list is seeded: getAllMcpServerOverrides resolves
|
||||
// servers without a per-conversation override to `mcpServers[i].enabled`,
|
||||
// and only explicit toggles are stored on the conversation.
|
||||
// Working directory picked on the new-chat screen gets threaded in
|
||||
// here too, then cleared so it doesn't bleed onto subsequent new chats.
|
||||
const conversation = await DatabaseService.createConversation(conversationName, {
|
||||
reasoningEffort: this.pendingReasoningEffort
|
||||
reasoningEffort: this.pendingReasoningEffort,
|
||||
cwd: this.pendingCwd ?? undefined
|
||||
});
|
||||
this.pendingCwd = null;
|
||||
|
||||
this.conversations = [conversation, ...this.conversations];
|
||||
this.activeConversation = conversation;
|
||||
@@ -276,6 +289,10 @@ class ConversationsStore {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Drop any cwd the user drafted on the empty new-chat screen -
|
||||
// it doesn't belong to this conversation.
|
||||
this.pendingCwd = null;
|
||||
|
||||
this.activeConversation = conversation;
|
||||
|
||||
if (conversation.currNode) {
|
||||
@@ -306,6 +323,7 @@ class ConversationsStore {
|
||||
this.activeMessages = [];
|
||||
// reload defaults so new chats inherit persisted state
|
||||
this.pendingReasoningEffort = ConversationsStore.loadReasoningEffortDefault();
|
||||
this.pendingCwd = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -855,6 +873,42 @@ class ConversationsStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the working directory for the active conversation. Pass `null` or
|
||||
* an empty string to clear it, which restores the picker's empty state.
|
||||
*
|
||||
* On the empty new-chat screen (no active conversation yet), the value
|
||||
* is buffered into `pendingCwd` so the user can pick before
|
||||
* sending the first message; `createConversation()` consumes it.
|
||||
*
|
||||
* @param value - Absolute server-side path to the working directory, or null to clear
|
||||
*/
|
||||
async setCwd(value: string | null): Promise<void> {
|
||||
const trimmed = value?.trim() || undefined;
|
||||
|
||||
// No chat yet - buffer for the first chat the user creates.
|
||||
if (!this.activeConversation) {
|
||||
this.pendingCwd = trimmed ?? null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeConversation = {
|
||||
...this.activeConversation,
|
||||
cwd: trimmed
|
||||
};
|
||||
|
||||
await DatabaseService.updateConversation(this.activeConversation.id, {
|
||||
cwd: trimmed
|
||||
});
|
||||
|
||||
const convIndex = this.conversations.findIndex((c) => c.id === this.activeConversation!.id);
|
||||
if (convIndex !== -1) {
|
||||
this.conversations[convIndex].cwd = trimmed;
|
||||
this.conversations = [...this.conversations];
|
||||
}
|
||||
this.pendingCwd = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forks a conversation at a specific message, creating a new conversation
|
||||
* containing messages from root up to the target message, then navigates to it.
|
||||
@@ -1169,6 +1223,7 @@ if (browser) {
|
||||
export const conversations = () => conversationsStore.conversations;
|
||||
export const activeConversation = () => conversationsStore.activeConversation;
|
||||
export const activeMessages = () => conversationsStore.activeMessages;
|
||||
export const pendingCwd = () => conversationsStore.pendingCwd;
|
||||
export const isConversationsInitialized = () => conversationsStore.isInitialized;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user