@lesliechan721/aw 0.1.0-beta.18 → 0.1.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/src/commands/operation-planner.d.ts +3 -3
  2. package/dist/src/commands/status-v6.d.ts +81 -31
  3. package/dist/src/commands/status-v6.js +494 -183
  4. package/dist/src/commands/status-v6.js.map +1 -1
  5. package/dist/src/commands/wizard.d.ts +2 -2
  6. package/dist/src/index.js +2 -2
  7. package/dist/src/tui/catalog-sources/projection.d.ts +2 -2
  8. package/dist/src/tui/catalog-sources/screens.js +17 -10
  9. package/dist/src/tui/catalog-sources/screens.js.map +1 -1
  10. package/dist/src/tui/console-app-v4.js +1291 -435
  11. package/dist/src/tui/console-app-v4.js.map +1 -1
  12. package/dist/src/tui/controller-runtime.d.ts +3 -3
  13. package/dist/src/tui/controller-runtime.js +85 -27
  14. package/dist/src/tui/controller-runtime.js.map +1 -1
  15. package/dist/src/tui/controller.d.ts +17 -7
  16. package/dist/src/tui/controller.js +265 -24
  17. package/dist/src/tui/controller.js.map +1 -1
  18. package/dist/src/tui/dependency-graph.d.ts +3 -0
  19. package/dist/src/tui/dependency-graph.js +37 -1
  20. package/dist/src/tui/dependency-graph.js.map +1 -1
  21. package/dist/src/tui/diagnostic-projection.d.ts +1 -0
  22. package/dist/src/tui/diagnostic-projection.js +51 -4
  23. package/dist/src/tui/diagnostic-projection.js.map +1 -1
  24. package/dist/src/tui/model.d.ts +22 -12
  25. package/dist/src/tui/model.js +251 -86
  26. package/dist/src/tui/model.js.map +1 -1
  27. package/dist/src/tui/operation-history.d.ts +1 -0
  28. package/dist/src/tui/operation-history.js +5 -0
  29. package/dist/src/tui/operation-history.js.map +1 -1
  30. package/dist/src/tui/operation-review-planner.js +67 -2
  31. package/dist/src/tui/operation-review-planner.js.map +1 -1
  32. package/dist/src/tui/plugin-write-task.d.ts +2 -0
  33. package/dist/src/tui/plugin-write-task.js.map +1 -1
  34. package/dist/src/tui/router.d.ts +6 -0
  35. package/dist/src/tui/router.js +56 -12
  36. package/dist/src/tui/router.js.map +1 -1
  37. package/dist/src/tui/screen-registry.d.ts +7 -0
  38. package/dist/src/utils/plugin-config-write.js +3 -3
  39. package/dist/src/utils/plugin-config-write.js.map +1 -1
  40. package/dist/src/utils/target-eligibility.d.ts +2 -2
  41. package/dist/src/utils/target-eligibility.js.map +1 -1
  42. package/package.json +3 -3
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import path from 'node:path';
5
+ import stringWidth from 'string-width';
5
6
  import { validatePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
6
7
  import { CATALOG_BETA_REF, CATALOG_LATEST_REF, OFFICIAL_CATALOG_SOURCE_SSH_URL, OFFICIAL_CATALOG_SOURCE_URL, } from '../constants.js';
7
8
  import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
@@ -40,18 +41,6 @@ function sourceOperationBlockLabel(reason) {
40
41
  case 'writeProgress': return '正在执行的写入操作';
41
42
  }
42
43
  }
43
- function catalogGenerationStateLabel(state) {
44
- switch (state) {
45
- case 'ready': return '可用';
46
- case 'activationRequired': return '需要启用';
47
- case 'repairRequired': return '需要修复';
48
- case 'configMismatch': return '来源设置已变化';
49
- case 'stateInvalid': return '版本状态需要恢复';
50
- case 'incompatible': return '当前 AW 不兼容';
51
- case 'unavailable': return '不可用';
52
- case undefined: return '尚未取得状态';
53
- }
54
- }
55
44
  function productResultMessage(message) {
56
45
  if (message === '后续:当前项目 sync')
57
46
  return '后续:同步当前项目位置';
@@ -68,6 +57,36 @@ function pluginMenuActionId(action) {
68
57
  ? `${action.id}:${action.pluginId}:${action.platform}:${action.scope}`
69
58
  : action.id;
70
59
  }
60
+ function batchTargetKey(target) {
61
+ return [target.pluginId, target.platform, target.scope].join('\0');
62
+ }
63
+ function batchPluginVersionSummary(plugin) {
64
+ const updates = plugin.update.targets.filter((target) => target.availableVersion !== undefined);
65
+ if (updates.length === 0)
66
+ return plugin.version ? `版本 ${plugin.version}` : '版本待确认';
67
+ const changes = [...new Set(updates.map((target) => `${target.installedVersion ?? '待确认'} → ${target.availableVersion}`))];
68
+ return changes.length === 1
69
+ ? changes[0]
70
+ : `${updates.length} 个位置版本不同(Enter 查看)`;
71
+ }
72
+ function pluginPlacementConclusion(placement) {
73
+ if (placement.state === 'notInstalled')
74
+ return { text: '未添加', status: 'muted' };
75
+ if (placement.state === 'blocked')
76
+ return { text: '无法添加:输出位置有冲突', status: 'error' };
77
+ if (placement.state === 'scopeConflict')
78
+ return { text: '使用位置冲突,需要选择一个位置', status: 'warning' };
79
+ const location = placement.desiredScopes?.map(scopeLabel).join('、') || scopeLabel(placement.scope);
80
+ if (placement.blockingReason === 'artifactCollision' || placement.actualState === 'blocked') {
81
+ return { text: `${location}暂时不可用`, status: 'error' };
82
+ }
83
+ if (placement.actualState === 'needsSync' || placement.health === 'incomplete') {
84
+ return { text: `${location}需要同步`, status: 'warning' };
85
+ }
86
+ if (placement.health === 'error')
87
+ return { text: `${location}状态未知`, status: 'warning' };
88
+ return { text: `${location}可用`, status: 'success' };
89
+ }
71
90
  function isCharacterInputMode(route, searchMode) {
72
91
  if (routeDefinition(route).keymap !== 'menuOrTextInput')
73
92
  return false;
@@ -246,12 +265,26 @@ function pluginPlacementSummary(plugin) {
246
265
  : placement.actualState === 'blocked' ? '已阻断'
247
266
  : placement.health === 'ready' ? '可用'
248
267
  : placement.health === 'incomplete' ? '需要处理' : '不可用';
249
- const desired = placement.desiredScopes?.length
250
- ? placement.desiredScopes.map(scopeLabel).join('+')
251
- : scopeLabel(placement.scope);
252
- return `${label} · ${desired} · ${health}`;
268
+ const desired = placement.desiredScopes?.map(scopeLabel).join('+') || '未保存';
269
+ const actual = placement.actualScopes?.map(scopeLabel).join('+') || '未启用';
270
+ return placement.desiredScopes?.length
271
+ ? `${label} · 已保存 ${desired} · 当前 ${actual} · ${health}`
272
+ : `${label} · 当前 ${actual} · ${health}`;
253
273
  }).join(' | ') || '没有可用平台';
254
274
  }
