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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +4 -4
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/remove-plugin.mjs +1 -1
  10. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +34 -778
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  14. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  16. package/dist/scripts/bundle-plugins.d.ts +1 -0
  17. package/dist/scripts/bundle-plugins.js +2 -1
  18. package/dist/scripts/bundle-plugins.js.map +1 -1
  19. package/dist/scripts/plugin-staging.d.ts +5 -6
  20. package/dist/scripts/plugin-staging.js +12 -13
  21. package/dist/scripts/plugin-staging.js.map +1 -1
  22. package/dist/src/authoring/workspace-commands.d.ts +2 -1
  23. package/dist/src/authoring/workspace-commands.js.map +1 -1
  24. package/dist/src/commands/aw-capabilities.d.ts +2 -2
  25. package/dist/src/commands/aw-capabilities.js +6 -11
  26. package/dist/src/commands/aw-capabilities.js.map +1 -1
  27. package/dist/src/commands/catalog-dev-source.d.ts +18 -12
  28. package/dist/src/commands/catalog-dev-source.js +241 -305
  29. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  30. package/dist/src/commands/catalog-generation.d.ts +6 -19
  31. package/dist/src/commands/catalog-generation.js +10 -11
  32. package/dist/src/commands/catalog-generation.js.map +1 -1
  33. package/dist/src/commands/catalog-publication-inspection.d.ts +28 -27
  34. package/dist/src/commands/catalog-publication-inspection.js +139 -50
  35. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  36. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  37. package/dist/src/commands/catalog-publication-journal.js +147 -0
  38. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  39. package/dist/src/commands/catalog-publication-target.d.ts +2 -1
  40. package/dist/src/commands/catalog-publication-target.js +37 -2
  41. package/dist/src/commands/catalog-publication-target.js.map +1 -1
  42. package/dist/src/commands/catalog-publish.d.ts +51 -15
  43. package/dist/src/commands/catalog-publish.js +576 -219
  44. package/dist/src/commands/catalog-publish.js.map +1 -1
  45. package/dist/src/commands/catalog-source.d.ts +3 -28
  46. package/dist/src/commands/catalog-source.js +29 -140
  47. package/dist/src/commands/catalog-source.js.map +1 -1
  48. package/dist/src/commands/catalog-status.d.ts +6 -26
  49. package/dist/src/commands/catalog-status.js +8 -146
  50. package/dist/src/commands/catalog-status.js.map +1 -1
  51. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  52. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  53. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  54. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  55. package/dist/src/commands/dependency-operation-plan.js +331 -6
  56. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  57. package/dist/src/commands/init.d.ts +5 -11
  58. package/dist/src/commands/init.js +30 -51
  59. package/dist/src/commands/init.js.map +1 -1
  60. package/dist/src/commands/offline-cleanup-evidence.d.ts +20 -0
  61. package/dist/src/commands/offline-cleanup-evidence.js +87 -9
  62. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -1
  63. package/dist/src/commands/options.d.ts +6 -1
  64. package/dist/src/commands/options.js +64 -6
  65. package/dist/src/commands/options.js.map +1 -1
  66. package/dist/src/commands/plugin-capabilities.js +4 -0
  67. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  68. package/dist/src/commands/rebuild-command.d.ts +10 -8
  69. package/dist/src/commands/rebuild-command.js +1217 -426
  70. package/dist/src/commands/rebuild-command.js.map +1 -1
  71. package/dist/src/commands/rebuild-integration.d.ts +54 -6
  72. package/dist/src/commands/rebuild-integration.js +691 -364
  73. package/dist/src/commands/rebuild-integration.js.map +1 -1
  74. package/dist/src/commands/rebuild-operations.d.ts +2 -0
  75. package/dist/src/commands/rebuild-operations.js +31 -17
  76. package/dist/src/commands/rebuild-operations.js.map +1 -1
  77. package/dist/src/commands/skill-eval.d.ts +33 -0
  78. package/dist/src/commands/skill-eval.js +363 -0
  79. package/dist/src/commands/skill-eval.js.map +1 -0
  80. package/dist/src/commands/{status-v5.d.ts → status-v6.d.ts} +59 -26
  81. package/dist/src/commands/{status-v5.js → status-v6.js} +882 -79
  82. package/dist/src/commands/status-v6.js.map +1 -0
  83. package/dist/src/commands/sync-command.d.ts +21 -0
  84. package/dist/src/commands/sync-command.js +206 -0
  85. package/dist/src/commands/sync-command.js.map +1 -0
  86. package/dist/src/commands/wizard.d.ts +2 -2
  87. package/dist/src/commands/write-commands.js +1 -43
  88. package/dist/src/commands/write-commands.js.map +1 -1
  89. package/dist/src/constants.d.ts +5 -4
  90. package/dist/src/constants.js +5 -4
  91. package/dist/src/constants.js.map +1 -1
  92. package/dist/src/eval/manifest-v1.d.ts +66 -0
  93. package/dist/src/eval/manifest-v1.js +326 -0
  94. package/dist/src/eval/manifest-v1.js.map +1 -0
  95. package/dist/src/generators/local-plugin.d.ts +3 -0
  96. package/dist/src/generators/local-plugin.js +123 -55
  97. package/dist/src/generators/local-plugin.js.map +1 -1
  98. package/dist/src/index.d.ts +2 -0
  99. package/dist/src/index.js +412 -152
  100. package/dist/src/index.js.map +1 -1
  101. package/dist/src/output/command-receipt.d.ts +58 -8
  102. package/dist/src/output/command-receipt.js +141 -11
  103. package/dist/src/output/command-receipt.js.map +1 -1
  104. package/dist/src/output/model.d.ts +3 -11
  105. package/dist/src/portable/commands.d.ts +2 -2
  106. package/dist/src/portable/commands.js +2 -8
  107. package/dist/src/portable/commands.js.map +1 -1
  108. package/dist/src/portable/materializer.d.ts +1 -4
  109. package/dist/src/portable/materializer.js +0 -2
  110. package/dist/src/portable/materializer.js.map +1 -1
  111. package/dist/src/tui/catalog-generation.d.ts +20 -41
  112. package/dist/src/tui/catalog-generation.js +65 -224
  113. package/dist/src/tui/catalog-generation.js.map +1 -1
  114. package/dist/src/tui/components.js +1 -2
  115. package/dist/src/tui/components.js.map +1 -1
  116. package/dist/src/tui/console-app-v4.js +461 -330
  117. package/dist/src/tui/console-app-v4.js.map +1 -1
  118. package/dist/src/tui/controller-runtime.d.ts +3 -5
  119. package/dist/src/tui/controller-runtime.js +182 -187
  120. package/dist/src/tui/controller-runtime.js.map +1 -1
  121. package/dist/src/tui/controller.d.ts +42 -45
  122. package/dist/src/tui/controller.js +454 -407
  123. package/dist/src/tui/controller.js.map +1 -1
  124. package/dist/src/tui/diagnostics.d.ts +1 -2
  125. package/dist/src/tui/diagnostics.js.map +1 -1
  126. package/dist/src/tui/index.d.ts +1 -1
  127. package/dist/src/tui/index.js +1 -1
  128. package/dist/src/tui/index.js.map +1 -1
  129. package/dist/src/tui/model.d.ts +81 -13
  130. package/dist/src/tui/model.js +378 -95
  131. package/dist/src/tui/model.js.map +1 -1
  132. package/dist/src/tui/router.d.ts +9 -0
  133. package/dist/src/tui/router.js.map +1 -1
  134. package/dist/src/types.d.ts +54 -47
  135. package/dist/src/types.js.map +1 -1
  136. package/dist/src/utils/agent-version.d.ts +4 -1
  137. package/dist/src/utils/agent-version.js +28 -3
  138. package/dist/src/utils/agent-version.js.map +1 -1
  139. package/dist/src/utils/catalog-digests.d.ts +7 -0
  140. package/dist/src/utils/catalog-digests.js +27 -0
  141. package/dist/src/utils/catalog-digests.js.map +1 -1
  142. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  143. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  144. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-error.d.ts +2 -1
  146. package/dist/src/utils/catalog-generation-error.js +12 -0
  147. package/dist/src/utils/catalog-generation-error.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-resolver.d.ts +10 -3
  149. package/dist/src/utils/catalog-generation-resolver.js +90 -76
  150. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-service.d.ts +24 -33
  152. package/dist/src/utils/catalog-generation-service.js +253 -567
  153. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-state.d.ts +4 -0
  155. package/dist/src/utils/catalog-generation-state.js +73 -106
  156. package/dist/src/utils/catalog-generation-state.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +16 -26
  158. package/dist/src/utils/catalog-generation-store.js +154 -290
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  161. package/dist/src/utils/{catalog-manifest-v5.js → catalog-manifest-v6.js} +23 -35
  162. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  163. package/dist/src/utils/catalog-operation-context.d.ts +6 -14
  164. package/dist/src/utils/catalog-operation-context.js +68 -169
  165. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  166. package/dist/src/utils/catalog-public-error.js +1 -4
  167. package/dist/src/utils/catalog-public-error.js.map +1 -1
  168. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  169. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  170. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  171. package/dist/src/utils/catalog-snapshot.d.ts +2 -2
  172. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  173. package/dist/src/utils/catalog-source-references.js +44 -55
  174. package/dist/src/utils/catalog-source-references.js.map +1 -1
  175. package/dist/src/utils/catalog-sources.js +25 -9
  176. package/dist/src/utils/catalog-sources.js.map +1 -1
  177. package/dist/src/utils/codex-config.d.ts +2 -0
  178. package/dist/src/utils/codex-config.js +22 -1
  179. package/dist/src/utils/codex-config.js.map +1 -1
  180. package/dist/src/utils/codex-manager.d.ts +50 -2
  181. package/dist/src/utils/codex-manager.js +160 -6
  182. package/dist/src/utils/codex-manager.js.map +1 -1
  183. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  184. package/dist/src/utils/codex-reconciler.js +327 -0
  185. package/dist/src/utils/codex-reconciler.js.map +1 -0
  186. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  187. package/dist/src/utils/codex-shared-native.js +87 -0
  188. package/dist/src/utils/codex-shared-native.js.map +1 -0
  189. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  190. package/dist/src/utils/codex-surface-analysis.js +247 -0
  191. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  192. package/dist/src/utils/git-catalog-provider.js +56 -115
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/manifest.d.ts +1 -3
  195. package/dist/src/utils/manifest.js +1 -3
  196. package/dist/src/utils/manifest.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +1 -10
  198. package/dist/src/utils/multi-source-catalog.js +8 -175
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +34 -0
  201. package/dist/src/utils/native-cli.js +91 -0
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -0
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +0 -10
  206. package/dist/src/utils/ownership.js +3 -88
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  209. package/dist/src/utils/path-catalog-provider.js +45 -62
  210. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  211. package/dist/src/utils/paths.d.ts +0 -1
  212. package/dist/src/utils/paths.js +0 -1
  213. package/dist/src/utils/paths.js.map +1 -1
  214. package/dist/src/utils/plugin-config-write.js +0 -5
  215. package/dist/src/utils/plugin-config-write.js.map +1 -1
  216. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  217. package/dist/src/utils/plugin-intent-state.js +325 -0
  218. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  219. package/dist/src/utils/target-eligibility.d.ts +3 -2
  220. package/dist/src/utils/target-eligibility.js +4 -3
  221. package/dist/src/utils/target-eligibility.js.map +1 -1
  222. package/package.json +5 -4
  223. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  224. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  225. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  226. package/dist/scripts/codex-eval-app-server.d.ts +0 -91
  227. package/dist/scripts/codex-eval-app-server.js +0 -1334
  228. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  229. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  230. package/dist/scripts/codex-eval-isolation.js +0 -318
  231. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  232. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  233. package/dist/scripts/codex-eval-process-management.js +0 -69
  234. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  235. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  236. package/dist/scripts/codex-eval-process-tree.js +0 -803
  237. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  238. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  239. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  240. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  241. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  242. package/dist/scripts/codex-trigger-eval.js +0 -968
  243. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  244. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  245. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  246. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  247. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  248. package/dist/scripts/eval-plugin-skills.js +0 -1589
  249. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  250. package/dist/scripts/eval-run-layout.d.ts +0 -17
  251. package/dist/scripts/eval-run-layout.js +0 -26
  252. package/dist/scripts/eval-run-layout.js.map +0 -1
  253. package/dist/scripts/plugin-eval-contract.d.ts +0 -189
  254. package/dist/scripts/plugin-eval-contract.js +0 -745
  255. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  256. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  257. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  258. package/dist/scripts/skill-reporting.d.ts +0 -212
  259. package/dist/scripts/skill-reporting.js +0 -922
  260. package/dist/scripts/skill-reporting.js.map +0 -1
  261. package/dist/src/commands/catalog-context.d.ts +0 -52
  262. package/dist/src/commands/catalog-context.js +0 -156
  263. package/dist/src/commands/catalog-context.js.map +0 -1
  264. package/dist/src/commands/status-v5.js.map +0 -1
  265. package/dist/src/utils/catalog-manifest-v5.d.ts +0 -5
  266. package/dist/src/utils/catalog-manifest-v5.js.map +0 -1
  267. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  268. package/dist/src/utils/catalog-source-cache.js +0 -321
  269. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  270. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  271. package/dist/src/utils/codex-project-surface.js +0 -49
  272. package/dist/src/utils/codex-project-surface.js.map +0 -1
  273. package/dist/src/utils/plugin-release.d.ts +0 -2
  274. package/dist/src/utils/plugin-release.js +0 -2
  275. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -34,7 +34,11 @@ export const CONSOLE_V4_SCREEN_IDS = [
34
34
  function pluginMenuActionId(action) {
35
35
  return action.id === 'platform' || action.id === 'scopeConflict'
36
36
  ? `${action.id}:${action.platform}`
37
- : 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;
38
42
  }
39
43
  function isCharacterInputMode(route, searchMode) {
40
44
  return searchMode
@@ -122,11 +126,22 @@ function pluginPlacementSummary(plugin) {
122
126
  const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
123
127
  if (!placement || placement.state === 'notInstalled')
124
128
  return `${label} · 未添加`;
129
+ if (placement.state === 'blocked')
130
+ return `${label} · 无法添加 · 输出冲突`;
125
131
  if (placement.state === 'scopeConflict')
126
132
  return `${label} · 位置冲突`;
127
- const health = placement.health === 'ready' ? '可用'
128
- : placement.health === 'incomplete' ? '需要处理' : '不可用';
129
- 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}`;
130
145
  }).join(' | ') || '没有可用平台';
131
146
  }
132
147
  function issueGroupLabel(group) {
@@ -321,38 +336,38 @@ function configUnavailableDiagnostic(route) {
321
336
  },
322
337
  };
323
338
  }
324
- function sourceDiagnostic(sourceId, state, detail) {
325
- const degraded = state === 'degraded';
339
+ function sourceDiagnostic(sourceId, state, detail, generation) {
326
340
  const unavailable = state === 'unavailable' || state === 'error';
327
- if (!degraded && !unavailable)
341
+ if (!unavailable)
328
342
  return undefined;
343
+ const metadataInvalid = generation?.errorCode === 'catalogGenerationMetadataInvalid';
329
344
  return {
330
- severity: degraded ? 'warning' : 'error',
331
- title: degraded ? '远端 Catalog 检查失败,正在使用缓存' : 'Catalog 来源不可用',
332
- userMessage: degraded
333
- ? '远端读取没有成功。当前使用匹配且已验证的缓存数据。'
334
- : '当前无法读取这个来源,并且没有可用缓存数据。',
335
- impact: degraded
336
- ? [
337
- { state: 'degraded', message: '数据可能不是最新;匹配缓存中的插件仍可浏览和安装。' },
338
- { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
339
- ]
340
- : [
341
- { state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
342
- { state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
343
- ],
344
- nextStep: degraded
345
- ? '重新检查来源;在修复前也可以继续使用匹配缓存。'
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}。`
346
354
  : '重新检查来源;如仍失败,请编辑来源 URL、路径或 ref。',
347
355
  technical: {
348
- code: safeDiagnosticCode(detail?.errorCode, degraded ? 'catalogSourceDegraded' : 'catalogSourceUnavailable'),
356
+ code: safeDiagnosticCode(generation?.errorCode ?? detail?.errorCode, 'catalogSourceUnavailable'),
349
357
  stage: 'resolve-source',
350
358
  sourceId,
351
359
  ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
352
360
  ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
353
- lastGood: degraded ? 'available' : 'unavailable',
354
- ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
355
- 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 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。'],
356
371
  },
357
372
  };
358
373
  }
