Every release member now declares publishConfig.access: public, so the scope no
longer mixes levels: the 221 packages/*/* and apps/* manifests join the vendored
framework and the native packages.
check-workspace-constraints drops the per-sequence expectation and holds every
release member to public, which is what stops a member from drifting back.
Access is a property of the package, not of a version: the dsh packages already
published as restricted become world-readable at their next publication.
`USER_PRESET_DIR` was exported with no production consumer — only the new test
read it, and a test that imports the implementation constant cannot catch that
constant being wrong. It stays module-internal, and the test spells the segment
it expects.
Overlaps master on the preset e2e, the web scaffold, and the generated
config-catalog triplet; all merged textually, so the catalog is regenerated and
its pairing hashes re-recorded rather than trusted.
Empty `roots` stopped meaning "no roster" when the harness-home root became
a default, so the case that keeps the unjoined-agent diagnostic silent is the
one opting out of every root — which is what it already passes and what its
name now says.
The service moved its "is a roster composed" reads to the derived root set;
the invariant companion still read `config.roots`. In the shape this change
exists for — an app configures nothing and the roster is the harness home
alone — that made the advisory warning fire while the fail-loud invariant
stayed silent, so an agent could address a model against an empty global
layer unchecked.
Both now read one source: `roots` exposes the resolved set, and the invariant
asks it. That decides the behavior deliberately rather than by omission — a
composition that mounts the roster now fails an unjoined agent whether its
roots were configured or derived, and `includeUserRoot: false` with no
configured roots is how a deployment keeps its agents on the host plane. Both
shapes are pinned; the derived-only case fails against the old predicate.
Three pieces of prose went stale with the first commit: the web-app bundle
comment still called the writable root an assembly fact patched in by
AppCLIEntry (removed in the profile-plugin-bundles refactor — `composeProfile`
owns it now, and only for the shipped root), and the shipped skill and its
Agent Note still called both roots "configuration". The README gains the
resolved-roster reader and the discoverable-but-undeletable preset a second
writable root produces.
`Config.roots` defaulted to `[]`, so a deployment that did not patch both roots
in got a roster with none — `dsh run` once booted exactly that and failed
resolving `standard`. The user root never needed an app: `<dshHome>/.agent-presets`
is the same place in every deployment, resolvable here the way
`dsh-skill-local` resolves `<dshHome>/skills`.
The roster now derives that root itself unless `includeUserRoot` is false, and
`apps/cli` supplies only the SHIPPED root, whose path an installed app alone
can resolve. The derived root is appended after every configured root, so a
shipped id still shadows a home directory claiming it and `writableRoot()`
still prefers an explicitly configured `user` root; the set is resolved once at
construction, because a root set that changed between a `list()` and the
`copy()` acting on its answer would author into a directory the caller never
saw.
Every test that pins an exact roster now says `includeUserRoot: false` — the
machine's real harness home must not decide what a golden or an assertion
counts.
Master moved peerDependencies to `workspace:^`; this branch's two added peers
(`dsh-agent`, `dsh-system-prompt`) take the same form. `docs/module-graph.*`
is generated; regenerated after the merge.
Hook bypassed for the same reason as the previous merge: the staged-pairing
check hands an archived note path to `verify-translation-pairing`. The
full-corpus gate passes.
Review found two defects in the previous commit's ordering fix.
@pku-xht: `historyStateFor` copied the attached session's events, the handler
then awaited `presenterScopeFor`, and only then read the projection baseline
off the still-live Session. An append during that await served events cut at N
beside a baseline folded to N+1 — one response describing two moments. The
same restructure had also moved the baseline read outside the `try`, so a
failing snapshot escaped the structured `internal` error.
Both awaits now happen before the cut: `historySourceFor` resolves which
session serves the read, `presenterScopeFor` ensures the recorded composition,
and `historyCutOf` then reads events and baseline adjacently with nothing
between them. The whole sequence is back inside the try.
The invariant judged any scoped assembly with a chain of one as an unjoined
agent, which rejects a legitimate assembly in a standing preset key (that key
has no parent of its own). It now gates on `context.agent` — a scope-only read
is not an agent and is out of range by construction rather than by a premise
about who else calls `assemble` — and asks the roster's own
`composedPreset()` instead of introspecting chain length. The advisory warning
uses the same relation.
Also from review: the `2026-08-05-per-agent-tool-presentation` note still
described `presentAs` as per-agent, which standing mounts made false and this
branch's own rewording contradicts; the duplicated "process-wide unit table"
argument collapses to the Agent Note with pointers from the five copies; a
dead `.sort()` before `arrayContaining`; and change-history narration in the
tool-cordis README.
1504 hand-written ranges pointing at workspace members become workspace:^, so
pnpm pack substitutes each member's real version at publication: sibling
peerDependencies follow the family version instead of being pinned at ^0.0.1,
and a reference to a vendored package follows that package's own line. Without
this, publishing 0.0.2 ships peer ranges naming a version that does not exist,
and 0.0.1-rc.1 does not satisfy ^0.0.1 either.
It also retires ranges that had gone stale against the workspace: ^4.0.0-rc.6
for a 4.0.0-rc.7 checkout, ^3.17.0 for schemastery 3.18.0.
workspace:* stays where an exact published version is the point, which is how
the Landlock entry pins its platform packages.
A workspace constraint now requires the protocol, so a new package cannot
reintroduce a hand-written range. The same constraint caught packages/boot/cmdline
arriving on master without the publishable trio, which this change completes.
Every package under packages/, apps/, and vendor/ drops "private": true and
declares publishConfig.access "restricted": the repository now states which
packages it publishes instead of deciding it at publish time. Each one also
declares its repository and directory, which is how a consumer of a private
package reaches its source.
The Landlock packages move to restricted with them. They have never been
published, so nothing anonymous depends on them today, and the whole
@deepseek-ai scope stays private.
The workspace constraint that required every package to be private now applies
to non-members only, and asserts the publishable trio on each release member.
`minimal` was rewritten upstream into a fixed-prompt PTY surface that now
carries its own compaction group, so this branch's token-meter move applies to
it too: the row leaves the group and `tokenMeter` leaves the realm, exactly as
in the other three presets. Master's header prose is kept whole — the note this
branch had added there described a composition that no longer exists.
`docs/event-producer-consumer.*` is generated; regenerated after the merge.
`session.history` built its projection baseline in `historyStateFor` and only
then awaited `presenterScopeFor`, which is what ensures the recorded preset's
standing mount. The unit table is process-wide while the units themselves are
registered by preset rows, so on a first cold read the baseline was serialized
before `todos` (and every other preset-owned key) existed, and every later read
served a complete one. Until this branch, a web lane happened to mount a preset
in `beforeAll` for unrelated reasons, which hid the ordering.
The baseline is now a thunk the handler reads after the scope resolves. Also
covers the roster-less early return of the unjoined-agent warning, which the
per-file coverage gate reported uncovered.
The vendored Cordis rescope (`cordis` -> `@deepseek-ai/cordis`,
`@cordisjs/plugin-*` -> `@deepseek-ai/cordis-plugin-*`) collided with this
branch's two added edges. Resolved by taking master's package names and
keeping this branch's additions: `@deepseek-ai/dsh-agent` and
`@deepseek-ai/dsh-system-prompt` peers, the `scopeChainOf` import, and the
`agent/created` type-only edge.
`docs/event-producer-consumer.*` is generated; regenerated after the merge
rather than hand-resolved.
Moving the agent plane behind presets left two readers on the wrong side of
the host/agent line.
`dsh-token-meter` was disabled on the host and mounted inside each preset's
`compaction` realm, but its three projection units register into the
process-wide `sessionProjections` table. A unit registered from one preset
answers for every session, so whether a `minimal` session showed a context
meter depended on whether some other session had mounted `standard` since
boot, and a process that only ever ran `minimal` showed none. The meter takes
no configuration, keys every fold by Session, and registers no tool or prompt
section, so it returns to the host composition and leaves the presets'
`isolate` map; the realm and `compact-basic` stay, because what a preset
chooses is whether its agent compacts, not whether its tokens are counted.
Nothing named an agent that joined no preset. The join is a scope-parent link,
and without it the tools, prompt-section, and skill views resolve the empty
global layer: the agent publishes, the turn runs, and the model receives
nothing. `AgentPresets` now logs one warning per such agent while a roster is
configured, and the invariant companion fails outright — at
`system-prompt/assemble` rather than at publication, because an unjoined agent
is legal until it addresses a model and `recompose` binds exactly such an
agent. The warning stays advisory: a synchronous `agent/created` throw vetoes
publication, and the ACP bridge, SDK server, and headless bundle all create an
unjoined agent today.
Three limits are recorded rather than fixed: projection key presence is not a
per-session capability signal, a superseded standing generation is never
reclaimed, and a `cordis_mount` temporary plugin belongs to the composition
rather than the session that mounted it.
Fixes#2203
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.
Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.
The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.
Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
`session.create` also adopts an already-live session, and the preceding
commit newly allows adopting one under the preset it switched to while
blank. Its response still echoed `header.agentPreset`, so that adoption
answered with the preset the session had just left — contradicting the
request it had accepted and the row `session.list` serves for the same
session from `resolveSessionPreset()`. The echo now resolves the same way.
The `assertPresetUnchanged` parameter doc said `existing` was the preset
the session was created under; both callers now pass what it runs.
`composeFrom()` was documented as "infallible" and "cannot fail" beside two
`@throws`. It has no composition failure mode — no roster read, no mount, no
file — but it does reject a caller error, and the wording now says which.
The package-level "switched preset" test re-linked to the same preset id,
so it could not tell reading the parent's live scope chain from reading its
creation header. A second fixture preset makes the switch real.
The Web browser lane's subagent goldens gain the preset badge a child now
shows, which is the visible consequence of recording its composition. That
lane runs only under DSH_EXAMPLE_MODE=lib and was missed before.
The Agent Note records two limits found in review: a cold-resumed
continuable child joins its parent's current composition rather than the one
its header names, and `toolFilter` does not constrain a joined child. The
latter is a regression from the agent-plane move rather than anything this
change introduces — with the same tools in the global layer the filter
applies normally — and is tracked in #2185.
Refs #2185
The windows-native job has been red since #1990 put the sandbox-windows-acl sources into the Windows 100%-per-file denominator without tests carrying them, and #1543 dropped the authoring.ts V8 ignore for the POSIX-only owner-execute branch. Non-blocking at merge time, the red state has propagated to every later pull request.
Cover every in-process ACL-sandbox failure branch with stub-based failure-path suites (ffi/acl/token/spawn/index), following the package's existing failure-paths pattern; the package now measures 100% per file under the Windows denominator. Exclude only the runner entry from the win32 denominator: it executes exclusively as a spawned child outside the instrumented run, and its behavior is pinned end-to-end by the runner suite. Restore the authoring.ts narrow V8 ignore and add one for the dispose token guard whose absent-token arm is lifecycle-unreachable. Update the dual-lane Agent Note with the denominator composition.
Tool and prompt-section visibility is inherited along dsh-scope's parent
chain, and an agent's scope key is minted with no parent. Per-session agent
presets moved every model-facing row onto the agent plane and made
AgentPresets.mount() the one thing that binds that link, from the api-proxy's
session create, resume, and fork paths. The two in-process subagent drivers
installed only the per-child persona and tool filter, so a child's scope chain
had length one and its registry view resolved the global layer alone — which
is empty wherever a preset roster is composed. One-shot children reached the
model with no tools, continuable ones with only the host-plane `report`, and
neither carried its parent's persona, workspace context, or skill catalog.
AgentPresets.composeFrom() joins one agent to the standing composition another
already runs on. It is a bind, not a mount: the child gets its parent's exact
generation, so a composition edited since the parent started cannot fork it
onto another one, and it is synchronous, which is what lets a child creation
window use it. applyChildComposition() now takes the parent and performs the
join first, making a child composed without it unrepresentable at the call
sites. childSessionMeta() records the joined id so a cold read rebuilds the
composition the child actually ran under.
The audit that followed found two api-proxy readers on the wrong authority:
presenterScopeFor() and the live-agent branch of assertPresetUnchanged() both
read header.agentPreset, which goes stale the moment a blank session switches
preset. A switched session's cold transcript resolved presenters in the older
composition's layer and silently degraded to generic cards, and the gateway
refused to adopt a live session under the preset it actually runs while
accepting the one it left. Both now resolve through resolveSessionPreset(),
matching the resume branch fifteen lines above. The owning architecture Agent
Note carried the stale claim that the header records what a session runs; it
is corrected to name the header/log pair and its three readers.
Fixes#2165