@lesliechan721/aw 0.1.0-beta.64 → 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 +405 -1626
  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 +9 -2
  267. package/dist/src/utils/plugin-config-references.js +75 -85
  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
20
  import { collectPluginConfigReferenceEvidence, pluginConfigHasExternalReferences, resolvePluginConfigReferenceConsumers, resolvePluginConfigReferencePlugins } from '../utils/plugin-config-references.js';
21
- import { createBatchIntentCompatiblePlanFingerprint } from '../commands/operation-plan.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,18 +115,17 @@ 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}`
@@ -134,8 +133,8 @@ export function configAffectedStatusTargets(targets, pluginId, configScope, cons
134
133
  }
135
134
  function configNeedsReferencePlugins(plugin) {
136
135
  return pluginConfigHasExternalReferences(plugin.manifest)
137
- || Object.values(plugin.manifest.config?.fields ?? {})
138
- .some((field) => field.type === 'array' && field.items.type === 'object');
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')));
139
138
  }
140
139
  async function configRetrySharedNativeRenderTargets(plugin, targets, env, referencePlugins) {
141
140
  const plugins = await resolvePluginConfigReferenceConsumers({ plugin, env, plugins: referencePlugins });
@@ -147,15 +146,16 @@ async function configRetryStoreDigest(options) {
147
146
  sourceIdentity: options.plugin.sourceIdentity, logicalIdentity: options.plugin.catalogLogicalIdentity,
148
147
  env: options.env, plugins: options.plugins });
149
148
  const byName = new Map([...plugins, options.plugin].map((plugin) => [plugin.manifest.name, plugin]));
150
- const profiles = [{ pluginName: options.plugin.manifest.name, scope: options.scope },
149
+ const profiles = [{ pluginName: options.plugin.manifest.name, scope: options.scope, platform: options.platform },
151
150
  ...options.targets.flatMap((target) => {
152
151
  const plugin = byName.get(target.pluginName);
153
152
  if (!plugin)
154
153
  throw operationReviewStale('重试目标的插件定义已变化。');
155
- return pluginConfigTargetRenderScopes(plugin, target).map((scope) => ({ pluginName: target.pluginName, scope }));
154
+ return pluginConfigTargetRenderScopes(plugin, target).map((scope) => ({ pluginName: target.pluginName, scope, platform: target.platform }));
156
155
  }),
157
- ...options.commandPackages.map(({ producer }) => ({ pluginName: producer.pluginName, scope: producer.scope })),
158
- ...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 }))];
159
159
  return digest(await collectPluginConfigReferenceEvidence({ ...options, profiles, plugins }));
160
160
  }
161
161
  function configImpactVisibleTargets(pluginName, targets) {
@@ -179,7 +179,9 @@ function configSaveBatchGroupIntent(intent, group) {
179
179
  return {
180
180
  kind: 'configSave',
181
181
  pluginId: intent.pluginId,
182
- scope: group.scope,
182
+ scope: 'user',
183
+ platform: group.platform,
184
+ context: intent.context ?? 'user',
183
185
  apply: intent.apply,
184
186
  ...(intent.scriptTimeoutMs === undefined ? {} : { scriptTimeoutMs: intent.scriptTimeoutMs }),
185
187
  ...(group.updates ? { updates: group.updates } : {}),
@@ -190,14 +192,11 @@ function configSaveBatchGroupIntent(intent, group) {
190
192
  };
191
193
  }
192
194
  function assertConfigSaveBatchIntent(intent) {
193
- const order = { project: 0, user: 1 };
194
- if (intent.groups.length === 0 || intent.groups.length > 2
195
- || new Set(intent.groups.map((group) => group.scope)).size !== intent.groups.length
196
- || intent.groups.some((group, index) => index > 0
197
- && 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
198
197
  || intent.groups.some((group) => Object.keys(group.updates ?? {}).length === 0
199
198
  && (group.removeKeys?.length ?? 0) === 0)) {
200
- throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含按当前项目、个人默认排序的唯一非空目标。');
199
+ throw new TuiOperationBlockedError('configBatchInvalid', '设置批次必须包含唯一且非空的公共或平台配置。');
201
200
  }
202
201
  }
203
202
  function operationReviewStale(message, diagnostics) {
@@ -227,28 +226,15 @@ async function configLayerStoreDigest(options) {
227
226
  ? { logicalIdentity: options.source.logicalIdentity }
228
227
  : {}),
229
228
  pluginName: options.pluginName,
230
- scope: options.scope,
231
- cwd: options.cwd,
232
- env: options.env,
233
- });
234
- return digest({ exists: store.exists, rawDigest: store.rawDigest });
235
- }
236
- async function inspectConfigShareability(options) {
237
- if (options.scope !== 'project')
238
- return undefined;
239
- const targetPath = resolvePluginConfigStorePath({
240
- sourceId: options.sourceId,
241
- pluginName: options.pluginName,
242
- scope: 'project',
229
+ scope: 'user',
230
+ platform: options.platform,
243
231
  cwd: options.cwd,
244
232
  env: options.env,
245
233
  });
246
- const visibility = await assertProjectSharedConfigPathVisible({
247
- cwd: options.cwd,
248
- targetPath,
249
- env: options.env,
250
- });
251
- 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 });
252
238
  }
253
239
  function assertSourceEvidenceMatches(expected, actual) {
254
240
  if (actual.sourceId !== expected.sourceId
@@ -261,21 +247,9 @@ function assertSourceEvidenceMatches(expected, actual) {
261
247
  throw operationReviewStale('确认后来源内容发生变化。');
262
248
  }
263
249
  }
264
- function writeReviewIsStale(result) {
265
- return result.targetResults.some((target) => target.errorCode === 'operationReviewStale'
266
- || target.errorCode === 'operationPlanStale');
267
- }
268
- function throwIfWriteReviewStale(result) {
269
- if (writeReviewIsStale(result)) {
270
- throw operationReviewStale('确认后实际写入计划发生变化。');
271
- }
272
- }
273
250
  const SOURCE_PROGRESS_PHASES = {
274
251
  savingConfig: '保存来源配置',
275
252
  };
276
- function pluginIntent(intent) {
277
- return 'pluginId' in intent;
278
- }
279
253
  function reviewedScriptTimeoutMs(intent) {
280
254
  if (intent.kind === 'pluginWriteTask') {
281
255
  return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
@@ -290,138 +264,6 @@ function reviewedScriptTimeoutMs(intent) {
290
264
  }
291
265
  return undefined;
292
266
  }
293
- function selectedBatchGroups(intent) {
294
- if (intent.selection.kind !== 'selected' && intent.selection.kind !== 'targets') {
295
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部同步必须从项目 intent roots 解析。');
296
- }
297
- const rawTargets = intent.selection.kind === 'targets'
298
- ? intent.selection.targets
299
- : intent.selection.pluginIds.map((pluginId) => ({
300
- pluginId, platform: intent.platform, scope: intent.scope,
301
- }));
302
- const normalizedTargets = rawTargets.map((target) => {
303
- if ((target.platform !== 'codex' && target.platform !== 'claude' && target.platform !== 'both')
304
- || (target.scope !== 'project' && target.scope !== 'user')
305
- || (intent.selection.kind === 'targets' && target.platform === 'both')) {
306
- throw new TuiOperationBlockedError('batchTargetInvalid', '批量精确目标必须使用有效 scope 和单个平台。');
307
- }
308
- const pluginId = target.pluginId;
309
- const parsed = parsePluginId(pluginId);
310
- if (!parsed.sourceId) {
311
- throw new TuiOperationBlockedError('invalidPluginId', '批量操作必须使用 canonical pluginId。');
312
- }
313
- const canonicalPluginId = formatPluginId({ sourceId: parsed.sourceId, pluginName: parsed.pluginName });
314
- if (intent.selection.kind === 'targets' && canonicalPluginId !== pluginId) {
315
- throw new TuiOperationBlockedError('invalidPluginId', '批量精确目标必须使用 canonical pluginId。');
316
- }
317
- return {
318
- pluginId: canonicalPluginId,
319
- platform: target.platform,
320
- scope: target.scope,
321
- };
322
- }).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
323
- .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'));
324
- const uniqueTargets = [...new Map(normalizedTargets.map((target) => [
325
- [target.pluginId, target.platform, target.scope].join('\0'), target,
326
- ])).values()];
327
- const requestedPluginIds = [...new Set(uniqueTargets.map((target) => target.pluginId))];
328
- if (requestedPluginIds.length === 0) {
329
- throw new TuiOperationBlockedError('syncSelectionRequired', '批量操作至少需要一个插件。');
330
- }
331
- const groups = new Map();
332
- for (const target of uniqueTargets) {
333
- const pluginId = target.pluginId;
334
- const sourceId = parsePluginId(pluginId).sourceId;
335
- const groupKey = intent.action === 'cleanup'
336
- ? ['cleanup', target.platform, target.scope].join('\0')
337
- : [sourceId, target.platform, target.scope].join('\0');
338
- const group = groups.get(groupKey) ?? {
339
- groupId: groupKey,
340
- sourceId: intent.action === 'cleanup' ? 'offline' : sourceId,
341
- platform: target.platform,
342
- scope: target.scope,
343
- requestedPluginIds: [],
344
- };
345
- group.requestedPluginIds.push(pluginId);
346
- groups.set(groupKey, group);
347
- }
348
- return { requestedPluginIds, groups: [...groups.values()] };
349
- }
350
- async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
351
- if (intent.selection.kind === 'selected' || intent.selection.kind === 'targets') {
352
- return selectedBatchGroups(intent);
353
- }
354
- if (intent.action !== 'sync' || intent.scope !== 'project') {
355
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部操作只支持 project scope sync。');
356
- }
357
- const state = await readPluginIntentState({ scope: 'project', cwd, env });
358
- const projectIntentDigest = calculatePluginIntentDigest(state);
359
- if (intent.selection.kind === 'currentProjectIntentTargets') {
360
- if (intent.selection.targets.length === 0
361
- || intent.selection.targets.some((target) => target.scope !== 'project')) {
362
- throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须是非空 project targets。');
363
- }
364
- const exact = selectedBatchGroups({
365
- ...intent,
366
- selection: { kind: 'targets', targets: intent.selection.targets },
367
- });
368
- const declaredPlatforms = new Map(state.roots.map((root) => [
369
- root.pluginId,
370
- new Set(root.platforms),
371
- ]));
372
- if (intent.selection.targets.some((target) => !declaredPlatforms.get(target.pluginId)?.has(target.platform))) {
373
- throw new TuiOperationBlockedError('batchTargetInvalid', '当前项目精确同步目标必须属于完整 project intent roots 声明。');
374
- }
375
- return { ...exact, projectIntentDigest };
376
- }
377
- const selection = resolveSyncIntentSelection({
378
- roots: state.roots,
379
- selectors: frozenRequestedPluginIds ?? [],
380
- all: frozenRequestedPluginIds === undefined,
381
- platform: intent.platform,
382
- });
383
- if (selection.requestedPluginIds.length === 0) {
384
- throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目没有可同步的 intent roots。');
385
- }
386
- return {
387
- requestedPluginIds: selection.requestedPluginIds,
388
- projectIntentDigest,
389
- groups: selection.groups.map((group) => ({
390
- groupId: `${group.sourceId}\0${group.platform}\0${intent.scope}`,
391
- sourceId: group.sourceId,
392
- platform: group.platform,
393
- scope: intent.scope,
394
- requestedPluginIds: group.pluginIds,
395
- })),
396
- };
397
- }
398
- async function runBatchWriteGroup(options) {
399
- const dependencyScopeOverrides = options.intent.action === 'install'
400
- ? options.intent.dependencyScopeOverrides?.filter((override) => parsePluginId(override.pluginId).sourceId === options.group.sourceId
401
- && (options.group.platform === 'both' || override.platform === options.group.platform))
402
- : undefined;
403
- const commandOptions = {
404
- cwd: options.cwd,
405
- ...(dependencyScopeOverrides?.length ? { dependencyScopeOverrides } : {}),
406
- platform: options.group.platform,
407
- scope: options.group.scope,
408
- ...(reviewedScriptTimeoutMs(options.intent) === undefined
409
- ? {}
410
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(options.intent) }),
411
- };
412
- switch (options.intent.action) {
413
- case 'install':
414
- return installWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
415
- case 'sync':
416
- return syncWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
417
- case 'reinstall':
418
- return reinstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
419
- case 'uninstall':
420
- return uninstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
421
- case 'cleanup':
422
- return cleanupWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
423
- }
424
- }
425
267
  async function loadDependencyOverviews(options) {
426
268
  const graph = await withResolvedMultiSourceCatalog({
427
269
  cliVersion: options.cliVersion,
@@ -714,8 +556,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
714
556
  };
715
557
  };
716
558
  const catalogOperationByEvidence = new WeakMap();
717
- const taskCatalogOperationsByEvidence = new WeakMap();
718
- const codexManagerByEvidence = new WeakMap();
719
559
  let sessionCatalogOperationContext;
720
560
  let sessionStatusResult;
721
561
  const sessionContextForPlugin = (pluginId) => {
@@ -725,33 +565,42 @@ export function createTuiControllerDependencies(context, progressFeed, options =
725
565
  return selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId]);
726
566
  };
727
567
  const readConfigStoresForTui = async (config) => {
728
- const scopes = config.scope === 'user' ? ['user'] : ['user', 'project'];
729
- const stores = await Promise.all(scopes.map((scope) => readPluginConfigStore({
568
+ const user = await readPluginConfigStore({
730
569
  sourceId: config.sourceId,
731
570
  sourceIdentity: config.sourceIdentity,
732
571
  logicalIdentity: config.logicalIdentity,
733
572
  pluginName: config.pluginName,
734
- scope,
573
+ scope: 'user',
574
+ platform: config.platform,
735
575
  cwd: context.cwd,
736
576
  env: context.env,
737
577
  schema: config.schema,
738
- })));
739
- const user = stores[0];
740
- const project = config.scope === 'user' ? undefined : stores[1];
578
+ });
741
579
  config.signal.throwIfAborted();
742
- const invalidStores = ([
743
- { scope: 'user', store: user },
744
- ...(project ? [{ scope: 'project', store: project }] : []),
745
- ]).flatMap(({ scope, store }) => store.state === 'invalid' ? [{
746
- scope,
747
- code: store.invalidCode,
748
- pathSummary: scope === 'user'
749
- ? `AW_HOME/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`
750
- : `<project>/.agent-workflows/plugin-config/${config.sourceId ?? '<source>'}/${config.pluginName}/config.json`,
751
- }] : []);
752
- if (invalidStores.length > 0)
753
- throw new TuiConfigInvalidStoreError(invalidStores);
754
- 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
+ }
755
604
  };
