Source-level environment and credential tests prove the individual loaders, but they do not prove that the published launcher runs them before Loader evaluates a shipped profile.
Start the built dsh binary with the shipped base bundle and a test-only LLM probe. Put the endpoint in $DSH_HOME/.env, put the bearer token only in $DSH_HOME/.credentials.yaml, remove inherited DeepSeek overrides, and assert the mock request received both without leaking the token. This covers launch order, profile composition, the adapter, and the credential seam without a real API.
The profile rework left references to the old entry modes behind. Renames
the user patch-layer API and its spec file (watchPersonalPatches ->
watchUserPatches, personal-config.spec.ts -> user-patches.spec.ts) and
retargets the prose that still named `config.yaml`, `--config`, raw-config
mode, and surface overlays: repository-plugin and mcp-memory READMEs, the
credentials-local anchor into app-boot, vendor manifest items 12-13, the
vendored include/hmr comments, and install.sh.
Restores the boot-failure guard the rework dropped with raw mode: the
built-bin case now boots `--profile web --patch <invalid>` and asserts the
settled diagnostic and exit 1, so the HMR initial-scan deadlock stays
covered; its orphaned raw fixture is renamed and the unused one deleted.
The superseded personal-config Agent Note and its superseding profile note
are now cross-linked.
A profile manifest and a bundle manifest are different kinds and shared one
flat `dsh` section: `dsh.plugins` listed bundles (not plugins) and `dsh.patch`
declared a bundle's layer. Each kind now names its role — a bundle declares
`dsh.bundle.patch`, a profile declares `dsh.profile.bundles` — so a
package.json states which role it plays and the list name matches its contents.
`DEFAULT_PROFILE_PLUGINS` becomes `DEFAULT_PROFILE_BUNDLES`, and
`DshManifestSection` splits into `DshBundleManifest`/`DshProfileManifest`.
Pre-release: no compatibility shim; turtle-ui moved with it (bd5ff10).
- Relative path specs (., ../plugin, file:/link: forms) anchor to the
invoking directory before forwarding: pnpm's cwd is the profile dir, so a
bare 'add .' from a plugin checkout used to self-link the profile (exit 0,
nothing installed). Bare paths stay bare and prefixed specs keep their
prefix, preserving pnpm's link-vs-copy semantics.
- dsh.plugins reconciles against the INSTALLED state on every successful
pnpm run, not the dependency diff: an update whose new version gains
dsh.patch activates the layer; a version that drops it (or a removal)
deactivates it. Template bundles are never touched.
- A failed pnpm run now names the profile directory, and a git-spec failure
explains pnpm >=10's prepare-script block with a pointer at the profile's
pnpm-workspace.yaml allowBuilds (turtle-ui's prepare-based git install is
the reference consumer); reference README documents all three.
The old $DSH_HOME/config.yaml personal overlay returns under the profile
scheme's filename: machine-local preferences that apply to every profile,
loaded after the profile's own cordis.patch.yml (so the home layer outranks
it) and before --patch overlays and flag patches. Both user layers are
hot-reloaded on long-lived surfaces and shown in --dump-config with their
own provenance labels; the built-bin e2e covers the home layer landing live.
- Clone patch lists per generation (boot + composeLive): the include pushes
insert rows by reference and mutates them in place, so a reused object
baked user overrides into bundle rows and removal could not revert; the
built-bin hot-reload e2e now asserts an override AND its removal reverting.
- The headless runner awaits Loader settlement before prompting (its inject
gate covers only apiProxy/httpServer) and abandons cleanly when the tree
died during the wait.
- healProfilesModuleFallback walks the app's full dependency+peer closure:
out-of-tree plugins import seam packages (dsh-compact, dsh-subprocess, ...)
that only implementations reach, and peers are how seams are declared.
- Profile init writes pnpm-workspace.yaml (nodeLinker: hoisted), not .npmrc
— pnpm >=10 reads settings from the workspace manifest.
- Web dumps reject boot-only flags instead of printing a tree that differs
from the same invocation's boot; --port validates at the flag;
--dump-default-config no longer parses the (possibly broken) user layer;
trustedHosts flag derivation merges over the composed value instead of
replacing it; web-runtime gains surfaceContext (headless disables the GUI
prompt/bash-vars the old -p never mounted); 'node_modules' is a reserved
profile name; plugin-warning names the recovery step; client AGENTS.md
registration surfaces point at the web-app bundle.
- Ship session-reference/tmux-context/tool-ask-user as app dependencies for
terminal front-door patch layers (turtle-ui), same stance as mcp-client.
A custom profile that mounts no telemetry-otel row exports nothing, so
DSH_TELEMETRY_DISABLED must not fail its boot (CI exports the switch
globally, which broke the lifecycle-fixture profile). The web-app dist
resolution test accepts the fail-loud unbuilt outcome the CI coverage lane
sees before any build, and the headless spec covers the idle-anchor and
pre-start skip branches under the per-file gate.
dsh --profile <name> replaces the fixed entry modes: --config and -p are
removed, --patch adds overlays over the composed profile, a positional task
selects one-shot mode (requires the headless-runner row), and dsh web stays as
the alias for --profile web carrying the Web flag family as patches. dsh
plugin --profile <name> forwards verbatim to pnpm in the profile directory,
initializes on first use, and reconciles the dsh.plugins layer list after
add/remove (patch-less packages warn and stay plain dependencies). Config
dumps and the keyless web e2e scaffold compose the same bundle layers over the
same empty root as the boot.
Master's transactional loader made the invalid-provider PTY case regress:
the HMR main watcher's initial scan refreshed the include mid-initial-apply,
the concurrent group updates stranded the include fiber, and once serialized
the failing apply's rollback deadlocked on HMR's refresh drain — dsh exited
13 with no diagnostic and the terminal stranded, the exact symptom this
branch fixes. Serialize every include child-tree mutation through one queue
and pass ignoreInitial to the HMR main watcher; the failing boot now settles
through boot()'s labelled rejection with the tree disposed and exit 1. The
PTY case asserts the settled diagnostic; the fail-loud release remains the
guard for rejections boot cannot see.
Conflicts: apps/cli/src/tui.ts (keep the release install over master's comment
rewording), packages/ui/app-boot/README* (master's new installFailLoud row
wording plus this branch's release and timeout rows).
The shipped-roster change made @deepseek-ai/dsh-tool-session-query a
default row of the shared base.cordis.yml, so the TUI and Web surfaces
put the five session-search tools in front of the model. That
contradicts the recorded opt-in stance for the model-facing session
query consumer; the ACP example remains the mounted reference.
Remove the row from the shared base, the now-dangling disabled patch in
the opt-in core-web profile, and the workspace dependency. The
ctx.sessionQuery index stays: the TUI's /resume and the Web content
search consume it directly. Both shipped-composition tests now pin the
20-tool catalog.
ds-review-bot round 1 on the DSH-home integration:
- generated wrappers now inject the services their manifest needs (skills/
tools beside loader), and loadPreparedRepository rejects a wrapper fiber
that settles anything but ACTIVE — a composition missing a required
service fails the repository transaction instead of committing an ACTIVE
row over a silently PENDING child (critical finding)
- the github: source ref segment excludes '#', so 'a#b' refs fail at the
config parser with the promised syntax instead of inside pnpm
- watchPersonalPatches re-reads the include's non-patch options per refresh
instead of a registration-time snapshot
- the TUI smoke's cache-seeded wrapper is produced by the real
prepareDshPlugin (cache LAYOUT stays a deliberate external pin)
- new Loader integration test drives a live repositories update through
entry.update: generation swap, old skills removed, failed candidate
rolled back to the previous generation
Merges master (default-workspace-write-ui, installer-adopt-checkout,
remove-scoped-bash, goal-clear-single-flight, frontend-plugin-loader,
install-interface-choice, …).
Conflict resolutions:
- apps/cli/tests/shipped-composition.e2e.ts: keep the fixed glob/grep
roster assertion; master's workspace-write composition now confines
tool-bash, so the escalation pair is pinned present (my earlier
absence pin is superseded) together with master's permission facts.
- even-out-shipped-tool-rosters note: both sides edited it; the merged
text keeps both sets of changes and the pair is re-recorded.