@lesliechan721/aw 0.1.0-beta.34 → 0.1.0-beta.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/scripts/test-plugin-scripts.js +1 -1
  2. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  3. package/dist/src/commands/aw-capabilities.js +1 -0
  4. package/dist/src/commands/aw-capabilities.js.map +1 -1
  5. package/dist/src/portable/materializer.js +19 -3
  6. package/dist/src/portable/materializer.js.map +1 -1
  7. package/dist/src/review/review-profile.js +19 -2
  8. package/dist/src/review/review-profile.js.map +1 -1
  9. package/dist/src/tui/components.d.ts +20 -0
  10. package/dist/src/tui/components.js +13 -1
  11. package/dist/src/tui/components.js.map +1 -1
  12. package/dist/src/tui/config-array-draft.d.ts +29 -0
  13. package/dist/src/tui/config-array-draft.js +48 -0
  14. package/dist/src/tui/config-array-draft.js.map +1 -0
  15. package/dist/src/tui/config-value-presentation.d.ts +1 -0
  16. package/dist/src/tui/config-value-presentation.js +17 -1
  17. package/dist/src/tui/config-value-presentation.js.map +1 -1
  18. package/dist/src/tui/console-app-v4.js +504 -288
  19. package/dist/src/tui/console-app-v4.js.map +1 -1
  20. package/dist/src/tui/controller-runtime.js +27 -2
  21. package/dist/src/tui/controller-runtime.js.map +1 -1
  22. package/dist/src/tui/controller.d.ts +15 -0
  23. package/dist/src/tui/controller.js +111 -4
  24. package/dist/src/tui/controller.js.map +1 -1
  25. package/dist/src/tui/model.d.ts +9 -0
  26. package/dist/src/tui/model.js +10 -2
  27. package/dist/src/tui/model.js.map +1 -1
  28. package/dist/src/tui/project-inventory.d.ts +68 -0
  29. package/dist/src/tui/project-inventory.js +203 -0
  30. package/dist/src/tui/project-inventory.js.map +1 -0
  31. package/dist/src/tui/router.d.ts +16 -3
  32. package/dist/src/tui/router.js.map +1 -1
  33. package/dist/src/tui/screen-registry.d.ts +7 -1
  34. package/dist/src/tui/screen-registry.js +1 -1
  35. package/dist/src/tui/screen-registry.js.map +1 -1
  36. package/dist/src/tui/theme.d.ts +12 -0
  37. package/dist/src/tui/theme.js +12 -0
  38. package/dist/src/tui/theme.js.map +1 -1
  39. package/dist/src/types.d.ts +2 -0
  40. package/dist/src/types.js.map +1 -1
  41. package/dist/src/utils/catalog-source-references.js +2 -2
  42. package/dist/src/utils/plugin-config-write.js +3 -1
  43. package/dist/src/utils/plugin-config-write.js.map +1 -1
  44. package/dist/src/utils/plugin-config.d.ts +5 -5
  45. package/dist/src/utils/plugin-config.js +47 -23
  46. package/dist/src/utils/plugin-config.js.map +1 -1
  47. package/package.json +3 -3
@@ -1,7 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { arrayDraftChanged, arrayDraftValue, createConfigArrayDraft, editConfigArrayDraft } from './config-array-draft.js';
3
+ import { PLUGIN_CONFIG_ARRAY_MAX_ITEMS } from '@lesliechan721/aw-plugin-core';
2
4
  import { presentConfigValue, safeConfigText } from './config-value-presentation.js';
3
5
  import { Box, Text, useInput, useWindowSize } from 'ink';
4
- import { useEffect, useRef, useState } from 'react';
6
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
5
7
  import path from 'node:path';
6
8
  import stringWidth from 'string-width';
