refactor: split gateway server methods
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
- Agent tools: scope the Discord tool to Discord surface runs.
|
||||
- Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style.
|
||||
- Gateway: split server helpers/tests into hooks/session-utils/ws-log/net modules for better isolation; add unit coverage for hooks/session utils/ws log.
|
||||
- Gateway: extract WS method handling + HTTP/provider/constant helpers to shrink server wiring and improve testability.
|
||||
- Onboarding: fix Control UI basePath usage when showing/opening gateway URLs.
|
||||
- macOS Connections: move to sidebar + detail layout with structured sections and header actions.
|
||||
- macOS onboarding: increase window height so the permissions page fits without scrolling.
|
||||
- Thinking: default to low for reasoning-capable models when no /think or config default is set.
|
||||
|
||||
9
src/gateway/server-constants.ts
Normal file
9
src/gateway/server-constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const MAX_PAYLOAD_BYTES = 512 * 1024; // cap incoming frame size
|
||||
export const MAX_BUFFERED_BYTES = 1.5 * 1024 * 1024; // per-connection send buffer limit
|
||||
|
||||
export const MAX_CHAT_HISTORY_MESSAGES_BYTES = 6 * 1024 * 1024; // keep history responses comfortably under client WS limits
|
||||
export const HANDSHAKE_TIMEOUT_MS = 10_000;
|
||||
export const TICK_INTERVAL_MS = 30_000;
|
||||
export const HEALTH_REFRESH_INTERVAL_MS = 60_000;
|
||||
export const DEDUPE_TTL_MS = 5 * 60_000;
|
||||
export const DEDUPE_MAX = 1000;
|
||||
@@ -1,21 +1,31 @@
|
||||
import {
|
||||
createServer as createHttpServer,
|
||||
type IncomingMessage,
|
||||
type Server as HttpServer,
|
||||
type IncomingMessage,
|
||||
type ServerResponse,
|
||||
} from "node:http";
|
||||
import { type WebSocketServer } from "ws";
|
||||
import type { WebSocketServer } from "ws";
|
||||
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
||||
import type { CanvasHostHandler } from "../canvas-host/server.js";
|
||||
import { type HooksConfigResolved, extractHookToken, normalizeAgentPayload, normalizeHookHeaders, normalizeWakePayload, readJsonBody } from "./hooks.js";
|
||||
import { applyHookMappings } from "./hooks-mapping.js";
|
||||
import { handleControlUiHttpRequest } from "./control-ui.js";
|
||||
import type { createSubsystemLogger } from "../logging.js";
|
||||
import { handleControlUiHttpRequest } from "./control-ui.js";
|
||||
import {
|
||||
extractHookToken,
|
||||
type HooksConfigResolved,
|
||||
normalizeAgentPayload,
|
||||
normalizeHookHeaders,
|
||||
normalizeWakePayload,
|
||||
readJsonBody,
|
||||
} from "./hooks.js";
|
||||
import { applyHookMappings } from "./hooks-mapping.js";
|
||||
|
||||
type SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
|
||||
|
||||
type HookDispatchers = {
|
||||
dispatchWakeHook: (value: { text: string; mode: "now" | "next-heartbeat" }) => void;
|
||||
dispatchWakeHook: (value: {
|
||||
text: string;
|
||||
mode: "now" | "next-heartbeat";
|
||||
}) => void;
|
||||
dispatchAgentHook: (value: {
|
||||
message: string;
|
||||
name: string;
|
||||
@@ -46,13 +56,22 @@ export type HooksRequestHandler = (
|
||||
res: ServerResponse,
|
||||
) => Promise<boolean>;
|
||||
|
||||
export function createHooksRequestHandler(opts: {
|
||||
hooksConfig: HooksConfigResolved | null;
|
||||
bindHost: string;
|
||||
port: number;
|
||||
logHooks: SubsystemLogger;
|
||||
} & HookDispatchers): HooksRequestHandler {
|
||||
const { hooksConfig, bindHost, port, logHooks, dispatchAgentHook, dispatchWakeHook } = opts;
|
||||
export function createHooksRequestHandler(
|
||||
opts: {
|
||||
hooksConfig: HooksConfigResolved | null;
|
||||
bindHost: string;
|
||||
port: number;
|
||||
logHooks: SubsystemLogger;
|
||||
} & HookDispatchers,
|
||||
): HooksRequestHandler {
|
||||
const {
|
||||
hooksConfig,
|
||||
bindHost,
|
||||
port,
|
||||
logHooks,
|
||||
dispatchAgentHook,
|
||||
dispatchWakeHook,
|
||||
} = opts;
|
||||
return async (req, res) => {
|
||||
if (!hooksConfig) return false;
|
||||
const url = new URL(req.url ?? "/", `http://${bindHost}:${port}`);
|
||||
@@ -97,7 +116,9 @@ export function createHooksRequestHandler(opts: {
|
||||
const headers = normalizeHookHeaders(req);
|
||||
|
||||
if (subPath === "wake") {
|
||||
const normalized = normalizeWakePayload(payload as Record<string, unknown>);
|
||||
const normalized = normalizeWakePayload(
|
||||
payload as Record<string, unknown>,
|
||||
);
|
||||
if (!normalized.ok) {
|
||||
sendJson(res, 400, { ok: false, error: normalized.error });
|
||||
return true;
|
||||
@@ -108,7 +129,9 @@ export function createHooksRequestHandler(opts: {
|
||||
}
|
||||
|
||||
if (subPath === "agent") {
|
||||
const normalized = normalizeAgentPayload(payload as Record<string, unknown>);
|
||||
const normalized = normalizeAgentPayload(
|
||||
payload as Record<string, unknown>,
|
||||
);
|
||||
if (!normalized.ok) {
|
||||
sendJson(res, 400, { ok: false, error: normalized.error });
|
||||
return true;
|
||||
@@ -178,8 +201,12 @@ export function createGatewayHttpServer(opts: {
|
||||
controlUiBasePath: string;
|
||||
handleHooksRequest: HooksRequestHandler;
|
||||
}): HttpServer {
|
||||
const { canvasHost, controlUiEnabled, controlUiBasePath, handleHooksRequest } =
|
||||
opts;
|
||||
const {
|
||||
canvasHost,
|
||||
controlUiEnabled,
|
||||
controlUiBasePath,
|
||||
handleHooksRequest,
|
||||
} = opts;
|
||||
const httpServer: HttpServer = createHttpServer((req, res) => {
|
||||
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
|
||||
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
||||
|
||||
2921
src/gateway/server-methods.ts
Normal file
2921
src/gateway/server-methods.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
||||
import type { ClawdisConfig } from "../config/config.js";
|
||||
import { shouldLogVerbose } from "../globals.js";
|
||||
import type { createSubsystemLogger } from "../logging.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { monitorDiscordProvider } from "../discord/index.js";
|
||||
import { probeDiscord } from "../discord/probe.js";
|
||||
import { shouldLogVerbose } from "../globals.js";
|
||||
import { monitorIMessageProvider } from "../imessage/index.js";
|
||||
import type { createSubsystemLogger } from "../logging.js";
|
||||
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { monitorSignalProvider } from "../signal/index.js";
|
||||
import { resolveTelegramToken } from "../telegram/token.js";
|
||||
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||
import { probeTelegram } from "../telegram/probe.js";
|
||||
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
||||
import { readWebSelfId } from "../web/session.js";
|
||||
import { resolveTelegramToken } from "../telegram/token.js";
|
||||
import type { WebProviderStatus } from "../web/auto-reply.js";
|
||||
import { readWebSelfId } from "../web/session.js";
|
||||
import { formatError } from "./server-utils.js";
|
||||
|
||||
export type TelegramRuntimeStatus = {
|
||||
@@ -245,7 +245,9 @@ export function createProviderManager(
|
||||
lastError: "disabled",
|
||||
};
|
||||
if (shouldLogVerbose()) {
|
||||
logTelegram.debug("telegram provider disabled (telegram.enabled=false)");
|
||||
logTelegram.debug(
|
||||
"telegram provider disabled (telegram.enabled=false)",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { formatError, normalizeVoiceWakeTriggers } from "./server-utils.js";
|
||||
describe("normalizeVoiceWakeTriggers", () => {
|
||||
test("returns defaults when input is empty", () => {
|
||||
expect(normalizeVoiceWakeTriggers([])).toEqual(defaultVoiceWakeTriggers());
|
||||
expect(normalizeVoiceWakeTriggers(null)).toEqual(defaultVoiceWakeTriggers());
|
||||
expect(normalizeVoiceWakeTriggers(null)).toEqual(
|
||||
defaultVoiceWakeTriggers(),
|
||||
);
|
||||
});
|
||||
|
||||
test("trims and limits entries", () => {
|
||||
@@ -20,8 +22,10 @@ describe("formatError", () => {
|
||||
});
|
||||
|
||||
test("handles status/code", () => {
|
||||
expect(formatError({ status: 500, code: "EPIPE" })).toBe("500 EPIPE");
|
||||
expect(formatError({ status: 404 })).toBe("404");
|
||||
expect(formatError({ code: "ENOENT" })).toBe("ENOENT");
|
||||
expect(formatError({ status: 500, code: "EPIPE" })).toBe(
|
||||
"status=500 code=EPIPE",
|
||||
);
|
||||
expect(formatError({ status: 404 })).toBe("status=404 code=unknown");
|
||||
expect(formatError({ code: "ENOENT" })).toBe("status=unknown code=ENOENT");
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -504,7 +504,7 @@ export async function runOnboardingWizard(
|
||||
const links = resolveControlUiLinks({
|
||||
bind,
|
||||
port,
|
||||
basePath: config.gateway?.controlUi?.basePath,
|
||||
basePath: baseConfig.gateway?.controlUi?.basePath,
|
||||
});
|
||||
const tokenParam =
|
||||
authMode === "token" && gatewayToken
|
||||
@@ -530,7 +530,7 @@ export async function runOnboardingWizard(
|
||||
const links = resolveControlUiLinks({
|
||||
bind,
|
||||
port,
|
||||
basePath: config.gateway?.controlUi?.basePath,
|
||||
basePath: baseConfig.gateway?.controlUi?.basePath,
|
||||
});
|
||||
const tokenParam =
|
||||
authMode === "token" && gatewayToken
|
||||
|
||||
Reference in New Issue
Block a user