275
+ function issueMatchesTarget(issue, pluginId, platform, scope) {
276
+ if (!platform && !scope && issue.pluginId === pluginId)
277
+ return true;
278
+ const matches = [
279
+ issue.rootEntity,
280
+ ...(issue.affectedTargets ?? []),
281
+ ...(issue.pluginId && issue.platform && issue.scope
282
+ ? [{ pluginId: issue.pluginId, platform: issue.platform, scope: issue.scope }]
283
+ : []),
284
+ ].filter((target) => target !== undefined && target.pluginId === pluginId);
285
+ return matches.some((target) => (!platform || target.platform === platform)
286
+ && (!scope || target.scope === scope));
287
+ }
255
288
  function issueGroupLabel(group) {
256
289
  return {
257
290
  sourceRemoved: '来源已移除',
@@ -290,6 +323,57 @@ function moveIndex(count, current, direction) {
290
323
  return 0;
291
324
  return (current + direction + count) % count;
292
325
  }
326
+ function dedupeActions(actions) {
327
+ const seen = new Set();
328
+ return actions.filter((action) => {
329
+ const id = action.actionId ?? action.label;
330
+ if (seen.has(id))
331
+ return false;
332
+ seen.add(id);
333
+ return true;
334
+ });
335
+ }
336
+ function wrapConsoleText(value, columns) {
337
+ const width = Math.max(1, columns);
338
+ const graphemes = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(value)]
339
+ .map((part) => part.segment);
340
+ const lines = [];
341
+ let line = '';
342
+ let lineWidth = 0;
343
+ for (const grapheme of graphemes) {
344
+ const graphemeWidth = stringWidth(grapheme);
345
+ if (line && lineWidth + graphemeWidth > width) {
346
+ lines.push(line);
347
+ line = '';
348
+ lineWidth = 0;
349
+ }
350
+ line += grapheme;
351
+ lineWidth += graphemeWidth;
352
+ }
353
+ if (line || lines.length === 0)
354
+ lines.push(line);
355
+ return lines;
356
+ }
357
+ function operationRiskWarnings(review) {
358
+ return [...new Set([
359
+ ...(review.sharedImpact.warning ? [review.sharedImpact.warning] : []),
360
+ ...review.changes.filter((change) => change.kind === 'warning' || change.kind === 'remove')
361
+ .map((change) => `${change.title}${change.description ? `:${change.description}` : ''}`),
362
+ ...review.changes.filter((change) => change.group === 'preserved')
363
+ .map((change) => `保留:${change.title}${change.description ? `:${change.description}` : ''}`),
364
+ ])];
365
+ }
366
+ function operationRiskLines(review, columns) {
367
+ const summaries = [
368
+ `操作:${review.title} · ${review.subject.name}`,
369
+ ...(review.confirmationDetails ?? []),
370
+ ...operationRiskWarnings(review).map((warning) => `! ${warning}`),
371
+ ];
372
+ return summaries.flatMap((summary) => {
373
+ const lines = wrapConsoleText(summary, Math.max(1, columns - 2));
374
+ return lines.map((line, index) => index === 0 ? line : ` ${line}`);
375
+ });
376
+ }
293
377
  function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
294
378
  return rowViewport({
295
379
  rowHeights: actions.map((action) => compact || (!action.description && !action.disabled) ? 1 : 2),
@@ -734,8 +818,15 @@ function placementStatus(placement) {
734
818
  if (placement.blockingReason === 'artifactCollision') {
735
819
  return { value: '输出冲突', status: 'error', description: '只允许查看或安全移除受管内容' };
736
820
  }
821
+ const current = placement.actualScopes?.map(scopeLabel).join('+') || '未启用';
822
+ const state = placement.actualState === 'needsSync' ? '需要同步'
823
+ : placement.actualState === 'blocked' ? '已阻断'
824
+ : placement.health === 'ready' ? '可用'
825
+ : placement.health === 'incomplete' ? '需要处理' : '不可用';
737
826
  return {
738
- value: `${placement.desiredScopes?.map(scopeLabel).join('+') || scopeLabel(placement.scope)} · ${placement.actualState === 'needsSync' ? '需要同步' : placement.actualState === 'blocked' ? '已阻断' : placement.health === 'ready' ? '可用' : placement.health === 'incomplete' ? '需要处理' : '不可用'}`,
827
+ value: placement.desiredScopes?.length
828
+ ? `已保存 ${placement.desiredScopes.map(scopeLabel).join('+')} · 当前 ${current} · ${state}`
829
+ : `当前 ${current} · ${state}`,
739
830
  status: placement.actualState === 'blocked' ? 'error' : placement.health === 'ready' ? 'success' : placement.health === 'incomplete' ? 'warning' : 'error',
740
831
  description: placement.actualState === 'needsSync' ? '已保存的使用设置与当前安装不同;可安全同步'
741
832
  : placement.actualState === 'blocked' ? '当前有安全阻断;查看恢复路径'
@@ -761,11 +852,8 @@ function dependencyPlacementLabel(item) {
761
852
  if (entry.state !== 'installed')
762
853
  return '';
763
854
  const desired = entry.desiredScopes?.map(scopeLabel).join('+') || scopeLabel(entry.scope);
764
- const actualScopes = [
765
- ...(entry.activation?.user ? ['所有项目'] : []),
766
- ...(entry.activation?.project ? ['当前项目'] : []),
767
- ];
768
- const actual = actualScopes.join('+') || scopeLabel(entry.scope);
855
+ const actualScopes = entry.actualScopes?.map(scopeLabel) ?? [];
856
+ const actual = actualScopes.join('+') || '未启用';
769
857
  const state = entry.actualState === 'needsSync' ? '需同步'
770
858
  : entry.actualState === 'blocked' ? '已阻断'
771
859
  : entry.actualState === 'ready' ? '可用' : aggregate.value;
@@ -792,6 +880,12 @@ function dependencyRelationDescription(item) {
792
880
  const requiredBy = item.requiredBy.map((entry) => entry.displayName).join('、');
793
881
  return `${item.relation === 'direct' ? '直接依赖' : '间接依赖'}${requiredBy ? ` · ${requiredBy} 需要它` : ''}`;
794
882
  }
883
+ function dependencyReferenceUsageLabel(reference, consumerName) {
884
+ const location = reference.consumer.scope === 'user'
885
+ ? '所有项目'
886
+ : reference.consumer.project === 'current' ? '当前项目' : '其他项目';
887
+ return `${consumerName}在${location}的${platformLabel(reference.consumer.platform)}中使用它`;
888
+ }
795
889
  function dependencySummaryText(summary) {
796
890
  return `${summary.directCount} 个直接 · ${summary.totalCount} 个总计`;
797
891
  }
@@ -836,6 +930,11 @@ function operationTargetStateLabel(state) {
836
930
  function reviewSymbol(change) {
837
931
  return { add: '+', change: '~', remove: '−', keep: '✓', warning: '!' }[change.kind];
838
932
  }
933
+ function isTechnicalReviewGroup(group) {
934
+ return group === 'projectIntent' || group === 'userIntent' || group === 'sharedCodexNative'
935
+ || group === 'userActivation' || group === 'projectActivation'
936
+ || group === 'awExtensions' || group === 'runtime';
937
+ }
839
938
  function currentPlugin(controller, route) {
840
939
  return 'pluginId' in route ? controller.getPluginOverview(route.pluginId) : undefined;
841
940
  }
@@ -862,6 +961,8 @@ export function ConsoleAppV4(props) {
862
961
  const [snapshot, setSnapshot] = useState(props.controller.snapshot);
863
962
  const [overlay, setOverlay] = useState();
864
963
  const [overlayFocus, setOverlayFocus] = useState(0);
964
+ const [operationConfirmation, setOperationConfirmation] = useState();
965
+ const [sourceTransportOverlay, setSourceTransportOverlay] = useState();
865
966
  const [discardForExit, setDiscardForExit] = useState(false);
866
967
  const [discardForSessionNavigation, setDiscardForSessionNavigation] = useState();
867
968
  const [discardDraftConfirmation, setDiscardDraftConfirmation] = useState();
@@ -884,6 +985,7 @@ export function ConsoleAppV4(props) {
884
985
  const [configLayerPages, setConfigLayerPages] = useState({});
885
986
  const [configSaveImpact, setConfigSaveImpact] = useState();
886
987
  const [configSaveImpactError, setConfigSaveImpactError] = useState(false);
988
+ const [configSaveImpactAttempt, setConfigSaveImpactAttempt] = useState(0);
887
989
  const [draftUpdates, setDraftUpdates] = useState({});
888
990
  const [draftRemoves, setDraftRemoves] = useState([]);
889
991
  const [objectDrafts, setObjectDrafts] = useState({});
@@ -929,7 +1031,6 @@ export function ConsoleAppV4(props) {
929
1031
  const [draftOwner, setDraftOwner] = useState();
930
1032
  const [pendingPresetId, setPendingPresetId] = useState();
931
1033
  const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
932
- const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
933
1034
  const dynamicMenuFocus = useRef(undefined);
934
1035
  const dependencyGraphContexts = useRef(new Map());
935
1036
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0, catalog: 0 });
@@ -1429,25 +1530,15 @@ export function ConsoleAppV4(props) {
1429
1530
  count = snapshot.operationHistorySummaries.length + 1;
1430
1531
  }
1431
1532
  else if (route.name === 'pluginList') {
1432
- const itemCount = props.controller.listPlugins({
1433
- mode: route.mode,
1434
- ...(route.sourceId ? { sourceId: route.sourceId } : {}),
1435
- ...(query ? { query } : {}),
1436
- installFilter,
1437
- }).length;
1438
- const listed = new Set(props.controller.listPlugins({
1533
+ const items = props.controller.listPlugins({
1439
1534
  mode: route.mode,
1440
1535
  ...(route.sourceId ? { sourceId: route.sourceId } : {}),
1441
1536
  ...(query ? { query } : {}),
1442
1537
  installFilter,
1443
- }).map((item) => item.pluginId));
1444
- const menuCount = itemCount > 0 && !query ? 1
1445
- + Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
1446
- .some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
1447
- + Number(route.mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
1448
- + Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
1449
- .some((candidate) => candidate.target.scope === 'project')) : 0;
1450
- count = itemCount + menuCount || 1;
1538
+ });
1539
+ const sourceUnavailable = route.sourceId !== undefined
1540
+ && props.controller.getSourceOverview(route.sourceId).health?.state === 'unavailable';
1541
+ count = pluginListRowIds(route.mode, items, query, sourceUnavailable).length || 1;
1451
1542
  }
1452
1543
  if (count === undefined)
1453
1544
  return;
@@ -1463,6 +1554,61 @@ export function ConsoleAppV4(props) {
1463
1554
  route.name === 'pluginList' ? route.sourceId : '',
1464
1555
  snapshot.viewModel,
1465
1556
  ]);
1557
+ useEffect(() => {
1558
+ if (route.name !== 'pluginWriteTask' || route.goal !== 'update')
1559
+ return;
1560
+ if (snapshot.router.current.context.autoPreparedUpdate)
1561
+ return;
1562
+ const targetKeys = route.targetKeys;
1563
+ const versionEvidence = route.versionEvidence;
1564
+ if (!targetKeys || targetKeys.length === 0 || !versionEvidence)
1565
+ return;
1566
+ const allCandidates = props.controller.listPluginWriteTaskCandidates('update');
1567
+ const retainedTargetKeys = new Set(targetKeys);
1568
+ const frozenPluginIds = new Set(route.recommendedPluginIds
1569
+ ?? targetKeys.map((targetKey) => targetKey.split('\0')[0]).filter(Boolean));
1570
+ const currentFrozenCandidates = allCandidates.filter((candidate) => frozenPluginIds.has(candidate.target.pluginId));
1571
+ if (currentFrozenCandidates.length !== targetKeys.length
1572
+ || currentFrozenCandidates.some((candidate) => !retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))))
1573
+ return;
1574
+ const currentByKey = new Map(allCandidates.map((candidate) => [
1575
+ pluginWriteTaskTargetKey(candidate), candidate,
1576
+ ]));
1577
+ const candidates = targetKeys.flatMap((targetKey) => {
1578
+ const candidate = currentByKey.get(targetKey);
1579
+ return candidate ? [candidate] : [];
1580
+ });
1581
+ const representedPluginIds = new Set(candidates.map((candidate) => candidate.target.pluginId));
1582
+ if (candidates.length !== targetKeys.length
1583
+ || candidates.some((candidate) => !candidate.eligible
1584
+ || !frozenPluginIds.has(candidate.target.pluginId))
1585
+ || [...frozenPluginIds].some((pluginId) => !representedPluginIds.has(pluginId)))
1586
+ return;
1587
+ const currentVersionEvidence = Object.assign({}, ...[...frozenPluginIds].map((pluginId) => {
1588
+ const candidatePlugin = props.controller.getPluginOverview(pluginId);
1589
+ return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
1590
+ }));
1591
+ if (Object.keys(currentVersionEvidence).length !== targetKeys.length
1592
+ || Object.entries(currentVersionEvidence).some(([key, version]) => versionEvidence[key] !== version)
1593
+ || Object.keys(versionEvidence).some((key) => currentVersionEvidence[key] === undefined))
1594
+ return;
1595
+ props.controller.updatePageContext({ autoPreparedUpdate: true });
1596
+ beginReview({
1597
+ kind: 'pluginWriteTask',
1598
+ goal: 'update',
1599
+ targets: candidates.map((candidate) => candidate.target),
1600
+ executionPolicy: 'all',
1601
+ }, props.controller.pluginWriteTaskReturnRoute(route));
1602
+ }, [
1603
+ props.controller,
1604
+ route.name,
1605
+ route.name === 'pluginWriteTask' ? route.goal : '',
1606
+ route.name === 'pluginWriteTask' ? route.targetKeys?.join('\0') ?? '' : '',
1607
+ route.name === 'pluginWriteTask' ? route.recommendedPluginIds?.join('\0') ?? '' : '',
1608
+ route.name === 'pluginWriteTask' ? JSON.stringify(route.versionEvidence ?? {}) : '',
1609
+ snapshot.router.current.context.autoPreparedUpdate,
1610
+ snapshot.viewModel,
1611
+ ]);
1466
1612
  useEffect(() => {
1467
1613
  if (route.name !== 'operationReportPreview')
1468
1614
  return;
@@ -1654,6 +1800,7 @@ export function ConsoleAppV4(props) {
1654
1800
  || Boolean(route.name === 'issueDetail' && issue
1655
1801
  && issue.diagnostic.technical)
1656
1802
  || Boolean(route.name === 'pluginDependencies' && plugin)
1803
+ || Boolean(route.name === 'pluginDependencyNode' && plugin)
1657
1804
  || portableVariantCandidates.length > 0
1658
1805
  || Boolean(route.name === 'portableDependencies' && plugin
1659
1806
  && props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependencies.length)
@@ -1675,6 +1822,7 @@ export function ConsoleAppV4(props) {
1675
1822
  let help = '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出';
1676
1823
  let content;
1677
1824
  let actions = [];
1825
+ let referenceDetailPagination;
1678
1826
  let pluginRows = [];
1679
1827
  let visibleConfigTabs = [];
1680
1828
  let activeConfigTabId;
@@ -1713,16 +1861,27 @@ export function ConsoleAppV4(props) {
1713
1861
  const base = policy.mode === 'compact' ? compactRows : standardRows;
1714
1862
  return base + Math.max(0, size.rows - baseline);
1715
1863
  };
1716
- const pluginListMenuCount = (mode, items, queryValue = query) => {
1717
- if (items.length === 0 || queryValue)
1718
- return 0;
1864
+ const pluginListRowIds = (mode, items, queryValue = query, sourceUnavailable = false) => {
1865
+ if (items.length === 0 || sourceUnavailable)
1866
+ return [];
1867
+ if (queryValue)
1868
+ return [...items.map((item) => item.pluginId), '@menu:batch'];
1719
1869
  const listed = new Set(items.map((item) => item.pluginId));
1720
- return 1
1721
- + Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
1722
- .some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
1723
- + Number(mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
1724
- + Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
1725
- .some((candidate) => candidate.target.scope === 'project'));
1870
+ return [
1871
+ ...(!queryValue && mode === 'installed'
1872
+ && props.controller.listPluginWriteTaskCandidates('update')
1873
+ .some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId))
1874
+ ? ['@menu:update'] : []),
1875
+ ...(!queryValue && mode === 'installed'
1876
+ && props.controller.listCurrentProjectIntentRoots().length > 0
1877
+ ? ['@menu:restore-project'] : []),
1878
+ ...(!queryValue && mode === 'installed'
1879
+ && props.controller.listPluginWriteTaskCandidates('restore')
1880
+ .some((candidate) => candidate.target.scope === 'project')
1881
+ ? ['@menu:repair-project'] : []),
1882
+ '@menu:batch',
1883
+ ...items.map((item) => item.pluginId),
1884
+ ];
1726
1885
  };
1727
1886
  const go = (next) => {
1728
1887
  props.controller.updatePageContext({
@@ -1758,13 +1917,13 @@ export function ConsoleAppV4(props) {
1758
1917
  ...(restored.query ? { query: restored.query } : {}),
1759
1918
  installFilter: restored.installFilter ?? 'all',
1760
1919
  });
1761
- itemCount = items.length;
1762
- const entityIndex = restored.focusedEntityId
1763
- ? items.findIndex((item) => item.pluginId === restored.focusedEntityId)
1764
- : -1;
1765
- restoredFocus = entityIndex >= 0
1766
- ? pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + entityIndex
1767
- : Math.min(restoredFocus, Math.max(0, pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + items.length - 1));
1920
+ const rowIds = pluginListRowIds(restoredFrame.route.mode, items, restored.query ?? '');
1921
+ const restoredEntityIndex = restored.focusedEntityId
1922
+ ? rowIds.indexOf(restored.focusedEntityId) : -1;
1923
+ itemCount = rowIds.length;
1924
+ restoredFocus = restoredEntityIndex >= 0
1925
+ ? restoredEntityIndex
1926
+ : Math.min(restoredFocus, Math.max(0, rowIds.length - 1));
1768
1927
  }
1769
1928
  else if (restoredFrame.route.name === 'sourceList') {
1770
1929
  const sources = props.controller.listSources();
@@ -1802,6 +1961,20 @@ export function ConsoleAppV4(props) {
1802
1961
  setShowTechnical(false);
1803
1962
  setActionError(undefined);
1804
1963
  };
1964
+ const moveBatchStage = (stage) => {
1965
+ const restored = batchDraft?.stageView?.[stage];
1966
+ setBatchDraft((current) => current ? {
1967
+ ...current,
1968
+ stage,
1969
+ stageView: {
1970
+ ...current.stageView,
1971
+ [current.stage]: { focusIndex: focus, scrollOffset },
1972
+ },
1973
+ } : current);
1974
+ setFocus(restored?.focusIndex ?? 0);
1975
+ setScrollOffset(restored?.scrollOffset ?? 0);
1976
+ setActionError(undefined);
1977
+ };
1805
1978
  const back = () => {
1806
1979
  if (route.name === 'sourceEdit'
1807
1980
  && sourceDraft
@@ -1929,7 +2102,9 @@ export function ConsoleAppV4(props) {
1929
2102
  };
1930
2103
  const batchCandidates = (draft) => {
1931
2104
  const mode = draft.mode;
1932
- const all = props.controller.listPlugins({ mode });
2105
+ const frozenPluginIds = draft.frozenPluginIds ? new Set(draft.frozenPluginIds) : undefined;
2106
+ const all = props.controller.listPlugins({ mode })
2107
+ .filter((candidate) => !frozenPluginIds || frozenPluginIds.has(candidate.pluginId));
1933
2108
  if (draft.showSelected) {
1934
2109
  const byId = new Map(all.map((candidate) => [candidate.pluginId, candidate]));
1935
2110
  return draft.selectedPluginIds.map((pluginId) => ({
@@ -1942,17 +2117,24 @@ export function ConsoleAppV4(props) {
1942
2117
  ...(draft.sourceId ? { sourceId: draft.sourceId } : {}),
1943
2118
  ...(draft.query ? { query: draft.query } : {}),
1944
2119
  installFilter: draft.installFilter,
1945
- }).map((plugin) => ({ pluginId: plugin.pluginId, plugin }));
2120
+ }).filter((plugin) => !frozenPluginIds || frozenPluginIds.has(plugin.pluginId))
2121
+ .map((plugin) => ({ pluginId: plugin.pluginId, plugin }));
1946
2122
  };
1947
2123
  const batchCandidateBlockReason = (draft, candidate) => !candidate.plugin
1948
2124
  ? '插件已不在当前状态中,可取消选择'
1949
- : candidate.plugin.configOnly
2125
+ : draft.mode === 'browse' && candidate.plugin.configOnly
1950
2126
  ? '来源中没有可信插件定义'
1951
- : draft.mode === 'browse' && hasInstalledPlacement(candidate.plugin)
1952
- ? '这个插件已经添加'
1953
- : undefined;
2127
+ : draft.mode === 'browse' && candidate.plugin.sourceUnavailable
2128
+ ? '插件来源不可用'
2129
+ : draft.mode === 'browse' && candidate.plugin.capabilityUnsupported
2130
+ ? '当前 AW CLI 不支持插件所需功能'
2131
+ : draft.mode === 'browse' && !props.controller.pluginHasInstallableTarget(candidate.plugin.pluginId)
2132
+ ? '没有可信且兼容的缺失位置'
2133
+ : undefined;
1954
2134
  const invalidBatchSelection = (draft) => {
2135
+ const frozenPluginIds = draft.frozenPluginIds ? new Set(draft.frozenPluginIds) : undefined;
1955
2136
  const byId = new Map(props.controller.listPlugins({ mode: draft.mode })
2137
+ .filter((plugin) => !frozenPluginIds || frozenPluginIds.has(plugin.pluginId))
1956
2138
  .map((plugin) => [plugin.pluginId, plugin]));
1957
2139
  return draft.selectedPluginIds.filter((pluginId) => batchCandidateBlockReason(draft, {
1958
2140
  pluginId,
@@ -2017,10 +2199,68 @@ export function ConsoleAppV4(props) {
2017
2199
  ? { versionEvidence: Object.assign({}, ...[...selected].map((pluginId) => {
2018
2200
  const candidatePlugin = props.controller.getPluginOverview(pluginId);
2019
2201
  return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
2020
- })) }
2202
+ })),
2203
+ ...(batchDraft?.frozenPluginIds
2204
+ ? { recommendedPluginIds: batchDraft.selectedPluginIds.slice() }
2205
+ : {}) }
2021
2206
  : {}),
2022
2207
  });
2023
2208
  };
2209
+ const openRecommendedUpdateTask = (pluginIds) => {
2210
+ props.controller.updatePageContext({
2211
+ focusIndex: focus,
2212
+ scrollOffset,
2213
+ query,
2214
+ installFilter,
2215
+ sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
2216
+ focusedEntityId: route.name === 'pluginList' ? pluginRows[focus]?.pluginId : undefined,
2217
+ });
2218
+ props.controller.beginPluginWriteBatch();
2219
+ const flowId = props.controller.snapshot.router.currentFlowId;
2220
+ if (!flowId)
2221
+ throw new Error('推荐更新流程缺少 flowId。');
2222
+ const frozenPluginIds = [...new Set(pluginIds)];
2223
+ setBatchDraft({
2224
+ flowId,
2225
+ recommendedGoal: 'update',
2226
+ mode: 'installed',
2227
+ stage: 'select',
2228
+ selection: 'selected',
2229
+ selectedPluginIds: frozenPluginIds.slice(),
2230
+ frozenPluginIds,
2231
+ platform: 'both',
2232
+ scope: 'project',
2233
+ ...(route.name === 'pluginList' && route.sourceId ? { sourceId: route.sourceId } : {}),
2234
+ query,
2235
+ installFilter,
2236
+ showSelected: false,
2237
+ });
2238
+ setDirty(true);
2239
+ const selected = new Set(frozenPluginIds);
2240
+ const candidates = props.controller.listPluginWriteTaskCandidates('update')
2241
+ .filter((candidate) => selected.has(candidate.target.pluginId));
2242
+ const first = candidates[0];
2243
+ if (!first) {
2244
+ setActionError('推荐插件没有经过版本证据确认的更新。');
2245
+ return;
2246
+ }
2247
+ const versionEvidence = Object.assign({}, ...frozenPluginIds.map((pluginId) => {
2248
+ const candidatePlugin = props.controller.getPluginOverview(pluginId);
2249
+ return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
2250
+ }));
2251
+ props.controller.push({
2252
+ name: 'pluginWriteTask',
2253
+ pluginId: first.target.pluginId,
2254
+ goal: 'update',
2255
+ targetKeys: candidates.map(pluginWriteTaskTargetKey),
2256
+ versionEvidence,
2257
+ recommendedPluginIds: frozenPluginIds,
2258
+ batchOrigin: { flowId, owner: 'batch-write' },
2259
+ });
2260
+ setFocus(0);
2261
+ setScrollOffset(0);
2262
+ setSearchFocus(false);
2263
+ };
2024
2264
  const continueInstallAfterTargets = async (candidate, targets) => {
2025
2265
  if (props.controller.snapshot.installPlacementDraft?.pluginId !== candidate.pluginId) {
2026
2266
  props.controller.beginInstallFlow(candidate.pluginId, scope);
@@ -2145,12 +2385,29 @@ export function ConsoleAppV4(props) {
2145
2385
  };
2146
2386
  const issueWriteIntent = (selectedIssue, action) => {
2147
2387
  const pluginId = action.pluginId ?? selectedIssue.pluginId;
2148
- if (!pluginId || !selectedIssue.platform)
2388
+ if (!pluginId)
2149
2389
  return undefined;
2150
2390
  const selectedScope = action.id === 'keepUserScope'
2151
2391
  ? 'project'
2152
2392
  : action.id === 'keepProjectScope' ? 'user' : action.scope ?? selectedIssue.scope;
2153
2393
  const selectedPlatform = action.platform ?? selectedIssue.platform;
2394
+ const dependencyIssue = selectedIssue.category === 'dependencyMissing'
2395
+ || selectedIssue.category === 'dependencyNotReady';
2396
+ const dependencyTargetAction = action.id === 'installDependency'
2397
+ || action.id === 'reinstallDependency';
2398
+ const sourceTargetCleanup = selectedIssue.entity.kind === 'source'
2399
+ && action.id === 'cleanup'
2400
+ && action.pluginId !== undefined
2401
+ && action.platform !== undefined
2402
+ && action.scope !== undefined;
2403
+ const targetSpecificOrigin = (dependencyIssue && !dependencyTargetAction)
2404
+ || sourceTargetCleanup;
2405
+ const consumerPluginId = targetSpecificOrigin
2406
+ ? pluginId : selectedIssue.pluginId;
2407
+ const consumerPlatform = targetSpecificOrigin
2408
+ ? selectedPlatform : selectedIssue.platform;
2409
+ const consumerScope = targetSpecificOrigin
2410
+ ? selectedScope : selectedIssue.scope;
2154
2411
  const writeAction = action.writeAction ?? (action.id === 'sync'
2155
2412
  ? 'sync'
2156
2413
  : action.id === 'refreshAndSync'
@@ -2180,16 +2437,20 @@ export function ConsoleAppV4(props) {
2180
2437
  issueKey: userIssueKey(selectedIssue),
2181
2438
  category: selectedIssue.category,
2182
2439
  actionId: action.id,
2183
- ...(selectedIssue.category === 'dependencyMissing' || selectedIssue.category === 'dependencyNotReady'
2440
+ ...(targetSpecificOrigin
2184
2441
  ? {
2185
- consumerPluginId: selectedIssue.pluginId,
2186
- consumerSourceIdentity: selectedIssue.sourceIdentity,
2187
- consumerPlatform: selectedIssue.platform,
2188
- consumerScope: selectedIssue.scope,
2189
- dependencyPluginId: selectedIssue.dependencyPluginId,
2190
- dependencyPlatform: selectedIssue.dependencyPlatform,
2191
- dependencyScope: selectedIssue.dependencyScope,
2192
- dependencyProject: selectedIssue.dependencyProject,
2442
+ consumerPluginId,
2443
+ consumerSourceIdentity: consumerPluginId
2444
+ ? props.controller.getPluginSourceIdentity(consumerPluginId)
2445
+ : selectedIssue.sourceIdentity,
2446
+ consumerPlatform,
2447
+ consumerScope,
2448
+ ...(dependencyIssue ? {
2449
+ dependencyPluginId: selectedIssue.dependencyPluginId,
2450
+ dependencyPlatform: selectedIssue.dependencyPlatform,
2451
+ dependencyScope: selectedIssue.dependencyScope,
2452
+ dependencyProject: selectedIssue.dependencyProject,
2453
+ } : {}),
2193
2454
  }
2194
2455
  : {}),
2195
2456
  },
@@ -2264,7 +2525,7 @@ export function ConsoleAppV4(props) {
2264
2525
  },
2265
2526
  ...(draft.type === 'git' ? [
2266
2527
  { title: '版本引用', before: ref(current), after: draft.refPolicy === 'cliChannel' ? '跟随 AW CLI' : draft.ref, changed: ref(current) !== (draft.refPolicy === 'cliChannel' ? '跟随 AW CLI' : draft.ref) },
2267
- { title: 'Catalog 子目录', before: current?.type === 'git' ? current.subdir ?? '未设置' : undefined, after: draft.subdir || '未设置', changed: current?.type !== 'git' || (current.subdir ?? '') !== draft.subdir },
2528
+ { title: 'Catalog 子目录', before: current?.type === 'git' ? current.subdir ?? '仓库根目录' : undefined, after: draft.subdir || '仓库根目录', changed: current?.type !== 'git' || (current.subdir ?? '') !== draft.subdir },
2268
2529
  ] : []),
2269
2530
  { title: '自动检查更新', before: current ? current.autoCheck !== false ? '开启' : '关闭' : undefined, after: draft.autoCheck ? '开启' : '关闭', changed: !current || (current.autoCheck !== false) !== draft.autoCheck },
2270
2531
  ];
@@ -2737,7 +2998,17 @@ export function ConsoleAppV4(props) {
2737
2998
  route.name === 'configSave' ? `${route.pluginId}:${route.layer.scope}` : '',
2738
2999
  draftRemoves,
2739
3000
  draftUpdates,
3001
+ configSaveImpactAttempt,
2740
3002
  ]);
3003
+ useEffect(() => {
3004
+ if (!operationConfirmation)
3005
+ return;
3006
+ if (route.name !== 'review'
3007
+ || snapshot.activeReview?.reviewToken !== operationConfirmation.reviewToken) {
3008
+ setOperationConfirmation(undefined);
3009
+ setOverlayFocus(0);
3010
+ }
3011
+ }, [operationConfirmation, route.name, snapshot.activeReview?.reviewToken]);
2741
3012
  if (snapshot.phase === 'error' && !isCatalogSourcesBootstrapFlow(snapshot)
2742
3013
  && !isOperationHistoryRoute(route)) {
2743
3014
  const sourcesConfigInvalid = snapshot.loadError?.code === 'catalogSourcesConfigInvalid';
@@ -2781,9 +3052,7 @@ export function ConsoleAppV4(props) {
2781
3052
  run: () => go({ name: 'sourceList' }),
2782
3053
  },
2783
3054
  ];
2784
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [snapshot.statusReadAtMs !== undefined
2785
- ? _jsx(StaticRow, { title: "\u6700\u8FD1\u6210\u529F\u8BFB\u53D6\u72B6\u6001", meta: new Date(snapshot.statusReadAtMs).toLocaleString('zh-CN', { hour12: false }), compact: true })
2786
- : null, _jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _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} 个插件可更新${home.directPluginUpdateCount < home.pluginUpdateCount ? `;${home.directPluginUpdateCount} 个可直接确认` : ''}`, status: "warning" }) : null, home.catalogRecoveryCount > 0 ? _jsx(ResultRow, { title: `${home.catalogRecoveryCount} 个插件来源需要检查或恢复`, status: "warning" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
3055
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `已添加 ${home.installedPlugins} 个插件`, status: "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] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
2787
3056
  }
2788
3057
  else if (route.name === 'pluginList') {
2789
3058
  const sourceId = route.sourceId ?? 'all';
@@ -2825,7 +3094,7 @@ export function ConsoleAppV4(props) {
2825
3094
  const projectRestoreCandidates = props.controller.listPluginWriteTaskCandidates('restore')
2826
3095
  .filter((candidate) => candidate.target.scope === 'project');
2827
3096
  const projectIntentRoots = props.controller.listCurrentProjectIntentRoots();
2828
- const menuRows = sourceError || items.length === 0 || query ? [] : [
3097
+ const menuRows = [
2829
3098
  ...(route.mode === 'installed' && recommendedUpdatePluginIds.length > 0 ? [{
2830
3099
  pluginId: '@menu:update',
2831
3100
  name: `更新 ${recommendedUpdatePluginIds.length} 个插件…`,
@@ -2834,7 +3103,7 @@ export function ConsoleAppV4(props) {
2834
3103
  placementSummary: '',
2835
3104
  statusLabel: '推荐',
2836
3105
  status: 'warning',
2837
- menuAction: () => openSelectedPluginTask('update', recommendedUpdatePluginIds),
3106
+ menuAction: () => openRecommendedUpdateTask(recommendedUpdatePluginIds),
2838
3107
  }] : []),
2839
3108
  ...(route.mode === 'installed' && projectIntentRoots.length > 0 ? [{
2840
3109
  pluginId: '@menu:restore-project',
@@ -2859,7 +3128,9 @@ export function ConsoleAppV4(props) {
2859
3128
  }] : []),
2860
3129
  {
2861
3130
  pluginId: '@menu:batch',
2862
- name: route.mode === 'browse' ? '选择多个插件添加…' : '选择多个…',
3131
+ name: query
3132
+ ? '选择多个当前结果…'
3133
+ : route.mode === 'browse' ? '选择多个插件添加…' : '选择多个…',
2863
3134
  sourceLabel: '',
2864
3135
  description: route.mode === 'browse'
2865
3136
  ? '先选择插件,再选择安装位置和平台'
@@ -2870,18 +3141,23 @@ export function ConsoleAppV4(props) {
2870
3141
  menuAction: () => openBatchSelection(route.mode === 'browse' ? 'install' : undefined),
2871
3142
  },
2872
3143
  ];
2873
- pluginRows = sourceError ? [] : [...menuRows, ...items.map((item) => ({
2874
- pluginId: item.pluginId,
2875
- name: item.displayName,
2876
- sourceLabel: item.sourceLabel,
2877
- description: item.configOnly
2878
- ? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
2879
- : item.description,
2880
- ...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
2881
- placementSummary: pluginPlacementSummary(item),
2882
- statusLabel: statusText(item),
2883
- status: pluginRowStatus(item),
2884
- }))];
3144
+ const pluginRowsById = new Map([
3145
+ ...menuRows.map((row) => [row.pluginId, row]),
3146
+ ...items.map((item) => [item.pluginId, {
3147
+ pluginId: item.pluginId,
3148
+ name: item.displayName,
3149
+ sourceLabel: item.sourceLabel,
3150
+ description: item.configOnly
3151
+ ? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
3152
+ : item.description,
3153
+ ...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
3154
+ placementSummary: pluginPlacementSummary(item),
3155
+ statusLabel: statusText(item),
3156
+ status: pluginRowStatus(item),
3157
+ }]),
3158
+ ]);
3159
+ pluginRows = pluginListRowIds(route.mode, items, query, sourceError)
3160
+ .flatMap((rowId) => pluginRowsById.get(rowId) ?? []);
2885
3161
  if (route.mode === 'installed' && installFilter === 'updateAvailable'
2886
3162
  && items.length === 0 && !sourceError) {
2887
3163
  title = '没有可更新插件';
@@ -2895,6 +3171,32 @@ export function ConsoleAppV4(props) {
2895
3171
  },
2896
3172
  }];
2897
3173
  }
3174
+ else if (query && items.length === 0 && !sourceError) {
3175
+ title = '搜索没有匹配插件';
3176
+ description = `“${query}”在当前来源和状态筛选中没有匹配项。`;
3177
+ actions = [{
3178
+ label: '清除搜索',
3179
+ description: '保留当前来源和状态筛选',
3180
+ run: () => {
3181
+ setQuery('');
3182
+ props.controller.updatePageContext({ query: '' });
3183
+ },
3184
+ }];
3185
+ }
3186
+ else if (sourceId !== 'all' && items.length === 0 && !sourceError) {
3187
+ title = route.mode === 'installed' ? '当前来源没有已添加插件' : '当前来源没有可显示插件';
3188
+ description = `来源 ${sourceId} 在当前状态筛选中没有匹配插件。`;
3189
+ actions = [{
3190
+ label: '查看全部来源',
3191
+ description: '保留当前状态筛选',
3192
+ run: () => {
3193
+ props.controller.replace({ name: 'pluginList', mode: route.mode });
3194
+ props.controller.updatePageContext({ sourceTabId: 'all' });
3195
+ setFocus(0);
3196
+ setScrollOffset(0);
3197
+ },
3198
+ }];
3199
+ }
2898
3200
  else if (route.mode === 'installed' && items.length === 0 && !sourceError) {
2899
3201
  title = '还没有添加插件';
2900
3202
  description = `从已配置的 ${props.controller.listSources().length} 个来源中浏览可用能力。`;
@@ -2902,11 +3204,14 @@ export function ConsoleAppV4(props) {
2902
3204
  }
2903
3205
  else if (route.mode === 'browse' && items.length === 0 && !sourceError) {
2904
3206
  actions = [{
2905
- label: '没有匹配插件',
3207
+ label: installFilter === 'uninstalled' ? '查看全部插件' : '没有可显示插件',
2906
3208
  description: installFilter === 'uninstalled'
2907
- ? '当前来源和搜索条件下没有未安装插件;按 f 查看全部,或按 / 编辑搜索'
2908
- : '按 / 编辑搜索,或切换来源',
2909
- disabled: '没有可打开的插件',
3209
+ ? '当前没有整个插件都未安装的结果;保留部分已添加插件并查看其缺失位置'
3210
+ : '当前所有来源都没有可浏览的插件',
3211
+ ...(installFilter === 'uninstalled' ? { run: () => {
3212
+ setInstallFilter('all');
3213
+ props.controller.updatePageContext({ installFilter: 'all' });
3214
+ } } : { disabled: '没有可打开的插件' }),
2910
3215
  }];
2911
3216
  }
2912
3217
  const pluginListStaticRows = 3;
@@ -2929,7 +3234,7 @@ export function ConsoleAppV4(props) {
2929
3234
  ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'uninstalled' ? '[未安装]' : '未安装', " ", uninstalledCount] }), " f \u5207\u6362"] })
2930
3235
  : _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'updateAvailable' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'updateAvailable' ? '[可更新]' : '可更新', " ", updateCount] }), " f \u5207\u6362"] }), sourceError
2931
3236
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
2932
- : route.mode === 'installed' && items.length === 0
3237
+ : route.mode === 'installed' && items.length === 0 && !query && sourceId === 'all'
2933
3238
  ? installFilter === 'updateAvailable'
2934
3239
  ? _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" })
2935
3240
  : _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" })
@@ -2959,11 +3264,17 @@ export function ConsoleAppV4(props) {
2959
3264
  const candidateActions = candidates.map((candidate) => {
2960
3265
  const blockReason = batchCandidateBlockReason(batchDraft, candidate);
2961
3266
  const isSelected = selected.has(candidate.pluginId);
3267
+ const plugin = candidate.plugin;
2962
3268
  return {
2963
- label: candidate.plugin?.displayName ?? `${candidate.pluginId}(已不可用,可取消选择)`,
2964
- description: blockReason
2965
- ? `${candidate.plugin?.sourceLabel ?? '状态已失效'} · ${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
2966
- : `${candidate.plugin.sourceLabel} · ${pluginPlacementSummary(candidate.plugin)}`,
3269
+ label: plugin
3270
+ ? `${plugin.displayName} · ${plugin.sourceLabel}`
3271
+ : `${candidate.pluginId}(已不可用,可取消选择)`,
3272
+ description: plugin
3273
+ ? `${plugin.description} · ${blockReason
3274
+ ? `${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
3275
+ : statusText(plugin)}`
3276
+ : `${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`,
3277
+ meta: plugin ? batchPluginVersionSummary(plugin) : '版本待确认',
2967
3278
  selected: isSelected,
2968
3279
  pluginId: candidate.pluginId,
2969
3280
  canSelectNew: blockReason === undefined,
@@ -2986,27 +3297,47 @@ export function ConsoleAppV4(props) {
2986
3297
  setActionError(`${invalid.length} 个已选插件已不可用。请先在“查看全部已选”中取消选择。`);
2987
3298
  return;
2988
3299
  }
3300
+ if (batchDraft.recommendedGoal) {
3301
+ openSelectedPluginTask(batchDraft.recommendedGoal, batchDraft.selectedPluginIds);
3302
+ return;
3303
+ }
2989
3304
  if (batchDraft.mode === 'browse') {
2990
3305
  setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
2991
3306
  props.controller.push({ name: 'batchPlacement' });
3307
+ setFocus(0);
3308
+ setScrollOffset(0);
2992
3309
  }
2993
3310
  else {
2994
- setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
3311
+ moveBatchStage('task');
2995
3312
  }
2996
- setFocus(0);
2997
- setScrollOffset(0);
2998
3313
  };
2999
3314
  const continueAction = {
3000
3315
  label: `查看确认(${batchDraft.selectedPluginIds.length})`,
3001
- description: batchDraft.selectedPluginIds.length > 0 ? '继续选择任务和确认范围' : '先选择至少一个插件',
3316
+ description: batchDraft.selectedPluginIds.length > 0
3317
+ ? batchDraft.recommendedGoal ? '重新读取更新证据并建立 Review' : '继续选择任务和确认范围'
3318
+ : '先选择至少一个插件',
3319
+ disabled: batchDraft.selectedPluginIds.length === 0 ? '先选择至少一个插件' : undefined,
3002
3320
  run: continueSelection,
3003
3321
  };
3004
- const utilityActions = [
3322
+ const selectedSetAction = {
3323
+ label: batchDraft.showSelected ? '返回当前结果' : `查看全部已选(${batchDraft.selectedPluginIds.length})`,
3324
+ description: hiddenSelected > 0 ? `${hiddenSelected} 个选择在其他筛选中` : '包括其他筛选中的选择',
3325
+ run: () => {
3326
+ setBatchDraft((current) => current ? {
3327
+ ...current,
3328
+ showSelected: !current.showSelected,
3329
+ showSelectionTools: false,
3330
+ } : current);
3331
+ setFocus(0);
3332
+ setScrollOffset(0);
3333
+ },
3334
+ };
3335
+ const utilityActions = batchDraft.showSelectionTools ? [
3005
3336
  {
3006
- label: batchDraft.showSelected ? '返回当前结果' : `查看全部已选(${batchDraft.selectedPluginIds.length})`,
3007
- description: hiddenSelected > 0 ? `${hiddenSelected} 个选择在其他筛选中` : '完整 canonical 选择集合',
3337
+ label: '返回插件选择',
3338
+ description: '保留当前已选插件',
3008
3339
  run: () => {
3009
- setBatchDraft((current) => current ? { ...current, showSelected: !current.showSelected } : current);
3340
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: false } : current);
3010
3341
  setFocus(0);
3011
3342
  setScrollOffset(0);
3012
3343
  },
@@ -3031,20 +3362,40 @@ export function ConsoleAppV4(props) {
3031
3362
  label: '清空全部已选',
3032
3363
  run: () => setBatchDraft((current) => current ? { ...current, selectedPluginIds: [] } : current),
3033
3364
  },
3365
+ ] : [
3366
+ selectedSetAction,
3367
+ {
3368
+ label: '选择工具…',
3369
+ description: '全选、取消当前结果或清空全部选择',
3370
+ run: () => {
3371
+ setBatchDraft((current) => current ? { ...current, showSelectionTools: true } : current);
3372
+ setFocus(candidateActions.length + 1);
3373
+ setScrollOffset(Math.max(0, candidateActions.length));
3374
+ },
3375
+ },
3034
3376
  ];
3035
3377
  actions = [continueAction, ...candidateActions, ...utilityActions];
3036
3378
  const batchFocus = Math.min(focus, actions.length - 1);
3037
3379
  context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
3038
3380
  help = '↑↓ 移动 · Space 选择 · Enter 查看/操作 · c 查看确认 · ←→ 来源 · / 搜索 · Esc 放弃';
3039
- const scrollActions = [...candidateActions, ...utilityActions];
3040
- const viewport = actionViewport(scrollActions, policy.mode === 'compact', Math.max(0, batchFocus - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)));
3041
- const focusedCandidate = batchFocus > 0 && batchFocus <= candidateActions.length
3042
- ? candidates[batchFocus - 1]?.plugin : undefined;
3381
+ const viewport = rowViewport({
3382
+ rowHeights: [
3383
+ ...candidateActions.map(() => 2),
3384
+ ...utilityActions.map((action) => policy.mode === 'compact'
3385
+ ? 1 : action.description || action.disabled ? 2 : 1),
3386
+ ],
3387
+ focus: Math.max(0, batchFocus - 1),
3388
+ requestedOffset: Math.max(0, scrollOffset - 1),
3389
+ rowBudget: Math.max(1, pageActionBudget(4)),
3390
+ });
3391
+ const candidateOffset = Math.min(viewport.offset, candidateActions.length);
3392
+ const candidateLimit = Math.max(0, Math.min(candidateActions.length, viewport.offset + viewport.limit) - candidateOffset);
3393
+ const utilityOffset = Math.max(0, viewport.offset - candidateActions.length);
3394
+ const utilityLimit = Math.max(0, Math.min(utilityActions.length, viewport.offset + viewport.limit - candidateActions.length)
3395
+ - utilityOffset);
3043
3396
  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
3044
3397
  ? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3045
- : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsx(SemanticRows, { actions: [continueAction], focus: batchFocus, kind: "action", compact: true }), focusedCandidate
3046
- ? _jsx(StaticRow, { title: `用途 · ${focusedCandidate.description}`, status: "info", compact: true })
3047
- : null, _jsx(SemanticRows, { actions: scrollActions, focus: batchFocus - 1, kind: "toggle", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
3398
+ : _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _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] });
3048
3399
  }
3049
3400
  else {
3050
3401
  const selectedIds = batchDraft.selectedPluginIds;
@@ -3063,6 +3414,13 @@ export function ConsoleAppV4(props) {
3063
3414
  setActionError('所选插件没有可用的已有安装位置。');
3064
3415
  return;
3065
3416
  }
3417
+ setBatchDraft((current) => current ? {
3418
+ ...current,
3419
+ action,
3420
+ maintenanceTargets: exactTargets,
3421
+ selectedMaintenanceTargetKeys: exactTargets.map(batchTargetKey),
3422
+ showPlacementTargets: false,
3423
+ } : current);
3066
3424
  beginReview({
3067
3425
  kind: 'pluginWriteBatch', action,
3068
3426
  selection: { kind: 'targets', targets: exactTargets },
@@ -3079,11 +3437,7 @@ export function ConsoleAppV4(props) {
3079
3437
  { label: '清理遗留内容…', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个有受管证据的位置;保留 foreign 和 shared 内容`, run: () => startExistingBatch('cleanup') },
3080
3438
  {
3081
3439
  label: '返回任务选择',
3082
- run: () => {
3083
- setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
3084
- setFocus(0);
3085
- setScrollOffset(0);
3086
- },
3440
+ run: () => moveBatchStage('task'),
3087
3441
  },
3088
3442
  ];
3089
3443
  }
@@ -3105,17 +3459,13 @@ export function ConsoleAppV4(props) {
3105
3459
  status: 'warning',
3106
3460
  run: () => openSelectedPluginTask('restore', selectedIds),
3107
3461
  }] : []),
3108
- { label: '移除所选插件…', description: `${uninstallTargets.length} 个现场目标;执行前检查所有权;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
3462
+ { label: '移除所选插件…', description: `${uninstallTargets.length} 个现有位置;可在下一页删减范围;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
3109
3463
  {
3110
3464
  label: '其他维护方式…',
3111
3465
  description: '同步、重新安装和普通清理',
3112
- run: () => {
3113
- setBatchDraft((current) => current ? { ...current, stage: 'maintenance' } : current);
3114
- setFocus(0);
3115
- setScrollOffset(0);
3116
- },
3466
+ run: () => moveBatchStage('maintenance'),
3117
3467
  },
3118
- { label: '返回调整选择', run: () => setBatchDraft((current) => current ? { ...current, stage: 'select' } : current) },
3468
+ { label: '返回调整选择', run: () => moveBatchStage('select') },
3119
3469
  ];
3120
3470
  }
3121
3471
  context = `${selectedIds.length} 个插件 · ${syncTargets.length} 个已保存或现场目标`;
@@ -3126,66 +3476,196 @@ export function ConsoleAppV4(props) {
3126
3476
  }
3127
3477
  else if (route.name === 'batchPlacement') {
3128
3478
  breadcrumb = '我的插件 / 批量操作 / 位置';
3129
- title = batchDraft?.selection === 'currentProjectIntentRoots'
3130
- ? '同步当前项目全部保存目标'
3131
- : `设置批量${batchDraft?.action ? batchActionLabel(batchDraft.action) : ''}位置`;
3132
- description = '所有选择使用同一位置和平台。确认页会重新读取状态并冻结实际计划。';
3133
- if (!batchDraft?.action) {
3479
+ const maintenanceTargets = batchDraft?.maintenanceTargets ?? [];
3480
+ const maintenanceSelection = new Set(batchDraft?.selectedMaintenanceTargetKeys ?? []);
3481
+ const maintenanceMode = (batchDraft?.maintenanceGoal !== undefined
3482
+ || batchDraft?.action !== undefined && batchDraft.action !== 'install')
3483
+ && maintenanceTargets.length > 0;
3484
+ const maintenanceLabel = batchDraft?.maintenanceGoal === 'update' ? '更新'
3485
+ : batchDraft?.maintenanceGoal === 'restore' ? '恢复'
3486
+ : batchDraft?.action ? batchActionLabel(batchDraft.action) : '处理';
3487
+ title = maintenanceMode ? `确认批量${maintenanceLabel}范围`
3488
+ : batchDraft?.selection === 'currentProjectIntentRoots'
3489
+ ? '同步当前项目全部保存目标'
3490
+ : `设置批量${batchDraft?.action ? batchActionLabel(batchDraft.action) : ''}位置`;
3491
+ description = maintenanceMode
3492
+ ? '默认沿用进入此页时的现有位置。可以取消位置,也可以恢复本次集合中的位置。'
3493
+ : '分别选择使用范围和工具。确认页会重新读取状态并冻结实际计划。';
3494
+ let installPlacementTargets = [];
3495
+ let maintenanceTargetCount = 0;
3496
+ if (!batchDraft?.action && !batchDraft?.maintenanceGoal) {
3134
3497
  actions = [{ label: '返回批量选择', run: back }];
3135
3498
  }
3136
- else {
3137
- const projectIntent = batchDraft.selection === 'currentProjectIntentRoots';
3499
+ else if (maintenanceMode) {
3500
+ const currentTargets = batchDraft.maintenanceGoal
3501
+ ? props.controller.listPluginWriteTaskCandidates(batchDraft.maintenanceGoal)
3502
+ .filter((candidate) => batchDraft.selectedPluginIds.includes(candidate.target.pluginId))
3503
+ .map((candidate) => candidate.target)
3504
+ : props.controller.listPluginWriteBatchTargets(batchDraft.action, batchDraft.selectedPluginIds);
3505
+ const frozenKeys = maintenanceTargets.map(batchTargetKey);
3506
+ const currentKeys = currentTargets.map(batchTargetKey);
3507
+ const targetEvidenceChanged = frozenKeys.length !== currentKeys.length
3508
+ || frozenKeys.some((key, index) => key !== currentKeys[index]);
3509
+ const selectedTargets = maintenanceTargets.filter((target) => maintenanceSelection.has(batchTargetKey(target)));
3510
+ const targetActions = maintenanceTargets.map((target) => ({
3511
+ label: `${props.controller.getPluginOverview(target.pluginId)?.displayName ?? target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3512
+ description: '本次处理这个现有位置',
3513
+ selected: maintenanceSelection.has(batchTargetKey(target)),
3514
+ run: () => setBatchDraft((current) => {
3515
+ if (!current)
3516
+ return current;
3517
+ const selectedKeys = new Set(current.selectedMaintenanceTargetKeys ?? []);
3518
+ const key = batchTargetKey(target);
3519
+ if (selectedKeys.has(key))
3520
+ selectedKeys.delete(key);
3521
+ else
3522
+ selectedKeys.add(key);
3523
+ return { ...current, selectedMaintenanceTargetKeys: [...selectedKeys] };
3524
+ }),
3525
+ }));
3526
+ maintenanceTargetCount = targetActions.length;
3138
3527
  actions = [
3528
+ ...targetActions,
3139
3529
  {
3140
- label: '当前项目', selected: batchDraft.scope === 'project',
3141
- run: () => setBatchDraft((current) => current ? { ...current, scope: 'project' } : current),
3142
- },
3143
- {
3144
- label: '所有项目', selected: batchDraft.scope === 'user',
3145
- disabled: projectIntent ? '项目全部同步固定使用当前项目' : undefined,
3146
- run: () => setBatchDraft((current) => current ? { ...current, scope: 'user' } : current),
3147
- },
3148
- ...['codex', 'claude', 'both'].map((platform) => ({
3149
- label: platform === 'both' ? 'Codex 和 Claude Code' : platformLabel(platform),
3150
- selected: batchDraft.platform === platform,
3151
- run: () => setBatchDraft((current) => current ? { ...current, platform } : current),
3152
- })),
3153
- {
3154
- label: `查看批量${batchActionLabel(batchDraft.action)}确认`,
3155
- description: `${batchDraft.selection === 'selected' ? `${batchDraft.selectedPluginIds.length} 个所选插件` : '当前项目全部保存目标'} · ${scopeLabel(batchDraft.scope)}`,
3156
- disabled: batchDraft.selection === 'selected' && batchDraft.selectedPluginIds.length === 0
3157
- ? '请至少选择一个插件' : undefined,
3158
- run: () => {
3159
- if (invalidBatchSelection(batchDraft).length > 0) {
3160
- setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
3161
- return;
3162
- }
3163
- const intent = {
3164
- kind: 'pluginWriteBatch',
3165
- action: batchDraft.action,
3166
- selection: batchDraft.selection === 'selected'
3167
- ? {
3168
- kind: 'selected',
3169
- pluginIds: [...new Set(batchDraft.selectedPluginIds)]
3170
- .sort((left, right) => left.localeCompare(right, 'en')),
3171
- }
3172
- : { kind: 'currentProjectIntentRoots' },
3173
- platform: batchDraft.platform,
3174
- scope: batchDraft.scope,
3175
- };
3176
- beginReview(intent);
3177
- setDirty(false);
3178
- setFocus(0);
3179
- setScrollOffset(0);
3180
- },
3530
+ label: `查看批量${maintenanceLabel}确认`,
3531
+ description: targetEvidenceChanged
3532
+ ? '安装位置已变化,请返回任务页重新选择'
3533
+ : `${selectedTargets.length} 个现有位置将处理`,
3534
+ disabled: targetEvidenceChanged ? '安装位置已变化,请重新确认本次范围'
3535
+ : selectedTargets.length === 0 ? '请至少保留一个处理位置' : undefined,
3536
+ run: () => beginReview(batchDraft.maintenanceGoal ? {
3537
+ kind: 'pluginWriteTask',
3538
+ goal: batchDraft.maintenanceGoal,
3539
+ targets: selectedTargets,
3540
+ executionPolicy: 'all',
3541
+ } : {
3542
+ kind: 'pluginWriteBatch',
3543
+ action: batchDraft.action,
3544
+ selection: { kind: 'targets', targets: selectedTargets },
3545
+ platform: selectedTargets[0].platform,
3546
+ scope: selectedTargets[0].scope,
3547
+ }),
3181
3548
  },
3549
+ { label: '返回任务选择', run: back },
3182
3550
  ];
3183
3551
  }
3552
+ else {
3553
+ const projectIntent = batchDraft.selection === 'currentProjectIntentRoots';
3554
+ installPlacementTargets = batchDraft.action === 'install'
3555
+ && batchDraft.selection === 'selected'
3556
+ ? props.controller.listPluginInstallBatchTargets(batchDraft.selectedPluginIds, batchDraft.platform, batchDraft.scope)
3557
+ : [];
3558
+ const addTargets = installPlacementTargets.filter((target) => target.state === 'add');
3559
+ const blockedTargets = installPlacementTargets.filter((target) => target.state === 'blocked');
3560
+ const existingTargets = installPlacementTargets.filter((target) => target.state === 'existing');
3561
+ if (batchDraft.showPlacementTargets) {
3562
+ actions = [
3563
+ ...installPlacementTargets.map((target) => ({
3564
+ label: `${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3565
+ description: target.state === 'add' ? '可添加'
3566
+ : target.state === 'existing' ? '已有,不重复添加' : `受阻:${target.reason}`,
3567
+ status: target.state === 'add' ? 'success'
3568
+ : target.state === 'blocked' ? 'warning' : 'muted',
3569
+ })),
3570
+ {
3571
+ label: '返回位置选择',
3572
+ run: () => {
3573
+ setBatchDraft((current) => current ? { ...current, showPlacementTargets: false } : current);
3574
+ setFocus(5);
3575
+ setScrollOffset(0);
3576
+ },
3577
+ },
3578
+ ];
3579
+ }
3580
+ else
3581
+ actions = [
3582
+ {
3583
+ label: '当前项目', selected: batchDraft.scope === 'project',
3584
+ run: () => setBatchDraft((current) => current ? { ...current, scope: 'project' } : current),
3585
+ },
3586
+ {
3587
+ label: '所有项目', selected: batchDraft.scope === 'user',
3588
+ disabled: projectIntent ? '项目全部同步固定使用当前项目' : undefined,
3589
+ run: () => setBatchDraft((current) => current ? { ...current, scope: 'user' } : current),
3590
+ },
3591
+ ...['codex', 'claude', 'both'].map((platform) => ({
3592
+ label: platform === 'both' ? 'Codex 和 Claude Code' : platformLabel(platform),
3593
+ selected: batchDraft.platform === platform,
3594
+ run: () => setBatchDraft((current) => current ? { ...current, platform } : current),
3595
+ })),
3596
+ ...(installPlacementTargets.length > 0 ? [{
3597
+ label: '查看全部位置和受阻原因…',
3598
+ description: `${addTargets.length} 个可添加 · ${existingTargets.length} 个已有 · ${blockedTargets.length} 个受阻`,
3599
+ run: () => {
3600
+ setBatchDraft((current) => current ? { ...current, showPlacementTargets: true } : current);
3601
+ setFocus(0);
3602
+ setScrollOffset(0);
3603
+ },
3604
+ }] : []),
3605
+ {
3606
+ label: batchDraft.action === 'install' && blockedTargets.length > 0
3607
+ ? `仅添加可执行的 ${addTargets.length} 个位置`
3608
+ : `查看批量${batchActionLabel(batchDraft.action)}确认`,
3609
+ description: batchDraft.action === 'install'
3610
+ ? `${addTargets.length} 个可添加 · ${existingTargets.length} 个已有 · ${blockedTargets.length} 个受阻`
3611
+ : `${batchDraft.selection === 'selected' ? `${batchDraft.selectedPluginIds.length} 个所选插件` : '当前项目全部保存目标'} · ${scopeLabel(batchDraft.scope)}`,
3612
+ disabled: batchDraft.selection === 'selected' && batchDraft.selectedPluginIds.length === 0
3613
+ ? '请至少选择一个插件'
3614
+ : batchDraft.action === 'install' && addTargets.length === 0
3615
+ ? '所选工具和位置没有可添加目标'
3616
+ : undefined,
3617
+ run: () => {
3618
+ if (invalidBatchSelection(batchDraft).length > 0) {
3619
+ setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
3620
+ return;
3621
+ }
3622
+ const intent = {
3623
+ kind: 'pluginWriteBatch',
3624
+ action: batchDraft.action,
3625
+ selection: batchDraft.action === 'install'
3626
+ ? { kind: 'targets', targets: addTargets.map(({ pluginId, platform, scope }) => ({
3627
+ pluginId, platform, scope,
3628
+ })) }
3629
+ : batchDraft.selection === 'selected'
3630
+ ? {
3631
+ kind: 'selected',
3632
+ pluginIds: [...new Set(batchDraft.selectedPluginIds)]
3633
+ .sort((left, right) => left.localeCompare(right, 'en')),
3634
+ }
3635
+ : { kind: 'currentProjectIntentRoots' },
3636
+ platform: addTargets[0]?.platform ?? batchDraft.platform,
3637
+ scope: addTargets[0]?.scope ?? batchDraft.scope,
3638
+ };
3639
+ beginReview(intent);
3640
+ setDirty(false);
3641
+ setFocus(0);
3642
+ setScrollOffset(0);
3643
+ },
3644
+ },
3645
+ ];
3646
+ }
3184
3647
  context = batchDraft?.selection === 'selected'
3185
- ? `${batchDraft.selectedPluginIds.length} 个插件`
3648
+ ? maintenanceMode
3649
+ ? `${maintenanceSelection.size}/${maintenanceTargets.length} 个位置`
3650
+ : `${batchDraft.selectedPluginIds.length} 个插件`
3186
3651
  : '保存的项目目标';
3187
- help = '↑↓ 移动 · Enter 选择 · Esc 返回修改';
3188
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
3652
+ help = batchDraft?.showPlacementTargets
3653
+ ? '↑↓ 查看全部位置 · Esc 返回位置选择'
3654
+ : '↑↓ 移动 · Enter 选择 · Esc 返回修改';
3655
+ const placementDetail = Boolean(batchDraft?.showPlacementTargets);
3656
+ const viewport = actionViewport(actions, placementDetail ? false : policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
3657
+ if (maintenanceMode) {
3658
+ const targetOffset = Math.min(viewport.offset, maintenanceTargetCount);
3659
+ const targetLimit = Math.max(0, Math.min(maintenanceTargetCount, viewport.offset + viewport.limit) - targetOffset);
3660
+ const actionOffset = Math.max(0, viewport.offset - maintenanceTargetCount);
3661
+ const actionLimit = Math.max(0, Math.min(actions.length - maintenanceTargetCount, viewport.offset + viewport.limit - maintenanceTargetCount) - actionOffset);
3662
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${maintenanceSelection.size} 个位置`, compact: true, children: targetLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(0, maintenanceTargetCount), focus: focus, kind: "toggle", compact: true, offset: targetOffset, limit: targetLimit }) : null }), actionLimit > 0 ? _jsx(SemanticRows, { actions: actions.slice(maintenanceTargetCount), focus: focus - maintenanceTargetCount, kind: "action", compact: policy.mode === 'compact', offset: actionOffset, limit: actionLimit }) : null] });
3663
+ }
3664
+ else if (!placementDetail && batchDraft?.action === 'install') {
3665
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(5), focus: focus - 5, kind: "action", compact: policy.mode === 'compact' }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5728\u54EA\u91CC\u4F7F\u7528" }), _jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: true }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u7528\u4E8E\u54EA\u4E2A\u5DE5\u5177" }), _jsx(SemanticRows, { actions: actions.slice(2, 5), focus: focus - 2, kind: "choice", compact: true })] });
3666
+ }
3667
+ else
3668
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: placementDetail ? 'action' : 'choice', compact: placementDetail ? false : policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
3189
3669
  }
3190
3670
  else if (route.name === 'pluginPreview' && plugin) {
3191
3671
  breadcrumb = `添加插件 / ${plugin.displayName}`;
@@ -3277,70 +3757,16 @@ export function ConsoleAppV4(props) {
3277
3757
  run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
3278
3758
  },
3279
3759
  ];
3280
- const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 2));
3281
- const dependencyGroups = ['direct', 'transitive'].map((relation) => ({
3282
- relation,
3283
- dependencies: (overview?.dependencies.filter((entry) => entry.relation === relation) ?? [])
3284
- .sort((left, right) => {
3285
- const priority = (entry) => {
3286
- const status = dependencyStatus(entry).status;
3287
- return status === 'error' ? 0 : status === 'warning' ? 1 : 2;
3288
- };
3289
- return priority(left) - priority(right);
3290
- }),
3291
- }));
3292
- const previewContentLines = size.rows - 6;
3293
- const previewActionLines = previewViewport.limit ?? actions.length;
3294
- const previewFixedStaticLines = 7 + (plugin.portableSkills && policy.mode !== 'compact' ? 5 : 0);
3295
- let dependencyLineBudget = Math.max(dependencyGroups.filter((group) => group.dependencies.length > 0).length, previewContentLines - previewActionLines - previewFixedStaticLines);
3296
- const visibleByRelation = new Map();
3297
- for (const group of dependencyGroups) {
3298
- if (group.dependencies.length === 0)
3299
- continue;
3300
- const firstItemLines = policy.mode === 'compact' || group.dependencies.length > 1 ? 1 : 2;
3301
- const hiddenSummaryLines = group.dependencies.length > 1 ? 1 : 0;
3302
- if (dependencyLineBudget >= firstItemLines + hiddenSummaryLines) {
3303
- visibleByRelation.set(group.relation, 1);
3304
- dependencyLineBudget -= firstItemLines + hiddenSummaryLines;
3305
- }
3306
- else {
3307
- visibleByRelation.set(group.relation, 0);
3308
- dependencyLineBudget -= 1;
3309
- }
3310
- }
3311
- for (const group of dependencyGroups) {
3312
- let visibleCount = visibleByRelation.get(group.relation) ?? 0;
3313
- while (dependencyLineBudget > 0 && visibleCount < group.dependencies.length) {
3314
- visibleCount += 1;
3315
- dependencyLineBudget -= 1;
3316
- }
3317
- visibleByRelation.set(group.relation, visibleCount);
3318
- }
3319
- const summaryRows = (relation) => {
3320
- const dependencies = dependencyGroups.find((group) => group.relation === relation).dependencies;
3321
- const visibleCount = policy.mode === 'compact' ? Math.min(1, dependencies.length)
3322
- : visibleByRelation.get(relation) ?? 0;
3323
- const visible = dependencies.slice(0, visibleCount);
3324
- const compactRows = policy.mode === 'compact' || dependencies.length > visible.length;
3325
- return [
3326
- ...visible.map((entry) => {
3327
- const state = dependencyStatus(entry);
3328
- return _jsx(StaticRow, { title: entry.displayName, description: dependencyRelationDescription(entry), meta: state.value, status: state.status, compact: compactRows }, entry.pluginId);
3329
- }),
3330
- ...(visible.length === 0 && dependencies.length > 0
3331
- ? [_jsx(StaticRow, { title: `${dependencies.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:count`)]
3332
- : dependencies.length > visible.length
3333
- ? [_jsx(StaticRow, { title: `另有 ${dependencies.length - visible.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:more`)]
3334
- : []),
3335
- ];
3336
- };
3337
- const compactDependencyRows = overview?.dependencies.slice(0, 2) ?? [];
3338
- content = hasDependencies
3339
- ? _jsxs(Box, { flexDirection: "column", children: [plugin.portableSkills && policy.mode !== 'compact' ? _jsx(SectionCard, { title: "\u53EF\u7528\u65B9\u5F0F", children: _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5" }) }) : null, _jsxs(SectionCard, { title: "\u5B89\u88C5\u5185\u5BB9", children: [policy.mode !== 'compact' ? _jsx(StaticRow, { title: plugin.displayName, description: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", status: "info" }) : null, plugin.portableSkills && policy.mode === 'compact' ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, compact: true }) : null, policy.mode === 'compact' ? compactDependencyRows.map((entry) => {
3340
- const state = dependencyStatus(entry);
3341
- return _jsx(StaticRow, { title: entry.displayName, meta: `${entry.relation === 'direct' ? '直接' : '间接'} · ${state.value}`, status: state.status, compact: true }, entry.pluginId);
3342
- }) : _jsxs(_Fragment, { children: [summaryRows('direct'), summaryRows('transitive')] }), policy.mode === 'compact' && (overview?.dependencies.length ?? 0) > compactDependencyRows.length ? _jsx(StaticRow, { title: `另有 ${(overview?.dependencies.length ?? 0) - compactDependencyRows.length} 个依赖`, status: "muted", compact: true }) : null, policy.mode !== 'compact' ? _jsx(StaticRow, { title: `${plugin.dependencySummary.totalCount + 1} 个插件将作为一个安装计划检查`, description: "\u4F9D\u8D56\u4ECE\u540C\u4E00\u4E2A\u6765\u6E90\u5FEB\u7167\u89E3\u6790\u3002" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' || previewViewport.limit !== undefined, offset: previewViewport.offset, limit: previewViewport.limit })] })
3343
- : _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", children: [_jsx(StaticRow, { title: plugin.supportedPlatforms.map(platformLabel).join(' · ') || '暂无可用目标平台' }), plugin.portableSkills ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: "\u53EF\u5B89\u88C5", description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5", compact: policy.mode === 'compact' }) : null] }), _jsx(SectionCard, { title: "\u4F7F\u7528\u8BF4\u660E", children: _jsx(StaticRow, { title: "\u5728 Agent \u4F1A\u8BDD\u4E2D\u63D0\u4F9B\u80FD\u529B", status: "success", description: "\u5B89\u88C5\u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539\u9879\u76EE\u6E90\u7801\u3002" }) }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: previewViewport.offset, limit: previewViewport.limit })] });
3760
+ const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 3));
3761
+ const dependencySummary = hasDependencies && plugin.dependencySummary
3762
+ ? `自动使用 ${plugin.dependencySummary.totalCount} 个所需插件;${plugin.dependencySummary.readyCount} 个可复用。`
3763
+ : '不需要其他插件。';
3764
+ const confirmationIssue = overview?.blockers[0]?.explanation
3765
+ ?? (plugin.capabilityUnsupported ? '当前 AW CLI 缺少此插件所需功能。'
3766
+ : plugin.sourceUnavailable ? '插件来源当前不可用。' : undefined);
3767
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u652F\u6301", children: _jsx(StaticRow, { title: plugin.supportedPlatforms.map(platformLabel).join(' · ') || '暂无可用目标平台', meta: plugin.portableSkills ? '也可安装通用 Skills' : undefined, status: plugin.supportedPlatforms.length > 0 || plugin.portableSkills ? 'success' : 'warning', compact: true }) }), _jsxs(SectionCard, { title: "\u6DFB\u52A0\u5185\u5BB9", children: [_jsx(StaticRow, { title: dependencySummary, status: confirmationIssue ? 'warning' : 'info', compact: true }), confirmationIssue
3768
+ ? _jsx(StaticRow, { title: "\u786E\u8BA4\u7684\u95EE\u9898", description: confirmationIssue, status: "warning", compact: true })
3769
+ : null] }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: previewViewport.offset, limit: previewViewport.limit })] });
3344
3770
  }
3345
3771
  else if (route.name === 'portableSkillSelect' && plugin && snapshot.portableDraft) {
3346
3772
  const draft = snapshot.portableDraft;
@@ -3512,8 +3938,8 @@ export function ConsoleAppV4(props) {
3512
3938
  context = direction === 'dependencies' ? '它需要的' : '谁在使用它';
3513
3939
  title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
3514
3940
  description = direction === 'dependencies'
3515
- ? '当前方向:它需要的。缩进表示上一层需要它。'
3516
- : '当前方向:谁在使用它。只显示已知的实际引用。';
3941
+ ? '每一层表示上面的插件需要下面的插件。'
3942
+ : '每一层表示下面的插件正在使用上面的插件;只显示已确认的引用。';
3517
3943
  const rowActions = visibleRows.map((row) => {
3518
3944
  const state = row.depth === 0
3519
3945
  ? { value: statusText(plugin), status: plugin.aggregateStatus === 'needsAttention'
@@ -3524,8 +3950,8 @@ export function ConsoleAppV4(props) {
3524
3950
  const compactBranch = policy.mode === 'compact' && row.depth > 2 ? '… └─ ' : branch;
3525
3951
  return {
3526
3952
  label: row.depth === 0
3527
- ? `根插件 · ${row.label}(当前插件)`
3528
- : `${compactBranch}${direction === 'dependencies' ? '必需依赖 · ' : '实际使用者 · '}${row.label}${row.sharedWithRowId ? ' ↗ [same]' : ''}`,
3953
+ ? `${row.label}(当前插件)`
3954
+ : `${compactBranch}${row.label}${row.sharedWithRowId ? '(共用,见上方)' : ''}`,
3529
3955
  description: row.hiddenDescendantCount > 0
3530
3956
  ? `另有 ${row.hiddenDescendantCount} 个${direction === 'dependencies' ? '所需插件' : '实际使用者'}`
3531
3957
  : row.depth === 0 ? '当前插件' : undefined,
@@ -3538,6 +3964,7 @@ export function ConsoleAppV4(props) {
3538
3964
  pluginId: plugin.pluginId,
3539
3965
  nodePluginId: row.pluginId,
3540
3966
  direction,
3967
+ nodePathPluginIds: row.pathPluginIds,
3541
3968
  });
3542
3969
  },
3543
3970
  };
@@ -3546,9 +3973,9 @@ export function ConsoleAppV4(props) {
3546
3973
  ? projection.scopeSummary.unknownConsumerPluginIds.map((pluginId) => {
3547
3974
  const declared = overview?.dependents.find((entry) => entry.pluginId === pluginId);
3548
3975
  return {
3549
- label: `未验证声明者 · ${declared?.displayName ?? pluginId}`,
3550
- description: '目录声明它依赖当前插件,但当前没有实际引用证据',
3551
- meta: declared?.aggregateStatus === 'notInstalled' ? '未安装' : '未验证',
3976
+ label: declared?.displayName ?? pluginId,
3977
+ description: '仅有依赖声明,尚未确认实际使用',
3978
+ meta: declared?.aggregateStatus === 'notInstalled' ? '仅声明 · 未安装' : '仅声明 · 未验证',
3552
3979
  status: 'muted',
3553
3980
  run: () => {
3554
3981
  persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
@@ -3582,47 +4009,133 @@ export function ConsoleAppV4(props) {
3582
4009
  const scopeText = projection.scopeSummary.references.length > 0
3583
4010
  ? projection.scopeSummary.references.map((entry) => `${platformLabel(entry.platform)} · ${scopeLabel(entry.scope)}`).join(';')
3584
4011
  : '当前加载范围;其他项目引用未检查';
3585
- const relationViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, showTechnical ? responsiveBudget(3, 6) : responsiveBudget(7, 11));
4012
+ const relationViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, showTechnical ? responsiveBudget(3, 6) : responsiveBudget(6, 10));
4013
+ const focusedGraphRow = focus < rowActions.length ? visibleRows[focus] : undefined;
3586
4014
  help = '↑↓ 选择 · → 展开 · ← 收起 · Enter 节点详情 · Esc 返回';
3587
4015
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `当前检查范围 · ${scopeText}`, status: "muted", compact: true }), projection.status.state === 'invalid'
3588
4016
  ? _jsx(Callout, { status: "error", title: "\u4F9D\u8D56\u5173\u7CFB\u65E0\u6548", description: projection.status.message }) : null, unverifiedConsumerActions.length > 0
3589
4017
  ? _jsx(StaticRow, { title: `未验证声明者 · ${unverifiedConsumerActions.length} 个`, description: "\u4EE5\u4E0B\u58F0\u660E\u8005\u4E0D\u8BA1\u5165\u5B9E\u9645\u4F7F\u7528\u8005", status: "muted", compact: true })
3590
- : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: relationViewport.offset, limit: relationViewport.limit }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: `${dependencyCount} 个依赖 · ${projection.scopeSummary.actualConsumerPluginIds.length} 个实际使用者`, meta: plugin.pluginId, compact: true }), _jsx(StaticRow, { title: `未验证声明者 · ${projection.scopeSummary.unknownConsumerPluginIds.length} 个`, compact: true })] }) : null] });
4018
+ : null, focusedGraphRow ? _jsx(StaticRow, { title: "\u7528\u9014", description: props.controller
4019
+ .getPluginOverview(focusedGraphRow.pluginId)?.description ?? '当前目录没有用途说明', compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: relationViewport.offset, limit: relationViewport.limit }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: `${dependencyCount} 个依赖 · ${projection.scopeSummary.actualConsumerPluginIds.length} 个实际使用者`, meta: plugin.pluginId, compact: true }), _jsx(StaticRow, { title: `未验证声明者 · ${projection.scopeSummary.unknownConsumerPluginIds.length} 个`, compact: true })] }) : null] });
3591
4020
  }
3592
4021
  }
3593
4022
  else if (route.name === 'pluginDependencyNode' && plugin) {
3594
4023
  const node = props.controller.getPluginOverview(route.nodePluginId);
3595
- const overview = dependencyOverview(plugin);
4024
+ const nodePathPluginIds = route.nodePathPluginIds ?? [plugin.pluginId, route.nodePluginId];
4025
+ const nodeProjection = buildDependencyGraphProjection({
4026
+ overviews: props.controller.listPluginDependencyOverviews(),
4027
+ rootPluginId: plugin.pluginId,
4028
+ direction: route.direction,
4029
+ expandedPluginIds: nodePathPluginIds,
4030
+ focusPluginId: route.nodePluginId,
4031
+ });
4032
+ const selectedRow = nodeProjection.rows.find((row) => row.pathPluginIds.join('\0') === nodePathPluginIds.join('\0'))
4033
+ ?? nodeProjection.rows.find((row) => row.pluginId === route.nodePluginId);
3596
4034
  const item = route.nodePluginId === plugin.pluginId ? undefined
3597
- : route.direction === 'dependencies'
3598
- ? overview?.dependencies.find((entry) => entry.pluginId === route.nodePluginId)
3599
- : overview?.dependents.find((entry) => entry.pluginId === route.nodePluginId);
3600
- const verifiedDependent = route.direction !== 'dependents'
3601
- || (item?.placementReferences?.length ?? 0) > 0;
4035
+ : (route.direction === 'dependencies'
4036
+ ? props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependencies
4037
+ : props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependents)
4038
+ ?.find((entry) => entry.pluginId === route.nodePluginId);
3602
4039
  const state = item ? dependencyStatus(item) : undefined;
4040
+ const relationship = selectedRow?.relationship
4041
+ ?? ((item?.placementReferences?.length ?? 0) > 0 ? 'unknown'
4042
+ : item ? 'declared' : undefined);
3603
4043
  breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId}`;
3604
4044
  context = route.direction === 'dependencies' ? '它需要的' : '谁在使用它';
3605
4045
  title = node?.displayName ?? route.nodePluginId;
3606
- description = route.nodePluginId === plugin.pluginId
4046
+ const pathPluginIds = selectedRow?.pathPluginIds ?? nodePathPluginIds;
4047
+ const intermediateNames = pathPluginIds.slice(1, -1).reverse().map((pluginId) => props.controller.getPluginOverview(pluginId)?.displayName ?? pluginId);
4048
+ const declaringPluginId = pathPluginIds.at(-2) ?? plugin.pluginId;
4049
+ const declaringPluginName = props.controller.getPluginOverview(declaringPluginId)?.displayName
4050
+ ?? declaringPluginId;
4051
+ description = relationship === 'root'
3607
4052
  ? '这是当前关系图的根插件。'
3608
- : route.direction === 'dependencies'
3609
- ? `${item?.requiredBy.map((entry) => entry.displayName).join('、') || plugin.displayName} 需要这个插件。`
3610
- : verifiedDependent
3611
- ? `${node?.displayName ?? route.nodePluginId} 的已安装位置实际引用当前插件。`
3612
- : `${item?.displayName ?? route.nodePluginId} 声明依赖当前插件,但当前没有实际引用证据。`;
3613
- actions = [
3614
- ...(route.nodePluginId !== plugin.pluginId ? [{
3615
- label: '以此节点查看关系',
3616
- description: '打开局部子树;面包屑保留来路',
3617
- run: () => go({
3618
- name: 'pluginDependencies',
3619
- pluginId: route.nodePluginId,
3620
- direction: route.direction,
3621
- }),
3622
- }] : []),
3623
- { label: '返回关系图', run: back },
3624
- ];
3625
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u8282\u70B9\u72B6\u6001", children: [_jsx(StaticRow, { title: node?.displayName ?? route.nodePluginId, meta: state?.value ?? (node ? statusText(node) : '未检查'), status: state?.status ?? 'muted', compact: true }), _jsx(StaticRow, { title: route.nodePluginId, description: "canonical pluginId", status: "muted", compact: true }), item?.placementReferences?.slice(0, responsiveBudget(1, 3)).map((reference) => _jsx(StaticRow, { title: `${platformLabel(reference.consumer.platform)} · ${scopeLabel(reference.consumer.scope)}`, description: `${reference.consumer.pluginId} → ${reference.dependency.pluginId}`, compact: true }, `${reference.consumer.pluginId}:${reference.consumer.platform}:${reference.consumer.scope}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4053
+ : relationship === 'directVerified'
4054
+ ? route.direction === 'dependencies'
4055
+ ? `${plugin.displayName} 的已安装位置直接使用这个插件。`
4056
+ : `${node?.displayName ?? route.nodePluginId} 的已安装位置直接使用当前插件。`
4057
+ : relationship === 'indirectVerified'
4058
+ ? route.direction === 'dependencies'
4059
+ ? `${plugin.displayName} 通过 ${intermediateNames.join('、')} 间接使用 ${node?.displayName ?? route.nodePluginId}。`
4060
+ : `${node?.displayName ?? route.nodePluginId} 通过 ${intermediateNames.join('、')} 间接使用 ${plugin.displayName}。`
4061
+ : relationship === 'unknown'
4062
+ ? '引用位置不能组成连续路径,不能确认这项间接使用。'
4063
+ : relationship === 'declared'
4064
+ ? route.direction === 'dependencies'
4065
+ ? `${declaringPluginName} 声明需要 ${node?.displayName ?? route.nodePluginId},但当前没有实际引用证据。`
4066
+ : `${node?.displayName ?? route.nodePluginId} 声明依赖当前插件,但当前没有实际引用证据。`
4067
+ : '当前数据不足,不能判断这项依赖关系。';
4068
+ const references = selectedRow?.edge?.placementReferences ?? item?.placementReferences ?? [];
4069
+ const referenceActions = references.map((reference) => {
4070
+ const consumerName = props.controller.getPluginOverview(reference.consumer.pluginId)?.displayName
4071
+ ?? reference.consumer.pluginId.split('/').at(-1);
4072
+ return {
4073
+ label: dependencyReferenceUsageLabel(reference, consumerName),
4074
+ meta: '已确认使用',
4075
+ };
4076
+ });
4077
+ if (route.view === 'references') {
4078
+ breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId} / 使用位置`;
4079
+ context = showTechnical ? '技术详情' : '使用位置';
4080
+ title = `已检查的使用位置 ${references.length} 个`;
4081
+ description = showTechnical
4082
+ ? '显示当前选择的完整引用字段。'
4083
+ : '这些位置有连续、已确认的实际引用。';
4084
+ actions = [...referenceActions, { label: '返回节点详情', run: back }];
4085
+ const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4086
+ const selectedReference = references[Math.min(focus, references.length - 1)];
4087
+ const referenceIdLines = (label, pluginId) => {
4088
+ const prefix = `${label} · `;
4089
+ return wrapConsoleText(pluginId, Math.max(1, size.columns - 10 - stringWidth(prefix)))
4090
+ .map((line) => `${prefix}${line}`);
4091
+ };
4092
+ const referenceDetailLines = selectedReference ? [
4093
+ ...referenceIdLines('consumer', selectedReference.consumer.pluginId),
4094
+ `consumer · platform=${platformLabel(selectedReference.consumer.platform)} · scope=${selectedReference.consumer.scope}`,
4095
+ `consumer · project=${selectedReference.consumer.project ?? 'none'}`,
4096
+ ...referenceIdLines('dependency', selectedReference.dependency.pluginId),
4097
+ `dependency · platform=${platformLabel(selectedReference.dependency.platform)} · scope=${selectedReference.dependency.scope}`,
4098
+ `dependency · project=${selectedReference.dependency.project ?? 'none'}`,
4099
+ ] : [];
4100
+ const referenceDetailPageRows = referenceDetailLines.length <= 6
4101
+ ? 6
4102
+ : responsiveBudget(4, 8);
4103
+ const referenceDetailOffset = Math.min(batchTargetOffset, Math.max(0, referenceDetailLines.length - 1));
4104
+ const visibleReferenceDetailLines = referenceDetailLines.slice(referenceDetailOffset, referenceDetailOffset + referenceDetailPageRows);
4105
+ referenceDetailPagination = showTechnical ? {
4106
+ total: referenceDetailLines.length,
4107
+ pageRows: referenceDetailPageRows,
4108
+ } : undefined;
4109
+ help = showTechnical
4110
+ ? 'PgUp/PgDn 查看当前位置详情 · ↑↓ 查看全部位置 · d 隐藏技术详情 · Esc 返回节点详情'
4111
+ : '↑↓ 查看全部位置 · d 技术详情 · Enter 返回节点详情 · Esc 返回节点详情';
4112
+ content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? null
4113
+ : _jsx(StaticRow, { title: "\u8BC1\u636E\u8FB9\u754C", description: "\u8FD9\u91CC\u53EA\u663E\u793A\u5DF2\u786E\u8BA4\u7684\u5B9E\u9645\u5F15\u7528\uFF1B\u63D2\u4EF6\u72B6\u6001\u4E0D\u4F5C\u4E3A\u5F15\u7528", status: "muted", compact: true }), showTechnical && referenceDetailOffset > 0
4114
+ ? _jsx(StaticRow, { title: `↑ 前面还有 ${referenceDetailOffset} 行`, status: "muted", compact: true }) : null, showTechnical ? visibleReferenceDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${referenceDetailOffset + index}:${line}`)) : null, showTechnical && referenceDetailOffset + visibleReferenceDetailLines.length < referenceDetailLines.length
4115
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${referenceDetailLines.length - referenceDetailOffset - visibleReferenceDetailLines.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: referenceViewport.offset, limit: referenceViewport.limit })] });
4116
+ }
4117
+ else {
4118
+ actions = [
4119
+ ...(references.length > 0 ? [{
4120
+ label: `查看全部位置(${references.length})`,
4121
+ description: '查看每个已检查位置及证据状态',
4122
+ run: () => go({ ...route, view: 'references' }),
4123
+ }] : []),
4124
+ ...(route.nodePluginId !== plugin.pluginId ? [{
4125
+ label: '以此节点查看关系',
4126
+ description: '打开局部子树;面包屑保留来路',
4127
+ run: () => go({
4128
+ name: 'pluginDependencies',
4129
+ pluginId: route.nodePluginId,
4130
+ direction: route.direction,
4131
+ }),
4132
+ }] : []),
4133
+ { label: '返回关系图', run: back },
4134
+ ];
4135
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u8282\u70B9\u72B6\u6001", children: [_jsx(StaticRow, { title: node?.displayName ?? route.nodePluginId, meta: state?.value ?? (node ? statusText(node) : '未检查'), status: state?.status ?? 'muted', compact: true }), _jsx(StaticRow, { title: "\u7528\u9014", description: node?.description ?? '当前目录没有用途说明', compact: true }), _jsx(StaticRow, { title: "\u4E3A\u4F55\u9700\u8981", description: description, compact: true }), _jsx(StaticRow, { title: "\u5F71\u54CD", description: state?.value ?? (node ? statusText(node) : '尚未检查当前状态'), status: state?.status ?? 'muted', compact: true }), references.length > 0
4136
+ ? _jsx(StaticRow, { title: `已检查的使用位置 ${references.length} 个`, description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u4F4D\u7F6E\u201D\u53EF\u9010\u9879\u67E5\u770B", compact: true })
4137
+ : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "pluginId", meta: route.nodePluginId, compact: true }), _jsx(StaticRow, { title: "relationship", meta: relationship ?? 'unknown', compact: true })] }) : null] });
4138
+ }
3626
4139
  }
3627
4140
  else if (route.name === 'installPlacement' && plugin) {
3628
4141
  breadcrumb = `${plugin.displayName} / 添加`;
@@ -3812,6 +4325,14 @@ export function ConsoleAppV4(props) {
3812
4325
  const allTaskCandidates = props.controller.listPluginWriteTaskCandidates(route.goal);
3813
4326
  const currentTargetKeys = new Set(allTaskCandidates.map(pluginWriteTaskTargetKey));
3814
4327
  const missingTargetKeys = route.targetKeys?.filter((targetKey) => !currentTargetKeys.has(targetKey)) ?? [];
4328
+ const recommendedPluginIds = route.recommendedPluginIds
4329
+ ? new Set(route.recommendedPluginIds) : undefined;
4330
+ const addedTargetKeys = retainedTargetKeys && recommendedPluginIds
4331
+ ? allTaskCandidates.filter((candidate) => recommendedPluginIds.has(candidate.target.pluginId)
4332
+ && !retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate)))
4333
+ .map(pluginWriteTaskTargetKey)
4334
+ : [];
4335
+ const targetSetChanged = missingTargetKeys.length > 0 || addedTargetKeys.length > 0;
3815
4336
  const candidates = allTaskCandidates
3816
4337
  .filter((candidate) => retainedTargetKeys
3817
4338
  ? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
@@ -3947,8 +4468,8 @@ export function ConsoleAppV4(props) {
3947
4468
  })).values()];