756
605
  const loadStatus = async (signal) => {
757
606
  signal.throwIfAborted();
@@ -822,7 +671,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
822
671
  signal.throwIfAborted();
823
672
  const catalogGenerations = await loadCatalogGenerations();
824
673
  signal.throwIfAborted();
825
- const configStoreInventory = (await Promise.all(['user', 'project'].map(async (scope) => {
674
+ const configStoreInventory = (await Promise.all(['user'].map(async (scope) => {
826
675
  try {
827
676
  return (await listNamespacedPluginConfigStoreIdentities({
828
677
  cwd: context.cwd,
@@ -845,24 +694,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
845
694
  }))).flat();
846
695
  signal.throwIfAborted();
847
696
  const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
848
- const projectIntentObservation = result.projectIntentObservation;
849
- const projectIntent = projectIntentObservation.state === 'ready' ? {
850
- state: 'ready',
851
- roots: structuredClone(projectIntentObservation.value.roots),
852
- } : projectIntentObservation.state === 'absent' ? {
853
- state: 'absent',
854
- } : {
855
- state: projectIntentObservation.state,
856
- code: projectIntentObservation.code,
857
- message: projectIntentObservation.message,
858
- };
859
697
  return {
860
698
  cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
861
699
  ...(result.projectRoot ? { projectRoot: result.projectRoot } : {}),
862
- projectIntent,
863
- ...(projectIntent.state === 'ready'
864
- ? { projectIntentRoots: structuredClone(projectIntent.roots) }
865
- : {}),
866
700
  configuredSources: structuredClone(configured.sources),
867
701
  payload: result.payload,
868
702
  pluginSummaries: result.pluginSummaries,
@@ -946,8 +780,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
946
780
  };
947
781
  }
948
782
  };
949
- 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
+ }
950
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
+ }
951
794
  if (intent.kind === 'catalogStorePrune') {
952
795
  signal?.throwIfAborted();
953
796
  const config = await readCatalogSourcesConfigSnapshot(context.env);
@@ -964,7 +807,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
964
807
  let batchBase;
965
808
  let batchCatalogOperation;
966
809
  for (const group of intent.groups) {
967
- 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);
968
811
  if (!current.config || !current.source || !current.targetFingerprint) {
969
812
  throw operationReviewStale('设置批次缺少单层证据。');
970
813
  }
@@ -974,10 +817,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
974
817
  }
975
818
  for (const expected of Object.values(group.editEvidence ?? {})) {
976
819
  if (expected.sourceIdentity !== current.source.sourceIdentity
977
- || expected.schemaIdentity !== current.config.schemaIdentity) {
820
+ || expected.schemaIdentity !== current.config.schemaIdentity
821
+ || expected.platform !== group.platform
822
+ || (expected.context ?? 'user') !== (intent.context ?? 'user')) {
978
823
  throw operationReviewStale('编辑期间插件来源或设置定义已变化,请重新打开列表。');
979
824
  }
980
- for (const scope of ['user', 'project']) {
825
+ for (const scope of ['user']) {
981
826
  if (expected.storeDigests[scope] === undefined)
982
827
  continue;
983
828
  const currentDigest = await configLayerStoreDigest({
@@ -992,13 +837,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
992
837
  batchBase ??= current;
993
838
  batchCatalogOperation ??= catalogOperationByEvidence.get(current);
994
839
  groupEvidence.push({
995
- scope: group.scope,
840
+ scope: 'user',
841
+ platform: group.platform,
996
842
  config: {
997
843
  ...current.config,
998
844
  storeDigest: await configLayerStoreDigest({
999
845
  source: current.source,
1000
846
  pluginName: parsePluginId(intent.pluginId).pluginName,
1001
- scope: group.scope,
847
+ scope: 'user',
848
+ platform: group.platform,
849
+ profileOnly: true,
1002
850
  cwd: context.cwd,
1003
851
  env: context.env,
1004
852
  }),
@@ -1015,23 +863,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1015
863
  catalogOperationByEvidence.set(evidence, batchCatalogOperation);
1016
864
  return evidence;
1017
865
  }
1018
- if ((intent.kind === 'pluginWrite'
1019
- && (intent.platform === 'codex' || intent.platform === 'both')
1020
- || intent.kind === 'pluginWriteTask'
1021
- && intent.targets.some((target) => target.platform === 'codex'))
1022
- && !codexObservationSession) {
1023
- const observationSession = await createCodexNativeObservationSession({
1024
- exactCwd: context.cwd,
1025
- env: context.env,
1026
- runner: context.nativeCliRunner,
1027
- });
1028
- try {
1029
- return await captureEvidence(intent, signal, preparation, operationInventory, freshCatalog, reusableCandidate, observationSession, frozenBatchEvidence);
1030
- }
1031
- finally {
1032
- await observationSession.dispose();
1033
- }
1034
- }
1035
866
  if (intent.kind === 'configReset') {
1036
867
  const parsed = parsePluginId(intent.pluginId);
1037
868
  if (!parsed.sourceId || !parsed.pluginName) {
@@ -1041,7 +872,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1041
872
  const target = await inspectPluginConfigResetTarget({
1042
873
  sourceId: parsed.sourceId,
1043
874
  pluginName: parsed.pluginName,
1044
- scope: intent.scope,
875
+ scope: 'user',
1045
876
  cwd: context.cwd,
1046
877
  env: context.env,
1047
878
  });
@@ -1050,7 +881,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1050
881
  throw new TuiOperationBlockedError('configResetTargetUnsupported', '设置目标是特殊文件系统节点,不能自动重置。请手动移动后重试。');
1051
882
  }
1052
883
  const relevantTargets = sessionStatusResult
1053
- ? configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.scope)
884
+ ? configAffectedStatusTargets(sessionStatusResult.payload.targets, intent.pluginId, intent.context ?? 'user', [], intent.platform)
1054
885
  : [];
1055
886
  const relevantRevisions = (sessionStatusResult?.targetRevisions ?? []).filter((revision) => relevantTargets.some((statusTarget) => statusTarget.platform === revision.platform
1056
887
  && statusTarget.scope === revision.scope
@@ -1070,7 +901,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1070
901
  source: sourceEvidenceForPlugin(resolvedSource, plugin.manifest.name),
1071
902
  impact: await analyzePluginConfigMutationImpact({
1072
903
  plugin,
1073
- scope: intent.scope,
904
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1074
905
  cwd: context.cwd,
1075
906
  env: context.env,
1076
907
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
@@ -1079,7 +910,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1079
910
  }),
1080
911
  });
1081
912
  }
1082
- catch {
913
+ catch (error) {
914
+ if (error?.code === 'configValidationMemoryExhausted')
915
+ throw error;
1083
916
  selected = undefined;
1084
917
  }
1085
918
  signal?.throwIfAborted();
@@ -1097,7 +930,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1097
930
  targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
1098
931
  } : {}),
1099
932
  config: {
1100
- scope: intent.scope,
933
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
1101
934
  storeDigest: selected?.impact.storeDigest ?? target.fingerprint,
1102
935
  impactState: selected?.impact.impactState ?? 'unknown',
1103
936
  ...(selected?.impact.noEffectiveChange ? { noEffectiveChange: true } : {}),
@@ -1112,7 +945,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1112
945
  resetTarget: {
1113
946
  sourceId: parsed.sourceId,
1114
947
  pluginName: parsed.pluginName,
1115
- scope: intent.scope,
948
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
1116
949
  kind: target.kind,
1117
950
  fingerprint: target.fingerprint,
1118
951
  },
@@ -1423,671 +1256,11 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1423
1256
  }
1424
1257
  return evidence;
1425
1258
  }
1426
- if (!pluginIntent(intent) && intent.kind !== 'pluginWriteBatch'
1427
- && intent.kind !== 'pluginWriteTask') {
1259
+ if (intent.kind !== 'configSave'
1260
+ && intent.kind !== 'portableInstall') {
1428
1261
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
1429
1262
  }
1430
1263
  signal?.throwIfAborted();
1431
- if (intent.kind === 'pluginWriteTask') {
1432
- await preparation?.reporter.emit('status');
1433
- const packageInfo = await readCliPackageInfo();
1434
- const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
1435
- const requestedBySource = new Map();
1436
- for (const target of intent.targets) {
1437
- const parsed = parsePluginId(target.pluginId);
1438
- if (!parsed.sourceId || formatPluginId({
1439
- sourceId: parsed.sourceId,
1440
- pluginName: parsed.pluginName,
1441
- }) !== target.pluginId) {
1442
- throw new TuiOperationBlockedError('invalidPluginId', '任务目标必须使用 canonical pluginId。');
1443
- }
1444
- requestedBySource.set(parsed.sourceId, [
1445
- ...(requestedBySource.get(parsed.sourceId) ?? []),
1446
- target,
1447
- ]);
1448
- }
1449
- const sources = [];
1450
- const executableTargets = [];
1451
- const causes = [];
1452
- const blockedTargets = [];
1453
- const observedTargets = [];
1454
- const observedRevisions = [];
1455
- const operationContexts = new Map();
1456
- await preparation?.reporter.emit('catalog');
1457
- for (const [sourceId, requestedTargets] of [...requestedBySource.entries()]
1458
- .sort(([left], [right]) => left.localeCompare(right, 'en'))) {
1459
- signal?.throwIfAborted();
1460
- let catalogOperation;
1461
- try {
1462
- catalogOperation = !freshCatalog && sessionCatalogOperationContext
1463
- ? selectCatalogOperationContext(sessionCatalogOperationContext, [sourceId])
1464
- : await resolveCatalogOperationContext({
1465
- sourceIds: [sourceId],
1466
- env: context.env,
1467
- ...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
1468
- ...(signal ? { signal } : {}),
1469
- });
1470
- }
1471
- catch (error) {
1472
- if (error instanceof Error && error.name === 'AbortError')
1473
- throw error;
1474
- blockedTargets.push(...requestedTargets.map((target, index) => {
1475
- const diagnostic = error instanceof CatalogGenerationError ? {
1476
- id: `task-source:${sourceId}:${index}`,
1477
- code: error.code,
1478
- stage: 'source',
1479
- sourceId,
1480
- safeMessage: 'The Catalog source could not be prepared safely.',
1481
- target: { pluginId: target.pluginId, platform: target.platform, scope: target.scope },
1482
- causes: [],
1483
- detailLines: [],
1484
- } : projectThrownOperationDiagnostic({
1485
- error,
1486
- id: `task-source:${sourceId}:${index}`,
1487
- stage: 'source',
1488
- fallbackCode: 'catalogSourceResolutionFailed',
1489
- sourceId,
1490
- target,
1491
- });
1492
- return {
1493
- target,
1494
- reason: 'targetUnavailable',
1495
- issueIds: [],
1496
- errorCodes: [diagnostic.code],
1497
- diagnostics: [diagnostic],
1498
- };
1499
- }));
1500
- continue;
1501
- }
1502
- operationContexts.set(sourceId, catalogOperation);
1503
- const status = await statusCommandV8({
1504
- plugins: [...new Set(requestedTargets.map((target) => target.pluginId))],
1505
- cwd: context.cwd,
1506
- env: context.env,
1507
- nativeCliRunner: context.nativeCliRunner,
1508
- ...(codexObservationSession ? { codexObservationSession } : {}),
1509
- cliVersion: packageInfo.version,
1510
- catalogOperationContext: catalogOperation,
1511
- inventoryCatalogSourceReferences: operationInventory,
1512
- });
1513
- observedTargets.push(...flattenStatusTargets(status.payload.targets));
1514
- observedRevisions.push(...status.targetRevisions);
1515
- const model = buildTuiViewModel({
1516
- status: status.payload,
1517
- pluginSummaries: status.pluginSummaries,
1518
- targetRevisions: status.targetRevisions,
1519
- });
1520
- const runtimeCandidates = intent.goal === 'restore'
1521
- ? buildPluginWriteTaskCandidates(model, 'restore')
1522
- : requestedTargets.map((target) => {
1523
- const expected = target.expectedUpdate;
1524
- const revision = status.targetRevisions.find((entry) => entry.pluginId === target.pluginId && entry.platform === target.platform
1525
- && entry.scope === target.scope);
1526
- const live = flattenStatusTargets(status.payload.targets).find((entry) => {
1527
- if (entry.pluginId !== target.pluginId || entry.platform !== target.platform)
1528
- return false;
1529
- const desired = statusTargetIntent(entry);
1530
- return entry.scope === target.scope || desired.scopes.includes(target.scope);
1531
- });
1532
- const generation = catalogOperation.generationSet.sources.find((entry) => entry.sourceId === sourceId);
1533
- const eligible = Boolean(expected && live && revision
1534
- && live.sourceIdentity === expected.sourceIdentity
1535
- && generation?.storeKey === expected.generationKey
1536
- && revision.revision.installedVersion === expected.installedVersion
1537
- && revision.revision.availableVersion === expected.activeVersion
1538
- && revision.revision.state === 'upgradeAvailable');
1539
- const directVersionUpgrade = live !== undefined && revision !== undefined
1540
- && isDirectCodexVersionUpgrade(live, revision.revision);
1541
- return {
1542
- target,
1543
- displayName: model.plugins.find((plugin) => plugin.pluginId === target.pluginId)
1544
- ?.displayName ?? parsePluginId(target.pluginId).pluginName,
1545
- sourceId,
1546
- eligible,
1547
- ...(!eligible ? { blockReason: 'updateEvidenceIncomplete' } : {}),
1548
- recommendedMethod: live?.state === 'ready' || directVersionUpgrade
1549
- ? 'sync' : 'reinstall',
1550
- issueIds: [],
1551
- };
1552
- });
1553
- const draft = buildPluginWriteTaskDraft({ ...intent, targets: requestedTargets }, model, runtimeCandidates);
1554
- blockedTargets.push(...draft.blockedTargets);
1555
- if (draft.executableTargets.length === 0)
1556
- continue;
1557
- const methodsByTarget = new Map();
1558
- for (const cause of draft.causes) {
1559
- const key = pluginWriteTaskTargetKey(cause.repairTarget);
1560
- const methods = methodsByTarget.get(key) ?? new Set();
1561
- methods.add(cause.method);
1562
- methodsByTarget.set(key, methods);
1563
- }
1564
- const conflicted = new Set([...methodsByTarget].filter(([, methods]) => methods.size > 1)
1565
- .map(([key]) => key));
1566
- if (conflicted.size > 0) {
1567
- const blockedKeys = new Set(draft.causes.filter((cause) => conflicted.has(pluginWriteTaskTargetKey(cause.repairTarget)))
1568
- .flatMap((cause) => cause.consumers.map(pluginWriteTaskTargetKey)));
1569
- blockedTargets.push(...draft.executableTargets.filter((target) => blockedKeys.has(pluginWriteTaskTargetKey(target))).map((target) => ({
1570
- target,
1571
- reason: 'taskActionConflict',
1572
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1573
- .map((cause) => cause.issueId),
1574
- })));
1575
- draft.executableTargets = draft.executableTargets.filter((target) => !blockedKeys.has(pluginWriteTaskTargetKey(target)));
1576
- draft.causes = draft.causes.filter((cause) => cause.consumers.some((consumer) => !blockedKeys.has(pluginWriteTaskTargetKey(consumer))));
1577
- }
1578
- if (draft.executableTargets.length === 0)
1579
- continue;
1580
- const taskTargets = () => draft.executableTargets.map((target) => {
1581
- const direct = draft.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
1582
- return {
1583
- pluginId: target.pluginId,
1584
- platform: target.platform,
1585
- scope: target.scope,
1586
- method: direct.some((cause) => cause.method === 'reinstall')
1587
- ? 'reinstall' : 'sync',
1588
- };
1589
- });
1590
- const repairTargets = () => [...new Map(draft.causes.map((cause) => [
1591
- pluginWriteTaskTargetKey(cause.repairTarget),
1592
- { ...cause.repairTarget, method: cause.method },
1593
- ])).values()];
1594
- let preview;
1595
- while (draft.executableTargets.length > 0) {
1596
- preview = await taskWriteCommand(taskTargets(), {
1597
- cwd: context.cwd,
1598
- repairTargets: repairTargets(),
1599
- ...(reviewedScriptTimeoutMs(intent) === undefined
1600
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1601
- }, {
1602
- ...context,
1603
- catalogOperationContext: catalogOperation,
1604
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1605
- expectedCatalogConfigDigest: configSnapshot.digest,
1606
- inventoryCatalogSourceReferences: operationInventory,
1607
- previewOperationPlan: true,
1608
- ...(codexObservationSession ? { codexObservationSession } : {}),
1609
- ...(preparation ? {
1610
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1611
- } : {}),
1612
- });
1613
- if (!preview.operationPlan) {
1614
- const projected = projectExecutionDiagnostics(preview, `task-preview:${sourceId}`, [], 'pluginWrite');
1615
- const sourceDiagnostics = projected.diagnostics.filter((diagnostic) => !diagnostic.target)
1616
- .map((diagnostic) => ({ ...diagnostic, sourceId, target: undefined }));
1617
- blockedTargets.push(...draft.executableTargets.map((target, targetIndex) => {
1618
- const matchingDiagnostics = projected.diagnostics.filter((diagnostic) => diagnostic.target?.pluginId === target.pluginId
1619
- && diagnostic.target.platform === target.platform
1620
- && diagnostic.target.scope === target.scope);
1621
- const preparedDiagnostics = [
1622
- ...matchingDiagnostics,
1623
- ...(targetIndex === 0 ? sourceDiagnostics : []),
1624
- ];
1625
- const targetDiagnostics = preparedDiagnostics.length > 0
1626
- ? preparedDiagnostics
1627
- : [projectThrownOperationDiagnostic({
1628
- error: undefined,
1629
- id: `task-preview:${sourceId}:${targetIndex}`,
1630
- stage: 'dependencyPlan',
1631
- fallbackCode: 'taskOperationPlanMissing',
1632
- sourceId,
1633
- target,
1634
- })];
1635
- const diagnostics = targetDiagnostics.map((diagnostic, diagnosticIndex) => ({
1636
- ...structuredClone(diagnostic),
1637
- id: `task-preview:${sourceId}:${targetIndex}:${diagnosticIndex}`,
1638
- sourceId,
1639
- }));
1640
- return {
1641
- target,
1642
- reason: 'targetUnavailable',
1643
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1644
- .map((cause) => cause.issueId),
1645
- errorCodes: [...new Set(diagnostics.map((diagnostic) => diagnostic.code))],
1646
- diagnostics,
1647
- };
1648
- }));
1649
- draft.executableTargets = [];
1650
- draft.causes = [];
1651
- break;
1652
- }
1653
- if (intent.goal === 'restore') {
1654
- const grouped = new Map();
1655
- const blockedByVersion = new Map();
1656
- for (const cause of draft.causes) {
1657
- const placements = (preview.dependencyPlan?.targets ?? []).filter((target) => target.pluginId === cause.repairTarget.pluginId
1658
- && target.platform === cause.repairTarget.platform);
1659
- const placement = placements.find((target) => target.requestedScope === cause.repairTarget.scope)
1660
- ?? (placements.length === 1 ? placements[0] : undefined);
1661
- if (!placement) {
1662
- throw new TuiOperationBlockedError('taskRepairPlacementMissing', '任务修复目标无法绑定到依赖位置。');
1663
- }
1664
- const repairTarget = {
1665
- pluginId: cause.repairTarget.pluginId,
1666
- platform: cause.repairTarget.platform,
1667
- scope: placement.requestedScope ?? placement.resolvedScope,
1668
- };
1669
- const version = evaluatePluginWriteTaskRestoreVersion(repairTarget, cause.restoreVersionEvidence?.state === 'absent', model);
1670
- if (!version.evidence || version.blockReason) {
1671
- for (const consumer of cause.consumers) {
1672
- const key = pluginWriteTaskTargetKey(consumer);
1673
- const current = blockedByVersion.get(key) ?? {
1674
- reason: version.blockReason ?? 'restoreVersionUnverified',
1675
- issueIds: new Set(),
1676
- };
1677
- if (version.blockReason === 'restoreVersionChanged') {
1678
- current.reason = version.blockReason;
1679
- }
1680
- current.issueIds.add(cause.issueId);
1681
- blockedByVersion.set(key, current);
1682
- }
1683
- continue;
1684
- }
1685
- const causeKey = ['restore', cause.sourceIdentity,
1686
- pluginWriteTaskTargetKey(repairTarget)].join('\0');
1687
- const current = grouped.get(causeKey);
1688
- grouped.set(causeKey, {
1689
- cause: {
1690
- ...cause,
1691
- causeKey,
1692
- issueId: [cause.issueId, ...(current?.issueIds ?? [])]
1693
- .sort((left, right) => left.localeCompare(right, 'en'))[0],
1694
- repairTarget,
1695
- consumers: [...new Map([
1696
- ...(current?.cause.consumers ?? []),
1697
- ...cause.consumers,
1698
- ].map((consumer) => [pluginWriteTaskTargetKey(consumer), consumer])).values()]
1699
- .sort((left, right) => pluginWriteTaskTargetKey(left)
1700
- .localeCompare(pluginWriteTaskTargetKey(right), 'en')),
1701
- restoreVersionEvidence: version.evidence,
1702
- },
1703
- issueIds: new Set([...(current?.issueIds ?? []), cause.issueId]),
1704
- methods: new Set([...(current?.methods ?? []), cause.method]),
1705
- });
1706
- }
1707
- for (const group of grouped.values()) {
1708
- if (group.methods.size < 2)
1709
- continue;
1710
- for (const consumer of group.cause.consumers) {
1711
- const key = pluginWriteTaskTargetKey(consumer);
1712
- const current = blockedByVersion.get(key) ?? {
1713
- reason: 'taskActionConflict',
1714
- issueIds: new Set(),
1715
- };
1716
- group.issueIds.forEach((issueId) => current.issueIds.add(issueId));
1717
- blockedByVersion.set(key, current);
1718
- }
1719
- }
1720
- const newlyBlockedKeys = new Set(blockedByVersion.keys());
1721
- blockedTargets.push(...draft.executableTargets.flatMap((target) => {
1722
- const blocked = blockedByVersion.get(pluginWriteTaskTargetKey(target));
1723
- return blocked ? [{
1724
- target,
1725
- reason: blocked.reason,
1726
- issueIds: [...blocked.issueIds].sort((left, right) => left.localeCompare(right, 'en')),
1727
- }] : [];
1728
- }));
1729
- const normalizedCauses = [...grouped.values()]
1730
- .filter((group) => group.methods.size === 1)
1731
- .map((group) => ({
1732
- ...group.cause,
1733
- consumers: group.cause.consumers.filter((consumer) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(consumer))),
1734
- }))
1735
- .filter((cause) => cause.consumers.length > 0)
1736
- .sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en'));
1737
- const normalizedTargets = draft.executableTargets.filter((target) => !newlyBlockedKeys.has(pluginWriteTaskTargetKey(target)));
1738
- const changed = JSON.stringify(normalizedCauses) !== JSON.stringify(draft.causes)
1739
- || JSON.stringify(normalizedTargets) !== JSON.stringify(draft.executableTargets);
1740
- draft.causes = normalizedCauses;
1741
- draft.executableTargets = normalizedTargets;
1742
- if (changed)
1743
- continue;
1744
- }
1745
- const blockedPlanTargets = (preview.dependencyPlan?.targets ?? []).filter((target) => target.action === 'block' || target.errorCode !== undefined);
1746
- if (blockedPlanTargets.length === 0)
1747
- break;
1748
- const requestedKeys = new Set(draft.executableTargets.map(pluginWriteTaskTargetKey));
1749
- const blockedPlansByRoot = new Map();
1750
- for (const blockedPlanTarget of blockedPlanTargets) {
1751
- const pending = [blockedPlanTarget];
1752
- const visited = new Set();
1753
- const mappedRoots = new Set();
1754
- const repairKey = pluginWriteTaskTargetKey({
1755
- pluginId: blockedPlanTarget.pluginId,
1756
- platform: blockedPlanTarget.platform,
1757
- scope: blockedPlanTarget.requestedScope ?? blockedPlanTarget.resolvedScope,
1758
- });
1759
- for (const cause of draft.causes) {
1760
- if (pluginWriteTaskTargetKey(cause.repairTarget) !== repairKey)
1761
- continue;
1762
- cause.consumers.forEach((consumer) => mappedRoots.add(pluginWriteTaskTargetKey(consumer)));
1763
- }
1764
- while (pending.length > 0) {
1765
- const current = pending.shift();
1766
- const currentKey = pluginWriteTaskTargetKey({
1767
- pluginId: current.pluginId,
1768
- platform: current.platform,
1769
- scope: current.requestedScope ?? current.resolvedScope,
1770
- });
1771
- if (visited.has(currentKey))
1772
- continue;
1773
- visited.add(currentKey);
1774
- if (current.relation === 'requested' && requestedKeys.has(currentKey)) {
1775
- mappedRoots.add(currentKey);
1776
- }
1777
- for (const consumer of current.requiredByTargets ?? []) {
1778
- const consumerKey = pluginWriteTaskTargetKey(consumer);
1779
- if (requestedKeys.has(consumerKey)) {
1780
- mappedRoots.add(consumerKey);
1781
- continue;
1782
- }
1783
- const placement = preview.dependencyPlan?.targets.find((target) => target.pluginId === consumer.pluginId && target.platform === consumer.platform
1784
- && (target.requestedScope ?? target.resolvedScope) === consumer.scope);
1785
- if (placement)
1786
- pending.push(placement);
1787
- }
1788
- }
1789
- if (mappedRoots.size === 0) {
1790
- throw new TuiOperationBlockedError('taskPlanConsumerMissing', '任务计划中的阻止项无法映射到请求目标。');
1791
- }
1792
- for (const key of mappedRoots) {
1793
- const plans = blockedPlansByRoot.get(key) ?? [];
1794
- plans.push(blockedPlanTarget);
1795
- blockedPlansByRoot.set(key, plans);
1796
- }
1797
- }
1798
- blockedTargets.push(...draft.executableTargets.flatMap((target) => {
1799
- const plans = blockedPlansByRoot.get(pluginWriteTaskTargetKey(target));
1800
- return plans ? [{
1801
- target,
1802
- reason: 'targetUnavailable',
1803
- issueIds: draft.causes.filter((cause) => cause.consumers.some((consumer) => pluginWriteTaskTargetKey(consumer) === pluginWriteTaskTargetKey(target)))
1804
- .map((cause) => cause.issueId),
1805
- errorCodes: [...new Set(plans.map((plan) => plan.errorCode ?? 'writePlanBlocked'))]
1806
- .sort((left, right) => left.localeCompare(right, 'en')),
1807
- diagnostics: plans.map((plan, index) => ({
1808
- id: `task-plan:${sourceId}:${target.pluginId}:${target.platform}:${target.scope}:${index}`,
1809
- code: plan.errorCode ?? 'writePlanBlocked',
1810
- stage: 'dependencyPlan',
1811
- sourceId,
1812
- target: {
1813
- pluginId: plan.pluginId, platform: plan.platform,
1814
- scope: plan.requestedScope ?? plan.resolvedScope,
1815
- },
1816
- causes: [],
1817
- detailLines: [],
1818
- safeMessage: plan.errorCode === 'artifactCollision'
1819
- ? '输出位置存在外来内容,无法覆盖。'
1820
- : plan.errorCode === 'exclusiveOwnerEvidenceInvalid'
1821
- ? '独立扩展的归属标记缺失或损坏。'
1822
- : plan.errorCode === 'dependencyNotReady'
1823
- ? '依赖安装状态无法读取。'
1824
- : '依赖计划阻止了此目标。',
1825
- })),
1826
- }] : [];
1827
- }));
1828
- draft.executableTargets = draft.executableTargets.filter((target) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(target)));
1829
- draft.causes = draft.causes.map((cause) => ({
1830
- ...cause,
1831
- consumers: cause.consumers.filter((consumer) => !blockedPlansByRoot.has(pluginWriteTaskTargetKey(consumer))),
1832
- })).filter((cause) => cause.consumers.length > 0);
1833
- }
1834
- if (!preview?.operationPlan || draft.executableTargets.length === 0)
1835
- continue;
1836
- const sourceIdentity = draft.causes[0]?.sourceIdentity
1837
- ?? status.payload.catalog.sources.find((source) => source.id === sourceId)?.identity;
1838
- if (!sourceIdentity) {
1839
- throw new TuiOperationBlockedError('taskSourceIdentityMissing', '任务来源身份不可验证。');
1840
- }
1841
- const sourceEvidence = {
1842
- sourceId,
1843
- sourceIdentity,
1844
- requestedTargets: draft.executableTargets,
1845
- causes: draft.causes,
1846
- preview,
1847
- executable: true,
1848
- };
1849
- sources.push(sourceEvidence);
1850
- executableTargets.push(...draft.executableTargets);
1851
- causes.push(...draft.causes);
1852
- }
1853
- const canonicalBlocked = [...new Map(blockedTargets.map((target) => [
1854
- pluginWriteTaskTargetKey(target.target), target,
1855
- ])).values()].sort((left, right) => pluginWriteTaskTargetKey(left.target)
1856
- .localeCompare(pluginWriteTaskTargetKey(right.target), 'en'));
1857
- if (executableTargets.length === 0) {
1858
- throw new TuiOperationBlockedError('taskNoExecutableTargets', '任务没有可安全执行的目标。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1859
- }
1860
- if (intent.executionPolicy === 'all' && canonicalBlocked.length > 0) {
1861
- throw new TuiOperationBlockedError('taskHasBlockedTargets', '部分目标需要先处理。请调整选择。', canonicalBlocked.flatMap((target) => target.diagnostics ?? []));
1862
- }
1863
- await preparation?.reporter.emit('evidence');
1864
- const evidence = {
1865
- catalog: {
1866
- configDigest: configSnapshot.digest,
1867
- compositeDigest: digest(sources.map((source) => [
1868
- source.sourceId,
1869
- source.sourceIdentity,
1870
- source.preview.operationPlan?.planFingerprint,
1871
- ])),
1872
- },
1873
- taskPlan: {
1874
- goal: intent.goal,
1875
- requestedTargets: intent.targets,
1876
- executableTargets: [...executableTargets].sort((left, right) => pluginWriteTaskTargetKey(left).localeCompare(pluginWriteTaskTargetKey(right), 'en')),
1877
- causes: [...new Map(causes.map((cause) => [cause.causeKey, cause])).values()]
1878
- .sort((left, right) => left.causeKey.localeCompare(right.causeKey, 'en')),
1879
- blockedTargets: canonicalBlocked,
1880
- sources,
1881
- targetFingerprint: statusTargetFingerprint(observedTargets, observedRevisions),
1882
- planFingerprint: '',
1883
- },
1884
- };
1885
- evidence.taskPlan.planFingerprint = operationEvidenceFingerprint(evidence);
1886
- taskCatalogOperationsByEvidence.set(evidence, operationContexts);
1887
- return evidence;
1888
- }
1889
- if (intent.kind === 'pluginWriteBatch') {
1890
- await preparation?.reporter.emit('status');
1891
- const selection = await resolveBatchGroups(intent, context.env, context.cwd, frozenBatchEvidence?.requestedPluginIds);
1892
- signal?.throwIfAborted();
1893
- if (intent.action === 'cleanup') {
1894
- const groups = [];
1895
- const reviewedRemovedPlugins = new Set();
1896
- for (const group of selection.groups) {
1897
- signal?.throwIfAborted();
1898
- const requestedPluginIds = group.requestedPluginIds.filter((pluginId) => !reviewedRemovedPlugins.has(pluginId));
1899
- if (requestedPluginIds.length === 0)
1900
- continue;
1901
- const preview = await runBatchWriteGroup({
1902
- intent,
1903
- group: { ...group, requestedPluginIds },
1904
- cwd: context.cwd,
1905
- runtime: {
1906
- ...context,
1907
- inventoryCatalogSourceReferences: operationInventory,
1908
- previewOperationPlan: true,
1909
- ...(codexObservationSession ? { codexObservationSession } : {}),
1910
- ...(preparation ? {
1911
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1912
- } : {}),
1913
- },
1914
- });
1915
- for (const pluginId of preview.removedPluginCleanupIds ?? []) {
1916
- reviewedRemovedPlugins.add(pluginId);
1917
- }
1918
- groups.push({ ...group, requestedPluginIds, preview,
1919
- executable: preview.operationPlan !== undefined });
1920
- }
1921
- await preparation?.reporter.emit('evidence');
1922
- signal?.throwIfAborted();
1923
- const evidence = {
1924
- catalog: {
1925
- configDigest: 'cleanup-offline',
1926
- compositeDigest: digest(groups.map((group) => [
1927
- group.groupId,
1928
- group.preview.operationPlan?.planFingerprint,
1929
- ])),
1930
- },
1931
- requestedPluginIds: selection.requestedPluginIds,
1932
- groups,
1933
- };
1934
- return evidence;
1935
- }
1936
- const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
1937
- const groups = [];
1938
- const ownsObservationSession = !codexObservationSession
1939
- && selection.groups.some((group) => {
1940
- const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1941
- return reviewed?.executable !== false
1942
- && (group.platform === 'codex' || group.platform === 'both');
1943
- });
1944
- const observationSession = codexObservationSession ?? (ownsObservationSession
1945
- ? await createCodexNativeObservationSession({
1946
- exactCwd: context.cwd,
1947
- env: context.env,
1948
- runner: context.nativeCliRunner,
1949
- })
1950
- : undefined);
1951
- try {
1952
- await preparation?.reporter.emit('catalog');
1953
- for (const group of selection.groups) {
1954
- signal?.throwIfAborted();
1955
- const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1956
- if (reviewed?.executable === false) {
1957
- groups.push({ ...group, preview: reviewed.preview, executable: false });
1958
- continue;
1959
- }
1960
- let catalogOperation;
1961
- try {
1962
- catalogOperation = !freshCatalog && sessionCatalogOperationContext
1963
- ? selectCatalogOperationContext(sessionCatalogOperationContext, [group.sourceId])
1964
- : await resolveCatalogOperationContext({
1965
- sourceIds: [group.sourceId],
1966
- env: context.env,
1967
- ...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
1968
- ...(signal ? { signal } : {}),
1969
- });
1970
- }
1971
- catch {
1972
- catalogOperation = undefined;
1973
- }
1974
- const preview = await runBatchWriteGroup({
1975
- intent,
1976
- group,
1977
- cwd: context.cwd,
1978
- runtime: {
1979
- ...context,
1980
- inventoryCatalogSourceReferences: operationInventory,
1981
- expectedCatalogConfigDigest: configSnapshot.digest,
1982
- ...(catalogOperation ? {
1983
- catalogOperationContext: catalogOperation,
1984
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1985
- } : {}),
1986
- previewOperationPlan: true,
1987
- ...(observationSession ? { codexObservationSession: observationSession } : {}),
1988
- ...(preparation ? {
1989
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1990
- } : {}),
1991
- },
1992
- });
1993
- groups.push({ ...group, preview, executable: preview.operationPlan !== undefined });
1994
- }
1995
- }
1996
- finally {
1997
- if (ownsObservationSession)
1998
- await observationSession?.dispose();
1999
- }
2000
- await preparation?.reporter.emit('evidence');
2001
- signal?.throwIfAborted();
2002
- const merged = mergeWriteCommandResults(groups.map((group) => group.preview));
2003
- const targetVersions = groups.flatMap((group) => {
2004
- if (group.platform === 'both')
2005
- return [];
2006
- const platform = group.platform;
2007
- return group.requestedPluginIds.map((pluginId) => {
2008
- const live = flattenStatusTargets(sessionStatusResult?.payload.targets ?? []).find((target) => target.pluginId === pluginId && target.platform === platform
2009
- && (target.scope === group.scope || statusTargetIntent(target).scopes.includes(group.scope)
2010
- || platform === 'codex' && statusTargetActivation(target)?.[group.scope] === true));
2011
- const revision = sessionStatusResult?.targetRevisions?.find((target) => target.pluginId === pluginId && target.platform === platform
2012
- && target.scope === group.scope)?.revision;
2013
- const activation = live ? statusTargetActivation(live) : undefined;
2014
- const ownership = live && live.scope === group.scope
2015
- ? statusTargetOwnershipState(live) : 'absent';
2016
- const present = ownership === 'present' || ownership === 'partial'
2017
- || platform === 'codex' && activation?.[group.scope] === true;
2018
- const planned = live ? statusTargetIntent(live).scopes.includes(group.scope) : false;
2019
- return {
2020
- pluginId,
2021
- platform,
2022
- scope: group.scope,
2023
- presence: present ? 'installed'
2024
- : ownership === 'absent' && planned ? 'new' : 'unknown',
2025
- ...(revision?.installedVersion ? { installedVersion: revision.installedVersion } : {}),
2026
- ...(revision?.availableVersion ? { availableVersion: revision.availableVersion } : {}),
2027
- };
2028
- });
2029
- });
2030
- const evidence = {
2031
- catalog: {
2032
- configDigest: configSnapshot.digest,
2033
- compositeDigest: sessionStatusResult?.payload.catalog.compositeDigest
2034
- ?? digest(groups.map((group) => [group.groupId, group.preview.operationPlan?.planFingerprint])),
2035
- },
2036
- requestedPluginIds: selection.requestedPluginIds,
2037
- ...(selection.projectIntentDigest
2038
- ? { projectIntentDigest: selection.projectIntentDigest }
2039
- : {}),
2040
- groups,
2041
- targetVersions: [...new Map(targetVersions.map((target) => [[
2042
- target.pluginId, target.platform, target.scope,
2043
- ].join('\0'), target])).values()].sort((left, right) => [
2044
- left.pluginId, left.platform, left.scope,
2045
- ].join('\0').localeCompare([
2046
- right.pluginId, right.platform, right.scope,
2047
- ].join('\0'), 'en')),
2048
- ...(merged.dependencyPlan ? { dependencyPlan: merged.dependencyPlan } : {}),
2049
- ...(merged.codexReconcilePlans ? { codexReconcilePlans: merged.codexReconcilePlans } : {}),
2050
- ...(merged.operationPlan ? { operationPlan: merged.operationPlan } : {}),
2051
- };
2052
- return evidence;
2053
- }
2054
- if (intent.kind === 'pluginWrite' && intent.action === 'cleanup') {
2055
- const preview = await cleanupWriteCommand([intent.pluginId], {
2056
- cwd: context.cwd,
2057
- platform: intent.platform,
2058
- scope: intent.scope,
2059
- force: intent.force === true,
2060
- }, {
2061
- ...context,
2062
- inventoryCatalogSourceReferences: operationInventory,
2063
- previewOperationPlan: true,
2064
- ...(codexObservationSession ? { codexObservationSession } : {}),
2065
- ...(preparation ? {
2066
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
2067
- } : {}),
2068
- });
2069
- signal?.throwIfAborted();
2070
- if (!preview.operationPlan) {
2071
- throw new TuiOperationBlockedError('cleanupPlanUnavailable', '无法建立可信的清理计划。');
2072
- }
2073
- const offlineCleanupPlans = preview.offlineCleanupPlans ?? [];
2074
- await preparation?.reporter.emit('evidence');
2075
- return {
2076
- catalog: {
2077
- configDigest: 'cleanup-offline',
2078
- compositeDigest: preview.operationPlan.planFingerprint,
2079
- },
2080
- operationPlan: preview.operationPlan,
2081
- offlineCleanupPlans,
2082
- ...(preview.removedPluginCleanupIds
2083
- ? { removedPluginCleanupIds: preview.removedPluginCleanupIds }
2084
- : {}),
2085
- ...(offlineCleanupPlans.length === 1
2086
- ? { offlineCleanup: offlineCleanupPlans[0] }
2087
- : {}),
2088
- targetFingerprint: preview.operationPlan.planFingerprint,
2089
- };
2090
- }
2091
1264
  const packageInfo = await readCliPackageInfo();
2092
1265
  let catalogOperation;
2093
1266
  let catalogOperationError;
@@ -2159,6 +1332,75 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2159
1332
  catalogOperationByEvidence.set(evidence, catalogOperation);
2160
1333
  return evidence;
2161
1334
  }
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
+ }
2162
1404
  const needsReferencePlugins = intent.kind === 'configSave'
2163
1405
  && await withResolvedCatalogPlugin({
2164
1406
  selector: intent.pluginId, cliVersion: packageInfo.version, env: context.env,
@@ -2193,7 +1435,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2193
1435
  if (configSnapshot.digest !== sessionStatusResult.payload.catalog.configDigest) {
2194
1436
  throw operationReviewStale('准备设置确认期间来源配置发生变化。');
2195
1437
  }
2196
- 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);
2197
1439
  const preparedCatalogOperation = catalogOperation;
2198
1440
  const selected = await withResolvedCatalogPlugin({
2199
1441
  selector: intent.pluginId,
@@ -2206,14 +1448,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2206
1448
  if (!plugin.manifest.config) {
2207
1449
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2208
1450
  }
1451
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2209
1452
  if (intent.kind === 'configSave') {
2210
1453
  await coercePluginConfigUpdates({
2211
- schema: plugin.manifest.config.fields,
1454
+ schema: definition.fields,
2212
1455
  updates: intent.updates ?? {},
2213
1456
  cwd: context.cwd,
2214
1457
  });
2215
1458
  for (const key of intent.removeKeys ?? []) {
2216
- if (!plugin.manifest.config.fields[key]) {
1459
+ if (!definition.fields[key]) {
2217
1460
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2218
1461
  }
2219
1462
  }
@@ -2221,7 +1464,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2221
1464
  const impact = intent.retryApply ? undefined : await analyzePluginConfigMutationImpact({
2222
1465
  plugin,
2223
1466
  referencePlugins,
2224
- scope: intent.scope,
1467
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2225
1468
  cwd: context.cwd,
2226
1469
  env: context.env,
2227
1470
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, relevantTargets),
@@ -2235,12 +1478,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2235
1478
  ? configImpactVisibleTargets(plugin.manifest.name, relevantTargets)
2236
1479
  : impact.affectedTargets;
2237
1480
  const commandPackages = impact?.affectedCommandPackages
2238
- ?? (await discoverPluginConfigManagedCommandPackages({ plugin, scope: intent.scope,
1481
+ ?? (await discoverPluginConfigManagedCommandPackages({ plugin, scope: intent.context ?? 'user',
2239
1482
  cwd: context.cwd, env: context.env ?? process.env, platformTargets: affectedTargets, referencePlugins })).packages;
2240
1483
  const preApplyAuthorization = configIntentRequestsApply(intent)
2241
1484
  ? (await resolveConfigPreApplyAuthorization({
2242
1485
  plugin,
2243
- scope: intent.scope,
1486
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2244
1487
  cwd: context.cwd,
2245
1488
  env: context.env,
2246
1489
  applyTargets: affectedTargets,
@@ -2256,12 +1499,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2256
1499
  return {
2257
1500
  pluginName: plugin.manifest.name,
2258
1501
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1502
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, intent.platform),
2259
1503
  impact,
2260
1504
  commandPackages,
2261
1505
  preApplyAuthorization,
2262
- retryStoreDigest: intent.retryApply ? await configRetryStoreDigest({ plugin, scope: intent.scope,
2263
- targets: affectedTargets, commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env,
2264
- plugins: referencePlugins }) : 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,
2265
1508
  sharedNativeRenderTargets: intent.retryApply
2266
1509
  ? await configRetrySharedNativeRenderTargets(plugin, affectedTargets, context.env, referencePlugins)
2267
1510
  : impact.sharedNativeRenderTargets,
@@ -2282,8 +1525,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2282
1525
  ...(selected.source ? { source: selected.source } : {}),
2283
1526
  targetFingerprint: statusTargetFingerprint(relevantTargets, relevantRevisions),
2284
1527
  config: {
2285
- scope: intent.scope,
2286
- ...(selected.source?.manifestDigest ? { schemaIdentity: selected.source.manifestDigest } : {}),
1528
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1529
+ schemaIdentity: selected.schemaIdentity,
2287
1530
  storeDigest: selected.impact?.storeDigest
2288
1531
  ?? selected.retryStoreDigest,
2289
1532
  impactState: selected.impact?.impactState ?? 'known',
@@ -2299,6 +1542,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2299
1542
  : {}),
2300
1543
  },
2301
1544
  };
1545
+ await assertConfigEditEvidence(intent, evidence);
2302
1546
  if (catalogOperation)
2303
1547
  catalogOperationByEvidence.set(evidence, catalogOperation);
2304
1548
  return evidence;
@@ -2307,17 +1551,11 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2307
1551
  const statusCatalogOperation = catalogOperation
2308
1552
  ?? (!freshCatalog ? sessionCatalogOperationContext : undefined);
2309
1553
  const status = await statusCommandV8({
2310
- plugins: [...new Set([intent.pluginId, ...configReferencePluginIds,
2311
- ...(intent.kind === 'pluginWrite' && intent.originIssue?.consumerPluginId
2312
- ? [intent.originIssue.consumerPluginId] : [])])],
2313
- ...('platform' in intent ? { platform: intent.platform } : {}),
2314
- ...(intent.kind !== 'pluginWrite' && intent.scope === 'project'
2315
- ? { scope: intent.scope }
2316
- : {}),
1554
+ plugins: [...new Set([intent.pluginId, ...configReferencePluginIds])],
1555
+ ...(intent.context === 'project' ? {} : { scope: 'user' }),
2317
1556
  cwd: context.cwd,
2318
1557
  env: context.env,
2319
1558
  nativeCliRunner: context.nativeCliRunner,
2320
- ...(codexObservationSession ? { codexObservationSession } : {}),
2321
1559
  cliVersion: packageInfo.version,
2322
1560
  ...(statusCatalogOperation ? { catalogOperationContext: statusCatalogOperation } : {}),
2323
1561
  inventoryCatalogSourceReferences: operationInventory,
@@ -2339,12 +1577,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2339
1577
  ...(signal ? { signal } : {}),
2340
1578
  });
2341
1579
  }
2342
- let dependencyPlan;
2343
- let codexReconcilePlans;
2344
- let operationPlan;
2345
1580
  await preparation?.reporter.emit('catalog');
2346
1581
  const configVisibleTargets = intent.kind === 'configSave'
2347
- ? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.scope, configReferencePluginIds)
1582
+ ? configAffectedStatusTargets(status.payload.targets, intent.pluginId, intent.context ?? 'user', configReferencePluginIds, intent.platform)
2348
1583
  : [];
2349
1584
  const selected = await withResolvedCatalogPlugin({
2350
1585
  selector: intent.pluginId,
@@ -2359,13 +1594,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2359
1594
  if (!plugin.manifest.config) {
2360
1595
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2361
1596
  }
1597
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2362
1598
  await coercePluginConfigUpdates({
2363
- schema: plugin.manifest.config.fields,
1599
+ schema: definition.fields,
2364
1600
  updates: intent.updates ?? {},
2365
1601
  cwd: context.cwd,
2366
1602
  });
2367
1603
  for (const key of intent.removeKeys ?? []) {
2368
- if (!plugin.manifest.config.fields[key]) {
1604
+ if (!definition.fields[key]) {
2369
1605
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2370
1606
  }
2371
1607
  }
@@ -2374,7 +1610,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2374
1610
  ? await analyzePluginConfigMutationImpact({
2375
1611
  plugin,
2376
1612
  referencePlugins,
2377
- scope: intent.scope,
1613
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2378
1614
  cwd: context.cwd,
2379
1615
  env: context.env,
2380
1616
  visibleTargets: configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets),
@@ -2390,7 +1626,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2390
1626
  : [];
2391
1627
  const commandPackages = impact?.affectedCommandPackages
2392
1628
  ?? (intent.kind === 'configSave' ? (await discoverPluginConfigManagedCommandPackages({
2393
- 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,
2394
1630
  platformTargets: configImpactVisibleTargets(plugin.manifest.name, configVisibleTargets),
2395
1631
  referencePlugins,
2396
1632
  })).packages : []);
@@ -2398,7 +1634,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2398
1634
  && configIntentRequestsApply(intent)
2399
1635
  ? (await resolveConfigPreApplyAuthorization({
2400
1636
  plugin,
2401
- scope: intent.scope,
1637
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
2402
1638
  cwd: context.cwd,
2403
1639
  env: context.env,
2404
1640
  applyTargets: intent.retryApply
@@ -2418,11 +1654,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2418
1654
  return {
2419
1655
  pluginName: plugin.manifest.name,
2420
1656
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1657
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, intent.platform),
2421
1658
  impact,
2422
1659
  commandPackages,
2423
1660
  preApplyAuthorization,
2424
1661
  retryStoreDigest: intent.kind === 'configSave' && intent.retryApply
2425
- ? await configRetryStoreDigest({ plugin, scope: intent.scope, targets: affectedTargets,
1662
+ ? await configRetryStoreDigest({ plugin, scope: intent.context ?? 'user', platform: intent.platform, targets: affectedTargets,
2426
1663
  commandPackages, preApplyAuthorization, cwd: context.cwd, env: context.env, plugins: referencePlugins }) : undefined,
2427
1664
  sharedNativeRenderTargets: intent.kind === 'configSave'
2428
1665
  ? intent.retryApply
@@ -2432,106 +1669,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2432
1669
  };
2433
1670
  },
2434
1671
  });
2435
- if (intent.kind === 'pluginWrite' && intent.expectedUpdate) {
2436
- const revision = status.targetRevisions.find((entry) => entry.pluginId === intent.pluginId && entry.platform === intent.platform
2437
- && entry.scope === intent.scope);
2438
- const generation = catalogOperation?.generationSet.sources.find((entry) => entry.sourceId === selected.source.sourceId);
2439
- if (intent.action !== 'sync'
2440
- || !('pluginVersion' in selected.source)
2441
- || selected.source.sourceIdentity !== intent.expectedUpdate.sourceIdentity
2442
- || selected.source.pluginVersion !== intent.expectedUpdate.activeVersion
2443
- || generation?.storeKey !== intent.expectedUpdate.generationKey
2444
- || revision?.revision.installedVersion !== intent.expectedUpdate.installedVersion
2445
- || revision?.revision.availableVersion !== intent.expectedUpdate.activeVersion
2446
- || revision.revision.state !== 'upgradeAvailable') {
2447
- throw operationReviewStale('插件更新版本或 Active Catalog 证据已变化。');
2448
- }
2449
- }
2450
- let codexManager;
2451
- let resolvedCodexManager;
2452
- const hasDeclaredDependencies = (status.pluginSummaries.find((summary) => summary.pluginId === intent.pluginId)?.dependencyTotalCount ?? 0) > 0;
2453
- if (intent.kind === 'pluginWrite') {
2454
- const command = {
2455
- install: installWriteCommand,
2456
- refreshAndSync: syncWriteCommand,
2457
- reinstall: reinstallWriteCommand,
2458
- uninstall: uninstallWriteCommand,
2459
- cleanup: cleanupWriteCommand,
2460
- sync: syncWriteCommand,
2461
- }[intent.action];
2462
- const preview = await command([intent.pluginId], {
2463
- cwd: context.cwd,
2464
- platform: intent.platform,
2465
- scope: intent.scope,
2466
- ...(reviewedScriptTimeoutMs(intent) === undefined
2467
- ? {}
2468
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
2469
- ...(intent.action === 'install' && intent.dependencyScopeOverrides
2470
- ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
2471
- : {}),
2472
- ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
2473
- }, {
2474
- ...context,
2475
- inventoryCatalogSourceReferences: operationInventory,
2476
- expectedCatalogConfigDigest: status.payload.catalog.configDigest,
2477
- ...(catalogOperation ? {
2478
- catalogOperationContext: catalogOperation,
2479
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
2480
- } : {}),
2481
- previewOperationPlan: true,
2482
- ...(codexObservationSession ? { codexObservationSession } : {}),
2483
- ...(preparation ? {
2484
- onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
2485
- } : {}),
2486
- });
2487
- signal?.throwIfAborted();
2488
- codexReconcilePlans = preview?.codexReconcilePlans;
2489
- operationPlan = preview?.operationPlan;
2490
- if (hasDeclaredDependencies
2491
- && (intent.action === 'install'
2492
- || intent.action === 'refreshAndSync'
2493
- || intent.action === 'reinstall'
2494
- || intent.action === 'sync')) {
2495
- dependencyPlan = preview.dependencyPlan;
2496
- if (!dependencyPlan) {
2497
- throw new TuiOperationBlockedError('dependencyGraphInvalid', '无法建立可信的依赖安装计划。');
2498
- }
2499
- const blocker = dependencyPlan.targets.find((target) => target.action === 'block');
2500
- if (blocker) {
2501
- const code = blocker.errorCode ?? 'dependencyNotReady';
2502
- const reason = code === 'dependencyScopeConflict' || code === 'codexExtensionScopeConflict'
2503
- ? '这个依赖的安装位置存在冲突,需要先选择保留的位置。'
2504
- : code === 'artifactCollision' || code === 'exclusiveOwnerEvidenceInvalid'
2505
- ? '这个依赖的安装位置被其他内容占用,需要先检查并处理冲突。'
2506
- : '这个依赖尚不可用,需要先检查并恢复后再安装。';
2507
- throw new TuiOperationBlockedError(code, `${blocker.pluginId}:${reason}`, [{
2508
- id: `dependency:${blocker.pluginId}:${blocker.platform}:${blocker.resolvedScope}`,
2509
- code, stage: 'dependencyPlan', safeMessage: reason,
2510
- target: { pluginId: blocker.pluginId, platform: blocker.platform, scope: blocker.resolvedScope },
2511
- causes: [], detailLines: [],
2512
- }]);
2513
- }
2514
- }
2515
- const managerPreview = preview?.codexManagerPreview;
2516
- if (managerPreview?.state === 'blocked') {
2517
- const message = managerPreview.code === 'codexSystemCliUnsupported'
2518
- ? '系统 Codex CLI 版本不受支持。请升级官方 Codex CLI 后重试。'
2519
- : 'AW-managed Codex runtime 当前不可用。请重试、升级 AW,或安装官方 Codex CLI。';
2520
- throw new TuiOperationBlockedError(managerPreview.code, `${managerPreview.code}: ${message}`);
2521
- }
2522
- if (managerPreview) {
2523
- if (managerPreview.state === 'system' || managerPreview.state === 'cached') {
2524
- resolvedCodexManager = managerPreview.manager;
2525
- }
2526
- codexManager = {
2527
- state: managerPreview.state,
2528
- packageName: MANAGED_CODEX_PACKAGE_NAME,
2529
- version: managerPreview.state === 'requires-install'
2530
- ? MANAGED_CODEX_VERSION
2531
- : managerPreview.manager.version,
2532
- };
2533
- }
2534
- }
2535
1672
  await preparation?.reporter.emit('evidence');
2536
1673
  signal?.throwIfAborted();
2537
1674
  const evidenceTargets = intent.kind === 'configSave'
@@ -2551,10 +1688,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2551
1688
  : {}),
2552
1689
  },
2553
1690
  source: selected.source,
2554
- ...(dependencyPlan ? { dependencyPlan } : {}),
2555
- ...(codexReconcilePlans ? { codexReconcilePlans } : {}),
2556
- ...(operationPlan ? { operationPlan } : {}),
2557
- ...(codexManager ? { codexManager } : {}),
2558
1691
  targetFingerprint: statusTargetFingerprint(evidenceTargets, evidenceRevisions),
2559
1692
  };
2560
1693
  if (intent.kind === 'configSave') {
@@ -2564,10 +1697,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2564
1697
  throw operationReviewStale('设置操作缺少实际配置输入证据。');
2565
1698
  signal?.throwIfAborted();
2566
1699
  evidence.config = {
2567
- scope: intent.scope,
2568
- ...('manifestDigest' in selected.source
2569
- ? { schemaIdentity: selected.source.manifestDigest }
2570
- : {}),
1700
+ scope: 'user', context: intent.context ?? 'user', platform: intent.platform,
1701
+ schemaIdentity: selected.schemaIdentity,
2571
1702
  storeDigest,
2572
1703
  impactState: impact?.impactState ?? 'known',
2573
1704
  ...(impact?.noEffectiveChange ? { noEffectiveChange: true } : {}),
@@ -2575,15 +1706,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2575
1706
  ? { commandPackages: selected.commandPackages } : {}),
2576
1707
  affectedTargets: (impact?.affectedTargets
2577
1708
  ?? evidenceTargets).map(({ pluginName, platform, scope }) => ({ pluginName, platform, scope })),
2578
- ...(intent.scope === 'project' ? {
2579
- shareability: await inspectConfigShareability({
2580
- sourceId: selected.source.sourceId,
2581
- pluginName: selected.pluginName,
2582
- scope: intent.scope,
2583
- cwd: context.cwd,
2584
- env: context.env,
2585
- }),
2586
- } : {}),
2587
1709
  ...('sharedNativeRenderTargets' in selected
2588
1710
  && selected.sharedNativeRenderTargets.length > 0
2589
1711
  ? { sharedNativeRenderTargets: selected.sharedNativeRenderTargets }
@@ -2594,13 +1716,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2594
1716
  : {}),
2595
1717
  };
2596
1718
  }
1719
+ await assertConfigEditEvidence(intent, evidence);
2597
1720
  if (catalogOperation)
2598
1721
  catalogOperationByEvidence.set(evidence, catalogOperation);
2599
- if (resolvedCodexManager)
2600
- codexManagerByEvidence.set(evidence, resolvedCodexManager);
2601
1722
  return evidence;
2602
1723
  };
2603
1724
  const dependencies = {
1725
+ readAgentSettings: () => readAgentSettings(context),
1726
+ previewAgentSettings: (preferences, pluginId) => previewAgentSettings(context, preferences, pluginId),
1727
+ saveAgentSettings: (request) => saveAgentSettings(context, request),
2604
1728
  beginCatalogSession(sessionOptions) {
2605
1729
  sessionCatalogOperationContext = undefined;
2606
1730
  if (sessionOptions?.reuseValidatedGenerations !== true) {
@@ -2609,6 +1733,104 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2609
1733
  }
2610
1734
  },
2611
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
+ },
2612
1834
  loadPluginContent,
2613
1835
  async probeCatalogFreshness(sourceId, freshnessOptions, signal) {
2614
1836
  signal.throwIfAborted();
@@ -2657,55 +1879,42 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2657
1879
  now: options.now ?? Date.now,
2658
1880
  });
2659
1881
  try {
2660
- const preparationWarnings = [];
2661
- if (intent.kind === 'pluginWrite' && intent.action === 'refreshAndSync') {
2662
- await reporter.emit('refresh');
2663
- const packageInfo = await readCliPackageInfo();
2664
- const sourceId = parsePluginId(intent.pluginId).sourceId;
2665
- await syncCatalogSourceGeneration({
2666
- sourceId,
2667
- cliVersion: packageInfo.version,
2668
- cwd: context.cwd,
2669
- env: context.env,
2670
- signal,
2671
- }).catch((error) => {
2672
- preparationWarnings.push(`catalogSourceRefreshFailed: ${sourceId}: ${error instanceof Error ? error.message : String(error)}`);
2673
- });
2674
- sessionCatalogOperationContext = undefined;
2675
- sessionStatusResult = undefined;
2676
- validatedStoreEntries.clear();
2677
- validatingStoreEntries.clear();
2678
- }
2679
- const taskObservationSession = intent.kind === 'pluginWriteTask'
2680
- && intent.targets.some((target) => target.platform === 'codex')
2681
- ? await createCodexNativeObservationSession({
2682
- exactCwd: context.cwd,
2683
- env: context.env,
2684
- runner: context.nativeCliRunner,
2685
- })
2686
- : undefined;
2687
- let evidence;
2688
- try {
2689
- evidence = await captureEvidence(intent, signal, {
2690
- reporter,
2691
- validateIntent: true,
2692
- }, createOperationInventoryReader(), intent.kind === 'pluginWrite'
2693
- && intent.action === 'refreshAndSync', reusableCandidate, taskObservationSession);
2694
- }
2695
- finally {
2696
- await taskObservationSession?.dispose();
2697
- }
2698
- if (preparationWarnings.length > 0)
2699
- evidence.preparationWarnings = preparationWarnings;
2700
- return evidence;
1882
+ return await captureEvidence(intent, signal, {
1883
+ reporter,
1884
+ validateIntent: true,
1885
+ }, createOperationInventoryReader(), false, reusableCandidate);
2701
1886
  }
2702
1887
  finally {
2703
1888
  await reporter.finish();
2704
1889
  }
2705
1890
  },
2706
1891
  async previewDependencyPlacement(intent, signal) {
2707
- const evidence = await captureEvidence(intent, signal);
2708
- 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
+ }
2709
1918
  },
2710
1919
  async validateIntent(intent, signal) {
2711
1920
  if (intent.kind === 'catalogSourcesRecovery') {
@@ -2744,13 +1953,14 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2744
1953
  if (!plugin.manifest.config) {
2745
1954
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2746
1955
  }
1956
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, intent.platform);
2747
1957
  await coercePluginConfigUpdates({
2748
- schema: plugin.manifest.config.fields,
1958
+ schema: definition.fields,
2749
1959
  updates: intent.updates ?? {},
2750
1960
  cwd: context.cwd,
2751
1961
  });
2752
1962
  for (const key of intent.removeKeys ?? []) {
2753
- if (!plugin.manifest.config.fields[key]) {
1963
+ if (!definition.fields[key]) {
2754
1964
  throw new TuiOperationBlockedError('unknownConfigKey', '设置字段已变化,请重新检查。');
2755
1965
  }
2756
1966
  }
@@ -2790,39 +2000,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2790
2000
  data: { code },
2791
2001
  });
2792
2002
  },
2793
- async getConfigGroups(pluginId, layer, signal) {
2794
- const packageInfo = await readCliPackageInfo();
2795
- return withResolvedCatalogPlugin({
2796
- selector: pluginId,
2797
- cliVersion: packageInfo.version,
2798
- env: context.env,
2799
- signal,
2800
- ...(sessionContextForPlugin(pluginId)
2801
- ? { operationContext: sessionContextForPlugin(pluginId) }
2802
- : {}),
2803
- consume: async ({ plugin }) => {
2804
- if (!plugin.manifest.config) {
2805
- throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2806
- }
2807
- const read = await resolveConfigRead({
2808
- pluginName: plugin.manifest.name,
2809
- plugin,
2810
- scope: layer.scope,
2811
- cwd: context.cwd,
2812
- env: context.env,
2813
- });
2814
- const effectiveRead = await resolveConfigRead({
2815
- pluginName: plugin.manifest.name,
2816
- plugin,
2817
- scope: 'effective',
2818
- cwd: context.cwd,
2819
- env: context.env,
2820
- });
2821
- return buildConfigGroups({ schema: plugin.manifest.config, read, effectiveRead });
2822
- },
2823
- });
2003
+ async getConfigGroups(pluginId, config, signal) {
2004
+ return (await dependencies.getConfigPage(pluginId, config, signal)).groups;
2824
2005
  },
2825
- async getConfigPage(pluginId, layer, signal) {
2006
+ async getConfigPage(pluginId, config, signal) {
2007
+ if (config.context === 'project')
2008
+ await resolveWorkContext({ cwd: context.cwd, context: 'project' });
2826
2009
  const packageInfo = await readCliPackageInfo();
2827
2010
  return withResolvedCatalogPlugin({
2828
2011
  selector: pluginId,
@@ -2836,112 +2019,107 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2836
2019
  if (!plugin.manifest.config) {
2837
2020
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2838
2021
  }
2022
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, config.platform);
2839
2023
  const stores = await readConfigStoresForTui({
2840
2024
  sourceId: plugin.sourceId,
2841
2025
  sourceIdentity: plugin.sourceIdentity,
2842
2026
  logicalIdentity: plugin.catalogLogicalIdentity,
2843
2027
  pluginName: plugin.manifest.name,
2844
- schema: plugin.manifest.config.fields,
2845
- scope: layer.scope,
2028
+ schema: definition.fields,
2029
+ platform: config.platform,
2846
2030
  signal,
2847
2031
  });
2848
2032
  const userStore = stores.user;
2849
- const projectStore = stores.project;
2850
2033
  const read = await resolveConfigRead({
2851
2034
  pluginName: plugin.manifest.name,
2852
2035
  plugin,
2853
- scope: layer.scope,
2036
+ scope: config.view === 'effective' ? 'effective' : 'user',
2037
+ platform: config.platform,
2038
+ context: config.context ?? 'user',
2854
2039
  cwd: context.cwd,
2855
2040
  env: context.env,
2856
2041
  });
2857
- const effectiveRead = layer.scope === 'effective' ? read : await resolveConfigRead({
2042
+ const effectiveRead = config.view === 'effective' ? read : await resolveConfigRead({
2858
2043
  pluginName: plugin.manifest.name,
2859
2044
  plugin,
2860
2045
  scope: 'effective',
2046
+ platform: config.platform,
2047
+ context: config.context ?? 'user',
2861
2048
  cwd: context.cwd,
2862
2049
  env: context.env,
2863
2050
  });
2864
- const layerReads = layer.scope === 'effective' ? undefined : {
2865
- user: layer.scope === 'user' ? read : await resolveConfigRead({
2866
- pluginName: plugin.manifest.name,
2867
- plugin,
2868
- scope: 'user',
2869
- cwd: context.cwd,
2870
- env: context.env,
2871
- }),
2872
- ...(layer.scope === 'user' ? {} : { project: read }),
2873
- };
2874
2051
  const effectiveRaw = await resolveEffectivePluginConfig({
2875
2052
  sourceId: plugin.sourceId,
2876
2053
  sourceIdentity: plugin.sourceIdentity,
2877
2054
  logicalIdentity: plugin.catalogLogicalIdentity,
2878
2055
  pluginName: plugin.manifest.name,
2879
2056
  manifest: plugin.manifest,
2880
- targetScope: layer.scope === 'user' ? 'user' : 'project',
2057
+ platform: config.platform,
2058
+ context: config.context ?? 'user',
2881
2059
  cwd: context.cwd,
2882
2060
  env: context.env,
2883
2061
  });
2884
- const layerStore = layer.scope === 'effective'
2885
- ? undefined
2886
- : layer.scope === 'user' ? userStore : projectStore;
2887
2062
  const page = buildConfigPage({
2888
2063
  pluginId,
2889
- schema: plugin.manifest.config,
2064
+ context: config.context ?? 'user',
2065
+ schema: definition,
2890
2066
  read,
2891
2067
  effectiveRead,
2892
- ...(layerReads ? { layerReads } : {}),
2893
- ...(layerStore ? { rawValues: layerStore.values } : {}),
2068
+ ...(config.view === 'effective' ? {} : { rawValues: userStore.values }),
2894
2069
  rawLayerValues: {
2895
- user: userStore.values,
2896
- ...(projectStore ? { project: projectStore.values } : {}),
2070
+ common: userStore.common ?? {},
2071
+ ...(userStore.platforms ?? {}),
2897
2072
  },
2898
2073
  effectiveRawValues: effectiveRaw.values,
2899
- 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)
2900
2076
  ? target.refreshKeys
2901
2077
  : [])),
2902
2078
  });
2903
2079
  return { ...page, editEvidence: {
2080
+ platform: config.platform,
2081
+ context: config.context ?? 'user',
2904
2082
  sourceIdentity: source.sourceIdentity,
2905
- schemaIdentity: source.manifestDigest,
2083
+ schemaIdentity: configSchemaIdentity(plugin, source.manifestDigest, config.platform),
2906
2084
  storeDigests: {
2907
2085
  user: digest({ exists: userStore.exists, rawDigest: userStore.rawDigest }),
2908
- ...(projectStore ? { project: digest({ exists: projectStore.exists, rawDigest: projectStore.rawDigest }) } : {}),
2909
2086
  },
2910
2087
  } };
2911
2088
  },
2912
2089
  });
2913
2090
  },
2914
- 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' });
2915
2093
  const packageInfo = await readCliPackageInfo();
2916
2094
  return withResolvedCatalogPlugin({
2917
2095
  selector: pluginId, cliVersion: packageInfo.version, env: context.env, signal,
2918
2096
  ...(sessionContextForPlugin(pluginId) ? { operationContext: sessionContextForPlugin(pluginId) } : {}),
2919
2097
  consume: async ({ plugin }) => {
2920
- const definition = plugin.manifest.config;
2921
- if (!definition)
2098
+ if (!plugin.manifest.config)
2922
2099
  throw new TuiOperationBlockedError('configSchemaUnavailable', '这个插件没有设置 schema。');
2100
+ const definition = compilePluginConfigDefinition(plugin.manifest.config, config.platform);
2923
2101
  const scalars = Object.values(definition.fields).flatMap(configReferenceScalars);
2924
2102
  if (!scalars.length)
2925
- return { selections: {} };
2103
+ return { platform: config.platform, selections: {} };
2926
2104
  const stores = await readConfigStoresForTui({
2927
2105
  sourceId: plugin.sourceId, sourceIdentity: plugin.sourceIdentity,
2928
2106
  logicalIdentity: plugin.catalogLogicalIdentity, pluginName: plugin.manifest.name,
2929
- schema: definition.fields, scope, signal,
2107
+ schema: definition.fields, platform: config.platform, signal,
2930
2108
  });
2931
- const edited = scope === 'user' ? stores.user : stores.project;
2109
+ const edited = stores.user;
2932
2110
  const values = { ...edited.values };
2933
2111
  for (const key of draft.removes)
2934
2112
  delete values[key];
2935
2113
  Object.assign(values, draft.updates);
2936
- const candidateStores = { ...stores, [scope]: { ...edited, values } };
2114
+ const candidateStores = { user: replacePluginConfigStoreLayer(edited, values, config.platform) };
2937
2115
  const references = await resolvePluginConfigReferenceContext({
2938
2116
  sourceId: plugin.sourceId, sourceIdentity: plugin.sourceIdentity,
2939
2117
  logicalIdentity: plugin.catalogLogicalIdentity, manifest: plugin.manifest,
2940
- scope, cwd: context.cwd, env: context.env,
2118
+ scope: 'user', context: config.context ?? 'user', platform: config.platform, cwd: context.cwd, env: context.env,
2941
2119
  });
2942
2120
  const effective = await resolveEffectivePluginConfigFromStores({
2943
- manifest: plugin.manifest, targetScope: scope,
2944
- projectRoot: resolveAwProjectRoot(context.cwd), stores: candidateStores,
2121
+ manifest: plugin.manifest, context: config.context ?? 'user', platform: config.platform,
2122
+ projectRoot: workContext.projectRoot ?? '', stores: candidateStores,
2945
2123
  referenceContext: references.context,
2946
2124
  });
2947
2125
  // Keep invalid raw sources for Core validation; effective.values omits them.
@@ -2952,17 +2130,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2952
2130
  const source = effective.sources[key];
2953
2131
  if (source === 'default')
2954
2132
  candidate[key] = definition.fields[key].default;
2955
- else if (source === 'user')
2956
- candidate[key] = candidateStores.user.values[key];
2957
- else if (source === 'project')
2958
- 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];
2959
2137
  }
2960
- const result = { selections: {} };
2138
+ const result = { platform: config.platform, selections: {} };
2961
2139
  for (const scalar of scalars) {
2962
2140
  const ref = scalar.valueRef;
2963
2141
  const selection = getPluginConfigReferenceChoices(definition, scalar, candidate, references.context);
2964
2142
  const entries = ref.plugin ? references.context.plugins[ref.plugin]?.values?.[ref.field] : candidate[ref.field];
2965
- result.selections[configReferenceKey(scalar)] = {
2143
+ result.selections[configReferenceKey(scalar, config.platform)] = {
2966
2144
  ...selection, source: (ref.plugin
2967
2145
  ? references.evidence.find((entry) => entry.plugin === ref.plugin)?.sources[ref.field]
2968
2146
  : effective.sources[ref.field]) ?? 'missing',
@@ -2974,7 +2152,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
2974
2152
  },
2975
2153
  });
2976
2154
  },
2977
- async getConfigResetAvailability(pluginId, layer, signal) {
2155
+ async getConfigResetAvailability(pluginId, config, signal) {
2156
+ if (config.context === 'project')
2157
+ await resolveWorkContext({ cwd: context.cwd, context: 'project' });
2978
2158
  const parsed = parsePluginId(pluginId);
2979
2159
  if (!parsed.sourceId || !parsed.pluginName) {
2980
2160
  throw new TuiOperationBlockedError('invalidPluginId', '设置目标必须使用 canonical pluginId。');
@@ -3007,7 +2187,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3007
2187
  const target = await inspectPluginConfigResetTarget({
3008
2188
  sourceId: parsed.sourceId,
3009
2189
  pluginName: parsed.pluginName,
3010
- scope: layer.scope,
2190
+ scope: 'user',
3011
2191
  cwd: context.cwd,
3012
2192
  env: context.env,
3013
2193
  });
@@ -3018,7 +2198,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3018
2198
  ...(sourceIdentity ? { sourceIdentity } : {}),
3019
2199
  ...(logicalIdentity ? { logicalIdentity } : {}),
3020
2200
  pluginName: parsed.pluginName,
3021
- scope: layer.scope,
2201
+ scope: 'user',
2202
+ platform: config.platform,
3022
2203
  cwd: context.cwd,
3023
2204
  env: context.env,
3024
2205
  });
@@ -3045,6 +2226,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3045
2226
  };
3046
2227
  },
3047
2228
  async executeIntent(intent, expectedEvidence, signal) {
2229
+ if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
2230
+ || intent.kind === 'pluginWriteTask') {
2231
+ throw new TuiOperationBlockedError('useScopedRecovery', '请使用统一范围恢复任务。');
2232
+ }
3048
2233
  if (intent.kind === 'catalogStorePrune') {
3049
2234
  signal?.throwIfAborted();
3050
2235
  if (!expectedEvidence.catalogStore) {
@@ -3331,351 +2516,13 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3331
2516
  }
3332
2517
  throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
3333
2518
  }
3334
- if (intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch'
3335
- || intent.kind === 'pluginWriteTask'
3336
- || intent.kind === 'configSave' && (intent.apply || intent.retryApply)
3337
- || intent.kind === 'configSaveBatch' && intent.apply) {
3338
- await prepareNativeCliExecution({ env: context.env, runner: context.nativeCliRunner });
3339
- }
3340
- const codexObservationSession = intent.kind === 'pluginWrite'
3341
- || intent.kind === 'pluginWriteBatch'
3342
- || intent.kind === 'pluginWriteTask'
3343
- ? await createCodexNativeObservationSession({
3344
- exactCwd: context.cwd,
3345
- env: context.env,
3346
- runner: context.nativeCliRunner,
3347
- manager: codexManagerByEvidence.get(expectedEvidence),
3348
- })
3349
- : undefined;
3350
2519
  return withAwHomeMutationLock({
3351
2520
  env: context.env,
3352
2521
  action: async (awHomeMutationToken) => {
3353
2522
  await tuiControllerRuntimeTestHooks?.beforeMutationEvidenceCapture?.();
3354
2523
  const operationInventory = createOperationInventoryReader();
3355
- const managedCommandProducerCache = new Map();
3356
- let currentEvidence;
3357
- try {
3358
- currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true, undefined, codexObservationSession, intent.kind === 'pluginWriteBatch'
3359
- ? expectedEvidence
3360
- : undefined);
3361
- }
3362
- catch (error) {
3363
- const code = error instanceof TuiOperationBlockedError ? error.code : undefined;
3364
- if (intent.kind === 'pluginWriteBatch'
3365
- && (intent.selection.kind === 'currentProjectIntentRoots'
3366
- || intent.selection.kind === 'currentProjectIntentTargets')
3367
- && error instanceof TuiOperationBlockedError) {
3368
- throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
3369
- }
3370
- if (intent.kind === 'pluginWriteTask'
3371
- && (code === 'taskHasBlockedTargets' || code === 'taskNoExecutableTargets')) {
3372
- throw operationReviewStale('确认后任务可执行目标发生变化。', error instanceof TuiOperationBlockedError ? error.diagnostics : undefined);
3373
- }
3374
- throw error;
3375
- }
3376
- if (intent.kind === 'pluginWriteBatch') {
3377
- const expectedBatch = expectedEvidence;
3378
- const currentBatch = currentEvidence;
3379
- if ((intent.selection.kind === 'currentProjectIntentRoots'
3380
- || intent.selection.kind === 'currentProjectIntentTargets')
3381
- && (!expectedBatch.projectIntentDigest
3382
- || expectedBatch.projectIntentDigest !== currentBatch.projectIntentDigest)) {
3383
- throw operationReviewStale('确认后完整项目 Intent 声明发生变化。');
3384
- }
3385
- const reviewedGroups = expectedBatch.groups.map((group) => ({
3386
- groupId: group.groupId,
3387
- sourceId: group.sourceId,
3388
- platform: group.platform,
3389
- scope: group.scope,
3390
- requestedPluginIds: group.requestedPluginIds,
3391
- executable: group.executable,
3392
- planFingerprint: group.preview.operationPlan
3393
- ? intent.action === 'cleanup'
3394
- ? group.preview.operationPlan.planFingerprint
3395
- : createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
3396
- : undefined,
3397
- }));
3398
- const currentGroups = currentBatch.groups.map((group) => ({
3399
- groupId: group.groupId,
3400
- sourceId: group.sourceId,
3401
- platform: group.platform,
3402
- scope: group.scope,
3403
- requestedPluginIds: group.requestedPluginIds,
3404
- executable: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
3405
- ?.executable === false ? false : group.executable,
3406
- planFingerprint: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
3407
- ?.executable === false
3408
- ? undefined
3409
- : group.preview.operationPlan
3410
- ? intent.action === 'cleanup'
3411
- ? group.preview.operationPlan.planFingerprint
3412
- : createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
3413
- : undefined,
3414
- }));
3415
- if (expectedBatch.catalog.configDigest !== currentBatch.catalog.configDigest
3416
- || JSON.stringify(expectedBatch.requestedPluginIds)
3417
- !== JSON.stringify(currentBatch.requestedPluginIds)
3418
- || JSON.stringify(reviewedGroups) !== JSON.stringify(currentGroups)) {
3419
- throw operationReviewStale('确认后批量目标或写入计划发生变化。');
3420
- }
3421
- await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3422
- const groupResults = [];
3423
- const completedTargetKeys = new Set();
3424
- const expectedIntentDigests = new Map();
3425
- for (const group of expectedBatch.groups) {
3426
- for (const intentDigest of group.preview.operationPlan?.evidence.intentDigests ?? []) {
3427
- expectedIntentDigests.set(intentDigest.scope, intentDigest.digest);
3428
- }
3429
- }
3430
- let executedGroupCount = 0;
3431
- const appendStoppedGroups = (startIndex, staleMessage) => {
3432
- for (const [offset, stopped] of expectedBatch.groups.slice(startIndex).entries()) {
3433
- groupResults.push({
3434
- groupId: stopped.groupId,
3435
- sourceId: stopped.sourceId,
3436
- platform: stopped.platform,
3437
- scope: stopped.scope,
3438
- requestedPluginIds: stopped.requestedPluginIds,
3439
- result: !stopped.executable ? stopped.preview : failedWriteCommandResult(intent.action, stopped.requestedPluginIds, { cwd: context.cwd, platform: stopped.platform, scope: stopped.scope }, offset === 0
3440
- ? new TuiOperationBlockedError('operationReviewStale', staleMessage)
3441
- : new TuiOperationBlockedError('batchNotStarted', '前一个批量组的写入计划已变化,因此这个组没有开始。')),
3442
- });
3443
- }
3444
- };
3445
- for (const [groupIndex, reviewed] of expectedBatch.groups.entries()) {
3446
- const current = currentBatch.groups.find((group) => group.groupId === reviewed.groupId);
3447
- if (!reviewed.executable) {
3448
- groupResults.push({
3449
- groupId: reviewed.groupId,
3450
- sourceId: reviewed.sourceId,
3451
- platform: reviewed.platform,
3452
- scope: reviewed.scope,
3453
- requestedPluginIds: reviewed.requestedPluginIds,
3454
- result: reviewed.preview,
3455
- });
3456
- continue;
3457
- }
3458
- const groupInventory = groupResults.length === 0
3459
- ? operationInventory
3460
- : createOperationInventoryReader();
3461
- let executionPreview = current.preview;
3462
- if (completedTargetKeys.size > 0
3463
- || (intent.action === 'cleanup' && executedGroupCount > 0)) {
3464
- executionPreview = await runBatchWriteGroup({
3465
- intent,
3466
- group: current,
3467
- cwd: context.cwd,
3468
- runtime: {
3469
- ...context,
3470
- awHomeMutationToken,
3471
- inventoryCatalogSourceReferences: groupInventory,
3472
- ...(intent.action === 'cleanup' ? {} : {
3473
- expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
3474
- }),
3475
- previewOperationPlan: true,
3476
- ...(codexObservationSession ? { codexObservationSession } : {}),
3477
- },
3478
- });
3479
- const cleanupIntentDigestChanged = intent.action === 'cleanup'
3480
- && executionPreview.operationPlan?.evidence.intentDigests.find((entry) => entry.scope === current.scope)?.digest !== expectedIntentDigests.get(current.scope);
3481
- if (cleanupIntentDigestChanged
3482
- || !reviewed.preview.operationPlan || !executionPreview.operationPlan
3483
- || createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
3484
- appendStoppedGroups(groupIndex, '前序批量结果改变了这个组的实际写入计划。');
3485
- break;
3486
- }
3487
- }
3488
- const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
3489
- const result = await runBatchWriteGroup({
3490
- intent,
3491
- group: current,
3492
- cwd: context.cwd,
3493
- runtime: {
3494
- ...context,
3495
- awHomeMutationToken,
3496
- preApplyConfirmed: true,
3497
- managedCommandProducerCache,
3498
- inventoryCatalogSourceReferences: groupInventory,
3499
- ...(intent.action === 'cleanup' ? {} : {
3500
- expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
3501
- }),
3502
- ...(executionPreview.operationPlan ? {
3503
- expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint,
3504
- } : {}),
3505
- ...(requiresManagedCodex ? {
3506
- managedCodexAuthorizer: async () => true,
3507
- } : {}),
3508
- ...(codexObservationSession ? { codexObservationSession } : {}),
3509
- },
3510
- });
3511
- if (writeReviewIsStale(result)) {
3512
- if (executedGroupCount === 0)
3513
- throwIfWriteReviewStale(result);
3514
- appendStoppedGroups(groupIndex, '这个组的实际写入计划发生变化。');
3515
- break;
3516
- }
3517
- if (intent.action === 'cleanup') {
3518
- for (const change of result.operationPlan?.intentChanges ?? []) {
3519
- expectedIntentDigests.set(change.scope, change.afterDigest);
3520
- }
3521
- }
3522
- executedGroupCount += 1;
3523
- for (const target of result.targetResults) {
3524
- if (target.state === 'completed') {
3525
- completedTargetKeys.add([
3526
- target.target.pluginId,
3527
- target.target.platform,
3528
- target.target.scope,
3529
- ].join('\0'));
3530
- }
3531
- }
3532
- groupResults.push({
3533
- groupId: reviewed.groupId,
3534
- sourceId: reviewed.sourceId,
3535
- platform: reviewed.platform,
3536
- scope: reviewed.scope,
3537
- requestedPluginIds: reviewed.requestedPluginIds,
3538
- result,
3539
- });
3540
- }
3541
- const merged = mergeWriteCommandResults(groupResults.map((group) => group.result));
3542
- return {
3543
- ...merged,
3544
- requestedPluginIds: expectedBatch.requestedPluginIds,
3545
- groupResults,
3546
- };
3547
- }
3548
- if (intent.kind === 'pluginWriteTask') {
3549
- const expectedTask = expectedEvidence;
3550
- const currentTask = currentEvidence;
3551
- if (!operationEvidenceMatches(expectedTask, currentTask)) {
3552
- throw operationReviewStale('确认后任务目标、根因、版本或写入计划发生变化。');
3553
- }
3554
- await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3555
- const operationContexts = taskCatalogOperationsByEvidence.get(currentEvidence)
3556
- ?? new Map();
3557
- const sourceResults = [];
3558
- const completedTargetKeys = new Set();
3559
- const preApplyExecutionCache = new Map();
3560
- const commandTargets = (source) => source.requestedTargets.map((target) => {
3561
- const direct = source.causes.filter((cause) => pluginWriteTaskTargetKey(cause.repairTarget) === pluginWriteTaskTargetKey(target));
3562
- return {
3563
- pluginId: target.pluginId,
3564
- platform: target.platform,
3565
- scope: target.scope,
3566
- method: direct.some((cause) => cause.method === 'reinstall')
3567
- ? 'reinstall' : 'sync',
3568
- };
3569
- });
3570
- const repairTargets = (source) => [...new Map(source.causes.map((cause) => [
3571
- pluginWriteTaskTargetKey(cause.repairTarget),
3572
- { ...cause.repairTarget, method: cause.method },
3573
- ])).values()];
3574
- const stoppedResult = (source, error) => mergeWriteCommandResults(source.requestedTargets.map((target) => failedWriteCommandResult('sync', [target.pluginId], {
3575
- cwd: context.cwd,
3576
- platform: target.platform,
3577
- scope: target.scope,
3578
- }, error)));
3579
- const appendStoppedSources = (startIndex, message) => {
3580
- for (const [offset, source] of expectedTask.taskPlan.sources.slice(startIndex).entries()) {
3581
- sourceResults.push({
3582
- sourceId: source.sourceId,
3583
- sourceIdentity: source.sourceIdentity,
3584
- requestedTargets: source.requestedTargets,
3585
- result: stoppedResult(source, offset === 0
3586
- ? new TuiOperationBlockedError('operationReviewStale', message)
3587
- : new TuiOperationBlockedError('taskNotStarted', '前一个来源计划已变化,因此这个来源没有开始。')),
3588
- });
3589
- }
3590
- };
3591
- for (const [sourceIndex, reviewed] of expectedTask.taskPlan.sources.entries()) {
3592
- const current = currentTask.taskPlan.sources.find((source) => source.sourceId === reviewed.sourceId);
3593
- const catalogOperation = operationContexts.get(reviewed.sourceId);
3594
- if (!current || !catalogOperation || current.sourceIdentity !== reviewed.sourceIdentity) {
3595
- if (sourceResults.length === 0) {
3596
- throw operationReviewStale('确认后任务来源身份发生变化。');
3597
- }
3598
- appendStoppedSources(sourceIndex, '确认后任务来源身份发生变化。');
3599
- break;
3600
- }
3601
- const sourceInventory = sourceResults.length === 0
3602
- ? operationInventory : createOperationInventoryReader();
3603
- let executionPreview = current.preview;
3604
- if (completedTargetKeys.size > 0) {
3605
- executionPreview = await taskWriteCommand(commandTargets(current), {
3606
- cwd: context.cwd,
3607
- repairTargets: repairTargets(current),
3608
- ...(reviewedScriptTimeoutMs(intent) === undefined
3609
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3610
- }, {
3611
- ...context,
3612
- awHomeMutationToken,
3613
- catalogOperationContext: catalogOperation,
3614
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
3615
- expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
3616
- inventoryCatalogSourceReferences: sourceInventory,
3617
- previewOperationPlan: true,
3618
- preApplyExecutionCache,
3619
- ...(codexObservationSession ? { codexObservationSession } : {}),
3620
- });
3621
- if (!reviewed.preview.operationPlan || !executionPreview.operationPlan
3622
- || createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
3623
- appendStoppedSources(sourceIndex, '前序结果改变了这个来源的实际写入计划。');
3624
- break;
3625
- }
3626
- }
3627
- const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
3628
- const result = await taskWriteCommand(commandTargets(current), {
3629
- cwd: context.cwd,
3630
- repairTargets: repairTargets(current),
3631
- ...(reviewedScriptTimeoutMs(intent) === undefined
3632
- ? {} : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3633
- }, {
3634
- ...context,
3635
- awHomeMutationToken,
3636
- catalogOperationContext: catalogOperation,
3637
- expectedGenerationSetDigest: catalogOperation.generationSetDigest,
3638
- expectedCatalogConfigDigest: expectedTask.catalog.configDigest,
3639
- inventoryCatalogSourceReferences: sourceInventory,
3640
- preApplyConfirmed: true,
3641
- preApplyExecutionCache,
3642
- managedCommandProducerCache,
3643
- ...(executionPreview.operationPlan
3644
- ? { expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint } : {}),
3645
- ...(requiresManagedCodex ? { managedCodexAuthorizer: async () => true } : {}),
3646
- ...(codexObservationSession ? { codexObservationSession } : {}),
3647
- });
3648
- if (writeReviewIsStale(result)) {
3649
- if (sourceResults.length === 0)
3650
- throwIfWriteReviewStale(result);
3651
- appendStoppedSources(sourceIndex, '这个来源的实际写入计划发生变化。');
3652
- break;
3653
- }
3654
- for (const target of result.targetResults) {
3655
- if (target.state === 'completed') {
3656
- completedTargetKeys.add([
3657
- target.target.pluginId,
3658
- target.target.platform,
3659
- target.target.scope,
3660
- ].join('\0'));
3661
- }
3662
- }
3663
- sourceResults.push({
3664
- sourceId: reviewed.sourceId,
3665
- sourceIdentity: reviewed.sourceIdentity,
3666
- requestedTargets: reviewed.requestedTargets,
3667
- result,
3668
- });
3669
- }
3670
- const merged = mergeWriteCommandResults(sourceResults.map((source) => source.result));
3671
- return {
3672
- ...merged,
3673
- taskPlan: expectedTask.taskPlan,
3674
- sourceResults,
3675
- };
3676
- }
3677
- if (intent.kind !== 'pluginWrite'
3678
- && !operationEvidenceMatches(expectedEvidence, currentEvidence)) {
2524
+ const currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true);
2525
+ if (!operationEvidenceMatches(expectedEvidence, currentEvidence)) {
3679
2526
  throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
3680
2527
  }
3681
2528
  const catalogOperation = catalogOperationByEvidence.get(currentEvidence);
@@ -3684,76 +2531,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3684
2531
  throw operationReviewStale('确认后 Active Catalog generation 发生变化。');
3685
2532
  }
3686
2533
  await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
3687
- if (intent.kind === 'pluginWrite') {
3688
- const options = {
3689
- cwd: context.cwd,
3690
- platform: intent.platform,
3691
- scope: intent.scope,
3692
- ...(reviewedScriptTimeoutMs(intent) === undefined
3693
- ? {}
3694
- : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
3695
- ...(intent.action === 'install' && intent.dependencyScopeOverrides
3696
- ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
3697
- : {}),
3698
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3699
- ? { allowManagedCodexInstall: true }
3700
- : {}),
3701
- ...(intent.action === 'cleanup' ? { force: intent.force === true } : {}),
3702
- };
3703
- const command = {
3704
- install: installWriteCommand,
3705
- refreshAndSync: syncWriteCommand,
3706
- reinstall: reinstallWriteCommand,
3707
- uninstall: uninstallWriteCommand,
3708
- cleanup: cleanupWriteCommand,
3709
- sync: syncWriteCommand,
3710
- }[intent.action];
3711
- if (intent.action === 'cleanup') {
3712
- if (!expectedEvidence.operationPlan || !currentEvidence.operationPlan
3713
- || expectedEvidence.operationPlan.planFingerprint
3714
- !== currentEvidence.operationPlan.planFingerprint) {
3715
- throw operationReviewStale('确认后清理计划发生变化。');
3716
- }
3717
- const result = await command([intent.pluginId], options, {
3718
- ...context,
3719
- ...(codexObservationSession ? { codexObservationSession } : {}),
3720
- awHomeMutationToken,
3721
- preApplyConfirmed: true,
3722
- inventoryCatalogSourceReferences: operationInventory,
3723
- expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint,
3724
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3725
- ? { managedCodexAuthorizer: async () => true }
3726
- : {}),
3727
- });
3728
- throwIfWriteReviewStale(result);
3729
- return result;
3730
- }
3731
- const currentSource = currentEvidence.source;
3732
- if (!expectedEvidence.source || !currentSource) {
3733
- throw new TuiOperationBlockedError('operationReviewStale', '确认信息缺少来源身份。');
3734
- }
3735
- const result = await command([intent.pluginId], options, {
3736
- ...context,
3737
- ...(codexObservationSession ? { codexObservationSession } : {}),
3738
- awHomeMutationToken,
3739
- preApplyConfirmed: true,
3740
- inventoryCatalogSourceReferences: operationInventory,
3741
- ...(expectedEvidence.operationPlan
3742
- ? { expectedPlanFingerprint: expectedEvidence.operationPlan.planFingerprint }
3743
- : {}),
3744
- expectedCatalogConfigDigest: expectedEvidence.catalog.configDigest,
3745
- ...(catalogOperation ? {
3746
- catalogOperationContext: catalogOperation,
3747
- expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
3748
- } : {}),
3749
- ...(expectedEvidence.codexManager?.state === 'requires-install'
3750
- ? { managedCodexAuthorizer: async () => true }
3751
- : {}),
3752
- reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual),
3753
- });
3754
- throwIfWriteReviewStale(result);
3755
- return result;
3756
- }
3757
2534
  if (intent.kind === 'configSaveBatch') {
3758
2535
  assertConfigSaveBatchIntent(intent);
3759
2536
  const reviewedBatch = expectedEvidence.configBatch;
@@ -3767,7 +2544,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3767
2544
  const packageInfo = await readCliPackageInfo();
3768
2545
  for (const group of intent.groups) {
3769
2546
  const groupIntent = configSaveBatchGroupIntent(intent, group);
3770
- const reviewedGroup = reviewedBatch.groups.find((item) => item.scope === group.scope);
2547
+ const reviewedGroup = reviewedBatch.groups.find((item) => item.platform === group.platform);
3771
2548
  if (!reviewedGroup) {
3772
2549
  throw operationReviewStale('确认信息缺少设置层基线。');
3773
2550
  }
@@ -3784,7 +2561,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3784
2561
  const freshLayerStoreDigest = await configLayerStoreDigest({
3785
2562
  source: freshSource,
3786
2563
  pluginName: parsePluginId(intent.pluginId).pluginName,
3787
- scope: group.scope,
2564
+ scope: 'user', platform: group.platform, profileOnly: true,
3788
2565
  cwd: context.cwd,
3789
2566
  env: context.env,
3790
2567
  });
@@ -3816,7 +2593,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3816
2593
  operationContext: groupCatalogOperation,
3817
2594
  }) : undefined;
3818
2595
  const applyTargets = intent.apply
3819
- ? freshConfig.affectedTargets.map((target) => ({
2596
+ ? freshConfig.affectedTargets.filter((target) => target.platform === 'codex' || target.platform === 'claude').map((target) => ({
3820
2597
  pluginName: target.pluginName ?? plugin.manifest.name,
3821
2598
  platform: target.platform,
3822
2599
  scope: target.scope,
@@ -3825,7 +2602,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3825
2602
  return applyPluginConfigWrite({
3826
2603
  plugin,
3827
2604
  pluginName: plugin.manifest.name,
3828
- scope: group.scope,
2605
+ scope: 'user', platform: group.platform, context: intent.context ?? 'user',
2606
+ apply: intent.apply,
3829
2607
  cwd: context.cwd,
3830
2608
  env: context.env,
3831
2609
  nativeCliRunner: context.nativeCliRunner,
@@ -3845,7 +2623,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3845
2623
  } : {}),
3846
2624
  },
3847
2625
  awHomeMutationToken,
3848
- applyTargets,
2626
+ ...(applyTargets.length ? { applyTargets } : {}),
3849
2627
  request: { updates: group.updates, removeKeys: group.removeKeys },
3850
2628
  sharedNativeApplyConfirmed: true,
3851
2629
  preApplyConfirmed: true,
@@ -3862,7 +2640,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3862
2640
  ? 'notNeeded'
3863
2641
  : 'failed';
3864
2642
  results.push({
3865
- scope: group.scope,
2643
+ scope: 'user', platform: group.platform,
3866
2644
  status: applyStatus === 'failed' ? 'failed' : 'succeeded',
3867
2645
  configSaved,
3868
2646
  applyRequested: intent.apply,
@@ -3883,7 +2661,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3883
2661
  ? error.code
3884
2662
  : configSaved ? 'runtimeApplyFailed' : 'configWriteFailed';
3885
2663
  results.push({
3886
- scope: group.scope,
2664
+ scope: 'user', platform: group.platform,
3887
2665
  status: 'failed',
3888
2666
  configSaved,
3889
2667
  applyRequested: intent.apply,
@@ -3919,7 +2697,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3919
2697
  return resetPluginConfigWithHelper({
3920
2698
  sourceId: parsed.sourceId,
3921
2699
  pluginName: parsed.pluginName,
3922
- scope: intent.scope,
2700
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
3923
2701
  cwd: context.cwd,
3924
2702
  env: context.env,
3925
2703
  nativeCliRunner: context.nativeCliRunner,
@@ -3948,16 +2726,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3948
2726
  operationContext: catalogOperation,
3949
2727
  }) : undefined;
3950
2728
  const applyTargets = intent.retryApply || intent.apply
3951
- ? currentEvidence.config?.affectedTargets.map((target) => ({
2729
+ ? currentEvidence.config?.affectedTargets.filter((target) => target.platform === 'codex' || target.platform === 'claude').map((target) => ({
3952
2730
  pluginName: target.pluginName ?? plugin.manifest.name,
3953
2731
  platform: target.platform,
3954
2732
  scope: target.scope,
3955
2733
  })) ?? []
3956
2734
  : [];
3957
2735
  const result = intent.retryApply
3958
- ? await replayConfigRuntimeWrites({
2736
+ ? await applyPluginConfig({
3959
2737
  plugin,
3960
- scope: intent.scope,
2738
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
2739
+ ...(intent.targets ? { targets: intent.targets } : {}),
2740
+ cliVersion: packageInfo.version,
3961
2741
  cwd: context.cwd,
3962
2742
  env: context.env,
3963
2743
  nativeCliRunner: context.nativeCliRunner,
@@ -3975,7 +2755,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3975
2755
  } : {}),
3976
2756
  },
3977
2757
  awHomeMutationToken,
3978
- applyTargets,
2758
+ expectedEvidence: expectedEvidence.config?.applyEvidence,
3979
2759
  sharedNativeApplyConfirmed: true,
3980
2760
  preApplyConfirmed: true,
3981
2761
  preApplyAuthorizedTargets: expectedEvidence.config?.preApplyAuthorization ?? [],
@@ -3983,7 +2763,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
3983
2763
  : await applyPluginConfigWrite({
3984
2764
  plugin,
3985
2765
  pluginName: plugin.manifest.name,
3986
- scope: intent.scope,
2766
+ scope: 'user', platform: intent.platform, context: intent.context ?? 'user',
2767
+ apply: intent.apply,
3987
2768
  cwd: context.cwd,
3988
2769
  env: context.env,
3989
2770
  nativeCliRunner: context.nativeCliRunner,
@@ -4001,7 +2782,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
4001
2782
  } : {}),
4002
2783
  },
4003
2784
  awHomeMutationToken,
4004
- applyTargets,
2785
+ ...(applyTargets.length ? { applyTargets } : {}),
4005
2786
  request: { updates: intent.updates, removeKeys: intent.removeKeys },
4006
2787
  sharedNativeApplyConfirmed: true,
4007
2788
  preApplyConfirmed: true,
@@ -4013,8 +2794,6 @@ export function createTuiControllerDependencies(context, progressFeed, options =
4013
2794
  }
4014
2795
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
4015
2796
  },
4016
- }).finally(async () => {
4017
- await codexObservationSession?.dispose();
4018
2797
  });
4019
2798
  },
4020
2799
  };