mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-29 11:19:44 +02:00
[eric] copy an agent and its inline browser comes with it (ENG-250); browsing caches are swept past a 1.5GB cap at boot so runaway disk use cannot get the app killed (ENG-247)
This commit is contained in:
+37
-1
@@ -138,7 +138,7 @@ try {
|
||||
}
|
||||
} catch (_) {}
|
||||
const path = require('path');
|
||||
const { spawn, execFileSync } = require('child_process');
|
||||
const { spawn, execFileSync, spawnSync } = require('child_process');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const hiddenBrowser = require('./hiddenBrowser');
|
||||
@@ -1923,6 +1923,40 @@ async function clearStaleFrontendCache() {
|
||||
}
|
||||
}
|
||||
|
||||
// Browsing caches grow without bound (measured on a real profile: Partitions 2.2GB, Code Cache
|
||||
// 291MB), and a machine that blows through macOS's disk-write ceiling gets the app killed with no
|
||||
// crash report (ENG-247). So bound them: past the cap, drop the REGENERABLE bytes at boot. Cookies,
|
||||
// localStorage and IndexedDB are deliberately untouched, clearing those would sign the user out of
|
||||
// every site their agents rely on; clearCache only drops fetched bytes the network can refetch.
|
||||
const DISK_CACHE_CAP_MB = 1500;
|
||||
|
||||
function dirSizeMb(dir) {
|
||||
try {
|
||||
const out = spawnSync('du', ['-sk', dir], { encoding: 'utf8', timeout: 20000 });
|
||||
const kb = parseInt(String((out && out.stdout) || '0').trim().split(/\s+/)[0], 10);
|
||||
return Number.isFinite(kb) ? Math.round(kb / 1024) : 0;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async function sweepOversizedCaches() {
|
||||
try {
|
||||
const root = app.getPath('userData');
|
||||
const before = dirSizeMb(path.join(root, 'Partitions')) + dirSizeMb(path.join(root, 'Code Cache'));
|
||||
if (before < DISK_CACHE_CAP_MB) {
|
||||
console.log(`[cache] browsing caches ${before}MB, under the ${DISK_CACHE_CAP_MB}MB cap; nothing to sweep`);
|
||||
return;
|
||||
}
|
||||
await session.fromPartition(BROWSER_PARTITION).clearCache();
|
||||
await session.defaultSession.clearCache();
|
||||
const after = dirSizeMb(path.join(root, 'Partitions')) + dirSizeMb(path.join(root, 'Code Cache'));
|
||||
console.log(`[cache] swept oversized browsing caches: ${before}MB -> ${after}MB (logins untouched)`);
|
||||
} catch (err) {
|
||||
console.warn('[cache] sweepOversizedCaches failed:', err && err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function setupAutoUpdater() {
|
||||
if (!autoUpdater) return;
|
||||
// Escape hatch for locally-built packaged smokes: an unpublished build otherwise downloads the
|
||||
@@ -2418,6 +2452,8 @@ app.whenReady().then(async () => {
|
||||
// Must run before createWindow loads the URL, or the renderer fetches the stale bundle first.
|
||||
await clearStaleFrontendCache();
|
||||
createWindow();
|
||||
// After first paint, never before: the sweep shells out to du and must not delay the window.
|
||||
setTimeout(() => { void sweepOversizedCaches(); }, 8000);
|
||||
if (!isDev) {
|
||||
setupAutoUpdater();
|
||||
mainWindow.webContents.on('did-finish-load', () => {
|
||||
|
||||
@@ -66,6 +66,18 @@ export function useDashboardClipboard({
|
||||
expanded: expandedSessionIds.includes(id),
|
||||
});
|
||||
names.push(session.name || id);
|
||||
// A chat's browser lives INSIDE it, so copying the agent has to carry it along; selecting a
|
||||
// card the user sees as one thing must not paste half of it (ENG-250). Deduped below in case
|
||||
// the browser was independently selected too.
|
||||
for (const bc of Object.values(browserCards)) {
|
||||
if (bc.docked_to !== id && bc.spawned_by !== id) continue;
|
||||
const tab = bc.tabs.find((t) => t.id === bc.activeTabId);
|
||||
copied.push({
|
||||
type: 'browser', id: bc.browser_id, name: tab?.title || 'Browser',
|
||||
meta: { name: tab?.title || 'Browser', url: tab?.url || bc.url, tabs: bc.tabs, spawnedBy: id },
|
||||
x: bc.x, y: bc.y, width: bc.width, height: bc.height,
|
||||
});
|
||||
}
|
||||
} else if (type === 'view') {
|
||||
const output = outputs[id];
|
||||
const vc = viewCards[id];
|
||||
@@ -90,7 +102,9 @@ export function useDashboardClipboard({
|
||||
names.push(title);
|
||||
}
|
||||
}
|
||||
setClipboardCards(copied);
|
||||
// Selecting an agent AND its browser must not paste the browser twice.
|
||||
const deduped = copied.filter((c, i) => copied.findIndex((o) => o.type === c.type && o.id === c.id) === i);
|
||||
setClipboardCards(deduped);
|
||||
navigator.clipboard.writeText(names.join(', ')).catch(() => {});
|
||||
// Copy IS attach: the selection lands in the composer as context chips, no select mode, no paste step.
|
||||
if (copied.length > 0) onCopiedToContext?.(copied);
|
||||
|
||||
Reference in New Issue
Block a user