@lesliechan721/aw 0.1.0-beta.34 → 0.1.0-beta.36

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.
Files changed (102) hide show
  1. package/dist/scripts/test-plugin-scripts.js +1 -1
  2. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  3. package/dist/src/commands/aw-capabilities.js +1 -0
  4. package/dist/src/commands/aw-capabilities.js.map +1 -1
  5. package/dist/src/commands/operation-plan.d.ts +19 -0
  6. package/dist/src/commands/operation-plan.js.map +1 -1
  7. package/dist/src/commands/rebuild-command.d.ts +1 -0
  8. package/dist/src/commands/rebuild-command.js +890 -8
  9. package/dist/src/commands/rebuild-command.js.map +1 -1
  10. package/dist/src/commands/rebuild-integration.d.ts +12 -0
  11. package/dist/src/commands/rebuild-integration.js +88 -2
  12. package/dist/src/commands/rebuild-integration.js.map +1 -1
  13. package/dist/src/commands/status-v6.js +109 -19
  14. package/dist/src/commands/status-v6.js.map +1 -1
  15. package/dist/src/commands/wizard.d.ts +5 -1
  16. package/dist/src/commands/wizard.js +4 -2
  17. package/dist/src/commands/wizard.js.map +1 -1
  18. package/dist/src/commands/write-commands.js +3 -0
  19. package/dist/src/commands/write-commands.js.map +1 -1
  20. package/dist/src/index.js +57 -4
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/portable/materializer.js +19 -3
  23. package/dist/src/portable/materializer.js.map +1 -1
  24. package/dist/src/review/review-profile.js +19 -2
  25. package/dist/src/review/review-profile.js.map +1 -1
  26. package/dist/src/tui/components.d.ts +39 -0
  27. package/dist/src/tui/components.js +51 -18
  28. package/dist/src/tui/components.js.map +1 -1
  29. package/dist/src/tui/config-array-draft.d.ts +29 -0
  30. package/dist/src/tui/config-array-draft.js +48 -0
  31. package/dist/src/tui/config-array-draft.js.map +1 -0
  32. package/dist/src/tui/config-value-presentation.d.ts +1 -0
  33. package/dist/src/tui/config-value-presentation.js +17 -1
  34. package/dist/src/tui/config-value-presentation.js.map +1 -1
  35. package/dist/src/tui/console-app-v4.js +1742 -1368
  36. package/dist/src/tui/console-app-v4.js.map +1 -1
  37. package/dist/src/tui/content-item-card.d.ts +13 -0
  38. package/dist/src/tui/content-item-card.js +51 -0
  39. package/dist/src/tui/content-item-card.js.map +1 -0
  40. package/dist/src/tui/controller-runtime.js +45 -4
  41. package/dist/src/tui/controller-runtime.js.map +1 -1
  42. package/dist/src/tui/controller.d.ts +18 -0
  43. package/dist/src/tui/controller.js +168 -12
  44. package/dist/src/tui/controller.js.map +1 -1
  45. package/dist/src/tui/dependency-graph-view.d.ts +39 -0
  46. package/dist/src/tui/dependency-graph-view.js +121 -0
  47. package/dist/src/tui/dependency-graph-view.js.map +1 -0
  48. package/dist/src/tui/dependency-graph.js +16 -19
  49. package/dist/src/tui/dependency-graph.js.map +1 -1
  50. package/dist/src/tui/entity-cards.d.ts +55 -0
  51. package/dist/src/tui/entity-cards.js +220 -0
  52. package/dist/src/tui/entity-cards.js.map +1 -0
  53. package/dist/src/tui/input-display.js +10 -1
  54. package/dist/src/tui/input-display.js.map +1 -1
  55. package/dist/src/tui/model.d.ts +11 -0
  56. package/dist/src/tui/model.js +20 -4
  57. package/dist/src/tui/model.js.map +1 -1
  58. package/dist/src/tui/operation-diagnostic-screens.d.ts +2 -18
  59. package/dist/src/tui/operation-diagnostic-screens.js +16 -69
  60. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -1
  61. package/dist/src/tui/operation-review-planner.js +44 -3
  62. package/dist/src/tui/operation-review-planner.js.map +1 -1
  63. package/dist/src/tui/plugin-content.d.ts +8 -0
  64. package/dist/src/tui/plugin-content.js +409 -65
  65. package/dist/src/tui/plugin-content.js.map +1 -1
  66. package/dist/src/tui/plugin-detail.d.ts +52 -0
  67. package/dist/src/tui/plugin-detail.js +165 -0
  68. package/dist/src/tui/plugin-detail.js.map +1 -0
  69. package/dist/src/tui/project-inventory.d.ts +68 -0
  70. package/dist/src/tui/project-inventory.js +203 -0
  71. package/dist/src/tui/project-inventory.js.map +1 -0
  72. package/dist/src/tui/query-header.d.ts +30 -0
  73. package/dist/src/tui/query-header.js +40 -0
  74. package/dist/src/tui/query-header.js.map +1 -0
  75. package/dist/src/tui/reader.d.ts +37 -0
  76. package/dist/src/tui/reader.js +47 -0
  77. package/dist/src/tui/reader.js.map +1 -0
  78. package/dist/src/tui/router.d.ts +26 -3
  79. package/dist/src/tui/router.js.map +1 -1
  80. package/dist/src/tui/screen-registry.d.ts +9 -1
  81. package/dist/src/tui/screen-registry.js +1 -1
  82. package/dist/src/tui/screen-registry.js.map +1 -1
  83. package/dist/src/tui/source-detail-card.d.ts +29 -0
  84. package/dist/src/tui/source-detail-card.js +45 -0
  85. package/dist/src/tui/source-detail-card.js.map +1 -0
  86. package/dist/src/tui/theme.d.ts +12 -0
  87. package/dist/src/tui/theme.js +12 -0
  88. package/dist/src/tui/theme.js.map +1 -1
  89. package/dist/src/tui/wizard.js +26 -4
  90. package/dist/src/tui/wizard.js.map +1 -1
  91. package/dist/src/types.d.ts +2 -0
  92. package/dist/src/types.js.map +1 -1
  93. package/dist/src/utils/catalog-source-references.js +2 -2
  94. package/dist/src/utils/local-marketplace.d.ts +1 -0
  95. package/dist/src/utils/local-marketplace.js +22 -0
  96. package/dist/src/utils/local-marketplace.js.map +1 -1
  97. package/dist/src/utils/plugin-config-write.js +3 -1
  98. package/dist/src/utils/plugin-config-write.js.map +1 -1
  99. package/dist/src/utils/plugin-config.d.ts +5 -5
  100. package/dist/src/utils/plugin-config.js +47 -23
  101. package/dist/src/utils/plugin-config.js.map +1 -1
  102. package/package.json +4 -4