@@ -423,7 +438,7 @@ function SourceTabs(props) {
423
438
  const state = 'state' in tab ? tab.state : 'ready';
424
439
  return (_jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent
425
440
  : state === 'error' ? tuiTheme.statusColors.error
426
- : 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));
427
442
  })] }));
428
443
  }
429
444
  function configTabs(page) {
@@ -455,13 +470,22 @@ function placementStatus(placement) {
455
470
  if (placement.state === 'notInstalled') {
456
471
  return { value: '未添加', status: 'muted', description: '选择使用位置后添加' };
457
472
  }
473
+ if (placement.state === 'blocked') {
474
+ return { value: '无法添加', status: 'error', description: '输出冲突;不会覆盖或删除占用内容' };
475
+ }
458
476
  if (placement.state === 'scopeConflict') {
459
477
  return { value: '位置冲突', status: 'warning', description: '需要先处理两个安装位置' };
460
478
  }
479
+ if (placement.blockingReason === 'artifactCollision') {
480
+ return { value: '输出冲突', status: 'error', description: '只允许查看或安全移除受管内容' };
481
+ }
461
482
  return {
462
- value: `${scopeLabel(placement.scope)} · ${placement.health === 'ready' ? '可用' : placement.health === 'incomplete' ? '需要处理' : '不可用'}`,
463
- status: placement.health === 'ready' ? 'success' : placement.health === 'incomplete' ? 'warning' : 'error',
464
- 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' ? '当前检查正常' : '查看影响和建议操作',
465
489
  };
466
490
  }
467
491
  function dependencyStatus(item) {
@@ -475,9 +499,29 @@ function dependencyStatus(item) {
475
499
  }
476
500
  function dependencyPlacementLabel(item) {
477
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
+ }
478
522
  if (item.aggregateStatus !== 'ready')
479
523
  return aggregate.value;
480
- const ready = item.placements.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
524
+ const ready = installed.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
481
525
  const scopes = new Set(ready.map((entry) => entry.state === 'installed' ? entry.scope : undefined));
482
526
  return scopes.size === 1 && ready[0]?.state === 'installed'
483
527
  ? `${scopeLabel(ready[0].scope)} · ${aggregate.value}`
@@ -524,7 +568,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
524
568
  return 'G01';
525
569
  const route = snapshot.router.current.route;
526
570
  if (route.name === 'home')
527
- return controller.listCatalogCandidates().length > 0 ? 'K01' : 'G02';
571
+ return 'G02';
528
572
  if (route.name === 'pluginList') {
529
573
  const items = controller.listPlugins({ mode: route.mode, sourceId: route.sourceId });
530
574
  if (route.mode === 'installed' && items.length === 0)
@@ -567,6 +611,10 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
567
611
  return 'D16';
568
612
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D04' : 'P06';
569
613
  }
614
+ if (route.name === 'dependencyPlacement')
615
+ return 'D03';
616
+ if (route.name === 'dependencyPlacementEditor')
617
+ return 'D04';
570
618
  if (route.name === 'pluginDetail') {
571
619
  return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D09' : 'P13';
572
620
  }
@@ -582,9 +630,12 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
582
630
  return 'P16';
583
631
  }
584
632
  if (route.name === 'reviewPreparing')
585
- return snapshot.reviewPreparation?.intent.kind === 'catalogActivate' ? 'K03' : 'P07';
633
+ return 'P07';
634
+ if (route.name === 'review' && snapshot.activeReview?.originIssue)
635
+ return 'I10';
586
636
  const dependencyOperation = snapshot.activeReview?.operationKind === 'pluginWrite'
587
- && 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'));
588
639
  if (route.name === 'review' && dependencyOperation) {
589
640
  return snapshot.activeReview?.pluginAction === 'uninstall' || snapshot.activeReview?.pluginAction === 'cleanup'
590
641
  ? 'D11' : 'D05';
@@ -600,8 +651,6 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
600
651
  return snapshot.result.state === 'succeeded' ? 'D07' : 'D08';
601
652
  }
602
653
  }
