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

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 (72) hide show
  1. package/dist/src/commands/catalog-dev-source.d.ts +31 -0
  2. package/dist/src/commands/catalog-dev-source.js +165 -5
  3. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  4. package/dist/src/commands/catalog-generation.d.ts +5 -0
  5. package/dist/src/commands/catalog-generation.js.map +1 -1
  6. package/dist/src/commands/catalog-publication-journal.d.ts +12 -0
  7. package/dist/src/commands/catalog-publication-journal.js +77 -0
  8. package/dist/src/commands/catalog-publication-journal.js.map +1 -1
  9. package/dist/src/commands/catalog-store.d.ts +5 -0
  10. package/dist/src/commands/catalog-store.js +19 -1
  11. package/dist/src/commands/catalog-store.js.map +1 -1
  12. package/dist/src/commands/dependency-operation-plan.d.ts +7 -0
  13. package/dist/src/commands/dependency-operation-plan.js +63 -27
  14. package/dist/src/commands/dependency-operation-plan.js.map +1 -1
  15. package/dist/src/commands/rebuild-command.d.ts +22 -8
  16. package/dist/src/commands/rebuild-command.js +562 -250
  17. package/dist/src/commands/rebuild-command.js.map +1 -1
  18. package/dist/src/commands/rebuild-integration.js +188 -67
  19. package/dist/src/commands/rebuild-integration.js.map +1 -1
  20. package/dist/src/commands/reconcile-operation-plan.d.ts +11 -9
  21. package/dist/src/commands/reconcile-operation-plan.js +25 -6
  22. package/dist/src/commands/reconcile-operation-plan.js.map +1 -1
  23. package/dist/src/commands/status-v6.js +2 -2
  24. package/dist/src/commands/status-v6.js.map +1 -1
  25. package/dist/src/commands/write-commands.d.ts +2 -1
  26. package/dist/src/commands/write-commands.js +24 -1
  27. package/dist/src/commands/write-commands.js.map +1 -1
  28. package/dist/src/tui/catalog-management-screens.d.ts +45 -0
  29. package/dist/src/tui/catalog-management-screens.js +25 -0
  30. package/dist/src/tui/catalog-management-screens.js.map +1 -0
  31. package/dist/src/tui/catalog-management.d.ts +240 -0
  32. package/dist/src/tui/catalog-management.js +856 -0
  33. package/dist/src/tui/catalog-management.js.map +1 -0
  34. package/dist/src/tui/catalog-sources/session.d.ts +5 -0
  35. package/dist/src/tui/catalog-sources/session.js +49 -1
  36. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  37. package/dist/src/tui/catalog-sources/types.d.ts +11 -0
  38. package/dist/src/tui/console-app-v4.d.ts +1 -1
  39. package/dist/src/tui/console-app-v4.js +2184 -564
  40. package/dist/src/tui/console-app-v4.js.map +1 -1
  41. package/dist/src/tui/controller-runtime.js +609 -13
  42. package/dist/src/tui/controller-runtime.js.map +1 -1
  43. package/dist/src/tui/controller.d.ts +121 -6
  44. package/dist/src/tui/controller.js +667 -17
  45. package/dist/src/tui/controller.js.map +1 -1
  46. package/dist/src/tui/dependency-graph.d.ts +67 -0
  47. package/dist/src/tui/dependency-graph.js +399 -0
  48. package/dist/src/tui/dependency-graph.js.map +1 -0
  49. package/dist/src/tui/lifecycle-action-view.js +4 -2
  50. package/dist/src/tui/lifecycle-action-view.js.map +1 -1
  51. package/dist/src/tui/model.d.ts +37 -0
  52. package/dist/src/tui/model.js +179 -17
  53. package/dist/src/tui/model.js.map +1 -1
  54. package/dist/src/tui/operation-review-planner.js +244 -8
  55. package/dist/src/tui/operation-review-planner.js.map +1 -1
  56. package/dist/src/tui/plugin-write-task.d.ts +66 -0
  57. package/dist/src/tui/plugin-write-task.js +265 -0
  58. package/dist/src/tui/plugin-write-task.js.map +1 -0
  59. package/dist/src/tui/router.d.ts +38 -2
  60. package/dist/src/tui/router.js.map +1 -1
  61. package/dist/src/tui/screen-registry.d.ts +105 -6
  62. package/dist/src/tui/screen-registry.js +31 -4
  63. package/dist/src/tui/screen-registry.js.map +1 -1
  64. package/dist/src/utils/catalog-generation-service.d.ts +4 -0
  65. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  66. package/dist/src/utils/codex-shared-native.d.ts +4 -0
  67. package/dist/src/utils/codex-shared-native.js +13 -0
  68. package/dist/src/utils/codex-shared-native.js.map +1 -1
  69. package/dist/src/utils/local-marketplace.d.ts +14 -1
  70. package/dist/src/utils/local-marketplace.js +242 -14
  71. package/dist/src/utils/local-marketplace.js.map +1 -1
  72. package/package.json +3 -3
