@yancyyu/openhermit 1.6.25 → 1.6.27

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 (75) hide show
  1. package/README.md +127 -77
  2. package/bin/hermit.mjs +151 -62
  3. package/dist-renderer/assets/{ProjectEditorOverlay-CZ1LI0pd.js → ProjectEditorOverlay-BBwYdXPv.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-DvyxOPvU.js → TeamGraphOverlay-DVq8rt6_.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-D8IKEBh4.js → _basePickBy-ZbF0pKvS.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BDBpSAHY.js → _baseUniq-BBLBOeXc.js} +1 -1
  7. package/dist-renderer/assets/{arc-CZagJLek.js → arc-wGaEgkCf.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Cq083Uu6.js → architectureDiagram-VXUJARFQ-BpMkdC35.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-CIEANvSv.js → blockDiagram-VD42YOAC-C8Z1xhG4.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Ca4h_BlA.js → c4Diagram-YG6GDRKO-CJmlw9LA.js} +1 -1
  11. package/dist-renderer/assets/channel-DJUrwVrK.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-DW9HjNgA.js → chunk-4BX2VUAB-CHPHiRPP.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-BddOEwBk.js → chunk-55IACEB6-DyVohOQb.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-Cmu8IjLN.js → chunk-B4BG7PRW-p5bffh_R.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-BU7nEH8e.js → chunk-DI55MBZ5-BnfGPSUu.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-BZNeq0CB.js → chunk-FMBD7UC4-B6SCKseX.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-DMb3gYzN.js → chunk-QN33PNHL-L12RvLBR.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-8wSSvqz0.js → chunk-QZHKN3VN-DeH1Kxge.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-B4OLgw45.js → chunk-TZMSLE5B-BWnjzSlI.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +1 -0
  22. package/dist-renderer/assets/clone-BftjWakJ.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CS9moNeT.js → cose-bilkent-S5V4N54A-BtzoT5fu.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-3pzdMwjZ.js → dagre-6UL2VRFP-CBBvuoUD.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-CGTnMY5C.js → diagram-PSM6KHXK-Be9BAKws.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-Cyzp8dOw.js → diagram-QEK2KX5R-BDS4PI_i.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-D_5SvdXI.js → diagram-S2PKOQOG-2Rameaq7.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-bWPYfs9c.js → erDiagram-Q2GNP2WA-CSIzCEZD.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-C5z47CKB.js → flowDiagram-NV44I4VS-ForEIVM5.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-eye9Cv6S.js → ganttDiagram-JELNMOA3-BJrli_xr.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DekQZoF8.js → gitGraphDiagram-V2S2FVAM-C_4GuLno.js} +1 -1
  32. package/dist-renderer/assets/{graph-kNnl-9Fl.js → graph-B1EAT_gw.js} +1 -1
  33. package/dist-renderer/assets/index-CWpFqEvz.css +1 -0
  34. package/dist-renderer/assets/{index-hGBnMHVl.js → index-DOA_jbYb.js} +1 -1
  35. package/dist-renderer/assets/{index-ATiHUmmE.js → index-DR602dwJ.js} +1 -1
  36. package/dist-renderer/assets/{index-CrOkTuIK.js → index-DYdseEwc.js} +532 -532
  37. package/dist-renderer/assets/{index-CnxDIJh8.js → index-Dwr5wu5x.js} +1 -1
  38. package/dist-renderer/assets/{index-CI2l57ID.js → index-eKRmS5kI.js} +1 -1
  39. package/dist-renderer/assets/{index-DB0k9yRL.js → index-k4tnOFC5.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D3ilrGOS.js → infoDiagram-HS3SLOUP-DjI0uaMz.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOCG2Rrk.js → journeyDiagram-XKPGCS4Q-jQ6Thae-.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-hZ03nCkx.js → kanban-definition-3W4ZIXB7-CKw6InbL.js} +1 -1
  43. package/dist-renderer/assets/{layout-D5Mqy2My.js → layout-Dad20y3V.js} +1 -1
  44. package/dist-renderer/assets/{linear-Clp9OpXU.js → linear-vMgo_2Cv.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D-w-qhNz.js → mindmap-definition-VGOIOE7T-DYp6YoHL.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D9K0-ecY.js → pieDiagram-ADFJNKIX-BytBecG9.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-B2f19Ki3.js → quadrantDiagram-AYHSOK5B-RUaspLsc.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-VezNpCaU.js → requirementDiagram-UZGBJVZJ-rR2B1Use.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BpHjEoR9.js → sankeyDiagram-TZEHDZUN-BJi5qYhq.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-Tdb7IZ4t.js → sequenceDiagram-WL72ISMW-BM-wggUb.js} +1 -1
  51. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BNA5Q-zz.js → stateDiagram-FKZM4ZOC-BqmcVjnj.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-w_4GEN2a.js → stateDiagram-v2-4FDKWEC3-By3JDVbB.js} +1 -1
  53. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BghtWMWY.js → timeline-definition-IT6M3QCI-szH0GUyk.js} +1 -1
  54. package/dist-renderer/assets/{treemap-GDKQZRPO-BVquWjHf.js → treemap-GDKQZRPO-BCMlh-Ex.js} +1 -1
  55. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BSV5wEDU.js → xychartDiagram-PRI3JC2R-dwDpvw0w.js} +1 -1
  56. package/dist-renderer/index.html +2 -2
  57. package/package.json +2 -2
  58. package/src/main/server.ts +52 -12
  59. package/src/renderer/api/httpClient.ts +1 -1
  60. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +2 -0
  61. package/src/renderer/components/settings/sections/AdvancedSection.tsx +2 -0
  62. package/src/renderer/components/settings/sections/CliStatusSection.tsx +2 -0
  63. package/src/renderer/components/settings/sections/HarnessSection.tsx +11 -0
  64. package/src/renderer/components/settings/sections/PlatformsSection.tsx +10 -2
  65. package/src/renderer/components/team/TeamDetailView.tsx +53 -42
  66. package/src/renderer/components/team/TeamListView.tsx +54 -31
  67. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +123 -0
  68. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +98 -2
  69. package/src/renderer/utils/openHermitEvents.ts +9 -0
  70. package/src/shared/types/team.ts +5 -0
  71. package/dist-renderer/assets/channel-CqQK8EX1.js +0 -1
  72. package/dist-renderer/assets/classDiagram-2ON5EDUG-9A3Cg8IA.js +0 -1
  73. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-9A3Cg8IA.js +0 -1
  74. package/dist-renderer/assets/clone-Bnr-WjeU.js +0 -1
  75. package/dist-renderer/assets/index-C4x095x4.css +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yancyyu/openhermit",
