@lesliechan721/aw 0.1.0-beta.29 → 0.1.0-beta.30

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.
@@ -11,7 +11,7 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
11
11
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
12
12
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
13
13
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
14
- import { ActionRow, Callout, ChoiceRow, ContentViewport, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, PinnedActionBar, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
14
+ import { ActionRow, ObjectCardRow, objectCardRowLayout, StatusBadge, diagnosticDetailLines, Callout, ChoiceRow, ContentViewport, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, PinnedActionBar, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
15
15
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
16
16
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
17
17
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
@@ -184,7 +184,7 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
184
184
  }
185
185
  if (route.name === 'batchSelection') {
186
186
  rows.push(['Space', '切换当前插件']);
187
- rows.push(['a', '全选或清空当前列表']);
187
+ rows.push(['a', '全选当前筛选结果']);
188
188
  rows.push(['c', '继续选择位置和平台']);
189
189
  }
190
190
  if (route.name === 'configFields' && route.layer.scope !== 'effective') {
@@ -198,13 +198,16 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
198
198
  rows.push(['d', route.name === 'progress' ? '打开技术进度' : '打开技术详情']);
199
199
  }
200
200
  if (sessionNavigation && !busy && !isCharacterInputMode(route, false)) {
201
- rows.push(['h', '返回首页']);
202
- rows.push(['H', '重新读取本地状态']);
201
+ rows.push(['h / H', '返回首页 / 重新读取本地状态']);
203
202
  }
204
203
  if (sessionNavigation && busy) {
205
204
  rows.push(['h / H', '操作成功后返回首页或重新读取']);
206
205
  rows.push(['Esc', '撤销等待中的页面跳转']);
207
206
  }
207
+ if (route.name === 'pluginList' || route.name === 'pluginDetail'
208
+ || route.name === 'batchSelection' || route.name === 'installTargets') {
209
+ rows.push(['状态', '✓已装 ○未装 !异常 ↑更新 ?未知']);
210
+ }
208
211
  rows.push(['q / Ctrl-C', busy ? '操作完成后退出' : '退出控制台']);
209
212
  rows.push(['? / o', '关闭帮助 / 查看操作记录']);
210
213
  return rows;
@@ -302,34 +305,49 @@ function pluginUpdateDescription(plugin) {
302
305
  }
303
306
  return '当前启用目录已验证;可直接进入更新确认。';
304
307
  }
305
- function pluginPlacementSummary(plugin) {
306
- if (plugin.configOnly) {
307
- return plugin.savedConfigScopes.length > 0
308
- ? `已保存${plugin.savedConfigScopes.map(scopeLabel).join('、')}设置`
309
- : '已保存设置';
308
+ function pluginPlatformBadges(plugin) {
309
+ const items = plugin.supportedPlatforms.map((platform) => {
310
+ const placements = plugin.installedPlacements.filter((entry) => entry.platform === platform);
311
+ const installed = placements.some((entry) => entry.state === 'installed' || entry.state === 'scopeConflict');
312
+ const unknown = plugin.sourceUnavailable || placements.length === 0
313
+ || placements.some((entry) => entry.state === 'installed' && entry.sharedNative?.state === 'unobserved');
314
+ const attention = placements.some((entry) => entry.state === 'blocked' || entry.state === 'scopeConflict'
315
+ || entry.state === 'installed' && (entry.health !== 'ready' || entry.blockingReason !== undefined));
316
+ const update = plugin.update.targets.some((target) => target.platform === platform
317
+ && target.availableVersion !== undefined);
318
+ return {
319
+ key: platform,
320
+ label: `${platformLabel(platform)}${attention && (installed || unknown) ? ' !' : ''}${update ? ' ↑' : ''}${unknown && installed ? ' ?' : ''}`,
321
+ symbol: installed ? '✓' : unknown ? '?' : attention ? '!' : '○',
322
+ status: attention ? 'warning' : unknown ? 'muted'
323
+ : update ? 'info' : installed ? 'success' : 'muted',
324
+ };
325
+ });
326
+ return _jsxs(Box, { children: [_jsx(StatusBadge, { items: items }), plugin.portableSkills ? _jsx(Text, { color: tuiTheme.colors.accent, children: " [Skills \u53EF\u9009]" }) : null, plugin.configOnly ? _jsx(Text, { color: tuiTheme.colors.muted, children: "[\u5DF2\u4FDD\u5B58\u8BBE\u7F6E]" }) : null] });
327
+ }
328
+ function installedScopeStates(plugin, platform) {
329
+ const states = new Map();
330
+ const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
331
+ if (!placement || placement.state === 'notInstalled' || placement.state === 'blocked')
332
+ return states;
333
+ const scoped = placement.state === 'scopeConflict'
334
+ ? placement.placements
335
+ : placement.scopedPlacements ?? [{
336
+ scope: placement.scope,
337
+ health: placement.health,
338
+ revision: placement.revision,
339
+ actualState: placement.actualState,
340
+ }];
341
+ for (const entry of scoped) {
342
+ const actualState = 'actualState' in entry ? entry.actualState : undefined;
343
+ const healthy = entry.health === 'ready' && actualState !== 'blocked'
344
+ && actualState !== 'needsSync';
345
+ states.set(entry.scope, healthy
346
+ ? { symbol: '✓', status: 'success' }
347
+ : { symbol: '!', status: actualState === 'blocked' || entry.health === 'error'
348
+ ? 'error' : 'warning' });
310
349
  }
311
- return plugin.supportedPlatforms.map((platform) => {
312
- const label = platformLabel(platform);
313
- const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
314
- if (!placement || placement.state === 'notInstalled')
315
- return `[○ ${label} 未安装]`;
316
- if (placement.state === 'blocked')
317
- return `[× ${label} 输出冲突]`;
318
- if (placement.state === 'scopeConflict')
319
- return `[! ${label} 位置冲突]`;
320
- if (placement.blockingReason === 'artifactCollision') {
321
- return `[! ${label}] ${scopeLabel(placement.scope)} · 输出冲突`;
322
- }
323
- const health = placement.actualState === 'needsSync' ? '需要同步'
324
- : placement.actualState === 'blocked' ? '已阻断'
325
- : placement.health === 'ready' ? '可用'
326
- : placement.health === 'incomplete' ? '需要处理' : '不可用';
327
- const desired = placement.desiredScopes?.map(scopeLabel).join('+') || '未保存';
328
- const actual = placement.actualScopes?.map(scopeLabel).join('+') || '未启用';
329
- return placement.desiredScopes?.length
330
- ? `[${health === '可用' ? '✓' : '!'} ${label}] 已保存 ${desired} · 当前 ${actual} · ${health}`
331
- : `[${health === '可用' ? '✓' : '!'} ${label}] 当前 ${actual} · ${health}`;
332
- }).join(' ') || '没有可用平台';
350
+ return states;
333
351
  }
334
352
  function issueMatchesTarget(issue, pluginId, platform, scope) {
335
353
  if (!platform && !scope && issue.pluginId === pluginId)
@@ -452,51 +470,22 @@ function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
452
470
  rowBudget: lineBudget,
453
471
  });
454
472
  }
