@lesliechan721/aw 0.1.0-beta.27 → 0.1.0-beta.28

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.
@@ -43,11 +43,13 @@ function pluginFilterLabel(filter) {
43
43
  function paginationActions(options) {
44
44
  return [
45
45
  {
46
+ actionId: 'pagination-previous',
46
47
  label: 'p 上一页',
47
48
  disabled: options.offset <= 0 ? '已在第一页' : undefined,
48
49
  run: () => options.move(-1),
49
50
  },
50
51
  {
52
+ actionId: 'pagination-next',
51
53
  label: 'n 下一页',
52
54
  disabled: options.offset + options.pageRows >= options.total ? '已在最后一页' : undefined,
53
55
  run: () => options.move(1),
@@ -1091,6 +1093,10 @@ export function ConsoleAppV4(props) {
1091
1093
  const [sourceDraft, setSourceDraft] = useState();
1092
1094
  const [batchDraft, setBatchDraft] = useState();
1093
1095
  const [batchTargetOffset, setBatchTargetOffset] = useState(0);
1096
+ const [portableResultDetailOpen, setPortableResultDetailOpen] = useState(false);
1097
+ const [portableResultDetailOffset, setPortableResultDetailOffset] = useState(0);
1098
+ const portableResultDetailPairKey = useRef(undefined);
1099
+ const portableResultDetailReturnFocus = useRef(0);
1094
1100
  const [expandedDependencyPluginIds, setExpandedDependencyPluginIds] = useState();
1095
1101
  const [sourceInput, setSourceInput] = useState('');
1096
1102
  const [sourceInputError, setSourceInputError] = useState();
@@ -1106,6 +1112,7 @@ export function ConsoleAppV4(props) {
1106
1112
  const [sourceDependenciesExpanded, setSourceDependenciesExpanded] = useState(false);
1107
1113
  const [portableAgentInput, setPortableAgentInput] = useState('');
1108
1114
  const [portableSelectionView, setPortableSelectionView] = useState('all');
1115
+ const [portableToolsOpen, setPortableToolsOpen] = useState(false);
1109
1116
  const [reportSaveMode, setReportSaveMode] = useState(false);
1110
1117
  const [reportSavePath, setReportSavePath] = useState('');
1111
1118
  const [reportSaveResult, setReportSaveResult] = useState();
@@ -1114,6 +1121,7 @@ export function ConsoleAppV4(props) {
1114
1121
  const dynamicMenuFocus = useRef(undefined);
1115
1122
  const pluginListFocusId = useRef(undefined);
1116
1123
  const dependencyGraphContexts = useRef(new Map());
1124
+ const selectedDependencyReferenceIndex = useRef(0);
1117
1125
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
1118
1126
  const previousExternalDraftValues = useRef({
1119
1127
  configDrafts, configCandidate, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
@@ -1164,6 +1172,11 @@ export function ConsoleAppV4(props) {
1164
1172
  const timer = setInterval(() => setClockTick((value) => value + 1), 1_000);
1165
1173
  return () => clearInterval(timer);
1166
1174
  }, [busy, route.name === 'progress' ? route.operationId : '']);
1175
+ useEffect(() => {
1176
+ setPortableResultDetailOpen(false);
1177
+ setPortableResultDetailOffset(0);
1178
+ portableResultDetailPairKey.current = undefined;
1179
+ }, [route.name, route.name === 'result' ? route.operationId : '']);
1167
1180
  const tooSmall = policy.mode === 'too-small';
1168
1181
  const requestExit = (request) => {
1169
1182
  if (exitSent.current)
@@ -1852,6 +1865,10 @@ export function ConsoleAppV4(props) {
1852
1865
  setSourceReferenceSummary({ blocked: true, inventoryComplete: false, dependencies: [] }); });
1853
1866
  return () => abort.abort();
1854
1867
  }, [props.controller, route.name, route.name === 'sourceRemoveBlocked' ? route.sourceId : '']);
1868
+ useEffect(() => {
1869
+ if (route.name !== 'portableSkillSelect')
1870
+ setPortableToolsOpen(false);
1871
+ }, [route.name]);
1855
1872
  const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchFocus);
1856
1873
  const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
1857
1874
  ? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
@@ -1862,6 +1879,7 @@ export function ConsoleAppV4(props) {
1862
1879
  : snapshot.portableDraft.candidates
1863
1880
  : [];
1864
1881
  const supportsTechnicalDetails = snapshot.phase === 'error'
1882
+ || Boolean(snapshot.projectIntentIssue)
1865
1883
  || Boolean(route.name === 'reviewPreparing' && snapshot.reviewPreparation?.code)
1866
1884
  || Boolean(route.name === 'progress' && snapshot.progress.messages.length > 0)
1867
1885
  || Boolean(route.name === 'review' && snapshot.activeReview?.technicalDetails?.length)
@@ -1869,6 +1887,7 @@ export function ConsoleAppV4(props) {
1869
1887
  && issue.diagnostic.technical)
1870
1888
  || Boolean(route.name === 'pluginDependencies' && plugin)
1871
1889
  || Boolean(route.name === 'pluginDependencyNode' && plugin)
1890
+ || route.name === 'catalogStoreRules'
1872
1891
  || portableVariantCandidates.length > 0
1873
1892
  || Boolean(route.name === 'portableDependencies' && plugin
1874
1893
  && props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependencies.length)
@@ -1893,6 +1912,7 @@ export function ConsoleAppV4(props) {
1893
1912
  let confirmationPagination;
1894
1913
  let pluginRows = [];
1895
1914
  let pinnedActions = [];
1915
+ let selectAllPortableCandidates;
1896
1916
  let togglePluginListFilter;
1897
1917
  let movePluginListSource;
1898
1918
  let visibleConfigTabs = [];
@@ -1926,11 +1946,32 @@ export function ConsoleAppV4(props) {
1926
1946
  : route.name === 'pluginDetail' ? policy.mode === 'compact' ? 6 : 8
1927
1947
  : route.name === 'operationHistory' ? 4
1928
1948
  : route.name === 'operationReportPreview' ? 5 : 0;
1949
+ let currentActionViewportLineBudget = pageActionBudget(actionViewportStaticRows);
1950
+ let portableResultDetailPagination;
1929
1951
  const responsiveBudget = (compactRows, standardRows) => {
1930
1952
  const baseline = policy.mode === 'compact' ? 18 : 24;
1931
1953
  const base = policy.mode === 'compact' ? compactRows : standardRows;
1932
1954
  return base + Math.max(0, size.rows - baseline);
1933
1955
  };
1956
+ const sourceTechnicalPageRows = policy.mode === 'compact'
1957
+ ? 1 : Math.max(1, responsiveBudget(5, 10) - 1);
1958
+ const resolveSourceTechnicalDiagnostic = (sourceId) => {
1959
+ const overview = props.controller.getSourceOverview(sourceId);
1960
+ return sourceDiagnostic(sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(sourceId), props.controller.getCatalogFreshness(sourceId)) ?? {
1961
+ severity: 'warning',
1962
+ title: '来源解析详情',
1963
+ userMessage: '来源当前可用。',
1964
+ impact: [{ state: 'preserved', message: '当前来源状态没有阻止操作。' }],
1965
+ nextStep: '返回来源详情。',
1966
+ technical: {
1967
+ code: 'catalogSourceReady',
1968
+ stage: 'resolve-source',
1969
+ sourceId,
1970
+ ...(overview.detail?.requestedRef ? { requestedRef: overview.detail.requestedRef } : {}),
1971
+ ...(overview.detail?.resolvedCommit ? { resolvedCommit: overview.detail.resolvedCommit } : {}),
1972
+ },
1973
+ };
1974
+ };
1934
1975
  const moveBatchTargetPage = (total, pageRows, direction) => {
1935
1976
  const lastOffset = Math.max(0, total - 1);
1936
1977
  setBatchTargetOffset((value) => Math.min(lastOffset, Math.max(0, value + direction * pageRows)));
@@ -1964,8 +2005,16 @@ export function ConsoleAppV4(props) {
1964
2005
  setActionError(undefined);
1965
2006
  };
1966
2007
  const toggleTechnical = () => {
2008
+ if (portableResultDetailOpen)
2009
+ setFocus(portableResultDetailReturnFocus.current);
2010
+ setPortableResultDetailOpen(false);
2011
+ setPortableResultDetailOffset(0);
1967
2012
  setShowTechnical((value) => !value);
1968
- setBatchTargetOffset(0);
2013
+ if (!(route.name === 'result' && snapshot.result
2014
+ && 'operationKind' in snapshot.result
2015
+ && snapshot.result.operationKind === 'portableInstall')) {
2016
+ setBatchTargetOffset(0);
2017
+ }
1969
2018
  };
1970
2019
  const restorePageContext = () => {
1971
2020
  const restoredFrame = props.controller.snapshot.router.current;
@@ -3065,10 +3114,22 @@ export function ConsoleAppV4(props) {
3065
3114
  else if (rendererFamily === 'home' && route.name === 'home') {
3066
3115
  const home = snapshot.viewModel.home;
3067
3116
  const projectReminder = snapshot.projectIntentReminder;
3117
+ const projectIntentIssue = snapshot.projectIntentIssue;
3068
3118
  const projectPendingCount = (projectReminder?.actionableTargets.length ?? 0)
3069
3119
  + (projectReminder?.blockedTargets.length ?? 0);
3070
3120
  context = '当前项目';
3071
3121
  actions = [
3122
+ ...(projectIntentIssue ? [{
3123
+ actionId: 'project-intent-issue',
3124
+ label: projectIntentIssue.state === 'invalid'
3125
+ ? '本项目插件声明无效'
3126
+ : '无法读取本项目插件声明',
3127
+ description: projectIntentIssue.state === 'invalid'
3128
+ ? '本项目插件声明无效;修复后重新加载'
3129
+ : '无法读取本项目插件声明;检查项目位置或权限后重新加载',
3130
+ status: 'warning',
3131
+ run: () => toggleTechnical(),
3132
+ }] : []),
3072
3133
  ...(projectPendingCount > 0 ? [{
3073
3134
  label: '项目插件',
3074
3135
  description: projectReminder.actionableTargets.length > 0
@@ -3093,12 +3154,16 @@ export function ConsoleAppV4(props) {
3093
3154
  run: () => go({ name: 'sourceList' }),
3094
3155
  },
3095
3156
  ];
3096
- content = _jsxs(Box, { flexDirection: "column", children: [projectPendingCount > 0
3157
+ content = _jsxs(Box, { flexDirection: "column", children: [projectIntentIssue && !showTechnical
3158
+ ? _jsx(StaticRow, { status: "warning", title: projectIntentIssue.state === 'invalid'
3159
+ ? '本项目插件声明无效'
3160
+ : '无法读取本项目插件声明', description: "\u5065\u5EB7\u6765\u6E90\u3001\u4E2A\u4EBA\u63D2\u4EF6\u548C\u5B89\u5168\u8BCA\u65AD\u4ECD\u53EF\u4F7F\u7528\u3002\u4FEE\u590D\u540E\u6309 H \u91CD\u65B0\u52A0\u8F7D\u3002" })
3161
+ : null, projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", children: [_jsx(StaticRow, { title: "\u72B6\u6001", meta: projectIntentIssue.state === 'invalid' ? '声明无效' : '无法读取', compact: true }), _jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, !showTechnical && projectPendingCount > 0
3097
3162
  ? _jsx(Callout, { status: "warning", title: `本项目有 ${projectPendingCount} 个插件目标尚未就绪`, description: `${projectReminder.actionableTargets.length} 个可同步;`
3098
3163
  + `${projectReminder.blockedTargets.length} 个需要先处理。` })
3099
- : projectReminder && projectReminder.unconfirmedTargets.length > 0
3164
+ : !showTechnical && projectReminder && projectReminder.unconfirmedTargets.length > 0
3100
3165
  ? _jsx(Callout, { status: "info", title: "\u90E8\u5206\u9879\u76EE\u63D2\u4EF6\u72B6\u6001\u65E0\u6CD5\u786E\u8BA4", description: "\u6CA1\u6709\u628A\u672A\u89C2\u6D4B\u72B6\u6001\u89E3\u91CA\u4E3A\u7F3A\u5931\u3002\u8BF7\u68C0\u67E5\u6765\u6E90\u6216\u5DE5\u5177\u72B6\u6001\u540E\u91CD\u65B0\u52A0\u8F7D\u3002" })
3101
- : null, _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' })] });
3166
+ : null, !showTechnical ? _jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `已添加 ${home.installedPlugins} 个插件`, status: "muted" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null, home.catalogCheckingCount > 0 ? _jsx(ResultRow, { title: `${home.catalogCheckingCount} 个插件来源正在检查`, status: "muted" }) : null, home.catalogUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.catalogUpdateCount} 个插件来源有更新`, status: "warning" }) : null, home.pluginUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.pluginUpdateCount} 个插件可更新`, status: "warning" }) : null, home.catalogRecoveryCount > 0 ? _jsx(ResultRow, { title: `${home.catalogRecoveryCount} 个插件来源需要检查或恢复`, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
3102
3167
  }
3103
3168
  else if (route.name === 'pluginList') {
3104
3169
  const sourceId = route.sourceId ?? 'all';
@@ -3877,20 +3942,44 @@ export function ConsoleAppV4(props) {
3877
3942
  skillName: candidate.name,
3878
3943
  }),
3879
3944
  }));
3880
- actions = [
3881
- ...candidateActions,
3882
- { actionId: 'portable-selection-continue', label: '继续', meta: draft.canContinue ? undefined : '至少选择一个 Skill', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: continuePortableSelection },
3883
- { label: '全选全部候选', run: () => props.controller.selectAllPortableSkills() },
3884
- { label: portableSelectionView === 'selected' ? '查看全部候选' : `只看已选(${draft.selectedCount})`, run: () => {
3945
+ selectAllPortableCandidates = { actionId: 'portable-selection-select-all', label: '全选当前结果', run: () => {
3946
+ for (const candidate of visibleCandidates) {
3947
+ if (!candidate.selected)
3948
+ props.controller.togglePortableSkill(candidate.name);
3949
+ }
3950
+ setPortableToolsOpen(false);
3951
+ } };
3952
+ const selectionToolActions = [
3953
+ selectAllPortableCandidates,
3954
+ { actionId: 'portable-selection-filter', label: portableSelectionView === 'selected' ? '查看全部候选' : `只看已选(${draft.selectedCount})`, run: () => {
3885
3955
  setPortableSelectionView((current) => current === 'all' ? 'selected' : 'all');
3956
+ setPortableToolsOpen(false);
3957
+ setFocus(0);
3958
+ setScrollOffset(0);
3959
+ } },
3960
+ { actionId: 'portable-selection-clear', label: '清空选择', disabled: draft.selectedCount === 0 ? '当前没有已选 Skill' : undefined, run: () => {
3961
+ props.controller.clearPortableSkills();
3962
+ setPortableToolsOpen(false);
3963
+ setFocus(0);
3964
+ setScrollOffset(0);
3965
+ } },
3966
+ ...(plugin.dependencySummary?.totalCount ? [{ actionId: 'portable-selection-dependencies', label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
3967
+ { actionId: 'portable-selection-tools-back', label: '返回 Skills 列表', run: () => {
3968
+ setPortableToolsOpen(false);
3886
3969
  setFocus(0);
3887
3970
  setScrollOffset(0);
3888
3971
  } },
3889
- { label: '清空选择', disabled: draft.selectedCount === 0 ? '当前没有已选 Skill' : undefined, run: () => props.controller.clearPortableSkills() },
3890
- ...(plugin.dependencySummary?.totalCount ? [{ label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
3891
3972
  ];
3892
- const candidateViewport = portableSkillCandidateViewport(candidateActions, visibleCandidates, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(actions.length - visibleCandidates.length
3893
- + (portableVariantCandidates.length > 0 ? 3 : 0)));
3973
+ const continueAction = { actionId: 'portable-selection-continue', label: `继续(${draft.selectedCount})`, meta: draft.canContinue ? undefined : '至少选择一个 Skill', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: continuePortableSelection };
3974
+ const openToolsAction = { actionId: 'portable-selection-tools', label: '选择工具', run: () => {
3975
+ setPortableToolsOpen(true);
3976
+ setFocusZone('content');
3977
+ setFocus(0);
3978
+ setScrollOffset(0);
3979
+ } };
3980
+ pinnedActions = [continueAction, openToolsAction];
3981
+ actions = portableToolsOpen ? selectionToolActions : candidateActions;
3982
+ const candidateViewport = portableSkillCandidateViewport(candidateActions, visibleCandidates, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(2 + (portableVariantCandidates.length > 0 ? 3 : 0)));
3894
3983
  const viewport = showTechnical
3895
3984
  ? { offset: focus < visibleCandidates.length ? focus : candidateViewport.offset, limit: 1 }
3896
3985
  : candidateViewport;
@@ -3905,19 +3994,29 @@ export function ConsoleAppV4(props) {
3905
3994
  const focusedVariant = variantCandidates.find((candidate) => candidate.name === visibleCandidates[focus]?.name);
3906
3995
  const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
3907
3996
  const technicalVariant = focusedVariant ?? visibleVariant ?? variantCandidates[0];
3908
- help = '↑↓ 移动 · Space 切换 · a 全选 · Enter 查看摘要 · c 继续 · Esc 返回';
3909
- content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? visibleCandidateEntries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, selected: Boolean(action.selected), focused: focus === index, compact: true }, String(action.label))) : providerGroups.map((entries) => {
3910
- const provider = entries[0].candidate;
3911
- const relation = provider.relation === 'requested' ? '当前插件'
3912
- : provider.relation === 'direct' ? '直接依赖插件' : '间接依赖插件';
3913
- return _jsx(SectionCard, { title: provider.providerDisplayName, meta: relation, compact: policy.mode === 'compact', children: entries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, description: action.description, selected: Boolean(action.selected), focused: focus === index, compact: policy.mode === 'compact' }, String(action.label))) }, provider.providerPluginId);
3914
- }), variantCandidates.length > 0
3915
- ? showTechnical
3916
- ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
3917
- : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
3918
- : null, visibleCandidates.length === 0
3919
- ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
3920
- : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: visibleCandidates.length }), showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] });
3997
+ help = portableToolsOpen
3998
+ ? '↑↓ 移动 · Enter 选择 · Tab 返回固定栏 · Esc 返回'
3999
+ : focusZone === 'actions'
4000
+ ? '←→/↑↓ 移动操作 · Enter 选择 · Tab 返回列表 · c 继续 · Esc 返回'
4001
+ : '↑↓ 移动 · Space 切换 · Enter 查看摘要 · Tab 固定栏 · c 继续 · Esc 返回';
4002
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(PinnedActionBar, { actions: pinnedActions.map((action) => ({
4003
+ id: action.actionId, label: action.label, disabled: Boolean(action.disabled),
4004
+ })), active: focusZone === 'actions', focus: pinnedActionFocus }), _jsxs(ContentViewport, { children: [portableToolsOpen
4005
+ ? _jsx(SemanticRows, { actions: selectionToolActions, focus: focusZone === 'content' ? focus : -1, kind: "action", compact: policy.mode === 'compact' })
4006
+ : showTechnical ? visibleCandidateEntries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, selected: Boolean(action.selected), focused: focusZone === 'content' && focus === index, compact: true }, String(action.label))) : providerGroups.map((entries) => {
4007
+ const provider = entries[0].candidate;
4008
+ const relation = provider.relation === 'requested' ? '当前插件'
4009
+ : provider.relation === 'direct' ? '直接依赖插件' : '间接依赖插件';
4010
+ return _jsx(SectionCard, { title: provider.providerDisplayName, meta: relation, compact: policy.mode === 'compact', children: entries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, description: action.description, selected: Boolean(action.selected), focused: focusZone === 'content' && focus === index, compact: policy.mode === 'compact' }, String(action.label))) }, provider.providerPluginId);
4011
+ }), !portableToolsOpen && variantCandidates.length > 0
4012
+ ? showTechnical
4013
+ ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4014
+ : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
4015
+ : null, !portableToolsOpen && visibleCandidates.length === 0
4016
+ ? _jsx(StaticRow, { title: "\u6CA1\u6709\u5DF2\u9009 Skill", description: "\u9009\u62E9\u201C\u67E5\u770B\u5168\u90E8\u5019\u9009\u201D\u540E\u53EF\u7EE7\u7EED\u52FE\u9009", status: "muted", compact: true })
4017
+ : null, !portableToolsOpen && !draft.canContinue
4018
+ ? _jsx(StaticRow, { title: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A Skill", status: "warning", compact: true })
4019
+ : null, !portableToolsOpen && showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] })] });
3921
4020
  }
3922
4021
  else if (route.name === 'portableSkillSummary' && plugin && snapshot.portableDraft) {
3923
4022
  const candidate = snapshot.portableDraft.candidates.find((entry) => entry.name === route.skillName);
@@ -4012,7 +4111,7 @@ export function ConsoleAppV4(props) {
4012
4111
  : overview?.dependents ?? [];
4013
4112
  const dependencyCount = overview?.dependencies.length ?? 0;
4014
4113
  const dependentCount = visibleDependents.length;
4015
- const dependentLimit = responsiveBudget(2, 4);
4114
+ const dependentLimit = responsiveBudget(1, 3);
4016
4115
  const dependencyRows = (entries, limit, dependent = false, offset = 0) => {
4017
4116
  const safeOffset = Math.min(offset, Math.max(0, entries.length - 1));
4018
4117
  const visible = entries.slice(safeOffset, safeOffset + limit);
@@ -4026,7 +4125,7 @@ export function ConsoleAppV4(props) {
4026
4125
  context = '已阻止';
4027
4126
  title = `无法移除 ${plugin.displayName}`;
4028
4127
  description = '仍有插件依赖它。请先处理依赖者,再重新尝试。';
4029
- actions = [
4128
+ const blockedRemovalActions = [
4030
4129
  { label: '查看完整依赖关系', run: () => {
4031
4130
  props.controller.replace({ name: 'pluginDependencies', pluginId: plugin.pluginId });
4032
4131
  setFocus(0);
@@ -4035,11 +4134,23 @@ export function ConsoleAppV4(props) {
4035
4134
  { label: '返回插件详情', run: back },
4036
4135
  { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4037
4136
  ];
4137
+ const blockedRemovalPagination = !showTechnical && visibleDependents.length > dependentLimit
4138
+ ? paginationActions({
4139
+ offset: batchTargetOffset,
4140
+ total: visibleDependents.length,
4141
+ pageRows: dependentLimit,
4142
+ move: (direction) => moveBatchTargetPage(visibleDependents.length, dependentLimit, direction),
4143
+ })
4144
+ : [];
4145
+ actions = [...blockedRemovalPagination, ...blockedRemovalActions];
4146
+ if (blockedRemovalPagination.length > 0) {
4147
+ referenceDetailPagination = { total: visibleDependents.length, pageRows: dependentLimit };
4148
+ }
4038
4149
  const blockedRemovalViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
4039
4150
  if (!showTechnical && visibleDependents.length > dependentLimit) {
4040
- help = 'PgUp/PgDn 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4151
+ help = 'p/n 查看全部被依赖关系 · ↑↓ 移动 · Enter 选择 · Esc 返回';
4041
4152
  }
4042
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: `${dependentCount} 个已安装插件仍在使用`, description: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002" }), showTechnical ? null : _jsx(SectionCard, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true, batchTargetOffset) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockedRemovalViewport.offset, limit: blockedRemovalViewport.limit }), showTechnical ? _jsx(IssueTechnicalDetails, { details: [{
4153
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `${dependentCount} 个已安装插件仍在使用`, status: "error", compact: true }), _jsx(StaticRow, { title: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002", status: "muted", compact: true }), showTechnical ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", compact: true }), dependencyRows(visibleDependents, dependentLimit, true, batchTargetOffset)] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockedRemovalViewport.offset, limit: blockedRemovalViewport.limit }), showTechnical ? _jsx(IssueTechnicalDetails, { details: [{
4043
4154
  code: 'dependencyStillRequired',
4044
4155
  message: '已安装插件仍在使用这个依赖;请先处理依赖者。',
4045
4156
  }], compact: policy.mode === 'compact' }) : null] });
@@ -4222,9 +4333,10 @@ export function ConsoleAppV4(props) {
4222
4333
  description = showTechnical
4223
4334
  ? '显示当前选择的完整引用字段。'
4224
4335
  : '这些位置的所需依赖当前可用,且本次检查发现它们同时满足依赖声明和安装位置。';
4225
- actions = [...referenceActions, { label: '返回节点详情', run: back }];
4226
- const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4227
- const selectedReference = references[Math.min(focus, references.length - 1)];
4336
+ if (!showTechnical && references[focus])
4337
+ selectedDependencyReferenceIndex.current = focus;
4338
+ const selectedReferenceIndex = Math.min(selectedDependencyReferenceIndex.current, Math.max(0, references.length - 1));
4339
+ const selectedReference = references[selectedReferenceIndex];
4228
4340
  const referenceIdLines = (label, pluginId) => {
4229
4341
  const prefix = `${label} · `;
4230
4342
  return wrapConsoleText(pluginId, Math.max(1, size.columns - 10 - stringWidth(prefix)))
@@ -4247,8 +4359,24 @@ export function ConsoleAppV4(props) {
4247
4359
  total: referenceDetailLines.length,
4248
4360
  pageRows: referenceDetailPageRows,
4249
4361
  } : undefined;
4362
+ const referencePaginationActions = showTechnical
4363
+ && referenceDetailLines.length > referenceDetailPageRows
4364
+ ? paginationActions({
4365
+ offset: referenceDetailOffset,
4366
+ total: referenceDetailLines.length,
4367
+ pageRows: referenceDetailPageRows,
4368
+ move: (direction) => moveBatchTargetPage(referenceDetailLines.length, referenceDetailPageRows, direction),
4369
+ })
4370
+ : [];
4371
+ actions = [
4372
+ ...(showTechnical && referenceActions[selectedReferenceIndex]
4373
+ ? [referenceActions[selectedReferenceIndex]] : referenceActions),
4374
+ ...referencePaginationActions,
4375
+ { label: '返回节点详情', run: back },
4376
+ ];
4377
+ const referenceViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(3, 6));
4250
4378
  help = showTechnical
4251
- ? 'PgUp/PgDn 查看当前位置详情 · ↑↓ 查看全部位置 · d 隐藏技术详情 · Esc 返回节点详情'
4379
+ ? 'p/n 查看当前位置详情 · ↑↓ 移动 · Enter 选择 · d 隐藏技术详情 · Esc 返回节点详情'
4252
4380
  : '↑↓ 查看全部位置 · d 技术详情 · Enter 返回节点详情 · Esc 返回节点详情';
4253
4381
  content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? null
4254
4382
  : _jsx(StaticRow, { title: "\u672C\u6B21\u68C0\u67E5", value: "\u53EA\u68C0\u67E5\u5F53\u524D\u9879\u76EE\u548C\u4E2A\u4EBA\u4F4D\u7F6E\uFF1B\u5176\u4ED6\u9879\u76EE\u672A\u68C0\u67E5", status: "muted", compact: true }), showTechnical && referenceDetailOffset > 0
@@ -5971,15 +6099,16 @@ export function ConsoleAppV4(props) {
5971
6099
  : totals ? `${totals.reclaimableBytes} bytes` : '正在检查';
5972
6100
  actions = [
5973
6101
  {
6102
+ actionId: 'catalog-store-prune-preview',
5974
6103
  label: '预览安全清理',
5975
6104
  description: totals ? `${totals.reclaimableEntries} 个可回收项 · ${sizeText}` : '先读取当前存储状态',
5976
6105
  ...(totals && totals.reclaimableEntries > 0
5977
6106
  ? { run: () => beginReview({ kind: 'catalogStorePrune' }) }
5978
6107
  : { disabled: catalogStoreError ? '当前无法读取本地存储' : totals ? '没有可回收项' : '正在检查' }),
5979
6108
  },
5980
- { label: '查看保留规则', description: '了解 Active、foreign 和未知项目为什么保留', run: () => go({ name: 'catalogStoreRules' }) },
5981
- { label: '重新检查', description: '重新读取当前本地存储', run: () => setCatalogStoreRefresh((value) => value + 1) },
5982
- { label: '返回来源列表', run: back },
6109
+ { actionId: 'catalog-store-rules', label: '查看保留规则', description: '了解哪些目录会保留,以及旧版本何时可以清理', run: () => go({ name: 'catalogStoreRules' }) },
6110
+ { actionId: 'catalog-store-refresh', label: '重新检查', description: '重新读取当前本地存储', run: () => setCatalogStoreRefresh((value) => value + 1) },
6111
+ { actionId: 'catalog-store-back', label: '返回来源列表', run: back },
5983
6112
  ];
5984
6113
  content = _jsxs(Box, { flexDirection: "column", children: [catalogStoreError
5985
6114
  ? _jsx(Callout, { status: "warning", title: "\u672C\u5730\u5B58\u50A8\u68C0\u67E5\u5931\u8D25", description: "\u672A\u6267\u884C\u5220\u9664\u3002\u8BF7\u68C0\u67E5 Catalog \u914D\u7F6E\u548C\u672C\u5730\u5B58\u50A8\u540E\u91CD\u8BD5\u3002" })
@@ -5990,9 +6119,13 @@ export function ConsoleAppV4(props) {
5990
6119
  else if (route.name === 'catalogStoreRules') {
5991
6120
  breadcrumb = '插件来源 / 本地存储 / 保留规则';
5992
6121
  title = '安全清理规则';
5993
- description = '实际清理会在现有 AW_HOME 写锁内重新检查每个已确认项目。';
5994
- actions = [{ label: '返回存储概览', run: back }];
5995
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u59CB\u7EC8\u4FDD\u7559", children: [_jsx(StaticRow, { title: "\u5F53\u524D Active Catalog", status: "success", compact: true }), _jsx(StaticRow, { title: "foreign\u3001\u7B26\u53F7\u94FE\u63A5\u3001unsafe\u3001\u635F\u574F\u6216\u672A\u77E5\u9879\u76EE", status: "muted", compact: true })] }), _jsxs(SectionCard, { title: "\u6EE1\u8DB3\u65F6\u95F4\u548C\u5B8C\u6574\u9A8C\u8BC1\u540E\u53EF\u56DE\u6536", children: [_jsx(StaticRow, { title: "\u975E Active \u6B63\u5F0F\u76EE\u5F55\u7248\u672C\uFF1A30 \u5929", compact: true }), _jsx(StaticRow, { title: "staging\u3001replacement \u548C delete claim\uFF1A24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "quarantine\uFF1A24 \u5C0F\u65F6\uFF0C\u5E76\u4E14\u5BF9\u5E94\u6B63\u5F0F\u76EE\u5F55\u7248\u672C\u5B8C\u6574\u5065\u5EB7", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6122
+ description = policy.mode === 'compact'
6123
+ ? '' : '清理前会再次检查每个项目,只删除能够确认安全的旧内容。';
6124
+ actions = [
6125
+ { actionId: 'catalog-store-rules-technical', label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
6126
+ { actionId: 'catalog-store-rules-back', label: '返回存储概览', run: back },
6127
+ ];
6128
+ content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical ? _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u59CB\u7EC8\u4FDD\u7559 \u00B7 \u5F53\u524D\u4F7F\u7528\u7684\u76EE\u5F55\u4E0D\u4F1A\u5220\u9664", children: _jsx(StaticRow, { title: "\u4E0D\u5C5E\u4E8E AW\u3001\u65E0\u6CD5\u786E\u8BA4\u5B89\u5168\u6216\u4ECD\u5728\u4F7F\u7528\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559", status: "muted", compact: true }) }), _jsxs(SectionCard, { title: "\u6EE1\u8DB3\u65F6\u95F4\u548C\u5B8C\u6574\u9A8C\u8BC1\u540E\u53EF\u56DE\u6536", children: [_jsx(StaticRow, { title: "\u65E7\u7248\u672C\u6EE1\u8DB3\u4FDD\u7559\u671F\u9650\u540E\u624D\u6E05\u7406", compact: true }), _jsx(StaticRow, { title: "\u672A\u5B8C\u6210\u7684\u4E0B\u8F7D\u6216\u66FF\u6362\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u9694\u79BB\u5185\u5BB9\u81F3\u5C11\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "\u5BF9\u5E94\u6B63\u5F0F\u7248\u672C\u5B8C\u6574\u53EF\u7528\u540E\u624D\u6E05\u7406", compact: true })] })] }) : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u6E05\u7406\u5224\u5B9A\u8BC1\u636E", children: [_jsx(StaticRow, { title: "\u9501", meta: "AW_HOME \u5199\u9501\u5185\u91CD\u65B0\u68C0\u67E5", compact: true }), _jsx(StaticRow, { title: "\u6B63\u5F0F\u76EE\u5F55\u7248\u672C", meta: "\u975E Active \u9879\u4FDD\u7559 30 \u5929", compact: true }), _jsx(StaticRow, { title: "staging\u3001replacement\u3001delete claim", meta: "\u4FDD\u7559 24 \u5C0F\u65F6", compact: true }), _jsx(StaticRow, { title: "quarantine", meta: "\u4FDD\u7559 24 \u5C0F\u65F6\uFF1B\u5BF9\u5E94\u6B63\u5F0F\u76EE\u5F55\u7248\u672C\u5FC5\u987B\u5B8C\u6574\u5065\u5EB7", compact: true }), _jsx(StaticRow, { title: "\u4FDD\u7559\u8282\u70B9", meta: "foreign\u3001\u7B26\u53F7\u94FE\u63A5\u3001unsafe\u3001\u635F\u574F\u6216\u672A\u77E5\u9879\u76EE", compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
5996
6129
  }
5997
6130
  else if (route.name === 'sourceDetail') {
5998
6131
  const overview = props.controller.getSourceOverview(route.sourceId);
@@ -6032,14 +6165,18 @@ export function ConsoleAppV4(props) {
6032
6165
  : logicalIdentityChanged ? '来源发布身份已变化。请编辑来源,或移除后重新添加。'
6033
6166
  : '来源没有可用的启用版本。可在这里取得并启用。';
6034
6167
  help = '↑↓ 移动 · Enter 选择 · Esc 返回';
6168
+ if (policy.mode === 'compact')
6169
+ description = '';
6035
6170
  const generationAction = logicalIdentityChanged ? []
6036
6171
  : generationState === 'incompatible' ? [{
6172
+ actionId: 'source-view-incompatible',
6037
6173
  label: '查看不兼容详情',
6038
6174
  description: '显示所需和缺少的功能支持',
6039
6175
  status: 'error',
6040
6176
  run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }),
6041
6177
  }]
6042
6178
  : generationState === 'repairRequired' ? [{
6179
+ actionId: 'source-repair',
6043
6180
  label: '重新下载旧版本并精确修复',
6044
6181
  description: '使用当前启用版本的精确提交;成功后保持此版本',
6045
6182
  disabled: activationBlocked ? `请先处理:${sourceOperationBlockLabel(activationBlocked)}` : undefined,
@@ -6049,6 +6186,7 @@ export function ConsoleAppV4(props) {
6049
6186
  sourceId: route.sourceId,
6050
6187
  }),
6051
6188
  }, ...(freshness?.state === 'incompatible' ? [] : [{
6189
+ actionId: 'source-replace-damaged',
6052
6190
  label: '使用待检查版本替换损坏版本',
6053
6191
  description: '旧版本无法取得时使用;进入独立影响确认',
6054
6192
  disabled: activationBlocked ? `请先处理:${sourceOperationBlockLabel(activationBlocked)}` : undefined,
@@ -6060,6 +6198,7 @@ export function ConsoleAppV4(props) {
6060
6198
  }])]
6061
6199
  : freshness?.state === 'incompatible' && generationState !== 'ready' ? []
6062
6200
  : generationState !== 'ready' ? [{
6201
+ actionId: 'source-activate',
6063
6202
  label: generationState === 'stateInvalid' ? '恢复状态并启用'
6064
6203
  : generationState === 'configMismatch' ? '验证并切换' : '获取并启用',
6065
6204
  description: '先准备只读的待检查版本确认;确认后才切换当前版本',
@@ -6073,8 +6212,9 @@ export function ConsoleAppV4(props) {
6073
6212
  }]
6074
6213
  : freshness?.state === 'updateAvailable' || freshness?.state === 'localChangesAvailable'
6075
6214
  ? [{
6215
+ actionId: 'source-update',
6076
6216
  label: freshness.state === 'localChangesAvailable'
6077
- ? '应用本地内容变化…' : `更新到 ${candidateVersion ?? '待检查版本'}…`,
6217
+ ? '应用本地内容变化' : `更新到 ${candidateVersion ?? '待检查版本'}`,
6078
6218
  description: '进入确认;确认前不会切换当前版本',
6079
6219
  disabled: activationBlocked ? `请先处理:${sourceOperationBlockLabel(activationBlocked)}` : undefined,
6080
6220
  run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId: route.sourceId }),
@@ -6083,6 +6223,7 @@ export function ConsoleAppV4(props) {
6083
6223
  || freshness?.state === 'rollbackDetected'
6084
6224
  || freshness?.state === 'incompatible'
6085
6225
  ? [{
6226
+ actionId: 'source-view-version-problem',
6086
6227
  label: freshness.state === 'versionConflict' ? '查看版本冲突'
6087
6228
  : freshness.state === 'rollbackDetected' ? '查看回退详情' : '查看不兼容详情',
6088
6229
  description: '普通更新已阻止;当前启用版本保持不变',
@@ -6090,15 +6231,17 @@ export function ConsoleAppV4(props) {
6090
6231
  run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }),
6091
6232
  }]
6092
6233
  : [{
6234
+ actionId: 'source-check',
6093
6235
  label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6094
6236
  description: '检查这个来源;检测到变化或异常时完整验证',
6095
6237
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6096
6238
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6097
6239
  }];
6098
6240
  const managementActions = [
6099
- { label: '来源设置', description: '下载方式、版本、自动检查和高级地址目录', run: () => openSourceEdit(route.sourceId) },
6241
+ { actionId: 'source-settings', label: '来源设置', description: '下载方式、版本、自动检查和高级地址目录', run: () => openSourceEdit(route.sourceId) },
6100
6242
  ...(generationState === 'incompatible'
6101
6243
  || (freshness?.state === 'incompatible' && generationState !== 'ready') ? [] : [{
6244
+ actionId: 'source-set-primary',
6102
6245
  label: generationState === 'ready' ? '设为主来源' : '启用并设为主来源',
6103
6246
  description: generationState === 'ready' ? '只改变默认插件来源' : '先启用确认的待检查版本,再设为主来源',
6104
6247
  disabled: detail?.primary ? '当前已经是主来源'
@@ -6114,20 +6257,22 @@ export function ConsoleAppV4(props) {
6114
6257
  setPrimaryAfterActivation: true,
6115
6258
  }),
6116
6259
  }]),
6117
- { label: '移除来源', description: '移除来源配置', status: 'error', disabled: props.controller.listSources().length <= 1 ? '至少保留一个来源' : detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6118
- { label: '技术详情', description: '查看脱敏错误代码和当前版本证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6260
+ { actionId: 'source-remove', label: '移除来源', description: '移除来源配置', status: 'error', disabled: props.controller.listSources().length <= 1 ? '至少保留一个来源' : detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
6261
+ { actionId: 'source-technical', label: '技术详情', description: '查看脱敏错误代码和当前版本证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
6119
6262
  ...(generationState !== 'ready'
6120
6263
  || freshness?.state === 'updateAvailable'
6121
6264
  || freshness?.state === 'localChangesAvailable'
6122
6265
  || freshness?.state === 'versionConflict'
6123
6266
  || freshness?.state === 'rollbackDetected'
6124
6267
  || freshness?.state === 'incompatible' ? [{
6268
+ actionId: 'source-check',
6125
6269
  label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6126
6270
  description: '检查这个来源;检测到变化或异常时完整验证',
6127
6271
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
6128
6272
  run: () => props.controller.recheckCatalogSource(route.sourceId),
6129
6273
  }] : []),
6130
6274
  {
6275
+ actionId: 'source-full-validate',
6131
6276
  label: '完整验证',
6132
6277
  description: '强制重新取得并完整验证这个来源',
6133
6278
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
@@ -6153,10 +6298,11 @@ export function ConsoleAppV4(props) {
6153
6298
  && (configuredSource.url.startsWith('git@') || configuredSource.url.startsWith('ssh://'))
6154
6299
  ? 'SSH' : 'HTTPS';
6155
6300
  const versionPolicy = detail?.type === 'path' ? '当前本地内容'
6156
- : detail?.refPolicy === 'cliChannel' ? '跟随 AW CLI channel'
6157
- : detail?.explicitRef ?? detail?.requestedRef ?? '固定 Git ref';
6301
+ : detail?.refPolicy === 'cliChannel' ? '跟随 AW 的稳定版或预览版目录'
6302
+ : '固定版本或 Git 引用';
6158
6303
  const sourceCount = snapshot.viewModel?.sourceTabs.find((source) => source.id === route.sourceId)?.count;
6159
6304
  const checkAction = {
6305
+ actionId: 'source-check',
6160
6306
  label: freshness?.state === 'checking' ? '正在检查' : '检查来源',
6161
6307
  description: '检查这个来源;检测到变化或异常时完整验证;不会自动启用更新',
6162
6308
  disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
@@ -6164,16 +6310,18 @@ export function ConsoleAppV4(props) {
6164
6310
  };
6165
6311
  actions = [
6166
6312
  ...sourcePrimaryAction,
6167
- ...(sourcePrimaryAction.some((action) => action.label === '检查来源' || action.label === '正在检查')
6313
+ ...(sourcePrimaryAction.some((action) => action.actionId === 'source-check')
6168
6314
  ? [] : [checkAction]),
6169
6315
  {
6316
+ actionId: 'source-view-plugins',
6170
6317
  label: '查看此来源插件',
6171
6318
  description: sourceCount === undefined ? '插件数未知' : `${sourceCount} 个插件`,
6172
6319
  run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
6173
6320
  },
6174
6321
  managementActions[0],
6175
- managementActions.find((action) => action.label === '移除来源'),
6322
+ managementActions.find((action) => action.actionId === 'source-remove'),
6176
6323
  {
6324
+ actionId: 'source-more',
6177
6325
  label: '更多操作',
6178
6326
  description: '替换候选、主来源和技术详情',
6179
6327
  run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
@@ -6276,37 +6424,19 @@ export function ConsoleAppV4(props) {
6276
6424
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
6277
6425
  }
6278
6426
  else if (route.name === 'sourceTechnical') {
6279
- const overview = props.controller.getSourceOverview(route.sourceId);
6280
- const detail = overview.detail;
6281
- const state = overview.health?.state ?? overview.source?.state;
6282
- const diagnostic = sourceDiagnostic(route.sourceId, state, detail, props.controller.getCatalogGeneration(route.sourceId), props.controller.getCatalogFreshness(route.sourceId)) ?? {
6283
- severity: 'warning',
6284
- title: '来源解析详情',
6285
- userMessage: '来源当前可用。',
6286
- impact: [{ state: 'preserved', message: '当前来源状态没有阻止操作。' }],
6287
- nextStep: '返回来源详情。',
6288
- technical: {
6289
- code: 'catalogSourceReady',
6290
- stage: 'resolve-source',
6291
- sourceId: route.sourceId,
6292
- ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
6293
- ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
6294
- },
6295
- };
6427
+ const diagnostic = resolveSourceTechnicalDiagnostic(route.sourceId);
6296
6428
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
6297
6429
  context = 'p 上一页 · n 下一页';
6298
6430
  title = '来源技术诊断';
6299
6431
  description = '';
6300
6432
  help = 'p/n 或 PgUp/PgDn 查看全部技术详情 · Enter 选择 · Esc 返回';
6301
- const sourceTechnicalRows = policy.mode === 'compact'
6302
- ? 1 : Math.max(1, responsiveBudget(5, 10) - 1);
6303
- const sourceTechnicalLastOffset = Math.max(0, diagnosticDetailRowCount(diagnostic) - sourceTechnicalRows);
6433
+ const sourceTechnicalLastOffset = Math.max(0, diagnosticDetailRowCount(diagnostic) - sourceTechnicalPageRows);
6304
6434
  actions = [
6305
- { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalRows)) },
6306
- { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalRows)) },
6435
+ { label: 'p 上一页', run: () => setBatchTargetOffset((value) => Math.max(0, value - sourceTechnicalPageRows)) },
6436
+ { label: 'n 下一页', run: () => setBatchTargetOffset((value) => Math.min(sourceTechnicalLastOffset, value + sourceTechnicalPageRows)) },
6307
6437
  { label: '返回来源详情', run: back },
6308
6438
  ];
6309
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: true, compact: policy.mode === 'compact', offset: batchTargetOffset, limit: sourceTechnicalRows }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6439
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: true, compact: policy.mode === 'compact', offset: batchTargetOffset, limit: sourceTechnicalPageRows }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6310
6440
  }
6311
6441
  else if (route.name === 'sourceAdd' && sourceDraft?.owner === route.owner) {
6312
6442
  breadcrumb = sourceDraft.mode === 'add' ? '插件来源 / 添加' : `插件来源 / ${sourceDraft.sourceId} / 编辑`;
@@ -6514,15 +6644,24 @@ export function ConsoleAppV4(props) {
6514
6644
  setBatchTargetOffset(0);
6515
6645
  } }, { label: '返回来源详情', run: back }];
6516
6646
  const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
6517
- const referenceLimit = Math.max(1, pageActionBudget(10));
6647
+ const referenceLimit = Math.max(1, pageActionBudget(12));
6518
6648
  const references = sourceReferenceSummary?.dependencies ?? [];
6519
6649
  const referenceOffset = Math.min(batchTargetOffset, Math.max(0, references.length - 1));
6520
6650
  const visibleReferences = references.slice(referenceOffset, referenceOffset + referenceLimit);
6521
6651
  const hiddenReferences = Math.max(0, references.length - referenceOffset - visibleReferences.length);
6522
6652
  if (sourceDependenciesExpanded && references.length > referenceLimit) {
6523
- help = 'PgUp/PgDn 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6653
+ actions.unshift(...paginationActions({
6654
+ offset: referenceOffset,
6655
+ total: references.length,
6656
+ pageRows: referenceLimit,
6657
+ move: (direction) => moveBatchTargetPage(references.length, referenceLimit, direction),
6658
+ }));
6659
+ referenceDetailPagination = { total: references.length, pageRows: referenceLimit };
6660
+ help = 'p/n 查看全部引用 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6524
6661
  }
6525
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: "\u5F15\u7528\u6C47\u603B", children: [_jsx(StaticRow, { title: "\u5B89\u88C5\u4F4D\u7F6E", meta: countLabel(sourceReferenceSummary?.installationCount, '个') }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u8BBE\u7F6E", meta: countLabel(sourceReferenceSummary?.configCount, '份') }), _jsx(StaticRow, { title: "\u751F\u547D\u5468\u671F\u610F\u56FE", meta: countLabel(sourceReferenceSummary?.lifecycleCount, '项') }), _jsx(StaticRow, { title: "Marketplace", meta: countLabel(sourceReferenceSummary?.marketplaceCount, '个') }), sourceDependenciesExpanded && referenceOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${referenceOffset} 项`, status: "muted", compact: true }) : null, sourceDependenciesExpanded ? visibleReferences.map((dependency, index) => _jsx(StaticRow, { title: `${dependency.category} · ${dependency.pluginId ?? dependency.sourceId ?? '身份未知'}`, meta: `${dependency.platform ? platformLabel(dependency.platform) : ''}${dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''}` }, `${dependency.category}:${dependency.pluginId ?? dependency.sourceId ?? referenceOffset + index}`)) : null, sourceDependenciesExpanded && hiddenReferences > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenReferences} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6662
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: sourceDependenciesExpanded
6663
+ ? referenceOffset > 0 ? `引用内容 · ↑ 前面还有 ${referenceOffset} 项` : '引用内容'
6664
+ : '引用汇总', children: [!sourceDependenciesExpanded ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5B89\u88C5\u4F4D\u7F6E", meta: countLabel(sourceReferenceSummary?.installationCount, '个') }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u8BBE\u7F6E", meta: countLabel(sourceReferenceSummary?.configCount, '份') }), _jsx(StaticRow, { title: "\u751F\u547D\u5468\u671F\u610F\u56FE", meta: countLabel(sourceReferenceSummary?.lifecycleCount, '项') }), _jsx(StaticRow, { title: "Marketplace", meta: countLabel(sourceReferenceSummary?.marketplaceCount, '个') })] }) : null, sourceDependenciesExpanded ? visibleReferences.map((dependency, index) => _jsx(StaticRow, { title: `${dependency.category} · ${dependency.pluginId ?? dependency.sourceId ?? '身份未知'}`, meta: `${dependency.platform ? platformLabel(dependency.platform) : ''}${dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''}` }, `${dependency.category}:${dependency.pluginId ?? dependency.sourceId ?? referenceOffset + index}`)) : null, sourceDependenciesExpanded && hiddenReferences > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenReferences} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
6526
6665
  }
6527
6666
  else if (route.name === 'issueList') {
6528
6667
  const issues = props.controller.listUserIssues();
@@ -6625,9 +6764,26 @@ export function ConsoleAppV4(props) {
6625
6764
  || entry.id === 'keepUserScope' || entry.id === 'keepProjectScope' ? 'error' : undefined,
6626
6765
  run: () => runIssueAction(entry),
6627
6766
  }));
6767
+ const issueDetailRows = diagnosticDetailRowCount(issue.diagnostic);
6768
+ const issueDetailPageRows = policy.mode === 'compact'
6769
+ ? 1
6770
+ : Math.max(1, responsiveBudget(5, 10) - 2);
6771
+ const issuePaginationActions = showTechnical && issueDetailRows > issueDetailPageRows
6772
+ ? paginationActions({
6773
+ offset: batchTargetOffset,
6774
+ total: issueDetailRows,
6775
+ pageRows: issueDetailPageRows,
6776
+ move: (direction) => moveBatchTargetPage(issueDetailRows, issueDetailPageRows, direction),
6777
+ })
6778
+ : [];
6779
+ actions.push(...issuePaginationActions);
6780
+ if (issuePaginationActions.length > 0) {
6781
+ referenceDetailPagination = { total: issueDetailRows, pageRows: issueDetailPageRows };
6782
+ help = 'p/n 查看全部技术字段 · ↑↓ 移动 · Enter 选择 · Esc 返回';
6783
+ }
6628
6784
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
6629
6785
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
6630
- const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? responsiveBudget(5, 10) : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
6786
+ const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, offset: showTechnical ? batchTargetOffset : undefined, limit: showTechnical ? issueDetailPageRows : undefined, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
6631
6787
  || issue.category === 'scopeConflict' });
6632
6788
  const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
6633
6789
  const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
@@ -6646,6 +6802,7 @@ export function ConsoleAppV4(props) {
6646
6802
  { label: '查看完整依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
6647
6803
  { label: '返回插件详情', run: () => go({ name: 'pluginDetail', pluginId: issuePlugin.pluginId }) },
6648
6804
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
6805
+ ...issuePaginationActions,
6649
6806
  ];
6650
6807
  const dependencyBlockedViewport = policy.mode === 'compact' && showTechnical
6651
6808
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -6686,6 +6843,7 @@ export function ConsoleAppV4(props) {
6686
6843
  } },
6687
6844
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
6688
6845
  ...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() }] : []),
6846
+ ...issuePaginationActions,
6689
6847
  ];
