@lesliechan721/aw 0.1.0-beta.37 → 0.1.0-beta.39

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 (55) hide show
  1. package/dist/authoring/assets/aw-runtime-config.d.mts +13 -0
  2. package/dist/authoring/assets/aw-runtime-config.mjs +199 -0
  3. package/dist/authoring/assets/aw-runtime-config.mjs.map +1 -0
  4. package/dist/authoring/scripts/lib/plugin-version-check.mjs +1 -1
  5. package/dist/authoring/scripts/validate-plugin.mjs +44 -3
  6. package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
  7. package/dist/scripts/test-plugin-scripts.js +3 -0
  8. package/dist/scripts/test-plugin-scripts.js.map +1 -1
  9. package/dist/src/commands/aw-capabilities.d.ts +1 -1
  10. package/dist/src/commands/aw-capabilities.js +4 -0
  11. package/dist/src/commands/aw-capabilities.js.map +1 -1
  12. package/dist/src/commands/plugin-capabilities.js +4 -0
  13. package/dist/src/commands/plugin-capabilities.js.map +1 -1
  14. package/dist/src/commands/rebuild-integration.js +16 -11
  15. package/dist/src/commands/rebuild-integration.js.map +1 -1
  16. package/dist/src/commands/skill-eval.d.ts +1 -0
  17. package/dist/src/commands/skill-eval.js +14 -2
  18. package/dist/src/commands/skill-eval.js.map +1 -1
  19. package/dist/src/generators/local-plugin.js +3 -0
  20. package/dist/src/generators/local-plugin.js.map +1 -1
  21. package/dist/src/index.js +2 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/review/review-profile.js +55 -15
  24. package/dist/src/review/review-profile.js.map +1 -1
  25. package/dist/src/review/source-binding.d.ts +3 -2
  26. package/dist/src/review/source-binding.js +32 -6
  27. package/dist/src/review/source-binding.js.map +1 -1
  28. package/dist/src/tui/config-leaf-editor.d.ts +16 -0
  29. package/dist/src/tui/config-leaf-editor.js +206 -0
  30. package/dist/src/tui/config-leaf-editor.js.map +1 -0
  31. package/dist/src/tui/config-value-presentation.js +23 -12
  32. package/dist/src/tui/config-value-presentation.js.map +1 -1
  33. package/dist/src/tui/console-app-v4.js +137 -25
  34. package/dist/src/tui/console-app-v4.js.map +1 -1
  35. package/dist/src/tui/model.js +12 -12
  36. package/dist/src/tui/model.js.map +1 -1
  37. package/dist/src/utils/artifact-renderer.d.ts +4 -0
  38. package/dist/src/utils/artifact-renderer.js +24 -3
  39. package/dist/src/utils/artifact-renderer.js.map +1 -1
  40. package/dist/src/utils/artifact-request.js +1 -5
  41. package/dist/src/utils/artifact-request.js.map +1 -1
  42. package/dist/src/utils/bundle-layout.js +8 -0
  43. package/dist/src/utils/bundle-layout.js.map +1 -1
  44. package/dist/src/utils/plugin-config-write.js +11 -1
  45. package/dist/src/utils/plugin-config-write.js.map +1 -1
  46. package/dist/src/utils/plugin-config.d.ts +13 -0
  47. package/dist/src/utils/plugin-config.js +113 -3
  48. package/dist/src/utils/plugin-config.js.map +1 -1
  49. package/dist/src/utils/plugin-scripts.d.ts +3 -0
  50. package/dist/src/utils/plugin-scripts.js +18 -3
  51. package/dist/src/utils/plugin-scripts.js.map +1 -1
  52. package/dist/src/utils/runtime-config.d.ts +20 -0
  53. package/dist/src/utils/runtime-config.js +75 -0
  54. package/dist/src/utils/runtime-config.js.map +1 -0
  55. package/package.json +3 -3
@@ -2,10 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { arrayDraftChanged, arrayDraftValue, createConfigArrayDraft, editConfigArrayDraft } from './config-array-draft.js';
3
3
  import { PLUGIN_CONFIG_ARRAY_MAX_ITEMS } from '@lesliechan721/aw-plugin-core';
4
4
  import { presentConfigValue, safeConfigText } from './config-value-presentation.js';
