@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
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import path from 'node:path';
5
+ import { CATALOG_BETA_REF, CATALOG_LATEST_REF, OFFICIAL_CATALOG_SOURCE_SSH_URL, OFFICIAL_CATALOG_SOURCE_URL, } from '../constants.js';
5
6
  import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
6
7
  import { canonicalizeCatalogGitUrl } from '../utils/catalog-source-identity.js';
7
8
  import { normalizePortableAgents } from '../portable/commands.js';
@@ -10,6 +11,8 @@ import { ActionRow, Callout, ChoiceRow, DiagnosticSummary, InputCard, PluginList
10
11
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
11
12
  import { extractPortableError, userIssueKey } from './controller.js';
12
13
  import { resolveTuiSizePolicy } from './layout.js';
14
+ import { InputDisplay, removeLastGrapheme } from './input-display.js';
15
+ import { catalogRefPolicyForPreset } from './source-ref-presets.js';
13
16
  import { buildPlatformActions, buildPluginDetailActions, buildPluginPreviewActions, hasInstalledPlacement, } from './model.js';
14
17
  import { tuiTheme } from './theme.js';
15
18
  import { contentRowBudget, rowViewport } from './viewport.js';
@@ -26,11 +29,16 @@ export const CONSOLE_V4_SCREEN_IDS = [
26
29
  'D01', 'D02', 'D03', 'D04', 'D05', 'D06', 'D07', 'D08', 'D09',
27
30
  'D10', 'D11', 'D12', 'D13', 'D14', 'D15', 'D16', 'D17',
28
31
  'U01', 'U02', 'U03', 'U04', 'U05', 'U06', 'U07', 'U08', 'U09', 'U10', 'U11',
32
+ 'K01', 'K02', 'K03', 'K04', 'K05', 'K06', 'K07',
29
33
  ];
30
34
  function pluginMenuActionId(action) {
31
35
  return action.id === 'platform' || action.id === 'scopeConflict'
32
36
  ? `${action.id}:${action.platform}`
33
- : action.id;
37
+ : action.id === 'addScope'
38
+ ? `${action.id}:${action.platform}:${action.scope}`
39
+ : action.id === 'resolveScopeConflict'
40
+ ? `${action.id}:${action.pluginId}:${action.platform}:${action.scope}`
41
+ : action.id;
34
42
  }
35
43
  function isCharacterInputMode(route, searchMode) {
36
44
  return searchMode
@@ -118,13 +126,36 @@ function pluginPlacementSummary(plugin) {
118
126
  const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
119
127
  if (!placement || placement.state === 'notInstalled')
120
128
  return `${label} · 未添加`;
129
+ if (placement.state === 'blocked')
130
+ return `${label} · 无法添加 · 输出冲突`;
121
131
  if (placement.state === 'scopeConflict')
122
132
  return `${label} · 位置冲突`;
123
- const health = placement.health === 'ready' ? '可用'
124
- : placement.health === 'incomplete' ? '需要处理' : '不可用';
125
- return `${label} · ${scopeLabel(placement.scope)} · ${health}`;
133
+ if (placement.blockingReason === 'artifactCollision') {
134
+ return `${label} · ${scopeLabel(placement.scope)} · 输出冲突`;
135
+ }
136
+ const health = placement.actualState === 'needsSync' ? '需要同步'
137
+ : placement.actualState === 'drifted' ? '已漂移'
138
+ : placement.actualState === 'conflict' ? '位置冲突'
139
+ : placement.health === 'ready' ? '可用'
140
+ : placement.health === 'incomplete' ? '需要处理' : '不可用';
141
+ const desired = placement.desiredScopes?.length
142
+ ? placement.desiredScopes.map(scopeLabel).join('+')
143
+ : scopeLabel(placement.scope);
144
+ return `${label} · ${desired} · ${health}`;
126
145
  }).join(' | ') || '没有可用平台';
127
146
  }
147
+ function issueGroupLabel(group) {
148
+ return {
149
+ sourceRemoved: '来源已移除',
150
+ artifactCollision: '输出冲突',
151
+ sourceUnavailable: '来源不可用',
152
+ dependency: '依赖',
153
+ scope: '位置',
154
+ config: '设置',
155
+ installation: '安装',
156
+ diagnostic: '诊断',
157
+ }[group];
158
+ }
128
159
  function pluginRowStatus(plugin) {
129
160
  if (plugin.aggregateStatus === 'needsAttention')
130
161
  return 'warning';
@@ -305,38 +336,38 @@ function configUnavailableDiagnostic(route) {
305
336
  },
306
337
  };
307
338
  }
308
- function sourceDiagnostic(sourceId, state, detail) {
309
- const degraded = state === 'degraded';
339
+ function sourceDiagnostic(sourceId, state, detail, generation) {
310
340
  const unavailable = state === 'unavailable' || state === 'error';
311
- if (!degraded && !unavailable)
341
+ if (!unavailable)
312
342
  return undefined;
343
+ const metadataInvalid = generation?.errorCode === 'catalogGenerationMetadataInvalid';
313
344
  return {
314
- severity: degraded ? 'warning' : 'error',
315
- title: degraded ? '远端 Catalog 检查失败,正在使用缓存' : 'Catalog 来源不可用',
316
- userMessage: degraded
317
- ? '远端读取没有成功。当前使用匹配且已验证的缓存数据。'
318
- : '当前无法读取这个来源,并且没有可用缓存数据。',
319
- impact: degraded
320
- ? [
321
- { state: 'degraded', message: '数据可能不是最新;匹配缓存中的插件仍可浏览和安装。' },
322
- { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
323
- ]
324
- : [
325
- { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
326
- { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
327
- ],
328
- nextStep: degraded
329
- ? '重新检查来源;在修复前也可以继续使用匹配缓存。'
345
+ severity: 'error',
346
+ title: 'Catalog 来源不可用',
347
+ userMessage: '当前无法读取这个来源。',
348
+ impact: [
349
+ { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
350
+ { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
351
+ ],
352
+ nextStep: metadataInvalid && generation.recoveryEntryPath
353
+ ? `先安全删除 AW_HOME 下的精确条目 ${generation.recoveryEntryPath},再运行 ${generation.syncInstruction}。`
330
354
  : '重新检查来源;如仍失败,请编辑来源 URL、路径或 ref。',
331
355
  technical: {
332
- code: safeDiagnosticCode(detail?.errorCode, degraded ? 'catalogSourceDegraded' : 'catalogSourceUnavailable'),
356
+ code: safeDiagnosticCode(generation?.errorCode ?? detail?.errorCode, 'catalogSourceUnavailable'),
333
357
  stage: 'resolve-source',
334
358
  sourceId,
335
359
  ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
336
360
  ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
337
- lastGood: degraded ? 'available' : 'unavailable',
338
- ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
339
- checks: ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。'],
361
+ ...(metadataInvalid && generation.recoveryEntryPath
362
+ ? { context: [{ key: 'storeEntry', value: generation.recoveryEntryPath }] }
363
+ : {}),
364
+ checks: metadataInvalid && generation.recoveryEntryPath
365
+ ? [
366
+ `确认损坏条目为 AW_HOME/${generation.recoveryEntryPath}。`,
367
+ '只删除该精确条目,不删除 Catalog store 根或其它 digest。',
368
+ `运行 ${generation.syncInstruction} 重建 Active。`,
369
+ ]
370
+ : ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。'],
340
371
  },
341
372
  };
342
373
  }
@@ -407,7 +438,7 @@ function SourceTabs(props) {
407
438
  const state = 'state' in tab ? tab.state : 'ready';
408
439
  return (_jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent
409
440
  : state === 'error' ? tuiTheme.statusColors.error
410
- : state === 'cache' ? tuiTheme.statusColors.warning : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, state === 'error' ? ' !' : ` ${tab.count ?? ''}`, active ? ']' : '', ' '] }, tab.id));
441
+ : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, state === 'error' ? ' !' : ` ${tab.count ?? ''}`, active ? ']' : '', ' '] }, tab.id));
411
442
  })] }));
412
443
  }
413
444
  function configTabs(page) {
@@ -439,13 +470,22 @@ function placementStatus(placement) {
439
470
  if (placement.state === 'notInstalled') {
440
471
  return { value: '未添加', status: 'muted', description: '选择使用位置后添加' };
441
472
  }
473
+ if (placement.state === 'blocked') {
474
+ return { value: '无法添加', status: 'error', description: '输出冲突;不会覆盖或删除占用内容' };
475
+ }
442
476
  if (placement.state === 'scopeConflict') {
443
477
  return { value: '位置冲突', status: 'warning', description: '需要先处理两个安装位置' };
444
478
  }
479
+ if (placement.blockingReason === 'artifactCollision') {
480
+ return { value: '输出冲突', status: 'error', description: '只允许查看或安全移除受管内容' };
481
+ }
445
482
  return {
446
- value: `${scopeLabel(placement.scope)} · ${placement.health === 'ready' ? '可用' : placement.health === 'incomplete' ? '需要处理' : '不可用'}`,
447
- status: placement.health === 'ready' ? 'success' : placement.health === 'incomplete' ? 'warning' : 'error',
448
- description: placement.health === 'ready' ? '当前检查正常' : '查看影响和建议操作',
483
+ value: `${placement.desiredScopes?.map(scopeLabel).join('+') || scopeLabel(placement.scope)} · ${placement.actualState === 'needsSync' ? '需要同步' : placement.actualState === 'drifted' ? '已漂移' : placement.actualState === 'conflict' ? '位置冲突' : placement.health === 'ready' ? '可用' : placement.health === 'incomplete' ? '需要处理' : '不可用'}`,
484
+ status: placement.actualState === 'conflict' ? 'error' : placement.health === 'ready' ? 'success' : placement.health === 'incomplete' ? 'warning' : 'error',
485
+ description: placement.actualState === 'needsSync' ? 'desired intent 与现场不同;可安全同步'
486
+ : placement.actualState === 'drifted' ? '现场已漂移;查看同步计划'
487
+ : placement.actualState === 'conflict' ? 'mixed 或 AW-only 重叠;查看恢复路径'
488
+ : placement.health === 'ready' ? '当前检查正常' : '查看影响和建议操作',
449
489
  };
450
490
  }
451
491
  function dependencyStatus(item) {
@@ -459,9 +499,29 @@ function dependencyStatus(item) {
459
499
  }
460
500
  function dependencyPlacementLabel(item) {
461
501
  const aggregate = dependencyStatus(item);
502
+ const installed = item.placements.filter((entry) => entry.state === 'installed');
503
+ const modern = installed.filter((entry) => entry.state === 'installed'
504
+ && (entry.desiredScopes !== undefined || entry.actualState !== undefined));
505
+ if (modern.length > 0) {
506
+ return modern.map((entry) => {
507
+ if (entry.state !== 'installed')
508
+ return '';
509
+ const desired = entry.desiredScopes?.map(scopeLabel).join('+') || scopeLabel(entry.scope);
510
+ const actualScopes = [
511
+ ...(entry.activation?.user ? ['所有项目'] : []),
512
+ ...(entry.activation?.project ? ['当前项目'] : []),
513
+ ];
514
+ const actual = actualScopes.join('+') || scopeLabel(entry.scope);
515
+ const state = entry.actualState === 'needsSync' ? '需同步'
516
+ : entry.actualState === 'drifted' ? '已漂移'
517
+ : entry.actualState === 'conflict' ? '冲突'
518
+ : entry.actualState === 'ready' ? '可用' : aggregate.value;
519
+ return `${platformLabel(entry.platform)} · desired ${desired} · actual ${actual} · ${state}`;
520
+ }).join(' | ');
521
+ }
462
522
  if (item.aggregateStatus !== 'ready')
463
523
  return aggregate.value;
464
- const ready = item.placements.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
524
+ const ready = installed.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
465
525
  const scopes = new Set(ready.map((entry) => entry.state === 'installed' ? entry.scope : undefined));
466
526
  return scopes.size === 1 && ready[0]?.state === 'installed'
467
527
  ? `${scopeLabel(ready[0].scope)} · ${aggregate.value}`
@@ -551,6 +611,10 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
551
611
  return 'D16';
552
612
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D04' : 'P06';
553
613
  }
614
+ if (route.name === 'dependencyPlacement')
615
+ return 'D03';
616
+ if (route.name === 'dependencyPlacementEditor')
617
+ return 'D04';
554
618
  if (route.name === 'pluginDetail') {
555
619
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D09' : 'P13';
556
620
  }
@@ -567,8 +631,11 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
567
631
  }
568
632
  if (route.name === 'reviewPreparing')
569
633
  return 'P07';
634
+ if (route.name === 'review' && snapshot.activeReview?.originIssue)
635
+ return 'I10';
570
636
  const dependencyOperation = snapshot.activeReview?.operationKind === 'pluginWrite'
571
- && Boolean(snapshot.activeReview.changes.some((change) => change.group));
637
+ && Boolean(snapshot.activeReview.changes.some((change) => change.relation === 'direct' || change.relation === 'transitive'
638
+ || change.group === 'dependencies'));
572
639
  if (route.name === 'review' && dependencyOperation) {
573
640
  return snapshot.activeReview?.pluginAction === 'uninstall' || snapshot.activeReview?.pluginAction === 'cleanup'
574
641
  ? 'D11' : 'D05';
@@ -584,8 +651,6 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
584
651
  return snapshot.result.state === 'succeeded' ? 'D07' : 'D08';
585
652
  }
586
653
  }
