@yancyyu/openhermit 1.6.29 → 1.6.31
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-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
- package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
- package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
- package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
- package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
- package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
- package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
- package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
- package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
- package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
- package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
- package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
- package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
- package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +209 -6
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/api/httpClient.ts +61 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +74 -123
- package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
- package/src/renderer/components/team/TeamListView.tsx +7 -32
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
- package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/teamSlice.ts +8 -2
- package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
- package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
- package/dist-renderer/assets/index-BhellmRb.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive shell environment resolver.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the user's interactive shell environment (PATH, etc.) by spawning
|
|
5
|
+
* a login/interactive shell and reading its exported variables. The result is
|
|
6
|
+
* cached for the lifetime of the process.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from TeamProvisioningService for reuse by ScheduledTaskExecutor
|
|
9
|
+
* and any other service that needs the user's shell environment.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getHomeDir } from '@main/utils/pathDecoder';
|
|
13
|
+
import { createLogger } from '@shared/utils/logger';
|
|
14
|
+
import { spawn } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const logger = createLogger('Utils:shellEnv');
|
|
17
|
+
|
|
18
|
+
const SHELL_ENV_TIMEOUT_MS = 12_000;
|
|
19
|
+
const SHELL_ENV_BEST_EFFORT_TIMEOUT_MS = 5_000;
|
|
20
|
+
const SHELL_ENV_FAILURE_COOLDOWN_MS = 60_000;
|
|
21
|
+
|
|
22
|
+
let cachedInteractiveShellEnv: NodeJS.ProcessEnv | null = null;
|
|
23
|
+
let shellEnvResolvePromise: Promise<NodeJS.ProcessEnv> | null = null;
|
|
24
|
+
let shellEnvFailureCooldownUntil = 0;
|
|
25
|
+
let lastShellEnvFailureMessage: string | null = null;
|
|
26
|
+
|
|
27
|
+
export interface ShellEnvResolveProgress {
|
|
28
|
+
phase: string;
|
|
29
|
+
message: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ShellEnvResolveOptions {
|
|
34
|
+
onProgress?: (progress: ShellEnvResolveProgress) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Stable diagnostic label for the caller that initiated the shell probe.
|
|
37
|
+
* Keep this to a short feature/service id, not a filesystem path.
|
|
38
|
+
*/
|
|
39
|
+
source?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ShellEnvBestEffortResolveOptions extends ShellEnvResolveOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Max time to wait on the critical path before returning fallbackEnv.
|
|
45
|
+
* By default, the full shell resolve continues in the background and caches
|
|
46
|
+
* on success. Set background=false for hot paths that only want cached env
|
|
47
|
+
* or an immediate fallback.
|
|
48
|
+
*/
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Whether a slow shell probe should continue in the background after the
|
|
52
|
+
* caller falls back. Disable this for startup/status hot paths where a
|
|
53
|
+
* delayed hard timeout would only create log noise and process pressure.
|
|
54
|
+
*/
|
|
55
|
+
background?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Returned when shell env is not ready quickly enough. This is intentionally
|
|
58
|
+
* not cached as a real shell env.
|
|
59
|
+
*/
|
|
60
|
+
fallbackEnv?: NodeJS.ProcessEnv;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emitProgress(
|
|
64
|
+
options: ShellEnvResolveOptions | undefined,
|
|
65
|
+
phase: string,
|
|
66
|
+
message: string
|
|
67
|
+
): void {
|
|
68
|
+
const source = normalizeShellEnvSource(options?.source);
|
|
69
|
+
options?.onProgress?.(source ? { phase, message, source } : { phase, message });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeShellEnvSource(source: string | undefined): string | null {
|
|
73
|
+
const trimmed = source?.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return trimmed.replace(/[^A-Za-z0-9_.:-]/g, '_').slice(0, 80);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatShellEnvSource(options: ShellEnvResolveOptions | undefined): string {
|
|
81
|
+
const source = normalizeShellEnvSource(options?.source);
|
|
82
|
+
return source ? ` source=${source}` : '';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function rememberShellEnvFailure(message: string): void {
|
|
86
|
+
lastShellEnvFailureMessage = message;
|
|
87
|
+
shellEnvFailureCooldownUntil = Date.now() + SHELL_ENV_FAILURE_COOLDOWN_MS;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function clearShellEnvFailure(): void {
|
|
91
|
+
lastShellEnvFailureMessage = null;
|
|
92
|
+
shellEnvFailureCooldownUntil = 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseNullSeparatedEnv(content: string): NodeJS.ProcessEnv {
|
|
96
|
+
const parsed: NodeJS.ProcessEnv = {};
|
|
97
|
+
const lines = content.split('\0');
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (!line) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const separatorIndex = line.indexOf('=');
|
|
103
|
+
if (separatorIndex <= 0) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const key = line.slice(0, separatorIndex);
|
|
107
|
+
const value = line.slice(separatorIndex + 1);
|
|
108
|
+
parsed[key] = value;
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function readShellEnv(shellPath: string, args: string[]): Promise<NodeJS.ProcessEnv> {
|
|
114
|
+
const envDump = await new Promise<string>((resolve, reject) => {
|
|
115
|
+
const child = spawn(shellPath, args, {
|
|
116
|
+
env: process.env,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
});
|
|
120
|
+
const chunks: Buffer[] = [];
|
|
121
|
+
let settled = false;
|
|
122
|
+
let timeoutHandle: NodeJS.Timeout | null = setTimeout(() => {
|
|
123
|
+
timeoutHandle = null;
|
|
124
|
+
child.kill();
|
|
125
|
+
// SIGKILL fallback if SIGTERM is ignored (e.g., shell stuck on .zshrc)
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
try {
|
|
128
|
+
child.kill('SIGKILL');
|
|
129
|
+
} catch {
|
|
130
|
+
/* already dead */
|
|
131
|
+
}
|
|
132
|
+
}, 3000);
|
|
133
|
+
if (!settled) {
|
|
134
|
+
settled = true;
|
|
135
|
+
reject(new Error('shell env resolve timeout'));
|
|
136
|
+
}
|
|
137
|
+
}, SHELL_ENV_TIMEOUT_MS);
|
|
138
|
+
|
|
139
|
+
child.stdout?.on('data', (chunk: Buffer) => {
|
|
140
|
+
chunks.push(chunk);
|
|
141
|
+
});
|
|
142
|
+
child.once('error', (error) => {
|
|
143
|
+
if (timeoutHandle) {
|
|
144
|
+
clearTimeout(timeoutHandle);
|
|
145
|
+
timeoutHandle = null;
|
|
146
|
+
}
|
|
147
|
+
if (!settled) {
|
|
148
|
+
settled = true;
|
|
149
|
+
reject(error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
child.once('close', (code: number | null, signal: NodeJS.Signals | null) => {
|
|
153
|
+
if (timeoutHandle) {
|
|
154
|
+
clearTimeout(timeoutHandle);
|
|
155
|
+
}
|
|
156
|
+
if (!settled) {
|
|
157
|
+
settled = true;
|
|
158
|
+
if (chunks.length === 0 && (code !== 0 || signal)) {
|
|
159
|
+
reject(
|
|
160
|
+
new Error(
|
|
161
|
+
signal
|
|
162
|
+
? `shell env command exited with signal ${signal}`
|
|
163
|
+
: `shell env command exited with code ${code}`
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
return parseNullSeparatedEnv(envDump);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the user's interactive shell environment.
|
|
177
|
+
*
|
|
178
|
+
* Tries login shell first (`-lic`), falls back to interactive (`-ic`).
|
|
179
|
+
* On Windows returns empty object. Result is cached after first success.
|
|
180
|
+
*/
|
|
181
|
+
export async function resolveInteractiveShellEnv(
|
|
182
|
+
options: ShellEnvResolveOptions = {}
|
|
183
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
184
|
+
if (cachedInteractiveShellEnv) {
|
|
185
|
+
emitProgress(options, 'shell-env-cached', 'Using cached shell environment...');
|
|
186
|
+
return cachedInteractiveShellEnv;
|
|
187
|
+
}
|
|
188
|
+
if (shellEnvResolvePromise) {
|
|
189
|
+
emitProgress(options, 'shell-env-waiting', 'Waiting for shell environment...');
|
|
190
|
+
return shellEnvResolvePromise;
|
|
191
|
+
}
|
|
192
|
+
if (process.platform === 'win32') {
|
|
193
|
+
emitProgress(options, 'shell-env-skipped', 'Skipping shell environment on Windows...');
|
|
194
|
+
cachedInteractiveShellEnv = {};
|
|
195
|
+
return cachedInteractiveShellEnv;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
shellEnvResolvePromise = (async () => {
|
|
199
|
+
const shellPath = process.env.SHELL || '/bin/zsh';
|
|
200
|
+
try {
|
|
201
|
+
emitProgress(options, 'shell-env-login', 'Reading login shell environment...');
|
|
202
|
+
const loginEnv = await readShellEnv(shellPath, ['-lic', 'env -0']);
|
|
203
|
+
cachedInteractiveShellEnv = loginEnv;
|
|
204
|
+
clearShellEnvFailure();
|
|
205
|
+
return loginEnv;
|
|
206
|
+
} catch (loginError) {
|
|
207
|
+
const loginMessage = loginError instanceof Error ? loginError.message : String(loginError);
|
|
208
|
+
try {
|
|
209
|
+
emitProgress(options, 'shell-env-interactive', 'Trying interactive shell environment...');
|
|
210
|
+
const interactiveEnv = await readShellEnv(shellPath, ['-ic', 'env -0']);
|
|
211
|
+
cachedInteractiveShellEnv = interactiveEnv;
|
|
212
|
+
clearShellEnvFailure();
|
|
213
|
+
return interactiveEnv;
|
|
214
|
+
} catch (interactiveError) {
|
|
215
|
+
const interactiveMessage =
|
|
216
|
+
interactiveError instanceof Error ? interactiveError.message : String(interactiveError);
|
|
217
|
+
logger.warn(
|
|
218
|
+
`Failed to resolve shell env after login and interactive probes${formatShellEnvSource(
|
|
219
|
+
options
|
|
220
|
+
)}: login=${loginMessage}; interactive=${interactiveMessage}`
|
|
221
|
+
);
|
|
222
|
+
rememberShellEnvFailure(interactiveMessage);
|
|
223
|
+
emitProgress(options, 'shell-env-fallback', 'Using current process environment...');
|
|
224
|
+
return {};
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
shellEnvResolvePromise = null;
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
|
|
231
|
+
return shellEnvResolvePromise;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve shell env without making the caller wait for slow prompt/plugin init.
|
|
236
|
+
*
|
|
237
|
+
* This is deliberately additive: fallbackEnv is returned only to the current
|
|
238
|
+
* caller, never cached. A successful background resolve still populates the
|
|
239
|
+
* normal interactive-shell cache used by buildMergedCliPath/buildEnrichedEnv.
|
|
240
|
+
*/
|
|
241
|
+
export async function resolveInteractiveShellEnvBestEffort(
|
|
242
|
+
options: ShellEnvBestEffortResolveOptions = {}
|
|
243
|
+
): Promise<NodeJS.ProcessEnv> {
|
|
244
|
+
if (cachedInteractiveShellEnv) {
|
|
245
|
+
emitProgress(options, 'shell-env-cached', 'Using cached shell environment...');
|
|
246
|
+
return cachedInteractiveShellEnv;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (process.platform === 'win32') {
|
|
250
|
+
return resolveInteractiveShellEnv(options);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const fallbackEnv = options.fallbackEnv ?? {};
|
|
254
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? SHELL_ENV_BEST_EFFORT_TIMEOUT_MS);
|
|
255
|
+
const startedAt = Date.now();
|
|
256
|
+
if (options.background === false) {
|
|
257
|
+
emitProgress(options, 'shell-env-best-effort-fallback', 'Using fallback shell environment...');
|
|
258
|
+
return fallbackEnv;
|
|
259
|
+
}
|
|
260
|
+
if (!shellEnvResolvePromise && startedAt < shellEnvFailureCooldownUntil) {
|
|
261
|
+
const retryInMs = Math.max(0, shellEnvFailureCooldownUntil - startedAt);
|
|
262
|
+
emitProgress(
|
|
263
|
+
options,
|
|
264
|
+
'shell-env-failure-cooldown',
|
|
265
|
+
lastShellEnvFailureMessage
|
|
266
|
+
? `Using fallback shell environment after recent failure: ${lastShellEnvFailureMessage}`
|
|
267
|
+
: `Using fallback shell environment for ${retryInMs}ms after recent failure...`
|
|
268
|
+
);
|
|
269
|
+
return fallbackEnv;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const resolvePromise = resolveInteractiveShellEnv(options);
|
|
273
|
+
if (timeoutMs === 0) {
|
|
274
|
+
emitProgress(options, 'shell-env-best-effort-fallback', 'Using fallback shell environment...');
|
|
275
|
+
return fallbackEnv;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
279
|
+
const fallbackPromise = new Promise<NodeJS.ProcessEnv>((resolve) => {
|
|
280
|
+
timeoutHandle = setTimeout(() => {
|
|
281
|
+
timeoutHandle = null;
|
|
282
|
+
emitProgress(
|
|
283
|
+
options,
|
|
284
|
+
'shell-env-best-effort-timeout',
|
|
285
|
+
'Shell environment is still resolving; using fallback for now...'
|
|
286
|
+
);
|
|
287
|
+
resolve(fallbackEnv);
|
|
288
|
+
}, timeoutMs);
|
|
289
|
+
timeoutHandle.unref?.();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const resolvedEnv = await Promise.race([resolvePromise, fallbackPromise]);
|
|
294
|
+
if (!cachedInteractiveShellEnv && shellEnvFailureCooldownUntil > startedAt) {
|
|
295
|
+
return fallbackEnv;
|
|
296
|
+
}
|
|
297
|
+
return resolvedEnv;
|
|
298
|
+
} finally {
|
|
299
|
+
if (timeoutHandle) {
|
|
300
|
+
clearTimeout(timeoutHandle);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clear the cached shell environment. Useful for testing.
|
|
307
|
+
*/
|
|
308
|
+
export function clearShellEnvCache(): void {
|
|
309
|
+
cachedInteractiveShellEnv = null;
|
|
310
|
+
shellEnvResolvePromise = null;
|
|
311
|
+
clearShellEnvFailure();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Return the cached shell environment synchronously, or null if not yet resolved.
|
|
316
|
+
*
|
|
317
|
+
* Use this when you need the shell env but cannot afford to wait for resolution
|
|
318
|
+
* (e.g. synchronous PATH enrichment with async pre-warming at startup).
|
|
319
|
+
*/
|
|
320
|
+
export function getCachedShellEnv(): NodeJS.ProcessEnv | null {
|
|
321
|
+
return cachedInteractiveShellEnv;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* HOME from login/interactive shell when resolved, else Electron/Node home.
|
|
326
|
+
* Matches TeamProvisioningService so CLI reads the same ~/.claude as the terminal.
|
|
327
|
+
*/
|
|
328
|
+
export function getShellPreferredHome(): string {
|
|
329
|
+
const fromShell = getCachedShellEnv()?.HOME?.trim();
|
|
330
|
+
return fromShell || getHomeDir();
|
|
331
|
+
}
|
|
@@ -2220,6 +2220,67 @@ export class HttpAPIClient implements ElectronAPI {
|
|
|
2220
2220
|
},
|
|
2221
2221
|
};
|
|
2222
2222
|
|
|
2223
|
+
// ---------------------------------------------------------------------------
|
|
2224
|
+
// Credentials (project env, MCP credentials)
|
|
2225
|
+
// ---------------------------------------------------------------------------
|
|
2226
|
+
|
|
2227
|
+
credentials = {
|
|
2228
|
+
getStatus: async () =>
|
|
2229
|
+
this.get<{ encryption: string; storagePath: string } | null>(
|
|
2230
|
+
'/api/extensions/credentials/status'
|
|
2231
|
+
),
|
|
2232
|
+
|
|
2233
|
+
getProjectEnv: async (projectPath: string) =>
|
|
2234
|
+
this.get<Record<string, string>>(
|
|
2235
|
+
`/api/extensions/credentials/project-env?projectPath=${encodeURIComponent(projectPath)}`
|
|
2236
|
+
),
|
|
2237
|
+
|
|
2238
|
+
saveProjectEnv: async (projectPath: string, vars: Record<string, string>): Promise<void> => {
|
|
2239
|
+
await this.post('/api/extensions/credentials/project-env', { projectPath, vars });
|
|
2240
|
+
},
|
|
2241
|
+
|
|
2242
|
+
scanRequired: async (
|
|
2243
|
+
projectPath: string,
|
|
2244
|
+
mcpServers: {
|
|
2245
|
+
name: string;
|
|
2246
|
+
envVars?: { name: string; isRequired: boolean; description?: string };
|
|
2247
|
+
}[],
|
|
2248
|
+
skillReqs: {
|
|
2249
|
+
name: string;
|
|
2250
|
+
envVars: { name: string; isRequired?: boolean; description?: string }[];
|
|
2251
|
+
}[]
|
|
2252
|
+
) =>
|
|
2253
|
+
this.post<{
|
|
2254
|
+
required: {
|
|
2255
|
+
name: string;
|
|
2256
|
+
isRequired: boolean;
|
|
2257
|
+
description?: string;
|
|
2258
|
+
source: string;
|
|
2259
|
+
value?: string;
|
|
2260
|
+
}[];
|
|
2261
|
+
}>('/api/extensions/credentials/scan-required', { projectPath, mcpServers, skillReqs }),
|
|
2262
|
+
|
|
2263
|
+
resolveAgentEnv: async (projectPath: string) =>
|
|
2264
|
+
this.get<Record<string, string>>(
|
|
2265
|
+
`/api/extensions/credentials/resolve-agent-env?projectPath=${encodeURIComponent(projectPath)}`
|
|
2266
|
+
),
|
|
2267
|
+
|
|
2268
|
+
getSkillGlobalEnv: async (skillFolderName: string) =>
|
|
2269
|
+
this.get<Record<string, string>>(
|
|
2270
|
+
`/api/extensions/credentials/skill-env?folderName=${encodeURIComponent(skillFolderName)}`
|
|
2271
|
+
),
|
|
2272
|
+
|
|
2273
|
+
saveSkillGlobalEnv: async (
|
|
2274
|
+
skillFolderName: string,
|
|
2275
|
+
vars: Record<string, string>
|
|
2276
|
+
): Promise<void> => {
|
|
2277
|
+
await this.post('/api/extensions/credentials/skill-env', {
|
|
2278
|
+
folderName: skillFolderName,
|
|
2279
|
+
vars,
|
|
2280
|
+
});
|
|
2281
|
+
},
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2223
2284
|
// ---------------------------------------------------------------------------
|
|
2224
2285
|
// Workspace (file system browsing)
|
|
2225
2286
|
// ---------------------------------------------------------------------------
|
|
@@ -30,6 +30,7 @@ import { useExtensionsTabState } from '@renderer/hooks/useExtensionsTabState';
|
|
|
30
30
|
import { useStore } from '@renderer/store';
|
|
31
31
|
import { createLoadingMultimodelCliStatus } from '@renderer/store/slices/cliInstallerSlice';
|
|
32
32
|
import {
|
|
33
|
+
filterExtensionStoreProviders,
|
|
33
34
|
formatCliExtensionCapabilityStatus,
|
|
34
35
|
getVisibleMultimodelProviders,
|
|
35
36
|
isMultimodelRuntimeStatus,
|
|
@@ -37,7 +38,6 @@ import {
|
|
|
37
38
|
import { resolveProjectPathById } from '@renderer/utils/projectLookup';
|
|
38
39
|
import { refreshCliStatusForCurrentMode } from '@renderer/utils/refreshCliStatus';
|
|
39
40
|
import { getRuntimeDisplayName } from '@renderer/utils/runtimeDisplayName';
|
|
40
|
-
import { getExtensionActionDisableReason } from '@shared/utils/extensionNormalizers';
|
|
41
41
|
import { getCliProviderExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
|
|
42
42
|
import {
|
|
43
43
|
AlertTriangle,
|
|
@@ -48,15 +48,20 @@ import {
|
|
|
48
48
|
Puzzle,
|
|
49
49
|
RefreshCw,
|
|
50
50
|
Server,
|
|
51
|
+
Sliders,
|
|
51
52
|
} from 'lucide-react';
|
|
52
53
|
import { useShallow } from 'zustand/react/shallow';
|
|
53
54
|
|
|
54
55
|
import { CustomMcpServerDialog } from './mcp/CustomMcpServerDialog';
|
|
56
|
+
import { EnvVarPanel } from './env/EnvVarPanel';
|
|
55
57
|
import { McpServersPanel } from './mcp/McpServersPanel';
|
|
58
|
+
import { PluginsPanel } from './plugins/PluginsPanel';
|
|
56
59
|
import { SkillsPanel } from './skills/SkillsPanel';
|
|
60
|
+
import { StoreExtensionToast } from './common/ExtensionToast';
|
|
57
61
|
import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
|
|
58
62
|
|
|
59
63
|
import type { CliProviderId, CliProviderStatus } from '@shared/types';
|
|
64
|
+
import type { ExtensionsSubTab } from '@renderer/hooks/useExtensionsTabState';
|
|
60
65
|
|
|
61
66
|
const ProviderCapabilityCardSkeleton = ({
|
|
62
67
|
providerId,
|
|
@@ -113,6 +118,12 @@ function isCodexSnapshotPending(
|
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
const EXTENSION_SUB_TABS = [
|
|
121
|
+
{
|
|
122
|
+
value: 'plugins' as const,
|
|
123
|
+
label: '插件',
|
|
124
|
+
icon: Puzzle,
|
|
125
|
+
description: 'Claude Code 私有扩展,增强运行时的能力与集成。',
|
|
126
|
+
},
|
|
116
127
|
{
|
|
117
128
|
value: 'mcp-servers' as const,
|
|
118
129
|
label: 'MCP 服务器',
|
|
@@ -125,6 +136,12 @@ const EXTENSION_SUB_TABS = [
|
|
|
125
136
|
icon: BookOpen,
|
|
126
137
|
description: '面向常见任务的可复用指令,帮助运行时更稳定地处理重复工作。',
|
|
127
138
|
},
|
|
139
|
+
{
|
|
140
|
+
value: 'env-vars' as const,
|
|
141
|
+
label: '环境变量',
|
|
142
|
+
icon: Sliders,
|
|
143
|
+
description: '管理运行时环境变量,启动 agent 时自动注入。',
|
|
144
|
+
},
|
|
128
145
|
] as const;
|
|
129
146
|
|
|
130
147
|
export const ExtensionStoreView = (): React.JSX.Element => {
|
|
@@ -132,6 +149,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
132
149
|
const {
|
|
133
150
|
bootstrapCliStatus,
|
|
134
151
|
fetchCliStatus,
|
|
152
|
+
fetchPluginCatalog,
|
|
135
153
|
fetchSkillsCatalog,
|
|
136
154
|
mcpBrowse,
|
|
137
155
|
mcpFetchInstalled,
|
|
@@ -149,7 +167,9 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
149
167
|
useShallow((s) => ({
|
|
150
168
|
bootstrapCliStatus: s.bootstrapCliStatus,
|
|
151
169
|
fetchCliStatus: s.fetchCliStatus,
|
|
170
|
+
fetchPluginCatalog: s.fetchPluginCatalog,
|
|
152
171
|
fetchSkillsCatalog: s.fetchSkillsCatalog,
|
|
172
|
+
pluginCatalog: s.pluginCatalog,
|
|
153
173
|
mcpBrowse: s.mcpBrowse,
|
|
154
174
|
mcpFetchInstalled: s.mcpFetchInstalled,
|
|
155
175
|
mcpBrowseLoading: s.mcpBrowseLoading,
|
|
@@ -239,6 +259,11 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
239
259
|
void mcpFetchInstalled(projectPath ?? undefined);
|
|
240
260
|
}, [mcpFetchInstalled, projectPath]);
|
|
241
261
|
|
|
262
|
+
// Fetch Plugin catalog on mount / project change
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
void fetchPluginCatalog(projectPath ?? undefined);
|
|
265
|
+
}, [fetchPluginCatalog, projectPath]);
|
|
266
|
+
|
|
242
267
|
// Fetch Skills catalog on mount / project change
|
|
243
268
|
useEffect(() => {
|
|
244
269
|
void fetchSkillsCatalog(projectPath ?? undefined);
|
|
@@ -265,19 +290,11 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
265
290
|
]);
|
|
266
291
|
|
|
267
292
|
const isRefreshing = effectiveCliStatusLoading || mcpBrowseLoading || skillsLoading;
|
|
268
|
-
const mcpMutationDisableReason = useMemo(
|
|
269
|
-
() =>
|
|
270
|
-
getExtensionActionDisableReason({
|
|
271
|
-
isInstalled: false,
|
|
272
|
-
cliStatus: effectiveCliStatus,
|
|
273
|
-
cliStatusLoading: effectiveCliStatusLoading,
|
|
274
|
-
section: 'mcp',
|
|
275
|
-
}),
|
|
276
|
-
[effectiveCliStatus, effectiveCliStatusLoading]
|
|
277
|
-
);
|
|
278
293
|
const cliStatusBanner = useMemo(() => {
|
|
279
294
|
const providers = effectiveCliStatus?.providers ?? [];
|
|
280
|
-
const visibleProviders =
|
|
295
|
+
const visibleProviders = filterExtensionStoreProviders(
|
|
296
|
+
getVisibleMultimodelProviders(providers)
|
|
297
|
+
);
|
|
281
298
|
const isMultimodel = isMultimodelRuntimeStatus(effectiveCliStatus);
|
|
282
299
|
const shouldShowMultimodelProviderCards =
|
|
283
300
|
isMultimodel && visibleProviders.length > 0 && effectiveCliStatus !== null;
|
|
@@ -504,7 +521,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
504
521
|
)}
|
|
505
522
|
<Tabs
|
|
506
523
|
value={tabState.activeSubTab}
|
|
507
|
-
onValueChange={(v) => tabState.setActiveSubTab(v as
|
|
524
|
+
onValueChange={(v) => tabState.setActiveSubTab(v as ExtensionsSubTab)}
|
|
508
525
|
>
|
|
509
526
|
<div className="-mx-6 flex items-end justify-between border-b border-border px-6">
|
|
510
527
|
<TabsList className="gap-1 rounded-b-none">
|
|
@@ -519,28 +536,37 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
519
536
|
))}
|
|
520
537
|
</TabsList>
|
|
521
538
|
{tabState.activeSubTab === 'mcp-servers' && (
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
>
|
|
532
|
-
<Plus className="mr-1 size-3.5" />
|
|
533
|
-
添加自定义
|
|
534
|
-
</Button>
|
|
535
|
-
</span>
|
|
536
|
-
</TooltipTrigger>
|
|
537
|
-
{mcpMutationDisableReason && (
|
|
538
|
-
<TooltipContent>{mcpMutationDisableReason}</TooltipContent>
|
|
539
|
-
)}
|
|
540
|
-
</Tooltip>
|
|
539
|
+
<Button
|
|
540
|
+
variant="outline"
|
|
541
|
+
size="sm"
|
|
542
|
+
onClick={() => setCustomMcpDialogOpen(true)}
|
|
543
|
+
className="mb-1 whitespace-nowrap"
|
|
544
|
+
>
|
|
545
|
+
<Plus className="mr-1 size-3.5" />
|
|
546
|
+
添加自定义
|
|
547
|
+
</Button>
|
|
541
548
|
)}
|
|
542
549
|
</div>
|
|
543
550
|
|
|
551
|
+
<TabsContent value="plugins" className="mt-0 pt-4">
|
|
552
|
+
<PluginsPanel
|
|
553
|
+
projectPath={projectPath}
|
|
554
|
+
pluginFilters={tabState.pluginFilters}
|
|
555
|
+
pluginSort={tabState.pluginSort}
|
|
556
|
+
setPluginSort={tabState.setPluginSort}
|
|
557
|
+
selectedPluginId={tabState.selectedPluginId}
|
|
558
|
+
setSelectedPluginId={tabState.setSelectedPluginId}
|
|
559
|
+
updatePluginSearch={tabState.updatePluginSearch}
|
|
560
|
+
toggleCategory={tabState.toggleCategory}
|
|
561
|
+
toggleCapability={tabState.toggleCapability}
|
|
562
|
+
toggleInstalledOnly={tabState.toggleInstalledOnly}
|
|
563
|
+
clearFilters={tabState.clearFilters}
|
|
564
|
+
hasActiveFilters={tabState.hasActiveFilters}
|
|
565
|
+
cliStatus={effectiveCliStatus}
|
|
566
|
+
cliStatusLoading={effectiveCliStatusLoading}
|
|
567
|
+
/>
|
|
568
|
+
</TabsContent>
|
|
569
|
+
|
|
544
570
|
<TabsContent value="mcp-servers" className="mt-0 pt-4">
|
|
545
571
|
<McpServersPanel
|
|
546
572
|
projectPath={projectPath}
|
|
@@ -568,19 +594,21 @@ export const ExtensionStoreView = (): React.JSX.Element => {
|
|
|
568
594
|
setSelectedSkillId={tabState.setSelectedSkillId}
|
|
569
595
|
/>
|
|
570
596
|
</TabsContent>
|
|
597
|
+
|
|
598
|
+
<TabsContent value="env-vars" className="mt-0 pt-4">
|
|
599
|
+
<EnvVarPanel projectPath={projectPath} />
|
|
600
|
+
</TabsContent>
|
|
571
601
|
</Tabs>
|
|
572
602
|
|
|
573
603
|
{/* Custom MCP server dialog (lifted to store view level) */}
|
|
574
604
|
<CustomMcpServerDialog
|
|
575
605
|
open={customMcpDialogOpen}
|
|
576
606
|
onClose={() => setCustomMcpDialogOpen(false)}
|
|
577
|
-
projectPath={projectPath}
|
|
578
|
-
cliStatus={effectiveCliStatus}
|
|
579
|
-
cliStatusLoading={effectiveCliStatusLoading}
|
|
580
607
|
/>
|
|
581
608
|
</div>
|
|
582
609
|
</div>
|
|
583
610
|
</div>
|
|
611
|
+
<StoreExtensionToast />
|
|
584
612
|
</TooltipProvider>
|
|
585
613
|
);
|
|
586
614
|
};
|
|
@@ -5,7 +5,7 @@ import { Info } from 'lucide-react';
|
|
|
5
5
|
import type { LucideIcon } from 'lucide-react';
|
|
6
6
|
|
|
7
7
|
interface ExtensionsSubTabTriggerProps {
|
|
8
|
-
value: 'mcp-servers' | 'skills';
|
|
8
|
+
value: 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
|
|
9
9
|
label: string;
|
|
10
10
|
description: string;
|
|
11
11
|
icon: LucideIcon;
|