@lesliechan721/aw 0.1.0-beta.8 → 0.1.0-beta.9

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 (275) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +4 -4
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/remove-plugin.mjs +1 -1
  10. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +34 -778
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  14. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  16. package/dist/scripts/bundle-plugins.d.ts +1 -0
  17. package/dist/scripts/bundle-plugins.js +2 -1
  18. package/dist/scripts/bundle-plugins.js.map +1 -1
  19. package/dist/scripts/plugin-staging.d.ts +5 -6
  20. package/dist/scripts/plugin-staging.js +12 -13
  21. package/dist/scripts/plugin-staging.js.map +1 -1
  22. package/dist/src/authoring/workspace-commands.d.ts +2 -1
  23. package/dist/src/authoring/workspace-commands.js.map +1 -1
  24. package/dist/src/commands/aw-capabilities.d.ts +2 -2
  25. package/dist/src/commands/aw-capabilities.js +6 -11
  26. package/dist/src/commands/aw-capabilities.js.map +1 -1
  27. package/dist/src/commands/catalog-dev-source.d.ts +18 -12
  28. package/dist/src/commands/catalog-dev-source.js +241 -305
  29. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  30. package/dist/src/commands/catalog-generation.d.ts +6 -19
  31. package/dist/src/commands/catalog-generation.js +10 -11
  32. package/dist/src/commands/catalog-generation.js.map +1 -1
  33. package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
  34. package/dist/src/commands/catalog-publication-inspection.js +139 -50
  35. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  36. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  37. package/dist/src/commands/catalog-publication-journal.js +147 -0
  38. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  39. package/dist/src/commands/catalog-publication-target.d.ts +2 -1
  40. package/dist/src/commands/catalog-publication-target.js +37 -2
  41. package/dist/src/commands/catalog-publication-target.js.map +1 -1
  42. package/dist/src/commands/catalog-publish.d.ts +51 -15
  43. package/dist/src/commands/catalog-publish.js +576 -219
  44. package/dist/src/commands/catalog-publish.js.map +1 -1
  45. package/dist/src/commands/catalog-source.d.ts +3 -28
  46. package/dist/src/commands/catalog-source.js +29 -140
  47. package/dist/src/commands/catalog-source.js.map +1 -1
  48. package/dist/src/commands/catalog-status.d.ts +6 -26
  49. package/dist/src/commands/catalog-status.js +8 -146
  50. package/dist/src/commands/catalog-status.js.map +1 -1
  51. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  52. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  53. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  54. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  55. package/dist/src/commands/dependency-operation-plan.js +331 -6
  56. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  57. package/dist/src/commands/init.d.ts +5 -11
  58. package/dist/src/commands/init.js +30 -51
  59. package/dist/src/commands/init.js.map +1 -1
  60. package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
  61. package/dist/src/commands/offline-cleanup-evidence.js +87 -9
  62. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
  63. package/dist/src/commands/options.d.ts +6 -1
  64. package/dist/src/commands/options.js +64 -6
  65. package/dist/src/commands/options.js.map +1 -1
  66. package/dist/src/commands/plugin-capabilities.js +4 -0
  67. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  68. package/dist/src/commands/rebuild-command.d.ts +10 -8
  69. package/dist/src/commands/rebuild-command.js +1217 -426
  70. package/dist/src/commands/rebuild-command.js.map +1 -1
  71. package/dist/src/commands/rebuild-integration.d.ts +54 -6
  72. package/dist/src/commands/rebuild-integration.js +691 -364
  73. package/dist/src/commands/rebuild-integration.js.map +1 -1
  74. package/dist/src/commands/rebuild-operations.d.ts +2 -0
  75. package/dist/src/commands/rebuild-operations.js +31 -17
  76. package/dist/src/commands/rebuild-operations.js.map +1 -1
  77. package/dist/src/commands/skill-eval.d.ts +33 -0
  78. package/dist/src/commands/skill-eval.js +363 -0
  79. package/dist/src/commands/skill-eval.js.map +1 -0
  80. package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
  81. package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
  82. package/dist/src/commands/status-v6.js.map +1 -0
  83. package/dist/src/commands/sync-command.d.ts +21 -0
  84. package/dist/src/commands/sync-command.js +206 -0
  85. package/dist/src/commands/sync-command.js.map +1 -0
  86. package/dist/src/commands/wizard.d.ts +2 -2
  87. package/dist/src/commands/write-commands.js +1 -43
  88. package/dist/src/commands/write-commands.js.map +1 -1
  89. package/dist/src/constants.d.ts +5 -4
  90. package/dist/src/constants.js +5 -4
  91. package/dist/src/constants.js.map +1 -1
  92. package/dist/src/eval/manifest-v1.d.ts +66 -0
  93. package/dist/src/eval/manifest-v1.js +326 -0
  94. package/dist/src/eval/manifest-v1.js.map +1 -0
  95. package/dist/src/generators/local-plugin.d.ts +3 -0
  96. package/dist/src/generators/local-plugin.js +123 -55
  97. package/dist/src/generators/local-plugin.js.map +1 -1
  98. package/dist/src/index.d.ts +2 -0
  99. package/dist/src/index.js +412 -152
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/output/command-receipt.d.ts +58 -8
  102. package/dist/src/output/command-receipt.js +141 -11
  103. package/dist/src/output/command-receipt.js.map +1 -1
  104. package/dist/src/output/model.d.ts +3 -11
  105. package/dist/src/portable/commands.d.ts +2 -2
  106. package/dist/src/portable/commands.js +2 -8
  107. package/dist/src/portable/commands.js.map +1 -1
  108. package/dist/src/portable/materializer.d.ts +1 -4
  109. package/dist/src/portable/materializer.js +0 -2
  110. package/dist/src/portable/materializer.js.map +1 -1
  111. package/dist/src/tui/catalog-generation.d.ts +20 -41
  112. package/dist/src/tui/catalog-generation.js +65 -224
  113. package/dist/src/tui/catalog-generation.js.map +1 -1
  114. package/dist/src/tui/components.js +1 -2
  115. package/dist/src/tui/components.js.map +1 -1
  116. package/dist/src/tui/console-app-v4.js +461 -330
  117. package/dist/src/tui/console-app-v4.js.map +1 -1
  118. package/dist/src/tui/controller-runtime.d.ts +3 -5
  119. package/dist/src/tui/controller-runtime.js +182 -187
  120. package/dist/src/tui/controller-runtime.js.map +1 -1
  121. package/dist/src/tui/controller.d.ts +42 -45
  122. package/dist/src/tui/controller.js +454 -407
  123. package/dist/src/tui/controller.js.map +1 -1
  124. package/dist/src/tui/diagnostics.d.ts +1 -2
  125. package/dist/src/tui/diagnostics.js.map +1 -1
  126. package/dist/src/tui/index.d.ts +1 -1
  127. package/dist/src/tui/index.js +1 -1
  128. package/dist/src/tui/index.js.map +1 -1
  129. package/dist/src/tui/model.d.ts +81 -13
  130. package/dist/src/tui/model.js +378 -95
  131. package/dist/src/tui/model.js.map +1 -1
  132. package/dist/src/tui/router.d.ts +9 -0
  133. package/dist/src/tui/router.js.map +1 -1
  134. package/dist/src/types.d.ts +54 -47
  135. package/dist/src/types.js.map +1 -1
  136. package/dist/src/utils/agent-version.d.ts +4 -1
  137. package/dist/src/utils/agent-version.js +28 -3
  138. package/dist/src/utils/agent-version.js.map +1 -1
  139. package/dist/src/utils/catalog-digests.d.ts +7 -0
  140. package/dist/src/utils/catalog-digests.js +27 -0
  141. package/dist/src/utils/catalog-digests.js.map +1 -1
  142. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  143. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  144. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-error.d.ts +2 -1
  146. package/dist/src/utils/catalog-generation-error.js +12 -0
  147. package/dist/src/utils/catalog-generation-error.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
  149. package/dist/src/utils/catalog-generation-resolver.js +90 -76
  150. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-service.d.ts +24 -33
  152. package/dist/src/utils/catalog-generation-service.js +253 -567
  153. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-state.d.ts +4 -0
  155. package/dist/src/utils/catalog-generation-state.js +73 -106
  156. package/dist/src/utils/catalog-generation-state.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +16 -26
  158. package/dist/src/utils/catalog-generation-store.js +154 -290
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  161. package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
  162. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  163. package/dist/src/utils/catalog-operation-context.d.ts +6 -14
  164. package/dist/src/utils/catalog-operation-context.js +68 -169
  165. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  166. package/dist/src/utils/catalog-public-error.js +1 -4
  167. package/dist/src/utils/catalog-public-error.js.map +1 -1
  168. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  169. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  170. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  171. package/dist/src/utils/catalog-snapshot.d.ts +2 -2
  172. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  173. package/dist/src/utils/catalog-source-references.js +44 -55
  174. package/dist/src/utils/catalog-source-references.js.map +1 -1
  175. package/dist/src/utils/catalog-sources.js +25 -9
  176. package/dist/src/utils/catalog-sources.js.map +1 -1
  177. package/dist/src/utils/codex-config.d.ts +2 -0
  178. package/dist/src/utils/codex-config.js +22 -1
  179. package/dist/src/utils/codex-config.js.map +1 -1
  180. package/dist/src/utils/codex-manager.d.ts +50 -2
  181. package/dist/src/utils/codex-manager.js +160 -6
  182. package/dist/src/utils/codex-manager.js.map +1 -1
  183. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  184. package/dist/src/utils/codex-reconciler.js +327 -0
  185. package/dist/src/utils/codex-reconciler.js.map +1 -0
  186. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  187. package/dist/src/utils/codex-shared-native.js +87 -0
  188. package/dist/src/utils/codex-shared-native.js.map +1 -0
  189. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  190. package/dist/src/utils/codex-surface-analysis.js +247 -0
  191. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  192. package/dist/src/utils/git-catalog-provider.js +56 -115
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/manifest.d.ts +1 -3
  195. package/dist/src/utils/manifest.js +1 -3
  196. package/dist/src/utils/manifest.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +1 -10
  198. package/dist/src/utils/multi-source-catalog.js +8 -175
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +34 -0
  201. package/dist/src/utils/native-cli.js +91 -0
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +0 -10
  206. package/dist/src/utils/ownership.js +3 -88
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  209. package/dist/src/utils/path-catalog-provider.js +45 -62
  210. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  211. package/dist/src/utils/paths.d.ts +0 -1
  212. package/dist/src/utils/paths.js +0 -1
  213. package/dist/src/utils/paths.js.map +1 -1
  214. package/dist/src/utils/plugin-config-write.js +0 -5
  215. package/dist/src/utils/plugin-config-write.js.map +1 -1
  216. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  217. package/dist/src/utils/plugin-intent-state.js +325 -0
  218. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  219. package/dist/src/utils/target-eligibility.d.ts +3 -2
  220. package/dist/src/utils/target-eligibility.js +4 -3
  221. package/dist/src/utils/target-eligibility.js.map +1 -1
  222. package/package.json +5 -4
  223. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  224. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  225. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  226. package/dist/scripts/codex-eval-app-server.d.ts +0 -91
  227. package/dist/scripts/codex-eval-app-server.js +0 -1334
  228. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  229. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  230. package/dist/scripts/codex-eval-isolation.js +0 -318
  231. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  232. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  233. package/dist/scripts/codex-eval-process-management.js +0 -69
  234. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  235. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  236. package/dist/scripts/codex-eval-process-tree.js +0 -803
  237. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  238. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  239. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  240. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  241. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  242. package/dist/scripts/codex-trigger-eval.js +0 -968
  243. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  244. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  245. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  246. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  247. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  248. package/dist/scripts/eval-plugin-skills.js +0 -1589
  249. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  250. package/dist/scripts/eval-run-layout.d.ts +0 -17
  251. package/dist/scripts/eval-run-layout.js +0 -26
  252. package/dist/scripts/eval-run-layout.js.map +0 -1
  253. package/dist/scripts/plugin-eval-contract.d.ts +0 -189
  254. package/dist/scripts/plugin-eval-contract.js +0 -745
  255. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  256. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  257. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  258. package/dist/scripts/skill-reporting.d.ts +0 -212
  259. package/dist/scripts/skill-reporting.js +0 -922
  260. package/dist/scripts/skill-reporting.js.map +0 -1
  261. package/dist/src/commands/catalog-context.d.ts +0 -52
  262. package/dist/src/commands/catalog-context.js +0 -156
  263. package/dist/src/commands/catalog-context.js.map +0 -1
  264. package/dist/src/commands/status-v5.js.map +0 -1
  265. package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
  266. package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
  267. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  268. package/dist/src/utils/catalog-source-cache.js +0 -321
  269. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  270. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  271. package/dist/src/utils/codex-project-surface.js +0 -49
  272. package/dist/src/utils/codex-project-surface.js.map +0 -1
  273. package/dist/src/utils/plugin-release.d.ts +0 -2
  274. package/dist/src/utils/plugin-release.js +0 -2
  275. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -3,18 +3,156 @@ function compareText(left, right) {
3
3
  return left.localeCompare(right, 'en');
4
4
  }
