@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
@@ -0,0 +1,770 @@
1
+ import { getCommandPackageId } from '@lesliechan721/aw-plugin-core/manifest';
2
+ import { addRequirements } from '../commands/intent-command.js';
3
+ import { createEmptyIntent, calculateIntentDigest, removeIntentRoot } from '../intent/intent-codec.js';
4
+ import { observeIntent, writeIntent, resolveIntentPath } from '../intent/intent-store.js';
5
+ import { describeStrongStateProblem } from '../intent/diagnostics.js';
6
+ import { normalizeAgents } from '../intent/deployment-preferences.js';
7
+ import { createScopeContext } from '../intent/scope-context.js';
8
+ import { withAwHomeMutationLock } from '../utils/aw-home-mutation-lock.js';
9
+ import { readSafeOperationError } from '../utils/safe-operation-diagnostic.js';
10
+ import { createCodexNativeObservationSession } from '../utils/codex-native-observation.js';
11
+ import { bindNativeCliRunnerSignal, getDefaultNativeCliRunner } from '../utils/native-cli.js';
12
+ import { analyzeCodexSurface } from '../utils/codex-surface-analysis.js';
13
+ import { validateCatalogSnapshotComplete } from '../utils/catalog-snapshot.js';
14
+ import { readCatalogGenerationState } from '../utils/catalog-generation-state.js';
15
+ import { parsePluginId } from '../utils/multi-source-catalog.js';
16
+ import { readDemand } from './demand.js';
17
+ import { readJointDemand, deriveJointDemand, findPlacementConflicts, targetsConflict } from './joint-demand.js';
18
+ import { prepareScopedRecovery, executeScopedRecovery, executeRecoveryPreApply, describeRecoveryPlan } from './coordinator.js';
19
+ import { readScopedRecoveryStatus, readRecoveryStatus } from './status.js';
20
+ const diagnostic = (code, message = code) => ({ code, message });
21
+ function intentReceipt(observation) {
22
+ return { state: observation.state,
23
+ roots: observation.state === 'valid' ? observation.value.roots.map((root) => root.pluginId)
24
+ : observation.state === 'absent' ? [] : null,
25
+ ...('digest' in observation ? { digest: observation.digest } : {}),
26
+ };
27
+ }
28
+ const observationEvidence = (value) => 'rawDigest' in value ? `${value.state}:${value.rawDigest}` : value.state;
29
+ function snapshotEvidence(snapshot) {
30
+ return JSON.stringify([observationEvidence(snapshot.user.intent),
31
+ snapshot.project ? observationEvidence(snapshot.project.intent) : null,
32
+ observationEvidence(snapshot.user.preferences), snapshot.user.catalogOperationContext?.generationSetDigest,
33
+ snapshot.project?.catalogOperationContext?.generationSetDigest]);
34
+ }
35
+ function asObservation(value) {
36
+ const rawBytes = Buffer.from(JSON.stringify(value));
37
+ return { state: 'valid', value, digest: calculateIntentDigest(value), rawDigest: calculateIntentDigest(value), rawBytes };
38
+ }
39
+ function mergeReceipts(plan, receipts) {
40
+ const receipt = structuredClone(plan.receipt);
41
+ receipt.targetResults = receipts.flatMap((item) => item.targetResults);
42
+ for (const root of plan.joint?.demand.roots ?? []) {
43
+ if (!receipt.requestedRoots.includes(root.pluginId) || !receipt.selection.agents.includes(root.target)
44
+ || !['unsupported', 'excluded'].includes(root.state))
45
+ continue;
46
+ receipt.targetResults.push({ pluginId: root.pluginId, agent: root.target, scope: root.scope,
47
+ requirementScope: root.scope, delivery: null, execution: 'no-op', observation: 'verified', content: 'satisfied',
48
+ satisfaction: root.state, requiredBy: [root.pluginId], blockers: [],
49
+ ...(root.reasons?.length ? { reasons: root.reasons } : {}) });
50
+ }
51
+ receipt.requestedTargets = receipt.targetResults.map(({ pluginId, agent, scope, delivery }) => ({ pluginId, agent, scope, delivery }));
52
+ for (const target of receipt.targetResults) {
53
+ const placement = plan.joint?.demand.placements.find((item) => item.pluginId === target.pluginId && item.target === target.agent);
54
+ if (placement) {
55
+ target.requiredBy = [...new Set(placement.contributions.map((item) => item.root))];
56
+ target.providerScope = placement.scope;
57
+ }
58
+ }
59
+ receipt.remainingTargets = receipts.flatMap((item) => item.remainingTargets);
60
+ receipt.errors = [...receipt.blockers, ...receipts.flatMap((item) => [...item.blockers, ...item.errors])];
61
+ receipt.outputsMayHaveChanged = receipts.some((item) => item.outputsMayHaveChanged);
62
+ receipt.commandResults = receipts.flatMap((item) => item.commandResults ?? []);
63
+ receipt.nativeToolChanges = receipts.flatMap((item) => item.nativeToolChanges ?? []);
64
+ receipt.recoveryActions = [...new Set(receipts.flatMap((item) => item.recoveryActions))];
65
+ receipt.ok = receipt.blockers.length === 0 && receipts.every((item) => item.ok);
66
+ return receipt;
67
+ }
68
+ /** Complete requirements determine placement before execution selectors are applied. */
69
+ export async function prepareJointRecovery(command, selectors, options, runtime) {
70
+ runtime = { ...runtime, preparedArtifactRenderResults: runtime.preparedArtifactRenderResults ?? new Map() };
71
+ const snapshot = await readJointDemand(options);
72
+ if (runtime.codexObservationSession)
73
+ return prepareJointRecoveryInSession(command, selectors, options, runtime, snapshot);
74
+ const observationContext = await createScopeContext({ ...options, scope: snapshot.context.context });
75
+ const executionRunner = runtime.nativeCliRunner ?? getDefaultNativeCliRunner();
76
+ let sessionRunner = bindNativeCliRunnerSignal(executionRunner, options.signal);
77
+ const session = await createCodexNativeObservationSession({ exactCwd: observationContext.executionCwd,
78
+ env: observationContext.env, runner: (invocation) => sessionRunner(invocation), manager: runtime.codexManager });
79
+ try {
80
+ const plan = await prepareJointRecoveryInSession(command, selectors, options, { ...runtime, codexObservationSession: session }, snapshot);
81
+ const dispose = plan.context.dispose;
82
+ plan.context.dispose = async () => {
83
+ try {
84
+ await dispose();
85
+ }
86
+ finally {
87
+ try {
88
+ await session.dispose();
89
+ }
90
+ finally {
91
+ await observationContext.dispose();
92
+ }
93
+ }
94
+ };
95
+ return plan;
96
+ }
97
+ catch (error) {
98
+ try {
99
+ await session.dispose();
100
+ }
101
+ finally {
102
+ await observationContext.dispose();
103
+ }
104
+ throw error;
105
+ }
106
+ finally {
107
+ // The same observation session also serves confirmed writes, which cannot use preparation cancellation.
108
+ sessionRunner = executionRunner;
109
+ }
110
+ }
111
+ async function prepareJointRecoveryInSession(command, selectors, options, runtime, snapshot) {
112
+ const evidence = snapshotEvidence(snapshot);
113
+ const scope = options.scope ?? snapshot.context.context;
114
+ const observedIntent = (scope === 'project' ? snapshot.project : snapshot.user).intent;
115
+ if (options.existingOnly) {
116
+ // Existing owned deployments can be refreshed without converting them into declarations.
117
+ const observed = await readRecoveryStatus({ ...options, nativeCliRunner: runtime.nativeCliRunner, codexObservationSession: runtime.codexObservationSession });
118
+ const installed = (observed.existingTargets ?? []).filter((item) => selectors.some((selector) => selector === item.pluginId
119
+ || !selector.includes('/') && parsePluginId(item.pluginId).pluginName === selector)
120
+ && (!options.targets || options.targets.includes(item.agent)));
121
+ if (installed.length) {
122
+ const generations = await readCatalogGenerationState(options.env);
123
+ for (const currentScope of snapshot.context.scopes) {
124
+ const sourceDemand = currentScope === 'user' ? snapshot.user : snapshot.project;
125
+ if (sourceDemand.intent.state !== 'valid' && sourceDemand.intent.state !== 'absent')
126
+ continue;
127
+ const value = structuredClone(sourceDemand.intent.state === 'valid' ? sourceDemand.intent.value : createEmptyIntent());
128
+ for (const item of installed.filter((item) => item.scope === currentScope)) {
129
+ if (value.roots.some((root) => root.pluginId === item.pluginId))
130
+ continue;
131
+ const sourceId = parsePluginId(item.pluginId).sourceId;
132
+ const identity = generations.sources[sourceId]?.active?.logicalIdentity;
133
+ if (!identity)
134
+ continue;
135
+ value.sources[sourceId] = { logicalIdentity: identity };
136
+ value.roots.push({ pluginId: item.pluginId });
137
+ }
138
+ snapshot = { ...snapshot, [currentScope]: await readDemand({ ...options, scope: currentScope, intent: asObservation(value) }) };
139
+ }
140
+ if (snapshot.preferences)
141
+ snapshot.preferences = { ...snapshot.preferences,
142
+ targets: normalizeAgents([...snapshot.preferences.targets, ...installed.map((item) => item.agent)]) };
143
+ }
144
+ }
145
+ const previousDemand = deriveJointDemand(snapshot);
146
+ const original = scope === 'project' ? snapshot.project : snapshot.user;
147
+ const errors = [];
148
+ let declaration;
149
+ let requested = [...selectors];
150
+ if (command === 'install') {
151
+ if (options.targets !== undefined)
152
+ errors.push(diagnostic('installTargetSelectionUnsupported', '安装使用已保存的本机 Agent。'));
153
+ if (!snapshot.preferences?.targets.length)
154
+ errors.push(diagnostic('targetsNotConfigured', '请先配置本机 Agent,再安装插件。'));
155
+ else {
156
+ const proposed = await addRequirements({ ...options, scope, selectors, token: runtime.awHomeMutationToken, dryRun: true, installSelectors: true });
157
+ errors.push(...proposed.errors);
158
+ if (proposed.state && !proposed.errors.length) {
159
+ requested = proposed.roots;
160
+ if (proposed.changed)
161
+ declaration = { scope, value: proposed.state };
162
+ const demand = await readDemand({ ...options, scope, intent: asObservation(proposed.state) });
163
+ snapshot = { ...snapshot, [scope]: demand };
164
+ }
165
+ }
166
+ }
167
+ else if (command === 'uninstall') {
168
+ try {
169
+ const sourceIds = new Set((original.roots ?? []).filter((root) => selectors.some((selector) => selector === root.pluginId
170
+ || !selector.includes('/') && parsePluginId(root.pluginId).pluginName === selector)).map((root) => root.sourceId));
171
+ for (const sourceId of sourceIds) {
172
+ const source = original.catalogOperationContext?.catalog.sources.find((entry) => entry.sourceId === sourceId)?.resolution;
173
+ if (!source)
174
+ throw new Error('sourceActiveUnavailable');
175
+ await validateCatalogSnapshotComplete(source.rootDir);
176
+ }
177
+ }
178
+ catch (error) {
179
+ options.signal?.throwIfAborted();
180
+ if (error instanceof Error && error.name === 'AbortError')
181
+ throw error;
182
+ errors.push(diagnostic('catalogSnapshotInvalid', '来源快照无法完整验证,未修改声明或输出。'));
183
+ }
184
+ if (original.intent.state !== 'valid' && original.intent.state !== 'absent')
185
+ errors.push(diagnostic('pluginIntentStateInvalid'));
186
+ else {
187
+ let value = original.intent.state === 'valid' ? original.intent.value : createEmptyIntent();
188
+ for (const selector of selectors) {
189
+ const parsed = parsePluginId(selector);
190
+ const matches = value.roots.filter((root) => parsed.sourceId ? root.pluginId === selector
191
+ : parsePluginId(root.pluginId).pluginName === parsed.pluginName);
192
+ if (matches.length !== 1) {
193
+ errors.push(diagnostic('pluginIntentRootMissing'));
194
+ continue;
195
+ }
196
+ requested[requested.indexOf(selector)] = matches[0].pluginId;
197
+ value = removeIntentRoot(value, matches[0].pluginId);
198
+ }
199
+ if (!errors.length) {
200
+ declaration = { scope, value };
201
+ const demand = await readDemand({ ...options, scope, intent: asObservation(value) });
202
+ snapshot = { ...snapshot, [scope]: demand };
203
+ }
204
+ }
205
+ }
206
+ const demand = deriveJointDemand(snapshot);
207
+ errors.push(...demand.errors);
208
+ const rootIds = [...new Set([...(snapshot.user.roots ?? []), ...(snapshot.project?.roots ?? [])]
209
+ .map((root) => root.pluginId))];
210
+ if (command !== 'install' && command !== 'uninstall') {
211
+ requested = selectors.length ? selectors.flatMap((selector) => {
212
+ const parsed = parsePluginId(selector);
213
+ const matches = (options.existingOnly ? [...new Set([...rootIds, ...demand.placements.map((item) => item.pluginId)])] : rootIds).filter((id) => parsed.sourceId ? id === selector : parsePluginId(id).pluginName === parsed.pluginName);
214
+ if (matches.length !== 1 && !options.existingOnly)
215
+ errors.push(diagnostic('syncPluginNotDesired', `${selector} 未声明或名称不唯一。`));
216
+ return matches.length === 1 ? matches : [];
217
+ }) : rootIds;
218
+ }
219
+ const targets = options.targets ? normalizeAgents(options.targets) : snapshot.preferences?.targets ?? [];
220
+ if (options.exactTargets && (!options.exactTargets.length && !options.existingOnly || options.exactTargets.some((exact) => !targets.includes(exact.agent) || !demand.roots.some((root) => root.pluginId === exact.pluginId
221
+ && requested.includes(root.pluginId) && root.target === exact.agent && (!exact.scope || root.scope === exact.scope))
222
+ && !demand.placements.some((placement) => placement.pluginId === exact.pluginId && placement.target === exact.agent
223
+ && (!exact.scope || placement.scope === exact.scope || placement.contributions.some((item) => item.scope === exact.scope))
224
+ && (requested.includes(placement.pluginId) || placement.contributions.some((item) => requested.includes(item.root))))))) {
225
+ errors.push(diagnostic('invalidExactTargets', '精确重试目标不属于当前请求,请重新检查并选择。'));
226
+ }
227
+ const selected = demand.placements.filter((placement) => targets.includes(placement.target)
228
+ && !demand.conflicts.some((conflict) => [conflict.left, conflict.right].some((position) => position.target === placement.target && position.scope === placement.scope))
229
+ && (!options.exactTargets || options.exactTargets.some((exact) => exact.agent === placement.target
230
+ && (!exact.scope || placement.contributions.some((item) => item.scope === exact.scope))
231
+ && (exact.pluginId === placement.pluginId || placement.contributions.some((item) => item.path.includes(exact.pluginId)))))
232
+ && (command === 'uninstall' ? requested.includes(placement.pluginId)
233
+ || previousDemand.placements.some((previous) => previous.pluginId === placement.pluginId && previous.target === placement.target && previous.scope !== placement.scope)
234
+ : placement.contributions.some((item) => requested.includes(item.root)) || options.existingOnly && requested.includes(placement.pluginId)));
235
+ const activationOnlyTargets = [];
236
+ if (snapshot.project && !errors.length) {
237
+ const sharedNative = selected.filter((item) => item.scope === 'user' && (item.target === 'codex' || item.target === 'claude')
238
+ && item.contributions.some((entry) => entry.scope === 'project'));
239
+ if (sharedNative.length) {
240
+ const expanded = { ...snapshot.project, roots: [...new Map(sharedNative.map((item) => [item.pluginId, {
241
+ pluginId: item.pluginId, sourceId: item.plugin.sourceId, sourceState: 'ready', blockers: [], plugin: item.plugin,
242
+ }])).values()], userProviders: Object.fromEntries(targets.map((target) => [target,
243
+ demand.placements.filter((item) => item.scope === 'user' && item.target === target).map((item) => item.pluginId)])) };
244
+ const observation = await readScopedRecoveryStatus({ ...options, nativeCliRunner: runtime.nativeCliRunner, codexObservationSession: runtime.codexObservationSession, scope: 'project', demandOverride: expanded,
245
+ cwd: snapshot.context.projectRoot, targets: undefined });
246
+ for (const item of sharedNative) {
247
+ const actual = observation.inventory?.find((entry) => entry.pluginId === item.pluginId)?.targetResults
248
+ .find((entry) => entry.agent === item.target);
249
+ if (item.target === 'codex') {
250
+ const surface = analyzeCodexSurface(item.plugin.manifest);
251
+ const live = actual?.backend?.live;
252
+ const explicitlyDisabled = live?.components?.some((component) => component.kind === 'activation'
253
+ && component.key === 'project' && component.state === 'disabled');
254
+ const userProvidesComponents = surface.components.every((component) => !component.scopes.includes('project') || component.scopes.includes('user'));
255
+ const retiringProjectComponents = live?.components?.some((component) => component.kind === 'ownership'
256
+ && component.key !== 'native' && !component.key.startsWith('activation-') && !component.key.startsWith('marketplace-')
257
+ && ['present', 'partial'].includes(component.state));
258
+ if (userProvidesComponents) {
259
+ if (!surface.nativeKinds.length || !explicitlyDisabled && !retiringProjectComponents)
260
+ continue;
261
+ activationOnlyTargets.push({ pluginId: item.pluginId, platform: 'codex', scope: 'project' });
262
+ selected.push({ ...item, scope: 'project' });
263
+ continue;
264
+ }
265
+ }
266
+ if (actual?.content !== 'satisfied') {
267
+ if (item.target === 'claude')
268
+ activationOnlyTargets.push({ pluginId: item.pluginId, platform: 'claude', scope: 'project' });
269
+ selected.push({ ...item, scope: 'project' });
270
+ }
271
+ }
272
+ }
273
+ }
274
+ const context = await createScopeContext({ ...options, scope });
275
+ const baseDemand = scope === 'project' ? snapshot.project : snapshot.user;
276
+ const receipt = {
277
+ schemaVersion: 1, kind: 'aw-recovery', command, scope, ok: false,
278
+ intent: intentReceipt(observedIntent),
279
+ requestedRoots: requested, requestedTargets: [], remainingTargets: [], targetResults: [],
280
+ selection: { agents: targets, origin: options.targets ? 'argument' : targets.length ? 'preference' : 'none', preferences: snapshot.user.preferences.state },
281
+ blockers: errors, errors, recoveryActions: [], declarationChanged: false, preferencesChanged: false,
282
+ configChanged: false, outputsMayHaveChanged: false,
283
+ coverage: { scopes: snapshot.context.scopes, ...(snapshot.context.projectRoot ? { projectRoot: snapshot.context.projectRoot } : {}) },
284
+ };
285
+ const children = [];
286
+ const { signal: _signal, ...executionOptions } = options;
287
+ const joint = { snapshot, demand, children, originalOptions: executionOptions,
288
+ selectors: [...selectors], declaration, evidence };
289
+ const plan = { command, options: { ...executionOptions, scope }, receipt, context, demand: baseDemand,
290
+ originalIntent: observationEvidence(original.intent), preferencesEvidence: observationEvidence(snapshot.user.preferences),
291
+ plannedImpact: { declaration: Boolean(declaration), preferences: false, config: false, outputs: false },
292
+ changes: declaration ? [`保存 ${scope} 插件需求。`] : [], warnings: [], nativeGroups: [],
293
+ historicalNativeTargets: new Set(), deliverySwitchChecks: [], portableGroups: [], portableProtectionGroups: [],
294
+ portableResidues: [], sourceRefreshes: [], sourceRefreshFailures: [], runtime, blockExecution: errors.some((error) => ['syncPluginNotDesired', 'pluginIntentRootMissing', 'pluginIntentStateInvalid', 'catalogSnapshotInvalid', 'installTargetSelectionUnsupported', 'invalidExactTargets'].includes(error.code)
295
+ || error.code === 'targetsNotConfigured' && !options.retireUnselectedTargets)
296
+ || !snapshot.preferences || snapshot.user.roots === null || snapshot.project?.roots === null, joint };
297
+ const dispose = context.dispose;
298
+ context.dispose = async () => { await Promise.all(children.map((child) => child.context.dispose())); await dispose(); };
299
+ if (plan.blockExecution)
300
+ return plan;
301
+ try {
302
+ const currentStatus = await readRecoveryStatus({ ...options, nativeCliRunner: runtime.nativeCliRunner,
303
+ codexObservationSession: runtime.codexObservationSession, targets: undefined });
304
+ // Native platforms share one complete marketplace candidate per scope.
305
+ const nativeTargets = targets.filter((target) => target === 'codex' || target === 'claude');
306
+ const targetGroups = [...(nativeTargets.length ? [nativeTargets] : []),
307
+ ...targets.filter((target) => target !== 'codex' && target !== 'claude').map((target) => [target])];
308
+ for (const currentScope of snapshot.context.scopes) {
309
+ for (const groupTargets of targetGroups) {
310
+ const placements = selected.filter((item) => item.scope === currentScope && groupTargets.includes(item.target)
311
+ && !(command === 'install' && scope === 'project' && currentScope === 'user'
312
+ && currentStatus.observedTargets?.some((observed) => observed.pluginId === item.pluginId
313
+ && observed.agent === item.target && observed.scope === 'user' && observed.content === 'satisfied')));
314
+ if (!placements.length)
315
+ continue;
316
+ const sourceDemand = currentScope === 'project' ? snapshot.project : snapshot.user;
317
+ const childPlacements = [
318
+ ...demand.placements.filter((item) => item.scope === currentScope && groupTargets.includes(item.target)),
319
+ ...placements,
320
+ ];
321
+ const userProviders = demand.placements.filter((item) => item.scope === 'user' && groupTargets.includes(item.target));
322
+ const childDemand = { ...sourceDemand,
323
+ targetRequirements: childPlacements.map((item) => ({ pluginId: item.pluginId, target: item.target })),
324
+ userProviders: Object.fromEntries(groupTargets.map((target) => [target, userProviders
325
+ .filter((item) => item.target === target).map((item) => item.pluginId)])),
326
+ roots: [...new Map(childPlacements.map((item) => [item.pluginId, { pluginId: item.pluginId, sourceId: item.plugin.sourceId,
327
+ sourceState: 'ready', blockers: [], plugin: item.plugin }])).values()] };
328
+ const child = await prepareScopedRecovery(command === 'reinstall' ? 'reinstall' : 'sync', [...new Set(placements.map((item) => item.pluginId))], { ...options, scope: currentScope, targets: groupTargets, all: false,
329
+ cwd: snapshot.context.projectRoot ?? options.cwd, demandOverride: childDemand,
330
+ exactTargets: placements.map((item) => ({ pluginId: item.pluginId, agent: item.target })),
331
+ nativeActivationOnlyTargets: activationOnlyTargets.filter((item) => item.scope === currentScope
332
+ && placements.some((placement) => placement.pluginId === item.pluginId && placement.target === item.platform)),
333
+ nativePlannedRetirements: command === 'uninstall' && currentScope === 'project'
334
+ ? placements.filter((item) => item.target === 'codex'
335
+ && currentStatus.ownedTargets?.some((owned) => owned.pluginId === item.pluginId && owned.agent === item.target && owned.scope === 'user')
336
+ && !demand.placements.some((provider) => provider.pluginId === item.pluginId && provider.target === item.target && provider.scope === 'user'))
337
+ .map((item) => ({ pluginId: item.pluginId, platform: 'codex', scope: 'user' })) : [],
338
+ excludedPluginIds: currentScope === 'project' ? userProviders.map((item) => item.pluginId) : [],
339
+ dependencyScopeOverrides: currentScope === 'project'
340
+ ? userProviders.filter((provider) => (provider.target === 'codex' || provider.target === 'claude')
341
+ && !placements.some((item) => item.pluginId === provider.pluginId && item.target === provider.target))
342
+ .map((item) => ({ pluginId: item.pluginId, platform: item.target, scope: 'user' })) : [],
343
+ }, runtime);
344
+ // Synthetic roots are only an execution projection, never persisted Intent.
345
+ child.originalIntent = observationEvidence(sourceDemand.intent);
346
+ if (options.existingOnly) {
347
+ const observed = await readScopedRecoveryStatus({ ...options, nativeCliRunner: runtime.nativeCliRunner, codexObservationSession: runtime.codexObservationSession, scope: currentScope,
348
+ cwd: snapshot.context.projectRoot ?? options.cwd, demandOverride: childDemand, targets: groupTargets });
349
+ for (const item of child.receipt.targetResults) {
350
+ if (!observed.existingTargets?.some((existing) => existing.pluginId === item.pluginId && existing.agent === item.agent && existing.scope === currentScope)) {
351
+ item.blockers.push(diagnostic('configApplyRequiresExistingOutput', '输出缺失或不可确认,请先运行 sync。'));
352
+ }
353
+ }
354
+ }
355
+ children.push(child);
356
+ }
357
+ }
358
+ const portableCandidates = children.flatMap((child) => child.portableGroups.flatMap((prepared) => prepared.targets
359
+ .filter((target) => target.disposition !== 'remove')
360
+ .map((target) => ({ child, prepared, target }))));
361
+ for (const [index, left] of portableCandidates.entries())
362
+ for (const right of portableCandidates.slice(index + 1)) {
363
+ if (left.child.options.scope === right.child.options.scope || left.target.agent !== right.target.agent
364
+ || left.target.skill !== right.target.skill)
365
+ continue;
366
+ const conflict = diagnostic('crossScopeSkillCollision', `${left.target.providerPluginId}@${left.child.options.scope} (${left.target.physicalPath}) 与 ${right.target.providerPluginId}@${right.child.options.scope} (${right.target.physicalPath}) 占用同一 Agent Skill 名。`);
367
+ for (const item of [left, right]) {
368
+ item.child.receipt.targetResults.find((target) => target.pluginId === item.prepared.options.selector && target.agent === item.target.agent)?.blockers.push(conflict);
369
+ }
370
+ plan.receipt.blockers.push(conflict);
371
+ }
372
+ // Only explicit target exits, selected removals and provider switches retire outputs.
373
+ // Unrelated indirect dependencies remain visible residue.
374
+ const ownsOutput = (item) => currentStatus.ownedTargets?.some((owned) => owned.pluginId === item.pluginId
375
+ && owned.agent === item.agent && owned.scope === item.scope) === true;
376
+ const requiredNativeActivation = (item) => {
377
+ const backend = item.backend;
378
+ const scopedExtensions = (backend?.live?.components ?? backend?.components ?? []).some((component) => component.kind === 'ownership' && component.key !== 'native'
379
+ && !component.key.startsWith('activation-') && !component.key.startsWith('marketplace-')
380
+ && ['present', 'partial'].includes(component.state));
381
+ if (item.scope !== 'project' || (item.agent !== 'codex' && item.agent !== 'claude'))
382
+ return false;
383
+ return demand.placements.some((placement) => {
384
+ if (placement.pluginId !== item.pluginId || placement.target !== item.agent)
385
+ return false;
386
+ if (item.agent === 'codex' && scopedExtensions) {
387
+ const surface = analyzeCodexSurface(placement.plugin.manifest);
388
+ if (surface.extensionKinds.length > 0 && surface.components.every((component) => !component.scopes.includes('project') || component.scopes.includes('user')))
389
+ return false;
390
+ }
391
+ return placement.contributions.some((contribution) => contribution.scope === 'project');
392
+ });
393
+ };
394
+ for (const candidate of portableCandidates)
395
+ for (const installed of currentStatus.extraObserved) {
396
+ const backend = installed.backend;
397
+ if (installed.scope === candidate.child.options.scope || installed.agent !== candidate.target.agent
398
+ || backend?.skill !== candidate.target.skill)
399
+ continue;
400
+ if (installed.pluginId === candidate.target.providerPluginId && backend.logicalIdentity === candidate.prepared.logicalIdentity)
401
+ continue;
402
+ const conflict = diagnostic('crossScopeSkillCollision', `${candidate.target.providerPluginId}@${candidate.child.options.scope} 与 ${installed.pluginId}@${installed.scope} 的 Skill ${candidate.target.skill} 来源不兼容。`);
403
+ candidate.child.receipt.targetResults.find((target) => target.pluginId === candidate.prepared.options.selector && target.agent === candidate.target.agent)?.blockers.push(conflict);
404
+ plan.receipt.blockers.push(conflict);
405
+ }
406
+ const retire = currentStatus.extraObserved.filter((item) => item.agent !== null && !requiredNativeActivation(item) && (options.retireUnselectedTargets && (!snapshot.preferences.targets.includes(item.agent)
407
+ || snapshot.preferences.exclusions.some((entry) => entry.plugin === item.pluginId && entry.target === item.agent))
408
+ || command === 'uninstall' && requested.includes(item.pluginId)
409
+ || selected.some((placement) => placement.pluginId === item.pluginId && placement.target === item.agent && placement.scope !== item.scope
410
+ && (item.delivery?.kind !== 'agent-skills' || item.backend?.logicalIdentity === placement.plugin.catalogLogicalIdentity))));
411
+ for (const item of currentStatus.observedTargets ?? []) {
412
+ if (ownsOutput(item) && !requiredNativeActivation(item) && selected.some((placement) => placement.pluginId === item.pluginId
413
+ && placement.target === item.agent && placement.scope !== item.scope)
414
+ && !demand.placements.some((placement) => placement.pluginId === item.pluginId
415
+ && placement.target === item.agent && placement.scope === item.scope))
416
+ retire.push(item);
417
+ }
418
+ // A removed declaration is still present in the observed snapshot, so include its exact old outputs.
419
+ if (command === 'uninstall')
420
+ for (const item of currentStatus.inventory?.flatMap((entry) => entry.targetResults) ?? []) {
421
+ if (!requested.includes(item.pluginId) || item.scope !== scope || !ownsOutput(item)
422
+ || demand.placements.some((placement) => placement.pluginId === item.pluginId && placement.target === item.agent && placement.scope === item.scope))
423
+ continue;
424
+ retire.push(item);
425
+ }
426
+ // Conflicting replacements are filtered before child preparation. Keep the
427
+ // affected plugins' old outputs even when that leaves no deployment child.
428
+ const blockedReplacementPlugins = new Set(demand.conflicts.flatMap((conflict) => [...conflict.left.plugins, ...conflict.right.plugins]));
429
+ const safeRetirements = retire.filter((item) => !blockedReplacementPlugins.has(item.pluginId));
430
+ const retiringKeys = new Set(safeRetirements.map((item) => `${item.pluginId}\0${item.agent}\0${item.scope}`));
431
+ const remainingPositions = currentStatus.extraObserved.filter((item) => item.agent !== null
432
+ && !retiringKeys.has(`${item.pluginId}\0${item.agent}\0${item.scope}`))
433
+ .map((item) => ({ pluginId: item.pluginId, target: item.agent, scope: item.scope }));
434
+ const remainingConflicts = findPlacementConflicts([...demand.placements, ...remainingPositions]);
435
+ if (remainingConflicts.length) {
436
+ plan.receipt.blockers.push(...remainingConflicts);
437
+ for (const child of children)
438
+ for (const target of child.receipt.targetResults) {
439
+ if (remainingConflicts.some((conflict) => [conflict.left, conflict.right].some((position) => position.target === target.agent && position.scope === target.scope)))
440
+ target.blockers.push(diagnostic('targetPlacementConflict'));
441
+ }
442
+ }
443
+ const deploymentBlocked = children.some((child) => child.blockExecution
444
+ || child.receipt.targetResults.some((item) => item.blockers.length));
445
+ if (!plan.blockExecution && !deploymentBlocked) {
446
+ const cleanups = [];
447
+ for (const item of [...new Map(safeRetirements.map((entry) => [`${entry.pluginId}\0${entry.agent}\0${entry.scope}`, entry])).values()]) {
448
+ if (!item.agent)
449
+ continue;
450
+ const cleanupDemand = item.scope === 'project' ? { ...snapshot.project,
451
+ userProviders: Object.fromEntries((snapshot.preferences?.targets ?? []).map((target) => [target,
452
+ demand.placements.filter((placement) => placement.scope === 'user' && placement.target === target)
453
+ .map((placement) => placement.pluginId)])), } : snapshot.user;
454
+ const cleanup = await prepareScopedRecovery('cleanup', [item.pluginId], { ...options,
455
+ scope: item.scope, cwd: snapshot.context.projectRoot ?? options.cwd, targets: [item.agent], all: false,
456
+ exactTargets: undefined, demandOverride: cleanupDemand }, runtime);
457
+ cleanups.push(cleanup);
458
+ }
459
+ children.unshift(...cleanups);
460
+ }
461
+ plan.sourceRefreshes = [...new Map(children.flatMap((child) => child.sourceRefreshes).map((item) => [item.sourceId, item])).values()];
462
+ plan.sourceRefreshFailures = [...new Map(children.flatMap((child) => child.sourceRefreshFailures).map((item) => [item.sourceId, item])).values()];
463
+ plan.changes.push(...children.flatMap((child) => child.changes));
464
+ plan.warnings.push(...children.flatMap((child) => child.warnings));
465
+ plan.plannedImpact.outputs = children.some((child) => child.plannedImpact.outputs);
466
+ plan.receipt = mergeReceipts(plan, children.map((child) => child.receipt));
467
+ const observedManager = children.flatMap((child) => child.nativeGroups)
468
+ .map((group) => group.preview.codexManagerPreview)
469
+ .find((entry) => entry?.state === 'system' || entry?.state === 'cached');
470
+ if (observedManager?.state === 'system' || observedManager?.state === 'cached') {
471
+ runtime.codexObservationSession?.setManager(observedManager.manager);
472
+ }
473
+ return plan;
474
+ }
475
+ catch (error) {
476
+ await context.dispose();
477
+ throw error;
478
+ }
479
+ }
480
+ export async function executeJointRecovery(plan, execution) {
481
+ const joint = plan.joint;
482
+ try {
483
+ if (plan.sourceRefreshes.length) {
484
+ plan.receipt.blockers.push(diagnostic('sourceRefreshConfirmationRequired'));
485
+ return mergeReceipts(plan, joint.children.map((child) => child.receipt));
486
+ }
487
+ return await withAwHomeMutationLock({ env: plan.options.env, token: execution.runtime?.awHomeMutationToken ?? plan.runtime.awHomeMutationToken,
488
+ action: async (token) => {
489
+ const actual = await readJointDemand(joint.originalOptions);
490
+ const currentIntent = (plan.options.scope === 'project' ? actual.project : actual.user).intent;
491
+ plan.receipt.intent = intentReceipt(currentIntent);
492
+ if (currentIntent.state !== 'valid' && currentIntent.state !== 'absent') {
493
+ plan.receipt.blockers.push(describeStrongStateProblem({ kind: 'intent', scope: plan.options.scope,
494
+ state: currentIntent.state, filePath: await resolveIntentPath(plan.options) }));
495
+ }
496
+ if (snapshotEvidence(actual) !== joint.evidence) {
497
+ plan.receipt.blockers.push(diagnostic('operationReviewStale'));
498
+ return mergeReceipts(plan, []);
499
+ }
500
+ if (plan.blockExecution)
501
+ return plan.receipt;
502
+ // Recheck all backend candidates before any strong-state or output write.
503
+ (execution.runtime?.codexObservationSession ?? plan.runtime.codexObservationSession)?.invalidate();
504
+ const checked = await prepareJointRecovery(plan.command, joint.selectors, joint.originalOptions, { ...plan.runtime, ...execution.runtime, awHomeMutationToken: token });
505
+ const stable = !checked.blockExecution && JSON.stringify(describeRecoveryPlan(plan)) === JSON.stringify(describeRecoveryPlan(checked));
506
+ await checked.context.dispose();
507
+ if (!stable) {
508
+ plan.receipt.blockers.push(diagnostic('operationReviewStale'));
509
+ return mergeReceipts(plan, []);
510
+ }
511
+ if (joint.declaration) {
512
+ const saved = await writeIntent({ ...plan.options, scope: joint.declaration.scope, token }, joint.declaration.value);
513
+ plan.receipt.declarationChanged = saved.changed;
514
+ const committed = await observeIntent({ ...plan.options, scope: joint.declaration.scope });
515
+ plan.receipt.intent = intentReceipt(committed);
516
+ if (committed.state !== 'valid') {
517
+ plan.receipt.blockers.push(committed.state === 'absent'
518
+ ? diagnostic('pluginIntentStateUnavailable', '保存后未找到插件声明,未执行输出变更。')
519
+ : describeStrongStateProblem({ kind: 'intent', scope: joint.declaration.scope,
520
+ state: committed.state, filePath: await resolveIntentPath({ ...plan.options, scope: joint.declaration.scope }) }));
521
+ return mergeReceipts(plan, []);
522
+ }
523
+ }
524
+ const results = joint.children.filter((child) => child.blockExecution
525
+ && child.receipt.requestedTargets.length === 0).map((child) => child.receipt);
526
+ const failedProviders = new Set();
527
+ const failedPackages = new Set();
528
+ const failedNodes = new Set();
529
+ const packageResults = [];
530
+ const runtime = { ...plan.runtime, ...execution.runtime, awHomeMutationToken: token };
531
+ runtime.preApplyExecutionCache ??= new Map();
532
+ runtime.managedCommandProducerCache ??= new Map();
533
+ const packages = runtime.configApplyPackages;
534
+ const failedCleanups = [];
535
+ const executeChild = async (child, pluginIds, agent) => {
536
+ const currentIntent = await observeIntent(child.options);
537
+ if (!joint.originalOptions.existingOnly)
538
+ child.demand.intent = currentIntent;
539
+ const fresh = await prepareScopedRecovery(child.command, typeof pluginIds === 'string' ? [pluginIds] : pluginIds, {
540
+ ...child.options, targets: agent === 'codex' || agent === 'claude' ? child.options.targets : [agent], all: false,
541
+ exactTargets: child.options.exactTargets?.filter((target) => (typeof pluginIds === 'string' ? [pluginIds] : pluginIds).includes(target.pluginId)),
542
+ }, runtime);
543
+ fresh.originalIntent = observationEvidence(currentIntent);
544
+ for (const target of fresh.receipt.targetResults) {
545
+ const reviewed = child.receipt.targetResults.find((entry) => entry.pluginId === target.pluginId && entry.agent === target.agent);
546
+ // Native/provider observations are refreshed after preceding nodes.
547
+ // Keep only joint constraints and an already failed script gate.
548
+ target.blockers.push(...(reviewed?.blockers ?? []).filter((error) => reviewed?.execution === 'failed'
549
+ || ['configApplyRequiresExistingOutput', 'crossScopeSkillCollision', 'targetPlacementConflict',
550
+ 'preApplyAuthorizationRequired', 'nativeToolAuthorizationRequired', 'providerSwitchPreApplyFailed'].includes(error.code)));
551
+ if (reviewed?.execution === 'failed')
552
+ target.execution = 'failed';
553
+ if (child.command !== 'cleanup' && failedCleanups.some((failed) => targetsConflict({ target: failed.agent, scope: failed.scope }, { target: target.agent, scope: target.scope })
554
+ || failed.agent === target.agent && (failed.pluginId === target.pluginId || joint.demand.roots.some((root) => root.pluginId === target.pluginId && root.target === target.agent && root.dependencies.includes(failed.pluginId))))) {
555
+ target.blockers.push(diagnostic('providerSwitchCleanupFailed'));
556
+ }
557
+ const dependencies = joint.demand.placements.filter((placement) => placement.target === target.agent
558
+ && placement.contributions.some((contribution) => contribution.path.includes(target.pluginId)));
559
+ if (failedPackages.has(`${target.pluginId}\0${target.scope}`) || failedPackages.has(`${target.pluginId}\0user`)
560
+ || dependencies.some((item) => failedPackages.has(`${item.pluginId}\0${item.scope}`))) {
561
+ target.blockers.push(diagnostic('commandPackageProviderNotSatisfied'));
562
+ }
563
+ const failedDependency = dependencies.find((placement) => failedProviders.has(`${placement.pluginId}\0${placement.target}\0${placement.scope}`));
564
+ if (failedDependency) {
565
+ target.blockers.push(diagnostic(target.scope === 'project' && failedDependency.scope === 'user'
566
+ ? 'userProviderNotSatisfied' : 'dependencyApplyFailed', `依赖提供者未完成。恢复后重试 aw plugin sync ${target.pluginId} --context ${target.scope} --target ${target.agent} --yes`));
567
+ }
568
+ }
569
+ const result = await executeScopedRecovery(fresh, { ...execution, runtime });
570
+ if (joint.originalOptions.existingOnly)
571
+ for (const target of result.targetResults) {
572
+ if (!target.blockers.some((error) => error.code === 'dependencyInstallFailed'))
573
+ continue;
574
+ target.blockers = target.blockers.map((error) => error.code === 'dependencyInstallFailed'
575
+ ? diagnostic('dependencyApplyFailed', `依赖提供者未完成。恢复后重试 aw plugin sync ${target.pluginId} --context ${target.scope} --target ${target.agent} --yes`)
576
+ : error);
577
+ if (target.execution === 'failed')
578
+ target.execution = 'not-run';
579
+ }
580
+ results.push(result);
581
+ return result;
582
+ };
583
+ // Provider changes can remove a working output. Complete the replacement's
584
+ // authorization and scripts first, sharing the normal execution cache.
585
+ const cleanupChildren = joint.children.filter((child) => child.command === 'cleanup');
586
+ const replacementFailures = new Set();
587
+ for (const child of joint.children.filter((child) => child.command !== 'cleanup')) {
588
+ for (const target of child.receipt.targetResults) {
589
+ if (child.options.nativeActivationOnlyTargets?.some((item) => item.pluginId === target.pluginId
590
+ && item.platform === target.agent && item.scope === target.scope))
591
+ continue;
592
+ if (!cleanupChildren.some((cleanup) => cleanup.receipt.requestedTargets.some((old) => old.pluginId === target.pluginId)))
593
+ continue;
594
+ const failure = target.blockers[0]?.code ?? await executeRecoveryPreApply({ ...child,
595
+ options: { ...child.options, yes: execution.yes ?? plan.options.yes },
596
+ }, target.pluginId, target.agent, runtime);
597
+ if (!failure)
598
+ continue;
599
+ target.blockers.push(diagnostic(failure));
600
+ if (failure !== 'preApplyAuthorizationRequired' && failure !== 'nativeToolAuthorizationRequired')
601
+ target.execution = 'failed';
602
+ replacementFailures.add(`${target.pluginId}\0${target.agent}`);
603
+ failedProviders.add(`${target.pluginId}\0${target.agent}\0${target.scope}`);
604
+ failedNodes.add(`${target.pluginId}\0${target.scope}`);
605
+ }
606
+ }
607
+ // Target exits can replace one Agent with another. Preserve the reviewed
608
+ // old output when its corresponding replacement cannot run.
609
+ for (const child of joint.children.filter((child) => child.command === 'cleanup')) {
610
+ for (const target of child.receipt.requestedTargets) {
611
+ if ([...replacementFailures].some((failed) => failed.startsWith(`${target.pluginId}\0`)
612
+ && (failed === `${target.pluginId}\0${target.agent}`
613
+ || !joint.children.some((replacement) => replacement.command !== 'cleanup'
614
+ && replacement.receipt.requestedTargets.some((entry) => entry.pluginId === target.pluginId && entry.agent === target.agent))))) {
615
+ child.receipt.targetResults.find((entry) => entry.pluginId === target.pluginId && entry.agent === target.agent)
616
+ .blockers.push(diagnostic('providerSwitchPreApplyFailed'));
617
+ }
618
+ const result = await executeChild(child, target.pluginId, target.agent);
619
+ if (!result.ok)
620
+ failedCleanups.push(...result.requestedTargets);
621
+ }
622
+ }
623
+ const nodes = new Map();
624
+ const nodeFor = (pluginId, scope) => {
625
+ const key = `${pluginId}\0${scope}`;
626
+ let node = nodes.get(key);
627
+ if (!node) {
628
+ node = { pluginId, scope, targets: [], dependencies: new Set() };
629
+ nodes.set(key, node);
630
+ }
631
+ return node;
632
+ };
633
+ for (const child of joint.children.filter((child) => child.command !== 'cleanup')) {
634
+ for (const target of child.receipt.requestedTargets)
635
+ nodeFor(target.pluginId, target.scope).targets.push({ child, agent: target.agent });
636
+ }
637
+ const hasNative = (node) => node.targets.some(({ agent }) => agent === 'codex' || agent === 'claude');
638
+ for (const target of packages?.targets ?? []) {
639
+ const sameScope = nodes.get(`${target.pluginId}\0${target.scope}`);
640
+ const native = sameScope && hasNative(sameScope) ? sameScope
641
+ : [...nodes.values()].find((node) => node.pluginId === target.pluginId && hasNative(node));
642
+ const node = native ?? nodeFor(target.pluginId, target.scope);
643
+ (node.packages ??= []).push(target);
644
+ }
645
+ for (const node of nodes.values()) {
646
+ for (const { agent } of node.targets) {
647
+ const plugin = joint.demand.placements.find((item) => item.pluginId === node.pluginId && item.target === agent)?.plugin;
648
+ for (const name of plugin?.manifest.dependencies ?? []) {
649
+ const pluginId = `${plugin.sourceId}/${name}`;
650
+ const provider = joint.demand.placements.find((item) => item.pluginId === pluginId && item.target === agent);
651
+ if (provider)
652
+ node.dependencies.add(`${pluginId}\0${provider.scope}`);
653
+ }
654
+ }
655
+ if (node.scope === 'project' && nodes.has(`${node.pluginId}\0user`))
656
+ node.dependencies.add(`${node.pluginId}\0user`);
657
+ for (const dependency of node.packages?.flatMap((item) => item.dependencies) ?? []) {
658
+ for (const provider of nodes.values())
659
+ if (provider.pluginId === dependency)
660
+ node.dependencies.add(`${dependency}\0${provider.scope}`);
661
+ }
662
+ }
663
+ const pending = [...nodes.values()].sort((left, right) => left.scope === right.scope
664
+ ? left.pluginId.localeCompare(right.pluginId) : left.scope === 'user' ? -1 : 1);
665
+ const executedNativeChildren = new Set();
666
+ const completedPackages = new Map();
667
+ const recordPackages = (pluginId, scope, produced) => {
668
+ const key = `${pluginId}\0${scope}`;
669
+ completedPackages.set(key, produced);
670
+ packageResults.push(...produced);
671
+ if (produced.some((result) => result.state !== 'completed')) {
672
+ failedPackages.add(key);
673
+ failedNodes.add(key);
674
+ }
675
+ return produced;
676
+ };
677
+ const applyPackages = async (pluginId, scope) => {
678
+ const key = `${pluginId}\0${scope}`;
679
+ const previous = completedPackages.get(key);
680
+ if (previous)
681
+ return previous;
682
+ return recordPackages(pluginId, scope, await packages.execute(pluginId, scope, token));
683
+ };
684
+ const blockPackages = (pluginId, scope, code) => completedPackages.get(`${pluginId}\0${scope}`)
685
+ ?? recordPackages(pluginId, scope, packages?.blocked?.(pluginId, scope, code) ?? []);
686
+ if (packages)
687
+ runtime.nativeTargetExecution = {
688
+ beforeApply: async (identity) => {
689
+ const results = [];
690
+ for (const target of packages.targets.filter((target) => target.pluginId === identity.pluginId
691
+ && (target.scope === 'user' || target.scope === identity.scope)))
692
+ results.push(...await applyPackages(target.pluginId, target.scope));
693
+ return results;
694
+ },
695
+ blocked: (identity, code) => packages.targets.filter((target) => target.pluginId === identity.pluginId
696
+ && (target.scope === 'user' || target.scope === identity.scope)).flatMap((target) => blockPackages(target.pluginId, target.scope, code === 'dependencyInstallFailed' ? 'dependencyApplyFailed' : code)),
697
+ };
698
+ while (pending.length) {
699
+ const index = pending.findIndex((node) => {
700
+ const groups = node.targets.filter(({ child, agent }) => (agent === 'codex' || agent === 'claude') && !executedNativeChildren.has(child));
701
+ const members = new Set([node, ...groups.flatMap(({ child }) => pending.filter((candidate) => candidate.targets.some((target) => target.child === child)))]);
702
+ return [...members].every((member) => [...member.dependencies].every((key) => !pending.some((candidate) => !members.has(candidate) && `${candidate.pluginId}\0${candidate.scope}` === key)));
703
+ });
704
+ if (index < 0) {
705
+ plan.receipt.blockers.push(diagnostic('dependencyContractInvalid'));
706
+ break;
707
+ }
708
+ const node = pending.splice(index, 1)[0];
709
+ const key = `${node.pluginId}\0${node.scope}`;
710
+ if (node.packages?.length && packages && !hasNative(node)) {
711
+ if (failedNodes.has(key) || [...node.dependencies].some((dependency) => failedNodes.has(dependency))) {
712
+ failedPackages.add(key);
713
+ failedNodes.add(key);
714
+ for (const target of node.packages)
715
+ blockPackages(target.pluginId, target.scope, 'dependencyApplyFailed');
716
+ plan.receipt.blockers.push(diagnostic('commandPackageProviderNotSatisfied', node.pluginId));
717
+ }
718
+ else {
719
+ try {
720
+ const produced = [];
721
+ for (const target of node.packages)
722
+ produced.push(...await applyPackages(target.pluginId, target.scope));
723
+ if (produced.some((result) => result.state !== 'completed')) {
724
+ failedPackages.add(key);
725
+ failedNodes.add(key);
726
+ }
727
+ }
728
+ catch (error) {
729
+ runtime.signal?.throwIfAborted();
730
+ if (error instanceof Error && error.name === 'AbortError' || readSafeOperationError(error))
731
+ throw error;
732
+ failedPackages.add(key);
733
+ failedNodes.add(key);
734
+ plan.receipt.blockers.push(diagnostic('commandPackageApplyFailed', node.pluginId));
735
+ }
736
+ }
737
+ }
738
+ for (const { child, agent } of node.targets) {
739
+ const native = agent === 'codex' || agent === 'claude';
740
+ if (native && executedNativeChildren.has(child))
741
+ continue;
742
+ if (native)
743
+ executedNativeChildren.add(child);
744
+ const result = await executeChild(child, native ? child.receipt.requestedTargets.map((target) => target.pluginId) : node.pluginId, agent);
745
+ for (const target of result.targetResults)
746
+ if (target.blockers.length || !['applied', 'no-op'].includes(target.execution)) {
747
+ failedNodes.add(`${target.pluginId}\0${target.scope}`);
748
+ failedProviders.add(`${target.pluginId}\0${target.agent}\0${target.scope}`);
749
+ }
750
+ }
751
+ }
752
+ const receipt = mergeReceipts(plan, results);
753
+ receipt.commandResults = [...new Map([...packageResults, ...(receipt.commandResults ?? [])]
754
+ .map((result) => [getCommandPackageId(result.producer), result])).values()];
755
+ receipt.recoveryActions = [...new Set([...receipt.recoveryActions,
756
+ ...receipt.commandResults.flatMap((result) => result.recoveryActions)])];
757
+ receipt.ok &&= failedPackages.size === 0;
758
+ receipt.outputsMayHaveChanged ||= packageResults.some((result) => result.state === 'completed');
759
+ receipt.errors.push(...results.flatMap((result) => result.targetResults.flatMap((target) => target.blockers)));
760
+ const status = await readRecoveryStatus({ ...joint.originalOptions, nativeCliRunner: runtime.nativeCliRunner, codexObservationSession: runtime.codexObservationSession, targets: undefined });
761
+ receipt.allSatisfied = status.allSatisfied;
762
+ receipt.inSync = status.inSync;
763
+ return receipt;
764
+ } });
765
+ }
766
+ finally {
767
+ await plan.context.dispose();
768
+ }
769
+ }
770
+ //# sourceMappingURL=joint-coordinator.js.map