@lesliechan721/aw 0.1.0-beta.64 → 0.1.0-beta.66

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 (304) 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/catalog-publication-inspection.js +2 -2
  27. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  28. package/dist/src/commands/console.js +4 -3
  29. package/dist/src/commands/console.js.map +1 -1
  30. package/dist/src/commands/intent-command.d.ts +30 -0
  31. package/dist/src/commands/intent-command.js +164 -0
  32. package/dist/src/commands/intent-command.js.map +1 -0
  33. package/dist/src/commands/operation-plan.d.ts +2 -0
  34. package/dist/src/commands/operation-plan.js +2 -1
  35. package/dist/src/commands/operation-plan.js.map +1 -1
  36. package/dist/src/commands/options.d.ts +3 -11
  37. package/dist/src/commands/options.js +11 -17
  38. package/dist/src/commands/options.js.map +1 -1
  39. package/dist/src/commands/plugin-capabilities.js +27 -33
  40. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  41. package/dist/src/commands/rebuild-command.d.ts +108 -6
  42. package/dist/src/commands/rebuild-command.js +617 -1549
  43. package/dist/src/commands/rebuild-command.js.map +1 -1
  44. package/dist/src/commands/rebuild-integration.d.ts +19 -3
  45. package/dist/src/commands/rebuild-integration.js +500 -459
  46. package/dist/src/commands/rebuild-integration.js.map +1 -1
  47. package/dist/src/commands/rebuild-operations.js +1 -2
  48. package/dist/src/commands/rebuild-operations.js.map +1 -1
  49. package/dist/src/commands/review-inspection.d.ts +2 -2
  50. package/dist/src/commands/review-inspection.js +30 -86
  51. package/dist/src/commands/review-inspection.js.map +1 -1
  52. package/dist/src/commands/review-preparation.js +16 -7
  53. package/dist/src/commands/review-preparation.js.map +1 -1
  54. package/dist/src/commands/skill-eval.d.ts +2 -0
  55. package/dist/src/commands/skill-eval.js +15 -9
  56. package/dist/src/commands/skill-eval.js.map +1 -1
  57. package/dist/src/commands/status-v6.d.ts +38 -5
  58. package/dist/src/commands/status-v6.js +269 -102
  59. package/dist/src/commands/status-v6.js.map +1 -1
  60. package/dist/src/commands/targets-command.d.ts +27 -0
  61. package/dist/src/commands/targets-command.js +77 -0
  62. package/dist/src/commands/targets-command.js.map +1 -0
  63. package/dist/src/commands/write-commands.js +5 -5
  64. package/dist/src/commands/write-commands.js.map +1 -1
  65. package/dist/src/eval/manifest-v1.d.ts +2 -0
  66. package/dist/src/eval/manifest-v1.js +7 -2
  67. package/dist/src/eval/manifest-v1.js.map +1 -1
  68. package/dist/src/generators/local-plugin.d.ts +2 -1
  69. package/dist/src/generators/local-plugin.js +111 -108
  70. package/dist/src/generators/local-plugin.js.map +1 -1
  71. package/dist/src/index.d.ts +10 -17
  72. package/dist/src/index.js +585 -1349
  73. package/dist/src/index.js.map +1 -1
  74. package/dist/src/intent/deployment-preferences.d.ts +63 -0
  75. package/dist/src/intent/deployment-preferences.js +170 -0
  76. package/dist/src/intent/deployment-preferences.js.map +1 -0
  77. package/dist/src/intent/diagnostics.d.ts +11 -0
  78. package/dist/src/intent/diagnostics.js +14 -0
  79. package/dist/src/intent/diagnostics.js.map +1 -0
  80. package/dist/src/intent/intent-codec.d.ts +19 -0
  81. package/dist/src/intent/intent-codec.js +117 -0
  82. package/dist/src/intent/intent-codec.js.map +1 -0
  83. package/dist/src/intent/intent-store.d.ts +43 -0
  84. package/dist/src/intent/intent-store.js +118 -0
  85. package/dist/src/intent/intent-store.js.map +1 -0
  86. package/dist/src/intent/scope-context.d.ts +33 -0
  87. package/dist/src/intent/scope-context.js +145 -0
  88. package/dist/src/intent/scope-context.js.map +1 -0
  89. package/dist/src/intent/work-context.d.ts +13 -0
  90. package/dist/src/intent/work-context.js +22 -0
  91. package/dist/src/intent/work-context.js.map +1 -0
  92. package/dist/src/output/command-receipt.js +16 -9
  93. package/dist/src/output/command-receipt.js.map +1 -1
  94. package/dist/src/portable/commands.d.ts +18 -8
  95. package/dist/src/portable/commands.js +46 -562
  96. package/dist/src/portable/commands.js.map +1 -1
  97. package/dist/src/portable/historical-materialization.d.ts +22 -0
  98. package/dist/src/portable/historical-materialization.js +124 -0
  99. package/dist/src/portable/historical-materialization.js.map +1 -0
  100. package/dist/src/portable/materializer.d.ts +8 -0
  101. package/dist/src/portable/materializer.js +75 -44
  102. package/dist/src/portable/materializer.js.map +1 -1
  103. package/dist/src/portable/recovery-layout.d.ts +17 -0
  104. package/dist/src/portable/recovery-layout.js +55 -0
  105. package/dist/src/portable/recovery-layout.js.map +1 -0
  106. package/dist/src/portable/recovery.d.ts +143 -0
  107. package/dist/src/portable/recovery.js +339 -0
  108. package/dist/src/portable/recovery.js.map +1 -0
  109. package/dist/src/portable/writer.d.ts +64 -0
  110. package/dist/src/portable/writer.js +316 -0
  111. package/dist/src/portable/writer.js.map +1 -0
  112. package/dist/src/recovery/coordinator.d.ts +137 -0
  113. package/dist/src/recovery/coordinator.js +1198 -0
  114. package/dist/src/recovery/coordinator.js.map +1 -0
  115. package/dist/src/recovery/demand.d.ts +44 -0
  116. package/dist/src/recovery/demand.js +178 -0
  117. package/dist/src/recovery/demand.js.map +1 -0
  118. package/dist/src/recovery/joint-coordinator.d.ts +24 -0
  119. package/dist/src/recovery/joint-coordinator.js +770 -0
  120. package/dist/src/recovery/joint-coordinator.js.map +1 -0
  121. package/dist/src/recovery/joint-demand.d.ts +68 -0
  122. package/dist/src/recovery/joint-demand.js +166 -0
  123. package/dist/src/recovery/joint-demand.js.map +1 -0
  124. package/dist/src/recovery/receipt.d.ts +81 -0
  125. package/dist/src/recovery/receipt.js +2 -0
  126. package/dist/src/recovery/receipt.js.map +1 -0
  127. package/dist/src/recovery/retirement.d.ts +45 -0
  128. package/dist/src/recovery/retirement.js +366 -0
  129. package/dist/src/recovery/retirement.js.map +1 -0
  130. package/dist/src/recovery/status.d.ts +108 -0
  131. package/dist/src/recovery/status.js +417 -0
  132. package/dist/src/recovery/status.js.map +1 -0
  133. package/dist/src/recovery/types.d.ts +86 -0
  134. package/dist/src/recovery/types.js +2 -0
  135. package/dist/src/recovery/types.js.map +1 -0
  136. package/dist/src/review/review-profile.d.ts +2 -1
  137. package/dist/src/review/review-profile.js +15 -15
  138. package/dist/src/review/review-profile.js.map +1 -1
  139. package/dist/src/review/review-selection.d.ts +1 -2
  140. package/dist/src/review/review-selection.js +40 -114
  141. package/dist/src/review/review-selection.js.map +1 -1
  142. package/dist/src/review/review-types.d.ts +4 -4
  143. package/dist/src/review/review-types.js.map +1 -1
  144. package/dist/src/review/skill-materialization.js +15 -6
  145. package/dist/src/review/skill-materialization.js.map +1 -1
  146. package/dist/src/review/source-binding.d.ts +14 -4
  147. package/dist/src/review/source-binding.js +62 -117
  148. package/dist/src/review/source-binding.js.map +1 -1
  149. package/dist/src/tui/agent-management.d.ts +23 -0
  150. package/dist/src/tui/agent-management.js +217 -0
  151. package/dist/src/tui/agent-management.js.map +1 -0
  152. package/dist/src/tui/agent-settings.d.ts +44 -0
  153. package/dist/src/tui/agent-settings.js +98 -0
  154. package/dist/src/tui/agent-settings.js.map +1 -0
  155. package/dist/src/tui/components.d.ts +3 -0
  156. package/dist/src/tui/components.js +10 -1
  157. package/dist/src/tui/components.js.map +1 -1
  158. package/dist/src/tui/config-references.d.ts +6 -3
  159. package/dist/src/tui/config-references.js +17 -12
  160. package/dist/src/tui/config-references.js.map +1 -1
  161. package/dist/src/tui/console-app-v4.d.ts +1 -2
  162. package/dist/src/tui/console-app-v4.js +1533 -1047
  163. package/dist/src/tui/console-app-v4.js.map +1 -1
  164. package/dist/src/tui/controller-runtime.d.ts +2 -1
  165. package/dist/src/tui/controller-runtime.js +405 -1626
  166. package/dist/src/tui/controller-runtime.js.map +1 -1
  167. package/dist/src/tui/controller.d.ts +131 -81
  168. package/dist/src/tui/controller.js +307 -227
  169. package/dist/src/tui/controller.js.map +1 -1
  170. package/dist/src/tui/entity-cards.d.ts +2 -2
  171. package/dist/src/tui/entity-cards.js +98 -20
  172. package/dist/src/tui/entity-cards.js.map +1 -1
  173. package/dist/src/tui/glossary.d.ts +2 -0
  174. package/dist/src/tui/glossary.js +20 -0
  175. package/dist/src/tui/glossary.js.map +1 -1
  176. package/dist/src/tui/model.d.ts +28 -10
  177. package/dist/src/tui/model.js +41 -41
  178. package/dist/src/tui/model.js.map +1 -1
  179. package/dist/src/tui/operation-review-planner.d.ts +2 -0
  180. package/dist/src/tui/operation-review-planner.js +58 -122
  181. package/dist/src/tui/operation-review-planner.js.map +1 -1
  182. package/dist/src/tui/plugin-content.js +7 -4
  183. package/dist/src/tui/plugin-content.js.map +1 -1
  184. package/dist/src/tui/plugin-detail.d.ts +22 -3
  185. package/dist/src/tui/plugin-detail.js +113 -26
  186. package/dist/src/tui/plugin-detail.js.map +1 -1
  187. package/dist/src/tui/reader.d.ts +1 -0
  188. package/dist/src/tui/reader.js +3 -1
  189. package/dist/src/tui/reader.js.map +1 -1
  190. package/dist/src/tui/router.d.ts +23 -33
  191. package/dist/src/tui/router.js.map +1 -1
  192. package/dist/src/tui/scoped-flow.d.ts +45 -0
  193. package/dist/src/tui/scoped-flow.js +96 -0
  194. package/dist/src/tui/scoped-flow.js.map +1 -0
  195. package/dist/src/tui/scoped-inventory.d.ts +122 -0
  196. package/dist/src/tui/scoped-inventory.js +225 -0
  197. package/dist/src/tui/scoped-inventory.js.map +1 -0
  198. package/dist/src/tui/screen-registry.d.ts +25 -25
  199. package/dist/src/tui/screen-registry.js +3 -7
  200. package/dist/src/tui/screen-registry.js.map +1 -1
  201. package/dist/src/tui/session-console-app.js +1 -1
  202. package/dist/src/tui/session-console-app.js.map +1 -1
  203. package/dist/src/tui/session-host.js +12 -2
  204. package/dist/src/tui/session-host.js.map +1 -1
  205. package/dist/src/types.d.ts +15 -12
  206. package/dist/src/types.js.map +1 -1
  207. package/dist/src/utils/agent-version.js +2 -0
  208. package/dist/src/utils/agent-version.js.map +1 -1
  209. package/dist/src/utils/artifact-plan.js +13 -6
  210. package/dist/src/utils/artifact-plan.js.map +1 -1
  211. package/dist/src/utils/artifact-renderer.d.ts +6 -3
  212. package/dist/src/utils/artifact-renderer.js +27 -32
  213. package/dist/src/utils/artifact-renderer.js.map +1 -1
  214. package/dist/src/utils/artifact-request.d.ts +1 -0
  215. package/dist/src/utils/artifact-request.js +22 -12
  216. package/dist/src/utils/artifact-request.js.map +1 -1
  217. package/dist/src/utils/artifact-sources.d.ts +2 -2
  218. package/dist/src/utils/bundle-layout.js +18 -16
  219. package/dist/src/utils/bundle-layout.js.map +1 -1
  220. package/dist/src/utils/catalog-digests.js +2 -0
  221. package/dist/src/utils/catalog-digests.js.map +1 -1
  222. package/dist/src/utils/catalog-runtime-projection.d.ts +4 -3
  223. package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
  224. package/dist/src/utils/catalog-source-references.d.ts +5 -0
  225. package/dist/src/utils/catalog-source-references.js +42 -57
  226. package/dist/src/utils/catalog-source-references.js.map +1 -1
  227. package/dist/src/utils/claude-native-render-receipt.d.ts +14 -0
  228. package/dist/src/utils/claude-native-render-receipt.js +26 -0
  229. package/dist/src/utils/claude-native-render-receipt.js.map +1 -0
  230. package/dist/src/utils/claude-skills-root.js +14 -3
  231. package/dist/src/utils/claude-skills-root.js.map +1 -1
  232. package/dist/src/utils/codex-manager.js +6 -12
  233. package/dist/src/utils/codex-manager.js.map +1 -1
  234. package/dist/src/utils/codex-native-observation.js +29 -13
  235. package/dist/src/utils/codex-native-observation.js.map +1 -1
  236. package/dist/src/utils/codex-reconciler.d.ts +2 -5
  237. package/dist/src/utils/codex-reconciler.js +25 -1
  238. package/dist/src/utils/codex-reconciler.js.map +1 -1
  239. package/dist/src/utils/codex-surface-analysis.js +5 -3
  240. package/dist/src/utils/codex-surface-analysis.js.map +1 -1
  241. package/dist/src/utils/local-marketplace.d.ts +14 -2
  242. package/dist/src/utils/local-marketplace.js +209 -42
  243. package/dist/src/utils/local-marketplace.js.map +1 -1
  244. package/dist/src/utils/managed-command-evidence.js +1 -1
  245. package/dist/src/utils/managed-command-evidence.js.map +1 -1
  246. package/dist/src/utils/managed-command-materialization.d.ts +4 -0
  247. package/dist/src/utils/managed-command-materialization.js +15 -6
  248. package/dist/src/utils/managed-command-materialization.js.map +1 -1
  249. package/dist/src/utils/managed-command-store.d.ts +15 -0
  250. package/dist/src/utils/managed-command-store.js +73 -8
  251. package/dist/src/utils/managed-command-store.js.map +1 -1
  252. package/dist/src/utils/native-cli.d.ts +2 -0
  253. package/dist/src/utils/native-cli.js +22 -0
  254. package/dist/src/utils/native-cli.js.map +1 -1
  255. package/dist/src/utils/native-demand.d.ts +10 -0
  256. package/dist/src/utils/native-demand.js +4 -0
  257. package/dist/src/utils/native-demand.js.map +1 -0
  258. package/dist/src/utils/ownership.js +2 -0
  259. package/dist/src/utils/ownership.js.map +1 -1
  260. package/dist/src/utils/paths.js +1 -1
  261. package/dist/src/utils/paths.js.map +1 -1
  262. package/dist/src/utils/platform-support.d.ts +1 -1
  263. package/dist/src/utils/platform-support.js +1 -1
  264. package/dist/src/utils/platform-support.js.map +1 -1
  265. package/dist/src/utils/plugin-config-impact.d.ts +3 -0
  266. package/dist/src/utils/plugin-config-impact.js +115 -273
  267. package/dist/src/utils/plugin-config-impact.js.map +1 -1
  268. package/dist/src/utils/plugin-config-references.d.ts +9 -2
  269. package/dist/src/utils/plugin-config-references.js +75 -85
  270. package/dist/src/utils/plugin-config-references.js.map +1 -1
  271. package/dist/src/utils/plugin-config-write.d.ts +58 -1
  272. package/dist/src/utils/plugin-config-write.js +756 -809
  273. package/dist/src/utils/plugin-config-write.js.map +1 -1
  274. package/dist/src/utils/plugin-config.d.ts +47 -46
  275. package/dist/src/utils/plugin-config.js +292 -833
  276. package/dist/src/utils/plugin-config.js.map +1 -1
  277. package/dist/src/utils/plugin-scripts.d.ts +11 -6
  278. package/dist/src/utils/plugin-scripts.js +52 -35
  279. package/dist/src/utils/plugin-scripts.js.map +1 -1
  280. package/dist/src/utils/runtime-config.js +5 -4
  281. package/dist/src/utils/runtime-config.js.map +1 -1
  282. package/dist/src/utils/runtime-skill-closure.d.ts +2 -2
  283. package/dist/src/utils/runtime-skill-closure.js.map +1 -1
  284. package/dist/src/utils/state-paths.d.ts +8 -3
  285. package/dist/src/utils/state-paths.js +53 -16
  286. package/dist/src/utils/state-paths.js.map +1 -1
  287. package/dist/src/utils/windows-npm-shim.d.ts +2 -0
  288. package/dist/src/utils/windows-npm-shim.js +5 -7
  289. package/dist/src/utils/windows-npm-shim.js.map +1 -1
  290. package/dist/vitest.config.js +1 -0
  291. package/dist/vitest.config.js.map +1 -1
  292. package/package.json +3 -3
  293. package/dist/src/commands/sync-command.d.ts +0 -40
  294. package/dist/src/commands/sync-command.js +0 -254
  295. package/dist/src/commands/sync-command.js.map +0 -1
  296. package/dist/src/portable/skills-cli.d.ts +0 -51
  297. package/dist/src/portable/skills-cli.js +0 -202
  298. package/dist/src/portable/skills-cli.js.map +0 -1
  299. package/dist/src/tui/project-inventory.d.ts +0 -68
  300. package/dist/src/tui/project-inventory.js +0 -203
  301. package/dist/src/tui/project-inventory.js.map +0 -1
  302. package/dist/src/utils/plugin-intent-state.d.ts +0 -49
  303. package/dist/src/utils/plugin-intent-state.js +0 -320
  304. package/dist/src/utils/plugin-intent-state.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { configReferenceKey, configReferenceValues, configReferenceScalars, configReferenceSelection, configReferenceMessage, configReferenceSource, configCrossLayerDependencies } from './config-references.js';
3
3
  import { arrayDraftChanged, arrayDraftValue, createConfigArrayDraft, editConfigArrayDraft } from './config-array-draft.js';
4
- import { PLUGIN_CONFIG_ARRAY_MAX_ITEMS } from '@lesliechan721/aw-plugin-core';
4
+ import { PLATFORM_TARGETS, PLUGIN_CONFIG_ARRAY_MAX_ITEMS } from '@lesliechan721/aw-plugin-core';
5
5
  import { presentConfigValue, safeConfigText } from './config-value-presentation.js';
6
6
  import { ConfigLeafEditor } from './config-leaf-editor.js';
7
7
  import { Box, Text, useInput, useWindowSize } from 'ink';
8
+ import { AgentManagement } from './agent-management.js';
8
9
  import { useEffect, useLayoutEffect, useRef, useState } from 'react';
9
10
  import path from 'node:path';
