feat: add gateway daemon runtime selector

This commit is contained in:
Peter Steinberger
2026-01-06 23:27:58 +01:00
parent 18c43fe462
commit 707f7918bc
17 changed files with 191 additions and 23 deletions

View File

@@ -59,6 +59,11 @@ import {
import { setupProviders } from "./onboard-providers.js";
import { promptRemoteGatewayConfig } from "./onboard-remote.js";
import { setupSkills } from "./onboard-skills.js";
import {
DEFAULT_GATEWAY_DAEMON_RUNTIME,
GATEWAY_DAEMON_RUNTIME_OPTIONS,
type GatewayDaemonRuntime,
} from "./daemon-runtime.js";
import {
applyOpenAICodexModelDefault,
OPENAI_CODEX_DEFAULT_MODEL,
@@ -502,11 +507,13 @@ async function maybeInstallDaemon(params: {
runtime: RuntimeEnv;
port: number;
gatewayToken?: string;
daemonRuntime?: GatewayDaemonRuntime;
}) {
const service = resolveGatewayService();
const loaded = await service.isLoaded({ env: process.env });
let shouldCheckLinger = false;
let shouldInstall = true;
let daemonRuntime = params.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
if (loaded) {
const action = guardCancel(
await select({
@@ -531,11 +538,25 @@ async function maybeInstallDaemon(params: {
}
if (shouldInstall) {
if (!params.daemonRuntime) {
daemonRuntime = guardCancel(
await select({
message: "Gateway daemon runtime",
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
}),
params.runtime,
) as GatewayDaemonRuntime;
}
const devMode =
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
process.argv[1]?.endsWith(".ts");
const { programArguments, workingDirectory } =
await resolveGatewayProgramArguments({ port: params.port, dev: devMode });
await resolveGatewayProgramArguments({
port: params.port,
dev: devMode,
runtime: daemonRuntime,
});
const environment: Record<string, string | undefined> = {
PATH: process.env.PATH,
CLAWDBOT_GATEWAY_TOKEN: params.gatewayToken,

View File

@@ -0,0 +1,27 @@
export type GatewayDaemonRuntime = "node" | "bun";
export const DEFAULT_GATEWAY_DAEMON_RUNTIME: GatewayDaemonRuntime = "node";
export const GATEWAY_DAEMON_RUNTIME_OPTIONS: Array<{
value: GatewayDaemonRuntime;
label: string;
hint?: string;
}> = [
{
value: "node",
label: "Node (recommended)",
hint:
"Required for WhatsApp (Baileys WebSocket + Bun can corrupt memory on reconnect)",
},
{
value: "bun",
label: "Bun (faster)",
hint: "Use only when WhatsApp is disabled",
},
];
export function isGatewayDaemonRuntime(
value: string | undefined,
): value is GatewayDaemonRuntime {
return value === "node" || value === "bun";
}

View File

@@ -2,7 +2,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { confirm, intro, note, outro } from "@clack/prompts";
import { confirm, intro, note, outro, select } from "@clack/prompts";
import {
DEFAULT_SANDBOX_BROWSER_IMAGE,
@@ -45,6 +45,11 @@ import {
guardCancel,
printWizardHeader,
} from "./onboard-helpers.js";
import {
DEFAULT_GATEWAY_DAEMON_RUNTIME,
GATEWAY_DAEMON_RUNTIME_OPTIONS,
type GatewayDaemonRuntime,
} from "./daemon-runtime.js";
import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
@@ -768,12 +773,24 @@ async function maybeMigrateLegacyGatewayService(
);
if (!install) return;
const daemonRuntime = guardCancel(
await select({
message: "Gateway daemon runtime",
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
}),
runtime,
) as GatewayDaemonRuntime;
const devMode =
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
process.argv[1]?.endsWith(".ts");
const port = resolveGatewayPort(cfg, process.env);
const { programArguments, workingDirectory } =
await resolveGatewayProgramArguments({ port, dev: devMode });
await resolveGatewayProgramArguments({
port,
dev: devMode,
runtime: daemonRuntime,
});
const environment: Record<string, string | undefined> = {
PATH: process.env.PATH,
CLAWDBOT_GATEWAY_TOKEN:

View File

@@ -30,6 +30,10 @@ import type {
OnboardMode,
OnboardOptions,
} from "./onboard-types.js";
import {
DEFAULT_GATEWAY_DAEMON_RUNTIME,
isGatewayDaemonRuntime,
} from "./daemon-runtime.js";
import { ensureSystemdUserLingerNonInteractive } from "./systemd-linger.js";
export async function runNonInteractiveOnboarding(
@@ -223,13 +227,24 @@ export async function runNonInteractiveOnboarding(
skipBootstrap: Boolean(nextConfig.agent?.skipBootstrap),
});
const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
if (opts.installDaemon) {
if (!isGatewayDaemonRuntime(daemonRuntimeRaw)) {
runtime.error("Invalid --daemon-runtime (use node or bun)");
runtime.exit(1);
return;
}
const service = resolveGatewayService();
const devMode =
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
process.argv[1]?.endsWith(".ts");
const { programArguments, workingDirectory } =
await resolveGatewayProgramArguments({ port, dev: devMode });
await resolveGatewayProgramArguments({
port,
dev: devMode,
runtime: daemonRuntimeRaw,
});
const environment: Record<string, string | undefined> = {
PATH: process.env.PATH,
CLAWDBOT_GATEWAY_TOKEN: gatewayToken,
@@ -260,6 +275,7 @@ export async function runNonInteractiveOnboarding(
authChoice,
gateway: { port, bind, authMode, tailscaleMode },
installDaemon: Boolean(opts.installDaemon),
daemonRuntime: opts.installDaemon ? daemonRuntimeRaw : undefined,
skipSkills: Boolean(opts.skipSkills),
skipHealth: Boolean(opts.skipHealth),
},

View File

@@ -1,3 +1,5 @@
import type { GatewayDaemonRuntime } from "./daemon-runtime.js";
export type OnboardMode = "local" | "remote";
export type AuthChoice =
| "oauth"
@@ -33,6 +35,7 @@ export type OnboardOptions = {
tailscale?: TailscaleMode;
tailscaleResetOnExit?: boolean;
installDaemon?: boolean;
daemonRuntime?: GatewayDaemonRuntime;
skipSkills?: boolean;
skipHealth?: boolean;
nodeManager?: NodeManagerChoice;