Make the client more secure (#3742)
Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { getApiUrl, getSecretKey } from '../config';
|
import { getApiUrl } from '../config';
|
||||||
|
|
||||||
interface initializeAgentProps {
|
interface initializeAgentProps {
|
||||||
model: string;
|
model: string;
|
||||||
@@ -10,7 +10,7 @@ export async function initializeAgent({ model, provider }: initializeAgentProps)
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
provider: provider.toLowerCase().replace(/ /g, '_'),
|
provider: provider.toLowerCase().replace(/ /g, '_'),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
removeExtension as apiRemoveExtension,
|
removeExtension as apiRemoveExtension,
|
||||||
providers,
|
providers,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { client } from '../api/client.gen';
|
|
||||||
import type {
|
import type {
|
||||||
ConfigResponse,
|
ConfigResponse,
|
||||||
UpsertConfigQuery,
|
UpsertConfigQuery,
|
||||||
@@ -18,8 +17,9 @@ import type {
|
|||||||
ProviderDetails,
|
ProviderDetails,
|
||||||
ExtensionQuery,
|
ExtensionQuery,
|
||||||
ExtensionConfig,
|
ExtensionConfig,
|
||||||
} from '../api/types.gen';
|
} from '../api';
|
||||||
import { removeShims } from './settings/extensions/utils';
|
import { removeShims } from './settings/extensions/utils';
|
||||||
|
import { ensureClientInitialized } from '../utils';
|
||||||
|
|
||||||
export type { ExtensionConfig } from '../api/types.gen';
|
export type { ExtensionConfig } from '../api/types.gen';
|
||||||
|
|
||||||
@@ -28,15 +28,6 @@ export type FixedExtensionEntry = ExtensionConfig & {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize client configuration
|
|
||||||
client.setConfig({
|
|
||||||
baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Secret-Key': window.appConfig.get('secretKey'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
interface ConfigContextType {
|
interface ConfigContextType {
|
||||||
config: ConfigResponse['config'];
|
config: ConfigResponse['config'];
|
||||||
providersList: ProviderDetails[];
|
providersList: ProviderDetails[];
|
||||||
@@ -184,6 +175,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load all configuration data and providers on mount
|
// Load all configuration data and providers on mount
|
||||||
(async () => {
|
(async () => {
|
||||||
|
await ensureClientInitialized();
|
||||||
// Load config
|
// Load config
|
||||||
const configResponse = await readAllConfig();
|
const configResponse = await readAllConfig();
|
||||||
setConfig(configResponse.data?.config || {});
|
setConfig(configResponse.data?.config || {});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { startOpenRouterSetup } from '../utils/openRouterSetup';
|
|||||||
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
import WelcomeGooseLogo from './WelcomeGooseLogo';
|
||||||
import { initializeSystem } from '../utils/providerUtils';
|
import { initializeSystem } from '../utils/providerUtils';
|
||||||
import { toastService } from '../toasts';
|
import { toastService } from '../toasts';
|
||||||
|
import { ensureClientInitialized } from '../utils';
|
||||||
|
|
||||||
interface ProviderGuardProps {
|
interface ProviderGuardProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -95,6 +96,8 @@ export default function ProviderGuard({ children }: ProviderGuardProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkProvider = async () => {
|
const checkProvider = async () => {
|
||||||
try {
|
try {
|
||||||
|
await ensureClientInitialized();
|
||||||
|
|
||||||
const config = window.electron.getConfig();
|
const config = window.electron.getConfig();
|
||||||
console.log('ProviderGuard - Full config:', config);
|
console.log('ProviderGuard - Full config:', config);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip';
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip';
|
||||||
import { getApiUrl, getSecretKey } from '../../config';
|
import { getApiUrl } from '../../config';
|
||||||
|
|
||||||
interface ActivityHeatmapCell {
|
interface ActivityHeatmapCell {
|
||||||
week: number;
|
week: number;
|
||||||
@@ -40,7 +40,7 @@ export function ActivityHeatmap() {
|
|||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Card, CardContent, CardDescription } from '../ui/card';
|
import { Card, CardContent, CardDescription } from '../ui/card';
|
||||||
// import { Folder } from 'lucide-react';
|
// import { Folder } from 'lucide-react';
|
||||||
import { getApiUrl, getSecretKey } from '../../config';
|
import { getApiUrl } from '../../config';
|
||||||
import { Greeting } from '../common/Greeting';
|
import { Greeting } from '../common/Greeting';
|
||||||
import { fetchSessions, fetchSessionDetails, type Session } from '../../sessions';
|
import { fetchSessions, fetchSessionDetails, type Session } from '../../sessions';
|
||||||
// import { fetchProjects, type ProjectMetadata } from '../../projects';
|
// import { fetchProjects, type ProjectMetadata } from '../../projects';
|
||||||
@@ -36,7 +36,7 @@ export function SessionInsights() {
|
|||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Settings, RefreshCw, ExternalLink } from 'lucide-react';
|
|||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog';
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog';
|
||||||
import UpdateSection from './UpdateSection';
|
import UpdateSection from './UpdateSection';
|
||||||
import { COST_TRACKING_ENABLED, UPDATES_ENABLED } from '../../../updates';
|
import { COST_TRACKING_ENABLED, UPDATES_ENABLED } from '../../../updates';
|
||||||
import { getApiUrl, getSecretKey } from '../../../config';
|
import { getApiUrl } from '../../../config';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||||
import ThemeSelector from '../../GooseSidebar/ThemeSelector';
|
import ThemeSelector from '../../GooseSidebar/ThemeSelector';
|
||||||
import BlockLogoBlack from './icons/block-lockup_black.png';
|
import BlockLogoBlack from './icons/block-lockup_black.png';
|
||||||
@@ -71,7 +71,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
|||||||
const checkPricingStatus = async () => {
|
const checkPricingStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = getApiUrl('/config/pricing');
|
const apiUrl = getApiUrl('/config/pricing');
|
||||||
const secretKey = getSecretKey();
|
const secretKey = await window.electron.getSecretKey();
|
||||||
|
|
||||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||||
if (secretKey) {
|
if (secretKey) {
|
||||||
@@ -100,7 +100,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
|||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
const apiUrl = getApiUrl('/config/pricing');
|
const apiUrl = getApiUrl('/config/pricing');
|
||||||
const secretKey = getSecretKey();
|
const secretKey = await window.electron.getSecretKey();
|
||||||
|
|
||||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||||
if (secretKey) {
|
if (secretKey) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ExtensionConfig } from '../../../api/types.gen';
|
import { ExtensionConfig } from '../../../api/types.gen';
|
||||||
import { getApiUrl, getSecretKey } from '../../../config';
|
import { getApiUrl } from '../../../config';
|
||||||
import { toastService, ToastServiceOptions } from '../../../toasts';
|
import { toastService, ToastServiceOptions } from '../../../toasts';
|
||||||
import { replaceWithShims } from './utils';
|
import { replaceWithShims } from './utils';
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export async function extensionApiCall(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { useConfig } from '../../ConfigContext';
|
import { useConfig } from '../../ConfigContext';
|
||||||
import { getApiUrl, getSecretKey } from '../../../config';
|
import { getApiUrl } from '../../../config';
|
||||||
|
|
||||||
interface ToolSelectionStrategy {
|
interface ToolSelectionStrategy {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -56,7 +56,7 @@ export const ToolSelectionStrategySection = () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,3 @@ export const getApiUrl = (endpoint: string): string => {
|
|||||||
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
||||||
return `${baseUrl}${cleanEndpoint}`;
|
return `${baseUrl}${cleanEndpoint}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSecretKey = (): string => {
|
|
||||||
return String(window.appConfig.get('secretKey') || '');
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getApiUrl, getSecretKey } from './config';
|
import { getApiUrl } from './config';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { safeJsonParse } from './utils/jsonUtils';
|
import { safeJsonParse } from './utils/jsonUtils';
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ export async function addExtension(
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
});
|
});
|
||||||
@@ -177,7 +177,7 @@ export async function removeExtension(name: string, silent: boolean = false): Pr
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(sanitizeName(name)),
|
body: JSON.stringify(sanitizeName(name)),
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-38
@@ -94,6 +94,7 @@ interface GooseProcessEnv {
|
|||||||
|
|
||||||
export const startGoosed = async (
|
export const startGoosed = async (
|
||||||
app: App,
|
app: App,
|
||||||
|
serverSecret: string,
|
||||||
dir: string | null = null,
|
dir: string | null = null,
|
||||||
env: Partial<GooseProcessEnv> = {}
|
env: Partial<GooseProcessEnv> = {}
|
||||||
): Promise<[number, string, ChildProcess]> => {
|
): Promise<[number, string, ChildProcess]> => {
|
||||||
@@ -182,7 +183,7 @@ export const startGoosed = async (
|
|||||||
PATH: `${path.dirname(resolvedGoosedPath)}${path.delimiter}${process.env.PATH || ''}`,
|
PATH: `${path.dirname(resolvedGoosedPath)}${path.delimiter}${process.env.PATH || ''}`,
|
||||||
// start with the port specified
|
// start with the port specified
|
||||||
GOOSE_PORT: String(port),
|
GOOSE_PORT: String(port),
|
||||||
GOOSE_SERVER__SECRET_KEY: process.env.GOOSE_SERVER__SECRET_KEY,
|
GOOSE_SERVER__SECRET_KEY: serverSecret,
|
||||||
// Add any additional environment variables passed in
|
// Add any additional environment variables passed in
|
||||||
...env,
|
...env,
|
||||||
} as GooseProcessEnv;
|
} as GooseProcessEnv;
|
||||||
@@ -208,20 +209,6 @@ export const startGoosed = async (
|
|||||||
}
|
}
|
||||||
log.info(`Binary path resolved to: ${goosedPath}`);
|
log.info(`Binary path resolved to: ${goosedPath}`);
|
||||||
|
|
||||||
// Verify binary exists and is a regular file
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const fs = require('fs');
|
|
||||||
const stats = fs.statSync(goosedPath);
|
|
||||||
if (!stats.isFile()) {
|
|
||||||
throw new Error(`Path is not a regular file: ${goosedPath}`);
|
|
||||||
}
|
|
||||||
log.info(`Binary exists and is a regular file: ${stats.isFile()}`);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`Binary not found or invalid at ${goosedPath}:`, error);
|
|
||||||
throw new Error(`Binary not found or invalid at ${goosedPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const spawnOptions = {
|
const spawnOptions = {
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
env: processEnv,
|
env: processEnv,
|
||||||
@@ -282,16 +269,11 @@ export const startGoosed = async (
|
|||||||
// Wait for the server to be ready
|
// Wait for the server to be ready
|
||||||
const isReady = await checkServerStatus(port);
|
const isReady = await checkServerStatus(port);
|
||||||
log.info(`Goosed isReady ${isReady}`);
|
log.info(`Goosed isReady ${isReady}`);
|
||||||
if (!isReady) {
|
|
||||||
log.error(`Goosed server failed to start on port ${port}`);
|
const try_kill_goose = () => {
|
||||||
try {
|
try {
|
||||||
if (isWindows) {
|
if (isWindows) {
|
||||||
// On Windows, use taskkill to forcefully terminate the process tree
|
|
||||||
// Security: Validate PID is numeric and use safe arguments
|
|
||||||
const pid = goosedProcess.pid?.toString() || '0';
|
const pid = goosedProcess.pid?.toString() || '0';
|
||||||
if (!/^\d+$/.test(pid)) {
|
|
||||||
throw new Error(`Invalid PID: ${pid}`);
|
|
||||||
}
|
|
||||||
spawn('taskkill', ['/pid', pid, '/T', '/F'], { shell: false });
|
spawn('taskkill', ['/pid', pid, '/T', '/F'], { shell: false });
|
||||||
} else {
|
} else {
|
||||||
goosedProcess.kill?.();
|
goosedProcess.kill?.();
|
||||||
@@ -299,6 +281,11 @@ export const startGoosed = async (
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Error while terminating goosed process:', error);
|
log.error('Error while terminating goosed process:', error);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
|
log.error(`Goosed server failed to start on port ${port}`);
|
||||||
|
try_kill_goose();
|
||||||
throw new Error(`Goosed server failed to start on port ${port}`);
|
throw new Error(`Goosed server failed to start on port ${port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,22 +293,7 @@ export const startGoosed = async (
|
|||||||
// TODO will need to do it at tab level next
|
// TODO will need to do it at tab level next
|
||||||
app.on('will-quit', () => {
|
app.on('will-quit', () => {
|
||||||
log.info('App quitting, terminating goosed server');
|
log.info('App quitting, terminating goosed server');
|
||||||
try {
|
try_kill_goose();
|
||||||
if (isWindows) {
|
|
||||||
// On Windows, use taskkill to forcefully terminate the process tree
|
|
||||||
// Security: Validate PID is numeric and use safe arguments
|
|
||||||
const pid = goosedProcess.pid?.toString() || '0';
|
|
||||||
if (!/^\d+$/.test(pid)) {
|
|
||||||
log.error(`Invalid PID for termination: ${pid}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
spawn('taskkill', ['/pid', pid, '/T', '/F'], { shell: false });
|
|
||||||
} else {
|
|
||||||
goosedProcess.kill?.();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log.error('Error while terminating goosed process:', error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info(`Goosed server successfully started on port ${port}`);
|
log.info(`Goosed server successfully started on port ${port}`);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState, useCallback, useEffect, useRef, useId, useReducer } from 'react';
|
import { useCallback, useEffect, useId, useReducer, useRef, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { getSecretKey } from '../config';
|
import { createUserMessage, hasCompletedToolCalls, Message } from '../types/message';
|
||||||
import { Message, createUserMessage, hasCompletedToolCalls } from '../types/message';
|
|
||||||
import { getSessionHistory } from '../api';
|
import { getSessionHistory } from '../api';
|
||||||
import { ChatState } from '../types/chatState';
|
import { ChatState } from '../types/chatState';
|
||||||
|
|
||||||
@@ -382,9 +381,7 @@ export function useMessageStream({
|
|||||||
break; // Don't throw error, just add the message
|
break; // Don't throw error, just add the message
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-token-limit errors, still throw the error
|
throw new Error(parsedEvent.error);
|
||||||
const error = new Error(parsedEvent.error);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'Finish': {
|
case 'Finish': {
|
||||||
@@ -478,7 +475,7 @@ export function useMessageStream({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
...extraMetadataRef.current.headers,
|
...extraMetadataRef.current.headers,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||||
import { useConfig } from '../components/ConfigContext';
|
import { useConfig } from '../components/ConfigContext';
|
||||||
import { getApiUrl, getSecretKey } from '../config';
|
import { getApiUrl } from '../config';
|
||||||
import { useDictationSettings } from './useDictationSettings';
|
import { useDictationSettings } from './useDictationSettings';
|
||||||
import { safeJsonParse } from '../utils/jsonUtils';
|
import { safeJsonParse } from '../utils/jsonUtils';
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ export const useWhisper = ({ onTranscription, onError, onSizeWarning }: UseWhisp
|
|||||||
let endpoint = '';
|
let endpoint = '';
|
||||||
let headers: Record<string, string> = {
|
let headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
};
|
};
|
||||||
let body: Record<string, string> = {
|
let body: Record<string, string> = {
|
||||||
audio: base64Audio,
|
audio: base64Audio,
|
||||||
|
|||||||
+21
-17
@@ -1,4 +1,4 @@
|
|||||||
import type { OpenDialogReturnValue, OpenDialogOptions } from 'electron';
|
import type { OpenDialogOptions, OpenDialogReturnValue } from 'electron';
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
App,
|
App,
|
||||||
@@ -25,7 +25,7 @@ import os from 'node:os';
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { startGoosed } from './goosed';
|
import { startGoosed } from './goosed';
|
||||||
import { getBinaryPath, expandTilde } from './utils/pathUtils';
|
import { expandTilde, getBinaryPath } from './utils/pathUtils';
|
||||||
import { loadShellEnv } from './utils/loadEnv';
|
import { loadShellEnv } from './utils/loadEnv';
|
||||||
import log from './utils/logger';
|
import log from './utils/logger';
|
||||||
import { ensureWinShims } from './utils/winShims';
|
import { ensureWinShims } from './utils/winShims';
|
||||||
@@ -463,12 +463,6 @@ const getGooseProvider = () => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateSecretKey = () => {
|
|
||||||
const key = process.env.GOOSE_EXTERNAL_BACKEND ? 'test' : crypto.randomBytes(32).toString('hex');
|
|
||||||
process.env.GOOSE_SERVER__SECRET_KEY = key;
|
|
||||||
return key;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSharingUrl = () => {
|
const getSharingUrl = () => {
|
||||||
// checks app env for sharing url
|
// checks app env for sharing url
|
||||||
loadShellEnv(app.isPackaged); // will try to take it from the zshrc file
|
loadShellEnv(app.isPackaged); // will try to take it from the zshrc file
|
||||||
@@ -484,11 +478,15 @@ const getVersion = () => {
|
|||||||
return process.env.GOOSE_VERSION;
|
return process.env.GOOSE_VERSION;
|
||||||
};
|
};
|
||||||
|
|
||||||
let [provider, model, predefinedModels] = getGooseProvider();
|
const [provider, model, predefinedModels] = getGooseProvider();
|
||||||
|
|
||||||
let sharingUrl = getSharingUrl();
|
const sharingUrl = getSharingUrl();
|
||||||
|
|
||||||
let gooseVersion = getVersion();
|
const gooseVersion = getVersion();
|
||||||
|
|
||||||
|
const SERVER_SECRET = process.env.GOOSE_EXTERNAL_BACKEND
|
||||||
|
? 'test'
|
||||||
|
: crypto.randomBytes(32).toString('hex');
|
||||||
|
|
||||||
let appConfig = {
|
let appConfig = {
|
||||||
GOOSE_DEFAULT_PROVIDER: provider,
|
GOOSE_DEFAULT_PROVIDER: provider,
|
||||||
@@ -499,7 +497,6 @@ let appConfig = {
|
|||||||
GOOSE_WORKING_DIR: '',
|
GOOSE_WORKING_DIR: '',
|
||||||
// If GOOSE_ALLOWLIST_WARNING env var is not set, defaults to false (strict blocking mode)
|
// If GOOSE_ALLOWLIST_WARNING env var is not set, defaults to false (strict blocking mode)
|
||||||
GOOSE_ALLOWLIST_WARNING: process.env.GOOSE_ALLOWLIST_WARNING === 'true',
|
GOOSE_ALLOWLIST_WARNING: process.env.GOOSE_ALLOWLIST_WARNING === 'true',
|
||||||
secretKey: generateSecretKey(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track windows by ID
|
// Track windows by ID
|
||||||
@@ -559,7 +556,12 @@ const createChat = async (
|
|||||||
const envVars = {
|
const envVars = {
|
||||||
GOOSE_SCHEDULER_TYPE: process.env.GOOSE_SCHEDULER_TYPE,
|
GOOSE_SCHEDULER_TYPE: process.env.GOOSE_SCHEDULER_TYPE,
|
||||||
};
|
};
|
||||||
const [newPort, newWorkingDir, newGoosedProcess] = await startGoosed(app, dir, envVars);
|
const [newPort, newWorkingDir, newGoosedProcess] = await startGoosed(
|
||||||
|
app,
|
||||||
|
SERVER_SECRET,
|
||||||
|
dir,
|
||||||
|
envVars
|
||||||
|
);
|
||||||
port = newPort;
|
port = newPort;
|
||||||
working_dir = newWorkingDir;
|
working_dir = newWorkingDir;
|
||||||
goosedProcess = newGoosedProcess;
|
goosedProcess = newGoosedProcess;
|
||||||
@@ -1038,14 +1040,17 @@ ipcMain.handle('directory-chooser', (_event, replace: boolean = false) => {
|
|||||||
// Handle scheduling engine settings
|
// Handle scheduling engine settings
|
||||||
ipcMain.handle('get-settings', () => {
|
ipcMain.handle('get-settings', () => {
|
||||||
try {
|
try {
|
||||||
const settings = loadSettings();
|
return loadSettings();
|
||||||
return settings;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting settings:', error);
|
console.error('Error getting settings:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-secret-key', () => {
|
||||||
|
return SERVER_SECRET;
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('set-scheduling-engine', async (_event, engine: string) => {
|
ipcMain.handle('set-scheduling-engine', async (_event, engine: string) => {
|
||||||
try {
|
try {
|
||||||
const settings = loadSettings();
|
const settings = loadSettings();
|
||||||
@@ -1614,8 +1619,7 @@ ipcMain.handle('list-files', async (_event, dirPath, extension) => {
|
|||||||
|
|
||||||
// Handle message box dialogs
|
// Handle message box dialogs
|
||||||
ipcMain.handle('show-message-box', async (_event, options) => {
|
ipcMain.handle('show-message-box', async (_event, options) => {
|
||||||
const result = await dialog.showMessageBox(options);
|
return dialog.showMessageBox(options);
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-allowed-extensions', async () => {
|
ipcMain.handle('get-allowed-extensions', async () => {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ type ElectronAPI = {
|
|||||||
setDockIcon: (show: boolean) => Promise<boolean>;
|
setDockIcon: (show: boolean) => Promise<boolean>;
|
||||||
getDockIconState: () => Promise<boolean>;
|
getDockIconState: () => Promise<boolean>;
|
||||||
getSettings: () => Promise<unknown | null>;
|
getSettings: () => Promise<unknown | null>;
|
||||||
|
getSecretKey: () => Promise<string>;
|
||||||
setSchedulingEngine: (engine: string) => Promise<boolean>;
|
setSchedulingEngine: (engine: string) => Promise<boolean>;
|
||||||
setQuitConfirmation: (show: boolean) => Promise<boolean>;
|
setQuitConfirmation: (show: boolean) => Promise<boolean>;
|
||||||
getQuitConfirmationState: () => Promise<boolean>;
|
getQuitConfirmationState: () => Promise<boolean>;
|
||||||
@@ -171,6 +172,7 @@ const electronAPI: ElectronAPI = {
|
|||||||
setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show),
|
setDockIcon: (show: boolean) => ipcRenderer.invoke('set-dock-icon', show),
|
||||||
getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'),
|
getDockIconState: () => ipcRenderer.invoke('get-dock-icon-state'),
|
||||||
getSettings: () => ipcRenderer.invoke('get-settings'),
|
getSettings: () => ipcRenderer.invoke('get-settings'),
|
||||||
|
getSecretKey: () => ipcRenderer.invoke('get-secret-key'),
|
||||||
setSchedulingEngine: (engine: string) => ipcRenderer.invoke('set-scheduling-engine', engine),
|
setSchedulingEngine: (engine: string) => ipcRenderer.invoke('set-scheduling-engine', engine),
|
||||||
setQuitConfirmation: (show: boolean) => ipcRenderer.invoke('set-quit-confirmation', show),
|
setQuitConfirmation: (show: boolean) => ipcRenderer.invoke('set-quit-confirmation', show),
|
||||||
getQuitConfirmationState: () => ipcRenderer.invoke('get-quit-confirmation-state'),
|
getQuitConfirmationState: () => ipcRenderer.invoke('get-quit-confirmation-state'),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { clsx, type ClassValue } from 'clsx';
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
import { client } from './api/client.gen';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
@@ -16,3 +17,19 @@ export function patchConsoleLogging() {
|
|||||||
// Intercept console methods
|
// Intercept console methods
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This needs to be called before any API calls are made, but since we're using the client
|
||||||
|
// in multiple useEffect locations, we can't be sure who goes first.
|
||||||
|
let clientInitialized = false;
|
||||||
|
|
||||||
|
export async function ensureClientInitialized() {
|
||||||
|
if (clientInitialized) return;
|
||||||
|
client.setConfig({
|
||||||
|
baseUrl: window.appConfig.get('GOOSE_API_HOST') + ':' + window.appConfig.get('GOOSE_PORT'),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
clientInitialized = true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Import the proper type from ConfigContext
|
// Import the proper type from ConfigContext
|
||||||
import { getApiUrl, getSecretKey } from '../config';
|
import { getApiUrl } from '../config';
|
||||||
import { safeJsonParse } from './jsonUtils';
|
import { safeJsonParse } from './jsonUtils';
|
||||||
|
|
||||||
export interface ModelCostInfo {
|
export interface ModelCostInfo {
|
||||||
@@ -31,7 +31,7 @@ async function fetchPricingForModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiUrl = getApiUrl('/config/pricing');
|
const apiUrl = getApiUrl('/config/pricing');
|
||||||
const secretKey = getSecretKey();
|
const secretKey = await window.electron.getSecretKey();
|
||||||
|
|
||||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||||
if (secretKey) {
|
if (secretKey) {
|
||||||
@@ -217,7 +217,7 @@ export async function refreshPricing(): Promise<boolean> {
|
|||||||
|
|
||||||
// The actual refresh happens on the backend when we call with configured_only: false
|
// The actual refresh happens on the backend when we call with configured_only: false
|
||||||
const apiUrl = getApiUrl('/config/pricing');
|
const apiUrl = getApiUrl('/config/pricing');
|
||||||
const secretKey = getSecretKey();
|
const secretKey = await window.electron.getSecretKey();
|
||||||
|
|
||||||
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
const headers: HeadersInit = { 'Content-Type': 'application/json' };
|
||||||
if (secretKey) {
|
if (secretKey) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getApiUrl, getSecretKey } from '../config';
|
import { getApiUrl } from '../config';
|
||||||
import { FullExtensionConfig } from '../extensions';
|
import { FullExtensionConfig } from '../extensions';
|
||||||
import { initializeAgent } from '../agent';
|
import { initializeAgent } from '../agent';
|
||||||
import {
|
import {
|
||||||
@@ -98,7 +98,7 @@ export const updateSystemPromptWithParameters = async (
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
extension: `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${substitutedInstructions}`,
|
extension: `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${substitutedInstructions}`,
|
||||||
@@ -135,8 +135,6 @@ export const updateSystemPromptWithParameters = async (
|
|||||||
* NOTE: This logic can be removed eventually when enough versions have passed
|
* NOTE: This logic can be removed eventually when enough versions have passed
|
||||||
* We leave the existing user settings in localStorage, in case users downgrade
|
* We leave the existing user settings in localStorage, in case users downgrade
|
||||||
* or things need to be reverted.
|
* or things need to be reverted.
|
||||||
*
|
|
||||||
* @param addExtension Function to add extension to config.yaml
|
|
||||||
*/
|
*/
|
||||||
export const migrateExtensionsToSettingsV3 = async () => {
|
export const migrateExtensionsToSettingsV3 = async () => {
|
||||||
console.log('need to perform extension migration v3');
|
console.log('need to perform extension migration v3');
|
||||||
@@ -227,7 +225,7 @@ export const initializeSystem = async (
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
extension: prompt,
|
extension: prompt,
|
||||||
@@ -247,7 +245,7 @@ export const initializeSystem = async (
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Secret-Key': getSecretKey(),
|
'X-Secret-Key': await window.electron.getSecretKey(),
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
response: responseConfig,
|
response: responseConfig,
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
|
|
||||||
|
build: {
|
||||||
|
target: 'esnext'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user