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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/dist/authoring/assets/aw-runtime-config.mjs +3 -3
  2. package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -1
  3. package/dist/authoring/scripts/add-skill.mjs +3 -17
  4. package/dist/authoring/scripts/add-skill.mjs.map +1 -1
  5. package/dist/authoring/scripts/init-plugin.mjs +1 -0
  6. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  8. package/dist/authoring/scripts/lib/skill-scaffold.d.mts +2 -0
  9. package/dist/authoring/scripts/lib/skill-scaffold.mjs +2 -0
  10. package/dist/authoring/scripts/lib/skill-scaffold.mjs.map +1 -1
  11. package/dist/authoring/scripts/validate-plugin.mjs +95 -698
  12. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  13. package/dist/scripts/aw-skill-eval-materializer.d.ts +2 -0
  14. package/dist/scripts/aw-skill-eval-materializer.js +74 -44
  15. package/dist/scripts/aw-skill-eval-materializer.js.map +1 -1
  16. package/dist/scripts/bundle-plugins.js +10 -2
  17. package/dist/scripts/bundle-plugins.js.map +1 -1
  18. package/dist/scripts/plugin-staging.d.ts +1 -1
  19. package/dist/scripts/plugin-staging.js +1 -1
  20. package/dist/scripts/plugin-staging.js.map +1 -1
  21. package/dist/scripts/test-plugin-scripts.js +31 -31
  22. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  23. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  24. package/dist/src/commands/aw-capabilities.js +2 -0
  25. package/dist/src/commands/aw-capabilities.js.map +1 -1
  26. package/dist/src/commands/console.js +4 -3
  27. package/dist/src/commands/console.js.map +1 -1
  28. package/dist/src/commands/intent-command.d.ts +30 -0
  29. package/dist/src/commands/intent-command.js +164 -0
  30. package/dist/src/commands/intent-command.js.map +1 -0
  31. package/dist/src/commands/operation-plan.d.ts +2 -0
  32. package/dist/src/commands/operation-plan.js +2 -1
  33. package/dist/src/commands/operation-plan.js.map +1 -1
  34. package/dist/src/commands/options.d.ts +3 -11
  35. package/dist/src/commands/options.js +11 -17
  36. package/dist/src/commands/options.js.map +1 -1
  37. package/dist/src/commands/plugin-capabilities.js +27 -33
  38. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  39. package/dist/src/commands/rebuild-command.d.ts +108 -6
  40. package/dist/src/commands/rebuild-command.js +617 -1549
  41. package/dist/src/commands/rebuild-command.js.map +1 -1
  42. package/dist/src/commands/rebuild-integration.d.ts +19 -3
  43. package/dist/src/commands/rebuild-integration.js +500 -459
  44. package/dist/src/commands/rebuild-integration.js.map +1 -1
  45. package/dist/src/commands/rebuild-operations.js +1 -2
  46. package/dist/src/commands/rebuild-operations.js.map +1 -1
  47. package/dist/src/commands/review-inspection.d.ts +2 -2
  48. package/dist/src/commands/review-inspection.js +30 -86
  49. package/dist/src/commands/review-inspection.js.map +1 -1
  50. package/dist/src/commands/review-preparation.js +16 -7
  51. package/dist/src/commands/review-preparation.js.map +1 -1
  52. package/dist/src/commands/skill-eval.d.ts +2 -0
  53. package/dist/src/commands/skill-eval.js +15 -9
  54. package/dist/src/commands/skill-eval.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +38 -5
  56. package/dist/src/commands/status-v6.js +269 -102
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/targets-command.d.ts +27 -0
  59. package/dist/src/commands/targets-command.js +77 -0
  60. package/dist/src/commands/targets-command.js.map +1 -0
  61. package/dist/src/commands/write-commands.js +5 -5
  62. package/dist/src/commands/write-commands.js.map +1 -1
  63. package/dist/src/eval/manifest-v1.d.ts +2 -0
  64. package/dist/src/eval/manifest-v1.js +7 -2
  65. package/dist/src/eval/manifest-v1.js.map +1 -1
  66. package/dist/src/generators/local-plugin.d.ts +2 -1
  67. package/dist/src/generators/local-plugin.js +111 -108
  68. package/dist/src/generators/local-plugin.js.map +1 -1
  69. package/dist/src/index.d.ts +10 -17
  70. package/dist/src/index.js +585 -1349
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/intent/deployment-preferences.d.ts +63 -0
  73. package/dist/src/intent/deployment-preferences.js +170 -0
  74. package/dist/src/intent/deployment-preferences.js.map +1 -0
  75. package/dist/src/intent/diagnostics.d.ts +11 -0
  76. package/dist/src/intent/diagnostics.js +14 -0
  77. package/dist/src/intent/diagnostics.js.map +1 -0
  78. package/dist/src/intent/intent-codec.d.ts +19 -0
  79. package/dist/src/intent/intent-codec.js +117 -0
  80. package/dist/src/intent/intent-codec.js.map +1 -0
  81. package/dist/src/intent/intent-store.d.ts +43 -0
  82. package/dist/src/intent/intent-store.js +118 -0
  83. package/dist/src/intent/intent-store.js.map +1 -0
  84. package/dist/src/intent/scope-context.d.ts +33 -0
  85. package/dist/src/intent/scope-context.js +145 -0
  86. package/dist/src/intent/scope-context.js.map +1 -0
  87. package/dist/src/intent/work-context.d.ts +13 -0
  88. package/dist/src/intent/work-context.js +22 -0
  89. package/dist/src/intent/work-context.js.map +1 -0
  90. package/dist/src/output/command-receipt.js +16 -9
  91. package/dist/src/output/command-receipt.js.map +1 -1
  92. package/dist/src/portable/commands.d.ts +18 -8
  93. package/dist/src/portable/commands.js +46 -562
  94. package/dist/src/portable/commands.js.map +1 -1
  95. package/dist/src/portable/historical-materialization.d.ts +22 -0
  96. package/dist/src/portable/historical-materialization.js +124 -0
  97. package/dist/src/portable/historical-materialization.js.map +1 -0
  98. package/dist/src/portable/materializer.d.ts +8 -0
  99. package/dist/src/portable/materializer.js +75 -44
  100. package/dist/src/portable/materializer.js.map +1 -1
  101. package/dist/src/portable/recovery-layout.d.ts +17 -0
  102. package/dist/src/portable/recovery-layout.js +55 -0
  103. package/dist/src/portable/recovery-layout.js.map +1 -0
  104. package/dist/src/portable/recovery.d.ts +143 -0
  105. package/dist/src/portable/recovery.js +339 -0
  106. package/dist/src/portable/recovery.js.map +1 -0
  107. package/dist/src/portable/writer.d.ts +64 -0
  108. package/dist/src/portable/writer.js +316 -0
  109. package/dist/src/portable/writer.js.map +1 -0
  110. package/dist/src/recovery/coordinator.d.ts +137 -0
  111. package/dist/src/recovery/coordinator.js +1198 -0
  112. package/dist/src/recovery/coordinator.js.map +1 -0
  113. package/dist/src/recovery/demand.d.ts +44 -0
  114. package/dist/src/recovery/demand.js +178 -0
  115. package/dist/src/recovery/demand.js.map +1 -0
  116. package/dist/src/recovery/joint-coordinator.d.ts +24 -0
  117. package/dist/src/recovery/joint-coordinator.js +770 -0
  118. package/dist/src/recovery/joint-coordinator.js.map +1 -0
  119. package/dist/src/recovery/joint-demand.d.ts +68 -0
  120. package/dist/src/recovery/joint-demand.js +166 -0
  121. package/dist/src/recovery/joint-demand.js.map +1 -0
  122. package/dist/src/recovery/receipt.d.ts +81 -0
  123. package/dist/src/recovery/receipt.js +2 -0
  124. package/dist/src/recovery/receipt.js.map +1 -0
  125. package/dist/src/recovery/retirement.d.ts +45 -0
  126. package/dist/src/recovery/retirement.js +366 -0
  127. package/dist/src/recovery/retirement.js.map +1 -0
  128. package/dist/src/recovery/status.d.ts +108 -0
  129. package/dist/src/recovery/status.js +417 -0
  130. package/dist/src/recovery/status.js.map +1 -0
  131. package/dist/src/recovery/types.d.ts +86 -0
  132. package/dist/src/recovery/types.js +2 -0
  133. package/dist/src/recovery/types.js.map +1 -0
  134. package/dist/src/review/review-profile.d.ts +2 -1
  135. package/dist/src/review/review-profile.js +15 -15
  136. package/dist/src/review/review-profile.js.map +1 -1
  137. package/dist/src/review/review-selection.d.ts +1 -2
  138. package/dist/src/review/review-selection.js +40 -114
  139. package/dist/src/review/review-selection.js.map +1 -1
  140. package/dist/src/review/review-types.d.ts +4 -4
  141. package/dist/src/review/review-types.js.map +1 -1
  142. package/dist/src/review/skill-materialization.js +15 -6
  143. package/dist/src/review/skill-materialization.js.map +1 -1
  144. package/dist/src/review/source-binding.d.ts +14 -4
  145. package/dist/src/review/source-binding.js +62 -117
  146. package/dist/src/review/source-binding.js.map +1 -1
  147. package/dist/src/tui/agent-management.d.ts +23 -0
  148. package/dist/src/tui/agent-management.js +217 -0
  149. package/dist/src/tui/agent-management.js.map +1 -0
  150. package/dist/src/tui/agent-settings.d.ts +44 -0
  151. package/dist/src/tui/agent-settings.js +98 -0
  152. package/dist/src/tui/agent-settings.js.map +1 -0
  153. package/dist/src/tui/components.d.ts +3 -0
  154. package/dist/src/tui/components.js +10 -1
  155. package/dist/src/tui/components.js.map +1 -1
  156. package/dist/src/tui/config-references.d.ts +6 -3
  157. package/dist/src/tui/config-references.js +17 -12
  158. package/dist/src/tui/config-references.js.map +1 -1
  159. package/dist/src/tui/console-app-v4.d.ts +1 -2
  160. package/dist/src/tui/console-app-v4.js +1533 -1047
  161. package/dist/src/tui/console-app-v4.js.map +1 -1
  162. package/dist/src/tui/controller-runtime.d.ts +2 -1
  163. package/dist/src/tui/controller-runtime.js +457 -1634
  164. package/dist/src/tui/controller-runtime.js.map +1 -1
  165. package/dist/src/tui/controller.d.ts +131 -81
  166. package/dist/src/tui/controller.js +307 -227
  167. package/dist/src/tui/controller.js.map +1 -1
  168. package/dist/src/tui/entity-cards.d.ts +2 -2
  169. package/dist/src/tui/entity-cards.js +98 -20
  170. package/dist/src/tui/entity-cards.js.map +1 -1
  171. package/dist/src/tui/glossary.d.ts +2 -0
  172. package/dist/src/tui/glossary.js +20 -0
  173. package/dist/src/tui/glossary.js.map +1 -1
  174. package/dist/src/tui/model.d.ts +28 -10
  175. package/dist/src/tui/model.js +41 -41
  176. package/dist/src/tui/model.js.map +1 -1
  177. package/dist/src/tui/operation-review-planner.d.ts +2 -0
  178. package/dist/src/tui/operation-review-planner.js +58 -122
  179. package/dist/src/tui/operation-review-planner.js.map +1 -1
  180. package/dist/src/tui/plugin-content.js +7 -4
  181. package/dist/src/tui/plugin-content.js.map +1 -1
  182. package/dist/src/tui/plugin-detail.d.ts +22 -3
  183. package/dist/src/tui/plugin-detail.js +113 -26
  184. package/dist/src/tui/plugin-detail.js.map +1 -1
  185. package/dist/src/tui/reader.d.ts +1 -0
  186. package/dist/src/tui/reader.js +3 -1
  187. package/dist/src/tui/reader.js.map +1 -1
  188. package/dist/src/tui/router.d.ts +23 -33
  189. package/dist/src/tui/router.js.map +1 -1
  190. package/dist/src/tui/scoped-flow.d.ts +45 -0
  191. package/dist/src/tui/scoped-flow.js +96 -0
  192. package/dist/src/tui/scoped-flow.js.map +1 -0
  193. package/dist/src/tui/scoped-inventory.d.ts +122 -0
  194. package/dist/src/tui/scoped-inventory.js +225 -0
  195. package/dist/src/tui/scoped-inventory.js.map +1 -0
  196. package/dist/src/tui/screen-registry.d.ts +25 -25
  197. package/dist/src/tui/screen-registry.js +3 -7
  198. package/dist/src/tui/screen-registry.js.map +1 -1
  199. package/dist/src/tui/session-console-app.js +1 -1
  200. package/dist/src/tui/session-console-app.js.map +1 -1
  201. package/dist/src/tui/session-host.js +12 -2
  202. package/dist/src/tui/session-host.js.map +1 -1
  203. package/dist/src/types.d.ts +15 -12
  204. package/dist/src/types.js.map +1 -1
  205. package/dist/src/utils/agent-version.js +2 -0
  206. package/dist/src/utils/agent-version.js.map +1 -1
  207. package/dist/src/utils/artifact-plan.js +13 -6
  208. package/dist/src/utils/artifact-plan.js.map +1 -1
  209. package/dist/src/utils/artifact-renderer.d.ts +6 -3
  210. package/dist/src/utils/artifact-renderer.js +27 -32
  211. package/dist/src/utils/artifact-renderer.js.map +1 -1
  212. package/dist/src/utils/artifact-request.d.ts +1 -0
  213. package/dist/src/utils/artifact-request.js +22 -12
  214. package/dist/src/utils/artifact-request.js.map +1 -1
  215. package/dist/src/utils/artifact-sources.d.ts +2 -2
  216. package/dist/src/utils/bundle-layout.js +18 -16
  217. package/dist/src/utils/bundle-layout.js.map +1 -1
  218. package/dist/src/utils/catalog-digests.js +2 -0
  219. package/dist/src/utils/catalog-digests.js.map +1 -1
  220. package/dist/src/utils/catalog-runtime-projection.d.ts +4 -3
  221. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  222. package/dist/src/utils/catalog-source-references.d.ts +5 -0
  223. package/dist/src/utils/catalog-source-references.js +42 -57
  224. package/dist/src/utils/catalog-source-references.js.map +1 -1
  225. package/dist/src/utils/claude-native-render-receipt.d.ts +14 -0
  226. package/dist/src/utils/claude-native-render-receipt.js +26 -0
  227. package/dist/src/utils/claude-native-render-receipt.js.map +1 -0
  228. package/dist/src/utils/claude-skills-root.js +14 -3
  229. package/dist/src/utils/claude-skills-root.js.map +1 -1
  230. package/dist/src/utils/codex-manager.js +6 -12
  231. package/dist/src/utils/codex-manager.js.map +1 -1
  232. package/dist/src/utils/codex-native-observation.js +29 -13
  233. package/dist/src/utils/codex-native-observation.js.map +1 -1
  234. package/dist/src/utils/codex-reconciler.d.ts +2 -5
  235. package/dist/src/utils/codex-reconciler.js +25 -1
  236. package/dist/src/utils/codex-reconciler.js.map +1 -1
  237. package/dist/src/utils/codex-surface-analysis.js +5 -3
  238. package/dist/src/utils/codex-surface-analysis.js.map +1 -1
  239. package/dist/src/utils/local-marketplace.d.ts +14 -2
  240. package/dist/src/utils/local-marketplace.js +209 -42
  241. package/dist/src/utils/local-marketplace.js.map +1 -1
  242. package/dist/src/utils/managed-command-evidence.js +1 -1
  243. package/dist/src/utils/managed-command-evidence.js.map +1 -1
  244. package/dist/src/utils/managed-command-materialization.d.ts +4 -0
  245. package/dist/src/utils/managed-command-materialization.js +15 -6
  246. package/dist/src/utils/managed-command-materialization.js.map +1 -1
  247. package/dist/src/utils/managed-command-store.d.ts +15 -0
  248. package/dist/src/utils/managed-command-store.js +73 -8
  249. package/dist/src/utils/managed-command-store.js.map +1 -1
  250. package/dist/src/utils/native-cli.d.ts +2 -0
  251. package/dist/src/utils/native-cli.js +22 -0
  252. package/dist/src/utils/native-cli.js.map +1 -1
  253. package/dist/src/utils/native-demand.d.ts +10 -0
  254. package/dist/src/utils/native-demand.js +4 -0
  255. package/dist/src/utils/native-demand.js.map +1 -0
  256. package/dist/src/utils/ownership.js +2 -0
  257. package/dist/src/utils/ownership.js.map +1 -1
  258. package/dist/src/utils/paths.js +1 -1
  259. package/dist/src/utils/paths.js.map +1 -1
  260. package/dist/src/utils/platform-support.d.ts +1 -1
  261. package/dist/src/utils/platform-support.js +1 -1
  262. package/dist/src/utils/platform-support.js.map +1 -1
  263. package/dist/src/utils/plugin-config-impact.d.ts +3 -0
  264. package/dist/src/utils/plugin-config-impact.js +115 -273
  265. package/dist/src/utils/plugin-config-impact.js.map +1 -1
  266. package/dist/src/utils/plugin-config-references.d.ts +11 -2
  267. package/dist/src/utils/plugin-config-references.js +85 -90
  268. package/dist/src/utils/plugin-config-references.js.map +1 -1
  269. package/dist/src/utils/plugin-config-write.d.ts +58 -1
  270. package/dist/src/utils/plugin-config-write.js +756 -809
  271. package/dist/src/utils/plugin-config-write.js.map +1 -1
  272. package/dist/src/utils/plugin-config.d.ts +47 -46
  273. package/dist/src/utils/plugin-config.js +292 -833
  274. package/dist/src/utils/plugin-config.js.map +1 -1
  275. package/dist/src/utils/plugin-scripts.d.ts +11 -6
  276. package/dist/src/utils/plugin-scripts.js +52 -35
  277. package/dist/src/utils/plugin-scripts.js.map +1 -1
  278. package/dist/src/utils/runtime-config.js +5 -4
  279. package/dist/src/utils/runtime-config.js.map +1 -1
  280. package/dist/src/utils/runtime-skill-closure.d.ts +2 -2
  281. package/dist/src/utils/runtime-skill-closure.js.map +1 -1
  282. package/dist/src/utils/state-paths.d.ts +8 -3
  283. package/dist/src/utils/state-paths.js +53 -16
  284. package/dist/src/utils/state-paths.js.map +1 -1
  285. package/dist/src/utils/windows-npm-shim.d.ts +2 -0
  286. package/dist/src/utils/windows-npm-shim.js +5 -7
  287. package/dist/src/utils/windows-npm-shim.js.map +1 -1
  288. package/dist/vitest.config.js +1 -0
  289. package/dist/vitest.config.js.map +1 -1
  290. package/package.json +3 -3
  291. package/dist/src/commands/sync-command.d.ts +0 -40
  292. package/dist/src/commands/sync-command.js +0 -254
  293. package/dist/src/commands/sync-command.js.map +0 -1
  294. package/dist/src/portable/skills-cli.d.ts +0 -51
  295. package/dist/src/portable/skills-cli.js +0 -202
  296. package/dist/src/portable/skills-cli.js.map +0 -1
  297. package/dist/src/tui/project-inventory.d.ts +0 -68
  298. package/dist/src/tui/project-inventory.js +0 -203
  299. package/dist/src/tui/project-inventory.js.map +0 -1
  300. package/dist/src/utils/plugin-intent-state.d.ts +0 -49
  301. package/dist/src/utils/plugin-intent-state.js +0 -320
  302. package/dist/src/utils/plugin-intent-state.js.map +0 -1
@@ -1,29 +1,26 @@
1
- import { getPluginConfigReferenceChoices } from '@lesliechan721/aw-plugin-core';
1
+ import { readAgentSettings, previewAgentSettings, saveAgentSettings } from './agent-settings.js';
2
+ import { compilePluginConfigDefinition, getPluginConfigReferenceChoices, PLATFORM_TARGETS, resolveSupportedPlatforms } from '@lesliechan721/aw-plugin-core';
2
3
  import { configReferenceKey, configReferenceScalars } from './config-references.js';
3
4
  import crypto from 'node:crypto';
4
5
  import path from 'node:path';
5
- import { flattenStatusTargets, statusCommandV8, statusTargetActivation, statusTargetIntent, statusTargetOwnershipState, } from '../commands/status-v6.js';
6
+ import { resolveWorkContext } from '../intent/work-context.js';
7
+ import { flattenStatusTargets, statusCommandV8, statusTargetHasLiveConfigConsumer, } from '../commands/status-v6.js';
6
8
  import { addCatalogSourceCommand, listCatalogSources, preflightCatalogSourceMutation, removeCatalogSourceCommand, setPrimaryCatalogSourceCommand, updateCatalogSourceCommand, } from '../commands/catalog-source.js';