5
+ import { ConfigLeafEditor } from './config-leaf-editor.js';
5
6
  import { Box, Text, useInput, useWindowSize } from 'ink';
6
7
  import { useEffect, useLayoutEffect, useRef, useState } from 'react';
7
8
  import path from 'node:path';
8
- import { validatePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
9
+ import { resolvePluginConfigObjectProperties, validatePluginConfigValue, } from '@lesliechan721/aw-plugin-core/manifest';
9
10
  import { CATALOG_BETA_REF, CATALOG_LATEST_REF, OFFICIAL_CATALOG_SOURCE_SSH_URL, OFFICIAL_CATALOG_SOURCE_URL, } from '../constants.js';
10
11
  import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
11
12
  import { canonicalizeCatalogGitUrl } from '../utils/catalog-source-identity.js';
@@ -1019,6 +1020,8 @@ export function ConsoleAppV4(props) {
1019
1020
  const [objectDrafts, setObjectDrafts] = useState({});
1020
1021
  const [changedObjectDraftKeys, setChangedObjectDraftKeys] = useState([]);
1021
1022
  const [objectError, setObjectError] = useState();
1023
+ const [nestedPropertyRevision, setNestedPropertyRevision] = useState();
1024
+ const [objectVariantPreview, setObjectVariantPreview] = useState();
1022
1025
  const [inputValue, setInputValue] = useState('');
1023
1026
  const [inputError, setInputError] = useState();
1024
1027
  const [configActionError, setConfigActionError] = useState();
@@ -1064,7 +1067,7 @@ export function ConsoleAppV4(props) {
1064
1067
  const dependencyGraphContexts = useRef(new Map());
1065
1068
  const externalDraftRevision = useRef({ config: 0, source: 0, batch: 0 });
1066
1069
  const previousExternalDraftValues = useRef({
1067
- configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1070
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, nestedPropertyRevision, objectVariantPreview, sourceDraft, batchDraft,
1068
1071
  });
1069
1072
  const externalDraftFlowIds = useRef(new Map());
1070
1073
  const configOverviewAnchors = useRef(new Map());
@@ -1358,7 +1361,9 @@ export function ConsoleAppV4(props) {
1358
1361
  || previous.configCandidate !== configCandidate
1359
1362
  || previous.arrayDrafts !== arrayDrafts
1360
1363
  || previous.objectDrafts !== objectDrafts
1361
- || previous.changedObjectDraftKeys !== changedObjectDraftKeys) {
1364
+ || previous.changedObjectDraftKeys !== changedObjectDraftKeys
1365
+ || previous.nestedPropertyRevision !== nestedPropertyRevision
1366
+ || previous.objectVariantPreview !== objectVariantPreview) {
1362
1367
  externalDraftRevision.current.config += 1;
1363
1368
  }
1364
1369
  if (previous.sourceDraft !== sourceDraft)
@@ -1366,7 +1371,7 @@ export function ConsoleAppV4(props) {
1366
1371
  if (previous.batchDraft !== batchDraft)
1367
1372
  externalDraftRevision.current.batch += 1;
1368
1373
  previousExternalDraftValues.current = {
1369
- configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, sourceDraft, batchDraft,
1374
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, changedObjectDraftKeys, nestedPropertyRevision, objectVariantPreview, sourceDraft, batchDraft,
1370
1375
  };
1371
1376
  const currentFlowId = snapshot.router.currentFlowId;
1372
1377
  if (currentFlowId && configRouteOwner) {
@@ -1375,7 +1380,7 @@ export function ConsoleAppV4(props) {
1375
1380
  if (currentFlowId && sourceDraft)
1376
1381
  externalDraftFlowIds.current.set(sourceDraft.owner, currentFlowId);
1377
1382
  const drafts = [];
1378
- if ((dirty || configCandidate || Object.values(arrayDrafts).some((draft) => draft.initial === undefined ? draft.rows.length > 0 : arrayDraftChanged(draft)) || changedObjectDraftKeys.length > 0) && draftOwner) {
1383
+ if ((dirty || configCandidate || nestedPropertyRevision || objectVariantPreview || Object.values(arrayDrafts).some((draft) => draft.initial === undefined ? draft.rows.length > 0 : arrayDraftChanged(draft)) || changedObjectDraftKeys.length > 0) && draftOwner) {
1379
1384
  const flowId = externalDraftFlowIds.current.get(draftOwner);
1380
1385
  drafts.push({
1381
1386
  kind: 'configDraft', owner: draftOwner, revision: externalDraftRevision.current.config,
@@ -1398,7 +1403,7 @@ export function ConsoleAppV4(props) {
1398
1403
  props.controller.setExternalDraftState(drafts);
1399
1404
  }, [
1400
1405
  props.controller, batchDraft, changedObjectDraftKeys, configRouteOwner, dirty, draftOwner,
1401
- configDrafts, configCandidate, arrayDrafts, objectDrafts, route.name, snapshot.router.currentFlowId, sourceDraft,
1406
+ configDrafts, configCandidate, arrayDrafts, objectDrafts, nestedPropertyRevision, objectVariantPreview, route.name, snapshot.router.currentFlowId, sourceDraft,
1402
1407
  ]);
1403
1408
  useEffect(() => () => props.controller.setExternalDraftState(), [props.controller]);
1404
1409
  const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
@@ -1788,15 +1793,24 @@ export function ConsoleAppV4(props) {
1788
1793
  const activeObjectDraft = activeObjectKey
1789
1794
  ? objectDrafts[activeObjectKey] ?? objectValueFromCandidate ?? objectValueFromUpdate ?? objectValueFromField ?? {}
1790
1795
  : undefined;
1796
+ const activeObjectProperties = activeField?.definition.type === 'object' && activeObjectDraft
1797
+ ? resolvePluginConfigObjectProperties(activeField.definition, activeObjectDraft, activeField.key).properties
1798
+ : undefined;
1791
1799
  const activePropertyDefinition = route.name === 'configObjectPropertyEditor'
1792
1800
  && activeField?.definition.type === 'object' && route.propertyKey !== undefined
1793
- ? activeField.definition.properties?.[route.propertyKey]
1801
+ ? activeObjectProperties?.[route.propertyKey]
1794
1802
  ?? (activeField.definition.additionalProperties === false
1795
1803
  ? undefined
1796
1804
  : activeField.definition.additionalProperties)
1797
1805
  : undefined;
1806
+ const activeVariantPreview = objectVariantPreview?.routeKey === routeKey ? objectVariantPreview : undefined;
1807
+ const nestedPropertyEditor = route.name === 'configObjectPropertyEditor' && route.stage === 'custom'
1808
+ && !activeVariantPreview && (activePropertyDefinition?.type === 'array' || activePropertyDefinition?.type === 'object');
1809
+ useEffect(() => { setObjectVariantPreview(undefined); }, [routeKey]);
1798
1810
  const activePropertySensitive = activeField?.sensitive === true
1799
1811
  || activePropertyDefinition?.sensitive === true;
1812
+ const activeFieldIsConst = activeField && 'const' in activeField.definition && activeField.definition.const !== undefined;
1813
+ const activePropertyIsConst = activePropertyDefinition && 'const' in activePropertyDefinition && activePropertyDefinition.const !== undefined;
1800
1814
  useEffect(() => {
1801
1815
  if (route.name !== 'configFieldEditor' || !activeField)
1802
1816
  return;
@@ -1896,7 +1910,7 @@ export function ConsoleAppV4(props) {
1896
1910
  || Boolean(route.name === 'configUnavailable' && plugin)
1897
1911
  || Boolean(route.name === 'result' && snapshot.result && 'operationKind' in snapshot.result
1898
1912
  && snapshot.result.operationKind === 'portableInstall'));
1899
- const characterInput = isCharacterInputMode(route, searchFocus)
1913
+ const characterInput = !activeVariantPreview && !activeFieldIsConst && !activePropertyIsConst && isCharacterInputMode(route, searchFocus)
1900
1914
  || route.name === 'operationReportPreview' && reportSaveMode;
1901
1915
  const canOpenHelp = !tooSmall && !characterInput && !preparationRunning;
1902
1916
  useEffect(() => {
@@ -3079,7 +3093,8 @@ export function ConsoleAppV4(props) {
3079
3093
  const itemDefinition = activeTopField?.definition.type === 'array' ? activeTopField.definition.items : undefined;
3080
3094
  const property = issue.path.property;
3081
3095
  const label = property && itemDefinition?.type === 'object'
3082
- ? itemDefinition.properties?.[property]?.title ?? property : property ?? activeField?.title ?? issue.path.field;
3096
+ ? activeObjectProperties?.[property]?.title ?? itemDefinition.properties?.[property]?.title ?? property
3097
+ : property ? activeObjectProperties?.[property]?.title ?? property : activeField?.title ?? issue.path.field;
3083
3098
  return `${index === undefined ? '' : `第 ${Number(index) + 1} 项/`}${label}:${issue.message}`;
3084
3099
  };
3085
3100
  const commitObjectDraft = () => {
@@ -3099,11 +3114,11 @@ export function ConsoleAppV4(props) {
3099
3114
  const openObjectProperty = (propertyKey, dynamic) => {
3100
3115
  if (!activeField || activeField.definition.type !== 'object')
3101
3116
  return;
3102
- const definition = activeField.definition.properties?.[propertyKey]
3117
+ const definition = activeObjectProperties?.[propertyKey]
3103
3118
  ?? (activeField.definition.additionalProperties === false
3104
3119
  ? undefined
3105
3120
  : activeField.definition.additionalProperties);
3106
- if (!definition)
3121
+ if (!definition || 'const' in definition && definition.const !== undefined)
3107
3122
  return;
3108
3123
  const exists = activeObjectDraft && Object.hasOwn(activeObjectDraft, propertyKey);
3109
3124
  go({
@@ -3121,12 +3136,36 @@ export function ConsoleAppV4(props) {
3121
3136
  if (!activeField || activeField.definition.type !== 'object' || !activeObjectDraft)
3122
3137
  return false;
3123
3138
  const candidate = { ...activeObjectDraft, [propertyKey]: value };
3139
+ if (value === undefined)
3140
+ delete candidate[propertyKey];
3124
3141
  const issues = validatePluginConfigValue(activeField.definition, candidate, activeField.key)
3125
3142
  .filter((issue) => issue.path.property === propertyKey);
3126
3143
  if (issues.length > 0) {
3127
3144
  setInputError(issues.slice(0, 2).map(objectIssueText).join(';'));
3128
3145
  return false;
3129
3146
  }
3147
+ const variants = activeField.definition.variants;
3148
+ if (variants?.discriminator === propertyKey && activeObjectDraft[propertyKey] !== value) {
3149
+ const resolved = resolvePluginConfigObjectProperties(activeField.definition, candidate, activeField.key);
3150
+ if (resolved.issues.length) {
3151
+ setInputError(resolved.issues.slice(0, 2).map(objectIssueText).join(';'));
3152
+ return false;
3153
+ }
3154
+ const removed = [];
3155
+ for (const property of Object.keys(candidate)) {
3156
+ const definition = resolved.properties[property];
3157
+ if (activeObjectProperties?.[property] && !definition
3158
+ || definition && 'const' in definition && definition.const !== undefined
3159
+ && Object.hasOwn(candidate, property) && candidate[property] !== definition.const) {
3160
+ removed.push({ property, value: candidate[property] });
3161
+ delete candidate[property];
3162
+ }
3163
+ }
3164
+ setObjectVariantPreview({ routeKey, value: candidate, removed });
3165
+ setFocus(0);
3166
+ setBatchTargetOffset(0);
3167
+ return false;
3168
+ }
3130
3169
  updateObjectDraft(candidate);
3131
3170
  return true;
3132
3171
  };
@@ -3265,20 +3304,19 @@ export function ConsoleAppV4(props) {
3265
3304
  const updatedValue = draft.updates[key];
3266
3305
  const afterObject = field?.definition.type === 'object'
3267
3306
  && typeof updatedValue === 'object' && !Array.isArray(updatedValue) ? updatedValue : undefined;
3268
- const childSummary = (property, value) => {
3307
+ const childSummary = (property, value, object) => {
3269
3308
  if (value === undefined)
3270
3309
  return '未设置';
3271
3310
  if (field.sensitive || field.definition.type !== 'object')
3272
3311
  return '已设置';
3273
- const definition = field.definition.properties?.[property]
3312
+ const definition = resolvePluginConfigObjectProperties(field.definition, object).properties[property]
3274
3313
  ?? (field.definition.additionalProperties === false
3275
3314
  ? undefined
3276
3315
  : field.definition.additionalProperties);
3277
3316
  if (definition?.sensitive)
3278
3317
  return '已设置';
3279
- if (typeof value === 'boolean')
3280
- return value ? '开启' : '关闭';
3281
- return value === '' ? '(空字符串)' : safeConfigText(String(value));
3318
+ return definition ? presentConfigValue({ definition, value, full: true }).text
3319
+ : safeConfigText(JSON.stringify(value));
3282
3320
  };
3283
3321
  const updatedChildren = beforeObject && afterObject
3284
3322
  ? [...new Set([...Object.keys(beforeObject), ...Object.keys(afterObject)])]
@@ -3296,7 +3334,7 @@ export function ConsoleAppV4(props) {
3296
3334
  }
3297
3335
  return left.localeCompare(right, 'en');
3298
3336
  })
3299
- .filter((property) => beforeObject[property] !== afterObject[property])
3337
+ .filter((property) => !configValuesEqual(beforeObject[property], afterObject[property]))
3300
3338
  .map((property) => {
3301
3339
  const definition = field.definition.type === 'object'
3302
3340
  ? field.definition.properties?.[property]
@@ -3306,8 +3344,8 @@ export function ConsoleAppV4(props) {
3306
3344
  : undefined;
3307
3345
  return {
3308
3346
  property,
3309
- before: childSummary(property, beforeObject[property]),
3310
- after: childSummary(property, afterObject[property]),
3347
+ before: childSummary(property, beforeObject[property], beforeObject),
3348
+ after: childSummary(property, afterObject[property], afterObject),
3311
3349
  ...(field.sensitive || definition?.sensitive ? { private: true } : {}),
3312
3350
  };
3313
3351
  })
@@ -5965,6 +6003,14 @@ export function ConsoleAppV4(props) {
5965
6003
  actions = canUnset ? [{ label: '删除此覆盖并恢复继承', run: () => removeDraft(activeField.key) }] : [];
5966
6004
  content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u4EC5\u53EF\u5220\u9664\u65E7\u503C", description: description }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
5967
6005
  }
6006
+ else if ('const' in activeField.definition && activeField.definition.const !== undefined) {
6007
+ title = activeField.title;
6008
+ description = '这个值由插件固定,不能编辑。';
6009
+ const constant = activeField.definition.const;
6010
+ actions = activeField.definition.required && activeField.value === undefined
6011
+ ? [{ label: '写入此固定值', run: () => updateDraft(activeField.key, constant) }] : [];
6012
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StaticRow, { title: `固定值 · ${presentConfigValue({ definition: activeField.definition, value: constant }).text}`, compact: true }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6013
+ }
5968
6014
  else if (showChoices) {
5969
6015
  title = `选择${activeField.title}`;
5970
6016
  description = activeField.type !== 'boolean' && activeField.allowCustom
@@ -6044,12 +6090,16 @@ export function ConsoleAppV4(props) {
6044
6090
  if (objectError)
6045
6091
  description = objectError;
6046
6092
  help = '↑↓ 移动 · Enter 编辑 · Esc 取消';
6047
- const fixedEntries = Object.entries(objectDefinition.properties ?? {});
6093
+ const fixedEntries = Object.entries(activeObjectProperties ?? {});
6048
6094
  const fixedKeys = new Set(fixedEntries.map(([property]) => property));
6049
6095
  const dynamicKeys = Object.keys(activeObjectDraft)
6050
6096
  .filter((property) => !fixedKeys.has(property))
6051
6097
  .sort((left, right) => left.localeCompare(right, 'en'));
6052
6098
  const propertySummary = (property, definition) => {
6099
+ if ('const' in definition && definition.const !== undefined) {
6100
+ const text = presentConfigValue({ definition, value: definition.const }).text;
6101
+ return `${'required' in definition && definition.required && !Object.hasOwn(activeObjectDraft, property) ? '必填 · ' : ''}固定值 · ${text}`;
6102
+ }
6053
6103
  const presentation = presentConfigValue({ definition,
6054
6104
  value: activeObjectDraft[property], privateValue: activeField.sensitive,
6055
6105
  configured: Object.hasOwn(activeObjectDraft, property),
@@ -6062,12 +6112,30 @@ export function ConsoleAppV4(props) {
6062
6112
  label: definition.title ?? property,
6063
6113
  description: definition.description,
6064
6114
  meta: propertySummary(property, definition),
6065
- run: () => openObjectProperty(property, false),
6115
+ disabled: 'const' in definition && definition.const !== undefined
6116
+ && !(definition.required && !Object.hasOwn(activeObjectDraft, property)) ? '插件固定值,不能编辑' : undefined,
6117
+ run: () => {
6118
+ if ('const' in definition && definition.const !== undefined) {
6119
+ updateObjectDraft({ ...activeObjectDraft, [property]: definition.const });
6120
+ }
6121
+ else
6122
+ openObjectProperty(property, false);
6123
+ },
6066
6124
  })),
6067
6125
  ...dynamicKeys.map((property) => ({
6068
6126
  label: property,
6069
- meta: propertySummary(property, objectDefinition.additionalProperties),
6070
- run: () => openObjectProperty(property, true),
6127
+ meta: objectDefinition.additionalProperties === false
6128
+ ? `不适用 · ${safeConfigText(JSON.stringify(activeObjectDraft[property]))}`
6129
+ : propertySummary(property, objectDefinition.additionalProperties),
6130
+ run: () => {
6131
+ if (objectDefinition.additionalProperties !== false)
6132
+ openObjectProperty(property, true);
6133
+ else {
6134
+ const next = { ...activeObjectDraft };
6135
+ delete next[property];
6136
+ updateObjectDraft(next);
6137
+ }
6138
+ },
6071
6139
  })),
6072
6140
  ];
6073
6141
  const objectOperationActions = [
@@ -6131,7 +6199,31 @@ export function ConsoleAppV4(props) {
6131
6199
  const definition = activePropertyDefinition;
6132
6200
  const propertyTitle = definition && property ? definition.title ?? property : '动态键';
6133
6201
  breadcrumb = `${plugin.displayName} / 设置 / ${activeField.title} / ${propertyTitle}`;
6134
- if (route.stage === 'key') {
6202
+ if (activeVariantPreview) {
6203
+ title = '确认分支切换';
6204
+ description = '确认后仅更新当前对象草稿;完整设置仍需 Review 后保存。';
6205
+ const candidateIssues = validatePluginConfigValue(activeField.definition, activeVariantPreview.value, activeField.key);
6206
+ readingText = [
6207
+ activeVariantPreview.removed.length ? '将移除这些旧值:' : '没有需要移除的旧值。',
6208
+ ...activeVariantPreview.removed.map((entry) => `${entry.property} = ${safeConfigText(JSON.stringify(entry.value))}`),
6209
+ '切换后的实际配置:', safeConfigText(JSON.stringify(activeVariantPreview.value, null, 2)),
6210
+ ...(candidateIssues.length ? ['需要补充或修正:', ...candidateIssues.map(objectIssueText)] : []),
6211
+ ].join('\n');
6212
+ actions = [{ label: '使用此候选草稿', run: () => {
6213
+ updateObjectDraft(activeVariantPreview.value);
6214
+ if (candidateIssues.length)
6215
+ setObjectError(candidateIssues.slice(0, 2).map(objectIssueText).join(';'));
6216
+ setObjectVariantPreview(undefined);
6217
+ back();
6218
+ } }, { label: '取消切换', run: () => { setObjectVariantPreview(undefined); back(); } }];
6219
+ content = _jsxs(Box, { flexDirection: "column", children: [readingPage(readingText, size.columns - 2, Math.max(1, pageActionBudget(4))), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true })] });
6220
+ }
6221
+ else if (property !== undefined && definition && 'const' in definition && definition.const !== undefined) {
6222
+ title = propertyTitle;
6223
+ description = '这个值由插件固定,不能编辑。';
6224
+ content = _jsx(StaticRow, { title: `固定值 · ${presentConfigValue({ definition, value: definition.const }).text}`, compact: true });
6225
+ }
6226
+ else if (route.stage === 'key') {
6135
6227
  title = '输入新键名';
6136
6228
  description = inputError ?? '键名和属性数量会按插件规则检查。';
6137
6229
  help = '输入 · Enter 继续 · Ctrl-U 清空输入 · Esc 取消';
@@ -6156,6 +6248,11 @@ export function ConsoleAppV4(props) {
6156
6248
  label: route.dynamic ? '删除动态键' : '删除属性',
6157
6249
  status: 'error',
6158
6250
  run: () => {
6251
+ if (activeField.definition.type === 'object' && activeField.definition.variants?.discriminator === property) {
6252
+ if (updateObjectProperty(property, undefined))
6253
+ back();
6254
+ return;
6255
+ }
6159
6256
  const next = { ...activeObjectDraft };
6160
6257
  delete next[property];
6161
6258
  updateObjectDraft(next);
@@ -6203,7 +6300,7 @@ export function ConsoleAppV4(props) {
6203
6300
  ...(definition.choices?.presets ?? []).map((preset) => ({
6204
6301
  label: `${preset.title}${preset.recommended ? '(推荐)' : ''}`,
6205
6302
  description: preset.description,
6206
- selected: !activePropertySensitive && current === preset.value,
6303
+ selected: !activePropertySensitive && configValuesEqual(current, preset.value),
6207
6304
  run: () => { if (updateObjectProperty(property, preset.value))
6208
6305
  back(); },
6209
6306
  })),
@@ -6217,6 +6314,13 @@ export function ConsoleAppV4(props) {
6217
6314
  ];
6218
6315
  content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
6219
6316
  }
6317
+ else if (property !== undefined && definition && (definition.type === 'array' || definition.type === 'object')) {
6318
+ title = `编辑${propertyTitle}`;
6319
+ description = '确认后更新当前对象草稿;Esc 取消此属性的全部修改。';
6320
+ help = '↑↓ 移动 · Enter 编辑 · a 操作 · c 使用 · Esc 取消';
6321
+ content = _jsx(ConfigLeafEditor, { definition: definition, value: activeObjectDraft[property], propertyKey: property, columns: size.columns, rowBudget: pageActionBudget(1), active: !overlay && !tooSmall && !busy, error: inputError, onDraftChange: setNestedPropertyRevision, onCancel: back, onConfirm: (value) => { if (!updateObjectProperty(property, value))
6322
+ return false; back(); return true; } }, `${snapshot.router.currentFrameId}:${property}`);
6323
+ }
6220
6324
  else if (property !== undefined && definition) {
6221
6325
  title = activePropertySensitive ? '输入敏感值' : `输入${propertyTitle}`;
6222
6326
  description = inputError ?? (activePropertySensitive ? '输入已隐藏。' : definition.description ?? '按 Enter 使用输入值。');
@@ -9270,6 +9374,11 @@ export function ConsoleAppV4(props) {
9270
9374
  }
9271
9375
  return;
9272
9376
  }
9377
+ if (nestedPropertyEditor && !overlay) {
9378
+ if (key.ctrl && input.toLowerCase() === 'c')
9379
+ requestConfirmedExit();
9380
+ return;
9381
+ }
9273
9382
  if (arrayToolsOpen && activeArrayDraft) {
9274
9383
  if (key.escape)
9275
9384
  setArrayToolsOpen(false);
@@ -9866,6 +9975,7 @@ export function ConsoleAppV4(props) {
9866
9975
  return;
9867
9976
  }
9868
9977
  if (route.name === 'configFieldEditor'
9978
+ && !activeFieldIsConst
9869
9979
  && (route.editor === 'string' || route.editor === 'number')
9870
9980
  && ((activeField?.choices?.length ?? 0) === 0 || route.mode === 'custom')
9871
9981
  && ['project', 'user'].some((scope) => configLayerPages[scope]?.groups.flatMap((group) => group.fields)
@@ -9912,6 +10022,8 @@ export function ConsoleAppV4(props) {
9912
10022
  }
9913
10023
  if (route.name === 'configObjectPropertyEditor'
9914
10024
  && (route.stage === 'key' || route.stage === 'custom')
10025
+ && !activeVariantPreview
10026
+ && !activePropertyIsConst
9915
10027
  && activeField?.definition.type === 'object'
9916
10028
  && activeObjectDraft) {
9917
10029
  if (key.escape) {