@lesliechan721/aw 0.1.0-beta.18 → 0.1.0-beta.20
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/operation-planner.d.ts +3 -3
- package/dist/src/commands/rebuild-command.js +0 -5
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +0 -1
- package/dist/src/commands/rebuild-integration.js +3 -152
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +81 -31
- package/dist/src/commands/status-v6.js +494 -183
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/wizard.d.ts +2 -2
- package/dist/src/index.js +2 -2
- package/dist/src/tui/catalog-sources/projection.d.ts +2 -2
- package/dist/src/tui/catalog-sources/screens.js +17 -10
- package/dist/src/tui/catalog-sources/screens.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +1300 -437
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +3 -3
- package/dist/src/tui/controller-runtime.js +114 -36
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +17 -7
- package/dist/src/tui/controller.js +269 -24
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph.d.ts +3 -0
- package/dist/src/tui/dependency-graph.js +37 -1
- package/dist/src/tui/dependency-graph.js.map +1 -1
- package/dist/src/tui/diagnostic-projection.d.ts +1 -0
- package/dist/src/tui/diagnostic-projection.js +51 -4
- package/dist/src/tui/diagnostic-projection.js.map +1 -1
- package/dist/src/tui/model.d.ts +22 -12
- package/dist/src/tui/model.js +251 -86
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-history.d.ts +1 -0
- package/dist/src/tui/operation-history.js +5 -0
- package/dist/src/tui/operation-history.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +67 -2
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-write-task.d.ts +2 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -1
- package/dist/src/tui/router.d.ts +6 -0
- package/dist/src/tui/router.js +56 -12
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +7 -0
- package/dist/src/utils/plugin-config-write.js +3 -3
- package/dist/src/utils/plugin-config-write.js.map +1 -1
- package/dist/src/utils/target-eligibility.d.ts +2 -2
- package/dist/src/utils/target-eligibility.js.map +1 -1
- package/package.json +3 -3
|
@@ -3,7 +3,7 @@ import type { CatalogActivationMode, CatalogSource, CatalogGenerationActiveV1, C
|
|
|
3
3
|
import type { PortableReview } from '../portable/commands.js';
|
|
4
4
|
import type { CatalogSourceUpdate } from '../utils/catalog-sources.js';
|
|
5
5
|
import type { CatalogGenerationFreshnessState, CatalogGenerationFreshnessResult } from '../utils/catalog-generation-service.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { StatusV8Payload, StatusV8PluginSummary, StatusScopedTarget, StatusTargetRevision } from '../commands/status-v6.js';
|
|
7
7
|
import type { ReconcileOperationPlan } from '../commands/reconcile-operation-plan.js';
|
|
8
8
|
import type { RebuildCommandResult } from '../commands/rebuild-command.js';
|
|
9
9
|
import type { OfflineCleanupTargetEvidence } from '../commands/offline-cleanup-evidence.js';
|
|
@@ -11,7 +11,7 @@ import type { CodexReconcilePlan } from '../utils/codex-reconciler.js';
|
|
|
11
11
|
import type { OperationPlan } from '../commands/operation-plan.js';
|
|
12
12
|
import type { RebuildProgressFeed, RebuildProgressSnapshot } from '../output/rebuild-progress.js';
|
|
13
13
|
import { type ConfigPageViewModel, type ConfigGroupViewModel, type InstallPlacementDraftViewModel, type PluginListItemViewModel, type PluginDependencyOverviewViewModel, type PortableSkillSelectionViewModel, type SourceHealthViewModel, type SourceDetailViewModel, type TuiViewModel, type UserIssueCategory, type UserIssueViewModel } from './model.js';
|
|
14
|
-
import { OperationHistoryStore, type OperationDiagnosticDetail, type OperationEffectFacts, type OperationHistorySummary, type OperationReportPreview, type SaveOperationReportResult } from './operation-history.js';
|
|
14
|
+
import { OperationHistoryStore, type OperationDiagnosticDetail, type OperationEffectFacts, type OperationHistorySummary, type OperationReportPreview, type SafeOperationDiagnostic, type SaveOperationReportResult } from './operation-history.js';
|
|
15
15
|
import { type ConfigInvalidStoreEvidence, type PluginInstallFilter, type PortableBlockerCode, type TuiPageContext, type TuiRoute, type TuiRouterSnapshot } from './router.js';
|
|
16
16
|
import { type CatalogSourceGenerationViewModel } from './catalog-generation.js';
|
|
17
17
|
import type { CatalogSourceFreshnessViewModel } from './catalog-freshness.js';
|
|
@@ -42,7 +42,11 @@ export interface PluginWriteBatchTarget {
|
|
|
42
42
|
platform: PlatformName;
|
|
43
43
|
scope: Scope;
|
|
44
44
|
}
|
|
45
|
-
export
|
|
45
|
+
export interface PluginInstallBatchTarget extends PluginWriteBatchTarget {
|
|
46
|
+
state: 'add' | 'existing' | 'blocked';
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function pluginWriteBatchTargets(action: Exclude<BatchPluginWriteAction, 'install'>, pluginIds: readonly string[], statusTargets: readonly StatusScopedTarget[]): PluginWriteBatchTarget[];
|
|
46
50
|
export interface PluginWriteBatchIntent {
|
|
47
51
|
kind: 'pluginWriteBatch';
|
|
48
52
|
action: BatchPluginWriteAction;
|
|
@@ -340,6 +344,7 @@ export interface OperationReviewModel {
|
|
|
340
344
|
version?: string;
|
|
341
345
|
};
|
|
342
346
|
changes: OperationReviewChange[];
|
|
347
|
+
confirmationDetails?: string[];
|
|
343
348
|
technicalDetails?: string[];
|
|
344
349
|
equivalentCommand?: string;
|
|
345
350
|
primaryLabel: string;
|
|
@@ -561,8 +566,8 @@ export interface PluginOperationProgressLine {
|
|
|
561
566
|
}
|
|
562
567
|
export interface TuiStatusLoadResult {
|
|
563
568
|
cliChannel?: 'latest' | 'beta';
|
|
564
|
-
payload:
|
|
565
|
-
pluginSummaries:
|
|
569
|
+
payload: StatusV8Payload;
|
|
570
|
+
pluginSummaries: StatusV8PluginSummary[];
|
|
566
571
|
targetRevisions?: StatusTargetRevision[];
|
|
567
572
|
sourceDetails?: SourceDetailViewModel[];
|
|
568
573
|
configuredSources?: CatalogSource[];
|
|
@@ -677,7 +682,8 @@ export interface TuiControllerSnapshot {
|
|
|
677
682
|
}
|
|
678
683
|
export declare class TuiOperationBlockedError extends Error {
|
|
679
684
|
readonly code: string;
|
|
680
|
-
|
|
685
|
+
readonly diagnostics?: SafeOperationDiagnostic[];
|
|
686
|
+
constructor(code: string, message: string, diagnostics?: readonly SafeOperationDiagnostic[]);
|
|
681
687
|
}
|
|
682
688
|
export declare class TuiConfigInvalidStoreError extends Error {
|
|
683
689
|
readonly code = "invalidConfigStores";
|
|
@@ -764,14 +770,17 @@ export declare class TuiController {
|
|
|
764
770
|
installFilter?: PluginInstallFilter;
|
|
765
771
|
}): PluginListItemViewModel[];
|
|
766
772
|
getPluginOverview(pluginId: string): PluginListItemViewModel | undefined;
|
|
773
|
+
getPluginSourceIdentity(pluginId: string): string | undefined;
|
|
767
774
|
listCurrentProjectIntentRoots(): string[];
|
|
768
775
|
listPluginWriteBatchTargets(action: Exclude<BatchPluginWriteAction, 'install'>, pluginIds: readonly string[]): PluginWriteBatchTarget[];
|
|
776
|
+
listPluginInstallBatchTargets(pluginIds: readonly string[], platform: PlatformName | 'both', scope: Scope): PluginInstallBatchTarget[];
|
|
777
|
+
pluginHasInstallableTarget(pluginId: string): boolean;
|
|
769
778
|
getPluginDependencyOverview(pluginId: string): PluginDependencyOverviewViewModel | undefined;
|
|
770
779
|
listPluginDependencyOverviews(): PluginDependencyOverviewViewModel[];
|
|
771
780
|
getBlockingDependents(pluginId: string, platform: PlatformName, scope: Scope): PluginDependencyOverviewViewModel['dependents'];
|
|
772
781
|
getPlatformPlacement(pluginId: string, platform: PlatformName): PluginListItemViewModel['installedPlacements'][number] | undefined;
|
|
773
782
|
listInstallTargets(pluginId: string, scope: Scope): InstallTargetOption[];
|
|
774
|
-
getTargetState(pluginId: string, platform: PlatformName, scope: Scope):
|
|
783
|
+
getTargetState(pluginId: string, platform: PlatformName, scope: Scope): StatusV8Payload['targets'][number]['state'] | undefined;
|
|
775
784
|
listSources(): TuiViewModel['sourceTabs'];
|
|
776
785
|
getSourceOverview(sourceId: string): {
|
|
777
786
|
source?: TuiViewModel['sourceTabs'][number];
|
|
@@ -794,6 +803,7 @@ export declare class TuiController {
|
|
|
794
803
|
pluginWriteTaskReturnRoute(route: Extract<TuiRoute, {
|
|
795
804
|
name: 'pluginWriteTask';
|
|
796
805
|
}>): TuiRoute;
|
|
806
|
+
returnToPluginWriteTaskBatchSelection(flowId: string): boolean;
|
|
797
807
|
retryFailedPluginWriteBatch(): ReviewPreparationSnapshot | undefined;
|
|
798
808
|
retryFailedPluginWriteTask(): ReviewPreparationSnapshot | undefined;
|
|
799
809
|
catalogActivationBlockReason(): CatalogActivationBlockReason | undefined;
|
|
@@ -2,7 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { CatalogManagementError, } from './catalog-management.js';
|
|
3
3
|
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
4
4
|
import { readSafeOperationError } from '../utils/safe-operation-diagnostic.js';
|
|
5
|
-
import { statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, } from '../commands/status-v6.js';
|
|
5
|
+
import { flattenStatusTargets, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, statusTargetSharedNative, } from '../commands/status-v6.js';
|
|
6
6
|
import { buildRebuildReceiptJson, requestedIntentAction } from '../output/command-receipt.js';
|
|
7
7
|
import { buildOperationResultModel } from '../commands/operation-planner.js';
|
|
8
8
|
import { buildOperationReviewProjection } from './operation-review-planner.js';
|
|
@@ -89,10 +89,13 @@ function reviewPreparationStageLabel(stage) {
|
|
|
89
89
|
}
|
|
90
90
|
export class TuiOperationBlockedError extends Error {
|
|
91
91
|
code;
|
|
92
|
-
|
|
92
|
+
diagnostics;
|
|
93
|
+
constructor(code, message, diagnostics) {
|
|
93
94
|
super(message);
|
|
94
95
|
this.name = 'TuiOperationBlockedError';
|
|
95
96
|
this.code = code;
|
|
97
|
+
if (diagnostics?.length)
|
|
98
|
+
this.diagnostics = [...structuredClone(diagnostics)];
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
export class TuiConfigInvalidStoreError extends Error {
|
|
@@ -545,6 +548,18 @@ export function userIssueKey(issue) {
|
|
|
545
548
|
issue.sourceId ?? '', issue.sourceIdentity ?? '', issue.platform ?? '', issue.scope ?? '']
|
|
546
549
|
.join('\0');
|
|
547
550
|
}
|
|
551
|
+
function originIssueRemains(issue, origin) {
|
|
552
|
+
if (!origin.consumerPluginId || !origin.consumerPlatform || !origin.consumerScope) {
|
|
553
|
+
return userIssueKey(issue) === origin.issueKey;
|
|
554
|
+
}
|
|
555
|
+
return issue.category === origin.category
|
|
556
|
+
&& issue.dependencyPluginId === origin.dependencyPluginId
|
|
557
|
+
&& issue.dependencyPlatform === origin.dependencyPlatform
|
|
558
|
+
&& issue.dependencyScope === origin.dependencyScope
|
|
559
|
+
&& issue.affectedTargets?.some((target) => target.pluginId === origin.consumerPluginId
|
|
560
|
+
&& target.platform === origin.consumerPlatform
|
|
561
|
+
&& target.scope === origin.consumerScope) === true;
|
|
562
|
+
}
|
|
548
563
|
function issueCode(error) {
|
|
549
564
|
const safeOperationError = readSafeOperationError(error);
|
|
550
565
|
if (safeOperationError)
|
|
@@ -923,8 +938,12 @@ export class TuiController {
|
|
|
923
938
|
}
|
|
924
939
|
#captureThrownProjection(operationId, error, code, stage, intent, effects) {
|
|
925
940
|
const sensitiveValues = intent ? collectOperationSensitiveValues(intent) : [];
|
|
941
|
+
const carried = error instanceof TuiOperationBlockedError ? error.diagnostics : undefined;
|
|
926
942
|
this.#operationDiagnostics.set(operationId, {
|
|
927
|
-
diagnostics:
|
|
943
|
+
diagnostics: carried?.length ? carried.map((diagnostic, index) => ({
|
|
944
|
+
...structuredClone(diagnostic),
|
|
945
|
+
id: `${this.#sessionEpoch}:${operationId}:failure:${index}`,
|
|
946
|
+
})) : [projectThrownOperationDiagnostic({
|
|
928
947
|
error,
|
|
929
948
|
id: `${this.#sessionEpoch}:${operationId}:failure`,
|
|
930
949
|
stage,
|
|
@@ -941,6 +960,89 @@ export class TuiController {
|
|
|
941
960
|
sensitiveValues,
|
|
942
961
|
});
|
|
943
962
|
}
|
|
963
|
+
#appendThrownProjection(operationId, error, code, stage, intent) {
|
|
964
|
+
const existing = this.#operationDiagnostics.get(operationId);
|
|
965
|
+
if (!existing) {
|
|
966
|
+
this.#captureThrownProjection(operationId, error, code, stage, intent);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const carried = error instanceof TuiOperationBlockedError ? error.diagnostics : undefined;
|
|
970
|
+
const diagnostics = carried?.length ? carried : [projectThrownOperationDiagnostic({
|
|
971
|
+
error,
|
|
972
|
+
id: `${this.#sessionEpoch}:${operationId}:failure:${existing.diagnostics.length}`,
|
|
973
|
+
stage,
|
|
974
|
+
fallbackCode: code,
|
|
975
|
+
sensitiveValues: existing.sensitiveValues,
|
|
976
|
+
})];
|
|
977
|
+
const offset = existing.diagnostics.length;
|
|
978
|
+
const appended = diagnostics.map((diagnostic, index) => ({
|
|
979
|
+
...structuredClone(diagnostic),
|
|
980
|
+
id: `${this.#sessionEpoch}:${operationId}:failure:${offset + index}`,
|
|
981
|
+
}));
|
|
982
|
+
existing.diagnostics.push(...appended);
|
|
983
|
+
const targetDiagnostics = appended.filter((diagnostic) => diagnostic.target !== undefined);
|
|
984
|
+
if (targetDiagnostics.length > 0) {
|
|
985
|
+
existing.targets = [...new Map([
|
|
986
|
+
...(existing.targets ?? []).map((target) => [[
|
|
987
|
+
target.pluginId ?? '', target.platform, target.scope,
|
|
988
|
+
].join('\0'), target]),
|
|
989
|
+
...targetDiagnostics.map((diagnostic) => [[
|
|
990
|
+
diagnostic.target.pluginId ?? '', diagnostic.target.platform, diagnostic.target.scope,
|
|
991
|
+
].join('\0'), {
|
|
992
|
+
...diagnostic.target,
|
|
993
|
+
state: 'skipped',
|
|
994
|
+
errorCode: diagnostic.code,
|
|
995
|
+
diagnosticIds: targetDiagnostics.filter((candidate) => candidate.target
|
|
996
|
+
&& candidate.target.pluginId === diagnostic.target.pluginId
|
|
997
|
+
&& candidate.target.platform === diagnostic.target.platform
|
|
998
|
+
&& candidate.target.scope === diagnostic.target.scope).map((candidate) => candidate.id),
|
|
999
|
+
}]),
|
|
1000
|
+
]).values()];
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
#recordStaleAttempt(operationId, intent) {
|
|
1004
|
+
if (!this.#operationDiagnostics.has(operationId)) {
|
|
1005
|
+
this.#captureThrownProjection(operationId, undefined, 'operationReviewStale', 'reviewValidation', intent);
|
|
1006
|
+
}
|
|
1007
|
+
const projected = this.#operationDiagnostics.get(operationId);
|
|
1008
|
+
const recordId = `${this.#sessionEpoch}:${operationId}`;
|
|
1009
|
+
const diagnosticsByTarget = new Map();
|
|
1010
|
+
for (const diagnostic of projected.diagnostics) {
|
|
1011
|
+
if (!diagnostic.target)
|
|
1012
|
+
continue;
|
|
1013
|
+
const key = [diagnostic.target.pluginId ?? '', diagnostic.target.platform, diagnostic.target.scope].join('\0');
|
|
1014
|
+
diagnosticsByTarget.set(key, [...(diagnosticsByTarget.get(key) ?? []), diagnostic]);
|
|
1015
|
+
}
|
|
1016
|
+
const targets = projected.targets ?? [...diagnosticsByTarget.entries()].map(([key, diagnostics]) => {
|
|
1017
|
+
const [pluginId, platform, scope] = key.split('\0');
|
|
1018
|
+
return {
|
|
1019
|
+
...(pluginId ? { pluginId } : {}),
|
|
1020
|
+
platform,
|
|
1021
|
+
scope,
|
|
1022
|
+
state: 'skipped',
|
|
1023
|
+
errorCode: diagnostics[0].code,
|
|
1024
|
+
diagnosticIds: diagnostics.map((diagnostic) => diagnostic.id),
|
|
1025
|
+
};
|
|
1026
|
+
});
|
|
1027
|
+
this.#operationHistory.record({
|
|
1028
|
+
summary: {
|
|
1029
|
+
recordId,
|
|
1030
|
+
epoch: this.#sessionEpoch,
|
|
1031
|
+
operationId,
|
|
1032
|
+
createdAtMs: this.dependencies.now?.() ?? Date.now(),
|
|
1033
|
+
state: 'blocked',
|
|
1034
|
+
code: projected.diagnostics[0]?.code ?? 'operationReviewStale',
|
|
1035
|
+
message: '状态已变化,原确认没有执行。已准备新的确认信息。',
|
|
1036
|
+
operationKind: intent.kind,
|
|
1037
|
+
...('pluginId' in intent ? { pluginId: intent.pluginId } : {}),
|
|
1038
|
+
diagnosticCount: projected.diagnostics.length,
|
|
1039
|
+
effects: projected.effects,
|
|
1040
|
+
},
|
|
1041
|
+
diagnostics: projected.diagnostics,
|
|
1042
|
+
targets,
|
|
1043
|
+
}, { sensitiveValues: projected.sensitiveValues });
|
|
1044
|
+
this.#operationDiagnostics.delete(operationId);
|
|
1045
|
+
}
|
|
944
1046
|
async #logErrorSafe(error, code, context) {
|
|
945
1047
|
try {
|
|
946
1048
|
await this.dependencies.logError?.(error, code, context);
|
|
@@ -1056,6 +1158,14 @@ export class TuiController {
|
|
|
1056
1158
|
}
|
|
1057
1159
|
#stableResultRoute(intent, result) {
|
|
1058
1160
|
const active = this.#router.activeFlow();
|
|
1161
|
+
if (intent.kind === 'pluginWriteTask'
|
|
1162
|
+
&& result.state === 'succeeded'
|
|
1163
|
+
&& 'returnRoute' in result
|
|
1164
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
1165
|
+
&& result.returnRoute.goal === result.summary.task?.goal
|
|
1166
|
+
&& result.returnRoute.batchOrigin?.owner === 'batch-write') {
|
|
1167
|
+
return { name: 'pluginList', mode: 'installed' };
|
|
1168
|
+
}
|
|
1059
1169
|
if (active?.parentFlowId)
|
|
1060
1170
|
return this.#normalizeRoute(active.returnTarget);
|
|
1061
1171
|
if (active && (intent.kind === 'portableInstall'
|
|
@@ -1145,7 +1255,18 @@ export class TuiController {
|
|
|
1145
1255
|
executionPolicy: 'all',
|
|
1146
1256
|
}
|
|
1147
1257
|
: undefined;
|
|
1148
|
-
|
|
1258
|
+
const batchFlowId = result.state === 'succeeded'
|
|
1259
|
+
&& intent.kind === 'pluginWriteTask'
|
|
1260
|
+
&& 'returnRoute' in result
|
|
1261
|
+
&& result.returnRoute?.name === 'pluginWriteTask'
|
|
1262
|
+
&& result.returnRoute.goal === result.summary.task?.goal
|
|
1263
|
+
&& result.returnRoute.batchOrigin?.owner === 'batch-write'
|
|
1264
|
+
? result.returnRoute.batchOrigin.flowId
|
|
1265
|
+
: undefined;
|
|
1266
|
+
const finished = batchFlowId
|
|
1267
|
+
? this.#router.finishFlowById(batchFlowId, 'batchWrite', target, terminal)
|
|
1268
|
+
: this.#router.finishFlow(target, terminal);
|
|
1269
|
+
if (!finished)
|
|
1149
1270
|
this.#router.replace(terminal);
|
|
1150
1271
|
}
|
|
1151
1272
|
#applyStatus(status) {
|
|
@@ -1900,15 +2021,93 @@ export class TuiController {
|
|
|
1900
2021
|
const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
|
|
1901
2022
|
return plugin ? cloneFrozen(plugin) : undefined;
|
|
1902
2023
|
}
|
|
2024
|
+
getPluginSourceIdentity(pluginId) {
|
|
2025
|
+
return flattenStatusTargets(this.#status?.payload.targets ?? []).find((target) => target.pluginId === pluginId)?.sourceIdentity;
|
|
2026
|
+
}
|
|
1903
2027
|
listCurrentProjectIntentRoots() {
|
|
1904
|
-
return cloneFrozen([...new Set((this.#status?.payload.targets ?? []).flatMap((target) => {
|
|
2028
|
+
return cloneFrozen([...new Set(flattenStatusTargets(this.#status?.payload.targets ?? []).flatMap((target) => {
|
|
1905
2029
|
const intent = statusTargetIntent(target);
|
|
1906
2030
|
return intent.relation === 'root' && intent.scopes.includes('project')
|
|
1907
2031
|
? [target.pluginId] : [];
|
|
1908
2032
|
}))].sort((left, right) => left.localeCompare(right, 'en')));
|
|
1909
2033
|
}
|
|
1910
2034
|
listPluginWriteBatchTargets(action, pluginIds) {
|
|
1911
|
-
return cloneFrozen(pluginWriteBatchTargets(action, pluginIds, this.#status?.payload.targets ?? []));
|
|
2035
|
+
return cloneFrozen(pluginWriteBatchTargets(action, pluginIds, flattenStatusTargets(this.#status?.payload.targets ?? [])));
|
|
2036
|
+
}
|
|
2037
|
+
listPluginInstallBatchTargets(pluginIds, platform, scope) {
|
|
2038
|
+
const platforms = platform === 'both' ? ['codex', 'claude'] : [platform];
|
|
2039
|
+
return cloneFrozen([...new Set(pluginIds)].flatMap((pluginId) => {
|
|
2040
|
+
const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
|
|
2041
|
+
return platforms.map((targetPlatform) => {
|
|
2042
|
+
const target = { pluginId, platform: targetPlatform, scope };
|
|
2043
|
+
const statusTargets = flattenStatusTargets(this.#status?.payload.targets ?? []).filter((entry) => entry.pluginId === pluginId && entry.platform === targetPlatform);
|
|
2044
|
+
const exactTarget = statusTargets.find((entry) => entry.scope === scope);
|
|
2045
|
+
const alreadyConfigured = statusTargets.some((entry) => {
|
|
2046
|
+
const activation = statusTargetActivation(entry);
|
|
2047
|
+
return statusTargetIntent(entry).scopes.includes(scope)
|
|
2048
|
+
|| (targetPlatform === 'codex'
|
|
2049
|
+
&& activation?.evidence === 'observed'
|
|
2050
|
+
&& activation[scope] === true);
|
|
2051
|
+
});
|
|
2052
|
+
const exactOwnership = exactTarget ? statusTargetOwnershipState(exactTarget) : 'absent';
|
|
2053
|
+
if (alreadyConfigured || exactOwnership === 'present' || exactOwnership === 'partial') {
|
|
2054
|
+
return { ...target, state: 'existing' };
|
|
2055
|
+
}
|
|
2056
|
+
if (!plugin)
|
|
2057
|
+
return { ...target, state: 'blocked', reason: '插件已不在当前状态中' };
|
|
2058
|
+
if (plugin.configOnly) {
|
|
2059
|
+
return { ...target, state: 'blocked', reason: '来源中没有可信插件定义' };
|
|
2060
|
+
}
|
|
2061
|
+
if (plugin.sourceUnavailable) {
|
|
2062
|
+
return { ...target, state: 'blocked', reason: '插件来源不可用' };
|
|
2063
|
+
}
|
|
2064
|
+
if (plugin.capabilityUnsupported) {
|
|
2065
|
+
return { ...target, state: 'blocked', reason: '当前 AW CLI 不支持插件所需功能' };
|
|
2066
|
+
}
|
|
2067
|
+
const option = this.listInstallTargets(pluginId, scope)
|
|
2068
|
+
.find((entry) => entry.platform === targetPlatform);
|
|
2069
|
+
if (!option?.compatible) {
|
|
2070
|
+
return { ...target, state: 'blocked', reason: option?.disabled ?? '插件不支持所选工具或位置' };
|
|
2071
|
+
}
|
|
2072
|
+
const representativeTarget = exactTarget
|
|
2073
|
+
?? (targetPlatform === 'codex' ? statusTargets[0] : undefined);
|
|
2074
|
+
const uncertainObservation = (representativeTarget
|
|
2075
|
+
&& statusTargetActivation(representativeTarget)?.evidence === 'unobserved')
|
|
2076
|
+
|| statusTargets.some((entry) => statusTargetSharedNative(entry)?.state === 'unobserved');
|
|
2077
|
+
if (uncertainObservation) {
|
|
2078
|
+
return { ...target, state: 'blocked', reason: '无法确认所选位置是否缺失' };
|
|
2079
|
+
}
|
|
2080
|
+
if (!representativeTarget || representativeTarget.state === 'blocked') {
|
|
2081
|
+
return {
|
|
2082
|
+
...target,
|
|
2083
|
+
state: 'blocked',
|
|
2084
|
+
reason: representativeTarget?.reasons.find((reason) => reason.severity === 'blocking')?.message
|
|
2085
|
+
?? '无法确认所选位置是否缺失',
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
if (targetPlatform === 'codex') {
|
|
2089
|
+
const intent = statusTargetIntent(representativeTarget);
|
|
2090
|
+
const activation = statusTargetActivation(representativeTarget);
|
|
2091
|
+
const confirmedMissing = activation?.evidence === 'observed' && activation[scope] === false
|
|
2092
|
+
|| intent.enabled && !intent.scopes.includes(scope)
|
|
2093
|
+
|| representativeTarget.state === 'absent'
|
|
2094
|
+
&& statusTargetOwnershipState(representativeTarget) === 'absent'
|
|
2095
|
+
&& !intent.enabled;
|
|
2096
|
+
if (!confirmedMissing) {
|
|
2097
|
+
return { ...target, state: 'blocked', reason: '无法确认所选位置是否缺失' };
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
else if (representativeTarget.state !== 'absent') {
|
|
2101
|
+
return { ...target, state: 'blocked', reason: '无法确认所选位置是否缺失' };
|
|
2102
|
+
}
|
|
2103
|
+
return { ...target, state: 'add' };
|
|
2104
|
+
});
|
|
2105
|
+
}).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
2106
|
+
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')));
|
|
2107
|
+
}
|
|
2108
|
+
pluginHasInstallableTarget(pluginId) {
|
|
2109
|
+
return ['project', 'user'].some((scope) => this.listPluginInstallBatchTargets([pluginId], 'both', scope)
|
|
2110
|
+
.some((target) => target.state === 'add'));
|
|
1912
2111
|
}
|
|
1913
2112
|
getPluginDependencyOverview(pluginId) {
|
|
1914
2113
|
const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
|
|
@@ -1918,6 +2117,7 @@ export class TuiController {
|
|
|
1918
2117
|
return cloneFrozen(this.#viewModel?.dependencyOverviews ?? []);
|
|
1919
2118
|
}
|
|
1920
2119
|
getBlockingDependents(pluginId, platform, scope) {
|
|
2120
|
+
const statusTargets = flattenStatusTargets(this.#status?.payload.targets ?? []);
|
|
1921
2121
|
const dependents = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId)?.dependents ?? [];
|
|
1922
2122
|
return cloneFrozen(dependents.flatMap((dependent) => {
|
|
1923
2123
|
const references = (dependent.placementReferences ?? []).filter((reference) => reference.dependency.pluginId === pluginId
|
|
@@ -1925,7 +2125,7 @@ export class TuiController {
|
|
|
1925
2125
|
&& reference.dependency.scope === scope
|
|
1926
2126
|
&& (scope === 'user' || reference.dependency.project === 'current')
|
|
1927
2127
|
&& (reference.consumer.project === 'other'
|
|
1928
|
-
||
|
|
2128
|
+
|| statusTargets.find((target) => target.pluginId === reference.consumer.pluginId
|
|
1929
2129
|
&& target.platform === reference.consumer.platform
|
|
1930
2130
|
&& target.scope === reference.consumer.scope)?.state !== 'absent'));
|
|
1931
2131
|
return references.length > 0 ? [{ ...dependent, placementReferences: references }] : [];
|
|
@@ -1946,7 +2146,8 @@ export class TuiController {
|
|
|
1946
2146
|
}));
|
|
1947
2147
|
}
|
|
1948
2148
|
return plugin.supportedPlatforms.map((platform) => {
|
|
1949
|
-
const
|
|
2149
|
+
const allStatusTargets = flattenStatusTargets(this.#status?.payload.targets ?? []);
|
|
2150
|
+
const platformTargets = allStatusTargets.filter((target) => target.pluginId === pluginId && target.platform === platform) ?? [];
|
|
1950
2151
|
const rootSummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === pluginId);
|
|
1951
2152
|
const rootSupportedScopes = rootSummary?.supportedTargets?.[platform]
|
|
1952
2153
|
?? platformTargets.filter((target) => !target.reasons.some((error) => error.code === 'unsupportedTarget')).map((target) => target.scope);
|
|
@@ -1962,7 +2163,7 @@ export class TuiController {
|
|
|
1962
2163
|
for (const dependency of overview?.dependencies ?? []) {
|
|
1963
2164
|
const dependencySummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === dependency.pluginId);
|
|
1964
2165
|
const allowedScopes = dependencySummary?.supportedTargets?.[platform]
|
|
1965
|
-
?? (
|
|
2166
|
+
?? (allStatusTargets.filter((target) => target.pluginId === dependency.pluginId
|
|
1966
2167
|
&& target.platform === platform
|
|
1967
2168
|
&& !target.reasons.some((error) => error.code === 'unsupportedTarget'))
|
|
1968
2169
|
.map((target) => target.scope) ?? []);
|
|
@@ -1975,7 +2176,7 @@ export class TuiController {
|
|
|
1975
2176
|
};
|
|
1976
2177
|
}
|
|
1977
2178
|
const collisionAtEveryViableScope = viableScopes.every((candidateScope) => {
|
|
1978
|
-
const target =
|
|
2179
|
+
const target = allStatusTargets.find((entry) => entry.pluginId === dependency.pluginId
|
|
1979
2180
|
&& entry.platform === platform
|
|
1980
2181
|
&& entry.scope === candidateScope);
|
|
1981
2182
|
return Boolean(target && targetHasArtifactCollision(target));
|
|
@@ -1992,7 +2193,7 @@ export class TuiController {
|
|
|
1992
2193
|
});
|
|
1993
2194
|
}
|
|
1994
2195
|
getTargetState(pluginId, platform, scope) {
|
|
1995
|
-
return this.#status?.payload.targets.find((target) => target.pluginId === pluginId
|
|
2196
|
+
return flattenStatusTargets(this.#status?.payload.targets ?? []).find((target) => target.pluginId === pluginId
|
|
1996
2197
|
&& target.platform === platform
|
|
1997
2198
|
&& target.scope === scope)?.state;
|
|
1998
2199
|
}
|
|
@@ -2075,6 +2276,23 @@ export class TuiController {
|
|
|
2075
2276
|
? active.returnTarget
|
|
2076
2277
|
: route);
|
|
2077
2278
|
}
|
|
2279
|
+
returnToPluginWriteTaskBatchSelection(flowId) {
|
|
2280
|
+
this.#assertAcceptingActions();
|
|
2281
|
+
const route = this.#router.snapshot.current.route;
|
|
2282
|
+
if (route.name !== 'review'
|
|
2283
|
+
|| this.#activeReview?.operationId !== route.operationId
|
|
2284
|
+
|| this.#activeReview.operationKind !== 'pluginWriteTask')
|
|
2285
|
+
return false;
|
|
2286
|
+
const reviewToken = this.#activeReview.reviewToken;
|
|
2287
|
+
const returned = this.#router.returnToBatchSelection(flowId);
|
|
2288
|
+
if (!returned)
|
|
2289
|
+
return false;
|
|
2290
|
+
this.#cancelReviewPreparation();
|
|
2291
|
+
this.#tickets.delete(reviewToken);
|
|
2292
|
+
this.#activeReview = undefined;
|
|
2293
|
+
this.#publish();
|
|
2294
|
+
return true;
|
|
2295
|
+
}
|
|
2078
2296
|
retryFailedPluginWriteBatch() {
|
|
2079
2297
|
if (!this.#batchRetryIntent)
|
|
2080
2298
|
return undefined;
|
|
@@ -2496,13 +2714,24 @@ export class TuiController {
|
|
|
2496
2714
|
? '本次加载时 Catalog 来源不可用。请刷新来源并重新加载后重试。'
|
|
2497
2715
|
: '无法准备确认信息。请返回检查选择,或重试。';
|
|
2498
2716
|
const preparationRecordId = `${this.#sessionEpoch}:${active.snapshot.id}`;
|
|
2499
|
-
const
|
|
2500
|
-
error
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2717
|
+
const carriedDiagnostics = error instanceof TuiOperationBlockedError
|
|
2718
|
+
? error.diagnostics : undefined;
|
|
2719
|
+
if (carriedDiagnostics?.length) {
|
|
2720
|
+
active.snapshot.message = `无法准备确认信息:${carriedDiagnostics.slice(0, 3)
|
|
2721
|
+
.map((diagnostic) => sanitizeDiagnosticText(`${diagnostic.target?.pluginId ? `${diagnostic.target.pluginId}:` : ''}${diagnostic.safeMessage}(${safeDiagnosticCode(diagnostic.code, 'operationBlocked')})`, [], collectOperationSensitiveValues(intent))).join(';')}`;
|
|
2722
|
+
}
|
|
2723
|
+
const preparationDiagnostics = carriedDiagnostics?.length
|
|
2724
|
+
? carriedDiagnostics.map((diagnostic, index) => ({
|
|
2725
|
+
...structuredClone(diagnostic),
|
|
2726
|
+
id: `${preparationRecordId}:failure:${index}`,
|
|
2727
|
+
}))
|
|
2728
|
+
: [projectThrownOperationDiagnostic({
|
|
2729
|
+
error,
|
|
2730
|
+
id: `${preparationRecordId}:failure`,
|
|
2731
|
+
stage: active.snapshot.currentStage ?? 'reviewPreparation',
|
|
2732
|
+
fallbackCode: code,
|
|
2733
|
+
sensitiveValues: collectOperationSensitiveValues(intent),
|
|
2734
|
+
})];
|
|
2506
2735
|
this.#operationHistory.record({
|
|
2507
2736
|
summary: {
|
|
2508
2737
|
recordId: preparationRecordId,
|
|
@@ -2510,18 +2739,29 @@ export class TuiController {
|
|
|
2510
2739
|
operationId: active.snapshot.id,
|
|
2511
2740
|
createdAtMs: this.dependencies.now?.() ?? Date.now(),
|
|
2512
2741
|
state: 'blocked',
|
|
2513
|
-
code:
|
|
2742
|
+
code: preparationDiagnostics[0].code,
|
|
2514
2743
|
message: active.snapshot.message,
|
|
2515
2744
|
operationKind: intent.kind,
|
|
2516
2745
|
...('pluginId' in intent ? { pluginId: intent.pluginId } : {}),
|
|
2517
|
-
diagnosticCount:
|
|
2746
|
+
diagnosticCount: preparationDiagnostics.length,
|
|
2518
2747
|
effects: {
|
|
2519
2748
|
configCommit: 'notApplicable', awWrite: 'notStarted',
|
|
2520
2749
|
preApplyExternalEffect: 'notApplicable', finalVerification: 'notStarted',
|
|
2521
2750
|
},
|
|
2522
2751
|
},
|
|
2523
|
-
diagnostics:
|
|
2524
|
-
targets: [
|
|
2752
|
+
diagnostics: preparationDiagnostics,
|
|
2753
|
+
targets: [...new Map(preparationDiagnostics.flatMap((diagnostic) => diagnostic.target ? [[
|
|
2754
|
+
[diagnostic.target.pluginId ?? '', diagnostic.target.platform, diagnostic.target.scope].join('\0'),
|
|
2755
|
+
{
|
|
2756
|
+
...diagnostic.target,
|
|
2757
|
+
state: 'skipped',
|
|
2758
|
+
errorCode: diagnostic.code,
|
|
2759
|
+
diagnosticIds: preparationDiagnostics.filter((candidate) => candidate.target
|
|
2760
|
+
&& candidate.target.pluginId === diagnostic.target.pluginId
|
|
2761
|
+
&& candidate.target.platform === diagnostic.target.platform
|
|
2762
|
+
&& candidate.target.scope === diagnostic.target.scope).map((candidate) => candidate.id),
|
|
2763
|
+
},
|
|
2764
|
+
]] : [])).values()],
|
|
2525
2765
|
}, { sensitiveValues: collectOperationSensitiveValues(intent) });
|
|
2526
2766
|
this.#publish();
|
|
2527
2767
|
await this.#logOperationError(error, code, intent);
|
|
@@ -3025,7 +3265,7 @@ export class TuiController {
|
|
|
3025
3265
|
}
|
|
3026
3266
|
const originIssue = ticket.intent.kind === 'pluginWrite' ? ticket.intent.originIssue : undefined;
|
|
3027
3267
|
if (originIssue && !dependencyPartial
|
|
3028
|
-
&& this.#viewModel?.issues.some((issue) =>
|
|
3268
|
+
&& this.#viewModel?.issues.some((issue) => originIssueRemains(issue, originIssue))) {
|
|
3029
3269
|
return this.#setBlocked(operationId, 'issueStillPresent', {
|
|
3030
3270
|
intent: ticket.intent,
|
|
3031
3271
|
navigation: 'result',
|
|
@@ -3133,7 +3373,6 @@ export class TuiController {
|
|
|
3133
3373
|
if (!this.#operationIsCurrent(generation))
|
|
3134
3374
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
3135
3375
|
if (code === 'operationReviewStale' || code === 'catalogGenerationReviewStale') {
|
|
3136
|
-
this.#operationDiagnostics.delete(operationId);
|
|
3137
3376
|
return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
|
|
3138
3377
|
}
|
|
3139
3378
|
if (ticket.intent.kind === 'catalogManagement') {
|
|
@@ -3254,6 +3493,7 @@ export class TuiController {
|
|
|
3254
3493
|
}
|
|
3255
3494
|
catch (error) {
|
|
3256
3495
|
const code = issueCode(error);
|
|
3496
|
+
this.#appendThrownProjection(operationId, error, code, 'reviewPreparation', ticket.intent);
|
|
3257
3497
|
await this.#logOperationError(error, code, ticket.intent);
|
|
3258
3498
|
return this.#setBlocked(operationId, code, {
|
|
3259
3499
|
intent: ticket.intent,
|
|
@@ -3267,6 +3507,7 @@ export class TuiController {
|
|
|
3267
3507
|
review,
|
|
3268
3508
|
...this.#operationIdentity(ticket.intent),
|
|
3269
3509
|
};
|
|
3510
|
+
this.#recordStaleAttempt(operationId, ticket.intent);
|
|
3270
3511
|
this.#result = result;
|
|
3271
3512
|
this.#publish();
|
|
3272
3513
|
return cloneFrozen(result);
|
|
@@ -3282,6 +3523,7 @@ export class TuiController {
|
|
|
3282
3523
|
catch (error) {
|
|
3283
3524
|
if (!this.#operationIsCurrent(generation))
|
|
3284
3525
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
3526
|
+
this.#appendThrownProjection(operationId, error, 'tuiStateReloadFailed', 'statusReload', ticket.intent);
|
|
3285
3527
|
await this.#logErrorSafe(error, 'tuiStateReloadFailed', { privateConfigOperation: true });
|
|
3286
3528
|
return this.#setBlocked(operationId, 'tuiStateReloadFailed', {
|
|
3287
3529
|
intent: ticket.intent,
|
|
@@ -3297,6 +3539,7 @@ export class TuiController {
|
|
|
3297
3539
|
if (!this.#operationIsCurrent(generation))
|
|
3298
3540
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
3299
3541
|
const code = issueCode(error);
|
|
3542
|
+
this.#appendThrownProjection(operationId, error, code, 'validate', intent);
|
|
3300
3543
|
await this.#logOperationError(error, code, intent);
|
|
3301
3544
|
if (!this.#operationIsCurrent(generation))
|
|
3302
3545
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
@@ -3325,6 +3568,7 @@ export class TuiController {
|
|
|
3325
3568
|
if (!this.#operationIsCurrent(generation))
|
|
3326
3569
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
3327
3570
|
const code = issueCode(error);
|
|
3571
|
+
this.#appendThrownProjection(operationId, error, code, 'reviewPreparation', intent);
|
|
3328
3572
|
await this.#logOperationError(error, code, intent);
|
|
3329
3573
|
if (!this.#operationIsCurrent(generation))
|
|
3330
3574
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
@@ -3349,6 +3593,7 @@ export class TuiController {
|
|
|
3349
3593
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
3350
3594
|
const review = this.#storeOperationReview(intent, evidence, 'replace', ticket.returnRoute);
|
|
3351
3595
|
const result = { state: 'stale', operationId, review, ...this.#operationIdentity(intent) };
|
|
3596
|
+
this.#recordStaleAttempt(operationId, intent);
|
|
3352
3597
|
this.#result = result;
|
|
3353
3598
|
this.#publish();
|
|
3354
3599
|
return cloneFrozen(result);
|