6690
6848
  const unsupportedTargetViewport = policy.mode === 'compact' && showTechnical
6691
6849
  ? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
@@ -7337,6 +7495,8 @@ export function ConsoleAppV4(props) {
7337
7495
  const portableOperation = operationKind === 'portableInstall';
7338
7496
  const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
7339
7497
  ? result.summary.portable : undefined;
7498
+ const portableResultScope = portableReceipt?.scope ?? result.portableIntent?.scope;
7499
+ const portableResultAgentMode = portableReceipt?.agentMode ?? result.portableIntent?.agentMode;
7340
7500
  const portableInstallFailed = portableReceipt?.failureCode === 'portableSkillsCliInstallFailed';
7341
7501
  const portableOriginIsDetail = 'returnRoute' in result
7342
7502
  && result.returnRoute?.name === 'pluginDetail';
@@ -7749,27 +7909,11 @@ export function ConsoleAppV4(props) {
7749
7909
  : target.state === 'skipped' ? 1 : target.relation === 'requested' ? 2 : 3;
7750
7910
  return priority(left) - priority(right);
7751
7911
  });
7752
- const resultPaginationTotal = Math.max(resultTargets.length, taskSummary?.rootResults.reduce((count, root) => count + 1 + (taskSummary.requestedTargets.filter((target) => target.pluginId === root.pluginId).length), 0) ?? 0, (resultSummary?.batch?.rootResults.length ?? 0) + resultTargets.length, result.operationKind === 'portableInstall'
7753
- ? portableReceipt?.selectedSkills.length ?? snapshot.portableResultSkills?.length ?? 0
7754
- : 0);
7755
- if (resultPaginationTotal > 1) {
7756
- actions.push(...paginationActions({
7757
- offset: batchTargetOffset,
7758
- total: resultPaginationTotal,
7759
- pageRows: 1,
7760
- move: (direction) => moveBatchTargetPage(resultPaginationTotal, 1, direction),
7761
- }));
7762
- referenceDetailPagination = { total: resultPaginationTotal, pageRows: 1 };
7763
- }
7764
- const resultActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(8));
7912
+ const baseResultPaginationTotal = Math.max(resultTargets.length, taskSummary?.rootResults.reduce((count, root) => count + 1 + (taskSummary.requestedTargets.filter((target) => target.pluginId === root.pluginId).length), 0) ?? 0, (resultSummary?.batch?.rootResults.length ?? 0) + resultTargets.length);
7765
7913
  const resultExtraRows = (resultSummary?.elapsedMs !== undefined ? 1 : 0)