3948
4469
  const taskLabel = pluginWriteTaskLabel(route.goal);
3949
4470
  const startTaskReview = (executionPolicy) => {
3950
- if (missingTargetKeys.length > 0) {
3951
- setActionError(`${missingTargetKeys.length} 个冻结位置已失效或无需处理。请返回调整后重新开始。`);
4471
+ if (targetSetChanged) {
4472
+ setActionError('冻结的精确位置已变化。请返回调整后重新开始。');
3952
4473
  return;
3953
4474
  }
3954
4475
  props.controller.startPluginWriteTaskReview({
@@ -3974,14 +4495,14 @@ export function ConsoleAppV4(props) {
3974
4495
  ? 'PgUp/PgDn 查看全部位置 · ↑↓ 移动 · Enter 选择 · Esc 返回'
3975
4496
  : '↑↓ 移动 · Enter 选择 · Esc 返回';
3976
4497
  actions = [
3977
- ...(missingTargetKeys.length === 0 && !versionEvidenceChanged
4498
+ ...(!targetSetChanged && !versionEvidenceChanged
3978
4499
  && blockedCandidates.length === 0 && executable.length > 0 ? [{
3979
4500
  label: `确认${taskLabel} ${executable.length} 个位置…`,
3980
4501
  description: '进入统一影响确认;执行前再次检查当前证据',
3981
4502
  status: 'success',
3982
4503
  run: () => startTaskReview('all'),
3983
4504
  }] : []),
3984
- ...(missingTargetKeys.length === 0 && !versionEvidenceChanged
4505
+ ...(!targetSetChanged && !versionEvidenceChanged
3985
4506
  && blockedCandidates.length > 0 && executable.length > 0 ? [{
3986
4507
  label: `先处理可执行的 ${executable.length} 个位置…`,
3987
4508
  description: `${blockedCandidates.length} 个受阻位置保留,不会无声跳过`,
@@ -4005,7 +4526,7 @@ export function ConsoleAppV4(props) {
4005
4526
  setScrollOffset(0);
4006
4527
  },
4007
4528
  }] : []),
4008
- { label: '返回插件详情', run: back },
4529
+ { label: route.recommendedPluginIds ? '返回调整选择' : '返回插件详情', run: back },
4009
4530
  ];
4010
4531
  const taskTargetLimit = policy.mode === 'compact' ? 3 : 6;
4011
4532
  const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
@@ -4025,7 +4546,7 @@ export function ConsoleAppV4(props) {
4025
4546
  : _jsx(StaticRow, { title: "\u5F53\u524D\u6CA1\u6709\u53EF\u6267\u884C\u76EE\u6807", status: "muted", compact: true }), candidates.length > taskTargetOffset + targetRows.length
4026
4547
  ? _jsx(StaticRow, { title: `↓ 后面还有 ${candidates.length - taskTargetOffset - targetRows.length} 个位置`, status: "muted", compact: true }) : null] }), blockedCandidates.length > 0 ? _jsx(Callout, { status: "warning", title: `${blockedCandidates.length} 个位置需要先处理或决定`, description: executable.length > 0
4027
4548
  ? '可以先处理可执行项,或进入下方对应入口处理受阻位置。'
4028
- : '请进入下方对应入口处理;返回后会重新读取当前证据。', compact: policy.mode === 'compact' }) : null, missingTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${missingTargetKeys.length} 个冻结位置已失效或无需处理`, description: "\u4E0D\u4F1A\u628A\u8FD9\u4E9B\u4F4D\u7F6E\u5F53\u4F5C\u5DF2\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u7F29\u5C0F\u672C\u6B21\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, versionEvidenceChanged ? _jsx(Callout, { status: "warning", title: "\u63D2\u4EF6\u76EE\u6807\u7248\u672C\u5DF2\u53D8\u5316", description: "\u4E0D\u4F1A\u6CBF\u7528\u6765\u6E90\u542F\u7528\u524D\u7684\u7248\u672C\u8BC1\u636E\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u6267\u884C\u65B0\u7248\u672C\u3002", compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: taskViewport.offset, limit: taskViewport.limit })] });
4549
+ : '请进入下方对应入口处理;返回后会重新读取当前证据。', compact: policy.mode === 'compact' }) : null, missingTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${missingTargetKeys.length} 个冻结位置已失效或无需处理`, description: "\u4E0D\u4F1A\u628A\u8FD9\u4E9B\u4F4D\u7F6E\u5F53\u4F5C\u5DF2\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u7F29\u5C0F\u672C\u6B21\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, addedTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${addedTargetKeys.length} 个新位置需要重新确认`, description: "\u4E0D\u4F1A\u628A\u5237\u65B0\u540E\u65B0\u589E\u7684\u4F4D\u7F6E\u81EA\u52A8\u52A0\u5165\u51BB\u7ED3\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, versionEvidenceChanged ? _jsx(Callout, { status: "warning", title: "\u63D2\u4EF6\u76EE\u6807\u7248\u672C\u5DF2\u53D8\u5316", description: "\u4E0D\u4F1A\u6CBF\u7528\u6765\u6E90\u542F\u7528\u524D\u7684\u7248\u672C\u8BC1\u636E\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u6267\u884C\u65B0\u7248\u672C\u3002", compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: taskViewport.offset, limit: taskViewport.limit })] });
4029
4550
  }
4030
4551
  else if (route.name === 'pluginDetail' && plugin) {
4031
4552
  breadcrumb = `我的插件 / ${plugin.displayName}`;
@@ -4079,8 +4600,14 @@ export function ConsoleAppV4(props) {
4079
4600
  setScrollOffset(0);
4080
4601
  },
4081
4602
  } : undefined;
4082
- const issueForPlugin = props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId || candidate.rootEntity?.pluginId === plugin.pluginId);
4083
- const exceptionalAction = plugin.aggregateStatus === 'unavailable'
4603
+ const issuesForPlugin = props.controller.listUserIssues().filter((candidate) => issueMatchesTarget(candidate, plugin.pluginId));
4604
+ const configIssue = issuesForPlugin.find((candidate) => candidate.group === 'config');
4605
+ const exceptionalAction = configIssue ? {
4606
+ label: '查看处理建议',
4607
+ description: '当前插件设置问题需要先完成或修正设置',
4608
+ status: 'warning',
4609
+ run: () => go({ name: 'issueDetail', issueId: configIssue.issueId }),
4610
+ } : plugin.aggregateStatus === 'unavailable'
4084
4611
  ? plugin.capabilityUnsupported && !plugin.sourceUnavailable ? {
4085
4612
  label: '需要升级 AW CLI',
4086
4613
  description: '查看缺少的功能和安全恢复方向',
@@ -4096,38 +4623,54 @@ export function ConsoleAppV4(props) {
4096
4623
  label: '查看处理建议',
4097
4624
  description: '当前问题需要先确认原因或处理方式',
4098
4625
  status: 'warning',
4099
- run: () => issueForPlugin
4100
- ? go({ name: 'issueDetail', issueId: issueForPlugin.issueId })
4101
- : go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
4626
+ run: () => issuesForPlugin.length === 1
4627
+ ? go({ name: 'issueDetail', issueId: issuesForPlugin[0].issueId })
4628
+ : issuesForPlugin.length > 1
4629
+ ? go({ name: 'issueList' })
4630
+ : go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
4102
4631
  } : undefined;
4103
- const primary = exceptionalAction ?? restoreAction ?? updateAction
4104
- ?? settingsAction ?? {
4632
+ const aboutAction = {
4633
+ actionId: 'about',
4105
4634
  label: '使用说明',
4106
4635
  description: '查看用途、支持平台和来源',
4107
4636
  run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
4108
4637
  };
4109
- actions = [
4110
- primary,
4111
- ...(settingsAction && primary !== settingsAction ? [settingsAction] : [{
4112
- label: '使用说明',
4113
- description: '查看用途、支持平台和来源',
4114
- run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
4115
- }]),
4638
+ const primary = exceptionalAction ?? restoreAction ?? updateAction
4639
+ ?? settingsAction ?? aboutAction;
4640
+ actions = dedupeActions([
4641
+ { ...primary, actionId: primary === aboutAction ? 'about'
4642
+ : primary === settingsAction ? 'settings'
4643
+ : primary === updateAction ? 'update'
4644
+ : primary === restoreAction ? 'restore' : 'exceptional' },
4645
+ ...(settingsAction && primary !== settingsAction
4646
+ ? [{ ...settingsAction, actionId: 'settings' }] : [aboutAction]),
4116
4647
  {
4648
+ actionId: 'more',
4117
4649
  label: '更多操作…',
4118
- description: '平台维护、依赖、通用 Skills、来源和诊断',
4650
+ description: settingsAction && primary !== settingsAction
4651
+ ? '平台维护、依赖、使用说明、通用 Skills、来源和诊断'
4652
+ : '平台维护、依赖、通用 Skills、来源和诊断',
4119
4653
  run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
4120
4654
  },
4121
- ];
4655
+ ]);
4122
4656
  const dependencyOverviewForDetail = dependencyOverview(plugin);
4123
- const dependencyIssue = dependencyOverviewForDetail?.dependencies.find((entry) => entry.aggregateStatus !== 'ready');
4124
- const dependencyDetailText = dependencyIssue
4125
- ? `${dependencyIssue.aggregateStatus === 'notInstalled' ? '缺少' : '异常'} ${dependencyIssue.displayName},${plugin.displayName} 暂时不可用。恢复会一并处理。`
4126
- : dependencyOverviewForDetail && dependencyOverviewForDetail.summary.totalCount > 0
4127
- ? `自动使用 ${dependencyOverviewForDetail.summary.totalCount} 个所需插件,全部就绪。查看关系…`
4128
- : undefined;
4129
- const detailViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
4130
- content = _jsxs(Box, { flexDirection: "column", children: [updateDescription ? _jsx(Callout, { status: "warning", title: statusText(plugin), description: updateDescription }) : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: dependencyIssue ? 'warning' : 'success', compact: true }) : null, _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: detailViewport.offset, limit: detailViewport.limit }) })] });
4657
+ const dependencyDetailText = dependencyOverviewForDetail
4658
+ && dependencyOverviewForDetail.summary.totalCount > 0
4659
+ ? `使用 ${dependencyOverviewForDetail.summary.totalCount} 个所需插件;尚未确认它们在这些位置的实际使用状态。`
4660
+ : undefined;
4661
+ const placementConclusions = plugin.installedPlacements.map((placement) => ({
4662
+ platform: placement.platform,
4663
+ ...pluginPlacementConclusion(placement),
4664
+ }));
4665
+ const hasUnavailablePlacement = placementConclusions.some((placement) => placement.status === 'error');
4666
+ const hasUncertainPlacement = placementConclusions.some((placement) => placement.status === 'warning');
4667
+ const confirmedAvailablePlacements = placementConclusions.filter((placement) => placement.status === 'success');
4668
+ const detailViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(Math.min(placementConclusions.length, 2) + 4));
4669
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: hasUnavailablePlacement ? 'error'
4670
+ : hasUncertainPlacement ? 'warning'
4671
+ : confirmedAvailablePlacements.length > 0 ? 'success' : 'info', title: hasUnavailablePlacement ? '部分使用位置暂时不可用'
4672
+ : hasUncertainPlacement ? '部分使用位置需要处理'
4673
+ : confirmedAvailablePlacements.length > 0 ? '已确认的使用位置可用' : '尚未确认使用位置', children: placementConclusions.map((placement) => _jsx(StaticRow, { title: platformLabel(placement.platform), meta: placement.text, status: placement.status, compact: true }, placement.platform)) }), updateDescription ? _jsx(Callout, { status: "warning", title: "\u6709\u53EF\u7528\u66F4\u65B0", description: updateDescription }) : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: "muted", compact: true }) : null, _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: detailViewport.offset, limit: detailViewport.limit }) })] });
4131
4674
  }