455
- function portableSkillCandidateViewport(actions, candidates, compact, focus, requestedOffset, rowBudget) {
456
- const candidateFocus = focus < candidates.length ? focus : requestedOffset;
457
- return groupedRowViewport({
458
- rowHeights: actions.map(() => compact ? 1 : 2),
459
- groupKeys: candidates.map((candidate) => candidate.providerPluginId),
460
- groupOverhead: 3,
461
- focus: candidateFocus,
473
+ function objectCardActionViewport(actions, width, compact, focus, requestedOffset, lineBudget) {
474
+ return rowViewport({
475
+ rowHeights: actions.map((action) => objectCardRowLayout({
476
+ width,
477
+ description: action.disabled ?? action.description ?? '',
478
+ meta: action.meta,
479
+ hasTags: action.tags !== undefined,
480
+ compact,
481
+ }).height),
482
+ focus,
462
483
  requestedOffset,
463
- rowBudget,
484
+ rowBudget: lineBudget,
464
485
  });
465
486
  }
466
- function groupedRowViewport(options) {
467
- const total = options.rowHeights.length;
468
- if (total === 0) {
469
- return { offset: 0, limit: 0, hiddenBefore: 0, hiddenAfter: 0, position: 0, total: 0 };
470
- }
471
- const focus = Math.min(Math.max(0, options.focus), total - 1);
472
- const height = (start, end) => {
473
- let rows = 0;
474
- let previousGroup;
475
- for (let index = start; index < end; index += 1) {
476
- const group = options.groupKeys[index];
477
- if (group !== undefined && group !== previousGroup)
478
- rows += options.groupOverhead;
479
- rows += options.rowHeights[index] ?? 1;
480
- previousGroup = group;
481
- }
482
- return rows;
483
- };
484
- let offset = Math.min(Math.max(0, options.requestedOffset), focus);
485
- while (offset < focus && height(offset, focus + 1) > options.rowBudget)
486
- offset += 1;
487
- let end = focus + 1;
488
- while (end < total && height(offset, end + 1) <= options.rowBudget)
489
- end += 1;
490
- while (offset > 0 && height(offset - 1, end) <= options.rowBudget)
491
- offset -= 1;
492
- return {
493
- offset,
494
- limit: end - offset,
495
- hiddenBefore: offset,
496
- hiddenAfter: total - end,
497
- position: focus + 1,
498
- total,
499
- };
487
+ function portableSkillCandidateViewport(actions, width, compact, focus, requestedOffset, rowBudget) {
488
+ return objectCardActionViewport(actions, width, compact, focus, requestedOffset, rowBudget);
500
489
  }
501
490
  function prefixRowLimit(rowHeights, rowBudget) {
502
491
  let used = 0;
@@ -547,41 +536,59 @@ function sourceLocationSummary(type, value) {
547
536
  }
548
537
  }
549
538
  function portableBlockerDiagnostic(route) {
550
- const cliUnavailable = route.code === 'portableSkillsCliMissing'
551
- || route.code === 'portableSkillsCliIncompatible';
539
+ const cliMissing = route.code === 'portableSkillsCliMissing';
540
+ const cliIncompatible = route.code === 'portableSkillsCliIncompatible';
541
+ const cliUnavailable = cliMissing || cliIncompatible;
552
542
  const sensitiveOutput = route.diagnosticCode === 'portableSensitiveOutput';
553
543
  const invalidRuntimeImport = route.diagnosticCode === 'portableRuntimeImportMissing';
544
+ const skillCollision = route.diagnosticCode === 'portableSkillCollision';
545
+ const distributionUnavailable = route.diagnosticCode === 'portableDistributionUnavailable';
554
546
  const userMessage = route.code === 'requiredConfigMissing'
555
547
  ? '插件缺少生成内容所需的设置。'
556
548
  : route.code === 'portableScopeUnavailable'
557
549
  ? '至少一个所选 Skill 不适用于这个范围。'
558
- : cliUnavailable
559
- ? '没有找到兼容的 Skills CLI。'
560
- : sensitiveOutput
561
- ? '生成的 Skill 内容包含敏感配置值或未解析模板。'
562
- : invalidRuntimeImport
563
- ? '所需运行时引用未解析、缺失或无效。'
564
- : '候选 Skill 没有通过 Portable 安全校验。';
550
+ : cliMissing
551
+ ? '没有找到已安装的 Skills CLI。'
552
+ : cliIncompatible
553
+ ? `已安装 Skills CLI,但版本不在支持范围 ${SKILLS_CLI_SUPPORTED_VERSION_LABEL}。`
554
+ : sensitiveOutput
555
+ ? '生成的 Skill 内容包含敏感配置值或未解析模板。'
556
+ : invalidRuntimeImport
557
+ ? '所需运行时引用未解析、缺失或无效。'
558
+ : skillCollision
559
+ ? '两个来源提供同名 Skill,无法安全确定写入内容。'
560
+ : distributionUnavailable
561
+ ? '插件没有可信的通用 Skills 分发定义。'
562
+ : '候选 Skill 没有通过 Portable 安全校验。';
565
563
  const blocked = route.code === 'requiredConfigMissing'
566
564
  ? '缺少生成 Skill 所需的设置;没有调用 Skills CLI。'
567
- : cliUnavailable
568
- ? '当前不能开始安装;没有调用 Skills CLI。'
569
- : route.code === 'portableScopeUnavailable'
570
- ? '所选内容不能在这个范围完整生成;没有开始写入。'
571
- : sensitiveOutput || invalidRuntimeImport
572
- ? '没有开始写入,也没有调用 Skills CLI。'
573
- : '候选内容没有通过安全校验;没有开始写入。';
565
+ : cliMissing
566
+ ? 'Skills CLI 尚未安装;当前不能开始安装。'
567
+ : cliIncompatible
568
+ ? '已安装的 Skills CLI 版本不兼容;当前不能开始安装。'
569
+ : route.code === 'portableScopeUnavailable'
570
+ ? '所选内容不能在这个范围完整生成;没有开始写入。'
571
+ : sensitiveOutput || invalidRuntimeImport || skillCollision || distributionUnavailable
572
+ ? '没有开始写入,也没有调用 Skills CLI。'
573
+ : '候选内容没有通过安全校验;没有开始写入。';
574
574
  const nextStep = route.code === 'requiredConfigMissing'
575
575
  ? '完成插件设置,然后重新准备。'
576
- : cliUnavailable
577
- ? '安装或升级兼容的 Skills CLI,然后重新检查。'
578
- : route.code === 'portableScopeUnavailable'
579
- ? '返回 Skill 选择或更改范围,然后重新检查。'
580
- : '修复来源内容或修改选择,然后重新检查。';
576
+ : cliMissing
577
+ ? '安装兼容的 Skills CLI,然后重新检查。'
578
+ : cliIncompatible
579
+ ? '升级或切换到兼容的 Skills CLI,然后重新检查。'
580
+ : route.code === 'portableScopeUnavailable'
581
+ ? '返回 Skill 选择或更改范围,然后重新检查。'
582
+ : skillCollision
583
+ ? '返回修改 Skills,移除同名冲突后重新检查。'
584
+ : distributionUnavailable
585
+ ? '修复插件的通用 Skills 分发定义,然后重新检查。'
586
+ : '修复来源内容或修改选择,然后重新检查。';
581
587
  return {
582
588
  severity: 'error',
583
589
  title: route.code === 'portableScopeUnavailable' ? '所选范围不可用'
584
- : cliUnavailable ? '暂时无法安装通用 Skills' : '无法准备通用 Skills',
590
+ : cliMissing ? '未安装 Skills CLI'
591
+ : cliIncompatible ? 'Skills CLI 版本不兼容' : '无法准备通用 Skills',
585
592
  userMessage,
586
593
  impact: [
587
594
  { state: 'blocked', message: blocked },
@@ -816,6 +823,8 @@ function SemanticRows(props) {
816
823
  focused: index === props.focus,
817
824
  compact: props.compact,
818
825
  };
826
+ if (props.cards)
827
+ return _jsx(ObjectCardRow, { width: props.columns ?? 58, title: action.label, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: index === props.focus, compact: props.compact }, `${action.label}:${index}`);
819
828
  if (props.kind === 'section')
820
829
  return _jsx(SectionRow, { ...rowProps }, `${action.label}:${index}`);
821
830
  if (props.kind === 'choice') {
@@ -831,7 +840,7 @@ function SemanticRows(props) {
831
840
  }
832
841
  function PluginRows(props) {
833
842
  return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
834
- .map((row, visibleIndex) => row.menuAction ? (_jsx(TaskRow, { title: row.name, description: row.description, meta: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, compact: true }, row.pluginId)) : (_jsx(PluginListRow, { name: row.name, sourceLabel: row.sourceLabel, description: row.description, attention: row.attention, placementSummary: row.placementSummary, statusLabel: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, anchored: !props.active && props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
843
+ .map((row, visibleIndex) => row.menuAction ? (_jsx(TaskRow, { title: row.name, description: row.description, meta: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, compact: true }, row.pluginId)) : (_jsx(PluginListRow, { name: row.name, sourceLabel: row.sourceLabel, description: row.description, attention: row.attention, placementSummary: row.placementSummary, tags: row.tags, statusLabel: row.statusLabel, status: row.status, focused: props.active && props.viewport.offset + visibleIndex === props.focus, anchored: !props.active && props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
835
844
  }
836
845
  function IssueTechnicalDetails(props) {
837
846
  const visible = props.details.slice(0, props.compact ? 1 : 2);
@@ -874,7 +883,7 @@ function SourceTabs(props) {
874
883
  }
875
884
  function configTabs(page) {
876
885
  return [
877
- ...page.groups.map((group) => ({
886
+ ...page.groups.filter((group) => group.fields.length > 0).map((group) => ({
878
887
  id: group.id,
879
888
  label: group.title,
880
889
  count: group.fields.length,
@@ -1050,11 +1059,12 @@ export function ConsoleAppV4(props) {
1050
1059
  const windowSize = useWindowSize();
1051
1060
  const size = props.size ?? windowSize;
1052
1061
  const policy = resolveTuiSizePolicy(size.columns, size.rows);
1053
- const [snapshot, setSnapshot] = useState(props.controller.snapshot);
1062
+ const [snapshot, setSnapshot] = useState(() => props.controller.snapshot);
1054
1063
  const [overlay, setOverlay] = useState();
1055
1064
  const [overlayFocus, setOverlayFocus] = useState(0);
1056
1065
  const [operationConfirmation, setOperationConfirmation] = useState();
1057
1066
  const [sourceTransportOverlay, setSourceTransportOverlay] = useState();
1067
+ const [pluginListChooser, setPluginListChooser] = useState();
1058
1068
  const [projectReminderOpen, setProjectReminderOpen] = useState(false);
1059
1069
  const [discardForExit, setDiscardForExit] = useState(false);
1060
1070
  const [discardForSessionNavigation, setDiscardForSessionNavigation] = useState();
@@ -1064,10 +1074,10 @@ export function ConsoleAppV4(props) {
1064
1074
  const [pinnedActionFocus, setPinnedActionFocus] = useState(0);
1065
1075
  const [scrollOffset, setScrollOffset] = useState(0);
1066
1076
  const [searchFocus, setSearchFocus] = useState(false);
1067
- const [query, setQuery] = useState(props.controller.snapshot.router.current.context.query ?? '');
1068
- const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter
1069
- ?? (props.controller.snapshot.router.current.route.name === 'pluginList'
1070
- && props.controller.snapshot.router.current.route.mode === 'installed' ? 'installed' : 'all'));
1077
+ const [query, setQuery] = useState(snapshot.router.current.context.query ?? '');
1078
+ const [installFilter, setInstallFilter] = useState(snapshot.router.current.context.installFilter
1079
+ ?? (snapshot.router.current.route.name === 'pluginList'
1080
+ && snapshot.router.current.route.mode === 'installed' ? 'installed' : 'all'));
1071
1081
  const [scope, setScope] = useState('user');
1072
1082
  const [selectedTargets, setSelectedTargets] = useState([]);
1073
1083
  const [installPlatformScopes, setInstallPlatformScopes] = useState({});
@@ -1088,6 +1098,8 @@ export function ConsoleAppV4(props) {
1088
1098
  const [inputValue, setInputValue] = useState('');
1089
1099
  const [inputError, setInputError] = useState();
1090
1100
  const [configActionError, setConfigActionError] = useState();
1101
+ const [configGroupChooserOpen, setConfigGroupChooserOpen] = useState(false);
1102
+ const [configGroupChoiceFocus, setConfigGroupChoiceFocus] = useState(0);
1091
1103
  const [actionError, setActionError] = useState();
1092
1104
  const [configResetAvailability, setConfigResetAvailability] = useState({});
1093
1105
  const [sourceDraft, setSourceDraft] = useState();
@@ -1095,6 +1107,7 @@ export function ConsoleAppV4(props) {
1095
1107
  const [batchTargetOffset, setBatchTargetOffset] = useState(0);
1096
1108
  const [portableResultDetailOpen, setPortableResultDetailOpen] = useState(false);
1097
1109
  const [portableResultDetailOffset, setPortableResultDetailOffset] = useState(0);
1110
+ const [portableRecheckBusy, setPortableRecheckBusy] = useState(false);
1098
1111
  const portableResultDetailPairKey = useRef(undefined);
1099
1112
  const portableResultDetailReturnFocus = useRef(0);
1100
1113
  const [expandedDependencyPluginIds, setExpandedDependencyPluginIds] = useState();
@@ -1113,6 +1126,7 @@ export function ConsoleAppV4(props) {
1113
1126
  const [portableAgentInput, setPortableAgentInput] = useState('');
1114
1127
  const [portableSelectionView, setPortableSelectionView] = useState('all');
1115
1128
  const [portableToolsOpen, setPortableToolsOpen] = useState(false);
1129
+ const [portableEmptySelectionOpen, setPortableEmptySelectionOpen] = useState(false);
1116
1130
  const [reportSaveMode, setReportSaveMode] = useState(false);
1117
1131
  const [reportSavePath, setReportSavePath] = useState('');
1118
1132
  const [reportSaveResult, setReportSaveResult] = useState();
@@ -1127,9 +1141,16 @@ export function ConsoleAppV4(props) {
1127
1141
  configDrafts, configCandidate, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1128
1142
  });
1129
1143
  const externalDraftFlowIds = useRef(new Map());
1144
+ const configOverviewAnchors = useRef(new Map());
1130
1145
  const exitSent = useRef(false);
1131
1146
  const operationBusy = useRef(false);
1132
1147
  const route = snapshot.router.current.route;
1148
+ if (route.name === 'configFields') {
1149
+ configOverviewAnchors.current.set(route.pluginId, {
1150
+ overviewFrameId: snapshot.router.currentFrameId,
1151
+ ...(snapshot.router.currentFlowId ? { flowId: snapshot.router.currentFlowId } : {}),
1152
+ });
1153
+ }
1133
1154
  const configDraftCount = ['project', 'user'].reduce((count, draftScope) => {
1134
1155
  const draft = configDrafts[draftScope];
1135
1156
  return count + Object.keys(draft?.updates ?? {}).length + (draft?.removes.length ?? 0);
@@ -1357,6 +1378,10 @@ export function ConsoleAppV4(props) {
1357
1378
  setInputValue('');
1358
1379
  }
1359
1380
  }, [route.name]);
1381
+ useEffect(() => {
1382
+ if (route.name !== 'configFields')
1383
+ setConfigGroupChooserOpen(false);
1384
+ }, [route.name]);
1360
1385
  const configRoute = route.name === 'configFields'
1361
1386
  || route.name === 'configFieldEditor'
1362
1387
  || route.name === 'configObjectPropertyEditor'
@@ -1506,9 +1531,12 @@ export function ConsoleAppV4(props) {
1506
1531
  : error instanceof Error
1507
1532
  ? safeDiagnosticCode(error.message.split(':', 1)[0], 'configUnavailable')
1508
1533
  : 'configUnavailable';
1534
+ const overviewAnchor = configOverviewAnchors.current.get(expectedPluginId);
1509
1535
  props.controller.replace({
1510
1536
  name: 'configUnavailable',
1511
1537
  pluginId: expectedPluginId,
1538
+ ...(overviewAnchor && overviewAnchor.flowId === snapshot.router.currentFlowId
1539
+ ? { overviewFrameId: overviewAnchor.overviewFrameId } : {}),
1512
1540
  diagnosticCode: code,
1513
1541
  diagnosticStage: 'read-config',
1514
1542
  ...(invalidStores ? { invalidStores } : {}),
@@ -1869,6 +1897,10 @@ export function ConsoleAppV4(props) {
1869
1897
  if (route.name !== 'portableSkillSelect')
1870
1898
  setPortableToolsOpen(false);
1871
1899
  }, [route.name]);
1900
+ useEffect(() => {
1901
+ if (route.name !== 'portableSkillSelect')
1902
+ setPortableEmptySelectionOpen(false);
1903
+ }, [route.name]);
1872
1904
  const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchFocus);
1873
1905
  const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
1874
1906
  ? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
@@ -1880,6 +1912,7 @@ export function ConsoleAppV4(props) {
1880
1912
  : [];
1881
1913
  const supportsTechnicalDetails = snapshot.phase === 'error'
1882
1914
  || Boolean(snapshot.projectIntentIssue)
1915
+ || route.name === 'projectStatus'
1883
1916
  || Boolean(route.name === 'reviewPreparing' && snapshot.reviewPreparation?.code)
1884
1917
  || Boolean(route.name === 'progress' && snapshot.progress.messages.length > 0)
1885
1918
  || Boolean(route.name === 'review' && snapshot.activeReview?.technicalDetails?.length)
@@ -1915,6 +1948,8 @@ export function ConsoleAppV4(props) {
1915
1948
  let selectAllPortableCandidates;
1916
1949
  let togglePluginListFilter;
1917
1950
  let movePluginListSource;
1951
+ let choosePluginListFilter;
1952
+ let choosePluginListSource;
1918
1953
  let visibleConfigTabs = [];
1919
1954
  let activeConfigTabId;
1920
1955
  const dynamicMenu = plugin && route.name === 'platform'
@@ -1953,8 +1988,9 @@ export function ConsoleAppV4(props) {
1953
1988
  const base = policy.mode === 'compact' ? compactRows : standardRows;
1954
1989
  return base + Math.max(0, size.rows - baseline);
1955
1990
  };
1956
- const sourceTechnicalPageRows = policy.mode === 'compact'
1957
- ? 1 : Math.max(1, responsiveBudget(5, 10) - 1);
1991
+ const sourceTechnicalPageRows = Math.max(1, contentRowBudget({
1992
+ terminalRows: size.rows, description: false, compact: policy.mode === 'compact', staticRows: 6,
1993
+ }));
1958
1994
  const resolveSourceTechnicalDiagnostic = (sourceId) => {
1959
1995
  const overview = props.controller.getSourceOverview(sourceId);
1960
1996
  return sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), props.controller.getCatalogFreshness(sourceId)) ?? {
@@ -2201,7 +2237,7 @@ export function ConsoleAppV4(props) {
2201
2237
  showSelected: false,
2202
2238
  });
2203
2239
  setDirty(true);
2204
- setFocus(1);
2240
+ setFocus(0);
2205
2241
  setScrollOffset(0);
2206
2242
  setSearchFocus(false);
2207
2243
  };
@@ -2742,12 +2778,39 @@ export function ConsoleAppV4(props) {
2742
2778
  }
2743
2779
  return `${field.effectiveDisplayValue}${field.unit && field.effectiveDisplayValue !== '未设置' ? ` ${field.unit}` : ''}`;
2744
2780
  };
2781
+ const pendingFieldDisplay = (field, scope) => {
2782
+ const draft = configDrafts[scope];
2783
+ if (!draft)
2784
+ return undefined;
2785
+ if (draft.removes.includes(field.key))
2786
+ return '将删除此位置的覆盖并恢复继承';
2787
+ if (!Object.hasOwn(draft.updates, field.key))
2788
+ return undefined;
2789
+ const value = draft.updates[field.key];
2790
+ if (field.sensitive)
2791
+ return '将更新';
2792
+ return typeof value === 'object' ? objectSummary(field, value) : scalarSummary(field, value);
2793
+ };
2794
+ const pendingFieldTargets = (field) => ['project', 'user'].flatMap((scope) => {
2795
+ const value = pendingFieldDisplay(field, scope);
2796
+ return value ? [{ scope, value }] : [];
2797
+ });
2745
2798
  const fieldEditor = (field) => {
2746
2799
  return field.type;
2747
2800
  };
2748
2801
  const fieldEditorMode = (field) => (field.choices?.length ?? 0) > 0 ? { mode: 'choices' } : {};
2749
2802
  const openConfigTarget = (candidate) => {
2750
- setConfigCandidate(candidate);
2803
+ const currentFlowId = snapshot.router.currentFlowId;
2804
+ const storedAnchor = configOverviewAnchors.current.get(candidate.pluginId);
2805
+ const storedOverviewFrameId = storedAnchor && storedAnchor.flowId === currentFlowId
2806
+ ? storedAnchor.overviewFrameId : undefined;
2807
+ const overviewFrameId = storedOverviewFrameId
2808
+ ?? props.controller.configOverviewFrameId(currentFlowId, candidate.pluginId);
2809
+ setConfigCandidate({
2810
+ ...candidate,
2811
+ ...(currentFlowId ? { flowId: currentFlowId } : {}),
2812
+ ...(overviewFrameId ? { overviewFrameId } : {}),
2813
+ });
2751
2814
  go({
2752
2815
  name: 'configLayer',
2753
2816
  pluginId: candidate.pluginId,
@@ -2917,17 +2980,21 @@ export function ConsoleAppV4(props) {
2917
2980
  const next = { ...configDrafts, [layer.scope]: { updates, removes: [...removes] } };
2918
2981
  if (Object.keys(updates).length === 0 && removes.size === 0)
2919
2982
  delete next[layer.scope];
2920
- setConfigDrafts(next);
2921
- setDirty(Object.keys(next).length > 0);
2922
2983
  const target = configCandidate.fieldKey
2923
2984
  ? configFieldTarget(configPage ?? page, configCandidate.fieldKey)
2924
2985
  : configCandidate.groupId ? { groupId: configCandidate.groupId, focusIndex: 0 } : undefined;
2986
+ const returned = props.controller.returnToConfigOverview(configCandidate.flowId, configCandidate.overviewFrameId, route.pluginId, target ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
2987
+ if (!returned) {
2988
+ setConfigActionError('设置总览已变化。请返回插件详情后重新打开设置。');
2989
+ return;
2990
+ }
2991
+ if (!configCandidate.flowId) {
2992
+ props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer: { scope: 'effective' } }, target ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
2993
+ }
2994
+ setConfigDrafts(next);
2995
+ setDirty(Object.keys(next).length > 0);
2925
2996
  setConfigCandidate(undefined);
2926
- props.controller.back();
2927
- props.controller.back();
2928
- props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer: { scope: 'effective' } }, target
2929
- ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
2930
- setFocus(0);
2997
+ setFocus(target?.focusIndex ?? 0);
2931
2998
  setScrollOffset(0);
2932
2999
  };
2933
3000
  const configChanges = (page, draft) => {
@@ -3117,6 +3184,7 @@ export function ConsoleAppV4(props) {
3117
3184
  const projectIntentIssue = snapshot.projectIntentIssue;
3118
3185
  const projectPendingCount = (projectReminder?.actionableTargets.length ?? 0)
3119
3186
  + (projectReminder?.blockedTargets.length ?? 0);
3187
+ const projectTargetCount = projectPendingCount + (projectReminder?.unconfirmedTargets.length ?? 0);
3120
3188
  context = '当前项目';
3121
3189
  actions = [
3122
3190
  ...(projectIntentIssue ? [{
@@ -3128,19 +3196,21 @@ export function ConsoleAppV4(props) {
3128
3196
  ? '本项目插件声明无效;修复后重新加载'
3129
3197
  : '无法读取本项目插件声明;检查项目位置或权限后重新加载',
3130
3198
  status: 'warning',
3131
- run: () => toggleTechnical(),
3199
+ run: () => go({ name: 'projectStatus' }),
3132
3200
  }] : []),
3133
- ...(projectPendingCount > 0 ? [{
3201
+ ...(projectTargetCount > 0 ? [{
3134
3202
  label: '项目插件',
3135
3203
  description: projectReminder.actionableTargets.length > 0
3136
- ? `${projectReminder.actionableTargets.length} 个可同步;`
3137
- + `${projectReminder.blockedTargets.length} 个需要先处理`
3138
- : `${projectReminder.blockedTargets.length} 个需要先处理`,
3139
- meta: String(projectPendingCount),
3204
+ ? `${projectReminder.actionableTargets.length} 个位置可同步;`
3205
+ + `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3206
+ + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3207
+ : projectReminder.blockedTargets.length > 0
3208
+ ? `${projectReminder.blockedTargets.length} 个位置需要先处理;`
3209
+ + `${projectReminder.unconfirmedTargets.length} 个位置未确认`
3210
+ : `${projectReminder.unconfirmedTargets.length} 个位置未确认`,
3211
+ meta: String(projectTargetCount),
3140
3212
  status: 'warning',
3141
- run: () => projectReminder.actionableTargets.length > 0
3142
- ? prepareCurrentProjectTargets(projectReminder.actionableTargets)
3143
- : prepareCurrentProjectPlugins(),
3213
+ run: () => go({ name: 'projectStatus' }),
3144
3214
  }] : []),
3145
3215
  { label: '插件', description: '浏览、安装和管理所有插件', meta: `${home.installedPlugins} 已安装`, status: home.pluginUpdateCount > 0 ? 'warning' : undefined, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3146
3216
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
@@ -3154,16 +3224,180 @@ export function ConsoleAppV4(props) {
3154
3224
  run: () => go({ name: 'sourceList' }),
3155
3225
  },
3156
3226
  ];
3157
- content = _jsxs(Box, { flexDirection: "column", children: [projectIntentIssue && !showTechnical
3158
- ? _jsx(StaticRow, { status: "warning", title: projectIntentIssue.state === 'invalid'
3159
- ? '本项目插件声明无效'
3160
- : '无法读取本项目插件声明', description: "\u5065\u5EB7\u6765\u6E90\u3001\u4E2A\u4EBA\u63D2\u4EF6\u548C\u5B89\u5168\u8BCA\u65AD\u4ECD\u53EF\u4F7F\u7528\u3002\u4FEE\u590D\u540E\u6309 H \u91CD\u65B0\u52A0\u8F7D\u3002" })
3161
- : null, projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", children: [_jsx(StaticRow, { title: "\u72B6\u6001", meta: projectIntentIssue.state === 'invalid' ? '声明无效' : '无法读取', compact: true }), _jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, !showTechnical && projectPendingCount > 0
3162
- ? _jsx(Callout, { status: "warning", title: `本项目有 ${projectPendingCount} 个插件目标尚未就绪`, description: `${projectReminder.actionableTargets.length} 个可同步;`
3163
- + `${projectReminder.blockedTargets.length} 个需要先处理。` })
3164
- : !showTechnical && projectReminder && projectReminder.unconfirmedTargets.length > 0
3165
- ? _jsx(Callout, { status: "info", title: "\u90E8\u5206\u9879\u76EE\u63D2\u4EF6\u72B6\u6001\u65E0\u6CD5\u786E\u8BA4", description: "\u6CA1\u6709\u628A\u672A\u89C2\u6D4B\u72B6\u6001\u89E3\u91CA\u4E3A\u7F3A\u5931\u3002\u8BF7\u68C0\u67E5\u6765\u6E90\u6216\u5DE5\u5177\u72B6\u6001\u540E\u91CD\u65B0\u52A0\u8F7D\u3002" })
3166
- : null, !showTechnical ? _jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `已添加 ${home.installedPlugins} 个插件`, status: "muted" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null, home.catalogCheckingCount > 0 ? _jsx(ResultRow, { title: `${home.catalogCheckingCount} 个插件来源正在检查`, status: "muted" }) : null, home.catalogUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.catalogUpdateCount} 个插件来源有更新`, status: "warning" }) : null, home.pluginUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.pluginUpdateCount} 个插件可更新`, status: "warning" }) : null, home.catalogRecoveryCount > 0 ? _jsx(ResultRow, { title: `${home.catalogRecoveryCount} 个插件来源需要检查或恢复`, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
3227
+ const homeViewport = rowViewport({
3228
+ rowHeights: actions.map((action) => showTechnical ? 1 : objectCardRowLayout({
3229
+ description: action.description ?? '', meta: action.meta, width: size.columns - 2,
3230
+ compact: policy.mode === 'compact',
3231
+ }).height), focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(showTechnical ? 7 : 2),
3232
+ });
3233
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[\u5DF2\u5B89\u88C5 ", home.installedPlugins, "] [\u53EF\u66F4\u65B0 ", home.pluginUpdateCount, "]", home.unavailableSourceCount > 0 ? ` [不可用来源 ${home.unavailableSourceCount}]` : ''] }), _jsx(Text, { color: tuiTheme.colors.muted, children: projectIntentIssue ? '! 本项目插件声明需要检查'
3234
+ : projectReminder && projectReminder.unconfirmedTargets.length > 0
3235
+ ? '? 部分项目插件状态无法确认' : projectPendingCount > 0
3236
+ ? `! ${projectPendingCount} 个位置待处理`
3237
+ : snapshot.projectDeclarationAbsent
3238
+ ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", cards: !showTechnical, columns: size.columns - 2, compact: policy.mode === 'compact', offset: homeViewport.offset, limit: homeViewport.limit })] });
3239
+ }
3240
+ else if (route.name === 'projectStatus') {
3241
+ const reminder = snapshot.projectIntentReminder;
3242
+ const projectIntentIssue = snapshot.projectIntentIssue;
3243
+ const actionable = reminder?.actionableTargets ?? [];
3244
+ const blocked = reminder?.blockedTargets ?? [];
3245
+ const unconfirmed = reminder?.unconfirmedTargets ?? [];
3246
+ const allTargets = [
3247
+ ...actionable.map((target) => ({ target, state: 'actionable' })),
3248
+ ...blocked.map((target) => ({ target, state: 'blocked' })),
3249
+ ...unconfirmed.map((target) => ({ target, state: 'unconfirmed' })),
3250
+ ];
3251
+ const pluginCount = new Set(allTargets.map(({ target }) => target.pluginId)).size;
3252
+ const sourceLabel = (pluginId) => pluginId.split('/')[0] ?? pluginId;
3253
+ const isSourceBlocker = (reasonCodes) => reasonCodes.some((code) => code === 'catalogSourceUnavailable' || code === 'sourceUnavailable'
3254
+ || code === 'catalogSourceMissing' || code === 'sourceMissing' || code === 'bundleUnavailable'
3255
+ || code === 'codexIntentSourceUnavailable' || code === 'sourceChanged');
3256
+ const sourceBlockedCount = blocked.filter((target) => isSourceBlocker(target.reasonCodes)).length;
3257
+ const reasonLabel = (reasonCodes, state) => {
3258
+ if (reasonCodes.includes('sourceChanged'))
3259
+ return '来源身份已变化';
3260
+ if (reasonCodes.includes('sourceMissing'))
3261
+ return '来源已移除';
3262
+ if (reasonCodes.includes('bundleUnavailable'))
3263
+ return '当前来源目录没有此插件';
3264
+ if (isSourceBlocker(reasonCodes))
3265
+ return '来源当前不可用';
3266
+ if (reasonCodes.includes('awCapabilityUnsupported'))
3267
+ return '当前 AW 不支持所需能力';
3268
+ if (reasonCodes.includes('codexExtensionScopeConflict'))
3269
+ return '使用位置冲突';
3270
+ if (reasonCodes.includes('artifactCollision'))
3271
+ return '输出内容冲突';
3272
+ if (reasonCodes.includes('codexSharedNativeForeign'))
3273
+ return '共享位置存在非本插件内容';
3274
+ if (reasonCodes.some((code) => code === 'codexSharedNativeUnobserved'
3275
+ || code === 'codexNativeObservationFailed'))
3276
+ return '平台状态未确认';
3277
+ if (state === 'actionable')
3278
+ return '已确认需要同步';
3279
+ if (state === 'blocked')
3280
+ return '当前目标受阻';
3281
+ return '当前状态未确认';
3282
+ };
3283
+ breadcrumb = '需要处理 / 本项目';
3284
+ title = projectIntentIssue || unconfirmed.length > 0
3285
+ ? '尚未确认项目插件状态'
3286
+ : blocked.length > 0 ? '部分项目插件需要先处理'
3287
+ : allTargets.length === 0 ? '项目插件状态已检查' : '项目共享插件';
3288
+ description = projectIntentIssue
3289
+ ? projectIntentIssue.state === 'invalid'
3290
+ ? '本项目插件声明无效。修复声明后重新检查。'
3291
+ : '读取本项目插件声明失败。当前不会执行安装或恢复。'
3292
+ : allTargets.length === 0
3293
+ ? '当前没有待同步或待检查的项目插件位置。'
3294
+ : `${pluginCount} 个插件涉及 ${allTargets.length} 个位置:`
3295
+ + `${actionable.length} 个可同步 · ${blocked.length} 个受阻 · ${unconfirmed.length} 个未确认`;
3296
+ context = allTargets.length > 0 ? `${pluginCount} 个插件 · ${allTargets.length} 个位置` : '当前项目';
3297
+ actions = allTargets.map(({ target, state }) => {
3298
+ const overview = props.controller.getPluginOverview(target.pluginId);
3299
+ const status = state === 'actionable' ? 'warning'
3300
+ : state === 'blocked' ? 'error' : 'muted';
3301
+ const symbol = state === 'actionable' ? '!' : state === 'blocked' ? '!' : '?';
3302
+ return {
3303
+ actionId: `project-target:${target.pluginId}:${target.platform}:${target.scope}`,
3304
+ label: overview?.displayName ?? target.pluginId.split('/').at(-1) ?? target.pluginId,
3305
+ description: `${platformLabel(target.platform)} · 当前项目 · ${reasonLabel(target.reasonCodes, state)}`,
3306
+ meta: `来源 ${sourceLabel(target.pluginId)} · 项目共享声明`,
3307
+ tags: _jsx(StatusBadge, { items: [{
3308
+ key: state,
3309
+ label: state === 'actionable' ? '待同步' : state === 'blocked' ? '需处理' : '未确认',
3310
+ symbol,
3311
+ status,
3312
+ }] }),
3313
+ status,
3314
+ run: state === 'blocked' && isSourceBlocker(target.reasonCodes)
3315
+ ? () => {
3316
+ const sourceId = sourceLabel(target.pluginId);
3317
+ go(props.controller.getSourceOverview(sourceId).source
3318
+ ? { name: 'sourceDetail', sourceId } : { name: 'sourceList' });
3319
+ }
3320
+ : state === 'unconfirmed' || !overview ? () => setShowTechnical(true)
3321
+ : () => go({ name: 'pluginDetail', pluginId: target.pluginId }),
3322
+ };
3323
+ });
3324
+ pinnedActions = [
3325
+ ...(actionable.length > 0 ? [{
3326
+ actionId: 'project-sync-actionable',
3327
+ label: `同步明确的 ${actionable.length} 个位置`,
3328
+ description: blocked.length + unconfirmed.length > 0
3329
+ ? `保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置;进入现有操作汇总后再确认`
3330
+ : '进入现有操作汇总后再确认',
3331
+ run: () => prepareCurrentProjectTargets(actionable),
3332
+ }] : []),
3333
+ ...(sourceBlockedCount > 0 ? [{
3334
+ actionId: 'project-open-sources',
3335
+ label: '处理插件来源',
3336
+ description: `${sourceBlockedCount} 个位置需要先处理来源问题`,
3337
+ run: () => go({ name: 'sourceList' }),
3338
+ }] : []),
3339
+ {
3340
+ actionId: 'project-recheck',
3341
+ label: '重新检查',
3342
+ description: '重新读取项目声明和平台状态;不会自动安装或恢复',
3343
+ run: () => requestSessionNavigation('reload'),
3344
+ },
3345
+ ...(projectIntentIssue || blocked.length > 0 || unconfirmed.length > 0 ? [{
3346
+ actionId: 'project-error-details',
3347
+ label: '错误详情',
3348
+ description: '查看现有安全错误代码和诊断,不执行写操作',
3349
+ run: () => setShowTechnical((value) => !value),
3350
+ }] : []),
3351
+ {
3352
+ actionId: 'project-back-home',
3353
+ label: '返回首页',
3354
+ run: back,
3355
+ },
3356
+ ];
3357
+ help = showTechnical
3358
+ ? 'p/n 或 PgUp/PgDn 阅读 · d 返回项目位置 · Tab 固定操作 · Esc 返回'
3359
+ : focusZone === 'actions'
3360
+ ? '←→ 移动固定操作 · Enter 选择 · Tab 返回项目位置 · Esc 返回'
3361
+ : '↑↓ 移动 · Enter 查看 · Tab 固定操作 · Esc 返回';
3362
+ const viewport = rowViewport({
3363
+ rowHeights: actions.map((action) => objectCardRowLayout({
3364
+ description: action.description ?? '', meta: action.meta,
3365
+ width: size.columns - 2, hasTags: true, compact: policy.mode === 'compact',
3366
+ }).height),
3367
+ focus,
3368
+ requestedOffset: scrollOffset,
3369
+ rowBudget: pageActionBudget(showTechnical ? 5
3370
+ : 1 + (projectIntentIssue || unconfirmed.length > 0 ? 1 : 0)
3371
+ + (actionable.length > 0 && blocked.length + unconfirmed.length > 0 ? 1 : 0)),
3372
+ });
3373
+ const technicalLines = [
3374
+ ...(projectIntentIssue ? [
3375
+ `项目声明 · ${safeDiagnosticCode(projectIntentIssue.code, 'projectIntentUnavailable')}`,
3376
+ ...wrapConsoleText(`说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`, Math.max(20, size.columns - 8)),
3377
+ ] : []),
3378
+ ...allTargets.flatMap(({ target, state }) => [
3379
+ ...wrapConsoleText(`${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, Math.max(20, size.columns - 8)),
3380
+ ...(target.reasonCodes.length > 0
3381
+ ? target.reasonCodes.flatMap((code) => wrapConsoleText(`原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`, Math.max(20, size.columns - 8)))
3382
+ : [state === 'actionable' ? '原因 · confirmedDifference'
3383
+ : state === 'blocked' ? '原因 · blocked' : '原因 · unconfirmed']),
3384
+ ]),
3385
+ ];
3386
+ const technicalPageRows = responsiveBudget(4, 8);
3387
+ const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
3388
+ const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
3389
+ referenceDetailPagination = showTechnical && technicalLines.length > 0
3390
+ ? { total: technicalLines.length, pageRows: technicalPageRows }
3391
+ : undefined;
3392
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
3393
+ id: action.actionId ?? `${action.label}:${index}`,
3394
+ label: action.label,
3395
+ disabled: Boolean(action.disabled),
3396
+ })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), projectIntentIssue || unconfirmed.length > 0 ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, actionable.length > 0 && blocked.length + unconfirmed.length > 0
3397
+ ? _jsx(StaticRow, { status: "info", title: `只同步明确项;保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置。`, compact: true })
3398
+ : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u72B6\u6001\u5B89\u5168\u8BE6\u60C5", compact: true, children: [technicalOffset > 0
3399
+ ? _jsx(StaticRow, { title: `↑ 前面还有 ${technicalOffset} 行`, status: "muted", compact: true }) : null, visibleTechnicalLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${technicalOffset + index}:${line}`)), technicalOffset + visibleTechnicalLines.length < technicalLines.length
3400
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
3167
3401
  }
3168
3402
  else if (route.name === 'pluginList') {
3169
3403
  const sourceId = route.sourceId ?? 'all';
@@ -3193,10 +3427,8 @@ export function ConsoleAppV4(props) {
3193
3427
  actions = sourceError
3194
3428
  ? [{ label: '查看来源问题', description: '当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
3195
3429
  : [];
3196
- togglePluginListFilter = () => {
3430
+ choosePluginListFilter = (nextFilter) => {
3197
3431
  const selectedPluginId = pluginRows[focus]?.pluginId ?? pluginListFocusId.current;
3198
- const nextFilter = PLUGIN_FILTERS[(PLUGIN_FILTERS.indexOf(installFilter) + 1)
3199
- % PLUGIN_FILTERS.length];
3200
3432
  const nextItems = props.controller.listPlugins({
3201
3433
  mode: route.mode,
3202
3434
  ...(route.sourceId ? { sourceId: route.sourceId } : {}),
@@ -3210,11 +3442,13 @@ export function ConsoleAppV4(props) {
3210
3442
  setFocus(retained >= 0 ? retained : 0);
3211
3443
  setScrollOffset(0);
3212
3444
  };
3213
- movePluginListSource = (direction) => {
3214
- const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
3445
+ togglePluginListFilter = () => {
3446
+ const nextFilter = PLUGIN_FILTERS[(PLUGIN_FILTERS.indexOf(installFilter) + 1)
3447
+ % PLUGIN_FILTERS.length];
3448
+ choosePluginListFilter?.(nextFilter);
3449
+ };
3450
+ choosePluginListSource = (next) => {
3215
3451
  const selectedPluginId = pluginRows[focus]?.pluginId;
3216
- const index = tabs.indexOf(sourceId);
3217
- const next = tabs[(index + direction + tabs.length) % tabs.length];
3218
3452
  const nextItems = props.controller.listPlugins({
3219
3453
  mode: route.mode,
3220
3454
  ...(next !== 'all' ? { sourceId: next } : {}),
@@ -3232,6 +3466,11 @@ export function ConsoleAppV4(props) {
3232
3466
  setFocus(retainedFocus);
3233
3467
  setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
3234
3468
  };
3469
+ movePluginListSource = (direction) => {
3470
+ const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
3471
+ const index = tabs.indexOf(sourceId);
3472
+ choosePluginListSource?.(tabs[(index + direction + tabs.length) % tabs.length]);
3473
+ };
3235
3474
  const listedPluginIds = new Set(items.map((item) => item.pluginId));
3236
3475
  const recommendedUpdateCandidates = props.controller.listPluginWriteTaskCandidates('update')
3237
3476
  .filter((candidate) => candidate.eligible && listedPluginIds.has(candidate.target.pluginId));
@@ -3275,13 +3514,20 @@ export function ConsoleAppV4(props) {
3275
3514
  {
3276
3515
  actionId: 'plugin-list-filter',
3277
3516
  label: `筛选:${pluginFilterLabel(installFilter)}`,
3278
- run: togglePluginListFilter,
3517
+ run: () => setPluginListChooser({
3518
+ kind: 'status',
3519
+ focus: Math.max(0, PLUGIN_FILTERS.indexOf(installFilter)),
3520
+ }),
3279
3521
  },
3280
3522
  { actionId: 'plugin-list-search', label: '/ 搜索', run: () => setSearchFocus(true) },
3281
3523
  {
3282
3524
  actionId: 'plugin-list-source',
3283
3525
  label: `来源:${sourceId === 'all' ? '全部' : sourceId}`,
3284
- run: () => movePluginListSource?.(1),
3526
+ run: () => setPluginListChooser({
3527
+ kind: 'source',
3528
+ focus: Math.max(0, ['all', ...props.controller.listSources().map((source) => source.id)]
3529
+ .indexOf(sourceId)),
3530
+ }),
3285
3531
  },
3286
3532
  ];
3287
3533
  const pluginRowsById = new Map([
@@ -3293,7 +3539,8 @@ export function ConsoleAppV4(props) {
3293
3539
  ? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
3294
3540
  : item.description,
3295
3541
  ...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
3296
- placementSummary: pluginPlacementSummary(item),
3542
+ placementSummary: '',
3543
+ tags: pluginPlatformBadges(item),
3297
3544
  statusLabel: statusText(item),
3298
3545
  status: pluginRowStatus(item),
3299
3546
  }]),
@@ -3374,7 +3621,7 @@ export function ConsoleAppV4(props) {
3374
3621
  id: action.actionId ?? `${action.label}:${index}`,
3375
3622
  label: action.label,
3376
3623
  disabled: Boolean(action.disabled),
3377
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _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: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3624
+ })), active: !pluginListChooser && focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _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: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3378
3625
  ? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3379
3626
  : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " \u5168\u90E8 ", filterCounts.all, " \u00B7 \u5DF2\u88C5 ", filterCounts.installed, " \u00B7 \u672A\u88C5 ", filterCounts.uninstalled, " \u00B7 \u66F4\u65B0 ", filterCounts.updateAvailable, " \u00B7 \u5904\u7406 ", filterCounts.attention, " f \u5207\u6362"] }), sourceError
3380
3627
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
@@ -3383,8 +3630,8 @@ export function ConsoleAppV4(props) {
3383
3630
  ? _jsx(Callout, { title: "\u6CA1\u6709\u53EF\u66F4\u65B0\u63D2\u4EF6", description: "\u5F53\u524D\u7B5B\u9009\u8303\u56F4\u5185\u6CA1\u6709\u7ECF\u8FC7\u7248\u672C\u8BC1\u636E\u786E\u8BA4\u7684\u66F4\u65B0\u3002" })
3384
3631
  : _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" })
3385
3632
  : null, _jsx(ContentViewport, { children: pluginRows.length > 0
3386
- ? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport, active: focusZone === 'content' })
3387
- : _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] }));
3633
+ ? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport, active: !pluginListChooser && focusZone === 'content' })
3634
+ : _jsx(SemanticRows, { actions: actions, focus: !pluginListChooser && focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] }));
3388
3635
  }
3389
3636
  else if (route.name === 'batchSelection') {
3390
3637
  breadcrumb = '插件 / 选择多个';
@@ -3419,6 +3666,7 @@ export function ConsoleAppV4(props) {
3419
3666
  : statusText(plugin)}`
3420
3667
  : `${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`,
3421
3668
  meta: plugin ? batchPluginVersionSummary(plugin) : '版本待确认',
3669
+ tags: plugin ? pluginPlatformBadges(plugin) : undefined,
3422
3670
  selected: isSelected,
3423
3671
  pluginId: candidate.pluginId,
3424
3672
  canSelectNew: blockReason === undefined,
@@ -3467,9 +3715,10 @@ export function ConsoleAppV4(props) {
3467
3715
  } : current);
3468
3716
  setFocus(0);
3469
3717
  setScrollOffset(0);
3718
+ setFocusZone('content');
3470
3719
  },
3471
3720
  };
3472
- const utilityActions = batchDraft.showSelectionTools ? [
3721
+ const selectionToolActions = [
3473
3722
  {
3474
3723
  label: '返回插件选择',
3475
3724
  description: '保留当前已选插件',
@@ -3477,6 +3726,7 @@ export function ConsoleAppV4(props) {
3477
3726
  setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
3478
3727
  setFocus(0);
3479
3728
  setScrollOffset(0);
3729
+ setFocusZone('content');
3480
3730
  },
3481
3731
  },
3482
3732
  {
@@ -3499,40 +3749,41 @@ export function ConsoleAppV4(props) {
3499
3749
  label: '清空全部已选',
3500
3750
  run: () => setBatchDraft((current) => current ? { ...current, selectedPluginIds: [] } : current),
3501
3751
  },
3502
- ] : [
3503
- selectedSetAction,
3504
- {
3505
- label: '选择工具',
3506
- description: '全选、取消当前结果或清空全部选择',
3507
- run: () => {
3508
- setBatchDraft((current) => current ? { ...current, showSelectionTools: true } : current);
3509
- setFocus(candidateActions.length + 1);
3510
- setScrollOffset(Math.max(0, candidateActions.length));
3511
- },
3512
- },
3513
3752
  ];
3514
- actions = [continueAction, ...candidateActions, ...utilityActions];
3515
- const batchFocus = Math.min(focus, actions.length - 1);
3753
+ const selectionToolsAction = {
3754
+ actionId: 'batch-selection-tools',
3755
+ label: batchDraft.showSelectionTools ? '返回插件选择' : '选择工具',
3756
+ description: '全选、取消当前结果或清空全部选择',
3757
+ run: () => {
3758
+ setBatchDraft((current) => current ? {
3759
+ ...current,
3760
+ showSelectionTools: !current.showSelectionTools,
3761
+ } : current);
3762
+ setFocus(0);
3763
+ setScrollOffset(0);
3764
+ setFocusZone('content');
3765
+ },
3766
+ };
3767
+ actions = batchDraft.showSelectionTools ? selectionToolActions : candidateActions;
3768
+ pinnedActions = [continueAction, selectedSetAction, selectionToolsAction];
3769
+ const batchFocus = Math.min(focus, Math.max(0, actions.length - 1));
3516
3770
  context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
3517
- help = '↑↓ 移动 · Space 选择 · Enter 查看 · c 查看确认 · ←→ 来源 · / 搜索 · f 状态 · Esc 返回';
3518
- const viewport = rowViewport({
3519
- rowHeights: [
3520
- ...candidateActions.map(() => 2),
3521
- ...utilityActions.map((action) => policy.mode === 'compact'
3522
- ? 1 : action.description || action.disabled ? 2 : 1),
3523
- ],
3524
- focus: Math.max(0, batchFocus - 1),
3525
- requestedOffset: Math.max(0, scrollOffset - 1),
3526
- rowBudget: Math.max(1, pageActionBudget(5)),
3527
- });
3528
- const candidateOffset = Math.min(viewport.offset, candidateActions.length);
3529
- const candidateLimit = Math.max(0, Math.min(candidateActions.length, viewport.offset + viewport.limit) - candidateOffset);
3530
- const utilityOffset = Math.max(0, viewport.offset - candidateActions.length);
3531
- const utilityLimit = Math.max(0, Math.min(utilityActions.length, viewport.offset + viewport.limit - candidateActions.length)
3532
- - utilityOffset);
3533
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3771
+ help = batchDraft.showSelectionTools
3772
+ ? '↑↓ 移动 · Enter 执行 · Tab 操作栏 · Esc 返回选择'
3773
+ : '↑↓ 移动 · Space 选择 · Enter 查看 · Tab 操作栏 · c 查看确认 · ←→ 来源 · / 搜索 · f 状态 · Esc 返回';
3774
+ const cardWidth = Math.max(20, size.columns - 2);
3775
+ const viewport = batchDraft.showSelectionTools
3776
+ ? actionViewport(actions, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)))
3777
+ : objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', batchFocus, scrollOffset, Math.max(1, pageActionBudget(4)));
3778
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
3779
+ id: action.actionId ?? action.label,
3780
+ label: action.label,
3781
+ disabled: Boolean(action.disabled),
3782
+ })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
3534
3783
  ? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3535
- : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(batchDraft.installFilter), "]"] }), " f \u5207\u6362"] }), _jsx(SemanticRows, { actions: [continueAction], focus: batchFocus, kind: "action", compact: true }), candidateLimit > 0 ? _jsx(SemanticRows, { actions: candidateActions, focus: batchFocus - 1, kind: "toggle", compact: false, offset: candidateOffset, limit: candidateLimit }) : null, utilityLimit > 0 ? _jsx(SemanticRows, { actions: utilityActions, focus: batchFocus - 1 - candidateActions.length, kind: "action", compact: policy.mode === 'compact', offset: utilityOffset, limit: utilityLimit }) : null] });
3784
+ : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(batchDraft.installFilter), "]"] }), " f \u5207\u6362"] }), _jsx(ContentViewport, { children: batchDraft.showSelectionTools
3785
+ ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? batchFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })
3786
+ : _jsx(Box, { flexDirection: "column", children: actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => (_jsx(ObjectCardRow, { width: cardWidth, title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.disabled ?? action.description ?? '', meta: action.meta, tags: action.tags, focused: focusZone === 'content' && viewport.offset + visibleIndex === batchFocus, anchored: focusZone !== 'content' && viewport.offset + visibleIndex === batchFocus, compact: policy.mode === 'compact' }, action.pluginId ?? action.label))) }) })] });
3536
3787
  }
3537
3788
  else {
3538
3789
  const selectedIds = batchDraft.selectedPluginIds;
@@ -3929,13 +4180,19 @@ export function ConsoleAppV4(props) {
3929
4180
  ? draft.candidates.filter((candidate) => candidate.selected)
3930
4181
  : draft.candidates;
3931
4182
  const continuePortableSelection = () => {
3932
- if (draft.canContinue)
3933
- go({ name: 'portableAgentMode', pluginId: plugin.pluginId });
4183
+ if (!draft.canContinue) {
4184
+ setPortableEmptySelectionOpen(true);
4185
+ return;
4186
+ }
4187
+ go({ name: 'portableAgentMode', pluginId: plugin.pluginId });
3934
4188
  };
3935
4189
  const candidateActions = visibleCandidates.map((candidate) => ({
3936
4190
  label: candidate.name,
3937
4191
  selected: candidate.selected,
3938
4192
  description: `${candidate.description}${candidate.dynamic ? ' · 按设置生成' : ''}`,
4193
+ meta: `${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件'
4194
+ : candidate.relation === 'direct' ? '直接依赖插件' : '间接依赖插件'}`,
4195
+ tags: _jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", candidate.availableScopes.map(scopeLabel).join('、') || '范围待确认', "]", candidate.dynamic ? ' [按设置生成]' : ''] }),
3939
4196
  run: () => go({
3940
4197
  name: 'portableSkillSummary',
3941
4198
  pluginId: plugin.pluginId,
@@ -3970,7 +4227,7 @@ export function ConsoleAppV4(props) {
3970
4227
  setScrollOffset(0);
3971
4228
  } },
3972
4229
  ];
3973
- const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount})`, meta: draft.canContinue ? undefined : '至少选择一个 Skill', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: continuePortableSelection };
4230
+ const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount})`, run: continuePortableSelection };
3974
4231
  const openToolsAction = { actionId: 'portable-selection-tools', label: '选择工具', run: () => {
3975
4232
  setPortableToolsOpen(true);
3976
4233
  setFocusZone('content');
@@ -3979,17 +4236,13 @@ export function ConsoleAppV4(props) {
3979
4236
  } };
3980
4237
  pinnedActions = [continueAction, openToolsAction];
3981
4238
  actions = portableToolsOpen ? selectionToolActions : candidateActions;
3982
- const candidateViewport = portableSkillCandidateViewport(candidateActions, visibleCandidates, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(2 + (portableVariantCandidates.length > 0 ? 3 : 0)));
4239
+ const candidateViewport = portableSkillCandidateViewport(candidateActions, Math.max(20, size.columns - 2), policy.mode === 'compact', focus, scrollOffset, pageActionBudget(2 + (portableVariantCandidates.length > 0 ? 3 : 0)));
3983
4240
  const viewport = showTechnical
3984
4241
  ? { offset: focus < visibleCandidates.length ? focus : candidateViewport.offset, limit: 1 }
3985
4242
  : candidateViewport;
3986
4243
  const visibleCandidateEntries = visibleCandidates
3987
4244
  .map((candidate, index) => ({ candidate, action: candidateActions[index], index }))
3988
4245
  .slice(viewport.offset, viewport.offset + viewport.limit);
3989
- const providerGroups = [...new Map(visibleCandidateEntries.map((entry) => [
3990
- entry.candidate.providerPluginId,
3991
- visibleCandidateEntries.filter((candidate) => candidate.candidate.providerPluginId === entry.candidate.providerPluginId),
3992
- ])).values()];
3993
4246
  const variantCandidates = portableVariantCandidates;
3994
4247
  const focusedVariant = variantCandidates.find((candidate) => candidate.name === visibleCandidates[focus]?.name);
3995
4248
  const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
@@ -4001,14 +4254,9 @@ export function ConsoleAppV4(props) {
4001
4254
  : '↑↓ 移动 · Space 切换 · Enter 查看摘要 · Tab 固定栏 · c 继续 · Esc 返回';
4002
4255
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4003
4256
  id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4004
- })), active: focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [portableToolsOpen
4257
+ })), active: !portableEmptySelectionOpen && focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [portableToolsOpen
4005
4258
  ? _jsx(SemanticRows, { actions: selectionToolActions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact' })
4006
- : showTechnical ? visibleCandidateEntries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, selected: Boolean(action.selected), focused: focusZone === 'content' && focus === index, compact: true }, String(action.label))) : providerGroups.map((entries) => {
4007
- const provider = entries[0].candidate;
4008
- const relation = provider.relation === 'requested' ? '当前插件'
4009
- : provider.relation === 'direct' ? '直接依赖插件' : '间接依赖插件';
4010
- 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: focusZone === 'content' && focus === index, compact: policy.mode === 'compact' }, String(action.label))) }, provider.providerPluginId);
4011
- }), !portableToolsOpen && variantCandidates.length > 0
4259
+ : visibleCandidateEntries.map(({ action, index }) => _jsx(ObjectCardRow, { width: Math.max(20, size.columns - 2), title: `${action.selected ? '[x]' : '[ ]'} ${action.label}`, description: action.description ?? '', meta: action.meta, tags: action.tags, focused: !portableEmptySelectionOpen && focusZone === 'content' && focus === index, anchored: !portableEmptySelectionOpen && focusZone !== 'content' && focus === index, muted: portableEmptySelectionOpen, compact: policy.mode === 'compact' }, String(action.label))), !portableToolsOpen && variantCandidates.length > 0
4012
4260
  ? showTechnical
