@hanamesh/dsh-app-host 0.2.0-rc.2 → 0.6.12

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.
Files changed (120) hide show
  1. package/LICENSE +4 -7
  2. package/README.md +136 -13
  3. package/consistency.json +41 -6
  4. package/dist/catalog/fixtures/consumer.json +35 -0
  5. package/dist/catalog/fixtures/provider.json +107 -0
  6. package/dist/catalog/suite.d.ts +4 -0
  7. package/dist/catalog/suite.js +50 -0
  8. package/dist/client-ui.d.ts +21 -3
  9. package/dist/client-ui.js +961 -94
  10. package/dist/client.js +2 -2
  11. package/dist/control-origin.js +9 -0
  12. package/dist/descriptor.js +6 -0
  13. package/dist/dsh-compat.js +6 -0
  14. package/dist/dsh.d.ts +21 -4
  15. package/dist/dsh.js +149 -58
  16. package/dist/gateway.js +140 -11
  17. package/dist/git-process.js +64 -0
  18. package/dist/git-worker.js +50 -0
  19. package/dist/index.d.ts +9 -7
  20. package/dist/install-target/fixtures/catalog.json +58 -0
  21. package/dist/install-target/fixtures/consumer-cases.json +148 -0
  22. package/dist/install-target/fixtures/install-failure-cases.json +58 -0
  23. package/dist/install-target/fixtures/provider-cases.json +618 -0
  24. package/dist/install-target/fixtures/reference-consumer.js +37 -0
  25. package/dist/install-target/fixtures/source-profile.js +123 -0
  26. package/dist/install-target/harness.js +43 -0
  27. package/dist/install-target/run.js +28 -0
  28. package/dist/install-target/suite.d.ts +19 -0
  29. package/dist/install-target/suite.js +328 -0
  30. package/dist/installation-status.d.ts +21 -0
  31. package/dist/installation-status.js +2 -0
  32. package/dist/library/catalog.js +134 -19
  33. package/dist/library/client-identity.js +31 -0
  34. package/dist/library/desktop-cli.js +7 -0
  35. package/dist/library/domain.js +3 -1
  36. package/dist/library/handoff.js +246 -0
  37. package/dist/library/install.js +138 -32
  38. package/dist/library/installation-cli.js +48 -0
  39. package/dist/library/installation-command-worker.js +27 -0
  40. package/dist/library/installation-status-http.js +26 -0
  41. package/dist/library/installation-status.js +91 -0
  42. package/dist/library/installation-worker.js +44 -0
  43. package/dist/library/locate.js +70 -9
  44. package/dist/library/node-binding.js +11 -0
  45. package/dist/library/recovery.js +66 -0
  46. package/dist/library/routes.js +90 -14
  47. package/dist/library/service.js +123 -27
  48. package/dist/library/suite.js +130 -0
  49. package/dist/manager.js +24 -8
  50. package/dist/node-runtime/domain.js +6 -0
  51. package/dist/node-runtime/releases.json +59 -0
  52. package/dist/node-runtime/service.js +101 -0
  53. package/dist/owned-probe-diagnostic.js +53 -0
  54. package/dist/router/receipts.js +8 -11
  55. package/dist/router/routes.js +7 -7
  56. package/dist/routes.js +9 -8
  57. package/dist/runtime.js +11 -1
  58. package/dist/usage-evidence.js +18 -86
  59. package/dist/workspace-origin.js +17 -0
  60. package/docs/APP_PACKAGE.md +4 -0
  61. package/docs/CONTRACT.md +68 -10
  62. package/docs/DSH_COMPATIBILITY.md +94 -0
  63. package/docs/DSH_INTEGRATION.md +41 -0
  64. package/docs/INSTALLATION_STATUS.md +96 -0
  65. package/docs/INSTALL_TARGET.md +130 -0
  66. package/docs/LIBRARY.md +44 -3
  67. package/docs/LICENSES.json +189 -61
  68. package/docs/PROVENANCE.json +53 -12
  69. package/docs/SECURITY.md +31 -0
  70. package/node_modules/@hanamesh/lib-provision/LICENSE +15 -0
  71. package/node_modules/@hanamesh/lib-provision/README.md +134 -0
  72. package/node_modules/@hanamesh/lib-provision/THIRD_PARTY_NOTICES.md +17 -0
  73. package/node_modules/@hanamesh/lib-provision/bin/hanamesh-provision.js +13 -0
  74. package/node_modules/@hanamesh/lib-provision/consistency.json +20 -0
  75. package/node_modules/@hanamesh/lib-provision/contract/CHANGELOG.md +5 -0
  76. package/node_modules/@hanamesh/lib-provision/contract/conformance.mjs +80 -0
  77. package/node_modules/@hanamesh/lib-provision/contract/fixtures/consumer.json +9 -0
  78. package/node_modules/@hanamesh/lib-provision/contract/fixtures/provider.json +11 -0
  79. package/node_modules/@hanamesh/lib-provision/contract/lib-provision.schema.json +47 -0
  80. package/node_modules/@hanamesh/lib-provision/package.json +50 -0
  81. package/package.json +81 -33
  82. package/schemas/catalog-provider-page.schema.json +4 -2
  83. package/schemas/catalog-source.schema.json +9 -7
  84. package/schemas/install-target.schema.json +298 -0
  85. package/dist/provision/LICENSE +0 -6
  86. package/dist/provision/THIRD_PARTY_NOTICES.md +0 -13
  87. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/archive.d.ts +0 -0
  88. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/archive.js +0 -0
  89. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/check.d.ts +0 -0
  90. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/check.js +0 -0
  91. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/checkpoints.d.ts +0 -0
  92. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/checkpoints.js +0 -0
  93. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/cli.d.ts +0 -0
  94. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/cli.js +0 -0
  95. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/download.d.ts +0 -0
  96. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/download.js +0 -0
  97. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/errors.d.ts +0 -0
  98. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/errors.js +0 -0
  99. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/extract.d.ts +0 -0
  100. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/extract.js +0 -0
  101. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/fsutil.d.ts +0 -0
  102. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/fsutil.js +0 -0
  103. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/index.d.ts +0 -0
  104. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/index.js +0 -0
  105. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/ledger.d.ts +0 -0
  106. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/ledger.js +0 -0
  107. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/manifest.d.ts +0 -0
  108. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/manifest.js +0 -0
  109. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/paths.d.ts +0 -0
  110. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/paths.js +0 -0
  111. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/platform.d.ts +0 -0
  112. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/platform.js +0 -0
  113. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/provision.d.ts +0 -0
  114. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/provision.js +0 -0
  115. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/tar.d.ts +0 -0
  116. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/tar.js +0 -0
  117. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/types.d.ts +0 -0
  118. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/types.js +0 -0
  119. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/zip.d.ts +0 -0
  120. /package/{dist/provision → node_modules/@hanamesh/lib-provision/lib}/zip.js +0 -0
