@lesliechan721/aw 0.1.0-beta.18 → 0.1.0-beta.19
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/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 +1291 -435
- 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 +85 -27
- 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 +265 -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
package/dist/src/tui/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { matchPluginConfigPreset, stablePluginConfigStringify, } from '@lesliechan721/aw-plugin-core/manifest';
|
|
2
|
-
import { statusTargetActivation, statusTargetExtensions, statusTargetIntent, statusTargetOwnershipState, statusTargetSharedNative, } from '../commands/status-v6.js';
|
|
2
|
+
import { flattenStatusTargets, statusTargetActivation, statusTargetExtensions, statusTargetIntent, statusTargetOwnershipState, statusTargetSharedNative, } from '../commands/status-v6.js';
|
|
3
3
|
import { buildAllowedLifecycleActions } from '../commands/operation-planner.js';
|
|
4
4
|
import { safeDiagnosticCode, } from './diagnostics.js';
|
|
5
5
|
import { projectCatalogSourceState } from './catalog-sources/projection.js';
|
|
@@ -184,10 +184,35 @@ function missingPureNativeScopes(plugin, placement) {
|
|
|
184
184
|
const supported = plugin.supportedScopes?.codex ?? ['user', 'project'];
|
|
185
185
|
return supported.filter((scope) => !placement.desiredScopes?.includes(scope) && placement.activation?.[scope] !== true);
|
|
186
186
|
}
|
|
187
|
+
function hasScopedExtensionOwnership(target) {
|
|
188
|
+
return target.components.some((component) => component.kind === 'ownership'
|
|
189
|
+
&& component.key !== 'native'
|
|
190
|
+
&& !component.key.startsWith('activation-')
|
|
191
|
+
&& !component.key.startsWith('marketplace-')
|
|
192
|
+
&& (component.state === 'present' || component.state === 'partial'));
|
|
193
|
+
}
|
|
187
194
|
function isInstalledScopeEvidence(target) {
|
|
195
|
+
const activation = statusTargetActivation(target);
|
|
196
|
+
const extensions = statusTargetExtensions(target);
|
|
188
197
|
return statusTargetIntent(target).enabled
|
|
189
|
-
|| statusTargetOwnershipState(target) === 'present'
|
|
190
|
-
|
|
198
|
+
|| target.platform === 'claude' && (statusTargetOwnershipState(target) === 'present'
|
|
199
|
+
|| statusTargetOwnershipState(target) === 'partial')
|
|
200
|
+
|| target.platform === 'codex' && hasScopedExtensionOwnership(target)
|
|
201
|
+
|| activation?.[target.scope] === true
|
|
202
|
+
|| extensions?.state === 'ready'
|
|
203
|
+
|| extensions?.state === 'drifted'
|
|
204
|
+
|| extensions?.state === 'conflict';
|
|
205
|
+
}
|
|
206
|
+
function hasActualScopeEvidence(target) {
|
|
207
|
+
const activation = statusTargetActivation(target);
|
|
208
|
+
const extensions = statusTargetExtensions(target);
|
|
209
|
+
return target.platform === 'claude' && (statusTargetOwnershipState(target) === 'present'
|
|
210
|
+
|| statusTargetOwnershipState(target) === 'partial')
|
|
211
|
+
|| target.platform === 'codex' && hasScopedExtensionOwnership(target)
|
|
212
|
+
|| activation?.[target.scope] === true
|
|
213
|
+
|| extensions?.state === 'ready'
|
|
214
|
+
|| extensions?.state === 'drifted'
|
|
215
|
+
|| extensions?.state === 'conflict';
|
|
191
216
|
}
|
|
192
217
|
function dependencyAssignmentExplanation(assignmentSource, requiredBy) {
|
|
193
218
|
const consumer = requiredBy[0];
|
|
@@ -296,6 +321,25 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
|
|
|
296
321
|
revision: revisionFor(errorTarget),
|
|
297
322
|
};
|
|
298
323
|
}
|
|
324
|
+
if (installed.length > 1) {
|
|
325
|
+
const projected = installed.map((target) => projectPlatformPlacement(platform, [target], revisions));
|
|
326
|
+
return {
|
|
327
|
+
...(projected.find((placement) => placement.health === 'error')
|
|
328
|
+
?? projected.find((placement) => placement.health === 'incomplete')
|
|
329
|
+
?? projected[0]),
|
|
330
|
+
scopedPlacements: projected.map((placement) => ({
|
|
331
|
+
scope: placement.scope,
|
|
332
|
+
health: placement.health,
|
|
333
|
+
revision: placement.revision,
|
|
334
|
+
...(placement.actualState ? { actualState: placement.actualState } : {}),
|
|
335
|
+
...(placement.recovery ? { recovery: placement.recovery } : {}),
|
|
336
|
+
...(placement.blockingReason ? { blockingReason: placement.blockingReason } : {}),
|
|
337
|
+
})),
|
|
338
|
+
desiredScopes: [...new Set(projected.flatMap((placement) => placement.desiredScopes ?? []))].sort(compareText),
|
|
339
|
+
actualScopes: [...new Set(installed.filter(hasActualScopeEvidence)
|
|
340
|
+
.map((target) => target.scope))].sort(compareText),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
299
343
|
const target = installed.find((candidate) => candidate.state === 'blocked')
|
|
300
344
|
?? installed.find((candidate) => candidate.state === 'needsSync')
|
|
301
345
|
?? installed.find((candidate) => candidate.state === 'orphaned')
|
|
@@ -314,6 +358,7 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
|
|
|
314
358
|
health: targetHealth(target),
|
|
315
359
|
revision: revisionFor(target),
|
|
316
360
|
desiredScopes: [...intent.scopes],
|
|
361
|
+
actualScopes: hasActualScopeEvidence(target) ? [target.scope] : [],
|
|
317
362
|
relation: intent.relation,
|
|
318
363
|
actualState: target.state,
|
|
319
364
|
...(activation ? { activation } : {}),
|
|
@@ -325,6 +370,9 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
|
|
|
325
370
|
}
|
|
326
371
|
function hasIncompletePlacement(plugin) {
|
|
327
372
|
return plugin.installedPlacements.some((placement) => placement.state === 'scopeConflict'
|
|
373
|
+
|| placement.state === 'installed'
|
|
374
|
+
&& placement.scopedPlacements?.some((entry) => entry.health !== 'ready'
|
|
375
|
+
|| entry.blockingReason !== undefined)
|
|
328
376
|
|| placement.state === 'installed'
|
|
329
377
|
&& (placement.health !== 'ready' || placement.blockingReason !== undefined));
|
|
330
378
|
}
|
|
@@ -436,6 +484,17 @@ export function buildPlatformActions(plugin, platform) {
|
|
|
436
484
|
return [{ id: 'add' }, { id: 'about' }];
|
|
437
485
|
if (placement.state === 'scopeConflict')
|
|
438
486
|
return [{ id: 'scopeConflict', platform }];
|
|
487
|
+
if (placement.scopedPlacements) {
|
|
488
|
+
const actions = placement.scopedPlacements.flatMap((entry) => buildPlatformActions({
|
|
489
|
+
...plugin,
|
|
490
|
+
installedPlacements: [{
|
|
491
|
+
...placement,
|
|
492
|
+
...entry,
|
|
493
|
+
scopedPlacements: undefined,
|
|
494
|
+
}],
|
|
495
|
+
}, platform));
|
|
496
|
+
return [...new Map(actions.map((action) => [JSON.stringify(action), action])).values()];
|
|
497
|
+
}
|
|
439
498
|
const scopedRecovery = scopeConflictRecoveryMenuActions(placement);
|
|
440
499
|
if (scopedRecovery.length > 0)
|
|
441
500
|
return [...scopedRecovery, { id: 'technical' }];
|
|
@@ -473,7 +532,9 @@ export function buildPlatformActions(plugin, platform) {
|
|
|
473
532
|
: [];
|
|
474
533
|
}
|
|
475
534
|
if (action.id === 'sync')
|
|
476
|
-
return [{
|
|
535
|
+
return [{
|
|
536
|
+
id: 'sync', platform: action.platform, scope: action.scope,
|
|
537
|
+
}];
|
|
477
538
|
if (action.id === 'reinstall')
|
|
478
539
|
return [{ id: 'reinstall', platform: action.platform, scope: action.scope }];
|
|
479
540
|
if (action.id === 'uninstall')
|
|
@@ -494,24 +555,33 @@ export function buildPlatformActions(plugin, platform) {
|
|
|
494
555
|
export function hasInstalledPlacement(plugin) {
|
|
495
556
|
return plugin.installedPlacements.some((placement) => placement.state === 'installed' || placement.state === 'scopeConflict');
|
|
496
557
|
}
|
|
497
|
-
function sourceIssue(health,
|
|
558
|
+
function sourceIssue(health, targetIssues = []) {
|
|
498
559
|
if (health.state !== 'unavailable')
|
|
499
560
|
return undefined;
|
|
500
561
|
const issueId = health.issueId ?? `source:${health.sourceId}:unavailable`;
|
|
562
|
+
const affectedTargets = [...new Map(targetIssues.flatMap((issue) => issue.affectedTargets ?? [])
|
|
563
|
+
.map((target) => [`${target.pluginId}\0${target.platform}\0${target.scope}`, target])).values()]
|
|
564
|
+
.sort((left, right) => compareText(left.pluginId, right.pluginId)
|
|
565
|
+
|| compareText(left.platform, right.platform)
|
|
566
|
+
|| compareText(left.scope, right.scope));
|
|
567
|
+
const cleanupActions = targetIssues.flatMap((issue) => issue.actions)
|
|
568
|
+
.filter((action) => action.id === 'cleanup');
|
|
501
569
|
return {
|
|
502
570
|
issueId,
|
|
503
571
|
category: 'sourceUnavailable',
|
|
504
572
|
title: `${health.sourceId} · 来源不可用`,
|
|
505
573
|
explanation: '来源检查失败。',
|
|
506
574
|
impact: ['当前不能浏览或安装这个来源的插件。',
|
|
507
|
-
...(
|
|
575
|
+
...(affectedTargets.length > 0 ? [`${affectedTargets.length} 个安装位置受此来源根问题影响。`] : [])],
|
|
508
576
|
entity: { kind: 'source', id: health.sourceId },
|
|
509
577
|
sourceId: health.sourceId,
|
|
510
|
-
|
|
578
|
+
...(affectedTargets.length > 0 ? { affectedTargets } : {}),
|
|
579
|
+
actions: dedupeIssueActions([
|
|
580
|
+
...cleanupActions,
|
|
511
581
|
{ id: 'editSource', label: '编辑来源', description: '检查 URL、路径或 ref' },
|
|
512
582
|
{ id: 'refreshSource', label: '稍后重试', description: '只重新检查来源' },
|
|
513
583
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
514
|
-
],
|
|
584
|
+
]),
|
|
515
585
|
technicalDetails: [{
|
|
516
586
|
code: 'catalogSourceUnavailable',
|
|
517
587
|
message: '来源检查失败。',
|
|
@@ -565,8 +635,9 @@ function lifecycleIssueActions(target, options = {}) {
|
|
|
565
635
|
}
|
|
566
636
|
function targetIssues(payload) {
|
|
567
637
|
const issues = [];
|
|
638
|
+
const statusTargets = flattenStatusTargets(payload.targets);
|
|
568
639
|
const canonicalRootsByEntity = new Map();
|
|
569
|
-
for (const target of
|
|
640
|
+
for (const target of statusTargets) {
|
|
570
641
|
for (const error of target.reasons) {
|
|
571
642
|
if (!error.rootIssueId || !error.rootIssueCode)
|
|
572
643
|
continue;
|
|
@@ -588,46 +659,98 @@ function targetIssues(payload) {
|
|
|
588
659
|
}
|
|
589
660
|
}
|
|
590
661
|
for (const target of payload.targets) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
662
|
+
if (target.platform !== 'codex' || !target.reasons.some((reason) => reason.code === 'codexExtensionScopeConflict'))
|
|
663
|
+
continue;
|
|
664
|
+
const recoveries = target.recoveryActions.flatMap((recovery) => {
|
|
665
|
+
const parsed = parsedScopedUninstallRecovery(recovery, target.platform);
|
|
666
|
+
return parsed ? [{ ...parsed, keepScope: parsed.scope === 'user'
|
|
667
|
+
? 'project' : 'user' }] : [];
|
|
668
|
+
});
|
|
669
|
+
issues.push({
|
|
670
|
+
issueId: `scope:${target.pluginId}:${target.platform}`,
|
|
671
|
+
category: 'scopeConflict',
|
|
672
|
+
title: `${target.pluginId} · Codex · 扩展位置冲突`,
|
|
673
|
+
explanation: 'AW 管理附加能力不能同时使用所有项目和当前项目位置。',
|
|
674
|
+
impact: ['选择要保留的使用位置。系统将移除另一位置,再同步保留的位置。'],
|
|
675
|
+
entity: { kind: 'plugin', id: target.pluginId },
|
|
676
|
+
pluginId: target.pluginId,
|
|
677
|
+
platform: target.platform,
|
|
678
|
+
sourceId: target.sourceId,
|
|
679
|
+
sourceIdentity: target.sourceIdentity,
|
|
680
|
+
placements: target.placements.map((placement) => placement.scope).sort(compareText),
|
|
681
|
+
actions: [
|
|
682
|
+
...recoveries.map((recovery) => ({
|
|
683
|
+
id: recovery.keepScope === 'user' ? 'keepUserScope' : 'keepProjectScope',
|
|
684
|
+
label: `保留${recovery.keepScope === 'user' ? '所有项目' : '当前项目'}`,
|
|
685
|
+
description: recovery.description,
|
|
686
|
+
pluginId: recovery.pluginId,
|
|
687
|
+
platform: target.platform,
|
|
688
|
+
scope: recovery.scope,
|
|
689
|
+
writeAction: 'uninstall',
|
|
690
|
+
})),
|
|
691
|
+
{ id: 'viewTechnical', label: '查看冲突详情' },
|
|
692
|
+
],
|
|
693
|
+
technicalDetails: [{
|
|
694
|
+
code: 'codexExtensionScopeConflict',
|
|
695
|
+
message: '扩展同时使用 user 和 project scope。',
|
|
696
|
+
}],
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
for (const target of payload.targets) {
|
|
700
|
+
if (target.platform !== 'codex')
|
|
701
|
+
continue;
|
|
702
|
+
const sharedReasons = target.reasons.filter((reason) => reason.code.startsWith('codexSharedNative') || reason.code === 'staleCacheObserved');
|
|
703
|
+
if (sharedReasons.length === 0 || target.state === 'ready' || target.state === 'absent')
|
|
704
|
+
continue;
|
|
705
|
+
const codes = new Set(sharedReasons.map((reason) => reason.code));
|
|
706
|
+
const sharedNative = statusTargetSharedNative(target);
|
|
707
|
+
const statusIssue = codes.has('codexSharedNativeForeign')
|
|
708
|
+
? { title: '平台共享内容被占用', explanation: 'Codex 平台共享位置包含 foreign 内容。' }
|
|
709
|
+
: codes.has('codexSharedNativeUpdateRequired')
|
|
710
|
+
? { title: '版本待同步', explanation: 'Codex 平台共享内容版本与当前插件版本不一致。' }
|
|
711
|
+
: codes.has('codexSharedNativeUnobserved')
|
|
712
|
+
? { title: '状态未确认', explanation: '未取得有效的 Codex 共享内容状态证据。' }
|
|
713
|
+
: { title: '平台共享内容待同步', explanation: 'Codex 平台共享内容缺失、损坏或与当前渲染输入不一致。' };
|
|
714
|
+
const recoveryActions = target.recoveryActions.flatMap((recovery) => {
|
|
715
|
+
if (!recovery.scope)
|
|
716
|
+
return [];
|
|
717
|
+
const scopedTarget = statusTargets.find((candidate) => candidate.pluginId === target.pluginId
|
|
718
|
+
&& candidate.platform === target.platform && candidate.scope === recovery.scope);
|
|
719
|
+
return scopedTarget && isInstalledScopeEvidence(scopedTarget)
|
|
720
|
+
? lifecycleIssueActions({ ...scopedTarget, recoveryActions: [recovery] })
|
|
721
|
+
: [];
|
|
722
|
+
});
|
|
723
|
+
issues.push({
|
|
724
|
+
issueId: `sharedNative:${target.pluginId}`,
|
|
725
|
+
category: target.state === 'blocked' ? 'diagnostic' : 'incompleteInstallation',
|
|
726
|
+
title: `${target.pluginId} · Codex · ${statusIssue.title}`,
|
|
727
|
+
explanation: statusIssue.explanation,
|
|
728
|
+
impact: [sharedNative?.state === 'blocked'
|
|
729
|
+
? '共享内容恢复前,当前插件不能安全同步。'
|
|
730
|
+
: '共享内容恢复前,相关 Codex 使用位置可能不可用。'],
|
|
731
|
+
entity: { kind: 'plugin', id: target.pluginId },
|
|
732
|
+
pluginId: target.pluginId,
|
|
733
|
+
platform: target.platform,
|
|
734
|
+
sourceId: target.sourceId,
|
|
735
|
+
sourceIdentity: target.sourceIdentity,
|
|
736
|
+
placements: target.placements.map((placement) => placement.scope).sort(compareText),
|
|
737
|
+
actions: [
|
|
738
|
+
...new Map(recoveryActions.map((action) => [
|
|
739
|
+
`${action.id}\0${action.scope ?? ''}`,
|
|
740
|
+
action,
|
|
741
|
+
])).values(),
|
|
742
|
+
{ id: 'viewTechnical', label: '技术详情' },
|
|
743
|
+
],
|
|
744
|
+
technicalDetails: sharedReasons.map((reason) => ({
|
|
745
|
+
code: reason.code,
|
|
746
|
+
message: reason.message,
|
|
747
|
+
})),
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
for (const target of statusTargets) {
|
|
751
|
+
if (target.reasons.length === 0 && (target.sharedReasons?.length ?? 0) > 0
|
|
752
|
+
&& isPureNativeCodexTarget(target))
|
|
753
|
+
continue;
|
|
631
754
|
if (target.state !== 'needsSync' && target.state !== 'blocked'
|
|
632
755
|
&& !target.reasons.some((error) => error.code === 'artifactCollision'
|
|
633
756
|
|| error.dependencyPluginId !== undefined && error.rootIssueId !== undefined))
|
|
@@ -736,37 +859,35 @@ function targetIssues(payload) {
|
|
|
736
859
|
const dependencyScope = rootError?.dependencyScope;
|
|
737
860
|
const dependencyProject = rootError?.dependencyProject;
|
|
738
861
|
const dependencyCategory = root.category.startsWith('dependency');
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
scope: dependencyScope,
|
|
748
|
-
state: 'absent',
|
|
749
|
-
recoveryActions: [],
|
|
750
|
-
}, { dependency: true, project: dependencyProject })
|
|
751
|
-
: [];
|
|
752
|
-
const directLifecycleActions = dependencyCategory ? [] : lifecycleIssueActions(target);
|
|
862
|
+
const directLifecycleActions = lifecycleIssueActions(target);
|
|
863
|
+
const desiredConsumer = statusTargetIntent(target).enabled
|
|
864
|
+
&& statusTargetIntent(target).scopes.includes(target.scope);
|
|
865
|
+
const consumerRecoveryActions = dependencyCategory
|
|
866
|
+
? directLifecycleActions.filter((action) => desiredConsumer
|
|
867
|
+
? action.id === 'sync' || action.id === 'reinstall' || action.id === 'refreshAndSync'
|
|
868
|
+
: action.id === 'cleanup')
|
|
869
|
+
: directLifecycleActions;
|
|
753
870
|
const actions = root.category === 'dependencyMissing'
|
|
754
871
|
? [
|
|
755
|
-
...
|
|
872
|
+
...consumerRecoveryActions,
|
|
873
|
+
...(dependencyPluginId ? [
|
|
874
|
+
{ id: 'viewDependencyIssue', label: '查看依赖插件问题', pluginId: dependencyPluginId },
|
|
875
|
+
] : []),
|
|
756
876
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
757
877
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
758
878
|
]
|
|
759
879
|
: root.category === 'dependencyNotReady'
|
|
760
880
|
? [
|
|
881
|
+
...consumerRecoveryActions,
|
|
761
882
|
...(dependencyPluginId ? [
|
|
762
883
|
{ id: 'viewDependencyIssue', label: '查看依赖插件问题', pluginId: dependencyPluginId },
|
|
763
884
|
] : []),
|
|
764
|
-
...dependencyLifecycleActions.filter((action) => action.id === 'reinstallDependency'),
|
|
765
885
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
766
886
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
767
887
|
]
|
|
768
888
|
: dependencyCategory
|
|
769
889
|
? [
|
|
890
|
+
...consumerRecoveryActions,
|
|
770
891
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
771
892
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
772
893
|
]
|
|
@@ -777,12 +898,12 @@ function targetIssues(payload) {
|
|
|
777
898
|
]
|
|
778
899
|
: root.category === 'incompleteInstallation'
|
|
779
900
|
? [
|
|
780
|
-
...
|
|
901
|
+
...consumerRecoveryActions,
|
|
781
902
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
782
903
|
]
|
|
783
904
|
: root.category === 'sourceMissing'
|
|
784
905
|
? [
|
|
785
|
-
...
|
|
906
|
+
...consumerRecoveryActions,
|
|
786
907
|
{ id: 'reAddSource', label: '重新添加同一来源', description: '保留来源 ID;来源类型和位置由你选择' },
|
|
787
908
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
788
909
|
]
|
|
@@ -793,7 +914,7 @@ function targetIssues(payload) {
|
|
|
793
914
|
]
|
|
794
915
|
: root.category === 'sourceUnavailable'
|
|
795
916
|
? [
|
|
796
|
-
...
|
|
917
|
+
...consumerRecoveryActions.filter((action) => action.id === 'cleanup'),
|
|
797
918
|
{ id: 'editSource', label: '查看来源', description: root.code === 'bundleUnavailable'
|
|
798
919
|
? '检查当前插件目录或稍后重试'
|
|
799
920
|
: root.code === 'codexIntentSourceUnavailable'
|
|
@@ -801,7 +922,7 @@ function targetIssues(payload) {
|
|
|
801
922
|
: '修复来源配置或稍后重试' },
|
|
802
923
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
803
924
|
]
|
|
804
|
-
: [...
|
|
925
|
+
: [...consumerRecoveryActions,
|
|
805
926
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
806
927
|
{ id: 'retryLater', label: '稍后重试' }];
|
|
807
928
|
const bundleUnavailable = root.code === 'bundleUnavailable';
|
|
@@ -840,7 +961,7 @@ function targetIssues(payload) {
|
|
|
840
961
|
scope: target.scope,
|
|
841
962
|
relationship: relationship.relationship,
|
|
842
963
|
}],
|
|
843
|
-
title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${sharedNativeProjectionIssue ? '平台共享内容待同步' : root.category === 'artifactCollision' ? '输出冲突' : dependencyCategory ? {
|
|
964
|
+
title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${dependencyCategory ? `${target.scope === 'user' ? '所有项目' : '当前项目'} · ` : ''}${sharedNativeProjectionIssue ? '平台共享内容待同步' : root.category === 'artifactCollision' ? '输出冲突' : dependencyCategory ? {
|
|
844
965
|
dependencyMissing: '缺少依赖',
|
|
845
966
|
dependencyNotReady: '依赖安装不完整',
|
|
846
967
|
dependencyTargetUnsupported: '依赖目标不兼容',
|
|
@@ -851,8 +972,10 @@ function targetIssues(payload) {
|
|
|
851
972
|
? '目标位置已有不属于当前插件的内容。AW 不会覆盖或删除这些内容。'
|
|
852
973
|
: dependencyCategory
|
|
853
974
|
? dependencyPluginId
|
|
854
|
-
?
|
|
855
|
-
|
|
975
|
+
? `依赖 ${dependencyPluginId}${dependencyPlatform && dependencyScope
|
|
976
|
+
? `(${dependencyPlatform === 'codex' ? 'Codex' : 'Claude Code'} · ${dependencyScope === 'user' ? '所有项目' : '当前项目'})`
|
|
977
|
+
: ''}未就绪;影响 ${target.pluginId}(${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.scope === 'user' ? '所有项目' : '当前项目'})。`
|
|
978
|
+
: `依赖检查未通过;影响 ${target.pluginId}(${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.scope === 'user' ? '所有项目' : '当前项目'})。`
|
|
856
979
|
: sharedNativeDamaged
|
|
857
980
|
? 'Codex 平台共享生成内容缺失或损坏。'
|
|
858
981
|
: sharedNativeRenderDrift
|
|
@@ -1216,6 +1339,14 @@ function issueReasonPriority(issue) {
|
|
|
1216
1339
|
diagnostic: 9,
|
|
1217
1340
|
}[code] ?? 9;
|
|
1218
1341
|
}
|
|
1342
|
+
function dedupeIssueActions(actions) {
|
|
1343
|
+
return [...new Map(actions.map((action) => [[
|
|
1344
|
+
action.id,
|
|
1345
|
+
action.pluginId ?? '',
|
|
1346
|
+
action.platform ?? '',
|
|
1347
|
+
action.scope ?? '',
|
|
1348
|
+
].join('\0'), action])).values()];
|
|
1349
|
+
}
|
|
1219
1350
|
function collapseRootIssues(issues) {
|
|
1220
1351
|
const byId = new Map();
|
|
1221
1352
|
for (const issue of issues)
|
|
@@ -1253,6 +1384,7 @@ function collapseRootIssues(issues) {
|
|
|
1253
1384
|
impact: [...new Set(ordered.flatMap((issue) => issue.impact))],
|
|
1254
1385
|
technicalDetails: [...new Map(ordered.flatMap((issue) => issue.technicalDetails)
|
|
1255
1386
|
.map((detail) => [`${detail.code}\0${detail.message}`, detail])).values()],
|
|
1387
|
+
actions: dedupeIssueActions(ordered.flatMap((issue) => issue.actions)),
|
|
1256
1388
|
};
|
|
1257
1389
|
});
|
|
1258
1390
|
}
|
|
@@ -1262,7 +1394,7 @@ export function buildTuiViewModel(options) {
|
|
|
1262
1394
|
const summaryById = new Map(options.pluginSummaries.map((summary) => [summary.pluginId, summary]));
|
|
1263
1395
|
const sourceById = new Map(options.status.catalog.sources.map((source) => [source.id, source]));
|
|
1264
1396
|
const targetsByPlugin = new Map();
|
|
1265
|
-
for (const target of options.status.targets) {
|
|
1397
|
+
for (const target of flattenStatusTargets(options.status.targets)) {
|
|
1266
1398
|
targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
|
|
1267
1399
|
}
|
|
1268
1400
|
const configScopesByPlugin = new Map();
|
|
@@ -1285,6 +1417,21 @@ export function buildTuiViewModel(options) {
|
|
|
1285
1417
|
const configOnly = summary === undefined && targets.length === 0 && savedConfigScopes.length > 0;
|
|
1286
1418
|
const placements = ['codex', 'claude'].flatMap((platform) => {
|
|
1287
1419
|
const platformTargets = targets.filter((target) => target.platform === platform);
|
|
1420
|
+
const aggregateConflict = platform === 'codex' && options.status.targets.some((target) => target.pluginId === pluginId
|
|
1421
|
+
&& target.platform === platform
|
|
1422
|
+
&& target.state === 'blocked'
|
|
1423
|
+
&& target.reasons.some((reason) => reason.code === 'codexExtensionScopeConflict'));
|
|
1424
|
+
if (aggregateConflict) {
|
|
1425
|
+
const scoped = platformTargets.flatMap((target) => {
|
|
1426
|
+
const projected = projectPlatformPlacement(platform, [target], options.targetRevisions);
|
|
1427
|
+
return projected.state === 'installed' ? [{
|
|
1428
|
+
scope: projected.scope,
|
|
1429
|
+
health: projected.health,
|
|
1430
|
+
revision: projected.revision,
|
|
1431
|
+
}] : [];
|
|
1432
|
+
});
|
|
1433
|
+
return [{ platform, state: 'scopeConflict', placements: scoped }];
|
|
1434
|
+
}
|
|
1288
1435
|
return platformTargets.length > 0
|
|
1289
1436
|
? [projectPlatformPlacement(platform, platformTargets, options.targetRevisions)]
|
|
1290
1437
|
: [];
|
|
@@ -1351,15 +1498,31 @@ export function buildTuiViewModel(options) {
|
|
|
1351
1498
|
issueCount: summary.dependencyIssueCount ?? 0,
|
|
1352
1499
|
} }
|
|
1353
1500
|
: {}),
|
|
1354
|
-
technicalComponents:
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
:
|
|
1362
|
-
|
|
1501
|
+
technicalComponents: [
|
|
1502
|
+
...targets.flatMap((target) => [
|
|
1503
|
+
...target.components,
|
|
1504
|
+
...(target.sharedComponents ?? []).filter((component) => component.kind === 'systemConsumer'),
|
|
1505
|
+
].map((component) => ({
|
|
1506
|
+
platform: target.platform,
|
|
1507
|
+
scope: target.scope,
|
|
1508
|
+
key: component.key,
|
|
1509
|
+
state: component.state,
|
|
1510
|
+
...(typeof component.metadata?.errorCode === 'string'
|
|
1511
|
+
? { errorCode: component.metadata.errorCode }
|
|
1512
|
+
: {}),
|
|
1513
|
+
}))),
|
|
1514
|
+
...new Map(targets.flatMap((target) => (target.sharedComponents ?? [])
|
|
1515
|
+
.filter((component) => component.kind === 'sharedNative')
|
|
1516
|
+
.map((component) => [`${target.platform}\0${component.key}`, {
|
|
1517
|
+
platform: target.platform,
|
|
1518
|
+
shared: true,
|
|
1519
|
+
key: component.key,
|
|
1520
|
+
state: component.state,
|
|
1521
|
+
...(typeof component.metadata?.errorCode === 'string'
|
|
1522
|
+
? { errorCode: component.metadata.errorCode }
|
|
1523
|
+
: {}),
|
|
1524
|
+
}]))).values(),
|
|
1525
|
+
],
|
|
1363
1526
|
update,
|
|
1364
1527
|
};
|
|
1365
1528
|
}).sort((left, right) => compareText(left.displayName, right.displayName)
|
|
@@ -1375,11 +1538,11 @@ export function buildTuiViewModel(options) {
|
|
|
1375
1538
|
.filter((health) => health.state === 'unavailable')
|
|
1376
1539
|
.map((health) => health.sourceId));
|
|
1377
1540
|
const allTargetIssues = targetIssues(options.status);
|
|
1378
|
-
const
|
|
1541
|
+
const mergedTargetIssues = new Map();
|
|
1379
1542
|
for (const issue of allTargetIssues) {
|
|
1380
1543
|
if (!issue.mergeEligible || !issue.sourceId || !sourceRootIds.has(issue.sourceId))
|
|
1381
1544
|
continue;
|
|
1382
|
-
|
|
1545
|
+
mergedTargetIssues.set(issue.sourceId, [...(mergedTargetIssues.get(issue.sourceId) ?? []), issue]);
|
|
1383
1546
|
}
|
|
1384
1547
|
const sourceDetails = new Map((options.sourceDetails ?? []).map((detail) => [detail.id, detail]));
|
|
1385
1548
|
const issueDrafts = [
|
|
@@ -1388,7 +1551,7 @@ export function buildTuiViewModel(options) {
|
|
|
1388
1551
|
...statusIssues(options.status),
|
|
1389
1552
|
...configIssues(options.pluginSummaries),
|
|
1390
1553
|
...dependencyOverviewIssues(options.dependencyOverviews ?? [], options.pluginSummaries, options.status),
|
|
1391
|
-
...[...(options.sourceHealth?.values() ?? [])].flatMap((health) => sourceIssue(health,
|
|
1554
|
+
...[...(options.sourceHealth?.values() ?? [])].flatMap((health) => sourceIssue(health, mergedTargetIssues.get(health.sourceId) ?? []) ?? []),
|
|
1392
1555
|
];
|
|
1393
1556
|
const stableIssueDrafts = collapseRootIssues(issueDrafts).sort((left, right) => issueGroupOrder[issueGroup(left)] - issueGroupOrder[issueGroup(right)]
|
|
1394
1557
|
|| issueReasonPriority(left) - issueReasonPriority(right)
|
|
@@ -1429,7 +1592,9 @@ export function buildTuiViewModel(options) {
|
|
|
1429
1592
|
dependencyOverviews: options.dependencyOverviews ?? [],
|
|
1430
1593
|
home: {
|
|
1431
1594
|
availablePlugins: pluginsWithAttention.filter((plugin) => plugin.aggregateStatus === 'available').length,
|
|
1432
|
-
installedPlugins: pluginsWithAttention
|
|
1595
|
+
installedPlugins: new Set(pluginsWithAttention
|
|
1596
|
+
.filter(hasInstalledPlacement)
|
|
1597
|
+
.map((plugin) => plugin.pluginId)).size,
|
|
1433
1598
|
issueCount: issues.length,
|
|
1434
1599
|
sourceCount: sourceTabs.length,
|
|
1435
1600
|
unavailableSourceCount: options.status.catalog.sources.filter((source) => source.error
|