dsh-plugin-manager-companion 0.1.0

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +144 -0
  3. package/README.md +142 -0
  4. package/cordis.patch.yml +9 -0
  5. package/dist/about.d.ts +77 -0
  6. package/dist/about.js +179 -0
  7. package/dist/cli.d.ts +226 -0
  8. package/dist/cli.js +856 -0
  9. package/dist/client/AboutPage.d.ts +75 -0
  10. package/dist/client/ConsolePage.d.ts +79 -0
  11. package/dist/client/KindsPage.d.ts +21 -0
  12. package/dist/client/MarketplacePage.d.ts +36 -0
  13. package/dist/client/OfficialSlots.d.ts +35 -0
  14. package/dist/client/UpgradeRow.d.ts +108 -0
  15. package/dist/client/index.d.ts +26 -0
  16. package/dist/client/locales.d.ts +475 -0
  17. package/dist/client/pmSelect.d.ts +38 -0
  18. package/dist/client/shared.d.ts +928 -0
  19. package/dist/client/upgradeView.d.ts +278 -0
  20. package/dist/client/wire.d.ts +401 -0
  21. package/dist/client.js +9194 -0
  22. package/dist/diagnostics.d.ts +332 -0
  23. package/dist/diagnostics.js +2631 -0
  24. package/dist/envManager.d.ts +1047 -0
  25. package/dist/envManager.js +3214 -0
  26. package/dist/fix.d.ts +60 -0
  27. package/dist/fix.js +168 -0
  28. package/dist/guard.d.ts +133 -0
  29. package/dist/guard.js +232 -0
  30. package/dist/index.d.ts +121 -0
  31. package/dist/index.js +1150 -0
  32. package/dist/installSession.d.ts +111 -0
  33. package/dist/installSession.js +150 -0
  34. package/dist/kinds.d.ts +464 -0
  35. package/dist/kinds.js +1029 -0
  36. package/dist/marketView.d.ts +261 -0
  37. package/dist/marketView.js +406 -0
  38. package/dist/marketplace.d.ts +248 -0
  39. package/dist/marketplace.js +500 -0
  40. package/dist/match.d.ts +67 -0
  41. package/dist/match.js +203 -0
  42. package/dist/net.d.ts +108 -0
  43. package/dist/net.js +163 -0
  44. package/dist/official.d.ts +145 -0
  45. package/dist/official.js +205 -0
  46. package/dist/paths.d.ts +108 -0
  47. package/dist/paths.js +236 -0
  48. package/dist/presets.d.ts +299 -0
  49. package/dist/presets.js +578 -0
  50. package/dist/qualityGate.d.ts +66 -0
  51. package/dist/qualityGate.js +247 -0
  52. package/dist/rank.d.ts +88 -0
  53. package/dist/rank.js +164 -0
  54. package/dist/registry.d.ts +295 -0
  55. package/dist/registry.js +686 -0
  56. package/dist/rest.d.ts +122 -0
  57. package/dist/rest.js +219 -0
  58. package/dist/scan.d.ts +134 -0
  59. package/dist/scan.js +396 -0
  60. package/dist/settings.d.ts +447 -0
  61. package/dist/settings.js +263 -0
  62. package/dist/tags.d.ts +119 -0
  63. package/dist/tags.js +166 -0
  64. package/dist/tools.d.ts +131 -0
  65. package/dist/tools.js +377 -0
  66. package/dist/types.d.ts +651 -0
  67. package/dist/types.js +13 -0
  68. package/dist/upgrade.d.ts +428 -0
  69. package/dist/upgrade.js +1100 -0
  70. package/dist/upgradeView.d.ts +313 -0
  71. package/dist/upgradeView.js +273 -0
  72. package/docs/images/readme/01-console-health.png +0 -0
  73. package/docs/images/readme/02-console-envs.png +0 -0
  74. package/docs/images/readme/03-marketplace.png +0 -0
  75. package/docs/images/readme/04-official-plugin-page.png +0 -0
  76. package/package.json +104 -0