package/dist/client-ui.js CHANGED
@@ -2,93 +2,856 @@
2
2
  window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(require){'use strict';
3
3
  const React=require('react');
4
4
  const h=React.createElement;
5
+ // Owned dictionaries; host locale remains the only language preference.
6
+ const COPY={
7
+ "zh": {
8
+ "copy0": "自动",
9
+ "copy1": "手动指定",
10
+ "copy2": "无可用供应商",
11
+ "copy3": "已停用",
12
+ "copy4": "应用自管",
13
+ "copy5": "应用默认(",
14
+ "copy6": "应用默认",
15
+ "copy7": "恢复",
16
+ "copy8": "停用",
17
+ "copy9": "供应商",
18
+ "copy10": "查看可用的模型服务并选择应用使用的模型。密钥值不会显示。",
19
+ "copy11": "名称",
20
+ "copy12": "来源",
21
+ "copy13": "状态",
22
+ "copy14": "密钥提示",
23
+ "copy15": "模型",
24
+ "copy16": "在 DSH「模型」中管理模型服务;Coding OAuth 的开关在该插件的设置页。",
25
+ "copy17": "应用使用的服务",
26
+ "copy18": "应用会自动使用已配置且支持的供应商。可在下方选择模型;保留「应用默认」则使用应用的默认模型。「停用」会停止向该应用提供此服务。",
27
+ "copy19": "应用",
28
+ "copy20": "槽位",
29
+ "copy21": "用途",
30
+ "copy22": "尚无需要模型服务的已安装应用。",
31
+ "copy23": "Core 未提供套件声明",
32
+ "copy24": "Core 套件声明无法用于精确安装。",
33
+ "copy25": "兼容升级检查未通过,尚未执行安装:",
34
+ "copy26": "检测不到结果",
35
+ "copy27": "套件升级未完成:",
36
+ "copy28": "。请核对当前版本;完全退出后可使用 HanaMesh 设置中的补齐命令修复并重新打开。",
37
+ "copy29": "套件升级未完成或结果尚未确认:",
38
+ "copy30": "。请核对已装版本;不会自动重试。",
39
+ "copy31": "网站交接:",
40
+ "copy32": "没能告诉网站「确认框已弹出」",
41
+ "copy33": "没能告诉网站「已取消」,网站会在作废时间到后显示过期",
42
+ "copy34": "没能告诉网站「确认框打不开」",
43
+ "copy35": "确认框打不开",
44
+ "copy36": "网站交接:读取交接失败,网站上的「安装」会一直等到过期(",
45
+ "copy37": "检测到 DSH Market(dshmarket):「扩展管理」的市场标签只能由一个插件提供,这次没有由 HanaMesh 市场接管。两者只能其一——要用 HanaMesh 市场请先卸载 dshmarket 并重启 DSH;要用 dshmarket 就从下方侧栏的「市场」入口使用 HanaMesh 市场。",
46
+ "copy38": "市场",
47
+ "copy39": "市场目录源",
48
+ "copy40": "可保存多个来源,但浏览时只启用一个。",
49
+ "copy41": "已启用",
50
+ "copy42": "启用",
51
+ "copy43": "上移",
52
+ "copy44": "下移",
53
+ "copy45": "删除",
54
+ "copy46": "添加并启用",
55
+ "copy47": "套件安装包来源",
56
+ "copy48": "选择三个官方套件组件的 npm 安装包 registry。市场目录源提供浏览清单,在「市场目录源」中单独设置。",
57
+ "copy49": "安装包来源",
58
+ "copy50": "现有配置(默认)",
59
+ "copy51": "Core 当前本机来源",
60
+ "copy52": "现有 registry:",
61
+ "copy53": "已保存选择:",
62
+ "copy54": "现有配置",
63
+ "copy55": "Core 来源:",
64
+ "copy56": " · 尚不可观察,请检查 Core 插件及其来源设置。",
65
+ "copy57": "保存选择不会安装。检查、确认和执行前都重新读取当前来源与 Core 兼容声明;来源变化后须重新查看并确认。",
66
+ "copy58": "正在保存…",
67
+ "copy59": "保存套件安装包来源",
68
+ "copy60": "刷新来源状态",
69
+ "copy61": "正在读取套件安装包来源…",
70
+ "copy62": "进度读取中断:",
71
+ "copy63": "。准备可能仍在进行,请点击「刷新状态」查看;不会自动重试。",
72
+ "copy64": "插件管理(推荐)",
73
+ "copy65": "已有独立 Node(高级)",
74
+ "copy66": "检查位置与空间",
75
+ "copy67": "下载",
76
+ "copy68": "校验 SHA-256",
77
+ "copy69": "解压",
78
+ "copy70": "验证独立 Node",
79
+ "copy71": "保存运行时",
80
+ "copy72": "保存并启用(已完成校验,不能再取消)",
81
+ "copy73": "环境已就绪",
82
+ "copy74": "检查已有环境",
83
+ "copy75": "安装环境",
84
+ "copy76": "准备一次,后续安装自动使用。无需重启 DSH,也无需填写 Node 路径;不会改动客户端或系统 Node。",
85
+ "copy77": "环境已就绪 · 后续插件操作自动使用独立 Node",
86
+ "copy78": "尚未准备安装环境;确认前不会下载。",
87
+ "copy79": "正在读取安装环境…",
88
+ "copy80": "已有路径仅在高级方式生效。下载来源必须使用 HTTPS;版本只能选择已校验发行。修改设置后先保存,再重新确认。",
89
+ "copy81": "保存环境设置",
90
+ "copy82": "准备安装环境",
91
+ "copy83": "刷新状态",
92
+ "copy84": "确认准备安装环境",
93
+ "copy85": "确认下载并准备",
94
+ "copy86": "平台 ",
95
+ "copy87": " · 最低 ",
96
+ "copy88": "下载来源",
97
+ "copy89": "下载体积",
98
+ "copy90": "解压后文件",
99
+ "copy91": "准备所需空间",
100
+ "copy92": "存放位置",
101
+ "copy93": "。确认后只从上述来源下载;不自动重试或换源。",
102
+ "copy94": "完整校验值与检查说明",
103
+ "copy95": "校验归档 SHA、文件所有权、平台与精确版本;Electron 包装脚本不会通过。",
104
+ "copy96": "暂不准备",
105
+ "copy97": "取消准备",
106
+ "copy98": "诊断详情",
107
+ "copy99": "当前自动绑定的路径",
108
+ "copy100": "支持 macOS 13.5+(Apple Silicon / Intel);Windows 暂不支持。",
109
+ "copy101": "读取中…",
110
+ "copy102": "正在 ",
111
+ "copy103": " 接收网站交接",
112
+ "copy104": "已关闭:网站上的「安装」按钮找不到本客户端",
113
+ "copy105": "未启动",
114
+ "copy106": "网站安装交接",
115
+ "copy107": "网站(如 market.hanamesh.com)点「安装」时,会在下列本机端口上找到本客户端,把要装的插件交给应用库;应用库在这里弹出确认,你确认后才安装。",
116
+ "copy108": "安装前需要独立 Node.js 24。请打开 AppHost「安装环境」点击「准备安装环境」,核对来源、版本和体积后确认;完成后自动可用,无需重启或填写路径。已有独立 Node 可在安装环境高级设置中指定,不支持 Electron 包装脚本。",
117
+ "copy109": " 启用(",
118
+ "copy110": ",默认 ",
119
+ "copy111": "开",
120
+ "copy112": "关",
121
+ "copy113": "端口(",
122
+ "copy114": ";按顺序取第一个空闲的)",
123
+ "copy115": "允许交接的网站(",
124
+ "copy116": ",每行一个,默认 ",
125
+ "copy117": "未确认多久后作废,毫秒(",
126
+ "copy118": " 分钟)",
127
+ "copy119": "应用库多久查一次新的交接,毫秒(",
128
+ "copy120": "保存并重新监听",
129
+ "copy121": "固定规则(不能关)",
130
+ "copy122": "为什么:",
131
+ "copy123": "安装命令返回成功,但 profile 读回的版本与固定版本不一致",
132
+ "copy124": "包不在当前 registry 上——尚未发布到 npm,或客户端 registry 设置不对",
133
+ "copy125": "DSH 安装命令失败,查看宿主日志",
134
+ "copy126": "当前宿主未提供安装参数(profile / node 路径)",
135
+ "copy127": "目录里已没有这一项,刷新后重试",
136
+ "copy128": "registry 上的最新版是预发布版(rc),客户端未允许安装预发布版",
137
+ "copy129": "运行时归档下载失败,检查网络或来源",
138
+ "copy130": "10 分钟内没有结果,可稍后重试",
139
+ "copy131": "HanaMesh 套件自身不能从市场安装或卸载",
140
+ "copy132": "先关闭该应用的所有视图再卸载",
141
+ "copy133": "详细原因见宿主日志",
142
+ "copy134": ";升级未完成,部分组件可能已改变。当前读回:",
143
+ "copy135": "检测不到",
144
+ "copy136": "(未启用)",
145
+ "copy137": "。请完全退出客户端,使用补齐命令(仅 macOS / Linux 终端;Windows 安全命令暂未提供)修复并重新打开:",
146
+ "copy138": ";但应用库没能读回 profile(",
147
+ "copy139": "),已安装列表可能仍显示「需重启」",
148
+ "copy140": ";安装前状态未被改动(已逐项读回)",
149
+ "copy141": ";已恢复到原版本 ",
150
+ "copy142": "(已逐项读回)",
151
+ "copy143": ";已恢复到安装前状态(已逐项读回)",
152
+ "copy144": ";恢复失败",
153
+ "copy145": ",未恢复:",
154
+ "copy146": "未知",
155
+ "copy147": ",安装前原件:",
156
+ "copy148": "。未安装成功,也未回到安装前状态,需要人工处理",
157
+ "copy149": "全部",
158
+ "copy150": "插件",
159
+ "copy151": "仅收录",
160
+ "copy152": "已安装",
161
+ "copy153": "需重启",
162
+ "copy154": "已卸载 · 需重启",
163
+ "copy155": "缺运行时",
164
+ "copy156": "包无效",
165
+ "copy157": "需重新打开客户端",
166
+ "copy158": "插件更改需要重新打开客户端后生效。",
167
+ "copy159": "请完全退出 HanaMesh 客户端或 DSH 后重新打开;关闭窗口不等于退出。",
168
+ "copy160": "中…",
169
+ "copy161": "读回中…",
170
+ "copy162": "完成,已生效,可以直接使用",
171
+ "copy163": "完成,重启 DSH 后生效",
172
+ "copy164": "结果不可追溯,请查看已安装列表后再明确操作",
173
+ "copy165": "失败:",
174
+ "copy166": "升级套件",
175
+ "copy167": "安装",
176
+ "copy168": "套件升级已读回,请完全退出并重新打开客户端。",
177
+ "copy169": "套件版本读回失败,请检查三个组件。",
178
+ "copy170": "安装完成,已加载,可以直接使用(已按精确版本读回)",
179
+ "copy171": "安装完成,已按精确版本读回",
180
+ "copy172": "安装读回失败:",
181
+ "copy173": "安装操作已完成,但目录读回失败:",
182
+ "copy174": "打开中…",
183
+ "copy175": "打开",
184
+ "copy176": "仅收录,不可安装",
185
+ "copy177": "HanaMesh 套件",
186
+ "copy178": "已安装 · Core ",
187
+ "copy179": "套件不完整,请查看组件状态",
188
+ "copy180": "套件状态未知,请查看 Core 声明与阻断原因",
189
+ "copy181": "Core 完整性:",
190
+ "copy182": " · 要求 ",
191
+ "copy183": " · 实际 ",
192
+ "copy184": " · 未启用",
193
+ "copy185": "检查兼容升级",
194
+ "copy186": "重试安装",
195
+ "copy187": "升级",
196
+ "copy188": "升级到 ",
197
+ "copy189": "补齐运行时",
198
+ "copy190": "卸载",
199
+ "copy191": "已卸载,重启 DSH 后生效",
200
+ "copy192": "已安装,重启 DSH 后生效",
201
+ "copy193": "未知发布者",
202
+ "copy194": "关闭中…",
203
+ "copy195": "关闭视图",
204
+ "copy196": "更多",
205
+ "copy197": "应用 · ",
206
+ "copy198": "插件 · ",
207
+ "copy199": "(未启用为 profile 层)",
208
+ "copy200": "HanaMesh 市场",
209
+ "copy201": "关闭",
210
+ "copy202": "启动时没能读取 profile(",
211
+ "copy203": "),「需重启」状态无法判断,下面的状态都按已加载显示",
212
+ "copy204": "安装目标开发小界面",
213
+ "copy205": "安装目标",
214
+ "copy206": "输入目录条目 ID 或包名,市场查询当前目录后确认安装。",
215
+ "copy207": "目录条目 ID",
216
+ "copy208": "itemId(可选)",
217
+ "copy209": "包名",
218
+ "copy210": "packageName(可选)",
219
+ "copy211": "查看安装目标",
220
+ "copy212": "市场安装确认",
221
+ "copy213": "插件名",
222
+ "copy214": "版本",
223
+ "copy215": "目录未提供版本",
224
+ "copy216": "测试目录(fixture)",
225
+ "copy217": "当前市场目录",
226
+ "copy218": "确认后升级三个官方组件;需要完全退出并重新打开客户端。",
227
+ "copy219": "确认安装",
228
+ "copy220": "取消",
229
+ "copy221": "正在查询市场目录…",
230
+ "copy222": "安装中…",
231
+ "copy223": "安装完成,请查看已安装列表",
232
+ "copy224": "安装完成,已加载,可以直接使用",
233
+ "copy225": "已取消,未安装",
234
+ "copy226": "安装结果不可追溯,请查看已安装列表;不会自动重试",
235
+ "copy227": "已安装,无需重复安装",
236
+ "copy228": "未安装:",
237
+ "copy229": "安装失败",
238
+ "copy230": "目录条目来源",
239
+ "copy231": "C-MARKET 合约验证",
240
+ "copy232": "测试版;输入与输出仅供当前客户端验证。安装仍须在正常确认框确认。",
241
+ "copy233": "合约调用",
242
+ "copy234": "合约样例输入",
243
+ "copy235": "调用合约",
244
+ "copy236": "合约调用输出",
245
+ "copy237": "尚未调用",
246
+ "copy238": "搜索应用与插件",
247
+ "copy239": "搜索",
248
+ "copy240": "类别",
249
+ "copy241": "运行方式",
250
+ "copy242": "Node 版本",
251
+ "copy243": "已有独立 Node 路径",
252
+ "copy244": "市场目录暂时无法读取。请检查目录源设置,或等待目录提供方更新后再读取。安装尚未开始。",
253
+ "copy245": "本次操作未能完成。请查看技术详情中的准确原因,检查相关设置后再操作。",
254
+ "copy246": "技术详情",
255
+ "copy247": "安装环境管理方式",
256
+ "copy248": "独立 Node 固定版本",
257
+ "copy249": "Node 下载来源",
258
+ "copy250": "独立 Node 存放位置",
259
+ "copy251": "已有独立 Node 路径(高级)",
260
+ "copy252": "一键准备支持 macOS 13.5+ arm64 / x64;Windows 尚不支持。下载仅在确认后开始;不重试、不切换来源。",
261
+ "copy253": "Node.js MIT;归档内 LICENSE 保留其第三方声明",
262
+ "copy254": "Core 来源暂不可用。请检查 Core 插件及其来源设置。",
263
+ "copy255": "运行时记录与已校验的官方发行记录不一致。",
264
+ "copy256": "请填写完整的安装环境设置。",
265
+ "copy257": "请选择已校验的固定 Node 版本,不能填写 latest。",
266
+ "copy258": "下载来源必须是没有账号、密码、查询参数的 HTTPS 地址。",
267
+ "copy259": "请选择绝对的专用存放目录。",
268
+ "copy260": "已有独立 Node 路径必须是绝对路径。",
269
+ "copy261": "准备安装环境未完成。请查看技术详情中的准确原因;未自动换源或重试。",
270
+ "copy262": "只在本机 127.0.0.1 上监听,请求的 Host 必须是 127.0.0.1 或 localhost 加监听端口",
271
+ "copy263": "防止别的机器或 DNS 重绑定的网页冒充本机",
272
+ "copy264": "只有「允许交接的网站」里的网站能探测本客户端、交接安装、读交接状态;别的网站什么也读不到",
273
+ "copy265": "否则任何网页都能知道你装了什么客户端、缺什么,用来识别你",
274
+ "copy266": "网站交接只会在应用库里弹出确认;只有你点「确认安装」才会安装",
275
+ "copy267": "网站不能替你决定装什么",
276
+ "copy268": "交接状态只给发起它的那个网站看",
277
+ "copy269": "一个网站不能读另一个网站的交接",
278
+ "copy270": "交接请求体不超过 4096 字节",
279
+ "copy271": "合法请求只有插件 id、包名、合约版本三项,最长不到 500 字节;更大的一定不是交接",
280
+ "copy272": "端口在 1024–65535 之间、互不重复,至少一个",
281
+ "copy273": "1024 以下要管理员权限;没有端口就没有地方让网站找到",
282
+ "copy274": "网站必须写成来源(协议 + 域名 [+ 端口]),http 只允许 127.0.0.1 / localhost",
283
+ "copy275": "浏览器按来源校验;明文 http 的公网网站可被中间人冒充",
284
+ "copy276": "轮询间隔是 1–2147483647 毫秒的整数,作废时间必须大于轮询间隔",
285
+ "copy277": "计时器超过上限会立即触发;作废早于第一次轮询,交接永远不会被看到",
286
+ "copy278": "进度读取已中断,准备可能仍在进行。请点击「刷新状态」查看;不会自动重试。",
287
+ "copy279": "DSH 模型设置",
288
+ "copy280": "Coding OAuth 本地网关",
289
+ "copy281": "已配置",
290
+ "copy282": "尚未配置",
291
+ "copy283": "暂时无法连接",
292
+ "copy284": "网关已关闭",
293
+ "copy285": "状态暂不可用",
294
+ "copy286": "来源暂不可用",
295
+ "copy287": "原始记录",
296
+ "copy288": "操作",
297
+ "copy289": "应用启动时按当前配置与应用声明匹配供应商并注入;这里只选择模型或撤回、恢复该连接的授权,不改变来源开关。",
298
+ "copy290": "网站点「安装」后,本客户端会请你确认;确认后才安装。",
299
+ "copy291": "安装前请先在「安装环境」准备所需环境。",
300
+ "copy292": "启用网站安装交接",
301
+ "copy293": "本机端口",
302
+ "copy294": "允许的网站(每行一个)",
303
+ "copy295": "确认等待时间(毫秒)",
304
+ "copy296": "检查新交接的间隔(毫秒)",
305
+ "copy297": "设置名称与默认值",
306
+ "copy298": "使用说明",
307
+ "copy300": "高级设置",
308
+ "copy301": "支持 macOS 13.5+(Apple Silicon / Intel)。Windows 的安装、守护与取消验证尚未接通,本流程不声称支持 Windows。"
309
+ },
310
+ "en": {
311
+ "copy0": "Automatic",
312
+ "copy1": "Manually assigned",
313
+ "copy2": "No available provider",
314
+ "copy3": "Disabled",
315
+ "copy4": "App-managed",
316
+ "copy5": "App default (",
317
+ "copy6": "App default",
318
+ "copy7": "Restore",
319
+ "copy8": "Disable",
320
+ "copy9": "Providers",
321
+ "copy10": "View available model services and choose a model for each app. Key values are never shown.",
322
+ "copy11": "Name",
323
+ "copy12": "Source",
324
+ "copy13": "Status",
325
+ "copy14": "Key hint",
326
+ "copy15": "Models",
327
+ "copy16": "Manage model services in DSH Models. Manage Coding OAuth in its own plugin settings.",
328
+ "copy17": "Services used by apps",
329
+ "copy18": "Apps automatically use configured providers they support. Choose a model below, or keep App default to use the app’s default model. Revoke stops this service from being supplied to the app.",
330
+ "copy19": "App",
331
+ "copy20": "Slot",
332
+ "copy21": "Purpose",
333
+ "copy22": "No installed apps need a model service yet.",
334
+ "copy23": "Core has not provided a suite declaration",
335
+ "copy24": "Core’s suite declaration cannot be used for an exact installation.",
336
+ "copy25": "Compatibility check failed; nothing was installed: ",
337
+ "copy26": "No result available",
338
+ "copy27": "Suite upgrade did not complete: ",
339
+ "copy28": ". Check the current versions. Fully quit the client, use the repair command in HanaMesh settings, then reopen it.",
340
+ "copy29": "Suite upgrade is incomplete or unconfirmed: ",
341
+ "copy30": ". Check the installed versions. No automatic retry will run.",
342
+ "copy31": "Website handoff: ",
343
+ "copy32": "Could not tell the website that confirmation is open",
344
+ "copy33": "Could not report cancellation; the website will show expiry when the request expires",
345
+ "copy34": "Could not tell the website that confirmation failed to open",
346
+ "copy35": "Could not open confirmation",
347
+ "copy36": "Website handoff could not be read. The website installation request will wait until expiry (",
348
+ "copy37": "DSH Market (dshmarket) is installed. Only one plugin can provide the Extension Management market tab, so HanaMesh did not take that tab. To use the HanaMesh tab, uninstall dshmarket and restart DSH. To keep dshmarket, open HanaMesh from Market in the sidebar.",
349
+ "copy38": "Market",
350
+ "copy39": "Market catalog sources",
351
+ "copy40": "Save multiple sources; only one is active while browsing.",
352
+ "copy41": "Enabled",
353
+ "copy42": "Enable",
354
+ "copy43": "Move up",
355
+ "copy44": "Move down",
356
+ "copy45": "Remove",
357
+ "copy46": "Add and enable",
358
+ "copy47": "Suite package source",
359
+ "copy48": "Choose the npm registry for the three official suite components. Browseable catalogs are configured separately in Market catalog sources.",
360
+ "copy49": "Package source",
361
+ "copy50": "Existing configuration (default)",
362
+ "copy51": "Core’s current local source",
363
+ "copy52": "Configured registry: ",
364
+ "copy53": "Saved selection: ",
365
+ "copy54": "Existing configuration",
366
+ "copy55": "Core source: ",
367
+ "copy56": " · Source is not observable. Check Core and its source settings.",
368
+ "copy57": "Saving does not install anything. The current source and Core compatibility declaration are read again before checking, confirming and installing. If the source changes, review and confirm again.",
369
+ "copy58": "Saving…",
370
+ "copy59": "Save suite package source",
371
+ "copy60": "Refresh source status",
372
+ "copy61": "Reading suite package source…",
373
+ "copy62": "Progress updates stopped: ",
374
+ "copy63": ". Preparation may still be running. Select Refresh status to check. No automatic retry will run.",
375
+ "copy64": "Plugin-managed (recommended)",
376
+ "copy65": "Existing standalone Node (advanced)",
377
+ "copy66": "Checking location and space",
378
+ "copy67": "Downloading",
379
+ "copy68": "Verifying SHA-256",
380
+ "copy69": "Extracting",
381
+ "copy70": "Verifying standalone Node",
382
+ "copy71": "Saving runtime",
383
+ "copy72": "Saving and enabling (verified; cancellation is no longer available)",
384
+ "copy73": "Environment ready",
385
+ "copy74": "Checking existing environment",
386
+ "copy75": "Installation environment",
387
+ "copy76": "Prepare once for future installations. No DSH restart or Node path entry is needed. The client and system Node are unchanged.",
388
+ "copy77": "Environment ready · future plugin operations use standalone Node automatically",
389
+ "copy78": "Installation environment is not prepared. Nothing downloads before confirmation.",
390
+ "copy79": "Reading installation environment…",
391
+ "copy80": "The existing path is used only in advanced mode. Downloads require HTTPS and a verified release. After changing settings, save and confirm a new plan.",
392
+ "copy81": "Save environment settings",
393
+ "copy82": "Prepare installation environment",
394
+ "copy83": "Refresh status",
395
+ "copy84": "Confirm installation environment preparation",
396
+ "copy85": "Confirm download and preparation",
397
+ "copy86": "Platform ",
398
+ "copy87": " · Minimum ",
399
+ "copy88": "Download source",
400
+ "copy89": "Download size",
401
+ "copy90": "Extracted files",
402
+ "copy91": "Space required",
403
+ "copy92": "Storage location",
404
+ "copy93": ". After confirmation, only the source above is used. No automatic retry or source switching.",
405
+ "copy94": "Full checksum and verification details",
406
+ "copy95": "Checks archive SHA, file ownership, platform and exact version. Electron wrapper scripts are rejected.",
407
+ "copy96": "Not now",
408
+ "copy97": "Cancel preparation",
409
+ "copy98": "Technical details",
410
+ "copy99": "Automatically selected executable",
411
+ "copy100": "Supports macOS 13.5+ (Apple Silicon / Intel). Windows is not supported.",
412
+ "copy101": "Reading…",
413
+ "copy102": "Receiving website handoffs at ",
414
+ "copy103": " ",
415
+ "copy104": "Disabled: the website Install button cannot find this client",
416
+ "copy105": "Not started",
417
+ "copy106": "Website installation handoff",
418
+ "copy107": "When Install is selected on a website such as market.hanamesh.com, the website finds this client on the local ports below. The market opens confirmation here; nothing installs until you confirm.",
419
+ "copy108": "Installation requires standalone Node.js 24. In AppHost Installation environment, select Prepare installation environment, review the source, version and size, then confirm. It becomes usable without restarting or entering a path. An existing standalone Node can be set in advanced settings. Electron wrappers are not supported.",
420
+ "copy109": " Enable (",
421
+ "copy110": ", default ",
422
+ "copy111": "On",
423
+ "copy112": "Off",
424
+ "copy113": "Ports (",
425
+ "copy114": "; the first available port is used)",
426
+ "copy115": "Allowed websites (",
427
+ "copy116": ", one per line, default ",
428
+ "copy117": "Unconfirmed request lifetime, milliseconds (",
429
+ "copy118": " minutes)",
430
+ "copy119": "Check for new handoffs every, milliseconds (",
431
+ "copy120": "Save and restart listener",
432
+ "copy121": "Required rules",
433
+ "copy122": "Reason: ",
434
+ "copy123": "Installation command succeeded, but the version read from the profile differs from the confirmed version",
435
+ "copy124": "Package not found in the current registry. It may not be published, or the registry setting may be incorrect",
436
+ "copy125": "DSH installation command failed. See the host logs",
437
+ "copy126": "This host has not provided installation parameters (profile / Node path)",
438
+ "copy127": "This item is no longer in the catalog. Refresh before trying again",
439
+ "copy128": "The latest registry version is a prerelease, which this client has not allowed",
440
+ "copy129": "Runtime archive download failed. Check the network or source",
441
+ "copy130": "No result within 10 minutes. Check the installed state before trying again",
442
+ "copy131": "The HanaMesh suite cannot be installed or uninstalled through the market",
443
+ "copy132": "Close all views of this app before uninstalling it",
444
+ "copy133": "See the host logs for the detailed reason",
445
+ "copy134": "; upgrade incomplete, and some components may have changed. Current readback: ",
446
+ "copy135": "Not available",
447
+ "copy136": " (disabled)",
448
+ "copy137": ". Fully quit the client and run the repair command (macOS / Linux terminal only; no safe Windows command is supplied), then reopen: ",
449
+ "copy138": "; the market could not read the profile (",
450
+ "copy139": "), so the installed list may still show Restart required",
451
+ "copy140": "; the pre-install state is unchanged (each item read back)",
452
+ "copy141": "; restored to previous version ",
453
+ "copy142": " (each item read back)",
454
+ "copy143": "; restored to the pre-install state (each item read back)",
455
+ "copy144": "; recovery failed",
456
+ "copy145": "; not restored: ",
457
+ "copy146": "Unknown",
458
+ "copy147": "; original files before installation: ",
459
+ "copy148": ". Installation failed and the previous state was not restored. Manual intervention is required",
460
+ "copy149": "All",
461
+ "copy150": "Plugins",
462
+ "copy151": "Listed only",
463
+ "copy152": "Installed",
464
+ "copy153": "Restart required",
465
+ "copy154": "Uninstalled · restart required",
466
+ "copy155": "Runtime missing",
467
+ "copy156": "Invalid package",
468
+ "copy157": "Reopen the client",
469
+ "copy158": "Plugin changes take effect after reopening the client.",
470
+ "copy159": "Fully quit the HanaMesh client or DSH, then reopen it. Closing a window does not quit the app.",
471
+ "copy160": " in progress…",
472
+ "copy161": "Reading back…",
473
+ "copy162": " complete and active; ready to use",
474
+ "copy163": " complete; restart DSH to apply",
475
+ "copy164": "The result is unavailable. Check the installed list before taking another action",
476
+ "copy165": " failed: ",
477
+ "copy166": "Upgrade suite",
478
+ "copy167": "Install",
479
+ "copy168": "Suite versions read back. Fully quit and reopen the client.",
480
+ "copy169": "Suite version readback failed. Check all three components.",
481
+ "copy170": "Installation complete and loaded; ready to use (exact version read back)",
482
+ "copy171": "Installation complete; exact version read back",
483
+ "copy172": "Installation readback failed: ",
484
+ "copy173": "Installation operation completed, but catalog readback failed: ",
485
+ "copy174": "Opening…",
486
+ "copy175": "Open",
487
+ "copy176": "Listed only; not installable",
488
+ "copy177": "HanaMesh suite",
489
+ "copy178": "Installed · Core ",
490
+ "copy179": "Suite incomplete. Check component status",
491
+ "copy180": "Suite status unknown. Check the Core declaration and blocking reasons",
492
+ "copy181": "Core completeness: ",
493
+ "copy182": " · Required ",
494
+ "copy183": " · Actual ",
495
+ "copy184": " · Disabled",
496
+ "copy185": "Check compatible upgrade",
497
+ "copy186": "Retry installation",
498
+ "copy187": "Upgrade",
499
+ "copy188": "Upgrade to ",
500
+ "copy189": "Prepare runtime",
501
+ "copy190": "Uninstall",
502
+ "copy191": "Uninstalled; restart DSH to apply",
503
+ "copy192": "Installed; restart DSH to apply",
504
+ "copy193": "Unknown publisher",
505
+ "copy194": "Closing…",
506
+ "copy195": "Close view",
507
+ "copy196": "More",
508
+ "copy197": "App · ",
509
+ "copy198": "Plugin · ",
510
+ "copy199": " (not enabled in profile)",
511
+ "copy200": "HanaMesh Market",
512
+ "copy201": "Close",
513
+ "copy202": "The profile could not be read at startup (",
514
+ "copy203": "). Restart requirements are unknown; the states below reflect loaded components",
515
+ "copy204": "Installation target developer panel",
516
+ "copy205": "Installation target",
517
+ "copy206": "Enter a catalog item ID or package name. The market reads the current catalog before asking for confirmation.",
518
+ "copy207": "Catalog item ID",
519
+ "copy208": "itemId (optional)",
520
+ "copy209": "Package name",
521
+ "copy210": "packageName (optional)",
522
+ "copy211": "Review installation target",
523
+ "copy212": "Confirm market installation",
524
+ "copy213": "Plugin name",
525
+ "copy214": "Version",
526
+ "copy215": "No version provided by catalog",
527
+ "copy216": "Test catalog (fixture)",
528
+ "copy217": "Current market catalog",
529
+ "copy218": "Confirmation upgrades three official components. Fully quit and reopen the client afterwards.",
530
+ "copy219": "Confirm installation",
531
+ "copy220": "Cancel",
532
+ "copy221": "Reading market catalog…",
533
+ "copy222": "Installing…",
534
+ "copy223": "Installation complete. Check the installed list",
535
+ "copy224": "Installation complete and loaded; ready to use",
536
+ "copy225": "Cancelled; nothing installed",
537
+ "copy226": "Installation result unavailable. Check the installed list; no automatic retry will run",
538
+ "copy227": "Already installed; no need to install again",
539
+ "copy228": "Not installed: ",
540
+ "copy229": "Installation failed",
541
+ "copy230": "Catalog item source",
542
+ "copy231": "C-MARKET contract verification",
543
+ "copy232": "Test build. Inputs and outputs verify only this client. Installations still require normal confirmation.",
544
+ "copy233": "Contract call",
545
+ "copy234": "Contract sample input",
546
+ "copy235": "Call contract",
547
+ "copy236": "Contract output",
548
+ "copy237": "Not called yet",
549
+ "copy238": "Search apps and plugins",
550
+ "copy239": "Search",
551
+ "copy240": "Category",
552
+ "copy241": "Runtime mode",
553
+ "copy242": "Node version",
554
+ "copy243": "Existing standalone Node path",
555
+ "copy244": "The market catalog cannot be read. Check catalog source settings, or read it again after the provider updates it. Installation has not started.",
556
+ "copy245": "This operation could not complete. Check the exact reason in Technical details and review the related settings before trying again.",
557
+ "copy246": "Technical details",
558
+ "copy247": "Environment management mode",
559
+ "copy248": "Pinned standalone Node version",
560
+ "copy249": "Node download source",
561
+ "copy250": "Standalone Node storage location",
562
+ "copy251": "Existing standalone Node path (advanced)",
563
+ "copy252": "One-click preparation supports macOS 13.5+ arm64 / x64. Windows is not supported. Download starts only after confirmation, without retries or source switching.",
564
+ "copy253": "Node.js MIT; the archive LICENSE retains third-party notices",
565
+ "copy254": "Core source is not available. Check Core and its source settings.",
566
+ "copy255": "The runtime record does not match a verified official release.",
567
+ "copy256": "Complete every installation environment setting.",
568
+ "copy257": "Select a verified pinned Node version. The value latest is not accepted.",
569
+ "copy258": "The download source must be an HTTPS URL without credentials or query parameters.",
570
+ "copy259": "Choose an absolute path to a dedicated storage directory.",
571
+ "copy260": "The existing standalone Node path must be absolute.",
572
+ "copy261": "Installation environment preparation did not complete. See Technical details for the exact reason. No automatic source switching or retry occurred.",
573
+ "copy262": "Listen only on 127.0.0.1. The request Host must be 127.0.0.1 or localhost with the listening port.",
574
+ "copy263": "Prevents remote machines or DNS-rebinding pages from impersonating this machine.",
575
+ "copy264": "Only allowed websites can discover this client, hand off installation or read handoff status.",
576
+ "copy265": "Prevents unrelated websites from identifying you through installed clients or missing capabilities.",
577
+ "copy266": "Website handoffs only open market confirmation. Installation requires your Confirm installation action.",
578
+ "copy267": "A website cannot decide what to install for you.",
579
+ "copy268": "Handoff status is visible only to the website that created it.",
580
+ "copy269": "One website cannot read another website’s handoffs.",
581
+ "copy270": "Handoff request body must not exceed 4096 bytes.",
582
+ "copy271": "A valid handoff contains only item ID, package name and contract version, under 500 bytes; a larger request is not a valid handoff.",
583
+ "copy272": "Provide at least one distinct port between 1024 and 65535.",
584
+ "copy273": "Lower ports need administrator permissions. Without a port the website cannot find this client.",
585
+ "copy274": "Websites must be origins (scheme + domain [+ port]). HTTP is allowed only for 127.0.0.1 / localhost.",
586
+ "copy275": "Browsers verify origins. Public HTTP websites can be impersonated by intermediaries.",
587
+ "copy276": "Polling interval must be an integer from 1 to 2147483647 milliseconds. Expiry must be longer than the polling interval.",
588
+ "copy277": "Oversized timers fire immediately. A handoff expiring before its first poll cannot be shown.",
589
+ "copy278": "Progress updates stopped; preparation may still be running. Select Refresh status to check. No automatic retry will run.",
590
+ "copy279": "DSH model settings",
591
+ "copy280": "Coding OAuth local gateway",
592
+ "copy281": "Configured",
593
+ "copy282": "Not configured",
594
+ "copy283": "Cannot connect right now",
595
+ "copy284": "Gateway is off",
596
+ "copy285": "Status unavailable",
597
+ "copy286": "Source unavailable",
598
+ "copy287": "Original record",
599
+ "copy288": "Actions",
600
+ "copy289": "At app startup, providers are matched to current configuration and the app declaration and injected. This page only selects models or revokes/restores authorization for that connection; source switches are unchanged.",
601
+ "copy290": "When you select Install on a website, this client asks you to confirm before installing.",
602
+ "copy291": "Before installing, prepare the required environment in Installation environment.",
603
+ "copy292": "Enable website installation handoff",
604
+ "copy293": "Local ports",
605
+ "copy294": "Allowed websites (one per line)",
606
+ "copy295": "Confirmation wait (milliseconds)",
607
+ "copy296": "Check for new handoffs (milliseconds)",
608
+ "copy297": "Setting names and defaults",
609
+ "copy298": "How it works",
610
+ "copy300": "Advanced settings",
611
+ "copy301": "Supports macOS 13.5+ (Apple Silicon / Intel). Windows installation, guardian and cancellation verification are not connected; this flow does not support Windows."
612
+ }
613
+ };
614
+ const COPY_KEYS=new Map(Object.entries(COPY.zh).map(([key,text])=>[text,key]));
615
+ let localeService=null;
616
+ function t(text){if(typeof text==='function')return text();const key=COPY_KEYS.get(text);if(!key)return text;return localeService?localeService.bind('hanamesh.apphost')(key):COPY.zh[key];}
617
+ function useLocale(){const[,update]=React.useState(0);React.useEffect(()=>localeService?.subscribe(()=>update(n=>n+1)),[]);}
618
+ function FailureNotice({error}){const original=error?.response?.error??error,code=typeof original==='string'?original:original?.code??original?.message??String(original);const details=typeof original==='string'?original:JSON.stringify(original instanceof Error?{message:original.message}:original,null,2);return h('div',{role:'alert',className:'hm-failure'},h('p',null,t(error?.context==='node-progress'?'进度读取已中断,准备可能仍在进行。请点击「刷新状态」查看;不会自动重试。':code==='INVALID_CATALOG_MANIFEST'?'市场目录暂时无法读取。请检查目录源设置,或等待目录提供方更新后再读取。安装尚未开始。':'本次操作未能完成。请查看技术详情中的准确原因,检查相关设置后再操作。')),h('details',null,h('summary',null,t('技术详情')),h('pre',null,details)));}
619
+
5
620
  const request=async(path,init={})=>{
6
621
  const response=await globalThis.fetch.bind(globalThis)(path,{...init,headers:{'x-hanamesh-client':'workspace-v1',...(init.body?{'content-type':'application/json'}:{}),...init.headers}});
7
- const value=await response.json();if(!response.ok)throw new Error(value?.error?.code??`HTTP_${response.status}`);return value;
622
+ const value=await response.json();if(!response.ok){const error=new Error(value?.error?.code??`HTTP_${response.status}`);error.response=value;throw error;}return value;
8
623
  };
9
624
  const entryId=entry=>entry.projection==='file'?`file:${entry.base??'home'}:${entry.path}`:`env:${entry.env}`;
10
- function ProvidersSection(){
625
+ function ProvidersSection(){useLocale();
11
626
  const[providers,setProviders]=React.useState([]),[apps,setApps]=React.useState([]),[plans,setPlans]=React.useState({}),[error,setError]=React.useState('');
12
627
  const load=React.useCallback(async()=>{try{setError('');const[p,a]=await Promise.all([request('/hanamesh/router/providers'),request('/hanamesh/apps')]);
13
- setProviders(p.providers??[]);setApps(a.apps??[]);const rows=await Promise.all((a.apps??[]).map(async app=>[app.id,await request('/hanamesh/router/plan?appId='+encodeURIComponent(app.id))]));setPlans(Object.fromEntries(rows));}catch(e){setError(String(e.message??e));}},[]);
628
+ setProviders(p.providers??[]);setApps(a.apps??[]);const rows=await Promise.all((a.apps??[]).map(async app=>[app.id,await request('/hanamesh/router/plan?appId='+encodeURIComponent(app.id))]));setPlans(Object.fromEntries(rows));}catch(e){setError(e);}},[]);
14
629
  React.useEffect(()=>{void load();},[load]);