4132
4675
  else if (route.name === 'pluginMore' && plugin) {
4133
4676
  breadcrumb = `我的插件 / ${plugin.displayName} / 更多操作`;
@@ -4323,17 +4866,18 @@ export function ConsoleAppV4(props) {
4323
4866
  };
4324
4867
  if (action.id === 'sync')
4325
4868
  return {
4326
- label: '同步已保存的使用位置…',
4869
+ label: `同步${scopeLabel(action.scope)}的使用位置…`,
4327
4870
  description: placement?.state === 'installed'
4328
- ? placement.recovery?.find((entry) => entry.command === 'sync')?.description
4871
+ ? (placement.scopedPlacements?.find((entry) => entry.scope === action.scope)?.recovery
4872
+ ?? placement.recovery)?.find((entry) => entry.command === 'sync')?.description
4329
4873
  ?? '进入现有 Review、进度和结果链修复 needsSync 或 drift'
4330
4874
  : undefined,
4331
4875
  run: () => placement?.state === 'installed'
4332
- ? beginReview({ kind: 'pluginWrite', action: 'sync', pluginId: plugin.pluginId, platform: route.platform, scope: placement.scope })
4876
+ ? beginReview({ kind: 'pluginWrite', action: 'sync', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope })
4333
4877
  : undefined,
4334
4878
  };
4335
4879
  if (action.id === 'reinstall')
4336
- return { label: '重新安装…', description: '保留现有设置并重新生成受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
4880
+ return { label: `重新安装${scopeLabel(action.scope)}位置…`, description: '保留现有设置并重新生成受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
4337
4881
  if (action.id === 'remove')
4338
4882
  return { label: `从${scopeLabel(action.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, action.platform, action.scope) };
4339
4883
  if (action.id === 'cleanup')
@@ -4380,7 +4924,7 @@ export function ConsoleAppV4(props) {
4380
4924
  ...(restrictedForceCleanup ? [restrictedForceCleanup] : []),
4381
4925
  ];
4382
4926
  breadcrumb = `${plugin.displayName} / ${platformLabel(route.platform)}`;
4383
- const installedPlacementRows = placement?.state === 'installed' ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: placement.desiredScopes?.map(scopeLabel).join('、') || scopeLabel(placement.scope), compact: true }), _jsx(StaticRow, { title: "\u72B6\u6001", meta: placement.actualState === 'ready' ? '可用'
4927
+ const installedPlacementRows = placement?.state === 'installed' ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5DF2\u4FDD\u5B58\u4F4D\u7F6E", meta: placement.desiredScopes?.map(scopeLabel).join('、') || '无', compact: true }), _jsx(StaticRow, { title: "\u5F53\u524D\u4F4D\u7F6E", meta: placement.actualScopes?.map(scopeLabel).join('、') || '未启用', compact: true }), _jsx(StaticRow, { title: "\u72B6\u6001", meta: placement.actualState === 'ready' ? '可用'
4384
4928
  : placement.actualState === 'needsSync' ? '需要同步'
4385
4929
  : placement.actualState === 'blocked' ? '已阻断'
4386
4930
  : placement.health === 'incomplete' ? '需要处理' : '暂时不可用', status: placement.actualState === 'ready' ? 'success' : placement.actualState === 'blocked' ? 'error' : 'warning', compact: true }), placement.health !== 'ready'
@@ -4697,12 +5241,24 @@ export function ConsoleAppV4(props) {
4697
5241
  description = route.layer.scope === 'project'
4698
5242
  ? '修改将保存到当前项目。'
4699
5243
  : '修改将保存为个人默认,并影响个人的所有项目;项目设置仍可覆盖。';
4700
- actions = configSaveImpact ? [
5244
+ actions = configSaveImpactError ? [
5245
+ {
5246
+ label: '重新检查影响',
5247
+ description: '重新读取当前安装位置;检查成功前不能保存',
5248
+ run: () => setConfigSaveImpactAttempt((value) => value + 1),
5249
+ },
5250
+ { label: '返回修改', run: back },
5251
+ ] : configSaveImpact ? [
4701
5252
  hasConsumers ? {
4702
5253
  label: '保存并生效',
4703
5254
  description: `保存设置,并重新应用:${targetSummary}`,
4704
5255
  status: 'success',
4705
5256
  run: () => beginConfigSave(true),
5257
+ } : configSaveImpact.impactState === 'unknown' ? {
5258
+ label: '仅保存设置(应用范围尚未确认)',
5259
+ description: '保存所选设置层;不扩大到未确认的安装位置',
5260
+ status: 'warning',
5261
+ run: () => beginConfigSave(false),
4706
5262
  } : {
4707
5263
  label: '保存设置',
4708
5264
  description: '当前修改没有需要重新应用的已安装目标',
@@ -4713,6 +5269,10 @@ export function ConsoleAppV4(props) {
4713
5269
  label: '只保存,稍后应用',
4714
5270
  description: '保存所选设置层,不重新应用当前目标',
4715
5271
  run: () => beginConfigSave(false),
5272
+ }] : configSaveImpact.impactState === 'unknown' ? [{
5273
+ label: '重新检查影响',
5274
+ description: '再次读取当前安装位置和配置消费者',
5275
+ run: () => setConfigSaveImpactAttempt((value) => value + 1),
4716
5276
  }] : []),
4717
5277
  { label: '返回修改', run: back },
4718
5278
  ] : [{ label: '返回修改', run: back }];
@@ -4720,9 +5280,11 @@ export function ConsoleAppV4(props) {
4720
5280
  ? _jsx(ProgressRow, { title: "\u5206\u6790\u5B9E\u9645\u914D\u7F6E\u6D88\u8D39\u8005", state: "active" })
4721
5281
  : hasConsumers
4722
5282
  ? affectedTargets.map((target) => _jsx(StaticRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, meta: "\u9700\u8981\u91CD\u65B0\u5E94\u7528", status: "warning", compact: true }, `${target.platform}:${target.scope}`))
4723
- : _jsx(StaticRow, { title: configSaveImpactError || configSaveImpact?.impactState === 'unknown'
4724
- ? '无法确认需要重新应用的目标;本次仅保存设置'
4725
- : '没有需要重新应用的已安装目标', status: configSaveImpactError || configSaveImpact?.impactState === 'unknown' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u5176\u4ED6\u8BBE\u7F6E\u5C42\u4FDD\u6301\u4E0D\u53D8", status: "success", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
5283
+ : _jsx(StaticRow, { title: configSaveImpactError
5284
+ ? '影响检查失败;设置尚未保存'
5285
+ : configSaveImpact?.impactState === 'unknown'
5286
+ ? '应用范围尚未确认;可以仅保存并稍后重查'
5287
+ : '没有需要重新应用的已安装目标', status: configSaveImpactError || configSaveImpact?.impactState === 'unknown' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u5176\u4ED6\u8BBE\u7F6E\u5C42\u4FDD\u6301\u4E0D\u53D8", status: "success", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4726
5288
  }
4727
5289
  else if (route.name === 'configFieldAction' && plugin && activeField) {
4728
5290
  const layer = configLayerLabel(route.layer);
@@ -5251,7 +5813,9 @@ export function ConsoleAppV4(props) {
5251
5813
  const overview = props.controller.getSourceOverview(source.id);
5252
5814
  const generation = props.controller.getCatalogGeneration(source.id);
5253
5815
  const freshness = props.controller.getCatalogFreshness(source.id);
5254
- const health = overview.health?.state ?? source.state;
5816
+ const health = generation?.state !== undefined && generation.state !== 'ready'
5817
+ ? 'unavailable'
5818
+ : overview.health?.state ?? source.state;
5255
5819
  const presentation = catalogSourceListPresentation({
5256
5820
  freshness,
5257
5821
  activeVersion: generation?.active?.catalogVersion,
@@ -5302,16 +5866,16 @@ export function ConsoleAppV4(props) {
5302
5866
  : freshness?.state === 'incompatible'
5303
5867
  ? generationState === 'repairRequired'
5304
5868
  ? `待检查版本需要升级 AW 或修改来源后才能使用;损坏的当前启用版本仍可按旧版本精确修复。`
5305
- : activeVersion
5869
+ : activeVersion && generationState === 'ready'
5306
5870
  ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
5307
5871
  : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;这个来源没有可用目录版本。`
5308
- : freshness?.state === 'checkFailed' && activeVersion
5872
+ : freshness?.state === 'checkFailed' && activeVersion && generationState === 'ready'
5309
5873
  ? `更新检查失败,但当前目录版本 ${activeVersion} 仍可浏览和使用。`
5310
- : freshness?.state === 'updateAvailable'
5874
+ : freshness?.state === 'updateAvailable' && generationState === 'ready'
5311
5875
  ? `当前使用 ${activeVersion ?? '未启用'};待检查版本 ${candidateVersion ?? '未知'} 可更新。`
5312
- : freshness?.state === 'localChangesAvailable'
5876
+ : freshness?.state === 'localChangesAvailable' && generationState === 'ready'
5313
5877
  ? '本地插件目录内容已变化;确认前仍使用当前启用版本。'
5314
- : activeVersion ? `当前使用目录版本 ${activeVersion}。`
5878
+ : activeVersion && generationState === 'ready' ? `当前使用目录版本 ${activeVersion}。`
5315
5879
  : generationState === 'stateInvalid' ? '目录版本状态无效。确认恢复后可以重新启用这个来源。'
5316
5880
  : generationState === 'repairRequired' ? combinedRepairAndMismatch
5317
5881
  ? '当前目录版本损坏,且来源配置已变化。验证待检查版本后可精确修复同一版本,或明确切换到新版本。'
@@ -5384,7 +5948,7 @@ export function ConsoleAppV4(props) {
5384
5948
  run: () => props.controller.recheckCatalogSource(route.sourceId),
5385
5949
  }];
5386
5950
  const managementActions = [
5387
- { label: '编辑来源…', description: '集中修改地址、引用、子目录和自动检查', run: () => openSourceEdit(route.sourceId) },
5951
+ { label: '来源设置', description: '下载方式、版本、自动检查和高级地址目录', run: () => openSourceEdit(route.sourceId) },
5388
5952
  ...(generationState === 'incompatible'
5389
5953
  || (freshness?.state === 'incompatible' && generationState !== 'ready') ? [] : [{
5390
5954
  label: generationState === 'ready' ? '设为主来源…' : '启用并设为主来源…',
@@ -5422,18 +5986,43 @@ export function ConsoleAppV4(props) {
5422
5986
  run: () => props.controller.fullValidateCatalogSource(route.sourceId),
5423
5987
  },
5424
5988
  ];
5989
+ const sourcePrimaryAction = generationAction.length === 0 ? [] : [{
5990
+ ...generationAction[0],
5991
+ ...(generationState === 'repairRequired' || generationState === 'stateInvalid'
5992
+ || generationState === 'unavailable'
5993
+ ? {
5994
+ label: '恢复来源',
5995
+ description: generationState === 'repairRequired'
5996
+ ? '优先取得当前版本并精确修复;不会自动切换版本'
5997
+ : '取得并验证可用目录后再启用',
5998
+ status: 'warning',
5999
+ }
6000
+ : {}),
6001
+ }];
5425
6002
  actions = [
5426
- ...generationAction.slice(0, 1),
6003
+ ...sourcePrimaryAction,
5427
6004
  managementActions[0],
5428
6005
  {
5429
- label: '更多处理方式…',
5430
- description: '设为主来源、移除、精确修复、替换、重新检查和完整验证',
6006
+ label: '更多操作…',
6007
+ description: '替换候选、主来源、移除、检查和技术详情',
5431
6008
  run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
5432
6009
  },
5433
6010
  ];
5434
6011
  const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
5435
- const freshnessLabel = catalogSourceFreshnessLabel(freshness);
5436
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5F53\u524D\u76EE\u5F55\u7248\u672C", status: generationState === 'incompatible' ? 'error' : activeVersion ? 'success' : 'warning', compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: activeVersion ? `版本 ${activeVersion}` : '未启用', meta: activeVersion ? generation?.active?.digestShort : catalogGenerationStateLabel(generationState), status: generationState === 'incompatible' ? 'error' : undefined, compact: true }), candidateVersion ? _jsx(StaticRow, { title: `待检查版本 ${candidateVersion}`, meta: freshnessLabel, status: freshness?.state === 'updateAvailable' || freshness?.state === 'localChangesAvailable' ? 'warning' : undefined, compact: true }) : _jsx(StaticRow, { title: freshnessLabel, status: freshness?.state === 'checkFailed' ? 'warning' : undefined, compact: true })] }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用', compact: true }), _jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录', compact: true }), _jsx(StaticRow, { title: "\u81EA\u52A8\u68C0\u67E5\u66F4\u65B0", meta: detail?.autoCheck === false ? '关闭' : '开启', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
6012
+ const freshnessLabel = freshness?.state === 'checkFailed' && generationState !== 'ready'
6013
+ ? '检查失败' : catalogSourceFreshnessLabel(freshness);
6014
+ const sourceStateLabel = generationState === 'ready' ? '来源可用'
6015
+ : generationState === 'repairRequired' ? '来源目录损坏,当前无法使用'
6016
+ : generationState === 'incompatible' ? '当前 AW 无法使用这个目录版本'
6017
+ : generationState === 'configMismatch' ? '来源设置与当前目录不一致'
6018
+ : generationState === 'stateInvalid' ? '来源状态需要恢复'
6019
+ : '来源尚未启用';
6020
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { title: sourceStateLabel, status: generationState === 'ready' ? 'success'
6021
+ : generationState === 'incompatible' ? 'error' : 'warning', description: activeVersion ? `当前版本 ${activeVersion}` : '当前没有可用目录版本', children: [_jsx(StaticRow, { title: "\u5F71\u54CD", meta: generationState === 'ready'
6022
+ ? freshness?.state === 'checkFailed' ? '更新检查失败;当前版本继续可用'
6023
+ : freshness?.state === 'updateAvailable' ? `可选择更新到 ${candidateVersion ?? '待检查版本'}`
6024
+ : '插件浏览和现有使用不受阻止'
6025
+ : '当前不能从这个来源浏览或恢复插件', 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 })] });
5437
6026
  }
5438
6027
  else if (route.name === 'sourceMore') {
5439
6028
  const overview = props.controller.getSourceOverview(route.sourceId);
@@ -5618,32 +6207,37 @@ export function ConsoleAppV4(props) {
5618
6207
  const draft = activeDraft;
5619
6208
  breadcrumb = `插件来源 / ${route.sourceId} / 编辑`;
5620
6209
  title = draft?.mode === 'add' ? `检查新来源 ${draft.sourceId}` : `编辑 ${route.sourceId}`;
5621
- description = '地址、引用、子目录和自动检查组成一个草稿;保存前统一 Review。';
6210
+ description = '在一个草稿中修改来源设置,最后查看修改汇总。';
5622
6211
  const locationSummary = draft
5623
6212
  ? projectCatalogSourceLocation(draft.location)
5624
6213
  : detail?.locationSummary ?? '不可用';
5625
- const setOfficialLocation = (location) => {
5626
- const current = draft ?? (() => {
5627
- const source = props.controller.getConfiguredSource(route.sourceId);
5628
- if (!source)
5629
- return undefined;
5630
- return sourceDraftFromSource(source, `source-update:${route.sourceId}:${Date.now()}`);
5631
- })();
5632
- if (!current || current.type !== 'git' || current.location === location)
5633
- return;
5634
- const next = { ...current, location };
5635
- setSourceDraft(next);
5636
- setDirty(true);
5637
- props.controller.replace({ name: 'sourceEdit', sourceId: route.sourceId, owner: next.owner });
5638
- };
6214
+ const configuredSource = props.controller.getConfiguredSource(route.sourceId);
6215
+ const currentLocation = draft?.location
6216
+ ?? (configuredSource?.type === 'git' ? configuredSource.url : undefined);
6217
+ const officialTransport = currentLocation === OFFICIAL_CATALOG_SOURCE_URL ? 'HTTPS'
6218
+ : currentLocation === OFFICIAL_CATALOG_SOURCE_SSH_URL ? 'SSH' : undefined;
6219
+ const catalogSubdir = draft ? draft.subdir : detail?.subdir;
5639
6220
  actions = [
5640
- ...(route.sourceId === 'official' && (draft?.type === 'git' || !draft && detail?.type === 'git') ? [
5641
- { label: 'HTTPS', description: '使用系统 Git credential helper', selected: locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_URL), run: () => setOfficialLocation(OFFICIAL_CATALOG_SOURCE_URL) },
5642
- { label: 'SSH', description: '使用本机 SSH key;Git 连接使用 BatchMode', selected: locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_SSH_URL), run: () => setOfficialLocation(OFFICIAL_CATALOG_SOURCE_SSH_URL) },
5643
- ] : [{ label: draft?.type === 'path' || detail?.type === 'path' ? '本地目录' : 'Git 仓库地址', description: locationSummary, run: () => startDraftField('location') }]),
6221
+ ...(route.sourceId === 'official' && officialTransport ? [{
6222
+ label: '下载方式',
6223
+ meta: officialTransport,
6224
+ description: officialTransport === 'HTTPS'
6225
+ ? '默认方式;无需预先配置 SSH 密钥'
6226
+ : '适合已配置 GitHub SSH 访问的用户',
6227
+ run: () => {
6228
+ const active = draft ?? (configuredSource
6229
+ ? sourceDraftFromSource(configuredSource, `source-update:${route.sourceId}:${Date.now()}`)
6230
+ : undefined);
6231
+ if (!active)
6232
+ return;
6233
+ setSourceTransportOverlay({ owner: active.owner });
6234
+ setOverlayFocus(officialTransport === 'HTTPS' ? 0 : 1);
6235
+ },
6236
+ }] : [{ label: draft?.type === 'path' || detail?.type === 'path' ? '本地目录' : 'Git 仓库地址', description: locationSummary, run: () => startDraftField('location') }]),
5644
6237
  ...(draft?.type === 'git' || !draft && detail?.type === 'git' ? [
5645
6238
  { label: '版本引用', description: draft ? draft.refPolicy === 'explicit' ? `固定:${draft.ref}` : '跟随 AW 版本通道' : detail?.refPolicy === 'explicit' ? `固定:${detail.explicitRef}` : '跟随 AW 版本通道', run: () => startDraftField('refPolicy') },
5646
- { label: 'Catalog 子目录', description: draft?.subdir || detail?.subdir || '未设置', run: () => startDraftField('subdir') },
6239
+ { label: '高级:Catalog 子目录', description: catalogSubdir === '' || catalogSubdir === undefined
6240
+ ? '仓库根目录' : catalogSubdir, run: () => startDraftField('subdir') },
5647
6241
  ] : []),
5648
6242
  { label: '自动检查更新', description: (draft?.autoCheck ?? detail?.autoCheck) ? '已开启' : '已关闭', selected: draft?.autoCheck ?? detail?.autoCheck, run: () => {
5649
6243
  if (!draft) {
@@ -5668,20 +6262,28 @@ export function ConsoleAppV4(props) {
5668
6262
  go({ name: 'sourceSave', owner: draft.owner });
5669
6263
  } },
5670
6264
  ];
5671
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u6765\u6E90\u8349\u7A3F", compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: "\u6765\u6E90 ID", meta: draft?.sourceId ?? route.sourceId, compact: true }), _jsx(StaticRow, { title: "\u7C7B\u578B", meta: (draft?.type ?? detail?.type) === 'git' ? 'Git 仓库' : '本地目录', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' }), route.sourceId === 'official' && detail?.type === 'git' ? _jsxs(SectionCard, { title: "\u6765\u6E90\u8EAB\u4EFD", children: [_jsx(StaticRow, { title: "\u903B\u8F91 Catalog", meta: "lesliechan721-arch/agent-workflow" }), _jsx(StaticRow, { title: "source identity", meta: "HTTPS / SSH \u4FDD\u6301\u4E00\u81F4" }), _jsx(StaticRow, { title: "\u660E\u6587 HTTP", meta: "\u4E0D\u652F\u6301", status: "warning" })] }) : null] });
6265
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5F53\u524D\u8BBE\u7F6E", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: "\u6765\u6E90\u7C7B\u578B", meta: (draft?.type ?? detail?.type) === 'git' ? 'Git 仓库' : '本地目录', compact: true }) }), _jsx(SemanticRows, { actions: actions, focus: sourceTransportOverlay ? -1 : focus, kind: "section", compact: policy.mode === 'compact' })] });
5672
6266
  }
5673
6267
  else if (route.name === 'sourceSave' && sourceDraft?.owner === route.owner) {
5674
6268
  const changes = sourceDraftChanges(sourceDraft);
6269
+ const savedSource = props.controller.getConfiguredSource(sourceDraft.sourceId);
6270
+ const protocolOnly = changes.length === 1 && changes[0]?.title === 'Git 仓库地址'
6271
+ && savedSource?.type === 'git'
6272
+ && (savedSource.url === OFFICIAL_CATALOG_SOURCE_URL
6273
+ || savedSource.url === OFFICIAL_CATALOG_SOURCE_SSH_URL)
6274
+ && (sourceDraft.location === OFFICIAL_CATALOG_SOURCE_URL
6275
+ || sourceDraft.location === OFFICIAL_CATALOG_SOURCE_SSH_URL);
5675
6276
  breadcrumb = `插件来源 / ${sourceDraft.sourceId} / 修改汇总`;
5676
6277
  context = `${changes.length} 项未保存`;
5677
6278
  title = sourceDraft.mode === 'add' ? `保存新来源 ${sourceDraft.sourceId}?` : `保存 ${sourceDraft.sourceId} 的修改?`;
5678
6279
  description = '选择是否在保存后验证候选并启用。保存成功与启用成功会分别报告。';
5679
6280
  actions = [
5680
- { label: '保存并启用', description: '保存来源配置,再验证并切换当前目录版本', status: 'success', run: () => beginSourceReview(sourceDraft, true) },
5681
- { label: '只保存', description: '只保存来源配置;当前目录版本保持不变', run: () => beginSourceReview(sourceDraft, false) },
6281
+ ...(protocolOnly ? [] : [{ label: '保存并启用', description: '保存来源配置,再验证并切换当前目录版本', status: 'success', run: () => beginSourceReview(sourceDraft, true) }]),
6282
+ { label: protocolOnly ? '只保存下载方式' : '只保存', description: '只保存来源配置;当前目录版本保持不变', status: protocolOnly ? 'success' : undefined, run: () => beginSourceReview(sourceDraft, false) },
6283
+ ...(protocolOnly ? [{ label: '保存并启用…', description: '保存后验证并切换当前目录版本', run: () => beginSourceReview(sourceDraft, true) }] : []),
5682
6284
  { label: '返回修改', run: back },
5683
6285
  ];
5684
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u6765\u6E90\u53D8\u5316", compact: policy.mode === 'compact', children: changes.map((change) => _jsx(ReviewChangeRow, { title: `~ ${change.title}`, description: `${change.before ?? '未设置'} → ${change.after}`, compact: true }, change.title)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6286
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u6765\u6E90\u53D8\u5316", compact: policy.mode === 'compact', children: changes.map((change) => _jsx(ReviewChangeRow, { title: `~ ${change.title}`, description: `${change.before ?? '未设置'} → ${change.after}`, compact: policy.mode === 'compact' }, change.title)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
5685
6287
  }
5686
6288
  else if (route.name === 'scriptTimeout' && pendingReviewIntent) {
5687
6289
  breadcrumb = '高级选项 / 安装前检查脚本';
@@ -5796,12 +6398,12 @@ export function ConsoleAppV4(props) {
5796
6398
  go({ name: 'pluginDependencies', pluginId: issue.pluginId });
5797
6399
  }
5798
6400
  else if (entry.id === 'viewDependencyIssue' && entry.pluginId) {
5799
- const dependencyIssue = props.controller.listUserIssues().find((candidate) => candidate.pluginId === entry.pluginId
6401
+ const dependencyIssue = props.controller.listUserIssues().find((candidate) => issueMatchesTarget(candidate, entry.pluginId, issue.dependencyPlatform, issue.dependencyScope)
5800
6402
  && candidate.issueId !== issue.issueId);
5801
6403
  if (dependencyIssue)
5802
6404
  go({ name: 'issueDetail', issueId: dependencyIssue.issueId });
5803
- else
5804
- go({ name: 'pluginDependencies', pluginId: entry.pluginId });
6405
+ else if (!showTechnical)
6406
+ toggleTechnical();
5805
6407
  }
5806
6408
  else
5807
6409
  toggleTechnical();
@@ -6031,14 +6633,12 @@ export function ConsoleAppV4(props) {
6031
6633
  const batchReview = review.operationKind === 'pluginWriteBatch';
6032
6634
  const taskReview = review.operationKind === 'pluginWriteTask';
6033
6635
  const catalogManagementReview = review.operationKind === 'catalogManagement';
6034
- const sharedImpactConfirmed = !review.requiresConfirmation
6035
- || confirmedSharedReviewToken === review.reviewToken;
6036
6636
  breadcrumb = `${review.subject.name} / ${review.title}`;
6037
6637
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
6038
6638
  ? review.title : `确认${review.title}`} ${review.subject.name}`;
6039
6639
  description = snapshot.result?.state === 'stale'
6040
6640
  ? '当前状态已变化。请检查新的变化后再次确认。'
6041
- : review.requiresConfirmation ? '' : review.description;
6641
+ : review.description;
6042
6642
  if (taskReview) {
6043
6643
  context = '精确目标 · 用户任务';
6044
6644
  help = 'PgUp/PgDn 查看全部影响 · ↑↓ 移动 · Enter 选择 · Esc 返回';
@@ -6048,20 +6648,19 @@ export function ConsoleAppV4(props) {
6048
6648
  help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6049
6649
  }
6050
6650
  actions = [
6051
- ...(review.requiresConfirmation ? [{
6052
- label: catalogManagementReview
6053
- ? sharedImpactConfirmed ? '已确认本次写入' : '确认本次写入'
6054
- : sharedImpactConfirmed ? '已确认共享影响' : '确认共享影响',
6055
- description: catalogManagementReview
6056
- ? '我已检查上方的精确写入、远端影响和保留项。'
6057
- : review.sharedImpact.warning ?? '此操作会修改多个插件共同使用的 Codex 平台内容',
6058
- selected: sharedImpactConfirmed,
6059
- run: () => {
6060
- setConfirmedSharedReviewToken(review.reviewToken);
6061
- setFocus(1);
6062
- },
6063
- }] : []),
6064
- { label: review.primaryLabel, description: '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, disabled: sharedImpactConfirmed ? undefined : '请先确认共享影响', run: () => {
6651
+ { label: review.primaryLabel, description: review.requiresConfirmation
6652
+ ? '打开独立风险确认;执行时再次检查当前状态'
6653
+ : '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
6654
+ if (review.requiresConfirmation) {
6655
+ setOperationConfirmation({
6656
+ reviewToken: review.reviewToken,
6657
+ returnFocus: focus,
6658
+ returnScrollOffset: scrollOffset,
6659
+ warningOffset: 0,
6660
+ });
6661
+ setOverlayFocus(0);
6662
+ return;
6663
+ }
6065
6664
  if (operationBusy.current)
6066
6665
  return;
6067
6666
  operationBusy.current = true;
@@ -6072,6 +6671,65 @@ export function ConsoleAppV4(props) {
6072
6671
  operationBusy.current = false;
6073
6672
  });
6074
6673
  } },
6674
+ ...(taskReview
6675
+ && pendingReviewIntent?.kind === 'pluginWriteTask'
6676
+ && pendingReviewIntent.goal === 'update'
6677
+ && batchDraft?.frozenPluginIds ? [{
6678
+ label: `调整选择(${new Set(pendingReviewIntent.targets
6679
+ .map((target) => target.pluginId)).size})`,
6680
+ description: '只可删减或恢复本次冻结的推荐插件;调整后重新读取证据并建立 Review',
6681
+ run: () => {
6682
+ if (!props.controller.returnToPluginWriteTaskBatchSelection(batchDraft.flowId)) {
6683
+ setActionError('推荐选择已失效。请返回我的插件重新开始。');
6684
+ return;
6685
+ }
6686
+ setBatchDraft((current) => current ? {
6687
+ ...current,
6688
+ stage: 'select',
6689
+ showSelected: false,
6690
+ } : current);
6691
+ setFocus(0);
6692
+ setScrollOffset(0);
6693
+ setShowTechnical(false);
6694
+ setActionError(undefined);
6695
+ },
6696
+ }] : []),
6697
+ ...(taskReview
6698
+ && pendingReviewIntent?.kind === 'pluginWriteTask'
6699
+ && route.name === 'review'
6700
+ && batchDraft ? [{
6701
+ label: '修改处理范围',
6702
+ description: '只可删减或恢复本次已有位置;修改后重新读取证据',
6703
+ run: () => {
6704
+ const targets = pendingReviewIntent.targets;
6705
+ setBatchDraft((current) => current ? {
6706
+ ...current,
6707
+ maintenanceGoal: pendingReviewIntent.goal,
6708
+ maintenanceTargets: current.maintenanceGoal === pendingReviewIntent.goal
6709
+ && current.maintenanceTargets
6710
+ ? current.maintenanceTargets : targets,
6711
+ selectedMaintenanceTargetKeys: targets.map(batchTargetKey),
6712
+ } : current);
6713
+ back();
6714
+ props.controller.push({ name: 'batchPlacement' });
6715
+ setFocus(0);
6716
+ setScrollOffset(0);
6717
+ },
6718
+ }] : []),
6719
+ ...(batchReview
6720
+ && pendingReviewIntent?.kind === 'pluginWriteBatch'
6721
+ && pendingReviewIntent.selection.kind === 'targets'
6722
+ && pendingReviewIntent.action !== 'install'
6723
+ && batchDraft?.maintenanceTargets ? [{
6724
+ label: '修改处理范围',
6725
+ description: '只可删减或恢复本次已有位置;修改后重新读取证据',
6726
+ run: () => {
6727
+ back();
6728
+ props.controller.push({ name: 'batchPlacement' });
6729
+ setFocus(0);
6730
+ setScrollOffset(0);
6731
+ },
6732
+ }] : []),
6075
6733
  ...(installReviewPluginId
6076
6734
  && (snapshot.installPlacementDraft?.targets.length ?? 0) > 0 ? [{
6077
6735
  label: '查看/调整依赖位置',
@@ -6085,7 +6743,7 @@ export function ConsoleAppV4(props) {
6085
6743
  setScrollOffset(0);
6086
6744
  },
6087
6745
  }] : []),
6088
- ...(sharedImpactConfirmed && (pendingReviewIntent?.kind === 'pluginWrite'
6746
+ ...((pendingReviewIntent?.kind === 'pluginWrite'
6089
6747
  && (pendingReviewIntent.action === 'install'
6090
6748
  || pendingReviewIntent.action === 'refreshAndSync'
6091
6749
  || pendingReviewIntent.action === 'reinstall'
@@ -6111,7 +6769,7 @@ export function ConsoleAppV4(props) {
6111
6769
  : back },
6112
6770
  ];
6113
6771
  const groupedChanges = review.changes.some((change) => change.group);
6114
- const compressedReview = policy.mode === 'compact' || review.requiresConfirmation;
6772
+ const compressedReview = policy.mode === 'compact';
6115
6773
  const groupTitle = {
6116
6774
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
6117
6775
  dependencies: review.operationKind === 'portableInstall' ? '已选择 Skills · 来自依赖插件' : '必需依赖',
@@ -6149,8 +6807,15 @@ export function ConsoleAppV4(props) {
6149
6807
  'projectIntent', 'userIntent', 'sharedCodexNative',
6150
6808
  'userActivation', 'projectActivation', 'awExtensions', 'runtime',
6151
6809
  ]);
6152
- const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0
6153
- && !technicalGroups.has(group.key));
6810
+ const userReviewChanges = review.changes.filter((change) => change.group === undefined
6811
+ || !isTechnicalReviewGroup(change.group)
6812
+ || change.kind === 'warning'
6813
+ || change.kind === 'remove');
6814
+ const visibleSpecs = groupedSpecs.map((group) => technicalGroups.has(group.key) ? {
6815
+ ...group,
6816
+ label: '风险',
6817
+ changes: group.changes.filter((change) => change.kind === 'warning' || change.kind === 'remove'),
6818
+ } : group).filter((group) => group.changes.length > 0);
6154
6819
  const headingLines = compressedReview ? 0 : visibleSpecs.filter((group) => group.label).length;
6155
6820
  const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
6156
6821
  const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
@@ -6186,15 +6851,15 @@ export function ConsoleAppV4(props) {
6186
6851
  })] }, group.key);
6187
6852
  });
6188
6853
  const batchChangeLimit = Math.max(1, responsiveBudget(6, 10) - actions.length);
6189
- const batchChanges = review.changes.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
6854
+ const batchChanges = userReviewChanges.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
6190
6855
  const batchHiddenBefore = batchTargetOffset;
6191
6856
  const prioritizedReviewChanges = [
6192
- ...review.changes.filter((change) => change.kind === 'warning'),
6193
- ...review.changes.filter((change) => change.kind !== 'warning'),
6857
+ ...userReviewChanges.filter((change) => change.kind === 'warning'),
6858
+ ...userReviewChanges.filter((change) => change.kind !== 'warning'),
6194
6859
  ];
6195
6860
  const pagedReviewChanges = prioritizedReviewChanges.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
6196
- const changeGroups = batchReview || review.changes.length > staticRowBudget
6197
- ? _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, review.changes.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length) > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${Math.max(0, review.changes.length - batchTargetOffset - (batchReview ? batchChanges : pagedReviewChanges).length)} 项`, status: "muted", compact: true }) : null] })
6861
+ const changeGroups = batchReview || userReviewChanges.length > staticRowBudget
6862
+ ? _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] })
6198
6863
  : standardChangeGroups;
