Files
deepseek-harness/packages/boot/app-boot
Tianyi Cui d801f262d8 fix(python-sdk): resolve packaged proxies from real module entries
The packaged dsh launcher must expose installation modules to profile-local plugins without writing symlinks into pkg's virtual filesystem. The first review fix selected ESM exports correctly in ordinary Node, but real carrier execution exposed package metadata and VFS behavior that a synthetic tree did not cover: executable and declaration packages have no import entry, legacy main fields rely on Node probing, and pkg's Windows VFS prevents filesystem package-scope resolution from seeing exports such as zod/mini and @google/genai/web.

Resolve explicit exports directly from each installed manifest with the maintained resolve.exports package under Node import conditions. Publish only package-local candidate files that exist, reject escaping or malformed targets, preserve the package installation URL without realpath, and keep Node's legacy resolver only for exports-less packages. This avoids pkg filesystem package lookup entirely while retaining fail-loud behavior for broken runtime entries.

Add regression coverage for import-only, nested, symlinked, zod-style, and genai-style condition maps; unavailable and types-only entries; invalid and escaping targets; executable/declaration packages; extensionless main; and legacy index fallback. profile.ts remains at 100% statements, branches, functions, and lines. Update the bilingual package and Agent Note contracts, replace the runtime dependency and generated notice, and regenerate the lockfile through pnpm.
2026-08-24 17:28:27 +08:00
..

@deepseek-ai/dsh-app-boot

English | 中文

Shared Loader boot glue for dsh profiles, including the CLI packaged by the Python runtime wheel. The product launcher owns profile composition and process lifecycle. Direct-config helpers serve lower-level embedders and tests; they do not define another supported application entrypoint.