7
- import { cleanupWriteCommand, failedWriteCommandResult, installWriteCommand, mergeWriteCommandResults, reinstallWriteCommand, syncWriteCommand, taskWriteCommand, uninstallWriteCommand, } from '../commands/write-commands.js';
8
- import { resolveSyncIntentSelection } from '../commands/sync-command.js';
9
9
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, } from '../commands/options.js';
10
10
  import { readCliPackageInfo } from '../utils/local-marketplace.js';
11
- import { createCodexNativeObservationSession, } from '../utils/codex-native-observation.js';
12
- import { prepareNativeCliExecution } from '../utils/native-cli.js';
13
11
  import { readCatalogSourcesConfigSnapshot, recoverInvalidCatalogSourcesConfig, } from '../utils/catalog-sources.js';
14
12
  import { withResolvedCatalogPlugin, withResolvedMultiSourceCatalog, formatPluginId, parsePluginId, } from '../utils/multi-source-catalog.js';
15
- import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, listNamespacedPluginConfigStoreIdentities, readPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, resolveEffectivePluginConfigFromStores, resolvePluginConfigReferenceContext, resolveAwProjectRoot, resolvePluginConfigStorePath, } from '../utils/plugin-config.js';
16
- import { assertProjectSharedConfigPathVisible } from '../utils/project-boundary.js';
17
- import { calculatePluginIntentDigest, readPluginIntentState, } from '../utils/plugin-intent-state.js';
18
- import { applyPluginConfigWrite, replayConfigRuntimeWrites, resolveConfigPreApplyAuthorization, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
13
+ import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, listNamespacedPluginConfigStoreIdentities, readPluginConfigStore, replacePluginConfigStoreLayer, resolveConfigRead, resolveEffectivePluginConfig, resolveEffectivePluginConfigFromStores, resolvePluginConfigReferenceContext, } from '../utils/plugin-config.js';
14
+ import { readRecoveryStatus } from '../recovery/status.js';
15
+ import { activateRecoverySourcesAndReprepare, prepareRecovery, executeRecovery, } from '../recovery/coordinator.js';
16
+ import { prepareRetire, executeRetire } from '../recovery/retirement.js';
17
+ import { prepareTargetsPlan, executeTargetsPlan, prepareIntentPlan, executeIntentPlan } from './scoped-flow.js';
18
+ import { applyPluginConfigWrite, applyPluginConfig, preparePluginConfigApply, disposePluginConfigApply, resolveConfigPreApplyAuthorization, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
19
19
  import { analyzePluginConfigMutationImpact, discoverPluginConfigManagedCommandPackages, pluginConfigTargetRenderScopes, pluginSharedNativeConfigTargets } from '../utils/plugin-config-impact.js';
20
- import { collectPluginConfigReferenceEvidence, resolvePluginConfigReferenceConsumers, resolvePluginConfigReferencePlugins } from '../utils/plugin-config-references.js';
21
- import { createBatchIntentCompatiblePlanFingerprint } from '../commands/operation-plan.js';
20
+ import { collectPluginConfigReferenceEvidence, pluginConfigHasExternalReferences, resolvePluginConfigReferenceConsumers, resolvePluginConfigReferencePlugins } from '../utils/plugin-config-references.js';
22
21
  import { inventoryCatalogSourceReferences } from '../utils/catalog-source-references.js';
23
22
  import { operationEvidenceMatches, operationEvidenceFingerprint, TuiConfigInvalidStoreError, TuiController, TuiOperationBlockedError, } from './controller.js';
24
- import { buildConfigGroups, buildConfigPage, buildTuiViewModel, hasActualScopeEvidence, isDirectCodexVersionUpgrade, projectPlatformPlacement, } from './model.js';
25
- import { buildPluginWriteTaskCandidates, buildPluginWriteTaskDraft, evaluatePluginWriteTaskRestoreVersion, pluginWriteTaskTargetKey, } from './plugin-write-task.js';
26
- import { projectExecutionDiagnostics, projectThrownOperationDiagnostic } from './diagnostic-projection.js';
23
+ import { buildConfigPage, hasActualScopeEvidence, projectPlatformPlacement, } from './model.js';
27
24
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
28
25
  import { calculateCatalogSourceConfigFingerprint, calculateCatalogSourceIdentity, } from '../utils/catalog-source-identity.js';
29
26
  import { checkAwCapabilitySupport, readPluginManifest } from '../utils/manifest.js';
@@ -32,14 +29,13 @@ import { IMPLEMENTED_AW_CAPABILITIES } from '../commands/aw-capabilities.js';
32
29
  import { calculateCatalogGenerationSetDigest, resolveActiveCatalogGeneration, } from '../utils/catalog-generation-resolver.js';
33
30
  import { resolveCatalogOperationContext, resolvePartialCatalogOperationContext, selectCatalogOperationContext, } from '../utils/catalog-operation-context.js';
34
31
  import { createCatalogStoreKey, readCatalogStoreEntry, } from '../utils/catalog-generation-store.js';
35
- import { activateCatalogSourceGeneration, compareCatalogGenerationFreshness, probeCatalogSourceFreshness, probeCatalogSourceGeneration, probeCatalogSourceRepairGeneration, recoverCatalogGenerationStateAndActivate, repairCatalogSourceGeneration, replaceDamagedCatalogSourceGeneration, syncCatalogSourceGeneration, } from '../utils/catalog-generation-service.js';
32
+ import { activateCatalogSourceGeneration, compareCatalogGenerationFreshness, probeCatalogSourceFreshness, probeCatalogSourceGeneration, probeCatalogSourceRepairGeneration, recoverCatalogGenerationStateAndActivate, repairCatalogSourceGeneration, replaceDamagedCatalogSourceGeneration, } from '../utils/catalog-generation-service.js';
36
33
  import { calculateCatalogSourceImpactDigest, inspectCatalogSourceImpact, } from '../utils/catalog-source-impact.js';
37
34
  import { readCatalogGenerationState } from '../utils/catalog-generation-state.js';
38
35
  import { CatalogGenerationError } from '../utils/catalog-generation-error.js';
39
36
  import { CatalogProviderCompatibilityError } from '../utils/catalog-public-error.js';
40
37
  import { loadCatalogGenerationViewModels } from './catalog-generation.js';
41
38
  import { executePortableRetry, installPortableSkills, preparePortableInstall, preparePortableRetry, PortableCommandError, recheckPortableInstall, } from '../portable/commands.js';
42
- import { MANAGED_CODEX_PACKAGE_NAME, MANAGED_CODEX_VERSION, } from '../utils/codex-manager.js';
43
39
  import { projectPluginContentOverview, } from './plugin-content.js';
44
40
  import { executeCatalogStoreTask, inspectCatalogStoreTask, prepareCatalogStoreTask, } from './catalog-store-task.js';
45
41
  function reusableCandidateEvidenceIsComplete(candidate) {
@@ -70,7 +66,7 @@ async function loadPortablePluginIds(options) {
70
66
  ? item.resolution.manifest.entries.flatMap((entry) => {
71
67
  const support = checkAwCapabilitySupport(entry.requires.aw.capabilities ?? [], IMPLEMENTED_AW_CAPABILITIES);
72
68
  return support.supported
73
- ? [readPluginManifest(path.join(item.resolution.rootDir, 'plugins', entry.name), { mode: 'bundle' }).then((plugin) => plugin.manifest.distributions?.agentSkills
69
+ ? [readPluginManifest(path.join(item.resolution.rootDir, 'plugins', entry.name), { mode: 'bundle' }).then((plugin) => [...resolveSupportedPlatforms(plugin.manifest)].some((target) => target !== 'codex' && target !== 'claude')
74
70
  ? formatPluginId({
75
71
  sourceId: item.resolution.sourceId,
76
72
  pluginName: entry.name,
@@ -88,6 +84,10 @@ export function setTuiControllerRuntimeTestHooksForTests(hooks) {
88
84
  function digest(value) {
89
85
  return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
90
86
  }
87
+ function configSchemaIdentity(plugin, manifestDigest, platform) {
88
+ return digest({ manifestDigest, profile: platform ?? 'common',
89
+ definition: compilePluginConfigDefinition(plugin.manifest.config, platform) });
90
+ }
91
91
  export function statusTargetFingerprint(targets, revisions = []) {
92
92
  return digest({
93
93
  targets: targets.map((target) => ({
@@ -115,41 +115,47 @@ export function statusTargetFingerprint(targets, revisions = []) {
115
115
  .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en')),
116
116
  });
117
117
  }
118
- export function configAffectedStatusTargets(targets, pluginId, configScope, consumerPluginIds = []) {
118
+ export function configAffectedStatusTargets(targets, pluginId, configContext, consumerPluginIds = [], platform) {
119
119
  const affected = new Map();
120
120
  const selected = new Set([pluginId, ...consumerPluginIds]);
121
121
  for (const target of flattenStatusTargets(targets)) {
122
- const intent = statusTargetIntent(target);
123
- if (!selected.has(target.pluginId) || !intent.enabled)
122
+ if (!selected.has(target.pluginId) || !statusTargetHasLiveConfigConsumer(target))
124
123
  continue;
125
- for (const scope of intent.scopes) {
126
- if (configScope === 'project' && scope !== 'project')
127
- continue;
128
- affected.set(`${target.pluginId}\0${target.platform}\0${scope}`, { ...target, scope });
129
- }
124
+ if (configContext === 'user' && target.scope !== 'user')
125
+ continue;
126
+ if (platform && target.platform !== platform)
127
+ continue;
128
+ affected.set(`${target.pluginId}\0${target.platform}\0${target.scope}`, target);
130
129
  }
131
130
  return [...affected.values()]
132
131
  .sort((left, right) => `${left.pluginId}\0${left.platform}\0${left.scope}`
133
132
  .localeCompare(`${right.pluginId}\0${right.platform}\0${right.scope}`, 'en'));
134
133
  }
135
- async function configRetrySharedNativeRenderTargets(plugin, targets, env) {
136
- const plugins = await resolvePluginConfigReferenceConsumers({ plugin, env });
134
+ function configNeedsReferencePlugins(plugin) {
135
+ return pluginConfigHasExternalReferences(plugin.manifest)
136
+ || (plugin.manifest.config !== undefined && [undefined, ...PLATFORM_TARGETS].some((platform) => Object.values(compilePluginConfigDefinition(plugin.manifest.config, platform).fields)
137
+ .some((field) => field.type === 'array' && field.items.type === 'object')));
138
+ }
139
+ async function configRetrySharedNativeRenderTargets(plugin, targets, env, referencePlugins) {
140
+ const plugins = await resolvePluginConfigReferenceConsumers({ plugin, env, plugins: referencePlugins });
137
141
  return plugins.flatMap((consumer) => pluginSharedNativeConfigTargets(consumer).filter((shared) => targets.some((target) => (target.pluginName ?? plugin.manifest.name) === consumer.manifest.name
138
142
  && target.platform === (shared.platform ?? 'codex'))));
139
143
  }
140
144
  async function configRetryStoreDigest(options) {
141
145
  const plugins = await resolvePluginConfigReferencePlugins({ sourceId: options.plugin.sourceId,
142
- sourceIdentity: options.plugin.sourceIdentity, logicalIdentity: options.plugin.catalogLogicalIdentity, env: options.env });
146
+ sourceIdentity: options.plugin.sourceIdentity, logicalIdentity: options.plugin.catalogLogicalIdentity,
147
+ env: options.env, plugins: options.plugins });
143
148
  const byName = new Map([...plugins, options.plugin].map((plugin) => [plugin.manifest.name, plugin]));
144
- const profiles = [{ pluginName: options.plugin.manifest.name, scope: options.scope },
149
+ const profiles = [{ pluginName: options.plugin.manifest.name, scope: options.scope, platform: options.platform },
145
150
  ...options.targets.flatMap((target) => {
146
151
  const plugin = byName.get(target.pluginName);
147
152
  if (!plugin)
148
153
  throw operationReviewStale('重试目标的插件定义已变化。');
149
- return pluginConfigTargetRenderScopes(plugin, target).map((scope) => ({ pluginName: target.pluginName, scope }));
154
+ return pluginConfigTargetRenderScopes(plugin, target).map((scope) => ({ pluginName: target.pluginName, scope, platform: target.platform }));
150
155
  }),
151
- ...options.commandPackages.map(({ producer }) => ({ pluginName: producer.pluginName, scope: producer.scope })),
152
- ...options.preApplyAuthorization.map(({ pluginName, scope }) => ({ pluginName, scope }))];
156
+ ...options.commandPackages.map(({ producer }) => ({ pluginName: producer.pluginName, scope: producer.scope,
157
+ platform: producer.platform ?? undefined })),
158
+ ...options.preApplyAuthorization.map(({ pluginName, scope }) => ({ pluginName, scope, platform: options.platform }))];
153
159
  return digest(await collectPluginConfigReferenceEvidence({ ...options, profiles, plugins }));
154
160
  }
155
161
  function configImpactVisibleTargets(pluginName, targets) {
@@ -173,7 +179,9 @@ function configSaveBatchGroupIntent(intent, group) {
173
179
  return {
174
180
  kind: 'configSave',
175
181
  pluginId: intent.pluginId,
176
- scope: group.scope,
182
+ scope: 'user',
183
+ platform: group.platform,
184
+ context: intent.context ?? 'user',
177
185
  apply: intent.apply,
178
186
  ...(intent.scriptTimeoutMs === undefined ? {} : { scriptTimeoutMs: intent.scriptTimeoutMs }),
179
187
  ...(group.updates ? { updates: group.updates } : {}),
@@ -184,14 +192,11 @@ function configSaveBatchGroupIntent(intent, group) {
184
192
  };
185
193
  }
186
194
  function assertConfigSaveBatchIntent(intent) {
187
- const order = { project: 0, user: 1 };
188
- if (intent.groups.length === 0 || intent.groups.length > 2
189
- || new Set(intent.groups.map((group) => group.scope)).size !== intent.groups.length
190
- || intent.groups.some((group, index) => index > 0
191
- && order[intent.groups[index - 1].scope] >= order[group.scope])
195
+ if (intent.groups.length === 0 || intent.groups.length > PLATFORM_TARGETS.length + 1
196
+ || new Set(intent.groups.map((group) => group.platform ?? 'common')).size !== intent.groups.length
192
197
  || intent.groups.some((group) => Object.keys(group.updates ?? {}).length === 0
193
198
  && (group.removeKeys?.length ?? 0) === 0)) {
194
- throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含按当前项目、个人默认排序的唯一非空目标。');
199
+ throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含唯一且非空的公共或平台配置。');
195
200
  }
196
201
  }
197
202
  function operationReviewStale(message, diagnostics) {
@@ -221,28 +226,15 @@ async function configLayerStoreDigest(options) {
221
226
  ? { logicalIdentity: options.source.logicalIdentity }
222
227
  : {}),
223
228
  pluginName: options.pluginName,
224
- scope: options.scope,
225
- cwd: options.cwd,
226
- env: options.env,
227
- });
228
- return digest({ exists: store.exists, rawDigest: store.rawDigest });
229
- }
230
- async function inspectConfigShareability(options) {
231
- if (options.scope !== 'project')
232
- return undefined;
233
- const targetPath = resolvePluginConfigStorePath({
234
- sourceId: options.sourceId,
235
- pluginName: options.pluginName,
236
- scope: 'project',
237
- cwd: options.cwd,
238
- env: options.env,
239
- });
240
- const visibility = await assertProjectSharedConfigPathVisible({
229
+ scope: 'user',
230
+ platform: options.platform,
241
231
  cwd: options.cwd,
242
- targetPath,
243
232
  env: options.env,
244
233
  });
245
- return visibility.state === 'unverified' ? 'unverified' : 'verified';
234
+ return options.profileOnly
235
+ ? digest({ profile: options.platform ?? 'common', state: store.state === 'invalid' ? 'invalid' : 'ready',
236
+ values: store.values, ...(store.state === 'invalid' ? { rawDigest: store.rawDigest } : {}) })
237
+ : digest({ exists: store.exists, rawDigest: store.rawDigest });
246
238
  }
247
239
  function assertSourceEvidenceMatches(expected, actual) {
248
240
  if (actual.sourceId !== expected.sourceId
@@ -255,21 +247,9 @@ function assertSourceEvidenceMatches(expected, actual) {
255
247
  throw operationReviewStale('确认后来源内容发生变化。');
256
248
  }
257
249
  }
258
- function writeReviewIsStale(result) {
259
- return result.targetResults.some((target) => target.errorCode === 'operationReviewStale'
260
- || target.errorCode === 'operationPlanStale');
261
- }
262
- function throwIfWriteReviewStale(result) {
263
- if (writeReviewIsStale(result)) {
264
- throw operationReviewStale('确认后实际写入计划发生变化。');
265
- }
266
- }
267
250
  const SOURCE_PROGRESS_PHASES = {
268
251
  savingConfig: '保存来源配置',
269
252
  };
270
- function pluginIntent(intent) {
271
- return 'pluginId' in intent;
272
- }
273
253
  function reviewedScriptTimeoutMs(intent) {
274
254
  if (intent.kind === 'pluginWriteTask') {
275
255
  return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
@@ -284,138 +264,6 @@ function reviewedScriptTimeoutMs(intent) {
284
264
  }
285
265
  return undefined;
286
266
  }
287
- function selectedBatchGroups(intent) {
288
- if (intent.selection.kind !== 'selected' && intent.selection.kind !== 'targets') {
289
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部同步必须从项目 intent roots 解析。');
290
- }
291
- const rawTargets = intent.selection.kind === 'targets'
292
- ? intent.selection.targets
293
- : intent.selection.pluginIds.map((pluginId) => ({
294
- pluginId, platform: intent.platform, scope: intent.scope,
295
- }));
296
- const normalizedTargets = rawTargets.map((target) => {
297
- if ((target.platform !== 'codex' && target.platform !== 'claude' && target.platform !== 'both')
298
- || (target.scope !== 'project' && target.scope !== 'user')
299
- || (intent.selection.kind === 'targets' && target.platform === 'both')) {
300
- throw new TuiOperationBlockedError('batchTargetInvalid', '批量精确目标必须使用有效 scope 和单个平台。');
301
- }
302
- const pluginId = target.pluginId;
303
- const parsed = parsePluginId(pluginId);
304
- if (!parsed.sourceId) {
305
- throw new TuiOperationBlockedError('invalidPluginId', '批量操作必须使用 canonical pluginId。');
306
- }
307
- const canonicalPluginId = formatPluginId({ sourceId: parsed.sourceId, pluginName: parsed.pluginName });
308
- if (intent.selection.kind === 'targets' && canonicalPluginId !== pluginId) {
309
- throw new TuiOperationBlockedError('invalidPluginId', '批量精确目标必须使用 canonical pluginId。');
310
- }
311
- return {
312
- pluginId: canonicalPluginId,
313
- platform: target.platform,
314
- scope: target.scope,
315
- };
316
- }).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
317
- .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'));
318
- const uniqueTargets = [...new Map(normalizedTargets.map((target) => [
319
- [target.pluginId, target.platform, target.scope].join('\0'), target,
320
- ])).values()];
321
- const requestedPluginIds = [...new Set(uniqueTargets.map((target) => target.pluginId))];
322
- if (requestedPluginIds.length === 0) {
323
- throw new TuiOperationBlockedError('syncSelectionRequired', '批量操作至少需要一个插件。');
324
- }
325
- const groups = new Map();
326
- for (const target of uniqueTargets) {
327
- const pluginId = target.pluginId;
328
- const sourceId = parsePluginId(pluginId).sourceId;
329
- const groupKey = intent.action === 'cleanup'
330
- ? ['cleanup', target.platform, target.scope].join('\0')
331
- : [sourceId, target.platform, target.scope].join('\0');
332
- const group = groups.get(groupKey) ?? {
333
- groupId: groupKey,
334
- sourceId: intent.action === 'cleanup' ? 'offline' : sourceId,
335
- platform: target.platform,
336
- scope: target.scope,
337
- requestedPluginIds: [],
338
- };
339
- group.requestedPluginIds.push(pluginId);
340
- groups.set(groupKey, group);
341
- }
342
- return { requestedPluginIds, groups: [...groups.values()] };
343
- }
344
- async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
345
- if (intent.selection.kind === 'selected' || intent.selection.kind === 'targets') {
346
- return selectedBatchGroups(intent);
347
- }
348
- if (intent.action !== 'sync' || intent.scope !== 'project') {
349
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部操作只支持 project scope sync。');
350
- }
351
- const state = await readPluginIntentState({ scope: 'project', cwd, env });
352
- const projectIntentDigest = calculatePluginIntentDigest(state);
353
- if (intent.selection.kind === 'currentProjectIntentTargets') {
354
- if (intent.selection.targets.length === 0
355
- || intent.selection.targets.some((target) => target.scope !== 'project')) {
356
- throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须是非空 project targets。');
357
- }
358
- const exact = selectedBatchGroups({
359
- ...intent,
360
- selection: { kind: 'targets', targets: intent.selection.targets },
361
- });
362
- const declaredPlatforms = new Map(state.roots.map((root) => [
363
- root.pluginId,
364
- new Set(root.platforms),
365
- ]));
366
- if (intent.selection.targets.some((target) => !declaredPlatforms.get(target.pluginId)?.has(target.platform))) {
367
- throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须属于完整 project intent roots 声明。');
368
- }
369
- return { ...exact, projectIntentDigest };
370
- }
371
- const selection = resolveSyncIntentSelection({
372
- roots: state.roots,
373
- selectors: frozenRequestedPluginIds ?? [],
374
- all: frozenRequestedPluginIds === undefined,
375
- platform: intent.platform,
376
- });
377
- if (selection.requestedPluginIds.length === 0) {
378
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目没有可同步的 intent roots。');
379
- }
380
- return {
381
- requestedPluginIds: selection.requestedPluginIds,
382
- projectIntentDigest,
383
- groups: selection.groups.map((group) => ({
384
- groupId: `${group.sourceId}\0${group.platform}\0${intent.scope}`,
385
- sourceId: group.sourceId,
386
- platform: group.platform,
387
- scope: intent.scope,
388
- requestedPluginIds: group.pluginIds,
389
- })),
390
- };
391
- }
392
- async function runBatchWriteGroup(options) {
393
- const dependencyScopeOverrides = options.intent.action === 'install'
394
- ? options.intent.dependencyScopeOverrides?.filter((override) => parsePluginId(override.pluginId).sourceId === options.group.sourceId
395
- && (options.group.platform === 'both' || override.platform === options.group.platform))
396
- : undefined;
397
- const commandOptions = {
398
- cwd: options.cwd,
399
- ...(dependencyScopeOverrides?.length ? { dependencyScopeOverrides } : {}),
400
- platform: options.group.platform,
401
- scope: options.group.scope,
402
- ...(reviewedScriptTimeoutMs(options.intent) === undefined
403
- ? {}
404
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(options.intent) }),
405
- };
406
- switch (options.intent.action) {
407
- case 'install':
408
- return installWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
409
- case 'sync':
410
- return syncWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
411
- case 'reinstall':
412
- return reinstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
413
- case 'uninstall':
414
- return uninstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
415
- case 'cleanup':
416
- return cleanupWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
417
- }
418
- }
419
267
  async function loadDependencyOverviews(options) {
420
268
  const graph = await withResolvedMultiSourceCatalog({
421
269
  cliVersion: options.cliVersion,
@@ -708,8 +556,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
708
556
  };
709
557
  };
710
558
  const catalogOperationByEvidence = new WeakMap();
711
- const taskCatalogOperationsByEvidence = new WeakMap();
712
- const codexManagerByEvidence = new WeakMap();
713
559
  let sessionCatalogOperationContext;
714
560
  let sessionStatusResult;
715
561
  const sessionContextForPlugin = (pluginId) => {
@@ -719,33 +565,42 @@ export function createTuiControllerDependencies(context, progressFeed, options =
719
565
  return selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId]);
720
566
  };
721
567
  const readConfigStoresForTui = async (config) => {
722
- const scopes = config.scope === 'user' ? ['user'] : ['user', 'project'];
723
- const stores = await Promise.all(scopes.map((scope) => readPluginConfigStore({
568
+ const user = await readPluginConfigStore({
724
569
  sourceId: config.sourceId,
725
570
  sourceIdentity: config.sourceIdentity,
726
571
  logicalIdentity: config.logicalIdentity,
727
572
  pluginName: config.pluginName,
728
- scope,
573
+ scope: 'user',
574
+ platform: config.platform,
729
575
  cwd: context.cwd,
730
576
  env: context.env,
731
577
  schema: config.schema,
732
- })));
733
- const user = stores[0];
734
- const project = config.scope === 'user' ? undefined : stores[1];
578
+ });
735
579
  config.signal.throwIfAborted();
736
- const invalidStores = ([
737
- { scope: 'user', store: user },
738
- ...(project ? [{ scope: 'project', store: project }] : []),
739
- ]).flatMap(({ scope, store }) => store.state === 'invalid' ? [{
740
- scope,
741
- code: store.invalidCode,
742
- pathSummary: scope === 'user'
743
- ? `AW_HOME/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`
744
- : `<project>/.agent-workflows/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`,
745
- }] : []);
746
- if (invalidStores.length > 0)
747
- throw new TuiConfigInvalidStoreError(invalidStores);
748
- return { user, ...(project ? { project } : {}) };
580
+ if (user.state === 'invalid')
581
+ throw new TuiConfigInvalidStoreError([{
582
+ scope: 'user', code: user.invalidCode,
583
+ pathSummary: `AW_HOME/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`,
584
+ }]);
585
+ return { user };
586
+ };
587
+ const assertConfigEditEvidence = async (intent, evidence) => {
588
+ if (!intent.editEvidence)
589
+ return;
590
+ if (!evidence.source || !evidence.config)
591
+ throw operationReviewStale('设置编辑缺少当前证据。');
592
+ const currentDigest = await configLayerStoreDigest({ source: evidence.source,
593
+ pluginName: parsePluginId(intent.pluginId).pluginName, scope: 'user', platform: intent.platform,
594
+ cwd: context.cwd, env: context.env });
595
+ for (const expected of Object.values(intent.editEvidence)) {
596
+ if (expected.sourceIdentity !== evidence.source.sourceIdentity
597
+ || expected.schemaIdentity !== evidence.config.schemaIdentity
598
+ || expected.platform !== intent.platform
599
+ || (expected.context ?? 'user') !== (intent.context ?? 'user')
600
+ || expected.storeDigests.user !== undefined && expected.storeDigests.user !== currentDigest) {
601
+ throw operationReviewStale('编辑期间设置、来源或定义已变化。请重新打开设置。');
602
+ }
603
+ }
749
604
  };
750
605
  const loadStatus = async (signal) => {
751
606
  signal.throwIfAborted();
@@ -816,7 +671,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
816
671
  signal.throwIfAborted();
817
672
  const catalogGenerations = await loadCatalogGenerations();
818
673
  signal.throwIfAborted();
819
- const configStoreInventory = (await Promise.all(['user', 'project'].map(async (scope) => {
674
+ const configStoreInventory = (await Promise.all(['user'].map(async (scope) => {
820
675
  try {
821
676
  return (await listNamespacedPluginConfigStoreIdentities({
822
677
  cwd: context.cwd,
@@ -839,24 +694,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
839
694
  }))).flat();
840
695
  signal.throwIfAborted();
841
696
  const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
842
- const projectIntentObservation = result.projectIntentObservation;
843
- const projectIntent = projectIntentObservation.state === 'ready' ? {
844
- state: 'ready',
845
- roots: structuredClone(projectIntentObservation.value.roots),
846
- } : projectIntentObservation.state === 'absent' ? {
847
- state: 'absent',
848
- } : {
849
- state: projectIntentObservation.state,
850
- code: projectIntentObservation.code,
851
- message: projectIntentObservation.message,
852
- };
853
697
  return {
854
698
  cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
855
699
  ...(result.projectRoot ? { projectRoot: result.projectRoot } : {}),
856
- projectIntent,
857
- ...(projectIntent.state === 'ready'
858
- ? { projectIntentRoots: structuredClone(projectIntent.roots) }
859
- : {}),
860
700
  configuredSources: structuredClone(configured.sources),
861
701
  payload: result.payload,
862
702
  pluginSummaries: result.pluginSummaries,
@@ -940,8 +780,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
940
780
  };
941
781
  }
942
782
  };
943
- const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate, codexObservationSession, frozenBatchEvidence) => {
783
+ const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate) => {
784
+ if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
785
+ || intent.kind === 'pluginWriteTask') {
786
+ throw new TuiOperationBlockedError('useScopedRecovery', '请使用统一范围恢复任务。');
787
+ }
944
788
  reviewedScriptTimeoutMs(intent);
789
+ if ((intent.kind === 'configSave' || intent.kind === 'configSaveBatch' || intent.kind === 'configReset')
790
+ && intent.context === 'project') {
791
+ await resolveWorkContext({ cwd: context.cwd, context: 'project' });
792
+ signal?.throwIfAborted();
793
+ }
945
794
  if (intent.kind === 'catalogStorePrune') {
946
795
  signal?.throwIfAborted();
947
796
  const config = await readCatalogSourcesConfigSnapshot(context.env);
@@ -958,7 +807,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
958
807
  let batchBase;
959
808
  let batchCatalogOperation;
960
809
  for (const group of intent.groups) {
961
- const current = await captureEvidence(configSaveBatchGroupIntent(intent, group), signal, preparation, operationInventory, freshCatalog, reusableCandidate, codexObservationSession);
810
+ const current = await captureEvidence(configSaveBatchGroupIntent(intent, group), signal, preparation, operationInventory, freshCatalog, reusableCandidate);
962
811
  if (!current.config || !current.source || !current.targetFingerprint) {
963
812
  throw operationReviewStale('设置批次缺少单层证据。');
964
813
  }
@@ -968,10 +817,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
968
817
  }
969
818
  for (const expected of Object.values(group.editEvidence ?? {})) {
970
819
  if (expected.sourceIdentity !== current.source.sourceIdentity
971
- || expected.schemaIdentity !== current.config.schemaIdentity) {
820
+ || expected.schemaIdentity !== current.config.schemaIdentity
821
+ || expected.platform !== group.platform
822
+ || (expected.context ?? 'user') !== (intent.context ?? 'user')) {
972
823
  throw operationReviewStale('编辑期间插件来源或设置定义已变化,请重新打开列表。');
973
824
  }
974
- for (const scope of ['user', 'project']) {
825
+ for (const scope of ['user']) {
975
826
  if (expected.storeDigests[scope] === undefined)
976
827
  continue;
977
828
  const currentDigest = await configLayerStoreDigest({
@@ -986,13 +837,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
986
837
  batchBase ??= current;
987
838
  batchCatalogOperation ??= catalogOperationByEvidence.get(current);
988
839
  groupEvidence.push({
989
- scope: group.scope,
840
+ scope: 'user',
841
+ platform: group.platform,
990
842
  config: {
991
843
  ...current.config,
992
844
  storeDigest: await configLayerStoreDigest({
993
845
  source: current.source,
994
846
  pluginName: parsePluginId(intent.pluginId).pluginName,
995
- scope: group.scope,
847
+ scope: 'user',
848
+ platform: group.platform,
849
+ profileOnly: true,
996
850
  cwd: context.cwd,
997
851
  env: context.env,
998
852
  }),
@@ -1009,23 +863,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1009
863
  catalogOperationByEvidence.set(evidence, batchCatalogOperation);
1010
864
  return evidence;
1011
865
  }
1012
- if ((intent.kind === 'pluginWrite'
1013
- && (intent.platform === 'codex' || intent.platform === 'both')
1014
- || intent.kind === 'pluginWriteTask'
1015
- && intent.targets.some((target) => target.platform === 'codex'))
1016
- && !codexObservationSession) {
1017
- const observationSession = await createCodexNativeObservationSession({
1018
- exactCwd: context.cwd,
1019
- env: context.env,
1020
- runner: context.nativeCliRunner,
1021
- });
1022
- try {
1023
- return await captureEvidence(intent, signal, preparation, operationInventory, freshCatalog, reusableCandidate, observationSession, frozenBatchEvidence);
1024
- }
1025
- finally {
1026
- await observationSession.dispose();
1027
- }
1028
- }
1029
866
  if (intent.kind === 'configReset') {
1030
867
  const parsed = parsePluginId(intent.pluginId);
1031
868
  if (!parsed.sourceId || !parsed.pluginName) {
@@ -1035,7 +872,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1035
872
  const target = await inspectPluginConfigResetTarget({
1036
873
  sourceId: parsed.sourceId,
1037
874
  pluginName: parsed.pluginName,
1038
- scope: intent.scope,
875
+ scope: 'user',
1039
876
  cwd: context.cwd,
1040
877
  env: context.env,
1041
878
  });
@@ -1044,7 +881,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1044
881
  throw new TuiOperationBlockedError('configResetTargetUnsupported', '设置目标是特殊文件系统节点,不能自动重置。请手动移动后重试。');
1045
882
  }
1046
883
  const relevantTargets = sessionStatusResult
1047
- ? configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope)
884
+ ? configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.context ?? 'user', [], intent.platform)
1048
885
  : [];
1049
886
  const relevantRevisions = (sessionStatusResult?.targetRevisions ?? []).filter((revision) => relevantTargets.some((statusTarget) => statusTarget.platform === revision.platform
1050
887
  && statusTarget.scope === revision.scope
@@ -1064,7 +901,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1064
901
  source: sourceEvidenceForPlugin(resolvedSource, plugin.manifest.name),
1065
902
  impact: await analyzePluginConfigMutationImpact({
1066
903
  plugin,
1067
- scope: intent.scope,
904
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1068
905
  cwd: context.cwd,
1069
906
  env: context.env,
1070
907
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
@@ -1073,7 +910,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1073
910
  }),
1074
911
  });
1075
912
  }
1076
- catch {
913
+ catch (error) {
914
+ if (error?.code === 'configValidationMemoryExhausted')
915
+ throw error;
1077
916
  selected = undefined;
1078
917
  }
1079
918
  signal?.throwIfAborted();
@@ -1091,7 +930,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1091
930
  targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
1092
931
  } : {}),
1093
932
  config: {
1094
- scope: intent.scope,
933
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
1095
934
  storeDigest: selected?.impact.storeDigest ?? target.fingerprint,
1096
935
  impactState: selected?.impact.impactState ?? 'unknown',
1097
936
  ...(selected?.impact.noEffectiveChange ? { noEffectiveChange: true } : {}),
@@ -1106,7 +945,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1106
945
  resetTarget: {
1107
946
  sourceId: parsed.sourceId,
1108
947
  pluginName: parsed.pluginName,
1109
- scope: intent.scope,
948
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
1110
949
  kind: target.kind,
1111
950
  fingerprint: target.fingerprint,
1112
951
  },
@@ -1417,671 +1256,11 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1417
1256
  }
1418
1257
  return evidence;
1419
1258
  }
1420
- if (!pluginIntent(intent) && intent.kind !== 'pluginWriteBatch'
1421
- && intent.kind !== 'pluginWriteTask') {
1259
+ if (intent.kind !== 'configSave'
1260
+ && intent.kind !== 'portableInstall') {
1422
1261
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
1423
1262
  }
1424
1263
  signal?.throwIfAborted();
1425
- if (intent.kind === 'pluginWriteTask') {
1426
- await preparation?.reporter.emit('status');
1427
- const packageInfo = await readCliPackageInfo();
1428
- const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
1429
- const requestedBySource = new Map();
1430
- for (const target of intent.targets) {
1431
- const parsed = parsePluginId(target.pluginId);
1432
- if (!parsed.sourceId || formatPluginId({
1433
- sourceId: parsed.sourceId,
1434
- pluginName: parsed.pluginName,
1435
- }) !== target.pluginId) {
1436
- throw new TuiOperationBlockedError('invalidPluginId', '任务目标必须使用 canonical pluginId。');
1437
- }
1438
- requestedBySource.set(parsed.sourceId, [
1439
- ...(requestedBySource.get(parsed.sourceId) ?? []),
1440
- target,
1441
- ]);
1442
- }
1443
- const sources = [];
1444
- const executableTargets = [];
1445
- const causes = [];
1446
- const blockedTargets = [];
1447
- const observedTargets = [];
1448
- const observedRevisions = [];
1449
- const operationContexts = new Map();
1450
- await preparation?.reporter.emit('catalog');
1451
- for (const [sourceId, requestedTargets] of [...requestedBySource.entries()]
1452
- .sort(([left], [right]) => left.localeCompare(right, 'en'))) {
1453
- signal?.throwIfAborted();
1454
- let catalogOperation;
1455
- try {
1456
- catalogOperation = !freshCatalog && sessionCatalogOperationContext
1457
- ? selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId])
1458
- : await resolveCatalogOperationContext({
1459
- sourceIds: [sourceId],
1460
- env: context.env,
1461
- ...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
1462
- ...(signal ? { signal } : {}),
1463
- });
1464
- }
1465
- catch (error) {
1466
- if (error instanceof Error && error.name === 'AbortError')
1467
- throw error;
1468
- blockedTargets.push(...requestedTargets.map((target, index) => {
1469
- const diagnostic = error instanceof CatalogGenerationError ? {
1470
- id: `task-source:${sourceId}:${index}`,
1471
- code: error.code,
1472
- stage: 'source',
1473
- sourceId,
1474
- safeMessage: 'The Catalog source could not be prepared safely.',
1475
- target: { pluginId: target.pluginId, platform: target.platform, scope: target.scope },
1476
- causes: [],
1477
- detailLines: [],
1478
- } : projectThrownOperationDiagnostic({
1479
- error,
1480
- id: `task-source:${sourceId}:${index}`,
1481
- stage: 'source',
1482
- fallbackCode: 'catalogSourceResolutionFailed',
1483
- sourceId,
1484
- target,
1485
- });
1486
- return {
1487
- target,
1488
- reason: 'targetUnavailable',
1489
- issueIds: [],
1490
- errorCodes: [diagnostic.code],
1491
- diagnostics: [diagnostic],
1492
- };
1493
- }));
1494
- continue;
1495
- }
1496
- operationContexts.set(sourceId, catalogOperation);
1497
- const status = await statusCommandV8({
1498
- plugins: [...new Set(requestedTargets.map((target) => target.pluginId))],
1499
- cwd: context.cwd,
1500
- env: context.env,
1501
- nativeCliRunner: context.nativeCliRunner,
1502
- ...(codexObservationSession ? { codexObservationSession } : {}),
1503
- cliVersion: packageInfo.version,
1504
- catalogOperationContext: catalogOperation,
1505
- inventoryCatalogSourceReferences: operationInventory,
1506
- });
1507
- observedTargets.push(...flattenStatusTargets(status.payload.targets));
1508
- observedRevisions.push(...status.targetRevisions);
1509
- const model = buildTuiViewModel({
1510
- status: status.payload,
1511
- pluginSummaries: status.pluginSummaries,
1512
- targetRevisions: status.targetRevisions,
1513
- });
1514
- const runtimeCandidates = intent.goal === 'restore'
1515
- ? buildPluginWriteTaskCandidates(model, 'restore')
1516
- : requestedTargets.map((target) => {
1517
- const expected = target.expectedUpdate;
1518
- const revision = status.targetRevisions.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
1519
- && entry.scope === target.scope);
1520
- const live = flattenStatusTargets(status.payload.targets).find((entry) => {
1521
- if (entry.pluginId !== target.pluginId || entry.platform !== target.platform)
1522
- return false;
1523
- const desired = statusTargetIntent(entry);
1524
- return entry.scope === target.scope || desired.scopes.includes(target.scope);
1525
- });
1526
- const generation = catalogOperation.generationSet.sources.find((entry) => entry.sourceId === sourceId);
1527
- const eligible = Boolean(expected && live && revision
1528
- && live.sourceIdentity === expected.sourceIdentity
1529
- && generation?.storeKey === expected.generationKey
1530
- && revision.revision.installedVersion === expected.installedVersion
1531
- && revision.revision.availableVersion === expected.activeVersion
1532
- && revision.revision.state === 'upgradeAvailable');
1533
- const directVersionUpgrade = live !== undefined && revision !== undefined
1534
- && isDirectCodexVersionUpgrade(live, revision.revision);
1535
- return {
1536
- target,
1537
- displayName: model.plugins.find((plugin) => plugin.pluginId === target.pluginId)
1538
- ?.displayName ?? parsePluginId(target.pluginId).pluginName,
1539
- sourceId,
1540
- eligible,
1541
- ...(!eligible ? { blockReason: 'updateEvidenceIncomplete' } : {}),
1542
- recommendedMethod: live?.state === 'ready' || directVersionUpgrade
1543
- ? 'sync' : 'reinstall',
1544
- issueIds: [],
1545
- };
1546
- });
1547
- const draft = buildPluginWriteTaskDraft({ ...intent, targets: requestedTargets }, model, runtimeCandidates);
1548
- blockedTargets.push(...draft.blockedTargets);
1549
- if (draft.executableTargets.length === 0)
1550
- continue;
1551
- const methodsByTarget = new Map();
1552
- for (const cause of draft.causes) {
1553
- const key = pluginWriteTaskTargetKey(cause.repairTarget);
1554
- const methods = methodsByTarget.get(key) ?? new Set();
1555
- methods.add(cause.method);
1556
- methodsByTarget.set(key, methods);
1557
- }
1558
- const conflicted = new Set([...methodsByTarget].filter(([, methods]) => methods.size > 1)
1559
- .map(([key]) => key));
1560
- if (conflicted.size > 0) {
1561
- const blockedKeys = new Set(draft.causes.filter((cause) => conflicted.has(pluginWriteTaskTargetKey(cause.repairTarget)))
1562
- .flatMap((cause) => cause.consumers.map(pluginWriteTaskTargetKey)));
1563
- blockedTargets.push(...draft.executableTargets.filter((target) => blockedKeys.has(pluginWriteTaskTargetKey(target))).map((target) => ({
1564
- target,
1565
- reason: 'taskActionConflict',
1566
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1567
- .map((cause) => cause.issueId),
1568
- })));
1569
- draft.executableTargets = draft.executableTargets.filter((target) => !blockedKeys.has(pluginWriteTaskTargetKey(target)));
1570
- draft.causes = draft.causes.filter((cause) => cause.consumers.some((consumer) => !blockedKeys.has(pluginWriteTaskTargetKey(consumer))));
1571
- }
1572
- if (draft.executableTargets.length === 0)
1573
- continue;
1574
- const taskTargets = () => draft.executableTargets.map((target) => {
1575
- const direct = draft.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
1576
- return {
1577
- pluginId: target.pluginId,
1578
- platform: target.platform,
1579
- scope: target.scope,
1580
- method: direct.some((cause) => cause.method === 'reinstall')
1581
- ? 'reinstall' : 'sync',
1582
- };
1583
- });
1584
- const repairTargets = () => [...new Map(draft.causes.map((cause) => [
1585
- pluginWriteTaskTargetKey(cause.repairTarget),
1586
- { ...cause.repairTarget, method: cause.method },
1587
- ])).values()];
1588
- let preview;
1589
- while (draft.executableTargets.length > 0) {
1590
- preview = await taskWriteCommand(taskTargets(), {
1591
- cwd: context.cwd,
1592
- repairTargets: repairTargets(),
1593
- ...(reviewedScriptTimeoutMs(intent) === undefined
1594
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1595
- }, {
1596
- ...context,
1597
- catalogOperationContext: catalogOperation,
1598
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1599
- expectedCatalogConfigDigest: configSnapshot.digest,
1600
- inventoryCatalogSourceReferences: operationInventory,
1601
- previewOperationPlan: true,
1602
- ...(codexObservationSession ? { codexObservationSession } : {}),
1603
- ...(preparation ? {
1604
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1605
- } : {}),
1606
- });
1607
- if (!preview.operationPlan) {
1608
- const projected = projectExecutionDiagnostics(preview, `task-preview:${sourceId}`, [], 'pluginWrite');
1609
- const sourceDiagnostics = projected.diagnostics.filter((diagnostic) => !diagnostic.target)
1610
- .map((diagnostic) => ({ ...diagnostic, sourceId, target: undefined }));
1611
- blockedTargets.push(...draft.executableTargets.map((target, targetIndex) => {
1612
- const matchingDiagnostics = projected.diagnostics.filter((diagnostic) => diagnostic.target?.pluginId === target.pluginId
1613
- && diagnostic.target.platform === target.platform
1614
- && diagnostic.target.scope === target.scope);
1615
- const preparedDiagnostics = [
1616
- ...matchingDiagnostics,
1617
- ...(targetIndex === 0 ? sourceDiagnostics : []),
1618
- ];
1619
- const targetDiagnostics = preparedDiagnostics.length > 0
1620
- ? preparedDiagnostics
1621
- : [projectThrownOperationDiagnostic({
1622
- error: undefined,
1623
- id: `task-preview:${sourceId}:${targetIndex}`,
1624
- stage: 'dependencyPlan',
1625
- fallbackCode: 'taskOperationPlanMissing',
1626
- sourceId,
1627
- target,
1628
- })];
1629
- const diagnostics = targetDiagnostics.map((diagnostic, diagnosticIndex) => ({
1630
- ...structuredClone(diagnostic),
1631
- id: `task-preview:${sourceId}:${targetIndex}:${diagnosticIndex}`,
1632
- sourceId,
1633
- }));
1634
- return {
1635
- target,
1636
- reason: 'targetUnavailable',
1637
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1638
- .map((cause) => cause.issueId),
1639
- errorCodes: [...new Set(diagnostics.map((diagnostic) => diagnostic.code))],
1640
- diagnostics,
1641
- };
1642
- }));
1643
- draft.executableTargets = [];
1644
- draft.causes = [];
1645
- break;
1646
- }
1647
- if (intent.goal === 'restore') {
1648
- const grouped = new Map();
1649
- const blockedByVersion = new Map();
1650
- for (const cause of draft.causes) {
1651
- const placements = (preview.dependencyPlan?.targets ?? []).filter((target) => target.pluginId === cause.repairTarget.pluginId
1652
- && target.platform === cause.repairTarget.platform);
1653
- const placement = placements.find((target) => target.requestedScope === cause.repairTarget.scope)
1654
- ?? (placements.length === 1 ? placements[0] : undefined);
1655
- if (!placement) {
1656
- throw new TuiOperationBlockedError('taskRepairPlacementMissing', '任务修复目标无法绑定到依赖位置。');
1657
- }
1658
- const repairTarget = {
1659
- pluginId: cause.repairTarget.pluginId,
1660
- platform: cause.repairTarget.platform,
1661
- scope: placement.requestedScope ?? placement.resolvedScope,
1662
- };
1663
- const version = evaluatePluginWriteTaskRestoreVersion(repairTarget, cause.restoreVersionEvidence?.state === 'absent', model);
1664
- if (!version.evidence || version.blockReason) {
1665
- for (const consumer of cause.consumers) {
1666
- const key = pluginWriteTaskTargetKey(consumer);
1667
- const current = blockedByVersion.get(key) ?? {
1668
- reason: version.blockReason ?? 'restoreVersionUnverified',
1669
- issueIds: new Set(),
1670
- };
1671
- if (version.blockReason === 'restoreVersionChanged') {
1672
- current.reason = version.blockReason;
1673
- }
1674
- current.issueIds.add(cause.issueId);
1675
- blockedByVersion.set(key, current);
1676
- }
1677
- continue;
1678
- }
1679
- const causeKey = ['restore', cause.sourceIdentity,
1680
- pluginWriteTaskTargetKey(repairTarget)].join('\0');
1681
- const current = grouped.get(causeKey);
1682
- grouped.set(causeKey, {
1683
- cause: {
1684
- ...cause,
1685
- causeKey,
1686
- issueId: [cause.issueId, ...(current?.issueIds ?? [])]
1687
- .sort((left, right) => left.localeCompare(right, 'en'))[0],
1688
- repairTarget,
1689
- consumers: [...new Map([
1690
- ...(current?.cause.consumers ?? []),
1691
- ...cause.consumers,
1692
- ].map((consumer) => [pluginWriteTaskTargetKey(consumer), consumer])).values()]
1693
- .sort((left, right) => pluginWriteTaskTargetKey(left)
1694
- .localeCompare(pluginWriteTaskTargetKey(right), 'en')),
1695
- restoreVersionEvidence: version.evidence,
1696
- },
1697
- issueIds: new Set([...(current?.issueIds ?? []), cause.issueId]),
1698
- methods: new Set([...(current?.methods ?? []), cause.method]),
1699
- });
1700
- }
1701
- for (const group of grouped.values()) {
1702
- if (group.methods.size < 2)
1703
- continue;
1704
- for (const consumer of group.cause.consumers) {
1705
- const key = pluginWriteTaskTargetKey(consumer);
1706
- const current = blockedByVersion.get(key) ?? {
1707
- reason: 'taskActionConflict',
1708
- issueIds: new Set(),
1709
- };
1710
- group.issueIds.forEach((issueId) => current.issueIds.add(issueId));
1711
- blockedByVersion.set(key, current);
1712
- }
1713
- }
1714
- const newlyBlockedKeys = new Set(blockedByVersion.keys());
1715
- blockedTargets.push(...draft.executableTargets.flatMap((target) => {
1716
- const blocked = blockedByVersion.get(pluginWriteTaskTargetKey(target));
1717
- return blocked ? [{
1718
- target,
1719
- reason: blocked.reason,
1720
- issueIds: [...blocked.issueIds].sort((left, right) => left.localeCompare(right, 'en')),
1721
- }] : [];
1722
- }));
1723
- const normalizedCauses = [...grouped.values()]
1724
- .filter((group) => group.methods.size === 1)
1725
- .map((group) => ({
1726
- ...group.cause,
1727
- consumers: group.cause.consumers.filter((consumer) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(consumer))),
1728
- }))
1729
- .filter((cause) => cause.consumers.length > 0)
1730
- .sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en'));
1731
- const normalizedTargets = draft.executableTargets.filter((target) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(target)));
1732
- const changed = JSON.stringify(normalizedCauses) !== JSON.stringify(draft.causes)
1733
- || JSON.stringify(normalizedTargets) !== JSON.stringify(draft.executableTargets);
1734
- draft.causes = normalizedCauses;
1735
- draft.executableTargets = normalizedTargets;
1736
- if (changed)
1737
- continue;
1738
- }
1739
- const blockedPlanTargets = (preview.dependencyPlan?.targets ?? []).filter((target) => target.action === 'block' || target.errorCode !== undefined);
1740
- if (blockedPlanTargets.length === 0)
1741
- break;
1742
- const requestedKeys = new Set(draft.executableTargets.map(pluginWriteTaskTargetKey));
1743
- const blockedPlansByRoot = new Map();
1744
- for (const blockedPlanTarget of blockedPlanTargets) {
1745
- const pending = [blockedPlanTarget];
1746
- const visited = new Set();
1747
- const mappedRoots = new Set();
1748
- const repairKey = pluginWriteTaskTargetKey({
1749
- pluginId: blockedPlanTarget.pluginId,
1750
- platform: blockedPlanTarget.platform,
1751
- scope: blockedPlanTarget.requestedScope ?? blockedPlanTarget.resolvedScope,
1752
- });
1753
- for (const cause of draft.causes) {
1754
- if (pluginWriteTaskTargetKey(cause.repairTarget) !== repairKey)
1755
- continue;
1756
- cause.consumers.forEach((consumer) => mappedRoots.add(pluginWriteTaskTargetKey(consumer)));
1757
- }
1758
- while (pending.length > 0) {
1759
- const current = pending.shift();
1760
- const currentKey = pluginWriteTaskTargetKey({
1761
- pluginId: current.pluginId,
1762
- platform: current.platform,
1763
- scope: current.requestedScope ?? current.resolvedScope,
1764
- });
1765
- if (visited.has(currentKey))
1766
- continue;
1767
- visited.add(currentKey);
1768
- if (current.relation === 'requested' && requestedKeys.has(currentKey)) {
1769
- mappedRoots.add(currentKey);
1770
- }
1771
- for (const consumer of current.requiredByTargets ?? []) {
1772
- const consumerKey = pluginWriteTaskTargetKey(consumer);
1773
- if (requestedKeys.has(consumerKey)) {
1774
- mappedRoots.add(consumerKey);
1775
- continue;
1776
- }
1777
- const placement = preview.dependencyPlan?.targets.find((target) => target.pluginId === consumer.pluginId && target.platform === consumer.platform
1778
- && (target.requestedScope ?? target.resolvedScope) === consumer.scope);
1779
- if (placement)
1780
- pending.push(placement);
1781
- }
1782
- }
1783
- if (mappedRoots.size === 0) {
1784
- throw new TuiOperationBlockedError('taskPlanConsumerMissing', '任务计划中的阻止项无法映射到请求目标。');
1785
- }
1786
- for (const key of mappedRoots) {
1787
- const plans = blockedPlansByRoot.get(key) ?? [];
1788
- plans.push(blockedPlanTarget);
1789
- blockedPlansByRoot.set(key, plans);
1790
- }
1791
- }
1792
- blockedTargets.push(...draft.executableTargets.flatMap((target) => {
1793
- const plans = blockedPlansByRoot.get(pluginWriteTaskTargetKey(target));
1794
- return plans ? [{
1795
- target,
1796
- reason: 'targetUnavailable',
1797
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1798
- .map((cause) => cause.issueId),
1799
- errorCodes: [...new Set(plans.map((plan) => plan.errorCode ?? 'writePlanBlocked'))]
1800
- .sort((left, right) => left.localeCompare(right, 'en')),
1801
- diagnostics: plans.map((plan, index) => ({
1802
- id: `task-plan:${sourceId}:${target.pluginId}:${target.platform}:${target.scope}:${index}`,
1803
- code: plan.errorCode ?? 'writePlanBlocked',
1804
- stage: 'dependencyPlan',
1805
- sourceId,
1806
- target: {
1807
- pluginId: plan.pluginId, platform: plan.platform,
1808
- scope: plan.requestedScope ?? plan.resolvedScope,
1809
- },
1810
- causes: [],
1811
- detailLines: [],
1812
- safeMessage: plan.errorCode === 'artifactCollision'
1813
- ? '输出位置存在外来内容,无法覆盖。'
1814
- : plan.errorCode === 'exclusiveOwnerEvidenceInvalid'
1815
- ? '独立扩展的归属标记缺失或损坏。'
1816
- : plan.errorCode === 'dependencyNotReady'
1817
- ? '依赖安装状态无法读取。'
1818
- : '依赖计划阻止了此目标。',
1819
- })),
1820
- }] : [];
1821
- }));
1822
- draft.executableTargets = draft.executableTargets.filter((target) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(target)));
1823
- draft.causes = draft.causes.map((cause) => ({
1824
- ...cause,
1825
- consumers: cause.consumers.filter((consumer) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(consumer))),
1826
- })).filter((cause) => cause.consumers.length > 0);
1827
- }
1828
- if (!preview?.operationPlan || draft.executableTargets.length === 0)
1829
- continue;
1830
- const sourceIdentity = draft.causes[0]?.sourceIdentity
1831
- ?? status.payload.catalog.sources.find((source) => source.id === sourceId)?.identity;
1832
- if (!sourceIdentity) {
1833
- throw new TuiOperationBlockedError('taskSourceIdentityMissing', '任务来源身份不可验证。');
1834
- }
1835
- const sourceEvidence = {
1836
- sourceId,
1837
- sourceIdentity,
1838
- requestedTargets: draft.executableTargets,
1839
- causes: draft.causes,
1840
- preview,
1841
- executable: true,
1842
- };
1843
- sources.push(sourceEvidence);
1844
- executableTargets.push(...draft.executableTargets);
1845
- causes.push(...draft.causes);
1846
- }
1847
- const canonicalBlocked = [...new Map(blockedTargets.map((target) => [
1848
- pluginWriteTaskTargetKey(target.target), target,
1849
- ])).values()].sort((left, right) => pluginWriteTaskTargetKey(left.target)
1850
- .localeCompare(pluginWriteTaskTargetKey(right.target), 'en'));
1851
- if (executableTargets.length === 0) {
1852
- throw new TuiOperationBlockedError('taskNoExecutableTargets', '任务没有可安全执行的目标。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1853
- }
1854
- if (intent.executionPolicy === 'all' && canonicalBlocked.length > 0) {
1855
- throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理。请调整选择。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1856
- }
1857
- await preparation?.reporter.emit('evidence');
1858
- const evidence = {
1859
- catalog: {
1860
- configDigest: configSnapshot.digest,
1861
- compositeDigest: digest(sources.map((source) => [
1862
- source.sourceId,
1863
- source.sourceIdentity,
1864
- source.preview.operationPlan?.planFingerprint,
1865
- ])),
1866
- },
1867
- taskPlan: {
1868
- goal: intent.goal,
1869
- requestedTargets: intent.targets,
1870
- executableTargets: [...executableTargets].sort((left, right) => pluginWriteTaskTargetKey(left).localeCompare(pluginWriteTaskTargetKey(right), 'en')),
1871
- causes: [...new Map(causes.map((cause) => [cause.causeKey, cause])).values()]
1872
- .sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en')),
1873
- blockedTargets: canonicalBlocked,
1874
- sources,
1875
- targetFingerprint: statusTargetFingerprint(observedTargets, observedRevisions),
1876
- planFingerprint: '',
1877
- },
1878
- };
1879
- evidence.taskPlan.planFingerprint = operationEvidenceFingerprint(evidence);
1880
- taskCatalogOperationsByEvidence.set(evidence, operationContexts);
1881
- return evidence;
1882
- }
1883
- if (intent.kind === 'pluginWriteBatch') {
1884
- await preparation?.reporter.emit('status');
1885
- const selection = await resolveBatchGroups(intent, context.env, context.cwd, frozenBatchEvidence?.requestedPluginIds);
1886
- signal?.throwIfAborted();
1887
- if (intent.action === 'cleanup') {
1888
- const groups = [];
1889
- const reviewedRemovedPlugins = new Set();
1890
- for (const group of selection.groups) {
1891
- signal?.throwIfAborted();
1892
- const requestedPluginIds = group.requestedPluginIds.filter((pluginId) => !reviewedRemovedPlugins.has(pluginId));
1893
- if (requestedPluginIds.length === 0)
1894
- continue;
1895
- const preview = await runBatchWriteGroup({
1896
- intent,
1897
- group: { ...group, requestedPluginIds },
1898
- cwd: context.cwd,
1899
- runtime: {
1900
- ...context,
1901
- inventoryCatalogSourceReferences: operationInventory,
1902
- previewOperationPlan: true,
1903
- ...(codexObservationSession ? { codexObservationSession } : {}),
1904
- ...(preparation ? {
1905
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1906
- } : {}),
1907
- },
1908
- });
1909
- for (const pluginId of preview.removedPluginCleanupIds ?? []) {
1910
- reviewedRemovedPlugins.add(pluginId);
1911
- }
1912
- groups.push({ ...group, requestedPluginIds, preview,
1913
- executable: preview.operationPlan !== undefined });
1914
- }
1915
- await preparation?.reporter.emit('evidence');
1916
- signal?.throwIfAborted();
1917
- const evidence = {
1918
- catalog: {
1919
- configDigest: 'cleanup-offline',
1920
- compositeDigest: digest(groups.map((group) => [
1921
- group.groupId,
1922
- group.preview.operationPlan?.planFingerprint,
1923
- ])),
1924
- },
1925
- requestedPluginIds: selection.requestedPluginIds,
1926
- groups,
1927
- };
1928
- return evidence;
1929
- }
1930
- const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
1931
- const groups = [];
1932
- const ownsObservationSession = !codexObservationSession
1933
- && selection.groups.some((group) => {
1934
- const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1935
- return reviewed?.executable !== false
1936
- && (group.platform === 'codex' || group.platform === 'both');
1937
- });
1938
- const observationSession = codexObservationSession ?? (ownsObservationSession
1939
- ? await createCodexNativeObservationSession({
1940
- exactCwd: context.cwd,
1941
- env: context.env,
1942
- runner: context.nativeCliRunner,
1943
- })
1944
- : undefined);
1945
- try {
1946
- await preparation?.reporter.emit('catalog');
1947
- for (const group of selection.groups) {
1948
- signal?.throwIfAborted();
1949
- const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1950
- if (reviewed?.executable === false) {
1951
- groups.push({ ...group, preview: reviewed.preview, executable: false });
1952
- continue;
1953
- }
1954
- let catalogOperation;
1955
- try {
1956
- catalogOperation = !freshCatalog && sessionCatalogOperationContext
1957
- ? selectCatalogOperationContext(sessionCatalogOperationContext, [group.sourceId])
1958
- : await resolveCatalogOperationContext({
1959
- sourceIds: [group.sourceId],
1960
- env: context.env,
1961
- ...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
1962
- ...(signal ? { signal } : {}),
1963
- });
1964
- }
1965
- catch {
1966
- catalogOperation = undefined;
1967
- }
1968
- const preview = await runBatchWriteGroup({
1969
- intent,
1970
- group,
1971
- cwd: context.cwd,
1972
- runtime: {
1973
- ...context,
1974
- inventoryCatalogSourceReferences: operationInventory,
1975
- expectedCatalogConfigDigest: configSnapshot.digest,
1976
- ...(catalogOperation ? {
1977
- catalogOperationContext: catalogOperation,
1978
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1979
- } : {}),
1980
- previewOperationPlan: true,
1981
- ...(observationSession ? { codexObservationSession: observationSession } : {}),
1982
- ...(preparation ? {
1983
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1984
- } : {}),
1985
- },
1986
- });
1987
- groups.push({ ...group, preview, executable: preview.operationPlan !== undefined });
1988
- }
1989
- }
1990
- finally {
1991
- if (ownsObservationSession)
1992
- await observationSession?.dispose();
1993
- }
1994
- await preparation?.reporter.emit('evidence');
1995
- signal?.throwIfAborted();
1996
- const merged = mergeWriteCommandResults(groups.map((group) => group.preview));
1997
- const targetVersions = groups.flatMap((group) => {
1998
- if (group.platform === 'both')
1999
- return [];
2000
- const platform = group.platform;
2001
- return group.requestedPluginIds.map((pluginId) => {
2002
- const live = flattenStatusTargets(sessionStatusResult?.payload.targets ?? []).find((target) => target.pluginId === pluginId && target.platform === platform
2003
- && (target.scope === group.scope || statusTargetIntent(target).scopes.includes(group.scope)
2004
- || platform === 'codex' && statusTargetActivation(target)?.[group.scope] === true));
2005
- const revision = sessionStatusResult?.targetRevisions?.find((target) => target.pluginId === pluginId && target.platform === platform
2006
- && target.scope === group.scope)?.revision;
2007
- const activation = live ? statusTargetActivation(live) : undefined;
2008
- const ownership = live && live.scope === group.scope
2009
- ? statusTargetOwnershipState(live) : 'absent';
2010
- const present = ownership === 'present' || ownership === 'partial'
2011
- || platform === 'codex' && activation?.[group.scope] === true;
2012
- const planned = live ? statusTargetIntent(live).scopes.includes(group.scope) : false;
2013
- return {
2014
- pluginId,
2015
- platform,
2016
- scope: group.scope,
2017
- presence: present ? 'installed'
2018
- : ownership === 'absent' && planned ? 'new' : 'unknown',
2019
- ...(revision?.installedVersion ? { installedVersion: revision.installedVersion } : {}),
2020
- ...(revision?.availableVersion ? { availableVersion: revision.availableVersion } : {}),
2021
- };
2022
- });
2023
- });
2024
- const evidence = {
2025
- catalog: {
2026
- configDigest: configSnapshot.digest,
2027
- compositeDigest: sessionStatusResult?.payload.catalog.compositeDigest
2028
- ?? digest(groups.map((group) => [group.groupId, group.preview.operationPlan?.planFingerprint])),
2029
- },
2030
- requestedPluginIds: selection.requestedPluginIds,
2031
- ...(selection.projectIntentDigest
2032
- ? { projectIntentDigest: selection.projectIntentDigest }
2033
- : {}),
2034
- groups,
2035
- targetVersions: [...new Map(targetVersions.map((target) => [[
2036
- target.pluginId, target.platform, target.scope,
2037
- ].join('\0'), target])).values()].sort((left, right) => [
2038
- left.pluginId, left.platform, left.scope,
2039
- ].join('\0').localeCompare([
2040
- right.pluginId, right.platform, right.scope,
2041
- ].join('\0'), 'en')),
2042
- ...(merged.dependencyPlan ? { dependencyPlan: merged.dependencyPlan } : {}),
2043
- ...(merged.codexReconcilePlans ? { codexReconcilePlans: merged.codexReconcilePlans } : {}),
2044
- ...(merged.operationPlan ? { operationPlan: merged.operationPlan } : {}),
2045
- };
2046
- return evidence;
2047
- }
2048
- if (intent.kind === 'pluginWrite' && intent.action === 'cleanup') {
2049
- const preview = await cleanupWriteCommand([intent.pluginId], {
2050
- cwd: context.cwd,
2051
- platform: intent.platform,
2052
- scope: intent.scope,
2053
- force: intent.force === true,
2054
- }, {
2055
- ...context,
2056
- inventoryCatalogSourceReferences: operationInventory,
2057
- previewOperationPlan: true,
2058
- ...(codexObservationSession ? { codexObservationSession } : {}),
2059
- ...(preparation ? {
2060
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
2061
- } : {}),
2062
- });
2063
- signal?.throwIfAborted();
2064
- if (!preview.operationPlan) {
2065
- throw new TuiOperationBlockedError('cleanupPlanUnavailable', '无法建立可信的清理计划。');
2066
- }
2067
- const offlineCleanupPlans = preview.offlineCleanupPlans ?? [];
2068
- await preparation?.reporter.emit('evidence');
2069
- return {
2070
- catalog: {
2071
- configDigest: 'cleanup-offline',
2072
- compositeDigest: preview.operationPlan.planFingerprint,
2073
- },
2074
- operationPlan: preview.operationPlan,
2075
- offlineCleanupPlans,
2076
- ...(preview.removedPluginCleanupIds
2077
- ? { removedPluginCleanupIds: preview.removedPluginCleanupIds }
2078
- : {}),
2079
- ...(offlineCleanupPlans.length === 1
2080
- ? { offlineCleanup: offlineCleanupPlans[0] }
2081
- : {}),
2082
- targetFingerprint: preview.operationPlan.planFingerprint,
2083
- };
2084
- }
2085
1264
  const packageInfo = await readCliPackageInfo();
2086
1265
  let catalogOperation;
2087
1266
  let catalogOperationError;
@@ -2153,13 +1332,97 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2153
1332
  catalogOperationByEvidence.set(evidence, catalogOperation);
2154
1333
  return evidence;
2155
1334
  }
2156
- const configReferencePluginIds = intent.kind === 'configSave'
1335
+ if (intent.kind === 'configSave' && intent.retryApply) {
1336
+ await preparation?.reporter.emit('config');
1337
+ if (!catalogOperation)
1338
+ throw catalogOperationError ?? operationReviewStale('无法取得设置应用所需的 Catalog。');
1339
+ const selectedCatalogOperation = catalogOperation;
1340
+ const evidence = await withResolvedCatalogPlugin({
1341
+ selector: intent.pluginId, cliVersion: packageInfo.version, env: context.env,
1342
+ ...(selectedCatalogOperation ? { operationContext: selectedCatalogOperation } : {}),
1343
+ ...(signal ? { signal } : {}),
1344
+ consume: async ({ plugin, source }) => {
1345
+ const referencePlugins = await resolvePluginConfigReferencePlugins({
1346
+ sourceId: plugin.sourceId, sourceIdentity: plugin.sourceIdentity,
1347
+ logicalIdentity: plugin.catalogLogicalIdentity, env: context.env,
1348
+ operationContext: selectedCatalogOperation,
1349
+ });
1350
+ const prepared = await preparePluginConfigApply({
1351
+ plugin, scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1352
+ ...(intent.targets ? { targets: intent.targets } : {}),
1353
+ cwd: context.cwd, env: context.env, cliVersion: packageInfo.version,
1354
+ nativeCliRunner: context.nativeCliRunner,
1355
+ rebuildRuntime: {
1356
+ ...context,
1357
+ referencePlugins,
1358
+ ...(selectedCatalogOperation ? { catalogOperationContext: selectedCatalogOperation,
1359
+ expectedGenerationSetDigest: selectedCatalogOperation.generationSetDigest } : {}),
1360
+ ...(reviewedScriptTimeoutMs(intent) === undefined ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1361
+ },
1362
+ });
1363
+ try {
1364
+ signal?.throwIfAborted();
1365
+ const targets = prepared.plan.receipt.requestedTargets
1366
+ .filter((target) => !prepared.plan.receipt.targetResults.some((result) => result.pluginId === target.pluginId && result.agent === target.agent && result.scope === target.scope
1367
+ && (result.satisfaction === 'unsupported' || result.satisfaction === 'excluded')))
1368
+ .map((target) => ({
1369
+ pluginName: parsePluginId(target.pluginId).pluginName, platform: target.agent, scope: target.scope,
1370
+ }));
1371
+ return {
1372
+ catalog: {
1373
+ configDigest: selectedCatalogOperation.catalog.configDigest,
1374
+ compositeDigest: selectedCatalogOperation.catalog.compositeDigest,
1375
+ ...(selectedCatalogOperation ? { generationSetDigest: selectedCatalogOperation.generationSetDigest } : {}),
1376
+ },
1377
+ source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1378
+ config: {
1379
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1380
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, intent.platform),
1381
+ storeDigest: digest(prepared.evidence),
1382
+ impactState: 'known', affectedTargets: targets,
1383
+ commandPackages: prepared.commandPackages,
1384
+ applyEvidence: prepared.evidence, applyChanges: prepared.plan.changes,
1385
+ applyWarnings: prepared.plan.warnings,
1386
+ applyBlockers: [...new Map([
1387
+ ...prepared.plan.receipt.blockers, ...prepared.plan.receipt.errors,
1388
+ ...prepared.plan.receipt.targetResults.flatMap((target) => target.blockers),
1389
+ ].map((item) => [`${item.code}\0${item.message}`, item])).values()],
1390
+ preApplyAuthorization: prepared.preApplyAuthorization,
1391
+ sharedNativeRenderTargets: await configRetrySharedNativeRenderTargets(plugin, targets.filter((target) => target.platform === 'codex' || target.platform === 'claude'), context.env, referencePlugins),
1392
+ },
1393
+ };
1394
+ }
1395
+ finally {
1396
+ await disposePluginConfigApply(prepared.plan);
1397
+ }
1398
+ },
1399
+ });
1400
+ if (catalogOperation)
1401
+ catalogOperationByEvidence.set(evidence, catalogOperation);
1402
+ return evidence;
1403
+ }
1404
+ const needsReferencePlugins = intent.kind === 'configSave'
1405
+ && await withResolvedCatalogPlugin({
1406
+ selector: intent.pluginId, cliVersion: packageInfo.version, env: context.env,
1407
+ ...(catalogOperation ? { operationContext: catalogOperation } : {}),
1408
+ ...(signal ? { signal } : {}),
1409
+ consume: ({ plugin }) => configNeedsReferencePlugins(plugin),
1410
+ });
1411
+ const referencePlugins = needsReferencePlugins
1412
+ ? await resolvePluginConfigReferencePlugins({
1413
+ sourceId: parsePluginId(intent.pluginId).sourceId,
1414
+ env: context.env,
1415
+ operationContext: catalogOperation,
1416
+ })
1417
+ : undefined;
1418
+ const configReferencePluginIds = intent.kind === 'configSave' && needsReferencePlugins
2157
1419
  ? await withResolvedCatalogPlugin({
2158
1420
  selector: intent.pluginId, cliVersion: packageInfo.version, env: context.env,
2159
1421
  ...(catalogOperation ? { operationContext: catalogOperation } : {}),
2160
1422
  ...(signal ? { signal } : {}),
2161
- consume: async ({ plugin }) => (await resolvePluginConfigReferenceConsumers({ plugin, env: context.env }))
2162
- .map((entry) => entry.pluginId ?? `${entry.sourceId}/${entry.manifest.name}`),
1423
+ consume: async ({ plugin }) => (await resolvePluginConfigReferenceConsumers({
1424
+ plugin, env: context.env, plugins: referencePlugins,
1425
+ })).map((entry) => entry.pluginId ?? `${entry.sourceId}/${entry.manifest.name}`),
2163
1426
  })
2164
1427
  : [];
2165
1428
  if (preparation && intent.kind === 'configSave') {
@@ -2172,7 +1435,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2172
1435
  if (configSnapshot.digest !== sessionStatusResult.payload.catalog.configDigest) {
2173
1436
  throw operationReviewStale('准备设置确认期间来源配置发生变化。');
2174
1437
  }
2175
- const relevantTargets = configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope, configReferencePluginIds);
1438
+ const relevantTargets = configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.context ?? 'user', configReferencePluginIds, intent.platform);
2176
1439
  const preparedCatalogOperation = catalogOperation;
2177
1440
  const selected = await withResolvedCatalogPlugin({
2178
1441
  selector: intent.pluginId,
@@ -2185,21 +1448,23 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2185
1448
  if (!plugin.manifest.config) {
2186
1449
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2187
1450
  }
1451
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2188
1452
  if (intent.kind === 'configSave') {
2189
1453
  await coercePluginConfigUpdates({
2190
- schema: plugin.manifest.config.fields,
1454
+ schema: definition.fields,
2191
1455
  updates: intent.updates ?? {},
2192
1456
  cwd: context.cwd,
2193
1457
  });
2194
1458
  for (const key of intent.removeKeys ?? []) {
2195
- if (!plugin.manifest.config.fields[key]) {
1459
+ if (!definition.fields[key]) {
2196
1460
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2197
1461
  }
2198
1462
  }
2199
1463
  }
2200
1464
  const impact = intent.retryApply ? undefined : await analyzePluginConfigMutationImpact({
2201
1465
  plugin,
2202
- scope: intent.scope,
1466
+ referencePlugins,
1467
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2203
1468
  cwd: context.cwd,
2204
1469
  env: context.env,
2205
1470
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
@@ -2213,17 +1478,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2213
1478
  ? configImpactVisibleTargets(plugin.manifest.name, relevantTargets)
2214
1479
  : impact.affectedTargets;
2215
1480
  const commandPackages = impact?.affectedCommandPackages
2216
- ?? (await discoverPluginConfigManagedCommandPackages({ plugin, scope: intent.scope,
2217
- cwd: context.cwd, env: context.env ?? process.env, platformTargets: affectedTargets })).packages;
1481
+ ?? (await discoverPluginConfigManagedCommandPackages({ plugin, scope: intent.context ?? 'user',
1482
+ cwd: context.cwd, env: context.env ?? process.env, platformTargets: affectedTargets, referencePlugins })).packages;
2218
1483
  const preApplyAuthorization = configIntentRequestsApply(intent)
2219
1484
  ? (await resolveConfigPreApplyAuthorization({
2220
1485
  plugin,
2221
- scope: intent.scope,
1486
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2222
1487
  cwd: context.cwd,
2223
1488
  env: context.env,
2224
1489
  applyTargets: affectedTargets,
2225
1490
  nativeCliRunner: context.nativeCliRunner,
2226
1491
  rebuildRuntime: {
1492
+ referencePlugins,
2227
1493
  catalogOperationContext: preparedCatalogOperation,
2228
1494
  expectedGenerationSetDigest: preparedCatalogOperation.generationSetDigest,
2229
1495
  inventoryCatalogSourceReferences: operationInventory,
@@ -2233,13 +1499,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2233
1499
  return {
2234
1500
  pluginName: plugin.manifest.name,
2235
1501
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1502
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, intent.platform),
2236
1503
  impact,
2237
1504
  commandPackages,
2238
1505
  preApplyAuthorization,
2239
- retryStoreDigest: intent.retryApply ? await configRetryStoreDigest({ plugin, scope: intent.scope,
2240
- targets: affectedTargets, commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env }) : undefined,
1506
+ retryStoreDigest: intent.retryApply ? await configRetryStoreDigest({ plugin, scope: intent.context ?? 'user', platform: intent.platform,
1507
+ targets: affectedTargets, commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env, plugins: referencePlugins }) : undefined,
2241
1508
  sharedNativeRenderTargets: intent.retryApply
2242
- ? await configRetrySharedNativeRenderTargets(plugin, affectedTargets, context.env)
1509
+ ? await configRetrySharedNativeRenderTargets(plugin, affectedTargets, context.env, referencePlugins)
2243
1510
  : impact.sharedNativeRenderTargets,
2244
1511
  };
2245
1512
  },
@@ -2258,8 +1525,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2258
1525
  ...(selected.source ? { source: selected.source } : {}),
2259
1526
  targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
2260
1527
  config: {
2261
- scope: intent.scope,
2262
- ...(selected.source?.manifestDigest ? { schemaIdentity: selected.source.manifestDigest } : {}),
1528
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1529
+ schemaIdentity: selected.schemaIdentity,
2263
1530
  storeDigest: selected.impact?.storeDigest
2264
1531
  ?? selected.retryStoreDigest,
2265
1532
  impactState: selected.impact?.impactState ?? 'known',
@@ -2275,6 +1542,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2275
1542
  : {}),
2276
1543
  },
2277
1544
  };
1545
+ await assertConfigEditEvidence(intent, evidence);
2278
1546
  if (catalogOperation)
2279
1547
  catalogOperationByEvidence.set(evidence, catalogOperation);
2280
1548
  return evidence;
@@ -2283,17 +1551,11 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2283
1551
  const statusCatalogOperation = catalogOperation
2284
1552
  ?? (!freshCatalog ? sessionCatalogOperationContext : undefined);
2285
1553
  const status = await statusCommandV8({
2286
- plugins: [...new Set([intent.pluginId, ...configReferencePluginIds,
2287
- ...(intent.kind === 'pluginWrite' && intent.originIssue?.consumerPluginId
2288
- ? [intent.originIssue.consumerPluginId] : [])])],
2289
- ...('platform' in intent ? { platform: intent.platform } : {}),
2290
- ...(intent.kind !== 'pluginWrite' && intent.scope === 'project'
2291
- ? { scope: intent.scope }
2292
- : {}),
1554
+ plugins: [...new Set([intent.pluginId, ...configReferencePluginIds])],
1555
+ ...(intent.context === 'project' ? {} : { scope: 'user' }),
2293
1556
  cwd: context.cwd,
2294
1557
  env: context.env,
2295
1558
  nativeCliRunner: context.nativeCliRunner,
2296
- ...(codexObservationSession ? { codexObservationSession } : {}),
2297
1559
  cliVersion: packageInfo.version,
2298
1560
  ...(statusCatalogOperation ? { catalogOperationContext: statusCatalogOperation } : {}),
2299
1561
  inventoryCatalogSourceReferences: operationInventory,
@@ -2315,12 +1577,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2315
1577
  ...(signal ? { signal } : {}),
2316
1578
  });
2317
1579
  }
2318
- let dependencyPlan;
2319
- let codexReconcilePlans;
2320
- let operationPlan;
2321
1580
  await preparation?.reporter.emit('catalog');
2322
1581
  const configVisibleTargets = intent.kind === 'configSave'
2323
- ? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.scope, configReferencePluginIds)
1582
+ ? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.context ?? 'user', configReferencePluginIds, intent.platform)
2324
1583
  : [];
2325
1584
  const selected = await withResolvedCatalogPlugin({
2326
1585
  selector: intent.pluginId,
@@ -2335,13 +1594,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2335
1594
  if (!plugin.manifest.config) {
2336
1595
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2337
1596
  }
1597
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2338
1598
  await coercePluginConfigUpdates({
2339
- schema: plugin.manifest.config.fields,
1599
+ schema: definition.fields,
2340
1600
  updates: intent.updates ?? {},
2341
1601
  cwd: context.cwd,
2342
1602
  });
2343
1603
  for (const key of intent.removeKeys ?? []) {
2344
- if (!plugin.manifest.config.fields[key]) {
1604
+ if (!definition.fields[key]) {
2345
1605
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2346
1606
  }
2347
1607
  }
@@ -2349,7 +1609,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2349
1609
  const impact = intent.kind === 'configSave' && !intent.retryApply
2350
1610
  ? await analyzePluginConfigMutationImpact({
2351
1611
  plugin,
2352
- scope: intent.scope,
1612
+ referencePlugins,
1613
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2353
1614
  cwd: context.cwd,
2354
1615
  env: context.env,
2355
1616
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets),
@@ -2365,14 +1626,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2365
1626
  : [];
2366
1627
  const commandPackages = impact?.affectedCommandPackages
2367
1628
  ?? (intent.kind === 'configSave' ? (await discoverPluginConfigManagedCommandPackages({
2368
- plugin, scope: intent.scope, cwd: context.cwd, env: context.env ?? process.env,
1629
+ plugin, scope: intent.context ?? 'user', cwd: context.cwd, env: context.env ?? process.env,
2369
1630
  platformTargets: configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets),
1631
+ referencePlugins,
2370
1632
  })).packages : []);
2371
1633
  const preApplyAuthorization = intent.kind === 'configSave'
2372
1634
  && configIntentRequestsApply(intent)
2373
1635
  ? (await resolveConfigPreApplyAuthorization({
2374
1636
  plugin,
2375
- scope: intent.scope,
1637
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2376
1638
  cwd: context.cwd,
2377
1639
  env: context.env,
2378
1640
  applyTargets: intent.retryApply
@@ -2380,6 +1642,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2380
1642
  : impact.affectedTargets,
2381
1643
  nativeCliRunner: context.nativeCliRunner,
2382
1644
  rebuildRuntime: {
1645
+ referencePlugins,
2383
1646
  ...(catalogOperation ? { catalogOperationContext: catalogOperation } : {}),
2384
1647
  ...(catalogOperation?.generationSetDigest
2385
1648
  ? { expectedGenerationSetDigest: catalogOperation.generationSetDigest }
@@ -2391,120 +1654,21 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2391
1654
  return {
2392
1655
  pluginName: plugin.manifest.name,
2393
1656
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1657
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, intent.platform),
2394
1658
  impact,
2395
1659
  commandPackages,
2396
1660
  preApplyAuthorization,
2397
1661
  retryStoreDigest: intent.kind === 'configSave' && intent.retryApply
2398
- ? await configRetryStoreDigest({ plugin, scope: intent.scope, targets: affectedTargets,
2399
- commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env }) : undefined,
1662
+ ? await configRetryStoreDigest({ plugin, scope: intent.context ?? 'user', platform: intent.platform, targets: affectedTargets,
1663
+ commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env, plugins: referencePlugins }) : undefined,
2400
1664
  sharedNativeRenderTargets: intent.kind === 'configSave'
2401
1665
  ? intent.retryApply
2402
- ? await configRetrySharedNativeRenderTargets(plugin, configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets), context.env)
1666
+ ? await configRetrySharedNativeRenderTargets(plugin, configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets), context.env, referencePlugins)
2403
1667
  : impact.sharedNativeRenderTargets
2404
1668
  : [],
2405
1669
  };
2406
1670
  },
2407
1671
  });
2408
- if (intent.kind === 'pluginWrite' && intent.expectedUpdate) {
2409
- const revision = status.targetRevisions.find((entry) => entry.pluginId === intent.pluginId && entry.platform === intent.platform
2410
- && entry.scope === intent.scope);
2411
- const generation = catalogOperation?.generationSet.sources.find((entry) => entry.sourceId === selected.source.sourceId);
2412
- if (intent.action !== 'sync'
2413
- || !('pluginVersion' in selected.source)
2414
- || selected.source.sourceIdentity !== intent.expectedUpdate.sourceIdentity
2415
- || selected.source.pluginVersion !== intent.expectedUpdate.activeVersion
2416
- || generation?.storeKey !== intent.expectedUpdate.generationKey
2417
- || revision?.revision.installedVersion !== intent.expectedUpdate.installedVersion
2418
- || revision?.revision.availableVersion !== intent.expectedUpdate.activeVersion
2419
- || revision.revision.state !== 'upgradeAvailable') {
2420
- throw operationReviewStale('插件更新版本或 Active Catalog 证据已变化。');
2421
- }
2422
- }
2423
- let codexManager;
2424
- let resolvedCodexManager;
2425
- const hasDeclaredDependencies = (status.pluginSummaries.find((summary) => summary.pluginId === intent.pluginId)?.dependencyTotalCount ?? 0) > 0;
2426
- if (intent.kind === 'pluginWrite') {
2427
- const command = {
2428
- install: installWriteCommand,
2429
- refreshAndSync: syncWriteCommand,
2430
- reinstall: reinstallWriteCommand,
2431
- uninstall: uninstallWriteCommand,
2432
- cleanup: cleanupWriteCommand,
2433
- sync: syncWriteCommand,
2434
- }[intent.action];
2435
- const preview = await command([intent.pluginId], {
2436
- cwd: context.cwd,
2437
- platform: intent.platform,
2438
- scope: intent.scope,
2439
- ...(reviewedScriptTimeoutMs(intent) === undefined
2440
- ? {}
2441
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
2442
- ...(intent.action === 'install' && intent.dependencyScopeOverrides
2443
- ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
2444
- : {}),
2445
- ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
2446
- }, {
2447
- ...context,
2448
- inventoryCatalogSourceReferences: operationInventory,
2449
- expectedCatalogConfigDigest: status.payload.catalog.configDigest,
2450
- ...(catalogOperation ? {
2451
- catalogOperationContext: catalogOperation,
2452
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
2453
- } : {}),
2454
- previewOperationPlan: true,
2455
- ...(codexObservationSession ? { codexObservationSession } : {}),
2456
- ...(preparation ? {
2457
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
2458
- } : {}),
2459
- });
2460
- signal?.throwIfAborted();
2461
- codexReconcilePlans = preview?.codexReconcilePlans;
2462
- operationPlan = preview?.operationPlan;
2463
- if (hasDeclaredDependencies
2464
- && (intent.action === 'install'
2465
- || intent.action === 'refreshAndSync'
2466
- || intent.action === 'reinstall'
2467
- || intent.action === 'sync')) {
2468
- dependencyPlan = preview.dependencyPlan;
2469
- if (!dependencyPlan) {
2470
- throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
2471
- }
2472
- const blocker = dependencyPlan.targets.find((target) => target.action === 'block');
2473
- if (blocker) {
2474
- const code = blocker.errorCode ?? 'dependencyNotReady';
2475
- const reason = code === 'dependencyScopeConflict' || code === 'codexExtensionScopeConflict'
2476
- ? '这个依赖的安装位置存在冲突,需要先选择保留的位置。'
2477
- : code === 'artifactCollision' || code === 'exclusiveOwnerEvidenceInvalid'
2478
- ? '这个依赖的安装位置被其他内容占用,需要先检查并处理冲突。'
2479
- : '这个依赖尚不可用,需要先检查并恢复后再安装。';
2480
- throw new TuiOperationBlockedError(code, `${blocker.pluginId}:${reason}`, [{
2481
- id: `dependency:${blocker.pluginId}:${blocker.platform}:${blocker.resolvedScope}`,
2482
- code, stage: 'dependencyPlan', safeMessage: reason,
2483
- target: { pluginId: blocker.pluginId, platform: blocker.platform, scope: blocker.resolvedScope },
2484
- causes: [], detailLines: [],
2485
- }]);
2486
- }
2487
- }
2488
- const managerPreview = preview?.codexManagerPreview;
2489
- if (managerPreview?.state === 'blocked') {
2490
- const message = managerPreview.code === 'codexSystemCliUnsupported'
2491
- ? '系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
2492
- : 'AW-managed Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。';
2493
- throw new TuiOperationBlockedError(managerPreview.code, `${managerPreview.code}: ${message}`);
2494
- }
2495
- if (managerPreview) {
2496
- if (managerPreview.state === 'system' || managerPreview.state === 'cached') {
2497
- resolvedCodexManager = managerPreview.manager;
2498
- }
2499
- codexManager = {
2500
- state: managerPreview.state,
2501
- packageName: MANAGED_CODEX_PACKAGE_NAME,
2502
- version: managerPreview.state === 'requires-install'
2503
- ? MANAGED_CODEX_VERSION
2504
- : managerPreview.manager.version,
2505
- };
2506
- }
2507
- }
2508
1672
  await preparation?.reporter.emit('evidence');
2509
1673
  signal?.throwIfAborted();
2510
1674
  const evidenceTargets = intent.kind === 'configSave'
@@ -2524,10 +1688,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2524
1688
  : {}),
2525
1689
  },
2526
1690
  source: selected.source,
2527
- ...(dependencyPlan ? { dependencyPlan } : {}),
2528
- ...(codexReconcilePlans ? { codexReconcilePlans } : {}),
2529
- ...(operationPlan ? { operationPlan } : {}),
2530
- ...(codexManager ? { codexManager } : {}),
2531
1691
  targetFingerprint: statusTargetFingerprint(evidenceTargets, evidenceRevisions),
2532
1692
  };
2533
1693
  if (intent.kind === 'configSave') {
@@ -2537,10 +1697,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2537
1697
  throw operationReviewStale('设置操作缺少实际配置输入证据。');
2538
1698
  signal?.throwIfAborted();
2539
1699
  evidence.config = {
2540
- scope: intent.scope,
2541
- ...('manifestDigest' in selected.source
2542
- ? { schemaIdentity: selected.source.manifestDigest }
2543
- : {}),
1700
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1701
+ schemaIdentity: selected.schemaIdentity,
2544
1702
  storeDigest,
2545
1703
  impactState: impact?.impactState ?? 'known',
2546
1704
  ...(impact?.noEffectiveChange ? { noEffectiveChange: true } : {}),
@@ -2548,15 +1706,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2548
1706
  ? { commandPackages: selected.commandPackages } : {}),
2549
1707
  affectedTargets: (impact?.affectedTargets
2550
1708
  ?? evidenceTargets).map(({ pluginName, platform, scope }) => ({ pluginName, platform, scope })),
2551
- ...(intent.scope === 'project' ? {
2552
- shareability: await inspectConfigShareability({
2553
- sourceId: selected.source.sourceId,
2554
- pluginName: selected.pluginName,
2555
- scope: intent.scope,
2556
- cwd: context.cwd,
2557
- env: context.env,
2558
- }),
2559
- } : {}),
2560
1709
  ...('sharedNativeRenderTargets' in selected
2561
1710
  && selected.sharedNativeRenderTargets.length > 0
2562
1711
  ? { sharedNativeRenderTargets: selected.sharedNativeRenderTargets }
@@ -2567,13 +1716,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2567
1716
  : {}),
2568
1717
  };
2569
1718
  }
1719
+ await assertConfigEditEvidence(intent, evidence);
2570
1720
  if (catalogOperation)
2571
1721
  catalogOperationByEvidence.set(evidence, catalogOperation);
2572
- if (resolvedCodexManager)
2573
- codexManagerByEvidence.set(evidence, resolvedCodexManager);
2574
1722
  return evidence;
2575
1723
  };
2576
1724
  const dependencies = {
1725
+ readAgentSettings: () => readAgentSettings(context),
1726
+ previewAgentSettings: (preferences, pluginId) => previewAgentSettings(context, preferences, pluginId),
1727
+ saveAgentSettings: (request) => saveAgentSettings(context, request),
2577
1728
  beginCatalogSession(sessionOptions) {
2578
1729
  sessionCatalogOperationContext = undefined;
2579
1730
  if (sessionOptions?.reuseValidatedGenerations !== true) {
@@ -2582,6 +1733,104 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2582
1733
  }
2583
1734
  },
2584
1735
  loadStatus,
1736
+ async loadScopedStatus(scope, signal) {
1737
+ signal.throwIfAborted();
1738
+ const packageInfo = await readCliPackageInfo();
1739
+ const currentCatalog = await resolvePartialCatalogOperationContext({
1740
+ env: context.env, signal, validatedStoreEntries, validatingStoreEntries,
1741
+ });
1742
+ signal.throwIfAborted();
1743
+ if (sessionCatalogOperationContext
1744
+ && currentCatalog.generationSetDigest !== sessionCatalogOperationContext.generationSetDigest) {
1745
+ throw new TuiOperationBlockedError('catalogSessionReloadRequired', 'Catalog 已变化。请按 H 重新加载,再检查清单。');
1746
+ }
1747
+ const catalogOperationContext = sessionCatalogOperationContext ?? currentCatalog;
1748
+ let nativeSnapshot;
1749
+ const status = await readRecoveryStatus({
1750
+ scope, cwd: context.cwd, env: context.env, cliVersion: packageInfo.version,
1751
+ nativeCliRunner: context.nativeCliRunner,
1752
+ catalogOperationContext, signal,
1753
+ onNativeStatus: async (native, demand) => {
1754
+ signal.throwIfAborted();
1755
+ const dependencyOverviews = await loadDependencyOverviews({ context,
1756
+ cliVersion: packageInfo.version, status: native, signal,
1757
+ catalogOperationContext: demand.catalogOperationContext,
1758
+ });
1759
+ nativeSnapshot = { payload: native.payload, pluginSummaries: native.pluginSummaries, targetRevisions: native.targetRevisions,
1760
+ dependencyOverviews };
1761
+ },
1762
+ });
1763
+ signal.throwIfAborted();
1764
+ if (!nativeSnapshot)
1765
+ throw new Error('nativeObservationUnavailable');
1766
+ return { ...status, nativeSnapshot };
1767
+ },
1768
+ async prepareScopedTask(request, signal) {
1769
+ signal.throwIfAborted();
1770
+ if (request.kind === 'targets') {
1771
+ return prepareTargetsPlan({ scope: request.scope, cwd: context.cwd, env: context.env }, request.action, request.agents);
1772
+ }
1773
+ if (request.kind === 'intent') {
1774
+ return prepareIntentPlan({ scope: request.scope, cwd: context.cwd, env: context.env }, request.action, request.selector);
1775
+ }
1776
+ if (request.kind === 'retire') {
1777
+ const packageInfo = await readCliPackageInfo();
1778
+ signal.throwIfAborted();
1779
+ const plan = await prepareRetire({ scope: request.scope, cwd: context.cwd, env: context.env,
1780
+ signal,
1781
+ selector: request.selector, cliVersion: packageInfo.version,
1782
+ ...(request.targets ? { targets: request.targets } : {}),
1783
+ deleteConfig: request.deleteConfig === true,
1784
+ }, context);
1785
+ if (signal.aborted) {
1786
+ await plan.context.dispose();
1787
+ signal.throwIfAborted();
1788
+ }
1789
+ return { kind: 'retire', scope: request.scope, plan };
1790
+ }
1791
+ const packageInfo = await readCliPackageInfo();
1792
+ signal.throwIfAborted();
1793
+ const plan = await prepareRecovery(request.command ?? 'sync', request.selectors, {
1794
+ signal,
1795
+ scope: request.scope, cwd: context.cwd, env: context.env,
1796
+ cliVersion: packageInfo.version,
1797
+ all: request.selectors.length === 0,
1798
+ ...(request.targets ? { targets: request.targets } : {}),
1799
+ ...(request.force ? { force: true } : {}),
1800
+ ...(request.refresh ? { refresh: true } : {}),
1801
+ ...(request.retireUnselectedTargets ? { retireUnselectedTargets: true } : {}),
1802
+ ...(request.scriptTimeoutMs !== undefined ? { scriptTimeoutMs: request.scriptTimeoutMs } : {}),
1803
+ ...(request.dependencyScopeOverrides?.length
1804
+ ? { dependencyScopeOverrides: request.dependencyScopeOverrides } : {}),
1805
+ ...(request.exactTargets ? { exactTargets: request.exactTargets } : {}),
1806
+ ...(request.saveDependencyScopeOverrides ? { saveDependencyScopeOverrides: true } : {}),
1807
+ }, context);
1808
+ if (signal.aborted) {
1809
+ await plan.context.dispose();
1810
+ signal.throwIfAborted();
1811
+ }
1812
+ return { kind: 'recovery', scope: request.scope, plan };
1813
+ },
1814
+ async executeScopedTask(plan) {
1815
+ if (plan.kind === 'targets') {
1816
+ const result = await executeTargetsPlan({ scope: plan.scope, cwd: context.cwd, env: context.env }, plan);
1817
+ return { kind: 'targets', ...result };
1818
+ }
1819
+ if (plan.kind === 'intent') {
1820
+ const receipt = await executeIntentPlan({ scope: plan.scope, cwd: context.cwd, env: context.env }, plan);
1821
+ return { kind: 'recovery', receipt };
1822
+ }
1823
+ if (plan.kind === 'retire') {
1824
+ const receipt = await executeRetire(plan.plan, { yes: true, runtime: context });
1825
+ return { kind: 'recovery', receipt };
1826
+ }
1827
+ if ((plan.plan.sourceRefreshes?.length ?? 0) > 0) {
1828
+ const refreshed = await activateRecoverySourcesAndReprepare(plan.plan, { runtime: context });
1829
+ return { kind: 'recoveryReview', plan: { kind: 'recovery', scope: plan.scope, plan: refreshed } };
1830
+ }
1831
+ const receipt = await executeRecovery(plan.plan, { yes: true, runtime: context });
1832
+ return { kind: 'recovery', receipt };
1833
+ },
2585
1834
  loadPluginContent,
2586
1835
  async probeCatalogFreshness(sourceId, freshnessOptions, signal) {
2587
1836
  signal.throwIfAborted();
@@ -2630,55 +1879,42 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2630
1879
  now: options.now ?? Date.now,
2631
1880
  });
2632
1881
  try {
2633
- const preparationWarnings = [];
2634
- if (intent.kind === 'pluginWrite' && intent.action === 'refreshAndSync') {
2635
- await reporter.emit('refresh');
2636
- const packageInfo = await readCliPackageInfo();
2637
- const sourceId = parsePluginId(intent.pluginId).sourceId;
2638
- await syncCatalogSourceGeneration({
2639
- sourceId,
2640
- cliVersion: packageInfo.version,
2641
- cwd: context.cwd,
2642
- env: context.env,
2643
- signal,
2644
- }).catch((error) => {
2645
- preparationWarnings.push(`catalogSourceRefreshFailed: ${sourceId}: ${error instanceof Error ? error.message : String(error)}`);
2646
- });
2647
- sessionCatalogOperationContext = undefined;
2648
- sessionStatusResult = undefined;
2649
- validatedStoreEntries.clear();
2650
- validatingStoreEntries.clear();
2651
- }
2652
- const taskObservationSession = intent.kind === 'pluginWriteTask'
2653
- && intent.targets.some((target) => target.platform === 'codex')
2654
- ? await createCodexNativeObservationSession({
2655
- exactCwd: context.cwd,
2656
- env: context.env,
2657
- runner: context.nativeCliRunner,
2658
- })
2659
- : undefined;
2660
- let evidence;
2661
- try {
2662
- evidence = await captureEvidence(intent, signal, {
2663
- reporter,
2664
- validateIntent: true,
2665
- }, createOperationInventoryReader(), intent.kind === 'pluginWrite'
2666
- && intent.action === 'refreshAndSync', reusableCandidate, taskObservationSession);
2667
- }
2668
- finally {
2669
- await taskObservationSession?.dispose();
2670
- }
2671
- if (preparationWarnings.length > 0)
2672
- evidence.preparationWarnings = preparationWarnings;
2673
- return evidence;
1882
+ return await captureEvidence(intent, signal, {
1883
+ reporter,
1884
+ validateIntent: true,
1885
+ }, createOperationInventoryReader(), false, reusableCandidate);
2674
1886
  }
2675
1887
  finally {
2676
1888
  await reporter.finish();
2677
1889
  }
2678
1890
  },
2679
1891
  async previewDependencyPlacement(intent, signal) {
2680
- const evidence = await captureEvidence(intent, signal);
2681
- return evidence.dependencyPlan;
1892
+ const targets = intent.platform === 'both' ? ['codex', 'claude']
1893
+ : [intent.platform === 'claude' ? 'claude' : 'codex'];
1894
+ const prepared = await dependencies.prepareScopedTask({
1895
+ kind: 'recovery', command: 'install', scope: intent.scope,
1896
+ selectors: [intent.pluginId], targets: [...targets],
1897
+ dependencyScopeOverrides: intent.dependencyScopeOverrides,
1898
+ }, signal);
1899
+ if (prepared.kind !== 'recovery')
1900
+ throw new Error('dependencyPlacementUnavailable');
1901
+ try {
1902
+ const plans = prepared.plan.nativeGroups.flatMap((group) => group.preview.dependencyPlan ? [group.preview.dependencyPlan] : []);
1903
+ if (plans.length === 0) {
1904
+ throw new Error(prepared.plan.receipt.blockers[0]?.code
1905
+ ?? prepared.plan.receipt.targetResults[0]?.blockers[0]?.code ?? 'dependencyPlacementUnavailable');
1906
+ }
1907
+ return {
1908
+ requestedPluginIds: [...new Set(plans.flatMap((plan) => plan.requestedPluginIds))],
1909
+ resolvedPluginIds: [...new Set(plans.flatMap((plan) => plan.resolvedPluginIds))],
1910
+ dependencyEdges: plans.flatMap((plan) => plan.dependencyEdges),
1911
+ scopeOverrides: plans.flatMap((plan) => plan.scopeOverrides),
1912
+ targets: plans.flatMap((plan) => plan.targets),
1913
+ };
1914
+ }
1915
+ finally {
1916
+ await prepared.plan.context.dispose();
1917
+ }
2682
1918
  },
2683
1919
  async validateIntent(intent, signal) {
2684
1920
  if (intent.kind === 'catalogSourcesRecovery') {
@@ -2717,13 +1953,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2717
1953
  if (!plugin.manifest.config) {
2718
1954
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2719
1955
  }
1956
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2720
1957
  await coercePluginConfigUpdates({
2721
- schema: plugin.manifest.config.fields,
1958
+ schema: definition.fields,
2722
1959
  updates: intent.updates ?? {},
2723
1960
  cwd: context.cwd,
2724
1961
  });
2725
1962
  for (const key of intent.removeKeys ?? []) {
2726
- if (!plugin.manifest.config.fields[key]) {
1963
+ if (!definition.fields[key]) {
2727
1964
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2728
1965
  }
2729
1966
  }
@@ -2763,39 +2000,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2763
2000
  data: { code },
2764
2001
  });
2765
2002
  },
2766
- async getConfigGroups(pluginId, layer, signal) {
2767
- const packageInfo = await readCliPackageInfo();
2768
- return withResolvedCatalogPlugin({
2769
- selector: pluginId,
2770
- cliVersion: packageInfo.version,
2771
- env: context.env,
2772
- signal,
2773
- ...(sessionContextForPlugin(pluginId)
2774
- ? { operationContext: sessionContextForPlugin(pluginId) }
2775
- : {}),
2776
- consume: async ({ plugin }) => {
2777
- if (!plugin.manifest.config) {
2778
- throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2779
- }
2780
- const read = await resolveConfigRead({
2781
- pluginName: plugin.manifest.name,
2782
- plugin,
2783
- scope: layer.scope,
2784
- cwd: context.cwd,
2785
- env: context.env,
2786
- });
2787
- const effectiveRead = await resolveConfigRead({
2788
- pluginName: plugin.manifest.name,
2789
- plugin,
2790
- scope: 'effective',
2791
- cwd: context.cwd,
2792
- env: context.env,
2793
- });
2794
- return buildConfigGroups({ schema: plugin.manifest.config, read, effectiveRead });
2795
- },
2796
- });
2003
+ async getConfigGroups(pluginId, config, signal) {
2004
+ return (await dependencies.getConfigPage(pluginId, config, signal)).groups;
2797
2005
  },
2798
- async getConfigPage(pluginId, layer, signal) {
2006
+ async getConfigPage(pluginId, config, signal) {
2007
+ if (config.context === 'project')
2008
+ await resolveWorkContext({ cwd: context.cwd, context: 'project' });
2799
2009
  const packageInfo = await readCliPackageInfo();
2800
2010
  return withResolvedCatalogPlugin({
2801
2011
  selector: pluginId,
@@ -2809,112 +2019,107 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2809
2019
  if (!plugin.manifest.config) {
2810
2020
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2811
2021
  }
2022
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, config.platform);
2812
2023
  const stores = await readConfigStoresForTui({
2813
2024
  sourceId: plugin.sourceId,
2814
2025
  sourceIdentity: plugin.sourceIdentity,
2815
2026
  logicalIdentity: plugin.catalogLogicalIdentity,
2816
2027
  pluginName: plugin.manifest.name,
2817
- schema: plugin.manifest.config.fields,
2818
- scope: layer.scope,
2028
+ schema: definition.fields,
2029
+ platform: config.platform,
2819
2030
  signal,
2820
2031
  });
2821
2032
  const userStore = stores.user;
2822
- const projectStore = stores.project;
2823
2033
  const read = await resolveConfigRead({
2824
2034
  pluginName: plugin.manifest.name,
2825
2035
  plugin,
2826
- scope: layer.scope,
2036
+ scope: config.view === 'effective' ? 'effective' : 'user',
2037
+ platform: config.platform,
2038
+ context: config.context ?? 'user',
2827
2039
  cwd: context.cwd,
2828
2040
  env: context.env,
2829
2041
  });
2830
- const effectiveRead = layer.scope === 'effective' ? read : await resolveConfigRead({
2042
+ const effectiveRead = config.view === 'effective' ? read : await resolveConfigRead({
2831
2043
  pluginName: plugin.manifest.name,
2832
2044
  plugin,
2833
2045
  scope: 'effective',
2046
+ platform: config.platform,
2047
+ context: config.context ?? 'user',
2834
2048
  cwd: context.cwd,
2835
2049
  env: context.env,
2836
2050
  });
2837
- const layerReads = layer.scope === 'effective' ? undefined : {
2838
- user: layer.scope === 'user' ? read : await resolveConfigRead({
2839
- pluginName: plugin.manifest.name,
2840
- plugin,
2841
- scope: 'user',
2842
- cwd: context.cwd,
2843
- env: context.env,
2844
- }),
2845
- ...(layer.scope === 'user' ? {} : { project: read }),
2846
- };
2847
2051
  const effectiveRaw = await resolveEffectivePluginConfig({
2848
2052
  sourceId: plugin.sourceId,
2849
2053
  sourceIdentity: plugin.sourceIdentity,
2850
2054
  logicalIdentity: plugin.catalogLogicalIdentity,
2851
2055
  pluginName: plugin.manifest.name,
2852
2056
  manifest: plugin.manifest,
2853
- targetScope: layer.scope === 'user' ? 'user' : 'project',
2057
+ platform: config.platform,
2058
+ context: config.context ?? 'user',
2854
2059
  cwd: context.cwd,
2855
2060
  env: context.env,
2856
2061
  });
2857
- const layerStore = layer.scope === 'effective'
2858
- ? undefined
2859
- : layer.scope === 'user' ? userStore : projectStore;
2860
2062
  const page = buildConfigPage({
2861
2063
  pluginId,
2862
- schema: plugin.manifest.config,
2064
+ context: config.context ?? 'user',
2065
+ schema: definition,
2863
2066
  read,
2864
2067
  effectiveRead,
2865
- ...(layerReads ? { layerReads } : {}),
2866
- ...(layerStore ? { rawValues: layerStore.values } : {}),
2068
+ ...(config.view === 'effective' ? {} : { rawValues: userStore.values }),
2867
2069
  rawLayerValues: {
2868
- user: userStore.values,
2869
- ...(projectStore ? { project: projectStore.values } : {}),
2070
+ common: userStore.common ?? {},
2071
+ ...(userStore.platforms ?? {}),
2870
2072
  },
2871
2073
  effectiveRawValues: effectiveRaw.values,
2872
- sharedNativeRefreshKeys: new Set((plugin.manifest.artifacts?.targets ?? []).flatMap((target) => target.target === 'codex.skill' && target.delivery.mode === 'codex-native'
2074
+ sharedNativeRefreshKeys: new Set((plugin.manifest.artifacts?.targets ?? []).flatMap((target) => (target.target === 'codex.skill' || target.target === 'claude.skill')
2075
+ && (!config.platform || target.platform === config.platform)
2873
2076
  ? target.refreshKeys
2874
2077
  : [])),
2875
2078
  });
2876
2079
  return { ...page, editEvidence: {
2080
+ platform: config.platform,
2081
+ context: config.context ?? 'user',
2877
2082
  sourceIdentity: source.sourceIdentity,
2878
- schemaIdentity: source.manifestDigest,
2083
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, config.platform),
2879
2084
  storeDigests: {
2880
2085
  user: digest({ exists: userStore.exists, rawDigest: userStore.rawDigest }),
2881
- ...(projectStore ? { project: digest({ exists: projectStore.exists, rawDigest: projectStore.rawDigest }) } : {}),
2882
2086
  },
2883
2087
  } };
2884
2088
  },
2885
2089
  });
2886
2090
  },
2887
- async getConfigReferenceContext(pluginId, scope, draft, signal) {
2091
+ async getConfigReferenceContext(pluginId, config, draft, signal) {
2092
+ const workContext = await resolveWorkContext({ cwd: context.cwd, context: config.context ?? 'user' });
2888
2093
  const packageInfo = await readCliPackageInfo();
2889
2094
  return withResolvedCatalogPlugin({
2890
2095
  selector: pluginId, cliVersion: packageInfo.version, env: context.env, signal,
2891
2096
  ...(sessionContextForPlugin(pluginId) ? { operationContext: sessionContextForPlugin(pluginId) } : {}),
2892
2097
  consume: async ({ plugin }) => {
2893
- const definition = plugin.manifest.config;
2894
- if (!definition)
2098
+ if (!plugin.manifest.config)
2895
2099
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2100
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, config.platform);
2896
2101
  const scalars = Object.values(definition.fields).flatMap(configReferenceScalars);
2897
2102
  if (!scalars.length)
2898
- return { selections: {} };
2103
+ return { platform: config.platform, selections: {} };
2899
2104
  const stores = await readConfigStoresForTui({
2900
2105
  sourceId: plugin.sourceId, sourceIdentity: plugin.sourceIdentity,
2901
2106
  logicalIdentity: plugin.catalogLogicalIdentity, pluginName: plugin.manifest.name,
2902
- schema: definition.fields, scope, signal,
2107
+ schema: definition.fields, platform: config.platform, signal,
2903
2108
  });
2904
- const edited = scope === 'user' ? stores.user : stores.project;
2109
+ const edited = stores.user;
2905
2110
  const values = { ...edited.values };
2906
2111
  for (const key of draft.removes)
2907
2112
  delete values[key];
2908
2113
  Object.assign(values, draft.updates);
2909
- const candidateStores = { ...stores, [scope]: { ...edited, values } };
2114
+ const candidateStores = { user: replacePluginConfigStoreLayer(edited, values, config.platform) };
2910
2115
  const references = await resolvePluginConfigReferenceContext({
2911
2116
  sourceId: plugin.sourceId, sourceIdentity: plugin.sourceIdentity,
2912
2117
  logicalIdentity: plugin.catalogLogicalIdentity, manifest: plugin.manifest,
2913
- scope, cwd: context.cwd, env: context.env,
2118
+ scope: 'user', context: config.context ?? 'user', platform: config.platform, cwd: context.cwd, env: context.env,
2914
2119
  });
2915
2120
  const effective = await resolveEffectivePluginConfigFromStores({
2916
- manifest: plugin.manifest, targetScope: scope,
2917
- projectRoot: resolveAwProjectRoot(context.cwd), stores: candidateStores,
2121
+ manifest: plugin.manifest, context: config.context ?? 'user', platform: config.platform,
2122
+ projectRoot: workContext.projectRoot ?? '', stores: candidateStores,
2918
2123
  referenceContext: references.context,
2919
2124
  });
2920
2125
  // Keep invalid raw sources for Core validation; effective.values omits them.
@@ -2925,17 +2130,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2925
2130
  const source = effective.sources[key];
2926
2131
  if (source === 'default')
2927
2132
  candidate[key] = definition.fields[key].default;
2928
- else if (source === 'user')
2929
- candidate[key] = candidateStores.user.values[key];
2930
- else if (source === 'project')
2931
- candidate[key] = candidateStores.project?.values[key];
2133
+ else if (source === 'common')
2134
+ candidate[key] = candidateStores.user.common?.[key];
2135
+ else if (source === 'platform' && config.platform)
2136
+ candidate[key] = candidateStores.user.platforms?.[config.platform]?.[key];
2932
2137
  }
2933
- const result = { selections: {} };
2138
+ const result = { platform: config.platform, selections: {} };
2934
2139
  for (const scalar of scalars) {
2935
2140
  const ref = scalar.valueRef;
2936
2141
  const selection = getPluginConfigReferenceChoices(definition, scalar, candidate, references.context);
2937
2142
  const entries = ref.plugin ? references.context.plugins[ref.plugin]?.values?.[ref.field] : candidate[ref.field];
2938
- result.selections[configReferenceKey(scalar)] = {
2143
+ result.selections[configReferenceKey(scalar, config.platform)] = {
2939
2144
  ...selection, source: (ref.plugin
2940
2145
  ? references.evidence.find((entry) => entry.plugin === ref.plugin)?.sources[ref.field]
2941
2146
  : effective.sources[ref.field]) ?? 'missing',
@@ -2947,7 +2152,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2947
2152
  },
2948
2153
  });
2949
2154
  },
2950
- async getConfigResetAvailability(pluginId, layer, signal) {
2155
+ async getConfigResetAvailability(pluginId, config, signal) {
2156
+ if (config.context === 'project')
2157
+ await resolveWorkContext({ cwd: context.cwd, context: 'project' });
2951
2158
  const parsed = parsePluginId(pluginId);
2952
2159
  if (!parsed.sourceId || !parsed.pluginName) {
2953
2160
  throw new TuiOperationBlockedError('invalidPluginId', '设置目标必须使用 canonical pluginId。');
@@ -2980,7 +2187,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2980
2187
  const target = await inspectPluginConfigResetTarget({
2981
2188
  sourceId: parsed.sourceId,
2982
2189
  pluginName: parsed.pluginName,
2983
- scope: layer.scope,
2190
+ scope: 'user',
2984
2191
  cwd: context.cwd,
2985
2192
  env: context.env,
2986
2193
  });
@@ -2991,7 +2198,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2991
2198
  ...(sourceIdentity ? { sourceIdentity } : {}),
2992
2199
  ...(logicalIdentity ? { logicalIdentity } : {}),
2993
2200
  pluginName: parsed.pluginName,
2994
- scope: layer.scope,
2201
+ scope: 'user',
2202
+ platform: config.platform,
2995
2203
  cwd: context.cwd,
2996
2204
  env: context.env,
2997
2205
  });
@@ -3018,6 +2226,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3018
2226
  };
3019
2227
  },
3020
2228
  async executeIntent(intent, expectedEvidence, signal) {
2229
+ if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
2230
+ || intent.kind === 'pluginWriteTask') {
2231
+ throw new TuiOperationBlockedError('useScopedRecovery', '请使用统一范围恢复任务。');
2232
+ }
3021
2233
  if (intent.kind === 'catalogStorePrune') {
3022
2234
  signal?.throwIfAborted();
3023
2235
  if (!expectedEvidence.catalogStore) {
@@ -3304,351 +2516,13 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3304
2516
  }
3305
2517
  throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
3306
2518
  }
3307
- if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
3308
- || intent.kind === 'pluginWriteTask'
3309
- || intent.kind === 'configSave' && (intent.apply || intent.retryApply)
3310
- || intent.kind === 'configSaveBatch' && intent.apply) {
3311
- await prepareNativeCliExecution({ env: context.env, runner: context.nativeCliRunner });
3312
- }
3313
- const codexObservationSession = intent.kind === 'pluginWrite'
3314
- || intent.kind === 'pluginWriteBatch'
3315
- || intent.kind === 'pluginWriteTask'
3316
- ? await createCodexNativeObservationSession({
3317
- exactCwd: context.cwd,
3318
- env: context.env,
3319
- runner: context.nativeCliRunner,
3320
- manager: codexManagerByEvidence.get(expectedEvidence),
3321
- })
3322
- : undefined;
3323
2519
  return withAwHomeMutationLock({
3324
2520
  env: context.env,
3325
2521
  action: async (awHomeMutationToken) => {
3326
2522
  await tuiControllerRuntimeTestHooks?.beforeMutationEvidenceCapture?.();
3327
2523
  const operationInventory = createOperationInventoryReader();
3328
- const managedCommandProducerCache = new Map();
3329
- let currentEvidence;
3330
- try {
3331
- currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true, undefined, codexObservationSession, intent.kind === 'pluginWriteBatch'
3332
- ? expectedEvidence
3333
- : undefined);
3334
- }
3335
- catch (error) {
3336
- const code = error instanceof TuiOperationBlockedError ? error.code : undefined;
3337
- if (intent.kind === 'pluginWriteBatch'
3338
- && (intent.selection.kind === 'currentProjectIntentRoots'
3339
- || intent.selection.kind === 'currentProjectIntentTargets')
3340
- && error instanceof TuiOperationBlockedError) {
3341
- throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
3342
- }
3343
- if (intent.kind === 'pluginWriteTask'
3344
- && (code === 'taskHasBlockedTargets' || code === 'taskNoExecutableTargets')) {
3345
- throw operationReviewStale('确认后任务可执行目标发生变化。', error instanceof TuiOperationBlockedError ? error.diagnostics : undefined);
3346
- }
3347
- throw error;
3348
- }
3349
- if (intent.kind === 'pluginWriteBatch') {
3350
- const expectedBatch = expectedEvidence;
3351
- const currentBatch = currentEvidence;
3352
- if ((intent.selection.kind === 'currentProjectIntentRoots'
3353
- || intent.selection.kind === 'currentProjectIntentTargets')
3354
- && (!expectedBatch.projectIntentDigest
3355
- || expectedBatch.projectIntentDigest !== currentBatch.projectIntentDigest)) {
3356
- throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
3357
- }
3358
- const reviewedGroups = expectedBatch.groups.map((group) => ({
3359
- groupId: group.groupId,
3360
- sourceId: group.sourceId,
3361
- platform: group.platform,
3362
- scope: group.scope,
3363
- requestedPluginIds: group.requestedPluginIds,
3364
- executable: group.executable,
3365
- planFingerprint: group.preview.operationPlan
3366
- ? intent.action === 'cleanup'
3367
- ? group.preview.operationPlan.planFingerprint
3368
- : createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
3369
- : undefined,
3370
- }));
3371
- const currentGroups = currentBatch.groups.map((group) => ({
3372
- groupId: group.groupId,
3373
- sourceId: group.sourceId,
3374
- platform: group.platform,
3375
- scope: group.scope,
3376
- requestedPluginIds: group.requestedPluginIds,
3377
- executable: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
3378
- ?.executable === false ? false : group.executable,
3379
- planFingerprint: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
3380
- ?.executable === false
3381
- ? undefined
3382
- : group.preview.operationPlan
3383
- ? intent.action === 'cleanup'
3384
- ? group.preview.operationPlan.planFingerprint
3385
- : createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
3386
- : undefined,
3387
- }));
3388
- if (expectedBatch.catalog.configDigest !== currentBatch.catalog.configDigest
3389
- || JSON.stringify(expectedBatch.requestedPluginIds)
3390
- !== JSON.stringify(currentBatch.requestedPluginIds)
3391
- || JSON.stringify(reviewedGroups) !== JSON.stringify(currentGroups)) {
3392
- throw operationReviewStale('确认后批量目标或写入计划发生变化。');
3393
- }
3394
- await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3395
- const groupResults = [];
3396
- const completedTargetKeys = new Set();
3397
- const expectedIntentDigests = new Map();
3398
- for (const group of expectedBatch.groups) {
3399
- for (const intentDigest of group.preview.operationPlan?.evidence.intentDigests ?? []) {
3400
- expectedIntentDigests.set(intentDigest.scope, intentDigest.digest);
3401
- }
3402
- }
3403
- let executedGroupCount = 0;
3404
- const appendStoppedGroups = (startIndex, staleMessage) => {
3405
- for (const [offset, stopped] of expectedBatch.groups.slice(startIndex).entries()) {
3406
- groupResults.push({
3407
- groupId: stopped.groupId,
3408
- sourceId: stopped.sourceId,
3409
- platform: stopped.platform,
3410
- scope: stopped.scope,
3411
- requestedPluginIds: stopped.requestedPluginIds,
3412
- result: !stopped.executable ? stopped.preview : failedWriteCommandResult(intent.action, stopped.requestedPluginIds, { cwd: context.cwd, platform: stopped.platform, scope: stopped.scope }, offset === 0
3413
- ? new TuiOperationBlockedError('operationReviewStale', staleMessage)
3414
- : new TuiOperationBlockedError('batchNotStarted', '前一个批量组的写入计划已变化,因此这个组没有开始。')),
3415
- });
3416
- }
3417
- };
3418
- for (const [groupIndex, reviewed] of expectedBatch.groups.entries()) {
3419
- const current = currentBatch.groups.find((group) => group.groupId === reviewed.groupId);
3420
- if (!reviewed.executable) {
3421
- groupResults.push({
3422
- groupId: reviewed.groupId,
3423
- sourceId: reviewed.sourceId,
3424
- platform: reviewed.platform,
3425
- scope: reviewed.scope,
3426
- requestedPluginIds: reviewed.requestedPluginIds,
3427
- result: reviewed.preview,
3428
- });
3429
- continue;
3430
- }
3431
- const groupInventory = groupResults.length === 0
3432
- ? operationInventory
3433
- : createOperationInventoryReader();
3434
- let executionPreview = current.preview;
3435
- if (completedTargetKeys.size > 0
3436
- || (intent.action === 'cleanup' && executedGroupCount > 0)) {
3437
- executionPreview = await runBatchWriteGroup({
3438
- intent,
3439
- group: current,
3440
- cwd: context.cwd,
3441
- runtime: {
3442
- ...context,
3443
- awHomeMutationToken,
3444
- inventoryCatalogSourceReferences: groupInventory,
3445
- ...(intent.action === 'cleanup' ? {} : {
3446
- expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
3447
- }),
3448
- previewOperationPlan: true,
3449
- ...(codexObservationSession ? { codexObservationSession } : {}),
3450
- },
3451
- });
3452
- const cleanupIntentDigestChanged = intent.action === 'cleanup'
3453
- && executionPreview.operationPlan?.evidence.intentDigests.find((entry) => entry.scope === current.scope)?.digest !== expectedIntentDigests.get(current.scope);
3454
- if (cleanupIntentDigestChanged
3455
- || !reviewed.preview.operationPlan || !executionPreview.operationPlan
3456
- || createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
3457
- appendStoppedGroups(groupIndex, '前序批量结果改变了这个组的实际写入计划。');
3458
- break;
3459
- }
3460
- }
3461
- const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
3462
- const result = await runBatchWriteGroup({
3463
- intent,
3464
- group: current,
3465
- cwd: context.cwd,
3466
- runtime: {
3467
- ...context,
3468
- awHomeMutationToken,
3469
- preApplyConfirmed: true,
3470
- managedCommandProducerCache,
3471
- inventoryCatalogSourceReferences: groupInventory,
3472
- ...(intent.action === 'cleanup' ? {} : {
3473
- expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
3474
- }),
3475
- ...(executionPreview.operationPlan ? {
3476
- expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint,
3477
- } : {}),
3478
- ...(requiresManagedCodex ? {
3479
- managedCodexAuthorizer: async () => true,
3480
- } : {}),
3481
- ...(codexObservationSession ? { codexObservationSession } : {}),
3482
- },
3483
- });
3484
- if (writeReviewIsStale(result)) {
3485
- if (executedGroupCount === 0)
3486
- throwIfWriteReviewStale(result);
3487
- appendStoppedGroups(groupIndex, '这个组的实际写入计划发生变化。');
3488
- break;
3489
- }
3490
- if (intent.action === 'cleanup') {
3491
- for (const change of result.operationPlan?.intentChanges ?? []) {
3492
- expectedIntentDigests.set(change.scope, change.afterDigest);
3493
- }
3494
- }
3495
- executedGroupCount += 1;
3496
- for (const target of result.targetResults) {
3497
- if (target.state === 'completed') {
3498
- completedTargetKeys.add([
3499
- target.target.pluginId,
3500
- target.target.platform,
3501
- target.target.scope,
3502
- ].join('\0'));
3503
- }
3504
- }
3505
- groupResults.push({
3506
- groupId: reviewed.groupId,
3507
- sourceId: reviewed.sourceId,
3508
- platform: reviewed.platform,
3509
- scope: reviewed.scope,
3510
- requestedPluginIds: reviewed.requestedPluginIds,
3511
- result,
3512
- });
3513
- }
3514
- const merged = mergeWriteCommandResults(groupResults.map((group) => group.result));
3515
- return {
3516
- ...merged,
3517
- requestedPluginIds: expectedBatch.requestedPluginIds,
3518
- groupResults,
3519
- };
3520
- }
3521
- if (intent.kind === 'pluginWriteTask') {
3522
- const expectedTask = expectedEvidence;
3523
- const currentTask = currentEvidence;
3524
- if (!operationEvidenceMatches(expectedTask, currentTask)) {
3525
- throw operationReviewStale('确认后任务目标、根因、版本或写入计划发生变化。');
3526
- }
3527
- await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3528
- const operationContexts = taskCatalogOperationsByEvidence.get(currentEvidence)
3529
- ?? new Map();
3530
- const sourceResults = [];
3531
- const completedTargetKeys = new Set();
3532
- const preApplyExecutionCache = new Map();
3533
- const commandTargets = (source) => source.requestedTargets.map((target) => {
3534
- const direct = source.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
3535
- return {
3536
- pluginId: target.pluginId,
3537
- platform: target.platform,
3538
- scope: target.scope,
3539
- method: direct.some((cause) => cause.method === 'reinstall')
3540
- ? 'reinstall' : 'sync',
3541
- };
3542
- });
3543
- const repairTargets = (source) => [...new Map(source.causes.map((cause) => [
3544
- pluginWriteTaskTargetKey(cause.repairTarget),
3545
- { ...cause.repairTarget, method: cause.method },
3546
- ])).values()];
3547
- const stoppedResult = (source, error) => mergeWriteCommandResults(source.requestedTargets.map((target) => failedWriteCommandResult('sync', [target.pluginId], {
3548
- cwd: context.cwd,
3549
- platform: target.platform,
3550
- scope: target.scope,
3551
- }, error)));
3552
- const appendStoppedSources = (startIndex, message) => {
3553
- for (const [offset, source] of expectedTask.taskPlan.sources.slice(startIndex).entries()) {
3554
- sourceResults.push({
3555
- sourceId: source.sourceId,
3556
- sourceIdentity: source.sourceIdentity,
3557
- requestedTargets: source.requestedTargets,
3558
- result: stoppedResult(source, offset === 0
3559
- ? new TuiOperationBlockedError('operationReviewStale', message)
3560
- : new TuiOperationBlockedError('taskNotStarted', '前一个来源计划已变化,因此这个来源没有开始。')),
3561
- });
3562
- }
3563
- };
3564
- for (const [sourceIndex, reviewed] of expectedTask.taskPlan.sources.entries()) {
3565
- const current = currentTask.taskPlan.sources.find((source) => source.sourceId === reviewed.sourceId);
3566
- const catalogOperation = operationContexts.get(reviewed.sourceId);
3567
- if (!current || !catalogOperation || current.sourceIdentity !== reviewed.sourceIdentity) {
3568
- if (sourceResults.length === 0) {
3569
- throw operationReviewStale('确认后任务来源身份发生变化。');
3570
- }
3571
- appendStoppedSources(sourceIndex, '确认后任务来源身份发生变化。');
3572
- break;
3573
- }
3574
- const sourceInventory = sourceResults.length === 0
3575
- ? operationInventory : createOperationInventoryReader();
3576
- let executionPreview = current.preview;
3577
- if (completedTargetKeys.size > 0) {
3578
- executionPreview = await taskWriteCommand(commandTargets(current), {
3579
- cwd: context.cwd,
3580
- repairTargets: repairTargets(current),
3581
- ...(reviewedScriptTimeoutMs(intent) === undefined
3582
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3583
- }, {
3584
- ...context,
3585
- awHomeMutationToken,
3586
- catalogOperationContext: catalogOperation,
3587
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
3588
- expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
3589
- inventoryCatalogSourceReferences: sourceInventory,
3590
- previewOperationPlan: true,
3591
- preApplyExecutionCache,
3592
- ...(codexObservationSession ? { codexObservationSession } : {}),
3593
- });
3594
- if (!reviewed.preview.operationPlan || !executionPreview.operationPlan
3595
- || createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
3596
- appendStoppedSources(sourceIndex, '前序结果改变了这个来源的实际写入计划。');
3597
- break;
3598
- }
3599
- }
3600
- const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
3601
- const result = await taskWriteCommand(commandTargets(current), {
3602
- cwd: context.cwd,
3603
- repairTargets: repairTargets(current),
3604
- ...(reviewedScriptTimeoutMs(intent) === undefined
3605
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3606
- }, {
3607
- ...context,
3608
- awHomeMutationToken,
3609
- catalogOperationContext: catalogOperation,
3610
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
3611
- expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
3612
- inventoryCatalogSourceReferences: sourceInventory,
3613
- preApplyConfirmed: true,
3614
- preApplyExecutionCache,
3615
- managedCommandProducerCache,
3616
- ...(executionPreview.operationPlan
3617
- ? { expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint } : {}),
3618
- ...(requiresManagedCodex ? { managedCodexAuthorizer: async () => true } : {}),
3619
- ...(codexObservationSession ? { codexObservationSession } : {}),
3620
- });
3621
- if (writeReviewIsStale(result)) {
3622
- if (sourceResults.length === 0)
3623
- throwIfWriteReviewStale(result);
3624
- appendStoppedSources(sourceIndex, '这个来源的实际写入计划发生变化。');
3625
- break;
3626
- }
3627
- for (const target of result.targetResults) {
3628
- if (target.state === 'completed') {
3629
- completedTargetKeys.add([
3630
- target.target.pluginId,
3631
- target.target.platform,
3632
- target.target.scope,
3633
- ].join('\0'));
3634
- }
3635
- }
3636
- sourceResults.push({
3637
- sourceId: reviewed.sourceId,
3638
- sourceIdentity: reviewed.sourceIdentity,
3639
- requestedTargets: reviewed.requestedTargets,
3640
- result,
3641
- });
3642
- }
3643
- const merged = mergeWriteCommandResults(sourceResults.map((source) => source.result));
3644
- return {
3645
- ...merged,
3646
- taskPlan: expectedTask.taskPlan,
3647
- sourceResults,
3648
- };
3649
- }
3650
- if (intent.kind !== 'pluginWrite'
3651
- && !operationEvidenceMatches(expectedEvidence, currentEvidence)) {
2524
+ const currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true);
2525
+ if (!operationEvidenceMatches(expectedEvidence, currentEvidence)) {
3652
2526
  throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
3653
2527
  }
3654
2528
  const catalogOperation = catalogOperationByEvidence.get(currentEvidence);
@@ -3657,76 +2531,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3657
2531
  throw operationReviewStale('确认后 Active Catalog generation 发生变化。');
3658
2532
  }
3659
2533
  await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3660
- if (intent.kind === 'pluginWrite') {
3661
- const options = {
3662
- cwd: context.cwd,
3663
- platform: intent.platform,
3664
- scope: intent.scope,
3665
- ...(reviewedScriptTimeoutMs(intent) === undefined
3666
- ? {}
3667
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3668
- ...(intent.action === 'install' && intent.dependencyScopeOverrides
3669
- ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
3670
- : {}),
3671
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3672
- ? { allowManagedCodexInstall: true }
3673
- : {}),
3674
- ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
3675
- };
3676
- const command = {
3677
- install: installWriteCommand,
3678
- refreshAndSync: syncWriteCommand,
3679
- reinstall: reinstallWriteCommand,
3680
- uninstall: uninstallWriteCommand,
3681
- cleanup: cleanupWriteCommand,
3682
- sync: syncWriteCommand,
3683
- }[intent.action];
3684
- if (intent.action === 'cleanup') {
3685
- if (!expectedEvidence.operationPlan || !currentEvidence.operationPlan
3686
- || expectedEvidence.operationPlan.planFingerprint
3687
- !== currentEvidence.operationPlan.planFingerprint) {
3688
- throw operationReviewStale('确认后清理计划发生变化。');
3689
- }
3690
- const result = await command([intent.pluginId], options, {
3691
- ...context,
3692
- ...(codexObservationSession ? { codexObservationSession } : {}),
3693
- awHomeMutationToken,
3694
- preApplyConfirmed: true,
3695
- inventoryCatalogSourceReferences: operationInventory,
3696
- expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint,
3697
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3698
- ? { managedCodexAuthorizer: async () => true }
3699
- : {}),
3700
- });
3701
- throwIfWriteReviewStale(result);
3702
- return result;
3703
- }
3704
- const currentSource = currentEvidence.source;
3705
- if (!expectedEvidence.source || !currentSource) {
3706
- throw new TuiOperationBlockedError('operationReviewStale', '确认信息缺少来源身份。');
3707
- }
3708
- const result = await command([intent.pluginId], options, {
3709
- ...context,
3710
- ...(codexObservationSession ? { codexObservationSession } : {}),
3711
- awHomeMutationToken,
3712
- preApplyConfirmed: true,
3713
- inventoryCatalogSourceReferences: operationInventory,
3714
- ...(expectedEvidence.operationPlan
3715
- ? { expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint }
3716
- : {}),
3717
- expectedCatalogConfigDigest: expectedEvidence.catalog.configDigest,
3718
- ...(catalogOperation ? {
3719
- catalogOperationContext: catalogOperation,
3720
- expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
3721
- } : {}),
3722
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3723
- ? { managedCodexAuthorizer: async () => true }
3724
- : {}),
3725
- reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual),
3726
- });
3727
- throwIfWriteReviewStale(result);
3728
- return result;
3729
- }
3730
2534
  if (intent.kind === 'configSaveBatch') {
3731
2535
  assertConfigSaveBatchIntent(intent);
3732
2536
  const reviewedBatch = expectedEvidence.configBatch;
@@ -3740,7 +2544,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3740
2544
  const packageInfo = await readCliPackageInfo();
3741
2545
  for (const group of intent.groups) {
3742
2546
  const groupIntent = configSaveBatchGroupIntent(intent, group);
3743
- const reviewedGroup = reviewedBatch.groups.find((item) => item.scope === group.scope);
2547
+ const reviewedGroup = reviewedBatch.groups.find((item) => item.platform === group.platform);
3744
2548
  if (!reviewedGroup) {
3745
2549
  throw operationReviewStale('确认信息缺少设置层基线。');
3746
2550
  }
@@ -3757,7 +2561,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3757
2561
  const freshLayerStoreDigest = await configLayerStoreDigest({
3758
2562
  source: freshSource,
3759
2563
  pluginName: parsePluginId(intent.pluginId).pluginName,
3760
- scope: group.scope,
2564
+ scope: 'user', platform: group.platform, profileOnly: true,
3761
2565
  cwd: context.cwd,
3762
2566
  env: context.env,
3763
2567
  });
@@ -3781,8 +2585,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3781
2585
  ...(groupCatalogOperation ? { operationContext: groupCatalogOperation } : {}),
3782
2586
  consume: async ({ plugin, source }) => {
3783
2587
  assertSourceEvidenceMatches(freshSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
2588
+ const referencePlugins = configNeedsReferencePlugins(plugin) ? await resolvePluginConfigReferencePlugins({
2589
+ sourceId: plugin.sourceId,
2590
+ sourceIdentity: plugin.sourceIdentity,
2591
+ logicalIdentity: plugin.catalogLogicalIdentity,
2592
+ env: context.env,
2593
+ operationContext: groupCatalogOperation,
2594
+ }) : undefined;
3784
2595
  const applyTargets = intent.apply
3785
- ? freshConfig.affectedTargets.map((target) => ({
2596
+ ? freshConfig.affectedTargets.filter((target) => target.platform === 'codex' || target.platform === 'claude').map((target) => ({
3786
2597
  pluginName: target.pluginName ?? plugin.manifest.name,
3787
2598
  platform: target.platform,
3788
2599
  scope: target.scope,
@@ -3791,12 +2602,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3791
2602
  return applyPluginConfigWrite({
3792
2603
  plugin,
3793
2604
  pluginName: plugin.manifest.name,
3794
- scope: group.scope,
2605
+ scope: 'user', platform: group.platform, context: intent.context ?? 'user',
2606
+ apply: intent.apply,
3795
2607
  cwd: context.cwd,
3796
2608
  env: context.env,
3797
2609
  nativeCliRunner: context.nativeCliRunner,
3798
2610
  rebuildRuntime: {
3799
2611
  ...context,
2612
+ referencePlugins,
3800
2613
  awHomeMutationToken,
3801
2614
  operationDiagnosticSink: (entries) => {
3802
2615
  operationRebuildResults.push(...structuredClone(entries));
@@ -3810,7 +2623,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3810
2623
  } : {}),
3811
2624
  },
3812
2625
  awHomeMutationToken,
3813
- applyTargets,
2626
+ ...(applyTargets.length ? { applyTargets } : {}),
3814
2627
  request: { updates: group.updates, removeKeys: group.removeKeys },
3815
2628
  sharedNativeApplyConfirmed: true,
3816
2629
  preApplyConfirmed: true,
@@ -3827,7 +2640,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3827
2640
  ? 'notNeeded'
3828
2641
  : 'failed';
3829
2642
  results.push({
3830
- scope: group.scope,
2643
+ scope: 'user', platform: group.platform,
3831
2644
  status: applyStatus === 'failed' ? 'failed' : 'succeeded',
3832
2645
  configSaved,
3833
2646
  applyRequested: intent.apply,
@@ -3848,7 +2661,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3848
2661
  ? error.code
3849
2662
  : configSaved ? 'runtimeApplyFailed' : 'configWriteFailed';
3850
2663
  results.push({
3851
- scope: group.scope,
2664
+ scope: 'user', platform: group.platform,
3852
2665
  status: 'failed',
3853
2666
  configSaved,
3854
2667
  applyRequested: intent.apply,
@@ -3884,7 +2697,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3884
2697
  return resetPluginConfigWithHelper({
3885
2698
  sourceId: parsed.sourceId,
3886
2699
  pluginName: parsed.pluginName,
3887
- scope: intent.scope,
2700
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
3888
2701
  cwd: context.cwd,
3889
2702
  env: context.env,
3890
2703
  nativeCliRunner: context.nativeCliRunner,
@@ -3905,22 +2718,32 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3905
2718
  ...(catalogOperation ? { operationContext: catalogOperation } : {}),
3906
2719
  consume: async ({ plugin, source }) => {
3907
2720
  assertSourceEvidenceMatches(currentSource, sourceEvidenceForPlugin(source, plugin.manifest.name));
2721
+ const referencePlugins = configNeedsReferencePlugins(plugin) ? await resolvePluginConfigReferencePlugins({
2722
+ sourceId: plugin.sourceId,
2723
+ sourceIdentity: plugin.sourceIdentity,
2724
+ logicalIdentity: plugin.catalogLogicalIdentity,
2725
+ env: context.env,
2726
+ operationContext: catalogOperation,
2727
+ }) : undefined;
3908
2728
  const applyTargets = intent.retryApply || intent.apply
3909
- ? currentEvidence.config?.affectedTargets.map((target) => ({
2729
+ ? currentEvidence.config?.affectedTargets.filter((target) => target.platform === 'codex' || target.platform === 'claude').map((target) => ({
3910
2730
  pluginName: target.pluginName ?? plugin.manifest.name,
3911
2731
  platform: target.platform,
3912
2732
  scope: target.scope,
3913
2733
  })) ?? []
3914
2734
  : [];
3915
2735
  const result = intent.retryApply
3916
- ? await replayConfigRuntimeWrites({
2736
+ ? await applyPluginConfig({
3917
2737
  plugin,
3918
- scope: intent.scope,
2738
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
2739
+ ...(intent.targets ? { targets: intent.targets } : {}),
2740
+ cliVersion: packageInfo.version,
3919
2741
  cwd: context.cwd,
3920
2742
  env: context.env,
3921
2743
  nativeCliRunner: context.nativeCliRunner,
3922
2744
  rebuildRuntime: {
3923
2745
  ...context,
2746
+ referencePlugins,
3924
2747
  awHomeMutationToken,
3925
2748
  operationDiagnosticSink,
3926
2749
  ...(reviewedScriptTimeoutMs(intent) === undefined
@@ -3932,7 +2755,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3932
2755
  } : {}),
3933
2756
  },
3934
2757
  awHomeMutationToken,
3935
- applyTargets,
2758
+ expectedEvidence: expectedEvidence.config?.applyEvidence,
3936
2759
  sharedNativeApplyConfirmed: true,
3937
2760
  preApplyConfirmed: true,
3938
2761
  preApplyAuthorizedTargets: expectedEvidence.config?.preApplyAuthorization ?? [],
@@ -3940,12 +2763,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3940
2763
  : await applyPluginConfigWrite({
3941
2764
  plugin,
3942
2765
  pluginName: plugin.manifest.name,
3943
- scope: intent.scope,
2766
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
2767
+ apply: intent.apply,
3944
2768
  cwd: context.cwd,
3945
2769
  env: context.env,
3946
2770
  nativeCliRunner: context.nativeCliRunner,
3947
2771
  rebuildRuntime: {
3948
2772
  ...context,
2773
+ referencePlugins,
3949
2774
  awHomeMutationToken,
3950
2775
  operationDiagnosticSink,
3951
2776
  ...(reviewedScriptTimeoutMs(intent) === undefined
@@ -3957,7 +2782,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3957
2782
  } : {}),
3958
2783
  },
3959
2784
  awHomeMutationToken,
3960
- applyTargets,
2785
+ ...(applyTargets.length ? { applyTargets } : {}),
3961
2786
  request: { updates: intent.updates, removeKeys: intent.removeKeys },
3962
2787
  sharedNativeApplyConfirmed: true,
3963
2788
  preApplyConfirmed: true,
@@ -3969,8 +2794,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3969
2794
  }
3970
2795
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
3971
2796
  },
3972
- }).finally(async () => {
3973
- await codexObservationSession?.dispose();
3974
2797
  });
3975
2798
  },
3976
2799
  };