6199
6864
  const technicalDetails = review.technicalDetails ?? [];
6200
6865
  const technicalDetailLimit = responsiveBudget(1, 3);
@@ -6231,11 +6896,11 @@ export function ConsoleAppV4(props) {
6231
6896
  const widenedDependencyCount = new Set(installDependencyTargets
6232
6897
  .filter((target) => target.affectsOtherProjects && target.action !== 'keep')
6233
6898
  .map((target) => target.pluginId)).size;
6234
- if (review.changes.length > staticRowBudget || showTechnical
6899
+ if (userReviewChanges.length > staticRowBudget || showTechnical
6235
6900
  && (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
6236
6901
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6237
6902
  }
6238
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.requiresConfirmation ? undefined : review.subject.name, meta: review.requiresConfirmation ? undefined : `${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: catalogManagementReview ? '! 需要确认本次写入' : '! 需要确认共享影响', meta: !catalogManagementReview && review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: catalogManagementReview ? review.description : review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), 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] }) });
6903
+ 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: catalogManagementReview ? '! 需要确认本次写入' : '! 需要确认共享影响', meta: !catalogManagementReview && review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: catalogManagementReview ? review.description : review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true }), 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] }) });
6239
6904
  }
6240
6905
  else if (route.name === 'progress') {
6241
6906
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -6250,15 +6915,26 @@ export function ConsoleAppV4(props) {
6250
6915
  breadcrumb = catalogManagementOperation ? '目录管理 / 进度' : taskOperation ? '插件任务 / 进度' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
6251
6916
  const configApply = snapshot.activeReview?.operationKind === 'configSave'
6252
6917
  && snapshot.activeReview.changes.some((change) => change.group === 'configImpact' && change.kind === 'change');
6918
+ const progressAction = snapshot.activeReview?.taskGoal === 'restore' ? '恢复'
6919
+ : snapshot.activeReview?.taskGoal === 'update' ? '更新'
6920
+ : configApply ? '应用设置'
6921
+ : snapshot.activeReview?.pluginAction === 'install' ? '添加'
6922
+ : snapshot.activeReview?.pluginAction === 'refreshAndSync' ? '更新'
6923
+ : snapshot.activeReview?.pluginAction === 'reinstall' ? '恢复'
6924
+ : snapshot.activeReview?.pluginAction === 'uninstall' ? '移除'
6925
+ : snapshot.activeReview?.pluginAction === 'cleanup' ? '清理'
6926
+ : snapshot.activeReview?.pluginAction === 'sync' ? '同步' : '处理';
6253
6927
  const catalogGenerationOperation = snapshot.activeReview?.operationKind === 'catalogGeneration';
6254
6928
  const catalogSourcesRecovery = snapshot.activeReview?.operationKind === 'catalogSourcesRecovery';
6255
- title = catalogManagementOperation ? '正在执行 Catalog 管理操作' : taskOperation ? '正在执行插件任务' : portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题'
6256
- : catalogGenerationOperation ? '正在验证并启用目录版本'
6257
- : catalogSourcesRecovery ? '正在恢复插件来源配置'
6258
- : sourceOperation ? '正在保存来源配置'
6259
- : configOperation ? configApply ? '正在保存并应用设置' : '正在保存设置'
6260
- : '正在执行插件操作';
6261
- description = catalogManagementOperation ? '正在执行已确认的精确 Workspace、Store、开发来源或发布操作。'
6929
+ title = catalogManagementOperation
6930
+ ? `正在${snapshot.activeReview?.title.replace(/^确认/u, '') ?? '完成目录管理任务'}`
6931
+ : taskOperation ? `正在${progressAction}插件` : portableOperation ? '正在安装通用 Skills' : issueOperation ? `正在${progressAction}插件`
6932
+ : catalogGenerationOperation ? '正在验证并启用目录版本'
6933
+ : catalogSourcesRecovery ? '正在恢复插件来源配置'
6934
+ : sourceOperation ? '正在保存来源配置'
6935
+ : configOperation ? configApply ? '正在保存并应用设置' : '正在保存设置'
6936
+ : `正在${progressAction}插件`;
6937
+ description = catalogManagementOperation ? '正在完成已确认的目录管理任务。'
6262
6938
  : issueOperation ? '完成后会重新读取状态并检查问题。'
6263
6939
  : catalogGenerationOperation ? '正在验证并应用已确认的目录版本操作。'
6264
6940
  : catalogSourcesRecovery ? '正在备份损坏配置并恢复官方默认来源。'
@@ -6289,7 +6965,7 @@ export function ConsoleAppV4(props) {
6289
6965
  if (entry.state === 'completed')
6290
6966
  return { label: '已完成', status: 'success' };
6291
6967
  if (entry.state === 'running')
6292
- return { label: '正在添加', status: 'info' };
6968
+ return { label: `正在${progressAction}`, status: 'info' };
6293
6969
  if (entry.state === 'failed')
6294
6970
  return { label: '失败', status: 'error' };
6295
6971
  if (entry.state === 'skipped')
@@ -6367,13 +7043,13 @@ export function ConsoleAppV4(props) {
6367
7043
  return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
6368
7044
  };
6369
7045
  const compactPortableProgress = policy.mode === 'compact' && showTechnical;
6370
- content = catalogManagementOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Catalog \u7BA1\u7406", children: [_jsx(ProgressRow, { title: "\u786E\u8BA4\u8BA1\u5212", meta: "\u5B8C\u6210", state: "complete" }), _jsx(ProgressRow, { title: currentPhase || '准备执行', description: progress.current?.target, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : currentPhase ? 'active' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5E76\u8BB0\u5F55\u7ED3\u679C", state: validationActive ? 'active' : 'pending' }), !showTechnical && latestMessage ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] }) : portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个来源或依赖插件提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [progressTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${progressTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleProgressTargets.map((entry) => {
7046
+ content = catalogManagementOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "\u76EE\u5F55\u7BA1\u7406", children: [_jsx(ProgressRow, { title: "\u786E\u8BA4\u8BA1\u5212", meta: "\u5B8C\u6210", state: "complete" }), _jsx(ProgressRow, { title: currentPhase || '准备执行', description: progress.current?.target, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : currentPhase ? 'active' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5E76\u8BB0\u5F55\u7ED3\u679C", state: validationActive ? 'active' : 'pending' }), !showTechnical && latestMessage ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] }) : portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个来源或依赖插件提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: `${progressAction}所选插件和所需插件`, description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [progressTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${progressTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleProgressTargets.map((entry) => {
6371
7047
  const state = progressLabel(entry);
6372
7048
  const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
6373
7049
  const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
6374
7050
  ?? entry.pluginId.split('/').at(-1);
6375
7051
  return _jsx(ResultRow, { title: title, description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${entry.pluginId}:${entry.platform}:${entry.scope}`);
6376
- }), hiddenProgressTargets > 0 ? _jsx(StaticRow, { title: `${batchProgress ? '↓ 后面还有' : '另有'} ${hiddenProgressTargets} 个目标`, status: "muted", compact: true }) : null] }) : null, !showTechnical && latestMessage && (!dependencyProgress || dependencyMessageLimit > 0) ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? (dependencyProgress
7052
+ }), hiddenProgressTargets > 0 ? _jsx(StaticRow, { title: `${batchProgress ? '↓ 后面还有' : '另有'} ${hiddenProgressTargets} 个目标`, status: "muted", compact: true }) : null] }) : null, !showTechnical && latestMessage && (!dependencyProgress || dependencyMessageLimit > 0) ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical && currentPhase ? _jsx(StaticRow, { title: "\u5F53\u524D\u6280\u672F\u9636\u6BB5", meta: currentPhase, compact: true }) : null, showTechnical ? (dependencyProgress
6377
7053
  ? dependencyMessageLimit > 0 ? technicalMessages.slice(-dependencyMessageLimit) : []
6378
7054
  : technicalMessages).map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] });
