@yancyyu/openhermit 1.5.8 → 1.5.10
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 +10 -1
- package/bin/alias-loader.mjs +51 -0
- package/bin/hermit.mjs +14 -5
- package/dist-renderer/assets/{ProjectEditorOverlay-BNoDw9T1.js → ProjectEditorOverlay-C5D83Zxv.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-CfGRKQIu.js → TeamGraphOverlay-ajzuM1-u.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Ct8Hm5_h.js → _basePickBy-C9H2zmVj.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BofrAFBx.js → _baseUniq-CpGZGemc.js} +1 -1
- package/dist-renderer/assets/{arc-AbJgatzR.js → arc-CbGBDw-m.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-gpniCJVk.js → architectureDiagram-VXUJARFQ-nuKXUIpb.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-aBbbmONC.js → blockDiagram-VD42YOAC-DHUUE7Jc.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DJio1IsU.js → c4Diagram-YG6GDRKO-OILHhqLM.js} +1 -1
- package/dist-renderer/assets/channel-DpUKLLrj.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-D1_HKao2.js → chunk-4BX2VUAB-dqNpZaQ8.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-NAmVxF4k.js → chunk-55IACEB6-BCoSJQM-.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-Ce829laz.js → chunk-B4BG7PRW-BLbg8yVR.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-Ct2Le12y.js → chunk-DI55MBZ5-CUUWOs1Q.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-Cie3DzKk.js → chunk-FMBD7UC4-D9geTN5P.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-4f5Yb50e.js → chunk-QN33PNHL-BGT8-BRX.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-D9ranl9c.js → chunk-QZHKN3VN-CC8ebGaM.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-bdGZWlEy.js → chunk-TZMSLE5B-CajekcT6.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-LL85aSlz.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-LL85aSlz.js +1 -0
- package/dist-renderer/assets/clone-BHWsFzFA.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-C6tvfcVi.js → cose-bilkent-S5V4N54A-C_x7hSy3.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-B-4qcZam.js → dagre-6UL2VRFP-C4Y1k4DZ.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-CwT3TLjx.js → diagram-PSM6KHXK-oRIeULoh.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-BWH6-ZFd.js → diagram-QEK2KX5R-DwSqw5HF.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DfpPnfi1.js → diagram-S2PKOQOG-DqjGYje2.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BFbEFR4x.js → erDiagram-Q2GNP2WA-CEV5bBgg.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-Dg3cf5hW.js → flowDiagram-NV44I4VS-BQQkrRyu.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-B21y55W5.js → ganttDiagram-JELNMOA3-CLy4WR1G.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BDV3BJzn.js → gitGraphDiagram-V2S2FVAM-6W3ioQu_.js} +1 -1
- package/dist-renderer/assets/{graph-BfaZ4hZt.js → graph-BnLKQvhH.js} +1 -1
- package/dist-renderer/assets/{index-CCqtDawH.js → index-B4aiRxoU.js} +1 -1
- package/dist-renderer/assets/{index-pMg_LlsS.js → index-B8lKqPVq.js} +1 -1
- package/dist-renderer/assets/{index-CZltVMDP.js → index-BRuhNKyU.js} +12 -12
- package/dist-renderer/assets/{index-BMXHMpkG.js → index-BufvLVIl.js} +1 -1
- package/dist-renderer/assets/{index-Ct0-y9TF.js → index-C1xShqKH.js} +1 -1
- package/dist-renderer/assets/{index-CVMSpK8C.js → index-zIOLLI7O.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DvMlS0CL.js → infoDiagram-HS3SLOUP-BoBweEEY.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DIyMluRv.js → journeyDiagram-XKPGCS4Q-DLL0V5oP.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CVOx8f-7.js → kanban-definition-3W4ZIXB7-HsFtEDG3.js} +1 -1
- package/dist-renderer/assets/{layout-BPKIXUf4.js → layout-ClIooAAq.js} +1 -1
- package/dist-renderer/assets/{linear-CScZGLr2.js → linear-r3RJcj8y.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-CmDQ7Wo6.js → mindmap-definition-VGOIOE7T-BA_P1U4V.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-DbVClin-.js → pieDiagram-ADFJNKIX-CzPAfkTB.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CAB0MYcW.js → quadrantDiagram-AYHSOK5B-PvdPWzFJ.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-w2Lfpg3T.js → requirementDiagram-UZGBJVZJ-CHqIL_Od.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-kvG1QoKY.js → sankeyDiagram-TZEHDZUN-ConzpACM.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DCVBQ23J.js → sequenceDiagram-WL72ISMW-Zryq4oxP.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-ItZ0JBvq.js → stateDiagram-FKZM4ZOC-BA9V7NHF.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-Hpmw4dMm.js → stateDiagram-v2-4FDKWEC3-CGnaujD-.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BzSFaAjV.js → timeline-definition-IT6M3QCI-DPs2ZjMm.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-fSz4hQn0.js → treemap-GDKQZRPO-B0lzrLxb.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CT1kaGlv.js → xychartDiagram-PRI3JC2R-CINGmMxX.js} +1 -1
- package/dist-renderer/index.html +1 -1
- package/package.json +2 -1
- package/src/main/server.ts +993 -764
- package/src/main/services/UpdateService.ts +4 -1
- package/src/main/services/ccConnect/CcConnectBridge.ts +1 -8
- package/src/main/services/ccConnect/CcConnectClient.ts +7 -2
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +14 -4
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +11 -6
- package/src/renderer/App.tsx +18 -7
- package/src/renderer/api/httpClient.ts +136 -42
- package/src/renderer/components/chat/ChatHistory.tsx +11 -8
- package/src/renderer/components/dashboard/DashboardView.tsx +4 -2
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +2 -7
- package/src/renderer/components/layout/Sidebar.tsx +3 -1
- package/src/renderer/components/schedules/SchedulesView.tsx +15 -13
- package/src/renderer/components/settings/SettingsTabs.tsx +2 -1
- package/src/renderer/components/settings/hooks/useSettingsHandlers.ts +4 -5
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +19 -4
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +63 -59
- package/src/renderer/components/settings/sections/GeneralSection.tsx +5 -11
- package/src/renderer/components/settings/sections/HarnessSection.tsx +30 -15
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +110 -51
- package/src/renderer/components/sidebar/SidebarSessions.tsx +100 -67
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +26 -43
- package/src/renderer/components/team/CcSessionsSection.tsx +34 -14
- package/src/renderer/components/team/TeamDetailView.tsx +150 -148
- package/src/renderer/components/team/TeamEmptyState.tsx +27 -16
- package/src/renderer/components/team/TeamListView.tsx +4 -2
- package/src/renderer/components/team/activity/ActivityItem.tsx +6 -1
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +282 -75
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +2 -1
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +64 -21
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +68 -19
- package/src/renderer/components/team/dialogs/ProjectPathSelector.tsx +20 -16
- package/src/renderer/components/team/dialogs/platformMeta.ts +66 -11
- package/src/renderer/components/team/editor/EditorFileTree.tsx +9 -7
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +7 -10
- package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +1 -3
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +1 -5
- package/src/renderer/components/team/messages/MessageComposer.tsx +3 -1
- package/src/renderer/components/team/messages/MessagesPanel.tsx +34 -26
- package/src/renderer/components/team/schedule/CcCronScheduleDialog.tsx +1 -3
- package/src/renderer/components/team/schedule/ScheduleSection.tsx +9 -10
- package/src/renderer/store/slices/scheduleSlice.ts +4 -1
- package/src/renderer/store/slices/teamSlice.ts +3 -1
- package/src/shared/types/api.ts +70 -21
- package/src/shared/utils/leadDetection.ts +5 -1
- package/tsconfig.json +26 -0
- package/dist-renderer/assets/channel-CZ8sd5Xf.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CMcfSKj5.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CMcfSKj5.js +0 -1
- package/dist-renderer/assets/clone-CMuwA8RV.js +0 -1
|
@@ -103,7 +103,10 @@ export class UpdateService {
|
|
|
103
103
|
return false;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
onProgress?.({
|
|
106
|
+
onProgress?.({
|
|
107
|
+
phase: 'downloading',
|
|
108
|
+
message: `Downloading Hermit ${versionInfo.latestVersion}...`,
|
|
109
|
+
});
|
|
107
110
|
|
|
108
111
|
onProgress?.({ phase: 'installing', message: 'Installing update...' });
|
|
109
112
|
execSync('npm update -g hermit', { cwd: REPO_ROOT, stdio: 'pipe' });
|
|
@@ -26,14 +26,7 @@ const logger = createLogger('CcConnectBridge');
|
|
|
26
26
|
|
|
27
27
|
const RECONNECT_DELAY_MS = 3_000;
|
|
28
28
|
const PING_INTERVAL_MS = 30_000;
|
|
29
|
-
const BRIDGE_CAPABILITIES = [
|
|
30
|
-
'text',
|
|
31
|
-
'buttons',
|
|
32
|
-
'card',
|
|
33
|
-
'typing',
|
|
34
|
-
'update_message',
|
|
35
|
-
'preview',
|
|
36
|
-
];
|
|
29
|
+
const BRIDGE_CAPABILITIES = ['text', 'buttons', 'card', 'typing', 'update_message', 'preview'];
|
|
37
30
|
|
|
38
31
|
export interface CcConnectBridgeEvents {
|
|
39
32
|
connected: [];
|
|
@@ -154,7 +154,9 @@ export class CcConnectClient {
|
|
|
154
154
|
logger.info(`cc-connect project "${name}" stopped`);
|
|
155
155
|
} catch (err) {
|
|
156
156
|
// Project might already not exist — log but don't throw
|
|
157
|
-
logger.warn(
|
|
157
|
+
logger.warn(
|
|
158
|
+
`Failed to stop project "${name}": ${err instanceof Error ? err.message : String(err)}`
|
|
159
|
+
);
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -374,7 +376,10 @@ export class CcConnectClient {
|
|
|
374
376
|
return this.request<CcCronJob>('POST', '/api/v1/cron', input);
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
async updateCronJob(
|
|
379
|
+
async updateCronJob(
|
|
380
|
+
id: string,
|
|
381
|
+
patch: Partial<CcCreateCronJobRequest> & { enabled?: boolean }
|
|
382
|
+
): Promise<CcCronJob> {
|
|
378
383
|
return this.request<CcCronJob>('PATCH', `/api/v1/cron/${encodeURIComponent(id)}`, patch);
|
|
379
384
|
}
|
|
380
385
|
|
|
@@ -76,7 +76,9 @@ export class TeamProvisioningService {
|
|
|
76
76
|
logger.info(`cc-connect restarted after creating project ${manifest.bindProject}`);
|
|
77
77
|
}
|
|
78
78
|
} catch (err) {
|
|
79
|
-
logger.warn(
|
|
79
|
+
logger.warn(
|
|
80
|
+
`cc-connect project creation failed (team=${slug}): ${err instanceof Error ? err.message : String(err)}`
|
|
81
|
+
);
|
|
80
82
|
// 不中断流程 — project 可能已存在
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -194,7 +196,9 @@ export class TeamProvisioningService {
|
|
|
194
196
|
`dispatched task ${task.id} → team:${targetSlug} (cc-project:${targetManifest.bindProject})`
|
|
195
197
|
);
|
|
196
198
|
} catch (err) {
|
|
197
|
-
logger.warn(
|
|
199
|
+
logger.warn(
|
|
200
|
+
`dispatchTask failed (target=${targetSlug}): ${err instanceof Error ? err.message : String(err)}`
|
|
201
|
+
);
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
// 记录消息到来源团队
|
|
@@ -218,7 +222,11 @@ export class TeamProvisioningService {
|
|
|
218
222
|
return this.workspace.createTask(teamSlug, payload);
|
|
219
223
|
}
|
|
220
224
|
|
|
221
|
-
patchTask(
|
|
225
|
+
patchTask(
|
|
226
|
+
teamSlug: string,
|
|
227
|
+
taskId: string,
|
|
228
|
+
patch: Parameters<TeamWorkspaceService['patchTask']>[2]
|
|
229
|
+
) {
|
|
222
230
|
return this.workspace.patchTask(teamSlug, taskId, patch);
|
|
223
231
|
}
|
|
224
232
|
|
|
@@ -269,7 +277,9 @@ export class TeamProvisioningService {
|
|
|
269
277
|
await fs.promises.writeFile(settingsPath, JSON.stringify(updated, null, 2), 'utf8');
|
|
270
278
|
logger.info(`injected MCP config → ${settingsPath}`);
|
|
271
279
|
} catch (err) {
|
|
272
|
-
logger.warn(
|
|
280
|
+
logger.warn(
|
|
281
|
+
`MCP config injection failed (workDir=${workDir}): ${err instanceof Error ? err.message : String(err)}`
|
|
282
|
+
);
|
|
273
283
|
}
|
|
274
284
|
}
|
|
275
285
|
}
|
|
@@ -230,7 +230,9 @@ export class TeamWorkspaceService {
|
|
|
230
230
|
try {
|
|
231
231
|
return await this.readTeamManifest(projectName);
|
|
232
232
|
} catch {
|
|
233
|
-
const match = (await this.listTeams()).find(
|
|
233
|
+
const match = (await this.listTeams()).find(
|
|
234
|
+
(manifest) => manifest.bindProject === projectName
|
|
235
|
+
);
|
|
234
236
|
if (match) return match;
|
|
235
237
|
throw new Error(`团队 "${projectName}" 不存在 (${teamsRoot()})`);
|
|
236
238
|
}
|
|
@@ -325,7 +327,11 @@ export class TeamWorkspaceService {
|
|
|
325
327
|
const lines = raw.split(/\n+/).filter(Boolean);
|
|
326
328
|
const all: GroupMessage[] = [];
|
|
327
329
|
for (const line of lines) {
|
|
328
|
-
try {
|
|
330
|
+
try {
|
|
331
|
+
all.push(JSON.parse(line) as GroupMessage);
|
|
332
|
+
} catch {
|
|
333
|
+
/* skip */
|
|
334
|
+
}
|
|
329
335
|
}
|
|
330
336
|
return all.length <= limit ? all : all.slice(all.length - limit);
|
|
331
337
|
}
|
|
@@ -334,10 +340,9 @@ export class TeamWorkspaceService {
|
|
|
334
340
|
|
|
335
341
|
private async readBoard(teamSlug: string): Promise<{ tasks: Task[] }> {
|
|
336
342
|
const storageSlug = await this.resolveStorageSlug(teamSlug);
|
|
337
|
-
return readJson<{ tasks: Task[] }>(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
);
|
|
343
|
+
return readJson<{ tasks: Task[] }>(path.join(teamRoot(storageSlug), 'tasks', 'board.json'), {
|
|
344
|
+
tasks: [],
|
|
345
|
+
});
|
|
341
346
|
}
|
|
342
347
|
|
|
343
348
|
private async writeBoard(teamSlug: string, board: { tasks: Task[] }): Promise<void> {
|
package/src/renderer/App.tsx
CHANGED
|
@@ -41,7 +41,9 @@ function buildPathForTab(activeTab: Tab | null): string {
|
|
|
41
41
|
}
|
|
42
42
|
switch (activeTab.type) {
|
|
43
43
|
case 'team':
|
|
44
|
-
return activeTab.teamName
|
|
44
|
+
return activeTab.teamName
|
|
45
|
+
? `/team/${encodeURIComponent(activeTab.teamName)}`
|
|
46
|
+
: DEFAULT_APP_PATH;
|
|
45
47
|
case 'teams':
|
|
46
48
|
return '/teams';
|
|
47
49
|
case 'settings':
|
|
@@ -59,9 +61,7 @@ function buildPathForTab(activeTab: Tab | null): string {
|
|
|
59
61
|
case 'notifications':
|
|
60
62
|
return '/notifications';
|
|
61
63
|
case 'graph':
|
|
62
|
-
return activeTab.teamName
|
|
63
|
-
? `/graph/${encodeURIComponent(activeTab.teamName)}`
|
|
64
|
-
: '/graph';
|
|
64
|
+
return activeTab.teamName ? `/graph/${encodeURIComponent(activeTab.teamName)}` : '/graph';
|
|
65
65
|
case 'report':
|
|
66
66
|
return activeTab.projectId && activeTab.sessionId
|
|
67
67
|
? `/report/${encodeURIComponent(activeTab.projectId)}/${encodeURIComponent(activeTab.sessionId)}`
|
|
@@ -87,14 +87,20 @@ function useTeamPersistence() {
|
|
|
87
87
|
}
|
|
88
88
|
}, 500);
|
|
89
89
|
}
|
|
90
|
-
} catch {
|
|
90
|
+
} catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
91
93
|
}, []);
|
|
92
94
|
|
|
93
95
|
// Save team when it changes
|
|
94
96
|
useEffect(() => {
|
|
95
97
|
const unsub = useStore.subscribe((state, prevState) => {
|
|
96
98
|
if (state.selectedTeamName !== prevState.selectedTeamName && state.selectedTeamName) {
|
|
97
|
-
try {
|
|
99
|
+
try {
|
|
100
|
+
localStorage.setItem(PERSIST_KEY, state.selectedTeamName);
|
|
101
|
+
} catch {
|
|
102
|
+
/* ignore */
|
|
103
|
+
}
|
|
98
104
|
}
|
|
99
105
|
});
|
|
100
106
|
return unsub;
|
|
@@ -161,7 +167,12 @@ function useTabPathPersistence() {
|
|
|
161
167
|
break;
|
|
162
168
|
case 'report':
|
|
163
169
|
if (arg1 && arg2) {
|
|
164
|
-
state.openTab({
|
|
170
|
+
state.openTab({
|
|
171
|
+
type: 'report',
|
|
172
|
+
label: 'Session Report',
|
|
173
|
+
projectId: arg1,
|
|
174
|
+
sessionId: arg2,
|
|
175
|
+
});
|
|
165
176
|
}
|
|
166
177
|
break;
|
|
167
178
|
default:
|
|
@@ -339,11 +339,23 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
339
339
|
getAppVersion = (): Promise<string> => this.get<string>('/api/version');
|
|
340
340
|
|
|
341
341
|
hermitConfig = {
|
|
342
|
-
get: async (): Promise<{
|
|
343
|
-
|
|
342
|
+
get: async (): Promise<{
|
|
343
|
+
ccBaseUrl: string;
|
|
344
|
+
ccBridgeUrl: string;
|
|
345
|
+
ccToken: string;
|
|
346
|
+
ccTokenSet: boolean;
|
|
347
|
+
}> => {
|
|
348
|
+
const res = await this.get<{
|
|
349
|
+
ok: boolean;
|
|
350
|
+
data: { ccBaseUrl: string; ccBridgeUrl: string; ccToken: string; ccTokenSet: boolean };
|
|
351
|
+
}>('/api/hermit-config');
|
|
344
352
|
return res.data;
|
|
345
353
|
},
|
|
346
|
-
update: async (patch: {
|
|
354
|
+
update: async (patch: {
|
|
355
|
+
ccBaseUrl?: string;
|
|
356
|
+
ccToken?: string;
|
|
357
|
+
ccBridgeUrl?: string;
|
|
358
|
+
}): Promise<void> => {
|
|
347
359
|
await this.post('/api/hermit-config', patch);
|
|
348
360
|
},
|
|
349
361
|
getRaw: async (): Promise<{ path: string; content: string }> => {
|
|
@@ -363,7 +375,10 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
363
375
|
return res.data;
|
|
364
376
|
},
|
|
365
377
|
update: async (patch: Record<string, unknown>): Promise<{ needsRestart: boolean }> => {
|
|
366
|
-
const res = await this.post<{ ok: boolean; data: { needsRestart: boolean } }>(
|
|
378
|
+
const res = await this.post<{ ok: boolean; data: { needsRestart: boolean } }>(
|
|
379
|
+
'/api/cc-config',
|
|
380
|
+
patch
|
|
381
|
+
);
|
|
367
382
|
return res.data;
|
|
368
383
|
},
|
|
369
384
|
getRaw: async (): Promise<{ path: string; content: string }> => {
|
|
@@ -379,7 +394,9 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
379
394
|
|
|
380
395
|
ccSettings = {
|
|
381
396
|
get: async (): Promise<Record<string, unknown>> => {
|
|
382
|
-
const res = await this.get<{ ok: boolean; data: Record<string, unknown> }>(
|
|
397
|
+
const res = await this.get<{ ok: boolean; data: Record<string, unknown> }>(
|
|
398
|
+
'/api/cc-settings'
|
|
399
|
+
);
|
|
383
400
|
return res.data;
|
|
384
401
|
},
|
|
385
402
|
patch: async (patch: Record<string, unknown>): Promise<void> => {
|
|
@@ -395,57 +412,127 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
395
412
|
|
|
396
413
|
// cc-connect setup flows (QR code + manual platform binding)
|
|
397
414
|
ccSetup = {
|
|
398
|
-
feishuBegin: async (): Promise<{
|
|
399
|
-
|
|
415
|
+
feishuBegin: async (): Promise<{
|
|
416
|
+
device_code: string;
|
|
417
|
+
qr_url: string;
|
|
418
|
+
base_url?: string;
|
|
419
|
+
interval: number;
|
|
420
|
+
expires_in: number;
|
|
421
|
+
}> => {
|
|
422
|
+
const res = await this.post<{
|
|
423
|
+
ok: boolean;
|
|
424
|
+
data: {
|
|
425
|
+
device_code: string;
|
|
426
|
+
qr_url: string;
|
|
427
|
+
base_url?: string;
|
|
428
|
+
interval: number;
|
|
429
|
+
expires_in: number;
|
|
430
|
+
};
|
|
431
|
+
}>('/api/setup/feishu/begin', {});
|
|
400
432
|
return res.data;
|
|
401
433
|
},
|
|
402
|
-
feishuPoll: async (
|
|
403
|
-
|
|
404
|
-
|
|
434
|
+
feishuPoll: async (
|
|
435
|
+
deviceCode: string,
|
|
436
|
+
baseUrl?: string
|
|
437
|
+
): Promise<{
|
|
438
|
+
status: string;
|
|
439
|
+
base_url?: string;
|
|
440
|
+
app_id?: string;
|
|
441
|
+
app_secret?: string;
|
|
442
|
+
platform?: string;
|
|
443
|
+
owner_open_id?: string;
|
|
444
|
+
slow_down?: boolean;
|
|
445
|
+
error?: string;
|
|
405
446
|
}> => {
|
|
406
|
-
const res = await this.post<{
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
447
|
+
const res = await this.post<{
|
|
448
|
+
ok: boolean;
|
|
449
|
+
data: {
|
|
450
|
+
status: string;
|
|
451
|
+
base_url?: string;
|
|
452
|
+
app_id?: string;
|
|
453
|
+
app_secret?: string;
|
|
454
|
+
platform?: string;
|
|
455
|
+
owner_open_id?: string;
|
|
456
|
+
slow_down?: boolean;
|
|
457
|
+
error?: string;
|
|
458
|
+
};
|
|
459
|
+
}>('/api/setup/feishu/poll', { device_code: deviceCode, base_url: baseUrl });
|
|
410
460
|
return res.data;
|
|
411
461
|
},
|
|
412
462
|
feishuSave: async (params: {
|
|
413
|
-
project: string;
|
|
414
|
-
|
|
415
|
-
|
|
463
|
+
project: string;
|
|
464
|
+
app_id: string;
|
|
465
|
+
app_secret: string;
|
|
466
|
+
platform_type?: string;
|
|
467
|
+
owner_open_id?: string;
|
|
468
|
+
work_dir?: string;
|
|
469
|
+
agent_type?: string;
|
|
416
470
|
}): Promise<{ message: string; restart_required: boolean }> => {
|
|
417
|
-
const res = await this.post<{
|
|
471
|
+
const res = await this.post<{
|
|
472
|
+
ok: boolean;
|
|
473
|
+
data: { message: string; restart_required: boolean };
|
|
474
|
+
}>('/api/setup/feishu/save', params);
|
|
418
475
|
return res.data;
|
|
419
476
|
},
|
|
420
477
|
|
|
421
478
|
weixinBegin: async (apiUrl?: string): Promise<{ qr_key: string; qr_url: string }> => {
|
|
422
|
-
const res = await this.post<{ ok: boolean; data: { qr_key: string; qr_url: string } }>(
|
|
479
|
+
const res = await this.post<{ ok: boolean; data: { qr_key: string; qr_url: string } }>(
|
|
480
|
+
'/api/setup/weixin/begin',
|
|
481
|
+
{ api_url: apiUrl }
|
|
482
|
+
);
|
|
423
483
|
return res.data;
|
|
424
484
|
},
|
|
425
|
-
weixinPoll: async (
|
|
426
|
-
|
|
427
|
-
|
|
485
|
+
weixinPoll: async (
|
|
486
|
+
qrKey: string,
|
|
487
|
+
apiUrl?: string
|
|
488
|
+
): Promise<{
|
|
489
|
+
status: string;
|
|
490
|
+
bot_token?: string;
|
|
491
|
+
ilink_bot_id?: string;
|
|
492
|
+
base_url?: string;
|
|
493
|
+
ilink_user_id?: string;
|
|
428
494
|
}> => {
|
|
429
|
-
const res = await this.post<{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
495
|
+
const res = await this.post<{
|
|
496
|
+
ok: boolean;
|
|
497
|
+
data: {
|
|
498
|
+
status: string;
|
|
499
|
+
bot_token?: string;
|
|
500
|
+
ilink_bot_id?: string;
|
|
501
|
+
base_url?: string;
|
|
502
|
+
ilink_user_id?: string;
|
|
503
|
+
};
|
|
504
|
+
}>('/api/setup/weixin/poll', { qr_key: qrKey, api_url: apiUrl });
|
|
433
505
|
return res.data;
|
|
434
506
|
},
|
|
435
507
|
weixinSave: async (params: {
|
|
436
|
-
project: string;
|
|
437
|
-
|
|
438
|
-
|
|
508
|
+
project: string;
|
|
509
|
+
token: string;
|
|
510
|
+
base_url?: string;
|
|
511
|
+
ilink_bot_id?: string;
|
|
512
|
+
ilink_user_id?: string;
|
|
513
|
+
work_dir?: string;
|
|
514
|
+
agent_type?: string;
|
|
439
515
|
}): Promise<{ message: string; restart_required: boolean }> => {
|
|
440
|
-
const res = await this.post<{
|
|
516
|
+
const res = await this.post<{
|
|
517
|
+
ok: boolean;
|
|
518
|
+
data: { message: string; restart_required: boolean };
|
|
519
|
+
}>('/api/setup/weixin/save', params);
|
|
441
520
|
return res.data;
|
|
442
521
|
},
|
|
443
522
|
|
|
444
|
-
addPlatform: async (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
523
|
+
addPlatform: async (
|
|
524
|
+
projectName: string,
|
|
525
|
+
body: {
|
|
526
|
+
type: string;
|
|
527
|
+
options?: Record<string, unknown>;
|
|
528
|
+
work_dir?: string;
|
|
529
|
+
agent_type?: string;
|
|
530
|
+
}
|
|
531
|
+
): Promise<{ message: string; restart_required: boolean }> => {
|
|
532
|
+
const res = await this.post<{
|
|
533
|
+
ok: boolean;
|
|
534
|
+
data: { message: string; restart_required: boolean };
|
|
535
|
+
}>(`/api/projects/${encodeURIComponent(projectName)}/add-platform`, body);
|
|
449
536
|
return res.data;
|
|
450
537
|
},
|
|
451
538
|
};
|
|
@@ -721,11 +808,14 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
721
808
|
// Fallback: return home directory as default
|
|
722
809
|
return [process.env.HOME ?? '/'];
|
|
723
810
|
},
|
|
724
|
-
browseFolders: async (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
811
|
+
browseFolders: async (
|
|
812
|
+
dirPath?: string
|
|
813
|
+
): Promise<{ path: string; dirs: string[]; hasParent: boolean }> => {
|
|
814
|
+
const res = await this.post<{
|
|
815
|
+
success: boolean;
|
|
816
|
+
data?: { path: string; dirs: string[]; hasParent: boolean };
|
|
817
|
+
error?: string;
|
|
818
|
+
}>('/api/config/browse-folders', { path: dirPath ?? '' });
|
|
729
819
|
if (!res.success) throw new Error(res.error ?? '无法浏览目录');
|
|
730
820
|
return res.data!;
|
|
731
821
|
},
|
|
@@ -1184,7 +1274,9 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
1184
1274
|
await this.post(`/api/teams/${encodeURIComponent(teamName)}/process-send`, { message });
|
|
1185
1275
|
},
|
|
1186
1276
|
setCollaboration: async (teamName: string, collaboration: boolean): Promise<void> => {
|
|
1187
|
-
await this.patch(`/api/teams/${encodeURIComponent(teamName)}/collaboration`, {
|
|
1277
|
+
await this.patch(`/api/teams/${encodeURIComponent(teamName)}/collaboration`, {
|
|
1278
|
+
collaboration,
|
|
1279
|
+
});
|
|
1188
1280
|
},
|
|
1189
1281
|
processAlive: async (teamName: string): Promise<boolean> => {
|
|
1190
1282
|
try {
|
|
@@ -1464,7 +1556,9 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
1464
1556
|
);
|
|
1465
1557
|
},
|
|
1466
1558
|
cancelSession: async (teamName: string, sessionId: string): Promise<void> =>
|
|
1467
|
-
await this.delete(
|
|
1559
|
+
await this.delete(
|
|
1560
|
+
`/api/teams/${encodeURIComponent(teamName)}/sessions/${encodeURIComponent(sessionId)}`
|
|
1561
|
+
),
|
|
1468
1562
|
onTeamChange: (callback: (event: unknown, data: TeamChangeEvent) => void): (() => void) => {
|
|
1469
1563
|
return this.addEventListener('team-change', (data: unknown) =>
|
|
1470
1564
|
callback(null, data as TeamChangeEvent)
|
|
@@ -278,14 +278,17 @@ export const ChatHistory = ({ tabId }: ChatHistoryProps): JSX.Element => {
|
|
|
278
278
|
}
|
|
279
279
|
}, [pageFromLatest, totalConversationPages]);
|
|
280
280
|
|
|
281
|
-
const goToConversationPage = useCallback(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
scrollContainerRef.current
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
281
|
+
const goToConversationPage = useCallback(
|
|
282
|
+
(nextPageFromLatest: number): void => {
|
|
283
|
+
setPageFromLatest(Math.max(0, Math.min(nextPageFromLatest, totalConversationPages - 1)));
|
|
284
|
+
requestAnimationFrame(() => {
|
|
285
|
+
if (scrollContainerRef.current) {
|
|
286
|
+
scrollContainerRef.current.scrollTop = 0;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
[totalConversationPages]
|
|
291
|
+
);
|
|
289
292
|
|
|
290
293
|
const setSearchQueryForTab = useCallback(
|
|
291
294
|
(query: string): void => {
|
|
@@ -124,7 +124,8 @@ export const DashboardView = (): React.JSX.Element => {
|
|
|
124
124
|
teamsLoading: state.teamsLoading,
|
|
125
125
|
}))
|
|
126
126
|
);
|
|
127
|
-
const showQuickstartGuide =
|
|
127
|
+
const showQuickstartGuide =
|
|
128
|
+
searchQuery.trim().length === 0 && !teamsLoading && teams.length === 0;
|
|
128
129
|
|
|
129
130
|
return (
|
|
130
131
|
<div className="relative flex-1 overflow-auto bg-surface">
|
|
@@ -144,7 +145,8 @@ export const DashboardView = (): React.JSX.Element => {
|
|
|
144
145
|
Hermit:一人公司的 AI 团队控制台
|
|
145
146
|
</h1>
|
|
146
147
|
<p className="mt-2 text-sm text-text-secondary">
|
|
147
|
-
几乎覆盖所有主流
|
|
148
|
+
几乎覆盖所有主流
|
|
149
|
+
Harness,支持全渠道接入,把团队编排、消息协作、任务推进和运行状态放在同一个工作台。
|
|
148
150
|
</p>
|
|
149
151
|
</div>
|
|
150
152
|
<div className="grid gap-4 px-6 py-5 md:grid-cols-3">
|
|
@@ -264,10 +264,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
264
264
|
projectPath,
|
|
265
265
|
]);
|
|
266
266
|
|
|
267
|
-
const isRefreshing =
|
|
268
|
-
effectiveCliStatusLoading ||
|
|
269
|
-
mcpBrowseLoading ||
|
|
270
|
-
skillsLoading;
|
|
267
|
+
const isRefreshing = effectiveCliStatusLoading || mcpBrowseLoading || skillsLoading;
|
|
271
268
|
const mcpMutationDisableReason = useMemo(
|
|
272
269
|
() =>
|
|
273
270
|
getExtensionActionDisableReason({
|
|
@@ -507,9 +504,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
507
504
|
)}
|
|
508
505
|
<Tabs
|
|
509
506
|
value={tabState.activeSubTab}
|
|
510
|
-
onValueChange={(v) =>
|
|
511
|
-
tabState.setActiveSubTab(v as 'mcp-servers' | 'skills')
|
|
512
|
-
}
|
|
507
|
+
onValueChange={(v) => tabState.setActiveSubTab(v as 'mcp-servers' | 'skills')}
|
|
513
508
|
>
|
|
514
509
|
<div className="-mx-6 flex items-end justify-between border-b border-border px-6">
|
|
515
510
|
<TabsList className="gap-1 rounded-b-none">
|
|
@@ -127,7 +127,9 @@ export const Sidebar = (): React.JSX.Element => {
|
|
|
127
127
|
aria-controls="sidebar-workspace-panel"
|
|
128
128
|
id="sidebar-tab-workspace"
|
|
129
129
|
className={`relative px-3 py-1.5 text-[11px] font-medium transition-colors ${
|
|
130
|
-
sidebarTab === 'workspace'
|
|
130
|
+
sidebarTab === 'workspace'
|
|
131
|
+
? 'text-text'
|
|
132
|
+
: 'text-text-muted hover:text-text-secondary'
|
|
131
133
|
}`}
|
|
132
134
|
style={
|
|
133
135
|
sidebarTab === 'workspace'
|
|
@@ -143,7 +143,11 @@ const ScheduleListItem = ({
|
|
|
143
143
|
onClick={() => onDelete(schedule.id)}
|
|
144
144
|
disabled={deleting}
|
|
145
145
|
>
|
|
146
|
-
{deleting ?
|
|
146
|
+
{deleting ? (
|
|
147
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
148
|
+
) : (
|
|
149
|
+
<Trash2 className="size-3.5" />
|
|
150
|
+
)}
|
|
147
151
|
</Button>
|
|
148
152
|
</TooltipTrigger>
|
|
149
153
|
<TooltipContent side="top">删除</TooltipContent>
|
|
@@ -295,18 +299,16 @@ export const SchedulesView = (): React.JSX.Element => {
|
|
|
295
299
|
// Filter by search query
|
|
296
300
|
if (searchQuery.trim()) {
|
|
297
301
|
const query = searchQuery.toLowerCase();
|
|
298
|
-
result = result.filter(
|
|
299
|
-
(s)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
);
|
|
302
|
+
result = result.filter((s) => {
|
|
303
|
+
const teamDisplayName = getTeamDisplayName(s.teamName).toLowerCase();
|
|
304
|
+
return (
|
|
305
|
+
(s.label ?? '').toLowerCase().includes(query) ||
|
|
306
|
+
teamDisplayName.includes(query) ||
|
|
307
|
+
s.teamName.toLowerCase().includes(query) ||
|
|
308
|
+
s.launchConfig.prompt.toLowerCase().includes(query) ||
|
|
309
|
+
getCronDescription(s.cronExpression).toLowerCase().includes(query)
|
|
310
|
+
);
|
|
311
|
+
});
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
// Sort: active first, then by next run ascending
|
|
@@ -35,7 +35,8 @@ const tabs: TabConfig[] = [
|
|
|
35
35
|
id: 'channels',
|
|
36
36
|
label: '渠道',
|
|
37
37
|
icon: PlugZap,
|
|
38
|
-
description:
|
|
38
|
+
description:
|
|
39
|
+
'管理飞书、微信、Telegram 等消息平台的接入配置。每个 cc-connect 项目可绑定一个或多个渠道。',
|
|
39
40
|
},
|
|
40
41
|
{
|
|
41
42
|
id: 'harness',
|
|
@@ -105,11 +105,10 @@ export function useSettingsHandlers({
|
|
|
105
105
|
(value: string) => {
|
|
106
106
|
fireAndForgetConfigUpdate('general', { agentLanguage: value });
|
|
107
107
|
// Sync to cc-connect: map 'system' → browser primary language code
|
|
108
|
-
const ccLang =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
api.ccSettings.patch({ language: ccLang }).catch(() => {/* best-effort */});
|
|
108
|
+
const ccLang = value === 'system' ? (navigator.language.split('-')[0] ?? 'zh') : value;
|
|
109
|
+
api.ccSettings.patch({ language: ccLang }).catch(() => {
|
|
110
|
+
/* best-effort */
|
|
111
|
+
});
|
|
113
112
|
},
|
|
114
113
|
[fireAndForgetConfigUpdate]
|
|
115
114
|
);
|
|
@@ -2,7 +2,13 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { api } from '@renderer/api';
|
|
4
4
|
import { Button } from '@renderer/components/ui/button';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogFooter,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
} from '@renderer/components/ui/dialog';
|
|
6
12
|
import { Textarea } from '@renderer/components/ui/textarea';
|
|
7
13
|
import appIcon from '@renderer/favicon.png';
|
|
8
14
|
import { Check, FileEdit, Loader2, RefreshCw, RotateCcw, X } from 'lucide-react';
|
|
@@ -67,7 +73,12 @@ const CcConnectConfigRawDialog = ({
|
|
|
67
73
|
if (!open) return null;
|
|
68
74
|
|
|
69
75
|
return (
|
|
70
|
-
<Dialog
|
|
76
|
+
<Dialog
|
|
77
|
+
open={open}
|
|
78
|
+
onOpenChange={(next) => {
|
|
79
|
+
if (!next) onClose();
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
71
82
|
<DialogContent className="max-w-4xl">
|
|
72
83
|
<DialogHeader>
|
|
73
84
|
<DialogTitle>编辑 配置</DialogTitle>
|
|
@@ -93,7 +104,8 @@ const CcConnectConfigRawDialog = ({
|
|
|
93
104
|
</div>
|
|
94
105
|
)}
|
|
95
106
|
<p className="text-xs text-[var(--color-text-muted)]">
|
|
96
|
-
保存后将直接覆盖 Hermit 管理的 cc-connect config.toml。若修改了端口或
|
|
107
|
+
保存后将直接覆盖 Hermit 管理的 cc-connect config.toml。若修改了端口或
|
|
108
|
+
token,请点击“重启服务”生效。
|
|
97
109
|
</p>
|
|
98
110
|
</div>
|
|
99
111
|
<DialogFooter>
|
|
@@ -207,7 +219,10 @@ export const AdvancedSection = ({}: AdvancedSectionProps): React.JSX.Element =>
|
|
|
207
219
|
</button>
|
|
208
220
|
</div>
|
|
209
221
|
{restartMsg && (
|
|
210
|
-
<p
|
|
222
|
+
<p
|
|
223
|
+
className="mb-2 text-xs"
|
|
224
|
+
style={{ color: restartMsg.includes('失败') ? '#f87171' : '#4ade80' }}
|
|
225
|
+
>
|
|
211
226
|
{restartMsg}
|
|
212
227
|
</p>
|
|
213
228
|
)}
|