@lesliechan721/aw 0.1.0-beta.4 → 0.1.0-beta.6

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 (99) hide show
  1. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +14 -0
  2. package/dist/authoring/scripts/lib/plugin-version-check.mjs +10 -224
  3. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
  4. package/dist/authoring/scripts/remove-plugin.d.mts +2 -0
  5. package/dist/authoring/scripts/remove-plugin.mjs +366 -0
  6. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
  7. package/dist/authoring/scripts/validate-plugin.mjs +9 -46
  8. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  9. package/dist/scripts/bundle-plugins.d.ts +0 -2
  10. package/dist/scripts/bundle-plugins.js +4 -2
  11. package/dist/scripts/bundle-plugins.js.map +1 -1
  12. package/dist/scripts/plugin-staging.d.ts +1 -16
  13. package/dist/scripts/plugin-staging.js +8 -115
  14. package/dist/scripts/plugin-staging.js.map +1 -1
  15. package/dist/src/authoring/workspace-commands.d.ts +9 -1
  16. package/dist/src/authoring/workspace-commands.js +24 -16
  17. package/dist/src/authoring/workspace-commands.js.map +1 -1
  18. package/dist/src/commands/catalog-publication-inspection.d.ts +106 -0
  19. package/dist/src/commands/catalog-publication-inspection.js +444 -0
  20. package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
  21. package/dist/src/commands/catalog-publish.d.ts +18 -2
  22. package/dist/src/commands/catalog-publish.js +261 -316
  23. package/dist/src/commands/catalog-publish.js.map +1 -1
  24. package/dist/src/commands/catalog-status.d.ts +60 -0
  25. package/dist/src/commands/catalog-status.js +251 -0
  26. package/dist/src/commands/catalog-status.js.map +1 -0
  27. package/dist/src/commands/plugin-capabilities.d.ts +22 -0
  28. package/dist/src/commands/plugin-capabilities.js +143 -0
  29. package/dist/src/commands/plugin-capabilities.js.map +1 -0
  30. package/dist/src/commands/rebuild-command.d.ts +2 -0
  31. package/dist/src/commands/rebuild-command.js +37 -6
  32. package/dist/src/commands/rebuild-command.js.map +1 -1
  33. package/dist/src/commands/rebuild-integration.d.ts +1 -0
  34. package/dist/src/commands/rebuild-integration.js +55 -57
  35. package/dist/src/commands/rebuild-integration.js.map +1 -1
  36. package/dist/src/commands/status-v4.d.ts +21 -0
  37. package/dist/src/commands/status-v4.js +63 -2
  38. package/dist/src/commands/status-v4.js.map +1 -1
  39. package/dist/src/index.js +79 -18
  40. package/dist/src/index.js.map +1 -1
  41. package/dist/src/tui/components.d.ts +70 -0
  42. package/dist/src/tui/components.js +149 -6
  43. package/dist/src/tui/components.js.map +1 -1
  44. package/dist/src/tui/console-app-v4.js +1660 -628
  45. package/dist/src/tui/console-app-v4.js.map +1 -1
  46. package/dist/src/tui/controller-runtime.d.ts +5 -1
  47. package/dist/src/tui/controller-runtime.js +184 -33
  48. package/dist/src/tui/controller-runtime.js.map +1 -1
  49. package/dist/src/tui/controller.d.ts +35 -2
  50. package/dist/src/tui/controller.js +300 -39
  51. package/dist/src/tui/controller.js.map +1 -1
  52. package/dist/src/tui/diagnostics.d.ts +31 -0
  53. package/dist/src/tui/diagnostics.js +71 -0
  54. package/dist/src/tui/diagnostics.js.map +1 -0
  55. package/dist/src/tui/index.d.ts +5 -3
  56. package/dist/src/tui/index.js +3 -1
  57. package/dist/src/tui/index.js.map +1 -1
  58. package/dist/src/tui/model.d.ts +33 -4
  59. package/dist/src/tui/model.js +252 -17
  60. package/dist/src/tui/model.js.map +1 -1
  61. package/dist/src/tui/router.d.ts +15 -9
  62. package/dist/src/tui/router.js +35 -0
  63. package/dist/src/tui/router.js.map +1 -1
  64. package/dist/src/tui/theme.d.ts +18 -10
  65. package/dist/src/tui/theme.js +18 -10
  66. package/dist/src/tui/theme.js.map +1 -1
  67. package/dist/src/tui/viewport.d.ts +20 -0
  68. package/dist/src/tui/viewport.js +29 -0
  69. package/dist/src/tui/viewport.js.map +1 -0
  70. package/dist/src/utils/catalog-runtime-projection.d.ts +1 -7
  71. package/dist/src/utils/catalog-runtime-projection.js +2 -62
  72. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  73. package/dist/src/utils/catalog-source-cache.d.ts +12 -11
  74. package/dist/src/utils/catalog-source-cache.js +20 -0
  75. package/dist/src/utils/catalog-source-cache.js.map +1 -1
  76. package/dist/src/utils/dynamic-skill-tree.js +1 -1
  77. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  78. package/dist/src/utils/git-catalog-provider.js +1 -1
  79. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  80. package/dist/src/utils/manifest.d.ts +0 -1
  81. package/dist/src/utils/manifest.js +0 -1
  82. package/dist/src/utils/manifest.js.map +1 -1
  83. package/dist/src/utils/plugin-config.js +28 -30
  84. package/dist/src/utils/plugin-config.js.map +1 -1
  85. package/dist/src/utils/skill-validation.d.ts +1 -1
  86. package/dist/src/utils/skill-validation.js +1 -1
  87. package/dist/src/utils/skill-validation.js.map +1 -1
  88. package/dist/src/utils/state-paths.d.ts +1 -0
  89. package/dist/src/utils/state-paths.js +5 -0
  90. package/dist/src/utils/state-paths.js.map +1 -1
  91. package/package.json +3 -4
  92. package/dist/src/utils/plugin-authoring-capabilities.d.ts +0 -23
  93. package/dist/src/utils/plugin-authoring-capabilities.js +0 -37
  94. package/dist/src/utils/plugin-authoring-capabilities.js.map +0 -1
  95. package/dist/src/utils/plugin-authoring-capabilities.json +0 -92
  96. package/dist/src/utils/skill-frontmatter-capabilities.d.ts +0 -201
  97. package/dist/src/utils/skill-frontmatter-capabilities.js +0 -5
  98. package/dist/src/utils/skill-frontmatter-capabilities.js.map +0 -1
  99. package/dist/src/utils/skill-frontmatter-capabilities.json +0 -200
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import path from 'node:path';
@@ -6,11 +6,13 @@ import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
6
6
  import { canonicalizeCatalogGitUrl } from '../utils/catalog-source-identity.js';
7
7
  import { normalizePortableAgents } from '../portable/commands.js';
8
8
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
9
- import { ScreenFrame } from './components.js';
9
+ import { ActionRow, Callout, ChoiceRow, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
10
+ import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
10
11
  import { extractPortableError, userIssueKey } from './controller.js';
11
12
  import { resolveTuiSizePolicy } from './layout.js';
12
- import { hasInstalledPlacement } from './model.js';
13
+ import { buildPlatformActions, buildPluginDetailActions, buildPluginPreviewActions, hasInstalledPlacement, } from './model.js';
13
14
  import { tuiTheme } from './theme.js';
15
+ import { contentRowBudget, rowViewport } from './viewport.js';
14
16
  export const CONSOLE_V4_SCREEN_IDS = [
15
17
  'G01', 'G02', 'G03', 'G04', 'G05',
16
18
  'P01', 'P02', 'P03', 'P04', 'P05', 'P06', 'P07', 'P08', 'P09', 'P10',
@@ -25,6 +27,49 @@ export const CONSOLE_V4_SCREEN_IDS = [
25
27
  'D10', 'D11', 'D12', 'D13', 'D14', 'D15', 'D16', 'D17',
26
28
  'U01', 'U02', 'U03', 'U04', 'U05', 'U06', 'U07', 'U08', 'U09', 'U10', 'U11',
27
29
  ];
30
+ function pluginMenuActionId(action) {
31
+ return action.id === 'platform' || action.id === 'scopeConflict'
32
+ ? `${action.id}:${action.platform}`
33
+ : action.id;
34
+ }
35
+ function isCharacterInputMode(route, searchMode) {
36
+ return searchMode
37
+ || route.name === 'portableAgentInput'
38
+ || route.name === 'configFieldEditor' && route.editor !== 'enum' && route.editor !== 'boolean'
39
+ || route.name === 'sourceAdd' && route.step !== 'type' && route.step !== 'refPolicy';
40
+ }
41
+ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy) {
42
+ const rows = [];
43
+ if (!busy && actionCount > 1)
44
+ rows.push(['↑/↓ · j/k', '移动当前唯一焦点']);
45
+ if (!busy && actionCount > 0)
46
+ rows.push(['Enter', '打开、选择或继续']);
47
+ if (!busy && route.name !== 'home')
48
+ rows.push(['Esc / b', '返回上一页']);
49
+ if (route.name === 'pluginList') {
50
+ rows.push(['←/→ · [ / ]', '切换来源']);
51
+ rows.push(['/', '进入搜索']);
52
+ if (route.mode === 'browse')
53
+ rows.push(['f', '切换全部或未安装']);
54
+ }
55
+ if (route.name === 'portableSkillSelect') {
56
+ rows.push(['Space', '切换当前 Skill']);
57
+ rows.push(['a', '全选 Skills']);
58
+ }
59
+ if (route.name === 'configFields' && route.layer.scope !== 'effective') {
60
+ rows.push(['s', '保存设置']);
61
+ rows.push(['u', '恢复当前字段继承']);
62
+ }
63
+ if (route.name === 'sourceList' || route.name === 'sourceDetail') {
64
+ rows.push(['r', '重新检查来源']);
65
+ }
66
+ if (supportsTechnicalDetails) {
67
+ rows.push(['d', route.name === 'progress' ? '打开技术进度' : '打开技术详情']);
68
+ }
69
+ rows.push(['q / Ctrl-C', busy ? '操作完成后退出' : '退出控制台']);
70
+ rows.push(['?', '关闭帮助']);
71
+ return rows;
72
+ }
28
73
  function configOwnerKey(route) {
29
74
  return `${route.pluginId}\0${route.layer.scope}\0${route.layer.projectLayer}`;
30
75
  }
@@ -67,18 +112,27 @@ function statusText(plugin) {
67
112
  unavailable: '来源不可用',
68
113
  }[plugin.aggregateStatus];
69
114
  }
