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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/dist/authoring/scripts/add-skill.mjs +4 -4
  2. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  3. package/dist/authoring/scripts/init-plugin.mjs +8 -37
  4. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  5. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  6. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +77 -90
  7. package/dist/authoring/scripts/lib/skill-scaffold.mjs +99 -120
  8. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  9. package/dist/authoring/scripts/plugin-status.mjs +8 -11
  10. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  11. package/dist/authoring/scripts/remove-plugin.mjs +2 -1
  12. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  13. package/dist/authoring/scripts/validate-plugin.mjs +64 -758
  14. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  15. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  16. package/dist/scripts/{plugin-eval-materializer.js → aw-skill-eval-materializer.js} +34 -145
  17. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  18. package/dist/scripts/bundle-plugins.d.ts +1 -0
  19. package/dist/scripts/bundle-plugins.js +2 -1
  20. package/dist/scripts/bundle-plugins.js.map +1 -1
  21. package/dist/scripts/plugin-staging.d.ts +6 -8
  22. package/dist/scripts/plugin-staging.js +15 -28
  23. package/dist/scripts/plugin-staging.js.map +1 -1
  24. package/dist/scripts/test-plugin-scripts.js +2 -33
  25. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  26. package/dist/src/adapters/helpers.js +2 -2
  27. package/dist/src/adapters/helpers.js.map +1 -1
  28. package/dist/src/authoring/workspace-commands.d.ts +17 -1
  29. package/dist/src/authoring/workspace-commands.js +21 -4
  30. package/dist/src/authoring/workspace-commands.js.map +1 -1
  31. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  32. package/dist/src/commands/aw-capabilities.js +254 -0
  33. package/dist/src/commands/aw-capabilities.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +118 -0
  35. package/dist/src/commands/catalog-dev-source.js +417 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +13 -0
  38. package/dist/src/commands/catalog-generation.js +13 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +29 -28
  41. package/dist/src/commands/catalog-publication-inspection.js +138 -50
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-journal.d.ts +49 -0
  44. package/dist/src/commands/catalog-publication-journal.js +147 -0
  45. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  46. package/dist/src/commands/catalog-publication-target.d.ts +18 -0
  47. package/dist/src/commands/catalog-publication-target.js +95 -0
  48. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  49. package/dist/src/commands/catalog-publish.d.ts +58 -15
  50. package/dist/src/commands/catalog-publish.js +590 -210
  51. package/dist/src/commands/catalog-publish.js.map +1 -1
  52. package/dist/src/commands/catalog-source.d.ts +12 -15
  53. package/dist/src/commands/catalog-source.js +61 -86
  54. package/dist/src/commands/catalog-source.js.map +1 -1
  55. package/dist/src/commands/catalog-status.d.ts +18 -22
  56. package/dist/src/commands/catalog-status.js +81 -115
  57. package/dist/src/commands/catalog-status.js.map +1 -1
  58. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  59. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  60. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  61. package/dist/src/commands/dependency-operation-plan.d.ts +88 -9
  62. package/dist/src/commands/dependency-operation-plan.js +331 -6
  63. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  64. package/dist/src/commands/init.d.ts +5 -11
  65. package/dist/src/commands/init.js +30 -51
  66. package/dist/src/commands/init.js.map +1 -1
  67. package/dist/src/commands/offline-cleanup-evidence.d.ts +43 -0
  68. package/dist/src/commands/offline-cleanup-evidence.js +116 -0
  69. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  70. package/dist/src/commands/options.d.ts +7 -1
  71. package/dist/src/commands/options.js +65 -6
  72. package/dist/src/commands/options.js.map +1 -1
  73. package/dist/src/commands/plugin-capabilities.js +5 -1
  74. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  75. package/dist/src/commands/rebuild-command.d.ts +27 -13
  76. package/dist/src/commands/rebuild-command.js +1472 -471
  77. package/dist/src/commands/rebuild-command.js.map +1 -1
  78. package/dist/src/commands/rebuild-integration.d.ts +63 -8
  79. package/dist/src/commands/rebuild-integration.js +761 -439
  80. package/dist/src/commands/rebuild-integration.js.map +1 -1
  81. package/dist/src/commands/rebuild-operations.d.ts +2 -8
  82. package/dist/src/commands/rebuild-operations.js +48 -132
  83. package/dist/src/commands/rebuild-operations.js.map +1 -1
  84. package/dist/src/commands/runtime.js +21 -34
  85. package/dist/src/commands/runtime.js.map +1 -1
  86. package/dist/src/commands/skill-eval.d.ts +33 -0
  87. package/dist/src/commands/skill-eval.js +363 -0
  88. package/dist/src/commands/skill-eval.js.map +1 -0
  89. package/dist/src/commands/status-v6.d.ts +202 -0
  90. package/dist/src/commands/status-v6.js +1938 -0
  91. package/dist/src/commands/status-v6.js.map +1 -0
  92. package/dist/src/commands/sync-command.d.ts +21 -0
  93. package/dist/src/commands/sync-command.js +206 -0
  94. package/dist/src/commands/sync-command.js.map +1 -0
  95. package/dist/src/commands/wizard.d.ts +2 -2
  96. package/dist/src/commands/wizard.js +2 -2
  97. package/dist/src/commands/wizard.js.map +1 -1
  98. package/dist/src/commands/write-commands.js +33 -19
  99. package/dist/src/commands/write-commands.js.map +1 -1
  100. package/dist/src/constants.d.ts +7 -1
  101. package/dist/src/constants.js +7 -1
  102. package/dist/src/constants.js.map +1 -1
  103. package/dist/src/eval/manifest-v1.d.ts +66 -0
  104. package/dist/src/eval/manifest-v1.js +326 -0
  105. package/dist/src/eval/manifest-v1.js.map +1 -0
  106. package/dist/src/generators/local-plugin.d.ts +3 -0
  107. package/dist/src/generators/local-plugin.js +167 -45
  108. package/dist/src/generators/local-plugin.js.map +1 -1
  109. package/dist/src/index.d.ts +6 -1
  110. package/dist/src/index.js +863 -126
  111. package/dist/src/index.js.map +1 -1
  112. package/dist/src/output/command-receipt.d.ts +58 -8
  113. package/dist/src/output/command-receipt.js +141 -11
  114. package/dist/src/output/command-receipt.js.map +1 -1
  115. package/dist/src/output/model.d.ts +3 -44
  116. package/dist/src/output/model.js +1 -144
  117. package/dist/src/output/model.js.map +1 -1
  118. package/dist/src/output/rebuild-progress.js +0 -3
  119. package/dist/src/output/rebuild-progress.js.map +1 -1
  120. package/dist/src/portable/commands.d.ts +6 -2
  121. package/dist/src/portable/commands.js +57 -6
  122. package/dist/src/portable/commands.js.map +1 -1
  123. package/dist/src/portable/materializer.d.ts +4 -4
  124. package/dist/src/portable/materializer.js +61 -12
  125. package/dist/src/portable/materializer.js.map +1 -1
  126. package/dist/src/tui/catalog-generation.d.ts +35 -0
  127. package/dist/src/tui/catalog-generation.js +85 -0
  128. package/dist/src/tui/catalog-generation.js.map +1 -0
  129. package/dist/src/tui/components.js +1 -2
  130. package/dist/src/tui/components.js.map +1 -1
  131. package/dist/src/tui/console-app-v4.d.ts +1 -1
  132. package/dist/src/tui/console-app-v4.js +647 -196
  133. package/dist/src/tui/console-app-v4.js.map +1 -1
  134. package/dist/src/tui/controller-runtime.d.ts +6 -2
  135. package/dist/src/tui/controller-runtime.js +646 -137
  136. package/dist/src/tui/controller-runtime.js.map +1 -1
  137. package/dist/src/tui/controller.d.ts +77 -15
  138. package/dist/src/tui/controller.js +647 -306
  139. package/dist/src/tui/controller.js.map +1 -1
  140. package/dist/src/tui/diagnostics.d.ts +1 -2
  141. package/dist/src/tui/diagnostics.js.map +1 -1
  142. package/dist/src/tui/index.d.ts +1 -0
  143. package/dist/src/tui/index.js +1 -0
  144. package/dist/src/tui/index.js.map +1 -1
  145. package/dist/src/tui/input-display.d.ts +8 -0
  146. package/dist/src/tui/input-display.js +36 -0
  147. package/dist/src/tui/input-display.js.map +1 -0
  148. package/dist/src/tui/model.d.ts +102 -14
  149. package/dist/src/tui/model.js +630 -139
  150. package/dist/src/tui/model.js.map +1 -1
  151. package/dist/src/tui/router.d.ts +35 -0
  152. package/dist/src/tui/router.js.map +1 -1
  153. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  154. package/dist/src/tui/source-ref-presets.js +11 -0
  155. package/dist/src/tui/source-ref-presets.js.map +1 -0
  156. package/dist/src/tui/views.js +31 -3
  157. package/dist/src/tui/views.js.map +1 -1
  158. package/dist/src/types.d.ts +104 -13
  159. package/dist/src/types.js.map +1 -1
  160. package/dist/src/utils/agent-version.d.ts +7 -1
  161. package/dist/src/utils/agent-version.js +41 -8
  162. package/dist/src/utils/agent-version.js.map +1 -1
  163. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  164. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  165. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  166. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  167. package/dist/src/utils/bundle-integrity.js +0 -25
  168. package/dist/src/utils/bundle-integrity.js.map +1 -1
  169. package/dist/src/utils/bundle-layout.js +8 -6
  170. package/dist/src/utils/bundle-layout.js.map +1 -1
  171. package/dist/src/utils/catalog-digests.d.ts +7 -0
  172. package/dist/src/utils/catalog-digests.js +40 -2
  173. package/dist/src/utils/catalog-digests.js.map +1 -1
  174. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  175. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  176. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  177. package/dist/src/utils/catalog-generation-error.d.ts +13 -0
  178. package/dist/src/utils/catalog-generation-error.js +28 -0
  179. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  180. package/dist/src/utils/catalog-generation-resolver.d.ts +20 -0
  181. package/dist/src/utils/catalog-generation-resolver.js +143 -0
  182. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  183. package/dist/src/utils/catalog-generation-service.d.ts +43 -0
  184. package/dist/src/utils/catalog-generation-service.js +306 -0
  185. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  186. package/dist/src/utils/catalog-generation-state.d.ts +17 -0
  187. package/dist/src/utils/catalog-generation-state.js +168 -0
  188. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  189. package/dist/src/utils/catalog-generation-store.d.ts +34 -0
  190. package/dist/src/utils/catalog-generation-store.js +251 -0
  191. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  192. package/dist/src/utils/catalog-manifest-v6.d.ts +5 -0
  193. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v6.js} +35 -43
  194. package/dist/src/utils/catalog-manifest-v6.js.map +1 -0
  195. package/dist/src/utils/catalog-operation-context.d.ts +20 -0
  196. package/dist/src/utils/catalog-operation-context.js +141 -0
  197. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  198. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  199. package/dist/src/utils/catalog-public-error.js +21 -8
  200. package/dist/src/utils/catalog-public-error.js.map +1 -1
  201. package/dist/src/utils/catalog-runtime-projection.d.ts +3 -0
  202. package/dist/src/utils/catalog-runtime-projection.js +5 -40
  203. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  204. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  205. package/dist/src/utils/catalog-snapshot.js +11 -3
  206. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  207. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  208. package/dist/src/utils/catalog-source-identity.js +21 -2
  209. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  210. package/dist/src/utils/catalog-source-references.d.ts +7 -0
  211. package/dist/src/utils/catalog-source-references.js +44 -55
  212. package/dist/src/utils/catalog-source-references.js.map +1 -1
  213. package/dist/src/utils/catalog-sources.d.ts +1 -0
  214. package/dist/src/utils/catalog-sources.js +55 -20
  215. package/dist/src/utils/catalog-sources.js.map +1 -1
  216. package/dist/src/utils/codex-config.d.ts +2 -0
  217. package/dist/src/utils/codex-config.js +26 -5
  218. package/dist/src/utils/codex-config.js.map +1 -1
  219. package/dist/src/utils/codex-manager.d.ts +81 -0
  220. package/dist/src/utils/codex-manager.js +810 -0
  221. package/dist/src/utils/codex-manager.js.map +1 -0
  222. package/dist/src/utils/codex-reconciler.d.ts +131 -0
  223. package/dist/src/utils/codex-reconciler.js +327 -0
  224. package/dist/src/utils/codex-reconciler.js.map +1 -0
  225. package/dist/src/utils/codex-shared-native.d.ts +56 -0
  226. package/dist/src/utils/codex-shared-native.js +87 -0
  227. package/dist/src/utils/codex-shared-native.js.map +1 -0
  228. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  229. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  230. package/dist/src/utils/codex-surface-analysis.d.ts +44 -0
  231. package/dist/src/utils/codex-surface-analysis.js +247 -0
  232. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  233. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  234. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  235. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  236. package/dist/src/utils/git-catalog-provider.js +66 -140
  237. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  238. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  239. package/dist/src/utils/lifecycle-active-store.js +35 -247
  240. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  241. package/dist/src/utils/manifest.d.ts +1 -3
  242. package/dist/src/utils/manifest.js +1 -3
  243. package/dist/src/utils/manifest.js.map +1 -1
  244. package/dist/src/utils/multi-source-catalog.d.ts +3 -9
  245. package/dist/src/utils/multi-source-catalog.js +53 -110
  246. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  247. package/dist/src/utils/native-cli.d.ts +49 -6
  248. package/dist/src/utils/native-cli.js +116 -33
  249. package/dist/src/utils/native-cli.js.map +1 -1
  250. package/dist/src/utils/orphan-marketplace-cleanup.js +1 -3
  251. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  252. package/dist/src/utils/ownership.d.ts +10 -11
  253. package/dist/src/utils/ownership.js +55 -112
  254. package/dist/src/utils/ownership.js.map +1 -1
  255. package/dist/src/utils/path-catalog-provider.d.ts +1 -1
  256. package/dist/src/utils/path-catalog-provider.js +52 -65
  257. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  258. package/dist/src/utils/paths.d.ts +0 -1
  259. package/dist/src/utils/paths.js +0 -1
  260. package/dist/src/utils/paths.js.map +1 -1
  261. package/dist/src/utils/plugin-config-write.js +18 -40
  262. package/dist/src/utils/plugin-config-write.js.map +1 -1
  263. package/dist/src/utils/plugin-config.d.ts +1 -12
  264. package/dist/src/utils/plugin-config.js +107 -226
  265. package/dist/src/utils/plugin-config.js.map +1 -1
  266. package/dist/src/utils/plugin-intent-state.d.ts +39 -0
  267. package/dist/src/utils/plugin-intent-state.js +325 -0
  268. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  269. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  270. package/dist/src/utils/plugin-scripts.js +1 -1
  271. package/dist/src/utils/plugin-scripts.js.map +1 -1
  272. package/dist/src/utils/state-paths.js +7 -2
  273. package/dist/src/utils/state-paths.js.map +1 -1
  274. package/dist/src/utils/target-eligibility.d.ts +8 -2
  275. package/dist/src/utils/target-eligibility.js +9 -5
  276. package/dist/src/utils/target-eligibility.js.map +1 -1
  277. package/package.json +6 -4
  278. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  279. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  280. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  281. package/dist/scripts/aw-skill-eval-engine.d.ts +0 -13
  282. package/dist/scripts/aw-skill-eval-engine.js +0 -31
  283. package/dist/scripts/aw-skill-eval-engine.js.map +0 -1
  284. package/dist/scripts/codex-eval-app-server.d.ts +0 -89
  285. package/dist/scripts/codex-eval-app-server.js +0 -1228
  286. package/dist/scripts/codex-eval-app-server.js.map +0 -1
  287. package/dist/scripts/codex-eval-isolation.d.ts +0 -61
  288. package/dist/scripts/codex-eval-isolation.js +0 -318
  289. package/dist/scripts/codex-eval-isolation.js.map +0 -1
  290. package/dist/scripts/codex-eval-process-management.d.ts +0 -8
  291. package/dist/scripts/codex-eval-process-management.js +0 -69
  292. package/dist/scripts/codex-eval-process-management.js.map +0 -1
  293. package/dist/scripts/codex-eval-process-tree.d.ts +0 -38
  294. package/dist/scripts/codex-eval-process-tree.js +0 -803
  295. package/dist/scripts/codex-eval-process-tree.js.map +0 -1
  296. package/dist/scripts/codex-eval-runtime-config.d.ts +0 -62
  297. package/dist/scripts/codex-eval-runtime-config.js +0 -843
  298. package/dist/scripts/codex-eval-runtime-config.js.map +0 -1
  299. package/dist/scripts/codex-trigger-eval.d.ts +0 -84
  300. package/dist/scripts/codex-trigger-eval.js +0 -968
  301. package/dist/scripts/codex-trigger-eval.js.map +0 -1
  302. package/dist/scripts/eval-plugin-skills-audited.d.ts +0 -6
  303. package/dist/scripts/eval-plugin-skills-audited.js +0 -261
  304. package/dist/scripts/eval-plugin-skills-audited.js.map +0 -1
  305. package/dist/scripts/eval-plugin-skills.d.ts +0 -45
  306. package/dist/scripts/eval-plugin-skills.js +0 -1586
  307. package/dist/scripts/eval-plugin-skills.js.map +0 -1
  308. package/dist/scripts/eval-run-layout.d.ts +0 -17
  309. package/dist/scripts/eval-run-layout.js +0 -26
  310. package/dist/scripts/eval-run-layout.js.map +0 -1
  311. package/dist/scripts/plugin-eval-contract.d.ts +0 -185
  312. package/dist/scripts/plugin-eval-contract.js +0 -695
  313. package/dist/scripts/plugin-eval-contract.js.map +0 -1
  314. package/dist/scripts/plugin-eval-materializer.d.ts +0 -13
  315. package/dist/scripts/plugin-eval-materializer.js.map +0 -1
  316. package/dist/scripts/skill-reporting.d.ts +0 -212
  317. package/dist/scripts/skill-reporting.js +0 -922
  318. package/dist/scripts/skill-reporting.js.map +0 -1
  319. package/dist/src/commands/status-v4.d.ts +0 -123
  320. package/dist/src/commands/status-v4.js +0 -905
  321. package/dist/src/commands/status-v4.js.map +0 -1
  322. package/dist/src/commands/status.d.ts +0 -93
  323. package/dist/src/commands/status.js +0 -442
  324. package/dist/src/commands/status.js.map +0 -1
  325. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  326. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  327. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  328. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  329. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  330. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  331. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
  332. package/dist/src/utils/catalog-manifest-v4.js.map +0 -1
  333. package/dist/src/utils/catalog-source-cache.d.ts +0 -62
  334. package/dist/src/utils/catalog-source-cache.js +0 -296
  335. package/dist/src/utils/catalog-source-cache.js.map +0 -1
  336. package/dist/src/utils/codex-project-surface.d.ts +0 -3
  337. package/dist/src/utils/codex-project-surface.js +0 -49
  338. package/dist/src/utils/codex-project-surface.js.map +0 -1
  339. package/dist/src/utils/plugin-release.d.ts +0 -2
  340. package/dist/src/utils/plugin-release.js +0 -2
  341. package/dist/src/utils/plugin-release.js.map +0 -1