10
11
  import { getCommandPackageId, resolvePluginConfigObjectProperties, validatePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
@@ -15,7 +16,7 @@ import { normalizePortableAgents, SKILLS_CLI_SUPPORTED_VERSION_LABEL, } from '..
15
16
  import { selectLifecycleRetryAction, } from '../commands/operation-planner.js';
16
17
  import { parseScriptTimeoutMs, SCRIPT_TIMEOUT_DEFAULT_MS, SCRIPT_TIMEOUT_MAX_MS, SCRIPT_TIMEOUT_MIN_MS, } from '../commands/options.js';
17
18
  import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
18
- import { ActionRow, ConfigTile, configTileHeight, HomeNavTile, KeyCap, ObjectCardFrame, ObjectCardRow, objectCardRowLayout, pluginListRowLayout, StatusBadge, diagnosticDetailLines, diagnosticDetailText, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
19
+ import { ActionRow, AgentStatusText, ConfigTile, configTileHeight, HomeNavTile, KeyCap, ObjectCardFrame, ObjectCardRow, objectCardRowLayout, pluginListRowLayout, StatusBadge, diagnosticDetailLines, diagnosticDetailText, Callout, ChoiceRow, ContentViewport, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
19
20
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
20
21
  import { extractPortableError, TuiConfigInvalidStoreError, userIssueKey, } from './controller.js';
21
22
  import { pluginWriteTaskVersionEvidence } from './plugin-write-task.js';
@@ -25,7 +26,7 @@ import { copyToClipboard } from './clipboard.js';
25
26
  import { resolveTuiSizePolicy } from './layout.js';
26
27
  import { buildDependencyGraphProjection, buildUnifiedDependencyGraphProjection, } from './dependency-graph.js';
27
28
  import { DependencyGraphView, projectDependencyGraphView } from './dependency-graph-view.js';
28
- import { platformLabel, scopeLabel } from './glossary.js';
29
+ import { agentLabel, platformLabel, scopeLabel, recoveryReason } from './glossary.js';
29
30
  import { InputDisplay, removeLastGrapheme } from './input-display.js';
30
31
  import { QueryHeader, querySearchWidth } from './query-header.js';
31
32
  import { Pager, Reader, paginateWrappedRows, readerPage } from './reader.js';
@@ -34,18 +35,239 @@ import { ContentItemCard, contentItemCardLayout, contentItemCardWindowHeight, co
34
35
  import { SourceDetailCard, sourceDetailCardLayout } from './source-detail-card.js';
35
36
  import { catalogRefPolicyForPreset } from './source-ref-presets.js';
36
37
  import { buildPlatformActions, configObjectHasSensitiveChild, hasInstalledPlacement, configValuesEqual, } from './model.js';
38
+ import { managedCommandPlanChanges } from './operation-review-planner.js';
37
39
  import { resolveTuiGlobalKeyAction, resolveTuiSessionNavigationKey } from './keys.js';
38
40
  import { tuiTheme } from './theme.js';
39
41
  import { contentRowBudget, rowViewport, wrapDisplayText } from './viewport.js';
40
42
  import { resolveRouteScreenId, routeDefinition, TUI_SCREEN_IDS, unreachableTuiRoute, } from './screen-registry.js';
41
43
  export const CONSOLE_V4_SCREEN_IDS = TUI_SCREEN_IDS;
44
+ function splitTaskLines(context) {
45
+ const targetLabel = (target) => `${target.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
46
+ return [
47
+ `完整任务请求:${context.targets.length} 个位置`,
48
+ ...context.targets.map((target) => `请求:${targetLabel(target)}`),
49
+ `本次范围:${scopeLabel(context.currentScope)}`,
50
+ ...context.targets.filter((target) => target.scope !== context.currentScope)
51
+ .map((target) => `其余范围未处理:${targetLabel(target)}`),
52
+ ];
53
+ }
54
+ function scopedRiskReasons(plan) {
55
+ if (plan.kind === 'targets' || plan.kind === 'intent')
56
+ return [];
57
+ if (plan.kind === 'retire')
58
+ return [
59
+ ...(plan.plan.options.deleteConfig ? ['本次会删除此插件的全部本机设置(通用与各 Agent),影响所有项目。'] : []),
60
+ ...(plan.scope === 'user' && plan.plan.plannedImpact.outputs
61
+ ? ['本次会清理个人原生输出,可能影响其他项目。'] : []),
62
+ ];
63
+ const reasons = [];
64
+ const visit = (entry) => {
65
+ reasons.push(...entry.nativeGroups.flatMap((group) => (group.preview.codexReconcilePlans ?? []).filter((item) => item.review.requiresConfirmation)
66
+ .map((item) => item.review.sharedImpact.warning
67
+ ?? '本次共享原生内容变更可能影响其他使用位置。')));
68
+ if (entry.receipt.scope === 'user' && entry.portableGroups.length > 0) {
69
+ reasons.push('本次可能修改所选工具的个人 Skills 目录,适用于所有项目。');
70
+ }
71
+ for (const refresh of entry.sourceRefreshes ?? []) {
72
+ if (!refresh.impact.inventoryComplete) {
73
+ reasons.push(`${refresh.sourceId} 的来源影响清单不完整;必须明确确认来源变更。`);
74
+ }
75
+ else if (refresh.impact.affectedReferences.length > 0) {
76
+ reasons.push(`${refresh.sourceId} 的来源变更会影响 ${refresh.impact.affectedReferences.length} 个已知引用。`);
77
+ }
78
+ }
79
+ for (const child of entry.joint?.children ?? [])
80
+ visit(child);
81
+ };
82
+ visit(plan.plan);
83
+ if (plan.plan.command === 'cleanup' && plan.plan.options.force) {
84
+ reasons.push('本次受限强制清理只删除已确认的专属目标;依赖此插件的其它目标可能需要恢复。');
85
+ }
86
+ return [...new Set(reasons)];
87
+ }
88
+ function recoveryPluginLabel(id) {
89
+ const [source, ...name] = id.split('/');
90
+ return name.length ? `${name.join('/')}(${source})` : id;
91
+ }
92
+ function recoveryTargetLines(receipt, result) {
93
+ const groups = new Map();
94
+ let unchanged = 0;
95
+ for (const target of receipt.targetResults) {
96
+ let action;
97
+ if (result) {
98
+ action = target.satisfaction === 'unsupported' || target.satisfaction === 'excluded' ? '! 已跳过'
99
+ : target.execution === 'applied' || target.execution === 'no-op' ? '✓ 完成'
100
+ : target.execution === 'failed' || target.execution === 'partial' ? '× 安装失败'
101
+ : target.blockers.length ? '! 已跳过' : '? 尚未确认';
102
+ }
103
+ else if (target.blockers.length || target.satisfaction === 'excluded' || target.satisfaction === 'unsupported') {
104
+ action = '! 跳过';
105
+ }
106
+ else if (receipt.command === 'cleanup' || receipt.command === 'uninstall' || receipt.command === 'retire') {
107
+ action = '− 移除';
108
+ }
109
+ else if ((target.content === 'satisfied' || target.observation === 'verified') && receipt.command !== 'reinstall') {
110
+ unchanged += 1;
111
+ continue;
112
+ }
113
+ else
114
+ action = target.content === 'absent' || target.observation === 'missing' ? '+ 新增安装' : '↻ 同步并修复';
115
+ const reason = target.blockers[0] || target.reasons?.[0] ? recoveryReason((target.blockers[0] ?? target.reasons[0]).code)
116
+ : target.satisfaction === 'excluded' ? '已关闭此 Agent'
117
+ : target.satisfaction === 'unsupported' ? '插件不支持此 Agent' : undefined;
118
+ const label = `${action} ${recoveryPluginLabel(target.pluginId)} ${scopeLabel(target.scope)}`;
119
+ const key = `${label}\0${reason ?? ''}`;
120
+ const group = groups.get(key) ?? { label: `${label}${reason ? ` · ${reason}` : ''}`, targets: [] };
121
+ const agent = agentLabel(target.agent);
122
+ if (!group.targets.includes(agent))
123
+ group.targets.push(agent);
124
+ groups.set(key, group);
125
+ }
126
+ return [...groups.values()].flatMap((group) => [group.label, ` → ${group.targets.join(' · ')}`])
127
+ .concat(unchanged ? [`✓ 另有 ${unchanged} 项已是最新`] : []);
128
+ }
129
+ function scopedPlanSummary(plan) {
130
+ if (plan.kind === 'targets')
131
+ return ['保存本机 Agent 选择',
132
+ `已选 ${plan.agents.map(agentLabel).join(' · ') || '无'}`, '已有输出保持不变;保存后可单独同步。'].join('\n');
133
+ if (plan.kind === 'intent')
134
+ return [
135
+ `${plan.action === 'add' ? '+ 加入插件清单' : '− 移出插件清单'} ${recoveryPluginLabel(plan.selector)}`,
136
+ scopeLabel(plan.scope), '已有安装内容保持不变。',
137
+ ].join('\n');
138
+ const receipt = plan.plan.receipt;
139
+ const lines = [receipt.coverage?.scopes.length === 2 ? '所有项目 + 当前项目' : scopeLabel(plan.scope),
140
+ ...recoveryTargetLines(receipt, false)];
141
+ if (plan.kind === 'recovery' && plan.plan.sourceRefreshes?.length) {
142
+ lines.unshift(...plan.plan.sourceRefreshes.map((refresh) => `↻ 启用来源 ${refresh.sourceId} 的候选版本`), '来源启用后,将重新准备插件操作汇总;本次不会直接更新插件。');
143
+ }
144
+ if (plan.plan.plannedImpact.declaration)
145
+ lines.push('插件清单也将保存本次变化。');
146
+ if (plan.plan.plannedImpact.config)
147
+ lines.push('− 删除所选插件的全部本机设置(通用与各 Agent)');
148
+ for (const change of receipt.nativeToolChanges ?? []) {
149
+ lines.push(`! 原生工具变更:${recoveryPluginLabel(`${change.producer.sourceId}/${change.producer.pluginName}`)}`);
150
+ for (const phase of ['before', 'after']) {
151
+ const closure = change[phase];
152
+ lines.push(` ${phase === 'before' ? '变更前' : '变更后'}:${closure?.definitions.map((tool) => tool.key).join(' · ') || '无'}`);
153
+ for (const consumer of closure?.consumers ?? [])
154
+ for (const tool of consumer.tools) {
155
+ lines.push(` ${consumer.key} 使用 ${tool.key}:${tool.policy === 'required' ? '必须可用' : '优先使用,可回退'}`);
156
+ }
157
+ }
158
+ }
159
+ if (plan.kind === 'recovery') {
160
+ const scripts = new Set();
161
+ const visit = (entry) => {
162
+ for (const script of [...(entry.preApplyAuthorization ?? []),
163
+ ...entry.nativeGroups.flatMap((group) => group.preview.preApplyAuthorization ?? [])]) {
164
+ scripts.add(`安装检查:${recoveryPluginLabel(script.pluginId)} · ${agentLabel(script.platform)} · ${scopeLabel(script.scope)}`);
165
+ }
166
+ for (const child of entry.joint?.children ?? [])
167
+ visit(child);
168
+ };
169
+ visit(plan.plan);
170
+ lines.push(...scripts);
171
+ }
172
+ lines.push(...scopedRiskReasons(plan).map((reason) => `! ${reason}`));
173
+ for (const diagnostic of [...receipt.errors, ...receipt.blockers])
174
+ lines.push(`! ${recoveryReason(diagnostic.code)}`);
175
+ return [...new Set(lines)].join('\n');
176
+ }
177
+ function scopedResultSummary(receipt) {
178
+ return [receipt.coverage?.scopes.length === 2 ? '所有项目 + 当前项目' : scopeLabel(receipt.scope), ...recoveryTargetLines(receipt, true),
179
+ ...(receipt.declarationChanged ? ['插件清单已保存。'] : []),
180
+ ...(receipt.preferencesChanged ? ['Agent 选择已保存。'] : []),
181
+ ...(receipt.configChanged ? ['插件设置已保存。'] : []),
182
+ ...[...new Set([...receipt.errors, ...receipt.blockers].map((entry) => recoveryReason(entry.code)))]
183
+ .map((reason) => `! ${reason}`),
184
+ ...(receipt.commandResults?.length ? [`命令包:${receipt.commandResults.filter((entry) => entry.errors.length === 0).length} 项完成,${receipt.commandResults.filter((entry) => entry.errors.length > 0).length} 项需处理;详情中可查看恢复方法。`] : []),
185
+ ].join('\n');
186
+ }
187
+ function scopedPlanText(plan, technical = false) {
188
+ if (!technical)
189
+ return scopedPlanSummary(plan);
190
+ if (plan.kind === 'targets')
191
+ return [
192
+ '偏好变更:', ...plan.changes, '需求变更:无', '配置变更:无', '输出变更:无',
193
+ ...plan.warnings,
194
+ ].join('\n');
195
+ if (plan.kind === 'intent')
196
+ return [
197
+ `范围:${plan.scope === 'user' ? '个人(所有项目)' : '当前项目'}`,
198
+ `完整请求:${plan.selector}`,
199
+ `需求变更:${plan.action === 'add' ? '添加' : '移除'} ${plan.selector}`,
200
+ '偏好变更:无', '配置变更:无', '输出变更:无',
201
+ ...plan.warnings,
202
+ ].join('\n');
203
+ const { receipt } = plan.plan;
204
+ const impact = plan.plan.plannedImpact;
205
+ const diagnostics = [...new Map([
206
+ ...receipt.errors.map((entry) => [`${entry.code}:${entry.message}`, `全局错误:${entry.code} · ${entry.message}`]),
207
+ ...receipt.blockers.map((entry) => [`${entry.code}:${entry.message}`, `全局受阻:${entry.code} · ${entry.message}`]),
208
+ ]).values()];
209
+ return [
210
+ `范围:${plan.scope === 'user' ? '个人(所有项目)' : '当前项目'}`,
211
+ `完整请求:${receipt.requestedRoots.join('、') || '无'}`,
212
+ `本机工具:${receipt.selection.agents.map(agentLabel).join('、') || '未选择'}`,
213
+ `需求变更(计划):${impact.declaration
214
+ ? plan.kind === 'retire' ? '删除当前范围声明' : '保存当前范围需求' : '无'}`,
215
+ `偏好变更(计划):${impact.preferences
216
+ ? plan.kind === 'retire' ? '删除当前范围本机依赖覆盖' : '保存当前范围明确选择的依赖偏好' : '无'}`,
217
+ `配置变更(计划):${impact.config ? '删除所选插件的全部本机配置(common 与所有平台)' : '无'}`,
218
+ `输出变更(计划):${impact.outputs ? '执行所列精确目标' : '无可执行输出变更'}`,
219
+ ...receipt.targetResults.map((target) => `${target.pluginId} · ${agentLabel(target.agent)} [${target.scope === 'user' ? '个人' : '当前项目'}] · ${target.delivery?.kind === 'native' ? '原生' : target.delivery?.kind === 'agent-skills' ? 'Skills' : '不支持'} · ${target.blockers.length
220
+ ? `受阻:${target.blockers.map((blocker) => blocker.code).join('、')}` : target.observation}`),
221
+ ...diagnostics,
222
+ ...receipt.targetResults.flatMap((target) => target.reasons?.map((reason) => `${reason.code} · ${reason.message}`) ?? []),
223
+ ...plan.plan.changes.map((change) => `影响:${change}`),
224
+ ...plan.plan.warnings.map((warning) => `注意:${warning}`),
225
+ ...(plan.kind === 'recovery' ? scopedCommandPlanText(plan) : []),
226
+ ...(plan.kind === 'retire' ? [`配置删除选项:${plan.plan.options.deleteConfig
227
+ ? impact.config ? '已选择' : '已选择;当前没有可删除配置' : '关闭(默认)'}`] : []),
228
+ ].join('\n');
229
+ }
230
+ function scopedCommandPlanText(plan) {
231
+ return managedCommandPlanChanges(plan.plan.nativeGroups.flatMap((group) => group.preview.operationPlan?.actions ?? []))
232
+ .map((change) => `${change.title}${change.description ? `:${change.description}` : ''}`);
233
+ }
234
+ function scopedResultText(receipt, technical = false) {
235
+ if (!technical)
236
+ return scopedResultSummary(receipt);
237
+ const diagnostics = [...new Map([
238
+ ...receipt.errors.map((entry) => [`${entry.code}:${entry.message}`, `全局错误:${entry.code} · ${entry.message}`]),
239
+ ...receipt.blockers.map((entry) => [`${entry.code}:${entry.message}`, `全局受阻:${entry.code} · ${entry.message}`]),
240
+ ]).values()];
241
+ const reviewStale = [...receipt.blockers, ...receipt.errors]
242
+ .some((entry) => entry.code === 'operationReviewStale');
243
+ return [
244
+ `范围:${receipt.scope === 'user' ? '个人(所有项目)' : '当前项目'}`,
245
+ `完整请求:${receipt.requestedRoots.join('、') || '无'}`,
246
+ `需求变更:${receipt.declarationChanged ? '是' : '无'}`,
247
+ `偏好变更:${receipt.preferencesChanged ? '是' : '无'}`,
248
+ `配置变更:${receipt.configChanged ? '是' : '无'}`,
249
+ `输出可能改变:${receipt.outputsMayHaveChanged ? '是' : '否'}`,
250
+ ...diagnostics,
251
+ ...receipt.targetResults.flatMap((target) => target.reasons?.map((reason) => `${reason.code} · ${reason.message}`) ?? []),
252
+ ...(reviewStale ? ['本次未执行,需要重新准备。'] : []),
253
+ ...receipt.targetResults.map((target) => `${target.pluginId} · ${agentLabel(target.agent)} [${target.scope === 'user' ? '个人' : '当前项目'}] · ${target.delivery?.kind === 'native' ? '原生' : target.delivery?.kind === 'agent-skills' ? 'Skills' : '无交付'} · ${target.execution} · ${target.observation}${target.blockers.length ? ` · 受阻 ${target.blockers.map((blocker) => blocker.code).join('、')}` : ''}`),
254
+ ...receipt.targetResults.flatMap((target) => target.recoveryActions?.map((action) => `${target.pluginId} · ${target.agent}:${action}`) ?? []),
255
+ ...(receipt.commandResults ?? []).flatMap((result) => [
256
+ `命令${result.producer.kind === 'package' ? '包' : '生产者'} ${result.producer.key} · ${result.producer.sourceId}/${result.producer.pluginName} · ${scopeLabel(result.producer.scope)}${result.producer.platform ? ` · ${platformLabel(result.producer.platform)}` : ''} · ${result.state}`,
257
+ ...(result.recoveryActions.some((action) => action.startsWith('aw command package remove '))
258
+ ? [result.producer.scope === 'user' ? '保留命令包:所有项目共用' : '保留命令包:当前项目内共用'] : []),
259
+ ...result.commandIds.map((id) => `命令:${id}`),
260
+ ...result.removedCommandIds.map((id) => `撤销命令:${id}`),
261
+ ...result.errors.map((error) => `命令错误:${error.code} · ${error.message}`),
262
+ ...result.recoveryActions.map((action) => `命令恢复:${action}`),
263
+ ]),
264
+ ...receipt.remainingTargets.map((target) => `未完成:${target.pluginId} · ${target.agent}`),
265
+ ...receipt.recoveryActions.map((action) => `恢复:${action}`),
266
+ ].join('\n');
267
+ }
42
268
  const PLUGIN_FILTERS = [
43
269
  'all', 'installed', 'uninstalled', 'updateAvailable', 'attention', 'project', 'user',
44
270
  ];
45
- const PROJECT_STATUS_FILTERS = ['all', 'pending', 'blocked', 'unknown', 'ready'];
46
- const PROJECT_STATUS_LABELS = {
47
- all: '全部', pending: '待同步', blocked: '受阻', unknown: '未确认', ready: '就绪',
48
- };
49
271
  function queryMatches(query, ...values) {
50
272
  const normalized = query.trim().toLocaleLowerCase();
51
273
  return !normalized || values.some((value) => value?.toLocaleLowerCase().includes(normalized));
@@ -129,16 +351,25 @@ function helpTextForPage(help, busy) {
129
351
  `q / Ctrl-C ${busy ? '操作完成后退出' : '退出控制台'}`,
130
352
  ...(!busy ? ['o 查看操作记录'] : []), 'Esc / ? 关闭帮助'].join('\n');
131
353
  }
132
- function projectDependencyLines(root) {
133
- return root?.dependencies.flatMap((dependency) => dependency.targets.map((target) => `依赖 ${dependency.displayName} · ${dependency.pluginId} · ${platformLabel(target.platform)} · ${target.location === 'sharedUser' ? '共用个人安装' : '当前项目'} · 目录参考 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'}${target.reasonCodes.length ? ` · ${target.reasonCodes.map((code) => safeDiagnosticCode(code, 'projectTargetUnknown')).join('、')}` : ''}`)) ?? [];
134
- }
135
354
  function configOwnerKey(route) {
136
355
  return route.pluginId;
137
356
  }
138
357
  function configPageMatches(page, route) {
139
358
  return Boolean(page && route
140
359
  && page.pluginId === route.pluginId
141
- && page.layer.scope === route.layer.scope);
360
+ && page.profile === route.layer.profile
361
+ && page.context === (route.layer.context ?? 'user'));
362
+ }
363
+ const CONFIG_PROFILE_KEYS = ['common', ...PLATFORM_TARGETS];
364
+ function configReadOptions(layer) {
365
+ return {
366
+ ...(layer.profile === 'common' ? {} : { platform: layer.profile }),
367
+ context: layer.context ?? 'user',
368
+ view: 'stored',
369
+ };
370
+ }
371
+ function configEditorOwnerKey(pluginId, layer) {
372
+ return `${pluginId}\0${layer.profile}\0${layer.context ?? 'user'}`;
142
373
  }
143
374
  function configFieldCanSet(field) {
144
375
  return field.canSet ?? field.writable;
@@ -226,30 +457,6 @@ function pluginPlatformBadges(plugin) {
226
457
  });
227
458
  return _jsxs(Box, { children: [_jsx(StatusBadge, { items: items }), plugin.portableSkills ? _jsx(Text, { color: tuiTheme.colors.accent, children: " [Skills \u53EF\u9009]" }) : null, plugin.configOnly ? _jsx(Text, { color: tuiTheme.colors.muted, children: "[\u5DF2\u4FDD\u5B58\u8BBE\u7F6E]" }) : null] });
228
459
  }
229
- function installedScopeStates(plugin, platform) {
230
- const states = new Map();
231
- const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
232
- if (!placement || placement.state === 'notInstalled' || placement.state === 'blocked')
233
- return states;
234
- const scoped = placement.state === 'scopeConflict'
235
- ? placement.placements
236
- : placement.scopedPlacements ?? [{
237
- scope: placement.scope,
238
- health: placement.health,
239
- revision: placement.revision,
240
- actualState: placement.actualState,
241
- }];
242
- for (const entry of scoped) {
243
- const actualState = 'actualState' in entry ? entry.actualState : undefined;
244
- const healthy = entry.health === 'ready' && actualState !== 'blocked'
245
- && actualState !== 'needsSync';
246
- states.set(entry.scope, healthy
247
- ? { symbol: '✓', status: 'success' }
248
- : { symbol: '!', status: actualState === 'blocked' || entry.health === 'error'
249
- ? 'error' : 'warning' });
250
- }
251
- return states;
252
- }
253
460
  function issueMatchesTarget(issue, pluginId, platform, scope) {
254
461
  if (!platform && !scope && issue.pluginId === pluginId)
255
462
  return true;
@@ -556,7 +763,8 @@ function configUnavailableDiagnostic(route) {
556
763
  { key: 'pluginId', value: route.pluginId },
557
764
  ...(route.invalidStores
558
765
  ? [{ key: 'scope', value: route.invalidStores.map((store) => store.scope).join(',') }]
559
- : route.layer ? [{ key: 'scope', value: route.layer.scope }] : []),
766
+ : route.layer ? [{ key: route.layer.profile === 'common' ? 'storeEntry' : 'platform',
767
+ value: route.layer.profile }] : []),
560
768
  ],
561
769
  checks: invalidLayer
562
770
  ? ['只删除所选 user 或 project 设置层。', '重置后重新读取继承值和默认值。']
@@ -892,7 +1100,8 @@ function dependencyRelationDescription(item) {
892
1100
  }
893
1101
  function dependencyEvidenceLines(row, subject) {
894
1102
  const references = row.edge?.availablePlacementReferences ?? [];
895
- const placementLines = subject?.placements.map((placement) => `安装位置 · ${platformLabel(placement.platform)} · ${scopeLabel(placement.scope)} · 实际安装版本 ${placement.installedVersion ?? '未确认'} · 观察 ${placement.observation === 'unknown' ? '未确认' : '已检查'}`) ?? [];
1103
+ const placementLines = subject?.placements.map((placement) => `安装位置 · ${placement.agent ? agentLabel(placement.agent)
1104
+ : placement.platform ? platformLabel(placement.platform) : '目标'}${placement.delivery ? ` · ${placement.delivery === 'native' ? '原生' : 'Skills'}` : ''} · ${scopeLabel(placement.scope)} · 实际安装版本 ${placement.installedVersion ?? '未确认'} · 观察 ${placement.observation === 'unknown' ? '未确认' : '已检查'}`) ?? [];
896
1105
  const referenceLines = (reference) => [
897
1106
  `consumer · ${reference.consumer.pluginId}`,
898
1107
  `consumer · platform=${platformLabel(reference.consumer.platform)} · scope=${reference.consumer.scope}`,
@@ -990,7 +1199,22 @@ export function ConsoleAppV4(props) {
990
1199
  const [helpPage, setHelpPage] = useState(1);
991
1200
  const [operationConfirmation, setOperationConfirmation] = useState();
992
1201
  const [sourceTransportOverlay, setSourceTransportOverlay] = useState();
993
- const [projectReminderOpen, setProjectReminderOpen] = useState(false);
1202
+ const [scopedFlow, setScopedFlow] = useState();
1203
+ const [agentSettings, setAgentSettings] = useState();
1204
+ const [agentSettingsError, setAgentSettingsError] = useState();
1205
+ const [agentWelcome, setAgentWelcome] = useState(false);
1206
+ const [agentWelcomeFocus, setAgentWelcomeFocus] = useState(0);
1207
+ const [configSavedFocus, setConfigSavedFocus] = useState(0);
1208
+ const agentWelcomeSeen = useRef(false);
1209
+ const scopedFlowToken = useRef(0);
1210
+ const scopedFlowBusy = useRef(false);
1211
+ const scopedSelected = useRef({});
1212
+ const scopedActiveRoot = useRef({});
1213
+ const scopedPageContext = useRef({});
1214
+ const [, setScopedSelectionRevision] = useState(0);
1215
+ const [scopedDiscardIntent, setScopedDiscardIntent] = useState();
1216
+ const scopedStatusRequested = useRef({});
1217
+ const [splitTaskReview, setSplitTaskReview] = useState();
994
1218
  const [discardForExit, setDiscardForExit] = useState(false);
995
1219
  const [discardForSessionNavigation, setDiscardForSessionNavigation] = useState();
996
1220
  const [discardDraftConfirmation, setDiscardDraftConfirmation] = useState();
@@ -1007,9 +1231,8 @@ export function ConsoleAppV4(props) {
1007
1231
  ?? (snapshot.router.current.route.name === 'pluginList'
1008
1232
  && snapshot.router.current.route.mode === 'installed' ? 'installed' : 'all'));
1009
1233
  const [scope, setScope] = useState('user');
1010
- const [selectedTargets, setSelectedTargets] = useState([]);
1011
- const [installPlatformScopes, setInstallPlatformScopes] = useState({});
1012
- const [installPlatformIndex, setInstallPlatformIndex] = useState(0);
1234
+ const [, setSelectedTargets] = useState([]);
1235
+ const [, setInstallPlatformScopes] = useState({});
1013
1236
  const [uninstallTargetKeys, setUninstallTargetKeys] = useState([]);
1014
1237
  const [technicalRouteKey, setTechnicalRouteKey] = useState();
1015
1238
  const [exitAfterBusy, setExitAfterBusy] = useState(false);
@@ -1017,12 +1240,13 @@ export function ConsoleAppV4(props) {
1017
1240
  const [, setClockTick] = useState(0);
1018
1241
  const [dirty, setDirty] = useState(false);
1019
1242
  const [configPage, setConfigPage] = useState();
1020
- const [configLayerCounts, setConfigLayerCounts] = useState({});
1021
1243
  const [configLayerPages, setConfigLayerPages] = useState({});
1022
1244
  const [configDrafts, setConfigDrafts] = useState({});
1023
1245
  const [referenceContexts, setReferenceContexts] = useState({});
1246
+ const [configReadRevision, setConfigReadRevision] = useState(0);
1024
1247
  const [referenceContextError, setReferenceContextError] = useState();
1025
1248
  const [configCandidate, setConfigCandidate] = useState();
1249
+ const [configSaveState, setConfigSaveState] = useState();
1026
1250
  const [arrayDrafts, setArrayDrafts] = useState({});
1027
1251
  const [arrayToolsOpen, setArrayToolsOpen] = useState(false);
1028
1252
  const [arrayToolFocus, setArrayToolFocus] = useState(0);
@@ -1085,6 +1309,29 @@ export function ConsoleAppV4(props) {
1085
1309
  const exitSent = useRef(false);
1086
1310
  const operationBusy = useRef(false);
1087
1311
  const route = snapshot.router.current.route;
1312
+ useEffect(() => {
1313
+ if (snapshot.phase !== 'ready')
1314
+ return;
1315
+ let active = true;
1316
+ void props.controller.readAgentSettings().then((settings) => {
1317
+ if (!active)
1318
+ return;
1319
+ setAgentSettings(settings);
1320
+ setAgentSettingsError(undefined);
1321
+ if (!agentWelcomeSeen.current && settings.preferences.targets.length === 0) {
1322
+ agentWelcomeSeen.current = true;
1323
+ setAgentWelcome(true);
1324
+ }
1325
+ }).catch(() => { if (active)
1326
+ setAgentSettingsError('无法读取 Agent 设置'); });
1327
+ return () => { active = false; };
1328
+ }, [props.controller, snapshot.phase]);
1329
+ useEffect(() => {
1330
+ if (route.name !== 'intentInventory' || scopedStatusRequested.current[route.scope])
1331
+ return;
1332
+ scopedStatusRequested.current[route.scope] = true;
1333
+ void props.controller.refreshScopedInventory(route.scope);
1334
+ }, [props.controller, route.name, route.name === 'intentInventory' ? route.scope : '']);
1088
1335
  const platformFacts = route.name === 'platform'
1089
1336
  ? props.controller.getPluginDetailSubject(route.pluginId)?.placements
1090
1337
  .filter((entry) => entry.platform === route.platform) ?? []
@@ -1099,15 +1346,13 @@ export function ConsoleAppV4(props) {
1099
1346
  ...(snapshot.router.currentFlowId ? { flowId: snapshot.router.currentFlowId } : {}),
1100
1347
  });
1101
1348
  }
1102
- const configDraftCount = ['project', 'user'].reduce((count, draftScope) => {
1103
- const draft = configDrafts[draftScope];
1349
+ const configDraftCount = CONFIG_PROFILE_KEYS.reduce((count, profile) => {
1350
+ const draft = configDrafts[profile];
1104
1351
  return count + Object.keys(draft?.updates ?? {}).length + (draft?.removes.length ?? 0);
1105
1352
  }, 0);
1106
- const editingScope = 'layer' in route && route.layer && route.layer.scope !== 'effective' ? route.layer.scope : undefined;
1107
- const draftUpdates = editingScope ? configDrafts[editingScope]?.updates ?? {} : {
1108
- ...(configDrafts.user?.updates ?? {}),
1109
- ...(configDrafts.project?.updates ?? {}),
1110
- };
1353
+ const editingProfile = 'layer' in route && route.layer ? route.layer.profile : undefined;
1354
+ const configContext = 'layer' in route && route.layer ? route.layer.context ?? 'user' : 'user';
1355
+ const draftUpdates = editingProfile ? configDrafts[editingProfile]?.updates ?? {} : {};
1111
1356
  const definition = routeDefinition(route);
1112
1357
  const rendererFamily = definition.rendererFamily;
1113
1358
  const routeKey = JSON.stringify(route);
@@ -1193,41 +1438,6 @@ export function ConsoleAppV4(props) {
1193
1438
  props.controller.dispose();
1194
1439
  };
1195
1440
  }, [props.controller, props.ownsController]);
1196
- useEffect(() => {
1197
- const reminder = snapshot.projectIntentReminder;
1198
- if (!props.onClaimProjectIntentReminder
1199
- || snapshot.phase !== 'ready'
1200
- || route.name !== 'home'
1201
- || !reminder
1202
- || reminder.actionableTargets.length + reminder.blockedTargets.length === 0
1203
- || overlay
1204
- || operationConfirmation
1205
- || sourceTransportOverlay
1206
- || dirty
1207
- || props.controller.hasSessionDraft)
1208
- return;
1209
- if (props.onClaimProjectIntentReminder(reminder.projectRoot, reminder.signature)) {
1210
- setOverlayFocus(0);
1211
- setProjectReminderOpen(true);
1212
- }
1213
- }, [
1214
- props.controller,
1215
- props.onClaimProjectIntentReminder,
1216
- snapshot.phase,
1217
- snapshot.projectIntentReminder?.signature,
1218
- route.name,
1219
- overlay,
1220
- operationConfirmation,
1221
- sourceTransportOverlay,
1222
- dirty,
1223
- ]);
1224
- useEffect(() => {
1225
- const reminder = snapshot.projectIntentReminder;
1226
- if (projectReminderOpen
1227
- && (!reminder || reminder.actionableTargets.length + reminder.blockedTargets.length === 0)) {
1228
- setProjectReminderOpen(false);
1229
- }
1230
- }, [projectReminderOpen, snapshot.projectIntentReminder]);
1231
1441
  useEffect(() => {
1232
1442
  if (route.name !== 'pluginContent' || snapshot.phase !== 'ready')
1233
1443
  return;
@@ -1258,13 +1468,12 @@ export function ConsoleAppV4(props) {
1258
1468
  || !snapshot.result.operationKind)
1259
1469
  return;
1260
1470
  if (snapshot.result.operationKind === 'configSaveBatch') {
1261
- const savedScopes = new Set(snapshot.result.summary.configGroups
1262
- ?.filter((group) => group.configSaved).map((group) => group.scope) ?? []);
1471
+ const savedProfiles = new Set(snapshot.result.summary.configGroups
1472
+ ?.filter((group) => group.configSaved).map((group) => group.platform ?? 'common') ?? []);
1263
1473
  const nextDrafts = Object.fromEntries(Object.entries(configDrafts)
1264
- .filter(([draftScope]) => !savedScopes.has(draftScope))
1265
- .map(([scope, draft]) => [scope, draft.editEvidence ? { ...draft, editEvidence: Object.fromEntries(Object.entries(draft.editEvidence).map(([key, evidence]) => [key, {
1266
- ...evidence, storeDigests: Object.fromEntries(Object.entries(evidence.storeDigests)
1267
- .filter(([savedScope]) => !savedScopes.has(savedScope))),
1474
+ .filter(([profile]) => !savedProfiles.has(profile))
1475
+ .map(([profile, draft]) => [profile, draft.editEvidence ? { ...draft, editEvidence: Object.fromEntries(Object.entries(draft.editEvidence).map(([key, evidence]) => [key, {
1476
+ ...evidence, storeDigests: {},
1268
1477
  }])) } : draft]));
1269
1478
  setConfigDrafts(nextDrafts);
1270
1479
  setDirty(Object.keys(nextDrafts).length > 0);
@@ -1276,12 +1485,12 @@ export function ConsoleAppV4(props) {
1276
1485
  }
1277
1486
  else if (snapshot.result.operationKind === 'configSave'
1278
1487
  || snapshot.result.operationKind === 'configReset') {
1279
- const completedScope = snapshot.result.summary.configScope;
1488
+ const completedProfile = snapshot.result.summary.configPlatform ?? 'common';
1280
1489
  const preserveAll = snapshot.result.operationKind === 'configSave'
1281
1490
  && snapshot.result.summary.configRetryApply === true;
1282
1491
  const nextDrafts = { ...configDrafts };
1283
- if (!preserveAll && completedScope)
1284
- delete nextDrafts[completedScope];
1492
+ if (!preserveAll)
1493
+ delete nextDrafts[completedProfile];
1285
1494
  setConfigDrafts(nextDrafts);
1286
1495
  setDirty(Object.keys(nextDrafts).length > 0);
1287
1496
  setConfigCandidate(undefined);
@@ -1396,7 +1605,7 @@ export function ConsoleAppV4(props) {
1396
1605
  if (suspended.externalDraft)
1397
1606
  drafts.push(suspended.externalDraft);
1398
1607
  }
1399
- if ((dirty || configCandidate && !configCandidate.selectingScope || nestedPropertyRevision || objectVariantPreview || Object.values(arrayDrafts).some((draft) => draft.initial === undefined ? draft.rows.length > 0 : arrayDraftChanged(draft)) || changedObjectDraftKeys.length > 0) && draftOwner) {
1608
+ if ((dirty || configCandidate || nestedPropertyRevision || objectVariantPreview || Object.values(arrayDrafts).some((draft) => draft.initial === undefined ? draft.rows.length > 0 : arrayDraftChanged(draft)) || changedObjectDraftKeys.length > 0) && draftOwner) {
1400
1609
  const flowId = externalDraftFlowIds.current.get(draftOwner);
1401
1610
  drafts.push({
1402
1611
  kind: 'configDraft', owner: draftOwner, revision: externalDraftRevision.current.config,
@@ -1422,38 +1631,39 @@ export function ConsoleAppV4(props) {
1422
1631
  configDrafts, configCandidate, arrayDrafts, objectDrafts, nestedPropertyRevision, objectVariantPreview, route.name, snapshot.router.currentFlowId, sourceDraft,
1423
1632
  ]);
1424
1633
  useEffect(() => () => props.controller.setExternalDraftState(), [props.controller]);
1425
- const currentLayerPage = configRoute && configRoute.layer.scope !== 'effective' ? configLayerPages[configRoute.layer.scope] : undefined;
1634
+ const currentLayerPage = configRoute ? configLayerPages[configRoute.layer.profile] : undefined;
1426
1635
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage
1427
1636
  : configPageMatches(currentLayerPage, configRoute) ? currentLayerPage : undefined;
1428
- const hasReferenceFields = (configPage ?? configLayerPages.user ?? configLayerPages.project)?.groups
1429
- .some((group) => group.fields.some((field) => configReferenceScalars(field.definition).length > 0)) ?? false;
1637
+ const hasReferenceFields = [configPage, ...Object.values(configLayerPages)]
1638
+ .some((page) => page?.groups.some((group) => group.fields.some((field) => configReferenceScalars(field.definition).length > 0)));
1430
1639
  useEffect(() => {
1431
1640
  if (!configFlowPluginId || !hasReferenceFields)
1432
1641
  return;
1433
1642
  const abort = new AbortController();
1434
1643
  setReferenceContexts({});
1435
1644
  setReferenceContextError(undefined);
1436
- void Promise.allSettled(['user', 'project'].map(async (scope) => ({ scope,
1437
- context: await props.controller.getConfigReferenceContext(configFlowPluginId, scope, configDrafts[scope] ?? { updates: {}, removes: [] }, abort.signal),
1645
+ void Promise.allSettled(CONFIG_PROFILE_KEYS.map(async (profile) => ({ profile,
1646
+ context: await props.controller.getConfigReferenceContext(configFlowPluginId, configReadOptions({ profile, context: configContext }), configDrafts[profile] ?? { updates: {}, removes: [] }, abort.signal),
1438
1647
  }))).then((results) => {
1439
1648
  if (abort.signal.aborted)
1440
1649
  return;
1441
1650
  setReferenceContexts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled'
1442
- ? [[result.value.scope, result.value.context]] : [])));
1651
+ ? [[result.value.profile, result.value.context]] : [])));
1443
1652
  if (results.some((result) => result.status === 'rejected'))
1444
1653
  setReferenceContextError('引用来源读取失败。请返回修复来源配置后重新打开设置。');
1445
1654
  });
1446
1655
  return () => abort.abort();
1447
- }, [props.controller, configFlowPluginId, configDrafts, editingScope, hasReferenceFields]);
1448
- const crossLayerDependencies = configCrossLayerDependencies(Object.fromEntries((configPage?.groups ?? []).flatMap((group) => group.fields.map((field) => [field.key, field.definition]))), configDrafts, referenceContexts);
1449
- const referenceContext = editingScope ? referenceContexts[editingScope] : undefined;
1656
+ }, [props.controller, configFlowPluginId, configDrafts, configContext, hasReferenceFields, configReadRevision]);
1657
+ const crossLayerDependencies = configCrossLayerDependencies(Object.fromEntries(Object.entries(configLayerPages).map(([profile, page]) => [profile,
1658
+ Object.fromEntries(page.groups.flatMap((group) => group.fields.map((field) => [field.key, field.definition])))])), configDrafts, referenceContexts);
1659
+ const referenceContext = editingProfile ? referenceContexts[editingProfile] : undefined;
1450
1660
  const configDraftIssues = (() => {
1451
1661
  const issues = [];
1452
- for (const draftScope of ['project', 'user']) {
1453
- const draft = configDrafts[draftScope];
1662
+ for (const profile of CONFIG_PROFILE_KEYS) {
1663
+ const draft = configDrafts[profile];
1454
1664
  if (!draft)
1455
1665
  continue;
1456
- const page = configLayerPages[draftScope];
1666
+ const page = configLayerPages[profile];
1457
1667
  if (!page)
1458
1668
  continue;
1459
1669
  const fields = new Map(page.groups.flatMap((group) => group.fields)
@@ -1462,20 +1672,21 @@ export function ConsoleAppV4(props) {
1462
1672
  const field = fields.get(key);
1463
1673
  const invalidReferences = field ? configReferenceValues(field.definition, value)
1464
1674
  .filter(({ scalar, value: selected }) => {
1465
- const reference = referenceContexts[draftScope]?.selections[configReferenceKey(scalar)];
1675
+ const context = referenceContexts[profile];
1676
+ const reference = context?.selections[configReferenceKey(scalar, context.platform)];
1466
1677
  return reference !== undefined && (reference.issues.length > 0
1467
1678
  || !reference.choices.some((choice) => choice.value === selected));
1468
1679
  }).map(({ scalar }) => configReferenceSource(scalar)) : [];
1469
1680
  if (!field || !configFieldCanSet(field)
1470
1681
  || validatePluginConfigValue(field.definition, value, key).length > 0
1471
1682
  || invalidReferences.length > 0) {
1472
- issues.push({ scope: draftScope, key, source: 'update', referenceSources: invalidReferences });
1683
+ issues.push({ profile, key, source: 'update', referenceSources: invalidReferences });
1473
1684
  }
1474
1685
  }
1475
1686
  for (const key of draft.removes) {
1476
1687
  const field = fields.get(key);
1477
1688
  if (!field || !configFieldCanUnset(field)) {
1478
- issues.push({ scope: draftScope, key, source: 'remove' });
1689
+ issues.push({ profile, key, source: 'remove' });
1479
1690
  }
1480
1691
  }
1481
1692
  }
@@ -1486,21 +1697,16 @@ export function ConsoleAppV4(props) {
1486
1697
  return;
1487
1698
  const abort = new AbortController();
1488
1699
  setConfigLayerPages({});
1489
- const layers = [
1490
- { key: 'user', layer: { scope: 'user' } },
1491
- { key: 'project', layer: { scope: 'project' } },
1492
- ];
1493
- void Promise.allSettled(layers.map(async ({ key, layer }) => ({
1494
- key,
1495
- page: await props.controller.getConfigPage(configFlowPluginId, layer, abort.signal),
1700
+ void Promise.allSettled(CONFIG_PROFILE_KEYS.map(async (profile) => ({
1701
+ key: profile,
1702
+ page: await props.controller.getConfigPage(configFlowPluginId, configReadOptions({ profile, context: configContext }), abort.signal),
1496
1703
  }))).then((results) => {
1497
1704
  if (abort.signal.aborted)
1498
1705
  return;
1499
- setConfigLayerCounts(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page.storedCount]] : [])));
1500
1706
  setConfigLayerPages(Object.fromEntries(results.flatMap((result) => result.status === 'fulfilled' ? [[result.value.key, result.value.page]] : [])));
1501
1707
  });
1502
1708
  return () => abort.abort();
1503
- }, [props.controller, configFlowPluginId]);
1709
+ }, [props.controller, configFlowPluginId, configContext, configReadRevision]);
1504
1710
  useEffect(() => {
1505
1711
  if (!configRoute)
1506
1712
  return;
@@ -1526,7 +1732,7 @@ export function ConsoleAppV4(props) {
1526
1732
  setConfigActionError(undefined);
1527
1733
  }
1528
1734
  }
1529
- void props.controller.getConfigPage(expectedPluginId, configRoute.layer, abort.signal)
1735
+ void props.controller.getConfigPage(expectedPluginId, configReadOptions(configRoute.layer), abort.signal)
1530
1736
  .then((page) => {
1531
1737
  if (abort.signal.aborted
1532
1738
  || expectedPluginId !== configRoute.pluginId
@@ -1539,11 +1745,7 @@ export function ConsoleAppV4(props) {
1539
1745
  const invalidStores = error instanceof TuiConfigInvalidStoreError
1540
1746
  ? error.evidence
1541
1747
  : undefined;
1542
- const layer = invalidStores?.length === 1
1543
- ? { scope: invalidStores[0].scope }
1544
- : !invalidStores && configRoute.layer.scope !== 'effective'
1545
- ? { scope: configRoute.layer.scope }
1546
- : undefined;
1748
+ const layer = configRoute.layer;
1547
1749
  const code = typeof error === 'object' && error !== null && 'code' in error
1548
1750
  ? safeDiagnosticCode(error.code, 'configUnavailable')
1549
1751
  : error instanceof Error
@@ -1574,7 +1776,7 @@ export function ConsoleAppV4(props) {
1574
1776
  }
1575
1777
  });
1576
1778
  return () => abort.abort();
1577
- }, [configRoute?.pluginId, configRoute?.layer.scope, props.controller]);
1779
+ }, [configRoute?.pluginId, configRoute?.layer.profile, configContext, configReadRevision, props.controller]);
1578
1780
  useEffect(() => {
1579
1781
  if (route.name !== 'configUnavailable'
1580
1782
  || route.diagnosticCode === 'configValidationMemoryExhausted'
@@ -1584,24 +1786,22 @@ export function ConsoleAppV4(props) {
1584
1786
  }
1585
1787
  const abort = new AbortController();
1586
1788
  setConfigResetAvailability({});
1587
- const scopes = route.invalidStores?.map(({ scope }) => scope)
1588
- ?? route.storedScopes
1589
- ?? (route.layer ? [route.layer.scope] : []);
1590
- void Promise.all(scopes.map(async (scope) => {
1789
+ const profiles = route.layer ? [route.layer.profile] : ['common'];
1790
+ void Promise.all(profiles.map(async (profile) => {
1591
1791
  try {
1592
1792
  return {
1593
- scope,
1594
- availability: await props.controller.getConfigResetAvailability(route.pluginId, { scope }),
1793
+ profile,
1794
+ availability: await props.controller.getConfigResetAvailability(route.pluginId, configReadOptions({ profile, context: route.layer?.context })),
1595
1795
  };
1596
1796
  }
1597
1797
  catch {
1598
- return { scope, availability: undefined };
1798
+ return { profile, availability: undefined };
1599
1799
  }
1600
1800
  })).then((results) => {
1601
1801
  if (!abort.signal.aborted) {
1602
1802
  setConfigResetAvailability(Object.fromEntries(results
1603
1803
  .filter((result) => result.availability !== undefined)
1604
- .map((result) => [result.scope, result.availability])));
1804
+ .map((result) => [result.profile, result.availability])));
1605
1805
  }
1606
1806
  });
1607
1807
  return () => abort.abort();
@@ -1614,15 +1814,19 @@ export function ConsoleAppV4(props) {
1614
1814
  ? route.invalidStores?.map((store) => `${store.scope}:${store.code}`).join('|') ?? ''
1615
1815
  : '',
1616
1816
  route.name === 'configUnavailable' ? route.storedScopes?.join('|') ?? '' : '',
1617
- route.name === 'configUnavailable' ? route.layer?.scope ?? '' : '',
1817
+ route.name === 'configUnavailable' ? route.layer?.profile ?? '' : '',
1818
+ route.name === 'configUnavailable' ? route.layer?.context ?? 'user' : '',
1618
1819
  ]);
1619
1820
  useEffect(() => {
1620
1821
  if (route.name !== 'configFields' || !currentConfigPage)
1621
1822
  return;
1823
+ const currentFrame = props.controller.snapshot.router.current;
1824
+ if (currentFrame.route.name !== 'configFields' || !configPageMatches(currentConfigPage, currentFrame.route))
1825
+ return;
1622
1826
  const tabs = configTabs(currentConfigPage);
1623
1827
  if (tabs.length === 0)
1624
1828
  return;
1625
- const current = snapshot.router.current.context.configGroupId;
1829
+ const current = currentFrame.context.configGroupId;
1626
1830
  if (tabs.some((tab) => tab.id === current))
1627
1831
  return;
1628
1832
  props.controller.updatePageContext({ configGroupId: tabs[0].id });
@@ -1657,6 +1861,8 @@ export function ConsoleAppV4(props) {
1657
1861
  || !frozenPluginIds.has(candidate.target.pluginId))
1658
1862
  || [...frozenPluginIds].some((pluginId) => !representedPluginIds.has(pluginId)))
1659
1863
  return;
1864
+ if (new Set(candidates.map((candidate) => candidate.target.scope)).size > 1)
1865
+ return;
1660
1866
  const currentVersionEvidence = Object.assign({}, ...[...frozenPluginIds].map((pluginId) => {
1661
1867
  const candidatePlugin = props.controller.getPluginOverview(pluginId);
1662
1868
  return candidatePlugin ? pluginWriteTaskVersionEvidence(candidatePlugin, candidates.filter((candidate) => candidate.target.pluginId === pluginId)) : {};
@@ -1776,7 +1982,7 @@ export function ConsoleAppV4(props) {
1776
1982
  const arrayItem = route.name === 'configFieldEditor' || route.name === 'configObjectPropertyEditor'
1777
1983
  ? route.arrayItem : undefined;
1778
1984
  const activeArrayKey = activeTopField?.definition.type === 'array' && configRoute
1779
- ? `${configOwnerKey(configRoute)}\0${activeTopField.key}` : undefined;
1985
+ ? `${configEditorOwnerKey(configRoute.pluginId, configRoute.layer)}\0${activeTopField.key}` : undefined;
1780
1986
  const activeArrayDraft = activeArrayKey ? arrayDrafts[activeArrayKey] : undefined;
1781
1987
  const itemValue = arrayItem && activeArrayDraft
1782
1988
  ? activeArrayDraft.rows.find((row) => row.rowId === arrayItem.rowId)?.value : undefined;
@@ -1797,11 +2003,9 @@ export function ConsoleAppV4(props) {
1797
2003
  useEffect(() => {
1798
2004
  if (!activeArrayKey || activeArrayDraft || !activeTopField || route.name !== 'configFieldEditor')
1799
2005
  return;
1800
- if (route.layer.scope === 'effective' && configReferenceScalars(activeTopField.definition).length)
1801
- return;
1802
2006
  const value = draftUpdates[activeTopField.key] ?? activeTopField.draftValue ?? activeTopField.value;
1803
2007
  setArrayDrafts((current) => ({ ...current, [activeArrayKey]: { ...createConfigArrayDraft(Array.isArray(value) ? value : undefined), evidence: currentConfigPage?.editEvidence } }));
1804
- }, [activeArrayKey, activeArrayDraft, activeTopField, route.name, editingScope]);
2008
+ }, [activeArrayKey, activeArrayDraft, activeTopField, route.name, editingProfile]);
1805
2009
  useEffect(() => {
1806
2010
  if (route.name !== 'configFieldEditor' || route.editor !== 'array' || !activeArrayKey || !activeArrayDraft)
1807
2011
  return;
@@ -1829,13 +2033,14 @@ export function ConsoleAppV4(props) {
1829
2033
  }
1830
2034
  }, [props.controller, route, activeField]);
1831
2035
  const activeObjectKey = activeField?.type === 'object' && configRoute
1832
- ? `${configOwnerKey(configRoute)}\0${activeField.key}${arrayItem ? `\0${arrayItem.rowId ?? 'new'}` : ''}`
2036
+ ? `${configEditorOwnerKey(configRoute.pluginId, configRoute.layer)}\0${activeField.key}${arrayItem ? `\0${arrayItem.rowId ?? 'new'}` : ''}`
1833
2037
  : undefined;
1834
2038
  const objectValueFromUpdate = !arrayItem && activeField && Object.hasOwn(draftUpdates, activeField.key)
1835
2039
  && typeof draftUpdates[activeField.key] === 'object'
1836
2040
  ? draftUpdates[activeField.key]
1837
2041
  : undefined;
1838
- const activeCandidateMutation = !arrayItem && activeField ? configCandidate?.mutations[activeField.key] : undefined;
2042
+ const activeCandidateMutation = !arrayItem && activeField && configCandidate && configCandidate.layer.profile === editingProfile
2043
+ && (configCandidate.layer.context ?? 'user') === configContext ? configCandidate.mutations[activeField.key] : undefined;
1839
2044
  const objectValueFromCandidate = activeCandidateMutation?.kind === 'set'
1840
2045
  && typeof activeCandidateMutation.value === 'object'
1841
2046
  ? activeCandidateMutation.value
@@ -1867,13 +2072,13 @@ export function ConsoleAppV4(props) {
1867
2072
  const activeFieldIsConst = activeField && 'const' in activeField.definition && activeField.definition.const !== undefined;
1868
2073
  const activePropertyIsConst = activePropertyDefinition && 'const' in activePropertyDefinition && activePropertyDefinition.const !== undefined;
1869
2074
  const referenceNotice = (scalar, value) => {
1870
- const selection = referenceContext?.selections[configReferenceKey(scalar)];
2075
+ const selection = referenceContext?.selections[configReferenceKey(scalar, referenceContext.platform)];
1871
2076
  const message = referenceContextError && !selection ? `来源 ${configReferenceSource(scalar)}:${referenceContextError}`
1872
2077
  : configReferenceMessage(scalar, selection, value);
1873
2078
  const source = 'valueRef' in scalar ? scalar.valueRef.field : '';
1874
- const pendingSource = !('valueRef' in scalar && scalar.valueRef?.plugin) && editingScope === 'project' && selection?.source !== 'project'
1875
- && (Object.hasOwn(configDrafts.user?.updates ?? {}, source) || configDrafts.user?.removes.includes(source));
1876
- return [message, pendingSource ? `来源 ${source} 在个人默认有未保存修改。请先保存来源配置,再编辑或保存引用配置。` : undefined]
2079
+ const pendingSource = !('valueRef' in scalar && scalar.valueRef?.plugin) && editingProfile !== 'common' && selection?.source !== 'platform'
2080
+ && (Object.hasOwn(configDrafts.common?.updates ?? {}, source) || configDrafts.common?.removes.includes(source));
2081
+ return [message, pendingSource ? `来源 ${source} 在公共设置有未保存修改。请先保存来源配置,再编辑或保存引用配置。` : undefined]
1877
2082
  .filter(Boolean).join(' ') || undefined;
1878
2083
  };
1879
2084
  const openReferenceSource = (scalar, leaf) => {
@@ -1896,7 +2101,7 @@ export function ConsoleAppV4(props) {
1896
2101
  useEffect(() => {
1897
2102
  if (route.name !== 'configFieldEditor' || !activeField)
1898
2103
  return;
1899
- const candidate = configCandidate?.mutations[activeField.key];
2104
+ const candidate = activeCandidateMutation;
1900
2105
  const draft = arrayItem ? itemValue : candidate?.kind === 'set' ? candidate.value : draftUpdates[activeField.key];
1901
2106
  setInputValue(activeField.sensitive
1902
2107
  ? draft === undefined ? '' : String(draft)
@@ -1907,6 +2112,8 @@ export function ConsoleAppV4(props) {
1907
2112
  }, [
1908
2113
  route.name,
1909
2114
  route.name === 'configFieldEditor' ? `${route.fieldKey}:${route.mode ?? ''}:${route.arrayItem ? route.arrayItem.rowId ?? 'new' : 'field'}` : '',
2115
+ editingProfile,
2116
+ configContext,
1910
2117
  activeField?.key,
1911
2118
  configCandidate,
1912
2119
  ]);
@@ -1925,6 +2132,8 @@ export function ConsoleAppV4(props) {
1925
2132
  route.name === 'configObjectPropertyEditor'
1926
2133
  ? `${route.fieldKey}:${route.propertyKey ?? ''}:${route.stage}`
1927
2134
  : '',
2135
+ editingProfile,
2136
+ configContext,
1928
2137
  ]);
1929
2138
  useEffect(() => {
1930
2139
  if (route.name !== 'sourceAdd')
@@ -1979,7 +2188,12 @@ export function ConsoleAppV4(props) {
1979
2188
  const supportsTechnicalDetails = !(route.name === 'pluginDependencies' && route.view !== 'removeBlocked'
1980
2189
  || route.name === 'pluginDependencyNode' || route.name === 'portableDependencies')
1981
2190
  && (snapshot.phase === 'error'
1982
- || route.name === 'projectStatus'
2191
+ || Boolean(route.name === 'intentInventory' && (snapshot.scopedInventories[route.scope]?.errors.length
2192
+ || snapshot.scopedInventories[route.scope]?.declaredRoots.length
2193
+ || snapshot.scopedInventories[route.scope]?.extraInstalledRoots.length
2194
+ || snapshot.scopedInventoryRefresh[route.scope]?.state === 'error'
2195
+ || snapshot.scopedInventories[route.scope]?.declarationState === 'invalid'
2196
+ || snapshot.scopedInventories[route.scope]?.preferenceState === 'invalid'))
1983
2197
  || Boolean(route.name === 'reviewPreparing' && snapshot.reviewPreparation?.code)
1984
2198
  || Boolean(route.name === 'progress' && snapshot.progress.messages.length > 0)
1985
2199
  || Boolean(route.name === 'review' && snapshot.activeReview?.technicalDetails?.length)
@@ -2048,27 +2262,21 @@ export function ConsoleAppV4(props) {
2048
2262
  + Math.max(0, wrapConsoleText(actionError ?? description ?? '', size.columns - 2).length - 1)
2049
2263
  + Math.max(0, wrapConsoleText(title, size.columns - 2).length - 1),
2050
2264
  });
2265
+ let detailNavigation;
2051
2266
  const detailCardContent = (subject, completionLines = []) => {
2052
2267
  const cardWidth = size.columns - 2;
2053
2268
  const card = pluginDetailCardLayout(subject, cardWidth);
2054
2269
  const availableRows = pageActionBudget(completionLines.length);
2055
- // Compact actions use one row each; paging adds one row inside the card.
2056
2270
  const actionRows = Math.min(3, actions.filter((action) => action.role !== 'navigation').length);
2057
- const needsPaging = card.fullHeight > availableRows - actionRows;
2058
- const lineLimit = needsPaging ? Math.max(1, availableRows - actionRows - 3) : undefined;
2059
- const pageCount = lineLimit ? Math.max(1, Math.ceil(card.factRows / lineLimit)) : 1;
2060
- const page = lineLimit ? Math.min(pageCount, Math.floor(batchTargetOffset / lineLimit) + 1) : 1;
2061
- const lineOffset = lineLimit ? (page - 1) * lineLimit : 0;
2062
- if (lineLimit)
2063
- readerNavigation = { page, pageCount, move: (direction) => {
2064
- setBatchTargetOffset((Math.min(pageCount, Math.max(1, page + direction)) - 1) * lineLimit);
2065
- } };
2271
+ const lineLimit = Math.max(1, availableRows - actionRows - 4);
2272
+ const maxOffset = Math.max(0, card.factRows - lineLimit);
2273
+ const lineOffset = Math.min(batchTargetOffset, maxOffset);
2274
+ detailNavigation = { maxOffset, lineLimit };
2066
2275
  const cardHeight = entityCardWindowHeight(card, lineOffset, lineLimit);
2067
2276
  currentActionViewportLineBudget = Math.max(1, pageActionBudget(completionLines.length) - cardHeight);
2068
2277
  const viewport = actionViewport(actions, true, focus, scrollOffset, currentActionViewportLineBudget);
2069
2278
  help = [
2070
- ...(actions.length > 0 ? ['↑↓ 选择动作'] : []),
2071
- ...(needsPaging ? ['←→ 翻信息'] : []),
2279
+ '↑↓ 连续阅读与选择', 'PgUp/PgDn 翻动',
2072
2280
  ...(actions[focus]?.run && !actions[focus]?.disabled ? ['Enter 选择'] : []),
2073
2281
  'Esc 返回',
2074
2282
  ].join(' · ');
@@ -2091,7 +2299,7 @@ export function ConsoleAppV4(props) {
2091
2299
  setBatchTargetOffset(next);
2092
2300
  },
2093
2301
  };
2094
- return _jsx(Reader, { text: text, columns: columns, rows: rows, page: page.page });
2302
+ return _jsx(Reader, { text: text, columns: columns, rows: rows, page: page.page, highlightChanges: Boolean(scopedFlow && !showTechnical) });
2095
2303
  };
2096
2304
  const pagedItems = (total, rows, offset = batchTargetOffset) => paginateWrappedRows({
2097
2305
  lines: Array.from({ length: total }, (_, index) => String(index)),
@@ -2169,8 +2377,15 @@ export function ConsoleAppV4(props) {
2169
2377
  return items.map((item) => item.pluginId);
2170
2378
  };
2171
2379
  const go = (next) => {
2172
- props.controller.updatePageContext({
2173
- focusIndex: route.name === 'projectStatus' || route.name === 'platform' ? effectiveFocus : focus,
2380
+ scopedFlowToken.current += 1;
2381
+ if (scopedFlow?.stage === 'preparing')
2382
+ props.controller.cancelScopedPreparation();
2383
+ if (scopedFlow?.stage === 'review' || scopedFlow?.stage === 'risk')
2384
+ void props.controller.discardScopedPlan(scopedFlow.plan);
2385
+ scopedFlowBusy.current = false;
2386
+ setScopedFlow(undefined);
2387
+ const leavingContext = {
2388
+ focusIndex: route.name === 'intentInventory' || route.name === 'platform' ? effectiveFocus : focus,
2174
2389
  scrollOffset,
2175
2390
  entityLineOffset: entityCardNavigation || route.name === 'pluginDetail' ? batchTargetOffset : undefined,
2176
2391
  query,
@@ -2180,21 +2395,29 @@ export function ConsoleAppV4(props) {
2180
2395
  ? pluginRows[focus]?.pluginId
2181
2396
  : route.name === 'pluginContent' ? actions[focus]?.actionId?.replace(/^content:/u, '')
2182
2397
  : route.name === 'batchSelection' ? actions[focus]?.pluginId
2183
- : route.name === 'projectStatus' ? actions[effectiveFocus]?.pluginId
2398
+ : route.name === 'intentInventory' ? actions[effectiveFocus]?.pluginId
2184
2399
  : route.name === 'sourceList' ? actions[focus]?.actionId?.replace(/^source:/u, '')
2185
2400
  : route.name === 'portableSkillSelect' ? actions[focus]?.actionId : undefined,
2186
- ...(route.name === 'projectStatus' || route.name === 'platform'
2401
+ ...(route.name === 'intentInventory' || route.name === 'platform'
2187
2402
  ? { focusedActionId: actions[effectiveFocus]?.actionId } : {}),
2188
- });
2189
- props.controller.push(next);
2403
+ };
2404
+ props.controller.updatePageContext(leavingContext);
2405
+ if (route.name === 'intentInventory') {
2406
+ const key = route.scope === 'project' ? `project:${route.projectKey ?? ''}` : 'user';
2407
+ scopedPageContext.current[key] = { ...snapshot.router.current.context, ...leavingContext };
2408
+ }
2409
+ const enteringContext = next.name === 'intentInventory'
2410
+ ? scopedPageContext.current[next.scope === 'project' ? `project:${next.projectKey ?? ''}` : 'user']
2411
+ : undefined;
2412
+ props.controller.push(next, enteringContext);
2190
2413
  if (route.name === 'pluginContent')
2191
2414
  setContentReader(undefined);
2192
- setFocus(0);
2415
+ setFocus(enteringContext?.focusIndex ?? 0);
2193
2416
  if (next.name === 'pluginDetail' || next.name === 'sourceDetail'
2194
- || next.name === 'projectStatus')
2417
+ || next.name === 'intentInventory')
2195
2418
  setBatchTargetOffset(0);
2196
- setScrollOffset(0);
2197
- setQuery('');
2419
+ setScrollOffset(enteringContext?.scrollOffset ?? 0);
2420
+ setQuery(enteringContext?.query ?? '');
2198
2421
  setSearchFocus(false);
2199
2422
  if (next.name === 'pluginList') {
2200
2423
  setInstallFilter(next.mode === 'installed' ? 'installed' : 'all');
@@ -2202,6 +2425,124 @@ export function ConsoleAppV4(props) {
2202
2425
  setShowTechnical(false);
2203
2426
  setActionError(undefined);
2204
2427
  };
2428
+ const beginScopedTask = (request, prepare = () => props.controller.prepareScopedTask(request)) => {
2429
+ if (scopedFlowBusy.current)
2430
+ return;
2431
+ scopedFlowBusy.current = true;
2432
+ const token = ++scopedFlowToken.current;
2433
+ setShowTechnical(false);
2434
+ setBatchTargetOffset(0);
2435
+ setScopedFlow({ stage: 'preparing', scope: request.scope, request: request.kind, focus: 0 });
2436
+ void prepare().then((plan) => {
2437
+ if (scopedFlowToken.current !== token) {
2438
+ void props.controller.discardScopedPlan(plan);
2439
+ return;
2440
+ }
2441
+ scopedFlowBusy.current = false;
2442
+ setScopedFlow({ stage: 'review', scope: request.scope, request, plan, focus: 0 });
2443
+ }).catch((error) => {
2444
+ if (scopedFlowToken.current !== token)
2445
+ return;
2446
+ scopedFlowBusy.current = false;
2447
+ setScopedFlow({ stage: 'result', scope: request.scope,
2448
+ errorCode: safeDiagnosticCode(error instanceof Error ? error.message : undefined, 'recoveryPreparationFailed'), focus: 0 });
2449
+ });
2450
+ };
2451
+ const agentSummaries = (pluginId) => {
2452
+ const entries = pluginId ? [pluginId] : props.controller.listPlugins({ mode: 'browse' }).map((entry) => entry.pluginId);
2453
+ return Object.fromEntries((agentSettings?.preferences.targets ?? []).map((target) => {
2454
+ const facts = entries.flatMap((id) => props.controller.getPluginDetailSubject(id)?.placements
2455
+ .filter((fact) => (fact.agent ?? fact.platform) === target) ?? []);
2456
+ const count = new Set(entries.filter((id) => props.controller.getPluginDetailSubject(id)?.placements
2457
+ .some((fact) => (fact.agent ?? fact.platform) === target))).size;
2458
+ const symbol = facts.some((fact) => fact.projectState === 'blocked' || fact.statusState === 'blocked') ? '×'
2459
+ : facts.some((fact) => fact.observation === 'unknown' || fact.projectState === 'unknown') ? '?'
2460
+ : facts.some((fact) => fact.projectState === 'pending' || fact.statusState === 'needsSync'
2461
+ || fact.statusState === 'absent' || fact.statusState === 'orphaned') ? '↻'
2462
+ : facts.length ? '✓' : '?';
2463
+ return [target, { symbol, count, label: symbol === '✓' ? '已安装' : symbol === '↻' ? '待同步'
2464
+ : symbol === '×' ? '安装受阻' : '未确认' }];
2465
+ }));
2466
+ };
2467
+ const openAgentManagement = (pluginId) => {
2468
+ setAgentWelcome(false);
2469
+ void props.controller.readAgentSettings().then((settings) => {
2470
+ setAgentSettings(settings);
2471
+ setAgentSettingsError(undefined);
2472
+ setScopedFlow({ stage: 'agents', scope: settings.context, settings, pluginId, focus: 0 });
2473
+ }).catch(() => setAgentSettingsError('无法读取 Agent 设置,请检查本机配置。'));
2474
+ };
2475
+ const closeScopedFlow = () => {
2476
+ scopedFlowToken.current += 1;
2477
+ scopedFlowBusy.current = false;
2478
+ if (scopedFlow?.stage === 'preparing')
2479
+ props.controller.cancelScopedPreparation();
2480
+ if (scopedFlow?.stage === 'review' || scopedFlow?.stage === 'risk')
2481
+ void props.controller.discardScopedPlan(scopedFlow.plan);
2482
+ setScopedFlow(undefined);
2483
+ if (scopedFlow?.stage === 'result' && scopedFlow.receipt?.ok && route.name === 'pluginWriteTask') {
2484
+ const parent = props.controller.pluginWriteTaskReturnRoute(route);
2485
+ if (parent.name === 'pluginWriteTask' && parent.goal !== route.goal) {
2486
+ props.controller.back();
2487
+ }
2488
+ else {
2489
+ props.controller.replace({ name: 'pluginDetail', pluginId: route.pluginId });
2490
+ }
2491
+ setFocus(0);
2492
+ setScrollOffset(0);
2493
+ setQuery('');
2494
+ }
2495
+ };
2496
+ const executeScopedReview = (plan) => {
2497
+ if (scopedFlowBusy.current)
2498
+ return;
2499
+ scopedFlowBusy.current = true;
2500
+ const token = ++scopedFlowToken.current;
2501
+ const request = scopedFlow?.stage === 'review' || scopedFlow?.stage === 'risk'
2502
+ ? scopedFlow.request : undefined;
2503
+ const batchOwner = batchDraft && (route.name === 'batchSelection' || route.name === 'batchPlacement'
2504
+ || route.name === 'batchInstallScope' || route.name === 'batchInstallPlatforms'
2505
+ || route.name === 'pluginWriteTask' && route.batchOrigin?.flowId === batchDraft.flowId);
2506
+ const nestedTaskParent = route.name === 'pluginWriteTask' && route.goal === 'update'
2507
+ ? props.controller.pluginWriteTaskReturnRoute(route) : undefined;
2508
+ setScopedFlow({ stage: 'progress', scope: plan.scope, plan, focus: 0 });
2509
+ void props.controller.executeScopedTask(plan).then((outcome) => {
2510
+ if (scopedFlowToken.current !== token)
2511
+ return;
2512
+ scopedFlowBusy.current = false;
2513
+ if (outcome.kind === 'recoveryReview') {
2514
+ const nextRequest = request?.kind === 'recovery'
2515
+ ? { ...request, refresh: false } : request;
2516
+ if (!nextRequest) {
2517
+ setScopedFlow({ stage: 'result', scope: plan.scope,
2518
+ errorCode: 'recoveryReprepareFailed', focus: 0 });
2519
+ return;
2520
+ }
2521
+ setScopedFlow({ stage: 'review', scope: plan.scope,
2522
+ request: nextRequest, plan: outcome.plan, focus: 0 });
2523
+ return;
2524
+ }
2525
+ setScopedFlow({ stage: 'result', scope: plan.scope, plan,
2526
+ ...(request ? { request } : {}),
2527
+ ...(outcome.kind === 'recovery' ? { receipt: outcome.receipt } : { changed: outcome.changed }),
2528
+ focus: 0 });
2529
+ if (batchOwner && outcome.kind === 'recovery' && outcome.receipt.ok
2530
+ && !(route.name === 'pluginWriteTask' && nestedTaskParent?.name === 'pluginWriteTask'
2531
+ && nestedTaskParent.goal !== route.goal)) {
2532
+ setBatchDraft(undefined);
2533
+ props.controller.replace({ name: 'pluginList', mode: 'installed' });
2534
+ }
2535
+ void props.controller.load().then(() => props.controller.refreshScopedInventory(plan.scope))
2536
+ .catch((error) => props.controller.recordUiFailure(error, { code: 'tuiScopedStatusReloadFailed', stage: 'scoped-status-reload' }));
2537
+ }).catch((error) => {
2538
+ if (scopedFlowToken.current !== token)
2539
+ return;
2540
+ scopedFlowBusy.current = false;
2541
+ setScopedFlow({ stage: 'result', scope: plan.scope, plan,
2542
+ ...(request ? { request } : {}),
2543
+ errorCode: safeDiagnosticCode(error instanceof Error ? error.message : undefined, 'recoveryFailed'), focus: 0 });
2544
+ });
2545
+ };
2205
2546
  const closeBatchSelectionTools = () => {
2206
2547
  if (!batchDraft)
2207
2548
  return;
@@ -2220,13 +2561,6 @@ export function ConsoleAppV4(props) {
2220
2561
  setFocus(portableResultDetailReturnFocus.current);
2221
2562
  setPortableResultDetailOpen(false);
2222
2563
  setPortableResultDetailOffset(0);
2223
- if (route.name === 'projectStatus' && dynamicActionIds && dynamicActionSignature !== undefined) {
2224
- const recheck = actions.findIndex((action) => action.actionId === 'project-recheck');
2225
- dynamicMenuFocus.current = { routeKey, actionIds: dynamicActionSignature,
2226
- focusedActionId: 'project-recheck' };
2227
- setFocus(Math.max(0, recheck));
2228
- setScrollOffset(0);
2229
- }
2230
2564
  setShowTechnical((value) => !value);
2231
2565
  if (!(route.name === 'result' && snapshot.result
2232
2566
  && 'operationKind' in snapshot.result
@@ -2254,9 +2588,10 @@ export function ConsoleAppV4(props) {
2254
2588
  ? restoredEntityIndex
2255
2589
  : Math.min(restoredFocus, Math.max(0, rowIds.length - 1));
2256
2590
  }
2257
- else if (restoredFrame.route.name === 'projectStatus') {
2591
+ else if (restoredFrame.route.name === 'intentInventory') {
2258
2592
  const actionId = restored.focusedActionId
2259
- ?? (restored.focusedEntityId ? `project-root:${restored.focusedEntityId}` : 'project-recheck');
2593
+ ?? (restored.focusedEntityId ? `inventory-root:${restored.focusedEntityId}`
2594
+ : 'inventory-recheck');
2260
2595
  dynamicMenuFocus.current = {
2261
2596
  routeKey: JSON.stringify(restoredFrame.route), actionIds: '', focusedActionId: actionId,
2262
2597
  };
@@ -2298,7 +2633,8 @@ export function ConsoleAppV4(props) {
2298
2633
  itemCount = candidates.length + 2;
2299
2634
  restoredFocus = entityIndex >= 0 ? entityIndex : Math.min(restoredFocus, Math.max(0, itemCount - 1));
2300
2635
  }
2301
- const dynamicMenuRoute = restoredFrame.route.name === 'projectStatus' || restoredFrame.route.name === 'platform';
2636
+ const dynamicMenuRoute = restoredFrame.route.name === 'intentInventory'
2637
+ || restoredFrame.route.name === 'platform';
2302
2638
  let restoredScroll = dynamicMenuRoute
2303
2639
  ? restored.scrollOffset ?? 0
2304
2640
  : Math.min(restored.scrollOffset ?? 0, Math.max(0, (itemCount ?? 1) - 1));
@@ -2384,18 +2720,70 @@ export function ConsoleAppV4(props) {
2384
2720
  setActionError(undefined);
2385
2721
  };
2386
2722
  const beginReview = (intent, returnRoute) => {
2723
+ setSplitTaskReview(undefined);
2724
+ const agentForPlatform = (platform) => platform === 'claude' ? 'claude' : 'codex';
2725
+ if (intent.kind === 'pluginWrite') {
2726
+ const agents = intent.platform === 'both' ? ['codex', 'claude']
2727
+ : [agentForPlatform(intent.platform)];
2728
+ beginScopedTask({ kind: 'recovery', scope: intent.scope,
2729
+ command: intent.action === 'refreshAndSync' ? 'sync' : intent.action,
2730
+ selectors: [intent.pluginId], targets: agents,
2731
+ exactTargets: agents.map((agent) => ({ pluginId: intent.pluginId, agent })),
2732
+ ...(intent.action === 'refreshAndSync' ? { refresh: true } : {}),
2733
+ ...(intent.force ? { force: true } : {}),
2734
+ ...(intent.dependencyScopeOverrides
2735
+ ? { dependencyScopeOverrides: intent.dependencyScopeOverrides } : {}),
2736
+ ...(intent.saveDependencyScopeOverrides ? { saveDependencyScopeOverrides: true } : {}),
2737
+ ...(intent.scriptTimeoutMs !== undefined ? { scriptTimeoutMs: intent.scriptTimeoutMs } : {}),
2738
+ });
2739
+ return;
2740
+ }
2741
+ if (intent.kind === 'pluginWriteTask') {
2742
+ const scopes = new Set(intent.targets.map((target) => target.scope));
2743
+ if (scopes.size !== 1) {
2744
+ setActionError('个人与项目目标需要分别确认,请先选择同一范围。');
2745
+ return;
2746
+ }
2747
+ const exactTargets = intent.targets.map((target) => ({ pluginId: target.pluginId,
2748
+ agent: agentForPlatform(target.platform) }));
2749
+ beginScopedTask({ kind: 'recovery', scope: intent.targets[0].scope,
2750
+ command: intent.goal === 'update' ? 'reinstall' : 'sync',
2751
+ selectors: [...new Set(exactTargets.map((target) => target.pluginId))],
2752
+ targets: [...new Set(exactTargets.map((target) => target.agent))], exactTargets,
2753
+ ...(intent.scriptTimeoutMs !== undefined ? { scriptTimeoutMs: intent.scriptTimeoutMs } : {}) });
2754
+ return;
2755
+ }
2756
+ if (intent.kind === 'pluginWriteBatch') {
2757
+ const targets = intent.selection.kind === 'targets' ? intent.selection.targets
2758
+ : intent.selection.pluginIds.flatMap((pluginId) => (intent.platform === 'both' ? ['codex', 'claude'] : [intent.platform])
2759
+ .map((platform) => ({ pluginId, platform, scope: intent.scope })));
2760
+ const scopes = new Set(targets.map((target) => target.scope));
2761
+ if (scopes.size !== 1) {
2762
+ setActionError('个人与项目目标需要分别确认,请先选择同一范围。');
2763
+ return;
2764
+ }
2765
+ const exactTargets = targets.map((target) => ({ pluginId: target.pluginId,
2766
+ agent: agentForPlatform(target.platform) }));
2767
+ beginScopedTask({ kind: 'recovery', scope: targets[0].scope, command: intent.action,
2768
+ selectors: [...new Set(exactTargets.map((target) => target.pluginId))],
2769
+ targets: [...new Set(exactTargets.map((target) => target.agent))], exactTargets,
2770
+ ...(intent.dependencyScopeOverrides ? { dependencyScopeOverrides: intent.dependencyScopeOverrides } : {}),
2771
+ ...(intent.saveDependencyScopeOverrides ? { saveDependencyScopeOverrides: true } : {}),
2772
+ ...(intent.scriptTimeoutMs !== undefined ? { scriptTimeoutMs: intent.scriptTimeoutMs } : {}) });
2773
+ return;
2774
+ }
2387
2775
  pendingReviewIntentRef.current = intent;
2388
2776
  setPendingReviewIntent(intent);
2389
2777
  const underlyingResultRoute = returnRoute ?? (route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
2390
2778
  ? snapshot.result.returnRoute
2391
2779
  : undefined);
2392
2780
  props.controller.updatePageContext({
2393
- focusIndex: route.name === 'projectStatus' || route.name === 'platform' ? effectiveFocus : focus,
2781
+ focusIndex: route.name === 'platform' ? effectiveFocus : focus,
2394
2782
  scrollOffset,
2395
2783
  query,
2396
2784
  installFilter,
2397
2785
  sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
2398
- ...(route.name === 'projectStatus' || route.name === 'platform'
2786
+ ...(route.name === 'platform'
2399
2787
  ? { focusedActionId: actions[effectiveFocus]?.actionId } : {}),
2400
2788
  });
2401
2789
  props.controller.startReviewPreparation(intent, underlyingResultRoute);
@@ -2418,6 +2806,8 @@ export function ConsoleAppV4(props) {
2418
2806
  : candidate.target.pluginId === route.pluginId);
2419
2807
  if (candidates.length === 0 || candidates.some((candidate) => !candidate.eligible))
2420
2808
  return;
2809
+ if (new Set(candidates.map((candidate) => candidate.target.scope)).size > 1)
2810
+ return;
2421
2811
  props.controller.updatePageContext({ autoPreparedRestore: true });
2422
2812
  beginReview({
2423
2813
  kind: 'pluginWriteTask',
@@ -2493,6 +2883,8 @@ export function ConsoleAppV4(props) {
2493
2883
  ? '插件已不在当前状态中,可取消选择'
2494
2884
  : draft.action === undefined
2495
2885
  && !props.controller.pluginHasInstallableTarget(candidate.plugin.pluginId)
2886
+ && !candidate.plugin.portableSkills
2887
+ && !props.controller.getPluginDetailSubject(candidate.plugin.pluginId)?.placements.some((placement) => placement.actual && placement.agent && placement.agent !== 'codex' && placement.agent !== 'claude')
2496
2888
  && props.controller.listPluginWriteBatchTargets('uninstall', [candidate.plugin.pluginId]).length === 0
2497
2889
  && props.controller.listPluginWriteBatchTargets('cleanup', [candidate.plugin.pluginId]).length === 0
2498
2890
  && !batchTaskPluginIds.has(candidate.plugin.pluginId)
@@ -2503,7 +2895,8 @@ export function ConsoleAppV4(props) {
2503
2895
  ? '插件来源不可用'
2504
2896
  : draft.action === 'install' && candidate.plugin.capabilityUnsupported
2505
2897
  ? '当前 AW CLI 不支持插件所需功能'
2506
- : draft.action === 'install' && !props.controller.pluginHasInstallableTarget(candidate.plugin.pluginId)
2898
+ : draft.action === 'install' && !candidate.plugin.portableSkills
2899
+ && !props.controller.pluginHasInstallableTarget(candidate.plugin.pluginId)
2507
2900
  ? '没有可信且兼容的缺失位置'
2508
2901
  : undefined;
2509
2902
  const invalidBatchSelection = (draft) => {
@@ -2590,27 +2983,6 @@ export function ConsoleAppV4(props) {
2590
2983
  : [...current.selectedPluginIds, pluginId],
2591
2984
  } : current);
2592
2985
  };
2593
- const prepareCurrentProjectTargets = (targets) => {
2594
- if (targets.length === 0) {
2595
- setActionError('当前项目没有已确认可同步的目标。');
2596
- return;
2597
- }
2598
- beginReview({
2599
- kind: 'pluginWriteBatch',
2600
- action: 'sync',
2601
- selection: {
2602
- kind: 'currentProjectIntentTargets',
2603
- targets: targets.map((target) => ({
2604
- pluginId: target.pluginId,
2605
- platform: target.platform,
2606
- scope: target.scope,
2607
- })),
2608
- },
2609
- platform: new Set(targets.map((target) => target.platform)).size > 1
2610
- ? 'both' : targets[0].platform,
2611
- scope: 'project',
2612
- });
2613
- };
2614
2986
  const openSelectedPluginTask = (goal, pluginIds, scopeConstraint) => {
2615
2987
  if (batchDraft && invalidBatchSelection(batchDraft).length > 0) {
2616
2988
  setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
@@ -2809,7 +3181,9 @@ export function ConsoleAppV4(props) {
2809
3181
  return;
2810
3182
  }
2811
3183
  try {
2812
- props.controller.startForceCleanupReview(candidate);
3184
+ const agent = platform === 'claude' ? 'claude' : 'codex';
3185
+ beginScopedTask({ kind: 'recovery', command: 'cleanup', scope: selectedScope,
3186
+ selectors: [pluginId], targets: [agent], exactTargets: [{ pluginId, agent }], force: true }, () => props.controller.startForceCleanupReview(candidate));
2813
3187
  setFocus(0);
2814
3188
  setScrollOffset(0);
2815
3189
  setActionError(undefined);
@@ -3017,9 +3391,7 @@ export function ConsoleAppV4(props) {
3017
3391
  setDirty(false);
3018
3392
  go({ name: 'sourceEdit', sourceId, owner });
3019
3393
  };
3020
- const configLayerLabel = (layer) => layer.scope === 'effective' ? '有效设置'
3021
- : layer.scope === 'user' ? '个人默认'
3022
- : '当前项目';
3394
+ const configLayerLabel = (layer) => layer.profile === 'common' ? '公共设置' : agentLabel(layer.profile);
3023
3395
  const fieldValueText = (field, value, full = false, configured = value !== undefined) => presentConfigValue({
3024
3396
  definition: field.definition,
3025
3397
  value,
@@ -3035,8 +3407,8 @@ export function ConsoleAppV4(props) {
3035
3407
  }
3036
3408
  return `${field.effectiveDisplayValue}${field.unit && field.effectiveDisplayValue !== '未设置' ? ` ${field.unit}` : ''}`;
3037
3409
  };
3038
- const pendingFieldDisplay = (field, scope) => {
3039
- const draft = configDrafts[scope];
3410
+ const pendingFieldDisplay = (field, profile) => {
3411
+ const draft = configDrafts[profile];
3040
3412
  if (!draft)
3041
3413
  return undefined;
3042
3414
  if (draft.removes.includes(field.key))
@@ -3044,49 +3416,42 @@ export function ConsoleAppV4(props) {
3044
3416
  if (!Object.hasOwn(draft.updates, field.key))
3045
3417
  return undefined;
3046
3418
  const value = draft.updates[field.key];
3047
- if (field.sensitive)
3419
+ const targetField = configLayerPages[profile]?.groups.flatMap((group) => group.fields)
3420
+ .find((candidate) => candidate.key === field.key);
3421
+ if (!targetField || targetField.sensitive)
3048
3422
  return '将更新';
3049
- return fieldValueText(field, value);
3423
+ return fieldValueText(targetField, value);
3050
3424
  };
3051
- const pendingFieldTargets = (field) => ['project', 'user'].flatMap((scope) => {
3052
- const value = pendingFieldDisplay(field, scope);
3053
- return value ? [{ scope, value }] : [];
3425
+ const pendingFieldTargets = (field) => CONFIG_PROFILE_KEYS.flatMap((profile) => {
3426
+ const value = pendingFieldDisplay(field, profile);
3427
+ return value ? [{ profile, value }] : [];
3054
3428
  });
3055
3429
  const fieldEditor = (field) => {
3056
3430
  return field.type;
3057
3431
  };
3058
3432
  const fieldEditorMode = (field) => (field.choices?.length ?? 0) > 0 || 'valueRef' in field.definition && field.definition.valueRef ? { mode: 'choices' } : {};
3059
- const openConfigTarget = (candidate, replace = false) => {
3433
+ const openConfigTarget = (candidate) => {
3060
3434
  const currentFlowId = snapshot.router.currentFlowId;
3061
3435
  const storedAnchor = configOverviewAnchors.current.get(candidate.pluginId);
3062
3436
  const storedOverviewFrameId = storedAnchor && storedAnchor.flowId === currentFlowId
3063
3437
  ? storedAnchor.overviewFrameId : undefined;
3064
3438
  const overviewFrameId = storedOverviewFrameId
3065
3439
  ?? props.controller.configOverviewFrameId(currentFlowId, candidate.pluginId);
3066
- setConfigCandidate({
3440
+ const selected = {
3067
3441
  ...candidate,
3068
3442
  ...(currentFlowId ? { flowId: currentFlowId } : {}),
3069
3443
  ...(overviewFrameId ? { overviewFrameId } : {}),
3070
- });
3071
- const targetRoute = {
3072
- name: 'configLayer',
3073
- pluginId: candidate.pluginId,
3074
- ...(candidate.fieldKey ? { fieldKey: candidate.fieldKey } : {}),
3075
- ...(candidate.groupId ? { groupId: candidate.groupId } : {}),
3076
- ...(candidate.presetId ? { presetId: candidate.presetId } : {}),
3077
3444
  };
3078
- if (replace)
3079
- props.controller.replace(targetRoute);
3080
- else
3081
- go(targetRoute);
3445
+ setConfigCandidate(selected);
3446
+ openConfigLayerTarget(candidate.layer, selected);
3082
3447
  };
3083
3448
  const updateDraft = (key, value) => {
3084
3449
  if (!configRoute || !currentConfigPage)
3085
3450
  return;
3086
3451
  setConfigActionError(undefined);
3087
- if (activeField && editingScope) {
3452
+ if (activeField && editingProfile) {
3088
3453
  for (const { scalar, value: referencedValue } of configReferenceValues(activeField.definition, value)) {
3089
- const selected = referenceContext?.selections[configReferenceKey(scalar)];
3454
+ const selected = referenceContext?.selections[configReferenceKey(scalar, referenceContext.platform)];
3090
3455
  if (!selected || selected.issues.length || !selected.choices.some((choice) => choice.value === referencedValue)) {
3091
3456
  setObjectError(referenceNotice(scalar, referencedValue));
3092
3457
  setInputError(referenceNotice(scalar, referencedValue));
@@ -3119,16 +3484,13 @@ export function ConsoleAppV4(props) {
3119
3484
  }
3120
3485
  const candidate = {
3121
3486
  pluginId: configRoute.pluginId,
3487
+ layer: configRoute.layer,
3122
3488
  fieldKey: key,
3123
3489
  groupId: snapshot.router.current.context.configGroupId,
3124
3490
  mutations: { [key]: { kind: 'set', value } },
3125
- ...(activeArrayDraft?.evidence ? { editEvidence: activeArrayDraft.evidence } : {}),
3491
+ editEvidence: activeArrayDraft?.evidence ?? currentConfigPage.editEvidence,
3126
3492
  };
3127
- if (editingScope && activeTopField && configReferenceScalars(activeTopField.definition).length) {
3128
- openConfigLayerTarget({ scope: editingScope }, candidate);
3129
- }
3130
- else
3131
- openConfigTarget(candidate);
3493
+ openConfigTarget(candidate);
3132
3494
  };
3133
3495
  const editArray = (edit) => {
3134
3496
  if (!activeArrayKey || !activeArrayDraft)
@@ -3173,14 +3535,13 @@ export function ConsoleAppV4(props) {
3173
3535
  setConfigActionError(undefined);
3174
3536
  const candidate = {
3175
3537
  pluginId: configRoute.pluginId,
3538
+ layer: configRoute.layer,
3539
+ editEvidence: currentConfigPage?.editEvidence,
3176
3540
  fieldKey: key,
3177
3541
  groupId: snapshot.router.current.context.configGroupId,
3178
3542
  mutations: { [key]: { kind: 'unset' } },
3179
3543
  };
3180
- if (editingScope && activeTopField && configReferenceScalars(activeTopField.definition).length)
3181
- openConfigLayerTarget({ scope: editingScope }, candidate);
3182
- else
3183
- openConfigTarget(candidate);
3544
+ openConfigTarget(candidate);
3184
3545
  };
3185
3546
  const updateObjectDraft = (value) => {
3186
3547
  if (!activeObjectKey)
@@ -3243,7 +3604,7 @@ export function ConsoleAppV4(props) {
3243
3604
  const definition = activeObjectProperties?.[propertyKey] ?? activeField.definition.additionalProperties;
3244
3605
  if (definition && value !== undefined) {
3245
3606
  for (const { scalar, value: selected } of configReferenceValues(definition, value)) {
3246
- const reference = referenceContext?.selections[configReferenceKey(scalar)];
3607
+ const reference = referenceContext?.selections[configReferenceKey(scalar, referenceContext.platform)];
3247
3608
  if (!reference || reference.issues.length || !reference.choices.some((choice) => choice.value === selected)) {
3248
3609
  setInputError(referenceNotice(scalar, selected) ?? '引用选项已变化,请重新选择。');
3249
3610
  return false;
@@ -3317,6 +3678,8 @@ export function ConsoleAppV4(props) {
3317
3678
  : undefined);
3318
3679
  openConfigTarget({
3319
3680
  pluginId: page.pluginId,
3681
+ layer: { profile: page.profile, context: page.context },
3682
+ editEvidence: page.editEvidence,
3320
3683
  presetId,
3321
3684
  groupId: target?.groupId,
3322
3685
  mutations,
@@ -3324,51 +3687,31 @@ export function ConsoleAppV4(props) {
3324
3687
  };
3325
3688
  const openConfigLayerTarget = (layer, explicitCandidate) => {
3326
3689
  const candidate = explicitCandidate ?? configCandidate;
3327
- if (!candidate)
3690
+ if (!candidate || candidate.layer.profile !== layer.profile
3691
+ || (candidate.layer.context ?? 'user') !== (layer.context ?? 'user'))
3328
3692
  return;
3329
3693
  const selectedCandidate = { ...candidate,
3330
3694
  flowId: candidate.flowId ?? snapshot.router.currentFlowId,
3331
3695
  overviewFrameId: candidate.overviewFrameId ?? configOverviewAnchors.current.get(candidate.pluginId)?.overviewFrameId,
3332
3696
  };
3333
- if (selectedCandidate.selectingScope) {
3334
- const field = configLayerPages[layer.scope]?.groups.flatMap((group) => group.fields).find((field) => field.key === selectedCandidate.fieldKey);
3335
- if (!field || !configFieldCanSet(field))
3336
- return;
3337
- if (field.definition.type === 'array') {
3338
- const owner = `${candidate.pluginId}\0${field.key}`;
3339
- const working = arrayDrafts[owner];
3340
- if (working) {
3341
- const stored = configDrafts[layer.scope]?.updates[field.key] ?? field.draftValue ?? field.value;
3342
- setArrayDrafts((current) => ({ ...current, [owner]: { ...working,
3343
- initial: Array.isArray(stored) ? structuredClone(stored) : undefined,
3344
- evidence: configLayerPages[layer.scope]?.editEvidence,
3345
- } }));
3346
- }
3347
- }
3348
- const available = ['user', 'project'].filter((scope) => configLayerPages[scope]?.groups
3349
- .flatMap((group) => group.fields).some((entry) => entry.key === field.key && configFieldCanSet(entry)));
3350
- const next = { name: 'configFieldEditor', pluginId: candidate.pluginId, layer, fieldKey: field.key,
3351
- editor: fieldEditor(field), ...fieldEditorMode(field) };
3352
- if (available.length === 1)
3353
- props.controller.replace(next);
3354
- else
3355
- go(next);
3356
- return;
3357
- }
3358
- const page = configLayerPages[layer.scope];
3359
- if (!page)
3697
+ const page = currentConfigPage?.pluginId === candidate.pluginId
3698
+ && currentConfigPage.profile === layer.profile && currentConfigPage.context === (layer.context ?? 'user')
3699
+ ? currentConfigPage : configLayerPages[layer.profile];
3700
+ if (!page || page.pluginId !== candidate.pluginId || page.context !== (layer.context ?? 'user'))
3360
3701
  return;
3361
3702
  const fields = new Map(page.groups.flatMap((group) => group.fields)
3362
3703
  .map((field) => [field.key, field]));
3363
3704
  const valid = Object.entries(selectedCandidate.mutations).every(([key, mutation]) => {
3364
3705
  const field = fields.get(key);
3365
- return Boolean(field && (mutation.kind === 'set' ? configFieldCanSet(field) : configFieldCanUnset(field)));
3706
+ return Boolean(field && (mutation.kind === 'set'
3707
+ ? configFieldCanSet(field) && validatePluginConfigValue(field.definition, mutation.value, key).length === 0
3708
+ : configFieldCanUnset(field)));
3366
3709
  });
3367
3710
  if (!valid) {
3368
- setConfigActionError('此值不能保存到所选位置。请返回修改值或选择其他位置。');
3711
+ setConfigActionError('此值不符合当前配置。请检查修改后的值。');
3369
3712
  return;
3370
3713
  }
3371
- const previous = configDrafts[layer.scope] ?? { updates: {}, removes: [] };
3714
+ const previous = configDrafts[layer.profile] ?? { updates: {}, removes: [] };
3372
3715
  const updates = { ...previous.updates };
3373
3716
  const removes = new Set(previous.removes);
3374
3717
  const editEvidence = { ...previous.editEvidence };
@@ -3395,9 +3738,9 @@ export function ConsoleAppV4(props) {
3395
3738
  removes.delete(key);
3396
3739
  }
3397
3740
  }
3398
- const next = { ...configDrafts, [layer.scope]: { updates, removes: [...removes], editEvidence } };
3741
+ const next = { ...configDrafts, [layer.profile]: { updates, removes: [...removes], editEvidence } };
3399
3742
  if (Object.keys(updates).length === 0 && removes.size === 0)
3400
- delete next[layer.scope];
3743
+ delete next[layer.profile];
3401
3744
  const target = selectedCandidate.fieldKey
3402
3745
  ? configFieldTarget(configPage ?? page, selectedCandidate.fieldKey)
3403
3746
  : selectedCandidate.groupId ? { groupId: selectedCandidate.groupId, focusIndex: 0 } : undefined;
@@ -3407,10 +3750,10 @@ export function ConsoleAppV4(props) {
3407
3750
  return;
3408
3751
  }
3409
3752
  if (!selectedCandidate.flowId) {
3410
- props.controller.replace({ name: 'configFields', pluginId: selectedCandidate.pluginId, layer: { scope: 'effective' } }, target ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
3753
+ props.controller.replace({ name: 'configFields', pluginId: selectedCandidate.pluginId, layer }, target ? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 } : undefined);
3411
3754
  }
3412
3755
  if (selectedCandidate.fieldKey) {
3413
- const owner = `${selectedCandidate.pluginId}\0${selectedCandidate.fieldKey}`;
3756
+ const owner = `${configEditorOwnerKey(selectedCandidate.pluginId, layer)}\0${selectedCandidate.fieldKey}`;
3414
3757
  const belongsToField = (key) => key === owner || key.startsWith(`${owner}\0`);
3415
3758
  setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== owner)));
3416
3759
  setObjectDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !belongsToField(key))));
@@ -3422,33 +3765,6 @@ export function ConsoleAppV4(props) {
3422
3765
  setFocus(target?.focusIndex ?? 0);
3423
3766
  setScrollOffset(0);
3424
3767
  };
3425
- useEffect(() => {
3426
- if (route.name !== 'configLayer' || !configCandidate)
3427
- return;
3428
- const scopes = ['project', 'user'].filter((scope) => {
3429
- const page = configLayerPages[scope];
3430
- if (!page)
3431
- return false;
3432
- const fields = page.groups.flatMap((group) => group.fields);
3433
- if (configCandidate.selectingScope)
3434
- return fields.some((field) => field.key === configCandidate.fieldKey && configFieldCanSet(field));
3435
- return Object.entries(configCandidate.mutations).every(([key, mutation]) => {
3436
- const field = fields.find((entry) => entry.key === key);
3437
- return field && (mutation.kind === 'set' ? configFieldCanSet(field) : configFieldCanUnset(field));
3438
- });
3439
- });
3440
- if (scopes.length === 1) {
3441
- openConfigLayerTarget({ scope: scopes[0] });
3442
- if (!configCandidate.selectingScope)
3443
- setConfigActionError(`仅可保存到${configLayerLabel({ scope: scopes[0] })};已加入待保存修改。`);
3444
- }
3445
- }, [route.name, configCandidate, configLayerPages]);
3446
- useEffect(() => {
3447
- if (route.name !== 'configFieldEditor' || route.layer.scope !== 'effective' || arrayItem || !activeTopField
3448
- || !configReferenceScalars(activeTopField.definition).length)
3449
- return;
3450
- openConfigTarget({ pluginId: route.pluginId, fieldKey: activeTopField.key, selectingScope: true, mutations: {} }, true);
3451
- }, [route.name, route.name === 'configFieldEditor' ? route.layer.scope : '', activeTopField?.key]);
3452
3768
  const configChanges = (page, draft) => {
3453
3769
  const fields = page.groups.flatMap((group) => group.fields);
3454
3770
  return [...Object.keys(draft.updates), ...draft.removes.filter((key) => !Object.hasOwn(draft.updates, key))]
@@ -3525,44 +3841,45 @@ export function ConsoleAppV4(props) {
3525
3841
  const discardInvalidConfigDraftChanges = () => {
3526
3842
  if (!configRoute || configDraftIssues.length === 0)
3527
3843
  return;
3528
- const invalidKeys = new Set(configDraftIssues.map((issue) => issue.key));
3529
- const nextDrafts = Object.fromEntries(['project', 'user'].flatMap((scope) => {
3530
- const draft = configDrafts[scope];
3844
+ const nextDrafts = Object.fromEntries(CONFIG_PROFILE_KEYS.flatMap((profile) => {
3845
+ const draft = configDrafts[profile];
3531
3846
  if (!draft)
3532
3847
  return [];
3533
- const invalidForScope = new Set(configDraftIssues
3534
- .filter((issue) => issue.scope === scope).map((issue) => issue.key));
3848
+ const invalidForProfile = new Set(configDraftIssues
3849
+ .filter((issue) => issue.profile === profile).map((issue) => issue.key));
3535
3850
  const updates = Object.fromEntries(Object.entries(draft.updates)
3536
- .filter(([key]) => !invalidForScope.has(key)));
3537
- const removes = draft.removes.filter((key) => !invalidForScope.has(key));
3538
- return Object.keys(updates).length + removes.length > 0 ? [[scope, { updates, removes,
3851
+ .filter(([key]) => !invalidForProfile.has(key)));
3852
+ const removes = draft.removes.filter((key) => !invalidForProfile.has(key));
3853
+ return Object.keys(updates).length + removes.length > 0 ? [[profile, { updates, removes,
3539
3854
  ...(draft.editEvidence ? { editEvidence: Object.fromEntries(Object.entries(draft.editEvidence)
3540
3855
  .filter(([key]) => Object.hasOwn(updates, key))) } : {}),
3541
3856
  }]] : [];
3542
3857
  }));
3543
- const owner = `${configOwnerKey(configRoute)}\0`;
3544
- const nextChangedObjectKeys = changedObjectDraftKeys.filter((key) => !key.startsWith(owner)
3545
- || !invalidKeys.has(key.slice(owner.length)));
3858
+ const invalidEditorOwners = configDraftIssues.map(({ profile, key }) => `${configEditorOwnerKey(configRoute.pluginId, { profile, context: configContext })}\0${key}`);
3859
+ const invalidEditor = (key) => invalidEditorOwners.some((owner) => key === owner || key.startsWith(`${owner}\0`));
3860
+ const nextChangedObjectKeys = changedObjectDraftKeys.filter((key) => !invalidEditor(key));
3546
3861
  setConfigDrafts(nextDrafts);
3547
3862
  setObjectDrafts((current) => Object.fromEntries(Object.entries(current)
3548
- .filter(([key]) => !key.startsWith(owner)
3549
- || !invalidKeys.has(key.slice(owner.length)))));
3863
+ .filter(([key]) => !invalidEditor(key))));
3864
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current)
3865
+ .filter(([key]) => !invalidEditor(key))));
3550
3866
  setChangedObjectDraftKeys(nextChangedObjectKeys);
3551
3867
  setDirty(Object.keys(nextDrafts).length + nextChangedObjectKeys.length > 0);
3552
3868
  setObjectError(undefined);
3553
3869
  setConfigActionError(undefined);
3554
3870
  };
3555
- const configSaveIntent = (apply) => {
3871
+ const configSaveIntent = (apply = false) => {
3556
3872
  if (!configRoute || configDraftCount === 0)
3557
3873
  return;
3558
- const groups = ['project', 'user'].flatMap((scope) => {
3559
- const draft = configDrafts[scope];
3560
- const page = configLayerPages[scope];
3561
- if (!draft || !page)
3874
+ const groups = CONFIG_PROFILE_KEYS.flatMap((profile) => {
3875
+ const draft = configDrafts[profile];
3876
+ const page = configLayerPages[profile];
3877
+ if (!draft || !page || page.pluginId !== configRoute.pluginId || page.context !== configContext)
3562
3878
  return [];
3563
3879
  const fields = page.groups.flatMap((group) => group.fields);
3564
3880
  return [{
3565
- scope,
3881
+ scope: 'user',
3882
+ ...(profile === 'common' ? {} : { platform: profile }),
3566
3883
  updates: draft.updates,
3567
3884
  ...(draft.editEvidence ? { editEvidence: draft.editEvidence } : {}),
3568
3885
  removeKeys: draft.removes,
@@ -3577,17 +3894,49 @@ export function ConsoleAppV4(props) {
3577
3894
  return {
3578
3895
  kind: 'configSaveBatch',
3579
3896
  pluginId: configRoute.pluginId,
3897
+ context: configContext,
3580
3898
  apply,
3581
3899
  groups,
3582
3900
  };
3583
3901
  };
3584
- const beginConfigSave = async (apply) => {
3902
+ const discardDependentConfigDrafts = () => {
3903
+ if (!configRoute || crossLayerDependencies.length === 0)
3904
+ return;
3905
+ const next = { ...configDrafts };
3906
+ for (const { profile, key } of crossLayerDependencies) {
3907
+ const previous = next[profile];
3908
+ if (!previous)
3909
+ continue;
3910
+ const updates = { ...previous.updates };
3911
+ delete updates[key];
3912
+ const removes = previous.removes.filter((removed) => removed !== key);
3913
+ const editEvidence = { ...previous.editEvidence };
3914
+ delete editEvidence[key];
3915
+ if (Object.keys(updates).length + removes.length === 0)
3916
+ delete next[profile];
3917
+ else
3918
+ next[profile] = { updates, removes, editEvidence };
3919
+ }
3920
+ const removedOwners = crossLayerDependencies.map(({ profile, key }) => `${configEditorOwnerKey(configRoute.pluginId, { profile, context: configContext })}\0${key}`);
3921
+ const removed = (key) => removedOwners.some((owner) => key === owner || key.startsWith(`${owner}\0`));
3922
+ const changedObjects = changedObjectDraftKeys.filter((key) => !removed(key));
3923
+ setConfigDrafts(next);
3924
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !removed(key))));
3925
+ setObjectDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !removed(key))));
3926
+ setChangedObjectDraftKeys(changedObjects);
3927
+ setDirty(Object.keys(next).length + changedObjects.length > 0);
3928
+ setObjectError(undefined);
3929
+ setConfigActionError(undefined);
3930
+ };
3931
+ const onSaveConfig = async () => {
3932
+ if (configSaveState?.status === 'saving')
3933
+ return;
3585
3934
  if (crossLayerDependencies.length) {
3586
- setConfigActionError(`请先保存来源配置,再编辑或保存引用配置:${[...new Set(crossLayerDependencies.map((entry) => entry.source))].join('、')}(个人默认)。请取消相关引用草稿后保存来源。`);
3935
+ setConfigActionError(`请先保存来源配置,再编辑或保存引用配置:${[...new Set(crossLayerDependencies.map((entry) => entry.source))].join('、')}(公共设置)。请取消相关引用草稿后保存来源。`);
3587
3936
  return;
3588
3937
  }
3589
- if (['user', 'project'].some((scope) => !referenceContexts[scope]
3590
- && Object.keys(configDrafts[scope]?.updates ?? {}).some((key) => configPage?.groups.flatMap((group) => group.fields)
3938
+ if (CONFIG_PROFILE_KEYS.some((profile) => !referenceContexts[profile]
3939
+ && Object.keys(configDrafts[profile]?.updates ?? {}).some((key) => configLayerPages[profile]?.groups.flatMap((group) => group.fields)
3591
3940
  .some((field) => field.key === key && configReferenceScalars(field.definition).length)))) {
3592
3941
  setConfigActionError(referenceContextError ?? '正在读取引用来源,请稍后保存。');
3593
3942
  return;
@@ -3596,22 +3945,73 @@ export function ConsoleAppV4(props) {
3596
3945
  setConfigActionError('字段已变化。请先放弃失效草稿,或返回继续检查来源。');
3597
3946
  return;
3598
3947
  }
3599
- const intent = configSaveIntent(apply);
3948
+ const intent = configSaveIntent();
3600
3949
  if (!intent)
3601
3950
  return;
3951
+ const profiles = intent.groups.map((group) => group.platform ?? 'common');
3952
+ const context = intent.context ?? 'user';
3953
+ const previouslySaved = configSaveState?.status === 'error' && configSaveState.pluginId === intent.pluginId
3954
+ && configSaveState.context === context ? configSaveState.profiles : [];
3955
+ setConfigSaveState({ status: 'saving', pluginId: intent.pluginId, profiles, context });
3956
+ setConfigActionError(undefined);
3602
3957
  try {
3603
- await beginReview(intent);
3604
- setConfigActionError(undefined);
3958
+ const result = await props.controller.saveConfigDraft(intent);
3959
+ const saved = new Set(result.savedProfiles);
3960
+ const pendingApply = CONFIG_PROFILE_KEYS.filter((profile) => saved.has(profile) || previouslySaved.includes(profile));
3961
+ const nextDrafts = Object.fromEntries(Object.entries(configDrafts)
3962
+ .filter(([profile]) => !saved.has(profile))
3963
+ .map(([profile, draft]) => [profile, draft.editEvidence ? { ...draft, editEvidence: Object.fromEntries(Object.entries(draft.editEvidence).map(([key, evidence]) => [key, { ...evidence, storeDigests: {} }])) } : draft]));
3964
+ const savedEditorOwners = result.savedProfiles.map((profile) => `${configEditorOwnerKey(intent.pluginId, { profile, context })}\0`);
3965
+ const belongsToSavedProfile = (key) => savedEditorOwners.some((owner) => key.startsWith(owner));
3966
+ const nextChangedObjects = changedObjectDraftKeys.filter((key) => !belongsToSavedProfile(key));
3967
+ setConfigDrafts(nextDrafts);
3968
+ setArrayDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !belongsToSavedProfile(key))));
3969
+ setObjectDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !belongsToSavedProfile(key))));
3970
+ setChangedObjectDraftKeys(nextChangedObjects);
3971
+ setConfigCandidate(undefined);
3972
+ setDirty(Object.keys(nextDrafts).length + nextChangedObjects.length > 0);
3973
+ if (saved.size > 0) {
3974
+ setConfigPage(undefined);
3975
+ setConfigLayerPages({});
3976
+ setReferenceContexts({});
3977
+ setConfigReadRevision((revision) => revision + 1);
3978
+ }
3979
+ if (result.status === 'succeeded') {
3980
+ setConfigSaveState({ status: 'saved', pluginId: intent.pluginId, profiles: pendingApply, context });
3981
+ }
3982
+ else {
3983
+ const error = result.status === 'partial' ? '部分设置未保存。已保留未保存的草稿,请检查后重试。'
3984
+ : '设置未保存。已保留草稿,请检查后重试。';
3985
+ setConfigSaveState({ status: 'error', pluginId: intent.pluginId, profiles: pendingApply, context, error });
3986
+ setConfigActionError(error);
3987
+ }
3605
3988
  }
3606
3989
  catch (error) {
3607
- props.controller.recordUiFailure(error, { code: 'tuiConfigReviewFailed', stage: 'config-review' });
3608
- setConfigActionError('设置值未通过验证。请重新检查修改。');
3990
+ props.controller.recordUiFailure(error, { code: 'tuiConfigSaveFailed', stage: 'config-save' });
3991
+ const message = '设置未保存。请重新检查修改后重试。';
3992
+ setConfigSaveState({ status: 'error', pluginId: intent.pluginId, profiles: previouslySaved, context, error: message });
3993
+ setConfigActionError(message);
3609
3994
  }
3610
3995
  };
3996
+ const beginConfigSave = onSaveConfig;
3997
+ const dismissConfigSaved = () => { setConfigSaveState(undefined); };
3998
+ const applySavedConfig = () => {
3999
+ if (configSaveState?.status !== 'saved' || configSaveState.profiles.length === 0)
4000
+ return;
4001
+ const saved = configSaveState;
4002
+ const platforms = saved.profiles.filter((profile) => profile !== 'common');
4003
+ setConfigSaveState(undefined);
4004
+ beginReview({
4005
+ kind: 'configSave', pluginId: saved.pluginId, context: saved.context,
4006
+ apply: true, retryApply: true, updates: {},
4007
+ ...(saved.profiles.includes('common') ? {}
4008
+ : platforms.length === 1 ? { platform: platforms[0] } : { targets: platforms }),
4009
+ });
4010
+ };
3611
4011
  useEffect(() => {
3612
4012
  if (route.name === 'configSave' && route.view !== 'changes' && configDraftCount > 0 && currentConfigPage) {
3613
4013
  props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer: route.layer });
3614
- beginConfigSave(true);
4014
+ void beginConfigSave();
3615
4015
  }
3616
4016
  }, [route.name, route.name === 'configSave' ? route.view : undefined, currentConfigPage, configDraftCount]);
3617
4017
  useEffect(() => {
@@ -3620,15 +4020,6 @@ export function ConsoleAppV4(props) {
3620
4020
  props.controller.replace({ name: 'sourceEdit', sourceId: sourceDraft.sourceId, owner: sourceDraft.owner });
3621
4021
  beginSourceReview(sourceDraft, false);
3622
4022
  }, [route.name, route.name === 'sourceSave' ? route.view : undefined, sourceDraft]);
3623
- useEffect(() => {
3624
- const intent = pendingReviewIntentRef.current;
3625
- if (route.name !== 'review' || intent?.kind !== 'configSaveBatch' || !intent.apply
3626
- || snapshot.activeReview?.configImpact?.state !== 'known'
3627
- || snapshot.activeReview.configImpact.affectedTargetCount !== 0)
3628
- return;
3629
- back();
3630
- void beginReview({ ...intent, apply: false });
3631
- }, [route.name, snapshot.activeReview?.reviewToken]);
3632
4023
  useEffect(() => {
3633
4024
  if (!operationConfirmation)
3634
4025
  return;
@@ -3638,7 +4029,7 @@ export function ConsoleAppV4(props) {
3638
4029
  setOverlayFocus(0);
3639
4030
  }
3640
4031
  }, [operationConfirmation, route.name, snapshot.activeReview?.reviewToken]);
3641
- if (snapshot.phase === 'error' && !isCatalogSourcesBootstrapFlow(snapshot)
4032
+ if (!scopedFlow && snapshot.phase === 'error' && !isCatalogSourcesBootstrapFlow(snapshot)
3642
4033
  && !isOperationHistoryRoute(route)) {
3643
4034
  const sourcesConfigInvalid = snapshot.loadError?.code === 'catalogSourcesConfigInvalid';
3644
4035
  breadcrumb = sourcesConfigInvalid ? 'Catalog 来源恢复' : '启动失败';
@@ -3656,7 +4047,7 @@ export function ConsoleAppV4(props) {
3656
4047
  }] : [];
3657
4048
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "error", title: "\u63D2\u4EF6\u72B6\u6001\u65E0\u6CD5\u8BFB\u53D6", description: "\u6CA1\u6709\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C\u3002" }), actions.length > 0 ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' }) : null, showTechnical ? _jsx(TechnicalDetails, { children: _jsx(StaticRow, { title: "\u5B89\u5168\u8BCA\u65AD", meta: snapshot.loadError?.code ?? 'statusLoadFailed', compact: true }) }) : null] });
3658
4049
  }
3659
- else if (snapshot.phase !== 'ready' && !isCatalogSourcesBootstrapFlow(snapshot)
4050
+ else if (!scopedFlow && snapshot.phase !== 'ready' && !isCatalogSourcesBootstrapFlow(snapshot)
3660
4051
  && !isOperationHistoryRoute(route)) {
3661
4052
  breadcrumb = '启动';
3662
4053
  title = '正在检查插件状态';
@@ -3664,9 +4055,20 @@ export function ConsoleAppV4(props) {
3664
4055
  help = 'Ctrl-C 退出';
3665
4056
  content = _jsxs(StatusCard, { status: "info", title: "\u51C6\u5907\u63A7\u5236\u53F0", children: [_jsx(ProgressRow, { title: "\u8BFB\u53D6\u63D2\u4EF6\u6765\u6E90", state: "active" }), _jsx(ProgressRow, { title: "\u68C0\u67E5\u5F53\u524D\u9879\u76EE", state: "pending" }), _jsx(ProgressRow, { title: "\u6C47\u603B\u53EF\u7528\u64CD\u4F5C", state: "pending" })] });
3666
4057
  }
3667
- else if (rendererFamily === 'home' && route.name === 'home') {
4058
+ else if (configSaveState?.status === 'saved') {
4059
+ title = '插件设置';
4060
+ description = undefined;
4061
+ content = _jsx(Box, { height: Math.max(5, size.rows - 7), flexDirection: "column", justifyContent: "center", children: _jsxs(Box, { borderStyle: "round", borderColor: tuiTheme.roles.focus, paddingX: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, color: tuiTheme.roles.focus, children: "\u5DF2\u4FDD\u5B58\uFF0C\u662F\u5426\u66F4\u65B0\u63D2\u4EF6\uFF1F" }), _jsx(Text, { children: "\u66F4\u65B0\u5DF2\u7ECF\u5B89\u88C5\u7684\u63D2\u4EF6\uFF0C\u4F7F\u8BBE\u7F6E\u751F\u6548\u3002" }), ['更新插件', '暂不更新'].map((label, index) => _jsxs(Text, { color: index === configSavedFocus ? tuiTheme.roles.focus : undefined, children: [index === configSavedFocus ? '› ' : ' ', label] }, label))] }) });
4062
+ help = '↑↓ 选择 · Enter 确认 · Esc 暂不更新';
4063
+ }
4064
+ else if (agentWelcome) {
4065
+ title = '欢迎使用 AW';
4066
+ description = undefined;
4067
+ content = _jsx(Box, { height: Math.max(5, size.rows - 7), flexDirection: "column", justifyContent: "center", children: _jsxs(Box, { borderStyle: "round", borderColor: tuiTheme.roles.focus, paddingX: 2, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "\u5148\u9009\u62E9\u4F60\u5728\u8FD9\u53F0\u7535\u8111\u4E0A\u4F7F\u7528\u7684 Agent" }), ['选择 Agent', '稍后再说'].map((label, index) => _jsxs(Text, { color: index === agentWelcomeFocus ? tuiTheme.roles.focus : undefined, children: [index === agentWelcomeFocus ? '› ' : ' ', label] }, label))] }) });
4068
+ help = '↑↓ 选择 · Enter 确认 · Esc 稍后再说';
4069
+ }
4070
+ else if (rendererFamily === 'home' && route.name === 'home' && !scopedFlow) {
3668
4071
  const home = snapshot.viewModel.home;
3669
- const inventory = snapshot.projectInventory;
3670
4072
  const plugins = props.controller.listPlugins({ mode: 'browse' });
3671
4073
  const attention = new Set(plugins.filter((entry) => entry.primaryAttention).map((entry) => entry.pluginId)).size;
3672
4074
  const unknown = new Set(plugins.filter((entry) => entry.supportedPlatforms.some((platform) => pluginPlatformUnknown(entry, platform))).map((entry) => entry.pluginId)).size;
@@ -3675,145 +4077,332 @@ export function ConsoleAppV4(props) {
3675
4077
  context = undefined;
3676
4078
  const pluginState = [home.pluginUpdateCount ? `↑ ${home.pluginUpdateCount} 可更新` : '',
3677
4079
  attention ? `! ${attention} 需处理` : '', unknown ? `? ${unknown} 未确认` : ''].filter(Boolean).join(' · ') || '✓ 当前可用';
3678
- const projectState = !inventory ? '检查中' : inventory.declarationState === 'notProject' ? '当前不是项目'
3679
- : inventory.declarationState === 'absent' ? '未配置项目插件'
3680
- : inventory.declarationState !== 'ready' ? '! 项目声明需要检查'
3681
- : [inventory.counts.blocked ? `! ${inventory.counts.blocked} 受阻` : '',
3682
- inventory.counts.pending ? `↻ ${inventory.counts.pending} 待同步` : '',
3683
- inventory.counts.unknown ? `? ${inventory.counts.unknown} 未确认` : ''].filter(Boolean).join(' · ') || '✓ 全部就绪';
3684
- const visibleProjectState = snapshot.projectInventoryRefresh.state === 'error'
3685
- ? `! 项目检查失败,显示上次检查结果 · ${projectState}` : projectState;
3686
4080
  const sourceState = [home.catalogUpdateCount ? `↑ ${home.catalogUpdateCount} 有更新` : '',
3687
4081
  home.catalogRecoveryCount ? `! ${home.catalogRecoveryCount} 检查失败` : '',
3688
4082
  home.unavailableSourceCount ? `! ${home.unavailableSourceCount} 不可用` : '',
3689
4083
  home.catalogCheckingCount ? `${home.catalogCheckingCount} 检查中` : ''].filter(Boolean).join(' · ') || '✓ 当前可用';
3690
4084
  actions = [
3691
4085
  { label: '插件', description: pluginState, run: () => go({ name: 'pluginList', mode: 'browse' }) },
3692
- { label: '项目插件', description: visibleProjectState, run: () => go({ name: 'projectStatus' }) },
4086
+ { label: '项目插件', description: (() => {
4087
+ const counts = snapshot.scopedInventories.project?.counts;
4088
+ return counts ? `✓ ${counts.ready} 个就绪 · ! ${counts.pending + counts.blocked + counts.unknown} 个需处理` : '查看当前项目的插件';
4089
+ })(), run: () => snapshot.projectRoot
4090
+ ? go({ name: 'intentInventory', scope: 'project', projectKey: snapshot.projectRoot })
4091
+ : go({ name: 'intentInventory', scope: 'user' }) },
3693
4092
  { label: '插件来源', description: sourceState, run: () => go({ name: 'sourceList' }) },
4093
+ { label: 'Agent 管理', description: agentSettingsError ?? (agentSettings?.preferences.targets
4094
+ .map((target) => `${agentSummaries()[target]?.symbol ?? '?'} ${agentLabel(target)}`).join(' · ') || '请选择本机使用的 Agent'),
4095
+ run: () => openAgentManagement() },
3694
4096
  ];
3695
- content = _jsx(Box, { flexDirection: "column", children: actions.map((action, index) => _jsx(Box, { marginBottom: 1, children: _jsx(HomeNavTile, { width: size.columns - 2, title: action.label, focused: !projectReminderOpen && focus === index, children: _jsx(Text, { wrap: "wrap", children: action.description?.split(' · ').map((state, stateIndex) => _jsxs(Text, { color: state.startsWith('↑') ? tuiTheme.roles.change : state.startsWith('!') || state.startsWith('↻') ? tuiTheme.roles.warning
3696
- : state.startsWith('?') ? tuiTheme.roles.unknown : state.startsWith('✓') ? tuiTheme.roles.success : tuiTheme.roles.secondary, children: [stateIndex > 0 ? ' ' : '', "[", state, "]"] }, stateIndex)) }) }) }, action.label)) });
4097
+ if (size.rows <= 18 && wrapConsoleText(actions[3].description ?? '', size.columns - 4).length > 1)
4098
+ title = '';
4099
+ content = _jsxs(Box, { flexDirection: "column", children: [actions.map((action, index) => _jsx(Box, { flexShrink: 0, marginBottom: size.rows >= 24 ? 1 : 0, children: _jsx(HomeNavTile, { width: size.columns - 2, title: action.label, focused: focus === index, children: index === 3 ? _jsx(AgentStatusText, { text: action.description ?? '' }) : _jsx(Text, { wrap: "wrap", children: action.description?.split(' · ').map((state, stateIndex) => _jsxs(Text, { color: state.startsWith('↑') ? tuiTheme.roles.change : state.startsWith('!') || state.startsWith('↻') ? tuiTheme.roles.warning
4100
+ : state.startsWith('?') ? tuiTheme.roles.unknown : state.startsWith('✓') ? tuiTheme.roles.success : tuiTheme.roles.secondary, children: [stateIndex > 0 ? ' ' : '', "[", state, "]"] }, stateIndex)) }) }) }, action.label)), snapshot.projectRoot ? _jsx(Box, { flexShrink: 0, children: _jsxs(Text, { color: tuiTheme.roles.secondary, children: ["\u5F53\u524D\u9879\u76EE ", path.basename(snapshot.projectRoot)] }) }) : null] });
4101
+ }
4102
+ else if (route.name === 'intentScopes') {
4103
+ breadcrumb = '插件清单';
4104
+ title = '选择清单范围';
4105
+ context = undefined;
4106
+ description = undefined;
4107
+ const scopes = snapshot.projectRoot ? ['user', 'project'] : ['user'];
4108
+ actions = scopes.map((scope) => {
4109
+ const inventory = snapshot.scopedInventories[scope];
4110
+ const count = inventory?.counts;
4111
+ const status = !inventory ? '未检查' : inventory.declarationState !== 'ready' && inventory.declarationState !== 'absent'
4112
+ ? '需求清单需检查' : count.blocked ? `${count.blocked} 受阻`
4113
+ : count.unknown ? `${count.unknown} 未确认` : count.pending ? `${count.pending} 待同步`
4114
+ : inventory.allSatisfied === true ? '已就绪' : '尚未添加插件';
4115
+ return { actionId: `scope:${scope}`, label: scope === 'user' ? '个人' : `当前项目 · ${inventory?.projectName ?? path.basename(snapshot.projectRoot ?? '')}`,
4116
+ description: `${scope === 'user' ? '跨项目使用的插件需求' : '随仓库共享的插件需求'} · ${status}`,
4117
+ run: () => go({ name: 'intentInventory', scope,
4118
+ ...(scope === 'project' && snapshot.projectRoot
4119
+ ? { projectKey: snapshot.projectRoot } : {}) }),
4120
+ };
4121
+ });
4122
+ content = _jsxs(Box, { flexDirection: "column", children: [actions.map((action, index) => _jsx(Box, { marginBottom: 1, children: _jsx(HomeNavTile, { width: size.columns - 2, title: action.label, focused: focus === index, children: _jsx(Text, { children: action.description }) }) }, action.actionId)), !snapshot.projectRoot
4123
+ ? _jsx(StaticRow, { title: "\u5F53\u524D\u6CA1\u6709\u9879\u76EE\uFF1B\u4E2A\u4EBA\u6E05\u5355\u4ECD\u53EF\u4F7F\u7528\u3002", status: "muted", compact: true }) : null] });
4124
+ }
4125
+ else if (scopedFlow) {
4126
+ const flow = scopedFlow;
4127
+ const ReviewSurface = showTechnical ? TechnicalDetails : ReviewCard;
4128
+ const ResultSurface = showTechnical ? TechnicalDetails : ResultCard;
4129
+ const detailsAction = { label: showTechnical ? '返回摘要 [d]' : '查看详情 [d]',
4130
+ run: () => { setShowTechnical((value) => !value); setBatchTargetOffset(0); } };
4131
+ breadcrumb = `插件清单 / ${flow.scope === 'user' ? '个人' : '当前项目'}`;
4132
+ context = undefined;
4133
+ if (flow.stage === 'agents') {
4134
+ title = 'Agent 管理';
4135
+ description = flow.pluginId ? `选择 ${flow.pluginId.split('/').pop()} 在哪些 Agent 中使用`
4136
+ : '选择这台电脑上使用的 Agent';
4137
+ breadcrumb = flow.pluginId ? '插件 / Agent 管理' : '首页 / Agent 管理';
4138
+ content = _jsx(AgentManagement, { controller: props.controller, settings: flow.settings, pluginId: flow.pluginId, columns: size.columns, rows: size.rows, summaries: agentSummaries(flow.pluginId), active: !overlay, onBack: closeScopedFlow, onExit: requestConfirmedExit, onSaved: setAgentSettings, onHome: () => { closeScopedFlow(); requestSessionNavigation('home'); }, onReload: () => { closeScopedFlow(); requestSessionNavigation('reload'); }, onHelp: () => { setOverlayFocus(0); setHelpPage(1); setOverlay('help'); }, onSync: () => beginScopedTask({ kind: 'recovery', command: 'sync', scope: flow.scope,
4139
+ selectors: flow.pluginId ? [flow.pluginId] : [], retireUnselectedTargets: true }) });
4140
+ help = '↑↓ 移动 · Enter/Space 勾选 · s 保存 · Esc 返回';
4141
+ }
4142
+ else if (flow.stage === 'recoveryScope') {
4143
+ title = flow.command === 'uninstall' ? '选择卸载范围' : '选择清理范围';
4144
+ description = '确认变化后处理此范围,保留其他范围仍需要的内容。';
4145
+ actions = flow.scopes.map((selectedScope) => ({ label: scopeLabel(selectedScope),
4146
+ status: 'error', run: () => beginScopedTask({ kind: 'recovery', command: flow.command,
4147
+ scope: selectedScope, selectors: flow.selectors ?? [flow.selector] }) }));
4148
+ content = _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true });
4149
+ }
4150
+ else if (flow.stage === 'intentScope') {
4151
+ title = flow.action === 'add' ? '选择插件需求范围' : '选择要移出的清单';
4152
+ description = flow.action === 'add' ? '保存需求不会立即安装。'
4153
+ : '移出需求后,现有安装仍会保留。';
4154
+ actions = flow.scopes
4155
+ .map((selectedScope) => ({ label: selectedScope === 'user' ? '个人(所有项目)' : '当前项目',
4156
+ run: () => beginScopedTask({ kind: 'intent', action: flow.action, scope: selectedScope,
4157
+ selector: flow.selector }) }));
4158
+ content = _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true });
4159
+ }
4160
+ else if (flow.stage === 'targets') {
4161
+ title = flow.scope === 'user' ? '个人插件使用的工具' : '此项目在本机使用的工具';
4162
+ description = '保存后可单独准备同步任务。';
4163
+ actions = ['codex', 'claude', 'cursor'].map((agent) => ({
4164
+ actionId: `agent:${agent}`, label: `${flow.agents.includes(agent) ? '✓' : '○'} ${agent}`,
4165
+ run: () => setScopedFlow({ ...flow,
4166
+ agents: flow.agents.includes(agent) ? flow.agents.filter((value) => value !== agent)
4167
+ : [...flow.agents, agent].sort(),
4168
+ }),
4169
+ }));
4170
+ actions.push({ actionId: 'targets-save', label: '保存选择', run: () => beginScopedTask({ kind: 'targets', action: 'set', scope: flow.scope, agents: flow.agents }) });
4171
+ actions.push({ actionId: 'targets-reset', label: '重置本机部署偏好', run: () => beginScopedTask({ kind: 'targets', action: 'reset', scope: flow.scope, agents: [] }) });
4172
+ content = _jsx(Box, { flexDirection: "column", children: _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true, limit: Math.max(1, pageActionBudget(3)) }) });
4173
+ help = '↑↓ 移动 · Enter/Space 勾选 · s 保存 · Esc 返回';
4174
+ }
4175
+ else if (flow.stage === 'preparing' || flow.stage === 'progress') {
4176
+ title = flow.stage === 'preparing' ? '正在准备操作汇总' : '正在执行恢复';
4177
+ description = flow.stage === 'preparing' ? '正在检查当前证据。Esc 可取消准备。'
4178
+ : '操作已开始。完成后显示每个目标结果。';
4179
+ readingText = flow.stage === 'progress' ? [
4180
+ ...(snapshot.progress.current
4181
+ ? [`${snapshot.progress.current.target} · ${snapshot.progress.current.phase}`] : []),
4182
+ ...snapshot.progress.messages,
4183
+ ...(flow.plan.kind === 'recovery' ? scopedCommandPlanText(flow.plan) : []),
4184
+ ].join('\n') : undefined;
4185
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(ProgressRow, { title: title, state: "active" }), readingText ? readingPage(readingText, size.columns - 6, Math.max(1, pageActionBudget(4))) : null] });
4186
+ }
4187
+ else if (flow.stage === 'timeout') {
4188
+ title = '设置脚本超时';
4189
+ description = `输入 ${SCRIPT_TIMEOUT_MIN_MS}–${SCRIPT_TIMEOUT_MAX_MS} 毫秒。默认 ${SCRIPT_TIMEOUT_DEFAULT_MS} 毫秒。`;
4190
+ help = '输入 · Enter 应用并重新准备 · Ctrl-U 清空 · Esc 保留原值';
4191
+ content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: scriptTimeoutInput, columns: Math.max(8, size.columns - 8) }), description: scriptTimeoutError ?? '此值只用于本次任务。', status: scriptTimeoutError ? 'error' : undefined });
4192
+ }
4193
+ else if (flow.stage === 'review') {
4194
+ title = '操作汇总';
4195
+ description = '确认需求、偏好、配置及输出影响。';
4196
+ readingText = scopedPlanText(flow.plan, showTechnical);
4197
+ help = '←→ 阅读 · ↑↓ 操作 · Enter 选择 · d 详情 · Esc 返回';
4198
+ actions = [
4199
+ { label: flow.plan.kind === 'targets' ? '确认保存本机工具'
4200
+ : flow.plan.kind === 'intent' ? '确认修改需求' : '确认恢复可执行目标',
4201
+ run: () => scopedRiskReasons(flow.plan).length
4202
+ ? setScopedFlow({ stage: 'risk', scope: flow.scope, request: flow.request,
4203
+ plan: flow.plan, focus: 0 }) : executeScopedReview(flow.plan) },
4204
+ { label: '返回修改', run: closeScopedFlow },
4205
+ detailsAction,
4206
+ ...(flow.request.kind === 'recovery' && batchDraft?.maintenanceTargets?.length
4207
+ && batchDraft.maintenanceTargets.every((target) => target.scope === flow.scope)
4208
+ ? [{ label: '修改处理范围', run: () => {
4209
+ void props.controller.discardScopedPlan(flow.plan);
4210
+ setScopedFlow(undefined);
4211
+ props.controller.push({ name: 'batchPlacement' });
4212
+ setFocus(0);
4213
+ setScrollOffset(0);
4214
+ } }] : []),
4215
+ ...(flow.request.kind === 'recovery' ? [{ label: '修改脚本超时', run: () => {
4216
+ const request = flow.request;
4217
+ if (request.kind !== 'recovery')
4218
+ return;
4219
+ void props.controller.discardScopedPlan(flow.plan);
4220
+ setScriptTimeoutInput(String(request.scriptTimeoutMs ?? SCRIPT_TIMEOUT_DEFAULT_MS));
4221
+ setScriptTimeoutError(undefined);
4222
+ setScopedFlow({ stage: 'timeout', scope: flow.scope, request, focus: 0 });
4223
+ } }] : []),
4224
+ ];
4225
+ content = _jsxs(ReviewSurface, { title: showTechnical ? "技术详情" : "本次变化", children: [readingPage(readingText, size.columns - 6, Math.max(1, pageActionBudget(actions.length + (showTechnical ? 5 : 4)))), _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true })] });
4226
+ }
4227
+ else if (flow.stage === 'risk') {
4228
+ title = '操作汇总';
4229
+ description = '确认需求、偏好、配置及输出影响。';
4230
+ readingText = scopedPlanText(flow.plan, showTechnical);
4231
+ actions = [
4232
+ { label: '返回检查', run: () => setScopedFlow({ ...flow, stage: 'review', focus: 0 }) },
4233
+ { label: '确认并执行', run: () => executeScopedReview(flow.plan) },
4234
+ detailsAction,
4235
+ ];
4236
+ content = _jsxs(ReviewSurface, { title: showTechnical ? "技术详情" : "本次变化", children: [readingPage(readingText, size.columns - 6, Math.max(1, pageActionBudget(actions.length + (showTechnical ? 5 : 4)))), _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true })] });
4237
+ }
4238
+ else {
4239
+ title = '恢复结果';
4240
+ help = '←→ 阅读 · ↑↓ 操作 · Enter 选择 · d 详情 · Esc 返回';
4241
+ description = flow.errorCode ? recoveryReason(flow.errorCode)
4242
+ : flow.receipt ? flow.receipt.ok ? '请求已完成。' : '请求仍有未完成目标。'
4243
+ : '本机工具已保存。';
4244
+ readingText = flow.receipt ? scopedResultText(flow.receipt, showTechnical)
4245
+ : (flow.errorCode ? showTechnical ? flow.errorCode : recoveryReason(flow.errorCode) : undefined) ?? (flow.changed ? '本机工具已保存。' : '本机工具没有变化。');
4246
+ const retryTargets = flow.receipt?.targetResults.filter((target) => (target.execution === 'failed' || target.execution === 'partial')
4247
+ && (target.observation === 'missing' || target.content === 'absent')) ?? [];
4248
+ const retryKeys = new Set(retryTargets.map((target) => `${target.pluginId}\0${target.agent}`));
4249
+ const unknownTargets = flow.receipt?.targetResults.filter((target) => target.observation === 'unknown'
4250
+ && !retryKeys.has(`${target.pluginId}\0${target.agent}`)) ?? [];
4251
+ const reviewStale = flow.receipt
4252
+ ? [...flow.receipt.blockers, ...flow.receipt.errors]
4253
+ .some((entry) => entry.code === 'operationReviewStale')
4254
+ : false;
4255
+ actions = [
4256
+ { label: route.name === 'issueDetail' ? '返回问题详情'
4257
+ : route.name === 'pluginWriteTask' ? '返回插件任务'
4258
+ : route.name === 'pluginDetail' ? '返回插件详情'
4259
+ : route.name === 'intentInventory' ? '返回当前清单'
4260
+ : '返回插件列表', run: closeScopedFlow },
4261
+ detailsAction,
4262
+ ...(reviewStale && flow.request ? [{ label: '重新准备本次操作', run: () => beginScopedTask(flow.request) }] : []),
4263
+ ...(flow.plan?.kind === 'targets' ? [{ label: '立即同步', run: () => beginScopedTask({
4264
+ kind: 'recovery', scope: flow.scope, selectors: [],
4265
+ }) }] : []),
4266
+ ...(unknownTargets.length ? [{ label: `检查未确认项(${unknownTargets.length})`, run: () => {
4267
+ closeScopedFlow();
4268
+ void props.controller.refreshScopedInventory(flow.scope);
4269
+ } }] : []),
4270
+ ...(retryTargets.length ? [{ label: `重试明确失败项(${retryTargets.length})`, run: () => beginScopedTask({
4271
+ kind: 'recovery', scope: flow.scope, command: flow.receipt.command,
4272
+ selectors: [...new Set(retryTargets.map((target) => target.pluginId))],
4273
+ targets: [...new Set(retryTargets.map((target) => target.agent))],
4274
+ exactTargets: retryTargets.map((target) => ({ pluginId: target.pluginId, agent: target.agent })),
4275
+ }) }] : []),
4276
+ ];
4277
+ content = _jsxs(ResultSurface, { status: flow.errorCode || flow.receipt && !flow.receipt.ok ? 'warning' : 'success', title: description, children: [readingPage(readingText, size.columns - 6, Math.max(1, pageActionBudget(actions.length + (showTechnical ? 5 : 4)))), _jsx(SemanticRows, { actions: actions, focus: flow.focus, kind: "action", compact: true })] });
4278
+ }
3697
4279
  }
3698
- else if (route.name === 'projectStatus') {
3699
- const inventory = snapshot.projectInventory;
3700
- const section = snapshot.router.current.context.projectSection ?? 'declared';
3701
- const statusFilter = snapshot.router.current.context.projectStatusFilter ?? 'all';
3702
- const stateLabel = { blocked: '受阻', pending: '待同步', unknown: '未确认', ready: '就绪' };
3703
- const searchedRoots = props.controller.listProjectInventoryRoots(section).filter((root) => queryMatches(query, root.displayName, root.pluginId, root.sourceId));
4280
+ else if (route.name === 'intentInventory') {
4281
+ const inventory = snapshot.scopedInventories[route.scope];
4282
+ const section = snapshot.router.current.context.inventorySection ?? 'declared';
4283
+ const statusFilter = snapshot.router.current.context.inventoryStatusFilter ?? 'all';
4284
+ const allRoots = props.controller.listScopedInventoryRoots(route.scope, section);
4285
+ const searchedRoots = allRoots.filter((root) => queryMatches(query, root.displayName, root.pluginId, root.sourceId));
3704
4286
  const roots = searchedRoots.filter((root) => statusFilter === 'all' || root.state === statusFilter);
3705
- const statusCounts = Object.fromEntries(PROJECT_STATUS_FILTERS.map((filter) => [filter,
4287
+ const filters = ['all', 'blocked', 'unknown', 'pending', 'ready'];
4288
+ const filterLabels = { all: '全部', blocked: '受阻', unknown: '未确认', pending: '待同步', ready: '就绪' };
4289
+ const counts = Object.fromEntries(filters.map((filter) => [filter,
3706
4290
  filter === 'all' ? searchedRoots.length : searchedRoots.filter((root) => root.state === filter).length,
3707
4291
  ]));
3708
4292
  toggleProjectStatusFilter = () => {
3709
- const available = PROJECT_STATUS_FILTERS.filter((filter) => filter === 'all' || filter === statusFilter || statusCounts[filter] > 0);
4293
+ const available = filters.filter((filter) => filter === 'all' || filter === statusFilter || counts[filter] > 0);
3710
4294
  const next = available[(available.indexOf(statusFilter) + 1) % available.length];
3711
- props.controller.updatePageContext({ projectStatusFilter: next });
3712
- dynamicMenuFocus.current = undefined;
3713
- actionJumpAnchor.current = undefined;
4295
+ props.controller.updatePageContext({ inventoryStatusFilter: next });
3714
4296
  setFocus(0);
3715
4297
  setScrollOffset(0);
3716
4298
  };
3717
4299
  moveProjectSection = () => {
3718
- const next = section === 'declared' ? 'extraInstalled' : 'declared';
3719
- props.controller.updatePageContext({ projectSection: next });
3720
- dynamicMenuFocus.current = undefined;
3721
- actionJumpAnchor.current = undefined;
4300
+ props.controller.updatePageContext({ inventorySection: section === 'declared' ? 'extraInstalled' : 'declared' });
3722
4301
  setFocus(0);
3723
4302
  setScrollOffset(0);
3724
4303
  };
3725
- breadcrumb = '项目插件';
3726
- title = showTechnical ? '项目状态安全详情' : inventory?.projectName ?? '项目插件';
3727
- context = undefined;
3728
- description = snapshot.projectInventoryRefresh.state === 'error'
3729
- ? '项目检查失败;以下显示上次检查结果。可重新检查。'
3730
- : inventory?.declarationState === 'notProject' ? '当前不是项目;没有项目同步目标。'
3731
- : inventory?.declarationState === 'absent' ? '未配置项目插件。'
3732
- : inventory?.declarationState === 'invalid' || inventory?.declarationState === 'unavailable' ? '项目声明需要检查。' : undefined;
4304
+ breadcrumb = `插件清单 / ${route.scope === 'user' ? '个人' : '当前项目'}`;
4305
+ title = showTechnical ? '清单安全详情' : route.scope === 'user' ? '个人清单' : '当前项目清单';
4306
+ description = snapshot.scopedInventoryRefresh[route.scope]?.errorCode === 'catalogSessionReloadRequired'
4307
+ ? 'Catalog 已变化;以下保留会话结果。请按 H 重新加载,再检查清单。'
4308
+ : snapshot.scopedInventoryRefresh[route.scope]?.state === 'error'
4309
+ ? '检查失败;以下显示上次结果,当前状态未确认。' : inventory?.declarationState === 'absent'
4310
+ ? '清单尚未添加插件。' : inventory?.declarationState === 'invalid'
4311
+ ? '无法确认需求清单;请查看详情并修正。' : !inventory ? '正在检查清单。'
4312
+ : inventory.selectedAgents.length === 0 && inventory.declaredRoots.length > 0
4313
+ ? `已有 ${inventory.declaredRoots.length} 项需求;本机工具未设置。`
4314
+ : inventory.coverage?.portable === false
4315
+ ? '通用 Skills 额外安装未完整覆盖;不会猜测无法关联的归属。' : undefined;
3733
4316
  actions = roots.map((root) => ({
3734
- actionId: `project-root:${root.pluginId}`, pluginId: root.pluginId,
3735
- label: root.displayName,
4317
+ actionId: `inventory-root:${root.pluginId}`, pluginId: root.pluginId, label: root.displayName,
3736
4318
  run: () => go({ name: 'pluginDetail', pluginId: root.pluginId }),
3737
4319
  }));
3738
- const syncTargets = inventory?.syncTargets ?? [];
3739
- const syncCount = new Set(syncTargets.map((target) => target.pluginId)).size;
3740
- const projectRestoreIds = [...new Set(props.controller.listPluginWriteTaskCandidates('restore')
3741
- .filter((candidate) => candidate.target.scope === 'project')
3742
- .map((candidate) => candidate.target.pluginId))];
4320
+ const selectedKey = route.scope === 'project' ? `project:${route.projectKey ?? ''}` : 'user';
4321
+ const selectedIds = scopedSelected.current[selectedKey] ?? [];
4322
+ const focusedRoot = roots.find((root) => root.pluginId === actions[effectiveFocus]?.pluginId);
4323
+ if (focusedRoot)
4324
+ scopedActiveRoot.current[selectedKey] = focusedRoot.pluginId;
4325
+ const activeRoot = focusedRoot ?? roots.find((root) => root.pluginId === scopedActiveRoot.current[selectedKey]);
3743
4326
  bottomActions = [
3744
- ...(inventory?.projectRoot && syncCount > 0 ? [{ actionId: 'project-sync-actionable',
3745
- label: `同步全部可处理声明(${syncCount}) [c]`, status: 'info',
3746
- run: () => prepareCurrentProjectTargets(syncTargets) }] : []),
3747
- ...(projectRestoreIds.length > 0 ? [{ actionId: 'project-restore',
3748
- label: `修复当前项目(${projectRestoreIds.length})`,
3749
- run: () => openSelectedPluginTask('restore', projectRestoreIds, 'project') }] : []),
3750
- ...(() => {
3751
- const issues = props.controller.listUserIssues().filter((issue) => props.controller.listProjectInventoryRoots('declared').some((root) => issueMatchesTarget(issue, root.pluginId)));
3752
- if (issues.length === 0)
3753
- return [];
3754
- const focusedRoot = roots[focus];
3755
- const selectedIssue = focusedRoot
3756
- ? issues.find((issue) => issueMatchesTarget(issue, focusedRoot.pluginId)) : undefined;
3757
- const issue = selectedIssue ?? (issues.length === 1 ? issues[0] : undefined);
3758
- return [{ actionId: 'project-open-issues',
3759
- label: issue ? '查看对应问题' : '查看需要处理',
3760
- run: () => go(issue
3761
- ? { name: 'issueDetail', issueId: issue.issueId } : { name: 'issueList' }) }];
3762
- })(),
3763
- ...(!showTechnical && (inventory?.counts.blocked || inventory?.counts.unknown || snapshot.projectIntentIssue) ? [{ actionId: 'project-error-details', label: '错误详情', run: toggleTechnical }] : []),
3764
- { actionId: 'project-recheck', label: snapshot.projectInventoryRefresh.state === 'checking' ? '正在检查项目' : '重新检查项目 [r]',
3765
- disabled: snapshot.projectInventoryRefresh.state === 'checking' ? '检查中' : undefined,
3766
- run: async () => { await props.controller.refreshProjectInventory(); } },
4327
+ { actionId: 'inventory-sync-all', label: `同步可执行项(${new Set(inventory?.syncTargets.map((target) => target.pluginId)).size})`,
4328
+ disabled: !inventory?.syncTargets.length ? '当前没有可执行目标' : undefined,
4329
+ run: () => {
4330
+ const syncTargets = inventory?.syncTargets ?? [];
4331
+ beginScopedTask({ kind: 'recovery', scope: route.scope,
4332
+ selectors: [...new Set(syncTargets.map((target) => target.pluginId))],
4333
+ targets: [...new Set(syncTargets.map((target) => target.agent))],
4334
+ exactTargets: syncTargets.map(({ pluginId, agent }) => ({ pluginId, agent })) });
4335
+ } },
4336
+ ...(selectedIds.length ? [{ actionId: 'inventory-sync-selected', label: `同步所选插件(${selectedIds.length})`,
4337
+ run: () => beginScopedTask({ kind: 'recovery', scope: route.scope, selectors: selectedIds }) }] : []),
4338
+ ...(section === 'declared' && activeRoot
4339
+ ? [{ actionId: 'inventory-remove-demand', label: '移除此插件需求(保留安装)',
4340
+ run: () => beginScopedTask({ kind: 'intent', action: 'remove', scope: route.scope,
4341
+ selector: activeRoot.pluginId }) }] : []),
4342
+ ...(activeRoot && activeRoot.sourceState === 'ready'
4343
+ && activeRoot.blockers?.some((blocker) => blocker.code === 'pluginUnavailable')
4344
+ ? [{ actionId: 'inventory-retire', label: '停用已移除插件(保留设置)',
4345
+ run: () => beginScopedTask({ kind: 'retire', scope: route.scope,
4346
+ selector: activeRoot.pluginId, deleteConfig: false }) },
4347
+ { actionId: 'inventory-retire-config', label: '停用已移除插件并删除设置',
4348
+ run: () => beginScopedTask({ kind: 'retire', scope: route.scope,
4349
+ selector: activeRoot.pluginId, deleteConfig: true }) }] : []),
4350
+ { actionId: 'inventory-targets', label: 'Agent 管理', run: () => openAgentManagement() },
4351
+ { actionId: 'inventory-add', label: '添加插件需求', run: () => {
4352
+ setScope(route.scope);
4353
+ go({ name: 'pluginList', mode: 'browse' });
4354
+ } },
4355
+ { actionId: 'inventory-switch', label: '切换清单范围', run: () => go({ name: 'intentScopes' }) },
4356
+ { actionId: 'inventory-recheck', label: '重新检查', run: () => { void props.controller.refreshScopedInventory(route.scope); } },
3767
4357
  ];
3768
- const technicalText = [snapshot.projectIntentIssue ? `${safeDiagnosticCode(snapshot.projectIntentIssue.code, 'projectIntentUnknown')} · ${sanitizeDiagnosticText(snapshot.projectIntentIssue.message)}` : '',
3769
- ...props.controller.listProjectInventoryRoots(section).flatMap((root) => [...projectDependencyLines(root), ...root.targets.flatMap((target) => [
3770
- `${root.pluginId} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
3771
- `目录参考 ${target.expectedVersion ?? '未知'} · 实际 ${target.installedVersion ?? '未知'} · ${stateLabel[target.state]}`,
3772
- ...target.reasonCodes.map((code) => `原因 · ${safeDiagnosticCode(code, 'projectTargetUnknown')}`)
3773
- ])])].filter(Boolean).join('\n');
3774
- const itemCount = showTechnical ? 0 : actions.length;
3775
- actions = [...(showTechnical ? [] : actions), ...bottomActions];
4358
+ actions = [...actions, ...bottomActions];
3776
4359
  dynamicActionIds = actions.map((action) => action.actionId);
3777
4360
  dynamicActionSignature = dynamicActionIds.join('\0');
3778
- const previousProjectFocus = dynamicMenuFocus.current;
3779
- if (previousProjectFocus?.routeKey === routeKey
3780
- && previousProjectFocus.actionIds !== dynamicActionSignature) {
3781
- const retained = previousProjectFocus.focusedActionId
3782
- ? dynamicActionIds.indexOf(previousProjectFocus.focusedActionId) : -1;
3783
- effectiveFocus = retained >= 0 ? retained
3784
- : itemCount > 0 ? 0 : dynamicActionIds.indexOf('project-recheck');
3785
- }
3786
- help = '↑↓ 移动 · Enter 查看 · Esc 返回';
4361
+ effectiveFocus = Math.min(focus, Math.max(0, actions.length - 1));
4362
+ help = '↑↓ 移动 · Enter 详情 · Space 勾选 · Esc 返回 · [a] 操作';
4363
+ const cardWidth = size.columns - 2;
4364
+ const cardSubjects = roots.map((root) => props.controller.getPluginDetailSubject(root.pluginId));
4365
+ const cardLayouts = roots.map((root, index) => projectRootCardLayout(root, cardSubjects[index], cardWidth));
4366
+ const lineLimit = Math.max(1, Math.min(3, pageActionBudget(3) - 4));
4367
+ entityCardNavigation = { itemCount: roots.length, factRows: cardLayouts.map((card) => card.factRows), lineLimit };
4368
+ const viewport = rowViewport({
4369
+ rowHeights: [...cardLayouts.map((card, index) => entityCardWindowHeight(card, index === effectiveFocus ? batchTargetOffset : 0, lineLimit)), ...bottomActions.map(() => 1)],
4370
+ focus: effectiveFocus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(4),
4371
+ });
4372
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(QueryHeader, { width: size.columns - 2, tabs: { items: [
4373
+ { id: 'declared', label: '声明插件', count: props.controller.listScopedInventoryRoots(route.scope, 'declared').length },
4374
+ { id: 'extraInstalled', label: '额外安装', count: props.controller.listScopedInventoryRoots(route.scope, 'extraInstalled').length },
4375
+ ], currentId: section }, search: { value: query, placeholder: '名称或来源', input: searchFocus
4376
+ ? _jsx(InputDisplay, { value: query, cursor: searchCursor, columns: querySearchWidth(size.columns - 2) }) : undefined }, filter: { label: filterLabels[statusFilter], count: counts[statusFilter],
4377
+ currentZero: counts[statusFilter] === 0 && statusFilter !== 'all' }, selection: selectedIds.length ? { total: selectedIds.length,
4378
+ hidden: selectedIds.filter((id) => !roots.some((root) => root.pluginId === id)).length } : undefined }), _jsx(StaticRow, { title: `本机工具 ${inventory?.selectedAgents.map(agentLabel).join('、') || '未设置'}`, compact: true }), roots.length === 0 ? _jsx(StaticRow, { title: query || statusFilter !== 'all' ? '当前条件无匹配;筛选保持原样。'
4379
+ : inventory?.declarationState === 'absent' ? '清单尚未添加插件。' : '当前分区没有插件。', compact: true }) : null, _jsx(ContentViewport, { children: actions.slice(viewport.offset, viewport.offset + viewport.limit).map((action, visibleIndex) => {
4380
+ const index = viewport.offset + visibleIndex;
4381
+ if (index >= roots.length)
4382
+ return _jsx(SemanticRows, { actions: actions, focus: searchFocus ? -1 : effectiveFocus, kind: "action", compact: true, offset: index, limit: 1 }, action.actionId);
4383
+ return _jsx(ProjectRootCard, { root: roots[index], subject: { ...cardSubjects[index], displayName: `${selectedIds.includes(roots[index].pluginId) ? '✓ ' : ''}${cardSubjects[index].displayName}` }, width: cardWidth, focused: !searchFocus && effectiveFocus === index, lineOffset: effectiveFocus === index ? batchTargetOffset : 0, lineLimit: lineLimit }, action.actionId);
4384
+ }) })] });
3787
4385
  if (showTechnical) {
3788
- readingText = technicalText;
3789
- content = _jsxs(Box, { flexDirection: "column", children: [readingPage(technicalText, size.columns - 2, Math.max(1, pageActionBudget(4) - 1)), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: true })] });
3790
- }
3791
- else {
3792
- const cardWidth = size.columns - 2;
3793
- const lineLimit = Math.max(1, Math.min(3, pageActionBudget(3) - 4));
3794
- const cardSubjects = roots.map((root) => props.controller.getPluginDetailSubject(root.pluginId));
3795
- const cardLayouts = roots.map((root, index) => projectRootCardLayout(root, cardSubjects[index], cardWidth));
3796
- entityCardNavigation = { itemCount, factRows: cardLayouts.map((card) => card.factRows), lineLimit };
3797
- const emptyHint = roots.length === 0 && inventory?.declarationState === 'ready'
3798
- && Boolean(searchedRoots.length > 0 || query || statusFilter !== 'all');
3799
- const projectViewport = rowViewport({
3800
- rowHeights: [...cardLayouts.map((card, index) => entityCardWindowHeight(card, index === effectiveFocus ? batchTargetOffset : 0, lineLimit)), ...bottomActions.map(() => 1)],
3801
- focus: effectiveFocus, requestedOffset: scrollOffset,
3802
- rowBudget: pageActionBudget(3 + Number(emptyHint)),
3803
- });
3804
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(QueryHeader, { width: size.columns - 2, tabs: { items: [
3805
- { id: 'declared', label: '项目声明', count: props.controller.listProjectInventoryRoots('declared').filter((root) => queryMatches(query, root.displayName, root.pluginId, root.sourceId)).length },
3806
- { id: 'extraInstalled', label: '额外安装', count: props.controller.listProjectInventoryRoots('extraInstalled').filter((root) => queryMatches(query, root.displayName, root.pluginId, root.sourceId)).length },
3807
- ], currentId: section }, search: { value: query, placeholder: '名称或来源', input: searchFocus
3808
- ? _jsx(InputDisplay, { value: query, cursor: searchCursor, columns: querySearchWidth(size.columns - 2) }) : undefined }, filter: { label: PROJECT_STATUS_LABELS[statusFilter], count: statusCounts[statusFilter],
3809
- currentZero: statusCounts[statusFilter] === 0 && statusFilter !== 'all' } }), emptyHint
3810
- ? _jsx(Text, { color: tuiTheme.roles.secondary, children: "\u5F53\u524D\u6761\u4EF6\u65E0\u5339\u914D\uFF1B\u641C\u7D22\u548C\u7B5B\u9009\u4FDD\u6301\u539F\u6837\u3002" }) : null, _jsx(ContentViewport, { children: actions.slice(projectViewport.offset, projectViewport.offset + projectViewport.limit)
3811
- .map((action, visibleIndex) => {
3812
- const index = projectViewport.offset + visibleIndex;
3813
- if (index >= itemCount)
3814
- return _jsx(SemanticRows, { actions: actions, focus: searchFocus ? -1 : effectiveFocus, kind: "action", compact: true, offset: index, limit: 1 }, action.actionId);
3815
- return _jsx(ProjectRootCard, { root: roots[index], subject: cardSubjects[index], width: cardWidth, focused: !searchFocus && effectiveFocus === index, lineOffset: effectiveFocus === index ? batchTargetOffset : 0, lineLimit: lineLimit }, action.actionId);
3816
- }) })] });
4386
+ readingText = [
4387
+ `范围:${route.scope === 'user' ? '个人' : '当前项目'}`,
4388
+ `需求清单:${inventory?.declarationState ?? '未检查'}`,
4389
+ `本机偏好:${inventory?.preferenceState ?? '未检查'}`,
4390
+ ...(inventory?.coverage?.portable === false ? ['通用 Skills 覆盖:未完整覆盖'] : []),
4391
+ ...(inventory?.errors.map((error) => `原因:${safeDiagnosticCode(error.code, 'inventoryUnavailable')}`) ?? []),
4392
+ ...roots.flatMap((root) => [root.pluginId,
4393
+ ...(root.blockers ?? []).map((reason) => safeDiagnosticCode(reason.code, 'inventoryUnavailable')),
4394
+ ...(root.agentResults ?? []).flatMap((target) => [
4395
+ `${target.agent} · ${target.observation} · ${target.content}`,
4396
+ ...target.blockers.map((reason) => safeDiagnosticCode(reason.code, 'targetUnavailable')),
4397
+ ...(target.reasons ?? []).map((reason) => `${reason.code} · ${reason.message}`),
4398
+ ]),
4399
+ ]),
4400
+ ...(snapshot.scopedInventoryRefresh[route.scope]?.state === 'error'
4401
+ ? [`上次快照已过期:${safeDiagnosticCode(snapshot.scopedInventoryRefresh[route.scope]?.errorCode, 'inventoryRefreshFailed')}`] : []),
4402
+ '重新检查只读取当前范围;不会安装或清理。',
4403
+ ].join('\n');
4404
+ actions = [{ label: '返回清单', run: toggleTechnical }];
4405
+ content = _jsxs(Box, { flexDirection: "column", children: [readingPage(readingText, size.columns - 2, pageActionBudget(3)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
3817
4406
  }
3818
4407
  }
3819
4408
  else if (route.name === 'pluginList') {
@@ -3936,9 +4525,33 @@ export function ConsoleAppV4(props) {
3936
4525
  description: item.configOnly
3937
4526
  ? '来源当前没有这个插件;可以查看已保存设置或定点重置设置层。'
3938
4527
  : item.description,
3939
- ...(item.primaryAttention ? { attention: item.primaryAttention.message } : {}),
3940
- placementSummary: '',
3941
- tags: pluginPlatformBadges(item),
4528
+ placementSummary: (() => {
4529
+ const subject = props.controller.getPluginDetailSubject(item.pluginId);
4530
+ const targets = agentSettings?.preferences.targets ?? [];
4531
+ return targets.filter((target) => !subject?.supportedPlatforms
4532
+ || subject.supportedPlatforms.includes(target)
4533
+ || subject.placements.some((entry) => (entry.agent ?? entry.platform) === target)).map((target) => {
4534
+ const excluded = agentSettings?.preferences.exclusions.some((entry) => entry.plugin === item.pluginId && entry.target === target);
4535
+ const facts = subject?.placements.filter((entry) => (entry.agent ?? entry.platform) === target) ?? [];
4536
+ const symbol = excluded ? '⊘' : facts.some((entry) => entry.projectState === 'blocked' || entry.statusState === 'blocked') ? '×'
4537
+ : facts.some((entry) => entry.projectState === 'pending' || entry.statusState === 'needsSync') ? '↻'
4538
+ : facts.length && facts.every((entry) => entry.observation === 'known' && (entry.projectState === 'ready' || entry.statusState === 'ready')) ? '✓' : '?';
4539
+ return `${symbol} ${agentLabel(target)}`;
4540
+ }).join(' ');
4541
+ })(),
4542
+ tags: (() => {
4543
+ const subject = props.controller.getPluginDetailSubject(item.pluginId);
4544
+ const badges = ['user', 'project'].flatMap((placementScope) => {
4545
+ const direct = subject?.declarations[placementScope].state === 'declared';
4546
+ const facts = subject?.placements.filter((entry) => entry.scope === placementScope) ?? [];
4547
+ if (!direct && !facts.length)
4548
+ return [];
4549
+ const ready = facts.length > 0 && facts.every((entry) => entry.observation === 'known'
4550
+ && (entry.projectState === 'ready' || entry.statusState === 'ready'));
4551
+ return [_jsxs(Text, { backgroundColor: placementScope === 'user' ? '#543e78' : '#234b70', children: [direct ? ready ? '■' : '□' : facts.some((entry) => entry.statusState === 'orphaned') ? '!' : '▣', " ", scopeLabel(placementScope), direct ? ready ? '' : ' 待安装' : facts.some((entry) => entry.statusState === 'orphaned') ? ' 可清理' : ' 依赖'] }, placementScope)];
4552
+ });
4553
+ return badges.length ? _jsx(Text, { children: badges }) : _jsx(Text, { color: tuiTheme.roles.secondary, children: "\u25CB \u5C1A\u672A\u6DFB\u52A0" });
4554
+ })(),
3942
4555
  statusLabel: statusText(item),
3943
4556
  status: pluginRowStatus(item),
3944
4557
  }]),
@@ -4012,7 +4625,7 @@ export function ConsoleAppV4(props) {
4012
4625
  ...pluginRows[index], width: size.columns - 2,
4013
4626
  hasTags: Boolean(pluginRows[index].tags),
4014
4627
  }).height : 1),
4015
- focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(3),
4628
+ focus, requestedOffset: scrollOffset, rowBudget: pageActionBudget(4),
4016
4629
  });
4017
4630
  content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(QueryHeader, { width: size.columns - 2, tabs: { label: '来源', items: sourceTabs, currentId: sourceId }, search: { value: query, placeholder: '名称或用途', input: searchFocus
4018
4631
  ? _jsx(InputDisplay, { value: query, cursor: searchCursor, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: querySearchWidth(size.columns - 2) }) : undefined }, filter: { label: pluginFilterLabel(installFilter), count: filterCounts[installFilter],
@@ -4021,8 +4634,8 @@ export function ConsoleAppV4(props) {
4021
4634
  : route.mode === 'installed' && items.length === 0 && !query && sourceId === 'all'
4022
4635
  ? installFilter === 'updateAvailable'
4023
4636
  ? _jsx(Callout, { title: "\u6CA1\u6709\u53EF\u66F4\u65B0\u63D2\u4EF6", description: "\u5F53\u524D\u7B5B\u9009\u8303\u56F4\u5185\u6CA1\u6709\u7ECF\u8FC7\u7248\u672C\u8BC1\u636E\u786E\u8BA4\u7684\u66F4\u65B0\u3002" })
4024
- : _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
4025
- : null, _jsxs(ContentViewport, { children: [_jsx(PluginRows, { columns: size.columns - 2, rows: pluginRows, focus: focus, viewport: viewport, active: !searchFocus }), _jsx(SemanticRows, { actions: actions, focus: searchFocus ? -1 : focus, kind: "action", compact: true, offset: Math.max(pluginRows.length, viewport.offset), limit: Math.max(0, viewport.offset + viewport.limit - Math.max(pluginRows.length, viewport.offset)) })] })] }));
4637
+ : _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u5148\u9009\u62E9\u672C\u673A\u4F7F\u7528\u7684 Agent\uFF0C\u518D\u6DFB\u52A0\u63D2\u4EF6\u3002" })
4638
+ : null, _jsxs(ContentViewport, { children: [_jsx(PluginRows, { columns: size.columns - 2, rows: pluginRows, focus: focus, viewport: viewport, active: !searchFocus }), _jsx(SemanticRows, { actions: actions, focus: searchFocus ? -1 : focus, kind: "action", compact: true, offset: Math.max(pluginRows.length, viewport.offset), limit: Math.max(0, viewport.offset + viewport.limit - Math.max(pluginRows.length, viewport.offset)) })] }), _jsx(Text, { color: tuiTheme.roles.secondary, children: size.columns < 80 ? '■ 已安装 □ 待安装 ▣ 依赖' : '■ 已安装 □ 待安装 ▣ 依赖 │ ✓ 正常 ↻ 待同步 × 失败 ? 未知 ⊘ 已关闭' })] }));
4026
4639
  }
4027
4640
  else if (route.name === 'batchSelection') {
4028
4641
  breadcrumb = '插件 / 选择多个';
@@ -4187,6 +4800,21 @@ export function ConsoleAppV4(props) {
4187
4800
  setActionError('已选插件状态已变化。请返回选择页检查并取消失效项。');
4188
4801
  return;
4189
4802
  }
4803
+ const subjects = selectedIds.flatMap((id) => props.controller.getPluginDetailSubject(id) ?? []);
4804
+ if (subjects.some((subject) => subject.placements.some((placement) => placement.agent
4805
+ && placement.agent !== 'codex' && placement.agent !== 'claude' && placement.actual))) {
4806
+ const scopes = ['user', 'project'].filter((candidate) => subjects.some((subject) => subject.placements.some((placement) => placement.scope === candidate && placement.actual)
4807
+ || subject.declarations[candidate].state === 'declared'));
4808
+ if ((action === 'uninstall' || action === 'cleanup') && scopes.length > 1) {
4809
+ setScopedFlow({ stage: 'recoveryScope', scope, selector: selectedIds[0],
4810
+ selectors: selectedIds, command: action, scopes: [...scopes], focus: 0 });
4811
+ }
4812
+ else
4813
+ beginScopedTask({ kind: 'recovery', command: action,
4814
+ scope: action === 'sync' || action === 'reinstall'
4815
+ ? scopes.includes('project') ? 'project' : 'user' : scopes[0], selectors: selectedIds });
4816
+ return;
4817
+ }
4190
4818
  const exactTargets = props.controller.listPluginWriteBatchTargets(action, selectedIds);
4191
4819
  if (exactTargets.length === 0) {
4192
4820
  setActionError('所选插件没有可用的已有安装位置。');
@@ -4208,11 +4836,14 @@ export function ConsoleAppV4(props) {
4208
4836
  };
4209
4837
  const syncTargets = props.controller.listPluginWriteBatchTargets('sync', selectedIds);
4210
4838
  const uninstallTargets = props.controller.listPluginWriteBatchTargets('uninstall', selectedIds);
4839
+ const portableTargetCount = selectedIds.flatMap((id) => props.controller.getPluginDetailSubject(id)?.placements ?? [])
4840
+ .filter((placement) => placement.actual && placement.agent && placement.agent !== 'codex'
4841
+ && placement.agent !== 'claude').length;
4211
4842
  if (batchDraft.stage === 'maintenance') {
4212
4843
  actions = [
4213
- { label: '同步已保存使用设置', status: 'info', description: `重新应用 ${syncTargets.length} 个已保存或现场目标`, run: () => startExistingBatch('sync') },
4214
- { label: '重新安装', status: 'info', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
4215
- { label: '清理遗留内容', status: 'error', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length} 个已核验位置;保留不属于插件或被共用的内容`, run: () => startExistingBatch('cleanup') },
4844
+ { label: '同步已保存使用设置', status: 'info', description: `重新应用 ${syncTargets.length + portableTargetCount} 个已保存或现场目标`, run: () => startExistingBatch('sync') },
4845
+ { label: '重新安装', status: 'info', description: `重新生成 ${props.controller.listPluginWriteBatchTargets('reinstall', selectedIds).length + portableTargetCount} 个已保存或现场目标`, run: () => startExistingBatch('reinstall') },
4846
+ { label: '清理遗留内容', status: 'error', description: `${props.controller.listPluginWriteBatchTargets('cleanup', selectedIds).length + portableTargetCount} 个已核验位置;保留不属于插件或被共用的内容`, run: () => startExistingBatch('cleanup') },
4216
4847
  {
4217
4848
  role: 'navigation', label: '返回任务选择',
4218
4849
  run: () => moveBatchStage('task'),
@@ -4242,7 +4873,7 @@ export function ConsoleAppV4(props) {
4242
4873
  {
4243
4874
  label: '安装到缺失位置',
4244
4875
  description: `${addTargetCount} 个当前工具和范围内的可安装位置`,
4245
- disabled: addTargetCount === 0 ? '当前选择没有可安装位置' : undefined,
4876
+ disabled: !agentSettings?.preferences.targets.length ? '请先选择 Agent' : undefined,
4246
4877
  run: () => {
4247
4878
  setBatchDraft((current) => current ? { ...current, action: 'install' } : current);
4248
4879
  props.controller.push({ name: 'batchInstallPlatforms' });
@@ -4250,7 +4881,9 @@ export function ConsoleAppV4(props) {
4250
4881
  setScrollOffset(0);
4251
4882
  },
4252
4883
  },
4253
- { label: '卸载所选插件', description: `${uninstallTargets.length} 个现有位置;可在下一页删减范围;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
4884
+ ...(!agentSettings?.preferences.targets.length
4885
+ ? [{ label: '打开 Agent 管理', run: () => openAgentManagement() }] : []),
4886
+ { label: '卸载所选插件', description: `${uninstallTargets.length + portableTargetCount} 个现有位置;选择范围后确认;保留设置`, status: 'error', run: () => startExistingBatch('uninstall') },
4254
4887
  {
4255
4888
  label: '其他维护方式',
4256
4889
  description: '同步、重新安装和普通清理',
@@ -4266,63 +4899,21 @@ export function ConsoleAppV4(props) {
4266
4899
  }
4267
4900
  }
4268
4901
  else if (route.name === 'batchInstallPlatforms' && batchDraft?.action === 'install') {
4269
- breadcrumb = '插件 / 批量安装 / 平台';
4270
- context = `第 1 步,共 3 步 · ${batchDraft.selectedPluginIds.length} 个插件`;
4271
- title = '选择安装平台';
4272
- description = '先选择原生平台。下一步再选择使用范围和检查精确目标。';
4273
- const selectedPlatforms = batchDraft.platform === 'both'
4274
- ? new Set(['codex', 'claude'])
4275
- : new Set([batchDraft.platform]);
4276
- const togglePlatform = (platform) => {
4277
- setBatchDraft((current) => {
4278
- if (!current)
4279
- return current;
4280
- const next = current.platform === 'both'
4281
- ? new Set(['codex', 'claude'])
4282
- : new Set([current.platform]);
4283
- if (next.has(platform)) {
4284
- if (next.size === 1) {
4285
- setActionError('至少选择一个目标平台。');
4286
- return current;
4287
- }
4288
- next.delete(platform);
4289
- }
4290
- else
4291
- next.add(platform);
4292
- setActionError(undefined);
4293
- return { ...current, platform: next.size === 2 ? 'both' : [...next][0] };
4294
- });
4295
- };
4296
- const confirmPlatforms = {
4297
- actionId: 'batch-install-platforms-confirm',
4298
- label: '确定所选平台 [c]',
4299
- description: `${[...selectedPlatforms].map(platformLabel).join('、')} · 继续选择目标`,
4300
- run: () => {
4301
- props.controller.push({ name: 'batchInstallScope' });
4302
- setFocus(0);
4303
- setScrollOffset(0);
4304
- },
4305
- };
4306
- actions = [
4307
- {
4308
- actionId: 'batch-install-platform-codex',
4309
- label: 'Codex',
4310
- description: '添加到 Codex 的合法缺失位置',
4311
- selected: selectedPlatforms.has('codex'),
4312
- run: () => togglePlatform('codex'),
4313
- },
4314
- {
4315
- actionId: 'batch-install-platform-claude',
4316
- label: 'Claude Code',
4317
- description: '添加到 Claude Code 的合法缺失位置',
4318
- selected: selectedPlatforms.has('claude'),
4319
- run: () => togglePlatform('claude'),
4320
- },
4321
- confirmPlatforms,
4322
- ];
4323
- bottomActions = [confirmPlatforms];
4324
- help = '↑↓ 移动 · Space/Enter 切换 · c 确定所选平台 · Esc 返回任务选择';
4325
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ContentViewport, { children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: 2 })] }) });
4902
+ breadcrumb = '插件 / 批量添加';
4903
+ title = '选择使用范围';
4904
+ description = `${batchDraft.selectedPluginIds.length} 个插件使用本机已选择的 Agent。`;
4905
+ const configured = Boolean(agentSettings?.preferences.targets.length);
4906
+ actions = (snapshot.projectRoot ? ['user', 'project'] : ['user']).map((selectedScope) => ({
4907
+ label: selectedScope === 'user' ? '■ 所有项目' : '■ 当前项目',
4908
+ disabled: configured ? undefined : '请先选择 Agent',
4909
+ run: () => beginScopedTask({ kind: 'recovery', command: 'install', scope: selectedScope,
4910
+ selectors: batchDraft.selectedPluginIds }),
4911
+ }));
4912
+ if (!configured)
4913
+ actions.push({ label: '打开 Agent 管理', run: () => openAgentManagement() });
4914
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: true }), _jsxs(Text, { color: tuiTheme.roles.focus, wrap: "wrap", children: ["\u5C06\u5B89\u88C5\u5230 ", agentSettings?.preferences.targets
4915
+ .map((target) => `◆ ${agentLabel(target)}`).join(' ') || '请先选择 Agent'] })] });
4916
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
4326
4917
  }
4327
4918
  else if (route.name === 'batchInstallScope' || route.name === 'batchPlacement') {
4328
4919
  breadcrumb = route.name === 'batchInstallScope'
@@ -4337,9 +4928,7 @@ export function ConsoleAppV4(props) {
4337
4928
  : batchDraft?.action ? batchActionLabel(batchDraft.action) : '处理';
4338
4929
  title = route.name === 'batchInstallScope' ? '选择安装目标'
4339
4930
  : maintenanceMode ? `确认批量${maintenanceLabel}范围`
4340
- : batchDraft?.selection === 'currentProjectIntentRoots'
4341
- ? '同步当前项目全部保存目标'
4342
- : `设置批量${batchDraft?.action ? batchActionLabel(batchDraft.action) : ''}位置`;
4931
+ : `设置批量${batchDraft?.action ? batchActionLabel(batchDraft.action) : ''}位置`;
4343
4932
  description = route.name === 'batchInstallScope'
4344
4933
  ? '选择范围后检查可添加、已有和受阻的精确位置。操作汇总之后才执行。'
4345
4934
  : maintenanceMode
@@ -4409,7 +4998,6 @@ export function ConsoleAppV4(props) {
4409
4998
  bottomActions = [confirmMaintenance];
4410
4999
  }
4411
5000
  else {
4412
- const projectIntent = batchDraft.selection === 'currentProjectIntentRoots';
4413
5001
  installPlacementTargets = batchDraft.action === 'install'
4414
5002
  && batchDraft.selection === 'selected'
4415
5003
  ? props.controller.listPluginInstallBatchTargets(batchDraft.selectedPluginIds, batchDraft.platform, batchDraft.scope)
@@ -4430,7 +5018,6 @@ export function ConsoleAppV4(props) {
4430
5018
  {
4431
5019
  actionId: 'batch-placement-scope-user',
4432
5020
  label: '所有项目', selected: batchDraft.scope === 'user',
4433
- disabled: projectIntent ? '项目全部同步固定使用当前项目' : undefined,
4434
5021
  run: () => setBatchDraft((current) => current ? { ...current, scope: 'user' } : current),
4435
5022
  },
4436
5023
  ],
@@ -4455,7 +5042,7 @@ export function ConsoleAppV4(props) {
4455
5042
  : `查看批量${batchActionLabel(batchDraft.action)}确认`,
4456
5043
  description: batchDraft.action === 'install'
4457
5044
  ? `${addTargets.length} 个可添加 · ${existingTargets.length} 个已有 · ${blockedTargets.length} 个受阻`
4458
- : `${batchDraft.selection === 'selected' ? `${batchDraft.selectedPluginIds.length} 个所选插件` : '当前项目全部保存目标'} · ${scopeLabel(batchDraft.scope)}`,
5045
+ : `${batchDraft.selectedPluginIds.length} 个所选插件 · ${scopeLabel(batchDraft.scope)}`,
4459
5046
  disabled: batchDraft.selection === 'selected' && batchDraft.selectedPluginIds.length === 0
4460
5047
  ? '请至少选择一个插件'
4461
5048
  : batchDraft.action === 'install' && addTargets.length === 0
@@ -4473,13 +5060,11 @@ export function ConsoleAppV4(props) {
4473
5060
  ? { kind: 'targets', targets: addTargets.map(({ pluginId, platform, scope }) => ({
4474
5061
  pluginId, platform, scope,
4475
5062
  })) }
4476
- : batchDraft.selection === 'selected'
4477
- ? {
4478
- kind: 'selected',
4479
- pluginIds: [...new Set(batchDraft.selectedPluginIds)]
4480
- .sort((left, right) => left.localeCompare(right, 'en')),
4481
- }
4482
- : { kind: 'currentProjectIntentRoots' },
5063
+ : {
5064
+ kind: 'selected',
5065
+ pluginIds: [...new Set(batchDraft.selectedPluginIds)]
5066
+ .sort((left, right) => left.localeCompare(right, 'en')),
5067
+ },
4483
5068
  platform: addTargets[0]?.platform ?? batchDraft.platform,
4484
5069
  scope: addTargets[0]?.scope ?? batchDraft.scope,
4485
5070
  };
@@ -4667,7 +5252,7 @@ export function ConsoleAppV4(props) {
4667
5252
  title = '输入 Agent ID';
4668
5253
  description = '多个 Agent 可使用逗号或空格分隔。';
4669
5254
  help = '输入 Agent ID · Enter 保存 · Ctrl-U 清空输入 · Esc 返回';
4670
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: portableAgentInput, columns: Math.max(8, size.columns - 8) }), description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
5255
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: portableAgentInput, columns: Math.max(8, size.columns - 8) }), description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, opencode, generic", status: "muted", compact: true })] });
4671
5256
  }
4672
5257
  else if (route.name === 'portableScope' && plugin && snapshot.portableDraft) {
4673
5258
  breadcrumb = `${plugin.displayName} / 通用 Skills / 范围`;
@@ -4700,7 +5285,7 @@ export function ConsoleAppV4(props) {
4700
5285
  { label: '返回问题摘要', run: () => toggleTechnical() },
4701
5286
  ] : [
4702
5287
  ...(route.code === 'requiredConfigMissing' ? [{ label: '完成插件设置', run: () => {
4703
- props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'effective' } });
5288
+ props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: { profile: 'common' } });
4704
5289
  setFocus(0);
4705
5290
  setScrollOffset(0);
4706
5291
  } }] : []),
@@ -4822,207 +5407,24 @@ export function ConsoleAppV4(props) {
4822
5407
  content = _jsx(Box, { flexDirection: "column", children: readingPage(readingText, size.columns - 2, pageActionBudget(2)) });
4823
5408
  help = '←→/PgUp/PgDn 翻页 · y 复制全文 · Esc 返回';
4824
5409
  }
4825
- else if (route.name === 'installTargets' && plugin) {
4826
- breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
4827
- context = '第 1 步,共 2 步';
4828
- title = '选择目标平台';
4829
- description = 'Codex 与 Claude Code 可以同时选择。通用 Agent Skills 使用独立安装流程。';
4830
- const choices = ['codex', 'claude'].map((platform) => {
4831
- const scopes = ['project', 'user'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
4832
- .some((entry) => entry.platform === platform && entry.compatible));
4833
- const evidence = ['project', 'user'].flatMap((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
4834
- .filter((entry) => entry.platform === platform));
4835
- return {
4836
- platform,
4837
- compatible: scopes.length > 0,
4838
- scopes,
4839
- disabled: scopes.length > 0 ? undefined
4840
- : evidence.find((entry) => entry.disabled)?.disabled ?? '没有可信且兼容的缺失位置',
4841
- };
4842
- });
4843
- const selectedNativeTargets = snapshot.installPlacementDraft?.pluginId === plugin.pluginId
4844
- ? snapshot.installPlacementDraft.platforms
4845
- : selectedTargets;
4846
- const validSelectedTargets = selectedNativeTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
4847
- const allUnavailable = choices.every((choice) => !choice.compatible) && !plugin.portableSkills;
4848
- if (route.view === 'blocked' || allUnavailable) {
4849
- context = '依赖限制';
4850
- title = '无法添加到所选目标平台';
4851
- description = '根插件或必需依赖不支持当前目标平台组合,因此不会进入确认页。';
4852
- actions = [
4853
- { label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
4854
- { role: 'navigation', label: '返回插件详情', run: back },
4855
- ];
4856
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u6240\u9009\u76EE\u6807\u5E73\u53F0\u4E0D\u53EF\u7528", children: choices.map((choice) => _jsx(StaticRow, { title: `${platformLabel(choice.platform)} · ${choice.disabled ? `不可选 · ${choice.disabled}` : '不可选'}`, status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
4857
- }
4858
- else {
4859
- const confirmNativePlatforms = () => {
4860
- if (validSelectedTargets.length === 0) {
4861
- setActionError('请至少选择一个目标平台。');
4862
- return;
4863
- }
4864
- setDirty(false);
4865
- props.controller.setInstallPlatforms(validSelectedTargets);
4866
- setInstallPlatformIndex(0);
4867
- go({ name: 'installPlacement', pluginId: plugin.pluginId });
4868
- };
4869
- const nativeActions = choices.map((choice) => {
4870
- const installed = installedScopeStates(plugin, choice.platform);
4871
- const configuredScopes = plugin.supportedScopes?.[choice.platform];
4872
- const supportedScopes = ['project', 'user'].filter((candidateScope) => configuredScopes?.includes(candidateScope)
4873
- || installed.has(candidateScope) || choice.scopes.includes(candidateScope));
4874
- const locationItems = supportedScopes.map((candidateScope) => {
4875
- const state = installed.get(candidateScope);
4876
- if (state)
4877
- return {
4878
- key: `${choice.platform}:${candidateScope}`,
4879
- label: scopeLabel(candidateScope),
4880
- symbol: state.symbol,
4881
- status: state.status,
4882
- };
4883
- return choice.scopes.includes(candidateScope)
4884
- ? { key: `${choice.platform}:${candidateScope}`, label: scopeLabel(candidateScope),
4885
- symbol: '○', status: 'muted' }
4886
- : { key: `${choice.platform}:${candidateScope}`, label: scopeLabel(candidateScope),
4887
- symbol: '?', status: 'muted' };
4888
- });
4889
- const missingLabels = choice.scopes.filter((candidateScope) => !installed.has(candidateScope))
4890
- .map(scopeLabel);
4891
- return {
4892
- actionId: `install-platform-${choice.platform}`,
4893
- label: platformLabel(choice.platform),
4894
- selected: validSelectedTargets.includes(choice.platform),
4895
- description: choice.disabled ? `不可选择:${choice.disabled}` : (missingLabels.length > 0
4896
- ? `可添加到:${missingLabels.join('、')}` : '所有支持位置已经安装'),
4897
- status: choice.disabled ? 'muted' : undefined,
4898
- tags: locationItems.length > 0 ? _jsx(StatusBadge, { items: locationItems }) : undefined,
4899
- run: () => {
4900
- if (!choice.compatible)
4901
- return;
4902
- setActionError(undefined);
4903
- setSelectedTargets((current) => {
4904
- const next = current.includes(choice.platform)
4905
- ? current.filter((platform) => platform !== choice.platform)
4906
- : [...current, choice.platform];
4907
- props.controller.setInstallPlatforms(next);
4908
- return next;
4909
- });
4910
- },
4911
- };
4912
- });
4913
- const portableAction = {
4914
- actionId: 'portable-agent-skills',
4915
- label: '通用 Agent Skills →',
4916
- description: plugin.portableSkills
4917
- ? '通过外部 Skills CLI 安装到支持的 Agent;只安装选定 Skills'
4918
- : '这个插件不提供通用 Agent Skills',
4919
- status: plugin.portableSkills ? undefined : 'muted',
4920
- tags: plugin.portableSkills
4921
- ? _jsx(Text, { color: tuiTheme.colors.accent, children: "[Skills \u53EF\u9009]" })
4922
- : undefined,
4923
- run: () => {
4924
- if (!plugin.portableSkills)
4925
- return;
4926
- beginPortable(plugin.pluginId);
4927
- },
4928
- };
4929
- const confirmAction = {
4930
- actionId: 'confirm-install-platforms',
4931
- label: '确定所选平台 [c]',
4932
- disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
4933
- run: confirmNativePlatforms,
4934
- };
4935
- actions = [...nativeActions, portableAction, confirmAction];
4936
- bottomActions = [confirmAction];
4937
- help = '↑↓ 移动 · Enter/Space 选择 · Esc 返回';
4938
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(0, nativeActions.length).map((action) => ({
4939
- ...action, label: `${action.selected ? '[x]' : '[ ]'} ${action.label}`,
4940
- })), focus: focus, kind: "action", compact: false }), _jsx(SemanticRows, { actions: actions, focus: focus, offset: nativeActions.length, kind: "action", compact: false })] });
4941
- }
4942
- }
4943
- else if (route.name === 'installPlacement' && plugin) {
4944
- breadcrumb = `${plugin.displayName} / 添加 / 使用范围`;
4945
- context = '第 2 步,共 2 步';
4946
- title = `你希望在哪里使用 ${plugin.displayName}?`;
4947
- const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
4948
- const selectedPlatforms = snapshot.installPlacementDraft?.platforms ?? selectedTargets;
4949
- const legalScopes = (platform) => ['project', 'user'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
4950
- .some((entry) => entry.platform === platform && entry.compatible));
4951
- const commonScopes = ['project', 'user'].filter((candidateScope) => selectedPlatforms.every((platform) => legalScopes(platform).includes(candidateScope)));
4952
- const continueCommonScope = async (candidateScope) => {
4953
- setScope(candidateScope);
4954
- props.controller.setInstallRootScope(candidateScope);
4955
- props.controller.setInstallPlatforms(selectedPlatforms);
4956
- if (hasDependencies) {
4957
- const configurable = await props.controller.prepareInstallDependencyPlacement();
4958
- if (configurable) {
4959
- go({ name: 'dependencyPlacement', pluginId: plugin.pluginId });
4960
- return;
4961
- }
4962
- }
4963
- beginReview(props.controller.installPlacementIntent());
4964
- };
4965
- if (commonScopes.length > 0) {
4966
- description = '所选平台使用同一个范围。操作汇总会显示实际目标和依赖计划。';
4967
- actions = commonScopes.map((candidateScope) => ({
4968
- label: scopeLabel(candidateScope),
4969
- description: candidateScope === 'user'
4970
- ? '在任意项目中可用,会改变你的用户环境'
4971
- : '仅供当前项目使用,适合随项目共享',
4972
- meta: candidateScope === 'project' ? '推荐' : undefined,
4973
- run: () => continueCommonScope(candidateScope),
4974
- }));
4975
- }
4976
- else {
4977
- description = '所选平台没有共同范围。请分别确定每个平台的合法位置。';
4978
- const platformIndex = Math.min(installPlatformIndex, Math.max(0, selectedPlatforms.length - 1));
4979
- const platform = selectedPlatforms[platformIndex];
4980
- title = platform ? `${platformLabel(platform)} 的使用范围` : '请返回选择目标平台';
4981
- context = `平台 ${platformIndex + 1} / ${selectedPlatforms.length}`;
4982
- if (platformIndex > 0)
4983
- returnToPreviousInstallPlatform = () => {
4984
- setInstallPlatformIndex(platformIndex - 1);
4985
- setFocus(0);
4986
- setScrollOffset(0);
4987
- };
4988
- actions = platform ? legalScopes(platform).map((candidateScope) => ({
4989
- label: scopeLabel(candidateScope),
4990
- selected: installPlatformScopes[platform] === candidateScope,
4991
- description: platformIndex + 1 < selectedPlatforms.length
4992
- ? `下一步:${platformLabel(selectedPlatforms[platformIndex + 1])} 的使用范围`
4993
- : '下一步:查看这些精确目标的操作汇总',
4994
- run: async () => {
4995
- const nextScopes = { ...installPlatformScopes, [platform]: candidateScope };
4996
- setInstallPlatformScopes(nextScopes);
4997
- if (platformIndex + 1 < selectedPlatforms.length) {
4998
- setInstallPlatformIndex(platformIndex + 1);
4999
- setFocus(0);
5000
- setScrollOffset(0);
5001
- return;
5002
- }
5003
- const exactTargets = selectedPlatforms.flatMap((selectedPlatform) => {
5004
- const selectedScope = nextScopes[selectedPlatform];
5005
- return selectedScope && legalScopes(selectedPlatform).includes(selectedScope)
5006
- ? [{ pluginId: plugin.pluginId, platform: selectedPlatform, scope: selectedScope }] : [];
5007
- });
5008
- if (exactTargets.length !== selectedPlatforms.length) {
5009
- setInstallPlatformIndex(0);
5010
- setFocus(0);
5011
- setScrollOffset(0);
5012
- setActionError('合法位置已变化。请重新确定每个平台的范围。');
5013
- return;
5014
- }
5015
- props.controller.setInstallRootTargets(exactTargets);
5016
- if (hasDependencies && await props.controller.prepareInstallDependencyPlacement()) {
5017
- go({ name: 'dependencyPlacement', pluginId: plugin.pluginId });
5018
- return;
5019
- }
5020
- beginReview(props.controller.installPlacementIntent());
5021
- },
5022
- })) : [];
5023
- }
5024
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsx(Callout, { title: "\u4F9D\u8D56\u8BA1\u5212", description: "\u6240\u9700\u63D2\u4EF6\u4F1A\u81EA\u52A8\u5B89\u6392\uFF1B\u786E\u8BA4\u524D\u53EF\u67E5\u770B\u5F71\u54CD\u3002" }) : null] });
5025
- help = '↑↓ 移动 · Enter 选择/继续 · Esc 返回';
5410
+ else if ((route.name === 'installTargets' || route.name === 'installPlacement') && plugin) {
5411
+ breadcrumb = `${plugin.displayName} / 添加插件`;
5412
+ title = '选择使用范围';
5413
+ context = undefined;
5414
+ description = '插件会使用本机已选择的 Agent。';
5415
+ const configured = Boolean(agentSettings?.preferences.targets.length);
5416
+ actions = (snapshot.projectRoot ? ['user', 'project'] : ['user']).map((selectedScope) => ({
5417
+ label: selectedScope === 'user' ? '■ 所有项目' : `■ 当前项目 · ${path.basename(snapshot.projectRoot ?? '')}`,
5418
+ description: selectedScope === 'user' ? '在你的每个项目中都能使用' : '记录到当前项目,与团队共享',
5419
+ disabled: configured ? undefined : '请先选择 Agent',
5420
+ run: () => beginScopedTask({ kind: 'recovery', command: 'install', scope: selectedScope,
5421
+ selectors: [plugin.pluginId] }),
5422
+ }));
5423
+ if (!configured)
5424
+ actions.push({ label: '打开 Agent 管理', run: () => openAgentManagement() });
5425
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: true }), _jsxs(Text, { color: tuiTheme.roles.focus, wrap: "wrap", children: ["\u5C06\u5B89\u88C5\u5230 ", agentSettings?.preferences.targets
5426
+ .map((target) => `◆ ${agentLabel(target)}`).join(' ') || '请先选择 Agent'] })] });
5427
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
5026
5428
  }
5027
5429
  else if (route.name === 'uninstallTargets' && plugin) {
5028
5430
  breadcrumb = `${plugin.displayName} / 卸载`;
@@ -5216,7 +5618,7 @@ export function ConsoleAppV4(props) {
5216
5618
  props.controller.replace({
5217
5619
  name: 'configFields',
5218
5620
  pluginId: target.pluginId,
5219
- layer: { scope: 'effective' },
5621
+ layer: { profile: 'common' },
5220
5622
  });
5221
5623
  },
5222
5624
  }]];
@@ -5306,19 +5708,25 @@ export function ConsoleAppV4(props) {
5306
5708
  }]];
5307
5709
  })).values()];
5308
5710
  const taskLabel = pluginWriteTaskLabel(route.goal);
5309
- const startTaskReview = (executionPolicy) => {
5711
+ const mixedScopes = new Set(candidates.map((candidate) => candidate.target.scope)).size > 1;
5712
+ const startTaskReview = (executionPolicy, selectedScope) => {
5310
5713
  if (targetSetChanged) {
5311
5714
  setActionError('本次安装位置已变化。请返回调整后重新开始。');
5312
5715
  return;
5313
5716
  }
5314
- props.controller.startPluginWriteTaskReview({
5717
+ if (selectedScope) {
5718
+ beginReview({ kind: 'pluginWriteTask', goal: route.goal,
5719
+ targets: candidates.filter((candidate) => candidate.target.scope === selectedScope)
5720
+ .map((candidate) => candidate.target), executionPolicy }, props.controller.pluginWriteTaskReturnRoute(route));
5721
+ return;
5722
+ }
5723
+ const intent = {
5315
5724
  kind: 'pluginWriteTask',
5316
5725
  goal: route.goal,
5317
5726
  targets: candidates.map((candidate) => candidate.target),
5318
5727
  executionPolicy,
5319
- }, props.controller.pluginWriteTaskReturnRoute(route));
5320
- setFocus(0);
5321
- setScrollOffset(0);
5728
+ };
5729
+ beginReview(intent, props.controller.pluginWriteTaskReturnRoute(route));
5322
5730
  };
5323
5731
  breadcrumb = taskPluginIds.length > 1
5324
5732
  ? `我的插件 / ${taskLabel} ${taskPluginIds.length} 个插件`
@@ -5334,14 +5742,21 @@ export function ConsoleAppV4(props) {
5334
5742
  ? 'PgUp/PgDn 查看全部位置 · ↑↓ 移动 · Enter 选择 · Esc 返回'
5335
5743
  : '↑↓ 移动 · Enter 选择 · Esc 返回';
5336
5744
  actions = [
5337
- ...(!targetSetChanged && !versionEvidenceChanged
5745
+ ...(!targetSetChanged && !versionEvidenceChanged && mixedScopes
5746
+ ? ['user', 'project'].filter((selectedScope) => executable.some((candidate) => candidate.target.scope === selectedScope)).map((selectedScope) => ({
5747
+ label: `先处理${scopeLabel(selectedScope)} ${executable.filter((candidate) => candidate.target.scope === selectedScope).length} 个位置`,
5748
+ description: `完整请求 ${candidates.length} 个位置;另一范围留待重新准备和确认`,
5749
+ status: 'warning',
5750
+ run: () => startTaskReview(blockedCandidates.length > 0 ? 'executableSubset' : 'all', selectedScope),
5751
+ })) : []),
5752
+ ...(!mixedScopes && !targetSetChanged && !versionEvidenceChanged
5338
5753
  && blockedCandidates.length === 0 && executable.length > 0 ? [{
5339
5754
  label: `确认${taskLabel} ${executable.length} 个位置`,
5340
5755
  description: '进入统一影响确认;执行前再次检查当前证据',
5341
5756
  status: 'success',
5342
5757
  run: () => startTaskReview('all'),
5343
5758
  }] : []),
5344
- ...(!targetSetChanged && !versionEvidenceChanged
5759
+ ...(!mixedScopes && !targetSetChanged && !versionEvidenceChanged
5345
5760
  && blockedCandidates.length > 0 && executable.length > 0 ? [{
5346
5761
  label: `先处理可执行的 ${executable.length} 个位置`,
5347
5762
  description: `${blockedCandidates.length} 个受阻位置保留,不会无声跳过`,
@@ -5405,7 +5820,6 @@ export function ConsoleAppV4(props) {
5405
5820
  description = undefined;
5406
5821
  context = '插件详情';
5407
5822
  const issues = props.controller.listUserIssues().filter((entry) => issueMatchesTarget(entry, detailSubject.pluginId));
5408
- const root = props.controller.getProjectInventoryRoot(detailSubject.pluginId);
5409
5823
  actions = [
5410
5824
  ...(issues.length > 0 ? [{ actionId: 'exceptional', label: '查看处理建议',
5411
5825
  status: 'warning',
@@ -5417,8 +5831,6 @@ export function ConsoleAppV4(props) {
5417
5831
  label: '依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: detailSubject.pluginId }) }] : []),
5418
5832
  ...(props.controller.getSourceOverview(detailSubject.sourceId).source ? [{ actionId: 'source',
5419
5833
  label: '查看插件来源', run: () => go({ name: 'sourceDetail', sourceId: detailSubject.sourceId }) }] : []),
5420
- ...(root && root.state !== 'ready' && issues.length === 0 ? [{ actionId: 'project-recheck',
5421
- label: '重新检查项目', run: async () => { await props.controller.refreshProjectInventory(); } }] : []),
5422
5834
  ];
5423
5835
  content = detailCardContent(detailSubject);
5424
5836
  }
@@ -5503,7 +5915,9 @@ export function ConsoleAppV4(props) {
5503
5915
  ? go({ name: 'issueList' })
5504
5916
  : go({ name: 'pluginTechnical', pluginId: plugin.pluginId }),
5505
5917
  } : undefined;
5506
- const installed = hasInstalledPlacement(plugin);
5918
+ const hasPortablePlacement = detailSubject.placements.some((placement) => placement.agent
5919
+ && placement.agent !== 'codex' && placement.agent !== 'claude' && placement.actual);
5920
+ const installed = hasInstalledPlacement(plugin) || hasPortablePlacement;
5507
5921
  const hasInstallableTarget = props.controller.pluginHasInstallableTarget(plugin.pluginId);
5508
5922
  const hasInstallationOption = hasInstallableTarget || plugin.portableSkills;
5509
5923
  const beginInstall = () => {
@@ -5521,7 +5935,7 @@ export function ConsoleAppV4(props) {
5521
5935
  };
5522
5936
  const installAction = !plugin.catalogEntryMissing ? {
5523
5937
  actionId: 'install',
5524
- label: installed ? '安装到其他位置' : '安装',
5938
+ label: installed ? '添加到其他位置…' : '添加插件…',
5525
5939
  description: plugin.sourceUnavailable
5526
5940
  ? '插件来源不可用'
5527
5941
  : plugin.capabilityUnsupported
@@ -5529,8 +5943,8 @@ export function ConsoleAppV4(props) {
5529
5943
  : !hasInstallationOption
5530
5944
  ? '当前工具和插件没有可用的安装方式'
5531
5945
  : '选择工具和使用范围',
5532
- ...(plugin.sourceUnavailable || plugin.capabilityUnsupported || !hasInstallationOption
5533
- ? { disabled: plugin.sourceUnavailable ? '插件来源不可用'
5946
+ ...(!agentSettings?.preferences.targets.length || plugin.sourceUnavailable || plugin.capabilityUnsupported || !hasInstallationOption
5947
+ ? { disabled: !agentSettings?.preferences.targets.length ? '请先选择 Agent' : plugin.sourceUnavailable ? '插件来源不可用'
5534
5948
  : plugin.capabilityUnsupported ? '需要升级 AW CLI' : '没有可用的安装方式' }
5535
5949
  : { run: beginInstall }),
5536
5950
  } : undefined;
@@ -5565,6 +5979,17 @@ export function ConsoleAppV4(props) {
5565
5979
  : '选择一个现有平台和实际安装位置',
5566
5980
  status: 'error',
5567
5981
  run: () => {
5982
+ if (hasPortablePlacement) {
5983
+ const scopes = ['user', 'project'].filter((candidate) => detailSubject.placements.some((placement) => placement.scope === candidate && placement.actual)
5984
+ || detailSubject.declarations[candidate].state === 'declared');
5985
+ if (scopes.length === 1)
5986
+ beginScopedTask({ kind: 'recovery', command: 'uninstall',
5987
+ scope: scopes[0], selectors: [plugin.pluginId] });
5988
+ else
5989
+ setScopedFlow({ stage: 'recoveryScope', scope, selector: plugin.pluginId,
5990
+ command: 'uninstall', scopes: [...scopes], focus: 0 });
5991
+ return;
5992
+ }
5568
5993
  const targets = props.controller.listPluginWriteBatchTargets('uninstall', [plugin.pluginId]);
5569
5994
  if (targets.length === 1) {
5570
5995
  const target = targets[0];
@@ -5595,9 +6020,36 @@ export function ConsoleAppV4(props) {
5595
6020
  actions = dedupeActions([
5596
6021
  ...(exceptionalAction ? [{ ...exceptionalAction, actionId: 'exceptional' }] : []),
5597
6022
  ...(restoreAction ? [{ ...restoreAction, actionId: 'restore' }] : []),
6023
+ ...(hasPortablePlacement && !restoreAction ? [{ actionId: 'sync', label: '同步并修复…',
6024
+ run: () => beginScopedTask({ kind: 'recovery', command: 'sync',
6025
+ scope: detailSubject.placements.some((placement) => placement.scope === 'project')
6026
+ || detailSubject.declarations.project.state === 'declared' ? 'project' : 'user',
6027
+ selectors: [plugin.pluginId] }) }] : []),
5598
6028
  ...(updateAction ? [{ ...updateAction, actionId: 'update' }] : []),
5599
6029
  ...(installAction ? [installAction] : []),
6030
+ ...(!agentSettings?.preferences.targets.length ? [{ actionId: 'choose-agents', label: '打开 Agent 管理', run: () => openAgentManagement() }] : []),
6031
+ ...(!plugin.catalogEntryMissing && !plugin.sourceUnavailable ? [{ actionId: 'add-demand',
6032
+ label: '加入插件清单(暂不安装)',
6033
+ run: () => snapshot.projectRoot
6034
+ ? setScopedFlow({ stage: 'intentScope', scope, selector: plugin.pluginId, action: 'add',
6035
+ scopes: ['user', 'project'],
6036
+ focus: scope === 'project' ? 1 : 0 })
6037
+ : beginScopedTask({ kind: 'intent', action: 'add', scope: 'user',
6038
+ selector: plugin.pluginId }) }] : []),
6039
+ ...(['user', 'project'].some((declaredScope) => detailSubject?.declarations[declaredScope].state === 'declared')
6040
+ ? [{ actionId: 'remove-demand', label: '移出插件清单(保留安装)', run: () => {
6041
+ const scopes = ['user', 'project'].filter((candidate) => detailSubject.declarations[candidate].state === 'declared');
6042
+ if (scopes.length === 1)
6043
+ beginScopedTask({ kind: 'intent', action: 'remove',
6044
+ scope: scopes[0], selector: plugin.pluginId });
6045
+ else
6046
+ setScopedFlow({ stage: 'intentScope', scope, selector: plugin.pluginId,
6047
+ action: 'remove', scopes: [...scopes], focus: 0 });
6048
+ } }] : []),
5600
6049
  ...(settingsAction ? [{ ...settingsAction, actionId: 'settings' }] : []),
6050
+ ...(agentSettings?.plugins.some((entry) => entry.pluginId === plugin.pluginId)
6051
+ ? [{ actionId: 'plugin-agents', label: 'Agent 管理',
6052
+ run: () => openAgentManagement(plugin.pluginId) }] : []),
5601
6053
  ...(contentAction ? [contentAction] : []),
5602
6054
  ...(dependencyAction ? [dependencyAction] : []),
5603
6055
  ...(uninstallAction ? [uninstallAction] : []),
@@ -5829,8 +6281,8 @@ export function ConsoleAppV4(props) {
5829
6281
  const selectedIssue = platformSelectedScope ? props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId && candidate.platform === route.platform
5830
6282
  && candidate.scope === platformSelectedScope) : undefined;
5831
6283
  const factActions = platformFacts.map((entry) => ({
5832
- actionId: `placement:${entry.platform}:${entry.scope}`,
5833
- label: `${platformLabel(entry.platform)} · ${scopeLabel(entry.scope)}`,
6284
+ actionId: `placement:${route.platform}:${entry.scope}`,
6285
+ label: `${platformLabel(route.platform)} · ${scopeLabel(entry.scope)}`,
5834
6286
  selected: entry.scope === platformSelectedScope,
5835
6287
  run: () => {
5836
6288
  props.controller.updatePageContext({
@@ -5887,67 +6339,9 @@ export function ConsoleAppV4(props) {
5887
6339
  }) })] });
5888
6340
  }
5889
6341
  else if (route.name === 'configLayer' && plugin) {
5890
- breadcrumb = `${plugin.displayName} / 设置`;
5891
- const configLayerDefinitionPage = configPage ?? configLayerPages.user ?? configLayerPages.project;
5892
- const selectedField = route.fieldKey && configLayerDefinitionPage
5893
- ? configLayerDefinitionPage.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
5894
- : undefined;
5895
- const selectedPreset = route.presetId && configLayerDefinitionPage
5896
- ? configLayerDefinitionPage.presets.find((preset) => preset.id === route.presetId)
5897
- : undefined;
5898
- const selectedMutation = selectedField && configCandidate
5899
- ? configCandidate.mutations[selectedField.key]
5900
- : undefined;
5901
- const candidateSummary = selectedField && selectedMutation
5902
- ? selectedMutation.kind === 'unset' ? '恢复继承'
5903
- : selectedField.sensitive ? '已更新'
5904
- : fieldValueText(selectedField, selectedMutation.value)
5905
- : selectedPreset && configCandidate
5906
- ? `方案“${selectedPreset.title}” · ${Object.keys(configCandidate.mutations).length} 项修改`
5907
- : undefined;
5908
- const candidateSummaryLines = candidateSummary
5909
- ? wrapConsoleText(candidateSummary, Math.max(12, size.columns - 14))
5910
- : [];
5911
- const candidateSummaryDisplay = candidateSummaryLines.length > 1
5912
- ? `${candidateSummaryLines[0]}…`
5913
- : candidateSummaryLines[0];
5914
- title = selectedField ? `“${selectedField.title}”保存到哪里?`
5915
- : selectedPreset ? `“${selectedPreset.title}”保存到哪里?` : '这个值保存到哪里?';
5916
- description = configCandidate?.selectingScope ? '先选择保存位置,再按该层配置选择引用值。' : selectedField
5917
- ? `${selectedField.sensitive ? '敏感值只能保存到个人设置。' : '选择后回到分组总览;磁盘尚未改变。'}`
5918
- : selectedPreset ? '选择后回到分组总览;磁盘尚未改变。' : '请返回重新选择值。';
5919
- if (configActionError)
5920
- description = configActionError;
5921
- help = '↑↓ 移动 · Enter 选择 · Esc 返回修改值';
5922
- const canUseLayer = (scope) => {
5923
- const page = configLayerPages[scope];
5924
- if (!page || !configCandidate)
5925
- return false;
5926
- const fields = page.groups.flatMap((group) => group.fields);
5927
- if (configCandidate.selectingScope)
5928
- return fields.some((field) => field.key === configCandidate.fieldKey && configFieldCanSet(field));
5929
- return Object.entries(configCandidate.mutations).every(([key, mutation]) => {
5930
- const field = fields.find((candidate) => candidate.key === key);
5931
- return Boolean(field && (mutation.kind === 'set' ? configFieldCanSet(field) : configFieldCanUnset(field)));
5932
- });
5933
- };
5934
- actions = [
5935
- ...(canUseLayer('project') ? [{
5936
- label: '当前项目',
5937
- description: '修改仅对当前项目生效;适合随仓库共享的约定',
5938
- meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`,
5939
- run: () => openConfigLayerTarget({ scope: 'project' }),
5940
- }] : []),
5941
- ...(canUseLayer('user') ? [{
5942
- label: '个人默认',
5943
- description: '影响个人的所有项目;项目设置仍可覆盖',
5944
- meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`,
5945
- run: () => openConfigLayerTarget({ scope: 'user' }),
5946
- }] : []),
5947
- ];
5948
- content = actions.length > 0
5949
- ? _jsxs(Box, { flexDirection: "column", children: [candidateSummaryDisplay ? _jsx(StatusCard, { status: "info", title: "\u5DF2\u786E\u5B9A\u65B0\u503C", description: `新值:${candidateSummaryDisplay}`, compact: true }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' })] })
5950
- : _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u4FDD\u5B58\u4F4D\u7F6E" });
6342
+ title = '插件设置';
6343
+ description = '正在准备当前 Agent 的设置…';
6344
+ content = _jsx(ProgressRow, { title: "\u8BFB\u53D6\u8BBE\u7F6E\u5B57\u6BB5", state: "active" });
5951
6345
  }
5952
6346
  else if (route.name === 'configFields' && plugin) {
5953
6347
  const readonly = false;
@@ -5972,7 +6366,7 @@ export function ConsoleAppV4(props) {
5972
6366
  const sources = [...new Set(configDraftIssues.flatMap((issue) => issue.referenceSources ?? []))];
5973
6367
  description = `${description ?? ''} 有 ${configDraftIssues.length} 项未保存修改已不适用于当前设置。${sources.length ? ` 请检查来源 ${sources.join('、')}。` : ''}`;
5974
6368
  }
5975
- const mutableLayer = { scope: 'effective' };
6369
+ const mutableLayer = route.layer;
5976
6370
  const tabActions = activeTab?.kind === 'presets'
5977
6371
  ? currentConfigPage.presets.map((preset) => ({
5978
6372
  label: preset.title,
@@ -5985,10 +6379,7 @@ export function ConsoleAppV4(props) {
5985
6379
  label: field.title,
5986
6380
  description: field.invalidOverrideIssue ?? field.description,
5987
6381
  configTile: (() => {
5988
- const project = pendingFieldDisplay(field, 'project');
5989
- const user = pendingFieldDisplay(field, 'user');
5990
- const covered = user !== undefined && field.effectiveSourceLabel === '当前项目' && project === undefined;
5991
- const pending = project ?? (covered ? undefined : user);
6382
+ const pending = pendingFieldDisplay(field, route.layer.profile);
5992
6383
  const targets = pendingFieldTargets(field);
5993
6384
  return {
5994
6385
  value: pending ?? effectiveFieldDisplay(field),
@@ -5996,86 +6387,48 @@ export function ConsoleAppV4(props) {
5996
6387
  description: field.description?.split('\n').map(safeConfigText).join('\n'),
5997
6388
  issue: field.invalidOverrideIssue?.split('\n').map(safeConfigText).join('\n'),
5998
6389
  invalid: Boolean(field.invalidOverrideIssue),
5999
- ...(targets.length ? { pending: covered
6000
- ? '待保存到个人;个人修改不会影响本项目'
6001
- : `待保存到${targets.map(({ scope }) => configLayerLabel({ scope })).join('、')}` } : {}),
6390
+ ...(targets.length ? { pending: '● 未保存' } : {}),
6002
6391
  };
6003
6392
  })(),
6004
6393
  configKey: field.key,
6005
- run: () => configReferenceScalars(field.definition).length
6006
- ? openConfigTarget({ pluginId: plugin.pluginId, fieldKey: field.key, selectingScope: true, mutations: {} })
6007
- : go({ name: 'configFieldEditor', pluginId: plugin.pluginId, layer: mutableLayer,
6008
- fieldKey: field.key, editor: fieldEditor(field), ...fieldEditorMode(field) }),
6394
+ run: () => go({ name: 'configFieldEditor', pluginId: plugin.pluginId, layer: mutableLayer,
6395
+ fieldKey: field.key, editor: fieldEditor(field), ...fieldEditorMode(field) }),
6009
6396
  }));
6010
6397
  const operationActions = [
6011
- ...(crossLayerDependencies.length ? [{
6012
- label: '取消跨层引用草稿,保留来源修改',
6013
- description: '先保存个人默认来源,再重新编辑当前项目引用。',
6014
- run: () => {
6015
- const keys = new Set(crossLayerDependencies.map((entry) => entry.key));
6016
- const next = { ...configDrafts };
6017
- const project = next.project;
6018
- const updates = Object.fromEntries(Object.entries(project.updates).filter(([key]) => !keys.has(key)));
6019
- if (Object.keys(updates).length || project.removes.length)
6020
- next.project = { ...project, updates };
6021
- else
6022
- delete next.project;
6023
- setConfigDrafts(next);
6024
- setObjectDrafts({});
6025
- setArrayDrafts({});
6026
- setChangedObjectDraftKeys([]);
6027
- setConfigActionError('已取消相关引用草稿。请先保存来源,再重新编辑引用。');
6028
- },
6029
- }] : []),
6030
- ...(configDraftIssues.length > 0 ? [{
6031
- label: '放弃无法保存的修改',
6032
- description: `${configDraftIssues.length} 项未保存修改已不再适用于当前插件设置;放弃后不会修改已保存设置。`,
6033
- status: 'warning',
6034
- run: discardInvalidConfigDraftChanges,
6035
- }] : []),
6036
- ...['project', 'user'].flatMap((resetScope) => {
6037
- const page = configLayerPages[resetScope];
6038
- if (!page || page.storedCount === 0)
6039
- return [];
6040
- return [{
6041
- label: resetScope === 'user' ? '清空个人默认' : '清空当前项目设置',
6042
- description: `删除${resetScope === 'user' ? '个人默认' : '当前项目'}中的全部值并恢复继承;其他设置层保持不变`,
6043
- status: 'error',
6044
- run: () => beginReview({
6045
- kind: 'configReset', pluginId: plugin.pluginId, scope: resetScope,
6046
- changes: page.groups.flatMap((group) => group.fields).filter((field) => field.configured).map((field) => ({
6047
- key: field.key,
6048
- title: field.title,
6049
- before: field.displayValue,
6050
- after: `恢复继承(${field.fallbackDisplayValue ?? '未设置'} · ${field.fallbackSourceLabel ?? '未设置'})`,
6051
- ...(field.fallbackObjectChanges?.length
6052
- ? { children: field.fallbackObjectChanges }
6053
- : {}),
6054
- })),
6055
- }),
6056
- }];
6057
- }),
6398
+ ...(crossLayerDependencies.length ? [{ label: '取消相关引用草稿', status: 'warning',
6399
+ description: '保留来源设置修改,只放弃依赖这些未保存值的引用草稿。', run: discardDependentConfigDrafts }] : []),
6400
+ ...(configDraftIssues.length > 0 ? [{ label: '放弃无法保存的修改',
6401
+ status: 'warning', run: discardInvalidConfigDraftChanges }] : []),
6402
+ { actionId: 'config-reset-profile', label: '恢复默认值 [u]', status: 'error',
6403
+ description: '删除当前 Agent 设置中的覆盖值,恢复继承。',
6404
+ run: () => beginReview({ kind: 'configReset', pluginId: plugin.pluginId, scope: 'user',
6405
+ ...configReadOptions(route.layer),
6406
+ changes: currentConfigPage.groups.flatMap((group) => group.fields).filter((field) => field.configured).map((field) => ({
6407
+ key: field.key, title: field.title, before: field.displayValue,
6408
+ after: `恢复继承(${field.fallbackDisplayValue ?? '未设置'})`,
6409
+ })),
6410
+ }) },
6058
6411
  ];
6059
6412
  const configSaveAction = {
6060
6413
  actionId: 'config-save-summary',
6061
- label: configDraftCount > 0 ? `保存设置(${configDraftCount}) [s]` : '保存设置 [s]',
6062
- description: '确认全部设置层的变化,再选择只保存或保存并应用',
6414
+ label: '保存 [s]',
6415
+ description: '保存设置后,可选择更新已安装的插件。',
6063
6416
  status: configDraftCount > 0 ? 'info' : 'muted',
6064
6417
  disabled: configDraftCount === 0 ? '没有待保存设置'
6065
6418
  : configDraftIssues.length > 0 ? '请先放弃失效草稿' : undefined,
6066
6419
  run: () => {
6067
6420
  setFocus(0);
6068
6421
  setScrollOffset(0);
6069
- beginConfigSave(true);
6422
+ onSaveConfig();
6070
6423
  },
6071
6424
  };
6072
- bottomActions = [configSaveAction, ...(crossLayerDependencies.length ? operationActions.slice(0, 1) : []), {
6425
+ bottomActions = [configSaveAction, ...operationActions, {
6073
6426
  label: '设置选项', run: () => go({ ...route, view: 'options' }),
6074
6427
  }];
6075
6428
  actions = [...tabActions, ...bottomActions];
6076
6429
  if (route.view === 'options') {
6077
6430
  title = '设置选项';
6078
- description = '查看各层存储值,或清空一个保存位置。';
6431
+ description = '查看用户设置,或恢复当前 Agent 的默认值。';
6079
6432
  actions = [{ label: '查看当前值的来源', run: () => {
6080
6433
  setBatchTargetOffset(0);
6081
6434
  go({ ...route, view: 'values' });
@@ -6083,7 +6436,7 @@ export function ConsoleAppV4(props) {
6083
6436
  }
6084
6437
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
6085
6438
  const focusedConfigField = activeGroup?.fields.find((field) => field.key === actions[effectiveConfigFocus]?.configKey);
6086
- const configRowBudget = pageActionBudget(Number(visibleConfigTabs.length > 1)
6439
+ const configRowBudget = pageActionBudget(1 + Number(visibleConfigTabs.length > 1)
6087
6440
  + (configDraftIssues.length > 0 ? 3 : 0));
6088
6441
  const focusedTile = actions[effectiveConfigFocus]?.configTile;
6089
6442
  if (focusedTile && route.view !== 'options' && route.view !== 'values') {
@@ -6095,40 +6448,42 @@ export function ConsoleAppV4(props) {
6095
6448
  }
6096
6449
  if (!readonly) {
6097
6450
  const canUnsetFocused = focusedConfigField
6098
- ? ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
6451
+ ? CONFIG_PROFILE_KEYS.some((profile) => configLayerPages[profile]?.groups.flatMap((group) => group.fields)
6099
6452
  .some((field) => field.key === focusedConfigField.key && configFieldCanUnset(field)))
6100
6453
  : false;
6101
6454
  help = `↑↓ 选择 · Enter ${focusedConfigField ? '修改' : '选择'}${!route.view && visibleConfigTabs.length > 1
6102
6455
  ? ' · ←→ 分组' : ''}${(configCardNavigation?.pageCount ?? 1) > 1 ? ' · PgUp/PgDn 卡片全文' : ''}`
6103
- + `${configDraftCount > 0 ? ' · s 查看汇总' : ''}${canUnsetFocused ? ' · u 恢复继承' : ''} · Esc 返回`;
6456
+ + `${configDraftCount > 0 ? ' · s 保存' : ''}${canUnsetFocused ? ' · u 恢复默认值' : ''} · Esc 返回`;
6104
6457
  }
6105
6458
  const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
6106
6459
  const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
6460
+ description = configActionError ?? (configSaveState?.status === 'error' ? configSaveState.error
6461
+ : configSaveState?.status === 'saving' ? '正在保存设置…'
6462
+ : `${configLayerLabel(route.layer)} · 用户设置${route.layer.context === 'project' ? ' · 当前项目' : ''}`);
6463
+ help += ' · Tab 切换 Agent';
6107
6464
  context = configDraftCount > 0 ? `${configDraftCount} 项待保存` : countLabel;
6108
6465
  content = route.view === 'options' ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })
6109
6466
  : route.view === 'values' ? (() => {
6110
6467
  title = '完整设置值与来源';
6111
6468
  description = '显示各保存位置的完整安全值;敏感值不回显。';
6112
- readingText = ['effective', 'project', 'user'].flatMap((scope) => ((scope === 'effective' ? currentConfigPage : configLayerPages[scope])?.groups.flatMap((group) => group.fields) ?? []).map((field) => `${field.title} · ${configLayerLabel({ scope })}:${scope === 'effective'
6113
- ? field.invalidOverrideIssue ? '需修正' : fieldValueText(field, field.draftValue ?? field.effectiveValue, true, field.effectiveSourceLabel !== '未设置')
6114
- : fieldValueText(field, field.draftValue ?? field.value, true, field.configured)}`)).join('\n');
6469
+ readingText = CONFIG_PROFILE_KEYS.flatMap((profile) => (configLayerPages[profile]?.groups.flatMap((group) => group.fields) ?? []).map((field) => `${field.title} · ${configLayerLabel({ profile })}:${fieldValueText(field, field.draftValue ?? field.value, true, field.configured)}`)).join('\n');
6115
6470
  actions = [{ label: '返回设置', run: back }];
6116
6471
  help = '←→ 或 PgUp/PgDn 阅读 · Esc 返回';
6117
6472
  return _jsxs(Box, { flexDirection: "column", children: [readingPage(readingText, size.columns - 2, Math.max(1, pageActionBudget(4) - 1)), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6118
- })() : _jsxs(Box, { flexDirection: "column", children: [!readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab && visibleConfigTabs.length > 1 ? _jsx(QueryHeader, { width: size.columns - 2, tabs: { label: '分组', items: visibleConfigTabs.map((tab) => ({
6473
+ })() : _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.roles.focus, bold: true, children: [route.layer.profile === 'common' ? '◎ 全部 Agent' : `${configLayerLabel(route.layer)} 专用`, " [Tab]"] }), !readonly && configDraftIssues.length > 0 ? _jsx(Callout, { status: "warning", title: "\u6709\u672A\u4FDD\u5B58\u4FEE\u6539\u65E0\u6CD5\u7EE7\u7EED\u4F7F\u7528", description: "\u63D2\u4EF6\u6765\u6E90\u6062\u590D\u540E\uFF0C\u8BBE\u7F6E\u5185\u5BB9\u6216\u53EF\u7528\u4F4D\u7F6E\u5DF2\u53D8\u5316\u3002\u8BF7\u653E\u5F03\u8FD9\u4E9B\u4FEE\u6539\uFF0C\u6216\u8FD4\u56DE\u68C0\u67E5\u63D2\u4EF6\u6765\u6E90\uFF1B\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u3002" }) : null, activeTab && visibleConfigTabs.length > 1 ? _jsx(QueryHeader, { width: size.columns - 2, tabs: { label: '分组', items: visibleConfigTabs.map((tab) => ({
6119
6474
  id: tab.id, label: tab.label, count: tab.count,
6120
6475
  })), currentId: activeTab.id } }) : null, _jsx(SequenceRows, { actions: actions, itemCount: tabActions.length, focus: effectiveConfigFocus, scrollOffset: scrollOffset, columns: size.columns - 2, rowBudget: configRowBudget, configPage: configCardNavigation?.page, compact: policy.mode === 'compact' })] });
6121
6476
  }
6122
6477
  }
6123
6478
  else if (route.name === 'configSave' && plugin) {
6124
- const draftGroups = ['project', 'user'].flatMap((scope) => {
6479
+ const draftGroups = CONFIG_PROFILE_KEYS.flatMap((scope) => {
6125
6480
  const draft = configDrafts[scope];
6126
6481
  const page = configLayerPages[scope];
6127
- return draft && page ? [{ scope, draft, changes: configChanges(page, draft) }] : [];
6482
+ return draft && page ? [{ profile: scope, draft, changes: configChanges(page, draft) }] : [];
6128
6483
  });
6129
6484
  const changes = draftGroups.flatMap((group) => group.changes.map((change) => ({
6130
6485
  ...change,
6131
- title: `${scopeLabel(group.scope)} · ${change.title}`,
6486
+ title: `${configLayerLabel({ profile: group.profile })} · ${change.title}`,
6132
6487
  })));
6133
6488
  if (route.view === 'changes') {
6134
6489
  readingText = changeDetailText(changes);
@@ -6153,12 +6508,12 @@ export function ConsoleAppV4(props) {
6153
6508
  label: '保存并生效',
6154
6509
  description: '保存全部草稿,并重新应用实际使用这些设置的目标',
6155
6510
  status: 'success',
6156
- run: () => beginConfigSave(true),
6511
+ run: () => onSaveConfig(),
6157
6512
  },
6158
6513
  {
6159
6514
  label: '只保存,稍后应用',
6160
6515
  description: '保存全部设置层,不重新应用安装目标',
6161
- run: () => beginConfigSave(false),
6516
+ run: () => onSaveConfig(),
6162
6517
  },
6163
6518
  { label: '返回修改', run: back },
6164
6519
  ];
@@ -6182,7 +6537,7 @@ export function ConsoleAppV4(props) {
6182
6537
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title}`;
6183
6538
  const scalarReference = 'valueRef' in activeField.definition && activeField.definition.valueRef ? activeField.definition : undefined;
6184
6539
  const showChoices = Boolean(scalarReference) || (activeField.choices?.length ?? 0) > 0 && route.mode !== 'custom';
6185
- const targetFields = ['project', 'user'].flatMap((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields).filter((field) => field.key === activeField.key) ?? []);
6540
+ const targetFields = [route.layer.profile].flatMap((profile) => configLayerPages[profile]?.groups.flatMap((group) => group.fields).filter((field) => field.key === activeField.key) ?? []);
6186
6541
  const canSet = targetFields.some(configFieldCanSet);
6187
6542
  const canUnset = !arrayItem && targetFields.some(configFieldCanUnset);
6188
6543
  if (!canSet) {
@@ -6195,7 +6550,7 @@ export function ConsoleAppV4(props) {
6195
6550
  const selection = configReferenceSelection(referenceContext, scalarReference);
6196
6551
  const current = arrayItem ? itemValue : draftUpdates[activeField.key] ?? activeField.draftValue ?? activeField.value;
6197
6552
  title = `选择${activeField.title}`;
6198
- description = referenceNotice(scalarReference, current) ?? `选项来自 ${configReferenceSource(scalarReference)};保存到${editingScope === 'user' ? '个人默认' : '当前项目'}。`;
6553
+ description = referenceNotice(scalarReference, current) ?? `选项来自 ${configReferenceSource(scalarReference)};保存到${configLayerLabel(route.layer)}。`;
6199
6554
  help = '↑↓ 移动 · Enter 使用 · Esc 返回修复来源';
6200
6555
  actions = selection.choices.map((choice) => ({ label: safeConfigText(choice.title), selected: choice.value === current,
6201
6556
  run: () => updateDraft(activeField.key, choice.value) }));
@@ -6383,10 +6738,6 @@ export function ConsoleAppV4(props) {
6383
6738
  : activeField.sensitive ? '当前值不会回显;留空不会自动删除旧值。' : activeField.description ?? '';
6384
6739
  content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: inputValue, sensitive: activeField.sensitive, placeholder: activeField.placeholder, columns: Math.max(8, size.columns - 8) }), description: inputError ?? (activeField.sensitive ? '输入已隐藏。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
6385
6740
  }
6386
- if (editingScope === 'user' && activeTopField && configReferenceScalars(activeTopField.definition).length
6387
- && configLayerPages.project?.groups.flatMap((group) => group.fields).some((field) => field.key === activeTopField.key && field.configured)) {
6388
- description = `${description ?? ''} 个人默认在当前项目被覆盖。`;
6389
- }
6390
6741
  if (canUnset) {
6391
6742
  const unset = { label: '恢复继承', description: '选择要删除覆盖值的位置', run: () => removeDraft(activeField.key) };
6392
6743
  const inputEditor = canSet && !showChoices && (route.editor === 'number' || route.editor === 'string');
@@ -6565,39 +6916,33 @@ export function ConsoleAppV4(props) {
6565
6916
  const canRecoverSource = configUnavailableCanRecoverSource(route);
6566
6917
  title = diagnostic.title;
6567
6918
  description = '';
6568
- const resetScopes = route.diagnosticCode === 'configValidationMemoryExhausted' ? []
6569
- : route.invalidStores?.map(({ scope }) => scope)
6570
- ?? route.storedScopes
6571
- ?? (route.layer ? [route.layer.scope] : []);
6919
+ const resetProfiles = route.diagnosticCode === 'configValidationMemoryExhausted' ? []
6920
+ : [route.layer?.profile ?? 'common'];
6572
6921
  actions = [
6573
- ...resetScopes.flatMap((scope) => {
6574
- const availability = configResetAvailability[scope];
6922
+ ...resetProfiles.flatMap((profile) => {
6923
+ const availability = configResetAvailability[profile];
6575
6924
  return availability?.exists && availability.resettable !== false
6576
6925
  ? [{
6577
- label: `重置${scope === 'user' ? '个人默认' : '当前项目'}设置层`,
6578
- description: '删除整个目标层;不创建备份',
6926
+ label: `重置${configLayerLabel({ profile })}`,
6927
+ description: '清除当前设置中的覆盖值;损坏的文档按恢复规则处理。',
6579
6928
  status: 'error',
6580
6929
  run: () => beginReview({
6581
- kind: 'configReset',
6582
- pluginId: route.pluginId,
6583
- scope,
6930
+ kind: 'configReset', pluginId: route.pluginId, scope: 'user',
6931
+ ...configReadOptions({ profile, context: route.layer?.context }),
6584
6932
  ...(availability.state === 'invalid' ? { expectedStoreState: 'invalid' } : {}),
6585
6933
  }),
6586
6934
  }]
6587
6935
  : [];
6588
6936
  }),
6589
6937
  ...(!invalidLayer && canRecoverSource ? [
6590
- {
6591
- label: '修复插件来源',
6592
- description: `检查并恢复 ${unavailableSourceId};完成后返回当前设置位置。`,
6593
- run: () => props.controller.beginConfigSourceRecovery(unavailableSourceId),
6594
- },
6938
+ { label: '修复插件来源', description: `检查并恢复 ${unavailableSourceId};完成后返回当前设置位置。`,
6939
+ run: () => props.controller.beginConfigSourceRecovery(unavailableSourceId) },
6595
6940
  ] : []),
6596
6941
  { label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => toggleTechnical() },
6597
6942
  { role: 'navigation', label: '返回', run: back },
6598
6943
  ];
6599
- const unsupportedScopes = resetScopes.filter((scope) => configResetAvailability[scope]?.resettable === false);
6600
- const unsupportedRows = unsupportedScopes.map((scope) => _jsx(StaticRow, { title: `${scope === 'user' ? '个人默认' : '当前项目'}设置层不能自动重置:特殊节点,请手动移动`, status: "warning", compact: true }, `unsupported-reset:${scope}`));
6944
+ const unsupportedRows = resetProfiles.filter((profile) => configResetAvailability[profile]?.resettable === false)
6945
+ .map((profile) => _jsx(StaticRow, { title: `${configLayerLabel({ profile })}不能自动重置:特殊节点,请手动移动`, status: "warning", compact: true }, `unsupported-reset:${profile}`));
6601
6946
  const technicalPageRows = responsiveBudget(2, 4);
6602
6947
  if (showTechnical && !invalidLayer) {
6603
6948
  referenceDetailPagination = {
@@ -7332,18 +7677,8 @@ export function ConsoleAppV4(props) {
7332
7677
  : go({ name: 'issueDetail', issueId: entry.issueId }),
7333
7678
  }));
7334
7679
  }
7335
- const reminder = snapshot.projectIntentReminder;
7336
- const pendingProjectTargets = (reminder?.actionableTargets.length ?? 0)
7337
- + (reminder?.blockedTargets.length ?? 0) + (reminder?.unconfirmedTargets.length ?? 0);
7338
- if (snapshot.projectIntentIssue || pendingProjectTargets > 0) {
7339
- title = '需要处理';
7340
- description = '查看项目插件、安装、来源或设置问题。';
7341
- actions.unshift({ actionId: 'project-status', label: '项目插件',
7342
- description: snapshot.projectIntentIssue ? '项目声明需要检查' : `${pendingProjectTargets} 个位置待处理或未确认`,
7343
- status: 'warning', run: () => go({ name: 'projectStatus' }) });
7344
- }
7345
7680
  const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
7346
- if (issues.length === 0 || snapshot.projectIntentIssue || pendingProjectTargets > 0) {
7681
+ if (issues.length === 0) {
7347
7682
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
7348
7683
  }
7349
7684
  else {
@@ -7389,7 +7724,7 @@ export function ConsoleAppV4(props) {
7389
7724
  else if (entry.id === 'viewSourceChange' && issue.sourceId)
7390
7725
  go({ name: 'sourceTechnical', sourceId: issue.sourceId });
7391
7726
  else if (entry.id === 'openConfig' && issue.pluginId) {
7392
- go({ name: 'configFields', pluginId: issue.pluginId, layer: { scope: 'effective' } });
7727
+ go({ name: 'configFields', pluginId: issue.pluginId, layer: { profile: 'common' } });
7393
7728
  }
7394
7729
  else if (entry.id === 'viewDependencies' && issue.pluginId) {
7395
7730
  go({ name: 'pluginDependencies', pluginId: issue.pluginId });
@@ -7651,6 +7986,8 @@ export function ConsoleAppV4(props) {
7651
7986
  else if (route.name === 'review' && snapshot.activeReview) {
7652
7987
  const review = snapshot.activeReview;
7653
7988
  readingText = [
7989
+ ...(review.operationKind === 'pluginWriteTask' && splitTaskReview
7990
+ ? splitTaskLines(splitTaskReview) : []),
7654
7991
  `${review.title} · ${review.subject.name}`,
7655
7992
  review.description,
7656
7993
  ...(review.equivalentCommand && !review.requiresConfirmation
@@ -8027,9 +8364,9 @@ export function ConsoleAppV4(props) {
8027
8364
  };
8028
8365
  help = 'PgUp/PgDn 查看全部确认信息 · ↑↓ 移动 · Enter 选择 · Esc 返回';
8029
8366
  }
8030
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: reviewRootSummary ? `本次插件 · ${reviewRootSummary}` : review.subject.name, meta: reviewRootSummary ? `版本 ${reviewVersionSummary} · 范围 ${reviewRangeSummary || '未知'}` : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, showTechnical && visibleTechnicalDetails.length > 0 ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && technicalDetailPage.offset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${technicalDetailPage.offset + index}:${detail}`)) }) : null, reviewPageCount > 1 ? _jsx(Pager, { page: reviewCurrentPage, pageCount: reviewPageCount, columns: size.columns - 6 }) : null, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" })] }) });
8367
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: reviewRootSummary ? `本次插件 · ${reviewRootSummary}` : review.subject.name, meta: reviewRootSummary ? `版本 ${reviewVersionSummary} · 范围 ${reviewRangeSummary || '未知'}` : `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: showEquivalentCommand ? review.description : undefined, children: [review.operationKind === 'pluginWriteTask' && splitTaskReview ? _jsx(StaticRow, { title: `完整请求 ${splitTaskReview.targets.length} 个位置 · 本次${scopeLabel(splitTaskReview.currentScope)} ${splitTaskReview.targets.filter((target) => target.scope === splitTaskReview.currentScope).length} 个`, description: `其余${scopeLabel(splitTaskReview.currentScope === 'user' ? 'project' : 'user')}位置需另行确认;y 复制完整请求`, status: "warning", compact: true }) : null, showEquivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, portableReview ? _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }) : null, showTechnical && review.catalogBinding ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `Catalog:${review.catalogBinding.sourceId} ${review.catalogBinding.catalogVersion} · 当前使用`, meta: `generation ${review.catalogBinding.generationDigestShort}`, status: "success", compact: true }), _jsx(StaticRow, { title: "\u64CD\u4F5C\u671F\u95F4\u4E0D\u68C0\u67E5\u8FDC\u7AEF Catalog", status: "success", compact: true })] }) : null, installDependencyPluginIds.size > 0 ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: `需要 ${installDependencyPluginIds.size} 个额外插件;${reusedDependencyPluginIds.size} 个复用、${newDependencyCount} 个新增`, status: "info", compact: true }), _jsx(StaticRow, { title: widenedDependencyCount > 0 ? `${widenedDependencyCount} 个依赖会扩大到所有项目` : '没有依赖会扩大到所有项目', status: widenedDependencyCount > 0 ? 'warning' : 'success', compact: true })] }) : null, review.requiresConfirmation ? _jsx(StaticRow, { title: review.operationKind === 'catalogStorePrune' ? '! 需要确认删除本次预览的可回收项目' : projectPreApplyConfirmation ? '! 需要确认当前项目同步脚本' : '! 需要确认会影响其他项目的内容', meta: review.sharedImpact.pluginIds.length > 0 ? `${review.sharedImpact.pluginIds.length} 个插件` : undefined, description: review.sharedImpact.warning, status: "warning", compact: true }) : null, showTechnical ? technicalChanges : changeGroups, showTechnical && visibleTechnicalDetails.length > 0 ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: catalogReferenceDetailOffset >= 0 && technicalDetailPage.offset + index >= catalogReferenceDetailOffset ? undefined : index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, `${technicalDetailPage.offset + index}:${detail}`)) }) : null, reviewPageCount > 1 ? _jsx(Pager, { page: reviewCurrentPage, pageCount: reviewPageCount, columns: size.columns - 6 }) : null, portableReview ? null : _jsx(SemanticRows, { actions: actions, focus: operationConfirmation ? -1 : focus, kind: "action", compact: true, offset: reviewActionViewport.offset, limit: reviewActionViewport.limit }), compressedReview || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" })] }) });
8031
8368
  if (pendingReviewIntent?.kind === 'configSaveBatch' || sourceDraft && (pendingReviewIntent?.kind === 'sourceMutation' || pendingReviewIntent?.kind === 'catalogGeneration') && pendingReviewIntent.sourceId === sourceDraft.sourceId) {
8032
- const changesText = pendingReviewIntent.kind === 'configSaveBatch' ? pendingReviewIntent.groups.map((group) => `${configLayerLabel({ scope: group.scope })}\n${(group.changes ?? []).flatMap((change) => [
8369
+ const changesText = pendingReviewIntent.kind === 'configSaveBatch' ? pendingReviewIntent.groups.map((group) => `${configLayerLabel({ profile: group.platform ?? 'common' })}\n${(group.changes ?? []).flatMap((change) => [
8033
8370
  `${change.title} · ${change.before ?? '未设置'} → ${change.after}${change.children?.length ? ` · ${change.children.length} 个子属性变化` : ''}`,
8034
8371
  ...(change.children ?? []).map((child) => `${child.property} · ${child.before} → ${child.after}`),
8035
8372
  ]).join('\n')}`).join('\n') : changeDetailText(sourceDraftChanges(sourceDraft));
@@ -8371,6 +8708,11 @@ export function ConsoleAppV4(props) {
8371
8708
  ? resultSummary?.message ?? '设置层已经保存;平台应用失败不会回滚设置。'
8372
8709
  : '设置层已经更新。'
8373
8710
  : '状态已刷新。';
8711
+ if (taskOperation && splitTaskReview?.currentScope === 'user') {
8712
+ if (!blocked && !partial)
8713
+ title = '✓ 所有项目范围已处理';
8714
+ description += ` 当前项目的 ${splitTaskReview.targets.filter((target) => target.scope === 'project').length} 个位置尚未处理。`;
8715
+ }
8374
8716
  const pluginId = resultSummary?.pluginId
8375
8717
  ?? ('pluginId' in result ? result.pluginId : undefined);
8376
8718
  const configResultPluginId = pluginId ?? ('returnRoute' in result
@@ -8378,12 +8720,11 @@ export function ConsoleAppV4(props) {
8378
8720
  && 'pluginId' in result.returnRoute
8379
8721
  ? result.returnRoute.pluginId
8380
8722
  : undefined);
8381
- const configRetryScopes = configResultPluginId === undefined ? []
8723
+ const configRetryProfiles = configResultPluginId === undefined ? []
8382
8724
  : operationKind === 'configSaveBatch'
8383
8725
  ? resultSummary?.configGroups?.filter((group) => group.configSaved && group.applyStatus === 'failed'
8384
- && group.errorCode !== 'invalidConfig').map((group) => group.scope) ?? []
8385
- : resultSummary?.configApplyRetryAvailable === true && resultSummary.configScope !== undefined
8386
- ? [resultSummary.configScope] : [];
8726
+ && group.errorCode !== 'invalidConfig').map((group) => group.platform ?? 'common') ?? []
8727
+ : resultSummary?.configApplyRetryAvailable === true ? [resultSummary.configPlatform ?? 'common'] : [];
8387
8728
  const configOrigin = successfulConfigOperation && pluginId
8388
8729
  ? props.controller.configOrigin(pluginId)
8389
8730
  : undefined;
@@ -8613,13 +8954,14 @@ export function ConsoleAppV4(props) {
8613
8954
  label: '返回首页', run: () => requestSessionNavigation('home') }]
8614
8955
  : configOperation && partial
8615
8956
  ? [
8616
- ...configRetryScopes.map((scope) => ({
8617
- label: configRetryScopes.length > 1 ? `重试应用 · ${scopeLabel(scope)}` : '重试应用',
8957
+ ...configRetryProfiles.map((profile) => ({
8958
+ label: configRetryProfiles.length > 1 ? `重试应用 · ${configLayerLabel({ profile })}` : '重试应用',
8618
8959
  description: '先重新显示操作汇总,并重新读取当前状态',
8619
8960
  run: () => beginReview({
8620
8961
  kind: 'configSave',
8621
8962
  pluginId: configResultPluginId,
8622
- scope,
8963
+ scope: 'user',
8964
+ ...configReadOptions({ profile, context: resultSummary?.configContext }),
8623
8965
  apply: true,
8624
8966
  retryApply: true,
8625
8967
  updates: {},
@@ -8711,9 +9053,10 @@ export function ConsoleAppV4(props) {
8711
9053
  const resultTargetName = (target) => target.pluginId
8712
9054
  ? props.controller.getPluginOverview(target.pluginId)?.displayName
8713
9055
  ?? target.pluginId.split('/').at(-1)
8714
- : `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
9056
+ : `${agentLabel(target.platform)} · ${scopeLabel(target.scope)}`;
8715
9057
  const resultStatus = blocked ? 'error' : partial ? 'warning' : 'success';
8716
- const resultTargets = resultSummary?.targets ?? [];
9058
+ const resultTargets = configOperation ? resultSummary?.configTargets ?? resultSummary?.targets ?? []
9059
+ : resultSummary?.targets ?? [];
8717
9060
  const commandResults = resultSummary?.commandResults ?? [];
8718
9061
  const retainedCommandPackage = (result) => result.producer.kind === 'package' && (resultSummary?.action === 'cleanup' || resultSummary?.action === 'uninstall');
8719
9062
  const commandResultRows = commandResults.map((result, index) => _jsx(ResultRow, { title: `${retainedCommandPackage(result) ? '保留' : ''}命令${result.producer.kind === 'package' ? '包' : '生产者'} ${result.producer.key}`, description: `${result.producer.sourceId}/${result.producer.pluginName} · ${scopeLabel(result.producer.scope)} · ${retainedCommandPackage(result) ? result.producer.scope === 'user' ? '所有项目共用 · ' : '当前项目内共用 · ' : ''}${result.commandIds.length} 个命令${result.removedCommandIds.length ? ` · 撤销 ${result.removedCommandIds.length} 个` : ''}${result.errors[0] ? ` · ${result.errors[0].code}` : ''}`, meta: retainedCommandPackage(result) ? '保留' : result.state === 'completed' ? '完成' : result.state === 'partial' ? '部分完成' : '失败', status: result.state === 'completed' ? 'success' : result.state === 'partial' ? 'warning' : 'error', compact: policy.mode === 'compact' }, `command:${index}`));
@@ -8794,12 +9137,13 @@ export function ConsoleAppV4(props) {
8794
9137
  `materialization digest · ${portableReceipt.materializationDigest}`] : []),
8795
9138
  ...(resultSummary?.elapsedMs !== undefined ? [`elapsed · ${resultSummary.elapsedMs} ms`] : []),
8796
9139
  ].join('\n') : '当前结果已变化。请返回结果摘要。' : [
9140
+ ...(taskOperation && splitTaskReview ? splitTaskLines(splitTaskReview) : []),
8797
9141
  resultSummary?.message ?? (blocked ? result.message : '操作结果'),
8798
9142
  ...(resultSummary?.messages ?? []).map(productResultMessage),
8799
9143
  ...(resultSummary?.changes ?? []),
8800
9144
  ...(resultSummary?.warnings ?? []),
8801
9145
  ...(resultSummary?.recoveryActions ?? []),
8802
- ...prioritizedTargets.map((target) => `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)} · ${operationTargetStateLabel(target.state).label}`),
9146
+ ...prioritizedTargets.map((target) => `${resultTargetName(target)} · ${agentLabel(target.platform)} · ${scopeLabel(target.scope)} · ${operationTargetStateLabel(target.state).label}`),
8803
9147
  ...commandResults.flatMap((result) => [
8804
9148
  `命令生产者 ${result.producer.key} · ${scopeLabel(result.producer.scope)} · ${result.state}`,
8805
9149
  ...(retainedCommandPackage(result) ? [`保留命令包 ${getCommandPackageId(result.producer)} · ${result.producer.scope === 'user' ? '所有项目共用' : '当前项目内共用'}`] : []),
@@ -8910,7 +9254,7 @@ export function ConsoleAppV4(props) {
8910
9254
  : group.applyStatus === 'failed' ? '已保存 · 应用失败'
8911
9255
  : group.applyStatus === 'succeeded' ? '已保存 · 已应用'
8912
9256
  : '已保存';
8913
- return _jsx(ResultRow, { title: scopeLabel(group.scope), description: group.errorCode, meta: outcome, status: status, compact: true }, `config:${group.scope}`);
9257
+ return _jsx(ResultRow, { title: configLayerLabel({ profile: group.platform ?? 'common' }), description: group.errorCode, meta: outcome, status: status, compact: true }, `config:${group.platform ?? 'common'}`);
8914
9258
  }) ?? [];
8915
9259
  const taskRootResults = taskSummary?.rootResults ?? [];
8916
9260
  const blockedTaskTargets = new Map((taskSummary?.blockedTargets ?? []).map((entry) => [
@@ -8950,7 +9294,7 @@ export function ConsoleAppV4(props) {
8950
9294
  : target.relation === 'transitive' ? '间接依赖' : '所选插件位置';
8951
9295
  return {
8952
9296
  key: `target:${root.pluginId}:${key}`,
8953
- title: `${root.pluginId}${targetPluginId === root.pluginId ? '' : ` → ${targetPluginId}`} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`,
9297
+ title: `${root.pluginId}${targetPluginId === root.pluginId ? '' : ` → ${targetPluginId}`} · ${agentLabel(target.platform)} · ${scopeLabel(target.scope)}`,
8954
9298
  description: blockedTarget
8955
9299
  ? pluginWriteTaskBlockLabel(blockedTarget.reason)
8956
9300
  : `${relation}${target.errorCode ? ` · ${target.errorCode}` : ''}`,
@@ -8982,7 +9326,7 @@ export function ConsoleAppV4(props) {
8982
9326
  const relation = target.relation === 'requested' ? '你选择的插件'
8983
9327
  : target.relation === 'direct' ? '直接依赖'
8984
9328
  : target.relation === 'transitive' ? '间接依赖' : undefined;
8985
- return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
9329
+ return _jsx(ResultRow, { title: `${resultTargetName(target)} · ${agentLabel(target.platform)} · ${scopeLabel(target.scope)}`, description: relation, meta: state.label, status: state.status ?? 'muted', compact: true }, `batch-target:${target.pluginId ?? ''}:${target.platform}:${target.scope}`);
8986
9330
  });
8987
9331
  const taskResultLimit = Math.max(1, pageActionBudget(5) - resultActionViewport.limit);
8988
9332
  const pluginResultRows = taskOperation ? taskSelectedPluginRows : batchPluginResultRows;
@@ -9253,7 +9597,7 @@ export function ConsoleAppV4(props) {
9253
9597
  const hints = [];
9254
9598
  if (route.name === 'pluginList')
9255
9599
  hints.push('/ 搜索', 'f 筛选', '←→ 来源', 'm 多选');
9256
- if (route.name === 'projectStatus' && !showTechnical)
9600
+ if (route.name === 'intentInventory' && !showTechnical)
9257
9601
  hints.push('/ 搜索', '←→ 分区', 'f 筛选');
9258
9602
  if (route.name === 'sourceList') {
9259
9603
  hints.push('/ 搜索');
@@ -9267,9 +9611,9 @@ export function ConsoleAppV4(props) {
9267
9611
  }
9268
9612
  if (route.name === 'portableSkillSelect' && !portableToolsOpen)
9269
9613
  hints.push('/ 搜索', 'c 继续');
9270
- if (bottomActions.some((action) => action.actionId === 'project-recheck' && !action.disabled))
9271
- hints.push('r 检查项目');
9272
- if (bottomActions.some((action) => action.actionId === 'project-sync-actionable' && !action.disabled))
9614
+ if (bottomActions.some((action) => action.actionId === 'inventory-recheck' && !action.disabled))
9615
+ hints.push('r 检查清单');
9616
+ if (bottomActions.some((action) => action.actionId === 'inventory-sync-all' && !action.disabled))
9273
9617
  hints.push('c 同步声明');
9274
9618
  if (bottomActions.some((action) => action.actionId === 'source-check-all' && !action.disabled))
9275
9619
  hints.push('r 检查全部');
@@ -9405,7 +9749,7 @@ export function ConsoleAppV4(props) {
9405
9749
  setScrollOffset(0);
9406
9750
  });
9407
9751
  useEffect(() => {
9408
- if (!targetActionSignature || !actions[focus]?.disabled)
9752
+ if (!targetActionSignature || actions[focus] && !actions[focus]?.disabled)
9409
9753
  return;
9410
9754
  const firstEnabled = actions.findIndex((action) => !action.disabled);
9411
9755
  if (firstEnabled < 0)
@@ -9525,6 +9869,173 @@ export function ConsoleAppV4(props) {
9525
9869
  ? Math.min(effectiveFocus, Math.max(0, actions.length - 1))
9526
9870
  : effectiveFocus;
9527
9871
  useInput((input, key) => {
9872
+ if (scopedFlow && tooSmall && !overlay) {
9873
+ if (scopedFlow.stage !== 'progress' && (input.toLowerCase() === 'q'
9874
+ || key.ctrl && input.toLowerCase() === 'c'))
9875
+ requestConfirmedExit();
9876
+ return;
9877
+ }
9878
+ if (configSaveState?.status === 'saving')
9879
+ return;
9880
+ if (configSaveState?.status === 'saved') {
9881
+ if (tooSmall)
9882
+ return;
9883
+ if (key.ctrl && input.toLowerCase() === 'c') {
9884
+ requestConfirmedExit();
9885
+ return;
9886
+ }
9887
+ if (key.escape) {
9888
+ dismissConfigSaved();
9889
+ setConfigSavedFocus(0);
9890
+ return;
9891
+ }
9892
+ if (key.upArrow || key.downArrow) {
9893
+ setConfigSavedFocus((value) => 1 - value);
9894
+ return;
9895
+ }
9896
+ if (key.return) {
9897
+ if (configSavedFocus === 0)
9898
+ applySavedConfig();
9899
+ else
9900
+ dismissConfigSaved();
9901
+ setConfigSavedFocus(0);
9902
+ }
9903
+ return;
9904
+ }
9905
+ if (agentWelcome) {
9906
+ if (tooSmall)
9907
+ return;
9908
+ if (key.ctrl && input.toLowerCase() === 'c') {
9909
+ requestConfirmedExit();
9910
+ return;
9911
+ }
9912
+ if (key.escape) {
9913
+ setAgentWelcome(false);
9914
+ return;
9915
+ }
9916
+ if (key.upArrow || key.downArrow) {
9917
+ setAgentWelcomeFocus((value) => 1 - value);
9918
+ return;
9919
+ }
9920
+ if (key.return) {
9921
+ if (agentWelcomeFocus === 0)
9922
+ openAgentManagement();
9923
+ else
9924
+ setAgentWelcome(false);
9925
+ }
9926
+ return;
9927
+ }
9928
+ if (scopedFlow?.stage === 'agents' && !overlay)
9929
+ return;
9930
+ if (scopedDiscardIntent) {
9931
+ if (key.escape || input.toLowerCase() === 'b' || input.toLowerCase() === 'n') {
9932
+ setScopedDiscardIntent(undefined);
9933
+ return;
9934
+ }
9935
+ if (input.toLowerCase() === 'y' || key.return) {
9936
+ const intent = scopedDiscardIntent;
9937
+ setScopedDiscardIntent(undefined);
9938
+ closeScopedFlow();
9939
+ if (intent === 'home' || intent === 'reload')
9940
+ requestSessionNavigation(intent);
9941
+ if (intent === 'exit')
9942
+ requestConfirmedExit();
9943
+ }
9944
+ return;
9945
+ }
9946
+ if (scopedFlow && !overlay) {
9947
+ const flow = scopedFlow;
9948
+ if (flow.stage === 'timeout') {
9949
+ if (key.escape) {
9950
+ beginScopedTask(flow.request);
9951
+ return;
9952
+ }
9953
+ if (key.return) {
9954
+ try {
9955
+ const scriptTimeoutMs = parseScriptTimeoutMs(scriptTimeoutInput);
9956
+ setScriptTimeoutError(undefined);
9957
+ beginScopedTask({ ...flow.request, scriptTimeoutMs });
9958
+ }
9959
+ catch (error) {
9960
+ setScriptTimeoutError(error instanceof Error ? error.message : '脚本超时无效。');
9961
+ }
9962
+ return;
9963
+ }
9964
+ if (input === '\u0015' || input.toLowerCase() === 'u' && key.ctrl)
9965
+ setScriptTimeoutInput('');
9966
+ else if (key.backspace || key.delete)
9967
+ setScriptTimeoutInput(removeLastGrapheme);
9968
+ else if (input && !key.ctrl && !key.meta && input >= ' ')
9969
+ setScriptTimeoutInput((value) => value + input);
9970
+ setScriptTimeoutError(undefined);
9971
+ return;
9972
+ }
9973
+ if (input.toLowerCase() === 'd' && (flow.stage === 'review' || flow.stage === 'risk' || flow.stage === 'result')) {
9974
+ setShowTechnical((value) => !value);
9975
+ setBatchTargetOffset(0);
9976
+ return;
9977
+ }
9978
+ const dirtyTargets = flow.stage === 'targets'
9979
+ && flow.agents.join('\0') !== flow.savedAgents.join('\0');
9980
+ const navigation = resolveTuiSessionNavigationKey({ input, key }, { characterInput: false });
9981
+ if (navigation) {
9982
+ if (flow.stage === 'progress')
9983
+ return;
9984
+ if (dirtyTargets) {
9985
+ setScopedDiscardIntent(navigation);
9986
+ return;
9987
+ }
9988
+ closeScopedFlow();
9989
+ requestSessionNavigation(navigation);
9990
+ return;
9991
+ }
9992
+ if (input.toLowerCase() === 'q' || key.ctrl && input.toLowerCase() === 'c') {
9993
+ if (flow.stage !== 'progress') {
9994
+ if (dirtyTargets)
9995
+ setScopedDiscardIntent('exit');
9996
+ else
9997
+ requestConfirmedExit();
9998
+ }
9999
+ return;
10000
+ }
10001
+ if (flow.stage === 'review' || flow.stage === 'risk' || flow.stage === 'result') {
10002
+ if (input.toLowerCase() === 'y' && readingText !== undefined) {
10003
+ void copyToClipboard(readingText).then(() => setCopyFeedback({ routeKey, message: '全文已复制' })).catch(() => setCopyFeedback({ routeKey, message: '复制失败,请重试' }));
10004
+ return;
10005
+ }
10006
+ if (key.pageUp || key.pageDown || key.leftArrow || key.rightArrow) {
10007
+ readerNavigation?.move(key.pageUp || key.leftArrow ? -1 : 1);
10008
+ return;
10009
+ }
10010
+ }
10011
+ if (flow.stage === 'progress')
10012
+ return;
10013
+ if (key.escape || input.toLowerCase() === 'b') {
10014
+ if (flow.stage === 'risk') {
10015
+ setScopedFlow({ ...flow, stage: 'review', focus: 0 });
10016
+ return;
10017
+ }
10018
+ if (dirtyTargets) {
10019
+ setScopedDiscardIntent('close');
10020
+ return;
10021
+ }
10022
+ closeScopedFlow();
10023
+ return;
10024
+ }
10025
+ if (key.upArrow || key.downArrow || input.toLowerCase() === 'j' || input.toLowerCase() === 'k') {
10026
+ setScopedFlow({ ...flow, focus: moveIndex(actions.length, flow.focus, key.upArrow || input.toLowerCase() === 'k' ? -1 : 1) });
10027
+ return;
10028
+ }
10029
+ if (flow.stage === 'targets' && input.toLowerCase() === 's') {
10030
+ runAction(actions.find((action) => action.actionId === 'targets-save'));
10031
+ return;
10032
+ }
10033
+ if (key.return || input === ' ' && flow.stage === 'targets')
10034
+ runAction(actions[flow.focus]);
10035
+ else if (key.return)
10036
+ runAction(actions[flow.focus]);
10037
+ return;
10038
+ }
9528
10039
  if (!overlay && route.name === 'reviewPreparing' && snapshot.reviewPreparation?.state === 'failed'
9529
10040
  && (key.escape || input.toLowerCase() === 'b')) {
9530
10041
  back();
@@ -9644,43 +10155,6 @@ export function ConsoleAppV4(props) {
9644
10155
  }
9645
10156
  return;
9646
10157
  }
9647
- if (projectReminderOpen) {
9648
- const closeProjectReminder = () => {
9649
- setProjectReminderOpen(false);
9650
- setOverlayFocus(0);
9651
- };
9652
- if (key.escape || input.toLowerCase() === 'b') {
9653
- closeProjectReminder();
9654
- }
9655
- else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
9656
- requestConfirmedExit(closeProjectReminder);
9657
- }
9658
- else if (props.onSessionNavigation && (input === 'h' || input === 'H')) {
9659
- closeProjectReminder();
9660
- requestSessionNavigation(input === 'H' ? 'reload' : 'home');
9661
- }
9662
- else if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
9663
- setOverlayFocus((value) => {
9664
- if ((snapshot.projectIntentReminder?.actionableTargets.length ?? 0) > 0) {
9665
- return moveIndex(3, value, key.upArrow || key.leftArrow ? -1 : 1);
9666
- }
9667
- return value === 0 ? 2 : 0;
9668
- });
9669
- }
9670
- else if (key.return) {
9671
- const reminder = snapshot.projectIntentReminder;
9672
- closeProjectReminder();
9673
- if (!reminder || overlayFocus === 0)
9674
- return;
9675
- if (overlayFocus === 1 && reminder.actionableTargets.length > 0) {
9676
- prepareCurrentProjectTargets(reminder.actionableTargets);
9677
- }
9678
- else {
9679
- go({ name: 'projectStatus' });
9680
- }
9681
- }
9682
- return;
9683
- }
9684
10158
  if (operationConfirmation) {
9685
10159
  const activeReview = snapshot.activeReview;
9686
10160
  const closeConfirmation = () => {
@@ -9903,6 +10377,15 @@ export function ConsoleAppV4(props) {
9903
10377
  });
9904
10378
  return;
9905
10379
  }
10380
+ if (route.name === 'configFields' && !route.view && visibleConfigTabs.length > 1
10381
+ && (key.leftArrow || key.rightArrow)) {
10382
+ const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeConfigTabId));
10383
+ const next = visibleConfigTabs[(current + (key.leftArrow ? -1 : 1) + visibleConfigTabs.length) % visibleConfigTabs.length];
10384
+ props.controller.updatePageContext({ configGroupId: next.id, focusIndex: 0, scrollOffset: 0 });
10385
+ setFocus(0);
10386
+ setScrollOffset(0);
10387
+ return;
10388
+ }
9906
10389
  if (readerNavigation && (key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)) {
9907
10390
  readerNavigation.move(key.pageUp || key.leftArrow ? -1 : 1);
9908
10391
  return;
@@ -10015,7 +10498,7 @@ export function ConsoleAppV4(props) {
10015
10498
  if ((key.pageUp || key.pageDown || key.leftArrow || key.rightArrow)
10016
10499
  && (route.name === 'portableDependencies' || route.name === 'pluginContent'
10017
10500
  || route.name === 'pluginTechnical'
10018
- || route.name === 'projectStatus' && showTechnical
10501
+ && showTechnical
10019
10502
  || route.name === 'issueDetail' && showTechnical
10020
10503
  || route.name === 'portableBlocker' && showTechnical
10021
10504
  || route.name === 'configUnavailable' && showTechnical
@@ -10208,7 +10691,7 @@ export function ConsoleAppV4(props) {
10208
10691
  return;
10209
10692
  }
10210
10693
  if (route.name === 'configFieldEditor' && !arrayItem && activeField && key.ctrl && input.toLowerCase() === 'r') {
10211
- const canUnset = ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
10694
+ const canUnset = [route.layer.profile].some((profile) => configLayerPages[profile]?.groups.flatMap((group) => group.fields)
10212
10695
  .some((field) => field.key === activeField.key && configFieldCanUnset(field)));
10213
10696
  if (canUnset)
10214
10697
  removeDraft(activeField.key);
@@ -10219,7 +10702,7 @@ export function ConsoleAppV4(props) {
10219
10702
  && !(activeField && 'valueRef' in activeField.definition && activeField.definition.valueRef)
10220
10703
  && (route.editor === 'string' || route.editor === 'number')
10221
10704
  && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')
10222
- && ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
10705
+ && [route.layer.profile].some((profile) => configLayerPages[profile]?.groups.flatMap((group) => group.fields)
10223
10706
  .some((field) => field.key === activeField?.key && configFieldCanSet(field)))) {
10224
10707
  if (key.escape) {
10225
10708
  setConfigCandidate(undefined);
@@ -10335,7 +10818,8 @@ export function ConsoleAppV4(props) {
10335
10818
  setInputError(undefined);
10336
10819
  return;
10337
10820
  }
10338
- if (searchFocus && (route.name === 'pluginList' || route.name === 'projectStatus'
10821
+ if (searchFocus && (route.name === 'pluginList'
10822
+ || route.name === 'intentInventory'
10339
10823
  || route.name === 'pluginContent'
10340
10824
  || route.name === 'batchSelection' || route.name === 'sourceList'
10341
10825
  || route.name === 'portableSkillSelect')) {
@@ -10351,7 +10835,7 @@ export function ConsoleAppV4(props) {
10351
10835
  setSearchFocus(false);
10352
10836
  const candidateCount = route.name === 'batchSelection' && batchDraft
10353
10837
  ? batchCandidates(batchDraft).length
10354
- : route.name === 'projectStatus' || route.name === 'pluginContent'
10838
+ : route.name === 'intentInventory' || route.name === 'pluginContent'
10355
10839
  || route.name === 'portableSkillSelect'
10356
10840
  || route.name === 'sourceList' ? actions.length - bottomActions.length : pluginRows.length;
10357
10841
  setFocus((value) => moveIndex(candidateCount, Math.min(value, Math.max(0, candidateCount - 1)), key.upArrow ? -1 : 1));
@@ -10387,12 +10871,12 @@ export function ConsoleAppV4(props) {
10387
10871
  ...(nextQuery ? { query: nextQuery } : {}), installFilter }).map((item) => `plugin:${item.pluginId}`);
10388
10872
  retained = ids.indexOf(focusedId);
10389
10873
  }
10390
- else if (route.name === 'projectStatus') {
10391
- const section = snapshot.router.current.context.projectSection ?? 'declared';
10392
- const filter = snapshot.router.current.context.projectStatusFilter ?? 'all';
10393
- retained = props.controller.listProjectInventoryRoots(section).filter((root) => queryMatches(nextQuery, root.displayName, root.pluginId, root.sourceId)
10874
+ else if (route.name === 'intentInventory') {
10875
+ const section = snapshot.router.current.context.inventorySection ?? 'declared';
10876
+ const filter = snapshot.router.current.context.inventoryStatusFilter ?? 'all';
10877
+ retained = props.controller.listScopedInventoryRoots(route.scope, section).filter((root) => queryMatches(nextQuery, root.displayName, root.pluginId, root.sourceId)
10394
10878
  && (filter === 'all' || root.state === filter))
10395
- .findIndex((root) => `project-root:${root.pluginId}` === focusedId);
10879
+ .findIndex((root) => `inventory-root:${root.pluginId}` === focusedId);
10396
10880
  }
10397
10881
  else if (route.name === 'pluginContent') {
10398
10882
  const type = snapshot.router.current.context.contentTypeTab ?? 'all';
@@ -10418,7 +10902,7 @@ export function ConsoleAppV4(props) {
10418
10902
  if (route.name !== 'batchSelection') {
10419
10903
  setQuery(nextQuery);
10420
10904
  props.controller.updatePageContext({ query: nextQuery });
10421
- if (route.name === 'projectStatus') {
10905
+ if (route.name === 'intentInventory') {
10422
10906
  dynamicMenuFocus.current = undefined;
10423
10907
  actionJumpAnchor.current = undefined;
10424
10908
  }
@@ -10433,9 +10917,18 @@ export function ConsoleAppV4(props) {
10433
10917
  // Paste and composed text are data, never multiple background commands.
10434
10918
  if (input.length > 1 && !key.ctrl && !key.meta)
10435
10919
  return;
10920
+ if (key.tab && route.name === 'configFields') {
10921
+ const index = CONFIG_PROFILE_KEYS.indexOf(route.layer.profile);
10922
+ const profile = CONFIG_PROFILE_KEYS[(index + (key.shift ? -1 : 1) + CONFIG_PROFILE_KEYS.length) % CONFIG_PROFILE_KEYS.length];
10923
+ props.controller.replace({ ...route, layer: { ...route.layer, profile } });
10924
+ setFocus(0);
10925
+ setScrollOffset(0);
10926
+ return;
10927
+ }
10436
10928
  if (key.tab)
10437
10929
  return;
10438
- if ((route.name === 'projectStatus' && !showTechnical
10930
+ if ((route.name === 'intentInventory' && !showTechnical
10931
+ || route.name === 'intentInventory' && !showTechnical
10439
10932
  || route.name === 'pluginContent' && !contentReader
10440
10933
  || route.name === 'sourceList' || route.name === 'portableSkillSelect' && !portableToolsOpen) && input === '/') {
10441
10934
  setSearchCursor([...new Intl.Segmenter(undefined, { granularity: 'grapheme' })
@@ -10465,7 +10958,7 @@ export function ConsoleAppV4(props) {
10465
10958
  const entityIndex = anchor.entityId
10466
10959
  ? actions.findIndex((action) => action.pluginId === anchor.entityId) : -1;
10467
10960
  const next = entityIndex >= 0 ? entityIndex : Math.min(anchor.index, Math.max(0, firstAction - 1));
10468
- if (route.name === 'projectStatus' && dynamicActionIds && dynamicActionSignature) {
10961
+ if ((route.name === 'intentInventory') && dynamicActionIds && dynamicActionSignature) {
10469
10962
  dynamicMenuFocus.current = { routeKey, actionIds: dynamicActionSignature,
10470
10963
  focusedActionId: dynamicActionIds[next] };
10471
10964
  }
@@ -10474,7 +10967,7 @@ export function ConsoleAppV4(props) {
10474
10967
  }
10475
10968
  else {
10476
10969
  actionJumpAnchor.current = { routeKey, index: focus, entityId: actions[focus]?.pluginId };
10477
- if (route.name === 'projectStatus' && dynamicActionIds && dynamicActionSignature) {
10970
+ if ((route.name === 'intentInventory') && dynamicActionIds && dynamicActionSignature) {
10478
10971
  dynamicMenuFocus.current = { routeKey, actionIds: dynamicActionSignature,
10479
10972
  focusedActionId: dynamicActionIds[firstAction] };
10480
10973
  }
@@ -10544,7 +11037,7 @@ export function ConsoleAppV4(props) {
10544
11037
  back();
10545
11038
  }
10546
11039
  else if (globalAction === 'back' && route.name === 'configLayer') {
10547
- if (configCandidate?.selectingScope) {
11040
+ if (configCandidate) {
10548
11041
  setConfigCandidate(undefined);
10549
11042
  const owner = `${route.pluginId}\0${route.fieldKey}`;
10550
11043
  setObjectDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !key.startsWith(owner))));
@@ -10559,10 +11052,7 @@ export function ConsoleAppV4(props) {
10559
11052
  }
10560
11053
  else if (globalAction === 'back'
10561
11054
  && (route.name === 'configFieldEditor' || route.name === 'configFieldAction')) {
10562
- const returnToReferenceScope = !arrayItem && editingScope && activeTopField
10563
- && configReferenceScalars(activeTopField.definition).length > 0
10564
- && ['user', 'project'].filter((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
10565
- .some((field) => field.key === activeTopField.key && configFieldCanSet(field))).length > 1;
11055
+ const returnToReferenceScope = false;
10566
11056
  if (!returnToReferenceScope)
10567
11057
  setConfigCandidate(undefined);
10568
11058
  if (activeArrayKey && !arrayItem && !returnToReferenceScope) {
@@ -10631,21 +11121,24 @@ export function ConsoleAppV4(props) {
10631
11121
  setFocus(0);
10632
11122
  setScrollOffset(0);
10633
11123
  }
10634
- else if (route.name === 'projectStatus' && input.toLowerCase() === 'f' && !showTechnical) {
11124
+ else if (route.name === 'intentInventory' && input.toLowerCase() === 'f' && !showTechnical) {
10635
11125
  toggleProjectStatusFilter?.();
10636
11126
  }
10637
- else if (route.name === 'projectStatus' && input.toLowerCase() === 'r') {
10638
- runAction(bottomActions.find((action) => action.actionId === 'project-recheck'));
11127
+ else if (route.name === 'intentInventory' && input.toLowerCase() === 'r') {
11128
+ runAction(bottomActions.find((action) => action.actionId === 'inventory-recheck'));
10639
11129
  }
10640
- else if (route.name === 'projectStatus' && input.toLowerCase() === 'c') {
10641
- runAction(bottomActions.find((action) => action.actionId === 'project-sync-actionable'));
11130
+ else if (route.name === 'intentInventory' && input.toLowerCase() === 'c') {
11131
+ runAction(bottomActions.find((action) => action.actionId === 'inventory-sync-all'));
10642
11132
  }
10643
- else if (route.name === 'installTargets' && route.view !== 'blocked'
10644
- && input.toLowerCase() === 'c') {
10645
- runAction(bottomActions.find((action) => action.actionId === 'confirm-install-platforms'));
10646
- }
10647
- else if (route.name === 'batchInstallPlatforms' && input.toLowerCase() === 'c') {
10648
- runAction(actions.find((action) => action.actionId === 'batch-install-platforms-confirm'));
11133
+ else if (route.name === 'intentInventory' && input === ' ' && !showTechnical) {
11134
+ const pluginId = actions[effectiveFocus]?.pluginId;
11135
+ if (pluginId) {
11136
+ const selectedKey = route.scope === 'project' ? `project:${route.projectKey ?? ''}` : 'user';
11137
+ const selected = scopedSelected.current[selectedKey] ?? [];
11138
+ scopedSelected.current[selectedKey] = selected.includes(pluginId)
11139
+ ? selected.filter((item) => item !== pluginId) : [...selected, pluginId];
11140
+ setScopedSelectionRevision((value) => value + 1);
11141
+ }
10649
11142
  }
10650
11143
  else if (route.name === 'batchInstallScope' && input.toLowerCase() === 'c') {
10651
11144
  runAction(actions.find((action) => action.actionId === 'batch-placement-confirm'));
@@ -10700,14 +11193,7 @@ export function ConsoleAppV4(props) {
10700
11193
  runAction(bottomActions[0]);
10701
11194
  }
10702
11195
  else if (route.name === 'configFields' && input.toLowerCase() === 'u') {
10703
- const key = actions[focus]?.configKey;
10704
- const field = currentConfigPage?.groups.flatMap((group) => group.fields)
10705
- .find((candidate) => candidate.key === key);
10706
- const canUnset = ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
10707
- .some((candidate) => candidate.key === key && configFieldCanUnset(candidate)));
10708
- if (field && canUnset) {
10709
- removeDraft(field.key);
10710
- }
11196
+ runAction(actions.find((action) => action.actionId === 'config-reset-profile'));
10711
11197
  }
10712
11198
  else if (route.name === 'configFields' && !route.view && visibleConfigTabs.length > 1
10713
11199
  && (key.leftArrow || key.rightArrow)) {
@@ -10718,7 +11204,7 @@ export function ConsoleAppV4(props) {
10718
11204
  setFocus(0);
10719
11205
  setScrollOffset(0);
10720
11206
  }
10721
- else if (route.name === 'projectStatus' && !showTechnical
11207
+ else if ((route.name === 'intentInventory') && !showTechnical
10722
11208
  && (key.leftArrow || key.rightArrow)) {
10723
11209
  moveProjectSection?.(key.leftArrow ? -1 : 1);
10724
11210
  }
@@ -10750,6 +11236,20 @@ export function ConsoleAppV4(props) {
10750
11236
  if (candidate)
10751
11237
  props.controller.togglePortableSkill(candidate.name);
10752
11238
  }
11239
+ else if (route.name === 'pluginDetail' && detailNavigation && (key.leftArrow || key.rightArrow)) {
11240
+ return;
11241
+ }
11242
+ else if (route.name === 'pluginDetail' && detailNavigation && (key.pageUp || key.pageDown)) {
11243
+ setBatchTargetOffset((value) => Math.max(0, Math.min(detailNavigation.maxOffset, value + (key.pageUp ? -detailNavigation.lineLimit : detailNavigation.lineLimit))));
11244
+ }
11245
+ else if (route.name === 'pluginDetail' && detailNavigation && focus === 0
11246
+ && (key.upArrow || input === 'k') && batchTargetOffset > 0) {
11247
+ setBatchTargetOffset((value) => value - 1);
11248
+ }
11249
+ else if (route.name === 'pluginDetail' && detailNavigation
11250
+ && (key.downArrow || input === 'j') && batchTargetOffset < detailNavigation.maxOffset) {
11251
+ setBatchTargetOffset((value) => value + 1);
11252
+ }
10753
11253
  else if (entityCardNavigation && focus < entityCardNavigation.itemCount
10754
11254
  && (key.upArrow || input.toLowerCase() === 'k') && batchTargetOffset > 0) {
10755
11255
  setBatchTargetOffset((value) => value - 1);
@@ -10834,7 +11334,14 @@ export function ConsoleAppV4(props) {
10834
11334
  }
10835
11335
  if (actionError)
10836
11336
  description = actionError;
10837
- const base = (_jsx(ScreenFrame, { title: projectReminderOpen ? '' : title, description: projectReminderOpen ? undefined : description, breadcrumb: breadcrumb, context: context, headerHint: !projectReminderOpen && route.name === 'pluginList' ? { key: 'm', label: '多选' } : undefined, help: portableEmptySelectionOpen ? 'Enter 返回选择 · Esc 返回' : help, size: size, children: projectReminderOpen || sourceTransportOverlay ? null : content }));
11337
+ const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, headerHint: route.name === 'pluginList' ? { key: 'm', label: '多选' } : undefined, help: portableEmptySelectionOpen ? 'Enter 返回选择 · Esc 返回' : help, size: size, children: sourceTransportOverlay ? null : content }));
11338
+ if (scopedFlow?.stage === 'risk') {
11339
+ const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
11340
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor((size.columns - modalWidth) / 2)), width: modalWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: "\u8BF7\u786E\u8BA4\u672C\u6B21\u98CE\u9669", description: "\u786E\u8BA4\u53EA\u5BF9\u5F53\u524D\u64CD\u4F5C\u6C47\u603B\u6709\u6548\uFF1B\u9ED8\u8BA4\u8FD4\u56DE\u68C0\u67E5\u3002", children: [readingPage(readingText ?? '', modalWidth - 6, Math.max(1, pageActionBudget(7))), _jsx(SemanticRows, { actions: actions, focus: scopedFlow.focus, kind: "action", compact: true })] }) })] });
11341
+ }
11342
+ if (scopedDiscardIntent) {
11343
+ return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor(size.columns / 6)), width: Math.max(40, Math.floor(size.columns * 2 / 3)), flexDirection: "column", children: _jsxs(ReviewCard, { title: "\u653E\u5F03\u672A\u4FDD\u5B58\u7684\u5DE5\u5177\u9009\u62E9\uFF1F", description: "\u6B64\u6E05\u5355\u7684\u672C\u673A\u5DE5\u5177\u8349\u7A3F\u5C1A\u672A\u4FDD\u5B58\u3002", children: [_jsx(SemanticRows, { actions: [{ label: '继续编辑' }, { label: '放弃修改', status: 'warning' }], focus: 1, kind: "action", compact: true }), _jsx(Text, { color: tuiTheme.colors.muted, children: "y / Enter \u653E\u5F03 \u00B7 Esc \u8FD4\u56DE" })] }) })] });
11344
+ }
10838
11345
  if (portableEmptySelectionOpen) {
10839
11346
  const modalWidth = Math.max(40, Math.floor(size.columns * 0.6));
10840
11347
  return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: 7, marginLeft: Math.max(2, Math.floor((size.columns - modalWidth) / 2)), width: modalWidth, flexDirection: "column", backgroundColor: tuiTheme.colors.panelBackground, children: _jsx(ReviewCard, { title: "\u8FD8\u6CA1\u6709\u9009\u62E9 Skill", description: "\u7528 Space \u52FE\u9009\uFF1B\u4E5F\u53EF\u4EE5\u6253\u5F00\u9009\u62E9\u5DE5\u5177\u3002", children: _jsx(SemanticRows, { actions: [{ label: '返回选择' }], focus: 0, kind: "action", compact: true }) }) })] });
@@ -10846,27 +11353,6 @@ export function ConsoleAppV4(props) {
10846
11353
  definition: activeField.definition.items, value: row.value, summary: activeField.itemSummary,
10847
11354
  }).text : '', children: [_jsx(SemanticRows, { actions: [{ label: '上移' }, { label: '下移' }, { label: '删除这一项', status: 'error' }], focus: arrayToolFocus, kind: "action", compact: true }), _jsx(Text, { children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }) })] });
10848
11355
  }
10849
- if (projectReminderOpen && snapshot.projectIntentReminder) {
10850
- const reminder = snapshot.projectIntentReminder;
10851
- const pluginCount = new Set([
10852
- ...reminder.actionableTargets,
10853
- ...reminder.blockedTargets,
10854
- ...reminder.unconfirmedTargets,
10855
- ].map((target) => target.pluginId)).size;
10856
- const reminderActions = [
10857
- { label: '稍后处理', description: '首页会保留项目插件入口' },
10858
- {
10859
- label: `查看并同步 ${reminder.actionableTargets.length} 个位置`,
10860
- description: `先显示完整操作汇总和脚本影响,再单独确认;保留 ${reminder.blockedTargets.length} 个受阻位置和 ${reminder.unconfirmedTargets.length} 个未确认位置`,
10861
- disabled: reminder.actionableTargets.length === 0 ? '当前没有已确认可同步的目标' : undefined,
10862
- },
10863
- { label: '查看全部项目插件', description: '包含需要先处理和无法确认的项目目标' },
10864
- ];
10865
- const reminderWidth = Math.max(40, size.columns - (policy.mode === 'compact' ? 8 : 16));
10866
- return _jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: base }), _jsx(Box, { position: "absolute", marginTop: policy.mode === 'compact' ? 2 : 5, marginLeft: Math.max(2, Math.floor((size.columns - reminderWidth) / 2)), width: reminderWidth, flexDirection: "column", children: _jsxs(ReviewCard, { title: `本项目有 ${pluginCount} 个插件尚未就绪`, description: `${reminder.actionableTargets.length} 个位置可同步;`
10867
- + `${reminder.blockedTargets.length} 个位置需要先处理;`
10868
- + `${reminder.unconfirmedTargets.length} 个位置未确认。`, children: [_jsx(StaticRow, { title: "\u6309\u9879\u76EE\u5171\u4EAB\u8BBE\u7F6E\u6062\u590D\uFF0C\u4E2A\u4EBA\u8BBE\u7F6E\u4E0D\u4F1A\u88AB\u6E05\u7A7A\u3002", compact: true }), _jsx(StaticRow, { title: "\u786E\u8BA4\u540C\u6B65\u524D\u53EF\u67E5\u770B\u811A\u672C\u548C\u5B8C\u6574\u5F71\u54CD\u3002", status: "warning", compact: true }), _jsx(SemanticRows, { actions: reminderActions, focus: overlayFocus, kind: "action", compact: true }), _jsx(StaticRow, { title: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u7A0D\u540E\u5904\u7406", status: "muted", compact: true })] }) })] });
10869
- }
10870
11356
  if (operationConfirmation && snapshot.activeReview) {
10871
11357
  const review = snapshot.activeReview;
10872
11358
  const portableUserConfirmation = review.operationKind === 'portableInstall'