70
- function statusColor(status) {
71
- if (status === 'success')
72
- return tuiTheme.statusColors.success;
73
- if (status === 'warning')
74
- return tuiTheme.statusColors.warning;
75
- if (status === 'error')
76
- return tuiTheme.statusColors.error;
77
- if (status === 'info')
78
- return tuiTheme.statusColors.info;
79
- if (status === 'muted')
80
- return tuiTheme.colors.muted;
81
- return undefined;
115
+ function pluginPlacementSummary(plugin) {
116
+ return plugin.supportedPlatforms.map((platform) => {
117
+ const label = platformLabel(platform);
118
+ const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
119
+ if (!placement || placement.state === 'notInstalled')
120
+ return `${label} · 未添加`;
121
+ if (placement.state === 'scopeConflict')
122
+ return `${label} · 位置冲突`;
123
+ const health = placement.health === 'ready' ? '可用'
124
+ : placement.health === 'incomplete' ? '需要处理' : '不可用';
125
+ return `${label} · ${scopeLabel(placement.scope)} · ${health}`;
126
+ }).join(' | ') || '没有可用平台';
127
+ }
128
+ function pluginRowStatus(plugin) {
129
+ if (plugin.aggregateStatus === 'needsAttention')
130
+ return 'warning';
131
+ if (plugin.aggregateStatus === 'installed')
132
+ return 'success';
133
+ if (plugin.aggregateStatus === 'unavailable')
134
+ return 'error';
135
+ return 'muted';
82
136
  }
83
137
  function moveFocus(actions, current, direction) {
84
138
  if (actions.length === 0)
@@ -90,67 +144,82 @@ function moveFocus(actions, current, direction) {
90
144
  }
91
145
  return current;
92
146
  }
93
- function actionLimitForLineBudget(actions, lineBudget) {
94
- let used = 0;
95
- let count = 0;
96
- for (const action of actions) {
97
- const height = action.description || action.disabled ? 2 : 1;
98
- if (count > 0 && used + height > lineBudget)
99
- break;
100
- used += height;
101
- count += 1;
102
- }
103
- return Math.max(1, count);
147
+ function moveIndex(count, current, direction) {
148
+ if (count === 0)
149
+ return 0;
150
+ return (current + direction + count) % count;
151
+ }
152
+ function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
153
+ return rowViewport({
154
+ rowHeights: actions.map((action) => compact || (!action.description && !action.disabled) ? 1 : 2),
155
+ focus,
156
+ requestedOffset,
157
+ rowBudget: lineBudget,
158
+ });
159
+ }
160
+ function portableSkillCandidateViewport(actions, candidates, compact, focus, requestedOffset, rowBudget) {
161
+ const candidateFocus = focus < candidates.length ? focus : requestedOffset;
162
+ return groupedRowViewport({
163
+ rowHeights: actions.map(() => compact ? 1 : 2),
164
+ groupKeys: candidates.map((candidate) => candidate.providerPluginId),
165
+ groupOverhead: 3,
166
+ focus: candidateFocus,
167
+ requestedOffset,
168
+ rowBudget,
169
+ });
104
170
  }
105
- function lineBudgetViewport(actions, focus, requestedOffset, lineBudget, groupKeys) {
171
+ function groupedRowViewport(options) {
172
+ const total = options.rowHeights.length;
173
+ if (total === 0) {
174
+ return { offset: 0, limit: 0, hiddenBefore: 0, hiddenAfter: 0, position: 0, total: 0 };
175
+ }
176
+ const focus = Math.min(Math.max(0, options.focus), total - 1);
106
177
  const height = (start, end) => {
107
- let lines = 0;
178
+ let rows = 0;
108
179
  let previousGroup;
109
180
  for (let index = start; index < end; index += 1) {
110
- const group = groupKeys?.[index];
111
- if (group && group !== previousGroup)
112
- lines += 1;
113
- lines += actions[index]?.description || actions[index]?.disabled ? 2 : 1;
181
+ const group = options.groupKeys[index];
182
+ if (group !== undefined && group !== previousGroup)
183
+ rows += options.groupOverhead;
184
+ rows += options.rowHeights[index] ?? 1;
114
185
  previousGroup = group;
115
186
  }
116
- return lines;
187
+ return rows;
117
188
  };
118
- let offset = Math.min(requestedOffset, Math.max(0, actions.length - 1));
119
- if (focus < offset)
120
- offset = focus;
121
- while (offset < focus && height(offset, focus + 1) > lineBudget)
189
+ let offset = Math.min(Math.max(0, options.requestedOffset), focus);
190
+ while (offset < focus && height(offset, focus + 1) > options.rowBudget)
122
191
  offset += 1;
123
- let end = Math.max(offset + 1, focus + 1);
124
- while (end < actions.length && height(offset, end + 1) <= lineBudget)
192
+ let end = focus + 1;
193
+ while (end < total && height(offset, end + 1) <= options.rowBudget)
125
194
  end += 1;
126
- return { offset, limit: Math.max(1, end - offset) };
127
- }
128
- function actionViewport(routeName, actions, mode, focus, requestedOffset) {
129
- if (routeName === 'portableDependencies') {
130
- return lineBudgetViewport(actions, focus, requestedOffset, mode === 'compact' ? 7 : 12);
131
- }
132
- const limit = routeName === 'pluginList' || routeName === 'issueList' || routeName === 'sourceList'
133
- ? actionLimitForLineBudget(actions, mode === 'compact' ? 8 : 12)
134
- : routeName === 'pluginDependencies' ? mode === 'compact' ? 2 : 7
135
- : mode === 'compact' ? 4
136
- : routeName === 'configFields' || routeName === 'review' || routeName === 'issueDetail' ? 7 : undefined;
137
- if (limit === undefined)
138
- return { offset: 0 };
139
- const maximumOffset = Math.max(0, actions.length - limit);
140
- let offset = Math.min(requestedOffset, maximumOffset);
141
- if (focus < offset)
142
- offset = focus;
143
- else if (focus >= offset + limit)
144
- offset = focus - limit + 1;
145
- return { limit, offset: Math.max(0, offset) };
195
+ while (offset > 0 && height(offset - 1, end) <= options.rowBudget)
196
+ offset -= 1;
197
+ return {
198
+ offset,
199
+ limit: end - offset,
200
+ hiddenBefore: offset,
201
+ hiddenAfter: total - end,
202
+ position: focus + 1,
203
+ total,
204
+ };
146
205
  }
147
- function portableSkillCandidateViewport(actions, candidates, mode, focus, requestedOffset) {
148
- const candidateFocus = focus < candidates.length ? focus : requestedOffset;
149
- return lineBudgetViewport(actions, candidateFocus, requestedOffset, mode === 'compact' ? 10 : 12, candidates.map((candidate) => candidate.providerPluginId));
206
+ function prefixRowLimit(rowHeights, rowBudget) {
207
+ let used = 0;
208
+ let visible = 0;
209
+ for (const rowHeight of rowHeights) {
210
+ const hiddenSummary = visible + 1 < rowHeights.length ? 1 : 0;
211
+ if (used + rowHeight + hiddenSummary > rowBudget)
212
+ break;
213
+ used += rowHeight;
214
+ visible += 1;
215
+ }
216
+ return visible;
150
217
  }
151
218
  function portableScopeVariantsDiffer(candidate) {
152
219
  const project = candidate.scopeDetails.project;
153
220
  const user = candidate.scopeDetails.user;
221
+ if (Boolean(project) !== Boolean(user))
222
+ return true;
154
223
  return Boolean(project && user && (project.description !== user.description
155
224
  || project.providerPluginId !== user.providerPluginId
156
225
  || project.providerDisplayName !== user.providerDisplayName
@@ -159,7 +228,7 @@ function portableScopeVariantsDiffer(candidate) {
159
228
  }
160
229
  function portableScopeVariantLabel(scope, detail) {
161
230
  if (!detail)
162
- return '';
231
+ return `${scope === 'project' ? '当前项目' : '所有项目'}:不可用`;
163
232
  return `${scope === 'project' ? '当前项目' : '所有项目'}:${detail.description} · ${detail.providerDisplayName}${detail.dynamic ? ' · 按设置生成' : ''}`;
164
233
  }
165
234
  function privatePathInput(value) {
@@ -168,49 +237,172 @@ function privatePathInput(value) {
168
237
  const basename = value.split(/[\\/]/u).filter(Boolean).at(-1) ?? '已选择';
169
238
  return `已选择 / … / ${basename}`;
170
239
  }
171
- function ActionRows(props) {
240
+ function portableBlockerDiagnostic(route) {
241
+ const cliUnavailable = route.code === 'portableSkillsCliMissing'
242
+ || route.code === 'portableSkillsCliIncompatible';
243
+ const userMessage = route.code === 'requiredConfigMissing'
244
+ ? '插件缺少生成内容所需的设置。'
245
+ : route.code === 'portableScopeUnavailable'
246
+ ? '至少一个所选 Skill 不适用于这个范围。'
247
+ : cliUnavailable
248
+ ? '没有找到兼容的 Skills CLI。'
249
+ : '候选 Skill 没有通过 Portable 安全校验。';
250
+ const blocked = route.code === 'requiredConfigMissing'
251
+ ? '缺少生成 Skill 所需的设置;没有调用 Skills CLI。'
252
+ : cliUnavailable
253
+ ? '当前不能开始安装;没有调用 Skills CLI。'
254
+ : route.code === 'portableScopeUnavailable'
255
+ ? '所选内容不能在这个范围完整生成;没有开始写入。'
256
+ : '候选内容没有通过安全校验;没有开始写入。';
257
+ const nextStep = route.code === 'requiredConfigMissing'
258
+ ? '完成插件设置,然后重新准备。'
259
+ : cliUnavailable
260
+ ? '安装或升级兼容的 Skills CLI,然后重新检查。'
261
+ : route.code === 'portableScopeUnavailable'
262
+ ? '返回 Skill 选择或更改范围,然后重新检查。'
263
+ : '修复来源内容或修改选择,然后重新检查。';
264
+ return {
265
+ severity: 'error',
266
+ title: route.code === 'portableScopeUnavailable' ? '所选范围不可用'
267
+ : cliUnavailable ? '暂时无法安装通用 Skills' : '无法准备通用 Skills',
268
+ userMessage,
269
+ impact: [
270
+ { state: 'blocked', message: blocked },
271
+ { state: 'preserved', message: '现有插件、Skills、设置和 Portable 草稿保持不变。' },
272
+ ],
273
+ nextStep,
274
+ technical: {
275
+ code: safeDiagnosticCode(route.diagnosticCode, 'portableValidationFailed'),
276
+ stage: cliUnavailable ? 'check-skills-cli' : 'prepare-portable',
277
+ ...(cliUnavailable ? { context: [{
278
+ key: 'compatibility',
279
+ value: 'Skills CLI >=1.5.23 <2.0.0',
280
+ }] } : {}),
281
+ checks: cliUnavailable
282
+ ? ['检查 PATH 中的 Skills CLI 版本。', '安装兼容版本后运行“重新检查”。']
283
+ : ['检查插件设置、所选范围和 Portable 来源内容。', '修复后运行“重新检查”。'],
284
+ },
285
+ };
286
+ }
287
+ function configUnavailableDiagnostic(route) {
288
+ return {
289
+ severity: 'error',
290
+ title: 'Manifest 或来源不可用',
291
+ userMessage: '当前无法取得可信的字段定义,因此不能验证新值。',
292
+ impact: [
293
+ { state: 'blocked', message: '恢复可信字段定义前,不能编辑或重置这个插件的设置。' },
294
+ { state: 'preserved', message: '已保存的设置保持不变。' },
295
+ ],
296
+ nextStep: '重新检查来源,然后再次打开设置。',
297
+ technical: {
298
+ code: safeDiagnosticCode(route.diagnosticCode, 'configUnavailable'),
299
+ stage: safeDiagnosticCode(route.diagnosticStage, 'read-config'),
300
+ context: [
301
+ { key: 'pluginId', value: route.pluginId },
302
+ ...(route.layer ? [{ key: 'scope', value: route.layer.scope }] : []),
303
+ ],
304
+ checks: ['恢复插件来源和可信 Manifest。', '重新检查来源后再次打开设置。'],
305
+ },
306
+ };
307
+ }
308
+ function sourceDiagnostic(sourceId, state, detail) {
309
+ const degraded = state === 'degraded';
310
+ const unavailable = state === 'unavailable' || state === 'error';
311
+ if (!degraded && !unavailable)
312
+ return undefined;
313
+ return {
314
+ severity: degraded ? 'warning' : 'error',
315
+ title: degraded ? '远端 Catalog 检查失败,正在使用缓存' : 'Catalog 来源不可用',
316
+ userMessage: degraded
317
+ ? '远端读取没有成功。当前使用匹配且已验证的缓存数据。'
318
+ : '当前无法读取这个来源,并且没有可用缓存数据。',
319
+ impact: degraded
320
+ ? [
321
+ { state: 'degraded', message: '数据可能不是最新;匹配缓存中的插件仍可浏览和安装。' },
322
+ { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
323
+ ]
324
+ : [
325
+ { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
326
+ { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
327
+ ],
328
+ nextStep: degraded
329
+ ? '重新检查来源;在修复前也可以继续使用匹配缓存。'
330
+ : '重新检查来源;如仍失败,请编辑来源 URL、路径或 ref。',
331
+ technical: {
332
+ code: safeDiagnosticCode(detail?.errorCode, degraded ? 'catalogSourceDegraded' : 'catalogSourceUnavailable'),
333
+ stage: 'resolve-source',
334
+ sourceId,
335
+ ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
336
+ ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
337
+ lastGood: degraded ? 'available' : 'unavailable',
338
+ ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
339
+ checks: ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。'],
340
+ },
341
+ };
342
+ }
343
+ function terminalTextWidth(value) {
344
+ return [...value].reduce((width, character) => width + (character.codePointAt(0) > 0xff ? 2 : 1), 0);
345
+ }
346
+ function SemanticRows(props) {
172
347
  const offset = props.offset ?? 0;
173
348
  const actions = props.limit === undefined
174
- ? props.actions
349
+ ? props.actions.slice(offset)
175
350
  : props.actions.slice(offset, offset + props.limit);
176
351
  return (_jsx(Box, { flexDirection: "column", children: actions.map((action, visibleIndex) => {
177
352
  const index = visibleIndex + offset;
178
- const focused = index === props.focus && !action.disabled;
179
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, backgroundColor: focused ? tuiTheme.colors.selectionBackground : undefined, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { bold: focused, color: action.disabled ? tuiTheme.colors.muted : statusColor(action.status), wrap: "truncate-end", children: [focused ? '› ' : ' ', action.label] }), action.meta ? _jsx(Text, { color: statusColor(action.status) ?? tuiTheme.colors.muted, children: action.meta }) : null] }), action.description || action.disabled
180
- ? _jsxs(Text, { color: tuiTheme.colors.muted, wrap: "truncate-end", children: [' ', action.disabled ?? action.description] })
181
- : null] }, `${action.label}:${index}`));
353
+ const rowProps = {
354
+ title: action.label,
355
+ description: action.description,
356
+ meta: action.meta ?? (props.compact ? action.disabled : undefined),
357
+ status: action.status,
358
+ disabled: action.disabled,
359
+ focused: index === props.focus,
360
+ compact: props.compact,
361
+ };
362
+ if (props.kind === 'section')
363
+ return _jsx(SectionRow, { ...rowProps }, `${action.label}:${index}`);
364
+ if (props.kind === 'choice') {
365
+ return _jsx(ChoiceRow, { ...rowProps, selected: Boolean(action.selected) }, `${action.label}:${index}`);
366
+ }
367
+ if (props.kind === 'toggle') {
368
+ return _jsx(ToggleRow, { ...rowProps, selected: Boolean(action.selected) }, `${action.label}:${index}`);
369
+ }
370
+ if (props.kind === 'action')
371
+ return _jsx(ActionRow, { ...rowProps }, `${action.label}:${index}`);
372
+ return _jsx(TaskRow, { ...rowProps }, `${action.label}:${index}`);
182
373
  }) }));
183
374
  }
184
- function PortableSkillRows(props) {
185
- const end = props.limit === undefined ? props.actions.length : props.offset + props.limit;
186
- const indexes = props.actions.map((_, index) => index)
187
- .filter((index) => index >= props.offset && index < end);
188
- return _jsx(Box, { flexDirection: "column", children: indexes.map((index) => {
189
- const candidate = props.candidates[index];
190
- const previous = props.candidates[index - 1];
191
- const showHeading = candidate && (index === props.offset
192
- || candidate.providerPluginId !== previous?.providerPluginId);
193
- const heading = candidate?.relation === 'requested'
194
- ? '当前插件'
195
- : candidate ? `来自依赖插件 · ${candidate.providerDisplayName}` : undefined;
196
- return _jsxs(Box, { flexDirection: "column", children: [showHeading && heading ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: heading }) : null, _jsx(ActionRows, { actions: props.actions, focus: props.focus, offset: index, limit: 1 })] }, index);
197
- }) });
375
+ function PluginRows(props) {
376
+ return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
377
+ .map((row, visibleIndex) => (_jsx(PluginListRow, { name: row.name, sourceLabel: row.sourceLabel, description: row.description, placementSummary: row.placementSummary, statusLabel: row.statusLabel, status: row.status, focused: props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
198
378
  }
199
- function Bordered(props) {
200
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tuiTheme.colors.border, paddingX: 1, children: [props.title ? _jsx(Text, { bold: true, children: props.title }) : null, props.children] }));
379
+ function IssueTechnicalDetails(props) {
380
+ const visible = props.details.slice(0, props.compact ? 1 : 2);
381
+ if (visible.length === 0)
382
+ return null;
383
+ const hidden = props.details.length - visible.length;
384
+ return _jsx(TechnicalDetails, { children: visible.flatMap((detail, index) => [
385
+ _jsx(StaticRow, { title: detail.code, compact: true }, `${detail.code}:${index}:code`),
386
+ _jsx(StaticRow, { title: detail.message, meta: index === visible.length - 1 && hidden > 0 ? `另有 ${hidden} 项` : undefined, compact: true }, `${detail.code}:${index}:message`),
387
+ ]) });
201
388
  }
202
389
  function SourceTabs(props) {
203
- const tabs = [{ id: 'all', label: '全部', count: props.controller.listPlugins({ mode: props.mode }).length },
390
+ const filter = { mode: props.mode, query: props.query, installFilter: props.installFilter };
391
+ const tabs = [{ id: 'all', label: '全部', count: props.controller.listPlugins(filter).length },
204
392
  ...props.controller.listSources().map((source) => ({
205
393
  ...source,
206
- count: props.controller.listPlugins({ mode: props.mode, sourceId: source.id }).length,
394
+ count: props.controller.listPlugins({ ...filter, sourceId: source.id }).length,
207
395
  }))];
208
396
  const index = Math.max(0, tabs.findIndex((tab) => tab.id === props.current));
209
- if (props.compact) {
397
+ const fullText = `来源 ${tabs.map((tab) => {
398
+ const state = 'state' in tab ? tab.state : 'ready';
399
+ return `${tab.id === props.current ? '[' : ''}${tab.label}${state === 'error' ? ' !' : ` ${tab.count ?? ''}`}${tab.id === props.current ? ']' : ''} `;
400
+ }).join('')}`;
401
+ if (props.compact || terminalTextWidth(fullText) > props.columns) {
210
402
  const tab = tabs[index];
211
- return _jsxs(Text, { children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
403
+ return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
212
404
  }
213
- return (_jsxs(Text, { children: ["\u6765\u6E90 ", tabs.map((tab) => {
405
+ return (_jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", tabs.map((tab) => {
214
406
  const active = tab.id === props.current;
215
407
  const state = 'state' in tab ? tab.state : 'ready';
216
408
  return (_jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent
@@ -218,6 +410,31 @@ function SourceTabs(props) {
218
410
  : state === 'cache' ? tuiTheme.statusColors.warning : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, state === 'error' ? ' !' : ` ${tab.count ?? ''}`, active ? ']' : '', ' '] }, tab.id));
219
411
  })] }));
220
412
  }
413
+ function configTabs(page) {
414
+ return [
415
+ ...page.groups.map((group) => ({
416
+ id: group.id,
417
+ label: group.title,
418
+ count: group.fields.length,
419
+ kind: 'group',
420
+ })),
421
+ ...(page.presets.length > 0
422
+ ? [{ id: '@presets', label: '快速方案', count: page.presets.length, kind: 'presets' }]
423
+ : []),
424
+ ];
425
+ }
426
+ function ConfigGroupTabs(props) {
427
+ const index = Math.max(0, props.tabs.findIndex((tab) => tab.id === props.current));
428
+ const fullText = `分组 ${props.tabs.map((tab) => `${tab.id === props.current ? '[' : ''}${tab.label} ${tab.count}${tab.id === props.current ? ']' : ''} `).join('')}`;
429
+ if (props.compact || terminalTextWidth(fullText) > props.columns) {
430
+ const tab = props.tabs[index];
431
+ return _jsxs(Text, { wrap: "truncate-end", children: ["\u5206\u7EC4 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, " ", tab.count, "]"] }), " ", index + 1, " / ", props.tabs.length, " \u2190 \u2192"] });
432
+ }
433
+ return (_jsxs(Text, { wrap: "truncate-end", children: ["\u5206\u7EC4 ", props.tabs.map((tab) => {
434
+ const active = tab.id === props.current;
435
+ return _jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, " ", tab.count, active ? ']' : '', ' '] }, tab.id);
436
+ }), "\u2190 \u2192"] }));
437
+ }
221
438
  function placementStatus(placement) {
222
439
  if (placement.state === 'notInstalled') {
223
440
  return { value: '未添加', status: 'muted', description: '选择使用位置后添加' };
@@ -241,15 +458,14 @@ function dependencyStatus(item) {
241
458
  return { value: '不可用', status: 'error' };
242
459
  }
243
460
  function dependencyPlacementLabel(item) {
244
- const placement = item.placements.find((entry) => entry.state === 'installed');
245
- if (placement?.state === 'installed') {
246
- const state = placement.health === 'ready' ? '可用'
247
- : placement.health === 'incomplete' ? '需要处理' : '不可用';
248
- return `${scopeLabel(placement.scope)} · ${state}`;
249
- }
250
- if (item.placements.some((entry) => entry.state === 'scopeConflict'))
251
- return '位置冲突';
252
- return dependencyStatus(item).value;
461
+ const aggregate = dependencyStatus(item);
462
+ if (item.aggregateStatus !== 'ready')
463
+ return aggregate.value;
464
+ const ready = item.placements.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
465
+ const scopes = new Set(ready.map((entry) => entry.state === 'installed' ? entry.scope : undefined));
466
+ return scopes.size === 1 && ready[0]?.state === 'installed'
467
+ ? `${scopeLabel(ready[0].scope)} · ${aggregate.value}`
468
+ : aggregate.value;
253
469
  }
254
470
  function dependentTargetLabel(item) {
255
471
  const consumer = item.placementReferences?.[0]?.consumer;
@@ -330,7 +546,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
330
546
  if (route.name === 'installPlacement') {
331
547
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D03' : 'P05';
332
548
  }
333
- if (route.name === 'installTools') {
549
+ if (route.name === 'installTargets') {
334
550
  if (route.view === 'blocked')
335
551
  return 'D16';
336
552
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D04' : 'P06';
@@ -349,6 +565,8 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
349
565
  return 'P14';
350
566
  return 'P16';
351
567
  }
568
+ if (route.name === 'reviewPreparing')
569
+ return 'P07';
352
570
  const dependencyOperation = snapshot.activeReview?.operationKind === 'pluginWrite'
353
571
  && Boolean(snapshot.activeReview.changes.some((change) => change.group));
354
572
  if (route.name === 'review' && dependencyOperation) {
@@ -357,9 +575,14 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
357
575
  }
358
576
  if (route.name === 'progress' && snapshot.progressTargets.length > 0)
359
577
  return 'D06';
360
- if (route.name === 'result' && (snapshot.result?.state === 'succeeded' || snapshot.result?.state === 'partial')
361
- && snapshot.result.summary.targets?.some((target) => target.relation)) {
362
- return snapshot.result.state === 'partial' ? 'D08' : 'D07';
578
+ if (route.name === 'result' && snapshot.result) {
579
+ const dependencyTargets = snapshot.result.state === 'succeeded' || snapshot.result.state === 'partial'
580
+ ? snapshot.result.summary.targets
581
+ : snapshot.result.state === 'blocked' && snapshot.result.code === 'operationFailed'
582
+ ? snapshot.result.summary?.targets : undefined;
583
+ if (dependencyTargets?.some((target) => target.relation)) {
584
+ return snapshot.result.state === 'succeeded' ? 'D07' : 'D08';
585
+ }
363
586
  }
364
587
  if (route.name === 'review' && snapshot.activeReview?.originIssue)
365
588
  return 'I10';
@@ -406,14 +629,11 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
406
629
  if (route.name === 'configLayer')
407
630
  return 'C01';
408
631
  if (route.name === 'configFields')
409
- return route.layer.scope === 'effective'
410
- ? 'C02' : route.groupId ? 'C04' : 'C03';
632
+ return route.layer.scope === 'effective' ? 'C02' : 'C03';
411
633
  if (route.name === 'configFieldEditor')
412
634
  return {
413
635
  enum: 'C05', boolean: 'C06', number: 'C07', string: 'C08', path: 'C09', sensitive: 'C10',
414
636
  }[route.editor];
415
- if (route.name === 'configPresets')
416
- return 'C11';
417
637
  if (route.name === 'configUnavailable')
418
638
  return 'C17';
419
639
  if (route.name === 'sourceList')
@@ -467,10 +687,14 @@ export function ConsoleAppV4(props) {
467
687
  const [scrollOffset, setScrollOffset] = useState(0);
468
688
  const [searchMode, setSearchMode] = useState(false);
469
689
  const [query, setQuery] = useState('');
690
+ const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
470
691
  const [scope, setScope] = useState('user');
471
- const [tools, setTools] = useState([]);
692
+ const [selectedTargets, setSelectedTargets] = useState([]);
693
+ const [targetSelections, setTargetSelections] = useState({});
694
+ const [preferredTarget, setPreferredTarget] = useState();
472
695
  const [technicalRouteKey, setTechnicalRouteKey] = useState();
473
696
  const [exitAfterBusy, setExitAfterBusy] = useState(false);
697
+ const [exitAfterPreparation, setExitAfterPreparation] = useState(false);
474
698
  const [, setClockTick] = useState(0);
475
699
  const [dirty, setDirty] = useState(false);
476
700
  const [configPage, setConfigPage] = useState();
@@ -481,7 +705,6 @@ export function ConsoleAppV4(props) {
481
705
  const [inputValue, setInputValue] = useState('');
482
706
  const [inputError, setInputError] = useState();
483
707
  const [configActionError, setConfigActionError] = useState();
484
- const [configResetAvailability, setConfigResetAvailability] = useState();
485
708
  const [actionError, setActionError] = useState();
486
709
  const [sourceDraft, setSourceDraft] = useState();
487
710
  const [sourceInput, setSourceInput] = useState('');
@@ -490,6 +713,8 @@ export function ConsoleAppV4(props) {
490
713
  const [sourceDependenciesExpanded, setSourceDependenciesExpanded] = useState(false);
491
714
  const [portableAgentInput, setPortableAgentInput] = useState('');
492
715
  const [draftOwner, setDraftOwner] = useState();
716
+ const [pendingPresetId, setPendingPresetId] = useState();
717
+ const dynamicMenuFocus = useRef(undefined);
493
718
  const exitSent = useRef(false);
494
719
  const operationBusy = useRef(false);
495
720
  const route = snapshot.router.current.route;
@@ -504,6 +729,8 @@ export function ConsoleAppV4(props) {
504
729
  ? props.controller.listUserIssues().find((entry) => entry.issueId === route.issueId)
505
730
  : undefined;
506
731
  const busy = route.name === 'progress';
732
+ const preparationRunning = route.name === 'reviewPreparing'
733
+ && snapshot.reviewPreparation?.state === 'running';
507
734
  useEffect(() => {
508
735
  if (!busy)
509
736
  return;
@@ -540,6 +767,14 @@ export function ConsoleAppV4(props) {
540
767
  : state === 'stale' ? 'stale' : 'completed',
541
768
  });
542
769
  }, [exitAfterBusy, snapshot.result]);
770
+ useEffect(() => {
771
+ if (!exitAfterPreparation || preparationRunning)
772
+ return;
773
+ requestExit({
774
+ code: snapshot.reviewPreparation?.state === 'failed' ? 1 : 0,
775
+ reason: snapshot.reviewPreparation?.state === 'failed' ? 'blocked' : 'user',
776
+ });
777
+ }, [exitAfterPreparation, preparationRunning, snapshot.reviewPreparation?.state]);
543
778
  useEffect(() => {
544
779
  if (route.name !== 'result'
545
780
  || !snapshot.result
@@ -569,7 +804,6 @@ export function ConsoleAppV4(props) {
569
804
  }, [route.name]);
570
805
  const configRoute = route.name === 'configFields'
571
806
  || route.name === 'configFieldEditor'
572
- || route.name === 'configPresets'
573
807
  ? route
574
808
  : undefined;
575
809
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
@@ -578,7 +812,6 @@ export function ConsoleAppV4(props) {
578
812
  return;
579
813
  const abort = new AbortController();
580
814
  const layers = [
581
- { key: 'effective', layer: { scope: 'effective', projectLayer: 'shared' } },
582
815
  { key: 'user', layer: { scope: 'user', projectLayer: 'shared' } },
583
816
  { key: 'project', layer: { scope: 'project', projectLayer: 'shared' } },
584
817
  { key: 'local', layer: { scope: 'project', projectLayer: 'local' } },
@@ -616,14 +849,19 @@ export function ConsoleAppV4(props) {
616
849
  return;
617
850
  setConfigPage(page);
618
851
  })
619
- .catch(() => {
852
+ .catch((error) => {
620
853
  if (!abort.signal.aborted) {
621
854
  const layer = configRoute.layer.scope === 'effective'
622
855
  ? undefined
623
856
  : { scope: configRoute.layer.scope, projectLayer: configRoute.layer.projectLayer };
857
+ const code = typeof error === 'object' && error !== null && 'code' in error
858
+ ? safeDiagnosticCode(error.code, 'configUnavailable')
859
+ : 'configUnavailable';
624
860
  props.controller.replace({
625
861
  name: 'configUnavailable',
626
862
  pluginId: expectedPluginId,
863
+ diagnosticCode: code,
864
+ diagnosticStage: 'read-config',
627
865
  ...(layer ? { layer } : {}),
628
866
  });
629
867
  }
@@ -631,22 +869,16 @@ export function ConsoleAppV4(props) {
631
869
  return () => abort.abort();
632
870
  }, [configLoadKey, configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
633
871
  useEffect(() => {
634
- if (route.name !== 'configUnavailable' || !route.layer) {
635
- setConfigResetAvailability(undefined);
872
+ if (route.name !== 'configFields' || !currentConfigPage)
636
873
  return;
637
- }
638
- const abort = new AbortController();
639
- void props.controller.getConfigResetAvailability(route.pluginId, route.layer)
640
- .then((availability) => {
641
- if (!abort.signal.aborted)
642
- setConfigResetAvailability(availability);
643
- })
644
- .catch(() => {
645
- if (!abort.signal.aborted)
646
- setConfigResetAvailability({ exists: false, sourceIdentityPresent: false });
647
- });
648
- return () => abort.abort();
649
- }, [props.controller, route.name, route.name === 'configUnavailable' ? route.pluginId : '', route.name === 'configUnavailable' ? JSON.stringify(route.layer) : '']);
874
+ const tabs = configTabs(currentConfigPage);
875
+ if (tabs.length === 0)
876
+ return;
877
+ const current = snapshot.router.current.context.configGroupId;
878
+ if (tabs.some((tab) => tab.id === current))
879
+ return;
880
+ props.controller.updatePageContext({ configGroupId: tabs[0].id });
881
+ }, [props.controller, route.name, currentConfigPage, snapshot.router.current.context.configGroupId]);
650
882
  useEffect(() => {
651
883
  let count;
652
884
  if (route.name === 'sourceList')
@@ -660,18 +892,19 @@ export function ConsoleAppV4(props) {
660
892
  mode: route.mode,
661
893
  ...(route.sourceId ? { sourceId: route.sourceId } : {}),
662
894
  ...(query ? { query } : {}),
895
+ installFilter,
663
896
  }).length;
664
897
  count = itemCount || 1;
665
898
  }
666
899
  if (count === undefined)
667
900
  return;
668
901
  setFocus((value) => Math.min(value, Math.max(0, count - 1)));
669
- const maximumVisible = policy.mode === 'compact' ? 4 : 6;
670
- setScrollOffset((value) => Math.min(value, Math.max(0, count - maximumVisible)));
902
+ setScrollOffset((value) => Math.min(value, Math.max(0, count - 1)));
671
903
  }, [
672
904
  policy.mode,
673
905
  props.controller,
674
906
  query,
907
+ installFilter,
675
908
  route.name,
676
909
  route.name === 'pluginList' ? route.mode : '',
677
910
  route.name === 'pluginList' ? route.sourceId : '',
@@ -713,51 +946,33 @@ export function ConsoleAppV4(props) {
713
946
  return () => abort.abort();
714
947
  }, [props.controller, route.name, route.name === 'sourceRemoveBlocked' ? route.sourceId : '']);
715
948
  const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchMode);
949
+ const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
950
+ ? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
951
+ : [];
952
+ const supportsTechnicalDetails = Boolean(route.name === 'progress' && snapshot.progress.messages.length > 0)
953
+ || Boolean(route.name === 'review' && snapshot.activeReview?.technicalDetails?.length)
954
+ || Boolean(route.name === 'issueDetail' && issue
955
+ && issue.diagnostic.technical)
956
+ || Boolean(route.name === 'pluginDependencies' && plugin)
957
+ || portableVariantCandidates.length > 0
958
+ || Boolean(route.name === 'portableDependencies' && plugin
959
+ && props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependencies.length)
960
+ || Boolean(route.name === 'portableBlocker' && plugin)
961
+ || Boolean(route.name === 'configUnavailable' && plugin)
962
+ || Boolean(route.name === 'result' && snapshot.result && 'operationKind' in snapshot.result
963
+ && snapshot.result.operationKind === 'portableInstall');
964
+ const canOpenHelp = !tooSmall
965
+ && !isCharacterInputMode(route, searchMode)
966
+ && !(route.name === 'installTargets' && route.view !== 'blocked');
716
967
  useEffect(() => {
717
968
  props.onScreenChange?.(screenId);
718
969
  }, [props.onScreenChange, screenId]);
719
- const selectedPlatforms = (candidate, selectedScope) => (candidate?.supportedPlatforms ?? []).map((platform) => {
720
- const placement = props.controller.getPlatformPlacement(candidate.pluginId, platform);
721
- if (!placement)
722
- return { platform, disabled: '当前没有可信的平台能力信息' };
723
- if (placement.state === 'scopeConflict')
724
- return { platform, disabled: '安装位置冲突;请先处理问题' };
725
- if (placement.state === 'installed') {
726
- return { platform, disabled: `已安装到${scopeLabel(placement.scope)};请先移除现有安装` };
727
- }
728
- if (!placement.supportedScopes.includes(selectedScope))
729
- return { platform, disabled: '不支持所选位置' };
730
- const overview = props.controller.getPluginDependencyOverview(candidate.pluginId);
731
- for (const dependency of overview?.dependencies ?? []) {
732
- const dependentPlugin = props.controller.getPluginOverview(dependency.pluginId);
733
- if (!dependentPlugin || !dependentPlugin.supportedPlatforms.includes(platform)) {
734
- return { platform, disabled: `${dependency.displayName} 不支持${platformLabel(platform)}` };
735
- }
736
- const dependencyPlacement = props.controller.getPlatformPlacement(dependency.pluginId, platform);
737
- if (dependencyPlacement?.state === 'scopeConflict') {
738
- return { platform, disabled: `${dependency.displayName} 的安装位置冲突` };
739
- }
740
- if (dependencyPlacement?.state === 'installed') {
741
- if (dependencyPlacement.health !== 'ready') {
742
- return { platform, disabled: `${dependency.displayName} 需要先处理` };
743
- }
744
- if (selectedScope === 'user' && dependencyPlacement.scope === 'project') {
745
- return { platform, disabled: `${dependency.displayName} 只在当前项目可用` };
746
- }
747
- }
748
- if (dependencyPlacement?.state === 'notInstalled'
749
- && !dependencyPlacement.supportedScopes.includes(selectedScope)) {
750
- return { platform, disabled: `${dependency.displayName} 不支持所选位置` };
751
- }
752
- }
753
- return { platform };
754
- });
755
970
  const dependencyOverview = (candidate) => candidate ? props.controller.getPluginDependencyOverview(candidate.pluginId) : undefined;
756
- const installIntent = (candidate) => ({
971
+ const installIntent = (candidate, targets = selectedTargets) => ({
757
972
  kind: 'pluginWrite',
758
973
  action: 'install',
759
974
  pluginId: candidate.pluginId,
760
- platform: tools.length === 2 ? 'both' : tools[0],
975
+ platform: targets.length === 2 ? 'both' : targets[0],
761
976
  scope,
762
977
  });
763
978
  let title = '管理你的 Agent 工作流插件';
@@ -767,8 +982,43 @@ export function ConsoleAppV4(props) {
767
982
  let help = '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出';
768
983
  let content;
769
984
  let actions = [];
985
+ let pluginRows = [];
986
+ let visibleConfigTabs = [];
987
+ let activeConfigTabId;
988
+ const dynamicMenu = plugin && route.name === 'pluginPreview'
989
+ ? buildPluginPreviewActions(plugin)
990
+ : plugin && route.name === 'pluginDetail'
991
+ ? buildPluginDetailActions(plugin)
992
+ : plugin && route.name === 'platform'
993
+ ? buildPlatformActions(plugin, route.platform)
994
+ : undefined;
995
+ const dynamicActionIds = dynamicMenu?.map(pluginMenuActionId);
996
+ const dynamicActionSignature = dynamicActionIds?.join('\0');
997
+ const previousDynamicFocus = dynamicMenuFocus.current;
998
+ const retainedDynamicFocus = dynamicActionIds
999
+ && previousDynamicFocus?.routeKey === routeKey
1000
+ && previousDynamicFocus.actionIds !== dynamicActionSignature
1001
+ && previousDynamicFocus.focusedActionId !== undefined
1002
+ ? dynamicActionIds.indexOf(previousDynamicFocus.focusedActionId)
1003
+ : -1;
1004
+ const effectiveFocus = dynamicActionIds
1005
+ && previousDynamicFocus?.routeKey === routeKey
1006
+ && previousDynamicFocus.actionIds !== dynamicActionSignature
1007
+ ? retainedDynamicFocus >= 0 ? retainedDynamicFocus : 0
1008
+ : focus;
1009
+ const pageActionBudget = (staticRows = 0) => contentRowBudget({
1010
+ terminalRows: size.rows,
1011
+ description: Boolean(description),
1012
+ compact: policy.mode === 'compact',
1013
+ staticRows,
1014
+ });
1015
+ const responsiveBudget = (compactRows, standardRows) => {
1016
+ const baseline = policy.mode === 'compact' ? 18 : 24;
1017
+ const base = policy.mode === 'compact' ? compactRows : standardRows;
1018
+ return base + Math.max(0, size.rows - baseline);
1019
+ };
770
1020
  const go = (next) => {
771
- props.controller.updatePageContext({ focusIndex: focus, scrollOffset, query, sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined });
1021
+ props.controller.updatePageContext({ focusIndex: focus, scrollOffset, query, installFilter, sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined });
772
1022
  props.controller.push(next);
773
1023
  setFocus(0);
774
1024
  setScrollOffset(0);
@@ -776,6 +1026,21 @@ export function ConsoleAppV4(props) {
776
1026
  setShowTechnical(false);
777
1027
  setActionError(undefined);
778
1028
  };
1029
+ const beginConfigFlow = (pluginId) => {
1030
+ props.controller.updatePageContext({
1031
+ focusIndex: focus,
1032
+ scrollOffset,
1033
+ query,
1034
+ installFilter,
1035
+ sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
1036
+ });
1037
+ props.controller.beginConfigFlow(pluginId);
1038
+ setFocus(0);
1039
+ setScrollOffset(0);
1040
+ setSearchMode(false);
1041
+ setShowTechnical(false);
1042
+ setActionError(undefined);
1043
+ };
779
1044
  const back = () => {
780
1045
  if (route.name === 'result')
781
1046
  props.controller.leaveResult();
@@ -785,6 +1050,7 @@ export function ConsoleAppV4(props) {
785
1050
  setFocus(restored.focusIndex ?? 0);
786
1051
  setScrollOffset(restored.scrollOffset ?? 0);
787
1052
  setQuery(restored.query ?? '');
1053
+ setInstallFilter(restored.installFilter ?? 'all');
788
1054
  setSearchMode(Boolean(restored.query));
789
1055
  setActionError(undefined);
790
1056
  };
@@ -794,15 +1060,23 @@ export function ConsoleAppV4(props) {
794
1060
  setFocus(restored.focusIndex ?? 0);
795
1061
  setScrollOffset(restored.scrollOffset ?? 0);
796
1062
  setQuery(restored.query ?? '');
1063
+ setInstallFilter(restored.installFilter ?? 'all');
797
1064
  setSearchMode(Boolean(restored.query));
798
1065
  setShowTechnical(false);
799
1066
  setActionError(undefined);
800
1067
  };
801
- const beginReview = async (intent) => {
1068
+ const beginReview = (intent) => {
802
1069
  const underlyingResultRoute = route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
803
1070
  ? snapshot.result.returnRoute
804
1071
  : undefined;
805
- await props.controller.buildOperationReview(intent, underlyingResultRoute);
1072
+ props.controller.updatePageContext({
1073
+ focusIndex: focus,
1074
+ scrollOffset,
1075
+ query,
1076
+ installFilter,
1077
+ sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
1078
+ });
1079
+ props.controller.startReviewPreparation(intent, underlyingResultRoute);
806
1080
  setFocus(0);
807
1081
  setScrollOffset(0);
808
1082
  };
@@ -826,10 +1100,11 @@ export function ConsoleAppV4(props) {
826
1100
  }
827
1101
  }
828
1102
  };
829
- const beginPortableReview = async (pluginId, selectedScope) => {
1103
+ const beginPortableReview = (pluginId, selectedScope) => {
830
1104
  props.controller.setPortableScope(selectedScope);
831
1105
  try {
832
- await props.controller.buildPortableInstallReview();
1106
+ props.controller.updatePageContext({ focusIndex: focus, scrollOffset });
1107
+ props.controller.startPortableInstallReviewPreparation();
833
1108
  setFocus(0);
834
1109
  setActionError(undefined);
835
1110
  }
@@ -895,9 +1170,11 @@ export function ConsoleAppV4(props) {
895
1170
  const selectedPlatform = action.platform ?? selectedIssue.platform;
896
1171
  const writeAction = action.writeAction ?? (action.id === 'reinstall'
897
1172
  ? 'reinstall'
898
- : action.id === 'remove' || action.id === 'keepUserScope' || action.id === 'keepProjectScope'
899
- ? 'uninstall'
900
- : undefined);
1173
+ : action.id === 'cleanup'
1174
+ ? 'cleanup'
1175
+ : action.id === 'remove' || action.id === 'keepUserScope' || action.id === 'keepProjectScope'
1176
+ ? 'uninstall'
1177
+ : undefined);
901
1178
  if (!selectedScope || !selectedPlatform || !writeAction)
902
1179
  return undefined;
903
1180
  return {
@@ -989,6 +1266,15 @@ export function ConsoleAppV4(props) {
989
1266
  return field.value ? '开启' : '关闭';
990
1267
  return `${field.displayValue}${field.unit && field.displayValue !== '未设置' ? ` ${field.unit}` : ''}`;
991
1268
  };
1269
+ const effectiveFieldDisplay = (field) => {
1270
+ if (field.type === 'enum' && field.effectiveValue !== undefined) {
1271
+ return field.valueLabels?.[String(field.effectiveValue)] ?? field.effectiveDisplayValue;
1272
+ }
1273
+ if (field.type === 'boolean' && field.effectiveValue !== undefined) {
1274
+ return field.effectiveValue ? '开启' : '关闭';
1275
+ }
1276
+ return `${field.effectiveDisplayValue}${field.unit && field.effectiveDisplayValue !== '未设置' ? ` ${field.unit}` : ''}`;
1277
+ };
992
1278
  const fieldEditor = (field) => {
993
1279
  if (field.sensitive)
994
1280
  return 'sensitive';
@@ -1028,6 +1314,88 @@ export function ConsoleAppV4(props) {
1028
1314
  return next;
1029
1315
  });
1030
1316
  };
1317
+ const configFieldTarget = (page, key) => {
1318
+ if (!key)
1319
+ return undefined;
1320
+ for (const group of page.groups) {
1321
+ const focusIndex = group.fields.findIndex((field) => field.key === key);
1322
+ if (focusIndex >= 0)
1323
+ return { groupId: group.id, focusIndex };
1324
+ }
1325
+ return undefined;
1326
+ };
1327
+ const applyConfigPreset = (page, presetId) => {
1328
+ const preset = page.presets.find((candidate) => candidate.id === presetId);
1329
+ if (!preset)
1330
+ return;
1331
+ const fields = new Map(page.groups.flatMap((group) => group.fields)
1332
+ .map((field) => [field.key, field]));
1333
+ setDraftUpdates((current) => {
1334
+ const next = { ...current };
1335
+ for (const [key, value] of Object.entries(preset.values)) {
1336
+ const field = fields.get(key);
1337
+ if (field?.configured && field.value === value)
1338
+ delete next[key];
1339
+ else
1340
+ next[key] = value;
1341
+ }
1342
+ const nextRemoves = draftRemoves.filter((key) => !Object.hasOwn(preset.values, key));
1343
+ setDraftRemoves(nextRemoves);
1344
+ setDirty(Object.keys(next).length + nextRemoves.length > 0);
1345
+ return next;
1346
+ });
1347
+ const target = configFieldTarget(page, Object.keys(preset.values)[0])
1348
+ ?? (page.groups[0] ? { groupId: page.groups[0].id, focusIndex: 0 } : undefined);
1349
+ if (target) {
1350
+ props.controller.updatePageContext({
1351
+ configGroupId: target.groupId,
1352
+ focusIndex: target.focusIndex,
1353
+ scrollOffset: 0,
1354
+ });
1355
+ setFocus(target.focusIndex);
1356
+ setScrollOffset(0);
1357
+ }
1358
+ setConfigActionError((preset.privateFieldTitles?.length ?? 0) > 0
1359
+ ? `${preset.privateFieldTitles.join('、')}需手动输入。`
1360
+ : undefined);
1361
+ };
1362
+ const openConfigLayerTarget = (layer) => {
1363
+ if (route.name !== 'configLayer')
1364
+ return;
1365
+ const field = route.fieldKey && configPage
1366
+ ? configPage.groups.flatMap((group) => group.fields).find((candidate) => candidate.key === route.fieldKey)
1367
+ : undefined;
1368
+ const fieldTarget = configPage ? configFieldTarget(configPage, route.fieldKey) : undefined;
1369
+ const preset = route.presetId && configPage
1370
+ ? configPage.presets.find((candidate) => candidate.id === route.presetId)
1371
+ : undefined;
1372
+ const presetTarget = preset
1373
+ ? configFieldTarget(configPage, Object.keys(preset.values)[0])
1374
+ : undefined;
1375
+ const target = fieldTarget ?? (route.groupId ? { groupId: route.groupId, focusIndex: 0 } : undefined) ?? presetTarget
1376
+ ?? (configPage?.groups[0] ? { groupId: configPage.groups[0].id, focusIndex: 0 } : undefined);
1377
+ const context = target
1378
+ ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 }
1379
+ : {};
1380
+ props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer }, context);
1381
+ if (route.presetId) {
1382
+ setPendingPresetId(route.presetId);
1383
+ setFocus(target?.focusIndex ?? 0);
1384
+ setScrollOffset(0);
1385
+ return;
1386
+ }
1387
+ if (field && route.fieldKey) {
1388
+ props.controller.push({
1389
+ name: 'configFieldEditor',
1390
+ pluginId: route.pluginId,
1391
+ layer,
1392
+ fieldKey: route.fieldKey,
1393
+ editor: fieldEditor(field),
1394
+ });
1395
+ }
1396
+ setFocus(0);
1397
+ setScrollOffset(0);
1398
+ };
1031
1399
  const configChanges = () => {
1032
1400
  const fields = currentConfigPage?.groups.flatMap((group) => group.fields) ?? [];
1033
1401
  return [...Object.keys(draftUpdates), ...draftRemoves.filter((key) => !Object.hasOwn(draftUpdates, key))]
@@ -1063,19 +1431,34 @@ export function ConsoleAppV4(props) {
1063
1431
  setConfigActionError('设置值未通过验证。请重新检查修改。');
1064
1432
  }
1065
1433
  };
1434
+ useEffect(() => {
1435
+ if (!pendingPresetId || route.name !== 'configFields'
1436
+ || route.layer.scope === 'effective' || !currentConfigPage)
1437
+ return;
1438
+ const presetId = pendingPresetId;
1439
+ setPendingPresetId(undefined);
1440
+ applyConfigPreset(currentConfigPage, presetId);
1441
+ }, [pendingPresetId, route.name, currentConfigPage]);
1442
+ useEffect(() => {
1443
+ if (!pendingPresetId)
1444
+ return;
1445
+ if (route.name === 'configFields' && route.layer.scope !== 'effective')
1446
+ return;
1447
+ setPendingPresetId(undefined);
1448
+ }, [pendingPresetId, route.name, route.name === 'configFields' ? route.layer.scope : '']);
1066
1449
  if (snapshot.phase === 'error') {
1067
1450
  breadcrumb = '启动失败';
1068
1451
  title = '无法读取插件状态';
1069
1452
  description = snapshot.loadError?.message ?? '当前不能安全显示插件状态。';
1070
1453
  help = 'q 退出 · Ctrl-C 退出';
1071
- content = _jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", snapshot.loadError?.code ?? 'statusLoadFailed'] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u6CA1\u6709\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C\u3002" })] });
1454
+ content = _jsx(StatusCard, { status: "error", title: snapshot.loadError?.code ?? 'statusLoadFailed', description: "\u6CA1\u6709\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C\u3002" });
1072
1455
  }
1073
1456
  else if (snapshot.phase !== 'ready') {
1074
1457
  breadcrumb = '启动';
1075
1458
  title = '正在检查插件状态';
1076
1459
  description = '单个来源失败不会阻止其他来源继续加载。';
1077
1460
  help = 'Ctrl-C 完成后退出';
1078
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tuiTheme.statusColors.success, children: "\u2713 \u8BFB\u53D6\u63D2\u4EF6\u6765\u6E90" }), _jsx(Text, { color: tuiTheme.colors.accent, children: "\u25CF \u68C0\u67E5\u5F53\u524D\u9879\u76EE" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u25CB \u6C47\u603B\u53EF\u7528\u64CD\u4F5C" })] }));
1461
+ content = _jsxs(StatusCard, { status: "info", title: "\u51C6\u5907\u63A7\u5236\u53F0", children: [_jsx(ProgressRow, { title: "\u8BFB\u53D6\u63D2\u4EF6\u6765\u6E90", state: "active" }), _jsx(ProgressRow, { title: "\u68C0\u67E5\u5F53\u524D\u9879\u76EE", state: "pending" }), _jsx(ProgressRow, { title: "\u6C47\u603B\u53EF\u7528\u64CD\u4F5C", state: "pending" })] });
1079
1462
  }
1080
1463
  else if (route.name === 'home') {
1081
1464
  const home = snapshot.viewModel.home;
@@ -1086,7 +1469,7 @@ export function ConsoleAppV4(props) {
1086
1469
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
1087
1470
  { label: '插件来源', description: '管理 Git 仓库和本地目录', meta: String(home.sourceCount), run: () => go({ name: 'sourceList' }) },
1088
1471
  ];
1089
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: tuiTheme.statusColors.success, children: ["\u2713 ", home.installedPlugins, " \u4E2A\u63D2\u4EF6\u53EF\u7528"] }), ' ', _jsxs(Text, { color: tuiTheme.statusColors.warning, children: ["! ", home.issueCount, " \u9879\u9700\u8981\u5904\u7406"] }), home.cachedSourceCount > 0 ? _jsxs(_Fragment, { children: [' ', _jsxs(Text, { color: tuiTheme.statusColors.warning, children: ["\u25D0 ", home.cachedSourceCount, " \u4E2A\u6765\u6E90\u4F7F\u7528\u7F13\u5B58"] })] }) : null] }), home.unavailableSourceCount > 0 ? _jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", home.unavailableSourceCount, " \u4E2A\u6765\u6E90\u4E0D\u53EF\u7528"] }) : null, _jsx(Box, { marginTop: 1, children: _jsx(ActionRows, { actions: actions, focus: focus }) })] }));
1472
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.cachedSourceCount > 0 ? _jsx(ResultRow, { title: `${home.cachedSourceCount} 个来源使用缓存`, status: "warning" }) : null, home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
1090
1473
  }
1091
1474
  else if (route.name === 'pluginList') {
1092
1475
  const sourceId = route.sourceId ?? 'all';
@@ -1094,37 +1477,59 @@ export function ConsoleAppV4(props) {
1094
1477
  mode: route.mode,
1095
1478
  ...(sourceId !== 'all' ? { sourceId } : {}),
1096
1479
  ...(query ? { query } : {}),
1480
+ installFilter,
1097
1481
  });
1098
1482
  breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
1099
1483
  title = breadcrumb;
1100
1484
  description = route.mode === 'browse' ? '选择希望 Agent 获得的能力。' : '查看状态,或选择一个插件进行管理。';
1101
1485
  context = `${items.length} 个插件`;
1102
- help = '↑↓ 移动 · Enter 查看 · ←→ 来源 · / 搜索 · Esc 返回';
1486
+ help = `↑↓ 移动 · Enter 查看 · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
1103
1487
  const sourceError = sourceId !== 'all' && props.controller.getSourceOverview(sourceId).source?.state === 'error';
1104
1488
  actions = sourceError
1105
1489
  ? [{ label: '查看来源问题', description: '没有可用缓存;当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
1106
- : items.map((item) => ({
1107
- label: item.displayName,
1108
- description: `${item.description}${sourceId === 'all' ? ` · ${item.sourceLabel}` : ''}`,
1109
- meta: statusText(item),
1110
- status: item.aggregateStatus === 'needsAttention' ? 'warning'
1111
- : item.aggregateStatus === 'installed' ? 'success'
1112
- : item.aggregateStatus === 'unavailable' ? 'error' : 'muted',
1113
- run: () => go(route.mode === 'browse'
1114
- ? { name: 'pluginPreview', pluginId: item.pluginId }
1115
- : { name: 'pluginDetail', pluginId: item.pluginId }),
1116
- }));
1490
+ : [];
1491
+ pluginRows = sourceError ? [] : items.map((item) => ({
1492
+ pluginId: item.pluginId,
1493
+ name: item.displayName,
1494
+ sourceLabel: item.sourceLabel,
1495
+ description: item.description,
1496
+ placementSummary: pluginPlacementSummary(item),
1497
+ statusLabel: statusText(item),
1498
+ status: pluginRowStatus(item),
1499
+ }));
1117
1500
  if (route.mode === 'installed' && items.length === 0 && !sourceError) {
1118
1501
  title = '还没有添加插件';
1119
1502
  description = `从已配置的 ${props.controller.listSources().length} 个来源中浏览可用能力。`;
1120
1503
  actions = [{ label: '浏览插件', description: '开始添加第一个插件', run: () => go({ name: 'pluginList', mode: 'browse' }) }];
1121
1504
  }
1122
- const viewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1123
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', mode: route.mode }), _jsxs(Text, { color: searchMode ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["/ ", searchMode ? query || '输入搜索内容' : '搜索名称、用途或来源…'] }), sourceError
1124
- ? _jsxs(Bordered, { children: [_jsxs(Text, { bold: true, children: [sourceId, " \u6682\u65F6\u65E0\u6CD5\u63D0\u4F9B\u63D2\u4EF6"] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u6CA1\u6709\u53EF\u7528\u7F13\u5B58\uFF0C\u56E0\u6B64\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })] })
1505
+ else if (route.mode === 'browse' && items.length === 0 && !sourceError) {
1506
+ actions = [{
1507
+ label: '没有匹配插件',
1508
+ description: installFilter === 'uninstalled' ? '当前来源和搜索条件下没有未安装插件' : '请调整来源或搜索条件',
1509
+ disabled: '没有可打开的插件',
1510
+ }];
1511
+ }
1512
+ const pluginListStaticRows = route.mode === 'browse' ? 3 : 2;
1513
+ const viewport = sourceError || pluginRows.length === 0
1514
+ ? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
1515
+ : rowViewport({
1516
+ rowHeights: pluginRows.map(() => 3),
1517
+ focus,
1518
+ requestedOffset: scrollOffset,
1519
+ rowBudget: pageActionBudget(pluginListStaticRows),
1520
+ });
1521
+ if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
1522
+ context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
1523
+ }
1524
+ content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchMode ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["/ ", searchMode ? query || '输入搜索内容' : '搜索名称、用途或来源…'] }), route.mode === 'browse'
1525
+ ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["[", installFilter === 'uninstalled' ? '未安装' : '全部', "]"] }), " f"] })
1526
+ : null, sourceError
1527
+ ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u6CA1\u6709\u53EF\u7528\u7F13\u5B58\uFF0C\u56E0\u6B64\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
1125
1528
  : route.mode === 'installed' && items.length === 0
1126
- ? _jsxs(Bordered, { children: [_jsx(Text, { bold: true, children: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })] })
1127
- : null, _jsx(ActionRows, { actions: actions, focus: focus, offset: viewport.offset, limit: viewport.limit })] }));
1529
+ ? _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
1530
+ : null, pluginRows.length > 0
1531
+ ? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport })
1532
+ : _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] }));
1128
1533
  }
1129
1534
  else if (route.name === 'pluginPreview' && plugin) {
1130
1535
  breadcrumb = `添加插件 / ${plugin.displayName}`;
@@ -1134,69 +1539,152 @@ export function ConsoleAppV4(props) {
1134
1539
  const overview = dependencyOverview(plugin);
1135
1540
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1136
1541
  const beginInstall = () => {
1137
- const initialScope = ['user', 'project'].find((candidateScope) => selectedPlatforms(plugin, candidateScope).some((entry) => !entry.disabled)) ?? scope;
1138
- const available = selectedPlatforms(plugin, initialScope)
1139
- .filter((entry) => !entry.disabled).map((entry) => entry.platform);
1542
+ const initialScope = ['user', 'project'].find((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
1543
+ .some((entry) => entry.compatible)) ?? scope;
1544
+ const available = props.controller.listInstallTargets(plugin.pluginId, initialScope)
1545
+ .filter((entry) => entry.compatible).map((entry) => entry.platform);
1140
1546
  setScope(initialScope);
1141
- setTools(available);
1547
+ setSelectedTargets([]);
1548
+ setTargetSelections({});
1549
+ setPreferredTarget(undefined);
1142
1550
  if (available.length > 0)
1143
1551
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
1144
1552
  else
1145
- go({ name: 'installTools', pluginId: plugin.pluginId, view: 'blocked' });
1553
+ go({ name: 'installTargets', pluginId: plugin.pluginId, view: 'blocked' });
1146
1554
  };
1147
- const portableAction = plugin.portableSkills
1148
- ? [{ label: '安装通用 Skills…', description: '选择 Skill、Agent 和使用范围', run: () => beginPortable(plugin.pluginId) }]
1149
- : [];
1150
- actions = hasDependencies && (overview?.blockers.length ?? 0) > 0
1151
- ? [
1152
- { label: '查看依赖问题', description: overview.blockers[0].explanation, status: 'warning', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
1153
- ...portableAction,
1154
- { label: '查看依赖关系', description: '查看直接、间接依赖和当前状态', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
1155
- { label: '查看插件设置定义', description: `${plugin.configFieldCount} 个配置字段`, run: () => go({ name: 'configLayer', pluginId: plugin.pluginId }) },
1156
- ]
1157
- : [
1158
- { label: '添加插件', description: '选择使用位置并确认变化', run: beginInstall },
1159
- ...portableAction,
1160
- ...(hasDependencies ? [{ label: '查看依赖关系', description: '查看直接、间接依赖和当前状态', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) }] : []),
1161
- { label: '查看插件设置定义', description: `${plugin.configFieldCount} 个配置字段`, run: () => go({ name: 'configLayer', pluginId: plugin.pluginId }) },
1162
- { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) },
1163
- ];
1555
+ const previewAction = (action) => {
1556
+ if (action.id === 'add')
1557
+ return { label: '添加插件', description: '选择使用位置并确认变化', run: beginInstall };
1558
+ if (action.id === 'manage')
1559
+ return { label: '管理插件', description: '查看已安装目标平台', run: () => go({ name: 'pluginDetail', pluginId: plugin.pluginId }) };
1560
+ if (action.id === 'repair')
1561
+ return { label: '管理并修复', description: '查看需要处理的安装位置', status: 'warning', run: () => go({ name: 'pluginDetail', pluginId: plugin.pluginId }) };
1562
+ if (action.id === 'portableSkills')
1563
+ return { label: '安装通用 Skills…', description: '选择 Skill 和 Agent 后交给 Skills CLI 安装', run: () => beginPortable(plugin.pluginId) };
1564
+ if (action.id === 'dependencies')
1565
+ return {
1566
+ label: (overview?.blockers.length ?? 0) > 0 ? '查看依赖问题' : '查看依赖关系',
1567
+ description: overview?.blockers[0]?.explanation ?? '查看直接、间接依赖和当前状态',
1568
+ ...((overview?.blockers.length ?? 0) > 0 ? { status: 'warning' } : {}),
1569
+ run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
1570
+ };
1571
+ if (action.id === 'settings')
1572
+ return { label: '查看插件设置', description: `${plugin.configFieldCount} 个配置字段及当前来源`, run: () => beginConfigFlow(plugin.pluginId) };
1573
+ if (action.id === 'sourceProblem')
1574
+ return { label: '查看来源问题', status: 'warning', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
1575
+ return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
1576
+ };
1577
+ const previewMenu = dynamicMenu;
1578
+ actions = previewMenu.map(previewAction);
1579
+ const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 2));
1580
+ const dependencyGroups = ['direct', 'transitive'].map((relation) => ({
1581
+ relation,
1582
+ dependencies: (overview?.dependencies.filter((entry) => entry.relation === relation) ?? [])
1583
+ .sort((left, right) => {
1584
+ const priority = (entry) => {
1585
+ const status = dependencyStatus(entry).status;
1586
+ return status === 'error' ? 0 : status === 'warning' ? 1 : 2;
1587
+ };
1588
+ return priority(left) - priority(right);
1589
+ }),
1590
+ }));
1591
+ const previewContentLines = size.rows - 6;
1592
+ const previewActionLines = previewViewport.limit ?? actions.length;
1593
+ const previewFixedStaticLines = 7 + (plugin.portableSkills && policy.mode !== 'compact' ? 5 : 0);
1594
+ let dependencyLineBudget = Math.max(dependencyGroups.filter((group) => group.dependencies.length > 0).length, previewContentLines - previewActionLines - previewFixedStaticLines);
1595
+ const visibleByRelation = new Map();
1596
+ for (const group of dependencyGroups) {
1597
+ if (group.dependencies.length === 0)
1598
+ continue;
1599
+ const firstItemLines = policy.mode === 'compact' || group.dependencies.length > 1 ? 1 : 2;
1600
+ const hiddenSummaryLines = group.dependencies.length > 1 ? 1 : 0;
1601
+ if (dependencyLineBudget >= firstItemLines + hiddenSummaryLines) {
1602
+ visibleByRelation.set(group.relation, 1);
1603
+ dependencyLineBudget -= firstItemLines + hiddenSummaryLines;
1604
+ }
1605
+ else {
1606
+ visibleByRelation.set(group.relation, 0);
1607
+ dependencyLineBudget -= 1;
1608
+ }
1609
+ }
1610
+ for (const group of dependencyGroups) {
1611
+ let visibleCount = visibleByRelation.get(group.relation) ?? 0;
1612
+ while (dependencyLineBudget > 0 && visibleCount < group.dependencies.length) {
1613
+ visibleCount += 1;
1614
+ dependencyLineBudget -= 1;
1615
+ }
1616
+ visibleByRelation.set(group.relation, visibleCount);
1617
+ }
1164
1618
  const summaryRows = (relation) => {
1165
- const dependencies = overview?.dependencies.filter((entry) => entry.relation === relation) ?? [];
1166
- const visible = dependencies.slice(0, 3);
1619
+ const dependencies = dependencyGroups.find((group) => group.relation === relation).dependencies;
1620
+ const visibleCount = policy.mode === 'compact' ? Math.min(1, dependencies.length)
1621
+ : visibleByRelation.get(relation) ?? 0;
1622
+ const visible = dependencies.slice(0, visibleCount);
1623
+ const compactRows = policy.mode === 'compact' || dependencies.length > visible.length;
1167
1624
  return [
1168
- ...visible.map((entry) => _jsxs(Text, { children: [_jsxs(Text, { color: relation === 'direct' ? tuiTheme.colors.text : tuiTheme.colors.muted, children: ["\u2022 ", entry.displayName] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: [" ", relation === 'direct' ? '直接依赖' : '间接依赖'] })] }, entry.pluginId)),
1169
- ...(dependencies.length > visible.length
1170
- ? [_jsxs(Text, { color: tuiTheme.colors.muted, children: [" \u53E6\u6709 ", dependencies.length - visible.length, " \u4E2A", relation === 'direct' ? '直接' : '间接', "\u4F9D\u8D56"] }, `${relation}:more`)]
1171
- : []),
1625
+ ...visible.map((entry) => {
1626
+ const state = dependencyStatus(entry);
1627
+ return _jsx(StaticRow, { title: entry.displayName, description: dependencyRelationDescription(entry), meta: state.value, status: state.status, compact: compactRows }, entry.pluginId);
1628
+ }),
1629
+ ...(visible.length === 0 && dependencies.length > 0
1630
+ ? [_jsx(StaticRow, { title: `${dependencies.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:count`)]
1631
+ : dependencies.length > visible.length
1632
+ ? [_jsx(StaticRow, { title: `另有 ${dependencies.length - visible.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:more`)]
1633
+ : []),
1172
1634
  ];
1173
1635
  };
1636
+ const compactDependencyRows = overview?.dependencies.slice(0, 2) ?? [];
1174
1637
  content = hasDependencies
1175
- ? _jsxs(Box, { flexDirection: "column", children: [plugin.portableSkills ? _jsx(Bordered, { title: "\u53EF\u7528\u65B9\u5F0F", children: _jsx(Text, { children: "\u901A\u7528 Skills \u63D0\u4F9B\u901A\u7528 Skills" }) }) : null, _jsxs(Bordered, { title: "\u5B89\u88C5\u5185\u5BB9", children: [_jsxs(Text, { children: [_jsxs(Text, { color: tuiTheme.colors.accent, children: ["\u2022 ", plugin.displayName] }), _jsx(Text, { color: tuiTheme.colors.muted, children: " \u4F60\u9009\u62E9\u7684\u63D2\u4EF6" })] }), summaryRows('direct'), summaryRows('transitive'), _jsxs(Text, { color: tuiTheme.colors.muted, children: [plugin.dependencySummary.totalCount + 1, " \u4E2A\u63D2\u4EF6\u5C06\u4F5C\u4E3A\u4E00\u4E2A\u5B89\u88C5\u8BA1\u5212\u68C0\u67E5\u3002"] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u4F9D\u8D56\u4ECE\u540C\u4E00\u4E2A\u6765\u6E90\u5FEB\u7167\u89E3\u6790\u3002" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] })
1176
- : _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { title: "\u53EF\u7528\u5DE5\u5177", children: [_jsx(Text, { children: plugin.supportedPlatforms.map(platformLabel).join(' · ') || '暂无可用工具' }), plugin.portableSkills ? _jsx(Text, { children: "\u901A\u7528 Skills \u63D0\u4F9B\u901A\u7528 Skills" }) : null] }), _jsxs(Bordered, { title: "\u4F7F\u7528\u8BF4\u660E", children: [_jsx(Text, { children: "\u2713 \u5728 Agent \u4F1A\u8BDD\u4E2D\u63D0\u4F9B\u80FD\u529B" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5B89\u88C5\u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539\u9879\u76EE\u6E90\u7801\u3002" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
1638
+ ? _jsxs(Box, { flexDirection: "column", children: [plugin.portableSkills && policy.mode !== 'compact' ? _jsx(SectionCard, { title: "\u53EF\u7528\u65B9\u5F0F", children: _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5" }) }) : null, _jsxs(SectionCard, { title: "\u5B89\u88C5\u5185\u5BB9", children: [policy.mode !== 'compact' ? _jsx(StaticRow, { title: plugin.displayName, description: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", status: "info" }) : null, plugin.portableSkills && policy.mode === 'compact' ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, compact: true }) : null, policy.mode === 'compact' ? compactDependencyRows.map((entry) => {
1639
+ const state = dependencyStatus(entry);
1640
+ return _jsx(StaticRow, { title: entry.displayName, meta: `${entry.relation === 'direct' ? '直接' : '间接'} · ${state.value}`, status: state.status, compact: true }, entry.pluginId);
1641
+ }) : _jsxs(_Fragment, { children: [summaryRows('direct'), summaryRows('transitive')] }), policy.mode === 'compact' && (overview?.dependencies.length ?? 0) > compactDependencyRows.length ? _jsx(StaticRow, { title: `另有 ${(overview?.dependencies.length ?? 0) - compactDependencyRows.length} 个依赖`, status: "muted", compact: true }) : null, policy.mode !== 'compact' ? _jsx(StaticRow, { title: `${plugin.dependencySummary.totalCount + 1} 个插件将作为一个安装计划检查`, description: "\u4F9D\u8D56\u4ECE\u540C\u4E00\u4E2A\u6765\u6E90\u5FEB\u7167\u89E3\u6790\u3002" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' || previewViewport.limit !== undefined, offset: previewViewport.offset, limit: previewViewport.limit })] })
1642
+ : _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", children: [_jsx(StaticRow, { title: plugin.supportedPlatforms.map(platformLabel).join(' · ') || '暂无可用目标平台' }), plugin.portableSkills ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: "\u53EF\u5B89\u88C5", description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5", compact: policy.mode === 'compact' }) : null] }), _jsx(SectionCard, { title: "\u4F7F\u7528\u8BF4\u660E", children: _jsx(StaticRow, { title: "\u5728 Agent \u4F1A\u8BDD\u4E2D\u63D0\u4F9B\u80FD\u529B", status: "success", description: "\u5B89\u88C5\u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539\u9879\u76EE\u6E90\u7801\u3002" }) }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: previewViewport.offset, limit: previewViewport.limit })] });
1177
1643
  }
1178
1644
  else if (route.name === 'portableSkillSelect' && plugin && snapshot.portableDraft) {
1179
1645
  const draft = snapshot.portableDraft;
1180
- const focusedCandidate = draft.candidates[focus];
1181
1646
  breadcrumb = `${plugin.displayName} / 通用 Skills`;
1182
1647
  context = `${draft.selectedCount} / ${draft.totalCount} 已选择`;
1183
1648
  title = '选择要安装的 Skills';
1184
1649
  description = '已根据插件依赖和当前设置准备候选;默认全部选择。';
1185
1650
  const candidateActions = draft.candidates.map((candidate) => ({
1186
- label: `${candidate.selected ? '◆' : '◇'} ${candidate.name}`,
1187
- description: portableScopeVariantsDiffer(candidate)
1188
- ? '当前项目与所有项目提供不同内容;下方显示两种范围变体'
1189
- : `${candidate.description} · ${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件' : candidate.relation === 'direct' ? '直接依赖' : '间接依赖'}${candidate.dynamic ? ' · 按设置生成' : ''}`,
1651
+ label: candidate.name,
1652
+ selected: candidate.selected,
1653
+ description: `${candidate.description}${candidate.dynamic ? ' · 按设置生成' : ''}`,
1190
1654
  run: () => props.controller.togglePortableSkill(candidate.name),
1191
1655
  }));
1192
1656
  actions = [
1193
1657
  ...candidateActions,
1194
- { label: '继续', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
1658
+ { label: '继续', meta: draft.canContinue ? undefined : '至少选择一个 Skill', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
1195
1659
  ...(plugin.dependencySummary?.totalCount ? [{ label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
1196
1660
  ];
1197
- const viewport = portableSkillCandidateViewport(candidateActions, draft.candidates, policy.mode, focus, scrollOffset);
1661
+ const candidateViewport = portableSkillCandidateViewport(candidateActions, draft.candidates, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(actions.length - draft.candidates.length
1662
+ + (portableVariantCandidates.length > 0 ? 3 : 0)));
1663
+ const viewport = showTechnical
1664
+ ? { offset: focus < draft.candidates.length ? focus : candidateViewport.offset, limit: 1 }
1665
+ : candidateViewport;
1666
+ const visibleCandidateEntries = draft.candidates
1667
+ .map((candidate, index) => ({ candidate, action: candidateActions[index], index }))
1668
+ .slice(viewport.offset, viewport.offset + viewport.limit);
1669
+ const providerGroups = [...new Map(visibleCandidateEntries.map((entry) => [
1670
+ entry.candidate.providerPluginId,
1671
+ visibleCandidateEntries.filter((candidate) => candidate.candidate.providerPluginId === entry.candidate.providerPluginId),
1672
+ ])).values()];
1673
+ const variantCandidates = portableVariantCandidates;
1674
+ const focusedVariant = variantCandidates.find((candidate) => candidate.name === draft.candidates[focus]?.name);
1675
+ const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
1676
+ const technicalVariant = focusedVariant ?? visibleVariant ?? variantCandidates[0];
1198
1677
  help = '↑↓ 移动 · Space 切换 · a 全选 · Enter 继续 · Esc 返回';
1199
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PortableSkillRows, { actions: candidateActions, candidates: draft.candidates, focus: focus, offset: viewport.offset, limit: viewport.limit }), focusedCandidate && portableScopeVariantsDiffer(focusedCandidate) ? _jsxs(Bordered, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(Text, { children: portableScopeVariantLabel('project', focusedCandidate.scopeDetails.project) }), _jsx(Text, { children: portableScopeVariantLabel('user', focusedCandidate.scopeDetails.user) })] }) : null, _jsx(ActionRows, { actions: actions, focus: focus, offset: draft.candidates.length, limit: actions.length - draft.candidates.length })] });
1678
+ content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? visibleCandidateEntries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, selected: Boolean(action.selected), focused: focus === index, compact: true }, String(action.label))) : providerGroups.map((entries) => {
1679
+ const provider = entries[0].candidate;
1680
+ const relation = provider.relation === 'requested' ? '当前插件'
1681
+ : provider.relation === 'direct' ? '直接依赖插件' : '间接依赖插件';
1682
+ return _jsx(SectionCard, { title: provider.providerDisplayName, meta: relation, compact: policy.mode === 'compact', children: entries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, description: action.description, selected: Boolean(action.selected), focused: focus === index, compact: policy.mode === 'compact' }, String(action.label))) }, provider.providerPluginId);
1683
+ }), variantCandidates.length > 0
1684
+ ? showTechnical
1685
+ ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
1686
+ : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
1687
+ : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: draft.candidates.length }), showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] });
1200
1688
  }
