@yancyyu/openhermit 1.6.25 → 1.6.26

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 (74) hide show
  1. package/README.md +4 -0
  2. package/bin/hermit.mjs +151 -62
  3. package/dist-renderer/assets/{ProjectEditorOverlay-CZ1LI0pd.js → ProjectEditorOverlay-Byepdwo2.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-DvyxOPvU.js → TeamGraphOverlay-vvWu-2c9.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-D8IKEBh4.js → _basePickBy-DfsmMgXN.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BDBpSAHY.js → _baseUniq-Bve-IKz5.js} +1 -1
  7. package/dist-renderer/assets/{arc-CZagJLek.js → arc-4cbkhagw.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Cq083Uu6.js → architectureDiagram-VXUJARFQ-CC9i0bMK.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-CIEANvSv.js → blockDiagram-VD42YOAC-BjFruJ65.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Ca4h_BlA.js → c4Diagram-YG6GDRKO-CrYzsQC1.js} +1 -1
  11. package/dist-renderer/assets/channel-BMMyVRy4.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-DW9HjNgA.js → chunk-4BX2VUAB-Bb9MCt7J.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-BddOEwBk.js → chunk-55IACEB6-BpOVOXVa.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-Cmu8IjLN.js → chunk-B4BG7PRW-GtEiO-7n.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-BU7nEH8e.js → chunk-DI55MBZ5-BRlzcOEj.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-BZNeq0CB.js → chunk-FMBD7UC4-DcvMVOZx.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-DMb3gYzN.js → chunk-QN33PNHL-B9pkjVpd.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-8wSSvqz0.js → chunk-QZHKN3VN-DzHPSm01.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-B4OLgw45.js → chunk-TZMSLE5B-BU9c0Hcn.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-Dz1VG1T3.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-Dz1VG1T3.js +1 -0
  22. package/dist-renderer/assets/clone-COsIIGZQ.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CS9moNeT.js → cose-bilkent-S5V4N54A-BqOg0x3V.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-3pzdMwjZ.js → dagre-6UL2VRFP-C9JTWefj.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-CGTnMY5C.js → diagram-PSM6KHXK-ljleG6ui.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-Cyzp8dOw.js → diagram-QEK2KX5R-BbV-WSTr.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-D_5SvdXI.js → diagram-S2PKOQOG-CKi3DFby.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-bWPYfs9c.js → erDiagram-Q2GNP2WA-D3HE7b-j.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-C5z47CKB.js → flowDiagram-NV44I4VS-C2yLRmM0.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-eye9Cv6S.js → ganttDiagram-JELNMOA3-XEV4KtUf.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DekQZoF8.js → gitGraphDiagram-V2S2FVAM-ufaCCg7c.js} +1 -1
  32. package/dist-renderer/assets/{graph-kNnl-9Fl.js → graph-BzPvdBp0.js} +1 -1
  33. package/dist-renderer/assets/{index-CrOkTuIK.js → index-A5CMVuXA.js} +525 -525
  34. package/dist-renderer/assets/{index-CI2l57ID.js → index-BprOls_t.js} +1 -1
  35. package/dist-renderer/assets/index-CWpFqEvz.css +1 -0
  36. package/dist-renderer/assets/{index-DB0k9yRL.js → index-Cr91T9ef.js} +1 -1
  37. package/dist-renderer/assets/{index-CnxDIJh8.js → index-DHq6dXy7.js} +1 -1
  38. package/dist-renderer/assets/{index-ATiHUmmE.js → index-DUIDxnaf.js} +1 -1
  39. package/dist-renderer/assets/{index-hGBnMHVl.js → index-yNYjzR2R.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D3ilrGOS.js → infoDiagram-HS3SLOUP-DKP5zgHc.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOCG2Rrk.js → journeyDiagram-XKPGCS4Q-Omd7tmzE.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-hZ03nCkx.js → kanban-definition-3W4ZIXB7-D7yw9yIY.js} +1 -1
  43. package/dist-renderer/assets/{layout-D5Mqy2My.js → layout-DZxAqFuM.js} +1 -1
  44. package/dist-renderer/assets/{linear-Clp9OpXU.js → linear-BXWJygRB.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D-w-qhNz.js → mindmap-definition-VGOIOE7T-BfJ09SBb.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D9K0-ecY.js → pieDiagram-ADFJNKIX-BYaLQhXj.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-B2f19Ki3.js → quadrantDiagram-AYHSOK5B-DeA0B1fw.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-VezNpCaU.js → requirementDiagram-UZGBJVZJ-DnFWn7-v.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BpHjEoR9.js → sankeyDiagram-TZEHDZUN-L9bek20k.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-Tdb7IZ4t.js → sequenceDiagram-WL72ISMW-BBmcJUXb.js} +1 -1
  51. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BNA5Q-zz.js → stateDiagram-FKZM4ZOC-DrwPQvTq.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-w_4GEN2a.js → stateDiagram-v2-4FDKWEC3-BOUQrTH6.js} +1 -1
  53. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BghtWMWY.js → timeline-definition-IT6M3QCI-Dldh9vsj.js} +1 -1
  54. package/dist-renderer/assets/{treemap-GDKQZRPO-BVquWjHf.js → treemap-GDKQZRPO-BsGSs8-P.js} +1 -1
  55. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BSV5wEDU.js → xychartDiagram-PRI3JC2R-BsR_bj-d.js} +1 -1
  56. package/dist-renderer/index.html +2 -2
  57. package/package.json +2 -2
  58. package/src/main/server.ts +38 -10
  59. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +2 -0
  60. package/src/renderer/components/settings/sections/AdvancedSection.tsx +2 -0
  61. package/src/renderer/components/settings/sections/CliStatusSection.tsx +2 -0
  62. package/src/renderer/components/settings/sections/HarnessSection.tsx +11 -0
  63. package/src/renderer/components/settings/sections/PlatformsSection.tsx +10 -2
  64. package/src/renderer/components/team/TeamDetailView.tsx +15 -24
  65. package/src/renderer/components/team/TeamListView.tsx +21 -12
  66. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +120 -0
  67. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +95 -2
  68. package/src/renderer/utils/openHermitEvents.ts +9 -0
  69. package/src/shared/types/team.ts +5 -0
  70. package/dist-renderer/assets/channel-CqQK8EX1.js +0 -1
  71. package/dist-renderer/assets/classDiagram-2ON5EDUG-9A3Cg8IA.js +0 -1
  72. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-9A3Cg8IA.js +0 -1
  73. package/dist-renderer/assets/clone-Bnr-WjeU.js +0 -1
  74. package/dist-renderer/assets/index-C4x095x4.css +0 -1
