@hanamesh/dsh-app-host 0.6.21 → 0.6.22

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.md CHANGED
@@ -1,6 +1,6 @@
1
1
  0.6.21 候选:普通市场和网站交接消费 Indexer 的固定 GitHub SHA/子目录/包名/构建名单。客户端沿原确认页展示来源、完整 commit 与本机构建提示;确认回传同一目标,目录更新不得换 SHA。执行仍走受控官方 DSH CLI / pnpm,读回精确来源与启用项后报告需完全退出重开;没有改上游或另写安装器。详见 [安装目标](docs/INSTALL_TARGET.md)。初轮已完成隔离 DSH、网站及真实 DTO 验收;本轮 R25 另修未匹配固定目标时的拒绝与退休目录版本字段,仍待复审,不宣称已发布。
2
2
 
3
- 0.6.16 候选:普通市场通过注入的宿主 pluginManager.registries() 读取源事实,并按宿主锚点/版本守卫加载公开 ./registry 规则模块;AppHost 不声明、安装或捆绑 manager。发布依赖/peer闭包与0.6.15一致。pnpm自有源(null)不显式传--registry,配置镜像则传同一源;查询30秒具名超时,脱敏原诊断同进UI和日志。
3
+ 0.6.22 候选:普通插件只执行目录固定目标;缺 installMethods 即拒绝。套件安装命令只消费 Core,删除普通包名的元数据查询和规则加载。
4
4
 
5
5
  0.6.15 候选:市场保留官方 CLI 的 stdout/stderr 真实失败原因并脱敏,在卡片与安装确认的技术详情中显示。正文显示 pnpm 真实错误类别;宿主发布时间检查失败时列出受影响包与实际 cutoff;HanaMesh 总包安装/升级使用精确发布版本,发布后即可验收;不修改 profile、pnpm 供应链策略或安装执行路径。
6
6
 
