@lesliechan721/aw 0.1.0-beta.7 → 0.1.0-beta.8

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 (237) hide show
  1. package/dist/authoring/scripts/init-plugin.mjs +4 -33
  2. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  3. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  4. package/dist/authoring/scripts/plugin-status.mjs +8 -11
  5. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  6. package/dist/authoring/scripts/remove-plugin.mjs +1 -0
  7. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  8. package/dist/authoring/scripts/validate-plugin.mjs +104 -54
  9. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  10. package/dist/scripts/codex-eval-app-server.d.ts +2 -0
  11. package/dist/scripts/codex-eval-app-server.js +107 -1
  12. package/dist/scripts/codex-eval-app-server.js.map +1 -1
  13. package/dist/scripts/eval-plugin-skills.js +5 -2
  14. package/dist/scripts/eval-plugin-skills.js.map +1 -1
  15. package/dist/scripts/plugin-eval-contract.d.ts +5 -1
  16. package/dist/scripts/plugin-eval-contract.js +54 -4
  17. package/dist/scripts/plugin-eval-contract.js.map +1 -1
  18. package/dist/scripts/plugin-staging.d.ts +6 -7
  19. package/dist/scripts/plugin-staging.js +11 -23
  20. package/dist/scripts/plugin-staging.js.map +1 -1
  21. package/dist/scripts/test-plugin-scripts.js +2 -33
  22. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  23. package/dist/src/adapters/helpers.js +2 -2
  24. package/dist/src/adapters/helpers.js.map +1 -1
  25. package/dist/src/authoring/workspace-commands.d.ts +17 -2
  26. package/dist/src/authoring/workspace-commands.js +21 -4
  27. package/dist/src/authoring/workspace-commands.js.map +1 -1
  28. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  29. package/dist/src/commands/aw-capabilities.js +259 -0
  30. package/dist/src/commands/aw-capabilities.js.map +1 -0
  31. package/dist/src/commands/catalog-context.d.ts +52 -0
  32. package/dist/src/commands/catalog-context.js +156 -0
  33. package/dist/src/commands/catalog-context.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +112 -0
  35. package/dist/src/commands/catalog-dev-source.js +481 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +26 -0
  38. package/dist/src/commands/catalog-generation.js +14 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
  41. package/dist/src/commands/catalog-publication-inspection.js +4 -5
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-target.d.ts +17 -0
  44. package/dist/src/commands/catalog-publication-target.js +60 -0
  45. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  46. package/dist/src/commands/catalog-publish.d.ts +8 -1
  47. package/dist/src/commands/catalog-publish.js +25 -2
  48. package/dist/src/commands/catalog-publish.js.map +1 -1
  49. package/dist/src/commands/catalog-source.d.ts +23 -1
  50. package/dist/src/commands/catalog-source.js +126 -40
  51. package/dist/src/commands/catalog-source.js.map +1 -1
  52. package/dist/src/commands/catalog-status.d.ts +18 -2
  53. package/dist/src/commands/catalog-status.js +122 -18
  54. package/dist/src/commands/catalog-status.js.map +1 -1
  55. package/dist/src/commands/init.js +1 -1
  56. package/dist/src/commands/init.js.map +1 -1
  57. package/dist/src/commands/offline-cleanup-evidence.d.ts +23 -0
  58. package/dist/src/commands/offline-cleanup-evidence.js +38 -0
  59. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  60. package/dist/src/commands/options.d.ts +1 -0
  61. package/dist/src/commands/options.js +1 -0
  62. package/dist/src/commands/options.js.map +1 -1
  63. package/dist/src/commands/plugin-capabilities.js +1 -1
  64. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  65. package/dist/src/commands/rebuild-command.d.ts +19 -7
  66. package/dist/src/commands/rebuild-command.js +415 -205
  67. package/dist/src/commands/rebuild-command.js.map +1 -1
  68. package/dist/src/commands/rebuild-integration.d.ts +10 -3
  69. package/dist/src/commands/rebuild-integration.js +71 -76
  70. package/dist/src/commands/rebuild-integration.js.map +1 -1
  71. package/dist/src/commands/rebuild-operations.d.ts +0 -8
  72. package/dist/src/commands/rebuild-operations.js +17 -115
  73. package/dist/src/commands/rebuild-operations.js.map +1 -1
  74. package/dist/src/commands/runtime.js +21 -34
  75. package/dist/src/commands/runtime.js.map +1 -1
  76. package/dist/src/commands/{status-v4.d.ts → status-v5.d.ts} +67 -21
  77. package/dist/src/commands/{status-v4.js → status-v5.js} +260 -30
  78. package/dist/src/commands/status-v5.js.map +1 -0
  79. package/dist/src/commands/wizard.d.ts +2 -2
  80. package/dist/src/commands/wizard.js +2 -2
  81. package/dist/src/commands/wizard.js.map +1 -1
  82. package/dist/src/commands/write-commands.js +73 -17
  83. package/dist/src/commands/write-commands.js.map +1 -1
  84. package/dist/src/constants.d.ts +6 -1
  85. package/dist/src/constants.js +6 -1
  86. package/dist/src/constants.js.map +1 -1
  87. package/dist/src/generators/local-plugin.js +57 -3
  88. package/dist/src/generators/local-plugin.js.map +1 -1
  89. package/dist/src/index.d.ts +4 -1
  90. package/dist/src/index.js +562 -85
  91. package/dist/src/index.js.map +1 -1
  92. package/dist/src/output/model.d.ts +1 -34
  93. package/dist/src/output/model.js +1 -144
  94. package/dist/src/output/model.js.map +1 -1
  95. package/dist/src/output/rebuild-progress.js +0 -3
  96. package/dist/src/output/rebuild-progress.js.map +1 -1
  97. package/dist/src/portable/commands.d.ts +4 -0
  98. package/dist/src/portable/commands.js +61 -4
  99. package/dist/src/portable/commands.js.map +1 -1
  100. package/dist/src/portable/materializer.d.ts +3 -0
  101. package/dist/src/portable/materializer.js +61 -10
  102. package/dist/src/portable/materializer.js.map +1 -1
  103. package/dist/src/tui/catalog-generation.d.ts +56 -0
  104. package/dist/src/tui/catalog-generation.js +244 -0
  105. package/dist/src/tui/catalog-generation.js.map +1 -0
  106. package/dist/src/tui/console-app-v4.d.ts +1 -1
  107. package/dist/src/tui/console-app-v4.js +446 -126
  108. package/dist/src/tui/console-app-v4.js.map +1 -1
  109. package/dist/src/tui/controller-runtime.d.ts +8 -2
  110. package/dist/src/tui/controller-runtime.js +606 -92
  111. package/dist/src/tui/controller-runtime.js.map +1 -1
  112. package/dist/src/tui/controller.d.ts +71 -6
  113. package/dist/src/tui/controller.js +377 -83
  114. package/dist/src/tui/controller.js.map +1 -1
  115. package/dist/src/tui/index.d.ts +1 -0
  116. package/dist/src/tui/index.js +1 -0
  117. package/dist/src/tui/index.js.map +1 -1
  118. package/dist/src/tui/input-display.d.ts +8 -0
  119. package/dist/src/tui/input-display.js +36 -0
  120. package/dist/src/tui/input-display.js.map +1 -0
  121. package/dist/src/tui/model.d.ts +27 -7
  122. package/dist/src/tui/model.js +273 -65
  123. package/dist/src/tui/model.js.map +1 -1
  124. package/dist/src/tui/router.d.ts +26 -0
  125. package/dist/src/tui/router.js.map +1 -1
  126. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  127. package/dist/src/tui/source-ref-presets.js +11 -0
  128. package/dist/src/tui/source-ref-presets.js.map +1 -0
  129. package/dist/src/tui/views.js +31 -3
  130. package/dist/src/tui/views.js.map +1 -1
  131. package/dist/src/types.d.ts +90 -6
  132. package/dist/src/types.js.map +1 -1
  133. package/dist/src/utils/agent-version.d.ts +3 -0
  134. package/dist/src/utils/agent-version.js +13 -5
  135. package/dist/src/utils/agent-version.js.map +1 -1
  136. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  137. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  138. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  139. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  140. package/dist/src/utils/bundle-integrity.js +0 -25
  141. package/dist/src/utils/bundle-integrity.js.map +1 -1
  142. package/dist/src/utils/bundle-layout.js +8 -6
  143. package/dist/src/utils/bundle-layout.js.map +1 -1
  144. package/dist/src/utils/catalog-digests.js +13 -2
  145. package/dist/src/utils/catalog-digests.js.map +1 -1
  146. package/dist/src/utils/catalog-generation-error.d.ts +12 -0
  147. package/dist/src/utils/catalog-generation-error.js +16 -0
  148. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  149. package/dist/src/utils/catalog-generation-resolver.d.ts +13 -0
  150. package/dist/src/utils/catalog-generation-resolver.js +129 -0
  151. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  152. package/dist/src/utils/catalog-generation-service.d.ts +52 -0
  153. package/dist/src/utils/catalog-generation-service.js +620 -0
  154. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  155. package/dist/src/utils/catalog-generation-state.d.ts +13 -0
  156. package/dist/src/utils/catalog-generation-state.js +201 -0
  157. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  158. package/dist/src/utils/catalog-generation-store.d.ts +44 -0
  159. package/dist/src/utils/catalog-generation-store.js +387 -0
  160. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  161. package/dist/src/utils/catalog-manifest-v5.d.ts +5 -0
  162. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v5.js} +39 -35
  163. package/dist/src/utils/{catalog-manifest-v4.js.map → catalog-manifest-v5.js.map} +1 -1
  164. package/dist/src/utils/catalog-operation-context.d.ts +28 -0
  165. package/dist/src/utils/catalog-operation-context.js +242 -0
  166. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  167. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  168. package/dist/src/utils/catalog-public-error.js +20 -4
  169. package/dist/src/utils/catalog-public-error.js.map +1 -1
  170. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  171. package/dist/src/utils/catalog-snapshot.js +11 -3
  172. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  173. package/dist/src/utils/catalog-source-cache.d.ts +1 -1
  174. package/dist/src/utils/catalog-source-cache.js +37 -12
  175. package/dist/src/utils/catalog-source-cache.js.map +1 -1
  176. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  177. package/dist/src/utils/catalog-source-identity.js +21 -2
  178. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  179. package/dist/src/utils/catalog-sources.d.ts +1 -0
  180. package/dist/src/utils/catalog-sources.js +30 -11
  181. package/dist/src/utils/catalog-sources.js.map +1 -1
  182. package/dist/src/utils/codex-config.js +4 -4
  183. package/dist/src/utils/codex-config.js.map +1 -1
  184. package/dist/src/utils/codex-manager.d.ts +33 -0
  185. package/dist/src/utils/codex-manager.js +656 -0
  186. package/dist/src/utils/codex-manager.js.map +1 -0
  187. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  188. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  189. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  190. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  191. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  192. package/dist/src/utils/git-catalog-provider.js +10 -25
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  195. package/dist/src/utils/lifecycle-active-store.js +35 -247
  196. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +3 -0
  198. package/dist/src/utils/multi-source-catalog.js +120 -10
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +15 -6
  201. package/dist/src/utils/native-cli.js +31 -39
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +0 -3
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +10 -1
  206. package/dist/src/utils/ownership.js +52 -24
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.js +12 -8
  209. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  210. package/dist/src/utils/plugin-config-write.js +18 -35
  211. package/dist/src/utils/plugin-config-write.js.map +1 -1
  212. package/dist/src/utils/plugin-config.d.ts +1 -12
  213. package/dist/src/utils/plugin-config.js +107 -226
  214. package/dist/src/utils/plugin-config.js.map +1 -1
  215. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  216. package/dist/src/utils/plugin-scripts.js +1 -1
  217. package/dist/src/utils/plugin-scripts.js.map +1 -1
  218. package/dist/src/utils/state-paths.js +7 -2
  219. package/dist/src/utils/state-paths.js.map +1 -1
  220. package/dist/src/utils/target-eligibility.d.ts +7 -2
  221. package/dist/src/utils/target-eligibility.js +6 -3
  222. package/dist/src/utils/target-eligibility.js.map +1 -1
  223. package/package.json +4 -3
  224. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  225. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  226. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  227. package/dist/src/commands/status-v4.js.map +0 -1
  228. package/dist/src/commands/status.d.ts +0 -93
  229. package/dist/src/commands/status.js +0 -442
  230. package/dist/src/commands/status.js.map +0 -1
  231. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  232. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  233. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  234. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  235. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  236. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  237. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
