mirror of
https://github.com/obra/superpowers.git
synced 2026-04-21 08:59:04 +08:00
Auto-exit server after 30 minutes idle, add liveness check to skill
Server tracks activity (HTTP requests, WebSocket messages, file changes) and exits after 30 minutes of inactivity. On exit, deletes .server-info and writes .server-stopped with reason. Visual companion guide now instructs agents to check .server-info before each screen push and restart if needed. Works on all harnesses, not just CC. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,7 @@ function getNewestScreen() {
|
||||
// ========== HTTP Request Handler ==========
|
||||
|
||||
function handleRequest(req, res) {
|
||||
touchActivity();
|
||||
if (req.method === 'GET' && req.url === '/') {
|
||||
const screenFile = getNewestScreen();
|
||||
let html = screenFile
|
||||
@@ -225,6 +226,7 @@ function handleMessage(text) {
|
||||
console.error('Failed to parse WebSocket message:', e.message);
|
||||
return;
|
||||
}
|
||||
touchActivity();
|
||||
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
||||
if (event.choice) {
|
||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
||||
@@ -239,6 +241,15 @@ function broadcast(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Activity Tracking ==========
|
||||
|
||||
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let lastActivity = Date.now();
|
||||
|
||||
function touchActivity() {
|
||||
lastActivity = Date.now();
|
||||
}
|
||||
|
||||
// ========== File Watching ==========
|
||||
|
||||
const debounceTimers = new Map();
|
||||
@@ -267,6 +278,7 @@ function startServer() {
|
||||
const filePath = path.join(SCREEN_DIR, filename);
|
||||
|
||||
if (!fs.existsSync(filePath)) return; // file was deleted
|
||||
touchActivity();
|
||||
|
||||
if (!knownFiles.has(filename)) {
|
||||
knownFiles.add(filename);
|
||||
@@ -282,6 +294,23 @@ function startServer() {
|
||||
});
|
||||
watcher.on('error', (err) => console.error('fs.watch error:', err.message));
|
||||
|
||||
// Exit if no activity for 30 minutes (prevents orphaned servers)
|
||||
const idleCheck = setInterval(() => {
|
||||
if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) {
|
||||
console.log(JSON.stringify({ type: 'server-stopped', reason: 'idle timeout' }));
|
||||
const infoFile = path.join(SCREEN_DIR, '.server-info');
|
||||
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
||||
fs.writeFileSync(
|
||||
path.join(SCREEN_DIR, '.server-stopped'),
|
||||
JSON.stringify({ reason: 'idle timeout', timestamp: Date.now() }) + '\n'
|
||||
);
|
||||
watcher.close();
|
||||
clearInterval(idleCheck);
|
||||
server.close(() => process.exit(0));
|
||||
}
|
||||
}, 60 * 1000); // check every minute
|
||||
idleCheck.unref(); // don't keep process alive just for the timer
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
const info = JSON.stringify({
|
||||
type: 'server-started', port: Number(PORT), host: HOST,
|
||||
|
||||
Reference in New Issue
Block a user