MAESTRO: Implement /api/session/:id/interrupt POST endpoint for session interruption

Add REST API endpoint to send SIGINT/Ctrl+C signal to sessions via the web interface.
This allows mobile and desktop web clients to gracefully interrupt running AI agents
or terminal processes.

- Add InterruptSessionCallback type for session interrupt operations
- Add setInterruptSessionCallback method to WebServer class
- Create /api/session/:id/interrupt POST endpoint with authentication and rate limiting
- Wire up callback in main process to use ProcessManager.interrupt()
This commit is contained in:
Pedram Amini
2025-11-27 03:04:10 -06:00
parent 90cc71ee49
commit b3f1ba660c
2 changed files with 75 additions and 0 deletions

View File

@@ -339,6 +339,12 @@ app.whenReady().then(() => {
return processManager.write(sessionId, data);
});
// Set up callback for web server to interrupt sessions
webServer.setInterruptSessionCallback((sessionId: string) => {
if (!processManager) return false;
return processManager.interrupt(sessionId);
});
// Initialize session web server manager with callbacks
sessionWebServerManager = new SessionWebServerManager(
// getSessionData callback - fetch session from store

View File

@@ -98,6 +98,10 @@ export type GetSessionDetailCallback = (sessionId: string) => SessionDetail | nu
// Returns true if successful, false if session not found or write failed
export type WriteToSessionCallback = (sessionId: string, data: string) => boolean;
// Callback type for interrupting a session (sending SIGINT/Ctrl+C)
// Returns true if successful, false if session not found or interrupt failed
export type InterruptSessionCallback = (sessionId: string) => boolean;
// Theme type for web clients (matches renderer/types/index.ts)
export interface WebTheme {
id: string;
@@ -142,6 +146,7 @@ export class WebServer {
private getSessionDetailCallback: GetSessionDetailCallback | null = null;
private getThemeCallback: GetThemeCallback | null = null;
private writeToSessionCallback: WriteToSessionCallback | null = null;
private interruptSessionCallback: InterruptSessionCallback | null = null;
constructor(port: number = 8000) {
this.port = port;
@@ -187,6 +192,14 @@ export class WebServer {
this.writeToSessionCallback = callback;
}
/**
* Set the callback function for interrupting a session
* This is called by the /api/session/:id/interrupt endpoint
*/
setInterruptSessionCallback(callback: InterruptSessionCallback) {
this.interruptSessionCallback = callback;
}
/**
* Set the authentication configuration
*/
@@ -477,6 +490,62 @@ export class WebServer {
};
});
// Interrupt session endpoint - sends SIGINT/Ctrl+C to a specific session
// Rate limited using POST rate limit config (more restrictive)
this.server.post('/api/session/:id/interrupt', {
preHandler: this.authenticateRequest.bind(this),
config: {
rateLimit: {
max: this.rateLimitConfig.maxPost,
timeWindow: this.rateLimitConfig.timeWindow,
},
},
}, async (request, reply) => {
const { id } = request.params as { id: string };
// Check if interrupt callback is configured
if (!this.interruptSessionCallback) {
reply.code(503).send({
error: 'Service Unavailable',
message: 'Session interrupt service not configured',
timestamp: Date.now(),
});
return;
}
// Check if session exists first
if (this.getSessionDetailCallback) {
const session = this.getSessionDetailCallback(id);
if (!session) {
reply.code(404).send({
error: 'Not Found',
message: `Session with id '${id}' not found`,
timestamp: Date.now(),
});
return;
}
}
// Send interrupt signal to the session
const success = this.interruptSessionCallback(id);
if (!success) {
reply.code(500).send({
error: 'Internal Server Error',
message: 'Failed to interrupt session',
timestamp: Date.now(),
});
return;
}
return {
success: true,
message: 'Interrupt signal sent successfully',
sessionId: id,
timestamp: Date.now(),
};
});
// Setup web interface routes under /web/* namespace
this.setupWebInterfaceRoutes();
}