Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .superset/lib/setup/steps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,8 @@ step_write_env() {
# Each workspace gets a range of 20 ports from its base.
# Offsets: +0 web, +1 api, +2 marketing, +3 admin, +4 docs,
# +5 desktop vite, +6 notifications, +7 streams, +8 streams internal, +9 electric,
# +10 caddy (HTTP/2 reverse proxy for API electric endpoint), +11 code inspector
# +10 caddy (HTTP/2 reverse proxy for API electric endpoint), +11 code inspector,
# +12 desktop automation (CDP)
local BASE=$SUPERSET_PORT_BASE

# App ports (fixed offsets from base)
Expand All @@ -366,6 +367,7 @@ step_write_env() {
local ELECTRIC_PORT=$((BASE + 9))
local CADDY_ELECTRIC_PORT=$((BASE + 10))
local CODE_INSPECTOR_PORT=$((BASE + 11))
local DESKTOP_AUTOMATION_PORT=$((BASE + 12))

echo ""
echo "# Workspace Ports (allocated from SUPERSET_PORT_BASE=$BASE, range=20)"
Expand All @@ -382,6 +384,7 @@ step_write_env() {
write_env_var "ELECTRIC_PORT" "$ELECTRIC_PORT"
write_env_var "CADDY_ELECTRIC_PORT" "$CADDY_ELECTRIC_PORT"
write_env_var "CODE_INSPECTOR_PORT" "$CODE_INSPECTOR_PORT"
write_env_var "DESKTOP_AUTOMATION_PORT" "$DESKTOP_AUTOMATION_PORT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "DESKTOP_AUTOMATION_PORT" --type-all -C 3

Repository: superset-sh/superset

Length of output: 502


🏁 Script executed:

# Check the ports.json generation section
fd "steps.sh" -type f | head -5

Repository: superset-sh/superset

Length of output: 234


🏁 Script executed:

rg "DESKTOP_AUTOMATION_PORT" -C 3

Repository: superset-sh/superset

Length of output: 2639


🏁 Script executed:

fd steps.sh

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Find and read the steps.sh file to check lines mentioned
fd "steps.sh" -type f | xargs wc -l

Repository: superset-sh/superset

Length of output: 253


🏁 Script executed:

fd "steps.sh" -o fd "ports.json"

Repository: superset-sh/superset

Length of output: 187


🏁 Script executed:

# Search more broadly for the file
find . -name "steps.sh" -o -name "ports.json" 2>/dev/null | head -20

Repository: superset-sh/superset

Length of output: 128


🏁 Script executed:

# Check if .superset directory exists
ls -la .superset 2>/dev/null || echo ".superset not found"

Repository: superset-sh/superset

Length of output: 408


🏁 Script executed:

wc -l ./.superset/lib/setup/steps.sh

Repository: superset-sh/superset

Length of output: 98


🏁 Script executed:

# Read the steps.sh file to check the relevant sections
cat -n ./.superset/lib/setup/steps.sh | sed -n '375,395p'

Repository: superset-sh/superset

Length of output: 1445


🏁 Script executed:

# Check the ports.json generation section
cat -n ./.superset/lib/setup/steps.sh | sed -n '425,450p'

Repository: superset-sh/superset

Length of output: 1227


Add DESKTOP_AUTOMATION_PORT to ports.json.

The DESKTOP_AUTOMATION_PORT is written to the .env file at line 387, but it's missing from the ports.json file generated at lines 428-445. All other ports (including CODE_INSPECTOR_PORT) are included in that file. Since this port is actively used for CDP/automation in the application, it should be documented in ports.json for consistency.

Add the following entry:

Suggested diff
    { "port": $CODE_INSPECTOR_PORT, "label": "Code Inspector" }
+   { "port": $DESKTOP_AUTOMATION_PORT, "label": "Desktop Automation" }
  ]
🤖 Prompt for AI Agents
In @.superset/lib/setup/steps.sh at line 387, Add DESKTOP_AUTOMATION_PORT to the
ports.json generation block so the port written by write_env_var
"DESKTOP_AUTOMATION_PORT" is documented; locate the ports.json creation code
(the block that currently writes CODE_INSPECTOR_PORT and other ports) and insert
an entry for DESKTOP_AUTOMATION_PORT alongside the existing port entries so it
appears in the generated ports.json output.

echo ""
echo "# Cross-app URLs (overrides from root .env)"
write_env_var "NEXT_PUBLIC_API_URL" "http://localhost:$API_PORT"
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/src/lib/electron-app/factories/app/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,16 @@ export async function makeAppSetup(
}
});