3
3
  "type": "module",
4
- "version": "1.6.25",
4
+ "version": "1.6.27",
5
5
  "description": "openHermit: team-oriented agent management workbench atop cc-connect.",
6
6
  "license": "AGPL-3.0",
7
7
  "author": {
@@ -107,7 +107,7 @@
107
107
  "@tiptap/pm": "^3.20.4",
108
108
  "@tiptap/react": "^3.20.4",
109
109
  "@tiptap/starter-kit": "^3.20.4",
110
- "cc-connect": "^1.3.3-beta.2",
110
+ "cc-connect": "1.3.3-beta.3",
111
111
  "class-variance-authority": "^0.7.1",
112
112
  "clsx": "^2.1.1",
113
113
  "cmdk": "1.0.4",
@@ -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, '..', '..');
@@ -692,7 +682,17 @@ app.patch<{ Body: Record<string, unknown> }>('/api/cc-settings', async (request)
692
682
  app.post('/api/cc-restart', async () => {
693
683
  try {
694
684
  await cc.restart();
695
- return { ok: true };
685
+ // Wait for cc-connect to come back (restart only signals, process respawns async)
686
+ for (let i = 0; i < 30; i++) {
687
+ await new Promise((r) => setTimeout(r, 1000));
688
+ try {
689
+ await cc.listProjects();
690
+ return { ok: true };
691
+ } catch {
692
+ /* not back yet */
693
+ }
694
+ }
695
+ return reply500(new Error('cc-connect did not come back within 30s'));
696
696
  } catch (err) {
697
697
  return reply500(err);
698
698
  }
@@ -774,7 +774,9 @@ app.get('/api/teams', async () => {
774
774
  };
775
775
  })
776
776
  );
777
- return summaries.filter((team) => team.pendingDelete !== true);
777
+ return summaries.filter(
778
+ (team) => team.pendingDelete !== true && team.teamName !== 'my-project'
779
+ );
778
780
  } catch {
779
781
  return [];
780
782
  }