@@ -1,34 +1,52 @@
1
1
  import crypto from 'node:crypto';
2
- import { statusCommandV4, } from '../commands/status-v4.js';
2
+ import path from 'node:path';
3
+ import { statusCommandV5, } from '../commands/status-v5.js';
3
4
  import { addCatalogSourceCommand, listCatalogSources, preflightCatalogSourceMutation, removeCatalogSourceCommand, setPrimaryCatalogSourceCommand, updateCatalogSourceCommand, } from '../commands/catalog-source.js';
4
5
  import { cleanupWriteCommand, installWriteCommand, reinstallWriteCommand, uninstallWriteCommand, upgradeWriteCommand, } from '../commands/write-commands.js';
5
6
  import { readCliPackageInfo } from '../utils/local-marketplace.js';
7
+ import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
6
8
  import { readCatalogSourcesConfigSnapshot, } from '../utils/catalog-sources.js';
7
9
  import { withResolvedCatalogPlugin, withResolvedMultiSourceCatalog, formatPluginId, parsePluginId, } from '../utils/multi-source-catalog.js';
8
10
  import { coercePluginConfigUpdates, readNamespacedPluginConfigStoreIdentity, readPluginConfigStore, resolveConfigRead, } from '../utils/plugin-config.js';
9
11
  import { applyPluginConfigWrite, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
12
+ import { offlineCleanupEvidenceFromInventory, } from '../commands/offline-cleanup-evidence.js';
13
+ import { inventoryCatalogSourceReferences } from '../utils/catalog-source-references.js';
10
14
  import { operationEvidenceFingerprint, TuiController, TuiOperationBlockedError, } from './controller.js';
11
15
  import { buildConfigGroups, buildConfigPage, projectPlatformPlacement, } from './model.js';
12
16
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
13
- import { targetCanBeUninstalled } from '../utils/target-eligibility.js';
17
+ import { targetCanBeUninstalled, targetHasArtifactCollision, } from '../utils/target-eligibility.js';
14
18
  import { calculateCatalogSourceIdentity } from '../utils/catalog-source-identity.js';
15
- import { resolvePluginsFromCatalog } from '../utils/manifest.js';
19
+ import { checkAwCapabilitySupport, readPluginManifest } from '../utils/manifest.js';
16
20
  import { withAwHomeMutationLock } from '../utils/aw-home-mutation-lock.js';
21
+ import { IMPLEMENTED_AW_CAPABILITIES } from '../commands/aw-capabilities.js';
22
+ import { resolveCatalogOperationContext, resolvePartialCatalogOperationContext, selectCatalogOperationContext, } from '../utils/catalog-operation-context.js';
23
+ import { activateCatalogSourceCandidate, refreshCatalogSourceGeneration, setCatalogSourceRefreshPolicy, } from '../utils/catalog-generation-service.js';
24
+ import { loadCatalogGenerationViewModels } from './catalog-generation.js';
17
25
  import { installPortableSkills, preparePortableInstall, PortableCommandError, } from '../portable/commands.js';
26
+ import { MANAGED_CODEX_PACKAGE_NAME, MANAGED_CODEX_VERSION, } from '../utils/codex-manager.js';
18
27
  async function loadPortablePluginIds(options) {
19
28
  return withResolvedMultiSourceCatalog({
20
29
  cliVersion: options.cliVersion,
21
30
  freshnessPolicy: 'cacheFirst',
22
31
  env: options.context.env,
23
32
  signal: options.signal,
33
+ ...(options.catalogOperationContext
34
+ ? { operationContext: options.catalogOperationContext }
35
+ : {}),
36
+ ...(options.bootstrapMissingActive === false ? { bootstrapMissingActive: false } : {}),
24
37
  consume: async (catalog) => (await Promise.all(catalog.sources.flatMap((item) => item.resolution
25
- ? [resolvePluginsFromCatalog(item.resolution.rootDir, undefined).then((plugins) => plugins
26
- .filter((plugin) => plugin.manifest.distributions?.agentSkills)
27
- .map((plugin) => formatPluginId({
28
- sourceId: item.resolution.sourceId,
29
- pluginName: plugin.manifest.name,
30
- })))]
31
- : []))).flat(),
38
+ ? item.resolution.manifest.entries.flatMap((entry) => {
39
+ const support = checkAwCapabilitySupport(entry.requires.aw.capabilities ?? [], IMPLEMENTED_AW_CAPABILITIES);
40
+ return support.supported
41
+ ? [readPluginManifest(path.join(item.resolution.rootDir, 'plugins', entry.name), { mode: 'bundle' }).then((plugin) => plugin.manifest.distributions?.agentSkills
42
+ ? formatPluginId({
43
+ sourceId: item.resolution.sourceId,
44
+ pluginName: entry.name,
45
+ })
46
+ : null)]
47
+ : [];
48
+ })
49
+ : []))).filter((pluginId) => pluginId !== null),
32
50
  });