1201
1689
  else if (route.name === 'portableDependencies' && plugin && snapshot.portableDraft) {
1202
1690
  const overview = dependencyOverview(plugin);
@@ -1205,51 +1693,49 @@ export function ConsoleAppV4(props) {
1205
1693
  title = '生成这些候选 Skills 需要的插件';
1206
1694
  description = '插件依赖用于生成,不能在这里取消。';
1207
1695
  actions = [
1208
- ...(overview?.dependencies ?? []).map((dependency) => ({
1209
- label: dependency.displayName,
1210
- description: `${dependency.relation === 'direct' ? '直接依赖' : '间接依赖'} · 贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个候选 Skill`,
1211
- })),
1212
1696
  { label: '返回 Skill 选择', run: back },
1213
- { label: '技术详情', run: () => setShowTechnical((value) => !value) },
1697
+ { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
1214
1698
  ];
1215
- const viewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1216
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsxs(Text, { children: [plugin.displayName, " \u00B7 \u8D21\u732E ", snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length, " \u4E2A\u5019\u9009 Skills"] }) }), _jsx(ActionRows, { actions: actions, focus: focus, offset: viewport.offset, limit: viewport.limit }), showTechnical ? _jsx(Text, { color: tuiTheme.colors.muted, children: overview?.dependencies.map((entry) => entry.pluginId).join('\n') }) : null] });
1699
+ const dependencies = overview?.dependencies ?? [];
1700
+ const visibleDependencies = dependencies.slice(0, showTechnical ? 1 : responsiveBudget(2, 3));
1701
+ const technicalDependencies = dependencies.slice(0, 1);
1702
+ const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
1703
+ content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", meta: "\u6839 provider", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56 providers", meta: `${dependencies.length} 个`, children: [visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '根 provider 的直接依赖' : '依赖链中的间接 provider'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > visibleDependencies.length ? _jsx(StaticRow, { title: `另有 ${dependencies.length - visibleDependencies.length} 个依赖 provider`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: `${entry.pluginId}${compactTechnicalSummary && dependencies.length > technicalDependencies.length ? ` · 另有 ${dependencies.length - technicalDependencies.length} 个依赖` : ''}`, compact: true }, entry.pluginId)) }) : null] });
1217
1704
  }
