refactor: remove macOS caffeinate sleep prevention functionality

This commit is contained in:
weishu
2025-12-17 15:59:50 +08:00
parent 502b63c236
commit a696cafdd4
7 changed files with 0 additions and 172 deletions
-1
View File
@@ -44,7 +44,6 @@ This will:
- `HAPPY_BOT_URL` - Bot URL (default: http://localhost:3006)
- `CLI_API_TOKEN` - Shared secret for bot authentication (required)
- `HAPPY_HOME_DIR` - Custom home directory for hapi data (default: ~/.happy)
- `HAPPY_DISABLE_CAFFEINATE` - Disable macOS sleep prevention (set to `true`, `1`, or `yes`)
- `HAPPY_EXPERIMENTAL` - Enable experimental features (set to `true`, `1`, or `yes`)
## Requirements
-14
View File
@@ -10,7 +10,6 @@ import { readSettings } from '@/persistence';
import { EnhancedMode, PermissionMode } from './loop';
import { MessageQueue2 } from '@/utils/MessageQueue2';
import { hashObject } from '@/utils/deterministicJson';
import { startCaffeinate, stopCaffeinate } from '@/utils/caffeinate';
import { extractSDKMetadataAsync } from '@/claude/sdk/metadataExtractor';
import { parseSpecialCommand } from '@/parsers/specialCommands';
import { getEnvironmentInfo } from '@/ui/doctor';
@@ -137,12 +136,6 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
controlledByUser: options.startingMode !== 'remote'
}));
// Start caffeinate to prevent sleep on macOS
const caffeinateStarted = startCaffeinate();
if (caffeinateStarted) {
logger.infoDeveloper('Sleep prevention enabled (macOS)');
}
// Import MessageQueue2 and create message queue
const messageQueue = new MessageQueue2<EnhancedMode>(mode => hashObject({
isPlan: mode.permissionMode === 'plan',
@@ -310,9 +303,6 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
await session.close();
}
// Stop caffeinate
stopCaffeinate();
// Stop Happy MCP server
happyServer.stop();
@@ -382,10 +372,6 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
logger.debug('Closing session...');
await session.close();
// Stop caffeinate before exiting
stopCaffeinate();
logger.debug('Stopped sleep prevention');
// Stop Happy MCP server
happyServer.stop();
logger.debug('Stopped Happy MCP server');
-4
View File
@@ -26,7 +26,6 @@ import type { CodexSessionConfig } from './types';
import { notifyDaemonSessionStarted } from "@/daemon/controlClient";
import { registerKillSessionHandler } from "@/claude/registerKillSessionHandler";
import { delay } from "@/utils/time";
import { stopCaffeinate } from "@/utils/caffeinate";
type ReadyEventOptions = {
pending: unknown;
@@ -271,9 +270,6 @@ export async function runCodex(opts: {
await session.close();
}
// Stop caffeinate
stopCaffeinate();
// Stop Happy MCP server
happyServer.stop();
-2
View File
@@ -25,7 +25,6 @@ class Configuration {
public readonly currentCliVersion: string
public readonly isExperimentalEnabled: boolean
public readonly disableCaffeinate: boolean
constructor() {
// Bot server configuration
@@ -52,7 +51,6 @@ class Configuration {
this.daemonLockFile = join(this.happyHomeDir, 'daemon.state.json.lock')
this.isExperimentalEnabled = ['true', '1', 'yes'].includes(process.env.HAPPY_EXPERIMENTAL?.toLowerCase() || '')
this.disableCaffeinate = ['true', '1', 'yes'].includes(process.env.HAPPY_DISABLE_CAFFEINATE?.toLowerCase() || '')
this.currentCliVersion = packageJson.version
-3
View File
@@ -173,9 +173,6 @@ I do not like how
- we loose track of children processes when daemon exits / restarts - we should write them to the same state file? At least the pids should be there for doctor & cleanup
- caffeinate process is not tracked in state at all & might become runaway
- caffeinate is also started by individual sesions - we should not do that for simpler cleanup
- the daemon control server binds to `127.0.0.1` on a random port; if we ever expose it beyond localhost, require an explicit auth token/header
-8
View File
@@ -9,7 +9,6 @@ import { SpawnSessionOptions, SpawnSessionResult } from '@/modules/common/regist
import { logger } from '@/ui/logger';
import { authAndSetupMachineIfNeeded } from '@/ui/auth';
import { configuration } from '@/configuration';
import { startCaffeinate, stopCaffeinate } from '@/utils/caffeinate';
import packageJson from '../../package.json';
import { getEnvironmentInfo } from '@/ui/doctor';
import { spawnHappyCLI } from '@/utils/spawnHappyCLI';
@@ -121,12 +120,6 @@ export async function startDaemon(): Promise<void> {
// 2. Should not have another daemon process running
try {
// Start caffeinate
const caffeinateStarted = startCaffeinate();
if (caffeinateStarted) {
logger.debug('[DAEMON RUN] Sleep prevention enabled');
}
// Ensure auth and machine registration BEFORE anything else
const { machineId } = await authAndSetupMachineIfNeeded();
logger.debug('[DAEMON RUN] Auth and machine setup complete');
@@ -562,7 +555,6 @@ export async function startDaemon(): Promise<void> {
apiMachine.shutdown();
await stopControlServer();
await cleanupDaemonState();
await stopCaffeinate();
await releaseDaemonLock(daemonLockHandle);
logger.debug('[DAEMON RUN] Cleanup completed, exiting process');
-140
View File
@@ -1,140 +0,0 @@
/**
* Caffeinate utility for preventing macOS from sleeping
* Uses the built-in macOS caffeinate command to keep the system awake
*/
import { spawn, ChildProcess } from 'child_process'
import { logger } from '@/ui/logger'
import { configuration } from '@/configuration'
let caffeinateProcess: ChildProcess | null = null
/**
* Start caffeinate to prevent system sleep
* Only works on macOS, silently does nothing on other platforms
*
* @returns true if caffeinate was started, false otherwise
*/
export function startCaffeinate(): boolean {
// Check if caffeinate is disabled via configuration
if (configuration.disableCaffeinate) {
logger.debug('[caffeinate] Caffeinate disabled via HAPPY_DISABLE_CAFFEINATE environment variable')
return false
}
// Only run on macOS
if (process.platform !== 'darwin') {
logger.debug('[caffeinate] Not on macOS, skipping caffeinate')
return false
}
// Don't start if already running
if (caffeinateProcess && !caffeinateProcess.killed) {
logger.debug('[caffeinate] Caffeinate already running')
return true
}
try {
// Spawn caffeinate with flags:
// -i: Prevent system from idle sleeping
// -m: Prevent disk from sleeping
caffeinateProcess = spawn('caffeinate', ['-im'], {
stdio: 'ignore',
detached: false
})
caffeinateProcess.on('error', (error) => {
logger.debug('[caffeinate] Error starting caffeinate:', error)
caffeinateProcess = null
})
caffeinateProcess.on('exit', (code, signal) => {
logger.debug(`[caffeinate] Process exited with code ${code}, signal ${signal}`)
caffeinateProcess = null
})
logger.debug(`[caffeinate] Started with PID ${caffeinateProcess.pid}`)
// Set up cleanup handlers
setupCleanupHandlers()
return true
} catch (error) {
logger.debug('[caffeinate] Failed to start caffeinate:', error)
return false
}
}
let isStopping = false
/**
* Stop the caffeinate process
*/
export async function stopCaffeinate(): Promise<void> {
// Prevent re-entrant calls during cleanup
if (isStopping) {
logger.debug('[caffeinate] Already stopping, skipping')
return
}
if (caffeinateProcess && !caffeinateProcess.killed) {
isStopping = true
logger.debug(`[caffeinate] Stopping caffeinate process PID ${caffeinateProcess.pid}`)
try {
caffeinateProcess.kill('SIGTERM')
// Give it a moment to terminate gracefully
await new Promise(resolve => setTimeout(resolve, 1000))
if (caffeinateProcess && !caffeinateProcess.killed) {
logger.debug('[caffeinate] Force killing caffeinate process')
caffeinateProcess.kill('SIGKILL')
}
caffeinateProcess = null
isStopping = false
} catch (error) {
logger.debug('[caffeinate] Error stopping caffeinate:', error)
isStopping = false
}
}
}
/**
* Check if caffeinate is currently running
*/
export function isCaffeinateRunning(): boolean {
return caffeinateProcess !== null && !caffeinateProcess.killed
}
/**
* Set up cleanup handlers to ensure caffeinate is stopped on exit
*/
let cleanupHandlersSet = false
function setupCleanupHandlers(): void {
if (cleanupHandlersSet) {
return
}
cleanupHandlersSet = true
// Clean up on various exit conditions
const cleanup = () => {
stopCaffeinate()
}
process.on('exit', cleanup)
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
process.on('SIGUSR1', cleanup)
process.on('SIGUSR2', cleanup)
process.on('uncaughtException', (error) => {
logger.debug('[caffeinate] Uncaught exception, cleaning up:', error)
cleanup()
})
process.on('unhandledRejection', (reason, promise) => {
logger.debug('[caffeinate] Unhandled rejection, cleaning up:', reason)
cleanup()
})
}