@lesliechan721/aw 0.1.0-beta.3

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 (429) hide show
  1. package/dist/authoring/assets/aw-config.d.mts +2 -0
  2. package/dist/authoring/assets/aw-config.mjs +556 -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/check-plugin-versions.d.mts +2 -0
  8. package/dist/authoring/scripts/check-plugin-versions.mjs +5 -0
  9. package/dist/authoring/scripts/check-plugin-versions.mjs.map +1 -0
  10. package/dist/authoring/scripts/init-plugin.d.mts +2 -0
  11. package/dist/authoring/scripts/init-plugin.mjs +512 -0
  12. package/dist/authoring/scripts/init-plugin.mjs.map +1 -0
  13. package/dist/authoring/scripts/lib/javascript-import-scanner.d.mts +4 -0
  14. package/dist/authoring/scripts/lib/javascript-import-scanner.mjs +261 -0
  15. package/dist/authoring/scripts/lib/javascript-import-scanner.mjs.map +1 -0
  16. package/dist/authoring/scripts/lib/plugin-root.d.mts +26 -0
  17. package/dist/authoring/scripts/lib/plugin-root.mjs +125 -0
  18. package/dist/authoring/scripts/lib/plugin-root.mjs.map +1 -0
  19. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +55 -0
  20. package/dist/authoring/scripts/lib/plugin-version-check.mjs +777 -0
  21. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -0
  22. package/dist/authoring/scripts/lib/semver.d.mts +9 -0
  23. package/dist/authoring/scripts/lib/semver.mjs +63 -0
  24. package/dist/authoring/scripts/lib/semver.mjs.map +1 -0
  25. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +124 -0
  26. package/dist/authoring/scripts/lib/skill-scaffold.mjs +374 -0
  27. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -0
  28. package/dist/authoring/scripts/plugin-status.d.mts +2 -0
  29. package/dist/authoring/scripts/plugin-status.mjs +96 -0
  30. package/dist/authoring/scripts/plugin-status.mjs.map +1 -0
  31. package/dist/authoring/scripts/validate-plugin.d.mts +2 -0
  32. package/dist/authoring/scripts/validate-plugin.mjs +4442 -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/index.d.ts +2 -0
  38. package/dist/index.js +8 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/scripts/aw-skill-eval-engine.d.ts +13 -0
  41. package/dist/scripts/aw-skill-eval-engine.js +31 -0
  42. package/dist/scripts/aw-skill-eval-engine.js.map +1 -0
  43. package/dist/scripts/bundle-plugins.d.ts +18 -0
  44. package/dist/scripts/bundle-plugins.js +123 -0
  45. package/dist/scripts/bundle-plugins.js.map +1 -0
  46. package/dist/scripts/codex-eval-app-server.d.ts +89 -0
  47. package/dist/scripts/codex-eval-app-server.js +1228 -0
  48. package/dist/scripts/codex-eval-app-server.js.map +1 -0
  49. package/dist/scripts/codex-eval-isolation.d.ts +61 -0
  50. package/dist/scripts/codex-eval-isolation.js +318 -0
  51. package/dist/scripts/codex-eval-isolation.js.map +1 -0
  52. package/dist/scripts/codex-eval-process-management.d.ts +8 -0
  53. package/dist/scripts/codex-eval-process-management.js +69 -0
  54. package/dist/scripts/codex-eval-process-management.js.map +1 -0
  55. package/dist/scripts/codex-eval-process-tree.d.ts +38 -0
  56. package/dist/scripts/codex-eval-process-tree.js +803 -0
  57. package/dist/scripts/codex-eval-process-tree.js.map +1 -0
  58. package/dist/scripts/codex-eval-runtime-config.d.ts +62 -0
  59. package/dist/scripts/codex-eval-runtime-config.js +843 -0
  60. package/dist/scripts/codex-eval-runtime-config.js.map +1 -0
  61. package/dist/scripts/codex-trigger-eval.d.ts +84 -0
  62. package/dist/scripts/codex-trigger-eval.js +968 -0
  63. package/dist/scripts/codex-trigger-eval.js.map +1 -0
  64. package/dist/scripts/eval-plugin-skills-audited.d.ts +6 -0
  65. package/dist/scripts/eval-plugin-skills-audited.js +261 -0
  66. package/dist/scripts/eval-plugin-skills-audited.js.map +1 -0
  67. package/dist/scripts/eval-plugin-skills.d.ts +45 -0
  68. package/dist/scripts/eval-plugin-skills.js +1586 -0
  69. package/dist/scripts/eval-plugin-skills.js.map +1 -0
  70. package/dist/scripts/eval-run-layout.d.ts +17 -0
  71. package/dist/scripts/eval-run-layout.js +26 -0
  72. package/dist/scripts/eval-run-layout.js.map +1 -0
  73. package/dist/scripts/plugin-eval-contract.d.ts +185 -0
  74. package/dist/scripts/plugin-eval-contract.js +695 -0
  75. package/dist/scripts/plugin-eval-contract.js.map +1 -0
  76. package/dist/scripts/plugin-eval-materializer.d.ts +13 -0
  77. package/dist/scripts/plugin-eval-materializer.js +421 -0
  78. package/dist/scripts/plugin-eval-materializer.js.map +1 -0
  79. package/dist/scripts/plugin-staging.d.ts +43 -0
  80. package/dist/scripts/plugin-staging.js +196 -0
  81. package/dist/scripts/plugin-staging.js.map +1 -0
  82. package/dist/scripts/skill-reporting.d.ts +212 -0
  83. package/dist/scripts/skill-reporting.js +922 -0
  84. package/dist/scripts/skill-reporting.js.map +1 -0
  85. package/dist/scripts/test-plugin-scripts.d.ts +7 -0
  86. package/dist/scripts/test-plugin-scripts.js +674 -0
  87. package/dist/scripts/test-plugin-scripts.js.map +1 -0
  88. package/dist/src/adapters/helpers.d.ts +44 -0
  89. package/dist/src/adapters/helpers.js +95 -0
  90. package/dist/src/adapters/helpers.js.map +1 -0
  91. package/dist/src/authoring/workspace-commands.d.ts +46 -0
  92. package/dist/src/authoring/workspace-commands.js +133 -0
  93. package/dist/src/authoring/workspace-commands.js.map +1 -0
  94. package/dist/src/commands/catalog-publish.d.ts +33 -0
  95. package/dist/src/commands/catalog-publish.js +374 -0
  96. package/dist/src/commands/catalog-publish.js.map +1 -0
  97. package/dist/src/commands/catalog-source.d.ts +111 -0
  98. package/dist/src/commands/catalog-source.js +266 -0
  99. package/dist/src/commands/catalog-source.js.map +1 -0
  100. package/dist/src/commands/console.d.ts +3 -0
  101. package/dist/src/commands/console.js +44 -0
  102. package/dist/src/commands/console.js.map +1 -0
  103. package/dist/src/commands/dependency-operation-plan.d.ts +28 -0
  104. package/dist/src/commands/dependency-operation-plan.js +15 -0
  105. package/dist/src/commands/dependency-operation-plan.js.map +1 -0
  106. package/dist/src/commands/init.d.ts +31 -0
  107. package/dist/src/commands/init.js +63 -0
  108. package/dist/src/commands/init.js.map +1 -0
  109. package/dist/src/commands/options.d.ts +68 -0
  110. package/dist/src/commands/options.js +146 -0
  111. package/dist/src/commands/options.js.map +1 -0
  112. package/dist/src/commands/rebuild-command.d.ts +77 -0
  113. package/dist/src/commands/rebuild-command.js +1681 -0
  114. package/dist/src/commands/rebuild-command.js.map +1 -0
  115. package/dist/src/commands/rebuild-integration.d.ts +94 -0
  116. package/dist/src/commands/rebuild-integration.js +3289 -0
  117. package/dist/src/commands/rebuild-integration.js.map +1 -0
  118. package/dist/src/commands/rebuild-operations.d.ts +110 -0
  119. package/dist/src/commands/rebuild-operations.js +791 -0
  120. package/dist/src/commands/rebuild-operations.js.map +1 -0
  121. package/dist/src/commands/review-publication.d.ts +99 -0
  122. package/dist/src/commands/review-publication.js +904 -0
  123. package/dist/src/commands/review-publication.js.map +1 -0
  124. package/dist/src/commands/runtime.d.ts +176 -0
  125. package/dist/src/commands/runtime.js +746 -0
  126. package/dist/src/commands/runtime.js.map +1 -0
  127. package/dist/src/commands/status-v4.d.ts +102 -0
  128. package/dist/src/commands/status-v4.js +844 -0
  129. package/dist/src/commands/status-v4.js.map +1 -0
  130. package/dist/src/commands/status.d.ts +93 -0
  131. package/dist/src/commands/status.js +442 -0
  132. package/dist/src/commands/status.js.map +1 -0
  133. package/dist/src/commands/wizard.d.ts +57 -0
  134. package/dist/src/commands/wizard.js +58 -0
  135. package/dist/src/commands/wizard.js.map +1 -0
  136. package/dist/src/commands/write-commands.d.ts +7 -0
  137. package/dist/src/commands/write-commands.js +167 -0
  138. package/dist/src/commands/write-commands.js.map +1 -0
  139. package/dist/src/constants.d.ts +25 -0
  140. package/dist/src/constants.js +26 -0
  141. package/dist/src/constants.js.map +1 -0
  142. package/dist/src/generators/agents.d.ts +3 -0
  143. package/dist/src/generators/agents.js +46 -0
  144. package/dist/src/generators/agents.js.map +1 -0
  145. package/dist/src/generators/hooks.d.ts +15 -0
  146. package/dist/src/generators/hooks.js +76 -0
  147. package/dist/src/generators/hooks.js.map +1 -0
  148. package/dist/src/generators/instructions.d.ts +13 -0
  149. package/dist/src/generators/instructions.js +66 -0
  150. package/dist/src/generators/instructions.js.map +1 -0
  151. package/dist/src/generators/local-plugin.d.ts +53 -0
  152. package/dist/src/generators/local-plugin.js +1268 -0
  153. package/dist/src/generators/local-plugin.js.map +1 -0
  154. package/dist/src/generators/permissions.d.ts +10 -0
  155. package/dist/src/generators/permissions.js +37 -0
  156. package/dist/src/generators/permissions.js.map +1 -0
  157. package/dist/src/generators/shared.d.ts +2 -0
  158. package/dist/src/generators/shared.js +15 -0
  159. package/dist/src/generators/shared.js.map +1 -0
  160. package/dist/src/index.d.ts +35 -0
  161. package/dist/src/index.js +2344 -0
  162. package/dist/src/index.js.map +1 -0
  163. package/dist/src/output/command-receipt.d.ts +90 -0
  164. package/dist/src/output/command-receipt.js +129 -0
  165. package/dist/src/output/command-receipt.js.map +1 -0
  166. package/dist/src/output/debug-log.d.ts +4 -0
  167. package/dist/src/output/debug-log.js +97 -0
  168. package/dist/src/output/debug-log.js.map +1 -0
  169. package/dist/src/output/equivalent-command.d.ts +9 -0
  170. package/dist/src/output/equivalent-command.js +39 -0
  171. package/dist/src/output/equivalent-command.js.map +1 -0
  172. package/dist/src/output/model.d.ts +117 -0
  173. package/dist/src/output/model.js +145 -0
  174. package/dist/src/output/model.js.map +1 -0
  175. package/dist/src/output/rebuild-progress.d.ts +21 -0
  176. package/dist/src/output/rebuild-progress.js +138 -0
  177. package/dist/src/output/rebuild-progress.js.map +1 -0
  178. package/dist/src/output/redact.d.ts +3 -0
  179. package/dist/src/output/redact.js +39 -0
  180. package/dist/src/output/redact.js.map +1 -0
  181. package/dist/src/output/reporter.d.ts +9 -0
  182. package/dist/src/output/reporter.js +133 -0
  183. package/dist/src/output/reporter.js.map +1 -0
  184. package/dist/src/portable/commands.d.ts +120 -0
  185. package/dist/src/portable/commands.js +506 -0
  186. package/dist/src/portable/commands.js.map +1 -0
  187. package/dist/src/portable/materializer.d.ts +50 -0
  188. package/dist/src/portable/materializer.js +408 -0
  189. package/dist/src/portable/materializer.js.map +1 -0
  190. package/dist/src/portable/skills-cli.d.ts +43 -0
  191. package/dist/src/portable/skills-cli.js +148 -0
  192. package/dist/src/portable/skills-cli.js.map +1 -0
  193. package/dist/src/tui/components.d.ts +32 -0
  194. package/dist/src/tui/components.js +42 -0
  195. package/dist/src/tui/components.js.map +1 -0
  196. package/dist/src/tui/console-app-v4.d.ts +20 -0
  197. package/dist/src/tui/console-app-v4.js +2613 -0
  198. package/dist/src/tui/console-app-v4.js.map +1 -0
  199. package/dist/src/tui/controller-runtime.d.ts +11 -0
  200. package/dist/src/tui/controller-runtime.js +902 -0
  201. package/dist/src/tui/controller-runtime.js.map +1 -0
  202. package/dist/src/tui/controller.d.ts +367 -0
  203. package/dist/src/tui/controller.js +1724 -0
  204. package/dist/src/tui/controller.js.map +1 -0
  205. package/dist/src/tui/index.d.ts +13 -0
  206. package/dist/src/tui/index.js +14 -0
  207. package/dist/src/tui/index.js.map +1 -0
  208. package/dist/src/tui/keys.d.ts +12 -0
  209. package/dist/src/tui/keys.js +22 -0
  210. package/dist/src/tui/keys.js.map +1 -0
  211. package/dist/src/tui/layout.d.ts +12 -0
  212. package/dist/src/tui/layout.js +19 -0
  213. package/dist/src/tui/layout.js.map +1 -0
  214. package/dist/src/tui/model.d.ts +279 -0
  215. package/dist/src/tui/model.js +652 -0
  216. package/dist/src/tui/model.js.map +1 -0
  217. package/dist/src/tui/prompt.d.ts +8 -0
  218. package/dist/src/tui/prompt.js +242 -0
  219. package/dist/src/tui/prompt.js.map +1 -0
  220. package/dist/src/tui/router.d.ts +153 -0
  221. package/dist/src/tui/router.js +102 -0
  222. package/dist/src/tui/router.js.map +1 -0
  223. package/dist/src/tui/runner.d.ts +11 -0
  224. package/dist/src/tui/runner.js +71 -0
  225. package/dist/src/tui/runner.js.map +1 -0
  226. package/dist/src/tui/theme.d.ts +18 -0
  227. package/dist/src/tui/theme.js +20 -0
  228. package/dist/src/tui/theme.js.map +1 -0
  229. package/dist/src/tui/views.d.ts +57 -0
  230. package/dist/src/tui/views.js +173 -0
  231. package/dist/src/tui/views.js.map +1 -0
  232. package/dist/src/tui/wizard.d.ts +11 -0
  233. package/dist/src/tui/wizard.js +321 -0
  234. package/dist/src/tui/wizard.js.map +1 -0
  235. package/dist/src/types.d.ts +313 -0
  236. package/dist/src/types.js +33 -0
  237. package/dist/src/types.js.map +1 -0
  238. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +12 -0
  239. package/dist/src/utils/active-marketplace-runtime-lock.js +133 -0
  240. package/dist/src/utils/active-marketplace-runtime-lock.js.map +1 -0
  241. package/dist/src/utils/agent-version.d.ts +13 -0
  242. package/dist/src/utils/agent-version.js +172 -0
  243. package/dist/src/utils/agent-version.js.map +1 -0
  244. package/dist/src/utils/artifact-plan.d.ts +26 -0
  245. package/dist/src/utils/artifact-plan.js +444 -0
  246. package/dist/src/utils/artifact-plan.js.map +1 -0
  247. package/dist/src/utils/artifact-renderer.d.ts +124 -0
  248. package/dist/src/utils/artifact-renderer.js +442 -0
  249. package/dist/src/utils/artifact-renderer.js.map +1 -0
  250. package/dist/src/utils/artifact-sources.d.ts +8 -0
  251. package/dist/src/utils/artifact-sources.js +10 -0
  252. package/dist/src/utils/artifact-sources.js.map +1 -0
  253. package/dist/src/utils/aw-home-mutation-lock.d.ts +10 -0
  254. package/dist/src/utils/aw-home-mutation-lock.js +55 -0
  255. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -0
  256. package/dist/src/utils/bundle-integrity.d.ts +3 -0
  257. package/dist/src/utils/bundle-integrity.js +88 -0
  258. package/dist/src/utils/bundle-integrity.js.map +1 -0
  259. package/dist/src/utils/bundle-layout.d.ts +4 -0
  260. package/dist/src/utils/bundle-layout.js +546 -0
  261. package/dist/src/utils/bundle-layout.js.map +1 -0
  262. package/dist/src/utils/catalog-digests.d.ts +8 -0
  263. package/dist/src/utils/catalog-digests.js +108 -0
  264. package/dist/src/utils/catalog-digests.js.map +1 -0
  265. package/dist/src/utils/catalog-manifest-v3.d.ts +2 -0
  266. package/dist/src/utils/catalog-manifest-v3.js +3 -0
  267. package/dist/src/utils/catalog-manifest-v3.js.map +1 -0
  268. package/dist/src/utils/catalog-manifest-v4.d.ts +5 -0
  269. package/dist/src/utils/catalog-manifest-v4.js +106 -0
  270. package/dist/src/utils/catalog-manifest-v4.js.map +1 -0
  271. package/dist/src/utils/catalog-public-error.d.ts +15 -0
  272. package/dist/src/utils/catalog-public-error.js +84 -0
  273. package/dist/src/utils/catalog-public-error.js.map +1 -0
  274. package/dist/src/utils/catalog-runtime-projection.d.ts +36 -0
  275. package/dist/src/utils/catalog-runtime-projection.js +172 -0
  276. package/dist/src/utils/catalog-runtime-projection.js.map +1 -0
  277. package/dist/src/utils/catalog-snapshot.d.ts +8 -0
  278. package/dist/src/utils/catalog-snapshot.js +12 -0
  279. package/dist/src/utils/catalog-snapshot.js.map +1 -0
  280. package/dist/src/utils/catalog-source-cache.d.ts +61 -0
  281. package/dist/src/utils/catalog-source-cache.js +276 -0
  282. package/dist/src/utils/catalog-source-cache.js.map +1 -0
  283. package/dist/src/utils/catalog-source-identity.d.ts +4 -0
  284. package/dist/src/utils/catalog-source-identity.js +118 -0
  285. package/dist/src/utils/catalog-source-identity.js.map +1 -0
  286. package/dist/src/utils/catalog-source-presentation.d.ts +1 -0
  287. package/dist/src/utils/catalog-source-presentation.js +32 -0
  288. package/dist/src/utils/catalog-source-presentation.js.map +1 -0
  289. package/dist/src/utils/catalog-source-references.d.ts +65 -0
  290. package/dist/src/utils/catalog-source-references.js +1418 -0
  291. package/dist/src/utils/catalog-source-references.js.map +1 -0
  292. package/dist/src/utils/catalog-sources.d.ts +39 -0
  293. package/dist/src/utils/catalog-sources.js +293 -0
  294. package/dist/src/utils/catalog-sources.js.map +1 -0
  295. package/dist/src/utils/codex-config.d.ts +124 -0
  296. package/dist/src/utils/codex-config.js +1011 -0
  297. package/dist/src/utils/codex-config.js.map +1 -0
  298. package/dist/src/utils/codex-project-surface.d.ts +3 -0
  299. package/dist/src/utils/codex-project-surface.js +49 -0
  300. package/dist/src/utils/codex-project-surface.js.map +1 -0
  301. package/dist/src/utils/codex-roles.d.ts +71 -0
  302. package/dist/src/utils/codex-roles.js +257 -0
  303. package/dist/src/utils/codex-roles.js.map +1 -0
  304. package/dist/src/utils/codex-skill-staging.d.ts +9 -0
  305. package/dist/src/utils/codex-skill-staging.js +8 -0
  306. package/dist/src/utils/codex-skill-staging.js.map +1 -0
  307. package/dist/src/utils/config-value.d.ts +3 -0
  308. package/dist/src/utils/config-value.js +23 -0
  309. package/dist/src/utils/config-value.js.map +1 -0
  310. package/dist/src/utils/dynamic-skill-tree.d.ts +32 -0
  311. package/dist/src/utils/dynamic-skill-tree.js +436 -0
  312. package/dist/src/utils/dynamic-skill-tree.js.map +1 -0
  313. package/dist/src/utils/file-rules.d.ts +1 -0
  314. package/dist/src/utils/file-rules.js +2 -0
  315. package/dist/src/utils/file-rules.js.map +1 -0
  316. package/dist/src/utils/git-catalog-provider.d.ts +7 -0
  317. package/dist/src/utils/git-catalog-provider.js +510 -0
  318. package/dist/src/utils/git-catalog-provider.js.map +1 -0
  319. package/dist/src/utils/hooks-contract.d.ts +1 -0
  320. package/dist/src/utils/hooks-contract.js +2 -0
  321. package/dist/src/utils/hooks-contract.js.map +1 -0
  322. package/dist/src/utils/json.d.ts +4 -0
  323. package/dist/src/utils/json.js +29 -0
  324. package/dist/src/utils/json.js.map +1 -0
  325. package/dist/src/utils/lifecycle-active-store.d.ts +175 -0
  326. package/dist/src/utils/lifecycle-active-store.js +1276 -0
  327. package/dist/src/utils/lifecycle-active-store.js.map +1 -0
  328. package/dist/src/utils/local-marketplace.d.ts +41 -0
  329. package/dist/src/utils/local-marketplace.js +317 -0
  330. package/dist/src/utils/local-marketplace.js.map +1 -0
  331. package/dist/src/utils/manifest.d.ts +14 -0
  332. package/dist/src/utils/manifest.js +36 -0
  333. package/dist/src/utils/manifest.js.map +1 -0
  334. package/dist/src/utils/marketplace.d.ts +3 -0
  335. package/dist/src/utils/marketplace.js +76 -0
  336. package/dist/src/utils/marketplace.js.map +1 -0
  337. package/dist/src/utils/multi-source-catalog.d.ts +72 -0
  338. package/dist/src/utils/multi-source-catalog.js +402 -0
  339. package/dist/src/utils/multi-source-catalog.js.map +1 -0
  340. package/dist/src/utils/native-cli.d.ts +75 -0
  341. package/dist/src/utils/native-cli.js +435 -0
  342. package/dist/src/utils/native-cli.js.map +1 -0
  343. package/dist/src/utils/orphan-marketplace-cleanup.d.ts +18 -0
  344. package/dist/src/utils/orphan-marketplace-cleanup.js +202 -0
  345. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -0
  346. package/dist/src/utils/ownership.d.ts +130 -0
  347. package/dist/src/utils/ownership.js +766 -0
  348. package/dist/src/utils/ownership.js.map +1 -0
  349. package/dist/src/utils/path-catalog-provider.d.ts +7 -0
  350. package/dist/src/utils/path-catalog-provider.js +87 -0
  351. package/dist/src/utils/path-catalog-provider.js.map +1 -0
  352. package/dist/src/utils/paths.d.ts +41 -0
  353. package/dist/src/utils/paths.js +138 -0
  354. package/dist/src/utils/paths.js.map +1 -0
  355. package/dist/src/utils/platform-entries.d.ts +1 -0
  356. package/dist/src/utils/platform-entries.js +2 -0
  357. package/dist/src/utils/platform-entries.js.map +1 -0
  358. package/dist/src/utils/platform-support.d.ts +1 -0
  359. package/dist/src/utils/platform-support.js +2 -0
  360. package/dist/src/utils/platform-support.js.map +1 -0
  361. package/dist/src/utils/plugin-authoring-capabilities.d.ts +23 -0
  362. package/dist/src/utils/plugin-authoring-capabilities.js +37 -0
  363. package/dist/src/utils/plugin-authoring-capabilities.js.map +1 -0
  364. package/dist/src/utils/plugin-authoring-capabilities.json +92 -0
  365. package/dist/src/utils/plugin-config-write.d.ts +107 -0
  366. package/dist/src/utils/plugin-config-write.js +565 -0
  367. package/dist/src/utils/plugin-config-write.js.map +1 -0
  368. package/dist/src/utils/plugin-config.d.ts +396 -0
  369. package/dist/src/utils/plugin-config.js +1898 -0
  370. package/dist/src/utils/plugin-config.js.map +1 -0
  371. package/dist/src/utils/plugin-dependencies.d.ts +12 -0
  372. package/dist/src/utils/plugin-dependencies.js +162 -0
  373. package/dist/src/utils/plugin-dependencies.js.map +1 -0
  374. package/dist/src/utils/plugin-release.d.ts +2 -0
  375. package/dist/src/utils/plugin-release.js +2 -0
  376. package/dist/src/utils/plugin-release.js.map +1 -0
  377. package/dist/src/utils/plugin-script-contract.d.ts +1 -0
  378. package/dist/src/utils/plugin-script-contract.js +2 -0
  379. package/dist/src/utils/plugin-script-contract.js.map +1 -0
  380. package/dist/src/utils/plugin-scripts.d.ts +172 -0
  381. package/dist/src/utils/plugin-scripts.js +585 -0
  382. package/dist/src/utils/plugin-scripts.js.map +1 -0
  383. package/dist/src/utils/process-lock.d.ts +47 -0
  384. package/dist/src/utils/process-lock.js +532 -0
  385. package/dist/src/utils/process-lock.js.map +1 -0
  386. package/dist/src/utils/process-tree.d.ts +2 -0
  387. package/dist/src/utils/process-tree.js +2 -0
  388. package/dist/src/utils/process-tree.js.map +1 -0
  389. package/dist/src/utils/project-boundary.d.ts +21 -0
  390. package/dist/src/utils/project-boundary.js +95 -0
  391. package/dist/src/utils/project-boundary.js.map +1 -0
  392. package/dist/src/utils/rendered-contract.d.ts +8 -0
  393. package/dist/src/utils/rendered-contract.js +134 -0
  394. package/dist/src/utils/rendered-contract.js.map +1 -0
  395. package/dist/src/utils/runtime-skill-closure.d.ts +13 -0
  396. package/dist/src/utils/runtime-skill-closure.js +863 -0
  397. package/dist/src/utils/runtime-skill-closure.js.map +1 -0
  398. package/dist/src/utils/skill-authoring.d.ts +1 -0
  399. package/dist/src/utils/skill-authoring.js +2 -0
  400. package/dist/src/utils/skill-authoring.js.map +1 -0
  401. package/dist/src/utils/skill-frontmatter-capabilities.d.ts +201 -0
  402. package/dist/src/utils/skill-frontmatter-capabilities.js +5 -0
  403. package/dist/src/utils/skill-frontmatter-capabilities.js.map +1 -0
  404. package/dist/src/utils/skill-frontmatter-capabilities.json +200 -0
  405. package/dist/src/utils/skill-validation.d.ts +37 -0
  406. package/dist/src/utils/skill-validation.js +326 -0
  407. package/dist/src/utils/skill-validation.js.map +1 -0
  408. package/dist/src/utils/slash-commands.d.ts +1 -0
  409. package/dist/src/utils/slash-commands.js +2 -0
  410. package/dist/src/utils/slash-commands.js.map +1 -0
  411. package/dist/src/utils/state-paths.d.ts +13 -0
  412. package/dist/src/utils/state-paths.js +129 -0
  413. package/dist/src/utils/state-paths.js.map +1 -0
  414. package/dist/src/utils/target-eligibility.d.ts +7 -0
  415. package/dist/src/utils/target-eligibility.js +26 -0
  416. package/dist/src/utils/target-eligibility.js.map +1 -0
  417. package/dist/src/utils/target-order.d.ts +7 -0
  418. package/dist/src/utils/target-order.js +10 -0
  419. package/dist/src/utils/target-order.js.map +1 -0
  420. package/dist/src/utils/template-engine.d.ts +1 -0
  421. package/dist/src/utils/template-engine.js +2 -0
  422. package/dist/src/utils/template-engine.js.map +1 -0
  423. package/dist/src/utils/test-path-fence.d.ts +12 -0
  424. package/dist/src/utils/test-path-fence.js +31 -0
  425. package/dist/src/utils/test-path-fence.js.map +1 -0
  426. package/dist/vitest.config.d.ts +2 -0
  427. package/dist/vitest.config.js +16 -0
  428. package/dist/vitest.config.js.map +1 -0
  429. package/package.json +61 -0
