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

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 (47) hide show
  1. package/dist/src/commands/operation-planner.d.ts +3 -3
  2. package/dist/src/commands/rebuild-command.js +0 -5
  3. package/dist/src/commands/rebuild-command.js.map +1 -1
  4. package/dist/src/commands/rebuild-integration.d.ts +0 -1
  5. package/dist/src/commands/rebuild-integration.js +3 -152
  6. package/dist/src/commands/rebuild-integration.js.map +1 -1
  7. package/dist/src/commands/status-v6.d.ts +81 -31
  8. package/dist/src/commands/status-v6.js +494 -183
  9. package/dist/src/commands/status-v6.js.map +1 -1
  10. package/dist/src/commands/wizard.d.ts +2 -2
  11. package/dist/src/index.js +2 -2
  12. package/dist/src/tui/catalog-sources/projection.d.ts +2 -2
  13. package/dist/src/tui/catalog-sources/screens.js +17 -10
  14. package/dist/src/tui/catalog-sources/screens.js.map +1 -1
  15. package/dist/src/tui/console-app-v4.js +1300 -437
  16. package/dist/src/tui/console-app-v4.js.map +1 -1
  17. package/dist/src/tui/controller-runtime.d.ts +3 -3
  18. package/dist/src/tui/controller-runtime.js +114 -36
  19. package/dist/src/tui/controller-runtime.js.map +1 -1
  20. package/dist/src/tui/controller.d.ts +17 -7
  21. package/dist/src/tui/controller.js +269 -24
  22. package/dist/src/tui/controller.js.map +1 -1
  23. package/dist/src/tui/dependency-graph.d.ts +3 -0
  24. package/dist/src/tui/dependency-graph.js +37 -1
  25. package/dist/src/tui/dependency-graph.js.map +1 -1
  26. package/dist/src/tui/diagnostic-projection.d.ts +1 -0
  27. package/dist/src/tui/diagnostic-projection.js +51 -4
  28. package/dist/src/tui/diagnostic-projection.js.map +1 -1
  29. package/dist/src/tui/model.d.ts +22 -12
  30. package/dist/src/tui/model.js +251 -86
  31. package/dist/src/tui/model.js.map +1 -1
  32. package/dist/src/tui/operation-history.d.ts +1 -0
  33. package/dist/src/tui/operation-history.js +5 -0
  34. package/dist/src/tui/operation-history.js.map +1 -1
  35. package/dist/src/tui/operation-review-planner.js +67 -2
  36. package/dist/src/tui/operation-review-planner.js.map +1 -1
  37. package/dist/src/tui/plugin-write-task.d.ts +2 -0
  38. package/dist/src/tui/plugin-write-task.js.map +1 -1
  39. package/dist/src/tui/router.d.ts +6 -0
  40. package/dist/src/tui/router.js +56 -12
  41. package/dist/src/tui/router.js.map +1 -1
  42. package/dist/src/tui/screen-registry.d.ts +7 -0
  43. package/dist/src/utils/plugin-config-write.js +3 -3
  44. package/dist/src/utils/plugin-config-write.js.map +1 -1
  45. package/dist/src/utils/target-eligibility.d.ts +2 -2
  46. package/dist/src/utils/target-eligibility.js.map +1 -1
  47. 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,11 +4924,18 @@ 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' ? '可用'
4384
- : placement.actualState === 'needsSync' ? '需要同步'
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' ? '可用'
4928
+ : placement.actualState === 'needsSync'
4929
+ ? placement.sharedNative?.reason === 'outdated'
4930
+ ? placement.revision.state === 'upgradeAvailable' ? '有更新' : '版本待同步'
4931
+ : '需要同步'
4385
4932
  : placement.actualState === 'blocked' ? '已阻断'
4386
4933
  : placement.health === 'incomplete' ? '需要处理' : '暂时不可用', status: placement.actualState === 'ready' ? 'success' : placement.actualState === 'blocked' ? 'error' : 'warning', compact: true }), placement.health !== 'ready'
4387
- ? _jsx(StaticRow, { title: "\u539F\u56E0", meta: placement.blockingReason === 'artifactCollision' ? '输出位置存在冲突内容' : '当前安装与已保存设置不一致', status: "warning", compact: true })
4934
+ ? _jsx(StaticRow, { title: "\u539F\u56E0", meta: placement.blockingReason === 'artifactCollision'
4935
+ ? '输出位置存在冲突内容'
4936
+ : placement.sharedNative?.reason === 'outdated'
4937
+ ? '已安装版本与当前 Catalog 版本不一致'
4938
+ : typedIssue?.explanation ?? '安装状态需要同步,请查看问题详情', status: "warning", compact: true })
4388
4939
  : null] }) : null;