15
- const mutate=async(path,body)=>{try{setError('');await request(path,{method:'POST',body:JSON.stringify(body)});await load();}catch(e){setError(String(e.message??e));}};
16
- const sourceRows=providers.map(provider=>h('tr',{key:provider.id},h('td',null,provider.displayName),h('td',null,provider.source),h('td',null,provider.state),h('td',null,provider.keyHint??'—'),h('td',null,(provider.models??[]).join('、')||'—')));
17
- const stateText={auto:'自动',granted:'手动指定',missing:'无可用供应商',revoked:'已停用','app-owned':'应用自管'};
630
+ const mutate=async(path,body)=>{try{setError('');await request(path,{method:'POST',body:JSON.stringify(body)});await load();}catch(e){setError(e);}};
631
+ const knownLabel=(labels,key,unknown)=>typeof key==='string'&&Object.hasOwn(labels,key)?labels[key]:unknown;
632
+ const providerStates={configured:t('已配置'),absent:t('尚未配置'),unreachable:t('暂时无法连接'),'gateway-off':t('网关已关闭')};
633
+ const providerSources={'dsh-models':t('DSH 模型设置'),'coding-oauth-gateway':t('Coding OAuth 本地网关')};
634
+ const sourceRows=providers.map(provider=>h('tr',{key:provider.id},h('td',{'data-label':t('名称')},provider.displayName),h('td',{'data-label':t('来源')},knownLabel(providerSources,provider.source,t('来源暂不可用')),h('details',null,h('summary',null,t('原始记录')),h('pre',null,JSON.stringify({id:provider.id,source:provider.source,state:provider.state},null,2)))),h('td',{'data-label':t('状态')},knownLabel(providerStates,provider.state,t('状态暂不可用'))),h('td',{'data-label':t('密钥提示')},provider.keyHint??'—'),h('td',{'data-label':t('模型')},(provider.models??[]).join('、')||'—')));
635
+ const stateText={auto:t('自动'),granted:t('手动指定'),missing:t('无可用供应商'),revoked:t('已停用'),'app-owned':t('应用自管')};
18
636
  const subjectText=item=>item.granted?(item.granted.kind==='provider'?item.granted.providerId:item.granted.ref??item.granted.key):'—';
19
637
  const acceptable=entry=>providers.find(p=>p.state==='configured'&&(!entry.providers?.length||entry.providers.includes(p.id)||(p.id==='coding-oauth-gateway'&&entry.providers.includes('openai'))));
20
638
  // One compact table for every app: app × slot → routed provider. Scales to many apps; no per-app card, no manual picker.
21
639
  const routeRows=apps.flatMap(app=>{const plan=plans[app.id];if(!plan)return[];const slots=(plan.items??[]).filter(item=>!item.derived);return slots.map((item,index)=>{const id=entryId(item.entry);
22
640
  const routed=item.state==='auto'||item.state==='granted',current=item.model??'',options=[...new Set([...(item.models??[]),...(current?[current]:[])])];
23
- const modelCell=!routed?'—':options.length?h('select',{value:current,onChange:e=>void mutate('/hanamesh/router/model',{appId:app.id,entryId:id,model:e.target.value})},h('option',{value:''},item.defaultModel?`应用默认(${item.defaultModel})`:'应用默认'),...options.map(m=>h('option',{key:m,value:m},m))):
24
- h('input',{type:'text',defaultValue:current,placeholder:item.defaultModel?`应用默认(${item.defaultModel})`:'应用默认',onBlur:e=>{if(e.target.value.trim()!==current)void mutate('/hanamesh/router/model',{appId:app.id,entryId:id,model:e.target.value.trim()});}});
25
- return h('tr',{key:`${app.id}:${id}`},index===0?h('td',{rowSpan:slots.length},app.name):null,h('td',null,item.entry.env??item.entry.path),h('td',null,item.entry.purpose??'—'),h('td',null,subjectText(item)),h('td',null,modelCell),h('td',null,stateText[item.state]??item.state),
26
- h('td',null,item.state==='revoked'?h('button',{type:'button',onClick:()=>{const provider=acceptable(item.entry);if(provider)void mutate('/hanamesh/router/grant',{appId:app.id,entryId:id,subject:provider.ref?{kind:'api-key',ref:provider.ref}:{kind:'provider',providerId:provider.id}});}},'恢复'):
27
- (item.state==='auto'||item.state==='granted')?h('button',{type:'button',onClick:()=>void mutate('/hanamesh/router/revoke',{appId:app.id,entryId:id})},'停用'):null));});});
28
- return h('section',{className:'hm-providers'},h('h2',null,'供应商'),h('p',null,'这里只显示来源、状态、提示与模型,不读取或展示密钥值。'),error?h('p',{role:'alert'},error):null,
29
- h('table',null,h('thead',null,h('tr',null,...['名称','来源','状态','Key 提示','模型'].map(label=>h('th',{key:label},label)))),h('tbody',null,...sourceRows)),
30
- h('p',{className:'hm-provider-hint'},'来源的开关在各自的地方:API key 在 DSH「Models」,Coding OAuth 本地网关在该插件自己的设置页(DSH 对话不需要它,只有按 OpenAI API 说话的应用需要)。'),
31
- h('h3',null,'路由'),
32
- h('p',{className:'hm-provider-hint'},'自动:profile 里已配置、且应用声明接受的供应商在应用启动时直接注入;模型随路由一起在这里选(留「应用默认」则用应用清单里的默认值)。「停用」让某一槽位退出自动路由。'),
33
- routeRows.length?h('table',null,h('thead',null,h('tr',null,...['应用','槽位','用途','供应商','模型','状态',''].map((label,index)=>h('th',{key:index},label)))),h('tbody',null,...routeRows)):h('p',{className:'hm-provider-hint'},'还没有安装任何声明了供应商槽位的应用。'));
641
+ const modelCell=!routed?'—':options.length?h('select',{value:current,onChange:e=>void mutate('/hanamesh/router/model',{appId:app.id,entryId:id,model:e.target.value})},h('option',{value:''},item.defaultModel?(t("应用默认(") + (item.defaultModel) + ")"):t('应用默认')),...options.map(m=>h('option',{key:m,value:m},m))):
642
+ h('input',{type:'text',defaultValue:current,placeholder:item.defaultModel?(t("应用默认(") + (item.defaultModel) + ")"):t('应用默认'),onBlur:e=>{if(e.target.value.trim()!==current)void mutate('/hanamesh/router/model',{appId:app.id,entryId:id,model:e.target.value.trim()});}});
643
+ return h('tr',{key:`${app.id}:${id}`},h('td',{'data-label':t('应用')},app.name),h('td',{'data-label':t('用途')},item.entry.purpose??'—'),h('td',{'data-label':t('供应商')},subjectText(item)),h('td',{'data-label':t('模型')},modelCell),h('td',{'data-label':t('状态')},knownLabel(stateText,item.state,t('状态暂不可用')),h('details',null,h('summary',null,t('原始记录')),h('pre',null,JSON.stringify({connection:item.entry.env??item.entry.path,state:item.state},null,2)))),
644
+ h('td',{'data-label':t('操作')},item.state==='revoked'?h('button',{type:'button',onClick:()=>{const provider=acceptable(item.entry);if(provider)void mutate('/hanamesh/router/grant',{appId:app.id,entryId:id,subject:provider.ref?{kind:'api-key',ref:provider.ref}:{kind:'provider',providerId:provider.id}});}},t('恢复')):
645
+ (item.state==='auto'||item.state==='granted')?h('button',{type:'button',onClick:()=>void mutate('/hanamesh/router/revoke',{appId:app.id,entryId:id})},t('停用')):null));});});
646
+ return h('section',{className:'hm-providers'},h('h2',null,t('供应商')),h('p',null,t('查看可用的模型服务并选择应用使用的模型。密钥值不会显示。')),error?FailureNotice({error}):null,
647
+ h('table',{className:'hm-provider-records'},h('thead',null,h('tr',null,...[t('名称'),t('来源'),t('状态'),t('密钥提示'),t('模型')].map(label=>h('th',{key:label},label)))),h('tbody',null,...sourceRows)),
648
+ h('p',{className:'hm-provider-hint'},t('在 DSH「模型」中管理模型服务;Coding OAuth 的开关在该插件的设置页。')),
649
+ h('h3',null,t('应用使用的服务')),
650
+ h('p',{className:'hm-provider-hint'},t('应用会自动使用已配置且支持的供应商。可在下方选择模型;保留「应用默认」则使用应用的默认模型。「停用」会停止向该应用提供此服务。')),
651
+ h('details',null,h('summary',null,t('技术详情')),h('p',null,t('应用启动时按当前配置与应用声明匹配供应商并注入;这里只选择模型或撤回、恢复该连接的授权,不改变来源开关。'))),
652
+ routeRows.length?h('table',{className:'hm-provider-records'},h('thead',null,h('tr',null,...[t('应用'),t('用途'),t('供应商'),t('模型'),t('状态'),''].map((label,index)=>h('th',{key:index},label)))),h('tbody',null,...routeRows)):h('p',{className:'hm-provider-hint'},t('尚无需要模型服务的已安装应用。')));
34
653
  }
