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.
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.
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.
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.
A composition is a file, but "edit it on the filesystem" is not a browser
affordance. The roster gains `read`/`write`/`remove` beside `select`, and
the browser gains a settings section over them: the presets as rows, one
composition open in a YAML editor at a time, and per-row default, duplicate,
and delete.
All four authoring methods are loopback-pinned. A composition names the
plugins a session runs, so reading one is reconnaissance, writing one is
arbitrary capability, and selecting one can move a session onto a preset
that edits the live runtime. `agentPreset.list` deliberately stays ordinary
and now reports `authorable`, so a surface knows whether creating is
possible at all rather than offering a button whose save always fails.
Authoring starts by duplicating: a shipped preset opens read-only because
the deployment's copy is what a broken local one is compared against. Ids
are contained before they become directory names, and the text is parsed
with the loader's own schema, so a save cannot leave a file no session
could load.
Fixes a defect the real-composition test found: a preset written under the
user's home could never mount, because the loader resolves a row against the
composition's own directory and Node's `node_modules` walk from there never
reaches the installed harness. The mount now records the host base and sends
bare specifiers there, leaving relative paths resolving from the preset.
Also closes the coverage the earlier surfaces in this stack shipped without —
the General row, the composer seat, and the plugin halves now have tests.
The creation header names the preset a session STARTED with and is frozen,
which is correct — it is a creation fact. Switching is legal only while a
session is blank, and that looked like enough: no history exists yet.
It is not, because the switch's effect outlives the blank window. The user
switches, then sends the first message; every turn from there runs under the
new composition while the header still names the old one. The session is
then locked around a misrecorded preset, and resume reads the header to
rebuild it — composing one preset's tools over a history another produced,
which is exactly the replay the blank-only lock exists to prevent, reached
by another route. A picker showed `standard` for a session running
`core-web`.
A switch is now an `agent-preset/selected` event appended after the swap
commits, and `resolveSessionPreset()` (last selection, else the header) is
what every reconstruction reads: the summary, resume, the conflict guard,
and the fork introduced one layer down.
`config.default` becomes the composition base of an `agent-presets` settings
namespace, so the user document layers over the deployment's engineering
default and a person can change which preset new sessions get without a
restart.
The value is read per resolution rather than snapshotted: a hot-reloaded
document takes effect on the next session created, and every running session
stays on the preset it was composed from — which is the same rule the
session-header guard enforces from the other side.
`resolve()` read `config.default` directly, which would have made the whole
setting inert; it now goes through `defaultId` like every other caller.
The write-protection test is rewritten against a temp profile root. It was
passing vacuously: the un-overridden Loader REWRITES the composition it read —
stamping `disabled: true` onto the self-disposing row — so the committed
fixture had been mutated by the very run that proved the bug, and every later
run compared against the damaged file and passed. Building the preset in a
temp directory makes the assertion immune to its own failure mode, and it now
fails with a visible `+ disabled: true` when the override is removed.
Review follow-ups on this layer. The exported schema is
`AgentPresetSettingsSchema`, symmetric with the `AgentPresetSettings`
interface it resolves and self-describing at an import site. The `session.create`
JSDoc promised "the deployment's default preset" for an omitted `agentPreset`,
which this layer makes false — it now names the effective default. The
constructor records why it does not use `installSettingsSection`: that helper
re-judges what a consumer DERIVED across attach and detach, and nothing here is
derived. The provider-unload test disposes the fiber `ctx.plugin()` handed back
instead of reaching into `ctx.reflect.store`, and the write-protection wait says
why slack is the right shape for an absence assertion.
The real composition covers the layering too. `apps/cli` boots the shipped
`cordis.yml`, stores `agent-presets.default`, and asserts an unnamed session
composes from it — the package suite proves the layering against a hand-built
context, this proves the roster and the settings provider are wired to each
other. That test also pins the settings row at a temp file: it defaulted to
`$DSH_HOME/settings.yaml`, so a developer's own stored default decided the
outcome of a file whose whole point is that only the shipped root does.
The Agent Note records the per-resolution read and its correspondence with the
session header, and the vacuous-test finding above.
Records are pruned by observation rather than by a disposal hook, for the
reason the module already states: three different owners can tear a
subtree down, and a cleared `uid` is what they share. That leaves the
pruning to whoever reads — and the only production reader is the
invariant companion, whose package is a development composition a
shipped host never loads.
So a live host pruned nothing: every session ever composed left a record
retaining its whole disposed subtree, since the fiber holds its config
and that config is the key its EntryTree is stored under.
Prune on the mount path too. Every session takes it, which bounds the
set at one generation of dead records instead of one per session.
A preset is a directory holding one `agent.cordis.yml`. Mounting it under an
agent's scope context during `setup(agentCtx)` gives that one session its own
tools and prompt sections while every other live session keeps its own.
No registry gains a tier. `dsh-tools` and `dsh-system-prompt` already file
registrations into the calling context's scope layer, and entry contexts chain
to the context a subtree was plugged into, so a composition mounted under
`agent.ctx` is that agent's alone and unwinds with it.
The mount audits itself because a directly-plugged subtree is absent from
`ctx.loader.entries()` and no boot audit covers it. It rejects an unscoped
target, a row that never became usable, and a row that published a service into
the root service realm — that last one is process-global rather than
per-session, and its collision with the next session surfaces as an unhandled
rejection `setup` never observes, leaving a half-composed agent that looks
healthy. The package invariant re-checks that rule on every service
notification, since a row publishing from a timer would escape a one-shot audit.
Raises the `packages/README.md` word ceiling from 920 to 980: the group table
must enumerate every group, and the new `preset/` row is necessary content.
Design: .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md