Export Role
resolveConfigPath(path, snapshotMode, cwd?) Absolute config path; snapshotMode === 'replay' swaps a cordis.yml/.yaml basename for its sibling cordis.snapshot.yml
loadEnv(binName, dir?, warn?) Load the gitignored .env (Node process.loadEnvFile); absent file is fine, an unloadable one warns a single labelled line (default: stderr)
loadLayeredEnv(binName, cwd?, warn?) Build the product CLI's frozen inherited > project .env > user .env snapshot, reject bootstrap-only file variables, and materialize accepted file values without replacing inherited ones
installFailLoud(binName, proc?, release?) Turn an unhandled boot or later Loader rejection into one labelled stderr line + exit(1); the optional release teardown is awaited between the two (bounded by FAIL_LOUD_RELEASE_TIMEOUT_MS) so a terminal-owning surface restores the terminal before exit; returns the uninstaller
FAIL_LOUD_RELEASE_TIMEOUT_MS How long installFailLoud waits for its release hook; a wedged disposer delays the fatal exit, never cancels it
assertEntriesLoaded(ctx, binName) Throw when a settled tree holds an enabled entry with no fiber, reporting every unresolved plugin name as a Cordis startup failure
assertEntriesActivated(ctx, binName) Include the assertEntriesLoaded check, then await every enabled entry after the Loader settles; throw with each failed plugin's original stack or each pending plugin's unresolved services
loadOptionalPatches(binName, file) Parse an optional patch-list file (a profile's cordis.patch.yml) — a top-level YAML array of include PatchOptions (id-targeted config overrides, insert lists, !!js allowed); absent file → undefined, an unreadable/unparsable/non-array file throws
loadOverlayPatches(binName, file) Parse a required top-level YAML array containing the same include PatchOptions entries described above; relative plugin names in inserted rows resolve beside this file, while a patch name used to assert an existing row stays literal; a missing file also throws because the caller named it
mountRootInclude(ctx, absoluteConfigPath, patches?, bareModuleBaseUrl?) Register the statically imported cordis:include and cordis:group builtins, mount the include, and retain the exact root entry used by user patch-layer HMR; an optional module base anchors bare package names to the installed host while relative names stay config-relative
watchUserPatches(ctx, options) Register the named patch file with the existing Cordis HMR service; each add/change/removal transactionally recomposes the full patch list through the caller's compose closure (app-owned layers around the current user layer) and returns an async disposer
resolveProfileDir / initProfile / loadProfile / readProfileManifest / writeProfileManifest / resolveBundleDir / composeEntries / healProfilesModuleFallback / PROFILE_TEMPLATES / DEFAULT_PROFILE_BUNDLES / DEFAULT_PROFILE_PATCH_RELOAD / PROFILES_DIR / PROFILE_PATCH_FILENAME Profile machinery and patch-file lifecycle (see Profiles)
boot(binName, absoluteConfigPath, patches?, prepare?, bareModuleBaseUrl?) Create the root context, expose dshHomePath(...segments) to Loader !!js config expressions, install Loader, run optional host preparation before config-tree entries mount (prepare may use Loader and provide launcher-owned context slots), then mount and await the include tree, assert entries loaded and activated, and return the root context — or dispose the partial context and reject a labelled error; the optional module base has the same resolution semantics as mountRootInclude
renderConfigDump(binName, absoluteConfigPath, layers, warn?) Compose the base config and labeled overlay layers offline with the include's own parser and patch algorithm (entryListSchema/applyEntryPatches), so the result equals what boot() mounts, and render YAML with !!js expressions verbatim; each run of rows that shares one source file and the same patch layers is preceded by a # == comment naming that file and those layers, keeping the output one loadable document; a patch matching no row goes to warn with its layer label (default: one stderr line), and read, parse, or field validation failures throw
addHarnessSourceSection(ctx, sourceRoot) Add a global harness:source prompt section (ordered just after the harness identity, before the persona) telling the agent the on-disk path to the DSH implementation checkout while warning it not to infer the current working directory from that path and to use pwd instead; a no-op returning undefined when the booted tree has no systemPrompt service. The section is registered against that service's fiber, so a dev HMR reload of the system prompt drops it until the next boot
HARNESS_SOURCE_SECTION The 'harness:source' section name addHarnessSourceSection registers under

Loader settlement rejects import and lifecycle failures with the failing entry and stage; boot() disposes the partial context and wraps that failure with the bin name. Entries settlement leaves behind are audited separately: assertEntriesLoaded turns an enabled fiber-less entry into a rejection naming every unresolved plugin, and assertEntriesActivated awaits each failed fiber to include its original stack in the startup rejection and names each pending entry's unresolved services. Before throwing, the audit marks those exact rejection reasons through one process checkpoint so installFailLoud coalesces Loader's duplicate notification while every unrelated unhandled rejection remains fatal.

The Loader mounts entries concurrently, so a surface can already own the terminal when something else fails: exiting without the tree's own teardown would leave raw mode, bracketed paste, and the keyboard protocol set on the user's shell, and an in-flight terminal query's reply would land as literal text at the next prompt. A config-tree failure settles through boot(), whose disposal of the partial context runs the surface's own shutdown before the labelled rejection. For the rejections boot() cannot see — a plugin's detached async work rejecting during or after mounting — a terminal-owning bin passes release to dispose the tree before the exit commits; dsh captures the root context in boot()'s prepare hook rather than from its return value so the hook covers the whole mounting window. While a release is in flight the handler stays installed and latched: the first rejection is the reported one, and later rejections (teardown's own included) are swallowed rather than becoming uncaught and killing the process mid-teardown.

cordis:group is registered beside cordis:include so a composition can give one isolate realm to a provider and its consumers together. Both load through the ambient module pipeline rather than the included tree's own specifier resolution, which is what lets a composition outside this workspace — an agent preset under the Harness home — use a group row at all.

Bare plugin specifiers in a config (@deepseek-ai/dsh-*, npm packages) resolve through the Cordis Loader's internal module loader. They resolve from the config directory by default; a closed runtime passes bareModuleBaseUrl to boot or mountRootInclude so its installed package tree remains authoritative even when the config lives inside another Node project. Relative specifiers always resolve against the config directory. Repository bins install Loader's optional node-addon-require-builtin peer; external callers must supply it or install plugins where plain Node import resolution can find them. The built dsh-app-boot artifact embeds the statically mounted Include implementation while leaving Loader external, so the include tree and host bind to one Loader peer. The pnpm dsh source path additionally maps manifest-declared workspace packages to their TypeScript source; its configuration gate requires every shipped raw/Web bare plugin to appear in the resolver manifest's dependencies.

This package carries no loader hooks and no dev-mode surface. The dsh app owns its Node source-launch hook and consumes these helpers for the boot sequence; built consumers continue to use plain Node package resolution.

Profiles

A profile is a directory under $DSH_HOME/profiles/<name> (the Harness home resolves through resolveDshHome: $DSH_HOME, else ~/.dsh) holding a package.json — out-of-tree plugin dependencies plus the profile manifest dsh.profile with its ordered bundles layer list and patchReload: live | startup — and the user's own cordis.patch.yml. live watches the profile and home-level patch files after boot; startup applies every layer once. A missing value keeps the historical live default for custom profiles. A bundle is an npm package whose manifest declares "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }; loadProfile resolves each dsh.profile.bundles name two-anchored (the dsh installation first, then the profile directory) and fails loud on a listed package without a bundle declaration. composeEntries applies patch layers over an empty entry list through the include's own applyEntryPatches, so composition, flag derivation, and config dumps cannot drift from what boots. healProfilesModuleFallback maintains the flat $DSH_HOME/profiles/node_modules directory under a cross-process writer lock. Plain Node writes one symlink per package in the installation dependency closure; a pkg executable resolves available explicit exports directly from each installed manifest with Node ESM import conditions and writes real proxy packages that re-export virtual module URLs, because an operating-system symlink cannot enter pkg's /snapshot tree. Export targets absent from an installed package remain unavailable without blocking its other exports; malformed export maps fail startup. An executable-only or declaration-only package with no module entry produces no proxy. The lock prevents concurrent launchers from observing partial proxies, and either carrier replaces the other carrier's managed entry. Both forms let profile plugins resolve installation packages through Node's ordinary parent walk and preserve one module instance for external plugin peers. PROFILE_TEMPLATES auto-initializes web with live reload and headless/sdk/acp with startup-only patches; other names fail loud until initProfile creates them through dsh plugin. loadProfile normalizes an exact installation-owned bundle tuple and a missing reload choice to its shipped template while preserving every explicit reload choice and every other manifest field; any extra, missing, or reordered bundle makes the list user-owned and leaves it unchanged.

User-level machine-local preferences also live in the Harness home:

  • .env — the product CLI's ordinary environment layers: the invoking directory's file outranks the Harness-home file, and both sit below the inherited environment. loadLayeredEnv snapshots each value's source, rejects bootstrap-only file variables case-insensitively, and materializes accepted values into process.env for Loader expressions and third-party libraries. Managed credentials live separately in .credentials.yaml; a credential left in either .env remains a lower-priority fallback.
  • cordis.patch.yml (home level) and profiles/<name>/cordis.patch.yml — the user patch layers, applied after every bundle layer (per-profile first, then the home-level file, which therefore outranks it): an id-targeted patch replaces the named entry's whole config (restate unchanged fields), insert adds entries, and !!js expressions interpolate at mount. A patch naming an entry id absent from the composed tree is a stderr warning. An empty or comments-only file throws (it parses to nothing, not to a list); disable the layer with [].

Every patchReload: live profile keeps both user patch files live through watchUserPatches. The watcher targets the exact path even when the file or immediate parent does not exist, serializes bursts, and recomposes the user patches inside the caller's layer order (bundle layers below, overlays above). A rejected read, parse, or Loader candidate leaves the last good tree running and the HMR service broadcasts hmr/config-update-failed(filename, Error) after logging it; observer failures are contained. Disposing the context closes the watcher and drains an active refresh. A startup profile installs neither these watchers nor the launcher's watch-only HMR fallback.

Model Experience

Indirectly, through the plugin tree it loads, which determines the prompts, schemas, messages, and model adapter in the resulting application; the one export that contributes model-visible text, addHarnessSourceSection, does so only when a consumer calls it after boot.

KV Cache effect

No direct invalidation from boot(); a consumer that calls addHarnessSourceSection places one short line near the system prompt's head, before per-request content, so it does not invalidate the cache across turns, and any other request-prefix change is owned by the named consumer.

Known Limitations and Deferred Work

  • Bare package specifiers depend on Loader internals — production bins need Loader's optional native helper; an in-process caller without it must use resolvable relative/file specifiers or provide its own module-resolution hook.
  • Snapshot replay swapping is basename-specific — only a config ending in cordis.yml or cordis.yaml maps to the sibling cordis.snapshot.yml; custom config names require caller-managed selection.
  • Environment discovery is launch-scopedloadLayeredEnv reads only the invocation directory and Harness home once; it does not search parents or follow a workspace selected later. loadEnv remains the one-directory helper for non-product bins.
  • A user patch replaces the whole matched config — an id-targeted patch does not deep-merge, so a profile override restates the bundle fields it keeps.