@lesliechan721/aw 0.1.0-beta.7 → 0.1.0-beta.8

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 (237) hide show
  1. package/dist/authoring/scripts/init-plugin.mjs +4 -33
  2. package/dist/authoring/scripts/init-plugin.mjs.map +1 -1
  3. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  4. package/dist/authoring/scripts/plugin-status.mjs +8 -11
  5. package/dist/authoring/scripts/plugin-status.mjs.map +1 -1
  6. package/dist/authoring/scripts/remove-plugin.mjs +1 -0
  7. package/dist/authoring/scripts/remove-plugin.mjs.map +1 -1
  8. package/dist/authoring/scripts/validate-plugin.mjs +104 -54
  9. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  10. package/dist/scripts/codex-eval-app-server.d.ts +2 -0
  11. package/dist/scripts/codex-eval-app-server.js +107 -1
  12. package/dist/scripts/codex-eval-app-server.js.map +1 -1
  13. package/dist/scripts/eval-plugin-skills.js +5 -2
  14. package/dist/scripts/eval-plugin-skills.js.map +1 -1
  15. package/dist/scripts/plugin-eval-contract.d.ts +5 -1
  16. package/dist/scripts/plugin-eval-contract.js +54 -4
  17. package/dist/scripts/plugin-eval-contract.js.map +1 -1
  18. package/dist/scripts/plugin-staging.d.ts +6 -7
  19. package/dist/scripts/plugin-staging.js +11 -23
  20. package/dist/scripts/plugin-staging.js.map +1 -1
  21. package/dist/scripts/test-plugin-scripts.js +2 -33
  22. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  23. package/dist/src/adapters/helpers.js +2 -2
  24. package/dist/src/adapters/helpers.js.map +1 -1
  25. package/dist/src/authoring/workspace-commands.d.ts +17 -2
  26. package/dist/src/authoring/workspace-commands.js +21 -4
  27. package/dist/src/authoring/workspace-commands.js.map +1 -1
  28. package/dist/src/commands/aw-capabilities.d.ts +78 -0
  29. package/dist/src/commands/aw-capabilities.js +259 -0
  30. package/dist/src/commands/aw-capabilities.js.map +1 -0
  31. package/dist/src/commands/catalog-context.d.ts +52 -0
  32. package/dist/src/commands/catalog-context.js +156 -0
  33. package/dist/src/commands/catalog-context.js.map +1 -0
  34. package/dist/src/commands/catalog-dev-source.d.ts +112 -0
  35. package/dist/src/commands/catalog-dev-source.js +481 -0
  36. package/dist/src/commands/catalog-dev-source.js.map +1 -0
  37. package/dist/src/commands/catalog-generation.d.ts +26 -0
  38. package/dist/src/commands/catalog-generation.js +14 -0
  39. package/dist/src/commands/catalog-generation.js.map +1 -0
  40. package/dist/src/commands/catalog-publication-inspection.d.ts +1 -1
  41. package/dist/src/commands/catalog-publication-inspection.js +4 -5
  42. package/dist/src/commands/catalog-publication-inspection.js.map +1 -1
  43. package/dist/src/commands/catalog-publication-target.d.ts +17 -0
  44. package/dist/src/commands/catalog-publication-target.js +60 -0
  45. package/dist/src/commands/catalog-publication-target.js.map +1 -0
  46. package/dist/src/commands/catalog-publish.d.ts +8 -1
  47. package/dist/src/commands/catalog-publish.js +25 -2
  48. package/dist/src/commands/catalog-publish.js.map +1 -1
  49. package/dist/src/commands/catalog-source.d.ts +23 -1
  50. package/dist/src/commands/catalog-source.js +126 -40
  51. package/dist/src/commands/catalog-source.js.map +1 -1
  52. package/dist/src/commands/catalog-status.d.ts +18 -2
  53. package/dist/src/commands/catalog-status.js +122 -18
  54. package/dist/src/commands/catalog-status.js.map +1 -1
  55. package/dist/src/commands/init.js +1 -1
  56. package/dist/src/commands/init.js.map +1 -1
  57. package/dist/src/commands/offline-cleanup-evidence.d.ts +23 -0
  58. package/dist/src/commands/offline-cleanup-evidence.js +38 -0
  59. package/dist/src/commands/offline-cleanup-evidence.js.map +1 -0
  60. package/dist/src/commands/options.d.ts +1 -0
  61. package/dist/src/commands/options.js +1 -0
  62. package/dist/src/commands/options.js.map +1 -1
  63. package/dist/src/commands/plugin-capabilities.js +1 -1
  64. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  65. package/dist/src/commands/rebuild-command.d.ts +19 -7
  66. package/dist/src/commands/rebuild-command.js +415 -205
  67. package/dist/src/commands/rebuild-command.js.map +1 -1
  68. package/dist/src/commands/rebuild-integration.d.ts +10 -3
  69. package/dist/src/commands/rebuild-integration.js +71 -76
  70. package/dist/src/commands/rebuild-integration.js.map +1 -1
  71. package/dist/src/commands/rebuild-operations.d.ts +0 -8
  72. package/dist/src/commands/rebuild-operations.js +17 -115
  73. package/dist/src/commands/rebuild-operations.js.map +1 -1
  74. package/dist/src/commands/runtime.js +21 -34
  75. package/dist/src/commands/runtime.js.map +1 -1
  76. package/dist/src/commands/{status-v4.d.ts → status-v5.d.ts} +67 -21
  77. package/dist/src/commands/{status-v4.js → status-v5.js} +260 -30
  78. package/dist/src/commands/status-v5.js.map +1 -0
  79. package/dist/src/commands/wizard.d.ts +2 -2
  80. package/dist/src/commands/wizard.js +2 -2
  81. package/dist/src/commands/wizard.js.map +1 -1
  82. package/dist/src/commands/write-commands.js +73 -17
  83. package/dist/src/commands/write-commands.js.map +1 -1
  84. package/dist/src/constants.d.ts +6 -1
  85. package/dist/src/constants.js +6 -1
  86. package/dist/src/constants.js.map +1 -1
  87. package/dist/src/generators/local-plugin.js +57 -3
  88. package/dist/src/generators/local-plugin.js.map +1 -1
  89. package/dist/src/index.d.ts +4 -1
  90. package/dist/src/index.js +562 -85
  91. package/dist/src/index.js.map +1 -1
  92. package/dist/src/output/model.d.ts +1 -34
  93. package/dist/src/output/model.js +1 -144
  94. package/dist/src/output/model.js.map +1 -1
  95. package/dist/src/output/rebuild-progress.js +0 -3
  96. package/dist/src/output/rebuild-progress.js.map +1 -1
  97. package/dist/src/portable/commands.d.ts +4 -0
  98. package/dist/src/portable/commands.js +61 -4
  99. package/dist/src/portable/commands.js.map +1 -1
  100. package/dist/src/portable/materializer.d.ts +3 -0
  101. package/dist/src/portable/materializer.js +61 -10
  102. package/dist/src/portable/materializer.js.map +1 -1
  103. package/dist/src/tui/catalog-generation.d.ts +56 -0
  104. package/dist/src/tui/catalog-generation.js +244 -0
  105. package/dist/src/tui/catalog-generation.js.map +1 -0
  106. package/dist/src/tui/console-app-v4.d.ts +1 -1
  107. package/dist/src/tui/console-app-v4.js +446 -126
  108. package/dist/src/tui/console-app-v4.js.map +1 -1
  109. package/dist/src/tui/controller-runtime.d.ts +8 -2
  110. package/dist/src/tui/controller-runtime.js +606 -92
  111. package/dist/src/tui/controller-runtime.js.map +1 -1
  112. package/dist/src/tui/controller.d.ts +71 -6
  113. package/dist/src/tui/controller.js +377 -83
  114. package/dist/src/tui/controller.js.map +1 -1
  115. package/dist/src/tui/index.d.ts +1 -0
  116. package/dist/src/tui/index.js +1 -0
  117. package/dist/src/tui/index.js.map +1 -1
  118. package/dist/src/tui/input-display.d.ts +8 -0
  119. package/dist/src/tui/input-display.js +36 -0
  120. package/dist/src/tui/input-display.js.map +1 -0
  121. package/dist/src/tui/model.d.ts +27 -7
  122. package/dist/src/tui/model.js +273 -65
  123. package/dist/src/tui/model.js.map +1 -1
  124. package/dist/src/tui/router.d.ts +26 -0
  125. package/dist/src/tui/router.js.map +1 -1
  126. package/dist/src/tui/source-ref-presets.d.ts +3 -0
  127. package/dist/src/tui/source-ref-presets.js +11 -0
  128. package/dist/src/tui/source-ref-presets.js.map +1 -0
  129. package/dist/src/tui/views.js +31 -3
  130. package/dist/src/tui/views.js.map +1 -1
  131. package/dist/src/types.d.ts +90 -6
  132. package/dist/src/types.js.map +1 -1
  133. package/dist/src/utils/agent-version.d.ts +3 -0
  134. package/dist/src/utils/agent-version.js +13 -5
  135. package/dist/src/utils/agent-version.js.map +1 -1
  136. package/dist/src/utils/aw-home-mutation-lock.d.ts +1 -0
  137. package/dist/src/utils/aw-home-mutation-lock.js +32 -4
  138. package/dist/src/utils/aw-home-mutation-lock.js.map +1 -1
  139. package/dist/src/utils/bundle-integrity.d.ts +0 -1
  140. package/dist/src/utils/bundle-integrity.js +0 -25
  141. package/dist/src/utils/bundle-integrity.js.map +1 -1
  142. package/dist/src/utils/bundle-layout.js +8 -6
  143. package/dist/src/utils/bundle-layout.js.map +1 -1
  144. package/dist/src/utils/catalog-digests.js +13 -2
  145. package/dist/src/utils/catalog-digests.js.map +1 -1
  146. package/dist/src/utils/catalog-generation-error.d.ts +12 -0
  147. package/dist/src/utils/catalog-generation-error.js +16 -0
  148. package/dist/src/utils/catalog-generation-error.js.map +1 -0
  149. package/dist/src/utils/catalog-generation-resolver.d.ts +13 -0
  150. package/dist/src/utils/catalog-generation-resolver.js +129 -0
  151. package/dist/src/utils/catalog-generation-resolver.js.map +1 -0
  152. package/dist/src/utils/catalog-generation-service.d.ts +52 -0
  153. package/dist/src/utils/catalog-generation-service.js +620 -0
  154. package/dist/src/utils/catalog-generation-service.js.map +1 -0
  155. package/dist/src/utils/catalog-generation-state.d.ts +13 -0
  156. package/dist/src/utils/catalog-generation-state.js +201 -0
  157. package/dist/src/utils/catalog-generation-state.js.map +1 -0
  158. package/dist/src/utils/catalog-generation-store.d.ts +44 -0
  159. package/dist/src/utils/catalog-generation-store.js +387 -0
  160. package/dist/src/utils/catalog-generation-store.js.map +1 -0
  161. package/dist/src/utils/catalog-manifest-v5.d.ts +5 -0
  162. package/dist/src/utils/{catalog-manifest-v4.js → catalog-manifest-v5.js} +39 -35
  163. package/dist/src/utils/{catalog-manifest-v4.js.map → catalog-manifest-v5.js.map} +1 -1
  164. package/dist/src/utils/catalog-operation-context.d.ts +28 -0
  165. package/dist/src/utils/catalog-operation-context.js +242 -0
  166. package/dist/src/utils/catalog-operation-context.js.map +1 -0
  167. package/dist/src/utils/catalog-public-error.d.ts +7 -3
  168. package/dist/src/utils/catalog-public-error.js +20 -4
  169. package/dist/src/utils/catalog-public-error.js.map +1 -1
  170. package/dist/src/utils/catalog-snapshot.d.ts +5 -3
  171. package/dist/src/utils/catalog-snapshot.js +11 -3
  172. package/dist/src/utils/catalog-snapshot.js.map +1 -1
  173. package/dist/src/utils/catalog-source-cache.d.ts +1 -1
  174. package/dist/src/utils/catalog-source-cache.js +37 -12
  175. package/dist/src/utils/catalog-source-cache.js.map +1 -1
  176. package/dist/src/utils/catalog-source-identity.d.ts +1 -0
  177. package/dist/src/utils/catalog-source-identity.js +21 -2
  178. package/dist/src/utils/catalog-source-identity.js.map +1 -1
  179. package/dist/src/utils/catalog-sources.d.ts +1 -0
  180. package/dist/src/utils/catalog-sources.js +30 -11
  181. package/dist/src/utils/catalog-sources.js.map +1 -1
  182. package/dist/src/utils/codex-config.js +4 -4
  183. package/dist/src/utils/codex-config.js.map +1 -1
  184. package/dist/src/utils/codex-manager.d.ts +33 -0
  185. package/dist/src/utils/codex-manager.js +656 -0
  186. package/dist/src/utils/codex-manager.js.map +1 -0
  187. package/dist/src/utils/codex-skill-staging.d.ts +2 -0
  188. package/dist/src/utils/codex-skill-staging.js.map +1 -1
  189. package/dist/src/utils/dynamic-skill-tree.d.ts +4 -0
  190. package/dist/src/utils/dynamic-skill-tree.js +35 -5
  191. package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
  192. package/dist/src/utils/git-catalog-provider.js +10 -25
  193. package/dist/src/utils/git-catalog-provider.js.map +1 -1
  194. package/dist/src/utils/lifecycle-active-store.d.ts +0 -28
  195. package/dist/src/utils/lifecycle-active-store.js +35 -247
  196. package/dist/src/utils/lifecycle-active-store.js.map +1 -1
  197. package/dist/src/utils/multi-source-catalog.d.ts +3 -0
  198. package/dist/src/utils/multi-source-catalog.js +120 -10
  199. package/dist/src/utils/multi-source-catalog.js.map +1 -1
  200. package/dist/src/utils/native-cli.d.ts +15 -6
  201. package/dist/src/utils/native-cli.js +31 -39
  202. package/dist/src/utils/native-cli.js.map +1 -1
  203. package/dist/src/utils/orphan-marketplace-cleanup.js +0 -3
  204. package/dist/src/utils/orphan-marketplace-cleanup.js.map +1 -1
  205. package/dist/src/utils/ownership.d.ts +10 -1
  206. package/dist/src/utils/ownership.js +52 -24
  207. package/dist/src/utils/ownership.js.map +1 -1
  208. package/dist/src/utils/path-catalog-provider.js +12 -8
  209. package/dist/src/utils/path-catalog-provider.js.map +1 -1
  210. package/dist/src/utils/plugin-config-write.js +18 -35
  211. package/dist/src/utils/plugin-config-write.js.map +1 -1
  212. package/dist/src/utils/plugin-config.d.ts +1 -12
  213. package/dist/src/utils/plugin-config.js +107 -226
  214. package/dist/src/utils/plugin-config.js.map +1 -1
  215. package/dist/src/utils/plugin-scripts.d.ts +1 -1
  216. package/dist/src/utils/plugin-scripts.js +1 -1
  217. package/dist/src/utils/plugin-scripts.js.map +1 -1
  218. package/dist/src/utils/state-paths.js +7 -2
  219. package/dist/src/utils/state-paths.js.map +1 -1
  220. package/dist/src/utils/target-eligibility.d.ts +7 -2
  221. package/dist/src/utils/target-eligibility.js +6 -3
  222. package/dist/src/utils/target-eligibility.js.map +1 -1
  223. package/package.json +4 -3
  224. package/dist/authoring/scripts/check-plugin-versions.d.mts +0 -2
  225. package/dist/authoring/scripts/check-plugin-versions.mjs +0 -5
  226. package/dist/authoring/scripts/check-plugin-versions.mjs.map +0 -1
  227. package/dist/src/commands/status-v4.js.map +0 -1
  228. package/dist/src/commands/status.d.ts +0 -93
  229. package/dist/src/commands/status.js +0 -442
  230. package/dist/src/commands/status.js.map +0 -1
  231. package/dist/src/utils/active-marketplace-runtime-lock.d.ts +0 -12
  232. package/dist/src/utils/active-marketplace-runtime-lock.js +0 -133
  233. package/dist/src/utils/active-marketplace-runtime-lock.js.map +0 -1
  234. package/dist/src/utils/catalog-manifest-v3.d.ts +0 -2
  235. package/dist/src/utils/catalog-manifest-v3.js +0 -3
  236. package/dist/src/utils/catalog-manifest-v3.js.map +0 -1
  237. package/dist/src/utils/catalog-manifest-v4.d.ts +0 -5
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Box, Text, useInput, useWindowSize } from 'ink';
3
3
  import { useEffect, useRef, useState } from 'react';
