mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-07 06:52:28 +00:00
init
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* HTTP control server for daemon management
|
||||
* Provides endpoints for listing sessions, stopping sessions, and daemon shutdown
|
||||
*/
|
||||
|
||||
import fastify, { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||
import { logger } from '@/ui/logger';
|
||||
import { Metadata } from '@/api/types';
|
||||
import { TrackedSession } from './types';
|
||||
import { SpawnSessionOptions, SpawnSessionResult } from '@/modules/common/registerCommonHandlers';
|
||||
|
||||
export function startDaemonControlServer({
|
||||
getChildren,
|
||||
stopSession,
|
||||
spawnSession,
|
||||
requestShutdown,
|
||||
onHappySessionWebhook
|
||||
}: {
|
||||
getChildren: () => TrackedSession[];
|
||||
stopSession: (sessionId: string) => boolean;
|
||||
spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
|
||||
requestShutdown: () => void;
|
||||
onHappySessionWebhook: (sessionId: string, metadata: Metadata) => void;
|
||||
}): Promise<{ port: number; stop: () => Promise<void> }> {
|
||||
return new Promise((resolve) => {
|
||||
const app = fastify({
|
||||
logger: false // We use our own logger
|
||||
});
|
||||
|
||||
// Set up Zod type provider
|
||||
app.setValidatorCompiler(validatorCompiler);
|
||||
app.setSerializerCompiler(serializerCompiler);
|
||||
const typed = app.withTypeProvider<ZodTypeProvider>();
|
||||
|
||||
// Session reports itself after creation
|
||||
typed.post('/session-started', {
|
||||
schema: {
|
||||
body: z.object({
|
||||
sessionId: z.string(),
|
||||
metadata: z.any() // Metadata type from API
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
status: z.literal('ok')
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async (request) => {
|
||||
const { sessionId, metadata } = request.body;
|
||||
|
||||
logger.debug(`[CONTROL SERVER] Session started: ${sessionId}`);
|
||||
onHappySessionWebhook(sessionId, metadata);
|
||||
|
||||
return { status: 'ok' as const };
|
||||
});
|
||||
|
||||
// List all tracked sessions
|
||||
typed.post('/list', {
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
children: z.array(z.object({
|
||||
startedBy: z.string(),
|
||||
happySessionId: z.string(),
|
||||
pid: z.number()
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async () => {
|
||||
const children = getChildren();
|
||||
logger.debug(`[CONTROL SERVER] Listing ${children.length} sessions`);
|
||||
return {
|
||||
children: children
|
||||
.filter(child => child.happySessionId !== undefined)
|
||||
.map(child => ({
|
||||
startedBy: child.startedBy,
|
||||
happySessionId: child.happySessionId!,
|
||||
pid: child.pid
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
// Stop specific session
|
||||
typed.post('/stop-session', {
|
||||
schema: {
|
||||
body: z.object({
|
||||
sessionId: z.string()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
success: z.boolean()
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async (request) => {
|
||||
const { sessionId } = request.body;
|
||||
|
||||
logger.debug(`[CONTROL SERVER] Stop session request: ${sessionId}`);
|
||||
const success = stopSession(sessionId);
|
||||
return { success };
|
||||
});
|
||||
|
||||
// Spawn new session
|
||||
typed.post('/spawn-session', {
|
||||
schema: {
|
||||
body: z.object({
|
||||
directory: z.string(),
|
||||
sessionId: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
success: z.boolean(),
|
||||
sessionId: z.string().optional(),
|
||||
approvedNewDirectoryCreation: z.boolean().optional()
|
||||
}),
|
||||
409: z.object({
|
||||
success: z.boolean(),
|
||||
requiresUserApproval: z.boolean().optional(),
|
||||
actionRequired: z.string().optional(),
|
||||
directory: z.string().optional()
|
||||
}),
|
||||
500: z.object({
|
||||
success: z.boolean(),
|
||||
error: z.string().optional()
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async (request, reply) => {
|
||||
const { directory, sessionId } = request.body;
|
||||
|
||||
logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || 'new'}`);
|
||||
const result = await spawnSession({ directory, sessionId });
|
||||
|
||||
switch (result.type) {
|
||||
case 'success':
|
||||
// Check if sessionId exists, if not return error
|
||||
if (!result.sessionId) {
|
||||
reply.code(500);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to spawn session: no session ID returned'
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
sessionId: result.sessionId,
|
||||
approvedNewDirectoryCreation: true
|
||||
};
|
||||
|
||||
case 'requestToApproveDirectoryCreation':
|
||||
reply.code(409); // Conflict - user input needed
|
||||
return {
|
||||
success: false,
|
||||
requiresUserApproval: true,
|
||||
actionRequired: 'CREATE_DIRECTORY',
|
||||
directory: result.directory
|
||||
};
|
||||
|
||||
case 'error':
|
||||
reply.code(500);
|
||||
return {
|
||||
success: false,
|
||||
error: result.errorMessage
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Stop daemon
|
||||
typed.post('/stop', {
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
status: z.string()
|
||||
})
|
||||
}
|
||||
}
|
||||
}, async () => {
|
||||
logger.debug('[CONTROL SERVER] Stop daemon request received');
|
||||
|
||||
// Give time for response to arrive
|
||||
setTimeout(() => {
|
||||
logger.debug('[CONTROL SERVER] Triggering daemon shutdown');
|
||||
requestShutdown();
|
||||
}, 50);
|
||||
|
||||
return { status: 'stopping' };
|
||||
});
|
||||
|
||||
app.listen({ port: 0, host: '127.0.0.1' }, (err, address) => {
|
||||
if (err) {
|
||||
logger.debug('[CONTROL SERVER] Failed to start:', err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const port = parseInt(address.split(':').pop()!);
|
||||
logger.debug(`[CONTROL SERVER] Started on port ${port}`);
|
||||
|
||||
resolve({
|
||||
port,
|
||||
stop: async () => {
|
||||
logger.debug('[CONTROL SERVER] Stopping server');
|
||||
await app.close();
|
||||
logger.debug('[CONTROL SERVER] Server stopped');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user