@@ -816,6 +818,16 @@ app.post('/api/teams/create', async (request, reply) => {
816
818
  request.log.warn({ err, teamName: name }, 'failed to persist local team metadata');
817
819
  }
818
820
 
821
+ // Bind provider refs if specified
822
+ const providerRefs = Array.isArray(body.providerRefs) ? (body.providerRefs as string[]) : [];
823
+ if (providerRefs.length > 0) {
824
+ try {
825
+ await cc.setProviderRefs(name, providerRefs);
826
+ } catch (err) {
827
+ request.log.warn({ err, teamName: name, providerRefs }, 'failed to set provider refs');
828
+ }
829
+ }
830
+
819
831
  return { ok: true, teamName: name, runId: null };
820
832
  } catch (err) {
821
833
  return reply.code(500).send({ error: err instanceof Error ? err.message : String(err) });
@@ -915,6 +927,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
915
927
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
916
928
  ? p.agent_mode.trim()
917
929
  : permissionMode;
930
+ const [providerRefs, globalProviders] = await Promise.all([
931
+ cc.getProviderRefs(name).catch(() => []),
932
+ cc.listProviders().catch(() => []),
933
+ ]);
918
934
 
919
935
  return {
920
936
  teamName: name,
@@ -955,6 +971,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
955
971
  description,
956
972
  workDir: p.work_dir ?? workDir,
957
973
  permissionMode: resolvedPermissionMode,
974
+ providerRefs,
975
+ globalProviders,
958
976
  settings: {
959
977
  ...projectSettings,
960
978
  language: resolvedLanguage,
@@ -1009,6 +1027,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/data', async (request, r
1009
1027
  description,
1010
1028
  workDir,
1011
1029
  permissionMode,
1030
+ providerRefs: [],
1031
+ globalProviders: [],
1012
1032
  heartbeat: null,
1013
1033
  settings: {
1014
1034
  language,
@@ -1042,6 +1062,9 @@ app.delete<{ Params: { name: string }; Querystring: { deleteFiles?: string } }>(
1042
1062
  '/api/teams/:name',
1043
1063
  async (request, reply) => {
1044
1064
  const teamName = request.params.name;
1065
+ if (teamName === 'default' || teamName === 'my-project') {
1066
+ return reply.code(403).send({ error: '该团队不可删除' });
1067
+ }
1045
1068
  try {
1046
1069
  let restartRequired = false;
1047
1070
  try {
@@ -3406,6 +3429,9 @@ async function applyTeamConfigUpdate(
3406
3429
  const disabledCommands = Array.isArray(body.disabledCommands)
3407
3430
  ? normalizeStringArray(body.disabledCommands)
3408
3431
  : undefined;
3432
+ const providerRefs = Array.isArray(body.providerRefs)
3433
+ ? normalizeStringArray(body.providerRefs)
3434
+ : undefined;
3409
3435
  const platformAllowFrom = body.platformAllowFrom
3410
3436
  ? normalizePlatformAllowFrom(body.platformAllowFrom)
3411
3437
  : undefined;
@@ -3470,6 +3496,13 @@ async function applyTeamConfigUpdate(
3470
3496
  ccSyncError = err instanceof Error ? err.message : String(err);
3471
3497
  }
3472
3498
  }
3499
+ if (providerRefs !== undefined) {
3500
+ try {
3501
+ await cc.setProviderRefs(teamName, providerRefs);
3502
+ } catch (err) {
3503
+ ccSyncError = err instanceof Error ? err.message : String(err);
3504
+ }
3505
+ }
3473
3506
 
3474
3507
  return {
3475
3508
  name: name || teamName,
@@ -3486,6 +3519,7 @@ async function applyTeamConfigUpdate(
3486
3519
  replyFooter: replyFooter ?? false,
3487
3520
  injectSender: injectSender ?? false,
3488
3521
  platformAllowFrom: platformAllowFrom ?? {},
3522
+ providerRefs: providerRefs ?? [],
3489
3523
  ccSyncError,
3490
3524
  };
3491
3525
  }
@@ -3557,6 +3591,10 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3557
3591
  typeof p.agent_mode === 'string' && p.agent_mode.trim().length > 0
3558
3592
  ? p.agent_mode.trim()
3559
3593
  : permissionMode;
3594
+ const [providerRefs, globalProviders] = await Promise.all([
3595
+ cc.getProviderRefs(name).catch(() => []),
3596
+ cc.listProviders().catch(() => []),
3597
+ ]);
3560
3598
  return {
3561
3599
  name,
3562
3600
  color,
@@ -3572,6 +3610,8 @@ app.get<{ Params: { name: string } }>('/api/teams/:name/config', async (request,
3572
3610
  injectSender: resolvedInjectSender,
3573
3611
  permissionMode: resolvedPermissionMode,
3574
3612
  platformAllowFrom: resolvedPlatformAllowFrom,
3613
+ providerRefs,
3614
+ globalProviders,
3575
3615
  settings: {
3576
3616
  ...projectSettings,
3577
3617
  language: resolvedLanguage,
@@ -278,7 +278,7 @@ export class HttpAPIClient implements ElectronAPI {
278
278
  try {
279
279
  const res = await fetch(`${this.baseUrl}${path}`, {
280
280
  method: 'DELETE',
281
- headers: { 'Content-Type': 'application/json' },
281
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
282
282
  body: body ? JSON.stringify(body) : undefined,
283
283
  signal: controller.signal,
284
284
  });
@@ -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) {
@@ -67,6 +67,7 @@ import {
67
67
  Plus,
68
68
  Terminal,
69
69
  Trash2,
70
+ Loader2,
70
71
  Users,
71
72
  } from 'lucide-react';
72
73
  import { useShallow } from 'zustand/react/shallow';
@@ -1065,6 +1066,7 @@ export const TeamDetailView = ({
1065
1066
 
1066
1067
  const [sendDialogOpen, setSendDialogOpen] = useState(false);
1067
1068
  const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
1069
+ const [deleting, setDeleting] = useState(false);
1068
1070
  const [trashOpen, setTrashOpen] = useState(false);
1069
1071
  const [sendDialogRecipient, setSendDialogRecipient] = useState<string | undefined>(undefined);
1070
1072
  const [sendDialogDefaultText, setSendDialogDefaultText] = useState<string | undefined>(undefined);
@@ -1618,16 +1620,13 @@ export const TeamDetailView = ({
1618
1620
  );
1619
1621
 
1620
1622
  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]);
1623
+ await api.ccSettings.restart();
1624
+ // Wait for cc-connect to come back, then refresh
1625
+ setTimeout(() => {
1626
+ void fetchTeams();
1627
+ void selectTeam(teamName);
1628
+ }, 3000);
1629
+ }, [fetchTeams, selectTeam, teamName]);
1631
1630
 
1632
1631
  const handleSaveAndRestartFromEdit = useCallback(
1633
1632
  async (runtimeConfig: {
@@ -1827,29 +1826,25 @@ export const TeamDetailView = ({
1827
1826
  }, []);
1828
1827
 
1829
1828
  const confirmDeleteTeam = useCallback((): void => {
1830
- setDeleteConfirmOpen(false);
1829
+ setDeleting(true);
1831
1830
  void (async () => {
1832
1831
  try {
1833
1832
  const result = await deleteTeam(teamName);
1834
1833
  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
- }
1834
+ await api.ccSettings.restart();
1845
1835
  }
1836
+ await fetchTeams();
1837
+ setDeleteConfirmOpen(false);
1846
1838
  if (tabId) closeTab(tabId);
1847
1839
  openTeamsTab();
1848
- } catch {
1849
- // error is shown via store
1840
+ } catch (err) {
1841
+ console.error('Failed to delete team:', err);
1842
+ setDeleteConfirmOpen(false);
1843
+ } finally {
1844
+ setDeleting(false);
1850
1845
  }
1851
1846
  })();
1852
- }, [teamName, deleteTeam, openTeamsTab, closeTab, tabId]);
1847
+ }, [teamName, deleteTeam, openTeamsTab, closeTab, tabId, fetchTeams]);
1853
1848
 
1854
1849
  const handleCreateTask = (
1855
1850
  subject: string,
@@ -2163,19 +2158,21 @@ export const TeamDetailView = ({
2163
2158
  {isTeamProvisioning ? '团队仍在编排中,暂时无法编辑' : '编辑团队'}
2164
2159
  </TooltipContent>
2165
2160
  </Tooltip>
2166
- <Tooltip>
2167
- <TooltipTrigger asChild>
2168
- <Button
2169
- variant="ghost"
2170
- size="sm"
2171
- className="h-7 gap-1 px-2 text-xs text-red-400 hover:bg-red-500/10 hover:text-red-300"
2172
- onClick={handleDeleteTeam}
2173
- >
2174
- <Trash2 size={12} />
2175
- </Button>
2176
- </TooltipTrigger>
2177
- <TooltipContent side="bottom">删除团队</TooltipContent>
2178
- </Tooltip>
2161
+ {teamName !== 'default' && teamName !== 'my-project' && (
2162
+ <Tooltip>
2163
+ <TooltipTrigger asChild>
2164
+ <Button
2165
+ variant="ghost"
2166
+ size="sm"
2167
+ className="h-7 gap-1 px-2 text-xs text-red-400 hover:bg-red-500/10 hover:text-red-300"
2168
+ onClick={handleDeleteTeam}
2169
+ >
2170
+ <Trash2 size={12} />
2171
+ </Button>
2172
+ </TooltipTrigger>
2173
+ <TooltipContent side="bottom">删除团队</TooltipContent>
2174
+ </Tooltip>
2175
+ )}
2179
2176
  </div>
2180
2177
  </div>
2181
2178
  {data.config.description && (
@@ -2653,6 +2650,8 @@ export const TeamDetailView = ({
2653
2650
  currentManagedSources={currentManagedSources}
2654
2651
  currentDisabledCommands={currentDisabledCommands}
2655
2652
  currentPlatformAllowFrom={currentPlatformAllowFrom}
2653
+ currentProviderRefs={data.providerRefs ?? []}
2654
+ globalProviders={data.globalProviders ?? []}
2656
2655
  currentMembers={membersWithLiveBranches.filter((m) => !isLeadMember(m))}
2657
2656
  leadMember={membersWithLiveBranches.find((m) => isLeadMember(m)) ?? null}
2658
2657
  resolvedMemberColorMap={resolvedMemberColorMap}
@@ -2665,9 +2664,10 @@ export const TeamDetailView = ({
2665
2664
  void fetchTeams();
2666
2665
  void selectTeam(teamName);
2667
2666
  }}
2668
- onDeleteTeam={handleDeleteTeam}
2667
+ onDeleteTeam={
2668
+ teamName !== 'default' && teamName !== 'my-project' ? handleDeleteTeam : undefined
2669
+ }
2669
2670
  onRestartTeam={handleRestartTeamFromEdit}
2670
- onSaveAndRestart={handleSaveAndRestartFromEdit}
2671
2671
  />
2672
2672
 
2673
2673
  <Dialog
@@ -2704,7 +2704,12 @@ export const TeamDetailView = ({
2704
2704
  </DialogContent>
2705
2705
  </Dialog>
2706
2706
 
2707
- <Dialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
2707
+ <Dialog
2708
+ open={deleteConfirmOpen}
2709
+ onOpenChange={(v) => {
2710
+ if (!deleting) setDeleteConfirmOpen(v);
2711
+ }}
2712
+ >
2708
2713
  <DialogContent className="max-w-sm">
2709
2714
  <DialogHeader>
2710
2715
  <DialogTitle>删除团队</DialogTitle>
@@ -2717,8 +2722,14 @@ export const TeamDetailView = ({
2717
2722
  <Button variant="ghost" size="sm" onClick={() => setDeleteConfirmOpen(false)}>
2718
2723
  取消
2719
2724
  </Button>
2720
- <Button variant="destructive" size="sm" onClick={confirmDeleteTeam}>
2721
- 删除
2725
+ <Button
2726
+ variant="destructive"
2727
+ size="sm"
2728
+ onClick={confirmDeleteTeam}
2729
+ disabled={deleting}
2730
+ >
2731
+ {deleting && <Loader2 size={14} className="mr-1.5 animate-spin" />}
2732
+ 删除并重启
2722
2733
  </Button>
2723
2734
  </DialogFooter>
2724
2735
  </DialogContent>
@@ -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 {
@@ -42,6 +43,7 @@ import {
42
43
  Download,
43
44
  FolderOpen,
44
45
  GitBranch,
46
+ Loader2,
45
47
  Play,
46
48
  RotateCcw,
47
49
  Search,
@@ -275,6 +277,7 @@ export const TeamListView = (): React.JSX.Element => {
275
277
  const [copyData, setCopyData] = useState<TeamCopyData | null>(null);
276
278
  const [searchQuery, setSearchQuery] = useState('');
277
279
  const [filter, setFilter] = useState<TeamListFilterState>(EMPTY_TEAM_FILTER);
280
+ const [deletingTeamName, setDeletingTeamName] = useState<string | null>(null);
278
281
  const [aliveTeams, setAliveTeams] = useState<string[]>([]);
279
282
  const {
280
283
  teams,
@@ -532,33 +535,28 @@ export const TeamListView = (): React.JSX.Element => {
532
535
  }
533
536
  const confirmed = await confirm({
534
537
  title: '删除团队',
535
- message: `确定删除团队“${teamDisplayName}”吗?此操作会同步删除 cc-connect 项目并移除本地团队数据。`,
536
- confirmLabel: '删除',
538
+ message: `确定删除团队”${teamDisplayName}”吗?此操作会同步删除 cc-connect 项目并移除本地团队数据。`,
539
+ confirmLabel: '删除并重启',
537
540
  cancelLabel: '取消',
538
541
  variant: 'danger',
539
542
  });
540
543
  if (confirmed) {
544
+ setDeletingTeamName(teamName);
541
545
  try {
542
546
  const result = await deleteTeam(teamName);
543
547
  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
- }
548
+ await api.ccSettings.restart();
554
549
  }
555
- } catch {
556
- // error via store
550
+ await fetchTeams();
551
+ } catch (err) {
552
+ console.error('Failed to delete team:', err);
553
+ } finally {
554
+ setDeletingTeamName(null);
557
555
  }
558
556
  }
559
557
  })();
560
558
  },
561
- [deleteTeam, teams]
559
+ [deleteTeam, teams, fetchTeams]
562
560
  );
563
561
 
564
562
  const handleRestoreTeam = useCallback(
@@ -674,6 +672,19 @@ export const TeamListView = (): React.JSX.Element => {
674
672
  void fetchAllTasks();
675
673
  }, [fetchTeams, fetchAllTasks]);
676
674
 
675
+ useEffect(() => {
676
+ const refresh = () => {
677
+ void fetchTeams();
678
+ void fetchAllTasks();
679
+ };
680
+ window.addEventListener(OPEN_HERMIT_EVENTS.runtimeRestarted, refresh);
681
+ window.addEventListener(OPEN_HERMIT_EVENTS.teamsChanged, refresh);
682
+ return () => {
683
+ window.removeEventListener(OPEN_HERMIT_EVENTS.runtimeRestarted, refresh);
684
+ window.removeEventListener(OPEN_HERMIT_EVENTS.teamsChanged, refresh);
685
+ };
686
+ }, [fetchTeams, fetchAllTasks]);
687
+
677
688
  const taskCountsByTeam = useMemo(() => buildTaskCountsByTeam(globalTasks), [globalTasks]);
678
689
 
679
690
  const activeTeams = useMemo<ActiveTeamRef[]>(() => {
@@ -798,6 +809,7 @@ export const TeamListView = (): React.JSX.Element => {
798
809
  async (request: TeamCreateRequest) => {
799
810
  await createTeam(request);
800
811
  await Promise.all([fetchTeams(), fetchAllTasks()]);
812
+ emitOpenHermitEvent(OPEN_HERMIT_EVENTS.teamsChanged);
801
813
  window.setTimeout(() => {
802
814
  void fetchTeams();
803
815
  void fetchAllTasks();
@@ -1094,6 +1106,7 @@ export const TeamListView = (): React.JSX.Element => {
1094
1106
  const matchesCurrentProject = currentProjectPath
1095
1107
  ? teamMatchesProjectSelection(team, currentProjectPath)
1096
1108
  : false;
1109
+ const isDeleting = deletingTeamName === team.teamName;
1097
1110
  return (
1098
1111
  <div
1099
1112
  key={team.teamName}
@@ -1101,14 +1114,22 @@ export const TeamListView = (): React.JSX.Element => {
1101
1114
  tabIndex={0}
1102
1115
  className="group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border border-l-[3px] border-[var(--color-border)] bg-[var(--color-surface)] p-4 hover:bg-[var(--color-surface-raised)]"
1103
1116
  style={teamColorSet ? { borderLeftColor: teamColorSet.border } : undefined}
1104
- onClick={() => openTeamTab(team.teamName, team.projectPath)}
1117
+ onClick={
1118
+ isDeleting ? undefined : () => openTeamTab(team.teamName, team.projectPath)
1119
+ }
1105
1120
  onKeyDown={(e) => {
1106
1121
  if (e.key === 'Enter' || e.key === ' ') {
1107
1122
  e.preventDefault();
1108
- openTeamTab(team.teamName, team.projectPath);
1123
+ if (!isDeleting) openTeamTab(team.teamName, team.projectPath);
1109
1124
  }
1110
1125
  }}
1111
1126
  >
1127
+ {isDeleting && (
1128
+ <div className="absolute inset-0 z-20 flex items-center justify-center gap-2 bg-black/60 text-sm text-white">
1129
+ <Loader2 size={16} className="animate-spin" />
1130
+ 删除并重启中…
1131
+ </div>
1132
+ )}
1112
1133
  <div className="flex flex-1 flex-col">
1113
1134
  <div className="flex items-start justify-between gap-2">
1114
1135
  <div className="flex min-w-0 flex-1 items-center gap-2">
@@ -1165,20 +1186,22 @@ export const TeamListView = (): React.JSX.Element => {
1165
1186
  <TooltipContent side="bottom">复制团队</TooltipContent>
1166
1187
  </Tooltip>
1167
1188
  )}
1168
- <Tooltip>
1169
- <TooltipTrigger asChild>
1170
- <button
1171
- type="button"
1172
- className="shrink-0 rounded p-1 text-[var(--color-text-muted)] opacity-0 transition-opacity hover:bg-red-500/10 hover:text-red-300 group-hover:opacity-100"
1173
- onClick={(e) =>
1174
- handleDeleteTeam(team.teamName, !!team.pendingCreate, e)
1175
- }
1176
- >
1177
- <Trash2 size={14} />
1178
- </button>
1179
- </TooltipTrigger>
1180
- <TooltipContent side="bottom">删除团队</TooltipContent>
1181
- </Tooltip>
1189
+ {team.teamName !== 'default' && team.teamName !== 'my-project' && (
1190
+ <Tooltip>
1191
+ <TooltipTrigger asChild>
1192
+ <button
1193
+ type="button"
1194
+ className="shrink-0 rounded p-1 text-[var(--color-text-muted)] opacity-0 transition-opacity hover:bg-red-500/10 hover:text-red-300 group-hover:opacity-100"
1195
+ onClick={(e) =>
1196
+ handleDeleteTeam(team.teamName, !!team.pendingCreate, e)
1197
+ }
1198
+ >
1199
+ <Trash2 size={14} />
1200
+ </button>
1201
+ </TooltipTrigger>
1202
+ <TooltipContent side="bottom">删除团队</TooltipContent>
1203
+ </Tooltip>
1204
+ )}
1182
1205
  </div>
1183
1206
  </div>
1184
1207
  <div className="mt-2 flex min-h-10 items-start gap-2">