7766
7914
  + visibleMessages.length
7767
7915
  + visibleChanges.length
7768
7916
  + (partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? 1 : 0);
7769
- const resultTargetLimit = prefixRowLimit(prioritizedTargets.map(() => policy.mode === 'compact' ? 1 : 2), pageActionBudget() - 3 - resultActionViewport.limit - resultExtraRows);
7770
- const resultTargetOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedTargets.length - 1));
7771
- const visibleResultTargets = prioritizedTargets.slice(resultTargetOffset, resultTargetOffset + resultTargetLimit);
7772
- const hiddenResultTargets = Math.max(0, resultTargets.length - resultTargetOffset - visibleResultTargets.length);
7773
7917
  const portableSelectedSkills = portableReceipt?.selectedSkills
7774
7918
  ?? snapshot.portableResultSkills
7775
7919
  ?? [];
@@ -7795,10 +7939,12 @@ export function ConsoleAppV4(props) {
7795
7939
  for (const target of portableReceipt?.unknown ?? []) {
7796
7940
  addPortablePair({ skill: target.skill, ...(target.agent ? { agent: target.agent } : {}), state: 'unknown' });
7797
7941
  }
7798
- for (const skill of portableSelectedSkills) {
7799
- const represented = [...portablePairs.values()].some((pair) => pair.skill === skill);
7800
- if (!represented)
7801
- addPortablePair({ skill, state: 'unknown' });
7942
+ if (!(portableReceipt && 'attemptedTargets' in portableReceipt)) {
7943
+ for (const skill of portableSelectedSkills) {
7944
+ const represented = [...portablePairs.values()].some((pair) => pair.skill === skill);
7945
+ if (!represented)
7946
+ addPortablePair({ skill, state: 'unknown' });
7947
+ }
7802
7948
  }
7803
7949
  const prioritizedPortablePairs = [...portablePairs.values()].sort((left, right) => {
7804
7950
  const priority = { unknown: 0, missing: 1, verified: 2 };
@@ -7806,29 +7952,157 @@ export function ConsoleAppV4(props) {
7806
7952
  || left.skill.localeCompare(right.skill, 'en')
7807
7953
  || (left.agent ?? '').localeCompare(right.agent ?? '', 'en');
7808
7954
  });
7809
- const portableTechnicalSummary = showTechnical && (partial || blocked);
7810
- const portableSkillLimit = showTechnical ? 1 : responsiveBudget(2, 6);
7955
+ const portableSkillLimit = 1;
7956
+ const resultPaginationTotal = result.operationKind === 'portableInstall'
7957
+ ? prioritizedPortablePairs.length
7958
+ : baseResultPaginationTotal;
7959
+ const resultPageRows = result.operationKind === 'portableInstall' ? portableSkillLimit : 1;
7811
7960
  const portableSkillOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedPortablePairs.length - 1));
7812
7961
  const visiblePortablePairs = prioritizedPortablePairs.slice(portableSkillOffset, portableSkillOffset + portableSkillLimit);
7813
- const hiddenPortableSkills = Math.max(0, prioritizedPortablePairs.length - portableSkillOffset - visiblePortablePairs.length);
7962
+ const portableResultRange = prioritizedPortablePairs.length > 0
7963
+ ? `第 ${portableSkillOffset + 1}–${portableSkillOffset + visiblePortablePairs.length} 项,共 ${prioritizedPortablePairs.length} 项`
7964
+ : undefined;
7965
+ const portablePairStateLabel = (pair) => blocked ? result.writeMayHaveCompleted ? '结果未知' : '未执行'
7966
+ : pair.state === 'unknown' ? '结果未知'
7967
+ : pair.state === 'missing' ? '确认缺失' : '已验证';
7968
+ const selectedPortablePair = portableResultDetailOpen
7969
+ ? prioritizedPortablePairs.find((pair) => portablePairKey(pair) === portableResultDetailPairKey.current)
7970
+ : visiblePortablePairs[0];
7971
+ const wrapPortableField = (label, value) => {
7972
+ const chunks = wrapConsoleText(value, Math.max(12, size.columns - stringWidth(label) - 14));
7973
+ return chunks.map((line, index) => `${label} ${index + 1}/${chunks.length} · ${line}`);
7974
+ };
7975
+ const portableResultDetailLines = selectedPortablePair ? [
7976
+ ...wrapPortableField('Skill', selectedPortablePair.skill),
7977
+ ...wrapPortableField('Agent', selectedPortablePair.agent ?? '无法确认'),
7978
+ `状态 · ${portablePairStateLabel(selectedPortablePair)}`,
7979
+ ...(portableResultScope ? [`范围 · ${scopeLabel(portableResultScope)}`] : []),
7980
+ ...(portableReceipt?.recovery
7981
+ ? wrapPortableField('安全恢复', portableReceipt.recovery) : []),
7982
+ ...(portableReceipt?.failureCode
7983
+ ? wrapPortableField('failure code', portableReceipt.failureCode) : []),
7984
+ ...(portableReceipt ? [`verification missing · ${portableReceipt.missing.length}`] : []),
7985
+ ...(portableReceipt ? wrapPortableField('Skills CLI version', portableReceipt.skillsCliVersion) : []),
7986
+ ...(portableReceipt
7987
+ ? wrapPortableField('materialization digest', portableReceipt.materializationDigest) : []),
7988
+ ...(resultSummary?.elapsedMs !== undefined ? [`elapsed · ${resultSummary.elapsedMs} ms`] : []),
7989
+ ] : ['当前结果已变化。请返回结果摘要。'];
7990
+ const portableResultDetailPageRows = responsiveBudget(5, 10);
7991
+ const safePortableResultDetailOffset = Math.min(portableResultDetailOffset, Math.max(0, portableResultDetailLines.length - 1));
7992
+ const visiblePortableResultDetailLines = portableResultDetailLines.slice(safePortableResultDetailOffset, safePortableResultDetailOffset + portableResultDetailPageRows);
7993
+ if (portableOperation && portableResultDetailOpen) {
7994
+ title = '当前结果完整字段';
7995
+ description = '按行显示当前 Skill、Agent、状态和技术证据。';
7996
+ help = 'p/n 阅读全部字段 · ↑↓ 移动 · Enter 选择 · Esc 返回结果摘要';
7997
+ const detailPaginationActions = portableResultDetailLines.length > portableResultDetailPageRows
7998
+ ? paginationActions({
7999
+ offset: safePortableResultDetailOffset,
8000
+ total: portableResultDetailLines.length,
8001
+ pageRows: portableResultDetailPageRows,
8002
+ move: (direction) => setPortableResultDetailOffset((value) => Math.min(Math.max(0, portableResultDetailLines.length - 1), Math.max(0, value + direction * portableResultDetailPageRows))),
8003
+ })
8004
+ : [];
8005
+ actions = [
8006
+ ...detailPaginationActions,
8007
+ { label: '返回结果摘要', run: () => {
8008
+ setPortableResultDetailOpen(false);
8009
+ setPortableResultDetailOffset(0);
8010
+ setFocus(portableResultDetailReturnFocus.current);
8011
+ setScrollOffset(0);
8012
+ } },
8013
+ ];
8014
+ portableResultDetailPagination = {
8015
+ total: portableResultDetailLines.length,
8016
+ pageRows: portableResultDetailPageRows,
8017
+ };
8018
+ }
8019
+ else if (portableOperation) {
8020
+ const detailAction = selectedPortablePair ? {
8021
+ actionId: 'portable-result-full-fields',
8022
+ label: '查看当前结果完整字段',
8023
+ run: () => {
8024
+ portableResultDetailPairKey.current = portablePairKey(selectedPortablePair);
8025
+ portableResultDetailReturnFocus.current = focus;
8026
+ setPortableResultDetailOpen(true);
8027
+ setPortableResultDetailOffset(0);
8028
+ setFocus(0);
8029
+ setScrollOffset(0);
8030
+ },
8031
+ } : undefined;
8032
+ const pagination = resultPaginationTotal > 1 ? paginationActions({
8033
+ offset: batchTargetOffset,
8034
+ total: resultPaginationTotal,
8035
+ pageRows: resultPageRows,
8036
+ move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8037
+ }) : [];
8038
+ actions = [
8039
+ ...actions.slice(0, 1),
8040
+ ...pagination,
8041
+ ...(detailAction ? [detailAction] : []),
8042
+ ...actions.slice(1),
8043
+ ];
8044
+ if (pagination.length > 0) {
8045
+ referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8046
+ }
8047
+ }
8048
+ else if (resultPaginationTotal > 1) {
8049
+ actions.push(...paginationActions({
8050
+ offset: batchTargetOffset,
8051
+ total: resultPaginationTotal,
8052
+ pageRows: resultPageRows,
8053
+ move: (direction) => moveBatchTargetPage(resultPaginationTotal, resultPageRows, direction),
8054
+ }));
8055
+ referenceDetailPagination = { total: resultPaginationTotal, pageRows: resultPageRows };
8056
+ }
8057
+ const portableTechnicalFieldCount = portableReceipt
8058
+ ? 3 + (portableReceipt.failureCode ? 1 : 0)
8059
+ + (policy.mode === 'compact' ? 0 : resultSummary?.elapsedMs !== undefined ? 1 : 0)
8060
+ : 1;
8061
+ const portableFixedRows = showTechnical
8062
+ ? 1 + portableSkillLimit + 1
8063
+ + (portableResultAgentMode === 'automatic' ? 1 : 0)
8064
+ + (portableReceipt?.recovery ? 1 : 0)
8065
+ + 1 + portableTechnicalFieldCount
8066
+ : 3 + portableSkillLimit + 1
8067
+ + (portableResultAgentMode === 'automatic' ? 1 : 0)
8068
+ + (portableReceipt?.recovery ? 1 : 0);
8069
+ currentActionViewportLineBudget = pageActionBudget(portableOperation
8070
+ ? portableResultDetailOpen
8071
+ ? visiblePortableResultDetailLines.length
8072
+ + (safePortableResultDetailOffset > 0 ? 1 : 0)
8073
+ + (safePortableResultDetailOffset + visiblePortableResultDetailLines.length
8074
+ < portableResultDetailLines.length ? 1 : 0)
8075
+ : portableFixedRows
8076
+ : resultPaginationTotal > 1 ? 7 : 8);
8077
+ const resultActionViewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
8078
+ const resultTargetLimit = Math.max(1, prefixRowLimit(prioritizedTargets.map(() => policy.mode === 'compact' ? 1 : 2), pageActionBudget() - 3 - resultActionViewport.limit - resultExtraRows));
8079
+ const resultTargetOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedTargets.length - 1));
8080
+ const visibleResultTargets = prioritizedTargets.slice(resultTargetOffset, resultTargetOffset + resultTargetLimit);
8081
+ const hiddenResultTargets = Math.max(0, resultTargets.length - resultTargetOffset - visibleResultTargets.length);
8082
+ const portableTechnicalSummary = showTechnical;
7814
8083
  const portableRows = visiblePortablePairs.map((pair) => {
7815
8084
  const status = blocked || pair.state !== 'verified' ? 'warning' : 'success';
7816
- const stateLabel = blocked ? result.writeMayHaveCompleted ? '结果未知' : '未执行'
7817
- : pair.state === 'unknown' ? '结果未知'
7818
- : pair.state === 'missing' ? '确认缺失'
7819
- : '已验证';
7820
- const meta = `Agent:${pair.agent ?? '无法确认'} · ${stateLabel}`;
8085
+ const stateLabel = portablePairStateLabel(pair);
8086
+ const meta = `${stateLabel} · Agent:${pair.agent ?? '无法确认'}`;
7821
8087
  return _jsx(ResultRow, { title: pair.skill, meta: meta, status: status, compact: true }, portablePairKey(pair));
7822
8088
  });