1218
1705
  else if (route.name === 'portableAgentMode' && plugin && snapshot.portableDraft) {
1219
1706
  breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
1220
1707
  title = '安装到哪些 Agent?';
1221
1708
  description = '可以自动选择检测到的 Agent,也可以限制到指定 Agent。';
1222
1709
  actions = [
1223
- { label: '自动选择', description: '自动选择检测到的 Agent;未检测到时可能选择全部 Agent', meta: '默认', run: () => { props.controller.setPortableAgentMode('automatic'); go({ name: 'portableScope', pluginId: plugin.pluginId }); } },
1224
- { label: '指定 Agent…', description: '输入一个或多个 Skills CLI ID', run: () => go({ name: 'portableAgentInput', pluginId: plugin.pluginId }) },
1710
+ { label: '使用 Skills CLI 默认选择', description: '继续 Skills CLI 默认选择', meta: '默认', selected: snapshot.portableDraft.agentMode === 'automatic', run: () => { props.controller.setPortableAgentMode('automatic'); go({ name: 'portableScope', pluginId: plugin.pluginId }); } },
1711
+ { label: '指定 Agent…', description: '输入一个或多个 Agent ID', selected: snapshot.portableDraft.agentMode === 'explicit', run: () => go({ name: 'portableAgentInput', pluginId: plugin.pluginId }) },
1225
1712
  ];
1226
- content = _jsx(ActionRows, { actions: actions, focus: focus });
1713
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
1227
1714
  }
1228
1715
  else if (route.name === 'portableAgentInput' && plugin) {
1229
1716
  breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
1230
1717
  context = '指定';
1231
1718
  title = '输入 Agent ID';
1232
- description = inputError ?? '多个 Agent 使用空格分隔。';
1233
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { children: _jsx(Text, { children: portableAgentInput || ' ' }) }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u793A\u4F8B\uFF1Acursor claude-code codex gemini-cli" })] });
1719
+ description = '多个 Agent 可使用逗号或空格分隔。';
1720
+ help = '输入 Agent ID · Enter 保存 · Esc 返回';
1721
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: portableAgentInput || ' ', description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
1234
1722
  }
1235
1723
  else if (route.name === 'portableScope' && plugin && snapshot.portableDraft) {
1236
1724
  breadcrumb = `${plugin.displayName} / 通用 Skills / 范围`;
1237
1725
  title = '在哪里使用这些 Skills?';
1238
1726
  description = `已选择 ${snapshot.portableDraft.selectedCount} 个 Skills · Agent:${snapshot.portableDraft.agentMode === 'automatic' ? '自动选择' : snapshot.portableDraft.requestedAgents.join('、')}`;
1239
1727
  actions = [
1240
- { label: '当前项目', description: '安装到当前项目的 Skill 目录', meta: '推荐', run: () => beginPortableReview(plugin.pluginId, 'project') },
1241
- { label: '所有项目', description: '安装到用户级 Skill 目录', run: () => beginPortableReview(plugin.pluginId, 'user') },
1728
+ { label: '当前项目', description: '仅供当前项目中的 Agent 使用', meta: '推荐', selected: snapshot.portableDraft.scope === 'project', run: () => props.controller.setPortableScope('project') },
1729
+ { label: '所有项目', description: '安装到用户范围,供所有项目使用', selected: snapshot.portableDraft.scope === 'user', run: () => props.controller.setPortableScope('user') },
1730
+ { label: '继续', description: '按所选范围重新确认最终内容', run: () => beginPortableReview(plugin.pluginId, snapshot.portableDraft.scope) },
1242
1731
  ];
1243
- content = _jsx(ActionRows, { actions: actions, focus: focus });
1732
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: 2 })] });
1244
1733
  }
1245
1734
  else if (route.name === 'portableBlocker' && plugin) {
1246
1735
  breadcrumb = `${plugin.displayName} / 通用 Skills`;
1247
- title = route.code === 'requiredConfigMissing' ? '无法准备通用 Skills'
1248
- : route.code === 'portableScopeUnavailable' ? '所选范围不可用'
1249
- : route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible' ? '暂时无法安装通用 Skills' : '无法准备通用 Skills';
1250
- description = route.code === 'requiredConfigMissing' ? '插件缺少生成内容所需的设置。'
1251
- : route.code === 'portableScopeUnavailable' ? '至少一个所选 Skill 不适用于这个范围。请返回 Skill 选择或更改范围。'
1252
- : route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible' ? '没有找到兼容的 Skills CLI。' : '候选 Skill 没有通过 Portable 校验。';
1736
+ const diagnostic = portableBlockerDiagnostic(route);
1737
+ title = diagnostic.title;
1738
+ description = '';
1253
1739
  actions = [
1254
1740
  ...(route.code === 'requiredConfigMissing' ? [{ label: '完成插件设置', run: () => {
1255
1741
  props.controller.replace({ name: 'configLayer', pluginId: plugin.pluginId });
@@ -1257,7 +1743,8 @@ export function ConsoleAppV4(props) {
1257
1743
  setScrollOffset(0);
1258
1744
  } }] : []),
1259
1745
  ...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
1260
- ? [{ label: '查看安装说明', run: () => setShowTechnical((value) => !value) }] : []),
1746
+ ? [{ label: showTechnical ? '返回问题摘要' : '查看安装说明', run: () => setShowTechnical((value) => !value) }]
1747
+ : []),
1261
1748
  ...(route.code === 'portableScopeUnavailable' ? [{ label: '返回 Skill 选择', run: () => {
1262
1749
  props.controller.resetPortableFlowToPreview(plugin.pluginId, { name: 'portableSkillSelect', pluginId: plugin.pluginId }, true);
1263
1750
  setFocus(0);
@@ -1265,45 +1752,37 @@ export function ConsoleAppV4(props) {
1265
1752
  } }] : []),
1266
1753
  { label: '重新检查', run: () => beginPortable(plugin.pluginId) },
1267
1754
  { label: '返回插件预览', run: () => props.controller.resetPortableFlowToPreview(plugin.pluginId) },
1755
+ ...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
1756
+ ? []
1757
+ : [{ label: showTechnical ? '返回问题摘要' : '技术详情', run: () => setShowTechnical((value) => !value) }]),
1268
1758
  ];
1269
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", route.code] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u6CA1\u6709\u8C03\u7528 Skills CLI \u5B89\u88C5\u3002" })] }), _jsx(ActionRows, { actions: actions, focus: focus }), showTechnical ? _jsx(Bordered, { title: route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible' ? '安装说明' : '技术详情', children: _jsx(Text, { color: tuiTheme.colors.muted, children: route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible' ? '安装或升级兼容的 Skills CLI(>=1.5.23 <2.0.0),然后重新检查。' : `诊断代码:${route.diagnosticCode}` }) }) : _jsx(Text, { color: tuiTheme.colors.muted, children: "d \u67E5\u770B\u8131\u654F\u8BCA\u65AD" })] });
1759
+ const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
1760
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
1270
1761
  }
1271
1762
  else if (route.name === 'pluginDependencies' && plugin) {
1272
1763
  const overview = dependencyOverview(plugin);
1273
1764
  const visibleDependents = route.view === 'removeBlocked' && route.platform && route.scope
1274
1765
  ? props.controller.getBlockingDependents(plugin.pluginId, route.platform, route.scope)
1275
1766
  : overview?.dependents ?? [];
1276
- const relationActions = [
1277
- ...(overview?.dependencies ?? []).map((entry) => {
1278
- const state = dependencyStatus(entry);
1279
- return {
1280
- label: entry.displayName,
1281
- description: dependencyRelationDescription(entry),
1282
- meta: dependencyPlacementLabel(entry),
1283
- status: state.status,
1284
- run: () => go({ name: 'pluginDetail', pluginId: entry.pluginId }),
1285
- };
1286
- }),
1287
- ...visibleDependents.map((entry) => {
1288
- const state = dependencyStatus(entry);
1289
- return {
1290
- label: entry.displayName,
1291
- description: dependencyRelationDescription(entry),
1292
- meta: route.view === 'removeBlocked' ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry),
1293
- status: state.status,
1294
- run: () => go({ name: 'pluginDetail', pluginId: entry.pluginId }),
1295
- };
1296
- }),
1297
- ];
1298
1767
  const dependencyCount = overview?.dependencies.length ?? 0;
1299
1768
  const dependentCount = visibleDependents.length;
1769
+ const dependencyLimit = policy.mode === 'compact'
1770
+ ? dependentCount > 0 ? 1 : 2
1771
+ : dependentCount > 0 ? 3 : 5;
1772
+ const dependentLimit = responsiveBudget(2, 4);
1773
+ const dependencyRows = (entries, limit, dependent = false) => {
1774
+ const visible = entries.slice(0, limit);
1775
+ return _jsxs(_Fragment, { children: [visible.map((entry) => {
1776
+ const state = dependencyStatus(entry);
1777
+ return _jsx(StaticRow, { title: entry.displayName, description: dependencyRelationDescription(entry), meta: dependent && route.view === 'removeBlocked' ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry), status: state.status, compact: policy.mode === 'compact' }, entry.pluginId);
1778
+ }), entries.length > visible.length ? _jsx(StaticRow, { title: `另有 ${entries.length - visible.length} 项`, status: "muted", compact: true }) : null] });
1779
+ };
1300
1780
  if (route.view === 'removeBlocked') {
1301
1781
  breadcrumb = `${plugin.displayName} / 移除`;
1302
1782
  context = '已阻止';
1303
1783
  title = `无法移除 ${plugin.displayName}`;
1304
1784
  description = '仍有插件依赖它。请先处理依赖者,再重新尝试。';
1305
1785
  actions = [
1306
- ...relationActions.slice(dependencyCount),
1307
1786
  ...(route.platform && route.scope ? [{
1308
1787
  label: '强制清理…',
1309
1788
  description: '依赖者会变为不完整;下一页仍需明确确认',
@@ -1323,10 +1802,13 @@ export function ConsoleAppV4(props) {
1323
1802
  setScrollOffset(0);
1324
1803
  } },
1325
1804
  { label: '返回插件详情', run: back },
1805
+ { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
1326
1806
  ];
1327
- const consumerActions = actions.slice(0, dependentCount);
1328
- const blockedViewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1329
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", dependentCount, " \u4E2A\u5DF2\u5B89\u88C5\u63D2\u4EF6\u4ECD\u5728\u4F7F\u7528"] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002" })] }), _jsx(Bordered, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", children: _jsx(ActionRows, { actions: consumerActions, focus: focus, offset: blockedViewport.offset, limit: blockedViewport.limit }) }), _jsx(ActionRows, { actions: actions.slice(dependentCount), focus: focus - dependentCount })] });
1807
+ const blockedRemovalViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
1808
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: `${dependentCount} 个已安装插件仍在使用`, description: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002" }), showTechnical ? null : _jsx(SectionCard, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockedRemovalViewport.offset, limit: blockedRemovalViewport.limit }), showTechnical ? _jsx(IssueTechnicalDetails, { details: [{
1809
+ code: 'dependencyStillRequired',
1810
+ message: '已安装插件仍在使用这个依赖;强制清理会使依赖者变为不完整。',
1811
+ }], compact: policy.mode === 'compact' }) : null] });
1330
1812
  }
1331
1813
  else {
1332
1814
  breadcrumb = `${plugin.displayName} / 依赖关系`;
@@ -1334,19 +1816,51 @@ export function ConsoleAppV4(props) {
1334
1816
  title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
1335
1817
  description = '依赖从同一个来源快照解析;关系页不会修改安装。';
1336
1818
  actions = [
1337
- ...relationActions,
1338
1819
  { label: '技术详情', description: '显示 canonical pluginId 和安全错误代码', run: () => setShowTechnical((value) => !value) },
1339
1820
  ];
1340
- const viewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1341
- const visibleRows = (start, end) => relationActions
1342
- .map((_, index) => index)
1343
- .filter((index) => index >= start && index < end
1344
- && index >= viewport.offset
1345
- && (viewport.limit === undefined || index < viewport.offset + viewport.limit))
1346
- .map((index) => _jsx(ActionRows, { actions: actions, focus: focus, offset: index, limit: 1 }, index));
1347
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { title: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", children: _jsxs(Text, { children: [plugin.displayName, " ", _jsx(Text, { color: tuiTheme.colors.muted, children: statusText(plugin) })] }) }), dependencyCount > 0 ? _jsx(Bordered, { title: "\u5FC5\u9700\u4F9D\u8D56", children: visibleRows(0, dependencyCount) }) : null, dependentCount > 0 ? _jsx(Bordered, { title: "\u4F7F\u7528\u5B83\u7684\u63D2\u4EF6", children: visibleRows(dependencyCount, dependencyCount + dependentCount) }) : null, overview?.blockers.map((blocker) => _jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", blocker.title] }), _jsx(Text, { color: tuiTheme.colors.muted, children: blocker.explanation })] }, blocker.code)), relationActions.length === 0 ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB\u3002" }) : null, viewport.limit !== undefined && viewport.offset + viewport.limit > relationActions.length
1348
- ? _jsx(ActionRows, { actions: actions, focus: focus, offset: relationActions.length, limit: 1 })
1349
- : null, showTechnical ? _jsxs(Bordered, { title: "\u6280\u672F\u8BE6\u60C5", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["canonical pluginId ", plugin.pluginId] }), overview?.dependencies.map((entry) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [entry.pluginId, " \u00B7 ", dependencyRelationDescription(entry)] }, entry.pluginId))] }) : null] });
1821
+ const rootStatus = plugin.aggregateStatus === 'installed' ? 'success'
1822
+ : plugin.aggregateStatus === 'needsAttention' ? 'warning'
1823
+ : plugin.aggregateStatus === 'unavailable' ? 'error' : 'muted';
1824
+ const dependencyActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
1825
+ const blockerCount = overview?.blockers.length ?? 0;
1826
+ const useBudgetedRelations = policy.mode === 'compact'
1827
+ || showTechnical
1828
+ || dependencyCount + dependentCount + blockerCount > 7;
1829
+ const relationCategories = [
1830
+ ...(blockerCount > 0 ? [{ key: 'blocker', count: blockerCount }] : []),
1831
+ ...(dependencyCount > 0 ? [{ key: 'dependency', count: dependencyCount }] : []),
1832
+ ...(dependentCount > 0 ? [{ key: 'dependent', count: dependentCount }] : []),
1833
+ ];
1834
+ const technicalLineBudget = showTechnical ? 4 : 0;
1835
+ const relationItemBudget = Math.max(relationCategories.length, size.rows - 6 - 1 - technicalLineBudget - 4);
1836
+ const relationBudgets = new Map(relationCategories.map((category) => [category.key, 1]));
1837
+ let remainingRelationLines = Math.max(0, relationItemBudget - relationCategories.length);
1838
+ for (const category of relationCategories) {
1839
+ if (category.key === 'blocker')
1840
+ continue;
1841
+ while (remainingRelationLines > 0 && (relationBudgets.get(category.key) ?? 0) < category.count) {
1842
+ relationBudgets.set(category.key, (relationBudgets.get(category.key) ?? 0) + 1);
1843
+ remainingRelationLines -= 1;
1844
+ }
1845
+ }
1846
+ const prioritizedRelations = (entries) => [...entries].sort((left, right) => {
1847
+ const priority = (entry) => {
1848
+ const status = dependencyStatus(entry).status;
1849
+ return status === 'error' ? 0 : status === 'warning' ? 1 : 2;
1850
+ };
1851
+ return priority(left) - priority(right);
1852
+ });
1853
+ const budgetedRelationRows = (entries, prefix, budget, dependent = false) => {
1854
+ const prioritized = prioritizedRelations(entries);
1855
+ const visible = prioritized.slice(0, budget);
1856
+ return visible.map((entry, index) => {
1857
+ const state = dependencyStatus(entry);
1858
+ const hidden = index === visible.length - 1 ? entries.length - visible.length : 0;
1859
+ const relationMeta = dependent ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry);
1860
+ return _jsx(StaticRow, { title: `${prefix} · ${entry.displayName}`, description: dependencyRelationDescription(entry), meta: `${relationMeta}${hidden > 0 ? ` · 另有 ${hidden} 项` : ''}`, status: state.status, compact: true }, entry.pluginId);
1861
+ });
1862
+ };
1863
+ content = _jsxs(Box, { flexDirection: "column", children: [useBudgetedRelations ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5173\u7CFB", compact: true, children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: statusText(plugin), status: rootStatus, compact: true }), overview?.blockers.slice(0, relationBudgets.get('blocker') ?? 0).map((blocker, index, blockers) => _jsx(StaticRow, { title: `阻塞 · ${blocker.title}`, meta: index === blockers.length - 1 && blockerCount > blockers.length ? `另有 ${blockerCount - blockers.length} 项` : undefined, description: blocker.explanation, status: "error", compact: true }, blocker.code)), budgetedRelationRows(overview?.dependencies ?? [], '必需依赖', relationBudgets.get('dependency') ?? 0), budgetedRelationRows(visibleDependents, '被依赖', relationBudgets.get('dependent') ?? 0, true), dependencyCount + dependentCount + blockerCount === 0 ? _jsx(StaticRow, { title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB", status: "muted", compact: true }) : null] }) : _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", meta: "\u6839\u8282\u70B9", children: _jsx(StaticRow, { title: plugin.displayName, meta: statusText(plugin), status: rootStatus }) }), dependencyCount > 0 ? _jsx(SectionCard, { title: "\u5FC5\u9700\u4F9D\u8D56", meta: "\u4F9D\u8D56\u4F18\u5148", children: dependencyRows(overview?.dependencies ?? [], dependencyLimit) }) : null, dependentCount > 0 ? _jsx(SectionCard, { title: "\u4F7F\u7528\u5B83\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true) }) : null, overview?.blockers.map((blocker) => _jsx(Callout, { status: "error", title: blocker.title, description: blocker.explanation }, blocker.code)), dependencyCount + dependentCount + blockerCount === 0 ? _jsx(Callout, { status: "muted", title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: useBudgetedRelations, offset: dependencyActionViewport.offset, limit: dependencyActionViewport.limit }), showTechnical ? _jsx(TechnicalDetails, { children: _jsx(StaticRow, { title: `${dependencyCount} 依赖 · ${dependentCount} 被依赖 · 已折叠`, meta: plugin.pluginId, compact: true }) }) : null] });
1350
1864
  }
1351
1865
  }
1352
1866
  else if (route.name === 'installPlacement' && plugin) {
@@ -1357,67 +1871,97 @@ export function ConsoleAppV4(props) {
1357
1871
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1358
1872
  description = hasDependencies
1359
1873
  ? '根插件和缺失依赖使用同一个位置;健康的所有项目依赖可在当前项目复用。'
1360
- : '工具已根据插件能力自动选择;需要时可以调整。';
1361
- const availableScopes = ['user', 'project'].filter((candidateScope) => selectedPlatforms(plugin, candidateScope).some((entry) => !entry.disabled));
1874
+ : '选择位置后,再选择一个或多个目标平台。';
1875
+ const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
1876
+ .some((entry) => entry.compatible));
1362
1877
  const reusableDependencies = overview?.dependencies.filter((dependency) => dependency.placements.some((placement) => placement.state === 'installed' && placement.health === 'ready'
1363
1878
  && (scope === 'project' || placement.scope === 'user'))).length ?? 0;
1364
1879
  const missingDependencies = Math.max(0, (overview?.summary.totalCount ?? 0) - reusableDependencies);
1365
1880
  actions = [
1366
1881
  ...availableScopes.map((candidateScope) => ({
1367
- label: `${candidateScope === scope ? '●' : '○'} ${scopeLabel(candidateScope)}`,
1882
+ label: scopeLabel(candidateScope),
1883
+ selected: candidateScope === scope,
1368
1884
  description: candidateScope === 'user'
1369
1885
  ? hasDependencies ? '根插件和缺失依赖都可在任意项目中使用' : '在任意项目中可使用,适合个人常用能力'
1370
1886
  : hasDependencies ? '缺失依赖添加到当前项目;已有所有项目依赖会复用' : '仅在当前项目中使用,适合团队共享',
1371
1887
  meta: candidateScope === 'user' ? '推荐' : undefined,
1372
1888
  run: () => {
1889
+ const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
1890
+ const compatible = choices.filter((entry) => entry.compatible).map((entry) => entry.platform);
1891
+ const previous = targetSelections[candidateScope];
1892
+ const next = previous
1893
+ ? previous.filter((platform) => compatible.includes(platform))
1894
+ : preferredTarget && compatible.includes(preferredTarget)
1895
+ ? [preferredTarget]
1896
+ : compatible.length === 1 ? compatible : [];
1373
1897
  setScope(candidateScope);
1374
- setTools(selectedPlatforms(plugin, candidateScope).filter((entry) => !entry.disabled).map((entry) => entry.platform));
1898
+ setSelectedTargets(next);
1899
+ setTargetSelections((current) => ({ ...current, [candidateScope]: next }));
1900
+ go({
1901
+ name: 'installTargets',
1902
+ pluginId: plugin.pluginId,
1903
+ ...(compatible.length === 0 ? { view: 'blocked' } : {}),
1904
+ });
1375
1905
  },
1376
1906
  })),
1377
- { label: '继续', disabled: tools.length === 0 ? '没有可安装的工具' : undefined, run: async () => beginReview(installIntent(plugin)) },
1378
- { label: '调整工具…', run: () => {
1379
- const choices = selectedPlatforms(plugin, scope);
1380
- go({ name: 'installTools', pluginId: plugin.pluginId,
1381
- ...(choices.every((choice) => choice.disabled) ? { view: 'blocked' } : {}) });
1382
- } },
1383
1907
  ];
1384
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ActionRows, { actions: actions.slice(0, availableScopes.length), focus: focus }), hasDependencies ? _jsxs(Bordered, { title: "\u4F9D\u8D56\u5904\u7406", children: [_jsxs(Text, { color: reusableDependencies > 0 ? tuiTheme.statusColors.success : tuiTheme.colors.muted, children: ["\u2713 ", reusableDependencies, " \u4E2A\u4F9D\u8D56\u4F1A\u590D\u7528\u73B0\u6709\u5B89\u88C5"] }), _jsxs(Text, { color: missingDependencies > 0 ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["+ ", missingDependencies, " \u4E2A\u4F9D\u8D56\u5C06\u5B89\u88C5\u5230\u6240\u9009\u4F4D\u7F6E"] })] }) : null, _jsx(Bordered, { title: "\u53EF\u7528\u5DE5\u5177", children: _jsxs(Text, { children: [tools.map(platformLabel).join('、') || '当前没有可用工具', "\uFF08\u5DF2\u81EA\u52A8\u9009\u62E9\uFF09"] }) }), _jsx(ActionRows, { actions: actions.slice(availableScopes.length), focus: focus - availableScopes.length })] });
1908
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5904\u7406", children: [_jsx(StaticRow, { title: `${reusableDependencies} 个依赖会复用现有安装`, status: reusableDependencies > 0 ? 'success' : 'muted' }), _jsx(StaticRow, { title: `${missingDependencies} 个依赖将安装到所选位置`, status: missingDependencies > 0 ? 'info' : 'muted' })] }) : null] });
1385
1909
  }
1386
- else if (route.name === 'installTools' && plugin) {
1387
- breadcrumb = `${plugin.displayName} / 添加 / 工具`;
1388
- context = '可选';
1389
- title = '选择要添加到哪些工具';
1910
+ else if (route.name === 'installTargets' && plugin) {
1911
+ breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
1912
+ context = '第 2 步,共 2 步';
1913
+ title = '选择目标平台';
1390
1914
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1391
1915
  description = hasDependencies
1392
- ? '只有根插件和所有依赖都支持所选位置的工具才可选择。'
1916
+ ? '只有根插件和所有依赖都支持所选位置的目标平台才可选择。'
1393
1917
  : '同一插件、同一平台只能安装到一个位置。';
1394
- const choices = selectedPlatforms(plugin, scope);
1395
- const allUnavailable = choices.length > 0 && choices.every((choice) => choice.disabled);
1918
+ const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
1919
+ const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
1920
+ const allUnavailable = choices.length > 0 && choices.every((choice) => !choice.compatible);
1396
1921
  if (route.view === 'blocked' || allUnavailable) {
1397
1922
  context = '依赖限制';
1398
- title = '无法添加到所选工具';
1399
- description = '根插件或必需依赖不支持当前工具组合,因此不会进入确认页。';
1923
+ title = '无法添加到所选目标平台';
1924
+ description = '根插件或必需依赖不支持当前目标平台组合,因此不会进入确认页。';
1400
1925
  actions = [
1401
1926
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
1402
1927
  { label: '返回调整位置', run: back },
1403
1928
  ];
1404
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { children: choices.map((choice) => _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u25C7 ", platformLabel(choice.platform), " \u4E0D\u53EF\u9009", choice.disabled ? ` · ${choice.disabled}` : ''] }, choice.platform)) }), _jsx(ActionRows, { actions: actions, focus: focus })] });
1929
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u6240\u9009\u76EE\u6807\u5E73\u53F0\u4E0D\u53EF\u7528", children: choices.map((choice) => _jsx(StaticRow, { title: platformLabel(choice.platform), description: choice.disabled ? `不可选 · ${choice.disabled}` : '不可选', status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
1405
1930
  }
1406
1931
  else {
1407
- actions = choices.map((choice) => ({
1408
- label: `${tools.includes(choice.platform) ? '◆' : '◇'} ${platformLabel(choice.platform)}`,
1932
+ const targetActions = choices.map((choice) => ({
1933
+ label: platformLabel(choice.platform),
1934
+ selected: validSelectedTargets.includes(choice.platform),
1409
1935
  description: choice.disabled ?? '当前未安装,支持所选位置',
1410
- meta: tools.includes(choice.platform) ? '已选择' : undefined,
1936
+ meta: validSelectedTargets.includes(choice.platform) ? '已选择' : undefined,
1411
1937
  disabled: choice.disabled,
1412
1938
  run: () => {
1413
- setDirty(true);
1414
- setTools((current) => current.includes(choice.platform)
1415
- ? current.filter((platform) => platform !== choice.platform)
1416
- : [...current, choice.platform]);
1939
+ if (!choice.compatible)
1940
+ return;
1941
+ setSelectedTargets((current) => {
1942
+ const next = current.includes(choice.platform)
1943
+ ? current.filter((platform) => platform !== choice.platform)
1944
+ : [...current, choice.platform];
1945
+ setTargetSelections((selections) => ({ ...selections, [scope]: next }));
1946
+ return next;
1947
+ });
1417
1948
  },
1418
1949
  }));
1419
- help = '↑↓ 移动 · Space 切换 · Enter 完成 · Esc 返回';
1420
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ActionRows, { actions: actions, focus: focus }), hasDependencies ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u4E0D\u517C\u5BB9\u7684\u5DE5\u5177\u4E0D\u80FD\u9009\u62E9\uFF1B\u8BF7\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB\u4E86\u89E3\u539F\u56E0\u3002" }) : null, _jsxs(Text, { color: tools.length > 0 ? tuiTheme.colors.accent : tuiTheme.statusColors.warning, children: ["Enter ", tools.length > 0 ? '完成选择' : '至少选择一个工具'] })] });
1950
+ actions = [
1951
+ ...targetActions,
1952
+ {
1953
+ label: '确认目标平台',
1954
+ description: validSelectedTargets.length > 0 ? `已选择 ${validSelectedTargets.length} 个平台` : undefined,
1955
+ disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
1956
+ run: () => {
1957
+ setDirty(false);
1958
+ void beginReview(installIntent(plugin, validSelectedTargets));
1959
+ },
1960
+ },
1961
+ { label: '返回选择位置', run: back },
1962
+ ];
1963
+ help = '↑↓ 移动 · Enter 选择/确认 · Esc 返回';
1964
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length }), hasDependencies ? _jsx(Callout, { title: "\u76EE\u6807\u5E73\u53F0\u517C\u5BB9\u6027", description: "\u4E0D\u517C\u5BB9\u7684\u76EE\u6807\u5E73\u53F0\u4E0D\u80FD\u9009\u62E9\uFF1B\u8BF7\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB\u4E86\u89E3\u539F\u56E0\u3002" }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length })] });
1421
1965
  }
1422
1966
  }
