@lesliechan721/aw 0.1.0-beta.10 → 0.1.0-beta.12

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 (227) hide show
  1. package/dist/authoring/assets/aw-config.d.mts +1 -50
  2. package/dist/authoring/assets/aw-config.mjs +58 -928
  3. package/dist/authoring/assets/aw-config.mjs.map +1 -1
  4. package/dist/authoring/scripts/init-plugin.mjs +28 -33
  5. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  6. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
  8. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
  9. package/dist/authoring/scripts/validate-plugin.mjs +159 -48
  10. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  11. package/dist/scripts/bundle-plugins.js +18 -4
  12. package/dist/scripts/bundle-plugins.js.map +1 -1
  13. package/dist/scripts/test-plugin-scripts.d.ts +2 -0
  14. package/dist/scripts/test-plugin-scripts.js +21 -5
  15. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  16. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  17. package/dist/src/commands/aw-capabilities.js +1 -1
  18. package/dist/src/commands/aw-capabilities.js.map +1 -1
  19. package/dist/src/commands/catalog-dev-source.d.ts +6 -3
  20. package/dist/src/commands/catalog-dev-source.js +96 -35
  21. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  22. package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
  23. package/dist/src/commands/catalog-publication-inspection.js +54 -8
  24. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  25. package/dist/src/commands/catalog-publication-journal.js +1 -1
  26. package/dist/src/commands/catalog-publication-journal.js.map +1 -1
  27. package/dist/src/commands/catalog-source.d.ts +19 -4
  28. package/dist/src/commands/catalog-source.js +121 -12
  29. package/dist/src/commands/catalog-source.js.map +1 -1
  30. package/dist/src/commands/catalog-store.d.ts +26 -7
  31. package/dist/src/commands/catalog-store.js +361 -42
  32. package/dist/src/commands/catalog-store.js.map +1 -1
  33. package/dist/src/commands/console.js +28 -17
  34. package/dist/src/commands/console.js.map +1 -1
  35. package/dist/src/commands/operation-plan.d.ts +2 -1
  36. package/dist/src/commands/operation-plan.js +24 -0
  37. package/dist/src/commands/operation-plan.js.map +1 -1
  38. package/dist/src/commands/operation-planner.d.ts +2 -2
  39. package/dist/src/commands/operation-planner.js +22 -20
  40. package/dist/src/commands/operation-planner.js.map +1 -1
  41. package/dist/src/commands/options.d.ts +2 -2
  42. package/dist/src/commands/options.js +18 -4
  43. package/dist/src/commands/options.js.map +1 -1
  44. package/dist/src/commands/plugin-capabilities.js +2 -2
  45. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  46. package/dist/src/commands/rebuild-command.d.ts +19 -0
  47. package/dist/src/commands/rebuild-command.js +828 -283
  48. package/dist/src/commands/rebuild-command.js.map +1 -1
  49. package/dist/src/commands/rebuild-integration.d.ts +25 -47
  50. package/dist/src/commands/rebuild-integration.js +1176 -1016
  51. package/dist/src/commands/rebuild-integration.js.map +1 -1
  52. package/dist/src/commands/rebuild-operations.d.ts +50 -12
  53. package/dist/src/commands/rebuild-operations.js +207 -101
  54. package/dist/src/commands/rebuild-operations.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +21 -2
  56. package/dist/src/commands/status-v6.js +163 -31
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/sync-command.d.ts +17 -0
  59. package/dist/src/commands/sync-command.js +45 -37
  60. package/dist/src/commands/sync-command.js.map +1 -1
  61. package/dist/src/commands/write-commands.d.ts +5 -0
  62. package/dist/src/commands/write-commands.js +50 -3
  63. package/dist/src/commands/write-commands.js.map +1 -1
  64. package/dist/src/generators/local-plugin.js +14 -4
  65. package/dist/src/generators/local-plugin.js.map +1 -1
  66. package/dist/src/index.js +260 -91
  67. package/dist/src/index.js.map +1 -1
  68. package/dist/src/output/command-receipt.js +1 -3
  69. package/dist/src/output/command-receipt.js.map +1 -1
  70. package/dist/src/output/rebuild-progress.js +1 -3
  71. package/dist/src/output/rebuild-progress.js.map +1 -1
  72. package/dist/src/portable/materializer.js +10 -8
  73. package/dist/src/portable/materializer.js.map +1 -1
  74. package/dist/src/tui/catalog-generation.js +41 -1
  75. package/dist/src/tui/catalog-generation.js.map +1 -1
  76. package/dist/src/tui/catalog-sources/session.d.ts +1 -0
  77. package/dist/src/tui/catalog-sources/session.js +16 -3
  78. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  79. package/dist/src/tui/catalog-sources/types.d.ts +3 -2
  80. package/dist/src/tui/components.d.ts +4 -0
  81. package/dist/src/tui/components.js +36 -24
  82. package/dist/src/tui/components.js.map +1 -1
  83. package/dist/src/tui/console-app-v4.d.ts +13 -4
  84. package/dist/src/tui/console-app-v4.js +2065 -857
  85. package/dist/src/tui/console-app-v4.js.map +1 -1
  86. package/dist/src/tui/controller-runtime.js +731 -62
  87. package/dist/src/tui/controller-runtime.js.map +1 -1
  88. package/dist/src/tui/controller.d.ts +156 -10
  89. package/dist/src/tui/controller.js +1187 -163
  90. package/dist/src/tui/controller.js.map +1 -1
  91. package/dist/src/tui/diagnostic-projection.d.ts +22 -0
  92. package/dist/src/tui/diagnostic-projection.js +437 -0
  93. package/dist/src/tui/diagnostic-projection.js.map +1 -0
  94. package/dist/src/tui/diagnostics.d.ts +1 -1
  95. package/dist/src/tui/diagnostics.js +7 -2
  96. package/dist/src/tui/diagnostics.js.map +1 -1
  97. package/dist/src/tui/glossary.d.ts +23 -0
  98. package/dist/src/tui/glossary.js +42 -0
  99. package/dist/src/tui/glossary.js.map +1 -0
  100. package/dist/src/tui/index.d.ts +4 -1
  101. package/dist/src/tui/index.js +4 -1
  102. package/dist/src/tui/index.js.map +1 -1
  103. package/dist/src/tui/keys.d.ts +44 -0
  104. package/dist/src/tui/keys.js +43 -15
  105. package/dist/src/tui/keys.js.map +1 -1
  106. package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
  107. package/dist/src/tui/lifecycle-action-view.js +34 -0
  108. package/dist/src/tui/lifecycle-action-view.js.map +1 -0
  109. package/dist/src/tui/model.d.ts +12 -2
  110. package/dist/src/tui/model.js +128 -52
  111. package/dist/src/tui/model.js.map +1 -1
  112. package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
  113. package/dist/src/tui/operation-diagnostic-screens.js +101 -0
  114. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
  115. package/dist/src/tui/operation-history.d.ts +80 -0
  116. package/dist/src/tui/operation-history.js +130 -0
  117. package/dist/src/tui/operation-history.js.map +1 -0
  118. package/dist/src/tui/operation-review-planner.js +366 -99
  119. package/dist/src/tui/operation-review-planner.js.map +1 -1
  120. package/dist/src/tui/router.d.ts +82 -17
  121. package/dist/src/tui/router.js +186 -38
  122. package/dist/src/tui/router.js.map +1 -1
  123. package/dist/src/tui/screen-registry.d.ts +546 -0
  124. package/dist/src/tui/screen-registry.js +179 -0
  125. package/dist/src/tui/screen-registry.js.map +1 -0
  126. package/dist/src/tui/session-console-app.d.ts +6 -0
  127. package/dist/src/tui/session-console-app.js +13 -0
  128. package/dist/src/tui/session-console-app.js.map +1 -0
  129. package/dist/src/tui/session-host.d.ts +44 -0
  130. package/dist/src/tui/session-host.js +197 -0
  131. package/dist/src/tui/session-host.js.map +1 -0
  132. package/dist/src/types.d.ts +64 -3
  133. package/dist/src/types.js +1 -1
  134. package/dist/src/types.js.map +1 -1
  135. package/dist/src/utils/agent-version.js +1 -1
  136. package/dist/src/utils/agent-version.js.map +1 -1
  137. package/dist/src/utils/artifact-plan.d.ts +2 -2
  138. package/dist/src/utils/artifact-plan.js +8 -6
  139. package/dist/src/utils/artifact-plan.js.map +1 -1
  140. package/dist/src/utils/artifact-renderer.d.ts +2 -1
  141. package/dist/src/utils/artifact-renderer.js +139 -120
  142. package/dist/src/utils/artifact-renderer.js.map +1 -1
  143. package/dist/src/utils/artifact-request.d.ts +5 -0
  144. package/dist/src/utils/artifact-request.js +40 -0
  145. package/dist/src/utils/artifact-request.js.map +1 -1
  146. package/dist/src/utils/bundle-layout.js +4 -4
  147. package/dist/src/utils/bundle-layout.js.map +1 -1
  148. package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
  149. package/dist/src/utils/catalog-capability-projection.js +14 -0
  150. package/dist/src/utils/catalog-capability-projection.js.map +1 -0
  151. package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
  152. package/dist/src/utils/catalog-generation-resolver.js +49 -27
  153. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  154. package/dist/src/utils/catalog-generation-service.d.ts +8 -1
  155. package/dist/src/utils/catalog-generation-service.js +81 -47
  156. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  157. package/dist/src/utils/catalog-generation-store.d.ts +1 -0
  158. package/dist/src/utils/catalog-generation-store.js +60 -5
  159. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  160. package/dist/src/utils/catalog-operation-context.js +1 -0
  161. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  162. package/dist/src/utils/catalog-public-error.js +4 -0
  163. package/dist/src/utils/catalog-public-error.js.map +1 -1
  164. package/dist/src/utils/catalog-source-impact.d.ts +3 -2
  165. package/dist/src/utils/catalog-source-impact.js +142 -20
  166. package/dist/src/utils/catalog-source-impact.js.map +1 -1
  167. package/dist/src/utils/catalog-source-references.js +95 -9
  168. package/dist/src/utils/catalog-source-references.js.map +1 -1
  169. package/dist/src/utils/catalog-sources.d.ts +1 -1
  170. package/dist/src/utils/catalog-sources.js +19 -3
  171. package/dist/src/utils/catalog-sources.js.map +1 -1
  172. package/dist/src/utils/codex-manager.js +8 -0
  173. package/dist/src/utils/codex-manager.js.map +1 -1
  174. package/dist/src/utils/codex-native-observation.d.ts +34 -1
  175. package/dist/src/utils/codex-native-observation.js +132 -0
  176. package/dist/src/utils/codex-native-observation.js.map +1 -1
  177. package/dist/src/utils/codex-reconciler.d.ts +0 -2
  178. package/dist/src/utils/codex-reconciler.js.map +1 -1
  179. package/dist/src/utils/codex-shared-native.d.ts +2 -26
  180. package/dist/src/utils/codex-shared-native.js +8 -82
  181. package/dist/src/utils/codex-shared-native.js.map +1 -1
  182. package/dist/src/utils/git-catalog-provider.d.ts +3 -0
  183. package/dist/src/utils/git-catalog-provider.js +40 -1
  184. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  185. package/dist/src/utils/local-marketplace.d.ts +1 -0
  186. package/dist/src/utils/local-marketplace.js +69 -73
  187. package/dist/src/utils/local-marketplace.js.map +1 -1
  188. package/dist/src/utils/multi-source-catalog.js +1 -0
  189. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  190. package/dist/src/utils/native-cli.d.ts +9 -4
  191. package/dist/src/utils/native-cli.js +280 -34
  192. package/dist/src/utils/native-cli.js.map +1 -1
  193. package/dist/src/utils/path-catalog-provider.js +2 -0
  194. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  195. package/dist/src/utils/plugin-config-impact.d.ts +33 -0
  196. package/dist/src/utils/plugin-config-impact.js +298 -0
  197. package/dist/src/utils/plugin-config-impact.js.map +1 -0
  198. package/dist/src/utils/plugin-config-write.d.ts +36 -14
  199. package/dist/src/utils/plugin-config-write.js +709 -225
  200. package/dist/src/utils/plugin-config-write.js.map +1 -1
  201. package/dist/src/utils/plugin-config.d.ts +58 -4
  202. package/dist/src/utils/plugin-config.js +328 -60
  203. package/dist/src/utils/plugin-config.js.map +1 -1
  204. package/dist/src/utils/plugin-scripts.d.ts +20 -12
  205. package/dist/src/utils/plugin-scripts.js +389 -114
  206. package/dist/src/utils/plugin-scripts.js.map +1 -1
  207. package/dist/src/utils/process-tree.d.ts +2 -2
  208. package/dist/src/utils/process-tree.js +1 -1
  209. package/dist/src/utils/process-tree.js.map +1 -1
  210. package/dist/src/utils/project-boundary.d.ts +11 -1
  211. package/dist/src/utils/project-boundary.js +33 -11
  212. package/dist/src/utils/project-boundary.js.map +1 -1
  213. package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
  214. package/dist/src/utils/safe-operation-diagnostic.js +33 -0
  215. package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
  216. package/dist/src/utils/sensitive-content.d.ts +4 -0
  217. package/dist/src/utils/sensitive-content.js +36 -0
  218. package/dist/src/utils/sensitive-content.js.map +1 -0
  219. package/dist/src/utils/windows-claude-shim.d.ts +9 -0
  220. package/dist/src/utils/windows-claude-shim.js +141 -0
  221. package/dist/src/utils/windows-claude-shim.js.map +1 -0
  222. package/dist/vitest.config.js +91 -10
  223. package/dist/vitest.config.js.map +1 -1
  224. package/package.json +9 -9
  225. package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
  226. package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
  227. package/dist/authoring/templates/aw-config-runtime.mjs.map +0 -1