4389
4940
  if (plugin.aggregateStatus === 'unavailable') {
4390
4941
  const sourceOverview = capabilityUnavailable ? undefined : props.controller.getSourceOverview(plugin.sourceId);
@@ -4697,12 +5248,24 @@ export function ConsoleAppV4(props) {
4697
5248
  description = route.layer.scope === 'project'
4698
5249
  ? '修改将保存到当前项目。'
4699
5250
  : '修改将保存为个人默认,并影响个人的所有项目;项目设置仍可覆盖。';
4700
- actions = configSaveImpact ? [
5251
+ actions = configSaveImpactError ? [
5252
+ {
5253
+ label: '重新检查影响',
5254
+ description: '重新读取当前安装位置;检查成功前不能保存',
5255
+ run: () => setConfigSaveImpactAttempt((value) => value + 1),
5256
+ },
5257
+ { label: '返回修改', run: back },
5258
+ ] : configSaveImpact ? [
4701
5259
  hasConsumers ? {
4702
5260
  label: '保存并生效',
4703
5261
  description: `保存设置,并重新应用:${targetSummary}`,
4704
5262
  status: 'success',
4705
5263
  run: () => beginConfigSave(true),
5264
+ } : configSaveImpact.impactState === 'unknown' ? {
5265
+ label: '仅保存设置(应用范围尚未确认)',
5266
+ description: '保存所选设置层;不扩大到未确认的安装位置',
5267
+ status: 'warning',
5268
+ run: () => beginConfigSave(false),
4706
5269
  } : {
4707
5270
  label: '保存设置',
4708
5271
  description: '当前修改没有需要重新应用的已安装目标',
@@ -4713,6 +5276,10 @@ export function ConsoleAppV4(props) {
4713
5276
  label: '只保存,稍后应用',
4714
5277
  description: '保存所选设置层,不重新应用当前目标',
4715
5278
  run: () => beginConfigSave(false),
5279
+ }] : configSaveImpact.impactState === 'unknown' ? [{
5280
+ label: '重新检查影响',
5281
+ description: '再次读取当前安装位置和配置消费者',
5282
+ run: () => setConfigSaveImpactAttempt((value) => value + 1),
4716
5283
  }] : []),
4717
5284
  { label: '返回修改', run: back },
4718
5285
  ] : [{ label: '返回修改', run: back }];
@@ -4720,9 +5287,11 @@ export function ConsoleAppV4(props) {
4720
5287
  ? _jsx(ProgressRow, { title: "\u5206\u6790\u5B9E\u9645\u914D\u7F6E\u6D88\u8D39\u8005", state: "active" })
4721
5288
  : hasConsumers
4722
5289
  ? 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' })] });
5290
+ : _jsx(StaticRow, { title: configSaveImpactError
5291
+ ? '影响检查失败;设置尚未保存'
5292
+ : configSaveImpact?.impactState === 'unknown'
5293
+ ? '应用范围尚未确认;可以仅保存并稍后重查'
5294
+ : '没有需要重新应用的已安装目标', 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
5295
  }
4727
5296
  else if (route.name === 'configFieldAction' && plugin && activeField) {
4728
5297
  const layer = configLayerLabel(route.layer);
@@ -5251,7 +5820,9 @@ export function ConsoleAppV4(props) {
5251
5820
  const overview = props.controller.getSourceOverview(source.id);
5252
5821
  const generation = props.controller.getCatalogGeneration(source.id);
5253
5822
  const freshness = props.controller.getCatalogFreshness(source.id);
5254
- const health = overview.health?.state ?? source.state;
5823
+ const health = generation?.state !== undefined && generation.state !== 'ready'
5824
+ ? 'unavailable'
5825
+ : overview.health?.state ?? source.state;
5255
5826
  const presentation = catalogSourceListPresentation({
5256
5827
  freshness,
5257
5828
  activeVersion: generation?.active?.catalogVersion,
@@ -5302,16 +5873,16 @@ export function ConsoleAppV4(props) {
5302
5873
  : freshness?.state === 'incompatible'
5303
5874
  ? generationState === 'repairRequired'
5304
5875
  ? `待检查版本需要升级 AW 或修改来源后才能使用;损坏的当前启用版本仍可按旧版本精确修复。`
5305
- : activeVersion
5876
+ : activeVersion && generationState === 'ready'
5306
5877
  ? `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;当前目录版本 ${activeVersion} 仍可浏览和使用。`
5307
5878
  : `当前 AW 缺少 ${freshness.missingCapabilities?.length ?? 0} 项待检查版本所需功能;这个来源没有可用目录版本。`
5308
- : freshness?.state === 'checkFailed' && activeVersion
5879
+ : freshness?.state === 'checkFailed' && activeVersion && generationState === 'ready'
5309
5880
  ? `更新检查失败,但当前目录版本 ${activeVersion} 仍可浏览和使用。`
5310
- : freshness?.state === 'updateAvailable'
5881
+ : freshness?.state === 'updateAvailable' && generationState === 'ready'
5311
5882
  ? `当前使用 ${activeVersion ?? '未启用'};待检查版本 ${candidateVersion ?? '未知'} 可更新。`
5312
- : freshness?.state === 'localChangesAvailable'
5883
+ : freshness?.state === 'localChangesAvailable' && generationState === 'ready'
5313
5884
  ? '本地插件目录内容已变化;确认前仍使用当前启用版本。'
5314
- : activeVersion ? `当前使用目录版本 ${activeVersion}。`
5885
+ : activeVersion && generationState === 'ready' ? `当前使用目录版本 ${activeVersion}。`
5315
5886
  : generationState === 'stateInvalid' ? '目录版本状态无效。确认恢复后可以重新启用这个来源。'
5316
5887
  : generationState === 'repairRequired' ? combinedRepairAndMismatch
5317
5888
  ? '当前目录版本损坏,且来源配置已变化。验证待检查版本后可精确修复同一版本,或明确切换到新版本。'
@@ -5384,7 +5955,7 @@ export function ConsoleAppV4(props) {
5384
5955
  run: () => props.controller.recheckCatalogSource(route.sourceId),
5385
5956
  }];
5386
5957
  const managementActions = [
5387
- { label: '编辑来源…', description: '集中修改地址、引用、子目录和自动检查', run: () => openSourceEdit(route.sourceId) },
5958
+ { label: '来源设置', description: '下载方式、版本、自动检查和高级地址目录', run: () => openSourceEdit(route.sourceId) },
5388
5959
  ...(generationState === 'incompatible'
5389
5960
  || (freshness?.state === 'incompatible' && generationState !== 'ready') ? [] : [{
5390
5961
  label: generationState === 'ready' ? '设为主来源…' : '启用并设为主来源…',
@@ -5422,18 +5993,43 @@ export function ConsoleAppV4(props) {
5422
5993
  run: () => props.controller.fullValidateCatalogSource(route.sourceId),
5423
5994
  },
5424
5995
  ];
5996
+ const sourcePrimaryAction = generationAction.length === 0 ? [] : [{
5997
+ ...generationAction[0],
5998
+ ...(generationState === 'repairRequired' || generationState === 'stateInvalid'
5999
+ || generationState === 'unavailable'
6000
+ ? {
6001
+ label: '恢复来源',
6002
+ description: generationState === 'repairRequired'
6003
+ ? '优先取得当前版本并精确修复;不会自动切换版本'
6004
+ : '取得并验证可用目录后再启用',
6005
+ status: 'warning',
6006
+ }
6007
+ : {}),
6008
+ }];
5425
6009
  actions = [
5426
- ...generationAction.slice(0, 1),
6010
+ ...sourcePrimaryAction,
5427
6011
  managementActions[0],
5428
6012
  {
5429
- label: '更多处理方式…',
5430
- description: '设为主来源、移除、精确修复、替换、重新检查和完整验证',
6013
+ label: '更多操作…',
6014
+ description: '替换候选、主来源、移除、检查和技术详情',
5431
6015
  run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
5432
6016
  },
5433
6017
  ];
5434
6018
  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 })] });
6019
+ const freshnessLabel = freshness?.state === 'checkFailed' && generationState !== 'ready'
6020
+ ? '检查失败' : catalogSourceFreshnessLabel(freshness);
6021
+ const sourceStateLabel = generationState === 'ready' ? '来源可用'
6022
+ : generationState === 'repairRequired' ? '来源目录损坏,当前无法使用'
6023
+ : generationState === 'incompatible' ? '当前 AW 无法使用这个目录版本'
6024
+ : generationState === 'configMismatch' ? '来源设置与当前目录不一致'
6025
+ : generationState === 'stateInvalid' ? '来源状态需要恢复'
6026
+ : '来源尚未启用';
6027
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { title: sourceStateLabel, status: generationState === 'ready' ? 'success'
6028
+ : generationState === 'incompatible' ? 'error' : 'warning', description: activeVersion ? `当前版本 ${activeVersion}` : '当前没有可用目录版本', children: [_jsx(StaticRow, { title: "\u5F71\u54CD", meta: generationState === 'ready'
6029
+ ? freshness?.state === 'checkFailed' ? '更新检查失败;当前版本继续可用'
6030
+ : freshness?.state === 'updateAvailable' ? `可选择更新到 ${candidateVersion ?? '待检查版本'}`
6031
+ : '插件浏览和现有使用不受阻止'
6032
+ : '当前不能从这个来源浏览或恢复插件', 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
6033
  }
5438
6034
  else if (route.name === 'sourceMore') {
5439
6035
  const overview = props.controller.getSourceOverview(route.sourceId);
@@ -5618,32 +6214,37 @@ export function ConsoleAppV4(props) {
5618
6214
  const draft = activeDraft;
5619
6215
  breadcrumb = `插件来源 / ${route.sourceId} / 编辑`;
5620
6216
  title = draft?.mode === 'add' ? `检查新来源 ${draft.sourceId}` : `编辑 ${route.sourceId}`;
5621
- description = '地址、引用、子目录和自动检查组成一个草稿;保存前统一 Review。';
6217
+ description = '在一个草稿中修改来源设置,最后查看修改汇总。';
5622
6218
  const locationSummary = draft
5623
6219
  ? projectCatalogSourceLocation(draft.location)
5624
6220
  : 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
- };
6221
+ const configuredSource = props.controller.getConfiguredSource(route.sourceId);
6222
+ const currentLocation = draft?.location
6223
+ ?? (configuredSource?.type === 'git' ? configuredSource.url : undefined);
6224
+ const officialTransport = currentLocation === OFFICIAL_CATALOG_SOURCE_URL ? 'HTTPS'
6225
+ : currentLocation === OFFICIAL_CATALOG_SOURCE_SSH_URL ? 'SSH' : undefined;
6226
+ const catalogSubdir = draft ? draft.subdir : detail?.subdir;
5639
6227
  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') }]),
6228
+ ...(route.sourceId === 'official' && officialTransport ? [{
6229
+ label: '下载方式',
6230
+ meta: officialTransport,
6231
+ description: officialTransport === 'HTTPS'
6232
+ ? '默认方式;无需预先配置 SSH 密钥'
6233
+ : '适合已配置 GitHub SSH 访问的用户',
6234
+ run: () => {
6235
+ const active = draft ?? (configuredSource
6236
+ ? sourceDraftFromSource(configuredSource, `source-update:${route.sourceId}:${Date.now()}`)
6237
+ : undefined);
6238
+ if (!active)
6239
+ return;
6240
+ setSourceTransportOverlay({ owner: active.owner });
6241
+ setOverlayFocus(officialTransport === 'HTTPS' ? 0 : 1);
6242
+ },
6243
+ }] : [{ label: draft?.type === 'path' || detail?.type === 'path' ? '本地目录' : 'Git 仓库地址', description: locationSummary, run: () => startDraftField('location') }]),
5644
6244
  ...(draft?.type === 'git' || !draft && detail?.type === 'git' ? [
5645
6245
  { 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') },
6246
+ { label: '高级:Catalog 子目录', description: catalogSubdir === '' || catalogSubdir === undefined
6247
+ ? '仓库根目录' : catalogSubdir, run: () => startDraftField('subdir') },
5647
6248
  ] : []),
5648
6249
  { label: '自动检查更新', description: (draft?.autoCheck ?? detail?.autoCheck) ? '已开启' : '已关闭', selected: draft?.autoCheck ?? detail?.autoCheck, run: () => {
5649
6250
  if (!draft) {
@@ -5668,20 +6269,28 @@ export function ConsoleAppV4(props) {
5668
6269
  go({ name: 'sourceSave', owner: draft.owner });
5669
6270
  } },
5670
6271
  ];
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] });
6272
+ 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
6273
  }
5673
6274
  else if (route.name === 'sourceSave' && sourceDraft?.owner === route.owner) {
5674
6275
  const changes = sourceDraftChanges(sourceDraft);
6276
+ const savedSource = props.controller.getConfiguredSource(sourceDraft.sourceId);
6277
+ const protocolOnly = changes.length === 1 && changes[0]?.title === 'Git 仓库地址'
6278
+ && savedSource?.type === 'git'
6279
+ && (savedSource.url === OFFICIAL_CATALOG_SOURCE_URL
6280
+ || savedSource.url === OFFICIAL_CATALOG_SOURCE_SSH_URL)
6281
+ && (sourceDraft.location === OFFICIAL_CATALOG_SOURCE_URL
6282
+ || sourceDraft.location === OFFICIAL_CATALOG_SOURCE_SSH_URL);
5675
6283
  breadcrumb = `插件来源 / ${sourceDraft.sourceId} / 修改汇总`;
5676
6284
  context = `${changes.length} 项未保存`;
5677
6285
  title = sourceDraft.mode === 'add' ? `保存新来源 ${sourceDraft.sourceId}?` : `保存 ${sourceDraft.sourceId} 的修改?`;
5678
6286
  description = '选择是否在保存后验证候选并启用。保存成功与启用成功会分别报告。';
5679
6287
  actions = [
5680
- { label: '保存并启用', description: '保存来源配置,再验证并切换当前目录版本', status: 'success', run: () => beginSourceReview(sourceDraft, true) },
5681
- { label: '只保存', description: '只保存来源配置;当前目录版本保持不变', run: () => beginSourceReview(sourceDraft, false) },
6288
+ ...(protocolOnly ? [] : [{ label: '保存并启用', description: '保存来源配置,再验证并切换当前目录版本', status: 'success', run: () => beginSourceReview(sourceDraft, true) }]),
6289
+ { label: protocolOnly ? '只保存下载方式' : '只保存', description: '只保存来源配置;当前目录版本保持不变', status: protocolOnly ? 'success' : undefined, run: () => beginSourceReview(sourceDraft, false) },
6290
+ ...(protocolOnly ? [{ label: '保存并启用…', description: '保存后验证并切换当前目录版本', run: () => beginSourceReview(sourceDraft, true) }] : []),
5682
6291
  { label: '返回修改', run: back },
5683
6292
  ];
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' })] });
6293
+ 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
6294
  }
5686
6295
  else if (route.name === 'scriptTimeout' && pendingReviewIntent) {
5687
6296
  breadcrumb = '高级选项 / 安装前检查脚本';
@@ -5796,12 +6405,12 @@ export function ConsoleAppV4(props) {
5796
6405
  go({ name: 'pluginDependencies', pluginId: issue.pluginId });
5797
6406
  }
5798
6407
  else if (entry.id === 'viewDependencyIssue' && entry.pluginId) {
5799
- const dependencyIssue = props.controller.listUserIssues().find((candidate) => candidate.pluginId === entry.pluginId
6408
+ const dependencyIssue = props.controller.listUserIssues().find((candidate) => issueMatchesTarget(candidate, entry.pluginId, issue.dependencyPlatform, issue.dependencyScope)
5800
6409
  && candidate.issueId !== issue.issueId);
5801
6410
  if (dependencyIssue)
5802
6411
  go({ name: 'issueDetail', issueId: dependencyIssue.issueId });
5803
- else
5804
- go({ name: 'pluginDependencies', pluginId: entry.pluginId });
6412
+ else if (!showTechnical)
6413
+ toggleTechnical();
5805
6414
  }
5806
6415
  else
5807
6416
  toggleTechnical();
@@ -6031,14 +6640,12 @@ export function ConsoleAppV4(props) {
6031
6640
  const batchReview = review.operationKind === 'pluginWriteBatch';
6032
6641
  const taskReview = review.operationKind === 'pluginWriteTask';
6033
6642
  const catalogManagementReview = review.operationKind === 'catalogManagement';
6034
- const sharedImpactConfirmed = !review.requiresConfirmation
6035
- || confirmedSharedReviewToken === review.reviewToken;
6036
6643
  breadcrumb = `${review.subject.name} / ${review.title}`;
6037
6644
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
6038
6645
  ? review.title : `确认${review.title}`} ${review.subject.name}`;
6039
6646
  description = snapshot.result?.state === 'stale'
6040
6647
  ? '当前状态已变化。请检查新的变化后再次确认。'
6041
- : review.requiresConfirmation ? '' : review.description;
6648
+ : review.description;
6042
6649
  if (taskReview) {
6043
6650
  context = '精确目标 · 用户任务';
6044
6651
  help = 'PgUp/PgDn 查看全部影响 · ↑↓ 移动 · Enter 选择 · Esc 返回';
@@ -6048,20 +6655,19 @@ export function ConsoleAppV4(props) {
6048
6655
  help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6049
6656
  }
6050
6657
  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: () => {
6658
+ { label: review.primaryLabel, description: review.requiresConfirmation
6659
+ ? '打开独立风险确认;执行时再次检查当前状态'
6660
+ : '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
6661
+ if (review.requiresConfirmation) {
6662
+ setOperationConfirmation({
6663
+ reviewToken: review.reviewToken,
6664
+ returnFocus: focus,
6665
+ returnScrollOffset: scrollOffset,
6666
+ warningOffset: 0,
6667
+ });
6668
+ setOverlayFocus(0);
6669
+ return;
6670
+ }
6065
6671
  if (operationBusy.current)
6066
6672
  return;
6067
6673
  operationBusy.current = true;
@@ -6072,6 +6678,65 @@ export function ConsoleAppV4(props) {
6072
6678
  operationBusy.current = false;
6073
6679
  });
6074
6680
  } },
6681
+ ...(taskReview
6682
+ && pendingReviewIntent?.kind === 'pluginWriteTask'
6683
+ && pendingReviewIntent.goal === 'update'
6684
+ && batchDraft?.frozenPluginIds ? [{
6685
+ label: `调整选择(${new Set(pendingReviewIntent.targets
6686
+ .map((target) => target.pluginId)).size})`,
6687
+ description: '只可删减或恢复本次冻结的推荐插件;调整后重新读取证据并建立 Review',
6688
+ run: () => {
6689
+ if (!props.controller.returnToPluginWriteTaskBatchSelection(batchDraft.flowId)) {
6690
+ setActionError('推荐选择已失效。请返回我的插件重新开始。');
6691
+ return;
6692
+ }
6693
+ setBatchDraft((current) => current ? {
6694
+ ...current,
6695
+ stage: 'select',
6696
+ showSelected: false,
6697
+ } : current);
6698
+ setFocus(0);
6699
+ setScrollOffset(0);
6700
+ setShowTechnical(false);
6701
+ setActionError(undefined);
6702
+ },
6703
+ }] : []),
6704
+ ...(taskReview
6705
+ && pendingReviewIntent?.kind === 'pluginWriteTask'
6706
+ && route.name === 'review'
6707
+ && batchDraft ? [{
6708
+ label: '修改处理范围',
6709
+ description: '只可删减或恢复本次已有位置;修改后重新读取证据',
6710
+ run: () => {
6711
+ const targets = pendingReviewIntent.targets;
6712
+ setBatchDraft((current) => current ? {
6713
+ ...current,
6714
+ maintenanceGoal: pendingReviewIntent.goal,
6715
+ maintenanceTargets: current.maintenanceGoal === pendingReviewIntent.goal
6716
+ && current.maintenanceTargets
6717
+ ? current.maintenanceTargets : targets,
6718
+ selectedMaintenanceTargetKeys: targets.map(batchTargetKey),
6719
+ } : current);
6720
+ back();
6721
+ props.controller.push({ name: 'batchPlacement' });
6722
+ setFocus(0);
6723
+ setScrollOffset(0);
6724
+ },
6725
+ }] : []),
6726
+ ...(batchReview
6727
+ && pendingReviewIntent?.kind === 'pluginWriteBatch'
6728
+ && pendingReviewIntent.selection.kind === 'targets'
6729
+ && pendingReviewIntent.action !== 'install'
6730
+ && batchDraft?.maintenanceTargets ? [{
6731
+ label: '修改处理范围',
6732
+ description: '只可删减或恢复本次已有位置;修改后重新读取证据',
6733
+ run: () => {
6734
+ back();
6735
+ props.controller.push({ name: 'batchPlacement' });
6736
+ setFocus(0);
6737
+ setScrollOffset(0);
6738
+ },
6739
+ }] : []),
6075
6740
  ...(installReviewPluginId
6076
6741
  && (snapshot.installPlacementDraft?.targets.length ?? 0) > 0 ? [{
6077
6742
  label: '查看/调整依赖位置',
@@ -6085,7 +6750,7 @@ export function ConsoleAppV4(props) {
6085
6750
  setScrollOffset(0);
6086
6751
  },
6087
6752
  }] : []),
6088
- ...(sharedImpactConfirmed && (pendingReviewIntent?.kind === 'pluginWrite'
6753
+ ...((pendingReviewIntent?.kind === 'pluginWrite'
6089
6754
  && (pendingReviewIntent.action === 'install'
6090
6755
  || pendingReviewIntent.action === 'refreshAndSync'
6091
6756
  || pendingReviewIntent.action === 'reinstall'
@@ -6111,7 +6776,7 @@ export function ConsoleAppV4(props) {
6111
6776
  : back },
6112
6777
  ];
6113
6778
  const groupedChanges = review.changes.some((change) => change.group);
6114
- const compressedReview = policy.mode === 'compact' || review.requiresConfirmation;
6779
+ const compressedReview = policy.mode === 'compact';
6115
6780
  const groupTitle = {
6116
6781
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
6117
6782
  dependencies: review.operationKind === 'portableInstall' ? '已选择 Skills · 来自依赖插件' : '必需依赖',
@@ -6149,8 +6814,15 @@ export function ConsoleAppV4(props) {
6149
6814
  'projectIntent', 'userIntent', 'sharedCodexNative',
6150
6815
  'userActivation', 'projectActivation', 'awExtensions', 'runtime',
6151
6816
  ]);
6152
- const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0
6153
- && !technicalGroups.has(group.key));
6817
+ const userReviewChanges = review.changes.filter((change) => change.group === undefined
6818
+ || !isTechnicalReviewGroup(change.group)
6819
+ || change.kind === 'warning'
6820
+ || change.kind === 'remove');
6821
+ const visibleSpecs = groupedSpecs.map((group) => technicalGroups.has(group.key) ? {
6822
+ ...group,
6823
+ label: '风险',
6824
+ changes: group.changes.filter((change) => change.kind === 'warning' || change.kind === 'remove'),
6825
+ } : group).filter((group) => group.changes.length > 0);
6154
6826
  const headingLines = compressedReview ? 0 : visibleSpecs.filter((group) => group.label).length;
6155
6827
  const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
6156
6828
  const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
@@ -6186,15 +6858,15 @@ export function ConsoleAppV4(props) {
6186
6858
  })] }, group.key);
6187
6859
  });
6188
6860
  const batchChangeLimit = Math.max(1, responsiveBudget(6, 10) - actions.length);
6189
- const batchChanges = review.changes.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
6861
+ const batchChanges = userReviewChanges.slice(batchTargetOffset, batchTargetOffset + batchChangeLimit);
6190
6862
  const batchHiddenBefore = batchTargetOffset;
6191
6863
  const prioritizedReviewChanges = [
6192
- ...review.changes.filter((change) => change.kind === 'warning'),
6193
- ...review.changes.filter((change) => change.kind !== 'warning'),
6864
+ ...userReviewChanges.filter((change) => change.kind === 'warning'),
6865
+ ...userReviewChanges.filter((change) => change.kind !== 'warning'),
6194
6866
  ];
6195
6867
  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] })
6868
+ const changeGroups = batchReview || userReviewChanges.length > staticRowBudget
6869
+ ? _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
6870
  : standardChangeGroups;
6199
6871
  const technicalDetails = review.technicalDetails ?? [];
6200
6872
  const technicalDetailLimit = responsiveBudget(1, 3);
@@ -6231,11 +6903,11 @@ export function ConsoleAppV4(props) {
6231
6903
  const widenedDependencyCount = new Set(installDependencyTargets
6232
6904
  .filter((target) => target.affectsOtherProjects && target.action !== 'keep')
6233
6905
  .map((target) => target.pluginId)).size;
6234
- if (review.changes.length > staticRowBudget || showTechnical
6906
+ if (userReviewChanges.length > staticRowBudget || showTechnical
6235
6907
  && (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
6236
6908
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6237
6909
  }
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] }) });
6910
+ 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
6911
  }
6240
6912
  else if (route.name === 'progress') {
6241
6913
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -6250,15 +6922,26 @@ export function ConsoleAppV4(props) {
6250
6922
  breadcrumb = catalogManagementOperation ? '目录管理 / 进度' : taskOperation ? '插件任务 / 进度' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
6251
6923
  const configApply = snapshot.activeReview?.operationKind === 'configSave'
6252
6924
  && snapshot.activeReview.changes.some((change) => change.group === 'configImpact' && change.kind === 'change');
6925
+ const progressAction = snapshot.activeReview?.taskGoal === 'restore' ? '恢复'
6926
+ : snapshot.activeReview?.taskGoal === 'update' ? '更新'
6927
+ : configApply ? '应用设置'
6928
+ : snapshot.activeReview?.pluginAction === 'install' ? '添加'
6929
+ : snapshot.activeReview?.pluginAction === 'refreshAndSync' ? '更新'
6930
+ : snapshot.activeReview?.pluginAction === 'reinstall' ? '恢复'
6931
+ : snapshot.activeReview?.pluginAction === 'uninstall' ? '移除'
6932
+ : snapshot.activeReview?.pluginAction === 'cleanup' ? '清理'
6933
+ : snapshot.activeReview?.pluginAction === 'sync' ? '同步' : '处理';
6253
6934
  const catalogGenerationOperation = snapshot.activeReview?.operationKind === 'catalogGeneration';
6254
6935
  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、开发来源或发布操作。'
6936
+ title = catalogManagementOperation
6937
+ ? `正在${snapshot.activeReview?.title.replace(/^确认/u, '') ?? '完成目录管理任务'}`
6938
+ : taskOperation ? `正在${progressAction}插件` : portableOperation ? '正在安装通用 Skills' : issueOperation ? `正在${progressAction}插件`
6939
+ : catalogGenerationOperation ? '正在验证并启用目录版本'
6940
+ : catalogSourcesRecovery ? '正在恢复插件来源配置'
6941
+ : sourceOperation ? '正在保存来源配置'
6942
+ : configOperation ? configApply ? '正在保存并应用设置' : '正在保存设置'
6943
+ : `正在${progressAction}插件`;
6944
+ description = catalogManagementOperation ? '正在完成已确认的目录管理任务。'
6262
6945
  : issueOperation ? '完成后会重新读取状态并检查问题。'
6263
6946
  : catalogGenerationOperation ? '正在验证并应用已确认的目录版本操作。'
6264
6947
  : catalogSourcesRecovery ? '正在备份损坏配置并恢复官方默认来源。'
@@ -6289,7 +6972,7 @@ export function ConsoleAppV4(props) {
6289
6972
  if (entry.state === 'completed')
6290
6973
  return { label: '已完成', status: 'success' };
6291
6974
  if (entry.state === 'running')
6292
- return { label: '正在添加', status: 'info' };
6975
+ return { label: `正在${progressAction}`, status: 'info' };
6293
6976
  if (entry.state === 'failed')
6294
6977
  return { label: '失败', status: 'error' };
6295
6978
  if (entry.state === 'skipped')
@@ -6367,13 +7050,13 @@ export function ConsoleAppV4(props) {
6367
7050
  return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
6368
7051
  };
6369
7052
  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) => {
7053
+ 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
7054
  const state = progressLabel(entry);
6372
7055
  const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
6373
7056
  const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
6374
7057
  ?? entry.pluginId.split('/').at(-1);
6375
7058
  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
7059
+ }), 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
7060
  ? dependencyMessageLimit > 0 ? technicalMessages.slice(-dependencyMessageLimit) : []
6378
7061
  : 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
7062
  }
@@ -6494,15 +7177,15 @@ export function ConsoleAppV4(props) {
6494
7177
  : taskOperation
6495
7178
  ? blocked ? '任务被安全阻止。已完成和未完成目标按实际结果保留。'
6496
7179
  : partial ? `${taskSummary?.retryTargets?.length ?? 0} 个失败目标可重试 · ${taskBlockedCount} 个目标需要决定。`
6497
- : `${taskSummary?.rootResults.length ?? 0} 个根插件已完成。`
7180
+ : `${taskSummary?.rootResults.length ?? 0} 个所选插件已处理;下方显示使用位置。`
6498
7181
  : batchOperation
6499
7182
  ? blocked
6500
7183
  ? '批量写入被阻止。已完成和未完成目标按实际结果保留。'
6501
7184
  : partial
6502
7185
  ? (resultSummary?.batch?.retryTargets?.length ?? 0) > 0
6503
- ? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个根插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
7186
+ ? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个所选插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
6504
7187
  : '失败目标无法精确定位。请查看本次记录后重新操作。'
6505
- : `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个根插件已完成。`
7188
+ : `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个所选插件已处理;下方显示使用位置和所需插件。`
6506
7189
  : portableOperation
6507
7190
  ? blocked
6508
7191
  ? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
@@ -6898,34 +7581,35 @@ export function ConsoleAppV4(props) {
6898
7581
  return { label: '未开始', status: 'muted' };
6899
7582
  return { label: '待确认', status: 'warning' };
6900
7583
  };
6901
- const taskResultRows = taskRootResults.flatMap((root) => {
7584
+ const taskSelectedPluginRows = taskRootResults.map((root) => {
6902
7585
  const rootState = taskRootState(root.state);
6903
7586
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
6904
7587
  ?? root.pluginId.split('/').at(-1);
6905
7588
  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
- ];
7589
+ return _jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`);
7590
+ });
7591
+ const taskLocationRows = taskRootResults.flatMap((root) => {
7592
+ const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
7593
+ return requestedTargets.map((target) => {
7594
+ const key = [target.pluginId, target.platform, target.scope].join('\0');
7595
+ const blockedTarget = blockedTaskTargets.get(key);
7596
+ const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
7597
+ && entry.scope === target.scope);
7598
+ const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
7599
+ : targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
7600
+ : targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
7601
+ : targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
7602
+ : { label: '未开始', status: 'muted' };
7603
+ return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
7604
+ ? pluginWriteTaskBlockLabel(blockedTarget.reason)
7605
+ : targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
7606
+ });
6923
7607
  });
6924
7608
  const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
6925
7609
  const rootState = taskRootState(root.state);
6926
7610
  const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
6927
7611
  ?? 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}`);
7612
+ return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个使用位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
6929
7613
  }).concat(prioritizedTargets.map((target) => {
6930
7614
  const state = operationTargetStateLabel(target.state);
6931
7615
  const relation = target.relation === 'requested' ? '你选择的插件'
@@ -6934,14 +7618,16 @@ export function ConsoleAppV4(props) {
6934
7618
  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
7619
  }));
6936
7620
  const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
6937
- const rootResultRows = taskOperation ? taskResultRows : batchResultRows;
7621
+ const rootResultRows = taskOperation
7622
+ ? [...taskSelectedPluginRows, ...taskLocationRows]
7623
+ : batchResultRows;
6938
7624
  const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
6939
7625
  const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
6940
7626
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
6941
7627
  || prioritizedPortableSkills.length > portableSkillLimit) {
6942
7628
  help = 'PgUp/PgDn 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6943
7629
  }
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
7630
+ 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
7631
  ? _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
7632
  ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
6947
7633
  : _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 +7666,7 @@ export function ConsoleAppV4(props) {
6980
7666
  if (prioritizedComponents.length > componentLimit) {
6981
7667
  help = 'PgUp/PgDn 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6982
7668
  }
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' })] });
7669
+ 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
7670
  }
6985
7671
  else {
6986
7672
  const missingPlugin = definition.entity === 'plugin' && plugin === undefined;
@@ -7260,6 +7946,89 @@ export function ConsoleAppV4(props) {
7260
7946
  }
7261
7947
  return;
7262
7948
  }
7949
+ if (operationConfirmation) {
7950
+ const activeReview = snapshot.activeReview;
7951
+ const closeConfirmation = () => {
7952
+ setOperationConfirmation(undefined);
7953
+ setFocus(operationConfirmation.returnFocus);
7954
+ setScrollOffset(operationConfirmation.returnScrollOffset);
7955
+ setOverlayFocus(0);
7956
+ };
7957
+ if (key.escape || input.toLowerCase() === 'b') {
7958
+ closeConfirmation();
7959
+ }
7960
+ else if (key.ctrl && input.toLowerCase() === 'c') {
7961
+ closeConfirmation();
7962
+ requestExit({ code: 0, reason: 'user' });
7963
+ }
7964
+ else if (input.toLowerCase() === 'q') {
7965
+ closeConfirmation();
7966
+ setDiscardForExit(dirty);
7967
+ setOverlay(dirty ? 'discard' : 'exit');
7968
+ }
7969
+ else if (props.onSessionNavigation && (input === 'h' || input === 'H')) {
7970
+ const navigation = input === 'H' ? 'reload' : 'home';
7971
+ closeConfirmation();
7972
+ requestSessionNavigation(navigation);
7973
+ }
7974
+ else if (key.pageUp || key.pageDown) {
7975
+ const warningCount = activeReview
7976
+ ? operationRiskLines(activeReview, Math.max(12, size.columns - 8)).length : 0;
7977
+ const warningLimit = policy.mode === 'compact' ? 3 : 7;
7978
+ setOperationConfirmation((current) => current ? {
7979
+ ...current,
7980
+ warningOffset: Math.min(Math.max(0, warningCount - warningLimit), Math.max(0, current.warningOffset + (key.pageUp ? -1 : 1))),
7981
+ } : current);
7982
+ }
7983
+ else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
7984
+ setOverlayFocus((value) => moveIndex(2, value, key.upArrow || key.leftArrow ? -1 : 1));
7985
+ }
7986
+ else if (key.return) {
7987
+ if (overlayFocus === 0) {
7988
+ closeConfirmation();
7989
+ }
7990
+ else if (activeReview?.reviewToken !== operationConfirmation.reviewToken) {
7991
+ closeConfirmation();
7992
+ setActionError('确认内容已变化。请重新检查操作汇总。');
7993
+ }
7994
+ else if (!operationBusy.current) {
7995
+ operationBusy.current = true;
7996
+ setOperationConfirmation(undefined);
7997
+ void props.controller.executeOperation(operationConfirmation.reviewToken).then(() => {
7998
+ setFocus(0);
7999
+ setScrollOffset(0);
8000
+ }).finally(() => {
8001
+ operationBusy.current = false;
8002
+ });
8003
+ }
8004
+ }
8005
+ return;
8006
+ }
8007
+ if (sourceTransportOverlay) {
8008
+ if (key.escape || input.toLowerCase() === 'b') {
8009
+ setSourceTransportOverlay(undefined);
8010
+ setOverlayFocus(0);
8011
+ }
8012
+ else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
8013
+ setOverlayFocus((value) => moveIndex(2, value, key.upArrow || key.leftArrow ? -1 : 1));
8014
+ }
8015
+ else if (key.return && route.name === 'sourceEdit') {
8016
+ const configured = props.controller.getConfiguredSource(route.sourceId);
8017
+ const current = sourceDraft?.owner === sourceTransportOverlay.owner
8018
+ ? sourceDraft
8019
+ : configured ? sourceDraftFromSource(configured, sourceTransportOverlay.owner) : undefined;
8020
+ const location = overlayFocus === 0
8021
+ ? OFFICIAL_CATALOG_SOURCE_URL : OFFICIAL_CATALOG_SOURCE_SSH_URL;
8022
+ if (current && current.type === 'git' && current.location !== location) {
8023
+ setSourceDraft({ ...current, location });
8024
+ setDirty(true);
8025
+ props.controller.replace({ name: 'sourceEdit', sourceId: route.sourceId, owner: current.owner });
8026
+ }
8027
+ setSourceTransportOverlay(undefined);
8028
+ setOverlayFocus(0);
8029
+ }
8030
+ return;
8031
+ }
7263
8032
  if (overlay) {
7264
8033
  if (key.ctrl && input.toLowerCase() === 'c') {
7265
8034
  requestExit({ code: snapshot.phase === 'error' ? 1 : 0, reason: snapshot.phase === 'error' ? 'initialError' : 'user' });
@@ -7369,6 +8138,13 @@ export function ConsoleAppV4(props) {
7369
8138
  setReportSaveError(undefined);
7370
8139
  return;
7371
8140
  }
8141
+ if ((key.pageUp || key.pageDown)
8142
+ && route.name === 'pluginDependencyNode' && route.view === 'references'
8143
+ && referenceDetailPagination) {
8144
+ const { total, pageRows } = referenceDetailPagination;
8145
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
8146
+ return;
8147
+ }
7372
8148
  if ((key.pageUp || key.pageDown)
7373
8149
  && route.name === 'pluginWriteTask') {
7374
8150
  const total = props.controller.listPluginWriteTaskCandidates(route.goal)
@@ -7380,7 +8156,12 @@ export function ConsoleAppV4(props) {
7380
8156
  if ((key.pageUp || key.pageDown)
7381
8157
  && route.name === 'review'
7382
8158
  && snapshot.activeReview) {
7383
- const total = Math.max(snapshot.activeReview.changes.length, showTechnical ? snapshot.activeReview.technicalDetails?.length ?? 0 : 0);
8159
+ const userChangeCount = snapshot.activeReview.changes.filter((change) => !isTechnicalReviewGroup(change.group)
8160
+ || change.kind === 'warning'
8161
+ || change.kind === 'remove').length;
8162
+ const total = showTechnical
8163
+ ? Math.max(snapshot.activeReview.changes.length, snapshot.activeReview.technicalDetails?.length ?? 0)
8164
+ : userChangeCount;
7384
8165
  const pageRows = 1;
7385
8166
  setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
7386
8167
  return;
@@ -7930,12 +8711,21 @@ export function ConsoleAppV4(props) {
7930
8711
  return;
7931
8712
  }
7932
8713
  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);
8714
+ const nextQuery = input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
8715
+ ? ''
8716
+ : key.backspace || key.delete
8717
+ ? removeLastGrapheme(query)
8718
+ : input && !key.ctrl && !key.meta && input >= ' '
8719
+ ? query + input : query;
8720
+ setQuery(nextQuery);
8721
+ const matchingItems = props.controller.listPlugins({
8722
+ mode: route.mode,
8723
+ ...(route.sourceId ? { sourceId: route.sourceId } : {}),
8724
+ ...(nextQuery ? { query: nextQuery } : {}),
8725
+ installFilter,
8726
+ });
8727
+ const matchingRows = pluginListRowIds(route.mode, matchingItems, nextQuery);
8728
+ setFocus(matchingItems[0] ? matchingRows.indexOf(matchingItems[0].pluginId) : 0);
7939
8729
  }
7940
8730
  else {
7941
8731
  setBatchDraft((draft) => draft ? {
@@ -7949,7 +8739,8 @@ export function ConsoleAppV4(props) {
7949
8739
  showSelected: false,
7950
8740
  } : draft);
7951
8741
  }
7952
- setFocus(route.name === 'batchSelection' ? 1 : 0);
8742
+ if (route.name === 'batchSelection')
8743
+ setFocus(1);
7953
8744
  setScrollOffset(0);
7954
8745
  return;
7955
8746
  }
@@ -8023,6 +8814,27 @@ export function ConsoleAppV4(props) {
8023
8814
  back();
8024
8815
  }
8025
8816
  else if (globalAction === 'back' && route.name === 'batchPlacement') {
8817
+ if (batchDraft?.showPlacementTargets) {
8818
+ setBatchDraft((current) => current ? { ...current, showPlacementTargets: false } : current);
8819
+ setFocus(5);
8820
+ setScrollOffset(0);
8821
+ }
8822
+ else
8823
+ back();
8824
+ }
8825
+ else if (globalAction === 'back'
8826
+ && route.name === 'batchSelection'
8827
+ && batchDraft
8828
+ && batchDraft.stage !== 'select'
8829
+ && snapshot.router.currentFlowId === batchDraft.flowId) {
8830
+ moveBatchStage(batchDraft.stage === 'maintenance' ? 'task' : 'select');
8831
+ }
8832
+ else if (globalAction === 'back'
8833
+ && batchDraft
8834
+ && route.name !== 'batchSelection'
8835
+ && snapshot.router.currentFlowId === batchDraft.flowId
8836
+ && (route.name !== 'pluginWriteTask'
8837
+ || route.batchOrigin?.flowId === batchDraft.flowId)) {
8026
8838
  back();
8027
8839
  }
8028
8840
  else if (globalAction === 'back'
@@ -8032,8 +8844,7 @@ export function ConsoleAppV4(props) {
8032
8844
  back();
8033
8845
  }
8034
8846
  else if (globalAction === 'back') {
8035
- if (dirty && route.name !== 'review'
8036
- && !(batchDraft && (route.name === 'pluginPreview' || route.name === 'pluginDetail'))) {
8847
+ if (dirty && route.name !== 'review') {
8037
8848
  setOverlayFocus(0);
8038
8849
  setDiscardForExit(false);
8039
8850
  setOverlay('discard');
@@ -8055,12 +8866,11 @@ export function ConsoleAppV4(props) {
8055
8866
  ...(query ? { query } : {}),
8056
8867
  installFilter: nextFilter,
8057
8868
  });
8058
- const retained = selectedPluginId
8059
- ? nextItems.findIndex((item) => item.pluginId === selectedPluginId)
8060
- : -1;
8869
+ const nextRowIds = pluginListRowIds(route.mode, nextItems, query);
8870
+ const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
8061
8871
  setInstallFilter(nextFilter);
8062
8872
  props.controller.updatePageContext({ installFilter: nextFilter });
8063
- setFocus(retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0);
8873
+ setFocus(retained >= 0 ? retained : 0);
8064
8874
  setScrollOffset(0);
8065
8875
  }
8066
8876
  else if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
@@ -8090,11 +8900,11 @@ export function ConsoleAppV4(props) {
8090
8900
  if (batchDraft.mode === 'browse') {
8091
8901
  setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
8092
8902
  props.controller.push({ name: 'batchPlacement' });
8903
+ setFocus(0);
8904
+ setScrollOffset(0);
8093
8905
  }
8094
8906
  else
8095
- setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
8096
- setFocus(0);
8097
- setScrollOffset(0);
8907
+ moveBatchStage('task');
8098
8908
  }
8099
8909
  else
8100
8910
  setActionError('请至少选择一个插件。');
@@ -8161,9 +8971,14 @@ export function ConsoleAppV4(props) {
8161
8971
  const index = tabs.indexOf(current);
8162
8972
  const next = tabs[(index + direction + tabs.length) % tabs.length];
8163
8973
  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;
8974
+ const nextSourceUnavailable = next !== 'all'
8975
+ && props.controller.getSourceOverview(next).health?.state === 'unavailable';
8976
+ const nextRowIds = pluginListRowIds(route.mode, nextItems, query, nextSourceUnavailable);
8977
+ const retained = selectedPluginId ? nextRowIds.indexOf(selectedPluginId) : -1;
8165
8978
  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;
8979
+ const retainedFocus = retained >= 0
8980
+ ? retained
8981
+ : nextItems[0] ? nextRowIds.indexOf(nextItems[0].pluginId) : 0;
8167
8982
  setFocus(retainedFocus);
8168
8983
  setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
8169
8984
  }
@@ -8176,6 +8991,9 @@ export function ConsoleAppV4(props) {
8176
8991
  props.controller.togglePortableSkill(candidate.name);
8177
8992
  }
8178
8993
  else if (key.upArrow || input.toLowerCase() === 'k') {
8994
+ if (route.name === 'pluginDependencyNode' && route.view === 'references') {
8995
+ setBatchTargetOffset(0);
8996
+ }
8179
8997
  setFocus((value) => {
8180
8998
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
8181
8999
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
@@ -8209,6 +9027,9 @@ export function ConsoleAppV4(props) {
8209
9027
  });
8210
9028
  }
8211
9029
  else if (key.downArrow || input.toLowerCase() === 'j') {
9030
+ if (route.name === 'pluginDependencyNode' && route.view === 'references') {
9031
+ setBatchTargetOffset(0);
9032
+ }
8212
9033
  setFocus((value) => {
8213
9034
  const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
8214
9035
  ? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
@@ -8260,6 +9081,48 @@ export function ConsoleAppV4(props) {
8260
9081
  if (actionError)
8261
9082
  description = actionError;
8262
9083
  const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, help: help, size: size, children: content }));
9084
+ if (operationConfirmation && snapshot.activeReview) {
9085
+ const review = snapshot.activeReview;
9086
+ const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
9087
+ const modalWarningLines = operationRiskLines(review, Math.max(12, modalWidth - 6));
9088
+ const shortWarningLimit = policy.mode === 'compact' ? 4 : 6;
9089
+ const shortConfirmation = modalWarningLines.length <= shortWarningLimit;
9090
+ const warningLines = shortConfirmation
9091
+ ? modalWarningLines
9092
+ : operationRiskLines(review, Math.max(12, size.columns - 8));
9093
+ const warningLimit = Math.max(1, policy.mode === 'compact' ? 3 : 7);
9094
+ const warningOffset = Math.min(operationConfirmation.warningOffset, Math.max(0, warningLines.length - warningLimit));
9095
+ const visibleWarnings = shortConfirmation
9096
+ ? warningLines : warningLines.slice(warningOffset, warningOffset + warningLimit);
9097
+ const confirmationActions = [
9098
+ { label: '返回检查', description: '回到完整操作汇总' },
9099
+ { label: `我已了解影响,${review.primaryLabel}`, status: 'error' },
9100
+ ];
9101
+ 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
9102
+ ? visibleWarnings.map((warning, index) => _jsx(StaticRow, { title: warning, status: warning.startsWith('! ') ? 'warning' : 'info', compact: true }, `${warningOffset + index}:${warning}`))
9103
+ : _jsx(StaticRow, { title: "! \u6B64\u64CD\u4F5C\u9700\u8981\u660E\u786E\u6388\u6743", status: "warning", compact: true }), !shortConfirmation && warningLines.length - warningOffset - visibleWarnings.length > 0
9104
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${warningLines.length - warningOffset - visibleWarnings.length} 行`, status: "muted", compact: true }) : null, _jsx(SemanticRows, { actions: confirmationActions, focus: overlayFocus, kind: "action", compact: true })] }));
9105
+ if (shortConfirmation) {
9106
+ 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 })] });
9107
+ }
9108
+ 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 });
9109
+ }
9110
+ if (sourceTransportOverlay) {
9111
+ const configuredSource = route.name === 'sourceEdit'
9112
+ ? props.controller.getConfiguredSource(route.sourceId)
9113
+ : undefined;
9114
+ const currentLocation = sourceDraft?.owner === sourceTransportOverlay.owner
9115
+ && sourceDraft.type === 'git'
9116
+ ? sourceDraft.location
9117
+ : configuredSource?.type === 'git' ? configuredSource.url : undefined;
9118
+ const currentTransport = currentLocation === OFFICIAL_CATALOG_SOURCE_URL ? 'HTTPS'
9119
+ : currentLocation === OFFICIAL_CATALOG_SOURCE_SSH_URL ? 'SSH' : undefined;
9120
+ const transportActions = [
9121
+ { label: 'HTTPS', description: '默认方式;无需预先配置 SSH 密钥', selected: currentTransport === 'HTTPS' },
9122
+ { label: 'SSH', description: '适合已配置 GitHub SSH 访问的用户', selected: currentTransport === 'SSH' },
9123
+ ];
9124
+ 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 }) }) })] });
9125
+ }
8263
9126
  if (!overlay)
8264
9127
  return base;
8265
9128
  const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy, props.onSessionNavigation !== undefined);