587
- if (route.name === 'review' && snapshot.activeReview?.originIssue)
588
- return 'I10';
589
654
  if (route.name === 'progress' && snapshot.activeReview?.originIssue)
590
655
  return 'I11';
591
656
  if (route.name === 'result' && snapshot.result?.originIssue) {
@@ -630,6 +695,8 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
630
695
  return 'C01';
631
696
  if (route.name === 'configFields')
632
697
  return route.layer.scope === 'effective' ? 'C02' : 'C03';
698
+ if (route.name === 'configFieldAction')
699
+ return 'C04';
633
700
  if (route.name === 'configFieldEditor')
634
701
  return {
635
702
  enum: 'C05', boolean: 'C06', number: 'C07', string: 'C08', path: 'C09', sensitive: 'C10',
@@ -639,7 +706,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
639
706
  if (route.name === 'sourceList')
640
707
  return 'S01';
641
708
  if (route.name === 'sourceDetail')
642
- return 'S02';
709
+ return 'K02';
643
710
  if (route.name === 'sourceTechnical')
644
711
  return 'S03';
645
712
  if (route.name === 'sourceAdd')
@@ -658,7 +725,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
658
725
  const category = controller.listUserIssues().find((issue) => issue.issueId === route.issueId)?.category;
659
726
  return {
660
727
  incompleteInstallation: 'I03',
661
- sourceDegraded: 'I04',
728
+ artifactCollision: 'I09',
662
729
  sourceUnavailable: 'I04',
663
730
  sourceMissing: 'I05',
664
731
  sourceIdentityChanged: 'I06',
@@ -685,7 +752,7 @@ export function ConsoleAppV4(props) {
685
752
  const [discardForExit, setDiscardForExit] = useState(false);
686
753
  const [focus, setFocus] = useState(0);
687
754
  const [scrollOffset, setScrollOffset] = useState(0);
688
- const [searchMode, setSearchMode] = useState(false);
755
+ const [searchFocus, setSearchFocus] = useState(false);
689
756
  const [query, setQuery] = useState('');
690
757
  const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
691
758
  const [scope, setScope] = useState('user');
@@ -699,7 +766,6 @@ export function ConsoleAppV4(props) {
699
766
  const [dirty, setDirty] = useState(false);
700
767
  const [configPage, setConfigPage] = useState();
701
768
  const [configLayerCounts, setConfigLayerCounts] = useState({});
702
- const [configLoadKey, setConfigLoadKey] = useState(0);
703
769
  const [draftUpdates, setDraftUpdates] = useState({});
704
770
  const [draftRemoves, setDraftRemoves] = useState([]);
705
771
  const [inputValue, setInputValue] = useState('');
@@ -714,6 +780,8 @@ export function ConsoleAppV4(props) {
714
780
  const [portableAgentInput, setPortableAgentInput] = useState('');
715
781
  const [draftOwner, setDraftOwner] = useState();
716
782
  const [pendingPresetId, setPendingPresetId] = useState();
783
+ const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
784
+ const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
717
785
  const dynamicMenuFocus = useRef(undefined);
718
786
  const exitSent = useRef(false);
719
787
  const operationBusy = useRef(false);
@@ -804,6 +872,7 @@ export function ConsoleAppV4(props) {
804
872
  }, [route.name]);
805
873
  const configRoute = route.name === 'configFields'
806
874
  || route.name === 'configFieldEditor'
875
+ || route.name === 'configFieldAction'
807
876
  ? route
808
877
  : undefined;
809
878
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
@@ -867,7 +936,7 @@ export function ConsoleAppV4(props) {
867
936
  }
868
937
  });
869
938
  return () => abort.abort();
870
- }, [configLoadKey, configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
939
+ }, [configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
871
940
  useEffect(() => {
872
941
  if (route.name !== 'configFields' || !currentConfigPage)
873
942
  return;
@@ -910,7 +979,7 @@ export function ConsoleAppV4(props) {
910
979
  route.name === 'pluginList' ? route.sourceId : '',
911
980
  snapshot.viewModel,
912
981
  ]);
913
- const activeField = route.name === 'configFieldEditor'
982
+ const activeField = route.name === 'configFieldEditor' || route.name === 'configFieldAction'
914
983
  ? currentConfigPage?.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
915
984
  : undefined;
916
985
  useEffect(() => {
@@ -945,7 +1014,7 @@ export function ConsoleAppV4(props) {
945
1014
  setSourceReferenceSummary({ blocked: true, inventoryComplete: false, dependencies: [] }); });
946
1015
  return () => abort.abort();
947
1016
  }, [props.controller, route.name, route.name === 'sourceRemoveBlocked' ? route.sourceId : '']);
948
- const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchMode);
1017
+ const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchFocus);
949
1018
  const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
950
1019
  ? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
951
1020
  : [];
@@ -962,19 +1031,12 @@ export function ConsoleAppV4(props) {
962
1031
  || Boolean(route.name === 'result' && snapshot.result && 'operationKind' in snapshot.result
963
1032
  && snapshot.result.operationKind === 'portableInstall');
964
1033
  const canOpenHelp = !tooSmall
965
- && !isCharacterInputMode(route, searchMode)
1034
+ && !isCharacterInputMode(route, searchFocus)
966
1035
  && !(route.name === 'installTargets' && route.view !== 'blocked');
967
1036
  useEffect(() => {
968
1037
  props.onScreenChange?.(screenId);
969
1038
  }, [props.onScreenChange, screenId]);
970
1039
  const dependencyOverview = (candidate) => candidate ? props.controller.getPluginDependencyOverview(candidate.pluginId) : undefined;
971
- const installIntent = (candidate, targets = selectedTargets) => ({
972
- kind: 'pluginWrite',
973
- action: 'install',
974
- pluginId: candidate.pluginId,
975
- platform: targets.length === 2 ? 'both' : targets[0],
976
- scope,
977
- });
978
1040
  let title = '管理你的 Agent 工作流插件';
979
1041
  let description = '为 Codex 和 Claude Code 添加、设置或移除开发能力。';
980
1042
  let breadcrumb = '首页';
@@ -1012,6 +1074,7 @@ export function ConsoleAppV4(props) {
1012
1074
  compact: policy.mode === 'compact',
1013
1075
  staticRows,
1014
1076
  });
1077
+ const dependencyPlacementStaticRows = policy.mode === 'compact' ? 3 : 10;
1015
1078
  const responsiveBudget = (compactRows, standardRows) => {
1016
1079
  const baseline = policy.mode === 'compact' ? 18 : 24;
1017
1080
  const base = policy.mode === 'compact' ? compactRows : standardRows;
@@ -1022,7 +1085,7 @@ export function ConsoleAppV4(props) {
1022
1085
  props.controller.push(next);
1023
1086
  setFocus(0);
1024
1087
  setScrollOffset(0);
1025
- setSearchMode(false);
1088
+ setSearchFocus(false);
1026
1089
  setShowTechnical(false);
1027
1090
  setActionError(undefined);
1028
1091
  };
@@ -1037,7 +1100,7 @@ export function ConsoleAppV4(props) {
1037
1100
  props.controller.beginConfigFlow(pluginId);
1038
1101
  setFocus(0);
1039
1102
  setScrollOffset(0);
1040
- setSearchMode(false);
1103
+ setSearchFocus(false);
1041
1104
  setShowTechnical(false);
1042
1105
  setActionError(undefined);
1043
1106
  };
@@ -1051,7 +1114,7 @@ export function ConsoleAppV4(props) {
1051
1114
  setScrollOffset(restored.scrollOffset ?? 0);
1052
1115
  setQuery(restored.query ?? '');
1053
1116
  setInstallFilter(restored.installFilter ?? 'all');
1054
- setSearchMode(Boolean(restored.query));
1117
+ setSearchFocus(false);
1055
1118
  setActionError(undefined);
1056
1119
  };
1057
1120
  const leaveResult = (target) => {
@@ -1061,7 +1124,7 @@ export function ConsoleAppV4(props) {
1061
1124
  setScrollOffset(restored.scrollOffset ?? 0);
1062
1125
  setQuery(restored.query ?? '');
1063
1126
  setInstallFilter(restored.installFilter ?? 'all');
1064
- setSearchMode(Boolean(restored.query));
1127
+ setSearchFocus(false);
1065
1128
  setShowTechnical(false);
1066
1129
  setActionError(undefined);
1067
1130
  };
@@ -1080,6 +1143,18 @@ export function ConsoleAppV4(props) {
1080
1143
  setFocus(0);
1081
1144
  setScrollOffset(0);
1082
1145
  };
1146
+ const continueInstallAfterTargets = async (candidate, targets) => {
1147
+ if (props.controller.snapshot.installPlacementDraft?.pluginId !== candidate.pluginId) {
1148
+ props.controller.beginInstallFlow(candidate.pluginId, scope);
1149
+ }
1150
+ props.controller.setInstallPlatforms(targets);
1151
+ const hasConfigurableDependencies = await props.controller.prepareInstallDependencyPlacement();
1152
+ if (hasConfigurableDependencies) {
1153
+ go({ name: 'dependencyPlacement', pluginId: candidate.pluginId });
1154
+ return;
1155
+ }
1156
+ beginReview(props.controller.installPlacementIntent());
1157
+ };
1083
1158
  const beginPortable = async (pluginId) => {
1084
1159
  try {
1085
1160
  await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
@@ -1148,6 +1223,24 @@ export function ConsoleAppV4(props) {
1148
1223
  scope: selectedScope,
1149
1224
  });
1150
1225
  };
1226
+ const beginSecondScope = (candidate, platform, selectedScope) => {
1227
+ setScope(selectedScope);
1228
+ setSelectedTargets([]);
1229
+ setTargetSelections({});
1230
+ setPreferredTarget(platform);
1231
+ props.controller.beginInstallFlow(candidate.pluginId, selectedScope);
1232
+ go({ name: 'installPlacement', pluginId: candidate.pluginId });
1233
+ };
1234
+ const beginScopeConflictRecovery = (action) => {
1235
+ void beginReview({
1236
+ kind: 'pluginWrite',
1237
+ action: 'uninstall',
1238
+ pluginId: action.pluginId,
1239
+ platform: action.platform,
1240
+ scope: action.scope,
1241
+ recoveryDescription: action.description,
1242
+ });
1243
+ };
1151
1244
  const beginAddSource = () => {
1152
1245
  const owner = `source-add:${Date.now()}`;
1153
1246
  setSourceDraft({ owner, mode: 'add', sourceId: '', type: 'git', location: '', refPolicy: 'cliChannel', ref: '', subdir: '' });
@@ -1183,6 +1276,9 @@ export function ConsoleAppV4(props) {
1183
1276
  pluginId,
1184
1277
  platform: selectedPlatform,
1185
1278
  scope: selectedScope,
1279
+ ...(selectedIssue.category === 'scopeConflict' && action.description
1280
+ ? { recoveryDescription: action.description }
1281
+ : {}),
1186
1282
  originIssue: {
1187
1283
  issueId: selectedIssue.issueId,
1188
1284
  issueKey: userIssueKey(selectedIssue),
@@ -1314,6 +1410,12 @@ export function ConsoleAppV4(props) {
1314
1410
  return next;
1315
1411
  });
1316
1412
  };
1413
+ const cancelDraftRemoval = (key) => {
1414
+ const nextRemoves = draftRemoves.filter((candidate) => candidate !== key);
1415
+ setDraftRemoves(nextRemoves);
1416
+ setDirty(Object.keys(draftUpdates).length + nextRemoves.length > 0);
1417
+ setConfigActionError(undefined);
1418
+ };
1317
1419
  const configFieldTarget = (page, key) => {
1318
1420
  if (!key)
1319
1421
  return undefined;
@@ -1384,15 +1486,8 @@ export function ConsoleAppV4(props) {
1384
1486
  setScrollOffset(0);
1385
1487
  return;
1386
1488
  }
1387
- if (field && route.fieldKey) {
1388
- props.controller.push({
1389
- name: 'configFieldEditor',
1390
- pluginId: route.pluginId,
1391
- layer,
1392
- fieldKey: route.fieldKey,
1393
- editor: fieldEditor(field),
1394
- });
1395
- }
1489
+ if (field && route.fieldKey)
1490
+ setPendingLayerFieldKey(route.fieldKey);
1396
1491
  setFocus(0);
1397
1492
  setScrollOffset(0);
1398
1493
  };
@@ -1405,7 +1500,9 @@ export function ConsoleAppV4(props) {
1405
1500
  key,
1406
1501
  title: field?.title ?? key,
1407
1502
  before: field?.displayValue ?? '未设置',
1408
- after: draftRemoves.includes(key) ? '未设置' : currentFieldDisplay(field),
1503
+ after: draftRemoves.includes(key)
1504
+ ? `恢复继承(${field?.fallbackDisplayValue ?? '未设置'} · ${field?.fallbackSourceLabel ?? '未设置'})`
1505
+ : currentFieldDisplay(field),
1409
1506
  };
1410
1507
  });
1411
1508
  };
@@ -1431,6 +1528,38 @@ export function ConsoleAppV4(props) {
1431
1528
  setConfigActionError('设置值未通过验证。请重新检查修改。');
1432
1529
  }
1433
1530
  };