35
654
  let libraryVisible=false;const libraryListeners=new Set();
36
655
  const setLibraryVisible=value=>{libraryVisible=value;for(const listener of libraryListeners)listener(value);};
656
+ let targetSelection=null,targetSequence=0,pendingOperations={};
657
+ const targetListeners=new Set(),pendingListeners=new Set();
658
+ const INSTALL_TARGET_HASH='#hanamesh-install?',INSTALL_TARGET_CONTRACT_VERSION='1';
659
+ const CORE_SUITE_FAILURES={CORE_SUITE_DECLARATION_MISSING:t('Core 未提供套件声明'),CORE_SUITE_DECLARATION_INVALID:t('Core 套件声明无法用于精确安装。')};
660
+ const suiteTargetFailure=target=>target.previewFailed?t('兼容升级检查未通过,尚未执行安装:')+(t(CORE_SUITE_FAILURES[target.error])??target.error??t('检测不到结果')):t(CORE_SUITE_FAILURES[target.error])??(target.details?.stage==='suite-upgrade'?t('套件升级未完成:')+(target.error??t('检测不到结果'))+t('。请核对当前版本;完全退出后可使用 HanaMesh 设置中的补齐命令修复并重新打开。'):t('套件升级未完成或结果尚未确认:')+(target.error??t('检测不到结果'))+t('。请核对已装版本;不会自动重试。'));
661
+ const setTarget=value=>{targetSelection=value;for(const listener of targetListeners)listener(value);};
662
+ const setPending=update=>{pendingOperations=typeof update==='function'?update(pendingOperations):update;for(const listener of pendingListeners)listener(pendingOperations);};
663
+ /** Public client entry: identifiers only; the market fetches its own catalog and owns confirmation. */
664
+ async function openInstallTarget(input,{handoffId}={}){
665
+ const sequence=++targetSequence;if(entryVisible)setLibraryVisible(true);
666
+ try{
667
+ if(!input||typeof input!=='object'||Array.isArray(input))throw new Error('INVALID_INPUT');
668
+ // rc.8: optional contract declaration; absent keeps v1 semantics, an unsupported version is refused before any request.
669
+ if(input.contractVersion!==undefined&&input.contractVersion!==INSTALL_TARGET_CONTRACT_VERSION)throw new Error('CONTRACT_VERSION_UNSUPPORTED');
670
+ if(Object.keys(input).some(key=>!['itemId','packageName','contractVersion'].includes(key))||(!input.itemId&&!input.packageName)||
671
+ (input.itemId!==undefined&&(typeof input.itemId!=='string'||!input.itemId.length||input.itemId.length>160||/[\u0000-\u001f\u007f]/.test(input.itemId)))||
672
+ (input.packageName!==undefined&&(typeof input.packageName!=='string'||input.packageName.length>214||!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(input.packageName))))throw new Error('INVALID_INPUT');
673
+ setTarget({status:'resolving',input:{...input}});
674
+ const query=new URLSearchParams(input),resolved=await request('/hanamesh/library/target?'+query);
675
+ if(sequence!==targetSequence)return{status:'superseded'};
676
+ const item=resolved.item,op=pendingOperations[item.id];
677
+ const status=op?.busy?'installing':['registered','installed','installed-not-loaded'].includes(item.installed?.state)&&!item.upgradeAvailable?'already-installed':'confirmation-required';
678
+ setTarget({...resolved,status,...(handoffId?{handoffId}:{})});return{status,itemId:item.id,packageName:item.package.name};
679
+ }catch(error){if(sequence===targetSequence)setTarget({status:input.itemId==='hanamesh:suite-upgrade'?'suite-upgrade-failed':'failed',error:String(error.message??error),diagnostic:{response:error.response,message:String(error.message??error)},...(input.itemId==='hanamesh:suite-upgrade'?{previewFailed:true,details:error.response?.error?.details}:{})});throw error;}
680
+ }
681
+ // Website handoff (C-MARKET 2026-10-15): the library's own loopback listener queued a target from an allowed website;
682
+ // this window shows it with the normal confirmation. Only the person's 「确认安装」 installs.
683
+ // Failures on this path are shown (market + settings, `handoffIssue`) and logged; the website gets the real reason from
684
+ // the handoff state (HANDOFF_OPEN_FAILED / HANDOFF_NOT_PICKED_UP / HANDOFF_EXPIRED), never a made-up 「已取消」.
685
+ // The poll interval is the setting library.handoff.pollMs, delivered with every poll.
686
+ // Until the first poll delivers the saved value, the setting's default (HANDOFF_DEFAULTS.pollMs; a test keeps them equal).
687
+ const HANDOFF_DEFAULT_POLL_MS=1500;
688
+ const handled=new Set(),issueListeners=new Set();let handoffIssue=null,handoffPollMs=HANDOFF_DEFAULT_POLL_MS;
689
+ const errorCode=error=>{const text=String(error?.message??error);return /^[A-Z][A-Z0-9_]{0,63}$/.test(text)?text:'CLIENT_ERROR';};
690
+ const setHandoffIssue=value=>{handoffIssue=value;for(const listener of issueListeners)listener(value);};
691
+ const noteHandoffIssue=(text,error)=>{text=COPY.zh[Object.keys(COPY.en).find(k=>COPY.en[k]===text)]??text;const line=()=>t('网站交接:')+t(text)+'('+String(error?.message??error)+')';try{globalThis.console?.warn?.('hanamesh-app-host '+t(line));}catch{}setHandoffIssue({text:line,cause:error.response?.error??{message:String(error.message??error)},at:Date.now()});};
692
+ const reportHandoff=async(handoffId,state,error)=>{if(!handoffId)return;
693
+ try{await request('/hanamesh/library/handoffs/state',{method:'POST',body:JSON.stringify({handoffId,state,...(error?{error}:{})})});}
694
+ catch(failure){noteHandoffIssue({shown:t('没能告诉网站「确认框已弹出」'),cancelled:t('没能告诉网站「已取消」,网站会在作废时间到后显示过期'),failed:t('没能告诉网站「确认框打不开」')}[state],failure);}};
695
+ async function pollHandoffs(){
696
+ const value=await request('/hanamesh/library/handoffs');handoffPollMs=value.pollMs;
697
+ if(handoffIssue?.poll)setHandoffIssue(null);
698
+ for(const row of value.handoffs??[]){
699
+ if(row.state!=='pending'||handled.has(row.handoffId))continue;handled.add(row.handoffId);
700
+ const input={...(row.itemId?{itemId:row.itemId}:{}),...(row.packageName&&row.itemId!=='hanamesh:suite-upgrade'?{packageName:row.packageName}:{}),contractVersion:INSTALL_TARGET_CONTRACT_VERSION};
701
+ let opened=false;
702
+ try{await openInstallTarget(input,{handoffId:row.handoffId});opened=true;}
703
+ catch(error){noteHandoffIssue(t('确认框打不开'),error);await reportHandoff(row.handoffId,'failed',{code:errorCode(error)});}
704
+ if(opened)await reportHandoff(row.handoffId,'shown');
705
+ return;
706
+ }
707
+ }
708
+ const pollHandoffsVisibly=async()=>{try{await pollHandoffs();}catch(error){const line=()=>t('网站交接:读取交接失败,网站上的「安装」会一直等到过期(')+String(error?.message??error)+')';try{globalThis.console?.warn?.('hanamesh-app-host '+t(line));}catch{}setHandoffIssue({text:line,cause:error.response?.error??{message:String(error.message??error)},poll:true,at:Date.now()});}};
709
+ function consumeInstallTargetHash(){
710
+ const hash=globalThis.location?.hash;if(!hash?.startsWith(INSTALL_TARGET_HASH))return;
711
+ const params=new URLSearchParams(hash.slice(INSTALL_TARGET_HASH.length));
712
+ if(new Set(params.keys()).size!==[...params.keys()].length){targetSequence++;if(entryVisible)setLibraryVisible(true);setTarget({status:'failed',error:'INVALID_INPUT'});return;}
713
+ void openInstallTarget(Object.fromEntries(params)).catch(()=>{});
714
+ }
37
715
  // rc.32 market seat. The shell's Extension Management panel (dsh-tauri-panel-extension) reads the client-side
38
716
  // cordis service `market` via `ctx.reflect.get('market')` and shows a 「市场」 tab when it is there; it calls
39
717
  // `render({preferredSubsectionId})` for the tab body and `setSettingsVisible(false)` while it owns the surface
40
718
  // (restored to `true` on dispose). Only one plugin can hold the seat — dshmarket provides the same one.
41
719
  const MARKET_SEAT='market',DSHMARKET_PACKAGE='dshmarket';
42
- const MARKET_CONFLICT_TEXT='检测到 DSH Market(dshmarket):「扩展管理」的市场标签只能由一个插件提供,这次没有由 HanaMesh 市场接管。两者只能其一——要用 HanaMesh 市场请先卸载 dshmarket 并重启 DSH;要用 dshmarket 就从下方侧栏的「市场」入口使用 HanaMesh 市场。';
720
+ const MARKET_CONFLICT_TEXT=t('检测到 DSH Market(dshmarket):「扩展管理」的市场标签只能由一个插件提供,这次没有由 HanaMesh 市场接管。两者只能其一——要用 HanaMesh 市场请先卸载 dshmarket 并重启 DSH;要用 dshmarket 就从下方侧栏的「市场」入口使用 HanaMesh 市场。');
43
721
  let marketConflict='';const conflictListeners=new Set();
44
722
  const setMarketConflict=value=>{marketConflict=value;for(const listener of conflictListeners)listener(value);};
45
723
  // The seat owner asks us to stand down our own entry points while it renders the market itself.
46
724
  let entryVisible=true;const entryListeners=new Set();
47
725
  const setEntryVisible=value=>{entryVisible=value;if(!value)setLibraryVisible(false);for(const listener of entryListeners)listener(value);};
