- resolve.ts: gate the display branch on linux (the native backend drives
exactly darwin/win32/linux) and require a zenity/kdialog binary on PATH,
probed once at boot (new probe.ts, injected predicate for tests); type
bindHost as the webserver schema's closed union.
- index.ts: the disposer now joins the removed entry's fiber teardown so
unloading the chooser settles only after the backend quiesced; export
BACKEND_PACKAGES as the runtime-string source of truth.
- verify-cordis-config: a composition mounting -auto must declare both
backends as dependencies (negative-tested), since keyless Linux CI only
ever resolves browse and would hide a dropped -native dep.
- apps/web scaffold: pin -browse via disable+insert (goldens are
interaction-specific); fix the stale workspace-flow comment.
- docs/module-graph.md regenerated; README + Agent Note document the
ssh -L shape, the PATH-only probe, and the new gate (zh pairs re-paired).
- composition spec: assert teardown quiescence without a loader await,
cover external entry removal, and await the loader's self-dispose
disabled-persist so it cannot race temp-dir teardown.
gen-cordis-catalog/gen-cordis-api pick up the SessionTitleService rename
JSDoc and line shifts; gen-doc-graphs picks up the session-title invariant
companion's session/event edge.
SessionTitleInvalidError narrows the one rename failure that blames the
input; the fallback-unpin append extracts to appendFallback beside
ensureFallback's guarded twin; a deferred-provider test proves rename
supersedes ACTIVE generation; the invariant companion enforces
messageSeqs-empty iff user-source on every appended session/title event
(tsconfig gains the session-title invariant path); SessionTitleEventData
field docs state the third source kind and the empty-seqs rule, mirrored
into the bilingual core-data-structures page; the note qualifies the
refresh unpin as conditional on a derivable replacement.
gen-cordis-catalog embeds source line anchors; the switch extraction and the
ignore annotation moved SessionTitleService, so the services page was stale
against the committed code.
sessionTitle.rename appends a user-source session/title event; onUserMessage
skips scheduling while a user title stands, and an explicit refresh is the
deliberate unpin (provider regeneration, or a re-derived fallback when no
provider is registered).
Add @deepseek-ai/dsh-host-directory-picker-auto, a node-half-only chooser
that samples the host situation once at boot (bind host via a new
httpServer.host getter, SSH markers, platform, DISPLAY/WAYLAND_DISPLAY)
and mounts the matching dual-face backend (-native or -browse) as a real
Loader entry in the in-memory root tree; the effect disposer removes it.
Entry-level mounting keeps the seam's one-row-swaps-both-faces invariant:
the client module table discovers the mounted backend's browser half
exactly as a config row's. apps/cli now composes -auto as its
directory-picker row; composing a backend row directly remains the pin.
Review round on #758.
bash.run() only promises to resolve for nonzero exits, timeouts, and aborts,
and bash.resolve() can reject on policy grounds, so either could escape the
serial agent/step listener and abort the model turn — contradicting the
plugin's documented failed-query no-op contract. Contain both and log a
warning instead; the location is optional context.
The Agent Note claimed an unchanged location suppresses the query. It does
not: only the interval floor is checked before the query, while change
suppression compares state the query returned. Corrected in both languages
and re-recorded the i18n pairs.
The aborted exit no longer awaits close (Node queues it behind any
in-flight read, chaining the departed caller back onto the very stall
the abort escaped) — the abandoned close's failure is swallowed, it has
no consumer. Symlink stat probes race the signal too, with a
per-candidate abort check between probes, so a stalled probe target
cannot keep a departed request alive. The deferred handle cleanup after
a lost opendir race now consumes its own close failure instead of
leaking it as an unhandled rejection.
Every filesystem await in the browse scan (opendir and each read) now
races the signal through raceAbort, so a stalled network open/read stops
with a departed caller and an already-aborted request rejects even for
an empty level; the abandoned settlement is swallowed and an abandoned
open that still mints a handle is closed, never leaked. apiproxy maps an
aborted listing to the cancelled wire code, matching pickDirectory and
command.execute, instead of reporting a false internal failure. The
fixture spec call sites gain the wire signal argument the previous
commit's static lane flagged.
capability.list gains an optional AbortSignal threaded from the RPC
carrier's request signal (the pickDirectory pattern): a disconnected or
timed-out caller stops the opendir loop instead of the scan outliving
its caller, and the abort surfaces as its own reason rather than a
directory-unreadable dressing. boundedInsert rejects a full window's
at-or-beyond-tail candidate on one comparison and binary-inserts
retained candidates, so an oversized level no longer pays a window scan
per dirent.
The browse level now streams through opendir into a name-sorted window of
maxEntries + 1 candidates (boundedInsert), so memory stays O(maxEntries)
no matter how many children a directory holds and enterability probing
touches only windowed candidates; a windowed broken symlink is not
backfilled since the eviction already marks the level truncated.
schemastery joins the package's runtime dependencies (the source launcher
and isolated installs failed to resolve the value import). The
folder-error dialog's Choose again goes inert while the flow hole is
empty, and the withdrawal effect also keys on the open transition, so a
flow can never open over a hole nobody serves.