@wrongstack/desktop 0.286.0 → 0.287.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main/main.js CHANGED
@@ -1,12 +1,13 @@
1
1
  // src/main/main.ts
2
- import * as path3 from "node:path";
2
+ import * as path4 from "node:path";
3
3
  import * as fs3 from "node:fs/promises";
4
4
  import { wstackGlobalRoot as wstackGlobalRoot3 } from "@wrongstack/core/utils";
5
5
  import {
6
6
  app,
7
7
  BaseWindow,
8
8
  dialog,
9
- ipcMain,
9
+ ipcMain as ipcMain2,
10
+ nativeImage,
10
11
  screen,
11
12
  shell,
12
13
  WebContentsView
@@ -874,8 +875,8 @@ var DesktopRuntimeManager = class extends EventEmitter2 {
874
875
  }
875
876
  async openProject(projectRoot, options = {}) {
876
877
  const resolved = path2.resolve(projectRoot);
877
- const stat2 = await fs2.stat(resolved).catch(() => null);
878
- if (!stat2?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
878
+ const stat3 = await fs2.stat(resolved).catch(() => null);
879
+ if (!stat3?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
879
880
  const kind = options.kind ?? "project";
880
881
  const touchRecent = options.touchRecent ?? kind === "project";
881
882
  const forceNew = options.forceNew === true;
@@ -1049,8 +1050,8 @@ var DesktopRuntimeManager = class extends EventEmitter2 {
1049
1050
  }
1050
1051
  async registerProject(projectRoot) {
1051
1052
  const resolved = path2.resolve(projectRoot);
1052
- const stat2 = await fs2.stat(resolved).catch(() => null);
1053
- if (!stat2?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
1053
+ const stat3 = await fs2.stat(resolved).catch(() => null);
1054
+ if (!stat3?.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
1054
1055
  const now = (/* @__PURE__ */ new Date()).toISOString();
1055
1056
  const entry = {
1056
1057
  name: path2.basename(resolved) || resolved,
@@ -1389,16 +1390,16 @@ function resolveWebUiEntry() {
1389
1390
  if (existsSync(candidate)) return candidate;
1390
1391
  } catch {
1391
1392
  }
1392
- const serverIndex = require2.resolve("@wrongstack/webui/server");
1393
- return path2.join(path2.dirname(serverIndex), "entry.js");
1393
+ const serverIndex = require2.resolve("@wrongstack/webui-server");
1394
+ return path2.join(path2.dirname(serverIndex), "server", "entry.js");
1394
1395
  }
1395
1396
  function resolveWebUiDistDir() {
1396
1397
  if (process.env["WRONGSTACK_WEBUI_DIST"]) {
1397
1398
  return path2.resolve(process.env["WRONGSTACK_WEBUI_DIST"]);
1398
1399
  }
1399
1400
  const require2 = createRequire(import.meta.url);
1400
- const serverIndex = require2.resolve("@wrongstack/webui/server");
1401
- const candidate = path2.resolve(path2.dirname(serverIndex), "..");
1401
+ const serverEntry = require2.resolve("@wrongstack/webui");
1402
+ const candidate = path2.dirname(serverEntry);
1402
1403
  if (existsSync(candidate)) return candidate;
1403
1404
  throw new Error(
1404
1405
  `WebUI frontend assets not found at ${candidate}. Build @wrongstack/webui or set WRONGSTACK_WEBUI_DIST.`
@@ -1663,10 +1664,17 @@ function isRecord(value) {
1663
1664
  }
1664
1665
 
1665
1666
  // src/main/state/constants.ts
1667
+ var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
1666
1668
  var SIDEBAR_WIDTH_WIDE = 292;
1667
1669
  var SIDEBAR_WIDTH_MEDIUM = 276;
1668
1670
  var SIDEBAR_WIDTH_NARROW = 252;
1669
1671
  var SIDEBAR_WIDTH_COLLAPSED = 56;
1672
+ var MIN_WINDOW_WIDTH2 = 760;
1673
+ var MIN_WINDOW_HEIGHT2 = 520;
1674
+ var MAX_PENDING_WEBUI_COMMANDS = 50;
1675
+ var MAX_PENDING_FLUSH_ATTEMPTS = 80;
1676
+ var WEBUI_COMMAND_FALLBACK_MS = 350;
1677
+ var WEBUI_COMMAND_ACK_TIMEOUT_MS = 2e3;
1670
1678
 
1671
1679
  // src/main/layout/sidebar.ts
1672
1680
  function getSidebarWidth(windowWidth, collapsed) {
@@ -1680,7 +1688,7 @@ function getSidebarWidth(windowWidth, collapsed) {
1680
1688
  import { Menu } from "electron";
1681
1689
 
1682
1690
  // src/main/menu/projects-menu.ts
1683
- var posix = (await import("node:path")).posix;
1691
+ import path3 from "node:path";
1684
1692
  function buildProjectsMenu(runtimes, actions, t) {
1685
1693
  const projectGroups = groupProjectRuntimesForMenu(runtimes);
1686
1694
  const menu = [
@@ -1688,9 +1696,11 @@ function buildProjectsMenu(runtimes, actions, t) {
1688
1696
  label: t("openProjectEllipsis"),
1689
1697
  accelerator: "CmdOrCtrl+O",
1690
1698
  click: () => actions.newSession("")
1691
- // Will open dialog
1692
1699
  },
1693
- { label: t("registerProjectEllipsis"), click: () => actions.newSession("") },
1700
+ {
1701
+ label: t("registerProjectEllipsis"),
1702
+ click: () => actions.registerProject?.()
1703
+ },
1694
1704
  { type: "separator" }
1695
1705
  ];
1696
1706
  if (projectGroups.length === 0) {
@@ -1812,7 +1822,7 @@ function groupProjectRuntimesForMenu(runtimes) {
1812
1822
  }
1813
1823
  groups.set(key, {
1814
1824
  key,
1815
- name: posix.basename(runtime.root) || runtime.name,
1825
+ name: path3.basename(runtime.root) || runtime.name,
1816
1826
  root: runtime.root,
1817
1827
  sessions: [runtime]
1818
1828
  });
@@ -1983,6 +1993,7 @@ function configureApplicationMenu(ctx) {
1983
1993
  const actions = {
1984
1994
  activate: (runtimeId) => void ctx.activateRuntime(runtimeId),
1985
1995
  activateAndNavigate,
1996
+ registerProject: () => void ctx.registerProject(),
1986
1997
  newSession: (runtimeId) => {
1987
1998
  if (runtimeId) void ctx.openProjectSession(runtimeId);
1988
1999
  else void ctx.openProject();
@@ -2010,16 +2021,295 @@ function configureApplicationMenu(ctx) {
2010
2021
  Menu.setApplicationMenu(Menu.buildFromTemplate(template));
2011
2022
  }
2012
2023
 
2024
+ // src/main/ipc-handlers/index.ts
2025
+ import { ipcMain } from "electron";
2026
+
2027
+ // src/main/validation/index.ts
2028
+ function validate(schema, data) {
2029
+ try {
2030
+ const result = schema.safeParse(data);
2031
+ if (result.success) {
2032
+ return { success: true, data: result.data };
2033
+ }
2034
+ return { success: false, error: formatZodError(result.error) };
2035
+ } catch (err) {
2036
+ return { success: false, error: String(err) };
2037
+ }
2038
+ }
2039
+ function validateOrDefault(schema, data, defaultValue) {
2040
+ const result = validate(schema, data);
2041
+ return result.success ? result.data : defaultValue;
2042
+ }
2043
+ function formatZodError(error) {
2044
+ return error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ");
2045
+ }
2046
+ function createValidationLogger(prefix) {
2047
+ const loggedErrors = /* @__PURE__ */ new Set();
2048
+ const maxErrors = 100;
2049
+ return {
2050
+ log(error) {
2051
+ if (loggedErrors.size >= maxErrors) return;
2052
+ const key = `${prefix}:${error}`;
2053
+ if (!loggedErrors.has(key)) {
2054
+ loggedErrors.add(key);
2055
+ console.warn(
2056
+ JSON.stringify({
2057
+ level: "warn",
2058
+ event: "desktop.validation_error",
2059
+ prefix,
2060
+ message: error,
2061
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2062
+ })
2063
+ );
2064
+ }
2065
+ },
2066
+ reset() {
2067
+ loggedErrors.clear();
2068
+ }
2069
+ };
2070
+ }
2071
+
2072
+ // src/main/validation/schemas.ts
2073
+ import { z } from "zod";
2074
+ var RUNTIME_ID_PATTERN = /^[a-zA-Z0-9._:-]{3,120}$/;
2075
+ var runtimeIdSchema = z.string().regex(RUNTIME_ID_PATTERN, "Invalid runtime ID format");
2076
+ var pathSchema = z.string().min(1).max(1e4);
2077
+ var nonEmptyStringSchema = z.string().min(1);
2078
+ var booleanSchema = z.boolean();
2079
+ var numberSchema = z.number().int().finite();
2080
+ var openProjectSchema = z.object({
2081
+ requestedRoot: pathSchema.optional()
2082
+ });
2083
+ var registerProjectSchema = z.object({
2084
+ requestedRoot: pathSchema.optional()
2085
+ });
2086
+ var unregisterProjectSchema = z.object({
2087
+ root: pathSchema
2088
+ });
2089
+ var openProjectSessionSchema = z.object({
2090
+ runtimeId: runtimeIdSchema.optional()
2091
+ });
2092
+ var activateRuntimeSchema = z.object({
2093
+ id: runtimeIdSchema
2094
+ });
2095
+ var closeRuntimeSchema = z.object({
2096
+ id: runtimeIdSchema
2097
+ });
2098
+ var sendMessageSchema = z.object({
2099
+ id: runtimeIdSchema,
2100
+ content: z.string()
2101
+ });
2102
+ var abortRuntimeSchema = z.object({
2103
+ id: runtimeIdSchema
2104
+ });
2105
+ var openRuntimeInBrowserSchema = z.object({
2106
+ id: runtimeIdSchema
2107
+ });
2108
+ var revealRuntimeRootSchema = z.object({
2109
+ id: runtimeIdSchema
2110
+ });
2111
+ var setShellSidebarCollapsedSchema = z.object({
2112
+ collapsed: booleanSchema
2113
+ });
2114
+ var navigateWebuiSchema = z.object({
2115
+ command: z.unknown()
2116
+ });
2117
+ var getConversationSchema = z.object({
2118
+ runtimeId: runtimeIdSchema
2119
+ });
2120
+ var webuiReadyChangedSchema = z.object({
2121
+ ready: booleanSchema
2122
+ });
2123
+ var webuiPrefsChangedSchema = z.object({
2124
+ prefs: z.record(z.string(), z.unknown())
2125
+ });
2126
+ var webuiCommandAckSchema = z.object({
2127
+ requestId: z.string(),
2128
+ handled: z.boolean(),
2129
+ message: z.string().optional()
2130
+ });
2131
+ var setLocaleSchema = z.object({
2132
+ locale: z.string().min(2).max(10)
2133
+ });
2134
+
2135
+ // src/main/ipc-handlers/index.ts
2136
+ var validationLogger = createValidationLogger("IPC");
2137
+ function registerIpcHandlers(ctx) {
2138
+ ipcMain.handle(IPC.getState, () => ctx.getRuntimeManager().snapshot());
2139
+ ipcMain.handle(IPC.getConversation, (_event, runtimeId) => {
2140
+ const result = validate(runtimeIdSchema, runtimeId);
2141
+ if (!result.success) {
2142
+ validationLogger.log(`getConversation: ${result.error}`);
2143
+ return ctx.getAgentBridge().snapshot("");
2144
+ }
2145
+ return ctx.getAgentBridge().snapshot(result.data);
2146
+ });
2147
+ ipcMain.handle(IPC.getWebuiStatus, () => ctx.getWebuiStatus());
2148
+ ipcMain.handle(IPC.navigateWebui, async (_event, command) => {
2149
+ return ctx.dispatchWebuiCommand(command);
2150
+ });
2151
+ ipcMain.handle(IPC.reloadWebui, async () => ctx.reloadActiveWebuiView());
2152
+ ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
2153
+ const result = validateOrDefault(
2154
+ pathSchema.transform(() => collapsed === true),
2155
+ collapsed,
2156
+ true
2157
+ );
2158
+ ctx.setShellSidebarCollapsed(result);
2159
+ return true;
2160
+ });
2161
+ ipcMain.handle(IPC.openSettings, async () => ctx.openSettings());
2162
+ ipcMain.handle(IPC.openProjectSession, async (_event, runtimeId) => {
2163
+ const validated = validateOptional(runtimeIdSchema, runtimeId);
2164
+ return ctx.openProjectSession(validated);
2165
+ });
2166
+ ipcMain.handle(IPC.openProject, async (_event, requestedRoot) => {
2167
+ const validated = validateOptional(pathSchema, requestedRoot);
2168
+ return ctx.openProject(validated);
2169
+ });
2170
+ ipcMain.handle(IPC.registerProject, async (_event, requestedRoot) => {
2171
+ const validated = validateOptional(pathSchema, requestedRoot);
2172
+ return ctx.registerProject(validated);
2173
+ });
2174
+ ipcMain.handle(IPC.unregisterProject, async (_event, root) => {
2175
+ const result = validate(pathSchema, root);
2176
+ if (!result.success) {
2177
+ validationLogger.log(`unregisterProject: ${result.error}`);
2178
+ return ctx.getRuntimeManager().snapshot();
2179
+ }
2180
+ return ctx.unregisterProject(result.data);
2181
+ });
2182
+ ipcMain.handle(IPC.activateRuntime, async (_event, id) => {
2183
+ const result = validate(runtimeIdSchema, id);
2184
+ if (!result.success) {
2185
+ validationLogger.log(`activateRuntime: ${result.error}`);
2186
+ return ctx.getRuntimeManager().snapshot();
2187
+ }
2188
+ return ctx.activateRuntime(result.data);
2189
+ });
2190
+ ipcMain.handle(IPC.closeRuntime, async (_event, id) => {
2191
+ const result = validate(runtimeIdSchema, id);
2192
+ if (!result.success) {
2193
+ validationLogger.log(`closeRuntime: ${result.error}`);
2194
+ return ctx.getRuntimeManager().snapshot();
2195
+ }
2196
+ return ctx.closeRuntime(result.data);
2197
+ });
2198
+ ipcMain.handle(IPC.sendMessage, async (_event, id, content) => {
2199
+ const idResult = validate(runtimeIdSchema, id);
2200
+ if (!idResult.success) {
2201
+ validationLogger.log(`sendMessage (id): ${idResult.error}`);
2202
+ return ctx.sendMessage("", "", "");
2203
+ }
2204
+ const contentResult = validate(pathSchema.transform(() => String(content ?? "")), content);
2205
+ const safeContent = contentResult.success ? contentResult.data : String(content ?? "");
2206
+ return ctx.sendMessage(
2207
+ idResult.data,
2208
+ ctx.getRuntimeManager().getRuntimeWsUrlWithToken(idResult.data) ?? "",
2209
+ safeContent
2210
+ );
2211
+ });
2212
+ ipcMain.handle(IPC.abortRuntime, async (_event, id) => {
2213
+ const result = validate(runtimeIdSchema, id);
2214
+ if (!result.success) {
2215
+ validationLogger.log(`abortRuntime: ${result.error}`);
2216
+ return ctx.abortRuntime("", "");
2217
+ }
2218
+ return ctx.abortRuntime(
2219
+ result.data,
2220
+ ctx.getRuntimeManager().getRuntimeWsUrlWithToken(result.data) ?? ""
2221
+ );
2222
+ });
2223
+ ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
2224
+ const result = validate(runtimeIdSchema, id);
2225
+ if (!result.success) {
2226
+ validationLogger.log(`openRuntimeInBrowser: ${result.error}`);
2227
+ return;
2228
+ }
2229
+ const url = ctx.getRuntimeManager().getRuntimeUrlWithToken(result.data);
2230
+ if (url) ctx.openExternal(url);
2231
+ });
2232
+ ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
2233
+ const result = validate(runtimeIdSchema, id);
2234
+ if (!result.success) {
2235
+ validationLogger.log(`revealRuntimeRoot: ${result.error}`);
2236
+ return;
2237
+ }
2238
+ const runtime = ctx.getRuntimeManager().getRuntime(result.data);
2239
+ if (runtime) ctx.revealInExplorer(runtime.root);
2240
+ });
2241
+ ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
2242
+ const entry = ctx.findWebuiEntryBySenderId(event.sender.id);
2243
+ if (!entry) return;
2244
+ const isReady = ready === true;
2245
+ entry.bridgeReady = isReady;
2246
+ if (entry.bridgeReady) {
2247
+ ctx.setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
2248
+ ctx.schedulePendingWebuiFlush(entry);
2249
+ } else if (entry.status.status === "ready") {
2250
+ ctx.setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
2251
+ }
2252
+ });
2253
+ ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
2254
+ const entry = ctx.findWebuiEntryBySenderId(event.sender.id);
2255
+ if (!entry) return;
2256
+ const sanitized = sanitizeWebuiPrefs(prefs);
2257
+ if (Object.keys(sanitized).length === 0) return;
2258
+ ctx.setEntryWebuiStatus(entry, {
2259
+ ...entry.status,
2260
+ prefs: { ...entry.status.prefs ?? {}, ...sanitized }
2261
+ });
2262
+ });
2263
+ ipcMain.on(IPC.webuiCommandAck, (event, requestId, handled, _message) => {
2264
+ const entry = ctx.findWebuiEntryBySenderId(event.sender.id);
2265
+ if (!entry) return;
2266
+ const result = validate(webuiCommandAckSchema, {
2267
+ requestId,
2268
+ handled,
2269
+ message: _message
2270
+ });
2271
+ if (!result.success) {
2272
+ validationLogger.log(`webuiCommandAck: ${result.error}`);
2273
+ return;
2274
+ }
2275
+ const pending = ctx.getPendingWebuiCommandAcks().get(result.data.requestId);
2276
+ if (!pending || pending.runtimeId !== entry.runtimeId) return;
2277
+ ctx.settlePendingWebuiCommandAck(result.data.requestId, result.data.handled);
2278
+ });
2279
+ ipcMain.on(IPC.setLocale, (_event, locale) => {
2280
+ const result = validate(setLocaleSchema, { locale });
2281
+ if (!result.success) {
2282
+ validationLogger.log(`setLocale: ${result.error}`);
2283
+ return;
2284
+ }
2285
+ ctx.getI18n().setMainLocale(result.data.locale);
2286
+ ctx.configureApplicationMenu();
2287
+ ctx.broadcastLocaleToEmbeddedWebuis(result.data.locale);
2288
+ void ctx.getConfigIo().writeUiLocale(result.data.locale);
2289
+ });
2290
+ }
2291
+ function validateOptional(schema, value) {
2292
+ if (value === void 0 || value === null) return void 0;
2293
+ const result = schema.safeParse(value);
2294
+ return result.success ? result.data : void 0;
2295
+ }
2296
+ function sanitizeWebuiPrefs(prefs) {
2297
+ const next = {};
2298
+ if (!isRecord2(prefs)) return next;
2299
+ if (typeof prefs["yolo"] === "boolean") next.yolo = prefs["yolo"];
2300
+ if (typeof prefs["nextPrediction"] === "boolean") next.nextPrediction = prefs["nextPrediction"];
2301
+ if (typeof prefs["contextAutoCompact"] === "boolean") {
2302
+ next.contextAutoCompact = prefs["contextAutoCompact"];
2303
+ }
2304
+ return next;
2305
+ }
2306
+ function isRecord2(value) {
2307
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2308
+ }
2309
+
2013
2310
  // src/main/main.ts
2014
- var OPEN_EXTERNAL_ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:"]);
2015
- var MIN_WINDOW_WIDTH2 = 760;
2016
- var MIN_WINDOW_HEIGHT2 = 520;
2017
- var MAX_PENDING_WEBUI_COMMANDS = 50;
2018
- var MAX_PENDING_FLUSH_ATTEMPTS = 80;
2019
- var WEBUI_COMMAND_FALLBACK_MS = 350;
2020
- var WEBUI_COMMAND_ACK_TIMEOUT_MS = 2e3;
2021
2311
  app.setAppUserModelId("com.wrongstack.desktop");
2022
- app.setPath("userData", path3.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
2312
+ app.setPath("userData", path4.join(wstackGlobalRoot3(), "desktop", "electron-profile"));
2023
2313
  var manager = new DesktopRuntimeManager();
2024
2314
  var bridge = new DesktopAgentBridge();
2025
2315
  var mainWindow = null;
@@ -2051,14 +2341,6 @@ function sameOrigin(candidate, base) {
2051
2341
  return false;
2052
2342
  }
2053
2343
  }
2054
- function isRecord2(value) {
2055
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2056
- }
2057
- function runtimeWsUrlOrThrow(runtimeId) {
2058
- const wsUrl = manager.getRuntimeWsUrlWithToken(runtimeId);
2059
- if (!wsUrl) throw new Error(`Runtime not found: ${runtimeId}`);
2060
- return wsUrl;
2061
- }
2062
2344
  function setShellSidebarCollapsed(collapsed) {
2063
2345
  shellSidebarCollapsed = collapsed;
2064
2346
  layoutWebuiViews();
@@ -2610,98 +2892,44 @@ function createMenuContext() {
2610
2892
  function configureApplicationMenu2() {
2611
2893
  configureApplicationMenu(createMenuContext());
2612
2894
  }
2613
- function registerIpcHandlers() {
2614
- ipcMain.handle(IPC.getState, () => manager.snapshot());
2615
- ipcMain.handle(
2616
- IPC.getConversation,
2617
- (_event, runtimeId) => bridge.snapshot(runtimeId)
2618
- );
2619
- ipcMain.handle(IPC.getWebuiStatus, () => webuiStatus);
2620
- ipcMain.handle(
2621
- IPC.navigateWebui,
2622
- async (_event, command) => dispatchWebuiCommand(command)
2623
- );
2624
- ipcMain.handle(IPC.reloadWebui, async () => reloadActiveWebuiView());
2625
- ipcMain.handle(IPC.setShellSidebarCollapsed, (_event, collapsed) => {
2626
- setShellSidebarCollapsed(collapsed === true);
2627
- return true;
2628
- });
2629
- ipcMain.handle(IPC.openSettings, async () => openSettings());
2630
- ipcMain.handle(
2631
- IPC.openProjectSession,
2632
- async (_event, runtimeId) => openProjectSession(runtimeId)
2633
- );
2634
- ipcMain.handle(
2635
- IPC.openProject,
2636
- async (_event, requestedRoot) => openProject(requestedRoot)
2637
- );
2638
- ipcMain.handle(
2639
- IPC.registerProject,
2640
- async (_event, requestedRoot) => registerProject(requestedRoot)
2641
- );
2642
- ipcMain.handle(IPC.unregisterProject, async (_event, root) => unregisterProject(root));
2643
- ipcMain.handle(IPC.activateRuntime, async (_event, id) => activateRuntime(id));
2644
- ipcMain.handle(IPC.closeRuntime, async (_event, id) => closeRuntime(id));
2645
- ipcMain.handle(
2646
- IPC.sendMessage,
2647
- async (_event, id, content) => bridge.sendMessage(id, runtimeWsUrlOrThrow(id), content)
2648
- );
2649
- ipcMain.handle(
2650
- IPC.abortRuntime,
2651
- async (_event, id) => bridge.abort(id, runtimeWsUrlOrThrow(id))
2652
- );
2653
- ipcMain.handle(IPC.openRuntimeInBrowser, async (_event, id) => {
2654
- const url = manager.getRuntimeUrlWithToken(id);
2655
- if (url) safeOpenExternal(url);
2656
- });
2657
- ipcMain.handle(IPC.revealRuntimeRoot, async (_event, id) => {
2658
- const runtime = manager.getRuntime(id);
2659
- if (runtime) void shell.openPath(runtime.root);
2660
- });
2661
- ipcMain.on(IPC.webuiReadyChanged, (event, ready) => {
2662
- const entry = findWebuiEntryBySenderId(event.sender.id);
2663
- if (!entry) return;
2664
- entry.bridgeReady = ready === true;
2665
- if (entry.bridgeReady) {
2666
- setEntryWebuiStatus(entry, { ...entry.status, status: "ready" });
2667
- schedulePendingWebuiFlush(entry);
2668
- } else if (entry.status.status === "ready") {
2669
- setEntryWebuiStatus(entry, { ...entry.status, status: "loading" });
2670
- }
2671
- });
2672
- ipcMain.on(IPC.webuiPrefsChanged, (event, prefs) => {
2673
- const entry = findWebuiEntryBySenderId(event.sender.id);
2674
- if (!entry) return;
2675
- const next = {};
2676
- if (isRecord2(prefs) && typeof prefs["yolo"] === "boolean") next.yolo = prefs["yolo"];
2677
- if (isRecord2(prefs) && typeof prefs["nextPrediction"] === "boolean") {
2678
- next.nextPrediction = prefs["nextPrediction"];
2679
- }
2680
- if (isRecord2(prefs) && typeof prefs["contextAutoCompact"] === "boolean") {
2681
- next.contextAutoCompact = prefs["contextAutoCompact"];
2682
- }
2683
- if (Object.keys(next).length === 0) return;
2684
- setEntryWebuiStatus(entry, {
2685
- ...entry.status,
2686
- prefs: { ...entry.status.prefs ?? {}, ...next }
2687
- });
2688
- });
2689
- ipcMain.on(
2690
- IPC.webuiCommandAck,
2691
- (event, requestId, handled, _message) => {
2692
- const entry = findWebuiEntryBySenderId(event.sender.id);
2693
- if (!entry || typeof requestId !== "string") return;
2694
- const pending = pendingWebuiCommandAcks.get(requestId);
2695
- if (!pending || pending.runtimeId !== entry.runtimeId) return;
2696
- settlePendingWebuiCommandAck(requestId, handled === true);
2697
- }
2698
- );
2699
- ipcMain.on(IPC.setLocale, (_event, locale) => {
2700
- setMainLocale(locale);
2701
- configureApplicationMenu2();
2702
- broadcastLocaleToEmbeddedWebuis(locale);
2703
- void writeUiLocale(locale);
2704
- });
2895
+ function buildIpcHandlerContext() {
2896
+ return {
2897
+ getMainWindow: () => mainWindow,
2898
+ getShellView: () => shellView,
2899
+ getWebuiViews: () => webuiViews,
2900
+ getWebuiStatus: () => webuiStatus,
2901
+ getRuntimeManager: () => manager,
2902
+ getAgentBridge: () => bridge,
2903
+ getI18n: () => ({ getMainLocale, setMainLocale, tMain }),
2904
+ getConfigIo: () => ({ desktopConfigPaths, readUiLocale, writeUiLocale }),
2905
+ getShellSidebarCollapsed: () => shellSidebarCollapsed,
2906
+ setShellSidebarCollapsed: (collapsed) => setShellSidebarCollapsed(collapsed),
2907
+ broadcastState: () => broadcastState(),
2908
+ publishWebuiStatus: (next) => publishWebuiStatus(next),
2909
+ syncActiveWebuiView: () => syncActiveWebuiView(),
2910
+ configureApplicationMenu: () => configureApplicationMenu2(),
2911
+ broadcastLocaleToEmbeddedWebuis: (locale) => broadcastLocaleToEmbeddedWebuis(locale),
2912
+ dispatchWebuiCommand: (command) => dispatchWebuiCommand(command),
2913
+ reloadActiveWebuiView: () => reloadActiveWebuiView(),
2914
+ openProject: (root) => openProject(root),
2915
+ registerProject: (root) => registerProject(root),
2916
+ unregisterProject: (root) => unregisterProject(root),
2917
+ openProjectSession: (id) => openProjectSession(id),
2918
+ activateRuntime: (id) => activateRuntime(id),
2919
+ closeRuntime: (id) => closeRuntime(id),
2920
+ openSettings: () => openSettings(),
2921
+ sendMessage: (id, wsUrl, content) => bridge.sendMessage(id, wsUrl, content),
2922
+ abortRuntime: (id, wsUrl) => bridge.abort(id, wsUrl),
2923
+ openExternal: (url) => safeOpenExternal(url),
2924
+ revealInExplorer: (root) => {
2925
+ void shell.openPath(root);
2926
+ },
2927
+ findWebuiEntryBySenderId: (senderId) => findWebuiEntryBySenderId(senderId),
2928
+ getPendingWebuiCommandAcks: () => pendingWebuiCommandAcks,
2929
+ settlePendingWebuiCommandAck: (requestId, handled) => settlePendingWebuiCommandAck(requestId, handled),
2930
+ setEntryWebuiStatus: (entry, next) => setEntryWebuiStatus(entry, next),
2931
+ schedulePendingWebuiFlush: (entry) => schedulePendingWebuiFlush(entry)
2932
+ };
2705
2933
  }
2706
2934
  async function boot() {
2707
2935
  const locale = await readUiLocale();
@@ -2719,18 +2947,27 @@ async function boot() {
2719
2947
  safeOpenExternal(url);
2720
2948
  return { action: "deny" };
2721
2949
  });
2722
- registerIpcHandlers();
2950
+ registerIpcHandlers(buildIpcHandlerContext());
2723
2951
  await shellView.webContents.loadURL(shellUrl);
2724
2952
  const prevState = validatedWindowState(manager.getWindowState());
2725
2953
  const defaultWidth = 1180;
2726
2954
  const defaultHeight = 720;
2955
+ let appIcon;
2956
+ try {
2957
+ const iconPath = new URL("../../assets/icon.svg", import.meta.url).pathname;
2958
+ await fs3.stat(iconPath);
2959
+ appIcon = nativeImage.createFromPath(iconPath);
2960
+ if (appIcon.isEmpty()) appIcon = void 0;
2961
+ } catch {
2962
+ }
2727
2963
  const winOptions = {
2728
2964
  width: prevState?.width ?? defaultWidth,
2729
2965
  height: prevState?.height ?? defaultHeight,
2730
2966
  show: false,
2731
2967
  minWidth: MIN_WINDOW_WIDTH2,
2732
2968
  minHeight: MIN_WINDOW_HEIGHT2,
2733
- title: tMain("windowTitle")
2969
+ title: tMain("windowTitle"),
2970
+ ...appIcon ? { icon: appIcon } : {}
2734
2971
  };
2735
2972
  if (prevState) {
2736
2973
  if (prevState.x !== void 0) winOptions.x = prevState.x;