@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.
- package/README.md +4 -0
- package/bin/hermit.mjs +90 -50
- package/dist-renderer/assets/{ProjectEditorOverlay-CZ1LI0pd.js → ProjectEditorOverlay-Byepdwo2.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-DvyxOPvU.js → TeamGraphOverlay-vvWu-2c9.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-D8IKEBh4.js → _basePickBy-DfsmMgXN.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BDBpSAHY.js → _baseUniq-Bve-IKz5.js} +1 -1
- package/dist-renderer/assets/{arc-CZagJLek.js → arc-4cbkhagw.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-Cq083Uu6.js → architectureDiagram-VXUJARFQ-CC9i0bMK.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-CIEANvSv.js → blockDiagram-VD42YOAC-BjFruJ65.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Ca4h_BlA.js → c4Diagram-YG6GDRKO-CrYzsQC1.js} +1 -1
- package/dist-renderer/assets/channel-BMMyVRy4.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DW9HjNgA.js → chunk-4BX2VUAB-Bb9MCt7J.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-BddOEwBk.js → chunk-55IACEB6-BpOVOXVa.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-Cmu8IjLN.js → chunk-B4BG7PRW-GtEiO-7n.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BU7nEH8e.js → chunk-DI55MBZ5-BRlzcOEj.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-BZNeq0CB.js → chunk-FMBD7UC4-DcvMVOZx.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DMb3gYzN.js → chunk-QN33PNHL-B9pkjVpd.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-8wSSvqz0.js → chunk-QZHKN3VN-DzHPSm01.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-B4OLgw45.js → chunk-TZMSLE5B-BU9c0Hcn.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-Dz1VG1T3.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-Dz1VG1T3.js +1 -0
- package/dist-renderer/assets/clone-COsIIGZQ.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CS9moNeT.js → cose-bilkent-S5V4N54A-BqOg0x3V.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-3pzdMwjZ.js → dagre-6UL2VRFP-C9JTWefj.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-CGTnMY5C.js → diagram-PSM6KHXK-ljleG6ui.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-Cyzp8dOw.js → diagram-QEK2KX5R-BbV-WSTr.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-D_5SvdXI.js → diagram-S2PKOQOG-CKi3DFby.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-bWPYfs9c.js → erDiagram-Q2GNP2WA-D3HE7b-j.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-C5z47CKB.js → flowDiagram-NV44I4VS-C2yLRmM0.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-eye9Cv6S.js → ganttDiagram-JELNMOA3-XEV4KtUf.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DekQZoF8.js → gitGraphDiagram-V2S2FVAM-ufaCCg7c.js} +1 -1
- package/dist-renderer/assets/{graph-kNnl-9Fl.js → graph-BzPvdBp0.js} +1 -1
- package/dist-renderer/assets/{index-CrOkTuIK.js → index-A5CMVuXA.js} +525 -525
- package/dist-renderer/assets/{index-CI2l57ID.js → index-BprOls_t.js} +1 -1
- package/dist-renderer/assets/index-CWpFqEvz.css +1 -0
- package/dist-renderer/assets/{index-DB0k9yRL.js → index-Cr91T9ef.js} +1 -1
- package/dist-renderer/assets/{index-CnxDIJh8.js → index-DHq6dXy7.js} +1 -1
- package/dist-renderer/assets/{index-ATiHUmmE.js → index-DUIDxnaf.js} +1 -1
- package/dist-renderer/assets/{index-hGBnMHVl.js → index-yNYjzR2R.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D3ilrGOS.js → infoDiagram-HS3SLOUP-DKP5zgHc.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOCG2Rrk.js → journeyDiagram-XKPGCS4Q-Omd7tmzE.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-hZ03nCkx.js → kanban-definition-3W4ZIXB7-D7yw9yIY.js} +1 -1
- package/dist-renderer/assets/{layout-D5Mqy2My.js → layout-DZxAqFuM.js} +1 -1
- package/dist-renderer/assets/{linear-Clp9OpXU.js → linear-BXWJygRB.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D-w-qhNz.js → mindmap-definition-VGOIOE7T-BfJ09SBb.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D9K0-ecY.js → pieDiagram-ADFJNKIX-BYaLQhXj.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-B2f19Ki3.js → quadrantDiagram-AYHSOK5B-DeA0B1fw.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-VezNpCaU.js → requirementDiagram-UZGBJVZJ-DnFWn7-v.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BpHjEoR9.js → sankeyDiagram-TZEHDZUN-L9bek20k.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-Tdb7IZ4t.js → sequenceDiagram-WL72ISMW-BBmcJUXb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BNA5Q-zz.js → stateDiagram-FKZM4ZOC-DrwPQvTq.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-w_4GEN2a.js → stateDiagram-v2-4FDKWEC3-BOUQrTH6.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BghtWMWY.js → timeline-definition-IT6M3QCI-Dldh9vsj.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-BVquWjHf.js → treemap-GDKQZRPO-BsGSs8-P.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BSV5wEDU.js → xychartDiagram-PRI3JC2R-BsR_bj-d.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +2 -2
- package/src/main/server.ts +38 -19
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +2 -0
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +2 -0
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +2 -0
- package/src/renderer/components/settings/sections/HarnessSection.tsx +11 -0
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +10 -2
- package/src/renderer/components/team/TeamDetailView.tsx +15 -24
- package/src/renderer/components/team/TeamListView.tsx +21 -12
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +120 -0
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +95 -2
- package/src/renderer/utils/openHermitEvents.ts +9 -0
- package/src/shared/types/team.ts +5 -0
- package/dist-renderer/assets/channel-CqQK8EX1.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-9A3Cg8IA.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-9A3Cg8IA.js +0 -1
- package/dist-renderer/assets/clone-Bnr-WjeU.js +0 -1
- package/dist-renderer/assets/index-C4x095x4.css +0 -1
package/src/main/server.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
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
|
-
|
|
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
|
|