@@ -1,12 +1,13 @@
1
- import { stablePluginConfigStringify } from '@lesliechan721/aw-plugin-core/manifest';
2
1
  import { statusCommandV7, statusTargetIntent, } from '../commands/status-v6.js';
3
2
  import { syncWriteCommand } from '../commands/write-commands.js';
4
3
  import { CONFIG_MUTATION_RECEIPT_SCHEMA_VERSION, } from '../types.js';
5
4
  import { getDefaultNativeCliRunner } from './native-cli.js';
6
5
  import { assertReadyPluginConfigStore, assertSafePluginConfigSegment, codexNativeRefreshKeys, coercePluginConfigUpdates, patchPluginConfigStore, readPluginConfigStore, readNamespacedPluginConfigStoreIdentity, resetPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, resolvePluginConfigStorePath, resolvePluginConfigSchemaAccess, summarizeEffectivePluginConfig, } from './plugin-config.js';
6
+ import { analyzePluginConfigMutationImpact } from './plugin-config-impact.js';
7
7
  import { collectSensitivePluginConfigValues, redactPluginScriptOutput, } from './plugin-scripts.js';
8
8
  import { resolveClaudeProjectKey, resolveCodexProjectKey } from './paths.js';
9
9
  import { withAwHomeMutationLock, } from './aw-home-mutation-lock.js';