app.on("web-contents-created", (_, contents) =>
app.on("web-contents-created", (_, contents) => {
if (contents.getType() === "webview") return;
contents.on("will-navigate", (event, url) => {
// Always prevent in-app navigation for external URLs
if (url.startsWith("http://") || url.startsWith("https://")) {
event.preventDefault();
shell.openExternal(url);
}
}),
);
});
});

app.on("window-all-closed", () => !PLATFORM.IS_MAC && app.quit());
app.on("before-quit", () => {});
Expand Down
70 changes: 70 additions & 0 deletions apps/desktop/src/lib/trpc/routers/browser-history/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { browserHistory } from "@superset/local-db";
import { like, or, sql } from "drizzle-orm";
import { localDb } from "main/lib/local-db";
import { z } from "zod";
import { publicProcedure, router } from "../..";

export const createBrowserHistoryRouter = () => {
return router({
getAll: publicProcedure.query(() => {
return localDb
.select()
.from(browserHistory)
.orderBy(sql`${browserHistory.lastVisitedAt} desc`)
.limit(500)
.all();
}),

search: publicProcedure
.input(z.object({ query: z.string() }))
.query(({ input }) => {
const pattern = `%${input.query}%`;
return localDb
.select()
.from(browserHistory)
.where(
or(
like(browserHistory.url, pattern),
like(browserHistory.title, pattern),
),
)
.orderBy(sql`${browserHistory.lastVisitedAt} desc`)
.limit(10)
.all();
}),

upsert: publicProcedure
.input(
z.object({
url: z.string(),
title: z.string(),
faviconUrl: z.string().nullable().optional(),
}),
)
.mutation(({ input }) => {
localDb
.insert(browserHistory)
.values({
url: input.url,
title: input.title,
faviconUrl: input.faviconUrl ?? null,
lastVisitedAt: Date.now(),
visitCount: 1,
})
.onConflictDoUpdate({
target: browserHistory.url,
set: {
title: input.title,
faviconUrl: input.faviconUrl ?? null,
lastVisitedAt: Date.now(),
visitCount: sql`${browserHistory.visitCount} + 1`,
},
})
.run();
}),

clear: publicProcedure.mutation(() => {
localDb.delete(browserHistory).run();
}),
});
};
176 changes: 176 additions & 0 deletions apps/desktop/src/lib/trpc/routers/browser/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { observable } from "@trpc/server/observable";
import { session } from "electron";
import { browserManager } from "main/lib/browser/browser-manager";
import { z } from "zod";
import { publicProcedure, router } from "../..";