1531
+ useEffect(() => {
1532
+ if (!pendingLayerFieldKey || route.name !== 'configFields'
1533
+ || route.layer.scope === 'effective' || !currentConfigPage)
1534
+ return;
1535
+ const field = currentConfigPage.groups.flatMap((group) => group.fields)
1536
+ .find((candidate) => candidate.key === pendingLayerFieldKey);
1537
+ setPendingLayerFieldKey(undefined);
1538
+ if (!field)
1539
+ return;
1540
+ const layer = { scope: route.layer.scope, projectLayer: route.layer.projectLayer };
1541
+ props.controller.push(field.configured ? {
1542
+ name: 'configFieldAction',
1543
+ pluginId: route.pluginId,
1544
+ layer,
1545
+ fieldKey: field.key,
1546
+ } : {
1547
+ name: 'configFieldEditor',
1548
+ pluginId: route.pluginId,
1549
+ layer,
1550
+ fieldKey: field.key,
1551
+ editor: fieldEditor(field),
1552
+ });
1553
+ }, [pendingLayerFieldKey, route.name, currentConfigPage]);
1554
+ useEffect(() => {
1555
+ if (!pendingLayerFieldKey)
1556
+ return;
1557
+ if (route.name === 'configFields' && route.layer.scope !== 'effective')
1558
+ return;
1559
+ if (route.name === 'configLayer')
1560
+ return;
1561
+ setPendingLayerFieldKey(undefined);
1562
+ }, [pendingLayerFieldKey, route.name]);
1434
1563
  useEffect(() => {
1435
1564
  if (!pendingPresetId || route.name !== 'configFields'
1436
1565
  || route.layer.scope === 'effective' || !currentConfigPage)
@@ -1469,7 +1598,7 @@ export function ConsoleAppV4(props) {
1469
1598
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
1470
1599
  { label: '插件来源', description: '管理 Git 仓库和本地目录', meta: String(home.sourceCount), run: () => go({ name: 'sourceList' }) },
1471
1600
  ];
1472
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.cachedSourceCount > 0 ? _jsx(ResultRow, { title: `${home.cachedSourceCount} 个来源使用缓存`, status: "warning" }) : null, home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
1601
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
1473
1602
  }
1474
1603
  else if (route.name === 'pluginList') {
1475
1604
  const sourceId = route.sourceId ?? 'all';
@@ -1479,6 +1608,12 @@ export function ConsoleAppV4(props) {
1479
1608
  ...(query ? { query } : {}),
1480
1609
  installFilter,
1481
1610
  });
1611
+ const uninstalledCount = props.controller.listPlugins({
1612
+ mode: route.mode,
1613
+ ...(sourceId !== 'all' ? { sourceId } : {}),
1614
+ ...(query ? { query } : {}),
1615
+ installFilter: 'uninstalled',
1616
+ }).length;
1482
1617
  breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
1483
1618
  title = breadcrumb;
1484
1619
  description = route.mode === 'browse' ? '选择希望 Agent 获得的能力。' : '查看状态,或选择一个插件进行管理。';
@@ -1486,13 +1621,15 @@ export function ConsoleAppV4(props) {
1486
1621
  help = `↑↓ 移动 · Enter 查看 · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
1487
1622
  const sourceError = sourceId !== 'all' && props.controller.getSourceOverview(sourceId).source?.state === 'error';
1488
1623
  actions = sourceError
1489
- ? [{ label: '查看来源问题', description: '没有可用缓存;当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
1624
+ ? [{ label: '查看来源问题', description: '当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
1490
1625
  : [];
1491
1626
  pluginRows = sourceError ? [] : items.map((item) => ({
1492
1627
  pluginId: item.pluginId,
1493
1628
  name: item.displayName,
1494
1629
  sourceLabel: item.sourceLabel,
1495
- description: item.description,
1630
+ description: item.primaryAttention
1631
+ ? `${item.primaryAttention.code} · ${item.primaryAttention.message}`
1632
+ : item.description,
1496
1633
  placementSummary: pluginPlacementSummary(item),
1497
1634
  statusLabel: statusText(item),
1498
1635
  status: pluginRowStatus(item),
@@ -1505,7 +1642,9 @@ export function ConsoleAppV4(props) {
1505
1642
  else if (route.mode === 'browse' && items.length === 0 && !sourceError) {
1506
1643
  actions = [{
1507
1644
  label: '没有匹配插件',
1508
- description: installFilter === 'uninstalled' ? '当前来源和搜索条件下没有未安装插件' : '请调整来源或搜索条件',
1645
+ description: installFilter === 'uninstalled'
1646
+ ? '当前来源和搜索条件下没有未安装插件;按 f 查看全部,或按 / 编辑搜索'
1647
+ : '按 / 编辑搜索,或切换来源',
1509
1648
  disabled: '没有可打开的插件',
1510
1649
  }];
1511
1650
  }
@@ -1521,10 +1660,12 @@ export function ConsoleAppV4(props) {
1521
1660
  if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
1522
1661
  context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
1523
1662
  }
1524
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchMode ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["/ ", searchMode ? query || '输入搜索内容' : '搜索名称、用途或来源…'] }), route.mode === 'browse'
1525
- ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["[", installFilter === 'uninstalled' ? '未安装' : '全部', "]"] }), " f"] })
1663
+ content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
1664
+ ? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
1665
+ : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), route.mode === 'browse'
1666
+ ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'uninstalled' ? '[未安装]' : '未安装', " ", uninstalledCount] }), " f \u5207\u6362"] })
1526
1667
  : null, sourceError
1527
- ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u6CA1\u6709\u53EF\u7528\u7F13\u5B58\uFF0C\u56E0\u6B64\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
1668
+ ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
1528
1669
  : route.mode === 'installed' && items.length === 0
1529
1670
  ? _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
1530
1671
  : null, pluginRows.length > 0
@@ -1547,6 +1688,7 @@ export function ConsoleAppV4(props) {
1547
1688
  setSelectedTargets([]);
1548
1689
  setTargetSelections({});
1549
1690
  setPreferredTarget(undefined);
1691
+ props.controller.beginInstallFlow(plugin.pluginId, initialScope);
1550
1692
  if (available.length > 0)
1551
1693
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
1552
1694
  else
@@ -1572,6 +1714,19 @@ export function ConsoleAppV4(props) {
1572
1714
  return { label: '查看插件设置', description: `${plugin.configFieldCount} 个配置字段及当前来源`, run: () => beginConfigFlow(plugin.pluginId) };
1573
1715
  if (action.id === 'sourceProblem')
1574
1716
  return { label: '查看来源问题', status: 'warning', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
1717
+ if (action.id === 'outputCollision') {
1718
+ const collision = props.controller.listUserIssues().find((issue) => issue.pluginId === plugin.pluginId && issue.category === 'artifactCollision');
1719
+ return {
1720
+ label: '查看输出冲突',
1721
+ description: '目标位置已有不属于当前插件的内容',
1722
+ status: 'error',
1723
+ run: () => collision
1724
+ ? go({ name: 'issueDetail', issueId: collision.issueId })
1725
+ : go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
1726
+ };
1727
+ }
1728
+ if (action.id === 'about')
1729
+ return { label: '关于', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
1575
1730
  return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
1576
1731
  };
1577
1732
  const previewMenu = dynamicMenu;
@@ -1718,7 +1873,7 @@ export function ConsoleAppV4(props) {
1718
1873
  title = '输入 Agent ID';
1719
1874
  description = '多个 Agent 可使用逗号或空格分隔。';
1720
1875
  help = '输入 Agent ID · Enter 保存 · Esc 返回';
1721
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: portableAgentInput || ' ', description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
1876
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: portableAgentInput, columns: Math.max(8, size.columns - 8) }), description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
1722
1877
  }
1723
1878
  else if (route.name === 'portableScope' && plugin && snapshot.portableDraft) {
1724
1879
  breadcrumb = `${plugin.displayName} / 通用 Skills / 范围`;
@@ -1814,8 +1969,28 @@ export function ConsoleAppV4(props) {
1814
1969
  breadcrumb = `${plugin.displayName} / 依赖关系`;
1815
1970
  context = overview ? `${overview.summary.totalCount + 1} 个插件` : '只读';
1816
1971
  title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
1817
- description = '依赖从同一个来源快照解析;关系页不会修改安装。';
1972
+ description = '依赖由同一来源快照解析;调整前会进入 Review。';
1973
+ const installedTargets = plugin.installedPlacements.filter((placement) => placement.state === 'installed');
1818
1974
  actions = [
1975
+ ...(hasInstalledPlacement(plugin) && dependencyCount > 0 && installedTargets.length > 0 ? [{
1976
+ label: '调整依赖位置…',
1977
+ description: '按平台设置 project/user;不会自动清理所有项目内容',
1978
+ run: async () => {
1979
+ const rootScope = installedTargets.some((placement) => placement.desiredScopes?.includes('project') || placement.scope === 'project')
1980
+ ? 'project' : 'user';
1981
+ const platforms = [...new Set(installedTargets.map((placement) => placement.platform))];
1982
+ setScope(rootScope);
1983
+ setSelectedTargets(platforms);
1984
+ props.controller.beginInstallFlow(plugin.pluginId, rootScope, { adjustingExisting: true });
1985
+ props.controller.setInstallPlatforms(platforms);
1986
+ const configurable = await props.controller.prepareInstallDependencyPlacement();
1987
+ if (!configurable) {
1988
+ setActionError('当前依赖位置都由约束固定,没有可调整项。');
1989
+ return;
1990
+ }
1991
+ go({ name: 'dependencyPlacement', pluginId: plugin.pluginId });
1992
+ },
1993
+ }] : []),
1819
1994
  { label: '技术详情', description: '显示 canonical pluginId 和安全错误代码', run: () => setShowTechnical((value) => !value) },
1820
1995
  ];
1821
1996
  const rootStatus = plugin.aggregateStatus === 'installed' ? 'success'
@@ -1867,23 +2042,19 @@ export function ConsoleAppV4(props) {
1867
2042
  breadcrumb = `${plugin.displayName} / 添加`;
1868
2043
  context = '第 1 步,共 2 步';
1869
2044
  title = `你希望在哪里使用 ${plugin.displayName}?`;
1870
- const overview = dependencyOverview(plugin);
1871
2045
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1872
2046
  description = hasDependencies
1873
- ? '根插件和缺失依赖使用同一个位置;健康的所有项目依赖可在当前项目复用。'
2047
+ ? '这里只选择根插件位置。依赖可以在下一步单独调整;依赖不能比根插件位置更窄。'
1874
2048
  : '选择位置后,再选择一个或多个目标平台。';
1875
2049
  const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
1876
2050
  .some((entry) => entry.compatible));
1877
- const reusableDependencies = overview?.dependencies.filter((dependency) => dependency.placements.some((placement) => placement.state === 'installed' && placement.health === 'ready'
1878
- && (scope === 'project' || placement.scope === 'user'))).length ?? 0;
1879
- const missingDependencies = Math.max(0, (overview?.summary.totalCount ?? 0) - reusableDependencies);
1880
2051
  actions = [
1881
2052
  ...availableScopes.map((candidateScope) => ({
1882
2053
  label: scopeLabel(candidateScope),
1883
2054
  selected: candidateScope === scope,
1884
2055
  description: candidateScope === 'user'
1885
- ? hasDependencies ? '根插件和缺失依赖都可在任意项目中使用' : '在任意项目中可使用,适合个人常用能力'
1886
- : hasDependencies ? '缺失依赖添加到当前项目;已有所有项目依赖会复用' : '仅在当前项目中使用,适合团队共享',
2056
+ ? hasDependencies ? '根插件在任意项目中可用;依赖位置下一步确认' : '在任意项目中可使用,适合个人常用能力'
2057
+ : hasDependencies ? '根插件只在当前项目使用;依赖可单独调整为所有项目' : '仅在当前项目中使用,适合团队共享',
1887
2058
  meta: candidateScope === 'user' ? '推荐' : undefined,
1888
2059
  run: () => {
1889
2060
  const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
@@ -1897,6 +2068,7 @@ export function ConsoleAppV4(props) {
1897
2068
  setScope(candidateScope);
1898
2069
  setSelectedTargets(next);
1899
2070
  setTargetSelections((current) => ({ ...current, [candidateScope]: next }));
2071
+ props.controller.setInstallRootScope(candidateScope);
1900
2072
  go({
1901
2073
  name: 'installTargets',
1902
2074
  pluginId: plugin.pluginId,
@@ -1905,7 +2077,7 @@ export function ConsoleAppV4(props) {
1905
2077
  },
1906
2078
  })),
1907
2079
  ];
1908
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5904\u7406", children: [_jsx(StaticRow, { title: `${reusableDependencies} 个依赖会复用现有安装`, status: reusableDependencies > 0 ? 'success' : 'muted' }), _jsx(StaticRow, { title: `${missingDependencies} 个依赖将安装到所选位置`, status: missingDependencies > 0 ? 'info' : 'muted' })] }) : null] });
2080
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsx(SectionCard, { title: "\u4F9D\u8D56\u4F4D\u7F6E", children: _jsx(StaticRow, { title: `${plugin.dependencySummary?.totalCount ?? 0} 个依赖将在下一步单独确认`, status: "info" }) }) : null] });
1909
2081
  }
1910
2082
  else if (route.name === 'installTargets' && plugin) {
1911
2083
  breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
@@ -1913,8 +2085,8 @@ export function ConsoleAppV4(props) {
1913
2085
  title = '选择目标平台';
1914
2086
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1915
2087
  description = hasDependencies
1916
- ? '只有根插件和所有依赖都支持所选位置的目标平台才可选择。'
1917
- : '同一插件、同一平台只能安装到一个位置。';
2088
+ ? '根插件必须支持所选位置;每个依赖必须至少有一个合法位置。'
2089
+ : '目标平台按所选位置验证;仅含 Codex native 的插件可同时启用两个位置。';
1918
2090
  const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
1919
2091
  const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
1920
2092
  const allUnavailable = choices.length > 0 && choices.every((choice) => !choice.compatible);
@@ -1943,6 +2115,7 @@ export function ConsoleAppV4(props) {
1943
2115
  ? current.filter((platform) => platform !== choice.platform)
1944
2116
  : [...current, choice.platform];
1945
2117
  setTargetSelections((selections) => ({ ...selections, [scope]: next }));
2118
+ props.controller.setInstallPlatforms(next);
1946
2119
  return next;
1947
2120
  });
1948
2121
  },
@@ -1955,7 +2128,15 @@ export function ConsoleAppV4(props) {
1955
2128
  disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
1956
2129
  run: () => {
1957
2130
  setDirty(false);
1958
- void beginReview(installIntent(plugin, validSelectedTargets));
2131
+ if (!hasDependencies) {
2132
+ if (props.controller.snapshot.installPlacementDraft?.pluginId !== plugin.pluginId) {
2133
+ props.controller.beginInstallFlow(plugin.pluginId, scope);
2134
+ }
2135
+ props.controller.setInstallPlatforms(validSelectedTargets);
2136
+ beginReview(props.controller.installPlacementIntent());
2137
+ return;
2138
+ }
2139
+ return continueInstallAfterTargets(plugin, validSelectedTargets);
1959
2140
  },
1960
2141
  },
1961
2142
  { label: '返回选择位置', run: back },
@@ -1964,6 +2145,83 @@ export function ConsoleAppV4(props) {
1964
2145
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length }), hasDependencies ? _jsx(Callout, { title: "\u76EE\u6807\u5E73\u53F0\u517C\u5BB9\u6027", description: "\u4E0D\u517C\u5BB9\u7684\u76EE\u6807\u5E73\u53F0\u4E0D\u80FD\u9009\u62E9\uFF1B\u8BF7\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB\u4E86\u89E3\u539F\u56E0\u3002" }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length })] });
1965
2146
  }
1966
2147
  }
2148
+ else if (route.name === 'dependencyPlacement' && plugin) {
2149
+ const draft = snapshot.installPlacementDraft?.pluginId === plugin.pluginId
2150
+ ? snapshot.installPlacementDraft : undefined;
2151
+ breadcrumb = `${plugin.displayName} / 添加 / 依赖位置`;
2152
+ context = '依赖位置';
2153
+ title = '选择每个平台的依赖位置';
2154
+ description = '每个直接或间接依赖都可独立设置。所有项目位置更宽,会影响其他项目和你的用户环境。';
2155
+ const dependencyActions = (draft?.targets ?? []).map((target) => ({
2156
+ label: `${target.displayName} · ${platformLabel(target.platform)}`,
2157
+ description: `${target.relation === 'direct' ? '直接依赖' : '间接依赖'} · ${target.explanation}`,
2158
+ meta: `${scopeLabel(target.scope)}${target.assignmentSource === 'consumerPromotion' ? ' · fixed-point 提升' : ''}`,
2159
+ selected: true,
2160
+ disabled: target.locked ? '位置由平台或上游约束固定' : undefined,
2161
+ run: () => go({
2162
+ name: 'dependencyPlacementEditor',
2163
+ pluginId: plugin.pluginId,
2164
+ dependencyPluginId: target.pluginId,
2165
+ platform: target.platform,
2166
+ }),
2167
+ }));
2168
+ actions = [
2169
+ ...dependencyActions,
2170
+ {
2171
+ label: '继续到确认',
2172
+ description: draft?.canContinue ? '使用这些最终位置准备 Review' : undefined,
2173
+ disabled: !draft?.canContinue ? draft?.error?.message ?? '依赖位置尚未就绪' : undefined,
2174
+ run: () => beginReview(props.controller.installPlacementIntent()),
2175
+ },
2176
+ {
2177
+ label: '恢复推荐位置',
2178
+ description: '恢复默认、健康 user 复用和 fixed-point 计算结果',
2179
+ run: () => props.controller.resetDependencyScopes(),
2180
+ },
2181
+ { label: '返回目标平台', run: back },
2182
+ ];
2183
+ const placementViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(dependencyPlacementStaticRows));
2184
+ const userCount = draft?.targets.filter((target) => target.scope === 'user').length ?? 0;
2185
+ const defaultCount = draft?.targets.filter((target) => target.assignmentSource === 'default').length ?? 0;
2186
+ const reusedCount = draft?.targets.filter((target) => target.assignmentSource === 'existingWiderTarget').length ?? 0;
2187
+ const promotedCount = draft?.targets.filter((target) => target.assignmentSource === 'consumerPromotion').length ?? 0;
2188
+ const reasonSummary = [
2189
+ ...(defaultCount > 0 ? [`默认 project ${defaultCount} 项`] : []),
2190
+ ...(reusedCount > 0 ? [`复用健康的所有项目安装 ${reusedCount} 项`] : []),
2191
+ ...(promotedCount > 0 ? [`fixed-point 提升 ${promotedCount} 项`] : []),
2192
+ ].join(';');
2193
+ content = _jsxs(Box, { flexDirection: "column", children: [policy.mode === 'compact'
2194
+ ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: draft ? scopeLabel(draft.rootScope) : '未就绪', compact: true }), _jsx(StaticRow, { title: `位置原因 · ${reasonSummary || '由平台或上游约束固定'}`, status: userCount > 0 ? 'warning' : 'info', compact: true }), _jsx(StaticRow, { title: "\u8303\u56F4\u63D0\u793A \u00B7 \u6240\u6709\u9879\u76EE\u4F4D\u7F6E\u4F1A\u5F71\u54CD\u5176\u4ED6\u9879\u76EE\u548C\u7528\u6237\u73AF\u5883", status: userCount > 0 ? 'warning' : 'muted', compact: true })] })
2195
+ : _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u6839\u63D2\u4EF6\u4F4D\u7F6E", children: _jsx(StaticRow, { title: plugin.displayName, meta: draft ? scopeLabel(draft.rootScope) : '未就绪', compact: true }) }), _jsx(Callout, { status: userCount > 0 ? 'warning' : 'info', title: userCount > 0 ? `${userCount} 个依赖平台目标使用所有项目位置` : '位置计算原因', description: `${reasonSummary || '依赖位置由平台或上游约束固定'}${userCount > 0 ? ';所有项目位置对其他项目也可见,并会改变你的用户环境' : ''}。` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: placementViewport.offset, limit: placementViewport.limit })] });
2196
+ help = '↑↓ 移动 · Enter 打开/继续 · Esc 返回';
2197
+ }
2198
+ else if (route.name === 'dependencyPlacementEditor' && plugin) {
2199
+ const target = snapshot.installPlacementDraft?.targets.find((entry) => entry.pluginId === route.dependencyPluginId && entry.platform === route.platform);
2200
+ breadcrumb = `${plugin.displayName} / 依赖位置 / ${target?.displayName ?? route.dependencyPluginId}`;
2201
+ context = platformLabel(route.platform);
2202
+ title = `设置 ${target?.displayName ?? route.dependencyPluginId} 的位置`;
2203
+ description = target?.explanation ?? '依赖位置已变化。请返回重新检查。';
2204
+ actions = ['project', 'user'].map((candidateScope) => {
2205
+ const unavailable = !target?.allowedScopes.includes(candidateScope)
2206
+ || target.minimumScope === 'user' && candidateScope === 'project';
2207
+ return {
2208
+ label: scopeLabel(candidateScope),
2209
+ selected: target?.scope === candidateScope,
2210
+ description: candidateScope === 'user'
2211
+ ? '更宽:对其他项目可见,并会改变你的用户环境'
2212
+ : '仅供当前项目使用;适合项目共享 intent',
2213
+ disabled: unavailable ? '这个依赖或上游约束不允许此位置' : undefined,
2214
+ run: async () => {
2215
+ await props.controller.setDependencyScope(route.dependencyPluginId, route.platform, candidateScope);
2216
+ back();
2217
+ },
2218
+ };
2219
+ });
2220
+ content = _jsxs(Box, { flexDirection: "column", children: [target?.assignmentSource === 'consumerPromotion'
2221
+ ? _jsx(Callout, { status: "warning", title: "fixed-point \u4F4D\u7F6E\u63D0\u5347", description: `上游依赖需要至少使用${scopeLabel(target.minimumScope)}。` })
2222
+ : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
2223
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
2224
+ }
1967
2225
  else if (route.name === 'pluginDetail' && plugin) {
1968
2226
  breadcrumb = `我的插件 / ${plugin.displayName}`;
1969
2227
  context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
@@ -1972,6 +2230,13 @@ export function ConsoleAppV4(props) {
1972
2230
  const dependencySummary = plugin.dependencySummary;
1973
2231
  const detailMenu = dynamicMenu;
1974
2232
  actions = detailMenu.map((action) => {
2233
+ if (action.id === 'resolveScopeConflict')
2234
+ return {
2235
+ label: `保留${scopeLabel(action.keepScope)},移除${scopeLabel(action.scope)} root…`,
2236
+ description: action.description,
2237
+ status: 'error',
2238
+ run: () => beginScopeConflictRecovery(action),
2239
+ };
1975
2240
  if (action.id === 'platform' || action.id === 'scopeConflict') {
1976
2241
  const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
1977
2242
  const projected = placementStatus(placement);
@@ -2017,14 +2282,20 @@ export function ConsoleAppV4(props) {
2017
2282
  && candidate.platform === route.platform
2018
2283
  && candidate.scope === placement.scope)
2019
2284
  : undefined;
2285
+ const collisionIssue = placement?.state === 'blocked'
2286
+ || placement?.state === 'installed' && placement.blockingReason === 'artifactCollision'
2287
+ ? props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId
2288
+ && candidate.platform === route.platform
2289
+ && candidate.category === 'artifactCollision')
2290
+ : undefined;
2020
2291
  const sourceIssue = plugin.aggregateStatus === 'unavailable'
2021
2292
  ? props.controller.listUserIssues().find((candidate) => candidate.entity.kind === 'source' && candidate.sourceId === plugin.sourceId)
2022
2293
  : undefined;
2023
2294
  const platformAction = (action) => {
2024
2295
  if (action.id === 'sourceProblem')
2025
- return { label: '查看来源问题', description: `${plugin.sourceId} 无可用缓存`, run: () => go({ name: 'issueDetail', issueId: `source:${plugin.sourceId}:unavailable` }) };
2296
+ return { label: '查看来源问题', description: `${plugin.sourceId} 当前不可用`, run: () => go({ name: 'issueDetail', issueId: `source:${plugin.sourceId}:unavailable` }) };
2026
2297
  if (action.id === 'refreshSource')
2027
- return { label: '重新检查状态', description: '不会修改现场内容', run: () => props.controller.refreshSource(plugin.sourceId) };
2298
+ return { label: '重新加载状态', description: `如需同步,先在终端运行 aw catalog source sync ${plugin.sourceId}`, run: async () => { await props.controller.reloadCatalog({ sourceId: plugin.sourceId }); } };
2028
2299
  if (action.id === 'add')
2029
2300
  return { label: `添加到 ${platformLabel(route.platform)}…`, description: '选择使用位置', run: () => {
2030
2301
  const initialScope = placement?.state === 'notInstalled'
@@ -2034,14 +2305,50 @@ export function ConsoleAppV4(props) {
2034
2305
  setSelectedTargets([]);
2035
2306
  setTargetSelections({});
2036
2307
  setPreferredTarget(route.platform);
2308
+ props.controller.beginInstallFlow(plugin.pluginId, initialScope);
2037
2309
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
2038
2310
  } };
2311
+ if (action.id === 'addScope')
2312
+ return {
2313
+ label: `在${scopeLabel(action.scope)}中启用…`,
2314
+ description: '只增加该 scope 的 activation;shared native 保持复用',
2315
+ run: () => beginSecondScope(plugin, action.platform, action.scope),
2316
+ };
2039
2317
  if (action.id === 'about')
2040
2318
  return { label: '查看支持信息', description: 'manifest 与平台能力', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
2319
+ if (action.id === 'source')
2320
+ return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
2321
+ if (action.id === 'outputCollision')
2322
+ return {
2323
+ label: '查看输出冲突',
2324
+ description: '不会覆盖或删除占用内容',
2325
+ status: 'error',
2326
+ run: () => collisionIssue
2327
+ ? go({ name: 'issueDetail', issueId: collisionIssue.issueId })
2328
+ : go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
2329
+ };
2041
2330
  if (action.id === 'scopeConflict')
2042
2331
  return { label: '查看位置冲突问题', run: () => go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${route.platform}` }) };
