Record sdk-minimal as the narrow repository-owned exception to base-first profile composition: callers still launch only dsh and cannot provide an arbitrary Cordis tree, while the shipped bundle may own a complete explicit roster. Cross-link the launcher, profile-bundle, Python-runtime, minimal-agent, snapshot, and telemetry decisions; the supersession audit keeps each older note active because its remaining rationale is independent.
Update the CLI, architecture, Python tutorial/reference, example, runtime-wheel reference, and bundle documentation. The docs distinguish the full sdk profile from sdk-minimal, explain explicit-home/plugin/patch customization, state the minimal permission and persistence choices, and retain the separately packaged web profile and frontend assets for direct dsh use.
Correct dsh-base descriptions to cover base-backed profiles, make SDK startup configuration visible in the generated config catalog, add sdk-minimal to the module graph, and regenerate the base-composition graph. English and Chinese pairs are re-recorded at the exact reviewed contents.
Resolve packaged profile proxies with Node ESM import conditions from each package installation, and fail loud when an explicit runtime export or legacy main entry is missing. Serialize the shared profile fallback under the existing cross-process writer lock so concurrent dsh processes cannot observe partial proxies; either carrier now replaces the other carrier’s managed entry without manual cleanup.
Give Python initialize its own 10-second default bound and name the selected profile in timeout diagnostics, while leaving ordinary agent turns unbounded by default. Package the dynamically resolved web frontend and skill-badge assets so the runtime wheel’s normal dsh profiles do not depend on pkg static-discovery accidents.
Rewrite the root launch rule and every active stale SDK-runtime note to the shipped dsh profile architecture in both languages. Focused tests prove import-only and transitive package exports, lock contention, cross-carrier transitions, missing-entry failures, asset inventory, and bounded initialization.
Move the shared module-reload policy into dsh-base by inserting its HMR row disabled, then remove the redundant disabled overrides from Web, headless, SDK, and ACP. No shipped profile enables server module reload; live profile patch watching continues through the launcher-owned config-only fallback, and browser client HMR remains a separate mechanism.
A later profile layer can opt into source-module reload explicitly with disabled: false while retaining the base root configuration. Composition tests cover every shipped mode and the explicit enable path, and the bundle references plus launcher Agent Notes document the resulting ownership and safety rationale.
Introduce @deepseek-ai/dsh-sdk-app as the thin application layer for the built-in sdk profile. The bundle contributes the JSON-RPC server and startup-only profile metadata, while dsh-base continues to own the shared agent, provider, persistence, and tool composition.
Publish ctx.appReady from the launcher only after the Loader tree and launcher-owned setup succeed. The stdio lifetime binding leaves stdin unread until the protocol transport claims it and defers EOF exit 0 until readiness commits, so early protocol frames remain buffered and a racing startup failure remains the nonzero process outcome. Fiber disposal cancels both pending lifecycle listeners.
Register the bundle in the CLI resolver closure, generated configuration catalog, workspace graph, and built-bin smoke. Startup tests prove that base plus sdk-app exposes the SDK server without taking ownership of shared runtime plugins; focused and built-bin regressions cover early input, EOF readiness, and startup-error precedence.
Add a patchReload field to built-in profile metadata and carry it through CLI profile resolution into app boot. Live profiles install the existing patch watcher; startup profiles freeze every layer after boot and apply later edits only on the next launch. Missing or invalid metadata fails before the plugin tree starts.
The implementation keeps reload policy with the profile that owns it instead of inferring behavior from an entrypoint. Unit tests cover metadata validation and both lifecycle modes, while the CLI and app-boot references document which built-ins are live versus startup.
Review asked why the launcher special-cases one plugin's row. It no
longer does: the four shipped compositions move into the package
(presets/, in files), dsh-agent-presets resolves its own shipped root
and prepends it before configured roots (includeShippedRoot, default
true, opt-out for bare-machinery embedders), and the per-composition
derived patch, its spec, and the dump layer are deleted — profile-boot
and dump-config return to plain layer stacking. The always-load
guarantee now rides the schema default instead of patch ordering, so a
whole-config replacement keeps the shipped set and the squash, reload
freeze, and dump divergence stop being possible.
Gate globs, the web scaffold, and both preset browser lanes drop their
hand-fed shipped roots; the roster e2e keeps asserting configured roots
beside the shipped four against the built lib.
Fixes#2863.
Review follow-ups: enumerate the derived shipped agent-preset root in
apps/cli README/reference dumps and the profile-boot module JSDoc
(bilingual pairs re-recorded), correct the stale AppCLIEntry/distIndex
analogy in the web scaffold and the shipped-root cross-reference in the
web preset e2e, drop the write-only ComposedProfile.rows field, and make
the Agent Note describe the dump path as sharing the derivation rather
than the builder.
The boot-time agent-presets overlay replaced the composed roots with the
shipped root alone, so roots configured in a profile's cordis.patch.yml
vanished from the roster (externally reported in
deepseek-ai/deepseek-harness#3636). The overlay also froze the row's
boot-time config above every live reload and never reached the config
dump, which therefore showed roots the boot dropped.
Derive the roster patch from the current layers instead: prepend the
shipped root (system trust, wins duplicate ids) to configured roots,
share one builder across boot, live user-layer reloads, and
--dump-config, and fail loud on a roots value the launcher cannot
statically rewrite.
Fixes#2863.
`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.
A fast one-shot can request exit through ctx.appExit while the launcher
still awaits its watcher setup; the failure guard now also swallows the
resulting rejections when the tree is no longer live, not only on
signal shutdown. Sync the app-boot README and headless bundle comment
with unconditional patch watching, and bring the two Agent Notes still
asserting the --dev row append and the headlessIo slot current.
The web runtime creates its dev-mode client-hmr row in the root tree
after Loader settlement with plain loader.create, deleting the vendored
Entry.enableRuntime state machine and dsh-cmdline's enableRow export.
Include declares the existing EntryGroup.key tree-carrier marker instead
of the EntryConfigResolver protocol (its own path stays literal; nothing
used a dynamic path). The launcher recognizes no app row: SIGTERM exits
0 on every surface, every boot watches its user patch layers, and the
headless runner exits through ctx.appExit, deleting ctx.headlessIo. Also
restores the vendor README rescope entry to the position the
rescope-vendor exact-edit anchor requires, fixing the master hygiene
regression.
Resolve three conflicts:
- packages/bundle/base/tests/base.spec.ts: keep both assertion sets — this
branch's telemetry-otel mode expression and master's subagent rows plus
manifest dependencies.
- packages/session/session-telemetry-otel/tests/otel.spec.ts: keep master's
per-mode sharing disclosure test, but select FULL explicitly instead of
relying on an omitted mode, because the default is now DISABLED; the
default-construction test keeps asserting disabled delivery and gains a
case pinning that an omitted mode reports 'disabled' sharing.
- scripts/snapshots/translation-prompt-v4/request-response.expected.json:
re-recorded from the merged READMEs rather than hand-merging the embedded
document text.
Entry \disabled\ interpolation makes the launcher's separate platform layer
unnecessary: the base bundle's cordis.patch.yml now gates both shell stacks
on its own rows — bash-sandbox/tool-bash disable on win32, and their twins
pwsh-sandbox/tool-pwsh mount only there with the inverted expression — so
exactly one shell stack mounts per host from one shared patch file.
windows.cordis.patch.yml and the launcher's windows-shell.ts injection (boot,
live recomposition, config dumps) are deleted, with the workspace-constraints
entry and the dsh-base exports/files entries following. The windows-shell spec
pins the effective per-platform roster through the real bundle layers, and
base.spec pins the four symmetric gates. The superseded active notes are
updated and cross-linked; the loader note records the fold itself.
Launcher flags come first and end at the first token dsh does not
recognize; everything after reaches the booted app verbatim, so
dsh --profile tui --resume <id> works with no launcher change and
dsh --profile web --help prints the web app's help. A bare dsh -h, which
has no app to hand the flag to, still prints the launcher's own.
src/web.ts is deleted: the Web flag family, its LAN-trust sampling, and the
one-shot task positional now live in their bundles, and runProfile no
longer knows any row id. What the startup row decides comes back as a
launcher-owned patch layer above every layer a user can edit, so a live
config edit recomposes the tree without resetting a served port.
dsh web and dsh --profile web finally boot through one path, which also
gives --profile web the harness-source prompt section that only the alias
used to add.
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.
The roots are an assembly fact (the shipped set beside this app's config,
the user's own under $DSH_HOME) but only `dsh web` patched them in, so the
merged `dsh run` booted the roster with no roots and failed resolving
`standard`. The shared profile boot now owns the patch for every launcher;
the one-shot transcript header consequently records its composing preset,
and master's interrupt_agent tool joins the standard composition's exact
catalog.
- apps/cli/reference/README: state the win32 permission/sandbox/approval
degradation so the workspace-write promise no longer misleads Windows users
- bundle README + agent note: give the complete bash-restore recipe (disable
pwsh-local/tool-pwsh and re-enable bash-sandbox/tool-bash), since both
executors register the same bash service and an incomplete recipe fails
loud at load
- windows.cordis.patch.yml: header comment notes the recipe and that the
ui-permission row belongs to dsh-web-app (base-only profiles get a
harmless no-match warning)
- profile-boot.ts: rewrap composeProfile JSDoc
- re-record i18n hashes for the touched bilingual pairs
win32 hosts booting a shipped profile now get pwsh-local as the ctx.bash
executor and tool-pwsh as the shell tool through the base bundle's new
windows.cordis.patch.yml platform layer, injected by the launcher between
the bundle layers and the user layers on win32. bash-sandbox, tool-bash,
permission, and ui-permission are disabled there: the POSIX-only executor
cannot run on Windows, and dsh-permission requires a confining executor.
Overriding the default is a composition decision through the user's
cordis.patch.yml; there is no environment override channel.
apps/cli and dsh-base re-declare dsh-pwsh-local/dsh-tool-pwsh so the
profile module fallback links them for cold starts (the profiles rework
had dropped them from the CLI closure).
Promotes the windows-pwsh-default Agent Note from proposed to implemented
and documents the platform layer in the base bundle README.
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).
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.
- composeProfile keeps layers as bundle/user/overlay+flags segments instead
of one flat list later re-sliced by index arithmetic; the row index drops
the group-walk (profile trees are flat patch compositions) and the double
composition.
- The config dump anchors on the profile's real empty root (written by the
shared prepareProfile) instead of materializing a temp file, so dump and
boot compose over the identical base by construction.
- dsh-base drops its patchPath export: the dsh.patch manifest field is the
one contract; the package carries no runtime API.
- packageDirFromAnchor is paths-probe only (the require.resolve fast path
duplicated the probe's outcome); basename() replaces hand-rolled path
splitting; verify-cordis-config stops re-reading bundle manifests in-loop.
- 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.