4013
4261
  ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4014
4262
  : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
@@ -4079,16 +4327,29 @@ export function ConsoleAppV4(props) {
4079
4327
  else if (route.name === 'portableBlocker' && plugin) {
4080
4328
  breadcrumb = `${plugin.displayName} / 通用 Skills`;
4081
4329
  const diagnostic = portableBlockerDiagnostic(route);
4082
- title = diagnostic.title;
4083
- description = '';
4084
- actions = [
4330
+ const cliUnavailable = route.code === 'portableSkillsCliMissing'
4331
+ || route.code === 'portableSkillsCliIncompatible';
4332
+ const showPortableRequirements = cliUnavailable && showTechnical;
4333
+ if (showPortableRequirements) {
4334
+ breadcrumb = `${plugin.displayName} / 通用 Skills / 环境`;
4335
+ context = '第 1 / 1 页';
4336
+ title = '准备安装工具';
4337
+ description = '完成环境准备后返回问题摘要并重新检查。';
4338
+ }
4339
+ else {
4340
+ title = diagnostic.title;
4341
+ description = '';
4342
+ }
4343
+ actions = showPortableRequirements ? [
4344
+ { label: '返回问题摘要', run: () => toggleTechnical() },
4345
+ ] : [
4085
4346
  ...(route.code === 'requiredConfigMissing' ? [{ label: '完成插件设置', run: () => {
4086
4347
  props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'effective' } });
4087
4348
  setFocus(0);
4088
4349
  setScrollOffset(0);
4089
4350
  } }] : []),
4090
- ...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
4091
- ? [{ label: showTechnical ? '返回问题摘要' : '查看安装说明', run: () => toggleTechnical() }]
4351
+ ...(cliUnavailable
4352
+ ? [{ label: '查看准备要求', run: () => toggleTechnical() }]
4092
4353
  : []),
4093
4354
  ...(route.code === 'portableScopeUnavailable' ? [{ label: '返回 Skill 选择', run: () => {
4094
4355
  props.controller.resetPortableFlowToPreview(plugin.pluginId, { name: 'portableSkillSelect', pluginId: plugin.pluginId }, true);
@@ -4097,12 +4358,12 @@ export function ConsoleAppV4(props) {
4097
4358
  } }] : []),
4098
4359
  { label: '重新检查', run: () => beginPortable(plugin.pluginId) },
4099
4360
  { label: snapshot.installPlacementDraft?.pluginId === plugin.pluginId ? '返回目标平台' : '返回插件详情', run: () => props.controller.resetPortableFlowToPreview(plugin.pluginId) },
4100
- ...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
4361
+ ...(cliUnavailable
4101
4362
  ? []
4102
4363
  : [{ label: showTechnical ? '返回问题摘要' : '技术详情', run: () => toggleTechnical() }]),
4103
4364
  ];
4104
4365
  const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
4105
- 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 })] });
4366
+ content = _jsxs(Box, { flexDirection: "column", children: [showPortableRequirements ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5728 PATH \u4E2D\u63D0\u4F9B\u517C\u5BB9\u7684 skills \u53EF\u6267\u884C\u6587\u4EF6\u3002", compact: true }), _jsx(StaticRow, { title: `当前 AW adapter 支持:${SKILLS_CLI_SUPPORTED_VERSION_LABEL}`, compact: true }), _jsx(StaticRow, { title: "\u51C6\u5907\u5B8C\u6210\u540E\u8FD4\u56DE\u95EE\u9898\u6458\u8981\u5E76\u91CD\u65B0\u68C0\u67E5\u3002", compact: true }), _jsx(StaticRow, { title: "\u4E0D\u4F1A\u81EA\u52A8\u8FD0\u884C npx latest\u3001sudo \u6216\u5728\u7EBF\u5B89\u88C5\u811A\u672C\u3002", status: "warning", compact: true }), _jsx(StaticRow, { title: "\u9009\u5B9A\u63D2\u4EF6\u3001\u5B89\u88C5\u76EE\u6807\u548C Portable \u8349\u7A3F\u4FDD\u6301\u4E0D\u53D8\u3002", status: "success", compact: true }), _jsx(StaticRow, { title: "AW \u4E0D\u4F1A\u5728\u6B64\u9875\u5B89\u88C5\u5916\u90E8\u4F9D\u8D56\u3002", status: "warning", compact: true })] }) : _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 })] });
4106
4367
  }
4107
4368
  else if (route.name === 'pluginDependencies' && plugin) {
4108
4369
  const overview = dependencyOverview(plugin);
@@ -4446,24 +4707,52 @@ export function ConsoleAppV4(props) {
4446
4707
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
4447
4708
  { label: '返回插件详情', run: back },
4448
4709
  ];
4449
- 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' })] });
4710
+ 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)} · ${choice.disabled ? `不可选 · ${choice.disabled}` : '不可选'}`, status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4450
4711
  }
4451
4712
  else {
4452
4713
  const confirmNativePlatforms = () => {
4714
+ if (validSelectedTargets.length === 0) {
4715
+ setActionError('请至少选择一个目标平台。');
4716
+ return;
4717
+ }
4453
4718
  setDirty(false);
4454
4719
  props.controller.setInstallPlatforms(validSelectedTargets);
4455
4720
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
4456
4721
  };
4457
- const targetActions = [
4458
- ...choices.map((choice) => ({
4722
+ const nativeActions = choices.map((choice) => {
4723
+ const installed = installedScopeStates(plugin, choice.platform);
4724
+ const configuredScopes = plugin.supportedScopes?.[choice.platform];
4725
+ const supportedScopes = ['project', 'user'].filter((candidateScope) => configuredScopes?.includes(candidateScope)
4726
+ || installed.has(candidateScope) || choice.scopes.includes(candidateScope));
4727
+ const locationItems = supportedScopes.map((candidateScope) => {
4728
+ const state = installed.get(candidateScope);
4729
+ if (state)
4730
+ return {
4731
+ key: `${choice.platform}:${candidateScope}`,
4732
+ label: scopeLabel(candidateScope),
4733
+ symbol: state.symbol,
4734
+ status: state.status,
4735
+ };
4736
+ return choice.scopes.includes(candidateScope)
4737
+ ? { key: `${choice.platform}:${candidateScope}`, label: scopeLabel(candidateScope),
4738
+ symbol: '○', status: 'muted' }
4739
+ : { key: `${choice.platform}:${candidateScope}`, label: scopeLabel(candidateScope),
4740
+ symbol: '?', status: 'muted' };
4741
+ });
4742
+ const missingLabels = choice.scopes.filter((candidateScope) => !installed.has(candidateScope))
4743
+ .map(scopeLabel);
4744
+ return {
4745
+ actionId: `install-platform-${choice.platform}`,
4459
4746
  label: platformLabel(choice.platform),
4460
4747
  selected: validSelectedTargets.includes(choice.platform),
4461
- description: choice.disabled ?? '当前未安装,支持所选位置',
4462
- meta: validSelectedTargets.includes(choice.platform) ? '已选择' : undefined,
4463
- disabled: choice.disabled,
4748
+ description: choice.disabled ? `不可选择:${choice.disabled}` : (missingLabels.length > 0
4749
+ ? `可添加到:${missingLabels.join('、')}` : '所有支持位置已经安装'),
4750
+ status: choice.disabled ? 'muted' : undefined,
4751
+ tags: locationItems.length > 0 ? _jsx(StatusBadge, { items: locationItems }) : undefined,
4464
4752
  run: () => {
4465
4753
  if (!choice.compatible)
4466
4754
  return;
4755
+ setActionError(undefined);
4467
4756
  setSelectedTargets((current) => {
4468
4757
  const next = current.includes(choice.platform)
4469
4758
  ? current.filter((platform) => platform !== choice.platform)
@@ -4472,30 +4761,46 @@ export function ConsoleAppV4(props) {
4472
4761
  return next;
4473
4762
  });
4474
4763
  },
4475
- })),
4476
- {
4477
- actionId: 'portable-agent-skills',
4478
- label: '通用 Agent Skills',
4479
- description: plugin.portableSkills
4480
- ? '通过外部 Skills CLI 安装到支持的 Agent'
4481
- : '这个插件不提供通用 Agent Skills',
4482
- disabled: plugin.portableSkills ? undefined : '这个插件不提供通用 Agent Skills',
4483
- run: () => beginPortable(plugin.pluginId),
4484
- },
4485
- ];
4486
- actions = [
4487
- ...targetActions,
4488
- {
4489
- actionId: 'confirm-install-platforms',
4490
- label: '确定所选平台',
4491
- description: validSelectedTargets.length > 0 ? `已选择 ${validSelectedTargets.length} 个平台` : undefined,
4492
- disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
4493
- run: confirmNativePlatforms,
4764
+ };
4765
+ });
4766
+ const portableAction = {
4767
+ actionId: 'portable-agent-skills',
4768
+ label: '通用 Agent Skills →',
4769
+ description: plugin.portableSkills
4770
+ ? '通过外部 Skills CLI 安装到支持的 Agent;只安装选定 Skills'
4771
+ : '这个插件不提供通用 Agent Skills',
4772
+ status: plugin.portableSkills ? undefined : 'muted',
4773
+ tags: plugin.portableSkills
4774
+ ? _jsx(Text, { color: tuiTheme.colors.accent, children: "[Skills \u53EF\u9009]" })
4775
+ : undefined,
4776
+ run: () => {
4777
+ if (!plugin.portableSkills)
4778
+ return;
4779
+ beginPortable(plugin.pluginId);
4494
4780
  },
4495
- { label: '返回插件详情', run: back },
4496
- ];
4497
- help = '↑↓ 移动 · Enter 选择/确定 · c 确定 · Esc 返回';
4498
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length })] });
4781
+ };
4782
+ const confirmAction = {
4783
+ actionId: 'confirm-install-platforms',
4784
+ label: '确定所选平台',
4785
+ disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
4786
+ run: confirmNativePlatforms,
4787
+ };
4788
+ const backAction = {
4789
+ actionId: 'install-platforms-back', label: '返回插件详情', run: back,
4790
+ };
4791
+ actions = [...nativeActions, portableAction];
4792
+ pinnedActions = [confirmAction, backAction];
4793
+ const cardWidth = Math.max(20, size.columns - 2);
4794
+ const installTargetStaticRows = (validSelectedTargets.length === 0 ? 2 : 1)
4795
+ + (policy.mode === 'compact' ? 4 : 0);
4796
+ const platformViewport = objectCardActionViewport(actions, cardWidth, policy.mode === 'compact', focus, scrollOffset, Math.max(1, pageActionBudget(installTargetStaticRows)));
4797
+ help = '↑↓ 移动 · Enter/Space 勾选平台 · Enter 打开通用 Skills · Tab 操作栏 · c 继续 · Esc 返回';
4798
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4799
+ id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4800
+ })), active: focusZone === 'actions', focus: pinnedActionFocus }), validSelectedTargets.length === 0
4801
+ ? _jsx(Text, { color: tuiTheme.colors.muted, children: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u76EE\u6807\u5E73\u53F0" }) : null, _jsx(ContentViewport, { children: actions.slice(platformViewport.offset, platformViewport.offset + platformViewport.limit)
4802
+ .map((action, visibleIndex) => _jsx(ObjectCardRow, { width: cardWidth, title: action.actionId?.startsWith('install-platform-')
4803
+ ? `${action.selected ? '[x]' : '[ ]'} ${action.label}` : action.label, description: action.disabled ?? action.description ?? '', tags: action.tags, focused: focusZone === 'content' && platformViewport.offset + visibleIndex === focus, anchored: focusZone !== 'content' && platformViewport.offset + visibleIndex === focus, compact: policy.mode === 'compact' }, action.actionId)) })] });
4499
4804
  }
4500
4805
  }
4501
4806
  else if (route.name === 'installPlacement' && plugin) {
@@ -4561,7 +4866,7 @@ export function ConsoleAppV4(props) {
4561
4866
  const scopeChoiceCount = commonScopes.length > 0 ? actions.length : Math.max(0, actions.length - 2);
4562
4867
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: commonScopes.length > 0 ? 'choice' : 'toggle', compact: policy.mode === 'compact', limit: scopeChoiceCount }), commonScopes.length === 0
4563
4868
  ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: scopeChoiceCount })
4564
- : null, hasDependencies ? _jsx(Callout, { title: "\u4F9D\u8D56\u8BA1\u5212", description: "\u786E\u8BA4\u524D\u4F1A\u590D\u7528\u73B0\u6709 planner \u8BA1\u7B97\u5FC5\u9700\u4F9D\u8D56\u3002" }) : null] });
4869
+ : null, hasDependencies ? _jsx(Callout, { title: "\u4F9D\u8D56\u8BA1\u5212", description: "\u6240\u9700\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u6392\uFF1B\u786E\u8BA4\u524D\u53EF\u67E5\u770B\u5F71\u54CD\u3002" }) : null] });
4565
4870
  help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
4566
4871
  }
4567
4872
  else if (route.name === 'uninstallTargets' && plugin) {
@@ -4928,7 +5233,7 @@ export function ConsoleAppV4(props) {
4928
5233
  breadcrumb = `插件 / ${plugin.displayName}`;
4929
5234
  context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
4930
5235
  title = plugin.displayName;
4931
- description = plugin.description;
5236
+ description = '';
4932
5237
  const updateDescription = pluginUpdateDescription(plugin);
4933
5238
  const installedVersions = [...new Set((plugin.update?.targets ?? [])
4934
5239
  .flatMap((target) => target.installedVersion ? [target.installedVersion] : []))];
@@ -5040,7 +5345,7 @@ export function ConsoleAppV4(props) {
5040
5345
  : plugin.capabilityUnsupported
5041
5346
  ? '当前 AW CLI 不支持这个插件'
5042
5347
  : !hasInstallationOption
5043
- ? '没有可信且兼容的安装方式'
5348
+ ? '当前工具和插件没有可用的安装方式'
5044
5349
  : '选择工具和使用范围',
5045
5350
  ...(plugin.sourceUnavailable || plugin.capabilityUnsupported || !hasInstallationOption
5046
5351
  ? { disabled: plugin.sourceUnavailable ? '插件来源不可用'
@@ -5104,35 +5409,34 @@ export function ConsoleAppV4(props) {
5104
5409
  && dependencyOverviewForDetail.summary.totalCount > 0
5105
5410
  ? `需要 ${dependencyOverviewForDetail.summary.totalCount} 个插件;当前可用关系按当前范围检查,未显示的关系尚未检查通过。`
5106
5411
  : undefined;
5107
- const placementConclusions = plugin.installedPlacements.map((placement) => {
5108
- const conclusions = pluginPlacementConclusions(placement);
5109
- const status = conclusions.some((conclusion) => conclusion.status === 'error') ? 'error'
5110
- : conclusions.some((conclusion) => conclusion.status === 'warning') ? 'warning'
5111
- : conclusions.some((conclusion) => conclusion.status === 'success') ? 'success'
5112
- : 'muted';
5113
- return {
5114
- platform: placement.platform,
5115
- key: placement.platform,
5116
- text: conclusions.map((conclusion) => conclusion.text).join(' · '),
5117
- status,
5118
- };
5119
- });
5120
- const hasUnavailablePlacement = placementConclusions.some((placement) => placement.status === 'error');
5121
- const hasUncertainPlacement = placementConclusions.some((placement) => placement.status === 'warning');
5122
- const confirmedAvailablePlacements = placementConclusions.filter((placement) => placement.status === 'success');
5123
- const visiblePlacementConclusions = placementConclusions;
5124
- const detailViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(actionViewportStaticRows));
5412
+ const detailPositions = plugin.installedPlacements
5413
+ .filter((placement) => placement.state !== 'notInstalled')
5414
+ .map((placement) => `${platformLabel(placement.platform)}:${pluginPlacementConclusions(placement).map((entry) => entry.text).join(' · ')}`);
5415
+ const detailSummary = [`${plugin.description}`, `版本 ${plugin.version ?? '未知'} · ${plugin.sourceLabel}`,
5416
+ ...detailPositions].join('\n');
5417
+ const detailSummaryLines = objectCardRowLayout({
5418
+ description: detailSummary, width: size.columns - 2, hasTags: true,
5419
+ }).descriptionLines;
5420
+ const detailExtraRows = (updateDescription ? 1 : 0) + (dependencyDetailText ? 1 : 0)
5421
+ + (snapshot.completionNotice?.pluginId === plugin.pluginId ? 4 : 0);
5422
+ const detailPageRows = Math.max(1, pageActionBudget(detailExtraRows + 4 + 3));
5423
+ const detailOffset = Math.min(batchTargetOffset, Math.max(0, detailSummaryLines.length - 1));
5424
+ const visibleDetailLines = detailSummaryLines.slice(detailOffset, detailOffset + detailPageRows);
5425
+ if (detailSummaryLines.length > detailPageRows) {
5426
+ context += ` · 摘要 ${detailOffset + 1}–${detailOffset + visibleDetailLines.length}/${detailSummaryLines.length}`;
5427
+ actions.push(...paginationActions({
5428
+ offset: detailOffset, total: detailSummaryLines.length, pageRows: detailPageRows,
5429
+ move: (direction) => moveBatchTargetPage(detailSummaryLines.length, detailPageRows, direction),
5430
+ }));
5431
+ referenceDetailPagination = { total: detailSummaryLines.length, pageRows: detailPageRows };
5432
+ }
5433
+ currentActionViewportLineBudget = pageActionBudget(detailExtraRows + 4 + visibleDetailLines.length);
5434
+ const detailViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
5125
5435
  content = _jsxs(Box, { flexDirection: "column", children: [snapshot.completionNotice?.pluginId === plugin.pluginId
5126
5436
  ? _jsx(Callout, { status: "success", title: snapshot.completionNotice.title, description: snapshot.completionNotice.description })
5127
- : null, _jsx(StatusCard, { status: hasUnavailablePlacement ? 'error'
5128
- : hasUncertainPlacement ? 'warning'
5129
- : confirmedAvailablePlacements.length > 0 ? 'success' : 'info', title: hasUnavailablePlacement ? '部分使用位置暂时不可用'
5130
- : hasUncertainPlacement ? '部分使用位置需要处理'
5131
- : confirmedAvailablePlacements.length > 0 ? '已确认的使用位置可用' : '尚未确认使用位置', children: visiblePlacementConclusions.map((placement) => _jsx(StaticRow, { title: platformLabel(placement.platform), meta: placement.text, status: placement.status, compact: true }, placement.key)) }), updateDescription
5437
+ : null, _jsx(ObjectCardRow, { width: size.columns - 2, title: plugin.displayName, description: visibleDetailLines.join('\n'), tags: pluginPlatformBadges(plugin) }), updateDescription
5132
5438
  ? _jsx(StaticRow, { title: "\u6709\u53EF\u7528\u66F4\u65B0", meta: updateDescription, status: "warning", compact: true })
5133
- : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: "muted", compact: true }) : null, policy.mode === 'compact'
5134
- ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit })
5135
- : _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit }) })] });
5439
+ : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailViewport.offset, limit: detailViewport.limit })] });
5136
5440
  }
5137
5441
  else if (route.name === 'pluginMore' && plugin) {
5138
5442
  breadcrumb = `插件 / ${plugin.displayName} / 更多操作`;
@@ -5480,6 +5784,24 @@ export function ConsoleAppV4(props) {
5480
5784
  const selectedPreset = route.presetId && configLayerDefinitionPage
5481
5785
  ? configLayerDefinitionPage.presets.find((preset) => preset.id === route.presetId)
5482
5786
  : undefined;
5787
+ const selectedMutation = selectedField && configCandidate
5788
+ ? configCandidate.mutations[selectedField.key]
5789
+ : undefined;
5790
+ const candidateSummary = selectedField && selectedMutation
5791
+ ? selectedMutation.kind === 'unset' ? '恢复继承'
5792
+ : selectedField.sensitive ? '已更新'
5793
+ : typeof selectedMutation.value === 'object'
5794
+ ? objectSummary(selectedField, selectedMutation.value)
5795
+ : scalarSummary(selectedField, selectedMutation.value)
5796
+ : selectedPreset && configCandidate
5797
+ ? `方案“${selectedPreset.title}” · ${Object.keys(configCandidate.mutations).length} 项修改`
5798
+ : undefined;
5799
+ const candidateSummaryLines = candidateSummary
5800
+ ? wrapConsoleText(candidateSummary, Math.max(12, size.columns - 14))
5801
+ : [];
5802
+ const candidateSummaryDisplay = candidateSummaryLines.length > 1
5803
+ ? `${candidateSummaryLines[0]}…`
5804
+ : candidateSummaryLines[0];
5483
5805
  title = selectedField ? `“${selectedField.title}”保存到哪里?`
5484
5806
  : selectedPreset ? `“${selectedPreset.title}”保存到哪里?` : '这个值保存到哪里?';
5485
5807
  description = selectedField
@@ -5513,19 +5835,19 @@ export function ConsoleAppV4(props) {
5513
5835
  }] : []),
5514
5836
  ];
5515
5837
  content = actions.length > 0
5516
- ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' })
5838
+ ? _jsxs(Box, { flexDirection: "column", children: [candidateSummaryDisplay ? _jsx(StatusCard, { status: "info", title: "\u5DF2\u786E\u5B9A\u65B0\u503C", description: `新值:${candidateSummaryDisplay}`, compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' })] })
5517
5839
  : _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u4FDD\u5B58\u4F4D\u7F6E" });
5518
5840
  }
5519
5841
  else if (route.name === 'configFields' && plugin) {
5520
5842
  const readonly = false;
5521
5843
  breadcrumb = `${plugin.displayName} / 设置`;
5522
5844
  title = '插件设置';
5523
- description = '先编辑值,再选择保存位置。← → 切换设置分组。';
5845
+ description = '先编辑值,再选择保存位置。Tab 可选择完整分组。';
5524
5846
  if (configActionError)
5525
5847
  description = configActionError;
5526
- help = '↑↓ 移动 · Enter 修改 · ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5848
+ help = '↑↓ 移动 · Enter 修改 · Tab 固定操作 · ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
5527
5849
  if (!currentConfigPage) {
5528
- content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u53EF\u4FE1\u8BBE\u7F6E\u5B9A\u4E49", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6\u63D2\u4EF6\u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
5850
+ content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u8BBE\u7F6E", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6\u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
5529
5851
  }
5530
5852
  else {
5531
5853
  visibleConfigTabs = configTabs(currentConfigPage);
@@ -5536,7 +5858,7 @@ export function ConsoleAppV4(props) {
5536
5858
  ? currentConfigPage.groups.find((group) => group.id === activeTab.id)
5537
5859
  : undefined;
5538
5860
  if (configDraftIssues.length > 0) {
5539
- description += ' 字段已变化;失效草稿不会保存。';
5861
+ description += ` 有 ${configDraftIssues.length} 项未保存修改已不适用于当前设置。`;
5540
5862
  }
5541
5863
  const mutableLayer = { scope: 'effective' };
5542
5864
  const tabActions = activeTab?.kind === 'presets'
@@ -5549,13 +5871,21 @@ export function ConsoleAppV4(props) {
5549
5871
  }))
5550
5872
  : (activeGroup?.fields ?? []).filter((field) => readonly || configFieldCanSet(field) || configFieldCanUnset(field)).map((field) => ({
5551
5873
  label: field.title,
5552
- description: field.invalidOverrideIssue ?? `${field.description ? `${field.description} · ` : ''}当前有效:${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`,
5874
+ description: field.invalidOverrideIssue ?? [
5875
+ `当前生效:${effectiveFieldDisplay(field)}(${field.effectiveSourceLabel})`,
5876
+ ...pendingFieldTargets(field).map(({ scope: draftScope, value }) => `待保存到${draftScope === 'project' ? '当前项目' : '个人默认'}:${value}`),
5877
+ field.description,
5878
+ ].filter((value) => Boolean(value)).join(' · '),
5553
5879
  meta: (() => {
5554
- const targets = ['project', 'user'].filter((draftScope) => Object.hasOwn(configDrafts[draftScope]?.updates ?? {}, field.key)
5555
- || configDrafts[draftScope]?.removes.includes(field.key));
5556
- return targets.length > 0
5557
- ? `拟保存:${targets.map((targetScope) => targetScope === 'project' ? '当前项目' : '个人默认').join('、')}`
5558
- : effectiveFieldDisplay(field);
5880
+ const targets = pendingFieldTargets(field);
5881
+ if (targets.length === 0) {
5882
+ return `${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`;
5883
+ }
5884
+ if (targets.length === 1) {
5885
+ const target = targets[0];
5886
+ return `${target.value} · 拟保存:${target.scope === 'project' ? '当前项目' : '个人默认'}`;
5887
+ }
5888
+ return targets.map((target) => `${target.scope === 'project' ? '当前项目' : '个人默认'}:${target.value}`).join(' · ');
5559
5889
  })(),
5560
5890
  configKey: field.key,
5561
5891
  run: () => go(['project', 'user'].some((draftScope) => configLayerPages[draftScope]?.groups.flatMap((group) => group.fields)
@@ -5575,8 +5905,8 @@ export function ConsoleAppV4(props) {
5575
5905
  }));
5576
5906
  const operationActions = [
5577
5907
  ...(configDraftIssues.length > 0 ? [{
5578
- label: '放弃已变化字段的草稿',
5579
- description: `${configDraftIssues.length} 项草稿的字段不存在、不可编辑或不再可删除;不会修改已保存设置。`,
5908
+ label: '放弃无法保存的修改',
5909
+ description: `${configDraftIssues.length} 项未保存修改已不再适用于当前插件设置;放弃后不会修改已保存设置。`,
5580
5910
  status: 'warning',
5581
5911
  run: discardInvalidConfigDraftChanges,
5582
5912
  }] : []),
@@ -5616,7 +5946,19 @@ export function ConsoleAppV4(props) {
5616
5946
  go({ name: 'configSave', pluginId: route.pluginId, layer: { scope: 'effective' } });
5617
5947
  },
5618
5948
  };
5619
- pinnedActions = [configSaveAction];
5949
+ const configGroupAction = {
5950
+ actionId: 'config-select-group',
5951
+ label: '选择分组',
5952
+ description: '查看全部设置分组和快速方案',
5953
+ disabled: visibleConfigTabs.length === 0 ? '没有可用设置分组' : undefined,
5954
+ run: () => {
5955
+ const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeTab?.id));
5956
+ setConfigGroupChoiceFocus(current);
5957
+ setConfigGroupChooserOpen(true);
5958
+ setFocusZone('content');
5959
+ },
5960
+ };
5961
+ pinnedActions = [configSaveAction, configGroupAction];
5620
5962
  actions = [...tabActions, ...operationActions];
5621
5963
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
5622
5964
  const focusedConfigField = effectiveConfigFocus < (activeGroup?.fields.length ?? 0)
@@ -5628,7 +5970,7 @@ export function ConsoleAppV4(props) {
5628
5970
  .some((field) => field.key === focusedConfigField.key && configFieldCanUnset(field)))
5629
5971
  : false;
5630
5972
  help = focusZone === 'actions'
5631
- ? 'Enter 查看保存汇总 · Tab 返回设置字段 · s 查看保存汇总 · Esc 返回'
5973
+ ? '↑↓ 移动 · Enter 选择固定操作 · Tab 返回设置字段 · s 查看保存汇总 · Esc 返回'
5632
5974
  : canUnsetFocused
5633
5975
  ? '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · u 恢复继承 · Esc 返回'
5634
5976
  : '↑↓ 移动 · Enter 修改 · ←→ 分组 · Tab 保存栏 · s 查看汇总 · Esc 返回';
@@ -5637,19 +5979,33 @@ export function ConsoleAppV4(props) {
5637
5979
  ? effectiveConfigFocus < tabActions.length ? effectiveConfigFocus : Math.min(scrollOffset, tabActions.length - 1)
5638
5980
  : 0;
5639
5981
  const operationRows = operationActions.reduce((rows, action) => rows + (policy.mode === 'compact' || !action.description ? 1 : 2), 0);
5640
- const viewport = actionViewport(tabActions, policy.mode === 'compact', fieldFocus, scrollOffset, pageActionBudget(2 + operationRows + 3 + (configDraftIssues.length > 0 ? 3 : 0)));
5982
+ const viewport = actionViewport(tabActions, false, fieldFocus, scrollOffset, pageActionBudget(2 + operationRows + 3 + (configDraftIssues.length > 0 ? 3 : 0)));
5641
5983
  const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
5642
5984
  const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
5643
5985
  context = configDraftCount > 0 ? `${configDraftCount} 项待保存` : countLabel;
5644
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: [{
5645
- id: 'config-save-summary',
5646
- label: configSaveAction.label,
5647
- disabled: Boolean(configSaveAction.disabled),
5648
- }], active: focusZone === 'actions', focus: pinnedActionFocus }), !readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u5B57\u6BB5\u5DF2\u53D8\u5316", description: "\u6765\u6E90\u6062\u590D\u540E\u5B57\u6BB5\u5B9A\u4E49\u5DF2\u66F4\u65B0\uFF1B\u5B57\u6BB5\u53EF\u80FD\u4E0D\u5B58\u5728\u3001\u4E0D\u53EF\u7F16\u8F91\u6216\u4E0D\u518D\u53EF\u5220\u9664\u3002\u8BF7\u663E\u5F0F\u653E\u5F03\u5931\u6548\u8349\u7A3F\uFF0C\u6216\u8FD4\u56DE\u7EE7\u7EED\u68C0\u67E5\u6765\u6E90\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u6216\u590D\u7528\u65E7\u786E\u8BA4\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
5649
- ? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "section", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })
5650
- : null, operationActions.length > 0
5651
- ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
5652
- : null] });
5986
+ const groupChoiceActions = visibleConfigTabs.map((tab) => ({
5987
+ label: tab.label,
5988
+ description: tab.kind === 'presets' ? `${tab.count} 个快速方案` : `${tab.count} 个设置字段`,
5989
+ }));
5990
+ const groupChoiceViewport = actionViewport(groupChoiceActions, false, configGroupChoiceFocus, 0, Math.max(2, pageActionBudget(5)));
5991
+ if (configGroupChooserOpen) {
5992
+ title = '选择设置分组';
5993
+ description = '选择后回到同一设置总览;未保存草稿保持不变。';
5994
+ help = '↑↓ 移动 · Enter 选择 · Esc 取消';
5995
+ context = `${visibleConfigTabs.length} 个入口`;
5996
+ }
5997
+ content = configGroupChooserOpen
5998
+ ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["\u5B8C\u6574\u540D\u79F0\uFF1A", visibleConfigTabs[configGroupChoiceFocus]?.label] }), _jsx(SectionCard, { title: "\u5168\u90E8\u5206\u7EC4\u548C\u5FEB\u901F\u65B9\u6848", meta: groupChoiceViewport.total > groupChoiceViewport.limit
5999
+ ? `${groupChoiceViewport.position}/${groupChoiceViewport.total}` : undefined, children: _jsx(SemanticRows, { actions: groupChoiceActions, focus: configGroupChoiceFocus, kind: "section", compact: false, offset: groupChoiceViewport.offset, limit: groupChoiceViewport.limit }) })] })
6000
+ : _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
6001
+ id: action.actionId,
6002
+ label: action.label,
6003
+ disabled: Boolean(action.disabled),
6004
+ })), active: focusZone === 'actions', focus: pinnedActionFocus }), !readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
6005
+ ? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "section", compact: false, offset: viewport.offset, limit: viewport.limit }) })
6006
+ : null, operationActions.length > 0
6007
+ ? _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? effectiveConfigFocus : -1, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
6008
+ : null] });
5653
6009
  }
5654
6010
  }
5655
6011
  else if (route.name === 'configSave' && plugin) {
@@ -6061,10 +6417,16 @@ export function ConsoleAppV4(props) {
6061
6417
  ? 'SSH' : 'HTTPS';
6062
6418
  const version = `${generation?.active?.catalogVersion ?? '未启用'}${freshness?.candidate?.catalogVersion
6063
6419
  ? `→${freshness.candidate.catalogVersion}` : ''}`;
6420
+ const available = generation?.state === 'ready' || health === 'ready';
6421
+ const unavailable = health === 'unavailable' || health === 'error' || health === 'recovery';
6064
6422
  return {
6065
- label: `${source.id}${source.primary ? ' 主来源' : ''} · ${transport} · ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`}`,
6423
+ label: source.id,
6066
6424
  description: sourceLocationSummary(overview.detail?.type ?? 'git', overview.detail?.locationSummary),
6067
- meta: presentation.label,
6425
+ meta: `当前 ${version} · ${source.count === undefined ? '插件数未知' : `${source.count} 个插件`}`,
6426
+ tags: _jsxs(Box, { children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", source.primary ? '主来源' : transport, "] "] }), _jsx(StatusBadge, { items: [{ key: 'health', label: available ? '可用' : unavailable ? '不可用' : '未确认',
6427
+ symbol: available ? '✓' : unavailable ? '!' : '?', status: available ? 'success' : unavailable ? 'warning' : 'muted' }] }), freshness && freshness.state !== 'current' && freshness.state !== 'fixed'
6428
+ ? _jsxs(Text, { color: tuiTheme.colors.accent, children: [" [", freshness.state === 'updateAvailable' ? '↑ ' : '', presentation.label, "]"] })
6429
+ : null] }),
6068
6430
  status: presentation.status,
