@lesliechan721/aw 0.1.0-beta.10

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 (508) hide show
  1. package/dist/authoring/assets/aw-config.d.mts +51 -0
  2. package/dist/authoring/assets/aw-config.mjs +1025 -0
  3. package/dist/authoring/assets/aw-config.mjs.map +1 -0
  4. package/dist/authoring/scripts/add-skill.d.mts +2 -0
  5. package/dist/authoring/scripts/add-skill.mjs +355 -0
  6. package/dist/authoring/scripts/add-skill.mjs.map +1 -0
  7. package/dist/authoring/scripts/init-plugin.d.mts +2 -0
  8. package/dist/authoring/scripts/init-plugin.mjs +487 -0
  9. package/dist/authoring/scripts/init-plugin.mjs.map +1 -0
  10. package/dist/authoring/scripts/lib/javascript-import-scanner.d.mts +4 -0
  11. package/dist/authoring/scripts/lib/javascript-import-scanner.mjs +261 -0
  12. package/dist/authoring/scripts/lib/javascript-import-scanner.mjs.map +1 -0
  13. package/dist/authoring/scripts/lib/plugin-root.d.mts +26 -0
  14. package/dist/authoring/scripts/lib/plugin-root.mjs +125 -0
  15. package/dist/authoring/scripts/lib/plugin-root.mjs.map +1 -0
  16. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +69 -0
  17. package/dist/authoring/scripts/lib/plugin-version-check.mjs +563 -0
  18. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -0
  19. package/dist/authoring/scripts/lib/semver.d.mts +9 -0
  20. package/dist/authoring/scripts/lib/semver.mjs +63 -0
  21. package/dist/authoring/scripts/lib/semver.mjs.map +1 -0
  22. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +111 -0
  23. package/dist/authoring/scripts/lib/skill-scaffold.mjs +353 -0
  24. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -0
  25. package/dist/authoring/scripts/plugin-status.d.mts +2 -0
  26. package/dist/authoring/scripts/plugin-status.mjs +93 -0
  27. package/dist/authoring/scripts/plugin-status.mjs.map +1 -0
  28. package/dist/authoring/scripts/remove-plugin.d.mts +2 -0
  29. package/dist/authoring/scripts/remove-plugin.mjs +367 -0
  30. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
  31. package/dist/authoring/scripts/validate-plugin.d.mts +2 -0
  32. package/dist/authoring/scripts/validate-plugin.mjs +3465 -0
  33. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -0
  34. package/dist/authoring/scripts/validate-skill.d.mts +2 -0
  35. package/dist/authoring/scripts/validate-skill.mjs +126 -0
  36. package/dist/authoring/scripts/validate-skill.mjs.map +1 -0
  37. package/dist/authoring/templates/aw-config-runtime.d.mts +2 -0
  38. package/dist/authoring/templates/aw-config-runtime.mjs +407 -0
  39. package/dist/authoring/templates/aw-config-runtime.mjs.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/scripts/aw-skill-eval-materializer.d.ts +24 -0
  44. package/dist/scripts/aw-skill-eval-materializer.js +309 -0
  45. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -0
  46. package/dist/scripts/bundle-plugins.d.ts +17 -0
  47. package/dist/scripts/bundle-plugins.js +126 -0
  48. package/dist/scripts/bundle-plugins.js.map +1 -0
  49. package/dist/scripts/plugin-staging.d.ts +26 -0
  50. package/dist/scripts/plugin-staging.js +76 -0
  51. package/dist/scripts/plugin-staging.js.map +1 -0
  52. package/dist/scripts/test-plugin-scripts.d.ts +7 -0
  53. package/dist/scripts/test-plugin-scripts.js +610 -0
  54. package/dist/scripts/test-plugin-scripts.js.map +1 -0
  55. package/dist/src/adapters/helpers.d.ts +44 -0
  56. package/dist/src/adapters/helpers.js +95 -0
  57. package/dist/src/adapters/helpers.js.map +1 -0
  58. package/dist/src/authoring/workspace-commands.d.ts +70 -0
  59. package/dist/src/authoring/workspace-commands.js +158 -0
  60. package/dist/src/authoring/workspace-commands.js.map +1 -0
  61. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  62. package/dist/src/commands/aw-capabilities.js +254 -0
  63. package/dist/src/commands/aw-capabilities.js.map +1 -0
  64. package/dist/src/commands/catalog-dev-source.d.ts +120 -0
  65. package/dist/src/commands/catalog-dev-source.js +434 -0
  66. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  67. package/dist/src/commands/catalog-generation.d.ts +16 -0
  68. package/dist/src/commands/catalog-generation.js +9 -0
  69. package/dist/src/commands/catalog-generation.js.map +1 -0
  70. package/dist/src/commands/catalog-publication-inspection.d.ts +138 -0
  71. package/dist/src/commands/catalog-publication-inspection.js +711 -0
  72. package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
  73. package/dist/src/commands/catalog-publication-journal.d.ts +56 -0
  74. package/dist/src/commands/catalog-publication-journal.js +215 -0
  75. package/dist/src/commands/catalog-publication-journal.js.map +1 -0
  76. package/dist/src/commands/catalog-publication-target.d.ts +18 -0
  77. package/dist/src/commands/catalog-publication-target.js +95 -0
  78. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  79. package/dist/src/commands/catalog-publish.d.ts +103 -0
  80. package/dist/src/commands/catalog-publish.js +804 -0
  81. package/dist/src/commands/catalog-publish.js.map +1 -0
  82. package/dist/src/commands/catalog-source.d.ts +134 -0
  83. package/dist/src/commands/catalog-source.js +377 -0
  84. package/dist/src/commands/catalog-source.js.map +1 -0
  85. package/dist/src/commands/catalog-status.d.ts +56 -0
  86. package/dist/src/commands/catalog-status.js +217 -0
  87. package/dist/src/commands/catalog-status.js.map +1 -0
  88. package/dist/src/commands/catalog-store.d.ts +80 -0
  89. package/dist/src/commands/catalog-store.js +348 -0
  90. package/dist/src/commands/catalog-store.js.map +1 -0
  91. package/dist/src/commands/catalog-workspace-resolution.d.ts +13 -0
  92. package/dist/src/commands/catalog-workspace-resolution.js +42 -0
  93. package/dist/src/commands/catalog-workspace-resolution.js.map +1 -0
  94. package/dist/src/commands/console.d.ts +3 -0
  95. package/dist/src/commands/console.js +44 -0
  96. package/dist/src/commands/console.js.map +1 -0
  97. package/dist/src/commands/dependency-operation-plan.d.ts +58 -0
  98. package/dist/src/commands/dependency-operation-plan.js +261 -0
  99. package/dist/src/commands/dependency-operation-plan.js.map +1 -0
  100. package/dist/src/commands/init.d.ts +25 -0
  101. package/dist/src/commands/init.js +42 -0
  102. package/dist/src/commands/init.js.map +1 -0
  103. package/dist/src/commands/offline-cleanup-evidence.d.ts +59 -0
  104. package/dist/src/commands/offline-cleanup-evidence.js +163 -0
  105. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  106. package/dist/src/commands/operation-plan.d.ts +126 -0
  107. package/dist/src/commands/operation-plan.js +47 -0
  108. package/dist/src/commands/operation-plan.js.map +1 -0
  109. package/dist/src/commands/operation-planner.d.ts +46 -0
  110. package/dist/src/commands/operation-planner.js +139 -0
  111. package/dist/src/commands/operation-planner.js.map +1 -0
  112. package/dist/src/commands/options.d.ts +70 -0
  113. package/dist/src/commands/options.js +194 -0
  114. package/dist/src/commands/options.js.map +1 -0
  115. package/dist/src/commands/plugin-capabilities.d.ts +22 -0
  116. package/dist/src/commands/plugin-capabilities.js +159 -0
  117. package/dist/src/commands/plugin-capabilities.js.map +1 -0
  118. package/dist/src/commands/rebuild-command.d.ts +88 -0
  119. package/dist/src/commands/rebuild-command.js +3626 -0
  120. package/dist/src/commands/rebuild-command.js.map +1 -0
  121. package/dist/src/commands/rebuild-integration.d.ts +190 -0
  122. package/dist/src/commands/rebuild-integration.js +4439 -0
  123. package/dist/src/commands/rebuild-integration.js.map +1 -0
  124. package/dist/src/commands/rebuild-operations.d.ts +100 -0
  125. package/dist/src/commands/rebuild-operations.js +625 -0
  126. package/dist/src/commands/rebuild-operations.js.map +1 -0
  127. package/dist/src/commands/reconcile-operation-plan.d.ts +26 -0
  128. package/dist/src/commands/reconcile-operation-plan.js +28 -0
  129. package/dist/src/commands/reconcile-operation-plan.js.map +1 -0
  130. package/dist/src/commands/review-publication.d.ts +99 -0
  131. package/dist/src/commands/review-publication.js +904 -0
  132. package/dist/src/commands/review-publication.js.map +1 -0
  133. package/dist/src/commands/runtime.d.ts +172 -0
  134. package/dist/src/commands/runtime.js +720 -0
  135. package/dist/src/commands/runtime.js.map +1 -0
  136. package/dist/src/commands/skill-eval.d.ts +33 -0
  137. package/dist/src/commands/skill-eval.js +363 -0
  138. package/dist/src/commands/skill-eval.js.map +1 -0
  139. package/dist/src/commands/status-v6.d.ts +206 -0
  140. package/dist/src/commands/status-v6.js +2175 -0
  141. package/dist/src/commands/status-v6.js.map +1 -0
  142. package/dist/src/commands/sync-command.d.ts +23 -0
  143. package/dist/src/commands/sync-command.js +242 -0
  144. package/dist/src/commands/sync-command.js.map +1 -0
  145. package/dist/src/commands/wizard.d.ts +56 -0
  146. package/dist/src/commands/wizard.js +58 -0
  147. package/dist/src/commands/wizard.js.map +1 -0
  148. package/dist/src/commands/write-commands.d.ts +7 -0
  149. package/dist/src/commands/write-commands.js +190 -0
  150. package/dist/src/commands/write-commands.js.map +1 -0
  151. package/dist/src/constants.d.ts +31 -0
  152. package/dist/src/constants.js +32 -0
  153. package/dist/src/constants.js.map +1 -0
  154. package/dist/src/eval/manifest-v1.d.ts +67 -0
  155. package/dist/src/eval/manifest-v1.js +347 -0
  156. package/dist/src/eval/manifest-v1.js.map +1 -0
  157. package/dist/src/generators/agents.d.ts +3 -0
  158. package/dist/src/generators/agents.js +46 -0
  159. package/dist/src/generators/agents.js.map +1 -0
  160. package/dist/src/generators/hooks.d.ts +15 -0
  161. package/dist/src/generators/hooks.js +76 -0
  162. package/dist/src/generators/hooks.js.map +1 -0
  163. package/dist/src/generators/instructions.d.ts +13 -0
  164. package/dist/src/generators/instructions.js +66 -0
  165. package/dist/src/generators/instructions.js.map +1 -0
  166. package/dist/src/generators/local-plugin.d.ts +56 -0
  167. package/dist/src/generators/local-plugin.js +1373 -0
  168. package/dist/src/generators/local-plugin.js.map +1 -0
  169. package/dist/src/generators/permissions.d.ts +10 -0
  170. package/dist/src/generators/permissions.js +37 -0
  171. package/dist/src/generators/permissions.js.map +1 -0
  172. package/dist/src/generators/shared.d.ts +2 -0
  173. package/dist/src/generators/shared.js +15 -0
  174. package/dist/src/generators/shared.js.map +1 -0
  175. package/dist/src/index.d.ts +41 -0
  176. package/dist/src/index.js +3327 -0
  177. package/dist/src/index.js.map +1 -0
  178. package/dist/src/output/command-receipt.d.ts +87 -0
  179. package/dist/src/output/command-receipt.js +302 -0
  180. package/dist/src/output/command-receipt.js.map +1 -0
  181. package/dist/src/output/debug-log.d.ts +4 -0
  182. package/dist/src/output/debug-log.js +97 -0
  183. package/dist/src/output/debug-log.js.map +1 -0
  184. package/dist/src/output/equivalent-command.d.ts +9 -0
  185. package/dist/src/output/equivalent-command.js +39 -0
  186. package/dist/src/output/equivalent-command.js.map +1 -0
  187. package/dist/src/output/model.d.ts +76 -0
  188. package/dist/src/output/model.js +2 -0
  189. package/dist/src/output/model.js.map +1 -0
  190. package/dist/src/output/rebuild-progress.d.ts +21 -0
  191. package/dist/src/output/rebuild-progress.js +128 -0
  192. package/dist/src/output/rebuild-progress.js.map +1 -0
  193. package/dist/src/output/redact.d.ts +3 -0
  194. package/dist/src/output/redact.js +39 -0
  195. package/dist/src/output/redact.js.map +1 -0
  196. package/dist/src/output/reporter.d.ts +9 -0
  197. package/dist/src/output/reporter.js +133 -0
  198. package/dist/src/output/reporter.js.map +1 -0
  199. package/dist/src/portable/commands.d.ts +124 -0
  200. package/dist/src/portable/commands.js +557 -0
  201. package/dist/src/portable/commands.js.map +1 -0
  202. package/dist/src/portable/materializer.d.ts +50 -0
  203. package/dist/src/portable/materializer.js +462 -0
  204. package/dist/src/portable/materializer.js.map +1 -0
  205. package/dist/src/portable/skills-cli.d.ts +43 -0
  206. package/dist/src/portable/skills-cli.js +148 -0
  207. package/dist/src/portable/skills-cli.js.map +1 -0
  208. package/dist/src/tui/catalog-freshness.d.ts +1 -0
  209. package/dist/src/tui/catalog-freshness.js +2 -0
  210. package/dist/src/tui/catalog-freshness.js.map +1 -0
  211. package/dist/src/tui/catalog-generation.d.ts +21 -0
  212. package/dist/src/tui/catalog-generation.js +116 -0
  213. package/dist/src/tui/catalog-generation.js.map +1 -0
  214. package/dist/src/tui/catalog-sources/projection.d.ts +4 -0
  215. package/dist/src/tui/catalog-sources/projection.js +17 -0
  216. package/dist/src/tui/catalog-sources/projection.js.map +1 -0
  217. package/dist/src/tui/catalog-sources/review.d.ts +3 -0
  218. package/dist/src/tui/catalog-sources/review.js +13 -0
  219. package/dist/src/tui/catalog-sources/review.js.map +1 -0
  220. package/dist/src/tui/catalog-sources/screens.d.ts +12 -0
  221. package/dist/src/tui/catalog-sources/screens.js +33 -0
  222. package/dist/src/tui/catalog-sources/screens.js.map +1 -0
  223. package/dist/src/tui/catalog-sources/session.d.ts +22 -0
  224. package/dist/src/tui/catalog-sources/session.js +225 -0
  225. package/dist/src/tui/catalog-sources/session.js.map +1 -0
  226. package/dist/src/tui/catalog-sources/types.d.ts +41 -0
  227. package/dist/src/tui/catalog-sources/types.js +2 -0
  228. package/dist/src/tui/catalog-sources/types.js.map +1 -0
  229. package/dist/src/tui/components.d.ts +102 -0
  230. package/dist/src/tui/components.js +184 -0
  231. package/dist/src/tui/components.js.map +1 -0
  232. package/dist/src/tui/console-app-v4.d.ts +20 -0
  233. package/dist/src/tui/console-app-v4.js +5066 -0
  234. package/dist/src/tui/console-app-v4.js.map +1 -0
  235. package/dist/src/tui/controller-runtime.d.ts +23 -0
  236. package/dist/src/tui/controller-runtime.js +2011 -0
  237. package/dist/src/tui/controller-runtime.js.map +1 -0
  238. package/dist/src/tui/controller.d.ts +557 -0
  239. package/dist/src/tui/controller.js +2120 -0
  240. package/dist/src/tui/controller.js.map +1 -0
  241. package/dist/src/tui/diagnostics.d.ts +30 -0
  242. package/dist/src/tui/diagnostics.js +71 -0
  243. package/dist/src/tui/diagnostics.js.map +1 -0
  244. package/dist/src/tui/index.d.ts +17 -0
  245. package/dist/src/tui/index.js +17 -0
  246. package/dist/src/tui/index.js.map +1 -0
  247. package/dist/src/tui/input-display.d.ts +8 -0
  248. package/dist/src/tui/input-display.js +36 -0
  249. package/dist/src/tui/input-display.js.map +1 -0
  250. package/dist/src/tui/keys.d.ts +12 -0
  251. package/dist/src/tui/keys.js +22 -0
  252. package/dist/src/tui/keys.js.map +1 -0
  253. package/dist/src/tui/layout.d.ts +12 -0
  254. package/dist/src/tui/layout.js +19 -0
  255. package/dist/src/tui/layout.js.map +1 -0
  256. package/dist/src/tui/model.d.ts +424 -0
  257. package/dist/src/tui/model.js +1493 -0
  258. package/dist/src/tui/model.js.map +1 -0
  259. package/dist/src/tui/operation-review-planner.d.ts +21 -0
  260. package/dist/src/tui/operation-review-planner.js +622 -0
  261. package/dist/src/tui/operation-review-planner.js.map +1 -0
  262. package/dist/src/tui/prompt.d.ts +8 -0
  263. package/dist/src/tui/prompt.js +231 -0
  264. package/dist/src/tui/prompt.js.map +1 -0
  265. package/dist/src/tui/router.d.ts +206 -0
  266. package/dist/src/tui/router.js +137 -0
  267. package/dist/src/tui/router.js.map +1 -0
  268. package/dist/src/tui/runner.d.ts +11 -0
  269. package/dist/src/tui/runner.js +71 -0
  270. package/dist/src/tui/runner.js.map +1 -0
  271. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  272. package/dist/src/tui/source-ref-presets.js +11 -0
  273. package/dist/src/tui/source-ref-presets.js.map +1 -0
  274. package/dist/src/tui/theme.d.ts +26 -0
  275. package/dist/src/tui/theme.js +28 -0
  276. package/dist/src/tui/theme.js.map +1 -0
  277. package/dist/src/tui/viewport.d.ts +20 -0
  278. package/dist/src/tui/viewport.js +29 -0
  279. package/dist/src/tui/viewport.js.map +1 -0
  280. package/dist/src/tui/views.d.ts +57 -0
  281. package/dist/src/tui/views.js +201 -0
  282. package/dist/src/tui/views.js.map +1 -0
  283. package/dist/src/tui/wizard.d.ts +11 -0
  284. package/dist/src/tui/wizard.js +323 -0
  285. package/dist/src/tui/wizard.js.map +1 -0
  286. package/dist/src/types.d.ts +474 -0
  287. package/dist/src/types.js +34 -0
  288. package/dist/src/types.js.map +1 -0
  289. package/dist/src/utils/agent-version.d.ts +19 -0
  290. package/dist/src/utils/agent-version.js +205 -0
  291. package/dist/src/utils/agent-version.js.map +1 -0
  292. package/dist/src/utils/artifact-plan.d.ts +27 -0
  293. package/dist/src/utils/artifact-plan.js +453 -0
  294. package/dist/src/utils/artifact-plan.js.map +1 -0
  295. package/dist/src/utils/artifact-renderer.d.ts +129 -0
  296. package/dist/src/utils/artifact-renderer.js +400 -0
  297. package/dist/src/utils/artifact-renderer.js.map +1 -0
  298. package/dist/src/utils/artifact-request.d.ts +16 -0
  299. package/dist/src/utils/artifact-request.js +42 -0
  300. package/dist/src/utils/artifact-request.js.map +1 -0
  301. package/dist/src/utils/artifact-sources.d.ts +8 -0
  302. package/dist/src/utils/artifact-sources.js +10 -0
  303. package/dist/src/utils/artifact-sources.js.map +1 -0
  304. package/dist/src/utils/aw-home-mutation-lock.d.ts +11 -0
  305. package/dist/src/utils/aw-home-mutation-lock.js +83 -0
  306. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -0
  307. package/dist/src/utils/bundle-integrity.d.ts +2 -0
  308. package/dist/src/utils/bundle-integrity.js +63 -0
  309. package/dist/src/utils/bundle-integrity.js.map +1 -0
  310. package/dist/src/utils/bundle-layout.d.ts +4 -0
  311. package/dist/src/utils/bundle-layout.js +531 -0
  312. package/dist/src/utils/bundle-layout.js.map +1 -0
  313. package/dist/src/utils/catalog-digests.d.ts +18 -0
  314. package/dist/src/utils/catalog-digests.js +156 -0
  315. package/dist/src/utils/catalog-digests.js.map +1 -0
  316. package/dist/src/utils/catalog-generation-control-snapshot.d.ts +11 -0
  317. package/dist/src/utils/catalog-generation-control-snapshot.js +45 -0
  318. package/dist/src/utils/catalog-generation-control-snapshot.js.map +1 -0
  319. package/dist/src/utils/catalog-generation-error.d.ts +16 -0
  320. package/dist/src/utils/catalog-generation-error.js +30 -0
  321. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  322. package/dist/src/utils/catalog-generation-resolver.d.ts +32 -0
  323. package/dist/src/utils/catalog-generation-resolver.js +251 -0
  324. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  325. package/dist/src/utils/catalog-generation-service.d.ts +161 -0
  326. package/dist/src/utils/catalog-generation-service.js +1237 -0
  327. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  328. package/dist/src/utils/catalog-generation-state.d.ts +22 -0
  329. package/dist/src/utils/catalog-generation-state.js +192 -0
  330. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  331. package/dist/src/utils/catalog-generation-store.d.ts +57 -0
  332. package/dist/src/utils/catalog-generation-store.js +397 -0
  333. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  334. package/dist/src/utils/catalog-manifest-v7.d.ts +5 -0
  335. package/dist/src/utils/catalog-manifest-v7.js +98 -0
  336. package/dist/src/utils/catalog-manifest-v7.js.map +1 -0
  337. package/dist/src/utils/catalog-operation-context.d.ts +20 -0
  338. package/dist/src/utils/catalog-operation-context.js +141 -0
  339. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  340. package/dist/src/utils/catalog-public-error.d.ts +19 -0
  341. package/dist/src/utils/catalog-public-error.js +100 -0
  342. package/dist/src/utils/catalog-public-error.js.map +1 -0
  343. package/dist/src/utils/catalog-runtime-projection.d.ts +33 -0
  344. package/dist/src/utils/catalog-runtime-projection.js +77 -0
  345. package/dist/src/utils/catalog-runtime-projection.js.map +1 -0
  346. package/dist/src/utils/catalog-snapshot.d.ts +10 -0
  347. package/dist/src/utils/catalog-snapshot.js +20 -0
  348. package/dist/src/utils/catalog-snapshot.js.map +1 -0
  349. package/dist/src/utils/catalog-source-identity.d.ts +5 -0
  350. package/dist/src/utils/catalog-source-identity.js +137 -0
  351. package/dist/src/utils/catalog-source-identity.js.map +1 -0
  352. package/dist/src/utils/catalog-source-impact.d.ts +20 -0
  353. package/dist/src/utils/catalog-source-impact.js +222 -0
  354. package/dist/src/utils/catalog-source-impact.js.map +1 -0
  355. package/dist/src/utils/catalog-source-presentation.d.ts +1 -0
  356. package/dist/src/utils/catalog-source-presentation.js +32 -0
  357. package/dist/src/utils/catalog-source-presentation.js.map +1 -0
  358. package/dist/src/utils/catalog-source-references.d.ts +66 -0
  359. package/dist/src/utils/catalog-source-references.js +1180 -0
  360. package/dist/src/utils/catalog-source-references.js.map +1 -0
  361. package/dist/src/utils/catalog-sources.d.ts +53 -0
  362. package/dist/src/utils/catalog-sources.js +456 -0
  363. package/dist/src/utils/catalog-sources.js.map +1 -0
  364. package/dist/src/utils/codex-config.d.ts +126 -0
  365. package/dist/src/utils/codex-config.js +1032 -0
  366. package/dist/src/utils/codex-config.js.map +1 -0
  367. package/dist/src/utils/codex-manager.d.ts +81 -0
  368. package/dist/src/utils/codex-manager.js +810 -0
  369. package/dist/src/utils/codex-manager.js.map +1 -0
  370. package/dist/src/utils/codex-marketplace-path.d.ts +5 -0
  371. package/dist/src/utils/codex-marketplace-path.js +45 -0
  372. package/dist/src/utils/codex-marketplace-path.js.map +1 -0
  373. package/dist/src/utils/codex-native-observation.d.ts +16 -0
  374. package/dist/src/utils/codex-native-observation.js +22 -0
  375. package/dist/src/utils/codex-native-observation.js.map +1 -0
  376. package/dist/src/utils/codex-native-render-receipt.d.ts +40 -0
  377. package/dist/src/utils/codex-native-render-receipt.js +205 -0
  378. package/dist/src/utils/codex-native-render-receipt.js.map +1 -0
  379. package/dist/src/utils/codex-reconciler.d.ts +128 -0
  380. package/dist/src/utils/codex-reconciler.js +294 -0
  381. package/dist/src/utils/codex-reconciler.js.map +1 -0
  382. package/dist/src/utils/codex-roles.d.ts +71 -0
  383. package/dist/src/utils/codex-roles.js +257 -0
  384. package/dist/src/utils/codex-roles.js.map +1 -0
  385. package/dist/src/utils/codex-shared-native.d.ts +63 -0
  386. package/dist/src/utils/codex-shared-native.js +142 -0
  387. package/dist/src/utils/codex-shared-native.js.map +1 -0
  388. package/dist/src/utils/codex-skill-staging.d.ts +11 -0
  389. package/dist/src/utils/codex-skill-staging.js +8 -0
  390. package/dist/src/utils/codex-skill-staging.js.map +1 -0
  391. package/dist/src/utils/codex-surface-analysis.d.ts +45 -0
  392. package/dist/src/utils/codex-surface-analysis.js +245 -0
  393. package/dist/src/utils/codex-surface-analysis.js.map +1 -0
  394. package/dist/src/utils/config-value.d.ts +3 -0
  395. package/dist/src/utils/config-value.js +23 -0
  396. package/dist/src/utils/config-value.js.map +1 -0
  397. package/dist/src/utils/dynamic-skill-tree.d.ts +36 -0
  398. package/dist/src/utils/dynamic-skill-tree.js +466 -0
  399. package/dist/src/utils/dynamic-skill-tree.js.map +1 -0
  400. package/dist/src/utils/file-rules.d.ts +1 -0
  401. package/dist/src/utils/file-rules.js +2 -0
  402. package/dist/src/utils/file-rules.js.map +1 -0
  403. package/dist/src/utils/git-catalog-provider.d.ts +22 -0
  404. package/dist/src/utils/git-catalog-provider.js +497 -0
  405. package/dist/src/utils/git-catalog-provider.js.map +1 -0
  406. package/dist/src/utils/hooks-contract.d.ts +1 -0
  407. package/dist/src/utils/hooks-contract.js +2 -0
  408. package/dist/src/utils/hooks-contract.js.map +1 -0
  409. package/dist/src/utils/json.d.ts +4 -0
  410. package/dist/src/utils/json.js +29 -0
  411. package/dist/src/utils/json.js.map +1 -0
  412. package/dist/src/utils/local-marketplace.d.ts +41 -0
  413. package/dist/src/utils/local-marketplace.js +316 -0
  414. package/dist/src/utils/local-marketplace.js.map +1 -0
  415. package/dist/src/utils/manifest.d.ts +11 -0
  416. package/dist/src/utils/manifest.js +33 -0
  417. package/dist/src/utils/manifest.js.map +1 -0
  418. package/dist/src/utils/marketplace.d.ts +3 -0
  419. package/dist/src/utils/marketplace.js +76 -0
  420. package/dist/src/utils/marketplace.js.map +1 -0
  421. package/dist/src/utils/multi-source-catalog.d.ts +66 -0
  422. package/dist/src/utils/multi-source-catalog.js +345 -0
  423. package/dist/src/utils/multi-source-catalog.js.map +1 -0
  424. package/dist/src/utils/native-cli.d.ts +118 -0
  425. package/dist/src/utils/native-cli.js +518 -0
  426. package/dist/src/utils/native-cli.js.map +1 -0
  427. package/dist/src/utils/orphan-marketplace-cleanup.d.ts +18 -0
  428. package/dist/src/utils/orphan-marketplace-cleanup.js +198 -0
  429. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -0
  430. package/dist/src/utils/ownership.d.ts +194 -0
  431. package/dist/src/utils/ownership.js +723 -0
  432. package/dist/src/utils/ownership.js.map +1 -0
  433. package/dist/src/utils/path-catalog-provider.d.ts +7 -0
  434. package/dist/src/utils/path-catalog-provider.js +74 -0
  435. package/dist/src/utils/path-catalog-provider.js.map +1 -0
  436. package/dist/src/utils/paths.d.ts +40 -0
  437. package/dist/src/utils/paths.js +137 -0
  438. package/dist/src/utils/paths.js.map +1 -0
  439. package/dist/src/utils/platform-entries.d.ts +1 -0
  440. package/dist/src/utils/platform-entries.js +2 -0
  441. package/dist/src/utils/platform-entries.js.map +1 -0
  442. package/dist/src/utils/platform-support.d.ts +1 -0
  443. package/dist/src/utils/platform-support.js +2 -0
  444. package/dist/src/utils/platform-support.js.map +1 -0
  445. package/dist/src/utils/plugin-config-write.d.ts +110 -0
  446. package/dist/src/utils/plugin-config-write.js +773 -0
  447. package/dist/src/utils/plugin-config-write.js.map +1 -0
  448. package/dist/src/utils/plugin-config.d.ts +389 -0
  449. package/dist/src/utils/plugin-config.js +1642 -0
  450. package/dist/src/utils/plugin-config.js.map +1 -0
  451. package/dist/src/utils/plugin-dependencies.d.ts +12 -0
  452. package/dist/src/utils/plugin-dependencies.js +162 -0
  453. package/dist/src/utils/plugin-dependencies.js.map +1 -0
  454. package/dist/src/utils/plugin-intent-state.d.ts +34 -0
  455. package/dist/src/utils/plugin-intent-state.js +289 -0
  456. package/dist/src/utils/plugin-intent-state.js.map +1 -0
  457. package/dist/src/utils/plugin-script-contract.d.ts +1 -0
  458. package/dist/src/utils/plugin-script-contract.js +2 -0
  459. package/dist/src/utils/plugin-script-contract.js.map +1 -0
  460. package/dist/src/utils/plugin-scripts.d.ts +101 -0
  461. package/dist/src/utils/plugin-scripts.js +253 -0
  462. package/dist/src/utils/plugin-scripts.js.map +1 -0
  463. package/dist/src/utils/process-lock.d.ts +47 -0
  464. package/dist/src/utils/process-lock.js +532 -0
  465. package/dist/src/utils/process-lock.js.map +1 -0
  466. package/dist/src/utils/process-tree.d.ts +2 -0
  467. package/dist/src/utils/process-tree.js +2 -0
  468. package/dist/src/utils/process-tree.js.map +1 -0
  469. package/dist/src/utils/project-boundary.d.ts +9 -0
  470. package/dist/src/utils/project-boundary.js +67 -0
  471. package/dist/src/utils/project-boundary.js.map +1 -0
  472. package/dist/src/utils/rendered-contract.d.ts +8 -0
  473. package/dist/src/utils/rendered-contract.js +134 -0
  474. package/dist/src/utils/rendered-contract.js.map +1 -0
  475. package/dist/src/utils/runtime-skill-closure.d.ts +13 -0
  476. package/dist/src/utils/runtime-skill-closure.js +863 -0
  477. package/dist/src/utils/runtime-skill-closure.js.map +1 -0
  478. package/dist/src/utils/safe-fs.d.ts +21 -0
  479. package/dist/src/utils/safe-fs.js +111 -0
  480. package/dist/src/utils/safe-fs.js.map +1 -0
  481. package/dist/src/utils/skill-authoring.d.ts +1 -0
  482. package/dist/src/utils/skill-authoring.js +2 -0
  483. package/dist/src/utils/skill-authoring.js.map +1 -0
  484. package/dist/src/utils/skill-validation.d.ts +37 -0
  485. package/dist/src/utils/skill-validation.js +326 -0
  486. package/dist/src/utils/skill-validation.js.map +1 -0
  487. package/dist/src/utils/slash-commands.d.ts +1 -0
  488. package/dist/src/utils/slash-commands.js +2 -0
  489. package/dist/src/utils/slash-commands.js.map +1 -0
  490. package/dist/src/utils/state-paths.d.ts +14 -0
  491. package/dist/src/utils/state-paths.js +139 -0
  492. package/dist/src/utils/state-paths.js.map +1 -0
  493. package/dist/src/utils/target-eligibility.d.ts +13 -0
  494. package/dist/src/utils/target-eligibility.js +35 -0
  495. package/dist/src/utils/target-eligibility.js.map +1 -0
  496. package/dist/src/utils/target-order.d.ts +7 -0
  497. package/dist/src/utils/target-order.js +10 -0
  498. package/dist/src/utils/target-order.js.map +1 -0
  499. package/dist/src/utils/template-engine.d.ts +1 -0
  500. package/dist/src/utils/template-engine.js +2 -0
  501. package/dist/src/utils/template-engine.js.map +1 -0
  502. package/dist/src/utils/test-path-fence.d.ts +12 -0
  503. package/dist/src/utils/test-path-fence.js +31 -0
  504. package/dist/src/utils/test-path-fence.js.map +1 -0
  505. package/dist/vitest.config.d.ts +2 -0
  506. package/dist/vitest.config.js +16 -0
  507. package/dist/vitest.config.js.map +1 -0
  508. package/package.json +64 -0
