@lesliechan721/aw 0.1.0-beta.13 → 0.1.0-beta.14
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/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 +2184 -564
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +609 -13
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +121 -6
- package/dist/src/tui/controller.js +667 -17
- 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 +164 -7
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +244 -8
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-write-task.d.ts +66 -0
- package/dist/src/tui/plugin-write-task.js +265 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -0
- package/dist/src/tui/router.d.ts +38 -2
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +105 -6
- package/dist/src/tui/screen-registry.js +31 -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
|
@@ -14,8 +14,10 @@ import { ActionRow, Callout, ChoiceRow, diagnosticDetailRowCount, DiagnosticSumm
|
|
|
14
14
|
import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
|
|
15
15
|
import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
|
|
16
16
|
import { catalogSourceFreshnessLabel, catalogSourceListPresentation, } from './catalog-sources/screens.js';
|
|
17
|
+
import { CatalogManagementIntro, CatalogPublicationSummary, CatalogStoreSummary, CatalogWorkspaceSummary, } from './catalog-management-screens.js';
|
|
17
18
|
import { operationDiagnosticLineCount, operationHistoryMeta, operationHistoryState, operationReportLineCount, OperationDiagnosticScreen, OperationReportPreviewScreen, } from './operation-diagnostic-screens.js';
|
|
18
19
|
import { resolveTuiSizePolicy } from './layout.js';
|
|
20
|
+
import { buildDependencyGraphProjection, } from './dependency-graph.js';
|
|
19
21
|
import { platformLabel, scopeLabel } from './glossary.js';
|
|
20
22
|
import { InputDisplay, removeLastGrapheme } from './input-display.js';
|
|
21
23
|
import { catalogRefPolicyForPreset } from './source-ref-presets.js';
|
|
@@ -30,6 +32,7 @@ function sourceOperationBlockLabel(reason) {
|
|
|
30
32
|
case 'configDraft': return '未保存的插件设置';
|
|
31
33
|
case 'sourceDraft': return '未保存的来源修改';
|
|
32
34
|
case 'batchDraft': return '未完成的批量操作选择';
|
|
35
|
+
case 'catalogDraft': return '未完成的 Catalog 管理输入';
|
|
33
36
|
case 'portableDraft': return '未完成的通用 Skills 选择';
|
|
34
37
|
case 'activeReview': return '等待确认的操作';
|
|
35
38
|
case 'reviewPreparation': return '正在准备确认信息的操作';
|
|
@@ -75,6 +78,11 @@ function isCharacterInputMode(route, searchMode) {
|
|
|
75
78
|
|| route.name === 'configObjectPropertyEditor'
|
|
76
79
|
&& (route.stage === 'key' || route.stage === 'custom')
|
|
77
80
|
|| route.name === 'sourceAdd' && route.step !== 'type' && route.step !== 'refPolicy'
|
|
81
|
+
|| route.name === 'catalogWorkspace'
|
|
82
|
+
&& (route.view === 'select' || route.view === 'create'
|
|
83
|
+
|| route.view === 'version' && route.step === 'catalogVersion')
|
|
84
|
+
|| route.name === 'catalogDev' && route.step !== undefined && route.step !== 'overview'
|
|
85
|
+
|| route.name === 'catalogPublish' && route.step !== 'overview'
|
|
78
86
|
|| route.name === 'scriptTimeout';
|
|
79
87
|
}
|
|
80
88
|
function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, sessionNavigation) {
|
|
@@ -104,7 +112,7 @@ function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy, se
|
|
|
104
112
|
rows.push(['s', '查看保存汇总']);
|
|
105
113
|
rows.push(['u', '恢复当前字段继承']);
|
|
106
114
|
}
|
|
107
|
-
if (route.name === 'sourceList' || route.name === 'sourceDetail') {
|
|
115
|
+
if (route.name === 'sourceList' || route.name === 'sourceDetail' || route.name === 'sourceMore') {
|
|
108
116
|
rows.push(['r', '重新检查来源']);
|
|
109
117
|
}
|
|
110
118
|
if (supportsTechnicalDetails) {
|
|
@@ -145,9 +153,49 @@ function batchActionLabel(action) {
|
|
|
145
153
|
cleanup: '清理',
|
|
146
154
|
}[action];
|
|
147
155
|
}
|
|
156
|
+
function pluginWriteTaskLabel(goal) {
|
|
157
|
+
return goal === 'update' ? '更新' : '恢复';
|
|
158
|
+
}
|
|
159
|
+
function pluginWriteTaskBlockLabel(reason) {
|
|
160
|
+
return {
|
|
161
|
+
catalogActivationRequired: '需要先更新并启用来源目录',
|
|
162
|
+
updateEvidenceIncomplete: '更新版本证据不完整',
|
|
163
|
+
updateIncompatible: '候选版本与当前 AW 不兼容',
|
|
164
|
+
restoreVersionChanged: '恢复会改变版本,需要单独决定',
|
|
165
|
+
restoreVersionUnverified: '当前安装版本无法验证',
|
|
166
|
+
manualRecoveryRequired: '需要选择手动处理方式',
|
|
167
|
+
taskActionConflict: '同一根因需要不同处理方式',
|
|
168
|
+
targetUnavailable: '这个安装位置已不可用',
|
|
169
|
+
}[reason ?? 'targetUnavailable'];
|
|
170
|
+
}
|
|
171
|
+
function pluginWriteTaskMethodLabel(method) {
|
|
172
|
+
return method === 'reinstall' ? '重新生成缺失内容' : '同步当前使用位置';
|
|
173
|
+
}
|
|
174
|
+
function pluginWriteTaskTargetKey(candidate) {
|
|
175
|
+
const { pluginId, platform, scope } = candidate.target;
|
|
176
|
+
return [pluginId, platform, scope].join('\0');
|
|
177
|
+
}
|
|
178
|
+
function pluginWriteTaskVersionEvidence(plugin, candidates) {
|
|
179
|
+
return Object.fromEntries(candidates.flatMap((candidate) => {
|
|
180
|
+
const update = plugin.update.targets.find((target) => target.platform === candidate.target.platform && target.scope === candidate.target.scope);
|
|
181
|
+
const version = candidate.target.expectedUpdate?.activeVersion
|
|
182
|
+
?? update?.candidateVersion ?? update?.availableVersion;
|
|
183
|
+
return version ? [[pluginWriteTaskTargetKey(candidate), version]] : [];
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
148
186
|
function statusText(plugin) {
|
|
149
187
|
if (plugin.configOnly)
|
|
150
188
|
return '已保存设置';
|
|
189
|
+
const updateTargets = (plugin.update?.targets ?? []).filter((target) => target.availableVersion !== undefined);
|
|
190
|
+
if (updateTargets.length === 1) {
|
|
191
|
+
const target = updateTargets[0];
|
|
192
|
+
return `可更新 ${target.installedVersion ?? '待确认'} → ${target.availableVersion}${target.requiresCatalogActivation ? '(需先更新来源)' : ''}`;
|
|
193
|
+
}
|
|
194
|
+
if (updateTargets.length > 1)
|
|
195
|
+
return `${updateTargets.length} 个位置可更新`;
|
|
196
|
+
const incompatibleCandidate = plugin.update?.targets.find((target) => target.candidateVersion !== undefined && (target.missingCapabilities?.length ?? 0) > 0);
|
|
197
|
+
if (incompatibleCandidate)
|
|
198
|
+
return `新版 ${incompatibleCandidate.candidateVersion} 暂不可用`;
|
|
151
199
|
return {
|
|
152
200
|
available: '未添加',
|
|
153
201
|
installed: '可用',
|
|
@@ -157,6 +205,21 @@ function statusText(plugin) {
|
|
|
157
205
|
: '来源不可用',
|
|
158
206
|
}[plugin.aggregateStatus];
|
|
159
207
|
}
|
|
208
|
+
function pluginUpdateDescription(plugin) {
|
|
209
|
+
const targets = (plugin.update?.targets ?? []).filter((target) => target.availableVersion !== undefined);
|
|
210
|
+
if (targets.length === 0) {
|
|
211
|
+
const incompatibleCandidate = plugin.update?.targets.find((target) => target.candidateVersion !== undefined && (target.missingCapabilities?.length ?? 0) > 0);
|
|
212
|
+
return incompatibleCandidate
|
|
213
|
+
? `候选版本缺少 ${incompatibleCandidate.missingCapabilities.length} 项 AW 功能支持;当前启用版本可继续使用。`
|
|
214
|
+
: undefined;
|
|
215
|
+
}
|
|
216
|
+
if (targets.some((target) => target.requiresCatalogActivation)) {
|
|
217
|
+
return plugin.update?.lastSuccessful
|
|
218
|
+
? `上次成功检查:${plugin.update.checkedAt ? new Date(plugin.update.checkedAt).toLocaleString('zh-CN', { hour12: false }) : '时间未知'};需先更新来源目录。`
|
|
219
|
+
: '已验证待检查版本;需先更新来源目录。';
|
|
220
|
+
}
|
|
221
|
+
return '当前启用目录已验证;可直接进入更新确认。';
|
|
222
|
+
}
|
|
160
223
|
function pluginPlacementSummary(plugin) {
|
|
161
224
|
if (plugin.configOnly) {
|
|
162
225
|
return plugin.savedConfigScopes.length > 0
|
|
@@ -728,6 +791,17 @@ function dependencyRelationDescription(item) {
|
|
|
728
791
|
function dependencySummaryText(summary) {
|
|
729
792
|
return `${summary.directCount} 个直接 · ${summary.totalCount} 个总计`;
|
|
730
793
|
}
|
|
794
|
+
function dependencyGraphRowStatus(row) {
|
|
795
|
+
if (row.nodeState === 'ready')
|
|
796
|
+
return { value: '正常', status: 'success' };
|
|
797
|
+
if (row.nodeState === 'missing')
|
|
798
|
+
return { value: '缺失', status: 'error' };
|
|
799
|
+
if (row.nodeState === 'needsAttention')
|
|
800
|
+
return { value: '待恢复', status: 'warning' };
|
|
801
|
+
if (row.nodeState === 'unavailable')
|
|
802
|
+
return { value: '不可用', status: 'error' };
|
|
803
|
+
return { value: '未检查', status: 'muted' };
|
|
804
|
+
}
|
|
731
805
|
function isCatalogSourcesBootstrapFlow(snapshot) {
|
|
732
806
|
const route = snapshot.router.current.route;
|
|
733
807
|
if (route.name === 'reviewPreparing') {
|
|
@@ -790,7 +864,7 @@ export function ConsoleAppV4(props) {
|
|
|
790
864
|
const [focus, setFocus] = useState(0);
|
|
791
865
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
792
866
|
const [searchFocus, setSearchFocus] = useState(false);
|
|
793
|
-
const [query, setQuery] = useState('');
|
|
867
|
+
const [query, setQuery] = useState(props.controller.snapshot.router.current.context.query ?? '');
|
|
794
868
|
const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
|
|
795
869
|
const [scope, setScope] = useState('user');
|
|
796
870
|
const [selectedTargets, setSelectedTargets] = useState([]);
|
|
@@ -803,6 +877,9 @@ export function ConsoleAppV4(props) {
|
|
|
803
877
|
const [dirty, setDirty] = useState(false);
|
|
804
878
|
const [configPage, setConfigPage] = useState();
|
|
805
879
|
const [configLayerCounts, setConfigLayerCounts] = useState({});
|
|
880
|
+
const [configLayerPages, setConfigLayerPages] = useState({});
|
|
881
|
+
const [configSaveImpact, setConfigSaveImpact] = useState();
|
|
882
|
+
const [configSaveImpactError, setConfigSaveImpactError] = useState(false);
|
|
806
883
|
const [draftUpdates, setDraftUpdates] = useState({});
|
|
807
884
|
const [draftRemoves, setDraftRemoves] = useState([]);
|
|
808
885
|
const [objectDrafts, setObjectDrafts] = useState({});
|
|
@@ -816,8 +893,24 @@ export function ConsoleAppV4(props) {
|
|
|
816
893
|
const [sourceDraft, setSourceDraft] = useState();
|
|
817
894
|
const [batchDraft, setBatchDraft] = useState();
|
|
818
895
|
const [batchTargetOffset, setBatchTargetOffset] = useState(0);
|
|
896
|
+
const [expandedDependencyPluginIds, setExpandedDependencyPluginIds] = useState();
|
|
819
897
|
const [sourceInput, setSourceInput] = useState('');
|
|
820
898
|
const [sourceInputError, setSourceInputError] = useState();
|
|
899
|
+
const [catalogInput, setCatalogInput] = useState('');
|
|
900
|
+
const [catalogInputError, setCatalogInputError] = useState();
|
|
901
|
+
const [catalogWorkspace, setCatalogWorkspace] = useState();
|
|
902
|
+
const [catalogValidation, setCatalogValidation] = useState();
|
|
903
|
+
const [catalogDev, setCatalogDev] = useState();
|
|
904
|
+
const [catalogStore, setCatalogStore] = useState();
|
|
905
|
+
const [catalogPublication, setCatalogPublication] = useState();
|
|
906
|
+
const [catalogLoading, setCatalogLoading] = useState(false);
|
|
907
|
+
const [catalogWorkspaceDraft, setCatalogWorkspaceDraft] = useState({
|
|
908
|
+
ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0',
|
|
909
|
+
});
|
|
910
|
+
const [catalogPublishDraft, setCatalogPublishDraft] = useState({
|
|
911
|
+
targetKind: 'remote', target: '', branch: 'catalog',
|
|
912
|
+
});
|
|
913
|
+
const [catalogDevSourceId, setCatalogDevSourceId] = useState('');
|
|
821
914
|
const [pendingReviewIntent, setPendingReviewIntent] = useState();
|
|
822
915
|
const pendingReviewIntentRef = useRef(undefined);
|
|
823
916
|
const [scriptTimeoutInput, setScriptTimeoutInput] = useState(String(SCRIPT_TIMEOUT_DEFAULT_MS));
|
|
@@ -834,9 +927,10 @@ export function ConsoleAppV4(props) {
|
|
|
834
927
|
const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
|
|
835
928
|
const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
|
|
836
929
|
const dynamicMenuFocus = useRef(undefined);
|
|
837
|
-
const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
|
|
930
|
+
const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0, catalog: 0 });
|
|
838
931
|
const previousExternalDraftValues = useRef({
|
|
839
932
|
draftUpdates, draftRemoves, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
|
|
933
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId,
|
|
840
934
|
});
|
|
841
935
|
const externalDraftFlowIds = useRef(new Map());
|
|
842
936
|
const exitSent = useRef(false);
|
|
@@ -939,6 +1033,36 @@ export function ConsoleAppV4(props) {
|
|
|
939
1033
|
setBatchDraft(undefined);
|
|
940
1034
|
setDirty(false);
|
|
941
1035
|
}, [route.name, snapshot.result?.operationKind]);
|
|
1036
|
+
useEffect(() => {
|
|
1037
|
+
if (route.name !== 'result'
|
|
1038
|
+
|| snapshot.result?.state !== 'succeeded'
|
|
1039
|
+
|| snapshot.result.operationKind !== 'pluginWriteTask'
|
|
1040
|
+
|| !('returnRoute' in snapshot.result)
|
|
1041
|
+
|| snapshot.result.returnRoute?.name !== 'pluginWriteTask'
|
|
1042
|
+
|| snapshot.result.returnRoute.batchOrigin?.owner !== 'batch-write'
|
|
1043
|
+
|| snapshot.result.returnRoute.batchOrigin.flowId
|
|
1044
|
+
!== batchDraft?.flowId)
|
|
1045
|
+
return;
|
|
1046
|
+
setBatchDraft(undefined);
|
|
1047
|
+
setDirty(false);
|
|
1048
|
+
}, [batchDraft?.flowId, route.name, snapshot.result]);
|
|
1049
|
+
useEffect(() => {
|
|
1050
|
+
if (route.name !== 'result'
|
|
1051
|
+
|| snapshot.result?.state !== 'succeeded'
|
|
1052
|
+
|| snapshot.result.operationKind !== 'catalogManagement')
|
|
1053
|
+
return;
|
|
1054
|
+
const action = snapshot.result.summary.catalogManagement?.action;
|
|
1055
|
+
if (action === 'workspaceInit') {
|
|
1056
|
+
setCatalogInput('');
|
|
1057
|
+
setCatalogWorkspaceDraft({
|
|
1058
|
+
ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0',
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
if (action === 'publishApply') {
|
|
1062
|
+
setCatalogInput('');
|
|
1063
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: 'catalog' });
|
|
1064
|
+
}
|
|
1065
|
+
}, [route.name, snapshot.result]);
|
|
942
1066
|
useEffect(() => {
|
|
943
1067
|
if (!batchDraft || (route.name !== 'batchSelection' && route.name !== 'batchPlacement'))
|
|
944
1068
|
return;
|
|
@@ -948,6 +1072,74 @@ export function ConsoleAppV4(props) {
|
|
|
948
1072
|
if (route.name !== 'sourceAdd')
|
|
949
1073
|
setSourceInput('');
|
|
950
1074
|
}, [route.name]);
|
|
1075
|
+
useEffect(() => {
|
|
1076
|
+
if (route.name !== 'catalogWorkspace' || route.view !== 'overview')
|
|
1077
|
+
return;
|
|
1078
|
+
let current = true;
|
|
1079
|
+
setCatalogLoading(true);
|
|
1080
|
+
setCatalogInputError(undefined);
|
|
1081
|
+
void props.controller.getCatalogWorkspaceManagement()
|
|
1082
|
+
.then((value) => { if (current)
|
|
1083
|
+
setCatalogWorkspace(value); })
|
|
1084
|
+
.catch((error) => {
|
|
1085
|
+
if (current)
|
|
1086
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]));
|
|
1087
|
+
})
|
|
1088
|
+
.finally(() => { if (current)
|
|
1089
|
+
setCatalogLoading(false); });
|
|
1090
|
+
return () => { current = false; };
|
|
1091
|
+
}, [props.controller, route.name, route.name === 'catalogWorkspace' ? route.view : '']);
|
|
1092
|
+
useEffect(() => {
|
|
1093
|
+
if (route.name !== 'catalogDev' || route.step && route.step !== 'overview')
|
|
1094
|
+
return;
|
|
1095
|
+
let current = true;
|
|
1096
|
+
setCatalogLoading(true);
|
|
1097
|
+
setCatalogInputError(undefined);
|
|
1098
|
+
void props.controller.getCatalogDevManagement()
|
|
1099
|
+
.then((value) => { if (current)
|
|
1100
|
+
setCatalogDev(value); })
|
|
1101
|
+
.catch((error) => {
|
|
1102
|
+
if (current)
|
|
1103
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]));
|
|
1104
|
+
})
|
|
1105
|
+
.finally(() => { if (current)
|
|
1106
|
+
setCatalogLoading(false); });
|
|
1107
|
+
return () => { current = false; };
|
|
1108
|
+
}, [props.controller, route.name, route.name === 'catalogDev' ? route.step : '']);
|
|
1109
|
+
useEffect(() => {
|
|
1110
|
+
if (route.name !== 'catalogStore')
|
|
1111
|
+
return;
|
|
1112
|
+
let current = true;
|
|
1113
|
+
setCatalogLoading(true);
|
|
1114
|
+
setCatalogInputError(undefined);
|
|
1115
|
+
void props.controller.getCatalogStoreManagement()
|
|
1116
|
+
.then((value) => { if (current)
|
|
1117
|
+
setCatalogStore(value); })
|
|
1118
|
+
.catch((error) => {
|
|
1119
|
+
if (current)
|
|
1120
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], []));
|
|
1121
|
+
})
|
|
1122
|
+
.finally(() => { if (current)
|
|
1123
|
+
setCatalogLoading(false); });
|
|
1124
|
+
return () => { current = false; };
|
|
1125
|
+
}, [props.controller, route.name]);
|
|
1126
|
+
useEffect(() => {
|
|
1127
|
+
if (route.name !== 'catalogPublish' || route.step !== 'overview')
|
|
1128
|
+
return;
|
|
1129
|
+
let current = true;
|
|
1130
|
+
setCatalogLoading(true);
|
|
1131
|
+
setCatalogInputError(undefined);
|
|
1132
|
+
void props.controller.getCatalogPublicationManagement()
|
|
1133
|
+
.then((value) => { if (current)
|
|
1134
|
+
setCatalogPublication(value); })
|
|
1135
|
+
.catch((error) => {
|
|
1136
|
+
if (current)
|
|
1137
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogPublishDraft.target]));
|
|
1138
|
+
})
|
|
1139
|
+
.finally(() => { if (current)
|
|
1140
|
+
setCatalogLoading(false); });
|
|
1141
|
+
return () => { current = false; };
|
|
1142
|
+
}, [props.controller, route.name, route.name === 'catalogPublish' ? route.step : '']);
|
|
951
1143
|
useEffect(() => {
|
|
952
1144
|
if (route.name === 'operationReportPreview')
|
|
953
1145
|
return;
|
|
@@ -993,8 +1185,14 @@ export function ConsoleAppV4(props) {
|
|
|
993
1185
|
externalDraftRevision.current.source += 1;
|
|
994
1186
|
if (previous.batchDraft !== batchDraft)
|
|
995
1187
|
externalDraftRevision.current.batch += 1;
|
|
1188
|
+
if (previous.catalogInput !== catalogInput
|
|
1189
|
+
|| previous.catalogWorkspaceDraft !== catalogWorkspaceDraft
|
|
1190
|
+
|| previous.catalogPublishDraft !== catalogPublishDraft
|
|
1191
|
+
|| previous.catalogDevSourceId !== catalogDevSourceId)
|
|
1192
|
+
externalDraftRevision.current.catalog += 1;
|
|
996
1193
|
previousExternalDraftValues.current = {
|
|
997
1194
|
draftUpdates, draftRemoves, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
|
|
1195
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId,
|
|
998
1196
|
};
|
|
999
1197
|
const currentFlowId = snapshot.router.currentFlowId;
|
|
1000
1198
|
if (currentFlowId && configRouteOwner) {
|
|
@@ -1002,8 +1200,6 @@ export function ConsoleAppV4(props) {
|
|
|
1002
1200
|
}
|
|
1003
1201
|
if (currentFlowId && sourceDraft)
|
|
1004
1202
|
externalDraftFlowIds.current.set(sourceDraft.owner, currentFlowId);
|
|
1005
|
-
if (currentFlowId && batchDraft)
|
|
1006
|
-
externalDraftFlowIds.current.set('batch-write', currentFlowId);
|
|
1007
1203
|
const drafts = [];
|
|
1008
1204
|
if ((dirty || changedObjectDraftKeys.length > 0) && draftOwner) {
|
|
1009
1205
|
const flowId = externalDraftFlowIds.current.get(draftOwner);
|
|
@@ -1020,16 +1216,31 @@ export function ConsoleAppV4(props) {
|
|
|
1020
1216
|
});
|
|
1021
1217
|
}
|
|
1022
1218
|
if (batchDraft) {
|
|
1023
|
-
const flowId = externalDraftFlowIds.current.get('batch-write');
|
|
1024
1219
|
drafts.push({
|
|
1025
1220
|
kind: 'batchDraft', owner: 'batch-write', revision: externalDraftRevision.current.batch,
|
|
1026
|
-
|
|
1221
|
+
flowId: batchDraft.flowId,
|
|
1027
1222
|
});
|
|
1028
1223
|
}
|
|
1224
|
+
const catalogWorkspaceDirty = catalogWorkspaceDraft.ownerName !== ''
|
|
1225
|
+
|| catalogWorkspaceDraft.ownerUrl !== '' || catalogWorkspaceDraft.displayName !== '';
|
|
1226
|
+
const catalogPublishDirty = catalogPublishDraft.target !== '';
|
|
1227
|
+
const catalogDraftActive = route.name === 'catalogWorkspace'
|
|
1228
|
+
&& (route.view === 'create' || route.view === 'version' && route.step === 'catalogVersion')
|
|
1229
|
+
|| route.name === 'catalogPublish' && route.step !== 'overview'
|
|
1230
|
+
|| route.name === 'catalogDev' && route.step !== undefined && route.step !== 'overview'
|
|
1231
|
+
|| route.name === 'catalogWorkspace' && route.view === 'overview' && catalogWorkspaceDirty
|
|
1232
|
+
|| route.name === 'catalogPublish' && route.step === 'overview' && catalogPublishDirty;
|
|
1233
|
+
if (catalogDraftActive)
|
|
1234
|
+
drafts.push({
|
|
1235
|
+
kind: 'catalogDraft', owner: 'catalog-management', revision: externalDraftRevision.current.catalog,
|
|
1236
|
+
});
|
|
1029
1237
|
props.controller.setExternalDraftState(drafts);
|
|
1030
1238
|
}, [
|
|
1031
1239
|
props.controller, batchDraft, changedObjectDraftKeys, configRouteOwner, dirty, draftOwner,
|
|
1032
1240
|
draftRemoves, draftUpdates, objectDrafts, snapshot.router.currentFlowId, sourceDraft,
|
|
1241
|
+
catalogInput, catalogWorkspaceDraft, catalogPublishDraft, catalogDevSourceId, route.name,
|
|
1242
|
+
route.name === 'catalogWorkspace' ? `${route.view}:${route.step ?? ''}` : '',
|
|
1243
|
+
route.name === 'catalogPublish' || route.name === 'catalogDev' ? route.step : '',
|
|
1033
1244
|
]);
|
|
1034
1245
|
useEffect(() => () => props.controller.setExternalDraftState(), [props.controller]);
|
|
1035
1246
|
const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
|
|
@@ -1071,17 +1282,19 @@ export function ConsoleAppV4(props) {
|
|
|
1071
1282
|
if (route.name !== 'configLayer')
|
|
1072
1283
|
return;
|
|
1073
1284
|
const abort = new AbortController();
|
|
1285
|
+
setConfigLayerPages({});
|
|
1074
1286
|
const layers = [
|
|
1075
1287
|
{ key: 'user', layer: { scope: 'user' } },
|
|
1076
1288
|
{ key: 'project', layer: { scope: 'project' } },
|
|
1077
1289
|
];
|
|
1078
1290
|
void Promise.allSettled(layers.map(async ({ key, layer }) => ({
|
|
1079
1291
|
key,
|
|
1080
|
-
|
|
1292
|
+
page: await props.controller.getConfigPage(route.pluginId, layer, abort.signal),
|
|
1081
1293
|
}))).then((results) => {
|
|
1082
1294
|
if (abort.signal.aborted)
|
|
1083
1295
|
return;
|
|
1084
|
-
setConfigLayerCounts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.
|
|
1296
|
+
setConfigLayerCounts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page.storedCount]] : [])));
|
|
1297
|
+
setConfigLayerPages(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page]] : [])));
|
|
1085
1298
|
});
|
|
1086
1299
|
return () => abort.abort();
|
|
1087
1300
|
}, [props.controller, route.name, route.name === 'configLayer' ? route.pluginId : '']);
|
|
@@ -1202,7 +1415,7 @@ export function ConsoleAppV4(props) {
|
|
|
1202
1415
|
useEffect(() => {
|
|
1203
1416
|
let count;
|
|
1204
1417
|
if (route.name === 'sourceList')
|
|
1205
|
-
count = props.controller.listSources().length +
|
|
1418
|
+
count = props.controller.listSources().length + 3;
|
|
1206
1419
|
else if (route.name === 'issueList') {
|
|
1207
1420
|
const issueCount = props.controller.listUserIssues().length;
|
|
1208
1421
|
count = issueCount || 2;
|
|
@@ -1217,7 +1430,17 @@ export function ConsoleAppV4(props) {
|
|
|
1217
1430
|
...(query ? { query } : {}),
|
|
1218
1431
|
installFilter,
|
|
1219
1432
|
}).length;
|
|
1220
|
-
const
|
|
1433
|
+
const listed = new Set(props.controller.listPlugins({
|
|
1434
|
+
mode: route.mode,
|
|
1435
|
+
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1436
|
+
...(query ? { query } : {}),
|
|
1437
|
+
installFilter,
|
|
1438
|
+
}).map((item) => item.pluginId));
|
|
1439
|
+
const menuCount = itemCount > 0 && !query ? 1
|
|
1440
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1441
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1442
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1443
|
+
.some((candidate) => candidate.target.scope === 'project')) : 0;
|
|
1221
1444
|
count = itemCount + menuCount || 1;
|
|
1222
1445
|
}
|
|
1223
1446
|
if (count === undefined)
|
|
@@ -1239,6 +1462,38 @@ export function ConsoleAppV4(props) {
|
|
|
1239
1462
|
return;
|
|
1240
1463
|
setScrollOffset(0);
|
|
1241
1464
|
}, [route.name, route.name === 'operationReportPreview' ? route.recordId : '']);
|
|
1465
|
+
useEffect(() => {
|
|
1466
|
+
if (route.name !== 'pluginDependencies')
|
|
1467
|
+
return;
|
|
1468
|
+
setExpandedDependencyPluginIds(undefined);
|
|
1469
|
+
setFocus(0);
|
|
1470
|
+
setScrollOffset(0);
|
|
1471
|
+
}, [
|
|
1472
|
+
route.name,
|
|
1473
|
+
route.name === 'pluginDependencies' ? route.pluginId : '',
|
|
1474
|
+
route.name === 'pluginDependencies' ? route.direction ?? 'dependencies' : '',
|
|
1475
|
+
]);
|
|
1476
|
+
useEffect(() => {
|
|
1477
|
+
if (route.name !== 'pluginList' || snapshot.phase !== 'ready'
|
|
1478
|
+
|| snapshot.router.current.context.focusIndex !== undefined)
|
|
1479
|
+
return;
|
|
1480
|
+
const items = props.controller.listPlugins({
|
|
1481
|
+
mode: route.mode,
|
|
1482
|
+
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1483
|
+
...(query ? { query } : {}),
|
|
1484
|
+
installFilter,
|
|
1485
|
+
});
|
|
1486
|
+
if (items.length === 0)
|
|
1487
|
+
return;
|
|
1488
|
+
const listed = new Set(items.map((item) => item.pluginId));
|
|
1489
|
+
const menuCount = query ? 0 : 1
|
|
1490
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1491
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1492
|
+
+ Number(route.mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1493
|
+
.some((candidate) => candidate.target.scope === 'project'));
|
|
1494
|
+
setFocus(menuCount);
|
|
1495
|
+
}, [props.controller, route.name, route.name === 'pluginList' ? route.mode : '',
|
|
1496
|
+
route.name === 'pluginList' ? route.sourceId : '', snapshot.phase]);
|
|
1242
1497
|
useEffect(() => {
|
|
1243
1498
|
setBatchTargetOffset(0);
|
|
1244
1499
|
}, [route.name, route.name === 'review' || route.name === 'result' ? route.operationId : '']);
|
|
@@ -1383,11 +1638,9 @@ export function ConsoleAppV4(props) {
|
|
|
1383
1638
|
let activeConfigTabId;
|
|
1384
1639
|
const dynamicMenu = plugin && route.name === 'pluginPreview'
|
|
1385
1640
|
? buildPluginPreviewActions(plugin)
|
|
1386
|
-
: plugin && route.name === '
|
|
1387
|
-
?
|
|
1388
|
-
:
|
|
1389
|
-
? buildPlatformActions(plugin, route.platform)
|
|
1390
|
-
: undefined;
|
|
1641
|
+
: plugin && route.name === 'platform'
|
|
1642
|
+
? buildPlatformActions(plugin, route.platform)
|
|
1643
|
+
: undefined;
|
|
1391
1644
|
const dynamicActionIds = dynamicMenu?.map(pluginMenuActionId);
|
|
1392
1645
|
const dynamicActionSignature = dynamicActionIds?.join('\0');
|
|
1393
1646
|
const previousDynamicFocus = dynamicMenuFocus.current;
|
|
@@ -1418,6 +1671,16 @@ export function ConsoleAppV4(props) {
|
|
|
1418
1671
|
const base = policy.mode === 'compact' ? compactRows : standardRows;
|
|
1419
1672
|
return base + Math.max(0, size.rows - baseline);
|
|
1420
1673
|
};
|
|
1674
|
+
const pluginListMenuCount = (mode, items, queryValue = query) => {
|
|
1675
|
+
if (items.length === 0 || queryValue)
|
|
1676
|
+
return 0;
|
|
1677
|
+
const listed = new Set(items.map((item) => item.pluginId));
|
|
1678
|
+
return 1
|
|
1679
|
+
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('update')
|
|
1680
|
+
.some((candidate) => candidate.eligible && listed.has(candidate.target.pluginId)))
|
|
1681
|
+
+ Number(mode === 'installed' && props.controller.listPluginWriteTaskCandidates('restore')
|
|
1682
|
+
.some((candidate) => candidate.target.scope === 'project'));
|
|
1683
|
+
};
|
|
1421
1684
|
const go = (next) => {
|
|
1422
1685
|
props.controller.updatePageContext({
|
|
1423
1686
|
focusIndex: focus,
|
|
@@ -1457,12 +1720,12 @@ export function ConsoleAppV4(props) {
|
|
|
1457
1720
|
? items.findIndex((item) => item.pluginId === restored.focusedEntityId)
|
|
1458
1721
|
: -1;
|
|
1459
1722
|
restoredFocus = entityIndex >= 0
|
|
1460
|
-
? entityIndex
|
|
1461
|
-
: Math.min(restoredFocus, Math.max(0, items.length - 1));
|
|
1723
|
+
? pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + entityIndex
|
|
1724
|
+
: Math.min(restoredFocus, Math.max(0, pluginListMenuCount(restoredFrame.route.mode, items, restored.query ?? '') + items.length - 1));
|
|
1462
1725
|
}
|
|
1463
1726
|
else if (restoredFrame.route.name === 'sourceList') {
|
|
1464
1727
|
const sources = props.controller.listSources();
|
|
1465
|
-
itemCount = sources.length +
|
|
1728
|
+
itemCount = sources.length + 3;
|
|
1466
1729
|
const entityIndex = restored.focusedEntityId
|
|
1467
1730
|
? sources.findIndex((source) => source.id === restored.focusedEntityId)
|
|
1468
1731
|
: -1;
|
|
@@ -1532,19 +1795,20 @@ export function ConsoleAppV4(props) {
|
|
|
1532
1795
|
setShowTechnical(false);
|
|
1533
1796
|
setActionError(undefined);
|
|
1534
1797
|
};
|
|
1535
|
-
const beginReview = (intent) => {
|
|
1798
|
+
const beginReview = (intent, returnRoute) => {
|
|
1536
1799
|
const supportsScriptTimeout = intent.kind === 'pluginWrite'
|
|
1537
1800
|
&& (intent.action === 'install' || intent.action === 'sync'
|
|
1538
1801
|
|| intent.action === 'refreshAndSync' || intent.action === 'reinstall')
|
|
1539
1802
|
|| intent.kind === 'pluginWriteBatch'
|
|
1540
1803
|
&& (intent.action === 'install' || intent.action === 'sync' || intent.action === 'reinstall')
|
|
1804
|
+
|| intent.kind === 'pluginWriteTask'
|
|
1541
1805
|
|| intent.kind === 'configSave' && intent.apply;
|
|
1542
1806
|
const pendingIntent = supportsScriptTimeout ? intent : undefined;
|
|
1543
1807
|
pendingReviewIntentRef.current = pendingIntent;
|
|
1544
1808
|
setPendingReviewIntent(pendingIntent);
|
|
1545
|
-
const underlyingResultRoute = route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
|
|
1809
|
+
const underlyingResultRoute = returnRoute ?? (route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
|
|
1546
1810
|
? snapshot.result.returnRoute
|
|
1547
|
-
: undefined;
|
|
1811
|
+
: undefined);
|
|
1548
1812
|
props.controller.updatePageContext({
|
|
1549
1813
|
focusIndex: focus,
|
|
1550
1814
|
scrollOffset,
|
|
@@ -1566,28 +1830,96 @@ export function ConsoleAppV4(props) {
|
|
|
1566
1830
|
focusedEntityId: route.name === 'pluginList' ? pluginRows[focus]?.pluginId : undefined,
|
|
1567
1831
|
});
|
|
1568
1832
|
props.controller.beginPluginWriteBatch();
|
|
1833
|
+
const flowId = props.controller.snapshot.router.currentFlowId;
|
|
1834
|
+
if (!flowId)
|
|
1835
|
+
throw new Error('批量插件流程缺少 flowId。');
|
|
1569
1836
|
setBatchDraft({
|
|
1837
|
+
flowId,
|
|
1570
1838
|
...(action ? { action } : {}),
|
|
1839
|
+
mode: route.name === 'pluginList' ? route.mode : action === 'install' ? 'browse' : 'installed',
|
|
1840
|
+
stage: 'select',
|
|
1571
1841
|
selection: 'selected',
|
|
1572
1842
|
selectedPluginIds: [],
|
|
1573
1843
|
platform: 'both',
|
|
1574
1844
|
scope: 'project',
|
|
1845
|
+
...(route.name === 'pluginList' && route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
1846
|
+
query,
|
|
1847
|
+
installFilter,
|
|
1848
|
+
showSelected: false,
|
|
1575
1849
|
});
|
|
1576
1850
|
setDirty(true);
|
|
1577
1851
|
setFocus(0);
|
|
1578
1852
|
setScrollOffset(0);
|
|
1579
1853
|
setSearchFocus(false);
|
|
1580
1854
|
};
|
|
1855
|
+
const batchCandidates = (draft) => {
|
|
1856
|
+
const mode = draft.mode;
|
|
1857
|
+
const all = props.controller.listPlugins({ mode });
|
|
1858
|
+
if (draft.showSelected) {
|
|
1859
|
+
const selected = new Set(draft.selectedPluginIds);
|
|
1860
|
+
return all.filter((candidate) => selected.has(candidate.pluginId));
|
|
1861
|
+
}
|
|
1862
|
+
return props.controller.listPlugins({
|
|
1863
|
+
mode,
|
|
1864
|
+
...(draft.sourceId ? { sourceId: draft.sourceId } : {}),
|
|
1865
|
+
...(draft.query ? { query: draft.query } : {}),
|
|
1866
|
+
installFilter: draft.installFilter,
|
|
1867
|
+
});
|
|
1868
|
+
};
|
|
1869
|
+
const batchCandidateBlockReason = (draft, candidate) => candidate.configOnly
|
|
1870
|
+
? '来源中没有可信插件定义'
|
|
1871
|
+
: draft.mode === 'browse' && hasInstalledPlacement(candidate)
|
|
1872
|
+
? '这个插件已经添加'
|
|
1873
|
+
: undefined;
|
|
1874
|
+
const toggleBatchPlugin = (pluginId) => {
|
|
1875
|
+
setBatchDraft((current) => current ? {
|
|
1876
|
+
...current,
|
|
1877
|
+
selectedPluginIds: current.selectedPluginIds.includes(pluginId)
|
|
1878
|
+
? current.selectedPluginIds.filter((candidate) => candidate !== pluginId)
|
|
1879
|
+
: [...current.selectedPluginIds, pluginId],
|
|
1880
|
+
} : current);
|
|
1881
|
+
};
|
|
1882
|
+
const existingBatchTargets = (pluginIds) => pluginIds.flatMap((pluginId) => props.controller.getPluginOverview(pluginId)
|
|
1883
|
+
?.installedPlacements.flatMap((placement) => placement.state === 'installed'
|
|
1884
|
+
? [{ pluginId, platform: placement.platform, scope: placement.scope }]
|
|
1885
|
+
: placement.state === 'scopeConflict'
|
|
1886
|
+
? placement.placements.map(({ scope: targetScope }) => ({
|
|
1887
|
+
pluginId, platform: placement.platform, scope: targetScope,
|
|
1888
|
+
}))
|
|
1889
|
+
: []) ?? []);
|
|
1890
|
+
const openSelectedPluginTask = (goal, pluginIds, scopeConstraint) => {
|
|
1891
|
+
const selected = new Set(pluginIds);
|
|
1892
|
+
const candidates = props.controller.listPluginWriteTaskCandidates(goal)
|
|
1893
|
+
.filter((candidate) => selected.has(candidate.target.pluginId)
|
|
1894
|
+
&& (!scopeConstraint || candidate.target.scope === scopeConstraint));
|
|
1895
|
+
const first = candidates[0];
|
|
1896
|
+
if (!first) {
|
|
1897
|
+
setActionError(goal === 'update'
|
|
1898
|
+
? '所选插件没有经过版本证据确认的更新。'
|
|
1899
|
+
: '所选插件没有可恢复的现有位置。');
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
go({
|
|
1903
|
+
name: 'pluginWriteTask',
|
|
1904
|
+
pluginId: first.target.pluginId,
|
|
1905
|
+
goal,
|
|
1906
|
+
targetKeys: candidates.map(pluginWriteTaskTargetKey),
|
|
1907
|
+
...(batchDraft ? {
|
|
1908
|
+
batchOrigin: { flowId: batchDraft.flowId, owner: 'batch-write' },
|
|
1909
|
+
} : {}),
|
|
1910
|
+
...(goal === 'update'
|
|
1911
|
+
? { versionEvidence: Object.fromEntries(candidates.flatMap((candidate) => candidate.target.expectedUpdate
|
|
1912
|
+
? [[pluginWriteTaskTargetKey(candidate), candidate.target.expectedUpdate.activeVersion]]
|
|
1913
|
+
: [])) }
|
|
1914
|
+
: {}),
|
|
1915
|
+
});
|
|
1916
|
+
};
|
|
1581
1917
|
const continueInstallAfterTargets = async (candidate, targets) => {
|
|
1582
1918
|
if (props.controller.snapshot.installPlacementDraft?.pluginId !== candidate.pluginId) {
|
|
1583
1919
|
props.controller.beginInstallFlow(candidate.pluginId, scope);
|
|
1584
1920
|
}
|
|
1585
1921
|
props.controller.setInstallPlatforms(targets);
|
|
1586
|
-
|
|
1587
|
-
if (hasConfigurableDependencies) {
|
|
1588
|
-
go({ name: 'dependencyPlacement', pluginId: candidate.pluginId });
|
|
1589
|
-
return;
|
|
1590
|
-
}
|
|
1922
|
+
await props.controller.prepareInstallDependencyPlacement();
|
|
1591
1923
|
beginReview(props.controller.installPlacementIntent());
|
|
1592
1924
|
};
|
|
1593
1925
|
const beginPortable = async (pluginId) => {
|
|
@@ -2187,27 +2519,33 @@ export function ConsoleAppV4(props) {
|
|
|
2187
2519
|
setObjectError(undefined);
|
|
2188
2520
|
setConfigActionError(undefined);
|
|
2189
2521
|
};
|
|
2190
|
-
const
|
|
2522
|
+
const configSaveIntent = (apply) => {
|
|
2191
2523
|
if (!configRoute || configRoute.layer.scope === 'effective' || !currentConfigPage || !dirty)
|
|
2192
2524
|
return;
|
|
2525
|
+
const fields = currentConfigPage.groups.flatMap((group) => group.fields);
|
|
2526
|
+
return {
|
|
2527
|
+
kind: 'configSave',
|
|
2528
|
+
pluginId: configRoute.pluginId,
|
|
2529
|
+
scope: configRoute.layer.scope,
|
|
2530
|
+
apply,
|
|
2531
|
+
updates: draftUpdates,
|
|
2532
|
+
removeKeys: draftRemoves,
|
|
2533
|
+
sensitiveKeys: fields.filter((field) => field.sensitive).map((field) => field.key),
|
|
2534
|
+
privateKeys: fields.filter((field) => configObjectHasSensitiveChild(field.definition))
|
|
2535
|
+
.map((field) => field.key),
|
|
2536
|
+
changes: configChanges(),
|
|
2537
|
+
};
|
|
2538
|
+
};
|
|
2539
|
+
const beginConfigSave = async (apply) => {
|
|
2193
2540
|
if (configDraftIssues.length > 0) {
|
|
2194
2541
|
setConfigActionError('字段已变化。请先放弃失效草稿,或返回继续检查来源。');
|
|
2195
2542
|
return;
|
|
2196
2543
|
}
|
|
2197
|
-
const
|
|
2544
|
+
const intent = configSaveIntent(apply);
|
|
2545
|
+
if (!intent)
|
|
2546
|
+
return;
|
|
2198
2547
|
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
|
-
});
|
|
2548
|
+
await beginReview(intent);
|
|
2211
2549
|
setConfigActionError(undefined);
|
|
2212
2550
|
}
|
|
2213
2551
|
catch (error) {
|
|
@@ -2263,6 +2601,35 @@ export function ConsoleAppV4(props) {
|
|
|
2263
2601
|
return;
|
|
2264
2602
|
setPendingPresetId(undefined);
|
|
2265
2603
|
}, [pendingPresetId, route.name, route.name === 'configFields' ? route.layer.scope : '']);
|
|
2604
|
+
useEffect(() => {
|
|
2605
|
+
if (route.name !== 'configSave') {
|
|
2606
|
+
setConfigSaveImpact(undefined);
|
|
2607
|
+
setConfigSaveImpactError(false);
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
const intent = configSaveIntent(false);
|
|
2611
|
+
if (!intent)
|
|
2612
|
+
return;
|
|
2613
|
+
const abort = new AbortController();
|
|
2614
|
+
setConfigSaveImpact(undefined);
|
|
2615
|
+
setConfigSaveImpactError(false);
|
|
2616
|
+
void props.controller.previewConfigSaveImpact(intent, abort.signal)
|
|
2617
|
+
.then((impact) => {
|
|
2618
|
+
if (!abort.signal.aborted)
|
|
2619
|
+
setConfigSaveImpact(impact);
|
|
2620
|
+
})
|
|
2621
|
+
.catch(() => {
|
|
2622
|
+
if (!abort.signal.aborted)
|
|
2623
|
+
setConfigSaveImpactError(true);
|
|
2624
|
+
});
|
|
2625
|
+
return () => abort.abort();
|
|
2626
|
+
}, [
|
|
2627
|
+
props.controller,
|
|
2628
|
+
route.name,
|
|
2629
|
+
route.name === 'configSave' ? `${route.pluginId}:${route.layer.scope}` : '',
|
|
2630
|
+
draftRemoves,
|
|
2631
|
+
draftUpdates,
|
|
2632
|
+
]);
|
|
2266
2633
|
if (snapshot.phase === 'error' && !isCatalogSourcesBootstrapFlow(snapshot)
|
|
2267
2634
|
&& !isOperationHistoryRoute(route)) {
|
|
2268
2635
|
const sourcesConfigInvalid = snapshot.loadError?.code === 'catalogSourcesConfigInvalid';
|
|
@@ -2294,7 +2661,7 @@ export function ConsoleAppV4(props) {
|
|
|
2294
2661
|
context = '当前项目';
|
|
2295
2662
|
actions = [
|
|
2296
2663
|
{ 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' }) },
|
|
2664
|
+
{ 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
2665
|
{ label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
|
|
2299
2666
|
{
|
|
2300
2667
|
label: '插件来源',
|
|
@@ -2308,7 +2675,7 @@ export function ConsoleAppV4(props) {
|
|
|
2308
2675
|
];
|
|
2309
2676
|
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
2677
|
? _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' })] });
|
|
2678
|
+
: 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
2679
|
}
|
|
2313
2680
|
else if (route.name === 'pluginList') {
|
|
2314
2681
|
const sourceId = route.sourceId ?? 'all';
|
|
@@ -2324,67 +2691,92 @@ export function ConsoleAppV4(props) {
|
|
|
2324
2691
|
...(query ? { query } : {}),
|
|
2325
2692
|
installFilter: 'uninstalled',
|
|
2326
2693
|
}).length;
|
|
2694
|
+
const updateCount = props.controller.listPlugins({
|
|
2695
|
+
mode: route.mode,
|
|
2696
|
+
...(sourceId !== 'all' ? { sourceId } : {}),
|
|
2697
|
+
...(query ? { query } : {}),
|
|
2698
|
+
installFilter: 'updateAvailable',
|
|
2699
|
+
}).length;
|
|
2327
2700
|
breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
|
|
2328
2701
|
title = breadcrumb;
|
|
2329
2702
|
description = route.mode === 'browse'
|
|
2330
|
-
? '
|
|
2331
|
-
: '
|
|
2703
|
+
? '选择一个插件,或从列表前部选择多个。'
|
|
2704
|
+
: '先处理推荐任务,或查看一个插件。';
|
|
2332
2705
|
context = `${items.length} 个插件`;
|
|
2333
|
-
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a 项目全同步' : ''} · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
|
|
2706
|
+
help = `↑↓ 移动 · Enter 查看 · m 批量${route.mode === 'installed' ? ' · a 项目全同步 · f 可更新' : ''} · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
|
|
2334
2707
|
const sourceError = sourceId !== 'all'
|
|
2335
2708
|
&& props.controller.getSourceOverview(sourceId).health?.state === 'unavailable';
|
|
2336
2709
|
actions = sourceError
|
|
2337
2710
|
? [{ label: '查看来源问题', description: '当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
|
|
2338
2711
|
: [];
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2712
|
+
const listedPluginIds = new Set(items.map((item) => item.pluginId));
|
|
2713
|
+
const recommendedUpdateCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
2714
|
+
.filter((candidate) => candidate.eligible && listedPluginIds.has(candidate.target.pluginId));
|
|
2715
|
+
const recommendedUpdatePluginIds = [...new Set(recommendedUpdateCandidates
|
|
2716
|
+
.map((candidate) => candidate.target.pluginId))];
|
|
2717
|
+
const projectRestoreCandidates = props.controller.listPluginWriteTaskCandidates('restore')
|
|
2718
|
+
.filter((candidate) => candidate.target.scope === 'project');
|
|
2719
|
+
const menuRows = sourceError || items.length === 0 || query ? [] : [
|
|
2720
|
+
...(route.mode === 'installed' && recommendedUpdatePluginIds.length > 0 ? [{
|
|
2721
|
+
pluginId: '@menu:update',
|
|
2722
|
+
name: `更新 ${recommendedUpdatePluginIds.length} 个插件…`,
|
|
2723
|
+
sourceLabel: '',
|
|
2724
|
+
description: '使用已验证版本和现有安装位置,直接查看任务确认',
|
|
2725
|
+
placementSummary: '',
|
|
2726
|
+
statusLabel: '推荐',
|
|
2727
|
+
status: 'warning',
|
|
2728
|
+
menuAction: () => openSelectedPluginTask('update', recommendedUpdatePluginIds),
|
|
2729
|
+
}] : []),
|
|
2730
|
+
...(route.mode === 'installed' && projectRestoreCandidates.length > 0 ? [{
|
|
2731
|
+
pluginId: '@menu:restore-project',
|
|
2732
|
+
name: '恢复本项目…',
|
|
2733
|
+
sourceLabel: '',
|
|
2734
|
+
description: '只处理当前项目已保存且需要恢复的插件位置',
|
|
2735
|
+
placementSummary: '',
|
|
2736
|
+
statusLabel: '推荐',
|
|
2737
|
+
status: 'warning',
|
|
2738
|
+
menuAction: () => openSelectedPluginTask('restore', [...new Set(projectRestoreCandidates
|
|
2739
|
+
.map((candidate) => candidate.target.pluginId))], 'project'),
|
|
2740
|
+
}] : []),
|
|
2741
|
+
{
|
|
2353
2742
|
pluginId: '@menu:batch',
|
|
2354
|
-
name: route.mode === 'browse' ? '选择多个插件添加…' : '
|
|
2743
|
+
name: route.mode === 'browse' ? '选择多个插件添加…' : '选择多个…',
|
|
2355
2744
|
sourceLabel: '',
|
|
2356
2745
|
description: route.mode === 'browse'
|
|
2357
|
-
? '
|
|
2358
|
-
: '
|
|
2746
|
+
? '先选择插件,再选择安装位置和平台'
|
|
2747
|
+
: '先选择插件,再选择更新、恢复、移除或维护任务',
|
|
2359
2748
|
placementSummary: '',
|
|
2360
2749
|
statusLabel: '菜单',
|
|
2361
2750
|
status: 'info',
|
|
2362
2751
|
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
|
-
|
|
2752
|
+
},
|
|
2753
|
+
];
|
|
2754
|
+
pluginRows = sourceError ? [] : [...menuRows, ...items.map((item) => ({
|
|
2755
|
+
pluginId: item.pluginId,
|
|
2756
|
+
name: item.displayName,
|
|
2757
|
+
sourceLabel: item.sourceLabel,
|
|
2758
|
+
description: item.configOnly
|
|
2759
|
+
? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
|
|
2760
|
+
: item.description,
|
|
2761
|
+
...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
|
|
2762
|
+
placementSummary: pluginPlacementSummary(item),
|
|
2763
|
+
statusLabel: statusText(item),
|
|
2764
|
+
status: pluginRowStatus(item),
|
|
2765
|
+
}))];
|
|
2766
|
+
if (route.mode === 'installed' && installFilter === 'updateAvailable'
|
|
2767
|
+
&& items.length === 0 && !sourceError) {
|
|
2768
|
+
title = '没有可更新插件';
|
|
2769
|
+
description = '当前筛选范围内没有经过版本证据确认的更新。';
|
|
2770
|
+
actions = [{
|
|
2771
|
+
label: '查看全部我的插件',
|
|
2772
|
+
description: '按 f 返回完整列表',
|
|
2773
|
+
run: () => {
|
|
2774
|
+
setInstallFilter('all');
|
|
2775
|
+
props.controller.updatePageContext({ installFilter: 'all' });
|
|
2383
2776
|
},
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2777
|
+
}];
|
|
2386
2778
|
}
|
|
2387
|
-
if (route.mode === 'installed' && items.length === 0 && !sourceError) {
|
|
2779
|
+
else if (route.mode === 'installed' && items.length === 0 && !sourceError) {
|
|
2388
2780
|
title = '还没有添加插件';
|
|
2389
2781
|
description = `从已配置的 ${props.controller.listSources().length} 个来源中浏览可用能力。`;
|
|
2390
2782
|
actions = [{ label: '浏览插件', description: '开始添加第一个插件', run: () => go({ name: 'pluginList', mode: 'browse' }) }];
|
|
@@ -2398,7 +2790,7 @@ export function ConsoleAppV4(props) {
|
|
|
2398
2790
|
disabled: '没有可打开的插件',
|
|
2399
2791
|
}];
|
|
2400
2792
|
}
|
|
2401
|
-
const pluginListStaticRows =
|
|
2793
|
+
const pluginListStaticRows = 3;
|
|
2402
2794
|
const viewport = sourceError || pluginRows.length === 0
|
|
2403
2795
|
? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
|
|
2404
2796
|
: rowViewport({
|
|
@@ -2416,73 +2808,179 @@ export function ConsoleAppV4(props) {
|
|
|
2416
2808
|
? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
|
|
2417
2809
|
: _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), route.mode === 'browse'
|
|
2418
2810
|
? _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
|
-
:
|
|
2811
|
+
: _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
2812
|
? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
|
|
2421
2813
|
: route.mode === 'installed' && items.length === 0
|
|
2422
|
-
?
|
|
2814
|
+
? installFilter === 'updateAvailable'
|
|
2815
|
+
? _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" })
|
|
2816
|
+
: _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
2817
|
: null, pluginRows.length > 0
|
|
2424
2818
|
? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport })
|
|
2425
2819
|
: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] }));
|
|
2426
2820
|
}
|
|
2427
2821
|
else if (route.name === 'batchSelection') {
|
|
2428
|
-
breadcrumb = '我的插件 /
|
|
2429
|
-
title = batchDraft?.
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2822
|
+
breadcrumb = `${batchDraft?.mode === 'browse' ? '添加插件' : '我的插件'} / 选择多个`;
|
|
2823
|
+
title = batchDraft?.stage === 'task' ? `为 ${batchDraft.selectedPluginIds.length} 个插件选择任务`
|
|
2824
|
+
: batchDraft?.stage === 'maintenance' ? '其他维护方式'
|
|
2825
|
+
: batchDraft?.mode === 'browse' ? '选择要添加的插件' : '选择插件';
|
|
2826
|
+
description = batchDraft?.stage === 'select'
|
|
2827
|
+
? 'Space 切换选择,Enter 查看插件。选择会跨来源、搜索和筛选保留。'
|
|
2828
|
+
: batchDraft?.stage === 'task'
|
|
2829
|
+
? '更新和恢复使用现有精确位置;移除会明确使用已有位置。'
|
|
2830
|
+
: '同步、重新安装和普通清理使用所选插件的已有位置。';
|
|
2831
|
+
if (!batchDraft) {
|
|
2832
|
+
actions = [{ label: '返回插件列表', run: back }];
|
|
2833
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
|
|
2834
|
+
}
|
|
2835
|
+
else if (batchDraft.stage === 'select') {
|
|
2836
|
+
const candidates = batchCandidates(batchDraft);
|
|
2837
|
+
const selected = new Set(batchDraft.selectedPluginIds);
|
|
2838
|
+
const currentIds = new Set(candidates.map((candidate) => candidate.pluginId));
|
|
2839
|
+
const hiddenSelected = batchDraft.selectedPluginIds.filter((pluginId) => !currentIds.has(pluginId)).length;
|
|
2840
|
+
const candidateActions = candidates.map((candidate) => {
|
|
2841
|
+
const blockReason = batchCandidateBlockReason(batchDraft, candidate);
|
|
2842
|
+
return {
|
|
2843
|
+
label: candidate.displayName,
|
|
2844
|
+
description: blockReason
|
|
2845
|
+
? `${candidate.sourceLabel} · 不可选择:${blockReason}`
|
|
2846
|
+
: `${candidate.sourceLabel} · ${pluginPlacementSummary(candidate)}`,
|
|
2847
|
+
selected: selected.has(candidate.pluginId),
|
|
2848
|
+
pluginId: candidate.pluginId,
|
|
2849
|
+
selectable: blockReason === undefined,
|
|
2850
|
+
status: blockReason ? 'muted' : undefined,
|
|
2851
|
+
run: () => go(batchDraft.mode === 'browse'
|
|
2852
|
+
? { name: 'pluginPreview', pluginId: candidate.pluginId }
|
|
2853
|
+
: { name: 'pluginDetail', pluginId: candidate.pluginId }),
|
|
2854
|
+
};
|
|
2855
|
+
});
|
|
2856
|
+
const selectableIds = candidates
|
|
2857
|
+
.filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined)
|
|
2858
|
+
.map((candidate) => candidate.pluginId);
|
|
2859
|
+
const continueSelection = () => {
|
|
2860
|
+
if (batchDraft.selectedPluginIds.length === 0) {
|
|
2861
|
+
setActionError('请至少选择一个插件。');
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
if (batchDraft.mode === 'browse') {
|
|
2865
|
+
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
2866
|
+
props.controller.push({ name: 'batchPlacement' });
|
|
2867
|
+
}
|
|
2868
|
+
else {
|
|
2869
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
2870
|
+
}
|
|
2871
|
+
setFocus(0);
|
|
2872
|
+
setScrollOffset(0);
|
|
2873
|
+
};
|
|
2874
|
+
const utilityActions = [
|
|
2435
2875
|
{
|
|
2436
|
-
label:
|
|
2437
|
-
description: '
|
|
2438
|
-
run:
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
}
|
|
2876
|
+
label: `查看确认(${batchDraft.selectedPluginIds.length})`,
|
|
2877
|
+
description: batchDraft.selectedPluginIds.length > 0 ? '继续选择任务和确认范围' : '先选择至少一个插件',
|
|
2878
|
+
run: continueSelection,
|
|
2879
|
+
},
|
|
2880
|
+
{
|
|
2881
|
+
label: batchDraft.showSelected ? '返回当前结果' : `查看全部已选(${batchDraft.selectedPluginIds.length})`,
|
|
2882
|
+
description: hiddenSelected > 0 ? `${hiddenSelected} 个选择在其他筛选中` : '完整 canonical 选择集合',
|
|
2883
|
+
run: () => setBatchDraft((current) => current ? { ...current, showSelected: !current.showSelected } : current),
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
label: '全选当前结果',
|
|
2887
|
+
description: `${selectableIds.length} 个当前筛选下可选结果,包括滚动范围外`,
|
|
2888
|
+
run: () => setBatchDraft((current) => current ? {
|
|
2889
|
+
...current,
|
|
2890
|
+
selectedPluginIds: [...new Set([...current.selectedPluginIds, ...selectableIds])],
|
|
2891
|
+
} : current),
|
|
2892
|
+
},
|
|
2893
|
+
{
|
|
2894
|
+
label: '取消当前结果',
|
|
2895
|
+
description: '保留其他来源或搜索中的选择',
|
|
2896
|
+
run: () => setBatchDraft((current) => current ? {
|
|
2897
|
+
...current,
|
|
2898
|
+
selectedPluginIds: current.selectedPluginIds.filter((pluginId) => !currentIds.has(pluginId)),
|
|
2899
|
+
} : current),
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
label: '清空全部已选',
|
|
2903
|
+
run: () => setBatchDraft((current) => current ? { ...current, selectedPluginIds: [] } : current),
|
|
2448
2904
|
},
|
|
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);
|
|
2454
|
-
setFocus(0);
|
|
2455
|
-
setScrollOffset(0);
|
|
2456
|
-
},
|
|
2457
|
-
})),
|
|
2458
2905
|
];
|
|
2459
|
-
|
|
2460
|
-
context =
|
|
2461
|
-
help = '↑↓ 移动 ·
|
|
2462
|
-
|
|
2906
|
+
actions = [...candidateActions, ...utilityActions];
|
|
2907
|
+
context = `已选 ${batchDraft.selectedPluginIds.length} 个 · 显示 ${candidates.length} 个${hiddenSelected > 0 ? ` · 隐藏 ${hiddenSelected} 个` : ''}`;
|
|
2908
|
+
help = '↑↓ 移动 · Space 选择 · Enter 查看/操作 · c 查看确认 · ←→ 来源 · / 搜索 · Esc 放弃';
|
|
2909
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(3));
|
|
2910
|
+
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
|
|
2911
|
+
? _jsx(InputDisplay, { value: batchDraft.query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
|
|
2912
|
+
: _jsxs(_Fragment, { children: [batchDraft.query || '搜索名称、用途或来源…', batchDraft.query ? ' / 编辑' : ''] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] });
|
|
2463
2913
|
}
|
|
2464
2914
|
else {
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2915
|
+
const selectedIds = batchDraft.selectedPluginIds;
|
|
2916
|
+
const taskCandidates = (goal) => {
|
|
2917
|
+
const selected = new Set(selectedIds);
|
|
2918
|
+
return props.controller.listPluginWriteTaskCandidates(goal)
|
|
2919
|
+
.filter((candidate) => selected.has(candidate.target.pluginId));
|
|
2920
|
+
};
|
|
2921
|
+
const exactTargets = existingBatchTargets(selectedIds);
|
|
2922
|
+
const startExistingBatch = (action) => {
|
|
2923
|
+
if (exactTargets.length === 0) {
|
|
2924
|
+
setActionError('所选插件没有可用的已有安装位置。');
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
beginReview({
|
|
2928
|
+
kind: 'pluginWriteBatch', action,
|
|
2929
|
+
selection: { kind: 'targets', targets: exactTargets },
|
|
2930
|
+
platform: exactTargets[0].platform,
|
|
2931
|
+
scope: exactTargets[0].scope,
|
|
2932
|
+
});
|
|
2933
|
+
};
|
|
2934
|
+
if (batchDraft.stage === 'maintenance') {
|
|
2935
|
+
actions = [
|
|
2936
|
+
{ label: '同步已保存使用设置…', description: '重新应用所选已有位置', run: () => startExistingBatch('sync') },
|
|
2937
|
+
{ label: '重新安装…', description: '重新生成所选已有位置的安装内容', run: () => startExistingBatch('reinstall') },
|
|
2938
|
+
{ label: '清理遗留内容…', description: '普通清理;保留 foreign 和 shared 内容', run: () => startExistingBatch('cleanup') },
|
|
2939
|
+
{
|
|
2940
|
+
label: '返回任务选择',
|
|
2941
|
+
run: () => {
|
|
2942
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
2943
|
+
setFocus(0);
|
|
2944
|
+
setScrollOffset(0);
|
|
2945
|
+
},
|
|
2946
|
+
},
|
|
2947
|
+
];
|
|
2948
|
+
}
|
|
2949
|
+
else {
|
|
2950
|
+
const updates = taskCandidates('update');
|
|
2951
|
+
const restores = taskCandidates('restore');
|
|
2952
|
+
const updateRoots = new Set(updates.map((candidate) => candidate.target.pluginId)).size;
|
|
2953
|
+
const restoreRoots = new Set(restores.map((candidate) => candidate.target.pluginId)).size;
|
|
2954
|
+
actions = [
|
|
2955
|
+
...(updates.length > 0 ? [{
|
|
2956
|
+
label: `更新所选中的 ${updateRoots} 个可更新插件…`,
|
|
2957
|
+
description: '使用已验证版本和现有精确位置',
|
|
2958
|
+
status: 'warning',
|
|
2959
|
+
run: () => openSelectedPluginTask('update', selectedIds),
|
|
2960
|
+
}] : []),
|
|
2961
|
+
...(restores.length > 0 ? [{
|
|
2962
|
+
label: `恢复所选中的 ${restoreRoots} 个插件…`,
|
|
2963
|
+
description: '按当前问题和现有精确位置准备方案',
|
|
2964
|
+
status: 'warning',
|
|
2965
|
+
run: () => openSelectedPluginTask('restore', selectedIds),
|
|
2966
|
+
}] : []),
|
|
2967
|
+
{ label: '移除所选插件…', description: `${exactTargets.length} 个已有位置;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
|
|
2968
|
+
{
|
|
2969
|
+
label: '其他维护方式…',
|
|
2970
|
+
description: '同步、重新安装和普通清理',
|
|
2971
|
+
run: () => {
|
|
2972
|
+
setBatchDraft((current) => current ? { ...current, stage: 'maintenance' } : current);
|
|
2973
|
+
setFocus(0);
|
|
2974
|
+
setScrollOffset(0);
|
|
2975
|
+
},
|
|
2976
|
+
},
|
|
2977
|
+
{ label: '返回调整选择', run: () => setBatchDraft((current) => current ? { ...current, stage: 'select' } : current) },
|
|
2978
|
+
];
|
|
2979
|
+
}
|
|
2980
|
+
context = `${selectedIds.length} 个插件 · ${exactTargets.length} 个已有位置`;
|
|
2981
|
+
help = '↑↓ 移动 · Enter 选择 · Esc 返回修改';
|
|
2482
2982
|
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" });
|
|
2983
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
2486
2984
|
}
|
|
2487
2985
|
}
|
|
2488
2986
|
else if (route.name === 'batchPlacement') {
|
|
@@ -2615,7 +3113,25 @@ export function ConsoleAppV4(props) {
|
|
|
2615
3113
|
return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
|
|
2616
3114
|
};
|
|
2617
3115
|
const previewMenu = dynamicMenu;
|
|
2618
|
-
|
|
3116
|
+
const advancedPreviewActions = previewMenu.map(previewAction);
|
|
3117
|
+
const primaryPreviewAction = advancedPreviewActions[0] ?? {
|
|
3118
|
+
label: '查看处理建议',
|
|
3119
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3120
|
+
};
|
|
3121
|
+
const previewSettingsAction = previewMenu.findIndex((action) => action.id === 'settings');
|
|
3122
|
+
actions = [
|
|
3123
|
+
primaryPreviewAction,
|
|
3124
|
+
...(previewSettingsAction > 0 ? [advancedPreviewActions[previewSettingsAction]] : [{
|
|
3125
|
+
label: '使用说明',
|
|
3126
|
+
description: '查看用途、支持平台和来源',
|
|
3127
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
3128
|
+
}]),
|
|
3129
|
+
{
|
|
3130
|
+
label: '更多操作…',
|
|
3131
|
+
description: '安装方式、依赖、通用 Skills、来源和诊断',
|
|
3132
|
+
run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
|
|
3133
|
+
},
|
|
3134
|
+
];
|
|
2619
3135
|
const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 2));
|
|
2620
3136
|
const dependencyGroups = ['direct', 'transitive'].map((relation) => ({
|
|
2621
3137
|
relation,
|
|
@@ -2806,9 +3322,6 @@ export function ConsoleAppV4(props) {
|
|
|
2806
3322
|
: overview?.dependents ?? [];
|
|
2807
3323
|
const dependencyCount = overview?.dependencies.length ?? 0;
|
|
2808
3324
|
const dependentCount = visibleDependents.length;
|
|
2809
|
-
const dependencyLimit = policy.mode === 'compact'
|
|
2810
|
-
? dependentCount > 0 ? 1 : 2
|
|
2811
|
-
: dependentCount > 0 ? 3 : 5;
|
|
2812
3325
|
const dependentLimit = responsiveBudget(2, 4);
|
|
2813
3326
|
const dependencyRows = (entries, limit, dependent = false, offset = 0) => {
|
|
2814
3327
|
const safeOffset = Math.min(offset, Math.max(0, entries.length - 1));
|
|
@@ -2843,62 +3356,103 @@ export function ConsoleAppV4(props) {
|
|
|
2843
3356
|
}
|
|
2844
3357
|
else {
|
|
2845
3358
|
breadcrumb = `${plugin.displayName} / 依赖关系`;
|
|
2846
|
-
|
|
3359
|
+
const direction = route.direction ?? 'dependencies';
|
|
3360
|
+
const projection = buildDependencyGraphProjection({
|
|
3361
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
3362
|
+
rootPluginId: plugin.pluginId,
|
|
3363
|
+
direction,
|
|
3364
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
3365
|
+
});
|
|
3366
|
+
const visibleRows = projection.rows;
|
|
3367
|
+
context = direction === 'dependencies' ? '它需要的' : '谁在使用它';
|
|
2847
3368
|
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
|
-
|
|
3369
|
+
description = direction === 'dependencies'
|
|
3370
|
+
? '当前方向:它需要的。缩进表示上一层需要它。'
|
|
3371
|
+
: '当前方向:谁在使用它。只显示已知的实际引用。';
|
|
3372
|
+
const rowActions = visibleRows.map((row) => {
|
|
3373
|
+
const state = row.depth === 0
|
|
3374
|
+
? { value: statusText(plugin), status: plugin.aggregateStatus === 'needsAttention'
|
|
3375
|
+
? 'warning' : plugin.aggregateStatus === 'unavailable' ? 'error' : 'success' }
|
|
3376
|
+
: dependencyGraphRowStatus(row);
|
|
3377
|
+
const branch = row.depth === 0 ? ''
|
|
3378
|
+
: `${row.ancestorHasNextSibling.map((next) => next ? '│ ' : ' ').join('')}${row.lastChild ? '└─' : '├─'} `;
|
|
3379
|
+
const compactBranch = policy.mode === 'compact' && row.depth > 2 ? '… └─ ' : branch;
|
|
3380
|
+
return {
|
|
3381
|
+
label: row.depth === 0
|
|
3382
|
+
? `根插件 · ${row.label}(当前插件)`
|
|
3383
|
+
: `${compactBranch}${direction === 'dependencies' ? '必需依赖 · ' : '实际使用者 · '}${row.label}${row.sharedWithRowId ? ' ↗ [same]' : ''}`,
|
|
3384
|
+
description: row.hiddenDescendantCount > 0
|
|
3385
|
+
? `另有 ${row.hiddenDescendantCount} 个${direction === 'dependencies' ? '所需插件' : '实际使用者'}`
|
|
3386
|
+
: row.depth === 0 ? '当前插件' : undefined,
|
|
3387
|
+
meta: state.value,
|
|
3388
|
+
status: state.status,
|
|
3389
|
+
run: () => go({
|
|
3390
|
+
name: 'pluginDependencyNode',
|
|
3391
|
+
pluginId: plugin.pluginId,
|
|
3392
|
+
nodePluginId: row.pluginId,
|
|
3393
|
+
direction,
|
|
3394
|
+
}),
|
|
2871
3395
|
};
|
|
2872
|
-
return priority(left) - priority(right);
|
|
2873
3396
|
});
|
|
2874
|
-
|
|
2875
|
-
...
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3397
|
+
actions = [
|
|
3398
|
+
...rowActions,
|
|
3399
|
+
{
|
|
3400
|
+
label: direction === 'dependencies' ? '切换到“谁在使用它”' : '切换到“它需要的”',
|
|
3401
|
+
description: '切换关系方向',
|
|
3402
|
+
run: () => props.controller.replace({
|
|
3403
|
+
name: 'pluginDependencies',
|
|
3404
|
+
pluginId: plugin.pluginId,
|
|
3405
|
+
direction: direction === 'dependencies' ? 'dependents' : 'dependencies',
|
|
3406
|
+
}),
|
|
3407
|
+
},
|
|
3408
|
+
{ label: showTechnical ? '隐藏检查详情' : '查看检查详情', run: () => toggleTechnical() },
|
|
2884
3409
|
];
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
content = _jsxs(Box, { flexDirection: "column", children: [useBudgetedRelations ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5173\u7CFB", compact: true, children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: statusText(plugin), status: rootStatus, compact: true }), relationOffset > 0 ? _jsx(StaticRow, { title: `↑ 前面还有 ${relationOffset} 项`, status: "muted", compact: true }) : null, visibleRelationRows, hiddenRelationRows > 0 ? _jsx(StaticRow, { title: `↓ 后面还有 ${hiddenRelationRows} 项`, status: "muted", compact: true }) : null, dependencyCount + dependentCount + blockerCount === 0 ? _jsx(StaticRow, { title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB", status: "muted", compact: true }) : null] }) : _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", meta: "\u6839\u8282\u70B9", children: _jsx(StaticRow, { title: plugin.displayName, meta: statusText(plugin), status: rootStatus }) }), dependencyCount > 0 ? _jsx(SectionCard, { title: "\u5FC5\u9700\u4F9D\u8D56", meta: "\u4F9D\u8D56\u4F18\u5148", children: dependencyRows(overview?.dependencies ?? [], dependencyLimit) }) : null, dependentCount > 0 ? _jsx(SectionCard, { title: "\u4F7F\u7528\u5B83\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true) }) : null, overview?.blockers.map((blocker) => _jsx(Callout, { status: "error", title: blocker.title, description: blocker.explanation }, blocker.code)), dependencyCount + dependentCount + blockerCount === 0 ? _jsx(Callout, { status: "muted", title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: useBudgetedRelations, offset: dependencyActionViewport.offset, limit: dependencyActionViewport.limit }), showTechnical ? _jsx(TechnicalDetails, { children: _jsx(StaticRow, { title: `${dependencyCount} 依赖 · ${dependentCount} 被依赖 · 已折叠`, meta: plugin.pluginId, compact: true }) }) : null] });
|
|
3410
|
+
const scopeText = projection.scopeSummary.references.length > 0
|
|
3411
|
+
? projection.scopeSummary.references.map((entry) => `${platformLabel(entry.platform)} · ${scopeLabel(entry.scope)}`).join(';')
|
|
3412
|
+
: '当前加载范围;其他项目引用未检查';
|
|
3413
|
+
const relationViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, showTechnical ? responsiveBudget(3, 6) : responsiveBudget(7, 11));
|
|
3414
|
+
help = '↑↓ 选择 · → 展开 · ← 收起 · Enter 节点详情 · Esc 返回';
|
|
3415
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `当前检查范围 · ${scopeText}`, status: "muted", compact: true }), projection.status.state === 'invalid'
|
|
3416
|
+
? _jsx(Callout, { status: "error", title: "\u4F9D\u8D56\u5173\u7CFB\u65E0\u6548", description: projection.status.message }) : 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} 个依赖 · ${dependentCount} 个实际使用者`, meta: plugin.pluginId, compact: true }), _jsx(StaticRow, { title: `未知引用 · ${projection.scopeSummary.unknownConsumerPluginIds.length} 个`, compact: true })] }) : null] });
|
|
2893
3417
|
}
|
|
2894
3418
|
}
|
|
3419
|
+
else if (route.name === 'pluginDependencyNode' && plugin) {
|
|
3420
|
+
const node = props.controller.getPluginOverview(route.nodePluginId);
|
|
3421
|
+
const overview = dependencyOverview(plugin);
|
|
3422
|
+
const item = route.nodePluginId === plugin.pluginId ? undefined
|
|
3423
|
+
: route.direction === 'dependencies'
|
|
3424
|
+
? overview?.dependencies.find((entry) => entry.pluginId === route.nodePluginId)
|
|
3425
|
+
: overview?.dependents.find((entry) => entry.pluginId === route.nodePluginId);
|
|
3426
|
+
const state = item ? dependencyStatus(item) : undefined;
|
|
3427
|
+
breadcrumb = `${plugin.displayName} / 依赖关系 / ${node?.displayName ?? route.nodePluginId}`;
|
|
3428
|
+
context = route.direction === 'dependencies' ? '它需要的' : '谁在使用它';
|
|
3429
|
+
title = node?.displayName ?? route.nodePluginId;
|
|
3430
|
+
description = route.nodePluginId === plugin.pluginId
|
|
3431
|
+
? '这是当前关系图的根插件。'
|
|
3432
|
+
: route.direction === 'dependencies'
|
|
3433
|
+
? `${item?.requiredBy.map((entry) => entry.displayName).join('、') || plugin.displayName} 需要这个插件。`
|
|
3434
|
+
: `${node?.displayName ?? route.nodePluginId} 的已安装位置实际引用当前插件。`;
|
|
3435
|
+
actions = [
|
|
3436
|
+
...(route.nodePluginId !== plugin.pluginId ? [{
|
|
3437
|
+
label: '以此节点查看关系',
|
|
3438
|
+
description: '打开局部子树;面包屑保留来路',
|
|
3439
|
+
run: () => go({
|
|
3440
|
+
name: 'pluginDependencies',
|
|
3441
|
+
pluginId: route.nodePluginId,
|
|
3442
|
+
direction: route.direction,
|
|
3443
|
+
}),
|
|
3444
|
+
}] : []),
|
|
3445
|
+
{ label: '返回关系图', run: back },
|
|
3446
|
+
];
|
|
3447
|
+
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' })] });
|
|
3448
|
+
}
|
|
2895
3449
|
else if (route.name === 'installPlacement' && plugin) {
|
|
2896
3450
|
breadcrumb = `${plugin.displayName} / 添加`;
|
|
2897
3451
|
context = '第 1 步,共 2 步';
|
|
2898
3452
|
title = `你希望在哪里使用 ${plugin.displayName}?`;
|
|
2899
3453
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
2900
3454
|
description = hasDependencies
|
|
2901
|
-
? '
|
|
3455
|
+
? '这里只选择根插件位置。依赖将采用 planner 推荐位置,并在 Review 中显示。'
|
|
2902
3456
|
: '选择位置后,再选择一个或多个目标平台。';
|
|
2903
3457
|
const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
|
|
2904
3458
|
.some((entry) => entry.compatible));
|
|
@@ -2907,8 +3461,8 @@ export function ConsoleAppV4(props) {
|
|
|
2907
3461
|
label: scopeLabel(candidateScope),
|
|
2908
3462
|
selected: candidateScope === scope,
|
|
2909
3463
|
description: candidateScope === 'user'
|
|
2910
|
-
? hasDependencies ? '
|
|
2911
|
-
: hasDependencies ? '
|
|
3464
|
+
? hasDependencies ? '根插件在任意项目中可用;依赖位置自动计算' : '在任意项目中可使用,适合个人常用能力'
|
|
3465
|
+
: hasDependencies ? '根插件只在当前项目使用;Review 会说明更宽的依赖位置' : '仅在当前项目中使用,适合团队共享',
|
|
2912
3466
|
meta: candidateScope === 'user' ? '推荐' : undefined,
|
|
2913
3467
|
run: () => {
|
|
2914
3468
|
const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
|
|
@@ -2931,7 +3485,7 @@ export function ConsoleAppV4(props) {
|
|
|
2931
3485
|
},
|
|
2932
3486
|
})),
|
|
2933
3487
|
];
|
|
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}
|
|
3488
|
+
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
3489
|
}
|
|
2936
3490
|
else if (route.name === 'installTargets' && plugin) {
|
|
2937
3491
|
breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
|
|
@@ -2939,7 +3493,7 @@ export function ConsoleAppV4(props) {
|
|
|
2939
3493
|
title = '选择目标平台';
|
|
2940
3494
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
2941
3495
|
description = hasDependencies
|
|
2942
|
-
? '
|
|
3496
|
+
? '根插件必须支持所选位置;确认后会自动规划依赖并进入 Review。'
|
|
2943
3497
|
: '目标平台按所选位置验证;仅含平台共享内容的插件可同时启用两个位置。';
|
|
2944
3498
|
const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
|
|
2945
3499
|
const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
|
|
@@ -3075,93 +3629,300 @@ export function ConsoleAppV4(props) {
|
|
|
3075
3629
|
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
|
|
3076
3630
|
help = '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
3077
3631
|
}
|
|
3632
|
+
else if (route.name === 'pluginWriteTask' && plugin) {
|
|
3633
|
+
const retainedTargetKeys = route.targetKeys ? new Set(route.targetKeys) : undefined;
|
|
3634
|
+
const candidates = props.controller.listPluginWriteTaskCandidates(route.goal)
|
|
3635
|
+
.filter((candidate) => retainedTargetKeys
|
|
3636
|
+
? retainedTargetKeys.has(pluginWriteTaskTargetKey(candidate))
|
|
3637
|
+
: candidate.target.pluginId === route.pluginId);
|
|
3638
|
+
const executable = candidates.filter((candidate) => candidate.eligible);
|
|
3639
|
+
const blockedCandidates = candidates.filter((candidate) => !candidate.eligible);
|
|
3640
|
+
const taskPluginIds = [...new Set(candidates.map((candidate) => candidate.target.pluginId))];
|
|
3641
|
+
const currentVersionEvidence = Object.assign({}, ...taskPluginIds.map((pluginId) => {
|
|
3642
|
+
const candidatePlugin = props.controller.getPluginOverview(pluginId);
|
|
3643
|
+
return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
|
|
3644
|
+
}));
|
|
3645
|
+
const versionEvidenceChanged = route.goal === 'update' && route.versionEvidence !== undefined
|
|
3646
|
+
&& (Object.entries(currentVersionEvidence).some(([key, version]) => route.versionEvidence?.[key] !== version)
|
|
3647
|
+
|| Object.keys(route.versionEvidence).some((key) => currentVersionEvidence[key] === undefined));
|
|
3648
|
+
const activationSources = [...new Set(blockedCandidates
|
|
3649
|
+
.filter((candidate) => candidate.blockReason === 'catalogActivationRequired')
|
|
3650
|
+
.map((candidate) => candidate.sourceId))];
|
|
3651
|
+
const taskLabel = pluginWriteTaskLabel(route.goal);
|
|
3652
|
+
const startTaskReview = (executionPolicy) => {
|
|
3653
|
+
props.controller.startPluginWriteTaskReview({
|
|
3654
|
+
kind: 'pluginWriteTask',
|
|
3655
|
+
goal: route.goal,
|
|
3656
|
+
targets: candidates.map((candidate) => candidate.target),
|
|
3657
|
+
executionPolicy,
|
|
3658
|
+
}, route);
|
|
3659
|
+
setFocus(0);
|
|
3660
|
+
setScrollOffset(0);
|
|
3661
|
+
};
|
|
3662
|
+
breadcrumb = taskPluginIds.length > 1
|
|
3663
|
+
? `我的插件 / ${taskLabel} ${taskPluginIds.length} 个插件`
|
|
3664
|
+
: `我的插件 / ${plugin.displayName} / ${taskLabel}`;
|
|
3665
|
+
context = '只读准备';
|
|
3666
|
+
title = taskPluginIds.length > 1
|
|
3667
|
+
? `${taskLabel} ${taskPluginIds.length} 个插件`
|
|
3668
|
+
: `${taskLabel} ${plugin.displayName}`;
|
|
3669
|
+
description = route.goal === 'update'
|
|
3670
|
+
? '只使用已验证的插件版本和精确安装位置。来源目录不会在准备时自动启用。'
|
|
3671
|
+
: '根据当前问题选择最小恢复方法,不自动改版本、位置或来源。';
|
|
3672
|
+
help = candidates.length > (policy.mode === 'compact' ? 3 : 6)
|
|
3673
|
+
? 'PgUp/PgDn 查看全部位置 · ↑↓ 移动 · Enter 选择 · Esc 返回'
|
|
3674
|
+
: '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
3675
|
+
actions = [
|
|
3676
|
+
...(!versionEvidenceChanged && blockedCandidates.length === 0 && executable.length > 0 ? [{
|
|
3677
|
+
label: `确认${taskLabel} ${executable.length} 个位置…`,
|
|
3678
|
+
description: '进入统一影响确认;执行前再次检查当前证据',
|
|
3679
|
+
status: 'success',
|
|
3680
|
+
run: () => startTaskReview('all'),
|
|
3681
|
+
}] : []),
|
|
3682
|
+
...(!versionEvidenceChanged && blockedCandidates.length > 0 && executable.length > 0 ? [{
|
|
3683
|
+
label: `先处理可执行的 ${executable.length} 个位置…`,
|
|
3684
|
+
description: `${blockedCandidates.length} 个受阻位置保留,不会无声跳过`,
|
|
3685
|
+
status: 'warning',
|
|
3686
|
+
run: () => startTaskReview('executableSubset'),
|
|
3687
|
+
}] : []),
|
|
3688
|
+
...activationSources.map((sourceId) => ({
|
|
3689
|
+
label: `先更新来源目录 ${sourceId}…`,
|
|
3690
|
+
description: '先单独确认目录变更;启用完成后重新准备插件更新',
|
|
3691
|
+
status: 'warning',
|
|
3692
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId }),
|
|
3693
|
+
})),
|
|
3694
|
+
...(versionEvidenceChanged ? [{
|
|
3695
|
+
label: '使用当前版本重新准备…',
|
|
3696
|
+
description: '来源启用后的插件版本与先前候选不同;先明确采用当前版本',
|
|
3697
|
+
status: 'warning',
|
|
3698
|
+
run: () => {
|
|
3699
|
+
props.controller.replace({ ...route, versionEvidence: currentVersionEvidence });
|
|
3700
|
+
setFocus(0);
|
|
3701
|
+
setScrollOffset(0);
|
|
3702
|
+
},
|
|
3703
|
+
}] : []),
|
|
3704
|
+
{ label: '返回插件详情', run: back },
|
|
3705
|
+
];
|
|
3706
|
+
const taskTargetLimit = policy.mode === 'compact' ? 3 : 6;
|
|
3707
|
+
const taskTargetOffset = Math.min(batchTargetOffset, Math.max(0, candidates.length - 1));
|
|
3708
|
+
const visibleTaskCandidates = candidates.slice(taskTargetOffset, taskTargetOffset + taskTargetLimit);
|
|
3709
|
+
const targetRows = visibleTaskCandidates.map((candidate) => {
|
|
3710
|
+
const target = candidate.target;
|
|
3711
|
+
const version = target.expectedUpdate
|
|
3712
|
+
? `${target.expectedUpdate.installedVersion} → ${target.expectedUpdate.activeVersion}`
|
|
3713
|
+
: '保持当前版本';
|
|
3714
|
+
return _jsx(StaticRow, { title: `${taskPluginIds.length > 1 ? `${candidate.displayName} · ${candidate.sourceId} · ` : ''}${platformLabel(target.platform)} · ${scopeLabel(target.scope)}${!candidate.eligible && policy.mode === 'compact'
|
|
3715
|
+
? ` · ${pluginWriteTaskBlockLabel(candidate.blockReason)}` : ''}`, description: candidate.eligible
|
|
3716
|
+
? pluginWriteTaskMethodLabel(candidate.recommendedMethod)
|
|
3717
|
+
: pluginWriteTaskBlockLabel(candidate.blockReason), meta: candidate.eligible ? version : '需要决定', status: candidate.eligible ? 'success' : 'warning', compact: policy.mode === 'compact' }, `${target.pluginId}:${target.platform}:${target.scope}`);
|
|
3718
|
+
});
|
|
3719
|
+
const taskViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(Math.min(candidates.length, policy.mode === 'compact' ? 3 : 6) + 2));
|
|
3720
|
+
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
|
|
3721
|
+
: _jsx(StaticRow, { title: "\u5F53\u524D\u6CA1\u6709\u53EF\u6267\u884C\u76EE\u6807", status: "muted", compact: true }), candidates.length > taskTargetOffset + targetRows.length
|
|
3722
|
+
? _jsx(StaticRow, { title: `↓ 后面还有 ${candidates.length - taskTargetOffset - targetRows.length} 个位置`, status: "muted", compact: true }) : null] }), blockedCandidates.length > 0 ? _jsx(Callout, { status: "warning", title: `${blockedCandidates.length} 个位置需要先处理或决定`, description: "\u53EA\u6709\u9009\u62E9\u201C\u5148\u5904\u7406\u53EF\u6267\u884C\u9879\u201D\u624D\u4F1A\u5EFA\u7ACB\u5B50\u96C6 Review\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 })] });
|
|
3723
|
+
}
|
|
3078
3724
|
else if (route.name === 'pluginDetail' && plugin) {
|
|
3079
3725
|
breadcrumb = `我的插件 / ${plugin.displayName}`;
|
|
3080
3726
|
context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
|
|
3081
3727
|
title = plugin.displayName;
|
|
3082
3728
|
description = plugin.description;
|
|
3083
|
-
const
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
},
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3729
|
+
const updateDescription = pluginUpdateDescription(plugin);
|
|
3730
|
+
const updateTaskCandidates = plugin.configOnly ? []
|
|
3731
|
+
: props.controller.listPluginWriteTaskCandidates('update')
|
|
3732
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
3733
|
+
const restoreTaskCandidates = plugin.configOnly ? []
|
|
3734
|
+
: props.controller.listPluginWriteTaskCandidates('restore')
|
|
3735
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
3736
|
+
const updateAction = updateTaskCandidates.length > 0 ? {
|
|
3737
|
+
label: updateTaskCandidates.length === 1 && updateTaskCandidates[0]?.target.expectedUpdate
|
|
3738
|
+
? `更新到 ${updateTaskCandidates[0].target.expectedUpdate.activeVersion}…`
|
|
3739
|
+
: updateTaskCandidates.some((candidate) => candidate.target.expectedUpdate)
|
|
3740
|
+
? `更新 ${plugin.displayName}…` : '准备插件更新…',
|
|
3741
|
+
description: updateTaskCandidates.some((candidate) => candidate.blockReason === 'catalogActivationRequired')
|
|
3742
|
+
? '需要先单独确认来源目录变更' : '使用精确版本和现有安装位置',
|
|
3743
|
+
status: 'warning',
|
|
3744
|
+
run: () => go({
|
|
3745
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
|
|
3746
|
+
targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
3747
|
+
versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
|
|
3748
|
+
}),
|
|
3749
|
+
} : undefined;
|
|
3750
|
+
const executableRestoreCandidates = restoreTaskCandidates.filter((candidate) => candidate.eligible);
|
|
3751
|
+
const restoreAction = executableRestoreCandidates.length > 0 ? {
|
|
3752
|
+
label: '恢复正常…',
|
|
3753
|
+
description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
|
|
3754
|
+
status: 'warning',
|
|
3755
|
+
run: () => go({
|
|
3756
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
|
|
3757
|
+
targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
3758
|
+
}),
|
|
3759
|
+
} : undefined;
|
|
3760
|
+
const settingsAction = plugin.configFieldCount > 0
|
|
3761
|
+
|| plugin.savedConfigScopes.length > 0 ? {
|
|
3762
|
+
label: plugin.configOnly ? '查看已保存设置' : '插件设置',
|
|
3763
|
+
description: plugin.configOnly
|
|
3764
|
+
? '检查已保存设置,并可重置记录中的设置层'
|
|
3765
|
+
: '查看当前实际生效的配置和值来源',
|
|
3766
|
+
meta: plugin.configFieldCount > 0 ? `${plugin.configFieldCount} 项配置` : undefined,
|
|
3767
|
+
run: () => {
|
|
3768
|
+
if (plugin.configOnly)
|
|
3769
|
+
props.controller.beginSavedConfigRecovery(plugin.pluginId);
|
|
3770
|
+
else
|
|
3771
|
+
beginConfigFlow(plugin.pluginId);
|
|
3772
|
+
setFocus(0);
|
|
3773
|
+
setScrollOffset(0);
|
|
3774
|
+
},
|
|
3775
|
+
} : undefined;
|
|
3776
|
+
const issueForPlugin = props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId || candidate.rootEntity?.pluginId === plugin.pluginId);
|
|
3777
|
+
const exceptionalAction = plugin.aggregateStatus === 'unavailable'
|
|
3778
|
+
? plugin.capabilityUnsupported && !plugin.sourceUnavailable ? {
|
|
3779
|
+
label: '需要升级 AW CLI',
|
|
3780
|
+
description: '查看缺少的功能和安全恢复方向',
|
|
3781
|
+
status: 'warning',
|
|
3782
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3783
|
+
} : {
|
|
3784
|
+
label: '恢复插件来源…',
|
|
3785
|
+
description: '来源恢复后再重新检查插件状态',
|
|
3786
|
+
status: 'warning',
|
|
3787
|
+
run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
|
|
3128
3788
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3789
|
+
: plugin.aggregateStatus === 'needsAttention' && !restoreAction ? {
|
|
3790
|
+
label: '查看处理建议',
|
|
3791
|
+
description: '当前问题需要先确认原因或处理方式',
|
|
3792
|
+
status: 'warning',
|
|
3793
|
+
run: () => issueForPlugin
|
|
3794
|
+
? go({ name: 'issueDetail', issueId: issueForPlugin.issueId })
|
|
3795
|
+
: go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3796
|
+
} : undefined;
|
|
3797
|
+
const primary = exceptionalAction ?? restoreAction ?? updateAction
|
|
3798
|
+
?? settingsAction ?? {
|
|
3799
|
+
label: '使用说明',
|
|
3800
|
+
description: '查看用途、支持平台和来源',
|
|
3801
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
3802
|
+
};
|
|
3803
|
+
actions = [
|
|
3804
|
+
primary,
|
|
3805
|
+
...(settingsAction && primary !== settingsAction ? [settingsAction] : [{
|
|
3806
|
+
label: '使用说明',
|
|
3807
|
+
description: '查看用途、支持平台和来源',
|
|
3808
|
+
run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }),
|
|
3809
|
+
}]),
|
|
3810
|
+
{
|
|
3811
|
+
label: '更多操作…',
|
|
3812
|
+
description: '平台维护、依赖、通用 Skills、来源和诊断',
|
|
3813
|
+
run: () => go({ name: 'pluginMore', pluginId: plugin.pluginId }),
|
|
3814
|
+
},
|
|
3815
|
+
];
|
|
3816
|
+
const dependencyOverviewForDetail = dependencyOverview(plugin);
|
|
3817
|
+
const dependencyIssue = dependencyOverviewForDetail?.dependencies.find((entry) => entry.aggregateStatus !== 'ready');
|
|
3818
|
+
const dependencyDetailText = dependencyIssue
|
|
3819
|
+
? `${dependencyIssue.aggregateStatus === 'notInstalled' ? '缺少' : '异常'} ${dependencyIssue.displayName},${plugin.displayName} 暂时不可用。恢复会一并处理。`
|
|
3820
|
+
: dependencyOverviewForDetail && dependencyOverviewForDetail.summary.totalCount > 0
|
|
3821
|
+
? `自动使用 ${dependencyOverviewForDetail.summary.totalCount} 个所需插件,全部就绪。查看关系…`
|
|
3822
|
+
: undefined;
|
|
3823
|
+
const detailViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
|
|
3824
|
+
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 }) })] });
|
|
3825
|
+
}
|
|
3826
|
+
else if (route.name === 'pluginMore' && plugin) {
|
|
3827
|
+
breadcrumb = `我的插件 / ${plugin.displayName} / 更多操作`;
|
|
3828
|
+
context = plugin.sourceLabel;
|
|
3829
|
+
title = '更多操作';
|
|
3830
|
+
description = '选择平台可使用同步、重新安装、移除和受限清理。';
|
|
3831
|
+
const dependencySummary = plugin.dependencySummary;
|
|
3832
|
+
const detailMenu = buildPluginDetailActions(plugin);
|
|
3833
|
+
const updateTaskCandidates = props.controller.listPluginWriteTaskCandidates('update')
|
|
3834
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
3835
|
+
const restoreTaskCandidates = props.controller.listPluginWriteTaskCandidates('restore')
|
|
3836
|
+
.filter((candidate) => candidate.target.pluginId === plugin.pluginId);
|
|
3837
|
+
actions = [
|
|
3838
|
+
...(updateTaskCandidates.length > 0 ? [{
|
|
3839
|
+
label: '更新插件…',
|
|
3840
|
+
description: '使用精确版本和现有安装位置',
|
|
3150
3841
|
status: 'warning',
|
|
3151
|
-
run: () => go({
|
|
3152
|
-
|
|
3153
|
-
|
|
3842
|
+
run: () => go({
|
|
3843
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'update',
|
|
3844
|
+
targetKeys: updateTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
3845
|
+
versionEvidence: pluginWriteTaskVersionEvidence(plugin, updateTaskCandidates),
|
|
3846
|
+
}),
|
|
3847
|
+
}] : []),
|
|
3848
|
+
...(restoreTaskCandidates.length > 0 ? [{
|
|
3849
|
+
label: '恢复正常…',
|
|
3850
|
+
description: `${restoreTaskCandidates.length} 个现有位置需要检查`,
|
|
3851
|
+
status: 'warning',
|
|
3852
|
+
run: () => go({
|
|
3853
|
+
name: 'pluginWriteTask', pluginId: plugin.pluginId, goal: 'restore',
|
|
3854
|
+
targetKeys: restoreTaskCandidates.map(pluginWriteTaskTargetKey),
|
|
3855
|
+
}),
|
|
3856
|
+
}] : []),
|
|
3857
|
+
...detailMenu.filter((action) => action.id !== 'refreshAndSync' && action.id !== 'about')
|
|
3858
|
+
.map((action) => {
|
|
3859
|
+
if (action.id === 'platform' || action.id === 'scopeConflict') {
|
|
3860
|
+
const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
|
|
3861
|
+
const projected = placementStatus(placement);
|
|
3862
|
+
return {
|
|
3863
|
+
label: `${platformLabel(action.platform)} 平台与维护`,
|
|
3864
|
+
description: `${projected.description};同步、重装、移除和清理入口`,
|
|
3865
|
+
meta: projected.value,
|
|
3866
|
+
status: projected.status,
|
|
3867
|
+
run: () => action.id === 'scopeConflict'
|
|
3868
|
+
? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
|
|
3869
|
+
: go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
3872
|
+
if (action.id === 'resolveScopeConflict')
|
|
3873
|
+
return {
|
|
3874
|
+
label: `处理 ${platformLabel(action.platform)} 位置冲突…`,
|
|
3875
|
+
description: action.description,
|
|
3876
|
+
status: 'error',
|
|
3877
|
+
run: () => beginScopeConflictRecovery(action),
|
|
3878
|
+
};
|
|
3879
|
+
if (action.id === 'dependencies')
|
|
3880
|
+
return {
|
|
3881
|
+
label: '依赖与被使用情况',
|
|
3882
|
+
description: dependencySummary?.issueCount ? `${dependencySummary.issueCount} 项需要处理` : '查看完整关系',
|
|
3883
|
+
meta: dependencySummary ? dependencySummaryText(dependencySummary) : undefined,
|
|
3884
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
3885
|
+
};
|
|
3886
|
+
if (action.id === 'portableSkills')
|
|
3887
|
+
return {
|
|
3888
|
+
label: '在其他 Agent 使用',
|
|
3889
|
+
description: '通过 Skills CLI 选择和安装通用 Skills',
|
|
3890
|
+
run: () => beginPortable(plugin.pluginId),
|
|
3891
|
+
};
|
|
3892
|
+
if (action.id === 'settings')
|
|
3893
|
+
return {
|
|
3894
|
+
label: plugin.configOnly ? '查看已保存设置' : '插件设置',
|
|
3895
|
+
run: () => plugin.configOnly
|
|
3896
|
+
? props.controller.beginSavedConfigRecovery(plugin.pluginId)
|
|
3897
|
+
: beginConfigFlow(plugin.pluginId),
|
|
3898
|
+
};
|
|
3899
|
+
if (action.id === 'sourceProblem')
|
|
3900
|
+
return {
|
|
3901
|
+
label: '恢复插件来源…', status: 'warning',
|
|
3902
|
+
run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }),
|
|
3903
|
+
};
|
|
3904
|
+
if (action.id === 'upgradeAwCli' || action.id === 'capabilityTechnical')
|
|
3905
|
+
return {
|
|
3906
|
+
label: action.id === 'upgradeAwCli' ? '升级 AW CLI' : '查看功能支持详情',
|
|
3907
|
+
status: action.id === 'upgradeAwCli' ? 'warning' : undefined,
|
|
3908
|
+
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3909
|
+
};
|
|
3154
3910
|
return {
|
|
3155
|
-
label: '
|
|
3156
|
-
description: '查看缺少的功能和安全恢复方向',
|
|
3911
|
+
label: '查看插件详情',
|
|
3157
3912
|
run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
|
|
3158
3913
|
};
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3914
|
+
}),
|
|
3915
|
+
...(!detailMenu.some((action) => action.id === 'dependencies') ? [{
|
|
3916
|
+
label: '依赖与被使用情况',
|
|
3917
|
+
description: '查看必需依赖和已知使用者;当前没有声明的必需依赖',
|
|
3918
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
3919
|
+
}] : []),
|
|
3920
|
+
{ label: '使用说明', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
|
|
3921
|
+
{ label: '查看插件来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) },
|
|
3922
|
+
{ label: '诊断与受管内容', description: '查看组件状态和安全错误代码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
|
|
3923
|
+
];
|
|
3924
|
+
const moreViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
3925
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: moreViewport.offset, limit: moreViewport.limit });
|
|
3165
3926
|
}
|
|
3166
3927
|
else if (route.name === 'platform' && plugin) {
|
|
3167
3928
|
const placement = props.controller.getPlatformPlacement(plugin.pluginId, route.platform);
|
|
@@ -3240,7 +4001,20 @@ export function ConsoleAppV4(props) {
|
|
|
3240
4001
|
run: () => beginScopeConflictRecovery(action),
|
|
3241
4002
|
};
|
|
3242
4003
|
if (action.id === 'refreshAndSync')
|
|
3243
|
-
return {
|
|
4004
|
+
return {
|
|
4005
|
+
label: `更新到 ${action.availableVersion}…`,
|
|
4006
|
+
description: '使用当前启用目录中的已确认版本',
|
|
4007
|
+
run: () => beginReview({
|
|
4008
|
+
kind: 'pluginWrite', action: 'sync', pluginId: plugin.pluginId,
|
|
4009
|
+
platform: action.platform, scope: action.scope,
|
|
4010
|
+
expectedUpdate: {
|
|
4011
|
+
installedVersion: action.installedVersion,
|
|
4012
|
+
activeVersion: action.availableVersion,
|
|
4013
|
+
sourceIdentity: action.sourceIdentity,
|
|
4014
|
+
generationKey: action.generationKey,
|
|
4015
|
+
},
|
|
4016
|
+
}),
|
|
4017
|
+
};
|
|
3244
4018
|
if (action.id === 'sync')
|
|
3245
4019
|
return {
|
|
3246
4020
|
label: '同步已保存的使用位置…',
|
|
@@ -3396,11 +4170,52 @@ export function ConsoleAppV4(props) {
|
|
|
3396
4170
|
? `当前:${effectiveFieldDisplay(selectedField)} · ${selectedField.effectiveSourceLabel}。选择保存位置后直接编辑。`
|
|
3397
4171
|
: selectedPreset ? '选择保存位置后,方案会应用到该位置的草稿。' : '选择修改保存到哪里。';
|
|
3398
4172
|
help = '↑↓ 移动 · Enter 打开 · Esc 返回';
|
|
4173
|
+
const canUseLayer = (scope) => {
|
|
4174
|
+
const page = configLayerPages[scope];
|
|
4175
|
+
if (!page) {
|
|
4176
|
+
if (scope === 'user')
|
|
4177
|
+
return true;
|
|
4178
|
+
if (selectedField)
|
|
4179
|
+
return !selectedField.sensitive && !selectedField.sharedNativeRender;
|
|
4180
|
+
if (selectedPreset && configPage) {
|
|
4181
|
+
const effectiveFields = configPage.groups.flatMap((group) => group.fields);
|
|
4182
|
+
return Object.keys(selectedPreset.values).some((key) => {
|
|
4183
|
+
const field = effectiveFields.find((candidate) => candidate.key === key);
|
|
4184
|
+
return Boolean(field && !field.sensitive && !field.sharedNativeRender);
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
return true;
|
|
4188
|
+
}
|
|
4189
|
+
const fields = page.groups.flatMap((group) => group.fields);
|
|
4190
|
+
if (route.fieldKey) {
|
|
4191
|
+
const field = fields.find((candidate) => candidate.key === route.fieldKey);
|
|
4192
|
+
return Boolean(field && (configFieldCanSet(field) || configFieldCanUnset(field)));
|
|
4193
|
+
}
|
|
4194
|
+
if (selectedPreset) {
|
|
4195
|
+
return Object.keys(selectedPreset.values).some((key) => {
|
|
4196
|
+
const field = fields.find((candidate) => candidate.key === key);
|
|
4197
|
+
return Boolean(field && configFieldCanSet(field));
|
|
4198
|
+
});
|
|
4199
|
+
}
|
|
4200
|
+
return fields.some((field) => configFieldCanSet(field) || configFieldCanUnset(field));
|
|
4201
|
+
};
|
|
3399
4202
|
actions = [
|
|
3400
|
-
|
|
3401
|
-
|
|
4203
|
+
...(canUseLayer('user') ? [{
|
|
4204
|
+
label: '个人默认',
|
|
4205
|
+
description: '影响个人的所有项目;项目设置仍可覆盖',
|
|
4206
|
+
meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`,
|
|
4207
|
+
run: () => openConfigLayerTarget({ scope: 'user' }),
|
|
4208
|
+
}] : []),
|
|
4209
|
+
...(canUseLayer('project') ? [{
|
|
4210
|
+
label: '当前项目',
|
|
4211
|
+
description: '修改仅对当前项目生效;适合随仓库共享的约定',
|
|
4212
|
+
meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`,
|
|
4213
|
+
run: () => openConfigLayerTarget({ scope: 'project' }),
|
|
4214
|
+
}] : []),
|
|
3402
4215
|
];
|
|
3403
|
-
content =
|
|
4216
|
+
content = actions.length > 0
|
|
4217
|
+
? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' })
|
|
4218
|
+
: _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u4FDD\u5B58\u4F4D\u7F6E" });
|
|
3404
4219
|
}
|
|
3405
4220
|
else if (route.name === 'configFields' && plugin) {
|
|
3406
4221
|
const readonly = route.layer.scope === 'effective';
|
|
@@ -3561,19 +4376,43 @@ export function ConsoleAppV4(props) {
|
|
|
3561
4376
|
}
|
|
3562
4377
|
else if (route.name === 'configSave' && plugin && currentConfigPage) {
|
|
3563
4378
|
const changes = configChanges();
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
|
|
4379
|
+
const affectedTargets = configSaveImpact?.affectedTargets ?? [];
|
|
4380
|
+
const hasConsumers = configSaveImpact?.impactState === 'known' && affectedTargets.length > 0;
|
|
4381
|
+
const targetSummary = affectedTargets
|
|
4382
|
+
.map((target) => `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`)
|
|
4383
|
+
.join('、');
|
|
3567
4384
|
breadcrumb = `${plugin.displayName} / 设置 / 保存汇总`;
|
|
3568
4385
|
context = `${changes.length} 项未保存`;
|
|
3569
4386
|
title = `保存 ${changes.length} 项设置修改?`;
|
|
3570
|
-
description = '
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
4387
|
+
description = route.layer.scope === 'project'
|
|
4388
|
+
? '修改将保存到当前项目。'
|
|
4389
|
+
: '修改将保存为个人默认,并影响个人的所有项目;项目设置仍可覆盖。';
|
|
4390
|
+
actions = configSaveImpact ? [
|
|
4391
|
+
hasConsumers ? {
|
|
4392
|
+
label: '保存并生效',
|
|
4393
|
+
description: `保存设置,并重新应用:${targetSummary}`,
|
|
4394
|
+
status: 'success',
|
|
4395
|
+
run: () => beginConfigSave(true),
|
|
4396
|
+
} : {
|
|
4397
|
+
label: '保存设置',
|
|
4398
|
+
description: '当前修改没有需要重新应用的已安装目标',
|
|
4399
|
+
status: 'success',
|
|
4400
|
+
run: () => beginConfigSave(false),
|
|
4401
|
+
},
|
|
4402
|
+
...(hasConsumers ? [{
|
|
4403
|
+
label: '只保存,稍后应用',
|
|
4404
|
+
description: '保存所选设置层,不重新应用当前目标',
|
|
4405
|
+
run: () => beginConfigSave(false),
|
|
4406
|
+
}] : []),
|
|
3574
4407
|
{ 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: [
|
|
4408
|
+
] : [{ label: '返回修改', run: back }];
|
|
4409
|
+
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
|
|
4410
|
+
? _jsx(ProgressRow, { title: "\u5206\u6790\u5B9E\u9645\u914D\u7F6E\u6D88\u8D39\u8005", state: "active" })
|
|
4411
|
+
: hasConsumers
|
|
4412
|
+
? 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}`))
|
|
4413
|
+
: _jsx(StaticRow, { title: configSaveImpactError || configSaveImpact?.impactState === 'unknown'
|
|
4414
|
+
? '无法确认需要重新应用的目标;本次仅保存设置'
|
|
4415
|
+
: '没有需要重新应用的已安装目标', 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
4416
|
}
|
|
3578
4417
|
else if (route.name === 'configFieldAction' && plugin && activeField) {
|
|
3579
4418
|
const layer = configLayerLabel(route.layer);
|
|
@@ -3880,6 +4719,217 @@ export function ConsoleAppV4(props) {
|
|
|
3880
4719
|
]), _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
4720
|
: _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
4721
|
}
|
|
4722
|
+
else if (route.name === 'catalogManagement') {
|
|
4723
|
+
breadcrumb = '插件来源 / 目录管理';
|
|
4724
|
+
title = '目录管理';
|
|
4725
|
+
description = '选择任务后才读取对应的本地状态。';
|
|
4726
|
+
actions = [
|
|
4727
|
+
{ label: 'Catalog 工作区', description: '选择、创建、查看状态、修改版本、构建和验证', run: () => go({ name: 'catalogWorkspace', view: 'overview' }) },
|
|
4728
|
+
{ label: '开发来源', description: '查看、启用或停用当前 Workspace 的开发来源', run: () => go({ name: 'catalogDev' }) },
|
|
4729
|
+
{ label: '本地存储', description: '查看保留项并预览可安全清理的空间', run: () => go({ name: 'catalogStore' }) },
|
|
4730
|
+
{ label: 'Catalog 发布', description: '准备计划、独立应用和恢复可信 pending 计划', run: () => go({ name: 'catalogPublish', step: 'overview' }) },
|
|
4731
|
+
];
|
|
4732
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(CatalogManagementIntro, {}), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
|
|
4733
|
+
}
|
|
4734
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'overview') {
|
|
4735
|
+
const model = catalogWorkspace ? {
|
|
4736
|
+
label: catalogWorkspace.label,
|
|
4737
|
+
state: catalogWorkspace.state,
|
|
4738
|
+
workspaceIdentity: catalogWorkspace.identity ?? undefined,
|
|
4739
|
+
catalogVersion: catalogWorkspace.catalog.version ?? undefined,
|
|
4740
|
+
channel: catalogWorkspace.catalog.channel ?? undefined,
|
|
4741
|
+
pluginCount: catalogWorkspace.plugins.length,
|
|
4742
|
+
snapshotState: catalogWorkspace.snapshot.state,
|
|
4743
|
+
gitState: !catalogWorkspace.git.checked ? '未检查'
|
|
4744
|
+
: !catalogWorkspace.git.hasHead ? '没有 HEAD'
|
|
4745
|
+
: catalogWorkspace.git.clean ? 'clean' : '有未提交变化',
|
|
4746
|
+
issues: catalogWorkspace.issues.map((issue) => issue.message),
|
|
4747
|
+
} : undefined;
|
|
4748
|
+
breadcrumb = '目录管理 / Catalog 工作区';
|
|
4749
|
+
title = 'Catalog 工作区';
|
|
4750
|
+
description = '状态读取不会 build、fetch、创建生成目录或运行插件代码。';
|
|
4751
|
+
actions = [
|
|
4752
|
+
{ label: '选择其他工作区…', description: '输入路径;界面只显示目录名', run: () => { setCatalogInput(''); go({ name: 'catalogWorkspace', view: 'select' }); } },
|
|
4753
|
+
...(catalogWorkspace?.state !== 'ready' ? [{
|
|
4754
|
+
label: '在所选目录创建工作区…', description: '输入 owner、显示名称和初始 Catalog 版本',
|
|
4755
|
+
run: () => { setCatalogInput(catalogWorkspaceDraft.ownerName); go({ name: 'catalogWorkspace', view: 'create', step: 'ownerName' }); },
|
|
4756
|
+
}] : []),
|
|
4757
|
+
...(catalogWorkspace?.state === 'ready' ? [
|
|
4758
|
+
{ label: '修改 Catalog 版本…', description: '选择 bump,或输入稳定版和精确 beta 版本', run: () => go({ name: 'catalogWorkspace', view: 'version' }) },
|
|
4759
|
+
...(catalogWorkspace.snapshot.state === 'absent' ? [{
|
|
4760
|
+
label: '构建 Catalog…', description: `bundle 并验证 ${catalogWorkspace.plugins.length} 个 canonical plugins;写入 .aw/catalog`,
|
|
4761
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild' }),
|
|
4762
|
+
}] : catalogWorkspace.snapshot.valid ? [{
|
|
4763
|
+
label: '重新构建 Catalog…', description: '替换已证明为生成物的 .aw/catalog;不删除其他内容',
|
|
4764
|
+
status: 'warning',
|
|
4765
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild', force: true }),
|
|
4766
|
+
}] : [{
|
|
4767
|
+
label: '生成目录无效,不能自动替换', description: '请检查问题并移开无法证明为生成物的目录后再构建',
|
|
4768
|
+
status: 'error', disabled: '现有生成目录未通过安全验证',
|
|
4769
|
+
}]),
|
|
4770
|
+
{ label: '完整验证生成目录', description: '只读验证现有 .aw/catalog,不重新构建', run: async () => {
|
|
4771
|
+
setCatalogLoading(true);
|
|
4772
|
+
setCatalogValidation(undefined);
|
|
4773
|
+
setCatalogInputError(undefined);
|
|
4774
|
+
try {
|
|
4775
|
+
setCatalogValidation(await props.controller.validateCatalogWorkspace());
|
|
4776
|
+
}
|
|
4777
|
+
catch (error) {
|
|
4778
|
+
setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], []));
|
|
4779
|
+
}
|
|
4780
|
+
finally {
|
|
4781
|
+
setCatalogLoading(false);
|
|
4782
|
+
}
|
|
4783
|
+
} },
|
|
4784
|
+
] : []),
|
|
4785
|
+
];
|
|
4786
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
|
|
4787
|
+
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 })] });
|
|
4788
|
+
}
|
|
4789
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'select') {
|
|
4790
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 选择';
|
|
4791
|
+
title = '输入 Workspace 路径';
|
|
4792
|
+
description = '相对路径从当前目录解析;输入保持在本地,不写入配置。';
|
|
4793
|
+
help = '输入 · Enter 选择 · Ctrl-U 清空 · Esc 取消';
|
|
4794
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: privatePathInput(catalogInput), columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '留空可恢复从当前目录自动发现。', status: catalogInputError ? 'error' : undefined });
|
|
4795
|
+
}
|
|
4796
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'create') {
|
|
4797
|
+
const step = route.step ?? 'ownerName';
|
|
4798
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 创建';
|
|
4799
|
+
context = { ownerName: '第 1 步,共 4 步', ownerUrl: '第 2 步,共 4 步', displayName: '第 3 步,共 4 步', catalogVersion: '第 4 步,共 4 步' }[step];
|
|
4800
|
+
title = { ownerName: '输入 owner 名称', ownerUrl: '输入 owner URL', displayName: '输入 Catalog 显示名称', catalogVersion: '输入初始 Catalog 版本' }[step];
|
|
4801
|
+
description = step === 'catalogVersion' ? '允许稳定 x.y.z 或精确 x.y.z-beta.N。' : '所有字段会在创建确认中再次显示。';
|
|
4802
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
4803
|
+
const displayValue = step === 'ownerUrl'
|
|
4804
|
+
? catalogInput ? `已输入 owner URL(${Array.from(catalogInput).length} 字符)` : ''
|
|
4805
|
+
: catalogInput;
|
|
4806
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: displayValue, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '按 Enter 继续。', status: catalogInputError ? 'error' : undefined });
|
|
4807
|
+
}
|
|
4808
|
+
else if (route.name === 'catalogWorkspace' && route.view === 'version') {
|
|
4809
|
+
breadcrumb = '目录管理 / Catalog 工作区 / 版本';
|
|
4810
|
+
title = route.step === 'catalogVersion' ? '输入精确 Catalog 版本' : '修改 Catalog 版本';
|
|
4811
|
+
description = route.step === 'catalogVersion' ? '允许稳定 x.y.z 或精确 x.y.z-beta.N。' : `当前版本:${catalogWorkspace?.catalog.version ?? '未知'}`;
|
|
4812
|
+
if (route.step === 'catalogVersion') {
|
|
4813
|
+
help = '输入 · Enter 确认 · Ctrl-U 清空 · Esc 取消';
|
|
4814
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: catalogInput, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '此动作会进入独立确认。', status: catalogInputError ? 'error' : undefined });
|
|
4815
|
+
}
|
|
4816
|
+
else {
|
|
4817
|
+
actions = [
|
|
4818
|
+
{ label: '提高 patch 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'patch' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
4819
|
+
{ label: '提高 minor 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'minor' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
4820
|
+
{ label: '提高 major 版本', run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', bump: 'major' }, { name: 'catalogWorkspace', view: 'overview' }) },
|
|
4821
|
+
{ label: '输入精确版本…', description: '用于指定稳定版或精确 beta.N', run: () => { setCatalogInput(''); go({ name: 'catalogWorkspace', view: 'version', step: 'catalogVersion' }); } },
|
|
4822
|
+
];
|
|
4823
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
else if (route.name === 'catalogDev' && (!route.step || route.step === 'overview')) {
|
|
4827
|
+
const snapshotReady = catalogDev?.snapshot.state === 'current' && catalogDev.snapshot.valid;
|
|
4828
|
+
breadcrumb = '目录管理 / 开发来源';
|
|
4829
|
+
title = '开发来源';
|
|
4830
|
+
description = '状态只读取所选 Workspace、managed Path source 和 Active digest。';
|
|
4831
|
+
actions = [
|
|
4832
|
+
{ label: '设置开发来源 ID…', description: catalogDevSourceId || '使用安全默认值', run: () => { setCatalogInput(catalogDevSourceId); go({ name: 'catalogDev', step: 'sourceId' }); } },
|
|
4833
|
+
...!snapshotReady ? catalogDev?.snapshot.state === 'absent' ? [
|
|
4834
|
+
{ label: '先构建 Catalog…', description: '先完成独立构建确认;构建成功后返回此页再确认启用',
|
|
4835
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild' }, { name: 'catalogDev' }) },
|
|
4836
|
+
] : catalogDev?.snapshot.state === 'stale' && catalogDev.snapshot.valid ? [
|
|
4837
|
+
{ label: '先重新构建 Catalog…', description: '独立确认替换受管的旧 .aw/catalog;成功后返回此页再确认启用',
|
|
4838
|
+
status: 'warning',
|
|
4839
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'workspaceBuild', force: true }, { name: 'catalogDev' }) },
|
|
4840
|
+
] : [
|
|
4841
|
+
{ label: '生成目录无效,不能自动替换', description: '请先在 Workspace 管理页检查问题;移开无法证明为生成物的目录后再构建',
|
|
4842
|
+
status: 'error', disabled: '现有生成目录未通过安全验证' },
|
|
4843
|
+
] : catalogDev?.source.exists ? [
|
|
4844
|
+
...!catalogDev.ready && snapshotReady ? [{
|
|
4845
|
+
label: '重新启用并同步开发来源…', description: '来源配置已存在;重新使用当前有效 snapshot 完成 sync 和 Active 切换',
|
|
4846
|
+
status: 'warning',
|
|
4847
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate',
|
|
4848
|
+
...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }, { name: 'catalogDev' }),
|
|
4849
|
+
}] : [],
|
|
4850
|
+
{ label: '停用开发来源…', description: '保留 .aw/catalog;检查安装、设置和 Marketplace 引用', run: () => beginReview({ kind: 'catalogManagement', action: 'devDeactivate' }) },
|
|
4851
|
+
{ label: '停用并选择替代主来源…', description: '开发来源为主来源时使用', run: () => { setCatalogInput(''); go({ name: 'catalogDev', step: 'replacementPrimary' }); } },
|
|
4852
|
+
] : [
|
|
4853
|
+
{ label: '启用开发来源…', description: '使用当前有效 snapshot 注册 managed Path source 并 sync', run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate', ...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }) },
|
|
4854
|
+
{ label: '启用并设为主来源…', description: '将来源启用和主来源变化放在同一影响确认中', run: () => beginReview({ kind: 'catalogManagement', action: 'devActivate', primary: true, ...(catalogDevSourceId ? { sourceId: catalogDevSourceId } : {}) }) },
|
|
4855
|
+
],
|
|
4856
|
+
];
|
|
4857
|
+
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' })] });
|
|
4858
|
+
}
|
|
4859
|
+
else if (route.name === 'catalogDev') {
|
|
4860
|
+
const replacement = route.step === 'replacementPrimary';
|
|
4861
|
+
breadcrumb = '目录管理 / 开发来源 / 输入';
|
|
4862
|
+
title = replacement ? '输入替代主来源 ID' : '输入开发来源 ID';
|
|
4863
|
+
description = replacement ? '停用和主来源变化会进入同一次影响确认。' : '留空可使用安全默认 ID。';
|
|
4864
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
4865
|
+
content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: catalogInput, columns: Math.max(8, size.columns - 8) }), description: catalogInputError ?? '按 Enter 继续。', status: catalogInputError ? 'error' : undefined });
|
|
4866
|
+
}
|
|
4867
|
+
else if (route.name === 'catalogStore') {
|
|
4868
|
+
const entries = catalogStore?.entries ?? [];
|
|
4869
|
+
const pageRows = responsiveBudget(3, 7);
|
|
4870
|
+
const entryOffset = Math.min(batchTargetOffset, Math.max(0, entries.length - 1));
|
|
4871
|
+
const entryEnd = Math.min(entries.length, entryOffset + pageRows);
|
|
4872
|
+
const reclaimable = entries.filter((entry) => entry.status === 'reclaimable');
|
|
4873
|
+
breadcrumb = '目录管理 / 本地存储';
|
|
4874
|
+
title = '本地 Catalog Store';
|
|
4875
|
+
description = '只清理已确认且执行时仍安全的弱状态;未知、foreign 和 Active 内容始终保留。';
|
|
4876
|
+
if (entries.length > pageRows)
|
|
4877
|
+
help = 'PgUp/PgDn 查看全部条目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4878
|
+
actions = [{
|
|
4879
|
+
label: '预览并清理可安全释放的空间…',
|
|
4880
|
+
description: `${reclaimable.length} 项;确认后新增候选不进入本次删除集合`,
|
|
4881
|
+
disabled: reclaimable.length ? undefined : '当前没有可安全清理的条目',
|
|
4882
|
+
run: () => beginReview({ kind: 'catalogManagement', action: 'storePrune', items: reclaimable.map((entry) => entry.item) }),
|
|
4883
|
+
}];
|
|
4884
|
+
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' })] });
|
|
4885
|
+
}
|
|
4886
|
+
else if (route.name === 'catalogPublish' && route.step === 'overview') {
|
|
4887
|
+
const records = catalogPublication?.records ?? [];
|
|
4888
|
+
const pageRows = responsiveBudget(3, 7);
|
|
4889
|
+
const recordOffset = Math.min(batchTargetOffset, Math.max(0, records.length - 1));
|
|
4890
|
+
const recordEnd = Math.min(records.length, recordOffset + pageRows);
|
|
4891
|
+
breadcrumb = '目录管理 / Catalog 发布';
|
|
4892
|
+
title = 'Catalog 发布';
|
|
4893
|
+
description = '准备计划与应用计划是两个独立写入动作。';
|
|
4894
|
+
if (records.length > pageRows)
|
|
4895
|
+
help = 'PgUp/PgDn 查看全部记录 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4896
|
+
actions = [
|
|
4897
|
+
{ 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' }); } },
|
|
4898
|
+
...records.filter((record) => record.canApply && record.planDigest).map((record) => ({
|
|
4899
|
+
label: `${record.state === 'uncertain' ? '对账或继续' : '应用'} ${record.catalogVersion}…`,
|
|
4900
|
+
description: record.requiresRemoteInput
|
|
4901
|
+
? '重新输入原 remote 名称或 URL;只恢复现有可信 plan digest'
|
|
4902
|
+
: '只使用现有可信 plan digest;不会从当前源码重建计划',
|
|
4903
|
+
status: record.state === 'uncertain' ? 'warning' : undefined,
|
|
4904
|
+
run: () => record.requiresRemoteInput
|
|
4905
|
+
? (() => {
|
|
4906
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: record.branch ?? 'catalog', expectedPlanDigest: record.planDigest });
|
|
4907
|
+
setCatalogInput('');
|
|
4908
|
+
go({ name: 'catalogPublish', step: 'target' });
|
|
4909
|
+
})()
|
|
4910
|
+
: beginReview({ kind: 'catalogManagement', action: 'publishApply', expectedPlanDigest: record.planDigest, ...(record.remote ? { remote: record.remote } : {}), ...(record.branch ? { branch: record.branch } : {}) }, { name: 'catalogPublish', step: 'overview' }),
|
|
4911
|
+
})),
|
|
4912
|
+
...records.filter((record) => record.mustIncreaseVersion).map((record) => ({
|
|
4913
|
+
label: `${record.catalogVersion} 无法恢复`, description: 'journal 或 pending repository 缺失或损坏;请提高 Catalog 版本后创建新计划', status: 'error', disabled: '不能重建或复用此版本',
|
|
4914
|
+
})),
|
|
4915
|
+
];
|
|
4916
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
|
|
4917
|
+
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 })] });
|
|
4918
|
+
}
|
|
4919
|
+
else if (route.name === 'catalogPublish') {
|
|
4920
|
+
breadcrumb = '目录管理 / Catalog 发布 / 准备计划';
|
|
4921
|
+
title = route.step === 'target' ? '输入 remote 名称或 remote URL' : '输入发布 branch';
|
|
4922
|
+
description = route.step === 'target'
|
|
4923
|
+
? catalogPublishDraft.expectedPlanDigest
|
|
4924
|
+
? '重新输入原目标。后端会与现有 plan digest 中的目标身份核对。'
|
|
4925
|
+
: 'URL 只显示脱敏摘要;remote 名称按原样使用。'
|
|
4926
|
+
: '留空使用固定默认 branch:catalog。';
|
|
4927
|
+
help = '输入 · Enter 继续 · Ctrl-U 清空 · Esc 取消';
|
|
4928
|
+
const displayValue = route.step === 'target'
|
|
4929
|
+
? catalogInput ? `已输入远程目标(${Array.from(catalogInput).length} 字符)` : ''
|
|
4930
|
+
: catalogInput;
|
|
4931
|
+
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 });
|
|
4932
|
+
}
|
|
3883
4933
|
else if (route.name === 'sourceList') {
|
|
3884
4934
|
breadcrumb = '插件来源';
|
|
3885
4935
|
title = '插件来源';
|
|
@@ -3912,9 +4962,14 @@ export function ConsoleAppV4(props) {
|
|
|
3912
4962
|
description: '并行检查各来源;结果不会自动启用待检查版本',
|
|
3913
4963
|
run: () => props.controller.recheckAllCatalogSources(),
|
|
3914
4964
|
},
|
|
4965
|
+
{
|
|
4966
|
+
label: '目录管理…',
|
|
4967
|
+
description: 'Catalog 工作区、开发来源、本地存储和发布',
|
|
4968
|
+
run: () => go({ name: 'catalogManagement' }),
|
|
4969
|
+
},
|
|
3915
4970
|
];
|
|
3916
4971
|
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
|
|
3917
|
-
const sourceCount = Math.max(0, actions.length -
|
|
4972
|
+
const sourceCount = Math.max(0, actions.length - 3);
|
|
3918
4973
|
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
4974
|
}
|
|
3920
4975
|
else if (route.name === 'sourceDetail') {
|
|
@@ -4018,8 +5073,7 @@ export function ConsoleAppV4(props) {
|
|
|
4018
5073
|
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
4019
5074
|
run: () => props.controller.recheckCatalogSource(route.sourceId),
|
|
4020
5075
|
}];
|
|
4021
|
-
|
|
4022
|
-
...generationAction,
|
|
5076
|
+
const managementActions = [
|
|
4023
5077
|
{ label: '编辑来源…', description: '集中修改地址、引用、子目录和自动检查', run: () => openSourceEdit(route.sourceId) },
|
|
4024
5078
|
...(generationState === 'incompatible'
|
|
4025
5079
|
|| (freshness?.state === 'incompatible' && generationState !== 'ready') ? [] : [{
|
|
@@ -4058,10 +5112,101 @@ export function ConsoleAppV4(props) {
|
|
|
4058
5112
|
run: () => props.controller.fullValidateCatalogSource(route.sourceId),
|
|
4059
5113
|
},
|
|
4060
5114
|
];
|
|
5115
|
+
actions = [
|
|
5116
|
+
...generationAction.slice(0, 1),
|
|
5117
|
+
managementActions[0],
|
|
5118
|
+
{
|
|
5119
|
+
label: '更多处理方式…',
|
|
5120
|
+
description: '设为主来源、移除、精确修复、替换、重新检查和完整验证',
|
|
5121
|
+
run: () => go({ name: 'sourceMore', sourceId: route.sourceId }),
|
|
5122
|
+
},
|
|
5123
|
+
];
|
|
4061
5124
|
const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
|
|
4062
5125
|
const freshnessLabel = catalogSourceFreshnessLabel(freshness);
|
|
4063
5126
|
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
5127
|
}
|
|
5128
|
+
else if (route.name === 'sourceMore') {
|
|
5129
|
+
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
5130
|
+
const detail = overview.detail;
|
|
5131
|
+
const generation = props.controller.getCatalogGeneration(route.sourceId);
|
|
5132
|
+
const freshness = props.controller.getCatalogFreshness(route.sourceId);
|
|
5133
|
+
const generationState = generation?.state ?? (generation?.active ? 'ready' : 'unavailable');
|
|
5134
|
+
const combinedRepairAndMismatch = generationState === 'repairRequired'
|
|
5135
|
+
&& generation?.configMismatch === true;
|
|
5136
|
+
const logicalIdentityChanged = generation?.errorCode === 'catalogLogicalIdentityChanged';
|
|
5137
|
+
const activationBlocked = props.controller.catalogSourceRecoveryBlockReason();
|
|
5138
|
+
const blocked = activationBlocked ? `请先处理:${sourceOperationBlockLabel(activationBlocked)}` : undefined;
|
|
5139
|
+
breadcrumb = `插件来源 / ${route.sourceId} / 更多处理方式`;
|
|
5140
|
+
title = '管理来源';
|
|
5141
|
+
description = '选择精确操作。每个写入动作都会进入独立确认。';
|
|
5142
|
+
help = '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5143
|
+
actions = [
|
|
5144
|
+
...(generationState === 'repairRequired' ? [{
|
|
5145
|
+
label: '重新下载旧版本并精确修复',
|
|
5146
|
+
description: '使用当前启用版本的精确提交;成功后保持此版本',
|
|
5147
|
+
disabled: blocked,
|
|
5148
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'repair', sourceId: route.sourceId }),
|
|
5149
|
+
}, ...freshness?.state === 'incompatible' ? [] : [{
|
|
5150
|
+
label: '使用待检查版本替换损坏版本…',
|
|
5151
|
+
description: '旧版本无法取得时使用;这会进入独立风险确认',
|
|
5152
|
+
disabled: blocked,
|
|
5153
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'replaceDamaged', sourceId: route.sourceId }),
|
|
5154
|
+
}]] : []),
|
|
5155
|
+
...(generationState !== 'ready' && generationState !== 'repairRequired'
|
|
5156
|
+
&& generationState !== 'incompatible' && !logicalIdentityChanged ? [{
|
|
5157
|
+
label: generationState === 'stateInvalid' ? '恢复状态并启用'
|
|
5158
|
+
: generationState === 'configMismatch' ? '验证并切换' : '获取并启用',
|
|
5159
|
+
description: '先准备只读候选;确认后才切换当前目录版本',
|
|
5160
|
+
disabled: blocked,
|
|
5161
|
+
run: () => beginReview({
|
|
5162
|
+
kind: 'catalogGeneration',
|
|
5163
|
+
action: generationState === 'stateInvalid' ? 'recoverState'
|
|
5164
|
+
: generationState === 'configMismatch' ? 'update' : 'activate',
|
|
5165
|
+
sourceId: route.sourceId,
|
|
5166
|
+
}),
|
|
5167
|
+
}] : []),
|
|
5168
|
+
...(generationState === 'ready'
|
|
5169
|
+
&& (freshness?.state === 'updateAvailable' || freshness?.state === 'localChangesAvailable') ? [{
|
|
5170
|
+
label: freshness.state === 'localChangesAvailable' ? '应用本地内容变化…'
|
|
5171
|
+
: `更新到 ${freshness.candidate?.catalogVersion ?? '待检查版本'}…`,
|
|
5172
|
+
description: '确认前不会切换当前版本',
|
|
5173
|
+
disabled: blocked,
|
|
5174
|
+
run: () => beginReview({ kind: 'catalogGeneration', action: 'update', sourceId: route.sourceId }),
|
|
5175
|
+
}] : []),
|
|
5176
|
+
{
|
|
5177
|
+
label: generationState === 'ready' ? '设为主来源…' : '启用并设为主来源…',
|
|
5178
|
+
description: generationState === 'ready' ? '只改变默认插件来源' : '启用已验证候选后改变默认来源',
|
|
5179
|
+
disabled: detail?.primary ? '当前已经是主来源'
|
|
5180
|
+
: logicalIdentityChanged ? '请编辑来源,或移除后重新添加' : blocked,
|
|
5181
|
+
run: () => generationState === 'ready'
|
|
5182
|
+
? beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId,
|
|
5183
|
+
changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] })
|
|
5184
|
+
: beginReview({
|
|
5185
|
+
kind: 'catalogGeneration',
|
|
5186
|
+
action: generationState === 'stateInvalid' ? 'recoverState'
|
|
5187
|
+
: generationState === 'repairRequired' && !combinedRepairAndMismatch ? 'repair'
|
|
5188
|
+
: generationState === 'configMismatch' || combinedRepairAndMismatch ? 'update' : 'activate',
|
|
5189
|
+
sourceId: route.sourceId,
|
|
5190
|
+
setPrimaryAfterActivation: true,
|
|
5191
|
+
}),
|
|
5192
|
+
},
|
|
5193
|
+
{ label: '移除来源…', description: '检查当前、历史和未知引用后再确认', status: 'error',
|
|
5194
|
+
disabled: detail?.primary ? '主来源不能直接移除' : undefined,
|
|
5195
|
+
run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId,
|
|
5196
|
+
changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
|
|
5197
|
+
{ label: freshness?.state === 'checking' ? '正在检查…' : '重新检查',
|
|
5198
|
+
description: '轻量检查这个来源;不会自动启用更新',
|
|
5199
|
+
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
5200
|
+
run: () => props.controller.recheckCatalogSource(route.sourceId) },
|
|
5201
|
+
{ label: '完整验证', description: '强制重新取得并完整验证这个来源',
|
|
5202
|
+
disabled: freshness?.state === 'checking' ? '检查正在进行' : undefined,
|
|
5203
|
+
run: () => props.controller.fullValidateCatalogSource(route.sourceId) },
|
|
5204
|
+
{ label: '技术详情', description: '查看脱敏错误代码和版本证据',
|
|
5205
|
+
run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
|
|
5206
|
+
];
|
|
5207
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(1));
|
|
5208
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
5209
|
+
}
|
|
4065
5210
|
else if (route.name === 'sourceTechnical') {
|
|
4066
5211
|
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
4067
5212
|
const detail = overview.detail;
|
|
@@ -4097,16 +5242,14 @@ export function ConsoleAppV4(props) {
|
|
|
4097
5242
|
if (route.step === 'type') {
|
|
4098
5243
|
title = '选择来源类型';
|
|
4099
5244
|
description = '来源类型创建后不能修改。';
|
|
4100
|
-
const selectType = (type
|
|
4101
|
-
setSourceDraft({ ...sourceDraft, type
|
|
5245
|
+
const selectType = (type) => {
|
|
5246
|
+
setSourceDraft({ ...sourceDraft, type });
|
|
4102
5247
|
setDirty(true);
|
|
4103
5248
|
go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner });
|
|
4104
5249
|
};
|
|
4105
5250
|
actions = [
|
|
4106
|
-
{ label: 'Git 仓库', description: '
|
|
4107
|
-
{ label: '本地目录', description: '
|
|
4108
|
-
{ label: 'Git 仓库 · 关闭自动检查', description: '仍可从详情页手动检查', run: () => selectType('git', false) },
|
|
4109
|
-
{ label: '本地目录 · 关闭自动检查', description: '仍可从详情页手动检查', run: () => selectType('path', false) },
|
|
5251
|
+
{ label: 'Git 仓库', description: '使用现有自动检查默认值;可在修改汇总前单独更改', run: () => selectType('git') },
|
|
5252
|
+
{ label: '本地目录', description: '使用现有自动检查默认值;可在修改汇总前单独更改', run: () => selectType('path') },
|
|
4110
5253
|
];
|
|
4111
5254
|
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
4112
5255
|
}
|
|
@@ -4534,13 +5677,15 @@ export function ConsoleAppV4(props) {
|
|
|
4534
5677
|
}
|
|
4535
5678
|
else if (route.name === 'reviewPreparing' && snapshot.reviewPreparation) {
|
|
4536
5679
|
const preparation = snapshot.reviewPreparation;
|
|
4537
|
-
const labels = preparation.operationKind === '
|
|
4538
|
-
? ['
|
|
4539
|
-
: preparation.operationKind === '
|
|
4540
|
-
? ['
|
|
4541
|
-
: preparation.operationKind === '
|
|
4542
|
-
? ['
|
|
4543
|
-
:
|
|
5680
|
+
const labels = preparation.operationKind === 'pluginWriteTask'
|
|
5681
|
+
? ['插件任务', '正在准备任务影响确认']
|
|
5682
|
+
: preparation.operationKind === 'portableInstall'
|
|
5683
|
+
? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
|
|
5684
|
+
: preparation.operationKind === 'configSave' || preparation.operationKind === 'configReset'
|
|
5685
|
+
? ['设置', '正在准备设置确认']
|
|
5686
|
+
: preparation.operationKind === 'sourceMutation' || preparation.operationKind === 'catalogGeneration'
|
|
5687
|
+
? ['插件来源', '正在准备来源变更确认']
|
|
5688
|
+
: ['插件操作', '正在准备插件操作确认'];
|
|
4544
5689
|
breadcrumb = `${labels[0]} / 准备确认`;
|
|
4545
5690
|
context = '只读';
|
|
4546
5691
|
title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
|
|
@@ -4569,22 +5714,37 @@ export function ConsoleAppV4(props) {
|
|
|
4569
5714
|
}
|
|
4570
5715
|
else if (route.name === 'review' && snapshot.activeReview) {
|
|
4571
5716
|
const review = snapshot.activeReview;
|
|
5717
|
+
const installReviewPluginId = pendingReviewIntent?.kind === 'pluginWrite'
|
|
5718
|
+
&& pendingReviewIntent.action === 'install'
|
|
5719
|
+
? pendingReviewIntent.pluginId
|
|
5720
|
+
: review.pluginAction === 'install' ? snapshot.installPlacementDraft?.pluginId : undefined;
|
|
4572
5721
|
const batchReview = review.operationKind === 'pluginWriteBatch';
|
|
5722
|
+
const taskReview = review.operationKind === 'pluginWriteTask';
|
|
5723
|
+
const catalogManagementReview = review.operationKind === 'catalogManagement';
|
|
4573
5724
|
const sharedImpactConfirmed = !review.requiresConfirmation
|
|
4574
5725
|
|| confirmedSharedReviewToken === review.reviewToken;
|
|
4575
5726
|
breadcrumb = `${review.subject.name} / ${review.title}`;
|
|
4576
|
-
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall'
|
|
5727
|
+
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' || taskReview
|
|
5728
|
+
? review.title : `确认${review.title}`} ${review.subject.name}`;
|
|
4577
5729
|
description = snapshot.result?.state === 'stale'
|
|
4578
5730
|
? '当前状态已变化。请检查新的变化后再次确认。'
|
|
4579
5731
|
: review.requiresConfirmation ? '' : review.description;
|
|
4580
|
-
if (
|
|
5732
|
+
if (taskReview) {
|
|
5733
|
+
context = '精确目标 · 用户任务';
|
|
5734
|
+
help = 'PgUp/PgDn 查看全部影响 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5735
|
+
}
|
|
5736
|
+
else if (batchReview) {
|
|
4581
5737
|
context = `${review.batch?.requestedPluginIds.length ?? 0} 个根插件 · ${review.batch?.groupCount ?? 0} 组`;
|
|
4582
5738
|
help = 'PgUp/PgDn 查看全部根插件和依赖 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4583
5739
|
}
|
|
4584
5740
|
actions = [
|
|
4585
5741
|
...(review.requiresConfirmation ? [{
|
|
4586
|
-
label:
|
|
4587
|
-
|
|
5742
|
+
label: catalogManagementReview
|
|
5743
|
+
? sharedImpactConfirmed ? '已确认本次写入' : '确认本次写入'
|
|
5744
|
+
: sharedImpactConfirmed ? '已确认共享影响' : '确认共享影响',
|
|
5745
|
+
description: catalogManagementReview
|
|
5746
|
+
? '我已检查上方的精确写入、远端影响和保留项。'
|
|
5747
|
+
: review.sharedImpact.warning ?? '此操作会修改多个插件共同使用的 Codex 平台内容',
|
|
4588
5748
|
selected: sharedImpactConfirmed,
|
|
4589
5749
|
run: () => {
|
|
4590
5750
|
setConfirmedSharedReviewToken(review.reviewToken);
|
|
@@ -4602,6 +5762,19 @@ export function ConsoleAppV4(props) {
|
|
|
4602
5762
|
operationBusy.current = false;
|
|
4603
5763
|
});
|
|
4604
5764
|
} },
|
|
5765
|
+
...(installReviewPluginId
|
|
5766
|
+
&& (snapshot.installPlacementDraft?.targets.length ?? 0) > 0 ? [{
|
|
5767
|
+
label: '查看/调整依赖位置',
|
|
5768
|
+
description: '当前已采用 planner 推荐位置;调整后会重新建立 Review',
|
|
5769
|
+
run: () => {
|
|
5770
|
+
props.controller.push({
|
|
5771
|
+
name: 'dependencyPlacement',
|
|
5772
|
+
pluginId: installReviewPluginId,
|
|
5773
|
+
});
|
|
5774
|
+
setFocus(0);
|
|
5775
|
+
setScrollOffset(0);
|
|
5776
|
+
},
|
|
5777
|
+
}] : []),
|
|
4605
5778
|
...(sharedImpactConfirmed && (pendingReviewIntent?.kind === 'pluginWrite'
|
|
4606
5779
|
&& (pendingReviewIntent.action === 'install'
|
|
4607
5780
|
|| pendingReviewIntent.action === 'refreshAndSync'
|
|
@@ -4611,6 +5784,7 @@ export function ConsoleAppV4(props) {
|
|
|
4611
5784
|
&& (pendingReviewIntent.action === 'install'
|
|
4612
5785
|
|| pendingReviewIntent.action === 'sync'
|
|
4613
5786
|
|| pendingReviewIntent.action === 'reinstall')
|
|
5787
|
+
|| pendingReviewIntent?.kind === 'pluginWriteTask'
|
|
4614
5788
|
|| pendingReviewIntent?.kind === 'configSave' && pendingReviewIntent.apply) ? [{
|
|
4615
5789
|
label: '高级选项:脚本超时',
|
|
4616
5790
|
description: `${pendingReviewIntent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS} 毫秒`,
|
|
@@ -4736,13 +5910,26 @@ export function ConsoleAppV4(props) {
|
|
|
4736
5910
|
? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
|
|
4737
5911
|
});
|
|
4738
5912
|
const showEquivalentCommand = Boolean(review.equivalentCommand && showTechnical && !review.requiresConfirmation);
|
|
5913
|
+
const installDependencyTargets = installReviewPluginId
|
|
5914
|
+
? snapshot.installPlacementDraft?.targets ?? [] : [];
|
|
5915
|
+
const installDependencyPluginIds = new Set(installDependencyTargets.map((target) => target.pluginId));
|
|
5916
|
+
const reusedDependencyPluginIds = new Set(installDependencyTargets
|
|
5917
|
+
.filter((target) => target.action === 'keep')
|
|
5918
|
+
.map((target) => target.pluginId));
|
|
5919
|
+
const newDependencyCount = [...installDependencyPluginIds]
|
|
5920
|
+
.filter((pluginId) => !reusedDependencyPluginIds.has(pluginId)).length;
|
|
5921
|
+
const widenedDependencyCount = new Set(installDependencyTargets
|
|
5922
|
+
.filter((target) => target.affectsOtherProjects && target.action !== 'keep')
|
|
5923
|
+
.map((target) => target.pluginId)).size;
|
|
4739
5924
|
if (review.changes.length > staticRowBudget || showTechnical
|
|
4740
5925
|
&& (review.changes.length > technicalChangeBudget || technicalDetails.length > technicalDetailLimit)) {
|
|
4741
5926
|
help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
4742
5927
|
}
|
|
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:
|
|
5928
|
+
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
5929
|
}
|
|
4745
5930
|
else if (route.name === 'progress') {
|
|
5931
|
+
const taskOperation = snapshot.activeReview?.operationKind === 'pluginWriteTask';
|
|
5932
|
+
const catalogManagementOperation = snapshot.activeReview?.operationKind === 'catalogManagement';
|
|
4746
5933
|
const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
|
|
4747
5934
|
const configOperation = snapshot.activeReview?.operationKind === 'configSave'
|
|
4748
5935
|
|| snapshot.activeReview?.operationKind === 'configReset';
|
|
@@ -4750,24 +5937,25 @@ export function ConsoleAppV4(props) {
|
|
|
4750
5937
|
|| snapshot.activeReview?.operationKind === 'catalogGeneration'
|
|
4751
5938
|
|| snapshot.activeReview?.operationKind === 'catalogSourcesRecovery';
|
|
4752
5939
|
const issueOperation = snapshot.activeReview?.originIssue;
|
|
4753
|
-
breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
|
|
5940
|
+
breadcrumb = catalogManagementOperation ? '目录管理 / 进度' : taskOperation ? '插件任务 / 进度' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
|
|
4754
5941
|
const configApply = snapshot.activeReview?.operationKind === 'configSave'
|
|
4755
5942
|
&& snapshot.activeReview.changes.some((change) => change.group === 'configImpact' && change.kind === 'change');
|
|
4756
5943
|
const catalogGenerationOperation = snapshot.activeReview?.operationKind === 'catalogGeneration';
|
|
4757
5944
|
const catalogSourcesRecovery = snapshot.activeReview?.operationKind === 'catalogSourcesRecovery';
|
|
4758
|
-
title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题'
|
|
5945
|
+
title = catalogManagementOperation ? '正在执行 Catalog 管理操作' : taskOperation ? '正在执行插件任务' : portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题'
|
|
4759
5946
|
: catalogGenerationOperation ? '正在验证并启用目录版本'
|
|
4760
5947
|
: catalogSourcesRecovery ? '正在恢复插件来源配置'
|
|
4761
5948
|
: sourceOperation ? '正在保存来源配置'
|
|
4762
5949
|
: configOperation ? configApply ? '正在保存并应用设置' : '正在保存设置'
|
|
4763
5950
|
: '正在执行插件操作';
|
|
4764
|
-
description =
|
|
4765
|
-
:
|
|
4766
|
-
:
|
|
4767
|
-
:
|
|
4768
|
-
:
|
|
4769
|
-
: '
|
|
4770
|
-
|
|
5951
|
+
description = catalogManagementOperation ? '正在执行已确认的精确 Workspace、Store、开发来源或发布操作。'
|
|
5952
|
+
: issueOperation ? '完成后会重新读取状态并检查问题。'
|
|
5953
|
+
: catalogGenerationOperation ? '正在验证并应用已确认的目录版本操作。'
|
|
5954
|
+
: catalogSourcesRecovery ? '正在备份损坏配置并恢复官方默认来源。'
|
|
5955
|
+
: sourceOperation ? '只原子保存来源配置,不访问网络。'
|
|
5956
|
+
: configOperation ? configApply ? '先保存所选层,再重新应用当前可见安装位置。' : '只保存所选设置层。'
|
|
5957
|
+
: '请勿关闭当前终端。';
|
|
5958
|
+
const batchProgress = snapshot.activeReview?.operationKind === 'pluginWriteBatch' || taskOperation;
|
|
4771
5959
|
const progress = snapshot.progress;
|
|
4772
5960
|
const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
|
|
4773
5961
|
?? (progress.startedAtMs === undefined ? 0 : Date.now() - progress.startedAtMs))) / 1_000));
|
|
@@ -4869,7 +6057,7 @@ export function ConsoleAppV4(props) {
|
|
|
4869
6057
|
return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
|
|
4870
6058
|
};
|
|
4871
6059
|
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) => {
|
|
6060
|
+
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
6061
|
const state = progressLabel(entry);
|
|
4874
6062
|
const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
|
|
4875
6063
|
const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
|
|
@@ -4889,6 +6077,27 @@ export function ConsoleAppV4(props) {
|
|
|
4889
6077
|
: result.state === 'blocked' ? result.summary : undefined;
|
|
4890
6078
|
const action = resultSummary?.action;
|
|
4891
6079
|
const operationKind = 'operationKind' in result ? result.operationKind : undefined;
|
|
6080
|
+
const catalogManagementOperation = operationKind === 'catalogManagement';
|
|
6081
|
+
const catalogManagementResult = catalogManagementOperation
|
|
6082
|
+
? resultSummary?.catalogManagement : undefined;
|
|
6083
|
+
const taskOperation = operationKind === 'pluginWriteTask';
|
|
6084
|
+
const taskSummary = resultSummary?.task;
|
|
6085
|
+
const taskBlockedCount = taskSummary?.blockedTargets.length ?? 0;
|
|
6086
|
+
const taskBatchOrigin = 'returnRoute' in result
|
|
6087
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
6088
|
+
? result.returnRoute.batchOrigin : undefined;
|
|
6089
|
+
const taskReturnRoute = taskSummary?.requestedTargets[0] ? {
|
|
6090
|
+
name: 'pluginWriteTask',
|
|
6091
|
+
pluginId: taskSummary.requestedTargets[0].pluginId,
|
|
6092
|
+
goal: taskSummary.goal,
|
|
6093
|
+
targetKeys: taskSummary.requestedTargets.map((target) => [target.pluginId, target.platform, target.scope].join('\0')),
|
|
6094
|
+
...(taskBatchOrigin ? { batchOrigin: taskBatchOrigin } : {}),
|
|
6095
|
+
...(taskSummary.goal === 'update' ? {
|
|
6096
|
+
versionEvidence: Object.fromEntries(taskSummary.requestedTargets.flatMap((target) => target.expectedUpdate
|
|
6097
|
+
? [[[target.pluginId, target.platform, target.scope].join('\0'), target.expectedUpdate.activeVersion]]
|
|
6098
|
+
: [])),
|
|
6099
|
+
} : {}),
|
|
6100
|
+
} : undefined;
|
|
4892
6101
|
const batchOperation = operationKind === 'pluginWriteBatch';
|
|
4893
6102
|
const portableOperation = operationKind === 'portableInstall';
|
|
4894
6103
|
const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
|
|
@@ -4912,79 +6121,96 @@ export function ConsoleAppV4(props) {
|
|
|
4912
6121
|
cleanup: '✓ 插件清理完成',
|
|
4913
6122
|
sync: '✓ 插件同步完成',
|
|
4914
6123
|
}[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
|
-
|
|
6124
|
+
title = catalogManagementOperation
|
|
6125
|
+
? blocked ? '× Catalog 管理操作未完成'
|
|
6126
|
+
: partial ? '! Catalog 管理操作只完成了一部分'
|
|
6127
|
+
: catalogManagementResult?.action === 'publishPlan' ? '✓ 发布计划已准备'
|
|
6128
|
+
: catalogManagementResult?.action === 'publishApply' ? '✓ Catalog 发布完成'
|
|
6129
|
+
: catalogManagementResult?.action === 'storePrune' ? '✓ Store 清理完成'
|
|
6130
|
+
: '✓ Catalog 管理操作完成'
|
|
6131
|
+
: taskOperation
|
|
6132
|
+
? blocked ? `× 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}没有完成`
|
|
6133
|
+
: partial ? `! 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}只完成了一部分`
|
|
6134
|
+
: `✓ 插件${pluginWriteTaskLabel(taskSummary?.goal ?? 'restore')}完成`
|
|
6135
|
+
: batchOperation
|
|
6136
|
+
? blocked ? '× 批量操作没有完成'
|
|
6137
|
+
: partial ? '! 批量操作只完成了一部分' : `✓ 批量${batchActionLabel(action)}完成`
|
|
6138
|
+
: portableOperation
|
|
6139
|
+
? blocked ? '× 通用 Skills 安装被阻止'
|
|
6140
|
+
: partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
|
|
6141
|
+
: blocked && result.writeMayHaveCompleted
|
|
6142
|
+
? '× 写入结果未确认'
|
|
6143
|
+
: issueOperation
|
|
6144
|
+
? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
|
|
6145
|
+
: sourceOperation
|
|
6146
|
+
? blocked ? '× 来源操作未完成'
|
|
6147
|
+
: operationKind === 'catalogSourcesRecovery' ? '✓ Catalog 来源配置已恢复'
|
|
6148
|
+
: resultSummary?.catalogAction === 'update'
|
|
6149
|
+
? resultSummary.catalogGeneration?.activeChanged
|
|
6150
|
+
&& resultSummary.catalogGeneration.outcome === 'activated'
|
|
6151
|
+
? '✓ Catalog 已更新' : '✓ Catalog 无需更新'
|
|
6152
|
+
: resultSummary?.catalogAction === 'repair' ? '✓ Catalog 已修复'
|
|
6153
|
+
: resultSummary?.catalogAction === 'replaceDamaged' ? '✓ 损坏的 Catalog 已替换'
|
|
6154
|
+
: resultSummary?.catalogAction === 'recoverState' ? '✓ Catalog 状态已恢复'
|
|
6155
|
+
: operationKind === 'catalogGeneration' ? '✓ Catalog 已启用'
|
|
6156
|
+
: '✓ 来源配置已保存'
|
|
6157
|
+
: configOperation
|
|
6158
|
+
? blocked
|
|
6159
|
+
? '× 设置未确认保存'
|
|
6160
|
+
: partial ? resultSummary?.configApplySkippedReason === 'invalidConfig'
|
|
6161
|
+
? '! 设置已保存,但未应用'
|
|
6162
|
+
: '! 设置已保存,但部分应用失败'
|
|
6163
|
+
: resultSummary?.configApplied ? '✓ 设置已保存并应用' : '✓ 设置已保存'
|
|
6164
|
+
: partial ? '! 插件操作只完成了一部分'
|
|
6165
|
+
: blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
|
|
6166
|
+
: completedTitle;
|
|
6167
|
+
description = catalogManagementOperation
|
|
6168
|
+
? blocked ? result.message : resultSummary?.message ?? 'Catalog 状态已更新。'
|
|
6169
|
+
: taskOperation
|
|
6170
|
+
? blocked ? '任务被安全阻止。已完成和未完成目标按实际结果保留。'
|
|
6171
|
+
: partial ? `${taskSummary?.retryTargets?.length ?? 0} 个失败目标可重试 · ${taskBlockedCount} 个目标需要决定。`
|
|
6172
|
+
: `${taskSummary?.rootResults.length ?? 0} 个根插件已完成。`
|
|
6173
|
+
: batchOperation
|
|
6174
|
+
? blocked
|
|
6175
|
+
? '批量写入被阻止。已完成和未完成目标按实际结果保留。'
|
|
6176
|
+
: partial
|
|
6177
|
+
? (resultSummary?.batch?.retryTargets?.length ?? 0) > 0
|
|
6178
|
+
? `${resultSummary?.batch?.failedRequestedPluginIds.length ?? 0} 个根插件失败,涉及 ${resultSummary?.batch?.retryTargets?.length ?? 0} 个位置。成功目标不会重放。`
|
|
6179
|
+
: '失败目标无法精确定位。请查看本次记录后重新操作。'
|
|
6180
|
+
: `${resultSummary?.batch?.requestedPluginIds.length ?? 0} 个根插件已完成。`
|
|
6181
|
+
: portableOperation
|
|
4968
6182
|
? blocked
|
|
4969
|
-
? result.code === '
|
|
4970
|
-
? '
|
|
4971
|
-
: result.code === '
|
|
4972
|
-
? '
|
|
4973
|
-
:
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
?
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
6183
|
+
? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
|
|
6184
|
+
? 'Review 已过期,写入未开始。请返回修改并重新确认。'
|
|
6185
|
+
: result.code === 'operationReviewStale'
|
|
6186
|
+
? '安装证据已变化,写入被阻止。请返回修改并重新确认。'
|
|
6187
|
+
: result.writeMayHaveCompleted
|
|
6188
|
+
? '写入结果无法确认。请检查外部 Skills 状态后重试。'
|
|
6189
|
+
: '安装在写入前被阻止。请返回修改后重试。'
|
|
6190
|
+
: portableInstallFailed ? 'Skills CLI 安装失败,可能已有部分目标写入。可以修改 Agent 或使用原选择重试。' : partial ? '无法确认全部已选择 Skills。' : '所选 Skills 已通过安装验证。'
|
|
6191
|
+
: blocked && result.writeMayHaveCompleted
|
|
6192
|
+
? result.message
|
|
6193
|
+
: issueOperation
|
|
6194
|
+
? blocked
|
|
6195
|
+
? result.code === 'issueVerificationFailed'
|
|
6196
|
+
? '写入可能已完成,但当前无法确认。请重新检查状态。'
|
|
6197
|
+
: result.code === 'issueStillPresent'
|
|
6198
|
+
? '写入已完成,但重新检查后原问题仍然存在。'
|
|
6199
|
+
: '操作被安全阻止。可返回问题详情后重试。'
|
|
6200
|
+
: '状态已重新加载;原问题已确认消失。'
|
|
6201
|
+
: sourceOperation
|
|
6202
|
+
? blocked
|
|
6203
|
+
? result.writeMayHaveCompleted
|
|
6204
|
+
? '来源配置可能已保存。请重新检查来源列表。'
|
|
6205
|
+
: '来源操作未执行。草稿已保留,可返回修改。'
|
|
6206
|
+
: partial ? resultSummary?.message ?? '来源配置已保存,但 Catalog 尚未启用。可立即重试。'
|
|
6207
|
+
: resultSummary?.message ?? '来源配置已保存。'
|
|
6208
|
+
: blocked ? result.message
|
|
6209
|
+
: configOperation
|
|
6210
|
+
? partial
|
|
6211
|
+
? resultSummary?.message ?? '设置层已经保存;平台应用失败不会回滚设置。'
|
|
6212
|
+
: '设置层已经更新。'
|
|
6213
|
+
: '状态已刷新。';
|
|
4988
6214
|
const pluginId = resultSummary?.pluginId
|
|
4989
6215
|
?? ('pluginId' in result ? result.pluginId : undefined);
|
|
4990
6216
|
const configResultPluginId = pluginId ?? ('returnRoute' in result
|
|
@@ -5034,6 +6260,7 @@ export function ConsoleAppV4(props) {
|
|
|
5034
6260
|
const retryAction = failedPluginId && failedTarget && failedTargetState
|
|
5035
6261
|
? selectLifecycleRetryAction({
|
|
5036
6262
|
operationKind: operationKind === 'catalogSourcesRecovery' || operationKind === 'pluginWriteBatch'
|
|
6263
|
+
|| operationKind === 'pluginWriteTask' || operationKind === 'catalogManagement'
|
|
5037
6264
|
? undefined : operationKind,
|
|
5038
6265
|
originalAction: action,
|
|
5039
6266
|
target: {
|
|
@@ -5054,146 +6281,214 @@ export function ConsoleAppV4(props) {
|
|
|
5054
6281
|
&& (candidate.platform ?? failedIssue.platform) === failedTarget?.platform
|
|
5055
6282
|
&& (candidate.scope ?? failedIssue.scope) === failedTarget?.scope)
|
|
5056
6283
|
: undefined;
|
|
5057
|
-
|
|
6284
|
+
const preparedPublication = catalogManagementResult?.action === 'publishPlan'
|
|
6285
|
+
&& catalogManagementResult.planDigest && catalogManagementResult.publication
|
|
6286
|
+
? catalogManagementResult : undefined;
|
|
6287
|
+
actions = catalogManagementOperation
|
|
5058
6288
|
? [
|
|
5059
|
-
...(
|
|
5060
|
-
label: '
|
|
5061
|
-
description: '
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
6289
|
+
...(preparedPublication ? [{
|
|
6290
|
+
label: '应用此发布计划…',
|
|
6291
|
+
description: '进入独立确认;取消确认不会写入远端',
|
|
6292
|
+
status: 'success',
|
|
6293
|
+
run: () => beginReview({
|
|
6294
|
+
kind: 'catalogManagement',
|
|
6295
|
+
action: 'publishApply',
|
|
6296
|
+
expectedPlanDigest: preparedPublication.planDigest,
|
|
6297
|
+
...(catalogPublishDraft.target
|
|
6298
|
+
? catalogPublishDraft.targetKind === 'remote'
|
|
6299
|
+
? { remote: catalogPublishDraft.target }
|
|
6300
|
+
: { remoteUrl: catalogPublishDraft.target }
|
|
6301
|
+
: {}),
|
|
6302
|
+
branch: catalogPublishDraft.branch,
|
|
6303
|
+
}, { name: 'catalogPublish', step: 'overview' }),
|
|
5067
6304
|
}] : []),
|
|
5068
|
-
{ label: '
|
|
6305
|
+
{ label: catalogManagementResult?.action === 'storePrune' ? '返回本地存储'
|
|
6306
|
+
: catalogManagementResult?.action === 'devActivate' || catalogManagementResult?.action === 'devDeactivate' ? '返回开发来源'
|
|
6307
|
+
: catalogManagementResult?.action === 'publishPlan' || catalogManagementResult?.action === 'publishApply' ? '返回发布管理'
|
|
6308
|
+
: '返回 Catalog 工作区',
|
|
6309
|
+
run: () => leaveResult(catalogManagementResult?.action === 'storePrune'
|
|
6310
|
+
? { name: 'catalogStore' }
|
|
6311
|
+
: catalogManagementResult?.action === 'devActivate' || catalogManagementResult?.action === 'devDeactivate'
|
|
6312
|
+
? { name: 'catalogDev' }
|
|
6313
|
+
: catalogManagementResult?.action === 'publishPlan' || catalogManagementResult?.action === 'publishApply'
|
|
6314
|
+
? { name: 'catalogPublish', step: 'overview' }
|
|
6315
|
+
: 'returnRoute' in result && result.returnRoute ? result.returnRoute
|
|
6316
|
+
: { name: 'catalogManagement' }) },
|
|
5069
6317
|
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
5070
6318
|
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
5071
6319
|
]
|
|
5072
|
-
:
|
|
5073
|
-
?
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
return;
|
|
5095
|
-
if (blocked || partial) {
|
|
5096
|
-
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
|
|
5097
|
-
await beginPortableReview(pluginId, snapshot.portableDraft?.scope ?? 'project');
|
|
5098
|
-
return;
|
|
6320
|
+
: taskOperation
|
|
6321
|
+
? [
|
|
6322
|
+
...((partial || blocked) && (taskSummary?.retryTargets?.length ?? 0) > 0 ? [{
|
|
6323
|
+
label: `继续处理剩余 ${taskSummary.retryTargets.length} 项…`,
|
|
6324
|
+
description: '重新检查失败的精确目标和必要依赖,并进入新的确认页',
|
|
6325
|
+
run: () => {
|
|
6326
|
+
props.controller.retryFailedPluginWriteTask();
|
|
6327
|
+
setFocus(0);
|
|
6328
|
+
setScrollOffset(0);
|
|
6329
|
+
},
|
|
6330
|
+
}] : []),
|
|
6331
|
+
...(taskBlockedCount > 0 && taskReturnRoute ? [{
|
|
6332
|
+
label: `返回任务处理 ${taskBlockedCount} 个需决定项`,
|
|
6333
|
+
description: '受阻目标没有加入失败重试;返回后可查看原因和可用的下一步',
|
|
6334
|
+
status: 'warning',
|
|
6335
|
+
run: () => leaveResult(taskReturnRoute),
|
|
6336
|
+
}] : []),
|
|
6337
|
+
{ label: '完成,返回我的插件', run: () => {
|
|
6338
|
+
if (taskBatchOrigin?.owner === 'batch-write'
|
|
6339
|
+
&& taskBatchOrigin.flowId === batchDraft?.flowId) {
|
|
6340
|
+
setBatchDraft(undefined);
|
|
6341
|
+
setDirty(false);
|
|
5099
6342
|
}
|
|
5100
|
-
|
|
5101
|
-
await beginPortable(pluginId);
|
|
5102
|
-
} },
|
|
5103
|
-
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => { if (pluginId)
|
|
5104
|
-
props.controller.resetPortableFlowToPreview(pluginId); } },
|
|
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);
|
|
6343
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
5115
6344
|
} },
|
|
6345
|
+
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
6346
|
+
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
5116
6347
|
]
|
|
5117
|
-
:
|
|
5118
|
-
?
|
|
5119
|
-
? [
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
:
|
|
5130
|
-
|
|
5131
|
-
|
|
6348
|
+
: batchOperation
|
|
6349
|
+
? [
|
|
6350
|
+
...((partial || blocked) && (resultSummary?.batch?.retryTargets?.length ?? 0) > 0 ? [{
|
|
6351
|
+
label: '仅重试失败目标…',
|
|
6352
|
+
description: '重新读取失败根目标及其依赖,并进入新的确认页',
|
|
6353
|
+
run: () => {
|
|
6354
|
+
props.controller.retryFailedPluginWriteBatch();
|
|
6355
|
+
setFocus(0);
|
|
6356
|
+
setScrollOffset(0);
|
|
6357
|
+
},
|
|
6358
|
+
}] : []),
|
|
6359
|
+
{ label: '返回我的插件', run: () => leaveResult() },
|
|
6360
|
+
{ label: '查看本次记录', run: () => result.operationRecordId
|
|
6361
|
+
&& go({ name: 'operationDiagnostic', recordId: result.operationRecordId }) },
|
|
6362
|
+
]
|
|
6363
|
+
: portableOperation
|
|
6364
|
+
? blocked || partial ? [
|
|
6365
|
+
{ label: blocked || partial ? '返回修改' : portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: async () => {
|
|
6366
|
+
if (!pluginId) {
|
|
6367
|
+
leaveResult();
|
|
6368
|
+
return;
|
|
6369
|
+
}
|
|
6370
|
+
if (!blocked && !partial) {
|
|
6371
|
+
props.controller.discardPortableDraft();
|
|
6372
|
+
leaveResult();
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6375
|
+
if (!await refreshPortableDraft(pluginId))
|
|
6376
|
+
return;
|
|
6377
|
+
if (portableInstallFailed && portableReceipt?.agentMode === 'explicit') {
|
|
6378
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableAgentInput', pluginId }, true);
|
|
6379
|
+
return;
|
|
6380
|
+
}
|
|
6381
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
|
|
6382
|
+
} },
|
|
6383
|
+
{ label: blocked || partial ? '重试' : '再安装其他 Skills…', run: async () => {
|
|
6384
|
+
if (!pluginId)
|
|
6385
|
+
return;
|
|
6386
|
+
if (blocked || partial) {
|
|
6387
|
+
props.controller.resetPortableFlowToPreview(pluginId, { name: 'portableSkillSelect', pluginId }, true);
|
|
6388
|
+
await beginPortableReview(pluginId, snapshot.portableDraft?.scope ?? 'project');
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
props.controller.resetPortableFlowToPreview(pluginId);
|
|
6392
|
+
await beginPortable(pluginId);
|
|
6393
|
+
} },
|
|
6394
|
+
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => { if (pluginId)
|
|
6395
|
+
props.controller.resetPortableFlowToPreview(pluginId); } },
|
|
6396
|
+
] : [
|
|
6397
|
+
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => {
|
|
6398
|
+
props.controller.discardPortableDraft();
|
|
6399
|
+
leaveResult();
|
|
6400
|
+
} },
|
|
6401
|
+
{ label: '再安装其他 Skills…', run: async () => {
|
|
6402
|
+
if (!pluginId)
|
|
6403
|
+
return;
|
|
6404
|
+
props.controller.resetPortableFlowToPreview(pluginId);
|
|
6405
|
+
await beginPortable(pluginId);
|
|
6406
|
+
} },
|
|
5132
6407
|
]
|
|
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
|
|
6408
|
+
: issueOperation && !dependencyRecovery
|
|
6409
|
+
? blocked || partial
|
|
5153
6410
|
? [
|
|
5154
|
-
{ label: '
|
|
6411
|
+
{ label: '返回问题详情', run: () => blocked
|
|
6412
|
+
? leaveResult()
|
|
6413
|
+
: failedIssue
|
|
6414
|
+
? leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId })
|
|
6415
|
+
: pluginId && failedTarget
|
|
6416
|
+
? leaveResult({ name: 'platform', pluginId, platform: failedTarget.platform })
|
|
6417
|
+
: leaveResult() },
|
|
6418
|
+
{ label: '重新检查问题', run: () => leaveResult({ name: 'issueList' }) },
|
|
6419
|
+
]
|
|
6420
|
+
: [
|
|
6421
|
+
{ label: '查看问题列表', run: () => leaveResult({ name: 'issueList' }) },
|
|
5155
6422
|
{ label: '返回首页', run: () => requestSessionNavigation('home') },
|
|
5156
6423
|
]
|
|
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
|
-
|
|
6424
|
+
: sourceOperation
|
|
6425
|
+
? [{
|
|
6426
|
+
label: !blocked && 'returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask'
|
|
6427
|
+
? '重新准备插件更新' : blocked ? '返回修改' : partial ? '现在启用 / 重试' : '查看来源',
|
|
6428
|
+
run: () => blocked ? props.controller.resumeResultDraft() : leaveResult(),
|
|
6429
|
+
}, { label: '返回首页', run: () => requestSessionNavigation('home') }]
|
|
6430
|
+
: configOperation && partial
|
|
6431
|
+
? [
|
|
6432
|
+
...(configRetryAvailable ? [{
|
|
6433
|
+
label: '重试应用',
|
|
6434
|
+
description: '先重新进入 Review,并重新采集当前证据',
|
|
6435
|
+
run: () => beginReview({
|
|
6436
|
+
kind: 'configSave',
|
|
6437
|
+
pluginId: configResultPluginId,
|
|
6438
|
+
scope: resultSummary.configScope,
|
|
6439
|
+
apply: true,
|
|
6440
|
+
retryApply: true,
|
|
6441
|
+
}),
|
|
6442
|
+
}] : []),
|
|
6443
|
+
{ label: invalidConfigResult ? '返回设置' : '返回修改', description: '重新打开已保存的设置层', run: () => leaveResult() },
|
|
6444
|
+
{ label: '返回设置删除字段或重置设置层', description: '显式删除顶层字段或整个设置层', run: () => leaveResult() },
|
|
6445
|
+
{ label: '保留设置并退出', run: () => requestExit({ code: 1, reason: 'partial' }) },
|
|
6446
|
+
]
|
|
6447
|
+
: configOperation && blocked
|
|
6448
|
+
? [
|
|
6449
|
+
{ label: '返回设置', run: () => props.controller.resumeResultDraft() },
|
|
6450
|
+
{ label: '返回首页', run: () => requestSessionNavigation('home') },
|
|
6451
|
+
]
|
|
6452
|
+
: [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
|
|
6453
|
+
? configOrigin?.name === 'pluginPreview' ? '返回插件预览' : '返回插件详情'
|
|
6454
|
+
: configOperation ? '查看插件'
|
|
6455
|
+
: action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
|
|
6456
|
+
if (successfulConfigOperation && configOrigin) {
|
|
6457
|
+
leaveResult();
|
|
6458
|
+
return;
|
|
6459
|
+
}
|
|
6460
|
+
if ((partial || blockedDependencyFailure) && failedPluginId && failedTarget) {
|
|
6461
|
+
if (failedIssue)
|
|
6462
|
+
leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId });
|
|
6463
|
+
else if (failedPluginPlatforms?.includes(failedTarget.platform)) {
|
|
6464
|
+
leaveResult({ name: 'platform', pluginId: failedPluginId, platform: failedTarget.platform });
|
|
6465
|
+
}
|
|
6466
|
+
else if (snapshot.viewModel?.plugins.some((entry) => entry.pluginId === failedPluginId)) {
|
|
6467
|
+
leaveResult({ name: 'pluginDetail', pluginId: failedPluginId });
|
|
6468
|
+
}
|
|
6469
|
+
else
|
|
6470
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6471
|
+
}
|
|
6472
|
+
else if (pluginId && snapshot.viewModel?.plugins.some((entry) => entry.pluginId === pluginId)) {
|
|
6473
|
+
leaveResult({ name: 'pluginDetail', pluginId });
|
|
6474
|
+
}
|
|
6475
|
+
else
|
|
6476
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
6477
|
+
} }, ...((partial || blockedDependencyFailure) && failedPluginId && failedTarget && retryAction ? [{
|
|
6478
|
+
label: '重试失败部分…',
|
|
6479
|
+
run: () => {
|
|
6480
|
+
const issueIntent = failedIssue && retryIssueAction
|
|
6481
|
+
? issueWriteIntent(failedIssue, retryIssueAction)
|
|
6482
|
+
: undefined;
|
|
6483
|
+
return beginReview(issueIntent ?? {
|
|
6484
|
+
kind: 'pluginWrite',
|
|
6485
|
+
action: retryAction,
|
|
6486
|
+
pluginId: failedPluginId,
|
|
6487
|
+
platform: failedTarget.platform,
|
|
6488
|
+
scope: failedTarget.scope,
|
|
6489
|
+
});
|
|
6490
|
+
},
|
|
6491
|
+
}] : []), { label: '返回首页', run: () => requestSessionNavigation('home') }];
|
|
5197
6492
|
const visibleMessages = resultSummary?.messages?.slice(policy.mode === 'compact' ? -2 : -4) ?? [];
|
|
5198
6493
|
const visibleChanges = resultSummary?.changes?.slice(0, policy.mode === 'compact' ? 2 : 4) ?? [];
|
|
5199
6494
|
const resultTargetName = (target) => target.pluginId
|
|
@@ -5254,11 +6549,68 @@ export function ConsoleAppV4(props) {
|
|
|
5254
6549
|
: resultSummary?.resetDisposition === 'quarantinedDirectory'
|
|
5255
6550
|
? `设置路径是目录;已隔离为 ${resultSummary.quarantineBasename ?? '安全隔离目录'}`
|
|
5256
6551
|
: undefined;
|
|
5257
|
-
|
|
6552
|
+
const taskRootResults = taskSummary?.rootResults ?? [];
|
|
6553
|
+
const blockedTaskTargets = new Map((taskSummary?.blockedTargets ?? []).map((entry) => [
|
|
6554
|
+
[entry.target.pluginId, entry.target.platform, entry.target.scope].join('\0'), entry,
|
|
6555
|
+
]));
|
|
6556
|
+
const taskRootState = (state) => {
|
|
6557
|
+
if (state === 'completed')
|
|
6558
|
+
return { label: '完成', status: 'success' };
|
|
6559
|
+
if (state === 'kept')
|
|
6560
|
+
return { label: '无需更改', status: 'success' };
|
|
6561
|
+
if (state === 'partial')
|
|
6562
|
+
return { label: '部分完成', status: 'warning' };
|
|
6563
|
+
if (state === 'failed')
|
|
6564
|
+
return { label: '失败', status: 'error' };
|
|
6565
|
+
if (state === 'notStarted')
|
|
6566
|
+
return { label: '未开始', status: 'muted' };
|
|
6567
|
+
return { label: '待确认', status: 'warning' };
|
|
6568
|
+
};
|
|
6569
|
+
const taskResultRows = taskRootResults.flatMap((root) => {
|
|
6570
|
+
const rootState = taskRootState(root.state);
|
|
6571
|
+
const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
|
|
6572
|
+
?? root.pluginId.split('/').at(-1);
|
|
6573
|
+
const requestedTargets = taskSummary?.requestedTargets.filter((target) => target.pluginId === root.pluginId) ?? [];
|
|
6574
|
+
return [
|
|
6575
|
+
_jsx(ResultRow, { title: rootName, description: `${requestedTargets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `root:${root.pluginId}`),
|
|
6576
|
+
...requestedTargets.map((target) => {
|
|
6577
|
+
const key = [target.pluginId, target.platform, target.scope].join('\0');
|
|
6578
|
+
const blockedTarget = blockedTaskTargets.get(key);
|
|
6579
|
+
const targetResult = root.targets.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
|
|
6580
|
+
&& entry.scope === target.scope);
|
|
6581
|
+
const targetState = blockedTarget ? { label: '待确认', status: 'warning' }
|
|
6582
|
+
: targetResult?.state === 'completed' ? { label: '完成', status: 'success' }
|
|
6583
|
+
: targetResult?.state === 'kept' ? { label: '无需更改', status: 'success' }
|
|
6584
|
+
: targetResult?.state === 'failed' ? { label: '失败', status: 'error' }
|
|
6585
|
+
: { label: '未开始', status: 'muted' };
|
|
6586
|
+
return _jsx(ResultRow, { title: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: blockedTarget
|
|
6587
|
+
? pluginWriteTaskBlockLabel(blockedTarget.reason)
|
|
6588
|
+
: targetResult?.errorCode, meta: targetState.label, status: targetState.status, compact: !blockedTarget }, `target:${key}`);
|
|
6589
|
+
}),
|
|
6590
|
+
];
|
|
6591
|
+
});
|
|
6592
|
+
const batchResultRows = (resultSummary?.batch?.rootResults ?? []).map((root) => {
|
|
6593
|
+
const rootState = taskRootState(root.state);
|
|
6594
|
+
const rootName = props.controller.getPluginOverview(root.pluginId)?.displayName
|
|
6595
|
+
?? root.pluginId.split('/').at(-1);
|
|
6596
|
+
return _jsx(ResultRow, { title: rootName, description: `${root.targets.length} 个精确安装位置`, meta: rootState.label, status: rootState.status, compact: true }, `batch-root:${root.pluginId}`);
|
|
6597
|
+
}).concat(prioritizedTargets.map((target) => {
|
|
6598
|
+
const state = operationTargetStateLabel(target.state);
|
|
6599
|
+
const relation = target.relation === 'requested' ? '你选择的插件'
|
|
6600
|
+
: target.relation === 'direct' ? '直接依赖'
|
|
6601
|
+
: target.relation === 'transitive' ? '间接依赖' : undefined;
|
|
6602
|
+
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}`);
|
|
6603
|
+
}));
|
|
6604
|
+
const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
|
|
6605
|
+
const rootResultRows = taskOperation ? taskResultRows : batchResultRows;
|
|
6606
|
+
const taskResultOffset = Math.min(batchTargetOffset, Math.max(0, rootResultRows.length - 1));
|
|
6607
|
+
const visibleTaskResultRows = rootResultRows.slice(taskResultOffset, taskResultOffset + taskResultLimit);
|
|
6608
|
+
if (taskOperation || batchOperation || resultTargets.length > resultTargetLimit
|
|
5258
6609
|
|| prioritizedPortableSkills.length > portableSkillLimit) {
|
|
5259
6610
|
help = 'PgUp/PgDn 查看全部项目 · ↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
5260
6611
|
}
|
|
5261
|
-
content =
|
|
6612
|
+
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
|
|
6613
|
+
? _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
6614
|
? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
|
|
5263
6615
|
: _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
6616
|
? _jsx(StaticRow, { title: "\u53E6\u6709\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "materialization digest \u00B7 elapsed", status: "muted", compact: true })
|
|
@@ -5311,11 +6663,14 @@ export function ConsoleAppV4(props) {
|
|
|
5311
6663
|
switch (route.name) {
|
|
5312
6664
|
case 'pluginPreview':
|
|
5313
6665
|
case 'pluginDependencies':
|
|
6666
|
+
case 'pluginDependencyNode':
|
|
5314
6667
|
case 'installPlacement':
|
|
5315
6668
|
case 'installTargets':
|
|
5316
6669
|
case 'dependencyPlacement':
|
|
5317
6670
|
case 'dependencyPlacementEditor':
|
|
6671
|
+
case 'pluginWriteTask':
|
|
5318
6672
|
case 'pluginDetail':
|
|
6673
|
+
case 'pluginMore':
|
|
5319
6674
|
case 'platform':
|
|
5320
6675
|
case 'pluginAbout':
|
|
5321
6676
|
case 'pluginTechnical':
|
|
@@ -5343,6 +6698,7 @@ export function ConsoleAppV4(props) {
|
|
|
5343
6698
|
case 'sourceAdd':
|
|
5344
6699
|
case 'sourceSave':
|
|
5345
6700
|
case 'scriptTimeout':
|
|
6701
|
+
case 'catalogWorkspace':
|
|
5346
6702
|
case 'reviewPreparing':
|
|
5347
6703
|
case 'review':
|
|
5348
6704
|
case 'result':
|
|
@@ -5475,6 +6831,10 @@ export function ConsoleAppV4(props) {
|
|
|
5475
6831
|
setObjectError(undefined);
|
|
5476
6832
|
setSourceDraft(undefined);
|
|
5477
6833
|
setBatchDraft(undefined);
|
|
6834
|
+
setCatalogInput('');
|
|
6835
|
+
setCatalogWorkspaceDraft({ ownerName: '', ownerUrl: '', displayName: '', catalogVersion: '0.1.0' });
|
|
6836
|
+
setCatalogPublishDraft({ targetKind: 'remote', target: '', branch: 'catalog' });
|
|
6837
|
+
setCatalogDevSourceId('');
|
|
5478
6838
|
}
|
|
5479
6839
|
function continueEditingFromSessionNavigation() {
|
|
5480
6840
|
if (props.pendingSessionNavigation)
|
|
@@ -5658,6 +7018,14 @@ export function ConsoleAppV4(props) {
|
|
|
5658
7018
|
setReportSaveError(undefined);
|
|
5659
7019
|
return;
|
|
5660
7020
|
}
|
|
7021
|
+
if ((key.pageUp || key.pageDown)
|
|
7022
|
+
&& route.name === 'pluginWriteTask') {
|
|
7023
|
+
const total = props.controller.listPluginWriteTaskCandidates(route.goal)
|
|
7024
|
+
.filter((candidate) => candidate.target.pluginId === route.pluginId
|
|
7025
|
+
&& (!route.targetKeys || route.targetKeys.includes(pluginWriteTaskTargetKey(candidate)))).length;
|
|
7026
|
+
setBatchTargetOffset((value) => Math.min(Math.max(0, total - 1), Math.max(0, value + (key.pageUp ? -1 : 1))));
|
|
7027
|
+
return;
|
|
7028
|
+
}
|
|
5661
7029
|
if ((key.pageUp || key.pageDown)
|
|
5662
7030
|
&& route.name === 'review'
|
|
5663
7031
|
&& snapshot.activeReview) {
|
|
@@ -5669,8 +7037,10 @@ export function ConsoleAppV4(props) {
|
|
|
5669
7037
|
if ((key.pageUp || key.pageDown)
|
|
5670
7038
|
&& route.name === 'result'
|
|
5671
7039
|
&& snapshot.result) {
|
|
5672
|
-
const
|
|
5673
|
-
|
|
7040
|
+
const pageSummary = 'summary' in snapshot.result ? snapshot.result.summary : undefined;
|
|
7041
|
+
const total = pageSummary
|
|
7042
|
+
? 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)
|
|
7043
|
+
+ (pageSummary.targets?.length ?? 0), snapshot.result.operationKind === 'portableInstall'
|
|
5674
7044
|
? snapshot.portableResultSkills?.length ?? 0
|
|
5675
7045
|
: 0)
|
|
5676
7046
|
: 0;
|
|
@@ -5705,6 +7075,15 @@ export function ConsoleAppV4(props) {
|
|
|
5705
7075
|
setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
|
|
5706
7076
|
return;
|
|
5707
7077
|
}
|
|
7078
|
+
if ((key.pageUp || key.pageDown) && (route.name === 'catalogStore'
|
|
7079
|
+
|| route.name === 'catalogPublish' && route.step === 'overview')) {
|
|
7080
|
+
const total = route.name === 'catalogStore'
|
|
7081
|
+
? catalogStore?.entries.length ?? 0
|
|
7082
|
+
: catalogPublication?.records.length ?? 0;
|
|
7083
|
+
const pageRows = responsiveBudget(3, 7);
|
|
7084
|
+
setBatchTargetOffset((value) => Math.min(Math.max(0, total - pageRows), Math.max(0, value + (key.pageUp ? -pageRows : pageRows))));
|
|
7085
|
+
return;
|
|
7086
|
+
}
|
|
5708
7087
|
if ((key.pageUp || key.pageDown) && route.name === 'pluginDependencies' && plugin) {
|
|
5709
7088
|
const overview = dependencyOverview(plugin);
|
|
5710
7089
|
const dependents = route.view === 'removeBlocked' && route.platform && route.scope
|
|
@@ -5875,6 +7254,180 @@ export function ConsoleAppV4(props) {
|
|
|
5875
7254
|
setSourceInputError(undefined);
|
|
5876
7255
|
return;
|
|
5877
7256
|
}
|
|
7257
|
+
if (route.name === 'catalogWorkspace' && (route.view === 'select'
|
|
7258
|
+
|| route.view === 'create' || route.view === 'version' && route.step === 'catalogVersion')) {
|
|
7259
|
+
if (key.escape) {
|
|
7260
|
+
if (route.view === 'create') {
|
|
7261
|
+
const step = route.step ?? 'ownerName';
|
|
7262
|
+
const next = { ...catalogWorkspaceDraft, [step]: catalogInput };
|
|
7263
|
+
setCatalogWorkspaceDraft(next);
|
|
7264
|
+
const previous = { ownerUrl: 'ownerName', displayName: 'ownerUrl', catalogVersion: 'displayName' }[step];
|
|
7265
|
+
if (previous) {
|
|
7266
|
+
setCatalogInput(next[previous]);
|
|
7267
|
+
props.controller.replace({ ...route, step: previous });
|
|
7268
|
+
}
|
|
7269
|
+
else
|
|
7270
|
+
back();
|
|
7271
|
+
}
|
|
7272
|
+
else
|
|
7273
|
+
back();
|
|
7274
|
+
return;
|
|
7275
|
+
}
|
|
7276
|
+
if (key.return) {
|
|
7277
|
+
const value = catalogInput.trim();
|
|
7278
|
+
setCatalogInputError(undefined);
|
|
7279
|
+
if (route.view === 'select') {
|
|
7280
|
+
setCatalogLoading(true);
|
|
7281
|
+
void props.controller.selectCatalogWorkspace(value || undefined).then((workspace) => {
|
|
7282
|
+
setCatalogWorkspace(workspace);
|
|
7283
|
+
props.controller.replace({ name: 'catalogWorkspace', view: 'overview' });
|
|
7284
|
+
setFocus(0);
|
|
7285
|
+
setScrollOffset(0);
|
|
7286
|
+
setCatalogInput('');
|
|
7287
|
+
}).catch((error) => setCatalogInputError(sanitizeDiagnosticText(error instanceof Error ? error.message : String(error), [], [catalogInput]))).finally(() => setCatalogLoading(false));
|
|
7288
|
+
return;
|
|
7289
|
+
}
|
|
7290
|
+
if (route.view === 'version') {
|
|
7291
|
+
if (!/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/u.test(value)) {
|
|
7292
|
+
setCatalogInputError('版本必须是 x.y.z 或精确 x.y.z-beta.N。');
|
|
7293
|
+
return;
|
|
7294
|
+
}
|
|
7295
|
+
beginReview({ kind: 'catalogManagement', action: 'workspaceVersion', version: value }, { name: 'catalogWorkspace', view: 'overview' });
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
const step = route.step ?? 'ownerName';
|
|
7299
|
+
if (!value) {
|
|
7300
|
+
setCatalogInputError('此字段不能为空。');
|
|
7301
|
+
return;
|
|
7302
|
+
}
|
|
7303
|
+
if (step === 'ownerUrl') {
|
|
7304
|
+
try {
|
|
7305
|
+
const url = new URL(value);
|
|
7306
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
7307
|
+
throw new Error();
|
|
7308
|
+
}
|
|
7309
|
+
catch {
|
|
7310
|
+
setCatalogInputError('请输入有效的 HTTP 或 HTTPS owner URL。');
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
if (step === 'catalogVersion' && !/^\d+\.\d+\.\d+(?:-beta\.\d+)?$/u.test(value)) {
|
|
7315
|
+
setCatalogInputError('版本必须是 x.y.z 或精确 x.y.z-beta.N。');
|
|
7316
|
+
return;
|
|
7317
|
+
}
|
|
7318
|
+
const next = { ...catalogWorkspaceDraft, [step]: value };
|
|
7319
|
+
setCatalogWorkspaceDraft(next);
|
|
7320
|
+
setCatalogInput('');
|
|
7321
|
+
if (step === 'ownerName')
|
|
7322
|
+
props.controller.replace({ ...route, step: 'ownerUrl' });
|
|
7323
|
+
else if (step === 'ownerUrl')
|
|
7324
|
+
props.controller.replace({ ...route, step: 'displayName' });
|
|
7325
|
+
else if (step === 'displayName') {
|
|
7326
|
+
setCatalogInput('0.1.0');
|
|
7327
|
+
props.controller.replace({ ...route, step: 'catalogVersion' });
|
|
7328
|
+
}
|
|
7329
|
+
else
|
|
7330
|
+
beginReview({ kind: 'catalogManagement', action: 'workspaceInit', ...next }, { name: 'catalogWorkspace', view: 'overview' });
|
|
7331
|
+
return;
|
|
7332
|
+
}
|
|
7333
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7334
|
+
setCatalogInput('');
|
|
7335
|
+
else if (key.backspace || key.delete)
|
|
7336
|
+
setCatalogInput(removeLastGrapheme);
|
|
7337
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7338
|
+
setCatalogInput((value) => value + input);
|
|
7339
|
+
setCatalogInputError(undefined);
|
|
7340
|
+
return;
|
|
7341
|
+
}
|
|
7342
|
+
if (route.name === 'catalogDev' && route.step && route.step !== 'overview') {
|
|
7343
|
+
if (key.escape) {
|
|
7344
|
+
back();
|
|
7345
|
+
return;
|
|
7346
|
+
}
|
|
7347
|
+
if (key.return) {
|
|
7348
|
+
const value = catalogInput.trim();
|
|
7349
|
+
if (value && !CATALOG_SOURCE_ID_PATTERN.test(value)) {
|
|
7350
|
+
setCatalogInputError('格式必须是小写字母、数字或连字符,最多 32 字符。');
|
|
7351
|
+
return;
|
|
7352
|
+
}
|
|
7353
|
+
if (route.step === 'sourceId') {
|
|
7354
|
+
setCatalogDevSourceId(value);
|
|
7355
|
+
back();
|
|
7356
|
+
}
|
|
7357
|
+
else {
|
|
7358
|
+
if (!value) {
|
|
7359
|
+
setCatalogInputError('请输入替代主来源 ID。');
|
|
7360
|
+
return;
|
|
7361
|
+
}
|
|
7362
|
+
beginReview({ kind: 'catalogManagement', action: 'devDeactivate', replacementPrimary: value }, { name: 'catalogDev' });
|
|
7363
|
+
}
|
|
7364
|
+
return;
|
|
7365
|
+
}
|
|
7366
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7367
|
+
setCatalogInput('');
|
|
7368
|
+
else if (key.backspace || key.delete)
|
|
7369
|
+
setCatalogInput(removeLastGrapheme);
|
|
7370
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7371
|
+
setCatalogInput((value) => value + input);
|
|
7372
|
+
setCatalogInputError(undefined);
|
|
7373
|
+
return;
|
|
7374
|
+
}
|
|
7375
|
+
if (route.name === 'catalogPublish' && route.step !== 'overview') {
|
|
7376
|
+
if (key.escape) {
|
|
7377
|
+
if (route.step === 'branch') {
|
|
7378
|
+
setCatalogPublishDraft((draft) => ({ ...draft, branch: catalogInput.trim() || 'catalog' }));
|
|
7379
|
+
setCatalogInput(catalogPublishDraft.target);
|
|
7380
|
+
props.controller.replace({ name: 'catalogPublish', step: 'target' });
|
|
7381
|
+
}
|
|
7382
|
+
else {
|
|
7383
|
+
const value = catalogInput.trim();
|
|
7384
|
+
const targetKind = value.includes('://') || /^[^@\s]+@[^:\s]+:/u.test(value) || value.startsWith('/')
|
|
7385
|
+
? 'remoteUrl' : 'remote';
|
|
7386
|
+
setCatalogPublishDraft((draft) => ({ ...draft, targetKind, target: value }));
|
|
7387
|
+
back();
|
|
7388
|
+
}
|
|
7389
|
+
return;
|
|
7390
|
+
}
|
|
7391
|
+
if (key.return) {
|
|
7392
|
+
const value = catalogInput.trim();
|
|
7393
|
+
if (route.step === 'target') {
|
|
7394
|
+
if (/\s/u.test(value)) {
|
|
7395
|
+
setCatalogInputError('remote 名称或 URL 不能包含空白。');
|
|
7396
|
+
return;
|
|
7397
|
+
}
|
|
7398
|
+
const targetKind = value.includes('://') || /^[^@\s]+@[^:\s]+:/u.test(value) || value.startsWith('/')
|
|
7399
|
+
? 'remoteUrl' : 'remote';
|
|
7400
|
+
setCatalogPublishDraft((draft) => ({ ...draft, targetKind, target: value }));
|
|
7401
|
+
setCatalogInput(catalogPublishDraft.expectedPlanDigest ? catalogPublishDraft.branch : 'catalog');
|
|
7402
|
+
props.controller.replace({ name: 'catalogPublish', step: 'branch' });
|
|
7403
|
+
}
|
|
7404
|
+
else {
|
|
7405
|
+
const branch = value || 'catalog';
|
|
7406
|
+
if (/\s|\.\.|[~^:?*\[\\]/u.test(branch) || branch.startsWith('-') || branch.endsWith('/')) {
|
|
7407
|
+
setCatalogInputError('请输入安全的 Git branch 名称。');
|
|
7408
|
+
return;
|
|
7409
|
+
}
|
|
7410
|
+
const draft = { ...catalogPublishDraft, branch };
|
|
7411
|
+
setCatalogPublishDraft(draft);
|
|
7412
|
+
const target = draft.target
|
|
7413
|
+
? draft.targetKind === 'remote' ? { remote: draft.target } : { remoteUrl: draft.target }
|
|
7414
|
+
: {};
|
|
7415
|
+
beginReview(draft.expectedPlanDigest
|
|
7416
|
+
? { kind: 'catalogManagement', action: 'publishApply', expectedPlanDigest: draft.expectedPlanDigest,
|
|
7417
|
+
...target, branch }
|
|
7418
|
+
: { kind: 'catalogManagement', action: 'publishPlan', ...target, branch }, { name: 'catalogPublish', step: 'overview' });
|
|
7419
|
+
}
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7422
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7423
|
+
setCatalogInput('');
|
|
7424
|
+
else if (key.backspace || key.delete)
|
|
7425
|
+
setCatalogInput(removeLastGrapheme);
|
|
7426
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7427
|
+
setCatalogInput((value) => value + input);
|
|
7428
|
+
setCatalogInputError(undefined);
|
|
7429
|
+
return;
|
|
7430
|
+
}
|
|
5878
7431
|
if (route.name === 'configFieldEditor'
|
|
5879
7432
|
&& (route.editor === 'string' || route.editor === 'number')
|
|
5880
7433
|
&& ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')) {
|
|
@@ -5982,18 +7535,22 @@ export function ConsoleAppV4(props) {
|
|
|
5982
7535
|
setInputError(undefined);
|
|
5983
7536
|
return;
|
|
5984
7537
|
}
|
|
5985
|
-
if (searchFocus && route.name === 'pluginList') {
|
|
7538
|
+
if (searchFocus && (route.name === 'pluginList' || route.name === 'batchSelection')) {
|
|
5986
7539
|
if (key.escape) {
|
|
5987
7540
|
setSearchFocus(false);
|
|
5988
7541
|
return;
|
|
5989
7542
|
}
|
|
5990
7543
|
if (key.return) {
|
|
5991
7544
|
setSearchFocus(false);
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
7545
|
+
if (route.name === 'pluginList') {
|
|
7546
|
+
const row = pluginRows[focus];
|
|
7547
|
+
if (row?.menuAction)
|
|
7548
|
+
row.menuAction();
|
|
7549
|
+
else if (row)
|
|
7550
|
+
go(route.mode === 'browse'
|
|
7551
|
+
? { name: 'pluginPreview', pluginId: row.pluginId }
|
|
7552
|
+
: { name: 'pluginDetail', pluginId: row.pluginId });
|
|
7553
|
+
}
|
|
5997
7554
|
return;
|
|
5998
7555
|
}
|
|
5999
7556
|
if (key.upArrow || key.downArrow) {
|
|
@@ -6004,20 +7561,43 @@ export function ConsoleAppV4(props) {
|
|
|
6004
7561
|
if (key.leftArrow || key.rightArrow) {
|
|
6005
7562
|
setSearchFocus(false);
|
|
6006
7563
|
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
6007
|
-
const current = route.sourceId ?? 'all';
|
|
7564
|
+
const current = route.name === 'pluginList' ? route.sourceId ?? 'all' : batchDraft?.sourceId ?? 'all';
|
|
6008
7565
|
const direction = key.leftArrow ? -1 : 1;
|
|
6009
7566
|
const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
|
|
6010
|
-
|
|
7567
|
+
if (route.name === 'pluginList') {
|
|
7568
|
+
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
|
|
7569
|
+
}
|
|
7570
|
+
else {
|
|
7571
|
+
setBatchDraft((draft) => draft ? {
|
|
7572
|
+
...draft,
|
|
7573
|
+
...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
|
|
7574
|
+
showSelected: false,
|
|
7575
|
+
} : draft);
|
|
7576
|
+
}
|
|
6011
7577
|
setFocus(0);
|
|
6012
7578
|
setScrollOffset(0);
|
|
6013
7579
|
return;
|
|
6014
7580
|
}
|
|
6015
|
-
if (
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
7581
|
+
if (route.name === 'pluginList') {
|
|
7582
|
+
if (input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl))
|
|
7583
|
+
setQuery('');
|
|
7584
|
+
else if (key.backspace || key.delete)
|
|
7585
|
+
setQuery(removeLastGrapheme);
|
|
7586
|
+
else if (input && !key.ctrl && !key.meta && input >= ' ')
|
|
7587
|
+
setQuery((value) => value + input);
|
|
7588
|
+
}
|
|
7589
|
+
else {
|
|
7590
|
+
setBatchDraft((draft) => draft ? {
|
|
7591
|
+
...draft,
|
|
7592
|
+
query: input === '\u0015' || (input.toLowerCase() === 'u' && key.ctrl)
|
|
7593
|
+
? ''
|
|
7594
|
+
: key.backspace || key.delete
|
|
7595
|
+
? removeLastGrapheme(draft.query)
|
|
7596
|
+
: input && !key.ctrl && !key.meta && input >= ' '
|
|
7597
|
+
? draft.query + input : draft.query,
|
|
7598
|
+
showSelected: false,
|
|
7599
|
+
} : draft);
|
|
7600
|
+
}
|
|
6021
7601
|
setFocus(0);
|
|
6022
7602
|
setScrollOffset(0);
|
|
6023
7603
|
return;
|
|
@@ -6028,6 +7608,26 @@ export function ConsoleAppV4(props) {
|
|
|
6028
7608
|
|| route.name === 'configObjectPropertyEditor')) {
|
|
6029
7609
|
return;
|
|
6030
7610
|
}
|
|
7611
|
+
if (route.name === 'pluginDependencies' && route.view !== 'removeBlocked'
|
|
7612
|
+
&& (key.leftArrow || key.rightArrow)) {
|
|
7613
|
+
const projection = buildDependencyGraphProjection({
|
|
7614
|
+
overviews: props.controller.listPluginDependencyOverviews(),
|
|
7615
|
+
rootPluginId: route.pluginId,
|
|
7616
|
+
direction: route.direction ?? 'dependencies',
|
|
7617
|
+
...(expandedDependencyPluginIds ? { expandedPluginIds: expandedDependencyPluginIds } : {}),
|
|
7618
|
+
});
|
|
7619
|
+
const row = projection.rows[focus];
|
|
7620
|
+
if (!row)
|
|
7621
|
+
return;
|
|
7622
|
+
const next = new Set(expandedDependencyPluginIds ?? projection.defaultExpandedPluginIds);
|
|
7623
|
+
if (key.rightArrow && row.canExpand)
|
|
7624
|
+
next.add(row.pluginId);
|
|
7625
|
+
if (key.leftArrow && row.canExpand)
|
|
7626
|
+
next.delete(row.pluginId);
|
|
7627
|
+
setExpandedDependencyPluginIds(next);
|
|
7628
|
+
setScrollOffset(0);
|
|
7629
|
+
return;
|
|
7630
|
+
}
|
|
6031
7631
|
const sessionNavigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: isCharacterInputMode(route, searchFocus) });
|
|
6032
7632
|
const globalAction = resolveTuiGlobalKeyAction({ input, key }, { mode: 'menu', allowBack: true });
|
|
6033
7633
|
if (sessionNavigation) {
|
|
@@ -6071,7 +7671,8 @@ export function ConsoleAppV4(props) {
|
|
|
6071
7671
|
back();
|
|
6072
7672
|
}
|
|
6073
7673
|
else if (globalAction === 'back') {
|
|
6074
|
-
if (dirty && route.name !== 'review'
|
|
7674
|
+
if (dirty && route.name !== 'review'
|
|
7675
|
+
&& !(batchDraft && (route.name === 'pluginPreview' || route.name === 'pluginDetail'))) {
|
|
6075
7676
|
setOverlayFocus(0);
|
|
6076
7677
|
setDiscardForExit(false);
|
|
6077
7678
|
setOverlay('discard');
|
|
@@ -6082,9 +7683,11 @@ export function ConsoleAppV4(props) {
|
|
|
6082
7683
|
else if (route.name === 'pluginList' && input === '/') {
|
|
6083
7684
|
setSearchFocus(true);
|
|
6084
7685
|
}
|
|
6085
|
-
else if (route.name === 'pluginList' &&
|
|
7686
|
+
else if (route.name === 'pluginList' && input.toLowerCase() === 'f') {
|
|
6086
7687
|
const selectedPluginId = pluginRows[focus]?.pluginId;
|
|
6087
|
-
const nextFilter = installFilter === 'all'
|
|
7688
|
+
const nextFilter = installFilter === 'all'
|
|
7689
|
+
? route.mode === 'browse' ? 'uninstalled' : 'updateAvailable'
|
|
7690
|
+
: 'all';
|
|
6088
7691
|
const nextItems = props.controller.listPlugins({
|
|
6089
7692
|
mode: route.mode,
|
|
6090
7693
|
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
@@ -6096,44 +7699,64 @@ export function ConsoleAppV4(props) {
|
|
|
6096
7699
|
: -1;
|
|
6097
7700
|
setInstallFilter(nextFilter);
|
|
6098
7701
|
props.controller.updatePageContext({ installFilter: nextFilter });
|
|
6099
|
-
setFocus(retained >= 0 ? retained : 0);
|
|
7702
|
+
setFocus(retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0);
|
|
6100
7703
|
setScrollOffset(0);
|
|
6101
7704
|
}
|
|
6102
7705
|
else if (route.name === 'pluginList' && input.toLowerCase() === 'm') {
|
|
6103
7706
|
openBatchSelection(route.mode === 'browse' ? 'install' : undefined);
|
|
6104
7707
|
}
|
|
6105
7708
|
else if (route.name === 'pluginList' && route.mode === 'installed' && input.toLowerCase() === 'a') {
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
props.controller.push({ name: 'batchPlacement' });
|
|
6115
|
-
}
|
|
6116
|
-
else if (route.name === 'batchSelection' && batchDraft?.action && input.toLowerCase() === 'a') {
|
|
6117
|
-
const candidates = props.controller.listPlugins({
|
|
6118
|
-
mode: batchDraft.action === 'install' ? 'browse' : 'installed',
|
|
6119
|
-
}).filter((candidate) => !candidate.configOnly);
|
|
7709
|
+
const projectRoots = [...new Set(props.controller.listPluginWriteTaskCandidates('restore')
|
|
7710
|
+
.filter((candidate) => candidate.target.scope === 'project')
|
|
7711
|
+
.map((candidate) => candidate.target.pluginId))];
|
|
7712
|
+
openSelectedPluginTask('restore', projectRoots, 'project');
|
|
7713
|
+
}
|
|
7714
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'a') {
|
|
7715
|
+
const candidates = batchCandidates(batchDraft)
|
|
7716
|
+
.filter((candidate) => batchCandidateBlockReason(batchDraft, candidate) === undefined);
|
|
6120
7717
|
setBatchDraft((current) => current ? {
|
|
6121
7718
|
...current,
|
|
6122
|
-
selectedPluginIds:
|
|
6123
|
-
|
|
7719
|
+
selectedPluginIds: [...new Set([
|
|
7720
|
+
...current.selectedPluginIds,
|
|
7721
|
+
...candidates.map((candidate) => candidate.pluginId),
|
|
7722
|
+
])],
|
|
6124
7723
|
} : current);
|
|
6125
7724
|
}
|
|
6126
|
-
else if (route.name === 'batchSelection' && batchDraft?.
|
|
7725
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input.toLowerCase() === 'c') {
|
|
6127
7726
|
if (batchDraft.selectedPluginIds.length > 0) {
|
|
6128
|
-
|
|
7727
|
+
if (batchDraft.mode === 'browse') {
|
|
7728
|
+
setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
|
|
7729
|
+
props.controller.push({ name: 'batchPlacement' });
|
|
7730
|
+
}
|
|
7731
|
+
else
|
|
7732
|
+
setBatchDraft((current) => current ? { ...current, stage: 'task' } : current);
|
|
6129
7733
|
setFocus(0);
|
|
6130
7734
|
setScrollOffset(0);
|
|
6131
7735
|
}
|
|
6132
7736
|
else
|
|
6133
7737
|
setActionError('请至少选择一个插件。');
|
|
6134
7738
|
}
|
|
6135
|
-
else if (route.name === 'batchSelection' && batchDraft?.
|
|
6136
|
-
|
|
7739
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === ' ') {
|
|
7740
|
+
const action = actions[effectiveFocus];
|
|
7741
|
+
if (action?.pluginId && action.selectable)
|
|
7742
|
+
toggleBatchPlugin(action.pluginId);
|
|
7743
|
+
}
|
|
7744
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select' && input === '/') {
|
|
7745
|
+
setSearchFocus(true);
|
|
7746
|
+
}
|
|
7747
|
+
else if (route.name === 'batchSelection' && batchDraft?.stage === 'select'
|
|
7748
|
+
&& (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
|
|
7749
|
+
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
7750
|
+
const current = batchDraft.sourceId ?? 'all';
|
|
7751
|
+
const direction = key.leftArrow || input === '[' ? -1 : 1;
|
|
7752
|
+
const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
|
|
7753
|
+
setBatchDraft((draft) => draft ? {
|
|
7754
|
+
...draft,
|
|
7755
|
+
...(next === 'all' ? { sourceId: undefined } : { sourceId: next }),
|
|
7756
|
+
showSelected: false,
|
|
7757
|
+
} : draft);
|
|
7758
|
+
setFocus(0);
|
|
7759
|
+
setScrollOffset(0);
|
|
6137
7760
|
}
|
|
6138
7761
|
else if (route.name === 'configFields' && route.layer.scope !== 'effective' && input.toLowerCase() === 's') {
|
|
6139
7762
|
if (dirty && configDraftIssues.length === 0) {
|
|
@@ -6167,20 +7790,17 @@ export function ConsoleAppV4(props) {
|
|
|
6167
7790
|
else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
|
|
6168
7791
|
const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
|
|
6169
7792
|
const current = route.sourceId ?? 'all';
|
|
6170
|
-
const selectedPluginId =
|
|
6171
|
-
|
|
6172
|
-
...(current !== 'all' ? { sourceId: current } : {}),
|
|
6173
|
-
...(query ? { query } : {}),
|
|
6174
|
-
installFilter,
|
|
6175
|
-
})[focus]?.pluginId;
|
|
7793
|
+
const selectedPluginId = pluginRows[focus]?.pluginId.startsWith('@menu:')
|
|
7794
|
+
? undefined : pluginRows[focus]?.pluginId;
|
|
6176
7795
|
const direction = key.leftArrow || input === '[' ? -1 : 1;
|
|
6177
7796
|
const index = tabs.indexOf(current);
|
|
6178
7797
|
const next = tabs[(index + direction + tabs.length) % tabs.length];
|
|
6179
7798
|
const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}), installFilter });
|
|
6180
7799
|
const retained = selectedPluginId ? nextItems.findIndex((item) => item.pluginId === selectedPluginId) : -1;
|
|
6181
7800
|
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
|
|
6182
|
-
|
|
6183
|
-
|
|
7801
|
+
const retainedFocus = retained >= 0 ? pluginListMenuCount(route.mode, nextItems) + retained : 0;
|
|
7802
|
+
setFocus(retainedFocus);
|
|
7803
|
+
setScrollOffset(retainedFocus >= 4 ? retainedFocus - 3 : 0);
|
|
6184
7804
|
}
|
|
6185
7805
|
else if (input.toLowerCase() === 'a' && route.name === 'portableSkillSelect') {
|
|
6186
7806
|
props.controller.selectAllPortableSkills();
|