5
5
  function targetHealth(target) {
6
+ if (target.actual?.state === 'ready')
7
+ return 'ready';
8
+ if (target.actual?.state === 'needsSync'
9
+ || target.actual?.state === 'drifted'
10
+ || target.actual?.state === 'conflict'
11
+ || target.actual?.state === 'orphaned'
12
+ || target.actual?.state === 'unresolved')
13
+ return 'incomplete';
6
14
  if (target.state === 'present')
7
15
  return 'ready';
8
16
  if (target.state === 'partial')
9
17
  return 'incomplete';
10
18
  return 'error';
11
19
  }
20
+ export function isPureNativeCodexTarget(target) {
21
+ return target.platform === 'codex'
22
+ && target.sharedNative?.shared === true
23
+ && (target.extensions?.kinds.length ?? 0) === 0;
24
+ }
25
+ function isPureNativeCodexPlacement(placement) {
26
+ return placement.platform === 'codex'
27
+ && placement.state === 'installed'
28
+ && placement.sharedNative?.shared === true
29
+ && (placement.extensions?.kinds.length ?? 0) === 0;
30
+ }
31
+ function parsedScopedUninstallRecovery(recovery, expectedPlatform) {
32
+ if (recovery.command !== 'uninstall' || !recovery.scope)
33
+ return undefined;
34
+ const match = /(?:^|\s)aw uninstall ([^\s]+) --platform (codex|claude) --scope (user|project)(?:,|,|\s|$)/u
35
+ .exec(recovery.description);
36
+ if (!match || match[2] !== expectedPlatform || match[3] !== recovery.scope)
37
+ return undefined;
38
+ return { pluginId: match[1], scope: recovery.scope, description: recovery.description };
39
+ }
40
+ function scopeConflictRecoveryMenuActions(placement) {
41
+ if (placement.actualState !== 'conflict')
42
+ return [];
43
+ return (placement.recovery ?? []).flatMap((recovery) => {
44
+ const parsed = parsedScopedUninstallRecovery(recovery, placement.platform);
45
+ return parsed ? [{
46
+ id: 'resolveScopeConflict',
47
+ pluginId: parsed.pluginId,
48
+ platform: placement.platform,
49
+ scope: parsed.scope,
50
+ keepScope: parsed.scope === 'user' ? 'project' : 'user',
51
+ description: parsed.description,
52
+ }] : [];
53
+ });
54
+ }
55
+ function missingPureNativeScopes(plugin, placement) {
56
+ if (!isPureNativeCodexPlacement(placement) || placement.actualState !== 'ready')
57
+ return [];
58
+ const supported = plugin.supportedScopes?.codex ?? ['user', 'project'];
59
+ return supported.filter((scope) => !placement.desiredScopes?.includes(scope) && placement.activation?.[scope] !== true);
60
+ }
12
61
  function isInstalledScopeEvidence(target) {
13
- return target.state === 'present' || target.state === 'partial';
62
+ return target.desired?.enabled === true
63
+ || target.state === 'present'
64
+ || target.state === 'partial';
65
+ }
66
+ function dependencyAssignmentExplanation(assignmentSource, requiredBy) {
67
+ const consumer = requiredBy[0];
68
+ return {
69
+ root: '根插件位置',
70
+ default: '推荐使用当前项目',
71
+ existingWiderTarget: '复用健康的所有项目安装',
72
+ userOverride: '你选择的位置',
73
+ consumerPromotion: consumer
74
+ ? `因 ${consumer} 的位置而一并调整`
75
+ : '因上游依赖位置而一并调整',
76
+ platformRequirement: '平台只支持这个位置',
77
+ }[assignmentSource];
78
+ }
79
+ export function dependencyPlacementTargets(plan) {
80
+ return plan.targets.filter((target) => target.relation === 'dependency').map((target) => ({
81
+ key: `${target.pluginId}\0${target.platform}`,
82
+ pluginId: target.pluginId,
83
+ displayName: target.pluginName,
84
+ platform: target.platform,
85
+ relation: plan.dependencyEdges.some((edge) => plan.requestedPluginIds.includes(edge.from)
86
+ && edge.to === target.pluginId
87
+ && edge.platform === target.platform)
88
+ ? 'direct'
89
+ : 'transitive',
90
+ scope: target.resolvedScope,
91
+ minimumScope: target.minimumScope,
92
+ allowedScopes: [...target.allowedScopes],
93
+ locked: (target.locked && target.lockReason !== 'dependencyScopeOverride')
94
+ || target.allowedScopes.filter((scope) => target.minimumScope === 'project' || scope === 'user').length < 2,
95
+ requiredBy: [...target.requiredBy],
96
+ assignmentSource: target.assignmentSource,
97
+ explanation: dependencyAssignmentExplanation(target.assignmentSource, target.requiredBy),
98
+ affectsOtherProjects: target.affectsOtherProjects,
99
+ action: target.action,
100
+ }));
14
101
  }