6069
6431
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
6070
6432
  };
@@ -6081,12 +6443,17 @@ export function ConsoleAppV4(props) {
6081
6443
  help = focusZone === 'actions'
6082
6444
  ? '←→ 移动固定操作 · Enter 选择 · Tab 返回来源卡片 · Esc 返回'
6083
6445
  : '↑↓ 移动 · Enter 打开 · Tab 固定操作 · r/c 检查全部 · Esc 返回';
6084
- const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(6, 10));
6446
+ const viewport = rowViewport({
6447
+ rowHeights: actions.map((action) => objectCardRowLayout({
6448
+ description: action.description ?? '', meta: action.meta, width: size.columns - 2, hasTags: true,
6449
+ }).height),
6450
+ focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(1),
6451
+ });
6085
6452
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action, index) => ({
6086
6453
  id: action.actionId ?? `${action.label}:${index}`,
6087
6454
  label: action.label,
6088
6455
  disabled: Boolean(action.disabled),
6089
- })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
6456
+ })), active: focusZone === 'actions', focus: pinnedActionFocus, visibleCount: policy.mode === 'compact' ? 2 : 3 }), _jsx(ContentViewport, { children: _jsx(SemanticRows, { actions: actions, focus: focusZone === 'content' ? focus : -1, kind: "section", cards: true, columns: size.columns - 2, compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })] });
6090
6457
  }