7823
- const compactTechnicalRows = portableReceipt ? [
7824
- portableReceipt.failureCode
7825
- ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }, "failure-code")
7826
- : _jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }, "materialization-digest"),
7827
- _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u6458\u8981\u5DF2\u4FDD\u7559", status: "muted", compact: true }, "folded-fields"),
7828
- ] : [
7829
- _jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }, "failure-code"),
7830
- _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u5B89\u5168 receipt", status: "muted", compact: true }, "folded-fields"),
7831
- ];
8089
+ const portableTechnicalRows = portableReceipt
8090
+ ? [
8091
+ ...(portableReceipt.failureCode
8092
+ ? [_jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }, "failure-code")]
8093
+ : []),
8094
+ _jsx(StaticRow, { title: "verification missing", meta: policy.mode === 'compact' && resultSummary?.elapsedMs !== undefined
8095
+ ? `${portableReceipt.missing.length} · elapsed ${resultSummary.elapsedMs} ms`
8096
+ : String(portableReceipt.missing.length), compact: true }, "verification-missing"),
8097
+ _jsx(StaticRow, { title: "Skills CLI version", meta: portableReceipt.skillsCliVersion, compact: true }, "skills-cli-version"),
8098
+ _jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }, "materialization-digest"),
8099
+ ...(policy.mode !== 'compact' && resultSummary?.elapsedMs !== undefined
8100
+ ? [_jsx(StaticRow, { title: "elapsed", meta: `${resultSummary.elapsedMs} ms`, compact: true }, "elapsed")]
8101
+ : []),
8102
+ ]
8103
+ : [
8104
+ _jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }, "failure-code"),
8105
+ ];
7832
8106
  const resetDispositionText = resultSummary?.resetDisposition === 'absent'