2332
+ if (action.id === 'resolveScopeConflict')
2333
+ return {
2334
+ label: `保留${scopeLabel(action.keepScope)},移除${scopeLabel(action.scope)} root…`,
2335
+ description: action.description,
2336
+ status: 'error',
2337
+ run: () => beginScopeConflictRecovery(action),
2338
+ };
2043
2339
  if (action.id === 'upgrade')
2044
2340
  return { label: `升级到 ${action.availableVersion}…`, description: '使用当前来源中的版本重建', run: () => beginReview({ kind: 'pluginWrite', action: 'upgrade', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
2341
+ if (action.id === 'sync')
2342
+ return {
2343
+ label: '同步 desired intent…',
2344
+ description: placement?.state === 'installed'
2345
+ ? placement.recovery?.find((entry) => entry.command === 'sync')?.description
2346
+ ?? '进入现有 Review、进度和结果链修复 needsSync 或 drift'
2347
+ : undefined,
2348
+ run: () => placement?.state === 'installed'
2349
+ ? beginReview({ kind: 'pluginWrite', action: 'sync', pluginId: plugin.pluginId, platform: route.platform, scope: placement.scope })
2350
+ : undefined,
2351
+ };
2045
2352
  if (action.id === 'reinstall')
2046
2353
  return { label: placement?.state === 'installed' && placement.health === 'incomplete' ? '重新连接缺失内容…' : '重新安装…', description: '保留现有设置并重新生成受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
2047
2354
  if (action.id === 'remove')
@@ -2053,6 +2360,7 @@ export function ConsoleAppV4(props) {
2053
2360
  return { label: '返回插件', run: back };
2054
2361
  };
2055
2362
  breadcrumb = `${plugin.displayName} / ${platformLabel(route.platform)}`;
2363
+ const installedPlacementRows = placement?.state === 'installed' ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "Desired placement", meta: placement.desiredScopes?.map(scopeLabel).join('、') || scopeLabel(placement.scope), compact: true }), _jsx(StaticRow, { title: "Actual scope \u72B6\u6001", meta: placement.actualState ?? placement.health, status: placement.actualState === 'ready' ? 'success' : placement.actualState === 'conflict' ? 'error' : 'warning', compact: true }), placement.sharedNative ? _jsx(StaticRow, { title: "Shared native", meta: placement.sharedNative.state, status: placement.sharedNative.state === 'present' ? 'success' : 'warning', compact: true }) : null, placement.activation ? _jsx(StaticRow, { title: "Activation", meta: `user ${placement.activation.user ? 'on' : 'off'} · project ${placement.activation.project ? 'on' : 'off'}`, compact: true }) : null, placement.extensions ? _jsx(StaticRow, { title: "AW-only extensions", meta: `${placement.extensions.state} · ${placement.extensions.kinds.join('、') || '无'}`, status: placement.extensions.state === 'ready' ? 'success' : placement.extensions.state === 'conflict' ? 'error' : 'warning', compact: true }) : null] }) : null;
2056
2364
  if (plugin.aggregateStatus === 'unavailable') {
2057
2365
  const sourceOverview = props.controller.getSourceOverview(plugin.sourceId);
2058
2366
  const diagnostic = sourceIssue?.diagnostic ?? sourceDiagnostic(plugin.sourceId, sourceOverview.health?.state ?? sourceOverview.source?.state, sourceOverview.detail);
@@ -2069,6 +2377,15 @@ export function ConsoleAppV4(props) {
2069
2377
  actions = platformMenu.map(platformAction);
2070
2378
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "muted", title: "\u672A\u6DFB\u52A0", description: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2071
2379
  }
2380
+ else if (placement.state === 'blocked'
2381
+ || placement.state === 'installed' && placement.blockingReason === 'artifactCollision') {
2382
+ title = `${platformLabel(route.platform)} 无法添加`;
2383
+ description = placement.state === 'installed'
2384
+ ? '受管内容仍可安全移除;输出位置同时存在冲突内容。'
2385
+ : '输出位置已有不属于当前插件的内容。';
2386
+ actions = platformMenu.map(platformAction);
2387
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "error", title: "\u8F93\u51FA\u51B2\u7A81", description: "AW \u4E0D\u4F1A\u8986\u76D6\u6216\u5220\u9664\u5360\u7528\u5185\u5BB9" }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2388
+ }
2072
2389
  else if (placement.state === 'scopeConflict') {
2073
2390
  title = '安装位置冲突';
2074
2391
  description = '正常状态只能保留一个位置。';
@@ -2104,14 +2421,17 @@ export function ConsoleAppV4(props) {
2104
2421
  title = `${platformLabel(route.platform)} ${placement.health === 'incomplete' ? '安装不完整' : '暂时不可用'}`;
2105
2422
  description = '缺少一项必要内容,插件可能无法稳定启动。';
2106
2423
  actions = platformMenu.map(platformAction);
2107
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u9700\u8981\u5904\u7406", description: `${scopeLabel(placement.scope)} · ${plugin.sourceLabel}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2424
+ const actualTitle = placement.actualState === 'needsSync' ? '需要同步'
2425
+ : placement.actualState === 'drifted' ? '现场已漂移'
2426
+ : placement.actualState === 'conflict' ? '位置与扩展冲突' : '需要处理';
2427
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: placement.actualState === 'conflict' ? 'error' : 'warning', title: actualTitle, description: `${scopeLabel(placement.scope)} · ${plugin.sourceLabel}`, children: installedPlacementRows }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2108
2428
  }