@@ -0,0 +1,2120 @@
1
+ import crypto from 'node:crypto';
2
+ import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
3
+ import { buildRebuildReceiptJson, requestedIntentAction } from '../output/command-receipt.js';
4
+ import { buildOperationResultModel } from '../commands/operation-planner.js';
5
+ import { buildOperationReviewProjection } from './operation-review-planner.js';
6
+ import { buildTuiViewModel, dependencyPlacementTargets, hasInstalledPlacement, } from './model.js';
7
+ import { safeDiagnosticCode } from './diagnostics.js';
8
+ import { targetHasArtifactCollision } from '../utils/target-eligibility.js';
9
+ import { TuiRouter, } from './router.js';
10
+ import { CatalogSourcesSession } from './catalog-sources/session.js';
11
+ import { catalogSourceAutoCheckOnlyValue, projectCatalogSourceMutationWarnings, } from './catalog-sources/review.js';
12
+ function reviewPreparationStageLabel(stage) {
13
+ return {
14
+ refresh: '刷新相关来源',
15
+ validate: '验证当前状态',
16
+ status: '读取安装状态',
17
+ catalog: '解析 Catalog',
18
+ inventory: '检查本地现场',
19
+ dependencyPlan: '准备依赖计划',
20
+ config: '验证设置',
21
+ portable: '准备 Portable Skills',
22
+ evidence: '准备确认信息',
23
+ }[stage];
24
+ }
25
+ export class TuiOperationBlockedError extends Error {
26
+ code;
27
+ constructor(code, message) {
28
+ super(message);
29
+ this.name = 'TuiOperationBlockedError';
30
+ this.code = code;
31
+ }
32
+ }
33
+ export class TuiConfigInvalidStoreError extends Error {
34
+ code = 'invalidConfigStores';
35
+ evidence;
36
+ constructor(evidence) {
37
+ super('One or more plugin config stores are invalid.');
38
+ this.name = 'TuiConfigInvalidStoreError';
39
+ this.evidence = structuredClone(evidence).sort((left, right) => (left.scope === 'user' ? 0 : 1) - (right.scope === 'user' ? 0 : 1));
40
+ }
41
+ }
42
+ function stableValue(value) {
43
+ if (Array.isArray(value))
44
+ return value.map(stableValue);
45
+ if (typeof value !== 'object' || value === null)
46
+ return value;
47
+ return Object.fromEntries(Object.entries(value)
48
+ .sort(([left], [right]) => left.localeCompare(right, 'en'))
49
+ .map(([key, entry]) => [key, stableValue(entry)]));
50
+ }
51
+ function deepFreeze(value) {
52
+ if (typeof value !== 'object' || value === null || Object.isFrozen(value))
53
+ return value;
54
+ for (const nested of Object.values(value))
55
+ deepFreeze(nested);
56
+ return Object.freeze(value);
57
+ }
58
+ function cloneFrozen(value) {
59
+ return deepFreeze(structuredClone(value));
60
+ }
61
+ function publicReviewPreparation(snapshot) {
62
+ if (snapshot.intent.kind !== 'catalogGeneration' || !snapshot.intent.sourceMutation)
63
+ return snapshot;
64
+ const mutation = snapshot.intent.sourceMutation;
65
+ const sourceMutation = mutation.action === 'add'
66
+ ? {
67
+ ...mutation,
68
+ source: mutation.source.type === 'path'
69
+ ? { ...mutation.source, path: '<private>' }
70
+ : { ...mutation.source, url: '<private>' },
71
+ }
72
+ : {
73
+ ...mutation,
74
+ update: 'url' in mutation.update
75
+ ? { ...mutation.update, ...(mutation.update.url ? { url: '<private>' } : {}) }
76
+ : { ...mutation.update, ...('path' in mutation.update && mutation.update.path
77
+ ? { path: '<private>' } : {}) },
78
+ };
79
+ return { ...snapshot, intent: { ...snapshot.intent, sourceMutation } };
80
+ }
81
+ export function operationEvidenceFingerprint(evidence) {
82
+ if (evidence.operationPlan)
83
+ return evidence.operationPlan.planFingerprint;
84
+ if (evidence.resetTarget) {
85
+ return crypto.createHash('sha256')
86
+ .update(JSON.stringify(stableValue({
87
+ resetTarget: evidence.resetTarget,
88
+ ...(evidence.source ? { source: evidence.source } : {}),
89
+ })))
90
+ .digest('hex');
91
+ }
92
+ const { codexManager: _runtimeSelection, ...staleEvidence } = evidence;
93
+ return crypto.createHash('sha256')
94
+ .update(JSON.stringify(stableValue(staleEvidence)))
95
+ .digest('hex');
96
+ }
97
+ export function operationEvidenceMatches(expected, current) {
98
+ if (expected.resetTarget && current.resetTarget) {
99
+ if (JSON.stringify(stableValue(expected.resetTarget))
100
+ !== JSON.stringify(stableValue(current.resetTarget))) {
101
+ return false;
102
+ }
103
+ if (!expected.source)
104
+ return true;
105
+ return current.source !== undefined
106
+ && JSON.stringify(stableValue(expected.source))
107
+ === JSON.stringify(stableValue(current.source));
108
+ }
109
+ return operationEvidenceFingerprint(expected) === operationEvidenceFingerprint(current);
110
+ }
111
+ function sourceIdFromPluginId(pluginId) {
112
+ const separator = pluginId.indexOf('/');
113
+ if (separator <= 0)
114
+ throw new TuiOperationBlockedError('invalidPluginId', '操作必须使用 canonical pluginId。');
115
+ return pluginId.slice(0, separator);
116
+ }
117
+ function pluginNameFromId(pluginId) {
118
+ const separator = pluginId.indexOf('/');
119
+ return separator < 0 ? pluginId : pluginId.slice(separator + 1);
120
+ }
121
+ export function userIssueKey(issue) {
122
+ return [issue.category, issue.pluginId ?? '', issue.dependencyPluginId ?? '',
123
+ issue.dependencyPlatform ?? '', issue.dependencyScope ?? '', issue.dependencyProject ?? '',
124
+ issue.sourceId ?? '', issue.sourceIdentity ?? '', issue.platform ?? '', issue.scope ?? '']
125
+ .join('\0');
126
+ }
127
+ function issueCode(error) {
128
+ if (error instanceof TuiOperationBlockedError)
129
+ return error.code;
130
+ if (error instanceof CatalogGenerationError)
131
+ return error.code;
132
+ if (error instanceof Error && error.name === 'CatalogSourcesConfigConflictError') {
133
+ return 'operationReviewStale';
134
+ }
135
+ const message = error instanceof Error ? error.message : String(error);
136
+ const prefix = message.split(':', 1)[0];
137
+ return /^[A-Za-z][A-Za-z0-9]*$/u.test(prefix) ? prefix : 'operationBlocked';
138
+ }
139
+ export function extractPortableError(error) {
140
+ const typedCode = typeof error === 'object' && error !== null
141
+ && 'code' in error && typeof error.code === 'string'
142
+ ? error.code
143
+ : undefined;
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ const fallback = message.match(/^([A-Za-z][A-Za-z0-9]*):/u)?.[1];
146
+ const diagnosticCode = safeDiagnosticCode(typedCode ?? fallback, 'portableValidationFailed');
147
+ if (diagnosticCode === 'requiredConfigMissing')
148
+ return { code: 'requiredConfigMissing', diagnosticCode };
149
+ if (diagnosticCode === 'portableSkillsCliMissing')
150
+ return { code: 'portableSkillsCliMissing', diagnosticCode };
151
+ if (diagnosticCode === 'portableSkillsCliIncompatible')
152
+ return { code: 'portableSkillsCliIncompatible', diagnosticCode };
153
+ if (diagnosticCode === 'portableScopeUnavailable')
154
+ return { code: 'portableScopeUnavailable', diagnosticCode };
155
+ return { code: 'portableValidationFailed', diagnosticCode };
156
+ }
157
+ export class TuiController {
158
+ dependencies;
159
+ #router = new TuiRouter();
160
+ #listeners = new Set();
161
+ #health = new Map();
162
+ #tickets = new Map();
163
+ #inflightOperations = new Map();
164
+ #abort = new AbortController();
165
+ #progressUnsubscribe;
166
+ #progressFeed;
167
+ #phase = 'idle';
168
+ #status;
169
+ #catalogGenerations = new Map();
170
+ #catalogFreshness = new Map();
171
+ #catalogSourcesSession;
172
+ #sessionGenerationKeys = new Map();
173
+ #viewModel;
174
+ #progress = { messages: [] };
175
+ #progressTargets = [];
176
+ #activeReview;
177
+ #reviewPreparation;
178
+ #reviewPreparationToken = 0;
179
+ #result;
180
+ #loadError;
181
+ #portableDraft;
182
+ #portableRepreparePluginId;
183
+ #portableRequestToken = 0;
184
+ #portableRequestAbort;
185
+ #portablePending;
186
+ #installPlacementDraft;
187
+ #installPlacementRequestToken = 0;
188
+ #installPlacementRequestAbort;
189
+ #generation = 0;
190
+ #disposed = false;
191
+ #externalDraft;
192
+ constructor(dependencies, progressFeed) {
193
+ this.dependencies = dependencies;
194
+ this.#progressFeed = progressFeed;
195
+ this.#catalogSourcesSession = new CatalogSourcesSession({
196
+ parentSignal: this.#abort.signal,
197
+ probe: async (sourceId, options, signal) => {
198
+ if (!this.dependencies.probeCatalogFreshness) {
199
+ throw new TuiOperationBlockedError('catalogFreshnessUnavailable', '来源检查入口不可用。');
200
+ }
201
+ return this.dependencies.probeCatalogFreshness(sourceId, options, signal);
202
+ },
203
+ onChange: (sourceId, freshness) => {
204
+ this.#catalogFreshness.set(sourceId, structuredClone(freshness));
205
+ this.#project();
206
+ this.#publish();
207
+ },
208
+ onError: (error, code) => {
209
+ void this.#logErrorSafe(error, code);
210
+ },
211
+ });
212
+ if (progressFeed) {
213
+ this.#progressUnsubscribe = progressFeed.subscribe((progress) => {
214
+ this.#progress = {
215
+ ...structuredClone(progress),
216
+ messages: progress.messages.slice(-8),
217
+ ...(this.#progress.startedAtMs !== undefined ? { startedAtMs: this.#progress.startedAtMs } : {}),
218
+ };
219
+ const current = progress.current;
220
+ if (current) {
221
+ const [pluginName, placement = ''] = current.target.split(' · ', 2);
222
+ const platform = placement.startsWith('Codex') ? 'codex'
223
+ : placement.startsWith('Claude Code') ? 'claude' : undefined;
224
+ const scope = placement.endsWith('当前项目') ? 'project'
225
+ : placement.endsWith('当前用户') ? 'user' : undefined;
226
+ if (pluginName && platform && scope) {
227
+ this.#progressTargets = this.#progressTargets.map((target) => pluginNameFromId(target.pluginId) === pluginName
228
+ && target.platform === platform && target.scope === scope
229
+ ? { ...target, state: target.state === 'failed' ? 'failed'
230
+ : current.phase.endsWith('失败') ? 'failed'
231
+ : current.phase.endsWith('已完成') ? 'completed' : 'running' }
232
+ : target);
233
+ }
234
+ }
235
+ this.#publish();
236
+ });
237
+ }
238
+ }
239
+ get snapshot() {
240
+ return cloneFrozen({
241
+ phase: this.#phase,
242
+ ...(this.#status?.cliChannel ? { cliChannel: this.#status.cliChannel } : {}),
243
+ router: this.#router.snapshot,
244
+ ...(this.#viewModel ? { viewModel: this.#viewModel } : {}),
245
+ sourceHealth: [...this.#health.values()],
246
+ catalogGenerations: [...this.#catalogGenerations.values()]
247
+ .sort((left, right) => left.sourceId.localeCompare(right.sourceId, 'en')),
248
+ catalogFreshness: [...this.#catalogFreshness.values()]
249
+ .sort((left, right) => left.sourceId.localeCompare(right.sourceId, 'en')),
250
+ reloadRequiredSourceIds: [],
251
+ progress: this.#progress,
252
+ progressTargets: this.#progressTargets,
253
+ ...(this.#activeReview ? { activeReview: this.#activeReview } : {}),
254
+ ...(this.#reviewPreparation
255
+ ? { reviewPreparation: publicReviewPreparation(this.#reviewPreparation.snapshot) }
256
+ : {}),
257
+ ...(this.#result ? { result: this.#result } : {}),
258
+ ...(this.#loadError ? { loadError: this.#loadError } : {}),
259
+ ...(this.#portableDraft ? { portableDraft: this.#portableDraft } : {}),
260
+ ...(this.#installPlacementDraft
261
+ ? { installPlacementDraft: {
262
+ pluginId: this.#installPlacementDraft.pluginId,
263
+ rootScope: this.#installPlacementDraft.rootScope,
264
+ platforms: this.#installPlacementDraft.platforms,
265
+ dependencyScopeOverrides: this.#installPlacementDraft.dependencyScopeOverrides,
266
+ state: this.#installPlacementDraft.state,
267
+ targets: this.#installPlacementDraft.targets,
268
+ canContinue: this.#installPlacementDraft.canContinue,
269
+ ...(this.#installPlacementDraft.error
270
+ ? { error: this.#installPlacementDraft.error }
271
+ : {}),
272
+ } }
273
+ : {}),
274
+ });
275
+ }
276
+ subscribe(listener) {
277
+ this.#listeners.add(listener);
278
+ listener(this.snapshot);
279
+ return () => this.#listeners.delete(listener);
280
+ }
281
+ #publish() {
282
+ if (this.#disposed)
283
+ return;
284
+ const snapshot = this.snapshot;
285
+ for (const listener of this.#listeners)
286
+ listener(snapshot);
287
+ }
288
+ async #logErrorSafe(error, code, context) {
289
+ try {
290
+ await this.dependencies.logError?.(error, code, context);
291
+ }
292
+ catch {
293
+ // Diagnostics must not replace the fixed safe Controller result.
294
+ }
295
+ }
296
+ #project() {
297
+ if (!this.#status)
298
+ return;
299
+ this.#viewModel = buildTuiViewModel({
300
+ status: this.#status.payload,
301
+ pluginSummaries: this.#status.pluginSummaries,
302
+ targetRevisions: this.#status.targetRevisions,
303
+ sourceHealth: this.#health,
304
+ sourceDetails: this.#status.sourceDetails,
305
+ catalogFreshness: this.#catalogFreshness,
306
+ catalogGenerations: this.#catalogGenerations,
307
+ dependencyOverviews: this.#status.dependencyOverviews,
308
+ portablePluginIds: this.#status.portablePluginIds,
309
+ });
310
+ }
311
+ #normalizeRoute(route) {
312
+ if (route.name === 'pluginList' && route.sourceId
313
+ && !this.#viewModel?.sourceTabs.some((source) => source.id === route.sourceId)) {
314
+ return { name: 'pluginList', mode: route.mode };
315
+ }
316
+ const pluginRoute = route.name === 'pluginPreview'
317
+ || route.name === 'portableSkillSelect'
318
+ || route.name === 'portableDependencies'
319
+ || route.name === 'portableAgentMode'
320
+ || route.name === 'portableAgentInput'
321
+ || route.name === 'portableScope'
322
+ || route.name === 'portableBlocker'
323
+ || route.name === 'pluginDependencies'
324
+ || route.name === 'installPlacement'
325
+ || route.name === 'installTargets'
326
+ || route.name === 'dependencyPlacement'
327
+ || route.name === 'dependencyPlacementEditor'
328
+ || route.name === 'pluginDetail'
329
+ || route.name === 'platform'
330
+ || route.name === 'pluginAbout'
331
+ || route.name === 'pluginTechnical'
332
+ || route.name === 'configLayer'
333
+ || route.name === 'configFields'
334
+ || route.name === 'configFieldEditor'
335
+ || route.name === 'configUnavailable';
336
+ if (pluginRoute && !this.#viewModel?.plugins.some((plugin) => plugin.pluginId === route.pluginId)) {
337
+ return {
338
+ name: 'pluginList',
339
+ mode: route.name === 'pluginPreview' || route.name === 'pluginDependencies'
340
+ || route.name === 'installPlacement' || route.name === 'installTargets'
341
+ || route.name === 'dependencyPlacement' || route.name === 'dependencyPlacementEditor'
342
+ ? 'browse'
343
+ : 'installed',
344
+ };
345
+ }
346
+ if (route.name === 'issueDetail'
347
+ && !this.#viewModel?.issues.some((issue) => issue.issueId === route.issueId)) {
348
+ return { name: 'issueList' };
349
+ }
350
+ if ((route.name === 'sourceDetail'
351
+ || route.name === 'sourceTechnical'
352
+ || route.name === 'sourceEdit'
353
+ || route.name === 'sourceRemoveBlocked')
354
+ && !this.#viewModel?.sourceTabs.some((source) => source.id === route.sourceId)) {
355
+ return { name: 'sourceList' };
356
+ }
357
+ return route;
358
+ }
359
+ #normalizeCurrentRoute() {
360
+ const route = this.#router.snapshot.current.route;
361
+ const normalized = this.#normalizeRoute(route);
362
+ if (normalized !== route)
363
+ this.#router.replace(normalized);
364
+ }
365
+ #applyStatus(status) {
366
+ this.#status = structuredClone(status);
367
+ if (status.catalogGenerations) {
368
+ this.#catalogGenerations.clear();
369
+ for (const source of status.catalogGenerations) {
370
+ this.#catalogGenerations.set(source.sourceId, structuredClone(source));
371
+ }
372
+ }
373
+ this.#sessionGenerationKeys.clear();
374
+ if (status.sessionGenerationKeys) {
375
+ for (const [sourceId, generationKey] of Object.entries(status.sessionGenerationKeys)) {
376
+ this.#sessionGenerationKeys.set(sourceId, generationKey);
377
+ }
378
+ }
379
+ this.#health.clear();
380
+ for (const source of status.payload.catalog.sources) {
381
+ this.#health.set(source.id, {
382
+ sourceId: source.id,
383
+ state: source.error ? 'unavailable'
384
+ : 'ready',
385
+ ...(source.error ? { message: '来源当前没有可用数据。' } : {}),
386
+ });
387
+ if (!this.#catalogFreshness.has(source.id)) {
388
+ this.#catalogFreshness.set(source.id, { sourceId: source.id, state: 'notChecked' });
389
+ }
390
+ }
391
+ const configuredSourceIds = new Set(status.payload.catalog.sources.map((source) => source.id));
392
+ for (const sourceId of this.#catalogFreshness.keys()) {
393
+ if (configuredSourceIds.has(sourceId))
394
+ continue;
395
+ this.#catalogFreshness.delete(sourceId);
396
+ }
397
+ const details = new Map((status.sourceDetails ?? []).map((detail) => [detail.id, detail]));
398
+ this.#catalogSourcesSession.configure(status.payload.catalog.sources.map((source) => {
399
+ const generation = this.#catalogGenerations.get(source.id);
400
+ const detail = details.get(source.id);
401
+ return {
402
+ sourceId: source.id,
403
+ primary: source.primary,
404
+ autoCheck: this.dependencies.probeCatalogFreshness !== undefined
405
+ && (detail?.autoCheck ?? true),
406
+ fingerprint: JSON.stringify({
407
+ sourceIdentity: source.identity,
408
+ requestedRef: source.requestedRef,
409
+ activeStoreKey: generation?.active?.storeKey,
410
+ generationState: generation?.state,
411
+ generationErrorCode: generation?.errorCode,
412
+ }),
413
+ fixedCommit: generation?.state === 'ready'
414
+ && generation.active !== undefined
415
+ && detail?.type === 'git'
416
+ && detail.refPolicy === 'explicit'
417
+ && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/iu.test(detail.explicitRef ?? ''),
418
+ };
419
+ }));
420
+ this.#project();
421
+ this.#normalizeCurrentRoute();
422
+ }
423
+ async load() {
424
+ this.dependencies.beginCatalogSession?.();
425
+ const generation = ++this.#generation;
426
+ this.#phase = 'loading';
427
+ this.#publish();
428
+ let status;
429
+ try {
430
+ status = await this.dependencies.loadStatus(this.#abort.signal);
431
+ }
432
+ catch (error) {
433
+ if (this.#disposed || generation !== this.#generation)
434
+ return;
435
+ const code = issueCode(error);
436
+ await this.#logErrorSafe(error, code);
437
+ this.#phase = 'error';
438
+ this.#loadError = { code, message: '无法安全读取插件状态。请检查诊断后重试或退出。' };
439
+ this.#publish();
440
+ return;
441
+ }
442
+ if (this.#disposed || generation !== this.#generation)
443
+ return;
444
+ this.#applyStatus(status);
445
+ this.#phase = 'ready';
446
+ this.#loadError = undefined;
447
+ this.#publish();
448
+ }
449
+ push(route, context) {
450
+ this.#cancelReviewPreparation();
451
+ this.#cancelPortableRequest();
452
+ this.#cancelInstallPlacementRequest();
453
+ this.#discardPortableDraftForRoute(route);
454
+ this.#router.push(route, context);
455
+ this.#publish();
456
+ }
457
+ hasPortableSkills(pluginId) {
458
+ return this.#status?.portablePluginIds?.includes(pluginId) ?? false;
459
+ }
460
+ preparePortableInstall(pluginId, navigation = 'push') {
461
+ if (!this.hasPortableSkills(pluginId)) {
462
+ throw new TuiOperationBlockedError('portableUnsupported', '插件不提供通用 Skills。');
463
+ }
464
+ this.#router.beginPortableFlow(pluginId);
465
+ const expectedRoute = this.#router.snapshot.current.route;
466
+ return this.#runPortableRequest(`prepare:${pluginId}`, async (signal, token) => {
467
+ const draft = await this.#buildPortableDraft(pluginId, false, signal);
468
+ this.#assertPortableRequestCurrent(token, expectedRoute);
469
+ this.#portableDraft = draft;
470
+ this.#router[navigation]({ name: 'portableSkillSelect', pluginId });
471
+ this.#publish();
472
+ return cloneFrozen(draft);
473
+ });
474
+ }
475
+ refreshPortableInstallDraft(pluginId) {
476
+ const expectedRoute = this.#router.snapshot.current.route;
477
+ return this.#runPortableRequest(`refresh:${pluginId}`, async (signal, token) => {
478
+ const draft = await this.#buildPortableDraft(pluginId, true, signal);
479
+ this.#assertPortableRequestCurrent(token, expectedRoute);
480
+ this.#portableDraft = draft;
481
+ this.#publish();
482
+ return cloneFrozen(draft);
483
+ });
484
+ }
485
+ async #buildPortableDraft(pluginId, preserveSelection, signal) {
486
+ const previous = preserveSelection && this.#portableDraft?.pluginId === pluginId
487
+ ? this.#portableDraft
488
+ : undefined;
489
+ const prepared = await Promise.allSettled(['project', 'user'].map(async (scope) => ({
490
+ scope,
491
+ evidence: await this.dependencies.captureEvidence({
492
+ kind: 'portableInstall',
493
+ pluginId,
494
+ scope,
495
+ selectedSkills: [],
496
+ }, signal),
497
+ })));
498
+ const available = prepared.flatMap((entry) => entry.status === 'fulfilled' && entry.value.evidence.portable ? [entry.value] : []);
499
+ if (available.length === 0) {
500
+ const failure = prepared.find((entry) => entry.status === 'rejected');
501
+ if (failure)
502
+ throw failure.reason;
503
+ throw new TuiOperationBlockedError('portablePrepareFailed', '无法准备通用 Skills。');
504
+ }
505
+ const merged = new Map();
506
+ for (const { scope, evidence } of available) {
507
+ for (const candidate of evidence.portable.selected) {
508
+ const current = merged.get(candidate.name);
509
+ const scopeDetail = {
510
+ description: candidate.description,
511
+ providerPluginId: candidate.providerPluginId,
512
+ providerDisplayName: candidate.providerDisplayName,
513
+ relation: candidate.relation,
514
+ dynamic: candidate.dynamic,
515
+ filesDigest: candidate.filesDigest,
516
+ };
517
+ if (current) {
518
+ current.availableScopes.push(scope);
519
+ current.scopeDetails[scope] = scopeDetail;
520
+ continue;
521
+ }
522
+ merged.set(candidate.name, {
523
+ ...candidate,
524
+ selected: true,
525
+ availableScopes: [scope],
526
+ scopeDetails: { [scope]: scopeDetail },
527
+ });
528
+ }
529
+ }
530
+ const selectedNames = new Set(previous?.candidates
531
+ .filter((candidate) => candidate.selected)
532
+ .map((candidate) => candidate.name) ?? []);
533
+ const candidates = [...merged.values()].map((candidate) => ({
534
+ ...candidate,
535
+ selected: previous ? selectedNames.has(candidate.name) : true,
536
+ }));
537
+ const dependencies = this.getPluginDependencyOverview(pluginId);
538
+ const selectedCount = candidates.filter((candidate) => candidate.selected).length;
539
+ return {
540
+ pluginId,
541
+ candidates,
542
+ selectedCount,
543
+ totalCount: candidates.length,
544
+ canContinue: selectedCount > 0,
545
+ ...(dependencies ? { dependencySummary: {
546
+ directCount: dependencies.summary.directCount,
547
+ transitiveCount: dependencies.summary.transitiveCount,
548
+ contributingSkillCount: candidates.filter((candidate) => candidate.relation !== 'requested').length,
549
+ } } : {}),
550
+ agentMode: previous?.agentMode ?? 'automatic',
551
+ requestedAgents: previous?.requestedAgents ?? [],
552
+ scope: previous?.scope ?? 'project',
553
+ };
554
+ }
555
+ togglePortableSkill(name) {
556
+ if (!this.#portableDraft)
557
+ return;
558
+ this.#portableDraft.candidates = this.#portableDraft.candidates.map((candidate) => candidate.name === name ? { ...candidate, selected: !candidate.selected } : candidate);
559
+ this.#portableDraft.selectedCount = this.#portableDraft.candidates.filter((candidate) => candidate.selected).length;
560
+ this.#portableDraft.canContinue = this.#portableDraft.selectedCount > 0;
561
+ this.#publish();
562
+ }
563
+ selectAllPortableSkills() {
564
+ if (!this.#portableDraft)
565
+ return;
566
+ this.#portableDraft.candidates = this.#portableDraft.candidates.map((candidate) => ({ ...candidate, selected: true }));
567
+ this.#portableDraft.selectedCount = this.#portableDraft.totalCount;
568
+ this.#portableDraft.canContinue = this.#portableDraft.totalCount > 0;
569
+ this.#publish();
570
+ }
571
+ setPortableAgentMode(mode, requestedAgents = []) {
572
+ if (!this.#portableDraft)
573
+ return;
574
+ this.#portableDraft.agentMode = mode;
575
+ this.#portableDraft.requestedAgents = [...requestedAgents];
576
+ this.#publish();
577
+ }
578
+ setPortableScope(scope) {
579
+ if (!this.#portableDraft)
580
+ return;
581
+ this.#portableDraft.scope = scope;
582
+ this.#publish();
583
+ }
584
+ async buildPortableInstallReview() {
585
+ const intent = this.#portableInstallIntent();
586
+ const draft = this.#portableDraft;
587
+ const expectedRoute = this.#router.snapshot.current.route;
588
+ return this.#runPortableRequest(`review:${JSON.stringify(intent)}`, async (signal, token) => {
589
+ const evidence = await this.dependencies.captureEvidence(structuredClone(intent), signal);
590
+ this.#assertPortableRequestCurrent(token, expectedRoute);
591
+ return this.#storeOperationReview(intent, evidence, 'push', this.#router.portableOrigin(draft.pluginId));
592
+ });
593
+ }
594
+ #portableInstallIntent() {
595
+ const draft = this.#portableDraft;
596
+ if (!draft?.canContinue) {
597
+ throw new TuiOperationBlockedError('portableNoSkillsSelected', '至少选择一个 Skill。');
598
+ }
599
+ const unavailable = draft.candidates.filter((candidate) => candidate.selected && !candidate.availableScopes.includes(draft.scope));
600
+ if (unavailable.length > 0) {
601
+ throw new TuiOperationBlockedError('portableScopeUnavailable', `所选范围不提供这些 Skill:${unavailable.map((candidate) => candidate.name).join(', ')}。请返回 Skill 选择或更改范围。`);
602
+ }
603
+ return {
604
+ kind: 'portableInstall',
605
+ pluginId: draft.pluginId,
606
+ scope: draft.scope,
607
+ selectedSkills: draft.candidates.filter((candidate) => candidate.selected).map((candidate) => candidate.name),
608
+ ...(draft.agentMode === 'explicit' ? { requestedAgents: draft.requestedAgents } : {}),
609
+ };
610
+ }
611
+ startPortableInstallReviewPreparation() {
612
+ const intent = this.#portableInstallIntent();
613
+ return this.startReviewPreparation(intent, this.#router.portableOrigin(intent.pluginId));
614
+ }
615
+ discardPortableDraft() {
616
+ this.#cancelPortableRequest();
617
+ this.#portableDraft = undefined;
618
+ this.#publish();
619
+ }
620
+ replace(route, context) {
621
+ this.#cancelReviewPreparation();
622
+ this.#cancelPortableRequest();
623
+ this.#cancelInstallPlacementRequest();
624
+ const current = this.#router.snapshot.current.route;
625
+ if (current.name === 'portableBlocker' && current.code === 'requiredConfigMissing'
626
+ && route.name === 'configLayer' && route.pluginId === current.pluginId) {
627
+ this.#portableRepreparePluginId = current.pluginId;
628
+ }
629
+ this.#discardPortableDraftForRoute(route);
630
+ this.#router.replace(route, context);
631
+ this.#publish();
632
+ }
633
+ beginConfigFlow(pluginId) {
634
+ this.#router.beginConfigFlow(pluginId);
635
+ this.#router.push({
636
+ name: 'configFields',
637
+ pluginId,
638
+ layer: { scope: 'effective' },
639
+ });
640
+ this.#publish();
641
+ }
642
+ configOrigin(pluginId) {
643
+ return this.#router.configOrigin(pluginId);
644
+ }
645
+ updatePageContext(context) {
646
+ this.#router.updateContext(context);
647
+ this.#publish();
648
+ }
649
+ back() {
650
+ const route = this.#router.snapshot.current.route;
651
+ this.#cancelReviewPreparation();
652
+ this.#cancelPortableRequest();
653
+ this.#cancelInstallPlacementRequest();
654
+ if (route.name === 'review' && this.#activeReview?.operationId === route.operationId) {
655
+ this.#tickets.delete(this.#activeReview.reviewToken);
656
+ this.#activeReview = undefined;
657
+ }
658
+ this.#router.back();
659
+ this.#normalizeCurrentRoute();
660
+ this.#discardPortableDraftForRoute(this.#router.snapshot.current.route);
661
+ this.#publish();
662
+ }
663
+ discardSourceDraft(owner, returnRoute) {
664
+ if (this.#activeReview?.operationKind === 'sourceMutation') {
665
+ this.#tickets.delete(this.#activeReview.reviewToken);
666
+ this.#activeReview = undefined;
667
+ }
668
+ this.#result = undefined;
669
+ this.#router.discardSourceFlow(owner, returnRoute);
670
+ this.#normalizeCurrentRoute();
671
+ this.#publish();
672
+ }
673
+ leaveResult(target) {
674
+ if (this.#router.snapshot.current.route.name !== 'result')
675
+ return;
676
+ if (this.#result?.operationKind === 'portableInstall'
677
+ && 'pluginId' in this.#result && this.#result.pluginId) {
678
+ const preserveDraft = target?.name === 'portableSkillSelect'
679
+ || target?.name === 'portableAgentInput';
680
+ this.resetPortableFlowToPreview(this.#result.pluginId, target, preserveDraft);
681
+ return;
682
+ }
683
+ if (this.#result?.state === 'succeeded'
684
+ && (this.#result.operationKind === 'configSave' || this.#result.operationKind === 'configReset')
685
+ && this.#result.pluginId
686
+ && this.#router.resetConfigFlow(this.#result.pluginId, target)) {
687
+ this.#result = undefined;
688
+ this.#publish();
689
+ return;
690
+ }
691
+ const route = target ?? (this.#result && 'returnRoute' in this.#result
692
+ ? this.#result.returnRoute
693
+ : undefined);
694
+ this.#router.back();
695
+ if (route)
696
+ this.#router.replace(this.#normalizeRoute(route));
697
+ else
698
+ this.#normalizeCurrentRoute();
699
+ this.#discardPortableDraftForRoute(this.#router.snapshot.current.route);
700
+ this.#result = undefined;
701
+ this.#publish();
702
+ }
703
+ #discardPortableDraftForRoute(route) {
704
+ const portableRoute = route.name === 'portableSkillSelect'
705
+ || route.name === 'portableDependencies'
706
+ || route.name === 'portableAgentMode'
707
+ || route.name === 'portableAgentInput'
708
+ || route.name === 'portableScope'
709
+ || route.name === 'portableBlocker';
710
+ const portableOperationRoute = (route.name === 'reviewPreparing' || route.name === 'review'
711
+ || route.name === 'progress' || route.name === 'result')
712
+ && (this.#reviewPreparation?.snapshot.intent.kind === 'portableInstall'
713
+ || this.#activeReview?.operationKind === 'portableInstall'
714
+ || this.#result?.operationKind === 'portableInstall');
715
+ if (!portableRoute && !portableOperationRoute) {
716
+ this.#cancelPortableRequest();
717
+ this.#portableDraft = undefined;
718
+ }
719
+ }
720
+ resetPortableFlowToPreview(pluginId, target, preserveDraft = false) {
721
+ this.#cancelPortableRequest();
722
+ if (!preserveDraft)
723
+ this.#portableDraft = undefined;
724
+ if (this.#activeReview?.operationKind === 'portableInstall') {
725
+ this.#tickets.delete(this.#activeReview.reviewToken);
726
+ this.#activeReview = undefined;
727
+ }
728
+ if (this.#result?.operationKind === 'portableInstall')
729
+ this.#result = undefined;
730
+ this.#router.resetPortableFlow(pluginId);
731
+ if (target && JSON.stringify(this.#router.snapshot.current.route) !== JSON.stringify(target)) {
732
+ this.#router.beginPortableFlow(pluginId);
733
+ this.#router.push(this.#normalizeRoute(target));
734
+ }
735
+ this.#publish();
736
+ }
737
+ #cancelPortableRequest() {
738
+ this.#portableRequestToken += 1;
739
+ this.#portableRequestAbort?.abort();
740
+ this.#portableRequestAbort = undefined;
741
+ this.#portablePending = undefined;
742
+ }
743
+ #runPortableRequest(key, action) {
744
+ if (this.#portablePending?.key === key) {
745
+ return this.#portablePending.promise;
746
+ }
747
+ this.#cancelPortableRequest();
748
+ const abort = new AbortController();
749
+ this.#portableRequestAbort = abort;
750
+ const token = this.#portableRequestToken;
751
+ const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
752
+ const promise = action(signal, token).finally(() => {
753
+ if (this.#portableRequestToken === token) {
754
+ this.#portableRequestAbort = undefined;
755
+ this.#portablePending = undefined;
756
+ }
757
+ });
758
+ this.#portablePending = { key, promise };
759
+ return promise;
760
+ }
761
+ #assertPortableRequestCurrent(token, expectedRoute) {
762
+ if (this.#disposed || this.#abort.signal.aborted || token !== this.#portableRequestToken
763
+ || JSON.stringify(this.#router.snapshot.current.route) !== JSON.stringify(expectedRoute)) {
764
+ throw new DOMException('Portable request was cancelled.', 'AbortError');
765
+ }
766
+ }
767
+ beginInstallFlow(pluginId, rootScope, options = {}) {
768
+ this.#cancelInstallPlacementRequest();
769
+ this.#installPlacementDraft = {
770
+ pluginId,
771
+ rootScope,
772
+ platforms: [],
773
+ dependencyScopeOverrides: [],
774
+ state: 'selecting',
775
+ targets: [],
776
+ canContinue: false,
777
+ adjustingExisting: options.adjustingExisting === true,
778
+ };
779
+ this.#publish();
780
+ }
781
+ setInstallRootScope(rootScope) {
782
+ const draft = this.#installPlacementDraft;
783
+ if (!draft)
784
+ return;
785
+ this.#cancelInstallPlacementRequest();
786
+ draft.rootScope = rootScope;
787
+ draft.platforms = [];
788
+ draft.plan = undefined;
789
+ draft.targets = [];
790
+ draft.state = 'selecting';
791
+ draft.canContinue = false;
792
+ draft.error = undefined;
793
+ this.#publish();
794
+ }
795
+ setInstallPlatforms(platforms) {
796
+ const draft = this.#installPlacementDraft;
797
+ if (!draft)
798
+ return;
799
+ this.#cancelInstallPlacementRequest();
800
+ const selected = [...new Set(platforms)].sort();
801
+ draft.platforms = selected;
802
+ draft.dependencyScopeOverrides = draft.dependencyScopeOverrides.filter((override) => selected.includes(override.platform));
803
+ draft.plan = undefined;
804
+ draft.targets = [];
805
+ draft.state = 'selecting';
806
+ draft.canContinue = selected.length > 0;
807
+ draft.error = undefined;
808
+ this.#publish();
809
+ }
810
+ #cancelInstallPlacementRequest() {
811
+ const cancelled = this.#installPlacementRequestAbort !== undefined;
812
+ this.#installPlacementRequestToken += 1;
813
+ this.#installPlacementRequestAbort?.abort();
814
+ this.#installPlacementRequestAbort = undefined;
815
+ if (cancelled && this.#installPlacementDraft?.state === 'loading') {
816
+ this.#installPlacementDraft.plan = undefined;
817
+ this.#installPlacementDraft.targets = [];
818
+ this.#installPlacementDraft.state = 'selecting';
819
+ this.#installPlacementDraft.canContinue = false;
820
+ this.#installPlacementDraft.error = undefined;
821
+ }
822
+ }
823
+ #installPlacementIntent() {
824
+ const draft = this.#installPlacementDraft;
825
+ if (!draft || draft.platforms.length === 0) {
826
+ throw new TuiOperationBlockedError('installPlacementIncomplete', '请先明确选择目标平台。');
827
+ }
828
+ return {
829
+ kind: 'pluginWrite',
830
+ action: 'install',
831
+ pluginId: draft.pluginId,
832
+ platform: draft.platforms.length === 2 ? 'both' : draft.platforms[0],
833
+ scope: draft.rootScope,
834
+ ...(draft.dependencyScopeOverrides.length > 0
835
+ ? { dependencyScopeOverrides: draft.dependencyScopeOverrides }
836
+ : {}),
837
+ ...(draft.adjustingExisting ? { dependencyPlacementAdjustment: true } : {}),
838
+ };
839
+ }
840
+ async prepareInstallDependencyPlacement() {
841
+ const draft = this.#installPlacementDraft;
842
+ if (!draft)
843
+ throw new TuiOperationBlockedError('installPlacementMissing', '安装位置草稿不存在。');
844
+ this.#cancelInstallPlacementRequest();
845
+ const abort = new AbortController();
846
+ this.#installPlacementRequestAbort = abort;
847
+ const token = this.#installPlacementRequestToken;
848
+ draft.state = 'loading';
849
+ draft.canContinue = false;
850
+ draft.error = undefined;
851
+ this.#publish();
852
+ try {
853
+ const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
854
+ const preview = async (intent) => this.dependencies.previewDependencyPlacement
855
+ ? this.dependencies.previewDependencyPlacement(structuredClone(intent), signal)
856
+ : (await this.dependencies.captureEvidence(structuredClone(intent), signal)).dependencyPlan;
857
+ const throwIfCancelled = () => {
858
+ if (this.#disposed || token !== this.#installPlacementRequestToken || abort.signal.aborted) {
859
+ throw new DOMException('Dependency placement preview was cancelled.', 'AbortError');
860
+ }
861
+ };
862
+ const plan = await preview(this.#installPlacementIntent());
863
+ throwIfCancelled();
864
+ if (!plan) {
865
+ draft.state = 'ready';
866
+ draft.plan = undefined;
867
+ draft.targets = [];
868
+ draft.canContinue = true;
869
+ this.#publish();
870
+ return false;
871
+ }
872
+ draft.plan = structuredClone(plan);
873
+ draft.targets = dependencyPlacementTargets(plan);
874
+ const blocker = plan.targets.find((target) => target.action === 'block');
875
+ draft.state = blocker ? 'blocked' : 'ready';
876
+ draft.canContinue = !blocker;
877
+ draft.error = blocker
878
+ ? {
879
+ code: blocker.errorCode ?? 'dependencyPlacementBlocked',
880
+ message: '依赖位置不能满足所选根插件和目标平台。',
881
+ }
882
+ : undefined;
883
+ this.#publish();
884
+ return draft.targets.some((target) => !target.locked);
885
+ }
886
+ catch (error) {
887
+ if (error instanceof Error && error.name === 'AbortError')
888
+ throw error;
889
+ const code = issueCode(error);
890
+ draft.state = 'blocked';
891
+ draft.canContinue = false;
892
+ draft.error = { code, message: '无法计算可信的依赖位置。请返回检查选择。' };
893
+ this.#publish();
894
+ throw error;
895
+ }
896
+ finally {
897
+ if (token === this.#installPlacementRequestToken) {
898
+ this.#installPlacementRequestAbort = undefined;
899
+ }
900
+ }
901
+ }
902
+ async setDependencyScope(pluginId, platform, scope) {
903
+ const draft = this.#installPlacementDraft;
904
+ const target = draft?.targets.find((entry) => entry.pluginId === pluginId && entry.platform === platform);
905
+ if (!draft || !target) {
906
+ throw new TuiOperationBlockedError('dependencyScopeOverrideInvalid', '这个依赖不支持所选位置。');
907
+ }
908
+ draft.dependencyScopeOverrides = [
909
+ ...draft.dependencyScopeOverrides.filter((override) => override.pluginId !== pluginId || override.platform !== platform),
910
+ { pluginId, platform, scope },
911
+ ].sort((left, right) => `${left.pluginId}\0${left.platform}`
912
+ .localeCompare(`${right.pluginId}\0${right.platform}`, 'en'));
913
+ await this.prepareInstallDependencyPlacement();
914
+ }
915
+ async resetDependencyScopes() {
916
+ const draft = this.#installPlacementDraft;
917
+ if (!draft)
918
+ return;
919
+ draft.dependencyScopeOverrides = [];
920
+ await this.prepareInstallDependencyPlacement();
921
+ }
922
+ installPlacementIntent() {
923
+ return this.#installPlacementIntent();
924
+ }
925
+ listPlugins(options) {
926
+ const query = options.query?.trim().toLocaleLowerCase('en');
927
+ return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
928
+ && (options.mode === 'browse' || hasInstalledPlacement(plugin))
929
+ && (options.installFilter !== 'uninstalled' || !hasInstalledPlacement(plugin))
930
+ && (!query || `${plugin.displayName}\n${plugin.description}\n${plugin.sourceId}`.toLocaleLowerCase('en').includes(query))));
931
+ }
932
+ getPluginOverview(pluginId) {
933
+ const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
934
+ return plugin ? cloneFrozen(plugin) : undefined;
935
+ }
936
+ getPluginDependencyOverview(pluginId) {
937
+ const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
938
+ return overview ? cloneFrozen(overview) : undefined;
939
+ }
940
+ getBlockingDependents(pluginId, platform, scope) {
941
+ const dependents = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId)?.dependents ?? [];
942
+ return cloneFrozen(dependents.flatMap((dependent) => {
943
+ const references = (dependent.placementReferences ?? []).filter((reference) => reference.dependency.pluginId === pluginId
944
+ && reference.dependency.platform === platform
945
+ && reference.dependency.scope === scope
946
+ && (scope === 'user' || reference.dependency.project === 'current')
947
+ && (reference.consumer.project === 'other'
948
+ || this.#status?.payload.targets.find((target) => target.pluginId === reference.consumer.pluginId
949
+ && target.platform === reference.consumer.platform
950
+ && target.scope === reference.consumer.scope)?.state !== 'absent'));
951
+ return references.length > 0 ? [{ ...dependent, placementReferences: references }] : [];
952
+ }));
953
+ }
954
+ getPlatformPlacement(pluginId, platform) {
955
+ return this.getPluginOverview(pluginId)?.installedPlacements.find((placement) => placement.platform === platform);
956
+ }
957
+ listInstallTargets(pluginId, scope) {
958
+ const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
959
+ if (!plugin)
960
+ return [];
961
+ if (plugin.aggregateStatus === 'unavailable') {
962
+ return plugin.supportedPlatforms.map((platform) => ({
963
+ platform,
964
+ compatible: false,
965
+ disabled: '插件来源不可用',
966
+ }));
967
+ }
968
+ return plugin.supportedPlatforms.map((platform) => {
969
+ const platformTargets = this.#status?.payload.targets.filter((target) => target.pluginId === pluginId && target.platform === platform) ?? [];
970
+ const rootSummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === pluginId);
971
+ const rootSupportedScopes = rootSummary?.supportedTargets?.[platform]
972
+ ?? platformTargets.filter((target) => !target.reasons.some((error) => error.code === 'unsupportedTarget')).map((target) => target.scope);
973
+ if (!rootSupportedScopes.includes(scope)) {
974
+ return { platform, compatible: false, disabled: '不支持所选位置' };
975
+ }
976
+ const statusTarget = platformTargets.find((target) => target.scope === scope)
977
+ ?? (platform === 'codex' ? platformTargets[0] : undefined);
978
+ if (statusTarget && targetHasArtifactCollision(statusTarget)) {
979
+ return { platform, compatible: false, disabled: '存在输出冲突;请先移除或迁移占用内容' };
980
+ }
981
+ const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
982
+ for (const dependency of overview?.dependencies ?? []) {
983
+ const dependencySummary = this.#status?.pluginSummaries.find((summary) => summary.pluginId === dependency.pluginId);
984
+ const allowedScopes = dependencySummary?.supportedTargets?.[platform]
985
+ ?? (this.#status?.payload.targets.filter((target) => target.pluginId === dependency.pluginId
986
+ && target.platform === platform
987
+ && !target.reasons.some((error) => error.code === 'unsupportedTarget'))
988
+ .map((target) => target.scope) ?? []);
989
+ const viableScopes = allowedScopes.filter((candidateScope) => scope === 'project' || candidateScope === 'user');
990
+ if (viableScopes.length === 0) {
991
+ return {
992
+ platform,
993
+ compatible: false,
994
+ disabled: `${dependency.displayName} 不支持${platform === 'codex' ? 'Codex' : 'Claude Code'}或所选位置`,
995
+ };
996
+ }
997
+ const collisionAtEveryViableScope = viableScopes.every((candidateScope) => {
998
+ const target = this.#status?.payload.targets.find((entry) => entry.pluginId === dependency.pluginId
999
+ && entry.platform === platform
1000
+ && entry.scope === candidateScope);
1001
+ return Boolean(target && targetHasArtifactCollision(target));
1002
+ });
1003
+ if (collisionAtEveryViableScope) {
1004
+ return {
1005
+ platform,
1006
+ compatible: false,
1007
+ disabled: `${dependency.displayName} 存在输出冲突`,
1008
+ };
1009
+ }
1010
+ }
1011
+ return { platform, compatible: true };
1012
+ });
1013
+ }
1014
+ getTargetState(pluginId, platform, scope) {
1015
+ return this.#status?.payload.targets.find((target) => target.pluginId === pluginId
1016
+ && target.platform === platform
1017
+ && target.scope === scope)?.state;
1018
+ }
1019
+ listSources() {
1020
+ return cloneFrozen(this.#viewModel?.sourceTabs ?? []);
1021
+ }
1022
+ getSourceOverview(sourceId) {
1023
+ return cloneFrozen({
1024
+ source: this.#viewModel?.sourceTabs.find((source) => source.id === sourceId),
1025
+ detail: this.#viewModel?.sourceDetails.find((source) => source.id === sourceId),
1026
+ health: this.#health.get(sourceId),
1027
+ });
1028
+ }
1029
+ getCatalogGeneration(sourceId) {
1030
+ const source = this.#catalogGenerations.get(sourceId);
1031
+ return source ? cloneFrozen(source) : undefined;
1032
+ }
1033
+ getCatalogFreshness(sourceId) {
1034
+ const freshness = this.#catalogFreshness.get(sourceId);
1035
+ return freshness ? cloneFrozen(freshness) : undefined;
1036
+ }
1037
+ recheckCatalogSource(sourceId) {
1038
+ return this.#catalogSourcesSession.recheck(sourceId);
1039
+ }
1040
+ fullValidateCatalogSource(sourceId) {
1041
+ return this.#catalogSourcesSession.fullValidate(sourceId);
1042
+ }
1043
+ setExternalDraftState(state) {
1044
+ this.#externalDraft = state;
1045
+ }
1046
+ catalogActivationBlockReason() {
1047
+ if (this.#externalDraft)
1048
+ return this.#externalDraft;
1049
+ if (this.#portableDraft)
1050
+ return 'portableDraft';
1051
+ if (this.#reviewPreparation)
1052
+ return 'reviewPreparation';
1053
+ if (this.#activeReview)
1054
+ return 'activeReview';
1055
+ if (this.#router.snapshot.current.route.name === 'progress'
1056
+ || this.#inflightOperations.size > 0)
1057
+ return 'writeProgress';
1058
+ return undefined;
1059
+ }
1060
+ getSessionCatalogVersion(sourceId) {
1061
+ return this.#status?.payload.catalog.sources.find((source) => source.id === sourceId)?.catalogVersion;
1062
+ }
1063
+ async reloadCatalog(options) {
1064
+ if (this.catalogActivationBlockReason())
1065
+ return false;
1066
+ this.dependencies.beginCatalogSession?.();
1067
+ const generation = ++this.#generation;
1068
+ this.#phase = 'loading';
1069
+ this.#publish();
1070
+ try {
1071
+ const status = await this.dependencies.loadStatus(this.#abort.signal);
1072
+ if (this.#disposed || generation !== this.#generation)
1073
+ return false;
1074
+ this.#applyStatus(status);
1075
+ this.#phase = 'ready';
1076
+ this.#loadError = undefined;
1077
+ this.#router.replace({ name: 'sourceDetail', sourceId: options.sourceId });
1078
+ this.#publish();
1079
+ return true;
1080
+ }
1081
+ catch (error) {
1082
+ if (this.#disposed || generation !== this.#generation)
1083
+ return false;
1084
+ const code = issueCode(error);
1085
+ await this.#logErrorSafe(error, code);
1086
+ this.#phase = 'error';
1087
+ this.#loadError = { code, message: '无法安全重新加载 Catalog。请检查诊断后重试。' };
1088
+ this.#publish();
1089
+ return false;
1090
+ }
1091
+ }
1092
+ async inspectSourceRemoval(sourceId) {
1093
+ if (!this.dependencies.inspectSourceRemoval) {
1094
+ throw new TuiOperationBlockedError('stateInventoryFailed', '来源引用清单不可用。');
1095
+ }
1096
+ return cloneFrozen(await this.dependencies.inspectSourceRemoval(sourceId, this.#abort.signal));
1097
+ }
1098
+ async getConfigGroups(pluginId, layer) {
1099
+ if (!this.dependencies.getConfigGroups) {
1100
+ throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
1101
+ }
1102
+ return cloneFrozen(await this.dependencies.getConfigGroups(pluginId, structuredClone(layer), this.#abort.signal));
1103
+ }
1104
+ async getConfigPage(pluginId, layer, signal) {
1105
+ if (!this.dependencies.getConfigPage) {
1106
+ throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
1107
+ }
1108
+ const combined = signal
1109
+ ? AbortSignal.any([this.#abort.signal, signal])
1110
+ : this.#abort.signal;
1111
+ return cloneFrozen(await this.dependencies.getConfigPage(pluginId, structuredClone(layer), combined));
1112
+ }
1113
+ async getConfigResetAvailability(pluginId, layer) {
1114
+ if (!this.dependencies.getConfigResetAvailability) {
1115
+ throw new TuiOperationBlockedError('configResetVerificationUnavailable', '设置层可信读取入口不可用。');
1116
+ }
1117
+ return cloneFrozen(await this.dependencies.getConfigResetAvailability(pluginId, structuredClone(layer), this.#abort.signal));
1118
+ }
1119
+ listUserIssues() {
1120
+ return cloneFrozen(this.#viewModel?.issues ?? []);
1121
+ }
1122
+ async buildOperationReview(intent, returnRoute) {
1123
+ try {
1124
+ this.#validateReviewIntent(intent);
1125
+ const evidence = await this.#prepareReviewEvidence(intent, this.#abort.signal);
1126
+ return this.#storeOperationReview(intent, evidence, 'push', returnRoute);
1127
+ }
1128
+ catch (error) {
1129
+ const code = issueCode(error);
1130
+ await this.#logOperationError(error, code, intent);
1131
+ throw new TuiOperationBlockedError(code, code === 'catalogGenerationUnavailable'
1132
+ ? '本次加载时 Catalog 来源不可用。请刷新来源并重新加载后重试。'
1133
+ : '操作未建立。请检查当前设置后重试。');
1134
+ }
1135
+ }
1136
+ startReviewPreparation(intent, returnRoute) {
1137
+ if (intent.kind === 'catalogGeneration' && !intent.sourceMutation) {
1138
+ const blockReason = this.catalogActivationBlockReason();
1139
+ if (blockReason) {
1140
+ throw new TuiOperationBlockedError('catalogActivationBlocked', `Catalog Active 切换已阻止:${blockReason}。请先完成或放弃当前草稿或操作。`);
1141
+ }
1142
+ }
1143
+ const inheritedReturnRoute = this.#reviewPreparation?.returnRoute;
1144
+ const current = this.#router.snapshot.current.route;
1145
+ const navigation = current.name === 'result' || current.name === 'reviewPreparing'
1146
+ ? 'replace' : 'push';
1147
+ const resolvedReturnRoute = returnRoute
1148
+ ?? (current.name === 'reviewPreparing' ? inheritedReturnRoute : undefined)
1149
+ ?? (current.name === 'result' && this.#result
1150
+ && 'returnRoute' in this.#result && this.#result.returnRoute
1151
+ ? this.#result.returnRoute
1152
+ : current);
1153
+ const normalizedReturnRoute = this.#normalizeRoute(resolvedReturnRoute);
1154
+ const key = JSON.stringify(stableValue({ intent, returnRoute: normalizedReturnRoute }));
1155
+ if (this.#reviewPreparation?.snapshot.state === 'running'
1156
+ && this.#reviewPreparation.key === key) {
1157
+ return cloneFrozen(this.#reviewPreparation.snapshot);
1158
+ }
1159
+ this.#cancelReviewPreparation();
1160
+ const id = crypto.randomUUID();
1161
+ const abort = new AbortController();
1162
+ const token = ++this.#reviewPreparationToken;
1163
+ const snapshot = {
1164
+ id,
1165
+ intent: structuredClone(intent),
1166
+ state: 'running',
1167
+ stages: this.dependencies.prepareReviewEvidence ? [] : [
1168
+ { id: 'validate', label: '验证当前状态', state: 'running' },
1169
+ { id: 'evidence', label: '准备确认信息', state: 'pending' },
1170
+ ],
1171
+ currentLabel: this.#reviewPreparationLabel(intent),
1172
+ };
1173
+ this.#reviewPreparation = {
1174
+ snapshot,
1175
+ key,
1176
+ returnRoute: structuredClone(normalizedReturnRoute),
1177
+ abort,
1178
+ token,
1179
+ };
1180
+ this.#result = undefined;
1181
+ this.#router[navigation]({ name: 'reviewPreparing', preparationId: id });
1182
+ this.#publish();
1183
+ queueMicrotask(() => void this.#runReviewPreparation(token));
1184
+ return cloneFrozen(snapshot);
1185
+ }
1186
+ retryReviewPreparation() {
1187
+ const preparation = this.#reviewPreparation;
1188
+ if (!preparation || preparation.snapshot.state !== 'failed')
1189
+ return undefined;
1190
+ return this.startReviewPreparation(preparation.snapshot.intent, preparation.returnRoute);
1191
+ }
1192
+ #validateReviewIntent(intent) {
1193
+ const primaryGeneration = intent.kind === 'sourceMutation' && intent.action === 'setPrimary'
1194
+ ? this.#catalogGenerations.get(intent.sourceId) : undefined;
1195
+ if (primaryGeneration && !primaryGeneration.active && primaryGeneration.state !== 'ready') {
1196
+ throw new TuiOperationBlockedError('catalogGenerationUnavailable', '来源尚未 ready。请先获取、修复或切换 Active Catalog。');
1197
+ }
1198
+ }
1199
+ #reviewPreparationLabel(intent) {
1200
+ if (intent.kind === 'catalogSourcesRecovery')
1201
+ return 'Catalog 来源配置';
1202
+ if ('pluginId' in intent) {
1203
+ return this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName
1204
+ ?? pluginNameFromId(intent.pluginId);
1205
+ }
1206
+ return intent.sourceId;
1207
+ }
1208
+ #reviewPreparationIsCurrent(token) {
1209
+ const preparation = this.#reviewPreparation;
1210
+ const route = this.#router.snapshot.current.route;
1211
+ return Boolean(preparation && preparation.token === token
1212
+ && !preparation.abort.signal.aborted
1213
+ && !this.#disposed
1214
+ && route.name === 'reviewPreparing' && route.preparationId === preparation.snapshot.id);
1215
+ }
1216
+ async #runReviewPreparation(token) {
1217
+ const preparation = this.#reviewPreparation;
1218
+ if (!preparation || preparation.token !== token)
1219
+ return;
1220
+ const intent = structuredClone(preparation.snapshot.intent);
1221
+ const signal = AbortSignal.any([this.#abort.signal, preparation.abort.signal]);
1222
+ try {
1223
+ this.#validateReviewIntent(intent);
1224
+ const evidence = await this.#prepareReviewEvidence(intent, signal, (stage) => {
1225
+ if (!this.#reviewPreparationIsCurrent(token))
1226
+ return;
1227
+ const existing = preparation.snapshot.stages.find((entry) => entry.id === stage);
1228
+ preparation.snapshot.stages = preparation.snapshot.stages.map((entry) => entry.state === 'running' ? { ...entry, state: 'completed' } : entry);
1229
+ if (existing) {
1230
+ preparation.snapshot.stages = preparation.snapshot.stages.map((entry) => entry.id === stage ? { ...entry, state: 'running' } : entry);
1231
+ }
1232
+ else {
1233
+ preparation.snapshot.stages.push({
1234
+ id: stage,
1235
+ label: reviewPreparationStageLabel(stage),
1236
+ state: 'running',
1237
+ });
1238
+ }
1239
+ this.#publish();
1240
+ });
1241
+ if (!this.#reviewPreparationIsCurrent(token))
1242
+ return;
1243
+ preparation.snapshot.stages = preparation.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'completed' } : stage);
1244
+ this.#publish();
1245
+ const returnRoute = structuredClone(preparation.returnRoute);
1246
+ this.#reviewPreparation = undefined;
1247
+ this.#storeOperationReview(intent, evidence, 'replace', returnRoute);
1248
+ }
1249
+ catch (error) {
1250
+ if (!this.#reviewPreparationIsCurrent(token))
1251
+ return;
1252
+ const code = issueCode(error);
1253
+ const active = this.#reviewPreparation;
1254
+ if (code === 'catalogLogicalIdentityChanged'
1255
+ && intent.kind === 'catalogGeneration') {
1256
+ const generation = this.#catalogGenerations.get(intent.sourceId);
1257
+ if (generation)
1258
+ this.#catalogGenerations.set(intent.sourceId, {
1259
+ ...generation,
1260
+ state: 'unavailable',
1261
+ errorCode: code,
1262
+ });
1263
+ }
1264
+ active.snapshot.state = 'failed';
1265
+ active.snapshot.stages = active.snapshot.stages.map((stage) => stage.state === 'running' ? { ...stage, state: 'failed' } : stage);
1266
+ active.snapshot.code = 'reviewPreparationFailed';
1267
+ active.snapshot.message = code === 'catalogLogicalIdentityChanged'
1268
+ ? 'Catalog logical identity 已变化。请编辑来源,或移除后重新添加。'
1269
+ : code === 'catalogGenerationActiveComparisonUnavailable'
1270
+ ? '损坏的当前 Active 无法提供可信版本,不能安全比较候选。当前 Active 保持不变。请明确编辑来源,或移除后重新添加。'
1271
+ : code === 'catalogReplaceDamagedRequired'
1272
+ ? '无法重新取得或精确匹配旧 Active。当前 Active 保持不变。请返回并选择“使用当前候选替换损坏的 Active”。'
1273
+ : code === 'catalogVersionConflict'
1274
+ ? '候选 Catalog 与当前 Active 版本相同,但内容摘要不同。普通更新已阻止。'
1275
+ : code === 'catalogRollbackDetected'
1276
+ ? '候选 Catalog 版本低于当前 Active。普通更新已阻止。'
1277
+ : code === 'catalogGenerationUnavailable'
1278
+ ? '本次加载时 Catalog 来源不可用。请刷新来源并重新加载后重试。'
1279
+ : '无法准备确认信息。请返回检查选择,或重试。';
1280
+ this.#publish();
1281
+ await this.#logOperationError(error, code, intent);
1282
+ }
1283
+ }
1284
+ async #prepareReviewEvidence(intent, signal, onStage = () => undefined) {
1285
+ if (this.dependencies.prepareReviewEvidence) {
1286
+ const reusableCandidate = intent.kind === 'catalogGeneration'
1287
+ && !intent.sourceMutation
1288
+ && (intent.action === 'activate' || intent.action === 'update')
1289
+ ? this.#catalogSourcesSession.reusableCandidate(intent.sourceId)
1290
+ : undefined;
1291
+ return this.dependencies.prepareReviewEvidence(structuredClone(intent), signal, onStage, reusableCandidate);
1292
+ }
1293
+ await onStage('validate');
1294
+ await this.dependencies.validateIntent?.(structuredClone(intent), signal);
1295
+ await onStage('evidence');
1296
+ return this.dependencies.captureEvidence(structuredClone(intent), signal);
1297
+ }
1298
+ #cancelReviewPreparation() {
1299
+ if (!this.#reviewPreparation)
1300
+ return;
1301
+ this.#reviewPreparationToken += 1;
1302
+ this.#reviewPreparation.abort.abort();
1303
+ this.#reviewPreparation = undefined;
1304
+ }
1305
+ async #logOperationError(error, code, intent) {
1306
+ await this.#logErrorSafe(error, code, {
1307
+ privateConfigOperation: intent.kind === 'configSave' || intent.kind === 'configReset'
1308
+ || intent.kind === 'sourceMutation',
1309
+ });
1310
+ }
1311
+ #pruneReviewTickets(now) {
1312
+ for (const [token, ticket] of this.#tickets) {
1313
+ if (ticket.review.expiresAt < now)
1314
+ this.#tickets.delete(token);
1315
+ }
1316
+ const maximum = Math.max(1, this.dependencies.maxReviewTickets ?? 32);
1317
+ while (this.#tickets.size >= maximum) {
1318
+ const oldest = this.#tickets.keys().next().value;
1319
+ if (!oldest)
1320
+ break;
1321
+ this.#tickets.delete(oldest);
1322
+ }
1323
+ }
1324
+ #storeOperationReview(intent, evidence, navigation = 'push', returnRoute = this.#router.snapshot.current.route) {
1325
+ if (this.#router.snapshot.current.route.name === 'result')
1326
+ navigation = 'replace';
1327
+ const projection = buildOperationReviewProjection({
1328
+ intent,
1329
+ evidence,
1330
+ ...('pluginId' in intent
1331
+ ? { dependencyOverview: this.getPluginDependencyOverview(intent.pluginId) }
1332
+ : {}),
1333
+ });
1334
+ const operationId = this.dependencies.createToken?.() ?? crypto.randomUUID();
1335
+ const reviewToken = this.dependencies.createToken?.() ?? crypto.randomUUID();
1336
+ const now = this.dependencies.now?.() ?? Date.now();
1337
+ this.#pruneReviewTickets(now);
1338
+ const expiresAt = now
1339
+ + (this.dependencies.reviewTtlMs ?? 2 * 60_000);
1340
+ const pluginId = 'pluginId' in intent ? intent.pluginId
1341
+ : 'sourceId' in intent ? intent.sourceId : 'Catalog 来源配置';
1342
+ if ('pluginId' in intent)
1343
+ sourceIdFromPluginId(intent.pluginId);
1344
+ const pluginVersion = 'pluginId' in intent
1345
+ ? evidence.source?.pluginVersion
1346
+ ?? this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.version
1347
+ : undefined;
1348
+ const review = {
1349
+ operationId,
1350
+ reviewToken,
1351
+ expiresAt,
1352
+ operationKind: intent.kind,
1353
+ ...(intent.kind === 'sourceMutation' ? { sourceAction: intent.action }
1354
+ : intent.kind === 'catalogGeneration' && intent.sourceMutation
1355
+ ? { sourceAction: intent.sourceMutation.action } : {}),
1356
+ ...(intent.kind === 'pluginWrite' ? { pluginAction: intent.action } : {}),
1357
+ ...(intent.kind === 'pluginWrite' && intent.originIssue
1358
+ ? { originIssue: intent.originIssue }
1359
+ : {}),
1360
+ ...projection,
1361
+ subject: {
1362
+ name: 'pluginId' in intent
1363
+ ? this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName
1364
+ ?? pluginNameFromId(intent.pluginId)
1365
+ : pluginId,
1366
+ ...('pluginId' in intent ? { sourceLabel: sourceIdFromPluginId(intent.pluginId) } : {}),
1367
+ ...(pluginVersion ? { version: pluginVersion } : {}),
1368
+ },
1369
+ ...(evidence.source && 'catalogVersion' in evidence.source
1370
+ && typeof evidence.source.catalogVersion === 'string'
1371
+ && evidence.source.snapshotDigest
1372
+ ? { catalogBinding: {
1373
+ sourceId: evidence.source.sourceId,
1374
+ catalogVersion: evidence.source.catalogVersion,
1375
+ generationDigestShort: evidence.source.snapshotDigest.replace(/^sha256:/u, '').slice(0, 8),
1376
+ } }
1377
+ : {}),
1378
+ };
1379
+ this.#tickets.set(reviewToken, {
1380
+ intent: structuredClone(intent),
1381
+ evidence: structuredClone(evidence),
1382
+ review: structuredClone(review),
1383
+ returnRoute: structuredClone(this.#normalizeRoute(returnRoute)),
1384
+ });
1385
+ this.#activeReview = structuredClone(review);
1386
+ this.#result = undefined;
1387
+ this.#router[navigation]({ name: 'review', operationId });
1388
+ this.#publish();
1389
+ return cloneFrozen(review);
1390
+ }
1391
+ executeOperation(reviewToken) {
1392
+ const inflight = this.#inflightOperations.get(reviewToken);
1393
+ if (inflight)
1394
+ return inflight.promise;
1395
+ const ticket = this.#tickets.get(reviewToken);
1396
+ const operationId = ticket?.review.operationId ?? 'unknown';
1397
+ if (!ticket) {
1398
+ return Promise.resolve(cloneFrozen({
1399
+ state: 'blocked',
1400
+ operationId,
1401
+ code: 'reviewTicketInvalid',
1402
+ message: '操作未完成。请重新检查后重试。',
1403
+ }));
1404
+ }
1405
+ this.#tickets.delete(reviewToken);
1406
+ if ((this.dependencies.now?.() ?? Date.now()) > ticket.review.expiresAt) {
1407
+ return Promise.resolve(this.#setBlocked(operationId, 'reviewTicketExpired', { intent: ticket.intent, returnRoute: ticket.returnRoute }));
1408
+ }
1409
+ const promise = this.#executeOperationTicket(ticket, operationId).finally(() => {
1410
+ if (this.#inflightOperations.get(reviewToken)?.promise === promise) {
1411
+ this.#inflightOperations.delete(reviewToken);
1412
+ }
1413
+ });
1414
+ this.#inflightOperations.set(reviewToken, { operationId, promise });
1415
+ return promise;
1416
+ }
1417
+ async #executeOperationTicket(ticket, operationId) {
1418
+ let generation = this.#generation;
1419
+ const startedAtMs = this.dependencies.now?.() ?? Date.now();
1420
+ this.#progressFeed?.clearCurrent?.();
1421
+ const dependencyPlan = ticket.evidence.dependencyPlan;
1422
+ this.#progressTargets = dependencyPlan?.targets.map((target) => ({
1423
+ pluginId: target.pluginId,
1424
+ platform: target.platform,
1425
+ scope: target.resolvedScope,
1426
+ relation: target.relation === 'requested'
1427
+ ? 'requested'
1428
+ : target.depth === 1 ? 'direct' : 'transitive',
1429
+ requiredBy: target.requiredBy,
1430
+ state: target.action === 'keep' ? 'kept' : 'pending',
1431
+ })) ?? [];
1432
+ this.#progress = {
1433
+ messages: [],
1434
+ startedAtMs,
1435
+ elapsedMs: 0,
1436
+ current: {
1437
+ target: 'pluginId' in ticket.intent ? ticket.intent.pluginId
1438
+ : 'sourceId' in ticket.intent ? ticket.intent.sourceId : 'Catalog 来源配置',
1439
+ phase: '验证当前状态',
1440
+ },
1441
+ };
1442
+ this.#router.replace({ name: 'progress', operationId });
1443
+ this.#publish();
1444
+ try {
1445
+ if (ticket.intent.kind !== 'pluginWrite') {
1446
+ await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
1447
+ }
1448
+ }
1449
+ catch (error) {
1450
+ if (!this.#operationIsCurrent(generation))
1451
+ return this.#detachedBlocked(operationId, ticket.intent);
1452
+ const code = issueCode(error);
1453
+ await this.#logOperationError(error, code, ticket.intent);
1454
+ if (!this.#operationIsCurrent(generation))
1455
+ return this.#detachedBlocked(operationId, ticket.intent);
1456
+ generation = ++this.#generation;
1457
+ try {
1458
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1459
+ if (!this.#operationIsCurrent(generation))
1460
+ return this.#detachedBlocked(operationId, ticket.intent);
1461
+ this.#applyStatus(refreshed);
1462
+ }
1463
+ catch (reloadError) {
1464
+ if (!this.#operationIsCurrent(generation))
1465
+ return this.#detachedBlocked(operationId, ticket.intent);
1466
+ await this.#logErrorSafe(reloadError, 'tuiStateReloadFailed', { privateConfigOperation: true });
1467
+ }
1468
+ return this.#setBlocked(operationId, code, {
1469
+ intent: ticket.intent,
1470
+ navigation: 'result',
1471
+ returnRoute: ticket.returnRoute,
1472
+ });
1473
+ }
1474
+ if (!this.#operationIsCurrent(generation))
1475
+ return this.#detachedBlocked(operationId, ticket.intent);
1476
+ if (ticket.intent.kind !== 'pluginWrite' && ticket.intent.kind !== 'catalogGeneration') {
1477
+ let current;
1478
+ try {
1479
+ current = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
1480
+ }
1481
+ catch (error) {
1482
+ if (!this.#operationIsCurrent(generation))
1483
+ return this.#detachedBlocked(operationId, ticket.intent);
1484
+ const code = issueCode(error);
1485
+ await this.#logOperationError(error, code, ticket.intent);
1486
+ if (!this.#operationIsCurrent(generation))
1487
+ return this.#detachedBlocked(operationId, ticket.intent);
1488
+ return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1489
+ }
1490
+ if (!this.#operationIsCurrent(generation))
1491
+ return this.#detachedBlocked(operationId, ticket.intent);
1492
+ if (!operationEvidenceMatches(ticket.evidence, current)) {
1493
+ return this.#rebuildStaleReview(ticket, operationId, current, generation);
1494
+ }
1495
+ }
1496
+ if (ticket.intent.kind === 'sourceMutation' || ticket.intent.kind === 'catalogGeneration'
1497
+ || ticket.intent.kind === 'catalogSourcesRecovery') {
1498
+ generation = ++this.#generation;
1499
+ }
1500
+ try {
1501
+ const execution = ticket.intent.kind === 'catalogGeneration'
1502
+ ? await this.dependencies.executeIntent(structuredClone(ticket.intent), structuredClone(ticket.evidence), this.#abort.signal)
1503
+ : await this.dependencies.executeIntent(structuredClone(ticket.intent), structuredClone(ticket.evidence));
1504
+ if (ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'update') {
1505
+ const confirmedAutoCheck = catalogSourceAutoCheckOnlyValue(ticket.intent.update);
1506
+ if (confirmedAutoCheck !== undefined) {
1507
+ this.#catalogSourcesSession.setAutoCheck(ticket.intent.sourceId, confirmedAutoCheck);
1508
+ }
1509
+ }
1510
+ const executionTargets = operationResultSummary(ticket.intent, execution).targets ?? [];
1511
+ this.#progressTargets = this.#progressTargets.map((target) => {
1512
+ const completed = executionTargets.find((candidate) => candidate.pluginId === target.pluginId
1513
+ && candidate.platform === target.platform && candidate.scope === target.scope);
1514
+ return completed ? { ...target, state: completed.state } : target;
1515
+ });
1516
+ if (!this.#operationIsCurrent(generation))
1517
+ return this.#detachedBlocked(operationId, ticket.intent);
1518
+ generation = ++this.#generation;
1519
+ if (ticket.intent.kind === 'sourceMutation' || ticket.intent.kind === 'catalogGeneration'
1520
+ || ticket.intent.kind === 'catalogSourcesRecovery') {
1521
+ this.dependencies.beginCatalogSession?.(ticket.intent.kind === 'sourceMutation' ? {
1522
+ reuseValidatedGenerations: true,
1523
+ } : undefined);
1524
+ try {
1525
+ this.#progressFeed?.setCurrent({ target: 'Catalog 来源', phase: '读取最终状态' }, 'Catalog 操作:读取最终状态');
1526
+ }
1527
+ catch {
1528
+ // Progress diagnostics must not replace the completed source write.
1529
+ }
1530
+ }
1531
+ let refreshed;
1532
+ try {
1533
+ refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1534
+ }
1535
+ catch (error) {
1536
+ if (!this.#operationIsCurrent(generation))
1537
+ return this.#detachedBlocked(operationId, ticket.intent);
1538
+ const verificationCode = ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1539
+ ? 'issueVerificationFailed'
1540
+ : 'tuiStateReloadFailed';
1541
+ await this.#logErrorSafe(error, verificationCode, {
1542
+ privateConfigOperation: true,
1543
+ });
1544
+ if (!this.#operationIsCurrent(generation))
1545
+ return this.#detachedBlocked(operationId, ticket.intent);
1546
+ return this.#setBlocked(operationId, verificationCode, {
1547
+ intent: ticket.intent,
1548
+ navigation: 'result',
1549
+ returnRoute: ticket.returnRoute,
1550
+ writeMayHaveCompleted: true,
1551
+ verification: ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1552
+ ? 'verificationFailed' : undefined,
1553
+ });
1554
+ }
1555
+ if (!this.#operationIsCurrent(generation))
1556
+ return this.#detachedBlocked(operationId, ticket.intent);
1557
+ this.#applyStatus(refreshed);
1558
+ if (ticket.intent.kind === 'catalogSourcesRecovery') {
1559
+ this.#phase = 'ready';
1560
+ this.#loadError = undefined;
1561
+ }
1562
+ const pluginSummary = ticket.intent.kind === 'pluginWrite'
1563
+ ? operationResultSummary(ticket.intent, execution)
1564
+ : undefined;
1565
+ const hasFailedDependencyTarget = pluginSummary?.targets?.some((target) => target.relation !== undefined && target.relation !== 'requested'
1566
+ && (target.state === 'failed' || target.state === 'skipped'));
1567
+ const hasPreservedTarget = pluginSummary?.targets?.some((target) => target.state === 'completed' || target.state === 'kept');
1568
+ const dependencyPartial = pluginSummary?.code === 'operationPartial'
1569
+ && Boolean(hasFailedDependencyTarget && hasPreservedTarget);
1570
+ const dependencyFailure = pluginSummary?.code === 'operationFailed'
1571
+ && Boolean(hasFailedDependencyTarget);
1572
+ if (pluginSummary?.code === 'operationFailed') {
1573
+ const unsuccessfulTargets = pluginSummary.targets?.filter((target) => target.state === 'failed' || target.state === 'skipped') ?? [];
1574
+ const managerFailureCode = unsuccessfulTargets.map((target) => target.errorCode).find((code) => code === 'codexSystemCliUnsupported'
1575
+ || code === 'managedCodexAuthorizationRequired'
1576
+ || code === 'managedCodexUnavailable');
1577
+ if (managerFailureCode) {
1578
+ return this.#setBlocked(operationId, managerFailureCode, {
1579
+ intent: ticket.intent,
1580
+ navigation: 'result',
1581
+ returnRoute: ticket.returnRoute,
1582
+ summary: pluginSummary,
1583
+ });
1584
+ }
1585
+ const dependencyStillRequired = unsuccessfulTargets.length > 0
1586
+ && unsuccessfulTargets.every((target) => target.errorCode === 'dependencyStillRequired');
1587
+ return this.#setBlocked(operationId, dependencyStillRequired ? 'dependencyStillRequired' : 'operationFailed', {
1588
+ intent: ticket.intent,
1589
+ navigation: 'result',
1590
+ returnRoute: ticket.returnRoute,
1591
+ ...(dependencyFailure || dependencyStillRequired ? { summary: pluginSummary } : {}),
1592
+ ...(!dependencyFailure && !dependencyStillRequired ? { writeMayHaveCompleted: true } : {}),
1593
+ });
1594
+ }
1595
+ const originIssue = ticket.intent.kind === 'pluginWrite' ? ticket.intent.originIssue : undefined;
1596
+ if (originIssue && !dependencyPartial
1597
+ && this.#viewModel?.issues.some((issue) => userIssueKey(issue) === originIssue.issueKey)) {
1598
+ return this.#setBlocked(operationId, 'issueStillPresent', {
1599
+ intent: ticket.intent,
1600
+ navigation: 'result',
1601
+ returnRoute: ticket.returnRoute,
1602
+ writeMayHaveCompleted: true,
1603
+ verification: 'issueStillPresent',
1604
+ });
1605
+ }
1606
+ if (ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset') {
1607
+ const configWrite = typeof execution === 'object' && execution !== null
1608
+ ? execution.configWrite
1609
+ : undefined;
1610
+ if (configWrite !== 'succeeded') {
1611
+ return this.#setBlocked(operationId, 'configWriteUnconfirmed', {
1612
+ intent: ticket.intent,
1613
+ navigation: 'back',
1614
+ });
1615
+ }
1616
+ }
1617
+ const baseSummary = dependencyPartial && pluginSummary
1618
+ ? { ...pluginSummary, code: 'operationPartial', message: '依赖安装没有全部完成。' }
1619
+ : pluginSummary ?? operationResultSummary(ticket.intent, execution);
1620
+ const summary = {
1621
+ ...baseSummary,
1622
+ elapsedMs: Math.max(0, (this.dependencies.now?.() ?? Date.now()) - startedAtMs),
1623
+ messages: [...this.#progress.messages, ...(baseSummary.messages ?? [])].slice(-8),
1624
+ };
1625
+ this.#progress = { ...this.#progress, elapsedMs: summary.elapsedMs };
1626
+ if (summary.code === 'operationFailed') {
1627
+ if (ticket.intent.kind === 'portableInstall') {
1628
+ const result = {
1629
+ state: 'partial',
1630
+ operationId,
1631
+ summary,
1632
+ operationKind: ticket.intent.kind,
1633
+ pluginId: ticket.intent.pluginId,
1634
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
1635
+ };
1636
+ this.#result = structuredClone(result);
1637
+ this.#activeReview = undefined;
1638
+ this.#router.replace({ name: 'result', operationId });
1639
+ this.#publish();
1640
+ return cloneFrozen(result);
1641
+ }
1642
+ return this.#setBlocked(operationId, 'operationFailed', {
1643
+ intent: ticket.intent,
1644
+ navigation: 'result',
1645
+ returnRoute: ticket.returnRoute,
1646
+ writeMayHaveCompleted: true,
1647
+ });
1648
+ }
1649
+ const result = {
1650
+ state: summary.code === 'operationPartial' ? 'partial' : 'succeeded',
1651
+ operationId,
1652
+ summary,
1653
+ operationKind: ticket.intent.kind,
1654
+ ...(ticket.intent.kind === 'sourceMutation' ? { sourceAction: ticket.intent.action } : {}),
1655
+ ...('pluginId' in ticket.intent ? { pluginId: ticket.intent.pluginId } : {}),
1656
+ ...(ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1657
+ ? { originIssue: ticket.intent.originIssue }
1658
+ : {}),
1659
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'add'
1660
+ ? { name: 'sourceList' }
1661
+ : ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'update'
1662
+ ? { name: 'sourceDetail', sourceId: ticket.intent.sourceId }
1663
+ : ticket.intent.kind === 'catalogGeneration'
1664
+ ? { name: 'sourceDetail', sourceId: ticket.intent.sourceId }
1665
+ : ticket.returnRoute)),
1666
+ };
1667
+ if ((ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset')
1668
+ && this.#portableRepreparePluginId === ticket.intent.pluginId) {
1669
+ this.#portableRepreparePluginId = undefined;
1670
+ this.#activeReview = undefined;
1671
+ this.#result = undefined;
1672
+ await this.preparePortableInstall(ticket.intent.pluginId, 'replace');
1673
+ return cloneFrozen(result);
1674
+ }
1675
+ this.#result = result;
1676
+ this.#activeReview = undefined;
1677
+ this.#router.replace({ name: 'result', operationId });
1678
+ this.#publish();
1679
+ return cloneFrozen(result);
1680
+ }
1681
+ catch (error) {
1682
+ if (!this.#operationIsCurrent(generation))
1683
+ return this.#detachedBlocked(operationId, ticket.intent);
1684
+ const code = issueCode(error);
1685
+ await this.#logOperationError(error, code, ticket.intent);
1686
+ if (!this.#operationIsCurrent(generation))
1687
+ return this.#detachedBlocked(operationId, ticket.intent);
1688
+ if (code === 'operationReviewStale' || code === 'catalogGenerationReviewStale') {
1689
+ return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
1690
+ }
1691
+ generation = ++this.#generation;
1692
+ try {
1693
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1694
+ if (!this.#operationIsCurrent(generation))
1695
+ return this.#detachedBlocked(operationId, ticket.intent);
1696
+ this.#applyStatus(refreshed);
1697
+ if (ticket.intent.kind === 'catalogSourcesRecovery'
1698
+ && code === 'catalogSourcesConfigRecoveryStale') {
1699
+ this.#phase = 'ready';
1700
+ this.#loadError = undefined;
1701
+ this.#activeReview = undefined;
1702
+ this.#result = undefined;
1703
+ this.#router.replace({ name: 'home' });
1704
+ this.#publish();
1705
+ return cloneFrozen({
1706
+ state: 'blocked',
1707
+ operationId,
1708
+ code,
1709
+ message: '来源配置已变化,已重新加载当前状态。',
1710
+ operationKind: ticket.intent.kind,
1711
+ });
1712
+ }
1713
+ }
1714
+ catch (reloadError) {
1715
+ if (!this.#operationIsCurrent(generation))
1716
+ return this.#detachedBlocked(operationId, ticket.intent);
1717
+ await this.#logErrorSafe(reloadError, 'tuiStateReloadFailed', {
1718
+ privateConfigOperation: true,
1719
+ });
1720
+ }
1721
+ if (!this.#operationIsCurrent(generation))
1722
+ return this.#detachedBlocked(operationId, ticket.intent);
1723
+ return this.#setBlocked(operationId, code, {
1724
+ intent: ticket.intent,
1725
+ navigation: 'result',
1726
+ returnRoute: ticket.returnRoute,
1727
+ writeMayHaveCompleted: ticket.intent.kind === 'catalogSourcesRecovery' ? false : true,
1728
+ });
1729
+ }
1730
+ }
1731
+ #operationIsCurrent(generation) {
1732
+ return !this.#disposed && !this.#abort.signal.aborted && generation === this.#generation;
1733
+ }
1734
+ #detachedBlocked(operationId, intent) {
1735
+ return cloneFrozen({
1736
+ state: 'blocked',
1737
+ operationId,
1738
+ code: 'operationCancelled',
1739
+ message: '操作已停止。',
1740
+ ...this.#operationIdentity(intent),
1741
+ });
1742
+ }
1743
+ async #rebuildStaleReview(ticket, operationId, currentEvidence, generation = this.#generation) {
1744
+ this.dependencies.beginCatalogSession?.();
1745
+ generation = ++this.#generation;
1746
+ try {
1747
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1748
+ if (!this.#operationIsCurrent(generation))
1749
+ return this.#detachedBlocked(operationId, ticket.intent);
1750
+ this.#applyStatus(refreshed);
1751
+ }
1752
+ catch (error) {
1753
+ if (!this.#operationIsCurrent(generation))
1754
+ return this.#detachedBlocked(operationId, ticket.intent);
1755
+ await this.#logErrorSafe(error, 'tuiStateReloadFailed', { privateConfigOperation: true });
1756
+ return this.#setBlocked(operationId, 'tuiStateReloadFailed', {
1757
+ intent: ticket.intent,
1758
+ navigation: 'result',
1759
+ returnRoute: ticket.returnRoute,
1760
+ });
1761
+ }
1762
+ const intent = ticket.intent;
1763
+ try {
1764
+ await this.dependencies.validateIntent?.(structuredClone(intent), this.#abort.signal);
1765
+ }
1766
+ catch (error) {
1767
+ if (!this.#operationIsCurrent(generation))
1768
+ return this.#detachedBlocked(operationId, ticket.intent);
1769
+ const code = issueCode(error);
1770
+ await this.#logOperationError(error, code, intent);
1771
+ if (!this.#operationIsCurrent(generation))
1772
+ return this.#detachedBlocked(operationId, ticket.intent);
1773
+ const result = {
1774
+ state: 'blocked',
1775
+ operationId,
1776
+ code,
1777
+ message: '状态已变化,原操作不再安全。请修改选择后重试。',
1778
+ ...this.#operationIdentity(intent),
1779
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
1780
+ };
1781
+ this.#result = structuredClone(result);
1782
+ this.#activeReview = undefined;
1783
+ this.#router.replace({ name: 'result', operationId });
1784
+ this.#publish();
1785
+ return cloneFrozen(result);
1786
+ }
1787
+ if (!this.#operationIsCurrent(generation))
1788
+ return this.#detachedBlocked(operationId, ticket.intent);
1789
+ let evidence = currentEvidence;
1790
+ if (!evidence) {
1791
+ try {
1792
+ evidence = await this.dependencies.captureEvidence(structuredClone(intent), this.#abort.signal);
1793
+ }
1794
+ catch (error) {
1795
+ if (!this.#operationIsCurrent(generation))
1796
+ return this.#detachedBlocked(operationId, ticket.intent);
1797
+ const code = issueCode(error);
1798
+ await this.#logOperationError(error, code, intent);
1799
+ if (!this.#operationIsCurrent(generation))
1800
+ return this.#detachedBlocked(operationId, ticket.intent);
1801
+ return this.#setBlocked(operationId, code, { intent, returnRoute: ticket.returnRoute });
1802
+ }
1803
+ }
1804
+ if (!this.#operationIsCurrent(generation))
1805
+ return this.#detachedBlocked(operationId, ticket.intent);
1806
+ if (intent.kind === 'portableInstall') {
1807
+ try {
1808
+ await this.refreshPortableInstallDraft(intent.pluginId);
1809
+ }
1810
+ catch (error) {
1811
+ if (!this.#operationIsCurrent(generation))
1812
+ return this.#detachedBlocked(operationId, ticket.intent);
1813
+ const code = issueCode(error);
1814
+ await this.#logOperationError(error, code, intent);
1815
+ return this.#setBlocked(operationId, code, { intent, returnRoute: ticket.returnRoute });
1816
+ }
1817
+ }
1818
+ if (!this.#operationIsCurrent(generation))
1819
+ return this.#detachedBlocked(operationId, ticket.intent);
1820
+ const review = this.#storeOperationReview(intent, evidence, 'replace', ticket.returnRoute);
1821
+ const result = { state: 'stale', operationId, review, ...this.#operationIdentity(intent) };
1822
+ this.#result = result;
1823
+ this.#publish();
1824
+ return cloneFrozen(result);
1825
+ }
1826
+ #setBlocked(operationId, code, options) {
1827
+ const message = code === 'managedCodexAuthorizationRequired'
1828
+ ? '操作在插件写入前停止。请在 Review 中确认获取 Codex runtime,或在命令行传入 --allow-managed-codex-install。'
1829
+ : code === 'codexSystemCliUnsupported'
1830
+ ? '操作在插件写入前停止。系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
1831
+ : code === 'managedCodexUnavailable'
1832
+ ? '操作在插件写入前停止。Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。'
1833
+ : code === 'portableSkillsCliInstallFailed'
1834
+ ? 'Skills CLI 安装失败。请修改 Agent 或重试。'
1835
+ : options?.writeMayHaveCompleted
1836
+ ? '写入可能已完成,当前无法确认。请查看刷新后的平台或问题状态。'
1837
+ : code === 'configResetStillPresent'
1838
+ ? '重置执行后设置层仍然存在。请重新检查设置问题。'
1839
+ : code === 'issueStillPresent'
1840
+ ? '写入已完成,但重新检查后原问题仍然存在。'
1841
+ : '操作未完成。请重新检查后重试。';
1842
+ const result = {
1843
+ state: 'blocked',
1844
+ operationId,
1845
+ code,
1846
+ message,
1847
+ ...(options?.intent ? this.#operationIdentity(options.intent) : {}),
1848
+ ...(options?.returnRoute ? { returnRoute: structuredClone(this.#normalizeRoute(options.returnRoute)) } : {}),
1849
+ ...(options?.writeMayHaveCompleted ? { writeMayHaveCompleted: true } : {}),
1850
+ ...(options?.verification ? { verification: options.verification } : {}),
1851
+ ...(options?.summary ? { summary: structuredClone(options.summary) } : {}),
1852
+ };
1853
+ this.#result = structuredClone(result);
1854
+ this.#activeReview = undefined;
1855
+ if (options?.navigation === 'back') {
1856
+ this.#router.back();
1857
+ this.#normalizeCurrentRoute();
1858
+ }
1859
+ else
1860
+ this.#router.replace({ name: 'result', operationId });
1861
+ this.#publish();
1862
+ return cloneFrozen(result);
1863
+ }
1864
+ #operationIdentity(intent) {
1865
+ return {
1866
+ operationKind: intent.kind,
1867
+ ...(intent.kind === 'sourceMutation' ? { sourceAction: intent.action } : {}),
1868
+ ...('pluginId' in intent ? { pluginId: intent.pluginId } : {}),
1869
+ ...(intent.kind === 'pluginWrite' && intent.originIssue
1870
+ ? { originIssue: intent.originIssue }
1871
+ : {}),
1872
+ };
1873
+ }
1874
+ dispose() {
1875
+ if (this.#disposed)
1876
+ return;
1877
+ this.#disposed = true;
1878
+ this.#cancelReviewPreparation();
1879
+ this.#cancelPortableRequest();
1880
+ this.#cancelInstallPlacementRequest();
1881
+ this.#catalogSourcesSession.dispose();
1882
+ this.#abort.abort();
1883
+ this.#progressUnsubscribe?.();
1884
+ this.#tickets.clear();
1885
+ this.#phase = 'disposed';
1886
+ this.#listeners.clear();
1887
+ }
1888
+ }
1889
+ function operationResultSummary(intent, execution) {
1890
+ if (intent.kind === 'catalogSourcesRecovery') {
1891
+ const result = execution;
1892
+ return {
1893
+ code: 'operationSucceeded',
1894
+ message: 'Catalog 来源配置已恢复。',
1895
+ messages: [
1896
+ `损坏配置已备份为 ${result.backupPath}。`,
1897
+ '已恢复唯一的主来源 official;自定义来源需要重新添加。',
1898
+ ],
1899
+ };
1900
+ }
1901
+ if (intent.kind === 'catalogGeneration') {
1902
+ const result = execution;
1903
+ const generation = result.generation;
1904
+ const updateActivated = intent.action === 'update'
1905
+ && generation?.activeChanged === true
1906
+ && generation.outcome === 'activated';
1907
+ return {
1908
+ code: result.status === 'partial' ? 'operationPartial' : 'operationSucceeded',
1909
+ catalogAction: intent.action,
1910
+ ...(generation ? { catalogGeneration: {
1911
+ activeChanged: generation.activeChanged,
1912
+ outcome: generation.outcome,
1913
+ catalogVersion: generation.resolution.catalogVersion,
1914
+ } } : {}),
1915
+ message: result.status === 'partial'
1916
+ ? result.partialReason === 'primary'
1917
+ ? 'Catalog 已启用,但主来源尚未切换。可在 TUI 中重试。'
1918
+ : '来源配置已保存,但 Catalog 尚未启用。可在 TUI 中立即重试。'
1919
+ : intent.action === 'repair' ? 'Catalog 已重新下载并修复。'
1920
+ : intent.action === 'replaceDamaged' ? '损坏的 Active Catalog 已由当前候选替换。'
1921
+ : intent.action === 'recoverState' ? 'Catalog generation state 已恢复,目标来源已启用。'
1922
+ : updateActivated ? `Catalog 已更新到 ${generation.resolution.catalogVersion}。`
1923
+ : intent.action === 'update' ? 'Catalog 已验证,Active 未变化。'
1924
+ : 'Catalog 已验证并启用。',
1925
+ messages: result.status === 'partial'
1926
+ ? [result.partialReason === 'primary'
1927
+ ? `新的 Active 可用;主来源保持不变。错误:${result.errorCode ?? 'catalogSourcePrimaryFailed'}`
1928
+ : `旧 Active 保持不变。错误:${result.errorCode ?? 'catalogGenerationRefreshFailed'}`]
1929
+ : updateActivated
1930
+ ? ['已安装插件不会自动升级。', '已重新计算可用的插件升级。']
1931
+ : intent.action === 'update'
1932
+ ? [`当前 Active Catalog 已是 ${generation?.resolution.catalogVersion ?? '已验证版本'}。`]
1933
+ : ['Plugin 列表已从新的 Active Catalog 重新加载。'],
1934
+ };
1935
+ }
1936
+ if (intent.kind === 'portableInstall') {
1937
+ const receipt = execution;
1938
+ const verificationUnknown = receipt.failures.some((failure) => failure.code === 'portableSkillsCliVerificationFailed'
1939
+ && receipt.verification.records.length === 0
1940
+ && receipt.verification.missing.length === 0);
1941
+ return {
1942
+ code: receipt.status === 'succeeded' ? 'operationSucceeded'
1943
+ : receipt.status === 'partial' ? 'operationPartial' : 'operationFailed',
1944
+ message: receipt.status === 'succeeded' ? '通用 Skills 已安装。'
1945
+ : receipt.status === 'partial' ? '安装命令已完成,但结果无法完全确认。'
1946
+ : 'Skills CLI 安装失败。',
1947
+ pluginId: intent.pluginId,
1948
+ messages: [
1949
+ ...receipt.selectedSkills.map((skill) => skill.name),
1950
+ ...receipt.failures.map((failure) => failure.message),
1951
+ ],
1952
+ portable: {
1953
+ selectedSkills: receipt.selectedSkills.map((skill) => skill.name),
1954
+ agentMode: receipt.agentMode,
1955
+ requestedAgents: receipt.requestedAgents,
1956
+ scope: receipt.scope,
1957
+ materializationDigest: receipt.materializationDigest,
1958
+ skillsCliVersion: receipt.skillsCliVersion,
1959
+ missing: receipt.verification.missing,
1960
+ verificationUnknown,
1961
+ ...(receipt.failures[0]?.recovery ? { recovery: receipt.failures[0].recovery } : {}),
1962
+ ...(receipt.failures[0]?.code ? { failureCode: receipt.failures[0].code } : {}),
1963
+ },
1964
+ };
1965
+ }
1966
+ if (intent.kind === 'pluginWrite') {
1967
+ const command = intent.action === 'refreshAndSync' ? 'sync' : intent.action;
1968
+ const rawExecution = typeof execution === 'object' && execution !== null
1969
+ ? execution
1970
+ : {};
1971
+ const normalizedTargetResults = Array.isArray(rawExecution.targetResults)
1972
+ ? rawExecution.targetResults.map((candidate) => {
1973
+ if (typeof candidate !== 'object' || candidate === null)
1974
+ return candidate;
1975
+ const entry = candidate;
1976
+ const rawTarget = typeof entry.target === 'object' && entry.target !== null
1977
+ ? entry.target
1978
+ : {};
1979
+ const pluginId = typeof rawTarget.pluginId === 'string'
1980
+ ? rawTarget.pluginId : undefined;
1981
+ return {
1982
+ ...entry,
1983
+ target: {
1984
+ ...rawTarget,
1985
+ ...(pluginId ? { pluginId } : {}),
1986
+ pluginName: typeof rawTarget.pluginName === 'string'
1987
+ ? rawTarget.pluginName : pluginNameFromId(pluginId ?? intent.pluginId),
1988
+ },
1989
+ action: typeof entry.action === 'string'
1990
+ ? entry.action : requestedIntentAction(command),
1991
+ };
1992
+ })
1993
+ : undefined;
1994
+ const receipt = buildRebuildReceiptJson(command, {
1995
+ ...rawExecution,
1996
+ ...(normalizedTargetResults ? { targetResults: normalizedTargetResults } : {}),
1997
+ platforms: Array.isArray(rawExecution.platforms) ? rawExecution.platforms : [],
1998
+ plugins: Array.isArray(rawExecution.plugins) ? rawExecution.plugins : [intent.pluginId],
1999
+ warnings: Array.isArray(rawExecution.warnings) ? rawExecution.warnings : [],
2000
+ });
2001
+ const result = buildOperationResultModel(receipt);
2002
+ const requestedIds = new Set(receipt.requestedPluginIds ?? [intent.pluginId]);
2003
+ return {
2004
+ code: result.state === 'succeeded' ? 'operationSucceeded'
2005
+ : result.state === 'partial' ? 'operationPartial' : 'operationFailed',
2006
+ message: result.state === 'succeeded' ? '操作已完成。'
2007
+ : result.state === 'partial' ? '操作只完成了一部分。' : '操作结果无法确认。',
2008
+ pluginId: intent.pluginId,
2009
+ action: intent.action,
2010
+ changes: result.changes,
2011
+ warnings: result.warnings,
2012
+ recoveryActions: result.recoveryActions,
2013
+ ...(result.planFingerprint ? { planFingerprint: result.planFingerprint } : {}),
2014
+ messages: [
2015
+ ...result.warnings.map((warning) => `警告:${warning}`),
2016
+ ...result.recoveryActions.map((action) => `恢复:${action}`),
2017
+ ...(intent.recoveryDescription
2018
+ ? [intent.recoveryDescription, `后续:${intent.scope === 'user' ? '当前项目' : '所有项目'} sync`]
2019
+ : []),
2020
+ ],
2021
+ targets: result.targetResults.map((entry) => ({
2022
+ pluginId: entry.target.pluginId,
2023
+ platform: entry.target.platform,
2024
+ scope: entry.target.scope,
2025
+ state: entry.action === 'skipped' ? 'skipped'
2026
+ : entry.state === 'failed' ? 'failed'
2027
+ : entry.action === 'keep' ? 'kept'
2028
+ : entry.state,
2029
+ relation: entry.relation === 'requested' ? 'requested'
2030
+ : entry.requiredBy.some((pluginId) => requestedIds.has(pluginId)) ? 'direct' : 'transitive',
2031
+ requiredBy: entry.requiredBy,
2032
+ ...(entry.errorCode === 'dependencyStillRequired'
2033
+ || entry.errorCode === 'codexSystemCliUnsupported'
2034
+ || entry.errorCode === 'managedCodexAuthorizationRequired'
2035
+ || entry.errorCode === 'managedCodexUnavailable'
2036
+ ? { errorCode: entry.errorCode }
2037
+ : {}),
2038
+ })),
2039
+ };
2040
+ }
2041
+ if (intent.kind === 'configSave' || intent.kind === 'configReset') {
2042
+ const rawTargets = typeof execution === 'object' && execution !== null
2043
+ && Array.isArray(execution.targetResults)
2044
+ ? execution.targetResults
2045
+ : [];
2046
+ const targets = rawTargets.flatMap((entry) => {
2047
+ const target = typeof entry.target === 'object' && entry.target !== null
2048
+ ? entry.target
2049
+ : entry;
2050
+ if ((target.platform !== 'codex' && target.platform !== 'claude')
2051
+ || (target.scope !== 'user' && target.scope !== 'project')
2052
+ || (entry.state !== 'completed' && entry.state !== 'failed'))
2053
+ return [];
2054
+ return [{
2055
+ ...(typeof target.pluginId === 'string' ? { pluginId: target.pluginId } : {}),
2056
+ platform: target.platform,
2057
+ scope: target.scope,
2058
+ state: entry.action === 'skipped' ? 'skipped'
2059
+ : entry.state === 'failed' ? 'failed'
2060
+ : entry.action === 'keep' ? 'kept'
2061
+ : entry.state,
2062
+ }];
2063
+ });
2064
+ const overallState = typeof execution === 'object' && execution !== null
2065
+ ? execution.overallState
2066
+ : undefined;
2067
+ const applySkippedReason = typeof execution === 'object' && execution !== null
2068
+ ? execution.applySkippedReason
2069
+ : undefined;
2070
+ const invalidConfig = applySkippedReason === 'invalidConfig';
2071
+ const resetDisposition = typeof execution === 'object' && execution !== null
2072
+ ? execution.resetDisposition
2073
+ : undefined;
2074
+ const quarantineBasename = typeof execution === 'object' && execution !== null
2075
+ ? execution.quarantineBasename
2076
+ : undefined;
2077
+ const partial = overallState === 'partial'
2078
+ || invalidConfig
2079
+ || targets.some((target) => target.state === 'failed' || target.state === 'skipped')
2080
+ && targets.some((target) => target.state === 'completed' || target.state === 'kept');
2081
+ const failed = overallState === 'failed'
2082
+ || (targets.length > 0
2083
+ && targets.every((target) => target.state === 'failed' || target.state === 'skipped'));
2084
+ return {
2085
+ code: failed || partial ? 'operationPartial' : 'operationSucceeded',
2086
+ message: invalidConfig ? '设置已保存,但没有应用。当前配置仍无效。'
2087
+ : failed || partial ? '设置已保存,但插件内容重新应用失败或部分失败。' : '操作已完成。',
2088
+ pluginId: intent.pluginId,
2089
+ configScope: intent.scope,
2090
+ configApplyRetryAvailable: intent.kind === 'configSave'
2091
+ && intent.apply && !invalidConfig && (failed || partial),
2092
+ ...(intent.kind === 'configSave'
2093
+ ? { configApplied: invalidConfig ? false : intent.apply }
2094
+ : invalidConfig ? { configApplied: false } : {}),
2095
+ ...(invalidConfig ? { configApplySkippedReason: 'invalidConfig' } : {}),
2096
+ ...(intent.kind === 'configReset'
2097
+ && (resetDisposition === 'absent'
2098
+ || resetDisposition === 'deletedFile'
2099
+ || resetDisposition === 'unlinkedSymlink'
2100
+ || resetDisposition === 'quarantinedDirectory')
2101
+ ? {
2102
+ resetDisposition,
2103
+ ...(resetDisposition === 'quarantinedDirectory' && typeof quarantineBasename === 'string'
2104
+ ? { quarantineBasename } : {}),
2105
+ }
2106
+ : {}),
2107
+ ...(targets.length > 0 ? { targets } : {}),
2108
+ };
2109
+ }
2110
+ const sourceWarnings = intent.kind === 'sourceMutation'
2111
+ ? projectCatalogSourceMutationWarnings(execution) : [];
2112
+ const overallFailed = typeof execution === 'object' && execution !== null
2113
+ && execution.overallState === 'failed';
2114
+ return {
2115
+ code: overallFailed ? 'operationFailed' : 'operationSucceeded',
2116
+ message: overallFailed ? '操作结果无法确认。' : '操作已完成。',
2117
+ ...(sourceWarnings.length > 0 ? { messages: sourceWarnings } : {}),
2118
+ };
2119
+ }
2120
+ //# sourceMappingURL=controller.js.map