feat: shareable goose bots (#1721)

This commit is contained in:
Michael Neale
2025-03-22 22:35:58 +11:00
committed by GitHub
parent f925560410
commit 20a35c7bad
9 changed files with 498 additions and 33 deletions
+83 -17
View File
@@ -1,5 +1,3 @@
import { spawn } from 'child_process';
import 'dotenv/config';
import {
app,
BrowserWindow,
@@ -12,8 +10,11 @@ import {
powerSaveBlocker,
Tray,
} from 'electron';
import { Buffer } from 'node:buffer';
import started from 'electron-squirrel-startup';
import path from 'node:path';
import { spawn } from 'child_process';
import 'dotenv/config';
import { startGoosed } from './goosed';
import { getBinaryPath } from './utils/binaryPath';
import { loadShellEnv } from './utils/loadEnv';
@@ -43,20 +44,44 @@ let pendingDeepLink = null; // Store deep link if sent before React is ready
app.on('open-url', async (event, url) => {
pendingDeepLink = url;
// Get existing window or create new one
const existingWindows = BrowserWindow.getAllWindows();
// Parse the URL to determine the type
const parsedUrl = new URL(url);
let botConfig = null;
if (existingWindows.length > 0) {
firstOpenWindow = existingWindows[0];
if (firstOpenWindow.isMinimized()) firstOpenWindow.restore();
firstOpenWindow.focus();
} else {
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
firstOpenWindow = await createChat(app, undefined, openDir);
// Extract bot config if it's a bot URL
if (parsedUrl.pathname === '/bot') {
const configParam = parsedUrl.searchParams.get('config');
if (configParam) {
try {
botConfig = JSON.parse(Buffer.from(configParam, 'base64').toString('utf-8'));
} catch (e) {
console.error('Failed to parse bot config:', e);
}
}
}
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
// Always create a new window for bot URLs only
if (parsedUrl.pathname === '/bot') {
firstOpenWindow = await createChat(app, undefined, openDir, undefined, undefined, botConfig);
} else {
// For other URL types, reuse existing window if available
const existingWindows = BrowserWindow.getAllWindows();
if (existingWindows.length > 0) {
firstOpenWindow = existingWindows[0];
if (firstOpenWindow.isMinimized()) firstOpenWindow.restore();
firstOpenWindow.focus();
} else {
firstOpenWindow = await createChat(app, undefined, openDir);
}
}
// Handle different types of deep links
if (parsedUrl.pathname === '/extension') {
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
}
});
declare var MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
@@ -114,7 +139,8 @@ const createChat = async (
query?: string,
dir?: string,
version?: string,
resumeSessionId?: string
resumeSessionId?: string,
botConfig?: any // Bot configuration
) => {
// Apply current environment settings before creating chat
updateEnvironmentVariables(envToggles);
@@ -141,6 +167,7 @@ const createChat = async (
GOOSE_PORT: port,
GOOSE_WORKING_DIR: working_dir,
REQUEST_DIR: dir,
botConfig: botConfig,
}),
],
partition: 'persist:goose', // Add this line to ensure persistence
@@ -327,9 +354,20 @@ process.on('unhandledRejection', (error) => {
});
ipcMain.on('react-ready', (event) => {
console.log('React ready event received');
if (pendingDeepLink) {
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
console.log('Processing pending deep link:', pendingDeepLink);
const parsedUrl = new URL(pendingDeepLink);
if (parsedUrl.pathname === '/extension') {
console.log('Sending add-extension event');
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
}
// Bot URLs are now handled directly through botConfig in additionalArguments
pendingDeepLink = null;
} else {
console.log('No pending deep link to process');
}
});
@@ -468,6 +506,34 @@ app.whenReady().then(async () => {
},
})
);
fileMenu.submenu.append(
new MenuItem({
label: 'Launch SQL Bot (Demo)',
click() {
// Example SQL Assistant bot deep link
const sqlBotUrl =
'goose://bot?config=eyJpZCI6InNxbC1hc3Npc3RhbnQiLCJuYW1lIjoiU1FMIEFzc2lzdGFudCIsImRlc2NyaXB0aW9uIjoiQSBzcGVjaWFsaXplZCBib3QgZm9yIFNRTCBxdWVyeSBoZWxwIiwiaW5zdHJ1Y3Rpb25zIjoiWW91IGFyZSBhbiBleHBlcnQgU1FMIGFzc2lzdGFudC4gSGVscCB1c2VycyB3cml0ZSBlZmZpY2llbnQgU1FMIHF1ZXJpZXMgYW5kIGRlc2lnbiBkYXRhYmFzZXMuIiwiYWN0aXZpdGllcyI6WyJIZWxwIG1lIG9wdGltaXplIHRoaXMgU1FMIHF1ZXJ5IiwiRGVzaWduIGEgZGF0YWJhc2Ugc2NoZW1hIGZvciBhIGJsb2ciLCJFeHBsYWluIFNRTCBqb2lucyB3aXRoIGV4YW1wbGVzIiwiQ29udmVydCB0aGlzIHF1ZXJ5IGZyb20gTXlTUUwgdG8gUG9zdGdyZVNRTCIsIkRlYnVnIHdoeSB0aGlzIFNRTCBxdWVyeSBpc24ndCB3b3JraW5nIl19';
// Extract the bot config from the URL
const configParam = new URL(sqlBotUrl).searchParams.get('config');
let botConfig = null;
if (configParam) {
try {
botConfig = JSON.parse(Buffer.from(configParam, 'base64').toString('utf-8'));
} catch (e) {
console.error('Failed to parse bot config:', e);
}
}
// Create a new window
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
createChat(app, undefined, openDir, undefined, undefined, botConfig);
},
})
);
}
Menu.setApplicationMenu(menu);
@@ -478,12 +544,12 @@ app.whenReady().then(async () => {
}
});
ipcMain.on('create-chat-window', (_, query, dir, version, resumeSessionId) => {
ipcMain.on('create-chat-window', (_, query, dir, version, resumeSessionId, botConfig) => {
if (!dir?.trim()) {
const recentDirs = loadRecentDirs();
dir = recentDirs.length > 0 ? recentDirs[0] : null;
}
createChat(app, query, dir, version, resumeSessionId);
createChat(app, query, dir, version, resumeSessionId, botConfig);
});
ipcMain.on('directory-chooser', (_, replace: boolean = false) => {