feat: add gateway lock, shell completion, and tailscale serve (Tier 4 features 1-3)

This commit is contained in:
William Valentin
2026-02-09 13:29:59 -08:00
parent 9be8f76bc7
commit 4413c4dc7c
12 changed files with 535 additions and 5 deletions
+18
View File
@@ -11,6 +11,7 @@ import type { AuthConfig } from './auth.js';
import {
parseMessage,
makeError,
makeResponse,
ErrorCode,
type OutboundMessage,
} from './protocol.js';
@@ -39,6 +40,8 @@ export interface GatewayServerConfig {
toolExecutor: ToolExecutor;
version?: string;
auth?: AuthConfig;
/** When true, only one WebSocket client can be connected at a time. */
lock?: boolean;
/** Whether to apply token auth to HTTP requests too (default: true when token is set). */
authHttp?: boolean;
uiDir?: string;
@@ -159,6 +162,15 @@ export class GatewayServer {
this.handleConnection(ws, authResult.identity);
});
// Register system.lock handler (needs access to connectionMap)
this.router.register('system.lock', async (request) => {
return makeResponse(request.id, {
locked: this.config.lock ?? false,
activeClients: this.connectionMap.size,
maxClients: this.config.lock ? 1 : null,
});
});
this.httpServer.listen(port, host, () => {
console.log(`Gateway server listening on ${host}:${port}`);
resolve();
@@ -199,6 +211,12 @@ export class GatewayServer {
}
private handleConnection(ws: WebSocket, identity?: string): void {
// Gateway lock — reject if another client is already connected
if (this.config.lock && this.connectionMap.size > 0) {
ws.close(4003, 'Gateway locked — another client is already connected');
return;
}
const connectionId = randomUUID();
this.sessionBridge.connect(connectionId);
this.connectionMap.set(ws, connectionId);