7
9
  import { validatePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
@@ -12,7 +14,7 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
12
14
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
13
15
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
14
16
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
15
- import { ActionRow, ObjectCardRow, objectCardRowLayout, pluginListRowLayout, StatusBadge, diagnosticDetailLines, diagnosticDetailText, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
17
+ import { ActionRow, ConfigTile, configTileHeight, HomeNavTile, KeyCap, ObjectCardRow, objectCardRowLayout, pluginListRowLayout, StatusBadge, diagnosticDetailLines, diagnosticDetailText, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
16
18
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
17
19
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
18
20
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
@@ -31,7 +33,7 @@ import { contentRowBudget, rowViewport, wrapDisplayText } from './viewport.js';
31
33
  import { resolveRouteScreenId, routeDefinition, TUI_SCREEN_IDS, unreachableTuiRoute, } from './screen-registry.js';
32
34
  export const CONSOLE_V4_SCREEN_IDS = TUI_SCREEN_IDS;
33
35
  const PLUGIN_FILTERS = [
34
- 'all', 'installed', 'uninstalled', 'updateAvailable', 'attention',
36
+ 'all', 'installed', 'uninstalled', 'updateAvailable', 'attention', 'project', 'user',
35
37
  ];
36
38
  function pluginFilterLabel(filter) {
37
39
  return {
@@ -40,6 +42,8 @@ function pluginFilterLabel(filter) {
40
42
  uninstalled: '未安装',
41
43
  updateAvailable: '可更新',
42
44
  attention: '需处理',
45
+ project: '当前项目',
46
+ user: '所有项目',
43
47
  }[filter];
44
48
  }
45
49
  function sourceOperationBlockLabel(reason) {
@@ -198,6 +202,9 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
198
202
  rows.push(['? / o', '关闭帮助 / 查看操作记录']);
199
203
  return rows;
200
204
  }
205
+ function projectDependencyLines(root) {
206
+ return root?.dependencies.flatMap((dependency) => dependency.targets.map((target) => `依赖 ${dependency.displayName} · ${dependency.pluginId} · ${platformLabel(target.platform)} · ${target.location === 'sharedUser' ? '共用个人安装' : '当前项目'} · 期望 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'}${target.reasonCodes.length ? ` · ${target.reasonCodes.map((code) => safeDiagnosticCode(code, 'projectTargetUnknown')).join('、')}` : ''}`)) ?? [];
207
+ }
201
208
  function configOwnerKey(route) {
202
209
  return route.pluginId;
203
210
  }
@@ -401,25 +408,7 @@ function dedupeActions(actions) {
401
408
  });
402
409
  }
403
410
  function wrapConsoleText(value, columns) {
404
- const width = Math.max(1, columns);
405
- const graphemes = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(value)]
406
- .map((part) => part.segment);
407
- const lines = [];
408
- let line = '';
409
- let lineWidth = 0;
410
- for (const grapheme of graphemes) {
411
- const graphemeWidth = stringWidth(grapheme);
412
- if (line && lineWidth + graphemeWidth > width) {
413
- lines.push(line);
414
- line = '';
415
- lineWidth = 0;
416
- }
417
- line += grapheme;
418
- lineWidth += graphemeWidth;
419
- }
420
- if (line || lines.length === 0)
421
- lines.push(line);
422
- return lines;
411
+ return wrapDisplayText(value, columns);
423
412
  }
424
413
  function sourceRefSummary(value) {
425
414
  if (!value)
@@ -859,7 +848,8 @@ function SemanticRows(props) {
859
848
  return null;
860
849
  const index = visibleIndex + offset;
861
850
  const rowProps = {
862
- title: action.label,
851
+ title: _jsx(Text, { children: action.label.split(/(\[[a-z/?]\])/u).map((part, index) => /^\[[a-z/?]\]$/u.test(part)
852
+ ? _jsx(KeyCap, { children: part.slice(1, -1) }, index) : part) }),
863
853
  description: action.description,
864
854
  meta: action.meta ?? (props.compact ? action.disabled : undefined),
865
855
  status: action.status,
@@ -889,11 +879,13 @@ function SequenceRows(props) {
889
879
  ];
890
880
  const viewport = rowViewport({
891
881
  rowHeights: props.actions.map((action, index) => action.role === 'navigation' ? 0 : index < props.itemCount
892
- ? props.wrapValues ? configLines(action).length : props.cards ? objectCardRowLayout({
893
- width: props.columns, description: action.disabled ?? action.description ?? '',
894
- meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows,
895
- hasTags: action.tags !== undefined, compact: props.compact,
896
- }).height : action.description ? 2 : 1
882
+ ? action.configTile ? configTileHeight({ width: props.columns, ...action.configTile })
883
+ : props.skillRows ? 2 + (action.exception ? 1 : 0)
884
+ : props.wrapValues ? configLines(action).length : props.cards ? objectCardRowLayout({
885
+ width: props.columns, description: action.disabled ?? action.description ?? '',
886
+ meta: action.meta, sourceLabel: action.sourceLabel, maxDescriptionRows: action.maxDescriptionRows,
887
+ hasTags: action.tags !== undefined, compact: props.compact,
888
+ }).height : action.description ? 2 : 1
897
889
  : 1),
898
890
  focus: props.focus, requestedOffset: props.scrollOffset, rowBudget: props.rowBudget,
899
891
  });
@@ -902,6 +894,10 @@ function SequenceRows(props) {
902
894
  return null;
903
895
  const index = viewport.offset + visibleIndex;
904
896
  const focused = !props.inactive && index === props.focus;
897
+ if (index < props.itemCount && action.configTile)
898
+ return _jsx(ConfigTile, { width: props.columns, title: action.label, focused: focused, ...action.configTile }, action.configKey);
899
+ if (index < props.itemCount && props.skillRows)
900
+ return _jsxs(Box, { flexDirection: "column", backgroundColor: focused ? tuiTheme.colors.selectionBackground : undefined, children: [_jsxs(Text, { bold: focused, color: tuiTheme.roles.primaryValue, wrap: "truncate-end", children: [focused ? '›' : ' ', " [", action.selected ? 'x' : ' ', "] ", action.label] }), _jsxs(Text, { color: tuiTheme.roles.secondary, wrap: "truncate-end", children: [" ", action.description || '暂无简介'] }), action.exception ? _jsxs(Text, { color: tuiTheme.roles.warning, wrap: "truncate-end", children: [" ! ", action.exception] }) : null] }, action.actionId ?? action.label);
905
901
  if (index < props.itemCount && props.wrapValues)
906
902
  return _jsx(Box, { flexDirection: "column", children: configLines(action).map((line, lineIndex) => _jsxs(Text, { bold: focused && lineIndex === 0, color: action.status === 'error' ? 'red' : undefined, children: [lineIndex === 0 ? focused ? '› ' : ' ' : ' ', line] }, lineIndex)) }, action.configKey ?? action.label);
907
903
  return index < props.itemCount && props.cards
@@ -1161,6 +1157,9 @@ export function ConsoleAppV4(props) {
1161
1157
  const [configLayerPages, setConfigLayerPages] = useState({});
1162
1158
  const [configDrafts, setConfigDrafts] = useState({});
1163
1159
  const [configCandidate, setConfigCandidate] = useState();
1160
+ const [arrayDrafts, setArrayDrafts] = useState({});
1161
+ const [arrayToolsOpen, setArrayToolsOpen] = useState(false);
1162
+ const [arrayToolFocus, setArrayToolFocus] = useState(0);
1164
1163
  const [objectDrafts, setObjectDrafts] = useState({});
1165
1164
  const [changedObjectDraftKeys, setChangedObjectDraftKeys] = useState([]);
1166
1165
  const [objectError, setObjectError] = useState();
@@ -1208,7 +1207,7 @@ export function ConsoleAppV4(props) {
1208
1207
  const selectedDependencyReferenceIndex = useRef(0);
1209
1208
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
1210
1209
  const previousExternalDraftValues = useRef({
1211
- configDrafts, configCandidate, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1210
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1212
1211
  });
1213
1212
  const externalDraftFlowIds = useRef(new Map());
1214
1213
  const configOverviewAnchors = useRef(new Map());
@@ -1354,11 +1353,16 @@ export function ConsoleAppV4(props) {
1354
1353
  const savedScopes = new Set(snapshot.result.summary.configGroups
1355
1354
  ?.filter((group) => group.configSaved).map((group) => group.scope) ?? []);
1356
1355
  const nextDrafts = Object.fromEntries(Object.entries(configDrafts)
1357
- .filter(([draftScope]) => !savedScopes.has(draftScope)));
1356
+ .filter(([draftScope]) => !savedScopes.has(draftScope))
1357
+ .map(([scope, draft]) => [scope, draft.editEvidence ? { ...draft, editEvidence: Object.fromEntries(Object.entries(draft.editEvidence).map(([key, evidence]) => [key, {
1358
+ ...evidence, storeDigests: Object.fromEntries(Object.entries(evidence.storeDigests)
1359
+ .filter(([savedScope]) => !savedScopes.has(savedScope))),
1360
+ }])) } : draft]));
1358
1361
  setConfigDrafts(nextDrafts);
1359
1362
  setDirty(Object.keys(nextDrafts).length > 0);
1360
1363
  setConfigCandidate(undefined);
1361
1364
  setObjectDrafts({});
1365
+ setArrayDrafts({});
1362
1366
  setChangedObjectDraftKeys([]);
1363
1367
  setObjectError(undefined);
1364
1368
  }
@@ -1374,6 +1378,7 @@ export function ConsoleAppV4(props) {
1374
1378
  setDirty(Object.keys(nextDrafts).length > 0);
1375
1379
  setConfigCandidate(undefined);
1376
1380
  setObjectDrafts({});
1381
+ setArrayDrafts({});
1377
1382
  setChangedObjectDraftKeys([]);
1378
1383
  setObjectError(undefined);
1379
1384
  }
@@ -1458,6 +1463,7 @@ export function ConsoleAppV4(props) {
1458
1463
  const previous = previousExternalDraftValues.current;
1459
1464
  if (previous.configDrafts !== configDrafts
1460
1465
  || previous.configCandidate !== configCandidate
1466
+ || previous.arrayDrafts !== arrayDrafts
1461
1467
  || previous.objectDrafts !== objectDrafts
1462
1468
  || previous.changedObjectDraftKeys !== changedObjectDraftKeys) {
1463
1469
  externalDraftRevision.current.config += 1;
@@ -1467,7 +1473,7 @@ export function ConsoleAppV4(props) {
1467
1473
  if (previous.batchDraft !== batchDraft)
1468
1474
  externalDraftRevision.current.batch += 1;
1469
1475
  previousExternalDraftValues.current = {
1470
- configDrafts, configCandidate, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1476
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1471
1477
  };
1472
1478
  const currentFlowId = snapshot.router.currentFlowId;
1473
1479
  if (currentFlowId && configRouteOwner) {
@@ -1476,7 +1482,7 @@ export function ConsoleAppV4(props) {
1476
1482
  if (currentFlowId && sourceDraft)
1477
1483
  externalDraftFlowIds.current.set(sourceDraft.owner, currentFlowId);
1478
1484
  const drafts = [];
1479
- if ((dirty || configCandidate || changedObjectDraftKeys.length > 0) && draftOwner) {
1485
+ if ((dirty || configCandidate || Object.values(arrayDrafts).some((draft) => draft.initial === undefined ? draft.rows.length > 0 : arrayDraftChanged(draft)) || changedObjectDraftKeys.length > 0) && draftOwner) {
1480
1486
  const flowId = externalDraftFlowIds.current.get(draftOwner);
1481
1487
  drafts.push({
1482
1488
  kind: 'configDraft', owner: draftOwner, revision: externalDraftRevision.current.config,
@@ -1499,7 +1505,7 @@ export function ConsoleAppV4(props) {
1499
1505
  props.controller.setExternalDraftState(drafts);
1500
1506
  }, [
1501
1507
  props.controller, batchDraft, changedObjectDraftKeys, configRouteOwner, dirty, draftOwner,
1502
- configDrafts, configCandidate, objectDrafts, route.name, snapshot.router.currentFlowId, sourceDraft,
1508
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, route.name, snapshot.router.currentFlowId, sourceDraft,
1503
1509
  ]);
1504
1510
  useEffect(() => () => props.controller.setExternalDraftState(), [props.controller]);
1505
1511
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
@@ -1564,6 +1570,7 @@ export function ConsoleAppV4(props) {
1564
1570
  setConfigDrafts({});
1565
1571
  setConfigCandidate(undefined);
1566
1572
  setObjectDrafts({});
1573
+ setArrayDrafts({});
1567
1574
  setChangedObjectDraftKeys([]);
1568
1575
  setObjectError(undefined);
1569
1576
  setDirty(false);
@@ -1805,14 +1812,49 @@ export function ConsoleAppV4(props) {
1805
1812
  setFocus(0);
1806
1813
  }, [props.controller, route.name, route.name === 'pluginList' ? route.mode : '',
1807
1814
  route.name === 'pluginList' ? route.sourceId : '', snapshot.phase]);
1808
- useEffect(() => {
1815
+ useLayoutEffect(() => {
1809
1816
  setBatchTargetOffset(0);
1810
1817
  }, [route.name, route.name === 'review' || route.name === 'result' ? route.operationId : '']);
1811
- const activeField = route.name === 'configFieldEditor'
1818
+ const activeTopField = route.name === 'configFieldEditor'
1812
1819
  || route.name === 'configObjectPropertyEditor'
1813
1820
  || route.name === 'configFieldAction'
1814
1821
  ? currentConfigPage?.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
1815
1822
  : undefined;
1823
+ const arrayItem = route.name === 'configFieldEditor' || route.name === 'configObjectPropertyEditor'
1824
+ ? route.arrayItem : undefined;
1825
+ const activeArrayKey = activeTopField?.definition.type === 'array' && configRoute
1826
+ ? `${configOwnerKey(configRoute)}\0${activeTopField.key}` : undefined;
1827
+ const activeArrayDraft = activeArrayKey ? arrayDrafts[activeArrayKey] : undefined;
1828
+ const itemValue = arrayItem && activeArrayDraft
1829
+ ? activeArrayDraft.rows.find((row) => row.rowId === arrayItem.rowId)?.value : undefined;
1830
+ const activeField = arrayItem && activeTopField?.definition.type === 'array'
1831
+ ? {
1832
+ ...activeTopField,
1833
+ type: activeTopField.definition.items.type,
1834
+ definition: activeTopField.definition.items,
1835
+ title: arrayItem.rowId === undefined ? '新增项' : `第 ${(activeArrayDraft?.rows.findIndex((row) => row.rowId === arrayItem.rowId) ?? 0) + 1} 项`,
1836
+ description: activeTopField.definition.items.description,
1837
+ choices: activeTopField.definition.items.choices?.presets,
1838
+ allowCustom: activeTopField.definition.items.choices?.allowCustom ?? true,
1839
+ value: itemValue, draftValue: itemValue, configured: itemValue !== undefined,
1840
+ min: activeTopField.definition.items.type === 'number' ? activeTopField.definition.items.validation?.minimum : undefined,
1841
+ max: activeTopField.definition.items.type === 'number' ? activeTopField.definition.items.validation?.maximum : undefined,
1842
+ }
1843
+ : activeTopField;
1844
+ useEffect(() => {
1845
+ if (!activeArrayKey || activeArrayDraft || !activeTopField || route.name !== 'configFieldEditor')
1846
+ return;
1847
+ const value = draftUpdates[activeTopField.key] ?? activeTopField.draftValue ?? activeTopField.value;
1848
+ setArrayDrafts((current) => ({ ...current, [activeArrayKey]: { ...createConfigArrayDraft(Array.isArray(value) ? value : undefined), evidence: currentConfigPage?.editEvidence } }));
1849
+ }, [activeArrayKey, activeArrayDraft, activeTopField, route.name]);
1850
+ useEffect(() => {
1851
+ if (route.name !== 'configFieldEditor' || route.editor !== 'array' || !activeArrayKey || !activeArrayDraft)
1852
+ return;
1853
+ const row = activeArrayDraft.rows[focus];
1854
+ if (row && row.rowId !== activeArrayDraft.selectedRowId) {
1855
+ setArrayDrafts((current) => ({ ...current, [activeArrayKey]: { ...activeArrayDraft, selectedRowId: row.rowId } }));
1856
+ }
1857
+ }, [route.name, route.name === 'configFieldEditor' ? route.editor : '', activeArrayKey, activeArrayDraft, focus]);
1816
1858
  useEffect(() => {
1817
1859
  if (!configRoute
1818
1860
  || configRoute.name === 'configFields'
@@ -1832,13 +1874,13 @@ export function ConsoleAppV4(props) {
1832
1874
  }
1833
1875
  }, [props.controller, route, activeField]);
1834
1876
  const activeObjectKey = activeField?.type === 'object' && configRoute
1835
- ? `${configOwnerKey(configRoute)}\0${activeField.key}`
1877
+ ? `${configOwnerKey(configRoute)}\0${activeField.key}${arrayItem ? `\0${arrayItem.rowId ?? 'new'}` : ''}`
1836
1878
  : undefined;
1837
- const objectValueFromUpdate = activeField && Object.hasOwn(draftUpdates, activeField.key)
1879
+ const objectValueFromUpdate = !arrayItem && activeField && Object.hasOwn(draftUpdates, activeField.key)
1838
1880
  && typeof draftUpdates[activeField.key] === 'object'
1839
1881
  ? draftUpdates[activeField.key]
1840
1882
  : undefined;
1841
- const activeCandidateMutation = activeField ? configCandidate?.mutations[activeField.key] : undefined;
1883
+ const activeCandidateMutation = !arrayItem && activeField ? configCandidate?.mutations[activeField.key] : undefined;
1842
1884
  const objectValueFromCandidate = activeCandidateMutation?.kind === 'set'
1843
1885
  && typeof activeCandidateMutation.value === 'object'
1844
1886
  ? activeCandidateMutation.value
@@ -1864,7 +1906,7 @@ export function ConsoleAppV4(props) {
1864
1906
  if (route.name !== 'configFieldEditor' || !activeField)
1865
1907
  return;
1866
1908
  const candidate = configCandidate?.mutations[activeField.key];
1867
- const draft = candidate?.kind === 'set' ? candidate.value : draftUpdates[activeField.key];
1909
+ const draft = arrayItem ? itemValue : candidate?.kind === 'set' ? candidate.value : draftUpdates[activeField.key];
1868
1910
  setInputValue(activeField.sensitive
1869
1911
  ? draft === undefined ? '' : String(draft)
1870
1912
  : typeof (draft ?? activeField.draftValue ?? activeField.value) === 'object'
@@ -1873,7 +1915,7 @@ export function ConsoleAppV4(props) {
1873
1915
  setInputError(undefined);
1874
1916
  }, [
1875
1917
  route.name,
1876
- route.name === 'configFieldEditor' ? `${route.fieldKey}:${route.mode ?? ''}` : '',
1918
+ route.name === 'configFieldEditor' ? `${route.fieldKey}:${route.mode ?? ''}:${route.arrayItem ? route.arrayItem.rowId ?? 'new' : 'field'}` : '',
1877
1919
  activeField?.key,
1878
1920
  configCandidate,
1879
1921
  ]);
@@ -2097,7 +2139,7 @@ export function ConsoleAppV4(props) {
2097
2139
  sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
2098
2140
  focusedEntityId: route.name === 'pluginList'
2099
2141
  ? pluginRows[focus]?.pluginId
2100
- : route.name === 'batchSelection' ? actions[focus]?.pluginId
2142
+ : route.name === 'batchSelection' || route.name === 'projectStatus' ? actions[focus]?.pluginId
2101
2143
  : route.name === 'sourceList' ? props.controller.listSources()[focus]?.id : undefined,
2102
2144
  });
2103
2145
  props.controller.push(next);
@@ -2155,6 +2197,12 @@ export function ConsoleAppV4(props) {
2155
2197
  ? restoredEntityIndex
2156
2198
  : Math.min(restoredFocus, Math.max(0, rowIds.length - 1));
2157
2199
  }
2200
+ else if (restoredFrame.route.name === 'projectStatus') {
2201
+ const roots = props.controller.listProjectInventoryRoots(restored.projectSection ?? 'declared').filter((root) => !restored.query || `${root.displayName} ${root.pluginId}`.toLocaleLowerCase().includes(restored.query.toLocaleLowerCase()));
2202
+ const index = roots.findIndex((root) => root.pluginId === restored.focusedEntityId);
2203
+ itemCount = roots.length + 2;
2204
+ restoredFocus = index >= 0 ? index : Math.min(restoredFocus, Math.max(0, itemCount - 1));
2205
+ }
2158
2206
  else if (restoredFrame.route.name === 'batchSelection' && batchDraft) {
2159
2207
  const candidates = batchCandidates(batchDraft);
2160
2208
  const entityIndex = candidates.findIndex((candidate) => candidate.pluginId === restored.focusedEntityId);
@@ -2842,6 +2890,7 @@ export function ConsoleAppV4(props) {
2842
2890
  configured,
2843
2891
  privateValue: field.sensitive,
2844
2892
  summary: field.summary,
2893
+ itemSummary: field.itemSummary,
2845
2894
  full,
2846
2895
  }).text;
2847
2896
  const effectiveFieldDisplay = (field) => {
@@ -2895,13 +2944,74 @@ export function ConsoleAppV4(props) {
2895
2944
  if (!configRoute || !currentConfigPage)
2896
2945
  return;
2897
2946
  setConfigActionError(undefined);
2947
+ if (arrayItem && activeArrayKey && activeArrayDraft && activeField) {
2948
+ const issues = validatePluginConfigValue(activeField.definition, value, key);
2949
+ if (issues.length) {
2950
+ setObjectError(issues.map(objectIssueText).join(';'));
2951
+ return;
2952
+ }
2953
+ const next = editConfigArrayDraft(activeArrayDraft, { kind: 'set', rowId: arrayItem.rowId, value: value });
2954
+ setArrayDrafts((current) => ({ ...current, [activeArrayKey]: { ...next, evidence: activeArrayDraft.evidence } }));
2955
+ if (activeObjectKey)
2956
+ setObjectDrafts((current) => Object.fromEntries(Object.entries(current).filter(([owner]) => owner !== activeObjectKey)));
2957
+ back();
2958
+ const index = next.rows.findIndex((row) => row.rowId === next.selectedRowId);
2959
+ props.controller.updatePageContext({ focusIndex: index, scrollOffset: index });
2960
+ setFocus(index);
2961
+ setScrollOffset(index);
2962
+ return;
2963
+ }
2964
+ if (activeField?.definition.type === 'array' && activeArrayKey && activeArrayDraft
2965
+ && activeArrayDraft.initial !== undefined && configValuesEqual(activeArrayDraft.initial, value)) {
2966
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([owner]) => owner !== activeArrayKey)));
2967
+ back();
2968
+ return;
2969
+ }
2898
2970
  openConfigTarget({
2899
2971
  pluginId: configRoute.pluginId,
2900
2972
  fieldKey: key,
2901
2973
  groupId: snapshot.router.current.context.configGroupId,
2902
2974
  mutations: { [key]: { kind: 'set', value } },
2975
+ ...(activeArrayDraft?.evidence ? { editEvidence: activeArrayDraft.evidence } : {}),
2976
+ });
2977
+ };
2978
+ const editArray = (edit) => {
2979
+ if (!activeArrayKey || !activeArrayDraft)
2980
+ return;
2981
+ const next = editConfigArrayDraft(activeArrayDraft, edit);
2982
+ setArrayDrafts((current) => ({ ...current, [activeArrayKey]: { ...next, evidence: activeArrayDraft.evidence } }));
2983
+ setObjectError(undefined);
2984
+ const index = Math.max(0, next.rows.findIndex((row) => row.rowId === next.selectedRowId));
2985
+ setFocus(index);
2986
+ setScrollOffset(index);
2987
+ };
2988
+ const openArrayItem = (rowId) => {
2989
+ if (!activeField || activeField.definition.type !== 'array' || !configRoute)
2990
+ return;
2991
+ setObjectError(undefined);
2992
+ go({
2993
+ name: 'configFieldEditor', pluginId: configRoute.pluginId, layer: configRoute.layer,
2994
+ fieldKey: activeField.key, editor: activeField.definition.items.type,
2995
+ ...(activeField.definition.items.choices ? { mode: 'choices' } : {}),
2996
+ arrayItem: { rowId },
2903
2997
  });
2904
2998
  };
2999
+ const commitArray = () => {
3000
+ if (!activeField || activeField.definition.type !== 'array' || !activeArrayDraft || !activeArrayKey)
3001
+ return;
3002
+ const value = arrayDraftValue(activeArrayDraft);
3003
+ const issues = validatePluginConfigValue(activeField.definition, value, activeField.key);
3004
+ if (issues.length) {
3005
+ setObjectError(issues.slice(0, 2).map(objectIssueText).join(';'));
3006
+ return;
3007
+ }
3008
+ if (!arrayDraftChanged(activeArrayDraft)) {
3009
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== activeArrayKey)));
3010
+ back();
3011
+ return;
3012
+ }
3013
+ updateDraft(activeField.key, value);
3014
+ };
2905
3015
  const removeDraft = (key) => {
2906
3016
  if (!configRoute)
2907
3017
  return;
@@ -2924,7 +3034,14 @@ export function ConsoleAppV4(props) {
2924
3034
  setObjectError(undefined);
2925
3035
  setConfigActionError(undefined);
2926
3036
  };
2927
- const objectIssueText = (issue) => `${issue.path.property ?? issue.path.field}:${issue.message}`;
3037
+ const objectIssueText = (issue) => {
3038
+ const index = issue.path.segments?.find((part) => typeof part === 'number');
3039
+ const itemDefinition = activeTopField?.definition.type === 'array' ? activeTopField.definition.items : undefined;
3040
+ const property = issue.path.property;
3041
+ const label = property && itemDefinition?.type === 'object'
3042
+ ? itemDefinition.properties?.[property]?.title ?? property : property ?? activeField?.title ?? issue.path.field;
3043
+ return `${index === undefined ? '' : `第 ${Number(index) + 1} 项/`}${label}:${issue.message}`;
3044
+ };
2928
3045
  const commitObjectDraft = () => {
2929
3046
  if (!activeField || activeField.definition.type !== 'object' || !activeObjectDraft)
2930
3047
  return;
@@ -2957,6 +3074,7 @@ export function ConsoleAppV4(props) {
2957
3074
  propertyKey,
2958
3075
  dynamic,
2959
3076
  stage: exists ? 'action' : objectPropertyStage(definition),
3077
+ ...(arrayItem ? { arrayItem } : {}),
2960
3078
  });
2961
3079
  };
2962
3080
  const updateObjectProperty = (propertyKey, value) => {
@@ -3031,12 +3149,18 @@ export function ConsoleAppV4(props) {
3031
3149
  const previous = configDrafts[layer.scope] ?? { updates: {}, removes: [] };
3032
3150
  const updates = { ...previous.updates };
3033
3151
  const removes = new Set(previous.removes);
3152
+ const editEvidence = { ...previous.editEvidence };
3034
3153
  for (const [key, mutation] of Object.entries(configCandidate.mutations)) {
3035
3154
  const field = fields.get(key);
3155
+ if (configCandidate.editEvidence)
3156
+ editEvidence[key] = configCandidate.editEvidence;
3157
+ else
3158
+ delete editEvidence[key];
3036
3159
  if (mutation.kind === 'set') {
3037
3160
  removes.delete(key);
3038
3161
  if (field.configured && configValuesEqual(field.draftValue ?? field.value, mutation.value)) {
3039
3162
  delete updates[key];
3163
+ delete editEvidence[key];
3040
3164
  }
3041
3165
  else
3042
3166
  updates[key] = mutation.value;
@@ -3049,7 +3173,7 @@ export function ConsoleAppV4(props) {
3049
3173
  removes.delete(key);
3050
3174
  }
3051
3175
  }
3052
- const next = { ...configDrafts, [layer.scope]: { updates, removes: [...removes] } };
3176
+ const next = { ...configDrafts, [layer.scope]: { updates, removes: [...removes], editEvidence } };
3053
3177
  if (Object.keys(updates).length === 0 && removes.size === 0)
3054
3178
  delete next[layer.scope];
3055
3179
  const target = configCandidate.fieldKey
@@ -3063,22 +3187,44 @@ export function ConsoleAppV4(props) {
3063
3187
  if (!configCandidate.flowId) {
3064
3188
  props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer: { scope: 'effective' } }, target ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
3065
3189
  }
3190
+ if (configCandidate.fieldKey) {
3191
+ const owner = `${configCandidate.pluginId}\0${configCandidate.fieldKey}`;
3192
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== owner)));
3193
+ }
3066
3194
  setConfigDrafts(next);
3067
3195
  setDirty(Object.keys(next).length > 0);
3068
3196
  setConfigCandidate(undefined);
3069
3197
  setFocus(target?.focusIndex ?? 0);
3070
3198
  setScrollOffset(0);
3071
3199
  };
3200
+ useEffect(() => {
3201
+ if (route.name !== 'configLayer' || !configCandidate)
3202
+ return;
3203
+ const scopes = ['project', 'user'].filter((scope) => {
3204
+ const page = configLayerPages[scope];
3205
+ if (!page)
3206
+ return false;
3207
+ const fields = page.groups.flatMap((group) => group.fields);
3208
+ return Object.entries(configCandidate.mutations).every(([key, mutation]) => {
3209
+ const field = fields.find((entry) => entry.key === key);
3210
+ return field && (mutation.kind === 'set' ? configFieldCanSet(field) : configFieldCanUnset(field));
3211
+ });
3212
+ });
3213
+ if (scopes.length === 1) {
3214
+ openConfigLayerTarget({ scope: scopes[0] });
3215
+ setConfigActionError(`仅可保存到${configLayerLabel({ scope: scopes[0] })};已加入待保存修改。`);
3216
+ }
3217
+ }, [route.name, configCandidate, configLayerPages]);
3072
3218
  const configChanges = (page, draft) => {
3073
3219
  const fields = page.groups.flatMap((group) => group.fields);
3074
3220
  return [...Object.keys(draft.updates), ...draft.removes.filter((key) => !Object.hasOwn(draft.updates, key))]
3075
3221
  .map((key) => {
3076
3222
  const field = fields.find((candidate) => candidate.key === key);
3077
3223
  const beforeObject = field?.definition.type === 'object'
3078
- && typeof field.draftValue === 'object' ? field.draftValue : undefined;
3224
+ && typeof field.draftValue === 'object' && !Array.isArray(field.draftValue) ? field.draftValue : undefined;
3079
3225
  const updatedValue = draft.updates[key];
3080
3226
  const afterObject = field?.definition.type === 'object'
3081
- && typeof updatedValue === 'object' ? updatedValue : undefined;
3227
+ && typeof updatedValue === 'object' && !Array.isArray(updatedValue) ? updatedValue : undefined;
3082
3228
  const childSummary = (property, value) => {
3083
3229
  if (value === undefined)
3084
3230
  return '未设置';
@@ -3135,7 +3281,9 @@ export function ConsoleAppV4(props) {
3135
3281
  return {
3136
3282
  key,
3137
3283
  title: field?.title ?? key,
3138
- before: field ? fieldValueText(field, field.draftValue ?? field.value, true, field.configured) : '未设置',
3284
+ before: field?.definition.type === 'array' && !field.configured && field.effectiveValue !== undefined
3285
+ ? `未设置(当前有效列表:${fieldValueText(field, field.effectiveValue, true)})`
3286
+ : field ? fieldValueText(field, field.draftValue ?? field.value, true, field.configured) : '未设置',
3139
3287
  after,
3140
3288
  ...(children && children.length > 0 ? { children } : {}),
3141
3289
  };
@@ -3154,7 +3302,10 @@ export function ConsoleAppV4(props) {
3154
3302
  const updates = Object.fromEntries(Object.entries(draft.updates)
3155
3303
  .filter(([key]) => !invalidForScope.has(key)));
3156
3304
  const removes = draft.removes.filter((key) => !invalidForScope.has(key));
3157
- return Object.keys(updates).length + removes.length > 0 ? [[scope, { updates, removes }]] : [];
3305
+ return Object.keys(updates).length + removes.length > 0 ? [[scope, { updates, removes,
3306
+ ...(draft.editEvidence ? { editEvidence: Object.fromEntries(Object.entries(draft.editEvidence)
3307
+ .filter(([key]) => Object.hasOwn(updates, key))) } : {}),
3308
+ }]] : [];
3158
3309
  }));
3159
3310
  const owner = `${configOwnerKey(configRoute)}\0`;
3160
3311
  const nextChangedObjectKeys = changedObjectDraftKeys.filter((key) => !key.startsWith(owner)
@@ -3180,6 +3331,7 @@ export function ConsoleAppV4(props) {
3180
3331
  return [{
3181
3332
  scope,
3182
3333
  updates: draft.updates,
3334
+ ...(draft.editEvidence ? { editEvidence: draft.editEvidence } : {}),
3183
3335
  removeKeys: draft.removes,
3184
3336
  sensitiveKeys: fields.filter((field) => field.sensitive).map((field) => field.key),
3185
3337
  privateKeys: fields.filter((field) => configObjectHasSensitiveChild(field.definition))
@@ -3213,6 +3365,27 @@ export function ConsoleAppV4(props) {
3213
3365
  setConfigActionError('设置值未通过验证。请重新检查修改。');
3214
3366
  }
3215
3367
  };
3368
+ useEffect(() => {
3369
+ if (route.name === 'configSave' && route.view !== 'changes' && configDraftCount > 0 && currentConfigPage) {
3370
+ props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer: route.layer });
3371
+ beginConfigSave(true);
3372
+ }
3373
+ }, [route.name, route.name === 'configSave' ? route.view : undefined, currentConfigPage, configDraftCount]);
3374
+ useEffect(() => {
3375
+ if (route.name !== 'sourceSave' || route.view === 'changes' || sourceDraft?.owner !== route.owner)
3376
+ return;
3377
+ props.controller.replace({ name: 'sourceEdit', sourceId: sourceDraft.sourceId, owner: sourceDraft.owner });
3378
+ beginSourceReview(sourceDraft, false);
3379
+ }, [route.name, route.name === 'sourceSave' ? route.view : undefined, sourceDraft]);
3380
+ useEffect(() => {
3381
+ const intent = pendingReviewIntentRef.current;
3382
+ if (route.name !== 'review' || intent?.kind !== 'configSaveBatch' || !intent.apply
3383
+ || snapshot.activeReview?.configImpact?.state !== 'known'
3384
+ || snapshot.activeReview.configImpact.affectedTargetCount !== 0)
3385
+ return;
3386
+ back();
3387
+ void beginReview({ ...intent, apply: false });
3388
+ }, [route.name, snapshot.activeReview?.reviewToken]);
3216
3389
  useEffect(() => {
3217
3390
  if (!operationConfirmation)
3218
3391
  return;
@@ -3250,177 +3423,84 @@ export function ConsoleAppV4(props) {
3250
3423
  }
3251
3424
  else if (rendererFamily === 'home' && route.name === 'home') {
3252
3425
  const home = snapshot.viewModel.home;
3253
- const projectReminder = snapshot.projectIntentReminder;
3254
- const projectIntentIssue = snapshot.projectIntentIssue;
3255
- const projectPendingCount = (projectReminder?.actionableTargets.length ?? 0)
3256
- + (projectReminder?.blockedTargets.length ?? 0);
3257
- context = '当前项目';
3426
+ const inventory = snapshot.projectInventory;
3427
+ const plugins = props.controller.listPlugins({ mode: 'browse' });
3428
+ const attention = new Set(plugins.filter((entry) => entry.primaryAttention).map((entry) => entry.pluginId)).size;
3429
+ const unknown = new Set(plugins.filter((entry) => entry.supportedPlatforms.some((platform) => pluginPlatformUnknown(entry, platform))).map((entry) => entry.pluginId)).size;
3430
+ title = '';
3431
+ description = undefined;
3432
+ context = undefined;
3433
+ const pluginState = [home.pluginUpdateCount ? `↑ ${home.pluginUpdateCount} 可更新` : '',
3434
+ attention ? `! ${attention} 需处理` : '', unknown ? `? ${unknown} 未确认` : ''].filter(Boolean).join(' · ') || '✓ 当前可用';
3435
+ const projectState = !inventory ? '检查中' : inventory.declarationState === 'notProject' ? '当前不是项目'
3436
+ : inventory.declarationState === 'absent' ? '未配置项目插件'
3437
+ : inventory.declarationState !== 'ready' ? '! 项目声明需要检查'
3438
+ : [inventory.counts.blocked ? `! ${inventory.counts.blocked} 受阻` : '',
3439
+ inventory.counts.pending ? `↻ ${inventory.counts.pending} 待同步` : '',
3440
+ inventory.counts.unknown ? `? ${inventory.counts.unknown} 未确认` : ''].filter(Boolean).join(' · ') || '✓ 全部就绪';
3441
+ const sourceState = [home.catalogUpdateCount ? `↑ ${home.catalogUpdateCount} 有更新` : '',
3442
+ home.catalogRecoveryCount ? `! ${home.catalogRecoveryCount} 检查失败` : '',
3443
+ home.unavailableSourceCount ? `! ${home.unavailableSourceCount} 不可用` : '',
3444
+ home.catalogCheckingCount ? `${home.catalogCheckingCount} 检查中` : ''].filter(Boolean).join(' · ') || '✓ 当前可用';
3258
3445
  actions = [
3259
- { label: '插件', description: '浏览、安装和管理所有插件', meta: `${home.installedPlugins} 已安装`, status: home.pluginUpdateCount > 0 ? 'warning' : undefined, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3260
- { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
3261
- {
3262
- label: '插件来源',
3263
- description: '管理 Git 仓库和本地目录',
3264
- meta: home.catalogUpdateCount > 0 ? `${home.catalogUpdateCount} 个更新`
3265
- : home.catalogCheckingCount > 0 ? `${home.catalogCheckingCount} 个检查中`
3266
- : home.catalogRecoveryCount > 0 ? `${home.catalogRecoveryCount} 个需处理` : String(home.sourceCount),
3267
- status: home.catalogUpdateCount > 0 || home.catalogRecoveryCount > 0 ? 'warning' : undefined,
3268
- run: () => go({ name: 'sourceList' }),
3269
- },
3446
+ { label: '插件', description: pluginState, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3447
+ { label: '项目插件', description: projectState, run: () => go({ name: 'projectStatus' }) },
3448
+ { label: '插件来源', description: sourceState, run: () => go({ name: 'sourceList' }) },
3270
3449
  ];
3271
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["[\u5DF2\u5B89\u88C5 ", home.installedPlugins, "] [\u53EF\u66F4\u65B0 ", home.pluginUpdateCount, "]", home.unavailableSourceCount > 0 ? ` [不可用来源 ${home.unavailableSourceCount}]` : ''] }), _jsx(Text, { color: tuiTheme.colors.muted, children: projectIntentIssue ? '! 本项目插件声明需要检查'
3272
- : projectReminder && projectReminder.unconfirmedTargets.length > 0
3273
- ? '? 部分项目插件状态无法确认' : projectPendingCount > 0
3274
- ? `! ${projectPendingCount} 个位置待处理`
3275
- : snapshot.projectDeclarationAbsent
3276
- ? '当前目录没有共享插件声明;不会自动安装插件' : '没有待同步提醒' }), projectIntentIssue && showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u58F0\u660E\u8BCA\u65AD", compact: true, children: [_jsx(StaticRow, { title: "\u9519\u8BEF\u4EE3\u7801", meta: projectIntentIssue.code, compact: true }), _jsx(StaticRow, { title: "\u8BF4\u660E", meta: sanitizeDiagnosticText(projectIntentIssue.message), compact: true })] }) : null, _jsx(SemanticRows, { actions: actions, focus: projectReminderOpen ? -1 : focus, kind: "task", compact: false })] });
3450
+ content = _jsx(Box, { flexDirection: "column", children: actions.map((action, index) => _jsx(Box, { marginBottom: 1, children: _jsx(HomeNavTile, { width: size.columns - 2, title: action.label, focused: !projectReminderOpen && focus === index, children: _jsx(Text, { wrap: "wrap", children: action.description?.split(' · ').map((state, stateIndex) => _jsxs(Text, { color: state.startsWith('↑') ? tuiTheme.roles.change : state.startsWith('!') || state.startsWith('↻') ? tuiTheme.roles.warning
3451
+ : state.startsWith('?') ? tuiTheme.roles.unknown : state.startsWith('✓') ? tuiTheme.roles.success : tuiTheme.roles.secondary, children: [stateIndex > 0 ? ' ' : '', "[", state, "]"] }, stateIndex)) }) }) }, action.label)) });
3277
3452
  }
3278
3453
  else if (route.name === 'projectStatus') {
3279
- const reminder = snapshot.projectIntentReminder;
3280
- const projectIntentIssue = snapshot.projectIntentIssue;
3281
- const actionable = reminder?.actionableTargets ?? [];
3282
- const blocked = reminder?.blockedTargets ?? [];
3283
- const unconfirmed = reminder?.unconfirmedTargets ?? [];
3284
- const allTargets = [
3285
- ...actionable.map((target) => ({ target, state: 'actionable' })),
3286
- ...blocked.map((target) => ({ target, state: 'blocked' })),
3287
- ...unconfirmed.map((target) => ({ target, state: 'unconfirmed' })),
3288
- ];
3289
- const pluginCount = new Set(allTargets.map(({ target }) => target.pluginId)).size;
3290
- const sourceLabel = (pluginId) => pluginId.split('/')[0] ?? pluginId;
3291
- const isSourceBlocker = (reasonCodes) => reasonCodes.some((code) => code === 'catalogSourceUnavailable' || code === 'sourceUnavailable'
3292
- || code === 'catalogSourceMissing' || code === 'sourceMissing' || code === 'bundleUnavailable'
3293
- || code === 'codexIntentSourceUnavailable' || code === 'sourceChanged');
3294
- const sourceBlockedCount = blocked.filter((target) => isSourceBlocker(target.reasonCodes)).length;
3295
- const reasonLabel = (reasonCodes, state) => {
3296
- if (reasonCodes.includes('sourceChanged'))
3297
- return '来源身份已变化';
3298
- if (reasonCodes.includes('sourceMissing'))
3299
- return '来源已移除';
3300
- if (reasonCodes.includes('bundleUnavailable'))
3301
- return '当前来源目录没有此插件';
3302
- if (isSourceBlocker(reasonCodes))
3303
- return '来源当前不可用';
3304
- if (reasonCodes.includes('awCapabilityUnsupported'))
3305
- return '当前 AW 不支持所需能力';
3306
- if (reasonCodes.includes('codexExtensionScopeConflict'))
3307
- return '使用位置冲突';
3308
- if (reasonCodes.includes('artifactCollision'))
3309
- return '输出内容冲突';
3310
- if (reasonCodes.includes('codexSharedNativeForeign'))
3311
- return '共享位置存在非本插件内容';
3312
- if (reasonCodes.some((code) => code === 'codexSharedNativeUnobserved'
3313
- || code === 'codexNativeObservationFailed'))
3314
- return '平台状态未确认';
3315
- if (state === 'actionable')
3316
- return '已确认需要同步';
3317
- if (state === 'blocked')
3318
- return '当前目标受阻';
3319
- return '当前状态未确认';
3320
- };
3321
- breadcrumb = '需要处理 / 本项目';
3322
- title = projectIntentIssue || unconfirmed.length > 0
3323
- ? '尚未确认项目插件状态'
3324
- : blocked.length > 0 ? '部分项目插件需要先处理'
3325
- : allTargets.length === 0 ? '项目插件状态已检查' : '项目共享插件';
3326
- description = projectIntentIssue
3327
- ? projectIntentIssue.state === 'invalid'
3328
- ? '本项目插件声明无效。修复声明后重新检查。'
3329
- : '读取本项目插件声明失败。当前不会执行安装或恢复。'
3330
- : allTargets.length === 0
3331
- ? '当前没有待同步或待检查的项目插件位置。'
3332
- : `${pluginCount} 个插件涉及 ${allTargets.length} 个位置:`
3333
- + `${actionable.length} 个可同步 · ${blocked.length} 个受阻 · ${unconfirmed.length} 个未确认`;
3334
- context = allTargets.length > 0 ? `${pluginCount} 个插件 · ${allTargets.length} 个位置` : '当前项目';
3335
- actions = allTargets.map(({ target, state }) => {
3336
- const overview = props.controller.getPluginOverview(target.pluginId);
3337
- const status = state === 'actionable' ? 'warning'
3338
- : state === 'blocked' ? 'error' : 'muted';
3339
- const symbol = state === 'actionable' ? '!' : state === 'blocked' ? '!' : '?';
3340
- return {
3341
- actionId: `project-target:${target.pluginId}:${target.platform}:${target.scope}`,
3342
- label: overview?.displayName ?? target.pluginId.split('/').at(-1) ?? target.pluginId,
3343
- description: `${platformLabel(target.platform)} · 当前项目 · ${reasonLabel(target.reasonCodes, state)}`,
3344
- meta: `来源 ${sourceLabel(target.pluginId)} · 项目共享声明`,
3345
- tags: _jsx(StatusBadge, { items: [{
3346
- key: state,
3347
- label: state === 'actionable' ? '待同步' : state === 'blocked' ? '需处理' : '未确认',
3348
- symbol,
3349
- status,
3350
- }] }),
3351
- status,
3352
- run: state === 'blocked' && isSourceBlocker(target.reasonCodes)
3353
- ? () => {
3354
- const sourceId = sourceLabel(target.pluginId);
3355
- go(props.controller.getSourceOverview(sourceId).source
3356
- ? { name: 'sourceDetail', sourceId } : { name: 'sourceList' });
3357
- }
3358
- : state === 'unconfirmed' || !overview ? () => setShowTechnical(true)
3359
- : () => go({ name: 'pluginDetail', pluginId: target.pluginId }),
3360
- };
3361
- });
3454
+ const inventory = snapshot.projectInventory;
3455
+ const section = snapshot.router.current.context.projectSection ?? 'declared';
3456
+ const stateLabel = { blocked: '受阻', pending: '待同步', unknown: '未确认', ready: '就绪' };
3457
+ const roots = props.controller.listProjectInventoryRoots(section).filter((root) => !query || `${root.displayName} ${root.pluginId}`.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
3458
+ breadcrumb = '项目插件';
3459
+ title = showTechnical ? '项目状态安全详情' : inventory?.projectName ?? '项目插件';
3460
+ context = undefined;
3461
+ description = inventory?.declarationState === 'notProject' ? '当前不是项目;没有项目同步目标。'
3462
+ : inventory?.declarationState === 'absent' ? '未配置项目插件。'
3463
+ : snapshot.projectInventoryRefresh.state === 'error' ? '项目检查失败;可重新检查。'
3464
+ : inventory?.declarationState === 'invalid' || inventory?.declarationState === 'unavailable' ? '项目声明需要检查。' : undefined;
3465
+ actions = roots.map((root) => ({
3466
+ actionId: `project-root:${root.pluginId}`, pluginId: root.pluginId,
3467
+ label: root.displayName,
3468
+ description: [root.targets.map((target) => `${platformLabel(target.platform)} · ${target.location === 'sharedUser' ? '共用个人安装' : '当前项目'} · ${stateLabel[target.state]}`).join(';'),
3469
+ ...(root.dependencies.some((dependency) => dependency.targets.some((target) => target.location === 'sharedUser'))
3470
+ ? [`${root.dependencies.filter((dependency) => dependency.targets.some((target) => target.location === 'sharedUser')).length} 个依赖共用个人安装`] : [])].join('\n'),
3471
+ maxDescriptionRows: 2,
3472
+ sourceLabel: root.sourceId,
3473
+ run: () => go({ name: 'pluginDetail', pluginId: root.pluginId }),
3474
+ }));
3475
+ const syncTargets = inventory?.syncTargets ?? [];
3476
+ const syncCount = new Set(syncTargets.map((target) => target.pluginId)).size;
3362
3477
  bottomActions = [
3363
- ...(actionable.length > 0 ? [{
3364
- actionId: 'project-sync-actionable',
3365
- label: `同步明确的 ${actionable.length} 个位置`,
3366
- description: blocked.length + unconfirmed.length > 0
3367
- ? `保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置;进入现有操作汇总后再确认`
3368
- : '进入现有操作汇总后再确认',
3369
- run: () => prepareCurrentProjectTargets(actionable),
3370
- }] : []),
3371
- ...(sourceBlockedCount > 0 ? [{
3372
- actionId: 'project-open-sources',
3373
- label: '处理插件来源',
3374
- description: `${sourceBlockedCount} 个位置需要先处理来源问题`,
3375
- run: () => go({ name: 'sourceList' }),
3376
- }] : []),
3377
- {
3378
- actionId: 'project-recheck',
3379
- label: '重新检查',
3380
- description: '重新读取项目声明和平台状态;不会自动安装或恢复',
3381
- run: () => requestSessionNavigation('reload'),
3382
- },
3383
- ...(projectIntentIssue || blocked.length > 0 || unconfirmed.length > 0 ? [{
3384
- actionId: 'project-error-details',
3385
- label: '错误详情',
3386
- description: '查看现有安全错误代码和诊断,不执行写操作',
3387
- run: () => setShowTechnical((value) => !value),
3388
- }] : []),
3478
+ ...(inventory?.projectRoot && syncCount > 0 ? [{ actionId: 'project-sync-actionable',
3479
+ label: `同步待就绪插件(${syncCount}) [c]`, run: () => prepareCurrentProjectTargets(syncTargets) }] : []),
3480
+ ...(inventory?.counts.blocked ? [{ actionId: 'project-open-sources', label: '处理插件来源', run: () => go({ name: 'sourceList' }) }] : []),
3481
+ ...(!showTechnical && (inventory?.counts.blocked || inventory?.counts.unknown || snapshot.projectIntentIssue) ? [{ actionId: 'project-error-details', label: '错误详情', run: () => { setShowTechnical(true); setBatchTargetOffset(0); } }] : []),
3482
+ { actionId: 'project-recheck', label: snapshot.projectInventoryRefresh.state === 'checking' ? '正在检查项目' : '重新检查项目 [r]',
3483
+ disabled: snapshot.projectInventoryRefresh.state === 'checking' ? '检查中' : undefined,
3484
+ run: async () => { await props.controller.refreshProjectInventory(); } },
3389
3485
  ];
3390
- help = showTechnical
3391
- ? '←→ 或 PgUp/PgDn 阅读 · d 返回项目位置 · Esc 返回'
3392
- : '↑↓ 移动 · Enter 查看 · Esc 返回';
3393
- const projectItemCount = showTechnical ? 0 : actions.length;
3486
+ const technicalText = [snapshot.projectIntentIssue ? `${safeDiagnosticCode(snapshot.projectIntentIssue.code, 'projectIntentUnknown')} · ${sanitizeDiagnosticText(snapshot.projectIntentIssue.message)}` : '',
3487
+ ...roots.flatMap((root) => [...projectDependencyLines(root), ...root.targets.flatMap((target) => [
3488
+ `${root.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3489
+ `期望 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'} · ${stateLabel[target.state]}`,
3490
+ ...target.reasonCodes.map((code) => `原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`)
3491
+ ])])].filter(Boolean).join('\n');
3492
+ const itemCount = showTechnical ? 0 : actions.length;
3394
3493
  actions = [...(showTechnical ? [] : actions), ...bottomActions];
3395
- const technicalFields = [
3396
- ...(projectIntentIssue ? [
3397
- `项目声明 · ${safeDiagnosticCode(projectIntentIssue.code, 'projectIntentUnavailable')}`,
3398
- `说明 · ${sanitizeDiagnosticText(projectIntentIssue.message)}`,
3399
- ] : []),
3400
- ...allTargets.flatMap(({ target, state }) => [
3401
- `${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3402
- ...(target.reasonCodes.length > 0
3403
- ? target.reasonCodes.map((code) => `原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`)
3404
- : [state === 'actionable' ? '原因 · confirmedDifference'
3405
- : state === 'blocked' ? '原因 · blocked' : '原因 · unconfirmed']),
3406
- ]),
3407
- ];
3408
- const technicalLines = technicalFields.flatMap((field) => wrapConsoleText(field, Math.max(20, size.columns - 8)));
3409
- if (showTechnical)
3410
- readingText = technicalFields.join('\n');
3411
- if (showTechnical)
3412
- description = undefined;
3413
- const technicalPageRows = responsiveBudget(4, 8);
3414
- const technicalOffset = Math.min(batchTargetOffset, Math.max(0, technicalLines.length - 1));
3415
- const visibleTechnicalLines = technicalLines.slice(technicalOffset, technicalOffset + technicalPageRows);
3416
- referenceDetailPagination = showTechnical && technicalLines.length > 0
3417
- ? { total: technicalLines.length, pageRows: technicalPageRows }
3418
- : undefined;
3419
- content = _jsxs(Box, { flexDirection: "column", children: [!showTechnical && (projectIntentIssue || unconfirmed.length > 0) ? _jsx(StaticRow, { status: "warning", title: "? \u672A\u77E5\u72B6\u6001\u4E0D\u7B49\u4E8E\u672A\u5B89\u88C5\uFF1B\u6CA1\u6709\u81EA\u52A8\u6267\u884C\u5B89\u88C5\u6216\u6062\u590D\u3002", compact: true }) : null, !showTechnical && actionable.length > 0 && blocked.length + unconfirmed.length > 0
3420
- ? _jsx(StaticRow, { status: "info", title: `只同步明确项;保留 ${blocked.length} 个受阻位置和 ${unconfirmed.length} 个未确认位置。`, compact: true })
3421
- : null, showTechnical ? _jsxs(TechnicalDetails, { title: "\u9879\u76EE\u72B6\u6001\u5B89\u5168\u8BE6\u60C5", compact: true, children: [technicalOffset > 0
3422
- ? _jsx(StaticRow, { title: `↑ 前面还有 ${technicalOffset} 行`, status: "muted", compact: true }) : null, visibleTechnicalLines.map((line, index) => _jsx(StaticRow, { title: line, compact: true }, `${technicalOffset + index}:${line}`)), technicalOffset + visibleTechnicalLines.length < technicalLines.length
3423
- ? _jsx(StaticRow, { title: `↓ 后面还有 ${technicalLines.length - technicalOffset - visibleTechnicalLines.length} 行`, status: "muted", compact: true }) : null] }) : _jsx(SequenceRows, { actions: actions, itemCount: projectItemCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2), compact: policy.mode === 'compact', cards: true }), showTechnical ? _jsx(SemanticRows, { actions: actions, offset: projectItemCount, focus: focus, kind: "action", compact: true }) : null] });
3494
+ help = '↑↓ 移动 · Enter 查看 · Esc 返回';
3495
+ if (showTechnical) {
3496
+ readingText = technicalText;
3497
+ const lines = wrapConsoleText(technicalText, size.columns - 2);
3498
+ const pageRows = pageActionBudget(3);
3499
+ referenceDetailPagination = { total: lines.length, pageRows };
3500
+ content = _jsxs(Box, { flexDirection: "column", children: [lines.slice(batchTargetOffset, batchTargetOffset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
3501
+ }
3502
+ else
3503
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(KeyCap, { children: "f" }), " ", section === 'declared' ? '项目声明' : '额外安装', " \u00B7 ", roots.length] }), _jsxs(Text, { children: ["\u641C\u7D22 ", _jsx(KeyCap, { children: "/" }), " ", searchFocus ? _jsx(InputDisplay, { value: query, cursor: searchCursor, columns: size.columns - 14 }) : query || '名称或来源'] }), roots.length === 0 ? _jsx(Text, { color: tuiTheme.roles.secondary, children: "\u672C\u6761\u4EF6\u65E0\u5339\u914D" }) : null, _jsx(SequenceRows, { actions: actions, itemCount: itemCount, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2 + (roots.length === 0 ? 1 : 0)), compact: policy.mode === 'compact', cards: true, inactive: searchFocus })] });
3424
3504
  }
3425
3505
  else if (route.name === 'pluginList') {
3426
3506
  const sourceId = route.sourceId ?? 'all';
@@ -3463,9 +3543,10 @@ export function ConsoleAppV4(props) {
3463
3543
  setFocus(retained >= 0 ? retained : Math.min(focus, Math.max(0, nextRowIds.length - 1)));
3464
3544
  setScrollOffset(0);
3465
3545
  };
3546
+ const availableFilters = PLUGIN_FILTERS.filter((filter) => filter === 'all' || filterCounts[filter] > 0 || filter === installFilter);
3466
3547
  togglePluginListFilter = () => {
3467
- const nextFilter = PLUGIN_FILTERS[(PLUGIN_FILTERS.indexOf(installFilter) + 1)
3468
- % PLUGIN_FILTERS.length];
3548
+ const nextFilter = availableFilters[(availableFilters.indexOf(installFilter) + 1)
3549
+ % availableFilters.length];
3469
3550
  choosePluginListFilter?.(nextFilter);
3470
3551
  };
3471
3552
  choosePluginListSource = (next) => {
@@ -3501,6 +3582,8 @@ export function ConsoleAppV4(props) {
3501
3582
  .filter((candidate) => candidate.target.scope === 'project');
3502
3583
  const projectIntentRoots = props.controller.listCurrentProjectIntentRoots();
3503
3584
  const menuActions = [
3585
+ ...(props.controller.listUserIssues().length > 0 ? [{ actionId: 'plugin-list-issues',
3586
+ label: '需要处理', run: () => go({ name: 'issueList' }) }] : []),
3504
3587
  ...(recommendedUpdatePluginIds.length > 0 ? [{
3505
3588
  actionId: 'plugin-list-update',
3506
3589
  label: `更新 ${recommendedUpdatePluginIds.length} 个`,
@@ -3562,10 +3645,9 @@ export function ConsoleAppV4(props) {
3562
3645
  .flatMap((rowId) => pluginRowsById.get(rowId) ?? []);
3563
3646
  if (pluginRows[focus])
3564
3647
  pluginListFocusId.current = pluginRows[focus].pluginId;
3565
- if (installFilter === 'updateAvailable'
3566
- && items.length === 0 && !sourceError) {
3567
- title = '没有可更新插件';
3568
- description = '当前筛选范围内没有经过版本证据确认的更新。';
3648
+ if (installFilter !== 'all' && items.length === 0 && !sourceError) {
3649
+ title = '本条件无匹配';
3650
+ description = '保留当前筛选;可切换筛选或修改搜索。';
3569
3651
  actions = [{
3570
3652
  label: '查看全部我的插件',
3571
3653
  description: '按 f 返回完整列表',
@@ -3630,9 +3712,9 @@ export function ConsoleAppV4(props) {
3630
3712
  }).height : 1),
3631
3713
  focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(3),
3632
3714
  });
3633
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 [/] ", searchFocus
3715
+ content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 ", _jsx(KeyCap, { children: "/" }), " ", searchFocus
3634
3716
  ? _jsx(InputDisplay, { value: query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
3635
- : _jsxs(_Fragment, { children: [query || '输入名称或用途', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " [f] \u5207\u6362"] }), sourceError
3717
+ : _jsxs(_Fragment, { children: [query || '输入名称或用途', query ? ' / 编辑' : ''] })] }), _jsxs(Text, { wrap: "truncate-end", color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", pluginFilterLabel(installFilter), " ", filterCounts[installFilter], "]"] }), " ", _jsx(KeyCap, { children: "f" }), " \u5207\u6362"] }), sourceError
3636
3718
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
3637
3719
  : route.mode === 'installed' && items.length === 0 && !query && sourceId === 'all'
3638
3720
  ? installFilter === 'updateAvailable'
@@ -4161,7 +4243,7 @@ export function ConsoleAppV4(props) {
4161
4243
  breadcrumb = `${plugin.displayName} / 通用 Skills`;
4162
4244
  context = `${draft.selectedCount} / ${draft.totalCount} 已选择`;
4163
4245
  title = '选择要安装的 Skills';
4164
- description = '已根据插件依赖和当前设置准备候选;默认全部选择。';
4246
+ description = undefined;
4165
4247
  const visibleCandidates = portableSelectionView === 'selected'
4166
4248
  ? draft.candidates.filter((candidate) => candidate.selected)
4167
4249
  : draft.candidates;
@@ -4173,12 +4255,13 @@ export function ConsoleAppV4(props) {
4173
4255
  go({ name: 'portableAgentMode', pluginId: plugin.pluginId });
4174
4256
  };
4175
4257
  const candidateActions = visibleCandidates.map((candidate) => ({
4176
- label: candidate.name,
4258
+ label: draft.candidates.filter((entry) => entry.name === candidate.name).length > 1
4259
+ ? `${candidate.name} · ${candidate.providerDisplayName}` : candidate.name,
4177
4260
  selected: candidate.selected,
4178
- description: `${candidate.description}${candidate.dynamic ? ' · 按设置生成' : ''}`,
4179
- meta: `${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件'
4180
- : candidate.relation === 'direct' ? '直接依赖插件' : '间接依赖插件'}`,
4181
- tags: _jsxs(Text, { color: tuiTheme.colors.muted, children: ["[", candidate.availableScopes.map(scopeLabel).join('、') || '范围待确认', "]", candidate.dynamic ? ' [按设置生成]' : ''] }),
4261
+ description: candidate.shortDescription ?? candidate.description.split(/[。!?\n]/u)[0]?.slice(0, 160),
4262
+ exception: candidate.availableScopes.length === 0 ? '安装范围待确认'
4263
+ : portableScopeVariantsDiffer(candidate) ? '范围内容不同,继续后确认'
4264
+ : candidate.availableScopes.length === 1 ? `仅${scopeLabel(candidate.availableScopes[0])}` : undefined,
4182
4265
  run: () => props.controller.togglePortableSkill(candidate.name),
4183
4266
  }));
4184
4267
  selectAllPortableCandidates = { actionId: 'portable-selection-select-all', label: '全选当前结果', run: () => {
@@ -4225,7 +4308,7 @@ export function ConsoleAppV4(props) {
4225
4308
  help = portableToolsOpen ? '↑↓ 移动 · Enter 选择 · Esc 返回'
4226
4309
  : '↑↓ 移动 · Space/Enter 切换 · Esc 返回';
4227
4310
  content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SequenceRows, { actions: actions, itemCount: portableToolsOpen ? 0 : candidateActions.length, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget((variantCandidates.length > 0 ? 3 : 0) + (!draft.canContinue ? 1 : 0)
4228
- + (showTechnical && technicalVariant ? 5 : 0)), compact: policy.mode === 'compact', cards: true, inactive: portableEmptySelectionOpen }), !portableToolsOpen && variantCandidates.length > 0
4311
+ + (showTechnical && technicalVariant ? 5 : 0)), compact: policy.mode === 'compact', skillRows: true, inactive: portableEmptySelectionOpen }), !portableToolsOpen && variantCandidates.length > 0
4229
4312
  ? showTechnical
4230
4313
  ? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
4231
4314
  : _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
@@ -4245,25 +4328,23 @@ export function ConsoleAppV4(props) {
4245
4328
  }
4246
4329
  else if (route.name === 'portableDependencies' && plugin && snapshot.portableDraft) {
4247
4330
  const overview = dependencyOverview(plugin);
4248
- breadcrumb = `${plugin.displayName} / 通用 Skills / 依赖`;
4249
- context = `${(overview?.summary.totalCount ?? 0) + 1} 个插件`;
4250
- title = '生成这些候选 Skills 需要的插件';
4251
- description = '插件依赖用于生成,不能在这里取消。';
4252
- actions = [
4253
- { role: 'navigation', label: '返回 Skill 选择', run: back },
4254
- { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => toggleTechnical() },
4255
- ];
4256
4331
  const dependencies = overview?.dependencies ?? [];
4257
- readingText = [plugin.displayName, ...dependencies.map((entry) => `${entry.displayName} · ${entry.pluginId} · ${entry.relation === 'direct' ? '直接依赖' : '间接依赖'} · ${entry.requiredBy.map((parent) => parent.displayName).join('、')} 需要它`)].join('\n');
4258
- const dependencyLimit = showTechnical ? 1 : responsiveBudget(2, 3);
4259
- const dependencyOffset = Math.min(batchTargetOffset, Math.max(0, dependencies.length - dependencyLimit));
4260
- const visibleDependencies = dependencies.slice(dependencyOffset, dependencyOffset + dependencyLimit);
4261
- const technicalDependencies = dependencies.slice(dependencyOffset, dependencyOffset + 1);
4262
- const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
4263
- help = dependencies.length > dependencyLimit
4264
- ? '↑↓ 移动 · PageUp/PageDown 或 ←→ 阅读依赖 · Enter 选择 · Esc 返回'
4265
- : '↑↓ 移动 · Enter 选择 · Esc 返回';
4266
- content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56\u63D2\u4EF6", meta: `${dependencies.length} 个`, children: [dependencyOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${dependencyOffset} 个依赖插件`, status: "muted", compact: true }) : null, visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '当前插件直接需要' : '由依赖插件继续需要'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > dependencyOffset + visibleDependencies.length ? _jsx(StaticRow, { title: `↓ 后面还有 ${dependencies.length - dependencyOffset - visibleDependencies.length} 个依赖插件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: entry.pluginId, compact: true }, entry.pluginId)) }) : null] });
4332
+ breadcrumb = `${plugin.displayName} / 通用 Skills / 依赖`;
4333
+ context = `${dependencies.length + 1} 个插件`;
4334
+ title = '本次生成依赖';
4335
+ description = '这些插件用于生成所选 Skills;安装位置在最终汇总中确认。';
4336
+ const fields = [plugin.displayName, ...dependencies.flatMap((entry) => [
4337
+ `${entry.displayName} · ${entry.pluginId}`,
4338
+ `${entry.relation === 'direct' ? '直接依赖' : '间接依赖'} · ${entry.requiredBy.map((parent) => parent.displayName).join('、')} 需要它`,
4339
+ `提供 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === entry.pluginId).length} 个 Skills`,
4340
+ ])];
4341
+ readingText = fields.join('\n');
4342
+ const lines = fields.flatMap((field) => wrapConsoleText(field, size.columns - 2));
4343
+ const pageRows = pageActionBudget(2);
4344
+ referenceDetailPagination = { total: lines.length, pageRows };
4345
+ const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
4346
+ actions = [{ label: '返回 Skills 选择', run: back }];
4347
+ content = _jsxs(Box, { flexDirection: "column", children: [lines.slice(offset, offset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsxs(Text, { color: tuiTheme.roles.secondary, children: [offset + 1, "\u2013", Math.min(lines.length, offset + pageRows), " / ", lines.length] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
4267
4348
  }
4268
4349
  else if (route.name === 'portableAgentMode' && plugin && snapshot.portableDraft) {
4269
4350
  breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
@@ -5212,6 +5293,20 @@ export function ConsoleAppV4(props) {
5212
5293
  ? '可以先处理可执行项,或进入下方对应入口处理受阻位置。'
5213
5294
  : '请进入下方对应入口处理;返回后会重新读取当前证据。', compact: policy.mode === 'compact' }) : null, missingTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${missingTargetKeys.length} 个本次安装位置已失效或无需处理`, description: "\u4E0D\u4F1A\u628A\u8FD9\u4E9B\u4F4D\u7F6E\u5F53\u4F5C\u5DF2\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u7F29\u5C0F\u672C\u6B21\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, addedTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${addedTargetKeys.length} 个新位置需要重新确认`, description: "\u4E0D\u4F1A\u628A\u5237\u65B0\u540E\u65B0\u589E\u7684\u4F4D\u7F6E\u81EA\u52A8\u52A0\u5165\u672C\u6B21\u64CD\u4F5C\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, versionEvidenceChanged ? _jsx(Callout, { status: "warning", title: "\u63D2\u4EF6\u76EE\u6807\u7248\u672C\u5DF2\u53D8\u5316", description: "\u4E0D\u4F1A\u6CBF\u7528\u6765\u6E90\u542F\u7528\u524D\u7684\u7248\u672C\u8BC1\u636E\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u6267\u884C\u65B0\u7248\u672C\u3002", compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: taskViewport.offset, limit: taskViewport.limit })] });
5214
5295
  }
5296
+ else if (route.name === 'pluginDetail' && !plugin && props.controller.getProjectInventoryRoot(route.pluginId)) {
5297
+ const root = props.controller.getProjectInventoryRoot(route.pluginId);
5298
+ breadcrumb = '项目插件';
5299
+ title = root.displayName;
5300
+ description = '来源当前没有此插件;项目声明仍保留。';
5301
+ const fields = [root.pluginId, `来源:${root.sourceId}`, ...projectDependencyLines(root), ...root.targets.map((target) => `${platformLabel(target.platform)} · ${target.location === 'sharedUser' ? '共用个人安装' : '当前项目'} · 期望 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'} · ${target.reasonCodes.join('、')}`)];
5302
+ readingText = fields.join('\n');
5303
+ const lines = fields.flatMap((field) => wrapConsoleText(field, size.columns - 2));
5304
+ const pageRows = pageActionBudget(3);
5305
+ referenceDetailPagination = { total: lines.length, pageRows };
5306
+ actions = [{ label: '查看插件来源', run: () => go(props.controller.getSourceOverview(root.sourceId).source
5307
+ ? { name: 'sourceDetail', sourceId: root.sourceId } : { name: 'sourceList' }) }];
5308
+ content = _jsxs(Box, { flexDirection: "column", children: [lines.slice(batchTargetOffset, batchTargetOffset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
5309
+ }
5215
5310
  else if (route.name === 'pluginDetail' && plugin) {
5216
5311
  breadcrumb = `插件 / ${plugin.displayName}`;
5217
5312
  context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
@@ -5409,6 +5504,8 @@ export function ConsoleAppV4(props) {
5409
5504
  plugin.description,
5410
5505
  `版本 · ${versionSummary}`,
5411
5506
  `来源 · ${plugin.sourceLabel}`,
5507
+ ...(props.controller.getProjectInventoryRoot(plugin.pluginId)?.targets.map((target) => `${platformLabel(target.platform)} · ${target.location === 'sharedUser' ? '共用个人安装' : '项目声明'} · 期望 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'}`) ?? []),
5508
+ ...projectDependencyLines(props.controller.getProjectInventoryRoot(plugin.pluginId)),
5412
5509
  ...(detailPositions.length > 0 ? detailPositions : [platformSummary]),
5413
5510
  ...(plugin.portableSkills ? ['[Skills 可选]'] : []),
5414
5511
  ...(plugin.configOnly ? ['[已保存设置]'] : []),
@@ -5769,7 +5866,7 @@ export function ConsoleAppV4(props) {
5769
5866
  const readonly = false;
5770
5867
  breadcrumb = `${plugin.displayName} / 设置`;
5771
5868
  title = '插件设置';
5772
- description = '先编辑值,再选择保存位置。←→ 切换分组。';
5869
+ description = undefined;
5773
5870
  if (configActionError)
5774
5871
  description = configActionError;
5775
5872
  help = '↑↓ 移动 · Enter 修改· ←→ 分组 · s 查看保存汇总 · u 恢复继承 · Esc 返回';
@@ -5785,7 +5882,7 @@ export function ConsoleAppV4(props) {
5785
5882
  ? currentConfigPage.groups.find((group) => group.id === activeTab.id)
5786
5883
  : undefined;
5787
5884
  if (configDraftIssues.length > 0) {
5788
- description += ` 有 ${configDraftIssues.length} 项未保存修改已不适用于当前设置。`;
5885
+ description = `${description ?? ''} 有 ${configDraftIssues.length} 项未保存修改已不适用于当前设置。`;
5789
5886
  }
5790
5887
  const mutableLayer = { scope: 'effective' };
5791
5888
  const tabActions = activeTab?.kind === 'presets'
@@ -5798,19 +5895,21 @@ export function ConsoleAppV4(props) {
5798
5895
  }))
5799
5896
  : (activeGroup?.fields ?? []).filter((field) => readonly || configFieldCanSet(field) || configFieldCanUnset(field)).map((field) => ({
5800
5897
  label: field.title,
5801
- description: field.invalidOverrideIssue ?? (focus === (activeGroup?.fields ?? []).indexOf(field)
5802
- ? [field.description, ...pendingFieldTargets(field).filter(() => pendingFieldTargets(field).length > 1
5803
- || field.effectiveSourceLabel === '当前项目' && pendingFieldDisplay(field, 'project') === undefined).map(({ scope, value }) => `待保存到${configLayerLabel({ scope })}:${value}`),
5804
- pendingFieldDisplay(field, 'user') !== undefined
5805
- && field.effectiveSourceLabel === '当前项目'
5806
- && pendingFieldDisplay(field, 'project') === undefined
5807
- ? '个人默认待修改,当前项目不受此值影响' : undefined,
5808
- ].filter(Boolean).join(' · ') : undefined),
5809
- meta: (() => {
5898
+ description: field.invalidOverrideIssue ?? field.description,
5899
+ configTile: (() => {
5810
5900
  const project = pendingFieldDisplay(field, 'project');
5811
5901
  const user = pendingFieldDisplay(field, 'user');
5812
- const pending = project ?? (field.effectiveSourceLabel === '当前项目' ? undefined : user);
5813
- return `${pending ?? effectiveFieldDisplay(field)} · ${pending !== undefined ? `拟保存:${project !== undefined ? '当前项目' : '个人默认'} · 待保存` : field.effectiveSourceLabel}${pendingFieldTargets(field).length > 0 && pending === undefined ? ' · 有草稿' : ''}`;
5902
+ const covered = user !== undefined && field.effectiveSourceLabel === '当前项目' && project === undefined;
5903
+ const pending = project ?? (covered ? undefined : user);
5904
+ const targets = pendingFieldTargets(field);
5905
+ return {
5906
+ value: pending ?? effectiveFieldDisplay(field),
5907
+ source: field.effectiveSourceLabel,
5908
+ invalid: Boolean(field.invalidOverrideIssue),
5909
+ ...(targets.length ? { pending: covered
5910
+ ? '待保存到个人;个人修改不会影响本项目'
5911
+ : `待保存到${targets.map(({ scope }) => configLayerLabel({ scope })).join('、')}` } : {}),
5912
+ };
5814
5913
  })(),
5815
5914
  configKey: field.key,
5816
5915
  run: () => go({
@@ -5852,13 +5951,13 @@ export function ConsoleAppV4(props) {
5852
5951
  actionId: 'config-save-summary',
5853
5952
  label: configDraftCount > 0 ? `保存设置(${configDraftCount}) [s]` : '保存设置 [s]',
5854
5953
  description: '确认全部设置层的变化,再选择只保存或保存并应用',
5855
- status: configDraftCount > 0 ? 'success' : 'muted',
5954
+ status: configDraftCount > 0 ? 'info' : 'muted',
5856
5955
  disabled: configDraftCount === 0 ? '没有待保存设置'
5857
5956
  : configDraftIssues.length > 0 ? '请先放弃失效草稿' : undefined,
5858
5957
  run: () => {
5859
5958
  setFocus(0);
5860
5959
  setScrollOffset(0);
5861
- go({ name: 'configSave', pluginId: route.pluginId, layer: { scope: 'effective' } });
5960
+ beginConfigSave(true);
5862
5961
  },
5863
5962
  };
5864
5963
  bottomActions = [configSaveAction, {
@@ -5905,7 +6004,7 @@ export function ConsoleAppV4(props) {
5905
6004
  actions = [{ label: '返回设置', run: back }];
5906
6005
  help = '←→ 或 PgUp/PgDn 阅读 · Esc 返回';
5907
6006
  return _jsxs(Box, { flexDirection: "column", children: [lines.slice(offset, offset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
5908
- })() : _jsxs(Box, { flexDirection: "column", children: [!readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, _jsx(SequenceRows, { actions: actions, itemCount: tabActions.length, wrapValues: true, focus: effectiveConfigFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2 + (configDraftIssues.length > 0 ? 3 : 0)), compact: policy.mode === 'compact' })] });
6007
+ })() : _jsxs(Box, { flexDirection: "column", children: [!readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, _jsx(SequenceRows, { actions: actions, itemCount: tabActions.length, focus: effectiveConfigFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2 + (configDraftIssues.length > 0 ? 3 : 0)), compact: policy.mode === 'compact' }), focusedConfigField && (focusedConfigField.invalidOverrideIssue || focusedConfigField.description) ? _jsx(Text, { color: focusedConfigField.invalidOverrideIssue ? tuiTheme.roles.error : tuiTheme.roles.secondary, wrap: "truncate-end", children: focusedConfigField.invalidOverrideIssue ?? focusedConfigField.description }) : null] });
5909
6008
  }
5910
6009
  }
5911
6010
  else if (route.name === 'configSave' && plugin) {
@@ -5977,12 +6076,12 @@ export function ConsoleAppV4(props) {
5977
6076
  const showChoices = (activeField.choices?.length ?? 0) > 0 && route.mode !== 'custom';
5978
6077
  const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields).filter((field) => field.key === activeField.key) ?? []);
5979
6078
  const canSet = targetFields.some(configFieldCanSet);
5980
- const canUnset = targetFields.some(configFieldCanUnset);
6079
+ const canUnset = !arrayItem && targetFields.some(configFieldCanUnset);
5981
6080
  if (!canSet) {
5982
6081
  title = activeField.title;
5983
6082
  description = activeField.invalidOverrideIssue ?? '此旧值不允许修改;可以删除覆盖并恢复继承。';
5984
- actions = [];
5985
- content = _jsx(StatusCard, { status: "warning", title: "\u4EC5\u53EF\u5220\u9664\u65E7\u503C", description: description });
6083
+ actions = canUnset ? [{ label: '删除此覆盖并恢复继承', run: () => removeDraft(activeField.key) }] : [];
6084
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u4EC5\u53EF\u5220\u9664\u65E7\u503C", description: description }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
5986
6085
  }
5987
6086
  else if (showChoices) {
5988
6087
  title = `选择${activeField.title}`;
@@ -5992,7 +6091,7 @@ export function ConsoleAppV4(props) {
5992
6091
  help = '↑↓ 移动 · Enter 使用 · Esc 取消';
5993
6092
  const current = activeCandidateMutation?.kind === 'set'
5994
6093
  ? activeCandidateMutation.value
5995
- : draftUpdates[activeField.key] ?? activeField.draftValue ?? activeField.value;
6094
+ : (arrayItem ? itemValue : draftUpdates[activeField.key]) ?? activeField.draftValue ?? activeField.value;
5996
6095
  const booleanCustomValues = activeField.type === 'boolean' && activeField.allowCustom
5997
6096
  ? [true, false].filter((value) => !activeField.choices?.some((preset) => preset.value === value))
5998
6097
  : [];
@@ -6022,20 +6121,42 @@ export function ConsoleAppV4(props) {
6022
6121
  ];
6023
6122
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
6024
6123
  }
6124
+ else if (route.editor === 'array' && activeField.definition.type === 'array') {
6125
+ title = activeField.title;
6126
+ description = objectError ?? `${activeArrayDraft?.rows.length ?? 0} 项 · 顺序将按当前列表保存`;
6127
+ help = '↑↓ 移动 · Enter 编辑 · c 使用此列表 · Esc 取消';
6128
+ const rows = activeArrayDraft?.rows ?? [];
6129
+ const definition = activeField.definition;
6130
+ const errors = activeArrayDraft ? validatePluginConfigValue(definition, arrayDraftValue(activeArrayDraft), activeField.key) : [];
6131
+ const itemActions = rows.map((row, index) => ({
6132
+ actionId: `array:${row.rowId}`,
6133
+ label: `${String(index + 1).padStart(2, '0')} ${presentConfigValue({ definition: definition.items, value: row.value, summary: activeField.itemSummary }).text}`,
6134
+ run: () => openArrayItem(row.rowId),
6135
+ }));
6136
+ bottomActions = [{
6137
+ label: '添加一项', run: () => openArrayItem(),
6138
+ disabled: rows.length >= (definition.validation?.maxItems ?? PLUGIN_CONFIG_ARRAY_MAX_ITEMS) ? '已达到列表最大项数' : undefined,
6139
+ }, {
6140
+ label: '调整当前项', disabled: rows.length === 0 ? '列表为空' : undefined,
6141
+ run: () => { setArrayToolFocus(0); setArrayToolsOpen(true); },
6142
+ }, { label: '使用此列表 [c]', run: commitArray }];
6143
+ actions = [...itemActions, ...bottomActions];
6144
+ content = _jsxs(Box, { flexDirection: "column", children: [rows.length === 0 ? _jsx(Text, { children: "\u7A7A\u5217\u8868\uFF1B\u786E\u8BA4\u4F7F\u7528\u540E\u8986\u76D6\u539F\u503C\u3002" }) : null, errors.length ? _jsx(Text, { color: tuiTheme.colors.warning, children: errors.slice(0, 2).map(objectIssueText).join(';') }) : null, _jsx(SequenceRows, { actions: actions, itemCount: itemActions.length, focus: focus, scrollOffset: scrollOffset, compact: true, columns: size.columns, rowBudget: pageActionBudget(errors.length ? 5 : 3) })] });
6145
+ }
6025
6146
  else if (route.editor === 'boolean') {
6026
6147
  title = `${activeField.title}?`;
6027
6148
  description = activeField.description ?? '选择开启或关闭。';
6028
6149
  help = '↑↓ 移动 · Enter 使用 · Esc 取消';
6029
6150
  const current = activeCandidateMutation?.kind === 'set'
6030
6151
  ? activeCandidateMutation.value
6031
- : draftUpdates[activeField.key] ?? activeField.value;
6152
+ : (arrayItem ? itemValue : draftUpdates[activeField.key]) ?? activeField.value;
6032
6153
  actions = [{ label: '开启', selected: !activeField.sensitive && current === true, run: () => updateDraft(activeField.key, true) }, { label: '关闭', selected: !activeField.sensitive && current === false, run: () => updateDraft(activeField.key, false) }];
6033
6154
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
6034
6155
  }
6035
6156
  else if (route.editor === 'object' && activeField.definition.type === 'object' && activeObjectDraft) {
6036
6157
  const objectDefinition = activeField.definition;
6037
6158
  title = `编辑${activeField.title}`;
6038
- description = activeField.configured
6159
+ description = arrayItem ? '确认后更新当前列表项;列表尚未保存。' : activeField.configured
6039
6160
  ? '保存时会提交这个顶层字段的完整对象。'
6040
6161
  : '草稿来自当前完整有效对象;保存后会在此层写入完整对象并覆盖继承对象。';
6041
6162
  if (objectError)
@@ -6077,11 +6198,12 @@ export function ConsoleAppV4(props) {
6077
6198
  fieldKey: activeField.key,
6078
6199
  dynamic: true,
6079
6200
  stage: 'key',
6201
+ ...(arrayItem ? { arrayItem } : {}),
6080
6202
  }),
6081
6203
  }] : []),
6082
6204
  {
6083
- label: '使用此完整对象',
6084
- description: `${Object.keys(activeObjectDraft).length} 个属性将作为单个顶层值保存`,
6205
+ label: arrayItem ? '使用这一项 [c]' : '使用此完整对象',
6206
+ description: arrayItem ? '确认完整对象后返回列表' : `${Object.keys(activeObjectDraft).length} 个属性将作为单个顶层值保存`,
6085
6207
  run: commitObjectDraft,
6086
6208
  },
6087
6209
  ];
@@ -6094,6 +6216,13 @@ export function ConsoleAppV4(props) {
6094
6216
  : 0;
6095
6217
  const objectViewport = actionViewport(propertyActions, policy.mode === 'compact', propertyFocus, scrollOffset, pageActionBudget(3 + objectOperationActions.length));
6096
6218
  content = _jsxs(Box, { flexDirection: "column", children: [propertyActions.length > 0 ? _jsx(SectionCard, { title: "\u5BF9\u8C61\u5C5E\u6027", children: _jsx(SemanticRows, { actions: propertyActions, focus: effectiveObjectFocus, kind: "section", compact: policy.mode === 'compact', offset: objectViewport.offset, limit: objectViewport.limit }) }) : null, _jsx(SemanticRows, { actions: actions, focus: effectiveObjectFocus, kind: "action", compact: policy.mode === 'compact', offset: propertyActions.length, limit: objectOperationActions.length })] });
6219
+ if (arrayItem) {
6220
+ for (const action of propertyActions) {
6221
+ action.configKey = action.label;
6222
+ action.configTile = { value: action.meta ?? '未设置', source: '' };
6223
+ }
6224
+ content = _jsx(SequenceRows, { actions: actions, itemCount: propertyActions.length, focus: focus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: pageActionBudget(2), compact: true });
6225
+ }
6097
6226
  }
6098
6227
  else {
6099
6228
  title = activeField.sensitive ? '输入敏感值' : `输入${activeField.title}`;
@@ -6839,7 +6968,7 @@ export function ConsoleAppV4(props) {
6839
6968
  if (!draft)
6840
6969
  return;
6841
6970
  setFocus(0);
6842
- go({ name: 'sourceSave', owner: draft.owner });
6971
+ beginSourceReview(draft, false);
6843
6972
  } },
6844
6973
  ];
6845
6974
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u5F53\u524D\u8BBE\u7F6E", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: "\u6765\u6E90\u7C7B\u578B", meta: (draft?.type ?? detail?.type) === 'git' ? 'Git 仓库' : '本地目录', compact: true }) }), _jsx(SemanticRows, { actions: actions, focus: sourceTransportOverlay ? -1 : focus, kind: "section", compact: policy.mode === 'compact' })] });
@@ -7311,7 +7440,7 @@ export function ConsoleAppV4(props) {
7311
7440
  const projectPreApplyConfirmation = review.changes.some((change) => change.kind === 'warning' && change.title.endsWith('当前项目同步目标将运行 preApply'));
7312
7441
  breadcrumb = `${review.subject.name} / ${review.title}`;
7313
7442
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
7314
- ? review.title : `确认${review.title}`}${review.operationKind === 'catalogStorePrune' ? '' : ` ${review.subject.name}`}`;
7443
+ ? review.title : review.title.startsWith('确认') ? review.title : `确认${review.title}`}${review.operationKind === 'catalogStorePrune' ? '' : ` ${review.subject.name}`}`;
7315
7444
  description = snapshot.result?.state === 'stale'
7316
7445
  ? '当前状态已变化。请检查新的变化后再次确认。'
7317
7446
  : review.description;
@@ -7323,8 +7452,11 @@ export function ConsoleAppV4(props) {
7323
7452
  context = `${review.batch?.requestedPluginIds.length ?? 0} 个根插件 · ${review.batch?.groupCount ?? 0} 组`;
7324
7453
  help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7325
7454
  }
7455
+ const configApplyBlocked = pendingReviewIntent?.kind === 'configSaveBatch' && pendingReviewIntent.apply
7456
+ && (review.configImpact?.state === 'unknown' || review.configImpact?.affectedTargetCount === 0);
7326
7457
  actions = [
7327
- { label: review.primaryLabel, description: requiresIndependentConfirmation
7458
+ { label: review.primaryLabel, disabled: configApplyBlocked ? review.configImpact?.state === 'unknown'
7459
+ ? '应用影响未确认;请选择只保存或重新检查' : '没有应用目标;请选择只保存' : undefined, description: requiresIndependentConfirmation
7328
7460
  ? '打开独立风险确认;执行时再次检查当前状态'
7329
7461
  : '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
7330
7462
  if (requiresIndependentConfirmation) {
@@ -7347,6 +7479,28 @@ export function ConsoleAppV4(props) {
7347
7479
  operationBusy.current = false;
7348
7480
  });
7349
7481
  } },
7482
+ ...(sourceDraft && (pendingReviewIntent?.kind === 'sourceMutation' || pendingReviewIntent?.kind === 'catalogGeneration')
7483
+ && pendingReviewIntent.sourceId === sourceDraft.sourceId ? [{
7484
+ label: pendingReviewIntent.kind === 'catalogGeneration' ? '改为只保存' : '改为保存并启用',
7485
+ run: () => {
7486
+ const activate = pendingReviewIntent.kind !== 'catalogGeneration';
7487
+ back();
7488
+ beginSourceReview(sourceDraft, activate);
7489
+ },
7490
+ }] : []),
7491
+ ...(pendingReviewIntent?.kind === 'configSaveBatch' && (pendingReviewIntent.apply
7492
+ || review.configImpact?.state === 'known' && review.configImpact.affectedTargetCount > 0) ? [{
7493
+ label: pendingReviewIntent.apply ? '改为只保存,稍后应用' : '改为保存并生效',
7494
+ description: '重新检查影响;此选择不写入配置',
7495
+ run: () => {
7496
+ const intent = { ...pendingReviewIntent, apply: !pendingReviewIntent.apply };
7497
+ back();
7498
+ beginReview(intent);
7499
+ },
7500
+ }] : []),
7501
+ ...(pendingReviewIntent?.kind === 'configSaveBatch' && review.configImpact?.state === 'unknown' ? [{
7502
+ label: '重新检查应用影响', run: () => { const intent = pendingReviewIntent; back(); beginReview(intent); },
7503
+ }] : []),
7350
7504
  ...(!showTechnical && catalogReferenceDetailOffset >= 0 ? [{
7351
7505
  label: '查看完整引用影响',
7352
7506
  description: '逐项查看安装、设置、生命周期和 Marketplace 引用',
@@ -7615,6 +7769,25 @@ export function ConsoleAppV4(props) {
7615
7769
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
7616
7770
  }
7617
7771
  content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: reviewRootSummary ? `本次插件 · ${reviewRootSummary}` : review.subject.name, meta: reviewRootSummary ? `版本 ${reviewVersionSummary} · 范围 ${reviewRangeSummary || '未知'}` : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && batchTargetOffset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${batchTargetOffset + index}:${detail}`)) }) : null] }) });
7772
+ if (pendingReviewIntent?.kind === 'configSaveBatch' || sourceDraft && (pendingReviewIntent?.kind === 'sourceMutation' || pendingReviewIntent?.kind === 'catalogGeneration') && pendingReviewIntent.sourceId === sourceDraft.sourceId) {
7773
+ const changesText = pendingReviewIntent.kind === 'configSaveBatch' ? pendingReviewIntent.groups.map((group) => `${configLayerLabel({ scope: group.scope })}\n${(group.changes ?? []).flatMap((change) => [
7774
+ `${change.title} · ${change.before ?? '未设置'} → ${change.after}${change.children?.length ? ` · ${change.children.length} 个子属性变化` : ''}`,
7775
+ ...(change.children ?? []).map((child) => `${child.property} · ${child.before} → ${child.after}`),
7776
+ ]).join('\n')}`).join('\n') : changeDetailText(sourceDraftChanges(sourceDraft));
7777
+ const impact = review.changes.filter((change) => change.group !== 'configChanges')
7778
+ .map((change) => `${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`);
7779
+ readingText = pendingReviewIntent.kind === 'configSaveBatch'
7780
+ ? [changesText, ...new Set([...impact, ...(review.technicalDetails ?? []), ...(review.confirmationDetails ?? []),
7781
+ ...(review.sharedImpact.warning ? [review.sharedImpact.warning] : [])])].join('\n')
7782
+ : `${changesText}\n\n${readingText}`;
7783
+ if (pendingReviewIntent.kind === 'configSaveBatch')
7784
+ description = pendingReviewIntent.apply ? '保存并生效;每层独立提交,应用结果单独报告。' : '只保存;每层独立提交,稍后应用。';
7785
+ const lines = wrapConsoleText(readingText, size.columns - 2);
7786
+ const pageRows = Math.max(1, pageActionBudget(actions.length + 1));
7787
+ referenceDetailPagination = { total: lines.length, pageRows };
7788
+ const offset = Math.min(batchTargetOffset, Math.max(0, lines.length - 1));
7789
+ content = _jsxs(Box, { flexDirection: "column", children: [lines.slice(offset, offset + pageRows).map((line, index) => _jsx(Text, { children: line }, index)), _jsxs(Text, { color: tuiTheme.roles.secondary, children: [offset + 1, "\u2013", Math.min(lines.length, offset + pageRows), " / ", lines.length] }), _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true })] });
7790
+ }
7618
7791
  }
7619
7792
  else if (route.name === 'progress') {
7620
7793
  const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
@@ -8798,7 +8971,7 @@ export function ConsoleAppV4(props) {
8798
8971
  else if (actions[focus]?.role === 'navigation')
8799
8972
  setFocus(moveFocus(actions, focus, 1));
8800
8973
  }, [routeKey, focus, actions.length, actions.map((action) => action.role ?? '').join(',')]);
8801
- const listCandidates = route.name === 'pluginList'
8974
+ const listCandidates = route.name === 'pluginList' || route.name === 'projectStatus'
8802
8975
  || route.name === 'batchSelection' && batchDraft?.stage === 'select'
8803
8976
  ? actions.filter((action) => action.pluginId !== undefined) : [];
8804
8977
  const listCandidateSignature = listCandidates.map((action) => action.pluginId).join('\0');
@@ -8813,7 +8986,7 @@ export function ConsoleAppV4(props) {
8813
8986
  }, [snapshot.router.currentFrameId, route.name, batchDraft?.stage,
8814
8987
  batchDraft?.showSelectionTools, listCandidateSignature, focus, scrollOffset]);
8815
8988
  useEffect(() => {
8816
- if (route.name !== 'pluginList' && !(route.name === 'batchSelection' && batchDraft?.stage === 'select')) {
8989
+ if (route.name !== 'pluginList' && route.name !== 'projectStatus' && !(route.name === 'batchSelection' && batchDraft?.stage === 'select')) {
8817
8990
  listFocusAnchor.current = undefined;
8818
8991
  return;
8819
8992
  }
@@ -8961,6 +9134,7 @@ export function ConsoleAppV4(props) {
8961
9134
  setConfigDrafts({});
8962
9135
  setConfigCandidate(undefined);
8963
9136
  setObjectDrafts({});
9137
+ setArrayDrafts({});
8964
9138
  setChangedObjectDraftKeys([]);
8965
9139
  setObjectError(undefined);
8966
9140
  setSourceDraft(undefined);
@@ -9057,6 +9231,21 @@ export function ConsoleAppV4(props) {
9057
9231
  }
9058
9232
  return;
9059
9233
  }
9234
+ if (arrayToolsOpen && activeArrayDraft) {
9235
+ if (key.escape)
9236
+ setArrayToolsOpen(false);
9237
+ else if (key.upArrow)
9238
+ setArrayToolFocus((value) => Math.max(0, value - 1));
9239
+ else if (key.downArrow)
9240
+ setArrayToolFocus((value) => Math.min(2, value + 1));
9241
+ else if (key.return && activeArrayDraft.selectedRowId) {
9242
+ editArray(arrayToolFocus === 2
9243
+ ? { kind: 'remove', rowId: activeArrayDraft.selectedRowId }
9244
+ : { kind: 'move', rowId: activeArrayDraft.selectedRowId, offset: arrayToolFocus === 0 ? -1 : 1 });
9245
+ setArrayToolsOpen(false);
9246
+ }
9247
+ return;
9248
+ }
9060
9249
  if (portableEmptySelectionOpen) {
9061
9250
  const closeEmptySelection = () => setPortableEmptySelectionOpen(false);
9062
9251
  if (key.escape || key.return || input.toLowerCase() === 'b') {
@@ -9252,6 +9441,7 @@ export function ConsoleAppV4(props) {
9252
9441
  setConfigDrafts({});
9253
9442
  setConfigCandidate(undefined);
9254
9443
  setObjectDrafts({});
9444
+ setArrayDrafts({});
9255
9445
  setChangedObjectDraftKeys([]);
9256
9446
  const discardedSourceDraft = sourceDraft;
9257
9447
  setSourceDraft(undefined);
@@ -9335,14 +9525,7 @@ export function ConsoleAppV4(props) {
9335
9525
  return;
9336
9526
  }
9337
9527
  if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9338
- && route.name === 'portableDependencies' && plugin) {
9339
- const total = dependencyOverview(plugin)?.dependencies.length ?? 0;
9340
- const pageRows = showTechnical ? 1 : responsiveBudget(2, 3);
9341
- setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp || key.leftArrow ? -pageRows : pageRows))));
9342
- return;
9343
- }
9344
- if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
9345
- && (route.name === 'pluginContent'
9528
+ && (route.name === 'portableDependencies' || route.name === 'pluginContent'
9346
9529
  || route.name === 'pluginTechnical'
9347
9530
  || route.name === 'projectStatus' && showTechnical
9348
9531
  || route.name === 'issueDetail' && showTechnical
@@ -9555,7 +9738,15 @@ export function ConsoleAppV4(props) {
9555
9738
  setSourceInputError(undefined);
9556
9739
  return;
9557
9740
  }
9558
- if (route.name === 'configFieldEditor' && activeField && key.ctrl && input.toLowerCase() === 'r') {
9741
+ if (route.name === 'configFieldEditor' && route.mode !== 'choices' && input === 'c'
9742
+ && (route.editor === 'array' || route.editor === 'object')) {
9743
+ if (route.editor === 'array')
9744
+ commitArray();
9745
+ else
9746
+ commitObjectDraft();
9747
+ return;
9748
+ }
9749
+ if (route.name === 'configFieldEditor' && !arrayItem && activeField && key.ctrl && input.toLowerCase() === 'r') {
9559
9750
  const canUnset = ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
9560
9751
  .some((field) => field.key === activeField.key && configFieldCanUnset(field)));
9561
9752
  if (canUnset)
@@ -9678,8 +9869,8 @@ export function ConsoleAppV4(props) {
9678
9869
  setInputError(undefined);
9679
9870
  return;
9680
9871
  }
9681
- if (searchFocus && (route.name === 'pluginList' || route.name === 'batchSelection')) {
9682
- const currentQuery = route.name === 'pluginList' ? query : batchDraft?.query ?? '';
9872
+ if (searchFocus && (route.name === 'pluginList' || route.name === 'projectStatus' || route.name === 'batchSelection')) {
9873
+ const currentQuery = route.name !== 'batchSelection' ? query : batchDraft?.query ?? '';
9683
9874
  const parts = [...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(currentQuery)]
9684
9875
  .map((part) => part.segment);
9685
9876
  const cursor = Math.min(searchCursor, parts.length);
@@ -9690,7 +9881,7 @@ export function ConsoleAppV4(props) {
9690
9881
  if (key.upArrow || key.downArrow) {
9691
9882
  setSearchFocus(false);
9692
9883
  const candidateCount = route.name === 'batchSelection' && batchDraft
9693
- ? batchCandidates(batchDraft).length : pluginRows.length;
9884
+ ? batchCandidates(batchDraft).length : route.name === 'projectStatus' ? actions.filter((action) => action.pluginId).length : pluginRows.length;
9694
9885
  setFocus((value) => moveIndex(candidateCount, Math.min(value, Math.max(0, candidateCount - 1)), key.upArrow ? -1 : 1));
9695
9886
  return;
9696
9887
  }
@@ -9715,7 +9906,7 @@ export function ConsoleAppV4(props) {
9715
9906
  }
9716
9907
  const nextQuery = parts.join('');
9717
9908
  setSearchCursor(nextCursor);
9718
- if (route.name === 'pluginList') {
9909
+ if (route.name !== 'batchSelection') {
9719
9910
  setQuery(nextQuery);
9720
9911
  props.controller.updatePageContext({ query: nextQuery });
9721
9912
  }
@@ -9731,6 +9922,11 @@ export function ConsoleAppV4(props) {
9731
9922
  return;
9732
9923
  if (key.tab)
9733
9924
  return;
9925
+ if (route.name === 'projectStatus' && input === '/' && !showTechnical) {
9926
+ setSearchCursor([...query].length);
9927
+ setSearchFocus(true);
9928
+ return;
9929
+ }
9734
9930
  if (route.name === 'pluginList' && input === '/') {
9735
9931
  runAction(listQueryActions.find((action) => action.actionId === 'plugin-list-search'));
9736
9932
  return;
@@ -9851,6 +10047,10 @@ export function ConsoleAppV4(props) {
9851
10047
  else if (globalAction === 'back'
9852
10048
  && (route.name === 'configFieldEditor' || route.name === 'configFieldAction')) {
9853
10049
  setConfigCandidate(undefined);
10050
+ if (activeArrayKey && !arrayItem) {
10051
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== activeArrayKey)));
10052
+ setObjectError(undefined);
10053
+ }
9854
10054
  if (activeObjectKey) {
9855
10055
  setObjectDrafts((current) => Object.fromEntries(Object.entries(current)
9856
10056
  .filter(([key]) => key !== activeObjectKey)));
@@ -9902,8 +10102,9 @@ export function ConsoleAppV4(props) {
9902
10102
  }
9903
10103
  else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
9904
10104
  && input.toLowerCase() === 'f') {
9905
- const nextFilter = PLUGIN_FILTERS[(PLUGIN_FILTERS.indexOf(batchDraft.installFilter) + 1)
9906
- % PLUGIN_FILTERS.length];
10105
+ const filters = PLUGIN_FILTERS.filter((filter) => filter === 'all' || filter === batchDraft.installFilter
10106
+ || batchCandidates({ ...batchDraft, installFilter: filter }).length > 0);
10107
+ const nextFilter = filters[(filters.indexOf(batchDraft.installFilter) + 1) % filters.length];
9907
10108
  setBatchDraft((current) => current ? {
9908
10109
  ...current,
9909
10110
  installFilter: nextFilter,
@@ -9912,6 +10113,14 @@ export function ConsoleAppV4(props) {
9912
10113
  setFocus(0);
9913
10114
  setScrollOffset(0);
9914
10115
  }
10116
+ else if (route.name === 'projectStatus' && input.toLowerCase() === 'f' && !showTechnical) {
10117
+ props.controller.updatePageContext({ projectSection: snapshot.router.current.context.projectSection === 'extraInstalled' ? 'declared' : 'extraInstalled' });
10118
+ setFocus(0);
10119
+ setScrollOffset(0);
10120
+ }
10121
+ else if (route.name === 'projectStatus' && input.toLowerCase() === 'r') {
10122
+ runAction(bottomActions.find((action) => action.actionId === 'project-recheck'));
10123
+ }
9915
10124
  else if (route.name === 'projectStatus' && input.toLowerCase() === 'c') {
9916
10125
  runAction(bottomActions.find((action) => action.actionId === 'project-sync-actionable'));
9917
10126
  }
@@ -10107,6 +10316,13 @@ export function ConsoleAppV4(props) {
10107
10316
  const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
10108
10317
  return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 7, marginLeft: Math.max(2, Math.floor((size.columns - modalWidth) / 2)), width: modalWidth, flexDirection: "column", backgroundColor: tuiTheme.colors.panelBackground, children: _jsx(ReviewCard, { title: "\u8FD8\u6CA1\u6709\u9009\u62E9 Skill", description: "\u7528 Space \u52FE\u9009\uFF1B\u4E5F\u53EF\u4EE5\u6253\u5F00\u9009\u62E9\u5DE5\u5177\u3002", children: _jsx(SemanticRows, { actions: [{ label: '返回选择' }], focus: 0, kind: "action", compact: true }) }) })] });
10109
10318
  }
10319
+ if (arrayToolsOpen && activeArrayDraft && activeField?.definition.type === 'array') {
10320
+ const index = activeArrayDraft.rows.findIndex((row) => row.rowId === activeArrayDraft.selectedRowId);
10321
+ const row = activeArrayDraft.rows[index];
10322
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 3, marginLeft: 3, width: size.columns - 6, flexDirection: "column", children: _jsxs(ReviewCard, { title: `调整第 ${index + 1} 项`, description: row ? presentConfigValue({
10323
+ definition: activeField.definition.items, value: row.value, summary: activeField.itemSummary,
10324
+ }).text : '', children: [_jsx(SemanticRows, { actions: [{ label: '上移' }, { label: '下移' }, { label: '删除这一项', status: 'error' }], focus: arrayToolFocus, kind: "action", compact: true }), _jsx(Text, { children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }) })] });
10325
+ }
10110
10326
  if (projectReminderOpen && snapshot.projectIntentReminder) {
10111
10327
  const reminder = snapshot.projectIntentReminder;
10112
10328
  const pluginCount = new Set([