@lesliechan721/aw 0.1.0-beta.7 → 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 (341) 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 +8 -37
  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/plugin-status.mjs +8 -11
  10. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  11. package/dist/authoring/scripts/remove-plugin.mjs +2 -1
  12. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  13. package/dist/authoring/scripts/validate-plugin.mjs +64 -758
  14. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  15. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  16. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  17. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  18. package/dist/scripts/bundle-plugins.d.ts +1 -0
  19. package/dist/scripts/bundle-plugins.js +2 -1
  20. package/dist/scripts/bundle-plugins.js.map +1 -1
  21. package/dist/scripts/plugin-staging.d.ts +6 -8
  22. package/dist/scripts/plugin-staging.js +15 -28
  23. package/dist/scripts/plugin-staging.js.map +1 -1
  24. package/dist/scripts/test-plugin-scripts.js +2 -33
  25. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  26. package/dist/src/adapters/helpers.js +2 -2
  27. package/dist/src/adapters/helpers.js.map +1 -1
  28. package/dist/src/authoring/workspace-commands.d.ts +17 -1
  29. package/dist/src/authoring/workspace-commands.js +21 -4
  30. package/dist/src/authoring/workspace-commands.js.map +1 -1
  31. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  32. package/dist/src/commands/aw-capabilities.js +254 -0
  33. package/dist/src/commands/aw-capabilities.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +118 -0
  35. package/dist/src/commands/catalog-dev-source.js +417 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +13 -0
  38. package/dist/src/commands/catalog-generation.js +13 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +29 -28
  41. package/dist/src/commands/catalog-publication-inspection.js +138 -50
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  44. package/dist/src/commands/catalog-publication-journal.js +147 -0
  45. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  46. package/dist/src/commands/catalog-publication-target.d.ts +18 -0
  47. package/dist/src/commands/catalog-publication-target.js +95 -0
  48. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  49. package/dist/src/commands/catalog-publish.d.ts +58 -15
  50. package/dist/src/commands/catalog-publish.js +590 -210
  51. package/dist/src/commands/catalog-publish.js.map +1 -1
  52. package/dist/src/commands/catalog-source.d.ts +12 -15
  53. package/dist/src/commands/catalog-source.js +61 -86
  54. package/dist/src/commands/catalog-source.js.map +1 -1
  55. package/dist/src/commands/catalog-status.d.ts +18 -22
  56. package/dist/src/commands/catalog-status.js +81 -115
  57. package/dist/src/commands/catalog-status.js.map +1 -1
  58. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  59. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  60. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  61. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  62. package/dist/src/commands/dependency-operation-plan.js +331 -6
  63. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  64. package/dist/src/commands/init.d.ts +5 -11
  65. package/dist/src/commands/init.js +30 -51
  66. package/dist/src/commands/init.js.map +1 -1
  67. package/dist/src/commands/offline-cleanup-evidence.d.ts +43 -0
  68. package/dist/src/commands/offline-cleanup-evidence.js +116 -0
  69. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  70. package/dist/src/commands/options.d.ts +7 -1
  71. package/dist/src/commands/options.js +65 -6
  72. package/dist/src/commands/options.js.map +1 -1
  73. package/dist/src/commands/plugin-capabilities.js +5 -1
  74. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  75. package/dist/src/commands/rebuild-command.d.ts +27 -13
  76. package/dist/src/commands/rebuild-command.js +1472 -471
  77. package/dist/src/commands/rebuild-command.js.map +1 -1
  78. package/dist/src/commands/rebuild-integration.d.ts +63 -8
  79. package/dist/src/commands/rebuild-integration.js +761 -439
  80. package/dist/src/commands/rebuild-integration.js.map +1 -1
  81. package/dist/src/commands/rebuild-operations.d.ts +2 -8
  82. package/dist/src/commands/rebuild-operations.js +48 -132
  83. package/dist/src/commands/rebuild-operations.js.map +1 -1
  84. package/dist/src/commands/runtime.js +21 -34
  85. package/dist/src/commands/runtime.js.map +1 -1
  86. package/dist/src/commands/skill-eval.d.ts +33 -0
  87. package/dist/src/commands/skill-eval.js +363 -0
  88. package/dist/src/commands/skill-eval.js.map +1 -0
  89. package/dist/src/commands/status-v6.d.ts +202 -0
  90. package/dist/src/commands/status-v6.js +1938 -0
  91. package/dist/src/commands/status-v6.js.map +1 -0
  92. package/dist/src/commands/sync-command.d.ts +21 -0
  93. package/dist/src/commands/sync-command.js +206 -0
  94. package/dist/src/commands/sync-command.js.map +1 -0
  95. package/dist/src/commands/wizard.d.ts +2 -2
  96. package/dist/src/commands/wizard.js +2 -2
  97. package/dist/src/commands/wizard.js.map +1 -1
  98. package/dist/src/commands/write-commands.js +33 -19
  99. package/dist/src/commands/write-commands.js.map +1 -1
  100. package/dist/src/constants.d.ts +7 -1
  101. package/dist/src/constants.js +7 -1
  102. package/dist/src/constants.js.map +1 -1
  103. package/dist/src/eval/manifest-v1.d.ts +66 -0
  104. package/dist/src/eval/manifest-v1.js +326 -0
  105. package/dist/src/eval/manifest-v1.js.map +1 -0
  106. package/dist/src/generators/local-plugin.d.ts +3 -0
  107. package/dist/src/generators/local-plugin.js +167 -45
  108. package/dist/src/generators/local-plugin.js.map +1 -1
  109. package/dist/src/index.d.ts +6 -1
  110. package/dist/src/index.js +863 -126
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/output/command-receipt.d.ts +58 -8
  113. package/dist/src/output/command-receipt.js +141 -11
  114. package/dist/src/output/command-receipt.js.map +1 -1
  115. package/dist/src/output/model.d.ts +3 -44
  116. package/dist/src/output/model.js +1 -144
  117. package/dist/src/output/model.js.map +1 -1
  118. package/dist/src/output/rebuild-progress.js +0 -3
  119. package/dist/src/output/rebuild-progress.js.map +1 -1
  120. package/dist/src/portable/commands.d.ts +6 -2
  121. package/dist/src/portable/commands.js +57 -6
  122. package/dist/src/portable/commands.js.map +1 -1
  123. package/dist/src/portable/materializer.d.ts +4 -4
  124. package/dist/src/portable/materializer.js +61 -12
  125. package/dist/src/portable/materializer.js.map +1 -1
  126. package/dist/src/tui/catalog-generation.d.ts +35 -0
  127. package/dist/src/tui/catalog-generation.js +85 -0
  128. package/dist/src/tui/catalog-generation.js.map +1 -0
  129. package/dist/src/tui/components.js +1 -2
  130. package/dist/src/tui/components.js.map +1 -1
  131. package/dist/src/tui/console-app-v4.d.ts +1 -1
  132. package/dist/src/tui/console-app-v4.js +647 -196
  133. package/dist/src/tui/console-app-v4.js.map +1 -1
  134. package/dist/src/tui/controller-runtime.d.ts +6 -2
  135. package/dist/src/tui/controller-runtime.js +646 -137
  136. package/dist/src/tui/controller-runtime.js.map +1 -1
  137. package/dist/src/tui/controller.d.ts +77 -15
  138. package/dist/src/tui/controller.js +647 -306
  139. package/dist/src/tui/controller.js.map +1 -1
  140. package/dist/src/tui/diagnostics.d.ts +1 -2
  141. package/dist/src/tui/diagnostics.js.map +1 -1
  142. package/dist/src/tui/index.d.ts +1 -0
  143. package/dist/src/tui/index.js +1 -0
  144. package/dist/src/tui/index.js.map +1 -1
  145. package/dist/src/tui/input-display.d.ts +8 -0
  146. package/dist/src/tui/input-display.js +36 -0
  147. package/dist/src/tui/input-display.js.map +1 -0
  148. package/dist/src/tui/model.d.ts +102 -14
  149. package/dist/src/tui/model.js +630 -139
  150. package/dist/src/tui/model.js.map +1 -1
  151. package/dist/src/tui/router.d.ts +35 -0
  152. package/dist/src/tui/router.js.map +1 -1
  153. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  154. package/dist/src/tui/source-ref-presets.js +11 -0
  155. package/dist/src/tui/source-ref-presets.js.map +1 -0
  156. package/dist/src/tui/views.js +31 -3
  157. package/dist/src/tui/views.js.map +1 -1
  158. package/dist/src/types.d.ts +104 -13
  159. package/dist/src/types.js.map +1 -1
  160. package/dist/src/utils/agent-version.d.ts +7 -1
  161. package/dist/src/utils/agent-version.js +41 -8
  162. package/dist/src/utils/agent-version.js.map +1 -1
  163. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  164. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  165. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  166. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  167. package/dist/src/utils/bundle-integrity.js +0 -25
  168. package/dist/src/utils/bundle-integrity.js.map +1 -1
  169. package/dist/src/utils/bundle-layout.js +8 -6
  170. package/dist/src/utils/bundle-layout.js.map +1 -1
  171. package/dist/src/utils/catalog-digests.d.ts +7 -0
  172. package/dist/src/utils/catalog-digests.js +40 -2
  173. package/dist/src/utils/catalog-digests.js.map +1 -1
  174. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  175. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  176. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  177. package/dist/src/utils/catalog-generation-error.d.ts +13 -0
  178. package/dist/src/utils/catalog-generation-error.js +28 -0
  179. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  180. package/dist/src/utils/catalog-generation-resolver.d.ts +20 -0
  181. package/dist/src/utils/catalog-generation-resolver.js +143 -0
  182. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  183. package/dist/src/utils/catalog-generation-service.d.ts +43 -0
  184. package/dist/src/utils/catalog-generation-service.js +306 -0
  185. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  186. package/dist/src/utils/catalog-generation-state.d.ts +17 -0
  187. package/dist/src/utils/catalog-generation-state.js +168 -0
  188. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  189. package/dist/src/utils/catalog-generation-store.d.ts +34 -0
  190. package/dist/src/utils/catalog-generation-store.js +251 -0
  191. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  192. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  193. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v6.js} +35 -43
  194. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  195. package/dist/src/utils/catalog-operation-context.d.ts +20 -0
  196. package/dist/src/utils/catalog-operation-context.js +141 -0
  197. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  198. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  199. package/dist/src/utils/catalog-public-error.js +21 -8
  200. package/dist/src/utils/catalog-public-error.js.map +1 -1
  201. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  202. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  203. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  204. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  205. package/dist/src/utils/catalog-snapshot.js +11 -3
  206. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  207. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  208. package/dist/src/utils/catalog-source-identity.js +21 -2
  209. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  210. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  211. package/dist/src/utils/catalog-source-references.js +44 -55
  212. package/dist/src/utils/catalog-source-references.js.map +1 -1
  213. package/dist/src/utils/catalog-sources.d.ts +1 -0
  214. package/dist/src/utils/catalog-sources.js +55 -20
  215. package/dist/src/utils/catalog-sources.js.map +1 -1
  216. package/dist/src/utils/codex-config.d.ts +2 -0
  217. package/dist/src/utils/codex-config.js +26 -5
  218. package/dist/src/utils/codex-config.js.map +1 -1
  219. package/dist/src/utils/codex-manager.d.ts +81 -0
  220. package/dist/src/utils/codex-manager.js +810 -0
  221. package/dist/src/utils/codex-manager.js.map +1 -0
  222. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  223. package/dist/src/utils/codex-reconciler.js +327 -0
  224. package/dist/src/utils/codex-reconciler.js.map +1 -0
  225. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  226. package/dist/src/utils/codex-shared-native.js +87 -0
  227. package/dist/src/utils/codex-shared-native.js.map +1 -0
  228. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  229. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  230. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  231. package/dist/src/utils/codex-surface-analysis.js +247 -0
  232. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  233. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  234. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  235. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  236. package/dist/src/utils/git-catalog-provider.js +66 -140
  237. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  238. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  239. package/dist/src/utils/lifecycle-active-store.js +35 -247
  240. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  241. package/dist/src/utils/manifest.d.ts +1 -3
  242. package/dist/src/utils/manifest.js +1 -3
  243. package/dist/src/utils/manifest.js.map +1 -1
  244. package/dist/src/utils/multi-source-catalog.d.ts +3 -9
  245. package/dist/src/utils/multi-source-catalog.js +53 -110
  246. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  247. package/dist/src/utils/native-cli.d.ts +49 -6
  248. package/dist/src/utils/native-cli.js +116 -33
  249. package/dist/src/utils/native-cli.js.map +1 -1
  250. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -3
  251. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  252. package/dist/src/utils/ownership.d.ts +10 -11
  253. package/dist/src/utils/ownership.js +55 -112
  254. package/dist/src/utils/ownership.js.map +1 -1
  255. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  256. package/dist/src/utils/path-catalog-provider.js +52 -65
  257. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  258. package/dist/src/utils/paths.d.ts +0 -1
  259. package/dist/src/utils/paths.js +0 -1
  260. package/dist/src/utils/paths.js.map +1 -1
  261. package/dist/src/utils/plugin-config-write.js +18 -40
  262. package/dist/src/utils/plugin-config-write.js.map +1 -1
  263. package/dist/src/utils/plugin-config.d.ts +1 -12
  264. package/dist/src/utils/plugin-config.js +107 -226
  265. package/dist/src/utils/plugin-config.js.map +1 -1
  266. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  267. package/dist/src/utils/plugin-intent-state.js +325 -0
  268. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  269. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  270. package/dist/src/utils/plugin-scripts.js +1 -1
  271. package/dist/src/utils/plugin-scripts.js.map +1 -1
  272. package/dist/src/utils/state-paths.js +7 -2
  273. package/dist/src/utils/state-paths.js.map +1 -1
  274. package/dist/src/utils/target-eligibility.d.ts +8 -2
  275. package/dist/src/utils/target-eligibility.js +9 -5
  276. package/dist/src/utils/target-eligibility.js.map +1 -1
  277. package/package.json +6 -4
  278. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  279. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  280. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  281. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  282. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  283. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  284. package/dist/scripts/codex-eval-app-server.d.ts +0 -89
  285. package/dist/scripts/codex-eval-app-server.js +0 -1228
  286. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  287. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  288. package/dist/scripts/codex-eval-isolation.js +0 -318
  289. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  290. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  291. package/dist/scripts/codex-eval-process-management.js +0 -69
  292. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  293. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  294. package/dist/scripts/codex-eval-process-tree.js +0 -803
  295. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  296. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  297. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  298. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  299. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  300. package/dist/scripts/codex-trigger-eval.js +0 -968
  301. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  302. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  303. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  304. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  305. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  306. package/dist/scripts/eval-plugin-skills.js +0 -1586
  307. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  308. package/dist/scripts/eval-run-layout.d.ts +0 -17
  309. package/dist/scripts/eval-run-layout.js +0 -26
  310. package/dist/scripts/eval-run-layout.js.map +0 -1
  311. package/dist/scripts/plugin-eval-contract.d.ts +0 -185
  312. package/dist/scripts/plugin-eval-contract.js +0 -695
  313. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  314. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  315. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  316. package/dist/scripts/skill-reporting.d.ts +0 -212
  317. package/dist/scripts/skill-reporting.js +0 -922
  318. package/dist/scripts/skill-reporting.js.map +0 -1
  319. package/dist/src/commands/status-v4.d.ts +0 -123
  320. package/dist/src/commands/status-v4.js +0 -905
  321. package/dist/src/commands/status-v4.js.map +0 -1
  322. package/dist/src/commands/status.d.ts +0 -93
  323. package/dist/src/commands/status.js +0 -442
  324. package/dist/src/commands/status.js.map +0 -1
  325. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  326. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  327. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  328. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  329. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  330. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  331. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
  332. package/dist/src/utils/catalog-manifest-v4.js.map +0 -1
  333. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  334. package/dist/src/utils/catalog-source-cache.js +0 -296
  335. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  336. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  337. package/dist/src/utils/codex-project-surface.js +0 -49
  338. package/dist/src/utils/codex-project-surface.js.map +0 -1
  339. package/dist/src/utils/plugin-release.d.ts +0 -2
  340. package/dist/src/utils/plugin-release.js +0 -2
  341. 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,63 +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]);