6379
7055
  }
@@ -6494,15 +7170,15 @@ export function ConsoleAppV4(props) {
6494
7170
  : taskOperation
6495
7171
  ? blocked ? '任务被安全阻止。已完成和未完成目标按实际结果保留。'
6496
7172
  : partial ? `${taskSummary?.retryTargets?.length ?? 0} 个失败目标可重试 · ${taskBlockedCount} 个目标需要决定。`
6497
- : `${taskSummary?.rootResults.length ?? 0} 个根插件已完成。`
7173
+ : `${taskSummary?.rootResults.length ?? 0} 个所选插件已处理;下方显示使用位置。`
6498
7174
  : batchOperation
6499
7175
  ? blocked
6500
7176
  ? '批量写入被阻止。已完成和未完成目标按实际结果保留。'
6501
7177
  : partial
6502
7178
  ? (resultSummary?.batch?.retryTargets?.length ?? 0) > 0
6503
- ? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个根插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
7179
+ ? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个所选插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
6504
7180
  : '失败目标无法精确定位。请查看本次记录后重新操作。'
6505
- : `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个根插件已完成。`
7181
+ : `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个所选插件已处理;下方显示使用位置和所需插件。`
6506
7182
  : portableOperation
6507
7183
  ? blocked
6508
7184
  ? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
@@ -6898,34 +7574,35 @@ export function ConsoleAppV4(props) {
6898
7574
  return { label: '未开始', status: 'muted' };
6899
7575
  return { label: '待确认', status: 'warning' };
6900
7576
  };
6901
- const taskResultRows = taskRootResults.flatMap((root) => {
7577
+ const taskSelectedPluginRows = taskRootResults.map((root) => {
6902
7578
  const rootState = taskRootState(root.state);
6903
7579
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
6904
7580
  ?? root.pluginId.split('/').at(-1);
6905
7581
  const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
6906
- return [
6907
- _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`),
6908
- ...requestedTargets.map((target) => {
6909
- const key = [target.pluginId, target.platform, target.scope].join('\0');
6910
- const blockedTarget = blockedTaskTargets.get(key);
6911
- const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
6912
- && entry.scope === target.scope);
6913
- const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
6914
- : targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
6915
- : targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
6916
- : targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
6917
- : { label: '未开始', status: 'muted' };
6918
- return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
6919
- ? pluginWriteTaskBlockLabel(blockedTarget.reason)
6920
- : targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
6921
- }),
6922
- ];
7582
+ return _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`);
7583
+ });
7584
+ const taskLocationRows = taskRootResults.flatMap((root) => {
7585
+ const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
7586
+ return requestedTargets.map((target) => {
7587
+ const key = [target.pluginId, target.platform, target.scope].join('\0');
7588
+ const blockedTarget = blockedTaskTargets.get(key);
7589
+ const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
7590
+ && entry.scope === target.scope);
7591
+ const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
7592
+ : targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
7593
+ : targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
7594
+ : targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
7595
+ : { label: '未开始', status: 'muted' };
7596
+ return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
7597
+ ? pluginWriteTaskBlockLabel(blockedTarget.reason)
7598
+ : targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
7599
+ });
6923
7600
  });
6924
7601
  const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
6925
7602
  const rootState = taskRootState(root.state);
6926
7603
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
6927
7604
  ?? root.pluginId.split('/').at(-1);
6928
- return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
7605
+ return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
6929
7606
  }).concat(prioritizedTargets.map((target) => {
6930
7607
  const state = operationTargetStateLabel(target.state);
6931
7608
  const relation = target.relation === 'requested' ? '你选择的插件'
@@ -6934,14 +7611,16 @@ export function ConsoleAppV4(props) {
6934
7611
  return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
6935
7612
  }));
6936
7613
  const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
6937
- const rootResultRows = taskOperation ? taskResultRows : batchResultRows;
7614
+ const rootResultRows = taskOperation
7615
+ ? [...taskSelectedPluginRows, ...taskLocationRows]
7616
+ : batchResultRows;
6938
7617
  const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
6939
7618
  const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
6940
7619
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
6941
7620
  || prioritizedPortableSkills.length > portableSkillLimit) {
6942
7621
  help = 'PgUp/PgDn 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6943
7622
  }