603
- if (route.name === 'review' && snapshot.activeReview?.originIssue)
604
- return 'I10';
605
654
  if (route.name === 'progress' && snapshot.activeReview?.originIssue)
606
655
  return 'I11';
607
656
  if (route.name === 'result' && snapshot.result?.originIssue) {
@@ -617,30 +666,27 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
617
666
  return snapshot.result.state === 'succeeded' ? 'S12' : 'S13';
618
667
  }
619
668
  if (route.name === 'review')
620
- return snapshot.activeReview?.operationKind === 'catalogActivate' ? 'K03'
621
- : snapshot.activeReview?.operationKind === 'portableInstall' ? 'U06'
622
- : snapshot.activeReview?.operationKind === 'configSave'
623
- ? 'C12'
624
- : snapshot.activeReview?.operationKind === 'configReset'
625
- ? 'C16'
669
+ return snapshot.activeReview?.operationKind === 'portableInstall' ? 'U06'
670
+ : snapshot.activeReview?.operationKind === 'configSave'
671
+ ? 'C12'
672
+ : snapshot.activeReview?.operationKind === 'configReset'
673
+ ? 'C16'
674
+ : snapshot.activeReview?.operationKind === 'pluginWrite'
675
+ && (snapshot.activeReview.pluginAction === 'upgrade' || snapshot.activeReview.pluginAction === 'reinstall')
676
+ ? 'P18'
626
677
  : snapshot.activeReview?.operationKind === 'pluginWrite'
627
- && (snapshot.activeReview.pluginAction === 'upgrade' || snapshot.activeReview.pluginAction === 'reinstall')
628
- ? 'P18'
629
- : snapshot.activeReview?.operationKind === 'pluginWrite'
630
- && (snapshot.activeReview.pluginAction === 'uninstall' || snapshot.activeReview.pluginAction === 'cleanup')
631
- ? 'P19' : 'P07';
678
+ && (snapshot.activeReview.pluginAction === 'uninstall' || snapshot.activeReview.pluginAction === 'cleanup')
679
+ ? 'P19' : 'P07';
632
680
  if (route.name === 'progress')
633
- return snapshot.activeReview?.operationKind === 'catalogActivate' ? 'K03'
634
- : snapshot.activeReview?.operationKind === 'portableInstall' ? 'U07'
635
- : snapshot.activeReview?.operationKind === 'configSave'
636
- || snapshot.activeReview?.operationKind === 'configReset' ? 'C13' : 'P08';
681
+ return snapshot.activeReview?.operationKind === 'portableInstall' ? 'U07'
682
+ : snapshot.activeReview?.operationKind === 'configSave'
683
+ || snapshot.activeReview?.operationKind === 'configReset' ? 'C13' : 'P08';
637
684
  if (route.name === 'result')
638
- return snapshot.result?.operationKind === 'catalogActivate' ? 'K03'
639
- : snapshot.result?.operationKind === 'portableInstall' ? 'U08'
640
- : snapshot.result?.operationKind === 'configSave'
641
- || snapshot.result?.operationKind === 'configReset'
642
- ? snapshot.result.state === 'succeeded' ? 'C14' : 'C15'
643
- : snapshot.result?.state === 'partial' ? 'P10' : 'P09';
685
+ return snapshot.result?.operationKind === 'portableInstall' ? 'U08'
686
+ : snapshot.result?.operationKind === 'configSave'
687
+ || snapshot.result?.operationKind === 'configReset'
688
+ ? snapshot.result.state === 'succeeded' ? 'C14' : 'C15'
689
+ : snapshot.result?.state === 'partial' ? 'P10' : 'P09';
644
690
  if (route.name === 'pluginAbout')
645
691
  return 'P20';
646
692
  if (route.name === 'pluginTechnical')
@@ -659,18 +705,10 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
659
705
  return 'C17';
660
706
  if (route.name === 'sourceList')
661
707
  return 'S01';
662
- if (route.name === 'sourceDetail' || route.name === 'catalogCandidate')
708
+ if (route.name === 'sourceDetail')
663
709
  return 'K02';
664
710
  if (route.name === 'sourceTechnical')
665
711
  return 'S03';
666
- if (route.name === 'catalogRefreshPolicy')
667
- return 'K04';
668
- if (route.name === 'catalogRefresh')
669
- return 'K05';
670
- if (route.name === 'catalogImmutableViolation')
671
- return 'K06';
672
- if (route.name === 'catalogActivationBlocked')
673
- return 'K07';
674
712
  if (route.name === 'sourceAdd')
