mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: remove macOS caffeinate sleep prevention functionality
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user