@deepseek-ai/dsh-app-boot 0.2.0-rc.1 → 0.2.1-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
@@ -33,8 +33,8 @@
33
33
  en: c4a301652b851ce9
34
34
  zh: 784871e19cd9db0a
35
35
  /deepseek-ai-dsh-app-boot/use-this-package/reading-plugin-display-metadata:
36
- en: 480ad3a4d96112f0
37
- zh: 10a92f1013aa5ac4
36
+ en: 98ef2935171952c0
37
+ zh: 8c160bdeffd68b0f
38
38
  /deepseek-ai-dsh-app-boot/use-this-package/startup-and-reload-failures:
39
39
  en: 09890e218ab49c84
40
40
  zh: e3d86597e7fb1adb
@@ -45,17 +45,17 @@
45
45
  en: f4a2695cb4c8a570
46
46
  zh: 89837235d2f376cf
47
47
  /deepseek-ai-dsh-app-boot/understand-the-implementation/design-notes:
48
- en: 4ac0c33806b5a5b2
49
- zh: cdb56f3adfcb4364
48
+ en: 25a0d1b46fd66d68
49
+ zh: 5cd8b4ac382dccdc
50
50
  /deepseek-ai-dsh-app-boot/understand-the-implementation/helper-behavior:
51
51
  en: f7bc3a8dd4b00bdd
52
52
  zh: 30098428aa769ad0
53
53
  /deepseek-ai-dsh-app-boot/understand-the-implementation/source-map:
54
- en: 25602fd8eb9b2907
55
- zh: 870a33dd34a52c32
54
+ en: 3da01827e3354655
55
+ zh: 06ccfaed83f65a76
56
56
  /deepseek-ai-dsh-app-boot/further-exploration:
57
- en: ffd10e94443a10ca
58
- zh: cd11495bae751bdd
57
+ en: 401269cc5ea8593e
58
+ zh: cd337c356b320565
59
59
  /deepseek-ai-dsh-app-boot/model-experience:
60
60
  en: 769777f1cb1a9408
61
61
  zh: f02b2834179df50d
package/README.md CHANGED
@@ -82,7 +82,7 @@ The projector preserves native omission behavior by checking literal defaults ag
82
82
 
83
83
  ### Reading plugin display metadata
84
84
 
85
- Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. Missing locale fields fall back to the accessible `package.json` at that address; malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads `package.json.icon` as an image data URL, even when locale text is complete; an icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format.
85
+ Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. For a package-root specifier, missing locale fields fall back to the accessible `package.json`; a subpath specifier never reads a `package.json`. Malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads an image data URL, even when locale text is complete: a package root uses its manifest `icon`, or `<package>/icon` when that field is omitted; a subpath uses `<specifier>/icon`. An icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format.
86
86
 
87
87
  <a id="startup-and-reload-failures"></a>
88
88
  ### Startup and reload failures
@@ -135,7 +135,7 @@ This section explains how the outcomes above are realized and points at the code
135
135
  - **Package metadata.** `ctx.pluginPackages.packageOf` locates the owning package without loading code or requiring an exported `package.json`; a subpath selects its package without validating that file. The installed runtime resolution owns selection even for a miss. Low-level embedders that install the service without one retain native lookup. Display metadata uses the separate entry-aware reader described above.
136
136
  - **Two Loader builtins.** `mountRootInclude` registers `cordis:include` and `cordis:group` as Loader builtins: a group row gives one `isolate` realm to a provider and its consumers together, and an agent preset outside this workspace cannot resolve `@deepseek-ai/cordis-plugin-group` by name. Both load through the ambient module pipeline rather than the included tree's own specifier resolution.
137
137
  - **Consumer-owned strictness.** Ordinary Loader groups keep successful siblings. App-boot applies the global required-entry policy after initial settlement; agent presets and dynamic multi-entry compositions own and dispose their separate Loader subtree when they require all-or-nothing setup. App-boot reads failed fibers to report their recorded errors and coalesces duplicate Loader rejection notifications through one process checkpoint.
