@yancyyu/openhermit 1.6.37 → 1.6.39
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/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
- package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
- package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
- package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
- package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
- package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
- package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
- package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
- package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
- package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
- package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
- package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
- package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
- package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
- package/dist-renderer/index.html +20 -53
- package/package.json +25 -18
- package/src/main/ipc/extensions.ts +30 -50
- package/src/main/server.ts +890 -247
- package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
- package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
- package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
- package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
- package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
- package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
- package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
- package/src/main/utils/teamProjectResolution.ts +15 -0
- package/src/renderer/App.tsx +8 -4
- package/src/renderer/api/httpClient.ts +174 -38
- package/src/renderer/api/providers.ts +23 -2
- package/src/renderer/assets/participant-avatars/01.svg +3 -0
- package/src/renderer/assets/participant-avatars/02.svg +3 -0
- package/src/renderer/assets/participant-avatars/03.svg +3 -0
- package/src/renderer/assets/participant-avatars/04.svg +3 -0
- package/src/renderer/assets/participant-avatars/05.svg +3 -0
- package/src/renderer/assets/participant-avatars/06.svg +3 -0
- package/src/renderer/assets/participant-avatars/07.svg +3 -0
- package/src/renderer/assets/participant-avatars/08.svg +3 -0
- package/src/renderer/assets/participant-avatars/09.svg +3 -0
- package/src/renderer/assets/participant-avatars/10.svg +3 -0
- package/src/renderer/assets/participant-avatars/11.svg +3 -0
- package/src/renderer/assets/participant-avatars/12.svg +3 -0
- package/src/renderer/assets/participant-avatars/13.svg +3 -0
- package/src/renderer/components/common/TerminalPane.tsx +213 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
- package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
- package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
- package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
- package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
- package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
- package/src/renderer/components/layout/PaneContent.tsx +8 -1
- package/src/renderer/components/layout/Sidebar.tsx +11 -54
- package/src/renderer/components/layout/SortableTab.tsx +20 -31
- package/src/renderer/components/layout/TabBar.tsx +1 -1
- package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
- package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
- package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
- package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
- package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
- package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
- package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
- package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
- package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
- package/src/renderer/components/schedules/calendar/index.ts +2 -0
- package/src/renderer/components/schedules/calendar/types.ts +44 -0
- package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
- package/src/renderer/components/settings/SettingsView.tsx +30 -35
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
- package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
- package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
- package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
- package/src/renderer/components/settings/sections/index.ts +0 -1
- package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
- package/src/renderer/components/splash/splashScene.ts +121 -929
- package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
- package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
- package/src/renderer/components/tasks/TasksView.tsx +112 -134
- package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
- package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
- package/src/renderer/components/team/TeamDetailView.tsx +325 -114
- package/src/renderer/components/team/TeamListView.tsx +108 -123
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
- package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
- package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
- package/src/renderer/components/team/members/MemberCard.tsx +14 -47
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
- package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
- package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
- package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
- package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
- package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
- package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
- package/src/renderer/components/team/tools/McpChip.tsx +45 -0
- package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
- package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
- package/src/renderer/index.css +39 -22
- package/src/renderer/index.html +17 -50
- package/src/renderer/store/index.ts +2 -1
- package/src/renderer/store/slices/scheduleSlice.ts +1 -1
- package/src/renderer/store/slices/teamSlice.ts +45 -168
- package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
- package/src/renderer/utils/memberHelpers.ts +5 -17
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
- package/src/renderer/utils/providerSlashCommands.ts +0 -5
- package/src/renderer/utils/scheduleFormatters.ts +3 -1
- package/src/renderer/utils/teamMessageFiltering.ts +14 -1
- package/src/renderer/utils/teamModelAvailability.ts +18 -2
- package/src/shared/types/api.ts +121 -2
- package/src/shared/types/ccConnect.ts +2 -0
- package/src/shared/types/extensions/api.ts +9 -0
- package/src/shared/types/extensions/index.ts +4 -0
- package/src/shared/types/extensions/mcp.ts +41 -0
- package/src/shared/types/index.ts +3 -0
- package/src/shared/types/systemManager.ts +49 -0
- package/src/shared/types/team.ts +29 -0
- package/src/shared/types/terminal.ts +4 -2
- package/src/shared/utils/extensionNormalizers.ts +29 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
- package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
- package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
- package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
- package/src/main/services/extensions/install/McpInstallService.ts +0 -407
- package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
- package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
- package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
- package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
SystemManagerConfig,
|
|
7
|
+
SystemManagerConfigPatch,
|
|
8
|
+
SystemManagerStatus,
|
|
9
|
+
} from '@shared/types/systemManager';
|
|
10
|
+
|
|
11
|
+
const CONFIG_FILE = 'system-manager.json';
|
|
12
|
+
|
|
13
|
+
function hermitHome(): string {
|
|
14
|
+
return process.env.HERMIT_HOME || path.join(os.homedir(), '.hermit');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function expandHome(input: string): string {
|
|
18
|
+
const normalized = input.trim().replace(/^~/, '~');
|
|
19
|
+
if (normalized === '~') return os.homedir();
|
|
20
|
+
if (normalized.startsWith('~/')) return path.join(os.homedir(), normalized.slice(2));
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function isDirectory(dirPath: string): Promise<boolean> {
|
|
25
|
+
try {
|
|
26
|
+
return (await stat(dirPath)).isDirectory();
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function commandExists(command: string): Promise<boolean> {
|
|
33
|
+
const paths = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
34
|
+
for (const dir of paths) {
|
|
35
|
+
try {
|
|
36
|
+
await access(path.join(dir, command));
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
// keep looking
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class SystemManagerConfigService {
|
|
46
|
+
private readonly configPath = path.join(hermitHome(), CONFIG_FILE);
|
|
47
|
+
|
|
48
|
+
constructor(private readonly defaultWorkDir: string) {}
|
|
49
|
+
|
|
50
|
+
async getConfig(): Promise<SystemManagerConfig> {
|
|
51
|
+
try {
|
|
52
|
+
const raw = await readFile(this.configPath, 'utf-8');
|
|
53
|
+
const parsed = JSON.parse(raw) as Partial<SystemManagerConfig>;
|
|
54
|
+
const selectedWorkDir = await this.normalizeDirectory(
|
|
55
|
+
parsed.selectedWorkDir || this.defaultWorkDir,
|
|
56
|
+
'selectedWorkDir'
|
|
57
|
+
);
|
|
58
|
+
const workflowFolder = parsed.workflowFolder
|
|
59
|
+
? await this.normalizeDirectory(parsed.workflowFolder, 'workflowFolder')
|
|
60
|
+
: undefined;
|
|
61
|
+
return {
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
selectedWorkDir,
|
|
64
|
+
...(workflowFolder ? { workflowFolder } : {}),
|
|
65
|
+
updatedAt:
|
|
66
|
+
typeof parsed.updatedAt === 'string' ? parsed.updatedAt : new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return {
|
|
70
|
+
schemaVersion: 1,
|
|
71
|
+
selectedWorkDir: this.defaultWorkDir,
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async updateConfig(patch: SystemManagerConfigPatch): Promise<SystemManagerConfig> {
|
|
78
|
+
const current = await this.getConfig();
|
|
79
|
+
const next: SystemManagerConfig = {
|
|
80
|
+
...current,
|
|
81
|
+
updatedAt: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (typeof patch.selectedWorkDir === 'string') {
|
|
85
|
+
next.selectedWorkDir = await this.normalizeDirectory(
|
|
86
|
+
patch.selectedWorkDir,
|
|
87
|
+
'selectedWorkDir'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (patch.workflowFolder === null) {
|
|
91
|
+
delete next.workflowFolder;
|
|
92
|
+
} else if (typeof patch.workflowFolder === 'string') {
|
|
93
|
+
next.workflowFolder = await this.normalizeDirectory(patch.workflowFolder, 'workflowFolder');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await mkdir(path.dirname(this.configPath), { recursive: true });
|
|
97
|
+
await writeFile(this.configPath, JSON.stringify(next, null, 2), 'utf-8');
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getStatus(): Promise<SystemManagerStatus> {
|
|
102
|
+
const config = await this.getConfig();
|
|
103
|
+
const hasClaude = await commandExists('claude');
|
|
104
|
+
return {
|
|
105
|
+
displayName: '控制台',
|
|
106
|
+
defaultWorkDir: this.defaultWorkDir,
|
|
107
|
+
selectedWorkDir: config.selectedWorkDir,
|
|
108
|
+
...(config.workflowFolder ? { workflowFolder: config.workflowFolder } : {}),
|
|
109
|
+
claudeCommand: 'claude',
|
|
110
|
+
localStatus: hasClaude ? 'ready' : 'missing-claude',
|
|
111
|
+
...(hasClaude ? {} : { error: '未在 PATH 中找到 claude 命令' }),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async normalizeDirectory(input: string, fieldName: string): Promise<string> {
|
|
116
|
+
const resolved = path.resolve(expandHome(input));
|
|
117
|
+
if (!(await isDirectory(resolved))) {
|
|
118
|
+
throw new Error(`${fieldName} 不是有效目录: ${resolved}`);
|
|
119
|
+
}
|
|
120
|
+
return resolved;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { spawn as spawnChild } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { stat } from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
import type { PtySpawnOptions } from '@shared/types/terminal';
|
|
7
|
+
import * as pty from 'node-pty';
|
|
8
|
+
|
|
9
|
+
const PYTHON_PTY_BRIDGE = String.raw`
|
|
10
|
+
import errno
|
|
11
|
+
import os
|
|
12
|
+
import pty
|
|
13
|
+
import select
|
|
14
|
+
import signal
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
cmd = sys.argv[1:]
|
|
18
|
+
child_pid = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def terminate(signum, _frame):
|
|
22
|
+
if child_pid:
|
|
23
|
+
try:
|
|
24
|
+
os.kill(child_pid, signum)
|
|
25
|
+
except OSError:
|
|
26
|
+
pass
|
|
27
|
+
sys.exit(128 + signum)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
signal.signal(signal.SIGTERM, terminate)
|
|
31
|
+
signal.signal(signal.SIGINT, terminate)
|
|
32
|
+
|
|
33
|
+
child_pid, fd = pty.fork()
|
|
34
|
+
if child_pid == 0:
|
|
35
|
+
os.execvpe(cmd[0], cmd, os.environ)
|
|
36
|
+
|
|
37
|
+
while True:
|
|
38
|
+
try:
|
|
39
|
+
readable, _, _ = select.select([fd, sys.stdin.fileno()], [], [])
|
|
40
|
+
except OSError:
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
if fd in readable:
|
|
44
|
+
try:
|
|
45
|
+
data = os.read(fd, 4096)
|
|
46
|
+
except OSError:
|
|
47
|
+
break
|
|
48
|
+
if not data:
|
|
49
|
+
break
|
|
50
|
+
os.write(sys.stdout.fileno(), data)
|
|
51
|
+
|
|
52
|
+
if sys.stdin.fileno() in readable:
|
|
53
|
+
try:
|
|
54
|
+
data = os.read(sys.stdin.fileno(), 4096)
|
|
55
|
+
except OSError as err:
|
|
56
|
+
if err.errno == errno.EIO:
|
|
57
|
+
data = b''
|
|
58
|
+
else:
|
|
59
|
+
raise
|
|
60
|
+
if data:
|
|
61
|
+
os.write(fd, data)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
_, status = os.waitpid(child_pid, 0)
|
|
65
|
+
except ChildProcessError:
|
|
66
|
+
sys.exit(0)
|
|
67
|
+
sys.exit(os.waitstatus_to_exitcode(status))
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
interface ManagedProcess {
|
|
71
|
+
id: string;
|
|
72
|
+
pid: number;
|
|
73
|
+
command: string;
|
|
74
|
+
args: string[];
|
|
75
|
+
cwd: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
write(data: string): void;
|
|
78
|
+
resize(cols: number, rows: number): void;
|
|
79
|
+
kill(signal?: NodeJS.Signals): void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const TERMINAL_KILL_TIMEOUT_MS = 1_500;
|
|
83
|
+
const TERMINAL_FORCE_KILL_TIMEOUT_MS = 1_500;
|
|
84
|
+
|
|
85
|
+
export type TerminalDataEvent = { ptyId: string; data: string };
|
|
86
|
+
export type TerminalExitEvent = { ptyId: string; exitCode: number };
|
|
87
|
+
|
|
88
|
+
export class SystemManagerPtyService extends EventEmitter {
|
|
89
|
+
private readonly sessions = new Map<string, ManagedProcess>();
|
|
90
|
+
|
|
91
|
+
async spawn(options: PtySpawnOptions = {}): Promise<string> {
|
|
92
|
+
const command = options.command || 'claude';
|
|
93
|
+
const args = options.args ?? [];
|
|
94
|
+
const cwd = options.cwd || process.cwd();
|
|
95
|
+
const cwdStat = await stat(cwd);
|
|
96
|
+
if (!cwdStat.isDirectory()) {
|
|
97
|
+
throw new Error(`cwd 不是有效目录: ${cwd}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const id = `pty-${randomUUID()}`;
|
|
101
|
+
const env = {
|
|
102
|
+
...process.env,
|
|
103
|
+
TERM: 'xterm-256color',
|
|
104
|
+
COLORTERM: 'truecolor',
|
|
105
|
+
...(options.env ?? {}),
|
|
106
|
+
} as Record<string, string>;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const proc = pty.spawn(command, args, {
|
|
110
|
+
name: 'xterm-256color',
|
|
111
|
+
cols: options.cols ?? 120,
|
|
112
|
+
rows: options.rows ?? 34,
|
|
113
|
+
cwd,
|
|
114
|
+
env,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
this.sessions.set(id, {
|
|
118
|
+
id,
|
|
119
|
+
pid: proc.pid,
|
|
120
|
+
command,
|
|
121
|
+
args,
|
|
122
|
+
cwd,
|
|
123
|
+
createdAt: new Date().toISOString(),
|
|
124
|
+
write: (data) => proc.write(data),
|
|
125
|
+
resize: (cols, rows) => proc.resize(Math.max(20, cols), Math.max(5, rows)),
|
|
126
|
+
kill: (signal) => proc.kill(signal),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
proc.onData((data) => {
|
|
130
|
+
this.emit('data', { ptyId: id, data } satisfies TerminalDataEvent);
|
|
131
|
+
});
|
|
132
|
+
proc.onExit(({ exitCode }) => {
|
|
133
|
+
if (!this.sessions.delete(id)) return;
|
|
134
|
+
this.emit('exit', { ptyId: id, exitCode } satisfies TerminalExitEvent);
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const child = spawnChild('python3', ['-u', '-c', PYTHON_PTY_BRIDGE, command, ...args], {
|
|
138
|
+
cwd,
|
|
139
|
+
env,
|
|
140
|
+
stdio: 'pipe',
|
|
141
|
+
});
|
|
142
|
+
this.sessions.set(id, {
|
|
143
|
+
id,
|
|
144
|
+
pid: child.pid ?? -1,
|
|
145
|
+
command,
|
|
146
|
+
args,
|
|
147
|
+
cwd,
|
|
148
|
+
createdAt: new Date().toISOString(),
|
|
149
|
+
write: (data) => child.stdin.write(data),
|
|
150
|
+
resize: () => {},
|
|
151
|
+
kill: (signal) => child.kill(signal),
|
|
152
|
+
});
|
|
153
|
+
this.emit('data', {
|
|
154
|
+
ptyId: id,
|
|
155
|
+
data: `[33m[Hermit] node-pty unavailable (${err instanceof Error ? err.message : String(err)}); using python PTY fallback.[0m\r\n`,
|
|
156
|
+
} satisfies TerminalDataEvent);
|
|
157
|
+
child.stdout.on('data', (data) => {
|
|
158
|
+
this.emit('data', { ptyId: id, data: data.toString() } satisfies TerminalDataEvent);
|
|
159
|
+
});
|
|
160
|
+
child.stderr.on('data', (data) => {
|
|
161
|
+
this.emit('data', { ptyId: id, data: data.toString() } satisfies TerminalDataEvent);
|
|
162
|
+
});
|
|
163
|
+
child.on('error', (error) => {
|
|
164
|
+
if (!this.sessions.delete(id)) return;
|
|
165
|
+
this.emit('data', {
|
|
166
|
+
ptyId: id,
|
|
167
|
+
data: `[31m[Hermit] failed to start process: ${error.message}[0m\r\n`,
|
|
168
|
+
} satisfies TerminalDataEvent);
|
|
169
|
+
this.emit('exit', { ptyId: id, exitCode: 1 } satisfies TerminalExitEvent);
|
|
170
|
+
});
|
|
171
|
+
child.on('exit', (exitCode) => {
|
|
172
|
+
if (!this.sessions.delete(id)) return;
|
|
173
|
+
this.emit('exit', { ptyId: id, exitCode: exitCode ?? 0 } satisfies TerminalExitEvent);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return id;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
write(ptyId: string, data: string): void {
|
|
181
|
+
const session = this.sessions.get(ptyId);
|
|
182
|
+
if (!session) throw new Error(`PTY 不存在: ${ptyId}`);
|
|
183
|
+
session.write(data);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
resize(ptyId: string, cols: number, rows: number): void {
|
|
187
|
+
const session = this.sessions.get(ptyId);
|
|
188
|
+
if (!session) return;
|
|
189
|
+
session.resize(cols, rows);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async kill(ptyId: string): Promise<void> {
|
|
193
|
+
const session = this.sessions.get(ptyId);
|
|
194
|
+
if (!session) return;
|
|
195
|
+
|
|
196
|
+
await new Promise<void>((resolve) => {
|
|
197
|
+
let settled = false;
|
|
198
|
+
let forceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
199
|
+
const finish = (): void => {
|
|
200
|
+
if (settled) return;
|
|
201
|
+
settled = true;
|
|
202
|
+
if (forceTimer) clearTimeout(forceTimer);
|
|
203
|
+
this.off('exit', onExit);
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
const onExit = (event: TerminalExitEvent): void => {
|
|
207
|
+
if (event.ptyId === ptyId) finish();
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this.on('exit', onExit);
|
|
211
|
+
session.kill();
|
|
212
|
+
forceTimer = setTimeout(() => {
|
|
213
|
+
if (!this.sessions.has(ptyId)) {
|
|
214
|
+
finish();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
session.kill('SIGKILL');
|
|
218
|
+
}, TERMINAL_KILL_TIMEOUT_MS);
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
if (this.sessions.delete(ptyId)) {
|
|
221
|
+
this.emit('exit', { ptyId, exitCode: 0 } satisfies TerminalExitEvent);
|
|
222
|
+
}
|
|
223
|
+
finish();
|
|
224
|
+
}, TERMINAL_KILL_TIMEOUT_MS + TERMINAL_FORCE_KILL_TIMEOUT_MS);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
killAll(): void {
|
|
229
|
+
for (const id of [...this.sessions.keys()]) {
|
|
230
|
+
void this.kill(id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WorkflowPromptContentResponse,
|
|
8
|
+
WorkflowPromptListResponse,
|
|
9
|
+
WorkflowPromptSummary,
|
|
10
|
+
} from '@shared/types/systemManager';
|
|
11
|
+
|
|
12
|
+
const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.prompt', '.workflow']);
|
|
13
|
+
const MAX_PROMPT_BYTES = 256 * 1024;
|
|
14
|
+
|
|
15
|
+
function expandHome(input: string): string {
|
|
16
|
+
const normalized = input.trim().replace(/^~/, '~');
|
|
17
|
+
if (normalized === '~') return os.homedir();
|
|
18
|
+
if (normalized.startsWith('~/')) return path.join(os.homedir(), normalized.slice(2));
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function promptId(filePath: string): string {
|
|
23
|
+
return createHash('sha256').update(filePath).digest('hex').slice(0, 16);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function labelFromFilename(filename: string): string {
|
|
27
|
+
return path.basename(filename, path.extname(filename)).replace(/[-_]+/g, ' ').trim() || filename;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class WorkflowPromptService {
|
|
31
|
+
async list(folderInput: string): Promise<WorkflowPromptListResponse> {
|
|
32
|
+
const folder = path.resolve(expandHome(folderInput));
|
|
33
|
+
const folderStat = await stat(folder);
|
|
34
|
+
if (!folderStat.isDirectory()) {
|
|
35
|
+
throw new Error(`workflow folder 不是有效目录: ${folder}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const warnings: string[] = [];
|
|
39
|
+
const prompts: WorkflowPromptSummary[] = [];
|
|
40
|
+
const entries = await readdir(folder, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry.isFile() || entry.name.startsWith('.')) continue;
|
|
44
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
45
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
46
|
+
const filePath = path.join(folder, entry.name);
|
|
47
|
+
const fileStat = await stat(filePath);
|
|
48
|
+
if (fileStat.size > MAX_PROMPT_BYTES) {
|
|
49
|
+
warnings.push(`${entry.name} 超过 256 KiB,已跳过`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
prompts.push({
|
|
53
|
+
id: promptId(filePath),
|
|
54
|
+
label: labelFromFilename(entry.name),
|
|
55
|
+
filename: entry.name,
|
|
56
|
+
path: filePath,
|
|
57
|
+
sizeBytes: fileStat.size,
|
|
58
|
+
updatedAt: fileStat.mtime.toISOString(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
prompts.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
63
|
+
return { folder, prompts, warnings };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async read(folderInput: string, id: string): Promise<WorkflowPromptContentResponse> {
|
|
67
|
+
const list = await this.list(folderInput);
|
|
68
|
+
const prompt = list.prompts.find((item) => item.id === id || item.filename === id);
|
|
69
|
+
if (!prompt) {
|
|
70
|
+
throw new Error(`未找到 workflow: ${id}`);
|
|
71
|
+
}
|
|
72
|
+
const content = await readFile(prompt.path, 'utf-8');
|
|
73
|
+
return { prompt, content };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -79,6 +79,7 @@ export class TaskDispatchService {
|
|
|
79
79
|
this.stopHeartbeat();
|
|
80
80
|
this.stopConsumers();
|
|
81
81
|
this.stopResponseConsumers();
|
|
82
|
+
this.collabBoard.setRedis(null);
|
|
82
83
|
this.redis?.disconnect();
|
|
83
84
|
this.redisSub?.disconnect();
|
|
84
85
|
this.redis = null;
|
|
@@ -757,27 +758,50 @@ export class TaskDispatchService {
|
|
|
757
758
|
|
|
758
759
|
private async connectRedis(): Promise<void> {
|
|
759
760
|
if (!this.config?.redis) return;
|
|
761
|
+
let redis: Redis | null = null;
|
|
762
|
+
let redisSub: Redis | null = null;
|
|
760
763
|
try {
|
|
761
764
|
const ioredis = await import('ioredis');
|
|
762
765
|
const { host, port, password, db } = this.config.redis;
|
|
763
|
-
const opts = {
|
|
766
|
+
const opts = {
|
|
767
|
+
host,
|
|
768
|
+
port,
|
|
769
|
+
password: password || undefined,
|
|
770
|
+
db: db ?? 0,
|
|
771
|
+
lazyConnect: true,
|
|
772
|
+
maxRetriesPerRequest: 0,
|
|
773
|
+
retryStrategy: () => null,
|
|
774
|
+
};
|
|
764
775
|
|
|
765
|
-
|
|
766
|
-
|
|
776
|
+
redis = new ioredis.default(opts);
|
|
777
|
+
redisSub = new ioredis.default(opts);
|
|
767
778
|
|
|
768
|
-
|
|
769
|
-
|
|
779
|
+
redis.on('error', () => {
|
|
780
|
+
/* handled by task bus connection status */
|
|
781
|
+
});
|
|
782
|
+
redisSub.on('error', () => {
|
|
783
|
+
/* handled by task bus connection status */
|
|
770
784
|
});
|
|
771
785
|
|
|
772
|
-
await
|
|
786
|
+
await redis.connect();
|
|
787
|
+
await redisSub.connect();
|
|
788
|
+
await redis.ping();
|
|
773
789
|
|
|
790
|
+
this.redis = redis;
|
|
791
|
+
this.redisSub = redisSub;
|
|
792
|
+
redis = null;
|
|
793
|
+
redisSub = null;
|
|
774
794
|
this.collabBoard.setRedis(this.redis);
|
|
775
795
|
this.startHeartbeat();
|
|
776
796
|
this.startConsumers();
|
|
777
797
|
this.startResponseConsumers();
|
|
778
798
|
this.subscribeStatus();
|
|
779
|
-
} catch
|
|
780
|
-
|
|
799
|
+
} catch {
|
|
800
|
+
this.collabBoard.setRedis(null);
|
|
801
|
+
redis?.disconnect();
|
|
802
|
+
redisSub?.disconnect();
|
|
803
|
+
this.redis?.disconnect();
|
|
804
|
+
this.redisSub?.disconnect();
|
|
781
805
|
this.redis = null;
|
|
782
806
|
this.redisSub = null;
|
|
783
807
|
}
|
|
@@ -45,6 +45,35 @@ function removeManagedTeamInstructions(content: string): string {
|
|
|
45
45
|
return next.replace(/\n{3,}/g, '\n\n').trimEnd();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
async function injectHermitTasksMcpConfig(workDir: string): Promise<void> {
|
|
49
|
+
const settingsPath = path.join(workDir, '.claude', 'settings.json');
|
|
50
|
+
let settings: Record<string, unknown> = {};
|
|
51
|
+
try {
|
|
52
|
+
const raw = await fs.promises.readFile(settingsPath, 'utf8');
|
|
53
|
+
settings = JSON.parse(raw) as Record<string, unknown>;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const existingMcpServers =
|
|
61
|
+
settings.mcpServers && typeof settings.mcpServers === 'object'
|
|
62
|
+
? (settings.mcpServers as Record<string, unknown>)
|
|
63
|
+
: {};
|
|
64
|
+
const port = process.env.PORT ?? '5680';
|
|
65
|
+
settings.mcpServers = {
|
|
66
|
+
...existingMcpServers,
|
|
67
|
+
'hermit-tasks': {
|
|
68
|
+
type: 'sse',
|
|
69
|
+
url: `http://127.0.0.1:${port}/mcp`,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
74
|
+
await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
75
|
+
}
|
|
76
|
+
|
|
48
77
|
export class TeamProvisioningService {
|
|
49
78
|
private readonly workspace: TeamWorkspaceService;
|
|
50
79
|
|
|
@@ -68,12 +97,16 @@ export class TeamProvisioningService {
|
|
|
68
97
|
* 4. 触发 cc-connect restart 激活 project
|
|
69
98
|
*/
|
|
70
99
|
async createTeam(
|
|
71
|
-
input: CreateTeamInput & { createCcProject?: boolean }
|
|
100
|
+
input: CreateTeamInput & { createCcProject?: boolean; injectInstructions?: boolean }
|
|
72
101
|
): Promise<{ slug: string; manifest: TeamManifest }> {
|
|
73
|
-
const { createCcProject = true, ...workspaceInput } = input;
|
|
102
|
+
const { createCcProject = true, injectInstructions = true, ...workspaceInput } = input;
|
|
74
103
|
|
|
75
104
|
const { slug, manifest } = await this.workspace.createTeam(workspaceInput);
|
|
76
105
|
|
|
106
|
+
if (injectInstructions && manifest.harness === 'claudecode') {
|
|
107
|
+
await injectHermitTasksMcpConfig(manifest.workDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
77
110
|
if (createCcProject) {
|
|
78
111
|
try {
|
|
79
112
|
const platformType = manifest.platform ?? 'bridge';
|
|
@@ -118,6 +151,7 @@ export class TeamProvisioningService {
|
|
|
118
151
|
Pick<
|
|
119
152
|
TeamManifest,
|
|
120
153
|
| 'displayName'
|
|
154
|
+
| 'bindProject'
|
|
121
155
|
| 'color'
|
|
122
156
|
| 'description'
|
|
123
157
|
| 'collaboration'
|
|
@@ -130,7 +164,10 @@ export class TeamProvisioningService {
|
|
|
130
164
|
| 'injectSender'
|
|
131
165
|
| 'managedSources'
|
|
132
166
|
| 'disabledCommands'
|
|
167
|
+
| 'platform'
|
|
168
|
+
| 'platformOptions'
|
|
133
169
|
| 'platformAllowFrom'
|
|
170
|
+
| 'platformAllowChat'
|
|
134
171
|
>
|
|
135
172
|
>
|
|
136
173
|
): Promise<TeamManifest> {
|
|
@@ -45,6 +45,8 @@ export interface TeamManifest {
|
|
|
45
45
|
managedSources?: string;
|
|
46
46
|
disabledCommands?: string[];
|
|
47
47
|
platformAllowFrom?: Record<string, string>;
|
|
48
|
+
/** 群聊允许的 chat ID(按平台) */
|
|
49
|
+
platformAllowChat?: Record<string, string>;
|
|
48
50
|
pendingDelete?: boolean;
|
|
49
51
|
restartRequired?: boolean;
|
|
50
52
|
/**
|
|
@@ -159,9 +161,9 @@ async function readJson<T>(p: string, fallback: T): Promise<T> {
|
|
|
159
161
|
try {
|
|
160
162
|
const raw = await fs.promises.readFile(p, 'utf8');
|
|
161
163
|
return JSON.parse(raw) as T;
|
|
162
|
-
} catch
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
} catch {
|
|
165
|
+
// File missing or corrupted JSON — return fallback silently
|
|
166
|
+
return fallback;
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
@@ -185,6 +187,17 @@ export class TeamWorkspaceService {
|
|
|
185
187
|
return match?.slug ?? teamSlug;
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
private async createUniqueStorageSlug(displayName: string): Promise<string> {
|
|
191
|
+
const baseSlug = toSlug(displayName);
|
|
192
|
+
let slug = baseSlug;
|
|
193
|
+
let suffix = 2;
|
|
194
|
+
while (await pathExists(path.join(teamRoot(slug), 'team.json'))) {
|
|
195
|
+
slug = `${baseSlug}-${suffix}`;
|
|
196
|
+
suffix += 1;
|
|
197
|
+
}
|
|
198
|
+
return slug;
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
async createTeam(
|
|
189
202
|
input: CreateTeamInput
|
|
190
203
|
): Promise<{ slug: string; root: string; manifest: TeamManifest }> {
|
|
@@ -192,7 +205,7 @@ export class TeamWorkspaceService {
|
|
|
192
205
|
if (!input.bindProject) throw new Error('bindProject is required');
|
|
193
206
|
if (!input.workDir) throw new Error('workDir is required');
|
|
194
207
|
|
|
195
|
-
const slug =
|
|
208
|
+
const slug = await this.createUniqueStorageSlug(input.displayName);
|
|
196
209
|
const root = teamRoot(slug);
|
|
197
210
|
|
|
198
211
|
await fs.promises.mkdir(root, { recursive: true });
|
|
@@ -263,6 +276,7 @@ export class TeamWorkspaceService {
|
|
|
263
276
|
const out: TeamManifest[] = [];
|
|
264
277
|
for (const e of entries) {
|
|
265
278
|
if (!e.isDirectory()) continue;
|
|
279
|
+
if (e.name.startsWith('.')) continue;
|
|
266
280
|
try {
|
|
267
281
|
out.push(await this.readTeamManifest(e.name));
|
|
268
282
|
} catch {
|
|
@@ -278,6 +292,7 @@ export class TeamWorkspaceService {
|
|
|
278
292
|
Pick<
|
|
279
293
|
TeamManifest,
|
|
280
294
|
| 'displayName'
|
|
295
|
+
| 'bindProject'
|
|
281
296
|
| 'color'
|
|
282
297
|
| 'description'
|
|
283
298
|
| 'collaboration'
|
|
@@ -290,7 +305,10 @@ export class TeamWorkspaceService {
|
|
|
290
305
|
| 'injectSender'
|
|
291
306
|
| 'managedSources'
|
|
292
307
|
| 'disabledCommands'
|
|
308
|
+
| 'platform'
|
|
309
|
+
| 'platformOptions'
|
|
293
310
|
| 'platformAllowFrom'
|
|
311
|
+
| 'platformAllowChat'
|
|
294
312
|
| 'pendingDelete'
|
|
295
313
|
| 'restartRequired'
|
|
296
314
|
>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TeamManifest } from '@main/services/teams-mvp/TeamWorkspaceService';
|
|
2
|
+
|
|
3
|
+
export async function resolveCcProjectName(
|
|
4
|
+
routeTeamName: string,
|
|
5
|
+
readTeamManifest: (teamName: string) => Promise<TeamManifest>
|
|
6
|
+
): Promise<string> {
|
|
7
|
+
try {
|
|
8
|
+
const manifest = await readTeamManifest(routeTeamName);
|
|
9
|
+
const bindProject = manifest.bindProject?.trim();
|
|
10
|
+
if (bindProject) return bindProject;
|
|
11
|
+
} catch {
|
|
12
|
+
// routeTeamName may already be a cc-connect project name.
|
|
13
|
+
}
|
|
14
|
+
return routeTeamName;
|
|
15
|
+
}
|
package/src/renderer/App.tsx
CHANGED
|
@@ -41,6 +41,7 @@ function buildPathForTab(activeTab: Tab | null): string {
|
|
|
41
41
|
}
|
|
42
42
|
switch (activeTab.type) {
|
|
43
43
|
case 'team':
|
|
44
|
+
if (activeTab.teamName === 'system-manager') return '/system-manager';
|
|
44
45
|
return activeTab.teamName
|
|
45
46
|
? `/team/${encodeURIComponent(activeTab.teamName)}`
|
|
46
47
|
: DEFAULT_APP_PATH;
|
|
@@ -142,6 +143,9 @@ function useTabPathPersistence() {
|
|
|
142
143
|
case 'teams':
|
|
143
144
|
state.openTeamsTab();
|
|
144
145
|
break;
|
|
146
|
+
case 'system-manager':
|
|
147
|
+
void state.openSystemManager();
|
|
148
|
+
break;
|
|
145
149
|
case 'settings':
|
|
146
150
|
state.openSettingsTab();
|
|
147
151
|
break;
|
|
@@ -208,11 +212,11 @@ declare global {
|
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
214
|
|
|
211
|
-
const SPLASH_MIN_DURATION_MS =
|
|
212
|
-
const SPLASH_ENHANCED_HOLD_MS =
|
|
215
|
+
const SPLASH_MIN_DURATION_MS = 2800;
|
|
216
|
+
const SPLASH_ENHANCED_HOLD_MS = 800;
|
|
213
217
|
const SPLASH_FADE_MS = 480;
|
|
214
|
-
const SPLASH_REDUCED_MIN_DURATION_MS =
|
|
215
|
-
const SPLASH_REDUCED_HOLD_MS =
|
|
218
|
+
const SPLASH_REDUCED_MIN_DURATION_MS = 600;
|
|
219
|
+
const SPLASH_REDUCED_HOLD_MS = 200;
|
|
216
220
|
const SPLASH_REDUCED_FADE_MS = 180;
|
|
217
221
|
const SPLASH_AVATAR_READY_MAX_WAIT_MS = 900;
|
|
218
222
|
const SPLASH_REDUCED_AVATAR_READY_MAX_WAIT_MS = 160;
|