193
- if (target.state !== 'partial' && target.state !== 'error')
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
+ }
437
+ if (target.state !== 'partial' && target.state !== 'error'
438
+ && !target.errors.some((error) => error.code === 'artifactCollision'
439
+ || error.dependencyPluginId !== undefined && error.rootIssueId !== undefined))
194
440
  continue;
195
441
  const codes = new Set(target.errors.map((error) => error.code));
196
442
  const dependencyCodes = [
@@ -200,41 +446,66 @@ function targetIssues(payload) {
200
446
  'dependencyStillRequired',
201
447
  'dependencyGraphInvalid',
202
448
  ];
203
- const dependencyRoots = [...new Map(target.errors.flatMap((error) => {
204
- if (!dependencyCodes.includes(error.code))
205
- return [];
206
- const code = error.code;
207
- const dependencyPluginId = dependencyPluginIdFromMessage(error.message);
208
- return [[`${code}\0${dependencyPluginId ?? ''}`, {
209
- code,
210
- category: code,
211
- mergeEligible: false,
212
- error,
213
- dependencyPluginId,
214
- }]];
215
- })).values()];
216
- const roots = dependencyRoots.length > 0
217
- ? dependencyRoots
218
- : target.state === 'partial'
219
- ? [{ code: 'partial', category: 'incompleteInstallation', mergeEligible: false }]
220
- : codes.has('sourceMissing')
221
- ? [{ code: 'sourceMissing', category: 'sourceMissing', mergeEligible: false }]
222
- : codes.has('sourceChanged')
223
- ? [{ code: 'sourceChanged', category: 'sourceIdentityChanged', mergeEligible: false }]
224
- : [
449
+ const dependencyRootMap = new Map();
450
+ for (const error of target.errors) {
451
+ if (!dependencyCodes.includes(error.code))
452
+ continue;
453
+ if (error.rootIssueCode === 'artifactCollision') {
454
+ dependencyRootMap.set(error.rootIssueId ?? `artifactCollision\0${error.dependencyPluginId ?? ''}`, {
455
+ code: 'artifactCollision',
456
+ category: 'artifactCollision',
457
+ mergeEligible: false,
458
+ error,
459
+ dependencyPluginId: error.dependencyPluginId,
460
+ });
461
+ continue;
462
+ }
463
+ const code = error.code;
464
+ const dependencyPluginId = error.dependencyPluginId ?? dependencyPluginIdFromMessage(error.message);
465
+ dependencyRootMap.set(`${code}\0${dependencyPluginId ?? ''}`, {
466
+ code,
467
+ category: code,
468
+ mergeEligible: false,
469
+ error,
470
+ dependencyPluginId,
471
+ });
472
+ }
473
+ const dependencyRoots = [...dependencyRootMap.values()];
474
+ const directCollision = target.errors.find((error) => error.code === 'artifactCollision');
475
+ const projectedCollisionRoots = dependencyRoots.filter((root) => root.code === 'artifactCollision');
476
+ const ordinaryDependencyRoots = dependencyRoots.filter((root) => root.code !== 'artifactCollision');
477
+ const roots = codes.has('sourceMissing')
478
+ ? [{ code: 'sourceMissing', category: 'sourceMissing', mergeEligible: false }]
479
+ : directCollision || projectedCollisionRoots.length > 0
480
+ ? [{
481
+ code: 'artifactCollision',
482
+ category: 'artifactCollision',
483
+ mergeEligible: false,
484
+ error: directCollision ?? projectedCollisionRoots[0].error,
485
+ dependencyPluginId: projectedCollisionRoots[0]?.dependencyPluginId,
486
+ }]
487
+ : codes.has('sourceChanged')
488
+ ? [{ code: 'sourceChanged', category: 'sourceIdentityChanged', mergeEligible: false }]
489
+ : codes.has('catalogSourceUnavailable') || codes.has('bundleUnavailable')
490
+ ? [
225
491
  ...(codes.has('catalogSourceUnavailable')
226
492
  ? [{ code: 'catalogSourceUnavailable', category: 'sourceUnavailable', mergeEligible: true }]
227
493
  : []),
228
494
  ...(codes.has('bundleUnavailable')
229
495
  ? [{ code: 'bundleUnavailable', category: 'sourceUnavailable', mergeEligible: false }]
230
496
  : []),
231
- ...(!codes.has('catalogSourceUnavailable') && !codes.has('bundleUnavailable')
232
- ? [{ code: 'targetDiagnostic', category: 'diagnostic', mergeEligible: false }]
233
- : []),
234
- ];
497
+ ]
498
+ : ordinaryDependencyRoots.length > 0
499
+ ? ordinaryDependencyRoots
500
+ : target.state === 'partial'
501
+ ? [{ code: 'partial', category: 'incompleteInstallation', mergeEligible: false }]
502
+ : [
503
+ { code: 'targetDiagnostic', category: 'diagnostic', mergeEligible: false },
504
+ ];
235
505
  for (const root of roots) {
236
506
  const rootError = root.error ?? target.errors.find((error) => error.code === root.code);
237
507
  const dependencyPluginId = root.dependencyPluginId
508
+ ?? rootError?.dependencyPluginId
238
509
  ?? (rootError ? dependencyPluginIdFromMessage(rootError.message) : undefined);
239
510
  const dependencyCategory = root.category.startsWith('dependency');
240
511
  const actions = root.category === 'dependencyMissing'
@@ -259,60 +530,98 @@ function targetIssues(payload) {
259
530
  { id: 'viewDependencies', label: '查看依赖关系' },
260
531
  { id: 'viewTechnical', label: '技术详情' },
261
532
  ]
262
- : root.category === 'incompleteInstallation'
533
+ : root.category === 'artifactCollision'
263
534
  ? [
264
- { id: 'reinstall', label: '重新安装', description: '保留插件设置,重新生成缺失内容' },
265
- { id: 'remove', label: '移除安装', description: '保留插件设置' },
266
- { id: 'viewTechnical', label: '技术详情' },
535
+ { id: 'viewTechnical', label: '查看冲突原因' },
536
+ { id: 'retryLater', label: '处理冲突后重试' },
267
537
  ]
268
- : root.category === 'sourceMissing'
538
+ : root.category === 'incompleteInstallation'
269
539
  ? [
270
- { id: 'reAddSource', label: '重新添加同一来源', description: '保留来源 ID;来源类型和位置由你选择' },
540
+ { id: 'reinstall', label: '重新安装', description: '保留插件设置,重新生成缺失内容' },
541
+ { id: 'remove', label: '移除安装', description: '保留插件设置' },
271
542
  { id: 'viewTechnical', label: '技术详情' },
272
543
  ]
273
- : root.category === 'sourceIdentityChanged'
544
+ : root.category === 'sourceMissing'
274
545
  ? [
275
- { id: 'viewSourceChange', label: '查看来源变更', description: '不会自动覆盖现场状态' },
546
+ { id: 'cleanup', label: '清理已安装内容', description: '只清理 matching owner 的现场内容;保留插件设置', writeAction: 'cleanup' },
547
+ { id: 'reAddSource', label: '重新添加同一来源', description: '保留来源 ID;来源类型和位置由你选择' },
276
548
  { id: 'viewTechnical', label: '技术详情' },
277
549
  ]
278
- : root.category === 'sourceUnavailable'
550
+ : root.category === 'sourceIdentityChanged'
279
551
  ? [
280
- ...(root.code === 'bundleUnavailable' ? [{
281
- id: 'cleanup',
282
- label: '清理已安装内容',
283
- description: '只清理 AW 管理的现场内容;保留插件设置',
284
- writeAction: 'cleanup',
285
- }] : []),
286
- { id: 'editSource', label: '查看来源', description: root.code === 'bundleUnavailable' ? '检查当前 Catalog 或稍后重试' : '修复来源配置或稍后重试' },
552
+ { id: 'viewSourceChange', label: '查看来源变更', description: '不会自动覆盖现场状态' },
287
553
  { id: 'viewTechnical', label: '技术详情' },
288
554
  ]
289
- : [{ id: 'viewTechnical', label: '技术详情' }, { id: 'retryLater', label: '稍后重试' }];
290
- const issueId = `target:${target.pluginId}:${target.platform}:${target.scope}:${target.state}${roots.length > 1 ? `:${root.code}` : ''}${dependencyPluginId ? `:${dependencyPluginId}` : ''}`;
555
+ : root.category === 'sourceUnavailable'
556
+ ? [
557
+ ...(root.code === 'bundleUnavailable' ? [{
558
+ id: 'cleanup',
559
+ label: '清理已安装内容',
560
+ description: '只清理 AW 管理的现场内容;保留插件设置',
561
+ writeAction: 'cleanup',
562
+ }] : []),
563
+ { id: 'editSource', label: '查看来源', description: root.code === 'bundleUnavailable' ? '检查当前 Catalog 或稍后重试' : '修复来源配置或稍后重试' },
564
+ { id: 'viewTechnical', label: '技术详情' },
565
+ ]
566
+ : [{ id: 'viewTechnical', label: '技术详情' }, { id: 'retryLater', label: '稍后重试' }];
291
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}` : ''}`);
292
589
  issues.push({
293
590
  issueId,
294
591
  category: root.category,
295
592
  rootCode: root.code,
296
593
  mergeEligible: root.mergeEligible,
297
- title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${dependencyCategory ? {
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
+ }],
602
+ title: `${target.pluginId} · ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${root.category === 'artifactCollision' ? '输出冲突' : dependencyCategory ? {
298
603
  dependencyMissing: '缺少依赖',
299
604
  dependencyNotReady: '依赖安装不完整',
300
605
  dependencyTargetUnsupported: '依赖目标不兼容',
301
606
  dependencyStillRequired: '依赖仍被使用',
302
607
  dependencyGraphInvalid: '依赖关系无效',
303
608
  }[root.category] : target.state === 'partial' ? '安装不完整' : bundleUnavailable ? '当前 Catalog 不再包含此插件' : '检查失败'}`,
304
- explanation: dependencyCategory
305
- ? dependencyPluginId
306
- ? `${dependencyPluginId} 导致当前依赖检查不能通过。`
307
- : '当前依赖检查不能安全通过。'
308
- : target.state === 'partial'
309
- ? '必要内容缺失,插件可能不能稳定运行。'
609
+ explanation: root.category === 'artifactCollision'
610
+ ? '目标位置已有不属于当前插件的内容。AW 不会覆盖或删除这些内容。'
611
+ : dependencyCategory
612
+ ? dependencyPluginId
613
+ ? `${dependencyPluginId} 导致当前依赖检查不能通过。`
614
+ : '当前依赖检查不能安全通过。'
615
+ : target.state === 'partial'
616
+ ? '必要内容缺失,插件可能不能稳定运行。'
617
+ : bundleUnavailable
618
+ ? '当前 Catalog 不再包含此插件,但仍发现 AW 管理的已安装内容。'
619
+ : '当前目标不能安全分类为正常安装。',
620
+ impact: [root.category === 'artifactCollision'
621
+ ? '请先移除或迁移冲突内容,然后重试安装或重新安装。'
310
622
  : bundleUnavailable
311
- ? '当前 Catalog 不再包含此插件,但仍发现 AW 管理的已安装内容。'
312
- : '当前目标不能安全分类为正常安装。',
313
- impact: [bundleUnavailable
314
- ? '可以清理可信的 AW 现场;这不表示来源缓存整体不可用。'
315
- : '写操作前需要重新检查这个目标。'],
623
+ ? '可以清理可信的 AW 现场;这不表示来源整体可用。'
624
+ : '写操作前需要重新检查这个目标。'],
316
625
  entity: { kind: 'plugin', id: target.pluginId },
317
626
  pluginId: target.pluginId,
318
627
  ...(dependencyPluginId ? { dependencyPluginId } : {}),
@@ -324,8 +633,18 @@ function targetIssues(payload) {
324
633
  technicalDetails: target.errors.length > 0
325
634
  ? target.errors.filter((error) => root.code === 'targetDiagnostic'
326
635
  || root.code === 'partial'
327
- || error.code === root.code)
328
- .map((error) => ({ code: error.code, message: '状态检查报告了这个安全错误代码。' }))
636
+ || error.code === root.code
637
+ || error.rootIssueId === issueId)
638
+ .map((error) => ({
639
+ code: error.code,
640
+ message: error.collision
641
+ ? ({
642
+ ownerMismatch: '目标由其他 owner 占用。',
643
+ unownedContent: '目标包含没有 AW owner 标记的内容。',
644
+ typeMismatch: '目标节点类型不符合预期。',
645
+ })[error.collision.reason]
646
+ : '状态检查报告了这个安全错误代码。',
647
+ }))
329
648
  : [{ code: 'targetStateError', message: '目标状态异常。' }],
330
649
  });