675
713
  return {
676
714
  type: 'S04', id: 'S05', location: 'S06', refPolicy: 'S07', ref: 'S08', subdir: 'S09',
@@ -688,7 +726,6 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
688
726
  return {
689
727
  incompleteInstallation: 'I03',
690
728
  artifactCollision: 'I09',
691
- sourceDegraded: 'I04',
692
729
  sourceUnavailable: 'I04',
693
730
  sourceMissing: 'I05',
694
731
  sourceIdentityChanged: 'I06',
@@ -729,7 +766,6 @@ export function ConsoleAppV4(props) {
729
766
  const [dirty, setDirty] = useState(false);
730
767
  const [configPage, setConfigPage] = useState();
731
768
  const [configLayerCounts, setConfigLayerCounts] = useState({});
732
- const [configLoadKey, setConfigLoadKey] = useState(0);
733
769
  const [draftUpdates, setDraftUpdates] = useState({});
734
770
  const [draftRemoves, setDraftRemoves] = useState([]);
735
771
  const [inputValue, setInputValue] = useState('');
@@ -745,6 +781,7 @@ export function ConsoleAppV4(props) {
745
781
  const [draftOwner, setDraftOwner] = useState();
746
782
  const [pendingPresetId, setPendingPresetId] = useState();
747
783
  const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
784
+ const [confirmedSharedReviewToken, setConfirmedSharedReviewToken] = useState();
748
785
  const dynamicMenuFocus = useRef(undefined);
749
786
  const exitSent = useRef(false);
750
787
  const operationBusy = useRef(false);
@@ -899,7 +936,7 @@ export function ConsoleAppV4(props) {
899
936
  }
900
937
  });
901
938
  return () => abort.abort();
902
- }, [configLoadKey, configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
939
+ }, [configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
903
940
  useEffect(() => {
904
941
  if (route.name !== 'configFields' || !currentConfigPage)
905
942
  return;
@@ -1000,13 +1037,6 @@ export function ConsoleAppV4(props) {
1000
1037
  props.onScreenChange?.(screenId);
1001
1038
  }, [props.onScreenChange, screenId]);
1002
1039
  const dependencyOverview = (candidate) => candidate ? props.controller.getPluginDependencyOverview(candidate.pluginId) : undefined;
1003
- const installIntent = (candidate, targets = selectedTargets) => ({
1004
- kind: 'pluginWrite',
1005
- action: 'install',
1006
- pluginId: candidate.pluginId,
1007
- platform: targets.length === 2 ? 'both' : targets[0],
1008
- scope,
1009
- });
1010
1040
  let title = '管理你的 Agent 工作流插件';
1011
1041
  let description = '为 Codex 和 Claude Code 添加、设置或移除开发能力。';
1012
1042
  let breadcrumb = '首页';
@@ -1044,6 +1074,7 @@ export function ConsoleAppV4(props) {
1044
1074
  compact: policy.mode === 'compact',
1045
1075
  staticRows,
1046
1076
  });
1077
+ const dependencyPlacementStaticRows = policy.mode === 'compact' ? 3 : 10;
1047
1078
  const responsiveBudget = (compactRows, standardRows) => {
1048
1079
  const baseline = policy.mode === 'compact' ? 18 : 24;
1049
1080
  const base = policy.mode === 'compact' ? compactRows : standardRows;
@@ -1074,8 +1105,6 @@ export function ConsoleAppV4(props) {
1074
1105
  setActionError(undefined);
1075
1106
  };
1076
1107
  const back = () => {
1077
- if (route.name === 'catalogRefresh')
1078
- props.controller.cancelCatalogRefresh(route.sourceId);
1079
1108
  if (route.name === 'result')
1080
1109
  props.controller.leaveResult();
1081
1110
  else
@@ -1114,6 +1143,18 @@ export function ConsoleAppV4(props) {
1114
1143
  setFocus(0);
1115
1144
  setScrollOffset(0);
1116
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
+ };
1117
1158
  const beginPortable = async (pluginId) => {
1118
1159
  try {
1119
1160
  await props.controller.preparePortableInstall(pluginId, route.name === 'portableBlocker' ? 'replace' : 'push');
@@ -1182,6 +1223,24 @@ export function ConsoleAppV4(props) {
1182
1223
  scope: selectedScope,
1183
1224
  });
1184
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
+ };
1185
1244
  const beginAddSource = () => {
1186
1245
  const owner = `source-add:${Date.now()}`;
1187
1246
  setSourceDraft({ owner, mode: 'add', sourceId: '', type: 'git', location: '', refPolicy: 'cliChannel', ref: '', subdir: '' });
@@ -1217,6 +1276,9 @@ export function ConsoleAppV4(props) {
1217
1276
  pluginId,
1218
1277
  platform: selectedPlatform,
1219
1278
  scope: selectedScope,
1279
+ ...(selectedIssue.category === 'scopeConflict' && action.description
1280
+ ? { recoveryDescription: action.description }
1281
+ : {}),
1220
1282
  originIssue: {
1221
1283
  issueId: selectedIssue.issueId,
1222
1284
  issueKey: userIssueKey(selectedIssue),
@@ -1529,27 +1591,14 @@ export function ConsoleAppV4(props) {
1529
1591
  }
1530
1592
  else if (route.name === 'home') {
1531
1593
  const home = snapshot.viewModel.home;
1532
- const candidates = props.controller.listCatalogCandidates();
1533
- const firstCandidate = candidates[0];
1534
- context = candidates.length > 0 ? 'Catalog 有更新' : '当前项目';
1594
+ context = '当前项目';
1535
1595
  actions = [
1536
1596
  { label: '添加插件', description: '浏览多个来源中的 Agent 能力', meta: String(home.availablePlugins), run: () => go({ name: 'pluginList', mode: 'browse' }) },
1537
- ...(firstCandidate ? [{
1538
- label: '查看 Catalog 更新…',
1539
- meta: candidates.length > 1 ? `${candidates.length} 个来源`
1540
- : firstCandidate.candidate.catalogVersion,
1541
- status: 'warning',
1542
- run: () => go({ name: 'catalogCandidate', sourceId: firstCandidate.sourceId }),
1543
- }] : []),
1544
1597
  { label: '我的插件', description: '查看状态、设置和移除已添加插件', meta: String(home.installedPlugins), run: () => go({ name: 'pluginList', mode: 'installed' }) },
1545
1598
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
1546
1599
  { label: '插件来源', description: '管理 Git 仓库和本地目录', meta: String(home.sourceCount), run: () => go({ name: 'sourceList' }) },
1547
1600
  ];
1548
- content = _jsxs(Box, { flexDirection: "column", children: [firstCandidate
1549
- ? policy.mode === 'compact'
1550
- ? _jsx(Text, { color: tuiTheme.colors.warning, wrap: "truncate-end", children: `${firstCandidate.sourceId} ${firstCandidate.active?.catalogVersion ?? '未加载'} → ${firstCandidate.candidate.catalogVersion} · 当前操作仍使用 ${props.controller.getSessionCatalogVersion(firstCandidate.sourceId) ?? firstCandidate.active?.catalogVersion ?? '已加载版本'}${candidates.length > 1 ? ` · 另有 ${candidates.length - 1} 个来源` : ''}` })
1551
- : _jsx(SectionCard, { title: `${firstCandidate.sourceId} ${firstCandidate.active?.catalogVersion ?? '未加载'} → ${firstCandidate.candidate.catalogVersion} · 当前操作仍使用 ${props.controller.getSessionCatalogVersion(firstCandidate.sourceId) ?? firstCandidate.active?.catalogVersion ?? '已加载版本'}`, meta: candidates.length > 1 ? `另有 ${candidates.length - 1} 个来源` : undefined, status: "warning" })
1552
- : null, _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' })] });
1553
1602
  }
1554
1603
  else if (route.name === 'pluginList') {
1555
1604
  const sourceId = route.sourceId ?? 'all';
@@ -1572,7 +1621,7 @@ export function ConsoleAppV4(props) {
1572
1621
  help = `↑↓ 移动 · Enter 查看 · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
1573
1622
  const sourceError = sourceId !== 'all' && props.controller.getSourceOverview(sourceId).source?.state === 'error';
1574
1623
  actions = sourceError
1575
- ? [{ label: '查看来源问题', description: '没有可用缓存;当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
1624
+ ? [{ label: '查看来源问题', description: '当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
1576
1625
  : [];
1577
1626
  pluginRows = sourceError ? [] : items.map((item) => ({
1578
1627
  pluginId: item.pluginId,
@@ -1616,7 +1665,7 @@ export function ConsoleAppV4(props) {
1616
1665
  : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), route.mode === 'browse'
1617
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"] })
1618
1667
  : null, sourceError
1619
- ? _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" })
1620
1669
  : route.mode === 'installed' && items.length === 0
1621
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" })
1622
1671
  : null, pluginRows.length > 0
@@ -1639,6 +1688,7 @@ export function ConsoleAppV4(props) {
1639
1688
  setSelectedTargets([]);
1640
1689
  setTargetSelections({});
1641
1690
  setPreferredTarget(undefined);
1691
+ props.controller.beginInstallFlow(plugin.pluginId, initialScope);
1642
1692
  if (available.length > 0)
1643
1693
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
1644
1694
  else
@@ -1664,6 +1714,19 @@ export function ConsoleAppV4(props) {
1664
1714
  return { label: '查看插件设置', description: `${plugin.configFieldCount} 个配置字段及当前来源`, run: () => beginConfigFlow(plugin.pluginId) };
1665
1715
  if (action.id === 'sourceProblem')
1666
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 }) };
1667
1730
  return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
1668
1731
  };
1669
1732
  const previewMenu = dynamicMenu;
@@ -1906,8 +1969,28 @@ export function ConsoleAppV4(props) {
1906
1969
  breadcrumb = `${plugin.displayName} / 依赖关系`;
1907
1970
  context = overview ? `${overview.summary.totalCount + 1} 个插件` : '只读';
1908
1971
  title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
1909
- description = '依赖从同一个来源快照解析;关系页不会修改安装。';
1972
+ description = '依赖由同一来源快照解析;调整前会进入 Review。';
1973
+ const installedTargets = plugin.installedPlacements.filter((placement) => placement.state === 'installed');
1910
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
+ }] : []),
1911
1994
  { label: '技术详情', description: '显示 canonical pluginId 和安全错误代码', run: () => setShowTechnical((value) => !value) },
1912
1995
  ];
1913
1996
  const rootStatus = plugin.aggregateStatus === 'installed' ? 'success'
@@ -1959,23 +2042,19 @@ export function ConsoleAppV4(props) {
1959
2042
  breadcrumb = `${plugin.displayName} / 添加`;
1960
2043
  context = '第 1 步,共 2 步';
1961
2044
  title = `你希望在哪里使用 ${plugin.displayName}?`;
1962
- const overview = dependencyOverview(plugin);
1963
2045
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
1964
2046
  description = hasDependencies
1965
- ? '根插件和缺失依赖使用同一个位置;健康的所有项目依赖可在当前项目复用。'
2047
+ ? '这里只选择根插件位置。依赖可以在下一步单独调整;依赖不能比根插件位置更窄。'
1966
2048
  : '选择位置后,再选择一个或多个目标平台。';
1967
2049
  const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
1968
2050
  .some((entry) => entry.compatible));
1969
- const reusableDependencies = overview?.dependencies.filter((dependency) => dependency.placements.some((placement) => placement.state === 'installed' && placement.health === 'ready'
1970
- && (scope === 'project' || placement.scope === 'user'))).length ?? 0;
1971
- const missingDependencies = Math.max(0, (overview?.summary.totalCount ?? 0) - reusableDependencies);
1972
2051
  actions = [
1973
2052
  ...availableScopes.map((candidateScope) => ({
1974
2053
  label: scopeLabel(candidateScope),
1975
2054
  selected: candidateScope === scope,
1976
2055
  description: candidateScope === 'user'
1977
- ? hasDependencies ? '根插件和缺失依赖都可在任意项目中使用' : '在任意项目中可使用,适合个人常用能力'
1978
- : hasDependencies ? '缺失依赖添加到当前项目;已有所有项目依赖会复用' : '仅在当前项目中使用,适合团队共享',
2056
+ ? hasDependencies ? '根插件在任意项目中可用;依赖位置下一步确认' : '在任意项目中可使用,适合个人常用能力'
2057
+ : hasDependencies ? '根插件只在当前项目使用;依赖可单独调整为所有项目' : '仅在当前项目中使用,适合团队共享',
1979
2058
  meta: candidateScope === 'user' ? '推荐' : undefined,
1980
2059
  run: () => {
1981
2060
  const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
@@ -1989,6 +2068,7 @@ export function ConsoleAppV4(props) {
1989
2068
  setScope(candidateScope);
1990
2069
  setSelectedTargets(next);
1991
2070
  setTargetSelections((current) => ({ ...current, [candidateScope]: next }));
2071
+ props.controller.setInstallRootScope(candidateScope);
1992
2072
  go({
1993
2073
  name: 'installTargets',
1994
2074
  pluginId: plugin.pluginId,
@@ -1997,7 +2077,7 @@ export function ConsoleAppV4(props) {
1997
2077
  },
1998
2078
  })),
1999
2079
  ];
2000
- 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] });
2001
2081
  }
2002
2082
  else if (route.name === 'installTargets' && plugin) {
2003
2083
  breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
@@ -2005,8 +2085,8 @@ export function ConsoleAppV4(props) {
2005
2085
  title = '选择目标平台';
2006
2086
  const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
2007
2087
  description = hasDependencies
2008
- ? '只有根插件和所有依赖都支持所选位置的目标平台才可选择。'
2009
- : '同一插件、同一平台只能安装到一个位置。';
2088
+ ? '根插件必须支持所选位置;每个依赖必须至少有一个合法位置。'
2089
+ : '目标平台按所选位置验证;仅含 Codex native 的插件可同时启用两个位置。';
2010
2090
  const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
2011
2091
  const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
2012
2092
  const allUnavailable = choices.length > 0 && choices.every((choice) => !choice.compatible);
@@ -2035,6 +2115,7 @@ export function ConsoleAppV4(props) {
2035
2115
  ? current.filter((platform) => platform !== choice.platform)
2036
2116
  : [...current, choice.platform];
2037
2117
  setTargetSelections((selections) => ({ ...selections, [scope]: next }));
2118
+ props.controller.setInstallPlatforms(next);
2038
2119
  return next;
2039
2120
  });
2040
2121
  },
@@ -2047,7 +2128,15 @@ export function ConsoleAppV4(props) {
2047
2128
  disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
2048
2129
  run: () => {
2049
2130
  setDirty(false);
2050
- 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);
2051
2140
  },
2052
2141
  },
2053
2142
  { label: '返回选择位置', run: back },
@@ -2056,6 +2145,83 @@ export function ConsoleAppV4(props) {
2056
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 })] });
2057
2146
  }
2058
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
+ }
2059
2225
  else if (route.name === 'pluginDetail' && plugin) {
2060
2226
  breadcrumb = `我的插件 / ${plugin.displayName}`;
2061
2227
  context = `${plugin.sourceLabel}${plugin.version ? ` · ${plugin.version}` : ''}`;
@@ -2064,6 +2230,13 @@ export function ConsoleAppV4(props) {
2064
2230
  const dependencySummary = plugin.dependencySummary;
2065
2231
  const detailMenu = dynamicMenu;
2066
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
+ };
2067
2240
  if (action.id === 'platform' || action.id === 'scopeConflict') {
2068
2241
  const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
2069
2242
  const projected = placementStatus(placement);
@@ -2109,14 +2282,20 @@ export function ConsoleAppV4(props) {
2109
2282
  && candidate.platform === route.platform
2110
2283
  && candidate.scope === placement.scope)
2111
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;
2112
2291
  const sourceIssue = plugin.aggregateStatus === 'unavailable'
2113
2292
  ? props.controller.listUserIssues().find((candidate) => candidate.entity.kind === 'source' && candidate.sourceId === plugin.sourceId)
2114
2293
  : undefined;
2115
2294
  const platformAction = (action) => {
2116
2295
  if (action.id === 'sourceProblem')
2117
- 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` }) };
2118
2297
  if (action.id === 'refreshSource')
2119
- 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 }); } };
2120
2299
  if (action.id === 'add')
2121
2300
  return { label: `添加到 ${platformLabel(route.platform)}…`, description: '选择使用位置', run: () => {
2122
2301
  const initialScope = placement?.state === 'notInstalled'
@@ -2126,14 +2305,50 @@ export function ConsoleAppV4(props) {
2126
2305
  setSelectedTargets([]);
2127
2306
  setTargetSelections({});
2128
2307
  setPreferredTarget(route.platform);
2308
+ props.controller.beginInstallFlow(plugin.pluginId, initialScope);
2129
2309
  go({ name: 'installPlacement', pluginId: plugin.pluginId });
2130
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
+ };
2131
2317
  if (action.id === 'about')
2132
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
+ };
2133
2330
  if (action.id === 'scopeConflict')
2134
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
+ };
2135
2339
  if (action.id === 'upgrade')
2136
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
+ };
2137
2352
  if (action.id === 'reinstall')
2138
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 }) };
2139
2354
  if (action.id === 'remove')
@@ -2145,6 +2360,7 @@ export function ConsoleAppV4(props) {
2145
2360
  return { label: '返回插件', run: back };
2146
2361
  };
2147
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;
2148
2364
  if (plugin.aggregateStatus === 'unavailable') {
2149
2365
  const sourceOverview = props.controller.getSourceOverview(plugin.sourceId);
2150
2366
  const diagnostic = sourceIssue?.diagnostic ?? sourceDiagnostic(plugin.sourceId, sourceOverview.health?.state ?? sourceOverview.source?.state, sourceOverview.detail);
@@ -2161,6 +2377,15 @@ export function ConsoleAppV4(props) {
2161
2377
  actions = platformMenu.map(platformAction);
2162
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' })] });
2163
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
+ }
2164
2389
  else if (placement.state === 'scopeConflict') {
2165
2390
  title = '安装位置冲突';
2166
2391
  description = '正常状态只能保留一个位置。';
@@ -2196,14 +2421,17 @@ export function ConsoleAppV4(props) {
2196
2421
  title = `${platformLabel(route.platform)} ${placement.health === 'incomplete' ? '安装不完整' : '暂时不可用'}`;
2197
2422
  description = '缺少一项必要内容,插件可能无法稳定启动。';
2198
2423
  actions = platformMenu.map(platformAction);
2199
- 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' })] });
2200
2428
  }
2201
2429
  else {
2202
2430
  title = `已安装到${scopeLabel(placement.scope)}`;
2203
2431
  const installedVersion = placement.revision.installedVersion;
2204
2432
  description = `当前检查正常${installedVersion ? `;已安装版本 ${installedVersion}` : ''}。`;
2205
2433
  actions = platformMenu.map(platformAction);
2206
- 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' })] });
2207
2435
  }
2208
2436
  }
2209
2437
  else if (route.name === 'configLayer' && plugin) {
@@ -2401,11 +2629,7 @@ export function ConsoleAppV4(props) {
2401
2629
  title = diagnostic.title;
2402
2630
  description = '';
2403
2631
  actions = [
2404
- { label: '重新检查来源', description: '刷新当前来源后重新读取可信定义', run: async () => {
2405
- await props.controller.refreshSource(plugin.sourceId);
2406
- setConfigLoadKey((value) => value + 1);
2407
- props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: route.layer ?? { scope: 'user', projectLayer: 'shared' } });
2408
- } },
2632
+ { label: `在终端运行 aw catalog source sync ${plugin.sourceId}`, description: '同步成功后重新加载 TUI', run: back },
2409
2633
  { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => setShowTechnical((value) => !value) },
2410
2634
  { label: '返回', run: back },
2411
2635
  ];
@@ -2416,29 +2640,21 @@ export function ConsoleAppV4(props) {
2416
2640
  title = '插件来源';
2417
2641
  description = '单个来源失败不会阻止其他来源继续工作。';
2418
2642
  context = `${props.controller.listSources().length} 个来源`;
2419
- help = '↑↓ 移动 · Enter 打开 · r 重新检查 · Esc 返回';
2643
+ help = '↑↓ 移动 · Enter 打开 · Esc 返回';
2420
2644
  actions = [
2421
2645
  ...props.controller.listSources().map((source) => {
2422
2646
  const overview = props.controller.getSourceOverview(source.id);
2423
2647
  const generation = props.controller.getCatalogGeneration(source.id);
2424
2648
  const health = overview.health?.state ?? source.state;
2425
- const status = generation?.refreshState === 'checking'
2426
- || generation?.refreshState === 'downloading'
2427
- || generation?.refreshState === 'validating' ? '正在检查'
2428
- : generation?.refreshState === 'immutableViolation' ? '发布异常'
2429
- : generation?.candidate ? `可用更新 ${generation.candidate.catalogVersion}`
2430
- : health === 'ready' ? '已从远端加载'
2431
- : health === 'path' ? '本地目录'
2432
- : health === 'checking' ? '正在检查'
2433
- : health === 'cache' || health === 'degraded' ? '使用缓存' : '无法读取';
2649
+ const status = generation?.active ? `Active ${generation.active.catalogVersion}`
2650
+ : health === 'ready' ? '当前使用'
2651
+ : '无法读取';
2434
2652
  return {
2435
2653
  label: `${source.id}${source.primary ? ' 主来源' : ''}`,
2436
2654
  description: `${overview.detail?.type === 'path' ? '本地目录' : 'Git 仓库'} · ${source.count ?? 0} 个插件`,
2437
2655
  meta: status,
2438
- status: generation?.refreshState === 'immutableViolation' || health === 'unavailable' ? 'error'
2439
- : generation?.candidate ? 'warning'
2440
- : health === 'cache' || health === 'degraded' ? 'warning'
2441
- : source.primary ? 'success' : undefined,
2656
+ status: !generation?.active || health === 'unavailable' ? 'error'
2657
+ : source.primary ? 'success' : undefined,
2442
2658
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
2443
2659
  };
2444
2660
  }),
@@ -2451,155 +2667,35 @@ export function ConsoleAppV4(props) {
2451
2667
  else if (route.name === 'sourceDetail') {
2452
2668
  const overview = props.controller.getSourceOverview(route.sourceId);
2453
2669
  const detail = overview.detail;
2454
- const state = overview.health?.state ?? overview.source?.state;
2455
- const diagnostic = sourceDiagnostic(route.sourceId, state, detail);
2456
2670
  const generation = props.controller.getCatalogGeneration(route.sourceId);
2457
- const sessionCatalogVersion = props.controller.getSessionCatalogVersion(route.sourceId)
2458
- ?? generation?.active?.catalogVersion;
2671
+ const activeVersion = generation?.active?.catalogVersion;
2672
+ const metadataInvalid = generation?.errorCode === 'catalogGenerationMetadataInvalid';
2673
+ const damagedEntry = generation?.recoveryEntryPath;
2459
2674
  breadcrumb = `插件来源 / ${route.sourceId}`;
2460
- context = detail?.type === 'path' ? '本地目录'
2461
- : detail?.freshness === 'remote' ? 'Git · 已连接'
2462
- : detail?.freshness === 'cache' ? 'Git · 使用缓存' : 'Git';
2675
+ context = detail?.type === 'path' ? '本地目录' : 'Git';
2463
2676
  title = route.sourceId;
2464
- description = diagnostic || generation ? '' : '来源状态可用。';
2465
- help = '↑↓ 移动 · Enter 选择 · r 重新检查 · Esc 返回';
2466
- const openRefresh = () => {
2467
- go({ name: 'catalogRefresh', sourceId: route.sourceId });
2468
- void props.controller.refreshSource(route.sourceId);
2469
- };
2470
- const generationActions = [
2471
- ...(generation?.candidate ? [{
2472
- label: `使用 ${generation.candidate.catalogVersion}…`,
2473
- description: '先查看影响;不会自动升级已安装插件',
2474
- run: () => go({ name: 'catalogCandidate', sourceId: route.sourceId }),
2475
- }] : []),
2476
- ...(generation?.refreshState === 'immutableViolation' ? [{
2477
- label: '查看发布异常',
2478
- description: '旧 Active 已安全保留',
2479
- status: 'error',
2480
- run: () => go({ name: 'catalogImmutableViolation', sourceId: route.sourceId }),
2481
- }] : []),
2482
- ];
2483
- actions = diagnostic
2484
- ? [
2485
- ...generationActions,
2486
- { label: '重新检查来源', description: '读取远端并验证可用 generation', run: openRefresh },
2487
- ...(state === 'degraded' ? [{
2488
- label: '继续使用缓存',
2489
- description: '浏览匹配且已验证的缓存数据',
2490
- run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
2491
- }] : []),
2492
- { label: '编辑来源…', description: '检查 URL、路径或 ref', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2493
- { 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 }] }) },
2494
- { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2495
- { label: '技术详情', description: '查看脱敏错误代码和解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2496
- ]
2497
- : [
2498
- ...generationActions,
2499
- { label: '重新检查来源', description: detail?.type === 'path' ? '显式读取本地目录' : '读取远端并准备 Candidate', run: openRefresh },
2500
- ...(generation?.sourceType === 'git' ? [{
2501
- label: '自动刷新',
2502
- description: generation.refreshPolicy === 'startup' ? '启动时自动检查' : '仅手动检查',
2503
- meta: generation.refreshPolicy === 'startup' ? '开启' : '关闭',
2504
- run: () => go({ name: 'catalogRefreshPolicy', sourceId: route.sourceId }),
2505
- }] : []),
2506
- { label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2507
- { 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 }] }) },
2508
- { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2509
- { label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2510
- ];
2511
- const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
2512
- content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
2513
- ? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
2514
- : _jsxs(_Fragment, { children: [sessionCatalogVersion ? _jsx(SectionCard, { title: "\u5F53\u524D\u4F7F\u7528", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: `Catalog ${sessionCatalogVersion}`, meta: `${generation?.active?.channel === 'beta' ? 'Beta' : 'Release'} · 本次会话固定使用`, compact: true }) }) : null, generation?.candidate ? _jsx(SectionCard, { title: "\u53EF\u7528\u66F4\u65B0", status: "warning", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: `Catalog ${generation.candidate.catalogVersion}`, meta: `已下载并验证 · +${generation.candidate.pluginChanges.added} ~${generation.candidate.pluginChanges.updated} −${generation.candidate.pluginChanges.removed}`, compact: true }) }) : null, policy.mode === 'compact' ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录', compact: true }), _jsx(StaticRow, { title: "\u68C0\u67E5", meta: `${generation?.sourceType === 'git' && generation.refreshPolicy === 'startup' ? '启动时' : '仅手动'} · ${generation?.lastCheckedAt ? '已有记录' : '尚未检查'}`, compact: true })] }) : _jsxs(SectionCard, { title: "\u6765\u6E90\u4E0E\u68C0\u67E5", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录' }), _jsx(StaticRow, { title: "\u81EA\u52A8\u5237\u65B0", meta: generation?.sourceType === 'git' ? generation.refreshPolicy === 'startup' ? '启动时检查' : '仅手动' : '仅手动' }), _jsx(StaticRow, { title: "\u4E0A\u6B21\u68C0\u67E5", meta: generation?.lastCheckedAt ?? '尚未检查' })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
2515
- }
2516
- else if (route.name === 'catalogCandidate') {
2517
- const generation = props.controller.getCatalogGeneration(route.sourceId);
2518
- const candidate = generation?.candidate;
2519
- breadcrumb = `插件来源 / ${route.sourceId} / Catalog 更新`;
2520
- context = '已下载并验证';
2521
- title = candidate ? `使用 ${route.sourceId} Catalog ${candidate.catalogVersion}` : 'Catalog 更新不再可用';
2522
- description = '切换 generation;不会自动升级已安装插件,也不会修改设置。';
2523
- actions = candidate ? [
2524
- { label: `使用 ${candidate.catalogVersion}…`, description: '进入激活 Review', run: () => {
2525
- props.controller.beginCatalogActivation({
2526
- sourceId: route.sourceId,
2527
- ...(dirty ? { dirtyDraft: { kind: 'config', label: '当前设置草稿' } }
2528
- : sourceDraft ? { dirtyDraft: { kind: 'source', label: '当前来源草稿' } } : {}),
2529
- });
2530
- setFocus(0);
2531
- setScrollOffset(0);
2532
- } },
2533
- { label: `继续使用 ${generation?.active?.catalogVersion ?? '当前版本'}`, run: back },
2534
- ] : [{ label: '返回来源', run: back }];
2535
- content = candidate ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "Catalog \u53D8\u5316", children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: `${generation?.active?.catalogVersion ?? '未加载'} → ${candidate.catalogVersion}` }), _jsx(StaticRow, { title: "\u901A\u9053 / \u5F15\u7528", meta: `${candidate.channel === 'beta' ? 'Beta' : 'Release'} · ${candidate.requestedRef}` }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u53D8\u5316", meta: `+${candidate.pluginChanges.added} ~${candidate.pluginChanges.updated} −${candidate.pluginChanges.removed}` })] }), _jsxs(SectionCard, { title: "\u6FC0\u6D3B\u540E", children: [_jsx(StaticRow, { title: "\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u5217\u8868\u548C\u4F9D\u8D56\u5173\u7CFB", status: "warning" }), _jsx(StaticRow, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7", status: "success" }), _jsx(StaticRow, { title: "\u5F53\u524D\u8BBE\u7F6E\u4E0D\u4F1A\u6539\u53D8", status: "success" })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] }) : _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
2536
- }
2537
- else if (route.name === 'catalogRefreshPolicy') {
2538
- const generation = props.controller.getCatalogGeneration(route.sourceId);
2539
- breadcrumb = `插件来源 / ${route.sourceId} / 自动刷新`;
2540
- title = 'Catalog 自动刷新';
2541
- description = `决定何时检查 ${route.sourceId} 的远端更新。更新不会自动激活。`;
2542
- actions = [
2543
- { label: '启动时自动检查', description: '首屏出现后在后台检查;不会自动切换版本', selected: generation?.refreshPolicy === 'startup', run: async () => props.controller.setCatalogRefreshPolicy(route.sourceId, 'startup') },
2544
- { label: '仅手动检查', description: '只有选择“重新检查来源”时访问远端', selected: generation?.refreshPolicy === 'manual', run: async () => props.controller.setCatalogRefreshPolicy(route.sourceId, 'manual') },
2545
- ];
2546
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2547
- }
2548
- else if (route.name === 'catalogRefresh') {
2549
- const generation = props.controller.getCatalogGeneration(route.sourceId);
2550
- const state = generation?.refreshState ?? 'idle';
2551
- const activeVersion = props.controller.getSessionCatalogVersion(route.sourceId)
2552
- ?? generation?.active?.catalogVersion ?? '已加载版本';
2553
- const stage = state === 'checking' || state === 'downloading' ? 1
2554
- : state === 'validating' ? 2 : 3;
2555
- const stageState = (index) => state === 'failed' || state === 'immutableViolation'
2556
- ? index === stage ? 'error' : index < stage ? 'complete' : 'pending'
2557
- : index < stage ? 'complete' : index === stage && stage < 3 ? 'active' : index <= stage ? 'complete' : 'pending';
2558
- breadcrumb = `插件来源 / ${route.sourceId} / 检查更新`;
2559
- context = '只读';
2560
- title = state === 'candidateReady' ? 'Catalog 更新已准备好'
2561
- : state === 'upToDate' ? 'Catalog 已是最新'
2562
- : state === 'immutableViolation' ? 'Catalog 发布异常'
2563
- : state === 'failed' ? 'Catalog 检查失败' : '正在检查 Catalog 更新';
2564
- description = `当前操作继续使用 Catalog ${activeVersion}。`;
2565
- help = state === 'checking' || state === 'downloading' || state === 'validating'
2566
- ? 'Esc 取消检查' : 'Enter 返回 · Esc 返回';
2567
- actions = state === 'immutableViolation'
2568
- ? [{ label: '查看发布异常', run: () => props.controller.replace({ name: 'catalogImmutableViolation', sourceId: route.sourceId }) }, { label: '返回来源', run: back }]
2569
- : state === 'checking' || state === 'downloading' || state === 'validating'
2570
- ? [] : [{ label: '返回来源', run: back }];
2571
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u68C0\u67E5\u6B65\u9AA4", children: [_jsx(ProgressRow, { title: "\u8BFB\u53D6\u5F53\u524D Active generation", state: "complete", compact: true }), _jsx(ProgressRow, { title: "\u68C0\u67E5\u5E76\u83B7\u53D6\u8FDC\u7AEF generation", state: stageState(1), compact: true }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1 Catalog", state: stageState(2), compact: true }), _jsx(ProgressRow, { title: "\u51C6\u5907 Candidate", state: stageState(3), compact: true })] }), actions.length > 0 ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }) : null] });
2572
- }
2573
- else if (route.name === 'catalogImmutableViolation') {
2574
- const generation = props.controller.getCatalogGeneration(route.sourceId);
2575
- breadcrumb = `插件来源 / ${route.sourceId} / 发布异常`;
2576
- context = '发布异常';
2577
- title = 'Catalog 版本内容发生变化';
2578
- description = `远端重新发布了已经存在的 Catalog ${generation?.active?.catalogVersion ?? '版本'}。`;
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 返回';
2579
2682
  actions = [
2580
- { label: '重新检查', description: '不可变性规则仍会生效', run: () => { props.controller.replace({ name: 'catalogRefresh', sourceId: route.sourceId }); void props.controller.refreshSource(route.sourceId); } },
2581
- { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
2582
- { label: '返回来源', run: back },
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 }) },
2583
2690
  ];