@@ -0,0 +1,1724 @@
1
+ import crypto from 'node:crypto';
2
+ import { buildTuiViewModel, hasInstalledPlacement, } from './model.js';
3
+ import { TuiRouter, } from './router.js';
4
+ export class TuiOperationBlockedError extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = 'TuiOperationBlockedError';
9
+ this.code = code;
10
+ }
11
+ }
12
+ function stableValue(value) {
13
+ if (Array.isArray(value))
14
+ return value.map(stableValue);
15
+ if (typeof value !== 'object' || value === null)
16
+ return value;
17
+ return Object.fromEntries(Object.entries(value)
18
+ .sort(([left], [right]) => left.localeCompare(right, 'en'))
19
+ .map(([key, entry]) => [key, stableValue(entry)]));
20
+ }
21
+ function deepFreeze(value) {
22
+ if (typeof value !== 'object' || value === null || Object.isFrozen(value))
23
+ return value;
24
+ for (const nested of Object.values(value))
25
+ deepFreeze(nested);
26
+ return Object.freeze(value);
27
+ }
28
+ function cloneFrozen(value) {
29
+ return deepFreeze(structuredClone(value));
30
+ }
31
+ export function operationEvidenceFingerprint(evidence) {
32
+ return crypto.createHash('sha256').update(JSON.stringify(stableValue(evidence))).digest('hex');
33
+ }
34
+ function sourceIdFromPluginId(pluginId) {
35
+ const separator = pluginId.indexOf('/');
36
+ if (separator <= 0)
37
+ throw new TuiOperationBlockedError('invalidPluginId', '操作必须使用 canonical pluginId。');
38
+ return pluginId.slice(0, separator);
39
+ }
40
+ function pluginNameFromId(pluginId) {
41
+ const separator = pluginId.indexOf('/');
42
+ return separator < 0 ? pluginId : pluginId.slice(separator + 1);
43
+ }
44
+ export function userIssueKey(issue) {
45
+ return [issue.category, issue.pluginId ?? '', issue.dependencyPluginId ?? '',
46
+ issue.dependencyPlatform ?? '', issue.dependencyScope ?? '', issue.dependencyProject ?? '',
47
+ issue.sourceId ?? '', issue.sourceIdentity ?? '', issue.platform ?? '', issue.scope ?? '']
48
+ .join('\0');
49
+ }
50
+ function operationLabels(intent) {
51
+ if (intent.kind === 'portableInstall') {
52
+ return {
53
+ title: '确认安装通用 Skills',
54
+ description: '只安装已选择的 Skills;不会修改原生插件安装。',
55
+ primaryLabel: `开始安装 ${intent.selectedSkills.length} 个 Skills`,
56
+ destructive: false,
57
+ };
58
+ }
59
+ if (intent.kind === 'pluginWrite') {
60
+ if (intent.originIssue) {
61
+ return {
62
+ title: '处理问题',
63
+ description: '只执行所选问题处理动作;插件设置保持不变。',
64
+ primaryLabel: '开始处理',
65
+ destructive: intent.action !== 'reinstall',
66
+ };
67
+ }
68
+ if (intent.action === 'cleanup' && intent.force === true) {
69
+ return {
70
+ title: '强制清理插件',
71
+ description: '跳过依赖保护并清理可证明属于 AW 的现场内容。依赖者会变为不完整。配置保持不变。',
72
+ primaryLabel: '确认强制清理',
73
+ destructive: true,
74
+ };
75
+ }
76
+ const labels = {
77
+ install: ['添加插件', '添加所选平台和使用位置。', false],
78
+ upgrade: ['升级插件', '使用当前来源内容重建所选安装。', false],
79
+ reinstall: ['重新安装插件', '清理 AW 管理的内容后完整重建。', true],
80
+ uninstall: ['移除插件', '移除 AW 管理的安装内容。配置保持不变。', true],
81
+ cleanup: ['清理插件', '只移除可证明属于 AW 的现场内容。配置保持不变。', true],
82
+ };
83
+ const [title, description, destructive] = labels[intent.action];
84
+ return { title, description, primaryLabel: title, destructive };
85
+ }
86
+ if (intent.kind === 'configSave') {
87
+ return {
88
+ title: '保存插件设置',
89
+ description: '保存所选设置层,并重新应用可见安装。',
90
+ primaryLabel: '保存并应用',
91
+ destructive: false,
92
+ };
93
+ }
94
+ if (intent.kind === 'configReset') {
95
+ return {
96
+ title: '重置插件设置',
97
+ description: '删除所选设置层的值,恢复继承。',
98
+ primaryLabel: '重置设置',
99
+ destructive: true,
100
+ };
101
+ }
102
+ if (intent.kind === 'sourceMutation') {
103
+ const labels = {
104
+ add: ['添加来源', '先保存来源配置,再尽力刷新来源。刷新失败时配置仍保留。', '保存并刷新', false],
105
+ update: ['修改来源', '先保存来源配置,再尽力刷新来源。刷新失败时配置仍保留。', '保存并刷新', false],
106
+ setPrimary: ['设为主来源', '只改变默认 marketplace,不迁移安装。', '设为主来源', false],
107
+ remove: ['移除来源', '移除来源配置;不会删除其它来源。', '移除来源', true],
108
+ };
109
+ const [title, description, primaryLabel, destructive] = labels[intent.action];
110
+ return { title, description, primaryLabel, destructive };
111
+ }
112
+ throw new TuiOperationBlockedError('operationNotImplemented', '问题修复操作尚未接入。');
113
+ }
114
+ function dependencyRelation(plan, pluginId) {
115
+ if (plan.requestedPluginIds.includes(pluginId))
116
+ return 'requested';
117
+ return plan.dependencyEdges.some((edge) => plan.requestedPluginIds.includes(edge.from) && edge.to === pluginId)
118
+ ? 'direct'
119
+ : 'transitive';
120
+ }
121
+ function reviewChanges(intent, evidence, dependencyOverview) {
122
+ if (intent.kind === 'portableInstall') {
123
+ const review = evidence?.portable;
124
+ if (!review)
125
+ throw new TuiOperationBlockedError('portablePrepareFailed', '缺少通用 Skills Review 证据。');
126
+ return [
127
+ ...review.selected.map((skill) => ({
128
+ kind: 'add',
129
+ group: skill.relation === 'requested' ? 'requested' : 'dependencies',
130
+ pluginId: skill.providerPluginId,
131
+ relation: skill.relation,
132
+ title: skill.name,
133
+ description: `${skill.description} · ${skill.providerDisplayName}${skill.dynamic ? ' · 按设置生成' : ''}`,
134
+ })),
135
+ { kind: 'change', group: 'portableTarget', title: 'Agent', description: review.agentMode === 'automatic'
136
+ ? '自动选择检测到的 Agent;未检测到时可能选择全部 Agent'
137
+ : review.requestedAgents.join('、') },
138
+ { kind: 'change', group: 'portableTarget', title: '范围', description: review.scope === 'project' ? '当前项目' : '所有项目' },
139
+ { kind: review.dependencies.length > 0 ? 'keep' : 'change', group: 'portableInputs', title: '插件依赖', description: review.dependencies.length > 0
140
+ ? `${review.dependencies.length} 个依赖用于生成,不能取消` : '无需插件依赖' },
141
+ ...(review.collisions.length > 0 ? [{ kind: 'warning', group: 'portableWarnings', title: '同名覆盖',
142
+ description: review.collisions.map((collision) => collision.name).join('、') }] : []),
143
+ ];
144
+ }
145
+ if (intent.kind === 'pluginWrite') {
146
+ const plan = evidence?.dependencyPlan;
147
+ if (plan && (intent.action === 'install' || intent.action === 'upgrade' || intent.action === 'reinstall')) {
148
+ const changes = plan.targets.map((target) => {
149
+ const relation = dependencyRelation(plan, target.pluginId);
150
+ const actionKind = target.action === 'keep'
151
+ ? 'keep'
152
+ : target.action === 'install'
153
+ ? 'add'
154
+ : 'change';
155
+ return {
156
+ kind: actionKind,
157
+ group: relation === 'requested' ? 'requested' : 'dependencies',
158
+ pluginId: target.pluginId,
159
+ relation,
160
+ title: pluginNameFromId(target.pluginId),
161
+ description: `${target.platform === 'codex' ? 'Codex' : 'Claude Code'} · ${target.resolvedScope === 'user' ? '所有项目' : '当前项目'}${target.action === 'keep' ? ' · 已可用' : ''}`,
162
+ };
163
+ });
164
+ changes.push({
165
+ kind: 'keep',
166
+ group: 'preserved',
167
+ title: '插件设置和已有依赖保持不变',
168
+ description: '移除根插件时不会自动清理依赖。',
169
+ });
170
+ return changes;
171
+ }
172
+ const platforms = intent.platform === 'both'
173
+ ? ['codex', 'claude']
174
+ : [intent.platform];
175
+ const changes = platforms.map((platform) => ({
176
+ kind: intent.action === 'uninstall' || intent.action === 'cleanup' ? 'remove' : intent.action === 'install' ? 'add' : 'change',
177
+ title: `${platform === 'codex' ? 'Codex' : 'Claude Code'} · ${intent.scope === 'user' ? '所有项目' : '当前项目'}`,
178
+ description: intent.action === 'uninstall' || intent.action === 'cleanup'
179
+ ? '插件设置不会删除。'
180
+ : '执行前会再次检查当前安装状态。',
181
+ }));
182
+ if (intent.action === 'uninstall' || intent.action === 'cleanup') {
183
+ changes.push(...(dependencyOverview?.dependencies ?? []).map((dependency) => ({
184
+ kind: 'keep',
185
+ group: 'preserved',
186
+ pluginId: dependency.pluginId,
187
+ relation: dependency.relation === 'direct' ? 'direct' : 'transitive',
188
+ title: dependency.displayName,
189
+ description: '依赖保持安装。',
190
+ })));
191
+ }
192
+ if (intent.action === 'cleanup' && intent.force === true) {
193
+ changes.push({
194
+ kind: 'warning',
195
+ title: '跳过依赖保护',
196
+ description: '依赖此插件的插件会变为不完整,需重新安装后恢复。',
197
+ });
198
+ }
199
+ if (intent.originIssue) {
200
+ changes.push({ kind: 'keep', title: '插件设置保持不变' }, { kind: 'warning', title: '执行前重新检查完整目标和来源证据' });
201
+ }
202
+ return changes;
203
+ }
204
+ if (intent.kind === 'configReset') {
205
+ const layerLabel = intent.scope === 'user'
206
+ ? '个人默认'
207
+ : intent.projectLayer === 'local' ? '当前项目(仅本机)' : '当前项目';
208
+ return [
209
+ { kind: 'remove', title: `删除${layerLabel}中的已存值`, description: '有效值将恢复为继承值或默认值。' },
210
+ { kind: 'change', title: '重新计算有效设置' },
211
+ { kind: 'change', title: '重新应用当前可见安装位置' },
212
+ { kind: 'keep', title: intent.scope === 'user' ? '当前项目和本机设置层保持不变' : '其他设置层保持不变' },
213
+ ];
214
+ }
215
+ if (intent.kind === 'sourceMutation') {
216
+ const changes = (intent.changes ?? []).map((change) => ({
217
+ kind: intent.action === 'remove' ? 'remove' : intent.action === 'add' ? 'add' : 'change',
218
+ title: change.title,
219
+ ...(change.private
220
+ ? { description: change.before ? '已设置 → 将更新' : '已选择' }
221
+ : change.before || change.after
222
+ ? { description: `${change.before ?? '未设置'} → ${change.after ?? '未设置'}` }
223
+ : {}),
224
+ }));
225
+ if (intent.action === 'add')
226
+ changes.push({ kind: 'keep', title: '保留现有主来源' });
227
+ if (intent.action === 'setPrimary')
228
+ changes.push({ kind: 'keep', title: '同名插件选择保持不变' }, { kind: 'keep', title: '现有安装和设置不会迁移' });
229
+ if (intent.action === 'remove')
230
+ changes.push({ kind: 'keep', title: '保留其他来源' });
231
+ return changes;
232
+ }
233
+ if (intent.kind !== 'configSave') {
234
+ throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
235
+ }
236
+ const privateKeys = new Set([...(intent.sensitiveKeys ?? []), ...(intent.privateKeys ?? [])]);
237
+ if (intent.changes) {
238
+ return [
239
+ ...intent.changes.map((change) => ({
240
+ kind: 'change',
241
+ title: change.title,
242
+ description: privateKeys.has(change.key)
243
+ ? change.after === '未设置' ? '已设置 → 未设置' : '已设置 → 将更新'
244
+ : `${change.before} → ${change.after}`,
245
+ })),
246
+ { kind: 'change', title: '重新应用插件内容', description: '只处理当前可见安装位置。' },
247
+ { kind: 'keep', title: '其他项目不会被自动扫描' },
248
+ ];
249
+ }
250
+ const updateKeys = Object.keys(intent.updates ?? {});
251
+ return [
252
+ ...updateKeys.map((key) => ({
253
+ kind: 'change',
254
+ title: key,
255
+ description: privateKeys.has(key) ? '已设置 → 将更新' : '将更新',
256
+ })),
257
+ ...(intent.removeKeys ?? []).map((key) => ({
258
+ kind: 'remove',
259
+ title: key,
260
+ description: privateKeys.has(key) ? '已设置 → 未设置' : '恢复继承',
261
+ })),
262
+ ];
263
+ }
264
+ function technicalDetails(evidence) {
265
+ const details = [
266
+ `Catalog 配置:${evidence.catalog.configDigest}`,
267
+ `Catalog 组合:${evidence.catalog.compositeDigest}`,
268
+ ];
269
+ if (evidence.source) {
270
+ details.push(`来源:${evidence.source.sourceId}`);
271
+ details.push(`来源身份:${evidence.source.sourceIdentity}`);
272
+ if (evidence.source.resolvedCommit)
273
+ details.push(`Snapshot commit:${evidence.source.resolvedCommit}`);
274
+ if (evidence.source.snapshotDigest)
275
+ details.push(`Snapshot digest:${evidence.source.snapshotDigest}`);
276
+ if (evidence.source.contentDigest)
277
+ details.push(`Content digest:${evidence.source.contentDigest}`);
278
+ }
279
+ if (evidence.targetFingerprint)
280
+ details.push(`目标状态:${evidence.targetFingerprint}`);
281
+ if (evidence.dependencyPlan)
282
+ details.push(`安装计划:${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`);
283
+ if (evidence.config)
284
+ details.push(`设置层:${evidence.config.scope}/${evidence.config.projectLayer} · ${evidence.config.storeDigest}`);
285
+ if (evidence.sourceWrite) {
286
+ details.push(`候选配置:${evidence.sourceWrite.candidateDigest}`);
287
+ details.push(`引用清单:${evidence.sourceWrite.inventoryDigest} · ${evidence.sourceWrite.referenceCount} 项`);
288
+ }
289
+ if (evidence.portable) {
290
+ details.push(`Portable 候选:${evidence.portable.selected.length} 个`);
291
+ details.push(`Materialization:${evidence.portable.materializationDigest}`);
292
+ details.push(`Skills CLI:${evidence.portable.skillsCliVersion}`);
293
+ }
294
+ return details;
295
+ }
296
+ function issueCode(error) {
297
+ if (error instanceof TuiOperationBlockedError)
298
+ return error.code;
299
+ if (error instanceof Error && error.name === 'CatalogSourcesConfigConflictError') {
300
+ return 'operationReviewStale';
301
+ }
302
+ const message = error instanceof Error ? error.message : String(error);
303
+ const prefix = message.split(':', 1)[0];
304
+ return /^[A-Za-z][A-Za-z0-9]*$/u.test(prefix) ? prefix : 'operationBlocked';
305
+ }
306
+ export function extractPortableError(error) {
307
+ const typedCode = typeof error === 'object' && error !== null
308
+ && 'code' in error && typeof error.code === 'string'
309
+ ? error.code
310
+ : undefined;
311
+ const message = error instanceof Error ? error.message : String(error);
312
+ const fallback = message.match(/^([A-Za-z][A-Za-z0-9]*):/u)?.[1];
313
+ const diagnosticCode = typedCode ?? fallback ?? 'portableValidationFailed';
314
+ if (diagnosticCode === 'requiredConfigMissing')
315
+ return { code: 'requiredConfigMissing', diagnosticCode };
316
+ if (diagnosticCode === 'portableSkillsCliMissing')
317
+ return { code: 'portableSkillsCliMissing', diagnosticCode };
318
+ if (diagnosticCode === 'portableSkillsCliIncompatible')
319
+ return { code: 'portableSkillsCliIncompatible', diagnosticCode };
320
+ if (diagnosticCode === 'portableScopeUnavailable')
321
+ return { code: 'portableScopeUnavailable', diagnosticCode };
322
+ return { code: 'portableValidationFailed', diagnosticCode };
323
+ }
324
+ export class TuiController {
325
+ dependencies;
326
+ #router = new TuiRouter();
327
+ #listeners = new Set();
328
+ #health = new Map();
329
+ #sourceTokens = new Map();
330
+ #successfulSourceRefreshes = new Map();
331
+ #sourceAborts = new Map();
332
+ #tickets = new Map();
333
+ #inflightOperations = new Map();
334
+ #abort = new AbortController();
335
+ #progressUnsubscribe;
336
+ #progressFeed;
337
+ #phase = 'idle';
338
+ #status;
339
+ #viewModel;
340
+ #progress = { messages: [] };
341
+ #progressTargets = [];
342
+ #activeReview;
343
+ #result;
344
+ #loadError;
345
+ #portableDraft;
346
+ #portableRepreparePluginId;
347
+ #portableRequestToken = 0;
348
+ #portableRequestAbort;
349
+ #portablePending;
350
+ #generation = 0;
351
+ #refreshSequence = 0;
352
+ #appliedRefreshSequence = 0;
353
+ #disposed = false;
354
+ constructor(dependencies, progressFeed) {
355
+ this.dependencies = dependencies;
356
+ this.#progressFeed = progressFeed;
357
+ if (progressFeed) {
358
+ this.#progressUnsubscribe = progressFeed.subscribe((progress) => {
359
+ this.#progress = {
360
+ ...structuredClone(progress),
361
+ messages: progress.messages.slice(-8),
362
+ ...(this.#progress.startedAtMs !== undefined ? { startedAtMs: this.#progress.startedAtMs } : {}),
363
+ };
364
+ const current = progress.current;
365
+ if (current) {
366
+ const [pluginName, placement = ''] = current.target.split(' · ', 2);
367
+ const platform = placement.startsWith('Codex') ? 'codex'
368
+ : placement.startsWith('Claude Code') ? 'claude' : undefined;
369
+ const scope = placement.endsWith('当前项目') ? 'project'
370
+ : placement.endsWith('当前用户') ? 'user' : undefined;
371
+ if (pluginName && platform && scope) {
372
+ this.#progressTargets = this.#progressTargets.map((target) => pluginNameFromId(target.pluginId) === pluginName
373
+ && target.platform === platform && target.scope === scope
374
+ ? { ...target, state: target.state === 'failed' ? 'failed'
375
+ : current.phase.endsWith('失败') ? 'failed'
376
+ : current.phase.endsWith('已完成') ? 'completed' : 'running' }
377
+ : target);
378
+ }
379
+ }
380
+ this.#publish();
381
+ });
382
+ }
383
+ }
384
+ get snapshot() {
385
+ return cloneFrozen({
386
+ phase: this.#phase,
387
+ router: this.#router.snapshot,
388
+ ...(this.#viewModel ? { viewModel: this.#viewModel } : {}),
389
+ sourceHealth: [...this.#health.values()],
390
+ progress: this.#progress,
391
+ progressTargets: this.#progressTargets,
392
+ ...(this.#activeReview ? { activeReview: this.#activeReview } : {}),
393
+ ...(this.#result ? { result: this.#result } : {}),
394
+ ...(this.#loadError ? { loadError: this.#loadError } : {}),
395
+ ...(this.#portableDraft ? { portableDraft: this.#portableDraft } : {}),
396
+ });
397
+ }
398
+ subscribe(listener) {
399
+ this.#listeners.add(listener);
400
+ listener(this.snapshot);
401
+ return () => this.#listeners.delete(listener);
402
+ }
403
+ #publish() {
404
+ if (this.#disposed)
405
+ return;
406
+ const snapshot = this.snapshot;
407
+ for (const listener of this.#listeners)
408
+ listener(snapshot);
409
+ }
410
+ async #logErrorSafe(error, code, context) {
411
+ try {
412
+ await this.dependencies.logError?.(error, code, context);
413
+ }
414
+ catch {
415
+ // Diagnostics must not replace the fixed safe Controller result.
416
+ }
417
+ }
418
+ #project() {
419
+ if (!this.#status)
420
+ return;
421
+ this.#viewModel = buildTuiViewModel({
422
+ status: this.#status.payload,
423
+ pluginSummaries: this.#status.pluginSummaries,
424
+ sourceHealth: this.#health,
425
+ sourceDetails: this.#status.sourceDetails,
426
+ dependencyOverviews: this.#status.dependencyOverviews,
427
+ portablePluginIds: this.#status.portablePluginIds,
428
+ });
429
+ }
430
+ #normalizeRoute(route) {
431
+ if (route.name === 'pluginList' && route.sourceId
432
+ && !this.#viewModel?.sourceTabs.some((source) => source.id === route.sourceId)) {
433
+ return { name: 'pluginList', mode: route.mode };
434
+ }
435
+ const pluginRoute = route.name === 'pluginPreview'
436
+ || route.name === 'portableSkillSelect'
437
+ || route.name === 'portableDependencies'
438
+ || route.name === 'portableAgentMode'
439
+ || route.name === 'portableAgentInput'
440
+ || route.name === 'portableScope'
441
+ || route.name === 'portableBlocker'
442
+ || route.name === 'pluginDependencies'
443
+ || route.name === 'installPlacement'
444
+ || route.name === 'installTools'
445
+ || route.name === 'pluginDetail'
446
+ || route.name === 'platform'
447
+ || route.name === 'pluginAbout'
448
+ || route.name === 'pluginTechnical'
449
+ || route.name === 'configLayer'
450
+ || route.name === 'configFields'
451
+ || route.name === 'configFieldEditor'
452
+ || route.name === 'configPresets'
453
+ || route.name === 'configUnavailable';
454
+ if (pluginRoute && !this.#viewModel?.plugins.some((plugin) => plugin.pluginId === route.pluginId)) {
455
+ return {
456
+ name: 'pluginList',
457
+ mode: route.name === 'pluginPreview' || route.name === 'pluginDependencies'
458
+ || route.name === 'installPlacement' || route.name === 'installTools'
459
+ ? 'browse'
460
+ : 'installed',
461
+ };
462
+ }
463
+ if (route.name === 'issueDetail'
464
+ && !this.#viewModel?.issues.some((issue) => issue.issueId === route.issueId)) {
465
+ return { name: 'issueList' };
466
+ }
467
+ if ((route.name === 'sourceDetail'
468
+ || route.name === 'sourceTechnical'
469
+ || route.name === 'sourceEdit'
470
+ || route.name === 'sourceRemoveBlocked')
471
+ && !this.#viewModel?.sourceTabs.some((source) => source.id === route.sourceId)) {
472
+ return { name: 'sourceList' };
473
+ }
474
+ return route;
475
+ }
476
+ #normalizeCurrentRoute() {
477
+ const route = this.#router.snapshot.current.route;
478
+ const normalized = this.#normalizeRoute(route);
479
+ if (normalized !== route)
480
+ this.#router.replace(normalized);
481
+ }
482
+ #applyStatus(status) {
483
+ this.#status = structuredClone(status);
484
+ this.#health.clear();
485
+ for (const source of status.payload.catalog.sources) {
486
+ this.#health.set(source.id, {
487
+ sourceId: source.id,
488
+ state: source.error ? 'unavailable'
489
+ : source.type === 'path' ? 'path'
490
+ : source.freshness === 'cache' ? 'cache' : 'ready',
491
+ ...(source.error ? { message: '来源当前没有可用数据。' } : {}),
492
+ });
493
+ const successful = this.#successfulSourceRefreshes.get(source.id);
494
+ if (successful?.generation === this.#generation
495
+ && successful.sourceIdentity === source.identity
496
+ && successful.requestedRef === source.requestedRef
497
+ && !source.error) {
498
+ this.#health.set(source.id, {
499
+ sourceId: source.id,
500
+ state: source.type === 'path' || source.freshness === 'path' ? 'path' : 'ready',
501
+ });
502
+ }
503
+ }
504
+ const sourceIds = new Set(status.payload.catalog.sources.map((source) => source.id));
505
+ for (const sourceId of this.#successfulSourceRefreshes.keys()) {
506
+ if (!sourceIds.has(sourceId))
507
+ this.#successfulSourceRefreshes.delete(sourceId);
508
+ }
509
+ this.#project();
510
+ this.#normalizeCurrentRoute();
511
+ }
512
+ #applyRefreshStatus(status, sourceId, health, refreshSequence) {
513
+ if (refreshSequence < this.#appliedRefreshSequence)
514
+ return false;
515
+ this.#appliedRefreshSequence = refreshSequence;
516
+ this.#applyStatus(status);
517
+ if (!status.payload.catalog.sources.some((source) => source.id === sourceId))
518
+ return true;
519
+ this.#health.set(sourceId, health);
520
+ this.#project();
521
+ return true;
522
+ }
523
+ #recordSuccessfulSourceRefresh(sourceId, checked, generation) {
524
+ const authoritative = this.#status?.payload.catalog.sources.find((source) => source.id === sourceId);
525
+ if (!authoritative || authoritative.type !== 'git' || authoritative.error
526
+ || authoritative.identity !== checked.sourceIdentity
527
+ || authoritative.requestedRef !== checked.requestedRef)
528
+ return;
529
+ this.#successfulSourceRefreshes.set(sourceId, {
530
+ generation,
531
+ sourceIdentity: checked.sourceIdentity,
532
+ requestedRef: checked.requestedRef,
533
+ });
534
+ this.#health.set(sourceId, {
535
+ sourceId,
536
+ state: 'ready',
537
+ });
538
+ }
539
+ #cancelSourceRefreshes() {
540
+ for (const abort of this.#sourceAborts.values())
541
+ abort.abort();
542
+ this.#sourceAborts.clear();
543
+ }
544
+ #startGitSourceRefreshes() {
545
+ for (const source of this.#status?.payload.catalog.sources ?? []) {
546
+ if (source.type === 'git')
547
+ void this.refreshSource(source.id).catch(() => undefined);
548
+ }
549
+ }
550
+ async load() {
551
+ this.#cancelSourceRefreshes();
552
+ const generation = ++this.#generation;
553
+ this.#phase = 'loading';
554
+ this.#publish();
555
+ let status;
556
+ try {
557
+ status = await this.dependencies.loadStatus(this.#abort.signal);
558
+ }
559
+ catch (error) {
560
+ if (this.#disposed || generation !== this.#generation)
561
+ return;
562
+ const code = issueCode(error);
563
+ await this.#logErrorSafe(error, code);
564
+ this.#phase = 'error';
565
+ this.#loadError = { code, message: '无法安全读取插件状态。请检查诊断后重试或退出。' };
566
+ this.#publish();
567
+ return;
568
+ }
569
+ if (this.#disposed || generation !== this.#generation)
570
+ return;
571
+ this.#applyStatus(status);
572
+ this.#phase = 'ready';
573
+ this.#loadError = undefined;
574
+ this.#publish();
575
+ this.#startGitSourceRefreshes();
576
+ }
577
+ async refreshSource(sourceId) {
578
+ if (this.#disposed || !this.#status)
579
+ return;
580
+ this.#sourceAborts.get(sourceId)?.abort();
581
+ const sourceAbort = new AbortController();
582
+ this.#sourceAborts.set(sourceId, sourceAbort);
583
+ const finishRefresh = () => {
584
+ if (this.#sourceAborts.get(sourceId) === sourceAbort)
585
+ this.#sourceAborts.delete(sourceId);
586
+ };
587
+ const signal = AbortSignal.any([this.#abort.signal, sourceAbort.signal]);
588
+ const token = (this.#sourceTokens.get(sourceId) ?? 0) + 1;
589
+ this.#sourceTokens.set(sourceId, token);
590
+ const generation = this.#generation;
591
+ const current = this.#status.payload.catalog.sources.find((source) => source.id === sourceId);
592
+ if (!current) {
593
+ finishRefresh();
594
+ return;
595
+ }
596
+ const hadCache = current.type === 'git' && current.error === undefined
597
+ && (current.freshness === 'cache' || current.freshness === 'remote');
598
+ this.#health.set(sourceId, { sourceId, state: 'checking' });
599
+ this.#project();
600
+ this.#publish();
601
+ let checkError;
602
+ let checked;
603
+ try {
604
+ checked = await this.dependencies.checkSource(sourceId, signal);
605
+ }
606
+ catch (error) {
607
+ checkError = error;
608
+ }
609
+ if (this.#disposed || signal.aborted || generation !== this.#generation
610
+ || token !== this.#sourceTokens.get(sourceId)) {
611
+ finishRefresh();
612
+ return;
613
+ }
614
+ if (checkError)
615
+ this.#successfulSourceRefreshes.delete(sourceId);
616
+ const refreshSequence = ++this.#refreshSequence;
617
+ let status;
618
+ let authoritativeReloadSucceeded = true;
619
+ try {
620
+ status = await this.dependencies.loadStatus(signal);
621
+ }
622
+ catch (error) {
623
+ authoritativeReloadSucceeded = false;
624
+ if (!checkError)
625
+ checkError = error;
626
+ this.#successfulSourceRefreshes.delete(sourceId);
627
+ status = structuredClone(this.#status);
628
+ }
629
+ if (this.#disposed || signal.aborted || generation !== this.#generation
630
+ || token !== this.#sourceTokens.get(sourceId)) {
631
+ finishRefresh();
632
+ return;
633
+ }
634
+ if (checkError) {
635
+ await this.#logErrorSafe(checkError, 'catalogSourceRefreshFailed', {
636
+ privateConfigOperation: true,
637
+ });
638
+ }
639
+ const refreshed = status.payload.catalog.sources.find((source) => source.id === sourceId);
640
+ const refreshedMatchesCheck = checked !== undefined && refreshed !== undefined
641
+ && refreshed.type === 'git'
642
+ && !refreshed.error
643
+ && refreshed.identity === checked.sourceIdentity
644
+ && refreshed.requestedRef === checked.requestedRef;
645
+ const state = checkError
646
+ ? authoritativeReloadSucceeded
647
+ ? !refreshed || refreshed.error
648
+ ? 'unavailable'
649
+ : refreshed.type === 'path' || refreshed.freshness === 'path'
650
+ ? 'path'
651
+ : refreshed.freshness === 'cache' ? 'degraded' : 'unavailable'
652
+ : hadCache ? 'degraded' : 'unavailable'
653
+ : !refreshed
654
+ ? 'unavailable'
655
+ : refreshed.error
656
+ ? 'unavailable'
657
+ : !refreshedMatchesCheck
658
+ ? refreshed.type === 'path' || refreshed.freshness === 'path'
659
+ ? 'path'
660
+ : refreshed.freshness === 'cache' ? 'cache' : 'unavailable'
661
+ : refreshed.type === 'path' || refreshed.freshness === 'path'
662
+ ? 'path'
663
+ : 'ready';
664
+ this.#applyRefreshStatus(status, sourceId, {
665
+ sourceId,
666
+ state,
667
+ ...(state === 'degraded' || state === 'unavailable' ? {
668
+ issueId: `source:${sourceId}:${state === 'degraded' ? 'cache' : 'unavailable'}`,
669
+ message: checkError
670
+ ? state === 'degraded'
671
+ ? '远端检查失败。当前仍使用已验证的缓存数据。'
672
+ : '远端检查失败,并且没有可用缓存数据。'
673
+ : state === 'degraded'
674
+ ? '远端检查后仍只能使用缓存数据。'
675
+ : '来源检查后没有可用数据。',
676
+ } : {}),
677
+ }, refreshSequence);
678
+ if (!checkError && refreshedMatchesCheck && checked) {
679
+ this.#recordSuccessfulSourceRefresh(sourceId, checked, generation);
680
+ }
681
+ finishRefresh();
682
+ if (generation !== this.#generation)
683
+ return;
684
+ this.#project();
685
+ this.#publish();
686
+ }
687
+ push(route, context) {
688
+ this.#cancelPortableRequest();
689
+ this.#discardPortableDraftForRoute(route);
690
+ this.#router.push(route, context);
691
+ this.#publish();
692
+ }
693
+ hasPortableSkills(pluginId) {
694
+ return this.#status?.portablePluginIds?.includes(pluginId) ?? false;
695
+ }
696
+ preparePortableInstall(pluginId, navigation = 'push') {
697
+ if (!this.hasPortableSkills(pluginId)) {
698
+ throw new TuiOperationBlockedError('portableUnsupported', '插件不提供通用 Skills。');
699
+ }
700
+ this.#router.beginPortableFlow(pluginId);
701
+ const expectedRoute = this.#router.snapshot.current.route;
702
+ return this.#runPortableRequest(`prepare:${pluginId}`, async (signal, token) => {
703
+ const draft = await this.#buildPortableDraft(pluginId, false, signal);
704
+ this.#assertPortableRequestCurrent(token, expectedRoute);
705
+ this.#portableDraft = draft;
706
+ this.#router[navigation]({ name: 'portableSkillSelect', pluginId });
707
+ this.#publish();
708
+ return cloneFrozen(draft);
709
+ });
710
+ }
711
+ refreshPortableInstallDraft(pluginId) {
712
+ const expectedRoute = this.#router.snapshot.current.route;
713
+ return this.#runPortableRequest(`refresh:${pluginId}`, async (signal, token) => {
714
+ const draft = await this.#buildPortableDraft(pluginId, true, signal);
715
+ this.#assertPortableRequestCurrent(token, expectedRoute);
716
+ this.#portableDraft = draft;
717
+ this.#publish();
718
+ return cloneFrozen(draft);
719
+ });
720
+ }
721
+ async #buildPortableDraft(pluginId, preserveSelection, signal) {
722
+ const previous = preserveSelection && this.#portableDraft?.pluginId === pluginId
723
+ ? this.#portableDraft
724
+ : undefined;
725
+ const prepared = await Promise.allSettled(['project', 'user'].map(async (scope) => ({
726
+ scope,
727
+ evidence: await this.dependencies.captureEvidence({
728
+ kind: 'portableInstall',
729
+ pluginId,
730
+ scope,
731
+ selectedSkills: [],
732
+ }, signal),
733
+ })));
734
+ const available = prepared.flatMap((entry) => entry.status === 'fulfilled' && entry.value.evidence.portable ? [entry.value] : []);
735
+ if (available.length === 0) {
736
+ const failure = prepared.find((entry) => entry.status === 'rejected');
737
+ if (failure)
738
+ throw failure.reason;
739
+ throw new TuiOperationBlockedError('portablePrepareFailed', '无法准备通用 Skills。');
740
+ }
741
+ const merged = new Map();
742
+ for (const { scope, evidence } of available) {
743
+ for (const candidate of evidence.portable.selected) {
744
+ const current = merged.get(candidate.name);
745
+ const scopeDetail = {
746
+ description: candidate.description,
747
+ providerPluginId: candidate.providerPluginId,
748
+ providerDisplayName: candidate.providerDisplayName,
749
+ relation: candidate.relation,
750
+ dynamic: candidate.dynamic,
751
+ filesDigest: candidate.filesDigest,
752
+ };
753
+ if (current) {
754
+ current.availableScopes.push(scope);
755
+ current.scopeDetails[scope] = scopeDetail;
756
+ continue;
757
+ }
758
+ merged.set(candidate.name, {
759
+ ...candidate,
760
+ selected: true,
761
+ availableScopes: [scope],
762
+ scopeDetails: { [scope]: scopeDetail },
763
+ });
764
+ }
765
+ }
766
+ const selectedNames = new Set(previous?.candidates
767
+ .filter((candidate) => candidate.selected)
768
+ .map((candidate) => candidate.name) ?? []);
769
+ const candidates = [...merged.values()].map((candidate) => ({
770
+ ...candidate,
771
+ selected: previous ? selectedNames.has(candidate.name) : true,
772
+ }));
773
+ const dependencies = this.getPluginDependencyOverview(pluginId);
774
+ const selectedCount = candidates.filter((candidate) => candidate.selected).length;
775
+ return {
776
+ pluginId,
777
+ candidates,
778
+ selectedCount,
779
+ totalCount: candidates.length,
780
+ canContinue: selectedCount > 0,
781
+ ...(dependencies ? { dependencySummary: {
782
+ directCount: dependencies.summary.directCount,
783
+ transitiveCount: dependencies.summary.transitiveCount,
784
+ contributingSkillCount: candidates.filter((candidate) => candidate.relation !== 'requested').length,
785
+ } } : {}),
786
+ agentMode: previous?.agentMode ?? 'automatic',
787
+ requestedAgents: previous?.requestedAgents ?? [],
788
+ scope: previous?.scope ?? 'project',
789
+ };
790
+ }
791
+ togglePortableSkill(name) {
792
+ if (!this.#portableDraft)
793
+ return;
794
+ this.#portableDraft.candidates = this.#portableDraft.candidates.map((candidate) => candidate.name === name ? { ...candidate, selected: !candidate.selected } : candidate);
795
+ this.#portableDraft.selectedCount = this.#portableDraft.candidates.filter((candidate) => candidate.selected).length;
796
+ this.#portableDraft.canContinue = this.#portableDraft.selectedCount > 0;
797
+ this.#publish();
798
+ }
799
+ selectAllPortableSkills() {
800
+ if (!this.#portableDraft)
801
+ return;
802
+ this.#portableDraft.candidates = this.#portableDraft.candidates.map((candidate) => ({ ...candidate, selected: true }));
803
+ this.#portableDraft.selectedCount = this.#portableDraft.totalCount;
804
+ this.#portableDraft.canContinue = this.#portableDraft.totalCount > 0;
805
+ this.#publish();
806
+ }
807
+ setPortableAgentMode(mode, requestedAgents = []) {
808
+ if (!this.#portableDraft)
809
+ return;
810
+ this.#portableDraft.agentMode = mode;
811
+ this.#portableDraft.requestedAgents = [...requestedAgents];
812
+ this.#publish();
813
+ }
814
+ setPortableScope(scope) {
815
+ if (!this.#portableDraft)
816
+ return;
817
+ this.#portableDraft.scope = scope;
818
+ this.#publish();
819
+ }
820
+ async buildPortableInstallReview() {
821
+ const draft = this.#portableDraft;
822
+ if (!draft?.canContinue) {
823
+ throw new TuiOperationBlockedError('portableNoSkillsSelected', '至少选择一个 Skill。');
824
+ }
825
+ const unavailable = draft.candidates.filter((candidate) => candidate.selected && !candidate.availableScopes.includes(draft.scope));
826
+ if (unavailable.length > 0) {
827
+ throw new TuiOperationBlockedError('portableScopeUnavailable', `所选范围不提供这些 Skill:${unavailable.map((candidate) => candidate.name).join(', ')}。请返回 Skill 选择或更改范围。`);
828
+ }
829
+ const intent = {
830
+ kind: 'portableInstall',
831
+ pluginId: draft.pluginId,
832
+ scope: draft.scope,
833
+ selectedSkills: draft.candidates.filter((candidate) => candidate.selected).map((candidate) => candidate.name),
834
+ ...(draft.agentMode === 'explicit' ? { requestedAgents: draft.requestedAgents } : {}),
835
+ };
836
+ const expectedRoute = this.#router.snapshot.current.route;
837
+ return this.#runPortableRequest(`review:${JSON.stringify(intent)}`, async (signal, token) => {
838
+ const evidence = await this.dependencies.captureEvidence(structuredClone(intent), signal);
839
+ this.#assertPortableRequestCurrent(token, expectedRoute);
840
+ return this.#storeOperationReview(intent, evidence, 'push', this.#router.portableOrigin(draft.pluginId));
841
+ });
842
+ }
843
+ discardPortableDraft() {
844
+ this.#cancelPortableRequest();
845
+ this.#portableDraft = undefined;
846
+ this.#publish();
847
+ }
848
+ replace(route, context) {
849
+ this.#cancelPortableRequest();
850
+ const current = this.#router.snapshot.current.route;
851
+ if (current.name === 'portableBlocker' && current.code === 'requiredConfigMissing'
852
+ && route.name === 'configLayer' && route.pluginId === current.pluginId) {
853
+ this.#portableRepreparePluginId = current.pluginId;
854
+ }
855
+ this.#discardPortableDraftForRoute(route);
856
+ this.#router.replace(route, context);
857
+ this.#publish();
858
+ }
859
+ updatePageContext(context) {
860
+ this.#router.updateContext(context);
861
+ this.#publish();
862
+ }
863
+ back() {
864
+ this.#cancelPortableRequest();
865
+ const route = this.#router.snapshot.current.route;
866
+ if (route.name === 'review' && this.#activeReview?.operationId === route.operationId) {
867
+ this.#tickets.delete(this.#activeReview.reviewToken);
868
+ this.#activeReview = undefined;
869
+ }
870
+ this.#router.back();
871
+ this.#normalizeCurrentRoute();
872
+ this.#discardPortableDraftForRoute(this.#router.snapshot.current.route);
873
+ this.#publish();
874
+ }
875
+ discardSourceDraft(owner, returnRoute) {
876
+ if (this.#activeReview?.operationKind === 'sourceMutation') {
877
+ this.#tickets.delete(this.#activeReview.reviewToken);
878
+ this.#activeReview = undefined;
879
+ }
880
+ this.#result = undefined;
881
+ this.#router.discardSourceFlow(owner, returnRoute);
882
+ this.#normalizeCurrentRoute();
883
+ this.#publish();
884
+ }
885
+ leaveResult(target) {
886
+ if (this.#router.snapshot.current.route.name !== 'result')
887
+ return;
888
+ if (this.#result?.operationKind === 'portableInstall'
889
+ && 'pluginId' in this.#result && this.#result.pluginId) {
890
+ const preserveDraft = target?.name === 'portableSkillSelect'
891
+ || target?.name === 'portableAgentInput';
892
+ this.resetPortableFlowToPreview(this.#result.pluginId, target, preserveDraft);
893
+ return;
894
+ }
895
+ const route = target ?? (this.#result && 'returnRoute' in this.#result
896
+ ? this.#result.returnRoute
897
+ : undefined);
898
+ this.#router.back();
899
+ if (route)
900
+ this.#router.replace(this.#normalizeRoute(route));
901
+ else
902
+ this.#normalizeCurrentRoute();
903
+ this.#discardPortableDraftForRoute(this.#router.snapshot.current.route);
904
+ this.#result = undefined;
905
+ this.#publish();
906
+ }
907
+ #discardPortableDraftForRoute(route) {
908
+ const portableRoute = route.name === 'portableSkillSelect'
909
+ || route.name === 'portableDependencies'
910
+ || route.name === 'portableAgentMode'
911
+ || route.name === 'portableAgentInput'
912
+ || route.name === 'portableScope'
913
+ || route.name === 'portableBlocker';
914
+ const portableOperationRoute = (route.name === 'review' || route.name === 'progress' || route.name === 'result')
915
+ && (this.#activeReview?.operationKind === 'portableInstall'
916
+ || this.#result?.operationKind === 'portableInstall');
917
+ if (!portableRoute && !portableOperationRoute) {
918
+ this.#cancelPortableRequest();
919
+ this.#portableDraft = undefined;
920
+ }
921
+ }
922
+ resetPortableFlowToPreview(pluginId, target, preserveDraft = false) {
923
+ this.#cancelPortableRequest();
924
+ if (!preserveDraft)
925
+ this.#portableDraft = undefined;
926
+ if (this.#activeReview?.operationKind === 'portableInstall') {
927
+ this.#tickets.delete(this.#activeReview.reviewToken);
928
+ this.#activeReview = undefined;
929
+ }
930
+ if (this.#result?.operationKind === 'portableInstall')
931
+ this.#result = undefined;
932
+ this.#router.resetPortableFlow(pluginId);
933
+ if (target && JSON.stringify(this.#router.snapshot.current.route) !== JSON.stringify(target)) {
934
+ this.#router.beginPortableFlow(pluginId);
935
+ this.#router.push(this.#normalizeRoute(target));
936
+ }
937
+ this.#publish();
938
+ }
939
+ #cancelPortableRequest() {
940
+ this.#portableRequestToken += 1;
941
+ this.#portableRequestAbort?.abort();
942
+ this.#portableRequestAbort = undefined;
943
+ this.#portablePending = undefined;
944
+ }
945
+ #runPortableRequest(key, action) {
946
+ if (this.#portablePending?.key === key) {
947
+ return this.#portablePending.promise;
948
+ }
949
+ this.#cancelPortableRequest();
950
+ const abort = new AbortController();
951
+ this.#portableRequestAbort = abort;
952
+ const token = this.#portableRequestToken;
953
+ const signal = AbortSignal.any([this.#abort.signal, abort.signal]);
954
+ const promise = action(signal, token).finally(() => {
955
+ if (this.#portableRequestToken === token) {
956
+ this.#portableRequestAbort = undefined;
957
+ this.#portablePending = undefined;
958
+ }
959
+ });
960
+ this.#portablePending = { key, promise };
961
+ return promise;
962
+ }
963
+ #assertPortableRequestCurrent(token, expectedRoute) {
964
+ if (this.#disposed || this.#abort.signal.aborted || token !== this.#portableRequestToken
965
+ || JSON.stringify(this.#router.snapshot.current.route) !== JSON.stringify(expectedRoute)) {
966
+ throw new DOMException('Portable request was cancelled.', 'AbortError');
967
+ }
968
+ }
969
+ listPlugins(options) {
970
+ const query = options.query?.trim().toLocaleLowerCase('en');
971
+ return cloneFrozen((this.#viewModel?.plugins ?? []).filter((plugin) => (!options.sourceId || plugin.sourceId === options.sourceId)
972
+ && (options.mode === 'browse' || hasInstalledPlacement(plugin))
973
+ && (!query || `${plugin.displayName}\n${plugin.description}\n${plugin.sourceId}`.toLocaleLowerCase('en').includes(query))));
974
+ }
975
+ getPluginOverview(pluginId) {
976
+ const plugin = this.#viewModel?.plugins.find((entry) => entry.pluginId === pluginId);
977
+ return plugin ? cloneFrozen(plugin) : undefined;
978
+ }
979
+ getPluginDependencyOverview(pluginId) {
980
+ const overview = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId);
981
+ return overview ? cloneFrozen(overview) : undefined;
982
+ }
983
+ getBlockingDependents(pluginId, platform, scope) {
984
+ const dependents = this.#viewModel?.dependencyOverviews.find((entry) => entry.pluginId === pluginId)?.dependents ?? [];
985
+ return cloneFrozen(dependents.flatMap((dependent) => {
986
+ const references = (dependent.placementReferences ?? []).filter((reference) => reference.dependency.pluginId === pluginId
987
+ && reference.dependency.platform === platform
988
+ && reference.dependency.scope === scope
989
+ && (scope === 'user' || reference.dependency.project === 'current')
990
+ && (reference.consumer.project === 'other'
991
+ || this.#status?.payload.targets.find((target) => target.pluginId === reference.consumer.pluginId
992
+ && target.platform === reference.consumer.platform
993
+ && target.scope === reference.consumer.scope)?.state !== 'absent'));
994
+ return references.length > 0 ? [{ ...dependent, placementReferences: references }] : [];
995
+ }));
996
+ }
997
+ getPlatformPlacement(pluginId, platform) {
998
+ return this.getPluginOverview(pluginId)?.installedPlacements.find((placement) => placement.platform === platform);
999
+ }
1000
+ getTargetState(pluginId, platform, scope) {
1001
+ return this.#status?.payload.targets.find((target) => target.pluginId === pluginId
1002
+ && target.platform === platform
1003
+ && target.scope === scope)?.state;
1004
+ }
1005
+ listSources() {
1006
+ return cloneFrozen(this.#viewModel?.sourceTabs ?? []);
1007
+ }
1008
+ getSourceOverview(sourceId) {
1009
+ return cloneFrozen({
1010
+ source: this.#viewModel?.sourceTabs.find((source) => source.id === sourceId),
1011
+ detail: this.#viewModel?.sourceDetails.find((source) => source.id === sourceId),
1012
+ health: this.#health.get(sourceId),
1013
+ });
1014
+ }
1015
+ async inspectSourceRemoval(sourceId) {
1016
+ if (!this.dependencies.inspectSourceRemoval) {
1017
+ throw new TuiOperationBlockedError('stateInventoryFailed', '来源引用清单不可用。');
1018
+ }
1019
+ return cloneFrozen(await this.dependencies.inspectSourceRemoval(sourceId, this.#abort.signal));
1020
+ }
1021
+ async getConfigGroups(pluginId, layer) {
1022
+ if (!this.dependencies.getConfigGroups) {
1023
+ throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
1024
+ }
1025
+ return cloneFrozen(await this.dependencies.getConfigGroups(pluginId, structuredClone(layer), this.#abort.signal));
1026
+ }
1027
+ async getConfigPage(pluginId, layer, signal) {
1028
+ if (!this.dependencies.getConfigPage) {
1029
+ throw new TuiOperationBlockedError('configUnavailable', '设置读取入口不可用。');
1030
+ }
1031
+ const combined = signal
1032
+ ? AbortSignal.any([this.#abort.signal, signal])
1033
+ : this.#abort.signal;
1034
+ return cloneFrozen(await this.dependencies.getConfigPage(pluginId, structuredClone(layer), combined));
1035
+ }
1036
+ async getConfigResetAvailability(pluginId, layer) {
1037
+ if (!this.dependencies.getConfigResetAvailability) {
1038
+ throw new TuiOperationBlockedError('configResetVerificationUnavailable', '设置层可信读取入口不可用。');
1039
+ }
1040
+ return cloneFrozen(await this.dependencies.getConfigResetAvailability(pluginId, structuredClone(layer), this.#abort.signal));
1041
+ }
1042
+ listUserIssues() {
1043
+ return cloneFrozen(this.#viewModel?.issues ?? []);
1044
+ }
1045
+ async buildOperationReview(intent, returnRoute) {
1046
+ try {
1047
+ this.#assertIssueOrigin(intent);
1048
+ if (intent.kind === 'pluginWrite'
1049
+ && (intent.action === 'uninstall' || intent.action === 'cleanup')
1050
+ && intent.force !== true
1051
+ && (intent.platform === 'both' ? ['codex', 'claude'] : [intent.platform])
1052
+ .some((platform) => this.getBlockingDependents(intent.pluginId, platform, intent.scope).length > 0)) {
1053
+ throw new TuiOperationBlockedError('dependencyStillRequired', '仍有已安装插件依赖这个插件。请先查看完整依赖关系。');
1054
+ }
1055
+ await this.dependencies.validateIntent?.(structuredClone(intent), this.#abort.signal);
1056
+ const evidence = await this.dependencies.captureEvidence(structuredClone(intent), this.#abort.signal);
1057
+ return this.#storeOperationReview(intent, evidence, 'push', returnRoute);
1058
+ }
1059
+ catch (error) {
1060
+ const code = issueCode(error);
1061
+ await this.#logOperationError(error, code, intent);
1062
+ throw new TuiOperationBlockedError(code, '操作未建立。请检查当前设置后重试。');
1063
+ }
1064
+ }
1065
+ async #logOperationError(error, code, intent) {
1066
+ await this.#logErrorSafe(error, code, {
1067
+ privateConfigOperation: intent.kind === 'configSave' || intent.kind === 'configReset'
1068
+ || intent.kind === 'sourceMutation',
1069
+ });
1070
+ }
1071
+ #pruneReviewTickets(now) {
1072
+ for (const [token, ticket] of this.#tickets) {
1073
+ if (ticket.review.expiresAt < now)
1074
+ this.#tickets.delete(token);
1075
+ }
1076
+ const maximum = Math.max(1, this.dependencies.maxReviewTickets ?? 32);
1077
+ while (this.#tickets.size >= maximum) {
1078
+ const oldest = this.#tickets.keys().next().value;
1079
+ if (!oldest)
1080
+ break;
1081
+ this.#tickets.delete(oldest);
1082
+ }
1083
+ }
1084
+ #storeOperationReview(intent, evidence, navigation = 'push', returnRoute = this.#router.snapshot.current.route) {
1085
+ if (this.#router.snapshot.current.route.name === 'result')
1086
+ navigation = 'replace';
1087
+ const labels = operationLabels(intent);
1088
+ const operationId = this.dependencies.createToken?.() ?? crypto.randomUUID();
1089
+ const reviewToken = this.dependencies.createToken?.() ?? crypto.randomUUID();
1090
+ const now = this.dependencies.now?.() ?? Date.now();
1091
+ this.#pruneReviewTickets(now);
1092
+ const expiresAt = now
1093
+ + (this.dependencies.reviewTtlMs ?? 2 * 60_000);
1094
+ const pluginId = 'pluginId' in intent ? intent.pluginId : intent.sourceId;
1095
+ if ('pluginId' in intent)
1096
+ sourceIdFromPluginId(intent.pluginId);
1097
+ const review = {
1098
+ operationId,
1099
+ reviewToken,
1100
+ expiresAt,
1101
+ operationKind: intent.kind,
1102
+ ...(intent.kind === 'sourceMutation' ? { sourceAction: intent.action } : {}),
1103
+ ...(intent.kind === 'pluginWrite' ? { pluginAction: intent.action } : {}),
1104
+ ...(intent.kind === 'pluginWrite' && intent.originIssue
1105
+ ? { originIssue: intent.originIssue }
1106
+ : {}),
1107
+ title: labels.title,
1108
+ description: labels.description,
1109
+ subject: {
1110
+ name: 'pluginId' in intent
1111
+ ? this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.displayName
1112
+ ?? pluginNameFromId(intent.pluginId)
1113
+ : pluginId,
1114
+ ...('pluginId' in intent ? { sourceLabel: sourceIdFromPluginId(intent.pluginId) } : {}),
1115
+ ...('pluginId' in intent
1116
+ && this.#viewModel?.plugins.find((plugin) => plugin.pluginId === intent.pluginId)?.version
1117
+ ? { version: this.#viewModel.plugins.find((plugin) => plugin.pluginId === intent.pluginId).version }
1118
+ : {}),
1119
+ },
1120
+ changes: reviewChanges(intent, evidence, 'pluginId' in intent ? this.getPluginDependencyOverview(intent.pluginId) : undefined),
1121
+ technicalDetails: technicalDetails(evidence),
1122
+ ...(intent.kind === 'pluginWrite' && intent.action === 'cleanup' && intent.force === true
1123
+ ? { equivalentCommand: `aw cleanup ${intent.pluginId} --platform ${intent.platform} --scope ${intent.scope} --force` }
1124
+ : {}),
1125
+ primaryLabel: intent.kind === 'pluginWrite' && evidence.dependencyPlan
1126
+ ? `${labels.primaryLabel} ${evidence.dependencyPlan.resolvedPluginIds.length} 个插件`
1127
+ : labels.primaryLabel,
1128
+ ...(labels.destructive ? { destructive: true } : {}),
1129
+ };
1130
+ this.#tickets.set(reviewToken, {
1131
+ intent: structuredClone(intent),
1132
+ evidence: structuredClone(evidence),
1133
+ review: structuredClone(review),
1134
+ returnRoute: structuredClone(this.#normalizeRoute(returnRoute)),
1135
+ });
1136
+ this.#activeReview = structuredClone(review);
1137
+ this.#result = undefined;
1138
+ this.#router[navigation]({ name: 'review', operationId });
1139
+ this.#publish();
1140
+ return cloneFrozen(review);
1141
+ }
1142
+ executeOperation(reviewToken) {
1143
+ const inflight = this.#inflightOperations.get(reviewToken);
1144
+ if (inflight)
1145
+ return inflight.promise;
1146
+ const ticket = this.#tickets.get(reviewToken);
1147
+ const operationId = ticket?.review.operationId ?? 'unknown';
1148
+ if (!ticket) {
1149
+ return Promise.resolve(cloneFrozen({
1150
+ state: 'blocked',
1151
+ operationId,
1152
+ code: 'reviewTicketInvalid',
1153
+ message: '操作未完成。请重新检查后重试。',
1154
+ }));
1155
+ }
1156
+ this.#tickets.delete(reviewToken);
1157
+ if ((this.dependencies.now?.() ?? Date.now()) > ticket.review.expiresAt) {
1158
+ return Promise.resolve(this.#setBlocked(operationId, 'reviewTicketExpired', { intent: ticket.intent, returnRoute: ticket.returnRoute }));
1159
+ }
1160
+ const promise = this.#executeOperationTicket(ticket, operationId).finally(() => {
1161
+ if (this.#inflightOperations.get(reviewToken)?.promise === promise) {
1162
+ this.#inflightOperations.delete(reviewToken);
1163
+ }
1164
+ });
1165
+ this.#inflightOperations.set(reviewToken, { operationId, promise });
1166
+ return promise;
1167
+ }
1168
+ async #executeOperationTicket(ticket, operationId) {
1169
+ let generation = this.#generation;
1170
+ const startedAtMs = this.dependencies.now?.() ?? Date.now();
1171
+ this.#progressFeed?.clearCurrent?.();
1172
+ const dependencyPlan = ticket.evidence.dependencyPlan;
1173
+ this.#progressTargets = dependencyPlan?.targets.map((target) => ({
1174
+ pluginId: target.pluginId,
1175
+ platform: target.platform,
1176
+ scope: target.resolvedScope,
1177
+ relation: dependencyRelation(dependencyPlan, target.pluginId),
1178
+ requiredBy: target.requiredBy,
1179
+ state: target.action === 'keep' ? 'kept' : 'pending',
1180
+ })) ?? [];
1181
+ this.#progress = {
1182
+ messages: [],
1183
+ startedAtMs,
1184
+ elapsedMs: 0,
1185
+ current: {
1186
+ target: 'pluginId' in ticket.intent ? ticket.intent.pluginId : ticket.intent.sourceId,
1187
+ phase: '验证当前状态',
1188
+ },
1189
+ };
1190
+ this.#router.replace({ name: 'progress', operationId });
1191
+ this.#publish();
1192
+ try {
1193
+ this.#assertIssueOrigin(ticket.intent);
1194
+ await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
1195
+ }
1196
+ catch (error) {
1197
+ if (!this.#operationIsCurrent(generation))
1198
+ return this.#detachedBlocked(operationId, ticket.intent);
1199
+ const code = issueCode(error);
1200
+ await this.#logOperationError(error, code, ticket.intent);
1201
+ if (!this.#operationIsCurrent(generation))
1202
+ return this.#detachedBlocked(operationId, ticket.intent);
1203
+ this.#cancelSourceRefreshes();
1204
+ generation = ++this.#generation;
1205
+ try {
1206
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1207
+ if (!this.#operationIsCurrent(generation))
1208
+ return this.#detachedBlocked(operationId, ticket.intent);
1209
+ this.#applyStatus(refreshed);
1210
+ }
1211
+ catch (reloadError) {
1212
+ if (!this.#operationIsCurrent(generation))
1213
+ return this.#detachedBlocked(operationId, ticket.intent);
1214
+ await this.#logErrorSafe(reloadError, 'tuiStateReloadFailed', { privateConfigOperation: true });
1215
+ }
1216
+ return this.#setBlocked(operationId, code, {
1217
+ intent: ticket.intent,
1218
+ navigation: 'result',
1219
+ returnRoute: ticket.returnRoute,
1220
+ });
1221
+ }
1222
+ if (!this.#operationIsCurrent(generation))
1223
+ return this.#detachedBlocked(operationId, ticket.intent);
1224
+ let current;
1225
+ try {
1226
+ current = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
1227
+ }
1228
+ catch (error) {
1229
+ if (!this.#operationIsCurrent(generation))
1230
+ return this.#detachedBlocked(operationId, ticket.intent);
1231
+ const code = issueCode(error);
1232
+ await this.#logOperationError(error, code, ticket.intent);
1233
+ if (!this.#operationIsCurrent(generation))
1234
+ return this.#detachedBlocked(operationId, ticket.intent);
1235
+ return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1236
+ }
1237
+ if (!this.#operationIsCurrent(generation))
1238
+ return this.#detachedBlocked(operationId, ticket.intent);
1239
+ if (operationEvidenceFingerprint(current) !== operationEvidenceFingerprint(ticket.evidence)) {
1240
+ return this.#rebuildStaleReview(ticket, operationId, current, generation);
1241
+ }
1242
+ if (ticket.intent.kind === 'sourceMutation') {
1243
+ this.#cancelSourceRefreshes();
1244
+ generation = ++this.#generation;
1245
+ }
1246
+ try {
1247
+ const execution = await this.dependencies.executeIntent(structuredClone(ticket.intent), structuredClone(ticket.evidence));
1248
+ const executionTargets = operationResultSummary(ticket.intent, execution).targets ?? [];
1249
+ this.#progressTargets = this.#progressTargets.map((target) => {
1250
+ const completed = executionTargets.find((candidate) => candidate.pluginId === target.pluginId
1251
+ && candidate.platform === target.platform && candidate.scope === target.scope);
1252
+ return completed ? { ...target, state: completed.state } : target;
1253
+ });
1254
+ if (!this.#operationIsCurrent(generation))
1255
+ return this.#detachedBlocked(operationId, ticket.intent);
1256
+ this.#cancelSourceRefreshes();
1257
+ generation = ++this.#generation;
1258
+ if (ticket.intent.kind === 'sourceMutation') {
1259
+ try {
1260
+ this.#progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase: '读取最终状态' }, '来源操作:读取最终状态');
1261
+ }
1262
+ catch {
1263
+ // Progress diagnostics must not replace the completed source write.
1264
+ }
1265
+ }
1266
+ let refreshed;
1267
+ try {
1268
+ refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1269
+ }
1270
+ catch (error) {
1271
+ if (!this.#operationIsCurrent(generation))
1272
+ return this.#detachedBlocked(operationId, ticket.intent);
1273
+ const verificationCode = ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1274
+ ? 'issueVerificationFailed'
1275
+ : 'tuiStateReloadFailed';
1276
+ await this.#logErrorSafe(error, verificationCode, {
1277
+ privateConfigOperation: true,
1278
+ });
1279
+ if (!this.#operationIsCurrent(generation))
1280
+ return this.#detachedBlocked(operationId, ticket.intent);
1281
+ return this.#setBlocked(operationId, verificationCode, {
1282
+ intent: ticket.intent,
1283
+ navigation: 'result',
1284
+ returnRoute: ticket.returnRoute,
1285
+ writeMayHaveCompleted: true,
1286
+ verification: ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1287
+ ? 'verificationFailed' : undefined,
1288
+ });
1289
+ }
1290
+ if (!this.#operationIsCurrent(generation))
1291
+ return this.#detachedBlocked(operationId, ticket.intent);
1292
+ this.#applyStatus(refreshed);
1293
+ const pluginSummary = ticket.intent.kind === 'pluginWrite'
1294
+ ? operationResultSummary(ticket.intent, execution)
1295
+ : undefined;
1296
+ const dependencyFailure = pluginSummary?.code === 'operationFailed'
1297
+ && pluginSummary.targets?.some((target) => target.relation !== undefined
1298
+ && (target.state === 'failed' || target.state === 'skipped'));
1299
+ if (pluginSummary?.code === 'operationFailed' && !dependencyFailure) {
1300
+ return this.#setBlocked(operationId, 'operationFailed', {
1301
+ intent: ticket.intent,
1302
+ navigation: 'result',
1303
+ returnRoute: ticket.returnRoute,
1304
+ writeMayHaveCompleted: true,
1305
+ });
1306
+ }
1307
+ const originIssue = ticket.intent.kind === 'pluginWrite' ? ticket.intent.originIssue : undefined;
1308
+ if (originIssue && !dependencyFailure
1309
+ && this.#viewModel?.issues.some((issue) => userIssueKey(issue) === originIssue.issueKey)) {
1310
+ return this.#setBlocked(operationId, 'issueStillPresent', {
1311
+ intent: ticket.intent,
1312
+ navigation: 'result',
1313
+ returnRoute: ticket.returnRoute,
1314
+ writeMayHaveCompleted: true,
1315
+ verification: 'issueStillPresent',
1316
+ });
1317
+ }
1318
+ if (ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset') {
1319
+ const configWrite = typeof execution === 'object' && execution !== null
1320
+ ? execution.configWrite
1321
+ : undefined;
1322
+ if (configWrite !== 'succeeded') {
1323
+ return this.#setBlocked(operationId, 'configWriteUnconfirmed', {
1324
+ intent: ticket.intent,
1325
+ navigation: 'back',
1326
+ });
1327
+ }
1328
+ }
1329
+ const baseSummary = dependencyFailure && pluginSummary
1330
+ ? { ...pluginSummary, code: 'operationPartial', message: '依赖安装没有全部完成。' }
1331
+ : pluginSummary ?? operationResultSummary(ticket.intent, execution);
1332
+ const summary = {
1333
+ ...baseSummary,
1334
+ elapsedMs: Math.max(0, (this.dependencies.now?.() ?? Date.now()) - startedAtMs),
1335
+ messages: [...this.#progress.messages, ...(baseSummary.messages ?? [])].slice(-8),
1336
+ };
1337
+ this.#progress = { ...this.#progress, elapsedMs: summary.elapsedMs };
1338
+ if (summary.code === 'operationFailed') {
1339
+ if (ticket.intent.kind === 'portableInstall') {
1340
+ const result = {
1341
+ state: 'partial',
1342
+ operationId,
1343
+ summary,
1344
+ operationKind: ticket.intent.kind,
1345
+ pluginId: ticket.intent.pluginId,
1346
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
1347
+ };
1348
+ this.#result = structuredClone(result);
1349
+ this.#activeReview = undefined;
1350
+ this.#router.replace({ name: 'result', operationId });
1351
+ this.#publish();
1352
+ return cloneFrozen(result);
1353
+ }
1354
+ return this.#setBlocked(operationId, 'operationFailed', {
1355
+ intent: ticket.intent,
1356
+ navigation: 'result',
1357
+ returnRoute: ticket.returnRoute,
1358
+ writeMayHaveCompleted: true,
1359
+ });
1360
+ }
1361
+ const result = {
1362
+ state: summary.code === 'operationPartial' ? 'partial' : 'succeeded',
1363
+ operationId,
1364
+ summary,
1365
+ operationKind: ticket.intent.kind,
1366
+ ...(ticket.intent.kind === 'sourceMutation' ? { sourceAction: ticket.intent.action } : {}),
1367
+ ...('pluginId' in ticket.intent ? { pluginId: ticket.intent.pluginId } : {}),
1368
+ ...(ticket.intent.kind === 'pluginWrite' && ticket.intent.originIssue
1369
+ ? { originIssue: ticket.intent.originIssue }
1370
+ : {}),
1371
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'add'
1372
+ ? { name: 'sourceList' }
1373
+ : ticket.intent.kind === 'sourceMutation' && ticket.intent.action === 'update'
1374
+ ? { name: 'sourceDetail', sourceId: ticket.intent.sourceId }
1375
+ : ticket.returnRoute)),
1376
+ };
1377
+ if ((ticket.intent.kind === 'configSave' || ticket.intent.kind === 'configReset')
1378
+ && this.#portableRepreparePluginId === ticket.intent.pluginId) {
1379
+ this.#portableRepreparePluginId = undefined;
1380
+ this.#activeReview = undefined;
1381
+ this.#result = undefined;
1382
+ await this.preparePortableInstall(ticket.intent.pluginId, 'replace');
1383
+ return cloneFrozen(result);
1384
+ }
1385
+ this.#result = result;
1386
+ this.#activeReview = undefined;
1387
+ this.#router.replace({ name: 'result', operationId });
1388
+ this.#publish();
1389
+ this.#startGitSourceRefreshes();
1390
+ return cloneFrozen(result);
1391
+ }
1392
+ catch (error) {
1393
+ if (!this.#operationIsCurrent(generation))
1394
+ return this.#detachedBlocked(operationId, ticket.intent);
1395
+ const code = issueCode(error);
1396
+ await this.#logOperationError(error, code, ticket.intent);
1397
+ if (!this.#operationIsCurrent(generation))
1398
+ return this.#detachedBlocked(operationId, ticket.intent);
1399
+ if (code === 'operationReviewStale') {
1400
+ return this.#rebuildStaleReview(ticket, operationId, undefined, generation);
1401
+ }
1402
+ this.#cancelSourceRefreshes();
1403
+ generation = ++this.#generation;
1404
+ try {
1405
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1406
+ if (!this.#operationIsCurrent(generation))
1407
+ return this.#detachedBlocked(operationId, ticket.intent);
1408
+ this.#applyStatus(refreshed);
1409
+ }
1410
+ catch (reloadError) {
1411
+ if (!this.#operationIsCurrent(generation))
1412
+ return this.#detachedBlocked(operationId, ticket.intent);
1413
+ await this.#logErrorSafe(reloadError, 'tuiStateReloadFailed', {
1414
+ privateConfigOperation: true,
1415
+ });
1416
+ }
1417
+ if (!this.#operationIsCurrent(generation))
1418
+ return this.#detachedBlocked(operationId, ticket.intent);
1419
+ return this.#setBlocked(operationId, code, {
1420
+ intent: ticket.intent,
1421
+ navigation: 'result',
1422
+ returnRoute: ticket.returnRoute,
1423
+ writeMayHaveCompleted: true,
1424
+ });
1425
+ }
1426
+ }
1427
+ #operationIsCurrent(generation) {
1428
+ return !this.#disposed && !this.#abort.signal.aborted && generation === this.#generation;
1429
+ }
1430
+ #detachedBlocked(operationId, intent) {
1431
+ return cloneFrozen({
1432
+ state: 'blocked',
1433
+ operationId,
1434
+ code: 'operationCancelled',
1435
+ message: '操作已停止。',
1436
+ ...this.#operationIdentity(intent),
1437
+ });
1438
+ }
1439
+ async #rebuildStaleReview(ticket, operationId, currentEvidence, generation = this.#generation) {
1440
+ this.#cancelSourceRefreshes();
1441
+ generation = ++this.#generation;
1442
+ try {
1443
+ const refreshed = await this.dependencies.loadStatus(this.#abort.signal);
1444
+ if (!this.#operationIsCurrent(generation))
1445
+ return this.#detachedBlocked(operationId, ticket.intent);
1446
+ this.#applyStatus(refreshed);
1447
+ }
1448
+ catch (error) {
1449
+ if (!this.#operationIsCurrent(generation))
1450
+ return this.#detachedBlocked(operationId, ticket.intent);
1451
+ await this.#logErrorSafe(error, 'tuiStateReloadFailed', { privateConfigOperation: true });
1452
+ return this.#setBlocked(operationId, 'tuiStateReloadFailed', {
1453
+ intent: ticket.intent,
1454
+ navigation: 'result',
1455
+ returnRoute: ticket.returnRoute,
1456
+ });
1457
+ }
1458
+ try {
1459
+ this.#assertIssueOrigin(ticket.intent);
1460
+ await this.dependencies.validateIntent?.(structuredClone(ticket.intent), this.#abort.signal);
1461
+ }
1462
+ catch (error) {
1463
+ if (!this.#operationIsCurrent(generation))
1464
+ return this.#detachedBlocked(operationId, ticket.intent);
1465
+ const code = issueCode(error);
1466
+ await this.#logOperationError(error, code, ticket.intent);
1467
+ if (!this.#operationIsCurrent(generation))
1468
+ return this.#detachedBlocked(operationId, ticket.intent);
1469
+ const result = {
1470
+ state: 'blocked',
1471
+ operationId,
1472
+ code,
1473
+ message: '状态已变化,原操作不再安全。请修改选择后重试。',
1474
+ ...this.#operationIdentity(ticket.intent),
1475
+ returnRoute: structuredClone(this.#normalizeRoute(ticket.returnRoute)),
1476
+ };
1477
+ this.#result = structuredClone(result);
1478
+ this.#activeReview = undefined;
1479
+ this.#router.replace({ name: 'result', operationId });
1480
+ this.#publish();
1481
+ return cloneFrozen(result);
1482
+ }
1483
+ if (!this.#operationIsCurrent(generation))
1484
+ return this.#detachedBlocked(operationId, ticket.intent);
1485
+ let evidence = currentEvidence;
1486
+ if (!evidence) {
1487
+ try {
1488
+ evidence = await this.dependencies.captureEvidence(structuredClone(ticket.intent), this.#abort.signal);
1489
+ }
1490
+ catch (error) {
1491
+ if (!this.#operationIsCurrent(generation))
1492
+ return this.#detachedBlocked(operationId, ticket.intent);
1493
+ const code = issueCode(error);
1494
+ await this.#logOperationError(error, code, ticket.intent);
1495
+ if (!this.#operationIsCurrent(generation))
1496
+ return this.#detachedBlocked(operationId, ticket.intent);
1497
+ return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1498
+ }
1499
+ }
1500
+ if (!this.#operationIsCurrent(generation))
1501
+ return this.#detachedBlocked(operationId, ticket.intent);
1502
+ if (ticket.intent.kind === 'portableInstall') {
1503
+ try {
1504
+ await this.refreshPortableInstallDraft(ticket.intent.pluginId);
1505
+ }
1506
+ catch (error) {
1507
+ if (!this.#operationIsCurrent(generation))
1508
+ return this.#detachedBlocked(operationId, ticket.intent);
1509
+ const code = issueCode(error);
1510
+ await this.#logOperationError(error, code, ticket.intent);
1511
+ return this.#setBlocked(operationId, code, { intent: ticket.intent, returnRoute: ticket.returnRoute });
1512
+ }
1513
+ }
1514
+ if (!this.#operationIsCurrent(generation))
1515
+ return this.#detachedBlocked(operationId, ticket.intent);
1516
+ const review = this.#storeOperationReview(ticket.intent, evidence, 'replace', ticket.returnRoute);
1517
+ const result = { state: 'stale', operationId, review, ...this.#operationIdentity(ticket.intent) };
1518
+ this.#result = result;
1519
+ this.#publish();
1520
+ return cloneFrozen(result);
1521
+ }
1522
+ #setBlocked(operationId, code, options) {
1523
+ const message = code === 'portableSkillsCliInstallFailed'
1524
+ ? 'Skills CLI 安装失败。请修改 Agent 或重试。'
1525
+ : options?.writeMayHaveCompleted
1526
+ ? '写入可能已完成,当前无法确认。请查看刷新后的平台或问题状态。'
1527
+ : code === 'configResetStillPresent'
1528
+ ? '重置执行后设置层仍然存在。请重新检查设置问题。'
1529
+ : code === 'issueStillPresent'
1530
+ ? '写入已完成,但重新检查后原问题仍然存在。'
1531
+ : '操作未完成。请重新检查后重试。';
1532
+ const result = {
1533
+ state: 'blocked',
1534
+ operationId,
1535
+ code,
1536
+ message,
1537
+ ...(options?.intent ? this.#operationIdentity(options.intent) : {}),
1538
+ ...(options?.returnRoute ? { returnRoute: structuredClone(this.#normalizeRoute(options.returnRoute)) } : {}),
1539
+ ...(options?.writeMayHaveCompleted ? { writeMayHaveCompleted: true } : {}),
1540
+ ...(options?.verification ? { verification: options.verification } : {}),
1541
+ };
1542
+ this.#result = structuredClone(result);
1543
+ this.#activeReview = undefined;
1544
+ if (options?.navigation === 'back') {
1545
+ this.#router.back();
1546
+ this.#normalizeCurrentRoute();
1547
+ }
1548
+ else
1549
+ this.#router.replace({ name: 'result', operationId });
1550
+ this.#publish();
1551
+ return cloneFrozen(result);
1552
+ }
1553
+ #operationIdentity(intent) {
1554
+ return {
1555
+ operationKind: intent.kind,
1556
+ ...(intent.kind === 'sourceMutation' ? { sourceAction: intent.action } : {}),
1557
+ ...('pluginId' in intent ? { pluginId: intent.pluginId } : {}),
1558
+ ...(intent.kind === 'pluginWrite' && intent.originIssue
1559
+ ? { originIssue: intent.originIssue }
1560
+ : {}),
1561
+ };
1562
+ }
1563
+ #assertIssueOrigin(intent) {
1564
+ if (intent.kind !== 'pluginWrite' || !intent.originIssue)
1565
+ return;
1566
+ const origin = intent.originIssue;
1567
+ const issue = this.#viewModel?.issues.find((candidate) => candidate.issueId === origin.issueId);
1568
+ const action = issue?.actions.find((candidate) => candidate.id === origin.actionId);
1569
+ if (!issue || !action || issue.category !== origin.category
1570
+ || userIssueKey(issue) !== origin.issueKey
1571
+ || (action.pluginId ?? issue.pluginId) !== intent.pluginId
1572
+ || issue.platform !== intent.platform
1573
+ || issue.sourceId !== sourceIdFromPluginId(intent.pluginId)) {
1574
+ throw new TuiOperationBlockedError('operationReviewStale', '原问题与当前状态不匹配。');
1575
+ }
1576
+ const expected = issue.category === 'dependencyMissing'
1577
+ ? action.id === 'installDependency' && action.writeAction === 'install'
1578
+ ? { action: 'install', scope: action.scope, platform: action.platform }
1579
+ : undefined
1580
+ : issue.category === 'dependencyNotReady'
1581
+ ? action.id === 'reinstallDependency' && action.writeAction === 'reinstall'
1582
+ ? { action: 'reinstall', scope: action.scope, platform: action.platform }
1583
+ : undefined
1584
+ : issue.category === 'incompleteInstallation'
1585
+ ? origin.actionId === 'reinstall'
1586
+ ? { action: 'reinstall', scope: issue.scope }
1587
+ : origin.actionId === 'remove'
1588
+ ? { action: 'uninstall', scope: issue.scope }
1589
+ : undefined
1590
+ : issue.category === 'scopeConflict'
1591
+ ? origin.actionId === 'keepUserScope'
1592
+ ? { action: 'uninstall', scope: 'project' }
1593
+ : origin.actionId === 'keepProjectScope'
1594
+ ? { action: 'uninstall', scope: 'user' }
1595
+ : undefined
1596
+ : undefined;
1597
+ const dependencyOriginValid = issue.category !== 'dependencyMissing' && issue.category !== 'dependencyNotReady'
1598
+ || (origin.consumerPluginId === issue.pluginId
1599
+ && origin.consumerSourceIdentity === issue.sourceIdentity
1600
+ && origin.consumerPlatform === issue.platform
1601
+ && origin.consumerScope === issue.scope
1602
+ && origin.dependencyPluginId === issue.dependencyPluginId
1603
+ && origin.dependencyPlatform === issue.dependencyPlatform
1604
+ && origin.dependencyScope === issue.dependencyScope
1605
+ && origin.dependencyProject === issue.dependencyProject);
1606
+ if (!expected || intent.action !== expected.action || intent.scope !== expected.scope
1607
+ || ('platform' in expected && intent.platform !== expected.platform) || !dependencyOriginValid) {
1608
+ throw new TuiOperationBlockedError('operationReviewStale', '问题动作与当前目标不匹配。');
1609
+ }
1610
+ }
1611
+ dispose() {
1612
+ if (this.#disposed)
1613
+ return;
1614
+ this.#disposed = true;
1615
+ this.#cancelPortableRequest();
1616
+ this.#abort.abort();
1617
+ this.#cancelSourceRefreshes();
1618
+ this.#progressUnsubscribe?.();
1619
+ this.#tickets.clear();
1620
+ this.#phase = 'disposed';
1621
+ this.#listeners.clear();
1622
+ }
1623
+ }
1624
+ function operationResultSummary(intent, execution) {
1625
+ if (intent.kind === 'portableInstall') {
1626
+ const receipt = execution;
1627
+ const verificationUnknown = receipt.failures.some((failure) => failure.code === 'portableSkillsCliVerificationFailed'
1628
+ && receipt.verification.records.length === 0
1629
+ && receipt.verification.missing.length === 0);
1630
+ return {
1631
+ code: receipt.status === 'succeeded' ? 'operationSucceeded'
1632
+ : receipt.status === 'partial' ? 'operationPartial' : 'operationFailed',
1633
+ message: receipt.status === 'succeeded' ? '通用 Skills 已安装。'
1634
+ : receipt.status === 'partial' ? '安装命令已完成,但结果无法完全确认。'
1635
+ : 'Skills CLI 安装失败。',
1636
+ pluginId: intent.pluginId,
1637
+ messages: [
1638
+ ...receipt.selectedSkills.map((skill) => skill.name),
1639
+ ...receipt.failures.map((failure) => failure.message),
1640
+ ],
1641
+ portable: {
1642
+ selectedSkills: receipt.selectedSkills.map((skill) => skill.name),
1643
+ agentMode: receipt.agentMode,
1644
+ requestedAgents: receipt.requestedAgents,
1645
+ scope: receipt.scope,
1646
+ materializationDigest: receipt.materializationDigest,
1647
+ missing: receipt.verification.missing,
1648
+ verificationUnknown,
1649
+ ...(receipt.failures[0]?.recovery ? { recovery: receipt.failures[0].recovery } : {}),
1650
+ ...(receipt.failures[0]?.code ? { failureCode: receipt.failures[0].code } : {}),
1651
+ },
1652
+ };
1653
+ }
1654
+ const sourceWarnings = intent.kind === 'sourceMutation'
1655
+ && typeof execution === 'object' && execution !== null
1656
+ && Array.isArray(execution.errors)
1657
+ ? execution.errors.flatMap((error) => typeof error?.message === 'string' ? [`警告:${error.message}`] : [])
1658
+ : [];
1659
+ const executionPlan = typeof execution === 'object' && execution !== null
1660
+ && typeof execution.dependencyPlan === 'object'
1661
+ ? execution.dependencyPlan
1662
+ : undefined;
1663
+ const rawTargets = typeof execution === 'object' && execution !== null
1664
+ && Array.isArray(execution.targetResults)
1665
+ ? execution.targetResults
1666
+ : [];
1667
+ const targets = rawTargets.flatMap((entry) => {
1668
+ const target = typeof entry.target === 'object' && entry.target !== null
1669
+ ? entry.target
1670
+ : entry;
1671
+ const platform = target.platform;
1672
+ const scope = target.scope;
1673
+ const pluginId = target.pluginId;
1674
+ const rawState = entry.state;
1675
+ const action = entry.action;
1676
+ if ((platform !== 'codex' && platform !== 'claude')
1677
+ || (scope !== 'user' && scope !== 'project')
1678
+ || (rawState !== 'completed' && rawState !== 'failed'))
1679
+ return [];
1680
+ const planned = executionPlan?.targets.find((candidate) => candidate.pluginId === pluginId && candidate.platform === platform
1681
+ && candidate.resolvedScope === scope);
1682
+ const state = action === 'keep' ? 'kept'
1683
+ : action === 'skipped' ? 'skipped'
1684
+ : rawState;
1685
+ return [{
1686
+ ...(typeof pluginId === 'string' ? { pluginId } : {}),
1687
+ platform: platform,
1688
+ scope: scope,
1689
+ state,
1690
+ ...(planned && executionPlan ? {
1691
+ relation: dependencyRelation(executionPlan, planned.pluginId),
1692
+ requiredBy: planned.requiredBy,
1693
+ } : {}),
1694
+ }];
1695
+ });
1696
+ const inferredTargets = intent.kind === 'pluginWrite' && targets.length === 0
1697
+ ? (intent.platform === 'both' ? ['codex', 'claude'] : [intent.platform]).map((platform) => ({
1698
+ platform,
1699
+ scope: intent.scope,
1700
+ state: 'completed',
1701
+ }))
1702
+ : targets;
1703
+ const partial = inferredTargets.some((target) => target.state === 'failed' || target.state === 'skipped')
1704
+ && inferredTargets.some((target) => target.state === 'completed' || target.state === 'kept');
1705
+ const overallFailed = typeof execution === 'object' && execution !== null
1706
+ && execution.overallState === 'failed';
1707
+ const failed = overallFailed || (inferredTargets.length > 0
1708
+ && inferredTargets.every((target) => target.state === 'failed' || target.state === 'skipped'));
1709
+ return {
1710
+ code: failed && !partial ? 'operationFailed' : partial ? 'operationPartial' : 'operationSucceeded',
1711
+ message: failed && !partial
1712
+ ? '操作结果无法确认。'
1713
+ : partial
1714
+ ? '操作只完成了一部分。'
1715
+ : '操作已完成。',
1716
+ ...(intent.kind === 'pluginWrite' ? {
1717
+ pluginId: intent.pluginId,
1718
+ action: intent.action,
1719
+ } : {}),
1720
+ ...(inferredTargets.length > 0 ? { targets: inferredTargets } : {}),
1721
+ ...(sourceWarnings.length > 0 ? { messages: sourceWarnings } : {}),
1722
+ };
1723
+ }
1724
+ //# sourceMappingURL=controller.js.map