@lesliechan721/aw 0.1.0-beta.63 → 0.1.0-beta.65

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 (302) hide show
  1. package/dist/authoring/assets/aw-runtime-config.mjs +3 -3
  2. package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -1
  3. package/dist/authoring/scripts/add-skill.mjs +3 -17
  4. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  5. package/dist/authoring/scripts/init-plugin.mjs +1 -0
  6. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  8. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +2 -0
  9. package/dist/authoring/scripts/lib/skill-scaffold.mjs +2 -0
  10. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +95 -698
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +2 -0
  14. package/dist/scripts/aw-skill-eval-materializer.js +74 -44
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -1
  16. package/dist/scripts/bundle-plugins.js +10 -2
  17. package/dist/scripts/bundle-plugins.js.map +1 -1
  18. package/dist/scripts/plugin-staging.d.ts +1 -1
  19. package/dist/scripts/plugin-staging.js +1 -1
  20. package/dist/scripts/plugin-staging.js.map +1 -1
  21. package/dist/scripts/test-plugin-scripts.js +31 -31
  22. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  23. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  24. package/dist/src/commands/aw-capabilities.js +2 -0
  25. package/dist/src/commands/aw-capabilities.js.map +1 -1
  26. package/dist/src/commands/console.js +4 -3
  27. package/dist/src/commands/console.js.map +1 -1
  28. package/dist/src/commands/intent-command.d.ts +30 -0
  29. package/dist/src/commands/intent-command.js +164 -0
  30. package/dist/src/commands/intent-command.js.map +1 -0
  31. package/dist/src/commands/operation-plan.d.ts +2 -0
  32. package/dist/src/commands/operation-plan.js +2 -1
  33. package/dist/src/commands/operation-plan.js.map +1 -1
  34. package/dist/src/commands/options.d.ts +3 -11
  35. package/dist/src/commands/options.js +11 -17
  36. package/dist/src/commands/options.js.map +1 -1
  37. package/dist/src/commands/plugin-capabilities.js +27 -33
  38. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  39. package/dist/src/commands/rebuild-command.d.ts +108 -6
  40. package/dist/src/commands/rebuild-command.js +617 -1549
  41. package/dist/src/commands/rebuild-command.js.map +1 -1
  42. package/dist/src/commands/rebuild-integration.d.ts +19 -3
  43. package/dist/src/commands/rebuild-integration.js +500 -459
  44. package/dist/src/commands/rebuild-integration.js.map +1 -1
  45. package/dist/src/commands/rebuild-operations.js +1 -2
  46. package/dist/src/commands/rebuild-operations.js.map +1 -1
  47. package/dist/src/commands/review-inspection.d.ts +2 -2
  48. package/dist/src/commands/review-inspection.js +30 -86
  49. package/dist/src/commands/review-inspection.js.map +1 -1
  50. package/dist/src/commands/review-preparation.js +16 -7
  51. package/dist/src/commands/review-preparation.js.map +1 -1
  52. package/dist/src/commands/skill-eval.d.ts +2 -0
  53. package/dist/src/commands/skill-eval.js +15 -9
  54. package/dist/src/commands/skill-eval.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +38 -5
  56. package/dist/src/commands/status-v6.js +269 -102
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/targets-command.d.ts +27 -0
  59. package/dist/src/commands/targets-command.js +77 -0
  60. package/dist/src/commands/targets-command.js.map +1 -0
  61. package/dist/src/commands/write-commands.js +5 -5
  62. package/dist/src/commands/write-commands.js.map +1 -1
  63. package/dist/src/eval/manifest-v1.d.ts +2 -0
  64. package/dist/src/eval/manifest-v1.js +7 -2
  65. package/dist/src/eval/manifest-v1.js.map +1 -1
  66. package/dist/src/generators/local-plugin.d.ts +2 -1
  67. package/dist/src/generators/local-plugin.js +111 -108
  68. package/dist/src/generators/local-plugin.js.map +1 -1
  69. package/dist/src/index.d.ts +10 -17
  70. package/dist/src/index.js +585 -1349
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/intent/deployment-preferences.d.ts +63 -0
  73. package/dist/src/intent/deployment-preferences.js +170 -0
  74. package/dist/src/intent/deployment-preferences.js.map +1 -0
  75. package/dist/src/intent/diagnostics.d.ts +11 -0
  76. package/dist/src/intent/diagnostics.js +14 -0
  77. package/dist/src/intent/diagnostics.js.map +1 -0
  78. package/dist/src/intent/intent-codec.d.ts +19 -0
  79. package/dist/src/intent/intent-codec.js +117 -0
  80. package/dist/src/intent/intent-codec.js.map +1 -0
  81. package/dist/src/intent/intent-store.d.ts +43 -0
  82. package/dist/src/intent/intent-store.js +118 -0
  83. package/dist/src/intent/intent-store.js.map +1 -0
  84. package/dist/src/intent/scope-context.d.ts +33 -0
  85. package/dist/src/intent/scope-context.js +145 -0
  86. package/dist/src/intent/scope-context.js.map +1 -0
  87. package/dist/src/intent/work-context.d.ts +13 -0
  88. package/dist/src/intent/work-context.js +22 -0
  89. package/dist/src/intent/work-context.js.map +1 -0
  90. package/dist/src/output/command-receipt.js +16 -9
  91. package/dist/src/output/command-receipt.js.map +1 -1
  92. package/dist/src/portable/commands.d.ts +18 -8
  93. package/dist/src/portable/commands.js +46 -562
  94. package/dist/src/portable/commands.js.map +1 -1
  95. package/dist/src/portable/historical-materialization.d.ts +22 -0
  96. package/dist/src/portable/historical-materialization.js +124 -0
  97. package/dist/src/portable/historical-materialization.js.map +1 -0
  98. package/dist/src/portable/materializer.d.ts +8 -0
  99. package/dist/src/portable/materializer.js +75 -44
  100. package/dist/src/portable/materializer.js.map +1 -1
  101. package/dist/src/portable/recovery-layout.d.ts +17 -0
  102. package/dist/src/portable/recovery-layout.js +55 -0
  103. package/dist/src/portable/recovery-layout.js.map +1 -0
  104. package/dist/src/portable/recovery.d.ts +143 -0
  105. package/dist/src/portable/recovery.js +339 -0
  106. package/dist/src/portable/recovery.js.map +1 -0
  107. package/dist/src/portable/writer.d.ts +64 -0
  108. package/dist/src/portable/writer.js +316 -0
  109. package/dist/src/portable/writer.js.map +1 -0
  110. package/dist/src/recovery/coordinator.d.ts +137 -0
  111. package/dist/src/recovery/coordinator.js +1198 -0
  112. package/dist/src/recovery/coordinator.js.map +1 -0
  113. package/dist/src/recovery/demand.d.ts +44 -0
  114. package/dist/src/recovery/demand.js +178 -0
  115. package/dist/src/recovery/demand.js.map +1 -0
  116. package/dist/src/recovery/joint-coordinator.d.ts +24 -0
  117. package/dist/src/recovery/joint-coordinator.js +770 -0
  118. package/dist/src/recovery/joint-coordinator.js.map +1 -0
  119. package/dist/src/recovery/joint-demand.d.ts +68 -0
  120. package/dist/src/recovery/joint-demand.js +166 -0
  121. package/dist/src/recovery/joint-demand.js.map +1 -0
  122. package/dist/src/recovery/receipt.d.ts +81 -0
  123. package/dist/src/recovery/receipt.js +2 -0
  124. package/dist/src/recovery/receipt.js.map +1 -0
  125. package/dist/src/recovery/retirement.d.ts +45 -0
  126. package/dist/src/recovery/retirement.js +366 -0
  127. package/dist/src/recovery/retirement.js.map +1 -0
  128. package/dist/src/recovery/status.d.ts +108 -0
  129. package/dist/src/recovery/status.js +417 -0
  130. package/dist/src/recovery/status.js.map +1 -0
  131. package/dist/src/recovery/types.d.ts +86 -0
  132. package/dist/src/recovery/types.js +2 -0
  133. package/dist/src/recovery/types.js.map +1 -0
  134. package/dist/src/review/review-profile.d.ts +2 -1
  135. package/dist/src/review/review-profile.js +15 -15
  136. package/dist/src/review/review-profile.js.map +1 -1
  137. package/dist/src/review/review-selection.d.ts +1 -2
  138. package/dist/src/review/review-selection.js +40 -114
  139. package/dist/src/review/review-selection.js.map +1 -1
  140. package/dist/src/review/review-types.d.ts +4 -4
  141. package/dist/src/review/review-types.js.map +1 -1
  142. package/dist/src/review/skill-materialization.js +15 -6
  143. package/dist/src/review/skill-materialization.js.map +1 -1
  144. package/dist/src/review/source-binding.d.ts +14 -4
  145. package/dist/src/review/source-binding.js +62 -117
  146. package/dist/src/review/source-binding.js.map +1 -1
  147. package/dist/src/tui/agent-management.d.ts +23 -0
  148. package/dist/src/tui/agent-management.js +217 -0
  149. package/dist/src/tui/agent-management.js.map +1 -0
  150. package/dist/src/tui/agent-settings.d.ts +44 -0
  151. package/dist/src/tui/agent-settings.js +98 -0
  152. package/dist/src/tui/agent-settings.js.map +1 -0
  153. package/dist/src/tui/components.d.ts +3 -0
  154. package/dist/src/tui/components.js +10 -1
  155. package/dist/src/tui/components.js.map +1 -1
  156. package/dist/src/tui/config-references.d.ts +6 -3
  157. package/dist/src/tui/config-references.js +17 -12
  158. package/dist/src/tui/config-references.js.map +1 -1
  159. package/dist/src/tui/console-app-v4.d.ts +1 -2
  160. package/dist/src/tui/console-app-v4.js +1533 -1047
  161. package/dist/src/tui/console-app-v4.js.map +1 -1
  162. package/dist/src/tui/controller-runtime.d.ts +2 -1
  163. package/dist/src/tui/controller-runtime.js +457 -1634
  164. package/dist/src/tui/controller-runtime.js.map +1 -1
  165. package/dist/src/tui/controller.d.ts +131 -81
  166. package/dist/src/tui/controller.js +307 -227
  167. package/dist/src/tui/controller.js.map +1 -1
  168. package/dist/src/tui/entity-cards.d.ts +2 -2
  169. package/dist/src/tui/entity-cards.js +98 -20
  170. package/dist/src/tui/entity-cards.js.map +1 -1
  171. package/dist/src/tui/glossary.d.ts +2 -0
  172. package/dist/src/tui/glossary.js +20 -0
  173. package/dist/src/tui/glossary.js.map +1 -1
  174. package/dist/src/tui/model.d.ts +28 -10
  175. package/dist/src/tui/model.js +41 -41
  176. package/dist/src/tui/model.js.map +1 -1
  177. package/dist/src/tui/operation-review-planner.d.ts +2 -0
  178. package/dist/src/tui/operation-review-planner.js +58 -122
  179. package/dist/src/tui/operation-review-planner.js.map +1 -1
  180. package/dist/src/tui/plugin-content.js +7 -4
  181. package/dist/src/tui/plugin-content.js.map +1 -1
  182. package/dist/src/tui/plugin-detail.d.ts +22 -3
  183. package/dist/src/tui/plugin-detail.js +113 -26
  184. package/dist/src/tui/plugin-detail.js.map +1 -1
  185. package/dist/src/tui/reader.d.ts +1 -0
  186. package/dist/src/tui/reader.js +3 -1
  187. package/dist/src/tui/reader.js.map +1 -1
  188. package/dist/src/tui/router.d.ts +23 -33
  189. package/dist/src/tui/router.js.map +1 -1
  190. package/dist/src/tui/scoped-flow.d.ts +45 -0
  191. package/dist/src/tui/scoped-flow.js +96 -0
  192. package/dist/src/tui/scoped-flow.js.map +1 -0
  193. package/dist/src/tui/scoped-inventory.d.ts +122 -0
  194. package/dist/src/tui/scoped-inventory.js +225 -0
  195. package/dist/src/tui/scoped-inventory.js.map +1 -0
  196. package/dist/src/tui/screen-registry.d.ts +25 -25
  197. package/dist/src/tui/screen-registry.js +3 -7
  198. package/dist/src/tui/screen-registry.js.map +1 -1
  199. package/dist/src/tui/session-console-app.js +1 -1
  200. package/dist/src/tui/session-console-app.js.map +1 -1
  201. package/dist/src/tui/session-host.js +12 -2
  202. package/dist/src/tui/session-host.js.map +1 -1
  203. package/dist/src/types.d.ts +15 -12
  204. package/dist/src/types.js.map +1 -1
  205. package/dist/src/utils/agent-version.js +2 -0
  206. package/dist/src/utils/agent-version.js.map +1 -1
  207. package/dist/src/utils/artifact-plan.js +13 -6
  208. package/dist/src/utils/artifact-plan.js.map +1 -1
  209. package/dist/src/utils/artifact-renderer.d.ts +6 -3
  210. package/dist/src/utils/artifact-renderer.js +27 -32
  211. package/dist/src/utils/artifact-renderer.js.map +1 -1
  212. package/dist/src/utils/artifact-request.d.ts +1 -0
  213. package/dist/src/utils/artifact-request.js +22 -12
  214. package/dist/src/utils/artifact-request.js.map +1 -1
  215. package/dist/src/utils/artifact-sources.d.ts +2 -2
  216. package/dist/src/utils/bundle-layout.js +18 -16
  217. package/dist/src/utils/bundle-layout.js.map +1 -1
  218. package/dist/src/utils/catalog-digests.js +2 -0
  219. package/dist/src/utils/catalog-digests.js.map +1 -1
  220. package/dist/src/utils/catalog-runtime-projection.d.ts +4 -3
  221. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  222. package/dist/src/utils/catalog-source-references.d.ts +5 -0
  223. package/dist/src/utils/catalog-source-references.js +42 -57
  224. package/dist/src/utils/catalog-source-references.js.map +1 -1
  225. package/dist/src/utils/claude-native-render-receipt.d.ts +14 -0
  226. package/dist/src/utils/claude-native-render-receipt.js +26 -0
  227. package/dist/src/utils/claude-native-render-receipt.js.map +1 -0
  228. package/dist/src/utils/claude-skills-root.js +14 -3
  229. package/dist/src/utils/claude-skills-root.js.map +1 -1
  230. package/dist/src/utils/codex-manager.js +6 -12
  231. package/dist/src/utils/codex-manager.js.map +1 -1
  232. package/dist/src/utils/codex-native-observation.js +29 -13
  233. package/dist/src/utils/codex-native-observation.js.map +1 -1
  234. package/dist/src/utils/codex-reconciler.d.ts +2 -5
  235. package/dist/src/utils/codex-reconciler.js +25 -1
  236. package/dist/src/utils/codex-reconciler.js.map +1 -1
  237. package/dist/src/utils/codex-surface-analysis.js +5 -3
  238. package/dist/src/utils/codex-surface-analysis.js.map +1 -1
  239. package/dist/src/utils/local-marketplace.d.ts +14 -2
  240. package/dist/src/utils/local-marketplace.js +209 -42
  241. package/dist/src/utils/local-marketplace.js.map +1 -1
  242. package/dist/src/utils/managed-command-evidence.js +1 -1
  243. package/dist/src/utils/managed-command-evidence.js.map +1 -1
  244. package/dist/src/utils/managed-command-materialization.d.ts +4 -0
  245. package/dist/src/utils/managed-command-materialization.js +15 -6
  246. package/dist/src/utils/managed-command-materialization.js.map +1 -1
  247. package/dist/src/utils/managed-command-store.d.ts +15 -0
  248. package/dist/src/utils/managed-command-store.js +73 -8
  249. package/dist/src/utils/managed-command-store.js.map +1 -1
  250. package/dist/src/utils/native-cli.d.ts +2 -0
  251. package/dist/src/utils/native-cli.js +22 -0
  252. package/dist/src/utils/native-cli.js.map +1 -1
  253. package/dist/src/utils/native-demand.d.ts +10 -0
  254. package/dist/src/utils/native-demand.js +4 -0
  255. package/dist/src/utils/native-demand.js.map +1 -0
  256. package/dist/src/utils/ownership.js +2 -0
  257. package/dist/src/utils/ownership.js.map +1 -1
  258. package/dist/src/utils/paths.js +1 -1
  259. package/dist/src/utils/paths.js.map +1 -1
  260. package/dist/src/utils/platform-support.d.ts +1 -1
  261. package/dist/src/utils/platform-support.js +1 -1
  262. package/dist/src/utils/platform-support.js.map +1 -1
  263. package/dist/src/utils/plugin-config-impact.d.ts +3 -0
  264. package/dist/src/utils/plugin-config-impact.js +115 -273
  265. package/dist/src/utils/plugin-config-impact.js.map +1 -1
  266. package/dist/src/utils/plugin-config-references.d.ts +11 -2
  267. package/dist/src/utils/plugin-config-references.js +85 -90
  268. package/dist/src/utils/plugin-config-references.js.map +1 -1
  269. package/dist/src/utils/plugin-config-write.d.ts +58 -1
  270. package/dist/src/utils/plugin-config-write.js +756 -809
  271. package/dist/src/utils/plugin-config-write.js.map +1 -1
  272. package/dist/src/utils/plugin-config.d.ts +47 -46
  273. package/dist/src/utils/plugin-config.js +292 -833
  274. package/dist/src/utils/plugin-config.js.map +1 -1
  275. package/dist/src/utils/plugin-scripts.d.ts +11 -6
  276. package/dist/src/utils/plugin-scripts.js +52 -35
  277. package/dist/src/utils/plugin-scripts.js.map +1 -1
  278. package/dist/src/utils/runtime-config.js +5 -4
  279. package/dist/src/utils/runtime-config.js.map +1 -1
  280. package/dist/src/utils/runtime-skill-closure.d.ts +2 -2
  281. package/dist/src/utils/runtime-skill-closure.js.map +1 -1
  282. package/dist/src/utils/state-paths.d.ts +8 -3
  283. package/dist/src/utils/state-paths.js +53 -16
  284. package/dist/src/utils/state-paths.js.map +1 -1
  285. package/dist/src/utils/windows-npm-shim.d.ts +2 -0
  286. package/dist/src/utils/windows-npm-shim.js +5 -7
  287. package/dist/src/utils/windows-npm-shim.js.map +1 -1
  288. package/dist/vitest.config.js +1 -0
  289. package/dist/vitest.config.js.map +1 -1
  290. package/package.json +3 -3
  291. package/dist/src/commands/sync-command.d.ts +0 -40
  292. package/dist/src/commands/sync-command.js +0 -254
  293. package/dist/src/commands/sync-command.js.map +0 -1
  294. package/dist/src/portable/skills-cli.d.ts +0 -51
  295. package/dist/src/portable/skills-cli.js +0 -202
  296. package/dist/src/portable/skills-cli.js.map +0 -1
  297. package/dist/src/tui/project-inventory.d.ts +0 -68
  298. package/dist/src/tui/project-inventory.js +0 -203
  299. package/dist/src/tui/project-inventory.js.map +0 -1
  300. package/dist/src/utils/plugin-intent-state.d.ts +0 -49
  301. package/dist/src/utils/plugin-intent-state.js +0 -320
  302. package/dist/src/utils/plugin-intent-state.js.map +0 -1
