fix: harden node bridge keepalive

This commit is contained in:
Peter Steinberger
2026-01-09 15:45:09 +01:00
parent 6177c2d575
commit f5cc6bb283
5 changed files with 97 additions and 56 deletions

View File

@@ -52,11 +52,11 @@ describe("Agent-specific sandbox config", () => {
spawnCalls.length = 0;
});
it(
"should use global sandbox config when no agent-specific config exists",
{ timeout: 15_000 },
async () => {
const { resolveSandboxContext } = await import("./sandbox.js");
it(
"should use global sandbox config when no agent-specific config exists",
{ timeout: 15_000 },
async () => {
const { resolveSandboxContext } = await import("./sandbox.js");
const cfg: ClawdbotConfig = {
agents: {
@@ -75,19 +75,19 @@ describe("Agent-specific sandbox config", () => {
},
};
const context = await resolveSandboxContext({
config: cfg,
sessionKey: "agent:main:main",
workspaceDir: "/tmp/test",
});
const context = await resolveSandboxContext({
config: cfg,
sessionKey: "agent:main:main",
workspaceDir: "/tmp/test",
});
expect(context).toBeDefined();
expect(context?.enabled).toBe(true);
},
);
expect(context).toBeDefined();
expect(context?.enabled).toBe(true);
},
);
it("should allow agent-specific docker setupCommand overrides", async () => {
const { resolveSandboxContext } = await import("./sandbox.js");
it("should allow agent-specific docker setupCommand overrides", async () => {
const { resolveSandboxContext } = await import("./sandbox.js");
const cfg: ClawdbotConfig = {
agents: {

View File

@@ -276,28 +276,28 @@ describe("doctor", () => {
exit: vi.fn(),
};
migrateLegacyConfig.mockReturnValue({
config: { whatsapp: { allowFrom: ["+15555550123"] } },
changes: ["Moved routing.allowFrom → whatsapp.allowFrom."],
});
migrateLegacyConfig.mockReturnValue({
config: { whatsapp: { allowFrom: ["+15555550123"] } },
changes: ["Moved routing.allowFrom → whatsapp.allowFrom."],
});
await doctorCommand(runtime, { nonInteractive: true });
await doctorCommand(runtime, { nonInteractive: true });
expect(writeConfigFile).toHaveBeenCalledTimes(1);
const written = writeConfigFile.mock.calls[0]?.[0] as Record<
string,
unknown
>;
expect((written.whatsapp as Record<string, unknown>)?.allowFrom).toEqual([
"+15555550123",
]);
expect(written.routing).toBeUndefined();
},
);
expect(writeConfigFile).toHaveBeenCalledTimes(1);
const written = writeConfigFile.mock.calls[0]?.[0] as Record<
string,
unknown
>;
expect((written.whatsapp as Record<string, unknown>)?.allowFrom).toEqual([
"+15555550123",
]);
expect(written.routing).toBeUndefined();
},
);
it("migrates legacy Clawdis services", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
it("migrates legacy Clawdis services", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
exists: true,
raw: "{}",
parsed: {},

View File

@@ -3,10 +3,10 @@ import net from "node:net";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { approveNodePairing, listNodePairing } from "../node-pairing.js";
import { startNodeBridgeServer } from "./server.js";
import { configureNodeBridgeSocket, startNodeBridgeServer } from "./server.js";
function createLineReader(socket: net.Socket) {
let buffer = "";
@@ -70,6 +70,16 @@ describe("node bridge server", () => {
delete process.env.CLAWDBOT_ENABLE_BRIDGE_IN_TESTS;
});
it("enables keepalive on sockets", () => {
const socket = {
setNoDelay: vi.fn(),
setKeepAlive: vi.fn(),
};
configureNodeBridgeSocket(socket);
expect(socket.setNoDelay).toHaveBeenCalledWith(true);
expect(socket.setKeepAlive).toHaveBeenCalledWith(true, 15_000);
});
it("rejects hello when not paired", async () => {
const server = await startNodeBridgeServer({
host: "127.0.0.1",

View File

@@ -160,6 +160,14 @@ function isTestEnv() {
return process.env.NODE_ENV === "test" || Boolean(process.env.VITEST);
}
export function configureNodeBridgeSocket(socket: {
setNoDelay: (noDelay?: boolean) => void;
setKeepAlive: (enable?: boolean, initialDelay?: number) => void;
}) {
socket.setNoDelay(true);
socket.setKeepAlive(true, 15_000);
}
function encodeLine(frame: AnyBridgeFrame) {
return `${JSON.stringify(frame)}\n`;
}
@@ -228,7 +236,7 @@ export async function startNodeBridgeServer(
const loopbackHost = "127.0.0.1";
const onConnection = (socket: net.Socket) => {
socket.setNoDelay(true);
configureNodeBridgeSocket(socket);
let buffer = "";
let isAuthenticated = false;