@lesliechan721/aw 0.1.0-beta.4 → 0.1.0-beta.6
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/authoring/scripts/lib/plugin-version-check.d.mts +14 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +10 -224
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.d.mts +2 -0
- package/dist/authoring/scripts/remove-plugin.mjs +366 -0
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
- package/dist/authoring/scripts/validate-plugin.mjs +9 -46
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.d.ts +0 -2
- package/dist/scripts/bundle-plugins.js +4 -2
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +1 -16
- package/dist/scripts/plugin-staging.js +8 -115
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +9 -1
- package/dist/src/authoring/workspace-commands.js +24 -16
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +106 -0
- package/dist/src/commands/catalog-publication-inspection.js +444 -0
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
- package/dist/src/commands/catalog-publish.d.ts +18 -2
- package/dist/src/commands/catalog-publish.js +261 -316
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +60 -0
- package/dist/src/commands/catalog-status.js +251 -0
- package/dist/src/commands/catalog-status.js.map +1 -0
- package/dist/src/commands/plugin-capabilities.d.ts +22 -0
- package/dist/src/commands/plugin-capabilities.js +143 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -0
- package/dist/src/commands/rebuild-command.d.ts +2 -0
- package/dist/src/commands/rebuild-command.js +37 -6
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +1 -0
- package/dist/src/commands/rebuild-integration.js +55 -57
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/status-v4.d.ts +21 -0
- package/dist/src/commands/status-v4.js +63 -2
- package/dist/src/commands/status-v4.js.map +1 -1
- package/dist/src/index.js +79 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/tui/components.d.ts +70 -0
- package/dist/src/tui/components.js +149 -6
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +1660 -628
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +5 -1
- package/dist/src/tui/controller-runtime.js +184 -33
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +35 -2
- package/dist/src/tui/controller.js +300 -39
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +31 -0
- package/dist/src/tui/diagnostics.js +71 -0
- package/dist/src/tui/diagnostics.js.map +1 -0
- package/dist/src/tui/index.d.ts +5 -3
- package/dist/src/tui/index.js +3 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +33 -4
- package/dist/src/tui/model.js +252 -17
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +15 -9
- package/dist/src/tui/router.js +35 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/theme.d.ts +18 -10
- package/dist/src/tui/theme.js +18 -10
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/viewport.d.ts +20 -0
- package/dist/src/tui/viewport.js +29 -0
- package/dist/src/tui/viewport.js.map +1 -0
- package/dist/src/utils/catalog-runtime-projection.d.ts +1 -7
- package/dist/src/utils/catalog-runtime-projection.js +2 -62
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-source-cache.d.ts +12 -11
- package/dist/src/utils/catalog-source-cache.js +20 -0
- package/dist/src/utils/catalog-source-cache.js.map +1 -1
- package/dist/src/utils/dynamic-skill-tree.js +1 -1
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.js +1 -1
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +0 -1
- package/dist/src/utils/manifest.js +0 -1
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/plugin-config.js +28 -30
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/skill-validation.d.ts +1 -1
- package/dist/src/utils/skill-validation.js +1 -1
- package/dist/src/utils/skill-validation.js.map +1 -1
- package/dist/src/utils/state-paths.d.ts +1 -0
- package/dist/src/utils/state-paths.js +5 -0
- package/dist/src/utils/state-paths.js.map +1 -1
- package/package.json +3 -4
- package/dist/src/utils/plugin-authoring-capabilities.d.ts +0 -23
- package/dist/src/utils/plugin-authoring-capabilities.js +0 -37
- package/dist/src/utils/plugin-authoring-capabilities.js.map +0 -1
- package/dist/src/utils/plugin-authoring-capabilities.json +0 -92
- package/dist/src/utils/skill-frontmatter-capabilities.d.ts +0 -201
- package/dist/src/utils/skill-frontmatter-capabilities.js +0 -5
- package/dist/src/utils/skill-frontmatter-capabilities.js.map +0 -1
- package/dist/src/utils/skill-frontmatter-capabilities.json +0 -200
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { buildTuiViewModel, hasInstalledPlacement, } from './model.js';
|
|
3
|
+
import { safeDiagnosticCode } from './diagnostics.js';
|
|
3
4
|
import { TuiRouter, } from './router.js';
|
|
5
|
+
function reviewPreparationStageLabel(stage) {
|
|
6
|
+
return {
|
|
7
|
+
validate: '验证当前状态',
|
|
8
|
+
status: '读取安装状态',
|
|
9
|
+
catalog: '解析 Catalog',
|
|
10
|
+
inventory: '检查本地现场',
|
|
11
|
+
dependencyPlan: '准备依赖计划',
|
|
12
|
+
config: '验证设置',
|
|
13
|
+
portable: '准备 Portable Skills',
|
|
14
|
+
evidence: '准备确认信息',
|
|
15
|
+
}[stage];
|
|
16
|
+
}
|
|
4
17
|
export class TuiOperationBlockedError extends Error {
|
|
5
18
|
code;
|
|
6
19
|
constructor(code, message) {
|
|
@@ -58,6 +71,14 @@ function operationLabels(intent) {
|
|
|
58
71
|
}
|
|
59
72
|
if (intent.kind === 'pluginWrite') {
|
|
60
73
|
if (intent.originIssue) {
|
|
74
|
+
if (intent.action === 'cleanup') {
|
|
75
|
+
return {
|
|
76
|
+
title: '清理已安装内容',
|
|
77
|
+
description: '只清理可证明属于 AW 的现场内容;保留插件设置;不运行 uninstall lifecycle。',
|
|
78
|
+
primaryLabel: '开始清理',
|
|
79
|
+
destructive: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
61
82
|
return {
|
|
62
83
|
title: '处理问题',
|
|
63
84
|
description: '只执行所选问题处理动作;插件设置保持不变。',
|
|
@@ -310,7 +331,7 @@ export function extractPortableError(error) {
|
|
|
310
331
|
: undefined;
|
|
311
332
|
const message = error instanceof Error ? error.message : String(error);
|
|
312
333
|
const fallback = message.match(/^([A-Za-z][A-Za-z0-9]*):/u)?.[1];
|
|
313
|
-
const diagnosticCode = typedCode ?? fallback
|
|
334
|
+
const diagnosticCode = safeDiagnosticCode(typedCode ?? fallback, 'portableValidationFailed');
|
|
314
335
|
if (diagnosticCode === 'requiredConfigMissing')
|
|
315
336
|
return { code: 'requiredConfigMissing', diagnosticCode };
|
|
316
337
|
if (diagnosticCode === 'portableSkillsCliMissing')
|
|
@@ -340,6 +361,8 @@ export class TuiController {
|
|
|
340
361
|
#progress = { messages: [] };
|
|
341
362
|
#progressTargets = [];
|
|
342
363
|
#activeReview;
|
|
364
|
+
#reviewPreparation;
|
|
365
|
+
#reviewPreparationToken = 0;
|
|
343
366
|
#result;
|
|
344
367
|
#loadError;
|
|
345
368
|
#portableDraft;
|
|
@@ -390,6 +413,7 @@ export class TuiController {
|
|
|
390
413
|
progress: this.#progress,
|
|
391
414
|
progressTargets: this.#progressTargets,
|
|
392
415
|
...(this.#activeReview ? { activeReview: this.#activeReview } : {}),
|
|
416
|
+
...(this.#reviewPreparation ? { reviewPreparation: this.#reviewPreparation.snapshot } : {}),
|
|
393
417
|
...(this.#result ? { result: this.#result } : {}),
|
|
394
418
|
...(this.#loadError ? { loadError: this.#loadError } : {}),
|
|
395
419
|
...(this.#portableDraft ? { portableDraft: this.#portableDraft } : {}),
|
|
@@ -421,6 +445,7 @@ export class TuiController {
|
|
|
421
445
|
this.#viewModel = buildTuiViewModel({
|
|
422
446
|
status: this.#status.payload,
|
|
423
447
|
pluginSummaries: this.#status.pluginSummaries,
|
|
448
|
+
targetRevisions: this.#status.targetRevisions,
|
|
424
449
|
sourceHealth: this.#health,
|
|
425
450
|
sourceDetails: this.#status.sourceDetails,
|
|
426
451
|
dependencyOverviews: this.#status.dependencyOverviews,
|
|
@@ -441,7 +466,7 @@ export class TuiController {
|
|
|
441
466
|
|| route.name === 'portableBlocker'
|
|
442
467
|
|| route.name === 'pluginDependencies'
|
|
443
468
|
|| route.name === 'installPlacement'
|
|
444
|
-
|| route.name === '
|
|
469
|
+
|| route.name === 'installTargets'
|
|
445
470
|
|| route.name === 'pluginDetail'
|
|
446
471
|
|| route.name === 'platform'
|
|
447
472
|
|| route.name === 'pluginAbout'
|
|
@@ -449,13 +474,12 @@ export class TuiController {
|
|
|
449
474
|
|| route.name === 'configLayer'
|
|
450
475
|
|| route.name === 'configFields'
|
|
451
476
|
|| route.name === 'configFieldEditor'
|
|
452
|
-
|| route.name === 'configPresets'
|
|
453
477
|
|| route.name === 'configUnavailable';
|
|
454
478
|
if (pluginRoute && !this.#viewModel?.plugins.some((plugin) => plugin.pluginId === route.pluginId)) {
|
|
455
479
|
return {
|
|
456
480
|
name: 'pluginList',
|
|
457
481
|
mode: route.name === 'pluginPreview' || route.name === 'pluginDependencies'
|
|
458
|
-
|| route.name === 'installPlacement' || route.name === '
|
|
482
|
+
|| route.name === 'installPlacement' || route.name === 'installTargets'
|
|
459
483
|
? 'browse'
|
|
460
484
|
: 'installed',
|
|
461
485
|
};
|
|
@@ -685,6 +709,7 @@ export class TuiController {
|
|
|
685
709
|
this.#publish();
|
|
686
710
|
}
|
|
687
711
|
push(route, context) {
|
|
712
|
+
this.#cancelReviewPreparation();
|
|
688
713
|
this.#cancelPortableRequest();
|
|
689
714
|
this.#discardPortableDraftForRoute(route);
|
|
690
715
|
this.#router.push(route, context);
|
|
@@ -818,6 +843,16 @@ export class TuiController {
|
|
|
818
843
|
this.#publish();
|
|
819
844
|
}
|
|
820
845
|
async buildPortableInstallReview() {
|
|
846
|
+
const intent = this.#portableInstallIntent();
|
|
847
|
+
const draft = this.#portableDraft;
|
|
848
|
+
const expectedRoute = this.#router.snapshot.current.route;
|
|
849
|
+
return this.#runPortableRequest(`review:${JSON.stringify(intent)}`, async (signal, token) => {
|
|
850
|
+
const evidence = await this.dependencies.captureEvidence(structuredClone(intent), signal);
|
|
851
|
+
this.#assertPortableRequestCurrent(token, expectedRoute);
|
|
852
|
+
return this.#storeOperationReview(intent, evidence, 'push', this.#router.portableOrigin(draft.pluginId));
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
#portableInstallIntent() {
|
|
821
856
|
const draft = this.#portableDraft;
|
|
822
857
|
if (!draft?.canContinue) {
|
|
823
858
|
throw new TuiOperationBlockedError('portableNoSkillsSelected', '至少选择一个 Skill。');
|
|
@@ -826,19 +861,17 @@ export class TuiController {
|
|
|
826
861
|
if (unavailable.length > 0) {
|
|
827
862
|
throw new TuiOperationBlockedError('portableScopeUnavailable', `所选范围不提供这些 Skill:${unavailable.map((candidate) => candidate.name).join(', ')}。请返回 Skill 选择或更改范围。`);
|
|
828
863
|
}
|
|
829
|
-
|
|
864
|
+
return {
|
|
830
865
|
kind: 'portableInstall',
|
|
831
866
|
pluginId: draft.pluginId,
|
|
832
867
|
scope: draft.scope,
|
|
833
868
|
selectedSkills: draft.candidates.filter((candidate) => candidate.selected).map((candidate) => candidate.name),
|
|
834
869
|
...(draft.agentMode === 'explicit' ? { requestedAgents: draft.requestedAgents } : {}),
|
|
835
870
|
};
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
return this.#storeOperationReview(intent, evidence, 'push', this.#router.portableOrigin(draft.pluginId));
|
|
841
|
-
});
|
|
871
|
+
}
|
|
872
|
+
startPortableInstallReviewPreparation() {
|
|
873
|
+
const intent = this.#portableInstallIntent();
|
|
874
|
+
return this.startReviewPreparation(intent, this.#router.portableOrigin(intent.pluginId));
|
|
842
875
|
}
|
|
843
876
|
discardPortableDraft() {
|
|
844
877
|
this.#cancelPortableRequest();
|
|
@@ -846,6 +879,7 @@ export class TuiController {
|
|
|
846
879
|
this.#publish();
|
|
847
880
|
}
|
|
848
881
|
replace(route, context) {
|
|
882
|
+
this.#cancelReviewPreparation();
|
|
849
883
|
this.#cancelPortableRequest();
|
|
850
884
|
const current = this.#router.snapshot.current.route;
|
|
851
885
|
if (current.name === 'portableBlocker' && current.code === 'requiredConfigMissing'
|
|
@@ -856,11 +890,24 @@ export class TuiController {
|
|
|
856
890
|
this.#router.replace(route, context);
|
|
857
891
|
this.#publish();
|
|
858
892
|
}
|
|
893
|
+
beginConfigFlow(pluginId) {
|
|
894
|
+
this.#router.beginConfigFlow(pluginId);
|
|
895
|
+
this.#router.push({
|
|
896
|
+
name: 'configFields',
|
|
897
|
+
pluginId,
|
|
898
|
+
layer: { scope: 'effective', projectLayer: 'shared' },
|
|
899
|
+
});
|
|
900
|
+
this.#publish();
|
|
901
|
+
}
|
|
902
|
+
configOrigin(pluginId) {
|
|
903
|
+
return this.#router.configOrigin(pluginId);
|
|
904
|
+
}
|
|
859
905
|
updatePageContext(context) {
|
|
860
906
|
this.#router.updateContext(context);
|
|
861
907
|
this.#publish();
|
|
862
908
|
}
|
|
863
909
|
back() {
|
|
910
|
+
this.#cancelReviewPreparation();
|
|
864
911
|
this.#cancelPortableRequest();
|
|
865
912
|
const route = this.#router.snapshot.current.route;
|
|
866
913
|
if (route.name === 'review' && this.#activeReview?.operationId === route.operationId) {
|
|
@@ -892,6 +939,14 @@ export class TuiController {
|
|
|
892
939
|
this.resetPortableFlowToPreview(this.#result.pluginId, target, preserveDraft);
|
|
893
940
|
return;
|
|
894
941
|
}
|
|
942
|
+
if (this.#result?.state === 'succeeded'
|
|
943
|
+
&& (this.#result.operationKind === 'configSave' || this.#result.operationKind === 'configReset')
|
|
944
|
+
&& this.#result.pluginId
|
|
945
|
+
&& this.#router.resetConfigFlow(this.#result.pluginId, target)) {
|
|
946
|
+
this.#result = undefined;
|
|
947
|
+
this.#publish();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
895
950
|
const route = target ?? (this.#result && 'returnRoute' in this.#result
|
|
896
951
|
? this.#result.returnRoute
|
|
897
952
|
: undefined);
|
|
@@ -911,8 +966,10 @@ export class TuiController {
|
|
|
911
966
|
|| route.name === 'portableAgentInput'
|
|
912
967
|
|| route.name === 'portableScope'
|
|
913
968
|
|| route.name === 'portableBlocker';
|
|
914
|
-
const portableOperationRoute = (route.name === '
|
|
915
|
-
|
|
969
|
+
const portableOperationRoute = (route.name === 'reviewPreparing' || route.name === 'review'
|
|
970
|
+
|| route.name === 'progress' || route.name === 'result')
|
|
971
|
+
&& (this.#reviewPreparation?.snapshot.intent.kind === 'portableInstall'
|
|
972
|
+
|| this.#activeReview?.operationKind === 'portableInstall'
|
|
916
973
|
|| this.#result?.operationKind === 'portableInstall');
|
|
917
974
|
if (!portableRoute && !portableOperationRoute) {
|
|
918
975
|
this.#cancelPortableRequest();
|
|
@@ -970,6 +1027,7 @@ export class TuiController {
|
|
|
970
1027
|
const query = options.query?.trim().toLocaleLowerCase('en');
|
|
971
1028
|
return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
|
|
972
1029
|
&& (options.mode === 'browse' || hasInstalledPlacement(plugin))
|
|
1030
|
+
&& (options.installFilter !== 'uninstalled' || !hasInstalledPlacement(plugin))
|
|
973
1031
|
&& (!query || `${plugin.displayName}\n${plugin.description}\n${plugin.sourceId}`.toLocaleLowerCase('en').includes(query))));
|
|
974
1032
|
}
|
|
975
1033
|
getPluginOverview(pluginId) {
|
|
@@ -997,6 +1055,64 @@ export class TuiController {
|
|
|
997
1055
|
getPlatformPlacement(pluginId, platform) {
|
|
998
1056
|
return this.getPluginOverview(pluginId)?.installedPlacements.find((placement) => placement.platform === platform);
|
|
999
1057
|
}
|
|
1058
|
+
listInstallTargets(pluginId, scope) {
|
|
1059
|
+
const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
|
|
1060
|
+
if (!plugin)
|
|
1061
|
+
return [];
|
|
1062
|
+
if (plugin.aggregateStatus === 'unavailable') {
|
|
1063
|
+
return plugin.supportedPlatforms.map((platform) => ({
|
|
1064
|
+
platform,
|
|
1065
|
+
compatible: false,
|
|
1066
|
+
disabled: '插件来源不可用',
|
|
1067
|
+
}));
|
|
1068
|
+
}
|
|
1069
|
+
return plugin.supportedPlatforms.map((platform) => {
|
|
1070
|
+
const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
|
|
1071
|
+
if (!placement)
|
|
1072
|
+
return { platform, compatible: false, disabled: '当前没有可信的平台能力信息' };
|
|
1073
|
+
if (placement.state === 'scopeConflict') {
|
|
1074
|
+
return { platform, compatible: false, disabled: '安装位置冲突;请先处理问题' };
|
|
1075
|
+
}
|
|
1076
|
+
if (placement.state === 'installed') {
|
|
1077
|
+
return {
|
|
1078
|
+
platform,
|
|
1079
|
+
compatible: false,
|
|
1080
|
+
disabled: `已安装到${placement.scope === 'user' ? '所有项目' : '当前项目'};请先移除现有安装`,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
if (!placement.supportedScopes.includes(scope)) {
|
|
1084
|
+
return { platform, compatible: false, disabled: '不支持所选位置' };
|
|
1085
|
+
}
|
|
1086
|
+
const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
|
|
1087
|
+
for (const dependency of overview?.dependencies ?? []) {
|
|
1088
|
+
const dependentPlugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === dependency.pluginId);
|
|
1089
|
+
if (!dependentPlugin || !dependentPlugin.supportedPlatforms.includes(platform)) {
|
|
1090
|
+
return {
|
|
1091
|
+
platform,
|
|
1092
|
+
compatible: false,
|
|
1093
|
+
disabled: `${dependency.displayName} 不支持${platform === 'codex' ? 'Codex' : 'Claude Code'}`,
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
const dependencyPlacement = dependentPlugin.installedPlacements.find((entry) => entry.platform === platform);
|
|
1097
|
+
if (dependencyPlacement?.state === 'scopeConflict') {
|
|
1098
|
+
return { platform, compatible: false, disabled: `${dependency.displayName} 的安装位置冲突` };
|
|
1099
|
+
}
|
|
1100
|
+
if (dependencyPlacement?.state === 'installed') {
|
|
1101
|
+
if (dependencyPlacement.health !== 'ready') {
|
|
1102
|
+
return { platform, compatible: false, disabled: `${dependency.displayName} 需要先处理` };
|
|
1103
|
+
}
|
|
1104
|
+
if (scope === 'user' && dependencyPlacement.scope === 'project') {
|
|
1105
|
+
return { platform, compatible: false, disabled: `${dependency.displayName} 只在当前项目可用` };
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (dependencyPlacement?.state === 'notInstalled'
|
|
1109
|
+
&& !dependencyPlacement.supportedScopes.includes(scope)) {
|
|
1110
|
+
return { platform, compatible: false, disabled: `${dependency.displayName} 不支持所选位置` };
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return { platform, compatible: true };
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1000
1116
|
getTargetState(pluginId, platform, scope) {
|
|
1001
1117
|
return this.#status?.payload.targets.find((target) => target.pluginId === pluginId
|
|
1002
1118
|
&& target.platform === platform
|
|
@@ -1044,16 +1160,8 @@ export class TuiController {
|
|
|
1044
1160
|
}
|
|
1045
1161
|
async buildOperationReview(intent, returnRoute) {
|
|
1046
1162
|
try {
|
|
1047
|
-
this.#
|
|
1048
|
-
|
|
1049
|
-
&& (intent.action === 'uninstall' || intent.action === 'cleanup')
|
|
1050
|
-
&& intent.force !== true
|
|
1051
|
-
&& (intent.platform === 'both' ? ['codex', 'claude'] : [intent.platform])
|
|
1052
|
-
.some((platform) => this.getBlockingDependents(intent.pluginId, platform, intent.scope).length > 0)) {
|
|
1053
|
-
throw new TuiOperationBlockedError('dependencyStillRequired', '仍有已安装插件依赖这个插件。请先查看完整依赖关系。');
|
|
1054
|
-
}
|
|
1055
|
-
await this.dependencies.validateIntent?.(structuredClone(intent), this.#abort.signal);
|
|
1056
|
-
const evidence = await this.dependencies.captureEvidence(structuredClone(intent), this.#abort.signal);
|
|
1163
|
+
this.#validateReviewIntent(intent);
|
|
1164
|
+
const evidence = await this.#prepareReviewEvidence(intent, this.#abort.signal);
|
|
1057
1165
|
return this.#storeOperationReview(intent, evidence, 'push', returnRoute);
|
|
1058
1166
|
}
|
|
1059
1167
|
catch (error) {
|
|
@@ -1062,6 +1170,142 @@ export class TuiController {
|
|
|
1062
1170
|
throw new TuiOperationBlockedError(code, '操作未建立。请检查当前设置后重试。');
|
|
1063
1171
|
}
|
|
1064
1172
|
}
|
|
1173
|
+
startReviewPreparation(intent, returnRoute) {
|
|
1174
|
+
const inheritedReturnRoute = this.#reviewPreparation?.returnRoute;
|
|
1175
|
+
const current = this.#router.snapshot.current.route;
|
|
1176
|
+
const navigation = current.name === 'result' || current.name === 'reviewPreparing'
|
|
1177
|
+
? 'replace' : 'push';
|
|
1178
|
+
const resolvedReturnRoute = returnRoute
|
|
1179
|
+
?? (current.name === 'reviewPreparing' ? inheritedReturnRoute : undefined)
|
|
1180
|
+
?? (current.name === 'result' && this.#result
|
|
1181
|
+
&& 'returnRoute' in this.#result && this.#result.returnRoute
|
|
1182
|
+
? this.#result.returnRoute
|
|
1183
|
+
: current);
|
|
1184
|
+
const normalizedReturnRoute = this.#normalizeRoute(resolvedReturnRoute);
|
|
1185
|
+
const key = JSON.stringify(stableValue({ intent, returnRoute: normalizedReturnRoute }));
|
|
1186
|
+
if (this.#reviewPreparation?.snapshot.state === 'running'
|
|
1187
|
+
&& this.#reviewPreparation.key === key) {
|
|
1188
|
+
return cloneFrozen(this.#reviewPreparation.snapshot);
|
|
1189
|
+
}
|
|
1190
|
+
this.#cancelReviewPreparation();
|
|
1191
|
+
const id = crypto.randomUUID();
|
|
1192
|
+
const abort = new AbortController();
|
|
1193
|
+
const token = ++this.#reviewPreparationToken;
|
|
1194
|
+
const snapshot = {
|
|
1195
|
+
id,
|
|
1196
|
+
intent: structuredClone(intent),
|
|
1197
|
+
state: 'running',
|
|
1198
|
+
stages: this.dependencies.prepareReviewEvidence ? [] : [
|
|
1199
|
+
{ id: 'validate', label: '验证当前状态', state: 'running' },
|
|
1200
|
+
{ id: 'evidence', label: '准备确认信息', state: 'pending' },
|
|
1201
|
+
],
|
|
1202
|
+
currentLabel: this.#reviewPreparationLabel(intent),
|
|
1203
|
+
};
|
|
1204
|
+
this.#reviewPreparation = {
|
|
1205
|
+
snapshot,
|
|
1206
|
+
key,
|
|
1207
|
+
returnRoute: structuredClone(normalizedReturnRoute),
|
|
1208
|
+
abort,
|
|
1209
|
+
token,
|
|
1210
|
+
};
|
|
1211
|
+
this.#result = undefined;
|
|
1212
|
+
this.#router[navigation]({ name: 'reviewPreparing', preparationId: id });
|
|
1213
|
+
this.#publish();
|
|
1214
|
+
queueMicrotask(() => void this.#runReviewPreparation(token));
|
|
1215
|
+
return cloneFrozen(snapshot);
|
|
1216
|
+
}
|
|
1217
|
+
retryReviewPreparation() {
|
|
1218
|
+
const preparation = this.#reviewPreparation;
|
|
1219
|
+
if (!preparation || preparation.snapshot.state !== 'failed')
|
|
1220
|
+
return undefined;
|
|
1221
|
+
return this.startReviewPreparation(preparation.snapshot.intent, preparation.returnRoute);
|
|
1222
|
+
}
|
|
1223
|
+
#validateReviewIntent(intent) {
|
|
1224
|
+
this.#assertIssueOrigin(intent);
|
|
1225
|
+
if (intent.kind === 'pluginWrite'
|
|
1226
|
+
&& (intent.action === 'uninstall' || intent.action === 'cleanup')
|
|
1227
|
+
&& intent.force !== true
|
|
1228
|
+
&& (intent.platform === 'both' ? ['codex', 'claude'] : [intent.platform])
|
|
1229
|
+
.some((platform) => this.getBlockingDependents(intent.pluginId, platform, intent.scope).length > 0)) {
|
|
1230
|
+
throw new TuiOperationBlockedError('dependencyStillRequired', '仍有已安装插件依赖这个插件。请先查看完整依赖关系。');
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
#reviewPreparationLabel(intent) {
|
|
1234
|
+
if ('pluginId' in intent) {
|
|
1235
|
+
return this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName
|
|
1236
|
+
?? pluginNameFromId(intent.pluginId);
|
|
1237
|
+
}
|
|
1238
|
+
return intent.sourceId;
|
|
1239
|
+
}
|
|
1240
|
+
#reviewPreparationIsCurrent(token) {
|
|
1241
|
+
const preparation = this.#reviewPreparation;
|
|
1242
|
+
return Boolean(preparation && preparation.token === token
|
|
1243
|
+
&& !preparation.abort.signal.aborted
|
|
1244
|
+
&& !this.#disposed
|
|
1245
|
+
&& this.#router.snapshot.current.route.name === 'reviewPreparing'
|
|
1246
|
+
&& this.#router.snapshot.current.route.preparationId === preparation.snapshot.id);
|
|
1247
|
+
}
|
|
1248
|
+
async #runReviewPreparation(token) {
|
|
1249
|
+
const preparation = this.#reviewPreparation;
|
|
1250
|
+
if (!preparation || preparation.token !== token)
|
|
1251
|
+
return;
|
|
1252
|
+
const intent = structuredClone(preparation.snapshot.intent);
|
|
1253
|
+
const signal = AbortSignal.any([this.#abort.signal, preparation.abort.signal]);
|
|
1254
|
+
try {
|
|
1255
|
+
this.#validateReviewIntent(intent);
|
|
1256
|
+
const evidence = await this.#prepareReviewEvidence(intent, signal, (stage) => {
|
|
1257
|
+
if (!this.#reviewPreparationIsCurrent(token))
|
|
1258
|
+
return;
|
|
1259
|
+
const existing = preparation.snapshot.stages.find((entry) => entry.id === stage);
|
|
1260
|
+
preparation.snapshot.stages = preparation.snapshot.stages.map((entry) => entry.state === 'running' ? { ...entry, state: 'completed' } : entry);
|
|
1261
|
+
if (existing) {
|
|
1262
|
+
preparation.snapshot.stages = preparation.snapshot.stages.map((entry) => entry.id === stage ? { ...entry, state: 'running' } : entry);
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
preparation.snapshot.stages.push({
|
|
1266
|
+
id: stage,
|
|
1267
|
+
label: reviewPreparationStageLabel(stage),
|
|
1268
|
+
state: 'running',
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
this.#publish();
|
|
1272
|
+
});
|
|
1273
|
+
if (!this.#reviewPreparationIsCurrent(token))
|
|
1274
|
+
return;
|
|
1275
|
+
preparation.snapshot.stages = preparation.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'completed' } : stage);
|
|
1276
|
+
this.#publish();
|
|
1277
|
+
const returnRoute = structuredClone(preparation.returnRoute);
|
|
1278
|
+
this.#reviewPreparation = undefined;
|
|
1279
|
+
this.#storeOperationReview(intent, evidence, 'replace', returnRoute);
|
|
1280
|
+
}
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
if (!this.#reviewPreparationIsCurrent(token))
|
|
1283
|
+
return;
|
|
1284
|
+
const active = this.#reviewPreparation;
|
|
1285
|
+
active.snapshot.state = 'failed';
|
|
1286
|
+
active.snapshot.stages = active.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'failed' } : stage);
|
|
1287
|
+
active.snapshot.code = 'reviewPreparationFailed';
|
|
1288
|
+
active.snapshot.message = '无法准备确认信息。请返回检查选择,或重试。';
|
|
1289
|
+
this.#publish();
|
|
1290
|
+
await this.#logOperationError(error, issueCode(error), intent);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
async #prepareReviewEvidence(intent, signal, onStage = () => undefined) {
|
|
1294
|
+
if (this.dependencies.prepareReviewEvidence) {
|
|
1295
|
+
return this.dependencies.prepareReviewEvidence(structuredClone(intent), signal, onStage);
|
|
1296
|
+
}
|
|
1297
|
+
await onStage('validate');
|
|
1298
|
+
await this.dependencies.validateIntent?.(structuredClone(intent), signal);
|
|
1299
|
+
await onStage('evidence');
|
|
1300
|
+
return this.dependencies.captureEvidence(structuredClone(intent), signal);
|
|
1301
|
+
}
|
|
1302
|
+
#cancelReviewPreparation() {
|
|
1303
|
+
if (!this.#reviewPreparation)
|
|
1304
|
+
return;
|
|
1305
|
+
this.#reviewPreparationToken += 1;
|
|
1306
|
+
this.#reviewPreparation.abort.abort();
|
|
1307
|
+
this.#reviewPreparation = undefined;
|
|
1308
|
+
}
|
|
1065
1309
|
async #logOperationError(error, code, intent) {
|
|
1066
1310
|
await this.#logErrorSafe(error, code, {
|
|
1067
1311
|
privateConfigOperation: intent.kind === 'configSave' || intent.kind === 'configReset'
|
|
@@ -1119,8 +1363,8 @@ export class TuiController {
|
|
|
1119
1363
|
},
|
|
1120
1364
|
changes: reviewChanges(intent, evidence, 'pluginId' in intent ? this.getPluginDependencyOverview(intent.pluginId) : undefined),
|
|
1121
1365
|
technicalDetails: technicalDetails(evidence),
|
|
1122
|
-
...(intent.kind === 'pluginWrite' && intent.action === 'cleanup'
|
|
1123
|
-
? { equivalentCommand: `aw cleanup ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope} --force` }
|
|
1366
|
+
...(intent.kind === 'pluginWrite' && intent.action === 'cleanup'
|
|
1367
|
+
? { equivalentCommand: `aw cleanup ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope}${intent.force === true ? ' --force' : ''}` }
|
|
1124
1368
|
: {}),
|
|
1125
1369
|
primaryLabel: intent.kind === 'pluginWrite' && evidence.dependencyPlan
|
|
1126
1370
|
? `${labels.primaryLabel} ${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`
|
|
@@ -1293,19 +1537,26 @@ export class TuiController {
|
|
|
1293
1537
|
const pluginSummary = ticket.intent.kind === 'pluginWrite'
|
|
1294
1538
|
? operationResultSummary(ticket.intent, execution)
|
|
1295
1539
|
: undefined;
|
|
1540
|
+
const hasFailedDependencyTarget = pluginSummary?.targets?.some((target) => target.relation !== undefined && (target.state === 'failed' || target.state === 'skipped'));
|
|
1541
|
+
const hasPreservedTarget = pluginSummary?.targets?.some((target) => target.state === 'completed' || target.state === 'kept');
|
|
1542
|
+
const dependencyPartial = pluginSummary?.code === 'operationPartial'
|
|
1543
|
+
&& Boolean(hasFailedDependencyTarget && hasPreservedTarget);
|
|
1296
1544
|
const dependencyFailure = pluginSummary?.code === 'operationFailed'
|
|
1297
|
-
&&
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1545
|
+
&& Boolean(hasFailedDependencyTarget);
|
|
1546
|
+
if (pluginSummary?.code === 'operationFailed') {
|
|
1547
|
+
const unsuccessfulTargets = pluginSummary.targets?.filter((target) => target.state === 'failed' || target.state === 'skipped') ?? [];
|
|
1548
|
+
const dependencyStillRequired = unsuccessfulTargets.length > 0
|
|
1549
|
+
&& unsuccessfulTargets.every((target) => target.errorCode === 'dependencyStillRequired');
|
|
1550
|
+
return this.#setBlocked(operationId, dependencyStillRequired ? 'dependencyStillRequired' : 'operationFailed', {
|
|
1301
1551
|
intent: ticket.intent,
|
|
1302
1552
|
navigation: 'result',
|
|
1303
1553
|
returnRoute: ticket.returnRoute,
|
|
1304
|
-
|
|
1554
|
+
...(dependencyFailure || dependencyStillRequired ? { summary: pluginSummary } : {}),
|
|
1555
|
+
...(!dependencyFailure && !dependencyStillRequired ? { writeMayHaveCompleted: true } : {}),
|
|
1305
1556
|
});
|
|
1306
1557
|
}
|
|
1307
1558
|
const originIssue = ticket.intent.kind === 'pluginWrite' ? ticket.intent.originIssue : undefined;
|
|
1308
|
-
if (originIssue && !
|
|
1559
|
+
if (originIssue && !dependencyPartial
|
|
1309
1560
|
&& this.#viewModel?.issues.some((issue) => userIssueKey(issue) === originIssue.issueKey)) {
|
|
1310
1561
|
return this.#setBlocked(operationId, 'issueStillPresent', {
|
|
1311
1562
|
intent: ticket.intent,
|
|
@@ -1326,7 +1577,7 @@ export class TuiController {
|
|
|
1326
1577
|
});
|
|
1327
1578
|
}
|
|
1328
1579
|
}
|
|
1329
|
-
const baseSummary =
|
|
1580
|
+
const baseSummary = dependencyPartial && pluginSummary
|
|
1330
1581
|
? { ...pluginSummary, code: 'operationPartial', message: '依赖安装没有全部完成。' }
|
|
1331
1582
|
: pluginSummary ?? operationResultSummary(ticket.intent, execution);
|
|
1332
1583
|
const summary = {
|
|
@@ -1538,6 +1789,7 @@ export class TuiController {
|
|
|
1538
1789
|
...(options?.returnRoute ? { returnRoute: structuredClone(this.#normalizeRoute(options.returnRoute)) } : {}),
|
|
1539
1790
|
...(options?.writeMayHaveCompleted ? { writeMayHaveCompleted: true } : {}),
|
|
1540
1791
|
...(options?.verification ? { verification: options.verification } : {}),
|
|
1792
|
+
...(options?.summary ? { summary: structuredClone(options.summary) } : {}),
|
|
1541
1793
|
};
|
|
1542
1794
|
this.#result = structuredClone(result);
|
|
1543
1795
|
this.#activeReview = undefined;
|
|
@@ -1587,13 +1839,17 @@ export class TuiController {
|
|
|
1587
1839
|
: origin.actionId === 'remove'
|
|
1588
1840
|
? { action: 'uninstall', scope: issue.scope }
|
|
1589
1841
|
: undefined
|
|
1590
|
-
: issue.category === '
|
|
1591
|
-
? origin.actionId === '
|
|
1592
|
-
? { action: '
|
|
1593
|
-
:
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1842
|
+
: issue.category === 'sourceUnavailable'
|
|
1843
|
+
? origin.actionId === 'cleanup' && action.writeAction === 'cleanup'
|
|
1844
|
+
? { action: 'cleanup', scope: issue.scope }
|
|
1845
|
+
: undefined
|
|
1846
|
+
: issue.category === 'scopeConflict'
|
|
1847
|
+
? origin.actionId === 'keepUserScope'
|
|
1848
|
+
? { action: 'uninstall', scope: 'project' }
|
|
1849
|
+
: origin.actionId === 'keepProjectScope'
|
|
1850
|
+
? { action: 'uninstall', scope: 'user' }
|
|
1851
|
+
: undefined
|
|
1852
|
+
: undefined;
|
|
1597
1853
|
const dependencyOriginValid = issue.category !== 'dependencyMissing' && issue.category !== 'dependencyNotReady'
|
|
1598
1854
|
|| (origin.consumerPluginId === issue.pluginId
|
|
1599
1855
|
&& origin.consumerSourceIdentity === issue.sourceIdentity
|
|
@@ -1612,6 +1868,7 @@ export class TuiController {
|
|
|
1612
1868
|
if (this.#disposed)
|
|
1613
1869
|
return;
|
|
1614
1870
|
this.#disposed = true;
|
|
1871
|
+
this.#cancelReviewPreparation();
|
|
1615
1872
|
this.#cancelPortableRequest();
|
|
1616
1873
|
this.#abort.abort();
|
|
1617
1874
|
this.#cancelSourceRefreshes();
|
|
@@ -1644,6 +1901,7 @@ function operationResultSummary(intent, execution) {
|
|
|
1644
1901
|
requestedAgents: receipt.requestedAgents,
|
|
1645
1902
|
scope: receipt.scope,
|
|
1646
1903
|
materializationDigest: receipt.materializationDigest,
|
|
1904
|
+
skillsCliVersion: receipt.skillsCliVersion,
|
|
1647
1905
|
missing: receipt.verification.missing,
|
|
1648
1906
|
verificationUnknown,
|
|
1649
1907
|
...(receipt.failures[0]?.recovery ? { recovery: receipt.failures[0].recovery } : {}),
|
|
@@ -1691,6 +1949,9 @@ function operationResultSummary(intent, execution) {
|
|
|
1691
1949
|
relation: dependencyRelation(executionPlan, planned.pluginId),
|
|
1692
1950
|
requiredBy: planned.requiredBy,
|
|
1693
1951
|
} : {}),
|
|
1952
|
+
...(entry.errorCode === 'dependencyStillRequired'
|
|
1953
|
+
? { errorCode: 'dependencyStillRequired' }
|
|
1954
|
+
: {}),
|
|
1694
1955
|
}];
|
|
1695
1956
|
});
|
|
1696
1957
|
const inferredTargets = intent.kind === 'pluginWrite' && targets.length === 0
|