@@ -1,65 +1,37 @@
1
1
  import crypto from 'node:crypto';
2
+ import { resolveWorkContext } from '../intent/work-context.js';
2
3
  import * as nodeFs from 'node:fs/promises';
3
4
  import path from 'node:path';
4
5
  import fs from 'fs-extra';
5
- import { canonicalizePluginConfigValues, formatPluginConfigIssue, getPluginConfigDependencyKeys, getPluginConfigDependencyFields, matchPluginConfigPreset, redactPluginConfigValue, stablePluginConfigStringify, validatePluginConfigCandidate, validatePluginConfigDocument, validatePluginConfigValue as validateCorePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
6
- import { assertTestReadablePath, assertTestWritablePath, } from './test-path-fence.js';
6
+ import { canonicalizePluginConfigValues, compilePluginConfigDefinition, PLATFORM_TARGETS, formatPluginConfigIssue, getPluginConfigDependencyKeys, getPluginConfigDependencyFields, matchPluginConfigPreset, redactPluginConfigValue, stablePluginConfigStringify, validatePluginConfigCandidate, validatePluginConfigDocument, validatePluginConfigValue as validateCorePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
7
7
  import { canonicalizePathThroughExistingAncestor, resolveAwHome, resolveRepoRootSync, } from './paths.js';
8
8
  import { assertRealFileParentWithinRoot, assertSafeFileMutationPath, atomicReplaceSafeFile, } from './safe-fs.js';
9
- import { assertProjectSharedConfigPathVisible, PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING, } from './project-boundary.js';
10
9
  import { pluginConfigHasExternalReferences, resolvePluginConfigReferenceContext, } from './plugin-config-references.js';
11
10
  export { resolvePluginConfigReferenceContext } from './plugin-config-references.js';
12
11
  export function codexNativeRefreshKeys(manifest) {
13
12
  return new Set((manifest.artifacts?.targets ?? []).flatMap((target) => target.platform === 'codex'
14
13
  && target.target === 'codex.skill'
15
- && target.delivery?.mode === 'codex-native'
16
14
  ? target.refreshKeys
17
15
  : []));
18
16
  }
19
- function readPluginConfigBinding(value, scope) {
20
- if (!isRecord(value))
21
- return null;
22
- if (scope === 'user') {
23
- return value.kind === 'source'
24
- && typeof value.sourceIdentity === 'string'
25
- && /^sha256:[0-9a-f]{64}$/u.test(value.sourceIdentity)
26
- && Object.keys(value).every((key) => ['kind', 'sourceIdentity'].includes(key))
27
- ? { kind: 'source', sourceIdentity: value.sourceIdentity }
28
- : null;
29
- }
30
- return value.kind === 'catalog'
31
- && typeof value.logicalIdentity === 'string'
32
- && value.logicalIdentity.trim().length > 0
33
- && Object.keys(value).every((key) => ['kind', 'logicalIdentity'].includes(key))
34
- ? { kind: 'catalog', logicalIdentity: value.logicalIdentity }
35
- : null;
36
- }
37
- const PLUGIN_CONFIG_ENVELOPE_V5_KEYS = new Set([
38
- 'schemaVersion',
39
- 'sourceId',
40
- 'pluginName',
41
- 'binding',
42
- 'values',
43
- ]);
44
- function readPluginConfigEnvelopeV5(value, scope) {
45
- if (!isRecord(value)
46
- || value.schemaVersion !== 5
47
- || typeof value.sourceId !== 'string'
48
- || typeof value.pluginName !== 'string'
49
- || !isRecord(value.values)
50
- || Object.keys(value).some((key) => !PLUGIN_CONFIG_ENVELOPE_V5_KEYS.has(key))) {
51
- return null;
52
- }
53
- const binding = readPluginConfigBinding(value.binding, scope);
54
- if (!binding)
17
+ function readPluginConfigBinding(value) {
18
+ return isRecord(value) && value.kind === 'source'
19
+ && typeof value.sourceIdentity === 'string'
20
+ && /^sha256:[0-9a-f]{64}$/u.test(value.sourceIdentity)
21
+ && Object.keys(value).every((key) => ['kind', 'sourceIdentity'].includes(key))
22
+ ? { kind: 'source', sourceIdentity: value.sourceIdentity } : null;
23
+ }
24
+ function readPluginConfigEnvelopeV6(value, _scope) {
25
+ if (!isRecord(value) || value.schemaVersion !== 6
26
+ || typeof value.sourceId !== 'string' || typeof value.pluginName !== 'string'
27
+ || !isRecord(value.common) || !isRecord(value.platforms)
28
+ || Object.keys(value).some((key) => !['schemaVersion', 'sourceId', 'pluginName', 'binding', 'common', 'platforms'].includes(key))
29
+ || Object.keys(value.platforms).some((key) => !PLATFORM_TARGETS.includes(key)))
55
30
  return null;
56
- return {
57
- schemaVersion: 5,
58
- sourceId: value.sourceId,
59
- pluginName: value.pluginName,
60
- binding,
61
- values: value.values,
62
- };
31
+ const binding = readPluginConfigBinding(value.binding);
32
+ return binding ? { schemaVersion: 6, sourceId: value.sourceId, pluginName: value.pluginName,
33
+ binding, common: value.common,
34
+ platforms: value.platforms } : null;
63
35
  }
64
36
  export class PluginConfigResetError extends Error {
65
37
  code;
@@ -252,10 +224,11 @@ async function calculateConfigSchemaManifestDigestForPlugin(plugin) {
252
224
  hash.update(JSON.stringify(plugin.manifest));
253
225
  return `sha256:${hash.digest('hex')}`;
254
226
  }
255
- function createConfigSchemaEvidenceFromCurrentCatalog(plugin, manifestDigest) {
227
+ function createConfigSchemaEvidenceFromCurrentCatalog(plugin, manifestDigest, platform) {
256
228
  return {
257
229
  source: 'currentCatalog',
258
- identity: createPluginConfigSchemaIdentity(plugin.manifest),
230
+ identity: createPluginConfigSchemaIdentity(plugin.manifest, platform),
231
+ platform,
259
232
  pluginVersion: plugin.manifest.version,
260
233
  manifestDigest,
261
234
  };
@@ -283,41 +256,16 @@ export function resolveAwProjectRoot(cwd) {
283
256
  return path.resolve(resolveRepoRootSync(cwd));
284
257
  }
285
258
  function resolvePluginConfigAllowedRoot(options) {
286
- return options.scope === 'user'
287
- ? resolveAwHome(options.env)
288
- : path.join(resolveAwProjectRoot(options.cwd), '.agent-workflows');
259
+ return resolveAwHome(options.env);
289
260
  }
261
+ /** Scope selects source context. All config values belong to the user document. */
290
262
  export function resolvePluginConfigStorePath(options) {
291
263
  const pluginName = assertSafePluginConfigSegment(options.pluginName);
292
- const sourceId = options.sourceId === undefined
293
- ? undefined
294
- : assertSafePluginConfigSegment(options.sourceId);
295
- const root = options.scope === 'user'
296
- ? resolveAwHome(options.env)
297
- : path.join(resolveAwProjectRoot(options.cwd), '.agent-workflows');
298
- const filePath = path.join(root, CONFIG_DIR, ...(sourceId ? [sourceId] : []), pluginName, CONFIG_FILE);
299
- if (options.scope !== 'project') {
300
- return filePath;
301
- }
302
- if (options.access === 'read') {
303
- return assertTestReadablePath(filePath, 'project plugin config store (.agent-workflows/plugin-config)', '用 registerReadonlyFixtureRoot() 或 runWithRepositoryCwdAsReadOnly() 登记只读 project fixture 根;需要写入时改用 withSafeTestCwd() 或 registerWritableStateRoot()。');
304
- }
305
- if (options.access === 'write') {
306
- return assertTestWritablePath(filePath, 'project plugin config store (.agent-workflows/plugin-config)', '用 withSafeTestCwd() 切到测试可写 cwd,或通过 registerWritableStateRoot() 登记 project fixture 根。');
307
- }
308
- return filePath;
264
+ const sourceId = options.sourceId === undefined ? undefined : assertSafePluginConfigSegment(options.sourceId);
265
+ return path.join(resolveAwHome(options.env), CONFIG_DIR, ...(sourceId ? [sourceId] : []), pluginName, CONFIG_FILE);
309
266
  }
310
267
  export function resolvePluginConfigStoreTargets(options) {
311
- if (options.scope === 'user') {
312
- return [{
313
- storeKind: 'user',
314
- path: resolvePluginConfigStorePath(options),
315
- }];
316
- }
317
- return [{
318
- storeKind: 'project',
319
- path: resolvePluginConfigStorePath(options),
320
- }];
268
+ return [{ storeKind: 'user', path: resolvePluginConfigStorePath(options) }];
321
269
  }
322
270
  function isPluginConfigScalar(value) {
323
271
  if (typeof value === 'string' || typeof value === 'boolean')
@@ -417,80 +365,75 @@ function validateStoreValues(options) {
417
365
  ? invalidStore(options.filePath, 'invalidValues', issues.join(' '), options.rawDigest)
418
366
  : readyStore(options.filePath, values, options.rawDigest);
419
367
  }
420
- async function readStoreFile(filePath) {
368
+ function documentStore(options) {
369
+ const common = validateStoreValues({ filePath: options.filePath, values: options.common, rawDigest: options.rawDigest });
370
+ if (common.state === 'invalid')
371
+ return common;
372
+ if (!isRecord(options.platforms) || Object.keys(options.platforms).some((key) => !PLATFORM_TARGETS.includes(key))) {
373
+ return invalidStore(options.filePath, 'invalidValues', 'The platforms field must contain known platform objects.', options.rawDigest);
374
+ }
375
+ const platforms = {};
376
+ for (const [platform, values] of Object.entries(options.platforms)) {
377
+ const store = validateStoreValues({ filePath: options.filePath, values, rawDigest: options.rawDigest });
378
+ if (store.state === 'invalid')
379
+ return store;
380
+ platforms[platform] = store.values;
381
+ }
382
+ return { ...common, common: common.values, platforms, ...(options.platform ? { platform: options.platform } : {}),
383
+ values: options.platform ? platforms[options.platform] ?? {} : common.values };
384
+ }
385
+ export function pluginConfigStoreForProfile(store, platform) {
386
+ return { ...store, platform, values: platform ? store.platforms?.[platform] ?? {} : store.common ?? store.values };
387
+ }
388
+ /** Replace one layer in memory without changing any other profile. */
389
+ export function replacePluginConfigStoreLayer(store, values, platform) {
390
+ const common = platform ? store.common ?? store.values : canonicalizePluginConfigValues(values);
391
+ const platforms = { ...store.platforms };
392
+ if (platform) {
393
+ if (Object.keys(values).length)
394
+ platforms[platform] = canonicalizePluginConfigValues(values);
395
+ else
396
+ delete platforms[platform];
397
+ }
398
+ const exists = Object.keys(common).length > 0 || Object.keys(platforms).length > 0;
399
+ return { ...store, common, platforms, platform, values: platform ? platforms[platform] ?? {} : common,
400
+ exists, state: exists ? 'ready' : 'missing', issues: [], rawDigest: undefined };
401
+ }
402
+ async function readStoreFile(filePath, platform) {
421
403
  const parsed = await parseStoreJson(filePath);
422
404
  if (parsed.state === 'invalidJson')
423
405
  return parsed.store;
424
- return validateStoreValues({ filePath, values: parsed.value, rawDigest: parsed.rawDigest });
406
+ const raw = parsed.value;
407
+ if (!isRecord(raw) || raw.schemaVersion !== 6
408
+ || Object.keys(raw).some((key) => !['schemaVersion', 'common', 'platforms'].includes(key))) {
409
+ return invalidStore(filePath, 'schemaVersionUnsupported', 'Only plugin config store schemaVersion 6 is supported. Reset the old document, then save the required values.', parsed.rawDigest);
410
+ }
411
+ return documentStore({ filePath, common: raw.common, platforms: raw.platforms, rawDigest: parsed.rawDigest, platform });
425
412
  }
426
413
  async function readNamespacedStoreFile(options) {
427
414
  const parsed = await parseStoreJson(options.filePath);
428
415
  if (parsed.state === 'invalidJson')
429
416
  return parsed.store;
430
- const raw = parsed.value;
431
- if (!isRecord(raw) || raw.schemaVersion !== 5) {
432
- return invalidStore(options.filePath, 'schemaVersionUnsupported', 'Only plugin config store schemaVersion 5 is supported.', parsed.rawDigest);
433
- }
434
- const envelope = readPluginConfigEnvelopeV5(raw, options.scope);
435
- if (!envelope) {
436
- return invalidStore(options.filePath, 'schemaVersionUnsupported', 'The plugin config store schemaVersion 5 envelope is invalid.', parsed.rawDigest);
417
+ const envelope = readPluginConfigEnvelopeV6(parsed.value);
418
+ if (!envelope)
419
+ return invalidStore(options.filePath, 'schemaVersionUnsupported', 'Only a valid plugin config store schemaVersion 6 is supported. Reset the old document, then save the required values.', parsed.rawDigest);
420
+ if (envelope.sourceId !== options.sourceId || envelope.pluginName !== options.pluginName
421
+ || !options.sourceIdentity || envelope.binding.sourceIdentity !== options.sourceIdentity) {
422
+ return invalidStore(options.filePath, 'identityMismatch', 'The user store is not bound to the requested source and plugin identity.', parsed.rawDigest);
437
423
  }
438
- if (envelope.sourceId !== options.sourceId
439
- || envelope.pluginName !== options.pluginName) {
440
- return invalidStore(options.filePath, 'identityMismatch', 'The store identity does not match its source and namespaced path.', parsed.rawDigest);
441
- }
442
- const binding = envelope.binding;
443
- const matchesBinding = options.scope === 'user'
444
- ? binding?.kind === 'source'
445
- && typeof options.sourceIdentity === 'string'
446
- && binding.sourceIdentity === options.sourceIdentity
447
- : binding?.kind === 'catalog'
448
- && typeof options.logicalIdentity === 'string'
449
- && binding.logicalIdentity === options.logicalIdentity;
450
- if (!matchesBinding) {
451
- return invalidStore(options.filePath, 'identityMismatch', options.scope === 'user'
452
- ? 'The user store is not bound to the requested source identity.'
453
- : 'The project store is not bound to the requested Catalog logical identity.', parsed.rawDigest);
454
- }
455
- return validateStoreValues({
456
- filePath: options.filePath,
457
- values: envelope.values,
458
- rawDigest: parsed.rawDigest,
459
- });
424
+ return documentStore({ filePath: options.filePath, common: envelope.common, platforms: envelope.platforms,
425
+ rawDigest: parsed.rawDigest, platform: options.platform });
460
426
  }
461
427
  function namespacedConfigContent(options) {
462
- const values = canonicalizePluginConfigValues(options.values);
463
- if (!options.sourceId)
464
- return `${JSON.stringify(values, null, 2)}\n`;
465
- const binding = options.scope === 'user'
466
- ? options.sourceIdentity
467
- ? { kind: 'source', sourceIdentity: options.sourceIdentity }
468
- : (() => { throw new Error('sourceChanged: user plugin config requires a source identity.'); })()
469
- : options.logicalIdentity
470
- ? { kind: 'catalog', logicalIdentity: options.logicalIdentity }
471
- : (() => { throw new Error('catalogUnavailable: project plugin config requires verified Catalog logical identity evidence.'); })();
472
- const envelope = {
473
- schemaVersion: 5,
474
- sourceId: options.sourceId,
475
- pluginName: options.pluginName,
476
- binding,
477
- values,
478
- };
479
- return `${JSON.stringify(envelope, null, 2)}\n`;
480
- }
481
- async function assertNoLegacyConfigCoexistence(options) {
428
+ const document = { schemaVersion: 6, common: canonicalizePluginConfigValues(options.common),
429
+ platforms: Object.fromEntries(Object.entries(options.platforms).sort(([a], [b]) => a.localeCompare(b, 'en'))
430
+ .map(([platform, values]) => [platform, canonicalizePluginConfigValues(values)])) };
482
431
  if (!options.sourceId)
483
- return;
484
- const legacyPath = resolvePluginConfigStorePath({
485
- pluginName: options.pluginName,
486
- scope: options.scope,
487
- cwd: options.cwd,
488
- env: options.env,
489
- ...(options.scope === 'project' ? { access: 'read' } : {}),
490
- });
491
- if (await lstatIfExists(legacyPath)) {
492
- throw new Error('legacyStateUnsupported: legacy and namespaced plugin config stores cannot coexist.');
493
- }
432
+ return `${JSON.stringify(document, null, 2)}\n`;
433
+ if (!options.sourceIdentity)
434
+ throw new Error('sourceChanged: user plugin config requires a source identity.');
435
+ return `${JSON.stringify({ ...document, sourceId: options.sourceId, pluginName: options.pluginName,
436
+ binding: { kind: 'source', sourceIdentity: options.sourceIdentity } }, null, 2)}\n`;
494
437
  }
495
438
  export async function readPluginConfigStore(options) {
496
439
  const filePath = resolvePluginConfigStorePath({
@@ -527,8 +470,9 @@ export async function readPluginConfigStore(options) {
527
470
  sourceIdentity: options.sourceIdentity,
528
471
  logicalIdentity: options.logicalIdentity,
529
472
  pluginName: options.pluginName,
473
+ platform: options.platform,
530
474
  })
531
- : readStoreFile(filePath);
475
+ : readStoreFile(filePath, options.platform);
532
476
  }
533
477
  export async function readNamespacedPluginConfigStoreIdentity(options) {
534
478
  const filePath = resolvePluginConfigStorePath({
@@ -541,13 +485,13 @@ export async function readNamespacedPluginConfigStoreIdentity(options) {
541
485
  if (parsed.state === 'invalidJson') {
542
486
  throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store.`);
543
487
  }
544
- const envelope = readPluginConfigEnvelopeV5(parsed.value, options.scope);
488
+ const envelope = readPluginConfigEnvelopeV6(parsed.value, options.scope);
545
489
  if (!envelope
546
490
  || envelope.sourceId !== options.sourceId
547
491
  || envelope.pluginName !== options.pluginName) {
548
492
  throw new Error('schemaVersionUnsupported: invalid namespaced plugin config store.');
549
493
  }
550
- const store = validateStoreValues({ filePath, values: envelope.values });
494
+ const store = documentStore({ filePath, common: envelope.common, platforms: envelope.platforms, platform: options.platform });
551
495
  assertReadyPluginConfigStore(store);
552
496
  return {
553
497
  binding: envelope.binding,
@@ -578,29 +522,19 @@ async function listConfigStoreDirectoryPluginNames(storeRoot) {
578
522
  return pluginNames;
579
523
  }
580
524
  export async function listPluginConfigStorePluginNames(options) {
581
- const projectRoot = resolveAwProjectRoot(options.cwd);
582
- const storeRoots = options.scope === 'user'
583
- ? [path.join(resolveAwHome(options.env), CONFIG_DIR)]
584
- : options.scope === 'project'
585
- ? [path.join(projectRoot, '.agent-workflows', CONFIG_DIR)]
586
- : [
587
- path.join(resolveAwHome(options.env), CONFIG_DIR),
588
- path.join(projectRoot, '.agent-workflows', CONFIG_DIR),
589
- ];
525
+ const storeRoots = [path.join(resolveAwHome(options.env), CONFIG_DIR)];
590
526
  const pluginNames = (await Promise.all(storeRoots.map((storeRoot) => listConfigStoreDirectoryPluginNames(storeRoot)))).flat();
591
527
  return [...new Set(pluginNames)].sort((left, right) => left.localeCompare(right));
592
528
  }
593
529
  export async function listNamespacedPluginConfigStoreIdentities(options) {
594
- const boundaryRoot = options.scope === 'user'
595
- ? resolveAwHome(options.env)
596
- : resolveAwProjectRoot(options.cwd);
530
+ const boundaryRoot = resolveAwHome(options.env);
597
531
  if (options.scope === 'user') {
598
532
  await canonicalizePathThroughExistingAncestor(boundaryRoot);
599
533
  }
600
534
  else {
601
535
  await fs.realpath(boundaryRoot);
602
536
  }
603
- const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
537
+ const storeRoot = path.join(boundaryRoot, CONFIG_DIR);
604
538
  if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
605
539
  return [];
606
540
  const output = [];
@@ -649,16 +583,14 @@ export async function listNamespacedPluginConfigStoreIdentities(options) {
649
583
  return output.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
650
584
  }
651
585
  export async function listNamespacedPluginConfigStores(options) {
652
- const boundaryRoot = options.scope === 'user'
653
- ? resolveAwHome(options.env)
654
- : resolveAwProjectRoot(options.cwd);
586
+ const boundaryRoot = resolveAwHome(options.env);
655
587
  if (options.scope === 'user') {
656
588
  await canonicalizePathThroughExistingAncestor(boundaryRoot);
657
589
  }
658
590
  else {
659
591
  await fs.realpath(boundaryRoot);
660
592
  }
661
- const storeRoot = path.join(boundaryRoot, ...(options.scope === 'project' ? ['.agent-workflows'] : []), CONFIG_DIR);
593
+ const storeRoot = path.join(boundaryRoot, CONFIG_DIR);
662
594
  const output = [];
663
595
  if (!await inspectConfigInventoryStoreRoot(boundaryRoot, storeRoot))
664
596
  return output;
@@ -684,13 +616,13 @@ export async function listNamespacedPluginConfigStores(options) {
684
616
  if (parsed.state === 'invalidJson') {
685
617
  throw new Error(`${parsed.store.invalidCode}: invalid namespaced plugin config store "${filePath}".`);
686
618
  }
687
- const envelope = readPluginConfigEnvelopeV5(parsed.value, options.scope);
619
+ const envelope = readPluginConfigEnvelopeV6(parsed.value, options.scope);
688
620
  if (!envelope
689
621
  || envelope.sourceId !== sourceId
690
622
  || envelope.pluginName !== pluginName) {
691
623
  throw new Error(`schemaVersionUnsupported: invalid namespaced plugin config store "${filePath}".`);
692
624
  }
693
- const store = validateStoreValues({ filePath, values: envelope.values });
625
+ const store = documentStore({ filePath, common: envelope.common, platforms: envelope.platforms });
694
626
  assertReadyPluginConfigStore(store);
695
627
  output.push({
696
628
  sourceId,
@@ -704,130 +636,51 @@ export async function listNamespacedPluginConfigStores(options) {
704
636
  return output.sort((left, right) => left.pluginId.localeCompare(right.pluginId));
705
637
  }
706
638
  export async function writePluginConfigStore(options) {
707
- await assertNoLegacyConfigCoexistence(options);
708
- const filePath = resolvePluginConfigStorePath({
709
- ...options,
710
- ...(options.scope === 'project' ? { access: 'write' } : {}),
711
- });
712
- const allowedRoot = resolvePluginConfigAllowedRoot(options);
713
- const validated = validateStoreValues({ filePath, values: options.values });
639
+ const current = await readPluginConfigStore(options);
640
+ assertReadyPluginConfigStore(current);
641
+ const validated = validateStoreValues({ filePath: current.path, values: options.values });
714
642
  assertReadyPluginConfigStore(validated);
715
- const warnings = [];
716
- await withConfigFileMutation({
717
- filePath,
718
- allowedRoot,
719
- action: async () => {
720
- if (!options.sourceId && Object.keys(validated.values).length === 0) {
721
- return atomicRemoveConfigFile({ filePath, allowedRoot });
722
- }
723
- if (options.sourceId && await lstatIfExists(filePath)) {
724
- const current = await readNamespacedStoreFile({
725
- filePath,
726
- sourceId: options.sourceId,
727
- scope: options.scope,
728
- sourceIdentity: options.sourceIdentity,
729
- logicalIdentity: options.logicalIdentity,
730
- pluginName: options.pluginName,
731
- });
732
- assertReadyPluginConfigStore(current);
733
- }
734
- if (options.scope === 'project') {
735
- const visibility = await assertProjectSharedConfigPathVisible({
736
- cwd: options.cwd,
737
- targetPath: filePath,
738
- env: options.env,
739
- });
740
- if (visibility?.state === 'unverified') {
741
- warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
742
- }
743
- }
744
- return atomicReplaceConfigFile({
745
- filePath,
746
- allowedRoot,
747
- content: namespacedConfigContent({ ...options, values: validated.values }),
748
- mode: 0o600,
749
- });
750
- },
751
- });
752
- return {
753
- pluginName: options.pluginName,
754
- scope: options.scope,
755
- path: filePath,
756
- warnings,
757
- };
643
+ return commitPluginConfigLayer(options, current, validated.values);
758
644
  }
759
- export async function patchPluginConfigStore(options) {
760
- await assertNoLegacyConfigCoexistence(options);
761
- const filePath = resolvePluginConfigStorePath({
762
- ...options,
763
- ...(options.scope === 'project' ? { access: 'write' } : {}),
764
- });
645
+ async function commitPluginConfigLayer(options, current, values) {
646
+ const next = replacePluginConfigStoreLayer(current, values, options.platform);
765
647
  const allowedRoot = resolvePluginConfigAllowedRoot(options);
648
+ await withConfigFileMutation({ filePath: current.path, allowedRoot, action: async () => {
649
+ if (!next.exists)
650
+ await atomicRemoveConfigFile({ filePath: current.path, allowedRoot });
651
+ else
652
+ await atomicReplaceConfigFile({ filePath: current.path, allowedRoot,
653
+ content: namespacedConfigContent({ ...options, common: next.common, platforms: next.platforms }), mode: 0o600 });
654
+ } });
655
+ return { pluginName: options.pluginName, scope: 'user', platform: options.platform, path: current.path, warnings: [] };
656
+ }
657
+ export async function patchPluginConfigStore(options) {
658
+ await options.reviewPreflight?.();
659
+ const current = await readPluginConfigStore(options);
660
+ assertReadyPluginConfigStore(current);
766
661
  const removedKeys = options.removeKeys ?? [];
767
- const warnings = [];
768
- await withConfigFileMutation({
769
- filePath,
770
- allowedRoot,
771
- action: async () => {
772
- await options.reviewPreflight?.();
773
- const current = (await lstatIfExists(filePath))
774
- ? options.sourceId
775
- ? await readNamespacedStoreFile({
776
- filePath,
777
- sourceId: options.sourceId,
778
- scope: options.scope,
779
- sourceIdentity: options.sourceIdentity,
780
- logicalIdentity: options.logicalIdentity,
781
- pluginName: options.pluginName,
782
- })
783
- : await readStoreFile(filePath)
784
- : readyStore(filePath, {});
785
- assertReadyPluginConfigStore(current);
786
- if (options.requireExistingRemoveKeys) {
787
- const missingKey = removedKeys.find((key) => !Object.hasOwn(current.values, key));
788
- if (missingKey)
789
- throw new Error(`Unknown config key "${missingKey}".`);
790
- }
791
- const next = { ...current.values };
792
- for (const key of removedKeys) {
793
- delete next[key];
794
- }
795
- for (const [key, value] of Object.entries(options.updates)) {
796
- next[key] = value;
797
- }
798
- if (Object.keys(next).length === 0) {
799
- await atomicRemoveConfigFile({ filePath, allowedRoot });
800
- }
801
- else {
802
- if (options.scope === 'project') {
803
- const visibility = await assertProjectSharedConfigPathVisible({
804
- cwd: options.cwd,
805
- targetPath: filePath,
806
- env: options.env,
807
- });
808
- if (visibility?.state === 'unverified') {
809
- warnings.push(PROJECT_CONFIG_SHAREABILITY_UNVERIFIED_WARNING);
810
- }
811
- }
812
- await atomicReplaceConfigFile({
813
- filePath,
814
- allowedRoot,
815
- content: namespacedConfigContent({ ...options, values: next }),
816
- mode: 0o600,
817
- });
818
- }
819
- },
820
- });
821
- return {
822
- pluginName: options.pluginName,
823
- scope: options.scope,
824
- path: filePath,
825
- warnings,
826
- updatedKeys: Object.keys(options.updates),
827
- removedKeys,
828
- };
662
+ if (options.requireExistingRemoveKeys) {
663
+ const missing = removedKeys.find((key) => !Object.hasOwn(current.values, key));
664
+ if (missing)
665
+ throw new Error(`Unknown config key "${missing}".`);
666
+ }
667
+ const next = Object.assign(Object.create(null), current.values);
668
+ for (const key of removedKeys)
669
+ delete next[key];
670
+ for (const [key, value] of Object.entries(options.updates))
671
+ next[key] = value;
672
+ const validated = validateStoreValues({ filePath: current.path, values: next });
673
+ assertReadyPluginConfigStore(validated);
674
+ return { ...await commitPluginConfigLayer(options, current, validated.values), updatedKeys: Object.keys(options.updates), removedKeys };
829
675
  }
830
676
  export async function resetPluginConfigStore(options) {
677
+ return resetPluginConfigStoreTarget(options, false);
678
+ }
679
+ /** Delete the plugin's complete user document, including every platform profile. */
680
+ export async function deletePluginConfigStore(options) {
681
+ return resetPluginConfigStoreTarget(options, true);
682
+ }
683
+ async function resetPluginConfigStoreTarget(options, wholeDocument) {
831
684
  const filePath = resolvePluginConfigStorePath({
832
685
  ...options,
833
686
  ...(options.scope === 'project' ? { access: 'write' } : {}),
@@ -846,8 +699,8 @@ export async function resetPluginConfigStore(options) {
846
699
  if (!stats) {
847
700
  return {
848
701
  pluginName: options.pluginName,
849
- scope: options.scope,
850
- storeKind: options.scope,
702
+ scope: 'user',
703
+ storeKind: 'user',
851
704
  path: filePath,
852
705
  existed: false,
853
706
  deleted: false,
@@ -863,8 +716,8 @@ export async function resetPluginConfigStore(options) {
863
716
  if (!parentStats) {
864
717
  return {
865
718
  pluginName: options.pluginName,
866
- scope: options.scope,
867
- storeKind: options.scope,
719
+ scope: 'user',
720
+ storeKind: 'user',
868
721
  path: filePath,
869
722
  existed: false,
870
723
  deleted: false,
@@ -879,21 +732,35 @@ export async function resetPluginConfigStore(options) {
879
732
  if (!target) {
880
733
  return {
881
734
  pluginName: options.pluginName,
882
- scope: options.scope,
883
- storeKind: options.scope,
735
+ scope: 'user',
736
+ storeKind: 'user',
884
737
  path: filePath,
885
738
  existed: false,
886
739
  deleted: false,
887
740
  resetDisposition: 'absent',
888
741
  };
889
742
  }
743
+ let store;
744
+ if (target.isFile()) {
745
+ try {
746
+ store = await readPluginConfigStore(options);
747
+ }
748
+ catch {
749
+ // A broken document still has the schema-independent reset recovery below.
750
+ }
751
+ }
752
+ if (!wholeDocument && store && store.state !== 'invalid' && replacePluginConfigStoreLayer(store, {}, options.platform).exists) {
753
+ await commitPluginConfigLayer(options, store, {});
754
+ return { pluginName: options.pluginName, scope: 'user', platform: options.platform, storeKind: 'user',
755
+ path: filePath, existed: true, deleted: false, resetDisposition: 'clearedLayer' };
756
+ }
890
757
  if (target.isFile() || target.isSymbolicLink()) {
891
758
  await assertNoConfigSymlink(allowedRoot, path.dirname(filePath));
892
759
  await nodeFs.unlink(filePath);
893
760
  return {
894
761
  pluginName: options.pluginName,
895
- scope: options.scope,
896
- storeKind: options.scope,
762
+ scope: 'user',
763
+ storeKind: 'user',
897
764
  path: filePath,
898
765
  existed: true,
899
766
  deleted: true,
@@ -907,8 +774,8 @@ export async function resetPluginConfigStore(options) {
907
774
  await nodeFs.rename(filePath, path.join(path.dirname(filePath), quarantineBasename));
908
775
  return {
909
776
  pluginName: options.pluginName,
910
- scope: options.scope,
911
- storeKind: options.scope,
777
+ scope: 'user',
778
+ storeKind: 'user',
912
779
  path: filePath,
913
780
  existed: true,
914
781
  deleted: true,
@@ -982,41 +849,7 @@ function resetTargetInspection(kind, stats) {
982
849
  };
983
850
  }
984
851
  export async function resetPluginConfigStores(options) {
985
- await assertNoLegacyConfigCoexistence(options);
986
- const targets = resolvePluginConfigStoreTargets({
987
- ...options,
988
- ...(options.scope === 'project' ? { access: 'write' } : {}),
989
- });
990
- const allowedRoot = resolvePluginConfigAllowedRoot(options);
991
- const receipts = [];
992
- const existing = [];
993
- for (const target of targets) {
994
- await prepareConfigFileParent(target.path, allowedRoot);
995
- await assertNoConfigSymlink(allowedRoot, target.path);
996
- const stats = await lstatIfExists(target.path);
997
- existing.push(stats !== null);
998
- if (!stats)
999
- continue;
1000
- if (!stats.isFile() || stats.isSymbolicLink()) {
1001
- throw new Error(`Config target must be a regular file: ${target.path}`);
1002
- }
1003
- }
1004
- for (const [index, target] of targets.entries()) {
1005
- const deleted = await atomicRemoveConfigFile({
1006
- filePath: target.path,
1007
- allowedRoot,
1008
- });
1009
- receipts.push({
1010
- pluginName: options.pluginName,
1011
- scope: options.scope,
1012
- storeKind: target.storeKind,
1013
- path: target.path,
1014
- existed: existing[index],
1015
- deleted,
1016
- resetDisposition: deleted ? 'deletedFile' : 'absent',
1017
- });
1018
- }
1019
- return receipts;
852
+ return [await resetPluginConfigStore(options)];
1020
853
  }
1021
854
  function coerceConfigValue(key, field, value) {
1022
855
  let coerced = value;
@@ -1039,19 +872,9 @@ function coerceConfigValue(key, field, value) {
1039
872
  export async function coercePluginConfigValue(options) {
1040
873
  return coerceConfigValue(options.key, options.field, options.value);
1041
874
  }
1042
- function validateConfigReadValue(key, field, value) {
1043
- const issues = validateCorePluginConfigValue(field, value, key);
1044
- if (issues.length > 0) {
1045
- throw new Error(formatPluginConfigIssue(issues[0]));
1046
- }
1047
- return value;
1048
- }
1049
875
  function redactValue(field, value) {
1050
876
  return redactPluginConfigValue(field, value);
1051
877
  }
1052
- function sourceForStoreLayer(scope) {
1053
- return scope === 'user' ? 'user' : 'project';
1054
- }
1055
878
  async function applyConfigLayer(options) {
1056
879
  for (const [key, field] of Object.entries(options.schema)) {
1057
880
  if (!Object.hasOwn(options.entries, key)) {
@@ -1087,65 +910,25 @@ async function applyConfigLayer(options) {
1087
910
  }
1088
911
  }
1089
912
  export async function resolveEffectivePluginConfig(options) {
913
+ const workContext = options.context ? await resolveWorkContext({ cwd: options.cwd, context: options.context }) : undefined;
1090
914
  const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
1091
- const schema = options.manifest.config?.fields;
1092
- if (!schema) {
1093
- throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
1094
- }
1095
- const projectRoot = resolveAwProjectRoot(options.cwd);
1096
- const userStore = await readPluginConfigStore({
1097
- sourceId: options.sourceId,
1098
- sourceIdentity: options.sourceIdentity,
1099
- logicalIdentity: options.logicalIdentity,
1100
- pluginName,
1101
- scope: 'user',
1102
- cwd: options.cwd,
1103
- env: options.env,
1104
- });
1105
- const projectStore = options.targetScope === 'user'
1106
- ? undefined
1107
- : await readPluginConfigStore({
1108
- sourceId: options.sourceId,
1109
- sourceIdentity: options.sourceIdentity,
1110
- logicalIdentity: options.logicalIdentity,
1111
- pluginName,
1112
- scope: 'project',
1113
- cwd: options.cwd,
1114
- env: options.env,
1115
- });
1116
- assertReadyPluginConfigStore(userStore);
1117
- if (projectStore)
1118
- assertReadyPluginConfigStore(projectStore);
915
+ const user = await readPluginConfigStore({ ...options, pluginName, scope: 'user' });
916
+ assertReadyPluginConfigStore(user);
1119
917
  const references = !options.referenceContext && pluginConfigHasExternalReferences(options.manifest)
1120
- ? await resolvePluginConfigReferenceContext({ ...options, scope: options.targetScope ?? 'project', plugins: options.referencePlugins })
1121
- : undefined;
1122
- return resolveEffectivePluginConfigFromStores({
1123
- pluginName,
1124
- manifest: options.manifest,
1125
- targetScope: options.targetScope,
1126
- projectRoot,
1127
- referenceContext: options.referenceContext ?? references?.context,
1128
- referenceEvidence: references?.evidence,
1129
- stores: {
1130
- user: userStore,
1131
- ...(projectStore ? { project: projectStore } : {}),
1132
- },
1133
- });
918
+ ? await resolvePluginConfigReferenceContext({ ...options, scope: options.context ?? options.targetScope ?? 'user', plugins: options.referencePlugins }) : undefined;
919
+ return resolveEffectivePluginConfigFromStores({ ...options, pluginName, projectRoot: workContext?.projectRoot ?? ((options.context ?? options.targetScope) === 'project' ? resolveAwProjectRoot(options.cwd) : ''),
920
+ referenceContext: options.referenceContext ?? references?.context, referenceEvidence: references?.evidence,
921
+ stores: { user } });
1134
922
  }
1135
923
  export async function resolveEffectivePluginConfigFromStores(options) {
1136
924
  const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
1137
- const schema = options.manifest.config?.fields;
925
+ const definition = options.manifest.config ? compilePluginConfigDefinition(options.manifest.config, options.platform) : undefined;
926
+ const schema = definition?.fields;
1138
927
  if (!schema) {
1139
- throw new Error(`Plugin "${pluginName}" does not declare an AW config schema.`);
928
+ throw new PluginConfigSchemaResolutionError('configSchemaUnavailable', `Plugin "${pluginName}" does not declare an AW config schema.`);
1140
929
  }
1141
930
  const userStore = options.stores.user;
1142
- const projectStore = options.targetScope === 'user' ? undefined : options.stores.project;
1143
931
  assertReadyPluginConfigStore(userStore);
1144
- if (!projectStore && options.targetScope !== 'user') {
1145
- throw new Error(`Project plugin config store is required for ${pluginName}.`);
1146
- }
1147
- if (projectStore)
1148
- assertReadyPluginConfigStore(projectStore);
1149
932
  const values = {};
1150
933
  const redactedValues = {};
1151
934
  const sources = {};
@@ -1154,7 +937,6 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1154
937
  const unsupportedLayerOverrides = [];
1155
938
  const dangerousKeyIssues = [];
1156
939
  const schemaKeys = new Set(Object.keys(schema));
1157
- const userOnlyKeys = codexNativeRefreshKeys(options.manifest);
1158
940
  for (const key of schemaKeys) {
1159
941
  sources[key] = 'missing';
1160
942
  }
@@ -1172,54 +954,31 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1172
954
  source: 'default',
1173
955
  });
1174
956
  }
1175
- const layers = options.targetScope === 'user'
1176
- ? [['user', userStore]]
1177
- : [
1178
- ['user', userStore],
1179
- ['project', projectStore],
1180
- ];
1181
- for (const [scope, store] of layers) {
1182
- const plainEntries = getStoreEntries(store.values);
1183
- const source = sourceForStoreLayer(scope);
1184
- const dangerousIssues = new Map(validatePluginConfigDocument(options.manifest.config, plainEntries).filter((issue) => issue.code === 'dangerousKey')
1185
- .map((issue) => [issue.path.field, issue]));
957
+ const layers = [
958
+ { entries: userStore.common ?? userStore.values, source: 'common' },
959
+ ...(options.platform ? [{ entries: userStore.platforms?.[options.platform] ?? {}, source: 'platform' }] : []),
960
+ ];
961
+ for (const { entries, source } of layers) {
962
+ const plainEntries = getStoreEntries(entries);
963
+ const dangerousIssues = new Map(validatePluginConfigDocument(definition, plainEntries)
964
+ .filter((issue) => issue.code === 'dangerousKey').map((issue) => [issue.path.field, issue]));
1186
965
  for (const key of Object.keys(plainEntries)) {
1187
- if (!schemaKeys.has(key)) {
1188
- const dangerousIssue = dangerousIssues.get(key);
1189
- if (dangerousIssue) {
1190
- dangerousKeyIssues.push({
1191
- type: 'invalid-config',
1192
- code: dangerousIssue.code,
1193
- key,
1194
- source,
1195
- message: formatPluginConfigIssue(dangerousIssue),
1196
- });
1197
- }
1198
- else {
1199
- ignoredUnknownKeys.push({ scope, source, key });
1200
- }
1201
- }
966
+ if (schemaKeys.has(key))
967
+ continue;
968
+ const dangerousIssue = dangerousIssues.get(key);
969
+ if (dangerousIssue)
970
+ dangerousKeyIssues.push({ type: 'invalid-config', code: dangerousIssue.code,
971
+ key, source, message: formatPluginConfigIssue(dangerousIssue) });
972
+ else
973
+ ignoredUnknownKeys.push({ scope: 'user', source, key });
1202
974
  }
1203
- const supportedEntries = scope === 'project'
1204
- ? Object.fromEntries(Object.entries(plainEntries).filter(([key]) => {
1205
- if (!schemaKeys.has(key) || !userOnlyKeys.has(key))
1206
- return true;
1207
- unsupportedLayerOverrides.push({
1208
- type: 'configLayerUnsupported',
1209
- key,
1210
- scope: 'project',
1211
- source: 'project',
1212
- });
1213
- return false;
1214
- }))
1215
- : plainEntries;
1216
975
  await applyConfigLayer({
1217
976
  values,
1218
977
  redactedValues,
1219
978
  sources,
1220
979
  fieldStates,
1221
980
  schema,
1222
- entries: supportedEntries,
981
+ entries: plainEntries,
1223
982
  source,
1224
983
  });
1225
984
  }
@@ -1228,12 +987,12 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1228
987
  const source = sources[key];
1229
988
  if (source === 'default')
1230
989
  candidateValues[key] = field.default;
1231
- if (source === 'user')
1232
- candidateValues[key] = ownStoreValue(userStore.values, key);
1233
- if (source === 'project' && projectStore)
1234
- candidateValues[key] = ownStoreValue(projectStore.values, key);
990
+ if (source === 'common')
991
+ candidateValues[key] = ownStoreValue(userStore.common ?? userStore.values, key);
992
+ if (source === 'platform' && options.platform)
993
+ candidateValues[key] = ownStoreValue(userStore.platforms?.[options.platform] ?? {}, key);
1235
994
  }
1236
- const candidateIssues = validatePluginConfigCandidate(options.manifest.config, candidateValues, undefined, options.referenceContext);
995
+ const candidateIssues = validatePluginConfigCandidate(definition, candidateValues, undefined, options.referenceContext);
1237
996
  const referenceIssues = candidateIssues.filter((issue) => issue.code.startsWith('valueRef')
1238
997
  && (!issue.path.plugin || issue.path.plugin === pluginName));
1239
998
  const missingReferenceIssues = [];
@@ -1263,7 +1022,7 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1263
1022
  for (const key of schemaKeys) {
1264
1023
  let dependencies;
1265
1024
  try {
1266
- dependencies = getPluginConfigDependencyFields(options.manifest.config, [key], options.referenceContext);
1025
+ dependencies = getPluginConfigDependencyFields(definition, [key], options.referenceContext);
1267
1026
  }
1268
1027
  catch {
1269
1028
  continue; // The local association issue already reports the invalid schema.
@@ -1294,7 +1053,7 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1294
1053
  for (const key of schemaKeys) {
1295
1054
  if (invalidKeys.has(key))
1296
1055
  continue;
1297
- const dependency = getPluginConfigDependencyKeys(options.manifest.config, [key])
1056
+ const dependency = getPluginConfigDependencyKeys(definition, [key])
1298
1057
  .find((dependencyKey) => dependencyKey !== key && invalidKeys.has(dependencyKey));
1299
1058
  if (dependency === undefined)
1300
1059
  continue;
@@ -1333,6 +1092,9 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1333
1092
  }
1334
1093
  return {
1335
1094
  pluginName,
1095
+ platform: options.platform,
1096
+ context: options.context ?? options.targetScope ?? 'user',
1097
+ definition: definition,
1336
1098
  projectRoot: options.projectRoot,
1337
1099
  values,
1338
1100
  redactedValues,
@@ -1344,7 +1106,6 @@ export async function resolveEffectivePluginConfigFromStores(options) {
1344
1106
  schema,
1345
1107
  stores: {
1346
1108
  user: userStore,
1347
- ...(projectStore ? { project: projectStore } : {}),
1348
1109
  },
1349
1110
  ...(options.referenceContext ? { referenceContext: options.referenceContext } : {}),
1350
1111
  ...(options.referenceEvidence ? { referenceEvidence: options.referenceEvidence } : {}),
@@ -1356,43 +1117,26 @@ function ownStoreValue(values, key) {
1356
1117
  /** Check cross-field relations before saving one complete layer candidate. */
1357
1118
  export async function assertPluginConfigMutationCandidate(options) {
1358
1119
  const user = await readPluginConfigStore({ ...options, scope: 'user' });
1359
- const project = options.scope === 'project'
1360
- ? await readPluginConfigStore({ ...options, scope: 'project' })
1361
- : undefined;
1362
1120
  assertReadyPluginConfigStore(user);
1363
- if (project)
1364
- assertReadyPluginConfigStore(project);
1365
- const edited = options.scope === 'user' ? user : project;
1366
- const values = { ...edited.values };
1121
+ const values = { ...user.values };
1367
1122
  for (const key of options.removeKeys)
1368
1123
  delete values[key];
1369
1124
  for (const [key, value] of Object.entries(options.updates))
1370
1125
  values[key] = value;
1371
- const candidateStore = { ...edited, values };
1372
- const references = pluginConfigHasExternalReferences(options.manifest)
1373
- ? await resolvePluginConfigReferenceContext({ ...options, plugins: options.referencePlugins }) : undefined;
1374
- const effective = await resolveEffectivePluginConfigFromStores({
1375
- pluginName: options.pluginName,
1376
- manifest: options.manifest,
1377
- targetScope: options.scope,
1378
- projectRoot: resolveAwProjectRoot(options.cwd),
1379
- referenceContext: references?.context,
1380
- stores: {
1381
- user: options.scope === 'user' ? candidateStore : user,
1382
- ...(project ? { project: candidateStore } : {}),
1383
- },
1384
- });
1385
- const before = await resolveEffectivePluginConfigFromStores({ manifest: options.manifest,
1386
- targetScope: options.scope, projectRoot: resolveAwProjectRoot(options.cwd),
1387
- stores: { user, ...(project ? { project } : {}) }, referenceContext: references?.context });
1388
- const existingIssues = new Set(before.issues.map((issue) => `${issue.key}\0${issue.code}\0${issue.message}`));
1389
- const editedKeys = new Set([...Object.keys(options.updates), ...options.removeKeys]);
1390
- const issues = effective.issues.filter((issue) => issue.code?.startsWith('valueRef')
1391
- && (editedKeys.has(issue.key) || !existingIssues.has(`${issue.key}\0${issue.code}\0${issue.message}`)));
1392
- if (issues.length > 0) {
1393
- throw Object.assign(new Error(`configReferenceInvalid: ${issues.map((issue) => issue.message).join(' ')}`), {
1394
- code: 'configReferenceInvalid',
1395
- });
1126
+ const candidate = replacePluginConfigStoreLayer(user, values, options.platform);
1127
+ const profiles = options.platform ? [options.platform] : [undefined, ...PLATFORM_TARGETS];
1128
+ for (const platform of profiles) {
1129
+ const references = pluginConfigHasExternalReferences(options.manifest)
1130
+ ? await resolvePluginConfigReferenceContext({ ...options, platform, plugins: options.referencePlugins }) : undefined;
1131
+ const resolve = (store) => resolveEffectivePluginConfigFromStores({ ...options, platform,
1132
+ targetScope: options.scope, projectRoot: options.scope === 'project' ? resolveAwProjectRoot(options.cwd) : '', stores: { user: store }, referenceContext: references?.context });
1133
+ const [before, after] = await Promise.all([resolve(user), resolve(candidate)]);
1134
+ const existing = new Set(before.issues.map((issue) => `${issue.key}\0${issue.code}\0${issue.message}`));
1135
+ const editedKeys = new Set([...Object.keys(options.updates), ...options.removeKeys]);
1136
+ const issues = after.issues.filter((issue) => (editedKeys.has(issue.key) && Object.hasOwn(options.updates, issue.key))
1137
+ || !existing.has(`${issue.key}\0${issue.code}\0${issue.message}`));
1138
+ if (issues.length)
1139
+ throw Object.assign(new Error(`configReferenceInvalid: ${platform ?? 'common'}: ${issues.map((issue) => issue.message).join(' ')}`), { code: 'configReferenceInvalid' });
1396
1140
  }
1397
1141
  }
1398
1142
  export async function coercePluginConfigUpdates(options) {
@@ -1414,7 +1158,7 @@ export async function coercePluginConfigUpdates(options) {
1414
1158
  export async function resolveProjectArtifactRenderConfig(options) {
1415
1159
  if (!options.manifest.config?.fields) {
1416
1160
  const pluginName = assertSafePluginConfigSegment(options.pluginName ?? options.manifest.name);
1417
- const [userStore, projectStore] = await Promise.all([
1161
+ const [userStore] = await Promise.all([
1418
1162
  readPluginConfigStore({
1419
1163
  sourceId: options.sourceId,
1420
1164
  sourceIdentity: options.sourceIdentity,
@@ -1424,18 +1168,8 @@ export async function resolveProjectArtifactRenderConfig(options) {
1424
1168
  cwd: options.cwd,
1425
1169
  env: options.env,
1426
1170
  }),
1427
- readPluginConfigStore({
1428
- sourceId: options.sourceId,
1429
- sourceIdentity: options.sourceIdentity,
1430
- logicalIdentity: options.logicalIdentity,
1431
- pluginName,
1432
- scope: 'project',
1433
- cwd: options.cwd,
1434
- env: options.env,
1435
- }),
1436
1171
  ]);
1437
1172
  assertReadyPluginConfigStore(userStore);
1438
- assertReadyPluginConfigStore(projectStore);
1439
1173
  return {
1440
1174
  pluginName,
1441
1175
  projectRoot: resolveAwProjectRoot(options.cwd),
@@ -1445,7 +1179,7 @@ export async function resolveProjectArtifactRenderConfig(options) {
1445
1179
  unsupportedLayerOverrides: [],
1446
1180
  ignoredUnknownKeys: [],
1447
1181
  schema: {},
1448
- stores: { user: userStore, project: projectStore },
1182
+ stores: { user: userStore },
1449
1183
  };
1450
1184
  }
1451
1185
  const effective = await resolveEffectivePluginConfig({
@@ -1463,101 +1197,11 @@ export async function resolveProjectArtifactRenderConfig(options) {
1463
1197
  schema: effective.schema,
1464
1198
  stores: {
1465
1199
  user: effective.stores.user,
1466
- project: effective.stores.project,
1467
- },
1468
- };
1469
- }
1470
- async function resolveRedactedLayerValue(options) {
1471
- try {
1472
- const value = validateConfigReadValue(options.key, options.field, options.value);
1473
- return {
1474
- value: redactValue(options.field, value),
1475
- };
1476
- }
1477
- catch (error) {
1478
- return {
1479
- issue: {
1480
- type: 'invalid-config',
1481
- key: options.key,
1482
- source: options.source,
1483
- message: error instanceof Error ? error.message : String(error),
1484
- },
1485
- };
1486
- }
1487
- }
1488
- export async function resolveProjectScopedPluginConfigView(options) {
1489
- const effective = await resolveEffectivePluginConfig(options);
1490
- const entries = {};
1491
- for (const [key, field] of Object.entries(effective.schema)) {
1492
- const entry = {
1493
- key,
1494
- effectiveValue: effective.redactedValues[key],
1495
- effectiveSource: effective.sources[key] ?? 'missing',
1496
- };
1497
- if (field.default !== undefined) {
1498
- const resolvedDefault = await resolveRedactedLayerValue({
1499
- key,
1500
- field,
1501
- source: 'default',
1502
- value: field.default,
1503
- });
1504
- if (resolvedDefault.value !== undefined) {
1505
- entry.defaultValue = resolvedDefault.value;
1506
- }
1507
- }
1508
- const userPlainEntries = getStoreEntries(effective.stores.user.values);
1509
- if (Object.hasOwn(userPlainEntries, key)) {
1510
- const resolvedUser = await resolveRedactedLayerValue({
1511
- key,
1512
- field,
1513
- source: 'user',
1514
- value: userPlainEntries[key],
1515
- });
1516
- if (resolvedUser.value !== undefined) {
1517
- entry.userValue = resolvedUser.value;
1518
- }
1519
- }
1520
- const projectPlainEntries = getStoreEntries(effective.stores.project.values);
1521
- if (Object.hasOwn(projectPlainEntries, key)) {
1522
- const resolvedProject = await resolveRedactedLayerValue({
1523
- key,
1524
- field,
1525
- source: 'project',
1526
- value: projectPlainEntries[key],
1527
- });
1528
- if (resolvedProject.value !== undefined) {
1529
- entry.projectValue = resolvedProject.value;
1530
- }
1531
- }
1532
- entries[key] = entry;
1533
- }
1534
- return {
1535
- pluginName: effective.pluginName,
1536
- projectRoot: effective.projectRoot,
1537
- schema: effective.schema,
1538
- stores: {
1539
- user: effective.stores.user,
1540
- project: effective.stores.project,
1541
1200
  },
1542
- redactedValues: effective.redactedValues,
1543
- sources: effective.sources,
1544
- entries,
1545
- issues: effective.issues,
1546
- unsupportedLayerOverrides: effective.unsupportedLayerOverrides,
1547
- ignoredUnknownKeys: effective.ignoredUnknownKeys,
1548
1201
  };
1549
1202
  }
1550
1203
  function toConfigSourceLayer(source) {
1551
- if (source === 'default') {
1552
- return 'default';
1553
- }
1554
- if (source === 'user') {
1555
- return 'user';
1556
- }
1557
- if (source === 'project') {
1558
- return 'project';
1559
- }
1560
- return null;
1204
+ return source === undefined || source === 'missing' ? null : source;
1561
1205
  }
1562
1206
  function configFieldProjection(options) {
1563
1207
  const source = options.source ?? 'missing';
@@ -1578,265 +1222,74 @@ function configFieldProjection(options) {
1578
1222
  };
1579
1223
  }
1580
1224
  async function buildManifestFieldEntries(options) {
1581
- const schema = options.plugin.manifest.config?.fields;
1582
- if (!schema) {
1583
- return [];
1584
- }
1585
- const userStore = await readPluginConfigStore({
1586
- sourceId: options.plugin.sourceId,
1587
- sourceIdentity: options.plugin.sourceIdentity,
1588
- logicalIdentity: options.plugin.catalogLogicalIdentity,
1589
- pluginName: options.plugin.manifest.name,
1590
- scope: 'user',
1591
- cwd: options.cwd,
1592
- env: options.env,
1593
- schema,
1594
- });
1595
- const projectStore = options.scope === 'user'
1596
- ? undefined
1597
- : await readPluginConfigStore({
1598
- sourceId: options.plugin.sourceId,
1599
- sourceIdentity: options.plugin.sourceIdentity,
1600
- logicalIdentity: options.plugin.catalogLogicalIdentity,
1601
- pluginName: options.plugin.manifest.name,
1602
- scope: 'project',
1603
- cwd: options.cwd,
1604
- env: options.env,
1605
- schema,
1606
- });
1607
- assertReadyPluginConfigStore(userStore);
1608
- if (projectStore)
1609
- assertReadyPluginConfigStore(projectStore);
1610
- const userPlainEntries = getStoreEntries(userStore.values);
1611
- const projectPlainEntries = projectStore ? getStoreEntries(projectStore.values) : {};
1612
1225
  const effective = await resolveEffectivePluginConfig({
1613
1226
  sourceId: options.plugin.sourceId,
1614
1227
  sourceIdentity: options.plugin.sourceIdentity,
1615
1228
  logicalIdentity: options.plugin.catalogLogicalIdentity,
1616
- pluginName: options.plugin.manifest.name,
1617
1229
  manifest: options.plugin.manifest,
1618
- targetScope: options.scope === 'user' ? 'user' : 'project',
1230
+ platform: options.platform,
1231
+ targetScope: options.context ?? (options.scope === 'project' ? 'project' : 'user'),
1619
1232
  cwd: options.cwd,
1620
1233
  env: options.env,
1621
1234
  });
1622
- const userOnlyKeys = codexNativeRefreshKeys(options.plugin.manifest);
1623
- const projectRemovalFallback = (key) => {
1624
- const field = schema[key];
1625
- if (Object.hasOwn(userPlainEntries, key)) {
1626
- const value = userPlainEntries[key];
1627
- const validationIssues = validateCorePluginConfigValue(field, value, key);
1628
- if (validationIssues.length > 0) {
1629
- return {
1630
- state: 'invalid',
1631
- sourceLayer: 'user',
1632
- issues: validationIssues.map((issue) => ({
1633
- type: 'invalid-config',
1634
- key,
1635
- source: 'user',
1636
- message: formatPluginConfigIssue(issue),
1637
- })),
1638
- };
1639
- }
1640
- return {
1641
- state: 'valid',
1642
- effectiveValue: redactValue(field, value),
1643
- sourceLayer: 'user',
1644
- };
1645
- }
1646
- if (field.default === undefined)
1647
- return { state: 'missing' };
1648
- const validationIssues = validateCorePluginConfigValue(field, field.default, key);
1649
- if (validationIssues.length > 0) {
1650
- return {
1651
- state: 'invalid',
1652
- sourceLayer: 'default',
1653
- issues: validationIssues.map((issue) => ({
1654
- type: 'invalid-config',
1655
- key,
1656
- source: 'default',
1657
- message: formatPluginConfigIssue(issue),
1658
- })),
1659
- };
1660
- }
1661
- return {
1662
- state: 'valid',
1663
- effectiveValue: redactValue(field, field.default),
1664
- sourceLayer: 'default',
1665
- };
1666
- };
1667
- const effectiveFieldState = (key) => {
1668
- const source = effective.sources[key];
1669
- const sourceLayer = toConfigSourceLayer(source);
1670
- const field = schema[key];
1671
- const fieldIssues = effective.issues.filter((entry) => entry.key === key);
1672
- const fieldState = effective.fieldStates[key];
1673
- const fallback = options.scope === 'project'
1674
- ? projectRemovalFallback(key)
1675
- : fieldState?.fallback
1676
- ? {
1677
- state: 'valid',
1678
- effectiveValue: fieldState.fallback.redactedValue,
1679
- sourceLayer: fieldState.fallback.source,
1680
- }
1681
- : undefined;
1682
- if (source === undefined || source === 'missing') {
1683
- return {
1684
- valueState: fieldIssues.length > 0 ? 'invalid' : 'default',
1685
- effectiveValue: null,
1686
- sourceLayer: null,
1687
- ...configFieldProjection({ field, source: 'missing', issues: fieldIssues }),
1688
- };
1235
+ const stored = pluginConfigStoreForProfile(effective.stores.user, options.platform).values;
1236
+ const fields = await Promise.all(Object.entries(effective.schema).map(async ([key, field]) => {
1237
+ const state = effective.fieldStates[key];
1238
+ const source = effective.sources[key] ?? 'missing';
1239
+ const issues = effective.issues.filter((issue) => issue.key === key);
1240
+ const hasStored = Object.hasOwn(stored, key);
1241
+ const storedSourceLayer = options.platform ? 'platform' : 'common';
1242
+ let fallback;
1243
+ if (hasStored) {
1244
+ const inherited = { ...stored };
1245
+ delete inherited[key];
1246
+ const withoutOverride = await resolveEffectivePluginConfigFromStores({
1247
+ manifest: options.plugin.manifest,
1248
+ platform: options.platform,
1249
+ projectRoot: effective.projectRoot,
1250
+ stores: { user: replacePluginConfigStoreLayer(effective.stores.user, inherited, options.platform) },
1251
+ referenceContext: effective.referenceContext,
1252
+ });
1253
+ const fallbackIssues = withoutOverride.issues.filter((issue) => issue.key === key);
1254
+ const fallbackSource = withoutOverride.sources[key];
1255
+ fallback = fallbackSource === 'missing' || fallbackSource === undefined
1256
+ ? { state: 'missing' }
1257
+ : fallbackIssues.length
1258
+ ? { state: 'invalid', sourceLayer: fallbackSource, issues: fallbackIssues }
1259
+ : { state: 'valid', sourceLayer: fallbackSource, effectiveValue: withoutOverride.redactedValues[key] };
1689
1260
  }
1690
- const issue = fieldIssues.find((entry) => entry.source === source);
1691
1261
  return {
1692
- valueState: issue ? 'invalid' : source === 'default' ? 'default' : 'configured',
1693
- effectiveValue: issue ? null : effective.redactedValues[key] ?? null,
1694
- sourceLayer,
1695
- ...(fieldState?.invalidOverride ? {
1696
- invalidOverride: {
1697
- sourceLayer: fieldState.invalidOverride.source,
1698
- issues: fieldState.invalidOverride.issues,
1699
- },
1700
- } : {}),
1701
- ...(fallback ? { fallback } : {}),
1702
- ...configFieldProjection({
1703
- field,
1704
- value: issue ? undefined : effective.values[key],
1705
- source,
1706
- issues: fieldIssues,
1707
- }),
1708
- };
1709
- };
1710
- const fields = [];
1711
- for (const [key, field] of Object.entries(schema)) {
1712
- const canSet = options.scope !== 'project'
1713
- || (!userOnlyKeys.has(key) && field.sensitive !== true);
1714
- const base = {
1715
1262
  key,
1716
1263
  title: field.title ?? key,
1717
1264
  kind: 'manifest',
1718
1265
  reserved: false,
1719
- writable: canSet,
1720
- canSet,
1721
- canUnset: options.scope === 'user'
1722
- ? Object.hasOwn(userPlainEntries, key)
1723
- : options.scope === 'project' && Object.hasOwn(projectPlainEntries, key),
1724
- valueState: 'default',
1725
- effectiveValue: null,
1726
- sourceLayer: null,
1727
- ...configFieldProjection({ field }),
1266
+ writable: true,
1267
+ canSet: true,
1268
+ canUnset: hasStored,
1269
+ valueState: issues.length ? 'invalid' : source === 'default' || source === 'missing' ? 'default' : 'configured',
1270
+ effectiveValue: effective.redactedValues[key] ?? null,
1271
+ sourceLayer: toConfigSourceLayer(source),
1272
+ ...(hasStored ? { storedSourceLayer, ...(validateCorePluginConfigValue(field, stored[key], key).length
1273
+ ? {} : { storedValue: redactValue(field, stored[key]) }) } : {}),
1274
+ ...(state?.invalidOverride ? { invalidOverride: { sourceLayer: state.invalidOverride.source, issues: state.invalidOverride.issues } } : {}),
1275
+ ...(fallback ? { fallback } : {}),
1276
+ ...configFieldProjection({ field, value: effective.values[key], source, issues }),
1728
1277
  };
1729
- if (options.scope === 'user') {
1730
- if (Object.hasOwn(userPlainEntries, key)) {
1731
- try {
1732
- const value = validateConfigReadValue(key, field, userPlainEntries[key]);
1733
- fields.push({
1734
- ...base,
1735
- valueState: 'configured',
1736
- effectiveValue: redactValue(field, value),
1737
- sourceLayer: 'user',
1738
- storedValue: redactValue(field, value),
1739
- storedSourceLayer: 'user',
1740
- ...configFieldProjection({ field, value, source: 'user' }),
1741
- });
1742
- }
1743
- catch {
1744
- fields.push({
1745
- ...base,
1746
- valueState: 'invalid',
1747
- sourceLayer: 'user',
1748
- source: 'user',
1749
- storedSourceLayer: 'user',
1750
- valid: false,
1751
- issues: [{ type: 'invalid-config', key, source: 'user' }],
1752
- });
1753
- }
1754
- continue;
1755
- }
1756
- if (field.default !== undefined) {
1757
- try {
1758
- const value = validateConfigReadValue(key, field, field.default);
1759
- fields.push({
1760
- ...base,
1761
- effectiveValue: redactValue(field, value),
1762
- sourceLayer: 'default',
1763
- ...configFieldProjection({ field, value, source: 'default' }),
1764
- });
1765
- }
1766
- catch {
1767
- fields.push({
1768
- ...base,
1769
- valueState: 'invalid',
1770
- sourceLayer: 'default',
1771
- source: 'default',
1772
- valid: false,
1773
- issues: [{ type: 'invalid-config', key, source: 'default' }],
1774
- });
1775
- }
1776
- continue;
1777
- }
1778
- fields.push(base);
1779
- continue;
1780
- }
1781
- let storedProjectValue;
1782
- if (options.scope === 'project' && Object.hasOwn(projectPlainEntries, key)) {
1783
- try {
1784
- const value = validateConfigReadValue(key, field, projectPlainEntries[key]);
1785
- storedProjectValue = {
1786
- storedValue: redactValue(field, value),
1787
- storedSourceLayer: 'project',
1788
- };
1789
- }
1790
- catch {
1791
- storedProjectValue = { storedSourceLayer: 'project' };
1792
- }
1793
- }
1794
- fields.push({ ...base, ...effectiveFieldState(key), ...storedProjectValue });
1278
+ }));
1279
+ for (const key of Object.keys(stored).filter((key) => !Object.hasOwn(effective.schema, key)).sort()) {
1280
+ const source = options.platform ? 'platform' : 'common';
1281
+ fields.push({ key, title: key, kind: 'manifest', reserved: false, writable: false, canSet: false, canUnset: true,
1282
+ valueState: 'configured', effectiveValue: '[redacted]', sourceLayer: source, storedSourceLayer: source,
1283
+ value: '[redacted]', source, matchedPreset: null, valid: false, issues: [] });
1795
1284
  }
1796
1285
  return fields;
1797
1286
  }
1798
1287
  async function buildLegacyStoreFieldEntries(options) {
1799
- const locations = options.scope === 'effective'
1800
- ? [
1801
- { scope: 'user', sourceLayer: 'user' },
1802
- { scope: 'project', sourceLayer: 'project' },
1803
- ]
1804
- : [{
1805
- scope: options.scope,
1806
- sourceLayer: options.scope === 'user' ? 'user' : 'project',
1807
- }];
1808
- const fields = new Map();
1809
- for (const location of locations) {
1810
- const store = await readPluginConfigStore({
1811
- sourceId: options.sourceId,
1812
- sourceIdentity: options.sourceIdentity,
1813
- logicalIdentity: options.logicalIdentity,
1814
- pluginName: options.pluginName,
1815
- scope: location.scope,
1816
- cwd: options.cwd,
1817
- env: options.env,
1818
- });
1819
- for (const key of Object.keys(store.values)) {
1820
- fields.set(key, {
1821
- key,
1822
- title: key,
1823
- kind: 'manifest',
1824
- reserved: false,
1825
- writable: false,
1826
- canSet: false,
1827
- canUnset: false,
1828
- valueState: 'configured',
1829
- effectiveValue: '[redacted]',
1830
- sourceLayer: location.sourceLayer,
1831
- value: '[redacted]',
1832
- source: location.sourceLayer,
1833
- matchedPreset: null,
1834
- valid: true,
1835
- issues: [],
1836
- });
1837
- }
1838
- }
1839
- return [...fields.values()].sort((left, right) => left.key.localeCompare(right.key));
1288
+ const store = await readPluginConfigStore({ ...options, scope: 'user' });
1289
+ const source = options.platform ? 'platform' : 'common';
1290
+ return Object.keys(store.values).sort().map((key) => ({ key, title: key, kind: 'manifest', reserved: false,
1291
+ writable: false, canSet: false, canUnset: true, valueState: 'configured', effectiveValue: '[redacted]',
1292
+ sourceLayer: source, value: '[redacted]', source, matchedPreset: null, valid: true, issues: [] }));
1840
1293
  }
1841
1294
  export async function resolvePluginConfigSchemaAccess(options) {
1842
1295
  const pluginName = assertSafePluginConfigSegment(options.pluginName);
@@ -1852,15 +1305,18 @@ export async function resolvePluginConfigSchemaAccess(options) {
1852
1305
  return {
1853
1306
  pluginName,
1854
1307
  plugin: options.currentPlugin,
1855
- schema: createConfigSchemaEvidenceFromCurrentCatalog(options.currentPlugin, await calculateConfigSchemaManifestDigestForPlugin(options.currentPlugin)),
1308
+ schema: createConfigSchemaEvidenceFromCurrentCatalog(options.currentPlugin, await calculateConfigSchemaManifestDigestForPlugin(options.currentPlugin), options.platform),
1856
1309
  };
1857
1310
  }
1858
1311
  export async function resolveConfigRead(options) {
1312
+ if (options.context)
1313
+ await resolveWorkContext({ cwd: options.cwd, context: options.context });
1859
1314
  const pluginName = assertSafePluginConfigSegment(options.pluginName);
1860
1315
  const schemaAccess = await resolvePluginConfigSchemaAccess({
1861
1316
  pluginName,
1862
1317
  currentPlugin: options.plugin,
1863
1318
  currentPluginError: options.pluginError,
1319
+ platform: options.platform,
1864
1320
  scope: options.scope,
1865
1321
  cwd: options.cwd,
1866
1322
  env: options.env,
@@ -1868,6 +1324,8 @@ export async function resolveConfigRead(options) {
1868
1324
  const manifestFields = schemaAccess.plugin
1869
1325
  ? await buildManifestFieldEntries({
1870
1326
  plugin: schemaAccess.plugin,
1327
+ context: options.context,
1328
+ platform: options.platform,
1871
1329
  scope: options.scope,
1872
1330
  cwd: options.cwd,
1873
1331
  env: options.env,
@@ -1878,15 +1336,18 @@ export async function resolveConfigRead(options) {
1878
1336
  logicalIdentity: options.logicalIdentity,
1879
1337
  pluginName,
1880
1338
  scope: options.scope,
1339
+ platform: options.platform,
1881
1340
  cwd: options.cwd,
1882
1341
  env: options.env,
1883
1342
  });
1884
1343
  return {
1885
1344
  pluginName,
1345
+ platform: options.platform,
1346
+ context: options.context ?? 'user',
1886
1347
  scope: options.scope,
1887
1348
  ...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
1888
1349
  ...(schemaAccess.plugin?.manifest.config?.fields
1889
- ? { fieldDefinitions: schemaAccess.plugin.manifest.config.fields }
1350
+ ? { fieldDefinitions: compilePluginConfigDefinition(schemaAccess.plugin.manifest.config, options.platform).fields }
1890
1351
  : {}),
1891
1352
  fields: manifestFields,
1892
1353
  };
@@ -1914,13 +1375,14 @@ export function summarizeEffectivePluginConfig(config) {
1914
1375
  ignoredUnknownKeys: config.ignoredUnknownKeys,
1915
1376
  };
1916
1377
  }
1917
- export function createPluginConfigSchemaIdentity(manifest) {
1378
+ export function createPluginConfigSchemaIdentity(manifest, platform) {
1918
1379
  const hash = crypto
1919
1380
  .createHash('sha256')
1920
1381
  .update(JSON.stringify({
1921
1382
  name: manifest.name,
1922
1383
  version: manifest.version,
1923
- config: manifest.config ?? null,
1384
+ platform: platform ?? null,
1385
+ config: manifest.config ? compilePluginConfigDefinition(manifest.config, platform) : null,
1924
1386
  }))
1925
1387
  .digest('hex');
1926
1388
  return `${manifest.name}:${hash}`;
@@ -1961,9 +1423,9 @@ export function toCliConfigPromptSchema(schema) {
1961
1423
  return promptSchema;
1962
1424
  }
1963
1425
  export function createPluginConfigFormModel(options) {
1964
- const schema = options.manifest.config?.fields;
1426
+ const schema = options.effective.schema;
1965
1427
  if (!schema) {
1966
- throw new Error(`Plugin "${options.pluginName ?? options.manifest.name}" does not declare an AW config schema.`);
1428
+ throw new PluginConfigSchemaResolutionError('configSchemaUnavailable', `Plugin "${options.pluginName ?? options.manifest.name}" does not declare an AW config schema.`);
1967
1429
  }
1968
1430
  const requiredKeys = [...new Set(options.effective.issues
1969
1431
  .filter((issue) => issue.type === 'missing-required' || issue.type === 'invalid-config')
@@ -1990,8 +1452,8 @@ export function createPluginConfigFormModel(options) {
1990
1452
  });
1991
1453
  return {
1992
1454
  pluginName: options.pluginName ?? options.manifest.name,
1993
- allowedScopes: options.allowedScopes ?? ['user', 'project'],
1994
- initialScope: options.initialScope,
1455
+ allowedScopes: ['user'],
1456
+ initialScope: 'user',
1995
1457
  requiredKeys,
1996
1458
  fields,
1997
1459
  };
@@ -2030,21 +1492,17 @@ export function toCliConfigPromptOptions(model) {
2030
1492
  requiredKeys: [...model.requiredKeys],
2031
1493
  };
2032
1494
  }
2033
- export function formatPluginConfigSetRecoveryCommand(pluginName, key, field, scope) {
2034
- return field.sensitive === true
2035
- ? `aw config set ${pluginName} ${key} --scope user --value-stdin`
2036
- : `aw config set ${pluginName} ${key} --scope ${scope} --value-json '<json>'`;
1495
+ export function formatPluginConfigSetRecoveryCommand(pluginName, key, field, context, platform) {
1496
+ const command = `aw plugin config set ${pluginName} ${key} --context ${context}`
1497
+ + (platform ? ` --platform ${platform}` : '');
1498
+ return field.sensitive === true ? `${command} --value-stdin` : `${command} --value-json '<json>'`;
2037
1499
  }
2038
- function formatRequiredConfigSetCommands(pluginName, issues, defaultScope, schema) {
1500
+ function formatRequiredConfigSetCommands(pluginName, issues, context, schema, platform) {
2039
1501
  const commands = new Set();
2040
1502
  for (const issue of issues) {
2041
- const scope = issue.source === 'user' || issue.source === 'project'
2042
- ? issue.source
2043
- : defaultScope;
2044
1503
  const field = schema[issue.key];
2045
- if (!field)
2046
- continue;
2047
- commands.add(formatPluginConfigSetRecoveryCommand(pluginName, issue.key, field, scope));
1504
+ if (field)
1505
+ commands.add(formatPluginConfigSetRecoveryCommand(pluginName, issue.key, field, context, platform));
2048
1506
  }
2049
1507
  return [...commands].map((command) => `\`${command}\``).join(' 或 ');
2050
1508
  }
@@ -2057,8 +1515,8 @@ export async function preflightRequiredPluginConfigs(options) {
2057
1515
  };
2058
1516
  for (const target of options.targets) {
2059
1517
  const pluginName = target.pluginName ?? target.manifest.name;
2060
- const identity = target.schemaSourceIdentity ?? createPluginConfigSchemaIdentity(target.manifest);
2061
- const dedupeKey = `${pluginName}\0${identity}`;
1518
+ const identity = target.schemaSourceIdentity ?? createPluginConfigSchemaIdentity(target.manifest, target.platform);
1519
+ const dedupeKey = `${pluginName}\0${target.platform ?? 'common'}\0${identity}`;
2062
1520
  if (visited.has(dedupeKey)) {
2063
1521
  continue;
2064
1522
  }
@@ -2067,12 +1525,13 @@ export async function preflightRequiredPluginConfigs(options) {
2067
1525
  if (!target.manifest.config?.fields) {
2068
1526
  continue;
2069
1527
  }
2070
- let effective = await resolveEffectivePluginConfig({
1528
+ const effective = await resolveEffectivePluginConfig({
2071
1529
  sourceId: target.sourceId,
2072
1530
  sourceIdentity: target.sourceIdentity,
2073
1531
  logicalIdentity: target.logicalIdentity,
2074
1532
  pluginName,
2075
1533
  manifest: target.manifest,
1534
+ platform: target.platform,
2076
1535
  cwd: options.cwd,
2077
1536
  env: options.context.env,
2078
1537
  });
@@ -2081,7 +1540,7 @@ export async function preflightRequiredPluginConfigs(options) {
2081
1540
  continue;
2082
1541
  }
2083
1542
  const details = blockingIssues.map((issue) => issue.key).join(', ');
2084
- const setCommands = formatRequiredConfigSetCommands(pluginName, blockingIssues, options.defaultScope, target.manifest.config.fields);
1543
+ const setCommands = formatRequiredConfigSetCommands(target.sourceId ? `${target.sourceId}/${pluginName}` : pluginName, blockingIssues, options.defaultScope, effective.schema, target.platform);
2085
1544
  throw new Error(`${pluginName} requires valid AW config before install/upgrade: ${details}. ` +
2086
1545
  `Run ${setCommands}, or open \`aw\` to complete setup.`);
2087
1546
  }