4
4
  import path from 'node:path';
5
+ import { CATALOG_BETA_REF, CATALOG_LATEST_REF, OFFICIAL_CATALOG_SOURCE_SSH_URL, OFFICIAL_CATALOG_SOURCE_URL, } from '../constants.js';
5
6
  import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
6
7
  import { canonicalizeCatalogGitUrl } from '../utils/catalog-source-identity.js';
7
8
  import { normalizePortableAgents } from '../portable/commands.js';
@@ -10,6 +11,8 @@ import { ActionRow, Callout, ChoiceRow, DiagnosticSummary, InputCard, PluginList
10
11
  import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
11
12
  import { extractPortableError, userIssueKey } from './controller.js';
12
13
  import { resolveTuiSizePolicy } from './layout.js';
14
+ import { InputDisplay, removeLastGrapheme } from './input-display.js';
15
+ import { catalogRefPolicyForPreset } from './source-ref-presets.js';
13
16
  import { buildPlatformActions, buildPluginDetailActions, buildPluginPreviewActions, hasInstalledPlacement, } from './model.js';
14
17
  import { tuiTheme } from './theme.js';
15
18
  import { contentRowBudget, rowViewport } from './viewport.js';
@@ -26,6 +29,7 @@ export const CONSOLE_V4_SCREEN_IDS = [
26
29
  'D01', 'D02', 'D03', 'D04', 'D05', 'D06', 'D07', 'D08', 'D09',
27
30
  'D10', 'D11', 'D12', 'D13', 'D14', 'D15', 'D16', 'D17',
28
31
  'U01', 'U02', 'U03', 'U04', 'U05', 'U06', 'U07', 'U08', 'U09', 'U10', 'U11',
32
+ 'K01', 'K02', 'K03', 'K04', 'K05', 'K06', 'K07',
29
33
  ];
30
34
  function pluginMenuActionId(action) {
31
35
  return action.id === 'platform' || action.id === 'scopeConflict'
@@ -125,6 +129,18 @@ function pluginPlacementSummary(plugin) {
125
129
  return `${label} · ${scopeLabel(placement.scope)} · ${health}`;
126
130
  }).join(' | ') || '没有可用平台';
127
131
  }