6944
- content = catalogManagementOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: resultSummary?.message ?? 'Catalog 管理结果', children: [catalogManagementResult?.catalogVersion ? _jsx(StaticRow, { title: "Catalog \u7248\u672C", meta: catalogManagementResult.catalogVersion, compact: true }) : null, catalogManagementResult?.store ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5B9E\u9645\u5220\u9664", meta: `${catalogManagementResult.store.deletedEntries} 项 · ${catalogManagementResult.store.deletedBytes} bytes`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u5B9E\u9645\u4FDD\u7559", meta: `${catalogManagementResult.store.retainedEntries} 项`, compact: true })] }) : null, catalogManagementResult?.publication ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u53D1\u5E03\u72B6\u6001", meta: catalogManagementResult.publication.state, status: catalogManagementResult.publication.state === 'uncertain' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u76EE\u6807", meta: `${catalogManagementResult.publication.remote} · ${catalogManagementResult.publication.branch}`, compact: true }), _jsx(StaticRow, { title: "\u8BA1\u5212\u6807\u8BC6", meta: catalogManagementResult.publication.planDigest.slice(0, 15), compact: true })] }) : catalogManagementResult?.planDigest ? _jsx(StaticRow, { title: "\u8BA1\u5212\u5DF2\u51C6\u5907", meta: catalogManagementResult.planDigest.slice(0, 15), status: "success", compact: true }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: productResultMessage(message), status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: batchOperation ? '根插件与目标结果' : '根插件结果', children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
7623
+ content = catalogManagementOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: resultSummary?.message ?? '目录管理结果', children: [catalogManagementResult?.catalogVersion ? _jsx(StaticRow, { title: "\u76EE\u5F55\u7248\u672C", meta: catalogManagementResult.catalogVersion, compact: true }) : null, catalogManagementResult?.store ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5B9E\u9645\u5220\u9664", meta: `${catalogManagementResult.store.deletedEntries} 项 · ${catalogManagementResult.store.deletedBytes} bytes`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u5B9E\u9645\u4FDD\u7559", meta: `${catalogManagementResult.store.retainedEntries} 项`, compact: true })] }) : null, catalogManagementResult?.publication ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u53D1\u5E03\u72B6\u6001", meta: catalogManagementResult.publication.state, status: catalogManagementResult.publication.state === 'uncertain' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u76EE\u6807", meta: `${catalogManagementResult.publication.remote} · ${catalogManagementResult.publication.branch}`, compact: true }), _jsx(StaticRow, { title: "\u8BA1\u5212\u6807\u8BC6", meta: catalogManagementResult.publication.planDigest.slice(0, 15), compact: true })] }) : catalogManagementResult?.planDigest ? _jsx(StaticRow, { title: "\u8BA1\u5212\u5DF2\u51C6\u5907", meta: catalogManagementResult.planDigest.slice(0, 15), status: "success", compact: true }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: productResultMessage(message), status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : 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
6945
7624
  ? _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: [portableTechnicalSummary ? _jsxs(_Fragment, { children: [portableSkillOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${portableSkillOffset} 个 Skills`, status: "muted", compact: true }) : null, portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt?.recovery ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true }) : null, _jsx(TechnicalDetails, { children: compactTechnicalRows })] }) : _jsx(_Fragment, { children: _jsxs(ResultCard, { status: resultStatus, title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', children: [portableSkillOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${portableSkillOffset} 个 Skills`, status: "muted", compact: true }) : null, portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt ? _jsx(StaticRow, { title: `Agent:${portableReceipt.agentMode === 'automatic' ? 'Skills CLI 默认选择' : portableReceipt.requestedAgents.join('、')}`, meta: `范围:${scopeLabel(portableReceipt.scope)}`, compact: true }) : null, portableReceipt?.recovery ? policy.mode === 'compact'
6946
7625
  ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
6947
7626
  : _jsx(Callout, { status: "warning", title: "\u5B89\u5168\u6062\u590D", description: portableReceipt.recovery }) : null, showTechnical && portableReceipt ? _jsxs(TechnicalDetails, { children: [portableReceipt.failureCode ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }) : null, _jsx(StaticRow, { title: "verification missing", meta: String(portableReceipt.missing.length), compact: true }), _jsx(StaticRow, { title: "Skills CLI version", meta: portableReceipt.skillsCliVersion, compact: true }), partial || blocked
@@ -6980,7 +7659,7 @@ export function ConsoleAppV4(props) {
6980
7659
  if (prioritizedComponents.length > componentLimit) {
6981
7660
  help = 'PgUp/PgDn 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6982
7661
  }
6983
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.capabilityUnsupported ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "failure code", meta: "awCapabilityUnsupported" }), _jsx(StaticRow, { title: "capability \u72B6\u6001", meta: "\u5F53\u524D AW CLI \u4E0D\u652F\u6301", status: "error" }), _jsx(StaticRow, { title: "\u7F3A\u5C11 capability", meta: plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability' }), _jsx(StaticRow, { title: "\u6062\u590D\u65B9\u5411", meta: plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。' })] }) : null, plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), componentOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${componentOffset} 个组件`, status: "muted", compact: true }) : null, visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${scopeLabel(component.scope)}` }, `${component.platform}:${component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
7662
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.capabilityUnsupported ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "failure code", meta: "awCapabilityUnsupported" }), _jsx(StaticRow, { title: "capability \u72B6\u6001", meta: "\u5F53\u524D AW CLI \u4E0D\u652F\u6301", status: "error" }), _jsx(StaticRow, { title: "\u7F3A\u5C11 capability", meta: plugin.missingCapabilities?.join('、') ?? 'Status 未提供具体 capability' }), _jsx(StaticRow, { title: "\u6062\u590D\u65B9\u5411", meta: plugin.capabilityRecovery ?? '升级 AW CLI 到支持所需 capability 的版本后重试。' })] }) : null, plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), componentOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${componentOffset} 个组件`, status: "muted", compact: true }) : null, visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${component.shared ? '平台共享' : scopeLabel(component.scope)}` }, `${component.platform}:${component.shared ? 'shared' : component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6984
7663
  }
