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

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 (218) hide show
  1. package/dist/authoring/assets/aw-config.d.mts +1 -50
  2. package/dist/authoring/assets/aw-config.mjs +58 -928
  3. package/dist/authoring/assets/aw-config.mjs.map +1 -1
  4. package/dist/authoring/scripts/init-plugin.mjs +28 -33
  5. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  6. package/dist/authoring/scripts/lib/plugin-version-check.d.mts +1 -0
  7. package/dist/authoring/scripts/lib/plugin-version-check.mjs +229 -8
  8. package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
  9. package/dist/authoring/scripts/validate-plugin.mjs +165 -45
  10. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  11. package/dist/scripts/bundle-plugins.js +18 -4
  12. package/dist/scripts/bundle-plugins.js.map +1 -1
  13. package/dist/scripts/test-plugin-scripts.d.ts +2 -0
  14. package/dist/scripts/test-plugin-scripts.js +21 -5
  15. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  16. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  17. package/dist/src/commands/aw-capabilities.js +1 -1
  18. package/dist/src/commands/aw-capabilities.js.map +1 -1
  19. package/dist/src/commands/catalog-dev-source.d.ts +6 -3
  20. package/dist/src/commands/catalog-dev-source.js +96 -35
  21. package/dist/src/commands/catalog-dev-source.js.map +1 -1
  22. package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
  23. package/dist/src/commands/catalog-publication-inspection.js +54 -8
  24. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  25. package/dist/src/commands/catalog-publication-journal.js +1 -1
  26. package/dist/src/commands/catalog-publication-journal.js.map +1 -1
  27. package/dist/src/commands/catalog-source.d.ts +19 -4
  28. package/dist/src/commands/catalog-source.js +121 -12
  29. package/dist/src/commands/catalog-source.js.map +1 -1
  30. package/dist/src/commands/catalog-store.d.ts +26 -7
  31. package/dist/src/commands/catalog-store.js +361 -42
  32. package/dist/src/commands/catalog-store.js.map +1 -1
  33. package/dist/src/commands/console.js +28 -17
  34. package/dist/src/commands/console.js.map +1 -1
  35. package/dist/src/commands/operation-plan.d.ts +2 -1
  36. package/dist/src/commands/operation-plan.js +24 -0
  37. package/dist/src/commands/operation-plan.js.map +1 -1
  38. package/dist/src/commands/operation-planner.d.ts +2 -2
  39. package/dist/src/commands/operation-planner.js +11 -19
  40. package/dist/src/commands/operation-planner.js.map +1 -1
  41. package/dist/src/commands/options.d.ts +2 -2
  42. package/dist/src/commands/options.js +18 -4
  43. package/dist/src/commands/options.js.map +1 -1
  44. package/dist/src/commands/plugin-capabilities.js +2 -2
  45. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  46. package/dist/src/commands/rebuild-command.d.ts +7 -0
  47. package/dist/src/commands/rebuild-command.js +769 -279
  48. package/dist/src/commands/rebuild-command.js.map +1 -1
  49. package/dist/src/commands/rebuild-integration.d.ts +24 -47
  50. package/dist/src/commands/rebuild-integration.js +1074 -1011
  51. package/dist/src/commands/rebuild-integration.js.map +1 -1
  52. package/dist/src/commands/rebuild-operations.d.ts +50 -12
  53. package/dist/src/commands/rebuild-operations.js +207 -101
  54. package/dist/src/commands/rebuild-operations.js.map +1 -1
  55. package/dist/src/commands/status-v6.d.ts +21 -2
  56. package/dist/src/commands/status-v6.js +163 -31
  57. package/dist/src/commands/status-v6.js.map +1 -1
  58. package/dist/src/commands/sync-command.d.ts +17 -0
  59. package/dist/src/commands/sync-command.js +45 -37
  60. package/dist/src/commands/sync-command.js.map +1 -1
  61. package/dist/src/commands/write-commands.d.ts +5 -0
  62. package/dist/src/commands/write-commands.js +50 -3
  63. package/dist/src/commands/write-commands.js.map +1 -1
  64. package/dist/src/generators/local-plugin.js +14 -4
  65. package/dist/src/generators/local-plugin.js.map +1 -1
  66. package/dist/src/index.js +216 -85
  67. package/dist/src/index.js.map +1 -1
  68. package/dist/src/output/command-receipt.js +1 -3
  69. package/dist/src/output/command-receipt.js.map +1 -1
  70. package/dist/src/output/rebuild-progress.js +1 -3
  71. package/dist/src/output/rebuild-progress.js.map +1 -1
  72. package/dist/src/portable/materializer.js +6 -6
  73. package/dist/src/portable/materializer.js.map +1 -1
  74. package/dist/src/tui/catalog-generation.js +41 -1
  75. package/dist/src/tui/catalog-generation.js.map +1 -1
  76. package/dist/src/tui/catalog-sources/session.d.ts +1 -0
  77. package/dist/src/tui/catalog-sources/session.js +16 -3
  78. package/dist/src/tui/catalog-sources/session.js.map +1 -1
  79. package/dist/src/tui/catalog-sources/types.d.ts +3 -2
  80. package/dist/src/tui/components.d.ts +4 -0
  81. package/dist/src/tui/components.js +36 -24
  82. package/dist/src/tui/components.js.map +1 -1
  83. package/dist/src/tui/console-app-v4.d.ts +13 -4
  84. package/dist/src/tui/console-app-v4.js +2067 -839
  85. package/dist/src/tui/console-app-v4.js.map +1 -1
  86. package/dist/src/tui/controller-runtime.js +585 -33
  87. package/dist/src/tui/controller-runtime.js.map +1 -1
  88. package/dist/src/tui/controller.d.ts +152 -6
  89. package/dist/src/tui/controller.js +1168 -156
  90. package/dist/src/tui/controller.js.map +1 -1
  91. package/dist/src/tui/diagnostic-projection.d.ts +22 -0
  92. package/dist/src/tui/diagnostic-projection.js +437 -0
  93. package/dist/src/tui/diagnostic-projection.js.map +1 -0
  94. package/dist/src/tui/diagnostics.d.ts +1 -1
  95. package/dist/src/tui/diagnostics.js +6 -1
  96. package/dist/src/tui/diagnostics.js.map +1 -1
  97. package/dist/src/tui/glossary.d.ts +23 -0
  98. package/dist/src/tui/glossary.js +42 -0
  99. package/dist/src/tui/glossary.js.map +1 -0
  100. package/dist/src/tui/index.d.ts +4 -1
  101. package/dist/src/tui/index.js +4 -1
  102. package/dist/src/tui/index.js.map +1 -1
  103. package/dist/src/tui/keys.d.ts +44 -0
  104. package/dist/src/tui/keys.js +43 -15
  105. package/dist/src/tui/keys.js.map +1 -1
  106. package/dist/src/tui/lifecycle-action-view.d.ts +6 -0
  107. package/dist/src/tui/lifecycle-action-view.js +34 -0
  108. package/dist/src/tui/lifecycle-action-view.js.map +1 -0
  109. package/dist/src/tui/model.d.ts +12 -2
  110. package/dist/src/tui/model.js +128 -52
  111. package/dist/src/tui/model.js.map +1 -1
  112. package/dist/src/tui/operation-diagnostic-screens.d.ts +21 -0
  113. package/dist/src/tui/operation-diagnostic-screens.js +101 -0
  114. package/dist/src/tui/operation-diagnostic-screens.js.map +1 -0
  115. package/dist/src/tui/operation-history.d.ts +80 -0
  116. package/dist/src/tui/operation-history.js +130 -0
  117. package/dist/src/tui/operation-history.js.map +1 -0
  118. package/dist/src/tui/operation-review-planner.js +306 -86
  119. package/dist/src/tui/operation-review-planner.js.map +1 -1
  120. package/dist/src/tui/router.d.ts +82 -17
  121. package/dist/src/tui/router.js +186 -38
  122. package/dist/src/tui/router.js.map +1 -1
  123. package/dist/src/tui/screen-registry.d.ts +546 -0
  124. package/dist/src/tui/screen-registry.js +179 -0
  125. package/dist/src/tui/screen-registry.js.map +1 -0
  126. package/dist/src/tui/session-console-app.d.ts +6 -0
  127. package/dist/src/tui/session-console-app.js +13 -0
  128. package/dist/src/tui/session-console-app.js.map +1 -0
  129. package/dist/src/tui/session-host.d.ts +44 -0
  130. package/dist/src/tui/session-host.js +197 -0
  131. package/dist/src/tui/session-host.js.map +1 -0
  132. package/dist/src/types.d.ts +64 -3
  133. package/dist/src/types.js +1 -1
  134. package/dist/src/types.js.map +1 -1
  135. package/dist/src/utils/agent-version.js +1 -1
  136. package/dist/src/utils/agent-version.js.map +1 -1
  137. package/dist/src/utils/artifact-renderer.d.ts +2 -1
  138. package/dist/src/utils/artifact-renderer.js +52 -52
  139. package/dist/src/utils/artifact-renderer.js.map +1 -1
  140. package/dist/src/utils/bundle-layout.js +4 -4
  141. package/dist/src/utils/bundle-layout.js.map +1 -1
  142. package/dist/src/utils/catalog-capability-projection.d.ts +8 -0
  143. package/dist/src/utils/catalog-capability-projection.js +14 -0
  144. package/dist/src/utils/catalog-capability-projection.js.map +1 -0
  145. package/dist/src/utils/catalog-generation-resolver.d.ts +14 -2
  146. package/dist/src/utils/catalog-generation-resolver.js +49 -27
  147. package/dist/src/utils/catalog-generation-resolver.js.map +1 -1
  148. package/dist/src/utils/catalog-generation-service.d.ts +8 -1
  149. package/dist/src/utils/catalog-generation-service.js +81 -47
  150. package/dist/src/utils/catalog-generation-service.js.map +1 -1
  151. package/dist/src/utils/catalog-generation-store.d.ts +1 -0
  152. package/dist/src/utils/catalog-generation-store.js +60 -5
  153. package/dist/src/utils/catalog-generation-store.js.map +1 -1
  154. package/dist/src/utils/catalog-operation-context.js +1 -0
  155. package/dist/src/utils/catalog-operation-context.js.map +1 -1
  156. package/dist/src/utils/catalog-public-error.js +4 -0
  157. package/dist/src/utils/catalog-public-error.js.map +1 -1
  158. package/dist/src/utils/catalog-source-impact.d.ts +3 -2
  159. package/dist/src/utils/catalog-source-impact.js +142 -20
  160. package/dist/src/utils/catalog-source-impact.js.map +1 -1
  161. package/dist/src/utils/catalog-source-references.js +95 -9
  162. package/dist/src/utils/catalog-source-references.js.map +1 -1
  163. package/dist/src/utils/catalog-sources.d.ts +1 -1
  164. package/dist/src/utils/catalog-sources.js +19 -3
  165. package/dist/src/utils/catalog-sources.js.map +1 -1
  166. package/dist/src/utils/codex-manager.js +8 -0
  167. package/dist/src/utils/codex-manager.js.map +1 -1
  168. package/dist/src/utils/codex-native-observation.d.ts +34 -1
  169. package/dist/src/utils/codex-native-observation.js +132 -0
  170. package/dist/src/utils/codex-native-observation.js.map +1 -1
  171. package/dist/src/utils/codex-reconciler.d.ts +0 -2
  172. package/dist/src/utils/codex-reconciler.js.map +1 -1
  173. package/dist/src/utils/codex-shared-native.d.ts +2 -26
  174. package/dist/src/utils/codex-shared-native.js +8 -82
  175. package/dist/src/utils/codex-shared-native.js.map +1 -1
  176. package/dist/src/utils/git-catalog-provider.d.ts +3 -0
  177. package/dist/src/utils/git-catalog-provider.js +40 -1
  178. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  179. package/dist/src/utils/local-marketplace.d.ts +1 -0
  180. package/dist/src/utils/local-marketplace.js +69 -73
  181. package/dist/src/utils/local-marketplace.js.map +1 -1
  182. package/dist/src/utils/multi-source-catalog.js +1 -0
  183. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  184. package/dist/src/utils/native-cli.d.ts +9 -4
  185. package/dist/src/utils/native-cli.js +280 -34
  186. package/dist/src/utils/native-cli.js.map +1 -1
  187. package/dist/src/utils/path-catalog-provider.js +2 -0
  188. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  189. package/dist/src/utils/plugin-config-write.d.ts +15 -13
  190. package/dist/src/utils/plugin-config-write.js +418 -198
  191. package/dist/src/utils/plugin-config-write.js.map +1 -1
  192. package/dist/src/utils/plugin-config.d.ts +48 -4
  193. package/dist/src/utils/plugin-config.js +289 -38
  194. package/dist/src/utils/plugin-config.js.map +1 -1
  195. package/dist/src/utils/plugin-scripts.d.ts +20 -12
  196. package/dist/src/utils/plugin-scripts.js +376 -109
  197. package/dist/src/utils/plugin-scripts.js.map +1 -1
  198. package/dist/src/utils/process-tree.d.ts +2 -2
  199. package/dist/src/utils/process-tree.js +1 -1
  200. package/dist/src/utils/process-tree.js.map +1 -1
  201. package/dist/src/utils/project-boundary.d.ts +11 -1
  202. package/dist/src/utils/project-boundary.js +17 -8
  203. package/dist/src/utils/project-boundary.js.map +1 -1
  204. package/dist/src/utils/safe-operation-diagnostic.d.ts +15 -0
  205. package/dist/src/utils/safe-operation-diagnostic.js +33 -0
  206. package/dist/src/utils/safe-operation-diagnostic.js.map +1 -0
  207. package/dist/src/utils/sensitive-content.d.ts +1 -0
  208. package/dist/src/utils/sensitive-content.js +15 -0
  209. package/dist/src/utils/sensitive-content.js.map +1 -0
  210. package/dist/src/utils/windows-claude-shim.d.ts +9 -0
  211. package/dist/src/utils/windows-claude-shim.js +141 -0
  212. package/dist/src/utils/windows-claude-shim.js.map +1 -0
  213. package/dist/vitest.config.js +91 -10
  214. package/dist/vitest.config.js.map +1 -1
  215. package/package.json +9 -9
  216. package/dist/authoring/templates/aw-config-runtime.d.mts +0 -2
  217. package/dist/authoring/templates/aw-config-runtime.mjs +0 -407
  218. package/dist/authoring/templates/aw-config-runtime.mjs.map +0 -1