132
+ function issueGroupLabel(group) {
133
+ return {
134
+ sourceRemoved: '来源已移除',
135
+ artifactCollision: '输出冲突',
136
+ sourceUnavailable: '来源不可用',
137
+ dependency: '依赖',
138
+ scope: '位置',
139
+ config: '设置',
140
+ installation: '安装',
141
+ diagnostic: '诊断',
142
+ }[group];
143
+ }
128
144
  function pluginRowStatus(plugin) {
129
145
  if (plugin.aggregateStatus === 'needsAttention')
130
146
  return 'warning';
@@ -508,7 +524,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
508
524
  return 'G01';
509
525
  const route = snapshot.router.current.route;
510
526
  if (route.name === 'home')
511
- return 'G02';
527
+ return controller.listCatalogCandidates().length > 0 ? 'K01' : 'G02';
512
528
  if (route.name === 'pluginList') {
513
529
  const items = controller.listPlugins({ mode: route.mode, sourceId: route.sourceId });
514
530
  if (route.mode === 'installed' && items.length === 0)
@@ -566,7 +582,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
566
582
  return 'P16';
567
583
  }
568
584
  if (route.name === 'reviewPreparing')
569
- return 'P07';
585
+ return snapshot.reviewPreparation?.intent.kind === 'catalogActivate' ? 'K03' : 'P07';
570
586
  const dependencyOperation = snapshot.activeReview?.operationKind === 'pluginWrite'
571
587
  && Boolean(snapshot.activeReview.changes.some((change) => change.group));
572
588
  if (route.name === 'review' && dependencyOperation) {
@@ -601,27 +617,30 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
601
617
  return snapshot.result.state === 'succeeded' ? 'S12' : 'S13';
602
618
  }
603
619
  if (route.name === 'review')
604
- return snapshot.activeReview?.operationKind === 'portableInstall' ? 'U06'
605
- : snapshot.activeReview?.operationKind === 'configSave'
606
- ? 'C12'
607
- : snapshot.activeReview?.operationKind === 'configReset'
608
- ? 'C16'
609
- : snapshot.activeReview?.operationKind === 'pluginWrite'
610
- && (snapshot.activeReview.pluginAction === 'upgrade' || snapshot.activeReview.pluginAction === 'reinstall')
611
- ? 'P18'
620
+ return snapshot.activeReview?.operationKind === 'catalogActivate' ? 'K03'
621
+ : snapshot.activeReview?.operationKind === 'portableInstall' ? 'U06'
622
+ : snapshot.activeReview?.operationKind === 'configSave'
623
+ ? 'C12'
624
+ : snapshot.activeReview?.operationKind === 'configReset'
625
+ ? 'C16'
612
626
  : snapshot.activeReview?.operationKind === 'pluginWrite'
613
- && (snapshot.activeReview.pluginAction === 'uninstall' || snapshot.activeReview.pluginAction === 'cleanup')
614
- ? 'P19' : 'P07';
627
+ && (snapshot.activeReview.pluginAction === 'upgrade' || snapshot.activeReview.pluginAction === 'reinstall')
628
+ ? 'P18'
629
+ : snapshot.activeReview?.operationKind === 'pluginWrite'
630
+ && (snapshot.activeReview.pluginAction === 'uninstall' || snapshot.activeReview.pluginAction === 'cleanup')
631
+ ? 'P19' : 'P07';
615
632
  if (route.name === 'progress')
616
- return snapshot.activeReview?.operationKind === 'portableInstall' ? 'U07'
617
- : snapshot.activeReview?.operationKind === 'configSave'
618
- || snapshot.activeReview?.operationKind === 'configReset' ? 'C13' : 'P08';
633
+ return snapshot.activeReview?.operationKind === 'catalogActivate' ? 'K03'
634
+ : snapshot.activeReview?.operationKind === 'portableInstall' ? 'U07'
635
+ : snapshot.activeReview?.operationKind === 'configSave'
636
+ || snapshot.activeReview?.operationKind === 'configReset' ? 'C13' : 'P08';
619
637
  if (route.name === 'result')
620
- return snapshot.result?.operationKind === 'portableInstall' ? 'U08'
621
- : snapshot.result?.operationKind === 'configSave'
622
- || snapshot.result?.operationKind === 'configReset'
623
- ? snapshot.result.state === 'succeeded' ? 'C14' : 'C15'
624
- : snapshot.result?.state === 'partial' ? 'P10' : 'P09';
638
+ return snapshot.result?.operationKind === 'catalogActivate' ? 'K03'
639
+ : snapshot.result?.operationKind === 'portableInstall' ? 'U08'
640
+ : snapshot.result?.operationKind === 'configSave'
641
+ || snapshot.result?.operationKind === 'configReset'
642
+ ? snapshot.result.state === 'succeeded' ? 'C14' : 'C15'
643
+ : snapshot.result?.state === 'partial' ? 'P10' : 'P09';
625
644
  if (route.name === 'pluginAbout')
626
645
  return 'P20';
627
646
  if (route.name === 'pluginTechnical')
@@ -630,6 +649,8 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
630
649
  return 'C01';
631
650
  if (route.name === 'configFields')
632
651
  return route.layer.scope === 'effective' ? 'C02' : 'C03';
652
+ if (route.name === 'configFieldAction')
653
+ return 'C04';
633
654
  if (route.name === 'configFieldEditor')
634
655
  return {
635
656
  enum: 'C05', boolean: 'C06', number: 'C07', string: 'C08', path: 'C09', sensitive: 'C10',
@@ -638,10 +659,18 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
638
659
  return 'C17';
639
660
  if (route.name === 'sourceList')
640
661
  return 'S01';
641
- if (route.name === 'sourceDetail')
642
- return 'S02';
662
+ if (route.name === 'sourceDetail' || route.name === 'catalogCandidate')
663
+ return 'K02';
643
664
  if (route.name === 'sourceTechnical')
644
665
  return 'S03';
666
+ if (route.name === 'catalogRefreshPolicy')
667
+ return 'K04';
668
+ if (route.name === 'catalogRefresh')
669
+ return 'K05';
670
+ if (route.name === 'catalogImmutableViolation')
671
+ return 'K06';
672
+ if (route.name === 'catalogActivationBlocked')
673
+ return 'K07';
645
674
  if (route.name === 'sourceAdd')
646
675
  return {
647
676
  type: 'S04', id: 'S05', location: 'S06', refPolicy: 'S07', ref: 'S08', subdir: 'S09',
@@ -658,6 +687,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
658
687
  const category = controller.listUserIssues().find((issue) => issue.issueId === route.issueId)?.category;
659
688
  return {
660
689
  incompleteInstallation: 'I03',
690
+ artifactCollision: 'I09',
661
691
  sourceDegraded: 'I04',
662
692
  sourceUnavailable: 'I04',
663
693
  sourceMissing: 'I05',
@@ -685,7 +715,7 @@ export function ConsoleAppV4(props) {
685
715
  const [discardForExit, setDiscardForExit] = useState(false);
686
716
  const [focus, setFocus] = useState(0);
687
717
  const [scrollOffset, setScrollOffset] = useState(0);
688
- const [searchMode, setSearchMode] = useState(false);
718
+ const [searchFocus, setSearchFocus] = useState(false);
689
719
  const [query, setQuery] = useState('');
690
720
  const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
691
721
  const [scope, setScope] = useState('user');
@@ -714,6 +744,7 @@ export function ConsoleAppV4(props) {
714
744
  const [portableAgentInput, setPortableAgentInput] = useState('');
715
745
  const [draftOwner, setDraftOwner] = useState();
716
746
  const [pendingPresetId, setPendingPresetId] = useState();
747
+ const [pendingLayerFieldKey, setPendingLayerFieldKey] = useState();
717
748
  const dynamicMenuFocus = useRef(undefined);
718
749
  const exitSent = useRef(false);
719
750
  const operationBusy = useRef(false);
@@ -804,6 +835,7 @@ export function ConsoleAppV4(props) {
804
835
  }, [route.name]);
805
836
  const configRoute = route.name === 'configFields'
806
837
  || route.name === 'configFieldEditor'
838
+ || route.name === 'configFieldAction'
807
839
  ? route
808
840
  : undefined;
809
841
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
@@ -910,7 +942,7 @@ export function ConsoleAppV4(props) {
910
942
  route.name === 'pluginList' ? route.sourceId : '',
911
943
  snapshot.viewModel,
912
944
  ]);
913
- const activeField = route.name === 'configFieldEditor'
945
+ const activeField = route.name === 'configFieldEditor' || route.name === 'configFieldAction'
914
946
  ? currentConfigPage?.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
915
947
  : undefined;
916
948
  useEffect(() => {
@@ -945,7 +977,7 @@ export function ConsoleAppV4(props) {
945
977
  setSourceReferenceSummary({ blocked: true, inventoryComplete: false, dependencies: [] }); });
946
978
  return () => abort.abort();
947
979
  }, [props.controller, route.name, route.name === 'sourceRemoveBlocked' ? route.sourceId : '']);
948
- const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchMode);
980
+ const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchFocus);
949
981
  const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
950
982
  ? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
951
983
  : [];
@@ -962,7 +994,7 @@ export function ConsoleAppV4(props) {
962
994
  || Boolean(route.name === 'result' && snapshot.result && 'operationKind' in snapshot.result
963
995
  && snapshot.result.operationKind === 'portableInstall');
964
996
  const canOpenHelp = !tooSmall
965
- && !isCharacterInputMode(route, searchMode)
997
+ && !isCharacterInputMode(route, searchFocus)
966
998
  && !(route.name === 'installTargets' && route.view !== 'blocked');
967
999
  useEffect(() => {
968
1000
  props.onScreenChange?.(screenId);
@@ -1022,7 +1054,7 @@ export function ConsoleAppV4(props) {
1022
1054
  props.controller.push(next);
1023
1055
  setFocus(0);
1024
1056
  setScrollOffset(0);
1025
- setSearchMode(false);
1057
+ setSearchFocus(false);
1026
1058
  setShowTechnical(false);
1027
1059
  setActionError(undefined);
1028
1060
  };
@@ -1037,11 +1069,13 @@ export function ConsoleAppV4(props) {
1037
1069
  props.controller.beginConfigFlow(pluginId);
1038
1070
  setFocus(0);
1039
1071
  setScrollOffset(0);
1040
- setSearchMode(false);
1072
+ setSearchFocus(false);
1041
1073
  setShowTechnical(false);
1042
1074
  setActionError(undefined);
1043
1075
  };
1044
1076
  const back = () => {
1077
+ if (route.name === 'catalogRefresh')
1078
+ props.controller.cancelCatalogRefresh(route.sourceId);
1045
1079
  if (route.name === 'result')
1046
1080
  props.controller.leaveResult();
1047
1081
  else
@@ -1051,7 +1085,7 @@ export function ConsoleAppV4(props) {
1051
1085
  setScrollOffset(restored.scrollOffset ?? 0);
1052
1086
  setQuery(restored.query ?? '');
1053
1087
  setInstallFilter(restored.installFilter ?? 'all');
1054
- setSearchMode(Boolean(restored.query));
1088
+ setSearchFocus(false);
1055
1089
  setActionError(undefined);
1056
1090
  };
1057
1091
  const leaveResult = (target) => {
@@ -1061,7 +1095,7 @@ export function ConsoleAppV4(props) {
1061
1095
  setScrollOffset(restored.scrollOffset ?? 0);
1062
1096
  setQuery(restored.query ?? '');
1063
1097
  setInstallFilter(restored.installFilter ?? 'all');
1064
- setSearchMode(Boolean(restored.query));
1098
+ setSearchFocus(false);
1065
1099
  setShowTechnical(false);
1066
1100
  setActionError(undefined);
1067
1101
  };
@@ -1314,6 +1348,12 @@ export function ConsoleAppV4(props) {
1314
1348
  return next;
1315
1349
  });
1316
1350
  };
1351
+ const cancelDraftRemoval = (key) => {
1352
+ const nextRemoves = draftRemoves.filter((candidate) => candidate !== key);
1353
+ setDraftRemoves(nextRemoves);
1354
+ setDirty(Object.keys(draftUpdates).length + nextRemoves.length > 0);
1355
+ setConfigActionError(undefined);
1356
+ };
1317
1357
  const configFieldTarget = (page, key) => {
1318
1358
  if (!key)
1319
1359
  return undefined;
@@ -1384,15 +1424,8 @@ export function ConsoleAppV4(props) {
1384
1424
  setScrollOffset(0);
1385
1425
  return;
1386
1426
  }
1387
- if (field && route.fieldKey) {
1388
- props.controller.push({
1389
- name: 'configFieldEditor',
1390
- pluginId: route.pluginId,
1391
- layer,
1392
- fieldKey: route.fieldKey,
1393
- editor: fieldEditor(field),
1394
- });
1395
- }
1427
+ if (field && route.fieldKey)
1428
+ setPendingLayerFieldKey(route.fieldKey);
1396
1429
  setFocus(0);
1397
1430
  setScrollOffset(0);
1398
1431
  };
@@ -1405,7 +1438,9 @@ export function ConsoleAppV4(props) {
1405
1438
  key,
1406
1439
  title: field?.title ?? key,
1407
1440
  before: field?.displayValue ?? '未设置',
1408
- after: draftRemoves.includes(key) ? '未设置' : currentFieldDisplay(field),
1441
+ after: draftRemoves.includes(key)
1442
+ ? `恢复继承(${field?.fallbackDisplayValue ?? '未设置'} · ${field?.fallbackSourceLabel ?? '未设置'})`
1443
+ : currentFieldDisplay(field),
1409
1444
  };
1410
1445
  });
1411
1446
  };
@@ -1431,6 +1466,38 @@ export function ConsoleAppV4(props) {
1431
1466
  setConfigActionError('设置值未通过验证。请重新检查修改。');
1432
1467
  }
1433
1468
  };
