@yancyyu/openhermit 1.6.30 → 1.6.32

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 (70) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-DsQt4FHy.js → ProjectEditorOverlay-DwWYwUf8.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-BjZC53xf.js → TeamGraphOverlay-BK6PUN3W.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CrWocIjq.js → _basePickBy-DdNwxEcj.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-B6d8ysWi.js → _baseUniq-BKO88SUv.js} +1 -1
  5. package/dist-renderer/assets/{arc-DAIYCFP8.js → arc-8OCcRtx5.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-B3UudXJh.js → architectureDiagram-VXUJARFQ-BaVzqHNU.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-DbptKQ4W.js → blockDiagram-VD42YOAC-BlD3aS2M.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-C4WQuZpV.js → c4Diagram-YG6GDRKO-CWhysgWg.js} +1 -1
  9. package/dist-renderer/assets/channel-VSASRd7w.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-Dp7fVpI_.js → chunk-4BX2VUAB-39vXfQp7.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-B8KGfbAy.js → chunk-55IACEB6-uCvPl6T8.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-BG1oJrjA.js → chunk-B4BG7PRW-BrGj559B.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DRmxNjht.js → chunk-DI55MBZ5-Djfr1KmT.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-D6VLvy16.js → chunk-FMBD7UC4-Cv2iCCiq.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DZou1667.js → chunk-QN33PNHL-CXzDZbbd.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-CghmasSh.js → chunk-QZHKN3VN-CRO6vWKS.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-B7apcMPK.js → chunk-TZMSLE5B-D5Luxvqw.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CZKOiI95.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CZKOiI95.js +1 -0
  20. package/dist-renderer/assets/clone-CEVt7zS8.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-05e5uQDp.js → cose-bilkent-S5V4N54A-BrIj9YHY.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-B06bRykF.js → dagre-6UL2VRFP-BFPvK4JJ.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-CY7VYQ7c.js → diagram-PSM6KHXK-CTyUB42n.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-BjKEH7dD.js → diagram-QEK2KX5R-V9DMzwyh.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-Bf4ELS1_.js → diagram-S2PKOQOG-D8fWNJWp.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-DJ753_L9.js → erDiagram-Q2GNP2WA-C69oTboq.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B71S-lC-.js → flowDiagram-NV44I4VS-C8MnYH3t.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-C_U42mSZ.js → ganttDiagram-JELNMOA3-DnRfBK-T.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js → gitGraphDiagram-V2S2FVAM-BzUxkq9s.js} +1 -1
  30. package/dist-renderer/assets/{graph-DY3qbzqj.js → graph-DUpjXmIN.js} +1 -1
  31. package/dist-renderer/assets/{index-C8B_nKOF.js → index-BnyC9eDn.js} +1 -1
  32. package/dist-renderer/assets/{index-BlOrAXp3.js → index-CP1a4BYJ.js} +569 -569
  33. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  34. package/dist-renderer/assets/{index-Bs27J5gB.js → index-CWP6WvZl.js} +1 -1
  35. package/dist-renderer/assets/{index-DLKyDr4T.js → index-D_f0E90u.js} +1 -1
  36. package/dist-renderer/assets/{index-Dhsk3_DD.js → index-F0-beTLg.js} +1 -1
  37. package/dist-renderer/assets/{index-GpUvV2xs.js → index-pIDl3FPP.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-BNs0y3IG.js → infoDiagram-HS3SLOUP-B43WMCmB.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-CqPnw4UV.js → journeyDiagram-XKPGCS4Q-DjfJCd1x.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-SLlzcUJ2.js → kanban-definition-3W4ZIXB7-CjRQqAUU.js} +1 -1
  41. package/dist-renderer/assets/{layout-BZLlNmbr.js → layout-DmcOdDIQ.js} +1 -1
  42. package/dist-renderer/assets/{linear-qz6v45xy.js → linear-DP0CqQZK.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-B1-kmEWV.js → mindmap-definition-VGOIOE7T-BMvf_dPT.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B8a02iNx.js → pieDiagram-ADFJNKIX-C2eg65Te.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BKv1Xfou.js → quadrantDiagram-AYHSOK5B-DsWbG3ez.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-B3DUpZi2.js → requirementDiagram-UZGBJVZJ-DU1uyMjP.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-DmPzuTsy.js → sankeyDiagram-TZEHDZUN-DJVBJwOK.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-Bo7RelRb.js → sequenceDiagram-WL72ISMW-CfRnIrs0.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-1epX98gV.js → stateDiagram-FKZM4ZOC-cNHRId0N.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-03Ym9PTr.js → stateDiagram-v2-4FDKWEC3-SBUDuLpP.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-r6isC62H.js → timeline-definition-IT6M3QCI-PwVrIKR_.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-Dtx0XOre.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-t4-rwdAw.js → xychartDiagram-PRI3JC2R-CqS3H24Y.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +1 -1
  56. package/src/renderer/components/extensions/ExtensionStoreView.tsx +4 -1
  57. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +100 -15
  58. package/src/renderer/components/team/TeamDetailView.tsx +55 -139
  59. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  60. package/src/renderer/components/team/TeamListView.tsx +7 -32
  61. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +283 -409
  62. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +280 -0
  63. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  64. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  65. package/dist-renderer/assets/channel-DbjZvWii.js +0 -1
  66. package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +0 -1
  67. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +0 -1
  68. package/dist-renderer/assets/clone-CJ1kxO2J.js +0 -1
  69. package/dist-renderer/assets/index-CmZPUEhS.css +0 -1
  70. package/dist-renderer/assets/treemap-GDKQZRPO-CGKpOUF2.js +0 -162
@@ -1,8 +1,5 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect } from 'react';
2
2
 
