@yancyyu/openhermit 1.6.29 → 1.6.31

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 (157) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
  9. package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
  20. package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
  31. package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
  33. package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
  34. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  35. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
  36. package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
  37. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +74 -123
  120. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  121. package/src/renderer/components/team/TeamListView.tsx +7 -32
  122. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  123. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
  124. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
  125. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  126. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  127. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  128. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  129. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  130. package/src/renderer/store/slices/teamSlice.ts +8 -2
  131. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  132. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  133. package/src/shared/types/api.ts +29 -0
  134. package/src/shared/types/extensions/index.ts +1 -0
  135. package/src/shared/types/extensions/mcp.ts +2 -0
  136. package/src/shared/types/extensions/plugin.ts +2 -1
  137. package/src/shared/types/extensions/skill.ts +7 -0
  138. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  139. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  140. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  141. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  142. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  143. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  144. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  145. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  146. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  147. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  148. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  149. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  150. package/src/features/recent-projects/main/index.ts +0 -3
  151. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  152. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  153. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  154. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  155. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  156. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  157. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -1,7 +1,5 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect } from 'react';
2
2
 
3
- import { api } from '@renderer/api';
4
- import { ALL_AGENT_TYPES, AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
5
3
  import { Button } from '@renderer/components/ui/button';
6
4
  import { Checkbox } from '@renderer/components/ui/checkbox';
7
5
  import {
@@ -12,502 +10,375 @@ import {
12
10
  DialogHeader,
13
11
  DialogTitle,
14
12
  } from '@renderer/components/ui/dialog';
13
+ import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
14
+ import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
15
15
  import { Loader2, Trash2 } from 'lucide-react';
16
16
 
17
- import type { ResolvedTeamMember } from '@shared/types';
18
17
  import type { CcAgentType } from '@shared/types/ccConnect';
19
- import type { GlobalProvider } from '@shared/types/providers';
18
+
19
+ import { PERMISSION_MODE_OPTIONS, useTeamEditForm } from './useTeamEditForm';
20
20
 
21
21
  interface EditTeamDialogProps {
22
22
  open: boolean;
23
23
  teamName: string;
24
- currentName: string;
25
- currentDescription: string;
26
- currentColor: string;
27
- currentAgentType?: string;
28
- currentWorkDir?: string;
29
- currentPermissionMode?: string;
30
- currentLanguage?: string;
31
- currentShowContextIndicator?: boolean;
32
- currentReplyFooter?: boolean;
33
- currentInjectSender?: boolean;
34
- currentManagedSources?: string;
35
- currentDisabledCommands?: string[];
36
- currentPlatformAllowFrom?: Record<string, string>;
37
- currentProviderRefs?: string[];
38
- globalProviders?: GlobalProvider[];
39
- currentMembers: ResolvedTeamMember[];
40
- leadMember?: ResolvedTeamMember | null;
41
- resolvedMemberColorMap?: ReadonlyMap<string, string>;
42
- isTeamAlive?: boolean;
43
- isTeamProvisioning?: boolean;
44
- projectPath?: string | null;
45
- /** Deprecated in cc-connect mode: runtime edits are managed from Harness configuration. */
46
- savedLaunchRequest?: unknown;
47
24
  onClose: () => void;
48
- onSaved: () => Promise<void> | void;
49
- onRestartTeam?: () => Promise<void> | void;
50
25
  onDeleteTeam?: () => void;
51
26
  }
52
27
 
53
- const PERMISSION_MODE_OPTIONS = [
54
- { value: 'default', label: '默认' },
55
- { value: 'acceptEdits', label: '自动接受编辑' },
56
- { value: 'bypassPermissions', label: '跳过权限确认' },
57
- { value: 'plan', label: '计划模式' },
58
- ] as const;
28
+ // ── Section wrapper ──────────────────────────────────────────
29
+ function FormSection({
30
+ title,
31
+ description,
32
+ variant = 'default',
33
+ children,
34
+ }: {
35
+ title: string;
36
+ description?: string;
37
+ variant?: 'default' | 'danger';
38
+ children: React.ReactNode;
39
+ }): React.JSX.Element {
40
+ const border = variant === 'danger' ? 'border-red-500/40' : 'border-[var(--color-border)]';
41
+ const bg = variant === 'danger' ? 'bg-red-500/5' : '';
42
+ const titleColor = variant === 'danger' ? 'text-red-300' : 'text-[var(--color-text)]';
43
+
44
+ return (
45
+ <div className={`rounded-md border ${border} ${bg} p-3`}>
46
+ <h3 className={`text-sm font-medium ${titleColor}`}>{title}</h3>
47
+ {description && (
48
+ <p className="mt-0.5 text-xs text-[var(--color-text-muted)]">{description}</p>
49
+ )}
50
+ <div className="mt-3 space-y-3">{children}</div>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ // ── Shared input class ───────────────────────────────────────
56
+ const inputCls =
57
+ 'w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]';
59
58
 
59
+ const labelCls = 'mb-1 block text-xs font-medium text-[var(--color-text-secondary)]';
60
+
61
+ // ── Main component ───────────────────────────────────────────
60
62
  export const EditTeamDialog = ({
61
63
  open,
62
64
  teamName,
63
- currentName,
64
- currentDescription,
65
- currentColor,
66
- currentAgentType = 'cursor',
67
- currentWorkDir = '',
68
- currentPermissionMode = 'default',
69
- currentLanguage = 'zh',
70
- currentShowContextIndicator = true,
71
- currentReplyFooter = true,
72
- currentInjectSender = false,
73
- currentManagedSources = '*',
74
- currentDisabledCommands = [],
75
- currentPlatformAllowFrom = {},
76
- currentProviderRefs = [],
77
- globalProviders = [],
78
- isTeamAlive = false,
79
- isTeamProvisioning = false,
80
65
  onClose,
81
- onSaved,
82
- onRestartTeam,
83
66
  onDeleteTeam,
84
67
  }: EditTeamDialogProps): React.JSX.Element => {
85
- const [name, setName] = useState(currentName);
86
- const [description, setDescription] = useState(currentDescription);
87
- const [agentType, setAgentType] = useState(currentAgentType);
88
- const [teamWorkDir, setTeamWorkDir] = useState(currentWorkDir);
89
- const [permissionMode, setPermissionMode] = useState(currentPermissionMode);
90
- const [language, setLanguage] = useState(currentLanguage);
91
- const [showContextIndicator, setShowContextIndicator] = useState(currentShowContextIndicator);
92
- const [replyFooter, setReplyFooter] = useState(currentReplyFooter);
93
- const [injectSender, setInjectSender] = useState(currentInjectSender);
94
- const [managedSources, setManagedSources] = useState(currentManagedSources);
95
- const [disabledCommandsInput, setDisabledCommandsInput] = useState(
96
- currentDisabledCommands.join(', ')
97
- );
98
- const [feishuAllowFrom, setFeishuAllowFrom] = useState(currentPlatformAllowFrom.feishu ?? '*');
99
- const [providerRefs, setProviderRefs] = useState<string>(currentProviderRefs[0] ?? '');
100
- const [saving, setSaving] = useState(false);
101
- const [error, setError] = useState<string | null>(null);
102
-
103
- useEffect(() => {
104
- if (!open) return;
105
- setName(currentName);
106
- setDescription(currentDescription);
107
- setAgentType(currentAgentType);
108
- setTeamWorkDir(currentWorkDir);
109
- setPermissionMode(currentPermissionMode);
110
- setLanguage(currentLanguage);
111
- setShowContextIndicator(currentShowContextIndicator);
112
- setReplyFooter(currentReplyFooter);
113
- setInjectSender(currentInjectSender);
114
- setManagedSources(currentManagedSources);
115
- setDisabledCommandsInput(currentDisabledCommands.join(', '));
116
- setFeishuAllowFrom(currentPlatformAllowFrom.feishu ?? '*');
117
- setProviderRefs(currentProviderRefs[0] ?? '');
118
- setError(null);
119
- }, [
120
- open,
121
- currentName,
122
- currentDescription,
123
- currentAgentType,
124
- currentWorkDir,
125
- currentPermissionMode,
126
- currentLanguage,
127
- currentShowContextIndicator,
128
- currentReplyFooter,
129
- currentInjectSender,
130
- currentManagedSources,
131
- currentDisabledCommands,
132
- currentPlatformAllowFrom,
133
- currentProviderRefs,
134
- ]);
135
-
136
- const clearError = (): void => setError(null);
137
-
138
- const handleSave = (): void => {
139
- if (!name.trim()) {
140
- setError('团队名称不能为空');
141
- return;
142
- }
143
- if (isTeamProvisioning) {
144
- setError('团队仍在启动准备中,暂时不能编辑设置。请等待启动完成后再试。');
145
- return;
146
- }
147
-
148
- const disabledCommands = disabledCommandsInput
149
- .split(',')
150
- .map((entry) => entry.trim())
151
- .filter((entry) => entry.length > 0);
152
- const feishu = feishuAllowFrom.trim();
68
+ const form = useTeamEditForm(teamName, open);
153
69
 
154
- setSaving(true);
155
- setError(null);
156
- void (async () => {
157
- try {
158
- await api.teams.updateConfig(teamName, {
159
- name: name.trim(),
160
- description: description.trim(),
161
- color: currentColor,
162
- agentType: agentType.trim() || undefined,
163
- workDir: teamWorkDir.trim() || undefined,
164
- permissionMode: permissionMode.trim() || undefined,
165
- showContextIndicator,
166
- replyFooter,
167
- injectSender,
168
- language: language.trim() || undefined,
169
- managedSources: managedSources.trim() || undefined,
170
- disabledCommands,
171
- platformAllowFrom: feishu ? { feishu } : {},
172
- providerRefs: providerRefs ? [providerRefs] : [],
173
- });
174
- await Promise.resolve(onSaved());
175
- onClose();
176
- } catch (err) {
177
- setError(err instanceof Error ? err.message : '保存失败');
178
- } finally {
179
- setSaving(false);
180
- }
181
- })();
182
- };
183
-
184
- const compatibleProviders = globalProviders.filter(
185
- (provider) =>
186
- !provider.agent_types ||
187
- provider.agent_types.length === 0 ||
188
- (provider.agent_types as string[]).includes(agentType)
189
- );
70
+ // No auto-close — user closes manually after seeing "保存成功"
190
71
 
191
72
  const toggleProviderRef = (providerName: string): void => {
192
- clearError();
193
- setProviderRefs((prev) => (prev === providerName ? '' : providerName));
73
+ form.clearError();
74
+ const next = form.providerRef === providerName ? '' : providerName;
75
+ form.setProviderRef(next);
194
76
  };
195
77
 
78
+ const saveLabel =
79
+ form.savePhase === 'done'
80
+ ? '保存成功'
81
+ : form.savePhase === 'restarting'
82
+ ? '重启服务中...'
83
+ : form.savePhase === 'saving'
84
+ ? '保存中...'
85
+ : '保存并重启';
86
+
196
87
  return (
197
- <Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
88
+ <Dialog
89
+ open={open}
90
+ onOpenChange={(nextOpen) => {
91
+ if (form.saving) return;
92
+ if (!nextOpen) onClose();
93
+ }}
94
+ >
198
95
  <DialogContent className="max-w-2xl">
199
96
  <DialogHeader>
200
97
  <DialogTitle>编辑团队</DialogTitle>
201
- <DialogDescription>修改团队名称、描述和 cc-connect 项目参数</DialogDescription>
98
+ <DialogDescription>修改团队名称、描述和运行参数</DialogDescription>
202
99
  </DialogHeader>
203
100
 
204
- <div className="space-y-3">
205
- <div>
206
- <label
207
- htmlFor="edit-team-name"
208
- className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
209
- >
210
- 名称
211
- </label>
212
- <input
213
- id="edit-team-name"
214
- type="text"
215
- value={name}
216
- onChange={(event) => {
217
- clearError();
218
- setName(event.target.value);
219
- }}
220
- onKeyDown={(event) => {
221
- if (event.key === 'Enter' && !saving && name.trim()) handleSave();
222
- }}
223
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
224
- placeholder="团队名称"
225
- />
226
- </div>
227
-
228
- <div>
229
- <label
230
- htmlFor="edit-team-description"
231
- className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
232
- >
233
- 描述
234
- </label>
235
- <textarea
236
- id="edit-team-description"
237
- value={description}
238
- onChange={(event) => {
239
- clearError();
240
- setDescription(event.target.value);
241
- }}
242
- rows={3}
243
- className="w-full resize-none rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
244
- placeholder="团队描述(可选)"
245
- />
246
- </div>
247
-
248
- <div className="space-y-3 rounded-md border border-[var(--color-border)] p-3">
101
+ <div className="space-y-4">
102
+ {/* ── Section 1: 基本信息 ─────────────────────────── */}
103
+ <FormSection title="基本信息">
104
+ <div>
105
+ <label htmlFor="edit-team-name" className={labelCls}>
106
+ 名称
107
+ </label>
108
+ <input
109
+ id="edit-team-name"
110
+ type="text"
111
+ value={form.name}
112
+ onChange={(e) => {
113
+ form.clearError();
114
+ form.setName(e.target.value);
115
+ }}
116
+ onKeyDown={(e) => {
117
+ if (e.key === 'Enter' && !form.saving && form.name.trim()) form.handleSave();
118
+ }}
119
+ className={inputCls}
120
+ placeholder="团队名称"
121
+ />
122
+ </div>
249
123
  <div>
250
- <h3 className="text-sm font-medium text-[var(--color-text)]">Agent 配置</h3>
251
- <p className="mt-0.5 text-xs text-[var(--color-text-muted)]">
252
- 这里直接维护 cc-connect 项目的基础运行参数。运行时模型和 Provider 请到 Harness
253
- 配置中管理。
254
- </p>
124
+ <label htmlFor="edit-team-description" className={labelCls}>
125
+ 描述
126
+ </label>
127
+ <textarea
128
+ id="edit-team-description"
129
+ value={form.description}
130
+ onChange={(e) => {
131
+ form.clearError();
132
+ form.setDescription(e.target.value);
133
+ }}
134
+ rows={2}
135
+ className={`${inputCls} resize-none`}
136
+ placeholder="团队描述(可选)"
137
+ />
255
138
  </div>
139
+ </FormSection>
256
140
 
141
+ {/* ── Section 2: Agent 配置 ───────────────────────── */}
142
+ <FormSection
143
+ title="Agent 配置"
144
+ description="运行参数配置。运行时模型和 Provider 请到 Harness 配置中管理。"
145
+ >
257
146
  <div className="grid gap-3 md:grid-cols-2">
258
147
  <div>
259
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
260
- Agent 类型
261
- </label>
262
- <select
263
- value={agentType}
264
- onChange={(event) => {
265
- clearError();
266
- setAgentType(event.target.value);
148
+ <label className={labelCls}>Agent 类型</label>
149
+ <HarnessSelect
150
+ value={form.agentType as CcAgentType}
151
+ onChange={(v) => {
152
+ form.clearError();
153
+ form.setAgentType(v);
267
154
  }}
268
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
269
- >
270
- {ALL_AGENT_TYPES.map((type) => (
271
- <option key={type} value={type}>
272
- {AGENT_TYPE_LABELS[type]}
273
- </option>
274
- ))}
275
- </select>
155
+ className="w-full"
156
+ />
276
157
  </div>
277
-
278
158
  <div>
279
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
280
- 权限模式
281
- </label>
159
+ <label className={labelCls}>权限模式</label>
282
160
  <select
283
- value={permissionMode}
284
- onChange={(event) => {
285
- clearError();
286
- setPermissionMode(event.target.value);
161
+ value={form.permissionMode}
162
+ onChange={(e) => {
163
+ form.clearError();
164
+ form.setPermissionMode(e.target.value);
287
165
  }}
288
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
166
+ className={inputCls}
289
167
  >
290
- {PERMISSION_MODE_OPTIONS.map((option) => (
291
- <option key={option.value} value={option.value}>
292
- {option.label}
168
+ {PERMISSION_MODE_OPTIONS.map((opt) => (
169
+ <option key={opt.value} value={opt.value}>
170
+ {opt.label}
293
171
  </option>
294
172
  ))}
295
173
  </select>
296
174
  </div>
297
175
  </div>
298
-
299
176
  <div>
300
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
301
- 工作目录
302
- </label>
177
+ <label className={labelCls}>工作目录</label>
303
178
  <input
304
179
  type="text"
305
- value={teamWorkDir}
306
- onChange={(event) => {
307
- clearError();
308
- setTeamWorkDir(event.target.value);
180
+ value={form.workDir}
181
+ onChange={(e) => {
182
+ form.clearError();
183
+ form.setWorkDir(e.target.value);
309
184
  }}
310
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 font-mono text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
185
+ className={`${inputCls} font-mono`}
311
186
  placeholder="/Users/you/code/project"
312
187
  />
313
188
  </div>
189
+ </FormSection>
314
190
 
191
+ {/* ── Section 3: 通信与平台 ───────────────────────── */}
192
+ <FormSection title="通信与平台">
315
193
  <div className="grid gap-3 md:grid-cols-2">
316
194
  <div>
317
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
318
- 语言
319
- </label>
195
+ <label className={labelCls}>语言</label>
320
196
  <input
321
197
  type="text"
322
- value={language}
323
- onChange={(event) => {
324
- clearError();
325
- setLanguage(event.target.value);
198
+ value={form.language}
199
+ onChange={(e) => {
200
+ form.clearError();
201
+ form.setLanguage(e.target.value);
326
202
  }}
327
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
203
+ className={inputCls}
328
204
  placeholder="zh"
329
205
  />
330
206
  </div>
331
-
332
207
  <div>
333
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
334
- 管理来源
335
- </label>
208
+ <label className={labelCls}>管理来源</label>
336
209
  <input
337
210
  type="text"
338
- value={managedSources}
339
- onChange={(event) => {
340
- clearError();
341
- setManagedSources(event.target.value);
211
+ value={form.managedSources}
212
+ onChange={(e) => {
213
+ form.clearError();
214
+ form.setManagedSources(e.target.value);
342
215
  }}
343
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
216
+ className={inputCls}
344
217
  placeholder="user1,user2 或 *"
345
218
  />
346
219
  </div>
347
220
  </div>
348
-
349
221
  <div>
350
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
351
- 已禁用命令
352
- </label>
222
+ <label className={labelCls}>平台访问控制(Feishu 允许的用户)</label>
353
223
  <input
354
224
  type="text"
355
- value={disabledCommandsInput}
356
- onChange={(event) => {
357
- clearError();
358
- setDisabledCommandsInput(event.target.value);
225
+ value={form.feishuAllowFrom}
226
+ onChange={(e) => {
227
+ form.clearError();
228
+ form.setFeishuAllowFrom(e.target.value);
359
229
  }}
360
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
361
- placeholder="restart, upgrade, cron"
230
+ className={inputCls}
231
+ placeholder="*"
362
232
  />
363
233
  </div>
234
+ </FormSection>
364
235
 
365
- <div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
366
- <div className="flex items-start justify-between gap-3">
367
- <div>
368
- <p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
369
- <p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
370
- 留空时使用本机 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType}{' '}
371
- 默认配置和登录状态。 只有需要给该团队指定模型供应商时,才绑定下面的全局
372
- Provider。
373
- </p>
374
- </div>
375
- {providerRefs ? (
376
- <button
377
- type="button"
378
- className="shrink-0 rounded-md border border-[var(--color-border)] px-2 py-1 text-[11px] text-[var(--color-text-muted)] hover:bg-white/5"
379
- onClick={() => setProviderRefs('')}
380
- >
381
- 使用本机默认
382
- </button>
383
- ) : null}
236
+ {/* ── Section 4: 高级开关 ─────────────────────────── */}
237
+ <div className="grid gap-2 md:grid-cols-3">
238
+ <label
239
+ htmlFor="edit-team-show-context-indicator"
240
+ className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
241
+ >
242
+ <Checkbox
243
+ id="edit-team-show-context-indicator"
244
+ checked={form.showContextIndicator}
245
+ onCheckedChange={(checked) => {
246
+ form.clearError();
247
+ form.setShowContextIndicator(checked === true);
248
+ }}
249
+ />
250
+ 上下文指示
251
+ </label>
252
+ <label
253
+ htmlFor="edit-team-reply-footer"
254
+ className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
255
+ >
256
+ <Checkbox
257
+ id="edit-team-reply-footer"
258
+ checked={form.replyFooter}
259
+ onCheckedChange={(checked) => {
260
+ form.clearError();
261
+ form.setReplyFooter(checked === true);
262
+ }}
263
+ />
264
+ 回复尾部信息
265
+ </label>
266
+ <label
267
+ htmlFor="edit-team-inject-sender"
268
+ className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
269
+ >
270
+ <Checkbox
271
+ id="edit-team-inject-sender"
272
+ checked={form.injectSender}
273
+ onCheckedChange={(checked) => {
274
+ form.clearError();
275
+ form.setInjectSender(checked === true);
276
+ }}
277
+ />
278
+ 注入发送者
279
+ </label>
280
+ </div>
281
+
282
+ {/* ── Section 5: Provider 绑定 ────────────────────── */}
283
+ <div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
284
+ <div className="flex items-start justify-between gap-3">
285
+ <div>
286
+ <p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
287
+ <p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
288
+ 留空时使用本机{' '}
289
+ {AGENT_TYPE_LABELS[form.agentType as CcAgentType] ?? form.agentType}{' '}
290
+ 默认配置和登录状态。只有需要给该团队指定模型供应商时,才绑定下面的全局 Provider。
291
+ </p>
384
292
  </div>
293
+ {form.providerRef ? (
294
+ <button
295
+ type="button"
296
+ className="shrink-0 rounded-md border border-[var(--color-border)] px-2 py-1 text-[11px] text-[var(--color-text-muted)] hover:bg-white/5"
297
+ onClick={() => form.setProviderRef('')}
298
+ >
299
+ 使用本机默认
300
+ </button>
301
+ ) : null}
302
+ </div>
385
303
 
386
- <div className="mt-3 space-y-2">
387
- {compatibleProviders.length > 0 ? (
388
- compatibleProviders.map((provider) => {
389
- const checked = providerRefs === provider.name;
390
- const at = agentType as CcAgentType;
391
- const endpoint = provider.endpoints?.[at] ?? provider.base_url ?? '默认端点';
392
- const model =
393
- provider.agent_models?.[at] ??
394
- provider.model ??
395
- provider.models?.[0]?.model ??
396
- '未指定模型';
397
- return (
398
- <button
399
- key={provider.name}
400
- type="button"
401
- onClick={() => toggleProviderRef(provider.name)}
402
- className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
403
- checked
404
- ? 'border-indigo-400/60 bg-indigo-500/10'
405
- : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
406
- }`}
407
- >
408
- <div className="flex items-center justify-between gap-3">
409
- <div className="min-w-0">
410
- <p className="truncate text-xs font-medium text-[var(--color-text)]">
411
- {provider.name}
412
- </p>
413
- <p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
414
- {model} · {endpoint}
415
- </p>
416
- </div>
417
- <span
418
- className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
419
- checked
420
- ? 'bg-indigo-400/20 text-indigo-200'
421
- : 'bg-white/5 text-[var(--color-text-muted)]'
422
- }`}
423
- >
424
- {checked ? '已绑定' : '可绑定'}
425
- </span>
304
+ <div className="mt-3 space-y-2">
305
+ {form.compatibleProviders.length > 0 ? (
306
+ form.compatibleProviders.map((provider) => {
307
+ const checked = form.providerRef === provider.name;
308
+ const at = form.agentType as CcAgentType;
309
+ const endpoint = provider.endpoints?.[at] ?? provider.base_url ?? '默认端点';
310
+ const model =
311
+ provider.agent_models?.[at] ??
312
+ provider.model ??
313
+ provider.models?.[0]?.model ??
314
+ '未指定模型';
315
+ return (
316
+ <button
317
+ key={provider.name}
318
+ type="button"
319
+ onClick={() => toggleProviderRef(provider.name)}
320
+ className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
321
+ checked
322
+ ? 'border-indigo-400/60 bg-indigo-500/10'
323
+ : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
324
+ }`}
325
+ >
326
+ <div className="flex items-center justify-between gap-3">
327
+ <div className="min-w-0">
328
+ <p className="truncate text-xs font-medium text-[var(--color-text)]">
329
+ {provider.name}
330
+ </p>
331
+ <p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
332
+ {model} · {endpoint}
333
+ </p>
426
334
  </div>
427
- </button>
428
- );
429
- })
430
- ) : (
431
- <div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
432
- 暂无适用于 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType} 的全局
433
- Provider。 可先在「设置 → Harness 配置」中添加;不添加也会使用本机默认登录态。
434
- </div>
435
- )}
436
- </div>
335
+ <span
336
+ className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
337
+ checked
338
+ ? 'bg-indigo-400/20 text-indigo-200'
339
+ : 'bg-white/5 text-[var(--color-text-muted)]'
340
+ }`}
341
+ >
342
+ {checked ? '已绑定' : '可绑定'}
343
+ </span>
344
+ </div>
345
+ </button>
346
+ );
347
+ })
348
+ ) : (
349
+ <div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
350
+ 暂无适用于 {AGENT_TYPE_LABELS[form.agentType as CcAgentType] ?? form.agentType}{' '}
351
+ 的全局 Provider。可先在「设置 → Harness
352
+ 配置」中添加;不添加也会使用本机默认登录态。
353
+ </div>
354
+ )}
437
355
  </div>
356
+ </div>
438
357
 
358
+ {/* ── Section 6: 危险操作 ─────────────────────────── */}
359
+ <FormSection title="危险操作" variant="danger">
439
360
  <div>
440
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
441
- 平台访问控制(Feishu 允许的用户)
442
- </label>
361
+ <label className={labelCls}>已禁用命令</label>
443
362
  <input
444
363
  type="text"
445
- value={feishuAllowFrom}
446
- onChange={(event) => {
447
- clearError();
448
- setFeishuAllowFrom(event.target.value);
364
+ value={form.disabledCommandsInput}
365
+ onChange={(e) => {
366
+ form.clearError();
367
+ form.setDisabledCommandsInput(e.target.value);
449
368
  }}
450
- className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
451
- placeholder="*"
369
+ className={inputCls}
370
+ placeholder="restart, upgrade, cron"
452
371
  />
453
372
  </div>
454
-
455
- <div className="grid gap-2 md:grid-cols-3">
456
- <label
457
- htmlFor="edit-team-show-context-indicator"
458
- className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
459
- >
460
- <Checkbox
461
- id="edit-team-show-context-indicator"
462
- checked={showContextIndicator}
463
- onCheckedChange={(checked) => {
464
- clearError();
465
- setShowContextIndicator(checked === true);
466
- }}
467
- />
468
- 上下文指示
469
- </label>
470
- <label
471
- htmlFor="edit-team-reply-footer"
472
- className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
473
- >
474
- <Checkbox
475
- id="edit-team-reply-footer"
476
- checked={replyFooter}
477
- onCheckedChange={(checked) => {
478
- clearError();
479
- setReplyFooter(checked === true);
480
- }}
481
- />
482
- 回复尾部信息
483
- </label>
484
- <label
485
- htmlFor="edit-team-inject-sender"
486
- className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
487
- >
488
- <Checkbox
489
- id="edit-team-inject-sender"
490
- checked={injectSender}
491
- onCheckedChange={(checked) => {
492
- clearError();
493
- setInjectSender(checked === true);
494
- }}
495
- />
496
- 注入发送者
497
- </label>
498
- </div>
499
-
500
- {onDeleteTeam && (
501
- <div className="rounded-md border border-red-500/40 bg-red-500/5 p-3">
502
- <p className="text-xs font-medium text-red-300">危险操作</p>
503
- <p className="mt-1 text-xs text-[var(--color-text-muted)]">
373
+ {onDeleteTeam && form.canDelete && (
374
+ <>
375
+ <p className="text-xs text-[var(--color-text-muted)]">
504
376
  删除项目会将团队从当前控制面板移除。
505
377
  </p>
506
378
  <Button
507
379
  type="button"
508
380
  variant="destructive"
509
381
  size="sm"
510
- className="mt-2"
511
382
  onClick={() => {
512
383
  onClose();
513
384
  window.setTimeout(onDeleteTeam, 0);
@@ -516,27 +387,25 @@ export const EditTeamDialog = ({
516
387
  <Trash2 size={14} className="mr-1.5" />
517
388
  删除项目
518
389
  </Button>
519
- </div>
390
+ </>
520
391
  )}
521
- </div>
392
+ </FormSection>
522
393
 
523
- {isTeamProvisioning ? (
524
- <p className="text-xs text-amber-300">团队仍在启动准备中,启动完成前暂时锁定编辑。</p>
525
- ) : null}
526
- {error ? <p className="text-xs text-red-400">{error}</p> : null}
394
+ {/* ── Status messages ──────────────────────────────── */}
395
+ {form.error && <p className="text-xs text-red-400">{form.error}</p>}
527
396
  </div>
528
397
 
529
398
  <DialogFooter>
530
- <Button variant="outline" size="sm" onClick={onClose} disabled={saving}>
531
- 取消
399
+ <Button variant="outline" size="sm" onClick={onClose} disabled={form.saving}>
400
+ {form.savePhase === 'done' ? '关闭' : '取消'}
532
401
  </Button>
533
402
  <Button
534
403
  size="sm"
535
- onClick={handleSave}
536
- disabled={saving || isTeamProvisioning || !name.trim()}
404
+ onClick={form.handleSave}
405
+ disabled={form.saving || form.savePhase === 'done' || !form.name.trim()}
537
406
  >
538
- {saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
539
- 保存
407
+ {form.saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
408
+ {saveLabel}
540
409
  </Button>
541
410
  </DialogFooter>
542
411
  </DialogContent>