2584
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5F53\u524D\u5904\u7406", children: [_jsx(StaticRow, { title: `继续使用本地已验证的 ${generation?.active?.catalogVersion ?? 'Active'}`, status: "success" }), _jsx(StaticRow, { title: "\u4E0D\u52A0\u8F7D\u8FDC\u7AEF\u53D8\u5316", status: "error" }), _jsx(StaticRow, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u548C\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8", status: "success" })] }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "code", meta: generation?.lastCheckCode ?? 'catalogImmutableVersionViolation' }), _jsx(StaticRow, { title: "source", meta: route.sourceId }), _jsx(StaticRow, { title: "digest", meta: generation?.active?.digestShort ?? '不可用' })] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
2585
- }
2586
- else if (route.name === 'catalogActivationBlocked') {
2587
- const generation = props.controller.getCatalogGeneration(route.sourceId);
2588
- const reason = {
2589
- configDraft: '当前有未完成的设置修改。', sourceDraft: '当前有未完成的来源修改。', portableDraft: '当前有未完成的 Portable Skills 选择。', activeReview: '当前有待确认的 Review。', reviewPreparation: '当前正在准备 Review。', writeProgress: '当前写操作尚未完成。', sourceMutationProgress: '当前来源修改尚未完成。', refreshProgress: '当前 Catalog 来源刷新尚未完成。',
2590
- }[route.reason];
2591
- breadcrumb = `插件来源 / ${route.sourceId} / 使用更新`;
2592
- context = '暂不可用';
2593
- title = '暂时不能切换 Catalog';
2594
- description = reason;
2595
- actions = [{ label: '返回完成或取消当前操作', run: back }, { label: `继续使用 ${props.controller.getSessionCatalogVersion(route.sourceId) ?? generation?.active?.catalogVersion ?? '当前版本'}`, run: back }];
2596
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u4FDD\u7559\u7684\u5DE5\u4F5C", children: [_jsx(StaticRow, { title: route.preservedLabel ?? reason, status: "success" }), _jsx(StaticRow, { title: `Candidate ${generation?.candidate?.catalogVersion ?? '更新'} 保持已下载状态`, status: "success" })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
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 })] });
2597
2693
  }
