From f1dba7276def91d58009f028eef4e0111701d96f Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Tue, 27 Jan 2026 09:09:07 -0800 Subject: [PATCH 1/2] feat: /shutdown API --- apps/server/src/api/server.ts | 7 ++++++- apps/server/src/api/types.ts | 3 +++ apps/server/src/main.ts | 1 + bun.lock | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/server/src/api/server.ts b/apps/server/src/api/server.ts index 85018447..31c03696 100644 --- a/apps/server/src/api/server.ts +++ b/apps/server/src/api/server.ts @@ -24,6 +24,7 @@ import { createKlavisRoutes } from './routes/klavis' import { createMcpRoutes } from './routes/mcp' import { createProviderRoutes } from './routes/provider' import { createSdkRoutes } from './routes/sdk' +import { createShutdownRoute } from './routes/shutdown' import type { Env, HttpServerConfig } from './types' import { defaultCorsConfig } from './utils/cors' @@ -49,12 +50,16 @@ export async function createHttpServer(config: HttpServerConfig) { allowRemote, } = config - const { healthWatchdog } = config + const { healthWatchdog, onShutdown } = config // DECLARATIVE route composition - chain .route() calls for type inference const app = new Hono() .use('/*', cors(defaultCorsConfig)) .route('/health', createHealthRoute({ watchdog: healthWatchdog })) + .route( + '/shutdown', + createShutdownRoute({ onShutdown: onShutdown ?? (() => {}) }), + ) .route( '/extension-status', createExtensionStatusRoute({ controllerContext }), diff --git a/apps/server/src/api/types.ts b/apps/server/src/api/types.ts index ab70ac9b..d4dc5781 100644 --- a/apps/server/src/api/types.ts +++ b/apps/server/src/api/types.ts @@ -84,6 +84,9 @@ export interface HttpServerConfig { // For health monitoring healthWatchdog?: HealthWatchdog + + // For shutdown route + onShutdown?: () => void } // Graph request schemas diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 219a2e8f..59d4edc0 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -93,6 +93,7 @@ export class Application { rateLimiter: new RateLimiter(this.getDb(), dailyRateLimit), codegenServiceUrl: this.config.codegenServiceUrl, healthWatchdog: this.healthWatchdog ?? undefined, + onShutdown: () => this.stop(), }) } catch (error) { this.handleStartupError('HTTP server', this.config.serverPort, error) diff --git a/bun.lock b/bun.lock index 7f6a1a8d..3ba7f246 100644 --- a/bun.lock +++ b/bun.lock @@ -1516,7 +1516,7 @@ "chroma-js": ["chroma-js@3.2.0", "", {}, "sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw=="], - "chrome-devtools-mcp": ["chrome-devtools-mcp@0.13.0", "", { "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-CgotJczVYe6wG2b5cqwNFq7n4VXIM8qfvfutdzVlABPKf0b99b0TDPuRsWrviCthigSHzhpMyFQW03P5Utt1Fg=="], + "chrome-devtools-mcp": ["chrome-devtools-mcp@0.14.0", "", { "bin": { "chrome-devtools-mcp": "build/src/index.js" } }, "sha512-JsnA8tApxOZHAUwduMsGFk0Mc3aQF0MX58fo9LoPxJFkyKdq34QonGPGNG38rWXJVQ2X70eI8wosJbOrXN79dQ=="], "chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="], From 2169b429f17fadf39873178c64fa04a8a0744cda Mon Sep 17 00:00:00 2001 From: Nikhil Sonti Date: Tue, 27 Jan 2026 09:15:31 -0800 Subject: [PATCH 2/2] fix: rename extension status to status --- apps/server/src/api/routes/shutdown.ts | 18 ++++++++++++++++++ .../routes/{extension-status.ts => status.ts} | 4 ++-- apps/server/src/api/server.ts | 7 ++----- apps/server/tests/__helpers__/setup.ts | 2 +- apps/server/tests/server.integration.test.ts | 4 ++-- 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 apps/server/src/api/routes/shutdown.ts rename apps/server/src/api/routes/{extension-status.ts => status.ts} (80%) diff --git a/apps/server/src/api/routes/shutdown.ts b/apps/server/src/api/routes/shutdown.ts new file mode 100644 index 00000000..9a4c7ca3 --- /dev/null +++ b/apps/server/src/api/routes/shutdown.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2025 BrowserOS + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { Hono } from 'hono' + +interface ShutdownRouteConfig { + onShutdown: () => void +} + +export function createShutdownRoute(config: ShutdownRouteConfig) { + return new Hono().post('/', (c) => { + setImmediate(config.onShutdown) + return c.json({ status: 'ok' }) + }) +} diff --git a/apps/server/src/api/routes/extension-status.ts b/apps/server/src/api/routes/status.ts similarity index 80% rename from apps/server/src/api/routes/extension-status.ts rename to apps/server/src/api/routes/status.ts index 01a8a89c..84a66403 100644 --- a/apps/server/src/api/routes/extension-status.ts +++ b/apps/server/src/api/routes/status.ts @@ -7,11 +7,11 @@ import { Hono } from 'hono' import type { ControllerContext } from '../../browser/extension/context' -interface ExtensionStatusDeps { +interface StatusDeps { controllerContext: ControllerContext } -export function createExtensionStatusRoute(deps: ExtensionStatusDeps) { +export function createStatusRoute(deps: StatusDeps) { const { controllerContext } = deps return new Hono().get('/', (c) => diff --git a/apps/server/src/api/server.ts b/apps/server/src/api/server.ts index 31c03696..75b1f74d 100644 --- a/apps/server/src/api/server.ts +++ b/apps/server/src/api/server.ts @@ -17,7 +17,6 @@ import { HttpAgentError } from '../agent/errors' import { logger } from '../lib/logger' import { bindPortWithRetry } from '../lib/port-binding' import { createChatRoutes } from './routes/chat' -import { createExtensionStatusRoute } from './routes/extension-status' import { createGraphRoutes } from './routes/graph' import { createHealthRoute } from './routes/health' import { createKlavisRoutes } from './routes/klavis' @@ -25,6 +24,7 @@ import { createMcpRoutes } from './routes/mcp' import { createProviderRoutes } from './routes/provider' import { createSdkRoutes } from './routes/sdk' import { createShutdownRoute } from './routes/shutdown' +import { createStatusRoute } from './routes/status' import type { Env, HttpServerConfig } from './types' import { defaultCorsConfig } from './utils/cors' @@ -60,10 +60,7 @@ export async function createHttpServer(config: HttpServerConfig) { '/shutdown', createShutdownRoute({ onShutdown: onShutdown ?? (() => {}) }), ) - .route( - '/extension-status', - createExtensionStatusRoute({ controllerContext }), - ) + .route('/status', createStatusRoute({ controllerContext })) .route('/test-provider', createProviderRoutes()) .route('/klavis', createKlavisRoutes({ browserosId: browserosId || '' })) .route( diff --git a/apps/server/tests/__helpers__/setup.ts b/apps/server/tests/__helpers__/setup.ts index 93965ca4..81b5e7af 100644 --- a/apps/server/tests/__helpers__/setup.ts +++ b/apps/server/tests/__helpers__/setup.ts @@ -44,7 +44,7 @@ const DEFAULT_BINARY_PATH = async function isExtensionConnected(port: number): Promise { try { - const response = await fetch(`http://127.0.0.1:${port}/extension-status`, { + const response = await fetch(`http://127.0.0.1:${port}/status`, { signal: AbortSignal.timeout(1000), }) if (response.ok) { diff --git a/apps/server/tests/server.integration.test.ts b/apps/server/tests/server.integration.test.ts index dc00a4b8..035aa198 100644 --- a/apps/server/tests/server.integration.test.ts +++ b/apps/server/tests/server.integration.test.ts @@ -68,9 +68,9 @@ describe('HTTP Server Integration Tests', () => { }) }) - describe('Extension status endpoint', () => { + describe('Status endpoint', () => { it('reports extension as connected', async () => { - const response = await fetch(`${getBaseUrl()}/extension-status`) + const response = await fetch(`${getBaseUrl()}/status`) assert.strictEqual(response.status, 200) const json = (await response.json()) as {