@deepseek-ai/dsh-app-boot 0.1.2-rc.1 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
5
- README.md: a9ed535c2662237229e0702dcf41eae4f93af5df
6
- README.zh.md: 4f7688f7db5060bb1bf00ca5d091ca4ad16466ea
5
+ README.md: 197ed81c1025f7211c3fb7a694547c52fb058ee8
6
+ README.zh.md: 5e465a86f5e624380710cc8c73d5356c649522e4
package/README.md CHANGED
@@ -45,15 +45,19 @@ With that entry point, success looks like a running app with every plugin active
45
45
  <a id="profiles"></a>
46
46
  ### Profiles
47
47
 
48
- A profile is how one dsh installation ships different app surfaces: `web`, `headless`, `acp`, `sdk`, and `sdk-minimal` start distinct compositions from the same launcher. A profile lives at `$DSH_HOME/profiles/<name>` and combines installable bundles, its own `cordis.patch.yml`, and `patchReload: live | startup`; omitted reload policy keeps the historical `live` default for custom profiles. The shipped `web` template uses live reload, while the other shipped templates apply patches only at startup. `sdk-minimal` names only its standalone bundle; the other templates retain base-plus-mode stacks. `dsh plugin` creates custom profiles, and a missing bundle or one without a patch declaration fails startup loudly.
48
+ Import profile and bundle declaration types from [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.md). App-boot owns profile loading, JSON validation, and resolved runtime data.
49
+
50
+ A profile is how one dsh installation ships different app surfaces: `web`, `headless`, `acp`, `sdk`, and `sdk-minimal` start distinct compositions from the same launcher. A profile lives at `$DSH_HOME/profiles/<name>` and combines installable bundles, its own `cordis.patch.yml`, and `patchReload: live | startup`; omitted reload policy keeps the historical `live` default for custom profiles. The shipped `web` template uses live reload, while the other shipped templates apply patches only at startup. `sdk-minimal` names only its standalone bundle; the other templates retain base-plus-mode stacks. `dsh --profile <name> --from-default-profile <template>` creates a custom profile at a new non-shipped name from one shipped template, while `dsh plugin` initializes a base-backed profile and manages its installed bundles. A missing bundle or one without a patch declaration fails startup loudly. Application-owned npm projects, such as Electron's reserved Desktop profile, use `loadProfileDirectory` to load an already initialized directory without exposing it through CLI profile lookup.
49
51
 
50
52
  Your machine-local preferences also live in the Harness home:
51
53
 
52
- - **`.env`** — your ordinary environment layers: the invoking directory's file outranks the Harness-home file, and both sit below the inherited environment. Variables that decide how the process starts (`PATH`, proxies, `DSH_*`, `XDG_*` and similar) are rejected from files: export them instead. For a non-product bin that just wants one directory's `.env`, a missing file is fine and an unloadable one prints one labelled warning line.
54
+ - **`.env`** — your ordinary environment layers: the invoking directory's file outranks the Harness-home file, and both sit below the inherited environment. Variables that decide how the process starts (`PATH`, `DSH_*`, `XDG_*` and similar) are rejected from files: export them instead. The four proxy names (`HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`) are accepted from the Harness-home file only, never from the invoking directory's, which arrives with a clone. For a non-product bin that just wants one directory's `.env`, a missing file is fine and an unloadable one prints one labelled warning line.
53
55
  - **`cordis.patch.yml`** — your tweak layer, applied after every bundle layer (per-profile first, then the home-level file, which therefore outranks it): replace one entry's whole config (restating the fields you keep), insert new entries, or interpolate `!!js` expressions at boot. A patch naming an entry that does not exist prints a stderr warning; an empty or comments-only file fails boot — disable the layer with `[]` instead.
54
56
 
55
57
  Profiles with `patchReload: live` watch both user patch files: a valid edit recomposes without restart, while a rejected edit leaves the last good app running. A `startup` profile installs neither those watchers nor the launcher's watch-only HMR fallback.
56
58
 
59
+ Inserted plugin names may be absolute filesystem paths, file URLs, or package specifiers. Patch loading converts absolute paths and patch-relative `./` or `../` paths to file URLs within `insert` rows and their nested groups; existing-entry name assertions and replacement `config` values remain literal.
60
+
57
61
  ### Previewing the effective configuration
