@lesliechan721/aw 0.1.0-beta.13 → 0.1.0-beta.15
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.
- package/dist/src/commands/catalog-dev-source.d.ts +31 -0
- package/dist/src/commands/catalog-dev-source.js +165 -5
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-generation.d.ts +5 -0
- package/dist/src/commands/catalog-generation.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +12 -0
- package/dist/src/commands/catalog-publication-journal.js +77 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -1
- package/dist/src/commands/catalog-store.d.ts +5 -0
- package/dist/src/commands/catalog-store.js +19 -1
- package/dist/src/commands/catalog-store.js.map +1 -1
- package/dist/src/commands/dependency-operation-plan.d.ts +7 -0
- package/dist/src/commands/dependency-operation-plan.js +63 -27
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +22 -8
- package/dist/src/commands/rebuild-command.js +562 -250
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/reconcile-operation-plan.d.ts +11 -9
- package/dist/src/commands/reconcile-operation-plan.js +25 -6
- package/dist/src/commands/reconcile-operation-plan.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +2 -1
- package/dist/src/commands/status-v6.js +7 -1
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +2 -1
- package/dist/src/commands/write-commands.js +24 -1
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/tui/catalog-management-screens.d.ts +45 -0
- package/dist/src/tui/catalog-management-screens.js +25 -0
- package/dist/src/tui/catalog-management-screens.js.map +1 -0
- package/dist/src/tui/catalog-management.d.ts +240 -0
- package/dist/src/tui/catalog-management.js +856 -0
- package/dist/src/tui/catalog-management.js.map +1 -0
- package/dist/src/tui/catalog-sources/session.d.ts +5 -0
- package/dist/src/tui/catalog-sources/session.js +49 -1
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-sources/types.d.ts +11 -0
- package/dist/src/tui/console-app-v4.d.ts +1 -1
- package/dist/src/tui/console-app-v4.js +2597 -587
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +668 -50
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +136 -7
- package/dist/src/tui/controller.js +752 -19
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph.d.ts +67 -0
- package/dist/src/tui/dependency-graph.js +399 -0
- package/dist/src/tui/dependency-graph.js.map +1 -0
- package/dist/src/tui/lifecycle-action-view.js +4 -2
- package/dist/src/tui/lifecycle-action-view.js.map +1 -1
- package/dist/src/tui/model.d.ts +37 -0
- package/dist/src/tui/model.js +163 -7
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +266 -9
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-write-task.d.ts +67 -0
- package/dist/src/tui/plugin-write-task.js +282 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -0
- package/dist/src/tui/router.d.ts +39 -2
- package/dist/src/tui/router.js +13 -5
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +125 -6
- package/dist/src/tui/screen-registry.js +35 -4
- package/dist/src/tui/screen-registry.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +4 -0
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/package.json +3 -3
|
@@ -13,9 +13,12 @@ import { projectCatalogSourceLocation } from '../utils/catalog-source-presentati
|
|
|
13
13
|
import { ActionRow, Callout, ChoiceRow, diagnosticDetailRowCount, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
|
|
14
14
|
import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
|
|
15
15
|
import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
|
|
16
|
+
import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
|
|
16
17
|
import { catalogSourceFreshnessLabel, catalogSourceListPresentation, } from './catalog-sources/screens.js';
|
|
18
|
+
import { CatalogManagementIntro, CatalogPublicationSummary, CatalogStoreSummary, CatalogWorkspaceSummary, } from './catalog-management-screens.js';
|
|
17
19
|
import { operationDiagnosticLineCount, operationHistoryMeta, operationHistoryState, operationReportLineCount, OperationDiagnosticScreen, OperationReportPreviewScreen, } from './operation-diagnostic-screens.js';
|
|
18
20
|
import { resolveTuiSizePolicy } from './layout.js';
|
|
21
|
+
import { buildDependencyGraphProjection, } from './dependency-graph.js';
|
|
19
22
|
import { platformLabel, scopeLabel } from './glossary.js';
|
|
20
23
|
import { InputDisplay, removeLastGrapheme } from './input-display.js';
|
|
21
24
|
import { catalogRefPolicyForPreset } from './source-ref-presets.js';
|
|
@@ -30,6 +33,7 @@ function sourceOperationBlockLabel(reason) {
|
|
|
30
33
|
case 'configDraft': return '未保存的插件设置';
|
|
31
34
|
case 'sourceDraft': return '未保存的来源修改';
|
|
32
35
|
case 'batchDraft': return '未完成的批量操作选择';
|
|
36
|
+
case 'catalogDraft': return '未完成的 Catalog 管理输入';
|
|
33
37
|
case 'portableDraft': return '未完成的通用 Skills 选择';
|
|
34
38
|
case 'activeReview': return '等待确认的操作';
|
|
35
39
|
case 'reviewPreparation': return '正在准备确认信息的操作';
|
|
@@ -75,6 +79,11 @@ function isCharacterInputMode(route, searchMode) {
|
|
|
75
79
|
|| route.name === 'configObjectPropertyEditor'
|
|
76
80
|
&& (route.stage === 'key' || route.stage === 'custom')
|
|
77
81
|
|| route.name === 'sourceAdd' && route.step !== 'type' && route.step !== 'refPolicy'
|
|
82
|
+
|| route.name === 'catalogWorkspace'
|
|
83
|
+
&& (route.view === 'select' || route.view === 'create'
|
|
84
|
+
|| route.view === 'version' && route.step === 'catalogVersion')
|
|
85
|
+
|| route.name === 'catalogDev' && route.step !== undefined && route.step !== 'overview'
|
|
86
|
+
|| route.name === 'catalogPublish' && route.step !== 'overview'
|
|
78
87
|
|| route.name === 'scriptTimeout';
|
|
79
88
|
}
|
|
80
89
|
function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, sessionNavigation) {
|
|
@@ -104,7 +113,7 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
|
|
|
104
113
|
rows.push(['s', '查看保存汇总']);
|
|
105
114
|
rows.push(['u', '恢复当前字段继承']);
|
|
106
115
|
}
|
|
107
|
-
if (route.name === 'sourceList' || route.name === 'sourceDetail') {
|
|
116
|
+
if (route.name === 'sourceList' || route.name === 'sourceDetail' || route.name === 'sourceMore') {
|
|
108
117
|
rows.push(['r', '重新检查来源']);
|
|
109
118
|
}
|
|
110
119
|
if (supportsTechnicalDetails) {
|
|
@@ -145,9 +154,52 @@ function batchActionLabel(action) {
|
|
|
145
154
|
cleanup: '清理',
|
|
146
155
|
}[action];
|
|
147
156
|
}
|
|
157
|
+
function pluginWriteTaskLabel(goal) {
|
|
158
|
+
return goal === 'update' ? '更新' : '恢复';
|
|
159
|
+
}
|
|
160
|
+
function pluginWriteTaskBlockLabel(reason) {
|
|
161
|
+
return {
|
|
162
|
+
catalogActivationRequired: '需要先更新并启用来源目录',
|
|
163
|
+
updateEvidenceIncomplete: '更新版本证据不完整',
|
|
164
|
+
updateIncompatible: '候选版本与当前 AW 不兼容',
|
|
165
|
+
restoreVersionChanged: '恢复会改变版本,需要单独决定',
|
|
166
|
+
restoreVersionUnverified: '当前安装版本无法验证',
|
|
167
|
+
manualRecoveryRequired: '需要选择手动处理方式',
|
|
168
|
+
taskActionConflict: '同一根因需要不同处理方式',
|
|
169
|
+
targetUnavailable: '这个安装位置已不可用',
|
|
170
|
+
}[reason ?? 'targetUnavailable'];
|
|
171
|
+
}
|
|
172
|
+
function pluginWriteTaskMethodLabel(method) {
|
|
173
|
+
return method === 'reinstall' ? '重新生成缺失内容' : '同步当前使用位置';
|
|
174
|
+
}
|
|
175
|
+
function pluginWriteTaskTargetKey(candidate) {
|
|
176
|
+
const { pluginId, platform, scope } = candidate.target;
|
|
177
|
+
return [pluginId, platform, scope].join('\0');
|
|
178
|
+
}
|
|
179
|
+
function dependencyGraphContextKey(pluginId, direction) {
|
|
180
|
+
return `${pluginId}\0${direction}`;
|
|
181
|
+
}
|
|
182
|
+
function dependencyGraphActionIds(projection) {
|
|
183
|
+
return [
|
|
184
|
+
...projection.rows.map((row) => `row:${row.id}`),
|
|
185
|
+
...projection.scopeSummary.unknownConsumerPluginIds.map((pluginId) => `unverified:${pluginId}`),
|
|
186
|
+
'direction',
|
|
187
|
+
'technical',
|
|
188
|
+
];
|
|
189
|
+
}
|
|
148
190
|
function statusText(plugin) {
|
|
149
191
|
if (plugin.configOnly)
|
|
150
192
|
return '已保存设置';
|
|
193
|
+
const updateTargets = (plugin.update?.targets ?? []).filter((target) => target.availableVersion !== undefined);
|
|
194
|
+
if (updateTargets.length === 1) {
|
|
195
|
+
const target = updateTargets[0];
|
|
196
|
+
return `可更新 ${target.installedVersion ?? '待确认'} → ${target.availableVersion}${target.requiresCatalogActivation ? '(需先更新来源)' : ''}`;
|
|
197
|
+
}
|
|
198
|
+
if (updateTargets.length > 1)
|
|
199
|
+
return `${updateTargets.length} 个位置可更新`;
|
|
200
|
+
const incompatibleCandidate = plugin.update?.targets.find((target) => target.candidateVersion !== undefined && (target.missingCapabilities?.length ?? 0) > 0);
|
|
201
|
+
if (incompatibleCandidate)
|
|
202
|
+
return `新版 ${incompatibleCandidate.candidateVersion} 暂不可用`;
|
|
151
203
|
return {
|
|
152
204
|
available: '未添加',
|
|
153
205
|
installed: '可用',
|
|
@@ -157,6 +209,21 @@ function statusText(plugin) {
|
|
|
157
209
|
: '来源不可用',
|
|
158
210
|
}[plugin.aggregateStatus];
|
|
159
211
|
}
|
|
212
|
+
function pluginUpdateDescription(plugin) {
|
|
213
|
+
const targets = (plugin.update?.targets ?? []).filter((target) => target.availableVersion !== undefined);
|
|
214
|
+
if (targets.length === 0) {
|
|
215
|
+
const incompatibleCandidate = plugin.update?.targets.find((target) => target.candidateVersion !== undefined && (target.missingCapabilities?.length ?? 0) > 0);
|
|
216
|
+
return incompatibleCandidate
|
|
217
|
+
? `候选版本缺少 ${incompatibleCandidate.missingCapabilities.length} 项 AW 功能支持;当前启用版本可继续使用。`
|
|
218
|
+
: undefined;
|
|
219
|
+
}
|
|
220
|
+
if (targets.some((target) => target.requiresCatalogActivation)) {
|
|
221
|
+
return plugin.update?.lastSuccessful
|
|
222
|
+
? `上次成功检查:${plugin.update.checkedAt ? new Date(plugin.update.checkedAt).toLocaleString('zh-CN', { hour12: false }) : '时间未知'};需先更新来源目录。`
|
|
223
|
+
: '已验证待检查版本;需先更新来源目录。';
|
|
224
|
+
}
|
|
225
|
+
return '当前启用目录已验证;可直接进入更新确认。';
|
|
226
|
+
}
|
|
160
227
|
function pluginPlacementSummary(plugin) {
|
|
161
228
|
if (plugin.configOnly) {
|
|
162
229
|
return plugin.savedConfigScopes.length > 0
|
|
@@ -728,6 +795,17 @@ function dependencyRelationDescription(item) {
|
|
|
728
795
|
function dependencySummaryText(summary) {
|
|
729
796
|
return `${summary.directCount} 个直接 · ${summary.totalCount} 个总计`;
|
|
730
797
|
}
|
|
798
|
+
function dependencyGraphRowStatus(row) {
|
|
799
|
+
if (row.nodeState === 'ready')
|
|
800
|
+
return { value: '正常', status: 'success' };
|
|
801
|
+
if (row.nodeState === 'missing')
|
|
802
|
+
return { value: '缺失', status: 'error' };
|
|
803
|
+
if (row.nodeState === 'needsAttention')
|
|
804
|
+
return { value: '待恢复', status: 'warning' };
|
|
805
|
+
if (row.nodeState === 'unavailable')
|
|
806
|
+
return { value: '不可用', status: 'error' };
|
|
807
|
+
return { value: '未检查', status: 'muted' };
|
|
808
|
+
}
|
|
731
809
|
function isCatalogSourcesBootstrapFlow(snapshot) {
|
|
732
810
|
const route = snapshot.router.current.route;
|
|
733
811
|
if (route.name === 'reviewPreparing') {
|
|
@@ -790,7 +868,7 @@ export function ConsoleAppV4(props) {
|
|
|
790
868
|
const [focus, setFocus] = useState(0);
|
|
791
869
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
792
870
|
const [searchFocus, setSearchFocus] = useState(false);
|
|
793
|
-
const [query, setQuery] = useState('');
|
|
871
|
+
const [query, setQuery] = useState(props.controller.snapshot.router.current.context.query ?? '');
|
|
794
872
|
const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
|
|
795
873
|
const [scope, setScope] = useState('user');
|
|
796
874
|
const [selectedTargets, setSelectedTargets] = useState([]);
|
|
@@ -803,6 +881,9 @@ export function ConsoleAppV4(props) {
|
|
|
803
881
|
const [dirty, setDirty] = useState(false);
|
|
804
882
|
const [configPage, setConfigPage] = useState();
|
|
805
883
|
const [configLayerCounts, setConfigLayerCounts] = useState({});
|
|
884
|
+
const [configLayerPages, setConfigLayerPages] = useState({});
|
|
885
|
+
const [configSaveImpact, setConfigSaveImpact] = useState();
|
|
886
|
+
const [configSaveImpactError, setConfigSaveImpactError] = useState(false);
|
|
806
887
|
const [draftUpdates, setDraftUpdates] = useState({});
|
|
807
888
|
const [draftRemoves, setDraftRemoves] = useState([]);
|
|
808
889
|
const [objectDrafts, setObjectDrafts] = useState({});
|
|
@@ -816,8 +897,24 @@ export function ConsoleAppV4(props) {
|
|
|
816
897
|
const [sourceDraft, setSourceDraft] = useState();
|
|
817
898
|
const [batchDraft, setBatchDraft] = useState();
|
|
818
899
|
const [batchTargetOffset, setBatchTargetOffset] = useState(0);
|
|
900
|
+
const [expandedDependencyPluginIds, setExpandedDependencyPluginIds] = useState();
|
|
819
901
|
const [sourceInput, setSourceInput] = useState('');
|
|
820
902
|
const [sourceInputError, setSourceInputError] = useState();
|
|
903
|
+
const [catalogInput, setCatalogInput] = useState('');
|
|
904
|
+
const [catalogInputError, setCatalogInputError] = useState();
|
|
905
|
+
const [catalogWorkspace, setCatalogWorkspace] = useState();
|
|
906
|
+
const [catalogValidation, setCatalogValidation] = useState();
|
|
907
|
+
const [catalogDev, setCatalogDev] = useState();
|
|
908
|
+
const [catalogStore, setCatalogStore] = useState();
|
|
909
|
+
const [catalogPublication, setCatalogPublication] = useState();
|
|
910
|
+
const [catalogLoading, setCatalogLoading] = useState(false);
|
|
911
|
+
const [catalogWorkspaceDraft, setCatalogWorkspaceDraft] = useState({
|
|
912
|
+
ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0',
|
|
913
|
+
});
|
|
914
|
+
const [catalogPublishDraft, setCatalogPublishDraft] = useState({
|
|
915
|
+
targetKind: 'remote', target: '', branch: 'catalog',
|
|
916
|
+
});
|
|
917
|
+
const [catalogDevSourceId, setCatalogDevSourceId] = useState('');
|
|
821
918
|
const [pendingReviewIntent, setPendingReviewIntent] = useState();
|
|
822
919
|
const pendingReviewIntentRef = useRef(undefined);
|
|
823
920
|
const [scriptTimeoutInput, setScriptTimeoutInput] = useState(String(SCRIPT_TIMEOUT_DEFAULT_MS));
|
|
@@ -834,9 +931,11 @@ export function ConsoleAppV4(props) {
|
|
|
834
931
|
const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
|
|
835
932
|
const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
|
|
836
933
|
const dynamicMenuFocus = useRef(undefined);
|
|
837
|
-
const
|
|
934
|
+
const dependencyGraphContexts = useRef(new Map());
|
|
935
|
+
const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0, catalog: 0 });
|
|
838
936
|
const previousExternalDraftValues = useRef({
|
|
839
937
|
draftUpdates, draftRemoves, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
|
|
938
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId,
|
|
840
939
|
});
|
|
841
940
|
const externalDraftFlowIds = useRef(new Map());
|
|
842
941
|
const exitSent = useRef(false);
|
|
@@ -939,6 +1038,36 @@ export function ConsoleAppV4(props) {
|
|
|
939
1038
|
setBatchDraft(undefined);
|
|
940
1039
|
setDirty(false);
|
|
941
1040
|
}, [route.name, snapshot.result?.operationKind]);
|
|
1041
|
+
useEffect(() => {
|
|
1042
|
+
if (route.name !== 'result'
|
|
1043
|
+
|| snapshot.result?.state !== 'succeeded'
|
|
1044
|
+
|| snapshot.result.operationKind !== 'pluginWriteTask'
|
|
1045
|
+
|| !('returnRoute' in snapshot.result)
|
|
1046
|
+
|| snapshot.result.returnRoute?.name !== 'pluginWriteTask'
|
|
1047
|
+
|| snapshot.result.returnRoute.goal !== snapshot.result.summary.task?.goal
|
|
1048
|
+
|| snapshot.result.returnRoute.batchOrigin?.owner !== 'batch-write'
|
|
1049
|
+
|| snapshot.result.returnRoute.batchOrigin.flowId !== batchDraft?.flowId)
|
|
1050
|
+
return;
|
|
1051
|
+
setBatchDraft(undefined);
|
|
1052
|
+
setDirty(false);
|
|
1053
|
+
}, [batchDraft?.flowId, route.name, snapshot.result]);
|
|
1054
|
+
useEffect(() => {
|
|
1055
|
+
if (route.name !== 'result'
|
|
1056
|
+
|| snapshot.result?.state !== 'succeeded'
|
|
1057
|
+
|| snapshot.result.operationKind !== 'catalogManagement')
|
|
1058
|
+
return;
|
|
1059
|
+
const action = snapshot.result.summary.catalogManagement?.action;
|
|
1060
|
+
if (action === 'workspaceInit') {
|
|
1061
|
+
setCatalogInput('');
|
|
1062
|
+
setCatalogWorkspaceDraft({
|
|
1063
|
+
ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0',
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (action === 'publishApply') {
|
|
1067
|
+
setCatalogInput('');
|
|
1068
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: 'catalog' });
|
|
1069
|
+
}
|
|
1070
|
+
}, [route.name, snapshot.result]);
|
|
942
1071
|
useEffect(() => {
|
|
943
1072
|
if (!batchDraft || (route.name !== 'batchSelection' && route.name !== 'batchPlacement'))
|
|
944
1073
|
return;
|
|
@@ -948,6 +1077,74 @@ export function ConsoleAppV4(props) {
|
|
|
948
1077
|
if (route.name !== 'sourceAdd')
|
|
949
1078
|
setSourceInput('');
|
|
950
1079
|
}, [route.name]);
|
|
1080
|
+
useEffect(() => {
|
|
1081
|
+
if (route.name !== 'catalogWorkspace' || route.view !== 'overview')
|
|
1082
|
+
return;
|
|
1083
|
+
let current = true;
|
|
1084
|
+
setCatalogLoading(true);
|
|
1085
|
+
setCatalogInputError(undefined);
|
|
1086
|
+
void props.controller.getCatalogWorkspaceManagement()
|
|
1087
|
+
.then((value) => { if (current)
|
|
1088
|
+
setCatalogWorkspace(value); })
|
|
1089
|
+
.catch((error) => {
|
|
1090
|
+
if (current)
|
|
1091
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]));
|
|
1092
|
+
})
|
|
1093
|
+
.finally(() => { if (current)
|
|
1094
|
+
setCatalogLoading(false); });
|
|
1095
|
+
return () => { current = false; };
|
|
1096
|
+
}, [props.controller, route.name, route.name === 'catalogWorkspace' ? route.view : '']);
|
|
1097
|
+
useEffect(() => {
|
|
1098
|
+
if (route.name !== 'catalogDev' || route.step && route.step !== 'overview')
|
|
1099
|
+
return;
|
|
1100
|
+
let current = true;
|
|
1101
|
+
setCatalogLoading(true);
|
|
1102
|
+
setCatalogInputError(undefined);
|
|
1103
|
+
void props.controller.getCatalogDevManagement()
|
|
1104
|
+
.then((value) => { if (current)
|
|
1105
|
+
setCatalogDev(value); })
|
|
1106
|
+
.catch((error) => {
|
|
1107
|
+
if (current)
|
|
1108
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]));
|
|
1109
|
+
})
|
|
1110
|
+
.finally(() => { if (current)
|
|
1111
|
+
setCatalogLoading(false); });
|
|
1112
|
+
return () => { current = false; };
|
|
1113
|
+
}, [props.controller, route.name, route.name === 'catalogDev' ? route.step : '']);
|
|
1114
|
+
useEffect(() => {
|
|
1115
|
+
if (route.name !== 'catalogStore')
|
|
1116
|
+
return;
|
|
1117
|
+
let current = true;
|
|
1118
|
+
setCatalogLoading(true);
|
|
1119
|
+
setCatalogInputError(undefined);
|
|
1120
|
+
void props.controller.getCatalogStoreManagement()
|
|
1121
|
+
.then((value) => { if (current)
|
|
1122
|
+
setCatalogStore(value); })
|
|
1123
|
+
.catch((error) => {
|
|
1124
|
+
if (current)
|
|
1125
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], []));
|
|
1126
|
+
})
|
|
1127
|
+
.finally(() => { if (current)
|
|
1128
|
+
setCatalogLoading(false); });
|
|
1129
|
+
return () => { current = false; };
|
|
1130
|
+
}, [props.controller, route.name]);
|
|
1131
|
+
useEffect(() => {
|
|
1132
|
+
if (route.name !== 'catalogPublish' || route.step !== 'overview')
|
|
1133
|
+
return;
|
|
1134
|
+
let current = true;
|
|
1135
|
+
setCatalogLoading(true);
|
|
1136
|
+
setCatalogInputError(undefined);
|
|
1137
|
+
void props.controller.getCatalogPublicationManagement()
|
|
1138
|
+
.then((value) => { if (current)
|
|
1139
|
+
setCatalogPublication(value); })
|
|
1140
|
+
.catch((error) => {
|
|
1141
|
+
if (current)
|
|
1142
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogPublishDraft.target]));
|
|
1143
|
+
})
|
|
1144
|
+
.finally(() => { if (current)
|
|
1145
|
+
setCatalogLoading(false); });
|
|
1146
|
+
return () => { current = false; };
|
|
1147
|
+
}, [props.controller, route.name, route.name === 'catalogPublish' ? route.step : '']);
|
|
951
1148
|
useEffect(() => {
|
|
952
1149
|
if (route.name === 'operationReportPreview')
|
|
953
1150
|
return;
|
|
@@ -993,8 +1190,14 @@ export function ConsoleAppV4(props) {
|
|
|
993
1190
|
externalDraftRevision.current.source += 1;
|
|
994
1191
|
if (previous.batchDraft !== batchDraft)
|
|
995
1192
|
externalDraftRevision.current.batch += 1;
|
|
1193
|
+
if (previous.catalogInput !== catalogInput
|
|
1194
|
+
|| previous.catalogWorkspaceDraft !== catalogWorkspaceDraft
|
|
1195
|
+
|| previous.catalogPublishDraft !== catalogPublishDraft
|
|
1196
|
+
|| previous.catalogDevSourceId !== catalogDevSourceId)
|
|
1197
|
+
externalDraftRevision.current.catalog += 1;
|
|
996
1198
|
previousExternalDraftValues.current = {
|
|
997
1199
|
draftUpdates, draftRemoves, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
|
|
1200
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId,
|
|
998
1201
|
};
|
|
999
1202
|
const currentFlowId = snapshot.router.currentFlowId;
|
|
1000
1203
|
if (currentFlowId && configRouteOwner) {
|
|
@@ -1002,8 +1205,6 @@ export function ConsoleAppV4(props) {
|
|
|
1002
1205
|
}
|
|
1003
1206
|
if (currentFlowId && sourceDraft)
|
|
1004
1207
|
externalDraftFlowIds.current.set(sourceDraft.owner, currentFlowId);
|
|
1005
|
-
if (currentFlowId && batchDraft)
|
|
1006
|
-
externalDraftFlowIds.current.set('batch-write', currentFlowId);
|
|
1007
1208
|
const drafts = [];
|
|
1008
1209
|
if ((dirty || changedObjectDraftKeys.length > 0) && draftOwner) {
|
|
1009
1210
|
const flowId = externalDraftFlowIds.current.get(draftOwner);
|
|
@@ -1020,16 +1221,31 @@ export function ConsoleAppV4(props) {
|
|
|
1020
1221
|
});
|
|
1021
1222
|
}
|
|
1022
1223
|
if (batchDraft) {
|
|
1023
|
-
const flowId = externalDraftFlowIds.current.get('batch-write');
|
|
1024
1224
|
drafts.push({
|
|
1025
1225
|
kind: 'batchDraft', owner: 'batch-write', revision: externalDraftRevision.current.batch,
|
|
1026
|
-
|
|
1226
|
+
flowId: batchDraft.flowId,
|
|
1027
1227
|
});
|
|
1028
1228
|
}
|
|
1229
|
+
const catalogWorkspaceDirty = catalogWorkspaceDraft.ownerName !== ''
|
|
1230
|
+
|| catalogWorkspaceDraft.ownerUrl !== '' || catalogWorkspaceDraft.displayName !== '';
|
|
1231
|
+
const catalogPublishDirty = catalogPublishDraft.target !== '';
|
|
1232
|
+
const catalogDraftActive = route.name === 'catalogWorkspace'
|
|
1233
|
+
&& (route.view === 'create' || route.view === 'version' && route.step === 'catalogVersion')
|
|
1234
|
+
|| route.name === 'catalogPublish' && route.step !== 'overview'
|
|
1235
|
+
|| route.name === 'catalogDev' && route.step !== undefined && route.step !== 'overview'
|
|
1236
|
+
|| route.name === 'catalogWorkspace' && route.view === 'overview' && catalogWorkspaceDirty
|
|
1237
|
+
|| route.name === 'catalogPublish' && route.step === 'overview' && catalogPublishDirty;
|
|
1238
|
+
if (catalogDraftActive)
|
|
1239
|
+
drafts.push({
|
|
1240
|
+
kind: 'catalogDraft', owner: 'catalog-management', revision: externalDraftRevision.current.catalog,
|
|
1241
|
+
});
|
|
1029
1242
|
props.controller.setExternalDraftState(drafts);
|
|
1030
1243
|
}, [
|
|
1031
1244
|
props.controller, batchDraft, changedObjectDraftKeys, configRouteOwner, dirty, draftOwner,
|
|
1032
1245
|
draftRemoves, draftUpdates, objectDrafts, snapshot.router.currentFlowId, sourceDraft,
|
|
1246
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId, route.name,
|
|
1247
|
+
route.name === 'catalogWorkspace' ? `${route.view}:${route.step ?? ''}` : '',
|
|
1248
|
+
route.name === 'catalogPublish' || route.name === 'catalogDev' ? route.step : '',
|
|
1033
1249
|
]);
|
|
1034
1250
|
useEffect(() => () => props.controller.setExternalDraftState(), [props.controller]);
|
|
1035
1251
|
const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
|
|
@@ -1071,17 +1287,19 @@ export function ConsoleAppV4(props) {
|
|
|
1071
1287
|
if (route.name !== 'configLayer')
|
|
1072
1288
|
return;
|
|
1073
1289
|
const abort = new AbortController();
|
|
1290
|
+
setConfigLayerPages({});
|
|
1074
1291
|
const layers = [
|
|
1075
1292
|
{ key: 'user', layer: { scope: 'user' } },
|
|
1076
1293
|
{ key: 'project', layer: { scope: 'project' } },
|
|
1077
1294
|
];
|
|
1078
1295
|
void Promise.allSettled(layers.map(async ({ key, layer }) => ({
|
|
1079
1296
|
key,
|
|
1080
|
-
|
|
1297
|
+
page: await props.controller.getConfigPage(route.pluginId, layer, abort.signal),
|
|
1081
1298
|
}))).then((results) => {
|
|
1082
1299
|
if (abort.signal.aborted)
|
|
1083
1300
|
return;
|
|
1084
|
-
setConfigLayerCounts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.
|
|
1301
|
+
setConfigLayerCounts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page.storedCount]] : [])));
|
|
1302
|
+
setConfigLayerPages(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page]] : [])));
|
|
1085
1303
|
});
|
|
1086
1304
|
return () => abort.abort();
|
|
1087
1305
|
}, [props.controller, route.name, route.name === 'configLayer' ? route.pluginId : '']);
|
|
@@ -1202,7 +1420,7 @@ export function ConsoleAppV4(props) {
|
|
|
1202
1420
|
useEffect(() => {
|
|
1203
1421
|
let count;
|
|
1204
1422
|
if (route.name === 'sourceList')
|
|
1205
|
-
count = props.controller.listSources().length +
|
|
1423
|
+
count = props.controller.listSources().length + 3;
|
|
1206
1424
|
else if (route.name === 'issueList') {
|
|
1207
1425
|
const issueCount = props.controller.listUserIssues().length;
|
|
1208
1426
|
count = issueCount || 2;
|
|
@@ -1217,7 +1435,18 @@ export function ConsoleAppV4(props) {
|
|
|
1217
1435
|
...(query ? { query } : {}),
|
|
1218
1436
|
installFilter,
|
|
1219
1437
|
}).length;
|
|
1220
|
-
const
|
|
1438
|
+
const listed = new Set(props.controller.listPlugins({
|
|
1439
|
+
mode: route.mode,
|
|
1440
|
+
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1441
|
+
...(query ? { query } : {}),
|
|
1442
|
+
installFilter,
|
|
1443
|
+
}).map((item) => item.pluginId));
|
|
1444
|
+
const menuCount = itemCount > 0 && !query ? 1
|
|
1445
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1446
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1447
|
+
+ Number(route.mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1448
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1449
|
+
.some((candidate) => candidate.target.scope === 'project')) : 0;
|
|
1221
1450
|
count = itemCount + menuCount || 1;
|
|
1222
1451
|
}
|
|
1223
1452
|
if (count === undefined)
|
|
@@ -1239,6 +1468,74 @@ export function ConsoleAppV4(props) {
|
|
|
1239
1468
|
return;
|
|
1240
1469
|
setScrollOffset(0);
|
|
1241
1470
|
}, [route.name, route.name === 'operationReportPreview' ? route.recordId : '']);
|
|
1471
|
+
useEffect(() => {
|
|
1472
|
+
if (route.name !== 'pluginDependencies')
|
|
1473
|
+
return;
|
|
1474
|
+
const direction = route.direction ?? 'dependencies';
|
|
1475
|
+
const key = dependencyGraphContextKey(route.pluginId, direction);
|
|
1476
|
+
const stored = dependencyGraphContexts.current.get(key);
|
|
1477
|
+
const overviews = props.controller.listPluginDependencyOverviews();
|
|
1478
|
+
const knownPluginIds = new Set(overviews.flatMap((overview) => [
|
|
1479
|
+
overview.pluginId,
|
|
1480
|
+
...overview.dependencies.map((item) => item.pluginId),
|
|
1481
|
+
...overview.dependents.map((item) => item.pluginId),
|
|
1482
|
+
]));
|
|
1483
|
+
const expandedPluginIds = stored
|
|
1484
|
+
? new Set([...stored.expandedPluginIds].filter((pluginId) => knownPluginIds.has(pluginId)))
|
|
1485
|
+
: undefined;
|
|
1486
|
+
const projection = buildDependencyGraphProjection({
|
|
1487
|
+
overviews,
|
|
1488
|
+
rootPluginId: route.pluginId,
|
|
1489
|
+
direction,
|
|
1490
|
+
...(expandedPluginIds ? { expandedPluginIds } : {}),
|
|
1491
|
+
});
|
|
1492
|
+
const actionIds = dependencyGraphActionIds(projection);
|
|
1493
|
+
const normalizedExpanded = expandedPluginIds ?? new Set(projection.defaultExpandedPluginIds);
|
|
1494
|
+
const focusedIndex = stored?.focusedActionId
|
|
1495
|
+
? actionIds.indexOf(stored.focusedActionId)
|
|
1496
|
+
: 0;
|
|
1497
|
+
const nextFocus = focusedIndex >= 0 ? focusedIndex : 0;
|
|
1498
|
+
const anchorIndex = stored?.scrollAnchorActionId
|
|
1499
|
+
? actionIds.indexOf(stored.scrollAnchorActionId)
|
|
1500
|
+
: 0;
|
|
1501
|
+
const nextScroll = Math.max(0, anchorIndex >= 0 ? Math.min(anchorIndex, nextFocus) : 0);
|
|
1502
|
+
setExpandedDependencyPluginIds(normalizedExpanded);
|
|
1503
|
+
setFocus(nextFocus);
|
|
1504
|
+
setScrollOffset(nextScroll);
|
|
1505
|
+
dependencyGraphContexts.current.set(key, {
|
|
1506
|
+
expandedPluginIds: normalizedExpanded,
|
|
1507
|
+
...(actionIds[nextFocus] ? { focusedActionId: actionIds[nextFocus] } : {}),
|
|
1508
|
+
...(actionIds[nextScroll] ? { scrollAnchorActionId: actionIds[nextScroll] } : {}),
|
|
1509
|
+
});
|
|
1510
|
+
}, [
|
|
1511
|
+
props.controller,
|
|
1512
|
+
route.name,
|
|
1513
|
+
route.name === 'pluginDependencies' ? route.pluginId : '',
|
|
1514
|
+
route.name === 'pluginDependencies' ? route.direction ?? 'dependencies' : '',
|
|
1515
|
+
snapshot.viewModel,
|
|
1516
|
+
]);
|
|
1517
|
+
useEffect(() => {
|
|
1518
|
+
if (route.name !== 'pluginList' || snapshot.phase !== 'ready'
|
|
1519
|
+
|| snapshot.router.current.context.focusIndex !== undefined)
|
|
1520
|
+
return;
|
|
1521
|
+
const items = props.controller.listPlugins({
|
|
1522
|
+
mode: route.mode,
|
|
1523
|
+
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1524
|
+
...(query ? { query } : {}),
|
|
1525
|
+
installFilter,
|
|
1526
|
+
});
|
|
1527
|
+
if (items.length === 0)
|
|
1528
|
+
return;
|
|
1529
|
+
const listed = new Set(items.map((item) => item.pluginId));
|
|
1530
|
+
const menuCount = query ? 0 : 1
|
|
1531
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1532
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1533
|
+
+ Number(route.mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1534
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1535
|
+
.some((candidate) => candidate.target.scope === 'project'));
|
|
1536
|
+
setFocus(menuCount);
|
|
1537
|
+
}, [props.controller, route.name, route.name === 'pluginList' ? route.mode : '',
|
|
1538
|
+
route.name === 'pluginList' ? route.sourceId : '', snapshot.phase]);
|
|
1242
1539
|
useEffect(() => {
|
|
1243
1540
|
setBatchTargetOffset(0);
|
|
1244
1541
|
}, [route.name, route.name === 'review' || route.name === 'result' ? route.operationId : '']);
|
|
@@ -1383,11 +1680,9 @@ export function ConsoleAppV4(props) {
|
|
|
1383
1680
|
let activeConfigTabId;
|
|
1384
1681
|
const dynamicMenu = plugin && route.name === 'pluginPreview'
|
|
1385
1682
|
? buildPluginPreviewActions(plugin)
|
|
1386
|
-
: plugin && route.name === '
|
|
1387
|
-
?
|
|
1388
|
-
:
|
|
1389
|
-
? buildPlatformActions(plugin, route.platform)
|
|
1390
|
-
: undefined;
|
|
1683
|
+
: plugin && route.name === 'platform'
|
|
1684
|
+
? buildPlatformActions(plugin, route.platform)
|
|
1685
|
+
: undefined;
|
|
1391
1686
|
const dynamicActionIds = dynamicMenu?.map(pluginMenuActionId);
|
|
1392
1687
|
const dynamicActionSignature = dynamicActionIds?.join('\0');
|
|
1393
1688
|
const previousDynamicFocus = dynamicMenuFocus.current;
|
|
@@ -1418,6 +1713,17 @@ export function ConsoleAppV4(props) {
|
|
|
1418
1713
|
const base = policy.mode === 'compact' ? compactRows : standardRows;
|
|
1419
1714
|
return base + Math.max(0, size.rows - baseline);
|
|
1420
1715
|
};
|
|
1716
|
+
const pluginListMenuCount = (mode, items, queryValue = query) => {
|
|
1717
|
+
if (items.length === 0 || queryValue)
|
|
1718
|
+
return 0;
|
|
1719
|
+
const listed = new Set(items.map((item) => item.pluginId));
|
|
1720
|
+
return 1
|
|
1721
|
+
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1722
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1723
|
+
+ Number(mode === 'installed' && props.controller.listCurrentProjectIntentRoots().length > 0)
|
|
1724
|
+
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1725
|
+
.some((candidate) => candidate.target.scope === 'project'));
|
|
1726
|
+
};
|
|
1421
1727
|
const go = (next) => {
|
|
1422
1728
|
props.controller.updatePageContext({
|
|
1423
1729
|
focusIndex: focus,
|
|
@@ -1457,12 +1763,12 @@ export function ConsoleAppV4(props) {
|
|
|
1457
1763
|
? items.findIndex((item) => item.pluginId === restored.focusedEntityId)
|
|
1458
1764
|
: -1;
|
|
1459
1765
|
restoredFocus = entityIndex >= 0
|
|
1460
|
-
? entityIndex
|
|
1461
|
-
: Math.min(restoredFocus, Math.max(0, items.length - 1));
|
|
1766
|
+
? pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + entityIndex
|
|
1767
|
+
: Math.min(restoredFocus, Math.max(0, pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + items.length - 1));
|
|
1462
1768
|
}
|
|
1463
1769
|
else if (restoredFrame.route.name === 'sourceList') {
|
|
1464
1770
|
const sources = props.controller.listSources();
|
|
1465
|
-
itemCount = sources.length +
|
|
1771
|
+
itemCount = sources.length + 3;
|
|
1466
1772
|
const entityIndex = restored.focusedEntityId
|
|
1467
1773
|
? sources.findIndex((source) => source.id === restored.focusedEntityId)
|
|
1468
1774
|
: -1;
|
|
@@ -1532,19 +1838,20 @@ export function ConsoleAppV4(props) {
|
|
|
1532
1838
|
setShowTechnical(false);
|
|
1533
1839
|
setActionError(undefined);
|
|
1534
1840
|
};
|
|
1535
|
-
const beginReview = (intent) => {
|
|
1841
|
+
const beginReview = (intent, returnRoute) => {
|
|
1536
1842
|
const supportsScriptTimeout = intent.kind === 'pluginWrite'
|
|
1537
1843
|
&& (intent.action === 'install' || intent.action === 'sync'
|
|
1538
1844
|
|| intent.action === 'refreshAndSync' || intent.action === 'reinstall')
|
|
1539
1845
|
|| intent.kind === 'pluginWriteBatch'
|
|
1540
1846
|
&& (intent.action === 'install' || intent.action === 'sync' || intent.action === 'reinstall')
|
|
1847
|
+
|| intent.kind === 'pluginWriteTask'
|
|
1541
1848
|
|| intent.kind === 'configSave' && intent.apply;
|
|
1542
1849
|
const pendingIntent = supportsScriptTimeout ? intent : undefined;
|
|
1543
1850
|
pendingReviewIntentRef.current = pendingIntent;
|
|
1544
1851
|
setPendingReviewIntent(pendingIntent);
|
|
1545
|
-
const underlyingResultRoute = route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
|
|
1852
|
+
const underlyingResultRoute = returnRoute ?? (route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
|
|
1546
1853
|
? snapshot.result.returnRoute
|
|
1547
|
-
: undefined;
|
|
1854
|
+
: undefined);
|
|
1548
1855
|
props.controller.updatePageContext({
|
|
1549
1856
|
focusIndex: focus,
|
|
1550
1857
|
scrollOffset,
|
|
@@ -1556,6 +1863,38 @@ export function ConsoleAppV4(props) {
|
|
|
1556
1863
|
setFocus(0);
|
|
1557
1864
|
setScrollOffset(0);
|
|
1558
1865
|
};
|
|
1866
|
+
useEffect(() => {
|
|
1867
|
+
if (route.name !== 'pluginWriteTask' || route.goal !== 'restore')
|
|
1868
|
+
return;
|
|
1869
|
+
if (snapshot.router.current.context.autoPreparedRestore)
|
|
1870
|
+
return;
|
|
1871
|
+
const retainedTargetKeys = route.targetKeys ? new Set(route.targetKeys) : undefined;
|
|
1872
|
+
const allCandidates = props.controller.listPluginWriteTaskCandidates('restore');
|
|
1873
|
+
const currentTargetKeys = new Set(allCandidates.map(pluginWriteTaskTargetKey));
|
|
1874
|
+
if (route.targetKeys?.some((targetKey) => !currentTargetKeys.has(targetKey)))
|
|
1875
|
+
return;
|
|
1876
|
+
const candidates = allCandidates
|
|
1877
|
+
.filter((candidate) => retainedTargetKeys
|
|
1878
|
+
? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
|
|
1879
|
+
: candidate.target.pluginId === route.pluginId);
|
|
1880
|
+
if (candidates.length === 0 || candidates.some((candidate) => !candidate.eligible))
|
|
1881
|
+
return;
|
|
1882
|
+
props.controller.updatePageContext({ autoPreparedRestore: true });
|
|
1883
|
+
beginReview({
|
|
1884
|
+
kind: 'pluginWriteTask',
|
|
1885
|
+
goal: 'restore',
|
|
1886
|
+
targets: candidates.map((candidate) => candidate.target),
|
|
1887
|
+
executionPolicy: 'all',
|
|
1888
|
+
}, props.controller.pluginWriteTaskReturnRoute(route));
|
|
1889
|
+
}, [
|
|
1890
|
+
props.controller,
|
|
1891
|
+
route.name,
|
|
1892
|
+
route.name === 'pluginWriteTask' ? route.goal : '',
|
|
1893
|
+
route.name === 'pluginWriteTask' ? route.pluginId : '',
|
|
1894
|
+
route.name === 'pluginWriteTask' ? route.targetKeys?.join('\0') ?? '' : '',
|
|
1895
|
+
snapshot.router.current.context.autoPreparedRestore,
|
|
1896
|
+
snapshot.viewModel,
|
|
1897
|
+
]);
|
|
1559
1898
|
const openBatchSelection = (action) => {
|
|
1560
1899
|
props.controller.updatePageContext({
|
|
1561
1900
|
focusIndex: focus,
|
|
@@ -1566,28 +1905,128 @@ export function ConsoleAppV4(props) {
|
|
|
1566
1905
|
focusedEntityId: route.name === 'pluginList' ? pluginRows[focus]?.pluginId : undefined,
|
|
1567
1906
|
});
|
|
1568
1907
|
props.controller.beginPluginWriteBatch();
|
|
1908
|
+
const flowId = props.controller.snapshot.router.currentFlowId;
|
|
1909
|
+
if (!flowId)
|
|
1910
|
+
throw new Error('批量插件流程缺少 flowId。');
|
|
1569
1911
|
setBatchDraft({
|
|
1912
|
+
flowId,
|
|
1570
1913
|
...(action ? { action } : {}),
|
|
1914
|
+
mode: route.name === 'pluginList' ? route.mode : action === 'install' ? 'browse' : 'installed',
|
|
1915
|
+
stage: 'select',
|
|
1571
1916
|
selection: 'selected',
|
|
1572
1917
|
selectedPluginIds: [],
|
|
1573
1918
|
platform: 'both',
|
|
1574
1919
|
scope: 'project',
|
|
1920
|
+
...(route.name === 'pluginList' && route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1921
|
+
query,
|
|
1922
|
+
installFilter,
|
|
1923
|
+
showSelected: false,
|
|
1575
1924
|
});
|
|
1576
1925
|
setDirty(true);
|
|
1577
|
-
setFocus(
|
|
1926
|
+
setFocus(1);
|
|
1578
1927
|
setScrollOffset(0);
|
|
1579
1928
|
setSearchFocus(false);
|
|
1580
1929
|
};
|
|
1930
|
+
const batchCandidates = (draft) => {
|
|
1931
|
+
const mode = draft.mode;
|
|
1932
|
+
const all = props.controller.listPlugins({ mode });
|
|
1933
|
+
if (draft.showSelected) {
|
|
1934
|
+
const byId = new Map(all.map((candidate) => [candidate.pluginId, candidate]));
|
|
1935
|
+
return draft.selectedPluginIds.map((pluginId) => ({
|
|
1936
|
+
pluginId,
|
|
1937
|
+
...(byId.get(pluginId) ? { plugin: byId.get(pluginId) } : {}),
|
|
1938
|
+
}));
|
|
1939
|
+
}
|
|
1940
|
+
return props.controller.listPlugins({
|
|
1941
|
+
mode,
|
|
1942
|
+
...(draft.sourceId ? { sourceId: draft.sourceId } : {}),
|
|
1943
|
+
...(draft.query ? { query: draft.query } : {}),
|
|
1944
|
+
installFilter: draft.installFilter,
|
|
1945
|
+
}).map((plugin) => ({ pluginId: plugin.pluginId, plugin }));
|
|
1946
|
+
};
|
|
1947
|
+
const batchCandidateBlockReason = (draft, candidate) => !candidate.plugin
|
|
1948
|
+
? '插件已不在当前状态中,可取消选择'
|
|
1949
|
+
: candidate.plugin.configOnly
|
|
1950
|
+
? '来源中没有可信插件定义'
|
|
1951
|
+
: draft.mode === 'browse' && hasInstalledPlacement(candidate.plugin)
|
|
1952
|
+
? '这个插件已经添加'
|
|
1953
|
+
: undefined;
|
|
1954
|
+
const invalidBatchSelection = (draft) => {
|
|
1955
|
+
const byId = new Map(props.controller.listPlugins({ mode: draft.mode })
|
|
1956
|
+
.map((plugin) => [plugin.pluginId, plugin]));
|
|
1957
|
+
return draft.selectedPluginIds.filter((pluginId) => batchCandidateBlockReason(draft, {
|
|
1958
|
+
pluginId,
|
|
1959
|
+
...(byId.get(pluginId) ? { plugin: byId.get(pluginId) } : {}),
|
|
1960
|
+
}) !== undefined);
|
|
1961
|
+
};
|
|
1962
|
+
const persistDependencyGraphContext = (graphRoute, projection, expandedPluginIds, focusIndex, offset) => {
|
|
1963
|
+
const direction = graphRoute.direction ?? 'dependencies';
|
|
1964
|
+
const actionIds = dependencyGraphActionIds(projection);
|
|
1965
|
+
dependencyGraphContexts.current.set(dependencyGraphContextKey(graphRoute.pluginId, direction), {
|
|
1966
|
+
expandedPluginIds: new Set(expandedPluginIds),
|
|
1967
|
+
...(actionIds[focusIndex] ? { focusedActionId: actionIds[focusIndex] } : {}),
|
|
1968
|
+
...(actionIds[offset] ? { scrollAnchorActionId: actionIds[offset] } : {}),
|
|
1969
|
+
});
|
|
1970
|
+
};
|
|
1971
|
+
const toggleBatchPlugin = (pluginId) => {
|
|
1972
|
+
setBatchDraft((current) => current ? {
|
|
1973
|
+
...current,
|
|
1974
|
+
selectedPluginIds: current.selectedPluginIds.includes(pluginId)
|
|
1975
|
+
? current.selectedPluginIds.filter((candidate) => candidate !== pluginId)
|
|
1976
|
+
: [...current.selectedPluginIds, pluginId],
|
|
1977
|
+
} : current);
|
|
1978
|
+
};
|
|
1979
|
+
const prepareCurrentProjectPlugins = () => {
|
|
1980
|
+
if (props.controller.listCurrentProjectIntentRoots().length === 0) {
|
|
1981
|
+
setActionError('当前项目没有已保存的插件清单。');
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
beginReview({
|
|
1985
|
+
kind: 'pluginWriteBatch',
|
|
1986
|
+
action: 'sync',
|
|
1987
|
+
selection: { kind: 'currentProjectIntentRoots' },
|
|
1988
|
+
platform: 'both',
|
|
1989
|
+
scope: 'project',
|
|
1990
|
+
});
|
|
1991
|
+
};
|
|
1992
|
+
const openSelectedPluginTask = (goal, pluginIds, scopeConstraint) => {
|
|
1993
|
+
if (batchDraft && invalidBatchSelection(batchDraft).length > 0) {
|
|
1994
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const selected = new Set(pluginIds);
|
|
1998
|
+
const candidates = props.controller.listPluginWriteTaskCandidates(goal)
|
|
1999
|
+
.filter((candidate) => selected.has(candidate.target.pluginId)
|
|
2000
|
+
&& (!scopeConstraint || candidate.target.scope === scopeConstraint));
|
|
2001
|
+
const first = candidates[0];
|
|
2002
|
+
if (!first) {
|
|
2003
|
+
setActionError(goal === 'update'
|
|
2004
|
+
? '所选插件没有经过版本证据确认的更新。'
|
|
2005
|
+
: '所选插件没有可恢复的现有位置。');
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
go({
|
|
2009
|
+
name: 'pluginWriteTask',
|
|
2010
|
+
pluginId: first.target.pluginId,
|
|
2011
|
+
goal,
|
|
2012
|
+
targetKeys: candidates.map(pluginWriteTaskTargetKey),
|
|
2013
|
+
...(batchDraft ? {
|
|
2014
|
+
batchOrigin: { flowId: batchDraft.flowId, owner: 'batch-write' },
|
|
2015
|
+
} : {}),
|
|
2016
|
+
...(goal === 'update'
|
|
2017
|
+
? { versionEvidence: Object.assign({}, ...[...selected].map((pluginId) => {
|
|
2018
|
+
const candidatePlugin = props.controller.getPluginOverview(pluginId);
|
|
2019
|
+
return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
|
|
2020
|
+
})) }
|
|
2021
|
+
: {}),
|
|
2022
|
+
});
|
|
2023
|
+
};
|
|
1581
2024
|
const continueInstallAfterTargets = async (candidate, targets) => {
|
|
1582
2025
|
if (props.controller.snapshot.installPlacementDraft?.pluginId !== candidate.pluginId) {
|
|
1583
2026
|
props.controller.beginInstallFlow(candidate.pluginId, scope);
|
|
1584
2027
|
}
|
|
1585
2028
|
props.controller.setInstallPlatforms(targets);
|
|
1586
|
-
|
|
1587
|
-
if (hasConfigurableDependencies) {
|
|
1588
|
-
go({ name: 'dependencyPlacement', pluginId: candidate.pluginId });
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
2029
|
+
await props.controller.prepareInstallDependencyPlacement();
|
|
1591
2030
|
beginReview(props.controller.installPlacementIntent());
|
|
1592
2031
|
};
|
|
1593
2032
|
const beginPortable = async (pluginId) => {
|
|
@@ -2069,18 +2508,19 @@ export function ConsoleAppV4(props) {
|
|
|
2069
2508
|
const openConfigLayerTarget = (layer) => {
|
|
2070
2509
|
if (route.name !== 'configLayer')
|
|
2071
2510
|
return;
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2511
|
+
const definitionPage = configPage ?? configLayerPages.user ?? configLayerPages.project;
|
|
2512
|
+
const field = route.fieldKey && definitionPage
|
|
2513
|
+
? definitionPage.groups.flatMap((group) => group.fields).find((candidate) => candidate.key === route.fieldKey)
|
|
2074
2514
|
: undefined;
|
|
2075
|
-
const fieldTarget =
|
|
2076
|
-
const preset = route.presetId &&
|
|
2077
|
-
?
|
|
2515
|
+
const fieldTarget = definitionPage ? configFieldTarget(definitionPage, route.fieldKey) : undefined;
|
|
2516
|
+
const preset = route.presetId && definitionPage
|
|
2517
|
+
? definitionPage.presets.find((candidate) => candidate.id === route.presetId)
|
|
2078
2518
|
: undefined;
|
|
2079
2519
|
const presetTarget = preset
|
|
2080
|
-
? configFieldTarget(
|
|
2520
|
+
? configFieldTarget(definitionPage, Object.keys(preset.values)[0])
|
|
2081
2521
|
: undefined;
|
|
2082
2522
|
const target = fieldTarget ?? (route.groupId ? { groupId: route.groupId, focusIndex: 0 } : undefined) ?? presetTarget
|
|
2083
|
-
?? (
|
|
2523
|
+
?? (definitionPage?.groups[0] ? { groupId: definitionPage.groups[0].id, focusIndex: 0 } : undefined);
|
|
2084
2524
|
const context = target
|
|
2085
2525
|
? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 }
|
|
2086
2526
|
: {};
|
|
@@ -2187,27 +2627,33 @@ export function ConsoleAppV4(props) {
|
|
|
2187
2627
|
setObjectError(undefined);
|
|
2188
2628
|
setConfigActionError(undefined);
|
|
2189
2629
|
};
|
|
2190
|
-
const
|
|
2630
|
+
const configSaveIntent = (apply) => {
|
|
2191
2631
|
if (!configRoute || configRoute.layer.scope === 'effective' || !currentConfigPage || !dirty)
|
|
2192
2632
|
return;
|
|
2633
|
+
const fields = currentConfigPage.groups.flatMap((group) => group.fields);
|
|
2634
|
+
return {
|
|
2635
|
+
kind: 'configSave',
|
|
2636
|
+
pluginId: configRoute.pluginId,
|
|
2637
|
+
scope: configRoute.layer.scope,
|
|
2638
|
+
apply,
|
|
2639
|
+
updates: draftUpdates,
|
|
2640
|
+
removeKeys: draftRemoves,
|
|
2641
|
+
sensitiveKeys: fields.filter((field) => field.sensitive).map((field) => field.key),
|
|
2642
|
+
privateKeys: fields.filter((field) => configObjectHasSensitiveChild(field.definition))
|
|
2643
|
+
.map((field) => field.key),
|
|
2644
|
+
changes: configChanges(),
|
|
2645
|
+
};
|
|
2646
|
+
};
|
|
2647
|
+
const beginConfigSave = async (apply) => {
|
|
2193
2648
|
if (configDraftIssues.length > 0) {
|
|
2194
2649
|
setConfigActionError('字段已变化。请先放弃失效草稿,或返回继续检查来源。');
|
|
2195
2650
|
return;
|
|
2196
2651
|
}
|
|
2197
|
-
const
|
|
2652
|
+
const intent = configSaveIntent(apply);
|
|
2653
|
+
if (!intent)
|
|
2654
|
+
return;
|
|
2198
2655
|
try {
|
|
2199
|
-
await beginReview(
|
|
2200
|
-
kind: 'configSave',
|
|
2201
|
-
pluginId: configRoute.pluginId,
|
|
2202
|
-
scope: configRoute.layer.scope,
|
|
2203
|
-
apply,
|
|
2204
|
-
updates: draftUpdates,
|
|
2205
|
-
removeKeys: draftRemoves,
|
|
2206
|
-
sensitiveKeys: fields.filter((field) => field.sensitive).map((field) => field.key),
|
|
2207
|
-
privateKeys: fields.filter((field) => configObjectHasSensitiveChild(field.definition))
|
|
2208
|
-
.map((field) => field.key),
|
|
2209
|
-
changes: configChanges(),
|
|
2210
|
-
});
|
|
2656
|
+
await beginReview(intent);
|
|
2211
2657
|
setConfigActionError(undefined);
|
|
2212
2658
|
}
|
|
2213
2659
|
catch (error) {
|
|
@@ -2263,6 +2709,35 @@ export function ConsoleAppV4(props) {
|
|
|
2263
2709
|
return;
|
|
2264
2710
|
setPendingPresetId(undefined);
|
|
2265
2711
|
}, [pendingPresetId, route.name, route.name === 'configFields' ? route.layer.scope : '']);
|
|
2712
|
+
useEffect(() => {
|
|
2713
|
+
if (route.name !== 'configSave') {
|
|
2714
|
+
setConfigSaveImpact(undefined);
|
|
2715
|
+
setConfigSaveImpactError(false);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
const intent = configSaveIntent(false);
|
|
2719
|
+
if (!intent)
|
|
2720
|
+
return;
|
|
2721
|
+
const abort = new AbortController();
|
|
2722
|
+
setConfigSaveImpact(undefined);
|
|
2723
|
+
setConfigSaveImpactError(false);
|
|
2724
|
+
void props.controller.previewConfigSaveImpact(intent, abort.signal)
|
|
2725
|
+
.then((impact) => {
|
|
2726
|
+
if (!abort.signal.aborted)
|
|
2727
|
+
setConfigSaveImpact(impact);
|
|
2728
|
+
})
|
|
2729
|
+
.catch(() => {
|
|
2730
|
+
if (!abort.signal.aborted)
|
|
2731
|
+
setConfigSaveImpactError(true);
|
|
2732
|
+
});
|
|
2733
|
+
return () => abort.abort();
|
|
2734
|
+
}, [
|
|
2735
|
+
props.controller,
|
|
2736
|
+
route.name,
|
|
2737
|
+
route.name === 'configSave' ? `${route.pluginId}:${route.layer.scope}` : '',
|
|
2738
|
+
draftRemoves,
|
|
2739
|
+
draftUpdates,
|
|
2740
|
+
]);
|
|
2266
2741
|
if (snapshot.phase === 'error' && !isCatalogSourcesBootstrapFlow(snapshot)
|
|
2267
2742
|
&& !isOperationHistoryRoute(route)) {
|
|
2268
2743
|
const sourcesConfigInvalid = snapshot.loadError?.code === 'catalogSourcesConfigInvalid';
|
|
@@ -2294,7 +2769,7 @@ export function ConsoleAppV4(props) {
|
|
|
2294
2769
|
context = '当前项目';
|
|
2295
2770
|
actions = [
|
|
2296
2771
|
{ label: '添加插件', description: '浏览多个来源中的 Agent 能力', meta: String(home.availablePlugins), run: () => go({ name: 'pluginList', mode: 'browse' }) },
|
|
2297
|
-
{ label: '我的插件', description: '查看状态、设置和移除已添加插件', meta: String(home.installedPlugins), run: () => go({ name: 'pluginList', mode: 'installed' }) },
|
|
2772
|
+
{ label: '我的插件', description: '查看状态、设置和移除已添加插件', meta: home.pluginUpdateCount > 0 ? `${home.installedPlugins} · ${home.pluginUpdateCount} 可更新` : String(home.installedPlugins), status: home.pluginUpdateCount > 0 ? 'warning' : undefined, run: () => go({ name: 'pluginList', mode: 'installed' }) },
|
|
2298
2773
|
{ label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
|
|
2299
2774
|
{
|
|
2300
2775
|
label: '插件来源',
|
|
@@ -2308,7 +2783,7 @@ export function ConsoleAppV4(props) {
|
|
|
2308
2783
|
];
|
|
2309
2784
|
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [snapshot.statusReadAtMs !== undefined
|
|
2310
2785
|
? _jsx(StaticRow, { title: "\u6700\u8FD1\u6210\u529F\u8BFB\u53D6\u72B6\u6001", meta: new Date(snapshot.statusReadAtMs).toLocaleString('zh-CN', { hour12: false }), compact: true })
|
|
2311
|
-
: null, _jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null, home.catalogCheckingCount > 0 ? _jsx(ResultRow, { title: `${home.catalogCheckingCount} 个插件来源正在检查`, status: "muted" }) : null, home.catalogUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.catalogUpdateCount} 个插件来源有更新`, status: "warning" }) : null, home.catalogRecoveryCount > 0 ? _jsx(ResultRow, { title: `${home.catalogRecoveryCount} 个插件来源需要检查或恢复`, status: "warning" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
|
|
2786
|
+
: null, _jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null, home.catalogCheckingCount > 0 ? _jsx(ResultRow, { title: `${home.catalogCheckingCount} 个插件来源正在检查`, status: "muted" }) : null, home.catalogUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.catalogUpdateCount} 个插件来源有更新`, status: "warning" }) : null, home.pluginUpdateCount > 0 ? _jsx(ResultRow, { title: `${home.pluginUpdateCount} 个插件可更新${home.directPluginUpdateCount < home.pluginUpdateCount ? `;${home.directPluginUpdateCount} 个可直接确认` : ''}`, status: "warning" }) : null, home.catalogRecoveryCount > 0 ? _jsx(ResultRow, { title: `${home.catalogRecoveryCount} 个插件来源需要检查或恢复`, status: "warning" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
|
|
2312
2787
|
}
|
|
2313
2788
|
else if (route.name === 'pluginList') {
|
|
2314
2789
|
const sourceId = route.sourceId ?? 'all';
|
|
@@ -2324,67 +2799,103 @@ export function ConsoleAppV4(props) {
|
|
|
2324
2799
|
...(query ? { query } : {}),
|
|
2325
2800
|
installFilter: 'uninstalled',
|
|
2326
2801
|
}).length;
|
|
2802
|
+
const updateCount = props.controller.listPlugins({
|
|
2803
|
+
mode: route.mode,
|
|
2804
|
+
...(sourceId !== 'all' ? { sourceId } : {}),
|
|
2805
|
+
...(query ? { query } : {}),
|
|
2806
|
+
installFilter: 'updateAvailable',
|
|
2807
|
+
}).length;
|
|
2327
2808
|
breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
|
|
2328
2809
|
title = breadcrumb;
|
|
2329
2810
|
description = route.mode === 'browse'
|
|
2330
|
-
? '
|
|
2331
|
-
: '
|
|
2811
|
+
? '选择一个插件,或从列表前部选择多个。'
|
|
2812
|
+
: '先处理推荐任务,或查看一个插件。';
|
|
2332
2813
|
context = `${items.length} 个插件`;
|
|
2333
|
-
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a
|
|
2814
|
+
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a 按项目清单准备 · f 可更新' : ''} · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
|
|
2334
2815
|
const sourceError = sourceId !== 'all'
|
|
2335
2816
|
&& props.controller.getSourceOverview(sourceId).health?.state === 'unavailable';
|
|
2336
2817
|
actions = sourceError
|
|
2337
2818
|
? [{ label: '查看来源问题', description: '当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
|
|
2338
2819
|
: [];
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2820
|
+
const listedPluginIds = new Set(items.map((item) => item.pluginId));
|
|
2821
|
+
const recommendedUpdateCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
2822
|
+
.filter((candidate) => candidate.eligible && listedPluginIds.has(candidate.target.pluginId));
|
|
2823
|
+
const recommendedUpdatePluginIds = [...new Set(recommendedUpdateCandidates
|
|
2824
|
+
.map((candidate) => candidate.target.pluginId))];
|
|
2825
|
+
const projectRestoreCandidates = props.controller.listPluginWriteTaskCandidates('restore')
|
|
2826
|
+
.filter((candidate) => candidate.target.scope === 'project');
|
|
2827
|
+
const projectIntentRoots = props.controller.listCurrentProjectIntentRoots();
|
|
2828
|
+
const menuRows = sourceError || items.length === 0 || query ? [] : [
|
|
2829
|
+
...(route.mode === 'installed' && recommendedUpdatePluginIds.length > 0 ? [{
|
|
2830
|
+
pluginId: '@menu:update',
|
|
2831
|
+
name: `更新 ${recommendedUpdatePluginIds.length} 个插件…`,
|
|
2832
|
+
sourceLabel: '',
|
|
2833
|
+
description: '使用已验证版本和现有安装位置,直接查看任务确认',
|
|
2834
|
+
placementSummary: '',
|
|
2835
|
+
statusLabel: '推荐',
|
|
2836
|
+
status: 'warning',
|
|
2837
|
+
menuAction: () => openSelectedPluginTask('update', recommendedUpdatePluginIds),
|
|
2838
|
+
}] : []),
|
|
2839
|
+
...(route.mode === 'installed' && projectIntentRoots.length > 0 ? [{
|
|
2840
|
+
pluginId: '@menu:restore-project',
|
|
2841
|
+
name: '按项目清单准备…',
|
|
2842
|
+
sourceLabel: '',
|
|
2843
|
+
description: `${projectIntentRoots.length} 个已保存根插件;允许补齐当前项目缺少的安装`,
|
|
2844
|
+
placementSummary: '',
|
|
2845
|
+
statusLabel: '项目',
|
|
2846
|
+
status: 'info',
|
|
2847
|
+
menuAction: prepareCurrentProjectPlugins,
|
|
2848
|
+
}] : []),
|
|
2849
|
+
...(route.mode === 'installed' && projectRestoreCandidates.length > 0 ? [{
|
|
2850
|
+
pluginId: '@menu:repair-project',
|
|
2851
|
+
name: '修复本项目异常…',
|
|
2852
|
+
sourceLabel: '',
|
|
2853
|
+
description: '严格按已确认的现有版本修复异常,不改变插件版本',
|
|
2854
|
+
placementSummary: '',
|
|
2855
|
+
statusLabel: '推荐',
|
|
2856
|
+
status: 'warning',
|
|
2857
|
+
menuAction: () => openSelectedPluginTask('restore', [...new Set(projectRestoreCandidates
|
|
2858
|
+
.map((candidate) => candidate.target.pluginId))], 'project'),
|
|
2859
|
+
}] : []),
|
|
2860
|
+
{
|
|
2353
2861
|
pluginId: '@menu:batch',
|
|
2354
|
-
name: route.mode === 'browse' ? '选择多个插件添加…' : '
|
|
2862
|
+
name: route.mode === 'browse' ? '选择多个插件添加…' : '选择多个…',
|
|
2355
2863
|
sourceLabel: '',
|
|
2356
2864
|
description: route.mode === 'browse'
|
|
2357
|
-
? '
|
|
2358
|
-
: '
|
|
2865
|
+
? '先选择插件,再选择安装位置和平台'
|
|
2866
|
+
: '先选择插件,再选择更新、恢复、移除或维护任务',
|
|
2359
2867
|
placementSummary: '',
|
|
2360
2868
|
statusLabel: '菜单',
|
|
2361
2869
|
status: 'info',
|
|
2362
2870
|
menuAction: () => openBatchSelection(route.mode === 'browse' ? 'install' : undefined),
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2871
|
+
},
|
|
2872
|
+
];
|
|
2873
|
+
pluginRows = sourceError ? [] : [...menuRows, ...items.map((item) => ({
|
|
2874
|
+
pluginId: item.pluginId,
|
|
2875
|
+
name: item.displayName,
|
|
2876
|
+
sourceLabel: item.sourceLabel,
|
|
2877
|
+
description: item.configOnly
|
|
2878
|
+
? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
|
|
2879
|
+
: item.description,
|
|
2880
|
+
...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
|
|
2881
|
+
placementSummary: pluginPlacementSummary(item),
|
|
2882
|
+
statusLabel: statusText(item),
|
|
2883
|
+
status: pluginRowStatus(item),
|
|
2884
|
+
}))];
|
|
2885
|
+
if (route.mode === 'installed' && installFilter === 'updateAvailable'
|
|
2886
|
+
&& items.length === 0 && !sourceError) {
|
|
2887
|
+
title = '没有可更新插件';
|
|
2888
|
+
description = '当前筛选范围内没有经过版本证据确认的更新。';
|
|
2889
|
+
actions = [{
|
|
2890
|
+
label: '查看全部我的插件',
|
|
2891
|
+
description: '按 f 返回完整列表',
|
|
2892
|
+
run: () => {
|
|
2893
|
+
setInstallFilter('all');
|
|
2894
|
+
props.controller.updatePageContext({ installFilter: 'all' });
|
|
2383
2895
|
},
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2896
|
+
}];
|
|
2386
2897
|
}
|
|
2387
|
-
if (route.mode === 'installed' && items.length === 0 && !sourceError) {
|
|
2898
|
+
else if (route.mode === 'installed' && items.length === 0 && !sourceError) {
|
|
2388
2899
|
title = '还没有添加插件';
|
|
2389
2900
|
description = `从已配置的 ${props.controller.listSources().length} 个来源中浏览可用能力。`;
|
|
2390
2901
|
actions = [{ label: '浏览插件', description: '开始添加第一个插件', run: () => go({ name: 'pluginList', mode: 'browse' }) }];
|
|
@@ -2398,7 +2909,7 @@ export function ConsoleAppV4(props) {
|
|
|
2398
2909
|
disabled: '没有可打开的插件',
|
|
2399
2910
|
}];
|
|
2400
2911
|
}
|
|
2401
|
-
const pluginListStaticRows =
|
|
2912
|
+
const pluginListStaticRows = 3;
|
|
2402
2913
|
const viewport = sourceError || pluginRows.length === 0
|
|
2403
2914
|
? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
|
|
2404
2915
|
: rowViewport({
|
|
@@ -2416,73 +2927,201 @@ export function ConsoleAppV4(props) {
|
|
|
2416
2927
|
? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
|
|
2417
2928
|
: _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), route.mode === 'browse'
|
|
2418
2929
|
? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'uninstalled' ? '[未安装]' : '未安装', " ", uninstalledCount] }), " f \u5207\u6362"] })
|
|
2419
|
-
:
|
|
2930
|
+
: _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'updateAvailable' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'updateAvailable' ? '[可更新]' : '可更新', " ", updateCount] }), " f \u5207\u6362"] }), sourceError
|
|
2420
2931
|
? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
|
|
2421
2932
|
: route.mode === 'installed' && items.length === 0
|
|
2422
|
-
?
|
|
2933
|
+
? installFilter === 'updateAvailable'
|
|
2934
|
+
? _jsx(Callout, { title: "\u6CA1\u6709\u53EF\u66F4\u65B0\u63D2\u4EF6", description: "\u5F53\u524D\u7B5B\u9009\u8303\u56F4\u5185\u6CA1\u6709\u7ECF\u8FC7\u7248\u672C\u8BC1\u636E\u786E\u8BA4\u7684\u66F4\u65B0\u3002" })
|
|
2935
|
+
: _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
|
|
2423
2936
|
: null, pluginRows.length > 0
|
|
2424
2937
|
? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport })
|
|
2425
2938
|
: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] }));
|
|
2426
2939
|
}
|
|
2427
2940
|
else if (route.name === 'batchSelection') {
|
|
2428
|
-
breadcrumb = '我的插件 /
|
|
2429
|
-
title = batchDraft?.
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2941
|
+
breadcrumb = `${batchDraft?.mode === 'browse' ? '添加插件' : '我的插件'} / 选择多个`;
|
|
2942
|
+
title = batchDraft?.stage === 'task' ? `为 ${batchDraft.selectedPluginIds.length} 个插件选择任务`
|
|
2943
|
+
: batchDraft?.stage === 'maintenance' ? '其他维护方式'
|
|
2944
|
+
: batchDraft?.mode === 'browse' ? '选择要添加的插件' : '选择插件';
|
|
2945
|
+
description = batchDraft?.stage === 'select'
|
|
2946
|
+
? 'Space 切换选择,Enter 查看插件。选择会跨来源、搜索和筛选保留。'
|
|
2947
|
+
: batchDraft?.stage === 'task'
|
|
2948
|
+
? '更新和恢复使用现有精确位置;移除会明确使用已有位置。'
|
|
2949
|
+
: '同步、重新安装和普通清理使用所选插件的已有位置。';
|
|
2950
|
+
if (!batchDraft) {
|
|
2951
|
+
actions = [{ label: '返回插件列表', run: back }];
|
|
2952
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
|
|
2953
|
+
}
|
|
2954
|
+
else if (batchDraft.stage === 'select') {
|
|
2955
|
+
const candidates = batchCandidates(batchDraft);
|
|
2956
|
+
const selected = new Set(batchDraft.selectedPluginIds);
|
|
2957
|
+
const currentIds = new Set(candidates.map((candidate) => candidate.pluginId));
|
|
2958
|
+
const hiddenSelected = batchDraft.selectedPluginIds.filter((pluginId) => !currentIds.has(pluginId)).length;
|
|
2959
|
+
const candidateActions = candidates.map((candidate) => {
|
|
2960
|
+
const blockReason = batchCandidateBlockReason(batchDraft, candidate);
|
|
2961
|
+
const isSelected = selected.has(candidate.pluginId);
|
|
2962
|
+
return {
|
|
2963
|
+
label: candidate.plugin?.displayName ?? `${candidate.pluginId}(已不可用,可取消选择)`,
|
|
2964
|
+
description: blockReason
|
|
2965
|
+
? `${candidate.plugin?.sourceLabel ?? '状态已失效'} · ${isSelected ? '可取消选择' : '不可选择'}:${blockReason}`
|
|
2966
|
+
: `${candidate.plugin.sourceLabel} · ${pluginPlacementSummary(candidate.plugin)}`,
|
|
2967
|
+
selected: isSelected,
|
|
2968
|
+
pluginId: candidate.pluginId,
|
|
2969
|
+
canSelectNew: blockReason === undefined,
|
|
2970
|
+
status: blockReason ? 'muted' : undefined,
|
|
2971
|
+
run: candidate.plugin ? () => go(batchDraft.mode === 'browse'
|
|
2972
|
+
? { name: 'pluginPreview', pluginId: candidate.pluginId }
|
|
2973
|
+
: { name: 'pluginDetail', pluginId: candidate.pluginId }) : undefined,
|
|
2974
|
+
};
|
|
2975
|
+
});
|
|
2976
|
+
const selectableIds = candidates
|
|
2977
|
+
.filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined)
|
|
2978
|
+
.map((candidate) => candidate.pluginId);
|
|
2979
|
+
const continueSelection = () => {
|
|
2980
|
+
if (batchDraft.selectedPluginIds.length === 0) {
|
|
2981
|
+
setActionError('请至少选择一个插件。');
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
const invalid = invalidBatchSelection(batchDraft);
|
|
2985
|
+
if (invalid.length > 0) {
|
|
2986
|
+
setActionError(`${invalid.length} 个已选插件已不可用。请先在“查看全部已选”中取消选择。`);
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
if (batchDraft.mode === 'browse') {
|
|
2990
|
+
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
2991
|
+
props.controller.push({ name: 'batchPlacement' });
|
|
2992
|
+
}
|
|
2993
|
+
else {
|
|
2994
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
2995
|
+
}
|
|
2996
|
+
setFocus(0);
|
|
2997
|
+
setScrollOffset(0);
|
|
2998
|
+
};
|
|
2999
|
+
const continueAction = {
|
|
3000
|
+
label: `查看确认(${batchDraft.selectedPluginIds.length})`,
|
|
3001
|
+
description: batchDraft.selectedPluginIds.length > 0 ? '继续选择任务和确认范围' : '先选择至少一个插件',
|
|
3002
|
+
run: continueSelection,
|
|
3003
|
+
};
|
|
3004
|
+
const utilityActions = [
|
|
2435
3005
|
{
|
|
2436
|
-
label: '
|
|
2437
|
-
description: '
|
|
3006
|
+
label: batchDraft.showSelected ? '返回当前结果' : `查看全部已选(${batchDraft.selectedPluginIds.length})`,
|
|
3007
|
+
description: hiddenSelected > 0 ? `${hiddenSelected} 个选择在其他筛选中` : '完整 canonical 选择集合',
|
|
2438
3008
|
run: () => {
|
|
2439
|
-
setBatchDraft({
|
|
2440
|
-
action: 'sync',
|
|
2441
|
-
selection: 'currentProjectIntentRoots',
|
|
2442
|
-
selectedPluginIds: [],
|
|
2443
|
-
platform: 'both',
|
|
2444
|
-
scope: 'project',
|
|
2445
|
-
});
|
|
2446
|
-
props.controller.push({ name: 'batchPlacement' });
|
|
2447
|
-
},
|
|
2448
|
-
},
|
|
2449
|
-
...['install', 'sync', 'reinstall', 'uninstall', 'cleanup'].map((action) => ({
|
|
2450
|
-
label: `选择多个插件并${batchActionLabel(action)}`,
|
|
2451
|
-
description: action === 'cleanup' ? '执行普通清理;保留不属于 AW 管理的内容' : undefined,
|
|
2452
|
-
run: () => {
|
|
2453
|
-
setBatchDraft((current) => current ? { ...current, action } : current);
|
|
3009
|
+
setBatchDraft((current) => current ? { ...current, showSelected: !current.showSelected } : current);
|
|
2454
3010
|
setFocus(0);
|
|
2455
3011
|
setScrollOffset(0);
|
|
2456
3012
|
},
|
|
2457
|
-
}
|
|
3013
|
+
},
|
|
3014
|
+
{
|
|
3015
|
+
label: '全选当前结果',
|
|
3016
|
+
description: `${selectableIds.length} 个当前筛选下可选结果,包括滚动范围外`,
|
|
3017
|
+
run: () => setBatchDraft((current) => current ? {
|
|
3018
|
+
...current,
|
|
3019
|
+
selectedPluginIds: [...new Set([...current.selectedPluginIds, ...selectableIds])],
|
|
3020
|
+
} : current),
|
|
3021
|
+
},
|
|
3022
|
+
{
|
|
3023
|
+
label: '取消当前结果',
|
|
3024
|
+
description: '保留其他来源或搜索中的选择',
|
|
3025
|
+
run: () => setBatchDraft((current) => current ? {
|
|
3026
|
+
...current,
|
|
3027
|
+
selectedPluginIds: current.selectedPluginIds.filter((pluginId) => !currentIds.has(pluginId)),
|
|
3028
|
+
} : current),
|
|
3029
|
+
},
|
|
3030
|
+
{
|
|
3031
|
+
label: '清空全部已选',
|
|
3032
|
+
run: () => setBatchDraft((current) => current ? { ...current, selectedPluginIds: [] } : current),
|
|
3033
|
+
},
|
|
2458
3034
|
];
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
3035
|
+
actions = [continueAction, ...candidateActions, ...utilityActions];
|
|
3036
|
+
const batchFocus = Math.min(focus, actions.length - 1);
|
|
3037
|
+
context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
|
|
3038
|
+
help = '↑↓ 移动 · Space 选择 · Enter 查看/操作 · c 查看确认 · ←→ 来源 · / 搜索 · Esc 放弃';
|
|
3039
|
+
const scrollActions = [...candidateActions, ...utilityActions];
|
|
3040
|
+
const viewport = actionViewport(scrollActions, policy.mode === 'compact', Math.max(0, batchFocus - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)));
|
|
3041
|
+
const focusedCandidate = batchFocus > 0 && batchFocus <= candidateActions.length
|
|
3042
|
+
? candidates[batchFocus - 1]?.plugin : undefined;
|
|
3043
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: batchDraft.showSelected ? 'all' : batchDraft.sourceId ?? 'all', compact: policy.mode === 'compact', columns: size.columns - 2, mode: batchDraft.mode, query: batchDraft.showSelected ? undefined : batchDraft.query || undefined, installFilter: batchDraft.showSelected ? 'all' : batchDraft.installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
|
|
3044
|
+
? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
|
|
3045
|
+
: _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsx(SemanticRows, { actions: [continueAction], focus: batchFocus, kind: "action", compact: true }), focusedCandidate
|
|
3046
|
+
? _jsx(StaticRow, { title: `用途 · ${focusedCandidate.description}`, status: "info", compact: true })
|
|
3047
|
+
: null, _jsx(SemanticRows, { actions: scrollActions, focus: batchFocus - 1, kind: "toggle", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
|
|
2463
3048
|
}
|
|
2464
3049
|
else {
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
3050
|
+
const selectedIds = batchDraft.selectedPluginIds;
|
|
3051
|
+
const taskCandidates = (goal) => {
|
|
3052
|
+
const selected = new Set(selectedIds);
|
|
3053
|
+
return props.controller.listPluginWriteTaskCandidates(goal)
|
|
3054
|
+
.filter((candidate) => selected.has(candidate.target.pluginId));
|
|
3055
|
+
};
|
|
3056
|
+
const startExistingBatch = (action) => {
|
|
3057
|
+
if (invalidBatchSelection(batchDraft).length > 0) {
|
|
3058
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
const exactTargets = props.controller.listPluginWriteBatchTargets(action, selectedIds);
|
|
3062
|
+
if (exactTargets.length === 0) {
|
|
3063
|
+
setActionError('所选插件没有可用的已有安装位置。');
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
beginReview({
|
|
3067
|
+
kind: 'pluginWriteBatch', action,
|
|
3068
|
+
selection: { kind: 'targets', targets: exactTargets },
|
|
3069
|
+
platform: exactTargets[0].platform,
|
|
3070
|
+
scope: exactTargets[0].scope,
|
|
3071
|
+
});
|
|
3072
|
+
};
|
|
3073
|
+
const syncTargets = props.controller.listPluginWriteBatchTargets('sync', selectedIds);
|
|
3074
|
+
const uninstallTargets = props.controller.listPluginWriteBatchTargets('uninstall', selectedIds);
|
|
3075
|
+
if (batchDraft.stage === 'maintenance') {
|
|
3076
|
+
actions = [
|
|
3077
|
+
{ label: '同步已保存使用设置…', description: `重新应用 ${syncTargets.length} 个已保存或现场目标`, run: () => startExistingBatch('sync') },
|
|
3078
|
+
{ label: '重新安装…', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
|
|
3079
|
+
{ label: '清理遗留内容…', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个有受管证据的位置;保留 foreign 和 shared 内容`, run: () => startExistingBatch('cleanup') },
|
|
3080
|
+
{
|
|
3081
|
+
label: '返回任务选择',
|
|
3082
|
+
run: () => {
|
|
3083
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
3084
|
+
setFocus(0);
|
|
3085
|
+
setScrollOffset(0);
|
|
3086
|
+
},
|
|
3087
|
+
},
|
|
3088
|
+
];
|
|
3089
|
+
}
|
|
3090
|
+
else {
|
|
3091
|
+
const updates = taskCandidates('update');
|
|
3092
|
+
const restores = taskCandidates('restore');
|
|
3093
|
+
const updateRoots = new Set(updates.map((candidate) => candidate.target.pluginId)).size;
|
|
3094
|
+
const restoreRoots = new Set(restores.map((candidate) => candidate.target.pluginId)).size;
|
|
3095
|
+
actions = [
|
|
3096
|
+
...(updates.length > 0 ? [{
|
|
3097
|
+
label: `更新所选中的 ${updateRoots} 个可更新插件…`,
|
|
3098
|
+
description: '使用已验证版本和现有精确位置',
|
|
3099
|
+
status: 'warning',
|
|
3100
|
+
run: () => openSelectedPluginTask('update', selectedIds),
|
|
3101
|
+
}] : []),
|
|
3102
|
+
...(restores.length > 0 ? [{
|
|
3103
|
+
label: `恢复所选中的 ${restoreRoots} 个插件…`,
|
|
3104
|
+
description: '按当前问题和现有精确位置准备方案',
|
|
3105
|
+
status: 'warning',
|
|
3106
|
+
run: () => openSelectedPluginTask('restore', selectedIds),
|
|
3107
|
+
}] : []),
|
|
3108
|
+
{ label: '移除所选插件…', description: `${uninstallTargets.length} 个现场目标;执行前检查所有权;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
|
|
3109
|
+
{
|
|
3110
|
+
label: '其他维护方式…',
|
|
3111
|
+
description: '同步、重新安装和普通清理',
|
|
3112
|
+
run: () => {
|
|
3113
|
+
setBatchDraft((current) => current ? { ...current, stage: 'maintenance' } : current);
|
|
3114
|
+
setFocus(0);
|
|
3115
|
+
setScrollOffset(0);
|
|
3116
|
+
},
|
|
3117
|
+
},
|
|
3118
|
+
{ label: '返回调整选择', run: () => setBatchDraft((current) => current ? { ...current, stage: 'select' } : current) },
|
|
3119
|
+
];
|
|
3120
|
+
}
|
|
3121
|
+
context = `${selectedIds.length} 个插件 · ${syncTargets.length} 个已保存或现场目标`;
|
|
3122
|
+
help = '↑↓ 移动 · Enter 选择 · Esc 返回修改';
|
|
2482
3123
|
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
2483
|
-
content =
|
|
2484
|
-
? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })
|
|
2485
|
-
: _jsx(Callout, { status: "warning", title: "\u6CA1\u6709\u53EF\u9009\u62E9\u7684\u63D2\u4EF6", description: "\u8FD4\u56DE\u63D2\u4EF6\u5217\u8868\u5E76\u68C0\u67E5\u6765\u6E90\u6216\u5B89\u88C5\u72B6\u6001\u3002" });
|
|
3124
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
2486
3125
|
}
|
|
2487
3126
|
}
|
|
2488
3127
|
else if (route.name === 'batchPlacement') {
|
|
@@ -2517,6 +3156,10 @@ export function ConsoleAppV4(props) {
|
|
|
2517
3156
|
disabled: batchDraft.selection === 'selected' && batchDraft.selectedPluginIds.length === 0
|
|
2518
3157
|
? '请至少选择一个插件' : undefined,
|
|
2519
3158
|
run: () => {
|
|
3159
|
+
if (invalidBatchSelection(batchDraft).length > 0) {
|
|
3160
|
+
setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
2520
3163
|
const intent = {
|
|
2521
3164
|
kind: 'pluginWriteBatch',
|
|
2522
3165
|
action: batchDraft.action,
|
|
@@ -2615,7 +3258,25 @@ export function ConsoleAppV4(props) {
|
|
|
2615
3258
|
return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
|
|
2616
3259
|
};
|
|
2617
3260
|
const previewMenu = dynamicMenu;
|
|
2618
|
-
|
|
3261
|
+
const advancedPreviewActions = previewMenu.map(previewAction);
|
|
3262
|
+
const primaryPreviewAction = advancedPreviewActions[0] ?? {
|
|
3263
|
+
label: '查看处理建议',
|
|
3264
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3265
|
+
};
|
|
3266
|
+
const previewSettingsAction = previewMenu.findIndex((action) => action.id === 'settings');
|
|
3267
|
+
actions = [
|
|
3268
|
+
primaryPreviewAction,
|
|
3269
|
+
...(previewSettingsAction > 0 ? [advancedPreviewActions[previewSettingsAction]] : [{
|
|
3270
|
+
label: '使用说明',
|
|
3271
|
+
description: '查看用途、支持平台和来源',
|
|
3272
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
3273
|
+
}]),
|
|
3274
|
+
{
|
|
3275
|
+
label: '更多操作…',
|
|
3276
|
+
description: '安装方式、依赖、通用 Skills、来源和诊断',
|
|
3277
|
+
run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
|
|
3278
|
+
},
|
|
3279
|
+
];
|
|
2619
3280
|
const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 2));
|
|
2620
3281
|
const dependencyGroups = ['direct', 'transitive'].map((relation) => ({
|
|
2621
3282
|
relation,
|
|
@@ -2806,9 +3467,6 @@ export function ConsoleAppV4(props) {
|
|
|
2806
3467
|
: overview?.dependents ?? [];
|
|
2807
3468
|
const dependencyCount = overview?.dependencies.length ?? 0;
|
|
2808
3469
|
const dependentCount = visibleDependents.length;
|
|
2809
|
-
const dependencyLimit = policy.mode === 'compact'
|
|
2810
|
-
? dependentCount > 0 ? 1 : 2
|
|
2811
|
-
: dependentCount > 0 ? 3 : 5;
|
|
2812
3470
|
const dependentLimit = responsiveBudget(2, 4);
|
|
2813
3471
|
const dependencyRows = (entries, limit, dependent = false, offset = 0) => {
|
|
2814
3472
|
const safeOffset = Math.min(offset, Math.max(0, entries.length - 1));
|
|
@@ -2843,62 +3501,136 @@ export function ConsoleAppV4(props) {
|
|
|
2843
3501
|
}
|
|
2844
3502
|
else {
|
|
2845
3503
|
breadcrumb = `${plugin.displayName} / 依赖关系`;
|
|
2846
|
-
|
|
3504
|
+
const direction = route.direction ?? 'dependencies';
|
|
3505
|
+
const projection = buildDependencyGraphProjection({
|
|
3506
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
3507
|
+
rootPluginId: plugin.pluginId,
|
|
3508
|
+
direction,
|
|
3509
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
3510
|
+
});
|
|
3511
|
+
const visibleRows = projection.rows;
|
|
3512
|
+
context = direction === 'dependencies' ? '它需要的' : '谁在使用它';
|
|
2847
3513
|
title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
|
|
2848
|
-
description =
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
3514
|
+
description = direction === 'dependencies'
|
|
3515
|
+
? '当前方向:它需要的。缩进表示上一层需要它。'
|
|
3516
|
+
: '当前方向:谁在使用它。只显示已知的实际引用。';
|
|
3517
|
+
const rowActions = visibleRows.map((row) => {
|
|
3518
|
+
const state = row.depth === 0
|
|
3519
|
+
? { value: statusText(plugin), status: plugin.aggregateStatus === 'needsAttention'
|
|
3520
|
+
? 'warning' : plugin.aggregateStatus === 'unavailable' ? 'error' : 'success' }
|
|
3521
|
+
: dependencyGraphRowStatus(row);
|
|
3522
|
+
const branch = row.depth === 0 ? ''
|
|
3523
|
+
: `${row.ancestorHasNextSibling.map((next) => next ? '│ ' : ' ').join('')}${row.lastChild ? '└─' : '├─'} `;
|
|
3524
|
+
const compactBranch = policy.mode === 'compact' && row.depth > 2 ? '… └─ ' : branch;
|
|
3525
|
+
return {
|
|
3526
|
+
label: row.depth === 0
|
|
3527
|
+
? `根插件 · ${row.label}(当前插件)`
|
|
3528
|
+
: `${compactBranch}${direction === 'dependencies' ? '必需依赖 · ' : '实际使用者 · '}${row.label}${row.sharedWithRowId ? ' ↗ [same]' : ''}`,
|
|
3529
|
+
description: row.hiddenDescendantCount > 0
|
|
3530
|
+
? `另有 ${row.hiddenDescendantCount} 个${direction === 'dependencies' ? '所需插件' : '实际使用者'}`
|
|
3531
|
+
: row.depth === 0 ? '当前插件' : undefined,
|
|
3532
|
+
meta: state.value,
|
|
3533
|
+
status: state.status,
|
|
3534
|
+
run: () => {
|
|
3535
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3536
|
+
go({
|
|
3537
|
+
name: 'pluginDependencyNode',
|
|
3538
|
+
pluginId: plugin.pluginId,
|
|
3539
|
+
nodePluginId: row.pluginId,
|
|
3540
|
+
direction,
|
|
3541
|
+
});
|
|
3542
|
+
},
|
|
2871
3543
|
};
|
|
2872
|
-
return priority(left) - priority(right);
|
|
2873
3544
|
});
|
|
2874
|
-
const
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3545
|
+
const unverifiedConsumerActions = direction === 'dependents'
|
|
3546
|
+
? projection.scopeSummary.unknownConsumerPluginIds.map((pluginId) => {
|
|
3547
|
+
const declared = overview?.dependents.find((entry) => entry.pluginId === pluginId);
|
|
3548
|
+
return {
|
|
3549
|
+
label: `未验证声明者 · ${declared?.displayName ?? pluginId}`,
|
|
3550
|
+
description: '目录声明它依赖当前插件,但当前没有实际引用证据',
|
|
3551
|
+
meta: declared?.aggregateStatus === 'notInstalled' ? '未安装' : '未验证',
|
|
3552
|
+
status: 'muted',
|
|
3553
|
+
run: () => {
|
|
3554
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3555
|
+
go({
|
|
3556
|
+
name: 'pluginDependencyNode',
|
|
3557
|
+
pluginId: plugin.pluginId,
|
|
3558
|
+
nodePluginId: pluginId,
|
|
3559
|
+
direction,
|
|
3560
|
+
});
|
|
3561
|
+
},
|
|
3562
|
+
};
|
|
3563
|
+
})
|
|
3564
|
+
: [];
|
|
3565
|
+
actions = [
|
|
3566
|
+
...rowActions,
|
|
3567
|
+
...unverifiedConsumerActions,
|
|
3568
|
+
{
|
|
3569
|
+
label: direction === 'dependencies' ? '切换到“谁在使用它”' : '切换到“它需要的”',
|
|
3570
|
+
description: '切换关系方向',
|
|
3571
|
+
run: () => {
|
|
3572
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), focus, scrollOffset);
|
|
3573
|
+
props.controller.replace({
|
|
3574
|
+
name: 'pluginDependencies',
|
|
3575
|
+
pluginId: plugin.pluginId,
|
|
3576
|
+
direction: direction === 'dependencies' ? 'dependents' : 'dependencies',
|
|
3577
|
+
});
|
|
3578
|
+
},
|
|
3579
|
+
},
|
|
3580
|
+
{ label: showTechnical ? '隐藏检查详情' : '查看检查详情', run: () => toggleTechnical() },
|
|
2884
3581
|
];
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
3582
|
+
const scopeText = projection.scopeSummary.references.length > 0
|
|
3583
|
+
? projection.scopeSummary.references.map((entry) => `${platformLabel(entry.platform)} · ${scopeLabel(entry.scope)}`).join(';')
|
|
3584
|
+
: '当前加载范围;其他项目引用未检查';
|
|
3585
|
+
const relationViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, showTechnical ? responsiveBudget(3, 6) : responsiveBudget(7, 11));
|
|
3586
|
+
help = '↑↓ 选择 · → 展开 · ← 收起 · Enter 节点详情 · Esc 返回';
|
|
3587
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `当前检查范围 · ${scopeText}`, status: "muted", compact: true }), projection.status.state === 'invalid'
|
|
3588
|
+
? _jsx(Callout, { status: "error", title: "\u4F9D\u8D56\u5173\u7CFB\u65E0\u6548", description: projection.status.message }) : null, unverifiedConsumerActions.length > 0
|
|
3589
|
+
? _jsx(StaticRow, { title: `未验证声明者 · ${unverifiedConsumerActions.length} 个`, description: "\u4EE5\u4E0B\u58F0\u660E\u8005\u4E0D\u8BA1\u5165\u5B9E\u9645\u4F7F\u7528\u8005", status: "muted", compact: true })
|
|
3590
|
+
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: relationViewport.offset, limit: relationViewport.limit }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: `${dependencyCount} 个依赖 · ${projection.scopeSummary.actualConsumerPluginIds.length} 个实际使用者`, meta: plugin.pluginId, compact: true }), _jsx(StaticRow, { title: `未验证声明者 · ${projection.scopeSummary.unknownConsumerPluginIds.length} 个`, compact: true })] }) : null] });
|
|
2893
3591
|
}
|
|
2894
3592
|
}
|
|
3593
|
+
else if (route.name === 'pluginDependencyNode' && plugin) {
|
|
3594
|
+
const node = props.controller.getPluginOverview(route.nodePluginId);
|
|
3595
|
+
const overview = dependencyOverview(plugin);
|
|
3596
|
+
const item = route.nodePluginId === plugin.pluginId ? undefined
|
|
3597
|
+
: route.direction === 'dependencies'
|
|
3598
|
+
? overview?.dependencies.find((entry) => entry.pluginId === route.nodePluginId)
|
|
3599
|
+
: overview?.dependents.find((entry) => entry.pluginId === route.nodePluginId);
|
|
3600
|
+
const verifiedDependent = route.direction !== 'dependents'
|
|
3601
|
+
|| (item?.placementReferences?.length ?? 0) > 0;
|
|
3602
|
+
const state = item ? dependencyStatus(item) : undefined;
|
|
3603
|
+
breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId}`;
|
|
3604
|
+
context = route.direction === 'dependencies' ? '它需要的' : '谁在使用它';
|
|
3605
|
+
title = node?.displayName ?? route.nodePluginId;
|
|
3606
|
+
description = route.nodePluginId === plugin.pluginId
|
|
3607
|
+
? '这是当前关系图的根插件。'
|
|
3608
|
+
: route.direction === 'dependencies'
|
|
3609
|
+
? `${item?.requiredBy.map((entry) => entry.displayName).join('、') || plugin.displayName} 需要这个插件。`
|
|
3610
|
+
: verifiedDependent
|
|
3611
|
+
? `${node?.displayName ?? route.nodePluginId} 的已安装位置实际引用当前插件。`
|
|
3612
|
+
: `${item?.displayName ?? route.nodePluginId} 声明依赖当前插件,但当前没有实际引用证据。`;
|
|
3613
|
+
actions = [
|
|
3614
|
+
...(route.nodePluginId !== plugin.pluginId ? [{
|
|
3615
|
+
label: '以此节点查看关系',
|
|
3616
|
+
description: '打开局部子树;面包屑保留来路',
|
|
3617
|
+
run: () => go({
|
|
3618
|
+
name: 'pluginDependencies',
|
|
3619
|
+
pluginId: route.nodePluginId,
|
|
3620
|
+
direction: route.direction,
|
|
3621
|
+
}),
|
|
3622
|
+
}] : []),
|
|
3623
|
+
{ label: '返回关系图', run: back },
|
|
3624
|
+
];
|
|
3625
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u8282\u70B9\u72B6\u6001", children: [_jsx(StaticRow, { title: node?.displayName ?? route.nodePluginId, meta: state?.value ?? (node ? statusText(node) : '未检查'), status: state?.status ?? 'muted', compact: true }), _jsx(StaticRow, { title: route.nodePluginId, description: "canonical pluginId", status: "muted", compact: true }), item?.placementReferences?.slice(0, responsiveBudget(1, 3)).map((reference) => _jsx(StaticRow, { title: `${platformLabel(reference.consumer.platform)} · ${scopeLabel(reference.consumer.scope)}`, description: `${reference.consumer.pluginId} → ${reference.dependency.pluginId}`, compact: true }, `${reference.consumer.pluginId}:${reference.consumer.platform}:${reference.consumer.scope}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
3626
|
+
}
|
|
2895
3627
|
else if (route.name === 'installPlacement' && plugin) {
|
|
2896
3628
|
breadcrumb = `${plugin.displayName} / 添加`;
|
|
2897
3629
|
context = '第 1 步,共 2 步';
|
|
2898
3630
|
title = `你希望在哪里使用 ${plugin.displayName}?`;
|
|
2899
3631
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
2900
3632
|
description = hasDependencies
|
|
2901
|
-
? '
|
|
3633
|
+
? '这里只选择根插件位置。依赖将采用 planner 推荐位置,并在 Review 中显示。'
|
|
2902
3634
|
: '选择位置后,再选择一个或多个目标平台。';
|
|
2903
3635
|
const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
|
|
2904
3636
|
.some((entry) => entry.compatible));
|
|
@@ -2907,8 +3639,8 @@ export function ConsoleAppV4(props) {
|
|
|
2907
3639
|
label: scopeLabel(candidateScope),
|
|
2908
3640
|
selected: candidateScope === scope,
|
|
2909
3641
|
description: candidateScope === 'user'
|
|
2910
|
-
? hasDependencies ? '
|
|
2911
|
-
: hasDependencies ? '
|
|
3642
|
+
? hasDependencies ? '根插件在任意项目中可用;依赖位置自动计算' : '在任意项目中可使用,适合个人常用能力'
|
|
3643
|
+
: hasDependencies ? '根插件只在当前项目使用;Review 会说明更宽的依赖位置' : '仅在当前项目中使用,适合团队共享',
|
|
2912
3644
|
meta: candidateScope === 'user' ? '推荐' : undefined,
|
|
2913
3645
|
run: () => {
|
|
2914
3646
|
const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
|
|
@@ -2931,7 +3663,7 @@ export function ConsoleAppV4(props) {
|
|
|
2931
3663
|
},
|
|
2932
3664
|
})),
|
|
2933
3665
|
];
|
|
2934
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsx(SectionCard, { title: "\u4F9D\u8D56\u4F4D\u7F6E", children: _jsx(StaticRow, { title: `${plugin.dependencySummary?.totalCount ?? 0}
|
|
3666
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsx(SectionCard, { title: "\u4F9D\u8D56\u4F4D\u7F6E", children: _jsx(StaticRow, { title: `${plugin.dependencySummary?.totalCount ?? 0} 个依赖将采用推荐位置`, description: "Review \u4E2D\u53EF\u67E5\u770B\u6216\u8C03\u6574", status: "info" }) }) : null] });
|
|
2935
3667
|
}
|
|
2936
3668
|
else if (route.name === 'installTargets' && plugin) {
|
|
2937
3669
|
breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
|
|
@@ -2939,7 +3671,7 @@ export function ConsoleAppV4(props) {
|
|
|
2939
3671
|
title = '选择目标平台';
|
|
2940
3672
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
2941
3673
|
description = hasDependencies
|
|
2942
|
-
? '
|
|
3674
|
+
? '根插件必须支持所选位置;确认后会自动规划依赖并进入 Review。'
|
|
2943
3675
|
: '目标平台按所选位置验证;仅含平台共享内容的插件可同时启用两个位置。';
|
|
2944
3676
|
const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
|
|
2945
3677
|
const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
|
|
@@ -3075,93 +3807,428 @@ export function ConsoleAppV4(props) {
|
|
|
3075
3807
|
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
|
|
3076
3808
|
help = '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
3077
3809
|
}
|
|
3810
|
+
else if (route.name === 'pluginWriteTask' && plugin) {
|
|
3811
|
+
const retainedTargetKeys = route.targetKeys ? new Set(route.targetKeys) : undefined;
|
|
3812
|
+
const allTaskCandidates = props.controller.listPluginWriteTaskCandidates(route.goal);
|
|
3813
|
+
const currentTargetKeys = new Set(allTaskCandidates.map(pluginWriteTaskTargetKey));
|
|
3814
|
+
const missingTargetKeys = route.targetKeys?.filter((targetKey) => !currentTargetKeys.has(targetKey)) ?? [];
|
|
3815
|
+
const candidates = allTaskCandidates
|
|
3816
|
+
.filter((candidate) => retainedTargetKeys
|
|
3817
|
+
? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
|
|
3818
|
+
: candidate.target.pluginId === route.pluginId);
|
|
3819
|
+
const executable = candidates.filter((candidate) => candidate.eligible);
|
|
3820
|
+
const blockedCandidates = candidates.filter((candidate) => !candidate.eligible);
|
|
3821
|
+
const taskPluginIds = [...new Set(candidates.map((candidate) => candidate.target.pluginId))];
|
|
3822
|
+
const currentVersionEvidence = Object.assign({}, ...taskPluginIds.map((pluginId) => {
|
|
3823
|
+
const candidatePlugin = props.controller.getPluginOverview(pluginId);
|
|
3824
|
+
return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
|
|
3825
|
+
}));
|
|
3826
|
+
const versionEvidenceChanged = route.goal === 'update' && route.versionEvidence !== undefined
|
|
3827
|
+
&& (Object.entries(currentVersionEvidence).some(([key, version]) => route.versionEvidence?.[key] !== version)
|
|
3828
|
+
|| Object.keys(route.versionEvidence).some((key) => currentVersionEvidence[key] === undefined));
|
|
3829
|
+
const activationSources = [...new Set(blockedCandidates
|
|
3830
|
+
.filter((candidate) => candidate.blockReason === 'catalogActivationRequired')
|
|
3831
|
+
.map((candidate) => candidate.sourceId))];
|
|
3832
|
+
const taskIssues = new Map(props.controller.listUserIssues().map((issue) => [issue.issueId, issue]));
|
|
3833
|
+
const recoveryActions = [...new Map(blockedCandidates.flatMap((candidate) => {
|
|
3834
|
+
if (candidate.blockReason === 'catalogActivationRequired')
|
|
3835
|
+
return [];
|
|
3836
|
+
const target = candidate.target;
|
|
3837
|
+
const targetLabel = `${candidate.displayName} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
|
|
3838
|
+
const linkedIssue = candidate.issueIds.map((issueId) => taskIssues.get(issueId)).find(Boolean);
|
|
3839
|
+
const sourceIssue = linkedIssue && (linkedIssue.group === 'sourceRemoved'
|
|
3840
|
+
|| linkedIssue.group === 'sourceUnavailable')
|
|
3841
|
+
? linkedIssue
|
|
3842
|
+
: [...taskIssues.values()].find((entry) => (entry.category === 'sourceUnavailable' || entry.category === 'sourceMissing'
|
|
3843
|
+
|| entry.category === 'sourceIdentityChanged')
|
|
3844
|
+
&& (entry.sourceId === candidate.sourceId || entry.pluginId === target.pluginId));
|
|
3845
|
+
const issue = linkedIssue ?? sourceIssue;
|
|
3846
|
+
const candidatePlugin = props.controller.getPluginOverview(target.pluginId);
|
|
3847
|
+
const configField = issue?.requiredFields?.[0];
|
|
3848
|
+
if (issue?.group === 'config' || configField) {
|
|
3849
|
+
const fieldKey = configField?.key;
|
|
3850
|
+
return [[`config:${target.pluginId}:${fieldKey ?? ''}`, {
|
|
3851
|
+
label: fieldKey ? `完成设置:${configField?.title ?? fieldKey}` : `检查 ${candidate.displayName} 的设置`,
|
|
3852
|
+
description: `${targetLabel};保存后返回任务重新读取当前证据`,
|
|
3853
|
+
status: 'warning',
|
|
3854
|
+
run: () => {
|
|
3855
|
+
beginConfigFlow(target.pluginId);
|
|
3856
|
+
props.controller.replace({
|
|
3857
|
+
name: 'configLayer',
|
|
3858
|
+
pluginId: target.pluginId,
|
|
3859
|
+
...(fieldKey ? { fieldKey } : {}),
|
|
3860
|
+
});
|
|
3861
|
+
},
|
|
3862
|
+
}]];
|
|
3863
|
+
}
|
|
3864
|
+
if (candidate.blockReason === 'restoreVersionChanged') {
|
|
3865
|
+
const updateCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
3866
|
+
.filter((entry) => pluginWriteTaskTargetKey(entry) === pluginWriteTaskTargetKey(candidate));
|
|
3867
|
+
const updatePlugin = props.controller.getPluginOverview(target.pluginId);
|
|
3868
|
+
if (updateCandidates.length > 0 && updatePlugin) {
|
|
3869
|
+
return [[`update:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3870
|
+
label: `更新并恢复 ${targetLabel}…`,
|
|
3871
|
+
description: '改用当前候选版本建立新任务;进入 Review 前仍会重新检查证据',
|
|
3872
|
+
status: 'warning',
|
|
3873
|
+
run: () => props.controller.beginNestedPluginWriteTask({
|
|
3874
|
+
name: 'pluginWriteTask',
|
|
3875
|
+
pluginId: target.pluginId,
|
|
3876
|
+
goal: 'update',
|
|
3877
|
+
targetKeys: updateCandidates.map(pluginWriteTaskTargetKey),
|
|
3878
|
+
...(route.batchOrigin ? { batchOrigin: route.batchOrigin } : {}),
|
|
3879
|
+
versionEvidence: pluginWriteTaskVersionEvidence(updatePlugin, updateCandidates),
|
|
3880
|
+
}),
|
|
3881
|
+
}]];
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
if (sourceIssue || candidatePlugin?.sourceUnavailable) {
|
|
3885
|
+
const sourceId = sourceIssue?.sourceId ?? candidate.sourceId;
|
|
3886
|
+
const sourceNeedsIssue = sourceIssue?.category === 'sourceMissing'
|
|
3887
|
+
|| sourceIssue?.category === 'sourceIdentityChanged'
|
|
3888
|
+
|| sourceIssue?.group === 'sourceRemoved';
|
|
3889
|
+
if (sourceNeedsIssue && sourceIssue) {
|
|
3890
|
+
return [[`source-issue:${sourceIssue.issueId}`, {
|
|
3891
|
+
label: sourceIssue.category === 'sourceMissing'
|
|
3892
|
+
? `重新添加来源 ${sourceId}` : `核对来源身份 ${sourceId}`,
|
|
3893
|
+
description: `${targetLabel};先查看来源问题的安全恢复选择`,
|
|
3894
|
+
status: 'warning',
|
|
3895
|
+
run: () => go({ name: 'issueDetail', issueId: sourceIssue.issueId }),
|
|
3896
|
+
}]];
|
|
3897
|
+
}
|
|
3898
|
+
return [[`source:${sourceId}`, {
|
|
3899
|
+
label: `核对来源 ${sourceId}`,
|
|
3900
|
+
description: `${targetLabel};修复或重新检查后返回任务`,
|
|
3901
|
+
status: 'warning',
|
|
3902
|
+
run: () => go({ name: 'sourceDetail', sourceId }),
|
|
3903
|
+
}]];
|
|
3904
|
+
}
|
|
3905
|
+
if (candidate.blockReason === 'restoreVersionUnverified'
|
|
3906
|
+
|| candidate.blockReason === 'updateEvidenceIncomplete') {
|
|
3907
|
+
return [
|
|
3908
|
+
[`reload:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3909
|
+
label: `重新检查 ${targetLabel}`,
|
|
3910
|
+
description: '重新读取安装、版本、来源和受管内容证据',
|
|
3911
|
+
run: async () => {
|
|
3912
|
+
await props.controller.load();
|
|
3913
|
+
setFocus(0);
|
|
3914
|
+
setScrollOffset(0);
|
|
3915
|
+
},
|
|
3916
|
+
}],
|
|
3917
|
+
[`diagnostic:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3918
|
+
label: `查看 ${targetLabel} 的诊断`,
|
|
3919
|
+
description: '版本仍无法确认时查看组件状态和安全恢复方向',
|
|
3920
|
+
run: () => go({ name: 'pluginTechnical', pluginId: target.pluginId }),
|
|
3921
|
+
}],
|
|
3922
|
+
];
|
|
3923
|
+
}
|
|
3924
|
+
if (issue) {
|
|
3925
|
+
return [[`issue:${issue.issueId}`, {
|
|
3926
|
+
label: `处理问题:${issue.title}`,
|
|
3927
|
+
description: `${targetLabel};只提供该问题允许的恢复动作`,
|
|
3928
|
+
status: 'warning',
|
|
3929
|
+
run: () => go({ name: 'issueDetail', issueId: issue.issueId }),
|
|
3930
|
+
}]];
|
|
3931
|
+
}
|
|
3932
|
+
if (candidate.blockReason === 'targetUnavailable'
|
|
3933
|
+
|| candidate.blockReason === 'manualRecoveryRequired'
|
|
3934
|
+
|| candidate.blockReason === 'taskActionConflict') {
|
|
3935
|
+
return [[`maintenance:${pluginWriteTaskTargetKey(candidate)}`, {
|
|
3936
|
+
label: `维护 ${targetLabel}`,
|
|
3937
|
+
description: '检查这个精确平台位置可用的安全维护动作',
|
|
3938
|
+
status: 'warning',
|
|
3939
|
+
run: () => go({ name: 'platform', pluginId: target.pluginId, platform: target.platform }),
|
|
3940
|
+
}]];
|
|
3941
|
+
}
|
|
3942
|
+
return [[`diagnostic:${target.pluginId}`, {
|
|
3943
|
+
label: `查看 ${candidate.displayName} 的诊断`,
|
|
3944
|
+
description: `${pluginWriteTaskBlockLabel(candidate.blockReason)};当前没有可安全自动执行的动作`,
|
|
3945
|
+
run: () => go({ name: 'pluginTechnical', pluginId: target.pluginId }),
|
|
3946
|
+
}]];
|
|
3947
|
+
})).values()];
|
|
3948
|
+
const taskLabel = pluginWriteTaskLabel(route.goal);
|
|
3949
|
+
const startTaskReview = (executionPolicy) => {
|
|
3950
|
+
if (missingTargetKeys.length > 0) {
|
|
3951
|
+
setActionError(`${missingTargetKeys.length} 个冻结位置已失效或无需处理。请返回调整后重新开始。`);
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
props.controller.startPluginWriteTaskReview({
|
|
3955
|
+
kind: 'pluginWriteTask',
|
|
3956
|
+
goal: route.goal,
|
|
3957
|
+
targets: candidates.map((candidate) => candidate.target),
|
|
3958
|
+
executionPolicy,
|
|
3959
|
+
}, props.controller.pluginWriteTaskReturnRoute(route));
|
|
3960
|
+
setFocus(0);
|
|
3961
|
+
setScrollOffset(0);
|
|
3962
|
+
};
|
|
3963
|
+
breadcrumb = taskPluginIds.length > 1
|
|
3964
|
+
? `我的插件 / ${taskLabel} ${taskPluginIds.length} 个插件`
|
|
3965
|
+
: `我的插件 / ${plugin.displayName} / ${taskLabel}`;
|
|
3966
|
+
context = '只读准备';
|
|
3967
|
+
title = taskPluginIds.length > 1
|
|
3968
|
+
? `${taskLabel} ${taskPluginIds.length} 个插件`
|
|
3969
|
+
: `${taskLabel} ${plugin.displayName}`;
|
|
3970
|
+
description = route.goal === 'update'
|
|
3971
|
+
? '只使用已验证的插件版本和精确安装位置。来源目录不会在准备时自动启用。'
|
|
3972
|
+
: '根据当前问题选择最小恢复方法,不自动改版本、位置或来源。';
|
|
3973
|
+
help = candidates.length > (policy.mode === 'compact' ? 3 : 6)
|
|
3974
|
+
? 'PgUp/PgDn 查看全部位置 · ↑↓ 移动 · Enter 选择 · Esc 返回'
|
|
3975
|
+
: '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
3976
|
+
actions = [
|
|
3977
|
+
...(missingTargetKeys.length === 0 && !versionEvidenceChanged
|
|
3978
|
+
&& blockedCandidates.length === 0 && executable.length > 0 ? [{
|
|
3979
|
+
label: `确认${taskLabel} ${executable.length} 个位置…`,
|
|
3980
|
+
description: '进入统一影响确认;执行前再次检查当前证据',
|
|
3981
|
+
status: 'success',
|
|
3982
|
+
run: () => startTaskReview('all'),
|
|
3983
|
+
}] : []),
|
|
3984
|
+
...(missingTargetKeys.length === 0 && !versionEvidenceChanged
|
|
3985
|
+
&& blockedCandidates.length > 0 && executable.length > 0 ? [{
|
|
3986
|
+
label: `先处理可执行的 ${executable.length} 个位置…`,
|
|
3987
|
+
description: `${blockedCandidates.length} 个受阻位置保留,不会无声跳过`,
|
|
3988
|
+
status: 'warning',
|
|
3989
|
+
run: () => startTaskReview('executableSubset'),
|
|
3990
|
+
}] : []),
|
|
3991
|
+
...activationSources.map((sourceId) => ({
|
|
3992
|
+
label: `先更新来源目录 ${sourceId}…`,
|
|
3993
|
+
description: '先单独确认目录变更;启用完成后重新准备插件更新',
|
|
3994
|
+
status: 'warning',
|
|
3995
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId }),
|
|
3996
|
+
})),
|
|
3997
|
+
...recoveryActions,
|
|
3998
|
+
...(versionEvidenceChanged ? [{
|
|
3999
|
+
label: '使用当前版本重新准备…',
|
|
4000
|
+
description: '来源启用后的插件版本与先前候选不同;先明确采用当前版本',
|
|
4001
|
+
status: 'warning',
|
|
4002
|
+
run: () => {
|
|
4003
|
+
props.controller.replace({ ...route, versionEvidence: currentVersionEvidence });
|
|
4004
|
+
setFocus(0);
|
|
4005
|
+
setScrollOffset(0);
|
|
4006
|
+
},
|
|
4007
|
+
}] : []),
|
|
4008
|
+
{ label: '返回插件详情', run: back },
|
|
4009
|
+
];
|
|
4010
|
+
const taskTargetLimit = policy.mode === 'compact' ? 3 : 6;
|
|
4011
|
+
const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
|
|
4012
|
+
const visibleTaskCandidates = candidates.slice(taskTargetOffset, taskTargetOffset + taskTargetLimit);
|
|
4013
|
+
const targetRows = visibleTaskCandidates.map((candidate) => {
|
|
4014
|
+
const target = candidate.target;
|
|
4015
|
+
const version = target.expectedUpdate
|
|
4016
|
+
? `${target.expectedUpdate.installedVersion} → ${target.expectedUpdate.activeVersion}`
|
|
4017
|
+
: '保持当前版本';
|
|
4018
|
+
return _jsx(StaticRow, { title: `${taskPluginIds.length > 1 ? `${candidate.displayName} · ${candidate.sourceId} · ` : ''}${platformLabel(target.platform)} · ${scopeLabel(target.scope)}${!candidate.eligible && policy.mode === 'compact'
|
|
4019
|
+
? ` · ${pluginWriteTaskBlockLabel(candidate.blockReason)}` : ''}`, description: candidate.eligible
|
|
4020
|
+
? pluginWriteTaskMethodLabel(candidate.recommendedMethod)
|
|
4021
|
+
: pluginWriteTaskBlockLabel(candidate.blockReason), meta: candidate.eligible ? version : '需要决定', status: candidate.eligible ? 'success' : 'warning', compact: policy.mode === 'compact' }, `${target.pluginId}:${target.platform}:${target.scope}`);
|
|
4022
|
+
});
|
|
4023
|
+
const taskViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(Math.min(candidates.length, policy.mode === 'compact' ? 3 : 6) + 2));
|
|
4024
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5904\u7406\u8303\u56F4", meta: `${executable.length} 个可执行 · ${blockedCandidates.length} 个需决定`, compact: policy.mode === 'compact', children: [taskTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskTargetOffset} 个位置`, status: "muted", compact: true }) : null, targetRows.length > 0 ? targetRows
|
|
4025
|
+
: _jsx(StaticRow, { title: "\u5F53\u524D\u6CA1\u6709\u53EF\u6267\u884C\u76EE\u6807", status: "muted", compact: true }), candidates.length > taskTargetOffset + targetRows.length
|
|
4026
|
+
? _jsx(StaticRow, { title: `↓ 后面还有 ${candidates.length - taskTargetOffset - targetRows.length} 个位置`, status: "muted", compact: true }) : null] }), blockedCandidates.length > 0 ? _jsx(Callout, { status: "warning", title: `${blockedCandidates.length} 个位置需要先处理或决定`, description: executable.length > 0
|
|
4027
|
+
? '可以先处理可执行项,或进入下方对应入口处理受阻位置。'
|
|
4028
|
+
: '请进入下方对应入口处理;返回后会重新读取当前证据。', compact: policy.mode === 'compact' }) : null, missingTargetKeys.length > 0 ? _jsx(Callout, { status: "warning", title: `${missingTargetKeys.length} 个冻结位置已失效或无需处理`, description: "\u4E0D\u4F1A\u628A\u8FD9\u4E9B\u4F4D\u7F6E\u5F53\u4F5C\u5DF2\u5B8C\u6210\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u7F29\u5C0F\u672C\u6B21\u8303\u56F4\u3002\u8BF7\u8FD4\u56DE\u8C03\u6574\u9009\u62E9\u540E\u91CD\u65B0\u5F00\u59CB\u3002", compact: policy.mode === 'compact' }) : null, versionEvidenceChanged ? _jsx(Callout, { status: "warning", title: "\u63D2\u4EF6\u76EE\u6807\u7248\u672C\u5DF2\u53D8\u5316", description: "\u4E0D\u4F1A\u6CBF\u7528\u6765\u6E90\u542F\u7528\u524D\u7684\u7248\u672C\u8BC1\u636E\uFF0C\u4E5F\u4E0D\u4F1A\u81EA\u52A8\u6267\u884C\u65B0\u7248\u672C\u3002", compact: policy.mode === 'compact' }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: taskViewport.offset, limit: taskViewport.limit })] });
|
|
4029
|
+
}
|
|
3078
4030
|
else if (route.name === 'pluginDetail' && plugin) {
|
|
3079
4031
|
breadcrumb = `我的插件 / ${plugin.displayName}`;
|
|
3080
4032
|
context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
|
|
3081
4033
|
title = plugin.displayName;
|
|
3082
4034
|
description = plugin.description;
|
|
4035
|
+
const updateDescription = pluginUpdateDescription(plugin);
|
|
4036
|
+
const updateTaskCandidates = plugin.configOnly ? []
|
|
4037
|
+
: props.controller.listPluginWriteTaskCandidates('update')
|
|
4038
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
4039
|
+
const restoreTaskCandidates = plugin.configOnly ? []
|
|
4040
|
+
: props.controller.listPluginWriteTaskCandidates('restore')
|
|
4041
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
4042
|
+
const updateAction = updateTaskCandidates.length > 0 ? {
|
|
4043
|
+
label: updateTaskCandidates.length === 1 && updateTaskCandidates[0]?.target.expectedUpdate
|
|
4044
|
+
? `更新到 ${updateTaskCandidates[0].target.expectedUpdate.activeVersion}…`
|
|
4045
|
+
: updateTaskCandidates.some((candidate) => candidate.target.expectedUpdate)
|
|
4046
|
+
? `更新 ${plugin.displayName}…` : '准备插件更新…',
|
|
4047
|
+
description: updateTaskCandidates.some((candidate) => candidate.blockReason === 'catalogActivationRequired')
|
|
4048
|
+
? '需要先单独确认来源目录变更' : '使用精确版本和现有安装位置',
|
|
4049
|
+
status: 'warning',
|
|
4050
|
+
run: () => go({
|
|
4051
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
|
|
4052
|
+
targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
4053
|
+
versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
|
|
4054
|
+
}),
|
|
4055
|
+
} : undefined;
|
|
4056
|
+
const executableRestoreCandidates = restoreTaskCandidates.filter((candidate) => candidate.eligible);
|
|
4057
|
+
const restoreAction = executableRestoreCandidates.length > 0 ? {
|
|
4058
|
+
label: '恢复正常…',
|
|
4059
|
+
description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
|
|
4060
|
+
status: 'warning',
|
|
4061
|
+
run: () => go({
|
|
4062
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
|
|
4063
|
+
targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
4064
|
+
}),
|
|
4065
|
+
} : undefined;
|
|
4066
|
+
const settingsAction = plugin.configFieldCount > 0
|
|
4067
|
+
|| plugin.savedConfigScopes.length > 0 ? {
|
|
4068
|
+
label: plugin.configOnly ? '查看已保存设置' : '插件设置',
|
|
4069
|
+
description: plugin.configOnly
|
|
4070
|
+
? '检查已保存设置,并可重置记录中的设置层'
|
|
4071
|
+
: '查看当前实际生效的配置和值来源',
|
|
4072
|
+
meta: plugin.configFieldCount > 0 ? `${plugin.configFieldCount} 项配置` : undefined,
|
|
4073
|
+
run: () => {
|
|
4074
|
+
if (plugin.configOnly)
|
|
4075
|
+
props.controller.beginSavedConfigRecovery(plugin.pluginId);
|
|
4076
|
+
else
|
|
4077
|
+
beginConfigFlow(plugin.pluginId);
|
|
4078
|
+
setFocus(0);
|
|
4079
|
+
setScrollOffset(0);
|
|
4080
|
+
},
|
|
4081
|
+
} : undefined;
|
|
4082
|
+
const issueForPlugin = props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId || candidate.rootEntity?.pluginId === plugin.pluginId);
|
|
4083
|
+
const exceptionalAction = plugin.aggregateStatus === 'unavailable'
|
|
4084
|
+
? plugin.capabilityUnsupported && !plugin.sourceUnavailable ? {
|
|
4085
|
+
label: '需要升级 AW CLI',
|
|
4086
|
+
description: '查看缺少的功能和安全恢复方向',
|
|
4087
|
+
status: 'warning',
|
|
4088
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
4089
|
+
} : {
|
|
4090
|
+
label: '恢复插件来源…',
|
|
4091
|
+
description: '来源恢复后再重新检查插件状态',
|
|
4092
|
+
status: 'warning',
|
|
4093
|
+
run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
|
|
4094
|
+
}
|
|
4095
|
+
: plugin.aggregateStatus === 'needsAttention' && !restoreAction ? {
|
|
4096
|
+
label: '查看处理建议',
|
|
4097
|
+
description: '当前问题需要先确认原因或处理方式',
|
|
4098
|
+
status: 'warning',
|
|
4099
|
+
run: () => issueForPlugin
|
|
4100
|
+
? go({ name: 'issueDetail', issueId: issueForPlugin.issueId })
|
|
4101
|
+
: go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
4102
|
+
} : undefined;
|
|
4103
|
+
const primary = exceptionalAction ?? restoreAction ?? updateAction
|
|
4104
|
+
?? settingsAction ?? {
|
|
4105
|
+
label: '使用说明',
|
|
4106
|
+
description: '查看用途、支持平台和来源',
|
|
4107
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
4108
|
+
};
|
|
4109
|
+
actions = [
|
|
4110
|
+
primary,
|
|
4111
|
+
...(settingsAction && primary !== settingsAction ? [settingsAction] : [{
|
|
4112
|
+
label: '使用说明',
|
|
4113
|
+
description: '查看用途、支持平台和来源',
|
|
4114
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
4115
|
+
}]),
|
|
4116
|
+
{
|
|
4117
|
+
label: '更多操作…',
|
|
4118
|
+
description: '平台维护、依赖、通用 Skills、来源和诊断',
|
|
4119
|
+
run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
|
|
4120
|
+
},
|
|
4121
|
+
];
|
|
4122
|
+
const dependencyOverviewForDetail = dependencyOverview(plugin);
|
|
4123
|
+
const dependencyIssue = dependencyOverviewForDetail?.dependencies.find((entry) => entry.aggregateStatus !== 'ready');
|
|
4124
|
+
const dependencyDetailText = dependencyIssue
|
|
4125
|
+
? `${dependencyIssue.aggregateStatus === 'notInstalled' ? '缺少' : '异常'} ${dependencyIssue.displayName},${plugin.displayName} 暂时不可用。恢复会一并处理。`
|
|
4126
|
+
: dependencyOverviewForDetail && dependencyOverviewForDetail.summary.totalCount > 0
|
|
4127
|
+
? `自动使用 ${dependencyOverviewForDetail.summary.totalCount} 个所需插件,全部就绪。查看关系…`
|
|
4128
|
+
: undefined;
|
|
4129
|
+
const detailViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
|
|
4130
|
+
content = _jsxs(Box, { flexDirection: "column", children: [updateDescription ? _jsx(Callout, { status: "warning", title: statusText(plugin), description: updateDescription }) : null, dependencyDetailText ? _jsx(StaticRow, { title: dependencyDetailText, status: dependencyIssue ? 'warning' : 'success', compact: true }) : null, _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: detailViewport.offset, limit: detailViewport.limit }) })] });
|
|
4131
|
+
}
|
|
4132
|
+
else if (route.name === 'pluginMore' && plugin) {
|
|
4133
|
+
breadcrumb = `我的插件 / ${plugin.displayName} / 更多操作`;
|
|
4134
|
+
context = plugin.sourceLabel;
|
|
4135
|
+
title = '更多操作';
|
|
4136
|
+
description = '选择平台可使用同步、重新安装、移除和受限清理。';
|
|
3083
4137
|
const dependencySummary = plugin.dependencySummary;
|
|
3084
|
-
const detailMenu =
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
setScrollOffset(0);
|
|
3094
|
-
},
|
|
3095
|
-
};
|
|
3096
|
-
if (action.id === 'sourceProblem')
|
|
3097
|
-
return {
|
|
3098
|
-
label: '查看来源问题',
|
|
3099
|
-
description: '恢复来源后可重新读取字段定义;不会把此记录当作可安装插件。',
|
|
4138
|
+
const detailMenu = buildPluginDetailActions(plugin);
|
|
4139
|
+
const updateTaskCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
4140
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
4141
|
+
const restoreTaskCandidates = props.controller.listPluginWriteTaskCandidates('restore')
|
|
4142
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
4143
|
+
actions = [
|
|
4144
|
+
...(updateTaskCandidates.length > 0 ? [{
|
|
4145
|
+
label: '更新插件…',
|
|
4146
|
+
description: '使用精确版本和现有安装位置',
|
|
3100
4147
|
status: 'warning',
|
|
3101
|
-
run: () => go({
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
return {
|
|
3111
|
-
label: `保留${scopeLabel(action.keepScope)},移除${scopeLabel(action.scope)} root…`,
|
|
3112
|
-
description: action.description,
|
|
3113
|
-
status: 'error',
|
|
3114
|
-
run: () => beginScopeConflictRecovery(action),
|
|
3115
|
-
};
|
|
3116
|
-
if (action.id === 'platform' || action.id === 'scopeConflict') {
|
|
3117
|
-
const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
|
|
3118
|
-
const projected = placementStatus(placement);
|
|
3119
|
-
return {
|
|
3120
|
-
label: platformLabel(action.platform),
|
|
3121
|
-
description: projected.description,
|
|
3122
|
-
meta: projected.value,
|
|
3123
|
-
status: projected.status,
|
|
3124
|
-
run: () => action.id === 'scopeConflict'
|
|
3125
|
-
? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
|
|
3126
|
-
: go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
|
|
3127
|
-
};
|
|
3128
|
-
}
|
|
3129
|
-
if (action.id === 'dependencies')
|
|
3130
|
-
return {
|
|
3131
|
-
label: '必需依赖',
|
|
3132
|
-
description: dependencySummary.issueCount > 0 ? `${dependencySummary.issueCount} 项需要处理` : '查看直接、间接依赖和被使用情况',
|
|
3133
|
-
meta: dependencySummaryText(dependencySummary),
|
|
3134
|
-
status: dependencySummary.issueCount > 0 ? 'warning' : undefined,
|
|
3135
|
-
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
3136
|
-
};
|
|
3137
|
-
if (action.id === 'portableSkills')
|
|
3138
|
-
return {
|
|
3139
|
-
label: '通用 Skills',
|
|
3140
|
-
description: '选择 Skill 和 Agent 后交给 Skills CLI 安装',
|
|
3141
|
-
meta: dependencySummary ? `可安装 · ${dependencySummary.totalCount} 个插件依赖` : '可安装',
|
|
3142
|
-
run: () => beginPortable(plugin.pluginId),
|
|
3143
|
-
};
|
|
3144
|
-
if (action.id === 'settings')
|
|
3145
|
-
return { label: '设置', description: '查看当前实际生效的配置和值来源', meta: `${plugin.configFieldCount} 项配置`, run: () => beginConfigFlow(plugin.pluginId) };
|
|
3146
|
-
if (action.id === 'upgradeAwCli')
|
|
3147
|
-
return {
|
|
3148
|
-
label: '升级 AW CLI',
|
|
3149
|
-
description: '升级到支持此插件的 AW CLI 版本后重试',
|
|
4148
|
+
run: () => go({
|
|
4149
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
|
|
4150
|
+
targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
4151
|
+
versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
|
|
4152
|
+
}),
|
|
4153
|
+
}] : []),
|
|
4154
|
+
...(restoreTaskCandidates.length > 0 ? [{
|
|
4155
|
+
label: '恢复正常…',
|
|
4156
|
+
description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
|
|
3150
4157
|
status: 'warning',
|
|
3151
|
-
run: () => go({
|
|
3152
|
-
|
|
3153
|
-
|
|
4158
|
+
run: () => go({
|
|
4159
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
|
|
4160
|
+
targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
4161
|
+
}),
|
|
4162
|
+
}] : []),
|
|
4163
|
+
...detailMenu.filter((action) => action.id !== 'refreshAndSync' && action.id !== 'about')
|
|
4164
|
+
.map((action) => {
|
|
4165
|
+
if (action.id === 'platform' || action.id === 'scopeConflict') {
|
|
4166
|
+
const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
|
|
4167
|
+
const projected = placementStatus(placement);
|
|
4168
|
+
return {
|
|
4169
|
+
label: `${platformLabel(action.platform)} 平台与维护`,
|
|
4170
|
+
description: `${projected.description};同步、重装、移除和清理入口`,
|
|
4171
|
+
meta: projected.value,
|
|
4172
|
+
status: projected.status,
|
|
4173
|
+
run: () => action.id === 'scopeConflict'
|
|
4174
|
+
? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
|
|
4175
|
+
: go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
if (action.id === 'resolveScopeConflict')
|
|
4179
|
+
return {
|
|
4180
|
+
label: `处理 ${platformLabel(action.platform)} 位置冲突…`,
|
|
4181
|
+
description: action.description,
|
|
4182
|
+
status: 'error',
|
|
4183
|
+
run: () => beginScopeConflictRecovery(action),
|
|
4184
|
+
};
|
|
4185
|
+
if (action.id === 'dependencies')
|
|
4186
|
+
return {
|
|
4187
|
+
label: '依赖与被使用情况',
|
|
4188
|
+
description: dependencySummary?.issueCount ? `${dependencySummary.issueCount} 项需要处理` : '查看完整关系',
|
|
4189
|
+
meta: dependencySummary ? dependencySummaryText(dependencySummary) : undefined,
|
|
4190
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
4191
|
+
};
|
|
4192
|
+
if (action.id === 'portableSkills')
|
|
4193
|
+
return {
|
|
4194
|
+
label: '在其他 Agent 使用',
|
|
4195
|
+
description: '通过 Skills CLI 选择和安装通用 Skills',
|
|
4196
|
+
run: () => beginPortable(plugin.pluginId),
|
|
4197
|
+
};
|
|
4198
|
+
if (action.id === 'settings')
|
|
4199
|
+
return {
|
|
4200
|
+
label: plugin.configOnly ? '查看已保存设置' : '插件设置',
|
|
4201
|
+
run: () => plugin.configOnly
|
|
4202
|
+
? props.controller.beginSavedConfigRecovery(plugin.pluginId)
|
|
4203
|
+
: beginConfigFlow(plugin.pluginId),
|
|
4204
|
+
};
|
|
4205
|
+
if (action.id === 'sourceProblem')
|
|
4206
|
+
return {
|
|
4207
|
+
label: '恢复插件来源…', status: 'warning',
|
|
4208
|
+
run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
|
|
4209
|
+
};
|
|
4210
|
+
if (action.id === 'upgradeAwCli' || action.id === 'capabilityTechnical')
|
|
4211
|
+
return {
|
|
4212
|
+
label: action.id === 'upgradeAwCli' ? '升级 AW CLI' : '查看功能支持详情',
|
|
4213
|
+
status: action.id === 'upgradeAwCli' ? 'warning' : undefined,
|
|
4214
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
4215
|
+
};
|
|
3154
4216
|
return {
|
|
3155
|
-
label: '
|
|
3156
|
-
description: '查看缺少的功能和安全恢复方向',
|
|
4217
|
+
label: '查看插件详情',
|
|
3157
4218
|
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3158
4219
|
};
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
4220
|
+
}),
|
|
4221
|
+
...(!detailMenu.some((action) => action.id === 'dependencies') ? [{
|
|
4222
|
+
label: '依赖与被使用情况',
|
|
4223
|
+
description: '查看必需依赖和已知使用者;当前没有声明的必需依赖',
|
|
4224
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
4225
|
+
}] : []),
|
|
4226
|
+
{ label: '使用说明', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
|
|
4227
|
+
{ label: '查看插件来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) },
|
|
4228
|
+
{ label: '诊断与受管内容', description: '查看组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
|
|
4229
|
+
];
|
|
4230
|
+
const moreViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
4231
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: moreViewport.offset, limit: moreViewport.limit });
|
|
3165
4232
|
}
|
|
3166
4233
|
else if (route.name === 'platform' && plugin) {
|
|
3167
4234
|
const placement = props.controller.getPlatformPlacement(plugin.pluginId, route.platform);
|
|
@@ -3240,7 +4307,20 @@ export function ConsoleAppV4(props) {
|
|
|
3240
4307
|
run: () => beginScopeConflictRecovery(action),
|
|
3241
4308
|
};
|
|
3242
4309
|
if (action.id === 'refreshAndSync')
|
|
3243
|
-
return {
|
|
4310
|
+
return {
|
|
4311
|
+
label: `更新到 ${action.availableVersion}…`,
|
|
4312
|
+
description: '使用当前启用目录中的已确认版本',
|
|
4313
|
+
run: () => beginReview({
|
|
4314
|
+
kind: 'pluginWrite', action: 'sync', pluginId: plugin.pluginId,
|
|
4315
|
+
platform: action.platform, scope: action.scope,
|
|
4316
|
+
expectedUpdate: {
|
|
4317
|
+
installedVersion: action.installedVersion,
|
|
4318
|
+
activeVersion: action.availableVersion,
|
|
4319
|
+
sourceIdentity: action.sourceIdentity,
|
|
4320
|
+
generationKey: action.generationKey,
|
|
4321
|
+
},
|
|
4322
|
+
}),
|
|
4323
|
+
};
|
|
3244
4324
|
if (action.id === 'sync')
|
|
3245
4325
|
return {
|
|
3246
4326
|
label: '同步已保存的使用位置…',
|
|
@@ -3384,11 +4464,12 @@ export function ConsoleAppV4(props) {
|
|
|
3384
4464
|
}
|
|
3385
4465
|
else if (route.name === 'configLayer' && plugin) {
|
|
3386
4466
|
breadcrumb = `${plugin.displayName} / 设置`;
|
|
3387
|
-
const
|
|
3388
|
-
|
|
4467
|
+
const configLayerDefinitionPage = configPage ?? configLayerPages.user ?? configLayerPages.project;
|
|
4468
|
+
const selectedField = route.fieldKey && configLayerDefinitionPage
|
|
4469
|
+
? configLayerDefinitionPage.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
|
|
3389
4470
|
: undefined;
|
|
3390
|
-
const selectedPreset = route.presetId &&
|
|
3391
|
-
?
|
|
4471
|
+
const selectedPreset = route.presetId && configLayerDefinitionPage
|
|
4472
|
+
? configLayerDefinitionPage.presets.find((preset) => preset.id === route.presetId)
|
|
3392
4473
|
: undefined;
|
|
3393
4474
|
title = selectedField ? `修改“${selectedField.title}”`
|
|
3394
4475
|
: selectedPreset ? `应用“${selectedPreset.title}”` : `修改 ${plugin.displayName} 设置`;
|
|
@@ -3396,11 +4477,52 @@ export function ConsoleAppV4(props) {
|
|
|
3396
4477
|
? `当前:${effectiveFieldDisplay(selectedField)} · ${selectedField.effectiveSourceLabel}。选择保存位置后直接编辑。`
|
|
3397
4478
|
: selectedPreset ? '选择保存位置后,方案会应用到该位置的草稿。' : '选择修改保存到哪里。';
|
|
3398
4479
|
help = '↑↓ 移动 · Enter 打开 · Esc 返回';
|
|
4480
|
+
const canUseLayer = (scope) => {
|
|
4481
|
+
const page = configLayerPages[scope];
|
|
4482
|
+
if (!page) {
|
|
4483
|
+
if (scope === 'user')
|
|
4484
|
+
return true;
|
|
4485
|
+
if (selectedField)
|
|
4486
|
+
return !selectedField.sensitive && !selectedField.sharedNativeRender;
|
|
4487
|
+
if (selectedPreset && configLayerDefinitionPage) {
|
|
4488
|
+
const effectiveFields = configLayerDefinitionPage.groups.flatMap((group) => group.fields);
|
|
4489
|
+
return Object.keys(selectedPreset.values).some((key) => {
|
|
4490
|
+
const field = effectiveFields.find((candidate) => candidate.key === key);
|
|
4491
|
+
return Boolean(field && !field.sensitive && !field.sharedNativeRender);
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
return true;
|
|
4495
|
+
}
|
|
4496
|
+
const fields = page.groups.flatMap((group) => group.fields);
|
|
4497
|
+
if (route.fieldKey) {
|
|
4498
|
+
const field = fields.find((candidate) => candidate.key === route.fieldKey);
|
|
4499
|
+
return Boolean(field && (configFieldCanSet(field) || configFieldCanUnset(field)));
|
|
4500
|
+
}
|
|
4501
|
+
if (selectedPreset) {
|
|
4502
|
+
return Object.keys(selectedPreset.values).some((key) => {
|
|
4503
|
+
const field = fields.find((candidate) => candidate.key === key);
|
|
4504
|
+
return Boolean(field && configFieldCanSet(field));
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
return fields.some((field) => configFieldCanSet(field) || configFieldCanUnset(field));
|
|
4508
|
+
};
|
|
3399
4509
|
actions = [
|
|
3400
|
-
|
|
3401
|
-
|
|
4510
|
+
...(canUseLayer('user') ? [{
|
|
4511
|
+
label: '个人默认',
|
|
4512
|
+
description: '影响个人的所有项目;项目设置仍可覆盖',
|
|
4513
|
+
meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`,
|
|
4514
|
+
run: () => openConfigLayerTarget({ scope: 'user' }),
|
|
4515
|
+
}] : []),
|
|
4516
|
+
...(canUseLayer('project') ? [{
|
|
4517
|
+
label: '当前项目',
|
|
4518
|
+
description: '修改仅对当前项目生效;适合随仓库共享的约定',
|
|
4519
|
+
meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`,
|
|
4520
|
+
run: () => openConfigLayerTarget({ scope: 'project' }),
|
|
4521
|
+
}] : []),
|
|
3402
4522
|
];
|
|
3403
|
-
content =
|
|
4523
|
+
content = actions.length > 0
|
|
4524
|
+
? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' })
|
|
4525
|
+
: _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u4FDD\u5B58\u4F4D\u7F6E" });
|
|
3404
4526
|
}
|
|
3405
4527
|
else if (route.name === 'configFields' && plugin) {
|
|
3406
4528
|
const readonly = route.layer.scope === 'effective';
|
|
@@ -3561,19 +4683,43 @@ export function ConsoleAppV4(props) {
|
|
|
3561
4683
|
}
|
|
3562
4684
|
else if (route.name === 'configSave' && plugin && currentConfigPage) {
|
|
3563
4685
|
const changes = configChanges();
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
|
|
4686
|
+
const affectedTargets = configSaveImpact?.affectedTargets ?? [];
|
|
4687
|
+
const hasConsumers = configSaveImpact?.impactState === 'known' && affectedTargets.length > 0;
|
|
4688
|
+
const targetSummary = affectedTargets
|
|
4689
|
+
.map((target) => `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`)
|
|
4690
|
+
.join('、');
|
|
3567
4691
|
breadcrumb = `${plugin.displayName} / 设置 / 保存汇总`;
|
|
3568
4692
|
context = `${changes.length} 项未保存`;
|
|
3569
4693
|
title = `保存 ${changes.length} 项设置修改?`;
|
|
3570
|
-
description = '
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
4694
|
+
description = route.layer.scope === 'project'
|
|
4695
|
+
? '修改将保存到当前项目。'
|
|
4696
|
+
: '修改将保存为个人默认,并影响个人的所有项目;项目设置仍可覆盖。';
|
|
4697
|
+
actions = configSaveImpact ? [
|
|
4698
|
+
hasConsumers ? {
|
|
4699
|
+
label: '保存并生效',
|
|
4700
|
+
description: `保存设置,并重新应用:${targetSummary}`,
|
|
4701
|
+
status: 'success',
|
|
4702
|
+
run: () => beginConfigSave(true),
|
|
4703
|
+
} : {
|
|
4704
|
+
label: '保存设置',
|
|
4705
|
+
description: '当前修改没有需要重新应用的已安装目标',
|
|
4706
|
+
status: 'success',
|
|
4707
|
+
run: () => beginConfigSave(false),
|
|
4708
|
+
},
|
|
4709
|
+
...(hasConsumers ? [{
|
|
4710
|
+
label: '只保存,稍后应用',
|
|
4711
|
+
description: '保存所选设置层,不重新应用当前目标',
|
|
4712
|
+
run: () => beginConfigSave(false),
|
|
4713
|
+
}] : []),
|
|
3574
4714
|
{ label: '返回修改', run: back },
|
|
3575
|
-
];
|
|
3576
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u8BBE\u7F6E\u53D8\u66F4", compact: policy.mode === 'compact', children: changes.map((change) => _jsx(ReviewChangeRow, { title: `~ ${draftRemoves.includes(change.key) ? `删除 ${change.title} 中的值` : change.title}`, description: `${change.before} → ${change.after}`, compact: policy.mode === 'compact' }, change.key)) }), _jsxs(SectionCard, { title: "\u5E94\u7528\u5F71\u54CD", compact: policy.mode === 'compact', children: [
|
|
4715
|
+
] : [{ label: '返回修改', run: back }];
|
|
4716
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u8BBE\u7F6E\u53D8\u66F4", compact: policy.mode === 'compact', children: changes.map((change) => _jsx(ReviewChangeRow, { title: `~ ${draftRemoves.includes(change.key) ? `删除 ${change.title} 中的值` : change.title}`, description: `${change.before} → ${change.after}`, compact: policy.mode === 'compact' }, change.key)) }), _jsxs(SectionCard, { title: "\u5E94\u7528\u5F71\u54CD", compact: policy.mode === 'compact', children: [!configSaveImpact && !configSaveImpactError
|
|
4717
|
+
? _jsx(ProgressRow, { title: "\u5206\u6790\u5B9E\u9645\u914D\u7F6E\u6D88\u8D39\u8005", state: "active" })
|
|
4718
|
+
: hasConsumers
|
|
4719
|
+
? affectedTargets.map((target) => _jsx(StaticRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, meta: "\u9700\u8981\u91CD\u65B0\u5E94\u7528", status: "warning", compact: true }, `${target.platform}:${target.scope}`))
|
|
4720
|
+
: _jsx(StaticRow, { title: configSaveImpactError || configSaveImpact?.impactState === 'unknown'
|
|
4721
|
+
? '无法确认需要重新应用的目标;本次仅保存设置'
|
|
4722
|
+
: '没有需要重新应用的已安装目标', status: configSaveImpactError || configSaveImpact?.impactState === 'unknown' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u5176\u4ED6\u8BBE\u7F6E\u5C42\u4FDD\u6301\u4E0D\u53D8", status: "success", compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
3577
4723
|
}
|
|
3578
4724
|
else if (route.name === 'configFieldAction' && plugin && activeField) {
|
|
3579
4725
|
const layer = configLayerLabel(route.layer);
|
|
@@ -3880,6 +5026,217 @@ export function ConsoleAppV4(props) {
|
|
|
3880
5026
|
]), _jsx(StaticRow, { title: "stage", meta: safeDiagnosticCode(route.diagnosticStage, 'read-config'), compact: true })] }) : null, unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] })
|
|
3881
5027
|
: _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), unsupportedRows, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
3882
5028
|
}
|
|
5029
|
+
else if (route.name === 'catalogManagement') {
|
|
5030
|
+
breadcrumb = '插件来源 / 目录管理';
|
|
5031
|
+
title = '目录管理';
|
|
5032
|
+
description = '选择任务后才读取对应的本地状态。';
|
|
5033
|
+
actions = [
|
|
5034
|
+
{ label: 'Catalog 工作区', description: '选择、创建、查看状态、修改版本、构建和验证', run: () => go({ name: 'catalogWorkspace', view: 'overview' }) },
|
|
5035
|
+
{ label: '开发来源', description: '查看、启用或停用当前 Workspace 的开发来源', run: () => go({ name: 'catalogDev' }) },
|
|
5036
|
+
{ label: '本地存储', description: '查看保留项并预览可安全清理的空间', run: () => go({ name: 'catalogStore' }) },
|
|
5037
|
+
{ label: 'Catalog 发布', description: '准备计划、独立应用和恢复可信 pending 计划', run: () => go({ name: 'catalogPublish', step: 'overview' }) },
|
|
5038
|
+
];
|
|
5039
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(CatalogManagementIntro, {}), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
|
|
5040
|
+
}
|
|
5041
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'overview') {
|
|
5042
|
+
const model = catalogWorkspace ? {
|
|
5043
|
+
label: catalogWorkspace.label,
|
|
5044
|
+
state: catalogWorkspace.state,
|
|
5045
|
+
workspaceIdentity: catalogWorkspace.identity ?? undefined,
|
|
5046
|
+
catalogVersion: catalogWorkspace.catalog.version ?? undefined,
|
|
5047
|
+
channel: catalogWorkspace.catalog.channel ?? undefined,
|
|
5048
|
+
pluginCount: catalogWorkspace.plugins.length,
|
|
5049
|
+
snapshotState: catalogWorkspace.snapshot.state,
|
|
5050
|
+
gitState: !catalogWorkspace.git.checked ? '未检查'
|
|
5051
|
+
: !catalogWorkspace.git.hasHead ? '没有 HEAD'
|
|
5052
|
+
: catalogWorkspace.git.clean ? 'clean' : '有未提交变化',
|
|
5053
|
+
issues: catalogWorkspace.issues.map((issue) => issue.message),
|
|
5054
|
+
} : undefined;
|
|
5055
|
+
breadcrumb = '目录管理 / Catalog 工作区';
|
|
5056
|
+
title = 'Catalog 工作区';
|
|
5057
|
+
description = '状态读取不会 build、fetch、创建生成目录或运行插件代码。';
|
|
5058
|
+
actions = [
|
|
5059
|
+
{ label: '选择其他工作区…', description: '输入路径;界面只显示目录名', run: () => { setCatalogInput(''); go({ name: 'catalogWorkspace', view: 'select' }); } },
|
|
5060
|
+
...(catalogWorkspace?.state !== 'ready' ? [{
|
|
5061
|
+
label: '在所选目录创建工作区…', description: '输入 owner、显示名称和初始 Catalog 版本',
|
|
5062
|
+
run: () => { setCatalogInput(catalogWorkspaceDraft.ownerName); go({ name: 'catalogWorkspace', view: 'create', step: 'ownerName' }); },
|
|
5063
|
+
}] : []),
|
|
5064
|
+
...(catalogWorkspace?.state === 'ready' ? [
|
|
5065
|
+
{ label: '修改 Catalog 版本…', description: '选择 bump,或输入稳定版和精确 beta 版本', run: () => go({ name: 'catalogWorkspace', view: 'version' }) },
|
|
5066
|
+
...(catalogWorkspace.snapshot.state === 'absent' ? [{
|
|
5067
|
+
label: '构建 Catalog…', description: `bundle 并验证 ${catalogWorkspace.plugins.length} 个 canonical plugins;写入 .aw/catalog`,
|
|
5068
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild' }),
|
|
5069
|
+
}] : catalogWorkspace.snapshot.valid ? [{
|
|
5070
|
+
label: '重新构建 Catalog…', description: '替换已证明为生成物的 .aw/catalog;不删除其他内容',
|
|
5071
|
+
status: 'warning',
|
|
5072
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild', force: true }),
|
|
5073
|
+
}] : [{
|
|
5074
|
+
label: '生成目录无效,不能自动替换', description: '请检查问题并移开无法证明为生成物的目录后再构建',
|
|
5075
|
+
status: 'error', disabled: '现有生成目录未通过安全验证',
|
|
5076
|
+
}]),
|
|
5077
|
+
{ label: '完整验证生成目录', description: '只读验证现有 .aw/catalog,不重新构建', run: async () => {
|
|
5078
|
+
setCatalogLoading(true);
|
|
5079
|
+
setCatalogValidation(undefined);
|
|
5080
|
+
setCatalogInputError(undefined);
|
|
5081
|
+
try {
|
|
5082
|
+
setCatalogValidation(await props.controller.validateCatalogWorkspace());
|
|
5083
|
+
}
|
|
5084
|
+
catch (error) {
|
|
5085
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], []));
|
|
5086
|
+
}
|
|
5087
|
+
finally {
|
|
5088
|
+
setCatalogLoading(false);
|
|
5089
|
+
}
|
|
5090
|
+
} },
|
|
5091
|
+
] : []),
|
|
5092
|
+
];
|
|
5093
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
|
|
5094
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(CatalogWorkspaceSummary, { model: model, loading: catalogLoading }), catalogValidation ? _jsxs(SectionCard, { title: "\u5B8C\u6574\u9A8C\u8BC1\u7ED3\u679C", children: [_jsx(StaticRow, { title: catalogValidation.state === 'valid' ? '生成目录有效' : catalogValidation.state === 'absent' ? '生成目录不存在' : '生成目录无效', meta: catalogValidation.catalogVersion ?? undefined, status: catalogValidation.state === 'valid' ? 'success' : 'warning', compact: true }), catalogValidation.issues.map((issue) => _jsx(StaticRow, { title: issue.message, meta: issue.code, status: "warning", compact: true }, issue.code))] }) : null, catalogInputError ? _jsx(Callout, { status: "error", title: "\u65E0\u6CD5\u8BFB\u53D6\u5DE5\u4F5C\u533A", description: catalogInputError }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
|
|
5095
|
+
}
|
|
5096
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'select') {
|
|
5097
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 选择';
|
|
5098
|
+
title = '输入 Workspace 路径';
|
|
5099
|
+
description = '相对路径从当前目录解析;输入保持在本地,不写入配置。';
|
|
5100
|
+
help = '输入 · Enter 选择 · Ctrl-U 清空 · Esc 取消';
|
|
5101
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: privatePathInput(catalogInput), columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '留空可恢复从当前目录自动发现。', status: catalogInputError ? 'error' : undefined });
|
|
5102
|
+
}
|
|
5103
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'create') {
|
|
5104
|
+
const step = route.step ?? 'ownerName';
|
|
5105
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 创建';
|
|
5106
|
+
context = { ownerName: '第 1 步,共 4 步', ownerUrl: '第 2 步,共 4 步', displayName: '第 3 步,共 4 步', catalogVersion: '第 4 步,共 4 步' }[step];
|
|
5107
|
+
title = { ownerName: '输入 owner 名称', ownerUrl: '输入 owner URL', displayName: '输入 Catalog 显示名称', catalogVersion: '输入初始 Catalog 版本' }[step];
|
|
5108
|
+
description = step === 'catalogVersion' ? '允许稳定 x.y.z 或精确 x.y.z-beta.N。' : '所有字段会在创建确认中再次显示。';
|
|
5109
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
5110
|
+
const displayValue = step === 'ownerUrl'
|
|
5111
|
+
? catalogInput ? `已输入 owner URL(${Array.from(catalogInput).length} 字符)` : ''
|
|
5112
|
+
: catalogInput;
|
|
5113
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: displayValue, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '按 Enter 继续。', status: catalogInputError ? 'error' : undefined });
|
|
5114
|
+
}
|
|
5115
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'version') {
|
|
5116
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 版本';
|
|
5117
|
+
title = route.step === 'catalogVersion' ? '输入精确 Catalog 版本' : '修改 Catalog 版本';
|
|
5118
|
+
description = route.step === 'catalogVersion' ? '允许稳定 x.y.z 或精确 x.y.z-beta.N。' : `当前版本:${catalogWorkspace?.catalog.version ?? '未知'}`;
|
|
5119
|
+
if (route.step === 'catalogVersion') {
|
|
5120
|
+
help = '输入 · Enter 确认 · Ctrl-U 清空 · Esc 取消';
|
|
5121
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: catalogInput, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '此动作会进入独立确认。', status: catalogInputError ? 'error' : undefined });
|
|
5122
|
+
}
|
|
5123
|
+
else {
|
|
5124
|
+
actions = [
|
|
5125
|
+
{ label: '提高 patch 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'patch' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
5126
|
+
{ label: '提高 minor 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'minor' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
5127
|
+
{ label: '提高 major 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'major' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
5128
|
+
{ label: '输入精确版本…', description: '用于指定稳定版或精确 beta.N', run: () => { setCatalogInput(''); go({ name: 'catalogWorkspace', view: 'version', step: 'catalogVersion' }); } },
|
|
5129
|
+
];
|
|
5130
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
else if (route.name === 'catalogDev' && (!route.step || route.step === 'overview')) {
|
|
5134
|
+
const snapshotReady = catalogDev?.snapshot.state === 'current' && catalogDev.snapshot.valid;
|
|
5135
|
+
breadcrumb = '目录管理 / 开发来源';
|
|
5136
|
+
title = '开发来源';
|
|
5137
|
+
description = '状态只读取所选 Workspace、managed Path source 和 Active digest。';
|
|
5138
|
+
actions = [
|
|
5139
|
+
{ label: '设置开发来源 ID…', description: catalogDevSourceId || '使用安全默认值', run: () => { setCatalogInput(catalogDevSourceId); go({ name: 'catalogDev', step: 'sourceId' }); } },
|
|
5140
|
+
...!snapshotReady ? catalogDev?.snapshot.state === 'absent' ? [
|
|
5141
|
+
{ label: '先构建 Catalog…', description: '先完成独立构建确认;构建成功后返回此页再确认启用',
|
|
5142
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild' }, { name: 'catalogDev' }) },
|
|
5143
|
+
] : catalogDev?.snapshot.state === 'stale' && catalogDev.snapshot.valid ? [
|
|
5144
|
+
{ label: '先重新构建 Catalog…', description: '独立确认替换受管的旧 .aw/catalog;成功后返回此页再确认启用',
|
|
5145
|
+
status: 'warning',
|
|
5146
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild', force: true }, { name: 'catalogDev' }) },
|
|
5147
|
+
] : [
|
|
5148
|
+
{ label: '生成目录无效,不能自动替换', description: '请先在 Workspace 管理页检查问题;移开无法证明为生成物的目录后再构建',
|
|
5149
|
+
status: 'error', disabled: '现有生成目录未通过安全验证' },
|
|
5150
|
+
] : catalogDev?.source.exists ? [
|
|
5151
|
+
...!catalogDev.ready && snapshotReady ? [{
|
|
5152
|
+
label: '重新启用并同步开发来源…', description: '来源配置已存在;重新使用当前有效 snapshot 完成 sync 和 Active 切换',
|
|
5153
|
+
status: 'warning',
|
|
5154
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate',
|
|
5155
|
+
...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }, { name: 'catalogDev' }),
|
|
5156
|
+
}] : [],
|
|
5157
|
+
{ label: '停用开发来源…', description: '保留 .aw/catalog;检查安装、设置和 Marketplace 引用', run: () => beginReview({ kind: 'catalogManagement', action: 'devDeactivate' }) },
|
|
5158
|
+
{ label: '停用并选择替代主来源…', description: '开发来源为主来源时使用', run: () => { setCatalogInput(''); go({ name: 'catalogDev', step: 'replacementPrimary' }); } },
|
|
5159
|
+
] : [
|
|
5160
|
+
{ label: '启用开发来源…', description: '使用当前有效 snapshot 注册 managed Path source 并 sync', run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate', ...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }) },
|
|
5161
|
+
{ label: '启用并设为主来源…', description: '将来源启用和主来源变化放在同一影响确认中', run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate', primary: true, ...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }) },
|
|
5162
|
+
],
|
|
5163
|
+
];
|
|
5164
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: catalogDev?.ready ? 'success' : catalogDev?.state === 'needsAttention' ? 'warning' : 'info', title: catalogLoading ? '正在读取开发来源' : catalogDev?.ready ? '开发来源可用' : catalogDev?.state === 'inactive' ? '开发来源未启用' : '开发来源需要处理', children: catalogDev ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u751F\u6210\u76EE\u5F55", meta: catalogDev.snapshot.state, compact: true }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: catalogDev.source.id ?? '未注册', compact: true }), _jsx(StaticRow, { title: "Active", meta: catalogDev.active.ready ? '可用' : catalogDev.active.exists ? '需要处理' : '不存在', compact: true })] }) : null }), catalogInputError ? _jsx(Callout, { status: "error", title: "\u65E0\u6CD5\u8BFB\u53D6\u5F00\u53D1\u6765\u6E90", description: catalogInputError }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
5165
|
+
}
|
|
5166
|
+
else if (route.name === 'catalogDev') {
|
|
5167
|
+
const replacement = route.step === 'replacementPrimary';
|
|
5168
|
+
breadcrumb = '目录管理 / 开发来源 / 输入';
|
|
5169
|
+
title = replacement ? '输入替代主来源 ID' : '输入开发来源 ID';
|
|
5170
|
+
description = replacement ? '停用和主来源变化会进入同一次影响确认。' : '留空可使用安全默认 ID。';
|
|
5171
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
5172
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: catalogInput, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '按 Enter 继续。', status: catalogInputError ? 'error' : undefined });
|
|
5173
|
+
}
|
|
5174
|
+
else if (route.name === 'catalogStore') {
|
|
5175
|
+
const entries = catalogStore?.entries ?? [];
|
|
5176
|
+
const pageRows = responsiveBudget(3, 7);
|
|
5177
|
+
const entryOffset = Math.min(batchTargetOffset, Math.max(0, entries.length - 1));
|
|
5178
|
+
const entryEnd = Math.min(entries.length, entryOffset + pageRows);
|
|
5179
|
+
const reclaimable = entries.filter((entry) => entry.status === 'reclaimable');
|
|
5180
|
+
breadcrumb = '目录管理 / 本地存储';
|
|
5181
|
+
title = '本地 Catalog Store';
|
|
5182
|
+
description = '只清理已确认且执行时仍安全的弱状态;未知、foreign 和 Active 内容始终保留。';
|
|
5183
|
+
if (entries.length > pageRows)
|
|
5184
|
+
help = 'PgUp/PgDn 查看全部条目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5185
|
+
actions = [{
|
|
5186
|
+
label: '预览并清理可安全释放的空间…',
|
|
5187
|
+
description: `${reclaimable.length} 项;确认后新增候选不进入本次删除集合`,
|
|
5188
|
+
disabled: reclaimable.length ? undefined : '当前没有可安全清理的条目',
|
|
5189
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'storePrune', items: reclaimable.map((entry) => entry.item) }),
|
|
5190
|
+
}];
|
|
5191
|
+
content = _jsxs(Box, { flexDirection: "column", children: [catalogStore ? _jsx(CatalogStoreSummary, { reclaimableCount: catalogStore.totals.reclaimableEntries, reclaimableBytes: catalogStore.totals.reclaimableBytes ?? 0, retainedCount: catalogStore.totals.retainedEntries, entries: entries.map((entry) => ({ key: entry.itemKey, title: `${entry.category} · ${entry.itemKey}`, meta: `${entry.reason}${entry.reclaimableBytes === null ? '' : ` · ${entry.reclaimableBytes} bytes`}`, status: entry.status === 'error' ? 'unsafe' : entry.status === 'active' ? 'retained' : entry.status })), before: entryOffset, after: entryEnd }) : _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u672C\u5730 Catalog Store" }), catalogInputError ? _jsx(Callout, { status: "error", title: "\u65E0\u6CD5\u8BFB\u53D6 Store", description: catalogInputError }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
5192
|
+
}
|
|
5193
|
+
else if (route.name === 'catalogPublish' && route.step === 'overview') {
|
|
5194
|
+
const records = catalogPublication?.records ?? [];
|
|
5195
|
+
const pageRows = responsiveBudget(3, 7);
|
|
5196
|
+
const recordOffset = Math.min(batchTargetOffset, Math.max(0, records.length - 1));
|
|
5197
|
+
const recordEnd = Math.min(records.length, recordOffset + pageRows);
|
|
5198
|
+
breadcrumb = '目录管理 / Catalog 发布';
|
|
5199
|
+
title = 'Catalog 发布';
|
|
5200
|
+
description = '准备计划与应用计划是两个独立写入动作。';
|
|
5201
|
+
if (records.length > pageRows)
|
|
5202
|
+
help = 'PgUp/PgDn 查看全部记录 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5203
|
+
actions = [
|
|
5204
|
+
{ label: '准备新发布计划…', description: '下一步输入 remote 或 URL 与 branch;执行 plan 会写 version-scoped journal 和隔离仓库', run: () => { setCatalogInput(catalogPublishDraft.expectedPlanDigest ? '' : catalogPublishDraft.target); setCatalogPublishDraft((draft) => draft.expectedPlanDigest ? { targetKind: 'remote', target: '', branch: 'catalog' } : draft); go({ name: 'catalogPublish', step: 'target' }); } },
|
|
5205
|
+
...records.filter((record) => record.canApply && record.planDigest).map((record) => ({
|
|
5206
|
+
label: `${record.state === 'uncertain' ? '对账或继续' : '应用'} ${record.catalogVersion}…`,
|
|
5207
|
+
description: record.requiresRemoteInput
|
|
5208
|
+
? '重新输入原 remote 名称或 URL;只恢复现有可信 plan digest'
|
|
5209
|
+
: '只使用现有可信 plan digest;不会从当前源码重建计划',
|
|
5210
|
+
status: record.state === 'uncertain' ? 'warning' : undefined,
|
|
5211
|
+
run: () => record.requiresRemoteInput
|
|
5212
|
+
? (() => {
|
|
5213
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: record.branch ?? 'catalog', expectedPlanDigest: record.planDigest });
|
|
5214
|
+
setCatalogInput('');
|
|
5215
|
+
go({ name: 'catalogPublish', step: 'target' });
|
|
5216
|
+
})()
|
|
5217
|
+
: beginReview({ kind: 'catalogManagement', action: 'publishApply', expectedPlanDigest: record.planDigest, ...(record.remote ? { remote: record.remote } : {}), ...(record.branch ? { branch: record.branch } : {}) }, { name: 'catalogPublish', step: 'overview' }),
|
|
5218
|
+
})),
|
|
5219
|
+
...records.filter((record) => record.mustIncreaseVersion).map((record) => ({
|
|
5220
|
+
label: `${record.catalogVersion} 无法恢复`, description: 'journal 或 pending repository 缺失或损坏;请提高 Catalog 版本后创建新计划', status: 'error', disabled: '不能重建或复用此版本',
|
|
5221
|
+
})),
|
|
5222
|
+
];
|
|
5223
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
|
|
5224
|
+
content = _jsxs(Box, { flexDirection: "column", children: [catalogPublication ? _jsx(CatalogPublicationSummary, { records: records.map((record) => ({ key: `${record.catalogVersion}:${record.state}`, catalogVersion: record.catalogVersion, state: record.state, branch: record.branch ?? undefined, message: record.issue?.message })), before: recordOffset, after: recordEnd }) : _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u53D1\u5E03\u8BB0\u5F55" }), catalogInputError ? _jsx(Callout, { status: "error", title: "\u65E0\u6CD5\u8BFB\u53D6\u53D1\u5E03\u8BB0\u5F55", description: catalogInputError }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
|
|
5225
|
+
}
|
|
5226
|
+
else if (route.name === 'catalogPublish') {
|
|
5227
|
+
breadcrumb = '目录管理 / Catalog 发布 / 准备计划';
|
|
5228
|
+
title = route.step === 'target' ? '输入 remote 名称或 remote URL' : '输入发布 branch';
|
|
5229
|
+
description = route.step === 'target'
|
|
5230
|
+
? catalogPublishDraft.expectedPlanDigest
|
|
5231
|
+
? '重新输入原目标。后端会与现有 plan digest 中的目标身份核对。'
|
|
5232
|
+
: 'URL 只显示脱敏摘要;remote 名称按原样使用。'
|
|
5233
|
+
: '留空使用固定默认 branch:catalog。';
|
|
5234
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
5235
|
+
const displayValue = route.step === 'target'
|
|
5236
|
+
? catalogInput ? `已输入远程目标(${Array.from(catalogInput).length} 字符)` : ''
|
|
5237
|
+
: catalogInput;
|
|
5238
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: displayValue, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? (route.step === 'branch' ? '执行 plan 会写 publication journal 和隔离仓库;取消 Review 不写入。' : '按 Enter 继续。'), status: catalogInputError ? 'error' : undefined });
|
|
5239
|
+
}
|
|
3883
5240
|
else if (route.name === 'sourceList') {
|
|
3884
5241
|
breadcrumb = '插件来源';
|
|
3885
5242
|
title = '插件来源';
|
|
@@ -3912,9 +5269,14 @@ export function ConsoleAppV4(props) {
|
|
|
3912
5269
|
description: '并行检查各来源;结果不会自动启用待检查版本',
|
|
3913
5270
|
run: () => props.controller.recheckAllCatalogSources(),
|
|
3914
5271
|
},
|
|
5272
|
+
{
|
|
5273
|
+
label: '目录管理…',
|
|
5274
|
+
description: 'Catalog 工作区、开发来源、本地存储和发布',
|
|
5275
|
+
run: () => go({ name: 'catalogManagement' }),
|
|
5276
|
+
},
|
|
3915
5277
|
];
|
|
3916
5278
|
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
|
|
3917
|
-
const sourceCount = Math.max(0, actions.length -
|
|
5279
|
+
const sourceCount = Math.max(0, actions.length - 3);
|
|
3918
5280
|
content = _jsx(Box, { flexDirection: "column", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: focus < sourceCount ? 'section' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) });
|
|
3919
5281
|
}
|
|
3920
5282
|
else if (route.name === 'sourceDetail') {
|
|
@@ -4018,8 +5380,7 @@ export function ConsoleAppV4(props) {
|
|
|
4018
5380
|
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
4019
5381
|
run: () => props.controller.recheckCatalogSource(route.sourceId),
|
|
4020
5382
|
}];
|
|
4021
|
-
|
|
4022
|
-
...generationAction,
|
|
5383
|
+
const managementActions = [
|
|
4023
5384
|
{ label: '编辑来源…', description: '集中修改地址、引用、子目录和自动检查', run: () => openSourceEdit(route.sourceId) },
|
|
4024
5385
|
...(generationState === 'incompatible'
|
|
4025
5386
|
|| (freshness?.state === 'incompatible' && generationState !== 'ready') ? [] : [{
|
|
@@ -4058,10 +5419,101 @@ export function ConsoleAppV4(props) {
|
|
|
4058
5419
|
run: () => props.controller.fullValidateCatalogSource(route.sourceId),
|
|
4059
5420
|
},
|
|
4060
5421
|
];
|
|
5422
|
+
actions = [
|
|
5423
|
+
...generationAction.slice(0, 1),
|
|
5424
|
+
managementActions[0],
|
|
5425
|
+
{
|
|
5426
|
+
label: '更多处理方式…',
|
|
5427
|
+
description: '设为主来源、移除、精确修复、替换、重新检查和完整验证',
|
|
5428
|
+
run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
|
|
5429
|
+
},
|
|
5430
|
+
];
|
|
4061
5431
|
const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
|
|
4062
5432
|
const freshnessLabel = catalogSourceFreshnessLabel(freshness);
|
|
4063
5433
|
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5F53\u524D\u76EE\u5F55\u7248\u672C", status: generationState === 'incompatible' ? 'error' : activeVersion ? 'success' : 'warning', compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: activeVersion ? `版本 ${activeVersion}` : '未启用', meta: activeVersion ? generation?.active?.digestShort : catalogGenerationStateLabel(generationState), status: generationState === 'incompatible' ? 'error' : undefined, compact: true }), candidateVersion ? _jsx(StaticRow, { title: `待检查版本 ${candidateVersion}`, meta: freshnessLabel, status: freshness?.state === 'updateAvailable' || freshness?.state === 'localChangesAvailable' ? 'warning' : undefined, compact: true }) : _jsx(StaticRow, { title: freshnessLabel, status: freshness?.state === 'checkFailed' ? 'warning' : undefined, compact: true })] }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用', compact: true }), _jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录', compact: true }), _jsx(StaticRow, { title: "\u81EA\u52A8\u68C0\u67E5\u66F4\u65B0", meta: detail?.autoCheck === false ? '关闭' : '开启', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
|
|
4064
5434
|
}
|
|
5435
|
+
else if (route.name === 'sourceMore') {
|
|
5436
|
+
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
5437
|
+
const detail = overview.detail;
|
|
5438
|
+
const generation = props.controller.getCatalogGeneration(route.sourceId);
|
|
5439
|
+
const freshness = props.controller.getCatalogFreshness(route.sourceId);
|
|
5440
|
+
const generationState = generation?.state ?? (generation?.active ? 'ready' : 'unavailable');
|
|
5441
|
+
const combinedRepairAndMismatch = generationState === 'repairRequired'
|
|
5442
|
+
&& generation?.configMismatch === true;
|
|
5443
|
+
const logicalIdentityChanged = generation?.errorCode === 'catalogLogicalIdentityChanged';
|
|
5444
|
+
const activationBlocked = props.controller.catalogSourceRecoveryBlockReason();
|
|
5445
|
+
const blocked = activationBlocked ? `请先处理:${sourceOperationBlockLabel(activationBlocked)}` : undefined;
|
|
5446
|
+
breadcrumb = `插件来源 / ${route.sourceId} / 更多处理方式`;
|
|
5447
|
+
title = '管理来源';
|
|
5448
|
+
description = '选择精确操作。每个写入动作都会进入独立确认。';
|
|
5449
|
+
help = '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5450
|
+
actions = [
|
|
5451
|
+
...(generationState === 'repairRequired' ? [{
|
|
5452
|
+
label: '重新下载旧版本并精确修复',
|
|
5453
|
+
description: '使用当前启用版本的精确提交;成功后保持此版本',
|
|
5454
|
+
disabled: blocked,
|
|
5455
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'repair', sourceId: route.sourceId }),
|
|
5456
|
+
}, ...freshness?.state === 'incompatible' ? [] : [{
|
|
5457
|
+
label: '使用待检查版本替换损坏版本…',
|
|
5458
|
+
description: '旧版本无法取得时使用;这会进入独立风险确认',
|
|
5459
|
+
disabled: blocked,
|
|
5460
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'replaceDamaged', sourceId: route.sourceId }),
|
|
5461
|
+
}]] : []),
|
|
5462
|
+
...(generationState !== 'ready' && generationState !== 'repairRequired'
|
|
5463
|
+
&& generationState !== 'incompatible' && !logicalIdentityChanged ? [{
|
|
5464
|
+
label: generationState === 'stateInvalid' ? '恢复状态并启用'
|
|
5465
|
+
: generationState === 'configMismatch' ? '验证并切换' : '获取并启用',
|
|
5466
|
+
description: '先准备只读候选;确认后才切换当前目录版本',
|
|
5467
|
+
disabled: blocked,
|
|
5468
|
+
run: () => beginReview({
|
|
5469
|
+
kind: 'catalogGeneration',
|
|
5470
|
+
action: generationState === 'stateInvalid' ? 'recoverState'
|
|
5471
|
+
: generationState === 'configMismatch' ? 'update' : 'activate',
|
|
5472
|
+
sourceId: route.sourceId,
|
|
5473
|
+
}),
|
|
5474
|
+
}] : []),
|
|
5475
|
+
...(generationState === 'ready'
|
|
5476
|
+
&& (freshness?.state === 'updateAvailable' || freshness?.state === 'localChangesAvailable') ? [{
|
|
5477
|
+
label: freshness.state === 'localChangesAvailable' ? '应用本地内容变化…'
|
|
5478
|
+
: `更新到 ${freshness.candidate?.catalogVersion ?? '待检查版本'}…`,
|
|
5479
|
+
description: '确认前不会切换当前版本',
|
|
5480
|
+
disabled: blocked,
|
|
5481
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId: route.sourceId }),
|
|
5482
|
+
}] : []),
|
|
5483
|
+
{
|
|
5484
|
+
label: generationState === 'ready' ? '设为主来源…' : '启用并设为主来源…',
|
|
5485
|
+
description: generationState === 'ready' ? '只改变默认插件来源' : '启用已验证候选后改变默认来源',
|
|
5486
|
+
disabled: detail?.primary ? '当前已经是主来源'
|
|
5487
|
+
: logicalIdentityChanged ? '请编辑来源,或移除后重新添加' : blocked,
|
|
5488
|
+
run: () => generationState === 'ready'
|
|
5489
|
+
? beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId,
|
|
5490
|
+
changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] })
|
|
5491
|
+
: beginReview({
|
|
5492
|
+
kind: 'catalogGeneration',
|
|
5493
|
+
action: generationState === 'stateInvalid' ? 'recoverState'
|
|
5494
|
+
: generationState === 'repairRequired' && !combinedRepairAndMismatch ? 'repair'
|
|
5495
|
+
: generationState === 'configMismatch' || combinedRepairAndMismatch ? 'update' : 'activate',
|
|
5496
|
+
sourceId: route.sourceId,
|
|
5497
|
+
setPrimaryAfterActivation: true,
|
|
5498
|
+
}),
|
|
5499
|
+
},
|
|
5500
|
+
{ label: '移除来源…', description: '检查当前、历史和未知引用后再确认', status: 'error',
|
|
5501
|
+
disabled: detail?.primary ? '主来源不能直接移除' : undefined,
|
|
5502
|
+
run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId,
|
|
5503
|
+
changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
|
|
5504
|
+
{ label: freshness?.state === 'checking' ? '正在检查…' : '重新检查',
|
|
5505
|
+
description: '轻量检查这个来源;不会自动启用更新',
|
|
5506
|
+
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
5507
|
+
run: () => props.controller.recheckCatalogSource(route.sourceId) },
|
|
5508
|
+
{ label: '完整验证', description: '强制重新取得并完整验证这个来源',
|
|
5509
|
+
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
5510
|
+
run: () => props.controller.fullValidateCatalogSource(route.sourceId) },
|
|
5511
|
+
{ label: '技术详情', description: '查看脱敏错误代码和版本证据',
|
|
5512
|
+
run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
|
|
5513
|
+
];
|
|
5514
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
5515
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
5516
|
+
}
|
|
4065
5517
|
else if (route.name === 'sourceTechnical') {
|
|
4066
5518
|
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
4067
5519
|
const detail = overview.detail;
|
|
@@ -4097,16 +5549,14 @@ export function ConsoleAppV4(props) {
|
|
|
4097
5549
|
if (route.step === 'type') {
|
|
4098
5550
|
title = '选择来源类型';
|
|
4099
5551
|
description = '来源类型创建后不能修改。';
|
|
4100
|
-
const selectType = (type
|
|
4101
|
-
setSourceDraft({ ...sourceDraft, type
|
|
5552
|
+
const selectType = (type) => {
|
|
5553
|
+
setSourceDraft({ ...sourceDraft, type });
|
|
4102
5554
|
setDirty(true);
|
|
4103
5555
|
go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner });
|
|
4104
5556
|
};
|
|
4105
5557
|
actions = [
|
|
4106
|
-
{ label: 'Git 仓库', description: '
|
|
4107
|
-
{ label: '本地目录', description: '
|
|
4108
|
-
{ label: 'Git 仓库 · 关闭自动检查', description: '仍可从详情页手动检查', run: () => selectType('git', false) },
|
|
4109
|
-
{ label: '本地目录 · 关闭自动检查', description: '仍可从详情页手动检查', run: () => selectType('path', false) },
|
|
5558
|
+
{ label: 'Git 仓库', description: '使用现有自动检查默认值;可在修改汇总前单独更改', run: () => selectType('git') },
|
|
5559
|
+
{ label: '本地目录', description: '使用现有自动检查默认值;可在修改汇总前单独更改', run: () => selectType('path') },
|
|
4110
5560
|
];
|
|
4111
5561
|
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
4112
5562
|
}
|
|
@@ -4534,13 +5984,15 @@ export function ConsoleAppV4(props) {
|
|
|
4534
5984
|
}
|
|
4535
5985
|
else if (route.name === 'reviewPreparing' && snapshot.reviewPreparation) {
|
|
4536
5986
|
const preparation = snapshot.reviewPreparation;
|
|
4537
|
-
const labels = preparation.operationKind === '
|
|
4538
|
-
? ['
|
|
4539
|
-
: preparation.operationKind === '
|
|
4540
|
-
? ['
|
|
4541
|
-
: preparation.operationKind === '
|
|
4542
|
-
? ['
|
|
4543
|
-
:
|
|
5987
|
+
const labels = preparation.operationKind === 'pluginWriteTask'
|
|
5988
|
+
? ['插件任务', '正在准备任务影响确认']
|
|
5989
|
+
: preparation.operationKind === 'portableInstall'
|
|
5990
|
+
? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
|
|
5991
|
+
: preparation.operationKind === 'configSave' || preparation.operationKind === 'configReset'
|
|
5992
|
+
? ['设置', '正在准备设置确认']
|
|
5993
|
+
: preparation.operationKind === 'sourceMutation' || preparation.operationKind === 'catalogGeneration'
|
|
5994
|
+
? ['插件来源', '正在准备来源变更确认']
|
|
5995
|
+
: ['插件操作', '正在准备插件操作确认'];
|
|
4544
5996
|
breadcrumb = `${labels[0]} / 准备确认`;
|
|
4545
5997
|
context = '只读';
|
|
4546
5998
|
title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
|
|
@@ -4569,22 +6021,37 @@ export function ConsoleAppV4(props) {
|
|
|
4569
6021
|
}
|
|
4570
6022
|
else if (route.name === 'review' && snapshot.activeReview) {
|
|
4571
6023
|
const review = snapshot.activeReview;
|
|
6024
|
+
const installReviewPluginId = pendingReviewIntent?.kind === 'pluginWrite'
|
|
6025
|
+
&& pendingReviewIntent.action === 'install'
|
|
6026
|
+
? pendingReviewIntent.pluginId
|
|
6027
|
+
: review.pluginAction === 'install' ? snapshot.installPlacementDraft?.pluginId : undefined;
|
|
4572
6028
|
const batchReview = review.operationKind === 'pluginWriteBatch';
|
|
6029
|
+
const taskReview = review.operationKind === 'pluginWriteTask';
|
|
6030
|
+
const catalogManagementReview = review.operationKind === 'catalogManagement';
|
|
4573
6031
|
const sharedImpactConfirmed = !review.requiresConfirmation
|
|
4574
6032
|
|| confirmedSharedReviewToken === review.reviewToken;
|
|
4575
6033
|
breadcrumb = `${review.subject.name} / ${review.title}`;
|
|
4576
|
-
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall'
|
|
6034
|
+
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
|
|
6035
|
+
? review.title : `确认${review.title}`} ${review.subject.name}`;
|
|
4577
6036
|
description = snapshot.result?.state === 'stale'
|
|
4578
6037
|
? '当前状态已变化。请检查新的变化后再次确认。'
|
|
4579
6038
|
: review.requiresConfirmation ? '' : review.description;
|
|
4580
|
-
if (
|
|
6039
|
+
if (taskReview) {
|
|
6040
|
+
context = '精确目标 · 用户任务';
|
|
6041
|
+
help = 'PgUp/PgDn 查看全部影响 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
6042
|
+
}
|
|
6043
|
+
else if (batchReview) {
|
|
4581
6044
|
context = `${review.batch?.requestedPluginIds.length ?? 0} 个根插件 · ${review.batch?.groupCount ?? 0} 组`;
|
|
4582
6045
|
help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4583
6046
|
}
|
|
4584
6047
|
actions = [
|
|
4585
6048
|
...(review.requiresConfirmation ? [{
|
|
4586
|
-
label:
|
|
4587
|
-
|
|
6049
|
+
label: catalogManagementReview
|
|
6050
|
+
? sharedImpactConfirmed ? '已确认本次写入' : '确认本次写入'
|
|
6051
|
+
: sharedImpactConfirmed ? '已确认共享影响' : '确认共享影响',
|
|
6052
|
+
description: catalogManagementReview
|
|
6053
|
+
? '我已检查上方的精确写入、远端影响和保留项。'
|
|
6054
|
+
: review.sharedImpact.warning ?? '此操作会修改多个插件共同使用的 Codex 平台内容',
|
|
4588
6055
|
selected: sharedImpactConfirmed,
|
|
4589
6056
|
run: () => {
|
|
4590
6057
|
setConfirmedSharedReviewToken(review.reviewToken);
|
|
@@ -4602,6 +6069,19 @@ export function ConsoleAppV4(props) {
|
|
|
4602
6069
|
operationBusy.current = false;
|
|
4603
6070
|
});
|
|
4604
6071
|
} },
|
|
6072
|
+
...(installReviewPluginId
|
|
6073
|
+
&& (snapshot.installPlacementDraft?.targets.length ?? 0) > 0 ? [{
|
|
6074
|
+
label: '查看/调整依赖位置',
|
|
6075
|
+
description: '当前已采用 planner 推荐位置;调整后会重新建立 Review',
|
|
6076
|
+
run: () => {
|
|
6077
|
+
props.controller.push({
|
|
6078
|
+
name: 'dependencyPlacement',
|
|
6079
|
+
pluginId: installReviewPluginId,
|
|
6080
|
+
});
|
|
6081
|
+
setFocus(0);
|
|
6082
|
+
setScrollOffset(0);
|
|
6083
|
+
},
|
|
6084
|
+
}] : []),
|
|
4605
6085
|
...(sharedImpactConfirmed && (pendingReviewIntent?.kind === 'pluginWrite'
|
|
4606
6086
|
&& (pendingReviewIntent.action === 'install'
|
|
4607
6087
|
|| pendingReviewIntent.action === 'refreshAndSync'
|
|
@@ -4611,6 +6091,7 @@ export function ConsoleAppV4(props) {
|
|
|
4611
6091
|
&& (pendingReviewIntent.action === 'install'
|
|
4612
6092
|
|| pendingReviewIntent.action === 'sync'
|
|
4613
6093
|
|| pendingReviewIntent.action === 'reinstall')
|
|
6094
|
+
|| pendingReviewIntent?.kind === 'pluginWriteTask'
|
|
4614
6095
|
|| pendingReviewIntent?.kind === 'configSave' && pendingReviewIntent.apply) ? [{
|
|
4615
6096
|
label: '高级选项:脚本超时',
|
|
4616
6097
|
description: `${pendingReviewIntent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS} 毫秒`,
|
|
@@ -4736,13 +6217,26 @@ export function ConsoleAppV4(props) {
|
|
|
4736
6217
|
? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
|
|
4737
6218
|
});
|
|
4738
6219
|
const showEquivalentCommand = Boolean(review.equivalentCommand && showTechnical && !review.requiresConfirmation);
|
|
6220
|
+
const installDependencyTargets = installReviewPluginId
|
|
6221
|
+
? snapshot.installPlacementDraft?.targets ?? [] : [];
|
|
6222
|
+
const installDependencyPluginIds = new Set(installDependencyTargets.map((target) => target.pluginId));
|
|
6223
|
+
const reusedDependencyPluginIds = new Set(installDependencyTargets
|
|
6224
|
+
.filter((target) => target.action === 'keep')
|
|
6225
|
+
.map((target) => target.pluginId));
|
|
6226
|
+
const newDependencyCount = [...installDependencyPluginIds]
|
|
6227
|
+
.filter((pluginId) => !reusedDependencyPluginIds.has(pluginId)).length;
|
|
6228
|
+
const widenedDependencyCount = new Set(installDependencyTargets
|
|
6229
|
+
.filter((target) => target.affectsOtherProjects && target.action !== 'keep')
|
|
6230
|
+
.map((target) => target.pluginId)).size;
|
|
4739
6231
|
if (review.changes.length > staticRowBudget || showTechnical
|
|
4740
6232
|
&& (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
|
|
4741
6233
|
help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4742
6234
|
}
|
|
4743
|
-
content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.requiresConfirmation ? undefined : review.subject.name, meta: review.requiresConfirmation ? undefined : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title:
|
|
6235
|
+
content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.requiresConfirmation ? undefined : review.subject.name, meta: review.requiresConfirmation ? undefined : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: catalogManagementReview ? '! 需要确认本次写入' : '! 需要确认共享影响', meta: !catalogManagementReview && review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: catalogManagementReview ? review.description : review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
|
|
4744
6236
|
}
|
|
4745
6237
|
else if (route.name === 'progress') {
|
|
6238
|
+
const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
|
|
6239
|
+
const catalogManagementOperation = snapshot.activeReview?.operationKind === 'catalogManagement';
|
|
4746
6240
|
const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
|
|
4747
6241
|
const configOperation = snapshot.activeReview?.operationKind === 'configSave'
|
|
4748
6242
|
|| snapshot.activeReview?.operationKind === 'configReset';
|
|
@@ -4750,24 +6244,25 @@ export function ConsoleAppV4(props) {
|
|
|
4750
6244
|
|| snapshot.activeReview?.operationKind === 'catalogGeneration'
|
|
4751
6245
|
|| snapshot.activeReview?.operationKind === 'catalogSourcesRecovery';
|
|
4752
6246
|
const issueOperation = snapshot.activeReview?.originIssue;
|
|
4753
|
-
breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
|
|
6247
|
+
breadcrumb = catalogManagementOperation ? '目录管理 / 进度' : taskOperation ? '插件任务 / 进度' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
|
|
4754
6248
|
const configApply = snapshot.activeReview?.operationKind === 'configSave'
|
|
4755
6249
|
&& snapshot.activeReview.changes.some((change) => change.group === 'configImpact' && change.kind === 'change');
|
|
4756
6250
|
const catalogGenerationOperation = snapshot.activeReview?.operationKind === 'catalogGeneration';
|
|
4757
6251
|
const catalogSourcesRecovery = snapshot.activeReview?.operationKind === 'catalogSourcesRecovery';
|
|
4758
|
-
title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题'
|
|
6252
|
+
title = catalogManagementOperation ? '正在执行 Catalog 管理操作' : taskOperation ? '正在执行插件任务' : portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题'
|
|
4759
6253
|
: catalogGenerationOperation ? '正在验证并启用目录版本'
|
|
4760
6254
|
: catalogSourcesRecovery ? '正在恢复插件来源配置'
|
|
4761
6255
|
: sourceOperation ? '正在保存来源配置'
|
|
4762
6256
|
: configOperation ? configApply ? '正在保存并应用设置' : '正在保存设置'
|
|
4763
6257
|
: '正在执行插件操作';
|
|
4764
|
-
description =
|
|
4765
|
-
:
|
|
4766
|
-
:
|
|
4767
|
-
:
|
|
4768
|
-
:
|
|
4769
|
-
: '
|
|
4770
|
-
|
|
6258
|
+
description = catalogManagementOperation ? '正在执行已确认的精确 Workspace、Store、开发来源或发布操作。'
|
|
6259
|
+
: issueOperation ? '完成后会重新读取状态并检查问题。'
|
|
6260
|
+
: catalogGenerationOperation ? '正在验证并应用已确认的目录版本操作。'
|
|
6261
|
+
: catalogSourcesRecovery ? '正在备份损坏配置并恢复官方默认来源。'
|
|
6262
|
+
: sourceOperation ? '只原子保存来源配置,不访问网络。'
|
|
6263
|
+
: configOperation ? configApply ? '先保存所选层,再重新应用当前可见安装位置。' : '只保存所选设置层。'
|
|
6264
|
+
: '请勿关闭当前终端。';
|
|
6265
|
+
const batchProgress = snapshot.activeReview?.operationKind === 'pluginWriteBatch' || taskOperation;
|
|
4771
6266
|
const progress = snapshot.progress;
|
|
4772
6267
|
const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
|
|
4773
6268
|
?? (progress.startedAtMs === undefined ? 0 : Date.now() - progress.startedAtMs))) / 1_000));
|
|
@@ -4869,7 +6364,7 @@ export function ConsoleAppV4(props) {
|
|
|
4869
6364
|
return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
|
|
4870
6365
|
};
|
|
4871
6366
|
const compactPortableProgress = policy.mode === 'compact' && showTechnical;
|
|
4872
|
-
content = portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个来源或依赖插件提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [progressTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${progressTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleProgressTargets.map((entry) => {
|
|
6367
|
+
content = catalogManagementOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Catalog \u7BA1\u7406", children: [_jsx(ProgressRow, { title: "\u786E\u8BA4\u8BA1\u5212", meta: "\u5B8C\u6210", state: "complete" }), _jsx(ProgressRow, { title: currentPhase || '准备执行', description: progress.current?.target, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : currentPhase ? 'active' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5E76\u8BB0\u5F55\u7ED3\u679C", state: validationActive ? 'active' : 'pending' }), !showTechnical && latestMessage ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] }) : portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个来源或依赖插件提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [progressTargetOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${progressTargetOffset} 个目标`, status: "muted", compact: true }) : null, visibleProgressTargets.map((entry) => {
|
|
4873
6368
|
const state = progressLabel(entry);
|
|
4874
6369
|
const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
|
|
4875
6370
|
const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
|
|
@@ -4889,6 +6384,42 @@ export function ConsoleAppV4(props) {
|
|
|
4889
6384
|
: result.state === 'blocked' ? result.summary : undefined;
|
|
4890
6385
|
const action = resultSummary?.action;
|
|
4891
6386
|
const operationKind = 'operationKind' in result ? result.operationKind : undefined;
|
|
6387
|
+
const catalogManagementOperation = operationKind === 'catalogManagement';
|
|
6388
|
+
const catalogManagementResult = catalogManagementOperation
|
|
6389
|
+
? resultSummary?.catalogManagement : undefined;
|
|
6390
|
+
const taskOperation = operationKind === 'pluginWriteTask';
|
|
6391
|
+
const taskSummary = resultSummary?.task;
|
|
6392
|
+
const taskBlockedCount = taskSummary?.blockedTargets.length ?? 0;
|
|
6393
|
+
const frozenTaskReturnRoute = 'returnRoute' in result
|
|
6394
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
6395
|
+
? result.returnRoute : undefined;
|
|
6396
|
+
const nestedTaskReturn = frozenTaskReturnRoute !== undefined
|
|
6397
|
+
&& taskSummary !== undefined
|
|
6398
|
+
&& frozenTaskReturnRoute.goal !== taskSummary.goal;
|
|
6399
|
+
const taskBatchOrigin = frozenTaskReturnRoute?.batchOrigin;
|
|
6400
|
+
const remainingTaskTargets = taskSummary?.blockedTargets.map((entry) => entry.target) ?? [];
|
|
6401
|
+
const taskReturnRoute = nestedTaskReturn
|
|
6402
|
+
? frozenTaskReturnRoute
|
|
6403
|
+
: taskSummary && remainingTaskTargets[0] ? {
|
|
6404
|
+
name: 'pluginWriteTask',
|
|
6405
|
+
pluginId: remainingTaskTargets[0].pluginId,
|
|
6406
|
+
goal: taskSummary.goal,
|
|
6407
|
+
targetKeys: remainingTaskTargets.map((target) => [target.pluginId, target.platform, target.scope].join('\0')),
|
|
6408
|
+
...(taskBatchOrigin ? { batchOrigin: taskBatchOrigin } : {}),
|
|
6409
|
+
...(taskSummary.goal === 'update' ? {
|
|
6410
|
+
versionEvidence: 'returnRoute' in result
|
|
6411
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
6412
|
+
&& result.returnRoute.versionEvidence
|
|
6413
|
+
? result.returnRoute.versionEvidence
|
|
6414
|
+
: Object.fromEntries(remainingTaskTargets.flatMap((target) => target.expectedUpdate
|
|
6415
|
+
? [[[target.pluginId, target.platform, target.scope].join('\0'), JSON.stringify({
|
|
6416
|
+
version: target.expectedUpdate.activeVersion,
|
|
6417
|
+
sourceIdentity: target.expectedUpdate.sourceIdentity,
|
|
6418
|
+
binding: target.expectedUpdate.generationKey,
|
|
6419
|
+
})]]
|
|
6420
|
+
: [])),
|
|
6421
|
+
} : {}),
|
|
6422
|
+
} : undefined;
|
|
4892
6423
|
const batchOperation = operationKind === 'pluginWriteBatch';
|
|
4893
6424
|
const portableOperation = operationKind === 'portableInstall';
|
|
4894
6425
|
const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
|
|
@@ -4912,79 +6443,96 @@ export function ConsoleAppV4(props) {
|
|
|
4912
6443
|
cleanup: '✓ 插件清理完成',
|
|
4913
6444
|
sync: '✓ 插件同步完成',
|
|
4914
6445
|
}[action ?? 'install'];
|
|
4915
|
-
title =
|
|
4916
|
-
? blocked ? '×
|
|
4917
|
-
: partial ? '!
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
:
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
6446
|
+
title = catalogManagementOperation
|
|
6447
|
+
? blocked ? '× Catalog 管理操作未完成'
|
|
6448
|
+
: partial ? '! Catalog 管理操作只完成了一部分'
|
|
6449
|
+
: catalogManagementResult?.action === 'publishPlan' ? '✓ 发布计划已准备'
|
|
6450
|
+
: catalogManagementResult?.action === 'publishApply' ? '✓ Catalog 发布完成'
|
|
6451
|
+
: catalogManagementResult?.action === 'storePrune' ? '✓ Store 清理完成'
|
|
6452
|
+
: '✓ Catalog 管理操作完成'
|
|
6453
|
+
: taskOperation
|
|
6454
|
+
? blocked ? `× 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}没有完成`
|
|
6455
|
+
: partial ? `! 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}只完成了一部分`
|
|
6456
|
+
: `✓ 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}完成`
|
|
6457
|
+
: batchOperation
|
|
6458
|
+
? blocked ? '× 批量操作没有完成'
|
|
6459
|
+
: partial ? '! 批量操作只完成了一部分' : `✓ 批量${batchActionLabel(action)}完成`
|
|
6460
|
+
: portableOperation
|
|
6461
|
+
? blocked ? '× 通用 Skills 安装被阻止'
|
|
6462
|
+
: partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
|
|
6463
|
+
: blocked && result.writeMayHaveCompleted
|
|
6464
|
+
? '× 写入结果未确认'
|
|
6465
|
+
: issueOperation
|
|
6466
|
+
? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
|
|
6467
|
+
: sourceOperation
|
|
6468
|
+
? blocked ? '× 来源操作未完成'
|
|
6469
|
+
: operationKind === 'catalogSourcesRecovery' ? '✓ Catalog 来源配置已恢复'
|
|
6470
|
+
: resultSummary?.catalogAction === 'update'
|
|
6471
|
+
? resultSummary.catalogGeneration?.activeChanged
|
|
6472
|
+
&& resultSummary.catalogGeneration.outcome === 'activated'
|
|
6473
|
+
? '✓ Catalog 已更新' : '✓ Catalog 无需更新'
|
|
6474
|
+
: resultSummary?.catalogAction === 'repair' ? '✓ Catalog 已修复'
|
|
6475
|
+
: resultSummary?.catalogAction === 'replaceDamaged' ? '✓ 损坏的 Catalog 已替换'
|
|
6476
|
+
: resultSummary?.catalogAction === 'recoverState' ? '✓ Catalog 状态已恢复'
|
|
6477
|
+
: operationKind === 'catalogGeneration' ? '✓ Catalog 已启用'
|
|
6478
|
+
: '✓ 来源配置已保存'
|
|
6479
|
+
: configOperation
|
|
6480
|
+
? blocked
|
|
6481
|
+
? '× 设置未确认保存'
|
|
6482
|
+
: partial ? resultSummary?.configApplySkippedReason === 'invalidConfig'
|
|
6483
|
+
? '! 设置已保存,但未应用'
|
|
6484
|
+
: '! 设置已保存,但部分应用失败'
|
|
6485
|
+
: resultSummary?.configApplied ? '✓ 设置已保存并应用' : '✓ 设置已保存'
|
|
6486
|
+
: partial ? '! 插件操作只完成了一部分'
|
|
6487
|
+
: blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
|
|
6488
|
+
: completedTitle;
|
|
6489
|
+
description = catalogManagementOperation
|
|
6490
|
+
? blocked ? result.message : resultSummary?.message ?? 'Catalog 状态已更新。'
|
|
6491
|
+
: taskOperation
|
|
6492
|
+
? blocked ? '任务被安全阻止。已完成和未完成目标按实际结果保留。'
|
|
6493
|
+
: partial ? `${taskSummary?.retryTargets?.length ?? 0} 个失败目标可重试 · ${taskBlockedCount} 个目标需要决定。`
|
|
6494
|
+
: `${taskSummary?.rootResults.length ?? 0} 个根插件已完成。`
|
|
6495
|
+
: batchOperation
|
|
6496
|
+
? blocked
|
|
6497
|
+
? '批量写入被阻止。已完成和未完成目标按实际结果保留。'
|
|
6498
|
+
: partial
|
|
6499
|
+
? (resultSummary?.batch?.retryTargets?.length ?? 0) > 0
|
|
6500
|
+
? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个根插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
|
|
6501
|
+
: '失败目标无法精确定位。请查看本次记录后重新操作。'
|
|
6502
|
+
: `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个根插件已完成。`
|
|
6503
|
+
: portableOperation
|
|
4968
6504
|
? blocked
|
|
4969
|
-
? result.code === '
|
|
4970
|
-
? '
|
|
4971
|
-
: result.code === '
|
|
4972
|
-
? '
|
|
4973
|
-
:
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
?
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
6505
|
+
? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
|
|
6506
|
+
? 'Review 已过期,写入未开始。请返回修改并重新确认。'
|
|
6507
|
+
: result.code === 'operationReviewStale'
|
|
6508
|
+
? '安装证据已变化,写入被阻止。请返回修改并重新确认。'
|
|
6509
|
+
: result.writeMayHaveCompleted
|
|
6510
|
+
? '写入结果无法确认。请检查外部 Skills 状态后重试。'
|
|
6511
|
+
: '安装在写入前被阻止。请返回修改后重试。'
|
|
6512
|
+
: portableInstallFailed ? 'Skills CLI 安装失败,可能已有部分目标写入。可以修改 Agent 或使用原选择重试。' : partial ? '无法确认全部已选择 Skills。' : '所选 Skills 已通过安装验证。'
|
|
6513
|
+
: blocked && result.writeMayHaveCompleted
|
|
6514
|
+
? result.message
|
|
6515
|
+
: issueOperation
|
|
6516
|
+
? blocked
|
|
6517
|
+
? result.code === 'issueVerificationFailed'
|
|
6518
|
+
? '写入可能已完成,但当前无法确认。请重新检查状态。'
|
|
6519
|
+
: result.code === 'issueStillPresent'
|
|
6520
|
+
? '写入已完成,但重新检查后原问题仍然存在。'
|
|
6521
|
+
: '操作被安全阻止。可返回问题详情后重试。'
|
|
6522
|
+
: '状态已重新加载;原问题已确认消失。'
|
|
6523
|
+
: sourceOperation
|
|
6524
|
+
? blocked
|
|
6525
|
+
? result.writeMayHaveCompleted
|
|
6526
|
+
? '来源配置可能已保存。请重新检查来源列表。'
|
|
6527
|
+
: '来源操作未执行。草稿已保留,可返回修改。'
|
|
6528
|
+
: partial ? resultSummary?.message ?? '来源配置已保存,但 Catalog 尚未启用。可立即重试。'
|
|
6529
|
+
: resultSummary?.message ?? '来源配置已保存。'
|
|
6530
|
+
: blocked ? result.message
|
|
6531
|
+
: configOperation
|
|
6532
|
+
? partial
|
|
6533
|
+
? resultSummary?.message ?? '设置层已经保存;平台应用失败不会回滚设置。'
|
|
6534
|
+
: '设置层已经更新。'
|
|
6535
|
+
: '状态已刷新。';
|
|
4988
6536
|
const pluginId = resultSummary?.pluginId
|
|
4989
6537
|
?? ('pluginId' in result ? result.pluginId : undefined);
|
|
4990
6538
|
const configResultPluginId = pluginId ?? ('returnRoute' in result
|
|
@@ -5034,6 +6582,7 @@ export function ConsoleAppV4(props) {
|
|
|
5034
6582
|
const retryAction = failedPluginId && failedTarget && failedTargetState
|
|
5035
6583
|
? selectLifecycleRetryAction({
|
|
5036
6584
|
operationKind: operationKind === 'catalogSourcesRecovery' || operationKind === 'pluginWriteBatch'
|
|
6585
|
+
|| operationKind === 'pluginWriteTask' || operationKind === 'catalogManagement'
|
|
5037
6586
|
? undefined : operationKind,
|
|
5038
6587
|
originalAction: action,
|
|
5039
6588
|
target: {
|
|
@@ -5054,146 +6603,221 @@ export function ConsoleAppV4(props) {
|
|
|
5054
6603
|
&& (candidate.platform ?? failedIssue.platform) === failedTarget?.platform
|
|
5055
6604
|
&& (candidate.scope ?? failedIssue.scope) === failedTarget?.scope)
|
|
5056
6605
|
: undefined;
|
|
5057
|
-
|
|
6606
|
+
const preparedPublication = catalogManagementResult?.action === 'publishPlan'
|
|
6607
|
+
&& catalogManagementResult.planDigest && catalogManagementResult.publication
|
|
6608
|
+
? catalogManagementResult : undefined;
|
|
6609
|
+
actions = catalogManagementOperation
|
|
5058
6610
|
? [
|
|
5059
|
-
...(
|
|
5060
|
-
label: '
|
|
5061
|
-
description: '
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
6611
|
+
...(preparedPublication ? [{
|
|
6612
|
+
label: '应用此发布计划…',
|
|
6613
|
+
description: '进入独立确认;取消确认不会写入远端',
|
|
6614
|
+
status: 'success',
|
|
6615
|
+
run: () => beginReview({
|
|
6616
|
+
kind: 'catalogManagement',
|
|
6617
|
+
action: 'publishApply',
|
|
6618
|
+
expectedPlanDigest: preparedPublication.planDigest,
|
|
6619
|
+
...(catalogPublishDraft.target
|
|
6620
|
+
? catalogPublishDraft.targetKind === 'remote'
|
|
6621
|
+
? { remote: catalogPublishDraft.target }
|
|
6622
|
+
: { remoteUrl: catalogPublishDraft.target }
|
|
6623
|
+
: {}),
|
|
6624
|
+
branch: catalogPublishDraft.branch,
|
|
6625
|
+
}, { name: 'catalogPublish', step: 'overview' }),
|
|
5067
6626
|
}] : []),
|
|
5068
|
-
{ label: '
|
|
6627
|
+
{ label: catalogManagementResult?.action === 'storePrune' ? '返回本地存储'
|
|
6628
|
+
: catalogManagementResult?.action === 'devActivate' || catalogManagementResult?.action === 'devDeactivate' ? '返回开发来源'
|
|
6629
|
+
: catalogManagementResult?.action === 'publishPlan' || catalogManagementResult?.action === 'publishApply' ? '返回发布管理'
|
|
6630
|
+
: '返回 Catalog 工作区',
|
|
6631
|
+
run: () => leaveResult(catalogManagementResult?.action === 'storePrune'
|
|
6632
|
+
? { name: 'catalogStore' }
|
|
6633
|
+
: catalogManagementResult?.action === 'devActivate' || catalogManagementResult?.action === 'devDeactivate'
|
|
6634
|
+
? { name: 'catalogDev' }
|
|
6635
|
+
: catalogManagementResult?.action === 'publishPlan' || catalogManagementResult?.action === 'publishApply'
|
|
6636
|
+
? { name: 'catalogPublish', step: 'overview' }
|
|
6637
|
+
: 'returnRoute' in result && result.returnRoute ? result.returnRoute
|
|
6638
|
+
: { name: 'catalogManagement' }) },
|
|
5069
6639
|
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
5070
6640
|
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
5071
6641
|
]
|
|
5072
|
-
:
|
|
5073
|
-
?
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
{ label:
|
|
5104
|
-
|
|
5105
|
-
] : [
|
|
5106
|
-
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => {
|
|
5107
|
-
props.controller.discardPortableDraft();
|
|
5108
|
-
leaveResult();
|
|
5109
|
-
} },
|
|
5110
|
-
{ label: '再安装其他 Skills…', run: async () => {
|
|
5111
|
-
if (!pluginId)
|
|
5112
|
-
return;
|
|
5113
|
-
props.controller.resetPortableFlowToPreview(pluginId);
|
|
5114
|
-
await beginPortable(pluginId);
|
|
5115
|
-
} },
|
|
6642
|
+
: taskOperation
|
|
6643
|
+
? [
|
|
6644
|
+
...(nestedTaskReturn && taskReturnRoute ? [{
|
|
6645
|
+
label: '返回剩余恢复任务',
|
|
6646
|
+
description: '保留父任务的冻结目标;返回后重新读取当前证据',
|
|
6647
|
+
status: 'success',
|
|
6648
|
+
run: () => leaveResult(taskReturnRoute),
|
|
6649
|
+
}] : []),
|
|
6650
|
+
...((partial || blocked) && (taskSummary?.retryTargets?.length ?? 0) > 0 ? [{
|
|
6651
|
+
label: `继续处理剩余 ${taskSummary.retryTargets.length} 项…`,
|
|
6652
|
+
description: '重新检查失败的精确目标和必要依赖,并进入新的确认页',
|
|
6653
|
+
run: () => {
|
|
6654
|
+
props.controller.retryFailedPluginWriteTask();
|
|
6655
|
+
setFocus(0);
|
|
6656
|
+
setScrollOffset(0);
|
|
6657
|
+
},
|
|
6658
|
+
}] : []),
|
|
6659
|
+
...(taskBlockedCount > 0 && taskReturnRoute ? [{
|
|
6660
|
+
label: `返回任务处理 ${taskBlockedCount} 个需决定项`,
|
|
6661
|
+
description: '受阻目标没有加入失败重试;返回后可查看原因和可用的下一步',
|
|
6662
|
+
status: 'warning',
|
|
6663
|
+
run: () => leaveResult(taskReturnRoute),
|
|
6664
|
+
}] : []),
|
|
6665
|
+
...(!nestedTaskReturn ? [{ label: '完成,返回我的插件', run: () => {
|
|
6666
|
+
if (taskBatchOrigin?.owner === 'batch-write'
|
|
6667
|
+
&& taskBatchOrigin.flowId === batchDraft?.flowId) {
|
|
6668
|
+
setBatchDraft(undefined);
|
|
6669
|
+
setDirty(false);
|
|
6670
|
+
}
|
|
6671
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6672
|
+
} }] : []),
|
|
6673
|
+
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
6674
|
+
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
5116
6675
|
]
|
|
5117
|
-
:
|
|
5118
|
-
?
|
|
5119
|
-
? [
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
:
|
|
5130
|
-
|
|
5131
|
-
|
|
6676
|
+
: batchOperation
|
|
6677
|
+
? [
|
|
6678
|
+
...((partial || blocked) && (resultSummary?.batch?.retryTargets?.length ?? 0) > 0 ? [{
|
|
6679
|
+
label: '仅重试失败目标…',
|
|
6680
|
+
description: '重新读取失败根目标及其依赖,并进入新的确认页',
|
|
6681
|
+
run: () => {
|
|
6682
|
+
props.controller.retryFailedPluginWriteBatch();
|
|
6683
|
+
setFocus(0);
|
|
6684
|
+
setScrollOffset(0);
|
|
6685
|
+
},
|
|
6686
|
+
}] : []),
|
|
6687
|
+
{ label: '返回我的插件', run: () => leaveResult() },
|
|
6688
|
+
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
6689
|
+
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
6690
|
+
]
|
|
6691
|
+
: portableOperation
|
|
6692
|
+
? blocked || partial ? [
|
|
6693
|
+
{ label: blocked || partial ? '返回修改' : portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: async () => {
|
|
6694
|
+
if (!pluginId) {
|
|
6695
|
+
leaveResult();
|
|
6696
|
+
return;
|
|
6697
|
+
}
|
|
6698
|
+
if (!blocked && !partial) {
|
|
6699
|
+
props.controller.discardPortableDraft();
|
|
6700
|
+
leaveResult();
|
|
6701
|
+
return;
|
|
6702
|
+
}
|
|
6703
|
+
if (!await refreshPortableDraft(pluginId))
|
|
6704
|
+
return;
|
|
6705
|
+
if (portableInstallFailed && portableReceipt?.agentMode === 'explicit') {
|
|
6706
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableAgentInput', pluginId }, true);
|
|
6707
|
+
return;
|
|
6708
|
+
}
|
|
6709
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
|
|
6710
|
+
} },
|
|
6711
|
+
{ label: blocked || partial ? '重试' : '再安装其他 Skills…', run: async () => {
|
|
6712
|
+
if (!pluginId)
|
|
6713
|
+
return;
|
|
6714
|
+
if (blocked || partial) {
|
|
6715
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
|
|
6716
|
+
await beginPortableReview(pluginId, snapshot.portableDraft?.scope ?? 'project');
|
|
6717
|
+
return;
|
|
6718
|
+
}
|
|
6719
|
+
props.controller.resetPortableFlowToPreview(pluginId);
|
|
6720
|
+
await beginPortable(pluginId);
|
|
6721
|
+
} },
|
|
6722
|
+
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => { if (pluginId)
|
|
6723
|
+
props.controller.resetPortableFlowToPreview(pluginId); } },
|
|
6724
|
+
] : [
|
|
6725
|
+
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => {
|
|
6726
|
+
props.controller.discardPortableDraft();
|
|
6727
|
+
leaveResult();
|
|
6728
|
+
} },
|
|
6729
|
+
{ label: '再安装其他 Skills…', run: async () => {
|
|
6730
|
+
if (!pluginId)
|
|
6731
|
+
return;
|
|
6732
|
+
props.controller.resetPortableFlowToPreview(pluginId);
|
|
6733
|
+
await beginPortable(pluginId);
|
|
6734
|
+
} },
|
|
5132
6735
|
]
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
: configOperation && partial
|
|
5136
|
-
? [
|
|
5137
|
-
...(configRetryAvailable ? [{
|
|
5138
|
-
label: '重试应用',
|
|
5139
|
-
description: '先重新进入 Review,并重新采集当前证据',
|
|
5140
|
-
run: () => beginReview({
|
|
5141
|
-
kind: 'configSave',
|
|
5142
|
-
pluginId: configResultPluginId,
|
|
5143
|
-
scope: resultSummary.configScope,
|
|
5144
|
-
apply: true,
|
|
5145
|
-
retryApply: true,
|
|
5146
|
-
}),
|
|
5147
|
-
}] : []),
|
|
5148
|
-
{ label: invalidConfigResult ? '返回设置' : '返回修改', description: '重新打开已保存的设置层', run: () => leaveResult() },
|
|
5149
|
-
{ label: '返回设置删除字段或重置设置层', description: '显式删除顶层字段或整个设置层', run: () => leaveResult() },
|
|
5150
|
-
{ label: '保留设置并退出', run: () => requestExit({ code: 1, reason: 'partial' }) },
|
|
5151
|
-
]
|
|
5152
|
-
: configOperation && blocked
|
|
6736
|
+
: issueOperation && !dependencyRecovery
|
|
6737
|
+
? blocked || partial
|
|
5153
6738
|
? [
|
|
5154
|
-
{ label: '
|
|
6739
|
+
{ label: '返回问题详情', run: () => blocked
|
|
6740
|
+
? leaveResult()
|
|
6741
|
+
: failedIssue
|
|
6742
|
+
? leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId })
|
|
6743
|
+
: pluginId && failedTarget
|
|
6744
|
+
? leaveResult({ name: 'platform', pluginId, platform: failedTarget.platform })
|
|
6745
|
+
: leaveResult() },
|
|
6746
|
+
{ label: '重新检查问题', run: () => leaveResult({ name: 'issueList' }) },
|
|
6747
|
+
]
|
|
6748
|
+
: [
|
|
6749
|
+
{ label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
|
|
5155
6750
|
{ label: '返回首页', run: () => requestSessionNavigation('home') },
|
|
5156
6751
|
]
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
label: '
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
?
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
6752
|
+
: sourceOperation
|
|
6753
|
+
? [{
|
|
6754
|
+
label: !blocked && 'returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask'
|
|
6755
|
+
? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试' : '查看来源',
|
|
6756
|
+
run: () => blocked ? props.controller.resumeResultDraft() : leaveResult(),
|
|
6757
|
+
}, { label: '返回首页', run: () => requestSessionNavigation('home') }]
|
|
6758
|
+
: configOperation && partial
|
|
6759
|
+
? [
|
|
6760
|
+
...(configRetryAvailable ? [{
|
|
6761
|
+
label: '重试应用',
|
|
6762
|
+
description: '先重新进入 Review,并重新采集当前证据',
|
|
6763
|
+
run: () => beginReview({
|
|
6764
|
+
kind: 'configSave',
|
|
6765
|
+
pluginId: configResultPluginId,
|
|
6766
|
+
scope: resultSummary.configScope,
|
|
6767
|
+
apply: true,
|
|
6768
|
+
retryApply: true,
|
|
6769
|
+
}),
|
|
6770
|
+
}] : []),
|
|
6771
|
+
{ label: invalidConfigResult ? '返回设置' : '返回修改', description: '重新打开已保存的设置层', run: () => leaveResult() },
|
|
6772
|
+
{ label: '返回设置删除字段或重置设置层', description: '显式删除顶层字段或整个设置层', run: () => leaveResult() },
|
|
6773
|
+
{ label: '保留设置并退出', run: () => requestExit({ code: 1, reason: 'partial' }) },
|
|
6774
|
+
]
|
|
6775
|
+
: configOperation && blocked
|
|
6776
|
+
? [
|
|
6777
|
+
{ label: '返回设置', run: () => props.controller.resumeResultDraft() },
|
|
6778
|
+
{ label: '返回首页', run: () => requestSessionNavigation('home') },
|
|
6779
|
+
]
|
|
6780
|
+
: [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
|
|
6781
|
+
? configOrigin.name === 'pluginPreview' ? '返回插件预览'
|
|
6782
|
+
: configOrigin.name === 'pluginWriteTask' ? '返回恢复任务' : '返回插件详情'
|
|
6783
|
+
: configOperation ? '查看插件'
|
|
6784
|
+
: action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
|
|
6785
|
+
if (successfulConfigOperation && configOrigin) {
|
|
6786
|
+
leaveResult();
|
|
6787
|
+
return;
|
|
6788
|
+
}
|
|
6789
|
+
if ((partial || blockedDependencyFailure) && failedPluginId && failedTarget) {
|
|
6790
|
+
if (failedIssue)
|
|
6791
|
+
leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId });
|
|
6792
|
+
else if (failedPluginPlatforms?.includes(failedTarget.platform)) {
|
|
6793
|
+
leaveResult({ name: 'platform', pluginId: failedPluginId, platform: failedTarget.platform });
|
|
6794
|
+
}
|
|
6795
|
+
else if (snapshot.viewModel?.plugins.some((entry) => entry.pluginId === failedPluginId)) {
|
|
6796
|
+
leaveResult({ name: 'pluginDetail', pluginId: failedPluginId });
|
|
6797
|
+
}
|
|
6798
|
+
else
|
|
6799
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6800
|
+
}
|
|
6801
|
+
else if (pluginId && snapshot.viewModel?.plugins.some((entry) => entry.pluginId === pluginId)) {
|
|
6802
|
+
leaveResult({ name: 'pluginDetail', pluginId });
|
|
6803
|
+
}
|
|
6804
|
+
else
|
|
6805
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6806
|
+
} }, ...((partial || blockedDependencyFailure) && failedPluginId && failedTarget && retryAction ? [{
|
|
6807
|
+
label: '重试失败部分…',
|
|
6808
|
+
run: () => {
|
|
6809
|
+
const issueIntent = failedIssue && retryIssueAction
|
|
6810
|
+
? issueWriteIntent(failedIssue, retryIssueAction)
|
|
6811
|
+
: undefined;
|
|
6812
|
+
return beginReview(issueIntent ?? {
|
|
6813
|
+
kind: 'pluginWrite',
|
|
6814
|
+
action: retryAction,
|
|
6815
|
+
pluginId: failedPluginId,
|
|
6816
|
+
platform: failedTarget.platform,
|
|
6817
|
+
scope: failedTarget.scope,
|
|
6818
|
+
});
|
|
6819
|
+
},
|
|
6820
|
+
}] : []), { label: '返回首页', run: () => requestSessionNavigation('home') }];
|
|
5197
6821
|
const visibleMessages = resultSummary?.messages?.slice(policy.mode === 'compact' ? -2 : -4) ?? [];
|
|
5198
6822
|
const visibleChanges = resultSummary?.changes?.slice(0, policy.mode === 'compact' ? 2 : 4) ?? [];
|
|
5199
6823
|
const resultTargetName = (target) => target.pluginId
|
|
@@ -5254,11 +6878,68 @@ export function ConsoleAppV4(props) {
|
|
|
5254
6878
|
: resultSummary?.resetDisposition === 'quarantinedDirectory'
|
|
5255
6879
|
? `设置路径是目录;已隔离为 ${resultSummary.quarantineBasename ?? '安全隔离目录'}`
|
|
5256
6880
|
: undefined;
|
|
5257
|
-
|
|
6881
|
+
const taskRootResults = taskSummary?.rootResults ?? [];
|
|
6882
|
+
const blockedTaskTargets = new Map((taskSummary?.blockedTargets ?? []).map((entry) => [
|
|
6883
|
+
[entry.target.pluginId, entry.target.platform, entry.target.scope].join('\0'), entry,
|
|
6884
|
+
]));
|
|
6885
|
+
const taskRootState = (state) => {
|
|
6886
|
+
if (state === 'completed')
|
|
6887
|
+
return { label: '完成', status: 'success' };
|
|
6888
|
+
if (state === 'kept')
|
|
6889
|
+
return { label: '无需更改', status: 'success' };
|
|
6890
|
+
if (state === 'partial')
|
|
6891
|
+
return { label: '部分完成', status: 'warning' };
|
|
6892
|
+
if (state === 'failed')
|
|
6893
|
+
return { label: '失败', status: 'error' };
|
|
6894
|
+
if (state === 'notStarted')
|
|
6895
|
+
return { label: '未开始', status: 'muted' };
|
|
6896
|
+
return { label: '待确认', status: 'warning' };
|
|
6897
|
+
};
|
|
6898
|
+
const taskResultRows = taskRootResults.flatMap((root) => {
|
|
6899
|
+
const rootState = taskRootState(root.state);
|
|
6900
|
+
const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
|
|
6901
|
+
?? root.pluginId.split('/').at(-1);
|
|
6902
|
+
const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
|
|
6903
|
+
return [
|
|
6904
|
+
_jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`),
|
|
6905
|
+
...requestedTargets.map((target) => {
|
|
6906
|
+
const key = [target.pluginId, target.platform, target.scope].join('\0');
|
|
6907
|
+
const blockedTarget = blockedTaskTargets.get(key);
|
|
6908
|
+
const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
|
|
6909
|
+
&& entry.scope === target.scope);
|
|
6910
|
+
const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
|
|
6911
|
+
: targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
|
|
6912
|
+
: targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
|
|
6913
|
+
: targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
|
|
6914
|
+
: { label: '未开始', status: 'muted' };
|
|
6915
|
+
return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
|
|
6916
|
+
? pluginWriteTaskBlockLabel(blockedTarget.reason)
|
|
6917
|
+
: targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
|
|
6918
|
+
}),
|
|
6919
|
+
];
|
|
6920
|
+
});
|
|
6921
|
+
const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
|
|
6922
|
+
const rootState = taskRootState(root.state);
|
|
6923
|
+
const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
|
|
6924
|
+
?? root.pluginId.split('/').at(-1);
|
|
6925
|
+
return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
|
|
6926
|
+
}).concat(prioritizedTargets.map((target) => {
|
|
6927
|
+
const state = operationTargetStateLabel(target.state);
|
|
6928
|
+
const relation = target.relation === 'requested' ? '你选择的插件'
|
|
6929
|
+
: target.relation === 'direct' ? '直接依赖'
|
|
6930
|
+
: target.relation === 'transitive' ? '间接依赖' : undefined;
|
|
6931
|
+
return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
|
|
6932
|
+
}));
|
|
6933
|
+
const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
|
|
6934
|
+
const rootResultRows = taskOperation ? taskResultRows : batchResultRows;
|
|
6935
|
+
const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
|
|
6936
|
+
const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
|
|
6937
|
+
if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
|
|
5258
6938
|
|| prioritizedPortableSkills.length > portableSkillLimit) {
|
|
5259
6939
|
help = 'PgUp/PgDn 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5260
6940
|
}
|
|
5261
|
-
content =
|
|
6941
|
+
content = catalogManagementOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: resultSummary?.message ?? 'Catalog 管理结果', children: [catalogManagementResult?.catalogVersion ? _jsx(StaticRow, { title: "Catalog \u7248\u672C", meta: catalogManagementResult.catalogVersion, compact: true }) : null, catalogManagementResult?.store ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5B9E\u9645\u5220\u9664", meta: `${catalogManagementResult.store.deletedEntries} 项 · ${catalogManagementResult.store.deletedBytes} bytes`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u5B9E\u9645\u4FDD\u7559", meta: `${catalogManagementResult.store.retainedEntries} 项`, compact: true })] }) : null, catalogManagementResult?.publication ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u53D1\u5E03\u72B6\u6001", meta: catalogManagementResult.publication.state, status: catalogManagementResult.publication.state === 'uncertain' ? 'warning' : 'success', compact: true }), _jsx(StaticRow, { title: "\u76EE\u6807", meta: `${catalogManagementResult.publication.remote} · ${catalogManagementResult.publication.branch}`, compact: true }), _jsx(StaticRow, { title: "\u8BA1\u5212\u6807\u8BC6", meta: catalogManagementResult.publication.planDigest.slice(0, 15), compact: true })] }) : catalogManagementResult?.planDigest ? _jsx(StaticRow, { title: "\u8BA1\u5212\u5DF2\u51C6\u5907", meta: catalogManagementResult.planDigest.slice(0, 15), status: "success", compact: true }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: productResultMessage(message), status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : taskOperation || batchOperation ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: batchOperation ? '根插件与目标结果' : '根插件结果', children: [taskResultOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${taskResultOffset} 项`, status: "muted", compact: true }) : null, visibleTaskResultRows, rootResultRows.length > taskResultOffset + visibleTaskResultRows.length
|
|
6942
|
+
? _jsx(StaticRow, { title: "\u2193 \u540E\u9762\u8FD8\u6709", meta: `${rootResultRows.length - taskResultOffset - visibleTaskResultRows.length} 项`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableTechnicalSummary ? _jsxs(_Fragment, { children: [portableSkillOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${portableSkillOffset} 个 Skills`, status: "muted", compact: true }) : null, portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt?.recovery ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true }) : null, _jsx(TechnicalDetails, { children: compactTechnicalRows })] }) : _jsx(_Fragment, { children: _jsxs(ResultCard, { status: resultStatus, title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', children: [portableSkillOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${portableSkillOffset} 个 Skills`, status: "muted", compact: true }) : null, portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt ? _jsx(StaticRow, { title: `Agent:${portableReceipt.agentMode === 'automatic' ? 'Skills CLI 默认选择' : portableReceipt.requestedAgents.join('、')}`, meta: `范围:${scopeLabel(portableReceipt.scope)}`, compact: true }) : null, portableReceipt?.recovery ? policy.mode === 'compact'
|
|
5262
6943
|
? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
|
|
5263
6944
|
: _jsx(Callout, { status: "warning", title: "\u5B89\u5168\u6062\u590D", description: portableReceipt.recovery }) : null, showTechnical && portableReceipt ? _jsxs(TechnicalDetails, { children: [portableReceipt.failureCode ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }) : null, _jsx(StaticRow, { title: "verification missing", meta: String(portableReceipt.missing.length), compact: true }), _jsx(StaticRow, { title: "Skills CLI version", meta: portableReceipt.skillsCliVersion, compact: true }), partial || blocked
|
|
5264
6945
|
? _jsx(StaticRow, { title: "\u53E6\u6709\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "materialization digest \u00B7 elapsed", status: "muted", compact: true })
|
|
@@ -5311,11 +6992,14 @@ export function ConsoleAppV4(props) {
|
|
|
5311
6992
|
switch (route.name) {
|
|
5312
6993
|
case 'pluginPreview':
|
|
5313
6994
|
case 'pluginDependencies':
|
|
6995
|
+
case 'pluginDependencyNode':
|
|
5314
6996
|
case 'installPlacement':
|
|
5315
6997
|
case 'installTargets':
|
|
5316
6998
|
case 'dependencyPlacement':
|
|
5317
6999
|
case 'dependencyPlacementEditor':
|
|
7000
|
+
case 'pluginWriteTask':
|
|
5318
7001
|
case 'pluginDetail':
|
|
7002
|
+
case 'pluginMore':
|
|
5319
7003
|
case 'platform':
|
|
5320
7004
|
case 'pluginAbout':
|
|
5321
7005
|
case 'pluginTechnical':
|
|
@@ -5343,6 +7027,7 @@ export function ConsoleAppV4(props) {
|
|
|
5343
7027
|
case 'sourceAdd':
|
|
5344
7028
|
case 'sourceSave':
|
|
5345
7029
|
case 'scriptTimeout':
|
|
7030
|
+
case 'catalogWorkspace':
|
|
5346
7031
|
case 'reviewPreparing':
|
|
5347
7032
|
case 'review':
|
|
5348
7033
|
case 'result':
|
|
@@ -5389,6 +7074,22 @@ export function ConsoleAppV4(props) {
|
|
|
5389
7074
|
const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
|
|
5390
7075
|
? actions.map((action) => String(Boolean(action.disabled))).join(',')
|
|
5391
7076
|
: '';
|
|
7077
|
+
const batchSelectionActionSignature = route.name === 'batchSelection'
|
|
7078
|
+
&& batchDraft?.stage === 'select'
|
|
7079
|
+
? actions.map((action) => action.pluginId ?? action.label).join('\0')
|
|
7080
|
+
: '';
|
|
7081
|
+
useEffect(() => {
|
|
7082
|
+
if (!batchSelectionActionSignature)
|
|
7083
|
+
return;
|
|
7084
|
+
const lastActionIndex = Math.max(0, actions.length - 1);
|
|
7085
|
+
if (focus > lastActionIndex) {
|
|
7086
|
+
setFocus(lastActionIndex);
|
|
7087
|
+
setScrollOffset(lastActionIndex);
|
|
7088
|
+
return;
|
|
7089
|
+
}
|
|
7090
|
+
if (scrollOffset > lastActionIndex)
|
|
7091
|
+
setScrollOffset(focus);
|
|
7092
|
+
}, [batchSelectionActionSignature, focus, scrollOffset]);
|
|
5392
7093
|
useEffect(() => {
|
|
5393
7094
|
if (!targetActionSignature || !actions[focus]?.disabled)
|
|
5394
7095
|
return;
|
|
@@ -5475,6 +7176,10 @@ export function ConsoleAppV4(props) {
|
|
|
5475
7176
|
setObjectError(undefined);
|
|
5476
7177
|
setSourceDraft(undefined);
|
|
5477
7178
|
setBatchDraft(undefined);
|
|
7179
|
+
setCatalogInput('');
|
|
7180
|
+
setCatalogWorkspaceDraft({ ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0' });
|
|
7181
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: 'catalog' });
|
|
7182
|
+
setCatalogDevSourceId('');
|
|
5478
7183
|
}
|
|
5479
7184
|
function continueEditingFromSessionNavigation() {
|
|
5480
7185
|
if (props.pendingSessionNavigation)
|
|
@@ -5491,6 +7196,9 @@ export function ConsoleAppV4(props) {
|
|
|
5491
7196
|
setOverlayFocus(0);
|
|
5492
7197
|
setOverlay('sessionNavigation');
|
|
5493
7198
|
}, [props.pendingSessionNavigation, props.pendingSessionNavigationDrafts]);
|
|
7199
|
+
const normalizedEffectiveFocus = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
7200
|
+
? Math.min(effectiveFocus, Math.max(0, actions.length - 1))
|
|
7201
|
+
: effectiveFocus;
|
|
5494
7202
|
useInput((input, key) => {
|
|
5495
7203
|
if (!overlay && route.name === 'reviewPreparing' && snapshot.reviewPreparation?.state === 'failed'
|
|
5496
7204
|
&& (key.escape || input.toLowerCase() === 'b')) {
|
|
@@ -5658,6 +7366,14 @@ export function ConsoleAppV4(props) {
|
|
|
5658
7366
|
setReportSaveError(undefined);
|
|
5659
7367
|
return;
|
|
5660
7368
|
}
|
|
7369
|
+
if ((key.pageUp || key.pageDown)
|
|
7370
|
+
&& route.name === 'pluginWriteTask') {
|
|
7371
|
+
const total = props.controller.listPluginWriteTaskCandidates(route.goal)
|
|
7372
|
+
.filter((candidate) => candidate.target.pluginId === route.pluginId
|
|
7373
|
+
&& (!route.targetKeys || route.targetKeys.includes(pluginWriteTaskTargetKey(candidate)))).length;
|
|
7374
|
+
setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -1 : 1))));
|
|
7375
|
+
return;
|
|
7376
|
+
}
|
|
5661
7377
|
if ((key.pageUp || key.pageDown)
|
|
5662
7378
|
&& route.name === 'review'
|
|
5663
7379
|
&& snapshot.activeReview) {
|
|
@@ -5669,8 +7385,10 @@ export function ConsoleAppV4(props) {
|
|
|
5669
7385
|
if ((key.pageUp || key.pageDown)
|
|
5670
7386
|
&& route.name === 'result'
|
|
5671
7387
|
&& snapshot.result) {
|
|
5672
|
-
const
|
|
5673
|
-
|
|
7388
|
+
const pageSummary = 'summary' in snapshot.result ? snapshot.result.summary : undefined;
|
|
7389
|
+
const total = pageSummary
|
|
7390
|
+
? Math.max(pageSummary.targets?.length ?? 0, pageSummary.task?.rootResults.reduce((count, root) => count + 1 + pageSummary.task.requestedTargets.filter((target) => target.pluginId === root.pluginId).length, 0) ?? 0, (pageSummary.batch?.rootResults.length ?? 0)
|
|
7391
|
+
+ (pageSummary.targets?.length ?? 0), snapshot.result.operationKind === 'portableInstall'
|
|
5674
7392
|
? snapshot.portableResultSkills?.length ?? 0
|
|
5675
7393
|
: 0)
|
|
5676
7394
|
: 0;
|
|
@@ -5705,6 +7423,15 @@ export function ConsoleAppV4(props) {
|
|
|
5705
7423
|
setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
|
|
5706
7424
|
return;
|
|
5707
7425
|
}
|
|
7426
|
+
if ((key.pageUp || key.pageDown) && (route.name === 'catalogStore'
|
|
7427
|
+
|| route.name === 'catalogPublish' && route.step === 'overview')) {
|
|
7428
|
+
const total = route.name === 'catalogStore'
|
|
7429
|
+
? catalogStore?.entries.length ?? 0
|
|
7430
|
+
: catalogPublication?.records.length ?? 0;
|
|
7431
|
+
const pageRows = responsiveBudget(3, 7);
|
|
7432
|
+
setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
|
|
7433
|
+
return;
|
|
7434
|
+
}
|
|
5708
7435
|
if ((key.pageUp || key.pageDown) && route.name === 'pluginDependencies' && plugin) {
|
|
5709
7436
|
const overview = dependencyOverview(plugin);
|
|
5710
7437
|
const dependents = route.view === 'removeBlocked' && route.platform && route.scope
|
|
@@ -5875,6 +7602,180 @@ export function ConsoleAppV4(props) {
|
|
|
5875
7602
|
setSourceInputError(undefined);
|
|
5876
7603
|
return;
|
|
5877
7604
|
}
|
|
7605
|
+
if (route.name === 'catalogWorkspace' && (route.view === 'select'
|
|
7606
|
+
|| route.view === 'create' || route.view === 'version' && route.step === 'catalogVersion')) {
|
|
7607
|
+
if (key.escape) {
|
|
7608
|
+
if (route.view === 'create') {
|
|
7609
|
+
const step = route.step ?? 'ownerName';
|
|
7610
|
+
const next = { ...catalogWorkspaceDraft, [step]: catalogInput };
|
|
7611
|
+
setCatalogWorkspaceDraft(next);
|
|
7612
|
+
const previous = { ownerUrl: 'ownerName', displayName: 'ownerUrl', catalogVersion: 'displayName' }[step];
|
|
7613
|
+
if (previous) {
|
|
7614
|
+
setCatalogInput(next[previous]);
|
|
7615
|
+
props.controller.replace({ ...route, step: previous });
|
|
7616
|
+
}
|
|
7617
|
+
else
|
|
7618
|
+
back();
|
|
7619
|
+
}
|
|
7620
|
+
else
|
|
7621
|
+
back();
|
|
7622
|
+
return;
|
|
7623
|
+
}
|
|
7624
|
+
if (key.return) {
|
|
7625
|
+
const value = catalogInput.trim();
|
|
7626
|
+
setCatalogInputError(undefined);
|
|
7627
|
+
if (route.view === 'select') {
|
|
7628
|
+
setCatalogLoading(true);
|
|
7629
|
+
void props.controller.selectCatalogWorkspace(value || undefined).then((workspace) => {
|
|
7630
|
+
setCatalogWorkspace(workspace);
|
|
7631
|
+
props.controller.replace({ name: 'catalogWorkspace', view: 'overview' });
|
|
7632
|
+
setFocus(0);
|
|
7633
|
+
setScrollOffset(0);
|
|
7634
|
+
setCatalogInput('');
|
|
7635
|
+
}).catch((error) => setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]))).finally(() => setCatalogLoading(false));
|
|
7636
|
+
return;
|
|
7637
|
+
}
|
|
7638
|
+
if (route.view === 'version') {
|
|
7639
|
+
if (!/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/u.test(value)) {
|
|
7640
|
+
setCatalogInputError('版本必须是 x.y.z 或精确 x.y.z-beta.N。');
|
|
7641
|
+
return;
|
|
7642
|
+
}
|
|
7643
|
+
beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', version: value }, { name: 'catalogWorkspace', view: 'overview' });
|
|
7644
|
+
return;
|
|
7645
|
+
}
|
|
7646
|
+
const step = route.step ?? 'ownerName';
|
|
7647
|
+
if (!value) {
|
|
7648
|
+
setCatalogInputError('此字段不能为空。');
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7651
|
+
if (step === 'ownerUrl') {
|
|
7652
|
+
try {
|
|
7653
|
+
const url = new URL(value);
|
|
7654
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
7655
|
+
throw new Error();
|
|
7656
|
+
}
|
|
7657
|
+
catch {
|
|
7658
|
+
setCatalogInputError('请输入有效的 HTTP 或 HTTPS owner URL。');
|
|
7659
|
+
return;
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
if (step === 'catalogVersion' && !/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/u.test(value)) {
|
|
7663
|
+
setCatalogInputError('版本必须是 x.y.z 或精确 x.y.z-beta.N。');
|
|
7664
|
+
return;
|
|
7665
|
+
}
|
|
7666
|
+
const next = { ...catalogWorkspaceDraft, [step]: value };
|
|
7667
|
+
setCatalogWorkspaceDraft(next);
|
|
7668
|
+
setCatalogInput('');
|
|
7669
|
+
if (step === 'ownerName')
|
|
7670
|
+
props.controller.replace({ ...route, step: 'ownerUrl' });
|
|
7671
|
+
else if (step === 'ownerUrl')
|
|
7672
|
+
props.controller.replace({ ...route, step: 'displayName' });
|
|
7673
|
+
else if (step === 'displayName') {
|
|
7674
|
+
setCatalogInput('0.1.0');
|
|
7675
|
+
props.controller.replace({ ...route, step: 'catalogVersion' });
|
|
7676
|
+
}
|
|
7677
|
+
else
|
|
7678
|
+
beginReview({ kind: 'catalogManagement', action: 'workspaceInit', ...next }, { name: 'catalogWorkspace', view: 'overview' });
|
|
7679
|
+
return;
|
|
7680
|
+
}
|
|
7681
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7682
|
+
setCatalogInput('');
|
|
7683
|
+
else if (key.backspace || key.delete)
|
|
7684
|
+
setCatalogInput(removeLastGrapheme);
|
|
7685
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7686
|
+
setCatalogInput((value) => value + input);
|
|
7687
|
+
setCatalogInputError(undefined);
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
if (route.name === 'catalogDev' && route.step && route.step !== 'overview') {
|
|
7691
|
+
if (key.escape) {
|
|
7692
|
+
back();
|
|
7693
|
+
return;
|
|
7694
|
+
}
|
|
7695
|
+
if (key.return) {
|
|
7696
|
+
const value = catalogInput.trim();
|
|
7697
|
+
if (value && !CATALOG_SOURCE_ID_PATTERN.test(value)) {
|
|
7698
|
+
setCatalogInputError('格式必须是小写字母、数字或连字符,最多 32 字符。');
|
|
7699
|
+
return;
|
|
7700
|
+
}
|
|
7701
|
+
if (route.step === 'sourceId') {
|
|
7702
|
+
setCatalogDevSourceId(value);
|
|
7703
|
+
back();
|
|
7704
|
+
}
|
|
7705
|
+
else {
|
|
7706
|
+
if (!value) {
|
|
7707
|
+
setCatalogInputError('请输入替代主来源 ID。');
|
|
7708
|
+
return;
|
|
7709
|
+
}
|
|
7710
|
+
beginReview({ kind: 'catalogManagement', action: 'devDeactivate', replacementPrimary: value }, { name: 'catalogDev' });
|
|
7711
|
+
}
|
|
7712
|
+
return;
|
|
7713
|
+
}
|
|
7714
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7715
|
+
setCatalogInput('');
|
|
7716
|
+
else if (key.backspace || key.delete)
|
|
7717
|
+
setCatalogInput(removeLastGrapheme);
|
|
7718
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7719
|
+
setCatalogInput((value) => value + input);
|
|
7720
|
+
setCatalogInputError(undefined);
|
|
7721
|
+
return;
|
|
7722
|
+
}
|
|
7723
|
+
if (route.name === 'catalogPublish' && route.step !== 'overview') {
|
|
7724
|
+
if (key.escape) {
|
|
7725
|
+
if (route.step === 'branch') {
|
|
7726
|
+
setCatalogPublishDraft((draft) => ({ ...draft, branch: catalogInput.trim() || 'catalog' }));
|
|
7727
|
+
setCatalogInput(catalogPublishDraft.target);
|
|
7728
|
+
props.controller.replace({ name: 'catalogPublish', step: 'target' });
|
|
7729
|
+
}
|
|
7730
|
+
else {
|
|
7731
|
+
const value = catalogInput.trim();
|
|
7732
|
+
const targetKind = value.includes('://') || /^[^@\s]+@[^:\s]+:/u.test(value) || value.startsWith('/')
|
|
7733
|
+
? 'remoteUrl' : 'remote';
|
|
7734
|
+
setCatalogPublishDraft((draft) => ({ ...draft, targetKind, target: value }));
|
|
7735
|
+
back();
|
|
7736
|
+
}
|
|
7737
|
+
return;
|
|
7738
|
+
}
|
|
7739
|
+
if (key.return) {
|
|
7740
|
+
const value = catalogInput.trim();
|
|
7741
|
+
if (route.step === 'target') {
|
|
7742
|
+
if (/\s/u.test(value)) {
|
|
7743
|
+
setCatalogInputError('remote 名称或 URL 不能包含空白。');
|
|
7744
|
+
return;
|
|
7745
|
+
}
|
|
7746
|
+
const targetKind = value.includes('://') || /^[^@\s]+@[^:\s]+:/u.test(value) || value.startsWith('/')
|
|
7747
|
+
? 'remoteUrl' : 'remote';
|
|
7748
|
+
setCatalogPublishDraft((draft) => ({ ...draft, targetKind, target: value }));
|
|
7749
|
+
setCatalogInput(catalogPublishDraft.expectedPlanDigest ? catalogPublishDraft.branch : 'catalog');
|
|
7750
|
+
props.controller.replace({ name: 'catalogPublish', step: 'branch' });
|
|
7751
|
+
}
|
|
7752
|
+
else {
|
|
7753
|
+
const branch = value || 'catalog';
|
|
7754
|
+
if (/\s|\.\.|[~^:?*\[\\]/u.test(branch) || branch.startsWith('-') || branch.endsWith('/')) {
|
|
7755
|
+
setCatalogInputError('请输入安全的 Git branch 名称。');
|
|
7756
|
+
return;
|
|
7757
|
+
}
|
|
7758
|
+
const draft = { ...catalogPublishDraft, branch };
|
|
7759
|
+
setCatalogPublishDraft(draft);
|
|
7760
|
+
const target = draft.target
|
|
7761
|
+
? draft.targetKind === 'remote' ? { remote: draft.target } : { remoteUrl: draft.target }
|
|
7762
|
+
: {};
|
|
7763
|
+
beginReview(draft.expectedPlanDigest
|
|
7764
|
+
? { kind: 'catalogManagement', action: 'publishApply', expectedPlanDigest: draft.expectedPlanDigest,
|
|
7765
|
+
...target, branch }
|
|
7766
|
+
: { kind: 'catalogManagement', action: 'publishPlan', ...target, branch }, { name: 'catalogPublish', step: 'overview' });
|
|
7767
|
+
}
|
|
7768
|
+
return;
|
|
7769
|
+
}
|
|
7770
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7771
|
+
setCatalogInput('');
|
|
7772
|
+
else if (key.backspace || key.delete)
|
|
7773
|
+
setCatalogInput(removeLastGrapheme);
|
|
7774
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7775
|
+
setCatalogInput((value) => value + input);
|
|
7776
|
+
setCatalogInputError(undefined);
|
|
7777
|
+
return;
|
|
7778
|
+
}
|
|
5878
7779
|
if (route.name === 'configFieldEditor'
|
|
5879
7780
|
&& (route.editor === 'string' || route.editor === 'number')
|
|
5880
7781
|
&& ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')) {
|
|
@@ -5982,18 +7883,22 @@ export function ConsoleAppV4(props) {
|
|
|
5982
7883
|
setInputError(undefined);
|
|
5983
7884
|
return;
|
|
5984
7885
|
}
|
|
5985
|
-
if (searchFocus && route.name === 'pluginList') {
|
|
7886
|
+
if (searchFocus && (route.name === 'pluginList' || route.name === 'batchSelection')) {
|
|
5986
7887
|
if (key.escape) {
|
|
5987
7888
|
setSearchFocus(false);
|
|
5988
7889
|
return;
|
|
5989
7890
|
}
|
|
5990
7891
|
if (key.return) {
|
|
5991
7892
|
setSearchFocus(false);
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
7893
|
+
if (route.name === 'pluginList') {
|
|
7894
|
+
const row = pluginRows[focus];
|
|
7895
|
+
if (row?.menuAction)
|
|
7896
|
+
row.menuAction();
|
|
7897
|
+
else if (row)
|
|
7898
|
+
go(route.mode === 'browse'
|
|
7899
|
+
? { name: 'pluginPreview', pluginId: row.pluginId }
|
|
7900
|
+
: { name: 'pluginDetail', pluginId: row.pluginId });
|
|
7901
|
+
}
|
|
5997
7902
|
return;
|
|
5998
7903
|
}
|
|
5999
7904
|
if (key.upArrow || key.downArrow) {
|
|
@@ -6004,21 +7909,44 @@ export function ConsoleAppV4(props) {
|
|
|
6004
7909
|
if (key.leftArrow || key.rightArrow) {
|
|
6005
7910
|
setSearchFocus(false);
|
|
6006
7911
|
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
6007
|
-
const current = route.sourceId ?? 'all';
|
|
7912
|
+
const current = route.name === 'pluginList' ? route.sourceId ?? 'all' : batchDraft?.sourceId ?? 'all';
|
|
6008
7913
|
const direction = key.leftArrow ? -1 : 1;
|
|
6009
7914
|
const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
|
|
6010
|
-
|
|
7915
|
+
if (route.name === 'pluginList') {
|
|
7916
|
+
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
|
|
7917
|
+
}
|
|
7918
|
+
else {
|
|
7919
|
+
setBatchDraft((draft) => draft ? {
|
|
7920
|
+
...draft,
|
|
7921
|
+
...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
|
|
7922
|
+
showSelected: false,
|
|
7923
|
+
} : draft);
|
|
7924
|
+
}
|
|
6011
7925
|
setFocus(0);
|
|
6012
7926
|
setScrollOffset(0);
|
|
6013
7927
|
return;
|
|
6014
7928
|
}
|
|
6015
|
-
if (
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
7929
|
+
if (route.name === 'pluginList') {
|
|
7930
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7931
|
+
setQuery('');
|
|
7932
|
+
else if (key.backspace || key.delete)
|
|
7933
|
+
setQuery(removeLastGrapheme);
|
|
7934
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7935
|
+
setQuery((value) => value + input);
|
|
7936
|
+
}
|
|
7937
|
+
else {
|
|
7938
|
+
setBatchDraft((draft) => draft ? {
|
|
7939
|
+
...draft,
|
|
7940
|
+
query: input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
|
|
7941
|
+
? ''
|
|
7942
|
+
: key.backspace || key.delete
|
|
7943
|
+
? removeLastGrapheme(draft.query)
|
|
7944
|
+
: input && !key.ctrl && !key.meta && input >= ' '
|
|
7945
|
+
? draft.query + input : draft.query,
|
|
7946
|
+
showSelected: false,
|
|
7947
|
+
} : draft);
|
|
7948
|
+
}
|
|
7949
|
+
setFocus(route.name === 'batchSelection' ? 1 : 0);
|
|
6022
7950
|
setScrollOffset(0);
|
|
6023
7951
|
return;
|
|
6024
7952
|
}
|
|
@@ -6028,6 +7956,36 @@ export function ConsoleAppV4(props) {
|
|
|
6028
7956
|
|| route.name === 'configObjectPropertyEditor')) {
|
|
6029
7957
|
return;
|
|
6030
7958
|
}
|
|
7959
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked'
|
|
7960
|
+
&& (key.leftArrow || key.rightArrow)) {
|
|
7961
|
+
const projection = buildDependencyGraphProjection({
|
|
7962
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
7963
|
+
rootPluginId: route.pluginId,
|
|
7964
|
+
direction: route.direction ?? 'dependencies',
|
|
7965
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
7966
|
+
});
|
|
7967
|
+
const row = projection.rows[focus];
|
|
7968
|
+
if (!row)
|
|
7969
|
+
return;
|
|
7970
|
+
const next = new Set(expandedDependencyPluginIds ?? projection.defaultExpandedPluginIds);
|
|
7971
|
+
if (key.rightArrow && row.canExpand)
|
|
7972
|
+
next.add(row.pluginId);
|
|
7973
|
+
if (key.leftArrow && row.canExpand)
|
|
7974
|
+
next.delete(row.pluginId);
|
|
7975
|
+
const nextProjection = buildDependencyGraphProjection({
|
|
7976
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
7977
|
+
rootPluginId: route.pluginId,
|
|
7978
|
+
direction: route.direction ?? 'dependencies',
|
|
7979
|
+
expandedPluginIds: next,
|
|
7980
|
+
});
|
|
7981
|
+
const nextFocus = Math.max(0, nextProjection.rows.findIndex((candidate) => candidate.id === row.id));
|
|
7982
|
+
const nextScroll = Math.min(scrollOffset, nextFocus);
|
|
7983
|
+
setExpandedDependencyPluginIds(next);
|
|
7984
|
+
setFocus(nextFocus);
|
|
7985
|
+
setScrollOffset(nextScroll);
|
|
7986
|
+
persistDependencyGraphContext(route, nextProjection, next, nextFocus, nextScroll);
|
|
7987
|
+
return;
|
|
7988
|
+
}
|
|
6031
7989
|
const sessionNavigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: isCharacterInputMode(route, searchFocus) });
|
|
6032
7990
|
const globalAction = resolveTuiGlobalKeyAction({ input, key }, { mode: 'menu', allowBack: true });
|
|
6033
7991
|
if (sessionNavigation) {
|
|
@@ -6071,7 +8029,8 @@ export function ConsoleAppV4(props) {
|
|
|
6071
8029
|
back();
|
|
6072
8030
|
}
|
|
6073
8031
|
else if (globalAction === 'back') {
|
|
6074
|
-
if (dirty && route.name !== 'review'
|
|
8032
|
+
if (dirty && route.name !== 'review'
|
|
8033
|
+
&& !(batchDraft && (route.name === 'pluginPreview' || route.name === 'pluginDetail'))) {
|
|
6075
8034
|
setOverlayFocus(0);
|
|
6076
8035
|
setDiscardForExit(false);
|
|
6077
8036
|
setOverlay('discard');
|
|
@@ -6082,9 +8041,11 @@ export function ConsoleAppV4(props) {
|
|
|
6082
8041
|
else if (route.name === 'pluginList' && input === '/') {
|
|
6083
8042
|
setSearchFocus(true);
|
|
6084
8043
|
}
|
|
6085
|
-
else if (route.name === 'pluginList' &&
|
|
8044
|
+
else if (route.name === 'pluginList' && input.toLowerCase() === 'f') {
|
|
6086
8045
|
const selectedPluginId = pluginRows[focus]?.pluginId;
|
|
6087
|
-
const nextFilter = installFilter === 'all'
|
|
8046
|
+
const nextFilter = installFilter === 'all'
|
|
8047
|
+
? route.mode === 'browse' ? 'uninstalled' : 'updateAvailable'
|
|
8048
|
+
: 'all';
|
|
6088
8049
|
const nextItems = props.controller.listPlugins({
|
|
6089
8050
|
mode: route.mode,
|
|
6090
8051
|
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
@@ -6096,44 +8057,68 @@ export function ConsoleAppV4(props) {
|
|
|
6096
8057
|
: -1;
|
|
6097
8058
|
setInstallFilter(nextFilter);
|
|
6098
8059
|
props.controller.updatePageContext({ installFilter: nextFilter });
|
|
6099
|
-
setFocus(retained >= 0 ? retained : 0);
|
|
8060
|
+
setFocus(retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0);
|
|
6100
8061
|
setScrollOffset(0);
|
|
6101
8062
|
}
|
|
6102
8063
|
else if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
|
|
6103
8064
|
openBatchSelection(route.mode === 'browse' ? 'install' : undefined);
|
|
6104
8065
|
}
|
|
6105
8066
|
else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
|
|
6106
|
-
|
|
6107
|
-
setBatchDraft({
|
|
6108
|
-
action: 'sync',
|
|
6109
|
-
selection: 'currentProjectIntentRoots',
|
|
6110
|
-
selectedPluginIds: [],
|
|
6111
|
-
platform: 'both',
|
|
6112
|
-
scope: 'project',
|
|
6113
|
-
});
|
|
6114
|
-
props.controller.push({ name: 'batchPlacement' });
|
|
8067
|
+
prepareCurrentProjectPlugins();
|
|
6115
8068
|
}
|
|
6116
|
-
else if (route.name === 'batchSelection' && batchDraft?.
|
|
6117
|
-
const candidates =
|
|
6118
|
-
|
|
6119
|
-
}).filter((candidate) => !candidate.configOnly);
|
|
8069
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
|
|
8070
|
+
const candidates = batchCandidates(batchDraft)
|
|
8071
|
+
.filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined);
|
|
6120
8072
|
setBatchDraft((current) => current ? {
|
|
6121
8073
|
...current,
|
|
6122
|
-
selectedPluginIds:
|
|
6123
|
-
|
|
8074
|
+
selectedPluginIds: [...new Set([
|
|
8075
|
+
...current.selectedPluginIds,
|
|
8076
|
+
...candidates.map((candidate) => candidate.pluginId),
|
|
8077
|
+
])],
|
|
6124
8078
|
} : current);
|
|
6125
8079
|
}
|
|
6126
|
-
else if (route.name === 'batchSelection' && batchDraft?.
|
|
8080
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
|
|
6127
8081
|
if (batchDraft.selectedPluginIds.length > 0) {
|
|
6128
|
-
|
|
8082
|
+
const invalid = invalidBatchSelection(batchDraft);
|
|
8083
|
+
if (invalid.length > 0) {
|
|
8084
|
+
setActionError(`${invalid.length} 个已选插件已不可用。请先在“查看全部已选”中取消选择。`);
|
|
8085
|
+
return;
|
|
8086
|
+
}
|
|
8087
|
+
if (batchDraft.mode === 'browse') {
|
|
8088
|
+
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
8089
|
+
props.controller.push({ name: 'batchPlacement' });
|
|
8090
|
+
}
|
|
8091
|
+
else
|
|
8092
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
6129
8093
|
setFocus(0);
|
|
6130
8094
|
setScrollOffset(0);
|
|
6131
8095
|
}
|
|
6132
8096
|
else
|
|
6133
8097
|
setActionError('请至少选择一个插件。');
|
|
6134
8098
|
}
|
|
6135
|
-
else if (route.name === 'batchSelection' && batchDraft?.
|
|
6136
|
-
|
|
8099
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === ' ') {
|
|
8100
|
+
const action = actions[normalizedEffectiveFocus];
|
|
8101
|
+
if (action?.pluginId
|
|
8102
|
+
&& (batchDraft.selectedPluginIds.includes(action.pluginId) || action.canSelectNew)) {
|
|
8103
|
+
toggleBatchPlugin(action.pluginId);
|
|
8104
|
+
}
|
|
8105
|
+
}
|
|
8106
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
|
|
8107
|
+
setSearchFocus(true);
|
|
8108
|
+
}
|
|
8109
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8110
|
+
&& (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
|
|
8111
|
+
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
8112
|
+
const current = batchDraft.sourceId ?? 'all';
|
|
8113
|
+
const direction = key.leftArrow || input === '[' ? -1 : 1;
|
|
8114
|
+
const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
|
|
8115
|
+
setBatchDraft((draft) => draft ? {
|
|
8116
|
+
...draft,
|
|
8117
|
+
...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
|
|
8118
|
+
showSelected: false,
|
|
8119
|
+
} : draft);
|
|
8120
|
+
setFocus(1);
|
|
8121
|
+
setScrollOffset(0);
|
|
6137
8122
|
}
|
|
6138
8123
|
else if (route.name === 'configFields' && route.layer.scope !== 'effective' && input.toLowerCase() === 's') {
|
|
6139
8124
|
if (dirty && configDraftIssues.length === 0) {
|
|
@@ -6167,20 +8152,17 @@ export function ConsoleAppV4(props) {
|
|
|
6167
8152
|
else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
|
|
6168
8153
|
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
6169
8154
|
const current = route.sourceId ?? 'all';
|
|
6170
|
-
const selectedPluginId =
|
|
6171
|
-
|
|
6172
|
-
...(current !== 'all' ? { sourceId: current } : {}),
|
|
6173
|
-
...(query ? { query } : {}),
|
|
6174
|
-
installFilter,
|
|
6175
|
-
})[focus]?.pluginId;
|
|
8155
|
+
const selectedPluginId = pluginRows[focus]?.pluginId.startsWith('@menu:')
|
|
8156
|
+
? undefined : pluginRows[focus]?.pluginId;
|
|
6176
8157
|
const direction = key.leftArrow || input === '[' ? -1 : 1;
|
|
6177
8158
|
const index = tabs.indexOf(current);
|
|
6178
8159
|
const next = tabs[(index + direction + tabs.length) % tabs.length];
|
|
6179
8160
|
const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}), installFilter });
|
|
6180
8161
|
const retained = selectedPluginId ? nextItems.findIndex((item) => item.pluginId === selectedPluginId) : -1;
|
|
6181
8162
|
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
|
|
6182
|
-
|
|
6183
|
-
|
|
8163
|
+
const retainedFocus = retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0;
|
|
8164
|
+
setFocus(retainedFocus);
|
|
8165
|
+
setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
|
|
6184
8166
|
}
|
|
6185
8167
|
else if (input.toLowerCase() === 'a' && route.name === 'portableSkillSelect') {
|
|
6186
8168
|
props.controller.selectAllPortableSkills();
|
|
@@ -6192,7 +8174,8 @@ export function ConsoleAppV4(props) {
|
|
|
6192
8174
|
}
|
|
6193
8175
|
else if (key.upArrow || input.toLowerCase() === 'k') {
|
|
6194
8176
|
setFocus((value) => {
|
|
6195
|
-
const current =
|
|
8177
|
+
const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8178
|
+
? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
|
|
6196
8179
|
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1) : moveFocus(actions, current, -1);
|
|
6197
8180
|
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
6198
8181
|
dynamicMenuFocus.current = {
|
|
@@ -6201,17 +8184,31 @@ export function ConsoleAppV4(props) {
|
|
|
6201
8184
|
focusedActionId: dynamicActionIds[next],
|
|
6202
8185
|
};
|
|
6203
8186
|
}
|
|
6204
|
-
const viewport = route.name === '
|
|
6205
|
-
?
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
8187
|
+
const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8188
|
+
? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
|
|
8189
|
+
: route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
8190
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
8191
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
8192
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
|
|
8193
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
|
|
8194
|
+
const projection = buildDependencyGraphProjection({
|
|
8195
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
8196
|
+
rootPluginId: route.pluginId,
|
|
8197
|
+
direction: route.direction ?? 'dependencies',
|
|
8198
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
8199
|
+
});
|
|
8200
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), next, viewport.offset);
|
|
8201
|
+
}
|
|
8202
|
+
setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8203
|
+
? next === 0 ? 0 : viewport.offset + 1
|
|
8204
|
+
: viewport.offset);
|
|
6209
8205
|
return next;
|
|
6210
8206
|
});
|
|
6211
8207
|
}
|
|
6212
8208
|
else if (key.downArrow || input.toLowerCase() === 'j') {
|
|
6213
8209
|
setFocus((value) => {
|
|
6214
|
-
const current =
|
|
8210
|
+
const current = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8211
|
+
? normalizedEffectiveFocus : dynamicActionIds ? effectiveFocus : value;
|
|
6215
8212
|
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1) : moveFocus(actions, current, 1);
|
|
6216
8213
|
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
6217
8214
|
dynamicMenuFocus.current = {
|
|
@@ -6220,11 +8217,24 @@ export function ConsoleAppV4(props) {
|
|
|
6220
8217
|
focusedActionId: dynamicActionIds[next],
|
|
6221
8218
|
};
|
|
6222
8219
|
}
|
|
6223
|
-
const viewport = route.name === '
|
|
6224
|
-
?
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
8220
|
+
const viewport = route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8221
|
+
? actionViewport(actions.slice(1), policy.mode === 'compact', Math.max(0, next - 1), Math.max(0, scrollOffset - 1), Math.max(1, pageActionBudget(5)))
|
|
8222
|
+
: route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
8223
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
8224
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
8225
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actionViewportStaticRows));
|
|
8226
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked') {
|
|
8227
|
+
const projection = buildDependencyGraphProjection({
|
|
8228
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
8229
|
+
rootPluginId: route.pluginId,
|
|
8230
|
+
direction: route.direction ?? 'dependencies',
|
|
8231
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
8232
|
+
});
|
|
8233
|
+
persistDependencyGraphContext(route, projection, expandedDependencyPluginIds ?? new Set(projection.defaultExpandedPluginIds), next, viewport.offset);
|
|
8234
|
+
}
|
|
8235
|
+
setScrollOffset(route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
8236
|
+
? next === 0 ? 0 : viewport.offset + 1
|
|
8237
|
+
: viewport.offset);
|
|
6228
8238
|
return next;
|
|
6229
8239
|
});
|
|
6230
8240
|
}
|
|
@@ -6238,7 +8248,7 @@ export function ConsoleAppV4(props) {
|
|
|
6238
8248
|
: { name: 'pluginDetail', pluginId: row.pluginId });
|
|
6239
8249
|
}
|
|
6240
8250
|
else if (key.return) {
|
|
6241
|
-
runAction(actions[
|
|
8251
|
+
runAction(actions[normalizedEffectiveFocus]);
|
|
6242
8252
|
}
|
|
6243
8253
|
});
|
|
6244
8254
|
if (tooSmall) {
|