@@ -2,13 +2,19 @@ import crypto from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import { statusCommandV7, statusTargetIntent, } from '../commands/status-v6.js';
4
4
  import { addCatalogSourceCommand, listCatalogSources, preflightCatalogSourceMutation, removeCatalogSourceCommand, setPrimaryCatalogSourceCommand, updateCatalogSourceCommand, } from '../commands/catalog-source.js';
5
- import { cleanupWriteCommand, installWriteCommand, reinstallWriteCommand, syncWriteCommand, uninstallWriteCommand, } from '../commands/write-commands.js';
5
+ import { cleanupWriteCommand, failedWriteCommandResult, installWriteCommand, mergeWriteCommandResults, reinstallWriteCommand, syncWriteCommand, uninstallWriteCommand, } from '../commands/write-commands.js';
6
+ import { resolveSyncIntentSelection } from '../commands/sync-command.js';
7
+ import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, } from '../commands/options.js';
6
8
  import { readCliPackageInfo } from '../utils/local-marketplace.js';
9
+ import { createCodexNativeObservationSession, } from '../utils/codex-native-observation.js';
7
10
  import { readCatalogSourcesConfigSnapshot, recoverInvalidCatalogSourcesConfig, } from '../utils/catalog-sources.js';
8
11
  import { withResolvedCatalogPlugin, withResolvedMultiSourceCatalog, formatPluginId, parsePluginId, } from '../utils/multi-source-catalog.js';
9
- import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, readPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, } from '../utils/plugin-config.js';
12
+ import { coercePluginConfigUpdates, inspectPluginConfigResetTarget, listNamespacedPluginConfigStoreIdentities, readPluginConfigStore, resolveConfigRead, resolveEffectivePluginConfig, resolvePluginConfigStorePath, } from '../utils/plugin-config.js';
13
+ import { assertProjectSharedConfigPathVisible } from '../utils/project-boundary.js';
14
+ import { readPluginIntentState } from '../utils/plugin-intent-state.js';
10
15
  import { applyPluginConfigWrite, replayConfigRuntimeWrites, resetPluginConfigWithHelper, } from '../utils/plugin-config-write.js';
11
- import { offlineCleanupEvidenceFromInventory, } from '../commands/offline-cleanup-evidence.js';
16
+ import { createBatchIntentCompatiblePlanFingerprint } from '../commands/operation-plan.js';
17
+ import { offlineCleanupEvidenceFromInventory, offlineCleanupEvidenceMatches, } from '../commands/offline-cleanup-evidence.js';
12
18
  import { inventoryCatalogSourceReferences } from '../utils/catalog-source-references.js';
