HanaMesh app-host · 0.1.0-rc.32
rc.32(2026-09-22,设计定案 §4,T5):客户端
market席位 + dshmarket 互斥。 客户端模块在 cordis 客户端上下文provide('market', {render(options), setSettingsVisible(visible)})——席位名与形状取自钉死的壳面板dsh-tauri-panel-extension@1.0.0(随包在hanamesh-desktop-tauri/src-tauri/resources/node_modules/):它ctx.reflect.get('market'),只在typeof value.render==='function'时认账,用render({preferredSubsectionId:'installed'})画「扩展管理 · 市场」标签页,并在自己接管期间setSettingsVisible(false)、dispose 时true。render返回的就是原来的「HanaMesh 市场」页(embedded:常显、无「关闭」、preferredSubsectionId==='installed'时「已安装」排在目录前);setSettingsVisible(false)收起我们自己的侧栏「市场」入口与覆盖页。互斥:占座前先读reflect.get('market',false),再问一次GET /hanamesh/library/installedPlugins看dshmarket在不在,两者任一命中就不 provide(cordis 对同名第二次provide直接抛错,抢座会把对方打挂),改在宿主日志与市场页顶部给一条「两者只能其一」的提示,其它功能照常;库路由答不上来时 fail open。重启:壳 iframe 内的hanamesh://restart改走壳已有的入站 postMessage 桥(window.parent.postMessage({type:'hanamesh://restart'},'*'),与hanamesh://bound-refresh同一命名空间)——WKWebView 不会把 iframe 内发起的自定义 scheme 导航交给系统;hanamesh://restart这个 URL 仍是系统级入口,由壳 rc.13 的deep_link.rs处理。市场本身的功能未改。AH-MS01–MS05 测试、M17 变异、X01 新增边界market-seat-exclusive。
rc.31(2026-09-22):修 rc.30 的回归——库入口
dist/index.js经router/receipts.js静态引入了 DSH 专属 peer@deepseek-ai/dsh-storage-domain,使只装本包的应用包(如 Vibe)import即ERR_MODULE_NOT_FOUND。storage-domain 规格移到src/router/receipts-domain.js(只由插件入口src/dsh.js打开),并加守卫测试:从dist/index.js可达的模块图里不得出现@deepseek-ai/*。
rc.30(2026-09-22):= rc.28(HanaMesh 市场)+ Router 使用回执:每小时
{appId, providerId, model, hour, count}本地账(storage-domainhanamesh_router_receipts、GET /hanamesh/router/receipts),在 UTC 整点结束或该应用最后一个活实例停止时,随use事件经 usage 席位上报(targetRef=appId+ 该小时请求最多的路由行作receipt);需 usage ≥ 0.2.0-rc.7。
分支
t6-usage-receipts(2026-09-22,设计定案 §3「协议层」,T6,未发版,由派发 session 合入下一 rc):Router 使用回执。 app-host 自己的 storage domainhanamesh_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 去重)。需要 usage0.2.0-rc.7(席位接受三个可选键;更早版本会rejected INVALID_RECORD_INPUT,只 debug 日志)。
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路径)/ 需重启。已装列表含插件:扫 profilepackage.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/*缺字段或字段非法 → 400INVALID_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/。
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 变异。
rc.26(2026-09-21,O3 stage-4 官方 DSH 发行路径 PRD §A.8):纯 DSH CLI profile(只
dsh plugin add hanamesh-core+ 应用包,无桌面壳 overlay)下的应用库可用。 ①library.sources未配置时默认[{manifestUrl:'https://market.hanamesh.com/catalog-source.json',enabled:true}](首启仍只在存储为空时播种;显式sources: []表示不要来源,保持为空——schema 用union([array, undefined])区分「未设」与「空数组」,旧版 schemastery 把未设强制成[]的行为不再影响);②library.profileDir缺省时从本包自身真实安装位置推断:import.meta.url向上找到名为@hanamesh/dsh-app-host的包根并realpath,再向上找第一个node_modules/@hanamesh/dsh-app-host解析到该真实根、且package.json的dependencies/dsh.profile.bundles提到 hanamesh 的祖先目录——pnpm.pnpm存储路径永不被返回,只返回链接它的 profile;profileName=basename(profileDir);dshBin=process.argv[1]仅当它以/@deepseek-ai/dsh/lib/bin.js结尾(启动本进程的 DSH CLI),否则再尝试从 profile 解析(失败不再抛错,只是安装不可用);nodeBinary=process.execPath仅当process.versions.electron未定义(K5:Electron 宿主仍须显式配置,否则NODE_RUNTIME_REQUIREDfail-closed)。显式配置永远优先(桌面壳 overlay 全部显式,行为不变);推断结果以一行结构化日志hanamesh-app-host library: inferred {...}打出。效果:CLI 安装且已注册的 Vibe 在库里显示为registered→ 卡片「打开」而非「安装」;安装/卸载/补齐 runtime 在纯 DSH 上可用(走node bin.js plugin --profile <name> add/remove,与桌面桥完全相同)。AH-L07–L10 测试(含真实 Cordis 根经 Config schema 的 L07 plugin 用例),M10 变异。 rc.25(2026-09-21,STATUSAPP-RUNTIME-LOCK):桌面壳整棵进程树被强杀(DSH + guardian 一起死)后,应用数据根下的.runtime.lock不再永久卡住下一次「打开」。锁记录现在带 owner pid + 进程启动时间令牌、guardian 创建的子进程(launcher / app 的 pid + 启动令牌 + 可执行文件)和端口;再次 acquire 时:owner 活着 → 仍DATA_ROOT_BUSY,details.pid给出活着的 pid;owner 死了(pid 不存在,或同 pid 但启动时间不同 = pid 复用)→ 逐个核对记录的子进程:已消失或被别的进程复用 pid 的忽略,只有 pid + 启动令牌 + 可执行文件三者都对得上的孤儿才被 SIGTERM→SIGKILL 后接管锁;活着但证明不了是我们的 → 仍DATA_ROOT_BUSY,details.pid/details.ownerPid指出该进程。只用 Node 内建(macOS/Linux 用ps -o lstart=//proc,Windows 退化为只判 pid 存活、永不接管活进程)。AH-L01–L06 测试(L05/L06 真 SIGKILL guardian + 宿主后再开),M09 变异。 rc.24(2026-09-21):模型随路由——路由表加「模型」列(所路由供应商的模型列表 / 文本框;「应用默认」= 清单默认值),POST /hanamesh/router/model;自动路由行选模型即成为同供应商的显式 grant。 rc.23(2026-09-21):Router 不再替应用选模型——自动路由时{{model|默认}}落到应用声明的默认值,不再取供应商模型列表第一项(用户在 Vibe 里被塞了gpt-5.3-codex-spark的根因);无providers的派生槽位不自动路由、plan 标derived、路由表不显示。 rc.22(2026-09-21):「供应商」页压成两张表(来源目录 + 应用×槽位路由表),可扩展到多个应用;去掉网关开关(归 oauth 插件自己的设置页)和「应用自管」切换(自动路由 + 应用内设置优先已消解冲突)。rc.21(2026-09-21,用户定契约):Router 自动路由——托管模式下,profile 里已配置、且应用声明接受的供应商在启动时直接注入,不再要求用户在宿主「供应商」页逐槽位选择;显式 grant 优先,「停用」= 该槽位退出自动路由;应用内部自己的设置优先于宿主注入。
coding-oauth-gateway(dsh-coding-subscription-oauth 的本地 OpenAI 兼容 API)作为openai槽位的兜底候选。宿主「供应商」页改为:来源目录 + 网关开关(写明它只服务应用,DSH 对话不需要)+ 每个应用的只读路由状态(停用/恢复)。AH-R02/R02b 测试。rc.20(2026-09-21):在 rc.19 之上再修两处(同一个「桌面壳里打开应用空白」,在真实 WKWebView 抓到的请求头证实):① 引导 303 不再带
referrer-policy: no-referrer——它作用于跳转那一跳,/导航到达时没有 Referer,rc.19 的无 cookie 授权路径永远匹配不上;② 新增配置frameAncestors: string[](壳自己的 webview origin,如tauri://localhost)——frame-ancestors对所有祖先生效,壳 → DSH → 应用三层时只写 DSH origin 会被 webview 拒绝渲染。H16 测试。rc.19 未发布到任何地方以外的 registry。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。rc.18(2026-09-20):应用库卡片跟踪安装/补齐运行时/卸载操作——按钮进入「…中…」,订阅
/hanamesh/library/events直到-done/-failed,失败时在卡片上显示错误码与可读原因(如REGISTRY_LOOKUP_FAILED:应用包不在当前 registry),并提供「重试安装」。用户 2026-09-20 实测:点安装无任何反应。宿主逻辑不变。
交付状态: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 自己的设置段;仍待用户验收。
收录不代表审核或推荐。套件与单包互斥(双向): 已单独安装本包的用户装
hanamesh-core前先dsh plugin remove @hanamesh/dsh-app-host;已装套件(core)再显式plugin add本包同样会以duplicate loader entry id起不来,移除那次显式安装即恢复。
rc.4 增加
credentialEnv/credentialResolver;rc.5 增加文件投射根与格式;rc.6 增加文件生命周期策略;rc.7 允许停止实例在下次打开时采用新定义;rc.8 增加credentialEnv[].sets声明。逐项证据见docs/acceptance/。ACCEPTED仍只有用户能给。
本仓完成可独立运行的应用实例管理、持久视图租约、受控网关和工作台 SDK。原型源码所在私有仓当时访问返回 404,因此这是新的实现候选,不是原型提取;原型许可证和 commit 尚未核实(原型现在可读,见 <umbrella>/research/dsh-greenfield-2026-09-09/workspace/packages/hanamesh-app-host/,契约差异未逐项对照)。
本次已实现
- FIX-01: 稳定
viewId、持久租约、generation fencing;同一 view 重开不增加匿名引用;原实例精确恢复;错误关闭拒绝;心跳超时自动回收;回执丢失后可由已认证 owner 显式恢复凭据,不重新绑定视图。 - FIX-02: 按 owner / app / deployment / data 身份在 spawn 前持久预留;并发 Open 合并为一个真实应用进程;多实例传入独立 data/HOME/XDG/TMP 目录,测试检查了应用真实写入的文件。
- 生命周期: owned 进程由独立 guardian 管理;宿主被 SIGKILL 后清理自有进程组;attach 只释放附着;有占用时 Stop 拒绝并列出视图;确认清理后才发布 stopped 与逐 view 通知。
- 受控网关: 固定数字回环上游、每代租约能力票据;只调整 XFO 和 CSP frame-ancestors,保留应用正文;HTTP 上传、SSE、WebSocket 可用;不向应用注入 SDK、不传宿主凭据、不改变应用 auth/CSRF/CORS。
- 交付契约: ESM 包、TypeScript 声明、工作台侧
./clientSDK、完整快照存储接口、DSH 插件入口、测试与崩溃一致性声明。 - K5: Electron 宿主必须显式给绝对且可执行的
nodeBinary;guardian/launcher 使用同一 Node,只继承DSH_HOME/HOME/LANG/TMPDIR/PATH白名单。未配置时 fail-closed 为NODE_RUNTIME_REQUIRED。 - Router: 合并授权、撤销、文件投射 ledger 与
sets,来源为 DSH credentials/LLM 目录和 coding-oauth gateway;不搬 key 探测、录入或 OAuth 端口。 - 市场(原应用库): 自带侧栏入口与覆盖页;目录源同一时刻只启用一项;插件与应用同一入口、同一
dsh plugin add/remove路径;已装列表含插件;区分已注册、待重启、可升级与缺 runtime。 - 锁定 runtime 供给:
@hanamesh/lib-provision精确 peer 为0.1.0-rc.1,开发端使用file:vendor/;build 把该零运行时依赖产物内联到dist/provision/,来源与 SHA-256 见docs/PROVENANCE.json。
运行
当前实测环境是 macOS arm64 / Node 24.13.1 / npm 11.8.0 / pnpm 10.33.0 / DSH 0.1.5-alpha.1。干净安装使用锁文件,运行依赖为 zod 4.5.4;私有 @hanamesh/lib-provision 不在 dependencies,而是精确 peer + vendor 内联产物。
npm ci
npm run build
npm test
npm run test:mutation
npm run test:types
npm run demo -- --smoke
npm run demo 启动一个受控示例应用,并让两个视图共用同一实例;终端打印应用 URL,Ctrl+C 清理自有进程和临时示例目录。它不是 DSH、工作台或两个上游应用的演示。
typescript 5.8.3 已固定在开发依赖中。rc.3 tarball 可用 npm install --offline --ignore-scripts --legacy-peer-deps /绝对路径/hanamesh-dsh-app-host-0.1.0-rc.3.tgz 安装到独立测试工程;真实 DSH 验收须用独立 DSH_HOME,不要装到个人 profile。
验收结果在哪里
docs/acceptance/REPORT.md 分开记录真实进程、真实 SIGKILL、HTTP 传输、fixture、浏览器受阻及 DSH 未完成项;原始 TAP、4 组变异日志、干净目录包验证日志随完整源码 ZIP 一并交付;npm tarball 只含运行文件与契约,不含验收日志。浏览器测试不会修改管理员策略,也不会因为 skip 而把对应产品验收标为 PASS。
接入入口
| 消费者 | 入口 | 说明 |
|---|---|---|
| 受信宿主代码 | @hanamesh/dsh-app-host |
AppHost、存储、路由、网关 |
| 工作台顶层页面 | @hanamesh/dsh-app-host/client |
明确的 Open 回执、恢复、心跳、Stop;禁止注入应用 iframe |
| DSH profile 适配 | bundle 自动落座包根;显式适配仍可用 @hanamesh/dsh-app-host/dsh |
包根懒加载 Cordis apply,使同一个 loader entry 同时被 client-modules 发现;见 docs/DSH_INTEGRATION.md |
| DSH 浏览器 UI | @hanamesh/dsh-app-host/client-ui |
设置里的「供应商」「市场目录源」,以及侧栏「市场」(HanaMesh 市场)与 shell.overlay 页面;Node 条件下 ./client 仍解析到工作台 SDK |
契约详见 docs/CONTRACT.md、docs/ROUTER_CONTRACT.md 与 docs/LIBRARY.md,未完成步骤见 docs/DSH_INTEGRATION.md,安全限制见 docs/SECURITY.md。本仓不包含应用适配包、钱包权限或 provider runtime driver。
GitHub 与来源
rc.3 修复只在本地隔离分支 codex/app-host-acc-fix;没有创建远程仓库、提交 PR 或推送。所需原型现在有本地副本,但尚未做逐项契约对照;不能把独立实现冒称为原型提取。
rc.1 原交付包的 ZIP 含本地 Git bundle 和提交信息。scripts/publish-github.sh --create-private 是尚未执行的历史发布辅助;执行前须重新核对目标仓与授权。
当前标注 UNLICENSED,不是 MIT/Apache 授权声明;没有擅自给用户代码选择开源许可证。docs/PROVENANCE.json 保留 rc.1 原始输入与当时环境记录;rc.3 的包摘要与复验证据见源码仓 docs/acceptance/wave01-rc3-artifact.json。
No comments yet. Be the first to write one.