33
51
  }
34
52
  let tuiControllerRuntimeTestHooks;
@@ -60,6 +78,13 @@ export function statusTargetFingerprint(targets, revisions = []) {
60
78
  .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')),
61
79
  });
62
80
  }
81
+ export function configAffectedStatusTargets(targets, pluginId, configScope) {
82
+ return targets.filter((target) => target.pluginId === pluginId
83
+ && (configScope === 'user' || target.scope === 'project')
84
+ && (target.state === 'present' || target.state === 'partial'))
85
+ .sort((left, right) => `${left.platform}\0${left.scope}`
86
+ .localeCompare(`${right.platform}\0${right.scope}`, 'en'));
87
+ }
63
88
  function operationReviewStale(message) {
64
89
  return new TuiOperationBlockedError('operationReviewStale', `operationReviewStale: ${message}`);
65
90
  }
@@ -73,6 +98,8 @@ function sourceEvidenceForPlugin(source, pluginName) {
73
98
  manifestDigest: source.manifestDigest,
74
99
  snapshotDigest: source.snapshotDigest,
75
100
  contentDigest: entry.contentDigest,
101
+ catalogVersion: source.manifest.catalogVersion,
102
+ channel: source.manifest.channel,
76
103
  };
77
104
  }
78
105
  function assertSourceEvidenceMatches(expected, actual) {
@@ -115,6 +142,9 @@ function assertIntentIsLegal(intent, targets, revisions = []) {
115
142
  if (intent.action === 'install' && otherScopeInstalled) {
116
143
  throw new TuiOperationBlockedError('scopeConflict', '这个平台已安装在另一个使用位置。请先移除原位置。');
117
144
  }
145
+ if (intent.action === 'install' && targetHasArtifactCollision(selected)) {
146
+ throw new TuiOperationBlockedError('artifactCollision', '所选位置存在输出冲突。请先移除或迁移冲突内容。');
147
+ }
118
148
  if (intent.action === 'install' && selected.state !== 'absent') {
119
149
  throw new TuiOperationBlockedError('targetAlreadyInstalled', '所选位置已经安装。');
120
150
  }
@@ -169,6 +199,13 @@ async function assertTypedIssueOrigin(intent, targets) {
169
199
  }
170
200
  return;
171
201
  }
202
+ else if (intent.originIssue.category === 'sourceMissing') {
203
+ if (intent.originIssue.actionId !== 'cleanup' || intent.action !== 'cleanup'
204
+ || !selected || !selected.errors.some((error) => error.code === 'sourceMissing')) {
205
+ stale('原来源缺失问题已变化。');
206
+ }
207
+ return;
208
+ }
172
209
  else if (intent.originIssue.category === 'scopeConflict') {
173
210
  const installedScopes = new Set(targets.filter((target) => target.pluginId === intent.pluginId
174
211
  && target.platform === intent.platform
@@ -205,22 +242,28 @@ async function loadDependencyOverviews(options) {
205
242
  freshnessPolicy: 'cacheFirst',
206
243
  env: options.context.env,
207
244
  signal: options.signal,
245
+ ...(options.catalogOperationContext
246
+ ? { operationContext: options.catalogOperationContext }
247
+ : {}),
248
+ ...(options.bootstrapMissingActive === false ? { bootstrapMissingActive: false } : {}),
208
249
  consume: async (catalog) => ({
209
- nodes: (await Promise.all(catalog.sources.flatMap((item) => item.resolution
210
- ? [resolvePluginsFromCatalog(item.resolution.rootDir, undefined).then((plugins) => plugins.map((plugin) => ({
211
- pluginId: formatPluginId({
212
- sourceId: item.resolution.sourceId,
213
- pluginName: plugin.manifest.name,
214
- }),
215
- displayName: plugin.manifest.interface?.displayName ?? plugin.manifest.name,
216
- dependencies: (plugin.manifest.dependencies ?? []).map((pluginName) => formatPluginId({ sourceId: item.resolution.sourceId, pluginName })),
217
- })))] : []))).flat(),
250
+ nodes: catalog.sources.flatMap((item) => item.resolution
251
+ ? item.resolution.manifest.entries.map((entry) => ({
252
+ pluginId: formatPluginId({
253
+ sourceId: item.resolution.sourceId,
254
+ pluginName: entry.name,
255
+ }),
256
+ displayName: entry.interface?.displayName ?? entry.name,
257
+ dependencies: entry.dependencies.map((pluginName) => formatPluginId({ sourceId: item.resolution.sourceId, pluginName })),
258
+ }))
259
+ : []),
218
260
  sourceErrors: catalog.sources.flatMap((item) => item.error
219
261
  ? [{ sourceId: item.sourceId, code: 'dependencyGraphInvalid' }]
220
262
  : []),
221
263
  }),
222
264
  });
223
265
  const nodes = new Map(graph.nodes.map((node) => [node.pluginId, node]));
266
+ const summaryById = new Map(options.status.pluginSummaries.map((summary) => [summary.pluginId, summary]));
224
267
  const displayName = (pluginId) => nodes.get(pluginId)?.displayName
225
268
  ?? parsePluginId(pluginId).pluginName;
226
269
  const reverse = new Map();
@@ -233,6 +276,9 @@ async function loadDependencyOverviews(options) {
233
276
  for (const target of options.status.payload.targets) {
234
277
  targetsByPlugin.set(target.pluginId, [...(targetsByPlugin.get(target.pluginId) ?? []), target]);
235
278
  }
279
+ const unsupportedPluginIds = new Set(options.status.pluginSummaries
280
+ .filter((summary) => (summary.missingCapabilities?.length ?? 0) > 0)
281
+ .map((summary) => summary.pluginId));
236
282
  const placementReferences = new Map();
237
283
  for (const consumer of options.status.payload.targets.filter((target) => target.state !== 'absent')) {
238
284
  const consumerNode = nodes.get(consumer.pluginId);
@@ -279,7 +325,8 @@ async function loadDependencyOverviews(options) {
279
325
  };
280
326
  const aggregateFor = (pluginId, placements) => {
281
327
  const sourceId = parsePluginId(pluginId).sourceId;
282
- if (graph.sourceErrors.some((error) => error.sourceId === sourceId))
328
+ if (graph.sourceErrors.some((error) => error.sourceId === sourceId)
329
+ || unsupportedPluginIds.has(pluginId))
283
330
  return 'unavailable';
284
331
  if (placements.length === 0 || placements.every((placement) => placement.state === 'notInstalled')) {
285
332
  return 'notInstalled';
@@ -359,6 +406,13 @@ async function loadDependencyOverviews(options) {
359
406
  title: '依赖关系需要处理',
360
407
  explanation: '当前目标的依赖证据不能安全通过检查。',
361
408
  })));
409
+ const consumerTargets = summaryById.get(pluginId)?.supportedTargets;
410
+ const unsupportedDependency = dependencyItems.find((dependency) => {
411
+ const dependencyTargets = summaryById.get(dependency.pluginId)?.supportedTargets;
412
+ if (!consumerTargets || !dependencyTargets)
413
+ return true;
414
+ return ['codex', 'claude'].some((platform) => consumerTargets[platform].length > 0 && dependencyTargets[platform].length === 0);
415
+ });
362
416
  const sourceId = parsePluginId(pluginId).sourceId;
363
417
  const blockers = [
364
418
  ...graph.sourceErrors.filter((error) => error.sourceId === sourceId).map((error) => ({
@@ -371,6 +425,11 @@ async function loadDependencyOverviews(options) {
371
425
  title: '依赖关系无效',
372
426
  explanation: '依赖关系形成环。',
373
427
  }] : []),
428
+ ...(unsupportedDependency ? [{
429
+ code: 'dependencyTargetUnsupported',
430
+ title: '依赖目标不兼容',
431
+ explanation: `${unsupportedDependency.displayName} 不支持根插件所需的平台。`,
432
+ }] : []),
374
433
  ...targetBlockers,
375
434
  ];
376
435
  const directCount = dependencyItems.filter((item) => item.relation === 'direct').length;
@@ -418,40 +477,85 @@ function createReviewStageReporter(options) {
418
477
  };
419
478
  }
420
479
  export function createTuiControllerDependencies(context, progressFeed, options = {}) {
480
+ const readInventory = options.inventoryCatalogSourceReferences
481
+ ?? inventoryCatalogSourceReferences;
482
+ const createOperationInventoryReader = () => {
483
+ let ordinaryInventory;
484
+ return (inventoryOptions) => {
485
+ if (inventoryOptions.includeHistoricalProjects === true) {
486
+ return readInventory(inventoryOptions);
487
+ }
488
+ ordinaryInventory ??= readInventory(inventoryOptions);
489
+ return ordinaryInventory;
490
+ };
491
+ };
492
+ const catalogOperationByEvidence = new WeakMap();
493
+ let sessionCatalogOperationContext;
494
+ let sessionStatusResult;
495
+ let bootstrapNextCatalogSession = true;
496
+ const sessionContextForPlugin = (pluginId) => {
497
+ const sourceId = parsePluginId(pluginId).sourceId;
498
+ if (!sourceId || !sessionCatalogOperationContext)
499
+ return undefined;
500
+ return selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId]);
501
+ };
421
502
  const loadStatus = async (signal) => {
422
503
  signal.throwIfAborted();
423
504
  const packageInfo = await readCliPackageInfo();
505
+ const catalogOperationContext = sessionCatalogOperationContext
506
+ ?? await resolvePartialCatalogOperationContext({
507
+ cliVersion: packageInfo.version,
508
+ env: context.env,
509
+ signal,
510
+ ...(bootstrapNextCatalogSession ? {} : { bootstrapMissingActive: false }),
511
+ ...(options.catalogGenerationRuntime ? { runtime: options.catalogGenerationRuntime } : {}),
512
+ }).catch(() => undefined);
513
+ if (catalogOperationContext)
514
+ sessionCatalogOperationContext = catalogOperationContext;
424
515
  for (let attempt = 0; attempt < 3; attempt += 1) {
425
- const result = await statusCommandV4({
516
+ const result = await statusCommandV5({
426
517
  cwd: context.cwd,
427
518
  env: context.env,
428
519
  cliVersion: packageInfo.version,
429
520
  verbose: context.diagnostics.verbose,
521
+ ...(catalogOperationContext ? { catalogOperationContext } : {}),
522
+ ...(!catalogOperationContext ? { bootstrapMissingActive: false } : {}),
523
+ ...(options.catalogGenerationRuntime ? { catalogGenerationRuntime: options.catalogGenerationRuntime } : {}),
524
+ inventoryCatalogSourceReferences: readInventory,
430
525
  });
431
526
  signal.throwIfAborted();
432
527
  const configured = await listCatalogSources({ env: context.env });
433
528
  signal.throwIfAborted();
434
529
  if (result.payload.catalog.configDigest !== configured.configDigest)
435
530
  continue;
531
+ sessionStatusResult = result;
436
532
  const dependencyOverviews = await loadDependencyOverviews({
437
533
  context,
438
534
  cliVersion: packageInfo.version,
439
535
  status: result,
440
536
  signal,
537
+ ...(catalogOperationContext ? { catalogOperationContext } : {}),
538
+ ...(!catalogOperationContext ? { bootstrapMissingActive: false } : {}),
441
539
  });
442
540
  const portablePluginIds = await loadPortablePluginIds({
443
541
  context,
444
542
  cliVersion: packageInfo.version,
445
543
  signal,
544
+ ...(catalogOperationContext ? { catalogOperationContext } : {}),
545
+ ...(!catalogOperationContext ? { bootstrapMissingActive: false } : {}),
446
546
  });
447
547
  signal.throwIfAborted();
548
+ const catalogGenerations = await loadCatalogGenerationViewModels({ env: context.env });
549
+ signal.throwIfAborted();
448
550
  const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
449
551
  return {
552
+ cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
450
553
  payload: result.payload,
451
554
  pluginSummaries: result.pluginSummaries,
452
555
  targetRevisions: result.targetRevisions,
453
556
  dependencyOverviews,
454
557
  portablePluginIds,
558
+ catalogGenerations,
455
559
  sourceDetails: configured.sources.map((source) => {
456
560
  const status = statusById.get(source.id);
457
561
  return {
@@ -479,7 +583,38 @@ export function createTuiControllerDependencies(context, progressFeed, options =
479
583
  }
480
584
  throw new TuiOperationBlockedError('operationReviewStale', '状态读取期间来源配置持续变化。请重试。');
481
585
  };
482
- const captureEvidence = async (intent, signal, freshnessPolicy = 'cacheFirst', preparation) => {
586
+ const captureEvidence = async (intent, signal, preparation, bootstrapMissingActive = true, operationInventory = createOperationInventoryReader()) => {
587
+ if (intent.kind === 'catalogActivate') {
588
+ signal?.throwIfAborted();
589
+ const source = (await loadCatalogGenerationViewModels({ env: context.env }))
590
+ .find((entry) => entry.sourceId === intent.sourceId);
591
+ if (!source?.active || !source.candidate) {
592
+ throw operationReviewStale('Catalog Candidate 不再可用。');
593
+ }
594
+ if (source.candidate.generationKey !== intent.candidateGenerationKey) {
595
+ throw operationReviewStale('Catalog Candidate 已变化。');
596
+ }
597
+ const sessionCatalog = sessionCatalogOperationContext?.catalog;
598
+ return {
599
+ catalog: {
600
+ configDigest: sessionCatalog?.configDigest ?? 'unavailable',
601
+ compositeDigest: sessionCatalog?.compositeDigest ?? 'unavailable',
602
+ ...(sessionCatalogOperationContext?.generationSetDigest
603
+ ? { generationSetDigest: sessionCatalogOperationContext.generationSetDigest }
604
+ : {}),
605
+ },
606
+ catalogCandidate: {
607
+ sourceId: source.sourceId,
608
+ activeVersion: source.active.catalogVersion,
609
+ candidateVersion: source.candidate.catalogVersion,
610
+ channel: source.candidate.channel,
611
+ requestedRef: source.candidate.requestedRef,
612
+ candidateGenerationKey: source.candidate.generationKey,
613
+ digestShort: source.candidate.digestShort,
614
+ pluginChanges: source.candidate.pluginChanges,
615
+ },
616
+ };
617
+ }
483
618
  if (intent.kind === 'sourceMutation') {
484
619
  signal?.throwIfAborted();
485
620
  if (intent.action === 'add' && !intent.source
@@ -522,8 +657,40 @@ export function createTuiControllerDependencies(context, progressFeed, options =
522
657
  }
523
658
  signal?.throwIfAborted();
524
659
  const packageInfo = await readCliPackageInfo();
660
+ const offlineCleanupReason = intent.kind === 'pluginWrite'
661
+ && intent.action === 'cleanup'
662
+ && intent.originIssue?.actionId === 'cleanup'
663
+ && (intent.originIssue?.category === 'sourceMissing'
664
+ || intent.originIssue?.category === 'sourceUnavailable')
665
+ ? intent.originIssue.category === 'sourceMissing' ? 'sourceMissing' : 'bundleUnavailable'
666
+ : undefined;
667
+ let catalogOperation;
668
+ let catalogOperationError;
669
+ if (!offlineCleanupReason && intent.kind !== 'portableInstall'
670
+ && sessionCatalogOperationContext) {
671
+ try {
672
+ const sourceIds = [parsePluginId(intent.pluginId).sourceId];
673
+ catalogOperation = bootstrapMissingActive
674
+ ? selectCatalogOperationContext(sessionCatalogOperationContext, sourceIds)
675
+ : await resolveCatalogOperationContext({
676
+ cliVersion: packageInfo.version,
677
+ sourceIds,
678
+ env: context.env,
679
+ ...(options.catalogGenerationRuntime ? { runtime: options.catalogGenerationRuntime } : {}),
680
+ ...(signal ? { signal } : {}),
681
+ ...(!bootstrapMissingActive ? { bootstrapMissingActive: false } : {}),
682
+ });
683
+ }
684
+ catch (error) {
685
+ catalogOperationError = error;
686
+ }
687
+ }
525
688
  if (intent.kind === 'portableInstall') {
526
689
  await preparation?.reporter.emit('portable');
690
+ const portableSourceId = parsePluginId(intent.pluginId).sourceId;
691
+ const portableCatalogOperation = sessionCatalogOperationContext
692
+ ? selectCatalogOperationContext(sessionCatalogOperationContext, [portableSourceId])
693
+ : undefined;
527
694
  const portable = await preparePortableInstall({
528
695
  selector: intent.pluginId,
529
696
  scope: intent.scope,
@@ -533,20 +700,106 @@ export function createTuiControllerDependencies(context, progressFeed, options =
533
700
  env: context.env,
534
701
  cliVersion: packageInfo.version,
535
702
  signal,
703
+ ...(portableCatalogOperation
704
+ ? { catalogOperationContext: portableCatalogOperation }
705
+ : {}),
536
706
  });
537
707
  signal?.throwIfAborted();
538
708
  await preparation?.reporter.emit('evidence');
539
709
  signal?.throwIfAborted();
540
- return {
710
+ const evidence = {
541
711
  catalog: {
542
712
  configDigest: portable.configDigest,
543
713
  compositeDigest: portable.evidenceDigest,
714
+ ...(portable.generationSetDigest
715
+ ? { generationSetDigest: portable.generationSetDigest }
716
+ : {}),
544
717
  },
545
718
  portable,
546
719
  };
720
+ if (catalogOperation)
721
+ catalogOperationByEvidence.set(evidence, catalogOperation);
722
+ return evidence;
723
+ }
724
+ if (preparation && (intent.kind === 'configSave' || intent.kind === 'configReset')) {
725
+ if (!sessionStatusResult || !catalogOperation) {
726
+ throw operationReviewStale('当前 TUI session 没有可复用的设置证据。');
727
+ }
728
+ await preparation.reporter.emit('config');
729
+ const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
730
+ signal?.throwIfAborted();
731
+ if (configSnapshot.digest !== sessionStatusResult.payload.catalog.configDigest) {
732
+ throw operationReviewStale('准备设置确认期间来源配置发生变化。');
733
+ }
734
+ const selected = await withResolvedCatalogPlugin({
735
+ selector: intent.pluginId,
736
+ cliVersion: packageInfo.version,
737
+ freshnessPolicy: 'cacheFirst',
738
+ configSnapshot,
739
+ env: context.env,
740
+ operationContext: catalogOperation,
741
+ ...(signal ? { signal } : {}),
742
+ consume: async ({ plugin, source }) => {
743
+ if (!plugin.manifest.config) {
744
+ throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
745
+ }
746
+ if (intent.kind === 'configSave') {
747
+ await coercePluginConfigUpdates({
748
+ schema: plugin.manifest.config.schema,
749
+ updates: intent.updates ?? {},
750
+ cwd: context.cwd,
751
+ });
752
+ for (const key of intent.removeKeys ?? []) {
753
+ if (!plugin.manifest.config.schema[key]) {
754
+ throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
755
+ }
756
+ }
757
+ }
758
+ return {
759
+ pluginName: plugin.manifest.name,
760
+ source: sourceEvidenceForPlugin(source, plugin.manifest.name),
761
+ };
762
+ },
763
+ });
764
+ const store = await readPluginConfigStore({
765
+ sourceId: selected.source.sourceId,
766
+ sourceIdentity: selected.source.sourceIdentity,
767
+ pluginName: selected.pluginName,
768
+ scope: intent.scope,
769
+ projectLayer: intent.projectLayer,
770
+ cwd: context.cwd,
771
+ env: context.env,
772
+ });
773
+ signal?.throwIfAborted();
774
+ const relevantTargets = configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope);
775
+ const relevantRevisions = (sessionStatusResult.targetRevisions ?? []).filter((revision) => relevantTargets.some((target) => target.platform === revision.platform
776
+ && target.scope === revision.scope
777
+ && target.pluginId === revision.pluginId));
778
+ await preparation.reporter.emit('evidence');
779
+ const evidence = {
780
+ catalog: {
781
+ configDigest: sessionStatusResult.payload.catalog.configDigest,
782
+ compositeDigest: sessionStatusResult.payload.catalog.compositeDigest,
783
+ generationSetDigest: catalogOperation.generationSetDigest,
784
+ },
785
+ source: selected.source,
786
+ targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
787
+ config: {
788
+ scope: intent.scope,
789
+ projectLayer: intent.projectLayer,
790
+ schemaIdentity: selected.source.manifestDigest,
791
+ storeDigest: digest({ exists: store.exists, values: store.values }),
792
+ affectedTargets: relevantTargets.map(({ platform, scope }) => ({ platform, scope })),
793
+ },
794
+ };
795
+ catalogOperationByEvidence.set(evidence, catalogOperation);
796
+ return evidence;
547
797
  }
548
798
  await preparation?.reporter.emit('status');
549
- const status = await statusCommandV4({
799
+ const statusCatalogOperation = offlineCleanupReason
800
+ ? undefined
801
+ : catalogOperation ?? (bootstrapMissingActive ? sessionCatalogOperationContext : undefined);
802
+ const status = await statusCommandV5({
550
803
  plugins: [...new Set([intent.pluginId,
551
804
  ...(intent.kind === 'pluginWrite' && intent.originIssue?.consumerPluginId
552
805
  ? [intent.originIssue.consumerPluginId] : [])])],
@@ -557,39 +810,77 @@ export function createTuiControllerDependencies(context, progressFeed, options =
557
810
  cwd: context.cwd,
558
811
  env: context.env,
559
812
  cliVersion: packageInfo.version,
813
+ ...(statusCatalogOperation ? { catalogOperationContext: statusCatalogOperation } : {}),
814
+ ...(offlineCleanupReason || !bootstrapMissingActive
815
+ || (!statusCatalogOperation && sessionCatalogOperationContext)
816
+ ? { bootstrapMissingActive: false }
817
+ : {}),
818
+ inventoryCatalogSourceReferences: operationInventory,
560
819
  });
561
820
  signal?.throwIfAborted();
821
+ const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
822
+ signal?.throwIfAborted();
823
+ if (configSnapshot.digest !== status.payload.catalog.configDigest) {
824
+ throw operationReviewStale('准备确认信息期间来源配置发生变化。');
825
+ }
826
+ if (catalogOperationError)
827
+ throw catalogOperationError;
562
828
  if (intent.kind === 'pluginWrite') {
563
829
  await assertTypedIssueOrigin(intent, status.payload.targets);
564
830
  if (preparation?.validateIntent) {
565
831
  assertIntentIsLegal(intent, status.payload.targets, status.targetRevisions);
566
832
  }
567
833
  }
568
- const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
569
- signal?.throwIfAborted();
570
- if (configSnapshot.digest !== status.payload.catalog.configDigest) {
571
- throw operationReviewStale('准备确认信息期间来源配置发生变化。');
834
+ if (!offlineCleanupReason && !catalogOperation) {
835
+ catalogOperation = await resolveCatalogOperationContext({
836
+ cliVersion: packageInfo.version,
837
+ sourceIds: [parsePluginId(intent.pluginId).sourceId],
838
+ configSnapshot,
839
+ env: context.env,
840
+ ...(options.catalogGenerationRuntime ? { runtime: options.catalogGenerationRuntime } : {}),
841
+ ...(signal ? { signal } : {}),
842
+ ...(!bootstrapMissingActive ? { bootstrapMissingActive: false } : {}),
843
+ });
572
844
  }
573
845
  let dependencyPlan;
574
846
  await preparation?.reporter.emit('catalog');
575
- const bundleUnavailableCleanupTarget = intent.kind === 'pluginWrite'
576
- && intent.action === 'cleanup'
577
- && intent.originIssue?.category === 'sourceUnavailable'
578
- && intent.originIssue.actionId === 'cleanup'
847
+ const offlineCleanupTarget = offlineCleanupReason && intent.kind === 'pluginWrite'
579
848
  ? status.payload.targets.find((target) => target.pluginId === intent.pluginId
580
849
  && target.platform === intent.platform
581
850
  && target.scope === intent.scope
582
- && target.errors.some((error) => error.code === 'bundleUnavailable'))
851
+ && target.errors.some((error) => error.code === offlineCleanupReason))
583
852
  : undefined;
584
- const catalogSource = bundleUnavailableCleanupTarget
585
- ? status.payload.catalog.sources.find((source) => source.id === bundleUnavailableCleanupTarget.sourceId)
853
+ const catalogSource = offlineCleanupTarget
854
+ ? status.payload.catalog.sources.find((source) => source.id === offlineCleanupTarget.sourceId)
586
855
  : undefined;
587
- const selected = bundleUnavailableCleanupTarget
856
+ let offlineCleanup;
857
+ if (offlineCleanupReason && offlineCleanupTarget) {
858
+ await preparation?.reporter.emit('inventory');
859
+ const inventory = await operationInventory({
860
+ cwd: context.cwd,
861
+ env: context.env,
862
+ });
863
+ signal?.throwIfAborted();
864
+ offlineCleanup = offlineCleanupEvidenceFromInventory({
865
+ reason: offlineCleanupReason,
866
+ pluginId: offlineCleanupTarget.pluginId,
867
+ pluginName: offlineCleanupTarget.pluginName,
868
+ sourceId: offlineCleanupTarget.sourceId,
869
+ sourceIdentity: offlineCleanupTarget.sourceIdentity,
870
+ platform: offlineCleanupTarget.platform,
871
+ scope: offlineCleanupTarget.scope,
872
+ inventory,
873
+ }) ?? undefined;
874
+ if (!offlineCleanup) {
875
+ throw operationReviewStale('离线清理现场证据已消失或无法验证。');
876
+ }
877
+ }
878
+ const selected = offlineCleanupTarget
588
879
  ? {
589
- pluginName: bundleUnavailableCleanupTarget.pluginName,
880
+ pluginName: offlineCleanupTarget.pluginName,
590
881
  source: {
591
- sourceId: bundleUnavailableCleanupTarget.sourceId,
592
- sourceIdentity: bundleUnavailableCleanupTarget.sourceIdentity,
882
+ sourceId: offlineCleanupTarget.sourceId,
883
+ sourceIdentity: offlineCleanupTarget.sourceIdentity,
593
884
  ...(catalogSource?.requestedRef ? { requestedRef: catalogSource.requestedRef } : {}),
594
885
  ...(catalogSource?.resolvedCommit ? { resolvedCommit: catalogSource.resolvedCommit } : {}),
595
886
  },
@@ -597,9 +888,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
597
888
  : await withResolvedCatalogPlugin({
598
889
  selector: intent.pluginId,
599
890
  cliVersion: packageInfo.version,
600
- freshnessPolicy,
891
+ freshnessPolicy: 'cacheFirst',
601
892
  configSnapshot,
602
893
  env: context.env,
894
+ ...(catalogOperation ? { operationContext: catalogOperation } : {}),
603
895
  ...(signal ? { signal } : {}),
604
896
  consume: async ({ plugin, source }) => {
605
897
  if (preparation?.validateIntent && intent.kind === 'configSave') {
@@ -624,48 +916,88 @@ export function createTuiControllerDependencies(context, progressFeed, options =
624
916
  };
625
917
  },
626
918
  });
919
+ let codexManager;
627
920
  const hasDeclaredDependencies = (status.pluginSummaries.find((summary) => summary.pluginId === intent.pluginId)?.dependencyTotalCount ?? 0) > 0;
628
- if (intent.kind === 'pluginWrite'
629
- && hasDeclaredDependencies
630
- && (intent.action === 'install' || intent.action === 'upgrade' || intent.action === 'reinstall')) {
921
+ if (intent.kind === 'pluginWrite') {
631
922
  const command = {
632
923
  install: installWriteCommand,
633
924
  upgrade: upgradeWriteCommand,
634
925
  reinstall: reinstallWriteCommand,
926
+ uninstall: uninstallWriteCommand,
927
+ cleanup: cleanupWriteCommand,
635
928
  }[intent.action];
636
929
  const preview = await command([intent.pluginId], {
637
930
  cwd: context.cwd,
638
931
  platform: intent.platform,
639
932
  scope: intent.scope,
933
+ ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
640
934
  }, {
641
935
  ...context,
936
+ inventoryCatalogSourceReferences: operationInventory,
642
937
  expectedCatalogConfigDigest: status.payload.catalog.configDigest,
938
+ ...(catalogOperation ? {
939
+ catalogOperationContext: catalogOperation,
940
+ expectedGenerationSetDigest: catalogOperation.generationSetDigest,
941
+ } : {}),
643
942
  previewDependencyPlanOnly: true,
644
943
  ...(preparation ? {
645
- previewCatalogFreshnessPolicy: 'cacheFirst',
646
944
  onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
647
945
  } : {}),
648
946
  });
649
947
  signal?.throwIfAborted();
650
- dependencyPlan = preview.dependencyPlan;
651
- if (!dependencyPlan) {
652
- throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
948
+ if (hasDeclaredDependencies
949
+ && (intent.action === 'install'
950
+ || intent.action === 'upgrade'
951
+ || intent.action === 'reinstall')) {
952
+ dependencyPlan = preview.dependencyPlan;
953
+ if (!dependencyPlan) {
954
+ throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
955
+ }
956
+ const blocker = dependencyPlan.targets.find((target) => target.action === 'block');
957
+ if (blocker) {
958
+ throw new TuiOperationBlockedError(blocker.errorCode ?? 'dependencyNotReady', `${blocker.errorCode ?? 'dependencyNotReady'}: 依赖安装计划被阻止。`);
959
+ }
653
960
  }
654
- const blocker = dependencyPlan.targets.find((target) => target.action === 'block');
655
- if (blocker) {
656
- throw new TuiOperationBlockedError(blocker.errorCode ?? 'dependencyNotReady', `${blocker.errorCode ?? 'dependencyNotReady'}: 依赖安装计划被阻止。`);
961
+ const managerPreview = preview?.codexManagerPreview;
962
+ if (managerPreview?.state === 'blocked') {
963
+ const message = managerPreview.code === 'codexSystemCliUnsupported'
964
+ ? '系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
965
+ : 'AW-managed Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。';
966
+ throw new TuiOperationBlockedError(managerPreview.code, `${managerPreview.code}: ${message}`);
967
+ }
968
+ if (managerPreview) {
969
+ codexManager = {
970
+ state: managerPreview.state,
971
+ packageName: MANAGED_CODEX_PACKAGE_NAME,
972
+ version: managerPreview.state === 'requires-install'
973
+ ? MANAGED_CODEX_VERSION
974
+ : managerPreview.manager.version,
975
+ };
657
976
  }
658
977
  }
659
978
  await preparation?.reporter.emit('evidence');
660
979
  signal?.throwIfAborted();
980
+ const evidenceTargets = intent.kind === 'configSave' || intent.kind === 'configReset'
981
+ ? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.scope)
982
+ : status.payload.targets;
983
+ const evidenceRevisions = intent.kind === 'configSave' || intent.kind === 'configReset'
984
+ ? (status.targetRevisions ?? []).filter((revision) => evidenceTargets.some((target) => target.platform === revision.platform
985
+ && target.scope === revision.scope
986
+ && target.pluginId === revision.pluginId))
987
+ : (status.targetRevisions ?? []);
661
988
  const evidence = {
662
989
  catalog: {
663
990
  configDigest: status.payload.catalog.configDigest,
664
991
  compositeDigest: status.payload.catalog.compositeDigest,
992
+ ...(catalogOperation
993
+ ? { generationSetDigest: catalogOperation.generationSetDigest }
994
+ : {}),
665
995
  },
666
996
  source: selected.source,
997
+ ...(offlineCleanup ? { offlineCleanup } : {}),
667
998
  ...(dependencyPlan ? { dependencyPlan } : {}),
668
- targetFingerprint: statusTargetFingerprint(status.payload.targets, status.targetRevisions ?? []),
999
+ ...(codexManager ? { codexManager } : {}),
1000
+ targetFingerprint: statusTargetFingerprint(evidenceTargets, evidenceRevisions),
669
1001
  };
670
1002
  if (intent.kind === 'configSave' || intent.kind === 'configReset') {
671
1003
  const store = await readPluginConfigStore({
@@ -683,11 +1015,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
683
1015
  projectLayer: intent.projectLayer,
684
1016
  schemaIdentity: selected.source.manifestDigest,
685
1017
  storeDigest: digest({ exists: store.exists, values: store.values }),
1018
+ affectedTargets: evidenceTargets.map(({ platform, scope }) => ({ platform, scope })),
686
1019
  };
687
1020
  }
1021
+ if (catalogOperation)
1022
+ catalogOperationByEvidence.set(evidence, catalogOperation);
688
1023
  return evidence;
689
1024
  };
690
1025
  const dependencies = {
1026
+ beginCatalogSession(sessionOptions) {
1027
+ sessionCatalogOperationContext = undefined;
1028
+ bootstrapNextCatalogSession = sessionOptions?.bootstrapMissingActive !== false;
1029
+ },
691
1030
  loadStatus,
692
1031
  async inspectSourceRemoval(_sourceId, signal) {
693
1032
  signal.throwIfAborted();
@@ -708,7 +1047,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
708
1047
  now: options.now ?? Date.now,
709
1048
  });
710
1049
  try {
711
- return await captureEvidence(intent, signal, 'cacheFirst', {
1050
+ return await captureEvidence(intent, signal, {
712
1051
  reporter,
713
1052
  validateIntent: true,
714
1053
  });
@@ -718,6 +1057,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
718
1057
  }
719
1058
  },
720
1059
  async validateIntent(intent, signal) {
1060
+ if (intent.kind === 'catalogActivate') {
1061
+ await captureEvidence(intent, signal);
1062
+ return;
1063
+ }
721
1064
  if (intent.kind === 'sourceMutation') {
722
1065
  const evidence = await captureEvidence(intent, signal);
723
1066
  if (evidence.sourceWrite?.configDigest === evidence.sourceWrite?.candidateDigest) {
@@ -733,6 +1076,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
733
1076
  freshnessPolicy: 'cacheFirst',
734
1077
  env: context.env,
735
1078
  signal,
1079
+ ...(sessionContextForPlugin(intent.pluginId)
1080
+ ? { operationContext: sessionContextForPlugin(intent.pluginId) }
1081
+ : {}),
736
1082
  consume: async ({ plugin }) => {
737
1083
  if (!plugin.manifest.config) {
738
1084
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
@@ -755,13 +1101,29 @@ export function createTuiControllerDependencies(context, progressFeed, options =
755
1101
  return;
756
1102
  signal.throwIfAborted();
757
1103
  const packageInfo = await readCliPackageInfo();
758
- const status = await statusCommandV4({
759
- plugins: [...new Set([intent.pluginId,
760
- ...(intent.originIssue?.consumerPluginId ? [intent.originIssue.consumerPluginId] : [])])],
1104
+ const pluginIds = [...new Set([intent.pluginId,
1105
+ ...(intent.originIssue?.consumerPluginId ? [intent.originIssue.consumerPluginId] : [])])];
1106
+ const offlineCleanup = intent.action === 'cleanup'
1107
+ && intent.originIssue?.actionId === 'cleanup'
1108
+ && (intent.originIssue.category === 'sourceMissing'
1109
+ || intent.originIssue.category === 'sourceUnavailable');
1110
+ const sourceIds = pluginIds.map((pluginId) => parsePluginId(pluginId).sourceId);
1111
+ const catalogOperation = offlineCleanup
1112
+ ? undefined
1113
+ : sessionCatalogOperationContext
1114
+ ? selectCatalogOperationContext(sessionCatalogOperationContext, sourceIds)
1115
+ : undefined;
1116
+ const status = await statusCommandV5({
1117
+ plugins: pluginIds,
761
1118
  platform: intent.platform,
762
1119
  cwd: context.cwd,
763
1120
  env: context.env,
764
1121
  cliVersion: packageInfo.version,
1122
+ ...(catalogOperation ? { catalogOperationContext: catalogOperation } : {}),
1123
+ ...(offlineCleanup || sessionCatalogOperationContext
1124
+ ? { bootstrapMissingActive: false }
1125
+ : {}),
1126
+ inventoryCatalogSourceReferences: readInventory,
765
1127
  });
766
1128
  signal.throwIfAborted();
767
1129
  await assertTypedIssueOrigin(intent, status.payload.targets);
@@ -788,6 +1150,48 @@ export function createTuiControllerDependencies(context, progressFeed, options =
788
1150
  },
789
1151
  });
790
1152
  },
1153
+ async refreshCatalogSource(sourceId, signal, onState) {
1154
+ let refreshError;
1155
+ let refreshCompleted = false;
1156
+ try {
1157
+ await refreshCatalogSourceGeneration({
1158
+ sourceId,
1159
+ cliVersion: (await readCliPackageInfo()).version,
1160
+ env: context.env,
1161
+ signal,
1162
+ runtime: {
1163
+ ...options.catalogGenerationRuntime,
1164
+ onProgress: (event) => onState(event.state),
1165
+ },
1166
+ });
1167
+ refreshCompleted = true;
1168
+ }
1169
+ catch (error) {
1170
+ refreshError = error;
1171
+ }
1172
+ signal.throwIfAborted();
1173
+ const source = (await loadCatalogGenerationViewModels({ env: context.env }))
1174
+ .find((entry) => entry.sourceId === sourceId);
1175
+ if (!source)
1176
+ throw refreshError ?? new TuiOperationBlockedError('catalogGenerationSourceMissing', 'Catalog 来源已不存在。');
1177
+ if (refreshError && source.refreshState !== 'failed'
1178
+ && source.refreshState !== 'immutableViolation')
1179
+ throw refreshError;
1180
+ return {
1181
+ source: refreshCompleted && source.refreshState === 'idle'
1182
+ ? { ...source, refreshState: 'upToDate' }
1183
+ : source,
1184
+ };
1185
+ },
1186
+ async setCatalogRefreshPolicy(sourceId, refreshPolicy) {
1187
+ await setCatalogSourceRefreshPolicy({
1188
+ sourceId,
1189
+ refreshPolicy,
1190
+ env: context.env,
1191
+ });
1192
+ return (await loadCatalogGenerationViewModels({ env: context.env }))
1193
+ .find((source) => source.sourceId === sourceId);
1194
+ },
791
1195
  captureEvidence,
792
1196
  logError(_error, code, logContext) {
793
1197
  return context.debugLogger.log({
@@ -808,6 +1212,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
808
1212
  freshnessPolicy: 'cacheFirst',
809
1213
  env: context.env,
810
1214
  signal,
1215
+ ...(sessionContextForPlugin(pluginId)
1216
+ ? { operationContext: sessionContextForPlugin(pluginId) }
1217
+ : {}),
811
1218
  consume: async ({ plugin }) => {
812
1219
  if (!plugin.manifest.config) {
813
1220
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
@@ -840,6 +1247,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
840
1247
  freshnessPolicy: 'cacheFirst',
841
1248
  env: context.env,
842
1249
  signal,
1250
+ ...(sessionContextForPlugin(pluginId)
1251
+ ? { operationContext: sessionContextForPlugin(pluginId) }
1252
+ : {}),
843
1253
  consume: async ({ plugin }) => {
844
1254
  if (!plugin.manifest.config) {
845
1255
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
@@ -860,7 +1270,39 @@ export function createTuiControllerDependencies(context, progressFeed, options =
860
1270
  cwd: context.cwd,
861
1271
  env: context.env,
862
1272
  });
863
- return buildConfigPage({ pluginId, schema: plugin.manifest.config, read, effectiveRead });
1273
+ const layerReads = layer.scope === 'effective' ? undefined : {
1274
+ user: await resolveConfigRead({
1275
+ pluginName: plugin.manifest.name,
1276
+ plugin,
1277
+ scope: 'user',
1278
+ projectLayer: 'shared',
1279
+ cwd: context.cwd,
1280
+ env: context.env,
1281
+ }),
1282
+ project: await resolveConfigRead({
1283
+ pluginName: plugin.manifest.name,
1284
+ plugin,
1285
+ scope: 'project',
1286
+ projectLayer: 'shared',
1287
+ cwd: context.cwd,
1288
+ env: context.env,
1289
+ }),
1290
+ local: await resolveConfigRead({
1291
+ pluginName: plugin.manifest.name,
1292
+ plugin,
1293
+ scope: 'project',
1294
+ projectLayer: 'local',
1295
+ cwd: context.cwd,
1296
+ env: context.env,
1297
+ }),
1298
+ };
1299
+ return buildConfigPage({
1300
+ pluginId,
1301
+ schema: plugin.manifest.config,
1302
+ read,
1303
+ effectiveRead,
1304
+ ...(layerReads ? { layerReads } : {}),
1305
+ });
864
1306
  },
865
1307
  });
866
1308
  },
@@ -920,6 +1362,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
920
1362
  env: context.env,
921
1363
  cliVersion: packageInfo.version,
922
1364
  expectedEvidenceDigest: expectedEvidence.portable.evidenceDigest,
1365
+ expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
923
1366
  onProgress(stage) {
924
1367
  const phase = stage === 'prepare' ? '检查插件依赖并生成候选'
925
1368
  : stage === 'add' ? '安装已选择的 Skills' : '验证安装结果';
@@ -934,49 +1377,84 @@ export function createTuiControllerDependencies(context, progressFeed, options =
934
1377
  throw error;
935
1378
  }
936
1379
  }
1380
+ if (intent.kind === 'catalogActivate') {
1381
+ const currentEvidence = await captureEvidence(intent, undefined, undefined, false);
1382
+ if (operationEvidenceFingerprint(currentEvidence) !== operationEvidenceFingerprint(expectedEvidence)) {
1383
+ throw operationReviewStale('确认后 Catalog Candidate 发生变化。');
1384
+ }
1385
+ try {
1386
+ return await activateCatalogSourceCandidate({
1387
+ sourceId: intent.sourceId,
1388
+ expectedCandidateGenerationKey: intent.candidateGenerationKey,
1389
+ env: context.env,
1390
+ });
1391
+ }
1392
+ catch (error) {
1393
+ if (error instanceof CatalogGenerationError
1394
+ && (error.code === 'catalogGenerationCandidateChanged'
1395
+ || error.code === 'catalogGenerationCandidateUnavailable')) {
1396
+ throw operationReviewStale('确认后 Catalog Candidate 发生变化。');
1397
+ }
1398
+ throw error;
1399
+ }
1400
+ }
1401
+ if (intent.kind === 'sourceMutation') {
1402
+ const currentEvidence = await captureEvidence(intent);
1403
+ if (operationEvidenceFingerprint(currentEvidence) !== operationEvidenceFingerprint(expectedEvidence)) {
1404
+ throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
1405
+ }
1406
+ await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
1407
+ const packageInfo = await readCliPackageInfo();
1408
+ const runtime = {
1409
+ onProgress: async (event) => {
1410
+ const phase = SOURCE_PROGRESS_PHASES[event.stage];
1411
+ progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase }, `来源操作:${phase}`);
1412
+ await tuiControllerRuntimeTestHooks?.onCatalogSourceProgress?.(event);
1413
+ },
1414
+ };
1415
+ const common = {
1416
+ cwd: context.cwd,
1417
+ env: context.env,
1418
+ expectedConfigDigest: expectedEvidence.sourceWrite?.configDigest,
1419
+ runtime,
1420
+ };
1421
+ if (intent.action === 'add' && intent.source) {
1422
+ return addCatalogSourceCommand({ ...common, source: intent.source, cliVersion: packageInfo.version });
1423
+ }
1424
+ if (intent.action === 'update' && intent.update) {
1425
+ return updateCatalogSourceCommand({ ...common, sourceId: intent.sourceId, update: intent.update, cliVersion: packageInfo.version });
1426
+ }
1427
+ if (intent.action === 'setPrimary') {
1428
+ return setPrimaryCatalogSourceCommand({ ...common, sourceId: intent.sourceId });
1429
+ }
1430
+ if (intent.action === 'remove') {
1431
+ return removeCatalogSourceCommand({ ...common, sourceId: intent.sourceId });
1432
+ }
1433
+ throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
1434
+ }
937
1435
  return withAwHomeMutationLock({
938
1436
  env: context.env,
939
1437
  action: async (awHomeMutationToken) => {
940
- const currentEvidence = await captureEvidence(intent, undefined, 'requireRemote');
1438
+ await tuiControllerRuntimeTestHooks?.beforeMutationEvidenceCapture?.();
1439
+ const operationInventory = createOperationInventoryReader();
1440
+ const currentEvidence = await captureEvidence(intent, undefined, undefined, false, operationInventory);
941
1441
  if (operationEvidenceFingerprint(currentEvidence) !== operationEvidenceFingerprint(expectedEvidence)) {
942
1442
  throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
943
1443
  }
944
- await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
945
- if (intent.kind === 'sourceMutation') {
946
- const packageInfo = await readCliPackageInfo();
947
- const runtime = {
948
- awHomeMutationToken,
949
- onProgress: async (event) => {
950
- const phase = SOURCE_PROGRESS_PHASES[event.stage];
951
- progressFeed?.setCurrent({ target: 'Catalog 来源配置', phase }, `来源操作:${phase}`);
952
- await tuiControllerRuntimeTestHooks?.onCatalogSourceProgress?.(event);
953
- },
954
- };
955
- const common = {
956
- cwd: context.cwd,
957
- env: context.env,
958
- expectedConfigDigest: expectedEvidence.sourceWrite?.configDigest,
959
- runtime,
960
- };
961
- if (intent.action === 'add' && intent.source) {
962
- return addCatalogSourceCommand({ ...common, source: intent.source, cliVersion: packageInfo.version });
963
- }
964
- if (intent.action === 'update' && intent.update) {
965
- return updateCatalogSourceCommand({ ...common, sourceId: intent.sourceId, update: intent.update, cliVersion: packageInfo.version });
966
- }
967
- if (intent.action === 'setPrimary') {
968
- return setPrimaryCatalogSourceCommand({ ...common, sourceId: intent.sourceId });
969
- }
970
- if (intent.action === 'remove') {
971
- return removeCatalogSourceCommand({ ...common, sourceId: intent.sourceId });
972
- }
973
- throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
1444
+ const catalogOperation = catalogOperationByEvidence.get(currentEvidence);
1445
+ if (expectedEvidence.catalog.generationSetDigest
1446
+ && catalogOperation?.generationSetDigest !== expectedEvidence.catalog.generationSetDigest) {
1447
+ throw operationReviewStale('确认后 Active Catalog generation 发生变化。');
974
1448
  }
1449
+ await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
975
1450
  if (intent.kind === 'pluginWrite') {
976
1451
  const options = {
977
1452
  cwd: context.cwd,
978
1453
  platform: intent.platform,
979
1454
  scope: intent.scope,
1455
+ ...(expectedEvidence.codexManager?.state === 'requires-install'
1456
+ ? { allowManagedCodexInstall: true }
1457
+ : {}),
980
1458
  ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
981
1459
  };
982
1460
  const command = {
@@ -987,14 +1465,28 @@ export function createTuiControllerDependencies(context, progressFeed, options =
987
1465
  cleanup: cleanupWriteCommand,
988
1466
  }[intent.action];
989
1467
  const currentSource = currentEvidence.source;
990
- if (!expectedEvidence.source || !currentSource) {
1468
+ const offlineCleanup = currentEvidence.offlineCleanup;
1469
+ if (!offlineCleanup && (!expectedEvidence.source || !currentSource)) {
991
1470
  throw new TuiOperationBlockedError('operationReviewStale', '确认信息缺少来源身份。');
992
1471
  }
993
1472
  const result = await command([intent.pluginId], options, {
994
1473
  ...context,
995
1474
  awHomeMutationToken,
1475
+ inventoryCatalogSourceReferences: operationInventory,
1476
+ ...(currentEvidence.dependencyPlan
1477
+ ? { expectedOperationDependencyPlan: currentEvidence.dependencyPlan }
1478
+ : {}),
996
1479
  expectedCatalogConfigDigest: expectedEvidence.catalog.configDigest,
997
- reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual),
1480
+ ...(catalogOperation ? {
1481
+ catalogOperationContext: catalogOperation,
1482
+ expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
1483
+ } : {}),
1484
+ ...(expectedEvidence.codexManager?.state === 'requires-install'
1485
+ ? { managedCodexAuthorizer: async () => true }
1486
+ : {}),
1487
+ ...(offlineCleanup
1488
+ ? { expectedOfflineCleanupEvidence: offlineCleanup }
1489
+ : { reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual) }),
998
1490
  });
999
1491
  throwIfWriteReviewStale(result);
1000
1492
  return result;
@@ -1008,10 +1500,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1008
1500
  return withResolvedCatalogPlugin({
1009
1501
  selector: intent.pluginId,
1010
1502
  cliVersion: packageInfo.version,
1011
- freshnessPolicy: 'requireRemote',
1503
+ freshnessPolicy: 'cacheFirst',
1012
1504
  env: context.env,
1505
+ ...(catalogOperation ? { operationContext: catalogOperation } : {}),
1013
1506
  consume: async ({ plugin, source }) => {
1014
1507
  assertSourceEvidenceMatches(currentSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
1508
+ const applyTargets = currentEvidence.config?.affectedTargets.map((target) => ({
1509
+ pluginName: plugin.manifest.name,
1510
+ platform: target.platform,
1511
+ scope: target.scope,
1512
+ })) ?? [];
1015
1513
  const result = intent.kind === 'configSave'
1016
1514
  ? await applyPluginConfigWrite({
1017
1515
  plugin,
@@ -1021,8 +1519,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1021
1519
  cwd: context.cwd,
1022
1520
  env: context.env,
1023
1521
  nativeCliRunner: context.nativeCliRunner,
1024
- rebuildRuntime: { ...context, awHomeMutationToken },
1522
+ rebuildRuntime: {
1523
+ ...context,
1524
+ awHomeMutationToken,
1525
+ ...(catalogOperation ? {
1526
+ catalogOperationContext: catalogOperation,
1527
+ expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
1528
+ } : {}),
1529
+ },
1025
1530
  awHomeMutationToken,
1531
+ applyTargets,
1026
1532
  request: { updates: intent.updates, removeKeys: intent.removeKeys },
1027
1533
  })
1028
1534
  : await resetPluginConfigWithHelper({
@@ -1033,8 +1539,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1033
1539
  cwd: context.cwd,
1034
1540
  env: context.env,
1035
1541
  nativeCliRunner: context.nativeCliRunner,
1036
- rebuildRuntime: { ...context, awHomeMutationToken },
1542
+ rebuildRuntime: {
1543
+ ...context,
1544
+ awHomeMutationToken,
1545
+ ...(catalogOperation ? {
1546
+ catalogOperationContext: catalogOperation,
1547
+ expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
1548
+ } : {}),
1549
+ },
1037
1550
  awHomeMutationToken,
1551
+ applyTargets,
1038
1552
  });
1039
1553
  return result;
1040
1554
  },