48
- function MarketConflictNotice(){
726
+ function HandoffIssueNotice(){useLocale();
727
+ const[issue,setIssue]=React.useState(handoffIssue);
728
+ React.useEffect(()=>{issueListeners.add(setIssue);return()=>issueListeners.delete(setIssue);},[]);
729
+ return issue?h('div',{role:'alert',className:'hm-library-failed','data-hanamesh-handoff-issue':''},h('p',null,t(issue.text)),h('details',null,h('summary',null,t('技术详情')),h('pre',null,JSON.stringify(issue.cause,null,2)))):null;
730
+ }
731
+ function MarketConflictNotice(){useLocale();
49
732
  const[text,setText]=React.useState(marketConflict);
50
733
  React.useEffect(()=>{conflictListeners.add(setText);return()=>conflictListeners.delete(setText);},[]);
51
734
  if(!text)return null;
52
- return h('p',{role:'status',className:'hm-market-conflict','data-hanamesh-market-conflict':DSHMARKET_PACKAGE},text);
735
+ return h('p',{role:'status',className:'hm-market-conflict','data-hanamesh-market-conflict':DSHMARKET_PACKAGE},t(text));
53
736
  }
54
- function LibraryAction(){
737
+ function LibraryAction(){useLocale();
55
738
  const[shown,setShown]=React.useState(entryVisible);
56
739
  React.useEffect(()=>{entryListeners.add(setShown);return()=>entryListeners.delete(setShown);},[]);
57
740
  if(!shown)return null;
58
- return h('button',{type:'button',className:'hm-library-action',onClick:()=>setLibraryVisible(true)},'市场');
741
+ return h('button',{type:'button',className:'hm-library-action',onClick:()=>setLibraryVisible(true)},t('市场'));
59
742
  }
60
- function LibrarySourcesSection(){
743
+ function LibrarySourcesSection(){useLocale();
61
744
  const[sources,setSources]=React.useState([]),[draft,setDraft]=React.useState(''),[error,setError]=React.useState('');
62
- const load=React.useCallback(async()=>{try{const value=await request('/hanamesh/library/sources');setSources(value.sources??[]);setError('');}catch(e){setError(String(e.message??e));}},[]);
745
+ const load=React.useCallback(async()=>{try{const value=await request('/hanamesh/library/sources');setSources(value.sources??[]);setError('');}catch(e){setError(e);}},[]);
63
746
  React.useEffect(()=>{void load();},[load]);
64
- const save=async next=>{try{await request('/hanamesh/library/sources',{method:'POST',body:JSON.stringify({sources:next})});await load();}catch(e){setError(String(e.message??e));}};
747
+ const save=async next=>{try{await request('/hanamesh/library/sources',{method:'POST',body:JSON.stringify({sources:next})});await load();}catch(e){setError(e);}};
65
748
  const move=(index,delta)=>{const next=[...sources],target=index+delta;if(target<0||target>=next.length)return;[next[index],next[target]]=[next[target],next[index]];void save(next);};
66
- return h('section',{className:'hm-library-sources'},h('h2',null,'市场目录源'),h('p',null,'可保存多个来源,但浏览时只启用一个。'),error?h('p',{role:'alert'},error):null,
67
- ...sources.map((source,index)=>h('div',{className:'hm-source-row',key:source.manifestUrl},h('code',null,source.manifestUrl),h('button',{type:'button',disabled:source.enabled,onClick:()=>void save(sources.map((row,i)=>({...row,enabled:i===index})))},source.enabled?'已启用':'启用'),h('button',{type:'button',onClick:()=>move(index,-1)},'上移'),h('button',{type:'button',onClick:()=>move(index,1)},'下移'),h('button',{type:'button',onClick:()=>void save(sources.filter((_,i)=>i!==index))},'删除'))),
68
- h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://…/catalog-source.json',onChange:event=>setDraft(event.target.value)}),h('button',{type:'button',onClick:()=>{if(draft)void save([...sources.map(row=>({...row,enabled:false})),{manifestUrl:draft,enabled:true}]);setDraft('');}},'添加并启用')));
749
+ return h('section',{className:'hm-library-sources'},h('h2',null,t('市场目录源')),
750
+ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://…/catalog-source.json',onChange:event=>setDraft(event.target.value)}),h('button',{type:'button',onClick:()=>{if(draft)void save([...sources.map(row=>({...row,enabled:false})),{manifestUrl:draft,enabled:true}]);setDraft('');}},t('添加并启用'))),h('p',null,t('可保存多个来源,但浏览时只启用一个。')),error?FailureNotice({error}):null,
751
+ ...sources.map((source,index)=>h('div',{className:'hm-source-row',key:source.manifestUrl},h('code',null,source.manifestUrl),h('button',{type:'button',disabled:source.enabled,onClick:()=>void save(sources.map((row,i)=>({...row,enabled:i===index})))},source.enabled?t('已启用'):t('启用')),h('button',{type:'button',onClick:()=>move(index,-1)},t('上移')),h('button',{type:'button',onClick:()=>move(index,1)},t('下移')),h('button',{type:'button',onClick:()=>void save(sources.filter((_,i)=>i!==index))},t('删除')))));
752
+ }
753
+ function SuiteSourceSettingsSection(){useLocale();
754
+ const[value,setValue]=React.useState(null),[mode,setMode]=React.useState('configured'),[error,setError]=React.useState(''),[busy,setBusy]=React.useState(false);
755
+ const errorText=e=>e;
756
+ const load=React.useCallback(async()=>{try{const next=await request('/hanamesh/library/suite-source');setValue(next);setMode(next.mode);setError('');}catch(e){setError(errorText(e));}},[]);
757
+ React.useEffect(()=>{void load();},[load]);
758
+ const save=async()=>{setBusy(true);try{const next=await request('/hanamesh/library/suite-source',{method:'POST',body:JSON.stringify({mode})});setValue(next);setMode(next.mode);setError('');}catch(e){setError(errorText(e));}finally{setBusy(false);}};
759
+ const observation=value?.observation,state=observation?.state;
760
+ return h('section',{className:'hm-node-runtime','data-hanamesh-suite-source':''},h('h2',null,t('套件安装包来源')),
761
+ error?FailureNotice({error}):null,
762
+ value?h('div',{className:'hm-suite-fields'},h('label',null,t('安装包来源'),h('select',{value:mode,disabled:busy,onChange:e=>setMode(e.target.value)},h('option',{value:'configured'},t('现有配置(默认)')),h('option',{value:'core'},t('Core 当前本机来源')))),
763
+ h('button',{type:'button',disabled:busy||mode===value.mode,onClick:()=>void save()},busy?t('正在保存…'):t('保存套件安装包来源')),h('button',{type:'button',disabled:busy,onClick:()=>void load()},t('刷新来源状态')),
764
+ h('p',null,t('现有 registry:'),h('code',null,value.configuredRegistry)),
765
+ h('p',null,t('已保存选择:')+(value.mode==='core'?t('Core 当前本机来源'):t('现有配置'))),
766
+ observation?.observable?h('div',null,h('p',{role:'status'},t('Core 来源:')+state.status+' · '+state.selection),state.registry?h('p',null,h('code',null,state.registry)):null,state.lastError?FailureNotice({error:state.lastError}):null):h('div',null,h('p',{role:'status'},t('Core 来源暂不可用。请检查 Core 插件及其来源设置。')),h('details',null,h('summary',null,t('技术详情')),h('code',null,observation?.code??'CORE_SUITE_SOURCE_MISSING'))),
767
+ h('details',null,h('summary',null,t('使用说明')),h('p',null,t('选择三个官方套件组件的 npm 安装包 registry。市场目录源提供浏览清单,在「市场目录源」中单独设置。')),h('p',null,t('保存选择不会安装。检查、确认和执行前都重新读取当前来源与 Core 兼容声明;来源变化后须重新查看并确认。')))):h('p',null,t('正在读取套件安装包来源…')));
768
+ }
769
+ function NodeRuntimeSection(){useLocale();
770
+ const[value,setValue]=React.useState(null),[draft,setDraft]=React.useState(null),[plan,setPlan]=React.useState(null),[error,setError]=React.useState(''),[working,setWorking]=React.useState(false);
771
+ const errorText=e=>e;
772
+ const load=React.useCallback(async()=>{try{const next=await request('/hanamesh/library/node-runtime');setValue(next);setDraft(current=>current??next.settings);setError('');return next;}catch(e){setError(errorText(e));return null;}},[]);
773
+ React.useEffect(()=>{void load();},[load]);
774
+ React.useEffect(()=>{if(!['preparing','committing'].includes(value?.operation?.state))return;let stopped=false,timer;
775
+ const tick=async()=>{try{const next=await request('/hanamesh/library/node-runtime');if(stopped)return;setValue(next);if(['preparing','committing'].includes(next.operation?.state))timer=setTimeout(()=>void tick(),1000);}catch(e){if(!stopped)setError({context:'node-progress',response:e.response,message:e.message??String(e)});}};
776
+ timer=setTimeout(()=>void tick(),1000);return()=>{stopped=true;clearTimeout(timer);};},[value?.operation?.id,value?.operation?.state]);
777
+ const action=async(path,body,receive)=>{setWorking(true);setError('');try{const next=await request('/hanamesh/library/node-runtime/'+path,{method:'POST',body:JSON.stringify(body)});receive?.(next);}catch(e){setError(errorText(e));}finally{setWorking(false);}};
778
+ const busy=working||['preparing','committing'].includes(value?.operation?.state),dirty=draft&&value&&JSON.stringify(draft)!==JSON.stringify(value.settings);
779
+ const edit=(key,val)=>{setDraft({...draft,[key]:val});setPlan(null);};
780
+ const field=(key,label,type='input')=>h('label',{key},label,h(type,{'data-node-setting':key,value:draft[key],disabled:busy,onChange:e=>edit(key,e.target.value)},type==='select'?key==='mode'?[h('option',{key:'managed',value:'managed'},t('插件管理(推荐)')),h('option',{key:'external',value:'external'},t('已有独立 Node(高级)'))]:value.versions.map(v=>h('option',{key:v,value:v},'Node.js '+v)):null));
781
+ const mb=n=>(n/1000000).toFixed(1)+' MB';
782
+ const phase={starting:t('检查位置与空间'),download:t('下载'),verify:t('校验 SHA-256'),extract:t('解压'),check:t('验证独立 Node'),promote:t('保存运行时'),bind:t('保存并启用(已完成校验,不能再取消)'),ready:t('环境已就绪'),restore:t('检查已有环境')};
783
+ const op=value?.operation;
784
+ return h('section',{className:'hm-node-runtime','data-hanamesh-node-runtime':''},h('h2',null,t('安装环境')),
785
+ draft&&value?h('div',{className:'hm-node-fields'},h('button',{type:'button',disabled:busy||dirty||!value.supported||draft.mode!=='managed',onClick:()=>void action('plan',{},setPlan)},t('准备安装环境')),h('button',{type:'button',disabled:working,onClick:()=>void load()},t('刷新状态'))):null,
786
+ h('p',null,t('准备一次,后续安装自动使用。无需重启 DSH,也无需填写 Node 路径;不会改动客户端或系统 Node。')),
787
+ error?FailureNotice({error}):null,
788
+ value?h('p',{role:'status'},value.ready?t('环境已就绪 · 后续插件操作自动使用独立 Node'):value.supported?t('尚未准备安装环境;确认前不会下载。'):t(value.limitations)):h('p',null,t('正在读取安装环境…')),
789
+ draft&&value?h('div',{className:'hm-node-fields'},field('mode',t(value.names.mode),'select'),field('version',t(value.names.version),'select'),
790
+ h('button',{type:'button',disabled:busy||!dirty,onClick:()=>void action('settings',{settings:draft},next=>{setValue(next);setDraft(next.settings);setPlan(null);})},t('保存环境设置')),
791
+ h('details',null,h('summary',null,t('高级设置')),field('sourceBase',t(value.names.sourceBase)),field('storageRoot',t(value.names.storageRoot)),field('externalBinary',t(value.names.externalBinary)),
792
+ h('small',null,t('已有路径仅在高级方式生效。下载来源必须使用 HTTPS;版本只能选择已校验发行。修改设置后先保存,再重新确认。')))):null,
793
+ plan?h('div',{className:'hm-node-confirm',role:'region','aria-label':t('确认准备安装环境')},h('h3',null,t('确认下载并准备')),
794
+ h('p',null,t('平台 ')+plan.platform+' · Node.js '+plan.version+t(' · 最低 ')+(plan.minimumOS??'macOS 13.5')),
795
+ h('dl',null,h('dt',null,t('下载来源')),h('dd',null,plan.source),h('dt',null,t('下载体积')),h('dd',null,mb(plan.bytes)),h('dt',null,t('解压后文件')),h('dd',null,mb(plan.unpackedBytes)),h('dt',null,t('准备所需空间')),h('dd',null,mb(plan.requiredBytes)),h('dt',null,t('存放位置')),h('dd',null,plan.root)),
796
+ h('p',null,t(plan.license)+t('。确认后只从上述来源下载;不自动重试或换源。')),
797
+ h('details',null,h('summary',null,t('完整校验值与检查说明')),h('code',null,'SHA-256 '+plan.sha256),h('p',null,t('校验归档 SHA、文件所有权、平台与精确版本;Electron 包装脚本不会通过。'))),
798
+ h('button',{type:'button',disabled:busy||dirty,onClick:()=>void action('prepare',{planId:plan.planId,confirm:true},next=>{setPlan(null);setValue({...value,...next});})},t('确认下载并准备')),
799
+ h('button',{type:'button',disabled:busy,onClick:()=>setPlan(null)},t('暂不准备'))):null,
800
+ op?h('div',{className:'hm-node-progress','aria-live':'polite'},h('p',null,phase[op.phase]??t('准备安装环境'),op.state==='preparing'?'…':''),
801
+ op.state==='preparing'&&op.phase==='download'?h('div',null,h('progress',{value:op.bytes??0,...(op.total?{max:op.total}:{})}),h('p',null,mb(op.bytes??0)+(op.total?' / '+mb(op.total):''))):null,
802
+ op.error?FailureNotice({error:op.error}):null,
803
+ op.state==='preparing'?h('button',{type:'button',disabled:working,onClick:()=>void action('cancel',{operationId:op.id},()=>void load())},t('取消准备')):null,
804
+ op.error?h('details',null,h('summary',null,t('诊断详情')),h('code',null,op.error.code)):null):null,
805
+ value?.executable?h('details',null,h('summary',null,t('当前自动绑定的路径')),h('code',null,value.executable)):null,
806
+ h('p',null,t('支持 macOS 13.5+(Apple Silicon / Intel);Windows 暂不支持。')),h('details',null,h('summary',null,t('技术详情')),h('p',null,t('支持 macOS 13.5+(Apple Silicon / Intel)。Windows 的安装、守护与取消验证尚未接通,本流程不声称支持 Windows。'))));
69
807
  }
70
- const FAILURE_REASONS={REGISTRY_LOOKUP_FAILED:'包不在当前 registry 上——尚未发布到 npm,或客户端 registry 设置不对',DSH_PLUGIN_FAILED:'DSH 安装命令失败,查看宿主日志',LIBRARY_INSTALL_UNAVAILABLE:'当前宿主未提供安装参数(profile / node 路径)',CATALOG_ITEM_MISSING:'目录里已没有这一项,刷新后重试',UNSTABLE_VERSION:'registry 上的最新版是预发布版(rc),客户端未允许安装预发布版',RUNTIME_ARCHIVE_UNAVAILABLE:'运行时归档下载失败,检查网络或来源',OPERATION_TIMEOUT:'10 分钟内没有结果,可稍后重试',PACKAGE_DENIED:'HanaMesh 套件自身不能从市场安装或卸载',INSTANCE_IN_USE:'先关闭该应用的所有视图再卸载'};
71
- const failureReason=code=>FAILURE_REASONS[code]??'详细原因见宿主日志';
808
+
809
+ /** Website handoff settings: every setting named with its default and editable; the fixed rules listed with their reason. */
810
+ function HandoffSettingsSection(){useLocale();
811
+ const[value,setValue]=React.useState(null),[draft,setDraft]=React.useState(null),[error,setError]=React.useState('');
812
+ const load=React.useCallback(async()=>{try{const next=await request('/hanamesh/library/handoff-settings');setValue(next);setDraft({...next.settings,ports:next.settings.ports.join(', '),allowedOrigins:next.settings.allowedOrigins.join('\n')});setError('');}catch(e){setError(e);}},[]);
813
+ React.useEffect(()=>{void load();},[load]);
814
+ const save=async()=>{try{const next=await request('/hanamesh/library/handoff-settings',{method:'POST',body:JSON.stringify({enabled:draft.enabled,ttlMs:Number(draft.ttlMs),pollMs:Number(draft.pollMs),
815
+ ports:draft.ports.split(/[\s,,]+/).filter(Boolean).map(Number),allowedOrigins:draft.allowedOrigins.split(/\s+/).filter(Boolean)})});setValue(next);setError('');}catch(e){setError(String(e.message??e)+(e.response?.error?.message?':'+e.response.error.message:''));}};
816
+ const listener=value?.listener,defaults=value?.defaults,names=value?.names;
817
+ const status=!listener?t('读取中…'):listener.status==='listening'?t('正在 ')+listener.origin+t(' 接收网站交接'):listener.status==='disabled'?t('已关闭:网站上的「安装」按钮找不到本客户端'):listener.status==='unavailable'?t('未启动'):t('未启动');
818
+ return h('section',{className:'hm-library-sources'},h('h2',null,t('网站安装交接')),
819
+ draft&&defaults?h('div',{className:'hm-handoff-settings'},
820
+ h('label',null,h('input',{type:'checkbox',checked:draft.enabled,onChange:event=>setDraft({...draft,enabled:event.target.checked})}),t('启用网站安装交接')),
821
+ h('button',{type:'button',onClick:()=>void save()},t('保存并重新监听')),
822
+ h('label',null,t('本机端口'),h('input',{value:draft.ports,onChange:event=>setDraft({...draft,ports:event.target.value})})),
823
+ h('label',null,t('允许的网站(每行一个)'),h('textarea',{rows:3,value:draft.allowedOrigins,onChange:event=>setDraft({...draft,allowedOrigins:event.target.value})})),
824
+ h('label',null,t('确认等待时间(毫秒)'),h('input',{value:String(draft.ttlMs),onChange:event=>setDraft({...draft,ttlMs:event.target.value})})),
825
+ h('label',null,t('检查新交接的间隔(毫秒)'),h('input',{value:String(draft.pollMs),onChange:event=>setDraft({...draft,pollMs:event.target.value})}))):null,
826
+ h('p',{role:'status'},status),listener?.status==='unavailable'?FailureNotice({error:listener.need}):null,error?FailureNotice({error}):null,h(HandoffIssueNotice,null),
827
+ h('p',null,t('网站点「安装」后,本客户端会请你确认;确认后才安装。')),
828
+ h('p',null,t('安装前请先在「安装环境」准备所需环境。')),
829
+ h('details',null,h('summary',null,t('使用说明')),h('p',null,t('安装前需要独立 Node.js 24。请打开 AppHost「安装环境」点击「准备安装环境」,核对来源、版本和体积后确认;完成后自动可用,无需重启或填写路径。已有独立 Node 可在安装环境高级设置中指定,不支持 Electron 包装脚本。'))),
830
+ h('details',{className:'hm-handoff-rules'},h('summary',null,t('技术详情')),
831
+ 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,
832
+ 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));
833
+ }
834
+ const FAILURE_REASONS={...CORE_SUITE_FAILURES,INSTALL_READBACK_MISMATCH:t('安装命令返回成功,但 profile 读回的版本与固定版本不一致'),REGISTRY_LOOKUP_FAILED:t('包不在当前 registry 上——尚未发布到 npm,或客户端 registry 设置不对'),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('先关闭该应用的所有视图再卸载')};
835
+ const failureReason=code=>t(FAILURE_REASONS[code])??t('详细原因见宿主日志');
836
+ const suiteRecoveryText=details=>details?.stage==='suite-upgrade'?t(';升级未完成,部分组件可能已改变。当前读回:')+(details.actual?.components?.map(c=>c.label+' '+(c.version??t('检测不到'))+(c.active?'':t('(未启用)'))).join(';')||t('检测不到'))+t('。请完全退出客户端,使用补齐命令(仅 macOS / Linux 终端;Windows 安全命令暂未提供)修复并重新打开:')+details.command:'';
837
+ /** pluginManager applied it, but the profile could not be read back: the installed list may still say 「需重启」. */
838
+ const baselineText=result=>result?.restartBaseline?.updated===false?t(';但应用库没能读回 profile(')+result.restartBaseline.code+t('),已安装列表可能仍显示「需重启」'):'';
839
+ /** rc.10 install recovery result: only an item-by-item readback that matched the baseline is shown as restored. */
840
+ const recoveryText=recovery=>{if(!recovery)return'';
841
+ if(recovery.status==='not-needed')return t(';安装前状态未被改动(已逐项读回)');
842
+ if(recovery.status==='restored')return recovery.mode==='upgrade'?t(';已恢复到原版本 ')+recovery.previousVersion+t('(已逐项读回)'):t(';已恢复到安装前状态(已逐项读回)');
843
+ const residue=(recovery.residue??[]).map(row=>row.item+(row.reason?'·'+row.reason:'')).join('、');
844
+ return t(';恢复失败')+(recovery.error?':'+recovery.error.code:'')+t(',未恢复:')+(residue||t('未知'))+(recovery.originals?.dir?t(',安装前原件:')+recovery.originals.dir:'')+t('。未安装成功,也未回到安装前状态,需要人工处理');};
72
845
  // rc.28 market: fixed filters first (everything / applications / plugins), then every category the catalog itself reported.
73
- const BASE_FILTERS=[['','全部'],['hanamesh-app','应用'],['plugin','插件']];
74
- const KIND_TEXT={application:'应用',plugin:'插件',listing:'仅收录'};
75
- const STATE_TEXT={registered:'已安装',installed:'已安装','installed-not-loaded':'需重启','uninstalled-not-unloaded':'已卸载 · 需重启','runtime-missing':'缺运行时',invalid:'包无效'};
76
- // The desktop shell hosts DSH inside its own webview frame and owns the `hanamesh://` scheme; official DSH in a plain browser has no frame.
77
- const inShell=()=>{try{return globalThis.window.self!==globalThis.window.top;}catch{return true;}};
78
- const RESTART_LINK='hanamesh://restart';
79
- // rc.32: WKWebView never hands a custom-scheme navigation started inside the DSH iframe to the OS, so the
80
- // deep link alone cannot reach the shell from here. The shell already listens for `hanamesh://…` messages on
81
- // its inbound iframe bridge (the same namespace as `hanamesh://bound-refresh`), so in-shell we post
82
- // `hanamesh://restart` to the parent and the shell calls its own restart. The `hanamesh://restart` URL stays
83
- // the OS-level entry (an external browser or `open hanamesh://restart`), handled in the shell's deep_link.rs.
84
- const requestRestart=()=>{try{globalThis.window.parent.postMessage({type:RESTART_LINK},'*');}catch{}};
846
+ const BASE_FILTERS=[['',t('全部')],['hanamesh-app',t('应用')],['plugin',t('插件')]];
847
+ const KIND_TEXT={application:t('应用'),plugin:t('插件'),listing:t('仅收录')};
848
+ const STATE_TEXT={registered:t('已安装'),installed:t('已安装'),'installed-not-loaded':t('需重启'),'uninstalled-not-unloaded':t('已卸载 · 需重启'),'runtime-missing':t('缺运行时'),invalid:t('包无效')};
85
849
  // Mirrors PROTECTED_PACKAGES on the host: the suite is never offered an uninstall button (the host answers 403 anyway).
86
- const PROTECTED=new Set(['@hanamesh/dsh-app-host','hanamesh-core','hanamesh-usage']);
87
- function RestartNotice({reason}){
88
- const shell=inShell();
89
- return h('p',{role:'status',className:'hm-restart-notice','data-hanamesh-restart':'required'},h('strong',null,'需重启 DSH'),' ',reason??'插件更改在 DSH 重启后生效。',' ',
90
- shell?h('a',{href:RESTART_LINK,className:'hm-restart-link',onClick:event=>{event.preventDefault();requestRestart();}},'重启 DSH'):h('span',{className:'hm-restart-hint'},'请手动重启 DSH。'));
850
+ const PROTECTED=new Set(['@hanamesh/dsh-app-host','hanamesh-core','hanamesh-usage','hanamesh-brand','@hanamesh/dsh-core','@hanamesh/dsh-usage']);
851
+ function RestartNotice({reason}){useLocale();
852
+ return h('p',{role:'status',className:'hm-restart-notice','data-hanamesh-restart':'required'},h('strong',null,t('需重新打开客户端')),' ',reason??t('插件更改需要重新打开客户端后生效。'),' ',h('span',{className:'hm-restart-hint'},t('请完全退出 HanaMesh 客户端或 DSH 后重新打开;关闭窗口不等于退出。')));
91
853
  }
854
+
92
855
  // rc.32: the same market page serves two surfaces — the shell overlay behind the sidebar 「市场」 button
93
856
  // (`embedded` false) and the Extension Management 「市场」 tab through the `market` seat (`embedded` true,
94
857
  // always visible, no close button, `preferredSubsectionId` decides which subsection comes first).
@@ -114,11 +877,64 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
114
877
  return closeTask??=request('/apps/close',{method:'POST',body:JSON.stringify(lease),keepalive:true}).catch(error=>{closeTask=undefined;throw error;});
115
878
  }};
116
879
  }
117
- function LibraryOverlay({embedded,preferredSubsectionId}={}){
118
- const[visible,setVisible]=React.useState(embedded?true:libraryVisible),[snapshot,setSnapshot]=React.useState(null),[error,setError]=React.useState(''),[receipt,setReceipt]=React.useState(null),[query,setQuery]=React.useState(''),[filter,setFilter]=React.useState(''),[extra,setExtra]=React.useState([]),[pending,setPending]=React.useState({}),[seenCategories,setSeenCategories]=React.useState([]),[changed,setChanged]=React.useState(false),[openingAppId,setOpeningAppId]=React.useState(null),[closing,setClosing]=React.useState(false);
880
+ const VERIFICATION_CALLS=[
881
+ {method:'GET',path:'/hanamesh/library/install-target',sample:{}},
882
+ {method:'GET',path:'/hanamesh/library',sample:{category:''}},
883
+ {method:'GET',path:'/hanamesh/library/target',sample:{itemId:'example.notes',packageName:'example-notes',contractVersion:'1'}},
884
+ {method:'POST',path:'/hanamesh/library/install',sample:{itemId:'example.notes',packageName:'example-notes',contractVersion:'1'}},
885
+ {method:'GET',path:'/hanamesh/library/events',sample:{after:0}},
886
+ {method:'GET',path:'/hanamesh/library/installedPlugins',sample:{}},
887
+ {method:'GET',path:'/hanamesh/apps',sample:{}},
888
+ {method:'POST',path:'/apps/open',sample:{appId:'notes',deploymentId:'local',viewId:'verification-view'}},
889
+ {method:'POST',path:'/apps/resume',sample:{viewId:'verification-view',leaseToken:''}},
890
+ {method:'POST',path:'/apps/heartbeat',sample:{viewId:'verification-view',leaseToken:''}},
891
+ {method:'POST',path:'/apps/close',sample:{viewId:'verification-view',leaseToken:''}},
892
+ ];
893
+ function LibraryOverlay({embedded,preferredSubsectionId,verification=false}={}){useLocale();
894
+ const[visible,setVisible]=React.useState(embedded?true:libraryVisible),[snapshot,setSnapshot]=React.useState(null),[error,setError]=React.useState(''),[receipt,setReceipt]=React.useState(null),[query,setQuery]=React.useState(''),[filter,setFilter]=React.useState(''),[extra,setExtra]=React.useState([]),[pending,updatePending]=React.useState(pendingOperations),[seenCategories,setSeenCategories]=React.useState([]),[changed,setChanged]=React.useState(false),[openingAppId,setOpeningAppId]=React.useState(null),[closing,setClosing]=React.useState(false);
895
+ const[target,updateTarget]=React.useState(targetSelection),[targetItemId,setTargetItemId]=React.useState(''),[targetPackageName,setTargetPackageName]=React.useState('');
896
+ const[verificationCall,setVerificationCall]=React.useState('GET /hanamesh/library/install-target'),[verificationInput,setVerificationInput]=React.useState('{}'),[verificationOutput,setVerificationOutput]=React.useState(null),[verifying,setVerifying]=React.useState(false);
897
+ const verificationBusy=React.useRef(false);
898
+ const verify=async()=>{
899
+ if(verification!==true||verificationBusy.current)return;
900
+ verificationBusy.current=true;setVerifying(true);
901
+ try{
902
+ const call=VERIFICATION_CALLS.find(call=>call.method+' '+call.path===verificationCall);
903
+ const input=JSON.parse(verificationInput);
904
+ if(!call||!input||typeof input!=='object'||Array.isArray(input))throw new Error('INVALID_INPUT');
905
+ // Even the test panel sends installations through the normal explicit confirmation.
906
+ const result=call.path==='/hanamesh/library/install'?await openInstallTarget(input):call.method==='POST'?await request(call.path,{method:'POST',body:JSON.stringify(input)}):await request(call.path+(Object.keys(input).length?'?'+new URLSearchParams(input):''));
907
+ setVerificationOutput(result);
908
+ }catch(error){setVerificationOutput(error.response??{error:{code:String(error.message??error)}});}
909
+ finally{verificationBusy.current=false;setVerifying(false);}
910
+ };
911
+ React.useEffect(()=>{targetListeners.add(updateTarget);pendingListeners.add(updatePending);return()=>{targetListeners.delete(updateTarget);pendingListeners.delete(updatePending);};},[]);
119
912
  React.useEffect(()=>{if(embedded)return;libraryListeners.add(setVisible);return()=>libraryListeners.delete(setVisible);},[embedded]);
120
913
  const heldView=React.useRef(null),mounted=React.useRef(true),surfaceVisible=React.useRef(visible),surfaceEpoch=React.useRef(0),opening=React.useRef(null);
121
914
  surfaceVisible.current=visible;
915
+ const frame=React.useRef(null);
916
+ React.useEffect(()=>{
917
+ if(!receipt?.uiUrl||!globalThis.location?.href?.startsWith('dsh-app://app/'))return;
918
+ const url=new URL(receipt.uiUrl),challenge=url.pathname.slice('/__hanamesh_bootstrap/'.length);
919
+ if(!url.pathname.startsWith('/__hanamesh_bootstrap/'))return;
920
+ let active=true,issued=false;
921
+ const receive=async event=>{
922
+ const exact=event.origin===url.origin&&event.source===frame.current?.contentWindow;
923
+ if(!exact||event.data?.challenge!==challenge)return;
924
+ console.info('HM_WORKSPACE_PROOF',JSON.stringify({origin:event.origin,window:true,type:event.data.type}));
925
+ if(event.data.type==='hm-bootstrap-ready')event.source.postMessage({type:'hm-bootstrap-parent',challenge},url.origin);
926
+ if(event.data.type==='hm-bootstrap-proof'&&!issued){
927
+ issued=true;
928
+ try {
929
+ const result=await request('/apps/bootstrap-authorize',{method:'POST',body:JSON.stringify({viewId:receipt.lease.viewId,
930
+ leaseToken:receipt.leaseToken,gatewayOrigin:url.origin,challenge})});
931
+ if(active&&event.source===frame.current?.contentWindow)event.source.postMessage({type:'hm-bootstrap-authorize',challenge,authorization:result.authorization},url.origin);
932
+ }catch(error){if(active){setError(String(error.message??'GATEWAY_AUTHORIZATION_DENIED'));console.info('HM_WORKSPACE_AUTHORIZATION_DENIED');}}
933
+ }
934
+ };
935
+ globalThis.addEventListener('message',receive);return()=>{active=false;globalThis.removeEventListener('message',receive);};
936
+ },[receipt]);
937
+
122
938
  React.useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[]);
123
939
  React.useEffect(()=>{
124
940
  if(!receipt)return;
@@ -140,16 +956,37 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
140
956
  // `plugin` is a client-side view (every npm entry that is not an application); every other filter is a provider category.
141
957
  const params=React.useCallback(cursor=>{const p=new URLSearchParams();if(query)p.set('q',query);p.set('category',filter==='plugin'?'':filter);if(cursor)p.set('cursor',cursor);return'?'+p.toString();},[query,filter]);
142
958
  const remember=page=>setSeenCategories(list=>[...new Set([...list,...(page.categories??[])])].sort());
143
- const load=React.useCallback(async()=>{try{setError('');setExtra([]);const page=await request('/hanamesh/library'+params());setSnapshot(page);remember(page);}catch(e){setError(String(e.message??e));}},[params]);
144
- const more=async()=>{try{const cursor=(extra.at(-1)??snapshot)?.page?.nextCursor;if(!cursor)return;const next=await request('/hanamesh/library'+params(cursor));setExtra(list=>[...list,next]);remember(next);}catch(e){setError(String(e.message??e));}};
959
+ const load=React.useCallback(async()=>{try{setError('');setExtra([]);const page=await request('/hanamesh/library'+params());setSnapshot(page);remember(page);}catch(e){setError(e);}},[params]);
960
+ const more=async()=>{try{const cursor=(extra.at(-1)??snapshot)?.page?.nextCursor;if(!cursor)return;const next=await request('/hanamesh/library'+params(cursor));setExtra(list=>[...list,next]);remember(next);}catch(e){if(String(e.message??e)==='CATALOG_CHANGED'){setSnapshot(null);setExtra([]);}setError(e);}};
145
961
  React.useEffect(()=>{if(visible)void load();},[visible,load]);
962
+ React.useEffect(()=>{if(visible&&['installed','installed-live'].includes(target?.status))void load();},[visible,load,target?.status]);
146
963
  // rc.18: install/provision/uninstall are 202 + events. Track the operation per card so the user sees
147
964
  // 「安装中…」 and, on failure, the code with a readable reason (user 2026-09-20: clicked install, nothing happened).
148
- const operate=async(path,body,key,label)=>{setError('');setPending(p=>({...p,[key]:{label,text:label+'中…'}}));try{const started=await request(path,{method:'POST',body:JSON.stringify(body)});const {operationId}=started;let after=0;const deadline=Date.now()+600000;let outcome=null;while(!outcome&&Date.now()<deadline){await new Promise(resolve=>setTimeout(resolve,1000));const feed=await request('/hanamesh/library/events?after='+after);after=feed.sequence??after;for(const event of feed.events??[]){if(event.operationId!==operationId)continue;if(event.type.endsWith('-done'))outcome={ok:true};else if(event.type.endsWith('-failed'))outcome={ok:false,code:event.code??'LIBRARY_OPERATION_FAILED'};}}
965
+ const operate=async(path,body,key,label,awaitReadback=false)=>{label=COPY.zh[Object.keys(COPY.en).find(k=>COPY.en[k]===label)]??label;if(pendingOperations[key]?.busy)return;setError('');setPending(p=>({...p,[key]:{label,text:()=>(t(label)+t('中…')),busy:true}}));try{const started=await request(path,{method:'POST',body:JSON.stringify(body)});const {operationId}=started;let after=0;const deadline=Date.now()+600000;let outcome=started.status==='already-installed'?{ok:true}:null;while(!outcome&&Date.now()<deadline){await new Promise(resolve=>setTimeout(resolve,1000));const feed=await request('/hanamesh/library/events?after='+after);after=feed.sequence??after;for(const event of feed.events??[]){if(event.operationId!==operationId)continue;if(event.type.endsWith('-done'))outcome={ok:true,result:event.result};else if(event.type.endsWith('-failed'))outcome={ok:false,code:event.code??'LIBRARY_OPERATION_FAILED',recovery:event.recovery,details:event.details};}if(feed.resetRequired&&!outcome&&!feed.operations?.includes(operationId))outcome={ok:false,unknown:true,code:'OPERATION_RESULT_UNAVAILABLE'};}
149
966
  if(!outcome)outcome={ok:false,code:'OPERATION_TIMEOUT'};
150
- if(outcome.ok)setChanged(true);
151
- setPending(p=>({...p,[key]:outcome.ok?{label,text:label+'完成,重启 DSH 后生效'}:{label,text:label+'失败:'+outcome.code+'('+failureReason(outcome.code)+')',failed:true}}));
152
- await load();}catch(e){setPending(p=>({...p,[key]:{label,text:label+'失败:'+String(e.message??e),failed:true}}));}};
967
+ const liveLoaded=outcome.ok&&outcome.result?.status==='applied';
968
+ if(outcome.ok&&!awaitReadback&&!liveLoaded)setChanged(true);
969
+ setPending(p=>({...p,[key]:outcome.ok?(awaitReadback?{label,text:()=>(t(label)+t('读回中…')),busy:true}:{label,text:()=>(liveLoaded?t(label)+t('完成,已生效,可以直接使用')+baselineText(outcome.result):t(label)+t('完成,重启 DSH 后生效'))}):{label,text:()=>(outcome.unknown?t(label)+t('结果不可追溯,请查看已安装列表后再明确操作'):t(label)+t('失败:')+outcome.code+'('+failureReason(outcome.code)+')'+recoveryText(outcome.recovery)+suiteRecoveryText(outcome.details)),failed:true}}));
970
+ await load();return outcome;}catch(e){const code=String(e.message??e);setPending(p=>({...p,[key]:{label,text:()=>(t(label)+t('失败:')+code),failed:true}}));return{ok:false,code};}};
971
+ const confirmTarget=async()=>{
972
+ const selected=targetSelection;if(selected?.status!=='confirmation-required'||pendingOperations[selected.item.id]?.busy)return;
973
+ setTarget({...selected,status:'installing'});
974
+ const suite=selected.item.kind==='suite-upgrade';
975
+ const outcome=await operate(suite?'/hanamesh/library/suite/upgrade':'/hanamesh/library/install',suite?{planId:selected.item.suiteUpgrade.planId}:{itemId:selected.item.id,packageName:selected.item.package.name},selected.item.id,suite?t('升级套件'):t('安装'),true);
976
+ if(outcome){
977
+ if(!outcome.ok){if(targetSelection?.item?.id===selected.item.id)setTarget({...targetSelection,status:outcome.unknown?'result-unavailable':suite?'suite-upgrade-failed':'failed',error:outcome.code,details:outcome.details});return;}
978
+ if(suite){
979
+ try{const snapshot=await request('/hanamesh/library/installedPlugins');if(!outcome.result.components.every(c=>snapshot.plugins.some(p=>p.packageName===c.name&&p.version===c.to&&p.active)))throw new Error('INSTALL_READBACK_MISMATCH');setPending(p=>({...p,[selected.item.id]:{label:t("升级套件"),text:()=>(t('套件升级已读回,请完全退出并重新打开客户端。'))}}));setChanged(true);setTarget({...selected,status:'installed'});}
980
+ catch(error){setPending(p=>({...p,[selected.item.id]:{label:t("升级套件"),text:()=>(t('套件版本读回失败,请检查三个组件。')),failed:true}}));setTarget({...selected,status:'readback-failed',error:String(error.message??error)});}return;
981
+ }
982
+ try{const fresh=await request('/hanamesh/library/target?'+new URLSearchParams({itemId:selected.item.id,packageName:selected.item.package.name}));
983
+ const row=fresh.item?.installed;
984
+ if(row?.packageName!==selected.item.package.name||!['registered','installed','installed-not-loaded'].includes(row.state)||(outcome.result?.version&&row.version!==outcome.result.version))throw new Error('INSTALL_READBACK_MISMATCH');
985
+ const live=outcome.result?.status==='applied';setPending(p=>({...p,[selected.item.id]:{label:t("安装"),text:()=>(live?t('安装完成,已加载,可以直接使用(已按精确版本读回)')+baselineText(outcome.result):t('安装完成,已按精确版本读回'))}}));if(!live)setChanged(true);
986
+ if(targetSelection?.item?.id===selected.item.id)setTarget({...fresh,status:live?'installed-live':'installed'});
987
+ }catch(error){const text=()=>t('安装读回失败:')+String(error.message??error);setPending(p=>({...p,[selected.item.id]:{label:t("安装"),text,failed:true}}));if(targetSelection?.item?.id===selected.item.id)setTarget({...targetSelection,status:'readback-failed',error:t('安装操作已完成,但目录读回失败:')+String(error.message??error)});}
988
+ }
989
+ };
153
990
  const open=async installed=>{
154
991
  if(opening.current)return;
155
992
  const active={appId:installed.appId};opening.current=active;setOpeningAppId(installed.appId);
@@ -184,52 +1021,76 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
184
1021
  }
185
1022
  else next={...next,instance:{...next.instance,status:instance.status,errorCode:instance.errorCode}};
186
1023
  }
187
- }catch(e){if(pendingReceipt){const lease=pendingReceipt;pendingReceipt=null;await request('/apps/close',{method:'POST',body:JSON.stringify({viewId:lease.lease.viewId,leaseToken:lease.leaseToken}),keepalive:true}).catch(()=>{});}if(mounted.current&&surfaceVisible.current)setError(String(e.message??e));}
1024
+ }catch(e){if(pendingReceipt){const lease=pendingReceipt;pendingReceipt=null;await request('/apps/close',{method:'POST',body:JSON.stringify({viewId:lease.lease.viewId,leaseToken:lease.leaseToken}),keepalive:true}).catch(()=>{});}if(mounted.current&&surfaceVisible.current)setError(e);}
188
1025
  finally{if(opening.current===active){opening.current=null;if(mounted.current)setOpeningAppId(null);}}
189
1026
  };
190
- const close=async()=>{if(closing)return;setClosing(true);try{await heldView.current?.close();setReceipt(null);setError('');}catch(e){setError(String(e.message??e));}finally{if(mounted.current)setClosing(false);}};
1027
+ const close=async()=>{if(closing)return;setClosing(true);try{await heldView.current?.close();setReceipt(null);setError('');}catch(e){setError(e);}finally{if(mounted.current)setClosing(false);}};
191
1028
  if(!visible)return null;
192
- const pages=[snapshot,...extra].filter(Boolean),all=pages.flatMap(page=>page.items??[]),items=filter==='plugin'?all.filter(item=>item.kind==='plugin'):all,installed=snapshot?.installed??[],plugins=snapshot?.plugins??[],nextCursor=(extra.at(-1)??snapshot)?.page?.nextCursor??null;
1029
+ const pages=[snapshot,...extra].filter(Boolean),all=pages.flatMap(page=>page.items??[]),items=filter==='plugin'?all.filter(item=>item.kind==='plugin'||item.kind==='suite'):all,installed=snapshot?.installed??[],plugins=snapshot?.plugins??[],nextCursor=(extra.at(-1)??snapshot)?.page?.nextCursor??null;
193
1030
  const restartRequired=changed||Boolean(snapshot?.restartRequired);
194
1031
  const filters=[...BASE_FILTERS,...seenCategories.filter(c=>c!=='hanamesh-app').map(c=>[c,c])];
195
- const openButton=row=>h('button',{type:'button',disabled:Boolean(openingAppId),onClick:()=>void open(row)},openingAppId===row.appId?'打开中…':'打开');
196
- const card=item=>{const row=item.installed,op=pending[item.id],busy=op&&!op.failed&&op.text.endsWith('中…');const installBody={itemId:item.id,...(item.package?.name?{packageName:item.package.name}:{})};
197
- let action=h('span',null,'仅收录,不可安装'),state=null;
198
- if(row)state=h('span',{className:'hm-market-state','data-hanamesh-state':row.state},(STATE_TEXT[row.state]??row.state)+(row.version?' '+row.version:''));
199
- if(busy)action=h('button',{type:'button',disabled:true},op.text);
200
- else if(item.kind!=='listing'&&!row)action=h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/install',installBody,item.id,'安装')},op?.failed?'重试安装':'安装');
201
- else if(row&&item.upgradeAvailable)action=h('span',{className:'hm-market-actions'},h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/install',installBody,item.id,'升级')},'升级到 '+item.latestVersion),row.state==='registered'?openButton(row):null);
202
- else if(row?.state==='runtime-missing')action=h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/provision',{appId:row.appId,packageName:row.packageName,runtimeItem:row.runtimeItem},item.id,'补齐运行时')},'补齐运行时');
203
- else if(row?.state==='registered')action=h('span',{className:'hm-market-actions'},openButton(row),h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/uninstall',{appId:row.appId,packageName:row.packageName,runtimeItem:row.runtimeItem},item.id,'卸载')},'卸载'));
204
- else if(row?.state==='installed')action=PROTECTED.has(row.packageName)?h('span',null,'HanaMesh 套件'):h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},item.id,'卸载')},'卸载');
205
- else if(row?.state==='uninstalled-not-unloaded')action=h('span',null,'已卸载,重启 DSH 后生效');
206
- else if(row)action=h('span',null,'已安装,重启 DSH 后生效');
1032
+ const openButton=row=>h('button',{type:'button',disabled:Boolean(openingAppId),onClick:()=>void open(row)},openingAppId===row.appId?t('打开中…'):t('打开'));
1033
+ const card=item=>{const row=item.installed,op=pending[item.id],busy=op?.busy;const installBody={itemId:item.id,...(item.package?.name?{packageName:item.package.name}:{})};
1034
+ let action=h('span',null,t('仅收录,不可安装')),state=null;
1035
+ if(row)state=h('span',{className:'hm-market-state','data-hanamesh-state':row.state},(t(STATE_TEXT[row.state]??row.state))+(row.version?' '+row.version:''));
1036
+ if(item.kind==='suite')return h('article',{key:item.id,'data-hanamesh-library-item':item.id,'data-hanamesh-kind':'suite'},h('h3',null,t('HanaMesh 套件')),h('p',null,item.summary),h('p',null,item.suite?.status==='installed'?t('已安装 · Core ')+item.suite.version:item.suite?.status==='incomplete'?t('套件不完整,请查看组件状态'):t('套件状态未知,请查看 Core 声明与阻断原因')),item.suite?.completeness?h('p',null,t('Core 完整性:')+item.suite.completeness):null,...(item.suite?.components??[]).map(c=>h('p',{key:c.name},c.requiredRange?c.label+t(' · 要求 ')+c.requiredRange+t(' · 实际 ')+(c.actualVersion??t('检测不到'))+' · '+c.status:c.label+' · '+(c.version??t('检测不到'))+(c.active?'':t(' · 未启用')))),...(item.suite?.blocked??[]).map((row,i)=>h('p',{key:'blocked-'+i,role:'status'},row.code+(row.moduleName?' · '+row.moduleName:'')+(row.coreVersion?' · '+row.coreVersion:''))),item.suiteUpgradeUnavailable?h('p',{role:'status'},item.suiteUpgradeUnavailable):null,h('button',{type:'button',disabled:Boolean(item.suiteUpgradeUnavailable),onClick:()=>void openInstallTarget({itemId:'hanamesh:suite-upgrade'}).catch(()=>{})},t('检查兼容升级')));
1037
+ if(busy)action=h('button',{type:'button',disabled:true},t(op.text));
1038
+ else if(item.kind!=='listing'&&!row)action=h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/install',installBody,item.id,t('安装'))},op?.failed?t('重试安装'):t('安装'));
1039
+ else if(row&&item.upgradeAvailable)action=h('span',{className:'hm-market-actions'},h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/install',installBody,item.id,t('升级'))},t('升级到 ')+item.latestVersion),row.state==='registered'?openButton(row):null);
1040
+ else if(row?.state==='runtime-missing')action=h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/provision',{appId:row.appId,packageName:row.packageName,runtimeItem:row.runtimeItem},item.id,t('补齐运行时'))},t('补齐运行时'));
1041
+ else if(row?.state==='registered')action=h('span',{className:'hm-market-actions'},openButton(row),h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/uninstall',{appId:row.appId,packageName:row.packageName,runtimeItem:row.runtimeItem},item.id,t('卸载'))},t('卸载')));
1042
+ else if(row?.state==='installed')action=PROTECTED.has(row.packageName)?h('span',null,t('HanaMesh 套件')):h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},item.id,t('卸载'))},t('卸载'));
1043
+ else if(row?.state==='uninstalled-not-unloaded')action=h('span',null,t('已卸载,重启 DSH 后生效'));
1044
+ else if(row)action=h('span',null,t('已安装,重启 DSH 后生效'));
207
1045
  return h('article',{key:item.id,'data-hanamesh-library-item':item.id,'data-hanamesh-kind':item.kind},h('h3',null,item.displayName),h('p',null,item.summary),
208
- h('small',null,KIND_TEXT[item.kind]+' · '+(item.publisher?.name??'未知发布者')+' · '+(item.latestVersion??'—')+((item.categories??[]).length?' · '+item.categories.join('、'):'')),
209
- state,action,op&&!busy?h('p',{role:op.failed?'alert':'status',className:op.failed?'hm-library-failed':'hm-library-ok'},op.text):null);};
210
- const catalogSection=receipt?h('div',{className:'hm-app-frame'},h('div',null,h('strong',null,receipt.instance.appId),h('button',{type:'button',disabled:closing,onClick:()=>void close()},closing?'关闭中…':'关闭视图')),h('iframe',{src:receipt.uiUrl,title:receipt.instance.appId,sandbox:'allow-forms allow-modals allow-popups allow-same-origin allow-scripts'})):
1046
+ h('small',null,t(KIND_TEXT[item.kind])+' · '+(item.publisher?.name??t('未知发布者'))+' · '+(item.latestVersion??'—')+((item.categories??[]).length?' · '+item.categories.join('、'):'')),
1047
+ state,action,op&&!busy?h('p',{role:op.failed?'alert':'status',className:op.failed?'hm-library-failed':'hm-library-ok'},t(op.text)):null);};
1048
+ const catalogSection=receipt?h('div',{className:'hm-app-frame'},h('div',null,h('strong',null,receipt.instance.appId),h('button',{type:'button',disabled:closing,onClick:()=>void close()},closing?t('关闭中…'):t('关闭视图'))),h('iframe',{ref:frame,src:receipt.uiUrl,title:receipt.instance.appId,sandbox:'allow-forms allow-modals allow-popups allow-same-origin allow-scripts'})):
211
1049
  h('div',{className:'hm-library-grid',key:'catalog'},...items.map(card));