1423
1967
  else if (route.name === 'pluginDetail' && plugin) {
@@ -1426,181 +1970,260 @@ export function ConsoleAppV4(props) {
1426
1970
  title = plugin.displayName;
1427
1971
  description = plugin.description;
1428
1972
  const dependencySummary = plugin.dependencySummary;
1429
- const placementActions = plugin.installedPlacements.map((placement) => {
1430
- const projected = placementStatus(placement);
1431
- return {
1432
- label: platformLabel(placement.platform),
1433
- description: projected.description,
1434
- meta: projected.value,
1435
- status: projected.status,
1436
- run: () => placement.state === 'scopeConflict'
1437
- ? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${placement.platform}` })
1438
- : go({ name: 'platform', pluginId: plugin.pluginId, platform: placement.platform }),
1439
- };
1973
+ const detailMenu = dynamicMenu;
1974
+ actions = detailMenu.map((action) => {
1975
+ if (action.id === 'platform' || action.id === 'scopeConflict') {
1976
+ const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
1977
+ const projected = placementStatus(placement);
1978
+ return {
1979
+ label: platformLabel(action.platform),
1980
+ description: projected.description,
1981
+ meta: projected.value,
1982
+ status: projected.status,
1983
+ run: () => action.id === 'scopeConflict'
1984
+ ? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
1985
+ : go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
1986
+ };
1987
+ }
1988
+ if (action.id === 'dependencies')
1989
+ return {
1990
+ label: '必需依赖',
1991
+ description: dependencySummary.issueCount > 0 ? `${dependencySummary.issueCount} 项需要处理` : '查看直接、间接依赖和被使用情况',
1992
+ meta: dependencySummaryText(dependencySummary),
1993
+ status: dependencySummary.issueCount > 0 ? 'warning' : undefined,
1994
+ run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
1995
+ };
1996
+ if (action.id === 'portableSkills')
1997
+ return {
1998
+ label: '通用 Skills',
1999
+ description: '选择 Skill 和 Agent 后交给 Skills CLI 安装',
2000
+ meta: dependencySummary ? `可安装 · ${dependencySummary.totalCount} 个插件依赖` : '可安装',
2001
+ run: () => beginPortable(plugin.pluginId),
2002
+ };
2003
+ if (action.id === 'settings')
2004
+ return { label: '设置', description: '查看当前实际生效的配置和值来源', meta: `${plugin.configFieldCount} 项配置`, run: () => beginConfigFlow(plugin.pluginId) };
2005
+ if (action.id === 'sourceProblem')
2006
+ return { label: '查看来源问题', status: 'warning', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
2007
+ return { label: '关于与技术详情', description: '版本、来源和受管内容', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
1440
2008
  });
1441
- const dependencyActions = dependencySummary ? [{
1442
- label: '必需依赖',
1443
- description: dependencySummary.issueCount > 0 ? `${dependencySummary.issueCount} 项需要处理` : '查看直接、间接依赖和被使用情况',
1444
- meta: dependencySummaryText(dependencySummary),
1445
- status: dependencySummary.issueCount > 0 ? 'warning' : undefined,
1446
- run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
1447
- }] : [];
1448
- const portableActions = plugin.portableSkills ? [{
1449
- label: '通用 Skills',
1450
- description: '由插件提供;安装状态未知',
1451
- meta: '已提供',
1452
- run: () => beginPortable(plugin.pluginId),
1453
- }] : [];
1454
- actions = [
1455
- ...placementActions,
1456
- ...dependencyActions,
1457
- ...portableActions,
1458
- { label: '设置', description: '个人默认、当前项目和本机覆盖', meta: `${plugin.configFieldCount} 项配置`, run: () => go({ name: 'configLayer', pluginId: plugin.pluginId }) },
1459
- { label: '关于与技术详情', description: '版本、来源和受管内容', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
1460
- ];
1461
- const dependencyOffset = placementActions.length;
1462
- const portableOffset = dependencyOffset + dependencyActions.length;
1463
- const settingOffset = portableOffset + portableActions.length;
1464
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { title: "\u4F7F\u7528\u72B6\u6001", children: _jsx(ActionRows, { actions: placementActions, focus: focus }) }), dependencySummary ? _jsx(Bordered, { title: "\u4F9D\u8D56\u5173\u7CFB", children: _jsx(ActionRows, { actions: dependencyActions, focus: focus - dependencyOffset }) }) : null, plugin.portableSkills ? _jsx(Bordered, { title: "\u901A\u7528 Skills", children: _jsx(ActionRows, { actions: portableActions, focus: focus - portableOffset }) }) : null, _jsx(Bordered, { title: "\u63D2\u4EF6\u8BBE\u7F6E", children: _jsx(ActionRows, { actions: actions.slice(settingOffset, -1), focus: focus - settingOffset }) }), _jsx(ActionRows, { actions: actions.slice(-1), focus: focus - actions.length + 1 })] });
2009
+ const detailViewport = actionViewport(actions, policy.mode === 'compact', effectiveFocus, scrollOffset, pageActionBudget(4));
2010
+ content = _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: detailViewport.offset, limit: detailViewport.limit }) });
1465
2011
  }
1466
2012
  else if (route.name === 'platform' && plugin) {
1467
2013
  const placement = props.controller.getPlatformPlacement(plugin.pluginId, route.platform);
2014
+ const platformMenu = dynamicMenu;
2015
+ const typedIssue = placement?.state === 'installed' && placement.health === 'error'
2016
+ ? props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId
2017
+ && candidate.platform === route.platform
2018
+ && candidate.scope === placement.scope)
2019
+ : undefined;
2020
+ const sourceIssue = plugin.aggregateStatus === 'unavailable'
2021
+ ? props.controller.listUserIssues().find((candidate) => candidate.entity.kind === 'source' && candidate.sourceId === plugin.sourceId)
2022
+ : undefined;
2023
+ const platformAction = (action) => {
2024
+ if (action.id === 'sourceProblem')
2025
+ return { label: '查看来源问题', description: `${plugin.sourceId} 无可用缓存`, run: () => go({ name: 'issueDetail', issueId: `source:${plugin.sourceId}:unavailable` }) };
2026
+ if (action.id === 'refreshSource')
2027
+ return { label: '重新检查状态', description: '不会修改现场内容', run: () => props.controller.refreshSource(plugin.sourceId) };
2028
+ if (action.id === 'add')
2029
+ return { label: `添加到 ${platformLabel(route.platform)}…`, description: '选择使用位置', run: () => {
2030
+ const initialScope = placement?.state === 'notInstalled'
2031
+ ? placement.supportedScopes[0] ?? scope
2032
+ : scope;
2033
+ setScope(initialScope);
2034
+ setSelectedTargets([]);
2035
+ setTargetSelections({});
2036
+ setPreferredTarget(route.platform);
2037
+ go({ name: 'installPlacement', pluginId: plugin.pluginId });
2038
+ } };
2039
+ if (action.id === 'about')
2040
+ return { label: '查看支持信息', description: 'manifest 与平台能力', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
2041
+ if (action.id === 'scopeConflict')
2042
+ return { label: '查看位置冲突问题', run: () => go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${route.platform}` }) };
2043
+ if (action.id === 'upgrade')
2044
+ return { label: `升级到 ${action.availableVersion}…`, description: '使用当前来源中的版本重建', run: () => beginReview({ kind: 'pluginWrite', action: 'upgrade', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
2045
+ if (action.id === 'reinstall')
2046
+ return { label: placement?.state === 'installed' && placement.health === 'incomplete' ? '重新连接缺失内容…' : '重新安装…', description: '保留现有设置并重新生成受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
2047
+ if (action.id === 'remove')
2048
+ return { label: `从${scopeLabel(action.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, action.platform, action.scope) };
2049
+ if (action.id === 'technical')
2050
+ return typedIssue
2051
+ ? { label: '查看对应问题', description: typedIssue.explanation, run: () => go({ name: 'issueDetail', issueId: typedIssue.issueId }) }
2052
+ : { label: placement?.state === 'installed' && placement.health === 'error' ? '查看安全技术详情' : placement?.state === 'installed' && placement.health === 'incomplete' ? '查看影响' : '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
2053
+ return { label: '返回插件', run: back };
2054
+ };
1468
2055
  breadcrumb = `${plugin.displayName} / ${platformLabel(route.platform)}`;
1469
2056
  if (plugin.aggregateStatus === 'unavailable') {
2057
+ const sourceOverview = props.controller.getSourceOverview(plugin.sourceId);
2058
+ const diagnostic = sourceIssue?.diagnostic ?? sourceDiagnostic(plugin.sourceId, sourceOverview.health?.state ?? sourceOverview.source?.state, sourceOverview.detail);
1470
2059
  title = `暂时无法管理 ${platformLabel(route.platform)}`;
1471
- description = '插件来源不可用,当前状态不能安全确认。';
1472
- actions = [{ label: '查看来源问题', description: `${plugin.sourceId} 无可用缓存`, run: () => go({ name: 'issueDetail', issueId: `source:${plugin.sourceId}:unavailable` }) }, { label: '重新检查状态', description: '不会修改现场内容', run: () => props.controller.refreshSource(plugin.sourceId) }];
1473
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.error, children: "\u00D7 \u6765\u6E90\u4E0D\u53EF\u7528" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2060
+ description = '';
2061
+ actions = platformMenu.map(platformAction);
2062
+ content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
2063
+ ? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
2064
+ : null, _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
1474
2065
  }
1475
2066
  else if (!placement || placement.state === 'notInstalled') {
1476
2067
  title = `尚未添加到 ${platformLabel(route.platform)}`;
1477
2068
  description = '此插件支持所列使用位置。';
1478
- actions = [{ label: `添加到 ${platformLabel(route.platform)}…`, description: '选择使用位置', run: () => {
1479
- const initialScope = placement?.state === 'notInstalled'
1480
- ? placement.supportedScopes[0] ?? scope
1481
- : scope;
1482
- setScope(initialScope);
1483
- setTools([route.platform]);
1484
- go({ name: 'installPlacement', pluginId: plugin.pluginId });
1485
- } }, { label: '查看支持信息', description: 'manifest 与平台能力', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) }];
1486
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.colors.muted, children: "\u25CB \u672A\u6DFB\u52A0" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2069
+ actions = platformMenu.map(platformAction);
2070
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "muted", title: "\u672A\u6DFB\u52A0", description: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
1487
2071
  }
1488
2072
  else if (placement.state === 'scopeConflict') {
1489
2073
  title = '安装位置冲突';
1490
2074
  description = '正常状态只能保留一个位置。';
1491
- actions = [{ label: '查看位置冲突问题', run: () => go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${route.platform}` }) }];
1492
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2075
+ actions = platformMenu.map(platformAction);
2076
+ content = _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' });
1493
2077
  }
1494
2078
  else if (placement.health === 'error') {
1495
- const typedIssue = props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId
1496
- && candidate.platform === route.platform
1497
- && candidate.scope === placement.scope);
2079
+ const diagnostic = typedIssue?.diagnostic ?? {
2080
+ severity: 'error',
2081
+ title: '状态检查失败',
2082
+ userMessage: '平台状态检查失败,当前不能安全选择写操作。',
2083
+ impact: [
2084
+ { state: 'blocked', message: '当前只提供只读操作。' },
2085
+ { state: 'preserved', message: '现有安装和已保存设置不会因本页自动改变。' },
2086
+ ],
2087
+ nextStep: '查看安全技术详情,然后重新检查当前状态。',
2088
+ technical: {
2089
+ code: 'targetStateError',
2090
+ stage: 'read-platform-state',
2091
+ context: [
2092
+ { key: 'pluginId', value: plugin.pluginId },
2093
+ { key: 'platform', value: route.platform },
2094
+ { key: 'scope', value: placement.scope },
2095
+ ],
2096
+ },
2097
+ };
1498
2098
  title = `${platformLabel(route.platform)} 暂时不可管理`;
1499
- description = typedIssue?.explanation ?? '平台状态检查失败,当前不能安全选择写操作。';
1500
- actions = [
1501
- ...(typedIssue ? [{ label: '查看对应问题', description: typedIssue.title, run: () => go({ name: 'issueDetail', issueId: typedIssue.issueId }) }] : []),
1502
- { label: '查看安全技术详情', description: '只显示类型化状态和错误码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
1503
- { label: '返回插件', run: back },
1504
- ];
1505
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.error, children: "\u00D7 \u72B6\u6001\u68C0\u67E5\u5931\u8D25" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5F53\u524D\u53EA\u63D0\u4F9B\u53EA\u8BFB\u64CD\u4F5C" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2099
+ description = '';
2100
+ actions = platformMenu.map(platformAction);
2101
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
1506
2102
  }
1507
2103
  else if (placement.health === 'incomplete') {
1508
2104
  title = `${platformLabel(route.platform)} ${placement.health === 'incomplete' ? '安装不完整' : '暂时不可用'}`;
1509
2105
  description = '缺少一项必要内容,插件可能无法稳定启动。';
1510
- actions = [
1511
- { label: '重新连接缺失内容…', description: '保留现有设置', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: route.platform, scope: placement.scope }) },
1512
- { label: '查看影响', description: '缺少的内容和范围', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
1513
- { label: `从${scopeLabel(placement.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, route.platform, placement.scope) },
1514
- ];
1515
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: "! \u9700\u8981\u5904\u7406" }), _jsxs(Text, { color: tuiTheme.colors.muted, children: [scopeLabel(placement.scope), " \u00B7 ", plugin.sourceLabel] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2106
+ actions = platformMenu.map(platformAction);
2107
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u9700\u8981\u5904\u7406", description: `${scopeLabel(placement.scope)} · ${plugin.sourceLabel}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
1516
2108
  }
1517
2109
  else {
1518
2110
  title = `已安装到${scopeLabel(placement.scope)}`;
1519
- description = `当前检查正常${plugin.version ? `;版本 ${plugin.version}` : ''}。`;
1520
- actions = [
1521
- { label: '运行升级…', description: '使用当前来源中的版本重建', run: () => beginReview({ kind: 'pluginWrite', action: 'upgrade', pluginId: plugin.pluginId, platform: route.platform, scope: placement.scope }) },
1522
- { label: '重新安装…', description: '重新生成并验证受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: route.platform, scope: placement.scope }) },
1523
- { label: `从${scopeLabel(placement.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, route.platform, placement.scope) },
1524
- { label: '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
1525
- ];
1526
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.success, children: "\u2713 \u72B6\u6001\u6B63\u5E38" }), _jsxs(Text, { color: tuiTheme.colors.muted, children: [scopeLabel(placement.scope), plugin.version ? ` · 当前版本 ${plugin.version}` : ''] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2111
+ const installedVersion = placement.revision.installedVersion;
2112
+ description = `当前检查正常${installedVersion ? `;已安装版本 ${installedVersion}` : ''}。`;
2113
+ actions = platformMenu.map(platformAction);
2114
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "success", title: "\u72B6\u6001\u6B63\u5E38", description: `${scopeLabel(placement.scope)}${installedVersion ? ` · 已安装版本 ${installedVersion}` : ''}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
1527
2115
  }
1528
2116
  }
1529
2117
  else if (route.name === 'configLayer' && plugin) {
1530
2118
  breadcrumb = `${plugin.displayName} / 设置`;
1531
- title = '选择要查看或修改的设置';
1532
- description = '安装位置与设置保存层是两个不同概念。';
2119
+ const selectedField = route.fieldKey && configPage
2120
+ ? configPage.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
2121
+ : undefined;
2122
+ const selectedPreset = route.presetId && configPage
2123
+ ? configPage.presets.find((preset) => preset.id === route.presetId)
2124
+ : undefined;
2125
+ title = selectedField ? `修改“${selectedField.title}”`
2126
+ : selectedPreset ? `应用“${selectedPreset.title}”` : `修改 ${plugin.displayName} 设置`;
2127
+ description = selectedField
2128
+ ? `当前:${effectiveFieldDisplay(selectedField)} · ${selectedField.effectiveSourceLabel}。选择保存位置后直接编辑。`
2129
+ : selectedPreset ? '选择保存位置后,方案会应用到该位置的草稿。' : '选择修改保存到哪里。';
1533
2130
  help = '↑↓ 移动 · Enter 打开 · Esc 返回';
1534
2131
  actions = [
1535
- { label: '有效设置', description: '合并后的实际结果 · 只读', meta: '查看', run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'effective', projectLayer: 'shared' } }) },
1536
- { label: '个人默认', description: '在所有项目中作为默认值', meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`, run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'user', projectLayer: 'shared' } }) },
1537
- { label: '当前项目', description: '随仓库共享,适合团队约定', meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`, run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'project', projectLayer: 'shared' } }) },
1538
- { label: '当前项目(仅本机)', description: '只保存在本机,不提交到仓库', meta: configLayerCounts.local === undefined ? undefined : configLayerCounts.local === 0 ? '未设置' : `${configLayerCounts.local} 个值`, run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'project', projectLayer: 'local' } }) },
2132
+ { label: '个人默认', description: '在所有项目中作为默认值', meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`, run: () => openConfigLayerTarget({ scope: 'user', projectLayer: 'shared' }) },
2133
+ { label: '当前项目', description: '随仓库共享,适合团队约定', meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`, run: () => openConfigLayerTarget({ scope: 'project', projectLayer: 'shared' }) },
2134
+ { label: '当前项目(仅本机)', description: '只保存在本机,不提交到仓库', meta: configLayerCounts.local === undefined ? undefined : configLayerCounts.local === 0 ? '未设置' : `${configLayerCounts.local} 个值`, run: () => openConfigLayerTarget({ scope: 'project', projectLayer: 'local' }) },
1539
2135
  ];
1540
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2136
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
1541
2137
  }
1542
2138
  else if (route.name === 'configFields' && plugin) {
1543
2139
  const readonly = route.layer.scope === 'effective';
1544
- const selectedGroups = route.groupId
1545
- ? currentConfigPage?.groups.filter((group) => group.id === route.groupId) ?? []
1546
- : currentConfigPage?.groups ?? [];
1547
- breadcrumb = `${plugin.displayName} / 设置 / ${configLayerLabel(route.layer)}`;
1548
- context = readonly ? '只读' : dirty ? `${Object.keys(draftUpdates).length + draftRemoves.length} 项未保存` : undefined;
1549
- title = readonly ? '当前实际使用的设置' : route.groupId ? selectedGroups[0]?.title ?? '高级设置' : configLayerLabel(route.layer);
1550
- description = readonly ? '由默认值、个人默认、当前项目和本机覆盖合并。' : route.groupId ? '这些字段来自 manifest 的高级设置分组。' : '字段来自插件 manifest;修改先保存在本地草稿。';
2140
+ breadcrumb = readonly ? `${plugin.displayName} / 设置` : `${plugin.displayName} / 设置 / ${configLayerLabel(route.layer)}`;
2141
+ title = readonly ? '当前插件设置' : '编辑插件设置';
2142
+ description = readonly
2143
+ ? '查看当前实际生效的值;← → 切换设置分组。'
2144
+ : `保存位置:${configLayerLabel(route.layer)} · ← → 切换设置分组。`;
1551
2145
  if (!readonly && configActionError)
1552
2146
  description = configActionError;
1553
- help = readonly ? '↑↓ 查看 · Esc 返回' : '↑↓ 移动 · Enter 修改 · s 保存 · u 恢复继承 · Esc 返回';
2147
+ help = readonly
2148
+ ? '↑↓ 选择配置 · Enter 修改 · ←→ 分组 · Esc 返回'
2149
+ : '↑↓ 移动 · Enter 修改 · ←→ 分组 · s 保存 · u 恢复继承 · Esc 返回';
1554
2150
  if (!currentConfigPage) {
1555
- content = _jsx(Text, { color: tuiTheme.colors.accent, children: "\u25CF \u6B63\u5728\u8BFB\u53D6\u53EF\u4FE1\u8BBE\u7F6E\u5B9A\u4E49" });
1556
- }
1557
- else if (readonly) {
1558
- actions = selectedGroups.flatMap((group) => group.fields.map((field) => ({
1559
- label: field.title,
1560
- description: `来自${field.sourceLabel}`,
1561
- meta: currentFieldDisplay(field),
1562
- })));
1563
- const limit = policy.mode === 'compact' ? 4 : 7;
1564
- content = _jsx(Bordered, { title: "\u6709\u6548\u8BBE\u7F6E", children: _jsx(ActionRows, { actions: actions, focus: focus, offset: scrollOffset, limit: limit }) });
2151
+ content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u53EF\u4FE1\u8BBE\u7F6E\u5B9A\u4E49", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6 manifest \u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
1565
2152
  }
1566
2153
  else {
2154
+ visibleConfigTabs = configTabs(currentConfigPage);
2155
+ const requestedTab = snapshot.router.current.context.configGroupId;
2156
+ const activeTab = visibleConfigTabs.find((tab) => tab.id === requestedTab) ?? visibleConfigTabs[0];
2157
+ activeConfigTabId = activeTab?.id;
2158
+ const activeGroup = activeTab?.kind === 'group'
2159
+ ? currentConfigPage.groups.find((group) => group.id === activeTab.id)
2160
+ : undefined;
1567
2161
  const mutableLayer = {
1568
2162
  scope: route.layer.scope,
1569
2163
  projectLayer: route.layer.projectLayer,
1570
2164
  };
1571
- const rowGroups = [];
1572
- if (!route.groupId && currentConfigPage.presets.length > 0) {
1573
- rowGroups.push({ id: 'presets', title: '快速方案', rows: [{ label: '当前方案', description: '应用 manifest 声明的预设', meta: `${currentConfigPage.presets.length} 个`, run: () => go({ name: 'configPresets', pluginId: plugin.pluginId, layer: mutableLayer }) }] });
1574
- }
1575
- for (const group of selectedGroups) {
1576
- const rows = group.advanced && !route.groupId
1577
- ? [{ label: group.title, description: group.description ?? '渐进展开高级字段', meta: `${group.fields.length} 项`, run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: route.layer, groupId: group.id }) }]
1578
- : group.fields.map((field) => ({
1579
- label: field.title,
1580
- description: field.description,
1581
- meta: currentFieldDisplay(field),
1582
- configKey: field.key,
1583
- run: () => go({ name: 'configFieldEditor', pluginId: plugin.pluginId, layer: mutableLayer, fieldKey: field.key, editor: fieldEditor(field) }),
1584
- }));
1585
- rowGroups.push({ id: group.id, title: group.advanced && !route.groupId ? '高级设置' : group.title, rows });
1586
- }
1587
- actions = rowGroups.flatMap((group) => group.rows);
1588
- if (!route.groupId)
1589
- actions.push({ label: '保存修改', description: dirty ? '查看安全变更汇总' : '当前没有未保存修改', disabled: dirty ? undefined : '当前没有未保存修改', run: beginConfigSave }, {
2165
+ const tabActions = activeTab?.kind === 'presets'
2166
+ ? currentConfigPage.presets.map((preset) => ({
2167
+ label: preset.title,
2168
+ description: (preset.privateFieldTitles?.length ?? 0) > 0
2169
+ ? `${preset.description ? `${preset.description} · ` : ''}${preset.privateFieldTitles.join('、')}需手动输入`
2170
+ : preset.description,
2171
+ run: readonly
2172
+ ? () => go({
2173
+ name: 'configLayer',
2174
+ pluginId: plugin.pluginId,
2175
+ presetId: preset.id,
2176
+ })
2177
+ : () => applyConfigPreset(currentConfigPage, preset.id),
2178
+ }))
2179
+ : (activeGroup?.fields ?? []).map((field) => ({
2180
+ label: field.title,
2181
+ description: readonly
2182
+ ? field.sourceLabel === '未设置' ? '没有任何位置提供值' : `来自${field.sourceLabel}`
2183
+ : `${field.description ? `${field.description} · ` : ''}当前有效:${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`,
2184
+ meta: readonly ? effectiveFieldDisplay(field) : currentFieldDisplay(field),
2185
+ configKey: field.key,
2186
+ run: readonly
2187
+ ? () => go({
2188
+ name: 'configLayer',
2189
+ pluginId: plugin.pluginId,
2190
+ fieldKey: field.key,
2191
+ groupId: activeGroup.id,
2192
+ })
2193
+ : () => go({
2194
+ name: 'configFieldEditor',
2195
+ pluginId: plugin.pluginId,
2196
+ layer: mutableLayer,
2197
+ fieldKey: field.key,
2198
+ editor: fieldEditor(field),
2199
+ }),
2200
+ }));
2201
+ const operationActions = readonly ? [] : [
2202
+ { label: '保存修改', description: dirty ? '查看安全变更汇总' : '当前没有未保存修改', disabled: dirty ? undefined : '当前没有未保存修改', run: beginConfigSave },
2203
+ {
1590
2204
  label: '重置此设置层…',
1591
2205
  description: '删除本层全部值并恢复继承',
1592
2206
  status: 'error',
1593
2207
  disabled: currentConfigPage.storedCount === 0 ? '本层没有已存值' : undefined,
1594
2208
  run: () => beginReview({ kind: 'configReset', pluginId: plugin.pluginId, scope: route.layer.scope, projectLayer: route.layer.projectLayer }),
1595
- });
1596
- let rowOffset = 0;
1597
- content = policy.mode === 'compact' || scrollOffset > 0
1598
- ? _jsx(Bordered, { title: "\u8BBE\u7F6E\u5B57\u6BB5", children: _jsx(ActionRows, { actions: actions, focus: focus, offset: scrollOffset, limit: policy.mode === 'compact' ? 4 : 7 }) })
1599
- : _jsxs(Box, { flexDirection: "column", children: [rowGroups.map((group) => {
1600
- const offset = rowOffset;
1601
- rowOffset += group.rows.length;
1602
- return _jsx(Bordered, { title: group.title, children: _jsx(ActionRows, { actions: actions, focus: focus, offset: offset, limit: group.rows.length }) }, group.id);
1603
- }), !route.groupId ? _jsx(ActionRows, { actions: actions, focus: focus, offset: rowOffset }) : null] });
2209
+ },
2210
+ ];
2211
+ actions = [...tabActions, ...operationActions];
2212
+ const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
2213
+ const fieldFocus = tabActions.length > 0
2214
+ ? effectiveConfigFocus < tabActions.length ? effectiveConfigFocus : Math.min(scrollOffset, tabActions.length - 1)
2215
+ : 0;
2216
+ const operationRows = operationActions.reduce((rows, action) => rows + (policy.mode === 'compact' || !action.description ? 1 : 2), 0);
2217
+ const viewport = actionViewport(tabActions, policy.mode === 'compact', fieldFocus, scrollOffset, pageActionBudget(1 + operationRows + 3));
2218
+ const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
2219
+ const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
2220
+ context = readonly ? countLabel
2221
+ : dirty ? `${Object.keys(draftUpdates).length + draftRemoves.length} 项未保存` : countLabel;
2222
+ content = _jsxs(Box, { flexDirection: "column", children: [activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
2223
+ ? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: effectiveConfigFocus, kind: "section", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })
2224
+ : null, operationActions.length > 0
2225
+ ? _jsx(SemanticRows, { actions: actions, focus: effectiveConfigFocus, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
2226
+ : null] });
1604
2227
  }
1605
2228
  }
1606
2229
  else if (route.name === 'configFieldEditor' && plugin && activeField) {
@@ -1613,16 +2236,16 @@ export function ConsoleAppV4(props) {
1613
2236
  description = '保存值仍是 manifest 中的原始 enum token。';
1614
2237
  actions = (activeField.enumValues ?? []).map((value) => ({
1615
2238
  label: `${activeField.valueLabels?.[value] ?? value}${activeField.recommendedValue === value ? '(推荐)' : ''}`,
1616
- meta: draftUpdates[activeField.key] === value || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === value) ? '●' : '○',
2239
+ selected: draftUpdates[activeField.key] === value || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === value),
1617
2240
  run: () => { updateDraft(activeField.key, value); back(); },
1618
2241
  }));
1619
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2242
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
1620
2243
  }
1621
2244
  else if (route.editor === 'boolean') {
1622
2245
  title = `${activeField.title}?`;
1623
2246
  description = activeField.description ?? '选择开启或关闭。';
1624
- actions = [{ label: '开启', run: () => { updateDraft(activeField.key, true); back(); } }, { label: '关闭', run: () => { updateDraft(activeField.key, false); back(); } }];
1625
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2247
+ actions = [{ label: '开启', selected: draftUpdates[activeField.key] === true || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === true), run: () => { updateDraft(activeField.key, true); back(); } }, { label: '关闭', selected: draftUpdates[activeField.key] === false || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === false), run: () => { updateDraft(activeField.key, false); back(); } }];
2248
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
1626
2249
  }