2598
2694
  else if (route.name === 'sourceTechnical') {
2599
2695
  const overview = props.controller.getSourceOverview(route.sourceId);
2600
2696
  const detail = overview.detail;
2601
2697
  const state = overview.health?.state ?? overview.source?.state;
2602
- const diagnostic = sourceDiagnostic(route.sourceId, state, detail) ?? {
2698
+ const diagnostic = sourceDiagnostic(route.sourceId, state, detail, props.controller.getCatalogGeneration(route.sourceId)) ?? {
2603
2699
  severity: 'warning',
2604
2700
  title: '来源解析详情',
2605
2701
  userMessage: '来源当前可用。',
@@ -2611,7 +2707,6 @@ export function ConsoleAppV4(props) {
2611
2707
  sourceId: route.sourceId,
2612
2708
  ...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
2613
2709
  ...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
2614
- ...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
2615
2710
  },
2616
2711
  };
2617
2712
  breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
@@ -2737,10 +2832,10 @@ export function ConsoleAppV4(props) {
2737
2832
  label: entry.category.startsWith('dependency')
2738
2833
  ? `${props.controller.getPluginOverview(entry.pluginId ?? '')?.displayName ?? entry.pluginId ?? entry.title}${entry.platform ? ` · ${platformLabel(entry.platform)}` : ''}`
2739
2834
  : entry.title,
2740
- description: `${issueGroupLabel(entry.group)} · ${entry.category.startsWith('dependency')
2835
+ description: entry.category.startsWith('dependency')
2741
2836
  ? `${props.controller.getPluginOverview(entry.dependencyPluginId ?? '')?.displayName ?? entry.dependencyPluginId ?? entry.explanation} · ${entry.impact[0] ?? ''}`
2742
- : `${entry.explanation} · ${entry.impact[0] ?? ''}`}`,
2743
- status: entry.category === 'sourceDegraded' ? 'warning' : 'error',
2837
+ : `${entry.explanation} · ${entry.impact[0] ?? ''}`,
2838
+ status: 'error',
2744
2839
  run: () => entry.category === 'dependencyStillRequired' && entry.pluginId
2745
2840
  ? go({ name: 'pluginDependencies', pluginId: entry.pluginId, view: 'removeBlocked',
2746
2841
  ...(entry.platform ? { platform: entry.platform } : {}),
@@ -2749,7 +2844,27 @@ export function ConsoleAppV4(props) {
2749
2844
  }));
2750
2845
  }
2751
2846
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
2752
- 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
+ }
2753
2868
  }