58
62
 
59
63
  Before you boot, you can print the exact configuration the app will mount: the dump shows the composed entry list with `!!js` expressions verbatim, grouped under comments naming each source file and the patch layers that changed it, as one loadable YAML document. Patches that match no row are reported with their layer label; a missing, unparsable, or invalid config fails the dump.
@@ -124,7 +128,7 @@ Indirectly, through the loaded plugin tree, which alone contributes model contex
124
128
 
125
129
  #### KV Cache effect
126
130
 
127
- Boot itself invalidates nothing in the request prefix. 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; any other request-prefix change is owned by the named consumer.
131
+ Boot itself changes no request prefix. `addHarnessSourceSection` places its source path after first-party reusable instructions, so different checkouts leave those preceding bytes unchanged when tools and configuration match. Provider cache reuse is not guaranteed.
128
132
 
129
133
  ## Known Limitations and Deferred Work
130
134
 
package/README.zh.md CHANGED
@@ -45,15 +45,19 @@ const ctx = await boot('dsh', resolveConfigPath(argv[2], process.env.DSH_SNAPSHO
45
45
  <a id="profiles"></a>
46
46
  ### Profile
47
47
 
48
- profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`headless`、`acp`、`sdk` 与 `sdk-minimal` 从同一 launcher 启动不同组合。profile 位于 `$DSH_HOME/profiles/<name>`,由可安装 bundle、自身 `cordis.patch.yml` 与 `patchReload: live | startup` 组成;自定义 profile 省略 reload 策略时保留历史 `live` 默认值。随产品交付的 `web` 模板实时重载,其他随附模板只在启动时应用 patch。`sdk-minimal` 只列出自身的独立 bundle,其他模板保留 base 加模式 bundle 的栈。`dsh plugin` 创建自定义 profile;缺失 bundle 或未声明 patch 的 bundle 会让启动明确失败。
48
+ Profile 与 bundle 的声明类型从 [`@deepseek-ai/dsh-package-manifest`](../../util/package-manifest/README.zh.md) 导入。App-boot 负责 profile 加载、JSON 校验和解析后的运行时数据。
49
+
50
+ profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`headless`、`acp`、`sdk` 与 `sdk-minimal` 从同一 launcher 启动不同组合。profile 位于 `$DSH_HOME/profiles/<name>`,由可安装 bundle、自身 `cordis.patch.yml` 与 `patchReload: live | startup` 组成;自定义 profile 省略 reload 策略时保留历史 `live` 默认值。随产品交付的 `web` 模板实时重载,其他随附模板只在启动时应用 patch。`sdk-minimal` 只列出自身的独立 bundle,其他模板保留 base 加模式 bundle 的栈。`dsh --profile <name> --from-default-profile <template>` 从一个随附模板,在新的非内置名称处创建自定义 profile;`dsh plugin` 则初始化以 base 为基础的 profile,并管理其中安装的 bundle。缺失 bundle 或未声明 patch 的 bundle 会让启动明确失败。由应用持有的 npm 项目(例如 Electron 保留的 Desktop profile)通过 `loadProfileDirectory` 加载已经初始化的目录,而不会将它暴露给 CLI profile 查找。
49
51
 
50
52
  你的机器本地偏好同样位于 harness home 中:
51
53
 
52
- - **`.env`**——你的普通环境层:调用目录的文件优先于 harness home 的文件,两者都低于继承环境。决定进程如何启动的变量(`PATH`、代理、`DSH_*`、`XDG_*` 等)会被文件拒绝:请改为导出。对于只想加载某个目录 `.env` 的非产品 bin,文件缺失不影响启动,文件无法加载时输出一行带标签的警告。
54
+ - **`.env`**——你的普通环境层:调用目录的文件优先于 harness home 的文件,两者都低于继承环境。决定进程如何启动的变量(`PATH`、`DSH_*`、`XDG_*` 等)会被文件拒绝:请改为导出。四个代理名(`HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY`、`NO_PROXY`)只从 harness home 的文件接受,绝不从调用目录的文件接受——后者随 clone 一起到来。对于只想加载某个目录 `.env` 的非产品 bin,文件缺失不影响启动,文件无法加载时输出一行带标签的警告。
53
55
  - **`cordis.patch.yml`**——你的 tweak 层,应用在所有组合包层之后(先应用逐 profile 的文件,再应用 home 级文件,因此后者优先级更高):替换某个条目的整个 config(重述你要保留的字段)、插入新条目,或在启动时插值 `!!js` 表达式。patch 指定的条目不存在时输出 stderr 警告;空文件或仅含注释的文件会导致启动失败——如需禁用该层,请改用 `[]`。
54
56
 
55
57
  带 `patchReload: live` 的 profile 会监视两份用户 patch 文件:有效编辑无需重启即可重新组合,被拒绝的编辑则让最后一个可用应用继续运行。`startup` profile 既不安装这些监视器,也不安装 launcher 的仅监视 HMR 回退。
56
58
 
59
+ 插入条目的插件名可以是绝对文件系统路径、文件 URL 或包标识符。patch 加载会把 `insert` 条目及其嵌套分组中的绝对路径以及相对于 patch 文件的 `./` 或 `../` 路径转换为文件 URL;对已有条目名称的断言及替换用的 `config` 值保持原样。
60
+
57
61
  ### 预览生效配置
58
62
 
59
63
  启动前,你可以打印应用将挂载的确切配置:dump 会以 `!!js` 表达式原样展示组合后的条目列表,并按注释分组标明每个源文件及其 patch 层,输出是一份可加载的 YAML 文档。未匹配到任何行的 patch 会连同其层标签一起报告;配置缺失、无法解析或字段无效都会使 dump 失败。
@@ -124,7 +128,7 @@ profile 是同一套 dsh 安装提供不同应用界面的方式:`web`、`head
124
128
 
125
129
  #### KV Cache 影响
126
130
 
127
- 启动本身不会使请求前缀中的任何内容失效。消费方调用 `addHarnessSourceSection` 时,会在系统提示词靠前位置、逐请求内容之前添加一行短文本,因此不会使跨轮次缓存失效;请求前缀的其他任何变化均由相应的具名消费方负责。
131
+ 启动本身不改变请求前缀。`addHarnessSourceSection` 将源码路径放在第一方可复用指令之后,因此工具与配置一致时,不同 checkout 不会改变前置字节。不保证提供方复用缓存。
128
132
 
129
133
  ## 已知限制与延期工作
130
134
 
package/lib/index.js CHANGED
@@ -831,27 +831,17 @@ function resolveBundleDir(binName, packageName, installAnchor, profileDir) {
831
831
  throw new Error(`${binName}: cannot resolve profile bundle ${JSON.stringify(packageName)} from the dsh installation or ${profileDir}; run 'dsh plugin --profile ${basename(profileDir)} install' if its dependency is not installed`);
832
832
  }
833
833
  /**
834
- * Load a profile: resolve every `dsh.profile.bundles` entry to its patch
835
- * layer and parse the profile's own patch file. A listed bundle without a
836
- * `dsh.bundle` manifest fails loud — naming a bundle-less package as a layer
837
- * is a misconfiguration, not "no patches".
834
+ * Load an already initialized profile directory without resolving it through
835
+ * the shared Harness home. This is used by application-owned profiles whose
836
+ * package project and lifecycle belong to that application.
838
837
  * @param binName - the diagnostic prefix on thrown errors.
839
- * @param name - the profile name.
840
- * @param installAnchor - absolute path of the dsh app's package.json (first resolution anchor).
841
- * @param home - the Harness home; defaults to {@link resolveDshHome}.
842
- * @param options - `userLayer: false` skips reading `cordis.patch.yml`, so a
843
- * bundles-only consumer (`--dump-default-config`, a recovery diagnostic)
844
- * cannot fail on a broken user layer.
845
- * @returns the loaded profile (empty `patches` when the user layer is skipped).
838
+ * @param dir - absolute profile package directory.
839
+ * @param installAnchor - absolute path of the owning dsh app's package.json.
840
+ * @param options - `userLayer: false` skips reading `cordis.patch.yml`.
841
+ * @returns the resolved bundle layers and optional user patch layer.
846
842
  */
847
- function loadProfile(binName, name, installAnchor, home = resolveDshHome(), options = {}) {
848
- const dir = resolveProfileDir(name, home);
849
- if (!existsSync(join(dir, "package.json"))) {
850
- const template = PROFILE_TEMPLATES[name];
851
- if (template === void 0) throw new Error(`${binName}: profile ${JSON.stringify(name)} does not exist; create it with 'dsh plugin --profile ${name} add <package>'`);
852
- initProfile(dir, template.bundles, template.patchReload);
853
- }
854
- const manifest = normalizeShippedProfile(name, dir, readProfileManifest(binName, dir));
843
+ function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
844
+ const manifest = readProfileManifest(binName, dir);
855
845
  const bundles = manifest.dsh?.profile?.bundles ?? [];
856
846
  const rawPatchReload = manifest.dsh?.profile?.patchReload;
857
847
  if (rawPatchReload !== void 0 && rawPatchReload !== "live" && rawPatchReload !== "startup") throw new Error(`${binName}: profile manifest ${join(dir, "package.json")} dsh.profile.patchReload must be "live" or "startup"`);
@@ -869,16 +859,41 @@ function loadProfile(binName, name, installAnchor, home = resolveDshHome(), opti
869
859
  };
870
860
  });
871
861
  const patchPath = join(dir, PROFILE_PATCH_FILENAME);
862
+ const patches = options.userLayer !== false && existsSync(patchPath) ? loadOverlayPatches(binName, patchPath) : [];
872
863
  return {
873
- name,
864
+ name: basename(dir),
874
865
  dir,
875
866
  layers,
876
867
  patchPath,
877
- patches: options.userLayer !== false && existsSync(patchPath) ? loadOverlayPatches(binName, patchPath) : [],
868
+ patches,
878
869
  patchReload
879
870
  };
880
871
  }
881
872
  /**
873
+ * Load a profile: resolve every `dsh.profile.bundles` entry to its patch
874
+ * layer and parse the profile's own patch file. A listed bundle without a
875
+ * `dsh.bundle` manifest fails loud — naming a bundle-less package as a layer
876
+ * is a misconfiguration, not "no patches".
877
+ * @param binName - the diagnostic prefix on thrown errors.
878
+ * @param name - the profile name.
879
+ * @param installAnchor - absolute path of the dsh app's package.json (first resolution anchor).
880
+ * @param home - the Harness home; defaults to {@link resolveDshHome}.
881
+ * @param options - `userLayer: false` skips reading `cordis.patch.yml`, so a
882
+ * bundles-only consumer (`--dump-default-config`, a recovery diagnostic)
883
+ * cannot fail on a broken user layer.
884
+ * @returns the loaded profile (empty `patches` when the user layer is skipped).
885
+ */
886
+ function loadProfile(binName, name, installAnchor, home = resolveDshHome(), options = {}) {
887
+ const dir = resolveProfileDir(name, home);
888
+ if (!existsSync(join(dir, "package.json"))) {
889
+ const template = PROFILE_TEMPLATES[name];
890
+ if (template === void 0) throw new Error(`${binName}: profile ${JSON.stringify(name)} does not exist; create it with 'dsh plugin --profile ${name} add <package>'`);
891
+ initProfile(dir, template.bundles, template.patchReload);
892
+ }
893
+ normalizeShippedProfile(name, dir, readProfileManifest(binName, dir));
894
+ return loadProfileDirectory(binName, dir, installAnchor, options);
895
+ }
896
+ /**
882
897
  * Compose patch layers into the effective entry list over an empty root —
883
898
  * the same single `applyEntryPatches` call the boot include makes, so flag
884
899
  * derivation and config dumps see exactly what mounts.
@@ -989,8 +1004,22 @@ const BOOTSTRAP_PREFIXES = [
989
1004
  "BASH_FUNC_"
990
1005
  ];
991
1006
  /**
1007
+ * The bootstrap names the Harness-home `.env` alone may set. A proxy chooses the route every
1008
+ * request takes, so the invoking directory's file — which arrives with a clone — keeps refusing
1009
+ * them; the home file is the user's own, and `DSH_HOME` is itself bootstrap-only, so no `.env` can
1010
+ * relocate this exemption. The CA and TLS names in the same group stay refused everywhere: they
1011
+ * change what is trusted, not where traffic goes.
1012
+ */
1013
+ const HOME_LAYER_PROXY_NAMES = new Set([
1014
+ "HTTP_PROXY",
1015
+ "HTTPS_PROXY",
1016
+ "ALL_PROXY",
1017
+ "NO_PROXY"
1018
+ ]);
1019
+ /**
992
1020
  * Whether a variable may come only from the inherited process environment
993
- * because it changes process, runtime, VCS, or network bootstrap.
1021
+ * because it changes process, runtime, VCS, or network bootstrap. The Harness-home
1022
+ * file is additionally allowed {@link HOME_LAYER_PROXY_NAMES}.
994
1023
  * @param name - the variable name.
995
1024
  * @returns true when only the inherited environment may supply it.
996
1025
  */
@@ -1004,11 +1033,13 @@ function isBootstrapOnly(name) {
1004
1033
  * @param binName - the diagnostic prefix on the thrown error.
1005
1034
  * @param dir - the directory whose `.env` to read.
1006
1035
  * @param warn - sink for the one-line unreadable-file diagnostic.
1036
+ * @param home - the resolved Harness home; when `dir` is it, {@link HOME_LAYER_PROXY_NAMES} are accepted.
1007
1037
  * @returns the parsed entries, or `undefined` when the file is absent or unreadable.
1008
- * @throws when the file declares a name {@link isBootstrapOnly} rejects.
1038
+ * @throws when the file declares a name {@link isBootstrapOnly} rejects and this layer may not set.
1009
1039
  */
1010
- function readEnvLayer(binName, dir, warn) {
1040
+ function readEnvLayer(binName, dir, warn, home) {
1011
1041
  const path = resolve(dir, ".env");
1042
+ const isHome = resolve(dir) === home;
1012
1043
  let content;
1013
1044
  try {
1014
1045
  content = readFileSync(path, "utf8");
@@ -1019,7 +1050,10 @@ function readEnvLayer(binName, dir, warn) {
1019
1050
  const values = parseEnv(content);
1020
1051
  for (const name of Object.keys(values)) {
1021
1052
  if (!isBootstrapOnly(name)) continue;
1022
- throw new Error(`${binName}: ${path} sets "${name}", which only the launching environment may set (it decides how this process starts, where its code and instructions load from, or how it reaches the network); export ${name} instead of putting it in a .env file`);
1053
+ const proxyName = HOME_LAYER_PROXY_NAMES.has(name.toUpperCase());
1054
+ if (isHome && proxyName) continue;
1055
+ const remedy = proxyName ? `export ${name}, or put it in ${resolve(home, ".env")}, which does not travel with a repository` : `export ${name} instead of putting it in a .env file`;
1056
+ throw new Error(`${binName}: ${path} sets "${name}", which only the launching environment may set (it decides how this process starts, where its code and instructions load from, or how it reaches the network); ${remedy}`);
1023
1057
  }
1024
1058
  return {
1025
1059
  path,
@@ -1035,13 +1069,13 @@ function readEnvLayer(binName, dir, warn) {
1035
1069
  * @param cwd - the invoking directory whose `.env` is the project layer.
1036
1070
  * @param warn - sink for the one-line misconfiguration diagnostics.
1037
1071
  * @returns this run's frozen environment snapshot.
1038
- * @throws when either file declares a bootstrap-only variable.
1072
+ * @throws when either file declares a bootstrap-only variable, except {@link HOME_LAYER_PROXY_NAMES} in the Harness-home file.
1039
1073
  */
1040
1074
  function loadLayeredEnv(binName, cwd = process.cwd(), warn = (line) => void process.stderr.write(line)) {
1041
1075
  const home = resolveDshHome();
1042
1076
  const inherited = { ...process.env };
1043
- const project = readEnvLayer(binName, cwd, warn);
1044
- const user = home === resolve(cwd) ? void 0 : readEnvLayer(binName, home, warn);
1077
+ const project = readEnvLayer(binName, cwd, warn, home);
1078
+ const user = home === resolve(cwd) ? void 0 : readEnvLayer(binName, home, warn, home);
1045
1079
  for (const layer of [project, user]) {
1046
1080
  if (layer === void 0) continue;
1047
1081
  for (const [name, value] of Object.entries(layer.values)) if (process.env[name] === void 0) process.env[name] = value;
@@ -1132,11 +1166,11 @@ function loadOverlayPatches(binName, file) {
1132
1166
  }
1133
1167
  return parsePatchList(binName, file, content, "overlay");
1134
1168
  }
1135
- /** Resolve relative plugin paths in one patch file's `insert` rows without changing assertion names. */
1169
+ /** Convert inserted filesystem paths to file URLs, anchoring relative paths beside the patch; keep assertion names literal. */
1136
1170
  function anchorInsertedPluginNames(patches, file) {
1137
1171
  const base = dirname(resolve(file));
1138
1172
  const visit = (entry) => {
1139
- if (typeof entry.name === "string" && (entry.name.startsWith("./") || entry.name.startsWith("../"))) entry.name = pathToFileURL(resolve(base, entry.name)).href;
1173
+ if (typeof entry.name === "string" && (isAbsolute(entry.name) || entry.name.startsWith("./") || entry.name.startsWith("../"))) entry.name = pathToFileURL(resolve(base, entry.name)).href;
1140
1174
  if (entry.group && Array.isArray(entry.config)) entry.config.forEach(visit);
1141
1175
  };
1142
1176
  for (const patch of patches) patch.insert?.forEach(visit);
@@ -1518,9 +1552,10 @@ const HARNESS_SOURCE_SECTION = "harness:source";
1518
1552
  * explicitly distinguishing it from the task workspace and current working
1519
1553
  * directory. The self-referential `dsh-tool-cordis` toolset reads and edits this
1520
1554
  * checkout. Call once on the settled boot context ({@link boot}); the section
1521
- * uses the shared first-party placement just after the harness identity opener
1522
- * and before the deployment persona. A booted tree with no `systemPrompt` service has no prompt to
1523
- * augment, so this is then a no-op that returns `undefined`. The section is
1555
+ * uses the shared first-party placement after reusable instructions
1556
+ * and before the Web surface and persona suffix. A booted tree with no
1557
+ * `systemPrompt` service has no prompt to augment, so this is then a no-op
1558
+ * that returns `undefined`. The section is
1524
1559
  * registered against the `systemPrompt` service's fiber, so a dev HMR reload of
1525
1560
  * that plugin drops it until the next boot.
1526
1561
  * @param ctx - the settled boot context whose global system prompt to augment.
@@ -1537,4 +1572,4 @@ function addHarnessSourceSection(ctx, sourceRoot) {
1537
1572
  });
1538
1573
  }
1539
1574
  //#endregion
1540
- export { DEFAULT_PROFILE_BUNDLES, DEFAULT_PROFILE_PATCH_RELOAD, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, PROFILES_DIR, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, addHarnessSourceSection, assertEntriesActivated, assertEntriesLoaded, boot, composeEntries, healProfilesModuleFallback, initProfile, installFailLoud, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, mountRootInclude, readProfileManifest, renderConfigDump, resolveBundleDir, resolveConfigPath, resolveProfileDir, watchUserPatches, writeProfileManifest };
1575
+ export { DEFAULT_PROFILE_BUNDLES, DEFAULT_PROFILE_PATCH_RELOAD, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, PROFILES_DIR, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, addHarnessSourceSection, assertEntriesActivated, assertEntriesLoaded, boot, composeEntries, healProfilesModuleFallback, initProfile, installFailLoud, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, readProfileManifest, renderConfigDump, resolveBundleDir, resolveConfigPath, resolveProfileDir, watchUserPatches, writeProfileManifest };
@@ -16,7 +16,7 @@ declare module '@deepseek-ai/cordis' {
16
16
  dshHomePath?: typeof dshHomePath;
17
17
  }
18
18
  }
19
- export { composeEntries, DEFAULT_PROFILE_BUNDLES, DEFAULT_PROFILE_PATCH_RELOAD, healProfilesModuleFallback, initProfile, loadProfile, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PROFILES_DIR, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest, type DshBundleManifest, type DshManifestSection, type DshProfileManifest, type Profile, type ProfileLayer, type ProfileManifest, type ProfileModuleFallbackOptions, type ProfilePatchReload, type ProfileTemplate, } from './profile.ts';
19
+ export { composeEntries, DEFAULT_PROFILE_BUNDLES, DEFAULT_PROFILE_PATCH_RELOAD, healProfilesModuleFallback, initProfile, loadProfile, loadProfileDirectory, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PROFILES_DIR, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest, type Profile, type ProfileLayer, type ProfileManifest, type ProfileModuleFallbackOptions, type ProfileTemplate, } from './profile.ts';
20
20
  /**
21
21
  * Resolve the config to boot. Replay swaps a `cordis.yml` basename for
22
22
  * `cordis.snapshot.yml` in the same directory; every other mode keeps the path.
@@ -44,7 +44,7 @@ export declare function loadEnv(binName: string, dir?: string, warn?: (line: str
44
44
  * @param cwd - the invoking directory whose `.env` is the project layer.
45
45
  * @param warn - sink for the one-line misconfiguration diagnostics.
46
46
  * @returns this run's frozen environment snapshot.
47
- * @throws when either file declares a bootstrap-only variable.
47
+ * @throws when either file declares a bootstrap-only variable, except {@link HOME_LAYER_PROXY_NAMES} in the Harness-home file.
48
48
  */
49
49
  export declare function loadLayeredEnv(binName: string, cwd?: string, warn?: (line: string) => void): LaunchEnvironmentSnapshot;
50
50
  /** Options for live user patch-layer reconciliation. */
@@ -254,9 +254,10 @@ export declare const HARNESS_SOURCE_SECTION = "harness:source";
254
254
  * explicitly distinguishing it from the task workspace and current working
255
255
  * directory. The self-referential `dsh-tool-cordis` toolset reads and edits this
256
256
  * checkout. Call once on the settled boot context ({@link boot}); the section
257
- * uses the shared first-party placement just after the harness identity opener
258
- * and before the deployment persona. A booted tree with no `systemPrompt` service has no prompt to
259
- * augment, so this is then a no-op that returns `undefined`. The section is
257
+ * uses the shared first-party placement after reusable instructions
258
+ * and before the Web surface and persona suffix. A booted tree with no
259
+ * `systemPrompt` service has no prompt to augment, so this is then a no-op
260
+ * that returns `undefined`. The section is
260
261
  * registered against the `systemPrompt` service's fiber, so a dev HMR reload of
261
262
  * that plugin drops it until the next boot.
262
263
  * @param ctx - the settled boot context whose global system prompt to augment.
@@ -24,24 +24,11 @@
24
24
  */
25
25
  import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader';
26
26
  import { type PatchOptions } from '@deepseek-ai/cordis-plugin-include';
27
+ import type { DshManifest, ProfilePatchReload } from '@deepseek-ai/dsh-package-manifest';
27
28
  /** Directory under the Harness home holding every profile. */
28
29
  export declare const PROFILES_DIR = "profiles";
29
30
  /** The user patch layer inside a profile directory (hot-reloaded on long-lived surfaces). */
30
31
  export declare const PROFILE_PATCH_FILENAME = "cordis.patch.yml";
31
- /** The bundle half of the `dsh` manifest section: what a bundle package exports. */
32
- export interface DshBundleManifest {
33
- /** The patch layer this bundle exports, relative to its package root. */
34
- patch: string;
35
- }
36
- /** The profile half of the `dsh` manifest section: what a profile directory composes. */
37
- export interface DshProfileManifest {
38
- /** Ordered bundle layer list (package names). */
39
- bundles?: string[];
40
- /** Whether user patch files reload while this profile remains active. */
41
- patchReload?: ProfilePatchReload;
42
- }
43
- /** User patch-file lifecycle selected by a profile. */
44
- export type ProfilePatchReload = 'live' | 'startup';
45
32
  /** Installation-owned defaults used when a shipped profile is first opened. */
46
33
  export interface ProfileTemplate {
47
34
  /** Ordered bundle layer list. */
@@ -49,22 +36,12 @@ export interface ProfileTemplate {
49
36
  /** User patch-file lifecycle for the generated profile. */
50
37
  patchReload: ProfilePatchReload;
51
38
  }
52
- /**
53
- * The profile-launcher slice of the `dsh`-owned package.json section. A
54
- * manifest may declare both roles; other consumers own additional keys.
55
- */
56
- export interface DshManifestSection {
57
- /** Bundle metadata consumed by the profile launcher. */
58
- bundle?: DshBundleManifest;
59
- /** Profile metadata consumed by the profile launcher. */
60
- profile?: DshProfileManifest;
61
- }
62
39
  /** The slice of package.json both profiles and bundles use. */
63
40
  export interface ProfileManifest {
64
41
  name?: string;
65
42
  dependencies?: Record<string, string>;
66
43
  peerDependencies?: Record<string, string>;
67
- dsh?: DshManifestSection;
44
+ dsh?: DshManifest;
68
45
  }
69
46
  /** One resolved bundle layer of a profile. */
70
47
  export interface ProfileLayer {
@@ -162,6 +139,19 @@ export declare function writeProfileManifest(dir: string, manifest: ProfileManif
162
139
  * @returns the bundle package's absolute directory.
163
140
  */
164
141
  export declare function resolveBundleDir(binName: string, packageName: string, installAnchor: string, profileDir: string): string;
142
+ /**
143
+ * Load an already initialized profile directory without resolving it through
144
+ * the shared Harness home. This is used by application-owned profiles whose
145
+ * package project and lifecycle belong to that application.
146
+ * @param binName - the diagnostic prefix on thrown errors.
147
+ * @param dir - absolute profile package directory.
148
+ * @param installAnchor - absolute path of the owning dsh app's package.json.
149
+ * @param options - `userLayer: false` skips reading `cordis.patch.yml`.
150
+ * @returns the resolved bundle layers and optional user patch layer.
151
+ */
152
+ export declare function loadProfileDirectory(binName: string, dir: string, installAnchor: string, options?: {
153
+ userLayer?: boolean;
154
+ }): Profile;
165
155
  /**
166
156
  * Load a profile: resolve every `dsh.profile.bundles` entry to its patch
167
157
  * layer and parse the profile's own patch file. A listed bundle without a
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-app-boot",
3
3
  "description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence",
4
- "version": "0.1.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -29,17 +29,18 @@
29
29
  "dependencies": {
30
30
  "js-yaml": "^4.2.0",
31
31
  "resolve.exports": "^2.0.3",
32
- "@deepseek-ai/dsh-atomic-write": "^0.1.2-rc.1"
32
+ "@deepseek-ai/dsh-atomic-write": "^0.1.5-alpha.1",
33
+ "@deepseek-ai/dsh-package-manifest": "^0.1.5-alpha.1"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "@deepseek-ai/cordis-plugin-group": "^1.0.2",
36
- "@deepseek-ai/cordis-plugin-hmr": "^1.0.17",
37
37
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
38
+ "@deepseek-ai/cordis-plugin-hmr": "^1.0.17",
38
39
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
39
- "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1",
40
- "@deepseek-ai/dsh-home-paths": "^0.1.2-rc.1",
41
- "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1",
42
- "@deepseek-ai/cordis": "^4.0.2"
40
+ "@deepseek-ai/dsh-launch-environment": "^0.1.5-alpha.1",
41
+ "@deepseek-ai/dsh-system-prompt": "^0.1.5-alpha.1",
42
+ "@deepseek-ai/cordis": "^4.0.2",
43
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-alpha.1"
43
44
  },
44
45
  "peerDependenciesMeta": {
45
46
  "@deepseek-ai/cordis-plugin-hmr": {
@@ -49,13 +50,13 @@
49
50
  "devDependencies": {
50
51
  "@types/js-yaml": "^4.0.9",
51
52
  "@deepseek-ai/cordis-plugin-group": "^1.0.2",
52
- "@deepseek-ai/cordis-plugin-include": "^1.0.7",
53
53
  "@deepseek-ai/cordis-plugin-hmr": "^1.0.17",
54
+ "@deepseek-ai/cordis-plugin-include": "^1.0.7",
54
55
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
56
+ "@deepseek-ai/dsh-launch-environment": "^0.1.5-alpha.1",
57
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-alpha.1",
58
+ "@deepseek-ai/dsh-system-prompt": "^0.1.5-alpha.1",
55
59
  "@deepseek-ai/cordis-plugin-timer": "^1.1.4",
56
- "@deepseek-ai/dsh-launch-environment": "^0.1.2-rc.1",
57
- "@deepseek-ai/dsh-home-paths": "^0.1.2-rc.1",
58
- "@deepseek-ai/dsh-system-prompt": "^0.1.2-rc.1",
59
60
  "@deepseek-ai/cordis": "^4.0.2"
60
61
  }
61
62
  }