212
- const moreButton=nextCursor&&!receipt?h('button',{type:'button',className:'hm-library-more',key:'more',onClick:()=>void more()},'更多'):null;
213
- const installedSection=!receipt&&(installed.length||plugins.length)?h('section',{className:'hm-market-installed',key:'installed'},h('h2',null,'已安装'),h('ul',null,
214
- ...installed.map(row=>h('li',{key:'app:'+row.packageName,'data-hanamesh-installed':row.packageName},'应用 · '+(row.name??row.packageName)+' · '+row.packageName+' '+(row.version??'')+' · '+(STATE_TEXT[row.state]??row.state))),
215
- ...plugins.map(row=>h('li',{key:'plugin:'+row.packageName,'data-hanamesh-installed':row.packageName},'插件 · '+row.packageName+' '+(row.version??'')+' · '+(STATE_TEXT[row.state]??row.state)+(row.bundle&&!row.active&&row.state==='installed'?'(未启用为 profile 层)':''),
216
- row.state==='installed'&&!PROTECTED.has(row.packageName)&&!pending['plugin:'+row.packageName]?h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},'plugin:'+row.packageName,'卸载')},'卸载'):pending['plugin:'+row.packageName]?h('span',{className:pending['plugin:'+row.packageName].failed?'hm-library-failed':'hm-library-ok'},' '+pending['plugin:'+row.packageName].text):null)))):null;
1050
+ const moreButton=nextCursor&&!receipt?h('button',{type:'button',className:'hm-library-more',key:'more',onClick:()=>void more()},t('更多')):null;
1051
+ const installedSection=!receipt&&(installed.length||plugins.length)?h('section',{className:'hm-market-installed',key:'installed'},h('h2',null,t('已安装')),h('ul',null,
1052
+ ...installed.map(row=>h('li',{key:'app:'+row.packageName,'data-hanamesh-installed':row.packageName},t('应用 · ')+(row.name??row.packageName)+' · '+row.packageName+' '+(row.version??'')+' · '+(t(STATE_TEXT[row.state]??row.state)))),
1053
+ ...plugins.filter(row=>!PROTECTED.has(row.packageName)).map(row=>h('li',{key:'plugin:'+row.packageName,'data-hanamesh-installed':row.packageName},t('插件 · ')+row.packageName+' '+(row.version??'')+' · '+(t(STATE_TEXT[row.state]??row.state))+(row.bundle&&!row.active&&row.state==='installed'?t('(未启用为 profile 层)'):''),
1054
+ row.state==='installed'&&!PROTECTED.has(row.packageName)&&!pending['plugin:'+row.packageName]?h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},'plugin:'+row.packageName,t('卸载'))},t('卸载')):pending['plugin:'+row.packageName]?h('span',{className:pending['plugin:'+row.packageName].failed?'hm-library-failed':'hm-library-ok'},' '+t(pending['plugin:'+row.packageName].text)):null)))):null;
217
1055
  // The seat owner may ask for a subsection to come first; `installed` is the only one it names today.