export const createBrowserRouter = () => {
return router({
register: publicProcedure
.input(z.object({ paneId: z.string(), webContentsId: z.number() }))
.mutation(({ input }) => {
browserManager.register(input.paneId, input.webContentsId);
return { success: true };
}),

unregister: publicProcedure
.input(z.object({ paneId: z.string() }))
.mutation(({ input }) => {
browserManager.unregister(input.paneId);
return { success: true };
}),

navigate: publicProcedure
.input(z.object({ paneId: z.string(), url: z.string() }))
.mutation(({ input }) => {
const wc = browserManager.getWebContents(input.paneId);
if (!wc) throw new Error(`No webContents for pane ${input.paneId}`);
wc.loadURL(input.url);
return { success: true };
}),

goBack: publicProcedure
.input(z.object({ paneId: z.string() }))
.mutation(({ input }) => {
const wc = browserManager.getWebContents(input.paneId);
if (wc?.canGoBack()) wc.goBack();
return { success: true };
}),

goForward: publicProcedure
.input(z.object({ paneId: z.string() }))
.mutation(({ input }) => {
const wc = browserManager.getWebContents(input.paneId);
if (wc?.canGoForward()) wc.goForward();
return { success: true };
}),

reload: publicProcedure
.input(z.object({ paneId: z.string(), hard: z.boolean().optional() }))
.mutation(({ input }) => {
const wc = browserManager.getWebContents(input.paneId);
if (!wc) return { success: false };
if (input.hard) {
wc.reloadIgnoringCache();
} else {
wc.reload();
}
return { success: true };
}),

screenshot: publicProcedure
.input(z.object({ paneId: z.string() }))
.mutation(async ({ input }) => {
const base64 = await browserManager.screenshot(input.paneId);
return { base64 };
}),

evaluateJS: publicProcedure
.input(z.object({ paneId: z.string(), code: z.string() }))
.mutation(async ({ input }) => {
const result = await browserManager.evaluateJS(
input.paneId,
input.code,
);
return { result };
}),

getConsoleLogs: publicProcedure
.input(z.object({ paneId: z.string() }))
.query(({ input }) => {
return browserManager.getConsoleLogs(input.paneId);
}),

consoleStream: publicProcedure
.input(z.object({ paneId: z.string() }))
.subscription(({ input }) => {
return observable<{
level: string;
message: string;
timestamp: number;
}>((emit) => {
const handler = (entry: {
level: string;
message: string;
timestamp: number;
}) => {
emit.next(entry);
};
browserManager.on(`console:${input.paneId}`, handler);
return () => {
browserManager.off(`console:${input.paneId}`, handler);
};
});
}),

onNewWindow: publicProcedure
.input(z.object({ paneId: z.string() }))
.subscription(({ input }) => {
return observable<{ url: string }>((emit) => {
const handler = (url: string) => {
emit.next({ url });
};
browserManager.on(`new-window:${input.paneId}`, handler);
return () => {
browserManager.off(`new-window:${input.paneId}`, handler);
};
});
}),

openDevTools: publicProcedure
.input(z.object({ paneId: z.string() }))
.mutation(({ input }) => {
browserManager.openDevTools(input.paneId);
return { success: true };
}),

getDevToolsUrl: publicProcedure
.input(z.object({ browserPaneId: z.string() }))
.query(async ({ input }) => {
const url = await browserManager.getDevToolsUrl(input.browserPaneId);
return { url };
}),

getPageInfo: publicProcedure
.input(z.object({ paneId: z.string() }))
.query(({ input }) => {
const wc = browserManager.getWebContents(input.paneId);
if (!wc) return null;
return {
url: wc.getURL(),
title: wc.getTitle(),
canGoBack: wc.canGoBack(),
canGoForward: wc.canGoForward(),
isLoading: wc.isLoading(),
};
}),

clearBrowsingData: publicProcedure
.input(
z.object({
type: z.enum(["cookies", "cache", "storage", "all"]),
}),
)
.mutation(async ({ input }) => {
const ses = session.fromPartition("persist:superset");
switch (input.type) {
case "cookies":
await ses.clearStorageData({ storages: ["cookies"] });
break;
case "cache":
await ses.clearCache();
break;
case "storage":
await ses.clearStorageData({
storages: ["localstorage", "indexdb"],
});
break;
case "all":
await ses.clearStorageData();
await ses.clearCache();
break;
}
return { success: true };
}),
});
};
4 changes: 4 additions & 0 deletions apps/desktop/src/lib/trpc/routers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { createAiChatRouter } from "./ai-chat";
import { createAnalyticsRouter } from "./analytics";
import { createAuthRouter } from "./auth";
import { createAutoUpdateRouter } from "./auto-update";
import { createBrowserRouter } from "./browser/browser";
import { createBrowserHistoryRouter } from "./browser-history";
import { createCacheRouter } from "./cache";
import { createChangesRouter } from "./changes";
import { createConfigRouter } from "./config";
Expand All @@ -25,6 +27,8 @@ export const createAppRouter = (getWindow: () => BrowserWindow | null) => {
return router({
aiChat: createAiChatRouter(),
analytics: createAnalyticsRouter(),
browser: createBrowserRouter(),
browserHistory: createBrowserHistoryRouter(),
auth: createAuthRouter(),
autoUpdate: createAutoUpdateRouter(),
cache: createCacheRouter(),
Expand Down
30 changes: 29 additions & 1 deletion apps/desktop/src/lib/trpc/routers/ui-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const fileViewerStateSchema = z.object({
const paneSchema = z.object({
id: z.string(),
tabId: z.string(),
type: z.enum(["terminal", "webview", "file-viewer", "chat"]),
type: z.enum(["terminal", "webview", "file-viewer", "chat", "devtools"]),
name: z.string(),
isNew: z.boolean().optional(),
status: z.enum(["idle", "working", "permission", "review"]).optional(),
Expand All @@ -44,6 +44,34 @@ const paneSchema = z.object({
cwdConfirmed: z.boolean().optional(),
fileViewer: fileViewerStateSchema.optional(),
chat: z.object({ sessionId: z.string() }).optional(),
browser: z
.object({
currentUrl: z.string(),
history: z.array(
z.object({
url: z.string(),
title: z.string(),
timestamp: z.number(),
faviconUrl: z.string().optional(),
}),
),
historyIndex: z.number(),
isLoading: z.boolean(),
viewport: z
.object({
name: z.string(),
width: z.number(),
height: z.number(),
})
.nullable()
.optional(),
})
.optional(),
devtools: z
.object({
targetPaneId: z.string(),
})
.optional(),
});

/**
Expand Down
13 changes: 13 additions & 0 deletions apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,19 @@ if (!gotTheLock) {
initSentry();
await initAppState();

// Load browser extension for webview panes
try {
const extensionPath = app.isPackaged
? path.join(process.resourcesPath, "browser-extension")
: path.join(__dirname, "../src/resources/browser-extension");
await session
.fromPartition("persist:superset")
.loadExtension(extensionPath);
console.log("[main] Browser extension loaded");
} catch (error) {
console.error("[main] Failed to load browser extension:", error);
}
Comment on lines +281 to +292

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find electron-builder and electron.vite config files
fd -e "json" -e "config" | grep -E "(electron|build)" | head -20

Repository: superset-sh/superset

Length of output: 96


🏁 Script executed:

# Also check the actual desktop app structure
find apps/desktop -type f -name "*.json" | head -20

Repository: superset-sh/superset

Length of output: 280


🏁 Script executed:

# Look for the browser-extension directory
find . -type d -name "browser-extension" 2>/dev/null

Repository: superset-sh/superset

Length of output: 110


🏁 Script executed:

# Check for electron.vite config in apps/desktop
find apps/desktop -name "electron.vite*" -o -name "vite.config*" | head -10

Repository: superset-sh/superset

Length of output: 100


🏁 Script executed:

# Read the package.json to see build configuration and electron-builder setup
cat apps/desktop/package.json | head -100

Repository: superset-sh/superset

Length of output: 4148


🏁 Script executed:

# Read the electron.vite.config.ts to understand build output structure
cat apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 7208


🏁 Script executed:

# Read the electron-builder.ts to check for extraResources configuration
cat apps/desktop/electron-builder.ts

Repository: superset-sh/superset

Length of output: 5025


🏁 Script executed:

# Find and read the vite/helpers file
find apps/desktop/vite -type f -name "helpers*" -exec cat {} \;

Repository: superset-sh/superset

Length of output: 2307


Fix the dev extension path: browser-extension is not copied to the dist/ directory and the relative path is incorrect.

The dev path path.join(__dirname, "../src/resources/browser-extension") resolves to dist/src/resources/browser-extension (one level too shallow), and browser-extension is not listed in RESOURCES_TO_COPY in vite/helpers.ts, so it won't be copied to dist/ at all during build. Either add browser-extension to the copy list (alongside sounds and tray) or change the path to ../../src/resources/browser-extension if relying on the source directory directly. The packaged path using process.resourcesPath works correctly since electron-builder.ts includes all of src/resources/ via the files configuration.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/index.ts` around lines 281 - 292, The dev extension
path for loading the browser extension is incorrect and the resource isn't being
copied into dist; update the dev branch for extensionPath used in the
app.isPackaged check so it points to the correct source location (e.g., use
../../src/resources/browser-extension if you want to load from source) OR add
"browser-extension" to the RESOURCES_TO_COPY array in vite/helpers.ts so the
extension is copied into dist and the existing path works; adjust the code
around extensionPath and the
session.fromPartition("persist:superset").loadExtension call accordingly and
ensure electron-builder.ts continues to include src/resources/ for packaged
builds.


// Must happen before renderer restore runs
await reconcileDaemonSessions();

Expand Down
Loading
Loading