The web-app bundle gained dsh-app-boot on master beside this branch's
dependency list; both rows stand, and verify-cordis-config confirms every
patch row still resolves from that manifest.
Merging master's rename of the client manifest field left this package on the
old `dshClient` name. The row still composed and its empty node half still
activated, but the browser roster scan never matched it, so the whole settings
section vanished with no error anywhere.
verify-cordis-config now requires a packages/client package's "./client"
export and its dsh.client declaration to agree in both directions; the
composition file cannot tell a surface plugin from a Host plugin, so the
manifests are where this is checkable. The check is scoped to that group
because a Host package's "./client" export is the typed wire face its browser
consumers import, not a plugin the roster serves.
Replaces the patch round trip. An app's entrypoint resolves the command
line into a service, and the rows it configures read that service from
their own config — port: !!js ctx.get('webStartup')?.port ?? 3080 — so the
resolved value beats the value written beside it and nothing is written
back into a row or handed to the launcher.
A bundle names the entrypoint row in its manifest (dsh.bundle.entrypoint),
which is what lets the boot mount in two passes: entrypoints alone, then
the whole composition. That ordering is required, not cosmetic — a row's
config expressions are evaluated when the include applies the row, and a
strict ctx.get only answers for a service whose providing fiber is already
active.
What this removes: ctx.appPatches and the launcher-owned patch layer, the
disable/re-enable recycle and its in-flight-mount barrier, overrideConfig,
and the reload hazard they existed for. A live config edit now re-applies
the second pass against services that are still up, so a served port
survives by construction.
What it adds: ctx.appReady, because Loader settlement no longer means the
app is up — a row mounted in the second pass can observe a settled tree
while that pass is still running, or already rolling back. The web URL line
waits for it, so a boot that fails in the second pass announces nothing.
The Agent Note keeps the three vendored-Loader facts the mechanism turns on
— a row's config is resolved and validated when its fiber is created, while
it is still waiting; an inject update loses the plugin's static injections;
a row cannot be inserted from inside a mounting plugin — with the
alternatives they ruled out.
Resolves the vendored-package rescope (cordis -> @deepseek-ai/cordis,
schemastery -> @deepseek-ai/schemastery) against this branch's settings
namespaces: the bash capability namespace, its two executors' section
installs, and the new plugin-config client package all move to the scoped
specifiers.
Controls committed on blur, which turned leaving a field into a durable,
revision-fenced document write the user could neither preview nor undo, and
silently discarded a draft the field did not accept.
A card's form now owns the staged text every control renders, and Save is the
only point where drafts become writes. Reset stages the composed default the
same way; an invalid draft blocks the save with its reason instead of being
dropped; Discard drops the drafts; a collapsed card marks that it holds some.
The Host stays the only authority on whether a value was accepted, so the save
reads the section back and keeps the drafts of a save that did not land.
Every harness package declares cordis as a peer dependency, so publishing the
harness publishes the vendored framework layer too; under the upstream names
that publication would squat them on the registry.
scripts/rescope-vendor.ts owns the rename: the nine-package mapping, a
delimited-token rule that leaves cordis.yml, the Loader's cordis: builtins and
vendor directory names alone, per-file exemptions where a name is a directory
or an upstream runtime identifier, and the exact edits for sites a token rule
cannot express — dot-notation lookups, unquoted manifest keys, a regex literal
whose failure would make every Context-merge scan silently find nothing, the
vendored-manifest table, and the contracts that told readers vendored packages
keep their upstream names.
Markdown follows the rename inside every fence, because a fence is code a
reader copies or configuration they mount, and in `docs/` prose as well, where
a sentence quoting a name teaches something this repository no longer resolves.
Prose elsewhere records what was true when it was written, and the same
spelling can mean something else: the Python SDK's `cordis` option, or the
unvendored `@cordisjs/plugin-http`. `docs/rescope.md` states both names on
purpose and is exempt.
exactEditState() classifies every exact edit as pending, applied, or invalid.
An insertion keeps its anchor and a deletion keeps its remainder, so each side
counts the form that survives: a duplicated insertion, a half-applied
replacement, and a deletion whose remainder moved are all invalid. The run
classifies every edit before writing anything and aborts on the first invalid
one, so a disagreement between the mapping and the tree cannot leave a
half-rescoped checkout; each write re-reads its file, because two edits can
target one. rescope-vendor.spec.ts pins those rejections, and --check asserts
the whole post-state from the hygiene gate, so CI owns the invariant.
--reverse restores the upstream names, verified as a round trip: reverse, then
apply, reproduces this tree byte for byte.
docs/rescope.md is the consumer-facing reference: the old-name/new-name table
with each package's role, what the rename deliberately leaves alone, the sites
callers must change, and the commands to apply, verify, and revert. The Agent
Note carries the decision and its consequences.
The rename itself lands in the next commit, produced by running the script.
A restriction was compiled against the global tool layer alone: only
global-layer tools were tested against `admits()`, and every chain-layer
tool was overlaid unfiltered afterward. That read the exempt set as "the
global layer" when what it means is "what this scope registers itself" —
two descriptions of the same set only while every model-facing tool sat in
the host composition.
Moving those rows onto the agent plane separated them. A preset's tools are
an ANCESTOR contribution to a joined agent, so a subagent's `toolFilter`
stopped constraining anything it was given; and with the global layer empty
`restrict()` rejected every name it received as unknown, failing the child
outright. With the same tools in the global layer the filter still admits
and applies normally, which is what makes this a regression of the move
rather than a standing limitation.
`view()` now filters everything a scope inherits — the global layer and
every ancestor layer on its chain — and exempts only the layer the scope
owns. That exemption is load-bearing rather than incidental: the delegation
runtime registers a child's `report` and structured-output tools into the
child's own layer, and a filter naming the capabilities the child may use
must not strip the machinery it answers through. Tool order, and with it
prefix-cache reuse, is unchanged: inherited names keep their global-then-
ancestor position and own-layer names still come last.
The diagnostic said "unknown global tool" while listing what is really the
inherited surface; it now names the surface it checks and says why an
own-layer name is not restrictable.
Fixes#2185
Also folds the duplication the three controls had grown: the draft-and-commit
input both editable fields render is now one component, and the two sibling
executors' identical import surface is marked as the deliberate mirror their
READMEs already describe.
A leaked host TSX_TSCONFIG_PATH redirected the tsx gate scripts to a staging
checkout and masked the verify-public-repository-links rejection of the
internal issue URL; all tsx-driven gates re-verified clean with the variable
unset.