@yancyyu/openhermit 1.6.24 → 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 +90 -50
  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 -19
  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, '..', '..');
@@ -68,7 +58,6 @@ const HOST = process.env.HOST ?? '127.0.0.1';
68
58
  const PORT = Number.parseInt(process.env.PORT ?? '5680', 10);
69
59
  const STATIC_DIR = process.env.STATIC_DIR ?? path.resolve(REPO_ROOT, 'dist-renderer');
70
60
  const HARNESS_BRIDGE_CONNECT_TIMEOUT_MS = 10_000;
71
- const RUNTIME_SETUP_MODE = process.env.HERMIT_RUNTIME_SETUP_MODE === '1';
72
61
 
73
62
  // ===========================================================================
74
63
  // Hermit runtime config — ~/.hermit/config.json
@@ -817,6 +806,16 @@ app.post('/api/teams/create', async (request, reply) => {
817
806
  request.log.warn({ err, teamName: name }, 'failed to persist local team metadata');
818
807
  }
819
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
+
820
819
  return { ok: true, teamName: name, runId: null };
821
820
  } catch (err) {
822
821
  return reply.code(500).send({ error: err instanceof Error ? err.message : String(err) });
@@ -916,6 +915,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
916
915
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
917
916
  ? p.agent_mode.trim()
918
917
  : permissionMode;
918
+ const [providerRefs, globalProviders] = await Promise.all([
919
+ cc.getProviderRefs(name).catch(() => []),
920
+ cc.listProviders().catch(() => []),
921
+ ]);
919
922
 
920
923
  return {
921
924
  teamName: name,
@@ -956,6 +959,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
956
959
  description,
957
960
  workDir: p.work_dir ?? workDir,
958
961
  permissionMode: resolvedPermissionMode,
962
+ providerRefs,
963
+ globalProviders,
959
964
  settings: {
960
965
  ...projectSettings,
961
966
  language: resolvedLanguage,
@@ -1010,6 +1015,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
1010
1015
  description,
1011
1016
  workDir,
1012
1017
  permissionMode,
1018
+ providerRefs: [],
1019
+ globalProviders: [],
1013
1020
  heartbeat: null,
1014
1021
  settings: {
1015
1022
  language,
@@ -1043,6 +1050,9 @@ app.delete<{ Params: { name: string }; Querystring: { deleteFiles?: string } }>(
1043
1050
  '/api/teams/:name',
1044
1051
  async (request, reply) => {
1045
1052
  const teamName = request.params.name;
1053
+ if (teamName === 'default') {
1054
+ return reply.code(403).send({ error: 'default 团队不可删除' });
1055
+ }
1046
1056
  try {
1047
1057
  let restartRequired = false;
1048
1058
  try {
@@ -2184,11 +2194,6 @@ function isCronNotFoundError(error: unknown): boolean {
2184
2194
  return /(\b404\b|not found|no matching|does not exist|不存在)/i.test(message);
2185
2195
  }
2186
2196
 
2187
- function isRuntimeUnavailableError(error: unknown): boolean {
2188
- const message = error instanceof Error ? error.message : String(error);
2189
- return /ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|fetch failed|cc-connect 不可达/i.test(message);
2190
- }
2191
-
2192
2197
  app.get('/api/schedules', async () => {
2193
2198
  try {
2194
2199
  const jobs = await cc.listCronJobs();
@@ -2196,9 +2201,6 @@ app.get('/api/schedules', async () => {
2196
2201
  const workDirMap = await resolveTeamWorkDirs(jobs.map((job) => job.project));
2197
2202
  return jobs.map((job) => mapCronJobToSchedule(job, workDirMap.get(job.project) ?? ''));
2198
2203
  } catch (err) {
2199
- if (RUNTIME_SETUP_MODE && isRuntimeUnavailableError(err)) {
2200
- return [];
2201
- }
2202
2204
  app.log.warn({ err }, 'list schedules from cc-connect failed');
2203
2205
  return [];
2204
2206
  }
@@ -3415,6 +3417,9 @@ async function applyTeamConfigUpdate(
3415
3417
  const disabledCommands = Array.isArray(body.disabledCommands)
3416
3418
  ? normalizeStringArray(body.disabledCommands)
3417
3419
  : undefined;
3420
+ const providerRefs = Array.isArray(body.providerRefs)
3421
+ ? normalizeStringArray(body.providerRefs)
3422
+ : undefined;
3418
3423
  const platformAllowFrom = body.platformAllowFrom
3419
3424
  ? normalizePlatformAllowFrom(body.platformAllowFrom)
3420
3425
  : undefined;
@@ -3479,6 +3484,13 @@ async function applyTeamConfigUpdate(
3479
3484
  ccSyncError = err instanceof Error ? err.message : String(err);
3480
3485
  }
3481
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
+ }
3482
3494
 
3483
3495
  return {
3484
3496
  name: name || teamName,
@@ -3495,6 +3507,7 @@ async function applyTeamConfigUpdate(
3495
3507
  replyFooter: replyFooter ?? false,
3496
3508
  injectSender: injectSender ?? false,
3497
3509
  platformAllowFrom: platformAllowFrom ?? {},
3510
+ providerRefs: providerRefs ?? [],
3498
3511
  ccSyncError,
3499
3512
  };
3500
3513
  }
@@ -3566,6 +3579,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3566
3579
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
3567
3580
  ? p.agent_mode.trim()
3568
3581
  : permissionMode;
3582
+ const [providerRefs, globalProviders] = await Promise.all([
3583
+ cc.getProviderRefs(name).catch(() => []),
3584
+ cc.listProviders().catch(() => []),
3585
+ ]);
3569
3586
  return {
3570
3587
  name,
3571
3588
  color,
@@ -3581,6 +3598,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3581
3598
  injectSender: resolvedInjectSender,
3582
3599
  permissionMode: resolvedPermissionMode,
3583
3600
  platformAllowFrom: resolvedPlatformAllowFrom,
3601
+ providerRefs,
3602
+ globalProviders,
3584
3603
  settings: {
3585
3604
  ...projectSettings,
3586
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