13
19
  import { operationEvidenceMatches, operationEvidenceFingerprint, TuiConfigInvalidStoreError, TuiController, TuiOperationBlockedError, } from './controller.js';
14
20
  import { buildConfigGroups, buildConfigPage, projectPlatformPlacement, } from './model.js';
@@ -113,12 +119,14 @@ export function configAffectedStatusTargets(targets, pluginId, configScope) {
113
119
  .localeCompare(`${right.platform}\0${right.scope}`, 'en'));
114
120
  }
115
121
  function configSharedNativeRenderTargets(plugin, changedKeys) {
116
- const changed = new Set(changedKeys);
122
+ const changed = changedKeys ? new Set(changedKeys) : undefined;
117
123
  return (plugin.manifest.artifacts?.targets ?? []).flatMap((target) => target.platform === 'codex'
118
124
  && target.target === 'codex.skill'
119
125
  && target.delivery.mode === 'codex-native'
120
126
  && target.skillKey
121
- && target.refreshKeys.some((key) => changed.has(key))
127
+ && (changed
128
+ ? target.refreshKeys.some((key) => changed.has(key))
129
+ : target.refreshKeys.length > 0)
122
130
  ? [{ id: target.id, skillKey: target.skillKey, refreshKeys: [...target.refreshKeys] }]
123
131
  : []);
124
132
  }