6091
6458
  else if (route.name === 'catalogStoreOverview') {
6092
6459
  breadcrumb = '插件来源 / 本地存储';
@@ -6327,7 +6694,6 @@ export function ConsoleAppV4(props) {
6327
6694
  run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
6328
6695
  },
6329
6696
  ];
6330
- const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
6331
6697
  const freshnessLabel = freshness?.state === 'checkFailed' && generationState !== 'ready'
6332
6698
  ? '检查失败' : catalogSourceFreshnessLabel(freshness);
6333
6699
  const sourceStateLabel = generationState === 'ready' ? '来源可用'
@@ -6336,12 +6702,24 @@ export function ConsoleAppV4(props) {
6336
6702
  : generationState === 'configMismatch' ? '来源设置与当前目录不一致'
6337
6703
  : generationState === 'stateInvalid' ? '来源状态需要恢复'
6338
6704
  : '来源尚未启用';
6339
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { title: sourceStateLabel, status: generationState === 'ready' ? 'success'
6340
- : generationState === 'incompatible' ? 'error' : 'warning', description: activeVersion ? `当前版本 ${activeVersion}` : '当前没有可用目录版本', children: [_jsx(StaticRow, { title: "\u5730\u5740", meta: sourceLocationSummary(detail?.type ?? 'git', detail?.locationSummary), compact: true }), _jsx(StaticRow, { title: "\u8FDE\u63A5\u65B9\u5F0F", meta: transport, compact: true }), _jsx(StaticRow, { title: "\u7248\u672C\u7B56\u7565", meta: versionPolicy, compact: true }), _jsx(StaticRow, { title: "\u63D2\u4EF6", meta: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个`, compact: true }), _jsx(StaticRow, { title: "\u6700\u8FD1\u68C0\u67E5", meta: freshness?.checkedAt ?? '本次会话尚未检查', compact: true }), _jsx(StaticRow, { title: "\u5F71\u54CD", meta: generationState === 'ready'
6341
- ? freshness?.state === 'checkFailed' ? '更新检查失败;当前版本继续可用'
6342
- : freshness?.state === 'updateAvailable' ? `可选择更新到 ${candidateVersion ?? '待检查版本'}`
6343
- : '插件浏览和现有使用不受阻止'
6344
- : '当前不能从这个来源浏览或恢复插件', status: generationState === 'ready' ? 'muted' : 'warning', compact: true }), _jsx(StaticRow, { title: "\u66F4\u65B0\u68C0\u67E5", meta: freshnessLabel, status: freshness?.state === 'checkFailed' ? 'warning' : undefined, compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
6705
+ const sourceSummary = [
6706
+ `${transport} · ${sourceLocationSummary(detail?.type ?? 'git', detail?.locationSummary)}`,
6707
+ `当前 ${activeVersion ?? '未启用'}${candidateVersion ? ` → ${candidateVersion}` : ''} · ${sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`}`,
6708
+ `版本策略:${versionPolicy}`,
6709
+ `检查:${freshnessLabel} · ${freshness?.checkedAt ?? '尚未检查'}`,
6710
+ ].join('\n');
6711
+ const sourceCardHeight = objectCardRowLayout({
6712
+ description: sourceSummary, width: size.columns - 2, hasTags: true,
6713
+ }).height;
6714
+ const focusedSourceDescription = actions[focus]?.disabled ?? actions[focus]?.description;
6715
+ const sourceDescriptionLines = focusedSourceDescription
6716
+ ? wrapConsoleText(focusedSourceDescription, size.columns - 2) : [];
6717
+ const sourceActionDescriptionLines = sourceDescriptionLines.length <= pageActionBudget(sourceCardHeight + 1)
6718
+ ? sourceDescriptionLines : [];
6719
+ const detailMenuViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(sourceCardHeight + sourceActionDescriptionLines.length));
6720
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ObjectCardRow, { width: size.columns - 2, title: route.sourceId, description: sourceSummary, tags: _jsxs(Box, { children: [detail?.primary ? _jsx(Text, { color: tuiTheme.colors.accent, children: "[\u4E3B\u6765\u6E90] " }) : null, _jsx(StatusBadge, { items: [{ key: 'source-state', label: sourceStateLabel,
6721
+ symbol: generationState === 'ready' ? '✓' : '!',
6722
+ status: generationState === 'ready' ? 'success' : 'warning' }] })] }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: detailMenuViewport.offset, limit: detailMenuViewport.limit }), sourceActionDescriptionLines.map((line, index) => _jsx(Text, { color: tuiTheme.colors.muted, children: line }, index))] });
6345
6723
  }