2109
2429
  else {
2110
2430
  title = `已安装到${scopeLabel(placement.scope)}`;
2111
2431
  const installedVersion = placement.revision.installedVersion;
2112
2432
  description = `当前检查正常${installedVersion ? `;已安装版本 ${installedVersion}` : ''}。`;
2113
2433
  actions = platformMenu.map(platformAction);
2114
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "success", title: "\u72B6\u6001\u6B63\u5E38", description: `${scopeLabel(placement.scope)}${installedVersion ? ` · 已安装版本 ${installedVersion}` : ''}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2434
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "success", title: "\u72B6\u6001\u6B63\u5E38", description: `${scopeLabel(placement.scope)}${installedVersion ? ` · 已安装版本 ${installedVersion}` : ''}`, children: installedPlacementRows }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
2115
2435
  }
2116
2436
  }
2117
2437
  else if (route.name === 'configLayer' && plugin) {
@@ -2190,7 +2510,12 @@ export function ConsoleAppV4(props) {
2190
2510
  fieldKey: field.key,
2191
2511
  groupId: activeGroup.id,
2192
2512
  })
2193
- : () => go({
2513
+ : () => go(field.configured ? {
2514
+ name: 'configFieldAction',
2515
+ pluginId: plugin.pluginId,
2516
+ layer: mutableLayer,
2517
+ fieldKey: field.key,
2518
+ } : {
2194
2519
  name: 'configFieldEditor',
2195
2520
  pluginId: plugin.pluginId,
2196
2521
  layer: mutableLayer,
@@ -2200,13 +2525,27 @@ export function ConsoleAppV4(props) {
2200
2525
  }));
2201
2526
  const operationActions = readonly ? [] : [
2202
2527
  { label: '保存修改', description: dirty ? '查看安全变更汇总' : '当前没有未保存修改', disabled: dirty ? undefined : '当前没有未保存修改', run: beginConfigSave },
2203
- {
2204
- label: '重置此设置层…',
2205
- description: '删除本层全部值并恢复继承',
2206
- status: 'error',
2207
- disabled: currentConfigPage.storedCount === 0 ? '本层没有已存值' : undefined,
2208
- run: () => beginReview({ kind: 'configReset', pluginId: plugin.pluginId, scope: route.layer.scope, projectLayer: route.layer.projectLayer }),
2209
- },
2528
+ ...(currentConfigPage.storedCount > 0 ? [{
2529
+ label: route.layer.scope === 'user'
2530
+ ? '清空个人默认…'
2531
+ : route.layer.projectLayer === 'local' ? '删除本机设置…' : '清空当前项目共享设置…',
2532
+ description: `删除${configLayerLabel(route.layer)}中的全部值并恢复继承;其他设置层保持不变`,
2533
+ status: 'error',
2534
+ run: () => beginReview({
2535
+ kind: 'configReset',
2536
+ pluginId: plugin.pluginId,
2537
+ scope: route.layer.scope,
2538
+ projectLayer: route.layer.projectLayer,
2539
+ changes: currentConfigPage.groups.flatMap((group) => group.fields)
2540
+ .filter((field) => field.configured)
2541
+ .map((field) => ({
2542
+ key: field.key,
2543
+ title: field.title,
2544
+ before: field.displayValue,
2545
+ after: `恢复继承(${field.fallbackDisplayValue ?? '未设置'} · ${field.fallbackSourceLabel ?? '未设置'})`,
2546
+ })),
2547
+ }),
2548
+ }] : []),
2210
2549
  ];
2211
2550
  actions = [...tabActions, ...operationActions];
2212
2551
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
@@ -2226,6 +2565,32 @@ export function ConsoleAppV4(props) {
2226
2565
  : null] });
2227
2566
  }
2228
2567
  }
2568
+ else if (route.name === 'configFieldAction' && plugin && activeField) {
2569
+ const layer = configLayerLabel(route.layer);
2570
+ breadcrumb = `${plugin.displayName} / 设置 / ${layer} / ${activeField.title}`;
2571
+ title = activeField.title;
2572
+ description = `这个字段在${layer}中已经有值。`;
2573
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
2574
+ const deleteLabel = route.layer.scope === 'user'
2575
+ ? '删除个人默认中的值'
2576
+ : route.layer.projectLayer === 'local' ? '删除本机设置中的值' : '删除当前项目中的值';
2577
+ actions = [
2578
+ {
2579
+ label: '修改值…',
2580
+ run: () => go({
2581
+ name: 'configFieldEditor',
2582
+ pluginId: plugin.pluginId,
2583
+ layer: route.layer,
2584
+ fieldKey: activeField.key,
2585
+ editor: fieldEditor(activeField),
2586
+ }),
2587
+ },
2588
+ draftRemoves.includes(activeField.key)
2589
+ ? { label: '撤销删除', run: () => { cancelDraftRemoval(activeField.key); back(); } }
2590
+ : { label: deleteLabel, status: 'error', run: () => { removeDraft(activeField.key); back(); } },
2591
+ ];
2592
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: `${layer}中的值`, meta: currentFieldDisplay(activeField) }), _jsx(StaticRow, { title: "\u5F53\u524D\u5B9E\u9645\u751F\u6548", meta: `${effectiveFieldDisplay(activeField)} · 来自${activeField.effectiveSourceLabel}` }), _jsx(StaticRow, { title: "\u5220\u9664\u540E\u5C06\u6062\u590D", meta: `${activeField.fallbackDisplayValue ?? '未设置'} · 来自${activeField.fallbackSourceLabel ?? '未设置'}` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2593
+ }
2229
2594
  else if (route.name === 'configFieldEditor' && plugin && activeField) {
2230
2595
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title}`;
2231
2596
  help = route.editor === 'enum' || route.editor === 'boolean'
@@ -2253,10 +2618,8 @@ export function ConsoleAppV4(props) {
2253
2618
  ? `允许范围:${activeField.min ?? '不限'}–${activeField.max ?? '不限'}${activeField.unit ? ` ${activeField.unit}` : ''}。`
2254
2619
  : route.editor === 'path' ? `${activeField.type === 'file' ? '文件' : '目录'}路径会在保存前由现有 schema 路径验证。`
2255
2620
  : route.editor === 'sensitive' ? '当前值不会回显;留空不会自动删除旧值。' : activeField.description ?? '';
2256
- const visibleInput = route.editor === 'sensitive'
2257
- ? '•'.repeat(inputValue.length)
2258
- : route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
2259
- content = _jsx(InputCard, { focused: true, title: visibleInput || activeField.placeholder || ' ', description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
2621
+ const visibleInput = route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
2622
+ content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: visibleInput, sensitive: route.editor === 'sensitive', placeholder: activeField.placeholder, columns: Math.max(8, size.columns - 8) }), description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
2260
2623
  }
2261
2624
  }