7833
8107
  ? '设置层原本不存在;没有删除内容'
7834
8108
  : resultSummary?.resetDisposition === 'deletedFile'
@@ -7908,14 +8182,19 @@ export function ConsoleAppV4(props) {
7908
8182
  const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
7909
8183
  if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
7910
8184
  || prioritizedPortablePairs.length > portableSkillLimit) {
7911
- help = 'PgUp/PgDn 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8185
+ help = 'p/n 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7912
8186
  }
7913
8187
  content = taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: "\u6240\u9009\u63D2\u4EF6\u7ED3\u679C", children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
7914
- ? _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
7915
- ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
7916
- : 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
7917
- ? _jsx(StaticRow, { title: "\u53E6\u6709\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "materialization digest \u00B7 elapsed", status: "muted", compact: true })
7918
- : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }), resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "elapsed", meta: `${resultSummary.elapsedMs} ms`, compact: true }) : null] })] }) : null, showTechnical && !portableReceipt ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }), _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u751F\u6210\u5B89\u5168 receipt", status: "muted", compact: true })] }) : null] }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: blockedDependencyFailure ? '依赖安装没有完成' : blocked ? result.writeMayHaveCompleted ? '结果未确认' : '操作未完成' : sourceOperation ? '来源配置写入已完成' : dependencyTargets ? partial ? '依赖安装没有全部完成' : '操作已完成' : resultSummary?.message ?? '状态已变化,请重新确认', meta: resultTargets.length > 0 ? `${resultTargets.length} 个目标` : undefined, children: [resetDispositionText ? _jsx(StaticRow, { title: resetDispositionText, status: "success", compact: true }) : null, configGroupRows, resultTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${resultTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleResultTargets.map((target) => { const state = operationTargetStateLabel(target.state); const relation = target.relation === 'requested' ? '你选择的插件' : target.relation === 'direct' ? '直接依赖' : target.relation === 'transitive' ? '间接依赖' : undefined; return _jsx(ResultRow, { title: resultTargetName(target), description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${target.pluginId ?? ''}:${target.platform}:${target.scope}`); }), hiddenResultTargets > 0 ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${hiddenResultTargets} 个目标`, status: "muted", compact: true }) : null, partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? _jsx(StaticRow, { title: "\u672A\u6267\u884C\u7684\u76EE\u6807\u6CA1\u6709\u5199\u5165\uFF1B\u5DF2\u5B8C\u6210\u6216\u5DF2\u4FDD\u7559\u7684\u76EE\u6807\u4FDD\u6301\u53EF\u7528\u3002", status: "muted", compact: true }) : null, configOperation && partial && !invalidConfigResult ? _jsx(Callout, { status: "warning", title: "\u6062\u590D\u6307\u5F15", description: "\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u5C42\u4E0D\u4F1A\u91CD\u5199\u3002\u5931\u8D25\u7684\u4FDD\u5B58\u5C42\u4FDD\u7559\u8349\u7A3F\uFF1B\u5E94\u7528\u5931\u8D25\u65F6\u53EA\u91CD\u8BD5\u5E94\u7528\u3002" }) : null, resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "\u5DF2\u7528\u65F6", meta: `${Math.floor(resultSummary.elapsedMs / 1_000)}s` }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: productResultMessage(message), status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] });
8188
+ ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableResultDetailOpen ? _jsxs(_Fragment, { children: [safePortableResultDetailOffset > 0
8189
+ ? _jsx(StaticRow, { title: `↑ 前面还有 ${safePortableResultDetailOffset} 行`, status: "muted", compact: true }) : null, visiblePortableResultDetailLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${safePortableResultDetailOffset + index}:${line}`)), safePortableResultDetailOffset + visiblePortableResultDetailLines.length
8190
+ < portableResultDetailLines.length
8191
+ ? _jsx(StaticRow, { title: `↓ 后面还有 ${portableResultDetailLines.length
8192
+ - safePortableResultDetailOffset - visiblePortableResultDetailLines.length} 行`, status: "muted", compact: true })
8193
+ : null] }) : portableTechnicalSummary ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', meta: portableResultRange, status: resultStatus, compact: true }), portableRows, portableResultScope ? _jsx(StaticRow, { title: "\u8303\u56F4", meta: scopeLabel(portableResultScope), compact: true }) : null, portableResultAgentMode === 'automatic'
8194
+ ? _jsx(StaticRow, { title: "Agent", meta: "Skills CLI \u9ED8\u8BA4\u9009\u62E9", compact: true }) : null, portableReceipt?.recovery ? _jsx(StaticRow, { title: `安全恢复:${portableReceipt.recovery}`, status: "warning", compact: true }) : null, _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5", status: "muted", compact: true }), portableTechnicalRows] }) : _jsx(_Fragment, { children: _jsxs(ResultCard, { status: resultStatus, title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', meta: portableResultRange, children: [portableRows, portableResultScope ? _jsx(StaticRow, { title: "\u8303\u56F4", meta: scopeLabel(portableResultScope), compact: true }) : null, portableResultAgentMode === 'automatic'
8195
+ ? _jsx(StaticRow, { title: "Agent", meta: "Skills CLI \u9ED8\u8BA4\u9009\u62E9", compact: true }) : null, portableReceipt?.recovery
8196
+ ? _jsx(StaticRow, { title: `安全恢复:${portableReceipt.recovery}`, status: "warning", compact: true })
8197
+ : null] }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: blockedDependencyFailure ? '依赖安装没有完成' : blocked ? result.writeMayHaveCompleted ? '结果未确认' : '操作未完成' : sourceOperation ? '来源配置写入已完成' : dependencyTargets ? partial ? '依赖安装没有全部完成' : '操作已完成' : resultSummary?.message ?? '状态已变化,请重新确认', meta: resultTargets.length > 0 ? `${resultTargets.length} 个目标` : undefined, children: [resetDispositionText ? _jsx(StaticRow, { title: resetDispositionText, status: "success", compact: true }) : null, configGroupRows, resultTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${resultTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleResultTargets.map((target) => { const state = operationTargetStateLabel(target.state); const relation = target.relation === 'requested' ? '你选择的插件' : target.relation === 'direct' ? '直接依赖' : target.relation === 'transitive' ? '间接依赖' : undefined; return _jsx(ResultRow, { title: resultTargetName(target), description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${target.pluginId ?? ''}:${target.platform}:${target.scope}`); }), hiddenResultTargets > 0 ? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${hiddenResultTargets} 个目标`, status: "muted", compact: true }) : null, partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? _jsx(StaticRow, { title: "\u672A\u6267\u884C\u7684\u76EE\u6807\u6CA1\u6709\u5199\u5165\uFF1B\u5DF2\u5B8C\u6210\u6216\u5DF2\u4FDD\u7559\u7684\u76EE\u6807\u4FDD\u6301\u53EF\u7528\u3002", status: "muted", compact: true }) : null, configOperation && partial && !invalidConfigResult ? _jsx(Callout, { status: "warning", title: "\u6062\u590D\u6307\u5F15", description: "\u5DF2\u4FDD\u5B58\u7684\u8BBE\u7F6E\u5C42\u4E0D\u4F1A\u91CD\u5199\u3002\u5931\u8D25\u7684\u4FDD\u5B58\u5C42\u4FDD\u7559\u8349\u7A3F\uFF1B\u5E94\u7528\u5931\u8D25\u65F6\u53EA\u91CD\u8BD5\u5E94\u7528\u3002" }) : null, resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "\u5DF2\u7528\u65F6", meta: `${Math.floor(resultSummary.elapsedMs / 1_000)}s` }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: productResultMessage(message), status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] });
7919
8198
  }
7920
8199
  else if (route.name === 'pluginContent' && plugin) {
7921
8200
  const overview = props.controller.getPluginContentOverview(plugin.pluginId);
@@ -7994,15 +8273,22 @@ export function ConsoleAppV4(props) {
7994
8273
  });
7995
8274
  const detailRowBudget = responsiveBudget(9, 15);
7996
8275
  const capabilityRowCount = plugin.capabilityUnsupported ? 4 : 0;
7997
- const componentRowBudget = Math.max(1, detailRowBudget - 2 - plugin.installedPlacements.length - capabilityRowCount);
8276
+ const componentRowBudget = Math.max(1, detailRowBudget - 4 - plugin.installedPlacements.length - capabilityRowCount);
7998
8277
  const componentLimit = prioritizedComponents.length > componentRowBudget
7999
- ? Math.max(1, componentRowBudget - 2)
8278
+ ? 1
8000
8279
  : componentRowBudget;
8001
8280
  const componentOffset = Math.min(batchTargetOffset, Math.max(0, prioritizedComponents.length - 1));
8002
8281
  const visibleComponents = prioritizedComponents.slice(componentOffset, componentOffset + componentLimit);
8003
8282
  const hiddenComponents = Math.max(0, prioritizedComponents.length - componentOffset - visibleComponents.length);
8004
8283
  if (prioritizedComponents.length > componentLimit) {
8005
- help = 'PgUp/PgDn 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8284
+ actions.push(...paginationActions({
8285
+ offset: componentOffset,
8286
+ total: prioritizedComponents.length,
8287
+ pageRows: componentLimit,
8288
+ move: (direction) => moveBatchTargetPage(prioritizedComponents.length, componentLimit, direction),
8289
+ }));
8290
+ referenceDetailPagination = { total: prioritizedComponents.length, pageRows: componentLimit };
8291
+ help = 'p/n 查看全部组件 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8006
8292
  }
8007
8293
  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' })] });
8008
8294
  }
@@ -8286,9 +8572,13 @@ export function ConsoleAppV4(props) {
8286
8572
  if (key.escape || key.return || input.toLowerCase() === 'b' || input === '?') {
8287
8573
  setOverlay(undefined);
8288
8574
  }
8289
- else if (key.ctrl && input.toLowerCase() === 'c') {
8575
+ else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
8290
8576
  setExitAfterBusy(true);
8291
8577
  }
8578
+ else if (sessionNavigation) {
8579
+ setOverlay(undefined);
8580
+ requestSessionNavigation(sessionNavigation);
8581
+ }
8292
8582
  }
8293
8583
  else if (key.escape && props.pendingSessionNavigation) {
8294
8584
  props.onCancelPendingSessionNavigation?.();
@@ -8324,6 +8614,9 @@ export function ConsoleAppV4(props) {
8324
8614
  if (key.escape || input.toLowerCase() === 'b') {
8325
8615
  closeProjectReminder();
8326
8616
  }
8617
+ else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
8618
+ requestConfirmedExit(closeProjectReminder);
8619
+ }
8327
8620
  else if (props.onSessionNavigation && (input === 'h' || input === 'H')) {
8328
8621
  closeProjectReminder();
8329
8622
  requestSessionNavigation(input === 'H' ? 'reload' : 'home');
@@ -8446,7 +8739,7 @@ export function ConsoleAppV4(props) {
8446
8739
  return;
8447
8740
  }
8448
8741
  if (overlay) {
8449
- if (key.ctrl && input.toLowerCase() === 'c') {
8742
+ if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
8450
8743
  if (overlay === 'sessionNavigation')
8451
8744
  continueEditingFromSessionNavigation();
8452
8745
  setDiscardForSessionNavigation(undefined);
@@ -8454,6 +8747,11 @@ export function ConsoleAppV4(props) {
8454
8747
  requestConfirmedExit(overlay === 'help' ? () => setOverlay(undefined) : undefined);
8455
8748
  return;
8456
8749
  }
8750
+ if (overlay === 'help' && props.onSessionNavigation && (input === 'h' || input === 'H')) {
8751
+ setOverlay(undefined);
8752
+ requestSessionNavigation(input === 'H' ? 'reload' : 'home');
8753
+ return;
8754
+ }
8457
8755
  if (overlay === 'help' && input.toLowerCase() === 'o') {
8458
8756
  setOverlay(undefined);
8459
8757
  go({ name: 'operationHistory' });
@@ -8558,8 +8856,20 @@ export function ConsoleAppV4(props) {
8558
8856
  setReportSaveError(undefined);
8559
8857
  return;
8560
8858
  }
8859
+ if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
8860
+ && route.name === 'result' && portableResultDetailOpen
8861
+ && portableResultDetailPagination) {
8862
+ const { total, pageRows } = portableResultDetailPagination;
8863
+ const direction = key.pageUp || input.toLowerCase() === 'p' ? -1 : 1;
8864
+ setPortableResultDetailOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + direction * pageRows)));
8865
+ return;
8866
+ }
8561
8867
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
8562
8868
  && (route.name === 'pluginContent'
8869
+ || route.name === 'pluginTechnical'
8870
+ || route.name === 'issueDetail' && showTechnical
8871
+ || route.name === 'sourceRemoveBlocked' && sourceDependenciesExpanded
8872
+ || route.name === 'pluginDependencies' && route.view === 'removeBlocked' && !showTechnical
8563
8873
  || route.name === 'pluginDependencyNode'
8564
8874
  && (route.view === 'references' || route.view === 'details')
8565
8875
  || route.name === 'configSave' && route.view === 'changes'
@@ -8593,11 +8903,9 @@ export function ConsoleAppV4(props) {
8593
8903
  }
8594
8904
  if ((key.pageUp || key.pageDown || input.toLowerCase() === 'p' || input.toLowerCase() === 'n')
8595
8905
  && route.name === 'sourceTechnical') {
8596
- const overview = props.controller.getSourceOverview(route.sourceId);
8597
- const diagnostic = sourceDiagnostic(route.sourceId, overview.health?.state ?? overview.source?.state, overview.detail, props.controller.getCatalogGeneration(route.sourceId), props.controller.getCatalogFreshness(route.sourceId));
8598
- const total = diagnostic ? diagnosticDetailRowCount(diagnostic) : 0;
8599
- const pageRows = responsiveBudget(5, 10);
8600
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p' ? -pageRows : pageRows))));
8906
+ const total = diagnosticDetailRowCount(resolveSourceTechnicalDiagnostic(route.sourceId));
8907
+ setBatchTargetOffset((value) => Math.min(Math.max(0, total - sourceTechnicalPageRows), Math.max(0, value + (key.pageUp || input.toLowerCase() === 'p'
8908
+ ? -sourceTechnicalPageRows : sourceTechnicalPageRows))));
8601
8909
  return;
8602
8910
  }
8603
8911
  if ((key.pageUp || key.pageDown) && route.name === 'sourceRemoveBlocked'
@@ -8964,7 +9272,8 @@ export function ConsoleAppV4(props) {
8964
9272
  }
8965
9273
  if ((route.name === 'pluginList' || route.name === 'sourceList'
8966
9274
  || route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
8967
- || route.name === 'batchPlacement' && pinnedActions.length > 0) && key.tab) {
9275
+ || route.name === 'batchPlacement' && pinnedActions.length > 0
9276
+ || route.name === 'portableSkillSelect') && key.tab) {
8968
9277
  setFocusZone((current) => current === 'content' ? 'actions' : 'content');
8969
9278
  return;
8970
9279
  }
@@ -9000,6 +9309,15 @@ export function ConsoleAppV4(props) {
9000
9309
  runAction(pinnedActions[pinnedActionFocus]);
9001
9310
  return;
9002
9311
  }
9312
+ if (route.name === 'portableSkillSelect' && focusZone === 'actions'
9313
+ && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow)) {
9314
+ setPinnedActionFocus((value) => moveFocus(pinnedActions, value, key.upArrow || key.leftArrow ? -1 : 1));
9315
+ return;
9316
+ }
9317
+ if (route.name === 'portableSkillSelect' && focusZone === 'actions' && key.return) {
9318
+ runAction(pinnedActions[pinnedActionFocus]);
9319
+ return;
9320
+ }
9003
9321
  if ((route.name === 'batchInstallPlatforms' || route.name === 'batchInstallScope'
9004
9322
  || route.name === 'batchPlacement' && pinnedActions.length > 0)
9005
9323
  && focusZone === 'actions'
@@ -9083,6 +9401,17 @@ export function ConsoleAppV4(props) {
9083
9401
  else if (input.toLowerCase() === 'd' && supportsTechnicalDetails) {
9084
9402
  toggleTechnical();
9085
9403
  }
9404
+ else if (globalAction === 'back' && route.name === 'result' && portableResultDetailOpen) {
9405
+ setPortableResultDetailOpen(false);
9406
+ setPortableResultDetailOffset(0);
9407
+ setFocus(portableResultDetailReturnFocus.current);
9408
+ setScrollOffset(0);
9409
+ }
9410
+ else if (globalAction === 'back' && route.name === 'portableSkillSelect' && portableToolsOpen) {
9411
+ setPortableToolsOpen(false);
9412
+ setFocus(0);
9413
+ setScrollOffset(0);
9414
+ }
9086
9415
  else if (globalAction === 'back'
9087
9416
  && (route.name === 'configSave' || route.name === 'sourceSave')
9088
9417
  && route.view === 'changes') {
@@ -9249,18 +9578,19 @@ export function ConsoleAppV4(props) {
9249
9578
  movePluginListSource?.(direction);
9250
9579
  }
9251
9580
  else if (input.toLowerCase() === 'c' && route.name === 'portableSkillSelect') {
9252
- runAction(actions.find((action) => action.actionId === 'portable-selection-continue'));
9581
+ runAction(pinnedActions.find((action) => action.actionId === 'portable-selection-continue'));
9253
9582
  }
9254
9583
  else if (input.toLowerCase() === 'a' && route.name === 'portableSkillSelect') {
9255
- props.controller.selectAllPortableSkills();
9584
+ runAction(selectAllPortableCandidates);
9256
9585
  }
9257
- else if (input === ' ' && route.name === 'portableSkillSelect') {
9586
+ else if (input === ' ' && route.name === 'portableSkillSelect'
9587
+ && focusZone === 'content' && !portableToolsOpen) {
9258
9588
  const candidate = portableVisibleCandidates[focus];
9259
9589
  if (candidate)
9260
9590
  props.controller.togglePortableSkill(candidate.name);
9261
9591
  }
9262
9592
  else if (key.upArrow || input.toLowerCase() === 'k') {
9263
- if (route.name === 'pluginDependencyNode' && route.view === 'references') {
9593
+ if (route.name === 'pluginDependencyNode' && route.view === 'references' && !showTechnical) {
9264
9594
  setBatchTargetOffset(0);
9265
9595
  }
9266
9596
  setFocus((value) => {
@@ -9276,10 +9606,10 @@ export function ConsoleAppV4(props) {
9276
9606
  }
9277
9607
  const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9278
9608
  ? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
9279
- : route.name === 'portableSkillSelect' && snapshot.portableDraft
9609
+ : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
9280
9610
  ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), portableVisibleCandidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
9281
9611
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
9282
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
9612
+ : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
9283
9613
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
9284
9614
  const projection = buildUnifiedDependencyGraphProjection({
9285
9615
  overviews: props.controller.listPluginDependencyOverviews(),
@@ -9298,7 +9628,7 @@ export function ConsoleAppV4(props) {
9298
9628
  });
9299
9629
  }
9300
9630
  else if (key.downArrow || input.toLowerCase() === 'j') {
9301
- if (route.name === 'pluginDependencyNode' && route.view === 'references') {
9631
+ if (route.name === 'pluginDependencyNode' && route.view === 'references' && !showTechnical) {
9302
9632
  setBatchTargetOffset(0);
9303
9633
  }
9304
9634
  setFocus((value) => {
@@ -9314,10 +9644,10 @@ export function ConsoleAppV4(props) {
9314
9644
  }
9315
9645
  const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
9316
9646
  ? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
9317
- : route.name === 'portableSkillSelect' && snapshot.portableDraft
9647
+ : route.name === 'portableSkillSelect' && snapshot.portableDraft && !portableToolsOpen
9318
9648
  ? portableSkillCandidateViewport(actions.slice(0, portableVisibleCandidates.length), portableVisibleCandidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - portableVisibleCandidates.length
9319
9649
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
9320
- : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
9650
+ : actionViewport(actions, route.name === 'pluginDetail' || policy.mode === 'compact', next, scrollOffset, currentActionViewportLineBudget);
9321
9651
  if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
9322
9652
  const projection = buildUnifiedDependencyGraphProjection({
9323
9653
  overviews: props.controller.listPluginDependencyOverviews(),