@lesliechan721/aw 0.1.0-beta.12 → 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/rebuild-integration.js +188 -67
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/reconcile-operation-plan.d.ts +11 -9
- package/dist/src/commands/reconcile-operation-plan.js +25 -6
- package/dist/src/commands/reconcile-operation-plan.js.map +1 -1
- package/dist/src/commands/status-v6.js +2 -2
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +2 -1
- package/dist/src/commands/write-commands.js +24 -1
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/tui/catalog-management-screens.d.ts +45 -0
- package/dist/src/tui/catalog-management-screens.js +25 -0
- package/dist/src/tui/catalog-management-screens.js.map +1 -0
- package/dist/src/tui/catalog-management.d.ts +240 -0
- package/dist/src/tui/catalog-management.js +856 -0
- package/dist/src/tui/catalog-management.js.map +1 -0
- package/dist/src/tui/catalog-sources/session.d.ts +5 -0
- package/dist/src/tui/catalog-sources/session.js +49 -1
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-sources/types.d.ts +11 -0
- package/dist/src/tui/console-app-v4.d.ts +1 -1
- package/dist/src/tui/console-app-v4.js +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 +179 -17
- 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/dist/src/utils/codex-shared-native.d.ts +4 -0
- package/dist/src/utils/codex-shared-native.js +13 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +14 -1
- package/dist/src/utils/local-marketplace.js +242 -14
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import { CatalogManagementError, } from './catalog-management.js';
|
|
2
3
|
import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
|
|
3
4
|
import { readSafeOperationError } from '../utils/safe-operation-diagnostic.js';
|
|
4
5
|
import { buildRebuildReceiptJson, requestedIntentAction } from '../output/command-receipt.js';
|
|
@@ -13,6 +14,7 @@ import { TuiRouter, } from './router.js';
|
|
|
13
14
|
import { normalizeTuiRoute } from './screen-registry.js';
|
|
14
15
|
import { CatalogSourcesSession } from './catalog-sources/session.js';
|
|
15
16
|
import { catalogSourceAutoCheckOnlyValue, projectCatalogSourceMutationWarnings, } from './catalog-sources/review.js';
|
|
17
|
+
import { buildPluginWriteTaskCandidates, pluginWriteTaskTargetKey, } from './plugin-write-task.js';
|
|
16
18
|
export const PLUGIN_WRITE_BATCH_ACTIONS = [
|
|
17
19
|
'install',
|
|
18
20
|
'sync',
|
|
@@ -70,10 +72,25 @@ function canonicalBatchTargets(targets) {
|
|
|
70
72
|
return [...byKey.values()].sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
|
|
71
73
|
.localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'));
|
|
72
74
|
}
|
|
75
|
+
function canonicalPluginWriteTaskTargets(targets) {
|
|
76
|
+
return [...new Map(targets.map((target) => [pluginWriteTaskTargetKey(target), target])).values()]
|
|
77
|
+
.sort((left, right) => pluginWriteTaskTargetKey(left)
|
|
78
|
+
.localeCompare(pluginWriteTaskTargetKey(right), 'en'));
|
|
79
|
+
}
|
|
73
80
|
function normalizeOperationIntent(intent) {
|
|
74
81
|
if (intent.kind === 'configSave' && intent.retryApply && !intent.apply) {
|
|
75
82
|
return { ...intent, apply: true };
|
|
76
83
|
}
|
|
84
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
85
|
+
return { ...intent, targets: canonicalPluginWriteTaskTargets(intent.targets) };
|
|
86
|
+
}
|
|
87
|
+
if (intent.kind === 'catalogManagement' && intent.action === 'storePrune') {
|
|
88
|
+
const items = [...new Map(intent.items.map((item) => [
|
|
89
|
+
`${item.category}\0${item.storeKey}`,
|
|
90
|
+
item,
|
|
91
|
+
])).values()].sort((left, right) => `${left.category}\0${left.storeKey}`.localeCompare(`${right.category}\0${right.storeKey}`, 'en'));
|
|
92
|
+
return { ...intent, items };
|
|
93
|
+
}
|
|
77
94
|
if (intent.kind !== 'pluginWriteBatch')
|
|
78
95
|
return intent;
|
|
79
96
|
if (intent.selection.kind === 'targets') {
|
|
@@ -92,6 +109,49 @@ function normalizeOperationIntent(intent) {
|
|
|
92
109
|
},
|
|
93
110
|
};
|
|
94
111
|
}
|
|
112
|
+
function assertPluginWriteTaskEvidence(intent, evidence) {
|
|
113
|
+
const candidate = evidence;
|
|
114
|
+
const plan = candidate.taskPlan;
|
|
115
|
+
if (!plan || plan.goal !== intent.goal || !Array.isArray(plan.requestedTargets)
|
|
116
|
+
|| !Array.isArray(plan.executableTargets) || !Array.isArray(plan.blockedTargets)
|
|
117
|
+
|| !Array.isArray(plan.causes) || !Array.isArray(plan.sources)
|
|
118
|
+
|| typeof plan.targetFingerprint !== 'string' || typeof plan.planFingerprint !== 'string'
|
|
119
|
+
|| JSON.stringify(plan.requestedTargets) !== JSON.stringify(intent.targets)
|
|
120
|
+
|| plan.executableTargets.length === 0) {
|
|
121
|
+
throw new TuiOperationBlockedError('taskEvidenceInvalid', '任务确认信息不完整。');
|
|
122
|
+
}
|
|
123
|
+
if (plan.planFingerprint !== operationEvidenceFingerprint(evidence)) {
|
|
124
|
+
throw new TuiOperationBlockedError('taskEvidenceInvalid', '任务确认指纹无效。');
|
|
125
|
+
}
|
|
126
|
+
if (intent.executionPolicy === 'all' && plan.blockedTargets.length > 0) {
|
|
127
|
+
throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理,不能作为完整任务执行。');
|
|
128
|
+
}
|
|
129
|
+
const requested = new Set(plan.requestedTargets.map(pluginWriteTaskTargetKey));
|
|
130
|
+
const executable = new Set(plan.executableTargets.map(pluginWriteTaskTargetKey));
|
|
131
|
+
const blocked = new Set(plan.blockedTargets.map((target) => pluginWriteTaskTargetKey(target.target)));
|
|
132
|
+
if (executable.size !== plan.executableTargets.length || blocked.size !== plan.blockedTargets.length
|
|
133
|
+
|| [...executable].some((key) => !requested.has(key) || blocked.has(key))
|
|
134
|
+
|| [...blocked].some((key) => !requested.has(key))
|
|
135
|
+
|| [...requested].some((key) => !executable.has(key) && !blocked.has(key))) {
|
|
136
|
+
throw new TuiOperationBlockedError('taskEvidenceInvalid', '任务目标分区无效。');
|
|
137
|
+
}
|
|
138
|
+
const grouped = new Set();
|
|
139
|
+
for (const source of plan.sources) {
|
|
140
|
+
if (!source.sourceId || !source.sourceIdentity || source.requestedTargets.length === 0
|
|
141
|
+
|| source.executable !== Boolean(source.preview.operationPlan)
|
|
142
|
+
|| source.requestedTargets.some((target) => !executable.has(pluginWriteTaskTargetKey(target))
|
|
143
|
+
|| sourceIdFromPluginId(target.pluginId) !== source.sourceId)
|
|
144
|
+
|| source.causes.some((cause) => cause.sourceId !== source.sourceId
|
|
145
|
+
|| cause.sourceIdentity !== source.sourceIdentity
|
|
146
|
+
|| cause.consumers.some((consumer) => !executable.has(pluginWriteTaskTargetKey(consumer))))) {
|
|
147
|
+
throw new TuiOperationBlockedError('taskEvidenceInvalid', '任务来源计划无效。');
|
|
148
|
+
}
|
|
149
|
+
source.requestedTargets.forEach((target) => grouped.add(pluginWriteTaskTargetKey(target)));
|
|
150
|
+
}
|
|
151
|
+
if ([...executable].some((key) => !grouped.has(key))) {
|
|
152
|
+
throw new TuiOperationBlockedError('taskEvidenceInvalid', '任务来源计划缺少可执行目标。');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
95
155
|
function assertPluginWriteBatchEvidence(evidence) {
|
|
96
156
|
const candidate = evidence;
|
|
97
157
|
if (!Array.isArray(candidate.requestedPluginIds) || !Array.isArray(candidate.groups)) {
|
|
@@ -195,7 +255,62 @@ export function pluginWriteBatchFailedRequestedPluginIds(execution) {
|
|
|
195
255
|
return canonicalPluginIds(pluginWriteBatchFailedRequestedTargets(execution)
|
|
196
256
|
.map((target) => target.pluginId));
|
|
197
257
|
}
|
|
258
|
+
export function pluginWriteTaskFailedRequestedTargets(execution) {
|
|
259
|
+
const requested = new Map(execution.taskPlan.requestedTargets.map((target) => [
|
|
260
|
+
pluginWriteTaskTargetKey(target), target,
|
|
261
|
+
]));
|
|
262
|
+
const failed = new Set();
|
|
263
|
+
const succeeded = new Set();
|
|
264
|
+
for (const source of execution.sourceResults) {
|
|
265
|
+
const sourceRequested = new Set(source.requestedTargets.map(pluginWriteTaskTargetKey));
|
|
266
|
+
if (source.result.targetResults.length === 0 && source.result.overallState !== 'succeeded') {
|
|
267
|
+
sourceRequested.forEach((key) => failed.add(key));
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
for (const result of source.result.targetResults) {
|
|
271
|
+
const pluginId = result.target.pluginId;
|
|
272
|
+
if (!pluginId)
|
|
273
|
+
continue;
|
|
274
|
+
const exactKey = pluginWriteTaskTargetKey({
|
|
275
|
+
pluginId,
|
|
276
|
+
platform: result.target.platform,
|
|
277
|
+
scope: result.target.scope,
|
|
278
|
+
});
|
|
279
|
+
if (sourceRequested.has(exactKey)) {
|
|
280
|
+
if (result.state === 'failed' || result.action === 'skipped')
|
|
281
|
+
failed.add(exactKey);
|
|
282
|
+
else
|
|
283
|
+
succeeded.add(exactKey);
|
|
284
|
+
}
|
|
285
|
+
if (result.state !== 'failed' && result.action !== 'skipped')
|
|
286
|
+
continue;
|
|
287
|
+
for (const cause of source.requestedTargets.length > 0
|
|
288
|
+
? execution.taskPlan.causes.filter((cause) => cause.sourceId === source.sourceId) : []) {
|
|
289
|
+
if (pluginWriteTaskTargetKey(cause.repairTarget) !== exactKey)
|
|
290
|
+
continue;
|
|
291
|
+
cause.consumers.forEach((consumer) => failed.add(pluginWriteTaskTargetKey(consumer)));
|
|
292
|
+
}
|
|
293
|
+
const placement = source.result.dependencyPlan?.targets.find((target) => target.pluginId === pluginId && target.platform === result.target.platform
|
|
294
|
+
&& (target.requestedScope ?? target.resolvedScope) === result.target.scope);
|
|
295
|
+
for (const consumer of placement?.requiredByTargets ?? []) {
|
|
296
|
+
failed.add(pluginWriteTaskTargetKey(consumer));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const key of succeeded)
|
|
301
|
+
failed.delete(key);
|
|
302
|
+
return canonicalPluginWriteTaskTargets([...failed].flatMap((key) => {
|
|
303
|
+
const target = requested.get(key);
|
|
304
|
+
return target ? [target] : [];
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
198
307
|
function collectOperationSensitiveValues(intent) {
|
|
308
|
+
if (intent.kind === 'catalogManagement') {
|
|
309
|
+
return [
|
|
310
|
+
...('ownerUrl' in intent && intent.ownerUrl ? [intent.ownerUrl] : []),
|
|
311
|
+
...('remoteUrl' in intent && intent.remoteUrl ? [intent.remoteUrl] : []),
|
|
312
|
+
];
|
|
313
|
+
}
|
|
199
314
|
if (intent.kind !== 'configSave')
|
|
200
315
|
return [];
|
|
201
316
|
const sensitiveKeys = new Set([...(intent.sensitiveKeys ?? []), ...(intent.privateKeys ?? [])]);
|
|
@@ -243,6 +358,53 @@ function cloneFrozen(value) {
|
|
|
243
358
|
return deepFreeze(structuredClone(value));
|
|
244
359
|
}
|
|
245
360
|
export function operationEvidenceFingerprint(evidence) {
|
|
361
|
+
if (evidence.catalogManagement) {
|
|
362
|
+
const management = evidence.catalogManagement;
|
|
363
|
+
return crypto.createHash('sha256')
|
|
364
|
+
.update(JSON.stringify(stableValue(management.action === 'storePrune'
|
|
365
|
+
? {
|
|
366
|
+
action: management.action,
|
|
367
|
+
workspaceBinding: management.workspaceBinding,
|
|
368
|
+
authorizedStoreItems: management.authorizedStoreItems,
|
|
369
|
+
}
|
|
370
|
+
: {
|
|
371
|
+
action: management.action,
|
|
372
|
+
workspaceBinding: management.workspaceBinding,
|
|
373
|
+
evidenceDigest: management.evidenceDigest,
|
|
374
|
+
})))
|
|
375
|
+
.digest('hex');
|
|
376
|
+
}
|
|
377
|
+
if ('taskPlan' in evidence) {
|
|
378
|
+
const task = evidence.taskPlan;
|
|
379
|
+
return crypto.createHash('sha256')
|
|
380
|
+
.update(JSON.stringify(stableValue({
|
|
381
|
+
catalog: evidence.catalog,
|
|
382
|
+
goal: task.goal,
|
|
383
|
+
requestedTargets: task.requestedTargets,
|
|
384
|
+
executableTargets: task.executableTargets,
|
|
385
|
+
causes: task.causes,
|
|
386
|
+
blockedTargets: task.blockedTargets,
|
|
387
|
+
targetFingerprint: task.targetFingerprint,
|
|
388
|
+
sources: task.sources.map((source) => ({
|
|
389
|
+
sourceId: source.sourceId,
|
|
390
|
+
sourceIdentity: source.sourceIdentity,
|
|
391
|
+
requestedTargets: source.requestedTargets,
|
|
392
|
+
causes: source.causes,
|
|
393
|
+
executable: source.executable,
|
|
394
|
+
planFingerprint: source.preview.operationPlan?.planFingerprint,
|
|
395
|
+
targetResults: source.preview.targetResults.map((target) => ({
|
|
396
|
+
pluginId: target.target.pluginId,
|
|
397
|
+
platform: target.target.platform,
|
|
398
|
+
scope: target.target.scope,
|
|
399
|
+
state: target.state,
|
|
400
|
+
action: target.action,
|
|
401
|
+
errorCode: target.errorCode,
|
|
402
|
+
requiredBy: target.requiredBy,
|
|
403
|
+
})),
|
|
404
|
+
})),
|
|
405
|
+
})))
|
|
406
|
+
.digest('hex');
|
|
407
|
+
}
|
|
246
408
|
if ('groups' in evidence && 'requestedPluginIds' in evidence) {
|
|
247
409
|
const batch = evidence;
|
|
248
410
|
return crypto.createHash('sha256')
|
|
@@ -337,6 +499,8 @@ function issueCode(error) {
|
|
|
337
499
|
return safeDiagnosticCode(safeOperationError.code, 'operationBlocked');
|
|
338
500
|
if (error instanceof TuiOperationBlockedError)
|
|
339
501
|
return error.code;
|
|
502
|
+
if (error instanceof CatalogManagementError)
|
|
503
|
+
return error.code;
|
|
340
504
|
if (error instanceof CatalogGenerationError)
|
|
341
505
|
return error.code;
|
|
342
506
|
if (error instanceof Error && error.name === 'CatalogSourcesConfigConflictError') {
|
|
@@ -408,6 +572,7 @@ export class TuiController {
|
|
|
408
572
|
#retiring = false;
|
|
409
573
|
#externalDrafts = [];
|
|
410
574
|
#batchRetryIntent;
|
|
575
|
+
#taskRetryIntent;
|
|
411
576
|
#operationHistory = new OperationHistoryStore();
|
|
412
577
|
#sessionEpoch = 0;
|
|
413
578
|
#lastRecordedResultId;
|
|
@@ -431,6 +596,7 @@ export class TuiController {
|
|
|
431
596
|
onError: (error, code) => {
|
|
432
597
|
void this.#logErrorSafe(error, code);
|
|
433
598
|
},
|
|
599
|
+
now: this.dependencies.now,
|
|
434
600
|
});
|
|
435
601
|
if (progressFeed) {
|
|
436
602
|
this.#progressUnsubscribe = progressFeed.subscribe((progress) => {
|
|
@@ -622,6 +788,7 @@ export class TuiController {
|
|
|
622
788
|
this.#installPlacementDraft = undefined;
|
|
623
789
|
this.#recoverableInstallPlacementDraft = undefined;
|
|
624
790
|
this.#batchRetryIntent = undefined;
|
|
791
|
+
this.#taskRetryIntent = undefined;
|
|
625
792
|
this.#externalDrafts = [];
|
|
626
793
|
this.#result = undefined;
|
|
627
794
|
this.#resultAnchor = undefined;
|
|
@@ -774,6 +941,18 @@ export class TuiController {
|
|
|
774
941
|
}
|
|
775
942
|
#ensureOperationFlow(intent) {
|
|
776
943
|
const active = this.#router.activeFlow();
|
|
944
|
+
if (intent.kind === 'catalogManagement') {
|
|
945
|
+
if (active?.kind === 'catalogManagement')
|
|
946
|
+
return;
|
|
947
|
+
this.#router.beginFlow('catalogManagement', this.#router.snapshot.current.route);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
951
|
+
if (active?.kind === 'pluginWriteTask')
|
|
952
|
+
return;
|
|
953
|
+
this.#router.beginFlow('pluginWriteTask', this.#router.snapshot.current.route);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
777
956
|
if (intent.kind === 'pluginWriteBatch') {
|
|
778
957
|
if (active?.kind === 'batchWrite')
|
|
779
958
|
return;
|
|
@@ -818,7 +997,9 @@ export class TuiController {
|
|
|
818
997
|
: intent.kind === 'sourceMutation' && intent.action !== 'remove'
|
|
819
998
|
? { name: 'sourceDetail', sourceId: intent.sourceId }
|
|
820
999
|
: intent.kind === 'catalogGeneration'
|
|
821
|
-
?
|
|
1000
|
+
? this.#router.snapshot.current.route.name === 'pluginWriteTask'
|
|
1001
|
+
? this.#router.snapshot.current.route
|
|
1002
|
+
: { name: 'sourceDetail', sourceId: intent.sourceId }
|
|
822
1003
|
: { name: 'sourceList' });
|
|
823
1004
|
}
|
|
824
1005
|
#stableResultRoute(intent, result) {
|
|
@@ -836,6 +1017,8 @@ export class TuiController {
|
|
|
836
1017
|
}
|
|
837
1018
|
if (intent.kind === 'pluginWriteBatch')
|
|
838
1019
|
return { name: 'pluginList', mode: 'installed' };
|
|
1020
|
+
if (intent.kind === 'pluginWriteTask')
|
|
1021
|
+
return { name: 'pluginList', mode: 'installed' };
|
|
839
1022
|
if (intent.kind === 'portableInstall' || intent.kind === 'configSave' || intent.kind === 'configReset') {
|
|
840
1023
|
return this.#normalizeRoute({ name: 'pluginDetail', pluginId: intent.pluginId });
|
|
841
1024
|
}
|
|
@@ -843,8 +1026,22 @@ export class TuiController {
|
|
|
843
1026
|
return this.#normalizeRoute({ name: 'sourceDetail', sourceId: intent.sourceId });
|
|
844
1027
|
}
|
|
845
1028
|
if (intent.kind === 'catalogGeneration') {
|
|
1029
|
+
if ('returnRoute' in result && result.returnRoute?.name === 'pluginWriteTask') {
|
|
1030
|
+
return this.#normalizeRoute(result.returnRoute);
|
|
1031
|
+
}
|
|
846
1032
|
return this.#normalizeRoute({ name: 'sourceDetail', sourceId: intent.sourceId });
|
|
847
1033
|
}
|
|
1034
|
+
if (intent.kind === 'catalogManagement') {
|
|
1035
|
+
if (intent.action === 'devActivate' || intent.action === 'devDeactivate') {
|
|
1036
|
+
return { name: 'catalogDev', step: 'overview' };
|
|
1037
|
+
}
|
|
1038
|
+
if (intent.action === 'storePrune')
|
|
1039
|
+
return { name: 'catalogStore' };
|
|
1040
|
+
if (intent.action === 'publishPlan' || intent.action === 'publishApply') {
|
|
1041
|
+
return { name: 'catalogPublish', step: 'overview' };
|
|
1042
|
+
}
|
|
1043
|
+
return { name: 'catalogWorkspace', view: 'overview' };
|
|
1044
|
+
}
|
|
848
1045
|
return { name: 'sourceList' };
|
|
849
1046
|
}
|
|
850
1047
|
#showResult(result, intent) {
|
|
@@ -883,6 +1080,19 @@ export class TuiController {
|
|
|
883
1080
|
},
|
|
884
1081
|
}
|
|
885
1082
|
: undefined;
|
|
1083
|
+
this.#taskRetryIntent = intent.kind === 'pluginWriteTask'
|
|
1084
|
+
&& 'summary' in result
|
|
1085
|
+
&& result.summary?.task
|
|
1086
|
+
&& (result.summary.task.retryTargets?.length ?? 0) > 0
|
|
1087
|
+
? {
|
|
1088
|
+
...structuredClone(intent),
|
|
1089
|
+
targets: result.summary.task.retryTargets.map((target) => {
|
|
1090
|
+
const current = this.listPluginWriteTaskCandidates(intent.goal).find((candidate) => pluginWriteTaskTargetKey(candidate.target) === pluginWriteTaskTargetKey(target));
|
|
1091
|
+
return structuredClone(current?.eligible ? current.target : target);
|
|
1092
|
+
}),
|
|
1093
|
+
executionPolicy: 'all',
|
|
1094
|
+
}
|
|
1095
|
+
: undefined;
|
|
886
1096
|
if (!this.#router.finishFlow(target, terminal))
|
|
887
1097
|
this.#router.replace(terminal);
|
|
888
1098
|
}
|
|
@@ -923,14 +1133,18 @@ export class TuiController {
|
|
|
923
1133
|
this.#catalogSourcesSession.configure(status.payload.catalog.sources.map((source) => {
|
|
924
1134
|
const generation = this.#catalogGenerations.get(source.id);
|
|
925
1135
|
const detail = details.get(source.id);
|
|
1136
|
+
const probeFingerprint = JSON.stringify({
|
|
1137
|
+
sourceIdentity: source.identity,
|
|
1138
|
+
requestedRef: source.requestedRef,
|
|
1139
|
+
});
|
|
926
1140
|
return {
|
|
927
1141
|
sourceId: source.id,
|
|
928
1142
|
primary: source.primary,
|
|
929
1143
|
autoCheck: this.dependencies.probeCatalogFreshness !== undefined
|
|
930
1144
|
&& (detail?.autoCheck ?? true),
|
|
1145
|
+
probeFingerprint,
|
|
931
1146
|
fingerprint: JSON.stringify({
|
|
932
|
-
|
|
933
|
-
requestedRef: source.requestedRef,
|
|
1147
|
+
probeFingerprint,
|
|
934
1148
|
activeStoreKey: generation?.active?.storeKey,
|
|
935
1149
|
generationState: generation?.state,
|
|
936
1150
|
generationErrorCode: generation?.errorCode,
|
|
@@ -1621,6 +1835,7 @@ export class TuiController {
|
|
|
1621
1835
|
return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
|
|
1622
1836
|
&& (options.mode === 'browse' || hasInstalledPlacement(plugin) || plugin.savedConfigScopes.length > 0)
|
|
1623
1837
|
&& (options.installFilter !== 'uninstalled' || !hasInstalledPlacement(plugin))
|
|
1838
|
+
&& (options.installFilter !== 'updateAvailable' || plugin.update?.state === 'available')
|
|
1624
1839
|
&& (!query || `${plugin.displayName}\n${plugin.description}\n${plugin.sourceId}`.toLocaleLowerCase('en').includes(query))));
|
|
1625
1840
|
}
|
|
1626
1841
|
getPluginOverview(pluginId) {
|
|
@@ -1631,6 +1846,9 @@ export class TuiController {
|
|
|
1631
1846
|
const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
|
|
1632
1847
|
return overview ? cloneFrozen(overview) : undefined;
|
|
1633
1848
|
}
|
|
1849
|
+
listPluginDependencyOverviews() {
|
|
1850
|
+
return cloneFrozen(this.#viewModel?.dependencyOverviews ?? []);
|
|
1851
|
+
}
|
|
1634
1852
|
getBlockingDependents(pluginId, platform, scope) {
|
|
1635
1853
|
const dependents = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId)?.dependents ?? [];
|
|
1636
1854
|
return cloneFrozen(dependents.flatMap((dependent) => {
|
|
@@ -1756,7 +1974,7 @@ export class TuiController {
|
|
|
1756
1974
|
return this.#catalogSourcesSession.fullValidate(sourceId);
|
|
1757
1975
|
}
|
|
1758
1976
|
setExternalDraftState(states = []) {
|
|
1759
|
-
const priority = { sourceDraft: 0, batchDraft: 1,
|
|
1977
|
+
const priority = { sourceDraft: 0, batchDraft: 1, catalogDraft: 2, configDraft: 3 };
|
|
1760
1978
|
this.#externalDrafts = structuredClone(states).sort((left, right) => priority[left.kind] - priority[right.kind] || left.owner.localeCompare(right.owner, 'en'));
|
|
1761
1979
|
this.#publish();
|
|
1762
1980
|
}
|
|
@@ -1769,6 +1987,9 @@ export class TuiController {
|
|
|
1769
1987
|
startPluginWriteBatchReview(intent) {
|
|
1770
1988
|
return this.startReviewPreparation(intent, { name: 'batchPlacement' });
|
|
1771
1989
|
}
|
|
1990
|
+
startPluginWriteTaskReview(intent, returnRoute) {
|
|
1991
|
+
return this.startReviewPreparation(intent, returnRoute);
|
|
1992
|
+
}
|
|
1772
1993
|
retryFailedPluginWriteBatch() {
|
|
1773
1994
|
if (!this.#batchRetryIntent)
|
|
1774
1995
|
return undefined;
|
|
@@ -1776,6 +1997,13 @@ export class TuiController {
|
|
|
1776
1997
|
this.#batchRetryIntent = undefined;
|
|
1777
1998
|
return this.startReviewPreparation(intent, { name: 'pluginList', mode: 'installed' });
|
|
1778
1999
|
}
|
|
2000
|
+
retryFailedPluginWriteTask() {
|
|
2001
|
+
if (!this.#taskRetryIntent)
|
|
2002
|
+
return undefined;
|
|
2003
|
+
const intent = structuredClone(this.#taskRetryIntent);
|
|
2004
|
+
this.#taskRetryIntent = undefined;
|
|
2005
|
+
return this.startReviewPreparation(intent, { name: 'pluginList', mode: 'installed' });
|
|
2006
|
+
}
|
|
1779
2007
|
catalogActivationBlockReason() {
|
|
1780
2008
|
if (this.#externalDrafts[0])
|
|
1781
2009
|
return this.#externalDrafts[0].kind;
|
|
@@ -1790,11 +2018,25 @@ export class TuiController {
|
|
|
1790
2018
|
return 'writeProgress';
|
|
1791
2019
|
return undefined;
|
|
1792
2020
|
}
|
|
1793
|
-
catalogSourceRecoveryBlockReason() {
|
|
2021
|
+
catalogSourceRecoveryBlockReason(intent) {
|
|
1794
2022
|
const activeFlow = this.#router.activeFlow();
|
|
2023
|
+
const route = this.#router.snapshot.current.route;
|
|
2024
|
+
const ownedFrozenBatchTask = intent !== undefined
|
|
2025
|
+
&& route.name === 'pluginWriteTask'
|
|
2026
|
+
&& route.targetKeys !== undefined
|
|
2027
|
+
&& route.targetKeys.length > 0
|
|
2028
|
+
&& route.batchOrigin !== undefined
|
|
2029
|
+
&& activeFlow?.id === route.batchOrigin.flowId
|
|
2030
|
+
&& this.listPluginWriteTaskCandidates(route.goal).some((candidate) => candidate.sourceId === intent.sourceId
|
|
2031
|
+
&& candidate.blockReason === 'catalogActivationRequired'
|
|
2032
|
+
&& route.targetKeys.includes(pluginWriteTaskTargetKey(candidate.target)));
|
|
1795
2033
|
const blockingExternalDraft = this.#externalDrafts.find((draft) => !(draft.kind === 'configDraft'
|
|
1796
2034
|
&& activeFlow?.kind === 'sourceRecovery'
|
|
1797
|
-
&& activeFlow.parentFlowId !== undefined)
|
|
2035
|
+
&& activeFlow.parentFlowId !== undefined)
|
|
2036
|
+
&& !(ownedFrozenBatchTask
|
|
2037
|
+
&& draft.kind === 'batchDraft'
|
|
2038
|
+
&& draft.owner === route.batchOrigin?.owner
|
|
2039
|
+
&& draft.flowId === route.batchOrigin.flowId));
|
|
1798
2040
|
if (blockingExternalDraft)
|
|
1799
2041
|
return blockingExternalDraft.kind;
|
|
1800
2042
|
if (this.#portableDraft)
|
|
@@ -1865,6 +2107,61 @@ export class TuiController {
|
|
|
1865
2107
|
: this.#abort.signal;
|
|
1866
2108
|
return cloneFrozen(await this.#trackRead(this.dependencies.getConfigPage(pluginId, structuredClone(layer), combined)));
|
|
1867
2109
|
}
|
|
2110
|
+
async selectCatalogWorkspace(workspaceRoot) {
|
|
2111
|
+
this.#assertAcceptingActions();
|
|
2112
|
+
if (!this.dependencies.catalogManagement) {
|
|
2113
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2114
|
+
}
|
|
2115
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.selectWorkspace(workspaceRoot)));
|
|
2116
|
+
}
|
|
2117
|
+
async getCatalogWorkspaceManagement() {
|
|
2118
|
+
this.#assertAcceptingActions();
|
|
2119
|
+
if (!this.dependencies.catalogManagement) {
|
|
2120
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2121
|
+
}
|
|
2122
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.getWorkspace()));
|
|
2123
|
+
}
|
|
2124
|
+
async validateCatalogWorkspace() {
|
|
2125
|
+
this.#assertAcceptingActions();
|
|
2126
|
+
if (!this.dependencies.catalogManagement) {
|
|
2127
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2128
|
+
}
|
|
2129
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.validateWorkspace()));
|
|
2130
|
+
}
|
|
2131
|
+
async getCatalogDevManagement() {
|
|
2132
|
+
this.#assertAcceptingActions();
|
|
2133
|
+
if (!this.dependencies.catalogManagement) {
|
|
2134
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2135
|
+
}
|
|
2136
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.getDev()));
|
|
2137
|
+
}
|
|
2138
|
+
async getCatalogStoreManagement() {
|
|
2139
|
+
this.#assertAcceptingActions();
|
|
2140
|
+
if (!this.dependencies.catalogManagement) {
|
|
2141
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2142
|
+
}
|
|
2143
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.getStore()));
|
|
2144
|
+
}
|
|
2145
|
+
async getCatalogPublicationManagement() {
|
|
2146
|
+
this.#assertAcceptingActions();
|
|
2147
|
+
if (!this.dependencies.catalogManagement) {
|
|
2148
|
+
throw new TuiOperationBlockedError('catalogManagementUnavailable', 'Catalog management is unavailable.');
|
|
2149
|
+
}
|
|
2150
|
+
return cloneFrozen(await this.#trackRead(this.dependencies.catalogManagement.getPublication()));
|
|
2151
|
+
}
|
|
2152
|
+
async previewConfigSaveImpact(intent, signal) {
|
|
2153
|
+
this.#assertAcceptingActions();
|
|
2154
|
+
const normalized = normalizeOperationIntent(intent);
|
|
2155
|
+
this.#validateReviewIntent(normalized);
|
|
2156
|
+
const combined = signal
|
|
2157
|
+
? AbortSignal.any([this.#abort.signal, signal])
|
|
2158
|
+
: this.#abort.signal;
|
|
2159
|
+
const evidence = await this.#trackRead(this.#prepareReviewEvidence(normalized, combined));
|
|
2160
|
+
if (!evidence.config) {
|
|
2161
|
+
throw new TuiOperationBlockedError('configImpactUnavailable', '无法确定设置修改的应用影响。');
|
|
2162
|
+
}
|
|
2163
|
+
return cloneFrozen(evidence.config);
|
|
2164
|
+
}
|
|
1868
2165
|
async getConfigResetAvailability(pluginId, layer) {
|
|
1869
2166
|
this.#assertAcceptingActions();
|
|
1870
2167
|
if (!this.dependencies.getConfigResetAvailability) {
|
|
@@ -1875,6 +2172,11 @@ export class TuiController {
|
|
|
1875
2172
|
listUserIssues() {
|
|
1876
2173
|
return cloneFrozen(this.#viewModel?.issues ?? []);
|
|
1877
2174
|
}
|
|
2175
|
+
listPluginWriteTaskCandidates(goal) {
|
|
2176
|
+
return cloneFrozen(this.#viewModel
|
|
2177
|
+
? buildPluginWriteTaskCandidates(this.#viewModel, goal)
|
|
2178
|
+
: []);
|
|
2179
|
+
}
|
|
1878
2180
|
async buildOperationReview(intent, returnRoute) {
|
|
1879
2181
|
this.#assertAcceptingActions();
|
|
1880
2182
|
intent = normalizeOperationIntent(intent);
|
|
@@ -1895,7 +2197,7 @@ export class TuiController {
|
|
|
1895
2197
|
this.#assertAcceptingActions();
|
|
1896
2198
|
intent = normalizeOperationIntent(intent);
|
|
1897
2199
|
if (intent.kind === 'catalogGeneration' && !intent.sourceMutation) {
|
|
1898
|
-
const blockReason = this.catalogSourceRecoveryBlockReason();
|
|
2200
|
+
const blockReason = this.catalogSourceRecoveryBlockReason(intent);
|
|
1899
2201
|
if (blockReason) {
|
|
1900
2202
|
throw new TuiOperationBlockedError('catalogActivationBlocked', `Catalog Active 切换已阻止:${blockReason}。请先完成或放弃当前草稿或操作。`);
|
|
1901
2203
|
}
|
|
@@ -1958,6 +2260,37 @@ export class TuiController {
|
|
|
1958
2260
|
return this.startReviewPreparation(structuredClone(intent), preparation.returnRoute);
|
|
1959
2261
|
}
|
|
1960
2262
|
#validateReviewIntent(intent) {
|
|
2263
|
+
if (intent.kind === 'catalogManagement') {
|
|
2264
|
+
if (intent.action === 'storePrune' && intent.items.length === 0) {
|
|
2265
|
+
throw new TuiOperationBlockedError('catalogStoreSelectionInvalid', 'Select at least one reclaimable Catalog store item.');
|
|
2266
|
+
}
|
|
2267
|
+
if (intent.action === 'workspaceVersion'
|
|
2268
|
+
&& (intent.version === undefined) === (intent.bump === undefined)) {
|
|
2269
|
+
throw new TuiOperationBlockedError('catalogVersionSelectionInvalid', 'Select exactly one Catalog version or bump level.');
|
|
2270
|
+
}
|
|
2271
|
+
if ((intent.action === 'publishPlan' || intent.action === 'publishApply')
|
|
2272
|
+
&& intent.remote !== undefined && intent.remoteUrl !== undefined) {
|
|
2273
|
+
throw new TuiOperationBlockedError('catalogPublicationTargetInvalid', 'Select a Git remote name or one direct remote URL.');
|
|
2274
|
+
}
|
|
2275
|
+
if (intent.action === 'publishApply'
|
|
2276
|
+
&& !/^sha256:[0-9a-f]{64}$/u.test(intent.expectedPlanDigest)) {
|
|
2277
|
+
throw new TuiOperationBlockedError('catalogPublicationPlanInvalid', 'Select an exact prepared Catalog publication plan.');
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
2281
|
+
if (intent.targets.length === 0
|
|
2282
|
+
|| JSON.stringify(intent.targets) !== JSON.stringify(canonicalPluginWriteTaskTargets(intent.targets))) {
|
|
2283
|
+
throw new TuiOperationBlockedError('taskSelectionInvalid', '任务目标必须是非空精确目标集合。');
|
|
2284
|
+
}
|
|
2285
|
+
for (const target of intent.targets) {
|
|
2286
|
+
sourceIdFromPluginId(target.pluginId);
|
|
2287
|
+
if ((target.platform !== 'codex' && target.platform !== 'claude')
|
|
2288
|
+
|| (target.scope !== 'user' && target.scope !== 'project')
|
|
2289
|
+
|| (intent.goal === 'update') !== Boolean(target.expectedUpdate)) {
|
|
2290
|
+
throw new TuiOperationBlockedError('taskSelectionInvalid', '任务目标或版本证据无效。');
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
1961
2294
|
if (intent.kind === 'pluginWriteBatch') {
|
|
1962
2295
|
if (intent.selection.kind === 'currentProjectIntentRoots'
|
|
1963
2296
|
&& (intent.action !== 'sync' || intent.scope !== 'project')) {
|
|
@@ -1978,8 +2311,12 @@ export class TuiController {
|
|
|
1978
2311
|
}
|
|
1979
2312
|
}
|
|
1980
2313
|
#reviewPreparationLabel(intent) {
|
|
2314
|
+
if (intent.kind === 'catalogManagement')
|
|
2315
|
+
return 'Catalog 管理';
|
|
1981
2316
|
if (intent.kind === 'catalogSourcesRecovery')
|
|
1982
2317
|
return 'Catalog 来源配置';
|
|
2318
|
+
if (intent.kind === 'pluginWriteTask')
|
|
2319
|
+
return `${intent.targets.length} 个安装位置`;
|
|
1983
2320
|
if (intent.kind === 'pluginWriteBatch') {
|
|
1984
2321
|
return intent.selection.kind === 'selected'
|
|
1985
2322
|
? `${intent.selection.pluginIds.length} 个插件`
|
|
@@ -2033,9 +2370,10 @@ export class TuiController {
|
|
|
2033
2370
|
preparation.snapshot.stages = preparation.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'completed' } : stage);
|
|
2034
2371
|
this.#publish();
|
|
2035
2372
|
const returnRoute = structuredClone(preparation.returnRoute);
|
|
2373
|
+
this.#storeOperationReview(intent, evidence, 'replace', returnRoute);
|
|
2036
2374
|
this.#reviewPreparationIntents.delete(preparation.snapshot.id);
|
|
2037
2375
|
this.#reviewPreparation = undefined;
|
|
2038
|
-
this.#
|
|
2376
|
+
this.#publish();
|
|
2039
2377
|
}
|
|
2040
2378
|
catch (error) {
|
|
2041
2379
|
if (!this.#reviewPreparationIsCurrent(token))
|
|
@@ -2148,6 +2486,8 @@ export class TuiController {
|
|
|
2148
2486
|
this.#ensureOperationFlow(intent);
|
|
2149
2487
|
if (intent.kind === 'pluginWriteBatch')
|
|
2150
2488
|
assertPluginWriteBatchEvidence(evidence);
|
|
2489
|
+
if (intent.kind === 'pluginWriteTask')
|
|
2490
|
+
assertPluginWriteTaskEvidence(intent, evidence);
|
|
2151
2491
|
if (this.#router.snapshot.current.route.name === 'result')
|
|
2152
2492
|
navigation = 'replace';
|
|
2153
2493
|
const projection = buildOperationReviewProjection({
|
|
@@ -2164,10 +2504,12 @@ export class TuiController {
|
|
|
2164
2504
|
const expiresAt = now
|
|
2165
2505
|
+ (this.dependencies.reviewTtlMs ?? 2 * 60_000);
|
|
2166
2506
|
const pluginId = 'pluginId' in intent ? intent.pluginId
|
|
2167
|
-
: 'sourceId' in intent ? intent.sourceId
|
|
2507
|
+
: 'sourceId' in intent ? intent.sourceId ?? 'Catalog 管理'
|
|
2168
2508
|
: intent.kind === 'pluginWriteBatch'
|
|
2169
2509
|
? `${evidence.requestedPluginIds.length} 个插件`
|
|
2170
|
-
:
|
|
2510
|
+
: intent.kind === 'pluginWriteTask'
|
|
2511
|
+
? `${evidence.taskPlan.requestedTargets.length} 个安装位置`
|
|
2512
|
+
: 'Catalog 来源配置';
|
|
2171
2513
|
if ('pluginId' in intent)
|
|
2172
2514
|
sourceIdFromPluginId(intent.pluginId);
|
|
2173
2515
|
const pluginVersion = 'pluginId' in intent
|
|
@@ -2184,6 +2526,7 @@ export class TuiController {
|
|
|
2184
2526
|
? { sourceAction: intent.sourceMutation.action } : {}),
|
|
2185
2527
|
...(intent.kind === 'pluginWrite' ? { pluginAction: intent.action } : {}),
|
|
2186
2528
|
...(intent.kind === 'pluginWriteBatch' ? { pluginAction: intent.action } : {}),
|
|
2529
|
+
...(intent.kind === 'pluginWriteTask' ? { taskGoal: intent.goal } : {}),
|
|
2187
2530
|
...(intent.kind === 'pluginWrite' && intent.originIssue
|
|
2188
2531
|
? { originIssue: intent.originIssue }
|
|
2189
2532
|
: {}),
|
|
@@ -2204,6 +2547,15 @@ export class TuiController {
|
|
|
2204
2547
|
.filter((group) => group.executable).length,
|
|
2205
2548
|
},
|
|
2206
2549
|
} : {}),
|
|
2550
|
+
...(intent.kind === 'pluginWriteTask' ? {
|
|
2551
|
+
task: {
|
|
2552
|
+
goal: intent.goal,
|
|
2553
|
+
requestedTargetCount: evidence.taskPlan.requestedTargets.length,
|
|
2554
|
+
executableTargetCount: evidence.taskPlan.executableTargets.length,
|
|
2555
|
+
blockedTargetCount: evidence.taskPlan.blockedTargets.length,
|
|
2556
|
+
causeCount: evidence.taskPlan.causes.length,
|
|
2557
|
+
},
|
|
2558
|
+
} : {}),
|
|
2207
2559
|
...(evidence.source && 'catalogVersion' in evidence.source
|
|
2208
2560
|
&& typeof evidence.source.catalogVersion === 'string'
|
|
2209
2561
|
&& evidence.source.snapshotDigest
|
|
@@ -2267,7 +2619,9 @@ export class TuiController {
|
|
|
2267
2619
|
this.#progressFeed?.clearCurrent?.();
|
|
2268
2620
|
const dependencyTargets = ticket.intent.kind === 'pluginWriteBatch'
|
|
2269
2621
|
? ticket.evidence.groups.flatMap((group) => group.preview.dependencyPlan?.targets ?? [])
|
|
2270
|
-
: ticket.
|
|
2622
|
+
: ticket.intent.kind === 'pluginWriteTask'
|
|
2623
|
+
? ticket.evidence.taskPlan.sources.flatMap((source) => source.preview.dependencyPlan?.targets ?? [])
|
|
2624
|
+
: ticket.evidence.dependencyPlan?.targets ?? [];
|
|
2271
2625
|
this.#progressTargets = dependencyTargets.map((target) => ({
|
|
2272
2626
|
pluginId: target.pluginId,
|
|
2273
2627
|
platform: target.platform,
|
|
@@ -2285,15 +2639,18 @@ export class TuiController {
|
|
|
2285
2639
|
current: {
|
|
2286
2640
|
target: ticket.intent.kind === 'pluginWriteBatch'
|
|
2287
2641
|
? `${ticket.evidence.requestedPluginIds.length} 个插件`
|
|
2288
|
-
:
|
|
2289
|
-
|
|
2642
|
+
: ticket.intent.kind === 'pluginWriteTask'
|
|
2643
|
+
? `${ticket.evidence.taskPlan.executableTargets.length} 个安装位置`
|
|
2644
|
+
: 'pluginId' in ticket.intent ? ticket.intent.pluginId
|
|
2645
|
+
: 'sourceId' in ticket.intent ? ticket.intent.sourceId ?? 'Catalog 管理' : 'Catalog 来源配置',
|
|
2290
2646
|
phase: '验证当前状态',
|
|
2291
2647
|
},
|
|
2292
2648
|
};
|
|
2293
2649
|
this.#router.replace({ name: 'progress', operationId });
|
|
2294
2650
|
this.#publish();
|
|
2295
2651
|
try {
|
|
2296
|
-
if (ticket.intent.kind !== 'pluginWrite' && ticket.intent.kind !== 'pluginWriteBatch'
|
|
2652
|
+
if (ticket.intent.kind !== 'pluginWrite' && ticket.intent.kind !== 'pluginWriteBatch'
|
|
2653
|
+
&& ticket.intent.kind !== 'pluginWriteTask') {
|
|
2297
2654
|
await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
|
|
2298
2655
|
}
|
|
2299
2656
|
}
|
|
@@ -2325,6 +2682,7 @@ export class TuiController {
|
|
|
2325
2682
|
if (!this.#operationIsCurrent(generation))
|
|
2326
2683
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
2327
2684
|
if (ticket.intent.kind !== 'pluginWrite' && ticket.intent.kind !== 'pluginWriteBatch'
|
|
2685
|
+
&& ticket.intent.kind !== 'pluginWriteTask'
|
|
2328
2686
|
&& ticket.intent.kind !== 'catalogGeneration') {
|
|
2329
2687
|
let current;
|
|
2330
2688
|
try {
|
|
@@ -2358,6 +2716,63 @@ export class TuiController {
|
|
|
2358
2716
|
...executionProjection,
|
|
2359
2717
|
sensitiveValues: collectOperationSensitiveValues(ticket.intent),
|
|
2360
2718
|
});
|
|
2719
|
+
if (ticket.intent.kind === 'catalogManagement') {
|
|
2720
|
+
if (ticket.intent.action === 'devActivate' || ticket.intent.action === 'devDeactivate') {
|
|
2721
|
+
this.dependencies.beginCatalogSession?.();
|
|
2722
|
+
generation = ++this.#generation;
|
|
2723
|
+
try {
|
|
2724
|
+
const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
|
|
2725
|
+
if (!this.#operationIsCurrent(generation)) {
|
|
2726
|
+
return this.#detachedBlocked(operationId, ticket.intent);
|
|
2727
|
+
}
|
|
2728
|
+
this.#applyStatus(refreshed);
|
|
2729
|
+
}
|
|
2730
|
+
catch (reloadError) {
|
|
2731
|
+
if (!this.#operationIsCurrent(generation)) {
|
|
2732
|
+
return this.#detachedBlocked(operationId, ticket.intent);
|
|
2733
|
+
}
|
|
2734
|
+
await this.#logErrorSafe(reloadError, 'tuiStateReloadFailed', {
|
|
2735
|
+
privateConfigOperation: true,
|
|
2736
|
+
});
|
|
2737
|
+
this.#status = undefined;
|
|
2738
|
+
this.#viewModel = undefined;
|
|
2739
|
+
this.#phase = 'error';
|
|
2740
|
+
this.#loadError = {
|
|
2741
|
+
code: 'tuiStateReloadFailed',
|
|
2742
|
+
message: 'Catalog 来源已变更,但插件状态刷新失败。请重新加载后再继续操作。',
|
|
2743
|
+
};
|
|
2744
|
+
return this.#setBlocked(operationId, 'tuiStateReloadFailed', {
|
|
2745
|
+
intent: ticket.intent,
|
|
2746
|
+
navigation: 'result',
|
|
2747
|
+
returnRoute: ticket.returnRoute,
|
|
2748
|
+
writeMayHaveCompleted: true,
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
const baseSummary = operationResultSummary(ticket.intent, execution);
|
|
2753
|
+
const summary = {
|
|
2754
|
+
...baseSummary,
|
|
2755
|
+
elapsedMs: Math.max(0, (this.dependencies.now?.() ?? Date.now()) - startedAtMs),
|
|
2756
|
+
messages: [...this.#progress.messages, ...(baseSummary.messages ?? [])].slice(-8)
|
|
2757
|
+
.map((message) => sanitizeDiagnosticText(message, [], collectOperationSensitiveValues(ticket.intent))),
|
|
2758
|
+
};
|
|
2759
|
+
const projection = this.#operationDiagnostics.get(operationId);
|
|
2760
|
+
if (projection)
|
|
2761
|
+
projection.effects.finalVerification = 'completed';
|
|
2762
|
+
this.#progress = { ...this.#progress, elapsedMs: summary.elapsedMs };
|
|
2763
|
+
const result = {
|
|
2764
|
+
state: summary.code === 'operationPartial' ? 'partial' : 'succeeded',
|
|
2765
|
+
operationId,
|
|
2766
|
+
summary,
|
|
2767
|
+
operationKind: ticket.intent.kind,
|
|
2768
|
+
returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
|
|
2769
|
+
};
|
|
2770
|
+
this.#result = result;
|
|
2771
|
+
this.#activeReview = undefined;
|
|
2772
|
+
this.#showResult(result, ticket.intent);
|
|
2773
|
+
this.#publish();
|
|
2774
|
+
return cloneFrozen(result);
|
|
2775
|
+
}
|
|
2361
2776
|
if (ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'update') {
|
|
2362
2777
|
const confirmedAutoCheck = catalogSourceAutoCheckOnlyValue(ticket.intent.update);
|
|
2363
2778
|
if (confirmedAutoCheck !== undefined) {
|
|
@@ -2453,6 +2868,17 @@ export class TuiController {
|
|
|
2453
2868
|
if (!this.#operationIsCurrent(generation))
|
|
2454
2869
|
return this.#detachedBlocked(operationId, ticket.intent);
|
|
2455
2870
|
this.#applyStatus(refreshed);
|
|
2871
|
+
if (ticket.intent.kind === 'catalogGeneration') {
|
|
2872
|
+
const completed = typeof execution === 'object' && execution !== null
|
|
2873
|
+
? execution.generation
|
|
2874
|
+
: undefined;
|
|
2875
|
+
const active = this.#catalogGenerations.get(ticket.intent.sourceId)?.active;
|
|
2876
|
+
if (completed && active
|
|
2877
|
+
&& active.catalogVersion === completed.resolution.catalogVersion
|
|
2878
|
+
&& active.resolvedCommit === completed.resolution.resolvedCommit) {
|
|
2879
|
+
this.#catalogSourcesSession.recordVerifiedGeneration(ticket.intent.sourceId, active);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2456
2882
|
const verifiedProjection = this.#operationDiagnostics.get(operationId);
|
|
2457
2883
|
if (verifiedProjection && verifiedProjection.effects.finalVerification !== 'failed') {
|
|
2458
2884
|
verifiedProjection.effects.finalVerification = 'completed';
|
|
@@ -2463,6 +2889,7 @@ export class TuiController {
|
|
|
2463
2889
|
}
|
|
2464
2890
|
const pluginSummary = ticket.intent.kind === 'pluginWrite'
|
|
2465
2891
|
|| ticket.intent.kind === 'pluginWriteBatch'
|
|
2892
|
+
|| ticket.intent.kind === 'pluginWriteTask'
|
|
2466
2893
|
? operationResultSummary(ticket.intent, execution)
|
|
2467
2894
|
: undefined;
|
|
2468
2895
|
const hasFailedDependencyTarget = pluginSummary?.targets?.some((target) => target.relation !== undefined && target.relation !== 'requested'
|
|
@@ -2503,9 +2930,10 @@ export class TuiController {
|
|
|
2503
2930
|
intent: ticket.intent,
|
|
2504
2931
|
navigation: 'result',
|
|
2505
2932
|
returnRoute: ticket.returnRoute,
|
|
2506
|
-
...(ticket.intent.kind === 'pluginWriteBatch' ||
|
|
2933
|
+
...(ticket.intent.kind === 'pluginWriteBatch' || ticket.intent.kind === 'pluginWriteTask'
|
|
2934
|
+
|| dependencyFailure || dependencyStillRequired
|
|
2507
2935
|
? { summary: pluginSummary } : {}),
|
|
2508
|
-
...(ticket.intent.kind !== 'pluginWriteBatch'
|
|
2936
|
+
...(ticket.intent.kind !== 'pluginWriteBatch' && ticket.intent.kind !== 'pluginWriteTask'
|
|
2509
2937
|
&& !dependencyFailure && !dependencyStillRequired && ['started', 'partial', 'completed', 'failed', 'unknown'].includes(this.#operationDiagnostics.get(operationId)?.effects.awWrite ?? 'unknown') ? { writeMayHaveCompleted: true } : {}),
|
|
2510
2938
|
});
|
|
2511
2939
|
}
|
|
@@ -2579,7 +3007,9 @@ export class TuiController {
|
|
|
2579
3007
|
: ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'update'
|
|
2580
3008
|
? { name: 'sourceDetail', sourceId: ticket.intent.sourceId }
|
|
2581
3009
|
: ticket.intent.kind === 'catalogGeneration'
|
|
2582
|
-
?
|
|
3010
|
+
? ticket.returnRoute.name === 'pluginWriteTask'
|
|
3011
|
+
? ticket.returnRoute
|
|
3012
|
+
: { name: 'sourceDetail', sourceId: ticket.intent.sourceId }
|
|
2583
3013
|
: ticket.returnRoute)),
|
|
2584
3014
|
};
|
|
2585
3015
|
if ((ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset')
|
|
@@ -2620,6 +3050,14 @@ export class TuiController {
|
|
|
2620
3050
|
this.#operationDiagnostics.delete(operationId);
|
|
2621
3051
|
return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
|
|
2622
3052
|
}
|
|
3053
|
+
if (ticket.intent.kind === 'catalogManagement') {
|
|
3054
|
+
return this.#setBlocked(operationId, code, {
|
|
3055
|
+
intent: ticket.intent,
|
|
3056
|
+
navigation: 'result',
|
|
3057
|
+
returnRoute: ticket.returnRoute,
|
|
3058
|
+
writeMayHaveCompleted: true,
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
2623
3061
|
generation = ++this.#generation;
|
|
2624
3062
|
let finalVerification = 'completed';
|
|
2625
3063
|
try {
|
|
@@ -2723,6 +3161,30 @@ export class TuiController {
|
|
|
2723
3161
|
});
|
|
2724
3162
|
}
|
|
2725
3163
|
async #rebuildStaleReview(ticket, operationId, currentEvidence, generation = this.#generation) {
|
|
3164
|
+
if (ticket.intent.kind === 'catalogManagement') {
|
|
3165
|
+
let evidence = currentEvidence;
|
|
3166
|
+
try {
|
|
3167
|
+
evidence ??= await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
|
|
3168
|
+
}
|
|
3169
|
+
catch (error) {
|
|
3170
|
+
const code = issueCode(error);
|
|
3171
|
+
await this.#logOperationError(error, code, ticket.intent);
|
|
3172
|
+
return this.#setBlocked(operationId, code, {
|
|
3173
|
+
intent: ticket.intent,
|
|
3174
|
+
returnRoute: ticket.returnRoute,
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
const review = this.#storeOperationReview(ticket.intent, evidence, 'replace', ticket.returnRoute);
|
|
3178
|
+
const result = {
|
|
3179
|
+
state: 'stale',
|
|
3180
|
+
operationId,
|
|
3181
|
+
review,
|
|
3182
|
+
...this.#operationIdentity(ticket.intent),
|
|
3183
|
+
};
|
|
3184
|
+
this.#result = result;
|
|
3185
|
+
this.#publish();
|
|
3186
|
+
return cloneFrozen(result);
|
|
3187
|
+
}
|
|
2726
3188
|
this.dependencies.beginCatalogSession?.();
|
|
2727
3189
|
generation = ++this.#generation;
|
|
2728
3190
|
try {
|
|
@@ -2874,6 +3336,22 @@ export class TuiController {
|
|
|
2874
3336
|
}
|
|
2875
3337
|
}
|
|
2876
3338
|
function operationResultSummaryUnsafe(intent, execution) {
|
|
3339
|
+
if (intent.kind === 'catalogManagement') {
|
|
3340
|
+
const result = execution;
|
|
3341
|
+
return {
|
|
3342
|
+
code: result.state === 'partial' ? 'operationPartial' : 'operationSucceeded',
|
|
3343
|
+
message: result.message,
|
|
3344
|
+
messages: result.messages,
|
|
3345
|
+
catalogManagement: {
|
|
3346
|
+
action: result.action,
|
|
3347
|
+
state: result.state,
|
|
3348
|
+
...(result.catalogVersion ? { catalogVersion: result.catalogVersion } : {}),
|
|
3349
|
+
...(result.planDigest ? { planDigest: result.planDigest } : {}),
|
|
3350
|
+
...(result.store ? { store: structuredClone(result.store) } : {}),
|
|
3351
|
+
...(result.publication ? { publication: structuredClone(result.publication) } : {}),
|
|
3352
|
+
},
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
2877
3355
|
if (intent.kind === 'catalogSourcesRecovery') {
|
|
2878
3356
|
const result = execution;
|
|
2879
3357
|
return {
|
|
@@ -2950,6 +3428,131 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
2950
3428
|
},
|
|
2951
3429
|
};
|
|
2952
3430
|
}
|
|
3431
|
+
if (intent.kind === 'pluginWriteTask') {
|
|
3432
|
+
const taskExecution = execution;
|
|
3433
|
+
const failedTargets = pluginWriteTaskFailedRequestedTargets(taskExecution);
|
|
3434
|
+
const failedKeys = new Set(failedTargets.map(pluginWriteTaskTargetKey));
|
|
3435
|
+
const technicalTargets = taskExecution.targetResults.map((entry) => {
|
|
3436
|
+
const placement = taskExecution.dependencyPlan?.targets.find((target) => target.pluginId === entry.target.pluginId && target.platform === entry.target.platform
|
|
3437
|
+
&& (target.requestedScope ?? target.resolvedScope) === entry.target.scope);
|
|
3438
|
+
return {
|
|
3439
|
+
pluginId: entry.target.pluginId,
|
|
3440
|
+
platform: entry.target.platform,
|
|
3441
|
+
scope: entry.target.scope,
|
|
3442
|
+
state: entry.action === 'skipped' ? 'skipped'
|
|
3443
|
+
: entry.state === 'failed' ? 'failed'
|
|
3444
|
+
: entry.action === 'keep' ? 'kept' : 'completed',
|
|
3445
|
+
relation: entry.relation === 'requested' ? 'requested'
|
|
3446
|
+
: placement?.depth === 1 ? 'direct' : 'transitive',
|
|
3447
|
+
requiredBy: entry.requiredBy,
|
|
3448
|
+
...(entry.errorCode ? { errorCode: safeDiagnosticCode(entry.errorCode, 'writeFailed') } : {}),
|
|
3449
|
+
};
|
|
3450
|
+
});
|
|
3451
|
+
const blocked = new Map(taskExecution.taskPlan.blockedTargets.map((entry) => [
|
|
3452
|
+
pluginWriteTaskTargetKey(entry.target), entry,
|
|
3453
|
+
]));
|
|
3454
|
+
const relatedResults = (target) => {
|
|
3455
|
+
const key = pluginWriteTaskTargetKey(target);
|
|
3456
|
+
const source = taskExecution.sourceResults.find((candidate) => candidate.requestedTargets.some((requested) => pluginWriteTaskTargetKey(requested) === key));
|
|
3457
|
+
const related = source?.result.targetResults.filter((entry) => {
|
|
3458
|
+
const pluginId = entry.target.pluginId;
|
|
3459
|
+
if (!pluginId)
|
|
3460
|
+
return false;
|
|
3461
|
+
const executionKey = pluginWriteTaskTargetKey({
|
|
3462
|
+
pluginId,
|
|
3463
|
+
platform: entry.target.platform,
|
|
3464
|
+
scope: entry.target.scope,
|
|
3465
|
+
});
|
|
3466
|
+
if (executionKey === key)
|
|
3467
|
+
return true;
|
|
3468
|
+
if (taskExecution.taskPlan.causes.some((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === executionKey
|
|
3469
|
+
&& cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === key)))
|
|
3470
|
+
return true;
|
|
3471
|
+
const placement = source.result.dependencyPlan?.targets.find((candidate) => candidate.pluginId === pluginId && candidate.platform === entry.target.platform
|
|
3472
|
+
&& (candidate.requestedScope ?? candidate.resolvedScope) === entry.target.scope);
|
|
3473
|
+
return placement?.requiredByTargets?.some((consumer) => pluginWriteTaskTargetKey(consumer) === key) ?? false;
|
|
3474
|
+
}) ?? [];
|
|
3475
|
+
const targetResults = related.map((entry) => {
|
|
3476
|
+
const placement = source?.result.dependencyPlan?.targets.find((candidate) => candidate.pluginId === entry.target.pluginId
|
|
3477
|
+
&& candidate.platform === entry.target.platform
|
|
3478
|
+
&& (candidate.requestedScope ?? candidate.resolvedScope) === entry.target.scope);
|
|
3479
|
+
return {
|
|
3480
|
+
pluginId: entry.target.pluginId,
|
|
3481
|
+
platform: entry.target.platform,
|
|
3482
|
+
scope: entry.target.scope,
|
|
3483
|
+
state: entry.action === 'skipped' ? 'skipped'
|
|
3484
|
+
: entry.state === 'failed' ? 'failed'
|
|
3485
|
+
: entry.action === 'keep' ? 'kept' : 'completed',
|
|
3486
|
+
relation: entry.relation === 'requested' ? 'requested'
|
|
3487
|
+
: placement?.depth === 1 ? 'direct' : 'transitive',
|
|
3488
|
+
...(entry.errorCode ? { errorCode: safeDiagnosticCode(entry.errorCode, 'writeFailed') } : {}),
|
|
3489
|
+
};
|
|
3490
|
+
});
|
|
3491
|
+
if (blocked.has(key) && !targetResults.some((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
|
|
3492
|
+
&& entry.scope === target.scope)) {
|
|
3493
|
+
targetResults.unshift({
|
|
3494
|
+
pluginId: target.pluginId,
|
|
3495
|
+
platform: target.platform,
|
|
3496
|
+
scope: target.scope,
|
|
3497
|
+
state: 'skipped',
|
|
3498
|
+
relation: 'requested',
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
const hasFailed = targetResults.some((entry) => entry.state === 'failed' || entry.state === 'skipped');
|
|
3502
|
+
const hasCompleted = targetResults.some((entry) => entry.state === 'completed' || entry.state === 'kept');
|
|
3503
|
+
const state = blocked.has(key) ? 'blocked'
|
|
3504
|
+
: !source ? 'notStarted'
|
|
3505
|
+
: hasFailed && hasCompleted ? 'partial'
|
|
3506
|
+
: hasFailed || failedKeys.has(key) ? 'failed'
|
|
3507
|
+
: targetResults.length > 0 && targetResults.every((entry) => entry.state === 'kept')
|
|
3508
|
+
? 'kept' : targetResults.length > 0 ? 'completed' : 'notStarted';
|
|
3509
|
+
return { state, targets: targetResults };
|
|
3510
|
+
};
|
|
3511
|
+
const roots = [...new Set(taskExecution.taskPlan.requestedTargets
|
|
3512
|
+
.map((target) => target.pluginId))].map((pluginId) => {
|
|
3513
|
+
const requestedTargets = taskExecution.taskPlan.requestedTargets.filter((target) => target.pluginId === pluginId);
|
|
3514
|
+
const positions = requestedTargets.map((target) => relatedResults(target));
|
|
3515
|
+
const successfulPositions = positions.filter((position) => position.state === 'completed' || position.state === 'kept');
|
|
3516
|
+
const state = successfulPositions.length > 0
|
|
3517
|
+
? successfulPositions.length === positions.length
|
|
3518
|
+
? positions.every((position) => position.state === 'kept') ? 'kept' : 'completed'
|
|
3519
|
+
: 'partial'
|
|
3520
|
+
: positions.some((position) => position.state === 'failed' || position.state === 'partial')
|
|
3521
|
+
? 'failed'
|
|
3522
|
+
: positions.some((position) => position.state === 'notStarted') ? 'notStarted' : 'blocked';
|
|
3523
|
+
const targets = [...new Map(positions.flatMap((position) => position.targets).map((target) => [[
|
|
3524
|
+
target.pluginId ?? '', target.platform, target.scope,
|
|
3525
|
+
].join('\0'), target])).values()];
|
|
3526
|
+
const causeKeys = [...new Set(taskExecution.taskPlan.causes.filter((cause) => cause.consumers.some((consumer) => consumer.pluginId === pluginId))
|
|
3527
|
+
.map((cause) => cause.causeKey))];
|
|
3528
|
+
return { pluginId, state, causeKeys, targets };
|
|
3529
|
+
});
|
|
3530
|
+
const retryTargets = canonicalPluginWriteTaskTargets(roots.flatMap((root) => root.state === 'failed' || root.state === 'partial' || root.state === 'notStarted'
|
|
3531
|
+
? failedTargets.filter((target) => target.pluginId === root.pluginId) : []));
|
|
3532
|
+
const completedCount = roots.filter((root) => root.state === 'completed'
|
|
3533
|
+
|| root.state === 'kept' || root.state === 'partial').length;
|
|
3534
|
+
const failedCount = roots.filter((root) => root.state === 'failed' || root.state === 'partial'
|
|
3535
|
+
|| root.state === 'notStarted' || root.state === 'blocked').length;
|
|
3536
|
+
const code = failedCount === 0 ? 'operationSucceeded'
|
|
3537
|
+
: completedCount > 0 ? 'operationPartial' : 'operationFailed';
|
|
3538
|
+
return {
|
|
3539
|
+
code,
|
|
3540
|
+
message: code === 'operationSucceeded' ? '任务已完成。'
|
|
3541
|
+
: code === 'operationPartial' ? '任务只完成了一部分。' : '任务没有完成。',
|
|
3542
|
+
warnings: taskExecution.warnings,
|
|
3543
|
+
recoveryActions: [...new Set(taskExecution.targetResults.flatMap((entry) => entry.recoveryActions ?? []))],
|
|
3544
|
+
...(taskExecution.operationPlan?.planFingerprint
|
|
3545
|
+
? { planFingerprint: taskExecution.operationPlan.planFingerprint } : {}),
|
|
3546
|
+
task: {
|
|
3547
|
+
goal: intent.goal,
|
|
3548
|
+
requestedTargets: taskExecution.taskPlan.requestedTargets,
|
|
3549
|
+
rootResults: roots,
|
|
3550
|
+
...(retryTargets.length > 0 ? { retryTargets } : {}),
|
|
3551
|
+
blockedTargets: taskExecution.taskPlan.blockedTargets,
|
|
3552
|
+
},
|
|
3553
|
+
targets: technicalTargets,
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
2953
3556
|
if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch') {
|
|
2954
3557
|
const command = intent.action === 'refreshAndSync' ? 'sync' : intent.action;
|
|
2955
3558
|
const rawExecution = typeof execution === 'object' && execution !== null
|
|
@@ -2995,6 +3598,52 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
2995
3598
|
? pluginWriteBatchFailedRequestedTargets(execution)
|
|
2996
3599
|
: [];
|
|
2997
3600
|
const failedRequestedPluginIds = canonicalPluginIds(retryTargets.map((target) => target.pluginId));
|
|
3601
|
+
const requestedTargetKeys = intent.kind === 'pluginWriteBatch'
|
|
3602
|
+
? new Set(execution.groupResults.flatMap((group) => {
|
|
3603
|
+
const roots = new Set(group.requestedPluginIds);
|
|
3604
|
+
return group.result.targetResults.flatMap((entry) => entry.target.pluginId
|
|
3605
|
+
&& roots.has(entry.target.pluginId)
|
|
3606
|
+
&& entry.target.scope === group.scope
|
|
3607
|
+
&& (group.platform === 'both' || entry.target.platform === group.platform)
|
|
3608
|
+
? [[entry.target.pluginId, entry.target.platform, entry.target.scope].join('\0')]
|
|
3609
|
+
: []);
|
|
3610
|
+
}))
|
|
3611
|
+
: new Set();
|
|
3612
|
+
const rootResults = intent.kind === 'pluginWriteBatch'
|
|
3613
|
+
? requestedPluginIds.map((pluginId) => {
|
|
3614
|
+
const observed = (result.targetResults ?? []).flatMap((entry) => entry.target.pluginId === pluginId
|
|
3615
|
+
&& requestedTargetKeys.has([
|
|
3616
|
+
pluginId, entry.target.platform, entry.target.scope,
|
|
3617
|
+
].join('\0')) ? [{
|
|
3618
|
+
pluginId,
|
|
3619
|
+
platform: entry.target.platform,
|
|
3620
|
+
scope: entry.target.scope,
|
|
3621
|
+
state: entry.action === 'skipped' ? 'skipped'
|
|
3622
|
+
: entry.state === 'failed' ? 'failed'
|
|
3623
|
+
: entry.action === 'keep' ? 'kept' : 'completed',
|
|
3624
|
+
relation: 'requested',
|
|
3625
|
+
...(entry.errorCode
|
|
3626
|
+
? { errorCode: safeDiagnosticCode(entry.errorCode, 'writeFailed') }
|
|
3627
|
+
: {}),
|
|
3628
|
+
}] : []);
|
|
3629
|
+
const targets = [...new Map([
|
|
3630
|
+
...retryTargets.filter((target) => target.pluginId === pluginId).map((target) => ({
|
|
3631
|
+
...target,
|
|
3632
|
+
state: 'skipped',
|
|
3633
|
+
relation: 'requested',
|
|
3634
|
+
})),
|
|
3635
|
+
...observed,
|
|
3636
|
+
].map((target) => [[target.pluginId, target.platform, target.scope].join('\0'), target]))
|
|
3637
|
+
.values()];
|
|
3638
|
+
const completed = targets.filter((target) => target.state === 'completed' || target.state === 'kept').length;
|
|
3639
|
+
const failed = targets.length - completed;
|
|
3640
|
+
const state = completed > 0
|
|
3641
|
+
? failed > 0 ? 'partial'
|
|
3642
|
+
: targets.every((target) => target.state === 'kept') ? 'kept' : 'completed'
|
|
3643
|
+
: failed > 0 ? 'failed' : 'notStarted';
|
|
3644
|
+
return { pluginId, state, targets };
|
|
3645
|
+
})
|
|
3646
|
+
: [];
|
|
2998
3647
|
return {
|
|
2999
3648
|
code: result.state === 'succeeded' ? 'operationSucceeded'
|
|
3000
3649
|
: result.state === 'partial' ? 'operationPartial' : 'operationFailed',
|
|
@@ -3017,6 +3666,7 @@ function operationResultSummaryUnsafe(intent, execution) {
|
|
|
3017
3666
|
batch: {
|
|
3018
3667
|
requestedPluginIds,
|
|
3019
3668
|
failedRequestedPluginIds,
|
|
3669
|
+
rootResults,
|
|
3020
3670
|
retryTargets,
|
|
3021
3671
|
groupCount: execution.groupResults.length,
|
|
3022
3672
|
},
|