@@ -50,16 +50,6 @@ import { TaskDispatchService } from './services/teams-mvp/TaskDispatchService';
50
50
  import type { TaskBusConfig } from '@shared/types/team';
51
51
  import { UpdateService } from './services/UpdateService';
52
52
 
53
- process.on('uncaughtException', (err) => {
54
- console.error('[openHermit:server] uncaughtException', err);
55
- process.exit(1);
56
- });
57
-
58
- process.on('unhandledRejection', (reason) => {
59
- console.error('[openHermit:server] unhandledRejection', reason);
60
- process.exit(1);
61
- });
62
-
63
53
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
64
54
  const pkg = JSON.parse(readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
65
55
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
@@ -816,6 +806,16 @@ app.post('/api/teams/create', async (request, reply) => {
816
806
  request.log.warn({ err, teamName: name }, 'failed to persist local team metadata');
817
807
  }
818
808
 
809
+ // Bind provider refs if specified
810
+ const providerRefs = Array.isArray(body.providerRefs) ? body.providerRefs as string[] : [];
811
+ if (providerRefs.length > 0) {
812
+ try {
813
+ await cc.setProviderRefs(name, providerRefs);
814
+ } catch (err) {
815
+ request.log.warn({ err, teamName: name, providerRefs }, 'failed to set provider refs');
816
+ }
817
+ }
818
+
819
819
  return { ok: true, teamName: name, runId: null };
820
820
  } catch (err) {
821
821
  return reply.code(500).send({ error: err instanceof Error ? err.message : String(err) });
@@ -915,6 +915,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
915
915
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
916
916
  ? p.agent_mode.trim()
917
917
  : permissionMode;
918
+ const [providerRefs, globalProviders] = await Promise.all([
919
+ cc.getProviderRefs(name).catch(() => []),
920
+ cc.listProviders().catch(() => []),
921
+ ]);
918
922
 
919
923
  return {
920
924
  teamName: name,
@@ -955,6 +959,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
955
959
  description,
956
960
  workDir: p.work_dir ?? workDir,
957
961
  permissionMode: resolvedPermissionMode,
962
+ providerRefs,
963
+ globalProviders,
958
964
  settings: {
959
965
  ...projectSettings,
960
966
  language: resolvedLanguage,
@@ -1009,6 +1015,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
1009
1015
  description,
1010
1016
  workDir,
1011
1017
  permissionMode,
1018
+ providerRefs: [],
1019
+ globalProviders: [],
1012
1020
  heartbeat: null,
1013
1021
  settings: {
1014
1022
  language,
@@ -1042,6 +1050,9 @@ app.delete<{ Params: { name: string }; Querystring: { deleteFiles?: string } }>(
1042
1050
  '/api/teams/:name',
1043
1051
  async (request, reply) => {
1044
1052
  const teamName = request.params.name;
1053
+ if (teamName === 'default') {
1054
+ return reply.code(403).send({ error: 'default 团队不可删除' });
1055
+ }
1045
1056
  try {
1046
1057
  let restartRequired = false;
1047
1058
  try {
@@ -3406,6 +3417,9 @@ async function applyTeamConfigUpdate(
3406
3417
  const disabledCommands = Array.isArray(body.disabledCommands)
3407
3418
  ? normalizeStringArray(body.disabledCommands)
3408
3419
  : undefined;
3420
+ const providerRefs = Array.isArray(body.providerRefs)
3421
+ ? normalizeStringArray(body.providerRefs)
3422
+ : undefined;
3409
3423
  const platformAllowFrom = body.platformAllowFrom
3410
3424
  ? normalizePlatformAllowFrom(body.platformAllowFrom)
3411
3425
  : undefined;
@@ -3470,6 +3484,13 @@ async function applyTeamConfigUpdate(
3470
3484
  ccSyncError = err instanceof Error ? err.message : String(err);
3471
3485
  }
3472
3486
  }
3487
+ if (providerRefs !== undefined) {
3488
+ try {
3489
+ await cc.setProviderRefs(teamName, providerRefs);
3490
+ } catch (err) {
3491
+ ccSyncError = err instanceof Error ? err.message : String(err);
3492
+ }
3493
+ }
3473
3494
 
3474
3495
  return {
3475
3496
  name: name || teamName,
@@ -3486,6 +3507,7 @@ async function applyTeamConfigUpdate(
3486
3507
  replyFooter: replyFooter ?? false,
3487
3508
  injectSender: injectSender ?? false,
3488
3509
  platformAllowFrom: platformAllowFrom ?? {},
3510
+ providerRefs: providerRefs ?? [],
3489
3511
  ccSyncError,
3490
3512
  };
3491
3513
  }
@@ -3557,6 +3579,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3557
3579
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
3558
3580
  ? p.agent_mode.trim()
3559
3581
  : permissionMode;
3582
+ const [providerRefs, globalProviders] = await Promise.all([
3583
+ cc.getProviderRefs(name).catch(() => []),
3584
+ cc.listProviders().catch(() => []),
3585
+ ]);
3560
3586
  return {
3561
3587
  name,
3562
3588
  color,
@@ -3572,6 +3598,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3572
3598
  injectSender: resolvedInjectSender,
3573
3599
  permissionMode: resolvedPermissionMode,
3574
3600
  platformAllowFrom: resolvedPlatformAllowFrom,
3601
+ providerRefs,
3602
+ globalProviders,
3575
3603
  settings: {
3576
3604
  ...projectSettings,
3577
3605
  language: resolvedLanguage,
@@ -10,6 +10,7 @@ import {
10
10
  DialogTitle,
11
11
  } from '@renderer/components/ui/dialog';
12
12
  import { Input } from '@renderer/components/ui/input';
13
+ import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
13
14
  import { Loader2, RefreshCw } from 'lucide-react';
14
15
 
15
16
  import type { CliProviderId, CliProviderStatus } from '@shared/types';
@@ -127,6 +128,7 @@ export const ProviderRuntimeSettingsDialog = ({
127
128
  setNewProviderBaseUrl('');
128
129
  setNewProviderApiKey('');
129
130
  await refreshProviders();
131
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.providersChanged);
130
132
  } catch (err) {
131
133
  setAddError(err instanceof Error ? err.message : '添加 Provider 失败');
132
134
  } finally {
@@ -10,6 +10,7 @@ import {
10
10
  DialogTitle,
11
11
  } from '@renderer/components/ui/dialog';
12
12
  import { Textarea } from '@renderer/components/ui/textarea';
13
+ import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
13
14
  import appIcon from '@renderer/favicon.png';
14
15
  import { Check, FileEdit, Loader2, RefreshCw, RotateCcw, X } from 'lucide-react';
15
16
 
@@ -147,6 +148,7 @@ export const AdvancedSection = ({}: AdvancedSectionProps): React.JSX.Element =>
147
148
  setRestartMsg(null);
148
149
  try {
149
150
  await api.ccSettings.restart();
151
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.runtimeRestarted);
150
152
  setRestartMsg('已重启');
151
153
  } catch {
152
154
  setRestartMsg('重启失败');
@@ -30,6 +30,7 @@ import { useStore } from '@renderer/store';
30
30
  import { createLoadingMultimodelCliStatus } from '@renderer/store/slices/cliInstallerSlice';
31
31
  import { getMainScreenCliProviders } from '@renderer/utils/claudeCodeOnlyProviders';
32
32
  import { formatBytes } from '@renderer/utils/formatters';
33
+ import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
33
34
  import { resolveProjectPathById } from '@renderer/utils/projectLookup';
34
35
  import { refreshCliStatusForCurrentMode } from '@renderer/utils/refreshCliStatus';
35
36
  import { getRuntimeDisplayName } from '@renderer/utils/runtimeDisplayName';
@@ -747,6 +748,7 @@ const GenericHarnessProviderDialog = ({
747
748
  setNewProviderBaseUrl('');
748
749
  setNewProviderModel('');
749
750
  onRefresh();
751
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.providersChanged);
750
752
  } catch (err) {
751
753
  setAddProviderError(err instanceof Error ? err.message : '添加 Provider 失败');
752
754
  } finally {
@@ -11,6 +11,7 @@ import { providersApi } from '@renderer/api/providers';
11
11
  import { ALL_AGENT_TYPES, AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
12
12
  import type { CcAgentType } from '@renderer/components/team/HarnessCards';
13
13
  import { cn } from '@renderer/lib/utils';
14
+ import { OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
14
15
  import { CheckCircle2 } from 'lucide-react';
15
16
 
16
17
  import { SettingsSectionHeader } from '../components/SettingsSectionHeader';
@@ -70,6 +71,16 @@ export const HarnessSection = (): React.JSX.Element => {
70
71
  void refresh();
71
72
  }, [refresh]);
72
73
 
74
+ useEffect(() => {
75
+ const handleProvidersChanged = () => {
76
+ void refresh();
77
+ };
78
+ window.addEventListener(OPEN_HERMIT_EVENTS.providersChanged, handleProvidersChanged);
79
+ return () => {
80
+ window.removeEventListener(OPEN_HERMIT_EVENTS.providersChanged, handleProvidersChanged);
81
+ };
82
+ }, [refresh]);
83
+
73
84
  // Build coverage map: agent type -> list of sources (providers + projects)
74
85
  const coveredTypes = new Map<CcAgentType, string[]>();
75
86
  for (const type of ALL_AGENT_TYPES) {
@@ -19,6 +19,7 @@ import {
19
19
  } from '@renderer/components/ui/dialog';
20
20
  import { Input } from '@renderer/components/ui/input';
21
21
  import { Label } from '@renderer/components/ui/label';
22
+ import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
22
23
  import {
23
24
  Select,
24
25
  SelectContent,
@@ -213,7 +214,9 @@ async function addPlatform(
213
214
  restart_required?: boolean;
214
215
  };
215
216
  if (!json.ok) throw new Error(json.error ?? '添加失败');
216
- return { restartRequired: json.data?.restart_required === true || json.restart_required === true };
217
+ return {
218
+ restartRequired: json.data?.restart_required === true || json.restart_required === true,
219
+ };
217
220
  }
218
221
 
219
222
  // ---------------------------------------------------------------------------
@@ -490,7 +493,11 @@ function AddPlatformDialog({
490
493
  else if (v === 'false') ccOptions[k] = false;
491
494
  else ccOptions[k] = v;
492
495
  }
493
- const result = await addPlatform(projectName, platformType, ccOptions as Record<string, string>);
496
+ const result = await addPlatform(
497
+ projectName,
498
+ platformType,
499
+ ccOptions as Record<string, string>
500
+ );
494
501
  onAdded(projectName);
495
502
  if (result.restartRequired) {
496
503
  const shouldRestart = await confirm({
@@ -501,6 +508,7 @@ function AddPlatformDialog({
501
508
  });
502
509
  if (shouldRestart) {
503
510
  await api.ccSettings.restart();
511
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.runtimeRestarted);
504
512
  }
505
513
  }
506
514
  } catch (e) {
@@ -1618,16 +1618,13 @@ export const TeamDetailView = ({
1618
1618
  );
1619
1619
 
1620
1620
  const handleRestartTeamFromEdit = useCallback(async (): Promise<void> => {
1621
- if (!data?.config.projectPath) {
1622
- throw new Error('团队缺少项目路径,无法自动重启。');
1623
- }
1624
- await api.teams.stop(teamName);
1625
- await launchTeam({
1626
- teamName,
1627
- cwd: data.config.projectPath,
1628
- });
1629
- await Promise.all([fetchTeams(), selectTeam(teamName)]);
1630
- }, [data?.config.projectPath, fetchTeams, launchTeam, selectTeam, teamName]);
1621
+ await api.ccSettings.restart();
1622
+ // Wait for cc-connect to come back, then refresh
1623
+ setTimeout(() => {
1624
+ void fetchTeams();
1625
+ void selectTeam(teamName);
1626
+ }, 3000);
1627
+ }, [fetchTeams, selectTeam, teamName]);
1631
1628
 
1632
1629
  const handleSaveAndRestartFromEdit = useCallback(
1633
1630
  async (runtimeConfig: {
@@ -1832,21 +1829,12 @@ export const TeamDetailView = ({
1832
1829
  try {
1833
1830
  const result = await deleteTeam(teamName);
1834
1831
  if (result.restartRequired) {
1835
- const shouldRestart = await confirm({
1836
- title: '重启 cc-connect',
1837
- message: '团队已从配置中删除。需要重启 cc-connect 才会停止对应运行时。',
1838
- confirmLabel: '立即重启',
1839
- cancelLabel: '稍后重启',
1840
- variant: 'danger',
1841
- });
1842
- if (shouldRestart) {
1843
- await api.ccSettings.restart();
1844
- }
1832
+ await api.ccSettings.restart();
1845
1833
  }
1846
1834
  if (tabId) closeTab(tabId);
1847
1835
  openTeamsTab();
1848
- } catch {
1849
- // error is shown via store
1836
+ } catch (err) {
1837
+ console.error('Failed to delete team:', err);
1850
1838
  }
1851
1839
  })();
1852
1840
  }, [teamName, deleteTeam, openTeamsTab, closeTab, tabId]);
@@ -2163,6 +2151,7 @@ export const TeamDetailView = ({
2163
2151
  {isTeamProvisioning ? '团队仍在编排中,暂时无法编辑' : '编辑团队'}
2164
2152
  </TooltipContent>
2165
2153
  </Tooltip>
2154
+ {teamName !== 'default' && (
2166
2155
  <Tooltip>
2167
2156
  <TooltipTrigger asChild>
2168
2157
  <Button
@@ -2176,6 +2165,7 @@ export const TeamDetailView = ({
2176
2165
  </TooltipTrigger>
2177
2166
  <TooltipContent side="bottom">删除团队</TooltipContent>
2178
2167
  </Tooltip>
2168
+ )}
2179
2169
  </div>
2180
2170
  </div>
2181
2171
  {data.config.description && (
@@ -2653,6 +2643,8 @@ export const TeamDetailView = ({
2653
2643
  currentManagedSources={currentManagedSources}
2654
2644
  currentDisabledCommands={currentDisabledCommands}
2655
2645
  currentPlatformAllowFrom={currentPlatformAllowFrom}
2646
+ currentProviderRefs={data.providerRefs ?? []}
2647
+ globalProviders={data.globalProviders ?? []}
2656
2648
  currentMembers={membersWithLiveBranches.filter((m) => !isLeadMember(m))}
2657
2649
  leadMember={membersWithLiveBranches.find((m) => isLeadMember(m)) ?? null}
2658
2650
  resolvedMemberColorMap={resolvedMemberColorMap}
@@ -2665,9 +2657,8 @@ export const TeamDetailView = ({
2665
2657
  void fetchTeams();
2666
2658
  void selectTeam(teamName);
2667
2659
  }}
2668
- onDeleteTeam={handleDeleteTeam}
2660
+ onDeleteTeam={teamName !== 'default' ? handleDeleteTeam : undefined}
2669
2661
  onRestartTeam={handleRestartTeamFromEdit}
2670
- onSaveAndRestart={handleSaveAndRestartFromEdit}
2671
2662
  />
2672
2663
 
2673
2664
  <Dialog
@@ -33,6 +33,7 @@ import {
33
33
  } from '@renderer/store/utils/stateResetHelpers';
34
34
  import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
35
35
  import { buildTaskCountsByTeam, normalizePath } from '@renderer/utils/pathNormalize';
36
+ import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
36
37
  import { getBaseName } from '@renderer/utils/pathUtils';
37
38
  import { nameColorSet } from '@renderer/utils/projectColor';
38
39
  import {
@@ -541,19 +542,11 @@ export const TeamListView = (): React.JSX.Element => {
541
542
  try {
542
543
  const result = await deleteTeam(teamName);
543
544
  if (result.restartRequired) {
544
- const shouldRestart = await confirm({
545
- title: '重启 cc-connect',
546
- message: '团队已从配置中删除。需要重启 cc-connect 才会停止对应运行时。',
547
- confirmLabel: '立即重启',
548
- cancelLabel: '稍后重启',
549
- variant: 'danger',
550
- });
551
- if (shouldRestart) {
552
- await api.ccSettings.restart();
553
- }
545
+ await api.ccSettings.restart();
546
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.runtimeRestarted);
554
547
  }
555
- } catch {
556
- // error via store
548
+ } catch (err) {
549
+ console.error('Failed to delete team:', err);
557
550
  }
558
551
  }
559
552
  })();
@@ -674,6 +667,19 @@ export const TeamListView = (): React.JSX.Element => {
674
667
  void fetchAllTasks();
675
668
  }, [fetchTeams, fetchAllTasks]);
676
669
 
670
+ useEffect(() => {
671
+ const refresh = () => {
672
+ void fetchTeams();
673
+ void fetchAllTasks();
674
+ };
675
+ window.addEventListener(OPEN_HERMIT_EVENTS.runtimeRestarted, refresh);
676
+ window.addEventListener(OPEN_HERMIT_EVENTS.teamsChanged, refresh);
677
+ return () => {
678
+ window.removeEventListener(OPEN_HERMIT_EVENTS.runtimeRestarted, refresh);
679
+ window.removeEventListener(OPEN_HERMIT_EVENTS.teamsChanged, refresh);
680
+ };
681
+ }, [fetchTeams, fetchAllTasks]);
682
+
677
683
  const taskCountsByTeam = useMemo(() => buildTaskCountsByTeam(globalTasks), [globalTasks]);
678
684
 
679
685
  const activeTeams = useMemo<ActiveTeamRef[]>(() => {
@@ -798,6 +804,7 @@ export const TeamListView = (): React.JSX.Element => {
798
804
  async (request: TeamCreateRequest) => {
799
805
  await createTeam(request);
800
806
  await Promise.all([fetchTeams(), fetchAllTasks()]);
807
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.teamsChanged);
801
808
  window.setTimeout(() => {
802
809
  void fetchTeams();
803
810
  void fetchAllTasks();
@@ -1165,6 +1172,7 @@ export const TeamListView = (): React.JSX.Element => {
1165
1172
  <TooltipContent side="bottom">复制团队</TooltipContent>
1166
1173
  </Tooltip>
1167
1174
  )}
1175
+ {team.teamName !== 'default' && (
1168
1176
  <Tooltip>
1169
1177
  <TooltipTrigger asChild>
1170
1178
  <button
@@ -1179,6 +1187,7 @@ export const TeamListView = (): React.JSX.Element => {
1179
1187
  </TooltipTrigger>
1180
1188
  <TooltipContent side="bottom">删除团队</TooltipContent>
1181
1189
  </Tooltip>
1190
+ )}
1182
1191
  </div>
1183
1192
  </div>
1184
1193
  <div className="mt-2 flex min-h-10 items-start gap-2">
@@ -54,6 +54,9 @@ import PlatformManualForm from './PlatformManualForm';
54
54
 
55
55
  import type { Project, TeamCreateRequest } from '@shared/types';
56
56
  import type { CcAgentType } from '@shared/types/ccConnect';
57
+ import type { GlobalProvider } from '@shared/types/providers';
58
+
59
+ import { providersApi } from '@renderer/api/providers';
57
60
 
58
61
  export interface ActiveTeamRef {
59
62
  teamName: string;
@@ -252,6 +255,10 @@ export const CreateTeamDialog = ({
252
255
  const [projectsLoading, setProjectsLoading] = useState(false);
253
256
  const [projectsError, setProjectsError] = useState<string | null>(null);
254
257
 
258
+ // ── Global providers ─────────────────────────────────────────────────
259
+ const [globalProviders, setGlobalProviders] = useState<GlobalProvider[]>([]);
260
+ const [selectedProviderRef, setSelectedProviderRef] = useState<string | null>(null);
261
+
255
262
  // ── Errors / submission ──────────────────────────────────────────────
256
263
  const [localError, setLocalError] = useState<string | null>(null);
257
264
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -273,6 +280,27 @@ export const CreateTeamDialog = ({
273
280
  : selectedProjectPath.trim()
274
281
  : customCwd.trim();
275
282
 
283
+ const compatibleProviders = useMemo(
284
+ () =>
285
+ globalProviders.filter(
286
+ (p) => !p.agent_types || p.agent_types.length === 0 || p.agent_types.includes(selectedHarness)
287
+ ),
288
+ [globalProviders, selectedHarness]
289
+ );
290
+
291
+ const selectProviderRef = (providerName: string) => {
292
+ setSelectedProviderRef((prev) => prev === providerName ? null : providerName);
293
+ };
294
+
295
+ // Clear selected provider when harness changes and it's no longer compatible
296
+ useEffect(() => {
297
+ setSelectedProviderRef((prev) => {
298
+ if (!prev) return prev;
299
+ const compatible = new Set(compatibleProviders.map((p) => p.name));
300
+ return compatible.has(prev) ? prev : null;
301
+ });
302
+ }, [compatibleProviders]);
303
+
276
304
  const conflictingTeam = useMemo(() => {
277
305
  if (!activeTeams?.length || !effectiveCwd) return null;
278
306
  const norm = normalizePath(effectiveCwd);
@@ -309,6 +337,23 @@ export const CreateTeamDialog = ({
309
337
  };
310
338
  }, [open]);
311
339
 
340
+ // ── Load global providers on open ────────────────────────────────────
341
+ useEffect(() => {
342
+ if (!open) return;
343
+ let cancelled = false;
344
+ void (async () => {
345
+ try {
346
+ const result = await providersApi.list();
347
+ if (!cancelled) setGlobalProviders(result.providers ?? []);
348
+ } catch {
349
+ if (!cancelled) setGlobalProviders([]);
350
+ }
351
+ })();
352
+ return () => {
353
+ cancelled = true;
354
+ };
355
+ }, [open]);
356
+
312
357
  // ── Auto-select default project path ─────────────────────────────────
313
358
  useEffect(() => {
314
359
  if (!open || cwdMode !== 'project' || selectedProjectPath) return;
@@ -337,6 +382,7 @@ export const CreateTeamDialog = ({
337
382
  setFieldErrors({});
338
383
  setIsSubmitting(false);
339
384
  setConflictDismissed(false);
385
+ setSelectedProviderRef(null);
340
386
  };
341
387
 
342
388
  // ── Platform selection ───────────────────────────────────────────────
@@ -402,6 +448,7 @@ export const CreateTeamDialog = ({
402
448
  harness: selectedHarness,
403
449
  platform: selectedPlatform || 'bridge',
404
450
  platformOptions: {},
451
+ providerRefs: selectedProviderRef ? [selectedProviderRef] : undefined,
405
452
  };
406
453
  await onCreate(request);
407
454
  onOpenTeam(request.teamName, effectiveCwd || undefined);
@@ -527,6 +574,79 @@ export const CreateTeamDialog = ({
527
574
  projectsError={projectsError}
528
575
  fieldError={fieldErrors.cwd}
529
576
  />
577
+
578
+ {/* Provider selection */}
579
+ <div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
580
+ <div className="flex items-start justify-between gap-3">
581
+ <div>
582
+ <p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
583
+ <p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
584
+ 留空时使用本机 {AGENT_TYPE_LABELS[selectedHarness] ?? selectedHarness} 默认配置和登录状态。
585
+ 只有需要给该团队指定模型供应商时,才绑定下面的全局 Provider。
586
+ </p>
587
+ </div>
588
+ {selectedProviderRef ? (
589
+ <button
590
+ type="button"
591
+ 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"
592
+ onClick={() => setSelectedProviderRef(null)}
593
+ >
594
+ 使用本机默认
595
+ </button>
596
+ ) : null}
597
+ </div>
598
+
599
+ <div className="mt-3 space-y-2">
600
+ {compatibleProviders.length > 0 ? (
601
+ compatibleProviders.map((provider) => {
602
+ const checked = selectedProviderRef === provider.name;
603
+ const endpoint = provider.endpoints?.[selectedHarness] ?? provider.base_url ?? '默认端点';
604
+ const model =
605
+ provider.agent_models?.[selectedHarness] ??
606
+ provider.model ??
607
+ provider.models?.[0]?.model ??
608
+ '未指定模型';
609
+ return (
610
+ <button
611
+ key={provider.name}
612
+ type="button"
613
+ onClick={() => selectProviderRef(provider.name)}
614
+ className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
615
+ checked
616
+ ? 'border-indigo-400/60 bg-indigo-500/10'
617
+ : 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
618
+ }`}
619
+ >
620
+ <div className="flex items-center justify-between gap-3">
621
+ <div className="min-w-0">
622
+ <p className="truncate text-xs font-medium text-[var(--color-text)]">
623
+ {provider.name}
624
+ </p>
625
+ <p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
626
+ {model} · {endpoint}
627
+ </p>
628
+ </div>
629
+ <span
630
+ className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
631
+ checked
632
+ ? 'bg-indigo-400/20 text-indigo-200'
633
+ : 'bg-white/5 text-[var(--color-text-muted)]'
634
+ }`}
635
+ >
636
+ {checked ? '已绑定' : '可绑定'}
637
+ </span>
638
+ </div>
639
+ </button>
640
+ );
641
+ })
642
+ ) : (
643
+ <div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
644
+ 暂无适用于 {AGENT_TYPE_LABELS[selectedHarness] ?? selectedHarness} 的全局 Provider。
645
+ 可先在「设置 → Harness 配置」中添加;不添加也会使用本机默认登录态。
646
+ </div>
647
+ )}
648
+ </div>
649
+ </div>
530
650
  </div>
531
651
  )}
532
652