3
- import { api } from '@renderer/api';
4
- import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
5
- import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
6
3
  import { Button } from '@renderer/components/ui/button';
7
4
  import { Checkbox } from '@renderer/components/ui/checkbox';
8
5
  import {
@@ -13,496 +10,375 @@ import {
13
10
  DialogHeader,
14
11
  DialogTitle,
15
12
  } from '@renderer/components/ui/dialog';
13
+ import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
14
+ import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
16
15
  import { Loader2, Trash2 } from 'lucide-react';
17
16
 
18
- import type { ResolvedTeamMember } from '@shared/types';
19
17
  import type { CcAgentType } from '@shared/types/ccConnect';
20
- import type { GlobalProvider } from '@shared/types/providers';
18
+
19
+ import { PERMISSION_MODE_OPTIONS, useTeamEditForm } from './useTeamEditForm';
21
20
 
22
21
  interface EditTeamDialogProps {
23
22
  open: boolean;
24
23
  teamName: string;
25
- currentName: string;
26
- currentDescription: string;
27
- currentColor: string;
28
- currentAgentType?: string;
29
- currentWorkDir?: string;
30
- currentPermissionMode?: string;
31
- currentLanguage?: string;
32
- currentShowContextIndicator?: boolean;
33
- currentReplyFooter?: boolean;
34
- currentInjectSender?: boolean;
35
- currentManagedSources?: string;
36
- currentDisabledCommands?: string[];
37
- currentPlatformAllowFrom?: Record<string, string>;
38
- currentProviderRefs?: string[];
39
- globalProviders?: GlobalProvider[];
40
- currentMembers: ResolvedTeamMember[];
41
- leadMember?: ResolvedTeamMember | null;
42
- resolvedMemberColorMap?: ReadonlyMap<string, string>;
43
- isTeamAlive?: boolean;
44
- isTeamProvisioning?: boolean;
45
- projectPath?: string | null;
46
- /** Deprecated in cc-connect mode: runtime edits are managed from Harness configuration. */
47
- savedLaunchRequest?: unknown;
48
24
  onClose: () => void;
49
- onSaved: () => Promise<void> | void;
50
- onRestartTeam?: () => Promise<void> | void;
51
25
  onDeleteTeam?: () => void;
52
26
  }
53
27
 
54
- const PERMISSION_MODE_OPTIONS = [
55
- { value: 'default', label: '默认' },
56
- { value: 'acceptEdits', label: '自动接受编辑' },
57
- { value: 'bypassPermissions', label: '跳过权限确认' },
58
- { value: 'plan', label: '计划模式' },
59
- ] 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)]';
58
+
59
+ const labelCls = 'mb-1 block text-xs font-medium text-[var(--color-text-secondary)]';
60
60
 