@@ -11,7 +11,9 @@ import type { OperationPlan } from '../commands/operation-plan.js';
11
11
  import type { RebuildProgressFeed, RebuildProgressSnapshot } from '../output/rebuild-progress.js';
12
12
  import { type ConfigPageViewModel, type ConfigGroupViewModel, type InstallPlacementDraftViewModel, type PluginListItemViewModel, type PluginDependencyOverviewViewModel, type PortableSkillSelectionViewModel, type SourceHealthViewModel, type SourceDetailViewModel, type TuiViewModel, type UserIssueCategory, type UserIssueViewModel } from './model.js';
13
13
  import type { PluginContentOverview } from './plugin-content.js';
14
+ import { type PluginDetailSubject } from './plugin-detail.js';
14
15
  import { OperationHistoryStore, type OperationDiagnosticDetail, type OperationEffectFacts, type OperationHistorySummary, type OperationReportPreview, type SafeOperationDiagnostic, type SaveOperationReportResult } from './operation-history.js';
16
+ import { type ProjectInventoryRoot, type ProjectInventoryViewModel } from './project-inventory.js';
15
17
  import { type ConfigInvalidStoreEvidence, type PluginInstallFilter, type PortableBlockerCode, type TuiPageContext, type TuiRoute, type TuiRouterSnapshot } from './router.js';
16
18
  import { type CatalogSourceGenerationViewModel } from './catalog-generation.js';
17
19
  import type { CatalogSourceFreshnessViewModel } from './catalog-freshness.js';
@@ -136,6 +138,7 @@ export interface ConfigOperationChange {
136
138
  }>;
137
139
  }