@@ -108,14 +108,34 @@ function omitSelectedPluginConfigErrors(inventory, targets) {
108
108
  function selectedPlatforms(selection) {
109
109
  return selection === 'both' ? ['codex', 'claude'] : [selection];
110
110
  }
111
- function selectCodexActivationPlans(plans, pluginIds) {
111
+ function selectCodexActivationPlans(plans, targetScopes, preservedActivations) {
112
112
  return plans.flatMap((plan) => {
113
- if (!plan.desiredTargets.some((target) => pluginIds.has(target.pluginId)))
113
+ const includesScope = (pluginId, scope) => targetScopes.has(`${pluginId}\0${scope}`);
114
+ const pluginIds = new Set(plan.desiredTargets.flatMap((target) => ['user', 'project'].some((scope) => includesScope(target.pluginId, scope))
115
+ ? [target.pluginId]
116
+ : []));
117
+ if (pluginIds.size === 0)
114
118
  return [];
119
+ const includesActivationAction = (action) => includesScope(action.pluginId, action.scope)
120
+ || !(action.scope === plan.requirement.scope
121
+ ? plan.managedInventory.desired
122
+ : plan.managedInventory.related.desired).includes(action.codexPluginId);
115
123
  const sharedNativeActions = plan.sharedNativeActions.filter((action) => pluginIds.has(action.pluginId));
116
- const userActions = plan.activationActions.user.filter((action) => pluginIds.has(action.pluginId));
117
- const projectActions = plan.activationActions.project.filter((action) => pluginIds.has(action.pluginId));
118
- const postcheckPlugins = plan.postcheck.plugins.filter((plugin) => pluginIds.has(plugin.pluginId));
124
+ const userActions = plan.activationActions.user.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
125
+ const projectActions = plan.activationActions.project.filter((action) => pluginIds.has(action.pluginId) && includesActivationAction(action));
126
+ const desiredTargets = plan.desiredTargets.flatMap((target) => {
127
+ const scopes = target.scopes.filter((scope) => includesScope(target.pluginId, scope));
128
+ return scopes.length > 0 ? [{ ...target, scopes }] : [];
129
+ });
130
+ const postcheckPlugins = plan.postcheck.plugins.filter((plugin) => pluginIds.has(plugin.pluginId)).map((plugin) => ({
131
+ ...plugin,
132
+ expectedUserEnabled: userActions.some((action) => action.pluginId === plugin.pluginId)
133
+ ? plugin.expectedUserEnabled
134
+ : preservedActivations?.user.get(plugin.codexPluginId) ?? false,
135
+ expectedProjectEnabled: projectActions.some((action) => action.pluginId === plugin.pluginId)
136
+ ? plugin.expectedProjectEnabled
137
+ : preservedActivations?.project.get(plugin.codexPluginId) ?? false,
138
+ }));
119
139
  const codexPluginIds = new Set([
120
140
  ...sharedNativeActions.map((action) => action.codexPluginId),
121
141
  ...userActions.map((action) => action.codexPluginId),
@@ -124,16 +144,20 @@ function selectCodexActivationPlans(plans, pluginIds) {
124
144
  ]);
125
145
  return [{
126
146
  ...plan,
147
+ desiredTargets,
127
148
  activationActions: { user: userActions, project: projectActions },
128
- staleActivationRemovals: plan.staleActivationRemovals.filter((action) => codexPluginIds.has(action.codexPluginId)),
149
+ extensionWrites: plan.extensionWrites.filter((action) => includesScope(action.pluginId, action.scope)),
150
+ extensionRemovals: plan.extensionRemovals.filter((action) => includesScope(action.pluginId, action.scope)),
151
+ staleActivationRemovals: plan.staleActivationRemovals.filter((action) => codexPluginIds.has(action.codexPluginId)
152
+ && [...pluginIds].some((pluginId) => includesScope(pluginId, action.scope))),
129
153
  postcheck: { ...plan.postcheck, plugins: postcheckPlugins },
130
154
  }];
131
155
  });
132
156
  }
133
- function failedCodexActivationPluginIds(options) {
157
+ function failedCodexActivationTargetScopes(options) {
134
158
  const error = options.error;
135
159
  if (!(error instanceof CodexActivationPostcheckError)) {
136
- return new Set(options.successfulPluginIds);
160
+ return new Set(options.successfulTargetScopes);
137
161
  }
138
162
  const ownersByCodexPluginId = new Map();
139
163
  for (const entry of options.plans.flatMap((plan) => [
@@ -149,13 +173,16 @@ function failedCodexActivationPluginIds(options) {
149
173
  for (const codexPluginId of error.codexPluginIds) {
150
174
  const owners = ownersByCodexPluginId.get(codexPluginId);
151
175
  if (!owners || owners.size === 0)
152
- return new Set(options.successfulPluginIds);
176
+ return new Set(options.successfulTargetScopes);
153
177
  for (const pluginId of owners) {
154
- if (options.successfulPluginIds.has(pluginId))
155
- failed.add(pluginId);
178
+ for (const scope of ['user', 'project']) {
179
+ const key = `${pluginId}\0${scope}`;
180
+ if (options.successfulTargetScopes.has(key))
181
+ failed.add(key);
182
+ }
156
183
  }
157
184
  }
158
- return failed.size > 0 ? failed : new Set(options.successfulPluginIds);
185
+ return failed.size > 0 ? failed : new Set(options.successfulTargetScopes);
159
186
  }
160
187
  function updateIntentOverrides(options) {
161
188
  const plannedKeys = new Set(options.plan.targets.map((target) => `${target.pluginId}\0${target.platform}`));
@@ -259,10 +286,17 @@ async function writeDesiredIntentSuperset(options) {
259
286
  const otherScope = options.scope === 'user' ? 'project' : 'user';
260
287
  const otherLocation = { scope: otherScope, cwd: options.cwd, env: options.env };
261
288
  let other = await readPluginIntentState(otherLocation);
262
- if (options.command === 'install') {
289
+ if (options.command === 'install' && options.removeOtherScopeRoots !== false) {
290
+ const protectedOtherRootPlatforms = new Set(options.plan.targets.flatMap((target) => target.relation === 'requested'
291
+ && (target.requestedScope ?? target.resolvedScope) === otherScope
292
+ ? [`${target.pluginId}\0${target.platform}`]
293
+ : []));
263
294
  other = removeSelectedRootPlatformsFromOtherScope({
264
295
  state: other,
265
- selectedRoots: options.selectedRoots,
296
+ selectedRoots: options.selectedRoots.flatMap((root) => {
297
+ const platforms = root.platforms.filter((platform) => !protectedOtherRootPlatforms.has(`${root.pluginId}\0${platform}`));
298
+ return platforms.length > 0 ? [{ ...root, platforms }] : [];
299
+ }),
266
300
  preserveCodexRootIds: options.preserveOtherCodexScopeRootIds,
267
301
  });
268
302
  }
@@ -616,24 +650,30 @@ function reconcileOverallState(targetResults, plan, rebuildResults = []) {
616
650
  return completedRequested || completedMutation || observedMutation ? 'partial' : 'failed';
617
651
  }
618
652
  function createRebuildOperationPlan(options) {
619
- const selectedRoots = [...new Map(options.dependencyPlan.targets.flatMap((target) => target.relation === 'requested'
620
- ? [[target.pluginId, {
621
- pluginId: target.pluginId,
622
- platforms: options.dependencyPlan.targets.flatMap((candidate) => candidate.relation === 'requested' && candidate.pluginId === target.pluginId
623
- ? [candidate.platform]
624
- : []),
625
- }]]
626
- : [])).values()];
627
- const intentBefore = options.intentBeforeByScope[options.scope];
628
- const intentAfter = deriveDesiredIntentState({
629
- command: options.command,
630
- state: intentBefore,
631
- selectedRoots,
632
- plan: options.dependencyPlan,
633
- catalogEntries: options.catalogEntries,
653
+ const intentChanges = ['user', 'project'].flatMap((scope) => {
654
+ const selectedRoots = [...new Map(options.dependencyPlan.targets.flatMap((target) => target.relation === 'requested'
655
+ && (target.requestedScope ?? target.resolvedScope) === scope
656
+ ? [[target.pluginId, {
657
+ pluginId: target.pluginId,
658
+ platforms: options.dependencyPlan.targets.flatMap((candidate) => candidate.relation === 'requested'
659
+ && candidate.pluginId === target.pluginId
660
+ && (candidate.requestedScope ?? candidate.resolvedScope) === scope
661
+ ? [candidate.platform]
662
+ : []),
663
+ }]]
664
+ : [])).values()];
665
+ const intentBefore = options.intentBeforeByScope[scope];
666
+ const intentAfter = deriveDesiredIntentState({
667
+ command: options.command,
668
+ state: intentBefore,
669
+ selectedRoots,
670
+ plan: options.dependencyPlan,
671
+ catalogEntries: options.catalogEntries,
672
+ });
673
+ const beforeDigest = calculatePluginIntentDigest(intentBefore);
674
+ const afterDigest = calculatePluginIntentDigest(intentAfter);
675
+ return beforeDigest === afterDigest ? [] : [{ scope, beforeDigest, afterDigest }];
634
676
  });
635
- const beforeDigest = calculatePluginIntentDigest(intentBefore);
636
- const afterDigest = calculatePluginIntentDigest(intentAfter);
637
677
  const actions = [
638
678
  ...(options.targetActions ?? []),
639
679
  ...options.codexPlans.flatMap((plan) => [
@@ -665,9 +705,7 @@ function createRebuildOperationPlan(options) {
665
705
  ]),
666
706
  ];
667
707
  return createOperationPlan({
668
- intentChanges: beforeDigest === afterDigest
669
- ? []
670
- : [{ scope: options.scope, beforeDigest, afterDigest }],
708
+ intentChanges,
671
709
  actions,
672
710
  changes: options.dependencyPlan.targets.map((target) => [
673
711
  target.pluginId,
@@ -1601,6 +1639,7 @@ async function runRebuildCommandInSession(options) {
1601
1639
  const dependencyEnabled = options.command === 'install'
1602
1640
  || options.command === 'sync'
1603
1641
  || options.command === 'reinstall';
1642
+ const intentCommand = options.taskTargets ? 'install' : options.command;
1604
1643
  const entryClosure = (roots) => {
1605
1644
  const result = [];
1606
1645
  const seen = new Set();
@@ -1690,10 +1729,15 @@ async function runRebuildCommandInSession(options) {
1690
1729
  });
1691
1730
  const closureEntriesByPlatform = new Map();
1692
1731
  if (dependencyEnabled) {
1693
- for (const platform of selectedPlatforms(options.commandOptions.platform)) {
1694
- const platformRoots = options.commandOptions.platform === 'both'
1695
- ? selected.filter((entry) => supportsPlatform(entry.plugin.manifest, platform))
1696
- : selected;
1732
+ const selectedTaskPlatforms = options.taskTargets
1733
+ ? [...new Set(options.taskTargets.map((target) => target.platform))]
1734
+ : selectedPlatforms(options.commandOptions.platform);
1735
+ for (const platform of selectedTaskPlatforms) {
1736
+ const platformRoots = options.taskTargets
1737
+ ? selected.filter((entry) => options.taskTargets.some((target) => target.pluginId === entry.pluginId && target.platform === platform))
1738
+ : options.commandOptions.platform === 'both'
1739
+ ? selected.filter((entry) => supportsPlatform(entry.plugin.manifest, platform))
1740
+ : selected;
1697
1741
  closureEntriesByPlatform.set(platform, resolveClosureEntries(platformRoots));
1698
1742
  }
1699
1743
  }
@@ -1702,45 +1746,114 @@ async function runRebuildCommandInSession(options) {
1702
1746
  : options.command === 'cleanup'
1703
1747
  ? resolveClosureEntries(selected)
1704
1748
  : selected;
1705
- const rawExecutionSelections = options.all
1706
- ? liveTargets.flatMap((target) => {
1707
- const entry = selected.find((candidate) => candidate.pluginId === target.pluginId);
1708
- if (target.sourceIdentity !== 'unavailable'
1709
- && entry.source.sourceIdentity !== target.sourceIdentity) {
1710
- throw namespacedPreflightError(`sourceChanged: ${target.pluginId} live target has a different source identity.`, liveTargets, legacyLiveTargets);
1711
- }
1712
- const sourcePlugins = available
1713
- .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1714
- .map((candidate) => candidate.plugin);
1715
- return (options.command === 'uninstall'
1716
- ? [entry.plugin]
1717
- : resolvePluginMetaDependencyClosure(sourcePlugins, [entry.plugin.manifest.name])).map((plugin) => ({
1718
- entry: available.find((candidate) => candidate.source.sourceId === entry.source.sourceId
1719
- && candidate.plugin.manifest.name === plugin.manifest.name),
1720
- platform: target.platform,
1721
- }));
1749
+ const taskRootsByTarget = new Map();
1750
+ for (const target of options.taskTargets ?? []) {
1751
+ const key = `${target.pluginId}\0${target.platform}`;
1752
+ taskRootsByTarget.set(key, [...(taskRootsByTarget.get(key) ?? []), target]);
1753
+ }
1754
+ const rawExecutionSelections = options.taskTargets
1755
+ ? options.taskTargets.flatMap((root) => {
1756
+ const rootEntry = selected.find((entry) => entry.pluginId === root.pluginId);
1757
+ return resolveClosureEntries([rootEntry]).flatMap((entry) => {
1758
+ const exactRoots = taskRootsByTarget.get(`${entry.pluginId}\0${root.platform}`) ?? [];
1759
+ if (exactRoots.length > 0) {
1760
+ return exactRoots.map((exactRoot) => ({
1761
+ entry,
1762
+ platform: root.platform,
1763
+ requestedScope: exactRoot.scope,
1764
+ requestedMethod: exactRoot.method,
1765
+ }));
1766
+ }
1767
+ return [{
1768
+ entry,
1769
+ platform: root.platform,
1770
+ requestedMethod: options.taskRepairTargets?.find((target) => target.pluginId === entry.pluginId
1771
+ && target.platform === root.platform)?.method,
1772
+ }];
1773
+ });
1722
1774
  })
1723
- : selectedPlatforms(options.commandOptions.platform).flatMap((platform) => (dependencyEnabled ? closureEntriesByPlatform.get(platform) ?? [] : closureEntries)
1724
- .flatMap((entry) => options.command !== 'cleanup'
1725
- && options.command !== 'uninstall'
1726
- && options.commandOptions.platform === 'both'
1727
- && !supportsPlatform(entry.plugin.manifest, platform)
1728
- ? []
1729
- : [{ entry, platform }]));
1775
+ : options.all
1776
+ ? liveTargets.flatMap((target) => {
1777
+ const entry = selected.find((candidate) => candidate.pluginId === target.pluginId);
1778
+ if (target.sourceIdentity !== 'unavailable'
1779
+ && entry.source.sourceIdentity !== target.sourceIdentity) {
1780
+ throw namespacedPreflightError(`sourceChanged: ${target.pluginId} live target has a different source identity.`, liveTargets, legacyLiveTargets);
1781
+ }
1782
+ const sourcePlugins = available
1783
+ .filter((candidate) => candidate.source.sourceId === entry.source.sourceId)
1784
+ .map((candidate) => candidate.plugin);
1785
+ return (options.command === 'uninstall'
1786
+ ? [entry.plugin]
1787
+ : resolvePluginMetaDependencyClosure(sourcePlugins, [entry.plugin.manifest.name])).map((plugin) => ({
1788
+ entry: available.find((candidate) => candidate.source.sourceId === entry.source.sourceId
1789
+ && candidate.plugin.manifest.name === plugin.manifest.name),
1790
+ platform: target.platform,
1791
+ }));
1792
+ })
1793
+ : selectedPlatforms(options.commandOptions.platform).flatMap((platform) => (dependencyEnabled ? closureEntriesByPlatform.get(platform) ?? [] : closureEntries)
1794
+ .flatMap((entry) => options.command !== 'cleanup'
1795
+ && options.command !== 'uninstall'
1796
+ && options.commandOptions.platform === 'both'
1797
+ && !supportsPlatform(entry.plugin.manifest, platform)
1798
+ ? []
1799
+ : [{ entry, platform }]));
1800
+ const taskSelectionMethods = new Map();
1801
+ for (const selection of rawExecutionSelections) {
1802
+ if (!selection.requestedMethod)
1803
+ continue;
1804
+ const key = [
1805
+ selection.entry.pluginId,
1806
+ selection.platform,
1807
+ selection.requestedScope ?? '',
1808
+ ].join('\0');
1809
+ const current = taskSelectionMethods.get(key);
1810
+ if (current && current !== selection.requestedMethod) {
1811
+ throw new Error(`rebuildTaskMethodConflict: ${selection.entry.pluginId}#${selection.platform}`);
1812
+ }
1813
+ taskSelectionMethods.set(key, selection.requestedMethod);
1814
+ }
1730
1815
  let executionSelections = [...new Map(rawExecutionSelections.map((selection) => [[
1731
1816
  selection.entry.pluginId,
1732
1817
  selection.entry.source.sourceIdentity,
1733
1818
  selection.platform,
1734
- options.commandOptions.scope,
1819
+ selection.requestedScope ?? '',
1735
1820
  ].join('\0'), selection])).values()];
1736
- const preflightTargets = executionSelections.map(({ entry, platform }) => ({
1821
+ const planTargetFor = (plan, selection) => plan.targets.find((target) => target.pluginId === selection.entry.pluginId
1822
+ && target.platform === selection.platform
1823
+ && (!options.taskTargets
1824
+ || (selection.requestedScope === undefined
1825
+ ? target.relation === 'dependency'
1826
+ : target.requestedScope === selection.requestedScope)));
1827
+ const executionKey = (selection, plan) => {
1828
+ const target = planTargetFor(plan, selection);
1829
+ return [
1830
+ selection.entry.pluginId,
1831
+ selection.platform,
1832
+ target?.resolvedScope ?? selection.requestedScope ?? options.commandOptions.scope,
1833
+ ].join('\0');
1834
+ };
1835
+ const planExecutionKey = (target) => [
1836
+ target.pluginId,
1837
+ target.platform,
1838
+ target.requestedScope ?? target.resolvedScope,
1839
+ ].join('\0');
1840
+ const dependencyTargetFor = (plan, consumer, dependencyPluginId) => {
1841
+ const candidates = plan.targets.filter((target) => target.pluginId === dependencyPluginId
1842
+ && target.platform === consumer.platform);
1843
+ const consumerScope = consumer.requestedScope ?? consumer.resolvedScope;
1844
+ return candidates.find((target) => (target.requiredByTargets ?? []).some((requiredBy) => requiredBy.pluginId === consumer.pluginId
1845
+ && requiredBy.platform === consumer.platform
1846
+ && requiredBy.scope === consumerScope))
1847
+ ?? (candidates.length === 1 ? candidates[0] : undefined);
1848
+ };
1849
+ const preflightTargets = executionSelections.map(({ entry, platform, requestedScope }) => ({
1737
1850
  pluginId: entry.pluginId,
1738
1851
  pluginName: entry.plugin.manifest.name,
1739
1852
  sourceId: entry.source.sourceId,
1740
1853
  sourceIdentity: entry.source.sourceIdentity,
1741
1854
  pluginVersion: entry.plugin.manifest.version,
1742
1855
  platform,
1743
- scope: options.commandOptions.scope,
1856
+ scope: requestedScope ?? options.commandOptions.scope,
1744
1857
  }));
1745
1858
  if (options.runtime?.previewOperationPlan) {
1746
1859
  await options.runtime.onPreviewStage?.('inventory');
@@ -1929,28 +2042,50 @@ async function runRebuildCommandInSession(options) {
1929
2042
  context: executionContext,
1930
2043
  scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
1931
2044
  });
1932
- const selectedRootsForPlan = (plan) => [...new Map(plan.targets.flatMap((target) => target.relation === 'requested'
2045
+ const selectedRootsForScope = (plan, scope) => [...new Map(plan.targets.flatMap((target) => target.relation === 'requested'
2046
+ && (target.requestedScope ?? target.resolvedScope) === scope
1933
2047
  ? [[target.pluginId, {
1934
2048
  pluginId: target.pluginId,
1935
2049
  platforms: plan.targets.flatMap((candidate) => candidate.relation === 'requested'
1936
2050
  && candidate.pluginId === target.pluginId
2051
+ && (candidate.requestedScope ?? candidate.resolvedScope) === scope
1937
2052
  ? [candidate.platform]
1938
2053
  : []),
1939
2054
  }]]
1940
2055
  : [])).values()];
1941
- const intentPlanForPreparedRoots = (plan, preparedKeys) => {
2056
+ const intentPlanForPreparedRoots = (plan, preparedKeys, scope) => {
1942
2057
  const retainedRootKeys = new Set(plan.targets.flatMap((target) => target.relation === 'requested'
1943
- && preparedKeys.has(`${target.pluginId}\0${target.platform}`)
1944
- ? [`${target.pluginId}\0${target.platform}`]
2058
+ && (target.requestedScope ?? target.resolvedScope) === scope
2059
+ && preparedKeys.has([
2060
+ target.pluginId,
2061
+ target.platform,
2062
+ target.requestedScope ?? target.resolvedScope,
2063
+ ].join('\0'))
2064
+ ? [[
2065
+ target.pluginId,
2066
+ target.platform,
2067
+ target.requestedScope ?? target.resolvedScope,
2068
+ ].join('\0')]
1945
2069
  : []));
1946
2070
  const closureKeys = new Set(retainedRootKeys);
1947
2071
  const pending = [...closureKeys];
1948
2072
  while (pending.length > 0) {
1949
2073
  const consumerKey = pending.pop();
1950
- for (const edge of plan.dependencyEdges) {
1951
- if (`${edge.from}\0${edge.platform}` !== consumerKey)
1952
- continue;
1953
- const dependencyKey = `${edge.to}\0${edge.platform}`;
2074
+ const consumer = plan.targets.find((target) => [
2075
+ target.pluginId,
2076
+ target.platform,
2077
+ target.requestedScope ?? target.resolvedScope,
2078
+ ].join('\0') === consumerKey);
2079
+ if (!consumer)
2080
+ continue;
2081
+ for (const dependency of plan.targets.filter((target) => (target.requiredByTargets ?? []).some((requiredBy) => requiredBy.pluginId === consumer.pluginId
2082
+ && requiredBy.platform === consumer.platform
2083
+ && requiredBy.scope === (consumer.requestedScope ?? consumer.resolvedScope)))) {
2084
+ const dependencyKey = [
2085
+ dependency.pluginId,
2086
+ dependency.platform,
2087
+ dependency.requestedScope ?? dependency.resolvedScope,
2088
+ ].join('\0');
1954
2089
  if (closureKeys.has(dependencyKey))
1955
2090
  continue;
1956
2091
  closureKeys.add(dependencyKey);
@@ -1961,16 +2096,28 @@ async function runRebuildCommandInSession(options) {
1961
2096
  plan: {
1962
2097
  ...plan,
1963
2098
  requestedPluginIds: plan.requestedPluginIds.filter((pluginId) => plan.targets.some((target) => target.pluginId === pluginId
1964
- && retainedRootKeys.has(`${target.pluginId}\0${target.platform}`))),
2099
+ && retainedRootKeys.has([target.pluginId, target.platform,
2100
+ target.requestedScope ?? target.resolvedScope].join('\0')))),
1965
2101
  resolvedPluginIds: plan.resolvedPluginIds.filter((pluginId) => plan.targets.some((target) => target.pluginId === pluginId
1966
- && closureKeys.has(`${target.pluginId}\0${target.platform}`))),
1967
- dependencyEdges: plan.dependencyEdges.filter((edge) => closureKeys.has(`${edge.from}\0${edge.platform}`)
1968
- && closureKeys.has(`${edge.to}\0${edge.platform}`)),
1969
- scopeOverrides: plan.scopeOverrides.filter((override) => closureKeys.has(`${override.pluginId}\0${override.platform}`)),
1970
- targets: plan.targets.filter((target) => closureKeys.has(`${target.pluginId}\0${target.platform}`)),
2102
+ && closureKeys.has([target.pluginId, target.platform,
2103
+ target.requestedScope ?? target.resolvedScope].join('\0')))),
2104
+ dependencyEdges: plan.dependencyEdges.filter((edge) => plan.targets.some((target) => target.pluginId === edge.from
2105
+ && target.platform === edge.platform
2106
+ && closureKeys.has([target.pluginId, target.platform,
2107
+ target.requestedScope ?? target.resolvedScope].join('\0')))
2108
+ && plan.targets.some((target) => target.pluginId === edge.to
2109
+ && target.platform === edge.platform
2110
+ && closureKeys.has([target.pluginId, target.platform,
2111
+ target.requestedScope ?? target.resolvedScope].join('\0')))),
2112
+ scopeOverrides: plan.scopeOverrides.filter((override) => plan.targets.some((target) => target.pluginId === override.pluginId
2113
+ && target.platform === override.platform
2114
+ && closureKeys.has([target.pluginId, target.platform,
2115
+ target.requestedScope ?? target.resolvedScope].join('\0')))),
2116
+ targets: plan.targets.filter((target) => closureKeys.has([target.pluginId, target.platform,
2117
+ target.requestedScope ?? target.resolvedScope].join('\0'))),
1971
2118
  },
1972
- selectedRoots: selectedRootsForPlan(plan).flatMap((root) => {
1973
- const platforms = root.platforms.filter((platform) => retainedRootKeys.has(`${root.pluginId}\0${platform}`));
2119
+ selectedRoots: selectedRootsForScope(plan, scope).flatMap((root) => {
2120
+ const platforms = root.platforms.filter((platform) => retainedRootKeys.has([root.pluginId, platform, scope].join('\0')));
1974
2121
  return platforms.length > 0 ? [{ ...root, platforms }] : [];
1975
2122
  }),
1976
2123
  };
@@ -2024,9 +2171,14 @@ async function runRebuildCommandInSession(options) {
2024
2171
  placementCodexConfigs ??= loadPlacementCodexConfigs();
2025
2172
  return placementCodexConfigs;
2026
2173
  };
2027
- for (const [order, { entry, platform }] of executionSelections.entries()) {
2028
- const relation = selectedIds.has(entry.pluginId) ? 'requested' : 'dependency';
2029
- let resolvedScope = options.commandOptions.scope;
2174
+ for (const [order, selection] of executionSelections.entries()) {
2175
+ const { entry, platform } = selection;
2176
+ const relation = options.taskTargets
2177
+ ? selection.requestedScope !== undefined ? 'requested' : 'dependency'
2178
+ : selectedIds.has(entry.pluginId) ? 'requested' : 'dependency';
2179
+ const requestedScope = selection.requestedScope ?? options.commandOptions.scope;
2180
+ const requestedMethod = selection.requestedMethod ?? options.command;
2181
+ let resolvedScope = requestedScope;
2030
2182
  const inspectScope = async (scope) => {
2031
2183
  const marketplaceName = marketplaceNameFor(entry, platform, scope);
2032
2184
  entry.plugin.marketplaceName = marketplaceName;
@@ -2040,7 +2192,7 @@ async function runRebuildCommandInSession(options) {
2040
2192
  platform,
2041
2193
  scope,
2042
2194
  cwd: options.commandOptions.cwd,
2043
- command: options.command,
2195
+ command: requestedMethod,
2044
2196
  catalog: toCatalogResolution(entry.source),
2045
2197
  readOnlyInspectionPlan: true,
2046
2198
  });
@@ -2143,14 +2295,14 @@ async function runRebuildCommandInSession(options) {
2143
2295
  ? inspected.state
2144
2296
  : 'partial';
2145
2297
  };
2146
- const requestedPlacementState = placementState(requestedInspection, options.commandOptions.scope);
2298
+ const requestedPlacementState = placementState(requestedInspection, requestedScope);
2147
2299
  let otherPlacementState = placementState(otherInspection, otherScope);
2148
2300
  if (surface?.classification === 'native'
2149
2301
  && requestedPlacementState === 'present'
2150
2302
  && otherPlacementState === 'present') {
2151
2303
  otherPlacementState = 'absent';
2152
2304
  }
2153
- actualStates.set(`${entry.pluginId}\0${platform}\0${options.commandOptions.scope}`, requestedPlacementState);
2305
+ actualStates.set(`${entry.pluginId}\0${platform}\0${requestedScope}`, requestedPlacementState);
2154
2306
  actualStates.set(`${entry.pluginId}\0${platform}\0${otherScope}`, otherPlacementState);
2155
2307
  const allowedScopes = platform === 'codex'
2156
2308
  ? [
@@ -2159,34 +2311,36 @@ async function runRebuildCommandInSession(options) {
2159
2311
  ]
2160
2312
  : ['project', 'user'];
2161
2313
  if (allowedScopes.length === 0)
2162
- allowedScopes.push(options.commandOptions.scope);
2314
+ allowedScopes.push(requestedScope);
2163
2315
  const hasWiderDesiredRoot = relation === 'dependency'
2164
- && options.commandOptions.scope === 'project'
2316
+ && requestedScope === 'project'
2165
2317
  && intentBeforeByScope.user.roots.some((root) => root.pluginId === entry.pluginId && root.platforms.includes(platform));
2166
- placementNodes.push({
2167
- pluginId: entry.pluginId,
2168
- pluginName: entry.plugin.manifest.name,
2169
- platform,
2170
- relation,
2171
- depth: order,
2172
- allowedScopes,
2173
- ...(relation === 'dependency'
2174
- && options.commandOptions.scope === 'project'
2175
- && (otherPlacementState === 'present' || hasWiderDesiredRoot)
2176
- ? {
2177
- preferredScope: 'user',
2178
- ...(otherPlacementState === 'present'
2179
- ? { preferredScopeReason: 'existingWiderTarget' }
2180
- : {}),
2181
- }
2182
- : {}),
2183
- });
2318
+ if (!placementNodes.some((node) => node.pluginId === entry.pluginId && node.platform === platform)) {
2319
+ placementNodes.push({
2320
+ pluginId: entry.pluginId,
2321
+ pluginName: entry.plugin.manifest.name,
2322
+ platform,
2323
+ relation,
2324
+ depth: order,
2325
+ allowedScopes,
2326
+ ...(relation === 'dependency'
2327
+ && requestedScope === 'project'
2328
+ && (otherPlacementState === 'present' || hasWiderDesiredRoot)
2329
+ ? {
2330
+ preferredScope: 'user',
2331
+ ...(otherPlacementState === 'present'
2332
+ ? { preferredScopeReason: 'existingWiderTarget' }
2333
+ : {}),
2334
+ }
2335
+ : {}),
2336
+ });
2337
+ }
2184
2338
  let state = requestedPlacementState;
2185
2339
  let action = relation === 'requested'
2186
2340
  ? options.command === 'cleanup' || options.command === 'uninstall'
2187
2341
  ? 'keep'
2188
- : options.command === 'reinstall' ? 'reinstall' : 'install'
2189
- : options.command === 'reinstall' ? 'reinstall' : 'install';
2342
+ : requestedMethod === 'reinstall' ? 'reinstall' : 'install'
2343
+ : requestedMethod === 'reinstall' ? 'reinstall' : 'install';
2190
2344
  let errorCode;
2191
2345
  if (dependencyEnabled
2192
2346
  && platform === 'codex'
@@ -2206,7 +2360,7 @@ async function runRebuildCommandInSession(options) {
2206
2360
  && (requestedInspection.state === 'error'
2207
2361
  || (requestedInspection.state === 'partial'
2208
2362
  && platform !== 'codex'
2209
- && options.command !== 'reinstall'
2363
+ && requestedMethod !== 'reinstall'
2210
2364
  && options.command !== 'cleanup'))) {
2211
2365
  action = 'block';
2212
2366
  errorCode = 'dependencyNotReady';
@@ -2219,13 +2373,13 @@ async function runRebuildCommandInSession(options) {
2219
2373
  errorCode = 'dependencyScopeConflict';
2220
2374
  }
2221
2375
  else if (relation === 'dependency'
2222
- && options.commandOptions.scope === 'project'
2376
+ && requestedScope === 'project'
2223
2377
  && otherInspection.state === 'present') {
2224
2378
  resolvedScope = 'user';
2225
2379
  state = 'present';
2226
2380
  }
2227
2381
  else if (relation === 'dependency'
2228
- && options.commandOptions.scope === 'project'
2382
+ && requestedScope === 'project'
2229
2383
  && (otherPlacementState === 'partial' || otherPlacementState === 'error')) {
2230
2384
  resolvedScope = 'user';
2231
2385
  state = otherPlacementState;
@@ -2235,12 +2389,12 @@ async function runRebuildCommandInSession(options) {
2235
2389
  }
2236
2390
  }
2237
2391
  else if (relation === 'dependency'
2238
- && options.commandOptions.scope === 'user'
2392
+ && requestedScope === 'user'
2239
2393
  && otherPlacementState === 'error') {
2240
2394
  action = 'block';
2241
2395
  errorCode = 'dependencyNotReady';
2242
2396
  }
2243
- const resolvedInspection = resolvedScope === options.commandOptions.scope
2397
+ const resolvedInspection = resolvedScope === requestedScope
2244
2398
  ? requestedInspection
2245
2399
  : otherInspection;
2246
2400
  const collisionComponents = relation === 'dependency'
@@ -2251,11 +2405,11 @@ async function runRebuildCommandInSession(options) {
2251
2405
  : requestedInspection.components;
2252
2406
  const hasForeignCollision = collisionComponents
2253
2407
  .some((component) => component.collision !== undefined);
2254
- const hasInvalidExclusiveOwner = options.command === 'reinstall'
2408
+ const hasInvalidExclusiveOwner = requestedMethod === 'reinstall'
2255
2409
  && invalidOwnerComponents.some((component) => (component.component.kind === 'skill-directory'
2256
2410
  || component.component.kind === 'owned-text-file')
2257
2411
  && component.collision?.reason === 'unownedContent');
2258
- state = resolvedScope === options.commandOptions.scope
2412
+ state = resolvedScope === requestedScope
2259
2413
  ? requestedPlacementState
2260
2414
  : otherPlacementState;
2261
2415
  if (dependencyEnabled && hasForeignCollision) {
@@ -2314,12 +2468,12 @@ async function runRebuildCommandInSession(options) {
2314
2468
  errorCode = surface?.errorCode ?? 'codexNativeKindUnsupported';
2315
2469
  }
2316
2470
  if (errorCode)
2317
- blockingTargets.set(`${entry.pluginId}\0${platform}`, errorCode);
2471
+ blockingTargets.set(`${entry.pluginId}\0${platform}\0${requestedScope}`, errorCode);
2318
2472
  targets.push({
2319
2473
  pluginId: entry.pluginId,
2320
2474
  pluginName: entry.plugin.manifest.name,
2321
2475
  platform,
2322
- requestedScope: options.commandOptions.scope,
2476
+ requestedScope,
2323
2477
  resolvedScope,
2324
2478
  relation,
2325
2479
  requiredBy: executionSelections.filter((candidate) => candidate.platform === platform
@@ -2336,9 +2490,21 @@ async function runRebuildCommandInSession(options) {
2336
2490
  const placement = createDependencyPlacementPlan({
2337
2491
  requestedPluginIds: selected.map((entry) => entry.pluginId),
2338
2492
  nodes: placementNodes,
2339
- roots: placementNodes.flatMap((node) => selectedIds.has(node.pluginId)
2340
- ? [{ pluginId: node.pluginId, platform: node.platform, scope: options.commandOptions.scope }]
2341
- : []),
2493
+ roots: options.taskTargets
2494
+ ? executionSelections.flatMap((selection) => selection.requestedScope
2495
+ ? [{
2496
+ pluginId: selection.entry.pluginId,
2497
+ platform: selection.platform,
2498
+ scope: selection.requestedScope,
2499
+ }]
2500
+ : [])
2501
+ : placementNodes.flatMap((node) => selectedIds.has(node.pluginId)
2502
+ ? [{
2503
+ pluginId: node.pluginId,
2504
+ platform: node.platform,
2505
+ scope: options.commandOptions.scope,
2506
+ }]
2507
+ : []),
2342
2508
  dependencyEdges: closureEntries.flatMap((entry) => (entry.plugin.manifest.dependencies ?? []).flatMap((dependency) => {
2343
2509
  const dependencyId = formatPluginId({
2344
2510
  sourceId: entry.source.sourceId,
@@ -2357,13 +2523,30 @@ async function runRebuildCommandInSession(options) {
2357
2523
  ignoredPersistedRootIds: selectedIds,
2358
2524
  }),
2359
2525
  });
2526
+ for (const repairTarget of options.taskRepairTargets ?? []) {
2527
+ if (!placement.targets.some((target) => target.pluginId === repairTarget.pluginId
2528
+ && target.platform === repairTarget.platform)) {
2529
+ throw new Error(`rebuildTaskRepairTargetOutsideClosure: ${repairTarget.pluginId}`
2530
+ + `#${repairTarget.platform}#${repairTarget.scope}`);
2531
+ }
2532
+ }
2360
2533
  return createReconcileOperationPlan({
2361
2534
  placement,
2362
2535
  actual: placement.targets.map((target) => {
2363
- const prior = targets.find((candidate) => candidate.pluginId === target.pluginId && candidate.platform === target.platform);
2536
+ const targetScope = target.requestedScope ?? target.resolvedScope;
2537
+ const inspectedScope = target.requestedScope ?? options.commandOptions.scope;
2538
+ const prior = targets.find((candidate) => candidate.pluginId === target.pluginId
2539
+ && candidate.platform === target.platform
2540
+ && candidate.requestedScope === inspectedScope);
2364
2541
  const state = actualStates.get(`${target.pluginId}\0${target.platform}\0${target.resolvedScope}`) ?? prior.state;
2365
- const errorCode = blockingTargets.get(`${target.pluginId}\0${target.platform}`)
2542
+ const errorCode = blockingTargets.get(`${target.pluginId}\0${target.platform}\0${targetScope}`)
2366
2543
  ?? prior.errorCode;
2544
+ const requestedMethod = options.taskTargets?.find((candidate) => candidate.pluginId === target.pluginId
2545
+ && candidate.platform === target.platform
2546
+ && candidate.scope === targetScope)?.method
2547
+ ?? options.taskRepairTargets?.find((candidate) => candidate.pluginId === target.pluginId
2548
+ && candidate.platform === target.platform)?.method
2549
+ ?? options.command;
2367
2550
  const otherScope = target.resolvedScope === 'user' ? 'project' : 'user';
2368
2551
  const otherState = actualStates.get(`${target.pluginId}\0${target.platform}\0${otherScope}`);
2369
2552
  const movesExistingPlacement = target.relation === 'dependency'
@@ -2382,11 +2565,12 @@ async function runRebuildCommandInSession(options) {
2382
2565
  ? 'move'
2383
2566
  : target.relation === 'dependency' && state === 'present'
2384
2567
  ? 'keep'
2385
- : state === 'partial' || options.command === 'reinstall'
2568
+ : state === 'partial' || requestedMethod === 'reinstall'
2386
2569
  ? 'reinstall'
2387
2570
  : 'install';
2388
2571
  return {
2389
2572
  ...prior,
2573
+ scope: targetScope,
2390
2574
  state,
2391
2575
  action,
2392
2576
  ...(errorCode ? { errorCode } : {}),
@@ -2409,10 +2593,16 @@ async function runRebuildCommandInSession(options) {
2409
2593
  };
2410
2594
  const createCodexPlans = async (plan, activationSupport = 'observed', codexManager) => {
2411
2595
  const codexTargets = plan.targets.filter((target) => target.platform === 'codex');
2412
- const selectedRoots = selectedRootsForPlan(plan);
2413
2596
  const currentScope = options.commandOptions.scope;
2414
2597
  const otherScope = currentScope === 'user' ? 'project' : 'user';
2415
- const reconciledCodexTargets = codexTargets;
2598
+ const reconciledCodexTargetsByPlugin = new Map();
2599
+ for (const target of codexTargets) {
2600
+ if (!reconciledCodexTargetsByPlugin.has(target.pluginId)
2601
+ || target.resolvedScope === 'user') {
2602
+ reconciledCodexTargetsByPlugin.set(target.pluginId, target);
2603
+ }
2604
+ }
2605
+ const reconciledCodexTargets = [...reconciledCodexTargetsByPlugin.values()];
2416
2606
  if (reconciledCodexTargets.length === 0)
2417
2607
  return [];
2418
2608
  const beforeByScope = intentBeforeByScope;
@@ -2420,20 +2610,32 @@ async function runRebuildCommandInSession(options) {
2420
2610
  user: beforeByScope.user,
2421
2611
  project: beforeByScope.project,
2422
2612
  };
2423
- afterByScope[currentScope] = deriveDesiredIntentState({
2424
- command: options.command,
2425
- state: beforeByScope[currentScope],
2426
- selectedRoots,
2427
- plan,
2428
- catalogEntries: intentCatalogEntries,
2429
- });
2430
- if (options.command === 'install') {
2431
- afterByScope[otherScope] = removeSelectedRootPlatformsFromOtherScope({
2432
- state: afterByScope[otherScope],
2613
+ for (const scope of ['user', 'project']) {
2614
+ const selectedRoots = selectedRootsForScope(plan, scope);
2615
+ afterByScope[scope] = deriveDesiredIntentState({
2616
+ command: intentCommand,
2617
+ state: beforeByScope[scope],
2433
2618
  selectedRoots,
2434
- preserveCodexRootIds: preserveOtherCodexScopeRootIds,
2619
+ plan,
2620
+ catalogEntries: intentCatalogEntries,
2435
2621
  });
2436
2622
  }
2623
+ if (options.command === 'install') {
2624
+ for (const scope of ['user', 'project']) {
2625
+ const relatedScope = scope === 'user' ? 'project' : 'user';
2626
+ const protectedRoots = new Set(selectedRootsForScope(plan, relatedScope)
2627
+ .flatMap((root) => root.platforms.map((platform) => `${root.pluginId}\0${platform}`)));
2628
+ const selectedRoots = selectedRootsForScope(plan, scope).flatMap((root) => {
2629
+ const platforms = root.platforms.filter((platform) => !protectedRoots.has(`${root.pluginId}\0${platform}`));
2630
+ return platforms.length > 0 ? [{ ...root, platforms }] : [];
2631
+ });
2632
+ afterByScope[relatedScope] = removeSelectedRootPlatformsFromOtherScope({
2633
+ state: afterByScope[relatedScope],
2634
+ selectedRoots,
2635
+ preserveCodexRootIds: preserveOtherCodexScopeRootIds,
2636
+ });
2637
+ }
2638
+ }
2437
2639
  const desiredScopesForEntry = (entry) => {
2438
2640
  const scopes = new Set();
2439
2641
  for (const intentScope of ['user', 'project']) {
@@ -2452,9 +2654,12 @@ async function runRebuildCommandInSession(options) {
2452
2654
  if (!closure.some((candidate) => candidate.pluginId === entry.pluginId)) {
2453
2655
  continue;
2454
2656
  }
2455
- const currentPlannedScope = intentScope === currentScope
2456
- && selectedRoots.some((selectedRoot) => selectedRoot.pluginId === root.pluginId)
2457
- ? plan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === 'codex')?.resolvedScope
2657
+ const currentPlannedScope = selectedRootsForScope(plan, intentScope)
2658
+ .some((selectedRoot) => selectedRoot.pluginId === root.pluginId)
2659
+ ? plan.targets.find((target) => target.pluginId === entry.pluginId
2660
+ && target.platform === 'codex'
2661
+ && (target.relation === 'dependency'
2662
+ || target.requestedScope === intentScope))?.resolvedScope
2458
2663
  : undefined;
2459
2664
  const override = intent.dependencyScopeOverrides.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === 'codex');
2460
2665
  const reusedUserDependency = intentScope === 'project'
@@ -2754,8 +2959,10 @@ async function runRebuildCommandInSession(options) {
2754
2959
  const preparedTargets = new Map();
2755
2960
  const preparedTargetErrors = new Map();
2756
2961
  const collisionSelections = [];
2757
- for (const { entry, platform } of executionSelections) {
2758
- const planned = plan.targets.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === platform);
2962
+ for (const selection of executionSelections) {
2963
+ const { entry, platform } = selection;
2964
+ const planned = planTargetFor(plan, selection);
2965
+ const key = executionKey(selection, plan);
2759
2966
  if (planned.action === 'block')
2760
2967
  continue;
2761
2968
  const desiredCleanup = platform === 'codex'
@@ -2771,7 +2978,7 @@ async function runRebuildCommandInSession(options) {
2771
2978
  : planned.relation === 'dependency'
2772
2979
  && (planned.action === 'install' || planned.action === 'reinstall')
2773
2980
  ? planned.action
2774
- : options.command;
2981
+ : selection.requestedMethod ?? options.command;
2775
2982
  const plannedNative = platform === 'codex'
2776
2983
  ? codexPlans.flatMap((codexPlan) => codexPlan.sharedNativeActions).find((action) => action.pluginId === entry.pluginId)
2777
2984
  : undefined;
@@ -2846,12 +3053,12 @@ async function runRebuildCommandInSession(options) {
2846
3053
  await integration.driver.preflightMutation?.(target);
2847
3054
  }
2848
3055
  }
2849
- preparedTargets.set(`${entry.pluginId}\0${platform}`, {
3056
+ preparedTargets.set(key, {
2850
3057
  target,
2851
3058
  });
2852
3059
  if (runTargetHook) {
2853
3060
  await rebuildCommandTestHooks?.afterTargetBuilt?.({
2854
- command: options.command,
3061
+ command: selection.requestedMethod ?? options.command,
2855
3062
  identity: target.identity,
2856
3063
  });
2857
3064
  }
@@ -2869,7 +3076,7 @@ async function runRebuildCommandInSession(options) {
2869
3076
  });
2870
3077
  }
2871
3078
  catch (error) {
2872
- preparedTargetErrors.set(`${entry.pluginId}\0${platform}`, error);
3079
+ preparedTargetErrors.set(key, error);
2873
3080
  }
2874
3081
  }
2875
3082
  const collisions = preflightDesiredArtifactCollisions(collisionSelections);
@@ -2878,11 +3085,12 @@ async function runRebuildCommandInSession(options) {
2878
3085
  }
2879
3086
  const configTargets = new Map();
2880
3087
  if (options.command !== 'cleanup' && options.command !== 'uninstall') {
2881
- for (const { entry, platform } of executionSelections) {
2882
- const planned = plan.targets.find((candidate) => candidate.pluginId === entry.pluginId && candidate.platform === platform);
3088
+ for (const selection of executionSelections) {
3089
+ const { entry } = selection;
3090
+ const planned = planTargetFor(plan, selection);
2883
3091
  if (!entry.plugin.manifest.config?.fields
2884
3092
  || planned.action === 'block'
2885
- || !preparedTargets.has(`${entry.pluginId}\0${platform}`))
3093
+ || !preparedTargets.has(executionKey(selection, plan)))
2886
3094
  continue;
2887
3095
  configTargets.set(`${entry.pluginId}\0${planned.resolvedScope}`, {
2888
3096
  entry,
@@ -2920,8 +3128,9 @@ async function runRebuildCommandInSession(options) {
2920
3128
  assertWriteInventoryComplete(stateInventory, exactOwnedTextTargets(authorizationPlan), exactSkillMarkerTargets(authorizationPlan));
2921
3129
  const preview = mergeRebuildCommandResults([]);
2922
3130
  preview.dependencyPlan = authorizationPlan;
2923
- preview.preApplyAuthorization = executionSelections.flatMap(({ entry, platform }) => {
2924
- const planned = authorizationPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
3131
+ preview.preApplyAuthorization = executionSelections.flatMap((selection) => {
3132
+ const { entry, platform } = selection;
3133
+ const planned = planTargetFor(authorizationPlan, selection);
2925
3134
  if (!planned || planned.action === 'block' || !entry.plugin.manifest.scripts?.preApply) {
2926
3135
  return [];
2927
3136
  }
@@ -2970,8 +3179,7 @@ async function runRebuildCommandInSession(options) {
2970
3179
  const preview = mergeRebuildCommandResults([]);
2971
3180
  preview.dependencyPlan = initialPlan;
2972
3181
  preview.operationPlan = createRebuildOperationPlan({
2973
- command: options.command,
2974
- scope: options.commandOptions.scope,
3182
+ command: intentCommand,
2975
3183
  dependencyPlan: initialPlan,
2976
3184
  codexPlans: initialCodexPlans,
2977
3185
  targetActions: previewPreparation.targetActions,
@@ -2993,7 +3201,7 @@ async function runRebuildCommandInSession(options) {
2993
3201
  }
2994
3202
  return preview;
2995
3203
  }
2996
- for (const { entry, platform } of executionSelections.filter((selection) => selectedIds.has(selection.entry.pluginId))) {
3204
+ for (const { entry, platform, requestedScope } of executionSelections.filter((selection) => selectedIds.has(selection.entry.pluginId))) {
2997
3205
  await options.runtime?.reviewPreflight?.({
2998
3206
  command: options.command,
2999
3207
  identity: {
@@ -3003,7 +3211,7 @@ async function runRebuildCommandInSession(options) {
3003
3211
  pluginName: entry.plugin.manifest.name,
3004
3212
  pluginVersion: entry.plugin.manifest.version,
3005
3213
  platform,
3006
- scope: options.commandOptions.scope,
3214
+ scope: requestedScope ?? options.commandOptions.scope,
3007
3215
  cwd: options.commandOptions.cwd,
3008
3216
  },
3009
3217
  configDigest: catalog.configDigest,
@@ -3040,8 +3248,7 @@ async function runRebuildCommandInSession(options) {
3040
3248
  let lockedCodexPlans = provisionalLockedCodexPlans;
3041
3249
  let lockedPreparation = await prepareRenderedTargets(lockedPlan, lockedCodexPlans, true);
3042
3250
  let lockedOperationPlan = createRebuildOperationPlan({
3043
- command: options.command,
3044
- scope: options.commandOptions.scope,
3251
+ command: intentCommand,
3045
3252
  dependencyPlan: lockedPlan,
3046
3253
  codexPlans: lockedCodexPlans,
3047
3254
  targetActions: lockedPreparation.targetActions,
@@ -3068,8 +3275,9 @@ async function runRebuildCommandInSession(options) {
3068
3275
  }
3069
3276
  const operationPreparations = new Map();
3070
3277
  const operationPreparationFailures = new Map();
3071
- const executionCommandFor = (entry, platform) => {
3072
- const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
3278
+ const executionCommandFor = (selection) => {
3279
+ const { entry, platform } = selection;
3280
+ const planned = planTargetFor(lockedPlan, selection);
3073
3281
  const desiredInCurrentScope = platform === 'codex'
3074
3282
  && lockedCodexPlans.some((codexPlan) => codexPlan.desiredTargets.some((target) => target.pluginId === entry.pluginId && target.scopes.includes(planned.resolvedScope)));
3075
3283
  if (platform === 'codex' && options.command === 'sync' && !desiredInCurrentScope) {
@@ -3085,7 +3293,7 @@ async function runRebuildCommandInSession(options) {
3085
3293
  && (planned.action === 'install' || planned.action === 'reinstall')) {
3086
3294
  return planned.action;
3087
3295
  }
3088
- return options.command;
3296
+ return selection.requestedMethod ?? options.command;
3089
3297
  };
3090
3298
  const prepareLockedTargets = async (preparationOptions) => {
3091
3299
  const preparations = new Map();
@@ -3096,8 +3304,9 @@ async function runRebuildCommandInSession(options) {
3096
3304
  scriptTimeoutMs: options.commandOptions.scriptTimeoutMs,
3097
3305
  preparedTargets: [...lockedPreparation.preparedTargets.values()].map((entry) => entry.target),
3098
3306
  });
3099
- for (const { entry, platform } of executionSelections) {
3100
- const key = `${entry.pluginId}\0${platform}`;
3307
+ for (const selection of executionSelections) {
3308
+ const { entry, platform } = selection;
3309
+ const key = executionKey(selection, lockedPlan);
3101
3310
  if (preparationOptions.eligibleKeys
3102
3311
  && !preparationOptions.eligibleKeys.has(key)) {
3103
3312
  states.set(key, 'skipped');
@@ -3108,10 +3317,12 @@ async function runRebuildCommandInSession(options) {
3108
3317
  sourceId: entry.source.sourceId,
3109
3318
  pluginName,
3110
3319
  });
3111
- const state = states.get(`${dependencyPluginId}\0${platform}`);
3320
+ const consumer = planTargetFor(lockedPlan, selection);
3321
+ const dependency = dependencyTargetFor(lockedPlan, consumer, dependencyPluginId);
3322
+ const state = dependency ? states.get(planExecutionKey(dependency)) : undefined;
3112
3323
  return state === 'failed' || state === 'skipped';
3113
3324
  });
3114
- const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
3325
+ const planned = planTargetFor(lockedPlan, selection);
3115
3326
  if (directDependencyFailed || planned.action === 'block') {
3116
3327
  states.set(key, 'skipped');
3117
3328
  continue;
@@ -3135,7 +3346,7 @@ async function runRebuildCommandInSession(options) {
3135
3346
  states.set(key, 'failed');
3136
3347
  continue;
3137
3348
  }
3138
- const executionCommand = executionCommandFor(entry, platform);
3349
+ const executionCommand = executionCommandFor(selection);
3139
3350
  if (executionCommand === 'cleanup' || executionCommand === 'uninstall') {
3140
3351
  states.set(key, 'ready');
3141
3352
  continue;
@@ -3300,8 +3511,7 @@ async function runRebuildCommandInSession(options) {
3300
3511
  lockedPreparation.integration.setNativeMarketplaceEligibleTargets(eligibleTargets);
3301
3512
  }
3302
3513
  lockedOperationPlan = createRebuildOperationPlan({
3303
- command: options.command,
3304
- scope: options.commandOptions.scope,
3514
+ command: intentCommand,
3305
3515
  dependencyPlan: lockedPlan,
3306
3516
  codexPlans: lockedCodexPlans,
3307
3517
  targetActions: lockedPreparation.targetActions,
@@ -3322,16 +3532,36 @@ async function runRebuildCommandInSession(options) {
3322
3532
  if (options.runtime?.awHomeMutationToken) {
3323
3533
  if (lifecycleCommand) {
3324
3534
  const preparedKeys = new Set(operationPreparations.keys());
3325
- const intentSelection = intentPlanForPreparedRoots(lockedPlan, preparedKeys);
3326
- const intentPlan = intentSelection.plan;
3327
- const selectedRoots = intentSelection.selectedRoots;
3328
- if (selectedRoots.length > 0) {
3535
+ for (const scope of ['user', 'project']) {
3536
+ const intentSelection = intentPlanForPreparedRoots(lockedPlan, preparedKeys, scope);
3537
+ if (intentSelection.selectedRoots.length > 0) {
3538
+ await writeDesiredIntentSuperset({
3539
+ command: intentCommand,
3540
+ removeOtherScopeRoots: !options.taskTargets,
3541
+ cwd: options.commandOptions.cwd,
3542
+ scope,
3543
+ selectedRoots: intentSelection.selectedRoots,
3544
+ plan: intentSelection.plan,
3545
+ codexPlan: lockedCodexPlans[0],
3546
+ preserveOtherCodexScopeRootIds,
3547
+ catalogEntries: intentCatalogEntries,
3548
+ env: runtimeEnv,
3549
+ });
3550
+ }
3551
+ }
3552
+ }
3553
+ else {
3554
+ for (const scope of ['user', 'project']) {
3555
+ const selectedRoots = selectedRootsForScope(lockedPlan, scope);
3556
+ if (selectedRoots.length === 0)
3557
+ continue;
3329
3558
  await writeDesiredIntentSuperset({
3330
- command: options.command,
3559
+ command: intentCommand,
3560
+ removeOtherScopeRoots: !options.taskTargets,
3331
3561
  cwd: options.commandOptions.cwd,
3332
- scope: options.commandOptions.scope,
3562
+ scope,
3333
3563
  selectedRoots,
3334
- plan: intentPlan,
3564
+ plan: lockedPlan,
3335
3565
  codexPlan: lockedCodexPlans[0],
3336
3566
  preserveOtherCodexScopeRootIds,
3337
3567
  catalogEntries: intentCatalogEntries,
@@ -3339,20 +3569,6 @@ async function runRebuildCommandInSession(options) {
3339
3569
  });
3340
3570
  }
3341
3571
  }
3342
- else {
3343
- const selectedRoots = selectedRootsForPlan(lockedPlan);
3344
- await writeDesiredIntentSuperset({
3345
- command: options.command,
3346
- cwd: options.commandOptions.cwd,
3347
- scope: options.commandOptions.scope,
3348
- selectedRoots,
3349
- plan: lockedPlan,
3350
- codexPlan: lockedCodexPlans[0],
3351
- preserveOtherCodexScopeRootIds,
3352
- catalogEntries: intentCatalogEntries,
3353
- env: runtimeEnv,
3354
- });
3355
- }
3356
3572
  }
3357
3573
  for (const pluginName of new Set(lockedPlan.targets.flatMap((target) => target.platform === 'claude'
3358
3574
  && (target.action === 'install'
@@ -3465,9 +3681,13 @@ async function runRebuildCommandInSession(options) {
3465
3681
  && target.resolvedScope === consumer.scope
3466
3682
  && (consumer.scope === 'user' || consumer.projectKey === commandProjectKey)));
3467
3683
  if (plannedConsumer) {
3468
- const dependencyExecutionKey = `${dependencyTarget.entry.pluginId}\0${consumer.platform}`;
3684
+ const plannedDependency = planTargetFor(lockedPlan, dependencyTarget);
3685
+ const plannedConsumerTarget = planTargetFor(lockedPlan, plannedConsumer);
3686
+ if (!plannedDependency || !plannedConsumerTarget)
3687
+ continue;
3688
+ const dependencyExecutionKey = planExecutionKey(plannedDependency);
3469
3689
  const required = removalRequiredConsumers.get(dependencyExecutionKey) ?? new Set();
3470
- required.add(`${consumer.entry.pluginId}\0${consumer.platform}`);
3690
+ required.add(planExecutionKey(plannedConsumerTarget));
3471
3691
  removalRequiredConsumers.set(dependencyExecutionKey, required);
3472
3692
  continue;
3473
3693
  }
@@ -3507,14 +3727,20 @@ async function runRebuildCommandInSession(options) {
3507
3727
  }
3508
3728
  const results = [];
3509
3729
  const dependencyStates = new Map();
3510
- for (const { entry, platform } of executionSelections) {
3511
- const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
3730
+ for (const selection of executionSelections) {
3731
+ const { entry, platform } = selection;
3732
+ const planned = planTargetFor(lockedPlan, selection);
3733
+ const key = executionKey(selection, lockedPlan);
3734
+ const requestedMethod = selection.requestedMethod ?? options.command;
3512
3735
  const directDependencyIds = (entry.plugin.manifest.dependencies ?? []).map((pluginName) => formatPluginId({ sourceId: entry.source.sourceId, pluginName }));
3513
3736
  const failedDependency = directDependencyIds.find((pluginId) => {
3514
- const state = dependencyStates.get(`${pluginId}\0${platform}`);
3737
+ const dependency = dependencyTargetFor(lockedPlan, planned, pluginId);
3738
+ const state = dependency
3739
+ ? dependencyStates.get(planExecutionKey(dependency))
3740
+ : undefined;
3515
3741
  return state === 'failed' || state === 'skipped';
3516
3742
  });
3517
- const failedConsumer = [...(removalRequiredConsumers.get(`${entry.pluginId}\0${platform}`) ?? [])]
3743
+ const failedConsumer = [...(removalRequiredConsumers.get(key) ?? [])]
3518
3744
  .find((consumerKey) => {
3519
3745
  const state = dependencyStates.get(consumerKey);
3520
3746
  return state === 'failed' || state === 'skipped';
@@ -3568,7 +3794,7 @@ async function runRebuildCommandInSession(options) {
3568
3794
  : code === 'dependencyNotReady'
3569
3795
  ? `${entry.pluginId} live state is unreadable. Run cleanup, then reinstall.`
3570
3796
  : `${entry.pluginId} dependency preflight is blocked.`}`;
3571
- const result = failedResult(options.command, identity, message);
3797
+ const result = failedResult(requestedMethod, identity, message);
3572
3798
  results.push({
3573
3799
  platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
3574
3800
  targetResults: [{
@@ -3590,10 +3816,10 @@ async function runRebuildCommandInSession(options) {
3590
3816
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3591
3817
  }],
3592
3818
  });
3593
- dependencyStates.set(`${entry.pluginId}\0${platform}`, failedDependency || failedConsumer ? 'skipped' : 'failed');
3819
+ dependencyStates.set(key, failedDependency || failedConsumer ? 'skipped' : 'failed');
3594
3820
  continue;
3595
3821
  }
3596
- const operationPreparationFailure = operationPreparationFailures.get(`${entry.pluginId}\0${platform}`);
3822
+ const operationPreparationFailure = operationPreparationFailures.get(key);
3597
3823
  if (operationPreparationFailure) {
3598
3824
  const targetResult = toTargetResult(operationPreparationFailure);
3599
3825
  results.push({
@@ -3611,16 +3837,16 @@ async function runRebuildCommandInSession(options) {
3611
3837
  scope: planned.resolvedScope,
3612
3838
  }],
3613
3839
  });
3614
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
3840
+ dependencyStates.set(key, 'failed');
3615
3841
  continue;
3616
3842
  }
3617
3843
  if (reconcileActivationOnly) {
3618
- const preparedError = preparedTargetErrors.get(`${entry.pluginId}\0${platform}`);
3844
+ const preparedError = preparedTargetErrors.get(key);
3619
3845
  if (preparedError) {
3620
3846
  const message = preparedError instanceof Error
3621
3847
  ? preparedError.message
3622
3848
  : String(preparedError);
3623
- const result = failedResult(options.command, identity, message);
3849
+ const result = failedResult(requestedMethod, identity, message);
3624
3850
  results.push({
3625
3851
  platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
3626
3852
  targetResults: [{
@@ -3636,12 +3862,12 @@ async function runRebuildCommandInSession(options) {
3636
3862
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3637
3863
  }],
3638
3864
  });
3639
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
3865
+ dependencyStates.set(key, 'failed');
3640
3866
  continue;
3641
3867
  }
3642
- const actions = operationPreparations.get(`${entry.pluginId}\0${platform}`)?.actions
3868
+ const actions = operationPreparations.get(key)?.actions
3643
3869
  ?? [];
3644
- const preparedTarget = preparedTargets.get(`${entry.pluginId}\0${platform}`)?.target;
3870
+ const preparedTarget = preparedTargets.get(key)?.target;
3645
3871
  let result;
3646
3872
  if (preparedTarget) {
3647
3873
  const nativeSkillKeys = new Set((entry.plugin.manifest.artifacts?.targets ?? [])
@@ -3655,7 +3881,7 @@ async function runRebuildCommandInSession(options) {
3655
3881
  && (!marketplaceRoot || path.relative(marketplaceRoot, component.path).startsWith('..')));
3656
3882
  if (legacyNativeSkills.length > 0) {
3657
3883
  result = await executeRebuildComponentCleanup({
3658
- command: options.command,
3884
+ command: requestedMethod,
3659
3885
  target: preparedTarget,
3660
3886
  driver: lockedPreparation.integration.driver,
3661
3887
  components: legacyNativeSkills,
@@ -3671,7 +3897,7 @@ async function runRebuildCommandInSession(options) {
3671
3897
  }
3672
3898
  }
3673
3899
  result ??= {
3674
- status: 'succeeded', command: options.command, target: identity,
3900
+ status: 'succeeded', command: requestedMethod, target: identity,
3675
3901
  actions,
3676
3902
  cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
3677
3903
  };
@@ -3687,7 +3913,7 @@ async function runRebuildCommandInSession(options) {
3687
3913
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3688
3914
  }],
3689
3915
  });
3690
- dependencyStates.set(`${entry.pluginId}\0${platform}`, result.status === 'succeeded' ? 'completed' : 'failed');
3916
+ dependencyStates.set(key, result.status === 'succeeded' ? 'completed' : 'failed');
3691
3917
  continue;
3692
3918
  }
3693
3919
  if (platform === 'codex'
@@ -3707,19 +3933,19 @@ async function runRebuildCommandInSession(options) {
3707
3933
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3708
3934
  }],
3709
3935
  });
3710
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'kept');
3936
+ dependencyStates.set(key, 'kept');
3711
3937
  continue;
3712
3938
  }
3713
3939
  if (planned.action === 'keep'
3714
3940
  && !reconcileObsolete
3715
3941
  && options.command !== 'cleanup'
3716
3942
  && options.command !== 'uninstall') {
3717
- const preparedError = preparedTargetErrors.get(`${entry.pluginId}\0${platform}`);
3943
+ const preparedError = preparedTargetErrors.get(key);
3718
3944
  if (preparedError) {
3719
3945
  const message = preparedError instanceof Error
3720
3946
  ? preparedError.message
3721
3947
  : String(preparedError);
3722
- const result = failedResult(options.command, identity, message);
3948
+ const result = failedResult(requestedMethod, identity, message);
3723
3949
  results.push({
3724
3950
  platforms: [platform], plugins: [], warnings: [], rebuildResults: [result],
3725
3951
  targetResults: [{
@@ -3735,13 +3961,13 @@ async function runRebuildCommandInSession(options) {
3735
3961
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3736
3962
  }],
3737
3963
  });
3738
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
3964
+ dependencyStates.set(key, 'failed');
3739
3965
  continue;
3740
3966
  }
3741
- const actions = operationPreparations.get(`${entry.pluginId}\0${platform}`)?.actions
3967
+ const actions = operationPreparations.get(key)?.actions
3742
3968
  ?? [];
3743
3969
  const result = {
3744
- status: 'succeeded', command: options.command, target: identity,
3970
+ status: 'succeeded', command: requestedMethod, target: identity,
3745
3971
  actions,
3746
3972
  cleanupAttempted: false, removed: [], absent: [], residual: [], quarantined: [], failures: [],
3747
3973
  };
@@ -3754,7 +3980,7 @@ async function runRebuildCommandInSession(options) {
3754
3980
  sourceId: identity.sourceId, platform, scope: planned.resolvedScope,
3755
3981
  }],
3756
3982
  });
3757
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'kept');
3983
+ dependencyStates.set(key, 'kept');
3758
3984
  continue;
3759
3985
  }
3760
3986
  const marketplaceName = marketplaceNameFor(entry, platform, planned.resolvedScope);
@@ -3779,7 +4005,7 @@ async function runRebuildCommandInSession(options) {
3779
4005
  ? planned.state === 'partial' ? 'reinstall' : 'install'
3780
4006
  : planned.relation === 'dependency'
3781
4007
  ? planned.action
3782
- : options.command,
4008
+ : requestedMethod,
3783
4009
  pluginNames: [entry.plugin.manifest.name],
3784
4010
  all: false,
3785
4011
  commandOptions: { ...options.commandOptions, platform, scope: planned.resolvedScope },
@@ -3814,10 +4040,10 @@ async function runRebuildCommandInSession(options) {
3814
4040
  execution: {
3815
4041
  reviewCompleted: true,
3816
4042
  targetHookCompleted: true,
3817
- prepared: preparedTargets.get(`${entry.pluginId}\0${platform}`),
3818
- operationPreparation: operationPreparations.get(`${entry.pluginId}\0${platform}`),
4043
+ prepared: preparedTargets.get(key),
4044
+ operationPreparation: operationPreparations.get(key),
3819
4045
  integration: lockedPreparation.integration,
3820
- preparationError: preparedTargetErrors.get(`${entry.pluginId}\0${platform}`),
4046
+ preparationError: preparedTargetErrors.get(key),
3821
4047
  skipPreApply: reconcileDesiredCleanup,
3822
4048
  },
3823
4049
  });
@@ -3828,7 +4054,7 @@ async function runRebuildCommandInSession(options) {
3828
4054
  result.action = 'cleanup';
3829
4055
  }
3830
4056
  results.push(executionResult);
3831
- dependencyStates.set(`${entry.pluginId}\0${platform}`, executionResult.overallState === 'succeeded' ? 'completed' : 'failed');
4057
+ dependencyStates.set(key, executionResult.overallState === 'succeeded' ? 'completed' : 'failed');
3832
4058
  }
3833
4059
  const failCompletedCodexTarget = (options_) => {
3834
4060
  const resultIndex = results.findIndex((candidate) => candidate.targetResults.some((target) => target.target.pluginId === options_.identity.pluginId
@@ -3874,20 +4100,26 @@ async function runRebuildCommandInSession(options) {
3874
4100
  : target),
3875
4101
  overallState: 'failed',
3876
4102
  };
3877
- dependencyStates.set(`${options_.identity.pluginId}\0codex`, 'failed');
4103
+ dependencyStates.set([
4104
+ options_.identity.pluginId,
4105
+ 'codex',
4106
+ options_.identity.scope,
4107
+ ].join('\0'), 'failed');
3878
4108
  return true;
3879
4109
  };
3880
4110
  const activationMutationPluginIds = new Set(lockedCodexPlans.flatMap((plan) => [
3881
4111
  ...plan.activationActions.user,
3882
4112
  ...plan.activationActions.project,
3883
4113
  ].flatMap((action) => action.action === 'none' ? [] : [action.pluginId])));
3884
- for (const { entry, platform } of executionSelections) {
4114
+ for (const selection of executionSelections) {
4115
+ const { entry, platform } = selection;
4116
+ const key = executionKey(selection, lockedPlan);
3885
4117
  if (platform !== 'codex'
3886
4118
  || !activationMutationPluginIds.has(entry.pluginId)
3887
- || (dependencyStates.get(`${entry.pluginId}\0${platform}`) !== 'completed'
3888
- && dependencyStates.get(`${entry.pluginId}\0${platform}`) !== 'kept'))
4119
+ || (dependencyStates.get(key) !== 'completed'
4120
+ && dependencyStates.get(key) !== 'kept'))
3889
4121
  continue;
3890
- const prepared = preparedTargets.get(`${entry.pluginId}\0${platform}`)?.target;
4122
+ const prepared = preparedTargets.get(key)?.target;
3891
4123
  if (!prepared)
3892
4124
  continue;
3893
4125
  try {
@@ -3898,12 +4130,22 @@ async function runRebuildCommandInSession(options) {
3898
4130
  failCompletedCodexTarget({ identity: prepared.identity, message, phase: 'native.write' });
3899
4131
  }
3900
4132
  }
3901
- const successfulCodexPluginIds = new Set(executionSelections.flatMap(({ entry, platform }) => platform === 'codex'
3902
- && (dependencyStates.get(`${entry.pluginId}\0${platform}`) === 'completed'
3903
- || dependencyStates.get(`${entry.pluginId}\0${platform}`) === 'kept')
3904
- ? [entry.pluginId]
4133
+ const successfulCodexTargetScopes = new Set(executionSelections.flatMap((selection) => selection.platform === 'codex'
4134
+ && (dependencyStates.get(executionKey(selection, lockedPlan)) === 'completed'
4135
+ || dependencyStates.get(executionKey(selection, lockedPlan)) === 'kept')
4136
+ ? [`${selection.entry.pluginId}\0${planTargetFor(lockedPlan, selection).resolvedScope}`]
3905
4137
  : []));
3906
- const activationPlans = selectCodexActivationPlans(lockedCodexPlans, successfulCodexPluginIds);
4138
+ const [activationUserBefore, activationProjectBefore] = await Promise.all([
4139
+ readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'user', runtimeEnv).configTomlPath),
4140
+ readCodexConfigFile(resolveCodexPaths(options.commandOptions.cwd, 'project', runtimeEnv).configTomlPath),
4141
+ ]);
4142
+ const preservedActivations = {
4143
+ user: new Map(Object.entries(activationUserBefore.snapshot.plugins)
4144
+ .map(([pluginId, activation]) => [pluginId, activation.enabled === true])),
4145
+ project: new Map(Object.entries(activationProjectBefore.snapshot.plugins)
4146
+ .map(([pluginId, activation]) => [pluginId, activation.enabled === true])),
4147
+ };
4148
+ const activationPlans = selectCodexActivationPlans(lockedCodexPlans, successfulCodexTargetScopes, preservedActivations);
3907
4149
  if (activationPlans.length > 0
3908
4150
  || lockedPreparation.integration.driver.completePostchecks) {
3909
4151
  let activationError;
@@ -3932,23 +4174,25 @@ async function runRebuildCommandInSession(options) {
3932
4174
  catch (error) {
3933
4175
  activationError ??= error;
3934
4176
  }
3935
- const failedCodexPluginIds = new Set(executionSelections.flatMap(({ entry, platform }) => platform === 'codex'
3936
- && (dependencyStates.get(`${entry.pluginId}\0${platform}`) === 'failed'
3937
- || dependencyStates.get(`${entry.pluginId}\0${platform}`) === 'skipped')
3938
- ? [entry.pluginId]
4177
+ const failedCodexTargetScopes = new Set(executionSelections.flatMap((selection) => selection.platform === 'codex'
4178
+ && (dependencyStates.get(executionKey(selection, lockedPlan)) === 'failed'
4179
+ || dependencyStates.get(executionKey(selection, lockedPlan)) === 'skipped')
4180
+ ? [`${selection.entry.pluginId}\0${planTargetFor(lockedPlan, selection).resolvedScope}`]
3939
4181
  : []));
3940
4182
  for (const failure of await lockedPreparation.integration.driver.completePostchecks?.() ?? []) {
3941
4183
  const message = failure.error instanceof Error
3942
4184
  ? failure.error.message
3943
4185
  : String(failure.error);
3944
4186
  if (failCompletedCodexTarget({ identity: failure.target.identity, message })) {
3945
- failedCodexPluginIds.add(failure.target.identity.pluginId ?? failure.target.identity.pluginName);
4187
+ const failedPluginId = failure.target.identity.pluginId
4188
+ ?? failure.target.identity.pluginName;
4189
+ failedCodexTargetScopes.add(`${failedPluginId}\0${failure.target.identity.scope}`);
3946
4190
  }
3947
4191
  }
3948
4192
  if (!activationError && activationPlans.length > 0) {
3949
- const postcheckPluginIds = new Set([...successfulCodexPluginIds]
3950
- .filter((pluginId) => !failedCodexPluginIds.has(pluginId)));
3951
- const postcheckPlans = selectCodexActivationPlans(activationPlans, postcheckPluginIds);
4193
+ const postcheckTargetScopes = new Set([...successfulCodexTargetScopes]
4194
+ .filter((key) => !failedCodexTargetScopes.has(key)));
4195
+ const postcheckPlans = selectCodexActivationPlans(activationPlans, postcheckTargetScopes, preservedActivations);
3952
4196
  try {
3953
4197
  await postcheckCodexReconcileActivations({
3954
4198
  plans: postcheckPlans,
@@ -3965,20 +4209,20 @@ async function runRebuildCommandInSession(options) {
3965
4209
  }
3966
4210
  }
3967
4211
  if (activationError) {
3968
- const failedActivationPluginIds = failedCodexActivationPluginIds({
4212
+ const failedActivationTargetScopes = failedCodexActivationTargetScopes({
3969
4213
  error: activationError,
3970
4214
  plans: activationPlans,
3971
- successfulPluginIds: successfulCodexPluginIds,
4215
+ successfulTargetScopes: successfulCodexTargetScopes,
3972
4216
  });
3973
4217
  const message = activationError instanceof Error
3974
4218
  ? activationError.message
3975
4219
  : String(activationError);
3976
4220
  const failedSelections = executionSelections.filter((candidate) => candidate.platform === 'codex'
3977
- && failedActivationPluginIds.has(candidate.entry.pluginId));
4221
+ && failedActivationTargetScopes.has(`${candidate.entry.pluginId}\0${planTargetFor(lockedPlan, candidate).resolvedScope}`));
3978
4222
  if (failedSelections.length === 0)
3979
4223
  throw activationError;
3980
4224
  for (const selection of failedSelections) {
3981
- const planned = lockedPlan.targets.find((target) => target.pluginId === selection.entry.pluginId && target.platform === 'codex');
4225
+ const planned = planTargetFor(lockedPlan, selection);
3982
4226
  const identity = {
3983
4227
  sourceId: selection.entry.source.sourceId,
3984
4228
  sourceIdentity: selection.entry.source.sourceIdentity,
@@ -3991,24 +4235,30 @@ async function runRebuildCommandInSession(options) {
3991
4235
  };
3992
4236
  if (!failCompletedCodexTarget({ identity, message }))
3993
4237
  throw activationError;
3994
- failedCodexPluginIds.add(selection.entry.pluginId);
4238
+ failedCodexTargetScopes.add(`${selection.entry.pluginId}\0${planned.resolvedScope}`);
3995
4239
  }
3996
4240
  }
3997
4241
  let propagated = true;
3998
4242
  while (propagated) {
3999
4243
  propagated = false;
4000
4244
  for (const selection of executionSelections.filter((candidate) => candidate.platform === 'codex')) {
4001
- if (failedCodexPluginIds.has(selection.entry.pluginId))
4245
+ const planned = planTargetFor(lockedPlan, selection);
4246
+ const targetScopeKey = `${selection.entry.pluginId}\0${planned.resolvedScope}`;
4247
+ if (failedCodexTargetScopes.has(targetScopeKey))
4002
4248
  continue;
4003
4249
  const failedDependency = (selection.entry.plugin.manifest.dependencies ?? [])
4004
4250
  .map((pluginName) => formatPluginId({
4005
4251
  sourceId: selection.entry.source.sourceId,
4006
4252
  pluginName,
4007
4253
  }))
4008
- .find((pluginId) => failedCodexPluginIds.has(pluginId));
4254
+ .find((pluginId) => {
4255
+ const dependency = dependencyTargetFor(lockedPlan, planned, pluginId);
4256
+ return dependency
4257
+ ? failedCodexTargetScopes.has(`${pluginId}\0${dependency.requestedScope ?? dependency.resolvedScope}`)
4258
+ : false;
4259
+ });
4009
4260
  if (!failedDependency)
4010
4261
  continue;
4011
- const planned = lockedPlan.targets.find((target) => target.pluginId === selection.entry.pluginId && target.platform === 'codex');
4012
4262
  const identity = {
4013
4263
  sourceId: selection.entry.source.sourceId,
4014
4264
  sourceIdentity: selection.entry.source.sourceIdentity,
@@ -4027,8 +4277,8 @@ async function runRebuildCommandInSession(options) {
4027
4277
  `run aw sync --all --platform codex --scope ${planned.resolvedScope}`,
4028
4278
  ],
4029
4279
  })) {
4030
- failedCodexPluginIds.add(selection.entry.pluginId);
4031
- dependencyStates.set(`${selection.entry.pluginId}\0codex`, 'failed');
4280
+ failedCodexTargetScopes.add(targetScopeKey);
4281
+ dependencyStates.set(executionKey(selection, lockedPlan), 'failed');
4032
4282
  propagated = true;
4033
4283
  }
4034
4284
  }
@@ -4054,9 +4304,11 @@ async function runRebuildCommandInSession(options) {
4054
4304
  });
4055
4305
  }
4056
4306
  if (options.command === 'install' || options.command === 'sync') {
4057
- for (const { entry, platform } of executionSelections) {
4058
- const planned = lockedPlan.targets.find((target) => target.pluginId === entry.pluginId && target.platform === platform);
4059
- if (dependencyStates.get(`${entry.pluginId}\0${platform}`) !== 'completed')
4307
+ for (const selection of executionSelections) {
4308
+ const { entry, platform } = selection;
4309
+ const planned = planTargetFor(lockedPlan, selection);
4310
+ const key = executionKey(selection, lockedPlan);
4311
+ if (dependencyStates.get(key) !== 'completed')
4060
4312
  continue;
4061
4313
  const previousScope = planned.resolvedScope === 'user' ? 'project' : 'user';
4062
4314
  const removesPreviousScope = planned.resolvedScope === 'user'
@@ -4100,7 +4352,7 @@ async function runRebuildCommandInSession(options) {
4100
4352
  })),
4101
4353
  });
4102
4354
  if (migration.overallState !== 'succeeded') {
4103
- dependencyStates.set(`${entry.pluginId}\0${platform}`, 'failed');
4355
+ dependencyStates.set(key, 'failed');
4104
4356
  }
4105
4357
  }
4106
4358
  }
@@ -4168,4 +4420,64 @@ export async function runRebuildCommand(options) {
4168
4420
  await session.dispose();
4169
4421
  }
4170
4422
  }
4423
+ export async function runRebuildTaskCommand(targets, options, runtime = {}) {
4424
+ if (targets.length === 0) {
4425
+ throw new Error('rebuildTaskTargetsRequired: At least one rebuild task target is required.');
4426
+ }
4427
+ const normalizeTargets = (values) => values.map((target) => {
4428
+ const selector = parsePluginId(target.pluginId);
4429
+ if (!selector.sourceId) {
4430
+ throw new Error(`rebuildTaskPluginIdInvalid: "${target.pluginId}" must include a source.`);
4431
+ }
4432
+ return { ...target, pluginId: formatPluginId({
4433
+ sourceId: selector.sourceId,
4434
+ pluginName: selector.pluginName,
4435
+ }) };
4436
+ });
4437
+ const normalizedRoots = normalizeTargets(targets);
4438
+ const normalizedRepairs = normalizeTargets(options.repairTargets ?? []);
4439
+ const sourceIds = new Set([...normalizedRoots, ...normalizedRepairs]
4440
+ .map((target) => parsePluginId(target.pluginId).sourceId));
4441
+ if (sourceIds.size !== 1) {
4442
+ throw new Error('rebuildTaskSourceConflict: All rebuild task targets must use one source.');
4443
+ }
4444
+ const methodsByKey = new Map();
4445
+ const repairMethodsByTarget = new Map();
4446
+ for (const target of [...normalizedRoots, ...normalizedRepairs]) {
4447
+ const key = [target.pluginId, target.platform, target.scope].join('\0');
4448
+ const current = methodsByKey.get(key);
4449
+ if (current && current !== target.method) {
4450
+ throw new Error(`rebuildTaskMethodConflict: ${target.pluginId}#${target.platform}#${target.scope}`);
4451
+ }
4452
+ methodsByKey.set(key, target.method);
4453
+ }
4454
+ for (const target of normalizedRepairs) {
4455
+ const key = [target.pluginId, target.platform].join('\0');
4456
+ const current = repairMethodsByTarget.get(key);
4457
+ if (current && current !== target.method) {
4458
+ throw new Error(`rebuildTaskMethodConflict: ${target.pluginId}#${target.platform}`);
4459
+ }
4460
+ repairMethodsByTarget.set(key, target.method);
4461
+ }
4462
+ const deduplicate = (values) => [...new Map(values.map((target) => [
4463
+ [target.pluginId, target.platform, target.scope].join('\0'),
4464
+ target,
4465
+ ])).values()];
4466
+ const normalized = deduplicate(normalizedRoots);
4467
+ const repairTargets = deduplicate(normalizedRepairs);
4468
+ const commandOptions = { ...options };
4469
+ delete commandOptions.repairTargets;
4470
+ return runRebuildCommand({
4471
+ command: 'sync',
4472
+ pluginNames: [...new Set(normalized.map((target) => target.pluginId))],
4473
+ commandOptions: {
4474
+ ...commandOptions,
4475
+ platform: 'both',
4476
+ scope: normalized[0].scope,
4477
+ },
4478
+ runtime,
4479
+ taskTargets: normalized,
4480
+ taskRepairTargets: repairTargets,
4481
+ });
4482
+ }
4171
4483
  //# sourceMappingURL=rebuild-command.js.map