61
+ // ── Main component ───────────────────────────────────────────
61
62
  export const EditTeamDialog = ({
62
63
  open,
63
64
  teamName,
64
- currentName,
65
- currentDescription,
66
- currentColor,
67
- currentAgentType = 'cursor',
68
- currentWorkDir = '',
69
- currentPermissionMode = 'default',
70
- currentLanguage = 'zh',
71
- currentShowContextIndicator = true,
72
- currentReplyFooter = true,
73
- currentInjectSender = false,
74
- currentManagedSources = '*',
75
- currentDisabledCommands = [],
76
- currentPlatformAllowFrom = {},
77
- currentProviderRefs = [],
78
- globalProviders = [],
79
- isTeamAlive = false,
80
- isTeamProvisioning = false,
81
65
  onClose,
82
- onSaved,
83
- onRestartTeam,
84
66
  onDeleteTeam,
85
67
  }: EditTeamDialogProps): React.JSX.Element => {
86
- const [name, setName] = useState(currentName);
87
- const [description, setDescription] = useState(currentDescription);
88
- const [agentType, setAgentType] = useState(currentAgentType);
89
- const [teamWorkDir, setTeamWorkDir] = useState(currentWorkDir);
90
- const [permissionMode, setPermissionMode] = useState(currentPermissionMode);
91
- const [language, setLanguage] = useState(currentLanguage);
92
- const [showContextIndicator, setShowContextIndicator] = useState(currentShowContextIndicator);
93
- const [replyFooter, setReplyFooter] = useState(currentReplyFooter);
94
- const [injectSender, setInjectSender] = useState(currentInjectSender);
95
- const [managedSources, setManagedSources] = useState(currentManagedSources);
96
- const [disabledCommandsInput, setDisabledCommandsInput] = useState(
97
- currentDisabledCommands.join(', ')
98
- );
99
- const [feishuAllowFrom, setFeishuAllowFrom] = useState(currentPlatformAllowFrom.feishu ?? '*');
100
- const [providerRefs, setProviderRefs] = useState<string>(currentProviderRefs[0] ?? '');
101
- const [saving, setSaving] = useState(false);
102
- const [error, setError] = useState<string | null>(null);
68
+ const form = useTeamEditForm(teamName, open);
103
69
 
104
- useEffect(() => {
105
- if (!open) return;
106
- setName(currentName);
107
- setDescription(currentDescription);
108
- setAgentType(currentAgentType);
109
- setTeamWorkDir(currentWorkDir);
110
- setPermissionMode(currentPermissionMode);
111
- setLanguage(currentLanguage);
112
- setShowContextIndicator(currentShowContextIndicator);
113
- setReplyFooter(currentReplyFooter);
114
- setInjectSender(currentInjectSender);
115
- setManagedSources(currentManagedSources);
116
- setDisabledCommandsInput(currentDisabledCommands.join(', '));
117
- setFeishuAllowFrom(currentPlatformAllowFrom.feishu ?? '*');
118
- setProviderRefs(currentProviderRefs[0] ?? '');
119
- setError(null);
120
- }, [
121
- open,
122
- currentName,
123
- currentDescription,
124
- currentAgentType,
125
- currentWorkDir,
126
- currentPermissionMode,
127
- currentLanguage,
128
- currentShowContextIndicator,
129
- currentReplyFooter,
130
- currentInjectSender,
131
- currentManagedSources,
132
- currentDisabledCommands,
133
- currentPlatformAllowFrom,
134
- currentProviderRefs,
135
- ]);
136
-
137
- const clearError = (): void => setError(null);
138
-
139
- const handleSave = (): void => {
140
- if (!name.trim()) {
141
- setError('团队名称不能为空');
142
- return;
143
- }
144
- if (isTeamProvisioning) {
145
- setError('团队仍在启动准备中,暂时不能编辑设置。请等待启动完成后再试。');
146
- return;
147
- }
148
-
149
- const disabledCommands = disabledCommandsInput
150
- .split(',')
151
- .map((entry) => entry.trim())
152
- .filter((entry) => entry.length > 0);
153
- const feishu = feishuAllowFrom.trim();
154
-
155
- setSaving(true);
156
- setError(null);
157
- void (async () => {
158
- try {
159
- await api.teams.updateConfig(teamName, {
160
- name: name.trim(),
161
- description: description.trim(),
162
- color: currentColor,
163
- agentType: agentType.trim() || undefined,
164
- workDir: teamWorkDir.trim() || undefined,
165
- permissionMode: permissionMode.trim() || undefined,
166
- showContextIndicator,
167
- replyFooter,
168
- injectSender,
169
- language: language.trim() || undefined,
170
- managedSources: managedSources.trim() || undefined,
171
- disabledCommands,
172
- platformAllowFrom: feishu ? { feishu } : {},
173
- providerRefs: providerRefs ? [providerRefs] : [],
174
- });
175
- await Promise.resolve(onSaved());
176
- onClose();
177
- } catch (err) {
178
- setError(err instanceof Error ? err.message : '保存失败');
179
- } finally {
180
- setSaving(false);
181
- }
182
- })();
183
- };
184
-
185
- const compatibleProviders = globalProviders.filter(
186
- (provider) =>
187
- !provider.agent_types ||
188
- provider.agent_types.length === 0 ||
189
- (provider.agent_types as string[]).includes(agentType)
190
- );
70
+ // No auto-close — user closes manually after seeing "保存成功"
191
71
 
192
72
  const toggleProviderRef = (providerName: string): void => {
193
- clearError();
194
- setProviderRefs((prev) => (prev === providerName ? '' : providerName));
73
+ form.clearError();
74
+ const next = form.providerRef === providerName ? '' : providerName;
75
+ form.setProviderRef(next);
195
76
  };
196
77
 
78
+ const saveLabel =
79
+ form.savePhase === 'done'
80
+ ? '保存成功'
81
+ : form.savePhase === 'restarting'
82
+ ? '重启服务中...'
83
+ : form.savePhase === 'saving'
84
+ ? '保存中...'
85
+ : '保存并重启';
86
+
197
87
  return (
198
- <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
+ >
199
95
  <DialogContent className="max-w-2xl">
200
96
  <DialogHeader>
201
97
  <DialogTitle>编辑团队</DialogTitle>
202
- <DialogDescription>修改团队名称、描述和 cc-connect 项目参数</DialogDescription>
98
+ <DialogDescription>修改团队名称、描述和运行参数</DialogDescription>
203
99
  </DialogHeader>
204
100
 
205
- <div className="space-y-3">
206
- <div>
207
- <label
208
- htmlFor="edit-team-name"
209
- className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
210
- >
211
- 名称
212
- </label>
213
- <input
214
- id="edit-team-name"
215
- type="text"
216
- value={name}
217
- onChange={(event) => {
218
- clearError();
219
- setName(event.target.value);
220
- }}
221
- onKeyDown={(event) => {
222
- if (event.key === 'Enter' && !saving && name.trim()) handleSave();
223
- }}
224
- 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)]"
225
- placeholder="团队名称"
226
- />
227
- </div>
228
-
229
- <div>
230
- <label
231
- htmlFor="edit-team-description"
232
- className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
233
- >
234
- 描述
235
- </label>
236
- <textarea
237
- id="edit-team-description"
238
- value={description}
239
- onChange={(event) => {
240
- clearError();
241
- setDescription(event.target.value);
242
- }}
243
- rows={3}
244
- 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)]"
245
- placeholder="团队描述(可选)"
246
- />
247
- </div>
248
-
249
- <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="基本信息">
250
104
  <div>