218
1056
  const body=preferredSubsectionId==='installed'?[installedSection,catalogSection,moreButton]:[catalogSection,moreButton,installedSection];
219
1057
  return h('section',{className:embedded?'hm-market hm-market-embedded':'hm-library-overlay hm-market'},
220
- h('header',null,h('div',null,h('h1',null,'HanaMesh 市场')),embedded?null:h('button',{type:'button',onClick:()=>setLibraryVisible(false)},'关闭')),
221
- h(MarketConflictNotice,null),
222
- h('div',{className:'hm-library-toolbar'},h('input',{type:'search',placeholder:'搜索应用与插件',value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{if(e.key==='Enter')void load();}}),h('button',{type:'button',onClick:()=>void load()},'搜索'),
223
- h('select',{'aria-label':'类别',value:filter,onChange:e=>setFilter(e.target.value)},...filters.map(([value,label])=>h('option',{key:value||'all',value},label)))),
1058
+ h('header',null,h('div',null,h('h1',null,t('HanaMesh 市场'))),embedded?null:h('button',{type:'button',onClick:()=>setLibraryVisible(false)},t('关闭'))),
1059
+ h(MarketConflictNotice,null),h(HandoffIssueNotice,null),
1060
+ snapshot?.restartBaseline?.known===false?h('p',{role:'alert',className:'hm-library-failed'},t('启动时没能读取 profile(')+snapshot.restartBaseline.code+t('),「需重启」状态无法判断,下面的状态都按已加载显示')):null,
1061
+ verification===true?h('section',{className:'hm-install-target','aria-label':t('安装目标开发小界面')},h('h2',null,t('安装目标')),
1062
+ h('p',null,t('输入目录条目 ID 或包名,市场查询当前目录后确认安装。')),
1063
+ h('form',{onSubmit:event=>{event.preventDefault();void openInstallTarget({...(targetItemId.trim()?{itemId:targetItemId.trim()}:{}),...(targetPackageName.trim()?{packageName:targetPackageName.trim()}:{} )}).catch(()=>{});}},
1064
+ h('input',{'aria-label':t('目录条目 ID'),value:targetItemId,placeholder:t('itemId(可选)'),onChange:event=>setTargetItemId(event.target.value)}),
1065
+ h('input',{'aria-label':t('包名'),value:targetPackageName,placeholder:t('packageName(可选)'),onChange:event=>setTargetPackageName(event.target.value)}),h('button',{type:'submit'},t('查看安装目标')))):null,
1066
+ target?h('div',{role:target.status==='confirmation-required'?'dialog':'status','aria-label':t('市场安装确认')},
1067
+ target.item?h('dl',null,h('dt',null,t('插件名')),h('dd',null,target.item.displayName),h('dt',null,t('包名')),h('dd',null,target.item.package.name),h('dt',null,t('版本')),h('dd',null,target.item.latestVersion??t('目录未提供版本')),h('dt',null,t('来源')),h('dd',null,target.source?.kind==='fixture'?t('测试目录(fixture)'):target.source?.manifestUrl??t('当前市场目录'))):null,
1068
+ target.item?.suiteUpgrade?h('ul',null,...target.item.suiteUpgrade.components.map(c=>h('li',{key:c.name},c.label+':'+c.from+' → '+c.to)),h('li',null,t('确认后升级三个官方组件;需要完全退出并重新打开客户端。'))):null,
1069
+ target.item?.suiteUpgrade?.registry?h('p',null,'Registry:'+target.item.suiteUpgrade.registry):null,
1070
+ ...(target.item?.suiteUpgrade?.blocked??target.details?.blocked??[]).map((row,i)=>h('p',{key:'suite-blocked-'+i,role:'status'},row.code+(row.moduleName?' · '+row.moduleName:'')+(row.coreVersion?' · '+row.coreVersion:''))),
1071
+ target.status==='confirmation-required'?h('div',null,h('button',{type:'button',onClick:()=>void confirmTarget()},t('确认安装')),h('button',{type:'button',onClick:()=>{targetSequence++;void reportHandoff(targetSelection?.handoffId,'cancelled');setTarget({...targetSelection,status:'cancelled'});}},t('取消'))):
1072
+ target.status==='failed'?FailureNotice({error:target.diagnostic??target.error}):h('p',{role:['readback-failed','suite-upgrade-failed'].includes(target.status)?'alert':'status'},({resolving:t('正在查询市场目录…'),installing:t('安装中…'),installed:t('安装完成,请查看已安装列表'),'installed-live':t('安装完成,已加载,可以直接使用'),cancelled:t('已取消,未安装'),'result-unavailable':t('安装结果不可追溯,请查看已安装列表;不会自动重试'),'already-installed':t('已安装,无需重复安装'),failed:t('未安装:')+(target.error??pending[target.item?.id]?.text??t('安装失败')),'readback-failed':target.error,'suite-upgrade-failed':suiteTargetFailure(target)})[target.status]),
1073
+ target.item?.source?h('div',{'aria-label':t('目录条目来源')},h('p',null,target.item.source.sourceId+' · '+target.item.source.sourceRef),...(target.item.source.sourceLinks??[]).map((link,index)=>h('p',{key:index},h('a',{href:link.sourceUrl,target:'_blank',rel:'noopener noreferrer'},link.sourceId+' · '+link.sourceRef),h('small',null,' '+link.firstSeenAt+' / '+link.lastSeenAt)))):null,
1074
+ target.item&&pending[target.item.id]?h('p',{role:pending[target.item.id].failed?'alert':'status'},t(pending[target.item.id].text)):null,
1075
+ ['installed','already-installed'].includes(target.status)&&target.item?.installed?.state==='registered'?openButton(target.item.installed):null):null,
1076
+ verification===true?h('section',{'aria-label':t('C-MARKET 合约验证')},h('h2',null,t('C-MARKET 合约验证')),
1077
+ h('p',null,t('测试版;输入与输出仅供当前客户端验证。安装仍须在正常确认框确认。')),
1078
+ h('select',{'aria-label':t('合约调用'),value:verificationCall,onChange:event=>{const call=VERIFICATION_CALLS.find(call=>call.method+' '+call.path===event.target.value);if(call){setVerificationCall(event.target.value);setVerificationInput(JSON.stringify(call.sample,null,2));setVerificationOutput(null);}}},...VERIFICATION_CALLS.map(call=>h('option',{key:call.method+' '+call.path,value:call.method+' '+call.path},call.method+' '+call.path))),
1079
+ h('textarea',{'aria-label':t('合约样例输入'),value:verificationInput,onChange:event=>setVerificationInput(event.target.value)}),
1080
+ h('button',{type:'button',disabled:verifying,onClick:()=>void verify()},t('调用合约')),
1081
+ h('pre',{'aria-label':t('合约调用输出')},verificationOutput===null?t('尚未调用'):JSON.stringify(verificationOutput,null,2))):null,
1082
+ h('div',{className:'hm-library-toolbar'},h('input',{type:'search',placeholder:t('搜索应用与插件'),value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{if(e.key==='Enter')void load();}}),h('button',{type:'button',onClick:()=>void load()},t('搜索')),
1083
+ h('select',{'aria-label':t('类别'),value:filter,onChange:e=>setFilter(e.target.value)},...filters.map(([value,label])=>h('option',{key:value||'all',value},t(label))))),
224
1084
  restartRequired?h(RestartNotice,null):null,
225
- error?h('p',{role:'alert'},error):null,
1085
+ error?FailureNotice({error}):null,
226
1086
  ...body);
