From 415e1deb2857a75cc094d08161881e39c76f8c65 Mon Sep 17 00:00:00 2001 From: Dimitrios Karkoulis Date: Fri, 17 Jul 2026 17:59:04 +0300 Subject: [PATCH 1/5] Remove detect/quarantine fs ops from client --- package-lock.json | 29 - package.json | 1 - src/main/__tests__/hookHealthMonitor.test.ts | 246 ------- src/main/__tests__/mcpConfigMonitor.test.ts | 307 --------- src/main/clients/types.ts | 4 +- src/main/detectord/bootstrap.ts | 5 +- src/main/detectord/mode.ts | 24 - src/main/dialogs/debugWindow.ts | 25 - src/main/discovery/mcpDiscovery.ts | 3 +- src/main/index.ts | 135 +--- src/main/ipc/approvalsHandler.ts | 6 +- src/main/ipc/ipcHandlers.ts | 39 +- src/main/ipc/ipcHandlersMcpSubmit.ts | 355 +--------- src/main/menus/trayMenu.ts | 16 +- src/main/quarantine/quarantineManager.ts | 290 -------- src/main/runtime/hookHealthMonitor.ts | 351 ---------- src/main/runtime/hookInjection.ts | 108 +-- src/main/runtime/mcpConfigMonitor.ts | 683 +------------------ src/preload/index.d.ts | 2 - src/preload/index.ts | 2 - src/renderer/src/components/ClientsView.tsx | 10 +- 21 files changed, 50 insertions(+), 2591 deletions(-) delete mode 100644 src/main/__tests__/hookHealthMonitor.test.ts delete mode 100644 src/main/__tests__/mcpConfigMonitor.test.ts delete mode 100644 src/main/detectord/mode.ts delete mode 100644 src/main/quarantine/quarantineManager.ts delete mode 100644 src/main/runtime/hookHealthMonitor.ts diff --git a/package-lock.json b/package-lock.json index 64046b2..b54a617 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.2", "@sentry/electron": "^7.8.0", - "chokidar": "^5.0.0", "electron-updater": "^6.3.9", "electron-window-state": "^5.0.0", "eventsource": "^4.1.0", @@ -4963,21 +4962,6 @@ "node": ">= 16" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -8332,19 +8316,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", diff --git a/package.json b/package.json index e89d304..3918eaa 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "dependencies": { "@electron-toolkit/preload": "^3.0.2", "@sentry/electron": "^7.8.0", - "chokidar": "^5.0.0", "electron-updater": "^6.3.9", "electron-window-state": "^5.0.0", "eventsource": "^4.1.0", diff --git a/src/main/__tests__/hookHealthMonitor.test.ts b/src/main/__tests__/hookHealthMonitor.test.ts deleted file mode 100644 index 91b3fb3..0000000 --- a/src/main/__tests__/hookHealthMonitor.test.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { tmpdir } from 'os' -import { join } from 'path' -import { promises as fs } from 'fs' - -// Mock electron -vi.mock('electron', () => ({ - app: { - getPath: (name: string) => { - if (name === 'userData') return join(tmpdir(), 'edison-test-userdata') - return join(tmpdir(), 'edison-test-' + name) - }, - getVersion: () => '1.0.0-test' - } -})) - -// Mock sentry -vi.mock('../infra/sentry', () => ({ - captureError: vi.fn() -})) - -// Mock setupConfig (imported by hookHealthMonitor for getMcpUrl / getIsServerOnline) -vi.mock('../infra/setupConfig', () => ({ - getMcpUrl: vi.fn().mockReturnValue(null), - getIsServerOnline: vi.fn().mockReturnValue(false) -})) - -// Mock hookInjection so we can control getHookStatus and getPendingErrorsDir -vi.mock('../runtime/hookInjection', () => ({ - getHookStatus: vi.fn().mockResolvedValue([ - { - client: 'claude-code', - installed: true, - hasHook: true, - hooksApplicable: true, - mcpApplicable: true, - hookCount: 4, - totalHooks: 4, - mcpConnected: false, - mcpConfigured: false - }, - { - client: 'cursor', - installed: true, - hasHook: false, - hooksApplicable: true, - mcpApplicable: true, - hookCount: 0, - totalHooks: 3, - mcpConnected: false, - mcpConfigured: false - }, - { - client: 'windsurf', - installed: false, - hasHook: false, - hooksApplicable: true, - mcpApplicable: true, - hookCount: 0, - totalHooks: 1, - mcpConnected: false, - mcpConfigured: false - } - ]), - getPendingErrorsDir: vi.fn().mockReturnValue(join(tmpdir(), 'edison-test-errors')), - getPendingRegistrationsDir: vi.fn().mockReturnValue(join(tmpdir(), 'edison-test-pending')), - getEdisonWatchDir: vi.fn().mockReturnValue(join(tmpdir(), 'edison-test-watchdir')) -})) - -import { - setOnHooksMissingCallback, - getHookStatusLabel, - startHookHealthMonitor, - stopHookHealthMonitor -} from '../runtime/hookHealthMonitor' - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- -let testDir: string - -async function createTestDir(): Promise { - const dir = join(tmpdir(), 'hook-health-test-' + Date.now() + '-' + Math.random().toString(36)) - await fs.mkdir(dir, { recursive: true }) - return dir -} - -async function cleanupDir(dir: string): Promise { - try { - await fs.rm(dir, { recursive: true, force: true }) - } catch { - /* ignore */ - } -} - -// ============================================================================ -// Tests -// ============================================================================ - -const pendingDir = join(tmpdir(), 'edison-test-pending') -const watchDir = join(tmpdir(), 'edison-test-watchdir') - -/** Poll until the file is gone or the timeout passes; returns whether it's gone. */ -async function waitForGone(filePath: string, timeoutMs = 3000): Promise { - const deadline = Date.now() + timeoutMs - while (Date.now() < deadline) { - try { - await fs.access(filePath) - } catch { - return true - } - await new Promise((r) => setTimeout(r, 50)) - } - return false -} - -describe('hookHealthMonitor', () => { - beforeEach(async () => { - testDir = await createTestDir() - // Ensure the mock dirs exist for the watcher and sweeps - const errorsDir = join(tmpdir(), 'edison-test-errors') - await fs.mkdir(errorsDir, { recursive: true }) - await fs.mkdir(pendingDir, { recursive: true }) - await fs.mkdir(watchDir, { recursive: true }) - }) - - afterEach(async () => { - await stopHookHealthMonitor() - await cleanupDir(testDir) - const errorsDir = join(tmpdir(), 'edison-test-errors') - await cleanupDir(errorsDir) - await cleanupDir(pendingDir) - await cleanupDir(watchDir) - vi.restoreAllMocks() - }) - - describe('setOnHooksMissingCallback', () => { - it('accepts a callback function', () => { - const cb = vi.fn() - setOnHooksMissingCallback(cb) - // Should not throw - }) - }) - - describe('getHookStatusLabel', () => { - it('returns a string label', () => { - const label = getHookStatusLabel() - expect(typeof label).toBe('string') - expect(label.length).toBeGreaterThan(0) - }) - }) - - describe('startHookHealthMonitor / stopHookHealthMonitor', () => { - it('starts and stops without error', async () => { - startHookHealthMonitor() - // Give it a moment to initialize - await new Promise((r) => setTimeout(r, 100)) - await stopHookHealthMonitor() - }) - - it('stop is safe to call multiple times', async () => { - await stopHookHealthMonitor() - await stopHookHealthMonitor() - // Should not throw - }) - - it('drains pre-existing registration files from pending/ on startup', async () => { - const regFile = join(pendingDir, '20260101-120000-12345-claude-code.json') - await fs.writeFile( - regFile, - JSON.stringify({ projectPath: '/tmp/x', registeredBy: 'claude-code' }) - ) - - startHookHealthMonitor() - expect(await waitForGone(regFile)).toBe(true) - }) - - it('consumes registration files added while running', async () => { - startHookHealthMonitor() - await new Promise((r) => setTimeout(r, 200)) - - const regFile = join(pendingDir, '20260101-130000-54321-cursor.json') - await fs.writeFile(regFile, JSON.stringify({ projectPath: '/tmp/y', registeredBy: 'cursor' })) - expect(await waitForGone(regFile)).toBe(true) - }) - - it('still consumes session-end files', async () => { - startHookHealthMonitor() - await new Promise((r) => setTimeout(r, 200)) - - const endFile = join(pendingDir, '20260101-140000-11111-session-end.json') - await fs.writeFile( - endFile, - JSON.stringify({ event: 'session_end', conversation_id: 'abc', reason: 'exit' }) - ) - expect(await waitForGone(endFile)).toBe(true) - }) - - it('leaves in-flight dot-temp files alone', async () => { - startHookHealthMonitor() - await new Promise((r) => setTimeout(r, 200)) - - const tmpFile = join(pendingDir, '.20260101-150000-22222-claude-code.json.tmp') - await fs.writeFile(tmpFile, '{partial') - expect(await waitForGone(tmpFile, 1000)).toBe(false) - }) - - it('sweeps active_session files for dead PIDs and keeps live ones', async () => { - const { spawnSync } = await import('child_process') - // A process that has already exited - its PID is guaranteed dead - const deadPid = spawnSync('true').pid as number - const deadFile = join(watchDir, `active_session_${deadPid}.json`) - const aliveFile = join(watchDir, `active_session_${process.pid}.json`) - await fs.writeFile(deadFile, JSON.stringify({ session_id: 'dead' })) - await fs.writeFile(aliveFile, JSON.stringify({ session_id: 'alive' })) - - startHookHealthMonitor() - expect(await waitForGone(deadFile)).toBe(true) - // Live PID's file must survive - await expect(fs.access(aliveFile)).resolves.toBeUndefined() - }) - - it('detects missing hooks and triggers callback', async () => { - const cb = vi.fn() - setOnHooksMissingCallback(cb) - - startHookHealthMonitor() - - // Wait for the first status check to complete - await new Promise((r) => setTimeout(r, 200)) - - await stopHookHealthMonitor() - - // The mock has cursor installed but missing hook - callback should fire - if (cb.mock.calls.length > 0) { - const entries = cb.mock.calls[0]![0] - expect(Array.isArray(entries)).toBe(true) - const cursorEntry = entries.find((e: { client: string }) => e.client === 'cursor') - if (cursorEntry) { - expect(cursorEntry.installed).toBe(true) - expect(cursorEntry.hasHook).toBe(false) - } - } - }) - }) -}) diff --git a/src/main/__tests__/mcpConfigMonitor.test.ts b/src/main/__tests__/mcpConfigMonitor.test.ts deleted file mode 100644 index e5b3aaa..0000000 --- a/src/main/__tests__/mcpConfigMonitor.test.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { tmpdir } from "os"; -import { join } from "path"; -import { promises as fs } from "fs"; - -// Mock electron -vi.mock("electron", () => ({ - app: { - getPath: (name: string) => { - if (name === "userData") return join(tmpdir(), "edison-test-userdata"); - return join(tmpdir(), "edison-test-" + name); - }, - getVersion: () => "1.0.0-test", - }, -})); - -// Mock sentry -vi.mock("../infra/sentry", () => ({ - captureError: vi.fn(), -})); - -import { - McpConfigMonitor, - isEdisonWatchServer, - filterOutEdisonWatchServers, - getClientDisplayName, -} from "../runtime/mcpConfigMonitor"; -import { SeenServersStore } from "../discovery/seenServersStore"; -import type { DiscoveredMcpServer } from "../discovery/mcpDiscovery"; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- -let testDir: string; - -async function createTestDir(): Promise { - const dir = join( - tmpdir(), - "config-monitor-test-" + Date.now() + "-" + Math.random().toString(36), - ); - await fs.mkdir(dir, { recursive: true }); - return dir; -} - -async function cleanupDir(dir: string): Promise { - try { - await fs.rm(dir, { recursive: true, force: true }); - } catch { - /* ignore */ - } -} - -// ============================================================================ -// Tests -// ============================================================================ - -describe("mcpConfigMonitor", () => { - beforeEach(async () => { - testDir = await createTestDir(); - }); - - afterEach(async () => { - await cleanupDir(testDir); - }); - - describe("isEdisonWatchServer", () => { - it("detects Edison Watch server by npx mcp-remote with edison args", () => { - const server: DiscoveredMcpServer = { - name: "gateway", - client: "cursor", - source: "user", - path: "/tmp/mcp.json", - config: { - command: "npx", - args: ["-y", "mcp-remote", "https://mcp.edison.watch/sse"], - }, - }; - expect(isEdisonWatchServer(server)).toBe(true); - }); - - it("detects Edison Watch server by URL containing 'edison'", () => { - const server: DiscoveredMcpServer = { - name: "my-proxy", - client: "cursor", - source: "user", - path: "/tmp/mcp.json", - config: { - type: "http", - url: "https://mcp.edison.watch/v1/sse", - } as never, - }; - expect(isEdisonWatchServer(server)).toBe(true); - }); - - it("detects Edison Watch server by localhost URL with /mcp path", () => { - const server: DiscoveredMcpServer = { - name: "local-proxy", - client: "cursor", - source: "user", - path: "/tmp/mcp.json", - config: { - type: "http", - url: "http://localhost:3000/mcp", - } as never, - }; - expect(isEdisonWatchServer(server)).toBe(true); - }); - - it("returns false for non-Edison servers", () => { - const server: DiscoveredMcpServer = { - name: "some-other-server", - client: "cursor", - source: "user", - path: "/tmp/mcp.json", - config: { command: "npx", args: ["-y", "some-server"] }, - }; - expect(isEdisonWatchServer(server)).toBe(false); - }); - }); - - describe("filterOutEdisonWatchServers", () => { - it("filters Edison servers from array", () => { - const servers: DiscoveredMcpServer[] = [ - { - name: "ew-proxy", - client: "cursor", - source: "user", - path: "/a", - config: { - type: "http", - url: "https://mcp.edison.watch/sse", - } as never, - }, - { - name: "other-server", - client: "cursor", - source: "user", - path: "/b", - config: { command: "other" }, - }, - ]; - const filtered = filterOutEdisonWatchServers(servers); - expect(filtered).toHaveLength(1); - expect(filtered[0]!.name).toBe("other-server"); - }); - - it("returns empty array when all servers are Edison", () => { - const servers: DiscoveredMcpServer[] = [ - { - name: "ew", - client: "cursor", - source: "user", - path: "/a", - config: { - command: "npx", - args: ["-y", "mcp-remote", "https://edison.watch/mcp/sse"], - }, - }, - ]; - expect(filterOutEdisonWatchServers(servers)).toHaveLength(0); - }); - }); - - describe("getClientDisplayName", () => { - it("returns human-readable names for known clients", () => { - expect(getClientDisplayName("vscode")).toBe("VS Code"); - expect(getClientDisplayName("cursor")).toBe("Cursor"); - expect(getClientDisplayName("claude-code")).toBe("Claude Code"); - expect(getClientDisplayName("windsurf")).toBe("Windsurf"); - expect(getClientDisplayName("zed")).toBe("Zed"); - }); - }); - - describe("McpConfigMonitor", () => { - it("creates monitor with SeenServersStore", () => { - const storePath = join(testDir, "seen.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - expect(monitor).toBeDefined(); - expect(monitor.getCurrentServers()).toEqual([]); - expect(monitor.getMonitoredPaths()).toEqual([]); - }); - - it("detects new servers via forceRescan", async () => { - const storePath = join(testDir, "seen.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - // Force a rescan - discoverMcpServers() scans system paths - const changes = await monitor.forceRescan(); - expect(Array.isArray(changes)).toBe(true); - }); - - it("detects removed servers after forceRescan", async () => { - const storePath = join(testDir, "seen-removed.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - // First scan to establish baseline - await monitor.forceRescan(); - const before = monitor.getCurrentServers().length; - - // Second scan - no changes expected on empty system - const changes = await monitor.forceRescan(); - const after = monitor.getCurrentServers().length; - - // Verify deterministic behavior - expect(after).toBe(before); - expect(Array.isArray(changes)).toBe(true); - }); - - it("handles corrupt config files gracefully", async () => { - const storePath = join(testDir, "seen-corrupt.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - const corruptPath = join(testDir, "corrupt.json"); - await fs.writeFile(corruptPath, "{{invalid json!!", "utf-8"); - - await monitor.addConfigPaths([corruptPath]); - - // Should not throw even with corrupt file - const changes = await monitor.forceRescan(); - expect(Array.isArray(changes)).toBe(true); - }); - - it("addConfigPaths requires monitor to be running", async () => { - const storePath = join(testDir, "seen-paths.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - const p1 = join(testDir, "a.json"); - await fs.writeFile(p1, "{}", "utf-8"); - - // Monitor not started - addConfigPaths returns empty - const added = await monitor.addConfigPaths([p1]); - expect(added).toEqual([]); - expect(monitor.getMonitoredPaths()).not.toContain(p1); - }); - - it("stop is safe to call multiple times", async () => { - const storePath = join(testDir, "seen-stop.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store); - - await monitor.stop(); - await monitor.stop(); - // Should not throw - }); - - it("accepts custom rescan interval", async () => { - const storePath = join(testDir, "seen-rescan.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store, 500, 30_000); - - expect(monitor).toBeDefined(); - await monitor.stop(); - }); - - it("periodic rescan triggers checkForChanges", async () => { - const storePath = join(testDir, "seen-periodic.json"); - const store = new SeenServersStore(storePath); - // Use a very short rescan interval for testing (50ms) - const monitor = new McpConfigMonitor(store, 500, 50); - - // Spy on the private checkForChanges method directly - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const checkSpy = vi - .spyOn(monitor as any, "checkForChanges") - .mockResolvedValue([]); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (monitor as any).isRunning = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (monitor as any).startRescanTimer(); - - try { - // Wait for at least one interval tick - await new Promise((r) => setTimeout(r, 120)); - - await monitor.stop(); - - expect(checkSpy).toHaveBeenCalled(); - } finally { - checkSpy.mockRestore(); - } - }); - - it("stop clears rescan timer", async () => { - const storePath = join(testDir, "seen-stop-rescan.json"); - const store = new SeenServersStore(storePath); - const monitor = new McpConfigMonitor(store, 500, 100); - - // isRunning defaults to false, so the timer's guard prevents checkForChanges - // from running even if a tick fires before stop() clears the interval. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (monitor as any).startRescanTimer(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((monitor as any).rescanTimer).not.toBeNull(); - - await monitor.stop(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((monitor as any).rescanTimer).toBeNull(); - }); - }); -}); diff --git a/src/main/clients/types.ts b/src/main/clients/types.ts index a602301..7779fdd 100644 --- a/src/main/clients/types.ts +++ b/src/main/clients/types.ts @@ -52,7 +52,7 @@ export interface HookBinding { export interface DirWatchTarget { path: string - /** chokidar depth. Cursor's workspaceStorage uses depth:1; plugin cache depth:3. */ + /** Watch recursion depth. Cursor's workspaceStorage uses depth:1; plugin cache depth:3. */ depth: number /** Optional predicate on the changed path to decide whether to react. */ filter?: (path: string) => boolean @@ -60,7 +60,7 @@ export interface DirWatchTarget { } export interface WatchTargets { - /** Files watched directly (chokidar depth:0). Same entries as configEntries(). */ + /** Files watched directly (depth:0). Same entries as configEntries(). */ files: McpConfigEntry[] /** Directory watchers with depth > 0. */ dirs: DirWatchTarget[] diff --git a/src/main/detectord/bootstrap.ts b/src/main/detectord/bootstrap.ts index 40df484..3b5ad4b 100644 --- a/src/main/detectord/bootstrap.ts +++ b/src/main/detectord/bootstrap.ts @@ -20,7 +20,6 @@ import { import { showDaemonApprovalDialog } from './approvalDialog' import { detectordBinaryExists, getDetectordBinaryPath } from './binary' import { ensureDetectord } from './lifecycle' -import { detectordPrimary } from './mode' import type { DetectordEvent, SecretOutcome, ServerView } from './protocol' import type { DetectordClient } from './socket' @@ -65,7 +64,7 @@ export async function bootstrapDetectord(creds?: DetectordEnrollInput): Promise< ) { return false } - const primary = detectordPrimary() + const primary = true console.log( `[detectord] bootstrap mode=${primary ? 'primary (enforce)' : 'shadow (detect-only)'} ` + `binary=${getDetectordBinaryPath()} available=${detectordBinaryExists()}` @@ -113,7 +112,7 @@ export async function bootstrapDetectord(creds?: DetectordEnrollInput): Promise< export async function setDetectordSecret( key: string ): Promise<{ ok: boolean; outcome?: SecretOutcome; reason?: string }> { - const ensured = await ensureDetectord((m) => console.log(m), detectordPrimary()) + const ensured = await ensureDetectord((m) => console.log(m), true) if (!ensured.ok) { console.warn(`[detectord] set secret skipped: ${ensured.reason}`) return { ok: false, reason: ensured.reason } diff --git a/src/main/detectord/mode.ts b/src/main/detectord/mode.ts deleted file mode 100644 index 232c2d6..0000000 --- a/src/main/detectord/mode.ts +++ /dev/null @@ -1,24 +0,0 @@ -// The single cutover switch. -// -// primary: the detector daemon owns install + hooks + quarantine (runs -// --enforce, enrolls install:true); the TS pipeline stands down. -// shadow: the daemon runs detect-only + logs; the TS pipeline stays primary. -// -// Reversible at any time: set EW_DETECTORD_PRIMARY=0 (or false/off) to fall back -// to the TS pipeline, then restart the app. Default is primary (cutover active). - -export function detectordPrimary(): boolean { - // The daemon ships for macOS (launchd), Windows (Task Scheduler), and Linux - // (systemd user service). Other platforms have no supervisor integration, so - // the TS pipeline stays primary there. - if ( - process.platform !== 'darwin' && - process.platform !== 'win32' && - process.platform !== 'linux' - ) { - return false - } - const v = (process.env.EW_DETECTORD_PRIMARY ?? '').toLowerCase() - if (v === '0' || v === 'false' || v === 'off' || v === 'no') return false - return true -} diff --git a/src/main/dialogs/debugWindow.ts b/src/main/dialogs/debugWindow.ts index 615ab28..3063fe6 100644 --- a/src/main/dialogs/debugWindow.ts +++ b/src/main/dialogs/debugWindow.ts @@ -170,10 +170,6 @@ function buildDebugHtml(servers: DiscoveredMcpServer[], projectPathsHtml: string

Debug Actions

-