1627
2250
  else {
1628
2251
  title = route.editor === 'number' ? `输入${activeField.title}` : route.editor === 'path' ? `选择${activeField.title}` : route.editor === 'sensitive' ? '输入敏感值' : `输入${activeField.title}`;
@@ -1633,55 +2256,25 @@ export function ConsoleAppV4(props) {
1633
2256
  const visibleInput = route.editor === 'sensitive'
1634
2257
  ? '•'.repeat(inputValue.length)
1635
2258
  : route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
1636
- content = _jsxs(Bordered, { children: [_jsx(Text, { children: visibleInput || activeField.placeholder || ' ' }), _jsx(Text, { color: inputError ? tuiTheme.statusColors.error : tuiTheme.colors.muted, children: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。') })] });
1637
- }
1638
- }
1639
- else if (route.name === 'configPresets' && plugin) {
1640
- breadcrumb = `${plugin.displayName} / 设置 / 预设`;
1641
- title = '选择快速方案';
1642
- description = '预设由插件 manifest 声明;应用后仍可逐项修改。';
1643
- actions = (currentConfigPage?.presets ?? []).map((preset) => ({
1644
- label: preset.title,
1645
- description: (preset.privateFieldTitles?.length ?? 0) > 0
1646
- ? `${preset.description ? `${preset.description} · ` : ''}${preset.privateFieldTitles.join('、')}需手动输入`
1647
- : preset.description,
1648
- run: () => {
1649
- const fields = new Map(currentConfigPage?.groups.flatMap((group) => group.fields)
1650
- .map((field) => [field.key, field]));
1651
- const nextUpdates = { ...draftUpdates };
1652
- for (const [key, value] of Object.entries(preset.values)) {
1653
- const field = fields.get(key);
1654
- if (field?.configured && field.value === value)
1655
- delete nextUpdates[key];
1656
- else
1657
- nextUpdates[key] = value;
1658
- }
1659
- const nextRemoves = draftRemoves.filter((key) => !Object.hasOwn(preset.values, key));
1660
- setDraftUpdates(nextUpdates);
1661
- setDraftRemoves(nextRemoves);
1662
- setDirty(Object.keys(nextUpdates).length + nextRemoves.length > 0);
1663
- back();
1664
- },
1665
- }));
1666
- content = currentConfigPage ? _jsx(ActionRows, { actions: actions, focus: focus }) : _jsx(Text, { color: tuiTheme.colors.accent, children: "\u25CF \u6B63\u5728\u8BFB\u53D6\u9884\u8BBE" });
2259
+ content = _jsx(InputCard, { focused: true, title: visibleInput || activeField.placeholder || ' ', description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
2260
+ }
1667
2261
  }
1668
2262
  else if (route.name === 'configUnavailable' && plugin) {
1669
2263
  breadcrumb = `${plugin.displayName} / 设置`;
1670
2264
  context = '暂时不可编辑';
1671
- title = '暂时无法编辑设置';
1672
- description = '当前无法取得可信的字段定义,因此不能验证新值。';
2265
+ const diagnostic = configUnavailableDiagnostic(route);
2266
+ title = diagnostic.title;
2267
+ description = '';
1673
2268
  actions = [
1674
2269
  { label: '重新检查来源', description: '刷新当前来源后重新读取可信定义', run: async () => {
1675
2270
  await props.controller.refreshSource(plugin.sourceId);
1676
2271
  setConfigLoadKey((value) => value + 1);
1677
2272
  props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: route.layer ?? { scope: 'user', projectLayer: 'shared' } });
1678
2273
  } },
1679
- ...(configResetAvailability?.sourceIdentityPresent
1680
- ? [{ label: '技术详情', description: '查看可信身份和设置层摘要', run: () => setShowTechnical((value) => !value) }]
1681
- : []),
2274
+ { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => setShowTechnical((value) => !value) },
1682
2275
  { label: '返回', run: back },
1683
2276
  ];
1684
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.error, children: "\u00D7 Manifest \u6216\u6765\u6E90\u4E0D\u53EF\u7528" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8\u3002\u6062\u590D\u6765\u6E90\u540E\u624D\u80FD\u7F16\u8F91\u6216\u91CD\u7F6E\u3002" })] }), showTechnical && configResetAvailability?.sourceIdentityPresent ? _jsxs(Bordered, { title: "\u6280\u672F\u8BE6\u60C5", children: [_jsxs(Text, { children: ["\u6765\u6E90\u8EAB\u4EFD\uFF1A", configResetAvailability.sourceIdentity] }), _jsxs(Text, { children: ["\u8BBE\u7F6E\u6458\u8981\uFF1A", configResetAvailability.storeDigest] })] }) : null, _jsx(ActionRows, { actions: actions, focus: focus })] });
2277
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
1685
2278
  }
1686
2279
  else if (route.name === 'sourceList') {
1687
2280
  breadcrumb = '插件来源';
@@ -1709,35 +2302,71 @@ export function ConsoleAppV4(props) {
1709
2302
  }),
1710
2303
  { label: '+ 添加来源', description: 'Git 仓库或本地目录', run: beginAddSource },
1711
2304
  ];
1712
- const viewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1713
- content = _jsx(ActionRows, { actions: actions, focus: focus, offset: viewport.offset, limit: viewport.limit });
2305
+ const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
2306
+ const sourceCount = Math.max(0, actions.length - 1);
2307
+ content = _jsx(Box, { flexDirection: "column", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: focus < sourceCount ? 'section' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) });
1714
2308
  }
1715
2309
  else if (route.name === 'sourceDetail') {
1716
2310
  const overview = props.controller.getSourceOverview(route.sourceId);
1717
2311
  const detail = overview.detail;
1718
2312
  const state = overview.health?.state ?? overview.source?.state;
2313
+ const diagnostic = sourceDiagnostic(route.sourceId, state, detail);
1719
2314
  breadcrumb = `插件来源 / ${route.sourceId}`;
1720
2315
  context = detail?.type === 'path' ? '本地目录' : 'Git';
1721
2316
  title = route.sourceId;
1722
- description = state === 'cache' || state === 'degraded' ? '当前使用已缓存的 Catalog 数据。'
1723
- : state === 'unavailable' ? '当前无法读取这个来源。' : '来源状态可用。';
2317
+ description = diagnostic ? '' : '来源状态可用。';
1724
2318
  help = '↑↓ 移动 · Enter 选择 · r 重新检查 · Esc 返回';
1725
- actions = [
1726
- { label: '重新检查来源', description: '读取远端并更新缓存', run: () => props.controller.refreshSource(route.sourceId) },
1727
- { label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
1728
- { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
1729
- { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
1730
- { label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
1731
- ];
1732
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { title: "\u5F53\u524D\u72B6\u6001", children: _jsx(Text, { color: state === 'unavailable' ? tuiTheme.statusColors.error : state === 'cache' || state === 'degraded' ? tuiTheme.statusColors.warning : tuiTheme.statusColors.success, children: state === 'checking' ? '● 正在检查' : state === 'unavailable' ? '× 无法读取' : state === 'cache' || state === 'degraded' ? '◐ 使用缓存' : '✓ 可用' }) }), _jsxs(Bordered, { title: "\u6765\u6E90\u914D\u7F6E", children: [_jsxs(Text, { children: ["\u4F4D\u7F6E ", detail?.locationSummary ?? '不可用'] }), _jsxs(Text, { children: ["\u7248\u672C\u9009\u62E9 ", detail?.type === 'path' ? '本地目录' : detail?.refPolicy === 'explicit' ? '固定 Git 引用' : '跟随 AW 版本通道'] }), detail?.explicitRef ? _jsxs(Text, { children: ["\u5F15\u7528 ", detail.explicitRef] }) : null, _jsxs(Text, { children: ["\u63D2\u4EF6 ", detail?.pluginCount ?? 0, " \u4E2A"] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2319
+ actions = diagnostic
2320
+ ? [
2321
+ { label: '重新检查来源', description: '读取远端并验证可用数据', run: () => props.controller.refreshSource(route.sourceId) },
2322
+ ...(state === 'degraded' ? [{
2323
+ label: '继续使用缓存',
2324
+ description: '浏览匹配且已验证的缓存数据',
2325
+ run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
2326
+ }] : []),
2327
+ { label: '编辑来源…', description: '检查 URL、路径或 ref', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2328
+ { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2329
+ { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2330
+ { label: '技术详情', description: '查看脱敏错误代码和解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2331
+ ]
2332
+ : [
2333
+ { label: '重新检查来源', description: '读取远端并更新缓存', run: () => props.controller.refreshSource(route.sourceId) },
2334
+ { label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2335
+ { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2336
+ { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2337
+ { label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2338
+ ];
2339
+ const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 6));
2340
+ content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
2341
+ ? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
2342
+ : _jsxs(_Fragment, { children: [_jsx(StatusCard, { status: state === 'checking' ? 'warning' : 'success', title: state === 'checking' ? '正在检查' : '可用' }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u63D2\u4EF6", meta: `${detail?.pluginCount ?? 0} 个` }), policy.mode === 'compact' ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u9009\u62E9", meta: detail?.type === 'path' ? '本地目录' : detail?.refPolicy === 'explicit' ? '固定 Git 引用' : '跟随 AW 版本通道' }), detail?.explicitRef ? _jsx(StaticRow, { title: "\u5F15\u7528", meta: detail.explicitRef }) : null] })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
1733
2343
  }
1734
2344
  else if (route.name === 'sourceTechnical') {
1735
- const detail = props.controller.getSourceOverview(route.sourceId).detail;
2345
+ const overview = props.controller.getSourceOverview(route.sourceId);
2346
+ const detail = overview.detail;
2347
+ const state = overview.health?.state ?? overview.source?.state;
2348
+ const diagnostic = sourceDiagnostic(route.sourceId, state, detail) ?? {
2349
+ severity: 'warning',
2350
+ title: '来源解析详情',
2351
+ userMessage: '来源当前可用。',
2352
+ impact: [{ state: 'preserved', message: '当前来源状态没有阻止操作。' }],
2353
+ nextStep: '返回来源详情。',
2354
+ technical: {
2355
+ code: 'catalogSourceReady',
2356
+ stage: 'resolve-source',
2357
+ sourceId: route.sourceId,
2358
+ ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
2359
+ ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
2360
+ ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
2361
+ },
2362
+ };
1736
2363
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
1737
2364
  context = '只读';
1738
- title = '来源身份与解析详情';
1739
- description = '用于诊断和安全审查;不显示认证信息或绝对路径。';
1740
- content = _jsxs(Bordered, { children: [_jsxs(Text, { children: ["sourceId ", route.sourceId] }), _jsxs(Text, { children: ["sourceIdentity ", detail?.sourceIdentity ?? '不可用'] }), _jsxs(Text, { children: ["requestedRef ", detail?.requestedRef ?? '不可用'] }), detail?.resolvedCommit ? _jsxs(Text, { children: ["resolvedCommit ", detail.resolvedCommit] }) : null, _jsxs(Text, { children: ["freshness ", detail?.freshness ?? '不可用'] }), _jsxs(Text, { children: ["configDigest ", detail?.configDigest ?? '不可用'] })] });
2365
+ title = '来源技术诊断';
2366
+ description = '';
2367
+ help = 'Enter 返回 · Esc 返回';
2368
+ actions = [{ label: '返回来源详情', run: back }];
2369
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: true, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
1741
2370
  }
1742
2371
  else if (route.name === 'sourceAdd' && sourceDraft?.owner === route.owner) {
1743
2372
  breadcrumb = sourceDraft.mode === 'add' ? '插件来源 / 添加' : `插件来源 / ${sourceDraft.sourceId} / 编辑`;
@@ -1751,7 +2380,7 @@ export function ConsoleAppV4(props) {
1751
2380
  { label: 'Git 仓库', description: '从远端 Git 仓库读取 Catalog', run: () => { setSourceDraft({ ...sourceDraft, type: 'git' }); go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner }); } },
1752
2381
  { label: '本地目录', description: '用于本地开发与测试', run: () => { setSourceDraft({ ...sourceDraft, type: 'path' }); go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner }); } },
1753
2382
  ];
1754
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2383
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
1755
2384
  }
1756
2385
  else if (route.step === 'refPolicy') {
1757
2386
  title = '选择版本引用方式';
@@ -1763,7 +2392,7 @@ export function ConsoleAppV4(props) {
1763
2392
  go({ name: 'sourceAdd', step: 'subdir', owner: route.owner }); } },
1764
2393
  { label: '固定 Git 引用', description: 'branch、tag 或 commit', run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
1765
2394
  ];
1766
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2395
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
1767
2396
  }
1768
2397
  else {
1769
2398
  title = route.step === 'id' ? '输入来源 ID' : route.step === 'location' ? `输入 ${sourceDraft.type === 'git' ? 'Git URL' : '绝对路径'}` : route.step === 'ref' ? '输入固定 Git 引用' : '输入 Catalog 子目录';
@@ -1773,7 +2402,7 @@ export function ConsoleAppV4(props) {
1773
2402
  : route.step === 'location' && sourceDraft.type === 'path'
1774
2403
  ? privatePathInput(sourceInput)
1775
2404
  : sourceInput;
1776
- content = _jsxs(Bordered, { children: [_jsx(Text, { children: displayedInput || ' ' }), _jsx(Text, { color: sourceInputError ? tuiTheme.statusColors.error : tuiTheme.colors.muted, children: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。') })] });
2405
+ content = _jsx(InputCard, { focused: true, title: displayedInput || ' ', description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
1777
2406
  }
1778
2407
  }
1779
2408
  else if (route.name === 'sourceEdit') {
@@ -1794,7 +2423,7 @@ export function ConsoleAppV4(props) {
1794
2423
  { label: '版本引用', description: detail.refPolicy === 'explicit' ? `固定:${detail.explicitRef}` : '跟随 AW 版本通道', run: () => startEdit('refPolicy') },
1795
2424
  { label: 'Catalog 子目录', description: detail.subdir ?? '未设置', run: () => startEdit('subdir') },
1796
2425
  ] : [{ label: '本地目录', description: '已选择本地目录', run: () => startEdit('location') }];
1797
- content = _jsx(ActionRows, { actions: actions, focus: focus });
2426
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
1798
2427
  }
1799
2428
  else if (route.name === 'sourceRemoveBlocked') {
1800
2429
  breadcrumb = `插件来源 / ${route.sourceId} / 移除`;
@@ -1803,7 +2432,7 @@ export function ConsoleAppV4(props) {
1803
2432
  description = sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整,不能把未知引用当作零。' : '仍有内容依赖这个来源。';
1804
2433
  actions = [{ label: sourceDependenciesExpanded ? '收起依赖内容' : '查看依赖内容', description: '只显示插件、来源、平台、位置和类别聚合', run: () => setSourceDependenciesExpanded((value) => !value) }, { label: '返回来源详情', run: back }];
1805
2434
  const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
1806
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { children: _jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用'] }) }), _jsxs(Bordered, { children: [_jsxs(Text, { children: ["\u5B89\u88C5\u4F4D\u7F6E ", countLabel(sourceReferenceSummary?.installationCount, '个')] }), _jsxs(Text, { children: ["\u63D2\u4EF6\u8BBE\u7F6E ", countLabel(sourceReferenceSummary?.configCount, '份')] }), _jsxs(Text, { children: ["\u751F\u547D\u5468\u671F\u72B6\u6001 ", countLabel(sourceReferenceSummary?.lifecycleCount, '个')] }), _jsxs(Text, { children: ["Marketplace ", countLabel(sourceReferenceSummary?.marketplaceCount, '个')] }), sourceDependenciesExpanded ? sourceReferenceSummary?.dependencies.slice(0, policy.mode === 'compact' ? 2 : 5).map((dependency, index) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [dependency.category, " \u00B7 ", dependency.pluginId ?? dependency.sourceId ?? '身份未知', dependency.platform ? ` · ${platformLabel(dependency.platform)}` : '', dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''] }, `${dependency.category}:${dependency.pluginId ?? dependency.sourceId ?? index}`)) : null] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2435
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: "\u5F15\u7528\u6C47\u603B", children: [_jsx(StaticRow, { title: "\u5B89\u88C5\u4F4D\u7F6E", meta: countLabel(sourceReferenceSummary?.installationCount, '个') }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u8BBE\u7F6E", meta: countLabel(sourceReferenceSummary?.configCount, '份') }), _jsx(StaticRow, { title: "\u751F\u547D\u5468\u671F\u72B6\u6001", meta: countLabel(sourceReferenceSummary?.lifecycleCount, '个') }), _jsx(StaticRow, { title: "Marketplace", meta: countLabel(sourceReferenceSummary?.marketplaceCount, '个') }), sourceDependenciesExpanded ? sourceReferenceSummary?.dependencies.slice(0, Math.max(1, pageActionBudget(10))).map((dependency, index) => _jsx(StaticRow, { title: `${dependency.category} · ${dependency.pluginId ?? dependency.sourceId ?? '身份未知'}`, meta: `${dependency.platform ? platformLabel(dependency.platform) : ''}${dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''}` }, `${dependency.category}:${dependency.pluginId ?? dependency.sourceId ?? index}`)) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
1807
2436
  }
1808
2437
  else if (route.name === 'issueList') {
1809
2438
  const issues = props.controller.listUserIssues();
@@ -1835,8 +2464,8 @@ export function ConsoleAppV4(props) {
1835
2464
  : go({ name: 'issueDetail', issueId: entry.issueId }),
1836
2465
  }));
1837
2466
  }
1838
- const viewport = actionViewport(route.name, actions, policy.mode, focus, scrollOffset);
1839
- content = _jsx(ActionRows, { actions: actions, focus: focus, offset: viewport.offset, limit: viewport.limit });
2467
+ const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
2468
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: issues.length > 0 ? 'task' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
1840
2469
  }
1841
2470
  else if (route.name === 'issueDetail' && issue) {
1842
2471
  breadcrumb = `需要处理 / ${issue.pluginId ?? issue.sourceId ?? issue.entity.id}`;
@@ -1854,7 +2483,9 @@ export function ConsoleAppV4(props) {
1854
2483
  else if (entry.id === 'continueWithCache' || entry.id === 'retryLater')
1855
2484
  go({ name: 'issueList' });
1856
2485
  else if (entry.id === 'editSource' && issue.sourceId)
1857
- go({ name: 'sourceDetail', sourceId: issue.sourceId });
2486
+ go(issue.entity.kind === 'source'
2487
+ ? { name: 'sourceEdit', sourceId: issue.sourceId }
2488
+ : { name: 'sourceDetail', sourceId: issue.sourceId });
1858
2489
  else if (entry.id === 'reAddSource' && issue.sourceId)
1859
2490
  beginIssueSourceAdd(issue.sourceId);
1860
2491
  else if (entry.id === 'viewSourceChange' && issue.sourceId)
@@ -1878,8 +2509,9 @@ export function ConsoleAppV4(props) {
1878
2509
  else
1879
2510
  setShowTechnical((value) => !value);
1880
2511
  };
1881
- actions = issue.actions.map((entry) => ({
1882
- label: entry.label,
2512
+ const issueHasTechnicalDetails = Boolean(issue.diagnostic.technical);
2513
+ actions = issue.actions.filter((entry) => entry.id !== 'viewTechnical' || issueHasTechnicalDetails).map((entry) => ({
2514
+ label: entry.id === 'viewTechnical' && showTechnical ? '返回问题摘要' : entry.label,
1883
2515
  description: entry.description,
1884
2516
  status: entry.id === 'remove'
1885
2517
  || entry.id === 'keepUserScope' || entry.id === 'keepProjectScope' ? 'error' : undefined,
@@ -1887,7 +2519,14 @@ export function ConsoleAppV4(props) {
1887
2519
  }));
1888
2520
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
1889
2521
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
1890
- const manualMigration = issue.category === 'legacyState' && showTechnical;
2522
+ const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, compact: policy.mode === 'compact' || issue.category.startsWith('dependency') });
2523
+ const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
2524
+ const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
2525
+ ? {
2526
+ offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)),
2527
+ limit: 2,
2528
+ }
2529
+ : {};
1891
2530
  if (issue.category === 'dependencyStillRequired' && issuePlugin) {
1892
2531
  const blockingDependents = issue.platform && issue.scope
1893
2532
  ? props.controller.getBlockingDependents(issuePlugin.pluginId, issue.platform, issue.scope)
@@ -1897,51 +2536,112 @@ export function ConsoleAppV4(props) {
1897
2536
  actions = [
1898
2537
  { label: '查看完整依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
1899
2538
  { label: '返回插件详情', run: () => go({ name: 'pluginDetail', pluginId: issuePlugin.pluginId }) },
2539
+ ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) }] : []),
1900
2540
  ];
1901
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { children: _jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", blockingDependents.length, " \u4E2A\u5DF2\u5B89\u88C5\u63D2\u4EF6\u4ECD\u5728\u4F7F\u7528"] }) }), _jsx(Bordered, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", children: blockingDependents.map((dependent) => _jsxs(Text, { children: [dependent.displayName, " ", _jsx(Text, { color: tuiTheme.colors.muted, children: dependentTargetLabel(dependent) })] }, dependent.pluginId)) }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2541
+ const dependencyBlockedViewport = policy.mode === 'compact' && showTechnical
2542
+ ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
2543
+ : actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
2544
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, showTechnical ? null : _jsx(SectionCard, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", children: blockingDependents.map((dependent) => _jsx(StaticRow, { title: dependent.displayName, meta: dependentTargetLabel(dependent) }, dependent.pluginId)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: dependencyBlockedViewport.offset, limit: dependencyBlockedViewport.limit })] });
2545
+ }
2546
+ else if ((issue.category === 'dependencyMissing' || issue.category === 'dependencyNotReady') && issuePlugin) {
2547
+ const dependencyName = issue.dependencyPluginId
2548
+ ? props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId
2549
+ : '必需依赖';
2550
+ const dependencyTarget = [
2551
+ issue.dependencyPlatform ? platformLabel(issue.dependencyPlatform) : undefined,
2552
+ issue.dependencyScope ? scopeLabel(issue.dependencyScope) : undefined,
2553
+ ].filter((entry) => Boolean(entry)).join(' · ');
2554
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', ...compactTechnicalActionViewport })] });
1902
2555
  }
1903
2556
  else if (issue.category === 'dependencyTargetUnsupported' && issuePlugin) {
1904
- const compatible = selectedPlatforms(issuePlugin, issue.scope ?? scope).filter((entry) => !entry.disabled);
1905
- title = `无法添加到 ${issue.platform ? platformLabel(issue.platform) : '所选工具'}`;
2557
+ const issueScope = issue.scope ?? scope;
2558
+ const compatible = props.controller.listInstallTargets(issuePlugin.pluginId, issueScope)
2559
+ .filter((entry) => entry.compatible);
2560
+ title = `无法添加到 ${issue.platform ? platformLabel(issue.platform) : '所选目标平台'}`;
1906
2561
  description = issue.dependencyPluginId
1907
- ? `${props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId} 不支持所选工具,因此完整插件计划不能运行。`
1908
- : '完整插件计划不支持所选工具。';
2562
+ ? `${props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId} 不支持所选目标平台,因此完整插件计划不能运行。`
2563
+ : '完整插件计划不支持所选目标平台。';
1909
2564
  actions = [
1910
- ...compatible.map((entry) => ({ label: `只添加到 ${platformLabel(entry.platform)}`, description: '返回位置页并使用完整依赖检查', run: () => {
1911
- setScope(issue.scope ?? scope);
1912
- setTools([entry.platform]);
1913
- go({ name: 'installPlacement', pluginId: issuePlugin.pluginId });
2565
+ ...compatible.map((entry) => ({ label: `只添加到 ${platformLabel(entry.platform)}`, description: '返回目标平台页并使用完整依赖检查', run: () => {
2566
+ setScope(issueScope);
2567
+ setSelectedTargets([entry.platform]);
2568
+ setTargetSelections((current) => ({ ...current, [issueScope]: [entry.platform] }));
2569
+ go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
1914
2570
  } })),
1915
- { label: '返回调整工具', run: () => go({ name: 'installTools', pluginId: issuePlugin.pluginId }) },
2571
+ { label: '返回调整目标平台', run: () => {
2572
+ setScope(issueScope);
2573
+ setSelectedTargets([]);
2574
+ setTargetSelections((current) => ({ ...current, [issueScope]: [] }));
2575
+ go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
2576
+ } },
1916
2577
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
2578
+ ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) }] : []),
1917
2579
  ];
1918
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", issue.dependencyPluginId ? `${props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId} 不支持所选工具` : '工具不兼容'] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u53EF\u7528\u5DE5\u5177\u4ECD\u4FDD\u6301\u53EF\u9009\uFF0C\u4E0D\u517C\u5BB9\u5DE5\u5177\u4E0D\u4F1A\u8FDB\u5165\u786E\u8BA4\u9875\u3002" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
2580
+ const unsupportedTargetViewport = policy.mode === 'compact' && showTechnical
2581
+ ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
2582
+ : actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
2583
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: unsupportedTargetViewport.offset, limit: unsupportedTargetViewport.limit })] });
1919
2584
  }
1920
2585
  else if (issue.category === 'dependencyGraphInvalid') {
1921
2586
  title = `暂时无法添加 ${issuePlugin?.displayName ?? issue.pluginId ?? '插件'}`;
1922
2587
  description = '来源中的依赖关系无效或安装记录不能安全验证。没有修改任何安装。';
1923
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.statusColors.error, children: ["\u00D7 ", issue.rootCode ?? issue.category] }), _jsx(Text, { color: tuiTheme.colors.muted, children: issue.explanation })] }), _jsx(ActionRows, { actions: actions, focus: focus }), showTechnical ? _jsx(Bordered, { title: "\u6280\u672F\u8BE6\u60C5", children: issue.technicalDetails.map((detail, index) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [detail.code, " \u00B7 ", detail.message] }, `${detail.code}:${index}`)) }) : _jsx(Text, { color: tuiTheme.colors.muted, children: "d \u67E5\u770B\u5B89\u5168\u9519\u8BEF\u4EE3\u7801" })] });
2588
+ const sourceId = issue.sourceId;
2589
+ if (sourceId) {
2590
+ actions = [
2591
+ { label: '查看来源问题', description: '进入来源页并重新检查 Catalog', run: () => go({ name: 'sourceDetail', sourceId }) },
2592
+ ...actions,
2593
+ ];
2594
+ }
2595
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
1924
2596
  }
1925
2597
  else {
1926
2598
  const sourceMissingCleanup = issue.category === 'sourceMissing' && issue.pluginId && issue.platform && issue.scope
1927
2599
  ? `aw cleanup ${issue.pluginId} --platform ${issue.platform} --scope ${issue.scope}`
1928
2600
  : undefined;
1929
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { title: "\u5F71\u54CD\u8303\u56F4", children: [issue.impact.map((line) => _jsx(Text, { children: line }, line)), sourceMissingCleanup ? _jsxs(_Fragment, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u53EF\u6E05\u7406\u53EF\u4FE1\u7684 AW \u73B0\u573A\uFF1A" }), _jsx(Text, { children: sourceMissingCleanup }), _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u5982\u9700\u8DF3\u8FC7 dependent \u4FDD\u62A4\uFF0C\u5728\u547D\u4EE4\u672B\u5C3E\u6DFB\u52A0 --force\u3002" })] }) : null, issue.category === 'sourceIdentityChanged' ? _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u8BF7\u786E\u8BA4\u6765\u6E90\u53D8\u66F4\uFF0C\u6216\u6062\u590D\u539F identity\uFF1B\u5F53\u524D\u4E0D\u81EA\u52A8\u8FC1\u79FB\u6216\u6E05\u7406\u3002" }) : null, issue.placements ? _jsxs(Text, { children: ["\u4F4D\u7F6E\uFF1A", issue.placements.map(scopeLabel).join('、')] }) : null, issue.requiredFields ? _jsxs(Text, { children: ["\u5B57\u6BB5\uFF1A", issue.requiredFields.map((field) => field.title).join('、')] }) : null] }), _jsx(ActionRows, { actions: actions, focus: focus, offset: policy.mode === 'compact' ? scrollOffset : undefined, limit: policy.mode === 'compact' ? 4 : 7 }), manualMigration ? _jsxs(Bordered, { title: "\u624B\u52A8\u5904\u7406\u8BF4\u660E", children: [_jsx(Text, { children: "1. \u4E0D\u81EA\u52A8\u8FC1\u79FB\u65E7\u7248\u72B6\u6001\u3002" }), _jsx(Text, { children: "2. \u8FD0\u884C aw status --json\uFF0C\u8BB0\u5F55\u516C\u5F00\u9519\u8BEF code\u3002" }), _jsx(Text, { children: "3. \u5907\u4EFD\u76F8\u5173 AW \u914D\u7F6E\u3002" }), _jsx(Text, { children: "4. \u6309\u6280\u672F\u8BE6\u60C5\u4F7F\u7528\u53D7\u652F\u6301\u547D\u4EE4\u6216\u4EBA\u5DE5\u5904\u7406\u3002" }), _jsx(Text, { children: "5. \u518D\u6B21\u8FD0\u884C\u72B6\u6001\u68C0\u67E5\u3002" })] }) : showTechnical ? _jsx(Bordered, { title: "\u6280\u672F\u8BE6\u60C5", children: issue.technicalDetails.map((detail, index) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [detail.code, " \u00B7 ", detail.message] }, `${detail.code}:${index}`)) }) : _jsx(Text, { color: tuiTheme.colors.muted, children: "d \u6216\u201C\u6280\u672F\u8BE6\u60C5\u201D\u67E5\u770B\u5B89\u5168\u9519\u8BEF\u4EE3\u7801" })] });
2601
+ const hasRecoveryEvidence = sourceMissingCleanup || issue.category === 'sourceIdentityChanged'
2602
+ || issue.placements || issue.requiredFields;
2603
+ const recoveryRows = _jsxs(_Fragment, { children: [sourceMissingCleanup ? _jsxs(_Fragment, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: sourceMissingCleanup }), _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u9700\u8981\u65F6\u53EF\u5728\u547D\u4EE4\u672B\u5C3E\u6DFB\u52A0 --force\u3002" })] }) : null, issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
2604
+ content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && hasRecoveryEvidence
2605
+ ? policy.mode === 'compact' ? recoveryRows : _jsx(SectionCard, { title: "\u6062\u590D\u8BC1\u636E", children: recoveryRows })
2606
+ : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: issueActionViewport.offset, limit: issueActionViewport.limit })] });
1930
2607
  }
1931
2608
  }