1469
+ useEffect(() => {
1470
+ if (!pendingLayerFieldKey || route.name !== 'configFields'
1471
+ || route.layer.scope === 'effective' || !currentConfigPage)
1472
+ return;
1473
+ const field = currentConfigPage.groups.flatMap((group) => group.fields)
1474
+ .find((candidate) => candidate.key === pendingLayerFieldKey);
1475
+ setPendingLayerFieldKey(undefined);
1476
+ if (!field)
1477
+ return;
1478
+ const layer = { scope: route.layer.scope, projectLayer: route.layer.projectLayer };
1479
+ props.controller.push(field.configured ? {
1480
+ name: 'configFieldAction',
1481
+ pluginId: route.pluginId,
1482
+ layer,
1483
+ fieldKey: field.key,
1484
+ } : {
1485
+ name: 'configFieldEditor',
1486
+ pluginId: route.pluginId,
1487
+ layer,
1488
+ fieldKey: field.key,
1489
+ editor: fieldEditor(field),
1490
+ });
1491
+ }, [pendingLayerFieldKey, route.name, currentConfigPage]);
1492
+ useEffect(() => {
1493
+ if (!pendingLayerFieldKey)
1494
+ return;
1495
+ if (route.name === 'configFields' && route.layer.scope !== 'effective')
1496
+ return;
1497
+ if (route.name === 'configLayer')
1498
+ return;
1499
+ setPendingLayerFieldKey(undefined);
1500
+ }, [pendingLayerFieldKey, route.name]);
1434
1501
  useEffect(() => {
1435
1502
  if (!pendingPresetId || route.name !== 'configFields'
1436
1503
  || route.layer.scope === 'effective' || !currentConfigPage)
@@ -1462,14 +1529,27 @@ export function ConsoleAppV4(props) {
1462
1529
  }
1463
1530
  else if (route.name === 'home') {
1464
1531
  const home = snapshot.viewModel.home;
1465
- context = '当前项目';
1532
+ const candidates = props.controller.listCatalogCandidates();
1533
+ const firstCandidate = candidates[0];
1534
+ context = candidates.length > 0 ? 'Catalog 有更新' : '当前项目';
1466
1535
  actions = [
1467
1536
  { label: '添加插件', description: '浏览多个来源中的 Agent 能力', meta: String(home.availablePlugins), run: () => go({ name: 'pluginList', mode: 'browse' }) },
1537
+ ...(firstCandidate ? [{
1538
+ label: '查看 Catalog 更新…',
1539
+ meta: candidates.length > 1 ? `${candidates.length} 个来源`
1540
+ : firstCandidate.candidate.catalogVersion,
1541
+ status: 'warning',
1542
+ run: () => go({ name: 'catalogCandidate', sourceId: firstCandidate.sourceId }),
1543
+ }] : []),
1468
1544
  { label: '我的插件', description: '查看状态、设置和移除已添加插件', meta: String(home.installedPlugins), run: () => go({ name: 'pluginList', mode: 'installed' }) },
1469
1545
  { label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
1470
1546
  { label: '插件来源', description: '管理 Git 仓库和本地目录', meta: String(home.sourceCount), run: () => go({ name: 'sourceList' }) },
1471
1547
  ];
1472
- content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.cachedSourceCount > 0 ? _jsx(ResultRow, { title: `${home.cachedSourceCount} 个来源使用缓存`, status: "warning" }) : null, home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
1548
+ content = _jsxs(Box, { flexDirection: "column", children: [firstCandidate
1549
+ ? policy.mode === 'compact'
1550
+ ? _jsx(Text, { color: tuiTheme.colors.warning, wrap: "truncate-end", children: `${firstCandidate.sourceId} ${firstCandidate.active?.catalogVersion ?? '未加载'} → ${firstCandidate.candidate.catalogVersion} · 当前操作仍使用 ${props.controller.getSessionCatalogVersion(firstCandidate.sourceId) ?? firstCandidate.active?.catalogVersion ?? '已加载版本'}${candidates.length > 1 ? ` · 另有 ${candidates.length - 1} 个来源` : ''}` })
1551
+ : _jsx(SectionCard, { title: `${firstCandidate.sourceId} ${firstCandidate.active?.catalogVersion ?? '未加载'} → ${firstCandidate.candidate.catalogVersion} · 当前操作仍使用 ${props.controller.getSessionCatalogVersion(firstCandidate.sourceId) ?? firstCandidate.active?.catalogVersion ?? '已加载版本'}`, meta: candidates.length > 1 ? `另有 ${candidates.length - 1} 个来源` : undefined, status: "warning" })
1552
+ : null, _jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.cachedSourceCount > 0 ? _jsx(ResultRow, { title: `${home.cachedSourceCount} 个来源使用缓存`, status: "warning" }) : null, home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
1473
1553
  }
1474
1554
  else if (route.name === 'pluginList') {
1475
1555
  const sourceId = route.sourceId ?? 'all';
@@ -1479,6 +1559,12 @@ export function ConsoleAppV4(props) {
1479
1559
  ...(query ? { query } : {}),
1480
1560
  installFilter,
1481
1561
  });
1562
+ const uninstalledCount = props.controller.listPlugins({
1563
+ mode: route.mode,
1564
+ ...(sourceId !== 'all' ? { sourceId } : {}),
1565
+ ...(query ? { query } : {}),
1566
+ installFilter: 'uninstalled',
1567
+ }).length;
1482
1568
  breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
1483
1569
  title = breadcrumb;
1484
1570
  description = route.mode === 'browse' ? '选择希望 Agent 获得的能力。' : '查看状态,或选择一个插件进行管理。';
@@ -1492,7 +1578,9 @@ export function ConsoleAppV4(props) {
1492
1578
  pluginId: item.pluginId,
1493
1579
  name: item.displayName,
1494
1580
  sourceLabel: item.sourceLabel,
1495
- description: item.description,
1581
+ description: item.primaryAttention
1582
+ ? `${item.primaryAttention.code} · ${item.primaryAttention.message}`
1583
+ : item.description,
1496
1584
  placementSummary: pluginPlacementSummary(item),
1497
1585
  statusLabel: statusText(item),
1498
1586
  status: pluginRowStatus(item),
@@ -1505,7 +1593,9 @@ export function ConsoleAppV4(props) {
1505
1593
  else if (route.mode === 'browse' && items.length === 0 && !sourceError) {
1506
1594
  actions = [{
1507
1595
  label: '没有匹配插件',
1508
- description: installFilter === 'uninstalled' ? '当前来源和搜索条件下没有未安装插件' : '请调整来源或搜索条件',
1596
+ description: installFilter === 'uninstalled'
1597
+ ? '当前来源和搜索条件下没有未安装插件;按 f 查看全部,或按 / 编辑搜索'
1598
+ : '按 / 编辑搜索,或切换来源',
1509
1599
  disabled: '没有可打开的插件',
1510
1600
  }];
1511
1601
  }
@@ -1521,8 +1611,10 @@ export function ConsoleAppV4(props) {
1521
1611
  if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
1522
1612
  context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
1523
1613
  }
1524
- content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchMode ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["/ ", searchMode ? query || '输入搜索内容' : '搜索名称、用途或来源…'] }), route.mode === 'browse'
1525
- ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["[", installFilter === 'uninstalled' ? '未安装' : '全部', "]"] }), " f"] })
1614
+ content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchFocus ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["\u641C\u7D22 / ", searchFocus
1615
+ ? _jsx(InputDisplay, { value: query, placeholder: "\u8F93\u5165\u641C\u7D22\u5185\u5BB9", columns: Math.max(8, size.columns - 14) })
1616
+ : _jsxs(_Fragment, { children: [query || '搜索名称、用途或来源…', query ? ' / 编辑' : ''] })] }), route.mode === 'browse'
1617
+ ? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u72B6\u6001 ", _jsx(Text, { color: installFilter === 'all' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: installFilter === 'all' ? '[全部]' : '全部' }), " ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [installFilter === 'uninstalled' ? '[未安装]' : '未安装', " ", uninstalledCount] }), " f \u5207\u6362"] })
1526
1618
  : null, sourceError
1527
1619
  ? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u6CA1\u6709\u53EF\u7528\u7F13\u5B58\uFF0C\u56E0\u6B64\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
1528
1620
  : route.mode === 'installed' && items.length === 0
@@ -1718,7 +1810,7 @@ export function ConsoleAppV4(props) {
1718
1810
  title = '输入 Agent ID';
1719
1811
  description = '多个 Agent 可使用逗号或空格分隔。';
1720
1812
  help = '输入 Agent ID · Enter 保存 · Esc 返回';
1721
- content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: portableAgentInput || ' ', description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
1813
+ 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 })] });
1722
1814
  }
1723
1815
  else if (route.name === 'portableScope' && plugin && snapshot.portableDraft) {
1724
1816
  breadcrumb = `${plugin.displayName} / 通用 Skills / 范围`;
@@ -2190,7 +2282,12 @@ export function ConsoleAppV4(props) {
2190
2282
  fieldKey: field.key,
2191
2283
  groupId: activeGroup.id,
2192
2284
  })
2193
- : () => go({
2285
+ : () => go(field.configured ? {
2286
+ name: 'configFieldAction',
2287
+ pluginId: plugin.pluginId,
2288
+ layer: mutableLayer,
2289
+ fieldKey: field.key,
2290
+ } : {
2194
2291
  name: 'configFieldEditor',
2195
2292
  pluginId: plugin.pluginId,
2196
2293
  layer: mutableLayer,
@@ -2200,13 +2297,27 @@ export function ConsoleAppV4(props) {
2200
2297
  }));
2201
2298
  const operationActions = readonly ? [] : [
2202
2299
  { label: '保存修改', description: dirty ? '查看安全变更汇总' : '当前没有未保存修改', disabled: dirty ? undefined : '当前没有未保存修改', run: beginConfigSave },
2203
- {
2204
- label: '重置此设置层…',
2205
- description: '删除本层全部值并恢复继承',
2206
- status: 'error',
2207
- disabled: currentConfigPage.storedCount === 0 ? '本层没有已存值' : undefined,
2208
- run: () => beginReview({ kind: 'configReset', pluginId: plugin.pluginId, scope: route.layer.scope, projectLayer: route.layer.projectLayer }),
2209
- },
2300
+ ...(currentConfigPage.storedCount > 0 ? [{
2301
+ label: route.layer.scope === 'user'
2302
+ ? '清空个人默认…'
2303
+ : route.layer.projectLayer === 'local' ? '删除本机设置…' : '清空当前项目共享设置…',
2304
+ description: `删除${configLayerLabel(route.layer)}中的全部值并恢复继承;其他设置层保持不变`,
2305
+ status: 'error',
2306
+ run: () => beginReview({
2307
+ kind: 'configReset',
2308
+ pluginId: plugin.pluginId,
2309
+ scope: route.layer.scope,
2310
+ projectLayer: route.layer.projectLayer,
2311
+ changes: currentConfigPage.groups.flatMap((group) => group.fields)
2312
+ .filter((field) => field.configured)
2313
+ .map((field) => ({
2314
+ key: field.key,
2315
+ title: field.title,
2316
+ before: field.displayValue,
2317
+ after: `恢复继承(${field.fallbackDisplayValue ?? '未设置'} · ${field.fallbackSourceLabel ?? '未设置'})`,
2318
+ })),
2319
+ }),
2320
+ }] : []),
2210
2321
  ];
2211
2322
  actions = [...tabActions, ...operationActions];
2212
2323
  const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
@@ -2226,6 +2337,32 @@ export function ConsoleAppV4(props) {
2226
2337
  : null] });