2754
2869
  else if (route.name === 'issueDetail' && issue) {
2755
2870
  breadcrumb = `需要处理 / ${issue.pluginId ?? issue.sourceId ?? issue.entity.id}`;
@@ -2763,8 +2878,8 @@ export function ConsoleAppV4(props) {
2763
2878
  return;
2764
2879
  }
2765
2880
  if (entry.id === 'refreshSource' && issue.sourceId)
2766
- await props.controller.refreshSource(issue.sourceId);
2767
- else if (entry.id === 'continueWithCache' || entry.id === 'retryLater')
2881
+ await props.controller.reloadCatalog({ sourceId: issue.sourceId });
2882
+ else if (entry.id === 'retryLater')
2768
2883
  go({ name: 'issueList' });
2769
2884
  else if (entry.id === 'editSource' && issue.sourceId)
2770
2885
  go(issue.entity.kind === 'source'
@@ -2803,7 +2918,8 @@ export function ConsoleAppV4(props) {
2803
2918
  }));
2804
2919
  const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
2805
2920
  const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
2806
- 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' });
2807
2923
  const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
2808
2924
  const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
2809
2925
  ? {
@@ -2850,12 +2966,15 @@ export function ConsoleAppV4(props) {
2850
2966
  setScope(issueScope);
2851
2967
  setSelectedTargets([entry.platform]);
2852
2968
  setTargetSelections((current) => ({ ...current, [issueScope]: [entry.platform] }));
2969
+ props.controller.beginInstallFlow(issuePlugin.pluginId, issueScope);
2970
+ props.controller.setInstallPlatforms([entry.platform]);
2853
2971
  go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
2854
2972
  } })),
2855
2973
  { label: '返回调整目标平台', run: () => {
2856
2974
  setScope(issueScope);
2857
2975
  setSelectedTargets([]);
2858
2976
  setTargetSelections((current) => ({ ...current, [issueScope]: [] }));
2977
+ props.controller.beginInstallFlow(issuePlugin.pluginId, issueScope);
2859
2978
  go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
2860
2979
  } },