@@ -130,6 +138,7 @@ function sourceEvidenceForPlugin(source, pluginName) {
130
138
  return {
131
139
  sourceId: source.sourceId,
132
140
  sourceIdentity: source.sourceIdentity,
141
+ logicalIdentity: source.logicalIdentity,
133
142
  requestedRef: source.requestedRef,
134
143
  resolvedCommit: source.resolvedCommit,
135
144
  manifestDigest: source.manifestDigest,
@@ -140,9 +149,27 @@ function sourceEvidenceForPlugin(source, pluginName) {
140
149
  channel: source.manifest.channel,
141
150
  };
142
151
  }
152
+ async function inspectConfigShareability(options) {
153
+ if (options.scope !== 'project')
154
+ return undefined;
155
+ const targetPath = resolvePluginConfigStorePath({
156
+ sourceId: options.sourceId,
157
+ pluginName: options.pluginName,
158
+ scope: 'project',
159
+ cwd: options.cwd,
160
+ env: options.env,
161
+ });
162
+ const visibility = await assertProjectSharedConfigPathVisible({
163
+ cwd: options.cwd,
164
+ targetPath,
165
+ env: options.env,
166
+ });
167
+ return visibility.state === 'unverified' ? 'unverified' : 'verified';
168
+ }
143
169
  function assertSourceEvidenceMatches(expected, actual) {
144
170
  if (actual.sourceId !== expected.sourceId
145
171
  || actual.sourceIdentity !== expected.sourceIdentity
172
+ || actual.logicalIdentity !== expected.logicalIdentity
146
173
  || actual.resolvedCommit !== expected.resolvedCommit
147
174
  || actual.manifestDigest !== expected.manifestDigest
148
175
  || actual.snapshotDigest !== expected.snapshotDigest
@@ -150,10 +177,12 @@ function assertSourceEvidenceMatches(expected, actual) {
150
177
  throw operationReviewStale('确认后来源内容发生变化。');
151
178
  }
152
179
  }
153
- function throwIfWriteReviewStale(result) {
154
- const stale = result.targetResults.find((target) => target.errorCode === 'operationReviewStale'
180
+ function writeReviewIsStale(result) {
181
+ return result.targetResults.some((target) => target.errorCode === 'operationReviewStale'
155
182
  || target.errorCode === 'operationPlanStale');
156
- if (stale) {
183
+ }
184
+ function throwIfWriteReviewStale(result) {
185
+ if (writeReviewIsStale(result)) {
157
186
  throw operationReviewStale('确认后实际写入计划发生变化。');
158
187
  }
159
188
  }
@@ -163,6 +192,122 @@ const SOURCE_PROGRESS_PHASES = {
163
192
  function pluginIntent(intent) {
164
193
  return 'pluginId' in intent;
165
194
  }
195
+ function reviewedScriptTimeoutMs(intent) {
196
+ if ((intent.kind === 'pluginWrite' || intent.kind === 'pluginWriteBatch')
197
+ && (intent.action === 'install' || intent.action === 'refreshAndSync'
198
+ || intent.action === 'reinstall' || intent.action === 'sync')) {
199
+ return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
200
+ }
201
+ if (intent.kind === 'configSave' && intent.apply) {
202
+ return parseScriptTimeoutMs(intent.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS);
203
+ }
204
+ return undefined;
205
+ }
206
+ function selectedBatchGroups(intent) {
207
+ if (intent.selection.kind !== 'selected' && intent.selection.kind !== 'targets') {
208
+ throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部同步必须从项目 intent roots 解析。');
209
+ }
210
+ const rawTargets = intent.selection.kind === 'targets'
211
+ ? intent.selection.targets
212
+ : intent.selection.pluginIds.map((pluginId) => ({
213
+ pluginId, platform: intent.platform, scope: intent.scope,
214
+ }));
215
+ const normalizedTargets = rawTargets.map((target) => {
216
+ if ((target.platform !== 'codex' && target.platform !== 'claude' && target.platform !== 'both')
217
+ || target.scope !== intent.scope
218
+ || (intent.selection.kind === 'targets' && target.platform === 'both')) {
219
+ throw new TuiOperationBlockedError('batchTargetInvalid', '批量精确目标必须使用共同 scope 和单个平台。');
220
+ }
221
+ const pluginId = target.pluginId;
222
+ const parsed = parsePluginId(pluginId);
223
+ if (!parsed.sourceId) {
224
+ throw new TuiOperationBlockedError('invalidPluginId', '批量操作必须使用 canonical pluginId。');
225
+ }
226
+ const canonicalPluginId = formatPluginId({ sourceId: parsed.sourceId, pluginName: parsed.pluginName });
227
+ if (intent.selection.kind === 'targets' && canonicalPluginId !== pluginId) {
228
+ throw new TuiOperationBlockedError('invalidPluginId', '批量精确目标必须使用 canonical pluginId。');
229
+ }
230
+ return {
231
+ pluginId: canonicalPluginId,
232
+ platform: target.platform,
233
+ scope: target.scope,
234
+ };
235
+ }).sort((left, right) => [left.pluginId, left.platform, left.scope].join('\0')
236
+ .localeCompare([right.pluginId, right.platform, right.scope].join('\0'), 'en'));
237
+ const uniqueTargets = [...new Map(normalizedTargets.map((target) => [
238
+ [target.pluginId, target.platform, target.scope].join('\0'), target,
239
+ ])).values()];
240
+ const requestedPluginIds = [...new Set(uniqueTargets.map((target) => target.pluginId))];
241
+ if (requestedPluginIds.length === 0) {
242
+ throw new TuiOperationBlockedError('syncSelectionRequired', '批量操作至少需要一个插件。');
243
+ }
244
+ const groups = new Map();
245
+ for (const target of uniqueTargets) {
246
+ const pluginId = target.pluginId;
247
+ const sourceId = parsePluginId(pluginId).sourceId;
248
+ const groupKey = [sourceId, target.platform, target.scope].join('\0');
249
+ const group = groups.get(groupKey) ?? {
250
+ groupId: groupKey,
251
+ sourceId,
252
+ platform: target.platform,
253
+ scope: target.scope,
254
+ requestedPluginIds: [],
255
+ };
256
+ group.requestedPluginIds.push(pluginId);
257
+ groups.set(groupKey, group);
258
+ }
259
+ return { requestedPluginIds, groups: [...groups.values()] };
260
+ }
261
+ async function resolveBatchGroups(intent, env, cwd, frozenRequestedPluginIds) {
262
+ if (intent.selection.kind === 'selected' || intent.selection.kind === 'targets') {
263
+ return selectedBatchGroups(intent);
264
+ }
265
+ if (intent.action !== 'sync' || intent.scope !== 'project') {
266
+ throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目全部操作只支持 project scope sync。');
267
+ }
268
+ const state = await readPluginIntentState({ scope: 'project', cwd, env });
269
+ const selection = resolveSyncIntentSelection({
270
+ roots: state.roots,
271
+ selectors: frozenRequestedPluginIds ?? [],
272
+ all: frozenRequestedPluginIds === undefined,
273
+ platform: intent.platform,
274
+ });
275
+ if (selection.requestedPluginIds.length === 0) {
276
+ throw new TuiOperationBlockedError('syncSelectionRequired', '当前项目没有可同步的 intent roots。');
277
+ }
278
+ return {
279
+ requestedPluginIds: selection.requestedPluginIds,
280
+ groups: selection.groups.map((group) => ({
281
+ groupId: `${group.sourceId}\0${group.platform}\0${intent.scope}`,
282
+ sourceId: group.sourceId,
283
+ platform: group.platform,
284
+ scope: intent.scope,
285
+ requestedPluginIds: group.pluginIds,
286
+ })),
287
+ };
288
+ }
289
+ async function runBatchWriteGroup(options) {
290
+ const commandOptions = {
291
+ cwd: options.cwd,
292
+ platform: options.group.platform,
293
+ scope: options.group.scope,
294
+ ...(reviewedScriptTimeoutMs(options.intent) === undefined
295
+ ? {}
296
+ : { scriptTimeoutMs: reviewedScriptTimeoutMs(options.intent) }),
297
+ };
298
+ switch (options.intent.action) {
299
+ case 'install':
300
+ return installWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
301
+ case 'sync':
302
+ return syncWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
303
+ case 'reinstall':
304
+ return reinstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
305
+ case 'uninstall':
306
+ return uninstallWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
307
+ case 'cleanup':
308
+ return cleanupWriteCommand(options.group.requestedPluginIds, commandOptions, options.runtime);
309
+ }
310
+ }
166
311
  async function loadDependencyOverviews(options) {
167
312
  const graph = await withResolvedMultiSourceCatalog({
168
313
  cliVersion: options.cliVersion,
@@ -437,6 +582,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
437
582
  };
438
583
  };
439
584
  const catalogOperationByEvidence = new WeakMap();
585
+ const codexManagerByEvidence = new WeakMap();
440
586
  let sessionCatalogOperationContext;
441
587
  let sessionStatusResult;
442
588
  const sessionContextForPlugin = (pluginId) => {
@@ -450,6 +596,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
450
596
  const stores = await Promise.all(scopes.map((scope) => readPluginConfigStore({
451
597
  sourceId: config.sourceId,
452
598
  sourceIdentity: config.sourceIdentity,
599
+ logicalIdentity: config.logicalIdentity,
453
600
  pluginName: config.pluginName,
454
601
  scope,
455
602
  cwd: context.cwd,
@@ -541,14 +688,38 @@ export function createTuiControllerDependencies(context, progressFeed, options =
541
688
  signal.throwIfAborted();
542
689
  const catalogGenerations = await loadCatalogGenerations();
543
690
  signal.throwIfAborted();
691
+ const configStoreInventory = (await Promise.all(['user', 'project'].map(async (scope) => {
692
+ try {
693
+ return (await listNamespacedPluginConfigStoreIdentities({
694
+ cwd: context.cwd,
695
+ env: context.env,
696
+ scope,
697
+ })).map(({ pluginId, sourceId, pluginName }) => ({
698
+ pluginId, sourceId, pluginName, scope,
699
+ }));
700
+ }
701
+ catch {
702
+ await context.debugLogger.log({
703
+ type: 'tui:config-inventory-error',
704
+ level: 'debug',
705
+ target: 'debugLog',
706
+ message: 'TUI saved config inventory could not be read.',
707
+ data: { scope },
708
+ });
709
+ return [];
710
+ }
711
+ }))).flat();
712
+ signal.throwIfAborted();
544
713
  const statusById = new Map(result.payload.catalog.sources.map((source) => [source.id, source]));
545
714
  return {
546
715
  cliChannel: packageInfo.version.includes('-') ? 'beta' : 'latest',
716
+ configuredSources: structuredClone(configured.sources),
547
717
  payload: result.payload,
548
718
  pluginSummaries: result.pluginSummaries,
549
719
  targetRevisions: result.targetRevisions,
550
720
  dependencyOverviews,
551
721
  portablePluginIds,
722
+ configStoreInventory,
552
723
  catalogGenerations,
553
724
  sessionGenerationKeys: Object.fromEntries((catalogOperationContext?.generationSet.sources ?? [])
554
725
  .map((source) => [source.sourceId, source.storeKey])),
@@ -580,7 +751,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
580
751
  }
581
752
  throw new TuiOperationBlockedError('operationReviewStale', '状态读取期间来源配置持续变化。请重试。');
582
753
  };
583
- const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate) => {
754
+ const captureEvidence = async (intent, signal, preparation, operationInventory = createOperationInventoryReader(), freshCatalog = false, reusableCandidate, codexObservationSession, frozenBatchEvidence) => {
755
+ reviewedScriptTimeoutMs(intent);
584
756
  if (intent.kind === 'configReset') {
585
757
  const parsed = parsePluginId(intent.pluginId);
586
758
  if (!parsed.sourceId || !parsed.pluginName) {
@@ -684,12 +856,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
684
856
  && (preview.identityChanged || sourceFingerprintChanged);
685
857
  if (preview.identityChanged)
686
858
  sourceIdentityChangeImpact = {
687
- impactSchemaVersion: 1,
859
+ impactSchemaVersion: 3,
688
860
  sourceId: preview.sourceId,
861
+ primaryChange: preview.references.primaryChange,
689
862
  inventoryComplete: preview.references.inventoryComplete,
690
863
  references: preview.references.references,
691
864
  affectedReferences: preview.references.references,
692
- changes: { added: [], removed: [], changed: [], newlyIncompatible: [], dependencyChanged: [] },
865
+ changes: {
866
+ added: [], removed: [], changed: [], newlyIncompatible: [],
867
+ dependencyChanged: [], platformSupportChanged: [],
868
+ },
693
869
  errors: preview.references.errors,
694
870
  };
695
871
  if (!explicitSourceChange) {
@@ -828,7 +1004,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
828
1004
  if (intent.action !== 'replaceDamaged' && !explicitSourceChange && candidateRelation === 'rollbackDetected') {
829
1005
  throw new TuiOperationBlockedError('catalogRollbackDetected', '候选 Catalog 版本低于当前 Active。普通更新已阻止。');
830
1006
  }
831
- const impact = intent.action === 'repair' ? undefined
1007
+ const impact = exactRepair ? undefined
832
1008
  : sourceIdentityChangeImpact ?? await inspectCatalogSourceImpact({
833
1009
  sourceId: intent.sourceId,
834
1010
  cwd: context.cwd,
@@ -837,6 +1013,29 @@ export function createTuiControllerDependencies(context, progressFeed, options =
837
1013
  mode: 'generation',
838
1014
  candidateManifest: probe.manifest,
839
1015
  });
1016
+ const primaryPreview = intent.setPrimaryAfterActivation
1017
+ ? await preflightCatalogSourceMutation({
1018
+ request: { action: 'setPrimary', sourceId: intent.sourceId },
1019
+ cwd: context.cwd,
1020
+ env: context.env,
1021
+ })
1022
+ : undefined;
1023
+ if (primaryPreview && primaryPreview.configDigest !== config.digest) {
1024
+ throw operationReviewStale('准备主来源切换期间来源配置发生变化。');
1025
+ }
1026
+ const primaryImpact = primaryPreview ? {
1027
+ impactSchemaVersion: 3,
1028
+ sourceId: primaryPreview.sourceId,
1029
+ primaryChange: primaryPreview.references.primaryChange,
1030
+ inventoryComplete: primaryPreview.references.inventoryComplete,
1031
+ references: primaryPreview.references.references,
1032
+ affectedReferences: primaryPreview.references.references,
1033
+ changes: {
1034
+ added: [], removed: [], changed: [], newlyIncompatible: [],
1035
+ dependencyChanged: [], platformSupportChanged: [],
1036
+ },
1037
+ errors: primaryPreview.references.errors,
1038
+ } : undefined;
840
1039
  return {
841
1040
  catalog: {
842
1041
  configDigest: config.digest,
@@ -857,6 +1056,11 @@ export function createTuiControllerDependencies(context, progressFeed, options =
857
1056
  impactDigest: calculateCatalogSourceImpactDigest(impact),
858
1057
  impactMode: sourceIdentityChangeImpact ? 'identityChange' : 'generation',
859
1058
  } : {}),
1059
+ ...(primaryPreview && primaryImpact ? {
1060
+ primaryImpact,
1061
+ primaryImpactDigest: primaryPreview.impactDigest,
1062
+ primaryConfigDigest: primaryPreview.configDigest,
1063
+ } : {}),
860
1064
  configDigest: config.digest,
861
1065
  ...(proposedConfigDigest ? { proposedConfigDigest } : {}),
862
1066
  ...(proposedSource ? { proposedSource } : {}),
@@ -896,12 +1100,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
896
1100
  referenceCount: preview.references.references.length,
897
1101
  inventoryComplete: preview.references.inventoryComplete,
898
1102
  impact: {
899
- impactSchemaVersion: 1,
1103
+ impactSchemaVersion: 3,
900
1104
  sourceId: preview.sourceId,
1105
+ primaryChange: preview.references.primaryChange,
901
1106
  inventoryComplete: preview.references.inventoryComplete,
902
1107
  references: preview.references.references,
903
1108
  affectedReferences: preview.references.references,
904
- changes: { added: [], removed: [], changed: [], newlyIncompatible: [], dependencyChanged: [] },
1109
+ changes: {
1110
+ added: [], removed: [], changed: [], newlyIncompatible: [],
1111
+ dependencyChanged: [], platformSupportChanged: [],
1112
+ },
905
1113
  errors: preview.references.errors,
906
1114
  },
907
1115
  },
@@ -912,10 +1120,95 @@ export function createTuiControllerDependencies(context, progressFeed, options =
912
1120
  }
913
1121
  return evidence;
914
1122
  }
915
- if (!pluginIntent(intent)) {
1123
+ if (!pluginIntent(intent) && intent.kind !== 'pluginWriteBatch') {
916
1124
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
917
1125
  }
918
1126
  signal?.throwIfAborted();
1127
+ if (intent.kind === 'pluginWriteBatch') {
1128
+ await preparation?.reporter.emit('status');
1129
+ const selection = await resolveBatchGroups(intent, context.env, context.cwd, frozenBatchEvidence?.requestedPluginIds);
1130
+ signal?.throwIfAborted();
1131
+ const configSnapshot = await readCatalogSourcesConfigSnapshot(context.env);
1132
+ const groups = [];
1133
+ const ownsObservationSession = !codexObservationSession
1134
+ && selection.groups.some((group) => {
1135
+ const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1136
+ return reviewed?.executable !== false
1137
+ && (group.platform === 'codex' || group.platform === 'both');
1138
+ });
1139
+ const observationSession = codexObservationSession ?? (ownsObservationSession
1140
+ ? await createCodexNativeObservationSession({
1141
+ exactCwd: context.cwd,
1142
+ env: context.env,
1143
+ runner: context.nativeCliRunner,
1144
+ })
1145
+ : undefined);
1146
+ try {
1147
+ await preparation?.reporter.emit('catalog');
1148
+ for (const group of selection.groups) {
1149
+ signal?.throwIfAborted();
1150
+ const reviewed = frozenBatchEvidence?.groups.find((candidate) => candidate.groupId === group.groupId);
1151
+ if (reviewed?.executable === false) {
1152
+ groups.push({ ...group, preview: reviewed.preview, executable: false });
1153
+ continue;
1154
+ }
1155
+ let catalogOperation;
1156
+ try {
1157
+ catalogOperation = !freshCatalog && sessionCatalogOperationContext
1158
+ ? selectCatalogOperationContext(sessionCatalogOperationContext, [group.sourceId])
1159
+ : await resolveCatalogOperationContext({
1160
+ sourceIds: [group.sourceId],
1161
+ env: context.env,
1162
+ ...(!freshCatalog ? { validatedStoreEntries, validatingStoreEntries } : {}),
1163
+ ...(signal ? { signal } : {}),
1164
+ });
1165
+ }
1166
+ catch {
1167
+ catalogOperation = undefined;
1168
+ }
1169
+ const preview = await runBatchWriteGroup({
1170
+ intent,
1171
+ group,
1172
+ cwd: context.cwd,
1173
+ runtime: {
1174
+ ...context,
1175
+ inventoryCatalogSourceReferences: operationInventory,
1176
+ expectedCatalogConfigDigest: configSnapshot.digest,
1177
+ ...(catalogOperation ? {
1178
+ catalogOperationContext: catalogOperation,
1179
+ expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1180
+ } : {}),
1181
+ previewOperationPlan: true,
1182
+ ...(observationSession ? { codexObservationSession: observationSession } : {}),
1183
+ ...(preparation ? {
1184
+ onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1185
+ } : {}),
1186
+ },
1187
+ });
1188
+ groups.push({ ...group, preview, executable: preview.operationPlan !== undefined });
1189
+ }
1190
+ }
1191
+ finally {
1192
+ if (ownsObservationSession)
1193
+ await observationSession?.dispose();
1194
+ }
1195
+ await preparation?.reporter.emit('evidence');
1196
+ signal?.throwIfAborted();
1197
+ const merged = mergeWriteCommandResults(groups.map((group) => group.preview));
1198
+ const evidence = {
1199
+ catalog: {
1200
+ configDigest: configSnapshot.digest,
1201
+ compositeDigest: sessionStatusResult?.payload.catalog.compositeDigest
1202
+ ?? digest(groups.map((group) => [group.groupId, group.preview.operationPlan?.planFingerprint])),
1203
+ },
1204
+ requestedPluginIds: selection.requestedPluginIds,
1205
+ groups,
1206
+ ...(merged.dependencyPlan ? { dependencyPlan: merged.dependencyPlan } : {}),
1207
+ ...(merged.codexReconcilePlans ? { codexReconcilePlans: merged.codexReconcilePlans } : {}),
1208
+ ...(merged.operationPlan ? { operationPlan: merged.operationPlan } : {}),
1209
+ };
1210
+ return evidence;
1211
+ }
919
1212
  const packageInfo = await readCliPackageInfo();
920
1213
  const offlineCleanupReason = intent.kind === 'pluginWrite'
921
1214
  && intent.action === 'cleanup'
@@ -1015,10 +1308,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1015
1308
  pluginName: plugin.manifest.name,
1016
1309
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1017
1310
  sharedNativeRenderTargets: intent.kind === 'configSave'
1018
- ? configSharedNativeRenderTargets(plugin, [
1019
- ...Object.keys(intent.updates ?? {}),
1020
- ...(intent.removeKeys ?? []),
1021
- ])
1311
+ ? configSharedNativeRenderTargets(plugin, intent.retryApply
1312
+ ? undefined
1313
+ : [
1314
+ ...Object.keys(intent.updates ?? {}),
1315
+ ...(intent.removeKeys ?? []),
1316
+ ])
1022
1317
  : [],
1023
1318
  };
1024
1319
  },
@@ -1028,6 +1323,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1028
1323
  ...(selected.source?.sourceIdentity
1029
1324
  ? { sourceIdentity: selected.source.sourceIdentity }
1030
1325
  : {}),
1326
+ ...(selected.source?.logicalIdentity
1327
+ ? { logicalIdentity: selected.source.logicalIdentity }
1328
+ : {}),
1031
1329
  pluginName: selected.pluginName,
1032
1330
  scope: intent.scope,
1033
1331
  cwd: context.cwd,
@@ -1099,6 +1397,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1099
1397
  let dependencyPlan;
1100
1398
  let codexReconcilePlans;
1101
1399
  let operationPlan;
1400
+ let forceCleanupEvidence;
1102
1401
  await preparation?.reporter.emit('catalog');
1103
1402
  const offlineCleanupTarget = offlineCleanupReason && intent.kind === 'pluginWrite'
1104
1403
  ? status.payload.targets.find((target) => target.pluginId === intent.pluginId
@@ -1169,15 +1468,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1169
1468
  pluginName: plugin.manifest.name,
1170
1469
  source: sourceEvidenceForPlugin(source, plugin.manifest.name),
1171
1470
  sharedNativeRenderTargets: intent.kind === 'configSave'
1172
- ? configSharedNativeRenderTargets(plugin, [
1173
- ...Object.keys(intent.updates ?? {}),
1174
- ...(intent.removeKeys ?? []),
1175
- ])
1471
+ ? configSharedNativeRenderTargets(plugin, intent.retryApply
1472
+ ? undefined
1473
+ : [
1474
+ ...Object.keys(intent.updates ?? {}),
1475
+ ...(intent.removeKeys ?? []),
1476
+ ])
1176
1477
  : [],
1177
1478
  };
1178
1479
  },
1179
1480
  });
1180
1481
  let codexManager;
1482
+ let resolvedCodexManager;
1181
1483
  const hasDeclaredDependencies = (status.pluginSummaries.find((summary) => summary.pluginId === intent.pluginId)?.dependencyTotalCount ?? 0) > 0;
1182
1484
  if (intent.kind === 'pluginWrite') {
1183
1485
  const command = {
@@ -1192,6 +1494,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1192
1494
  cwd: context.cwd,
1193
1495
  platform: intent.platform,
1194
1496
  scope: intent.scope,
1497
+ ...(reviewedScriptTimeoutMs(intent) === undefined
1498
+ ? {}
1499
+ : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1195
1500
  ...(intent.action === 'install' && intent.dependencyScopeOverrides
1196
1501
  ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
1197
1502
  : {}),
@@ -1205,6 +1510,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1205
1510
  expectedGenerationSetDigest: catalogOperation.generationSetDigest,
1206
1511
  } : {}),
1207
1512
  previewOperationPlan: true,
1513
+ ...(codexObservationSession ? { codexObservationSession } : {}),
1208
1514
  ...(preparation ? {
1209
1515
  onPreviewStage: (stage) => stage === 'catalog' ? undefined : preparation.reporter.emit(stage),
1210
1516
  } : {}),
@@ -1212,6 +1518,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1212
1518
  signal?.throwIfAborted();
1213
1519
  codexReconcilePlans = preview?.codexReconcilePlans;
1214
1520
  operationPlan = preview?.operationPlan;
1521
+ if (intent.action === 'cleanup' && intent.force === true) {
1522
+ const candidates = preview?.offlineCleanupPlans?.filter((candidate) => candidate.forceExclusive === true
1523
+ && candidate.pluginId === intent.pluginId
1524
+ && candidate.platform === intent.platform
1525
+ && candidate.scope === intent.scope
1526
+ && candidate.plan.actions.length > 0) ?? [];
1527
+ if (candidates.length !== 1) {
1528
+ throw new TuiOperationBlockedError('forceCleanupPathUnavailable', '没有唯一且可验证的精确清理目标。');
1529
+ }
1530
+ [forceCleanupEvidence] = candidates;
1531
+ }
1215
1532
  if (hasDeclaredDependencies
1216
1533
  && (intent.action === 'install'
1217
1534
  || intent.action === 'refreshAndSync'
@@ -1234,6 +1551,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1234
1551
  throw new TuiOperationBlockedError(managerPreview.code, `${managerPreview.code}: ${message}`);
1235
1552
  }
1236
1553
  if (managerPreview) {
1554
+ if (managerPreview.state === 'system' || managerPreview.state === 'cached') {
1555
+ resolvedCodexManager = managerPreview.manager;
1556
+ }
1237
1557
  codexManager = {
1238
1558
  state: managerPreview.state,
1239
1559
  packageName: MANAGED_CODEX_PACKAGE_NAME,
@@ -1263,6 +1583,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1263
1583
  },
1264
1584
  source: selected.source,
1265
1585
  ...(offlineCleanup ? { offlineCleanup } : {}),
1586
+ ...(forceCleanupEvidence ? { offlineCleanup: forceCleanupEvidence } : {}),
1266
1587
  ...(dependencyPlan ? { dependencyPlan } : {}),
1267
1588
  ...(codexReconcilePlans ? { codexReconcilePlans } : {}),
1268
1589
  ...(operationPlan ? { operationPlan } : {}),
@@ -1273,6 +1594,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1273
1594
  const store = await readPluginConfigStore({
1274
1595
  sourceId: selected.source.sourceId,
1275
1596
  sourceIdentity: selected.source.sourceIdentity,
1597
+ ...('logicalIdentity' in selected.source
1598
+ ? { logicalIdentity: selected.source.logicalIdentity }
1599
+ : {}),
1276
1600
  pluginName: selected.pluginName,
1277
1601
  scope: intent.scope,
1278
1602
  cwd: context.cwd,
@@ -1286,6 +1610,15 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1286
1610
  : {}),
1287
1611
  storeDigest: digest({ exists: store.exists, rawDigest: store.rawDigest }),
1288
1612
  affectedTargets: evidenceTargets.map(({ platform, scope }) => ({ platform, scope })),
1613
+ ...(intent.scope === 'project' ? {
1614
+ shareability: await inspectConfigShareability({
1615
+ sourceId: selected.source.sourceId,
1616
+ pluginName: selected.pluginName,
1617
+ scope: intent.scope,
1618
+ cwd: context.cwd,
1619
+ env: context.env,
1620
+ }),
1621
+ } : {}),
1289
1622
  ...('sharedNativeRenderTargets' in selected
1290
1623
  && selected.sharedNativeRenderTargets.length > 0
1291
1624
  ? { sharedNativeRenderTargets: selected.sharedNativeRenderTargets }
@@ -1294,6 +1627,8 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1294
1627
  }
1295
1628
  if (catalogOperation)
1296
1629
  catalogOperationByEvidence.set(evidence, catalogOperation);
1630
+ if (resolvedCodexManager)
1631
+ codexManagerByEvidence.set(evidence, resolvedCodexManager);
1297
1632
  return evidence;
1298
1633
  };
1299
1634
  const dependencies = {
@@ -1501,6 +1836,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1501
1836
  const stores = await readConfigStoresForTui({
1502
1837
  sourceId: plugin.sourceId,
1503
1838
  sourceIdentity: plugin.sourceIdentity,
1839
+ logicalIdentity: plugin.catalogLogicalIdentity,
1504
1840
  pluginName: plugin.manifest.name,
1505
1841
  schema: plugin.manifest.config.fields,
1506
1842
  scope: layer.scope,
@@ -1535,6 +1871,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1535
1871
  const effectiveRaw = await resolveEffectivePluginConfig({
1536
1872
  sourceId: plugin.sourceId,
1537
1873
  sourceIdentity: plugin.sourceIdentity,
1874
+ logicalIdentity: plugin.catalogLogicalIdentity,
1538
1875
  pluginName: plugin.manifest.name,
1539
1876
  manifest: plugin.manifest,
1540
1877
  targetScope: layer.scope === 'user' ? 'user' : 'project',
@@ -1569,9 +1906,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1569
1906
  throw new TuiOperationBlockedError('invalidPluginId', '设置目标必须使用 canonical pluginId。');
1570
1907
  }
1571
1908
  let sourceIdentity;
1909
+ let logicalIdentity;
1572
1910
  try {
1573
1911
  const packageInfo = await readCliPackageInfo();
1574
- sourceIdentity = await withResolvedCatalogPlugin({
1912
+ const source = await withResolvedCatalogPlugin({
1575
1913
  selector: pluginId,
1576
1914
  cliVersion: packageInfo.version,
1577
1915
  env: context.env,
@@ -1579,11 +1917,17 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1579
1917
  ...(sessionContextForPlugin(pluginId)
1580
1918
  ? { operationContext: sessionContextForPlugin(pluginId) }
1581
1919
  : {}),
1582
- consume: async ({ source }) => source.sourceIdentity,
1920
+ consume: async ({ source }) => ({
1921
+ sourceIdentity: source.sourceIdentity,
1922
+ logicalIdentity: source.logicalIdentity,
1923
+ }),
1583
1924
  });
1925
+ sourceIdentity = source.sourceIdentity;
1926
+ logicalIdentity = source.logicalIdentity;
1584
1927
  }
1585
1928
  catch {
1586
1929
  sourceIdentity = undefined;
1930
+ logicalIdentity = undefined;
1587
1931
  }
1588
1932
  signal.throwIfAborted();
1589
1933
  const target = await inspectPluginConfigResetTarget({
@@ -1598,6 +1942,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1598
1942
  const store = await readPluginConfigStore({
1599
1943
  sourceId: parsed.sourceId,
1600
1944
  ...(sourceIdentity ? { sourceIdentity } : {}),
1945
+ ...(logicalIdentity ? { logicalIdentity } : {}),
1601
1946
  pluginName: parsed.pluginName,
1602
1947
  scope: layer.scope,
1603
1948
  cwd: context.cwd,
@@ -1645,13 +1990,21 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1645
1990
  expectedConfigDigest: expected.configDigest,
1646
1991
  };
1647
1992
  if (intent.sourceMutation.action === 'add') {
1648
- await addCatalogSourceCommand({ ...common, source: intent.sourceMutation.source });
1993
+ await addCatalogSourceCommand({
1994
+ ...common,
1995
+ source: intent.sourceMutation.source,
1996
+ expectedProposedConfigDigest: expected.proposedConfigDigest,
1997
+ expectedImpactDigest: expected.impactDigest,
1998
+ requireImpactConfirmation: true,
1999
+ confirmImpact: true,
2000
+ });
1649
2001
  }
1650
2002
  else {
1651
2003
  await updateCatalogSourceCommand({
1652
2004
  ...common,
1653
2005
  sourceId: intent.sourceId,
1654
2006
  update: intent.sourceMutation.update,
2007
+ expectedProposedConfigDigest: expected.proposedConfigDigest,
1655
2008
  expectedImpactDigest: expected.impactDigest,
1656
2009
  requireIdentityChangeConfirmation: true,
1657
2010
  confirmIdentityChange: true,
@@ -1751,6 +2104,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1751
2104
  sourceId: intent.sourceId,
1752
2105
  cwd: context.cwd,
1753
2106
  env: context.env,
2107
+ expectedConfigDigest: expected.primaryConfigDigest,
2108
+ expectedImpactDigest: expected.primaryImpactDigest,
2109
+ requireImpactConfirmation: true,
2110
+ confirmImpact: true,
1754
2111
  });
1755
2112
  primaryChanged = true;
1756
2113
  }
@@ -1761,7 +2118,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1761
2118
  configSaved,
1762
2119
  generation,
1763
2120
  activeAvailable: true,
1764
- errorCode: error instanceof Error ? error.name : 'catalogSourcePrimaryFailed',
2121
+ errorCode: typeof error === 'object' && error !== null
2122
+ && 'code' in error && typeof error.code === 'string'
2123
+ ? error.code
2124
+ : error instanceof Error ? error.name : 'catalogSourcePrimaryFailed',
1765
2125
  };
1766
2126
  }
1767
2127
  }
@@ -1827,11 +2187,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1827
2187
  cwd: context.cwd,
1828
2188
  env: context.env,
1829
2189
  expectedConfigDigest: expectedEvidence.sourceWrite?.configDigest,
2190
+ expectedProposedConfigDigest: expectedEvidence.sourceWrite?.proposedConfigDigest,
1830
2191
  expectedImpactDigest: expectedEvidence.sourceWrite?.impactDigest,
1831
2192
  runtime,
1832
2193
  };
1833
2194
  if (intent.action === 'add' && intent.source) {
1834
- return addCatalogSourceCommand({ ...common, source: intent.source, cliVersion: packageInfo.version });
2195
+ return addCatalogSourceCommand({
2196
+ ...common,
2197
+ source: intent.source,
2198
+ cliVersion: packageInfo.version,
2199
+ requireImpactConfirmation: true,
2200
+ confirmImpact: true,
2201
+ });
1835
2202
  }
1836
2203
  if (intent.action === 'update' && intent.update) {
1837
2204
  return updateCatalogSourceCommand({
@@ -1844,7 +2211,12 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1844
2211
  });
1845
2212
  }
1846
2213
  if (intent.action === 'setPrimary') {
1847
- return setPrimaryCatalogSourceCommand({ ...common, sourceId: intent.sourceId });
2214
+ return setPrimaryCatalogSourceCommand({
2215
+ ...common,
2216
+ sourceId: intent.sourceId,
2217
+ requireImpactConfirmation: true,
2218
+ confirmImpact: true,
2219
+ });
1848
2220
  }
1849
2221
  if (intent.action === 'remove') {
1850
2222
  return removeCatalogSourceCommand({
@@ -1856,12 +2228,164 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1856
2228
  }
1857
2229
  throw new TuiOperationBlockedError('invalidSourceMutation', '来源操作缺少候选输入。');
1858
2230
  }
2231
+ const codexObservationSession = intent.kind === 'pluginWrite'
2232
+ || intent.kind === 'pluginWriteBatch'
2233
+ ? await createCodexNativeObservationSession({
2234
+ exactCwd: context.cwd,
2235
+ env: context.env,
2236
+ runner: context.nativeCliRunner,
2237
+ manager: codexManagerByEvidence.get(expectedEvidence),
2238
+ })
2239
+ : undefined;
1859
2240
  return withAwHomeMutationLock({
1860
2241
  env: context.env,
1861
2242
  action: async (awHomeMutationToken) => {
1862
2243
  await tuiControllerRuntimeTestHooks?.beforeMutationEvidenceCapture?.();
1863
2244
  const operationInventory = createOperationInventoryReader();
1864
- const currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true);
2245
+ const currentEvidence = await captureEvidence(intent, undefined, undefined, operationInventory, true, undefined, codexObservationSession, intent.kind === 'pluginWriteBatch'
2246
+ ? expectedEvidence
2247
+ : undefined);
2248
+ if (intent.kind === 'pluginWriteBatch') {
2249
+ const expectedBatch = expectedEvidence;
2250
+ const currentBatch = currentEvidence;
2251
+ const reviewedGroups = expectedBatch.groups.map((group) => ({
2252
+ groupId: group.groupId,
2253
+ sourceId: group.sourceId,
2254
+ platform: group.platform,
2255
+ scope: group.scope,
2256
+ requestedPluginIds: group.requestedPluginIds,
2257
+ executable: group.executable,
2258
+ planFingerprint: group.preview.operationPlan
2259
+ ? createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
2260
+ : undefined,
2261
+ }));
2262
+ const currentGroups = currentBatch.groups.map((group) => ({
2263
+ groupId: group.groupId,
2264
+ sourceId: group.sourceId,
2265
+ platform: group.platform,
2266
+ scope: group.scope,
2267
+ requestedPluginIds: group.requestedPluginIds,
2268
+ executable: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
2269
+ ?.executable === false ? false : group.executable,
2270
+ planFingerprint: expectedBatch.groups.find((reviewed) => reviewed.groupId === group.groupId)
2271
+ ?.executable === false
2272
+ ? undefined
2273
+ : group.preview.operationPlan
2274
+ ? createBatchIntentCompatiblePlanFingerprint(group.preview.operationPlan)
2275
+ : undefined,
2276
+ }));
2277
+ if (expectedBatch.catalog.configDigest !== currentBatch.catalog.configDigest
2278
+ || JSON.stringify(expectedBatch.requestedPluginIds)
2279
+ !== JSON.stringify(currentBatch.requestedPluginIds)
2280
+ || JSON.stringify(reviewedGroups) !== JSON.stringify(currentGroups)) {
2281
+ throw operationReviewStale('确认后批量目标或写入计划发生变化。');
2282
+ }
2283
+ await tuiControllerRuntimeTestHooks?.afterMutationEvidenceCapture?.();
2284
+ const groupResults = [];
2285
+ const completedTargetKeys = new Set();
2286
+ let executedGroupCount = 0;
2287
+ const appendStoppedGroups = (startIndex, staleMessage) => {
2288
+ for (const [offset, stopped] of expectedBatch.groups.slice(startIndex).entries()) {
2289
+ groupResults.push({
2290
+ groupId: stopped.groupId,
2291
+ sourceId: stopped.sourceId,
2292
+ platform: stopped.platform,
2293
+ scope: stopped.scope,
2294
+ requestedPluginIds: stopped.requestedPluginIds,
2295
+ result: !stopped.executable ? stopped.preview : failedWriteCommandResult(intent.action, stopped.requestedPluginIds, { cwd: context.cwd, platform: stopped.platform, scope: stopped.scope }, offset === 0
2296
+ ? new TuiOperationBlockedError('operationReviewStale', staleMessage)
2297
+ : new TuiOperationBlockedError('batchNotStarted', '前一个批量组的写入计划已变化,因此这个组没有开始。')),
2298
+ });
2299
+ }
2300
+ };
2301
+ for (const [groupIndex, reviewed] of expectedBatch.groups.entries()) {
2302
+ const current = currentBatch.groups.find((group) => group.groupId === reviewed.groupId);
2303
+ if (!reviewed.executable) {
2304
+ groupResults.push({
2305
+ groupId: reviewed.groupId,
2306
+ sourceId: reviewed.sourceId,
2307
+ platform: reviewed.platform,
2308
+ scope: reviewed.scope,
2309
+ requestedPluginIds: reviewed.requestedPluginIds,
2310
+ result: reviewed.preview,
2311
+ });
2312
+ continue;
2313
+ }
2314
+ const groupInventory = groupResults.length === 0
2315
+ ? operationInventory
2316
+ : createOperationInventoryReader();
2317
+ let executionPreview = current.preview;
2318
+ if (completedTargetKeys.size > 0) {
2319
+ executionPreview = await runBatchWriteGroup({
2320
+ intent,
2321
+ group: current,
2322
+ cwd: context.cwd,
2323
+ runtime: {
2324
+ ...context,
2325
+ awHomeMutationToken,
2326
+ inventoryCatalogSourceReferences: groupInventory,
2327
+ expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
2328
+ previewOperationPlan: true,
2329
+ ...(codexObservationSession ? { codexObservationSession } : {}),
2330
+ },
2331
+ });
2332
+ if (!reviewed.preview.operationPlan || !executionPreview.operationPlan
2333
+ || createBatchIntentCompatiblePlanFingerprint(reviewed.preview.operationPlan, completedTargetKeys) !== createBatchIntentCompatiblePlanFingerprint(executionPreview.operationPlan, completedTargetKeys)) {
2334
+ appendStoppedGroups(groupIndex, '前序批量结果改变了这个组的实际写入计划。');
2335
+ break;
2336
+ }
2337
+ }
2338
+ const requiresManagedCodex = reviewed.preview.codexManagerPreview?.state === 'requires-install';
2339
+ const result = await runBatchWriteGroup({
2340
+ intent,
2341
+ group: current,
2342
+ cwd: context.cwd,
2343
+ runtime: {
2344
+ ...context,
2345
+ awHomeMutationToken,
2346
+ inventoryCatalogSourceReferences: groupInventory,
2347
+ expectedCatalogConfigDigest: expectedBatch.catalog.configDigest,
2348
+ ...(executionPreview.operationPlan ? {
2349
+ expectedPlanFingerprint: executionPreview.operationPlan.planFingerprint,
2350
+ } : {}),
2351
+ ...(requiresManagedCodex ? {
2352
+ managedCodexAuthorizer: async () => true,
2353
+ } : {}),
2354
+ ...(codexObservationSession ? { codexObservationSession } : {}),
2355
+ },
2356
+ });
2357
+ if (writeReviewIsStale(result)) {
2358
+ if (executedGroupCount === 0)
2359
+ throwIfWriteReviewStale(result);
2360
+ appendStoppedGroups(groupIndex, '这个组的实际写入计划发生变化。');
2361
+ break;
2362
+ }
2363
+ executedGroupCount += 1;
2364
+ for (const target of result.targetResults) {
2365
+ if (target.state === 'completed') {
2366
+ completedTargetKeys.add([
2367
+ target.target.pluginId,
2368
+ target.target.platform,
2369
+ target.target.scope,
2370
+ ].join('\0'));
2371
+ }
2372
+ }
2373
+ groupResults.push({
2374
+ groupId: reviewed.groupId,
2375
+ sourceId: reviewed.sourceId,
2376
+ platform: reviewed.platform,
2377
+ scope: reviewed.scope,
2378
+ requestedPluginIds: reviewed.requestedPluginIds,
2379
+ result,
2380
+ });
2381
+ }
2382
+ const merged = mergeWriteCommandResults(groupResults.map((group) => group.result));
2383
+ return {
2384
+ ...merged,
2385
+ requestedPluginIds: expectedBatch.requestedPluginIds,
2386
+ groupResults,
2387
+ };
2388
+ }
1865
2389
  if (intent.kind !== 'pluginWrite'
1866
2390
  && !operationEvidenceMatches(expectedEvidence, currentEvidence)) {
1867
2391
  throw new TuiOperationBlockedError('operationReviewStale', '确认后状态发生变化。');
@@ -1877,6 +2401,9 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1877
2401
  cwd: context.cwd,
1878
2402
  platform: intent.platform,
1879
2403
  scope: intent.scope,
2404
+ ...(reviewedScriptTimeoutMs(intent) === undefined
2405
+ ? {}
2406
+ : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1880
2407
  ...(intent.action === 'install' && intent.dependencyScopeOverrides
1881
2408
  ? { dependencyScopeOverrides: intent.dependencyScopeOverrides }
1882
2409
  : {}),
@@ -1895,11 +2422,19 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1895
2422
  }[intent.action];
1896
2423
  const currentSource = currentEvidence.source;
1897
2424
  const offlineCleanup = currentEvidence.offlineCleanup;
2425
+ const reviewedOfflineCleanup = expectedEvidence.offlineCleanup;
2426
+ if ((offlineCleanup || reviewedOfflineCleanup)
2427
+ && (!offlineCleanup
2428
+ || !reviewedOfflineCleanup
2429
+ || !offlineCleanupEvidenceMatches(reviewedOfflineCleanup, offlineCleanup))) {
2430
+ throw new TuiOperationBlockedError('operationReviewStale', '确认后离线清理证据发生变化。');
2431
+ }
1898
2432
  if (!offlineCleanup && (!expectedEvidence.source || !currentSource)) {
1899
2433
  throw new TuiOperationBlockedError('operationReviewStale', '确认信息缺少来源身份。');
1900
2434
  }
1901
2435
  const result = await command([intent.pluginId], options, {
1902
2436
  ...context,
2437
+ ...(codexObservationSession ? { codexObservationSession } : {}),
1903
2438
  awHomeMutationToken,
1904
2439
  inventoryCatalogSourceReferences: operationInventory,
1905
2440
  ...(expectedEvidence.operationPlan
@@ -1913,14 +2448,18 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1913
2448
  ...(expectedEvidence.codexManager?.state === 'requires-install'
1914
2449
  ? { managedCodexAuthorizer: async () => true }
1915
2450
  : {}),
1916
- ...(offlineCleanup
1917
- ? { expectedOfflineCleanupEvidence: offlineCleanup }
2451
+ ...(reviewedOfflineCleanup
2452
+ ? { expectedOfflineCleanupEvidence: reviewedOfflineCleanup }
1918
2453
  : { reviewPreflight: (actual) => assertSourceEvidenceMatches(currentSource, actual) }),
1919
2454
  });
1920
2455
  throwIfWriteReviewStale(result);
1921
2456
  return result;
1922
2457
  }
1923
2458
  if (intent.kind === 'configSave' || intent.kind === 'configReset') {
2459
+ const operationRebuildResults = [];
2460
+ const operationDiagnosticSink = (results) => {
2461
+ operationRebuildResults.push(...structuredClone(results));
2462
+ };
1924
2463
  const currentSource = currentEvidence.source;
1925
2464
  if (intent.kind === 'configReset') {
1926
2465
  const parsed = parsePluginId(intent.pluginId);
@@ -1937,6 +2476,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1937
2476
  rebuildRuntime: { ...context, awHomeMutationToken },
1938
2477
  awHomeMutationToken,
1939
2478
  apply: false,
2479
+ sharedNativeApplyConfirmed: false,
1940
2480
  });
1941
2481
  }
1942
2482
  if (!currentSource) {
@@ -1967,6 +2507,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1967
2507
  rebuildRuntime: {
1968
2508
  ...context,
1969
2509
  awHomeMutationToken,
2510
+ operationDiagnosticSink,
2511
+ ...(reviewedScriptTimeoutMs(intent) === undefined
2512
+ ? {}
2513
+ : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1970
2514
  ...(catalogOperation ? {
1971
2515
  catalogOperationContext: catalogOperation,
1972
2516
  expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
@@ -1974,6 +2518,7 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1974
2518
  },
1975
2519
  awHomeMutationToken,
1976
2520
  applyTargets,
2521
+ sharedNativeApplyConfirmed: true,
1977
2522
  })
1978
2523
  : await applyPluginConfigWrite({
1979
2524
  plugin,
@@ -1985,6 +2530,10 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1985
2530
  rebuildRuntime: {
1986
2531
  ...context,
1987
2532
  awHomeMutationToken,
2533
+ operationDiagnosticSink,
2534
+ ...(reviewedScriptTimeoutMs(intent) === undefined
2535
+ ? {}
2536
+ : { scriptTimeoutMs: reviewedScriptTimeoutMs(intent) }),
1988
2537
  ...(catalogOperation ? {
1989
2538
  catalogOperationContext: catalogOperation,
1990
2539
  expectedGenerationSetDigest: expectedEvidence.catalog.generationSetDigest,
@@ -1993,13 +2542,16 @@ export function createTuiControllerDependencies(context, progressFeed, options =
1993
2542
  awHomeMutationToken,
1994
2543
  applyTargets,
1995
2544
  request: { updates: intent.updates, removeKeys: intent.removeKeys },
2545
+ sharedNativeApplyConfirmed: true,
1996
2546
  });
1997
- return result;
2547
+ return { ...result, operationRebuildResults };
1998
2548
  },
1999
2549
  });
2000
2550
  }
2001
2551
  throw new TuiOperationBlockedError('operationNotImplemented', '这个写操作尚未接入新 Controller。');
2002
2552
  },
2553
+ }).finally(async () => {
2554
+ await codexObservationSession?.dispose();
2003
2555
  });
2004
2556
  },
2005
2557
  };