2609
+ else if (route.name === 'reviewPreparing' && snapshot.reviewPreparation) {
2610
+ const preparation = snapshot.reviewPreparation;
2611
+ const labels = preparation.intent.kind === 'portableInstall'
2612
+ ? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
2613
+ : preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
2614
+ ? ['设置', '正在准备设置确认']
2615
+ : preparation.intent.kind === 'sourceMutation'
2616
+ ? ['插件来源', '正在准备来源变更确认']
2617
+ : ['插件操作', '正在准备插件操作确认'];
2618
+ breadcrumb = `${labels[0]} / 准备确认`;
2619
+ context = '只读';
2620
+ title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
2621
+ description = preparation.state === 'failed'
2622
+ ? preparation.message ?? '无法准备确认信息。请返回检查选择,或重试。'
2623
+ : '这个阶段只读取和验证状态,不会修改任何安装或设置。';
2624
+ help = preparation.state === 'running'
2625
+ ? 'Esc 取消 · Ctrl-C 完成后退出'
2626
+ : '↑↓ 移动 · Enter 选择 · Esc 返回';
2627
+ actions = preparation.state === 'failed' ? [
2628
+ { label: '重试', description: '重新读取和验证当前状态', run: () => {
2629
+ props.controller.retryReviewPreparation();
2630
+ setFocus(0);
2631
+ setScrollOffset(0);
2632
+ } },
2633
+ { label: '返回修改', run: back },
2634
+ ] : [];
2635
+ const stageState = (state) => state === 'running' ? 'active' : state === 'completed' ? 'complete' : state === 'failed' ? 'error' : 'pending';
2636
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u51C6\u5907\u6B65\u9AA4", children: preparation.stages.map((stage) => _jsx(ProgressRow, { title: stage.label, state: stageState(stage.state), meta: stage.state === 'running' ? '进行中' : stage.state === 'completed' ? '完成' : stage.state === 'failed' ? '失败' : '未开始', compact: policy.mode === 'compact' }, stage.id)) }), preparation.currentLabel ? _jsx(StaticRow, { title: "\u5F53\u524D", meta: preparation.currentLabel, status: "muted" }) : null, preparation.code && preparation.message
2637
+ ? _jsx(Callout, { status: "error", title: preparation.code, description: preparation.message }) : null, actions.length > 0 ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' }) : null] });
2638
+ }
1932
2639
  else if (route.name === 'review' && snapshot.activeReview) {
1933
2640
  const review = snapshot.activeReview;
1934
2641
  breadcrumb = `${review.subject.name} / ${review.title}`;
1935
- title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}确认${review.title} ${review.subject.name}`;
2642
+ title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' ? review.title : `确认${review.title}`} ${review.subject.name}`;
1936
2643
  description = snapshot.result?.state === 'stale' ? '当前状态已变化。请检查新的变化后再次确认。' : review.description;
1937
2644
  actions = [
1938
- ...review.changes.map((change) => ({
1939
- label: `${reviewSymbol(change)} ${change.title}`,
1940
- description: change.description,
1941
- status: change.kind === 'remove' ? 'error'
1942
- : change.kind === 'warning' ? 'warning'
1943
- : change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined,
1944
- })),
1945
2645
  { label: review.primaryLabel, description: '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
1946
2646
  if (operationBusy.current)
1947
2647
  return;
@@ -1960,11 +2660,6 @@ export function ConsoleAppV4(props) {
1960
2660
  }
1961
2661
  : back },
1962
2662
  ];
1963
- const reviewViewport = lineBudgetViewport(actions, focus, scrollOffset, policy.mode === 'compact' ? 8 : 15, [...review.changes.map((change) => change.group), undefined, undefined]);
1964
- const reviewRows = (indexes) => indexes
1965
- .filter((index) => index >= reviewViewport.offset
1966
- && (reviewViewport.limit === undefined || index < reviewViewport.offset + reviewViewport.limit))
1967
- .map((index) => _jsx(ActionRows, { actions: actions, focus: focus, offset: index, limit: 1 }, index));
1968
2663
  const groupedChanges = review.changes.some((change) => change.group);
1969
2664
  const groupTitle = {
1970
2665
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
@@ -1974,12 +2669,75 @@ export function ConsoleAppV4(props) {
1974
2669
  portableWarnings: '覆盖冲突',
1975
2670
  preserved: '保持不变',
1976
2671
  };
1977
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(Bordered, { children: [_jsxs(Text, { bold: true, children: [review.subject.name, " ", review.subject.sourceLabel, " ", review.subject.version] }), review.equivalentCommand ? _jsxs(_Fragment, { children: [_jsx(Text, { children: review.description }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] })] }) : null, groupedChanges ? Object.keys(groupTitle).map((group) => {
1978
- const indexes = review.changes.map((change, index) => (change.group ?? 'requested') === group ? index : -1)
1979
- .filter((index) => index >= 0);
1980
- const rows = reviewRows(indexes);
1981
- return rows.length > 0 ? _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: groupTitle[group] }), rows] }, group) : null;
1982
- }) : _jsx(_Fragment, { children: reviewRows(review.changes.map((_, index) => index)) }), reviewRows([review.changes.length, review.changes.length + 1]), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u25B8 \u6280\u672F\u8BE6\u60C5\uFF08d\uFF09" }), showTechnical ? review.technicalDetails?.map((detail) => _jsx(Text, { color: tuiTheme.colors.muted, children: detail }, detail)) : null] }) });
2672
+ const changeStatus = (change) => change.kind === 'remove' ? 'error'
2673
+ : change.kind === 'warning' ? 'warning'
2674
+ : change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined;
2675
+ const groupOrder = ['portableWarnings', 'portableTarget', 'requested', 'dependencies', 'preserved', 'portableInputs'];
2676
+ const groupedSpecs = groupedChanges
2677
+ ? groupOrder.map((group) => ({
2678
+ key: group,
2679
+ label: groupTitle[group],
2680
+ protectedCount: group === 'portableWarnings' || group === 'portableTarget' || group === 'preserved',
2681
+ changes: review.changes.filter((change) => (change.group ?? 'requested') === group),
2682
+ }))
2683
+ : [
2684
+ { key: 'warnings', label: '警告', protectedCount: true, changes: review.changes.filter((change) => change.kind === 'warning') },
2685
+ { key: 'kept', label: '保持不变', protectedCount: true, changes: review.changes.filter((change) => change.kind === 'keep') },
2686
+ { key: 'changes', label: undefined, protectedCount: false, changes: review.changes.filter((change) => change.kind !== 'warning' && change.kind !== 'keep') },
2687
+ ];
2688
+ const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0);
2689
+ const headingLines = policy.mode === 'compact' ? 0 : visibleSpecs.filter((group) => group.label).length;
2690
+ const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
2691
+ const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
2692
+ const targetGroup = visibleSpecs.find((group) => group.key === 'portableTarget');
2693
+ if (targetGroup && targetGroup.changes.length > 1 && staticRowBudget > visibleSpecs.length) {
2694
+ allocations.set(targetGroup.key, 2);
2695
+ }
2696
+ let remainingRows = staticRowBudget - [...allocations.values()].reduce((sum, count) => sum + count, 0);
2697
+ for (const group of visibleSpecs) {
2698
+ if (remainingRows <= 0)
2699
+ break;
2700
+ const current = allocations.get(group.key) ?? 1;
2701
+ const added = Math.min(remainingRows, group.changes.length - current);
2702
+ allocations.set(group.key, current + added);
2703
+ remainingRows -= added;
2704
+ }
2705
+ const changeGroups = visibleSpecs.map((group) => {
2706
+ const visibleCount = allocations.get(group.key) ?? 1;
2707
+ const hiddenCount = group.changes.length - visibleCount;
2708
+ return _jsxs(Box, { flexDirection: "column", children: [policy.mode !== 'compact' && group.label ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: group.label }) : null, group.changes.slice(0, visibleCount).map((change, index) => {
2709
+ const retainDescription = change.kind === 'warning' || group.key === 'portableTarget'
2710
+ || group.key === 'requested' || group.key === 'dependencies';
2711
+ const description = retainDescription ? change.description : undefined;
2712
+ const compactGroupLabel = group.key === 'requested' ? '根插件'
2713
+ : group.key === 'dependencies' ? '依赖'
2714
+ : group.key === 'preserved' ? '保持' : group.label;
2715
+ const category = policy.mode === 'compact' && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
2716
+ const count = index === visibleCount - 1 && (hiddenCount > 0 || group.protectedCount)
2717
+ ? hiddenCount > 0 ? `另有 ${hiddenCount} 项` : `共 ${group.changes.length} 项`
2718
+ : undefined;
2719
+ return _jsx(ReviewChangeRow, { title: `${category}${reviewSymbol(change)} ${change.title}${description ? ` · ${description}` : ''}`, meta: count, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
2720
+ })] }, group.key);
2721
+ });
2722
+ const technicalDetails = review.technicalDetails ?? [];
2723
+ const technicalDetailLimit = responsiveBudget(1, 3);
2724
+ const visibleTechnicalDetails = technicalDetails.slice(0, technicalDetailLimit);
2725
+ const hiddenTechnicalDetails = technicalDetails.length - visibleTechnicalDetails.length;
2726
+ const technicalCardRows = 3 + visibleTechnicalDetails.length;
2727
+ const technicalChangeBudget = Math.max(1, size.rows - 6 - 3 - (review.equivalentCommand ? 1 : 0) - actions.length - technicalCardRows);
2728
+ const prioritizedTechnicalChanges = [
2729
+ ...review.changes.filter((change) => change.kind === 'warning'),
2730
+ ...review.changes.filter((change) => change.group === 'portableTarget'),
2731
+ ...review.changes.filter((change) => change.kind !== 'warning' && change.group !== 'portableTarget'),
2732
+ ];
2733
+ const visibleTechnicalChanges = prioritizedTechnicalChanges.slice(0, technicalChangeBudget);
2734
+ const hiddenTechnicalChanges = review.changes.length - visibleTechnicalChanges.length;
2735
+ const technicalChanges = visibleTechnicalChanges.map((change, index) => {
2736
+ const category = change.group ? groupTitle[change.group] : undefined;
2737
+ return _jsx(ReviewChangeRow, { title: `${category ? `${category} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`, meta: index === visibleTechnicalChanges.length - 1 && hiddenTechnicalChanges > 0
2738
+ ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
2739
+ });
2740
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel} ${review.subject.version ?? ''}`, description: review.equivalentCommand ? review.description : undefined, children: [review.equivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), policy.mode === 'compact' || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
1983
2741
  }
1984
2742
  else if (route.name === 'progress') {
1985
2743
  const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
@@ -1990,13 +2748,24 @@ export function ConsoleAppV4(props) {
1990
2748
  breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
1991
2749
  title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
1992
2750
  description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
1993
- help = 'd 技术进度 · Ctrl-C 完成后退出';
2751
+ help = 'Ctrl-C 完成后退出';
1994
2752
  const progress = snapshot.progress;
1995
2753
  const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
1996
2754
  ?? (progress.startedAtMs === undefined ? 0 : Date.now() - progress.startedAtMs))) / 1_000));
1997
2755
  const latestMessage = progress.messages.at(-1);
1998
- const technicalMessages = progress.messages.slice(policy.mode === 'compact' ? -4 : -8);
2756
+ const technicalMessageLimit = portableOperation ? responsiveBudget(1, 3) : responsiveBudget(4, 4);
2757
+ const technicalMessages = progress.messages.slice(-technicalMessageLimit);
2758
+ const hiddenTechnicalMessages = progress.messages.length - technicalMessages.length;
1999
2759
  const dependencyProgress = snapshot.progressTargets.length > 0;
2760
+ const currentPhase = progress.current?.phase ?? '';
2761
+ const failedProgress = snapshot.progressTargets.some((entry) => entry.state === 'failed');
2762
+ const validationActive = currentPhase.includes('检查写入结果')
2763
+ || currentPhase.includes('读取最终状态')
2764
+ || (currentPhase.includes('验证') && currentPhase !== '验证当前状态'
2765
+ && !currentPhase.includes('验证设置值'));
2766
+ const preflightActive = !validationActive && (currentPhase.includes('检查')
2767
+ || currentPhase === '验证当前状态' || currentPhase.includes('验证设置值'));
2768
+ const executionActive = Boolean(currentPhase) && !preflightActive && !validationActive && !failedProgress;
2000
2769
  const progressLabel = (entry) => {
2001
2770
  if (entry.state === 'kept')
2002
2771
  return { label: '已保留', status: 'success' };
@@ -2010,22 +2779,93 @@ export function ConsoleAppV4(props) {
2010
2779
  return { label: '未执行 · 依赖未完成', status: 'warning' };
2011
2780
  return { label: entry.relation === 'requested' ? '等待依赖' : '未开始', status: 'muted' };
2012
2781
  };
2013
- content = portableOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: progress.current?.phase.includes('检查') ? tuiTheme.colors.accent : tuiTheme.statusColors.success, children: [progress.current?.phase.includes('检查') ? '●' : '✓', " \u68C0\u67E5\u63D2\u4EF6\u4F9D\u8D56"] }), _jsxs(Text, { color: progress.current?.phase.includes('检查') ? tuiTheme.colors.accent : tuiTheme.statusColors.success, children: [progress.current?.phase.includes('检查') ? '●' : '✓', " \u751F\u6210\u5019\u9009 Skills"] }), _jsxs(Text, { color: progress.current?.phase.includes('安装') ? tuiTheme.colors.accent : progress.current?.phase.includes('验证') ? tuiTheme.statusColors.success : tuiTheme.colors.muted, children: [progress.current?.phase.includes('安装') ? '●' : progress.current?.phase.includes('验证') ? '✓' : '○', " \u5B89\u88C5\u9009\u62E9"] }), _jsxs(Text, { color: progress.current?.phase.includes('验证') ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [progress.current?.phase.includes('验证') ? '●' : '○', " \u9A8C\u8BC1"] }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5F53\u524D\uFF1ASkills CLI" }), showTechnical ? technicalMessages.map((message, index) => _jsx(Text, { color: tuiTheme.colors.muted, children: message }, `${index}:${message}`)) : null] }) : _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.statusColors.success, children: ["\u2713 ", sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : dependencyProgress ? '检查依赖关系' : '检查兼容性'] }), _jsxs(Text, { color: tuiTheme.colors.accent, children: ["\u25CF ", progress.current?.phase ?? (sourceOperation ? '保存来源配置并刷新' : configOperation ? '保存设置层并重新应用' : dependencyProgress ? '添加必需依赖' : '连接到所选工具')] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u25CB ", dependencyProgress ? '验证完整结果' : '验证结果'] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u5DF2\u7528\u65F6\uFF1A", elapsedSeconds, "s"] }), progress.current ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u5F53\u524D\uFF1A", progress.current.target] }) : null, dependencyProgress ? _jsx(Bordered, { children: snapshot.progressTargets.map((entry) => {
2014
- const state = progressLabel(entry);
2015
- const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
2016
- const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
2017
- ?? entry.pluginId.split('/').at(-1);
2018
- return _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [title, " ", _jsx(Text, { color: tuiTheme.colors.muted, children: relation })] }), _jsx(Text, { color: statusColor(state.status), children: state.label })] }, `${entry.pluginId}:${entry.platform}:${entry.scope}`);
2019
- }) }) : null, !showTechnical && latestMessage ? _jsx(Text, { color: tuiTheme.colors.muted, children: latestMessage }) : null, showTechnical ? technicalMessages.map((message, index) => _jsx(Text, { color: tuiTheme.colors.muted, children: message }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA\u3002" }) : null] });
2782
+ const prioritizedProgressTargets = [...snapshot.progressTargets].sort((left, right) => {
2783
+ const priority = (entry) => entry.state === 'failed' ? 0
2784
+ : entry.state === 'running' ? 1
2785
+ : entry.relation === 'requested' ? 2
2786
+ : entry.state === 'skipped' ? 3 : 4;
2787
+ return priority(left) - priority(right);
2788
+ });
2789
+ const progressStepRows = policy.mode === 'compact' ? 4 : 5;
2790
+ const progressTargetAndMessageBudget = pageActionBudget()
2791
+ - 2
2792
+ - progressStepRows
2793
+ - 3
2794
+ - (exitAfterBusy ? 3 : 0);
2795
+ const dependencyMessageLimit = progressTargetAndMessageBudget
2796
+ >= (policy.mode === 'compact' ? 2 : 3) + 1
2797
+ ? Math.min(1, showTechnical ? technicalMessages.length : latestMessage ? 1 : 0)
2798
+ : 0;
2799
+ const progressTargetBudget = progressTargetAndMessageBudget - dependencyMessageLimit;
2800
+ const progressTargetLimit = prefixRowLimit(prioritizedProgressTargets.map(() => policy.mode === 'compact' ? 1 : 2), progressTargetBudget);
2801
+ const visibleProgressTargets = prioritizedProgressTargets.slice(0, progressTargetLimit);
2802
+ const hiddenProgressTargets = snapshot.progressTargets.length - visibleProgressTargets.length;
2803
+ const targetStates = snapshot.progressTargets.map((entry) => entry.state);
2804
+ const executionComplete = targetStates.length > 0
2805
+ && targetStates.every((state) => state === 'completed' || state === 'kept');
2806
+ const executionStarted = targetStates.some((state) => state === 'running' || state === 'completed'
2807
+ || state === 'failed' || state === 'skipped');
2808
+ const relationStep = currentPhase === '验证当前状态' || preflightActive ? 'active' : 'complete';
2809
+ const planStep = relationStep === 'active' ? 'pending' : 'complete';
2810
+ const closureStep = failedProgress ? 'error'
2811
+ : executionComplete ? 'complete'
2812
+ : executionStarted || executionActive ? 'active' : 'pending';
2813
+ const verificationStep = failedProgress && validationActive ? 'error'
2814
+ : validationActive ? 'active' : 'pending';
2815
+ const portableDraft = snapshot.portableDraft;
2816
+ const portableReviewProviderIds = snapshot.activeReview?.changes
2817
+ .filter((change) => change.group === 'requested' || change.group === 'dependencies')
2818
+ .flatMap((change) => change.pluginId ? [change.pluginId] : []) ?? [];
2819
+ const portableProviderCount = new Set(portableReviewProviderIds.length > 0
2820
+ ? portableReviewProviderIds
2821
+ : portableDraft?.candidates.filter((candidate) => candidate.selected)
2822
+ .map((candidate) => candidate.providerPluginId) ?? []).size;
2823
+ const portableSelectedCount = portableDraft?.selectedCount
2824
+ ?? snapshot.activeReview?.changes.filter((change) => change.group === 'requested' || change.group === 'dependencies').length
2825
+ ?? 0;
2826
+ const portablePreparing = currentPhase.includes('检查插件依赖并生成候选');
2827
+ const portableInstalling = currentPhase.includes('安装已选择的 Skills');
2828
+ const portableVerifying = currentPhase.includes('验证安装结果');
2829
+ const portableStep = portableVerifying ? 3 : portableInstalling ? 2 : portablePreparing ? 1 : 0;
2830
+ const portableProgressState = (step) => {
2831
+ if (failedProgress && step === portableStep)
2832
+ return 'error';
2833
+ if (step < portableStep)
2834
+ return 'complete';
2835
+ if (step === portableStep)
2836
+ return 'active';
2837
+ return 'pending';
2838
+ };
2839
+ const portableProgressMeta = (step) => {
2840
+ const state = portableProgressState(step);
2841
+ if (state === 'complete')
2842
+ return '完成';
2843
+ if (state === 'error')
2844
+ return '失败';
2845
+ if (state === 'pending')
2846
+ return '未开始';
2847
+ return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
2848
+ };
2849
+ const compactPortableProgress = policy.mode === 'compact' && showTechnical;
2850
+ content = portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个 provider 提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置并刷新' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [visibleProgressTargets.map((entry) => {
2851
+ const state = progressLabel(entry);
2852
+ const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
2853
+ const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
2854
+ ?? entry.pluginId.split('/').at(-1);
2855
+ return _jsx(ResultRow, { title: title, description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${entry.pluginId}:${entry.platform}:${entry.scope}`);
2856
+ }), hiddenProgressTargets > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenProgressTargets} 个目标`, status: "muted", compact: true }) : null] }) : null, !showTechnical && latestMessage && (!dependencyProgress || dependencyMessageLimit > 0) ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? (dependencyProgress
2857
+ ? dependencyMessageLimit > 0 ? technicalMessages.slice(-dependencyMessageLimit) : []
2858
+ : technicalMessages).map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] });
2020
2859
  }