@@ -0,0 +1,928 @@
1
+ /**
2
+ * 客户端共享层 — 自有 REST 调用面、状态控制器与官方插件页 slot 契约的镜像。
3
+ *
4
+ * 归属:A 类·重写(旧仓库 src/client/api.ts 与 store.ts 只作意图参考,未复制代码)。
5
+ * 旧实现参考:dsh-web-plugin-manager/src/client/*(理解"客户端要读什么、什么时候刷新"的意图)。
6
+ * 官方复用:@deepseek-ai/dsh-client-store 的 createSnapshotStore(状态容器)、
7
+ * @deepseek-ai/dsh-client-ui-slots 的 ComposedProps/EntryKeyOf(组件 props 的组合别名)、
8
+ * @deepseek-ai/dsh-client-ui-settings/client 的 ctx.settingsScope(配置读写,不碰 YAML)。
9
+ * 前提检查:旧实现自带 27 个 op 的客户端 SDK + 乐观更新 + 自建 job 轮询。0.1.6 之后
10
+ * 官方能力(插件启停/安装/清单)客户端直连官方 Remote,本插件的自有能力按
11
+ * docs/REST-CONTRACT.md 走 /api2/companion;乐观更新被取消(变更必须等 job 落定)。
12
+ *
13
+ * 四条纪律:
14
+ * 1. 变更类请求不带 AbortSignal(中止只杀传输,会留下"改了但没反馈"的状态),
15
+ * 只有加载类请求可以带。
16
+ * 2. 组件不订阅任何外部源:控制器持有 SnapshotStore,经 inject 的 hooks 隔间
17
+ * 合成 use<Name> 选择器 Hook。
18
+ * 3. 这一层不认识 React,也不认识 ctx;它只做数据与动作。
19
+ * 4. **外来数据在这里归一**:REST 与官方 settings 的返回值先过 wire.ts 的归一函数,
20
+ * 再进 SnapshotStore。组件因此可以按类型直接读字段——这一步是实测驱动的:不归一
21
+ * 时,一个缺字段的载荷会让官方 SlotErrorBoundary 把整个 settings.section 渲染成空 div。
22
+ */
23
+ import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
24
+ import { type RelativeTimeUnit, type TagTone } from '@deepseek-ai/dsh-client-ui-primitives';
25
+ import type { ComposedProps, EntryKeyOf, SlotMap, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
26
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
27
+ import { type MarketLabelKey } from '../marketView.ts';
28
+ import type { OfficialCapabilities } from '../official.ts';
29
+ import type { CompanionConfig } from '../settings.ts';
30
+ import type { DiagnosticGroup, DiagnosticIssue, DiagnosticLayer, DiagnosticReport, DiagnosticSeverity, EnvironmentBackup, EnvironmentBackupDiff, EnvironmentInfo, InstalledKind, MarketItemKind, MarketplaceResult } from '../types.ts';
31
+ import { NS, type CompanionLocaleKey } from './locales.ts';
32
+ import { type AboutFactsView, type ClientConfig, type TrialDisclosureView, type TrialEnvironmentsView, type TrialOutcomeView, type UpgradeCheckView } from './wire.ts';
33
+ import { type UpgradeOutcomeKind } from '../upgradeView.ts';
34
+ /**
35
+ * 官方 settings 服务上本插件的命名空间。
36
+ *
37
+ * 与 host 侧 src/settings.ts 的 SETTINGS_NAMESPACE 必须一致;这里刻意不 import 那个
38
+ * 模块的值:它是一个 host 模块(值会拉进 schemastery 与整套 host 代码),客户端只
39
+ * 认这个字符串。改名时必须两处同改(settings.ts 的 schema 注册会拒绝未知命名空间)。
40
+ */
41
+ export declare const SETTINGS_NAMESPACE = "plugin-manager-companion";
42
+ /** 自有 REST 的路由前缀(host 侧 src/rest.ts 的 ROUTE_PREFIX)。 */
43
+ export declare const REST_PREFIX = "/api2/companion";
44
+ /**
45
+ * REST 调用失败。code 是稳定机器码(见 REST-CONTRACT 的错误码表),
46
+ * message 面向用户,可直接展示。
47
+ */
48
+ export declare class CompanionError extends Error {
49
+ readonly code: string;
50
+ /**
51
+ * @param code - 稳定机器码。
52
+ * @param message - 面向用户的说明。
53
+ */
54
+ constructor(code: string, message: string);
55
+ }
56
+ /**
57
+ * 调用一个自有 op。
58
+ *
59
+ * @param op - 操作名。
60
+ * @param body - 请求体(JSON-safe)。
61
+ * @param signal - 仅加载类请求可传;变更类一律不传。
62
+ * @returns 信封里的 value。
63
+ * @throws {CompanionError} 信封为失败,或 HTTP 层失败时。
64
+ */
65
+ export declare function callOp<T>(op: string, body?: unknown, signal?: AbortSignal): Promise<T>;
66
+ /** 可注入的等待实现,测试可替换。 */
67
+ export declare const sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
68
+ /**
69
+ * 跑一个被 job 化的长操作:POST 拿 jobId,再轮询 job 直到落定。
70
+ *
71
+ * 为什么必须等:变更类操作在服务端推进,HTTP 超时与服务端状态会脱节;乐观更新
72
+ * 会让 UI 显示一个并未发生的成功(REST-CONTRACT 明确禁止)。
73
+ *
74
+ * @param op - 操作名。
75
+ * @param body - 请求体。
76
+ * @param signal - 加载类长操作可传,用于中止轮询。
77
+ * @returns job 的最终结果。
78
+ * @throws {CompanionError} job 失败、结果丢失或轮询被中止时。
79
+ */
80
+ export declare function runJob<T>(op: string, body?: unknown, signal?: AbortSignal): Promise<T>;
81
+ /**
82
+ * 官方 `plugins.*` 配置面的 owner props(镜像官方
83
+ * packages/client/ui-plugin-manager/src/client/slot-contract.ts 的
84
+ * PluginConfigViewProps;官方包不在本仓库的安装集里,所以这里按逐字镜像声明)。
85
+ *
86
+ * 漂移风险:官方若改这个契约,必须同步这里——注册面本身是编译期检查的,
87
+ * 运行期由官方页面渲染,类型对不上就是渲染出错的第一个信号。
88
+ */
89
+ export interface PluginConfigViewProps {
90
+ /** summary 只渲染标题下的一句话;page 渲染带自己保存控件的表单。 */
91
+ readonly view: 'summary' | 'page';
92
+ }
93
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
94
+ interface SlotMap {
95
+ /** 官方插件页的官方插件卡片(list)。 */
96
+ 'plugins.item': {
97
+ kind: 'list';
98
+ scope: 'root';
99
+ owner: PluginConfigViewProps;
100
+ };
101
+ /** 一个组合包自己的配置,key 是包名(keyed)。 */
102
+ 'plugins.bundle.config': {
103
+ kind: 'keyed';
104
+ scope: 'root';
105
+ owner: PluginConfigViewProps;
106
+ };
107
+ /** 一行自己的配置,key 是 `<包名>#<行 id>`(keyed)。 */
108
+ 'plugins.row.config': {
109
+ kind: 'keyed';
110
+ scope: 'root';
111
+ owner: PluginConfigViewProps;
112
+ };
113
+ }
114
+ }
115
+ /**
116
+ * 一个本插件注册项的完整组件 props:官方组合别名 + 本插件字典命名空间的 t 座位。
117
+ * 直接用官方 ComposedProps,绝不手写派生成员(packages/client/AGENTS.md 的 slot 纪律)。
118
+ */
119
+ export type CompanionSlotProps<K extends keyof SlotMap & string, I extends object> = ComposedProps<K, EntryKeyOf<K>, never, undefined, I, never, typeof NS>;
120
+ /** 诊断层的展示顺序与字典键。 */
121
+ export declare const LAYER_ORDER: readonly DiagnosticLayer[];
122
+ /** 层 → 字典键。 */
123
+ export declare const LAYER_LABEL: Readonly<Record<DiagnosticLayer, CompanionLocaleKey>>;
124
+ /** 诊断层 → 配置表单里的标签键(与体检页的层名分开:一处是报告,一处是开关)。 */
125
+ export declare const DIAGNOSTIC_LABEL: Readonly<Record<DiagnosticLayer, CompanionLocaleKey>>;
126
+ /** 处置等级 → 字典键。 */
127
+ export declare const SEVERITY_LABEL: Readonly<Record<DiagnosticSeverity, CompanionLocaleKey>>;
128
+ /** 处置等级 → Tag 色调(可自动修复是好消息,只报告才是需要读者注意的)。 */
129
+ export declare const SEVERITY_TONE: Readonly<Record<DiagnosticSeverity, TagTone>>;
130
+ /** 市场条目类型 → 字典键。 */
131
+ export declare const KIND_LABEL: Readonly<Record<MarketItemKind, CompanionLocaleKey>>;
132
+ /**
133
+ * 市场纯函数模块给出的字典键 → 本插件字典键。
134
+ *
135
+ * 市场管道(src/marketView.ts)只产出**键名**,文案归字典所有;这里做一次映射,
136
+ * 类型上要求 MarketLabelKey 全集,少一个键就编译失败。
137
+ */
138
+ export declare const MARKET_LABEL: Readonly<Record<MarketLabelKey, CompanionLocaleKey>>;
139
+ /**
140
+ * 健康分:满分 100,按最严重的处置等级扣分。
141
+ *
142
+ * 旧实现按问题条数线性扣分,一个 C 级问题就能把分数打到 0,读数失去意义;
143
+ * 这里改成按等级加权——可自动修复扣 5、需确认扣 10、只报告扣 20,下限 0。
144
+ *
145
+ * @param issues - 报告里的发现。
146
+ * @returns 0..100 的整数。
147
+ */
148
+ export declare function healthScore(issues: readonly DiagnosticIssue[]): number;
149
+ /** 相对时间的桶 → 字典键(词条归字典所有,分桶由官方 relativeTime 给出)。 */
150
+ export declare const RELATIVE_LABEL: Readonly<Record<RelativeTimeUnit, CompanionLocaleKey>>;
151
+ /**
152
+ * 把 ISO 时间渲染成"刚刚 / 5 分钟前…"。
153
+ *
154
+ * 分桶用官方 relativeTime(两个界面说同一时刻必须用同一个桶),词条来自本插件字典。
155
+ * now 取调用时刻:这是展示函数,不参与纯计算,也不需要 uSES 语义。
156
+ *
157
+ * @param t - 本插件字典的翻译函数。
158
+ * @param iso - ISO 8601 时间串;无法解析时原样返回。
159
+ * @returns 已本地化的相对时间文本。
160
+ */
161
+ export declare function formatRelative(t: TranslateNS<typeof NS>, iso: string): string;
162
+ /**
163
+ * 一个层值 → 字典键。
164
+ *
165
+ * 报告来自外部(host),所以层值可能是本客户端不认识的字符串;此时返回 undefined,
166
+ * 由组件回退成显示原始 id。直接 `t(LAYER_LABEL[layer])` 会把未知值渲染成 undefined。
167
+ *
168
+ * @param layer - 报告里的层值。
169
+ * @returns 字典键;未知层值时 undefined。
170
+ */
171
+ export declare function layerLabelKey(layer: string): CompanionLocaleKey | undefined;
172
+ /**
173
+ * 处置等级 → 字典键。
174
+ * @param severity - 报告里的等级值。
175
+ * @returns 字典键;未知等级时 undefined。
176
+ */
177
+ export declare function severityLabelKey(severity: string): CompanionLocaleKey | undefined;
178
+ /**
179
+ * 处置等级 → Tag 色调。
180
+ * @param severity - 报告里的等级值。
181
+ * @returns 色调;未知等级按"需注意"显示(绝不把未知降级成好色调)。
182
+ */
183
+ export declare function severityToneOf(severity: string): TagTone;
184
+ /**
185
+ * 证据类型 → 展示用标识。
186
+ * @param kind - 证据里的类型值。
187
+ * @returns 展示标识;未知类型原样显示(技术标识不做翻译)。
188
+ */
189
+ export declare function evidenceKindOf(kind: string): string;
190
+ /**
191
+ * 一条发现是否属于某个分组。
192
+ *
193
+ * 判据只用 wire 上真实存在的字段(层 / 类别 / 等级 + 作用域清单),**不重算 host 的组键格式**:
194
+ * 键是实现细节,而"这一组包含哪些发现"必须能从契约字段推出来。发现自身没有 scope 时按
195
+ * 空串对待(与 host 的"无法归属即空串"一致);组没有作用域清单时,层+类别+等级相同即同组。
196
+ *
197
+ * @param issue - 一条发现。
198
+ * @param group - 报告里的一个分组。
199
+ * @returns 属于该组时为 true。
200
+ */
201
+ export declare function issueInGroup(issue: DiagnosticIssue, group: DiagnosticGroup): boolean;
202
+ /** 证据类型 → 展示用前缀(不做翻译:文件/运行时/官方是技术标识)。 */
203
+ export declare const EVIDENCE_KIND: Readonly<Record<'file' | 'runtime' | 'official', string>>;
204
+ /**
205
+ * 体检子页的状态。
206
+ *
207
+ * 字段刻意**不是** readonly:这是 SnapshotStore 的 draft,控制器通过 update 就地改它。
208
+ */
209
+ export interface HealthState {
210
+ report: DiagnosticReport | undefined;
211
+ running: boolean;
212
+ error: string | undefined;
213
+ /** 客户端自己判定的失败(载荷残缺等);文案归字典,控制层只给键。 */
214
+ errorKey?: CompanionLocaleKey;
215
+ /**
216
+ * 当前这条失败**来自哪个动作**:诊断还是修复。
217
+ *
218
+ * 为什么必须是显式字段:靠"notice 写过没有"这种间接线索归因时不成立——修复走 callOp 抛异常
219
+ * 的那条路径只写 error、不写 notice,界面于是把"修复失败"说成"体检失败"(真机实测的 P1)。
220
+ * 写入规则:谁写下当前这条失败,谁就写这里;它描述的失败被清掉时同步清成 undefined。
221
+ */
222
+ failureFrom?: 'diagnose' | 'fix' | undefined;
223
+ /**
224
+ * 诊断目标环境名;undefined 表示当前环境。
225
+ *
226
+ * 为什么保留 undefined 而不是在控制器里解析成当前环境名:控制器不认识 profile 列表
227
+ * (那是 host 的事实)。界面把"用户选的就是当前环境"折回 undefined,语义只有两个:
228
+ * "当前环境"与"某个具名环境"。
229
+ */
230
+ target: string | undefined;
231
+ /** 正在执行修复的 issue id。 */
232
+ fixingId: string | undefined;
233
+ /** 最近一次修复的输出。 */
234
+ notice: string | undefined;
235
+ /** 官方能力探针:哪里能力缺失要如实告诉用户,而不是把缺失伪装成健康。 */
236
+ capabilities: OfficialCapabilities | undefined;
237
+ }
238
+ /** 体检子页的注入面:hooks 隔间合成 useHealth 选择器 Hook。 */
239
+ export interface HealthFace {
240
+ hooks: {
241
+ health: SnapshotStore<HealthState>;
242
+ };
243
+ /** 跑一次全量(或指定层)诊断。 */
244
+ diagnose(layers?: readonly DiagnosticLayer[]): void;
245
+ /** 执行一条发现的修复动作。 */
246
+ fix(issue: DiagnosticIssue): void;
247
+ /**
248
+ * 切换诊断目标环境;undefined 表示当前环境。
249
+ *
250
+ * 切换会**立刻丢弃**上一份报告(报告属于某个环境,跨环境复用它的展开状态与修复按钮
251
+ * 就是事故),并立即重跑诊断;在途的那次响应按代号作废。
252
+ */
253
+ setDiagnosticTarget(environment: string | undefined): void;
254
+ }
255
+ /** 体检控制器:持有报告状态,动作全部走自有 REST。 */
256
+ export declare class HealthController {
257
+ private readonly store;
258
+ /**
259
+ * 诊断代号:每次目标切换或重新诊断自增。
260
+ *
261
+ * 用途唯一——作废在途的响应。用户在诊断跑到一半时切了环境,旧环境的报告绝不能
262
+ * 落进新目标的状态里(那会让"报告属于哪个环境"变成一个谎言)。
263
+ */
264
+ private generation;
265
+ /**
266
+ * 诊断与修复**各记各的失败**:诊断成功只清自己写下的那条,修复失败不会被顺手抹掉
267
+ * (与 client-dev 在 task-18 修的 ReadFailureLedger 同一思路——读的成功不该清掉操作的失败)。
268
+ */
269
+ private readonly diagnoseFailure;
270
+ private readonly fixFailure;
271
+ constructor();
272
+ /** 供注册项使用的注入面。 */
273
+ inject(): HealthFace;
274
+ /**
275
+ * 切换诊断目标环境。
276
+ * @param environment - 目标环境名;undefined 表示当前环境。
277
+ */
278
+ setTarget(environment: string | undefined): void;
279
+ /**
280
+ * 跑一次诊断(用户动作)。
281
+ *
282
+ * 会清掉上一条**动作结果**(notice):用户主动体检即"处置"了上一次动作留下的提示。
283
+ * @param layers - 只诊断这些层;省略即按配置全量。
284
+ */
285
+ diagnose(layers?: readonly DiagnosticLayer[]): Promise<void>;
286
+ /**
287
+ * 修复之后的自动刷新:报告必须更新,但修复的结果提示要留着。
288
+ *
289
+ * 为什么不能直接用 diagnose():那是"用户主动体检",开头会清 notice;而这条 notice 是用户
290
+ * 刚点的那个修复动作的产物(成功与失败都一样),在同一个 0ms 内被清掉等于用户什么都没看到
291
+ * (真机实测时间线:+0ms 写 notice → +0ms 被自动刷新清掉)。
292
+ */
293
+ private refreshAfterFix;
294
+ /**
295
+ * 诊断主体:两条入口(用户体检 / 修复后的自动刷新)只差"要不要保留上一条动作结果"。
296
+ * @param layers - 只诊断这些层;省略即按配置全量。
297
+ * @param options - keepNotice 为真时保留上一条动作结果(修复的产物)。
298
+ */
299
+ private runDiagnosis;
300
+ /**
301
+ * 执行一条修复动作。
302
+ * @param issue - 带 fix 的发现。
303
+ */
304
+ fix(issue: DiagnosticIssue): Promise<void>;
305
+ }
306
+ /** 环境子页的状态。 */
307
+ export interface EnvironmentsState {
308
+ environments: readonly EnvironmentInfo[];
309
+ loading: boolean;
310
+ /** 正在执行的操作名(用于按钮禁用与提示)。 */
311
+ busy: string | undefined;
312
+ error: string | undefined;
313
+ /** 客户端自己判定的失败(载荷残缺、备份文件格式不对);文案归字典。 */
314
+ errorKey?: CompanionLocaleKey;
315
+ notice: string | undefined;
316
+ /** 已读入内存的备份文档。 */
317
+ backup: EnvironmentBackup | undefined;
318
+ /** 最近一次差异对比结果。 */
319
+ diff: EnvironmentBackupDiff | undefined;
320
+ }
321
+ /** 环境子页的注入面。 */
322
+ export interface EnvironmentsFace {
323
+ hooks: {
324
+ environments: SnapshotStore<EnvironmentsState>;
325
+ };
326
+ refreshEnvironments(): void;
327
+ /** 启动一个环境;background 为真时不占用前台终端。 */
328
+ startEnvironment(name: string, background: boolean): void;
329
+ stopEnvironment(name: string): void;
330
+ createEnvironment(name: string, template?: string): void;
331
+ renameEnvironment(from: string, to: string): void;
332
+ removeEnvironment(name: string): void;
333
+ copyPlugins(from: string, to: string, names: readonly string[]): void;
334
+ /** 导出备份:结果同时下载成文件并留在状态里供对比。 */
335
+ exportBackup(name: string): void;
336
+ /** 读入一个备份文件(浏览器 File,由 UI 的 file input 提供)。 */
337
+ loadBackup(file: File): void;
338
+ /** 对比已读入的备份与目标环境。 */
339
+ diffBackup(target: string): void;
340
+ /** 用已读入的备份恢复目标环境。 */
341
+ restoreBackup(target: string): void;
342
+ /** 清掉上一次操作的通知与错误。 */
343
+ dismissEnvironmentNotice(): void;
344
+ }
345
+ /** 环境控制器。 */
346
+ export declare class EnvironmentsController {
347
+ private readonly store;
348
+ /** "读列表"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
349
+ private readonly readFailure;
350
+ constructor();
351
+ /** 供注册项使用的注入面。 */
352
+ inject(): EnvironmentsFace;
353
+ /**
354
+ * 重新读环境列表。
355
+ *
356
+ * **只动 loading**:`notice` / `error` 描述的是"上一次操作",刷新列表是另一回事,
357
+ * 无权替操作宣布结果。这里曾经先清 error/errorKey,而 act() 是"先落结果、再 refresh"——
358
+ * 于是写操作失败后马上被这一行抹掉,结果块的 exitCode 恒为 0、界面上呈现成"完成"
359
+ * (真机复现:复制插件填一个不存在的包 → 结果块「完成」,输出却是失败文本)。
360
+ *
361
+ * 为什么不改成"把落定挪到 refresh 之后":那要求每个新操作都记得排序,本 bug 正是
362
+ * 这种调用顺序约定失效的产物;而"刷新列表不碰操作结果"是一条不依赖调用方记性的规则。
363
+ * 失败态由操作自身清零(每个操作开始时清)与 dismissEnvironmentNotice 负责。
364
+ */
365
+ refresh(): Promise<void>;
366
+ /**
367
+ * 跑一个变更类操作:写 busy → 执行 → 记结果 → 刷新列表。
368
+ *
369
+ * 变更一律不带 AbortSignal:中止只杀传输,服务端仍会推进。
370
+ *
371
+ * @param label - 操作标签,用于 UI 的忙碌提示。
372
+ * @param task - 具体调用(返回值一律过归一,形状不对时如实报失败而不是当成功)。
373
+ */
374
+ private act;
375
+ /**
376
+ * 导出备份。
377
+ * @param name - 被备份的环境名。
378
+ */
379
+ exportBackup(name: string): Promise<void>;
380
+ /**
381
+ * 读入备份文件。
382
+ * @param file - 浏览器 File 对象。
383
+ */
384
+ loadBackup(file: File): Promise<void>;
385
+ /**
386
+ * 差异对比。
387
+ * @param target - 目标环境名。
388
+ */
389
+ diffBackup(target: string): Promise<void>;
390
+ /**
391
+ * 用已读入的备份恢复目标环境。
392
+ * @param target - 目标环境名。
393
+ */
394
+ restoreBackup(target: string): Promise<void>;
395
+ }
396
+ /** 市场子页的状态。 */
397
+ export interface MarketplaceState {
398
+ result: MarketplaceResult | undefined;
399
+ loading: boolean;
400
+ error: string | undefined;
401
+ /** 客户端自己判定的失败(索引载荷残缺);文案归字典。 */
402
+ errorKey?: CompanionLocaleKey;
403
+ query: string;
404
+ /** 选中的分类 id;空串表示全部。 */
405
+ category: string;
406
+ /** 选中的类型;空串表示全部。 */
407
+ kind: MarketItemKind | '';
408
+ /** 正在安装的条目 repo。 */
409
+ installing: string | undefined;
410
+ installError: string | undefined;
411
+ /** 质量门发现的问题(安装未通过时保留,供用户追责)。 */
412
+ gateIssues: readonly string[];
413
+ rolledBack: boolean;
414
+ /**
415
+ * 这次安装的试装结论(质量门第二步)。undefined = 宿主没给这个字段,
416
+ * 而不是"试装通过"——界面据此区分"没试装"与"试装通过"。
417
+ */
418
+ trial: TrialOutcomeView | undefined;
419
+ }
420
+ /** 市场子页的注入面。 */
421
+ export interface MarketplaceFace {
422
+ hooks: {
423
+ marketplace: SnapshotStore<MarketplaceState>;
424
+ };
425
+ loadMarketplace(refresh: boolean): void;
426
+ setMarketQuery(query: string): void;
427
+ setMarketCategory(category: string): void;
428
+ setMarketKind(kind: MarketItemKind | ''): void;
429
+ installMarketItem(item: MarketItemView): void;
430
+ dismissInstallNotice(): void;
431
+ }
432
+ /** 市场条目在 UI 侧的稳定视图(只带渲染与安装需要的字段)。 */
433
+ export interface MarketItemView {
434
+ readonly repo: string;
435
+ readonly name: string;
436
+ /**
437
+ * 安装 spec —— **host 侧给的**(MarketItem.installSpec),客户端只负责原样转发。
438
+ *
439
+ * 不在这里拼字符串是有意的:官方 parseInstallSpec 只认 registry 名 / 绝对路径 / git URL /
440
+ * tarball 四种形态,而市场条目的天然键是 owner/repo(不在其中,官方直接判 invalid-spec)。
441
+ * 那套规则若在客户端再实现一份,索引字段一变就要改两处,且必然漂移。
442
+ * 契约上可选:老载荷没有这个字段时不猜——送空串,由 host 的字段校验如实报错。
443
+ */
444
+ readonly installSpec?: string;
445
+ }
446
+ /** 市场控制器。 */
447
+ export declare class MarketplaceController {
448
+ private readonly store;
449
+ /** "读索引"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
450
+ private readonly readFailure;
451
+ constructor();
452
+ /** 供注册项使用的注入面。 */
453
+ inject(): MarketplaceFace;
454
+ /**
455
+ * 读市场索引。
456
+ * @param refresh - 是否强制绕过缓存。
457
+ */
458
+ load(refresh: boolean): Promise<void>;
459
+ /**
460
+ * 经质量门安装一个市场条目。
461
+ *
462
+ * spec 由 host 决定(MarketItem.installSpec),本方法**只转发**;缺失时送空串,
463
+ * 让 host 的字段校验给出可读错误,而不是在这里编一个 spec 出来。
464
+ *
465
+ * @param item - 要安装的条目。
466
+ */
467
+ install(item: MarketItemView): Promise<void>;
468
+ }
469
+ /** 技能与预设子页的状态。 */
470
+ export interface KindsState {
471
+ records: readonly InstalledKind[];
472
+ orphans: readonly string[];
473
+ loading: boolean;
474
+ error: string | undefined;
475
+ /** 客户端自己判定的失败(载荷残缺);文案归字典。 */
476
+ errorKey?: CompanionLocaleKey;
477
+ busy: string | undefined;
478
+ notice: string | undefined;
479
+ }
480
+ /** 技能与预设子页的注入面。 */
481
+ export interface KindsFace {
482
+ hooks: {
483
+ kinds: SnapshotStore<KindsState>;
484
+ };
485
+ loadKinds(): void;
486
+ uninstallKind(repo: string): void;
487
+ }
488
+ /** 技能与预设控制器。 */
489
+ export declare class KindsController {
490
+ private readonly store;
491
+ /** "读记录"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
492
+ private readonly readFailure;
493
+ constructor();
494
+ /** 供注册项使用的注入面。 */
495
+ inject(): KindsFace;
496
+ /** 读技能与预设记录。 */
497
+ load(): Promise<void>;
498
+ /**
499
+ * 卸载一个已安装的技能或预设。
500
+ * @param repo - owner/repo。
501
+ */
502
+ uninstall(repo: string): Promise<void>;
503
+ }
504
+ /** 设置子页的状态:官方 settings 的镜像 + 本地草稿。 */
505
+ export interface ConfigState {
506
+ status: 'loading' | 'ready' | 'unavailable';
507
+ writable: boolean;
508
+ /** 官方已解析的当前值(原样保留:保存时的"前后对比"和写盘路径都以它为准)。 */
509
+ value: CompanionConfig | undefined;
510
+ /**
511
+ * 渲染用草稿:已过 wire 归一(缺字段按客户端默认值补齐)再叠加本地编辑。
512
+ *
513
+ * 类型是 {@link ClientConfig} 而不是 host 的 CompanionConfig:host 侧 `trial` 是可选字段,
514
+ * 归一后它一定存在——组件因此不需要(也不许)在读取处替它兜默认值。
515
+ */
516
+ draft: ClientConfig | undefined;
517
+ /** 宿主给的文档缺字段(draft 里有默认值补出来的部分)——界面要如实说明。 */
518
+ incomplete: boolean;
519
+ dirty: boolean;
520
+ saving: boolean;
521
+ failed: boolean;
522
+ saved: boolean;
523
+ }
524
+ /** 设置子页的注入面。 */
525
+ export interface ConfigFace {
526
+ hooks: {
527
+ config: SnapshotStore<ConfigState>;
528
+ };
529
+ /** 改一个字段(路径是命名空间内的嵌套路径)。 */
530
+ editConfigField(path: readonly string[], value: unknown): void;
531
+ saveConfig(): void;
532
+ discardConfig(): void;
533
+ }
534
+ /** 设置控制器:草稿 + 一次保存写全部改动(与官方插件配置页同一交互模型)。 */
535
+ export declare class ConfigController {
536
+ private readonly scope;
537
+ private readonly store;
538
+ private staged;
539
+ /**
540
+ * @param scope - 官方 settings 服务上本插件命名空间的句柄。
541
+ */
542
+ constructor(scope: SettingsScope<CompanionConfig>);
543
+ /** 供注册项使用的注入面。 */
544
+ inject(): ConfigFace;
545
+ /** 把官方快照与本地草稿合成一份状态。 */
546
+ private publish;
547
+ /**
548
+ * 暂存一个字段编辑(不写盘;保存时才写)。
549
+ * @param path - 命名空间内的路径。
550
+ * @param value - 新值。
551
+ */
552
+ edit(path: readonly string[], value: unknown): void;
553
+ /** 丢弃所有暂存编辑。 */
554
+ discard(): void;
555
+ /** 保存全部暂存编辑(一次 mutate,天然共用同一个修订栅栏)。 */
556
+ save(): Promise<void>;
557
+ }
558
+ /** 上一次试装环境变更的结果(结构化:界面按 kind 选文案,不把整句拼进状态)。 */
559
+ export interface TrialActionState {
560
+ readonly kind: 'remove' | 'cleanup';
561
+ readonly ok: boolean;
562
+ readonly removed: readonly string[];
563
+ readonly output: string;
564
+ readonly code?: string;
565
+ }
566
+ /** 「设置」子页里试装那一节的状态。 */
567
+ export interface TrialState {
568
+ /** 披露事实(capabilities op 的 trialDisclosure);undefined = 还没读到或读不到。 */
569
+ disclosure: TrialDisclosureView | undefined;
570
+ /** 披露读不到的原因(宿主消息)。 */
571
+ disclosureError: string | undefined;
572
+ /** 披露读不到的原因(我们自己的判定,文案归字典)。 */
573
+ disclosureErrorKey?: CompanionLocaleKey;
574
+ /** 测试环境报告;undefined = 还没读到或读失败(界面据此显示加载中/失败,不显示"没有测试环境")。 */
575
+ report: TrialEnvironmentsView | undefined;
576
+ loading: boolean;
577
+ /** 正在执行的动作标签(与官方页一致的 动词+名字 形态)。 */
578
+ busy: string | undefined;
579
+ /** 读列表自己写下的失败(只有它能被下一次成功的读取清掉)。 */
580
+ error: string | undefined;
581
+ errorKey?: CompanionLocaleKey;
582
+ /** 上一次变更操作的结果(成功也留,直到用户处置)。 */
583
+ action: TrialActionState | undefined;
584
+ }
585
+ /** 试装管理面的注入面。 */
586
+ export interface TrialFace {
587
+ hooks: {
588
+ trial: SnapshotStore<TrialState>;
589
+ };
590
+ loadTrial(): void;
591
+ removeTrialEnvironment(name: string): void;
592
+ cleanupTrialEnvironments(): void;
593
+ }
594
+ /**
595
+ * 试装控制器:读披露事实与测试环境列表,执行"删除一个"与"清理过期"。
596
+ *
597
+ * 披露事实走 capabilities op 而不是在客户端抄一份数字:数字与口径由 host 给;
598
+ * 抄一份就会在下次实测后漂移,而漂移的是"用户以为自己承担了什么风险"。
599
+ */
600
+ export declare class TrialController {
601
+ private readonly store;
602
+ /** "读列表"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
603
+ private readonly readFailure;
604
+ constructor();
605
+ /** 供注册项使用的注入面。 */
606
+ inject(): TrialFace;
607
+ /**
608
+ * 读披露事实 + 测试环境列表。
609
+ *
610
+ * 只动 loading / 自己写下的读失败:action 描述的是"上一次操作",刷新无权替它宣布结果
611
+ * (task-18 的真机 P1:读操作清掉写操作的结果,失败就被渲染成完成)。披露读不到时也只记原因,
612
+ * 不让整节变成不可用——用户仍能改配置,只是看不到"会发生什么"。
613
+ */
614
+ load(): Promise<void>;
615
+ /**
616
+ * 删除一个测试环境。
617
+ * @param name - 测试环境名。
618
+ */
619
+ remove(name: string): Promise<void>;
620
+ /** 清理过期的测试环境(走 job:可能跨多个目录)。 */
621
+ cleanup(): Promise<void>;
622
+ /**
623
+ * 跑一个变更类操作:写 busy → 执行 → 落结果 → 刷新列表。
624
+ *
625
+ * 结果必须先落定再刷新:反过来会让刚写下的失败被 refresh 抹掉(task-18 的 P1 就是这个顺序)。
626
+ *
627
+ * @param kind - 操作种类(界面按它选文案)。
628
+ * @param label - 忙碌提示用的标签。
629
+ * @param task - 具体调用(返回值一律过归一,形状不对时如实报失败而不是当成功)。
630
+ */
631
+ private act;
632
+ }
633
+ /**
634
+ * 升级那一块的状态。
635
+ *
636
+ * 三块事实各自独立,**不合并**:
637
+ * · check:最近一次 upgradeCheck 的结果(四态就在这里);
638
+ * · busy:正在跑的包名(升级是长操作,界面据此禁用入口);
639
+ * · action:最近一次升级/回滚的结果 —— 它必须活到用户处置为止,
640
+ * 不能被任何一次刷新抹掉(task-18 的真机 P1:读操作清掉写操作的结果,失败被渲染成完成)。
641
+ */
642
+ export interface UpgradeState {
643
+ /** 检查结果;undefined = 还没查过(界面据此显示"还没检查",不是"已是最新")。 */
644
+ check: UpgradeCheckView | undefined;
645
+ loading: boolean;
646
+ /** 读检查结果自己写下的失败(只有它能被下一次成功的读取清掉)。 */
647
+ error: string | undefined;
648
+ /** 客户端自己判定的失败(载荷残缺);文案归字典。 */
649
+ errorKey?: CompanionLocaleKey;
650
+ /** 正在升级的包名。 */
651
+ busy: string | undefined;
652
+ /** 最近一次升级的结果(成功也留,直到用户处置或下一次升级开始)。 */
653
+ action: UpgradeActionResultState | undefined;
654
+ /** 最近一次回滚的结果。 */
655
+ rollback: UpgradeRollbackResultState | undefined;
656
+ }
657
+ /** 一次升级的结果(结构化:界面按 outcome 选文案,不把整句拼进状态)。 */
658
+ export interface UpgradeActionResultState {
659
+ /** 诚实分类:done / rolled-back / failed / unverified(见 upgradeView.upgradeOutcome)。 */
660
+ readonly outcome: UpgradeOutcomeKind;
661
+ readonly name: string;
662
+ readonly fromVersion: string | null;
663
+ readonly toVersion: string;
664
+ readonly ok: boolean;
665
+ readonly output: string;
666
+ readonly code?: string;
667
+ /** 金丝雀的读法:passed / failed / not-run / absent(没验证 ≠ 验证失败 ≠ 通过)。 */
668
+ readonly canary: 'passed' | 'failed' | 'not-run' | 'absent';
669
+ /** 没跑金丝雀的原因(ran===false 时才有)。 */
670
+ readonly canaryNote?: string;
671
+ /**
672
+ * 金丝雀的**完整结论原文**(含根因链)。
673
+ *
674
+ * 为什么必须带出来:顶层的 output 是"结论与后果"那一层,而用户追责要看的是
675
+ * 根因链(例如 duplicate loader entry id)。丢掉它,界面只能说"没通过",
676
+ * 用户拿不到任何可查的东西——task-78/80 的验收明确要求根因链可见。
677
+ */
678
+ readonly canaryOutput?: string;
679
+ /** 金丝雀的激活证据:候选有没有真的进测试环境的层栈。 */
680
+ readonly canaryActivated?: boolean;
681
+ readonly restartRequired: boolean;
682
+ readonly diskFacts: readonly string[];
683
+ /**
684
+ * 本次升级用的 spec(host 的官方 add 收到的那个)。
685
+ *
686
+ * 为什么结果态要带它:回滚入口就长在这个块里,而回滚需要它来判断"原来是不是本地来源"。
687
+ * 缺了它,回滚会把 link: 装的包换成 registry 版本——那不是回滚,是换来源。
688
+ */
689
+ readonly spec?: string;
690
+ }
691
+ /** 一次回滚的结果。 */
692
+ export interface UpgradeRollbackResultState {
693
+ readonly name: string;
694
+ readonly ok: boolean;
695
+ readonly output: string;
696
+ readonly code?: string;
697
+ readonly fromVersion: string | null;
698
+ readonly toVersion: string;
699
+ /** 盘上核对是否一致;undefined = 读不出来(不是"不干净")。 */
700
+ readonly clean: boolean | undefined;
701
+ readonly diskFacts: readonly string[];
702
+ }
703
+ /** 升级动作的注入面(官方插件页的升级行与市场页卡片共用同一份)。 */
704
+ export interface UpgradeFace {
705
+ hooks: {
706
+ upgrade: SnapshotStore<UpgradeState>;
707
+ };
708
+ /**
709
+ * 进入即查:**同一会话内只发一次**(TTL / 开关 / 负缓存判定都在 host 侧,客户端不重复实现)。
710
+ *
711
+ * 为什么需要这个去重口:"进入即查"的触发点在**渲染期**(官方插件页/市场页挂载时的 effect),
712
+ * 而这两个面会被反复挂载。没有去重就会变成"每开一次页面出一趟网",
713
+ * 而 TTL 的语义是"距上次成功检查超过间隔才查"——去重的依据在 host,触发次数得由客户端收住。
714
+ */
715
+ ensureUpgrades(): void;
716
+ /** 手动检查(无视开关、TTL 与负缓存);refresh=true 是用户点的重试。 */
717
+ loadUpgrades(refresh: boolean): void;
718
+ /** 升级一个包到指定版本(长操作,走 job)。 */
719
+ upgradePackage(name: string, version: string, spec?: string): void;
720
+ /** 回滚一个包到指定版本。 */
721
+ rollbackPackage(name: string, version: string, spec?: string): void;
722
+ /** 处置(清掉)最近一次升级结果。 */
723
+ dismissUpgradeNotice(): void;
724
+ }
725
+ /**
726
+ * 本页只列"这套软件本身"的单元(DESIGN §5.5 的用户裁决)。
727
+ *
728
+ * 三类都在,但**第三方插件一律不出现在这一页**——它们的升级入口是官方插件页里该包自己的页面
729
+ * 与市场页卡片。所以这里不能简单地"把 units 全画出来",必须筛。
730
+ *
731
+ * 判据用 **host 已经分好的 `kind`**,不自己按包名猜:
732
+ * · `installation-provided` → ① 官方运行时(全局安装的那份);
733
+ * · `profile-dependency` → ② 官方自带实验包(与第三方在**同一类**里,靠包名分);
734
+ * · `self` → ③ 本插件自身。
735
+ * ②与第三方同 kind,所以还需要一条"是不是官方的"判据,见 {@link isOfficialPackage}。
736
+ */
737
+ export declare const SOFTWARE_UNIT_KINDS: readonly string[];
738
+ /**
739
+ * 一个包名是否属于官方(`@deepseek-ai/` 作用域)。
740
+ *
741
+ * 为什么用作用域而不是维护一张白名单:官方包名会随版本增删(`dsh-experimental-*` 尤其),
742
+ * 白名单一过期就会**静默漏掉一个该显示的单元**;作用域是发布方自己定的、稳定的。
743
+ * 与仓库既有判据同源(`src/diagnostics.ts` 的 `name.startsWith('@deepseek-ai/')`)。
744
+ *
745
+ * 注意:本插件自身(`dsh-plugin-manager-companion`)**不在**这个作用域里,
746
+ * 但它必须显示——所以判据是"官方 或 本插件自身",见 {@link isSoftwareUnit}。
747
+ *
748
+ * @param name - 包名。
749
+ * @returns 是否官方作用域。
750
+ */
751
+ export declare function isOfficialPackage(name: string): boolean;
752
+ /**
753
+ * 一个升级单元是否属于"这套软件本身"(本页要显示的)。
754
+ *
755
+ * @param unit - 单元视图(只要有 name / kind 两个字段)。
756
+ * @returns 是否显示在这一页。
757
+ */
758
+ export declare function isSoftwareUnit(unit: {
759
+ readonly name: string;
760
+ readonly kind: string;
761
+ }): boolean;
762
+ /**
763
+ * 从全部单元里挑出本页要显示的,并按三类排好(① → ② → ③,类内按包名)。
764
+ *
765
+ * 为什么固定顺序:用户来这一页是依次看"官方运行时能不能升 / 实验包能不能升 / 我自己能不能升",
766
+ * 顺序随检查结果的返回次序变会让每次打开都长得不一样。
767
+ *
768
+ * @param units - 检查结果里的全部单元。
769
+ * @returns 本页要显示的单元(已排序)。
770
+ */
771
+ export declare function softwareUnits<T extends {
772
+ readonly name: string;
773
+ readonly kind: string;
774
+ }>(units: readonly T[]): readonly T[];
775
+ /** 「关于」页的状态。 */
776
+ export interface AboutState {
777
+ /** 事实集合;undefined = 还没读到(界面显示"还没读到",不是一张空表)。 */
778
+ facts: AboutFactsView | undefined;
779
+ loading: boolean;
780
+ /** 读失败的原因(op 挂了);文案归字典。 */
781
+ error: string | undefined;
782
+ errorKey?: CompanionLocaleKey;
783
+ }
784
+ /** 「关于」页的注入面。 */
785
+ export interface AboutFace {
786
+ hooks: {
787
+ about: SnapshotStore<AboutState>;
788
+ };
789
+ /**
790
+ * 读一次事实。
791
+ *
792
+ * 与升级面的"进入即查"不同,这里**不去重**:关于页读的是本地事实(无网络、无副作用),
793
+ * 每次进入重读一遍反而更准(缓存年龄会随时间变、用户可能刚删了缓存文件)。
794
+ * 去重那一套是为"出网"设计的,套在这里只会让数字变旧。
795
+ *
796
+ * @param refresh - 用户点的重试(与首次进入走同一条路,保留参数是为了接口一致)。
797
+ */
798
+ loadAbout(refresh: boolean): void;
799
+ }
800
+ /**
801
+ * 「关于」页的控制器。
802
+ *
803
+ * 与 UpgradeController 同一形状(store + 读失败账本),但**没有** busy / 动作结果——
804
+ * 这一页纯读,没有任何写动作,所以不需要那些字段。
805
+ */
806
+ export declare class AboutController {
807
+ private readonly store;
808
+ /** "读事实"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
809
+ private readonly readFailure;
810
+ constructor();
811
+ /** 供注册项使用的注入面。 */
812
+ inject(): AboutFace;
813
+ /** 当前快照。 */
814
+ snapshot(): AboutState;
815
+ /**
816
+ * 读一次事实。
817
+ *
818
+ * 只动 loading / 自己写下的读失败——与升级面同一条纪律:
819
+ * 一次刷新无权替上一次的失败宣布结果。
820
+ *
821
+ * @param _refresh - 用户点的重试(本 op 无缓存,参数不影响读法)。
822
+ */
823
+ load(_refresh: boolean): Promise<void>;
824
+ }
825
+ /**
826
+ * 市场页的注入面:市场自己的面 + 升级面。
827
+ *
828
+ * 为什么写成一个显式接口(而不是 `MarketplaceFace & UpgradeFace`):两边的 hooks 记录
829
+ * 用交叉类型表达时,官方 `PropsHooks` 的映射类型在交叉上**推导不出** useMarketplace /
830
+ * useUpgrade 两个成员(实测编译报"Property 'useMarketplace' is missing")。
831
+ * 一个显式的 hooks 记录把两件事说清楚,也省掉一处会被读错的类型体操。
832
+ */
833
+ export interface MarketplaceConsoleFace {
834
+ hooks: {
835
+ marketplace: SnapshotStore<MarketplaceState>;
836
+ upgrade: SnapshotStore<UpgradeState>;
837
+ };
838
+ loadMarketplace(refresh: boolean): void;
839
+ setMarketQuery(query: string): void;
840
+ setMarketCategory(category: string): void;
841
+ setMarketKind(kind: MarketItemKind | ''): void;
842
+ installMarketItem(item: MarketItemView): void;
843
+ dismissInstallNotice(): void;
844
+ ensureUpgrades(): void;
845
+ loadUpgrades(refresh: boolean): void;
846
+ upgradePackage(name: string, version: string, spec?: string): void;
847
+ rollbackPackage(name: string, version: string, spec?: string): void;
848
+ dismissUpgradeNotice(): void;
849
+ }
850
+ /**
851
+ * 升级控制器。
852
+ *
853
+ * 两条纪律与其它控制器一致,另加一条本块特有的:
854
+ * · **不做乐观更新**:升级结果只由 job 的落定结果写入(REST-CONTRACT 明确禁止);
855
+ * · **失败不吞**:载荷残缺记 errorKey,host 报错记 error,两者都不写进 action;
856
+ * · **升级成功后立刻重查**(版本事实过期了),但重查**不许**清掉刚写下的结果
857
+ * —— 顺序必须是"先落结果、再刷新"(task-18 的 P1 就是这个顺序)。
858
+ */
859
+ export declare class UpgradeController {
860
+ private readonly store;
861
+ /** "读检查结果"写下的失败;只有它能被下一次成功的读取清掉(见 ReadFailureLedger)。 */
862
+ private readonly readFailure;
863
+ /** 进入即查是否已经发过(一次会话一次;见 ensureLoaded)。 */
864
+ private kicked;
865
+ constructor();
866
+ /** 供注册项使用的注入面。 */
867
+ inject(): UpgradeFace;
868
+ /** 当前快照(index.ts 的注册对账要读它)。 */
869
+ snapshot(): UpgradeState;
870
+ /**
871
+ * 进入即查的**去重口**:一次会话只发一次(见 {@link UpgradeFace.ensureUpgrades})。
872
+ *
873
+ * 已经查过(成功或失败)就不再发;手动检查与台账变化走 {@link load},绕过这个闸门。
874
+ */
875
+ ensureLoaded(): Promise<void>;
876
+ /**
877
+ * 读一次升级检查结果。
878
+ *
879
+ * 只动 loading / 自己写下的读失败:action 描述的是"上一次操作",刷新无权替它宣布结果。
880
+ *
881
+ * @param refresh - 手动检查(无视开关、TTL 与负缓存)。
882
+ */
883
+ load(refresh: boolean): Promise<void>;
884
+ /**
885
+ * 升级一个包(长操作:走 job + 轮询)。
886
+ *
887
+ * 结果落定后**先写 action,再重查**——顺序反过来,重查会清掉刚写下的失败。
888
+ *
889
+ * @param name - 包名。
890
+ * @param version - 目标版本。
891
+ * @param spec - 当前来源(host 据此判断"升级会改变来源"与回滚目标)。
892
+ */
893
+ upgrade(name: string, version: string, spec?: string): Promise<void>;
894
+ /**
895
+ * 回滚一个包到指定版本。
896
+ *
897
+ * @param name - 包名。
898
+ * @param version - 回滚目标版本。
899
+ * @param spec - 原来源(本地来源时回滚装回那个来源)。
900
+ */
901
+ rollback(name: string, version: string, spec?: string): Promise<void>;
902
+ }
903
+ /**
904
+ * 把字节数格式化成紧凑文本。
905
+ *
906
+ * 单位是量纲不是文案,两种语言共用,因此不进字典。
907
+ *
908
+ * @param bytes - 字节数。
909
+ * @returns 形如 12.3 MiB 的文本。
910
+ */
911
+ export declare function formatBytes(bytes: number): string;
912
+ /**
913
+ * 环境控制台注册项的组合注入面:体检、环境、设置三块能力共用一个注册项
914
+ * (一个入口 + 本地子页面,所以只声明一份 face)。
915
+ *
916
+ * 试装(质量门第二步)的字段与测试环境管理都在「设置」子页里,所以它的面也挂在这一份上。
917
+ */
918
+ export type ConsoleFace = HealthFace & EnvironmentsFace & ConfigFace & TrialFace;
919
+ /**
920
+ * 把一份 JSON 触发成浏览器下载。
921
+ *
922
+ * 只在浏览器里执行:非浏览器渲染环境(测试)下 document 不存在,此时静默跳过——
923
+ * 备份内容已留在状态里,UI 仍可对比与恢复。
924
+ *
925
+ * @param fileName - 建议的文件名。
926
+ * @param payload - 要写进文件的内容。
927
+ */
928
+ export declare function downloadJson(fileName: string, payload: unknown): void;