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
10 changes: 10 additions & 0 deletions apps/desktop/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
import { makeAppSetup } from "lib/electron-app/factories/app/setup";
import {
handleAuthCallback,
loadToken,
parseAuthDeepLink,
} from "lib/trpc/routers/auth/utils/auth-functions";
import { applyShellEnvToProcess } from "lib/trpc/routers/workspaces/utils/shell-env";
import { env as mainEnv } from "main/env.main";
import {
DEFAULT_CONFIRM_ON_QUIT,
PLATFORM,
Expand Down Expand Up @@ -358,6 +360,14 @@ if (!gotTheLock) {
// before the tray initializes, so it shows accurate status immediately.
await getHostServiceCoordinator().discoverAll();

if (IS_DEV) {
getHostServiceCoordinator().enableDevReload(async () => {
const { token } = await loadToken();
if (!token) return null;
return { authToken: token, cloudApiUrl: mainEnv.NEXT_PUBLIC_API_URL };
});
}
Comment thread
Kitenite marked this conversation as resolved.

await makeAppSetup(() => MainWindow());
setupAutoUpdater();
initTray();
Expand Down
88 changes: 88 additions & 0 deletions apps/desktop/src/main/lib/host-service-coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as childProcess from "node:child_process";
import { randomBytes } from "node:crypto";
import { EventEmitter } from "node:events";
import * as fs from "node:fs";
import { createServer } from "node:net";
import path from "node:path";
import { settings } from "@superset/local-db";
Expand Down Expand Up @@ -103,6 +104,7 @@ export class HostServiceCoordinator extends EventEmitter {
>();
private scriptPath = path.join(__dirname, "host-service.js");
private machineId = getHashedDeviceId();
private devReloadWatcher: fs.FSWatcher | null = null;

async start(
organizationId: string,
Expand Down Expand Up @@ -222,6 +224,92 @@ export class HostServiceCoordinator extends EventEmitter {
);
}

/**
* Dev-only: watch the built host-service bundle and restart running
* instances when it changes. Gives a fast edit→reload loop for code
* under packages/host-service and src/main/host-service without
* restarting Electron. In-memory host-service state (PTYs, watchers,
* chat streams) is torn down on each reload — this is not true HMR.
*/
enableDevReload(
configProvider: () => Promise<SpawnConfig | null>,
): () => void {
if (this.devReloadWatcher) return () => {};

const scriptDir = path.dirname(this.scriptPath);
const scriptFile = path.basename(this.scriptPath);
let debounce: ReturnType<typeof setTimeout> | null = null;
let reloading = false;

const waitForStableBundle = async (): Promise<boolean> => {
const deadline = Date.now() + 5_000;
let lastSize = -1;
let stableSince = 0;
while (Date.now() < deadline) {
try {
const stat = fs.statSync(this.scriptPath);
if (stat.size > 0 && stat.size === lastSize) {
if (Date.now() - stableSince >= 150) return true;
} else {
lastSize = stat.size;
stableSince = Date.now();
}
} catch {
lastSize = -1;
stableSince = 0;
}
await new Promise((r) => setTimeout(r, 50));
}
return false;
};

const trigger = () => {
if (debounce) clearTimeout(debounce);
debounce = setTimeout(() => {
void (async () => {
if (reloading) return;
if (this.getActiveOrganizationIds().length === 0) return;
reloading = true;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
try {
const ready = await waitForStableBundle();
if (!ready) {
console.warn(
"[host-service] bundle did not stabilize, skipping reload",
);
return;
}
const config = await configProvider();
if (!config) return;
console.log(
"[host-service] bundle changed, restarting running instances",
);
await this.restartAll(config);
} catch (error) {
console.error("[host-service] dev reload failed:", error);
} finally {
reloading = false;
}
})();
}, 250);
};
Comment thread
Kitenite marked this conversation as resolved.

try {
this.devReloadWatcher = fs.watch(scriptDir, (_event, filename) => {
if (filename && filename !== scriptFile) return;
trigger();
});
} catch (error) {
console.error("[host-service] failed to enable dev reload:", error);
return () => {};
}

return () => {
if (debounce) clearTimeout(debounce);
this.devReloadWatcher?.close();
this.devReloadWatcher = null;
};
}

// ── Adoption ──────────────────────────────────────────────────────

private async tryAdopt(organizationId: string): Promise<Connection | null> {
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading