browse the directories of the server the pane is on

Reported from the iPad: MacBook selected, and the directory picker still listed alpha
folders.

Three layers all defaulted to this origin — useFilesAPI, DirPickerModal and PwdSelector — so
the pane pointed one way and the pickers another. Same defect as browseDirectories in the
mobile app, found this morning: a path only means something on the machine it came from, and
offering another machine folders is worse than offering none, because picking one silently
runs the agent somewhere that does not exist.

The dir-picker cache is keyed by server too. Without it one machine tree is served from cache
under the other name, which looks like the fix not working.

Other useFilesAPI callers pass no server and are unchanged — the code editor and the message
bubble still read this origin exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 21:47:38 +01:00
co-authored by Claude Opus 5
parent 8bab607366
commit 2d1fc05518
4 changed files with 23 additions and 8 deletions
@@ -8,12 +8,14 @@ type DirPickerModalProps = {
open: boolean;
onClose: () => void;
onSelect: (absPath: string) => void;
/** Whose filesystem to browse. Absent = this origin. */
serverId?: string | null;
};
// A simplified file-browser modal for picking a working directory (returns an absolute path).
// Navigates within the home root; dirs elsewhere are reachable via the selector's free-text field.
export const DirPickerModal = ({ open, onClose, onSelect }: DirPickerModalProps) => {
const api = useFilesAPI('home');
export const DirPickerModal = ({ open, onClose, onSelect, serverId }: DirPickerModalProps) => {
const api = useFilesAPI('home', serverId);
const [path, setPath] = useState('/'); // root-relative, always starts with '/'
const [creating, setCreating] = useState(false);
const [newName, setNewName] = useState('');
@@ -23,7 +25,9 @@ export const DirPickerModal = ({ open, onClose, onSelect }: DirPickerModalProps)
const [showHidden, setShowHidden] = useState(false);
const { data, isLoading, refetch } = useQuery({
queryKey: ['dir-picker', path],
// Server in the key: two machines have different trees, and without it one machine's folders
// are served from cache under the other's name.
queryKey: ['dir-picker', path, serverId ?? null],
queryFn: () => api.listDir(path),
enabled: open,
});
@@ -6,14 +6,16 @@ import { DirPickerModal } from './DirPickerModal';
type PwdSelectorProps = {
value: string | null; // null = the default general_chat_sessions dir
onChange: (cwd: string | null) => void;
/** Which Officer's directories to offer. Absent = this origin. */
serverId?: string | null;
};
// A shorter, friendlier label for a working directory.
const shorten = (cwd: string) => cwd.replace(/^\/home\/[^/]+/, '~');
const basename = (cwd: string) => cwd.split('/').filter(Boolean).pop() ?? cwd;
export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => {
const { pwds, defaultCwd } = useChatPwds();
export const PwdSelector = ({ value, onChange, serverId }: PwdSelectorProps) => {
const { pwds, defaultCwd } = useChatPwds(serverId);
const [open, setOpen] = useState(false);
const [browse, setBrowse] = useState(false);
const [custom, setCustom] = useState('');
@@ -96,7 +98,7 @@ export const PwdSelector = ({ value, onChange }: PwdSelectorProps) => {
</>
)}
<DirPickerModal open={browse} onClose={() => setBrowse(false)} onSelect={(p) => pick(p)} />
<DirPickerModal open={browse} onClose={() => setBrowse(false)} onSelect={(p) => pick(p)} serverId={serverId} />
</div>
);
};
@@ -95,6 +95,7 @@ export const SessionList = () => {
}}
/>
<PwdSelector
serverId={serverId}
value={activeCwd}
onChange={(cwd) => {
setSelected(null); // sessions belong to a cwd — clear the open one when switching
@@ -1,9 +1,17 @@
import { useClient, getHeaders } from 'hooks/useClient';
import { useServerClient } from 'hooks/useServerClient';
const API_URL = '/api';
export const useFilesAPI = (root: string = 'home') => {
const client = useClient();
/**
* `serverId` names another Officer; absent is this origin, which is every existing caller.
*
* Load-bearing for the chat directory pickers: a path only means something on the machine it came from,
* so browsing without it showed THIS server's folders while the pane was pointed at another — the exact
* bug the mobile app has with `browseDirectories`.
*/
export const useFilesAPI = (root: string = 'home', serverId?: string | null) => {
const client = useServerClient(serverId);
const rootParam = root !== 'home' ? `root=${encodeURIComponent(root)}` : '';
const withRoot = (url: string) =>
rootParam ? (url.includes('?') ? `${url}&${rootParam}` : `${url}?${rootParam}`) : url;