251
- <h3 className="text-sm font-medium text-[var(--color-text)]">Agent 配置</h3>
252
- <p className="mt-0.5 text-xs text-[var(--color-text-muted)]">
253
- 这里直接维护 cc-connect 项目的基础运行参数。运行时模型和 Provider 请到 Harness
254
- 配置中管理。
255
- </p>
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
+ />
256
122
  </div>
123
+ <div>
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
+ />
138
+ </div>
139
+ </FormSection>
257
140
 
141
+ {/* ── Section 2: Agent 配置 ───────────────────────── */}
142
+ <FormSection
143
+ title="Agent 配置"
144
+ description="运行参数配置。运行时模型和 Provider 请到 Harness 配置中管理。"
145
+ >
258
146
  <div className="grid gap-3 md:grid-cols-2">
259
147
  <div>
260
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
261
- Agent 类型
262
- </label>
148
+ <label className={labelCls}>Agent 类型</label>
263
149
  <HarnessSelect
264
- value={agentType as CcAgentType}
150
+ value={form.agentType as CcAgentType}
265
151
  onChange={(v) => {
266
- clearError();
267
- setAgentType(v);
152
+ form.clearError();
153
+ form.setAgentType(v);
268
154
  }}
269
155
  className="w-full"
270
156
  />