227
1087
  }
228
1088
  /** The `market` seat object read by the shell's Extension Management panel. */
229
- function createMarketSeat(){
1089
+ function createMarketSeat({verification=false}={}){
230
1090
  return{
231
- render(options){return h(LibraryOverlay,{embedded:true,preferredSubsectionId:options?.preferredSubsectionId});},
1091
+ render(options){return h(LibraryOverlay,{embedded:true,preferredSubsectionId:options?.preferredSubsectionId,...(verification===true||options?.verification===true?{verification:true}:{})});},
232
1092
  setSettingsVisible(visible){setEntryVisible(visible!==false);},
1093
+ openInstallTarget,
233
1094
  };
234
1095
  }
235
1096
  /** Another client plugin (dshmarket) already holds the seat — `reflect.get` is a sibling client service, not a host service. */
@@ -240,7 +1101,7 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
240
1101
  catch{return false;}
241
1102
  }
242
1103
  /** Take the seat only when dshmarket is provably absent; otherwise stand down with a readable notice and a log line. */
243
- function claimMarketSeat(ctx){
1104
+ function claimMarketSeat(ctx,{verification=false}={}){
244
1105
  let dispose,released=false;
245
1106
  // Both logs: the host logger when the client context carries one, and always the browser console —
246
1107
  // a DSH client logger is not visible from the page, and the console is where this is actually read.
@@ -253,19 +1114,25 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
253
1114
  if(released)return;
254
1115
  if(conflict){standDown();return;}
255
1116
  // A seat that appears after the panel mounted is fine: cordis notifies every `inject(['market'])` fiber.
256
- try{dispose=ctx.provide(MARKET_SEAT,createMarketSeat());}catch{standDown();}
1117
+ try{dispose=ctx.provide(MARKET_SEAT,createMarketSeat({verification}));}catch{standDown();}
257
1118
  })();
258
1119
  return()=>{released=true;setMarketConflict('');setEntryVisible(true);dispose?.();};
259
1120
  }
260
1121
  const name='hanamesh-app-host-client',inject=['slots','locale'];
261
1122
  // No cross-plugin reads here: the browser-side cordis context never carries host services such as hanameshCore
262
1123
  // (dsh-client-modules boots its own root context); Core status lives in Core's own Settings section.
263
- function apply(ctx){ctx.effect(()=>{const style=document.createElement('style');style.textContent='.hm-providers,.hm-library-sources{display:grid;gap:16px}.hm-providers table{width:100%;border-collapse:collapse}.hm-providers th,.hm-providers td{padding:8px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 14%,transparent)}.hm-provider-card{padding:16px;border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px}.hm-provider-row{display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(180px,1fr) auto;gap:8px;align-items:center;margin:8px 0}.hm-source-row{display:grid;grid-template-columns:minmax(0,1fr) repeat(4,auto);gap:8px}.hm-source-row code{overflow-wrap:anywhere}.hm-source-add{display:flex;gap:8px}.hm-source-add input{flex:1}.hm-library-overlay{position:absolute;inset:0;z-index:30;pointer-events:auto;overflow:auto;padding:24px;background:#fff;color:#18181b}.hm-library-overlay button,.hm-library-overlay input{color:#18181b}.hm-library-overlay>header{display:flex;justify-content:space-between;align-items:center}.hm-library-toolbar{display:flex;gap:8px;align-items:center;margin:8px 0}.hm-library-toolbar input[type=search]{flex:1;padding:6px 10px}.hm-library-more{margin:12px auto;display:block}.hm-library-failed{color:#b91c1c;margin:0}.hm-library-ok{color:#166534;margin:0}.hm-restart-notice{padding:10px 12px;border:1px solid #f59e0b;border-radius:8px;background:#fffbeb;margin:8px 0}.hm-restart-link{margin-left:4px;font-weight:600}.hm-market-state{font-size:12px;color:#3f3f46}.hm-market-actions{display:flex;gap:8px;flex-wrap:wrap}.hm-market-embedded{display:block}.hm-market-embedded>header{display:flex;justify-content:space-between;align-items:center}.hm-market-conflict{padding:10px 12px;border:1px solid #6366f1;border-radius:8px;background:color-mix(in srgb,#6366f1 8%,transparent);margin:8px 0}.hm-market-installed{margin-top:24px}.hm-market-installed ul{padding-left:18px;display:grid;gap:6px}.hm-library-toolbar select{padding:6px 10px}.hm-library-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}.hm-library-grid article{display:grid;gap:10px;padding:16px;border:1px solid color-mix(in srgb,currentColor 16%,transparent);border-radius:12px}.hm-app-frame{display:grid;grid-template-rows:auto 1fr;height:calc(100vh - 130px)}.hm-market-embedded>.hm-app-frame{position:fixed;inset:0;z-index:1001;height:100vh;box-sizing:border-box;background:#fff;padding:12px 16px}.hm-app-frame>div{display:flex;justify-content:space-between}.hm-app-frame iframe{width:100%;height:100%;border:0}';document.head.append(style);return()=>style.remove();},'hanamesh-app-host:style');
264
- ctx.effect(()=>ctx.slots.inject('settings.section',()=>ctx.slots.register({name:'settings.section',id:'hanamesh-providers',order:18,label:'供应商'},ProvidersSection)),'hanamesh-app-host:providers');
265
- ctx.effect(()=>ctx.slots.inject('settings.section',()=>ctx.slots.register({name:'settings.section',id:'hanamesh-library-sources',order:19,label:'市场目录源'},LibrarySourcesSection)),'hanamesh-app-host:library-sources');
1124
+ function localizedSection(ctx,id,order,label,component){return ctx.slots.inject('settings.section',()=>{let dispose;const refresh=()=>{dispose?.();dispose=ctx.slots.register({name:'settings.section',id,order,label:t(label)},component);};refresh();const unsubscribe=localeService?.subscribe(refresh);return()=>{unsubscribe?.();dispose?.();};});}
1125
+ function apply(ctx,{verification=false}={}){localeService=ctx.locale??null;if(localeService)ctx.effect(()=>localeService.register('hanamesh.apphost',COPY),'hanamesh-app-host:locale');ctx.effect(()=>{const style=document.createElement('style');style.textContent='.hm-node-runtime{display:grid;gap:12px;max-width:900px;color:inherit}.hm-node-fields{display:grid;gap:10px}.hm-node-fields label{display:grid;gap:6px}.hm-node-runtime input,.hm-node-runtime select,.hm-node-runtime button{font:inherit;color:inherit;background:transparent;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:8px;min-height:44px;padding:8px 12px;box-sizing:border-box;max-width:100%}.hm-node-runtime button{cursor:pointer}.hm-node-runtime button:hover:not(:disabled){background:color-mix(in srgb,currentColor 9%,transparent)}.hm-node-runtime button:focus-visible{outline:2px solid currentColor;outline-offset:2px}.hm-node-runtime button:disabled{opacity:.5;cursor:default}.hm-node-confirm,.hm-node-progress{border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:12px;padding:16px}.hm-node-confirm dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:8px}.hm-node-runtime dd{margin:0}.hm-node-runtime code,.hm-node-runtime dd{overflow-wrap:anywhere}.hm-node-runtime progress{width:100%}.hm-node-runtime [role=alert]{border-left:3px solid currentColor;padding-left:10px}'+'.hm-providers,.hm-library-sources{display:grid;gap:16px}.hm-providers table{width:100%;border-collapse:collapse}.hm-providers th,.hm-providers td{padding:8px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 14%,transparent)}.hm-provider-card{padding:16px;border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px}.hm-provider-row{display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(180px,1fr) auto;gap:8px;align-items:center;margin:8px 0}.hm-source-row{display:grid;grid-template-columns:minmax(0,1fr) repeat(4,auto);gap:8px}.hm-source-row code{overflow-wrap:anywhere}.hm-source-add{display:flex;gap:8px}.hm-source-add input{flex:1}.hm-library-overlay{position:absolute;inset:0;z-index:30;pointer-events:auto;overflow:auto;padding:24px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.hm-library-overlay button,.hm-library-overlay input,.hm-library-overlay select,.hm-market-embedded button,.hm-market-embedded input,.hm-market-embedded select{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}.hm-library-overlay>header{display:flex;justify-content:space-between;align-items:center}.hm-library-toolbar{display:flex;gap:8px;align-items:center;margin:8px 0}.hm-library-toolbar input[type=search]{flex:1;padding:6px 10px}.hm-library-more{margin:12px auto;display:block}.hm-library-failed{color:var(--dsw-alias-state-error-primary);margin:0}.hm-library-ok{color:var(--dsw-alias-state-success-primary);margin:0}.hm-restart-notice{padding:10px 12px;border:1px solid var(--dsw-alias-state-warn-primary);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary);margin:8px 0}.hm-restart-link{margin-left:4px;font-weight:600}.hm-market-state{font-size:12px;color:var(--dsw-alias-label-secondary)}.hm-market-actions{display:flex;gap:8px;flex-wrap:wrap}.hm-market-embedded{display:block}.hm-market-embedded>header{display:flex;justify-content:space-between;align-items:center}.hm-market-conflict{padding:10px 12px;border:1px solid var(--dsw-alias-state-business-primary);border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 8%,transparent);margin:8px 0}.hm-install-target{display:grid;gap:8px;padding:16px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;margin:12px 0}.hm-install-target form{display:flex;gap:8px;flex-wrap:wrap}.hm-install-target input{flex:1;min-width:180px;padding:6px 10px}.hm-install-target dl{display:grid;grid-template-columns:80px 1fr;gap:6px}.hm-install-target dd{margin:0;overflow-wrap:anywhere}.hm-market-installed{margin-top:24px}.hm-market-installed ul{padding-left:18px;display:grid;gap:6px}.hm-library-toolbar select{padding:6px 10px}.hm-library-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}.hm-library-grid article{display:grid;gap:10px;padding:16px;border:1px solid color-mix(in srgb,currentColor 16%,transparent);border-radius:12px}.hm-app-frame{display:grid;grid-template-rows:auto 1fr;height:calc(100vh - 130px)}.hm-market-embedded>.hm-app-frame{position:fixed;inset:0;z-index:1001;height:100vh;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);padding:12px 16px}.hm-app-frame>div{display:flex;justify-content:space-between}.hm-app-frame iframe{width:100%;height:100%;border:0}';style.textContent+='/* Only AppHost surfaces: never resize the host settings shell. */\n.hm-node-runtime,.hm-library-sources,.hm-providers,.hm-market{min-width:0;width:100%;max-width:100%;box-sizing:border-box;overflow-wrap:anywhere}\n.hm-node-runtime>*,.hm-library-sources>*,.hm-node-fields>*,.hm-handoff-settings>*,.hm-library-toolbar>*{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-node-runtime input,.hm-node-runtime select{min-width:0;width:100%;max-width:100%;box-sizing:border-box;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2)}\n.hm-library-sources button,.hm-market button{min-height:44px;max-width:100%;white-space:normal;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px}\n.hm-library-sources button:focus-visible,.hm-market button:focus-visible{outline:2px solid currentColor;outline-offset:2px}\n.hm-handoff-settings{display:grid;gap:12px}.hm-handoff-settings label{display:grid;gap:6px}.hm-handoff-settings input[type=checkbox]{width:auto}\n.hm-node-confirm,.hm-node-progress{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-failure pre{max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}.hm-failure summary{cursor:pointer}.hm-failure p{margin:8px 0}\n.hm-library-toolbar,.hm-source-add{flex-wrap:wrap}.hm-library-toolbar input[type=search]{min-width:0;width:100%;flex:1 1 180px}\n.hm-library-grid{grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr))}\n.hm-providers{overflow-x:auto}.hm-source-row{grid-template-columns:minmax(0,1fr);gap:6px}\n@media(max-width:640px){.hm-library-overlay{padding:12px}.hm-node-confirm{padding:10px}.hm-node-confirm dl,.hm-install-target dl{grid-template-columns:minmax(0,1fr)}.hm-node-confirm dt{font-weight:600}.hm-node-confirm dd{margin-bottom:6px}.hm-library-toolbar select{width:100%}}\n';style.textContent+='\n.hm-provider-records td{overflow-wrap:anywhere}.hm-providers select,.hm-providers input{min-width:0;max-width:100%;width:100%;box-sizing:border-box}.hm-provider-records details>summary{cursor:pointer;min-height:44px;display:flex;align-items:center}.hm-provider-records pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}\n@media(max-width:640px){.hm-providers{overflow:visible}.hm-provider-records,.hm-provider-records tbody{display:block}.hm-provider-records thead{display:none}.hm-provider-records tr{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin:10px 0}.hm-provider-records td{display:block;min-width:0;padding:0;border:0}.hm-provider-records td::before{content:attr(data-label);display:block;font-weight:600;margin-bottom:4px}.hm-provider-records td:empty{display:none}}\n';style.textContent+="\n.hm-providers,.hm-library-sources,.hm-node-runtime{overflow-wrap:normal;word-break:normal}\n.hm-providers h2,.hm-library-sources h2,.hm-node-runtime h2{margin:0;font-weight:600;line-height:1.5}\n.hm-providers p,.hm-library-sources p,.hm-node-runtime p{margin:0;line-height:1.6}\n.hm-suite-fields,.hm-suite-fields>label,.hm-handoff-settings,.hm-source-add{display:grid;grid-template-columns:minmax(0,1fr);gap:10px;min-width:0}\n.hm-suite-fields>*,.hm-source-add>*,.hm-providers>*,.hm-library-sources details,.hm-node-runtime details{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-suite-fields select,.hm-providers select,.hm-providers input{display:block;width:100%;min-width:0;max-width:100%;box-sizing:border-box;min-height:44px;padding:8px;font:inherit;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}\n.hm-library-sources input[type=checkbox]{display:inline-block;width:20px;min-height:20px;max-width:20px}\n.hm-library-sources summary,.hm-providers summary,.hm-node-runtime summary{min-height:44px;display:flex;align-items:center;cursor:pointer}\n.hm-library-sources code,.hm-library-sources dd,.hm-providers pre{overflow-wrap:anywhere;white-space:pre-wrap}\n.hm-handoff-settings>button,.hm-suite-fields>button{width:100%}\n@media(max-width:640px){.hm-provider-records tr{padding:8px}.hm-node-fields{grid-template-columns:minmax(0,1fr)}}\n";document.head.append(style);return()=>style.remove();},'hanamesh-app-host:style');
1126
+ ctx.effect(()=>localizedSection(ctx,'hanamesh-providers',18,"供应商",ProvidersSection),'hanamesh-app-host:providers');
1127
+ ctx.effect(()=>localizedSection(ctx,'hanamesh-library-sources',19,"市场目录源",LibrarySourcesSection),'hanamesh-app-host:library-sources');
1128
+ ctx.effect(()=>localizedSection(ctx,'hanamesh-suite-source',19.5,"套件安装包来源",SuiteSourceSettingsSection),'hanamesh-app-host:suite-source');
1129
+ ctx.effect(()=>localizedSection(ctx,'hanamesh-node-runtime',20,"安装环境",NodeRuntimeSection),'hanamesh-app-host:node-runtime');
1130
+ ctx.effect(()=>localizedSection(ctx,'hanamesh-library-handoff',20,"网站安装交接",HandoffSettingsSection),'hanamesh-app-host:library-handoff');
266
1131
  ctx.effect(()=>ctx.slots.inject('sidebar.footer.action',()=>ctx.slots.register({name:'sidebar.footer.action',id:'hanamesh-library',order:30},LibraryAction)),'hanamesh-app-host:library-action');
267
- ctx.effect(()=>ctx.slots.inject('shell.overlay',()=>ctx.slots.register({name:'shell.overlay',id:'hanamesh-library-overlay',order:30},LibraryOverlay)),'hanamesh-app-host:library-overlay');
268
- ctx.effect(()=>claimMarketSeat(ctx),'hanamesh-app-host:market-seat');}
269
- return{name,inject,apply,ProvidersSection,LibrarySourcesSection,LibraryAction,LibraryOverlay,MarketConflictNotice,createMarketSeat,
270
- MARKET_SEAT,MARKET_CONFLICT_TEXT,marketState:()=>({conflict:marketConflict,entryVisible})};
1132
+ ctx.effect(()=>ctx.slots.inject('shell.overlay',()=>ctx.slots.register({name:'shell.overlay',id:'hanamesh-library-overlay',order:30},verification===true?()=>h(LibraryOverlay,{verification:true}):LibraryOverlay)),'hanamesh-app-host:library-overlay');
1133
+ ctx.effect(()=>claimMarketSeat(ctx,{verification}),'hanamesh-app-host:market-seat');
1134
+ ctx.effect(()=>{let stopped=false,timer;const tick=async()=>{await pollHandoffsVisibly();if(!stopped)timer=setTimeout(()=>void tick(),handoffPollMs);};void tick();return()=>{stopped=true;clearTimeout(timer);};},'hanamesh-app-host:website-handoff');
1135
+ ctx.effect(()=>{globalThis.addEventListener?.('hashchange',consumeInstallTargetHash);consumeInstallTargetHash();return()=>globalThis.removeEventListener?.('hashchange',consumeInstallTargetHash);},'hanamesh-app-host:install-target-navigation');}
1136
+ return{name,inject,apply,ProvidersSection,SuiteSourceSettingsSection,LibrarySourcesSection,HandoffSettingsSection,NodeRuntimeSection,LibraryAction,LibraryOverlay,MarketConflictNotice,createMarketSeat,openInstallTarget,
1137
+ MARKET_SEAT,MARKET_CONFLICT_TEXT,INSTALL_TARGET_HASH,INSTALL_TARGET_CONTRACT_VERSION,marketState:()=>({conflict:marketConflict,entryVisible})};
271
1138
  }});