Unify TUI runtime commands with gateway and harden gateway restart
This commit is contained in:
+71
-11
@@ -24,7 +24,7 @@ import { assembleSystemPrompt } from '../prompt/index.js';
|
||||
import { join, relative, resolve, sep } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import type { MemoryStore } from '../memory/store.js';
|
||||
import type { CommandRegistry } from '../commands/index.js';
|
||||
import type { CommandRegistry, RuntimeBackendMode } from '../commands/index.js';
|
||||
import type { ComponentRegistry } from '../intents/index.js';
|
||||
import type { RoutingPolicy } from '../routing/index.js';
|
||||
import type { HookEngine } from '../hooks/index.js';
|
||||
@@ -293,6 +293,8 @@ export interface GatewayDeps {
|
||||
getChannelAgents: () => Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }> | null;
|
||||
memoryStore?: MemoryStore;
|
||||
commandRegistry?: CommandRegistry;
|
||||
getBackendMode?: () => RuntimeBackendMode;
|
||||
setBackendMode?: (mode: RuntimeBackendMode) => void;
|
||||
intentRegistry?: ComponentRegistry;
|
||||
routingPolicy?: RoutingPolicy;
|
||||
hookEngine?: HookEngine;
|
||||
@@ -388,6 +390,8 @@ export function createGateway(deps: GatewayDeps): GatewayServer {
|
||||
channelRegistry,
|
||||
pairingManager,
|
||||
memoryStore: deps.memoryStore,
|
||||
getBackendMode: deps.getBackendMode,
|
||||
setBackendMode: deps.setBackendMode,
|
||||
restart: async () => {
|
||||
console.log('Restart requested via gateway');
|
||||
await lifecycle.shutdown();
|
||||
@@ -472,25 +476,31 @@ export async function startServices(deps: {
|
||||
memoryStore?: MemoryStore;
|
||||
memoryDir: string;
|
||||
dataDir: string;
|
||||
gatewayStartRetry?: {
|
||||
maxAttempts?: number;
|
||||
retryDelayMs?: number;
|
||||
sleep?: (ms: number) => Promise<void>;
|
||||
};
|
||||
}): Promise<void> {
|
||||
const { config, lifecycle, channelRegistry, gateway, modelRouter, memoryStore, memoryDir, dataDir } = deps;
|
||||
|
||||
// Register shutdown handler for channels
|
||||
lifecycle.onShutdown(async () => {
|
||||
await channelRegistry.stopAll();
|
||||
console.log('Channel adapters stopped');
|
||||
});
|
||||
|
||||
// Start all channel adapters
|
||||
await channelRegistry.startAll();
|
||||
|
||||
// Start gateway (HTTP + WS server)
|
||||
lifecycle.onShutdown(async () => {
|
||||
await gateway.stop();
|
||||
console.log('Gateway server stopped');
|
||||
});
|
||||
|
||||
await gateway.start();
|
||||
const host = config.server.localhost ? '127.0.0.1' : '0.0.0.0';
|
||||
await startGatewayWithRetry(gateway, host, config.server.port, deps.gatewayStartRetry);
|
||||
|
||||
// Register shutdown handler for channels
|
||||
lifecycle.onShutdown(async () => {
|
||||
await channelRegistry.stopAll();
|
||||
console.log('Channel adapters stopped');
|
||||
});
|
||||
|
||||
// Start all channel adapters after gateway bind succeeds.
|
||||
await channelRegistry.startAll();
|
||||
|
||||
// Tailscale Serve
|
||||
if (config.server.tailscale?.serve) {
|
||||
@@ -589,3 +599,53 @@ export async function startServices(deps: {
|
||||
|
||||
console.log('Flynn daemon started');
|
||||
}
|
||||
|
||||
function isAddressInUseError(error: unknown): error is NodeJS.ErrnoException {
|
||||
return (
|
||||
typeof error === 'object'
|
||||
&& error !== null
|
||||
&& 'code' in error
|
||||
&& (error as NodeJS.ErrnoException).code === 'EADDRINUSE'
|
||||
);
|
||||
}
|
||||
|
||||
async function startGatewayWithRetry(
|
||||
gateway: Pick<GatewayServer, 'start' | 'stop'>,
|
||||
host: string,
|
||||
port: number,
|
||||
retry?: {
|
||||
maxAttempts?: number;
|
||||
retryDelayMs?: number;
|
||||
sleep?: (ms: number) => Promise<void>;
|
||||
},
|
||||
): Promise<void> {
|
||||
const maxAttempts = Math.max(1, retry?.maxAttempts ?? 10);
|
||||
const retryDelayMs = Math.max(0, retry?.retryDelayMs ?? 500);
|
||||
const sleep = retry?.sleep ?? ((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
try {
|
||||
await gateway.start();
|
||||
return;
|
||||
} catch (error) {
|
||||
if (!isAddressInUseError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await gateway.stop().catch(() => {});
|
||||
|
||||
if (attempt === maxAttempts) {
|
||||
throw new Error(
|
||||
`Gateway bind failed: ${host}:${port} is already in use after ${maxAttempts} attempts. `
|
||||
+ 'Another Flynn daemon or process is already listening on this port.',
|
||||
);
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`Gateway bind collision on ${host}:${port} (attempt ${attempt}/${maxAttempts}); `
|
||||
+ `retrying in ${retryDelayMs}ms...`,
|
||||
);
|
||||
await sleep(retryDelayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user