271
157
  </div>
272
-
273
158
  <div>
274
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
275
- 权限模式
276
- </label>
159
+ <label className={labelCls}>权限模式</label>
277
160
  <select
278
- value={permissionMode}
279
- onChange={(event) => {
280
- clearError();
281
- setPermissionMode(event.target.value);
161
+ value={form.permissionMode}
162
+ onChange={(e) => {
163
+ form.clearError();
164
+ form.setPermissionMode(e.target.value);
282
165
  }}
283
- 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}
284
167
  >
285
- {PERMISSION_MODE_OPTIONS.map((option) => (
286
- <option key={option.value} value={option.value}>
287
- {option.label}
168
+ {PERMISSION_MODE_OPTIONS.map((opt) => (
169
+ <option key={opt.value} value={opt.value}>
170
+ {opt.label}
288
171
  </option>
289
172
  ))}
290
173
  </select>
291
174
  </div>
292
175
  </div>
293
-
294
176
  <div>
295
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
296
- 工作目录
297
- </label>
177
+ <label className={labelCls}>工作目录</label>
298
178
  <input
299
179
  type="text"
300
- value={teamWorkDir}
301
- onChange={(event) => {
302
- clearError();
303
- setTeamWorkDir(event.target.value);
180
+ value={form.workDir}
181
+ onChange={(e) => {
182
+ form.clearError();
183
+ form.setWorkDir(e.target.value);
304
184
  }}
305
- 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`}
306
186
  placeholder="/Users/you/code/project"
307
187
  />
308
188
  </div>
189
+ </FormSection>
309
190
 
191
+ {/* ── Section 3: 通信与平台 ───────────────────────── */}
192
+ <FormSection title="通信与平台">
310
193
  <div className="grid gap-3 md:grid-cols-2">
311
194
  <div>
312
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
313
- 语言
314
- </label>
195
+ <label className={labelCls}>语言</label>
315
196
  <input
316
197
  type="text"
317
- value={language}
318
- onChange={(event) => {
319
- clearError();
320
- setLanguage(event.target.value);
198
+ value={form.language}
199
+ onChange={(e) => {
200
+ form.clearError();
201
+ form.setLanguage(e.target.value);
321
202
  }}
322
- 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}
323
204
  placeholder="zh"
324
205
  />
325
206
  </div>
326
-
327
207
  <div>
328
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
329
- 管理来源
330
- </label>
208
+ <label className={labelCls}>管理来源</label>
331
209
  <input
332
210
  type="text"
333
- value={managedSources}
334
- onChange={(event) => {
335
- clearError();
336
- setManagedSources(event.target.value);
211
+ value={form.managedSources}
212
+ onChange={(e) => {
213
+ form.clearError();
214
+ form.setManagedSources(e.target.value);
337
215
  }}
338
- 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}
339
217
  placeholder="user1,user2 或 *"
340
218
  />
341
219
  </div>
342
220
  </div>
343
-
344
221
  <div>
345
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
346
- 已禁用命令
347
- </label>
222
+ <label className={labelCls}>平台访问控制(Feishu 允许的用户)</label>
348
223
  <input
349
224
  type="text"
350
- value={disabledCommandsInput}
351
- onChange={(event) => {
352
- clearError();
353
- setDisabledCommandsInput(event.target.value);
225
+ value={form.feishuAllowFrom}
226
+ onChange={(e) => {
227
+ form.clearError();
228
+ form.setFeishuAllowFrom(e.target.value);
354
229
  }}
355
- 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)]"
356
- placeholder="restart, upgrade, cron"
230
+ className={inputCls}
231
+ placeholder="*"
357
232
  />
358
233
  </div>
234
+ </FormSection>
359
235
 
360
- <div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
361
- <div className="flex items-start justify-between gap-3">
362
- <div>
363
- <p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
364
- <p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
365
- 留空时使用本机 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType}{' '}
366
- 默认配置和登录状态。 只有需要给该团队指定模型供应商时,才绑定下面的全局
367
- Provider。
368
- </p>
369
- </div>
370
- {providerRefs ? (
371
- <button
372
- type="button"
373
- 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"
374
- onClick={() => setProviderRefs('')}
375
- >
376
- 使用本机默认
377
- </button>
378
- ) : 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>
379
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>
380
303
 
381
- <div className="mt-3 space-y-2">
382
- {compatibleProviders.length > 0 ? (
383
- compatibleProviders.map((provider) => {
384
- const checked = providerRefs === provider.name;
385
- const at = agentType as CcAgentType;
386
- const endpoint = provider.endpoints?.[at] ?? provider.base_url ?? '默认端点';
387
- const model =
388
- provider.agent_models?.[at] ??
389
- provider.model ??
390
- provider.models?.[0]?.model ??
391
- '未指定模型';
392
- return (
393
- <button
394
- key={provider.name}
395
- type="button"
396
- onClick={() => toggleProviderRef(provider.name)}
397
- className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
398
- checked
399
- ? 'border-indigo-400/60 bg-indigo-500/10'
400
- : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
401
- }`}
402
- >
403
- <div className="flex items-center justify-between gap-3">
404
- <div className="min-w-0">
405
- <p className="truncate text-xs font-medium text-[var(--color-text)]">
406
- {provider.name}
407
- </p>
408
- <p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
409
- {model} · {endpoint}
410
- </p>
411
- </div>
412
- <span
413
- className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
414
- checked
415
- ? 'bg-indigo-400/20 text-indigo-200'
416
- : 'bg-white/5 text-[var(--color-text-muted)]'
417
- }`}
418
- >
419
- {checked ? '已绑定' : '可绑定'}
420
- </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>
421
334
  </div>
422
- </button>
423
- );
424
- })
425
- ) : (
426
- <div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
427
- 暂无适用于 {AGENT_TYPE_LABELS[agentType as CcAgentType] ?? agentType} 的全局
428
- Provider。 可先在「设置 → Harness 配置」中添加;不添加也会使用本机默认登录态。
429
- </div>
430
- )}
431
- </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
+ )}
432
355
  </div>
356
+ </div>
433
357
 
358
+ {/* ── Section 6: 危险操作 ─────────────────────────── */}
359
+ <FormSection title="危险操作" variant="danger">
434
360
  <div>
435
- <label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
436
- 平台访问控制(Feishu 允许的用户)
437
- </label>
361
+ <label className={labelCls}>已禁用命令</label>
438
362
  <input
439
363
  type="text"
440
- value={feishuAllowFrom}
441
- onChange={(event) => {
442
- clearError();
443
- setFeishuAllowFrom(event.target.value);
364
+ value={form.disabledCommandsInput}
365
+ onChange={(e) => {
366
+ form.clearError();
367
+ form.setDisabledCommandsInput(e.target.value);
444
368
  }}
445
- 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)]"
446
- placeholder="*"
369
+ className={inputCls}
370
+ placeholder="restart, upgrade, cron"
447
371
  />
448
372
  </div>
449
-
450
- <div className="grid gap-2 md:grid-cols-3">
451
- <label
452
- htmlFor="edit-team-show-context-indicator"
453
- 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)]"
454
- >
455
- <Checkbox
456
- id="edit-team-show-context-indicator"
457
- checked={showContextIndicator}
458
- onCheckedChange={(checked) => {
459
- clearError();
460
- setShowContextIndicator(checked === true);
461
- }}
462
- />
463
- 上下文指示
464
- </label>
465
- <label
466
- htmlFor="edit-team-reply-footer"
467
- 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)]"
468
- >
469
- <Checkbox
470
- id="edit-team-reply-footer"
471
- checked={replyFooter}
472
- onCheckedChange={(checked) => {
473
- clearError();
474
- setReplyFooter(checked === true);
475
- }}
476
- />
477
- 回复尾部信息
478
- </label>
479
- <label
480
- htmlFor="edit-team-inject-sender"
481
- 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)]"
482
- >
483
- <Checkbox
484
- id="edit-team-inject-sender"
485
- checked={injectSender}
486
- onCheckedChange={(checked) => {
487
- clearError();
488
- setInjectSender(checked === true);
489
- }}
490
- />
491
- 注入发送者
492
- </label>
493
- </div>
494
-
495
- {onDeleteTeam && (
496
- <div className="rounded-md border border-red-500/40 bg-red-500/5 p-3">
497
- <p className="text-xs font-medium text-red-300">危险操作</p>
498
- <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)]">
499
376
  删除项目会将团队从当前控制面板移除。
500
377
  </p>
501
378
  <Button
502
379
  type="button"
503
380
  variant="destructive"
504
381
  size="sm"
505
- className="mt-2"
506
382
  onClick={() => {
507
383
  onClose();
508
384
  window.setTimeout(onDeleteTeam, 0);
@@ -511,27 +387,25 @@ export const EditTeamDialog = ({
511
387
  <Trash2 size={14} className="mr-1.5" />
512
388
  删除项目
513
389
  </Button>
514
- </div>
390
+ </>
515
391
  )}
516
- </div>
392
+ </FormSection>
517
393
 
518
- {isTeamProvisioning ? (
519
- <p className="text-xs text-amber-300">团队仍在启动准备中,启动完成前暂时锁定编辑。</p>
520
- ) : null}
521
- {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>}
522
396
  </div>
523
397
 
524
398
  <DialogFooter>
525
- <Button variant="outline" size="sm" onClick={onClose} disabled={saving}>
526
- 取消
399
+ <Button variant="outline" size="sm" onClick={onClose} disabled={form.saving}>
400
+ {form.savePhase === 'done' ? '关闭' : '取消'}
527
401
  </Button>
528
402
  <Button
529
403
  size="sm"
530
- onClick={handleSave}
531
- disabled={saving || isTeamProvisioning || !name.trim()}
404
+ onClick={form.handleSave}
405
+ disabled={form.saving || form.savePhase === 'done' || !form.name.trim()}
532
406
  >
533
- {saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
534
- 保存
407
+ {form.saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
408
+ {saveLabel}
535
409
  </Button>
536
410
  </DialogFooter>
537
411
  </DialogContent>