10
+ import { assertProjectSharedConfigPathVisible, } from './project-boundary.js';
10
11
  export function buildConfigMutationReceipt(command, receipt) {
11
12
  return {
12
13
  ...receipt,
@@ -14,6 +15,60 @@ export function buildConfigMutationReceipt(command, receipt) {
14
15
  command,
15
16
  };
16
17
  }
18
+ function publicConfigApplyTargetResult(result) {
19
+ return {
20
+ target: {
21
+ pluginName: result.target.pluginName,
22
+ ...(result.target.pluginId ? { pluginId: result.target.pluginId } : {}),
23
+ platform: result.target.platform,
24
+ scope: result.target.scope,
25
+ },
26
+ action: result.action,
27
+ state: result.state,
28
+ ...(result.message ? { message: result.message } : {}),
29
+ ...(result.errorCode ? { errorCode: result.errorCode } : {}),
30
+ ...(result.relation ? { relation: result.relation } : {}),
31
+ ...(result.requiredBy ? { requiredBy: result.requiredBy } : {}),
32
+ };
33
+ }
34
+ function publicConfigApplyResult(apply) {
35
+ if (apply.state === 'skipped')
36
+ return apply;
37
+ return {
38
+ ...apply,
39
+ targetResults: apply.targetResults.map(publicConfigApplyTargetResult),
40
+ };
41
+ }
42
+ export function configWriteStateForError(error) {
43
+ return error?.configWrite === 'failed'
44
+ ? 'failed'
45
+ : 'notStarted';
46
+ }
47
+ function markConfigWriteFailed(error) {
48
+ const failure = error instanceof Error ? error : new Error(String(error));
49
+ return Object.assign(failure, { configWrite: 'failed' });
50
+ }
51
+ function skippedApply(reason) {
52
+ return { state: 'skipped', reason, targetResults: [] };
53
+ }
54
+ function unavailableApply(reason, stage, targetResults = []) {
55
+ const messages = {
56
+ invalidConfig: 'The effective config is invalid for every visible target.',
57
+ schemaUnavailable: 'The config schema is unavailable after the config commit.',
58
+ sourceUnavailable: 'The plugin source is unavailable after the config commit.',
59
+ effectiveConfigUnavailable: 'The effective config cannot be read after the config commit.',
60
+ targetDiscoveryFailed: 'Visible config apply targets cannot be discovered after the config commit.',
61
+ };
62
+ return {
63
+ state: 'unavailable',
64
+ reason,
65
+ targetResults,
66
+ error: { stage, code: reason, message: messages[reason] },
67
+ };
68
+ }
69
+ function overallStateForApply(apply) {
70
+ return apply.state === 'succeeded' || apply.state === 'skipped' ? 'succeeded' : 'partial';
71
+ }
17
72
  function uniqueInOrder(values) {
18
73
  return [...new Set(values)];
19
74
  }
@@ -91,6 +146,9 @@ async function reinstallVisibleTargets(options) {
91
146
  : { targets: dedupeTargets(options.applyTargets), failures: [] });
92
147
  const targetResults = [...discovered.failures];
93
148
  let runtimeFailed = discovered.failures.length > 0;
149
+ let invalidTargets = 0;
150
+ let unavailableTargets = 0;
151
+ const errors = [];
94
152
  const reinstall = options.reinstallCommandImpl ?? syncWriteCommand;
95
153
  if (options.plugin.sourceId && !options.plugin.pluginId) {
96
154
  throw new Error(`Plugin "${options.plugin.manifest.name}" is missing its namespaced plugin ID.`);
@@ -105,6 +163,7 @@ async function reinstallVisibleTargets(options) {
105
163
  ...(options.plugin.sourceId ? {
106
164
  sourceId: options.plugin.sourceId,
107
165
  sourceIdentity: options.plugin.sourceIdentity,
166
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
108
167
  } : {}),
109
168
  pluginName: options.plugin.manifest.name,
110
169
  manifest: options.plugin.manifest,
@@ -118,6 +177,10 @@ async function reinstallVisibleTargets(options) {
118
177
  // Generic credential redaction and known config paths still apply.
119
178
  }
120
179
  let sanitized = redactPluginScriptOutput(message, sensitiveValues);
180
+ for (const privatePath of [target.projectKey, options.cwd]) {
181
+ if (privatePath)
182
+ sanitized = sanitized.split(privatePath).join('<project>');
183
+ }
121
184
  for (const scope of ['user', 'project']) {
122
185
  const configPath = resolvePluginConfigStorePath({
123
186
  sourceId: options.plugin.sourceId,
@@ -150,7 +213,50 @@ async function reinstallVisibleTargets(options) {
150
213
  : Array.isArray(entry) && /^(?:messages|warnings)$/u.test(key)),
151
214
  ])));
152
215
  };
216
+ const preApplyExecutionCache = new Map();
153
217
  for (const target of discovered.targets) {
218
+ try {
219
+ const effective = await resolveEffectivePluginConfig({
220
+ ...(options.plugin.sourceId ? {
221
+ sourceId: options.plugin.sourceId,
222
+ sourceIdentity: options.plugin.sourceIdentity,
223
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
224
+ } : {}),
225
+ pluginName: options.plugin.manifest.name,
226
+ manifest: options.plugin.manifest,
227
+ targetScope: target.scope,
228
+ cwd: options.cwd,
229
+ env: options.env,
230
+ });
231
+ if (effective.issues.length > 0) {
232
+ invalidTargets += 1;
233
+ targetResults.push({
234
+ target,
235
+ action: 'reinstall',
236
+ state: 'skipped',
237
+ errorCode: 'invalidConfig',
238
+ message: 'The effective config is invalid for this target.',
239
+ });
240
+ continue;
241
+ }
242
+ }
243
+ catch {
244
+ runtimeFailed = true;
245
+ unavailableTargets += 1;
246
+ targetResults.push({
247
+ target,
248
+ action: 'reinstall',
249
+ state: 'failed',
250
+ errorCode: 'effectiveConfigUnavailable',
251
+ message: 'The effective config cannot be read for this target.',
252
+ });
253
+ errors.push({
254
+ stage: 'targetValidation',
255
+ code: 'effectiveConfigUnavailable',
256
+ message: 'The effective config cannot be read for a visible target.',
257
+ });
258
+ continue;
259
+ }
154
260
  try {
155
261
  const debugLogger = options.rebuildRuntime?.debugLogger;
156
262
  const reporter = options.rebuildRuntime?.reporter;
@@ -158,10 +264,15 @@ async function reinstallVisibleTargets(options) {
158
264
  cwd: options.cwd,
159
265
  platform: target.platform,
160
266
  scope: target.scope,
267
+ ...(options.rebuildRuntime?.scriptTimeoutMs === undefined
268
+ ? {}
269
+ : { scriptTimeoutMs: options.rebuildRuntime.scriptTimeoutMs }),
161
270
  }, {
162
271
  ...options.rebuildRuntime,
163
272
  env: options.env,
164
273
  nativeCliRunner,
274
+ preApplyConfirmed: options.preApplyConfirmed === true,
275
+ preApplyExecutionCache,
165
276
  ...(debugLogger ? {
166
277
  debugLogger: {
167
278
  ...debugLogger,
@@ -175,10 +286,12 @@ async function reinstallVisibleTargets(options) {
175
286
  },
176
287
  } : {}),
177
288
  });
178
- if (result.overallState === 'failed')
289
+ options.rebuildRuntime?.operationDiagnosticSink?.(result.rebuildResults);
290
+ if (result.overallState !== 'succeeded')
179
291
  runtimeFailed = true;
180
292
  targetResults.push(...await Promise.all(result.targetResults.map(async (entry) => ({
181
293
  ...entry,
294
+ state: entry.action === 'skipped' ? 'skipped' : entry.state,
182
295
  ...(entry.message ? { message: await sanitizeFailure(entry.message, entry.target) } : {}),
183
296
  }))));
184
297
  }
@@ -191,17 +304,33 @@ async function reinstallVisibleTargets(options) {
191
304
  state: 'failed',
192
305
  message,
193
306
  });
307
+ errors.push({
308
+ stage: 'apply',
309
+ code: 'targetApplyFailed',
310
+ message: 'A visible config apply target failed.',
311
+ });
194
312
  }
195
313
  }
196
- const overallState = runtimeFailed || targetResults.some((entry) => entry.state === 'failed')
197
- ? 'partial' : 'succeeded';
198
- return {
199
- overallState,
200
- targetResults,
201
- ...(discovered.targets.length === 0 && discovered.failures.length === 0
202
- ? { applySkippedReason: 'noVisibleTarget' }
203
- : {}),
204
- };
314
+ if (discovered.targets.length === 0 && discovered.failures.length === 0) {
315
+ return skippedApply('noVisibleTarget');
316
+ }
317
+ if (discovered.targets.length > 0 && invalidTargets === discovered.targets.length) {
318
+ return unavailableApply('invalidConfig', 'targetValidation', targetResults);
319
+ }
320
+ if (discovered.targets.length > 0 && unavailableTargets === discovered.targets.length) {
321
+ return unavailableApply('effectiveConfigUnavailable', 'targetValidation', targetResults);
322
+ }
323
+ if (runtimeFailed
324
+ || invalidTargets > 0
325
+ || unavailableTargets > 0
326
+ || targetResults.some((entry) => entry.state === 'failed' || entry.state === 'skipped')) {
327
+ return {
328
+ state: 'partial',
329
+ targetResults,
330
+ ...(errors.length > 0 ? { errors } : {}),
331
+ };
332
+ }
333
+ return { state: 'succeeded', targetResults };
205
334
  }
206
335
  async function resolveConfigApplyTargets(options) {
207
336
  if (options.applyTargets !== undefined) {
@@ -217,15 +346,16 @@ async function resolveConfigApplyTargets(options) {
217
346
  rebuildRuntime: options.rebuildRuntime,
218
347
  });
219
348
  }