2861
2980
  { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
@@ -2891,13 +3010,11 @@ export function ConsoleAppV4(props) {
2891
3010
  const preparation = snapshot.reviewPreparation;
2892
3011
  const labels = preparation.intent.kind === 'portableInstall'
2893
3012
  ? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
2894
- : preparation.intent.kind === 'catalogActivate'
2895
- ? ['插件来源 / Catalog 更新', '正在准备 Catalog 激活确认']
2896
- : preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
2897
- ? ['设置', '正在准备设置确认']
2898
- : preparation.intent.kind === 'sourceMutation'
2899
- ? ['插件来源', '正在准备来源变更确认']
2900
- : ['插件操作', '正在准备插件操作确认'];
3013
+ : preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
3014
+ ? ['设置', '正在准备设置确认']
3015
+ : preparation.intent.kind === 'sourceMutation'
3016
+ ? ['插件来源', '正在准备来源变更确认']
3017
+ : ['插件操作', '正在准备插件操作确认'];
2901
3018
  breadcrumb = `${labels[0]} / 准备确认`;
2902
3019
  context = '只读';
2903
3020
  title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
@@ -2921,11 +3038,24 @@ export function ConsoleAppV4(props) {
2921
3038
  }
2922
3039
  else if (route.name === 'review' && snapshot.activeReview) {
2923
3040
  const review = snapshot.activeReview;
3041
+ const sharedImpactConfirmed = !review.requiresConfirmation
3042
+ || confirmedSharedReviewToken === review.reviewToken;
2924
3043
  breadcrumb = `${review.subject.name} / ${review.title}`;
2925
3044
  title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' ? review.title : `确认${review.title}`} ${review.subject.name}`;
2926
- description = snapshot.result?.state === 'stale' ? '当前状态已变化。请检查新的变化后再次确认。' : review.description;
3045
+ description = snapshot.result?.state === 'stale'
3046
+ ? '当前状态已变化。请检查新的变化后再次确认。'
3047
+ : review.requiresConfirmation ? '' : review.description;
2927
3048
  actions = [
2928
- { 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: () => {
2929
3059
  if (operationBusy.current)
2930
3060
  return;
2931
3061
  operationBusy.current = true;
@@ -2944,9 +3074,16 @@ export function ConsoleAppV4(props) {
2944
3074
  : back },
2945
3075
  ];
2946
3076
  const groupedChanges = review.changes.some((change) => change.group);
3077
+ const compressedReview = policy.mode === 'compact' || review.requiresConfirmation;
2947
3078
  const groupTitle = {
2948
3079
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
2949
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',
2950
3087
  runtime: 'Codex runtime',
2951
3088
  portableTarget: '安装目标',
2952
3089
  portableInputs: '准备内容',
@@ -2958,7 +3095,7 @@ export function ConsoleAppV4(props) {
2958
3095
  const changeStatus = (change) => change.kind === 'remove' ? 'error'
2959
3096
  : change.kind === 'warning' ? 'warning'
2960
3097
  : change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined;
2961
- const groupOrder = ['portableWarnings', 'portableTarget', 'configChanges', 'configImpact', 'requested', 'dependencies', 'runtime', 'preserved', 'portableInputs'];
3098
+ const groupOrder = ['portableWarnings', 'portableTarget', 'configChanges', 'configImpact', 'requested', 'dependencies', 'projectIntent', 'userIntent', 'sharedCodexNative', 'userActivation', 'projectActivation', 'awExtensions', 'runtime', 'preserved', 'portableInputs'];
2962
3099
  const groupedSpecs = groupedChanges
2963
3100
  ? groupOrder.map((group) => ({
2964
3101
  key: group,
@@ -2972,7 +3109,7 @@ export function ConsoleAppV4(props) {
2972
3109
  { key: 'changes', label: undefined, protectedCount: false, changes: review.changes.filter((change) => change.kind !== 'warning' && change.kind !== 'keep') },
2973
3110
  ];
2974
3111
  const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0);
2975
- const headingLines = policy.mode === 'compact' ? 0 : visibleSpecs.filter((group) => group.label).length;
3112
+ const headingLines = compressedReview ? 0 : visibleSpecs.filter((group) => group.label).length;
2976
3113
  const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
2977
3114
  const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
2978
3115
  const targetGroup = visibleSpecs.find((group) => group.key === 'portableTarget');
@@ -2991,7 +3128,7 @@ export function ConsoleAppV4(props) {
2991
3128
  const changeGroups = visibleSpecs.map((group) => {
2992
3129
  const visibleCount = allocations.get(group.key) ?? 1;
2993
3130
  const hiddenCount = group.changes.length - visibleCount;
2994
- 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) => {
2995
3132
  const retainDescription = change.kind === 'warning' || group.key === 'portableTarget'
2996
3133
  || group.key === 'requested' || group.key === 'dependencies'
2997
3134
  || group.key === 'configChanges' || group.key === 'configImpact';
@@ -2999,7 +3136,7 @@ export function ConsoleAppV4(props) {
2999
3136
  const compactGroupLabel = group.key === 'requested' ? '根插件'
3000
3137
  : group.key === 'dependencies' ? '依赖'
3001
3138
  : group.key === 'preserved' ? '保持' : group.label;
3002
- const category = policy.mode === 'compact' && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
3139
+ const category = compressedReview && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
3003
3140
  const count = index === visibleCount - 1 && (hiddenCount > 0 || group.protectedCount)
3004
3141
  ? hiddenCount > 0 ? `另有 ${hiddenCount} 项` : `共 ${group.changes.length} 项`
3005
3142
  : undefined;
@@ -3024,18 +3161,19 @@ export function ConsoleAppV4(props) {
3024
3161
  return _jsx(ReviewChangeRow, { title: `${category ? `${category} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`, meta: index === visibleTechnicalChanges.length - 1 && hiddenTechnicalChanges > 0
3025
3162
  ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
3026
3163
  });
3027
- 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, 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, 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] }) });
3028
3167
  }
3029
3168
  else if (route.name === 'progress') {
3030
- const catalogActivation = snapshot.activeReview?.operationKind === 'catalogActivate';
3031
3169
  const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
3032
3170
  const configOperation = snapshot.activeReview?.operationKind === 'configSave'
3033
3171
  || snapshot.activeReview?.operationKind === 'configReset';
3034
3172
  const sourceOperation = snapshot.activeReview?.operationKind === 'sourceMutation';
3035
3173
  const issueOperation = snapshot.activeReview?.originIssue;
3036
- breadcrumb = catalogActivation ? '插件来源 / Catalog 更新' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
3037
- title = catalogActivation ? '正在激活已验证的 Catalog generation' : portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
3038
- description = catalogActivation ? '不会重新下载;完成后重新加载插件列表和依赖关系。' : issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
3174
+ breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
3175
+ title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存来源配置' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
3176
+ description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '只原子保存来源配置,不访问网络。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
3039
3177
  help = 'Ctrl-C 完成后退出';
3040
3178
  const progress = snapshot.progress;
3041
3179
  const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
@@ -3135,7 +3273,7 @@ export function ConsoleAppV4(props) {
3135
3273
  return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
3136
3274
  };
3137
3275
  const compactPortableProgress = policy.mode === 'compact' && showTechnical;
3138
- 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) => {
3139
3277
  const state = progressLabel(entry);
3140
3278
  const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
3141
3279
  const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
@@ -3155,7 +3293,6 @@ export function ConsoleAppV4(props) {
3155
3293
  : result.state === 'blocked' ? result.summary : undefined;
3156
3294
  const action = resultSummary?.action;
3157
3295
  const operationKind = 'operationKind' in result ? result.operationKind : undefined;
3158
- const catalogActivation = operationKind === 'catalogActivate';
3159
3296
  const portableOperation = operationKind === 'portableInstall';
3160
3297
  const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
3161
3298
  ? result.summary.portable : undefined;
@@ -3166,8 +3303,6 @@ export function ConsoleAppV4(props) {
3166
3303
  ? operationKind : undefined;
3167
3304
  const successfulConfigOperation = configOperation !== undefined && !blocked && !partial;
3168
3305
  const sourceOperation = operationKind === 'sourceMutation';
3169
- const sourceRefreshWarning = sourceOperation && 'summary' in result
3170
- && result.summary?.messages?.some((message) => message.startsWith('警告:'));
3171
3306
  const issueOperation = result.originIssue;
3172
3307
  const completedTitle = {
3173
3308
  install: '✓ 插件添加完成',
@@ -3175,26 +3310,24 @@ export function ConsoleAppV4(props) {
3175
3310
  reinstall: '✓ 插件重新安装完成',
3176
3311
  uninstall: '✓ 插件移除完成',
3177
3312
  cleanup: '✓ 插件清理完成',
3313
+ sync: '✓ 插件同步完成',
3178
3314
  }[action ?? 'install'];
3179
- breadcrumb = catalogActivation ? '插件来源 / Catalog 更新 / 结果' : breadcrumb;
3180
- title = catalogActivation
3181
- ? blocked ? '× Catalog 更新未启用' : '✓ Catalog 更新已启用'
3182
- : portableOperation
3183
- ? blocked ? '× 通用 Skills 安装被阻止'
3184
- : partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
3185
- : blocked && result.writeMayHaveCompleted
3186
- ? '× 写入结果未确认'
3187
- : issueOperation
3188
- ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
3189
- : sourceOperation
3190
- ? blocked ? '× 来源操作未完成' : sourceRefreshWarning ? '! 来源配置已保存,但刷新失败' : '✓ 来源操作完成'
3191
- : configOperation
3192
- ? blocked
3193
- ? '× 设置未确认保存'
3194
- : partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
3195
- : partial ? '! 插件操作只完成了一部分'
3196
- : blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
3197
- : completedTitle;
3315
+ title = portableOperation
3316
+ ? blocked ? '× 通用 Skills 安装被阻止'
3317
+ : partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
3318
+ : blocked && result.writeMayHaveCompleted
3319
+ ? '× 写入结果未确认'
3320
+ : issueOperation
3321
+ ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
3322
+ : sourceOperation
3323
+ ? blocked ? '× 来源操作未完成' : '✓ 来源配置已保存'
3324
+ : configOperation
3325
+ ? blocked
3326
+ ? '× 设置未确认保存'
3327
+ : partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
3328
+ : partial ? '! 插件操作只完成了一部分'
3329
+ : blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
3330
+ : completedTitle;
3198
3331
  description = portableOperation
3199
3332
  ? blocked
3200
3333
  ? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
@@ -3218,11 +3351,9 @@ export function ConsoleAppV4(props) {
3218
3351
  : sourceOperation
3219
3352
  ? blocked
3220
3353
  ? result.writeMayHaveCompleted
3221
- ? '来源配置可能已保存。请重新检查来源列表;刷新失败不会撤销配置。'
3354
+ ? '来源配置可能已保存。请重新检查来源列表。'
3222
3355
  : '来源操作未执行。草稿已保留,可返回修改。'
3223
- : sourceRefreshWarning
3224
- ? '来源配置已保留。请根据警告修复来源后重试刷新。'
3225
- : '来源配置已保存,刷新已完成。'
3356
+ : '来源配置已保存。请在终端运行 aw catalog source sync,然后重新加载 TUI。'
3226
3357
  : blocked ? result.message
3227
3358
  : configOperation ? '设置层已经更新。' : '状态已刷新。';
3228
3359
  const pluginId = resultSummary?.pluginId
@@ -3504,7 +3635,9 @@ export function ConsoleAppV4(props) {
3504
3635
  try {
3505
3636
  const pending = action.run?.();
3506
3637
  if (pending)
3507
- void pending.catch(() => {
3638
+ void pending.catch((error) => {
3639
+ if (error instanceof Error && error.name === 'AbortError')
3640
+ return;
3508
3641
  setActionError('操作未完成。请重新检查后重试。');
3509
3642
  });
3510
3643
  }
@@ -3860,14 +3993,12 @@ export function ConsoleAppV4(props) {
3860
3993
  setScrollOffset(0);
3861
3994
  }
3862
3995
  else if (input.toLowerCase() === 'r' && route.name === 'sourceDetail') {
3863
- go({ name: 'catalogRefresh', sourceId: route.sourceId });
3864
- void props.controller.refreshSource(route.sourceId);
3996
+ void props.controller.reloadCatalog({ sourceId: route.sourceId });
3865
3997
  }
3866
3998
  else if (input.toLowerCase() === 'r' && route.name === 'sourceList') {
3867
3999
  const source = props.controller.listSources()[focus];
3868
4000
  if (source) {
3869
- go({ name: 'catalogRefresh', sourceId: source.id });
3870
- void props.controller.refreshSource(source.id);
4001
+ void props.controller.reloadCatalog({ sourceId: source.id });
3871
4002
  }
3872
4003
  }
3873
4004
  else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
@@ -3910,7 +4041,7 @@ export function ConsoleAppV4(props) {
3910
4041
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
3911
4042
  ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3912
4043
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
3913
- : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
4044
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(route.name === 'dependencyPlacement' ? dependencyPlacementStaticRows : 0));
3914
4045
  setScrollOffset(viewport.offset);
3915
4046
  return next;
3916
4047
  });
@@ -3929,7 +4060,7 @@ export function ConsoleAppV4(props) {
3929
4060
  const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
3930
4061
  ? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
3931
4062
  + (portableVariantCandidates.length > 0 ? 3 : 0)))
3932
- : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
4063
+ : actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget(route.name === 'dependencyPlacement' ? dependencyPlacementStaticRows : 0));
3933
4064
  setScrollOffset(viewport.offset);
3934
4065
  return next;
3935
4066
  });