2227
2338
  }
2228
2339
  }
2340
+ else if (route.name === 'configFieldAction' && plugin && activeField) {
2341
+ const layer = configLayerLabel(route.layer);
2342
+ breadcrumb = `${plugin.displayName} / 设置 / ${layer} / ${activeField.title}`;
2343
+ title = activeField.title;
2344
+ description = `这个字段在${layer}中已经有值。`;
2345
+ help = '↑↓ 移动 · Enter 选择 · Esc 返回';
2346
+ const deleteLabel = route.layer.scope === 'user'
2347
+ ? '删除个人默认中的值'
2348
+ : route.layer.projectLayer === 'local' ? '删除本机设置中的值' : '删除当前项目中的值';
2349
+ actions = [
2350
+ {
2351
+ label: '修改值…',
2352
+ run: () => go({
2353
+ name: 'configFieldEditor',
2354
+ pluginId: plugin.pluginId,
2355
+ layer: route.layer,
2356
+ fieldKey: activeField.key,
2357
+ editor: fieldEditor(activeField),
2358
+ }),
2359
+ },
2360
+ draftRemoves.includes(activeField.key)
2361
+ ? { label: '撤销删除', run: () => { cancelDraftRemoval(activeField.key); back(); } }
2362
+ : { label: deleteLabel, status: 'error', run: () => { removeDraft(activeField.key); back(); } },
2363
+ ];
2364
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: `${layer}中的值`, meta: currentFieldDisplay(activeField) }), _jsx(StaticRow, { title: "\u5F53\u524D\u5B9E\u9645\u751F\u6548", meta: `${effectiveFieldDisplay(activeField)} · 来自${activeField.effectiveSourceLabel}` }), _jsx(StaticRow, { title: "\u5220\u9664\u540E\u5C06\u6062\u590D", meta: `${activeField.fallbackDisplayValue ?? '未设置'} · 来自${activeField.fallbackSourceLabel ?? '未设置'}` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2365
+ }
2229
2366
  else if (route.name === 'configFieldEditor' && plugin && activeField) {
2230
2367
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title}`;
2231
2368
  help = route.editor === 'enum' || route.editor === 'boolean'
@@ -2253,10 +2390,8 @@ export function ConsoleAppV4(props) {
2253
2390
  ? `允许范围:${activeField.min ?? '不限'}–${activeField.max ?? '不限'}${activeField.unit ? ` ${activeField.unit}` : ''}。`
2254
2391
  : route.editor === 'path' ? `${activeField.type === 'file' ? '文件' : '目录'}路径会在保存前由现有 schema 路径验证。`
2255
2392
  : route.editor === 'sensitive' ? '当前值不会回显;留空不会自动删除旧值。' : activeField.description ?? '';
2256
- const visibleInput = route.editor === 'sensitive'
2257
- ? '•'.repeat(inputValue.length)
2258
- : route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
2259
- content = _jsx(InputCard, { focused: true, title: visibleInput || activeField.placeholder || ' ', description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
2393
+ const visibleInput = route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
2394
+ content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: visibleInput, sensitive: route.editor === 'sensitive', placeholder: activeField.placeholder, columns: Math.max(8, size.columns - 8) }), description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
2260
2395
  }
2261
2396
  }
2262
2397
  else if (route.name === 'configUnavailable' && plugin) {
@@ -2285,18 +2420,25 @@ export function ConsoleAppV4(props) {
2285
2420
  actions = [
2286
2421
  ...props.controller.listSources().map((source) => {
2287
2422
  const overview = props.controller.getSourceOverview(source.id);
2423
+ const generation = props.controller.getCatalogGeneration(source.id);
2288
2424
  const health = overview.health?.state ?? source.state;
2289
- const status = health === 'ready' ? '已从远端加载'
2290
- : health === 'path' ? '本地目录'
2291
- : health === 'checking' ? '正在检查'
2292
- : health === 'cache' || health === 'degraded' ? '使用缓存' : '无法读取';
2425
+ const status = generation?.refreshState === 'checking'
2426
+ || generation?.refreshState === 'downloading'
2427
+ || generation?.refreshState === 'validating' ? '正在检查'
2428
+ : generation?.refreshState === 'immutableViolation' ? '发布异常'
2429
+ : generation?.candidate ? `可用更新 ${generation.candidate.catalogVersion}`
2430
+ : health === 'ready' ? '已从远端加载'
2431
+ : health === 'path' ? '本地目录'
2432
+ : health === 'checking' ? '正在检查'
2433
+ : health === 'cache' || health === 'degraded' ? '使用缓存' : '无法读取';
2293
2434
  return {
2294
2435
  label: `${source.id}${source.primary ? ' 主来源' : ''}`,
2295
2436
  description: `${overview.detail?.type === 'path' ? '本地目录' : 'Git 仓库'} · ${source.count ?? 0} 个插件`,
2296
2437
  meta: status,
2297
- status: health === 'unavailable' ? 'error'
2298
- : health === 'cache' || health === 'degraded' ? 'warning'
2299
- : source.primary ? 'success' : undefined,
2438
+ status: generation?.refreshState === 'immutableViolation' || health === 'unavailable' ? 'error'
2439
+ : generation?.candidate ? 'warning'
2440
+ : health === 'cache' || health === 'degraded' ? 'warning'
2441
+ : source.primary ? 'success' : undefined,
2300
2442
  run: () => go({ name: 'sourceDetail', sourceId: source.id }),
2301
2443
  };
2302
2444
  }),
@@ -2311,14 +2453,37 @@ export function ConsoleAppV4(props) {
2311
2453
  const detail = overview.detail;
2312
2454
  const state = overview.health?.state ?? overview.source?.state;
2313
2455
  const diagnostic = sourceDiagnostic(route.sourceId, state, detail);
2456
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2457
+ const sessionCatalogVersion = props.controller.getSessionCatalogVersion(route.sourceId)
2458
+ ?? generation?.active?.catalogVersion;
2314
2459
  breadcrumb = `插件来源 / ${route.sourceId}`;
2315
- context = detail?.type === 'path' ? '本地目录' : 'Git';
2460
+ context = detail?.type === 'path' ? '本地目录'
2461
+ : detail?.freshness === 'remote' ? 'Git · 已连接'
2462
+ : detail?.freshness === 'cache' ? 'Git · 使用缓存' : 'Git';
2316
2463
  title = route.sourceId;
2317
- description = diagnostic ? '' : '来源状态可用。';
2464
+ description = diagnostic || generation ? '' : '来源状态可用。';
2318
2465
  help = '↑↓ 移动 · Enter 选择 · r 重新检查 · Esc 返回';
2466
+ const openRefresh = () => {
2467
+ go({ name: 'catalogRefresh', sourceId: route.sourceId });
2468
+ void props.controller.refreshSource(route.sourceId);
2469
+ };
2470
+ const generationActions = [
2471
+ ...(generation?.candidate ? [{
2472
+ label: `使用 ${generation.candidate.catalogVersion}…`,
2473
+ description: '先查看影响;不会自动升级已安装插件',
2474
+ run: () => go({ name: 'catalogCandidate', sourceId: route.sourceId }),
2475
+ }] : []),
2476
+ ...(generation?.refreshState === 'immutableViolation' ? [{
2477
+ label: '查看发布异常',
2478
+ description: '旧 Active 已安全保留',
2479
+ status: 'error',
2480
+ run: () => go({ name: 'catalogImmutableViolation', sourceId: route.sourceId }),
2481
+ }] : []),
2482
+ ];
2319
2483
  actions = diagnostic
2320
2484
  ? [
2321
- { label: '重新检查来源', description: '读取远端并验证可用数据', run: () => props.controller.refreshSource(route.sourceId) },
2485
+ ...generationActions,
2486
+ { label: '重新检查来源', description: '读取远端并验证可用 generation', run: openRefresh },
2322
2487
  ...(state === 'degraded' ? [{
2323
2488
  label: '继续使用缓存',
2324
2489
  description: '浏览匹配且已验证的缓存数据',
@@ -2330,16 +2495,105 @@ export function ConsoleAppV4(props) {
2330
2495
  { label: '技术详情', description: '查看脱敏错误代码和解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2331
2496
  ]
2332
2497
  : [
2333
- { label: '重新检查来源', description: '读取远端并更新缓存', run: () => props.controller.refreshSource(route.sourceId) },
2498
+ ...generationActions,
2499
+ { label: '重新检查来源', description: detail?.type === 'path' ? '显式读取本地目录' : '读取远端并准备 Candidate', run: openRefresh },
2500
+ ...(generation?.sourceType === 'git' ? [{
2501
+ label: '自动刷新',
2502
+ description: generation.refreshPolicy === 'startup' ? '启动时自动检查' : '仅手动检查',
2503
+ meta: generation.refreshPolicy === 'startup' ? '开启' : '关闭',
2504
+ run: () => go({ name: 'catalogRefreshPolicy', sourceId: route.sourceId }),
2505
+ }] : []),
2334
2506
  { label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
2335
2507
  { label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
2336
2508
  { label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
2337
2509
  { label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
2338
2510
  ];
2339
- const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 6));
2511
+ const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 5));
2340
2512
  content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
2341
2513
  ? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
2342
- : _jsxs(_Fragment, { children: [_jsx(StatusCard, { status: state === 'checking' ? 'warning' : 'success', title: state === 'checking' ? '正在检查' : '可用' }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u63D2\u4EF6", meta: `${detail?.pluginCount ?? 0} 个` }), policy.mode === 'compact' ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u9009\u62E9", meta: detail?.type === 'path' ? '本地目录' : detail?.refPolicy === 'explicit' ? '固定 Git 引用' : '跟随 AW 版本通道' }), detail?.explicitRef ? _jsx(StaticRow, { title: "\u5F15\u7528", meta: detail.explicitRef }) : null] })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
2514
+ : _jsxs(_Fragment, { children: [sessionCatalogVersion ? _jsx(SectionCard, { title: "\u5F53\u524D\u4F7F\u7528", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: `Catalog ${sessionCatalogVersion}`, meta: `${generation?.active?.channel === 'beta' ? 'Beta' : 'Release'} · 本次会话固定使用`, compact: true }) }) : null, generation?.candidate ? _jsx(SectionCard, { title: "\u53EF\u7528\u66F4\u65B0", status: "warning", compact: policy.mode === 'compact', children: _jsx(StaticRow, { title: `Catalog ${generation.candidate.catalogVersion}`, meta: `已下载并验证 · +${generation.candidate.pluginChanges.added} ~${generation.candidate.pluginChanges.updated} −${generation.candidate.pluginChanges.removed}`, compact: true }) }) : null, policy.mode === 'compact' ? _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录', compact: true }), _jsx(StaticRow, { title: "\u68C0\u67E5", meta: `${generation?.sourceType === 'git' && generation.refreshPolicy === 'startup' ? '启动时' : '仅手动'} · ${generation?.lastCheckedAt ? '已有记录' : '尚未检查'}`, compact: true })] }) : _jsxs(SectionCard, { title: "\u6765\u6E90\u4E0E\u68C0\u67E5", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u5F15\u7528", meta: generation?.active?.requestedRef ?? detail?.explicitRef ?? detail?.requestedRef ?? '本地目录' }), _jsx(StaticRow, { title: "\u81EA\u52A8\u5237\u65B0", meta: generation?.sourceType === 'git' ? generation.refreshPolicy === 'startup' ? '启动时检查' : '仅手动' : '仅手动' }), _jsx(StaticRow, { title: "\u4E0A\u6B21\u68C0\u67E5", meta: generation?.lastCheckedAt ?? '尚未检查' })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
2515
+ }
2516
+ else if (route.name === 'catalogCandidate') {
2517
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2518
+ const candidate = generation?.candidate;
2519
+ breadcrumb = `插件来源 / ${route.sourceId} / Catalog 更新`;
2520
+ context = '已下载并验证';
2521
+ title = candidate ? `使用 ${route.sourceId} Catalog ${candidate.catalogVersion}` : 'Catalog 更新不再可用';
2522
+ description = '切换 generation;不会自动升级已安装插件,也不会修改设置。';
2523
+ actions = candidate ? [
2524
+ { label: `使用 ${candidate.catalogVersion}…`, description: '进入激活 Review', run: () => {
2525
+ props.controller.beginCatalogActivation({
2526
+ sourceId: route.sourceId,
2527
+ ...(dirty ? { dirtyDraft: { kind: 'config', label: '当前设置草稿' } }
2528
+ : sourceDraft ? { dirtyDraft: { kind: 'source', label: '当前来源草稿' } } : {}),
2529
+ });
2530
+ setFocus(0);
2531
+ setScrollOffset(0);
2532
+ } },
2533
+ { label: `继续使用 ${generation?.active?.catalogVersion ?? '当前版本'}`, run: back },
2534
+ ] : [{ label: '返回来源', run: back }];
2535
+ content = candidate ? _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "Catalog \u53D8\u5316", children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: `${generation?.active?.catalogVersion ?? '未加载'} → ${candidate.catalogVersion}` }), _jsx(StaticRow, { title: "\u901A\u9053 / \u5F15\u7528", meta: `${candidate.channel === 'beta' ? 'Beta' : 'Release'} · ${candidate.requestedRef}` }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u53D8\u5316", meta: `+${candidate.pluginChanges.added} ~${candidate.pluginChanges.updated} −${candidate.pluginChanges.removed}` })] }), _jsxs(SectionCard, { title: "\u6FC0\u6D3B\u540E", children: [_jsx(StaticRow, { title: "\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u5217\u8868\u548C\u4F9D\u8D56\u5173\u7CFB", status: "warning" }), _jsx(StaticRow, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7", status: "success" }), _jsx(StaticRow, { title: "\u5F53\u524D\u8BBE\u7F6E\u4E0D\u4F1A\u6539\u53D8", status: "success" })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] }) : _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true });
2536
+ }
2537
+ else if (route.name === 'catalogRefreshPolicy') {
2538
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2539
+ breadcrumb = `插件来源 / ${route.sourceId} / 自动刷新`;
2540
+ title = 'Catalog 自动刷新';
2541
+ description = `决定何时检查 ${route.sourceId} 的远端更新。更新不会自动激活。`;
2542
+ actions = [
2543
+ { label: '启动时自动检查', description: '首屏出现后在后台检查;不会自动切换版本', selected: generation?.refreshPolicy === 'startup', run: async () => props.controller.setCatalogRefreshPolicy(route.sourceId, 'startup') },
2544
+ { label: '仅手动检查', description: '只有选择“重新检查来源”时访问远端', selected: generation?.refreshPolicy === 'manual', run: async () => props.controller.setCatalogRefreshPolicy(route.sourceId, 'manual') },
2545
+ ];
2546
+ content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2547
+ }
2548
+ else if (route.name === 'catalogRefresh') {
2549
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2550
+ const state = generation?.refreshState ?? 'idle';
2551
+ const activeVersion = props.controller.getSessionCatalogVersion(route.sourceId)
2552
+ ?? generation?.active?.catalogVersion ?? '已加载版本';
2553
+ const stage = state === 'checking' || state === 'downloading' ? 1
2554
+ : state === 'validating' ? 2 : 3;
2555
+ const stageState = (index) => state === 'failed' || state === 'immutableViolation'
2556
+ ? index === stage ? 'error' : index < stage ? 'complete' : 'pending'
2557
+ : index < stage ? 'complete' : index === stage && stage < 3 ? 'active' : index <= stage ? 'complete' : 'pending';
2558
+ breadcrumb = `插件来源 / ${route.sourceId} / 检查更新`;
2559
+ context = '只读';
2560
+ title = state === 'candidateReady' ? 'Catalog 更新已准备好'
2561
+ : state === 'upToDate' ? 'Catalog 已是最新'
2562
+ : state === 'immutableViolation' ? 'Catalog 发布异常'
2563
+ : state === 'failed' ? 'Catalog 检查失败' : '正在检查 Catalog 更新';
2564
+ description = `当前操作继续使用 Catalog ${activeVersion}。`;
2565
+ help = state === 'checking' || state === 'downloading' || state === 'validating'
2566
+ ? 'Esc 取消检查' : 'Enter 返回 · Esc 返回';
2567
+ actions = state === 'immutableViolation'
2568
+ ? [{ label: '查看发布异常', run: () => props.controller.replace({ name: 'catalogImmutableViolation', sourceId: route.sourceId }) }, { label: '返回来源', run: back }]
2569
+ : state === 'checking' || state === 'downloading' || state === 'validating'
2570
+ ? [] : [{ label: '返回来源', run: back }];
2571
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u68C0\u67E5\u6B65\u9AA4", children: [_jsx(ProgressRow, { title: "\u8BFB\u53D6\u5F53\u524D Active generation", state: "complete", compact: true }), _jsx(ProgressRow, { title: "\u68C0\u67E5\u5E76\u83B7\u53D6\u8FDC\u7AEF generation", state: stageState(1), compact: true }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1 Catalog", state: stageState(2), compact: true }), _jsx(ProgressRow, { title: "\u51C6\u5907 Candidate", state: stageState(3), compact: true })] }), actions.length > 0 ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }) : null] });
2572
+ }
2573
+ else if (route.name === 'catalogImmutableViolation') {
2574
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2575
+ breadcrumb = `插件来源 / ${route.sourceId} / 发布异常`;
2576
+ context = '发布异常';
2577
+ title = 'Catalog 版本内容发生变化';
2578
+ description = `远端重新发布了已经存在的 Catalog ${generation?.active?.catalogVersion ?? '版本'}。`;
2579
+ actions = [
2580
+ { label: '重新检查', description: '不可变性规则仍会生效', run: () => { props.controller.replace({ name: 'catalogRefresh', sourceId: route.sourceId }); void props.controller.refreshSource(route.sourceId); } },
2581
+ { label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
2582
+ { label: '返回来源', run: back },
2583
+ ];
2584
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u5F53\u524D\u5904\u7406", children: [_jsx(StaticRow, { title: `继续使用本地已验证的 ${generation?.active?.catalogVersion ?? 'Active'}`, status: "success" }), _jsx(StaticRow, { title: "\u4E0D\u52A0\u8F7D\u8FDC\u7AEF\u53D8\u5316", status: "error" }), _jsx(StaticRow, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u548C\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8", status: "success" })] }), showTechnical ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "code", meta: generation?.lastCheckCode ?? 'catalogImmutableVersionViolation' }), _jsx(StaticRow, { title: "source", meta: route.sourceId }), _jsx(StaticRow, { title: "digest", meta: generation?.active?.digestShort ?? '不可用' })] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
2585
+ }
2586
+ else if (route.name === 'catalogActivationBlocked') {
2587
+ const generation = props.controller.getCatalogGeneration(route.sourceId);
2588
+ const reason = {
2589
+ configDraft: '当前有未完成的设置修改。', sourceDraft: '当前有未完成的来源修改。', portableDraft: '当前有未完成的 Portable Skills 选择。', activeReview: '当前有待确认的 Review。', reviewPreparation: '当前正在准备 Review。', writeProgress: '当前写操作尚未完成。', sourceMutationProgress: '当前来源修改尚未完成。', refreshProgress: '当前 Catalog 来源刷新尚未完成。',
2590
+ }[route.reason];
2591
+ breadcrumb = `插件来源 / ${route.sourceId} / 使用更新`;
2592
+ context = '暂不可用';
2593
+ title = '暂时不能切换 Catalog';
2594
+ description = reason;
2595
+ actions = [{ label: '返回完成或取消当前操作', run: back }, { label: `继续使用 ${props.controller.getSessionCatalogVersion(route.sourceId) ?? generation?.active?.catalogVersion ?? '当前版本'}`, run: back }];
2596
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u4FDD\u7559\u7684\u5DE5\u4F5C", children: [_jsx(StaticRow, { title: route.preservedLabel ?? reason, status: "success" }), _jsx(StaticRow, { title: `Candidate ${generation?.candidate?.catalogVersion ?? '更新'} 保持已下载状态`, status: "success" })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2343
2597
  }
2344
2598
  else if (route.name === 'sourceTechnical') {
2345
2599
  const overview = props.controller.getSourceOverview(route.sourceId);
@@ -2383,16 +2637,23 @@ export function ConsoleAppV4(props) {
2383
2637
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2384
2638
  }
2385
2639
  else if (route.step === 'refPolicy') {
2386
- title = '选择版本引用方式';
2387
- description = '跟随 AW 通道适合常规来源;固定 ref 适合测试来源。';
2640
+ title = '选择版本来源';
2641
+ description = 'Release 和 Beta 是固定 Git ref 的显示预设。';
2642
+ const useRefPreset = async (refPolicy, ref) => {
2643
+ const next = { ...sourceDraft, refPolicy, ref };
2644
+ setSourceDraft(next);
2645
+ if (next.mode === 'update')
2646
+ await beginSourceUpdateReview(next);
2647
+ else
2648
+ go({ name: 'sourceAdd', step: 'subdir', owner: route.owner });
2649
+ };
2388
2650
  actions = [
2389
- { label: '跟随 AW 版本通道', description: '自动选择 catalog/latest 或 catalog/beta', run: async () => { const next = { ...sourceDraft, refPolicy: 'cliChannel', ref: '' }; setSourceDraft(next); if (next.mode === 'update')
2390
- await beginSourceUpdateReview(next);
2391
- else
2392
- go({ name: 'sourceAdd', step: 'subdir', owner: route.owner }); } },
2393
- { label: '固定 Git 引用', description: 'branch、tag 或 commit', run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
2651
+ { label: '跟随 AW CLI', description: `当前 CLI ${snapshot.cliChannel === 'beta' ? 'beta' : 'release'} → catalog/${snapshot.cliChannel === 'beta' ? 'beta' : 'latest'}`, selected: sourceDraft.refPolicy === 'cliChannel', run: () => { const policy = catalogRefPolicyForPreset('followCli'); return useRefPreset(policy.type, ''); } },
2652
+ { label: 'Release', description: '固定读取 catalog/latest', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_LATEST_REF, run: () => { const policy = catalogRefPolicyForPreset('release'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
2653
+ { label: 'Beta', description: '固定读取 catalog/beta', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref === CATALOG_BETA_REF, run: () => { const policy = catalogRefPolicyForPreset('beta'); return useRefPreset(policy.type, policy.type === 'explicit' ? policy.ref : ''); } },
2654
+ { label: '指定 Git ref…', description: 'branch、tag、完整 ref 或 commit', selected: sourceDraft.refPolicy === 'explicit' && sourceDraft.ref !== CATALOG_LATEST_REF && sourceDraft.ref !== CATALOG_BETA_REF, run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
2394
2655
  ];
2395
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
2656
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u8DDF\u968F\u542B\u4E49\uFF1A\u5F53\u524D CLI ", snapshot.cliChannel === 'beta' ? 'beta → catalog/beta' : 'release → catalog/latest'] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' })] });
2396
2657
  }
2397
2658
  else {
2398
2659
  title = route.step === 'id' ? '输入来源 ID' : route.step === 'location' ? `输入 ${sourceDraft.type === 'git' ? 'Git URL' : '绝对路径'}` : route.step === 'ref' ? '输入固定 Git 引用' : '输入 Catalog 子目录';
@@ -2402,7 +2663,7 @@ export function ConsoleAppV4(props) {
2402
2663
  : route.step === 'location' && sourceDraft.type === 'path'
2403
2664
  ? privatePathInput(sourceInput)
2404
2665
  : sourceInput;
2405
- content = _jsx(InputCard, { focused: true, title: displayedInput || ' ', description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
2666
+ content = _jsx(InputCard, { focused: true, title: _jsx(InputDisplay, { value: displayedInput, columns: Math.max(8, size.columns - 8) }), description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
2406
2667
  }
2407
2668
  }
2408
2669
  else if (route.name === 'sourceEdit') {
@@ -2418,12 +2679,35 @@ export function ConsoleAppV4(props) {
2418
2679
  setDirty(true);
2419
2680
  go({ name: 'sourceAdd', step: field === 'location' ? 'location' : field === 'subdir' ? 'subdir' : 'refPolicy', owner });
2420
2681
  };
2682
+ const switchOfficialTransport = (location) => {
2683
+ if (!detail || detail.type !== 'git'
2684
+ || detail.locationSummary === projectCatalogSourceLocation(location))
2685
+ return;
2686
+ const owner = `source-update:${route.sourceId}:location:${Date.now()}`;
2687
+ const next = {
2688
+ owner,
2689
+ mode: 'update',
2690
+ sourceId: route.sourceId,
2691
+ type: 'git',
2692
+ location,
2693
+ refPolicy: detail.refPolicy ?? 'cliChannel',
2694
+ ref: detail.explicitRef ?? '',
2695
+ subdir: detail.subdir ?? '',
2696
+ editField: 'location',
2697
+ };
2698
+ setSourceDraft(next);
2699
+ setDirty(true);
2700
+ void beginSourceUpdateReview(next);
2701
+ };
2421
2702
  actions = detail?.type === 'git' ? [
2422
- { label: 'Git URL', description: detail.locationSummary, run: () => startEdit('location') },
2703
+ ...(route.sourceId === 'official' ? [
2704
+ { label: 'HTTPS', description: '使用系统 Git credential helper', selected: detail.locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_URL), run: () => switchOfficialTransport(OFFICIAL_CATALOG_SOURCE_URL) },
2705
+ { label: 'SSH', description: '使用本机 SSH key;Git 连接使用 BatchMode', selected: detail.locationSummary === projectCatalogSourceLocation(OFFICIAL_CATALOG_SOURCE_SSH_URL), run: () => switchOfficialTransport(OFFICIAL_CATALOG_SOURCE_SSH_URL) },
2706
+ ] : [{ label: 'Git URL', description: detail.locationSummary, run: () => startEdit('location') }]),
2423
2707
  { label: '版本引用', description: detail.refPolicy === 'explicit' ? `固定:${detail.explicitRef}` : '跟随 AW 版本通道', run: () => startEdit('refPolicy') },
2424
2708
  { label: 'Catalog 子目录', description: detail.subdir ?? '未设置', run: () => startEdit('subdir') },
2425
2709
  ] : [{ label: '本地目录', description: '已选择本地目录', run: () => startEdit('location') }];
2426
- content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
2710
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' }), route.sourceId === 'official' && detail?.type === 'git' ? _jsxs(SectionCard, { title: "\u6765\u6E90\u8EAB\u4EFD", children: [_jsx(StaticRow, { title: "\u903B\u8F91 Catalog", meta: "lesliechan721-arch/agent-workflow" }), _jsx(StaticRow, { title: "source identity", meta: "HTTPS / SSH \u4FDD\u6301\u4E00\u81F4" }), _jsx(StaticRow, { title: "\u660E\u6587 HTTP", meta: "\u4E0D\u652F\u6301", status: "warning" })] }) : null] });
2427
2711
  }
2428
2712
  else if (route.name === 'sourceRemoveBlocked') {
2429
2713
  breadcrumb = `插件来源 / ${route.sourceId} / 移除`;
@@ -2453,9 +2737,9 @@ export function ConsoleAppV4(props) {
2453
2737
  label: entry.category.startsWith('dependency')
2454
2738
  ? `${props.controller.getPluginOverview(entry.pluginId ?? '')?.displayName ?? entry.pluginId ?? entry.title}${entry.platform ? ` · ${platformLabel(entry.platform)}` : ''}`
2455
2739
  : entry.title,
2456
- description: entry.category.startsWith('dependency')
2740
+ description: `${issueGroupLabel(entry.group)} · ${entry.category.startsWith('dependency')
2457
2741
  ? `${props.controller.getPluginOverview(entry.dependencyPluginId ?? '')?.displayName ?? entry.dependencyPluginId ?? entry.explanation} · ${entry.impact[0] ?? ''}`
2458
- : `${entry.explanation} · ${entry.impact[0] ?? ''}`,
2742
+ : `${entry.explanation} · ${entry.impact[0] ?? ''}`}`,
2459
2743
  status: entry.category === 'sourceDegraded' ? 'warning' : 'error',
2460
2744
  run: () => entry.category === 'dependencyStillRequired' && entry.pluginId
2461
2745
  ? go({ name: 'pluginDependencies', pluginId: entry.pluginId, view: 'removeBlocked',
@@ -2595,12 +2879,9 @@ export function ConsoleAppV4(props) {
2595
2879
  content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
2596
2880
  }
2597
2881
  else {
2598
- const sourceMissingCleanup = issue.category === 'sourceMissing' && issue.pluginId && issue.platform && issue.scope
2599
- ? `aw cleanup ${issue.pluginId} --platform ${issue.platform} --scope ${issue.scope}`
2600
- : undefined;
2601
- const hasRecoveryEvidence = sourceMissingCleanup || issue.category === 'sourceIdentityChanged'
2882
+ const hasRecoveryEvidence = issue.category === 'sourceIdentityChanged'
2602
2883
  || issue.placements || issue.requiredFields;
2603
- const recoveryRows = _jsxs(_Fragment, { children: [sourceMissingCleanup ? _jsxs(_Fragment, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: sourceMissingCleanup }), _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u9700\u8981\u65F6\u53EF\u5728\u547D\u4EE4\u672B\u5C3E\u6DFB\u52A0 --force\u3002" })] }) : null, issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
2884
+ const recoveryRows = _jsxs(_Fragment, { children: [issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
2604
2885
  content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && hasRecoveryEvidence
2605
2886
  ? policy.mode === 'compact' ? recoveryRows : _jsx(SectionCard, { title: "\u6062\u590D\u8BC1\u636E", children: recoveryRows })
2606
2887
  : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: issueActionViewport.offset, limit: issueActionViewport.limit })] });
@@ -2610,11 +2891,13 @@ export function ConsoleAppV4(props) {
2610
2891
  const preparation = snapshot.reviewPreparation;
2611
2892
  const labels = preparation.intent.kind === 'portableInstall'
2612
2893
  ? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
2613
- : preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
2614
- ? ['设置', '正在准备设置确认']
2615
- : preparation.intent.kind === 'sourceMutation'
2616
- ? ['插件来源', '正在准备来源变更确认']
2617
- : ['插件操作', '正在准备插件操作确认'];
2894
+ : preparation.intent.kind === 'catalogActivate'
2895
+ ? ['插件来源 / Catalog 更新', '正在准备 Catalog 激活确认']
2896
+ : preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
2897
+ ? ['设置', '正在准备设置确认']
2898
+ : preparation.intent.kind === 'sourceMutation'
2899
+ ? ['插件来源', '正在准备来源变更确认']
2900
+ : ['插件操作', '正在准备插件操作确认'];
2618
2901
  breadcrumb = `${labels[0]} / 准备确认`;
2619
2902
  context = '只读';
2620
2903
  title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
@@ -2664,15 +2947,18 @@ export function ConsoleAppV4(props) {
2664
2947
  const groupTitle = {
2665
2948
  requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
2666
2949
  dependencies: review.operationKind === 'portableInstall' ? '已选择 Skills · 来自依赖插件' : '必需依赖',
2950
+ runtime: 'Codex runtime',
2667
2951
  portableTarget: '安装目标',
2668
2952
  portableInputs: '准备内容',
2669
2953
  portableWarnings: '覆盖冲突',
2954
+ configChanges: '设置变更',
2955
+ configImpact: '应用影响',
2670
2956
  preserved: '保持不变',
2671
2957
  };
2672
2958
  const changeStatus = (change) => change.kind === 'remove' ? 'error'
2673
2959
  : change.kind === 'warning' ? 'warning'
2674
2960
  : change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined;
2675
- const groupOrder = ['portableWarnings', 'portableTarget', 'requested', 'dependencies', 'preserved', 'portableInputs'];
2961
+ const groupOrder = ['portableWarnings', 'portableTarget', 'configChanges', 'configImpact', 'requested', 'dependencies', 'runtime', 'preserved', 'portableInputs'];
2676
2962
  const groupedSpecs = groupedChanges
2677
2963
  ? groupOrder.map((group) => ({
2678
2964
  key: group,
@@ -2707,7 +2993,8 @@ export function ConsoleAppV4(props) {
2707
2993
  const hiddenCount = group.changes.length - visibleCount;
2708
2994
  return _jsxs(Box, { flexDirection: "column", children: [policy.mode !== 'compact' && group.label ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: group.label }) : null, group.changes.slice(0, visibleCount).map((change, index) => {
2709
2995
  const retainDescription = change.kind === 'warning' || group.key === 'portableTarget'
2710
- || group.key === 'requested' || group.key === 'dependencies';
2996
+ || group.key === 'requested' || group.key === 'dependencies'
2997
+ || group.key === 'configChanges' || group.key === 'configImpact';
2711
2998
  const description = retainDescription ? change.description : undefined;
2712
2999
  const compactGroupLabel = group.key === 'requested' ? '根插件'
2713
3000
  : group.key === 'dependencies' ? '依赖'
@@ -2737,17 +3024,18 @@ export function ConsoleAppV4(props) {
2737
3024
  return _jsx(ReviewChangeRow, { title: `${category ? `${category} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`, meta: index === visibleTechnicalChanges.length - 1 && hiddenTechnicalChanges > 0
2738
3025
  ? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
2739
3026
  });