6346
6724
  else if (route.name === 'sourceMore') {
6347
6725
  const overview = props.controller.getSourceOverview(route.sourceId);
@@ -6426,17 +6804,19 @@ export function ConsoleAppV4(props) {
6426
6804
  else if (route.name === 'sourceTechnical') {
6427
6805
  const diagnostic = resolveSourceTechnicalDiagnostic(route.sourceId);
6428
6806
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
6429
- context = 'p 上一页 · n 下一页';
6807
+ const lines = diagnosticDetailLines(diagnostic, size.columns - 6);
6808
+ const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - sourceTechnicalPageRows));
6809
+ context = `${offset + 1}–${Math.min(lines.length, offset + sourceTechnicalPageRows)} / ${lines.length} 行`;
6430
6810
  title = '来源技术诊断';
6431
6811
  description = '';
6432
- help = 'p/n 或 PgUp/PgDn 查看全部技术详情 · Enter 选择 · Esc 返回';
6433
- const sourceTechnicalLastOffset = Math.max(0, diagnosticDetailRowCount(diagnostic) - sourceTechnicalPageRows);
6812
+ help = '↑↓ 选择翻页 · Enter 执行 · p/n 翻页 · Esc 返回';
6813
+ const sourceTechnicalLastOffset = Math.max(0, lines.length - sourceTechnicalPageRows);
6434
6814
  actions = [
6435
6815
  { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalPageRows)) },
6436
6816
  { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalPageRows)) },
6437
6817
  { label: '返回来源详情', run: back },
6438
6818
  ];
6439
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: true, compact: policy.mode === 'compact', offset: batchTargetOffset, limit: sourceTechnicalPageRows }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6819
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5B8C\u6574\u5B89\u5168\u5B57\u6BB5", compact: true, children: lines.slice(offset, offset + sourceTechnicalPageRows).map((line, index) => _jsx(Text, { children: line }, offset + index)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6440
6820
  }