138
140
  export interface ConfigSaveBatchGroup {
141
+ editEvidence?: Record<string, import('./model.js').ConfigEditEvidence>;
139
142
  scope: Scope;
140
143
  updates?: Record<string, unknown>;
141
144
  removeKeys?: string[];
@@ -290,6 +293,7 @@ export interface OperationEvidence {
290
293
  targetFingerprint?: string;
291
294
  offlineCleanup?: OfflineCleanupTargetEvidence;
292
295
  offlineCleanupPlans?: OfflineCleanupTargetEvidence[];
296
+ removedPluginCleanupIds?: string[];
293
297
  dependencyPlan?: ReconcileOperationPlan;
294
298
  codexReconcilePlans?: CodexReconcilePlan[];
295
299
  operationPlan?: OperationPlan;
@@ -382,6 +386,10 @@ export interface OperationReviewModel {
382
386
  version?: string;
383
387
  };
384
388
  changes: OperationReviewChange[];
389
+ configImpact?: {
390
+ state: 'known' | 'unknown';
391
+ affectedTargetCount: number;
392
+ };
385
393
  confirmationDetails?: string[];
386
394
  technicalDetails?: string[];
387
395
  equivalentCommand?: string;
@@ -778,6 +786,12 @@ export interface TuiControllerSnapshot {
778
786
  operationRecordId?: string;
779
787
  };
780
788
  projectIntentReminder?: ProjectIntentReminder;
789
+ projectInventory?: ProjectInventoryViewModel;
790
+ projectInventoryRefresh: {
791
+ state: 'idle' | 'checking' | 'error';
792
+ checkedAtMs?: number;
793
+ errorCode?: string;
794
+ };
781
795
  projectDeclarationAbsent?: boolean;
782
796
  projectIntentIssue?: {
783
797
  state: 'invalid' | 'unavailable';
@@ -833,6 +847,9 @@ export declare class TuiController {
833
847
  discardDrafts?: boolean;
834
848
  }): boolean;
835
849
  load(): Promise<void>;
850
+ listProjectInventoryRoots(section?: 'declared' | 'extraInstalled'): ProjectInventoryRoot[];
851
+ getProjectInventoryRoot(pluginId: string): ProjectInventoryRoot | undefined;
852
+ refreshProjectInventory(): Promise<boolean>;
836
853
  push(route: TuiRoute, context?: TuiPageContext): void;
837
854
  hasPortableSkills(pluginId: string): boolean;
838
855
  preparePortableInstall(pluginId: string, navigation?: 'push' | 'replace'): Promise<PortableSkillSelectionViewModel>;
@@ -881,6 +898,7 @@ export declare class TuiController {
881
898
  installFilter?: PluginInstallFilter;
882
899
  }): PluginListItemViewModel[];
883
900
  getPluginOverview(pluginId: string): PluginListItemViewModel | undefined;
901
+ getPluginDetailSubject(pluginId: string): PluginDetailSubject | undefined;
884
902
  getPluginContentOverview(pluginId: string): PluginContentOverview | undefined;
885
903
  getPluginContentState(pluginId: string): 'idle' | 'loading' | 'ready';
886
904
  loadPluginContent(pluginId: string): Promise<void>;
@@ -5,11 +5,13 @@ import { flattenStatusTargets, statusTargetActivation, statusTargetExtensions, s
5
5
  import { buildRebuildReceiptJson, requestedIntentAction } from '../output/command-receipt.js';
6
6
  import { buildOperationResultModel } from '../commands/operation-planner.js';
7
7
  import { buildOperationReviewProjection } from './operation-review-planner.js';
8
- import { buildTuiViewModel, dependencyPlacementTargets, hasInstalledPlacement, } from './model.js';
8
+ import { buildTuiViewModel, dependencyPlacementTargets, } from './model.js';
9
+ import { buildPluginDetailSubject } from './plugin-detail.js';
9
10
  import { safeDiagnosticCode, sanitizeDiagnosticText } from './diagnostics.js';
10
11
  import { projectExecutionDiagnostics, projectThrownOperationDiagnostic } from './diagnostic-projection.js';
11
12
  import { OperationHistoryStore, } from './operation-history.js';
12
13
  import { targetHasArtifactCollision } from '../utils/target-eligibility.js';
14
+ import { buildProjectInventory, hasStatusInstallationEvidence, } from './project-inventory.js';
13
15
  import { TuiRouter, } from './router.js';
14
16
  import { normalizeTuiRoute } from './screen-registry.js';
15
17
  import { CatalogSourcesSession } from './catalog-sources/session.js';
@@ -36,7 +38,10 @@ export function pluginWriteBatchTargets(action, pluginIds, statusTargets) {
36
38
  const activation = statusTargetActivation(target);
37
39
  const hasManagedOutput = ownership !== 'absent';
38
40
  const hasCleanupEvidence = ownership === 'present' || ownership === 'partial'
39
- || intent.relation === 'managed';
41
+ || intent.relation === 'managed'
42
+ || target.reasons.some((reason) => reason.code === 'removedPluginCleanupRequired')
43
+ || (target.platform === 'codex' && activation?.evidence === 'observed'
44
+ && (activation.user !== undefined || activation.project !== undefined));
40
45
  if (action === 'sync' || action === 'reinstall') {
41
46
  intent.scopes.forEach((scope) => add(target, scope));
42
47
  if (hasManagedOutput)
@@ -61,9 +66,9 @@ export function pluginWriteBatchTargets(action, pluginIds, statusTargets) {
61
66
  else if (hasCleanupEvidence) {
62
67
  add(target, target.scope);
63
68
  if (target.platform === 'codex') {
64
- if (activation?.user === true)
69
+ if (activation?.user !== undefined)
65
70
  add(target, 'user');
66
- if (activation?.project === true)
71
+ if (activation?.project !== undefined)
67
72
  add(target, 'project');
68
73
  }
69
74
  }
@@ -731,6 +736,9 @@ export class TuiController {
731
736
  #portableResultRecheck;
732
737
  #completionNotice;
733
738
  #projectIntentReminder;
739
+ #projectInventory;
740
+ #projectInventoryRefresh = { state: 'idle' };
741
+ #projectInventoryRefreshToken = 0;
734
742
  #loadError;
735
743
  #portableDraft;
736
744
  #recoverablePortableDraft;
@@ -850,6 +858,8 @@ export class TuiController {
850
858
  ...(this.#projectIntentReminder
851
859
  ? { projectIntentReminder: this.#projectIntentReminder }
852
860
  : {}),
861
+ ...(this.#projectInventory ? { projectInventory: this.#projectInventory } : {}),
862
+ projectInventoryRefresh: this.#projectInventoryRefresh,
853
863
  ...(this.#status?.projectIntent?.state === 'absent' ? { projectDeclarationAbsent: true } : {}),
854
864
  ...(this.#status?.projectIntent?.state === 'invalid'
855
865
  || this.#status?.projectIntent?.state === 'unavailable'
@@ -1197,13 +1207,34 @@ export class TuiController {
1197
1207
  portablePluginIds: this.#status.portablePluginIds,
1198
1208
  configStoreInventory: this.#status.configStoreInventory,
1199
1209
  });
1210
+ this.#projectInventory = buildProjectInventory({
1211
+ projectRoot: this.#status.projectRoot,
1212
+ projectIntent: this.#status.projectIntent,
1213
+ status: this.#status.payload,
1214
+ pluginSummaries: this.#viewModel.plugins,
1215
+ targetRevisions: this.#status.targetRevisions,
1216
+ dependencyOverviews: this.#status.dependencyOverviews,
1217
+ actionableTargets: this.#projectIntentReminder?.actionableTargets,
1218
+ });
1200
1219
  }
1201
1220
  #normalizeRoute(route) {
1202
- return normalizeTuiRoute(route, this.#routeEntityState());
1221
+ const state = this.#routeEntityState();
1222
+ if (route.name === 'pluginDetail' || route.name === 'pluginPreview'
1223
+ || route.name === 'pluginDependencies' && route.view !== 'removeBlocked'
1224
+ || route.name === 'pluginDependencyNode') {
1225
+ return normalizeTuiRoute(route, {
1226
+ ...state,
1227
+ hasPlugin: (pluginId) => this.getPluginDetailSubject(pluginId) !== undefined,
1228
+ });
1229
+ }
1230
+ return normalizeTuiRoute(route, state);
1203
1231
  }
1204
1232
  #routeEntityState() {
1205
1233
  return {
1206
- hasPlugin: (pluginId) => this.#viewModel?.plugins.some((plugin) => plugin.pluginId === pluginId) ?? false,
1234
+ hasPlugin: (pluginId) => this.#viewModel?.plugins.some((plugin) => plugin.pluginId === pluginId)
1235
+ || this.#projectInventory?.declaredRoots.some((root) => root.pluginId === pluginId)
1236
+ || this.#projectInventory?.extraInstalledRoots.some((root) => root.pluginId === pluginId)
1237
+ || false,
1207
1238
  hasSource: (sourceId) => this.#viewModel?.sourceTabs.some((source) => source.id === sourceId) ?? false,
1208
1239
  hasIssue: (issueId) => this.#viewModel?.issues.some((issue) => issue.issueId === issueId) ?? false,
1209
1240
  hasHistoryRecord: (recordId) => this.#operationHistory.get(recordId) !== undefined,
@@ -1527,6 +1558,8 @@ export class TuiController {
1527
1558
  this.#assertAcceptingActions();
1528
1559
  this.dependencies.beginCatalogSession?.();
1529
1560
  const generation = ++this.#generation;
1561
+ this.#projectInventoryRefreshToken += 1;
1562
+ this.#projectInventoryRefresh = { state: 'idle' };
1530
1563
  this.#pluginContentEpoch += 1;
1531
1564
  this.#pluginContent.clear();
1532
1565
  this.#pluginContentInflight.clear();
@@ -1554,6 +1587,49 @@ export class TuiController {
1554
1587
  this.#loadError = undefined;
1555
1588
  this.#publish();
1556
1589
  }
1590
+ listProjectInventoryRoots(section = 'declared') {
1591
+ return cloneFrozen(section === 'declared'
1592
+ ? this.#projectInventory?.declaredRoots ?? []
1593
+ : this.#projectInventory?.extraInstalledRoots ?? []);
1594
+ }
1595
+ getProjectInventoryRoot(pluginId) {
1596
+ const root = [...(this.#projectInventory?.declaredRoots ?? []),
1597
+ ...(this.#projectInventory?.extraInstalledRoots ?? [])]
1598
+ .find((entry) => entry.pluginId === pluginId);
1599
+ return root ? cloneFrozen(root) : undefined;
1600
+ }
1601
+ async refreshProjectInventory() {
1602
+ this.#assertAcceptingActions();
1603
+ if (!this.#status || this.#phase !== 'ready')
1604
+ return false;
1605
+ const token = ++this.#projectInventoryRefreshToken;
1606
+ const generation = this.#generation;
1607
+ this.#projectInventoryRefresh = { state: 'checking' };
1608
+ this.#publish();
1609
+ try {
1610
+ const status = await this.#trackRead(this.dependencies.loadStatus(this.#abort.signal));
1611
+ if (this.#disposed || generation !== this.#generation
1612
+ || token !== this.#projectInventoryRefreshToken)
1613
+ return false;
1614
+ this.#applyStatus(status);
1615
+ this.#projectInventoryRefresh = {
1616
+ state: 'idle',
1617
+ checkedAtMs: this.#statusReadAtMs,
1618
+ };
1619
+ this.#publish();
1620
+ return true;
1621
+ }
1622
+ catch (error) {
1623
+ if (this.#disposed || generation !== this.#generation
1624
+ || token !== this.#projectInventoryRefreshToken)
1625
+ return false;
1626
+ const code = issueCode(error);
1627
+ await this.#logErrorSafe(error, code);
1628
+ this.#projectInventoryRefresh = { state: 'error', errorCode: code };
1629
+ this.#publish();
1630
+ return false;
1631
+ }
1632
+ }
1557
1633
  push(route, context) {
1558
1634
  if (this.#router.snapshot.current.route.name === 'result') {
1559
1635
  this.#invalidatePortableResultRecheck();
@@ -2433,10 +2509,39 @@ export class TuiController {
2433
2509
  }
2434
2510
  listPlugins(options) {
2435
2511
  const query = options.query?.trim().toLocaleLowerCase('en');
2512
+ const actualScopes = new Map();
2513
+ const statusTargets = flattenStatusTargets(this.#status?.payload.targets ?? []);
2514
+ const targetsByPlugin = new Map();
2515
+ for (const target of statusTargets) {
2516
+ targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
2517
+ const owned = target.platform === 'claude'
2518
+ ? ['present', 'partial'].includes(statusTargetOwnershipState(target))
2519
+ : target.components.some((component) => component.kind === 'ownership'
2520
+ && component.key !== 'native'
2521
+ && !component.key.startsWith('activation-')
2522
+ && !component.key.startsWith('marketplace-')
2523
+ && (component.state === 'present' || component.state === 'partial'));
2524
+ const activation = target.platform === 'codex' ? statusTargetActivation(target) : undefined;
2525
+ if (!owned && !(activation?.evidence === 'observed'
2526
+ && activation[target.scope] === true)
2527
+ && !hasStatusInstallationEvidence(target))
2528
+ continue;
2529
+ const scopes = actualScopes.get(target.pluginId) ?? new Set();
2530
+ scopes.add(target.scope);
2531
+ actualScopes.set(target.pluginId, scopes);
2532
+ }
2436
2533
  return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
2437
2534
  && (options.installFilter !== 'installed'
2438
- || hasInstalledPlacement(plugin))
2439
- && (options.installFilter !== 'uninstalled' || !hasInstalledPlacement(plugin))
2535
+ || (actualScopes.get(plugin.pluginId)?.size ?? 0) > 0)
2536
+ && (options.installFilter !== 'uninstalled'
2537
+ || plugin.aggregateStatus === 'available'
2538
+ && (targetsByPlugin.get(plugin.pluginId)?.length ?? 0) > 0
2539
+ && targetsByPlugin.get(plugin.pluginId).every((target) => target.state === 'absent')
2540
+ && (actualScopes.get(plugin.pluginId)?.size ?? 0) === 0)
2541
+ && (options.installFilter !== 'project'
2542
+ || actualScopes.get(plugin.pluginId)?.has('project') === true)
2543
+ && (options.installFilter !== 'user'
2544
+ || actualScopes.get(plugin.pluginId)?.has('user') === true)
2440
2545
  && (options.installFilter !== 'updateAvailable' || plugin.update?.state === 'available')
2441
2546
  && (options.installFilter !== 'attention'
2442
2547
  || plugin.aggregateStatus === 'needsAttention' || plugin.aggregateStatus === 'unavailable'
@@ -2448,6 +2553,20 @@ export class TuiController {
2448
2553
  const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
2449
2554
  return plugin ? cloneFrozen(plugin) : undefined;
2450
2555
  }
2556
+ getPluginDetailSubject(pluginId) {
2557
+ if (!this.#status || !this.#viewModel)
2558
+ return undefined;
2559
+ const subject = buildPluginDetailSubject({
2560
+ pluginId,
2561
+ status: this.#status.payload,
2562
+ pluginSummaries: this.#status.pluginSummaries,
2563
+ targetRevisions: this.#status.targetRevisions,
2564
+ plugins: this.#viewModel.plugins,
2565
+ projectInventory: this.#projectInventory,
2566
+ dependencyOverviews: this.#status.dependencyOverviews,
2567
+ });
2568
+ return subject ? cloneFrozen(subject) : undefined;
2569
+ }
2451
2570
  getPluginContentOverview(pluginId) {
2452
2571
  const overview = this.#viewModel?.contentOverviews[pluginId];
2453
2572
  return overview ? cloneFrozen(overview) : undefined;
@@ -2485,6 +2604,10 @@ export class TuiController {
2485
2604
  pluginVersion: '待确认',
2486
2605
  catalogVersion: '待确认',
2487
2606
  evidence: 'unavailable',
2607
+ coverage: 'unavailable',
2608
+ coverageIssue: {
2609
+ code: 'unreadable', message: '当前无法安全读取这个插件的内容。',
2610
+ },
2488
2611
  items: [],
2489
2612
  notices: ['当前无法安全读取这个插件的内容。'],
2490
2613
  });
@@ -2511,7 +2634,20 @@ export class TuiController {
2511
2634
  }))].sort((left, right) => left.localeCompare(right, 'en')));
2512
2635
  }
2513
2636
  listPluginWriteBatchTargets(action, pluginIds) {
2514
- return cloneFrozen(pluginWriteBatchTargets(action, pluginIds, flattenStatusTargets(this.#status?.payload.targets ?? [])));
2637
+ const targets = pluginWriteBatchTargets(action, pluginIds, flattenStatusTargets(this.#status?.payload.targets ?? []));
2638
+ if (action === 'cleanup') {
2639
+ for (const plugin of this.#viewModel?.plugins ?? []) {
2640
+ if (!pluginIds.includes(plugin.pluginId) || !plugin.configOnly
2641
+ || targets.some((target) => target.pluginId === plugin.pluginId))
2642
+ continue;
2643
+ targets.push({
2644
+ pluginId: plugin.pluginId,
2645
+ platform: 'codex',
2646
+ scope: plugin.savedConfigScopes[0] ?? 'user',
2647
+ });
2648
+ }
2649
+ }
2650
+ return cloneFrozen(targets);
2515
2651
  }
2516
2652
  listPluginInstallBatchTargets(pluginIds, platform, scope) {
2517
2653
  const platforms = platform === 'both' ? ['codex', 'claude'] : [platform];
@@ -2529,14 +2665,14 @@ export class TuiController {
2529
2665
  && activation[scope] === true);
2530
2666
  });
2531
2667
  const exactOwnership = exactTarget ? statusTargetOwnershipState(exactTarget) : 'absent';
2668
+ if (plugin?.catalogEntryMissing) {
2669
+ return { ...target, state: 'blocked', reason: '来源中没有可信插件定义' };
2670
+ }
2532
2671
  if (alreadyConfigured || exactOwnership === 'present' || exactOwnership === 'partial') {
2533
2672
  return { ...target, state: 'existing' };
2534
2673
  }
2535
2674
  if (!plugin)
2536
2675
  return { ...target, state: 'blocked', reason: '插件已不在当前状态中' };
2537
- if (plugin.configOnly) {
2538
- return { ...target, state: 'blocked', reason: '来源中没有可信插件定义' };
2539
- }
2540
2676
  if (plugin.sourceUnavailable) {
2541
2677
  return { ...target, state: 'blocked', reason: '插件来源不可用' };
2542
2678
  }
@@ -2617,6 +2753,11 @@ export class TuiController {
2617
2753
  const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
2618
2754
  if (!plugin)
2619
2755
  return [];
2756
+ if (plugin.catalogEntryMissing) {
2757
+ return plugin.supportedPlatforms.map((platform) => ({
2758
+ platform, compatible: false, disabled: '来源中没有可信插件定义',
2759
+ }));
2760
+ }
2620
2761
  if (plugin.aggregateStatus === 'unavailable') {
2621
2762
  return plugin.supportedPlatforms.map((platform) => ({
2622
2763
  platform,
@@ -3277,6 +3418,20 @@ export class TuiController {
3277
3418
  assertPluginWriteTaskEvidence(intent, evidence);
3278
3419
  if (this.#router.snapshot.current.route.name === 'result')
3279
3420
  navigation = 'replace';
3421
+ const configEvidence = intent.kind === 'configSave'
3422
+ ? evidence.config ? [evidence.config] : undefined
3423
+ : intent.kind === 'configSaveBatch'
3424
+ ? evidence.configBatch?.groups.map((group) => group.config)
3425
+ : undefined;
3426
+ if ((intent.kind === 'configSave' || intent.kind === 'configSaveBatch')
3427
+ && (!configEvidence || configEvidence.length !== (intent.kind === 'configSave' ? 1 : intent.groups.length))) {
3428
+ throw new TuiOperationBlockedError('configImpactUnavailable', '设置确认缺少完整的应用影响证据。');
3429
+ }
3430
+ const configImpact = configEvidence ? {
3431
+ state: configEvidence.some((group) => group.impactState !== 'known') ? 'unknown' : 'known',
3432
+ affectedTargetCount: new Set(configEvidence.flatMap((group) => group.affectedTargets
3433
+ .map((target) => `${target.platform}\0${target.scope}`))).size,
3434
+ } : undefined;
3280
3435
  const projection = buildOperationReviewProjection({
3281
3436
  intent,
3282
3437
  evidence,
@@ -3349,6 +3504,7 @@ export class TuiController {
3349
3504
  ? { originIssue: intent.originIssue }
3350
3505
  : {}),
3351
3506
  ...projection,
3507
+ ...(configImpact ? { configImpact } : {}),
3352
3508
  subject: {
3353
3509
  name: 'pluginId' in intent
3354
3510
  ? this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName