@lesliechan721/aw 0.1.0-beta.17 → 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/rebuild-command.js +4 -5
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/status-v6.d.ts +83 -33
- package/dist/src/commands/status-v6.js +531 -191
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/wizard.d.ts +2 -2
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- 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 +1296 -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 +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 +276 -89
- 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/codex-manager.d.ts +5 -7
- package/dist/src/utils/codex-manager.js +24 -97
- package/dist/src/utils/codex-manager.js.map +1 -1
- 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))
|
|
@@ -638,6 +761,28 @@ function targetIssues(payload) {
|
|
|
638
761
|
const sharedNativeDamaged = target.state === 'needsSync'
|
|
639
762
|
&& codes.has('codexSharedNativeCorrupt');
|
|
640
763
|
const sharedNativeProjectionIssue = sharedNativeRenderDrift || sharedNativeDamaged;
|
|
764
|
+
const sharedNative = statusTargetSharedNative(target);
|
|
765
|
+
const systemConsumer = statusTargetActivation(target)?.systemConsumer;
|
|
766
|
+
const statusUnconfirmed = systemConsumer?.state === 'unresolved'
|
|
767
|
+
|| codes.has('codexSharedNativeUnobserved')
|
|
768
|
+
|| codes.has('codexNativeObservationFailed')
|
|
769
|
+
|| (sharedNative?.state === 'blocked' && sharedNative.reason === 'unreadable');
|
|
770
|
+
const contentMissing = codes.has('missingContent')
|
|
771
|
+
|| sharedNative?.state === 'missing'
|
|
772
|
+
|| statusTargetExtensions(target)?.state === 'missing'
|
|
773
|
+
|| target.components.some((component) => component.kind === 'ownership'
|
|
774
|
+
&& component.state === 'absent');
|
|
775
|
+
const statusIssue = systemConsumer?.state === 'incompatible'
|
|
776
|
+
? { title: 'Codex 版本不兼容', explanation: '当前 Codex 版本低于要求,请升级后重试。' }
|
|
777
|
+
: statusUnconfirmed
|
|
778
|
+
? { title: '状态未确认', explanation: '未取得有效的 Codex 状态证据,暂时无法确认插件是否可用。' }
|
|
779
|
+
: codes.has('codexActivationDrift')
|
|
780
|
+
? { title: '激活范围不一致', explanation: 'Codex 激活配置与期望的使用范围不一致。' }
|
|
781
|
+
: codes.has('codexSharedNativeUpdateRequired')
|
|
782
|
+
? { title: '版本待同步', explanation: '已安装的 Codex 平台共享内容版本与当前插件版本不一致。' }
|
|
783
|
+
: sharedNativeDamaged || contentMissing
|
|
784
|
+
? { title: '安装不完整', explanation: '必要内容缺失或损坏,需要运行同步。' }
|
|
785
|
+
: { title: '状态待同步', explanation: '现场状态与期望状态不一致,需要重新检查并同步。' };
|
|
641
786
|
const dependencyCodes = [
|
|
642
787
|
'dependencyMissing',
|
|
643
788
|
'dependencyNotReady',
|
|
@@ -714,37 +859,35 @@ function targetIssues(payload) {
|
|
|
714
859
|
const dependencyScope = rootError?.dependencyScope;
|
|
715
860
|
const dependencyProject = rootError?.dependencyProject;
|
|
716
861
|
const dependencyCategory = root.category.startsWith('dependency');
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
scope: dependencyScope,
|
|
726
|
-
state: 'absent',
|
|
727
|
-
recoveryActions: [],
|
|
728
|
-
}, { dependency: true, project: dependencyProject })
|
|
729
|
-
: [];
|
|
730
|
-
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;
|
|
731
870
|
const actions = root.category === 'dependencyMissing'
|
|
732
871
|
? [
|
|
733
|
-
...
|
|
872
|
+
...consumerRecoveryActions,
|
|
873
|
+
...(dependencyPluginId ? [
|
|
874
|
+
{ id: 'viewDependencyIssue', label: '查看依赖插件问题', pluginId: dependencyPluginId },
|
|
875
|
+
] : []),
|
|
734
876
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
735
877
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
736
878
|
]
|
|
737
879
|
: root.category === 'dependencyNotReady'
|
|
738
880
|
? [
|
|
881
|
+
...consumerRecoveryActions,
|
|
739
882
|
...(dependencyPluginId ? [
|
|
740
883
|
{ id: 'viewDependencyIssue', label: '查看依赖插件问题', pluginId: dependencyPluginId },
|
|
741
884
|
] : []),
|
|
742
|
-
...dependencyLifecycleActions.filter((action) => action.id === 'reinstallDependency'),
|
|
743
885
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
744
886
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
745
887
|
]
|
|
746
888
|
: dependencyCategory
|
|
747
889
|
? [
|
|
890
|
+
...consumerRecoveryActions,
|
|
748
891
|
{ id: 'viewDependencies', label: '查看依赖关系' },
|
|
749
892
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
750
893
|
]
|
|
@@ -755,12 +898,12 @@ function targetIssues(payload) {
|
|
|
755
898
|
]
|
|
756
899
|
: root.category === 'incompleteInstallation'
|
|
757
900
|
? [
|
|
758
|
-
...
|
|
901
|
+
...consumerRecoveryActions,
|
|
759
902
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
760
903
|
]
|
|
761
904
|
: root.category === 'sourceMissing'
|
|
762
905
|
? [
|
|
763
|
-
...
|
|
906
|
+
...consumerRecoveryActions,
|
|
764
907
|
{ id: 'reAddSource', label: '重新添加同一来源', description: '保留来源 ID;来源类型和位置由你选择' },
|
|
765
908
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
766
909
|
]
|
|
@@ -771,7 +914,7 @@ function targetIssues(payload) {
|
|
|
771
914
|
]
|
|
772
915
|
: root.category === 'sourceUnavailable'
|
|
773
916
|
? [
|
|
774
|
-
...
|
|
917
|
+
...consumerRecoveryActions.filter((action) => action.id === 'cleanup'),
|
|
775
918
|
{ id: 'editSource', label: '查看来源', description: root.code === 'bundleUnavailable'
|
|
776
919
|
? '检查当前插件目录或稍后重试'
|
|
777
920
|
: root.code === 'codexIntentSourceUnavailable'
|
|
@@ -779,7 +922,7 @@ function targetIssues(payload) {
|
|
|
779
922
|
: '修复来源配置或稍后重试' },
|
|
780
923
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
781
924
|
]
|
|
782
|
-
: [...
|
|
925
|
+
: [...consumerRecoveryActions,
|
|
783
926
|
{ id: 'viewTechnical', label: '技术详情' },
|
|
784
927
|
{ id: 'retryLater', label: '稍后重试' }];
|
|
785
928
|
const bundleUnavailable = root.code === 'bundleUnavailable';
|
|
@@ -818,25 +961,27 @@ function targetIssues(payload) {
|
|
|
818
961
|
scope: target.scope,
|
|
819
962
|
relationship: relationship.relationship,
|
|
820
963
|
}],
|
|
821
|
-
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 ? {
|
|
822
965
|
dependencyMissing: '缺少依赖',
|
|
823
966
|
dependencyNotReady: '依赖安装不完整',
|
|
824
967
|
dependencyTargetUnsupported: '依赖目标不兼容',
|
|
825
968
|
dependencyStillRequired: '依赖仍被使用',
|
|
826
969
|
dependencyGraphInvalid: '依赖关系无效',
|
|
827
|
-
}[root.category] : target.state === 'needsSync'
|
|
970
|
+
}[root.category] : target.state === 'needsSync' || statusUnconfirmed || systemConsumer?.state === 'incompatible' ? statusIssue.title : bundleUnavailable ? '当前插件目录不再包含此插件' : codexIntentSourceUnavailable ? '已保存使用设置不能解析' : '检查失败'}`,
|
|
828
971
|
explanation: root.category === 'artifactCollision'
|
|
829
972
|
? '目标位置已有不属于当前插件的内容。AW 不会覆盖或删除这些内容。'
|
|
830
973
|
: dependencyCategory
|
|
831
974
|
? dependencyPluginId
|
|
832
|
-
?
|
|
833
|
-
|
|
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' ? '所有项目' : '当前项目'})。`
|
|
834
979
|
: sharedNativeDamaged
|
|
835
980
|
? 'Codex 平台共享生成内容缺失或损坏。'
|
|
836
981
|
: sharedNativeRenderDrift
|
|
837
982
|
? 'Codex 平台共享生成内容与当前渲染输入不一致。'
|
|
838
|
-
: target.state === 'needsSync'
|
|
839
|
-
?
|
|
983
|
+
: target.state === 'needsSync' || statusUnconfirmed || systemConsumer?.state === 'incompatible'
|
|
984
|
+
? statusIssue.explanation
|
|
840
985
|
: bundleUnavailable
|
|
841
986
|
? '当前插件目录不再包含此插件,但仍发现 AW 管理的已安装内容。'
|
|
842
987
|
: codexIntentSourceUnavailable
|
|
@@ -1194,6 +1339,14 @@ function issueReasonPriority(issue) {
|
|
|
1194
1339
|
diagnostic: 9,
|
|
1195
1340
|
}[code] ?? 9;
|
|
1196
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
|
+
}
|
|
1197
1350
|
function collapseRootIssues(issues) {
|
|
1198
1351
|
const byId = new Map();
|
|
1199
1352
|
for (const issue of issues)
|
|
@@ -1231,6 +1384,7 @@ function collapseRootIssues(issues) {
|
|
|
1231
1384
|
impact: [...new Set(ordered.flatMap((issue) => issue.impact))],
|
|
1232
1385
|
technicalDetails: [...new Map(ordered.flatMap((issue) => issue.technicalDetails)
|
|
1233
1386
|
.map((detail) => [`${detail.code}\0${detail.message}`, detail])).values()],
|
|
1387
|
+
actions: dedupeIssueActions(ordered.flatMap((issue) => issue.actions)),
|
|
1234
1388
|
};
|
|
1235
1389
|
});
|
|
1236
1390
|
}
|
|
@@ -1240,7 +1394,7 @@ export function buildTuiViewModel(options) {
|
|
|
1240
1394
|
const summaryById = new Map(options.pluginSummaries.map((summary) => [summary.pluginId, summary]));
|
|
1241
1395
|
const sourceById = new Map(options.status.catalog.sources.map((source) => [source.id, source]));
|
|
1242
1396
|
const targetsByPlugin = new Map();
|
|
1243
|
-
for (const target of options.status.targets) {
|
|
1397
|
+
for (const target of flattenStatusTargets(options.status.targets)) {
|
|
1244
1398
|
targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
|
|
1245
1399
|
}
|
|
1246
1400
|
const configScopesByPlugin = new Map();
|
|
@@ -1263,6 +1417,21 @@ export function buildTuiViewModel(options) {
|
|
|
1263
1417
|
const configOnly = summary === undefined && targets.length === 0 && savedConfigScopes.length > 0;
|
|
1264
1418
|
const placements = ['codex', 'claude'].flatMap((platform) => {
|
|
1265
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
|
+
}
|
|
1266
1435
|
return platformTargets.length > 0
|
|
1267
1436
|
? [projectPlatformPlacement(platform, platformTargets, options.targetRevisions)]
|
|
1268
1437
|
: [];
|
|
@@ -1329,15 +1498,31 @@ export function buildTuiViewModel(options) {
|
|
|
1329
1498
|
issueCount: summary.dependencyIssueCount ?? 0,
|
|
1330
1499
|
} }
|
|
1331
1500
|
: {}),
|
|
1332
|
-
technicalComponents:
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
:
|
|
1340
|
-
|
|
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
|
+
],
|
|
1341
1526
|
update,
|
|
1342
1527
|
};
|
|
1343
1528
|
}).sort((left, right) => compareText(left.displayName, right.displayName)
|
|
@@ -1353,11 +1538,11 @@ export function buildTuiViewModel(options) {
|
|
|
1353
1538
|
.filter((health) => health.state === 'unavailable')
|
|
1354
1539
|
.map((health) => health.sourceId));
|
|
1355
1540
|
const allTargetIssues = targetIssues(options.status);
|
|
1356
|
-
const
|
|
1541
|
+
const mergedTargetIssues = new Map();
|
|
1357
1542
|
for (const issue of allTargetIssues) {
|
|
1358
1543
|
if (!issue.mergeEligible || !issue.sourceId || !sourceRootIds.has(issue.sourceId))
|
|
1359
1544
|
continue;
|
|
1360
|
-
|
|
1545
|
+
mergedTargetIssues.set(issue.sourceId, [...(mergedTargetIssues.get(issue.sourceId) ?? []), issue]);
|
|
1361
1546
|
}
|
|
1362
1547
|
const sourceDetails = new Map((options.sourceDetails ?? []).map((detail) => [detail.id, detail]));
|
|
1363
1548
|
const issueDrafts = [
|
|
@@ -1366,7 +1551,7 @@ export function buildTuiViewModel(options) {
|
|
|
1366
1551
|
...statusIssues(options.status),
|
|
1367
1552
|
...configIssues(options.pluginSummaries),
|
|
1368
1553
|
...dependencyOverviewIssues(options.dependencyOverviews ?? [], options.pluginSummaries, options.status),
|
|
1369
|
-
...[...(options.sourceHealth?.values() ?? [])].flatMap((health) => sourceIssue(health,
|
|
1554
|
+
...[...(options.sourceHealth?.values() ?? [])].flatMap((health) => sourceIssue(health, mergedTargetIssues.get(health.sourceId) ?? []) ?? []),
|
|
1370
1555
|
];
|
|
1371
1556
|
const stableIssueDrafts = collapseRootIssues(issueDrafts).sort((left, right) => issueGroupOrder[issueGroup(left)] - issueGroupOrder[issueGroup(right)]
|
|
1372
1557
|
|| issueReasonPriority(left) - issueReasonPriority(right)
|
|
@@ -1407,7 +1592,9 @@ export function buildTuiViewModel(options) {
|
|
|
1407
1592
|
dependencyOverviews: options.dependencyOverviews ?? [],
|
|
1408
1593
|
home: {
|
|
1409
1594
|
availablePlugins: pluginsWithAttention.filter((plugin) => plugin.aggregateStatus === 'available').length,
|
|
1410
|
-
installedPlugins: pluginsWithAttention
|
|
1595
|
+
installedPlugins: new Set(pluginsWithAttention
|
|
1596
|
+
.filter(hasInstalledPlacement)
|
|
1597
|
+
.map((plugin) => plugin.pluginId)).size,
|
|
1411
1598
|
issueCount: issues.length,
|
|
1412
1599
|
sourceCount: sourceTabs.length,
|
|
1413
1600
|
unavailableSourceCount: options.status.catalog.sources.filter((source) => source.error
|