···94949595import {
9696 getExtensionHostWindow,
9797- isConsolidatedExtension,
9897 getRunningExtensions,
9998 getAllRegisteredExtensions,
10099 reloadExtension,
···13041303 ipcMain.handle('extension-window-devtools', async (ev, data) => {
13051304 try {
13061305 const extId = data.id;
13071307-13081308- // Consolidated extensions run in the extension host window
13091309- if (isConsolidatedExtension(extId)) {
13101310- const hostWin = getExtensionHostWindow();
13111311- if (!hostWin || hostWin.isDestroyed()) {
13121312- return { success: false, error: 'Extension host is not running' };
13131313- }
13141314- hostWin.webContents.openDevTools({ mode: 'detach' });
13151315- return { success: true, data: { id: extId, isConsolidated: true } };
13161316- }
1317130613181307 // v2 tile or unknown — extensionWindows map removed in Phase 3.2
13191308 return { success: false, error: `Extension ${extId} is not running as a legacy window` };
+26-63
backend/electron/main.ts
···87878888// Track which lazy extensions have been loaded
8989const lazyExtensionLoaded = new Set<string>();
9090-// Track which consolidated extensions have actually loaded (eager or lazy)
9191-const loadedConsolidatedExtensions = new Set<string>();
9290// Pending lazy load callbacks (extId -> resolve[])
9391const lazyLoadCallbacks = new Map<string, Array<() => void>>();
9492···740738 if (lazyExtensionLoaded.has(extId)) return; // Already resolved normally
741739 console.error(`[ext:lazy] Timeout waiting for ext:ready from ${extId} (${LAZY_LOAD_TIMEOUT_MS}ms)`);
742740 lazyExtensionLoaded.add(extId);
743743- loadedConsolidatedExtensions.add(extId);
744741 const callbacks = lazyLoadCallbacks.get(extId) || [];
745742 lazyLoadCallbacks.delete(extId);
746743 for (const cb of callbacks) {
···785782786783function handleLazyExtensionReady(extId: string, registeredTopics?: string[]): void {
787784 lazyExtensionLoaded.add(extId);
788788- loadedConsolidatedExtensions.add(extId);
789785790786 const callbacks = lazyLoadCallbacks.get(extId) || [];
791787 lazyLoadCallbacks.delete(extId);
···11311127 const match = url.match(/^peek:\/\/([^/]+)\/background\.html/);
11321128 if (match) {
11331129 const extId = match[1];
11341134- if (!loadedConsolidatedExtensions.has(extId)) {
11351135- loadedConsolidatedExtensions.add(extId);
11361136- DEBUG && console.log(`[ext:host] Frame loaded: ${extId} (${loadedConsolidatedExtensions.size} total)`);
11371137-11381138- // Note: lazy extension ready callbacks are resolved via ext:ready pubsub
11391139- // (see subscribe('ext:ready', ...) below), not here — did-frame-finish-load
11401140- // fires before the extension's JS modules have executed and registered handlers.
11411141- }
11301130+ DEBUG && console.log(`[ext:host] Frame loaded: ${extId}`);
11421131 }
11431132 }
11441133 } catch (err) {
···11911180 // grant) — no longer loaded as an iframe in the consolidated extension host.
11921181 await createExtensionHostWindow();
11931182 await initCmd({ tilePreloadPath });
11941194- // Keep cmd visible to getRunningExtensions(): the set is documented as
11951195- // "consolidated iframe extensions" but is the logical "core renderer is up"
11961196- // tracker used by the Features pane. Adding cmd here preserves that
11971197- // surfacing now that the renderer is a standalone BrowserWindow.
11981198- loadedConsolidatedExtensions.add('cmd');
1199118312001184 // Load HUD right after cmd — it's core app infrastructure like cmd, and
12011185 // registers the `hud` command + CmdOrCtrl+H shortcut via the portable api.
12021186 // HUD now runs as a standalone tile renderer (tile-preload + trustedBuiltin
12031187 // grant) — no longer loaded as an iframe in the consolidated extension host.
12041188 await initHud({ tilePreloadPath });
12051205- // Keep hud visible to getRunningExtensions(): the set is documented as
12061206- // "consolidated iframe extensions" but is the logical "core renderer is up"
12071207- // tracker used by the Features pane. Adding hud here preserves that
12081208- // surfacing now that the renderer is a standalone BrowserWindow.
12091209- loadedConsolidatedExtensions.add('hud');
1210118912111190 // Load page background right after hud — registers the `open` and `modal`
12121191 // commands. Page is core infrastructure (the canvas architecture in
···12141193 // page now runs as a standalone tile renderer (tile-preload + trustedBuiltin
12151194 // grant) — no longer loaded as an iframe in the consolidated extension host.
12161195 await initPage({ tilePreloadPath });
12171217- // Keep page visible to getRunningExtensions(): the set is documented as
12181218- // "consolidated iframe extensions" but is the logical "core renderer is up"
12191219- // tracker used by the Features pane. Adding page here preserves that
12201220- // surfacing now that the renderer is a standalone BrowserWindow.
12211221- loadedConsolidatedExtensions.add('page');
1222119612231197 // Launch the privileged test-fixture renderer when running under
12241198 // Playwright (E2E_TEST=true). This is the v2 replacement for the v1
···12901264 return 0;
12911265}
1292126612671267+// Core renderers that are always considered "running" once the app has started.
12681268+// These are not v2 tiles (no tile manifest) and not v1 iframe extensions, but
12691269+// the Features pane should always show them as running.
12701270+const CORE_RENDERER_IDS = ['cmd', 'hud', 'page'];
12711271+12931272/**
12941273 * Get running extensions info
12951295- * Includes both consolidated (iframe) and separate window extensions
12741274+ * Includes core renderers, declarative extensions, and v2 tiles
12961275 */
12971276export function getRunningExtensions(): Array<{ id: string; manifest: unknown; status: string }> {
12981277 const running: Array<{ id: string; manifest: unknown; status: string }> = [];
1299127813001300- // Add consolidated extensions that have actually loaded (in extension host iframes).
13011301- // Iterates loadedConsolidatedExtensions (populated by did-frame-finish-load), which
13021302- // includes both CONSOLIDATED_EXTENSION_IDS features AND core pseudo-extensions like
13031303- // cmd and hud that are loaded by core glue (cmd-glue.ts, hud-glue.ts) rather than
13041304- // by the feature system.
13051305- if (extensionHostWindow && !extensionHostWindow.isDestroyed()) {
13061306- for (const extId of loadedConsolidatedExtensions) {
13071307- if (!isBuiltinExtensionEnabled(extId)) continue;
13081308- const extPath = getExtensionPath(extId);
13091309- const manifest = extPath ? loadExtensionManifest(extPath) : null;
13101310- running.push({
13111311- id: extId,
13121312- manifest: manifest || { id: extId },
13131313- status: 'running'
13141314- });
13151315- }
12791279+ // Add core renderers (cmd, hud, page) — always running once app is up
12801280+ for (const extId of CORE_RENDERER_IDS) {
12811281+ if (!isBuiltinExtensionEnabled(extId)) continue;
12821282+ const extPath = getExtensionPath(extId);
12831283+ const manifest = extPath ? loadExtensionManifest(extPath) : null;
12841284+ running.push({
12851285+ id: extId,
12861286+ manifest: manifest || { id: extId },
12871287+ status: 'running'
12881288+ });
13161289 }
1317129013181291 // Add declarative-only extensions (no background page, but still active)
···13301303 }
1331130413321305 // Add v2 tiles launched by the tile-launcher as separate BrowserWindows.
13331333- // These aren't in loadedConsolidatedExtensions (that's for iframes in the host).
13341306 for (const tileId of getLoadedTileIds()) {
13351307 if (running.some(r => r.id === tileId)) continue;
13361308 if (!isBuiltinExtensionEnabled(tileId)) continue;
···13531325 const registeredIds = getRegisteredExtensionIds();
13541326 const result: Array<{ id: string; manifest: unknown; status: string }> = [];
1355132713561356- // Build a set of running extension IDs for status info
13571357- const runningSet = new Set<string>();
13581358- if (extensionHostWindow && !extensionHostWindow.isDestroyed()) {
13591359- for (const extId of CONSOLIDATED_EXTENSION_IDS) {
13601360- if (loadedConsolidatedExtensions.has(extId)) {
13611361- runningSet.add(extId);
13621362- }
13631363- }
13641364- }
13281328+ // Build a set of running extension IDs for status info.
13291329+ // Sources: core renderers (always up), declarative extensions, and loaded v2 tiles.
13301330+ const runningSet = new Set<string>(CORE_RENDERER_IDS);
13651331 for (const extId of declarativeExtensions) {
13661332 runningSet.add(extId);
13331333+ }
13341334+ for (const tileId of getLoadedTileIds()) {
13351335+ runningSet.add(tileId);
13671336 }
1368133713691338 for (const extId of registeredIds) {
···13871356 return extensionHostWindow;
13881357}
1389135813901390-/**
13911391- * Check if an extension runs in consolidated mode (iframe in host)
13921392- */
13931393-export function isConsolidatedExtension(extId: string): boolean {
13941394- return CONSOLIDATED_EXTENSION_IDS.includes(extId);
13951395-}
1396135913971360/**
13981361 * Check if an extension is a dev extension (loaded via CLI)
···14731436export async function reloadExtension(extId: string): Promise<object | null> {
14741437 DEBUG && console.log(`[ext:reload] Reloading extension: ${extId}`);
1475143814761476- // Check if it's a consolidated extension (not supported for reload)
14771477- if (isConsolidatedExtension(extId)) {
14781478- console.error(`[ext:reload] Cannot reload consolidated extension: ${extId} (reload the app instead)`);
14391439+ // Check if it's a core renderer (cmd/hud/page) — not reloadable without a full app restart
14401440+ if (CORE_RENDERER_IDS.includes(extId)) {
14411441+ console.error(`[ext:reload] Cannot reload core renderer: ${extId} (reload the app instead)`);
14791442 return null;
14801443 }
14811444