Isolation is restored on the premise the review corrected: a workspace file
need not be agent-authored — a read row makes every file in a cloned
repository openable — and a same-origin active document was measured driving
/api/settings.describe to a 200 with full data. Script-capable documents go
back into an opaque origin; the preview's lost localStorage is the known cost,
and a separate serving origin is the way to retire it.
- confine(): a workspace rooted at a filesystem root has a realpath already
ending in the separator, and the doubled prefix 403'd every child.
- turnDeliverables(): reset on the turn boundary, not only at a closing
assistant, so an interrupted turn cannot spill into the next turn's row;
and recognize a mutation by render intent (diff card, or generic with
kind 'edit') so str_replace_editor's insert counts.
- 405 answers name the methods it allows.
- The e2e now cold-seeds a recorded WRITE turn, so the assembled application
covers the Produced row, its chip's served URL, and the isolation header.
- Agent Note matched to what shipped (the row is in this PR, not deferred);
ui-conversation README documents the new destination and the row; the
fixture lane's dead-tab quirk and the cold-path listing cost are recorded.
Serve one file at a time out of a Session's workspace under /f on the web
transport, and point the conversation's existing file-open affordance at it.
Clicking a write/edit/read row's path now opens that file in a browser tab —
including from a LAN client, where the Host's system opener is fenced to
loopback and answered nothing.
- /f/<sessionId>/<segments> in client-connection, behind the same
browser-trust fence as /api; realpath confinement, streamed reads,
GET/HEAD only, nosniff + no-store.
- Script-capable documents carry CSP sandbox: model-authored markup must not
be same-origin with /api, where events.mux is a readable GET stream.
- ApiProxy.workspaceRootOf answers where a Session's files live without
resuming an agent; the client program cannot reach the core services.
- The /f URL shape lives in dsh-host-apiproxy/api so both ends share one
encoding (client bundles may not value-import another plugin).