mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 12:12:22 +02:00
[eric] windows: fix UA-spoof leak onto recreated main window + add diag logs across preload, createWindow, recreateMainWindow, resumeSession, launchAndSendFirstMessage, AgentChat, WebSocketManager so packaged-build stderr captures the chat-spawn crash trail
This commit is contained in:
+19
-1
@@ -147,6 +147,8 @@ let isQuittingFromSplash = false; // guards against double-quit during error sh
|
||||
let rendererCrashTimes = []; // timestamps of recent render-process-gone events; caps the auto-reload retry storm
|
||||
const recentBackendStderr = []; // ring buffer (last ~60 lines) for splash error UI
|
||||
let splashDataUrlCache = null;
|
||||
// Set to true around `new BrowserWindow()` for the top-level main window so the popup-UA spoofer in app.on('web-contents-created') doesn't accidentally rewrite the main window's UA. The web-contents-created event fires synchronously inside the BrowserWindow constructor, before mainWindow assignment returns; without this flag, the previous identity check (contents !== mainWindow.webContents) is racy across recreateMainWindow() because mainWindow still points to the OLD window during construction of the NEW one.
|
||||
let isCreatingMainWindow = false;
|
||||
|
||||
const isPackaged = app.isPackaged;
|
||||
const isDev = process.env.ELECTRON_DEV === '1';
|
||||
@@ -673,6 +675,8 @@ async function loadAuthToken() {
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
isCreatingMainWindow = true;
|
||||
console.log('[diag][main] createWindow start');
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
@@ -784,6 +788,16 @@ function createWindow() {
|
||||
};
|
||||
mainWindow.on('blur', () => sendFocusEvent('blur'));
|
||||
mainWindow.on('focus', () => sendFocusEvent('focus'));
|
||||
|
||||
// Forward renderer console output to main stderr so packaged-build diagnostics survive without DevTools open.
|
||||
mainWindow.webContents.on('console-message', (_e, level, message, line, sourceId) => {
|
||||
const tag = ['LOG', 'INFO', 'WARN', 'ERROR'][level] || 'LOG';
|
||||
const src = sourceId ? sourceId.split('/').pop() : '';
|
||||
console.log(`[renderer:${tag}] ${message}${src ? ` (${src}:${line})` : ''}`);
|
||||
});
|
||||
|
||||
isCreatingMainWindow = false;
|
||||
console.log('[diag][main] createWindow end, ua=', mainWindow.webContents.getUserAgent());
|
||||
}
|
||||
|
||||
// Crash recovery path A: tear down the dead BrowserWindow and stand up a fresh one. Used by the render-process-gone handler under the 3-in-60s cap.
|
||||
@@ -795,14 +809,16 @@ function createWindow() {
|
||||
// Why setImmediate for the destroy:
|
||||
// - We're INSIDE the old window's render-process-gone handler. Destroying its BrowserWindow from inside its own event callback works in current Electron but is fragile across version bumps; deferring one tick is free insurance.
|
||||
function recreateMainWindow() {
|
||||
console.log('[diag][main] recreateMainWindow START, crashesInWindow=', rendererCrashTimes.length);
|
||||
const oldWindow = mainWindow;
|
||||
mainWindowReady = false;
|
||||
try {
|
||||
createWindow();
|
||||
} catch (err) {
|
||||
console.error('[main] recreateMainWindow: createWindow failed:', err && err.message);
|
||||
console.error('[diag][main] recreateMainWindow: createWindow failed:', err && err.message);
|
||||
return;
|
||||
}
|
||||
console.log('[diag][main] recreateMainWindow created fresh window, ua=', mainWindow && mainWindow.webContents.getUserAgent());
|
||||
const freshWindow = (mainWindow && mainWindow !== oldWindow) ? mainWindow : null;
|
||||
if (freshWindow) {
|
||||
freshWindow.once('ready-to-show', () => {
|
||||
@@ -1159,9 +1175,11 @@ app.on('web-contents-created', (_event, contents) => {
|
||||
// skipped — they render user-visited sites and must advertise the real UA.
|
||||
if (
|
||||
contents.getType() === 'window' &&
|
||||
!isCreatingMainWindow &&
|
||||
mainWindow &&
|
||||
contents !== mainWindow.webContents
|
||||
) {
|
||||
console.log('[diag][main] spoofing UA for popup webContents id=', contents.id);
|
||||
const OAUTH_POPUP_UA = process.platform === 'win32'
|
||||
? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
|
||||
'(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openswarm",
|
||||
"version": "1.1.44",
|
||||
"version": "1.1.45",
|
||||
"description": "OpenSwarm — AI Agent Orchestrator",
|
||||
"author": "openswarm-ai",
|
||||
"main": "main.js",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][preload] start, ua=', navigator.userAgent);
|
||||
|
||||
// Synchronous exposure. The previous async IIFE (await ipcRenderer.invoke) raced React mount: any code reading window.openswarm during the gap (BrowserCard's Electron-detection falling back to iframe mode, AgentChat's auth-token call throwing) saw undefined. sendSync blocks the renderer for one IPC round-trip during preload before any user-visible paint, so window.openswarm is guaranteed to exist before the first frontend bundle evaluates.
|
||||
const port = ipcRenderer.sendSync('get-backend-port-sync');
|
||||
const webviewPreloadPath = ipcRenderer.sendSync('get-webview-preload-path-sync');
|
||||
|
||||
@@ -159,6 +159,8 @@ interface AgentChatProps {
|
||||
}
|
||||
|
||||
const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose, embedded, autoFocus, isGlowing, onDismissGlow, initialContextPaths, onBranch }) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][AgentChat] render', { sessionIdProp, embedded });
|
||||
const c = useClaudeTokens();
|
||||
const STATUS_STYLES: Record<string, { color: string; bg: string }> = {
|
||||
running: { color: c.status.success, bg: c.status.successBg },
|
||||
|
||||
@@ -74,6 +74,8 @@ const HANDLE_DEFS: { dir: ResizeDir; sx: Record<string, any> }[] = [
|
||||
];
|
||||
|
||||
const isElectron = navigator.userAgent.includes('Electron');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][BrowserCard] isElectron=', isElectron, 'ua=', navigator.userAgent, 'hasOpenswarm=', !!(window as any).openswarm);
|
||||
|
||||
const chromeUserAgent = navigator.userAgent
|
||||
.replace(/\s*Electron\/\S+/, '')
|
||||
|
||||
@@ -291,13 +291,19 @@ export const fetchSession = createAsyncThunk(
|
||||
export const launchAndSendFirstMessage = createAsyncThunk(
|
||||
'agents/launchAndSendFirstMessage',
|
||||
async ({ draftId, config, prompt, mode, model, provider, images, contextPaths, forcedTools, attachedSkills, selectedBrowserIds }: LaunchAndSendPayload) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] launchAndSendFirstMessage START draft=', draftId, 'mode=', mode, 'model=', model, 'provider=', provider);
|
||||
const launchRes = await fetch(`${AGENTS_API}/launch`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] launch fetch ok=', launchRes.ok, 'status=', launchRes.status);
|
||||
const launchData = await launchRes.json();
|
||||
const session = launchData.session as AgentSession;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] launch parsed, sessionId=', session && session.id);
|
||||
|
||||
await fetch(`${AGENTS_API}/sessions/${session.id}/message`, {
|
||||
method: 'POST',
|
||||
@@ -470,9 +476,21 @@ export const searchHistory = createAsyncThunk(
|
||||
export const resumeSession = createAsyncThunk(
|
||||
'agents/resumeSession',
|
||||
async ({ sessionId }: { sessionId: string }) => {
|
||||
const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/resume`, { method: 'POST' });
|
||||
const data = await res.json();
|
||||
return data.session as AgentSession;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] resumeSession START', sessionId);
|
||||
try {
|
||||
const res = await fetch(`${AGENTS_API}/sessions/${sessionId}/resume`, { method: 'POST' });
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] resumeSession fetch ok=', res.ok, 'status=', res.status);
|
||||
const data = await res.json();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][thunk] resumeSession parsed, keys=', Object.keys(data || {}).join(','));
|
||||
return data.session as AgentSession;
|
||||
} catch (e: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[diag][thunk] resumeSession THREW', e && e.message);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -192,6 +192,8 @@ class WebSocketManager {
|
||||
const token = _getAuthTokenSafe();
|
||||
const sep = this.url.includes('?') ? '&' : '?';
|
||||
const urlWithToken = token ? `${this.url}${sep}token=${encodeURIComponent(token)}` : this.url;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[diag][ws] connect', this.url, 'sessionId=', this.options.sessionId, 'hasToken=', !!token);
|
||||
this.ws = new WebSocket(urlWithToken);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
|
||||
Reference in New Issue
Block a user