15
102
  function dependencyPluginIdFromMessage(message) {
16
103
  return message.match(/(?:[a-z0-9]+(?:-[a-z0-9]+)*)\/(?:[a-z0-9]+(?:-[a-z0-9]+)*)/u)?.[0];
17
104
  }
105
+ function rootPlacementFromIssueId(issueId, fallback) {
106
+ if (issueId) {
107
+ for (const platform of ['claude', 'codex']) {
108
+ for (const scope of ['project', 'user']) {
109
+ if (issueId.includes(`:${platform}:${scope}:`)
110
+ || issueId.endsWith(`:${platform}:${scope}`))
111
+ return { platform, scope };
112
+ }
113
+ }
114
+ }
115
+ return fallback;
116
+ }
117
+ function rootPluginIdFromIssueId(issueId, rootIssueCode) {
118
+ if (!issueId || !rootIssueCode || !issueId.startsWith(`${rootIssueCode}:`))
119
+ return undefined;
120
+ for (const platform of ['claude', 'codex']) {
121
+ for (const scope of ['project', 'user']) {
122
+ const marker = `:${platform}:${scope}`;
123
+ const markerIndex = issueId.indexOf(marker, rootIssueCode.length + 1);
124
+ if (markerIndex > rootIssueCode.length + 1) {
125
+ return issueId.slice(rootIssueCode.length + 1, markerIndex);
126
+ }
127
+ }
128
+ }
129
+ return undefined;
130
+ }
131
+ function targetIssueRelationship(options) {
132
+ const consumer = options.error?.rootIssueId !== undefined
133
+ && options.error.rootIssueCode !== undefined
134
+ && (options.error.code !== options.error.rootIssueCode
135
+ || options.dependencyPluginId !== undefined
136
+ && options.dependencyPluginId !== options.target.pluginId);
137
+ if (!consumer) {
138
+ return {
139
+ relationship: 'direct',
140
+ rootEntity: {
141
+ pluginId: options.target.pluginId,
142
+ platform: options.target.platform,
143
+ scope: options.target.scope,
144
+ },
145
+ };
146
+ }
147
+ const placement = rootPlacementFromIssueId(options.error?.rootIssueId, options.target);
148
+ return {
149
+ relationship: 'consumer',
150
+ rootEntity: {
151
+ pluginId: rootPluginIdFromIssueId(options.error?.rootIssueId, options.error?.rootIssueCode) ?? options.dependencyPluginId ?? options.target.pluginId,
152
+ ...placement,
153
+ },
154
+ };
155
+ }
18
156
  export function projectPlatformPlacement(platform, targets, revisions = []) {
19
157
  const revisionFor = (target) => revisions.find((entry) => entry.pluginId === target.pluginId
20
158
  && entry.platform === target.platform
@@ -23,6 +161,9 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
23
161
  const supportedScopes = [...new Set(targets.map((target) => target.scope))];
24
162
  const installed = targets.filter(isInstalledScopeEvidence);
25
163
  if (installed.length === 0) {
164
+ if (targets.some((target) => target.errors.some((error) => error.code === 'artifactCollision' && error.severity === 'blocking'))) {
165
+ return { platform, state: 'blocked', supportedScopes, reason: 'artifactCollision' };
166
+ }
26
167
  const errorTarget = targets.find((target) => target.state === 'error');
27
168
  if (!errorTarget)
28
169
  return { platform, state: 'notInstalled', supportedScopes };
@@ -36,12 +177,25 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
36
177
  }
37
178
  if (installed.length === 1) {
38
179
  const target = installed[0];
180
+ const blockingReason = target.errors.some((error) => error.code === 'artifactCollision' && error.severity === 'blocking')
181
+ ? 'artifactCollision'
182
+ : undefined;
39
183
  return {
40
184
  platform,
41
185
  state: 'installed',
42
186
  scope: target.scope,
43
187
  health: targetHealth(target),
44
188
  revision: revisionFor(target),
189
+ ...(target.desired ? {
190
+ desiredScopes: [...target.desired.scopes],
191
+ relation: target.desired.relation,
192
+ } : {}),
193
+ ...(target.actual ? { actualState: target.actual.state } : {}),
194
+ ...(target.activation ? { activation: target.activation } : {}),
195
+ ...(target.sharedNative ? { sharedNative: target.sharedNative } : {}),
196
+ ...(target.extensions ? { extensions: target.extensions } : {}),
197
+ ...(target.recovery ? { recovery: [...target.recovery] } : {}),
198
+ ...(blockingReason ? { blockingReason } : {}),
45
199
  };
46
200
  }
47
201
  return {
@@ -56,11 +210,17 @@ export function projectPlatformPlacement(platform, targets, revisions = []) {
56
210
  }
57
211
  function hasIncompletePlacement(plugin) {
58
212
  return plugin.installedPlacements.some((placement) => placement.state === 'scopeConflict'
59
- || placement.state === 'installed' && placement.health !== 'ready');
213
+ || placement.state === 'installed'
214
+ && (placement.health !== 'ready' || placement.blockingReason !== undefined));
60
215
  }
61
216
  function hasMissingPlatform(plugin) {
62
217
  return plugin.supportedPlatforms.some((platform) => plugin.installedPlacements.find((placement) => placement.platform === platform)?.state === 'notInstalled');
63
218
  }
219
+ function allSupportedPlatformsBlocked(plugin) {
220
+ return plugin.supportedPlatforms.length > 0 && plugin.supportedPlatforms.every((platform) => plugin.installedPlacements.some((placement) => placement.platform === platform
221
+ && (placement.state === 'blocked'
222
+ || placement.state === 'installed' && placement.blockingReason === 'artifactCollision')));
223
+ }
64
224
  function capabilityActions(plugin) {
65
225
  return [
66
226
  ...(plugin.portableSkills ? [{ id: 'portableSkills' }] : []),
@@ -73,12 +233,22 @@ export function buildPluginPreviewActions(plugin) {
73
233
  if (plugin.aggregateStatus === 'unavailable') {
74
234
  return [{ id: 'sourceProblem' }, ...(installed ? [{ id: 'manage' }] : [])];
75
235
  }
76
- const primary = hasIncompletePlacement(plugin)
77
- || (plugin.dependencySummary?.issueCount ?? 0) > 0
78
- ? { id: 'repair' }
79
- : installed && !hasMissingPlatform(plugin)
80
- ? { id: 'manage' }
81
- : { id: 'add' };
236
+ if (allSupportedPlatformsBlocked(plugin)) {
237
+ return [
238
+ { id: 'outputCollision' },
239
+ ...(hasInstalledPlacement(plugin) ? [{ id: 'manage' }] : []),
240
+ { id: 'source' },
241
+ { id: 'about' },
242
+ ];
243
+ }
244
+ const collisionWithInstallablePlatform = hasMissingPlatform(plugin)
245
+ && plugin.installedPlacements.some((placement) => placement.state === 'blocked'
246
+ || placement.state === 'installed' && placement.blockingReason === 'artifactCollision');
247
+ const primary = collisionWithInstallablePlatform
248
+ ? { id: 'add' }
249
+ : hasIncompletePlacement(plugin) || (plugin.dependencySummary?.issueCount ?? 0) > 0
250
+ ? { id: 'repair' }
251
+ : installed && !hasMissingPlatform(plugin) ? { id: 'manage' } : { id: 'add' };
82
252
  return [primary, ...capabilityActions(plugin), { id: 'source' }];
83
253
  }
84
254
  export function buildPluginDetailActions(plugin) {
@@ -92,9 +262,12 @@ export function buildPluginDetailActions(plugin) {
92
262
  ];
93
263
  }
94
264
  return [
95
- ...plugin.installedPlacements.map((placement) => placement.state === 'scopeConflict'
96
- ? { id: 'scopeConflict', platform: placement.platform }
97
- : { id: 'platform', platform: placement.platform }),
265
+ ...plugin.installedPlacements.flatMap((placement) => [
266
+ ...(placement.state === 'installed' ? scopeConflictRecoveryMenuActions(placement) : []),
267
+ placement.state === 'scopeConflict'
268
+ ? { id: 'scopeConflict', platform: placement.platform }
269
+ : { id: 'platform', platform: placement.platform },
270
+ ]),
98
271
  ...((plugin.dependencySummary?.totalCount ?? 0) > 0 ? [{ id: 'dependencies' }] : []),
99
272
  ...(plugin.portableSkills ? [{ id: 'portableSkills' }] : []),
100
273
  ...(plugin.configFieldCount > 0 ? [{ id: 'settings' }] : []),
@@ -106,13 +279,35 @@ export function buildPlatformActions(plugin, platform) {
106
279
  return [{ id: 'sourceProblem' }, { id: 'refreshSource' }];
107
280
  }
108
281
  const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
282
+ if (placement?.state === 'blocked') {
283
+ return [{ id: 'outputCollision' }, { id: 'source' }, { id: 'about' }];
284
+ }
109
285
  if (!placement || placement.state === 'notInstalled')
110
286
  return [{ id: 'add' }, { id: 'about' }];
111
287
  if (placement.state === 'scopeConflict')
112
288
  return [{ id: 'scopeConflict', platform }];
289
+ const scopedRecovery = scopeConflictRecoveryMenuActions(placement);
290
+ if (scopedRecovery.length > 0)
291
+ return [...scopedRecovery, { id: 'technical' }];
292
+ if (placement.blockingReason === 'artifactCollision') {
293
+ return [
294
+ { id: 'outputCollision' },
295
+ { id: 'remove', platform, scope: placement.scope },
296
+ { id: 'technical' },
297
+ ];
298
+ }
113
299
  if (placement.health === 'error')
114
300
  return [{ id: 'technical' }, { id: 'back' }];
115
301
  if (placement.health === 'incomplete') {
302
+ if (platform === 'codex'
303
+ && (placement.actualState === 'needsSync' || placement.actualState === 'drifted'
304
+ || placement.recovery?.some((action) => action.command === 'sync'))) {
305
+ return [
306
+ { id: 'sync' },
307
+ { id: 'technical' },
308
+ { id: 'remove', platform, scope: placement.scope },
309
+ ];
310
+ }
116
311
  return [
117
312
  { id: 'reinstall', platform, scope: placement.scope },
118
313
  { id: 'technical' },
@@ -120,6 +315,9 @@ export function buildPlatformActions(plugin, platform) {
120
315
  ];
121
316
  }
122
317
  return [
318
+ ...missingPureNativeScopes(plugin, placement).map((scope) => ({
319
+ id: 'addScope', platform, scope,
320
+ })),
123
321
  ...(placement.revision.state === 'upgradeAvailable'
124
322
  ? [{
125
323
  id: 'upgrade',
@@ -134,64 +332,111 @@ export function buildPlatformActions(plugin, platform) {
134
332
  ];
135
333
  }
136
334
  export function hasInstalledPlacement(plugin) {
137
- return plugin.installedPlacements.some((placement) => placement.state !== 'notInstalled');
335
+ return plugin.installedPlacements.some((placement) => placement.state === 'installed' || placement.state === 'scopeConflict');
138
336
  }
139
337
  function sourceState(source, health) {
140
338
  if (health?.state === 'ready')
141
339
  return 'ready';
142
- if (health?.state === 'path')
143
- return 'path';
144
- if (health?.state === 'degraded')
145
- return 'cache';
146
340
  if (health?.state === 'unavailable')
147
341
  return 'error';
148
342
  if (source.error)
149
343
  return 'error';
150
- if (source.type === 'path' || source.freshness === 'path')
151
- return 'path';
152
- return source.freshness === 'cache' ? 'cache' : 'ready';
344
+ return 'ready';
153
345
  }
154
346
  function sourceIssue(health, impactedTargets = 0) {
155
- if (health.state !== 'degraded' && health.state !== 'unavailable')
347
+ if (health.state !== 'unavailable')
156
348
  return undefined;
157
- const cached = health.state === 'degraded';
158
- const issueId = health.issueId ?? `source:${health.sourceId}:${cached ? 'cache' : 'unavailable'}`;
349
+ const issueId = health.issueId ?? `source:${health.sourceId}:unavailable`;
159
350
  return {
160
351
  issueId,
161
- category: cached ? 'sourceDegraded' : 'sourceUnavailable',
162
- title: `${health.sourceId} · ${cached ? '来源使用缓存' : '来源不可用'}`,
163
- explanation: cached
164
- ? '远端检查失败。当前仍使用已验证的缓存数据。'
165
- : '来源检查失败,并且没有可用缓存数据。',
166
- impact: [cached ? '插件仍可浏览,但数据可能不是最新。' : '当前不能浏览或安装这个来源的插件。',
352
+ category: 'sourceUnavailable',
353
+ title: `${health.sourceId} · 来源不可用`,
354
+ explanation: '来源检查失败。',
355
+ impact: ['当前不能浏览或安装这个来源的插件。',
167
356
  ...(impactedTargets > 0 ? [`${impactedTargets} 个安装位置受此来源根问题影响。`] : [])],
168
357
  entity: { kind: 'source', id: health.sourceId },
169
358
  sourceId: health.sourceId,
170
- actions: cached
171
- ? [
172
- { id: 'refreshSource', label: '重新检查来源', description: '不会修改已安装内容' },
173
- { id: 'continueWithCache', label: '继续使用缓存', description: '当前缓存仍可安全读取' },
174
- { id: 'viewTechnical', label: '技术详情' },
175
- ]
176
- : [
177
- { id: 'editSource', label: '编辑来源', description: '检查 URL、路径或 ref' },
178
- { id: 'refreshSource', label: '稍后重试', description: '只重新检查来源' },
179
- { id: 'viewTechnical', label: '技术详情' },
180
- ],
359
+ actions: [
360
+ { id: 'editSource', label: '编辑来源', description: '检查 URL、路径或 ref' },
361
+ { id: 'refreshSource', label: '稍后重试', description: '只重新检查来源' },
362
+ { id: 'viewTechnical', label: '技术详情' },
363
+ ],
181
364
  technicalDetails: [{
182
- code: cached ? 'catalogSourceDegraded' : 'catalogSourceUnavailable',
183
- message: cached ? '远端检查失败;已验证缓存仍可用。' : '来源检查失败;没有可用缓存。',
365
+ code: 'catalogSourceUnavailable',
366
+ message: '来源检查失败。',
184
367
  }],
185
368
  };
186
369
  }
187
370
  function targetIssues(payload) {
188
371
  const issues = [];
189
372
  const byPlacement = new Map();
373
+ const canonicalRootsByEntity = new Map();
374
+ for (const target of payload.targets) {
375
+ for (const error of target.errors) {
376
+ if (!error.rootIssueId || !error.rootIssueCode)
377
+ continue;
378
+ const dependencyPluginId = error.dependencyPluginId
379
+ ?? dependencyPluginIdFromMessage(error.message);
380
+ const relationship = targetIssueRelationship({ target, error, dependencyPluginId });
381
+ if (relationship.relationship !== 'consumer')
382
+ continue;
383
+ const key = [
384
+ error.rootIssueCode,
385
+ relationship.rootEntity.pluginId,
386
+ relationship.rootEntity.platform,
387
+ relationship.rootEntity.scope,
388
+ ].join('\0');
389
+ const current = canonicalRootsByEntity.get(key);
390
+ if (!current || compareText(error.rootIssueId, current) < 0) {
391
+ canonicalRootsByEntity.set(key, error.rootIssueId);
392
+ }
393
+ }
394
+ }
190
395
  for (const target of payload.targets) {
191
396
  const key = `${target.pluginId}\0${target.platform}`;
192
397
  byPlacement.set(key, [...(byPlacement.get(key) ?? []), target]);
398
+ const scopedConflictRecovery = target.actual?.state === 'conflict'
399
+ ? target.recovery.flatMap((recovery) => {
400
+ const parsed = parsedScopedUninstallRecovery(recovery, target.platform);
401
+ return parsed ? [{ ...parsed, keepScope: parsed.scope === 'user'
402
+ ? 'project' : 'user' }] : [];
403
+ })
404
+ : [];
405
+ if (scopedConflictRecovery.length > 0) {
406
+ issues.push({
407
+ issueId: `scope:${target.pluginId}:${target.platform}`,
408
+ category: 'scopeConflict',
409
+ title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · 扩展位置冲突`,
410
+ explanation: 'AW-only 扩展不能同时使用所有项目和当前项目位置。',
411
+ impact: ['选择一条 root uninstall 路径,然后按该路径同步保留的 scope。'],
412
+ entity: { kind: 'plugin', id: target.pluginId },
413
+ pluginId: target.pluginId,
414
+ platform: target.platform,
415
+ scope: target.scope,
416
+ sourceId: target.sourceId,
417
+ sourceIdentity: target.sourceIdentity,
418
+ placements: [...target.desired.scopes].sort(compareText),
419
+ actions: [
420
+ ...scopedConflictRecovery.map((recovery) => ({
421
+ id: recovery.keepScope === 'user' ? 'keepUserScope' : 'keepProjectScope',
422
+ label: `保留${recovery.keepScope === 'user' ? '所有项目' : '当前项目'}`,
423
+ description: recovery.description,
424
+ pluginId: recovery.pluginId,
425
+ platform: target.platform,
426
+ scope: recovery.scope,
427
+ writeAction: 'uninstall',
428
+ })),
429
+ { id: 'viewTechnical', label: '查看冲突详情' },
430
+ ],
431
+ technicalDetails: [{
432
+ code: 'codexExtensionScopeConflict',
433
+ message: 'mixed 或 AW-only 扩展同时使用两个 scope。',
434
+ }],
435
+ });
436
+ }
193
437
  if (target.state !== 'partial' && target.state !== 'error'
194
- && !target.errors.some((error) => error.code === 'artifactCollision'))
438
+ && !target.errors.some((error) => error.code === 'artifactCollision'
439
+ || error.dependencyPluginId !== undefined && error.rootIssueId !== undefined))
195
440
  continue;
196
441
  const codes = new Set(target.errors.map((error) => error.code));
197
442
  const dependencyCodes = [
@@ -319,14 +564,41 @@ function targetIssues(payload) {
319
564
  { id: 'viewTechnical', label: '技术详情' },
320
565
  ]
321
566
  : [{ id: 'viewTechnical', label: '技术详情' }, { id: 'retryLater', label: '稍后重试' }];
322
- const issueId = rootError?.rootIssueId
323
- ?? `target:${target.pluginId}:${target.platform}:${target.scope}:${target.state}${roots.length > 1 ? `:${root.code}` : ''}${dependencyPluginId ? `:${dependencyPluginId}` : ''}`;
324
567
  const bundleUnavailable = root.code === 'bundleUnavailable';
568
+ const relationship = targetIssueRelationship({
569
+ target,
570
+ error: rootError,
571
+ dependencyPluginId,
572
+ });
573
+ const directRootCode = root.code === 'partial' ? 'incompleteInstallation'
574
+ : root.code === 'targetDiagnostic' ? 'diagnostic'
575
+ : undefined;
576
+ const canonicalRoot = relationship.relationship === 'direct' && directRootCode
577
+ ? canonicalRootsByEntity.get([
578
+ directRootCode,
579
+ relationship.rootEntity.pluginId,
580
+ relationship.rootEntity.platform,
581
+ relationship.rootEntity.scope,
582
+ ].join('\0'))
583
+ : undefined;
584
+ const issueId = rootError?.rootIssueId
585
+ ?? canonicalRoot
586
+ ?? (directRootCode
587
+ ? `${directRootCode}:${target.pluginId}:${target.platform}:${target.scope}`
588
+ : `target:${target.pluginId}:${target.platform}:${target.scope}:${target.state}${roots.length > 1 ? `:${root.code}` : ''}${dependencyPluginId ? `:${dependencyPluginId}` : ''}`);
325
589
  issues.push({
326
590
  issueId,
327
591
  category: root.category,
328
592
  rootCode: root.code,
329
593
  mergeEligible: root.mergeEligible,
594
+ relationship: relationship.relationship,
595
+ rootEntity: relationship.rootEntity,
596
+ affectedTargets: [{
597
+ pluginId: target.pluginId,
598
+ platform: target.platform,
599
+ scope: target.scope,
600
+ relationship: relationship.relationship,
601
+ }],
330
602
  title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${root.category === 'artifactCollision' ? '输出冲突' : dependencyCategory ? {
331
603
  dependencyMissing: '缺少依赖',
332
604
  dependencyNotReady: '依赖安装不完整',
@@ -348,7 +620,7 @@ function targetIssues(payload) {
348
620
  impact: [root.category === 'artifactCollision'
349
621
  ? '请先移除或迁移冲突内容,然后重试安装或重新安装。'
350
622
  : bundleUnavailable
351
- ? '可以清理可信的 AW 现场;这不表示来源缓存整体不可用。'
623
+ ? '可以清理可信的 AW 现场;这不表示来源整体可用。'
352
624
  : '写操作前需要重新检查这个目标。'],
353
625
  entity: { kind: 'plugin', id: target.pluginId },
354
626
  pluginId: target.pluginId,
@@ -586,46 +858,39 @@ function enrichDependencyIssuePlacements(issues, overviews) {
586
858
  }
587
859
  function issueDiagnostic(issue, sourceDetails) {
588
860
  const source = issue.sourceId ? sourceDetails.get(issue.sourceId) : undefined;
589
- const sourceDegraded = issue.category === 'sourceDegraded';
590
861
  const sourceUnavailable = issue.category === 'sourceUnavailable' && issue.entity.kind === 'source';
591
862
  const configUnavailable = issue.category === 'diagnostic' && issue.issueId.startsWith('config:');
592
863
  const configInvalid = issue.category === 'requiredConfigMissing';
593
864
  const dependency = issue.category.startsWith('dependency');
594
865
  const platformState = issue.issueId.startsWith('status:') || issue.rootCode === 'targetDiagnostic';
595
- const preserved = sourceDegraded || sourceUnavailable
866
+ const preserved = sourceUnavailable
596
867
  ? '已安装插件和已保存设置不会被删除。'
597
868
  : configUnavailable
598
869
  ? '已保存的设置保持不变。'
599
870
  : '现有安装和已保存设置不会因本页自动改变。';
600
- const impact = sourceDegraded
871
+ const impact = sourceUnavailable
601
872
  ? [
602
- { state: 'degraded', message: '远端数据可能不是最新;匹配的已验证缓存仍可浏览和安装。' },
873
+ { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
603
874
  { state: 'preserved', message: preserved },
604
875
  ]
605
- : sourceUnavailable
606
- ? [
607
- { state: 'blocked', message: '没有可用缓存;当前不能浏览或安装这个来源中的插件。' },
608
- { state: 'preserved', message: preserved },
609
- ]
610
- : [
611
- ...issue.impact.map((message) => ({
612
- state: dependency || configUnavailable || configInvalid || platformState ? 'blocked' : 'degraded',
613
- message,
614
- })),
615
- { state: 'preserved', message: preserved },
616
- ];
876
+ : [
877
+ ...issue.impact.map((message) => ({
878
+ state: dependency || configUnavailable || configInvalid || platformState ? 'blocked' : 'degraded',
879
+ message,
880
+ })),
881
+ { state: 'preserved', message: preserved },
882
+ ];
617
883
  const primaryAction = issue.actions.find((action) => action.id !== 'viewTechnical');
618
884
  const nextStep = primaryAction
619
885
  ? `${primaryAction.label},然后重新检查当前状态。`
620
886
  : '打开技术详情,记录安全错误代码后重新检查。';
621
- const sourceDiagnostic = sourceDegraded || sourceUnavailable;
887
+ const sourceDiagnostic = sourceUnavailable;
622
888
  const rawCode = sourceDiagnostic
623
889
  ? source?.errorCode ?? issue.technicalDetails[0]?.code
624
890
  : issue.technicalDetails[0]?.code;
625
- const code = safeDiagnosticCode(rawCode, sourceDegraded
626
- ? 'catalogSourceDegraded'
627
- : sourceUnavailable ? 'catalogSourceUnavailable' : 'diagnosticUnavailable');
628
- const stage = sourceDegraded || sourceUnavailable ? 'resolve-source'
891
+ const code = safeDiagnosticCode(rawCode, sourceUnavailable
892
+ ? 'catalogSourceUnavailable' : 'diagnosticUnavailable');
893
+ const stage = sourceUnavailable ? 'resolve-source'
629
894
  : configUnavailable ? 'read-config'
630
895
  : configInvalid ? 'validate-config'
631
896
  : dependency ? 'resolve-dependencies'
@@ -637,9 +902,6 @@ function issueDiagnostic(issue, sourceDetails) {
637
902
  : []),
638
903
  ...(issue.platform ? [{ key: 'platform', value: issue.platform }] : []),
639
904
  ...(issue.scope ? [{ key: 'scope', value: issue.scope }] : []),
640
- ...(sourceDiagnostic && source?.freshness
641
- ? [{ key: 'freshness', value: source.freshness }]
642
- : []),
643
905
  ...(issue.requiredFields?.slice(0, 1).map((field) => ({
644
906
  key: 'field',
645
907
  value: field.title,
@@ -653,7 +915,7 @@ function issueDiagnostic(issue, sourceDetails) {
653
915
  '按技术详情使用受支持命令或人工处理。',
654
916
  '再次运行状态检查。',
655
917
  ]
656
- : sourceDegraded || sourceUnavailable
918
+ : sourceUnavailable
657
919
  ? ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。']
658
920
  : dependency
659
921
  ? ['检查依赖关系和依赖插件状态。', '完成恢复动作后重新检查根插件。']
@@ -663,7 +925,7 @@ function issueDiagnostic(issue, sourceDetails) {
663
925
  ? ['完成或修正列出的设置字段。', '重新检查插件设置。']
664
926
  : ['检查目标平台配置和访问权限。', '完成恢复动作后重新运行状态检查。'];
665
927
  return {
666
- severity: sourceDegraded ? 'warning' : 'error',
928
+ severity: 'error',
667
929
  title: issue.title,
668
930
  userMessage: issue.explanation,
669
931
  impact,
@@ -674,9 +936,6 @@ function issueDiagnostic(issue, sourceDetails) {
674
936
  ...(sourceDiagnostic && issue.sourceId ? { sourceId: issue.sourceId } : {}),
675
937
  ...(sourceDiagnostic && source?.requestedRef ? { requestedRef: source.requestedRef } : {}),
676
938
  ...(sourceDiagnostic && source?.resolvedCommit ? { resolvedCommit: source.resolvedCommit } : {}),
677
- ...(sourceDegraded || sourceUnavailable
678
- ? { lastGood: sourceDegraded ? 'available' : 'unavailable' }
679
- : {}),
680
939
  ...(context.length > 0 ? { context } : {}),
681
940
  checks,
682
941
  },
@@ -698,7 +957,6 @@ function issueGroup(issue) {
698
957
  if (issue.category === 'artifactCollision')
699
958
  return 'artifactCollision';
700
959
  if (issue.category === 'sourceUnavailable'
701
- || issue.category === 'sourceDegraded'
702
960
  || issue.category === 'sourceIdentityChanged')
703
961
  return 'sourceUnavailable';
704
962
  if (issue.category.startsWith('dependency'))
@@ -719,8 +977,6 @@ function primaryAttentionCode(issue) {
719
977
  }
720
978
  if (issue.category === 'incompleteInstallation')
721
979
  return 'incompleteInstallation';
722
- if (issue.category === 'sourceDegraded')
723
- return 'sourceUnavailable';
724
980
  if (issue.category === 'sourceIdentityChanged')
725
981
  return 'sourceUnavailable';
726
982
  return issue.rootCode ?? issue.category;
@@ -746,21 +1002,43 @@ function issueReasonPriority(issue) {
746
1002
  }
747
1003
  function collapseRootIssues(issues) {
748
1004
  const byId = new Map();
749
- for (const issue of issues) {
750
- const existing = byId.get(issue.issueId);
751
- if (!existing) {
752
- byId.set(issue.issueId, issue);
753
- continue;
754
- }
755
- const primary = issue.dependencyPluginId ? issue : existing;
756
- byId.set(issue.issueId, {
1005
+ for (const issue of issues)
1006
+ byId.set(issue.issueId, [...(byId.get(issue.issueId) ?? []), issue]);
1007
+ return [...byId.values()].map((group) => {
1008
+ const ordered = [...group].sort((left, right) => Number(right.relationship === 'direct') - Number(left.relationship === 'direct')
1009
+ || compareText(left.pluginId ?? '', right.pluginId ?? '')
1010
+ || compareText(left.platform ?? '', right.platform ?? '')
1011
+ || compareText(left.scope ?? '', right.scope ?? ''));
1012
+ const primary = ordered[0];
1013
+ const direct = ordered.find((issue) => issue.relationship === 'direct');
1014
+ const rootEntity = direct?.rootEntity ?? ordered.map((issue) => issue.rootEntity)
1015
+ .filter((entity) => entity !== undefined)
1016
+ .sort((left, right) => compareText(left.pluginId, right.pluginId)
1017
+ || compareText(left.platform, right.platform)
1018
+ || compareText(left.scope, right.scope))[0];
1019
+ const affectedTargets = [...new Map(ordered.flatMap((issue) => issue.affectedTargets ?? [])
1020
+ .map((target) => [`${target.pluginId}\0${target.platform}\0${target.scope}`, target])).values()]
1021
+ .sort((left, right) => compareText(left.pluginId, right.pluginId)
1022
+ || compareText(left.platform, right.platform)
1023
+ || compareText(left.scope, right.scope));
1024
+ const projected = rootEntity && !direct ? {
757
1025
  ...primary,
758
- impact: [...new Set([...existing.impact, ...issue.impact])],
759
- technicalDetails: [...new Map([...existing.technicalDetails, ...issue.technicalDetails]
1026
+ title: `${rootEntity.pluginId} · ${rootEntity.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${primary.category === 'artifactCollision' ? '输出冲突' : primary.title.split(' · ').at(-1) ?? primary.title}`,
1027
+ entity: { kind: 'plugin', id: rootEntity.pluginId },
1028
+ pluginId: rootEntity.pluginId,
1029
+ platform: rootEntity.platform,
1030
+ scope: rootEntity.scope,
1031
+ sourceId: rootEntity.pluginId.split('/', 1)[0],
1032
+ } : primary;
1033
+ return {
1034
+ ...projected,
1035
+ ...(rootEntity ? { rootEntity } : {}),
1036
+ ...(affectedTargets.length > 0 ? { affectedTargets } : {}),
1037
+ impact: [...new Set(ordered.flatMap((issue) => issue.impact))],
1038
+ technicalDetails: [...new Map(ordered.flatMap((issue) => issue.technicalDetails)
760
1039
  .map((detail) => [`${detail.code}\0${detail.message}`, detail])).values()],
761
- });
762
- }
763
- return [...byId.values()];
1040
+ };
1041
+ });
764
1042
  }
765
1043
  export function buildTuiViewModel(options) {
766
1044
  const dependencyById = new Map((options.dependencyOverviews ?? [])
@@ -782,8 +1060,9 @@ export function buildTuiViewModel(options) {
782
1060
  : [];
783
1061
  });
784
1062
  const attention = placements.some((placement) => placement.state === 'scopeConflict'
785
- || (placement.state === 'installed' && placement.health !== 'ready'));
786
- const installed = placements.some((placement) => placement.state !== 'notInstalled');
1063
+ || (placement.state === 'installed'
1064
+ && (placement.health !== 'ready' || placement.blockingReason !== undefined)));
1065
+ const installed = placements.some((placement) => placement.state === 'installed' || placement.state === 'scopeConflict');
787
1066
  const unavailable = options.sourceHealth?.get(sourceId)?.state === 'unavailable'
788
1067
  || (summary?.missingCapabilities?.length ?? 0) > 0;
789
1068
  return {
@@ -800,6 +1079,10 @@ export function buildTuiViewModel(options) {
800
1079
  supportedPlatforms: summary?.supportedTargets
801
1080
  ? ['codex', 'claude'].filter((platform) => summary.supportedTargets[platform].length > 0)
802
1081
  : placements.map((placement) => placement.platform),
1082
+ ...(summary?.supportedTargets ? { supportedScopes: {
1083
+ codex: [...summary.supportedTargets.codex],
1084
+ claude: [...summary.supportedTargets.claude],
1085
+ } } : {}),
803
1086
  installedPlacements: placements,
804
1087
  aggregateStatus: unavailable ? 'unavailable' : attention ? 'needsAttention' : installed ? 'installed' : 'available',
805
1088
  portableSkills: options.portablePluginIds?.includes(pluginId) ?? false,
@@ -836,7 +1119,7 @@ export function buildTuiViewModel(options) {
836
1119
  state: sourceState(source, options.sourceHealth?.get(source.id)),
837
1120
  }));
838
1121
  const sourceRootIds = new Set([...(options.sourceHealth?.values() ?? [])]
839
- .filter((health) => health.state === 'degraded' || health.state === 'unavailable')
1122
+ .filter((health) => health.state === 'unavailable')
840
1123
  .map((health) => health.sourceId));
841
1124
  const allTargetIssues = targetIssues(options.status);
842
1125
  const targetImpactCounts = new Map();
@@ -866,6 +1149,7 @@ export function buildTuiViewModel(options) {
866
1149
  const pluginsWithAttention = plugins.map((plugin) => {
867
1150
  const primary = stableIssueDrafts.filter((issue) => issue.pluginId === plugin.pluginId
868
1151
  || issue.dependencyPluginId === plugin.pluginId
1152
+ || issue.affectedTargets?.some((target) => target.pluginId === plugin.pluginId)
869
1153
  || issue.entity.kind === 'source' && issue.sourceId === plugin.sourceId)
870
1154
  .sort((left, right) => issueReasonPriority(left) - issueReasonPriority(right)
871
1155
  || compareText(primaryAttentionCode(left), primaryAttentionCode(right))
@@ -875,7 +1159,7 @@ export function buildTuiViewModel(options) {
875
1159
  return {
876
1160
  ...plugin,
877
1161
  primaryAttention: {
878
- severity: primary.category === 'sourceDegraded' ? 'warning' : 'error',
1162
+ severity: 'error',
879
1163
  code: primaryAttentionCode(primary),
880
1164
  message: primary.explanation,
881
1165
  },
@@ -895,7 +1179,6 @@ export function buildTuiViewModel(options) {
895
1179
  installedPlugins: pluginsWithAttention.filter(hasInstalledPlacement).length,
896
1180
  issueCount: issues.length,
897
1181
  sourceCount: sourceTabs.length,
898
- cachedSourceCount: sourceTabs.filter((source) => source.state === 'cache').length,
899
1182
  unavailableSourceCount: sourceTabs.filter((source) => source.state === 'error').length,
900
1183
  },
901
1184
  };