6441
6821
  else if (route.name === 'sourceAdd' && sourceDraft?.owner === route.owner) {
6442
6822
  breadcrumb = sourceDraft.mode === 'add' ? '插件来源 / 添加' : `插件来源 / ${sourceDraft.sourceId} / 编辑`;
@@ -6460,7 +6840,7 @@ export function ConsoleAppV4(props) {
6460
6840
  }
6461
6841
  else if (route.step === 'refPolicy') {
6462
6842
  title = '选择版本来源';
6463
- description = 'Release 和 Beta 是固定 Git ref 的显示预设。';
6843
+ description = '选择稳定版、预览版,或指定一个版本。';
6464
6844
  const useRefPreset = async (refPolicy, ref) => {
6465
6845
  const next = { ...sourceDraft, refPolicy, ref };
6466
6846
  setSourceDraft(next);
@@ -6471,17 +6851,17 @@ export function ConsoleAppV4(props) {
6471
6851
  go({ name: 'sourceAdd', step: 'subdir', owner: route.owner });
6472
6852
  };
6473
6853
  actions = [
6474
- { label: '跟随 AW CLI', description: `当前 CLI ${snapshot.cliChannel === 'beta' ? 'beta' : 'release'} → catalog/${snapshot.cliChannel === 'beta' ? 'beta' : 'latest'}`, selected: sourceDraft.refPolicy === 'cliChannel', run: () => { const policy = catalogRefPolicyForPreset('followCli'); return useRefPreset(policy.type, ''); } },
6475
- { label: 'Release', description: '固定读取 catalog/latest', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_LATEST_REF, run: () => { const policy = catalogRefPolicyForPreset('release'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
6476
- { label: 'Beta', description: '固定读取 catalog/beta', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_BETA_REF, run: () => { const policy = catalogRefPolicyForPreset('beta'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
6477
- { label: '指定 Git ref', description: 'branch、tag、完整 ref 或 commit', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref !== CATALOG_LATEST_REF && sourceDraft.ref !== CATALOG_BETA_REF, run: () => {
6854
+ { label: '自动选择', description: `跟随当前 AW 使用${snapshot.cliChannel === 'beta' ? '预览版' : '稳定版'}目录`, selected: sourceDraft.refPolicy === 'cliChannel', run: () => { const policy = catalogRefPolicyForPreset('followCli'); return useRefPreset(policy.type, ''); } },
6855
+ { label: '稳定版', description: '使用稳定发布的插件目录', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_LATEST_REF, run: () => { const policy = catalogRefPolicyForPreset('release'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
6856
+ { label: '预览版', description: '使用预览发布的插件目录', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_BETA_REF, run: () => { const policy = catalogRefPolicyForPreset('beta'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
6857
+ { label: '指定版本', description: '输入分支名、标签或完整提交标识', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref !== CATALOG_LATEST_REF && sourceDraft.ref !== CATALOG_BETA_REF, run: () => {
6478
6858
  if (route.returnToDraft)
6479
6859
  props.controller.replace({ name: 'sourceAdd', step: 'ref', owner: route.owner, returnToDraft: true });
6480
6860
  else
6481
6861
  go({ name: 'sourceAdd', step: 'ref', owner: route.owner });
6482
6862
  } },
6483
6863
  ];
6484
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u8DDF\u968F\u542B\u4E49\uFF1A\u5F53\u524D CLI ", snapshot.cliChannel === 'beta' ? 'beta → catalog/beta' : 'release → catalog/latest'] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
6864
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u81EA\u52A8\u9009\u62E9\uFF1A\u5F53\u524D AW \u4F7F\u7528", snapshot.cliChannel === 'beta' ? '预览版' : '稳定版', "\u76EE\u5F55"] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
6485
6865
  }
6486
6866
  else {
6487
6867
  title = route.step === 'id' ? '输入来源 ID' : route.step === 'location' ? `输入 ${sourceDraft.type === 'git' ? 'Git URL' : '绝对路径'}` : route.step === 'ref' ? '输入固定 Git 引用' : '输入 Catalog 子目录';
@@ -6953,7 +7333,11 @@ export function ConsoleAppV4(props) {
6953
7333
  { label: 'n 下一页', run: () => setScrollOffset((value) => Math.min(lastReportOffset, value + reportLineLimit)) },
6954
7334
  { label: '返回技术详情', run: back },
6955
7335
  ];
6956
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: Math.min(Math.max(0, focus - (policy.mode === 'compact' ? 1 : 2)), Math.max(0, actions.length - (policy.mode === 'compact' ? 2 : 3))), limit: policy.mode === 'compact' ? 2 : 3 })] });
7336
+ const reportFeedbackVisible = Boolean(reportSaveResult || reportSaveError);
7337
+ const compactReportActions = policy.mode === 'compact' || reportFeedbackVisible;
7338
+ const reportActionLimit = policy.mode === 'compact' || reportFeedbackVisible ? 2 : 3;
7339
+ const reportActionOffset = Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - reportActionLimit));
7340
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ContentViewport, { children: _jsx(OperationReportPreviewScreen, { preview: preview, compact: policy.mode === 'compact', offset: scrollOffset, limit: reportLineLimit }) }), reportSaveResult ? _jsx(Callout, { status: "success", title: "\u62A5\u544A\u5DF2\u4FDD\u5B58", description: reportSaveResult, compact: policy.mode === 'compact' }) : null, reportSaveError ? _jsx(Callout, { status: "error", title: "\u62A5\u544A\u672A\u4FDD\u5B58", description: reportSaveError, compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: compactReportActions, offset: reportActionOffset, limit: reportActionLimit })] });
6957
7341
  }
6958
7342
  else {
6959
7343
  actions = [{ label: '返回技术详情', run: back }];
@@ -7011,15 +7395,28 @@ export function ConsoleAppV4(props) {
7011
7395
  }
7012
7396
  else if (route.name === 'review' && snapshot.activeReview) {
7013
7397
  const review = snapshot.activeReview;
7398
+ const technicalDetails = (review.technicalDetails ?? []).flatMap((detail) => review.operationKind === 'catalogGeneration' && detail.startsWith('引用影响:')
7399
+ ? wrapConsoleText(detail, Math.max(20, size.columns - 10))
7400
+ : [detail]);
7401
+ const catalogReferenceDetailOffset = review.operationKind === 'catalogGeneration'
7402
+ ? technicalDetails.findIndex((detail) => detail.startsWith('引用影响:'))
7403
+ : -1;
7404
+ const portableUserConfirmation = review.operationKind === 'portableInstall'
7405
+ && snapshot.portableDraft?.scope === 'user';
7406
+ const requiresIndependentConfirmation = review.requiresConfirmation || portableUserConfirmation;
7014
7407
  const installReviewPluginId = pendingReviewIntent?.kind === 'pluginWrite'
7015
7408
  && pendingReviewIntent.action === 'install'
7016
7409
  ? pendingReviewIntent.pluginId
7017
7410
  : review.pluginAction === 'install' ? snapshot.installPlacementDraft?.pluginId : undefined;
7018
7411
  const batchReview = review.operationKind === 'pluginWriteBatch';
7019
7412
  const taskReview = review.operationKind === 'pluginWriteTask';
7413
+ const exactProjectSyncReview = batchReview && review.pluginAction === 'sync'
7414
+ && review.changes.some((change) => change.group === 'requested'
7415
+ && change.description?.includes('当前项目'));
7416
+ const projectPreApplyConfirmation = review.changes.some((change) => change.kind === 'warning' && change.title.endsWith('当前项目同步目标将运行 preApply'));
7020
7417
  breadcrumb = `${review.subject.name} / ${review.title}`;
7021
7418
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
7022
- ? review.title : `确认${review.title}`} ${review.subject.name}`;
7419
+ ? review.title : `确认${review.title}`}${review.operationKind === 'catalogStorePrune' ? '' : ` ${review.subject.name}`}`;
7023
7420
  description = snapshot.result?.state === 'stale'
7024
7421
  ? '当前状态已变化。请检查新的变化后再次确认。'
7025
7422
  : review.description;
@@ -7032,10 +7429,10 @@ export function ConsoleAppV4(props) {
7032
7429
  help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7033
7430
  }
7034
7431
  actions = [
7035
- { label: review.primaryLabel, description: review.requiresConfirmation
7432
+ { label: review.primaryLabel, description: requiresIndependentConfirmation
7036
7433
  ? '打开独立风险确认;执行时再次检查当前状态'
7037
7434
  : '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
7038
- if (review.requiresConfirmation) {
7435
+ if (requiresIndependentConfirmation) {
7039
7436
  setOperationConfirmation({
7040
7437
  reviewToken: review.reviewToken,
7041
7438
  returnFocus: focus,
@@ -7055,6 +7452,16 @@ export function ConsoleAppV4(props) {
7055
7452
  operationBusy.current = false;
7056
7453
  });
7057
7454
  } },
7455
+ ...(!showTechnical && catalogReferenceDetailOffset >= 0 ? [{
7456
+ label: '查看完整引用影响',
7457
+ description: '逐项查看安装、设置、生命周期和 Marketplace 引用',
7458
+ run: () => {
7459
+ setShowTechnical(true);
7460
+ setBatchTargetOffset(catalogReferenceDetailOffset);
7461
+ setFocus(0);
7462
+ setScrollOffset(0);
7463
+ },
7464
+ }] : []),
7058
7465
  ...(taskReview
7059
7466
  && pendingReviewIntent?.kind === 'pluginWriteTask'
7060
7467
  && pendingReviewIntent.goal === 'update'
@@ -7193,7 +7600,7 @@ export function ConsoleAppV4(props) {
7193
7600
  const headingLines = compressedReview ? 0 : visibleSpecs.filter((group) => group.label).length;
7194
7601
  const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
7195
7602
  const reviewPaginationTotal = showTechnical
7196
- ? Math.max(review.changes.length, review.technicalDetails?.length ?? 0)
7603
+ ? Math.max(review.changes.length, technicalDetails.length)
7197
7604
  : userReviewChanges.length;
7198
7605
  const reviewPageRows = Math.max(1, responsiveBudget(6, 10) - actions.length - 2);
7199
7606
  if (reviewPaginationTotal > reviewPageRows) {
@@ -7238,18 +7645,33 @@ export function ConsoleAppV4(props) {
7238
7645
  return _jsx(ReviewChangeRow, { title: `${category}${reviewSymbol(change)} ${change.title}${description ? ` · ${description}` : ''}`, meta: count, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
7239
7646
  })] }, group.key);
7240
7647
  });
7241
- const batchChangeLimit = policy.mode === 'compact' ? 1 : Math.max(1, responsiveBudget(6, 10) - reviewActionViewport.limit);
7648
+ const portableReview = review.operationKind === 'portableInstall';
7649
+ const batchChangeLimit = portableReview
7650
+ ? responsiveBudget(3, 5)
7651
+ : policy.mode === 'compact' ? 1 : Math.max(1, responsiveBudget(6, 10) - reviewActionViewport.limit);
7242
7652
  const batchChanges = userReviewChanges.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
7243
7653
  const batchHiddenBefore = batchTargetOffset;
7244
- const prioritizedReviewChanges = [
7654
+ const prioritizedReviewChanges = portableReview ? [
7655
+ ...userReviewChanges.filter((change) => change.kind === 'warning'),
7656
+ ...userReviewChanges.filter((change) => change.kind !== 'warning'
7657
+ && change.group === 'portableTarget'),
7658
+ ...userReviewChanges.filter((change) => change.kind !== 'warning'
7659
+ && change.group !== 'portableTarget'),
7660
+ ] : [
7245
7661
  ...userReviewChanges.filter((change) => change.kind === 'warning'),
7246
7662
  ...userReviewChanges.filter((change) => change.kind !== 'warning'),
7247
7663
  ];
7248
7664
  const pagedReviewChanges = prioritizedReviewChanges.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
7249
- const changeGroups = batchReview || userReviewChanges.length > staticRowBudget
7250
- ? _jsxs(Box, { flexDirection: "column", children: [batchHiddenBefore > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${batchHiddenBefore} 项`, status: "muted", compact: true }) : null, (batchReview ? batchChanges : pagedReviewChanges).map((change, index) => _jsx(ReviewChangeRow, { title: `${change.sourceId ? `${change.sourceId} · ` : ''}${change.relation === 'requested' ? '根插件' : change.relation === 'direct' ? '直接依赖' : change.relation === 'transitive' ? '间接依赖' : ''} ${reviewSymbol(change)} ${change.title}`, meta: change.description, status: changeStatus(change), compact: true }, `${change.batchGroupId ?? ''}:${change.pluginId ?? change.title}:${index}`)), Math.max(0, userReviewChanges.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length) > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${Math.max(0, userReviewChanges.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length)} 项`, status: "muted", compact: true }) : null] })
7665
+ const changeGroups = portableReview || batchReview || userReviewChanges.length > staticRowBudget
7666
+ ? _jsxs(Box, { flexDirection: "column", children: [batchHiddenBefore > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${batchHiddenBefore} 项`, status: "muted", compact: true }) : null, (batchReview ? batchChanges : pagedReviewChanges).map((change, index) => _jsx(ReviewChangeRow, { title: `${[
7667
+ portableReview && change.group ? groupTitle[change.group] : undefined,
7668
+ change.sourceId,
7669
+ change.relation === 'requested' ? '根插件'
7670
+ : change.relation === 'direct' ? '直接依赖'
7671
+ : change.relation === 'transitive' ? '间接依赖' : undefined,
7672
+ ].filter(Boolean).join(' · ')}${portableReview && change.group || change.sourceId || change.relation ? ' · ' : ''}${reviewSymbol(change)} ${change.title}`, description: (portableReview && change.kind !== 'warning') || exactProjectSyncReview
7673
+ ? change.description : undefined, meta: portableReview || exactProjectSyncReview ? undefined : change.description, status: changeStatus(change), compact: !portableReview && !exactProjectSyncReview }, `${change.batchGroupId ?? ''}:${change.pluginId ?? change.title}:${index}`)), Math.max(0, userReviewChanges.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length) > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${Math.max(0, userReviewChanges.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length)} 项`, status: "muted", compact: true }) : null] })
7251
7674
  : standardChangeGroups;
7252
- const technicalDetails = review.technicalDetails ?? [];
7253
7675
  const technicalDetailLimit = responsiveBudget(1, 1);
7254
7676
  const visibleTechnicalDetails = technicalDetails.slice(batchTargetOffset, batchTargetOffset + technicalDetailLimit);
7255
7677
  const hiddenTechnicalDetails = Math.max(0, technicalDetails.length - batchTargetOffset - visibleTechnicalDetails.length);
@@ -7291,7 +7713,7 @@ export function ConsoleAppV4(props) {
7291
7713
  && (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
7292
7714
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7293
7715
  }
7294
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || 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] }) });
7716
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || 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: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7295
7717
  }
7296
7718
  else if (route.name === 'progress') {
7297
7719
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -7332,7 +7754,10 @@ export function ConsoleAppV4(props) {
7332
7754
  : catalogSourcesRecovery ? '正在备份损坏配置并恢复官方默认来源。'
7333
7755
  : sourceOperation ? '只原子保存来源配置,不访问网络。'
7334
7756
  : configOperation ? configApply ? '先保存所选层,再重新应用当前可见安装位置。' : '只保存所选设置层。'
7335
- : '请勿关闭当前终端。';
7757
+ : snapshot.activeReview?.operationKind === 'pluginWriteBatch'
7758
+ && snapshot.activeReview.pluginAction === 'sync'
7759
+ ? '只同步确认页中的精确目标;不会自动升级插件来源。'
7760
+ : '请勿关闭当前终端。';
7336
7761
  const batchProgress = snapshot.activeReview?.operationKind === 'pluginWriteBatch' || taskOperation;
7337
7762
  const progress = snapshot.progress;
7338
7763
  const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
@@ -7540,16 +7965,17 @@ export function ConsoleAppV4(props) {
7540
7965
  ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
7541
7966
  : sourceOperation
7542
7967
  ? blocked ? '× 来源操作未完成'
7543
- : operationKind === 'catalogSourcesRecovery' ? '✓ Catalog 来源配置已恢复'
7544
- : resultSummary?.catalogAction === 'update'
7545
- ? resultSummary.catalogGeneration?.activeChanged
7546
- && resultSummary.catalogGeneration.outcome === 'activated'
7547
- ? '✓ Catalog 已更新' : '✓ Catalog 无需更新'
7548
- : resultSummary?.catalogAction === 'repair' ? '✓ Catalog 已修复'
7549
- : resultSummary?.catalogAction === 'replaceDamaged' ? '✓ 损坏的 Catalog 已替换'
7550
- : resultSummary?.catalogAction === 'recoverState' ? '✓ Catalog 状态已恢复'
7551
- : operationKind === 'catalogGeneration' ? '✓ Catalog 已启用'
7552
- : '✓ 来源配置已保存'
7968
+ : partial ? '! 来源操作只完成了一部分'
7969
+ : operationKind === 'catalogSourcesRecovery' ? '✓ Catalog 来源配置已恢复'
7970
+ : resultSummary?.catalogAction === 'update'
7971
+ ? resultSummary.catalogGeneration?.activeChanged
7972
+ && resultSummary.catalogGeneration.outcome === 'activated'
7973
+ ? '✓ Catalog 已更新' : '✓ Catalog 无需更新'
7974
+ : resultSummary?.catalogAction === 'repair' ? '✓ Catalog 已修复'
7975
+ : resultSummary?.catalogAction === 'replaceDamaged' ? '✓ 损坏的 Catalog 已替换'
7976
+ : resultSummary?.catalogAction === 'recoverState' ? '✓ Catalog 状态已恢复'
7977
+ : operationKind === 'catalogGeneration' ? '✓ Catalog 已启用'
7978
+ : '✓ 来源配置已保存'
7553
7979
  : configOperation
7554
7980
  ? blocked
7555
7981
  ? '× 设置未确认保存'
@@ -7598,7 +8024,9 @@ export function ConsoleAppV4(props) {
7598
8024
  ? '来源配置可能已保存。请重新检查来源列表。'
7599
8025
  : '来源操作未执行。草稿已保留,可返回修改。'
7600
8026
  : partial ? resultSummary?.message ?? '来源配置已保存,但 Catalog 尚未启用。可立即重试。'
7601
- : resultSummary?.message ?? '来源配置已保存。'
8027
+ : result.sourceAction === 'remove'
8028
+ ? '来源记录已移除;已安装插件和插件设置保留。'
8029
+ : resultSummary?.message ?? '来源配置已保存。'
7602
8030
  : blocked ? result.message
7603
8031
  : configOperation
7604
8032
  ? partial
@@ -7747,13 +8175,26 @@ export function ConsoleAppV4(props) {
7747
8175
  : portableOperation
7748
8176
  ? blocked || partial ? [
7749
8177
  ...(portableReceipt?.verificationUnknown ? [{
7750
- label: '重新检查结果',
7751
- description: '只读取 Skills CLI 当前状态,不执行安装',
8178
+ label: portableRecheckBusy ? '正在重新检查结果…' : '重新检查结果',
8179
+ description: portableRecheckBusy
8180
+ ? '正在读取 Skills CLI 当前状态;不会执行安装'
8181
+ : '只读取 Skills CLI 当前状态,不执行安装',
8182
+ disabled: portableRecheckBusy ? '只读检查进行中' : undefined,
7752
8183
  status: 'warning',
7753
8184
  run: async () => {
7754
- await props.controller.recheckPortableResult();
7755
- setFocus(0);
7756
- setScrollOffset(0);
8185
+ if (operationBusy.current)
8186
+ return;
8187
+ operationBusy.current = true;
8188
+ setPortableRecheckBusy(true);
8189
+ try {
8190
+ await props.controller.recheckPortableResult();
8191
+ setFocus(0);
8192
+ setScrollOffset(0);
8193
+ }
8194
+ finally {
8195
+ operationBusy.current = false;
8196
+ setPortableRecheckBusy(false);
8197
+ }
7757
8198
  },
7758
8199
  }] : []),
7759
8200
  ...(!portableReceipt?.verificationUnknown
@@ -8058,14 +8499,14 @@ export function ConsoleAppV4(props) {
8058
8499
  ? 3 + (portableReceipt.failureCode ? 1 : 0)
8059
8500
  + (policy.mode === 'compact' ? 0 : resultSummary?.elapsedMs !== undefined ? 1 : 0)
8060
8501
  : 1;
8061
- const portableFixedRows = showTechnical
8502
+ const portableFixedRows = (portableRecheckBusy ? 1 : 0) + (showTechnical
8062
8503
  ? 1 + portableSkillLimit + 1
8063
8504
  + (portableResultAgentMode === 'automatic' ? 1 : 0)
8064
8505
  + (portableReceipt?.recovery ? 1 : 0)
8065
8506
  + 1 + portableTechnicalFieldCount
8066
8507
  : 3 + portableSkillLimit + 1
8067
8508
  + (portableResultAgentMode === 'automatic' ? 1 : 0)
8068
- + (portableReceipt?.recovery ? 1 : 0);
8509
+ + (portableReceipt?.recovery ? 1 : 0));
8069
8510
  currentActionViewportLineBudget = pageActionBudget(portableOperation
8070
8511
  ? portableResultDetailOpen
8071
8512
  ? visiblePortableResultDetailLines.length
@@ -8185,7 +8626,9 @@ export function ConsoleAppV4(props) {
8185
8626
  help = 'p/n 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8186
8627
  }
8187
8628
  content = taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: "\u6240\u9009\u63D2\u4EF6\u7ED3\u679C", children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
8188
- ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableResultDetailOpen ? _jsxs(_Fragment, { children: [safePortableResultDetailOffset > 0
8629
+ ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableRecheckBusy
8630
+ ? _jsx(ProgressRow, { title: "\u6B63\u5728\u91CD\u65B0\u68C0\u67E5\u7ED3\u679C", description: "\u53EA\u8BFB\u53D6 Skills CLI \u5F53\u524D\u72B6\u6001\uFF0C\u4E0D\u6267\u884C\u5B89\u88C5", state: "active", compact: policy.mode === 'compact' })
8631
+ : null, portableResultDetailOpen ? _jsxs(_Fragment, { children: [safePortableResultDetailOffset > 0
8189
8632
  ? _jsx(StaticRow, { title: `↑ 前面还有 ${safePortableResultDetailOffset} 行`, status: "muted", compact: true }) : null, visiblePortableResultDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${safePortableResultDetailOffset + index}:${line}`)), safePortableResultDetailOffset + visiblePortableResultDetailLines.length
8190
8633
  < portableResultDetailLines.length
8191
8634
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${portableResultDetailLines.length
@@ -8376,7 +8819,8 @@ export function ConsoleAppV4(props) {
8376
8819
  }
8377
8820
  if (canOpenHelp && supportsTechnicalDetails) {
8378
8821
  const technicalHelp = route.name === 'progress' ? 'd 技术进度' : 'd 技术详情';
8379
- const remainingHelp = help.replace(' · ? 帮助', '').replace('? 帮助 · ', '');
8822
+ const remainingHelp = help.split(' · ')
8823
+ .filter((item) => item !== '? 帮助' && item !== technicalHelp).join(' · ');
8380
8824
  help = `${technicalHelp} · ? 帮助 · ${remainingHelp}`;
8381
8825
  }
8382
8826
  else if (canOpenHelp && !help.includes('? 帮助')) {
@@ -8384,7 +8828,9 @@ export function ConsoleAppV4(props) {
8384
8828
  }
8385
8829
  if (props.onSessionNavigation && !isCharacterInputMode(route, searchFocus)) {
8386
8830
  help = busy
8387
- ? `h/H 完成后导航 · Esc 撤销等待 · ${help}`
8831
+ ? `${props.pendingSessionNavigation === 'reload' ? '已安排完成后重载'
8832
+ : props.pendingSessionNavigation === 'home' ? '已安排完成后回首页'
8833
+ : 'h/H 完成后导航'} · Esc 撤销等待 · ${help}`
8388
8834
  : `h 首页 · H 重载 · ${help}`;
8389
8835
  }
8390
8836
  const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
@@ -8606,6 +9052,16 @@ export function ConsoleAppV4(props) {
8606
9052
  }
8607
9053
  return;
8608
9054
  }
9055
+ if (portableEmptySelectionOpen) {
9056
+ const closeEmptySelection = () => setPortableEmptySelectionOpen(false);
9057
+ if (key.escape || key.return || input.toLowerCase() === 'b') {
9058
+ closeEmptySelection();
9059
+ }
9060
+ else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
9061
+ requestConfirmedExit(closeEmptySelection);
9062
+ }
9063
+ return;
9064
+ }
8609
9065
  if (projectReminderOpen) {
8610
9066
  const closeProjectReminder = () => {
8611
9067
  setProjectReminderOpen(false);
@@ -8638,7 +9094,7 @@ export function ConsoleAppV4(props) {
8638
9094
  prepareCurrentProjectTargets(reminder.actionableTargets);
8639
9095
  }
8640
9096
  else {
8641
- prepareCurrentProjectPlugins();
9097
+ go({ name: 'projectStatus' });
8642
9098
  }
8643
9099
  }
8644
9100
  return;
@@ -8738,6 +9194,35 @@ export function ConsoleAppV4(props) {
8738
9194
  }
8739
9195
  return;
8740
9196
  }
9197
+ if (pluginListChooser) {
9198
+ const sourceIds = ['all', ...props.controller.listSources().map((source) => source.id)];
9199
+ const itemCount = pluginListChooser.kind === 'source' ? sourceIds.length : PLUGIN_FILTERS.length;
9200
+ if (key.escape) {
9201
+ setPluginListChooser(undefined);
9202
+ }
9203
+ else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9204
+ const direction = key.upArrow || key.leftArrow ? -1 : 1;
9205
+ setPluginListChooser((current) => current ? {
9206
+ ...current,
9207
+ focus: moveIndex(itemCount, current.focus, direction),
9208
+ } : current);
9209
+ }
9210
+ else if (key.return) {
9211
+ const selectedIndex = Math.min(pluginListChooser.focus, Math.max(0, itemCount - 1));
9212
+ if (pluginListChooser.kind === 'source') {
9213
+ const selectedSourceId = sourceIds[selectedIndex];
9214
+ if (selectedSourceId)
9215
+ choosePluginListSource?.(selectedSourceId);
9216
+ }
9217
+ else {
9218
+ const selectedFilter = PLUGIN_FILTERS[selectedIndex];
9219
+ if (selectedFilter)
9220
+ choosePluginListFilter?.(selectedFilter);
9221
+ }
9222
+ setPluginListChooser(undefined);
9223
+ }
9224
+ return;
9225
+ }
8741
9226
  if (overlay) {
8742
9227
  if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
8743
9228
  if (overlay === 'sessionNavigation')
@@ -8867,11 +9352,13 @@ export function ConsoleAppV4(props) {
8867
9352
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
8868
9353
  && (route.name === 'pluginContent'
8869
9354
  || route.name === 'pluginTechnical'
9355
+ || route.name === 'projectStatus' && showTechnical
8870
9356
  || route.name === 'issueDetail' && showTechnical
8871
9357
  || route.name === 'sourceRemoveBlocked' && sourceDependenciesExpanded
8872
9358
  || route.name === 'pluginDependencies' && route.view === 'removeBlocked' && !showTechnical
8873
9359
  || route.name === 'pluginDependencyNode'
8874
9360
  && (route.view === 'references' || route.view === 'details')
9361
+ || route.name === 'pluginDetail'
8875
9362
  || route.name === 'configSave' && route.view === 'changes'
8876
9363
  || route.name === 'sourceSave' && route.view === 'changes'
8877
9364
  || route.name === 'review'
@@ -8903,7 +9390,7 @@ export function ConsoleAppV4(props) {
8903
9390
  }
8904
9391
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
8905
9392
  && route.name === 'sourceTechnical') {
8906
- const total = diagnosticDetailRowCount(resolveSourceTechnicalDiagnostic(route.sourceId));
9393
+ const total = diagnosticDetailLines(resolveSourceTechnicalDiagnostic(route.sourceId), size.columns - 6).length;
8907
9394
  setBatchTargetOffset((value) => Math.min(Math.max(0, total - sourceTechnicalPageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p'
8908
9395
  ? -sourceTechnicalPageRows : sourceTechnicalPageRows))));
8909
9396
  return;
@@ -9035,7 +9522,7 @@ export function ConsoleAppV4(props) {
9035
9522
  }
9036
9523
  else if (route.step === 'ref') {
9037
9524
  if (sourceInput.trim() === '' || sourceInput !== sourceInput.trim() || /[\s\u0000-\u001f\u007f]/u.test(sourceInput))
9038
- error = '必须输入不含空白或控制字符的 Git ref。';
9525
+ error = '请输入分支名、标签或提交标识,不含空白或控制字符。';
9039
9526
  }
9040
9527
  else if (sourceInput !== '') {
9041
9528
  const normalized = path.posix.normalize(sourceInput);
@@ -9156,7 +9643,7 @@ export function ConsoleAppV4(props) {
9156
9643
  : [];
9157
9644
  const definition = activeField.definition.additionalProperties;
9158
9645
  if (!definition) {
9159
- setInputError('additionalProperty · 此对象不允许动态键。');
9646
+ setInputError('这个设置不支持新增项目。');
9160
9647
  return;
9161
9648
  }
9162
9649
  const countIssues = validatePluginConfigValue(activeField.definition, { ...activeObjectDraft, [propertyKey]: definition.type === 'number'
@@ -9266,14 +9753,41 @@ export function ConsoleAppV4(props) {
9266
9753
  } : draft);
9267
9754
  }
9268
9755
  if (route.name === 'batchSelection')
9269
- setFocus(1);
9756
+ setFocus(0);
9270
9757
  setScrollOffset(0);
9271
9758
  return;
9272
9759
  }
9273
- if ((route.name === 'pluginList' || route.name === 'sourceList'
9760
+ if (route.name === 'configFields' && configGroupChooserOpen) {
9761
+ if (key.escape || input.toLowerCase() === 'b') {
9762
+ setConfigGroupChooserOpen(false);
9763
+ setFocusZone('actions');
9764
+ setPinnedActionFocus(1);
9765
+ }
9766
+ else if (key.upArrow || key.downArrow) {
9767
+ setConfigGroupChoiceFocus((value) => moveIndex(visibleConfigTabs.length, value, key.upArrow ? -1 : 1));
9768
+ }
9769
+ else if (key.return) {
9770
+ const selected = visibleConfigTabs[configGroupChoiceFocus];
9771
+ if (selected) {
9772
+ props.controller.updatePageContext({
9773
+ configGroupId: selected.id,
9774
+ focusIndex: 0,
9775
+ scrollOffset: 0,
9776
+ });
9777
+ setConfigGroupChooserOpen(false);
9778
+ setFocusZone('content');
9779
+ setFocus(0);
9780
+ setScrollOffset(0);
9781
+ }
9782
+ }
9783
+ return;
9784
+ }
9785
+ if ((route.name === 'pluginList' || route.name === 'sourceList' || route.name === 'projectStatus'
9274
9786
  || route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9275
9787
  || route.name === 'batchPlacement' && pinnedActions.length > 0
9276
- || route.name === 'portableSkillSelect') && key.tab) {
9788
+ || route.name === 'portableSkillSelect'
9789
+ || route.name === 'batchSelection' && batchDraft?.stage === 'select'
9790
+ || route.name === 'installTargets' && route.view !== 'blocked') && key.tab) {
9277
9791
  setFocusZone((current) => current === 'content' ? 'actions' : 'content');
9278
9792
  return;
9279
9793
  }
@@ -9309,6 +9823,15 @@ export function ConsoleAppV4(props) {
9309
9823
  runAction(pinnedActions[pinnedActionFocus]);
9310
9824
  return;
9311
9825
  }
9826
+ if (route.name === 'projectStatus' && focusZone === 'actions'
9827
+ && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9828
+ setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9829
+ return;
9830
+ }
9831
+ if (route.name === 'projectStatus' && focusZone === 'actions' && key.return) {
9832
+ runAction(pinnedActions[pinnedActionFocus]);
9833
+ return;
9834
+ }
9312
9835
  if (route.name === 'portableSkillSelect' && focusZone === 'actions'
9313
9836
  && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9314
9837
  setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
@@ -9318,6 +9841,19 @@ export function ConsoleAppV4(props) {
9318
9841
  runAction(pinnedActions[pinnedActionFocus]);
9319
9842
  return;
9320
9843
  }
9844
+ if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9845
+ || route.name === 'installTargets' && route.view !== 'blocked')
9846
+ && focusZone === 'actions'
9847
+ && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9848
+ setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9849
+ return;
9850
+ }
9851
+ if ((route.name === 'batchSelection' && batchDraft?.stage === 'select'
9852
+ || route.name === 'installTargets' && route.view !== 'blocked')
9853
+ && focusZone === 'actions' && key.return) {
9854
+ runAction(pinnedActions[pinnedActionFocus]);
9855
+ return;
9856
+ }
9321
9857
  if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9322
9858
  || route.name === 'batchPlacement' && pinnedActions.length > 0)
9323
9859
  && focusZone === 'actions'
@@ -9412,6 +9948,12 @@ export function ConsoleAppV4(props) {
9412
9948
  setFocus(0);
9413
9949
  setScrollOffset(0);
9414
9950
  }
9951
+ else if (globalAction === 'back' && route.name === 'batchSelection'
9952
+ && batchDraft?.stage === 'select' && batchDraft.showSelectionTools) {
9953
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
9954
+ setFocus(0);
9955
+ setScrollOffset(0);
9956
+ }
9415
9957
  else if (globalAction === 'back'
9416
9958
  && (route.name === 'configSave' || route.name === 'sourceSave')
9417
9959
  && route.view === 'changes') {
@@ -9485,12 +10027,15 @@ export function ConsoleAppV4(props) {
9485
10027
  installFilter: nextFilter,
9486
10028
  showSelected: false,
9487
10029
  } : current);
9488
- setFocus(1);
10030
+ setFocus(0);
9489
10031
  setScrollOffset(0);
9490
10032
  }
9491
10033
  else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
9492
10034
  prepareCurrentProjectPlugins();
9493
10035
  }
10036
+ else if (route.name === 'projectStatus' && input.toLowerCase() === 'c') {
10037
+ runAction(pinnedActions.find((action) => action.actionId === 'project-sync-actionable'));
10038
+ }
9494
10039
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
9495
10040
  const candidates = batchCandidates(batchDraft)
9496
10041
  .filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined);
@@ -9504,7 +10049,7 @@ export function ConsoleAppV4(props) {
9504
10049
  }
9505
10050
  else if (route.name === 'installTargets' && route.view !== 'blocked'
9506
10051
  && input.toLowerCase() === 'c') {
9507
- runAction(actions.find((action) => action.actionId === 'confirm-install-platforms'));
10052
+ runAction(pinnedActions.find((action) => action.actionId === 'confirm-install-platforms'));
9508
10053
  }
9509
10054
  else if (route.name === 'batchInstallPlatforms' && input.toLowerCase() === 'c') {
9510
10055
  runAction(actions.find((action) => action.actionId === 'batch-install-platforms-confirm'));
@@ -9517,15 +10062,22 @@ export function ConsoleAppV4(props) {
9517
10062
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
9518
10063
  }
9519
10064
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
9520
- runAction(actions.find((action) => action.actionId === 'batch-selection-continue'));
10065
+ runAction(pinnedActions.find((action) => action.actionId === 'batch-selection-continue'));
9521
10066
  }
9522
- else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === ' ') {
10067
+ else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
10068
+ && focusZone === 'content' && input === ' ') {
9523
10069
  const action = actions[normalizedEffectiveFocus];
9524
10070
  if (action?.pluginId
9525
10071
  && (batchDraft.selectedPluginIds.includes(action.pluginId) || action.canSelectNew)) {
9526
10072
  toggleBatchPlugin(action.pluginId);
9527
10073
  }
9528
10074
  }
10075
+ else if (route.name === 'installTargets' && route.view !== 'blocked'
10076
+ && focusZone === 'content' && input === ' ') {
10077
+ const action = actions[focus];
10078
+ if (action?.actionId?.startsWith('install-platform-'))
10079
+ runAction(action);
10080
+ }
9529
10081
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
9530
10082
  setSearchFocus(true);
9531
10083
  }
@@ -9540,7 +10092,7 @@ export function ConsoleAppV4(props) {
9540
10092
  ...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
9541
10093
  showSelected: false,
9542
10094
  } : draft);
9543
- setFocus(1);
10095
+ setFocus(0);
9544
10096
  setScrollOffset(0);
9545
10097
  }
9546
10098
  else if (route.name === 'configFields' && input.toLowerCase() === 's') {
@@ -9596,7 +10148,9 @@ export function ConsoleAppV4(props) {
9596
10148
  setFocus((value) => {
9597
10149
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9598
10150
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
9599
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1) : moveFocus(actions, current, -1);
10151
+ const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1)
10152
+ : route.name === 'sourceDetail' ? moveIndex(actions.length, current, -1)
10153
+ : moveFocus(actions, current, -1);
9600
10154
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
9601
10155
  dynamicMenuFocus.current = {
9602
10156
  routeKey,
@@ -9605,11 +10159,16 @@ export function ConsoleAppV4(props) {
9605
10159
  };
9606
10160
  }
9607
10161
  const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9608
- ? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
10162
+ ? batchDraft.showSelectionTools
10163
+ ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10164
+ : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
9609
10165
  : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
9610
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), portableVisibleCandidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10166
+ ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
9611
10167
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
9612
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10168
+ : route.name === 'installTargets' && route.view !== 'blocked'
10169
+ ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10170
+ + (policy.mode === 'compact' ? 4 : 0))))
10171
+ : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
9613
10172
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
9614
10173
  const projection = buildUnifiedDependencyGraphProjection({
9615
10174
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -9621,9 +10180,7 @@ export function ConsoleAppV4(props) {
9621
10180
  ...projection.dependents.defaultExpandedPluginIds,
9622
10181
  ]), next, viewport.offset);
9623
10182
  }
9624
- setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
9625
- ? next === 0 ? 0 : viewport.offset + 1
9626
- : viewport.offset);
10183
+ setScrollOffset(viewport.offset);
9627
10184
  return next;
9628
10185
  });
9629
10186
  }
@@ -9634,7 +10191,9 @@ export function ConsoleAppV4(props) {
9634
10191
  setFocus((value) => {
9635
10192
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9636
10193
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
9637
- const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1) : moveFocus(actions, current, 1);
10194
+ const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1)
10195
+ : route.name === 'sourceDetail' ? moveIndex(actions.length, current, 1)
10196
+ : moveFocus(actions, current, 1);
9638
10197
  if (dynamicActionIds && dynamicActionSignature !== undefined) {
9639
10198
  dynamicMenuFocus.current = {
9640
10199
  routeKey,
@@ -9643,11 +10202,16 @@ export function ConsoleAppV4(props) {
9643
10202
  };
9644
10203
  }
9645
10204
  const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9646
- ? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
10205
+ ? batchDraft.showSelectionTools
10206
+ ? actionViewport(actions, policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
10207
+ : objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget(4)))
9647
10208
  : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
9648
- ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), portableVisibleCandidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
10209
+ ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
9649
10210
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
9650
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
10211
+ : route.name === 'installTargets' && route.view !== 'blocked'
10212
+ ? objectCardActionViewport(actions, Math.max(20, size.columns - 2), policy.mode === 'compact', next, scrollOffset, Math.max(1, pageActionBudget((pinnedActions[0]?.disabled ? 2 : 1)
10213
+ + (policy.mode === 'compact' ? 4 : 0))))
10214
+ : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
9651
10215
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
9652
10216
  const projection = buildUnifiedDependencyGraphProjection({
9653
10217
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -9659,9 +10223,7 @@ export function ConsoleAppV4(props) {
9659
10223
  ...projection.dependents.defaultExpandedPluginIds,
9660
10224
  ]), next, viewport.offset);
9661
10225
  }
9662
- setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
9663
- ? next === 0 ? 0 : viewport.offset + 1
9664
- : viewport.offset);
10226
+ setScrollOffset(viewport.offset);
9665
10227
  return next;
9666
10228
  });
9667
10229
  }
@@ -9681,34 +10243,54 @@ export function ConsoleAppV4(props) {
9681
10243
  }
9682
10244
  if (actionError)
9683
10245
  description = actionError;
9684
- const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, help: help, size: size, children: content }));
10246
+ const base = (_jsx(ScreenFrame, { title: projectReminderOpen ? '' : title, description: projectReminderOpen ? undefined : description, breadcrumb: breadcrumb, context: context, help: portableEmptySelectionOpen ? 'Enter 返回选择 · Esc 返回' : help, size: size, children: projectReminderOpen || sourceTransportOverlay ? null : content }));
10247
+ if (portableEmptySelectionOpen) {
10248
+ const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
10249
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 7, marginLeft: Math.max(2, Math.floor((size.columns - modalWidth) / 2)), width: modalWidth, flexDirection: "column", backgroundColor: tuiTheme.colors.panelBackground, children: _jsx(ReviewCard, { title: "\u8FD8\u6CA1\u6709\u9009\u62E9 Skill", description: "\u7528 Space \u52FE\u9009\uFF1B\u4E5F\u53EF\u4EE5\u6253\u5F00\u9009\u62E9\u5DE5\u5177\u3002", children: _jsx(SemanticRows, { actions: [{ label: '返回选择' }], focus: 0, kind: "action", compact: true }) }) })] });
10250
+ }
9685
10251
  if (projectReminderOpen && snapshot.projectIntentReminder) {
9686
10252
  const reminder = snapshot.projectIntentReminder;
9687
10253
  const pluginCount = new Set([
9688
10254
  ...reminder.actionableTargets,
9689
10255
  ...reminder.blockedTargets,
10256
+ ...reminder.unconfirmedTargets,
9690
10257
  ].map((target) => target.pluginId)).size;
9691
10258
  const reminderActions = [
9692
10259
  { label: '稍后处理', description: '首页会保留项目插件入口' },
9693
10260
  {
9694
- label: `查看并同步可处理的 ${reminder.actionableTargets.length} 个`,
9695
- description: '先显示完整操作汇总和脚本影响,再单独确认',
10261
+ label: `查看并同步 ${reminder.actionableTargets.length} 个位置`,
10262
+ description: `先显示完整操作汇总和脚本影响,再单独确认;保留 ${reminder.blockedTargets.length} 个受阻位置和 ${reminder.unconfirmedTargets.length} 个未确认位置`,
9696
10263
  disabled: reminder.actionableTargets.length === 0 ? '当前没有已确认可同步的目标' : undefined,
9697
10264
  },
9698
10265
  { label: '查看全部项目插件', description: '包含需要先处理和无法确认的项目目标' },
9699
10266
  ];
9700
- return _jsx(ScreenFrame, { title: "\u9879\u76EE\u63D2\u4EF6\u6062\u590D\u63D0\u9192", breadcrumb: "\u9996\u9875 / \u9879\u76EE\u63D2\u4EF6", context: "\u53EA\u8BFB\u68C0\u67E5", help: "\u2191\u2193 \u79FB\u52A8 \u00B7 Enter \u9009\u62E9 \u00B7 Esc \u7A0D\u540E\u5904\u7406", size: size, children: _jsxs(ReviewCard, { title: `本项目有 ${pluginCount} 个插件尚未就绪`, description: `${reminder.actionableTargets.length} 个可同步;`
9701
- + `${reminder.blockedTargets.length} 个需要先处理。`, children: [_jsx(StaticRow, { title: "\u6309\u9879\u76EE\u5171\u4EAB\u8BBE\u7F6E\u6062\u590D\uFF0C\u4E2A\u4EBA\u8BBE\u7F6E\u4E0D\u4F1A\u88AB\u6E05\u7A7A\u3002", compact: true }), _jsx(StaticRow, { title: "\u786E\u8BA4\u540C\u6B65\u524D\u53EF\u67E5\u770B\u811A\u672C\u548C\u5B8C\u6574\u5F71\u54CD\u3002", status: "warning", compact: true }), _jsx(SemanticRows, { actions: reminderActions, focus: overlayFocus, kind: "action", compact: true })] }) });
10267
+ const reminderWidth = Math.max(40, size.columns - (policy.mode === 'compact' ? 8 : 16));
10268
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: policy.mode === 'compact' ? 2 : 5, marginLeft: Math.max(2, Math.floor((size.columns - reminderWidth) / 2)), width: reminderWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: `本项目有 ${pluginCount} 个插件尚未就绪`, description: `${reminder.actionableTargets.length} 个位置可同步;`
10269
+ + `${reminder.blockedTargets.length} 个位置需要先处理;`
10270
+ + `${reminder.unconfirmedTargets.length} 个位置未确认。`, children: [_jsx(StaticRow, { title: "\u6309\u9879\u76EE\u5171\u4EAB\u8BBE\u7F6E\u6062\u590D\uFF0C\u4E2A\u4EBA\u8BBE\u7F6E\u4E0D\u4F1A\u88AB\u6E05\u7A7A\u3002", compact: true }), _jsx(StaticRow, { title: "\u786E\u8BA4\u540C\u6B65\u524D\u53EF\u67E5\u770B\u811A\u672C\u548C\u5B8C\u6574\u5F71\u54CD\u3002", status: "warning", compact: true }), _jsx(SemanticRows, { actions: reminderActions, focus: overlayFocus, kind: "action", compact: true }), _jsx(StaticRow, { title: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u7A0D\u540E\u5904\u7406", status: "muted", compact: true })] }) })] });
9702
10271
  }
9703
10272
  if (operationConfirmation && snapshot.activeReview) {
9704
10273
  const review = snapshot.activeReview;
9705
- const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
9706
- const modalWarningLines = operationRiskLines(review, Math.max(12, modalWidth - 6));
10274
+ const portableUserConfirmation = review.operationKind === 'portableInstall'
10275
+ && snapshot.portableDraft?.scope === 'user';
10276
+ const confirmationRiskLines = (columns) => portableUserConfirmation
10277
+ ? [
10278
+ '! 本次在所有项目范围安装选定 Skills。',
10279
+ '具体对象和范围以刚才的汇总为准。',
10280
+ ].flatMap((line) => wrapConsoleText(line, Math.max(1, columns - 2)))
10281
+ : operationRiskLines(review, columns);
10282
+ const modalWidth = portableUserConfirmation
10283
+ ? Math.max(40, size.columns - (policy.mode === 'compact' ? 8 : 16))
10284
+ : Math.max(40, Math.floor(size.columns * 0.6));
10285
+ const modalMarginLeft = portableUserConfirmation
10286
+ ? Math.max(2, Math.floor((size.columns - modalWidth) / 2))
10287
+ : Math.max(2, Math.floor(size.columns / 5));
10288
+ const modalWarningLines = confirmationRiskLines(Math.max(12, modalWidth - 6));
9707
10289
  const shortWarningLimit = policy.mode === 'compact' ? 4 : 6;
9708
10290
  const shortConfirmation = modalWarningLines.length <= shortWarningLimit;
9709
10291
  const warningLines = shortConfirmation
9710
10292
  ? modalWarningLines
9711
- : operationRiskLines(review, Math.max(12, size.columns - 8));
10293
+ : confirmationRiskLines(Math.max(12, size.columns - 8));
9712
10294
  const warningLimit = Math.max(1, policy.mode === 'compact' ? 1 : 7);
9713
10295
  const warningOffset = Math.min(operationConfirmation.warningOffset, Math.max(0, warningLines.length - 1));
9714
10296
  const visibleWarnings = shortConfirmation
@@ -9725,7 +10307,10 @@ export function ConsoleAppV4(props) {
9725
10307
  }
9726
10308
  const confirmationActions = [
9727
10309
  { label: '返回检查', description: '回到完整操作汇总' },
9728
- { label: `我已了解影响,${review.primaryLabel}`, status: 'error' },
10310
+ {
10311
+ label: portableUserConfirmation ? '确认并执行' : `我已了解影响,${review.primaryLabel}`,
10312
+ status: 'error',
10313
+ },
9729
10314
  ...(warningPaginated ? paginationActions({
9730
10315
  offset: warningOffset,
9731
10316
  total: warningLines.length,
@@ -9733,14 +10318,16 @@ export function ConsoleAppV4(props) {
9733
10318
  move: moveWarningPage,
9734
10319
  }) : []),
9735
10320
  ];
9736
- const confirmationCard = (_jsxs(ReviewCard, { title: "\u8BF7\u786E\u8BA4\u672C\u6B21\u98CE\u9669", description: "\u786E\u8BA4\u53EA\u5BF9\u5F53\u524D\u64CD\u4F5C\u6C47\u603B\u6709\u6548\uFF1B\u5185\u5BB9\u53D8\u5316\u540E\u5FC5\u987B\u91CD\u65B0\u68C0\u67E5\u3002", children: [!shortConfirmation && warningOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${warningOffset} 行`, status: "muted", compact: true }) : null, visibleWarnings.length > 0
10321
+ const confirmationCard = (_jsxs(ReviewCard, { title: portableUserConfirmation ? '确认本次影响' : '请确认本次风险', description: "\u786E\u8BA4\u53EA\u5BF9\u5F53\u524D\u64CD\u4F5C\u6C47\u603B\u6709\u6548\uFF1B\u5185\u5BB9\u53D8\u5316\u540E\u5FC5\u987B\u91CD\u65B0\u68C0\u67E5\u3002", children: [!shortConfirmation && warningOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${warningOffset} 行`, status: "muted", compact: true }) : null, visibleWarnings.length > 0
9737
10322
  ? visibleWarnings.map((warning, index) => _jsx(StaticRow, { title: warning, status: warning.startsWith('! ') ? 'warning' : 'info', compact: true }, `${warningOffset + index}:${warning}`))
9738
10323
  : _jsx(StaticRow, { title: "! \u6B64\u64CD\u4F5C\u9700\u8981\u660E\u786E\u6388\u6743", status: "warning", compact: true }), !shortConfirmation && warningLines.length - warningOffset - visibleWarnings.length > 0
9739
10324
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${warningLines.length - warningOffset - visibleWarnings.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: confirmationActions, focus: overlayFocus, kind: "action", compact: true })] }));
9740
10325
  if (shortConfirmation) {
9741
- return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor(size.columns / 5)), width: modalWidth, flexDirection: "column", children: confirmationCard })] });
10326
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: modalMarginLeft, width: modalWidth, flexDirection: "column", children: confirmationCard })] });
9742
10327
  }
9743
- return _jsx(ScreenFrame, { title: `确认${review.title} ${review.subject.name}?`, breadcrumb: `${review.subject.name} / 风险确认`, context: "\u72EC\u7ACB\u6388\u6743", help: "PgUp/PgDn \u67E5\u770B\u5168\u90E8\u8B66\u544A \u00B7 \u2191\u2193 \u79FB\u52A8 \u00B7 Enter \u9009\u62E9 \u00B7 Esc \u8FD4\u56DE", size: size, children: confirmationCard });
10328
+ return _jsx(ScreenFrame, { title: review.operationKind === 'catalogStorePrune'
10329
+ ? `确认${review.title}?`
10330
+ : `确认${review.title} ${review.subject.name}?`, breadcrumb: `${review.subject.name} / 风险确认`, context: "\u72EC\u7ACB\u6388\u6743", help: "PgUp/PgDn \u67E5\u770B\u5168\u90E8\u8B66\u544A \u00B7 \u2191\u2193 \u79FB\u52A8 \u00B7 Enter \u9009\u62E9 \u00B7 Esc \u8FD4\u56DE", size: size, children: confirmationCard });
9744
10331
  }
9745
10332
  if (sourceTransportOverlay) {
9746
10333
  const configuredSource = route.name === 'sourceEdit'
@@ -9756,7 +10343,34 @@ export function ConsoleAppV4(props) {
9756
10343
  { label: 'HTTPS', description: '默认方式;无需预先配置 SSH 密钥', selected: currentTransport === 'HTTPS' },
9757
10344
  { label: 'SSH', description: '适合已配置 GitHub SSH 访问的用户', selected: currentTransport === 'SSH' },
9758
10345
  ];
9759
- return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: 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: _jsx(ReviewCard, { title: "\u9009\u62E9\u4E0B\u8F7D\u65B9\u5F0F", description: `当前草稿:${currentTransport ?? '未识别'}。选择只更新当前草稿;保存前仍会显示统一汇总。`, children: _jsx(SemanticRows, { actions: transportActions, focus: overlayFocus, kind: "choice", compact: false }) }) })] });
10346
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 4, marginLeft: 2, width: size.columns - 4, flexDirection: "column", children: _jsxs(ReviewCard, { title: "\u9009\u62E9\u4E0B\u8F7D\u65B9\u5F0F", description: `当前草稿:${currentTransport ?? '未识别'};仅修改草稿,保存前可查看汇总。`, children: [_jsx(SemanticRows, { actions: transportActions, focus: overlayFocus, kind: "choice", compact: false }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" })] }) })] });
10347
+ }
10348
+ if (pluginListChooser && route.name === 'pluginList') {
10349
+ const sourceId = route.sourceId ?? 'all';
10350
+ const chooserActions = pluginListChooser.kind === 'source'
10351
+ ? [
10352
+ { label: '全部来源', selected: sourceId === 'all' },
10353
+ ...props.controller.listSources().map((source) => ({
10354
+ label: source.label,
10355
+ meta: source.id === source.label ? undefined : source.id,
10356
+ selected: source.id === sourceId,
10357
+ })),
10358
+ ]
10359
+ : PLUGIN_FILTERS.map((filter) => ({
10360
+ label: pluginFilterLabel(filter),
10361
+ selected: filter === installFilter,
10362
+ }));
10363
+ const chooserFocus = Math.min(pluginListChooser.focus, Math.max(0, chooserActions.length - 1));
10364
+ const chooserViewport = rowViewport({
10365
+ rowHeights: chooserActions.map(() => 1),
10366
+ focus: chooserFocus,
10367
+ requestedOffset: 0,
10368
+ rowBudget: responsiveBudget(6, 9),
10369
+ });
10370
+ const chooserWidth = Math.max(40, Math.floor(size.columns * 0.8));
10371
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: policy.mode === 'compact' ? 2 : 5, marginLeft: Math.max(2, Math.floor((size.columns - chooserWidth) / 2)), width: chooserWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: pluginListChooser.kind === 'source' ? '选择插件来源' : '筛选插件', description: pluginListChooser.kind === 'status' ? '只改变列表,不改变插件详情。' : undefined, children: [_jsx(SemanticRows, { actions: chooserActions, focus: chooserFocus, kind: "choice", compact: true, offset: chooserViewport.offset, limit: chooserViewport.limit }), chooserActions.length > chooserViewport.limit
10372
+ ? _jsx(StaticRow, { title: `${chooserViewport.position} / ${chooserViewport.total}`, status: "muted", compact: true })
10373
+ : null, _jsx(StaticRow, { title: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88", status: "muted", compact: true })] }) })] });
9760
10374
  }
9761
10375
  if (!overlay)
9762
10376
  return base;
@@ -9773,7 +10387,7 @@ export function ConsoleAppV4(props) {
9773
10387
  { label: overlay === 'discard' || overlay === 'sessionNavigation' ? '继续编辑' : '继续使用' },
9774
10388
  { label: overlay === 'discard' || overlay === 'sessionNavigation' ? '放弃修改' : '退出', status: 'error' },
9775
10389
  ];
9776
- 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' ? '当前页快捷键'
10390
+ return (_jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: overlay === 'help' ? null : base }), _jsx(Box, { position: "absolute", marginTop: overlay === 'help' ? 0 : 5, marginLeft: overlay === 'help' ? 1 : Math.max(2, Math.floor(size.columns / 5)), width: overlay === 'help' ? size.columns - 2 : Math.max(40, Math.floor(size.columns * 0.6)), flexDirection: "column", children: _jsxs(ReviewCard, { title: overlay === 'help' ? '当前页快捷键'
9777
10391
  : overlay === 'discard' || overlay === 'sessionNavigation' ? '放弃未保存修改?'
9778
10392
  : '退出控制台?', children: [overlay === 'help'
9779
10393
  ? _jsx(_Fragment, { children: helpRows.map(([key, value]) => _jsx(StaticRow, { title: `${key} ${value}`, compact: true }, key)) })