2262
2625
  else if (route.name === 'configUnavailable' && plugin) {
@@ -2266,11 +2629,7 @@ export function ConsoleAppV4(props) {
2266
2629
  title = diagnostic.title;
2267
2630
  description = '';
2268
2631
  actions = [
2269
- { label: '重新检查来源', description: '刷新当前来源后重新读取可信定义', run: async () => {
2270
- await props.controller.refreshSource(plugin.sourceId);
2271
- setConfigLoadKey((value) => value + 1);
2272
- props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: route.layer ?? { scope: 'user', projectLayer: 'shared' } });
2273
- } },
2632
+ { label: `在终端运行 aw catalog source sync ${plugin.sourceId}`, description: '同步成功后重新加载 TUI', run: back },
2274
2633
  { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => setShowTechnical((value) => !value) },
2275
2634
  { label: '返回', run: back },
2276
2635
  ];
@@ -2281,22 +2640,21 @@ export function ConsoleAppV4(props) {
2281
2640
  title = '插件来源';
2282
2641
  description = '单个来源失败不会阻止其他来源继续工作。';
2283
2642
  context = `${props.controller.listSources().length} 个来源`;
2284
- help = '↑↓ 移动 · Enter 打开 · r 重新检查 · Esc 返回';
2643
+ help = '↑↓ 移动 · Enter 打开 · Esc 返回';
2285
2644
  actions = [
2286
2645
  ...props.controller.listSources().map((source) => {
2287
2646
  const overview = props.controller.getSourceOverview(source.id);
2647
+ const generation = props.controller.getCatalogGeneration(source.id);
2288
2648
  const health = overview.health?.state ?? source.state;
2289
- const status = health === 'ready' ? '已从远端加载'
2290
- : health === 'path' ? '本地目录'
2291
- : health === 'checking' ? '正在检查'
2292
- : health === 'cache' || health === 'degraded' ? '使用缓存' : '无法读取';
2649
+ const status = generation?.active ? `Active ${generation.active.catalogVersion}`
2650
+ : health === 'ready' ? '当前使用'
2651
+ : '无法读取';
2293
2652
  return {
2294
2653
  label: `${source.id}${source.primary ? ' 主来源' : ''}`,
2295
2654
  description: `${overview.detail?.type === 'path' ? '本地目录' : 'Git 仓库'} · ${source.count ?? 0} 个插件`,
2296
2655
  meta: status,
2297
- status: health === 'unavailable' ? 'error'
2298
- : health === 'cache' || health === 'degraded' ? 'warning'
2299
- : source.primary ? 'success' : undefined,
2656
+ status: !generation?.active || health === 'unavailable' ? 'error'
2657
+ : source.primary ? 'success' : undefined,
2300
2658
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
2301
2659
  };
2302
2660
  }),
@@ -2309,43 +2667,35 @@ export function ConsoleAppV4(props) {
2309
2667
  else if (route.name === 'sourceDetail') {
2310
2668
  const overview = props.controller.getSourceOverview(route.sourceId);
2311
2669
  const detail = overview.detail;
2312
- const state = overview.health?.state ?? overview.source?.state;
2313
- const diagnostic = sourceDiagnostic(route.sourceId, state, detail);
2670
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2671
+ const activeVersion = generation?.active?.catalogVersion;
2672
+ const metadataInvalid = generation?.errorCode === 'catalogGenerationMetadataInvalid';
2673
+ const damagedEntry = generation?.recoveryEntryPath;
2314
2674
  breadcrumb = `插件来源 / ${route.sourceId}`;
2315
2675
  context = detail?.type === 'path' ? '本地目录' : 'Git';
2316
2676
  title = route.sourceId;
2317
- description = diagnostic ? '' : '来源状态可用。';
2318
- help = '↑↓ 移动 · Enter 选择 · r 重新检查 · Esc 返回';
2319
- actions = diagnostic
2320
- ? [
2321
- { label: '重新检查来源', description: '读取远端并验证可用数据', run: () => props.controller.refreshSource(route.sourceId) },
2322
- ...(state === 'degraded' ? [{
2323
- label: '继续使用缓存',
2324
- description: '浏览匹配且已验证的缓存数据',
2325
- run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
2326
- }] : []),
2327
- { label: '编辑来源…', description: '检查 URL、路径或 ref', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2328
- { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2329
- { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2330
- { label: '技术详情', description: '查看脱敏错误代码和解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2331
- ]
2332
- : [
2333
- { label: '重新检查来源', description: '读取远端并更新缓存', run: () => props.controller.refreshSource(route.sourceId) },
2334
- { label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2335
- { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2336
- { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2337
- { label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2338
- ];
2339
- const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 6));
2340
- content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
2341
- ? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
2342
- : _jsxs(_Fragment, { children: [_jsx(StatusCard, { status: state === 'checking' ? 'warning' : 'success', title: state === 'checking' ? '正在检查' : '可用' }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u63D2\u4EF6", meta: `${detail?.pluginCount ?? 0} 个` }), policy.mode === 'compact' ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u9009\u62E9", meta: detail?.type === 'path' ? '本地目录' : detail?.refPolicy === 'explicit' ? '固定 Git 引用' : '跟随 AW 版本通道' }), detail?.explicitRef ? _jsx(StaticRow, { title: "\u5F15\u7528", meta: detail.explicitRef }) : null] })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
2677
+ description = activeVersion ? `当前使用 Active Catalog ${activeVersion}。`
2678
+ : metadataInvalid && damagedEntry
2679
+ ? `Active Catalog 条目已损坏。先安全删除 AW_HOME 下的精确条目 ${damagedEntry},再运行 ${generation.syncInstruction},然后重新加载 TUI。`
2680
+ : `来源没有可用 Active。请在终端运行 ${generation?.syncInstruction ?? `aw catalog source sync ${route.sourceId}`},然后重新加载 TUI。`;
2681
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
2682
+ actions = [
2683
+ { label: metadataInvalid ? '修复后重新加载 TUI' : '重新加载 TUI', description: metadataInvalid && damagedEntry
2684
+ ? `先删除精确条目 ${damagedEntry},再运行 ${generation.syncInstruction}`
2685
+ : `先在终端运行 aw catalog source sync ${route.sourceId},再重新加载 Active`, run: () => props.controller.reloadCatalog({ sourceId: route.sourceId }) },
2686
+ ...(!metadataInvalid ? [{ label: '编辑来源…', description: '只修改来源配置;不会访问网络', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) }] : []),
2687
+ { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2688
+ { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2689
+ { label: '技术详情', description: '查看脱敏错误代码和 Active 证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2690
+ ];
2691
+ const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
2692
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "Active Catalog", status: activeVersion ? 'success' : 'warning', compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: activeVersion ? `Catalog ${activeVersion}` : '未同步', meta: activeVersion ? generation?.active?.digestShort : generation?.syncInstruction, compact: true }) }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", compact: policy.mode === 'compact', children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用', compact: true }), _jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录', compact: true })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
2343
2693
  }
2344
2694
  else if (route.name === 'sourceTechnical') {
2345
2695
  const overview = props.controller.getSourceOverview(route.sourceId);
2346
2696
  const detail = overview.detail;
2347
2697
  const state = overview.health?.state ?? overview.source?.state;
2348
- const diagnostic = sourceDiagnostic(route.sourceId, state, detail) ?? {
2698
+ const diagnostic = sourceDiagnostic(route.sourceId, state, detail, props.controller.getCatalogGeneration(route.sourceId)) ?? {
2349
2699
  severity: 'warning',
2350
2700
  title: '来源解析详情',
2351
2701
  userMessage: '来源当前可用。',
@@ -2357,7 +2707,6 @@ export function ConsoleAppV4(props) {
2357
2707
  sourceId: route.sourceId,
2358
2708
  ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
2359
2709
  ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
2360
- ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
2361
2710
  },
2362
2711
  };
2363
2712
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
@@ -2383,16 +2732,23 @@ export function ConsoleAppV4(props) {
2383
2732
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2384
2733
  }
2385
2734
  else if (route.step === 'refPolicy') {
2386
- title = '选择版本引用方式';
2387
- description = '跟随 AW 通道适合常规来源;固定 ref 适合测试来源。';
2735
+ title = '选择版本来源';
2736
+ description = 'Release 和 Beta 是固定 Git ref 的显示预设。';
2737
+ const useRefPreset = async (refPolicy, ref) => {
2738
+ const next = { ...sourceDraft, refPolicy, ref };
2739
+ setSourceDraft(next);
2740
+ if (next.mode === 'update')
2741
+ await beginSourceUpdateReview(next);
2742
+ else
2743
+ go({ name: 'sourceAdd', step: 'subdir', owner: route.owner });
2744
+ };
2388
2745
  actions = [
2389
- { label: '跟随 AW 版本通道', description: '自动选择 catalog/latest 或 catalog/beta', run: async () => { const next = { ...sourceDraft, refPolicy: 'cliChannel', ref: '' }; setSourceDraft(next); if (next.mode === 'update')
2390
- await beginSourceUpdateReview(next);
2391
- else
2392
- go({ name: 'sourceAdd', step: 'subdir', owner: route.owner }); } },
2393
- { label: '固定 Git 引用', description: 'branch、tag 或 commit', run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
2746
+ { label: '跟随 AW CLI', description: `当前 CLI ${snapshot.cliChannel === 'beta' ? 'beta' : 'release'} → catalog/${snapshot.cliChannel === 'beta' ? 'beta' : 'latest'}`, selected: sourceDraft.refPolicy === 'cliChannel', run: () => { const policy = catalogRefPolicyForPreset('followCli'); return useRefPreset(policy.type, ''); } },
2747
+ { label: 'Release', description: '固定读取 catalog/latest', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_LATEST_REF, run: () => { const policy = catalogRefPolicyForPreset('release'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
2748
+ { label: 'Beta', description: '固定读取 catalog/beta', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_BETA_REF, run: () => { const policy = catalogRefPolicyForPreset('beta'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
2749
+ { label: '指定 Git ref…', description: 'branch、tag、完整 ref 或 commit', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref !== CATALOG_LATEST_REF && sourceDraft.ref !== CATALOG_BETA_REF, run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
2394
2750
  ];
2395
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2751
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u8DDF\u968F\u542B\u4E49\uFF1A\u5F53\u524D CLI ", snapshot.cliChannel === 'beta' ? 'beta → catalog/beta' : 'release → catalog/latest'] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
2396
2752
  }
2397
2753
  else {
2398
2754
  title = route.step === 'id' ? '输入来源 ID' : route.step === 'location' ? `输入 ${sourceDraft.type === 'git' ? 'Git URL' : '绝对路径'}` : route.step === 'ref' ? '输入固定 Git 引用' : '输入 Catalog 子目录';
@@ -2402,7 +2758,7 @@ export function ConsoleAppV4(props) {
2402
2758
  : route.step === 'location' && sourceDraft.type === 'path'
2403
2759
  ? privatePathInput(sourceInput)
2404
2760
  : sourceInput;
2405
- content = _jsx(InputCard, { focused: true, title: displayedInput || ' ', description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
2761
+ content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: displayedInput, columns: Math.max(8, size.columns - 8) }), description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
2406
2762
  }
2407
2763
  }
2408
2764
  else if (route.name === 'sourceEdit') {
@@ -2418,12 +2774,35 @@ export function ConsoleAppV4(props) {
2418
2774
  setDirty(true);
2419
2775
  go({ name: 'sourceAdd', step: field === 'location' ? 'location' : field === 'subdir' ? 'subdir' : 'refPolicy', owner });
2420
2776
  };
2777
+ const switchOfficialTransport = (location) => {
2778
+ if (!detail || detail.type !== 'git'
2779
+ || detail.locationSummary === projectCatalogSourceLocation(location))
2780
+ return;
2781
+ const owner = `source-update:${route.sourceId}:location:${Date.now()}`;
2782
+ const next = {
2783
+ owner,
2784
+ mode: 'update',
2785
+ sourceId: route.sourceId,
2786
+ type: 'git',
2787
+ location,
2788
+ refPolicy: detail.refPolicy ?? 'cliChannel',
2789
+ ref: detail.explicitRef ?? '',
2790
+ subdir: detail.subdir ?? '',
2791
+ editField: 'location',
2792
+ };
2793
+ setSourceDraft(next);
2794
+ setDirty(true);
2795
+ void beginSourceUpdateReview(next);
2796
+ };
2421
2797
  actions = detail?.type === 'git' ? [
2422
- { label: 'Git URL', description: detail.locationSummary, run: () => startEdit('location') },
2798
+ ...(route.sourceId === 'official' ? [
2799
+ { label: 'HTTPS', description: '使用系统 Git credential helper', selected: detail.locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_URL), run: () => switchOfficialTransport(OFFICIAL_CATALOG_SOURCE_URL) },
2800
+ { label: 'SSH', description: '使用本机 SSH key;Git 连接使用 BatchMode', selected: detail.locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_SSH_URL), run: () => switchOfficialTransport(OFFICIAL_CATALOG_SOURCE_SSH_URL) },
2801
+ ] : [{ label: 'Git URL', description: detail.locationSummary, run: () => startEdit('location') }]),
2423
2802
  { label: '版本引用', description: detail.refPolicy === 'explicit' ? `固定:${detail.explicitRef}` : '跟随 AW 版本通道', run: () => startEdit('refPolicy') },
2424
2803
  { label: 'Catalog 子目录', description: detail.subdir ?? '未设置', run: () => startEdit('subdir') },
2425
2804
  ] : [{ label: '本地目录', description: '已选择本地目录', run: () => startEdit('location') }];
2426
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
2805
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' }), route.sourceId === 'official' && detail?.type === 'git' ? _jsxs(SectionCard, { title: "\u6765\u6E90\u8EAB\u4EFD", children: [_jsx(StaticRow, { title: "\u903B\u8F91 Catalog", meta: "lesliechan721-arch/agent-workflow" }), _jsx(StaticRow, { title: "source identity", meta: "HTTPS / SSH \u4FDD\u6301\u4E00\u81F4" }), _jsx(StaticRow, { title: "\u660E\u6587 HTTP", meta: "\u4E0D\u652F\u6301", status: "warning" })] }) : null] });
2427
2806
  }
2428
2807
  else if (route.name === 'sourceRemoveBlocked') {
2429
2808
  breadcrumb = `插件来源 / ${route.sourceId} / 移除`;
@@ -2456,7 +2835,7 @@ export function ConsoleAppV4(props) {
2456
2835
  description: entry.category.startsWith('dependency')
2457
2836
  ? `${props.controller.getPluginOverview(entry.dependencyPluginId ?? '')?.displayName ?? entry.dependencyPluginId ?? entry.explanation} · ${entry.impact[0] ?? ''}`
2458
2837
  : `${entry.explanation} · ${entry.impact[0] ?? ''}`,
2459
- status: entry.category === 'sourceDegraded' ? 'warning' : 'error',
2838
+ status: 'error',
2460
2839
  run: () => entry.category === 'dependencyStillRequired' && entry.pluginId
2461
2840
  ? go({ name: 'pluginDependencies', pluginId: entry.pluginId, view: 'removeBlocked',
2462
2841
  ...(entry.platform ? { platform: entry.platform } : {}),
@@ -2465,7 +2844,27 @@ export function ConsoleAppV4(props) {
2465
2844
  }));
2466
2845
  }
2467
2846
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
2468
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: issues.length > 0 ? 'task' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
2847
+ if (issues.length === 0) {
2848
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
2849
+ }
2850
+ else {
2851
+ const groupCounts = new Map();
2852
+ for (const issueEntry of issues) {
2853
+ groupCounts.set(issueEntry.group, (groupCounts.get(issueEntry.group) ?? 0) + 1);
2854
+ }
2855
+ const visibleGroups = [];
2856
+ const visibleEnd = Math.min(actions.length, viewport.offset + viewport.limit);
2857
+ for (let index = viewport.offset; index < visibleEnd; index += 1) {
2858
+ const group = issues[index].group;
2859
+ const currentGroup = visibleGroups.at(-1);
2860
+ if (!currentGroup || currentGroup.group !== group) {
2861
+ visibleGroups.push({ group, startIndex: index, actions: [actions[index]] });
2862
+ }
2863
+ else
2864
+ currentGroup.actions.push(actions[index]);
2865
+ }
2866
+ content = _jsx(Box, { flexDirection: "column", children: visibleGroups.map((group) => _jsx(SectionCard, { title: issueGroupLabel(group.group), meta: `${groupCounts.get(group.group) ?? group.actions.length} 项`, compact: policy.mode === 'compact', children: _jsx(SemanticRows, { actions: group.actions, focus: focus - group.startIndex, kind: "task", compact: policy.mode === 'compact' }) }, `${group.group}:${group.startIndex}`)) });
2867
+ }
2469
2868
  }
2470
2869
  else if (route.name === 'issueDetail' && issue) {
2471
2870
  breadcrumb = `需要处理 / ${issue.pluginId ?? issue.sourceId ?? issue.entity.id}`;
@@ -2479,8 +2878,8 @@ export function ConsoleAppV4(props) {
2479
2878
  return;
2480
2879
  }
2481
2880
  if (entry.id === 'refreshSource' && issue.sourceId)
2482
- await props.controller.refreshSource(issue.sourceId);
2483
- else if (entry.id === 'continueWithCache' || entry.id === 'retryLater')
2881
+ await props.controller.reloadCatalog({ sourceId: issue.sourceId });
2882
+ else if (entry.id === 'retryLater')
2484
2883
  go({ name: 'issueList' });
2485
2884
  else if (entry.id === 'editSource' && issue.sourceId)
2486
2885
  go(issue.entity.kind === 'source'
@@ -2519,7 +2918,8 @@ export function ConsoleAppV4(props) {
2519
2918
  }));
2520
2919
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
2521
2920
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
2522
- const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, compact: policy.mode === 'compact' || issue.category.startsWith('dependency') });
2921
+ const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, compact: policy.mode === 'compact' || issue.category.startsWith('dependency')
2922
+ || issue.category === 'scopeConflict' });
2523
2923
  const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
2524
2924
  const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
2525
2925
  ? {
@@ -2566,12 +2966,15 @@ export function ConsoleAppV4(props) {
2566
2966
  setScope(issueScope);
2567
2967
  setSelectedTargets([entry.platform]);
2568
2968
  setTargetSelections((current) => ({ ...current, [issueScope]: [entry.platform] }));
2969
+ props.controller.beginInstallFlow(issuePlugin.pluginId, issueScope);
2970
+ props.controller.setInstallPlatforms([entry.platform]);
2569
2971
  go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
2570
2972
  } })),
2571
2973
  { label: '返回调整目标平台', run: () => {
2572
2974
  setScope(issueScope);
2573
2975
  setSelectedTargets([]);
2574
2976
  setTargetSelections((current) => ({ ...current, [issueScope]: [] }));
2977
+ props.controller.beginInstallFlow(issuePlugin.pluginId, issueScope);
2575
2978
  go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
2576
2979
  } },
2577
2980
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
@@ -2595,12 +2998,9 @@ export function ConsoleAppV4(props) {
2595
2998
  content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2596
2999
  }
2597
3000
  else {
2598
- const sourceMissingCleanup = issue.category === 'sourceMissing' && issue.pluginId && issue.platform && issue.scope
2599
- ? `aw cleanup ${issue.pluginId} --platform ${issue.platform} --scope ${issue.scope}`
2600
- : undefined;
2601
- const hasRecoveryEvidence = sourceMissingCleanup || issue.category === 'sourceIdentityChanged'
3001
+ const hasRecoveryEvidence = issue.category === 'sourceIdentityChanged'
2602
3002
  || issue.placements || issue.requiredFields;
2603
- const recoveryRows = _jsxs(_Fragment, { children: [sourceMissingCleanup ? _jsxs(_Fragment, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: sourceMissingCleanup }), _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u9700\u8981\u65F6\u53EF\u5728\u547D\u4EE4\u672B\u5C3E\u6DFB\u52A0 --force\u3002" })] }) : null, issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
3003
+ const recoveryRows = _jsxs(_Fragment, { children: [issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
2604
3004
  content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && hasRecoveryEvidence
2605
3005
  ? policy.mode === 'compact' ? recoveryRows : _jsx(SectionCard, { title: "\u6062\u590D\u8BC1\u636E", children: recoveryRows })
2606
3006
  : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: issueActionViewport.offset, limit: issueActionViewport.limit })] });
@@ -2638,11 +3038,24 @@ export function ConsoleAppV4(props) {
2638
3038
  }
2639
3039
  else if (route.name === 'review' && snapshot.activeReview) {
2640
3040
  const review = snapshot.activeReview;
3041
+ const sharedImpactConfirmed = !review.requiresConfirmation
3042
+ || confirmedSharedReviewToken === review.reviewToken;
2641
3043
  breadcrumb = `${review.subject.name} / ${review.title}`;
2642
3044
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' ? review.title : `确认${review.title}`} ${review.subject.name}`;
2643
- description = snapshot.result?.state === 'stale' ? '当前状态已变化。请检查新的变化后再次确认。' : review.description;
3045
+ description = snapshot.result?.state === 'stale'
3046
+ ? '当前状态已变化。请检查新的变化后再次确认。'
3047
+ : review.requiresConfirmation ? '' : review.description;
2644
3048
  actions = [
2645
- { label: review.primaryLabel, description: '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
3049
+ ...(review.requiresConfirmation ? [{
3050
+ label: sharedImpactConfirmed ? '已确认 shared impact' : '确认 shared impact',
3051
+ description: review.sharedImpact.warning ?? '此操作会修改多个插件共享的 Codex native 内容',
3052
+ selected: sharedImpactConfirmed,
3053
+ run: () => {
3054
+ setConfirmedSharedReviewToken(review.reviewToken);
3055
+ setFocus(1);
3056
+ },
3057
+ }] : []),
3058
+ { label: review.primaryLabel, description: '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, disabled: sharedImpactConfirmed ? undefined : '请先显式确认 shared impact', run: () => {
2646
3059
  if (operationBusy.current)
2647
3060
  return;
2648
3061
  operationBusy.current = true;
@@ -2661,18 +3074,28 @@ export function ConsoleAppV4(props) {
2661
3074
  : back },
2662
3075
  ];
2663
3076
  const groupedChanges = review.changes.some((change) => change.group);
3077
+ const compressedReview = policy.mode === 'compact' || review.requiresConfirmation;
2664
3078
  const groupTitle = {
2665
3079
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
2666
3080
  dependencies: review.operationKind === 'portableInstall' ? '已选择 Skills · 来自依赖插件' : '必需依赖',
3081
+ projectIntent: 'Project intent',
3082
+ userIntent: 'User intent',
3083
+ sharedCodexNative: 'Shared Codex native',
3084
+ userActivation: 'User activation',
3085
+ projectActivation: 'Project activation',
3086
+ awExtensions: 'AW extensions',
3087
+ runtime: 'Codex runtime',
2667
3088
  portableTarget: '安装目标',
2668
3089
  portableInputs: '准备内容',
2669
3090
  portableWarnings: '覆盖冲突',
3091
+ configChanges: '设置变更',
3092
+ configImpact: '应用影响',
2670
3093
  preserved: '保持不变',
2671
3094
  };
2672
3095
  const changeStatus = (change) => change.kind === 'remove' ? 'error'
2673
3096
  : change.kind === 'warning' ? 'warning'
2674
3097
  : change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined;
2675
- const groupOrder = ['portableWarnings', 'portableTarget', 'requested', 'dependencies', 'preserved', 'portableInputs'];
3098
+ const groupOrder = ['portableWarnings', 'portableTarget', 'configChanges', 'configImpact', 'requested', 'dependencies', 'projectIntent', 'userIntent', 'sharedCodexNative', 'userActivation', 'projectActivation', 'awExtensions', 'runtime', 'preserved', 'portableInputs'];
2676
3099
  const groupedSpecs = groupedChanges
2677
3100
  ? groupOrder.map((group) => ({
2678
3101
  key: group,
@@ -2686,7 +3109,7 @@ export function ConsoleAppV4(props) {
2686
3109
  { key: 'changes', label: undefined, protectedCount: false, changes: review.changes.filter((change) => change.kind !== 'warning' && change.kind !== 'keep') },
2687
3110
  ];
2688
3111
  const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0);
2689
- const headingLines = policy.mode === 'compact' ? 0 : visibleSpecs.filter((group) => group.label).length;
3112
+ const headingLines = compressedReview ? 0 : visibleSpecs.filter((group) => group.label).length;
2690
3113
  const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
2691
3114
  const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
2692
3115
  const targetGroup = visibleSpecs.find((group) => group.key === 'portableTarget');
@@ -2705,14 +3128,15 @@ export function ConsoleAppV4(props) {
2705
3128
  const changeGroups = visibleSpecs.map((group) => {
2706
3129
  const visibleCount = allocations.get(group.key) ?? 1;
2707
3130
  const hiddenCount = group.changes.length - visibleCount;
2708
- return _jsxs(Box, { flexDirection: "column", children: [policy.mode !== 'compact' && group.label ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: group.label }) : null, group.changes.slice(0, visibleCount).map((change, index) => {
3131
+ return _jsxs(Box, { flexDirection: "column", children: [!compressedReview && group.label ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: group.label }) : null, group.changes.slice(0, visibleCount).map((change, index) => {
2709
3132
  const retainDescription = change.kind === 'warning' || group.key === 'portableTarget'
2710
- || group.key === 'requested' || group.key === 'dependencies';
3133
+ || group.key === 'requested' || group.key === 'dependencies'
3134
+ || group.key === 'configChanges' || group.key === 'configImpact';
2711
3135
  const description = retainDescription ? change.description : undefined;
2712
3136
  const compactGroupLabel = group.key === 'requested' ? '根插件'
2713
3137
  : group.key === 'dependencies' ? '依赖'
2714
3138
  : group.key === 'preserved' ? '保持' : group.label;
2715
- const category = policy.mode === 'compact' && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
3139
+ const category = compressedReview && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
2716
3140
  const count = index === visibleCount - 1 && (hiddenCount > 0 || group.protectedCount)
2717
3141
  ? hiddenCount > 0 ? `另有 ${hiddenCount} 项` : `共 ${group.changes.length} 项`
2718
3142
  : undefined;
@@ -2737,7 +3161,9 @@ export function ConsoleAppV4(props) {
2737
3161
  return _jsx(ReviewChangeRow, { title: `${category ? `${category} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`, meta: index === visibleTechnicalChanges.length - 1 && hiddenTechnicalChanges > 0
2738
3162
  ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
2739
3163
  });
2740
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel} ${review.subject.version ?? ''}`, description: review.equivalentCommand ? review.description : undefined, children: [review.equivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), policy.mode === 'compact' || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
3164
+ const showEquivalentCommand = Boolean(review.equivalentCommand
3165
+ && (!review.requiresConfirmation || showTechnical));
3166
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.requiresConfirmation ? undefined : review.subject.name, meta: review.requiresConfirmation ? undefined : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: "! Shared impact \u9700\u8981\u663E\u5F0F\u786E\u8BA4", meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
2741
3167
  }
2742
3168
  else if (route.name === 'progress') {
2743
3169
  const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
@@ -2746,8 +3172,8 @@ export function ConsoleAppV4(props) {
2746
3172
  const sourceOperation = snapshot.activeReview?.operationKind === 'sourceMutation';
2747
3173
  const issueOperation = snapshot.activeReview?.originIssue;
2748
3174
  breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
2749
- title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
2750
- description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
3175
+ title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存来源配置' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
3176
+ description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '只原子保存来源配置,不访问网络。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
2751
3177
  help = 'Ctrl-C 完成后退出';
2752
3178
  const progress = snapshot.progress;
2753
3179
  const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
@@ -2847,7 +3273,7 @@ export function ConsoleAppV4(props) {
2847
3273
  return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
2848
3274
  };
2849
3275
  const compactPortableProgress = policy.mode === 'compact' && showTechnical;
2850
- content = portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个 provider 提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置并刷新' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [visibleProgressTargets.map((entry) => {
3276
+ content = portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个 provider 提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [visibleProgressTargets.map((entry) => {
2851
3277
  const state = progressLabel(entry);
2852
3278
  const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
2853
3279
  const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
@@ -2877,8 +3303,6 @@ export function ConsoleAppV4(props) {
2877
3303
  ? operationKind : undefined;
2878
3304
  const successfulConfigOperation = configOperation !== undefined && !blocked && !partial;
2879
3305
  const sourceOperation = operationKind === 'sourceMutation';
2880
- const sourceRefreshWarning = sourceOperation && 'summary' in result
2881
- && result.summary?.messages?.some((message) => message.startsWith('警告:'));
2882
3306
  const issueOperation = result.originIssue;
2883
3307
  const completedTitle = {
2884
3308
  install: '✓ 插件添加完成',
@@ -2886,6 +3310,7 @@ export function ConsoleAppV4(props) {
2886
3310
  reinstall: '✓ 插件重新安装完成',
2887
3311
  uninstall: '✓ 插件移除完成',
2888
3312
  cleanup: '✓ 插件清理完成',
3313
+ sync: '✓ 插件同步完成',
2889
3314
  }[action ?? 'install'];
2890
3315
  title = portableOperation
2891
3316
  ? blocked ? '× 通用 Skills 安装被阻止'
@@ -2895,7 +3320,7 @@ export function ConsoleAppV4(props) {
2895
3320
  : issueOperation
2896
3321
  ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
2897
3322
  : sourceOperation
2898
- ? blocked ? '× 来源操作未完成' : sourceRefreshWarning ? '! 来源配置已保存,但刷新失败' : '✓ 来源操作完成'
3323
+ ? blocked ? '× 来源操作未完成' : '✓ 来源配置已保存'
2899
3324
  : configOperation
2900
3325
  ? blocked
2901
3326
  ? '× 设置未确认保存'
@@ -2926,11 +3351,9 @@ export function ConsoleAppV4(props) {
2926
3351
  : sourceOperation
2927
3352
  ? blocked
2928
3353
  ? result.writeMayHaveCompleted
2929
- ? '来源配置可能已保存。请重新检查来源列表;刷新失败不会撤销配置。'
3354
+ ? '来源配置可能已保存。请重新检查来源列表。'
2930
3355
  : '来源操作未执行。草稿已保留,可返回修改。'
2931
- : sourceRefreshWarning
2932
- ? '来源配置已保留。请根据警告修复来源后重试刷新。'
2933
- : '来源配置已保存,刷新已完成。'
3356
+ : '来源配置已保存。请在终端运行 aw catalog source sync,然后重新加载 TUI。'
2934
3357
  : blocked ? result.message
2935
3358
  : configOperation ? '设置层已经更新。' : '状态已刷新。';
2936
3359
  const pluginId = resultSummary?.pluginId
@@ -3212,7 +3635,9 @@ export function ConsoleAppV4(props) {
3212
3635
  try {
3213
3636
  const pending = action.run?.();
3214
3637
  if (pending)
3215
- void pending.catch(() => {
3638
+ void pending.catch((error) => {
3639
+ if (error instanceof Error && error.name === 'AbortError')
3640
+ return;
3216
3641
  setActionError('操作未完成。请重新检查后重试。');
3217
3642
  });
3218
3643
  }
@@ -3326,7 +3751,7 @@ export function ConsoleAppV4(props) {
3326
3751
  return;
3327
3752
  }
3328
3753
  if (key.backspace || key.delete)
3329
- setPortableAgentInput((value) => value.slice(0, -1));
3754
+ setPortableAgentInput(removeLastGrapheme);
3330
3755
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3331
3756
  setPortableAgentInput((value) => value + input);
3332
3757
  setInputError(undefined);
@@ -3413,7 +3838,7 @@ export function ConsoleAppV4(props) {
3413
3838
  return;
3414
3839
  }
3415
3840
  if (key.backspace || key.delete)
3416
- setSourceInput((value) => value.slice(0, -1));
3841
+ setSourceInput(removeLastGrapheme);
3417
3842
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3418
3843
  setSourceInput((value) => value + input);
3419
3844
  setSourceInputError(undefined);
@@ -3455,19 +3880,44 @@ export function ConsoleAppV4(props) {
3455
3880
  return;
3456
3881
  }
3457
3882
  if (key.backspace || key.delete)
3458
- setInputValue((value) => value.slice(0, -1));
3883
+ setInputValue(removeLastGrapheme);
3459
3884
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3460
3885
  setInputValue((value) => value + input);
3461
3886
  setInputError(undefined);
3462
3887
  return;
3463
3888
  }
3464
- if (searchMode && route.name === 'pluginList') {
3889
+ if (searchFocus && route.name === 'pluginList') {
3465
3890
  if (key.escape) {
3466
- setSearchMode(false);
3891
+ setSearchFocus(false);
3892
+ return;
3893
+ }
3894
+ if (key.return) {
3895
+ setSearchFocus(false);
3896
+ const row = pluginRows[focus];
3897
+ if (row)
3898
+ go(route.mode === 'browse'
3899
+ ? { name: 'pluginPreview', pluginId: row.pluginId }
3900
+ : { name: 'pluginDetail', pluginId: row.pluginId });
3901
+ return;
3902
+ }
3903
+ if (key.upArrow || key.downArrow) {
3904
+ setSearchFocus(false);
3905
+ setFocus((value) => moveIndex(pluginRows.length, value, key.upArrow ? -1 : 1));
3906
+ return;
3907
+ }
3908
+ if (key.leftArrow || key.rightArrow) {
3909
+ setSearchFocus(false);
3910
+ const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
3911
+ const current = route.sourceId ?? 'all';
3912
+ const direction = key.leftArrow ? -1 : 1;
3913
+ const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
3914
+ props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
3915
+ setFocus(0);
3916
+ setScrollOffset(0);
3467
3917
  return;
3468
3918
  }
3469
3919
  if (key.backspace || key.delete)
3470
- setQuery((value) => value.slice(0, -1));
3920
+ setQuery(removeLastGrapheme);
3471
3921
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3472
3922
  setQuery((value) => value + input);
3473
3923
  setFocus(0);
@@ -3492,7 +3942,7 @@ export function ConsoleAppV4(props) {
3492
3942
  setShowTechnical((value) => !value);
3493
3943
  }
3494
3944
  else if ((key.escape || input.toLowerCase() === 'b')
3495
- && route.name === 'configFieldEditor') {
3945
+ && (route.name === 'configFieldEditor' || route.name === 'configFieldAction')) {
3496
3946
  back();
3497
3947
  }
3498
3948
  else if (key.escape || input.toLowerCase() === 'b') {
@@ -3505,7 +3955,7 @@ export function ConsoleAppV4(props) {
3505
3955
  back();
3506
3956
  }
3507
3957
  else if (route.name === 'pluginList' && input === '/') {
3508
- setSearchMode(true);
3958
+ setSearchFocus(true);
3509
3959
  }
3510
3960
  else if (route.name === 'pluginList' && route.mode === 'browse' && input.toLowerCase() === 'f') {
3511
3961
  const selectedPluginId = pluginRows[focus]?.pluginId;
@@ -3543,12 +3993,13 @@ export function ConsoleAppV4(props) {
3543
3993
  setScrollOffset(0);
3544
3994
  }
3545
3995
  else if (input.toLowerCase() === 'r' && route.name === 'sourceDetail') {
3546
- void props.controller.refreshSource(route.sourceId);
3996
+ void props.controller.reloadCatalog({ sourceId: route.sourceId });
3547
3997
  }
3548
3998
  else if (input.toLowerCase() === 'r' && route.name === 'sourceList') {
3549
3999
  const source = props.controller.listSources()[focus];
3550
- if (source)
3551
- void props.controller.refreshSource(source.id);
4000
+ if (source) {
4001
+ void props.controller.reloadCatalog({ sourceId: source.id });
4002
+ }
3552
4003
  }
3553
4004
  else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
3554
4005
  const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
@@ -3590,7 +4041,7 @@ export function ConsoleAppV4(props) {
3590
4041
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
3591
4042
  ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3592
4043
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
3593
- : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
4044
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(route.name === 'dependencyPlacement' ? dependencyPlacementStaticRows : 0));
3594
4045
  setScrollOffset(viewport.offset);
3595
4046
  return next;
3596
4047
  });
@@ -3609,7 +4060,7 @@ export function ConsoleAppV4(props) {
3609
4060
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
3610
4061
  ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3611
4062
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
3612
- : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
4063
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(route.name === 'dependencyPlacement' ? dependencyPlacementStaticRows : 0));
3613
4064
  setScrollOffset(viewport.offset);
3614
4065
  return next;
3615
4066
  });