138
- - **One runtime resolution.** The installation-first and ordered-bundle breadth-first traversal produces the runtime table. Runtime resolution creates no links; runtime resolution entries occupy their package names at `$DSH_HOME/profiles/node_modules`, and every other name sees that directory as an ordinary ancestor. Profile load removes the `.dsh-module-fallback` projections that link-backend releases wrote into a profile; pnpm-installed packages stay. External bare targets selected by package `imports` use the same package order, while Node retains mapping, conditions, and exact target resolution. A complete successor may add package names and update linked-root membership atomically within the existing package-mapping and local-name constraints.
138
+ - **One runtime resolution.** The installation-first and ordered-bundle breadth-first traversal produces the runtime table. Runtime resolution creates no links; runtime resolution entries occupy their package names at `$DSH_HOME/profiles/node_modules`, and every other name sees that directory as an ordinary ancestor. Profile load removes the `.dsh-module-fallback` projections that link-backend releases wrote into a profile; pnpm-installed packages stay. External bare targets selected by package `imports` use the same package order, while Node retains mapping, conditions, and exact target resolution. A complete successor may add package names, remove profile-scoped mappings and profile-local names, and update linked-root membership atomically; retained mappings keep their normalized directory, version, and scope. Installation anchors remain fixed; a profile mapping may change its declarer. [Plugin Manager](../plugin-manager/README.md) publishes successors after package operations ([decision](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md)).
139
139
  - **Removing linked interception.** A successor may remove a linked root without restart. Directories outside all remaining roots use native lookup for subsequent requests, which may find a development copy or report a missing package. Existing module references and Node caches stay unchanged. Re-adding the same link name and target is allowed; pointing a previously published name at a different target is rejected even after removal ([generation rules](../../../.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md#immutable-generations)).
140
140
  - **Application-owned profiles.** Application-owned profiles use the same runtime resolution. Links within the active profile directory, including pnpm store links, are not external roots even when the profile is outside the shared profiles tree. Resolution does not modify their `node_modules`; pnpm owns installed packages.
141
141
  - **Owned Workers.** Worker build banners import `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap` before bundled business code. Each Worker installs the structured-cloned runtime resolution in its own isolate. The bootstrap bundle has no static package imports. Source Worker entries retain their self-contained dependency closure, and third-party Workers receive no injection.
@@ -159,7 +159,6 @@ The exports each own one stage of the boot: config resolution and snapshot repla
159
159
  | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | Profile patch backup and recovery bundle activation |
160
160
  | [`src/config-schema/`](src/config-schema/) | Profile schema generation, discovery, native projection, and result types |
161
161
  | [`src/profile-resolution/`](src/profile-resolution/) | Runtime resolver, package-metadata service, and built Worker bootstrap |
162
- | — | No runtime invariant companion is published; one interception owns each runtime resolution. |
163
162
 
164
163
  </details>
165
164
 
@@ -177,7 +176,7 @@ Read these pages when the package-level contract is not enough. They move from t
177
176
  - [dsh-home-paths](../../util/home-paths/README.md) — the Harness-home resolver (`resolveDshHome`).
178
177
  - [Configuration source ownership](../../../.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md) — why a discovered file may not decide bootstrap behavior.
179
178
  - [Profile plugin bundles](../../../.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md) — the profile and bundle composition design.
180
- - [User-patch HMR tests](../../../.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md) — ownership of live-patch behavior and native filesystem delivery.
179
+ - [User-patch HMR tests](tests/user-patches.spec.ts) — ownership of live-patch behavior and native filesystem delivery.
181
180
 
182
181
  -----
183
182
 
package/README.zh.md CHANGED
@@ -82,7 +82,7 @@ profile 导入插件前,DSH 会检查其 `peerDependencies` 中对 `@deepseek-
82
82
 
83
83
  ### 读取插件展示元信息
84
84
 
85
- 使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。缺失的 locale 字段回退到该地址下可访问的 `package.json`;格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会将 `package.json.icon` 加载为图片 data URL;图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。
85
+ 使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。包根标识缺失的 locale 字段回退到可访问的 `package.json`;子路径标识从不读取 `package.json`。格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会加载图片 data URL:包根使用清单 `icon`,省略该字段时使用 `<包名>/icon`;子路径使用 `<标识>/icon`。图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。
86
86
 
87
87
  <a id="startup-and-reload-failures"></a>
88
88
  ### 启动与重载失败
@@ -135,7 +135,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
135
135
  - **包元数据。** `ctx.pluginPackages.packageOf` 定位所属包,不加载代码,也不要求导出 `package.json`;子路径选择其所属包,不校验该文件。安装 runtime resolution 后,即使查询未命中也以其选包规则为准。仅安装服务而不提供 runtime resolution 的底层嵌入方保留原生查询。展示元数据使用上文另述的入口感知读取器。
136
136
  - **两个 Loader builtin。** `mountRootInclude` 把 `cordis:include` 与 `cordis:group` 注册为 Loader builtin:group 行能把一个提供方与它的消费方放进同一个 `isolate` realm,而位于本工作区之外的 agent preset 无法按名称解析 `@deepseek-ai/cordis-plugin-group`。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析。
137
137
  - **由 consumer 持有严格语义。** 普通 Loader group 保留成功 sibling。App-boot 在首次结算后应用全局 required-entry policy;agent preset 与动态多 entry 组合在需要 all-or-nothing setup 时,持有并拆卸各自的独立 Loader 子树。App-boot 读取 failed fiber 来报告已记录的错误,并在一个进程检查点内合并 Loader 重复的 rejection 通知。
138
- - **唯一 runtime resolution。** 安装优先、有序 bundle 逐根 breadth-first 遍历生成运行时表。runtime 解析不创建链接;runtime resolution 条目占据 `$DSH_HOME/profiles/node_modules` 上各自的包名位置,其余包名把该目录当作普通祖先。profile 加载时删除 Link 后端发布版写进 profile 的 `.dsh-module-fallback` 投影;pnpm 安装的包保留。package `imports` 选中的外部 bare target 使用相同的选包顺序,映射、conditions 和精确 target 解析仍由 Node 负责。完整后继 runtime resolution 可以在既有包映射和本地包名约束内原子增加 package name、更新 linked root 集合。
138
+ - **唯一 runtime resolution。** 安装优先、有序 bundle 逐根 breadth-first 遍历生成运行时表。runtime 解析不创建链接;runtime resolution 条目占据 `$DSH_HOME/profiles/node_modules` 上各自的包名位置,其余包名把该目录当作普通祖先。profile 加载时删除 Link 后端发布版写进 profile 的 `.dsh-module-fallback` 投影;pnpm 安装的包保留。package `imports` 选中的外部 bare target 使用相同的选包顺序,映射、conditions 和精确 target 解析仍由 Node 负责。完整后继 runtime resolution 可以原子地增加 package name、移除 profile 范围的映射和 profile 本地包名、更新 linked root 集合;保留映射的规范化目录、版本和作用域不变。安装范围的锚点不变;profile 映射可以更换声明来源。[插件管理器](../plugin-manager/README.zh.md)在包操作之后发布后继代([决策](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md))。
139
139
  - **移除链接拦截。** 后继 generation 可以移除 linked root,无需重启。目录不再被任何剩余 root 覆盖时,后续请求使用原生查询,可能找到开发副本,也可能报告缺包。已有模块引用和 Node 缓存保持不变。同名、同目标可以重新加入;曾发布的名称改指向不同目标时,即使中间移除过也会被拒绝([generation 规则](../../../.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md#immutable-generations))。
140
140
  - **应用自有 profile。** 应用自有 profile 使用相同的 runtime resolution。目标位于当前 profile 目录内的链接(包括 pnpm store 链接)不算外部 root,即使 profile 位于共享 profiles 树外。解析过程不修改其 `node_modules`;已安装包由 pnpm 管理。
141
141
  - **自有 Worker。** Worker 构建 banner 会在业务 bundle 前导入 `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap`。每个 Worker 在自己的 isolate 中安装结构化克隆的 runtime resolution。bootstrap bundle 不静态导入任何包。源码 Worker 入口保留自包含依赖,第三方 Worker 不接受注入。
@@ -159,7 +159,6 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
159
159
  | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | profile patch 备份与恢复 bundle 启用状态 |
160
160
  | [`src/config-schema/`](src/config-schema/) | Profile schema 生成、发现、原生投影与结果类型 |
161
161
  | [`src/profile-resolution/`](src/profile-resolution/) | 运行时 resolver、package metadata 服务与构建后 Worker bootstrap |
162
- | — | 不发布运行时不变式伴生入口;每个 runtime resolution 只有一个拦截所有。 |
163
162
 
164
163
  </details>
165
164
 
@@ -177,7 +176,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
177
176
  - [dsh-home-paths](../../util/home-paths/README.zh.md)——harness home 解析器(`resolveDshHome`)。
178
177
  - [配置来源归属](../../../.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md)——被发现的文件为何不得决定 bootstrap 行为。
179
178
  - [Profile 插件组合包](../../../.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.zh.md)——profile 与组合包组合设计。
180
- - [用户 patch HMR 测试](../../../.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md)——实时 patch 行为与原生文件系统投递的验证归属。
179
+ - [用户 patch HMR 测试](tests/user-patches.spec.ts)——实时 patch 行为与原生文件系统投递的验证归属。
181
180
 
182
181
  -----
183
182
 
package/lib/index.js CHANGED
@@ -539,6 +539,11 @@ const INSTALLATION_OWNED_PROFILE_TUPLES = { headless: [
539
539
  "@deepseek-ai/dsh-web-app",
540
540
  "@deepseek-ai/dsh-headless"
541
541
  ] };
542
+ /**
543
+ * Bundles an earlier release shipped and the installation no longer carries;
544
+ * {@link loadProfileDirectory} removes them from the profile's bundle list.
545
+ */
546
+ const RETIRED_BUNDLES = new Set(["@deepseek-ai/dsh-experimental-schedule-bundle"]);
542
547
  /** The bundle list a `dsh plugin` init uses for a name with no shipped template. */
543
548
  const DEFAULT_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base"];
544
549
  /**
@@ -553,7 +558,7 @@ const OPTIONAL_BUNDLES = [
553
558
  "@deepseek-ai/dsh-experimental-agent-team-profile",
554
559
  "@deepseek-ai/dsh-experimental-voice-input-bundle",
555
560
  "@deepseek-ai/dsh-experimental-auto-review",
556
- "@deepseek-ai/dsh-experimental-schedule-bundle"
561
+ "@deepseek-ai/dsh-experimental-inspector-profile"
557
562
  ];
558
563
  const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer:
559
564
  # a top-level YAML array of loader patch entries (id-targeted config
@@ -738,7 +743,11 @@ async function createRuntimeResolution(options) {
738
743
  const localPackageNames = profile === void 0 ? [] : installedProfilePackageNames(profile, manifest);
739
744
  const profilePackages = profile === void 0 ? /* @__PURE__ */ new Map() : collectProfileScopePackages(profile, packageNames, profileDeclarers, profileVersions);
740
745
  const linkedRoots = profile === void 0 ? [] : linkedProfileRoots(profile, profilesDir);
741
- return await Promise.resolve(Object.freeze({
746
+ return await Promise.resolve(new ProfileRuntimeResolution({
747
+ installAnchor,
748
+ profileDir: profile?.dir,
749
+ home
750
+ }, {
742
751
  profilesDir,
743
752
  profileDir: profile?.dir,
744
753
  localPackageNames: Object.freeze(localPackageNames),
@@ -758,6 +767,45 @@ async function createRuntimeResolution(options) {
758
767
  }))])
759
768
  }));
760
769
  }
770
+ /**
771
+ * A runtime resolution that remembers the inputs it was computed from. Worker environment data carries only its
772
+ * fields; the inputs stay private to the thread that computed it.
773
+ */
774
+ var ProfileRuntimeResolution = class {
775
+ profilesDir;
776
+ profileDir;
777
+ localPackageNames;
778
+ entries;
779
+ linkedRoots;
780
+ #source;
781
+ /**
782
+ * @param source - inputs of {@link createRuntimeResolution}, reused by {@link computeLatestResolution}.
783
+ * @param table - the computed package table.
784
+ */
785
+ constructor(source, table) {
786
+ this.#source = source;
787
+ this.profilesDir = table.profilesDir;
788
+ this.profileDir = table.profileDir;
789
+ this.localPackageNames = table.localPackageNames;
790
+ this.entries = table.entries;
791
+ this.linkedRoots = table.linkedRoots;
792
+ Object.freeze(this);
793
+ }
794
+ /**
795
+ * Compute the latest generation from the same installation, profile directory, and Harness home, rereading the
796
+ * profile's manifest, bundle selection, and installed packages from disk, without retaining synthetic layers.
797
+ * With no profile directory, only installation packages are recomputed. This instance is unchanged.
798
+ * @returns a new resolution for the latest generation.
799
+ */
800
+ computeLatestResolution() {
801
+ const { installAnchor, home, profileDir } = this.#source;
802
+ return createRuntimeResolution({
803
+ installAnchor,
804
+ home,
805
+ ...profileDir === void 0 ? {} : { profile: loadProfileDirectory("dsh", profileDir, installAnchor) }
806
+ });
807
+ }
808
+ };
761
809
  /** Synthetic profiles used by direct callers may have no on-disk manifest. */
762
810
  function readOptionalProfileManifest(profile) {
763
811
  if (profile === void 0) return void 0;
@@ -848,6 +896,19 @@ function writeProfileManifest(dir, manifest) {
848
896
  function sameBundles(left, right) {
849
897
  return left.length === right.length && left.every((value, index) => value === right[index]);
850
898
  }
899
+ /** Return `manifest` with `dsh.profile.bundles` replaced, preserving all other fields. */
900
+ function withBundles(manifest, bundles) {
901
+ return {
902
+ ...manifest,
903
+ dsh: {
904
+ ...manifest.dsh,
905
+ profile: {
906
+ ...manifest.dsh?.profile,
907
+ bundles: [...bundles]
908
+ }
909
+ }
910
+ };
911
+ }
851
912
  /**
852
913
  * Normalize an exact installation-owned bundle tuple to its shipped template,
853
914
  * preserving all other manifest fields. Other bundle lists remain untouched.
@@ -858,16 +919,19 @@ function normalizeShippedProfile(name, dir, manifest) {
858
919
  const bundles = manifest.dsh?.profile?.bundles;
859
920
  if (template === void 0 || bundles === void 0) return manifest;
860
921
  if (!(installationOwned !== void 0 && sameBundles(bundles, installationOwned))) return manifest;
861
- const normalized = {
862
- ...manifest,
863
- dsh: {
864
- ...manifest.dsh,
865
- profile: {
866
- ...manifest.dsh?.profile,
867
- bundles: [...template.bundles]
868
- }
869
- }
870
- };
922
+ const normalized = withBundles(manifest, template.bundles);
923
+ writeProfileManifest(dir, normalized);
924
+ return normalized;
925
+ }
926
+ /**
927
+ * Remove {@link RETIRED_BUNDLES} from a profile's bundle list, writing the
928
+ * manifest back only when it listed one.
929
+ */
930
+ function dropRetiredBundles(dir, manifest) {
931
+ const bundles = manifest.dsh?.profile?.bundles ?? [];
932
+ const kept = bundles.filter((bundle) => !RETIRED_BUNDLES.has(bundle));
933
+ if (kept.length === bundles.length) return manifest;
934
+ const normalized = withBundles(manifest, kept);
871
935
  writeProfileManifest(dir, normalized);
872
936
  return normalized;
873
937
  }
@@ -909,7 +973,8 @@ function resolveBundleDir(binName, packageName, installAnchor, profileDir) {
909
973
  * Load an already initialized profile directory without resolving it through
910
974
  * the shared Harness home. This is used by application-owned profiles whose
911
975
  * package project and lifecycle belong to that application.
912
- * Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped
976
+ * Retired bundles are removed from the stored bundle list first, rewriting the
977
+ * manifest when it listed one. Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped
913
978
  * without changing the manifest and listed in `skippedBundles`; nothing is printed.
914
979
  * @param binName - the diagnostic prefix on thrown errors.
915
980
  * @param dir - absolute profile package directory.
@@ -918,7 +983,7 @@ function resolveBundleDir(binName, packageName, installAnchor, profileDir) {
918
983
  * @returns the successfully loaded bundle layers and optional user patch layer.
919
984
  */
920
985
  function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
921
- const bundles = readProfileManifest(binName, dir).dsh?.profile?.bundles ?? [];
986
+ const bundles = dropRetiredBundles(dir, readProfileManifest(binName, dir)).dsh?.profile?.bundles ?? [];
922
987
  const layers = [];
923
988
  const skippedBundles = [];
924
989
  const exemptions = bundles.length === 0 ? {} : readProfileVersionExemptions(dir);
@@ -1362,10 +1427,10 @@ var ResolutionRouter = class {
1362
1427
  if (successor.profilesDir !== this.current.profilesDir || successor.profileDir !== this.current.profileDir) throw new Error("profile resolution: a successor cannot change its profile scope");
1363
1428
  for (const [name, current] of this.current.entries) {
1364
1429
  const next = entries.get(name);
1365
- if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || !sameResolution(current.declarer, next.declarer) || current.version !== next.version || current.scope !== next.scope) throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
1430
+ if (next === void 0 && current.scope === "profile") continue;
1431
+ if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || current.scope === "installation" && !sameResolution(current.declarer, next.declarer) || current.version !== next.version || current.scope !== next.scope) throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
1366
1432
  }
1367
1433
  const localPackageNames = new Set(successor.localPackageNames);
1368
- for (const name of this.current.localPackageNames) if (!localPackageNames.has(name)) throw new Error(`profile resolution: removing local package ${JSON.stringify(name)} requires a process restart`);
1369
1434
  for (const name of localPackageNames) if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) throw new Error(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`);
1370
1435
  for (const root of successor.linkedRoots) {
1371
1436
  const previous = this.linkedTargets.get(root.name);
@@ -1857,23 +1922,41 @@ const ICON_MEDIA_TYPES = new Map([
1857
1922
  [".jpeg", "image/jpeg"],
1858
1923
  [".webp", "image/webp"]
1859
1924
  ]);
1860
- function iconOf(value, manifestPath) {
1861
- const icon = textOf(value, `${manifestPath}: icon`);
1862
- if (icon === void 0) return void 0;
1863
- if (isAbsolute(icon) || win32.isAbsolute(icon) || /^[A-Za-z][A-Za-z\d+.-]*:/u.test(icon)) throw new Error(`${manifestPath}: icon must be a relative file path`);
1864
- const mediaType = ICON_MEDIA_TYPES.get(extname(icon).toLowerCase());
1865
- if (mediaType === void 0) throw new Error(`${manifestPath}: icon must be SVG, PNG, JPEG, or WebP`);
1866
- const directory = realpathSync(dirname(manifestPath));
1867
- const file = realpathSync(resolve(directory, icon));
1868
- const local = relative(directory, file);
1869
- if (local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local)) throw new Error(`${manifestPath}: icon must remain inside its manifest directory`);
1925
+ function iconOf(specifier, packageName, parentURL, manifestPath, value) {
1926
+ const icon = manifestPath === void 0 ? void 0 : textOf(value, `${manifestPath}: icon`);
1927
+ let target;
1928
+ let label;
1929
+ if (manifestPath !== void 0 && icon !== void 0) {
1930
+ if (isAbsolute(icon) || win32.isAbsolute(icon) || /^[A-Za-z][A-Za-z\d+.-]*:/u.test(icon)) throw new Error(`${manifestPath}: icon must be a relative file path`);
1931
+ label = manifestPath;
1932
+ const root = realpathSync(dirname(manifestPath));
1933
+ target = resolve(root, icon);
1934
+ const local = relative(root, realpathSync(target));
1935
+ if (local === ".." || local.startsWith(`..${sep}`) || isAbsolute(local)) throw new Error(`${label}: icon must remain inside its manifest directory`);
1936
+ } else {
1937
+ const exported = optionalResourcePath(`${specifier}/icon`, parentURL);
1938
+ if (exported === void 0) return void 0;
1939
+ target = label = exported;
1940
+ assertPackageOwned(realpathSync(target), packageName);
1941
+ }
1942
+ const mediaType = ICON_MEDIA_TYPES.get(extname(target).toLowerCase());
1943
+ if (mediaType === void 0) throw new Error(`${label}: icon must be SVG, PNG, JPEG, or WebP`);
1944
+ const file = realpathSync(target);
1870
1945
  const stat = statSync(file);
1871
- if (!stat.isFile()) throw new Error(`${manifestPath}: icon must be a regular file`);
1872
- if (stat.size > MAX_ICON_BYTES) throw new Error(`${manifestPath}: icon exceeds 256 KiB`);
1946
+ if (!stat.isFile()) throw new Error(`${label}: icon must be a regular file`);
1947
+ if (stat.size > MAX_ICON_BYTES) throw new Error(`${label}: icon exceeds 256 KiB`);
1873
1948
  const bytes = readFileSync(file);
1874
- if (bytes.length > MAX_ICON_BYTES) throw new Error(`${manifestPath}: icon exceeds 256 KiB`);
1949
+ if (bytes.length > MAX_ICON_BYTES) throw new Error(`${label}: icon exceeds 256 KiB`);
1875
1950
  return `data:${mediaType};base64,${bytes.toString("base64")}`;
1876
1951
  }
1952
+ /** Require an ancestor of the real file whose package.json names the owning package. */
1953
+ function assertPackageOwned(file, name) {
1954
+ for (let directory = dirname(file);; directory = dirname(directory)) {
1955
+ const manifest = join(directory, "package.json");
1956
+ if (existsSync(manifest) && readObject(manifest).name === name) return;
1957
+ if (dirname(directory) === directory) throw new Error(`${file}: icon must remain inside its package directory`);
1958
+ }
1959
+ }
1877
1960
  /**
1878
1961
  * Resolve a plugin resource through the active Node ESM resolver without evaluating it.
1879
1962
  * @param specifier - complete resource module specifier, including its locale filename.
@@ -1954,24 +2037,29 @@ function localizedText(field, dictionaries, fallback, finalFallback) {
1954
2037
  };
1955
2038
  }
1956
2039
  /**
1957
- * Read localized display text and the icon declared in a plugin's exported package.json.
1958
- * Icons are manifest-relative SVG, PNG, JPEG, or WebP files of at most 256 KiB,
1959
- * contained in the manifest directory after realpath resolution. Icon failures retain display text.
2040
+ * Read plugin locale JSON display text and icon without evaluating JavaScript entries.
2041
+ * Only a package-root specifier reads its exported package.json: `name`/`description` fill missing
2042
+ * locale fields, and a declared `icon` takes priority over the `<specifier>/icon` resource.
2043
+ * A subpath specifier never reads package.json; its icon comes only from `<specifier>/icon`.
2044
+ * Manifest icons remain inside their declaring directory; exported icons remain inside their
2045
+ * owning package after realpath resolution. Both accept SVG, PNG, JPEG, or WebP up to 256 KiB.
2046
+ * Icon failures retain display text without falling back.
1960
2047
  * Non-package specifiers are skipped without invoking the resource resolver.
1961
2048
  * Language files share the directory containing the resolved English resource;
1962
2049
  * each file is resolved through the complete plugin specifier before reading.
1963
- * Missing fields use the same address's package.json name/description. Translation
1964
- * maps retain an English fallback, ultimately the full module specifier for titles and empty for descriptions.
2050
+ * Translation maps retain an English fallback, ultimately the full module specifier
2051
+ * for titles and empty for descriptions.
1965
2052
  * @param specifier - configured plugin module name, including any package subpath.
1966
2053
  * @param parentURL - owning Loader tree's module-resolution base.
1967
- * @returns display fields and any icon diagnostic, or undefined for non-package specifiers or absent metadata.
2054
+ * @returns display fields and any resource diagnostic, or undefined for non-package specifiers or absent metadata.
1968
2055
  */
1969
2056
  function readPluginMeta(specifier, parentURL) {
1970
- if (barePackageName(specifier) === void 0) return void 0;
2057
+ const packageName = barePackageName(specifier);
2058
+ if (packageName === void 0) return void 0;
1971
2059
  try {
1972
2060
  const englishPath = optionalResourcePath(`${specifier}/locale/en.json`, parentURL);
1973
2061
  const dictionaries = englishPath === void 0 ? /* @__PURE__ */ new Map() : dictionariesOf(englishPath, specifier, parentURL);
1974
- const manifestPath = optionalResourcePath(`${specifier}/package.json`, parentURL);
2062
+ const manifestPath = packageName === specifier ? optionalResourcePath(`${specifier}/package.json`, parentURL) : void 0;
1975
2063
  const manifest = manifestPath === void 0 ? void 0 : readObject(manifestPath);
1976
2064
  const title = localizedText("title", dictionaries, fallbackText(manifest?.name), specifier);
1977
2065
  const description = localizedText("description", dictionaries, fallbackText(manifest?.description), "");
@@ -1981,7 +2069,7 @@ function readPluginMeta(specifier, parentURL) {
1981
2069
  };
1982
2070
  let icon;
1983
2071
  try {
1984
- icon = manifestPath === void 0 ? void 0 : iconOf(manifest?.icon, manifestPath);
2072
+ icon = iconOf(specifier, packageName, parentURL, manifestPath, manifest?.icon);
1985
2073
  } catch (error) {
1986
2074
  return {
1987
2075
  ...text,
@@ -3209,9 +3297,11 @@ var PluginPackages = class extends Service {
3209
3297
  packages = /* @__PURE__ */ new Map();
3210
3298
  interception;
3211
3299
  disposeWorkerResolution;
3300
+ current;
3212
3301
  constructor(ctx, config = {}) {
3213
3302
  super(ctx, "pluginPackages");
3214
3303
  if (config.resolution === void 0) return;
3304
+ this.current = config.resolution;
3215
3305
  const interception = installRuntimeInterception(config.resolution);
3216
3306
  this.disposeWorkerResolution = registerWorkerResolution(config.resolution);
3217
3307
  this.interception = interception;
@@ -3222,17 +3312,30 @@ var PluginPackages = class extends Service {
3222
3312
  }
3223
3313
  /**
3224
3314
  * Publish a complete successor generation for this process and subsequently created Workers.
3225
- * Linked roots may be removed without unloading modules or clearing Node caches.
3315
+ * Profile mappings and local package names may be removed after their plugins stop; linked roots may also be removed.
3316
+ * Retained profile mappings may change their declarer, but not their normalized directory, version, or scope;
3317
+ * installation mappings must remain unchanged. Publication does not unload modules or clear Node caches.
3226
3318
  * @param successor - fully constructed generation accepted by {@link RuntimeInterception.replace}.
3227
3319
  */
3228
3320
  replace(successor) {
3229
3321
  if (this.interception === void 0) throw new Error("plugin-packages: runtime resolution is not installed");
3230
3322
  this.interception.replace(successor);
3323
+ this.current = successor;
3231
3324
  this.packages = /* @__PURE__ */ new Map();
3232
3325
  this.disposeWorkerResolution?.();
3233
3326
  this.disposeWorkerResolution = registerWorkerResolution(successor);
3234
3327
  }
3235
3328
  /**
3329
+ * Publish the latest generation computed by the installed resolution through {@link replace}. Package contents
3330
+ * and loaded modules are not reloaded.
3331
+ * @throws when no resolution is installed, it is plain data rather than a {@link ProfileRuntimeResolution},
3332
+ * reading the latest files fails, or the successor is rejected. A computed resolution without a profile can refresh.
3333
+ */
3334
+ async refresh() {
3335
+ if (!(this.current instanceof ProfileRuntimeResolution)) throw new Error("plugin-packages: the installed runtime resolution cannot be recomputed");
3336
+ this.replace(await this.current.computeLatestResolution());
3337
+ }
3338
+ /**
3236
3339
  * Locate the package named by a specifier without requiring a package export.
3237
3340
  * @param specifier - module specifier whose package owns the requested module.
3238
3341
  * @param parentURL - URL whose Node lookup order applies.
@@ -4134,4 +4237,4 @@ function addHarnessSourceSection(ctx, sourceRoot) {
4134
4237
  });
4135
4238
  }
4136
4239
  //#endregion
4137
- export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, LOADER_EXPRESSION_SCHEMA, OPTIONAL_BUNDLES, PROFILES_DIR, PROFILE_COMPATIBILITY_FILENAME, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PluginPackages, StartupError, addHarnessSourceSection, auditStartupEntries, boot, bundlePatchFiles, bundlePatchPaths, composeEntries, createConfigProjector, createRuntimeResolution, evaluatePluginCompatibility, generateConfigSchema, getDshRuntimeVersion, initProfile, installFailLoud, isNativeConfigSchema, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, pluginCompatibilityWarning, prepareProfileEntries, prepareProfilePatches, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfilePlugins, readProfileVersionExemptions, reconcileProfilePatches, reconcileProfilePlugins, removeLinkProjections, renderConfigDump, reportSkippedBundles, resolveBundleDir, resolveConfigPath, resolveProfileDir, resolveTelemetryPatch, sanitizeProfile, setProfileVersionExemption, writeProfileBundles, writeProfileManifest };
4240
+ export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, LOADER_EXPRESSION_SCHEMA, OPTIONAL_BUNDLES, PROFILES_DIR, PROFILE_COMPATIBILITY_FILENAME, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PluginPackages, ProfileRuntimeResolution, StartupError, addHarnessSourceSection, auditStartupEntries, boot, bundlePatchFiles, bundlePatchPaths, composeEntries, createConfigProjector, createRuntimeResolution, evaluatePluginCompatibility, generateConfigSchema, getDshRuntimeVersion, initProfile, installFailLoud, isNativeConfigSchema, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, pluginCompatibilityWarning, prepareProfileEntries, prepareProfilePatches, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfilePlugins, readProfileVersionExemptions, reconcileProfilePatches, reconcileProfilePlugins, removeLinkProjections, renderConfigDump, reportSkippedBundles, resolveBundleDir, resolveConfigPath, resolveProfileDir, resolveTelemetryPatch, sanitizeProfile, setProfileVersionExemption, writeProfileBundles, writeProfileManifest };
@@ -34,7 +34,7 @@ declare module '@deepseek-ai/cordis' {
34
34
  'app-boot/config-reload'(): void;
35
35
  }
36
36
  }
37
- export { composeEntries, createRuntimeResolution, DEFAULT_PROFILE_BUNDLES, OPTIONAL_BUNDLES, bundlePatchFiles, bundlePatchPaths, initProfile, removeLinkProjections, loadProfile, loadProfileDirectory, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PROFILES_DIR, readProfileManifest, reportSkippedBundles, resolveBundleDir, resolveProfileDir, writeProfileManifest, type Profile, type ProfileLayer, type SkippedBundle, type ProfileManifest, type LinkedRoot, type RuntimeResolutionOptions, type RuntimeResolutionEntry, type RuntimeResolution, type ProfileTemplate, } from './profile.ts';
37
+ export { composeEntries, createRuntimeResolution, ProfileRuntimeResolution, DEFAULT_PROFILE_BUNDLES, OPTIONAL_BUNDLES, bundlePatchFiles, bundlePatchPaths, initProfile, removeLinkProjections, loadProfile, loadProfileDirectory, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PROFILES_DIR, readProfileManifest, reportSkippedBundles, resolveBundleDir, resolveProfileDir, writeProfileManifest, type Profile, type ProfileLayer, type SkippedBundle, type ProfileManifest, type LinkedRoot, type RuntimeResolutionOptions, type RuntimeResolutionEntry, type RuntimeResolution, type ProfileTemplate, } from './profile.ts';
38
38
  export { PluginPackages, type PluginPackage, type PluginPackagesConfig, } from './profile-resolution/service.ts';
39
39
  /**
40
40
  * Resolve the config to boot. Replay swaps a `cordis.yml` basename for
@@ -9,17 +9,21 @@ import type { PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest';
9
9
  */
10
10
  export declare function resolvePluginResource(specifier: string, parentURL: string): string;
11
11
  /**
12
- * Read localized display text and the icon declared in a plugin's exported package.json.
13
- * Icons are manifest-relative SVG, PNG, JPEG, or WebP files of at most 256 KiB,
14
- * contained in the manifest directory after realpath resolution. Icon failures retain display text.
12
+ * Read plugin locale JSON display text and icon without evaluating JavaScript entries.
13
+ * Only a package-root specifier reads its exported package.json: `name`/`description` fill missing
14
+ * locale fields, and a declared `icon` takes priority over the `<specifier>/icon` resource.
15
+ * A subpath specifier never reads package.json; its icon comes only from `<specifier>/icon`.
16
+ * Manifest icons remain inside their declaring directory; exported icons remain inside their
17
+ * owning package after realpath resolution. Both accept SVG, PNG, JPEG, or WebP up to 256 KiB.
18
+ * Icon failures retain display text without falling back.
15
19
  * Non-package specifiers are skipped without invoking the resource resolver.
16
20
  * Language files share the directory containing the resolved English resource;
17
21
  * each file is resolved through the complete plugin specifier before reading.
18
- * Missing fields use the same address's package.json name/description. Translation
19
- * maps retain an English fallback, ultimately the full module specifier for titles and empty for descriptions.
22
+ * Translation maps retain an English fallback, ultimately the full module specifier
23
+ * for titles and empty for descriptions.
20
24
  * @param specifier - configured plugin module name, including any package subpath.
21
25
  * @param parentURL - owning Loader tree's module-resolution base.
22
- * @returns display fields and any icon diagnostic, or undefined for non-package specifiers or absent metadata.
26
+ * @returns display fields and any resource diagnostic, or undefined for non-package specifiers or absent metadata.
23
27
  */
24
28
  export declare function readPluginMeta(specifier: string, parentURL: string): PluginLocalizedMeta | undefined;
25
29
  //# sourceMappingURL=package-meta.d.ts.map
@@ -11,11 +11,14 @@ export interface RuntimeInterception {
11
11
  */
12
12
  packageDir(specifier: string, parentURL: string): string | undefined;
13
13
  /**
14
- * Atomically publish a complete successor and fresh caches. Linked roots may be added or removed.
15
- * Removed roots stop intercepting uncovered directories; existing modules and Node caches remain intact.
16
- * @param successor - fully constructed generation retaining existing package mappings and local names.
17
- * @throws when the profile scope or existing mappings change, local names are removed or override
18
- * existing mappings, or a previously published link name selects a different real directory.
14
+ * Atomically publish a complete successor and fresh caches. Profile-scoped mappings, local names, and linked
15
+ * roots may be removed; removed roots stop intercepting uncovered directories. Existing modules and Node caches
16
+ * remain intact, so callers remove a package only after its plugins stop.
17
+ * A retained profile mapping may change its declarer if its normalized directory, version, and scope stay unchanged.
18
+ * @param successor - fully constructed generation retaining the installation mappings.
19
+ * @throws when the profile scope, a retained directory, version, scope, or installation declarer changes;
20
+ * an installation mapping is removed; a new local name overrides an existing mapping; or a previously published
21
+ * link name selects a different real directory.
19
22
  */
20
23
  replace(successor: RuntimeResolution): void;
21
24
  /** Restore the native resolver methods. Interceptions dispose in reverse order. */
@@ -1,7 +1,7 @@
1
1
  /** Package metadata resolved through one runtime interception. */
2
2
  import { Service, type Context } from '@deepseek-ai/cordis';
3
3
  import type { PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest';
4
- import type { RuntimeResolution } from '../profile.ts';
4
+ import { type RuntimeResolution } from '../profile.ts';
5
5
  declare module '@deepseek-ai/cordis' {
6
6
  interface Context {
7
7
  /** Deterministic package lookup for configured plugin specifiers. */
@@ -31,13 +31,23 @@ export declare class PluginPackages extends Service {
31
31
  private packages;
32
32
  private readonly interception;
33
33
  private disposeWorkerResolution;
34
+ private current;
34
35
  constructor(ctx: Context, config?: PluginPackagesConfig);
35
36
  /**
36
37
  * Publish a complete successor generation for this process and subsequently created Workers.
37
- * Linked roots may be removed without unloading modules or clearing Node caches.
38
+ * Profile mappings and local package names may be removed after their plugins stop; linked roots may also be removed.
39
+ * Retained profile mappings may change their declarer, but not their normalized directory, version, or scope;
40
+ * installation mappings must remain unchanged. Publication does not unload modules or clear Node caches.
38
41
  * @param successor - fully constructed generation accepted by {@link RuntimeInterception.replace}.
39
42
  */
40
43
  replace(successor: RuntimeResolution): void;
44
+ /**
45
+ * Publish the latest generation computed by the installed resolution through {@link replace}. Package contents
46
+ * and loaded modules are not reloaded.
47
+ * @throws when no resolution is installed, it is plain data rather than a {@link ProfileRuntimeResolution},
48
+ * reading the latest files fails, or the successor is rejected. A computed resolution without a profile can refresh.
49
+ */
50
+ refresh(): Promise<void>;
41
51
  /**
42
52
  * Locate the package named by a specifier without requiring a package export.
43
53
  * @param specifier - module specifier whose package owns the requested module.
@@ -175,7 +175,37 @@ export interface RuntimeResolutionOptions {
175
175
  * @param options - installation anchor, optional loaded profile, and Harness home.
176
176
  * @returns the complete immutable runtime resolution.
177
177
  */
178
- export declare function createRuntimeResolution(options: RuntimeResolutionOptions): Promise<RuntimeResolution>;
178
+ export declare function createRuntimeResolution(options: RuntimeResolutionOptions): Promise<ProfileRuntimeResolution>;
179
+ /** Inputs a {@link ProfileRuntimeResolution} reuses to compute its successor. */
180
+ interface ResolutionSource {
181
+ installAnchor: string;
182
+ home: string;
183
+ profileDir: string | undefined;
184
+ }
185
+ /**
186
+ * A runtime resolution that remembers the inputs it was computed from. Worker environment data carries only its
187
+ * fields; the inputs stay private to the thread that computed it.
188
+ */
189
+ export declare class ProfileRuntimeResolution implements RuntimeResolution {
190
+ #private;
191
+ readonly profilesDir: string;
192
+ readonly profileDir: string | undefined;
193
+ readonly localPackageNames: readonly string[];
194
+ readonly entries: readonly RuntimeResolutionEntry[];
195
+ readonly linkedRoots: readonly LinkedRoot[];
196
+ /**
197
+ * @param source - inputs of {@link createRuntimeResolution}, reused by {@link computeLatestResolution}.
198
+ * @param table - the computed package table.
199
+ */
200
+ constructor(source: ResolutionSource, table: RuntimeResolution);
201
+ /**
202
+ * Compute the latest generation from the same installation, profile directory, and Harness home, rereading the
203
+ * profile's manifest, bundle selection, and installed packages from disk, without retaining synthetic layers.
204
+ * With no profile directory, only installation packages are recomputed. This instance is unchanged.
205
+ * @returns a new resolution for the latest generation.
206
+ */
207
+ computeLatestResolution(): Promise<ProfileRuntimeResolution>;
208
+ }
179
209
  /**
180
210
  * Read a profile's manifest.
181
211
  * @param binName - the diagnostic prefix on the thrown error.
@@ -206,7 +236,8 @@ export declare function resolveBundleDir(binName: string, packageName: string, i
206
236
  * Load an already initialized profile directory without resolving it through
207
237
  * the shared Harness home. This is used by application-owned profiles whose
208
238
  * package project and lifecycle belong to that application.
209
- * Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped
239
+ * Retired bundles are removed from the stored bundle list first, rewriting the
240
+ * manifest when it listed one. Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped
210
241
  * without changing the manifest and listed in `skippedBundles`; nothing is printed.
211
242
  * @param binName - the diagnostic prefix on thrown errors.
212
243
  * @param dir - absolute profile package directory.
@@ -242,4 +273,5 @@ export declare function loadProfile(binName: string, name: string, installAnchor
242
273
  * @returns the composed entry list.
243
274
  */
244
275
  export declare function composeEntries(layers: readonly PatchOptions[][], warn?: (message: string) => void): EntryOptions[];
276
+ export {};
245
277
  //# sourceMappingURL=profile.d.ts.map
@@ -249,10 +249,10 @@ var ResolutionRouter = class {
249
249
  if (successor.profilesDir !== this.current.profilesDir || successor.profileDir !== this.current.profileDir) throw new Error("profile resolution: a successor cannot change its profile scope");
250
250
  for (const [name, current] of this.current.entries) {
251
251
  const next = entries.get(name);
252
- if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || !sameResolution(current.declarer, next.declarer) || current.version !== next.version || current.scope !== next.scope) throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
252
+ if (next === void 0 && current.scope === "profile") continue;
253
+ if (next === void 0 || !sameResolution(current.packageDir, next.packageDir) || current.scope === "installation" && !sameResolution(current.declarer, next.declarer) || current.version !== next.version || current.scope !== next.scope) throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`);
253
254
  }
254
255
  const localPackageNames = new Set(successor.localPackageNames);
255
- for (const name of this.current.localPackageNames) if (!localPackageNames.has(name)) throw new Error(`profile resolution: removing local package ${JSON.stringify(name)} requires a process restart`);
256
256
  for (const name of localPackageNames) if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) throw new Error(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`);
257
257
  for (const root of successor.linkedRoots) {
258
258
  const previous = this.linkedTargets.get(root.name);
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.2.0-rc.1",
4
+ "version": "0.2.1-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,29 +38,29 @@
38
38
  "node-addon-require-builtin": "^0.1.6",
39
39
  "resolve.exports": "^2.0.3",
40
40
  "semver": "^7.8.5",
41
- "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1",
42
- "@deepseek-ai/dsh-package-manifest": "0.2.0-rc.1"
41
+ "@deepseek-ai/dsh-atomic-write": "0.2.1-alpha.1",
42
+ "@deepseek-ai/dsh-package-manifest": "0.2.1-alpha.1"
43
43
  },
44
44
  "peerDependencies": {
45
- "@deepseek-ai/cordis-plugin-include": "~1.0.9",
46
- "@deepseek-ai/cordis-plugin-group": "~1.0.4",
47
- "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
48
- "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
49
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
50
- "@deepseek-ai/cordis": "~4.0.4",
51
- "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.1"
45
+ "@deepseek-ai/cordis-plugin-group": "~1.0.5-alpha.1",
46
+ "@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.1",
47
+ "@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.1",
48
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.1",
49
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.1",
50
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1",
51
+ "@deepseek-ai/dsh-home-paths": "0.2.1-alpha.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/js-yaml": "^4.0.9",
55
55
  "@types/semver": "^7.8.0",
56
- "@deepseek-ai/schemastery": "~3.18.4",
57
- "@deepseek-ai/cordis-plugin-group": "~1.0.4",
58
- "@deepseek-ai/cordis-plugin-include": "~1.0.9",
59
- "@deepseek-ai/cordis-plugin-loader": "~1.0.5",
60
- "@deepseek-ai/cordis-plugin-timer": "~1.1.6",
61
- "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.1",
62
- "@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
63
- "@deepseek-ai/cordis": "~4.0.4",
64
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1"
56
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.1",
57
+ "@deepseek-ai/cordis-plugin-group": "~1.0.5-alpha.1",
58
+ "@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.1",
59
+ "@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.1",
60
+ "@deepseek-ai/cordis-plugin-timer": "~1.1.7-alpha.1",
61
+ "@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.1",
62
+ "@deepseek-ai/dsh-home-paths": "0.2.1-alpha.1",
63
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.1",
64
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
65
65
  }
66
66
  }