2021
2860
  else if (route.name === 'result' && snapshot.result) {
2022
2861
  const result = snapshot.result;
2023
2862
  const partial = result.state === 'partial';
2024
2863
  const blocked = result.state === 'blocked';
2025
2864
  breadcrumb = '插件操作 / 结果';
2026
- const action = result.state === 'succeeded' || result.state === 'partial'
2027
- ? result.summary.action
2028
- : undefined;
2865
+ const resultSummary = result.state === 'succeeded' || result.state === 'partial'
2866
+ ? result.summary
2867
+ : result.state === 'blocked' ? result.summary : undefined;
2868
+ const action = resultSummary?.action;
2029
2869
  const operationKind = 'operationKind' in result ? result.operationKind : undefined;
2030
2870
  const portableOperation = operationKind === 'portableInstall';
2031
2871
  const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
@@ -2035,9 +2875,10 @@ export function ConsoleAppV4(props) {
2035
2875
  && result.returnRoute?.name === 'pluginDetail';
2036
2876
  const configOperation = operationKind === 'configSave' || operationKind === 'configReset'
2037
2877
  ? operationKind : undefined;
2878
+ const successfulConfigOperation = configOperation !== undefined && !blocked && !partial;
2038
2879
  const sourceOperation = operationKind === 'sourceMutation';
2039
2880
  const sourceRefreshWarning = sourceOperation && 'summary' in result
2040
- && result.summary.messages?.some((message) => message.startsWith('警告:'));
2881
+ && result.summary?.messages?.some((message) => message.startsWith('警告:'));
2041
2882
  const issueOperation = result.originIssue;
2042
2883
  const completedTitle = {
2043
2884
  install: '✓ 插件添加完成',
@@ -2059,7 +2900,9 @@ export function ConsoleAppV4(props) {
2059
2900
  ? blocked
2060
2901
  ? '× 设置未确认保存'
2061
2902
  : partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
2062
- : partial ? '! 插件操作只完成了一部分' : blocked ? '× 插件操作未完成' : completedTitle;
2903
+ : partial ? '! 插件操作只完成了一部分'
2904
+ : blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
2905
+ : completedTitle;
2063
2906
  description = portableOperation
2064
2907
  ? blocked
2065
2908
  ? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
@@ -2090,16 +2933,29 @@ export function ConsoleAppV4(props) {
2090
2933
  : '来源配置已保存,刷新已完成。'
2091
2934
  : blocked ? result.message
2092
2935
  : configOperation ? '设置层已经更新。' : '状态已刷新。';
2093
- const completedResult = result.state === 'succeeded' || result.state === 'partial'
2094
- ? result
2095
- : undefined;
2096
- const pluginId = completedResult?.summary.pluginId
2936
+ const pluginId = resultSummary?.pluginId
2097
2937
  ?? ('pluginId' in result ? result.pluginId : undefined);
2098
- const failedTarget = completedResult?.summary.targets
2938
+ const configOrigin = successfulConfigOperation && pluginId
2939
+ ? props.controller.configOrigin(pluginId)
2940
+ : undefined;
2941
+ const dependencyTargets = resultSummary?.targets?.some((target) => target.relation) ?? false;
2942
+ const blockedDependencyFailure = blocked && result.code === 'operationFailed' && dependencyTargets;
2943
+ const dependencyRecovery = (partial && dependencyTargets) || blockedDependencyFailure;
2944
+ if (blockedDependencyFailure) {
2945
+ title = '× 依赖安装没有完成';
2946
+ description = '依赖目标失败,后续目标未执行。可以查看失败项或重试失败的依赖。';
2947
+ }
2948
+ const failedTarget = resultSummary?.targets
2099
2949
  ?.filter((target) => target.state === 'failed')
2100
2950
  .sort((left, right) => [left.pluginId ?? '', left.platform, left.scope].join('\0')
2101
2951
  .localeCompare([right.pluginId ?? '', right.platform, right.scope].join('\0'), 'en'))[0];
2102
- const failedPluginId = failedTarget?.pluginId ?? pluginId;
2952
+ const failedTargetPluginId = failedTarget?.pluginId;
2953
+ const failedPluginId = dependencyRecovery
2954
+ ? failedTargetPluginId
2955
+ : failedTargetPluginId && props.controller.getPluginOverview(failedTargetPluginId)
2956
+ ? failedTargetPluginId : pluginId;
2957
+ const failedPluginPlatforms = failedPluginId
2958
+ ? props.controller.getPluginOverview(failedPluginId)?.supportedPlatforms : undefined;
2103
2959
  const failedIssue = failedPluginId && failedTarget
2104
2960
  ? props.controller.listUserIssues().find((candidate) => (candidate.pluginId === failedPluginId || candidate.dependencyPluginId === failedPluginId)
2105
2961
  && candidate.platform === failedTarget.platform
@@ -2121,7 +2977,7 @@ export function ConsoleAppV4(props) {
2121
2977
  && (candidate.scope ?? failedIssue.scope) === failedTarget?.scope)
2122
2978
  : undefined;
2123
2979
  actions = portableOperation
2124
- ? [
2980
+ ? blocked || partial ? [
2125
2981
  { label: blocked || partial ? '返回修改' : portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: async () => {
2126
2982
  if (!pluginId) {
2127
2983
  leaveResult();
@@ -2153,8 +3009,19 @@ export function ConsoleAppV4(props) {
2153
3009
  } },
2154
3010
  { label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => { if (pluginId)
2155
3011
  props.controller.resetPortableFlowToPreview(pluginId); } },
3012
+ ] : [
3013
+ { label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => {
3014
+ props.controller.discardPortableDraft();
3015
+ leaveResult();
3016
+ } },
3017
+ { label: '再安装其他 Skills…', run: async () => {
3018
+ if (!pluginId)
3019
+ return;
3020
+ props.controller.resetPortableFlowToPreview(pluginId);
3021
+ await beginPortable(pluginId);
3022
+ } },
2156
3023
  ]
2157
- : issueOperation
3024
+ : issueOperation && !dependencyRecovery
2158
3025
  ? blocked || partial
2159
3026
  ? [
2160
3027
  { label: '返回问题详情', run: () => blocked
@@ -2177,19 +3044,32 @@ export function ConsoleAppV4(props) {
2177
3044
  { label: '返回设置', run: () => leaveResult() },
2178
3045
  { label: '返回首页', run: () => leaveResult({ name: 'home' }) },
2179
3046
  ]
2180
- : [{ label: partial ? '查看失败项' : configOperation ? '查看插件' : action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
2181
- if (partial && pluginId && failedTarget) {
3047
+ : [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
3048
+ ? configOrigin?.name === 'pluginPreview' ? '返回插件预览' : '返回插件详情'
3049
+ : configOperation ? '查看插件'
3050
+ : action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
3051
+ if (successfulConfigOperation && configOrigin) {
3052
+ leaveResult();
3053
+ return;
3054
+ }
3055
+ if ((partial || blockedDependencyFailure) && failedPluginId && failedTarget) {
2182
3056
  if (failedIssue)
2183
3057
  leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId });
3058
+ else if (failedPluginPlatforms?.includes(failedTarget.platform)) {
3059
+ leaveResult({ name: 'platform', pluginId: failedPluginId, platform: failedTarget.platform });
3060
+ }
3061
+ else if (snapshot.viewModel?.plugins.some((entry) => entry.pluginId === failedPluginId)) {
3062
+ leaveResult({ name: 'pluginDetail', pluginId: failedPluginId });
3063
+ }
2184
3064
  else
2185
- leaveResult({ name: 'platform', pluginId, platform: failedTarget.platform });
3065
+ leaveResult({ name: 'pluginList', mode: 'installed' });
2186
3066
  }
2187
3067
  else if (pluginId && snapshot.viewModel?.plugins.some((entry) => entry.pluginId === pluginId)) {
2188
3068
  leaveResult({ name: 'pluginDetail', pluginId });
2189
3069
  }
2190
3070
  else
2191
3071
  leaveResult({ name: 'pluginList', mode: 'installed' });
2192
- } }, ...(partial && pluginId && failedTarget && retryAction ? [{
3072
+ } }, ...((partial || blockedDependencyFailure) && failedPluginId && failedTarget && retryAction ? [{
2193
3073
  label: '重试失败部分…',
2194
3074
  run: () => {
2195
3075
  const issueIntent = failedIssue && retryIssueAction
@@ -2204,20 +3084,65 @@ export function ConsoleAppV4(props) {
2204
3084
  });
2205
3085
  },
2206
3086
  }] : []), { label: '返回首页', run: () => leaveResult({ name: 'home' }) }];
2207
- const visibleMessages = completedResult?.summary.messages?.slice(policy.mode === 'compact' ? -2 : -4) ?? [];
2208
- const dependencyTargets = completedResult?.summary.targets?.some((target) => target.relation) ?? false;
3087
+ const visibleMessages = resultSummary?.messages?.slice(policy.mode === 'compact' ? -2 : -4) ?? [];
2209
3088
  const resultTargetName = (target) => target.pluginId
2210
3089
  ? props.controller.getPluginOverview(target.pluginId)?.displayName
2211
3090
  ?? target.pluginId.split('/').at(-1)
2212
3091
  : `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
2213
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [result.state === 'blocked' ? _jsxs(Text, { color: tuiTheme.statusColors.error, children: [result.writeMayHaveCompleted ? '结果未确认' : '操作未完成', " \u00B7 ", result.code] }) : sourceOperation ? _jsx(Text, { color: tuiTheme.statusColors.success, children: "\u6765\u6E90\u914D\u7F6E\u5199\u5165\u5DF2\u5B8C\u6210\u3002" }) : completedResult && (completedResult.summary.targets?.length ?? 0) > 0 ? _jsxs(_Fragment, { children: [dependencyTargets ? _jsx(Text, { color: partial ? tuiTheme.statusColors.warning : tuiTheme.statusColors.success, children: partial ? '依赖安装没有全部完成' : '操作已完成' }) : null, completedResult.summary.targets.map((target) => { const state = operationTargetStateLabel(target.state); const relation = target.relation === 'requested' ? '你选择的插件' : target.relation === 'direct' ? '直接依赖' : target.relation === 'transitive' ? '间接依赖' : undefined; return _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [resultTargetName(target), relation ? _jsxs(Text, { color: tuiTheme.colors.muted, children: [" \u00B7 ", relation] }) : null] }), _jsx(Text, { color: statusColor(state.status), children: state.label })] }, `${target.pluginId ?? ''}:${target.platform}:${target.scope}`); }), partial && completedResult.summary.targets.some((target) => target.state === 'skipped') ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u672A\u6267\u884C\u7684\u76EE\u6807\u6CA1\u6709\u5199\u5165\uFF1B\u5DF2\u5B8C\u6210\u6216\u5DF2\u4FDD\u7559\u7684\u76EE\u6807\u4FDD\u6301\u53EF\u7528\u3002" }) : null] }) : completedResult ? _jsx(Text, { color: portableInstallFailed ? tuiTheme.statusColors.error : partial ? tuiTheme.statusColors.warning : tuiTheme.statusColors.success, children: completedResult.summary.message }) : _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u786E\u8BA4\u3002" }), portableReceipt ? _jsxs(_Fragment, { children: [_jsxs(Text, { children: ["Skills\uFF1A", portableReceipt.selectedSkills.join('、')] }), _jsxs(Text, { children: ["Agent\uFF1A", portableReceipt.agentMode === 'automatic' ? '自动选择检测到的 Agent;未检测到时可能选择全部 Agent' : portableReceipt.requestedAgents.join('、')] }), _jsxs(Text, { children: ["\u8303\u56F4\uFF1A", scopeLabel(portableReceipt.scope)] }), portableReceipt.verificationUnknown ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u9A8C\u8BC1\u7ED3\u679C\uFF1A\u672A\u77E5" }) : _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u9A8C\u8BC1\u7F3A\u9879\uFF1A", portableReceipt.missing.length] }), portableReceipt.recovery ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u6062\u590D\uFF1A", portableReceipt.recovery] }) : null, _jsxs(Text, { color: tuiTheme.colors.muted, children: ["Materialization\uFF1A", portableReceipt.materializationDigest] })] }) : null, completedResult?.summary.elapsedMs !== undefined ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u5DF2\u7528\u65F6\uFF1A", Math.floor(completedResult.summary.elapsedMs / 1_000), "s"] }) : null, visibleMessages.map((message, index) => _jsx(Text, { color: tuiTheme.colors.muted, children: message }, `${index}:${message}`))] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
3092
+ const resultStatus = blocked ? 'error' : partial ? 'warning' : 'success';
3093
+ const resultTargets = resultSummary?.targets ?? [];
3094
+ const hasPreservedTarget = resultTargets.some((target) => target.state === 'completed' || target.state === 'kept');
3095
+ const prioritizedTargets = [...resultTargets].sort((left, right) => {
3096
+ const priority = (target) => target.state === 'failed' ? 0
3097
+ : target.state === 'skipped' ? 1 : target.relation === 'requested' ? 2 : 3;
3098
+ return priority(left) - priority(right);
3099
+ });
3100
+ const resultActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(8));
3101
+ const resultExtraRows = (resultSummary?.elapsedMs !== undefined ? 1 : 0)
3102
+ + visibleMessages.length
3103
+ + (partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? 1 : 0);
3104
+ const resultTargetLimit = prefixRowLimit(prioritizedTargets.map(() => policy.mode === 'compact' ? 1 : 2), pageActionBudget() - 3 - resultActionViewport.limit - resultExtraRows);
3105
+ const visibleResultTargets = prioritizedTargets.slice(0, resultTargetLimit);
3106
+ const hiddenResultTargets = resultTargets.length - visibleResultTargets.length;
3107
+ const portableMissingSkills = new Set(portableReceipt?.missing.map((entry) => entry.skill) ?? []);
3108
+ const portableSelectedSkills = portableReceipt?.selectedSkills
3109
+ ?? snapshot.portableDraft?.candidates.filter((candidate) => candidate.selected).map((candidate) => candidate.name)
3110
+ ?? [];
3111
+ const prioritizedPortableSkills = [...portableSelectedSkills].sort((left, right) => Number(portableMissingSkills.has(right)) - Number(portableMissingSkills.has(left)));
3112
+ const portableTechnicalSummary = showTechnical && (partial || blocked);
3113
+ const portableSkillLimit = showTechnical ? 1 : responsiveBudget(2, 6);
3114
+ const visiblePortableSkills = prioritizedPortableSkills.slice(0, portableSkillLimit);
3115
+ const hiddenPortableSkills = prioritizedPortableSkills.length - visiblePortableSkills.length;
3116
+ const portableRows = visiblePortableSkills.map((skill) => {
3117
+ const missing = portableMissingSkills.has(skill);
3118
+ const status = blocked || portableReceipt?.verificationUnknown ? 'warning'
3119
+ : missing ? 'warning' : 'success';
3120
+ const meta = blocked ? result.writeMayHaveCompleted ? '结果未知' : '未执行'
3121
+ : portableReceipt?.verificationUnknown ? '结果未知'
3122
+ : missing ? '未完成 · 缺少目标' : '已安装';
3123
+ return _jsx(ResultRow, { title: skill, meta: meta, status: status, compact: true }, skill);
3124
+ });
3125
+ const compactTechnicalRows = portableReceipt ? [
3126
+ portableReceipt.failureCode
3127
+ ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }, "failure-code")
3128
+ : _jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }, "materialization-digest"),
3129
+ _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u6458\u8981\u5DF2\u4FDD\u7559", status: "muted", compact: true }, "folded-fields"),
3130
+ ] : [
3131
+ _jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }, "failure-code"),
3132
+ _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u5B89\u5168 receipt", status: "muted", compact: true }, "folded-fields"),
3133
+ ];
3134
+ content = portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableTechnicalSummary ? _jsxs(_Fragment, { children: [portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt?.recovery ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true }) : null, _jsx(TechnicalDetails, { children: compactTechnicalRows })] }) : _jsx(_Fragment, { children: _jsxs(ResultCard, { status: resultStatus, title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', children: [portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt ? _jsx(StaticRow, { title: `Agent:${portableReceipt.agentMode === 'automatic' ? 'Skills CLI 默认选择' : portableReceipt.requestedAgents.join('、')}`, meta: `范围:${scopeLabel(portableReceipt.scope)}`, compact: true }) : null, portableReceipt?.recovery ? policy.mode === 'compact'
3135
+ ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
3136
+ : _jsx(Callout, { status: "warning", title: "\u5B89\u5168\u6062\u590D", description: portableReceipt.recovery }) : null, showTechnical && portableReceipt ? _jsxs(TechnicalDetails, { children: [portableReceipt.failureCode ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }) : null, _jsx(StaticRow, { title: "verification missing", meta: String(portableReceipt.missing.length), compact: true }), _jsx(StaticRow, { title: "Skills CLI version", meta: portableReceipt.skillsCliVersion, compact: true }), partial || blocked
3137
+ ? _jsx(StaticRow, { title: "\u53E6\u6709\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "materialization digest \u00B7 elapsed", status: "muted", compact: true })
3138
+ : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }), resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "elapsed", meta: `${resultSummary.elapsedMs} ms`, compact: true }) : null] })] }) : null, showTechnical && !portableReceipt ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }), _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u751F\u6210\u5B89\u5168 receipt", status: "muted", compact: true })] }) : null] }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: blockedDependencyFailure ? '依赖安装没有完成' : blocked ? `${result.writeMayHaveCompleted ? '结果未确认' : '操作未完成'} · ${result.code}` : sourceOperation ? '来源配置写入已完成' : dependencyTargets ? partial ? '依赖安装没有全部完成' : '操作已完成' : resultSummary?.message ?? '状态已变化,请重新确认', children: [visibleResultTargets.map((target) => { const state = operationTargetStateLabel(target.state); const relation = target.relation === 'requested' ? '你选择的插件' : target.relation === 'direct' ? '直接依赖' : target.relation === 'transitive' ? '间接依赖' : undefined; return _jsx(ResultRow, { title: resultTargetName(target), description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${target.pluginId ?? ''}:${target.platform}:${target.scope}`); }), hiddenResultTargets > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenResultTargets} 个目标`, status: "muted", compact: true }) : null, partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? _jsx(StaticRow, { title: "\u672A\u6267\u884C\u7684\u76EE\u6807\u6CA1\u6709\u5199\u5165\uFF1B\u5DF2\u5B8C\u6210\u6216\u5DF2\u4FDD\u7559\u7684\u76EE\u6807\u4FDD\u6301\u53EF\u7528\u3002", status: "muted", compact: true }) : null, resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "\u5DF2\u7528\u65F6", meta: `${Math.floor(resultSummary.elapsedMs / 1_000)}s` }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: message, status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] });
2214
3139
  }
2215
3140
  else if (route.name === 'pluginAbout' && plugin) {
2216
3141
  breadcrumb = `${plugin.displayName} / 关于`;
2217
3142
  title = plugin.displayName;
2218
3143
  description = plugin.description;
2219
3144
  actions = [{ label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) }, { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) }];
2220
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7248\u672C ", _jsx(Text, { color: tuiTheme.colors.text, children: plugin.version ?? '未知' })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u6765\u6E90 ", _jsx(Text, { color: tuiTheme.colors.text, children: plugin.sourceLabel })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u53EF\u7528\u5DE5\u5177 ", _jsx(Text, { color: tuiTheme.colors.text, children: plugin.supportedPlatforms.map(platformLabel).join('、') })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u914D\u7F6E ", _jsxs(Text, { color: tuiTheme.colors.text, children: [plugin.configFieldCount, " \u4E2A\u5B57\u6BB5"] })] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
3145
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: plugin.version ?? '未知' }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceLabel }), _jsx(StaticRow, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", meta: plugin.supportedPlatforms.map(platformLabel).join('、') }), _jsx(StaticRow, { title: "\u914D\u7F6E", meta: `${plugin.configFieldCount} 个字段` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2221
3146
  }
2222
3147
  else if (route.name === 'pluginTechnical' && plugin) {
2223
3148
  breadcrumb = `${plugin.displayName} / 技术详情`;
@@ -2225,18 +3150,67 @@ export function ConsoleAppV4(props) {
2225
3150
  title = '安装与身份详情';
2226
3151
  description = '默认页面不会展示这些内部信息。';
2227
3152
  actions = [{ label: '返回插件', run: back }];
2228
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["canonical pluginId ", _jsx(Text, { color: tuiTheme.colors.text, children: plugin.pluginId })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u6765\u6E90 ", _jsx(Text, { color: tuiTheme.colors.text, children: plugin.sourceId })] }), plugin.installedPlacements.map((placement) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [platformLabel(placement.platform), " target ", _jsx(Text, { color: tuiTheme.colors.text, children: placementStatus(placement).value })] }, placement.platform)), plugin.technicalComponents.map((component) => _jsxs(Text, { color: tuiTheme.colors.muted, children: [platformLabel(component.platform), " \u00B7 ", scopeLabel(component.scope), " \u00B7 ", component.key, " \u00B7 ", component.state, component.errorCode ? ` · ${component.errorCode}` : ''] }, `${component.platform}:${component.scope}:${component.key}`))] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
3153
+ const prioritizedComponents = [...plugin.technicalComponents].sort((left, right) => {
3154
+ const priority = (state) => state === 'error' ? 0
3155
+ : state === 'partial' ? 1 : state === 'absent' ? 2 : 3;
3156
+ return priority(left.state) - priority(right.state);
3157
+ });
3158
+ const detailRowBudget = responsiveBudget(9, 15);
3159
+ const componentRowBudget = Math.max(1, detailRowBudget - 2 - plugin.installedPlacements.length);
3160
+ const componentLimit = prioritizedComponents.length > componentRowBudget
3161
+ ? Math.max(1, componentRowBudget - 1)
3162
+ : componentRowBudget;
3163
+ const visibleComponents = prioritizedComponents.slice(0, componentLimit);
3164
+ const hiddenComponents = prioritizedComponents.length - visibleComponents.length;
3165
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${scopeLabel(component.scope)}` }, `${component.platform}:${component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2229
3166
  }
2230
3167
  else {
2231
3168
  breadcrumb = '后续阶段';
2232
3169
  title = '此业务页将在后续阶段接入';
2233
3170
  description = '当前阶段只实现插件发现、安装和维护页面。';
2234
3171
  actions = [{ label: '返回上一页', run: back }];
2235
- content = _jsx(ActionRows, { actions: actions, focus: focus });
3172
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' });
3173
+ }
3174
+ if (canOpenHelp && supportsTechnicalDetails) {
3175
+ const technicalHelp = route.name === 'progress' ? 'd 技术进度' : 'd 技术详情';
3176
+ const remainingHelp = help.replace(' · ? 帮助', '').replace('? 帮助 · ', '');
3177
+ help = `${technicalHelp} · ? 帮助 · ${remainingHelp}`;
2236
3178
  }
3179
+ else if (canOpenHelp && !help.includes('? 帮助')) {
3180
+ help = `? 帮助 · ${help}`;
3181
+ }
3182
+ const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
3183
+ ? actions.map((action) => String(Boolean(action.disabled))).join(',')
3184
+ : '';
3185
+ useEffect(() => {
3186
+ if (!targetActionSignature || !actions[focus]?.disabled)
3187
+ return;
3188
+ const firstEnabled = actions.findIndex((action) => !action.disabled);
3189
+ if (firstEnabled < 0)
3190
+ return;
3191
+ setFocus(firstEnabled);
3192
+ setScrollOffset(0);
3193
+ }, [focus, targetActionSignature]);
3194
+ useEffect(() => {
3195
+ if (!dynamicActionIds || dynamicActionSignature === undefined) {
3196
+ dynamicMenuFocus.current = undefined;
3197
+ return;
3198
+ }
3199
+ if (effectiveFocus !== focus) {
3200
+ setFocus(effectiveFocus);
3201
+ setScrollOffset(0);
3202
+ }
3203
+ dynamicMenuFocus.current = {
3204
+ routeKey,
3205
+ actionIds: dynamicActionSignature,
3206
+ focusedActionId: dynamicActionIds[effectiveFocus],
3207
+ };
3208
+ }, [dynamicActionSignature, routeKey]);
2237
3209
  const runAction = (action) => {
3210
+ if (!action || action.disabled)
3211
+ return;
2238
3212
  try {
2239
- const pending = action?.run?.();
3213
+ const pending = action.run?.();
2240
3214
  if (pending)
2241
3215
  void pending.catch(() => {
2242
3216
  setActionError('操作未完成。请重新检查后重试。');
@@ -2247,11 +3221,37 @@ export function ConsoleAppV4(props) {
2247
3221
  }
2248
3222
  };
2249
3223
  useInput((input, key) => {
3224
+ if (!overlay && route.name === 'reviewPreparing' && snapshot.reviewPreparation?.state === 'failed'
3225
+ && (key.escape || input.toLowerCase() === 'b')) {
3226
+ back();
3227
+ return;
3228
+ }
3229
+ if (preparationRunning) {
3230
+ if (key.escape || input.toLowerCase() === 'b') {
3231
+ back();
3232
+ }
3233
+ else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
3234
+ setExitAfterPreparation(true);
3235
+ }
3236
+ return;
3237
+ }
2250
3238
  if (busy) {
2251
- if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
3239
+ if (overlay === 'help') {
3240
+ if (key.escape || key.return || input.toLowerCase() === 'b' || input === '?') {
3241
+ setOverlay(undefined);
3242
+ }
3243
+ else if (key.ctrl && input.toLowerCase() === 'c') {
3244
+ setExitAfterBusy(true);
3245
+ }
3246
+ }
3247
+ else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
2252
3248
  setExitAfterBusy(true);
2253
3249
  }
2254
- else if (!tooSmall && input.toLowerCase() === 'd') {
3250
+ else if (canOpenHelp && input === '?') {
3251
+ setOverlayFocus(0);
3252
+ setOverlay('help');
3253
+ }
3254
+ else if (!tooSmall && supportsTechnicalDetails && input.toLowerCase() === 'd') {
2255
3255
  setShowTechnical((value) => !value);
2256
3256
  }
2257
3257
  return;
@@ -2272,7 +3272,8 @@ export function ConsoleAppV4(props) {
2272
3272
  return;
2273
3273
  }
2274
3274
  if (key.leftArrow || key.rightArrow || key.upArrow || key.downArrow) {
2275
- setOverlayFocus((value) => value === 0 ? 1 : 0);
3275
+ if (overlay !== 'help')
3276
+ setOverlayFocus((value) => value === 0 ? 1 : 0);
2276
3277
  return;
2277
3278
  }
2278
3279
  if (key.return) {
@@ -2314,7 +3315,7 @@ export function ConsoleAppV4(props) {
2314
3315
  if (key.return) {
2315
3316
  try {
2316
3317
  const normalizedInput = portableAgentInput.trim();
2317
- const agents = normalizePortableAgents(normalizedInput ? normalizedInput.split(/\s+/u) : []);
3318
+ const agents = normalizePortableAgents(normalizedInput ? normalizedInput.split(/[\s,]+/u) : []);
2318
3319
  props.controller.setPortableAgentMode('explicit', agents);
2319
3320
  setInputError(undefined);
2320
3321
  go({ name: 'portableScope', pluginId: route.pluginId });
@@ -2475,11 +3476,10 @@ export function ConsoleAppV4(props) {
2475
3476
  }
2476
3477
  if (key.tab && (route.name === 'configLayer'
2477
3478
  || route.name === 'configFields'
2478
- || route.name === 'configFieldEditor'
2479
- || route.name === 'configPresets')) {
3479
+ || route.name === 'configFieldEditor')) {
2480
3480
  return;
2481
3481
  }
2482
- if (input === '?') {
3482
+ if (canOpenHelp && input === '?') {
2483
3483
  setOverlayFocus(0);
2484
3484
  setOverlay('help');
2485
3485
  }
@@ -2488,18 +3488,11 @@ export function ConsoleAppV4(props) {
2488
3488
  setDiscardForExit(dirty);
2489
3489
  setOverlay(dirty ? 'discard' : 'exit');
2490
3490
  }
2491
- else if (input.toLowerCase() === 'd' && (route.name === 'review'
2492
- || route.name === 'issueDetail'
2493
- || route.name === 'configUnavailable'
2494
- || route.name === 'pluginDependencies'
2495
- || route.name === 'portableDependencies'
2496
- || route.name === 'portableBlocker')) {
3491
+ else if (input.toLowerCase() === 'd' && supportsTechnicalDetails) {
2497
3492
  setShowTechnical((value) => !value);
2498
3493
  }
2499
3494
  else if ((key.escape || input.toLowerCase() === 'b')
2500
- && (route.name === 'configFieldEditor'
2501
- || route.name === 'configPresets'
2502
- || (route.name === 'configFields' && Boolean(route.groupId)))) {
3495
+ && route.name === 'configFieldEditor') {
2503
3496
  back();
2504
3497
  }
2505
3498
  else if (key.escape || input.toLowerCase() === 'b') {
@@ -2514,6 +3507,23 @@ export function ConsoleAppV4(props) {
2514
3507
  else if (route.name === 'pluginList' && input === '/') {
2515
3508
  setSearchMode(true);
2516
3509
  }
3510
+ else if (route.name === 'pluginList' && route.mode === 'browse' && input.toLowerCase() === 'f') {
3511
+ const selectedPluginId = pluginRows[focus]?.pluginId;
3512
+ const nextFilter = installFilter === 'all' ? 'uninstalled' : 'all';
3513
+ const nextItems = props.controller.listPlugins({
3514
+ mode: route.mode,
3515
+ ...(route.sourceId ? { sourceId: route.sourceId } : {}),
3516
+ ...(query ? { query } : {}),
3517
+ installFilter: nextFilter,
3518
+ });
3519
+ const retained = selectedPluginId
3520
+ ? nextItems.findIndex((item) => item.pluginId === selectedPluginId)
3521
+ : -1;
3522
+ setInstallFilter(nextFilter);
3523
+ props.controller.updatePageContext({ installFilter: nextFilter });
3524
+ setFocus(retained >= 0 ? retained : 0);
3525
+ setScrollOffset(0);
3526
+ }
2517
3527
  else if (route.name === 'configFields' && route.layer.scope !== 'effective' && input.toLowerCase() === 's') {
2518
3528
  if (dirty)
2519
3529
  void beginConfigSave();
@@ -2523,6 +3533,15 @@ export function ConsoleAppV4(props) {
2523
3533
  if (key)
2524
3534
  removeDraft(key);
2525
3535
  }
3536
+ else if (route.name === 'configFields' && visibleConfigTabs.length > 1
3537
+ && (key.leftArrow || key.rightArrow)) {
3538
+ const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeConfigTabId));
3539
+ const direction = key.leftArrow ? -1 : 1;
3540
+ const next = visibleConfigTabs[(current + direction + visibleConfigTabs.length) % visibleConfigTabs.length];
3541
+ props.controller.updatePageContext({ configGroupId: next.id, focusIndex: 0, scrollOffset: 0 });
3542
+ setFocus(0);
3543
+ setScrollOffset(0);
3544
+ }
2526
3545
  else if (input.toLowerCase() === 'r' && route.name === 'sourceDetail') {
2527
3546
  void props.controller.refreshSource(route.sourceId);
2528
3547
  }
@@ -2538,13 +3557,14 @@ export function ConsoleAppV4(props) {
2538
3557
  mode: route.mode,
2539
3558
  ...(current !== 'all' ? { sourceId: current } : {}),
2540
3559
  ...(query ? { query } : {}),
3560
+ installFilter,
2541
3561
  })[focus]?.pluginId;
2542
3562
  const direction = key.leftArrow || input === '[' ? -1 : 1;
2543
3563
  const index = tabs.indexOf(current);
2544
3564
  const next = tabs[(index + direction + tabs.length) % tabs.length];
2545
- const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}) });
3565
+ const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}), installFilter });
2546
3566
  const retained = selectedPluginId ? nextItems.findIndex((item) => item.pluginId === selectedPluginId) : -1;
2547
- props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, sourceTabId: next });
3567
+ props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
2548
3568
  setFocus(retained >= 0 ? retained : 0);
2549
3569
  setScrollOffset(retained >= 4 ? retained - 3 : 0);
2550
3570
  }
@@ -2558,56 +3578,68 @@ export function ConsoleAppV4(props) {
2558
3578
  }
2559
3579
  else if (key.upArrow || input.toLowerCase() === 'k') {
2560
3580
  setFocus((value) => {
2561
- const next = moveFocus(actions, value, -1);
3581
+ const current = dynamicActionIds ? effectiveFocus : value;
3582
+ const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1) : moveFocus(actions, current, -1);
3583
+ if (dynamicActionIds && dynamicActionSignature !== undefined) {
3584
+ dynamicMenuFocus.current = {
3585
+ routeKey,
3586
+ actionIds: dynamicActionSignature,
3587
+ focusedActionId: dynamicActionIds[next],
3588
+ };
3589
+ }
2562
3590
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
2563
- ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode, next, scrollOffset)
2564
- : actionViewport(route.name, actions, policy.mode, next, scrollOffset);
3591
+ ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3592
+ + (portableVariantCandidates.length > 0 ? 3 : 0)))
3593
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
2565
3594
  setScrollOffset(viewport.offset);
2566
3595
  return next;
2567
3596
  });
2568
3597
  }
2569
3598
  else if (key.downArrow || input.toLowerCase() === 'j') {
2570
3599
  setFocus((value) => {
2571
- const next = moveFocus(actions, value, 1);
3600
+ const current = dynamicActionIds ? effectiveFocus : value;
3601
+ const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1) : moveFocus(actions, current, 1);
3602
+ if (dynamicActionIds && dynamicActionSignature !== undefined) {
3603
+ dynamicMenuFocus.current = {
3604
+ routeKey,
3605
+ actionIds: dynamicActionSignature,
3606
+ focusedActionId: dynamicActionIds[next],
3607
+ };
3608
+ }
2572
3609
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
2573
- ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode, next, scrollOffset)
2574
- : actionViewport(route.name, actions, policy.mode, next, scrollOffset);
3610
+ ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3611
+ + (portableVariantCandidates.length > 0 ? 3 : 0)))
3612
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
2575
3613
  setScrollOffset(viewport.offset);
2576
3614
  return next;
2577
3615
  });
2578
3616
  }
2579
- else if (input === ' ' && route.name === 'installTools' && route.view !== 'blocked') {
2580
- runAction(actions[focus]);
2581
- }
2582
- else if (key.return && route.name === 'installTools' && route.view !== 'blocked') {
2583
- if (tools.length > 0) {
2584
- setDirty(false);
2585
- back();
2586
- }
3617
+ else if (key.return && route.name === 'pluginList' && pluginRows[focus]) {
3618
+ go(route.mode === 'browse'
3619
+ ? { name: 'pluginPreview', pluginId: pluginRows[focus].pluginId }
3620
+ : { name: 'pluginDetail', pluginId: pluginRows[focus].pluginId });
2587
3621
  }
2588
3622
  else if (key.return) {
2589
- runAction(actions[focus]);
3623
+ runAction(actions[effectiveFocus]);
2590
3624
  }
2591
3625
  });
2592
3626
  if (tooSmall) {
2593
- return (_jsx(ScreenFrame, { title: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", breadcrumb: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", context: `${size.columns} × ${size.rows}`, help: "q \u9000\u51FA", size: size, children: _jsxs(Bordered, { children: [_jsx(Text, { bold: true, children: "\u2194 \u8BF7\u5C06\u7EC8\u7AEF\u8C03\u6574\u5230\u81F3\u5C11 60 \u00D7 18" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u8C03\u6574\u540E\u4F1A\u8FD4\u56DE\u539F\u9875\u9762\uFF0C\u4E0D\u4F1A\u4E22\u5931\u5F53\u524D\u9009\u62E9\u3002" })] }) }));
3627
+ return (_jsx(ScreenFrame, { title: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", breadcrumb: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", context: `${size.columns} × ${size.rows}`, help: "q \u9000\u51FA", size: size, children: _jsx(Callout, { status: "warning", title: "\u2194 \u8BF7\u5C06\u7EC8\u7AEF\u8C03\u6574\u5230\u81F3\u5C11 60 \u00D7 18", description: "\u8C03\u6574\u540E\u4F1A\u8FD4\u56DE\u539F\u9875\u9762\uFF0C\u4E0D\u4F1A\u4E22\u5931\u5F53\u524D\u9009\u62E9\u3002" }) }));
2594
3628
  }
2595
3629
  if (actionError)
2596
3630
  description = actionError;
2597
3631
  const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, help: help, size: size, children: content }));
2598
3632
  if (!overlay)
2599
3633
  return base;
2600
- const helpRows = [
2601
- ['↑ / ↓', '移动当前唯一焦点'],
2602
- ['Enter', '打开、选择或继续'],
2603
- ['Esc / b', '返回上一页'],
2604
- ...(route.name === 'pluginList' ? [['← / →', '切换来源'], ['/', '进入搜索']] : []),
2605
- ...(route.name === 'installTools' ? [['Space', '切换工具选择']] : []),
2606
- ...(route.name === 'review' || route.name === 'progress' ? [['d', '打开技术详情']] : []),
2607
- ['?', '关闭帮助'],
2608
- ];
2609
- return (_jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsxs(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor(size.columns / 5)), width: Math.max(40, Math.floor(size.columns * 0.6)), flexDirection: "column", borderStyle: "round", borderColor: tuiTheme.colors.accent, paddingX: 1, children: [_jsx(Text, { bold: true, children: overlay === 'help' ? '当前页快捷键' : overlay === 'discard' ? '放弃未保存修改?' : '退出控制台?' }), overlay === 'help'
2610
- ? _jsx(_Fragment, { children: helpRows.map(([key, value]) => _jsxs(Text, { children: [key.padEnd(9), value] }, key)) })
2611
- : _jsx(Text, { color: tuiTheme.colors.muted, children: overlay === 'discard' ? '放弃后将返回上一页。' : '进行中的写操作会先完成。' }), _jsxs(Text, { bold: true, color: overlayFocus === 0 ? tuiTheme.colors.accent : undefined, children: ["\u203A ", overlay === 'help' ? '关闭' : overlay === 'discard' ? '继续编辑' : '继续使用'] }), overlay === 'exit' || overlay === 'discard' ? _jsxs(Text, { bold: true, color: overlayFocus === 1 ? tuiTheme.statusColors.error : undefined, children: [overlayFocus === 1 ? '› ' : ' ', overlay === 'discard' ? '放弃修改' : '退出'] }) : null] })] }));
3634
+ const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy);
3635
+ const overlayActions = overlay === 'help'
3636
+ ? [{ label: '关闭' }]
3637
+ : [
3638
+ { label: overlay === 'discard' ? '继续编辑' : '继续使用' },
3639
+ { label: overlay === 'discard' ? '放弃修改' : '退出', status: 'error' },
3640
+ ];
3641
+ return (_jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: overlay === 'help' ? null : base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor(size.columns / 5)), width: Math.max(40, Math.floor(size.columns * 0.6)), flexDirection: "column", children: _jsxs(ReviewCard, { title: overlay === 'help' ? '当前页快捷键' : overlay === 'discard' ? '放弃未保存修改?' : '退出控制台?', children: [overlay === 'help'
3642
+ ? _jsx(_Fragment, { children: helpRows.map(([key, value]) => _jsx(StaticRow, { title: `${key} ${value}`, compact: true }, key)) })
3643
+ : _jsx(StaticRow, { title: overlay === 'discard' ? '放弃后将返回上一页。' : '进行中的写操作会先完成。', status: "muted" }), _jsx(SemanticRows, { actions: overlayActions, focus: overlayFocus, kind: "action", compact: true })] }) })] }));
2612
3644
  }
2613
3645
  //# sourceMappingURL=console-app-v4.js.map