@@ -1,6 +1,9 @@
1
1
  import crypto from 'node:crypto';
2
- import { buildTuiViewModel, hasInstalledPlacement, } from './model.js';
2
+ import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
3
+ import { requestedIntentAction } from '../output/command-receipt.js';
4
+ import { buildTuiViewModel, dependencyPlacementTargets, hasInstalledPlacement, } from './model.js';
3
5
  import { safeDiagnosticCode } from './diagnostics.js';
6
+ import { targetHasArtifactCollision } from '../utils/target-eligibility.js';
4
7
  import { TuiRouter, } from './router.js';
5
8
  function reviewPreparationStageLabel(stage) {
6
9
  return {
@@ -42,7 +45,10 @@ function cloneFrozen(value) {
42
45
  return deepFreeze(structuredClone(value));
43
46
  }
44
47
  export function operationEvidenceFingerprint(evidence) {
45
- return crypto.createHash('sha256').update(JSON.stringify(stableValue(evidence))).digest('hex');
48
+ const { codexManager: _runtimeSelection, ...staleEvidence } = evidence;
49
+ return crypto.createHash('sha256')
50
+ .update(JSON.stringify(stableValue(staleEvidence)))
51
+ .digest('hex');
46
52
  }
47
53
  function sourceIdFromPluginId(pluginId) {
48
54
  const separator = pluginId.indexOf('/');
@@ -69,6 +75,22 @@ function operationLabels(intent) {
69
75
  destructive: false,
70
76
  };
71
77
  }
78
+ if (intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly === 'desired') {
79
+ return {
80
+ title: '调整依赖位置',
81
+ description: '先更新当前项目的 desired placement;不会删除所有项目中的实际内容。',
82
+ primaryLabel: '更新 desired placement',
83
+ destructive: false,
84
+ };
85
+ }
86
+ if (intent.kind === 'pluginWrite' && intent.dependencyPlacementAdjustment) {
87
+ return {
88
+ title: '调整依赖位置',
89
+ description: '按最终依赖位置更新 desired intent,并应用非破坏性变更。',
90
+ primaryLabel: '应用依赖位置',
91
+ destructive: false,
92
+ };
93
+ }
72
94
  if (intent.kind === 'pluginWrite') {
73
95
  if (intent.originIssue) {
74
96
  if (intent.action === 'cleanup') {
@@ -100,6 +122,7 @@ function operationLabels(intent) {
100
122
  reinstall: ['重新安装插件', '清理 AW 管理的内容后完整重建。', true],
101
123
  uninstall: ['移除插件', '移除 AW 管理的安装内容。配置保持不变。', true],
102
124
  cleanup: ['清理插件', '只移除可证明属于 AW 的现场内容。配置保持不变。', true],
125
+ sync: ['同步插件', '按已保存的 desired intent 修复当前现场。', false],
103
126
  };
104
127
  const [title, description, destructive] = labels[intent.action];
105
128
  return { title, description, primaryLabel: title, destructive };
@@ -122,8 +145,8 @@ function operationLabels(intent) {
122
145
  }
123
146
  if (intent.kind === 'sourceMutation') {
124
147
  const labels = {
125
- add: ['添加来源', '先保存来源配置,再尽力刷新来源。刷新失败时配置仍保留。', '保存并刷新', false],
126
- update: ['修改来源', '先保存来源配置,再尽力刷新来源。刷新失败时配置仍保留。', '保存并刷新', false],
148
+ add: ['添加来源', '只保存来源配置。保存后请在终端运行 source sync。', '保存来源', false],
149
+ update: ['修改来源', '只保存来源配置。保存后请在终端运行 source sync。', '保存来源', false],
127
150
  setPrimary: ['设为主来源', '只改变默认 marketplace,不迁移安装。', '设为主来源', false],
128
151
  remove: ['移除来源', '移除来源配置;不会删除其它来源。', '移除来源', true],
129
152
  };
@@ -164,42 +187,138 @@ function reviewChanges(intent, evidence, dependencyOverview) {
164
187
  ];
165
188
  }
166
189
  if (intent.kind === 'pluginWrite') {
190
+ const desiredIntentKind = requestedIntentAction(intent.action);
191
+ const runtimeChanges = evidence?.codexManager
192
+ ? [{
193
+ kind: evidence.codexManager.state === 'requires-install' ? 'add' : 'keep',
194
+ group: 'runtime',
195
+ title: evidence.codexManager.state === 'requires-install'
196
+ ? '获取 Codex runtime'
197
+ : 'Codex runtime',
198
+ description: evidence.codexManager.state === 'requires-install'
199
+ ? `${evidence.codexManager.packageName}@${evidence.codexManager.version} · AW-managed 缓存`
200
+ : evidence.codexManager.state === 'cached'
201
+ ? `${evidence.codexManager.packageName}@${evidence.codexManager.version} · 复用 AW-managed 缓存`
202
+ : `使用兼容的 system Codex ${evidence.codexManager.version}`,
203
+ }]
204
+ : [];
205
+ const codexChanges = (evidence?.codexReconcilePlans ?? []).flatMap((codexPlan) => [
206
+ ...codexPlan.sharedNativeActions.map((action) => ({
207
+ kind: action.action === 'reuse' || action.action === 'claim' || action.action === 'none' ? 'keep'
208
+ : action.action === 'install' ? 'add' : 'change',
209
+ group: 'sharedCodexNative',
210
+ pluginId: action.pluginId,
211
+ title: action.action === 'claim'
212
+ ? `${action.codexPluginId}:复用精确现有 shared native(不建立 ledger)`
213
+ : action.codexPluginId,
214
+ description: action.action === 'claim'
215
+ ? '复用已存在的精确 shared native;不建立 ownership ledger'
216
+ : `${action.action} · shared${action.affectsOtherScopes ? ' · 可能影响其他项目' : ''}`,
217
+ })),
218
+ ...codexPlan.activationActions.user.map((action) => ({
219
+ kind: action.action === 'none' ? 'keep' : action.desired ? 'change' : 'remove',
220
+ group: 'userActivation',
221
+ pluginId: action.pluginId,
222
+ title: action.codexPluginId,
223
+ description: `desired=${String(action.desired)} · ${action.action}`,
224
+ })),
225
+ ...codexPlan.activationActions.project.map((action) => ({
226
+ kind: action.action === 'none' ? 'keep' : action.desired ? 'change' : 'remove',
227
+ group: 'projectActivation',
228
+ pluginId: action.pluginId,
229
+ title: action.codexPluginId,
230
+ description: `desired=${String(action.desired)} · ${action.action}`,
231
+ })),
232
+ ...[...codexPlan.extensionWrites, ...codexPlan.extensionRemovals]
233
+ .map((action) => ({
234
+ kind: action.action === 'write' ? 'change' : 'remove',
235
+ group: 'awExtensions',
236
+ pluginId: action.pluginId,
237
+ title: action.componentKey,
238
+ description: `${action.scope} · ${action.action}`,
239
+ })),
240
+ ]);
167
241
  const plan = evidence?.dependencyPlan;
168
- if (plan && (intent.action === 'install' || intent.action === 'upgrade' || intent.action === 'reinstall')) {
242
+ if (plan && (intent.action === 'install' || intent.action === 'upgrade'
243
+ || intent.action === 'reinstall' || intent.action === 'sync')) {
169
244
  const changes = plan.targets.map((target) => {
170
245
  const relation = dependencyRelation(plan, target.pluginId);
171
- const actionKind = target.action === 'keep'
172
- ? 'keep'
173
- : target.action === 'install'
174
- ? 'add'
175
- : 'change';
176
246
  return {
177
- kind: actionKind,
178
- group: relation === 'requested' ? 'requested' : 'dependencies',
247
+ kind: desiredIntentKind,
248
+ group: target.resolvedScope === 'project' ? 'projectIntent' : 'userIntent',
179
249
  pluginId: target.pluginId,
180
250
  relation,
181
251
  title: pluginNameFromId(target.pluginId),
182
- description: `${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.resolvedScope === 'user' ? '所有项目' : '当前项目'}${target.action === 'keep' ? ' · 已可用' : ''}`,
252
+ description: `${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.resolvedScope === 'user' ? '所有项目' : '当前项目'}${target.action === 'keep' ? ' · 已可用' : ''}${target.assignmentSource === 'existingWiderTarget' ? ' · 复用健康的所有项目安装' : target.assignmentSource === 'consumerPromotion' ? ' · 随上游依赖一并调整' : ''}`,
183
253
  };
184
254
  });
185
255
  changes.push({
186
256
  kind: 'keep',
187
257
  group: 'preserved',
188
- title: '插件设置和已有依赖保持不变',
189
- description: '移除根插件时不会自动清理依赖。',
258
+ title: intent.dependencyPlacementAdjustment
259
+ ? '根插件和插件设置保持不变'
260
+ : '插件设置和已有依赖保持不变',
261
+ description: intent.dependencyPlacementAdjustment
262
+ ? '这里只调整所列 dependency placement。'
263
+ : '移除根插件时不会自动清理依赖。',
190
264
  });
191
- return changes;
265
+ if (intent.dependencyPlacementOnly === 'desired') {
266
+ changes.push({
267
+ kind: desiredIntentKind,
268
+ group: 'projectIntent',
269
+ title: '先更新依赖 desired placement',
270
+ description: '这一步不删除所有项目中的实际内容。',
271
+ }, {
272
+ kind: 'warning',
273
+ group: 'preserved',
274
+ title: '后续第 1 步:显式 user cleanup;不自动扫描/清理',
275
+ description: '请在已知的所有项目位置确认 cleanup。',
276
+ }, {
277
+ kind: 'keep',
278
+ group: 'preserved',
279
+ title: '后续第 2 步:同步当前项目',
280
+ description: 'cleanup 完成后运行当前项目 sync。',
281
+ });
282
+ return changes;
283
+ }
284
+ return [...changes, ...codexChanges, ...runtimeChanges];
192
285
  }
193
286
  const platforms = intent.platform === 'both'
194
287
  ? ['codex', 'claude']
195
288
  : [intent.platform];
196
289
  const changes = platforms.map((platform) => ({
290
+ kind: desiredIntentKind,
291
+ group: intent.scope === 'project' ? 'projectIntent' : 'userIntent',
292
+ pluginId: intent.pluginId,
293
+ relation: 'requested',
294
+ title: pluginNameFromId(intent.pluginId),
295
+ description: `${platform === 'codex' ? 'Codex' : 'Claude Code'} · ${intent.scope === 'user' ? '所有项目' : '当前项目'}`,
296
+ }));
297
+ changes.push(...platforms.map((platform) => ({
197
298
  kind: intent.action === 'uninstall' || intent.action === 'cleanup' ? 'remove' : intent.action === 'install' ? 'add' : 'change',
299
+ group: 'requested',
300
+ pluginId: intent.pluginId,
301
+ relation: 'requested',
198
302
  title: `${platform === 'codex' ? 'Codex' : 'Claude Code'} · ${intent.scope === 'user' ? '所有项目' : '当前项目'}`,
199
303
  description: intent.action === 'uninstall' || intent.action === 'cleanup'
200
304
  ? '插件设置不会删除。'
201
305
  : '执行前会再次检查当前安装状态。',
202
- }));
306
+ })));
307
+ if (intent.action === 'cleanup' && evidence?.offlineCleanup) {
308
+ const actionLabels = {
309
+ removeOwnedOutput: '移除 AW-owned 输出',
310
+ removeNativeRegistration: '移除原生插件注册',
311
+ removeLifecycleSnapshot: '移除 lifecycle snapshot',
312
+ };
313
+ changes.push(...evidence.offlineCleanup.plan.actions.map((action) => ({
314
+ kind: 'remove',
315
+ group: 'requested',
316
+ pluginId: evidence.offlineCleanup.pluginId,
317
+ relation: 'requested',
318
+ title: actionLabels[action.action],
319
+ description: action.target,
320
+ })));
321
+ }
203
322
  if (intent.action === 'uninstall' || intent.action === 'cleanup') {
204
323
  changes.push(...(dependencyOverview?.dependencies ?? []).map((dependency) => ({
205
324
  kind: 'keep',
@@ -220,17 +339,31 @@ function reviewChanges(intent, evidence, dependencyOverview) {
220
339
  if (intent.originIssue) {
221
340
  changes.push({ kind: 'keep', title: '插件设置保持不变' }, { kind: 'warning', title: '执行前重新检查完整目标和来源证据' });
222
341
  }
223
- return changes;
342
+ if (intent.recoveryDescription) {
343
+ changes.push({
344
+ kind: 'warning',
345
+ group: 'preserved',
346
+ title: `后续:${intent.scope === 'user' ? '当前项目' : '所有项目'} sync`,
347
+ description: intent.recoveryDescription,
348
+ });
349
+ }
350
+ return [...changes, ...codexChanges, ...runtimeChanges];
224
351
  }
225
352
  if (intent.kind === 'configReset') {
226
353
  const layerLabel = intent.scope === 'user'
227
354
  ? '个人默认'
228
355
  : intent.projectLayer === 'local' ? '当前项目(仅本机)' : '当前项目';
229
356
  return [
230
- { kind: 'remove', title: `删除${layerLabel}中的已存值`, description: '有效值将恢复为继承值或默认值。' },
231
- { kind: 'change', title: '重新计算有效设置' },
232
- { kind: 'change', title: '重新应用当前可见安装位置' },
233
- { kind: 'keep', title: intent.scope === 'user' ? '当前项目和本机设置层保持不变' : '其他设置层保持不变' },
357
+ ...(intent.changes?.map((change) => ({
358
+ kind: 'remove', group: 'configChanges', title: `${layerLabel} · ${change.title}`,
359
+ description: `${change.before} → ${change.after}`,
360
+ })) ?? [{ kind: 'remove', group: 'configChanges', title: `删除${layerLabel}中的已存值`, description: '有效值将恢复为继承值或默认值。' }]),
361
+ ...((evidence?.config?.affectedTargets.length ?? 0) > 0
362
+ ? evidence.config.affectedTargets.map((target) => ({
363
+ kind: 'change', group: 'configImpact', title: `重新应用 ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.scope === 'user' ? '所有项目' : '当前项目'}`,
364
+ }))
365
+ : [{ kind: 'keep', group: 'configImpact', title: '当前没有可见安装;只保存设置' }]),
366
+ { kind: 'keep', group: 'preserved', title: intent.scope === 'user' ? '当前项目和本机设置层保持不变' : '其他设置层保持不变' },
234
367
  ];
235
368
  }
236
369
  if (intent.kind === 'sourceMutation') {
@@ -255,17 +388,24 @@ function reviewChanges(intent, evidence, dependencyOverview) {
255
388
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
256
389
  }
257
390
  const privateKeys = new Set([...(intent.sensitiveKeys ?? []), ...(intent.privateKeys ?? [])]);
391
+ const removeKeys = new Set(intent.removeKeys ?? []);
258
392
  if (intent.changes) {
259
393
  return [
260
394
  ...intent.changes.map((change) => ({
261
- kind: 'change',
262
- title: change.title,
263
- description: privateKeys.has(change.key)
264
- ? change.after === '未设置' ? '已设置 → 未设置' : '已设置 → 将更新'
265
- : `${change.before} → ${change.after}`,
395
+ kind: removeKeys.has(change.key) ? 'remove' : 'change',
396
+ group: 'configChanges',
397
+ title: removeKeys.has(change.key) ? `删除 ${change.title} 中的值` : change.title,
398
+ description: removeKeys.has(change.key)
399
+ ? privateKeys.has(change.key) ? `已设置 → ${change.after}` : `${change.before} → ${change.after}`
400
+ : privateKeys.has(change.key) ? '已设置 → 将更新'
401
+ : `${change.before} → ${change.after}`,
266
402
  })),
267
- { kind: 'change', title: '重新应用插件内容', description: '只处理当前可见安装位置。' },
268
- { kind: 'keep', title: '其他项目不会被自动扫描' },
403
+ ...((evidence?.config?.affectedTargets.length ?? 0) > 0
404
+ ? evidence.config.affectedTargets.map((target) => ({
405
+ kind: 'change', group: 'configImpact', title: `重新应用 ${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.scope === 'user' ? '所有项目' : '当前项目'}`,
406
+ }))
407
+ : [{ kind: 'keep', group: 'configImpact', title: '当前没有可见安装;只保存设置' }]),
408
+ { kind: 'keep', group: 'preserved', title: '其他项目不会被自动扫描' },
269
409
  ];
270
410
  }
271
411
  const updateKeys = Object.keys(intent.updates ?? {});
@@ -283,33 +423,51 @@ function reviewChanges(intent, evidence, dependencyOverview) {
283
423
  ];
284
424
  }
285
425
  function technicalDetails(evidence) {
426
+ const short = (value) => {
427
+ const normalized = value.startsWith('sha256:') ? value.slice(7) : value;
428
+ return normalized.slice(0, 8);
429
+ };
286
430
  const details = [
287
- `Catalog 配置:${evidence.catalog.configDigest}`,
288
- `Catalog 组合:${evidence.catalog.compositeDigest}`,
431
+ `Catalog 配置:${short(evidence.catalog.configDigest)}`,
432
+ `Catalog 组合:${short(evidence.catalog.compositeDigest)}`,
289
433
  ];
434
+ if (evidence.catalog.generationSetDigest) {
435
+ details.push(`Catalog generation:${short(evidence.catalog.generationSetDigest)}`);
436
+ }
290
437
  if (evidence.source) {
291
438
  details.push(`来源:${evidence.source.sourceId}`);
292
- details.push(`来源身份:${evidence.source.sourceIdentity}`);
439
+ if (evidence.source.sourceIdentity)
440
+ details.push(`来源身份:${short(evidence.source.sourceIdentity)}`);
293
441
  if (evidence.source.resolvedCommit)
294
- details.push(`Snapshot commit:${evidence.source.resolvedCommit}`);
442
+ details.push(`Snapshot commit:${short(evidence.source.resolvedCommit)}`);
295
443
  if (evidence.source.snapshotDigest)
296
- details.push(`Snapshot digest:${evidence.source.snapshotDigest}`);
444
+ details.push(`Snapshot digest:${short(evidence.source.snapshotDigest)}`);
297
445
  if (evidence.source.contentDigest)
298
- details.push(`Content digest:${evidence.source.contentDigest}`);
446
+ details.push(`Content digest:${short(evidence.source.contentDigest)}`);
299
447
  }
300
448
  if (evidence.targetFingerprint)
301
- details.push(`目标状态:${evidence.targetFingerprint}`);
449
+ details.push(`目标状态:${short(evidence.targetFingerprint)}`);
450
+ if (evidence.offlineCleanup) {
451
+ details.push(`离线清理原因:${evidence.offlineCleanup.reason}`);
452
+ details.push(`离线清理目标:${short(evidence.offlineCleanup.targetFingerprint)}`);
453
+ details.push(`离线清理计划:${evidence.offlineCleanup.plan.actions.length} 个动作 · ${short(evidence.offlineCleanup.planDigest)}`);
454
+ for (const action of evidence.offlineCleanup.plan.actions) {
455
+ details.push(`离线清理动作:${action.action} · ${action.target}`);
456
+ }
457
+ }
302
458
  if (evidence.dependencyPlan)
303
459
  details.push(`安装计划:${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`);
460
+ if (evidence.projectIntentDigest)
461
+ details.push(`Project intent:${short(evidence.projectIntentDigest)}`);
304
462
  if (evidence.config)
305
- details.push(`设置层:${evidence.config.scope}/${evidence.config.projectLayer} · ${evidence.config.storeDigest}`);
463
+ details.push(`设置层:${evidence.config.scope}/${evidence.config.projectLayer} · ${short(evidence.config.storeDigest)}`);
306
464
  if (evidence.sourceWrite) {
307
- details.push(`候选配置:${evidence.sourceWrite.candidateDigest}`);
308
- details.push(`引用清单:${evidence.sourceWrite.inventoryDigest} · ${evidence.sourceWrite.referenceCount} 项`);
465
+ details.push(`新配置:${short(evidence.sourceWrite.proposedConfigDigest)}`);
466
+ details.push(`引用清单:${short(evidence.sourceWrite.inventoryDigest)} · ${evidence.sourceWrite.referenceCount} 项`);
309
467
  }
310
468
  if (evidence.portable) {
311
469
  details.push(`Portable 候选:${evidence.portable.selected.length} 个`);
312
- details.push(`Materialization:${evidence.portable.materializationDigest}`);
470
+ details.push(`Materialization:${short(evidence.portable.materializationDigest)}`);
313
471
  details.push(`Skills CLI:${evidence.portable.skillsCliVersion}`);
314
472
  }
315
473
  return details;
@@ -317,6 +475,8 @@ function technicalDetails(evidence) {
317
475
  function issueCode(error) {
318
476
  if (error instanceof TuiOperationBlockedError)
319
477
  return error.code;
478
+ if (error instanceof CatalogGenerationError)
479
+ return error.code;
320
480
  if (error instanceof Error && error.name === 'CatalogSourcesConfigConflictError') {
321
481
  return 'operationReviewStale';
322
482
  }
@@ -347,9 +507,6 @@ export class TuiController {
347
507
  #router = new TuiRouter();
348
508
  #listeners = new Set();
349
509
  #health = new Map();
350
- #sourceTokens = new Map();
351
- #successfulSourceRefreshes = new Map();
352
- #sourceAborts = new Map();
353
510
  #tickets = new Map();
354
511
  #inflightOperations = new Map();
355
512
  #abort = new AbortController();
@@ -357,6 +514,8 @@ export class TuiController {
357
514
  #progressFeed;
358
515
  #phase = 'idle';
359
516
  #status;
517
+ #catalogGenerations = new Map();
518
+ #sessionGenerationKeys = new Map();
360
519
  #viewModel;
361
520
  #progress = { messages: [] };
362
521
  #progressTargets = [];
@@ -370,9 +529,10 @@ export class TuiController {
370
529
  #portableRequestToken = 0;
371
530
  #portableRequestAbort;
372
531
  #portablePending;
532
+ #installPlacementDraft;
533
+ #installPlacementRequestToken = 0;
534
+ #installPlacementRequestAbort;
373
535
  #generation = 0;
374
- #refreshSequence = 0;
375
- #appliedRefreshSequence = 0;
376
536
  #disposed = false;
377
537
  constructor(dependencies, progressFeed) {
378
538
  this.dependencies = dependencies;
@@ -407,9 +567,13 @@ export class TuiController {
407
567
  get snapshot() {
408
568
  return cloneFrozen({
409
569
  phase: this.#phase,
570
+ ...(this.#status?.cliChannel ? { cliChannel: this.#status.cliChannel } : {}),
410
571
  router: this.#router.snapshot,
411
572
  ...(this.#viewModel ? { viewModel: this.#viewModel } : {}),
412
573
  sourceHealth: [...this.#health.values()],
574
+ catalogGenerations: [...this.#catalogGenerations.values()]
575
+ .sort((left, right) => left.sourceId.localeCompare(right.sourceId, 'en')),
576
+ reloadRequiredSourceIds: [],
413
577
  progress: this.#progress,
414
578
  progressTargets: this.#progressTargets,
415
579
  ...(this.#activeReview ? { activeReview: this.#activeReview } : {}),
@@ -417,6 +581,20 @@ export class TuiController {
417
581
  ...(this.#result ? { result: this.#result } : {}),
418
582
  ...(this.#loadError ? { loadError: this.#loadError } : {}),
419
583
  ...(this.#portableDraft ? { portableDraft: this.#portableDraft } : {}),
584
+ ...(this.#installPlacementDraft
585
+ ? { installPlacementDraft: {
586
+ pluginId: this.#installPlacementDraft.pluginId,
587
+ rootScope: this.#installPlacementDraft.rootScope,
588
+ platforms: this.#installPlacementDraft.platforms,
589
+ dependencyScopeOverrides: this.#installPlacementDraft.dependencyScopeOverrides,
590
+ state: this.#installPlacementDraft.state,
591
+ targets: this.#installPlacementDraft.targets,
592
+ canContinue: this.#installPlacementDraft.canContinue,
593
+ ...(this.#installPlacementDraft.error
594
+ ? { error: this.#installPlacementDraft.error }
595
+ : {}),
596
+ } }
597
+ : {}),
420
598
  });
421
599
  }
422
600
  subscribe(listener) {
@@ -467,6 +645,8 @@ export class TuiController {
467
645
  || route.name === 'pluginDependencies'
468
646
  || route.name === 'installPlacement'
469
647
  || route.name === 'installTargets'
648
+ || route.name === 'dependencyPlacement'
649
+ || route.name === 'dependencyPlacementEditor'
470
650
  || route.name === 'pluginDetail'
471
651
  || route.name === 'platform'
472
652
  || route.name === 'pluginAbout'
@@ -480,6 +660,7 @@ export class TuiController {
480
660
  name: 'pluginList',
481
661
  mode: route.name === 'pluginPreview' || route.name === 'pluginDependencies'
482
662
  || route.name === 'installPlacement' || route.name === 'installTargets'
663
+ || route.name === 'dependencyPlacement' || route.name === 'dependencyPlacementEditor'
483
664
  ? 'browse'
484
665
  : 'installed',
485
666
  };
@@ -505,74 +686,32 @@ export class TuiController {
505
686
  }
506
687
  #applyStatus(status) {
507
688
  this.#status = structuredClone(status);
689
+ if (status.catalogGenerations) {
690
+ this.#catalogGenerations.clear();
691
+ for (const source of status.catalogGenerations) {
692
+ this.#catalogGenerations.set(source.sourceId, structuredClone(source));
693
+ }
694
+ }
695
+ this.#sessionGenerationKeys.clear();
696
+ if (status.sessionGenerationKeys) {
697
+ for (const [sourceId, generationKey] of Object.entries(status.sessionGenerationKeys)) {
698
+ this.#sessionGenerationKeys.set(sourceId, generationKey);
699
+ }
700
+ }
508
701
  this.#health.clear();
509
702
  for (const source of status.payload.catalog.sources) {
510
703
  this.#health.set(source.id, {
511
704
  sourceId: source.id,
512
705
  state: source.error ? 'unavailable'
513
- : source.type === 'path' ? 'path'
514
- : source.freshness === 'cache' ? 'cache' : 'ready',
706
+ : 'ready',
515
707
  ...(source.error ? { message: '来源当前没有可用数据。' } : {}),
516
708
  });
517
- const successful = this.#successfulSourceRefreshes.get(source.id);
518
- if (successful?.generation === this.#generation
519
- && successful.sourceIdentity === source.identity
520
- && successful.requestedRef === source.requestedRef
521
- && !source.error) {
522
- this.#health.set(source.id, {
523
- sourceId: source.id,
524
- state: source.type === 'path' || source.freshness === 'path' ? 'path' : 'ready',
525
- });
526
- }
527
- }
528
- const sourceIds = new Set(status.payload.catalog.sources.map((source) => source.id));
529
- for (const sourceId of this.#successfulSourceRefreshes.keys()) {
530
- if (!sourceIds.has(sourceId))
531
- this.#successfulSourceRefreshes.delete(sourceId);
532
709
  }
533
710
  this.#project();
534
711
  this.#normalizeCurrentRoute();
535
712
  }
536
- #applyRefreshStatus(status, sourceId, health, refreshSequence) {
537
- if (refreshSequence < this.#appliedRefreshSequence)
538
- return false;
539
- this.#appliedRefreshSequence = refreshSequence;
540
- this.#applyStatus(status);
541
- if (!status.payload.catalog.sources.some((source) => source.id === sourceId))
542
- return true;
543
- this.#health.set(sourceId, health);
544
- this.#project();
545
- return true;
546
- }
547
- #recordSuccessfulSourceRefresh(sourceId, checked, generation) {
548
- const authoritative = this.#status?.payload.catalog.sources.find((source) => source.id === sourceId);
549
- if (!authoritative || authoritative.type !== 'git' || authoritative.error
550
- || authoritative.identity !== checked.sourceIdentity
551
- || authoritative.requestedRef !== checked.requestedRef)
552
- return;
553
- this.#successfulSourceRefreshes.set(sourceId, {
554
- generation,
555
- sourceIdentity: checked.sourceIdentity,
556
- requestedRef: checked.requestedRef,
557
- });
558
- this.#health.set(sourceId, {
559
- sourceId,
560
- state: 'ready',
561
- });
562
- }
563
- #cancelSourceRefreshes() {
564
- for (const abort of this.#sourceAborts.values())
565
- abort.abort();
566
- this.#sourceAborts.clear();
567
- }
568
- #startGitSourceRefreshes() {
569
- for (const source of this.#status?.payload.catalog.sources ?? []) {
570
- if (source.type === 'git')
571
- void this.refreshSource(source.id).catch(() => undefined);
572
- }
573
- }
574
713
  async load() {
575
- this.#cancelSourceRefreshes();
714
+ this.dependencies.beginCatalogSession?.();
576
715
  const generation = ++this.#generation;
577
716
  this.#phase = 'loading';
578
717
  this.#publish();
@@ -596,121 +735,11 @@ export class TuiController {
596
735
  this.#phase = 'ready';
597
736
  this.#loadError = undefined;
598
737
  this.#publish();
599
- this.#startGitSourceRefreshes();
600
- }
601
- async refreshSource(sourceId) {
602
- if (this.#disposed || !this.#status)
603
- return;
604
- this.#sourceAborts.get(sourceId)?.abort();
605
- const sourceAbort = new AbortController();
606
- this.#sourceAborts.set(sourceId, sourceAbort);
607
- const finishRefresh = () => {
608
- if (this.#sourceAborts.get(sourceId) === sourceAbort)
609
- this.#sourceAborts.delete(sourceId);
610
- };
611
- const signal = AbortSignal.any([this.#abort.signal, sourceAbort.signal]);
612
- const token = (this.#sourceTokens.get(sourceId) ?? 0) + 1;
613
- this.#sourceTokens.set(sourceId, token);
614
- const generation = this.#generation;
615
- const current = this.#status.payload.catalog.sources.find((source) => source.id === sourceId);
616
- if (!current) {
617
- finishRefresh();
618
- return;
619
- }
620
- const hadCache = current.type === 'git' && current.error === undefined
621
- && (current.freshness === 'cache' || current.freshness === 'remote');
622
- this.#health.set(sourceId, { sourceId, state: 'checking' });
623
- this.#project();
624
- this.#publish();
625
- let checkError;
626
- let checked;
627
- try {
628
- checked = await this.dependencies.checkSource(sourceId, signal);
629
- }
630
- catch (error) {
631
- checkError = error;
632
- }
633
- if (this.#disposed || signal.aborted || generation !== this.#generation
634
- || token !== this.#sourceTokens.get(sourceId)) {
635
- finishRefresh();
636
- return;
637
- }
638
- if (checkError)
639
- this.#successfulSourceRefreshes.delete(sourceId);
640
- const refreshSequence = ++this.#refreshSequence;
641
- let status;
642
- let authoritativeReloadSucceeded = true;
643
- try {
644
- status = await this.dependencies.loadStatus(signal);
645
- }
646
- catch (error) {
647
- authoritativeReloadSucceeded = false;
648
- if (!checkError)
649
- checkError = error;
650
- this.#successfulSourceRefreshes.delete(sourceId);
651
- status = structuredClone(this.#status);
652
- }
653
- if (this.#disposed || signal.aborted || generation !== this.#generation
654
- || token !== this.#sourceTokens.get(sourceId)) {
655
- finishRefresh();
656
- return;
657
- }
658
- if (checkError) {
659
- await this.#logErrorSafe(checkError, 'catalogSourceRefreshFailed', {
660
- privateConfigOperation: true,
661
- });
662
- }
663
- const refreshed = status.payload.catalog.sources.find((source) => source.id === sourceId);
664
- const refreshedMatchesCheck = checked !== undefined && refreshed !== undefined
665
- && refreshed.type === 'git'
666
- && !refreshed.error
667
- && refreshed.identity === checked.sourceIdentity
668
- && refreshed.requestedRef === checked.requestedRef;
669
- const state = checkError
670
- ? authoritativeReloadSucceeded
671
- ? !refreshed || refreshed.error
672
- ? 'unavailable'
673
- : refreshed.type === 'path' || refreshed.freshness === 'path'
674
- ? 'path'
675
- : refreshed.freshness === 'cache' ? 'degraded' : 'unavailable'
676
- : hadCache ? 'degraded' : 'unavailable'
677
- : !refreshed
678
- ? 'unavailable'
679
- : refreshed.error
680
- ? 'unavailable'
681
- : !refreshedMatchesCheck
682
- ? refreshed.type === 'path' || refreshed.freshness === 'path'
683
- ? 'path'
684
- : refreshed.freshness === 'cache' ? 'cache' : 'unavailable'
685
- : refreshed.type === 'path' || refreshed.freshness === 'path'
686
- ? 'path'
687
- : 'ready';
688
- this.#applyRefreshStatus(status, sourceId, {
689
- sourceId,
690
- state,
691
- ...(state === 'degraded' || state === 'unavailable' ? {
692
- issueId: `source:${sourceId}:${state === 'degraded' ? 'cache' : 'unavailable'}`,
693
- message: checkError
694
- ? state === 'degraded'
695
- ? '远端检查失败。当前仍使用已验证的缓存数据。'
696
- : '远端检查失败,并且没有可用缓存数据。'
697
- : state === 'degraded'
698
- ? '远端检查后仍只能使用缓存数据。'
699
- : '来源检查后没有可用数据。',
700
- } : {}),
701
- }, refreshSequence);
702
- if (!checkError && refreshedMatchesCheck && checked) {
703
- this.#recordSuccessfulSourceRefresh(sourceId, checked, generation);
704
- }
705
- finishRefresh();
706
- if (generation !== this.#generation)
707
- return;
708
- this.#project();
709
- this.#publish();
710
738
  }
711
739
  push(route, context) {
712
740
  this.#cancelReviewPreparation();
713
741
  this.#cancelPortableRequest();
742
+ this.#cancelInstallPlacementRequest();
714
743
  this.#discardPortableDraftForRoute(route);
715
744
  this.#router.push(route, context);
716
745
  this.#publish();
@@ -881,6 +910,7 @@ export class TuiController {
881
910
  replace(route, context) {
882
911
  this.#cancelReviewPreparation();
883
912
  this.#cancelPortableRequest();
913
+ this.#cancelInstallPlacementRequest();
884
914
  const current = this.#router.snapshot.current.route;
885
915
  if (current.name === 'portableBlocker' && current.code === 'requiredConfigMissing'
886
916
  && route.name === 'configLayer' && route.pluginId === current.pluginId) {
@@ -907,9 +937,10 @@ export class TuiController {
907
937
  this.#publish();
908
938
  }
909
939
  back() {
940
+ const route = this.#router.snapshot.current.route;
910
941
  this.#cancelReviewPreparation();
911
942
  this.#cancelPortableRequest();
912
- const route = this.#router.snapshot.current.route;
943
+ this.#cancelInstallPlacementRequest();
913
944
  if (route.name === 'review' && this.#activeReview?.operationId === route.operationId) {
914
945
  this.#tickets.delete(this.#activeReview.reviewToken);
915
946
  this.#activeReview = undefined;
@@ -1023,6 +1054,213 @@ export class TuiController {
1023
1054
  throw new DOMException('Portable request was cancelled.', 'AbortError');
1024
1055
  }
1025
1056
  }
1057
+ beginInstallFlow(pluginId, rootScope, options = {}) {
1058
+ this.#cancelInstallPlacementRequest();
1059
+ this.#installPlacementDraft = {
1060
+ pluginId,
1061
+ rootScope,
1062
+ platforms: [],
1063
+ dependencyScopeOverrides: [],
1064
+ state: 'selecting',
1065
+ targets: [],
1066
+ canContinue: false,
1067
+ adjustingExisting: options.adjustingExisting === true,
1068
+ revalidateOverrides: false,
1069
+ };
1070
+ this.#publish();
1071
+ }
1072
+ setInstallRootScope(rootScope) {
1073
+ const draft = this.#installPlacementDraft;
1074
+ if (!draft)
1075
+ return;
1076
+ this.#cancelInstallPlacementRequest();
1077
+ draft.rootScope = rootScope;
1078
+ draft.platforms = [];
1079
+ draft.revalidateOverrides = true;
1080
+ draft.plan = undefined;
1081
+ draft.targets = [];
1082
+ draft.state = 'selecting';
1083
+ draft.canContinue = false;
1084
+ draft.error = undefined;
1085
+ this.#publish();
1086
+ }
1087
+ setInstallPlatforms(platforms) {
1088
+ const draft = this.#installPlacementDraft;
1089
+ if (!draft)
1090
+ return;
1091
+ this.#cancelInstallPlacementRequest();
1092
+ const selected = [...new Set(platforms)].sort();
1093
+ draft.platforms = selected;
1094
+ draft.dependencyScopeOverrides = draft.dependencyScopeOverrides.filter((override) => selected.includes(override.platform));
1095
+ draft.revalidateOverrides = true;
1096
+ draft.plan = undefined;
1097
+ draft.targets = [];
1098
+ draft.state = 'selecting';
1099
+ draft.canContinue = selected.length > 0;
1100
+ draft.error = undefined;
1101
+ this.#publish();
1102
+ }
1103
+ #cancelInstallPlacementRequest() {
1104
+ const cancelled = this.#installPlacementRequestAbort !== undefined;
1105
+ this.#installPlacementRequestToken += 1;
1106
+ this.#installPlacementRequestAbort?.abort();
1107
+ this.#installPlacementRequestAbort = undefined;
1108
+ if (cancelled && this.#installPlacementDraft?.state === 'loading') {
1109
+ this.#installPlacementDraft.plan = undefined;
1110
+ this.#installPlacementDraft.targets = [];
1111
+ this.#installPlacementDraft.state = 'selecting';
1112
+ this.#installPlacementDraft.canContinue = false;
1113
+ this.#installPlacementDraft.error = undefined;
1114
+ }
1115
+ }
1116
+ #installPlacementIntent() {
1117
+ const draft = this.#installPlacementDraft;
1118
+ if (!draft || draft.platforms.length === 0) {
1119
+ throw new TuiOperationBlockedError('installPlacementIncomplete', '请先明确选择目标平台。');
1120
+ }
1121
+ return {
1122
+ kind: 'pluginWrite',
1123
+ action: 'install',
1124
+ pluginId: draft.pluginId,
1125
+ platform: draft.platforms.length === 2 ? 'both' : draft.platforms[0],
1126
+ scope: draft.rootScope,
1127
+ ...(draft.dependencyScopeOverrides.length > 0
1128
+ ? { dependencyScopeOverrides: draft.dependencyScopeOverrides }
1129
+ : {}),
1130
+ ...(draft.adjustingExisting ? { dependencyPlacementAdjustment: true } : {}),
1131
+ };
1132
+ }
1133
+ async prepareInstallDependencyPlacement() {
1134
+ const draft = this.#installPlacementDraft;
1135
+ if (!draft)
1136
+ throw new TuiOperationBlockedError('installPlacementMissing', '安装位置草稿不存在。');
1137
+ this.#cancelInstallPlacementRequest();
1138
+ const abort = new AbortController();
1139
+ this.#installPlacementRequestAbort = abort;
1140
+ const token = this.#installPlacementRequestToken;
1141
+ draft.state = 'loading';
1142
+ draft.canContinue = false;
1143
+ draft.error = undefined;
1144
+ this.#publish();
1145
+ try {
1146
+ const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
1147
+ const preview = async (intent) => this.dependencies.previewDependencyPlacement
1148
+ ? this.dependencies.previewDependencyPlacement(structuredClone(intent), signal)
1149
+ : (await this.dependencies.captureEvidence(structuredClone(intent), signal)).dependencyPlan;
1150
+ const throwIfCancelled = () => {
1151
+ if (this.#disposed || token !== this.#installPlacementRequestToken || abort.signal.aborted) {
1152
+ throw new DOMException('Dependency placement preview was cancelled.', 'AbortError');
1153
+ }
1154
+ };
1155
+ const filterOverrides = (plan) => {
1156
+ const before = draft.dependencyScopeOverrides;
1157
+ const filtered = before.filter((override) => {
1158
+ const target = plan?.targets.find((candidate) => candidate.relation === 'dependency'
1159
+ && candidate.pluginId === override.pluginId
1160
+ && candidate.platform === override.platform);
1161
+ return Boolean(target
1162
+ && target.allowedScopes.includes(override.scope)
1163
+ && !(target.minimumScope === 'user' && override.scope === 'project'));
1164
+ });
1165
+ if (filtered.length === before.length)
1166
+ return false;
1167
+ draft.dependencyScopeOverrides = filtered;
1168
+ return true;
1169
+ };
1170
+ let plan;
1171
+ if (draft.revalidateOverrides && draft.dependencyScopeOverrides.length > 0) {
1172
+ const baselineIntent = this.#installPlacementIntent();
1173
+ delete baselineIntent.dependencyScopeOverrides;
1174
+ const baselinePlan = await preview(baselineIntent);
1175
+ throwIfCancelled();
1176
+ filterOverrides(baselinePlan);
1177
+ plan = draft.dependencyScopeOverrides.length > 0
1178
+ ? await preview(this.#installPlacementIntent())
1179
+ : baselinePlan;
1180
+ throwIfCancelled();
1181
+ if (filterOverrides(plan)) {
1182
+ plan = await preview(this.#installPlacementIntent());
1183
+ throwIfCancelled();
1184
+ }
1185
+ }
1186
+ else {
1187
+ plan = await preview(this.#installPlacementIntent());
1188
+ }
1189
+ throwIfCancelled();
1190
+ draft.revalidateOverrides = false;
1191
+ if (!plan) {
1192
+ draft.state = 'ready';
1193
+ draft.plan = undefined;
1194
+ draft.targets = [];
1195
+ draft.canContinue = true;
1196
+ this.#publish();
1197
+ return false;
1198
+ }
1199
+ draft.plan = structuredClone(plan);
1200
+ draft.targets = dependencyPlacementTargets(plan);
1201
+ const blocker = plan.targets.find((target) => target.action === 'block');
1202
+ draft.state = blocker ? 'blocked' : 'ready';
1203
+ draft.canContinue = !blocker;
1204
+ draft.error = blocker
1205
+ ? {
1206
+ code: blocker.errorCode ?? 'dependencyPlacementBlocked',
1207
+ message: '依赖位置不能满足所选根插件和目标平台。',
1208
+ }
1209
+ : undefined;
1210
+ this.#publish();
1211
+ return draft.targets.some((target) => !target.locked);
1212
+ }
1213
+ catch (error) {
1214
+ if (error instanceof Error && error.name === 'AbortError')
1215
+ throw error;
1216
+ const code = issueCode(error);
1217
+ draft.state = 'blocked';
1218
+ draft.canContinue = false;
1219
+ draft.error = { code, message: '无法计算可信的依赖位置。请返回检查选择。' };
1220
+ this.#publish();
1221
+ throw error;
1222
+ }
1223
+ finally {
1224
+ if (token === this.#installPlacementRequestToken) {
1225
+ this.#installPlacementRequestAbort = undefined;
1226
+ }
1227
+ }
1228
+ }
1229
+ async setDependencyScope(pluginId, platform, scope) {
1230
+ const draft = this.#installPlacementDraft;
1231
+ const target = draft?.targets.find((entry) => entry.pluginId === pluginId && entry.platform === platform);
1232
+ if (!draft || !target || target.locked || !target.allowedScopes.includes(scope)
1233
+ || target.minimumScope === 'user' && scope === 'project') {
1234
+ throw new TuiOperationBlockedError('dependencyScopeOverrideInvalid', '这个依赖不支持所选位置。');
1235
+ }
1236
+ draft.dependencyScopeOverrides = [
1237
+ ...draft.dependencyScopeOverrides.filter((override) => override.pluginId !== pluginId || override.platform !== platform),
1238
+ { pluginId, platform, scope },
1239
+ ].sort((left, right) => `${left.pluginId}\0${left.platform}`
1240
+ .localeCompare(`${right.pluginId}\0${right.platform}`, 'en'));
1241
+ draft.revalidateOverrides = false;
1242
+ await this.prepareInstallDependencyPlacement();
1243
+ }
1244
+ async resetDependencyScopes() {
1245
+ const draft = this.#installPlacementDraft;
1246
+ if (!draft)
1247
+ return;
1248
+ draft.dependencyScopeOverrides = [];
1249
+ draft.revalidateOverrides = false;
1250
+ await this.prepareInstallDependencyPlacement();
1251
+ }
1252
+ installPlacementIntent() {
1253
+ const intent = this.#installPlacementIntent();
1254
+ const narrowsUserTarget = this.#installPlacementDraft?.plan?.targets.some((target) => {
1255
+ if (target.relation !== 'dependency' || target.resolvedScope !== 'project')
1256
+ return false;
1257
+ const persistedScopes = new Set(this.#status?.payload.targets.flatMap((candidate) => candidate.pluginId === target.pluginId && candidate.platform === target.platform
1258
+ ? candidate.desired?.scopes ?? []
1259
+ : []) ?? []);
1260
+ return persistedScopes.has('user') && !persistedScopes.has('project');
1261
+ });
1262
+ return narrowsUserTarget ? { ...intent, dependencyPlacementOnly: 'desired' } : intent;
1263
+ }
1026
1264
  listPlugins(options) {
1027
1265
  const query = options.query?.trim().toLocaleLowerCase('en');
1028
1266
  return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
@@ -1067,47 +1305,46 @@ export class TuiController {
1067
1305
  }));
1068
1306
  }
1069
1307
  return plugin.supportedPlatforms.map((platform) => {
1070
- const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
1071
- if (!placement)
1072
- return { platform, compatible: false, disabled: '当前没有可信的平台能力信息' };
1073
- if (placement.state === 'scopeConflict') {
1074
- return { platform, compatible: false, disabled: '安装位置冲突;请先处理问题' };
1075
- }
1076
- if (placement.state === 'installed') {
1077
- return {
1078
- platform,
1079
- compatible: false,
1080
- disabled: `已安装到${placement.scope === 'user' ? '所有项目' : '当前项目'};请先移除现有安装`,
1081
- };
1082
- }
1083
- if (!placement.supportedScopes.includes(scope)) {
1308
+ const platformTargets = this.#status?.payload.targets.filter((target) => target.pluginId === pluginId && target.platform === platform) ?? [];
1309
+ const rootSummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === pluginId);
1310
+ const rootSupportedScopes = rootSummary?.supportedTargets?.[platform]
1311
+ ?? platformTargets.filter((target) => !target.errors.some((error) => error.code === 'unsupportedTarget')).map((target) => target.scope);
1312
+ if (!rootSupportedScopes.includes(scope)) {
1084
1313
  return { platform, compatible: false, disabled: '不支持所选位置' };
1085
1314
  }
1315
+ const statusTarget = platformTargets.find((target) => target.scope === scope)
1316
+ ?? (platform === 'codex' ? platformTargets[0] : undefined);
1317
+ if (statusTarget && targetHasArtifactCollision(statusTarget)) {
1318
+ return { platform, compatible: false, disabled: '存在输出冲突;请先移除或迁移占用内容' };
1319
+ }
1086
1320
  const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
1087
1321
  for (const dependency of overview?.dependencies ?? []) {
1088
- const dependentPlugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === dependency.pluginId);
1089
- if (!dependentPlugin || !dependentPlugin.supportedPlatforms.includes(platform)) {
1322
+ const dependencySummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === dependency.pluginId);
1323
+ const allowedScopes = dependencySummary?.supportedTargets?.[platform]
1324
+ ?? (this.#status?.payload.targets.filter((target) => target.pluginId === dependency.pluginId
1325
+ && target.platform === platform
1326
+ && !target.errors.some((error) => error.code === 'unsupportedTarget'))
1327
+ .map((target) => target.scope) ?? []);
1328
+ const viableScopes = allowedScopes.filter((candidateScope) => scope === 'project' || candidateScope === 'user');
1329
+ if (viableScopes.length === 0) {
1090
1330
  return {
1091
1331
  platform,
1092
1332
  compatible: false,
1093
- disabled: `${dependency.displayName} 不支持${platform === 'codex' ? 'Codex' : 'Claude Code'}`,
1333
+ disabled: `${dependency.displayName} 不支持${platform === 'codex' ? 'Codex' : 'Claude Code'}或所选位置`,
1094
1334
  };
1095
1335
  }
1096
- const dependencyPlacement = dependentPlugin.installedPlacements.find((entry) => entry.platform === platform);
1097
- if (dependencyPlacement?.state === 'scopeConflict') {
1098
- return { platform, compatible: false, disabled: `${dependency.displayName} 的安装位置冲突` };
1099
- }
1100
- if (dependencyPlacement?.state === 'installed') {
1101
- if (dependencyPlacement.health !== 'ready') {
1102
- return { platform, compatible: false, disabled: `${dependency.displayName} 需要先处理` };
1103
- }
1104
- if (scope === 'user' && dependencyPlacement.scope === 'project') {
1105
- return { platform, compatible: false, disabled: `${dependency.displayName} 只在当前项目可用` };
1106
- }
1107
- }
1108
- if (dependencyPlacement?.state === 'notInstalled'
1109
- && !dependencyPlacement.supportedScopes.includes(scope)) {
1110
- return { platform, compatible: false, disabled: `${dependency.displayName} 不支持所选位置` };
1336
+ const collisionAtEveryViableScope = viableScopes.every((candidateScope) => {
1337
+ const target = this.#status?.payload.targets.find((entry) => entry.pluginId === dependency.pluginId
1338
+ && entry.platform === platform
1339
+ && entry.scope === candidateScope);
1340
+ return Boolean(target && targetHasArtifactCollision(target));
1341
+ });
1342
+ if (collisionAtEveryViableScope) {
1343
+ return {
1344
+ platform,
1345
+ compatible: false,
1346
+ disabled: `${dependency.displayName} 存在输出冲突`,
1347
+ };
1111
1348
  }
1112
1349
  }
1113
1350
  return { platform, compatible: true };
@@ -1128,6 +1365,40 @@ export class TuiController {
1128
1365
  health: this.#health.get(sourceId),
1129
1366
  });
1130
1367
  }
1368
+ getCatalogGeneration(sourceId) {
1369
+ const source = this.#catalogGenerations.get(sourceId);
1370
+ return source ? cloneFrozen(source) : undefined;
1371
+ }
1372
+ getSessionCatalogVersion(sourceId) {
1373
+ return this.#status?.payload.catalog.sources.find((source) => source.id === sourceId)?.catalogVersion;
1374
+ }
1375
+ async reloadCatalog(options) {
1376
+ this.dependencies.beginCatalogSession?.();
1377
+ const generation = ++this.#generation;
1378
+ this.#phase = 'loading';
1379
+ this.#publish();
1380
+ try {
1381
+ const status = await this.dependencies.loadStatus(this.#abort.signal);
1382
+ if (this.#disposed || generation !== this.#generation)
1383
+ return false;
1384
+ this.#applyStatus(status);
1385
+ this.#phase = 'ready';
1386
+ this.#loadError = undefined;
1387
+ this.#router.replace({ name: 'sourceDetail', sourceId: options.sourceId });
1388
+ this.#publish();
1389
+ return true;
1390
+ }
1391
+ catch (error) {
1392
+ if (this.#disposed || generation !== this.#generation)
1393
+ return false;
1394
+ const code = issueCode(error);
1395
+ await this.#logErrorSafe(error, code);
1396
+ this.#phase = 'error';
1397
+ this.#loadError = { code, message: '无法安全重新加载 Catalog。请检查诊断后重试。' };
1398
+ this.#publish();
1399
+ return false;
1400
+ }
1401
+ }
1131
1402
  async inspectSourceRemoval(sourceId) {
1132
1403
  if (!this.dependencies.inspectSourceRemoval) {
1133
1404
  throw new TuiOperationBlockedError('stateInventoryFailed', '来源引用清单不可用。');
@@ -1167,7 +1438,9 @@ export class TuiController {
1167
1438
  catch (error) {
1168
1439
  const code = issueCode(error);
1169
1440
  await this.#logOperationError(error, code, intent);
1170
- throw new TuiOperationBlockedError(code, '操作未建立。请检查当前设置后重试。');
1441
+ throw new TuiOperationBlockedError(code, code === 'catalogGenerationUnavailable'
1442
+ ? '本次加载时 Catalog 来源不可用。请刷新来源并重新加载后重试。'
1443
+ : '操作未建立。请检查当前设置后重试。');
1171
1444
  }
1172
1445
  }
1173
1446
  startReviewPreparation(intent, returnRoute) {
@@ -1239,11 +1512,11 @@ export class TuiController {
1239
1512
  }
1240
1513
  #reviewPreparationIsCurrent(token) {
1241
1514
  const preparation = this.#reviewPreparation;
1515
+ const route = this.#router.snapshot.current.route;
1242
1516
  return Boolean(preparation && preparation.token === token
1243
1517
  && !preparation.abort.signal.aborted
1244
1518
  && !this.#disposed
1245
- && this.#router.snapshot.current.route.name === 'reviewPreparing'
1246
- && this.#router.snapshot.current.route.preparationId === preparation.snapshot.id);
1519
+ && route.name === 'reviewPreparing' && route.preparationId === preparation.snapshot.id);
1247
1520
  }
1248
1521
  async #runReviewPreparation(token) {
1249
1522
  const preparation = this.#reviewPreparation;
@@ -1281,13 +1554,16 @@ export class TuiController {
1281
1554
  catch (error) {
1282
1555
  if (!this.#reviewPreparationIsCurrent(token))
1283
1556
  return;
1557
+ const code = issueCode(error);
1284
1558
  const active = this.#reviewPreparation;
1285
1559
  active.snapshot.state = 'failed';
1286
1560
  active.snapshot.stages = active.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'failed' } : stage);
1287
1561
  active.snapshot.code = 'reviewPreparationFailed';
1288
- active.snapshot.message = '无法准备确认信息。请返回检查选择,或重试。';
1562
+ active.snapshot.message = code === 'catalogGenerationUnavailable'
1563
+ ? '本次加载时 Catalog 来源不可用。请刷新来源并重新加载后重试。'
1564
+ : '无法准备确认信息。请返回检查选择,或重试。';
1289
1565
  this.#publish();
1290
- await this.#logOperationError(error, issueCode(error), intent);
1566
+ await this.#logOperationError(error, code, intent);
1291
1567
  }
1292
1568
  }
1293
1569
  async #prepareReviewEvidence(intent, signal, onStage = () => undefined) {
@@ -1335,6 +1611,7 @@ export class TuiController {
1335
1611
  this.#pruneReviewTickets(now);
1336
1612
  const expiresAt = now
1337
1613
  + (this.dependencies.reviewTtlMs ?? 2 * 60_000);
1614
+ const requiresConfirmation = (evidence.codexReconcilePlans ?? []).some((plan) => plan.review.requiresConfirmation);
1338
1615
  const pluginId = 'pluginId' in intent ? intent.pluginId : intent.sourceId;
1339
1616
  if ('pluginId' in intent)
1340
1617
  sourceIdFromPluginId(intent.pluginId);
@@ -1361,15 +1638,31 @@ export class TuiController {
1361
1638
  ? { version: this.#viewModel.plugins.find((plugin) => plugin.pluginId === intent.pluginId).version }
1362
1639
  : {}),
1363
1640
  },
1641
+ ...(evidence.source && 'catalogVersion' in evidence.source
1642
+ && typeof evidence.source.catalogVersion === 'string'
1643
+ && evidence.source.snapshotDigest
1644
+ ? { catalogBinding: {
1645
+ sourceId: evidence.source.sourceId,
1646
+ catalogVersion: evidence.source.catalogVersion,
1647
+ generationDigestShort: evidence.source.snapshotDigest.replace(/^sha256:/u, '').slice(0, 8),
1648
+ } }
1649
+ : {}),
1364
1650
  changes: reviewChanges(intent, evidence, 'pluginId' in intent ? this.getPluginDependencyOverview(intent.pluginId) : undefined),
1365
1651
  technicalDetails: technicalDetails(evidence),
1366
- ...(intent.kind === 'pluginWrite' && intent.action === 'cleanup'
1367
- ? { equivalentCommand: `aw cleanup ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope}${intent.force === true ? ' --force' : ''}` }
1652
+ ...(intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly !== 'desired'
1653
+ ? { equivalentCommand: `aw ${intent.action} ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope}${(intent.dependencyScopeOverrides ?? []).map((override) => ` --dependency-scope ${override.pluginId}#${override.platform}=${override.scope}`).join('')}${intent.action === 'cleanup' && intent.force === true ? ' --force' : ''}${evidence.codexManager?.state === 'requires-install' ? ' --allow-managed-codex-install' : ''}${requiresConfirmation ? ' --yes' : ''}` }
1368
1654
  : {}),
1369
1655
  primaryLabel: intent.kind === 'pluginWrite' && evidence.dependencyPlan
1370
1656
  ? `${labels.primaryLabel} ${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`
1371
1657
  : labels.primaryLabel,
1372
1658
  ...(labels.destructive ? { destructive: true } : {}),
1659
+ requiresConfirmation,
1660
+ sharedImpact: {
1661
+ pluginIds: [...new Set((evidence.codexReconcilePlans ?? []).flatMap((plan) => plan.review.sharedImpact.pluginIds))],
1662
+ ...((evidence.codexReconcilePlans ?? []).find((plan) => plan.review.sharedImpact.warning)?.review.sharedImpact.warning
1663
+ ? { warning: (evidence.codexReconcilePlans ?? []).find((plan) => plan.review.sharedImpact.warning).review.sharedImpact.warning }
1664
+ : {}),
1665
+ },
1373
1666
  };
1374
1667
  this.#tickets.set(reviewToken, {
1375
1668
  intent: structuredClone(intent),
@@ -1435,7 +1728,9 @@ export class TuiController {
1435
1728
  this.#publish();
1436
1729
  try {
1437
1730
  this.#assertIssueOrigin(ticket.intent);
1438
- await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
1731
+ if (ticket.intent.kind !== 'pluginWrite') {
1732
+ await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
1733
+ }
1439
1734
  }
1440
1735
  catch (error) {
1441
1736
  if (!this.#operationIsCurrent(generation))
@@ -1444,7 +1739,6 @@ export class TuiController {
1444
1739
  await this.#logOperationError(error, code, ticket.intent);
1445
1740
  if (!this.#operationIsCurrent(generation))
1446
1741
  return this.#detachedBlocked(operationId, ticket.intent);
1447
- this.#cancelSourceRefreshes();
1448
1742
  generation = ++this.#generation;
1449
1743
  try {
1450
1744
  const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
@@ -1465,26 +1759,27 @@ export class TuiController {
1465
1759
  }
1466
1760
  if (!this.#operationIsCurrent(generation))
1467
1761
  return this.#detachedBlocked(operationId, ticket.intent);
1468
- let current;
1469
- try {
1470
- current = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
1471
- }
1472
- catch (error) {
1473
- if (!this.#operationIsCurrent(generation))
1474
- return this.#detachedBlocked(operationId, ticket.intent);
1475
- const code = issueCode(error);
1476
- await this.#logOperationError(error, code, ticket.intent);
1762
+ if (ticket.intent.kind !== 'pluginWrite') {
1763
+ let current;
1764
+ try {
1765
+ current = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
1766
+ }
1767
+ catch (error) {
1768
+ if (!this.#operationIsCurrent(generation))
1769
+ return this.#detachedBlocked(operationId, ticket.intent);
1770
+ const code = issueCode(error);
1771
+ await this.#logOperationError(error, code, ticket.intent);
1772
+ if (!this.#operationIsCurrent(generation))
1773
+ return this.#detachedBlocked(operationId, ticket.intent);
1774
+ return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1775
+ }
1477
1776
  if (!this.#operationIsCurrent(generation))
1478
1777
  return this.#detachedBlocked(operationId, ticket.intent);
1479
- return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1480
- }
1481
- if (!this.#operationIsCurrent(generation))
1482
- return this.#detachedBlocked(operationId, ticket.intent);
1483
- if (operationEvidenceFingerprint(current) !== operationEvidenceFingerprint(ticket.evidence)) {
1484
- return this.#rebuildStaleReview(ticket, operationId, current, generation);
1778
+ if (operationEvidenceFingerprint(current) !== operationEvidenceFingerprint(ticket.evidence)) {
1779
+ return this.#rebuildStaleReview(ticket, operationId, current, generation);
1780
+ }
1485
1781
  }
1486
1782
  if (ticket.intent.kind === 'sourceMutation') {
1487
- this.#cancelSourceRefreshes();
1488
1783
  generation = ++this.#generation;
1489
1784
  }
1490
1785
  try {
@@ -1497,9 +1792,11 @@ export class TuiController {
1497
1792
  });
1498
1793
  if (!this.#operationIsCurrent(generation))
1499
1794
  return this.#detachedBlocked(operationId, ticket.intent);
1500
- this.#cancelSourceRefreshes();
1501
1795
  generation = ++this.#generation;
1502
1796
  if (ticket.intent.kind === 'sourceMutation') {
1797
+ this.dependencies.beginCatalogSession?.({
1798
+ reuseValidatedGenerations: true,
1799
+ });
1503
1800
  try {
1504
1801
  this.#progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase: '读取最终状态' }, '来源操作:读取最终状态');
1505
1802
  }
@@ -1545,6 +1842,17 @@ export class TuiController {
1545
1842
  && Boolean(hasFailedDependencyTarget);
1546
1843
  if (pluginSummary?.code === 'operationFailed') {
1547
1844
  const unsuccessfulTargets = pluginSummary.targets?.filter((target) => target.state === 'failed' || target.state === 'skipped') ?? [];
1845
+ const managerFailureCode = unsuccessfulTargets.map((target) => target.errorCode).find((code) => code === 'codexSystemCliUnsupported'
1846
+ || code === 'managedCodexAuthorizationRequired'
1847
+ || code === 'managedCodexUnavailable');
1848
+ if (managerFailureCode) {
1849
+ return this.#setBlocked(operationId, managerFailureCode, {
1850
+ intent: ticket.intent,
1851
+ navigation: 'result',
1852
+ returnRoute: ticket.returnRoute,
1853
+ summary: pluginSummary,
1854
+ });
1855
+ }
1548
1856
  const dependencyStillRequired = unsuccessfulTargets.length > 0
1549
1857
  && unsuccessfulTargets.every((target) => target.errorCode === 'dependencyStillRequired');
1550
1858
  return this.#setBlocked(operationId, dependencyStillRequired ? 'dependencyStillRequired' : 'operationFailed', {
@@ -1637,7 +1945,6 @@ export class TuiController {
1637
1945
  this.#activeReview = undefined;
1638
1946
  this.#router.replace({ name: 'result', operationId });
1639
1947
  this.#publish();
1640
- this.#startGitSourceRefreshes();
1641
1948
  return cloneFrozen(result);
1642
1949
  }
1643
1950
  catch (error) {
@@ -1650,7 +1957,6 @@ export class TuiController {
1650
1957
  if (code === 'operationReviewStale') {
1651
1958
  return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
1652
1959
  }
1653
- this.#cancelSourceRefreshes();
1654
1960
  generation = ++this.#generation;
1655
1961
  try {
1656
1962
  const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
@@ -1688,7 +1994,7 @@ export class TuiController {
1688
1994
  });
1689
1995
  }
1690
1996
  async #rebuildStaleReview(ticket, operationId, currentEvidence, generation = this.#generation) {
1691
- this.#cancelSourceRefreshes();
1997
+ this.dependencies.beginCatalogSession?.();
1692
1998
  generation = ++this.#generation;
1693
1999
  try {
1694
2000
  const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
@@ -1706,15 +2012,16 @@ export class TuiController {
1706
2012
  returnRoute: ticket.returnRoute,
1707
2013
  });
1708
2014
  }
2015
+ const intent = ticket.intent;
1709
2016
  try {
1710
- this.#assertIssueOrigin(ticket.intent);
1711
- await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
2017
+ this.#assertIssueOrigin(intent);
2018
+ await this.dependencies.validateIntent?.(structuredClone(intent), this.#abort.signal);
1712
2019
  }
1713
2020
  catch (error) {
1714
2021
  if (!this.#operationIsCurrent(generation))
1715
2022
  return this.#detachedBlocked(operationId, ticket.intent);
1716
2023
  const code = issueCode(error);
1717
- await this.#logOperationError(error, code, ticket.intent);
2024
+ await this.#logOperationError(error, code, intent);
1718
2025
  if (!this.#operationIsCurrent(generation))
1719
2026
  return this.#detachedBlocked(operationId, ticket.intent);
1720
2027
  const result = {
@@ -1722,7 +2029,7 @@ export class TuiController {
1722
2029
  operationId,
1723
2030
  code,
1724
2031
  message: '状态已变化,原操作不再安全。请修改选择后重试。',
1725
- ...this.#operationIdentity(ticket.intent),
2032
+ ...this.#operationIdentity(intent),
1726
2033
  returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
1727
2034
  };
1728
2035
  this.#result = structuredClone(result);
@@ -1736,50 +2043,56 @@ export class TuiController {
1736
2043
  let evidence = currentEvidence;
1737
2044
  if (!evidence) {
1738
2045
  try {
1739
- evidence = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
2046
+ evidence = await this.dependencies.captureEvidence(structuredClone(intent), this.#abort.signal);
1740
2047
  }
1741
2048
  catch (error) {
1742
2049
  if (!this.#operationIsCurrent(generation))
1743
2050
  return this.#detachedBlocked(operationId, ticket.intent);
1744
2051
  const code = issueCode(error);
1745
- await this.#logOperationError(error, code, ticket.intent);
2052
+ await this.#logOperationError(error, code, intent);
1746
2053
  if (!this.#operationIsCurrent(generation))
1747
2054
  return this.#detachedBlocked(operationId, ticket.intent);
1748
- return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
2055
+ return this.#setBlocked(operationId, code, { intent, returnRoute: ticket.returnRoute });
1749
2056
  }
1750
2057
  }
1751
2058
  if (!this.#operationIsCurrent(generation))
1752
2059
  return this.#detachedBlocked(operationId, ticket.intent);
1753
- if (ticket.intent.kind === 'portableInstall') {
2060
+ if (intent.kind === 'portableInstall') {
1754
2061
  try {
1755
- await this.refreshPortableInstallDraft(ticket.intent.pluginId);
2062
+ await this.refreshPortableInstallDraft(intent.pluginId);
1756
2063
  }
1757
2064
  catch (error) {
1758
2065
  if (!this.#operationIsCurrent(generation))
1759
2066
  return this.#detachedBlocked(operationId, ticket.intent);
1760
2067
  const code = issueCode(error);
1761
- await this.#logOperationError(error, code, ticket.intent);
1762
- return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
2068
+ await this.#logOperationError(error, code, intent);
2069
+ return this.#setBlocked(operationId, code, { intent, returnRoute: ticket.returnRoute });
1763
2070
  }
1764
2071
  }
1765
2072
  if (!this.#operationIsCurrent(generation))
1766
2073
  return this.#detachedBlocked(operationId, ticket.intent);
1767
- const review = this.#storeOperationReview(ticket.intent, evidence, 'replace', ticket.returnRoute);
1768
- const result = { state: 'stale', operationId, review, ...this.#operationIdentity(ticket.intent) };
2074
+ const review = this.#storeOperationReview(intent, evidence, 'replace', ticket.returnRoute);
2075
+ const result = { state: 'stale', operationId, review, ...this.#operationIdentity(intent) };
1769
2076
  this.#result = result;
1770
2077
  this.#publish();
1771
2078
  return cloneFrozen(result);
1772
2079
  }
1773
2080
  #setBlocked(operationId, code, options) {
1774
- const message = code === 'portableSkillsCliInstallFailed'
1775
- ? 'Skills CLI 安装失败。请修改 Agent 或重试。'
1776
- : options?.writeMayHaveCompleted
1777
- ? '写入可能已完成,当前无法确认。请查看刷新后的平台或问题状态。'
1778
- : code === 'configResetStillPresent'
1779
- ? '重置执行后设置层仍然存在。请重新检查设置问题。'
1780
- : code === 'issueStillPresent'
1781
- ? '写入已完成,但重新检查后原问题仍然存在。'
1782
- : '操作未完成。请重新检查后重试。';
2081
+ const message = code === 'managedCodexAuthorizationRequired'
2082
+ ? '操作在插件写入前停止。请在 Review 中确认获取 Codex runtime,或在命令行传入 --allow-managed-codex-install。'
2083
+ : code === 'codexSystemCliUnsupported'
2084
+ ? '操作在插件写入前停止。系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
2085
+ : code === 'managedCodexUnavailable'
2086
+ ? '操作在插件写入前停止。Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。'
2087
+ : code === 'portableSkillsCliInstallFailed'
2088
+ ? 'Skills CLI 安装失败。请修改 Agent 或重试。'
2089
+ : options?.writeMayHaveCompleted
2090
+ ? '写入可能已完成,当前无法确认。请查看刷新后的平台或问题状态。'
2091
+ : code === 'configResetStillPresent'
2092
+ ? '重置执行后设置层仍然存在。请重新检查设置问题。'
2093
+ : code === 'issueStillPresent'
2094
+ ? '写入已完成,但重新检查后原问题仍然存在。'
2095
+ : '操作未完成。请重新检查后重试。';
1783
2096
  const result = {
1784
2097
  state: 'blocked',
1785
2098
  operationId,
@@ -1843,13 +2156,17 @@ export class TuiController {
1843
2156
  ? origin.actionId === 'cleanup' && action.writeAction === 'cleanup'
1844
2157
  ? { action: 'cleanup', scope: issue.scope }
1845
2158
  : undefined
1846
- : issue.category === 'scopeConflict'
1847
- ? origin.actionId === 'keepUserScope'
1848
- ? { action: 'uninstall', scope: 'project' }
1849
- : origin.actionId === 'keepProjectScope'
1850
- ? { action: 'uninstall', scope: 'user' }
1851
- : undefined
1852
- : undefined;
2159
+ : issue.category === 'sourceMissing'
2160
+ ? origin.actionId === 'cleanup' && action.writeAction === 'cleanup'
2161
+ ? { action: 'cleanup', scope: issue.scope }
2162
+ : undefined
2163
+ : issue.category === 'scopeConflict'
2164
+ ? origin.actionId === 'keepUserScope'
2165
+ ? { action: 'uninstall', scope: 'project' }
2166
+ : origin.actionId === 'keepProjectScope'
2167
+ ? { action: 'uninstall', scope: 'user' }
2168
+ : undefined
2169
+ : undefined;
1853
2170
  const dependencyOriginValid = issue.category !== 'dependencyMissing' && issue.category !== 'dependencyNotReady'
1854
2171
  || (origin.consumerPluginId === issue.pluginId
1855
2172
  && origin.consumerSourceIdentity === issue.sourceIdentity
@@ -1870,8 +2187,8 @@ export class TuiController {
1870
2187
  this.#disposed = true;
1871
2188
  this.#cancelReviewPreparation();
1872
2189
  this.#cancelPortableRequest();
2190
+ this.#cancelInstallPlacementRequest();
1873
2191
  this.#abort.abort();
1874
- this.#cancelSourceRefreshes();
1875
2192
  this.#progressUnsubscribe?.();
1876
2193
  this.#tickets.clear();
1877
2194
  this.#phase = 'disposed';
@@ -1879,6 +2196,18 @@ export class TuiController {
1879
2196
  }
1880
2197
  }
1881
2198
  function operationResultSummary(intent, execution) {
2199
+ if (intent.kind === 'pluginWrite' && intent.dependencyPlacementOnly === 'desired') {
2200
+ return {
2201
+ code: 'operationSucceeded',
2202
+ message: '依赖 desired placement 已更新。',
2203
+ pluginId: intent.pluginId,
2204
+ action: intent.action,
2205
+ messages: [
2206
+ '第 1 步:AW 不会扫描未知项目或自动 cleanup;请在已知的所有项目位置显式确认 cleanup。',
2207
+ '第 2 步:cleanup 完成后同步当前项目。',
2208
+ ],
2209
+ };
2210
+ }
1882
2211
  if (intent.kind === 'portableInstall') {
1883
2212
  const receipt = execution;
1884
2213
  const verificationUnknown = receipt.failures.some((failure) => failure.code === 'portableSkillsCliVerificationFailed'
@@ -1950,7 +2279,10 @@ function operationResultSummary(intent, execution) {
1950
2279
  requiredBy: planned.requiredBy,
1951
2280
  } : {}),
1952
2281
  ...(entry.errorCode === 'dependencyStillRequired'
1953
- ? { errorCode: 'dependencyStillRequired' }
2282
+ || entry.errorCode === 'codexSystemCliUnsupported'
2283
+ || entry.errorCode === 'managedCodexAuthorizationRequired'
2284
+ || entry.errorCode === 'managedCodexUnavailable'
2285
+ ? { errorCode: entry.errorCode }
1954
2286
  : {}),
1955
2287
  }];
1956
2288
  });
@@ -1979,7 +2311,16 @@ function operationResultSummary(intent, execution) {
1979
2311
  action: intent.action,
1980
2312
  } : {}),
1981
2313
  ...(inferredTargets.length > 0 ? { targets: inferredTargets } : {}),
1982
- ...(sourceWarnings.length > 0 ? { messages: sourceWarnings } : {}),
2314
+ ...(sourceWarnings.length > 0 || intent.kind === 'pluginWrite' && intent.recoveryDescription
2315
+ ? { messages: [
2316
+ ...sourceWarnings,
2317
+ ...(intent.kind === 'pluginWrite' && intent.recoveryDescription
2318
+ ? [
2319
+ intent.recoveryDescription,
2320
+ `后续:${intent.scope === 'user' ? '当前项目' : '所有项目'} sync`,
2321
+ ] : []),
2322
+ ] }
2323
+ : {}),
1983
2324
  };
1984
2325
  }
1985
2326
  //# sourceMappingURL=controller.js.map