@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,1898 @@
1
+ import crypto from 'node:crypto';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import * as nodeFs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import fs from 'fs-extra';
6
+ import { assertTestReadablePath, assertTestWritablePath, } from './test-path-fence.js';
7
+ import { markActiveMarketplaceDurableCommit, withActiveMarketplaceRuntimeLease, } from './active-marketplace-runtime-lock.js';
8
+ import { canonicalizePathThroughExistingAncestor, resolveAwHome, resolveRepoRootSync, } from './paths.js';
9
+ import { acquireProcessLock } from './process-lock.js';
10
+ const configCommitStorage = new AsyncLocalStorage();
11
+ function markConfigDurableCommit() {
12
+ let context = configCommitStorage.getStore();
13
+ while (context) {
14
+ context.durableCommit = true;
15
+ context = context.parent;
16
+ }
17
+ markActiveMarketplaceDurableCommit();
18
+ }
19
+ export class PluginConfigSchemaResolutionError extends Error {
20
+ code;
21
+ details;
22
+ constructor(code, message, details) {
23
+ super(`${code}: ${message}`);
24
+ this.name = 'PluginConfigSchemaResolutionError';
25
+ this.code = code;
26
+ this.details = details;
27
+ }
28
+ }
29
+ export function isPluginConfigSchemaResolutionError(error) {
30
+ return error instanceof PluginConfigSchemaResolutionError;
31
+ }
32
+ export class PluginConfigStoreInventoryError extends Error {
33
+ code = 'invalidPluginConfigStore';
34
+ constructor(message) {
35
+ super(`invalidPluginConfigStore: ${message}`);
36
+ this.name = 'PluginConfigStoreInventoryError';
37
+ }
38
+ }
39
+ const CONFIG_DIR = 'plugin-config';
40
+ const CONFIG_FILE = 'config.json';
41
+ let atomicConfigBeforeRenameHookForTests;
42
+ let atomicConfigAfterRenameHookForTests;
43
+ let configFileLockBeforeReleaseHookForTests;
44
+ export function setAtomicConfigBeforeRenameHookForTests(hook) {
45
+ atomicConfigBeforeRenameHookForTests = hook;
46
+ }
47
+ export function setAtomicConfigAfterRenameHookForTests(hook) {
48
+ atomicConfigAfterRenameHookForTests = hook;
49
+ }
50
+ export function setConfigFileLockBeforeReleaseHookForTests(hook) {
51
+ configFileLockBeforeReleaseHookForTests = hook;
52
+ }
53
+ function assertConfigPathBoundary(allowedRoot, candidatePath) {
54
+ const root = path.resolve(allowedRoot);
55
+ const candidate = path.resolve(candidatePath);
56
+ const relative = path.relative(root, candidate);
57
+ if (relative === ''
58
+ || relative === '..'
59
+ || relative.startsWith(`..${path.sep}`)
60
+ || path.isAbsolute(relative)) {
61
+ throw new Error(`Config path escapes its allowed root: ${candidate}`);
62
+ }
63
+ return { root, candidate };
64
+ }
65
+ async function lstatIfExists(targetPath) {
66
+ try {
67
+ return await fs.lstat(targetPath);
68
+ }
69
+ catch (error) {
70
+ if (error.code === 'ENOENT')
71
+ return null;
72
+ throw error;
73
+ }
74
+ }
75
+ async function assertNoConfigSymlink(allowedRoot, candidatePath) {
76
+ const { root, candidate } = assertConfigPathBoundary(allowedRoot, candidatePath);
77
+ const paths = [root];
78
+ let current = root;
79
+ for (const segment of path.relative(root, candidate).split(path.sep)) {
80
+ current = path.join(current, segment);
81
+ paths.push(current);
82
+ }
83
+ for (const currentPath of paths) {
84
+ const stats = await lstatIfExists(currentPath);
85
+ if (stats?.isSymbolicLink()) {
86
+ throw new Error(`Config path cannot contain a symbolic link: ${currentPath}`);
87
+ }
88
+ }
89
+ }
90
+ async function inspectConfigReadTarget(allowedRoot, filePath) {
91
+ await assertNoConfigSymlink(allowedRoot, filePath);
92
+ const stats = await lstatIfExists(filePath);
93
+ if (!stats)
94
+ return null;
95
+ if (!stats.isFile() || stats.isSymbolicLink()) {
96
+ throw new Error(`Config read target must be a regular file: ${filePath}`);
97
+ }
98
+ return stats;
99
+ }
100
+ async function inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot) {
101
+ const { root, candidate } = assertConfigPathBoundary(boundaryRoot, path.join(storeRoot, CONFIG_FILE));
102
+ let current = root;
103
+ for (const segment of path.relative(root, path.dirname(candidate)).split(path.sep).filter(Boolean)) {
104
+ current = path.join(current, segment);
105
+ const stats = await lstatIfExists(current);
106
+ if (!stats)
107
+ return false;
108
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
109
+ throw new PluginConfigStoreInventoryError(stats.isSymbolicLink()
110
+ ? `Config path cannot contain a symbolic link: ${current}`
111
+ : `Config inventory boundary must be a directory: ${current}`);
112
+ }
113
+ }
114
+ return true;
115
+ }
116
+ async function readConfigDirectoryIdentity(directoryPath) {
117
+ const stats = await fs.lstat(directoryPath);
118
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
119
+ throw new Error(`Config parent must be a real directory: ${directoryPath}`);
120
+ }
121
+ return {
122
+ dev: stats.dev,
123
+ ino: stats.ino,
124
+ realPath: await fs.realpath(directoryPath),
125
+ };
126
+ }
127
+ function assertConfigDirectoryStable(label, before, after) {
128
+ if (before.dev !== after.dev || before.ino !== after.ino || before.realPath !== after.realPath) {
129
+ throw new Error(`${label} identity changed before atomic config replacement.`);
130
+ }
131
+ }
132
+ function assertRealConfigBoundary(root, parent) {
133
+ const relative = path.relative(root.realPath, parent.realPath);
134
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
135
+ throw new Error('Config parent realpath escapes its allowed root.');
136
+ }
137
+ }
138
+ async function prepareConfigFileParent(filePath, allowedRoot) {
139
+ assertConfigPathBoundary(allowedRoot, filePath);
140
+ await assertNoConfigSymlink(allowedRoot, filePath);
141
+ await fs.ensureDir(path.dirname(filePath));
142
+ await assertNoConfigSymlink(allowedRoot, filePath);
143
+ }
144
+ export function resolveConfigFileLockPath(filePath) {
145
+ return path.join(path.dirname(filePath), `.${path.basename(filePath)}.aw.lock`);
146
+ }
147
+ async function releaseConfigFileLock(lease) {
148
+ let lastError;
149
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
150
+ try {
151
+ await configFileLockBeforeReleaseHookForTests?.(lease.lockPath, attempt);
152
+ await lease.release();
153
+ return null;
154
+ }
155
+ catch (error) {
156
+ lastError = error;
157
+ if (attempt < 3) {
158
+ await new Promise((resolve) => setTimeout(resolve, 10));
159
+ }
160
+ }
161
+ }
162
+ try {
163
+ await lease.release();
164
+ }
165
+ catch {
166
+ // Preserve the first release diagnostic. A final direct retry avoids a hook-created self-lock.
167
+ }
168
+ return lastError;
169
+ }
170
+ function emitConfigReleaseWarning(message) {
171
+ try {
172
+ process.emitWarning(message, { code: 'AW_CONFIG_LOCK_RELEASE_FAILED' });
173
+ }
174
+ catch {
175
+ // Diagnostics cannot replace the primary action result or error.
176
+ }
177
+ }
178
+ export async function withConfigFileLock(options) {
179
+ await prepareConfigFileParent(options.filePath, options.allowedRoot);
180
+ const lockPath = resolveConfigFileLockPath(options.filePath);
181
+ await assertNoConfigSymlink(options.allowedRoot, lockPath);
182
+ const lease = await acquireProcessLock({ lockPath });
183
+ let result;
184
+ let actionError;
185
+ let actionSucceeded = false;
186
+ const commitContext = {
187
+ parent: configCommitStorage.getStore(),
188
+ durableCommit: false,
189
+ };
190
+ try {
191
+ await assertNoConfigSymlink(options.allowedRoot, options.filePath);
192
+ result = await configCommitStorage.run(commitContext, options.action);
193
+ actionSucceeded = true;
194
+ }
195
+ catch (error) {
196
+ actionError = error;
197
+ }
198
+ const releaseError = await releaseConfigFileLock(lease);
199
+ if (!actionSucceeded) {
200
+ if (releaseError) {
201
+ emitConfigReleaseWarning('Config action failed, and its process lock release also failed.');
202
+ }
203
+ throw actionError;
204
+ }
205
+ if (releaseError) {
206
+ if (commitContext.durableCommit) {
207
+ emitConfigReleaseWarning('Config action completed successfully, but its process lock could not be released after 3 attempts. The committed result remains successful; the lock can require process exit or manual recovery before another update.');
208
+ }
209
+ else {
210
+ throw releaseError;
211
+ }
212
+ }
213
+ return result;
214
+ }
215
+ export async function withPluginConfigTransactionLease(options) {
216
+ const storePath = resolvePluginConfigStorePath({
217
+ ...options,
218
+ ...(options.scope === 'project' ? { access: 'write' } : {}),
219
+ });
220
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
221
+ return withConfigFileLock({
222
+ filePath: `${storePath}.transaction`,
223
+ allowedRoot,
224
+ action: options.action,
225
+ });
226
+ }
227
+ export async function atomicReplaceConfigFile(options) {
228
+ await prepareConfigFileParent(options.filePath, options.allowedRoot);
229
+ const rootPath = path.resolve(options.allowedRoot);
230
+ const parentPath = path.dirname(path.resolve(options.filePath));
231
+ const rootIdentity = await readConfigDirectoryIdentity(rootPath);
232
+ const parentIdentity = await readConfigDirectoryIdentity(parentPath);
233
+ assertRealConfigBoundary(rootIdentity, parentIdentity);
234
+ const currentTarget = await lstatIfExists(options.filePath);
235
+ if (currentTarget && (!currentTarget.isFile() || currentTarget.isSymbolicLink())) {
236
+ throw new Error(`Config target must be a regular file: ${options.filePath}`);
237
+ }
238
+ const temporaryPath = path.join(parentPath, `.${path.basename(options.filePath)}.tmp-${process.pid}-${crypto.randomUUID()}`);
239
+ assertConfigPathBoundary(options.allowedRoot, temporaryPath);
240
+ let handle;
241
+ try {
242
+ handle = await nodeFs.open(temporaryPath, 'wx', options.mode ?? (currentTarget ? currentTarget.mode & 0o777 : 0o600));
243
+ await handle.writeFile(options.content, 'utf8');
244
+ await handle.sync();
245
+ await handle.close();
246
+ handle = undefined;
247
+ await atomicConfigBeforeRenameHookForTests?.(options.filePath, temporaryPath);
248
+ await assertNoConfigSymlink(options.allowedRoot, options.filePath);
249
+ const currentRootIdentity = await readConfigDirectoryIdentity(rootPath);
250
+ const currentParentIdentity = await readConfigDirectoryIdentity(parentPath);
251
+ assertRealConfigBoundary(currentRootIdentity, currentParentIdentity);
252
+ assertConfigDirectoryStable('Config allowed root', rootIdentity, currentRootIdentity);
253
+ assertConfigDirectoryStable('Config parent', parentIdentity, currentParentIdentity);
254
+ const targetBeforeRename = await lstatIfExists(options.filePath);
255
+ if (targetBeforeRename && (!targetBeforeRename.isFile() || targetBeforeRename.isSymbolicLink())) {
256
+ throw new Error(`Config target must be a regular file: ${options.filePath}`);
257
+ }
258
+ await nodeFs.rename(temporaryPath, options.filePath);
259
+ markConfigDurableCommit();
260
+ // rename is the commit point. Diagnostics and durability work after it are best effort,
261
+ // because reporting failure here would leave the new generation on disk without giving
262
+ // the caller a safe way to restore the old generation.
263
+ await atomicConfigAfterRenameHookForTests?.(options.filePath).catch(() => undefined);
264
+ await (async () => {
265
+ const finalTarget = await fs.lstat(options.filePath);
266
+ if (!finalTarget.isFile() || finalTarget.isSymbolicLink()) {
267
+ throw new Error('Atomic config replacement produced an invalid target.');
268
+ }
269
+ if (await fs.readFile(options.filePath, 'utf8') !== options.content) {
270
+ throw new Error('Atomic config replacement content differs from the committed content.');
271
+ }
272
+ })().catch(() => undefined);
273
+ const directoryHandle = await nodeFs.open(parentPath, 'r').catch(() => null);
274
+ await directoryHandle?.sync().catch(() => undefined);
275
+ await directoryHandle?.close().catch(() => undefined);
276
+ }
277
+ finally {
278
+ await handle?.close().catch(() => undefined);
279
+ await nodeFs.rm(temporaryPath, { force: true }).catch(() => undefined);
280
+ }
281
+ }
282
+ export async function atomicRemoveConfigFile(options) {
283
+ await prepareConfigFileParent(options.filePath, options.allowedRoot);
284
+ const rootPath = path.resolve(options.allowedRoot);
285
+ const parentPath = path.dirname(path.resolve(options.filePath));
286
+ const rootIdentity = await readConfigDirectoryIdentity(rootPath);
287
+ const parentIdentity = await readConfigDirectoryIdentity(parentPath);
288
+ assertRealConfigBoundary(rootIdentity, parentIdentity);
289
+ const currentTarget = await lstatIfExists(options.filePath);
290
+ if (!currentTarget)
291
+ return false;
292
+ if (!currentTarget.isFile() || currentTarget.isSymbolicLink()) {
293
+ throw new Error(`Config target must be a regular file: ${options.filePath}`);
294
+ }
295
+ await atomicConfigBeforeRenameHookForTests?.(options.filePath, options.filePath);
296
+ await assertNoConfigSymlink(options.allowedRoot, options.filePath);
297
+ const currentRootIdentity = await readConfigDirectoryIdentity(rootPath);
298
+ const currentParentIdentity = await readConfigDirectoryIdentity(parentPath);
299
+ assertRealConfigBoundary(currentRootIdentity, currentParentIdentity);
300
+ assertConfigDirectoryStable('Config allowed root', rootIdentity, currentRootIdentity);
301
+ assertConfigDirectoryStable('Config parent', parentIdentity, currentParentIdentity);
302
+ const targetBeforeRemove = await lstatIfExists(options.filePath);
303
+ if (!targetBeforeRemove)
304
+ return false;
305
+ if (!targetBeforeRemove.isFile() || targetBeforeRemove.isSymbolicLink()) {
306
+ throw new Error(`Config target must be a regular file: ${options.filePath}`);
307
+ }
308
+ await nodeFs.unlink(options.filePath);
309
+ markConfigDurableCommit();
310
+ await atomicConfigAfterRenameHookForTests?.(options.filePath).catch(() => undefined);
311
+ const directoryHandle = await nodeFs.open(parentPath, 'r').catch(() => null);
312
+ await directoryHandle?.sync().catch(() => undefined);
313
+ await directoryHandle?.close().catch(() => undefined);
314
+ return true;
315
+ }
316
+ function isRecord(value) {
317
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
318
+ }
319
+ function formatValue(value) {
320
+ return typeof value === 'string' ? value : JSON.stringify(value);
321
+ }
322
+ async function calculateConfigManifestDigest(filePath) {
323
+ const hash = crypto.createHash('sha256');
324
+ hash.update(await fs.readFile(filePath));
325
+ return `sha256:${hash.digest('hex')}`;
326
+ }
327
+ async function calculateConfigSchemaManifestDigestForPlugin(plugin) {
328
+ if (await lstatIfExists(plugin.manifestPath)) {
329
+ return calculateConfigManifestDigest(plugin.manifestPath);
330
+ }
331
+ const hash = crypto.createHash('sha256');
332
+ hash.update(JSON.stringify(plugin.manifest));
333
+ return `sha256:${hash.digest('hex')}`;
334
+ }
335
+ function createConfigSchemaEvidenceFromCurrentCatalog(plugin, manifestDigest) {
336
+ return {
337
+ source: 'currentCatalog',
338
+ identity: createPluginConfigSchemaIdentity(plugin.manifest),
339
+ pluginVersion: plugin.manifest.version,
340
+ manifestDigest,
341
+ };
342
+ }
343
+ function getStoreEntries(values) {
344
+ return { ...values };
345
+ }
346
+ export function assertSafePluginConfigSegment(pluginName) {
347
+ if (typeof pluginName !== 'string' || pluginName.trim() === '') {
348
+ throw new Error('Invalid plugin config segment: expected a non-empty plugin name.');
349
+ }
350
+ if (pluginName !== pluginName.trim() ||
351
+ pluginName === '.' ||
352
+ pluginName === '..' ||
353
+ pluginName.includes('/') ||
354
+ pluginName.includes('\\') ||
355
+ path.posix.isAbsolute(pluginName) ||
356
+ path.win32.isAbsolute(pluginName) ||
357
+ /^[A-Za-z][A-Za-z0-9+.-]*:/.test(pluginName)) {
358
+ throw new Error(`Invalid plugin config segment "${pluginName}".`);
359
+ }
360
+ return pluginName;
361
+ }
362
+ export function resolveAwProjectRoot(cwd) {
363
+ return path.resolve(resolveRepoRootSync(cwd));
364
+ }
365
+ function resolvePluginConfigAllowedRoot(options) {
366
+ return options.scope === 'user'
367
+ ? resolveAwHome(options.env)
368
+ : path.join(resolveAwProjectRoot(options.cwd), '.agent-workflows');
369
+ }
370
+ export function resolvePluginConfigStorePath(options) {
371
+ const pluginName = assertSafePluginConfigSegment(options.pluginName);
372
+ const sourceId = options.sourceId === undefined
373
+ ? undefined
374
+ : assertSafePluginConfigSegment(options.sourceId);
375
+ const root = options.scope === 'user'
376
+ ? resolveAwHome(options.env)
377
+ : path.join(resolveAwProjectRoot(options.cwd), '.agent-workflows');
378
+ const configDir = options.scope === 'project' && options.projectLayer === 'local'
379
+ ? 'plugin-config.local'
380
+ : CONFIG_DIR;
381
+ const filePath = path.join(root, configDir, ...(sourceId ? [sourceId] : []), pluginName, CONFIG_FILE);
382
+ if (options.scope !== 'project') {
383
+ return filePath;
384
+ }
385
+ if (options.access === 'read') {
386
+ return assertTestReadablePath(filePath, options.projectLayer === 'local'
387
+ ? 'project local plugin config store (.agent-workflows/plugin-config.local)'
388
+ : 'project shared plugin config store (.agent-workflows/plugin-config)', '用 registerReadonlyFixtureRoot() 或 runWithRepositoryCwdAsReadOnly() 登记只读 project fixture 根;需要写入时改用 withSafeTestCwd() 或 registerWritableStateRoot()。');
389
+ }
390
+ if (options.access === 'write') {
391
+ return assertTestWritablePath(filePath, options.projectLayer === 'local'
392
+ ? 'project local plugin config store (.agent-workflows/plugin-config.local)'
393
+ : 'project shared plugin config store (.agent-workflows/plugin-config)', '用 withSafeTestCwd() 切到测试可写 cwd,或通过 registerWritableStateRoot() 登记 project fixture 根。');
394
+ }
395
+ return filePath;
396
+ }
397
+ export function resolvePluginConfigStoreTargets(options) {
398
+ if (options.scope === 'user') {
399
+ return [{
400
+ storeKind: 'user',
401
+ path: resolvePluginConfigStorePath(options),
402
+ }];
403
+ }
404
+ return [
405
+ {
406
+ storeKind: 'projectShared',
407
+ path: resolvePluginConfigStorePath({
408
+ ...options,
409
+ projectLayer: 'shared',
410
+ }),
411
+ projectLayer: 'shared',
412
+ },
413
+ {
414
+ storeKind: 'projectLocal',
415
+ path: resolvePluginConfigStorePath({
416
+ ...options,
417
+ projectLayer: 'local',
418
+ }),
419
+ projectLayer: 'local',
420
+ },
421
+ ];
422
+ }
423
+ async function readStoreFile(filePath) {
424
+ const value = await fs.readJson(filePath);
425
+ if (!isRecord(value)) {
426
+ throw new Error(`Invalid plugin config store "${filePath}": expected a JSON object.`);
427
+ }
428
+ return value;
429
+ }
430
+ async function readNamespacedStoreFile(options) {
431
+ const raw = await fs.readJson(options.filePath);
432
+ if (!isRecord(raw) || raw.schemaVersion !== 2 || !isRecord(raw.values)) {
433
+ throw new Error(`legacyStateUnsupported: invalid or legacy plugin config store "${options.filePath}".`);
434
+ }
435
+ if (raw.sourceId !== options.sourceId || raw.pluginName !== options.pluginName) {
436
+ throw new Error('Plugin config target identity does not match its namespaced path.');
437
+ }
438
+ if (raw.sourceIdentity !== options.sourceIdentity) {
439
+ throw new Error('sourceChanged: plugin config source identity does not match the configured source.');
440
+ }
441
+ return raw.values;
442
+ }
443
+ function namespacedConfigContent(options) {
444
+ if (!options.sourceId)
445
+ return `${JSON.stringify(options.values, null, 2)}\n`;
446
+ if (!options.sourceIdentity)
447
+ throw new Error('sourceIdentity is required for namespaced plugin config.');
448
+ const envelope = {
449
+ schemaVersion: 2,
450
+ sourceId: options.sourceId,
451
+ sourceIdentity: options.sourceIdentity,
452
+ pluginName: options.pluginName,
453
+ values: options.values,
454
+ };
455
+ return `${JSON.stringify(envelope, null, 2)}\n`;
456
+ }
457
+ async function assertNoLegacyConfigCoexistence(options) {
458
+ if (!options.sourceId)
459
+ return;
460
+ const legacyPath = resolvePluginConfigStorePath({
461
+ pluginName: options.pluginName,
462
+ scope: options.scope,
463
+ cwd: options.cwd,
464
+ env: options.env,
465
+ projectLayer: options.projectLayer,
466
+ ...(options.scope === 'project' ? { access: 'read' } : {}),
467
+ });
468
+ if (await lstatIfExists(legacyPath)) {
469
+ throw new Error('legacyStateUnsupported: legacy and namespaced plugin config stores cannot coexist.');
470
+ }
471
+ }
472
+ async function withUserPluginConfigRuntimeLease(options, action) {
473
+ return options.scope === 'user' || options.sourceId !== undefined
474
+ ? withActiveMarketplaceRuntimeLease(options.env, action)
475
+ : action();
476
+ }
477
+ export async function readPluginConfigStore(options) {
478
+ const filePath = resolvePluginConfigStorePath({
479
+ ...options,
480
+ ...(options.scope === 'project' ? { access: 'read' } : {}),
481
+ });
482
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
483
+ if (options.sourceId) {
484
+ if (!options.sourceIdentity)
485
+ throw new Error('sourceIdentity is required for namespaced plugin config.');
486
+ const legacyPath = resolvePluginConfigStorePath({
487
+ pluginName: options.pluginName,
488
+ scope: options.scope,
489
+ cwd: options.cwd,
490
+ env: options.env,
491
+ projectLayer: options.projectLayer,
492
+ ...(options.scope === 'project' ? { access: 'read' } : {}),
493
+ });
494
+ if (await inspectConfigReadTarget(allowedRoot, legacyPath)) {
495
+ throw new Error('legacyStateUnsupported: legacy and namespaced plugin config stores cannot coexist.');
496
+ }
497
+ }
498
+ if (!await inspectConfigReadTarget(allowedRoot, filePath)) {
499
+ return {
500
+ path: filePath,
501
+ exists: false,
502
+ values: {},
503
+ };
504
+ }
505
+ return {
506
+ path: filePath,
507
+ exists: true,
508
+ values: options.sourceId
509
+ ? await readNamespacedStoreFile({
510
+ filePath,
511
+ sourceId: options.sourceId,
512
+ sourceIdentity: options.sourceIdentity,
513
+ pluginName: options.pluginName,
514
+ })
515
+ : await readStoreFile(filePath),
516
+ };
517
+ }
518
+ export async function readNamespacedPluginConfigStoreIdentity(options) {
519
+ const filePath = resolvePluginConfigStorePath({
520
+ ...options,
521
+ ...(options.scope === 'project' ? { access: 'read' } : {}),
522
+ });
523
+ if (!await inspectConfigReadTarget(resolvePluginConfigAllowedRoot(options), filePath))
524
+ return null;
525
+ const raw = await fs.readJson(filePath);
526
+ if (!isRecord(raw) || raw.schemaVersion !== 2 || !isRecord(raw.values)
527
+ || raw.sourceId !== options.sourceId || raw.pluginName !== options.pluginName
528
+ || typeof raw.sourceIdentity !== 'string') {
529
+ throw new Error('legacyStateUnsupported: invalid namespaced plugin config store.');
530
+ }
531
+ return {
532
+ sourceIdentity: raw.sourceIdentity,
533
+ store: { path: filePath, exists: true, values: raw.values },
534
+ };
535
+ }
536
+ async function listConfigStoreDirectoryPluginNames(storeRoot) {
537
+ if (!await lstatIfExists(storeRoot)) {
538
+ return [];
539
+ }
540
+ const entries = await fs.readdir(storeRoot, { withFileTypes: true });
541
+ const pluginNames = [];
542
+ for (const entry of entries) {
543
+ if (!entry.isDirectory()) {
544
+ throw new PluginConfigStoreInventoryError(`Config plugin namespace must be a directory: ${path.join(storeRoot, entry.name)}`);
545
+ }
546
+ let pluginName;
547
+ try {
548
+ pluginName = assertSafePluginConfigSegment(entry.name);
549
+ }
550
+ catch {
551
+ continue;
552
+ }
553
+ if (await lstatIfExists(path.join(storeRoot, pluginName, CONFIG_FILE))) {
554
+ pluginNames.push(pluginName);
555
+ }
556
+ }
557
+ return pluginNames;
558
+ }
559
+ export async function listPluginConfigStorePluginNames(options) {
560
+ const projectRoot = resolveAwProjectRoot(options.cwd);
561
+ const storeRoots = options.scope === 'user'
562
+ ? [path.join(resolveAwHome(options.env), CONFIG_DIR)]
563
+ : options.scope === 'project'
564
+ ? [path.join(projectRoot, '.agent-workflows', options.projectLayer === 'local' ? 'plugin-config.local' : CONFIG_DIR)]
565
+ : [
566
+ path.join(resolveAwHome(options.env), CONFIG_DIR),
567
+ path.join(projectRoot, '.agent-workflows', CONFIG_DIR),
568
+ path.join(projectRoot, '.agent-workflows', 'plugin-config.local'),
569
+ ];
570
+ const pluginNames = (await Promise.all(storeRoots.map((storeRoot) => listConfigStoreDirectoryPluginNames(storeRoot)))).flat();
571
+ return [...new Set(pluginNames)].sort((left, right) => left.localeCompare(right));
572
+ }
573
+ export async function listNamespacedPluginConfigStores(options) {
574
+ const boundaryRoot = options.scope === 'user'
575
+ ? resolveAwHome(options.env)
576
+ : resolveAwProjectRoot(options.cwd);
577
+ if (options.scope === 'user') {
578
+ await canonicalizePathThroughExistingAncestor(boundaryRoot);
579
+ }
580
+ else {
581
+ await fs.realpath(boundaryRoot);
582
+ }
583
+ const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), options.scope === 'project' && options.projectLayer === 'local'
584
+ ? 'plugin-config.local'
585
+ : CONFIG_DIR);
586
+ const output = [];
587
+ if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
588
+ return output;
589
+ for (const sourceEntry of await fs.readdir(storeRoot, { withFileTypes: true })) {
590
+ if (!sourceEntry.isDirectory()) {
591
+ throw new PluginConfigStoreInventoryError(sourceEntry.isSymbolicLink()
592
+ ? `Config path cannot contain a symbolic link: ${path.join(storeRoot, sourceEntry.name)}`
593
+ : `Config source namespace must be a directory: ${path.join(storeRoot, sourceEntry.name)}`);
594
+ }
595
+ const sourceId = assertSafePluginConfigSegment(sourceEntry.name);
596
+ const sourceRoot = path.join(storeRoot, sourceId);
597
+ for (const pluginEntry of await fs.readdir(sourceRoot, { withFileTypes: true })) {
598
+ if (!pluginEntry.isDirectory()) {
599
+ throw new PluginConfigStoreInventoryError(pluginEntry.isSymbolicLink()
600
+ ? `Config path cannot contain a symbolic link: ${path.join(sourceRoot, pluginEntry.name)}`
601
+ : `Config plugin namespace must be a directory: ${path.join(sourceRoot, pluginEntry.name)}`);
602
+ }
603
+ const pluginName = assertSafePluginConfigSegment(pluginEntry.name);
604
+ const filePath = path.join(storeRoot, sourceId, pluginName, CONFIG_FILE);
605
+ if (!await inspectConfigReadTarget(storeRoot, filePath))
606
+ continue;
607
+ const raw = await fs.readJson(filePath);
608
+ if (!isRecord(raw) || raw.schemaVersion !== 2 || !isRecord(raw.values)
609
+ || raw.sourceId !== sourceId || raw.pluginName !== pluginName
610
+ || typeof raw.sourceIdentity !== 'string') {
611
+ throw new Error(`legacyStateUnsupported: invalid namespaced plugin config store "${filePath}".`);
612
+ }
613
+ output.push({
614
+ sourceId,
615
+ sourceIdentity: raw.sourceIdentity,
616
+ pluginName,
617
+ pluginId: `${sourceId}/${pluginName}`,
618
+ values: raw.values,
619
+ });
620
+ }
621
+ }
622
+ return output.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
623
+ }
624
+ export async function writePluginConfigStore(options) {
625
+ await assertNoLegacyConfigCoexistence(options);
626
+ const filePath = resolvePluginConfigStorePath({
627
+ ...options,
628
+ ...(options.scope === 'project' ? { access: 'write' } : {}),
629
+ });
630
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
631
+ await withUserPluginConfigRuntimeLease(options, async () => {
632
+ await withConfigFileLock({
633
+ filePath,
634
+ allowedRoot,
635
+ action: async () => atomicReplaceConfigFile({
636
+ filePath,
637
+ allowedRoot,
638
+ content: namespacedConfigContent(options),
639
+ mode: 0o600,
640
+ }),
641
+ });
642
+ });
643
+ return {
644
+ pluginName: options.pluginName,
645
+ scope: options.scope,
646
+ path: filePath,
647
+ };
648
+ }
649
+ export async function patchPluginConfigStore(options) {
650
+ await assertNoLegacyConfigCoexistence(options);
651
+ const filePath = resolvePluginConfigStorePath({
652
+ ...options,
653
+ ...(options.scope === 'project' ? { access: 'write' } : {}),
654
+ });
655
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
656
+ const removedKeys = options.removeKeys ?? [];
657
+ await withUserPluginConfigRuntimeLease(options, async () => {
658
+ await withConfigFileLock({
659
+ filePath,
660
+ allowedRoot,
661
+ action: async () => {
662
+ await options.reviewPreflight?.();
663
+ const current = (await lstatIfExists(filePath))
664
+ ? options.sourceId
665
+ ? await readNamespacedStoreFile({
666
+ filePath,
667
+ sourceId: options.sourceId,
668
+ sourceIdentity: options.sourceIdentity,
669
+ pluginName: options.pluginName,
670
+ })
671
+ : await readStoreFile(filePath)
672
+ : {};
673
+ if (options.requireExistingRemoveKeys) {
674
+ const missingKey = removedKeys.find((key) => !Object.hasOwn(current, key));
675
+ if (missingKey)
676
+ throw new Error(`Unknown config key "${missingKey}".`);
677
+ }
678
+ const next = { ...current };
679
+ for (const key of removedKeys) {
680
+ delete next[key];
681
+ }
682
+ for (const [key, value] of Object.entries(options.updates)) {
683
+ next[key] = value;
684
+ }
685
+ await atomicReplaceConfigFile({
686
+ filePath,
687
+ allowedRoot,
688
+ content: namespacedConfigContent({ ...options, values: next }),
689
+ mode: 0o600,
690
+ });
691
+ },
692
+ });
693
+ });
694
+ return {
695
+ pluginName: options.pluginName,
696
+ scope: options.scope,
697
+ path: filePath,
698
+ updatedKeys: Object.keys(options.updates),
699
+ removedKeys,
700
+ };
701
+ }
702
+ export async function resetPluginConfigStore(options) {
703
+ await assertNoLegacyConfigCoexistence(options);
704
+ const filePath = resolvePluginConfigStorePath({
705
+ ...options,
706
+ ...(options.scope === 'project' ? { access: 'write' } : {}),
707
+ });
708
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
709
+ const existed = await withUserPluginConfigRuntimeLease(options, async () => {
710
+ return withConfigFileLock({
711
+ filePath,
712
+ allowedRoot,
713
+ action: async () => {
714
+ await options.reviewPreflight?.();
715
+ const target = await lstatIfExists(filePath);
716
+ if (!target)
717
+ return false;
718
+ if (!target.isFile() || target.isSymbolicLink()) {
719
+ throw new Error(`Config target must be a regular file: ${filePath}`);
720
+ }
721
+ if (options.sourceId) {
722
+ if (!options.sourceIdentity) {
723
+ throw new Error('sourceIdentity is required for namespaced plugin config.');
724
+ }
725
+ await readNamespacedStoreFile({
726
+ filePath,
727
+ sourceId: options.sourceId,
728
+ sourceIdentity: options.sourceIdentity,
729
+ pluginName: options.pluginName,
730
+ });
731
+ }
732
+ else {
733
+ await readStoreFile(filePath);
734
+ }
735
+ return atomicRemoveConfigFile({ filePath, allowedRoot });
736
+ },
737
+ });
738
+ });
739
+ return {
740
+ pluginName: options.pluginName,
741
+ scope: options.scope,
742
+ storeKind: options.scope === 'user'
743
+ ? 'user'
744
+ : options.projectLayer === 'local'
745
+ ? 'projectLocal'
746
+ : 'projectShared',
747
+ path: filePath,
748
+ existed,
749
+ deleted: existed,
750
+ };
751
+ }
752
+ export async function resetPluginConfigStores(options) {
753
+ await assertNoLegacyConfigCoexistence(options);
754
+ const targets = resolvePluginConfigStoreTargets({
755
+ ...options,
756
+ ...(options.scope === 'project' ? { access: 'write' } : {}),
757
+ });
758
+ const allowedRoot = resolvePluginConfigAllowedRoot(options);
759
+ const receipts = [];
760
+ await withUserPluginConfigRuntimeLease(options, async () => {
761
+ const withTargetLocks = async (index, action) => {
762
+ const target = targets[index];
763
+ if (!target)
764
+ return action();
765
+ return withConfigFileLock({
766
+ filePath: target.path,
767
+ allowedRoot,
768
+ action: () => withTargetLocks(index + 1, action),
769
+ });
770
+ };
771
+ await withTargetLocks(0, async () => {
772
+ const existing = [];
773
+ for (const target of targets) {
774
+ const stats = await lstatIfExists(target.path);
775
+ existing.push(stats !== null);
776
+ if (!stats)
777
+ continue;
778
+ if (!stats.isFile() || stats.isSymbolicLink()) {
779
+ throw new Error(`Config target must be a regular file: ${target.path}`);
780
+ }
781
+ if (options.sourceId) {
782
+ if (!options.sourceIdentity) {
783
+ throw new Error('sourceIdentity is required for namespaced plugin config.');
784
+ }
785
+ await readNamespacedStoreFile({
786
+ filePath: target.path,
787
+ sourceId: options.sourceId,
788
+ sourceIdentity: options.sourceIdentity,
789
+ pluginName: options.pluginName,
790
+ });
791
+ }
792
+ else {
793
+ await readStoreFile(target.path);
794
+ }
795
+ }
796
+ for (const [index, target] of targets.entries()) {
797
+ const deleted = await atomicRemoveConfigFile({
798
+ filePath: target.path,
799
+ allowedRoot,
800
+ });
801
+ receipts.push({
802
+ pluginName: options.pluginName,
803
+ scope: options.scope,
804
+ storeKind: target.storeKind,
805
+ path: target.path,
806
+ existed: existing[index],
807
+ deleted,
808
+ });
809
+ }
810
+ });
811
+ });
812
+ return receipts;
813
+ }
814
+ function coercePrimitiveValue(key, field, value) {
815
+ if (field.type === 'string') {
816
+ if (typeof value !== 'string') {
817
+ throw new Error(`Config "${key}" must be a string.`);
818
+ }
819
+ return value;
820
+ }
821
+ if (field.type === 'number') {
822
+ const numberValue = typeof value === 'number'
823
+ ? value
824
+ : typeof value === 'string' && value.trim() !== ''
825
+ ? Number(value)
826
+ : Number.NaN;
827
+ if (!Number.isFinite(numberValue)) {
828
+ throw new Error(`Config "${key}" must be a number.`);
829
+ }
830
+ if (field.min !== undefined && numberValue < field.min) {
831
+ throw new Error(`Config "${key}" must be greater than or equal to ${field.min}.`);
832
+ }
833
+ if (field.max !== undefined && numberValue > field.max) {
834
+ throw new Error(`Config "${key}" must be less than or equal to ${field.max}.`);
835
+ }
836
+ return numberValue;
837
+ }
838
+ if (field.type === 'boolean') {
839
+ if (typeof value === 'boolean') {
840
+ return value;
841
+ }
842
+ if (typeof value === 'string') {
843
+ const normalized = value.trim().toLowerCase();
844
+ if (['true', '1', 'yes', 'y'].includes(normalized)) {
845
+ return true;
846
+ }
847
+ if (['false', '0', 'no', 'n'].includes(normalized)) {
848
+ return false;
849
+ }
850
+ }
851
+ throw new Error(`Config "${key}" must be a boolean.`);
852
+ }
853
+ if (field.type === 'enum') {
854
+ if (typeof value !== 'string') {
855
+ throw new Error(`Config "${key}" must be an enum string.`);
856
+ }
857
+ if (!field.values?.includes(value)) {
858
+ throw new Error(`Config "${key}" must be one of: ${(field.values ?? []).join(', ')}.`);
859
+ }
860
+ return value;
861
+ }
862
+ if (field.type === 'file' || field.type === 'directory') {
863
+ if (typeof value !== 'string' || value.trim() === '') {
864
+ throw new Error(`Config "${key}" must be a path string.`);
865
+ }
866
+ return value;
867
+ }
868
+ throw new Error(`Config "${key}" has unsupported type "${field.type}".`);
869
+ }
870
+ async function resolvePathConfigValue(options) {
871
+ if (options.field.type !== 'file' && options.field.type !== 'directory') {
872
+ return String(options.value);
873
+ }
874
+ const rawPath = String(options.value);
875
+ const resolvedPath = path.isAbsolute(rawPath)
876
+ ? rawPath
877
+ : path.resolve(options.projectRoot, rawPath);
878
+ const realPath = await fs.realpath(resolvedPath);
879
+ const stats = await fs.stat(realPath);
880
+ if (options.field.type === 'file' && !stats.isFile()) {
881
+ throw new Error(`Config "${options.key}" must point to an existing file.`);
882
+ }
883
+ if (options.field.type === 'directory' && !stats.isDirectory()) {
884
+ throw new Error(`Config "${options.key}" must point to an existing directory.`);
885
+ }
886
+ return realPath;
887
+ }
888
+ export async function coercePluginConfigValue(options) {
889
+ const primitive = coercePrimitiveValue(options.key, options.field, options.value);
890
+ if (options.field.type === 'file' || options.field.type === 'directory') {
891
+ return resolvePathConfigValue({
892
+ key: options.key,
893
+ field: options.field,
894
+ value: primitive,
895
+ projectRoot: options.projectRoot,
896
+ });
897
+ }
898
+ return primitive;
899
+ }
900
+ function redactValue(field, value) {
901
+ return field.sensitive ? '[redacted]' : value;
902
+ }
903
+ function sourceForStoreLayer(scope, projectLayer = 'shared') {
904
+ if (scope === 'user') {
905
+ return 'user';
906
+ }
907
+ return projectLayer === 'local' ? 'projectLocal' : 'project';
908
+ }
909
+ function isPathTypeField(field) {
910
+ return field.type === 'file' || field.type === 'directory';
911
+ }
912
+ async function applyConfigLayer(options) {
913
+ for (const [key, field] of Object.entries(options.schema)) {
914
+ if (!Object.hasOwn(options.entries, key)) {
915
+ continue;
916
+ }
917
+ try {
918
+ const coerced = await coercePluginConfigValue({
919
+ key,
920
+ field,
921
+ value: options.entries[key],
922
+ projectRoot: options.projectRoot,
923
+ });
924
+ options.values[key] = coerced;
925
+ options.redactedValues[key] = redactValue(field, coerced);
926
+ options.sources[key] = options.source;
927
+ options.invalidIssues.delete(key);
928
+ }
929
+ catch (error) {
930
+ delete options.values[key];
931
+ delete options.redactedValues[key];
932
+ options.sources[key] = options.source;
933
+ options.invalidIssues.set(key, {
934
+ type: 'invalid-config',
935
+ key,
936
+ source: options.source,
937
+ message: error instanceof Error ? error.message : String(error),
938
+ });
939
+ }
940
+ }
941
+ }
942
+ export async function resolveEffectivePluginConfig(options) {
943
+ const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
944
+ const schema = options.manifest.config?.schema;
945
+ if (!schema) {
946
+ throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
947
+ }
948
+ const projectRoot = resolveAwProjectRoot(options.cwd);
949
+ const [userStore, projectStore, projectLocalStore] = await Promise.all([
950
+ readPluginConfigStore({
951
+ sourceId: options.sourceId,
952
+ sourceIdentity: options.sourceIdentity,
953
+ pluginName,
954
+ scope: 'user',
955
+ cwd: options.cwd,
956
+ env: options.env,
957
+ }),
958
+ readPluginConfigStore({
959
+ sourceId: options.sourceId,
960
+ sourceIdentity: options.sourceIdentity,
961
+ pluginName,
962
+ scope: 'project',
963
+ cwd: options.cwd,
964
+ env: options.env,
965
+ }),
966
+ readPluginConfigStore({
967
+ sourceId: options.sourceId,
968
+ sourceIdentity: options.sourceIdentity,
969
+ pluginName,
970
+ scope: 'project',
971
+ cwd: options.cwd,
972
+ env: options.env,
973
+ projectLayer: 'local',
974
+ }),
975
+ ]);
976
+ const values = {};
977
+ const redactedValues = {};
978
+ const sources = {};
979
+ const invalidIssues = new Map();
980
+ const ignoredUnknownKeys = [];
981
+ const schemaKeys = new Set(Object.keys(schema));
982
+ for (const key of schemaKeys) {
983
+ sources[key] = 'missing';
984
+ }
985
+ for (const [key, field] of Object.entries(schema)) {
986
+ if (field.default === undefined) {
987
+ continue;
988
+ }
989
+ await applyConfigLayer({
990
+ values,
991
+ redactedValues,
992
+ sources,
993
+ invalidIssues,
994
+ schema: { [key]: field },
995
+ entries: { [key]: field.default },
996
+ source: 'default',
997
+ projectRoot,
998
+ });
999
+ }
1000
+ for (const [scope, store, projectLayer] of [
1001
+ ['user', userStore, 'shared'],
1002
+ ['project', projectStore, 'shared'],
1003
+ ['project', projectLocalStore, 'local'],
1004
+ ]) {
1005
+ const plainEntries = getStoreEntries(store.values);
1006
+ for (const key of Object.keys(plainEntries)) {
1007
+ if (!schemaKeys.has(key)) {
1008
+ ignoredUnknownKeys.push({
1009
+ scope,
1010
+ projectLayer,
1011
+ source: sourceForStoreLayer(scope, projectLayer),
1012
+ key,
1013
+ });
1014
+ }
1015
+ }
1016
+ await applyConfigLayer({
1017
+ values,
1018
+ redactedValues,
1019
+ sources,
1020
+ invalidIssues,
1021
+ schema,
1022
+ entries: plainEntries,
1023
+ source: sourceForStoreLayer(scope, projectLayer),
1024
+ projectRoot,
1025
+ });
1026
+ }
1027
+ const issues = [...invalidIssues.values()];
1028
+ for (const [key, field] of Object.entries(schema)) {
1029
+ if (field.required && values[key] === undefined) {
1030
+ issues.push({
1031
+ type: 'missing-required',
1032
+ key,
1033
+ source: sources[key],
1034
+ message: `Config "${key}" is required.`,
1035
+ });
1036
+ }
1037
+ }
1038
+ return {
1039
+ pluginName,
1040
+ projectRoot,
1041
+ values,
1042
+ redactedValues,
1043
+ sources,
1044
+ issues,
1045
+ ignoredUnknownKeys,
1046
+ schema,
1047
+ stores: {
1048
+ user: userStore,
1049
+ project: projectStore,
1050
+ projectLocal: projectLocalStore,
1051
+ },
1052
+ };
1053
+ }
1054
+ export async function coercePluginConfigUpdates(options) {
1055
+ const projectRoot = resolveAwProjectRoot(options.cwd);
1056
+ const output = {};
1057
+ for (const [key, value] of Object.entries(options.updates)) {
1058
+ const field = options.schema[key];
1059
+ if (!field) {
1060
+ throw new Error(`Unknown config key "${key}".`);
1061
+ }
1062
+ output[key] = await coercePluginConfigValue({
1063
+ key,
1064
+ field,
1065
+ value,
1066
+ projectRoot,
1067
+ });
1068
+ }
1069
+ return output;
1070
+ }
1071
+ function createProjectArtifactBoundaryIssue(options) {
1072
+ if (options.reason === 'pathTypeDefault') {
1073
+ return {
1074
+ key: options.key,
1075
+ source: options.source,
1076
+ reason: options.reason,
1077
+ message: `Config "${options.key}" uses file/directory default value, ` +
1078
+ 'which cannot participate in repo-visible project artifact truth.',
1079
+ };
1080
+ }
1081
+ if (options.reason === 'pathTypeRequired') {
1082
+ return {
1083
+ key: options.key,
1084
+ source: options.source,
1085
+ reason: options.reason,
1086
+ message: `Config "${options.key}" is a required file/directory key, ` +
1087
+ 'which is not portable enough for repo-visible project artifacts.',
1088
+ };
1089
+ }
1090
+ if (options.reason === 'sharedPathValue') {
1091
+ return {
1092
+ key: options.key,
1093
+ source: options.source,
1094
+ reason: options.reason,
1095
+ message: `Config "${options.key}" is stored in project shared config as a file/directory path. ` +
1096
+ 'Use user or project local config for machine-local paths.',
1097
+ };
1098
+ }
1099
+ return {
1100
+ key: options.key,
1101
+ source: options.source,
1102
+ reason: options.reason,
1103
+ message: `Config "${options.key}" is declared as a refresh key for repo-visible project artifacts, ` +
1104
+ 'but file/directory config is machine-local and cannot drive shared artifact refresh.',
1105
+ };
1106
+ }
1107
+ export function collectProjectArtifactConfigBoundaryIssues(options) {
1108
+ const issues = [];
1109
+ const refreshKeySet = new Set(options.refreshKeys ?? []);
1110
+ for (const [key, field] of Object.entries(options.schema)) {
1111
+ if (!isPathTypeField(field)) {
1112
+ continue;
1113
+ }
1114
+ if (field.default !== undefined) {
1115
+ issues.push(createProjectArtifactBoundaryIssue({
1116
+ key,
1117
+ source: 'default',
1118
+ reason: 'pathTypeDefault',
1119
+ }));
1120
+ }
1121
+ if (field.required) {
1122
+ issues.push(createProjectArtifactBoundaryIssue({
1123
+ key,
1124
+ source: 'missing',
1125
+ reason: 'pathTypeRequired',
1126
+ }));
1127
+ }
1128
+ if (options.sharedEntries && Object.hasOwn(options.sharedEntries, key)) {
1129
+ issues.push(createProjectArtifactBoundaryIssue({
1130
+ key,
1131
+ source: 'project',
1132
+ reason: 'sharedPathValue',
1133
+ }));
1134
+ }
1135
+ if (refreshKeySet.has(key)) {
1136
+ issues.push(createProjectArtifactBoundaryIssue({
1137
+ key,
1138
+ source: 'project',
1139
+ reason: 'pathTypeRefreshKey',
1140
+ }));
1141
+ }
1142
+ }
1143
+ return issues;
1144
+ }
1145
+ export async function resolveProjectArtifactRenderConfig(options) {
1146
+ const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
1147
+ const schema = options.manifest.config?.schema;
1148
+ const projectRoot = resolveAwProjectRoot(options.cwd);
1149
+ const [userStore, projectStore, projectLocalStore] = await Promise.all([
1150
+ readPluginConfigStore({
1151
+ sourceId: options.sourceId,
1152
+ sourceIdentity: options.sourceIdentity,
1153
+ pluginName,
1154
+ scope: 'user',
1155
+ cwd: options.cwd,
1156
+ env: options.env,
1157
+ }),
1158
+ readPluginConfigStore({
1159
+ sourceId: options.sourceId,
1160
+ sourceIdentity: options.sourceIdentity,
1161
+ pluginName,
1162
+ scope: 'project',
1163
+ cwd: options.cwd,
1164
+ env: options.env,
1165
+ }),
1166
+ readPluginConfigStore({
1167
+ sourceId: options.sourceId,
1168
+ sourceIdentity: options.sourceIdentity,
1169
+ pluginName,
1170
+ scope: 'project',
1171
+ cwd: options.cwd,
1172
+ env: options.env,
1173
+ projectLayer: 'local',
1174
+ }),
1175
+ ]);
1176
+ if (!schema) {
1177
+ return {
1178
+ pluginName,
1179
+ projectRoot,
1180
+ values: {},
1181
+ sources: {},
1182
+ issues: [],
1183
+ ignoredUnknownKeys: [],
1184
+ boundaryIssues: [],
1185
+ schema: {},
1186
+ stores: {
1187
+ user: userStore,
1188
+ project: projectStore,
1189
+ projectLocal: projectLocalStore,
1190
+ },
1191
+ };
1192
+ }
1193
+ const values = {};
1194
+ const sources = {};
1195
+ const invalidIssues = new Map();
1196
+ const ignoredUnknownKeys = [];
1197
+ const schemaKeys = new Set(Object.keys(schema));
1198
+ const boundaryIssues = collectProjectArtifactConfigBoundaryIssues({
1199
+ schema,
1200
+ sharedEntries: projectStore.values,
1201
+ refreshKeys: options.refreshKeys,
1202
+ });
1203
+ for (const key of Object.keys(schema)) {
1204
+ sources[key] = 'missing';
1205
+ }
1206
+ const projectPlainEntries = getStoreEntries(projectStore.values);
1207
+ const userPlainEntries = getStoreEntries(userStore.values);
1208
+ const projectLocalPlainEntries = getStoreEntries(projectLocalStore.values);
1209
+ for (const key of Object.keys(projectPlainEntries)) {
1210
+ if (!schemaKeys.has(key)) {
1211
+ ignoredUnknownKeys.push({ scope: 'project', projectLayer: 'shared', source: 'project', key });
1212
+ }
1213
+ }
1214
+ for (const key of Object.keys(userPlainEntries)) {
1215
+ if (!schemaKeys.has(key)) {
1216
+ ignoredUnknownKeys.push({ scope: 'user', projectLayer: 'shared', source: 'user', key });
1217
+ }
1218
+ }
1219
+ for (const key of Object.keys(projectLocalPlainEntries)) {
1220
+ if (!schemaKeys.has(key)) {
1221
+ ignoredUnknownKeys.push({ scope: 'project', projectLayer: 'local', source: 'projectLocal', key });
1222
+ }
1223
+ }
1224
+ for (const [key, field] of Object.entries(schema)) {
1225
+ if (!isPathTypeField(field) && field.default !== undefined) {
1226
+ await applyConfigLayer({
1227
+ values,
1228
+ redactedValues: {},
1229
+ sources,
1230
+ invalidIssues,
1231
+ schema: { [key]: field },
1232
+ entries: { [key]: field.default },
1233
+ source: 'default',
1234
+ projectRoot,
1235
+ });
1236
+ }
1237
+ }
1238
+ const sharedSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !isPathTypeField(field)));
1239
+ await applyConfigLayer({
1240
+ values,
1241
+ redactedValues: {},
1242
+ sources,
1243
+ invalidIssues,
1244
+ schema: sharedSchema,
1245
+ entries: projectPlainEntries,
1246
+ source: 'project',
1247
+ projectRoot,
1248
+ });
1249
+ const issues = [
1250
+ ...invalidIssues.values(),
1251
+ ...boundaryIssues.map((issue) => ({
1252
+ type: 'invalid-config',
1253
+ key: issue.key,
1254
+ source: issue.source,
1255
+ message: issue.message,
1256
+ })),
1257
+ ];
1258
+ for (const [key, field] of Object.entries(schema)) {
1259
+ if (isPathTypeField(field)) {
1260
+ continue;
1261
+ }
1262
+ if (field.required && values[key] === undefined) {
1263
+ issues.push({
1264
+ type: 'missing-required',
1265
+ key,
1266
+ source: sources[key],
1267
+ message: `Config "${key}" is required.`,
1268
+ });
1269
+ }
1270
+ }
1271
+ return {
1272
+ pluginName,
1273
+ projectRoot,
1274
+ values,
1275
+ sources,
1276
+ issues,
1277
+ ignoredUnknownKeys,
1278
+ boundaryIssues,
1279
+ schema,
1280
+ stores: {
1281
+ user: userStore,
1282
+ project: projectStore,
1283
+ projectLocal: projectLocalStore,
1284
+ },
1285
+ };
1286
+ }
1287
+ export async function resolveProjectLocalPluginConfigView(options) {
1288
+ const effective = await resolveEffectivePluginConfig(options);
1289
+ const artifactConfig = await resolveProjectArtifactRenderConfig(options);
1290
+ const values = {};
1291
+ const redactedValues = {};
1292
+ const sources = {};
1293
+ const issues = [];
1294
+ for (const [key, field] of Object.entries(effective.schema)) {
1295
+ sources[key] = 'missing';
1296
+ const localPlainEntries = getStoreEntries(effective.stores.projectLocal.values);
1297
+ if (!Object.hasOwn(localPlainEntries, key)) {
1298
+ continue;
1299
+ }
1300
+ sources[key] = 'projectLocal';
1301
+ try {
1302
+ const coerced = await coercePluginConfigValue({
1303
+ key,
1304
+ field,
1305
+ value: localPlainEntries[key],
1306
+ projectRoot: effective.projectRoot,
1307
+ });
1308
+ values[key] = coerced;
1309
+ redactedValues[key] = redactValue(field, coerced);
1310
+ }
1311
+ catch (error) {
1312
+ issues.push({
1313
+ type: 'invalid-config',
1314
+ key,
1315
+ source: 'projectLocal',
1316
+ message: error instanceof Error ? error.message : String(error),
1317
+ });
1318
+ }
1319
+ }
1320
+ return {
1321
+ pluginName: effective.pluginName,
1322
+ projectRoot: effective.projectRoot,
1323
+ schema: effective.schema,
1324
+ localStore: effective.stores.projectLocal,
1325
+ values,
1326
+ redactedValues,
1327
+ sources,
1328
+ issues,
1329
+ ignoredUnknownKeys: effective.ignoredUnknownKeys.filter((entry) => entry.source === 'projectLocal'),
1330
+ repoVisibleArtifactConfig: {
1331
+ values: artifactConfig.values,
1332
+ sources: artifactConfig.sources,
1333
+ issues: artifactConfig.issues,
1334
+ boundaryIssues: artifactConfig.boundaryIssues,
1335
+ },
1336
+ };
1337
+ }
1338
+ async function resolveRedactedLayerValue(options) {
1339
+ try {
1340
+ const coerced = await coercePluginConfigValue({
1341
+ key: options.key,
1342
+ field: options.field,
1343
+ value: options.value,
1344
+ projectRoot: options.projectRoot,
1345
+ });
1346
+ return {
1347
+ value: redactValue(options.field, coerced),
1348
+ };
1349
+ }
1350
+ catch (error) {
1351
+ return {
1352
+ issue: {
1353
+ type: 'invalid-config',
1354
+ key: options.key,
1355
+ source: options.source,
1356
+ message: error instanceof Error ? error.message : String(error),
1357
+ },
1358
+ };
1359
+ }
1360
+ }
1361
+ export async function resolveProjectScopedPluginConfigView(options) {
1362
+ const effective = await resolveEffectivePluginConfig(options);
1363
+ const artifactConfig = await resolveProjectArtifactRenderConfig(options);
1364
+ const entries = {};
1365
+ for (const [key, field] of Object.entries(effective.schema)) {
1366
+ const entry = {
1367
+ key,
1368
+ effectiveValue: effective.redactedValues[key],
1369
+ effectiveSource: effective.sources[key] ?? 'missing',
1370
+ repoVisibleArtifactValue: artifactConfig.values[key] === undefined
1371
+ ? undefined
1372
+ : redactValue(field, artifactConfig.values[key]),
1373
+ repoVisibleArtifactSource: artifactConfig.sources[key] ?? 'missing',
1374
+ repoVisibleArtifactUsesSharedTruth: true,
1375
+ };
1376
+ if (field.default !== undefined) {
1377
+ const resolvedDefault = await resolveRedactedLayerValue({
1378
+ key,
1379
+ field,
1380
+ projectRoot: effective.projectRoot,
1381
+ source: 'default',
1382
+ value: field.default,
1383
+ });
1384
+ if (resolvedDefault.value !== undefined) {
1385
+ entry.defaultValue = resolvedDefault.value;
1386
+ }
1387
+ }
1388
+ const userPlainEntries = getStoreEntries(effective.stores.user.values);
1389
+ if (Object.hasOwn(userPlainEntries, key)) {
1390
+ const resolvedUser = await resolveRedactedLayerValue({
1391
+ key,
1392
+ field,
1393
+ projectRoot: effective.projectRoot,
1394
+ source: 'user',
1395
+ value: userPlainEntries[key],
1396
+ });
1397
+ if (resolvedUser.value !== undefined) {
1398
+ entry.userValue = resolvedUser.value;
1399
+ }
1400
+ }
1401
+ const projectPlainEntries = getStoreEntries(effective.stores.project.values);
1402
+ if (Object.hasOwn(projectPlainEntries, key)) {
1403
+ const resolvedProject = await resolveRedactedLayerValue({
1404
+ key,
1405
+ field,
1406
+ projectRoot: effective.projectRoot,
1407
+ source: 'project',
1408
+ value: projectPlainEntries[key],
1409
+ });
1410
+ if (resolvedProject.value !== undefined) {
1411
+ entry.projectSharedValue = resolvedProject.value;
1412
+ }
1413
+ }
1414
+ const localPlainEntries = getStoreEntries(effective.stores.projectLocal.values);
1415
+ if (Object.hasOwn(localPlainEntries, key)) {
1416
+ const resolvedLocal = await resolveRedactedLayerValue({
1417
+ key,
1418
+ field,
1419
+ projectRoot: effective.projectRoot,
1420
+ source: 'projectLocal',
1421
+ value: localPlainEntries[key],
1422
+ });
1423
+ if (resolvedLocal.value !== undefined) {
1424
+ entry.projectLocalValue = resolvedLocal.value;
1425
+ }
1426
+ }
1427
+ entries[key] = entry;
1428
+ }
1429
+ return {
1430
+ pluginName: effective.pluginName,
1431
+ projectRoot: effective.projectRoot,
1432
+ schema: effective.schema,
1433
+ stores: effective.stores,
1434
+ redactedValues: effective.redactedValues,
1435
+ sources: effective.sources,
1436
+ entries,
1437
+ issues: effective.issues,
1438
+ ignoredUnknownKeys: effective.ignoredUnknownKeys,
1439
+ repoVisibleArtifactConfig: {
1440
+ values: artifactConfig.values,
1441
+ sources: artifactConfig.sources,
1442
+ issues: artifactConfig.issues,
1443
+ boundaryIssues: artifactConfig.boundaryIssues,
1444
+ },
1445
+ };
1446
+ }
1447
+ function toConfigSourceLayer(source) {
1448
+ if (source === 'default') {
1449
+ return 'default';
1450
+ }
1451
+ if (source === 'user') {
1452
+ return 'user';
1453
+ }
1454
+ if (source === 'project') {
1455
+ return 'project';
1456
+ }
1457
+ if (source === 'projectLocal') {
1458
+ return 'local';
1459
+ }
1460
+ return null;
1461
+ }
1462
+ async function buildManifestFieldEntries(options) {
1463
+ const schema = options.plugin.manifest.config?.schema;
1464
+ if (!schema) {
1465
+ return [];
1466
+ }
1467
+ const projectRoot = resolveAwProjectRoot(options.cwd);
1468
+ const [userStore, projectStore, projectLocalStore] = await Promise.all([
1469
+ readPluginConfigStore({
1470
+ sourceId: options.plugin.sourceId,
1471
+ sourceIdentity: options.plugin.sourceIdentity,
1472
+ pluginName: options.plugin.manifest.name,
1473
+ scope: 'user',
1474
+ cwd: options.cwd,
1475
+ env: options.env,
1476
+ }),
1477
+ readPluginConfigStore({
1478
+ sourceId: options.plugin.sourceId,
1479
+ sourceIdentity: options.plugin.sourceIdentity,
1480
+ pluginName: options.plugin.manifest.name,
1481
+ scope: 'project',
1482
+ projectLayer: 'shared',
1483
+ cwd: options.cwd,
1484
+ env: options.env,
1485
+ }),
1486
+ readPluginConfigStore({
1487
+ sourceId: options.plugin.sourceId,
1488
+ sourceIdentity: options.plugin.sourceIdentity,
1489
+ pluginName: options.plugin.manifest.name,
1490
+ scope: 'project',
1491
+ projectLayer: 'local',
1492
+ cwd: options.cwd,
1493
+ env: options.env,
1494
+ }),
1495
+ ]);
1496
+ const userPlainEntries = getStoreEntries(userStore.values);
1497
+ const projectPlainEntries = getStoreEntries(projectStore.values);
1498
+ const localPlainEntries = getStoreEntries(projectLocalStore.values);
1499
+ const effective = await resolveEffectivePluginConfig({
1500
+ sourceId: options.plugin.sourceId,
1501
+ sourceIdentity: options.plugin.sourceIdentity,
1502
+ pluginName: options.plugin.manifest.name,
1503
+ manifest: options.plugin.manifest,
1504
+ cwd: options.cwd,
1505
+ env: options.env,
1506
+ });
1507
+ const fields = [];
1508
+ for (const [key, field] of Object.entries(schema)) {
1509
+ const base = {
1510
+ key,
1511
+ title: field.title ?? key,
1512
+ kind: 'manifest',
1513
+ reserved: false,
1514
+ writable: true,
1515
+ valueState: 'default',
1516
+ effectiveValue: null,
1517
+ sourceLayer: null,
1518
+ };
1519
+ if (options.scope === 'user') {
1520
+ if (Object.hasOwn(userPlainEntries, key)) {
1521
+ try {
1522
+ const value = await coercePluginConfigValue({
1523
+ key,
1524
+ field,
1525
+ value: userPlainEntries[key],
1526
+ projectRoot,
1527
+ });
1528
+ fields.push({
1529
+ ...base,
1530
+ valueState: 'configured',
1531
+ effectiveValue: redactValue(field, value),
1532
+ sourceLayer: 'user',
1533
+ storedValue: redactValue(field, value),
1534
+ storedSourceLayer: 'user',
1535
+ });
1536
+ }
1537
+ catch {
1538
+ fields.push({
1539
+ ...base,
1540
+ valueState: 'invalid',
1541
+ sourceLayer: 'user',
1542
+ });
1543
+ }
1544
+ continue;
1545
+ }
1546
+ if (field.default !== undefined) {
1547
+ try {
1548
+ const value = await coercePluginConfigValue({
1549
+ key,
1550
+ field,
1551
+ value: field.default,
1552
+ projectRoot,
1553
+ });
1554
+ fields.push({
1555
+ ...base,
1556
+ effectiveValue: redactValue(field, value),
1557
+ sourceLayer: 'default',
1558
+ });
1559
+ }
1560
+ catch {
1561
+ fields.push({
1562
+ ...base,
1563
+ valueState: 'invalid',
1564
+ sourceLayer: 'default',
1565
+ });
1566
+ }
1567
+ continue;
1568
+ }
1569
+ fields.push(base);
1570
+ continue;
1571
+ }
1572
+ if (options.projectLayer === 'local') {
1573
+ if (Object.hasOwn(localPlainEntries, key)) {
1574
+ try {
1575
+ const value = await coercePluginConfigValue({
1576
+ key,
1577
+ field,
1578
+ value: localPlainEntries[key],
1579
+ projectRoot,
1580
+ });
1581
+ fields.push({
1582
+ ...base,
1583
+ valueState: 'configured',
1584
+ effectiveValue: redactValue(field, value),
1585
+ sourceLayer: 'local',
1586
+ storedValue: redactValue(field, value),
1587
+ storedSourceLayer: 'local',
1588
+ });
1589
+ }
1590
+ catch {
1591
+ fields.push({
1592
+ ...base,
1593
+ valueState: 'invalid',
1594
+ sourceLayer: 'local',
1595
+ });
1596
+ }
1597
+ }
1598
+ else {
1599
+ fields.push(base);
1600
+ }
1601
+ continue;
1602
+ }
1603
+ let storedProjectValue;
1604
+ if (options.scope === 'project' && Object.hasOwn(projectPlainEntries, key)) {
1605
+ try {
1606
+ const value = await coercePluginConfigValue({
1607
+ key,
1608
+ field,
1609
+ value: projectPlainEntries[key],
1610
+ projectRoot,
1611
+ });
1612
+ storedProjectValue = {
1613
+ storedValue: redactValue(field, value),
1614
+ storedSourceLayer: 'project',
1615
+ };
1616
+ }
1617
+ catch {
1618
+ storedProjectValue = undefined;
1619
+ }
1620
+ }
1621
+ const source = effective.sources[key];
1622
+ const layer = toConfigSourceLayer(source);
1623
+ if (source === 'default') {
1624
+ fields.push({
1625
+ ...base,
1626
+ effectiveValue: effective.redactedValues[key] ?? null,
1627
+ sourceLayer: 'default',
1628
+ ...storedProjectValue,
1629
+ });
1630
+ continue;
1631
+ }
1632
+ if (source === 'user' || source === 'project' || source === 'projectLocal') {
1633
+ const issue = effective.issues.find((entry) => entry.key === key && entry.source === source);
1634
+ fields.push({
1635
+ ...base,
1636
+ valueState: issue ? 'invalid' : 'configured',
1637
+ effectiveValue: issue ? null : effective.redactedValues[key] ?? null,
1638
+ sourceLayer: layer,
1639
+ ...storedProjectValue,
1640
+ });
1641
+ continue;
1642
+ }
1643
+ fields.push({ ...base, ...storedProjectValue });
1644
+ }
1645
+ return fields;
1646
+ }
1647
+ async function buildLegacyStoreFieldEntries(options) {
1648
+ const locations = options.scope === 'effective'
1649
+ ? [
1650
+ { scope: 'user', projectLayer: 'shared', sourceLayer: 'user' },
1651
+ { scope: 'project', projectLayer: 'shared', sourceLayer: 'project' },
1652
+ { scope: 'project', projectLayer: 'local', sourceLayer: 'local' },
1653
+ ]
1654
+ : [{
1655
+ scope: options.scope,
1656
+ projectLayer: options.projectLayer,
1657
+ sourceLayer: options.scope === 'user'
1658
+ ? 'user'
1659
+ : options.projectLayer === 'local' ? 'local' : 'project',
1660
+ }];
1661
+ const fields = new Map();
1662
+ for (const location of locations) {
1663
+ const store = await readPluginConfigStore({
1664
+ sourceId: options.sourceId,
1665
+ sourceIdentity: options.sourceIdentity,
1666
+ pluginName: options.pluginName,
1667
+ scope: location.scope,
1668
+ ...(location.scope === 'project' ? { projectLayer: location.projectLayer } : {}),
1669
+ cwd: options.cwd,
1670
+ env: options.env,
1671
+ });
1672
+ for (const key of Object.keys(store.values)) {
1673
+ fields.set(key, {
1674
+ key,
1675
+ title: key,
1676
+ kind: 'manifest',
1677
+ reserved: false,
1678
+ writable: false,
1679
+ valueState: 'configured',
1680
+ effectiveValue: '[redacted]',
1681
+ sourceLayer: location.sourceLayer,
1682
+ });
1683
+ }
1684
+ }
1685
+ return [...fields.values()].sort((left, right) => left.key.localeCompare(right.key));
1686
+ }
1687
+ export async function resolvePluginConfigSchemaAccess(options) {
1688
+ const pluginName = assertSafePluginConfigSegment(options.pluginName);
1689
+ if (options.currentPluginError) {
1690
+ throw options.currentPluginError;
1691
+ }
1692
+ if (!options.currentPlugin) {
1693
+ return {
1694
+ pluginName,
1695
+ plugin: null,
1696
+ };
1697
+ }
1698
+ return {
1699
+ pluginName,
1700
+ plugin: options.currentPlugin,
1701
+ schema: createConfigSchemaEvidenceFromCurrentCatalog(options.currentPlugin, await calculateConfigSchemaManifestDigestForPlugin(options.currentPlugin)),
1702
+ };
1703
+ }
1704
+ export async function resolveConfigRead(options) {
1705
+ const pluginName = assertSafePluginConfigSegment(options.pluginName);
1706
+ const schemaAccess = await resolvePluginConfigSchemaAccess({
1707
+ pluginName,
1708
+ currentPlugin: options.plugin,
1709
+ currentPluginError: options.pluginError,
1710
+ scope: options.scope,
1711
+ projectLayer: options.projectLayer,
1712
+ cwd: options.cwd,
1713
+ env: options.env,
1714
+ });
1715
+ const manifestFields = schemaAccess.plugin
1716
+ ? await buildManifestFieldEntries({
1717
+ plugin: schemaAccess.plugin,
1718
+ scope: options.scope,
1719
+ projectLayer: options.projectLayer,
1720
+ cwd: options.cwd,
1721
+ env: options.env,
1722
+ })
1723
+ : await buildLegacyStoreFieldEntries({
1724
+ sourceId: options.sourceId,
1725
+ sourceIdentity: options.sourceIdentity,
1726
+ pluginName,
1727
+ scope: options.scope,
1728
+ projectLayer: options.projectLayer,
1729
+ cwd: options.cwd,
1730
+ env: options.env,
1731
+ });
1732
+ return {
1733
+ pluginName,
1734
+ scope: options.scope,
1735
+ projectLayer: options.projectLayer,
1736
+ ...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
1737
+ fields: manifestFields,
1738
+ };
1739
+ }
1740
+ export function findConfigFieldEntry(fields, key) {
1741
+ return fields.find((field) => field.key === key) ?? null;
1742
+ }
1743
+ export function hasConfigReadContent(result) {
1744
+ return result.fields.length > 0;
1745
+ }
1746
+ export function summarizeEffectivePluginConfig(config) {
1747
+ return {
1748
+ values: config.redactedValues,
1749
+ sources: config.sources,
1750
+ issues: config.issues,
1751
+ ignoredUnknownKeys: config.ignoredUnknownKeys,
1752
+ };
1753
+ }
1754
+ export function createPluginConfigSchemaIdentity(manifest) {
1755
+ const hash = crypto
1756
+ .createHash('sha256')
1757
+ .update(JSON.stringify({
1758
+ name: manifest.name,
1759
+ version: manifest.version,
1760
+ config: manifest.config ?? null,
1761
+ }))
1762
+ .digest('hex');
1763
+ return `${manifest.name}:${hash}`;
1764
+ }
1765
+ export function toCliConfigPromptSchema(schema) {
1766
+ const promptSchema = {};
1767
+ for (const [key, field] of Object.entries(schema)) {
1768
+ promptSchema[key] = {
1769
+ type: field.type === 'enum' ? 'string' : field.type,
1770
+ label: field.title ?? key,
1771
+ description: field.description,
1772
+ required: field.required,
1773
+ sensitive: field.sensitive,
1774
+ default: field.default,
1775
+ enum: field.type === 'enum' ? field.values : undefined,
1776
+ min: field.min,
1777
+ max: field.max,
1778
+ };
1779
+ }
1780
+ return promptSchema;
1781
+ }
1782
+ export function createPluginConfigFormModel(options) {
1783
+ const schema = options.manifest.config?.schema;
1784
+ if (!schema) {
1785
+ throw new Error(`Plugin "${options.pluginName ?? options.manifest.name}" does not declare an AW config schema.`);
1786
+ }
1787
+ const requiredKeys = [...new Set(options.effective.issues
1788
+ .filter((issue) => issue.type === 'missing-required' || issue.type === 'invalid-config')
1789
+ .map((issue) => issue.key))];
1790
+ return {
1791
+ pluginName: options.pluginName ?? options.manifest.name,
1792
+ allowedScopes: options.allowedScopes ?? ['user', 'project'],
1793
+ initialScope: options.initialScope,
1794
+ requiredKeys,
1795
+ fields: Object.entries(schema).map(([key, field]) => ({
1796
+ key,
1797
+ type: field.type,
1798
+ label: field.title ?? key,
1799
+ description: field.description,
1800
+ required: field.required === true,
1801
+ sensitive: field.sensitive === true,
1802
+ defaultValue: field.default,
1803
+ enumValues: field.type === 'enum' ? field.values : undefined,
1804
+ min: field.min,
1805
+ max: field.max,
1806
+ configured: options.effective.values[key] !== undefined,
1807
+ currentValue: field.sensitive || isPathTypeField(field)
1808
+ ? undefined
1809
+ : options.effective.values[key],
1810
+ })),
1811
+ };
1812
+ }
1813
+ export function toCliConfigPromptOptions(model) {
1814
+ const currentValues = {};
1815
+ const configuredKeys = [];
1816
+ for (const field of model.fields) {
1817
+ if (field.configured) {
1818
+ configuredKeys.push(field.key);
1819
+ }
1820
+ if (field.currentValue !== undefined) {
1821
+ currentValues[field.key] = field.currentValue;
1822
+ }
1823
+ }
1824
+ return {
1825
+ pluginName: model.pluginName,
1826
+ schema: Object.fromEntries(model.fields.map((field) => [
1827
+ field.key,
1828
+ {
1829
+ type: field.type === 'enum' ? 'string' : field.type,
1830
+ label: field.label,
1831
+ description: field.description,
1832
+ required: field.required,
1833
+ sensitive: field.sensitive,
1834
+ default: field.defaultValue,
1835
+ enum: field.enumValues,
1836
+ min: field.min,
1837
+ max: field.max,
1838
+ },
1839
+ ])),
1840
+ initialScope: model.initialScope,
1841
+ allowedScopes: [...model.allowedScopes],
1842
+ currentValues,
1843
+ configuredKeys,
1844
+ requiredKeys: [...model.requiredKeys],
1845
+ };
1846
+ }
1847
+ function formatRequiredConfigSetCommands(pluginName, issues, defaultScope) {
1848
+ const commands = new Set();
1849
+ for (const issue of issues) {
1850
+ const scope = issue.source === 'user' || issue.source === 'project'
1851
+ ? issue.source
1852
+ : defaultScope;
1853
+ commands.add(`aw config set ${pluginName} ${issue.key} <value> -s ${scope}`);
1854
+ }
1855
+ return [...commands].map((command) => `\`${command}\``).join(' 或 ');
1856
+ }
1857
+ export async function preflightRequiredPluginConfigs(options) {
1858
+ const visited = new Set();
1859
+ const result = {
1860
+ checked: 0,
1861
+ prompted: 0,
1862
+ written: [],
1863
+ };
1864
+ for (const target of options.targets) {
1865
+ const pluginName = target.pluginName ?? target.manifest.name;
1866
+ const identity = target.schemaSourceIdentity ?? createPluginConfigSchemaIdentity(target.manifest);
1867
+ const dedupeKey = `${pluginName}\0${identity}`;
1868
+ if (visited.has(dedupeKey)) {
1869
+ continue;
1870
+ }
1871
+ visited.add(dedupeKey);
1872
+ result.checked += 1;
1873
+ if (!target.manifest.config?.schema) {
1874
+ continue;
1875
+ }
1876
+ let effective = await resolveEffectivePluginConfig({
1877
+ sourceId: target.sourceId,
1878
+ sourceIdentity: target.sourceIdentity,
1879
+ pluginName,
1880
+ manifest: target.manifest,
1881
+ cwd: options.cwd,
1882
+ env: options.context.env,
1883
+ });
1884
+ const blockingIssues = effective.issues.filter((issue) => issue.type === 'missing-required' || issue.type === 'invalid-config');
1885
+ if (blockingIssues.length === 0) {
1886
+ continue;
1887
+ }
1888
+ const details = blockingIssues.map((issue) => issue.key).join(', ');
1889
+ const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope);
1890
+ throw new Error(`${pluginName} requires valid AW config before install/upgrade: ${details}. ` +
1891
+ `Run ${setCommands}, or open \`aw\` to complete setup.`);
1892
+ }
1893
+ return result;
1894
+ }
1895
+ export function renderPluginConfigValueForDisplay(value) {
1896
+ return formatValue(value);
1897
+ }
1898
+ //# sourceMappingURL=plugin-config.js.map