fix(host): review round 20 — canonical shape declared at the interface; hermetic home-shape spec; single resolve

This commit is contained in:
creatixchu
2026-07-30 02:10:07 +08:00
parent cc92b6b578
commit 983e6d07a4
9 changed files with 55 additions and 17 deletions

View File

@@ -137,8 +137,9 @@ function displayCrumbs(listing: DirectoryListing, homeLabel: string): DirectoryE
/**
* The listing's platform separator, read from the host-resolved root crumb
* (`/`, `C:\`, `\\server\share\`) — exact for every root form the backend
* emits, immune both to a home delivered in the other slash flavor
* (`USERPROFILE=C:/Users/Alice`) and to backslashes inside POSIX names.
* emits, and immune to backslashes inside POSIX names (which the home text
* may legally carry; the wire contract already excludes non-canonical
* shapes elsewhere).
* TODO: replace with a host-stamped `separator` field on the wire
* DirectoryListing so the platform fact travels verbatim (the trade-off is
* recorded in the directory-picker capability seam Agent Note).

View File

@@ -217,9 +217,12 @@ export default class BrowseDirectoryPicker extends DirectoryPicker {
private async list(path?: string, signal?: AbortSignal): Promise<DirectoryListing> {
// Resolved like every other path in the listing: the environment may
// decorate HOME (trailing or repeated separators, dot segments, win32
// forward slashes) and homedir() ships it verbatim, while clients
// compare home against the resolved `path`/`crumbs` — the wire contract
// promises one canonical shape for all three.
// forward slashes) and homedir() ships it verbatim, while the wire
// contract promises one canonical shape for every listing path. A
// relative or drive-less HOME rebases under the process cwd / current
// drive here — the behavior the fullyQualified fence refuses for wire
// values — accepted for the host's own environment, since the listed
// target derives from home and stays consistent with it.
const home = resolve(homedir())
// The seam contract takes fully qualified paths only; resolve() would
// silently rebase a relative or empty wire value under the host process
@@ -227,7 +230,7 @@ export default class BrowseDirectoryPicker extends DirectoryPicker {
if (path !== undefined && !fullyQualified(path)) {
throw new DirectoryPickerError('directory-unreadable', path, `cannot list "${path}": not a fully qualified path`)
}
const target = resolve(path ?? home)
const target = path === undefined ? home : resolve(path)
// Stream the level (opendir, one dirent at a time) into a name-sorted
// window of maxEntries + 1 candidates: memory stays bounded no matter how
// many children the directory holds, the window keeps the name-sorted