@@ -82,7 +82,7 @@ CLI-8:未配置来源的新用户默认请求 HanaMesh 视图(`https://marke
82
82
 
83
83
  > 分支 `t6-usage-receipts`(2026-09-22,设计定案 §3「协议层」,T6,**未发版**,由派发 session 合入下一 rc):**Router 使用回执。** app-host 自己的 storage domain `hanamesh_router_receipts` 按小时记 `{appId, providerId, model, hour, count}`(Router 每次注入记路由与 `injections`,网关每次转发 `count+1`;不记内容、不记值),`GET /hanamesh/router/receipts[?appId=]` 可查本地账。usage 席位上报的 `use` 事件改为在 **UTC 小时结束或应用最后一个活实例停止时**发出(`occurredAt` 仍是该小时首个转发请求;同 key 一小时一条不变),并附 `targetRef=appId` 与该小时的 `receipt {providerId, model, count}`;无席位只记本地账不报错;宿主死机前未报的小时在下次启动由账本补报(席位按 key 去重)。需要 usage `0.2.0-rc.7`(席位接受三个可选键;更早版本会 `rejected INVALID_RECORD_INPUT`,只 debug 日志)。
84
84
 
85
- > rc.28(2026-09-22,设计定案 §4「HanaMesh 市场」,T4):**「应用库」升级为 HanaMesh 市场——插件与应用同一入口、同一安装路径。** 侧栏入口与页面标题改为「市场」/「HanaMesh 市场」,设置段「应用库来源」改名「市场目录源」。目录默认列出**全部**类别(客户端显式 `category=`),筛选 = 全部 / 应用(`hanamesh-app`)/ 插件(客户端视图:非应用的 npm 条目)/ 目录页里出现过的每个类别;搜索、「更多」保留。每条目带 `kind`(`application` / `plugin` / `listing`)、`installed`(已装行)与 `upgradeAvailable`(目录 `latestVersion` 严格新于已装版本,内建 semver 比较,无依赖)。卡片状态:未装「安装」/ 已装(应用「打开」「卸载」,插件「卸载」)/ 可升级「升级到 x.y.z」(同一 `dsh plugin add --save-exact` 路径)/ 需重启。**已装列表含插件**:扫 profile `package.json` 的 `dependencies`(减去应用包),`bundle`(声明 `dsh.bundle`)与 `active`(在 `dsh.profile.bundles`);「需重启」由本插件启动时的依赖快照推出:启动后新增 → `installed-not-loaded`,启动后移除 → `uninstalled-not-unloaded`。任一操作完成或列表含上述状态时页面顶部出现「需重启 DSH」:在桌面壳内(DSH 页在壳 iframe 里)给出 `hanamesh://restart` 深链按钮,官方 DSH 直开浏览器时只提示「请手动重启 DSH」。新路由:`GET /hanamesh/library/installedPlugins` → `{plugins,apps,restartRequired}`;`POST /hanamesh/library/plugins/install {packageName}`(须在启用目录里能搜到该包;应用包走应用路径)与 `POST /hanamesh/library/plugins/uninstall {packageName}`(应用包转应用卸载:runtime + 运行中检查)→ 202 + 事件流;`POST /hanamesh/library/install` 现接受 `{itemId, packageName?}`(`packageName` 用于目录精确查找,插件条目也可装)。旧路由全部保留。**修 `LIB-PROVISION-INPUT`**:`provision` / `uninstall` / `plugins/*` 缺字段或字段非法 → 400 `INVALID_INPUT`,操作不启动(此前 TypeError → 事件 `LIBRARY_OPERATION_FAILED`)。受保护包 `hanamesh-core` / `hanamesh-usage` / `@hanamesh/dsh-app-host`(含旧写法)永不经市场装卸(`PACKAGE_DENIED`;rc.27 前阻止名单写的是从未存在过的 `@hanamesh/dsh-core`)。不做 T5(`provide('market')`、dshmarket 互斥、recommender 接线)。AH-M01–M08 测试,M13/M14 变异;真实门见 `docs/acceptance/t4-market-20260922/`。
85
+ > rc.28(2026-09-22,设计定案 §4「HanaMesh 市场」,T4):**「应用库」升级为 HanaMesh 市场——插件与应用同一入口、同一安装路径。** 侧栏入口与页面标题改为「市场」/「HanaMesh 市场」,设置段「应用库来源」改名「市场目录源」。目录默认列出**全部**类别(客户端显式 `category=`),筛选 = 全部 / 应用(`hanamesh-app`)/ 插件(客户端视图:非应用的 npm 条目)/ 目录页里出现过的每个类别;搜索、「更多」保留。每条目带 `kind`(`application` / `plugin` / `listing`)、`installed`(已装行)与 `upgradeAvailable`(审核 npm `installTarget.version` 严格新于已装版本)。卡片状态:未装「安装」/ 已装(应用「打开」「卸载」,插件「卸载」)/ 可升级「升级」(同一 `dsh plugin add --save-exact` 路径)/ 需重启。**已装列表含插件**:扫 profile `package.json` 的 `dependencies`(减去应用包),`bundle`(声明 `dsh.bundle`)与 `active`(在 `dsh.profile.bundles`);「需重启」由本插件启动时的依赖快照推出:启动后新增 → `installed-not-loaded`,启动后移除 → `uninstalled-not-unloaded`。任一操作完成或列表含上述状态时页面顶部出现「需重启 DSH」:在桌面壳内(DSH 页在壳 iframe 里)给出 `hanamesh://restart` 深链按钮,官方 DSH 直开浏览器时只提示「请手动重启 DSH」。新路由:`GET /hanamesh/library/installedPlugins` → `{plugins,apps,restartRequired}`;`POST /hanamesh/library/plugins/install {packageName}`(须在启用目录里能搜到该包;应用包走应用路径)与 `POST /hanamesh/library/plugins/uninstall {packageName}`(应用包转应用卸载:runtime + 运行中检查)→ 202 + 事件流;`POST /hanamesh/library/install` 现接受 `{itemId, packageName?}`(`packageName` 用于目录精确查找,插件条目也可装)。旧路由全部保留。**修 `LIB-PROVISION-INPUT`**:`provision` / `uninstall` / `plugins/*` 缺字段或字段非法 → 400 `INVALID_INPUT`,操作不启动(此前 TypeError → 事件 `LIBRARY_OPERATION_FAILED`)。受保护包 `hanamesh-core` / `hanamesh-usage` / `@hanamesh/dsh-app-host`(含旧写法)永不经市场装卸(`PACKAGE_DENIED`;rc.27 前阻止名单写的是从未存在过的 `@hanamesh/dsh-core`)。不做 T5(`provide('market')`、dshmarket 互斥、recommender 接线)。AH-M01–M08 测试,M13/M14 变异;真实门见 `docs/acceptance/t4-market-20260922/`。
86
86
 
87
87
  > rc.27(2026-09-21,用户定,STATUS `P2-USE-EVENTS`):**应用的使用证据由 app-host 上报。** 此前 app-host 从不调用 usage 插件的 record 座位,应用既没有 `open` 也没有 `use` 事件。现在:`ctx.inject(['hanameshUsage'])` 可选取得座位(鸭子类型 `record()`,三插件仍互不 import;缺席/不兼容/抛错/`rejected` 只 debug 日志,永不阻止启动或转发);`open` = 实例到达 `ready` 一次(`open:<appId>:<instanceId>`);`use` = 该实例网关真正转发了已授权请求且应用回应非 401/403,每应用每 UTC 小时一次(`use:<appId>:<YYYYMMDDHH>`,时间取桶内第一个请求);引导票据、网关拒绝、宿主就绪探针不计。`hanaRef` = 应用 npm 包名:描述符新增可选 `packageName`(向后兼容),缺省由启动时的已安装扫描 `host.bindPackageName()` 绑定,两者都没有则该应用不产生证据。`sourcePlugin = '@hanamesh/dsh-app-host'`。新面:`host.packageName/bindPackageName/onActivity`、`FixedGateway({onForward})`、`createUsageEvidence()`、`list().apps[].packageName`。契约见 `docs/CONTRACT.md`「应用使用证据」;AH-U01–U07 测试(U07 为真实 Cordis 根,座位先于/后于本插件提供两种顺序),M11/M12 变异。
88
88
 
@@ -98,7 +98,7 @@ CLI-8:未配置来源的新用户默认请求 HanaMesh 视图(`https://marke
98
98
  >
99
99
  > rc.19(2026-09-21):网关不再只认 cookie——嵌入式 webview(HanaMesh 桌面 Tauri/WKWebView、开了跟踪防护的 WebView2)里应用 iframe 相对壳的顶层 origin 是第三方,`SameSite=Strict` 的引导 cookie 被丢弃,303 之后每个请求都 403(响应带 `frame-ancestors 'none'`)→ 用户看到空白 iframe(2026-09-21 桌面 rc.4 + Vibe 实测)。rc.19 在 cookie 缺席时改用 Fetch Metadata:`sec-fetch-site: same-origin`(应用自身子资源/XHR)、或 `same-site` + `iframe` + 父 origin referer(引导后的框架导航)、或应用 origin 的 WebSocket 握手,且必须已有一张为仍在有效期 lease 消费过的引导票;cross-site / `none`(地址栏)一律拒绝。H15 三条测试;浏览器直开仍走 cookie。
100
100
  >
101
- > rc.18(2026-09-20):应用库卡片跟踪安装/补齐运行时/卸载操作——按钮进入「…中…」,订阅 `/hanamesh/library/events` 直到 `-done`/`-failed`,失败时在卡片上显示错误码与可读原因(如 `REGISTRY_LOOKUP_FAILED`:应用包不在当前 registry),并提供「重试安装」。用户 2026-09-20 实测:点安装无任何反应。宿主逻辑不变。
101
+ > rc.18(2026-09-20):应用库卡片跟踪安装/补齐运行时/卸载操作——按钮进入「…中…」,订阅 `/hanamesh/library/events` 直到 `-done`/`-failed`,失败时在卡片上显示错误码与可读原因(如 `NOT_INSTALLABLE`:目录没有审核过的固定目标),并提供「重试安装」。用户 2026-09-20 实测:点安装无任何反应。宿主逻辑不变。
102
102
 
103
103
  **交付状态:rc.3 用户 ACCEPTED 2026-09-13;rc.4–rc.12 为增量 🧪;rc.13 只补上架前提(MIT 许可证、`repository` 字段、DSH peer 精确钉 `0.1.5-alpha.1`);rc.14 删除客户端里对 `hanameshCore` 的死读取(浏览器侧 cordis 上下文不含宿主服务,该读取恒为 null,且把「未安装 HanaMesh Core」误显给已装用户)——本包对 Core 现在零引用,Core 状态只在 Core 自己的设置段;仍待用户验收。**
104
104
 
@@ -240,4 +240,6 @@ Actions runs on the standard public/free `macos-15` arm64 runner because the exi
240
240
 
241
241
  ## Core 公开套件声明的测试依赖
242
242
 
243
- 源码测试通过 `@hanamesh/core/suite` 读取 Core 单源公开命令。`@hanamesh/core` 是仓库声明的 devDependency,按 npm 版本范围 `^0.3.28` 从公开 npm 安装,冻结 pnpm 锁记录实际版本与 SRI;许可证记录见 `docs/LICENSES.json`。Core 不成为运行时依赖或 bundled 包。
243
+ 源码测试通过 `@hanamesh/core/suite` 读取 Core 单源公开命令。`@hanamesh/core` 是仓库声明的 devDependency,按 npm 版本范围 `^0.3.30` 从公开 npm 安装,冻结 pnpm 锁记录实际版本与 SRI;许可证记录见 `docs/LICENSES.json`。Core 不成为运行时依赖或 bundled 包。
244
+
245
+ 套件升级的执行定义来自可信 Cordis Core 声明 `upgrade.installation:{name,version,command,args}`;AppHost将实际profile与可选registry交给Core,使用返回的完整argv,不自行加flag、解析命令前缀或补默认npm源。缺定义时具名拒绝,参数纳入确认快照。该数据接口避免AppHost在Core新版本发布前依赖未发布SDK。
package/dist/client-ui.js CHANGED
@@ -129,15 +129,11 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
129
129
  "copy121": "固定规则(不能关)",
130
130
  "copy122": "为什么:",
131
131
  "copy123": "安装命令返回成功,但 profile 读回的版本与固定版本不一致",
132
- "copy124": "无法查询 DSH 配置的 registry;检查 DSH 源设置与网络,展开技术详情后手动重试",
133
- "registryLookupTimeout": "registry 查询超过 30 秒;检查 DSH 源设置与网络后手动重试",
134
- "registryConfigUnavailable": "DSH 未提供可读的源配置;检查客户端插件管理器,展开技术详情",
135
132
  "copy125": "DSH 安装命令失败,请展开技术详情查看原始原因",
136
- "installAgeRejected": "宿主的依赖发布时间检查未通过(默认需发布满 24 小时,以技术详情中的 cutoff 为准)。已知 DSH 0.2.0-rc.2 内置 pnpm 11.7.0 对同名多条白名单只认第一条。请在 DSH 设置中检查当前版本并升级 DSH,或等待受影响版本满足规则后手动重试。",
133
+ "installAgeRejected": "宿主的依赖发布时间检查未通过。请核对安装目标包含精确版本并保留技术详情;若仍失败,请在 DSH 设置中检查并升级 DSH。",
137
134
  "installAffectedPackages": "受影响包:",
138
135
  "copy126": "当前宿主未提供安装参数(profile / node 路径)",
139
136
  "copy127": "目录里已没有这一项,刷新后重试",
140
- "copy128": "registry 上的最新版是预发布版(rc),客户端未允许安装预发布版",
141
137
  "copy129": "运行时归档下载失败,检查网络或来源",
142
138
  "copy130": "10 分钟内没有结果,可稍后重试",
143
139
  "copy131": "HanaMesh 套件自身不能从市场安装或卸载",
@@ -434,15 +430,11 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
434
430
  "copy121": "Required rules",
435
431
  "copy122": "Reason: ",
436
432
  "copy123": "Installation command succeeded, but the version read from the profile differs from the confirmed version",
437
- "copy124": "Cannot query the registry configured in DSH. Check DSH source settings and network, expand technical details, then retry manually",
438
- "registryLookupTimeout": "Registry lookup exceeded 30 seconds. Check DSH source settings and network, then retry manually",
439
- "registryConfigUnavailable": "DSH did not expose readable registry configuration. Check the client plugin manager and expand technical details",
440
433
  "copy125": "DSH installation failed. Expand Technical details for the original cause",
441
- "installAgeRejected": "The host rejected a dependency's release age (the default requires 24 hours since publication; see the actual cutoff in Technical details). Known issue: pnpm 11.7.0 bundled with DSH 0.2.0-rc.2 only recognizes the first allowlist entry for a package name. Check your version in DSH Settings and upgrade DSH, or wait until the affected versions meet the rule before trying again manually.",
434
+ "installAgeRejected": "The host rejected a dependency release-age check. Verify the exact installation target and retain Technical details. If it still fails, check and upgrade DSH in Settings.",
442
435
  "installAffectedPackages": "Affected packages: ",
443
436
  "copy126": "This host has not provided installation parameters (profile / Node path)",
444
437
  "copy127": "This item is no longer in the catalog. Refresh before trying again",
445
- "copy128": "The latest registry version is a prerelease, which this client has not allowed",
446
438
  "copy129": "Runtime archive download failed. Check the network or source",
447
439
  "copy130": "No result within 10 minutes. Check the installed state before trying again",
448
440
  "copy131": "The HanaMesh suite cannot be installed or uninstalled through the market",
@@ -841,12 +833,12 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
841
833
  defaults&&names?h('div',null,h('h3',null,t('设置名称与默认值')),h('dl',null,...['enabled','ports','allowedOrigins','ttlMs','pollMs'].flatMap(key=>[h('dt',{key:key+'-name'},names[key]),h('dd',{key:key+'-value'},Array.isArray(defaults[key])?defaults[key].join(', '):typeof defaults[key]==='boolean'?(defaults[key]?t('开'):t('关')):String(defaults[key]))]))):null,
842
834
  value?.rules?h('div',null,h('h3',null,t('固定规则(不能关)')),h('ul',null,...value.rules.map(row=>h('li',{key:row.rule},t(row.rule),h('br',null),h('small',null,t('为什么:')+t(row.why)))))):null));
843
835
  }
844
- const FAILURE_REASONS={...CORE_SUITE_FAILURES,INSTALL_READBACK_MISMATCH:t('安装命令返回成功,但 profile 读回的版本与固定版本不一致'),REGISTRY_LOOKUP_FAILED:t('无法查询 DSH 配置的 registry;检查 DSH 源设置与网络,展开技术详情后手动重试'),REGISTRY_LOOKUP_TIMEOUT:t('registry 查询超过 30 秒;检查 DSH 源设置与网络后手动重试'),REGISTRY_CONFIG_UNAVAILABLE:t('DSH 未提供可读的源配置;检查客户端插件管理器,展开技术详情'),DSH_PLUGIN_FAILED:t('DSH 安装命令失败,请展开技术详情查看原始原因'),LIBRARY_INSTALL_UNAVAILABLE:t('当前宿主未提供安装参数(profile / node 路径)'),CATALOG_ITEM_MISSING:t('目录里已没有这一项,刷新后重试'),UNSTABLE_VERSION:t('registry 上的最新版是预发布版(rc),客户端未允许安装预发布版'),RUNTIME_ARCHIVE_UNAVAILABLE:t('运行时归档下载失败,检查网络或来源'),OPERATION_TIMEOUT:t('10 分钟内没有结果,可稍后重试'),PACKAGE_DENIED:t('HanaMesh 套件自身不能从市场安装或卸载'),INSTANCE_IN_USE:t('先关闭该应用的所有视图再卸载')};
836
+ const FAILURE_REASONS={...CORE_SUITE_FAILURES,INSTALL_READBACK_MISMATCH:t('安装命令返回成功,但 profile 读回的版本与固定版本不一致'),DSH_PLUGIN_FAILED:t('DSH 安装命令失败,请展开技术详情查看原始原因'),LIBRARY_INSTALL_UNAVAILABLE:t('当前宿主未提供安装参数(profile / node 路径)'),CATALOG_ITEM_MISSING:t('目录里已没有这一项,刷新后重试'),RUNTIME_ARCHIVE_UNAVAILABLE:t('运行时归档下载失败,检查网络或来源'),OPERATION_TIMEOUT:t('10 分钟内没有结果,可稍后重试'),PACKAGE_DENIED:t('HanaMesh 套件自身不能从市场安装或卸载'),INSTANCE_IN_USE:t('先关闭该应用的所有视图再卸载')};
845
837
  const failureReason=(code,details)=>{
846
838
  const diagnostic=code==='DSH_PLUGIN_FAILED'?details?.diagnostic??'':'',pnpmCode=diagnostic.match(/\bERR_PNPM_[A-Z0-9_]+\b/)?.[0];
847
839
  if(pnpmCode==='ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION'){
848
840
  const packages=[...new Set([...diagnostic.matchAll(/((?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)\s+was published\b/g)].map(match=>match[1]))];
849
- return pnpmCode+' · '+t('宿主的依赖发布时间检查未通过(默认需发布满 24 小时,以技术详情中的 cutoff 为准)。已知 DSH 0.2.0-rc.2 内置 pnpm 11.7.0 对同名多条白名单只认第一条。请在 DSH 设置中检查当前版本并升级 DSH,或等待受影响版本满足规则后手动重试。')+(packages.length?' '+t('受影响包:')+packages.join(', '):'');
841
+ return pnpmCode+' · '+t('宿主的依赖发布时间检查未通过。请核对安装目标包含精确版本并保留技术详情;若仍失败,请在 DSH 设置中检查并升级 DSH。')+(packages.length?' '+t('受影响包:')+packages.join(', '):'');
850
842
  }
851
843
  return(pnpmCode?pnpmCode+' · ':'')+(t(FAILURE_REASONS[code])??t('详细原因见宿主日志'));
852
844
  };
package/dist/dsh.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface DshPluginConfig {
13
13
  /** `sources` undefined → the HanaMesh catalog source; explicit `[]` → none. `profileDir`/`profileName`/`dshBin` (and
14
14
  * `nodeBinary`) are inferred from the proven Desktop layout or plain DSH when undefined — see docs/CONTRACT.md 「应用库配置」; explicit values win. */
15
15
  /** rc.28: the same keys serve the HanaMesh market (plugins + applications; routes `/hanamesh/library*`, see docs/CONTRACT.md「市场」). */
16
- library?:{fixture?:string;sources?:Array<{manifestUrl:string;enabled?:boolean}>;profileDir?:string;profileName?:string;dshBin?:string;registry?:string;allowPrerelease?:boolean;eventRetention?:number;installationReaders?:Partial<Record<'claude'|'codex',InstallationReaderConfig>>};
16
+ library?:{fixture?:string;sources?:Array<{manifestUrl:string;enabled?:boolean}>;profileDir?:string;profileName?:string;dshBin?:string;registry?:string;eventRetention?:number;installationReaders?:Partial<Record<'claude'|'codex',InstallationReaderConfig>>};
17
17
  applications?: AppDefinition[]; leaseTtlMs?: number; sweepIntervalMs?: number;
18
18
  }
19
19
  /** Adapt an open `ctx.storageDomain` handle (single layout, global snapshot) to the store binding. */
package/dist/dsh.js CHANGED
@@ -73,7 +73,7 @@ export const Config = z.object({
73
73
  sources:z.union([z.array(z.any()),z.const(undefined)]),
74
74
  /** Locations are inferred from the verified Desktop argv/profile or plain DSH (see library/locate.js). */
75
75
  profileDir:z.string(),profileName:z.string(),dshBin:z.string(),
76
- registry:z.string(),allowPrerelease:z.boolean(),
76
+ registry:z.string(),
77
77
  /** Publicly named event history capacity; a lost cursor reports resetRequired. */
78
78
  eventRetention:z.number().default(512),
79
79
  /** Explicit official CLI readers; unset means unknown, never inferred from PATH or private files. */
@@ -236,8 +236,6 @@ export async function apply(ctx, config) {
236
236
  locations.inferred.map(key=>[key,key==='sources'?locations.sources.map(source=>source.manifestUrl):locations[key]]))));
237
237
  // Exact-save capability comes from the host's public package metadata. Older managers use the public CLI
238
238
  // for installs and preserve restart-required; applications and compensation also use that CLI.
239
- let pluginManager;
240
- optional('pluginManager', service => { pluginManager=service; });
241
239
  let dshBin=locations.dshBin;
242
240
  if(!dshBin&&locations.profileDir&&!desktop)try{dshBin=createRequire(join(locations.profileDir,'package.json')).resolve('@deepseek-ai/dsh/lib/bin.js');}catch{dshBin=undefined;}
243
241
  const cliUsable=Boolean(locations.desktopLauncher||dshBin&&(!desktop||locations.desktopCli||config.library?.dshBin));
@@ -245,9 +243,9 @@ export async function apply(ctx, config) {
245
243
  if(locations.profileDir&&locations.profileName){
246
244
  provisionApi=await import('@hanamesh/lib-provision');
247
245
  installer=nodeBinding(()=>nodeRuntime.executable,nodeBinary=>createLibraryInstaller({profileDir:locations.profileDir,profileName:locations.profileName,dataRoot,
248
- ...(cliUsable?{nodeBinary,dshBin,desktopCli:locations.desktopCli,desktopLauncher:locations.desktopLauncher}:{}),pluginManagerVersion:locations.pluginManagerVersion,profileWriter,registryRules:locations.pluginManagerRegistry?()=>import(pathToFileURL(locations.pluginManagerRegistry).href):undefined,
249
- registry:libraryConfig.registry,allowPrerelease:libraryConfig.allowPrerelease,provision:provisionApi.provision,remove:provisionApi.remove,ledger:provisionApi.ledger,
250
- coreService:()=>coreService,suiteSourceService:()=>suiteSourceService,suiteSourceMode:()=>library?.suiteSourceSettings().mode??'configured',pluginManager:()=>pluginManager,emit:event=>library?.emit(event)}));
246
+ ...(cliUsable?{nodeBinary,dshBin,desktopCli:locations.desktopCli,desktopLauncher:locations.desktopLauncher}:{}),pluginManagerVersion:locations.pluginManagerVersion,profileWriter,
247
+ registry:libraryConfig.registry,provision:provisionApi.provision,remove:provisionApi.remove,ledger:provisionApi.ledger,
248
+ coreService:()=>coreService,suiteSourceService:()=>suiteSourceService,suiteSourceMode:()=>library?.suiteSourceSettings().mode??'configured',emit:event=>library?.emit(event)}));
251
249
  }
252
250
  /** What a person must do so the library can install, in their words (C-MARKET probe `error.need`). */
253
251
  const unavailable=()=>{
@@ -94,8 +94,8 @@ export async function sourceRecoveryProfile({recovery},item={}){
94
94
  if(item.mode==='upgrade')await profile.seed(target.packageName,app.previous);
95
95
  for(const [command,phase,skip] of item.inject??[])profile.fail(command,phase,skip);
96
96
  if(item.concurrent)profile.concurrentAdd('@fixture/concurrent','1.0.0');
97
- // Named single-source contract fixture; not production registry policy.
98
- const installer=emit=>createLibraryInstaller({registryRules:async()=>({registryPlan:(_requested,configured)=>[configured.registry],attributeFailure:()=>'other'}),pluginManager:()=>({registries:async()=>({registry:'https://source-fixture.invalid/',fallbackRegistries:[],resolved:'https://source-fixture.invalid/'})}),profileDir:profile.profileDir,profileName:profile.profileName,dataRoot:profile.dataRoot,nodeBinary:'/source-fixture/bin/node',dshBin:'/source-fixture/dsh/lib/bin.js',
97
+ // The catalog fixture already supplies the exact reviewed version.
98
+ const installer=emit=>createLibraryInstaller({profileDir:profile.profileDir,profileName:profile.profileName,dataRoot:profile.dataRoot,nodeBinary:'/source-fixture/bin/node',dshBin:'/source-fixture/dsh/lib/bin.js',
99
99
  fetchImpl:profile.fetchImpl({[app.packageName]:app.latest,[plugin.packageName]:plugin.latest}),spawn:profile.spawn,provision:profile.provision,remove:profile.remove,ledger:profile.ledger,emit});
100
100
  return{profile,target,installer};
101
101
  }
@@ -27,7 +27,6 @@ export function installationDiagnostic(error){
27
27
  cause=field(cause,'cause');
28
28
  }
29
29
  // Only known installer fields, never arbitrary details/stack/response objects.
30
- if(error instanceof AppHostError&&error.code==='REGISTRY_LOOKUP_FAILED'&&Number.isInteger(error.details?.status))lines.push(`Registry HTTP status: ${error.details.status}`);
31
30
  if(error instanceof AppHostError&&error.code==='DSH_PLUGIN_FAILED'){
32
31
  lines.push(`CLI exit: ${Number.isInteger(error.details?.code)?error.details.code:'unknown'}; signal: ${clean(error.details?.signal,80)||'none'}`);
33
32
  lines.push(`stderr: ${clean(error.details?.stderr)||'<empty>'}`);
@@ -34,7 +34,7 @@ export function packageInstallTarget(value){
34
34
 
35
35
  export const installTargetName=target=>target.source==='github'?target.packageName:target.name;
36
36
  export const sameInstallTarget=(left,right)=>JSON.stringify(packageInstallTarget(left))===JSON.stringify(packageInstallTarget(right));
37
- export const catalogInstallationAllowed=item=>item?.installMethods===undefined?item?.oneClickInstall?.allowed!==false:item?.oneClickInstall?.allowed===true;
37
+ export const catalogInstallationAllowed=item=>Array.isArray(item?.installMethods)&&item?.oneClickInstall?.allowed===true;
38
38
  export function catalogPackageTargets(item){
39
39
  if(!catalogInstallationAllowed(item))return[];
40
40
  return (item.installMethods??[]).filter(method=>['hanamesh','dsh'].includes(method.channel)&&method.target)
@@ -8,7 +8,7 @@ import { redact } from '../runtime.js';
8
8
  import { AppHostError, requireCondition, SerialQueue } from '../errors.js';
9
9
  import {noteFailurePhase} from './diagnostics.js';
10
10
  import { validateDefinition } from '../descriptor.js';
11
- import { itemKind } from './catalog.js';
11
+ import { isApplication } from './catalog.js';
12
12
  import { profileSnapshot, runtimeSnapshot, profileChecks, runtimeChecks, packageRestoreArgs, keepOriginals } from './recovery.js';
13
13
  import {SUITE_BUNDLE,prepareSuiteUpgrade,readSuiteStatus,createCoreSuiteAdapter} from './suite.js';
14
14
  import {packageInstallTarget,installTargetName,pinnedGithubSpec} from './github-target.js';
@@ -17,7 +17,6 @@ import {packageInstallTarget,installTargetName,pinnedGithubSpec} from './github-
17
17
  export const PROTECTED_PACKAGES=Object.freeze([SUITE_BUNDLE,'@hanamesh/core','@hanamesh/usage','@hanamesh/dsh-app-host','hanamesh-brand']);
18
18
  const blocked=new Set(PROTECTED_PACKAGES);
19
19
  const packageName=value=>typeof value==='string'&&/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(value);
20
- const versionText=value=>typeof value==='string'&&/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
21
20
  async function json(path){return JSON.parse(await readFile(path,'utf8'));}
22
21
  const defaultSpawn=spawnGitSafe;
23
22
 
@@ -46,8 +45,8 @@ async function clearGeneratedRuntimeCache(result,runtimeRoot){
46
45
  }
47
46
 
48
47
  /** Package mutations use the public CLI in an owned monitored process; upstream's in-process manager has no scoped env/monitor port. */
49
- export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBinary,dshBin,desktopCli,desktopLauncher,pluginManagerVersion,registryRules,registry,allowPrerelease=false,
50
- fetchImpl=globalThis.fetch,spawn=defaultSpawn,provision,remove,ledger,coreSuitePlan,coreService=()=>undefined,suiteSourceMode=()=>'configured',suiteSourceService=()=>undefined,pluginManager=()=>undefined,emit=()=>{},profileWriter=new SerialQueue()}){
48
+ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBinary,dshBin,desktopCli,desktopLauncher,pluginManagerVersion,registry,
49
+ spawn=defaultSpawn,provision,remove,ledger,coreSuitePlan,coreService=()=>undefined,suiteSourceMode=()=>'configured',suiteSourceService=()=>undefined,emit=()=>{},profileWriter=new SerialQueue()}){
51
50
  requireCondition(typeof profileDir==='string'&&resolve(profileDir)===profileDir,'PROFILE_DIR_REQUIRED','library.profileDir must be an absolute path.');
52
51
  requireCondition(typeof dataRoot==='string'&&resolve(dataRoot)===dataRoot,'INVALID_ROOT','dataRoot must be absolute.');
53
52
  const launcherAvailable=typeof desktopLauncher?.command==='string'&&resolve(desktopLauncher.command)===desktopLauncher.command&&typeof desktopLauncher.home==='string'&&join(desktopLauncher.home,'profiles',profileName)===profileDir;
@@ -66,58 +65,15 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
66
65
  const runtimeApi=async name=>injected[name]??(await defaults())[name];
67
66
  async function installed(packageNameValue){const root=join(profileDir,'node_modules',...packageNameValue.split('/')),pkg=await json(join(root,'package.json'));const appFile=typeof pkg.hanamesh?.app==='string'?pkg.hanamesh.app:'app.json';return{pkg,definition:validateDefinition(await json(join(root,appFile)))};}
68
67
  const commandPath=command=>[dirname(command),...(process.platform==='win32'?(process.env.PATH??process.env.Path??'').split(delimiter):['/opt/homebrew/bin','/usr/local/bin','/usr/bin','/bin'])].join(delimiter);
69
- async function run(args){try{requireCondition(cliAvailable,'DSH_CLI_UNAVAILABLE','The official dsh plugin CLI is unavailable in this host.',{need:['nodeBinary','dshBin']},503);
70
- const options={cwd:profileDir,workerNode:supervisorNode,env:safeGitEnv({HOME:process.env.HOME,DSH_HOME:process.env.DSH_HOME,LANG:process.env.LANG??'C.UTF-8',PATH:commandPath(nodeBinary??desktopLauncher.command)})},tail=['plugin','--profile',profileName,...args];
68
+ const run=args=>runCli(['plugin','--profile',profileName,...args]);
69
+ async function runCli(tail){try{requireCondition(cliAvailable,'DSH_CLI_UNAVAILABLE','The official dsh plugin CLI is unavailable in this host.',{need:['nodeBinary','dshBin']},503);
70
+ const options={cwd:profileDir,workerNode:supervisorNode,env:safeGitEnv({HOME:process.env.HOME,DSH_HOME:process.env.DSH_HOME,LANG:process.env.LANG??'C.UTF-8',PATH:commandPath(nodeBinary??desktopLauncher.command)})};
71
71
  if(launcherAvailable)options.env=safeGitEnv({...options.env,DSH_HOME:desktopLauncher.home,PATH:commandPath(desktopLauncher.command)});
72
72
  const result=await spawn(launcherAvailable?desktopLauncher.command:nodeBinary,launcherAvailable?tail:[...(desktopCli?['--expose-internals',fileURLToPath(new URL('./desktop-cli.js',import.meta.url)),desktopCli.entry,desktopCli.runtimeDir,desktopCli.supportDir]:[dshBin]),...tail],options);
73
73
  const clean=text=>redact(stripVTControlCharacters(String(text??''))).replace(/(https?:\/\/)[^/\s@]+@/g,'$1[REDACTED]@').slice(-2000);
74
74
  const stdout=clean(result.stdout),stderr=clean(result.stderr);
75
75
  requireCondition(result.code===0,'DSH_PLUGIN_FAILED','DSH plugin operation failed.',{code:result.code,signal:result.signal,stderr,diagnostic:[stdout,stderr].filter(Boolean).join('\n')||`DSH plugin exited with code ${result.code}${result.signal?' ('+result.signal+')':''}.`},502);return result;
76
76
  }catch(error){noteFailurePhase(error,'CLI run');throw error;}}
77
- /** Bound the entire pre-install lookup, including public source discovery and metadata readback. */
78
- async function latest(name){
79
- const controller=new AbortController();let timer,phase='registry configuration';
80
- const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>{
81
- const error=new AppHostError('REGISTRY_LOOKUP_TIMEOUT','Registry lookup timed out after 30000ms. Check DSH registry settings and network, then retry explicitly.',{},504);
82
- noteFailurePhase(error,phase);controller.abort(error);reject(error);
83
- },30000);});
84
- async function lookup(){
85
- const manager=pluginManager();
86
- requireCondition(typeof manager?.registries==='function','REGISTRY_CONFIG_UNAVAILABLE','DSH does not expose registry configuration. Restore the public plugin manager before installing.',{},503);
87
- // Public DSH ports own ordering, normalization, de-duplication and private-registry isolation.
88
- const ruleMessage='DSH does not expose its registry rules. Restore the public plugin manager before installing.';
89
- requireCondition(typeof registryRules==='function','REGISTRY_CONFIG_UNAVAILABLE',ruleMessage,{},503);
90
- let rules;try{rules=await registryRules();}catch(cause){const error=new AppHostError('REGISTRY_CONFIG_UNAVAILABLE',ruleMessage,{},503);error.cause=cause;throw error;}
91
- const {registryPlan,attributeFailure}=rules??{};
92
- requireCondition(typeof registryPlan==='function'&&typeof attributeFailure==='function','REGISTRY_CONFIG_UNAVAILABLE',ruleMessage,{},503);
93
- controller.signal.throwIfAborted();const configured=await manager.registries();controller.signal.throwIfAborted();
94
- const plan=registryPlan(registry,configured);
95
- for(let index=0;index<plan.length;index++){
96
- const selected=plan[index],source=selected??configured.resolved;let failureKind='unknown';
97
- controller.signal.throwIfAborted();phase='registry configuration';
98
- requireCondition(typeof source==='string','REGISTRY_CONFIG_UNAVAILABLE','DSH could not resolve its configured registry. Check DSH registry settings.',{},503);
99
- const base=new URL(source);requireCondition(['https:','http:'].includes(base.protocol),'REGISTRY_DENIED','Registry URL is invalid.');
100
- try{
101
- phase='registry fetch';let response;
102
- try{response=await fetchImpl(new URL(`${base.href.replace(/\/$/,'')}/${encodeURIComponent(name)}/latest`),{headers:{accept:'application/json','accept-encoding':'identity'},signal:controller.signal});}
103
- catch(error){failureKind='network';throw error;}
104
- controller.signal.throwIfAborted();if(!response.ok){failureKind=response.status===404?'not-found':response.status>=500?'network':'unknown';throw new AppHostError('REGISTRY_LOOKUP_FAILED','Registry latest lookup failed. Check DSH registry settings and network, then retry explicitly.',{status:response.status},502);}
105
- phase='registry readback';const metadata=await response.json();controller.signal.throwIfAborted();const version=metadata.version;
106
- requireCondition(versionText(version)&&(allowPrerelease||!version.includes('-')),'UNSTABLE_VERSION','Registry latest must be an exact stable version.');
107
- return{version,registry:selected};
108
- }catch(error){
109
- // Ask only fallbacks configured by DSH, and only when its public rule permits another source.
110
- if(!controller.signal.aborted&&index+1<plan.length&&attributeFailure(failureKind,'',{kind:'registry',name,spec:name})==='registry')continue;
111
- throw error;
112
- }
113
- }
114
- }
115
- try{return await Promise.race([lookup(),timeout]);}
116
- catch(cause){
117
- const error=cause instanceof AppHostError?cause:new AppHostError('REGISTRY_LOOKUP_FAILED','Registry lookup failed. Check DSH registry settings and network, then retry explicitly.',{},502);
118
- if(error!==cause)error.cause=cause;noteFailurePhase(error,phase);throw error;
119
- }finally{clearTimeout(timer);}
120
- }
121
77
  /**
122
78
  * rc.10 compensation after a failed install: the package goes back to its baseline spec through the official CLI, runtime
123
79
  * items this operation newly claimed are removed by ownership, a replaced baseline item is provisioned again from the
@@ -162,8 +118,7 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
162
118
  try{
163
119
  base=await profileSnapshot(profileDir,name);
164
120
  stage='before-add';let version;const target=item.installTarget,github=target?.source==='github';
165
- if(target?.source==='npm'){version=target.version;installRegistry=registry??null;}
166
- else if(!github)({version,registry:installRegistry}=await latest(name));
121
+ if(target.source==='npm'){version=target.version;installRegistry=registry??null;}
167
122
  const spec=github?pinnedGithubSpec(target.repo,target.ref,target.path):`${name}@${version}`;
168
123
  const status='restart-required';
169
124
  stage='add';await run(['add','--save-exact',...(installRegistry===null?[]:[`--registry=${installRegistry}`]),...(target?.allowBuild??[]).map(entry=>`--allow-build=${entry}`),spec]);
@@ -188,7 +143,7 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
188
143
  catch(error){emit({type:'library.install-failed',...(item.id?{itemId:item.id}:{}),packageName:name,kind,...(item.installTarget?{installTarget:item.installTarget}:{}),code:error.code??'INSTALL_FAILED',...(error.recovery?{recovery:error.recovery}:{})});throw error;}
189
144
  }
190
145
  let suitePlan;
191
- const suiteProvider=coreSuitePlan??createCoreSuiteAdapter({core:coreService,registry:registry??'https://registry.npmjs.org',suiteSourceMode,suiteSourceService});
146
+ const suiteProvider=coreSuitePlan??createCoreSuiteAdapter({core:coreService,registry,profileName,suiteSourceMode,suiteSourceService});
192
147
  async function previewSuite(){
193
148
  const next=await prepareSuiteUpgrade({coreSuitePlan:suiteProvider});
194
149
  suitePlan={...next,planId:randomUUID()};return structuredClone(suitePlan);
@@ -204,7 +159,7 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
204
159
  requireCondition(suitePlan?.planId===planId,'SUITE_PLAN_CHANGED','升级确认已变化,请重新查看并确认。',{},409);
205
160
  const confirmed=suitePlan;suitePlan=undefined;
206
161
  const fresh=await prepareSuiteUpgrade({coreSuitePlan:suiteProvider});
207
- const identity=plan=>JSON.stringify({sourceBinding:plan.sourceBinding,bundle:plan.bundle,components:plan.components,declarationId:plan.declarationId,registry:plan.registry,selectedUpgrade:plan.selectedUpgrade,physicalBaseline:plan.physicalBaseline});
162
+ const identity=plan=>JSON.stringify({installation:plan.installation,sourceBinding:plan.sourceBinding,bundle:plan.bundle,components:plan.components,declarationId:plan.declarationId,registry:plan.registry,selectedUpgrade:plan.selectedUpgrade,physicalBaseline:plan.physicalBaseline});
208
163
  requireCondition(identity(fresh)===identity(confirmed),'SUITE_PLAN_CHANGED','套件声明或版本已变化,请重新查看并确认。',{},409);
209
164
  requireCondition(fresh.available,'SUITE_NO_UPDATE','当前没有兼容新版。',{},409);
210
165
  const before=await readSuiteStatus(profileDir);
@@ -214,7 +169,7 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
214
169
  requireCondition(identity(executing)===identity(confirmed),'SUITE_PLAN_CHANGED','执行前套件安装包来源或声明已变化,请重新查看并确认。',{},409);
215
170
  emit({type:'library.install-started',itemId:'hanamesh:suite-upgrade',packageName:SUITE_BUNDLE,kind:'suite-upgrade'});
216
171
  try{
217
- await run(['add','--save-exact','--strict-peer-dependencies',`--registry=${fresh.registry??registry??'https://registry.npmjs.org'}`,`${SUITE_BUNDLE}@${fresh.bundle.to}`]);
172
+ await runCli(fresh.installation.args);
218
173
  const actual=await readSuiteStatus(profileDir);
219
174
  const saved=await json(join(profileDir,'package.json'));
220
175
  requireCondition(actual.status==='installed'&&actual.version===fresh.bundle.to&&saved.dependencies?.[SUITE_BUNDLE]===fresh.bundle.to&&fresh.components.every((c,i)=>actual.components[i]?.version===c.to),'INSTALL_READBACK_MISMATCH','套件升级后的版本或启用状态不符,请完全退出后按补齐命令修复。',{},502);
@@ -225,13 +180,11 @@ export function createLibraryInstaller({profileDir,profileName,dataRoot,nodeBina
225
180
  }
226
181
  });},
227
182
  async install(item,context){
228
- if(item.installTarget){const target=packageInstallTarget(item.installTarget);item={...item,installTarget:target,package:{registry:target.source,name:installTargetName(target)}};}
229
- const kind=itemKind(item);
230
- requireCondition(kind!=='listing','NOT_INSTALLABLE','Catalog item has no npm package to install.');
183
+ requireCondition(item?.installTarget,'NOT_INSTALLABLE','A fixed catalog installation target is required.',{reason:'fixed-install-target-required'});
184
+ const target=packageInstallTarget(item.installTarget);item={...item,installTarget:target,package:{registry:target.source,name:installTargetName(target)}};
185
+ const kind=isApplication(item)?'application':'plugin';
231
186
  return await installAs(item,kind,context);
232
187
  },
233
- /** rc.28: install a plugin by package name (already verified against the catalog by the service). */
234
- async installPlugin({packageName:name},context){return await installAs({package:{registry:'npm',name}},'plugin',context);},
235
188
  async provisionRuntime({appId,packageName:installedName,runtimeItem}){return await profileWriter.run(async()=>{const record=await installed(installedName);requireCondition(record.definition.id===appId,'APP_ID_MISMATCH','Installed package app id does not match.');const runtime=record.definition.deployments.map(deployment=>deployment.runtime).find(value=>value?.item===runtimeItem);requireCondition(runtime,'RUNTIME_NOT_DECLARED','Runtime item is not declared.');const result=await (await runtimeApi('provision'))(runtime.manifest,{root:runtimeRoot(appId),only:[runtime.item],onProgress:event=>emit({type:'library.provision-progress',appId,...event})});return{status:'restart-required',appId,packageName:installedName,result};});},
236
189
  async uninstall({packageName:installedName,appId,runtimeItem,running}){permitted(installedName);requireCondition(!running,'INSTANCE_IN_USE','Stop all application instances before uninstalling.',{},409);return await profileWriter.run(async()=>{await run(['remove',installedName]);if(runtimeItem){const result=await (await runtimeApi('remove'))(runtimeItem,{root:runtimeRoot(appId)});await clearGeneratedRuntimeCache(result,runtimeRoot(appId));}return{status:'restart-required',kind:'application',appId,packageName:installedName,dataPreserved:true};});},
237
190
  /** rc.28: `dsh plugin remove <name>`; the suite itself (PROTECTED_PACKAGES) is refused with PACKAGE_DENIED. */
@@ -28,18 +28,10 @@ async function pluginManagerFacts(anchor){
28
28
  try{
29
29
  const require=createRequire(anchor),manifestPath=require.resolve('@deepseek-ai/dsh-plugin-manager/package.json'),pkg=await json(manifestPath);
30
30
  if(pkg?.name!=='@deepseek-ai/dsh-plugin-manager'||typeof pkg.version!=='string')return {};
31
- let pluginManagerRegistry;
32
- try{const path=require.resolve('@deepseek-ai/dsh-plugin-manager/registry');if(path.startsWith(dirname(manifestPath)+sep))pluginManagerRegistry=path;}catch{}
33
- return{pluginManagerVersion:pkg.version,...(pluginManagerRegistry?{pluginManagerRegistry}:{})};
31
+ return{pluginManagerVersion:pkg.version};
34
32
  }catch{return {};}
35
33
  }
36
34
  export async function inferPluginManagerVersion(anchor){return(await pluginManagerFacts(anchor)).pluginManagerVersion;}
37
- async function desktopRegistryPath(root,pkg){
38
- const entry=pkg.exports?.['./registry'],target=typeof entry==='string'?entry:entry?.import??entry?.default;
39
- if(typeof target!=='string'||!target.startsWith('./'))return undefined;
40
- const path=resolve(root,target);if(!path.startsWith(root+sep))return undefined;
41
- try{await access(path);return path;}catch{return undefined;}
42
- }
43
35
  /** A DSH profile manifest that carries HanaMesh: `dsh.profile` (written by `dsh plugin`) or a hanamesh dependency. */
44
36
  export function isHanameshProfileManifest(manifest){
45
37
  if(!manifest||typeof manifest!=='object'||Array.isArray(manifest))return false;
@@ -109,7 +101,6 @@ export async function inferDesktopLayout({moduleUrl=import.meta.url,argv=process
109
101
  }
110
102
  const managerManifest=await json(join(runtimeDir,'node_modules/@deepseek-ai/dsh-plugin-manager/package.json'));
111
103
  const pluginManagerVersion=managerManifest?.name==='@deepseek-ai/dsh-plugin-manager'&&managerManifest.version===hostManifest.version?managerManifest.version:undefined;
112
- const pluginManagerRegistry=pluginManagerVersion?await desktopRegistryPath(join(runtimeDir,'node_modules/@deepseek-ai/dsh-plugin-manager'),managerManifest):undefined;
113
104
  // Current canonical runtime manifest only. No legacy descriptor inference or PATH search.
114
105
  if(metadata?.platform===process.platform&&metadata?.arch===process.arch&&
115
106
  metadata.desktopVersion===(await json(join(hostRoot,'package.json')))?.version&&/^24\.\d+\.\d+$/.test(metadata.node??'')) {
@@ -121,7 +112,7 @@ export async function inferDesktopLayout({moduleUrl=import.meta.url,argv=process
121
112
  if(actual.node===metadata.node&&actual.electron===undefined)nodeBinary=await realpath(node);
122
113
  } catch { /* Unavailable/unproven payload Node stays unavailable; never use Electron. */ }
123
114
  }
124
- return {profileDir:await realpath(profileDir),profileName:'desktop',dshBin,parentOrigin:'dsh-app://app',frameAncestors:[],desktopCli,desktopLauncher,pluginManagerVersion,pluginManagerRegistry,nodeBinary,runtimeDir};
115
+ return {profileDir:await realpath(profileDir),profileName:'desktop',dshBin,parentOrigin:'dsh-app://app',frameAncestors:[],desktopCli,desktopLauncher,pluginManagerVersion,nodeBinary,runtimeDir};
125
116
  }
126
117
 
127
118
  /** The DSH CLI that started this process — `argv[1]` only when it IS `@deepseek-ai/dsh/lib/bin.js`. */
@@ -149,6 +140,6 @@ export async function resolveLibraryLocations(config={},{moduleUrl=import.meta.u
149
140
  let dshBin=config.dshBin;if(dshBin===undefined){dshBin=desktop?.dshBin??inferDshBin(argv);if(dshBin)inferred.push('dshBin');}
150
141
  let node=nodeBinary;if(node===undefined){node=desktop?.nodeBinary??inferNodeBinary({execPath,versions});if(node)inferred.push('nodeBinary');}
151
142
  let sources=config.sources;if(sources===undefined){sources=structuredClone(DEFAULT_CATALOG_SOURCES);inferred.push('sources');}
152
- const {pluginManagerVersion,pluginManagerRegistry}=desktop??await pluginManagerFacts(inferDshBin(argv)??(profileDir?join(profileDir,'package.json'):undefined));
153
- return{profileDir,profileName,dshBin,nodeBinary:node,sources,inferred,...(pluginManagerVersion?{pluginManagerVersion}:{}),...(pluginManagerRegistry?{pluginManagerRegistry}:{}),...(desktop&&desktop.profileDir===profileDir&&config.dshBin===undefined?{desktopCli:desktop.desktopCli,desktopLauncher:desktop.desktopLauncher}:{})};
143
+ const {pluginManagerVersion}=desktop??await pluginManagerFacts(inferDshBin(argv)??(profileDir?join(profileDir,'package.json'):undefined));
144
+ return{profileDir,profileName,dshBin,nodeBinary:node,sources,inferred,...(pluginManagerVersion?{pluginManagerVersion}:{}),...(desktop&&desktop.profileDir===profileDir&&config.dshBin===undefined?{desktopCli:desktop.desktopCli,desktopLauncher:desktop.desktopLauncher}:{})};
154
145
  }
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { AppHostError, requireCondition, SerialQueue } from '../errors.js';
3
3
  import { loadCatalog, isApplication, itemKind, upgradeAvailable, validateManifestUrl } from './catalog.js';
4
4
  import { scanInstalled, scanInstalledPlugins, snapshotProfileDependencies, readProfileManifest } from './installed.js';
5
- import {packageInstallTarget,installTargetName,sameInstallTarget,catalogPackageTargets,pinnedGithubSpec,catalogInstallationAllowed} from './github-target.js';
5
+ import {packageInstallTarget,installTargetName,sameInstallTarget,catalogPackageTargets,pinnedGithubSpec} from './github-target.js';
6
6
  import {SUITE_BUNDLE,SUITE_TARGET,SUITE_PACKAGES,readSuiteStatus,CORE_SUITE_DECLARATION_MISSING,readSuiteSourceState} from './suite.js';
7
7
  import { PROTECTED_PACKAGES } from './install.js';
8
8
  import { handoffSettings } from './handoff.js';
@@ -80,11 +80,11 @@ export function createLibraryService({domain,host,config={},dataRoot,ledgerReade
80
80
  requireCondition(itemId===undefined||(text(itemId,160)&&!/[\u0000-\u001f\u007f]/.test(itemId)),'INVALID_INPUT','itemId is invalid.');
81
81
  requireCondition(packageName===undefined||(text(packageName,214)&&PACKAGE_NAME.test(packageName)),'INVALID_INPUT','packageName is invalid.');
82
82
  async function resolved(found,source){
83
- requireCondition(catalogInstallationAllowed(found),'NOT_INSTALLABLE','The catalog has not approved one-click installation.',{reason:found.oneClickInstall?.reason});
83
+ requireCondition(found.oneClickInstall?.allowed===true,'NOT_INSTALLABLE','The catalog has not approved one-click installation.',{reason:found.oneClickInstall?.reason??'fixed-install-target-required'});
84
84
  const targets=catalogPackageTargets(found),selected=targets.find(target=>expected?sameInstallTarget(target,expected):packageName===undefined||installTargetName(target)===packageName);
85
85
  requireCondition(!expected||selected,'CATALOG_CHANGED','The reviewed installation source or build permission changed. Review the current target again.',{},409);
86
- requireCondition(selected||(found.installMethods===undefined&&found.package?.registry==='npm'),'NOT_INSTALLABLE','This catalog entry has no fixed package installation target for the requested package.',{reason:'fixed-install-target-required'});
87
- const name=selected?installTargetName(selected):found.package.name;
86
+ requireCondition(selected,'NOT_INSTALLABLE','This catalog entry has no fixed package installation target for the requested package.',{reason:'fixed-install-target-required'});
87
+ const name=installTargetName(selected);
88
88
  requireCondition(!protectedPackage(name),'PACKAGE_DENIED','The HanaMesh suite is not managed through the market.',{},403);
89
89
  const rows=[...await installedPlugins(),...await installed()],row=rows.find(value=>value.packageName===name)??null;
90
90
  let upgrade=npmUpgradeAvailable(selected,row);
@@ -92,7 +92,7 @@ export function createLibraryService({domain,host,config={},dataRoot,ledgerReade
92
92
  const saved=await readProfileManifest(config.profileDir);
93
93
  upgrade=saved.dependencies[name]!==pinnedGithubSpec(selected.repo,selected.ref,selected.path)||row.active===false;
94
94
  }
95
- const item={...found,...(selected?{package:{registry:selected.source,name},installTarget:selected}:{}),installed:row,upgradeAvailable:upgrade};
95
+ const item={...found,package:{registry:selected.source,name},installTarget:selected,installed:row,upgradeAvailable:upgrade};
96
96
  return{contractVersion:INSTALL_TARGET_CONTRACT_VERSION,source,item:{...item,kind:itemKind(item)}};
97
97
  }
98
98
  const packageMatches=row=>row.package?.name===packageName||(row.installMethods??[]).some(method=>method.target&&(method.target.packageName??method.target.name)===packageName);
@@ -39,13 +39,11 @@ const exact=value=>typeof value==='string'&&semver.valid(value)===value;
39
39
  const digest=value=>typeof value==='string'&&/^[a-f0-9]{64}$/.test(value);
40
40
  const nullableText=value=>value===null||text(value);
41
41
  const invalid=()=>requireCondition(false,'CORE_SUITE_DECLARATION_INVALID','Core 套件声明无法用于精确安装。',{},503);
42
- function exactInstallCommand(value){
43
- if(value===null)return true;
44
- const prefix=`dsh plugin --profile desktop add --save-exact ${SUITE_BUNDLE}@`;
45
- return typeof value==='string'&&value.startsWith(prefix)&&exact(value.slice(prefix.length));
42
+ function validInstallation(value,version){
43
+ return object(value)&&value.name===SUITE_BUNDLE&&value.version===version&&text(value.command)&&Array.isArray(value.args)&&value.args.length>0&&value.args.every(text);
46
44
  }
47
45
  function validateDeclaration(value){
48
- if(!object(value)||value.schemaVersion!==1||!text(value.checkedAt)||!exactInstallCommand(value.installCommand)||!digest(value.declarationId)||!text(value.nodeVersion)||!nullableText(value.registry)||!(value.bundleVersion===null||exact(value.bundleVersion))||!['complete','incomplete','unknown'].includes(value.completeness))invalid();
46
+ if(!object(value)||value.schemaVersion!==1||!text(value.checkedAt)||!nullableText(value.installCommand)||!digest(value.declarationId)||!text(value.nodeVersion)||!nullableText(value.registry)||!(value.bundleVersion===null||exact(value.bundleVersion))||!['complete','incomplete','unknown'].includes(value.completeness))invalid();
49
47
  if(value.completeness==='complete'&&!exact(value.bundleVersion))invalid();
50
48
  const manifest=value.manifest;
51
49
  if(!object(manifest)||manifest.schemaVersion!==1||!text(manifest.id)||!object(manifest.bundle)||manifest.bundle.name!==SUITE_BUNDLE||!text(manifest.bundle.repository)||!object(manifest.core)||manifest.core.name!==SUITE_PACKAGES[0].name||!exact(manifest.core.version)||!text(manifest.core.repository)||!Array.isArray(manifest.components)||manifest.components.length!==2)invalid();
@@ -61,6 +59,7 @@ function validateDeclaration(value){
61
59
  if(!Object.values(value.peerFacts).every(nullableText)||!Object.values(value.metadataDigests).every(text))invalid();
62
60
  for(const upgrade of value.upgrades){
63
61
  if(!object(upgrade)||!object(upgrade.bundle)||!text(upgrade.bundle.id)||upgrade.bundle.moduleName!==SUITE_BUNDLE||!exact(upgrade.bundle.version)||!text(upgrade.bundle.repository)||!digest(upgrade.bundle.metadataDigest)||!text(upgrade.registry)||!Array.isArray(upgrade.targets)||upgrade.targets.length!==3)invalid();
62
+ if(!validInstallation(upgrade.installation,upgrade.bundle.version))invalid();
64
63
  const targets=new Set();
65
64
  for(const row of upgrade.targets){if(!object(row)||!text(row.id)||!SUITE_PACKAGES.some(p=>p.name===row.moduleName)||targets.has(row.moduleName)||!exact(row.version)||!text(row.repository)||!digest(row.metadataDigest))invalid();targets.add(row.moduleName);}
66
65
  }
@@ -76,7 +75,7 @@ export function readSuiteSourceState(source){
76
75
  return{protocolVersion:state.protocolVersion,status:state.status,selection:state.selection,registry:state.registry,archiveCount:state.archiveCount,lastError:state.lastError};
77
76
  }
78
77
  /** Consume only the optional protocol-1 Cordis method and the public C-CORE section D DTO. */
79
- export function createCoreSuiteAdapter({core=()=>undefined,registry,suiteSourceMode=()=>'configured',suiteSourceService=()=>undefined}={}){
78
+ export function createCoreSuiteAdapter({core=()=>undefined,registry,suiteSourceMode=()=>'configured',suiteSourceService=()=>undefined,profileName}={}){
80
79
  const current=()=>{const value=core();return value?.protocolVersion==='1'&&typeof value.getSuiteDeclaration==='function'?value:undefined;};
81
80
  // AppHost-local service identity only: Core protocol 1 supplies no revision/generation.
82
81
  const instances=new WeakMap();
@@ -101,17 +100,17 @@ export function createCoreSuiteAdapter({core=()=>undefined,registry,suiteSourceM
101
100
  }
102
101
  const get=async()=>{
103
102
  const binding=sourceBinding();
104
- const declaration=await read({registry:binding.registry});
103
+ const declaration=await read({...(binding.registry===undefined?{}:{registry:binding.registry}),...(profileName===undefined?{}:{profile:profileName})});
105
104
  requireCondition(JSON.stringify(sourceBinding())===JSON.stringify(binding),'SUITE_PLAN_CHANGED','套件安装包来源已变化,请重新查看并确认。',{},409);
106
105
  requireCondition(declaration.completeness==='complete','CORE_SUITE_CURRENT_INCOMPLETE','Core 套件完整性:'+declaration.completeness,{completeness:declaration.completeness,blocked:declaration.blocked},409);
107
106
  let expected;try{const url=new URL(binding.registry);url.pathname=url.pathname.replace(/\/$/,'')+'/';expected=url.href;}catch{}
108
- requireCondition(expected&&declaration.registry===expected,'CORE_SUITE_SOURCE_MISMATCH','Core 声明来源与执行 registry 不符。',{blocked:declaration.blocked},409);
107
+ requireCondition(text(declaration.registry)&&(binding.registry===undefined||declaration.registry===expected),'CORE_SUITE_SOURCE_MISMATCH','Core 声明来源与执行 registry 不符。',{blocked:declaration.blocked},409);
109
108
  requireCondition(declaration.upgrades.length>0,'CORE_SUITE_NO_COMPATIBLE_TUPLE','Core 未提供可执行升级组合。',{blocked:declaration.blocked},409);
110
109
  // The first displayed Core tuple is the presentation default; App-host never ranks versions.
111
110
  const selectedUpgrade=declaration.upgrades[0];
112
111
  requireCondition(selectedUpgrade.registry===declaration.registry,'CORE_SUITE_SOURCE_MISMATCH','Core 升级组合来源与执行 registry 不符。',{},409);
113
112
  const components=SUITE_PACKAGES.map(({name,label})=>{const from=declaration.installed.find(row=>row.moduleName===name).actualVersion;if(!exact(from))invalid();return{name,label,from,to:selectedUpgrade.targets.find(row=>row.moduleName===name).version};});
114
- return{available:true,bundle:{name:SUITE_BUNDLE,from:declaration.bundleVersion,to:selectedUpgrade.bundle.version},components,installCommand:declaration.installCommand,sourceBinding:binding,declarationId:declaration.declarationId,registry:declaration.registry,selectedUpgrade,physicalBaseline:declaration.installed,upgrades:declaration.upgrades,blocked:declaration.blocked};
113
+ return{available:true,bundle:{name:SUITE_BUNDLE,from:declaration.bundleVersion,to:selectedUpgrade.bundle.version},components,installation:selectedUpgrade.installation,installCommand:declaration.installCommand,sourceBinding:binding,declarationId:declaration.declarationId,registry:declaration.registry,selectedUpgrade,physicalBaseline:declaration.installed,upgrades:declaration.upgrades,blocked:declaration.blocked};
115
114
  };
116
115
  Object.defineProperty(get,'unavailable',{get:()=>current()?null:CORE_SUITE_DECLARATION_MISSING});
117
116
  get.readFacts=async()=>{
@@ -132,7 +131,7 @@ export async function prepareSuiteUpgrade({coreSuitePlan}={}){
132
131
  const plan=await coreSuitePlan();
133
132
  requireCondition(plan!==undefined&&plan!==null,'CORE_SUITE_DECLARATION_MISSING',CORE_SUITE_DECLARATION_MISSING,{},503);
134
133
  const invalid=()=>requireCondition(false,'CORE_SUITE_DECLARATION_INVALID','Core 套件声明无法用于精确安装。',{},503);
135
- if(typeof plan!=='object'||Array.isArray(plan)||typeof plan.available!=='boolean'||!object(plan.bundle)||plan.bundle.name!==SUITE_BUNDLE||!exact(plan.bundle.from)||!exact(plan.bundle.to)||!exactInstallCommand(plan.installCommand)||!Array.isArray(plan.components)||plan.components.length!==SUITE_PACKAGES.length)invalid();
134
+ if(typeof plan!=='object'||Array.isArray(plan)||typeof plan.available!=='boolean'||!object(plan.bundle)||plan.bundle.name!==SUITE_BUNDLE||!exact(plan.bundle.from)||!exact(plan.bundle.to)||!nullableText(plan.installCommand)||!validInstallation(plan.installation,plan.bundle.to)||!Array.isArray(plan.components)||plan.components.length!==SUITE_PACKAGES.length)invalid();
136
135
  const rows=new Map();
137
136
  for(const row of plan.components){
138
137
  if(!row||typeof row!=='object'||!SUITE_PACKAGES.some(p=>p.name===row.name)||rows.has(row.name)||typeof row.from!=='string'||semver.valid(row.from)!==row.from||typeof row.to!=='string'||semver.valid(row.to)!==row.to)invalid();
@@ -35,7 +35,7 @@ export async function apply(ctx, config = {}) {
35
35
  }
36
36
  ```
37
37
 
38
- 预发布版本只用于隔离验收且不得成为 `latest`;应用库生产路径只接受 registry `latest` 返回的稳定精确版本。发布者必须同时保留 npm 包完整性、runtime 三方来源与 sha256、以及与 app-host 精确 peer 版本对应的可复核构建记录。
38
+ 预发布版本只用于隔离验收且不得成为 `latest`;应用库安装路径只接受启用目录 installMethods 给出的审核精确版本,且条目 oneClickInstall.allowed:true;不在安装时读取可变标签。发布者必须同时保留 npm 包完整性、runtime 三方来源与 sha256、以及与 app-host 精确 peer 版本对应的可复核构建记录。
39
39
 
40
40
  ## Existing configuration and original upstream archives (D-230)
41
41
 
package/docs/CONTRACT.md CHANGED
@@ -151,7 +151,7 @@ DSH 绑定必须提供同一个 `single` domain 的一次完整镜像 publish
151
151
  |---|---|---|---|
152
152
  | `/hanamesh/library` | GET | `q`、`category`(缺省 `hanamesh-app`;`category=` 空 = 全部)、`cursor` | `{source, items[], categories[], installed[], plugins[], restartRequired, page}`;`items[]` 每条目在目录字段之外加 `kind`(`application` = `hanamesh-app` + npm 包;`plugin` = 其它 npm 包;`listing` = 无 npm 包,仅收录)、`application`(兼容)、`installed`(应用行或插件行,或 `null`)、`upgradeAvailable`(已选 npm `installTarget.version` 严格新于已装 `version`;不读取退休目录版本;已装行处于 `uninstalled-not-unloaded` 时恒 false);`categories` = 本页条目出现过的类别(目录契约没有类别清单端点) |
153
153
  | `/hanamesh/library/installedPlugins` | GET(POST → 405) | 无参数 | `{plugins[], apps[], restartRequired}`;`plugins[]`:`{packageName, version, kind:'plugin', bundle, active, state, errorCode?}`,`state` ∈ `installed` / `installed-not-loaded`(启动后新增)/ `uninstalled-not-unloaded`(启动后移除,进程仍加载)/ `invalid`(依赖里有、`node_modules` 里读不到);`apps[]` = 原已安装应用扫描行(`registered` / `installed-not-loaded` / `runtime-missing` / `invalid`) |
154
- | `/hanamesh/library/install` | POST | `{itemId, packageName?}` | 202 `{operationId,status:'started'}`;`packageName` 给出时用它精确查目录(`q=<包名>&category=`)再按 `itemId` 匹配;应用与插件同一路径走官方 CLI;有 installMethods 时必须选中固定 target,npm 用 target.version,GitHub 用完整 SHA/子目录/构建名单,不在安装时解析 latest。无匹配目标拒绝 NOT_INSTALLABLE 并返回具名原因。未提供 installMethods 的标准目录保持既有 npm 路径;应用另外供给 runtime;也是升级路径 |
154
+ | `/hanamesh/library/install` | POST | `{itemId, packageName?}` | 202 `{operationId,status:'started'}`;`packageName` 给出时用它精确查目录(`q=<包名>&category=`)再按 `itemId` 匹配;应用与插件同一路径走官方 CLI;必须有 installMethods、oneClickInstall.allowed:true 并选中固定 target,npm 用 target.version,GitHub 用完整 SHA/子目录/构建名单,不在安装时解析 latest。无匹配目标拒绝 NOT_INSTALLABLE 并返回具名原因。未提供 installMethods 拒绝安装;应用另外供给 runtime;也是升级路径 |
155
155
  | `/hanamesh/library/plugins/install` | POST | `{packageName}` | 202;包必须在启用目录里能搜到(否则 404 `CATALOG_ITEM_MISSING`);目录条目是应用则走应用路径 |
156
156
  | `/hanamesh/library/plugins/uninstall` | POST | `{packageName}` | 202;`dsh plugin remove <包>`;若该包是已装应用则转应用卸载(runtime 清理 + 运行中 `INSTANCE_IN_USE` 409) |
157
157
  | `/hanamesh/library/provision` | POST | `{appId, packageName, runtimeItem}` 三者必填 | 202;缺字段/非法 → 400 `INVALID_INPUT`(`LIB-PROVISION-INPUT`) |
@@ -231,7 +231,7 @@ Storage space budget is archive bytes + twice measured regular-file payload (new
231
231
 
232
232
  ## B17 套件安装包来源(2026-10-21 候选)
233
233
 
234
- AppHost 自有设置页「套件安装包来源」,公开同源端点 `GET|POST /hanamesh/library/suite-source`。GET 无参数,返回 `{mode, configuredRegistry, observation, traceId}`;`mode` 为 `configured|core`。`configured` 是唯一新增默认:继续使用原 `library.registry`(未设仍为 `https://registry.npmjs.org`)。市场目录 `library.sources` 的未设播种、显式空数组、自定义及已保存值均独立保持;目录 manifest URL 不是 npm registry。
234
+ AppHost 自有设置页「套件安装包来源」,公开同源端点 `GET|POST /hanamesh/library/suite-source`。GET 无参数,返回 `{mode, configuredRegistry, observation, traceId}`;`mode` 为 `configured|core`。`configured` 是唯一新增默认:使用显式 `library.registry`,未设时请求Core声明实际来源。市场目录 `library.sources` 的未设播种、显式空数组、自定义及已保存值均独立保持;目录 manifest URL 不是 npm registry。
235
235
 
236
236
  POST 严格只接受 `{mode:'configured'}` 或 `{mode:'core'}`,不接受 registry、版本或其他字段/查询参数。沿用精确 Host、同源 Origin、workspace-v1、Connection admission、逐操作 authorization、JSON/65536 字节及 no-store 规则。选择存自己的 `hanamesh_library` 单镜像可选 `suiteSource:{mode}`;未存值不改旧记录。来源、目录和网站交接设置按接收顺序串行读改写整个镜像,成功持久化后才发布新内存状态;保存失败 `500/SUITE_SOURCE_SAVE_FAILED`,旧选择和其他字段保持;关闭等待已排队写入。
237
237
 
@@ -239,7 +239,7 @@ GET 的 `observation` 为 `{observable:false,code}` 或 `{observable:true,state}
239
239
 
240
240
  明确选择 core 后,预览、确认、紧接执行之前各自重新读取当前 source 和 Core `getSuiteDeclaration({registry})`,并在异步声明返回后复核来源。`ready` 仅表示来源可读;兼容性仍唯一由 Core declaration 决定。disabled/failed/disposed 或无有效 registry 为 `409/CORE_SUITE_SOURCE_NOT_READY`;不缓存 ready、不回退配置 registry、不挑最新、不重试。沿用 declarationId、精确 tuple/metadata digest/repository、物理已装 baseline 和本人 planId。URL、selection、选择模式或提供服务的对象身份变化均终止原确认;新预览仍须本人确认。服务对象身份是 AppHost 内存内 opaque token,**不是 Core 的 generation/revision**;protocol 1 没有该事实或 watch,不能声称观察到两次读取间完全消失又恢复原状的事件。
241
241
 
242
- 最后执行仍调用现有官方 CLI `add --save-exact --strict-peer-dependencies --registry=<声明原址>`,精确三组件;正常市场外部包路径不变。正在执行时源连接丢失由实际 CLI/输运终态报告,不靠重试/退出屏障维持源。Core B17 服务只来自候选 `c5e1a7373b9896c385f14f79a64bb277f43bde5c` 的公开能力;无 Core 运行时 import,无 Desktop hook/bootstrap、三锁或先于插件加载的服务。源码/真实 Cordis+JSON domain(AUTH_DOUBLE)与正常包验证不等同真实原生安装或 owner 验收。
242
+ 最后调用现有官方 CLI,完整 argv 来自Core的升级 installation,安装一个精确总包;正常市场外部包路径不变。正在执行时源连接丢失由实际 CLI/输运终态报告,不靠重试/退出屏障维持源。Core B17 服务只来自候选 `c5e1a7373b9896c385f14f79a64bb277f43bde5c` 的公开能力;无 Core 运行时 import,无 Desktop hook/bootstrap、三锁或先于插件加载的服务。源码/真实 Cordis+JSON domain(AUTH_DOUBLE)与正常包验证不等同真实原生安装或 owner 验收。
243
243
 
244
244
  公开类型位于 `@hanamesh/dsh-app-host/dsh` 的 `SuiteSourceMode/CoreSuiteSourceState/SuiteSourceSettings/SuiteSourceSettingsInput`;页面导出 `SuiteSourceSettingsSection`。回归:`suite-source.test.mjs`、`handoff-integration.test.mjs` B17-CORDIS,变异 M84–M89(M61 检查全部后续读取被错误缓存)。
245
245
 
@@ -256,6 +256,6 @@ B17 review repair(同0.6.8候选):`AppHost` 未注入 `runtimeLedgerReader
256
256
 
257
257
  ## 0.6.16 市场查询来源与诊断(原协议1)
258
258
 
259
- 普通市场安装/升级的 latest 查询消费 DSH 公开 pluginManager.registries() 与公开 registryPlan/attributeFailure,沿用用户配置源顺序和私有源隔离规则;成功后将同一源选择传入原受控CLI:显式源用--registry=<source>,pnpm自有源(null)不传--registry以保留scope/认证配置。整个配置/查询/读回共30000ms,超时abort并报REGISTRY_LOOKUP_TIMEOUT,未提供公开能力/解析源报REGISTRY_CONFIG_UNAVAILABLE;原生异常为REGISTRY_LOOKUP_FAILED并保留cause。没有新的用户配置、请求字段或协议版本。既有 details.diagnostic 带脱敏类型/消息/cause.code/实际阶段及已知CLI exit/stderr摘要,同时写宿主日志;其它错误、回滚与重启语义保留。Core套件升级路径的声明/源和缺省值单独保留,不采用普通市场的源选择。
259
+ A2 0.6.22:普通插件必须有 Indexer 已审核的 installMethods 和 oneClickInstall.allowed:true;仅执行其 npm 精确版本或完整 GitHub SHA。缺目标具名拒绝,不根据包名查可变版本标签。CLI 使用显式 library.registry(若有),否则保留宿主原生 registry 配置。删除普通插件按包名内部直装方法、元数据查询及其专用设置/规则加载。套件仅透传 Core installCommand。安装失败保留脱敏 CLI/读回诊断及补偿结果;精确版本发布后即可原生验收。
260
260
 
261
- 0.6.16最小接线:公开规则模块从宿主manager同锚点/版本守卫加载,不在AppHost声明manager依赖/peer;找不到模块或公开函数则REGISTRY_CONFIG_UNAVAILABLE,无npmjs回退或自创排序。发布依赖闭包与0.6.15一致。
261
+ A2 0.6.22:升级tuple必须带Core生成的installation(name/version/command/args),name/version与该tuple一致。AppHost传实际profile和可选配置来源;未配置来源由Core显式声明。CLI直接执行args;confirmation同时绑定该定义。installCommand是Core的可空修复展示文本,不解析命令前缀。旧Core缺定义不回退自拼。
@@ -33,7 +33,7 @@ Internal connection HTTPS/bind-address handling changed upstream; the local HTTP
33
33
 
34
34
  ## Installation behavior
35
35
 
36
- 0.6.15 retains the monitored public CLI. CLI failures carry bounded, redacted stdout and stderr through the asynchronous operation's diagnostic details; the market shows the original cause in an expandable panel. The main failure message includes the exact pnpm category. A minimum-release-age rejection lists affected package versions, explains the default 24-hour requirement while deferring to the actual cutoff, explains the known first-entry-only defect in pnpm 11.7.0 bundled with DSH 0.2.0-rc.2, and directs the person to DSH Settings to check the version and upgrade DSH, or wait until the rule is met before a manual retry. No profile configuration, dependency exclusion or supply-chain policy is changed.
36
+ 0.6.15 retains the monitored public CLI. CLI failures carry bounded, redacted stdout and stderr through the asynchronous operation's diagnostic details; the market shows the original cause in an expandable panel. The main failure message includes the exact pnpm category. A minimum-release-age rejection lists affected package versions and retains the actual cutoff in technical details. Verify the exact installation target and upgrade DSH through its Settings if necessary. No profile configuration, dependency exclusion or supply-chain policy is changed.
37
37
 
38
38
  The official 0.2.0-rc.2 bundled pnpm 11.7.0 uses only the first matching package-name rule in `minimumReleaseAgeExclude`: an earlier exact old-version entry shadows a later exact new-version entry. The existing lockfile verifier can therefore reject an unrelated installation even though the new version is visibly listed. The 11.7.0 package obtained from npm has the same entry and core bytes as the supplied Desktop copy. Official Desktop 0.2.1-rc.3's pnpm 11.28.5 unions all matching exact versions and passes the same diagnostic profile. This establishes two tested endpoints, not the first fixed version. Both Desktop CLI and in-process manager use this pnpm path and set `ELECTRON_RUN_AS_NODE=1`; switching to the manager does not correct the 11.7.0 rule evaluation.
39
39
 
@@ -10,7 +10,7 @@ type PackageInstallTarget =
10
10
  | {source:'github';repo:string;ref:string;path:string|null;packageName:string;allowBuild:string[]};
11
11
  ```
12
12
 
13
- GitHub ref 必须是收录时固定的完整 40 位 SHA,不在安装时解析分支。结构化目标必须带 itemId;App-host 从自己的启用目录 installMethods 解析并逐字段核对,整个条目 oneClickInstall.allowed=false 时拒绝,不能换用同名 npm 包。条目带 installMethods 而未选中匹配目标时,返回 NOT_INSTALLABLE / details.reason=fixed-install-target-required,不得退回 package 展示名去装 npm latest;只有未提供 installMethods 的条目保留原标准 npm 路径。HanaMesh 视图拒绝退休的 latestVersion/license,确认页不显示版本行,升级按钮不拼目录版本;npm 升级按 installTarget.version 与已装实际版本比较。GET target/导航片段将 installTarget JSON 编码;POST install/网站 handoff 使用对象。响应 item.installTarget 是客户端确认回传的预期目标。目录 SHA、路径或构建许可变化会拒绝 CATALOG_CHANGED,必须重新查看;不能在确认之后自动换目标。
13
+ GitHub ref 必须是收录时固定的完整 40 位 SHA,不在安装时解析分支。结构化目标必须带 itemId;App-host 从自己的启用目录 installMethods 解析并逐字段核对,整个条目 oneClickInstall.allowed=false 时拒绝,不能换用同名 npm 包。条目必须提供 installMethods 和 oneClickInstall.allowed:true,并选中匹配的固定目标;缺 installMethods、未获准或无匹配目标时返回 NOT_INSTALLABLE / details.reason=fixed-install-target-required(或提供方具名原因),不得退回 package 展示名解析可变版本。HanaMesh 视图拒绝退休的 latestVersion/license,确认页不显示版本行,升级按钮不拼目录版本;npm 升级按 installTarget.version 与已装实际版本比较。GET target/导航片段将 installTarget JSON 编码;POST install/网站 handoff 使用对象。响应 item.installTarget 是客户端确认回传的预期目标。目录 SHA、路径或构建许可变化会拒绝 CATALOG_CHANGED,必须重新查看;不能在确认之后自动换目标。
14
14
 
15
15
  确认沿现有市场表面,展示完整仓库、commit、子目录;allowBuild 非空时单列「会在本机执行构建脚本」和名单,并说明 pnpm 会保存许可。取消或预览不执行 CLI。本人确认后,仅调用原受控 `dsh plugin --profile <profile> add --save-exact [--allow-build=<项>…] github:owner/repo#<SHA>[&path:<目录>]`,每段都是独立 argv,不执行来源 command。npm 目标直接使用收录时固定的精确版本,不在安装时再解析版本。
16
16
 
@@ -96,7 +96,7 @@ node node_modules/@hanamesh/dsh-app-host/dist/install-target/run.js consumer --u
96
96
  一次安装(应用或插件、新装或升级)按以下顺序进行,同一宿主内同一时间只有一个 profile 写者(安装、补偿、卸载、provision 串行;`dsh plugin` 自己按每条命令加 profile 写锁):
97
97
 
98
98
  1. **基线**:profile `package.json` 里本包的规格、`dsh.profile.bundles`、`pnpm-lock.yaml`、`node_modules` 实际解析的版本;provision 前再记 runtime 根的 lib-provision ledger。
99
- 2. 官方 `dsh plugin add --save-exact <包>@<registry latest>`。
99
+ 2. 官方 `dsh plugin add --save-exact <包>@<审核目标精确版本>`;GitHub 使用目录中的完整40位SHA和已确认构建许可。
100
100
  3. **读回**:profile 规格与实际解析版本都等于固定版本才继续,否则 `INSTALL_READBACK_MISMATCH`。
101
101
  4. 应用:读 `app.json` 校验定义 → 随包 lib-provision 供给声明的 runtime。
102
102
 
@@ -111,7 +111,7 @@ node node_modules/@hanamesh/dsh-app-host/dist/install-target/run.js consumer --u
111
111
 
112
112
  `checks` 逐项列出 dependency、module、bundle、lockfile、bundles 与 `runtime:<item>` 的 expected/actual/ok。别的写者在此期间改动的其他依赖永不撤销,lockfile 因此无法证明已恢复时报 `CONCURRENT_PROFILE_WRITE`。未认领文件与 `<dataRoot>` 下的应用数据从不删除。失败不会自动重试。
113
113
 
114
- 补偿的 `add` / `install` 沿用本次 latest 查询与正向安装选中的同一 registry:显式源继续传 `--registry=<source>`,pnpm 自有源(`null`)继续省略该参数。回滚不会重新选源、查询 latest 或退到默认公共源;`remove` 参数保持原样。恢复动作列表保留原包操作摘要,不加入 registry URL。
114
+ 补偿的 `add` / `install` 沿用本次正向安装选中的同一 registry:显式源继续传 `--registry=<source>`,pnpm 自有源(`null`)继续省略该参数。回滚不会重新选源、重新解析版本或退到默认公共源;`remove` 参数保持原样。恢复动作列表保留原包操作摘要,不加入 registry URL。
115
115
 
116
116
  市场显示:`安装失败:<原码>(原因)` 后接 `已恢复到安装前状态(已逐项读回)` / `已恢复到原版本 x(已逐项读回)` / `安装前状态未被改动(已逐项读回)`;`failed` 显示 `恢复失败:<码>,未恢复:<项>,安装前原件:<目录>。未安装成功,也未回到安装前状态,需要人工处理`,不会显示为成功或「已回滚」。
117
117
 
package/docs/LIBRARY.md CHANGED
@@ -17,8 +17,6 @@
17
17
 
18
18
  钉版本 DSH 没有向插件公开 profile 目录或 profile 名。桌面壳通过 overlay 显式给出 `library.profileDir`(绝对路径)、`library.profileName`、`library.dshBin` 与 `nodeBinary`,显式值永远优先。**纯 DSH CLI profile(rc.26)** 下缺省值按 `CONTRACT.md`「应用库配置」推断:`profileDir` 来自本包自身真实安装位置所属的 profile、`profileName` 为其目录名、`dshBin` 只取启动本进程的 `@deepseek-ai/dsh/lib/bin.js`、`nodeBinary` 只在非 Electron 进程取 `process.execPath`。Electron 下仍必须由 dsh-runtime/core 提供绝对 `nodeBinary`;应用库不会在 Electron 里回退到 `process.execPath`。
19
19
 
20
- 安装先向 registry 的 `latest` 端点复核精确稳定版本,再用显式 Node 执行 DSH 自己的 `bin.js plugin --profile <name> add --save-exact <package>@<version>`。成功后读取包内 `app.json`;如果描述符声明 runtime,则调用随包内联的锁定 `@hanamesh/lib-provision@0.1.0-rc.3`。生产默认拒绝 prerelease;隔离 Verdaccio 验收可显式设 `allowPrerelease: true`。
21
-
22
20
  rc.10:add 之后的读回、定义校验或 provision 失败按基线补偿(新装 `dsh plugin remove`、升级 `dsh plugin add` 原规格,runtime 按 ownership 回收或按原定义重供),逐项读回后在 `library.install-failed` 的 `recovery` 里报告 `not-needed`/`restored`/`failed`;详见 `INSTALL_TARGET.md`「安装失败后的恢复」。
23
21
 
24
22
  安装/卸载完成只返回 `restart-required`,绝不静默重启 DSH。卸载只删除包与 lib-provision 账本认领的 runtime 文件,保留 `<dataRoot>` 下应用数据;彻底删除需用户手动处理对应数据目录。
@@ -56,11 +54,7 @@ Both SOURCE fixture sets and suites ship as `@hanamesh/dsh-app-host/catalog/suit
56
54
 
57
55
  市场只显示一张官方、不计分的「HanaMesh 套件」卡。完整性只显示 Core 的 `completeness`,同时展示 `installed` 的要求范围、实际版本、状态和 `blocked` 的具名原因;Core 缺席或读取失败时完整性为 unknown,并保留物理包版本供诊断。Core、使用记录、应用库不再分别作为普通可安装卡。品牌插件只属于 HanaMesh 自有客户端。
58
56
 
59
- DSH 首次安装或缺组件时,完全退出客户端后执行同一行,再重新打开:
60
-
61
- ```sh
62
- dsh plugin --profile desktop add --save-exact '@hanamesh/core@0.3.21' '@hanamesh/usage@0.3.0-rc.6' '@hanamesh/dsh-app-host@0.6.14'
63
- ```
57
+ DSH 首次安装使用公开 Core 精确发布元数据中的 `hanameshSuiteInstall.command`;已运行客户端的修复命令使用 Core 声明 `installCommand`。两者由 Core 同一格式器提供,只安装总包的精确版本,完全退出后安装并重新打开。缺命令显示具名不可用,不自造组件命令。
64
58
 
65
59
  套件版本范围、是否完整、候选来源及兼容/升级规则唯一属于 Core。App-host 只读物理安装版本,不能用它们推导兼容或安装许可;不自行读取 npm latest、计算组件组合、校验候选宿主 peers 或持有 Core 的版本范围副本。普通安装/卸载接口继续拒绝套件包。
66
60
 
@@ -87,12 +81,7 @@ HanaMesh 视图原样保留可选 `official:boolean`(包括 false)与 C-INDE
87
81
 
88
82
  ## 0.6.16 · 安装查询来源与失败诊断
89
83
 
90
- 市场通过公开 `pluginManager.registries()` 读取 DSH 的首选源、顺序 fallback 和 pnpm 已解析的源;从宿主自己的安装锚点按既有版本守卫定位 `exports['./registry']`,动态加载其公开 `registryPlan` / `attributeFailure`,不读取私有配置或发明兜底源。`library.registry` 若显式设置,沿用 DSH 对请求源的规则(外部私有源单独使用)。查询 latest 成功后,精确版本及同一源选择传入原受控CLI:显式配置源用 `add --save-exact --registry=<source>`,DSH的pnpm自有源(null)沿原生语义不传 `--registry`,保留scope源和认证配置;缺公开源能力或 pnpm 自有源无法解析,明确 `REGISTRY_CONFIG_UNAVAILABLE`,不猜 npmjs。DSH rc.2、alpha.2、rc.3 均公开这些端口。
91
-
92
- 整个查询(配置读取、配置源顺序查询、JSON 读回)共享 30000ms 上限。到期 abort 并报 `REGISTRY_LOOKUP_TIMEOUT`,不重试、不在超时后继续 fallback。只有 DSH 规则允许的网络/未找到失败才查询其已配置后继源;HTTP 认证/权限拒绝及无效 JSON 不换源。包版本稳定性策略、受控 CLI、读回、补偿、restart-required 行为保留。
93
-
94
- 原生 lookup 异常包为 `REGISTRY_LOOKUP_FAILED`,原 Error 保留于 cause;已知具名码保留。`library.*-failed.details.diagnostic` 最多4001字符,含脱敏异常类型/消息/cause.code 与实际 registry configuration/fetch/readback、CLI run、profile readback 阶段;CLI 非零另含 exit、signal 与 stderr/输出摘要。同一摘要及 operationId 写宿主 logger.warn(logger失效时安全console),UI已有可展开技术详情显示,并提示检查 DSH 源设置/网络后手动重试。不记录请求头、正文、环境、stack 或任意 details;不猜历史丢失的原错。
84
+ D1: DSH suite installation and upgrades use only @hanamesh/hanamesh. Core owns the bundle metadata and exact three dependency targets. A confirmed strict CLI add changes the bundle once; readback checks its saved version, activation and locked physical children. The suite display version is the bundle version. Ordinary market install/remove denies the bundle.
95
85
 
96
- 规则模块只由宿主公开exports提供;普通DSH用与manager版本查询相同createRequire(anchor),Desktop限已证明的runtimeDir与匹配版本。没有新增manager依赖/peer、没有生产规则副本;无法定位/加载规则或端口不完整均REGISTRY_CONFIG_UNAVAILABLE。钉SHA的rc.2规则仅位于tests/fixtures,不进入发布包。
97
86
 
98
- D1: DSH suite installation and upgrades use only @hanamesh/hanamesh. Core owns the bundle metadata and exact three dependency targets. A confirmed strict CLI add changes the bundle once; readback checks its saved version, activation and locked physical children. The suite display version is the bundle version. Ordinary market install/remove denies the bundle.
87
+ A2 0.6.22:普通插件必须有 Indexer 已审核的 installMethods 和 oneClickInstall.allowed:true;仅执行其 npm 精确版本或完整 GitHub SHA。缺目标具名拒绝,不根据包名查可变版本标签。CLI 使用显式 library.registry(若有),否则保留宿主原生 registry 配置。删除普通插件按包名内部直装方法、元数据查询及其专用设置/规则加载。套件仅透传 Core installCommand。安装失败保留脱敏 CLI/读回诊断及补偿结果;精确版本发布后即可原生验收。
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": {
3
3
  "name": "@hanamesh/dsh-app-host",
4
- "version": "0.6.21",
4
+ "version": "0.6.22",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "source": "git+https://github.com/yzsnstotz/hanamesh-plugin-app-host.git",
7
7
  "purpose": "Application ownership, view leases, Router forwarding, and application library for official DSH"
@@ -291,18 +291,18 @@
291
291
  "reviewDocs": "cce157612b1390b845ccca3f0632247de9fb3229"
292
292
  },
293
293
  "@hanamesh/core": {
294
- "version": "0.3.28",
294
+ "version": "0.3.30",
295
295
  "license": "MIT",
296
- "source": "https://registry.npmjs.org/@hanamesh/core/-/core-0.3.28.tgz",
296
+ "source": "https://registry.npmjs.org/@hanamesh/core/-/core-0.3.30.tgz",
297
297
  "purpose": "Development-only public Core suite declaration and provider-owned install command for clean CI; never runtime or bundled",
298
298
  "usedAs": [
299
299
  "devDependencies"
300
300
  ],
301
301
  "specs": {
302
- "devDependencies": "^0.3.28"
302
+ "devDependencies": "^0.3.30"
303
303
  },
304
- "sourceCommit": "62059ca586be2bffa054b5d392ebde41c51939ec",
305
- "archiveSha256": "9ef8c250efa3bc46de583dc82103719d71d9f3dcc2233fd49731b4467337efa9",
304
+ "sourceCommit": "11335137f909d28cd5724d6d206b770841ddb653",
305
+ "archiveSha256": "6dfa824891aa9115052473e1b27cf87811ff47a757addd812f6d6daf9c35c338",
306
306
  "licenseNotice": "MIT License\n\nCopyright (c) 2026 HanaMesh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
307
307
  "licenseSha256": "cd29e05178eecb3207c494901de7d41785a7b31216e94ab37f3e104e93f3f3b1"
308
308
  }
package/docs/SECURITY.md CHANGED
@@ -8,7 +8,7 @@ D-214: verification is trusted producer/client-view configuration, never a Targe
8
8
 
9
9
  # 安全与运行边界
10
10
 
11
- 0.6.21 GitHub 安装候选(R25 修复):仅消费 Indexer 的完整 SHA/包名/子目录/构建许可,且与 App-host 当前启用目录逐字段核对。条目存在 installMethods 时必须选中其中的固定目标;没有匹配目标即 NOT_INSTALLABLE,不从 npm 展示名回退解析 latest。HanaMesh 视图拒绝退休的 latestVersion/license,确认页和升级按钮不显示目录版本;npm 升级只比较固定目标 version 与已装实际版本。网站或客户端的 installTarget 是预期值,不是任意执行源;未知字段、浮动 ref、参数注入/越界目录拒绝,套件保护名单同样适用于 Git 包。既有确认回传完整目标,目录变更不得偷换 SHA 或放行额外脚本。官方 CLI 用 argv,不执行采集来源的 shell command;受控独立 Node、Git helper 与监测边界不变。脚本许可由 pnpm 管理并可能持久化,安装失败保留具体 build-permissions 残留,不手改策略、不冒充恢复。仍不是 OS 沙箱。
11
+ 0.6.22 固定目标安装(A2):仅消费 Indexer 的完整 SHA/包名/子目录/构建许可,且与 App-host 当前启用目录逐字段核对。条目必须有 installMethods 和 oneClickInstall.allowed:true,并选中其中的固定目标;缺方法、未获准或无匹配目标即 NOT_INSTALLABLE,不从 npm 展示名回退解析 latest。HanaMesh 视图拒绝退休的 latestVersion/license,确认页和升级按钮不显示目录版本;npm 升级只比较固定目标 version 与已装实际版本。网站或客户端的 installTarget 是预期值,不是任意执行源;未知字段、浮动 ref、参数注入/越界目录拒绝,套件保护名单同样适用于 Git 包。既有确认回传完整目标,目录变更不得偷换 SHA 或放行额外脚本。官方 CLI 用 argv,不执行采集来源的 shell command;受控独立 Node、Git helper 与监测边界不变。脚本许可由 pnpm 管理并可能持久化,安装失败保留具体 build-permissions 残留,不手改策略、不冒充恢复。仍不是 OS 沙箱。
12
12
 
13
13
  ## 可信边界
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanamesh/dsh-app-host",
3
- "version": "0.6.21",
3
+ "version": "0.6.22",
4
4
  "description": "HanaMesh application ownership, durable view leases, and application library for official DSH 0.2.x",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -68,6 +68,28 @@
68
68
  "docs/DSH_COMPATIBILITY.md",
69
69
  "docs/INSTALLATION_STATUS.md"
70
70
  ],
71
+ "scripts": {
72
+ "build": "node scripts/build.mjs",
73
+ "test": "node scripts/safe-git.mjs node --test --test-reporter=tap --test-concurrency=1 tests/*.test.mjs",
74
+ "test:contract": "pnpm run build && node --test --test-reporter=tap --test-concurrency=1 tests/install-target-contract.test.mjs tests/catalog-major-compat.test.mjs",
75
+ "test:mutation": "node scripts/safe-git.mjs node scripts/mutations.mjs",
76
+ "test:types": "tsc --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext tests/consumer.mts",
77
+ "test:native-driver": "node scripts/safe-git.mjs node --test --test-reporter=tap tests/native/driver.test.mjs",
78
+ "check": "node scripts/safe-git.mjs node scripts/check.mjs",
79
+ "prepack": "npm run build",
80
+ "demo": "node examples/demo.mjs",
81
+ "preflight:dsh": "node scripts/preflight-dsh.mjs",
82
+ "check:consistency": "node scripts/check-consistency.mjs",
83
+ "check:toolchain": "node scripts/check-toolchain.mjs",
84
+ "preflight": "node scripts/preflight.mjs",
85
+ "verify:pack": "node scripts/safe-git.mjs node scripts/verify-package.mjs",
86
+ "check:devkit-supply": "node scripts/check-devkit-consumption.mjs",
87
+ "check:provision-supply": "node scripts/check-provision-consumption.mjs",
88
+ "install:safe": "node scripts/safe-git.mjs pnpm install --frozen-lockfile",
89
+ "release:check": "hanamesh-release check --lockfile pnpm-lock.yaml",
90
+ "release:publish": "hanamesh-release publish --lockfile pnpm-lock.yaml",
91
+ "prepublishOnly": "hanamesh-release source-guard"
92
+ },
71
93
  "dsh": {
72
94
  "bundle": {
73
95
  "patch": "./profile/cordis.patch.yml"
@@ -115,7 +137,7 @@
115
137
  }
116
138
  },
117
139
  "devDependencies": {
118
- "@hanamesh/core": "^0.3.28",
140
+ "@hanamesh/core": "^0.3.30",
119
141
  "@deepseek-ai/cordis": "4.0.5-alpha.1",
120
142
  "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.2",
121
143
  "@deepseek-ai/dsh-scope": "0.2.1-alpha.2",
@@ -132,27 +154,8 @@
132
154
  "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.2",
133
155
  "@hanamesh/release-platform-gate": "file:vendor/hanamesh-release-platform-gate-0.1.10.tgz"
134
156
  },
157
+ "packageManager": "pnpm@10.33.0",
135
158
  "bundleDependencies": [
136
159
  "@hanamesh/lib-provision"
137
- ],
138
- "scripts": {
139
- "build": "node scripts/build.mjs",
140
- "test": "node scripts/safe-git.mjs node --test --test-reporter=tap --test-concurrency=1 tests/*.test.mjs",
141
- "test:contract": "pnpm run build && node --test --test-reporter=tap --test-concurrency=1 tests/install-target-contract.test.mjs tests/catalog-major-compat.test.mjs",
142
- "test:mutation": "node scripts/safe-git.mjs node scripts/mutations.mjs",
143
- "test:types": "tsc --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext tests/consumer.mts",
144
- "test:native-driver": "node scripts/safe-git.mjs node --test --test-reporter=tap tests/native/driver.test.mjs",
145
- "check": "node scripts/safe-git.mjs node scripts/check.mjs",
146
- "demo": "node examples/demo.mjs",
147
- "preflight:dsh": "node scripts/preflight-dsh.mjs",
148
- "check:consistency": "node scripts/check-consistency.mjs",
149
- "check:toolchain": "node scripts/check-toolchain.mjs",
150
- "preflight": "node scripts/preflight.mjs",
151
- "verify:pack": "node scripts/safe-git.mjs node scripts/verify-package.mjs",
152
- "check:devkit-supply": "node scripts/check-devkit-consumption.mjs",
153
- "check:provision-supply": "node scripts/check-provision-consumption.mjs",
154
- "install:safe": "node scripts/safe-git.mjs pnpm install --frozen-lockfile",
155
- "release:check": "hanamesh-release check --lockfile pnpm-lock.yaml",
156
- "release:publish": "hanamesh-release publish --lockfile pnpm-lock.yaml"
157
- }
158
- }
160
+ ]
161
+ }