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: scope the Discord tool to Discord surface runs.
|
||||||
- Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style.
|
- 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: 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 Connections: move to sidebar + detail layout with structured sections and header actions.
|
||||||
- macOS onboarding: increase window height so the permissions page fits without scrolling.
|
- 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.
|
- 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 {
|
import {
|
||||||
createServer as createHttpServer,
|
createServer as createHttpServer,
|
||||||
type IncomingMessage,
|
|
||||||
type Server as HttpServer,
|
type Server as HttpServer,
|
||||||
|
type IncomingMessage,
|
||||||
type ServerResponse,
|
type ServerResponse,
|
||||||
} from "node:http";
|
} from "node:http";
|
||||||
import { type WebSocketServer } from "ws";
|
import type { WebSocketServer } from "ws";
|
||||||
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
||||||
import type { CanvasHostHandler } from "../canvas-host/server.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 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 SubsystemLogger = ReturnType<typeof createSubsystemLogger>;
|
||||||
|
|
||||||
type HookDispatchers = {
|
type HookDispatchers = {
|
||||||
dispatchWakeHook: (value: { text: string; mode: "now" | "next-heartbeat" }) => void;
|
dispatchWakeHook: (value: {
|
||||||
|
text: string;
|
||||||
|
mode: "now" | "next-heartbeat";
|
||||||
|
}) => void;
|
||||||
dispatchAgentHook: (value: {
|
dispatchAgentHook: (value: {
|
||||||
message: string;
|
message: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -46,13 +56,22 @@ export type HooksRequestHandler = (
|
|||||||
res: ServerResponse,
|
res: ServerResponse,
|
||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
|
|
||||||
export function createHooksRequestHandler(opts: {
|
export function createHooksRequestHandler(
|
||||||
hooksConfig: HooksConfigResolved | null;
|
opts: {
|
||||||
bindHost: string;
|
hooksConfig: HooksConfigResolved | null;
|
||||||
port: number;
|
bindHost: string;
|
||||||
logHooks: SubsystemLogger;
|
port: number;
|
||||||
} & HookDispatchers): HooksRequestHandler {
|
logHooks: SubsystemLogger;
|
||||||
const { hooksConfig, bindHost, port, logHooks, dispatchAgentHook, dispatchWakeHook } = opts;
|
} & HookDispatchers,
|
||||||
|
): HooksRequestHandler {
|
||||||
|
const {
|
||||||
|
hooksConfig,
|
||||||
|
bindHost,
|
||||||
|
port,
|
||||||
|
logHooks,
|
||||||
|
dispatchAgentHook,
|
||||||
|
dispatchWakeHook,
|
||||||
|
} = opts;
|
||||||
return async (req, res) => {
|
return async (req, res) => {
|
||||||
if (!hooksConfig) return false;
|
if (!hooksConfig) return false;
|
||||||
const url = new URL(req.url ?? "/", `http://${bindHost}:${port}`);
|
const url = new URL(req.url ?? "/", `http://${bindHost}:${port}`);
|
||||||
@@ -97,7 +116,9 @@ export function createHooksRequestHandler(opts: {
|
|||||||
const headers = normalizeHookHeaders(req);
|
const headers = normalizeHookHeaders(req);
|
||||||
|
|
||||||
if (subPath === "wake") {
|
if (subPath === "wake") {
|
||||||
const normalized = normalizeWakePayload(payload as Record<string, unknown>);
|
const normalized = normalizeWakePayload(
|
||||||
|
payload as Record<string, unknown>,
|
||||||
|
);
|
||||||
if (!normalized.ok) {
|
if (!normalized.ok) {
|
||||||
sendJson(res, 400, { ok: false, error: normalized.error });
|
sendJson(res, 400, { ok: false, error: normalized.error });
|
||||||
return true;
|
return true;
|
||||||
@@ -108,7 +129,9 @@ export function createHooksRequestHandler(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (subPath === "agent") {
|
if (subPath === "agent") {
|
||||||
const normalized = normalizeAgentPayload(payload as Record<string, unknown>);
|
const normalized = normalizeAgentPayload(
|
||||||
|
payload as Record<string, unknown>,
|
||||||
|
);
|
||||||
if (!normalized.ok) {
|
if (!normalized.ok) {
|
||||||
sendJson(res, 400, { ok: false, error: normalized.error });
|
sendJson(res, 400, { ok: false, error: normalized.error });
|
||||||
return true;
|
return true;
|
||||||
@@ -178,8 +201,12 @@ export function createGatewayHttpServer(opts: {
|
|||||||
controlUiBasePath: string;
|
controlUiBasePath: string;
|
||||||
handleHooksRequest: HooksRequestHandler;
|
handleHooksRequest: HooksRequestHandler;
|
||||||
}): HttpServer {
|
}): HttpServer {
|
||||||
const { canvasHost, controlUiEnabled, controlUiBasePath, handleHooksRequest } =
|
const {
|
||||||
opts;
|
canvasHost,
|
||||||
|
controlUiEnabled,
|
||||||
|
controlUiBasePath,
|
||||||
|
handleHooksRequest,
|
||||||
|
} = opts;
|
||||||
const httpServer: HttpServer = createHttpServer((req, res) => {
|
const httpServer: HttpServer = createHttpServer((req, res) => {
|
||||||
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
|
// Don't interfere with WebSocket upgrades; ws handles the 'upgrade' event.
|
||||||
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
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 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 { monitorDiscordProvider } from "../discord/index.js";
|
||||||
import { probeDiscord } from "../discord/probe.js";
|
import { probeDiscord } from "../discord/probe.js";
|
||||||
|
import { shouldLogVerbose } from "../globals.js";
|
||||||
import { monitorIMessageProvider } from "../imessage/index.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 { monitorSignalProvider } from "../signal/index.js";
|
||||||
import { resolveTelegramToken } from "../telegram/token.js";
|
|
||||||
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
import { monitorTelegramProvider } from "../telegram/monitor.js";
|
||||||
import { probeTelegram } from "../telegram/probe.js";
|
import { probeTelegram } from "../telegram/probe.js";
|
||||||
import { monitorWebProvider, webAuthExists } from "../providers/web/index.js";
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { readWebSelfId } from "../web/session.js";
|
|
||||||
import type { WebProviderStatus } from "../web/auto-reply.js";
|
import type { WebProviderStatus } from "../web/auto-reply.js";
|
||||||
|
import { readWebSelfId } from "../web/session.js";
|
||||||
import { formatError } from "./server-utils.js";
|
import { formatError } from "./server-utils.js";
|
||||||
|
|
||||||
export type TelegramRuntimeStatus = {
|
export type TelegramRuntimeStatus = {
|
||||||
@@ -245,7 +245,9 @@ export function createProviderManager(
|
|||||||
lastError: "disabled",
|
lastError: "disabled",
|
||||||
};
|
};
|
||||||
if (shouldLogVerbose()) {
|
if (shouldLogVerbose()) {
|
||||||
logTelegram.debug("telegram provider disabled (telegram.enabled=false)");
|
logTelegram.debug(
|
||||||
|
"telegram provider disabled (telegram.enabled=false)",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { formatError, normalizeVoiceWakeTriggers } from "./server-utils.js";
|
|||||||
describe("normalizeVoiceWakeTriggers", () => {
|
describe("normalizeVoiceWakeTriggers", () => {
|
||||||
test("returns defaults when input is empty", () => {
|
test("returns defaults when input is empty", () => {
|
||||||
expect(normalizeVoiceWakeTriggers([])).toEqual(defaultVoiceWakeTriggers());
|
expect(normalizeVoiceWakeTriggers([])).toEqual(defaultVoiceWakeTriggers());
|
||||||
expect(normalizeVoiceWakeTriggers(null)).toEqual(defaultVoiceWakeTriggers());
|
expect(normalizeVoiceWakeTriggers(null)).toEqual(
|
||||||
|
defaultVoiceWakeTriggers(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("trims and limits entries", () => {
|
test("trims and limits entries", () => {
|
||||||
@@ -20,8 +22,10 @@ describe("formatError", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("handles status/code", () => {
|
test("handles status/code", () => {
|
||||||
expect(formatError({ status: 500, code: "EPIPE" })).toBe("500 EPIPE");
|
expect(formatError({ status: 500, code: "EPIPE" })).toBe(
|
||||||
expect(formatError({ status: 404 })).toBe("404");
|
"status=500 code=EPIPE",
|
||||||
expect(formatError({ code: "ENOENT" })).toBe("ENOENT");
|
);
|
||||||
|
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({
|
const links = resolveControlUiLinks({
|
||||||
bind,
|
bind,
|
||||||
port,
|
port,
|
||||||
basePath: config.gateway?.controlUi?.basePath,
|
basePath: baseConfig.gateway?.controlUi?.basePath,
|
||||||
});
|
});
|
||||||
const tokenParam =
|
const tokenParam =
|
||||||
authMode === "token" && gatewayToken
|
authMode === "token" && gatewayToken
|
||||||
@@ -530,7 +530,7 @@ export async function runOnboardingWizard(
|
|||||||
const links = resolveControlUiLinks({
|
const links = resolveControlUiLinks({
|
||||||
bind,
|
bind,
|
||||||
port,
|
port,
|
||||||
basePath: config.gateway?.controlUi?.basePath,
|
basePath: baseConfig.gateway?.controlUi?.basePath,
|
||||||
});
|
});
|
||||||
const tokenParam =
|
const tokenParam =
|
||||||
authMode === "token" && gatewayToken
|
authMode === "token" && gatewayToken
|
||||||
|
|||||||
Reference in New Issue
Block a user