The per-session random write SID forced a full tree propagation per
session per server lifetime (minutes on large workspaces). The write
SID is now the per-workspace identity derived from the canonical
workspace path (workspaceWriteSid: sha256 -> S-1-4-x-y), stored
nowhere: the workspace-root ACE materializes once per workspace per
machine and every later provision hits the exact-ACE skip.
- workspace ACEs are STANDING (never revoked - the reuse cache); temp
ACEs stay revocable (disposed with the provider), so an inheritable
ACE never outlives its session's temp dir on the ambient temp root
- AclSandbox requires the write SID under workspace-write; read-only
parses/grants nothing; the runner derives the SID itself (the
--write-sid flag's presence still marks the seam-managed contract)
- the acl-session record drops writeSid (sessionId/workspace/tempDir
remain): the SID-tamper surface and its validation are gone
- sandbox-local holds two grant maps: standing workspace grants and
revocable per-session temp grants
Docs (README pair, design note pair, catalogs, type-equiv) and the
acl-session/grant/acl/probe/runner suites updated; workspace-sid.spec
pins the derivation contract.
A hand-damaged preset was silent until the worst moment. An unparsable
composition listed as an ordinary selectable row and failed only at the
next session start — set as default, every new session failed. A
directory whose composition file was deleted vanished from the roster
while still occupying its id: copy answered "delete the existing preset
first" while remove answered "not found", a dead end.
Discovery now owns health: every id-shaped directory is a roster slot,
broken when its composition is missing or unloadable, checked with the
loader's own entryListSchema dialect (!!js included) so health never
rejects what the loader accepts. `broken` rides AgentPreset, the
agentPreset.list entry, and the UI row; mount/recompose/standingKeyFor
refuse broken up front with the discovery-reported reason, while
resolve/read/remove still answer. The section renders marked red cards —
unselectable, uncopyable, deletable, location kept on custom rows — and
both pickers drop broken rows entirely.
The cordis preset's persona now forbids editing the shipped install
(corrupting cordis would disable the mode itself) and points authoring
at $DSH_HOME/.agent-presets; its skill teaches preset.yml metadata, the
copy-first workflow, the one-escalation sandbox reality, and honest
verification. Exercised live: asked to edit the shipped composition the
composed agent refuses citing both rules; asked for real presets (simple
and complex) it lands them under the user root with one approved
escalation each and self-checks with the loader dialect.
Rebuild of the region machinery (PR3) on the post-#904 Typert projection:
renderPageRegion/renderInheritedPage live in dsh-typert-generator beside the
projection; scripts/gen-cordis-catalog.ts owns the curated SERVICE_PAGE /
EVENT_SCOPE_PAGE / SERVICE_WALK_EXEMPTIONS / LINK_MAP partition (fail-loud in
both directions, with the independent Context-merge scan backstopping the
projection's blind spot), spliceRegion, and the guarded pair auto-record.
docs/cordis-catalog/ is deleted: the flat events/services catalogs dissolve
into per-page regions and docs/cordis-catalog/core moves to docs/cordis-api/
with the inherited tier as its own generated page. The partition absorbs the
post-regrouping surface: ctx.typert → invariants.md, ctx.directoryPicker →
workspace.md, skills/* events → skills.md, and the four launcher-provided tui
accessor values join the named exemptions.
The folder is becoming the home of one-doc-per-subsystem pages (intro +
data structures + cordis services/events), so the name must describe the
whole contract, not just the type-vocabulary third of it. Mechanical
rename rebuilt on current master: every inbound Markdown link, generator
constant, website route, type-equiv manifest path, and spec expectation
moves together; the zh sides of the notes whose prose names the folder
are aligned (子系统) in the same change; touched bilingual pairs
re-recorded; translation-prompt snapshot re-recorded (its example embeds
development.md). Historical Agent Note slugs keep their dated
filenames.
Move the triplet to implemented/architecture/, rewrite Proposal into a
present-tense Decision, fold Acceptance criteria and Risks into
Consequences (recording what pins the result), retitle the migration
section to what the move actually touched, and repoint the seven
inbound FIXME/README links.
The task registry has run every background bash, pwsh, pty-send, and
one-shot subagent since it landed, but only the model could read it: a
human at the Web client could not see that a build was running, tell a
finished task from a stuck one, or find its outcome anywhere but the
`run_in_background` tool card that printed an id and never updated.
Task state now reaches the browser as one whole-snapshot `session/tasks`
mux frame per session, pushed at every registry commit that changes what
that session can see. `TaskService` gains `onTasksChanged`, which is
owner-granular because owner-disposal removal is a change no per-task
record can express. The carrier reads the exact owner the listener hands
it, so a push stays correct while that scope tears down, and reads the
baseline through the non-resuming `ctx.agents.get` so listing never
revives a cold session. The client keeps a last-wins mirror on
`SessionListState`, and a new `dsh-client-ui-task` package renders it
beside the subagent catalog — rendering nothing at all until the session
has a task, so an ordinary conversation grows no new chrome.
Streamed per-task output and human-initiated cancellation are separate
phases; the note records why neither has to undo this channel, and why
no Web path may call the consuming `ctx.tasks.read()`.
- restore the parent's AGENTS.md body around this branch's e2b row (the
replay had resurrected the pre-profile-CLI layout, deleting the api/,
bundle/, settings/, and credentials/ groups); the fs/lsp row
condensations stay for the word budget
- aggregate the composite rollback failure, not the original error, when
private state cleanup also fails — a surviving remote group is no
longer hidden by a later cleanup error (triple-failure regression)
- own the state directory before the makeDir RPC, matching the terminal
transaction, so a cancellation racing a committed creation still
enters cleanup
- drop the hidden pollMs parameter defaults; the schemastery default is
the one home, and tests pass the cadence explicitly
- restore spawn/spawnTerminal graceMs validation to the seam's
documented bound (the earlier removal cited subprocess-local as not
validating; it does), with rejection regressions
The web YAML editor is gone. agentPreset.write (arbitrary composition
text) became agentPreset.copy { from, agentPreset, name? }: a host-side
whole-directory copy of ids the host resolves itself — symlinks
dereferenced, modes re-tightened to owner-only with owner-execute kept,
metadata rewritten to keep the source's description but never its name or
roster order. No composition text or path crosses the wire in either
authoring direction, and the entryListSchema/!!js concern dissolves with
assertComposition itself.
The settings section becomes: a read-only viewer over shipped
compositions, a copy dialog (id + optional display name) as the only
create entry, delete for custom rows, and a location action leading into
the preset's own files — agentPreset.openDocument { agentPreset } resolves
the directory host-side and opens it natively, or answers
{ opened: false, path } for the row to show as text where the deployment
has no desktop. agentPreset.list reports hasDocument beside authorable;
the gateway's nativeOpen config pins the capability where
canOpenNativePath platform detection would mislead. The privileged set is
now read/copy/openDocument/remove.
With files as the only composition editor, standing mounts grew
stamp-keyed generations: ensureStanding compares the composition file's
mtime+size and starts the next generation for later sessions, while every
joined session keeps the generation it runs on.
New keyless web lane (agent-preset-authoring, overlay pins
nativeOpen: false so goldens render one branch on every platform) drives
view/copy/reveal/delete end to end; the real-composition CLI e2e switches
to copy semantics.
The linear replay carried old-lineage content over parent-owned files;
this checkpoint restores them and adapts the branch to the parent's
post-rebase seam:
- restore all pty/lsp/subprocess/code-runtime surfaces to the parent's
exact content (this branch claims none of them) and drop the net-zero
code-runtime-e2b/pty-e2b/lsp-e2b residue and its registrations
- widen serializeRemoteEnvironment to the seam's NodeJS.ProcessEnv
tombstone contract: an explicit undefined removes an ambient entry
- migrate the two E2B fixture Agent stubs to the Inbox-model interface
and Session.create
- re-apply the branch's gen-doc-graphs roles, THIRD_PARTY_NOTICES e2b
row, and packages/README group row (trimmed to the doc budget);
regenerate catalogs and re-record bilingual pairings
- subprocess-e2b: the 20 ms remote poll cadence becomes a validated pollMs
Config field (each tick is one control-plane request); the README documents
the latency-versus-request-count trade.
- subprocess-e2b: extract src/remote.ts owning asError, signalOpts,
commandOpts, delay, waitTick, and one tolerant signalRemoteGroups shared by
the pgid-keyed process ladder and sid-keyed terminal ladder, so the two
teardown paths keep identical error tolerance.
- subprocess-e2b: service disposal aggregates sibling cleanup failures into
one AggregateError instead of discarding all but the first.
- subprocess-e2b: waitForProcessGroupId refuses published group ids <= 1, so
a same-UID rewrite of the pid file cannot aim termination at kill -- -1;
README documents the same-UID control-state limitation.
- subprocess-e2b: drain-grace expiry now releases an inherited-output E2B
callback blocked on host backpressure before disconnecting, so the SDK
settlement cannot stay pinned behind an unread host stream.
- subprocess-e2b: spawn/spawnTerminal stop validating typed spec fields
(trust-TypeScript rule; pty-local validates its config before specs exist);
resolveExecutable rejects separator-containing relative paths per the seam
contract; terminal setups tracked as a Set of records.
- subprocess-e2b: PTY output push-without-backpressure is a documented
contract (flowing consumer folds bytes; paused consumer buffers).
- fs-e2b: streamText normalizes the pinned SDK's empty-file '' return into an
empty stream instead of throwing on getReader().
- e2b overlays: comment the one-world cwd invariant across e2b.cwd,
workspaceRoot, and bash-local's implicit default workdir.
Remove the one-consumer bounded-read primitive and shared terminal lifecycle controller, make terminal cleanup one awaited provider operation, and reuse one Code Runtime contract suite. Keep only reproduced cancellation and policy fixes; defer unproven replacement, prompt-attribution, and streaming-frame concerns to scoped markers.