331
650
  }
@@ -459,6 +778,39 @@ function configIssues(pluginSummaries) {
459
778
  }];
460
779
  });
461
780
  }
781
+ function dependencyOverviewIssues(overviews, pluginSummaries, status) {
782
+ const summaryById = new Map(pluginSummaries.map((summary) => [summary.pluginId, summary]));
783
+ return overviews.flatMap((overview) => overview.blockers.flatMap((blocker) => {
784
+ if (blocker.code !== 'dependencyGraphInvalid'
785
+ && blocker.code !== 'dependencyTargetUnsupported')
786
+ return [];
787
+ if (status.targets.some((target) => target.pluginId === overview.pluginId
788
+ && target.errors.some((error) => error.code === blocker.code)))
789
+ return [];
790
+ const summary = summaryById.get(overview.pluginId);
791
+ const sourceId = overview.pluginId.slice(0, overview.pluginId.indexOf('/'));
792
+ const category = blocker.code;
793
+ return [{
794
+ issueId: `dependency:${overview.pluginId}:${blocker.code}`,
795
+ category,
796
+ rootCode: category,
797
+ mergeEligible: false,
798
+ title: `${summary?.displayName ?? summary?.pluginName ?? overview.pluginId} · ${blocker.title}`,
799
+ explanation: blocker.explanation,
800
+ impact: [category === 'dependencyGraphInvalid'
801
+ ? '修复依赖关系后才能安全计算安装顺序。'
802
+ : '当前不能在所有声明的平台上安全安装这个插件。'],
803
+ entity: { kind: 'plugin', id: overview.pluginId },
804
+ pluginId: overview.pluginId,
805
+ sourceId,
806
+ actions: [
807
+ { id: 'viewDependencies', label: '查看依赖关系' },
808
+ { id: 'viewTechnical', label: '技术详情' },
809
+ ],
810
+ technicalDetails: [{ code: blocker.code, message: blocker.explanation }],
811
+ }];
812
+ }));
813
+ }
462
814
  function enrichDependencyIssuePlacements(issues, overviews) {
463
815
  const overviewById = new Map(overviews.map((overview) => [overview.pluginId, overview]));
464
816
  return issues.map((issue) => {
@@ -506,46 +858,39 @@ function enrichDependencyIssuePlacements(issues, overviews) {
506
858
  }
507
859
  function issueDiagnostic(issue, sourceDetails) {
508
860
  const source = issue.sourceId ? sourceDetails.get(issue.sourceId) : undefined;
509
- const sourceDegraded = issue.category === 'sourceDegraded';
510
861
  const sourceUnavailable = issue.category === 'sourceUnavailable' && issue.entity.kind === 'source';
511
862
  const configUnavailable = issue.category === 'diagnostic' && issue.issueId.startsWith('config:');
512
863
  const configInvalid = issue.category === 'requiredConfigMissing';
513
864
  const dependency = issue.category.startsWith('dependency');
514
865
  const platformState = issue.issueId.startsWith('status:') || issue.rootCode === 'targetDiagnostic';
515
- const preserved = sourceDegraded || sourceUnavailable
866
+ const preserved = sourceUnavailable
516
867
  ? '已安装插件和已保存设置不会被删除。'
517
868
  : configUnavailable
518
869
  ? '已保存的设置保持不变。'
519
870
  : '现有安装和已保存设置不会因本页自动改变。';
520
- const impact = sourceDegraded
871
+ const impact = sourceUnavailable
521
872
  ? [
522
- { state: 'degraded', message: '远端数据可能不是最新;匹配的已验证缓存仍可浏览和安装。' },
873
+ { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
523
874
  { state: 'preserved', message: preserved },
524
875
  ]
525
- : sourceUnavailable
526
- ? [
527
- { state: 'blocked', message: '没有可用缓存;当前不能浏览或安装这个来源中的插件。' },
528
- { state: 'preserved', message: preserved },
529
- ]
530
- : [
531
- ...issue.impact.map((message) => ({
532
- state: dependency || configUnavailable || configInvalid || platformState ? 'blocked' : 'degraded',
533
- message,
534
- })),
535
- { state: 'preserved', message: preserved },
536
- ];
876
+ : [
877
+ ...issue.impact.map((message) => ({
878
+ state: dependency || configUnavailable || configInvalid || platformState ? 'blocked' : 'degraded',
879
+ message,
880
+ })),
881
+ { state: 'preserved', message: preserved },
882
+ ];
537
883
  const primaryAction = issue.actions.find((action) => action.id !== 'viewTechnical');
538
884
  const nextStep = primaryAction
539
885
  ? `${primaryAction.label},然后重新检查当前状态。`
540
886
  : '打开技术详情,记录安全错误代码后重新检查。';
541
- const sourceDiagnostic = sourceDegraded || sourceUnavailable;
887
+ const sourceDiagnostic = sourceUnavailable;
542
888
  const rawCode = sourceDiagnostic
543
889
  ? source?.errorCode ?? issue.technicalDetails[0]?.code
544
890
  : issue.technicalDetails[0]?.code;
545
- const code = safeDiagnosticCode(rawCode, sourceDegraded
546
- ? 'catalogSourceDegraded'
547
- : sourceUnavailable ? 'catalogSourceUnavailable' : 'diagnosticUnavailable');
548
- const stage = sourceDegraded || sourceUnavailable ? 'resolve-source'
891
+ const code = safeDiagnosticCode(rawCode, sourceUnavailable
892
+ ? 'catalogSourceUnavailable' : 'diagnosticUnavailable');
893
+ const stage = sourceUnavailable ? 'resolve-source'
549
894
  : configUnavailable ? 'read-config'
550
895
  : configInvalid ? 'validate-config'
551
896
  : dependency ? 'resolve-dependencies'
@@ -557,9 +902,6 @@ function issueDiagnostic(issue, sourceDetails) {
557
902
  : []),
558
903
  ...(issue.platform ? [{ key: 'platform', value: issue.platform }] : []),
559
904
  ...(issue.scope ? [{ key: 'scope', value: issue.scope }] : []),
560
- ...(sourceDiagnostic && source?.freshness
561
- ? [{ key: 'freshness', value: source.freshness }]
562
- : []),
563
905
  ...(issue.requiredFields?.slice(0, 1).map((field) => ({
564
906
  key: 'field',
565
907
  value: field.title,
@@ -573,7 +915,7 @@ function issueDiagnostic(issue, sourceDetails) {
573
915
  '按技术详情使用受支持命令或人工处理。',
574
916
  '再次运行状态检查。',
575
917
  ]
576
- : sourceDegraded || sourceUnavailable
918
+ : sourceUnavailable
577
919
  ? ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。']
578
920
  : dependency
579
921
  ? ['检查依赖关系和依赖插件状态。', '完成恢复动作后重新检查根插件。']
@@ -583,7 +925,7 @@ function issueDiagnostic(issue, sourceDetails) {
583
925
  ? ['完成或修正列出的设置字段。', '重新检查插件设置。']
584
926
  : ['检查目标平台配置和访问权限。', '完成恢复动作后重新运行状态检查。'];
585
927
  return {
586
- severity: sourceDegraded ? 'warning' : 'error',
928
+ severity: 'error',
587
929
  title: issue.title,
588
930
  userMessage: issue.explanation,
589
931
  impact,
@@ -594,14 +936,110 @@ function issueDiagnostic(issue, sourceDetails) {
594
936
  ...(sourceDiagnostic && issue.sourceId ? { sourceId: issue.sourceId } : {}),
595
937
  ...(sourceDiagnostic && source?.requestedRef ? { requestedRef: source.requestedRef } : {}),
596
938
  ...(sourceDiagnostic && source?.resolvedCommit ? { resolvedCommit: source.resolvedCommit } : {}),
597
- ...(sourceDegraded || sourceUnavailable
598
- ? { lastGood: sourceDegraded ? 'available' : 'unavailable' }
599
- : {}),
600
939
  ...(context.length > 0 ? { context } : {}),
601
940
  checks,
602
941
  },
603
942
  };
604
943
  }
944
+ const issueGroupOrder = {
945
+ sourceRemoved: 0,
946
+ artifactCollision: 1,
947
+ sourceUnavailable: 2,
948
+ dependency: 3,
949
+ scope: 4,
950
+ config: 5,
951
+ installation: 6,
952
+ diagnostic: 7,
953
+ };
954
+ function issueGroup(issue) {
955
+ if (issue.category === 'sourceMissing')
956
+ return 'sourceRemoved';
957
+ if (issue.category === 'artifactCollision')
958
+ return 'artifactCollision';
959
+ if (issue.category === 'sourceUnavailable'
960
+ || issue.category === 'sourceIdentityChanged')
961
+ return 'sourceUnavailable';
962
+ if (issue.category.startsWith('dependency'))
963
+ return 'dependency';
964
+ if (issue.category === 'scopeConflict')
965
+ return 'scope';
966
+ if (issue.category === 'requiredConfigMissing')
967
+ return 'config';
968
+ if (issue.category === 'incompleteInstallation')
969
+ return 'installation';
970
+ return 'diagnostic';
971
+ }
972
+ function primaryAttentionCode(issue) {
973
+ if (issue.category === 'requiredConfigMissing') {
974
+ return issue.requiredFields?.some((field) => field.type === 'invalid-config')
975
+ ? 'invalidConfig'
976
+ : 'requiredConfigMissing';
977
+ }
978
+ if (issue.category === 'incompleteInstallation')
979
+ return 'incompleteInstallation';
980
+ if (issue.category === 'sourceIdentityChanged')
981
+ return 'sourceUnavailable';
982
+ return issue.rootCode ?? issue.category;
983
+ }
984
+ function issueReasonPriority(issue) {
985
+ const code = primaryAttentionCode(issue);
986
+ return {
987
+ sourceMissing: 0,
988
+ artifactCollision: 1,
989
+ sourceUnavailable: 2,
990
+ catalogSourceUnavailable: 2,
991
+ bundleUnavailable: 2,
992
+ dependencyGraphInvalid: 3,
993
+ dependencyTargetUnsupported: 4,
994
+ dependencyNotReady: 5,
995
+ dependencyMissing: 5,
996
+ scopeConflict: 6,
997
+ requiredConfigMissing: 7,
998
+ invalidConfig: 7,
999
+ incompleteInstallation: 8,
1000
+ diagnostic: 9,
1001
+ }[code] ?? 9;
1002
+ }
1003
+ function collapseRootIssues(issues) {
1004
+ const byId = new Map();
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 ? {
1025
+ ...primary,
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)
1039
+ .map((detail) => [`${detail.code}\0${detail.message}`, detail])).values()],
1040
+ };
1041
+ });
1042
+ }
605
1043
  export function buildTuiViewModel(options) {
606
1044
  const dependencyById = new Map((options.dependencyOverviews ?? [])
607
1045
  .map((overview) => [overview.pluginId, overview]));
@@ -622,9 +1060,11 @@ export function buildTuiViewModel(options) {
622
1060
  : [];
623
1061
  });
624
1062
  const attention = placements.some((placement) => placement.state === 'scopeConflict'
625
- || (placement.state === 'installed' && placement.health !== 'ready'));
626
- const installed = placements.some((placement) => placement.state !== 'notInstalled');
627
- const unavailable = options.sourceHealth?.get(sourceId)?.state === 'unavailable';
1063
+ || (placement.state === 'installed'
1064
+ && (placement.health !== 'ready' || placement.blockingReason !== undefined)));
1065
+ const installed = placements.some((placement) => placement.state === 'installed' || placement.state === 'scopeConflict');
1066
+ const unavailable = options.sourceHealth?.get(sourceId)?.state === 'unavailable'
1067
+ || (summary?.missingCapabilities?.length ?? 0) > 0;
628
1068
  return {
629
1069
  pluginId,
630
1070
  sourceId,
@@ -639,9 +1079,16 @@ export function buildTuiViewModel(options) {
639
1079
  supportedPlatforms: summary?.supportedTargets
640
1080
  ? ['codex', 'claude'].filter((platform) => summary.supportedTargets[platform].length > 0)
641
1081
  : placements.map((placement) => placement.platform),
1082
+ ...(summary?.supportedTargets ? { supportedScopes: {
1083
+ codex: [...summary.supportedTargets.codex],
1084
+ claude: [...summary.supportedTargets.claude],
1085
+ } } : {}),
642
1086
  installedPlacements: placements,
643
1087
  aggregateStatus: unavailable ? 'unavailable' : attention ? 'needsAttention' : installed ? 'installed' : 'available',
644
1088
  portableSkills: options.portablePluginIds?.includes(pluginId) ?? false,
1089
+ ...(summary?.missingCapabilities
1090
+ ? { missingCapabilities: summary.missingCapabilities }
1091
+ : {}),
645
1092
  ...(dependencyById.get(pluginId)
646
1093
  ? { dependencySummary: dependencyById.get(pluginId).summary }
647
1094
  : summary && (summary.dependencyTotalCount ?? 0) > 0
@@ -672,7 +1119,7 @@ export function buildTuiViewModel(options) {
672
1119
  state: sourceState(source, options.sourceHealth?.get(source.id)),
673
1120
  }));
674
1121
  const sourceRootIds = new Set([...(options.sourceHealth?.values() ?? [])]
675
- .filter((health) => health.state === 'degraded' || health.state === 'unavailable')
1122
+ .filter((health) => health.state === 'unavailable')
676
1123
  .map((health) => health.sourceId));
677
1124
  const allTargetIssues = targetIssues(options.status);
678
1125
  const targetImpactCounts = new Map();
@@ -687,27 +1134,51 @@ export function buildTuiViewModel(options) {
687
1134
  || !issue.sourceId || !sourceRootIds.has(issue.sourceId)),
688
1135
  ...statusIssues(options.status),
689
1136
  ...configIssues(options.pluginSummaries),
1137
+ ...dependencyOverviewIssues(options.dependencyOverviews ?? [], options.pluginSummaries, options.status),
690
1138
  ...[...(options.sourceHealth?.values() ?? [])].flatMap((health) => sourceIssue(health, targetImpactCounts.get(health.sourceId) ?? 0) ?? []),
691
1139
  ], options.dependencyOverviews ?? []);
692
- const issues = issueDrafts.map((issue) => ({
1140
+ const stableIssueDrafts = collapseRootIssues(issueDrafts).sort((left, right) => issueGroupOrder[issueGroup(left)] - issueGroupOrder[issueGroup(right)]
1141
+ || issueReasonPriority(left) - issueReasonPriority(right)
1142
+ || compareText(primaryAttentionCode(left), primaryAttentionCode(right))
1143
+ || compareText(left.issueId, right.issueId));
1144
+ const issues = stableIssueDrafts.map((issue) => ({
693
1145
  ...issue,
1146
+ group: issueGroup(issue),
694
1147
  diagnostic: issueDiagnostic(issue, sourceDetails),
695
1148
  }));
1149
+ const pluginsWithAttention = plugins.map((plugin) => {
1150
+ const primary = stableIssueDrafts.filter((issue) => issue.pluginId === plugin.pluginId
1151
+ || issue.dependencyPluginId === plugin.pluginId
1152
+ || issue.affectedTargets?.some((target) => target.pluginId === plugin.pluginId)
1153
+ || issue.entity.kind === 'source' && issue.sourceId === plugin.sourceId)
1154
+ .sort((left, right) => issueReasonPriority(left) - issueReasonPriority(right)
1155
+ || compareText(primaryAttentionCode(left), primaryAttentionCode(right))
1156
+ || compareText(left.issueId, right.issueId))[0];
1157
+ if (!primary)
1158
+ return plugin;
1159
+ return {
1160
+ ...plugin,
1161
+ primaryAttention: {
1162
+ severity: 'error',
1163
+ code: primaryAttentionCode(primary),
1164
+ message: primary.explanation,
1165
+ },
1166
+ };
1167
+ });
696
1168
  return {
697
- plugins,
1169
+ plugins: pluginsWithAttention,
698
1170
  sourceTabs,
699
1171
  sourceDetails: (options.sourceDetails ?? []).map((detail) => ({
700
1172
  ...detail,
701
- pluginCount: plugins.filter((plugin) => plugin.sourceId === detail.id).length,
1173
+ pluginCount: pluginsWithAttention.filter((plugin) => plugin.sourceId === detail.id).length,
702
1174
  })),
703
1175
  issues,
704
1176
  dependencyOverviews: options.dependencyOverviews ?? [],
705
1177
  home: {
706
- availablePlugins: plugins.filter((plugin) => plugin.aggregateStatus === 'available').length,
707
- installedPlugins: plugins.filter(hasInstalledPlacement).length,
1178
+ availablePlugins: pluginsWithAttention.filter((plugin) => plugin.aggregateStatus === 'available').length,
1179
+ installedPlugins: pluginsWithAttention.filter(hasInstalledPlacement).length,
708
1180
  issueCount: issues.length,
709
1181
  sourceCount: sourceTabs.length,
710
- cachedSourceCount: sourceTabs.filter((source) => source.state === 'cache').length,
711
1182
  unavailableSourceCount: sourceTabs.filter((source) => source.state === 'error').length,
712
1183
  },
713
1184
  };
@@ -737,6 +1208,7 @@ export function buildConfigGroups(options) {
737
1208
  const entryByKey = new Map(options.read.fields.map((field) => [field.key, field]));
738
1209
  const effectiveEntryByKey = new Map((options.effectiveRead ?? options.read).fields.map((field) => [field.key, field]));
739
1210
  const effectivePage = options.read.scope === 'effective';
1211
+ const layerEntry = (layer, key) => options.layerReads?.[layer].fields.find((field) => field.key === key);
740
1212
  const fieldModel = (key) => {
741
1213
  const schema = options.schema.schema[key];
742
1214
  const entry = entryByKey.get(key);
@@ -762,6 +1234,16 @@ export function buildConfigGroups(options) {
762
1234
  && effectiveEntry.effectiveValue !== undefined
763
1235
  && effectiveEntry.effectiveValue !== null
764
1236
  && (effectiveEntry.valueState === 'configured' || effectiveEntry.valueState === 'default');
1237
+ const omittedLayer = options.read.scope === 'user'
1238
+ ? 'user'
1239
+ : options.read.projectLayer === 'local' ? 'local' : 'project';
1240
+ const fallback = ['local', 'project', 'user']
1241
+ .filter((layer) => layer !== omittedLayer)
1242
+ .map((layer) => ({ layer, entry: layerEntry(layer, key) }))
1243
+ .find((candidate) => candidate.entry?.storedSourceLayer !== undefined);
1244
+ const fallbackValue = fallback?.entry?.storedValue ?? schema.default;
1245
+ const fallbackConfigured = fallback !== undefined || schema.default !== undefined;
1246
+ const fallbackSource = fallback?.layer ?? (schema.default !== undefined ? 'default' : undefined);
765
1247
  return {
766
1248
  key,
767
1249
  title: schema.title ?? key,
@@ -788,6 +1270,15 @@ export function buildConfigGroups(options) {
788
1270
  value: effectiveEntry?.effectiveValue,
789
1271
  }),
790
1272
  effectiveSourceLabel: configSourceLabel(effectiveEntry?.sourceLayer ?? null),
1273
+ ...(!effectivePage && configured ? {
1274
+ fallbackDisplayValue: displayConfigValue({
1275
+ privateValue,
1276
+ inherited: false,
1277
+ configured: fallbackConfigured,
1278
+ value: fallbackValue,
1279
+ }),
1280
+ fallbackSourceLabel: fallbackSource ? configSourceLabel(fallbackSource) : '未设置',
1281
+ } : {}),
791
1282
  ...(!privateValue && selectedValue !== undefined
792
1283
  ? { value: selectedValue }
793
1284
  : {}),