6985
7664
  else {
6986
7665
  const missingPlugin = definition.entity === 'plugin' && plugin === undefined;
@@ -7260,6 +7939,89 @@ export function ConsoleAppV4(props) {
7260
7939
  }
7261
7940
  return;
7262
7941
  }
7942
+ if (operationConfirmation) {
7943
+ const activeReview = snapshot.activeReview;
7944
+ const closeConfirmation = () => {
7945
+ setOperationConfirmation(undefined);
7946
+ setFocus(operationConfirmation.returnFocus);
7947
+ setScrollOffset(operationConfirmation.returnScrollOffset);
7948
+ setOverlayFocus(0);
7949
+ };
7950
+ if (key.escape || input.toLowerCase() === 'b') {
7951
+ closeConfirmation();
7952
+ }
7953
+ else if (key.ctrl && input.toLowerCase() === 'c') {
7954
+ closeConfirmation();
7955
+ requestExit({ code: 0, reason: 'user' });
7956
+ }
7957
+ else if (input.toLowerCase() === 'q') {
7958
+ closeConfirmation();
7959
+ setDiscardForExit(dirty);
7960
+ setOverlay(dirty ? 'discard' : 'exit');
7961
+ }
7962
+ else if (props.onSessionNavigation && (input === 'h' || input === 'H')) {
7963
+ const navigation = input === 'H' ? 'reload' : 'home';
7964
+ closeConfirmation();
7965
+ requestSessionNavigation(navigation);
7966
+ }
7967
+ else if (key.pageUp || key.pageDown) {
7968
+ const warningCount = activeReview
7969
+ ? operationRiskLines(activeReview, Math.max(12, size.columns - 8)).length : 0;
7970
+ const warningLimit = policy.mode === 'compact' ? 3 : 7;
7971
+ setOperationConfirmation((current) => current ? {
7972
+ ...current,
7973
+ warningOffset: Math.min(Math.max(0, warningCount - warningLimit), Math.max(0, current.warningOffset + (key.pageUp ? -1 : 1))),
7974
+ } : current);
7975
+ }
7976
+ else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
7977
+ setOverlayFocus((value) => moveIndex(2, value, key.upArrow || key.leftArrow ? -1 : 1));
7978
+ }
7979
+ else if (key.return) {
7980
+ if (overlayFocus === 0) {
7981
+ closeConfirmation();
7982
+ }
7983
+ else if (activeReview?.reviewToken !== operationConfirmation.reviewToken) {
7984
+ closeConfirmation();
7985
+ setActionError('确认内容已变化。请重新检查操作汇总。');
7986
+ }
7987
+ else if (!operationBusy.current) {
7988
+ operationBusy.current = true;
7989
+ setOperationConfirmation(undefined);
7990
+ void props.controller.executeOperation(operationConfirmation.reviewToken).then(() => {
7991
+ setFocus(0);
7992
+ setScrollOffset(0);
7993
+ }).finally(() => {
7994
+ operationBusy.current = false;
7995
+ });
7996
+ }
7997
+ }
7998
+ return;
7999
+ }
8000
+ if (sourceTransportOverlay) {
8001
+ if (key.escape || input.toLowerCase() === 'b') {
8002
+ setSourceTransportOverlay(undefined);
8003
+ setOverlayFocus(0);
8004
+ }
8005
+ else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
8006
+ setOverlayFocus((value) => moveIndex(2, value, key.upArrow || key.leftArrow ? -1 : 1));
8007
+ }
8008
+ else if (key.return && route.name === 'sourceEdit') {
8009
+ const configured = props.controller.getConfiguredSource(route.sourceId);
8010
+ const current = sourceDraft?.owner === sourceTransportOverlay.owner
8011
+ ? sourceDraft
8012
+ : configured ? sourceDraftFromSource(configured, sourceTransportOverlay.owner) : undefined;
8013
+ const location = overlayFocus === 0
8014
+ ? OFFICIAL_CATALOG_SOURCE_URL : OFFICIAL_CATALOG_SOURCE_SSH_URL;
8015
+ if (current && current.type === 'git' && current.location !== location) {
8016
+ setSourceDraft({ ...current, location });
8017
+ setDirty(true);
8018
+ props.controller.replace({ name: 'sourceEdit', sourceId: route.sourceId, owner: current.owner });
8019
+ }
8020
+ setSourceTransportOverlay(undefined);
8021
+ setOverlayFocus(0);
8022
+ }
8023
+ return;
8024
+ }
7263
8025
  if (overlay) {
7264
8026
  if (key.ctrl && input.toLowerCase() === 'c') {
7265
8027
  requestExit({ code: snapshot.phase === 'error' ? 1 : 0, reason: snapshot.phase === 'error' ? 'initialError' : 'user' });
@@ -7369,6 +8131,13 @@ export function ConsoleAppV4(props) {
7369
8131
  setReportSaveError(undefined);
7370
8132
  return;
7371
8133
  }
8134
+ if ((key.pageUp || key.pageDown)
8135
+ && route.name === 'pluginDependencyNode' && route.view === 'references'
8136
+ && referenceDetailPagination) {
8137
+ const { total, pageRows } = referenceDetailPagination;
8138
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
8139
+ return;
8140
+ }
7372
8141
  if ((key.pageUp || key.pageDown)
7373
8142
  && route.name === 'pluginWriteTask') {
7374
8143
  const total = props.controller.listPluginWriteTaskCandidates(route.goal)
@@ -7380,7 +8149,12 @@ export function ConsoleAppV4(props) {
7380
8149
  if ((key.pageUp || key.pageDown)
7381
8150
  && route.name === 'review'
7382
8151
  && snapshot.activeReview) {
7383
- const total = Math.max(snapshot.activeReview.changes.length, showTechnical ? snapshot.activeReview.technicalDetails?.length ?? 0 : 0);
8152
+ const userChangeCount = snapshot.activeReview.changes.filter((change) => !isTechnicalReviewGroup(change.group)
8153
+ || change.kind === 'warning'
8154
+ || change.kind === 'remove').length;
8155
+ const total = showTechnical
8156
+ ? Math.max(snapshot.activeReview.changes.length, snapshot.activeReview.technicalDetails?.length ?? 0)
8157
+ : userChangeCount;
7384
8158
  const pageRows = 1;
7385
8159
  setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
7386
8160
  return;
@@ -7930,12 +8704,21 @@ export function ConsoleAppV4(props) {
7930
8704
  return;
7931
8705
  }
7932
8706
  if (route.name === 'pluginList') {
7933
- if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
7934
- setQuery('');
7935
- else if (key.backspace || key.delete)
7936
- setQuery(removeLastGrapheme);
7937
- else if (input && !key.ctrl && !key.meta && input >= ' ')
7938
- setQuery((value) => value + input);
8707
+ const nextQuery = input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
8708
+ ? ''
8709
+ : key.backspace || key.delete
8710
+ ? removeLastGrapheme(query)
8711
+ : input && !key.ctrl && !key.meta && input >= ' '
8712
+ ? query + input : query;
8713
+ setQuery(nextQuery);
8714
+ const matchingItems = props.controller.listPlugins({
8715
+ mode: route.mode,
8716
+ ...(route.sourceId ? { sourceId: route.sourceId } : {}),
8717
+ ...(nextQuery ? { query: nextQuery } : {}),
8718
+ installFilter,
8719
+ });
8720
+ const matchingRows = pluginListRowIds(route.mode, matchingItems, nextQuery);
8721
+ setFocus(matchingItems[0] ? matchingRows.indexOf(matchingItems[0].pluginId) : 0);
7939
8722
  }
7940
8723
  else {
7941
8724
  setBatchDraft((draft) => draft ? {
@@ -7949,7 +8732,8 @@ export function ConsoleAppV4(props) {
7949
8732
  showSelected: false,
7950
8733
  } : draft);
7951
8734
  }
7952
- setFocus(route.name === 'batchSelection' ? 1 : 0);
8735
+ if (route.name === 'batchSelection')
8736
+ setFocus(1);
7953
8737
  setScrollOffset(0);
7954
8738
  return;
7955
8739
  }
@@ -8023,6 +8807,27 @@ export function ConsoleAppV4(props) {
8023
8807
  back();
8024
8808
  }
8025
8809
  else if (globalAction === 'back' && route.name === 'batchPlacement') {
8810
+ if (batchDraft?.showPlacementTargets) {
8811
+ setBatchDraft((current) => current ? { ...current, showPlacementTargets: false } : current);
8812
+ setFocus(5);
8813
+ setScrollOffset(0);
8814
+ }
8815
+ else
8816
+ back();
8817
+ }
8818
+ else if (globalAction === 'back'
8819
+ && route.name === 'batchSelection'
8820
+ && batchDraft
8821
+ && batchDraft.stage !== 'select'
8822
+ && snapshot.router.currentFlowId === batchDraft.flowId) {
8823
+ moveBatchStage(batchDraft.stage === 'maintenance' ? 'task' : 'select');
8824
+ }
8825
+ else if (globalAction === 'back'
8826
+ && batchDraft
8827
+ && route.name !== 'batchSelection'
8828
+ && snapshot.router.currentFlowId === batchDraft.flowId
8829
+ && (route.name !== 'pluginWriteTask'
8830
+ || route.batchOrigin?.flowId === batchDraft.flowId)) {
8026
8831
  back();
8027
8832
  }
8028
8833
  else if (globalAction === 'back'
@@ -8032,8 +8837,7 @@ export function ConsoleAppV4(props) {
8032
8837
  back();
8033
8838
  }
8034
8839
  else if (globalAction === 'back') {
8035
- if (dirty && route.name !== 'review'
8036
- && !(batchDraft && (route.name === 'pluginPreview' || route.name === 'pluginDetail'))) {
8840
+ if (dirty && route.name !== 'review') {
8037
8841
  setOverlayFocus(0);
8038
8842
  setDiscardForExit(false);
8039
8843
  setOverlay('discard');
@@ -8055,12 +8859,11 @@ export function ConsoleAppV4(props) {
8055
8859
  ...(query ? { query } : {}),
8056
8860
  installFilter: nextFilter,
8057
8861
  });
8058
- const retained = selectedPluginId
8059
- ? nextItems.findIndex((item) => item.pluginId === selectedPluginId)
8060
- : -1;
8862
+ const nextRowIds = pluginListRowIds(route.mode, nextItems, query);
8863
+ const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
8061
8864
  setInstallFilter(nextFilter);
8062
8865
  props.controller.updatePageContext({ installFilter: nextFilter });
8063
- setFocus(retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0);
8866
+ setFocus(retained >= 0 ? retained : 0);
8064
8867
  setScrollOffset(0);
8065
8868
  }
8066
8869
  else if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
@@ -8090,11 +8893,11 @@ export function ConsoleAppV4(props) {
8090
8893
  if (batchDraft.mode === 'browse') {
8091
8894
  setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
8092
8895
  props.controller.push({ name: 'batchPlacement' });
8896
+ setFocus(0);
8897
+ setScrollOffset(0);
8093
8898
  }
8094
8899
  else
8095
- setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
8096
- setFocus(0);
8097
- setScrollOffset(0);
8900
+ moveBatchStage('task');
8098
8901
  }
8099
8902
  else
8100
8903
  setActionError('请至少选择一个插件。');
@@ -8161,9 +8964,14 @@ export function ConsoleAppV4(props) {
8161
8964
  const index = tabs.indexOf(current);
8162
8965
  const next = tabs[(index + direction + tabs.length) % tabs.length];
8163
8966
  const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}), installFilter });
8164
- const retained = selectedPluginId ? nextItems.findIndex((item) => item.pluginId === selectedPluginId) : -1;
8967
+ const nextSourceUnavailable = next !== 'all'
8968
+ && props.controller.getSourceOverview(next).health?.state === 'unavailable';
8969
+ const nextRowIds = pluginListRowIds(route.mode, nextItems, query, nextSourceUnavailable);
8970
+ const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
8165
8971
  props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
8166
- const retainedFocus = retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0;
8972
+ const retainedFocus = retained >= 0
8973
+ ? retained
8974
+ : nextItems[0] ? nextRowIds.indexOf(nextItems[0].pluginId) : 0;
8167
8975
  setFocus(retainedFocus);
8168
8976
  setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
8169
8977
  }
@@ -8176,6 +8984,9 @@ export function ConsoleAppV4(props) {
8176
8984
  props.controller.togglePortableSkill(candidate.name);
8177
8985
  }
8178
8986
  else if (key.upArrow || input.toLowerCase() === 'k') {
8987
+ if (route.name === 'pluginDependencyNode' && route.view === 'references') {
8988
+ setBatchTargetOffset(0);
8989
+ }
8179
8990
  setFocus((value) => {
8180
8991
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
8181
8992
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
@@ -8209,6 +9020,9 @@ export function ConsoleAppV4(props) {
8209
9020
  });
8210
9021
  }
8211
9022
  else if (key.downArrow || input.toLowerCase() === 'j') {
9023
+ if (route.name === 'pluginDependencyNode' && route.view === 'references') {
9024
+ setBatchTargetOffset(0);
9025
+ }
8212
9026
  setFocus((value) => {
8213
9027
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
8214
9028
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
@@ -8260,6 +9074,48 @@ export function ConsoleAppV4(props) {
8260
9074
  if (actionError)
8261
9075
  description = actionError;
8262
9076
  const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, help: help, size: size, children: content }));
9077
+ if (operationConfirmation && snapshot.activeReview) {
9078
+ const review = snapshot.activeReview;
9079
+ const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
9080
+ const modalWarningLines = operationRiskLines(review, Math.max(12, modalWidth - 6));
9081
+ const shortWarningLimit = policy.mode === 'compact' ? 4 : 6;
9082
+ const shortConfirmation = modalWarningLines.length <= shortWarningLimit;
9083
+ const warningLines = shortConfirmation
9084
+ ? modalWarningLines
9085
+ : operationRiskLines(review, Math.max(12, size.columns - 8));
9086
+ const warningLimit = Math.max(1, policy.mode === 'compact' ? 3 : 7);
9087
+ const warningOffset = Math.min(operationConfirmation.warningOffset, Math.max(0, warningLines.length - warningLimit));
9088
+ const visibleWarnings = shortConfirmation
9089
+ ? warningLines : warningLines.slice(warningOffset, warningOffset + warningLimit);
9090
+ const confirmationActions = [
9091
+ { label: '返回检查', description: '回到完整操作汇总' },
9092
+ { label: `我已了解影响,${review.primaryLabel}`, status: 'error' },
9093
+ ];
9094
+ 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
9095
+ ? visibleWarnings.map((warning, index) => _jsx(StaticRow, { title: warning, status: warning.startsWith('! ') ? 'warning' : 'info', compact: true }, `${warningOffset + index}:${warning}`))
9096
+ : _jsx(StaticRow, { title: "! \u6B64\u64CD\u4F5C\u9700\u8981\u660E\u786E\u6388\u6743", status: "warning", compact: true }), !shortConfirmation && warningLines.length - warningOffset - visibleWarnings.length > 0
9097
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${warningLines.length - warningOffset - visibleWarnings.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: confirmationActions, focus: overlayFocus, kind: "action", compact: true })] }));
9098
+ if (shortConfirmation) {
9099
+ 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 })] });
9100
+ }
9101
+ 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 });
9102
+ }
9103
+ if (sourceTransportOverlay) {
9104
+ const configuredSource = route.name === 'sourceEdit'
9105
+ ? props.controller.getConfiguredSource(route.sourceId)
9106
+ : undefined;
9107
+ const currentLocation = sourceDraft?.owner === sourceTransportOverlay.owner
9108
+ && sourceDraft.type === 'git'
9109
+ ? sourceDraft.location
9110
+ : configuredSource?.type === 'git' ? configuredSource.url : undefined;
9111
+ const currentTransport = currentLocation === OFFICIAL_CATALOG_SOURCE_URL ? 'HTTPS'
9112
+ : currentLocation === OFFICIAL_CATALOG_SOURCE_SSH_URL ? 'SSH' : undefined;
9113
+ const transportActions = [
9114
+ { label: 'HTTPS', description: '默认方式;无需预先配置 SSH 密钥', selected: currentTransport === 'HTTPS' },
9115
+ { label: 'SSH', description: '适合已配置 GitHub SSH 访问的用户', selected: currentTransport === 'SSH' },
9116
+ ];
9117
+ 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 }) }) })] });
9118
+ }
8263
9119
  if (!overlay)
8264
9120
  return base;
8265
9121
  const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy, props.onSessionNavigation !== undefined);