2740
- content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel} ${review.subject.version ?? ''}`, description: review.equivalentCommand ? review.description : undefined, children: [review.equivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), policy.mode === 'compact' || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
3027
+ content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel ?? ''} ${review.subject.version ?? ''}`, description: review.equivalentCommand ? review.description : undefined, children: [review.equivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, 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, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), policy.mode === 'compact' || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
2741
3028
  }
2742
3029
  else if (route.name === 'progress') {
3030
+ const catalogActivation = snapshot.activeReview?.operationKind === 'catalogActivate';
2743
3031
  const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
2744
3032
  const configOperation = snapshot.activeReview?.operationKind === 'configSave'
2745
3033
  || snapshot.activeReview?.operationKind === 'configReset';
2746
3034
  const sourceOperation = snapshot.activeReview?.operationKind === 'sourceMutation';
2747
3035
  const issueOperation = snapshot.activeReview?.originIssue;
2748
- breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
2749
- title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
2750
- description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
3036
+ breadcrumb = catalogActivation ? '插件来源 / Catalog 更新' : portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
3037
+ title = catalogActivation ? '正在激活已验证的 Catalog generation' : portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
3038
+ description = catalogActivation ? '不会重新下载;完成后重新加载插件列表和依赖关系。' : issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
2751
3039
  help = 'Ctrl-C 完成后退出';
2752
3040
  const progress = snapshot.progress;
2753
3041
  const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
@@ -2867,6 +3155,7 @@ export function ConsoleAppV4(props) {
2867
3155
  : result.state === 'blocked' ? result.summary : undefined;
2868
3156
  const action = resultSummary?.action;
2869
3157
  const operationKind = 'operationKind' in result ? result.operationKind : undefined;
3158
+ const catalogActivation = operationKind === 'catalogActivate';
2870
3159
  const portableOperation = operationKind === 'portableInstall';
2871
3160
  const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
2872
3161
  ? result.summary.portable : undefined;
@@ -2887,22 +3176,25 @@ export function ConsoleAppV4(props) {
2887
3176
  uninstall: '✓ 插件移除完成',
2888
3177
  cleanup: '✓ 插件清理完成',
2889
3178
  }[action ?? 'install'];
2890
- title = portableOperation
2891
- ? blocked ? '× 通用 Skills 安装被阻止'
2892
- : partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
2893
- : blocked && result.writeMayHaveCompleted
2894
- ? '× 写入结果未确认'
2895
- : issueOperation
2896
- ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
2897
- : sourceOperation
2898
- ? blocked ? '× 来源操作未完成' : sourceRefreshWarning ? '! 来源配置已保存,但刷新失败' : '✓ 来源操作完成'
2899
- : configOperation
2900
- ? blocked
2901
- ? '× 设置未确认保存'
2902
- : partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
2903
- : partial ? '! 插件操作只完成了一部分'
2904
- : blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
2905
- : completedTitle;
3179
+ breadcrumb = catalogActivation ? '插件来源 / Catalog 更新 / 结果' : breadcrumb;
3180
+ title = catalogActivation
3181
+ ? blocked ? '× Catalog 更新未启用' : '✓ Catalog 更新已启用'
3182
+ : portableOperation
3183
+ ? blocked ? '× 通用 Skills 安装被阻止'
3184
+ : partial ? '! 通用 Skills 结果未完全完成' : '✓ 通用 Skills 已安装'
3185
+ : blocked && result.writeMayHaveCompleted
3186
+ ? '× 写入结果未确认'
3187
+ : issueOperation
3188
+ ? blocked || partial ? '× 问题仍未解决' : '✓ 问题已处理'
3189
+ : sourceOperation
3190
+ ? blocked ? '× 来源操作未完成' : sourceRefreshWarning ? '! 来源配置已保存,但刷新失败' : '✓ 来源操作完成'
3191
+ : configOperation
3192
+ ? blocked
3193
+ ? '× 设置未确认保存'
3194
+ : partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
3195
+ : partial ? '! 插件操作只完成了一部分'
3196
+ : blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
3197
+ : completedTitle;
2906
3198
  description = portableOperation
2907
3199
  ? blocked
2908
3200
  ? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
@@ -3326,7 +3618,7 @@ export function ConsoleAppV4(props) {
3326
3618
  return;
3327
3619
  }
3328
3620
  if (key.backspace || key.delete)
3329
- setPortableAgentInput((value) => value.slice(0, -1));
3621
+ setPortableAgentInput(removeLastGrapheme);
3330
3622
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3331
3623
  setPortableAgentInput((value) => value + input);
3332
3624
  setInputError(undefined);
@@ -3413,7 +3705,7 @@ export function ConsoleAppV4(props) {
3413
3705
  return;
3414
3706
  }
3415
3707
  if (key.backspace || key.delete)
3416
- setSourceInput((value) => value.slice(0, -1));
3708
+ setSourceInput(removeLastGrapheme);
3417
3709
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3418
3710
  setSourceInput((value) => value + input);
3419
3711
  setSourceInputError(undefined);
@@ -3455,19 +3747,44 @@ export function ConsoleAppV4(props) {
3455
3747
  return;
3456
3748
  }
3457
3749
  if (key.backspace || key.delete)
3458
- setInputValue((value) => value.slice(0, -1));
3750
+ setInputValue(removeLastGrapheme);
3459
3751
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3460
3752
  setInputValue((value) => value + input);
3461
3753
  setInputError(undefined);
3462
3754
  return;
3463
3755
  }
3464
- if (searchMode && route.name === 'pluginList') {
3756
+ if (searchFocus && route.name === 'pluginList') {
3465
3757
  if (key.escape) {
3466
- setSearchMode(false);
3758
+ setSearchFocus(false);
3759
+ return;
3760
+ }
3761
+ if (key.return) {
3762
+ setSearchFocus(false);
3763
+ const row = pluginRows[focus];
3764
+ if (row)
3765
+ go(route.mode === 'browse'
3766
+ ? { name: 'pluginPreview', pluginId: row.pluginId }
3767
+ : { name: 'pluginDetail', pluginId: row.pluginId });
3768
+ return;
3769
+ }
3770
+ if (key.upArrow || key.downArrow) {
3771
+ setSearchFocus(false);
3772
+ setFocus((value) => moveIndex(pluginRows.length, value, key.upArrow ? -1 : 1));
3773
+ return;
3774
+ }
3775
+ if (key.leftArrow || key.rightArrow) {
3776
+ setSearchFocus(false);
3777
+ const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];
3778
+ const current = route.sourceId ?? 'all';
3779
+ const direction = key.leftArrow ? -1 : 1;
3780
+ const next = tabs[(tabs.indexOf(current) + direction + tabs.length) % tabs.length];
3781
+ props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
3782
+ setFocus(0);
3783
+ setScrollOffset(0);
3467
3784
  return;
3468
3785
  }
3469
3786
  if (key.backspace || key.delete)
3470
- setQuery((value) => value.slice(0, -1));
3787
+ setQuery(removeLastGrapheme);
3471
3788
  else if (input && !key.ctrl && !key.meta && input >= ' ')
3472
3789
  setQuery((value) => value + input);
3473
3790
  setFocus(0);
@@ -3492,7 +3809,7 @@ export function ConsoleAppV4(props) {
3492
3809
  setShowTechnical((value) => !value);
3493
3810
  }
3494
3811
  else if ((key.escape || input.toLowerCase() === 'b')
3495
- && route.name === 'configFieldEditor') {
3812
+ && (route.name === 'configFieldEditor' || route.name === 'configFieldAction')) {
3496
3813
  back();
3497
3814
  }
3498
3815
  else if (key.escape || input.toLowerCase() === 'b') {
@@ -3505,7 +3822,7 @@ export function ConsoleAppV4(props) {
3505
3822
  back();
3506
3823
  }
3507
3824
  else if (route.name === 'pluginList' && input === '/') {
3508
- setSearchMode(true);
3825
+ setSearchFocus(true);
3509
3826
  }
3510
3827
  else if (route.name === 'pluginList' && route.mode === 'browse' && input.toLowerCase() === 'f') {
3511
3828
  const selectedPluginId = pluginRows[focus]?.pluginId;
@@ -3543,12 +3860,15 @@ export function ConsoleAppV4(props) {
3543
3860
  setScrollOffset(0);
3544
3861
  }
3545
3862
  else if (input.toLowerCase() === 'r' && route.name === 'sourceDetail') {
3863
+ go({ name: 'catalogRefresh', sourceId: route.sourceId });
3546
3864
  void props.controller.refreshSource(route.sourceId);
3547
3865
  }
3548
3866
  else if (input.toLowerCase() === 'r' && route.name === 'sourceList') {
3549
3867
  const source = props.controller.listSources()[focus];
3550
- if (source)
3868
+ if (source) {
3869
+ go({ name: 'catalogRefresh', sourceId: source.id });
3551
3870
  void props.controller.refreshSource(source.id);
3871
+ }
3552
3872
  }
3553
3873
  else if (route.name === 'pluginList' && (key.leftArrow || key.rightArrow || input === '[' || input === ']')) {
3554
3874
  const tabs = ['all', ...props.controller.listSources().map((source) => source.id)];