220
- async function configApplyHasIssues(options) {
221
- const scopes = [...new Set(options.targets.map((target) => target.scope))];
222
- for (const scope of scopes) {
223
- if (options.knownValidScopes?.includes(scope))
224
- continue;
349
+ async function configApplyHasValidCodexTarget(options) {
350
+ const codexScopes = new Set(options.targets
351
+ .filter((target) => target.platform === 'codex')
352
+ .map((target) => target.scope));
353
+ for (const scope of codexScopes) {
225
354
  const effective = await resolveEffectivePluginConfig({
226
355
  ...(options.plugin.sourceId ? {
227
356
  sourceId: options.plugin.sourceId,
228
357
  sourceIdentity: options.plugin.sourceIdentity,
358
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
229
359
  } : {}),
230
360
  pluginName: options.plugin.manifest.name,
231
361
  manifest: options.plugin.manifest,
@@ -233,14 +363,141 @@ async function configApplyHasIssues(options) {
233
363
  cwd: options.cwd,
234
364
  env: options.env,
235
365
  });
236
- if (effective.issues.length > 0)
366
+ if (effective.issues.length === 0)
237
367
  return true;
238
368
  }
239
369
  return false;
240
370
  }
371
+ async function applyConfigToVisibleTargets(options) {
372
+ let discovered;
373
+ if (options.discovered) {
374
+ discovered = options.discovered;
375
+ }
376
+ else {
377
+ try {
378
+ discovered = await resolveConfigApplyTargets(options);
379
+ }
380
+ catch {
381
+ return unavailableApply('targetDiscoveryFailed', 'targetDiscovery');
382
+ }
383
+ }
384
+ if (discovered.targets.length === 0
385
+ && discovered.failures.length === 0
386
+ && options.noVisibleTargetHasInvalidConfig) {
387
+ return unavailableApply('invalidConfig', 'targetValidation');
388
+ }
389
+ return reinstallVisibleTargets({ ...options, discovered });
390
+ }
391
+ export async function resolveConfigPreApplyAuthorization(options) {
392
+ const applyTargets = dedupeTargets(options.applyTargets);
393
+ if (applyTargets.length === 0 || !pluginMayRunPreApply(options.plugin)) {
394
+ return { targets: [], availableApplyTargets: applyTargets, unavailableTargetResults: [] };
395
+ }
396
+ if (options.plugin.sourceId && !options.plugin.pluginId) {
397
+ return {
398
+ targets: [],
399
+ availableApplyTargets: [],
400
+ unavailableTargetResults: applyTargets.map((target) => ({
401
+ target,
402
+ action: 'reinstall',
403
+ state: 'failed',
404
+ errorCode: 'sourceUnavailable',
405
+ message: 'The preApply authorization scope cannot be resolved for this target.',
406
+ })),
407
+ };
408
+ }
409
+ const selector = options.plugin.sourceId
410
+ ? options.plugin.pluginId
411
+ : options.plugin.manifest.name;
412
+ const groups = new Map();
413
+ for (const target of applyTargets) {
414
+ const targets = groups.get(target.scope) ?? [];
415
+ targets.push(target);
416
+ groups.set(target.scope, targets);
417
+ }
418
+ const authorization = [];
419
+ const availableApplyTargets = [];
420
+ const unavailableTargetResults = [];
421
+ const resolveGroup = async (targets) => {
422
+ const platforms = new Set(targets.map((target) => target.platform));
423
+ const platform = platforms.size === 2 ? 'both' : [...platforms][0];
424
+ try {
425
+ const preview = await (options.reinstallCommandImpl ?? syncWriteCommand)([selector], {
426
+ cwd: options.cwd,
427
+ platform,
428
+ scope: targets[0].scope,
429
+ }, {
430
+ ...options.rebuildRuntime,
431
+ env: options.env ?? process.env,
432
+ nativeCliRunner: options.nativeCliRunner,
433
+ resolvePreApplyAuthorization: true,
434
+ });
435
+ if (preview.overallState !== 'succeeded' || !preview.preApplyAuthorization)
436
+ return false;
437
+ authorization.push(...preview.preApplyAuthorization);
438
+ availableApplyTargets.push(...targets);
439
+ return true;
440
+ }
441
+ catch {
442
+ return false;
443
+ }
444
+ };
445
+ for (const targets of groups.values()) {
446
+ if (await resolveGroup(targets))
447
+ continue;
448
+ for (const target of targets) {
449
+ if (targets.length > 1 && await resolveGroup([target]))
450
+ continue;
451
+ unavailableTargetResults.push({
452
+ target,
453
+ action: 'reinstall',
454
+ state: 'failed',
455
+ errorCode: 'sourceUnavailable',
456
+ message: 'The preApply authorization scope cannot be resolved for this target.',
457
+ });
458
+ }
459
+ }
460
+ return {
461
+ targets: [...new Map(authorization.map((target) => [[
462
+ target.pluginId,
463
+ target.platform,
464
+ target.scope,
465
+ ].join('\0'), target])).values()],
466
+ availableApplyTargets: dedupeTargets(availableApplyTargets),
467
+ unavailableTargetResults,
468
+ };
469
+ }
470
+ function pluginMayRunPreApply(plugin) {
471
+ return plugin.manifest.scripts?.preApply !== undefined
472
+ || (plugin.manifest.dependencies?.length ?? 0) > 0;
473
+ }
474
+ function throwPreApplyAuthorizationRequired(targets) {
475
+ const plugins = [...new Set(targets.map((target) => target.pluginId))].join(', ');
476
+ const error = new Error(`preApplyAuthorizationRequired: Applying this config runs plugin preApply for ${plugins}. Re-run with --yes.`);
477
+ error.code = 'preApplyAuthorizationRequired';
478
+ throw error;
479
+ }
480
+ function preApplyAuthorizationIsConfirmed(options) {
481
+ if (options.confirmed !== true)
482
+ return false;
483
+ if (options.authorizedTargets === undefined)
484
+ return true;
485
+ const authorized = new Set(options.authorizedTargets.map((target) => [
486
+ target.pluginId,
487
+ target.sourceIdentity,
488
+ target.platform,
489
+ target.scope,
490
+ ].join('\0')));
491
+ return options.required.every((target) => authorized.has([
492
+ target.pluginId,
493
+ target.sourceIdentity,
494
+ target.platform,
495
+ target.scope,
496
+ ].join('\0')));
497
+ }
241
498
  function otherProjectsWarning(scope) {
242
499
  return scope === 'user'
243
- ? ['其它项目不会由本次命令扫描或更新。请在每个项目目录中运行对应的 aw reinstall 命令。']
500
+ ? ['其它项目不会由本次命令扫描或更新,可能需要在对应项目目录中运行 aw sync。']
244
501
  : [];
245
502
  }
246
503
  function buildReceiptFieldEntry(options) {
@@ -261,25 +518,90 @@ export async function replayConfigRuntimeWrites(options) {
261
518
  action: async (awHomeMutationToken) => {
262
519
  const env = options.env ?? process.env;
263
520
  const rebuildRuntime = { ...options.rebuildRuntime, awHomeMutationToken };
264
- const discovered = await resolveConfigApplyTargets({
521
+ let discovered;
522
+ try {
523
+ discovered = await resolveConfigApplyTargets({
524
+ plugin: options.plugin,
525
+ scope: options.scope,
526
+ cwd: options.cwd,
527
+ env,
528
+ applyTargets: options.applyTargets,
529
+ nativeCliRunner: options.nativeCliRunner,
530
+ rebuildRuntime,
531
+ });
532
+ }
533
+ catch {
534
+ const apply = unavailableApply('targetDiscoveryFailed', 'targetDiscovery');
535
+ return {
536
+ configWrite: 'succeeded',
537
+ overallState: 'partial',
538
+ apply: publicConfigApplyResult(apply),
539
+ otherProjectsMayRequireSync: false,
540
+ };
541
+ }
542
+ const preApplyAuthorization = await resolveConfigPreApplyAuthorization({
265
543
  plugin: options.plugin,
266
544
  scope: options.scope,
267
545
  cwd: options.cwd,
268
546
  env,
269
- applyTargets: options.applyTargets,
547
+ applyTargets: discovered.targets,
270
548
  nativeCliRunner: options.nativeCliRunner,
271
549
  rebuildRuntime,
550
+ reinstallCommandImpl: options.reinstallCommandImpl,
272
551
  });
273
- const applied = await configApplyHasIssues({
274
- plugin: options.plugin,
275
- targets: discovered.targets,
276
- cwd: options.cwd,
277
- env,
278
- }) ? {
279
- overallState: 'partial',
280
- targetResults: [],
281
- applySkippedReason: 'invalidConfig',
282
- } : await reinstallVisibleTargets({
552
+ if (preApplyAuthorization.targets.length > 0 && !preApplyAuthorizationIsConfirmed({
553
+ required: preApplyAuthorization.targets,
554
+ confirmed: options.preApplyConfirmed,
555
+ authorizedTargets: options.preApplyAuthorizedTargets,
556
+ })) {
557
+ const apply = {
558
+ state: 'partial',
559
+ targetResults: preApplyAuthorization.targets.map((target) => ({
560
+ target: {
561
+ pluginName: target.pluginName,
562
+ pluginId: target.pluginId,
563
+ platform: target.platform,
564
+ scope: target.scope,
565
+ },
566
+ action: 'reinstall',
567
+ state: 'failed',
568
+ errorCode: 'preApplyAuthorizationRequired',
569
+ message: 'Running plugin preApply requires explicit authorization.',
570
+ })),
571
+ };
572
+ return {
573
+ configWrite: 'succeeded',
574
+ overallState: 'partial',
575
+ apply: publicConfigApplyResult(apply),
576
+ otherProjectsMayRequireSync: false,
577
+ };
578
+ }
579
+ discovered = {
580
+ targets: preApplyAuthorization.availableApplyTargets,
581
+ failures: [...discovered.failures, ...preApplyAuthorization.unavailableTargetResults],
582
+ };
583
+ if (discovered.targets.length === 0
584
+ && preApplyAuthorization.unavailableTargetResults.length > 0) {
585
+ const apply = unavailableApply('sourceUnavailable', 'source', preApplyAuthorization.unavailableTargetResults);
586
+ return {
587
+ configWrite: 'succeeded',
588
+ overallState: 'partial',
589
+ apply: publicConfigApplyResult(apply),
590
+ otherProjectsMayRequireSync: false,
591
+ };
592
+ }
593
+ if (options.scope === 'user'
594
+ && options.sharedNativeApplyConfirmed !== true
595
+ && pluginHasSharedNativeRefreshKeys(options.plugin)
596
+ && await configApplyHasValidCodexTarget({
597
+ plugin: options.plugin,
598
+ targets: discovered.targets,
599
+ cwd: options.cwd,
600
+ env,
601
+ })) {
602
+ throwSharedNativeApplyConfirmationRequired();
603
+ }
604
+ const apply = await reinstallVisibleTargets({
283
605
  plugin: options.plugin,
284
606
  scope: options.scope,
285
607
  cwd: options.cwd,
@@ -289,23 +611,27 @@ export async function replayConfigRuntimeWrites(options) {
289
611
  reinstallCommandImpl: options.reinstallCommandImpl,
290
612
  rebuildRuntime,
291
613
  discovered,
614
+ preApplyConfirmed: options.preApplyConfirmed,
292
615
  });
293
616
  return {
294
617
  configWrite: 'succeeded',
295
- overallState: applied.overallState,
296
- targetResults: applied.targetResults,
297
- ...(applied.applySkippedReason ? { applySkippedReason: applied.applySkippedReason } : {}),
298
- otherProjectsRequireReinstall: options.scope === 'user',
618
+ overallState: overallStateForApply(apply),
619
+ apply: publicConfigApplyResult(apply),
620
+ otherProjectsMayRequireSync: false,
299
621
  };
300
622
  },
301
623
  });
302
624
  }
303
- function changedKeysAffectSharedNativeTarget(options) {
304
- const changedKeys = new Set(options.changedKeys);
305
- return (options.plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
625
+ function pluginHasSharedNativeRefreshKeys(plugin) {
626
+ return (plugin.manifest.artifacts?.targets ?? []).some((target) => target.platform === 'codex'
306
627
  && target.target === 'codex.skill'
307
628
  && target.delivery?.mode === 'codex-native'
308
- && target.refreshKeys.some((key) => changedKeys.has(key)));
629
+ && target.refreshKeys.length > 0);
630
+ }
631
+ function throwSharedNativeApplyConfirmationRequired() {
632
+ const error = new Error('sharedNativeConfigApplyConfirmationRequired: Applying this user config changes a shared Codex native artifact. Re-run with --yes.');
633
+ error.code = 'sharedNativeConfigApplyConfirmationRequired';
634
+ throw error;
309
635
  }
310
636
  async function applyPluginConfigWriteUnlocked(options) {
311
637
  const requestedPluginName = options.pluginName ?? options.plugin?.manifest.name;
@@ -315,6 +641,7 @@ async function applyPluginConfigWriteUnlocked(options) {
315
641
  const configIdentity = options.plugin?.sourceId ? {
316
642
  sourceId: options.plugin.sourceId,
317
643
  sourceIdentity: options.plugin.sourceIdentity,
644
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
318
645
  } : {};
319
646
  const requestedUpdates = options.request.updates ?? {};
320
647
  const requestedRemoveKeys = uniqueInOrder(options.request.removeKeys ?? []);
@@ -346,11 +673,14 @@ async function applyPluginConfigWriteUnlocked(options) {
346
673
  });
347
674
  assertReadyPluginConfigStore(userStore);
348
675
  }
349
- for (const key of requestedRemoveKeys) {
350
- if (!schema[key])
351
- throw new Error(`Unknown config key "${key}".`);
352
- }
353
676
  if (options.scope === 'project') {
677
+ const rejectedSensitiveKeys = Object.keys(requestedUpdates)
678
+ .filter((key) => schema[key]?.sensitive === true);
679
+ if (rejectedSensitiveKeys.length > 0) {
680
+ const error = new Error('sensitiveProjectConfigUnsupported: Sensitive config can only be set in user scope.');
681
+ error.code = 'sensitiveProjectConfigUnsupported';
682
+ throw error;
683
+ }
354
684
  const userOnlyKeys = codexNativeRefreshKeys(schemaPlugin.manifest);
355
685
  const rejectedKeys = Object.keys(requestedUpdates).filter((key) => userOnlyKeys.has(key));
356
686
  if (rejectedKeys.length > 0) {
@@ -359,126 +689,164 @@ async function applyPluginConfigWriteUnlocked(options) {
359
689
  throw error;
360
690
  }
361
691
  }
362
- const updates = await coercePluginConfigUpdates({ schema, updates: requestedUpdates, cwd: options.cwd });
363
- const store = await readPluginConfigStore({
364
- ...configIdentity,
365
- pluginName,
692
+ let discovered;
693
+ let targetDiscoveryFailed = false;
694
+ if (options.apply !== false) {
695
+ try {
696
+ discovered = await resolveConfigApplyTargets({
697
+ plugin: schemaPlugin,
698
+ scope: options.scope,
699
+ cwd: options.cwd,
700
+ env: options.env ?? process.env,
701
+ applyTargets: options.applyTargets,
702
+ nativeCliRunner: options.nativeCliRunner,
703
+ rebuildRuntime: options.rebuildRuntime,
704
+ });
705
+ }
706
+ catch {
707
+ targetDiscoveryFailed = true;
708
+ }
709
+ }
710
+ const impact = await analyzePluginConfigMutationImpact({
711
+ plugin: schemaPlugin,
366
712
  scope: options.scope,
367
713
  cwd: options.cwd,
368
714
  env: options.env,
369
- schema,
715
+ visibleTargets: discovered?.targets ?? [],
716
+ mutation: {
717
+ kind: 'patch',
718
+ updates: requestedUpdates,
719
+ removeKeys: requestedRemoveKeys,
720
+ },
370
721
  });
371
- assertReadyPluginConfigStore(store);
722
+ const updates = await coercePluginConfigUpdates({ schema, updates: requestedUpdates, cwd: options.cwd });
372
723
  const warnings = [];
373
- const removedKeys = requestedRemoveKeys.filter((key) => Object.hasOwn(store.values, key));
374
- const updatedKeys = Object.entries(updates)
375
- .filter(([key, value]) => !Object.hasOwn(store.values, key)
376
- || stablePluginConfigStringify(store.values[key]) !== stablePluginConfigStringify(value))
377
- .map(([key]) => key);
378
- const changedKeys = [...updatedKeys, ...removedKeys];
379
- let applyTargets = options.applyTargets;
380
- const sharedNativeAffected = changedKeysAffectSharedNativeTarget({
381
- plugin: schemaPlugin,
382
- changedKeys,
383
- });
724
+ const { updatedKeys, removedKeys, staleRemovedKeys } = impact;
725
+ let preApplyUnavailableReason;
726
+ let preApplyUnavailableTargetResults = [];
727
+ let applyDiscovery = discovered
728
+ ? { targets: impact.affectedTargets, failures: discovered.failures }
729
+ : undefined;
730
+ const shouldApply = options.apply !== false && impact.storeChanged && !impact.noEffectiveChange;
731
+ if (shouldApply && pluginMayRunPreApply(schemaPlugin)) {
732
+ if (applyDiscovery) {
733
+ const preApplyAuthorization = await resolveConfigPreApplyAuthorization({
734
+ plugin: schemaPlugin,
735
+ scope: options.scope,
736
+ cwd: options.cwd,
737
+ env: options.env,
738
+ applyTargets: applyDiscovery.targets,
739
+ nativeCliRunner: options.nativeCliRunner,
740
+ rebuildRuntime: options.rebuildRuntime,
741
+ reinstallCommandImpl: options.reinstallCommandImpl,
742
+ });
743
+ if (preApplyAuthorization.targets.length > 0 && !preApplyAuthorizationIsConfirmed({
744
+ required: preApplyAuthorization.targets,
745
+ confirmed: options.preApplyConfirmed,
746
+ authorizedTargets: options.preApplyAuthorizedTargets,
747
+ })) {
748
+ throwPreApplyAuthorizationRequired(preApplyAuthorization.targets);
749
+ }
750
+ preApplyUnavailableTargetResults = preApplyAuthorization.unavailableTargetResults;
751
+ applyDiscovery = {
752
+ targets: preApplyAuthorization.availableApplyTargets,
753
+ failures: [...applyDiscovery.failures, ...preApplyUnavailableTargetResults],
754
+ };
755
+ if (applyDiscovery.targets.length === 0 && preApplyUnavailableTargetResults.length > 0) {
756
+ preApplyUnavailableReason = 'sourceUnavailable';
757
+ }
758
+ }
759
+ }
384
760
  if (options.apply !== false
385
761
  && options.scope === 'user'
386
- && options.sharedNativeApplyConfirmed === false
387
- && sharedNativeAffected) {
388
- applyTargets ??= (await discoverVisibleTargets({
389
- plugin: schemaPlugin,
390
- pluginName,
391
- scope: options.scope,
392
- cwd: options.cwd,
393
- env: options.env ?? process.env,
394
- nativeCliRunner: options.nativeCliRunner ?? getDefaultNativeCliRunner(),
395
- rebuildRuntime: options.rebuildRuntime,
396
- })).targets;
397
- if (applyTargets.some((target) => target.platform === 'codex')) {
398
- const error = new Error('sharedNativeConfigApplyConfirmationRequired: Applying this user config changes a shared Codex native artifact. Re-run with --yes.');
399
- error.code = 'sharedNativeConfigApplyConfirmationRequired';
400
- throw error;
762
+ && options.sharedNativeApplyConfirmed !== true
763
+ && impact.sharedNativeRenderTargets.length > 0
764
+ && impact.affectedTargets.some((target) => target.platform === 'codex')) {
765
+ throwSharedNativeApplyConfirmationRequired();
766
+ }
767
+ if (impact.storeChanged) {
768
+ try {
769
+ const write = await patchPluginConfigStore({
770
+ ...configIdentity,
771
+ pluginName,
772
+ scope: options.scope,
773
+ cwd: options.cwd,
774
+ env: options.env,
775
+ updates: Object.fromEntries(updatedKeys.map((key) => [key, updates[key]])),
776
+ removeKeys: requestedRemoveKeys,
777
+ schema,
778
+ });
779
+ warnings.push(...write.warnings);
780
+ }
781
+ catch (error) {
782
+ throw markConfigWriteFailed(error);
401
783
  }
402
784
  }
403
- if (changedKeys.length > 0) {
404
- await patchPluginConfigStore({
785
+ let effective;
786
+ try {
787
+ effective = summarizeEffectivePluginConfig(await resolveEffectivePluginConfig({
405
788
  ...configIdentity,
406
789
  pluginName,
407
- scope: options.scope,
790
+ manifest: schemaPlugin.manifest,
791
+ targetScope: options.scope,
408
792
  cwd: options.cwd,
409
793
  env: options.env,
410
- updates: Object.fromEntries(updatedKeys.map((key) => [key, updates[key]])),
411
- removeKeys: requestedRemoveKeys,
412
- schema,
413
- });
414
- }
415
- const effective = summarizeEffectivePluginConfig(await resolveEffectivePluginConfig({
416
- ...configIdentity,
417
- pluginName,
418
- manifest: schemaPlugin.manifest,
419
- targetScope: options.scope,
420
- cwd: options.cwd,
421
- env: options.env,
422
- }));
423
- let applied;
424
- if (changedKeys.length === 0) {
425
- applied = {
426
- overallState: 'succeeded',
427
- targetResults: [],
428
- applySkippedReason: 'noConfigChange',
429
- };
794
+ }));
430
795
  }
431
- else if (effective.issues.length > 0) {
432
- applied = {
796
+ catch {
797
+ const apply = unavailableApply('effectiveConfigUnavailable', 'effectiveConfig');
798
+ const otherProjectsMayRequireSync = options.scope === 'user' && impact.storeChanged;
799
+ if (otherProjectsMayRequireSync)
800
+ warnings.push(...otherProjectsWarning(options.scope));
801
+ return {
802
+ plugin: pluginName,
803
+ scope: options.scope,
804
+ ...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
805
+ updatedKeys,
806
+ removedKeys,
807
+ staleRemovedKeys,
808
+ configWrite: 'succeeded',
433
809
  overallState: 'partial',
434
- targetResults: [],
435
- applySkippedReason: 'invalidConfig',
810
+ apply: publicConfigApplyResult(apply),
811
+ otherProjectsMayRequireSync,
812
+ warnings,
436
813
  };
437
814
  }
438
- else if (options.apply === false) {
439
- applied = {
440
- overallState: 'succeeded',
441
- targetResults: [],
442
- applySkippedReason: 'notRequested',
443
- };
815
+ let apply;
816
+ try {
817
+ apply = !impact.storeChanged
818
+ ? skippedApply('noConfigChange')
819
+ : options.apply === false
820
+ ? skippedApply('notRequested')
821
+ : targetDiscoveryFailed
822
+ ? unavailableApply('targetDiscoveryFailed', 'targetDiscovery')
823
+ : impact.noEffectiveChange
824
+ && ((discovered?.targets.length ?? 0) > 0
825
+ || staleRemovedKeys.length === impact.changedKeys.length)
826
+ ? skippedApply('noEffectiveConfigChange')
827
+ : preApplyUnavailableReason
828
+ ? unavailableApply(preApplyUnavailableReason, preApplyUnavailableReason === 'sourceUnavailable' ? 'source' : 'targetDiscovery', preApplyUnavailableTargetResults)
829
+ : await applyConfigToVisibleTargets({
830
+ plugin: schemaPlugin,
831
+ scope: options.scope,
832
+ cwd: options.cwd,
833
+ env: options.env ?? process.env,
834
+ applyTargets: applyDiscovery?.targets ?? impact.affectedTargets,
835
+ nativeCliRunner: options.nativeCliRunner,
836
+ reinstallCommandImpl: options.reinstallCommandImpl,
837
+ rebuildRuntime: options.rebuildRuntime,
838
+ noVisibleTargetHasInvalidConfig: (discovered?.targets.length ?? 0) > 0
839
+ && effective.issues.length > 0,
840
+ preApplyConfirmed: options.preApplyConfirmed,
841
+ discovered: applyDiscovery,
842
+ });
444
843
  }
445
- else {
446
- const discovered = await resolveConfigApplyTargets({
447
- plugin: schemaPlugin,
448
- scope: options.scope,
449
- cwd: options.cwd,
450
- env: options.env ?? process.env,
451
- applyTargets: options.applyTargets,
452
- nativeCliRunner: options.nativeCliRunner,
453
- rebuildRuntime: options.rebuildRuntime,
454
- });
455
- applied = await configApplyHasIssues({
456
- plugin: schemaPlugin,
457
- targets: discovered.targets,
458
- cwd: options.cwd,
459
- env: options.env,
460
- knownValidScopes: [options.scope],
461
- }) ? {
462
- overallState: 'partial',
463
- targetResults: [],
464
- applySkippedReason: 'invalidConfig',
465
- } : await reinstallVisibleTargets({
466
- plugin: schemaPlugin,
467
- scope: options.scope,
468
- cwd: options.cwd,
469
- env: options.env ?? process.env,
470
- applyTargets,
471
- nativeCliRunner: options.nativeCliRunner,
472
- reinstallCommandImpl: options.reinstallCommandImpl,
473
- rebuildRuntime: options.rebuildRuntime,
474
- discovered,
475
- });
844
+ catch (error) {
845
+ const failure = error instanceof Error ? error : new Error(String(error));
846
+ throw Object.assign(failure, { configWrite: 'succeeded' });
476
847
  }
477
- const otherProjectsRequireReinstall = options.scope === 'user'
478
- && changedKeys.length > 0
479
- && effective.issues.length === 0
480
- && applied.applySkippedReason !== 'invalidConfig';
481
- if (otherProjectsRequireReinstall)
848
+ const otherProjectsMayRequireSync = options.scope === 'user' && impact.storeChanged;
849
+ if (otherProjectsMayRequireSync)
482
850
  warnings.push(...otherProjectsWarning(options.scope));
483
851
  return {
484
852
  plugin: pluginName,
@@ -486,12 +854,12 @@ async function applyPluginConfigWriteUnlocked(options) {
486
854
  ...(schemaAccess.schema ? { schema: schemaAccess.schema } : {}),
487
855
  updatedKeys,
488
856
  removedKeys,
857
+ staleRemovedKeys,
489
858
  effective,
490
859
  configWrite: 'succeeded',
491
- overallState: applied.overallState,
492
- targetResults: applied.targetResults,
493
- ...(applied.applySkippedReason ? { applySkippedReason: applied.applySkippedReason } : {}),
494
- otherProjectsRequireReinstall,
860
+ overallState: overallStateForApply(apply),
861
+ apply: publicConfigApplyResult(apply),
862
+ otherProjectsMayRequireSync,
495
863
  warnings,
496
864
  };
497
865
  }
@@ -500,6 +868,20 @@ export async function applyPluginConfigWrite(options) {
500
868
  if (!requestedPluginName)
501
869
  throw new Error('Plugin name is required for config write.');
502
870
  assertSafePluginConfigSegment(requestedPluginName);
871
+ if (options.scope === 'project') {
872
+ const targetPath = resolvePluginConfigStorePath({
873
+ sourceId: options.plugin?.sourceId,
874
+ pluginName: requestedPluginName,
875
+ scope: 'project',
876
+ cwd: options.cwd,
877
+ env: options.env,
878
+ });
879
+ await assertProjectSharedConfigPathVisible({
880
+ cwd: options.cwd,
881
+ targetPath,
882
+ env: options.env,
883
+ });
884
+ }
503
885
  return withAwHomeMutationLock({
504
886
  env: options.env,
505
887
  token: options.awHomeMutationToken,
@@ -522,6 +904,7 @@ export async function applyPluginConfigWrite(options) {
522
904
  ...(options.plugin.sourceId ? {
523
905
  sourceId: options.plugin.sourceId,
524
906
  sourceIdentity: options.plugin.sourceIdentity,
907
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
525
908
  } : {}),
526
909
  pluginName: options.plugin.manifest.name,
527
910
  manifest: options.plugin.manifest,
@@ -539,6 +922,7 @@ export async function applyPluginConfigWrite(options) {
539
922
  }
540
923
  }
541
924
  let message = redactPluginScriptOutput(error instanceof Error ? error.message : String(error), sensitiveValues);
925
+ message = message.split(options.cwd).join('<project>');
542
926
  for (const scope of ['user', 'project']) {
543
927
  const configPath = resolvePluginConfigStorePath({
544
928
  sourceId: options.plugin?.sourceId,
@@ -556,6 +940,13 @@ export async function applyPluginConfigWrite(options) {
556
940
  const safeError = new Error(message);
557
941
  const code = error.code;
558
942
  safeError.code = typeof code === 'string' ? code : 'configApplyFailed';
943
+ const configWrite = error.configWrite;
944
+ if (configWrite === 'succeeded' || configWriteStateForError(error) === 'failed') {
945
+ safeError.configWrite = configWrite === 'succeeded' ? 'succeeded' : 'failed';
946
+ }
947
+ if (error && typeof error === 'object' && 'operationDiagnostic' in error) {
948
+ safeError.operationDiagnostic = error.operationDiagnostic;
949
+ }
559
950
  throw safeError;
560
951
  }
561
952
  },
@@ -563,19 +954,14 @@ export async function applyPluginConfigWrite(options) {
563
954
  }
564
955
  async function resetPluginConfigWithHelperUnlocked(options) {
565
956
  const pluginName = assertSafePluginConfigSegment(options.pluginName);
566
- const schemaAccess = options.plugin ? await resolvePluginConfigSchemaAccess({
567
- pluginName,
568
- currentPlugin: options.plugin,
569
- scope: options.scope,
570
- cwd: options.cwd,
571
- env: options.env,
572
- }) : undefined;
573
957
  let configIdentity = options.plugin?.sourceId ? {
574
958
  sourceId: options.plugin.sourceId,
575
959
  sourceIdentity: options.plugin.sourceIdentity,
960
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
576
961
  } : options.sourceId ? {
577
962
  sourceId: options.sourceId,
578
963
  sourceIdentity: options.sourceIdentity,
964
+ logicalIdentity: options.logicalIdentity,
579
965
  } : {};
580
966
  if (options.sourceId && !options.sourceIdentity) {
581
967
  try {
@@ -586,10 +972,15 @@ async function resetPluginConfigWithHelperUnlocked(options) {
586
972
  cwd: options.cwd,
587
973
  env: options.env,
588
974
  });
589
- if (identity)
975
+ if (identity?.binding.kind === 'source')
976
+ configIdentity = {
977
+ sourceId: options.sourceId,
978
+ sourceIdentity: identity.binding.sourceIdentity,
979
+ };
980
+ if (identity?.binding.kind === 'catalog')
590
981
  configIdentity = {
591
982
  sourceId: options.sourceId,
592
- sourceIdentity: identity.sourceIdentity,
983
+ logicalIdentity: identity.binding.logicalIdentity,
593
984
  };
594
985
  }
595
986
  catch {
@@ -611,87 +1002,179 @@ async function resetPluginConfigWithHelperUnlocked(options) {
611
1002
  // Reset is the final recovery boundary and does not require a readable store.
612
1003
  }
613
1004
  const warnings = [];
614
- const reset = await resetPluginConfigStore({
615
- ...configIdentity,
616
- pluginName,
617
- scope: options.scope,
618
- cwd: options.cwd,
619
- env: options.env,
620
- });
621
- let applied = {
622
- overallState: 'succeeded',
623
- targetResults: [],
624
- applySkippedReason: reset.deleted ? 'notRequested' : 'noConfigChange',
625
- };
1005
+ let impact;
1006
+ let targetDiscoveryFailed = false;
1007
+ let visibleTargets = [];
1008
+ if (options.plugin) {
1009
+ if (options.apply !== false) {
1010
+ try {
1011
+ visibleTargets = (await resolveConfigApplyTargets({
1012
+ plugin: options.plugin,
1013
+ scope: options.scope,
1014
+ cwd: options.cwd,
1015
+ env: options.env ?? process.env,
1016
+ applyTargets: options.applyTargets,
1017
+ nativeCliRunner: options.nativeCliRunner,
1018
+ rebuildRuntime: options.rebuildRuntime,
1019
+ })).targets;
1020
+ }
1021
+ catch {
1022
+ targetDiscoveryFailed = true;
1023
+ }
1024
+ }
1025
+ try {
1026
+ impact = await analyzePluginConfigMutationImpact({
1027
+ plugin: options.plugin,
1028
+ scope: options.scope,
1029
+ cwd: options.cwd,
1030
+ env: options.env,
1031
+ visibleTargets,
1032
+ mutation: { kind: 'reset' },
1033
+ });
1034
+ removedKeys = impact.removedKeys;
1035
+ }
1036
+ catch {
1037
+ // Reset remains available when its before state cannot be read.
1038
+ }
1039
+ }
1040
+ let preApplyUnavailableReason;
1041
+ let preApplyUnavailableTargetResults = [];
1042
+ let applyDiscovery = options.plugin && !targetDiscoveryFailed
1043
+ ? { targets: impact?.affectedTargets ?? visibleTargets, failures: [] }
1044
+ : undefined;
1045
+ if (options.plugin
1046
+ && options.apply !== false
1047
+ && impact?.storeChanged
1048
+ && !impact.noEffectiveChange
1049
+ && applyDiscovery
1050
+ && pluginMayRunPreApply(options.plugin)) {
1051
+ const preApplyAuthorization = await resolveConfigPreApplyAuthorization({
1052
+ plugin: options.plugin,
1053
+ scope: options.scope,
1054
+ cwd: options.cwd,
1055
+ env: options.env,
1056
+ applyTargets: applyDiscovery.targets,
1057
+ nativeCliRunner: options.nativeCliRunner,
1058
+ rebuildRuntime: options.rebuildRuntime,
1059
+ reinstallCommandImpl: options.reinstallCommandImpl,
1060
+ });
1061
+ if (preApplyAuthorization.targets.length > 0 && !preApplyAuthorizationIsConfirmed({
1062
+ required: preApplyAuthorization.targets,
1063
+ confirmed: options.preApplyConfirmed,
1064
+ authorizedTargets: options.preApplyAuthorizedTargets,
1065
+ })) {
1066
+ throwPreApplyAuthorizationRequired(preApplyAuthorization.targets);
1067
+ }
1068
+ preApplyUnavailableTargetResults = preApplyAuthorization.unavailableTargetResults;
1069
+ applyDiscovery = {
1070
+ targets: preApplyAuthorization.availableApplyTargets,
1071
+ failures: preApplyUnavailableTargetResults,
1072
+ };
1073
+ if (applyDiscovery.targets.length === 0 && preApplyUnavailableTargetResults.length > 0) {
1074
+ preApplyUnavailableReason = 'sourceUnavailable';
1075
+ }
1076
+ }
1077
+ if (options.plugin
1078
+ && options.apply !== false
1079
+ && options.scope === 'user'
1080
+ && options.sharedNativeApplyConfirmed !== true
1081
+ && (impact
1082
+ ? impact.sharedNativeRenderTargets.length > 0
1083
+ && impact.affectedTargets.some((target) => target.platform === 'codex')
1084
+ : pluginHasSharedNativeRefreshKeys(options.plugin))
1085
+ && visibleTargets.some((target) => target.platform === 'codex')) {
1086
+ throwSharedNativeApplyConfirmationRequired();
1087
+ }
1088
+ let reset;
1089
+ try {
1090
+ reset = await resetPluginConfigStore({
1091
+ ...configIdentity,
1092
+ pluginName,
1093
+ scope: options.scope,
1094
+ cwd: options.cwd,
1095
+ env: options.env,
1096
+ });
1097
+ }
1098
+ catch (error) {
1099
+ throw markConfigWriteFailed(error);
1100
+ }
1101
+ let schemaAccess;
1102
+ let apply = reset.deleted ? skippedApply('notRequested') : skippedApply('noConfigChange');
626
1103
  let effective;
627
- if (schemaAccess?.plugin) {
628
- if (schemaAccess.plugin.manifest.config?.fields) {
1104
+ if (options.plugin) {
1105
+ try {
1106
+ schemaAccess = await resolvePluginConfigSchemaAccess({
1107
+ pluginName,
1108
+ currentPlugin: options.plugin,
1109
+ scope: options.scope,
1110
+ cwd: options.cwd,
1111
+ env: options.env,
1112
+ });
1113
+ }
1114
+ catch {
1115
+ if (reset.deleted && options.apply !== false) {
1116
+ apply = unavailableApply('sourceUnavailable', 'source');
1117
+ }
1118
+ }
1119
+ const schemaPlugin = schemaAccess?.plugin;
1120
+ if (schemaPlugin?.manifest.config?.fields) {
629
1121
  try {
630
1122
  effective = summarizeEffectivePluginConfig(await resolveEffectivePluginConfig({
631
1123
  ...configIdentity,
632
1124
  pluginName,
633
- manifest: schemaAccess.plugin.manifest,
1125
+ manifest: schemaPlugin.manifest,
634
1126
  targetScope: options.scope,
635
1127
  cwd: options.cwd,
636
1128
  env: options.env,
637
1129
  }));
638
1130
  }
639
1131
  catch {
640
- if (options.apply !== false) {
641
- applied = {
642
- overallState: 'partial',
643
- targetResults: [],
644
- applySkippedReason: 'invalidConfig',
645
- };
1132
+ if (reset.deleted && options.apply !== false) {
1133
+ apply = unavailableApply('effectiveConfigUnavailable', 'effectiveConfig');
646
1134
  }
647
1135
  }
1136
+ if (reset.deleted
1137
+ && options.apply !== false
1138
+ && apply.state === 'skipped'
1139
+ && impact?.noEffectiveChange
1140
+ && visibleTargets.length > 0) {
1141
+ apply = skippedApply('noEffectiveConfigChange');
1142
+ }
1143
+ else if (reset.deleted
1144
+ && options.apply !== false
1145
+ && apply.state === 'skipped'
1146
+ && targetDiscoveryFailed) {
1147
+ apply = unavailableApply('targetDiscoveryFailed', 'targetDiscovery');
1148
+ }
1149
+ else if (reset.deleted && options.apply !== false && apply.state === 'skipped') {
1150
+ apply = preApplyUnavailableReason
1151
+ ? unavailableApply(preApplyUnavailableReason, 'source', preApplyUnavailableTargetResults)
1152
+ : await applyConfigToVisibleTargets({
1153
+ plugin: schemaPlugin,
1154
+ scope: options.scope,
1155
+ cwd: options.cwd,
1156
+ env: options.env ?? process.env,
1157
+ applyTargets: applyDiscovery?.targets ?? impact?.affectedTargets ?? options.applyTargets,
1158
+ nativeCliRunner: options.nativeCliRunner,
1159
+ reinstallCommandImpl: options.reinstallCommandImpl,
1160
+ rebuildRuntime: options.rebuildRuntime,
1161
+ noVisibleTargetHasInvalidConfig: (effective?.issues.length ?? 0) > 0,
1162
+ preApplyConfirmed: options.preApplyConfirmed,
1163
+ discovered: applyDiscovery,
1164
+ });
1165
+ }
648
1166
  }
649
- if (reset.deleted && effective && effective.issues.length > 0) {
650
- applied = {
651
- overallState: 'partial',
652
- targetResults: [],
653
- applySkippedReason: 'invalidConfig',
654
- };
655
- }
656
- else if (reset.deleted && options.apply !== false && applied.applySkippedReason !== 'invalidConfig') {
657
- const discovered = await resolveConfigApplyTargets({
658
- plugin: schemaAccess.plugin,
659
- scope: options.scope,
660
- cwd: options.cwd,
661
- env: options.env ?? process.env,
662
- applyTargets: options.applyTargets,
663
- nativeCliRunner: options.nativeCliRunner,
664
- rebuildRuntime: options.rebuildRuntime,
665
- });
666
- applied = await configApplyHasIssues({
667
- plugin: schemaAccess.plugin,
668
- targets: discovered.targets,
669
- cwd: options.cwd,
670
- env: options.env,
671
- knownValidScopes: effective ? [options.scope] : undefined,
672
- }) ? {
673
- overallState: 'partial',
674
- targetResults: [],
675
- applySkippedReason: 'invalidConfig',
676
- } : await reinstallVisibleTargets({
677
- plugin: schemaAccess.plugin,
678
- scope: options.scope,
679
- cwd: options.cwd,
680
- env: options.env ?? process.env,
681
- applyTargets: options.applyTargets,
682
- nativeCliRunner: options.nativeCliRunner,
683
- reinstallCommandImpl: options.reinstallCommandImpl,
684
- rebuildRuntime: options.rebuildRuntime,
685
- discovered,
686
- });
1167
+ else if (reset.deleted && options.apply !== false && apply.state === 'skipped') {
1168
+ apply = unavailableApply('schemaUnavailable', 'schema');
687
1169
  }
688
1170
  }
689
- const otherProjectsRequireReinstall = Boolean(schemaAccess?.plugin)
690
- && options.scope === 'user'
691
- && reset.deleted
692
- && (effective?.issues.length ?? 0) === 0
693
- && applied.applySkippedReason !== 'invalidConfig';
694
- if (otherProjectsRequireReinstall)
1171
+ else if (reset.deleted && options.apply !== false) {
1172
+ apply = options.pluginError
1173
+ ? unavailableApply('sourceUnavailable', 'source')
1174
+ : unavailableApply('schemaUnavailable', 'schema');
1175
+ }
1176
+ const otherProjectsMayRequireSync = options.scope === 'user' && reset.deleted;
1177
+ if (otherProjectsMayRequireSync)
695
1178
  warnings.push(...otherProjectsWarning(options.scope));
696
1179
  return {
697
1180
  plugin: pluginName,
@@ -704,10 +1187,9 @@ async function resetPluginConfigWithHelperUnlocked(options) {
704
1187
  removedKeys,
705
1188
  ...(effective ? { effective } : {}),
706
1189
  configWrite: 'succeeded',
707
- overallState: applied.overallState,
708
- targetResults: applied.targetResults,
709
- ...(applied.applySkippedReason ? { applySkippedReason: applied.applySkippedReason } : {}),
710
- otherProjectsRequireReinstall,
1190
+ overallState: overallStateForApply(apply),
1191
+ apply: publicConfigApplyResult(apply),
1192
+ otherProjectsMayRequireSync,
711
1193
  warnings,
712
1194
  };
713
1195
  }
@@ -751,9 +1233,11 @@ export async function describeResetAffectedFields(options) {
751
1233
  ...(options.plugin?.sourceId ? {
752
1234
  sourceId: options.plugin.sourceId,
753
1235
  sourceIdentity: options.plugin.sourceIdentity,
1236
+ logicalIdentity: options.plugin.catalogLogicalIdentity,
754
1237
  } : options.sourceId ? {
755
1238
  sourceId: options.sourceId,
756
1239
  sourceIdentity: options.sourceIdentity,
1240
+ logicalIdentity: options.logicalIdentity,
757
1241
  } : {}),
758
1242
  pluginName: options.pluginName,
759
1243
  scope: options.scope,