Skip to content

Commit 3c84ce2

Browse files
committed
feat(settings): warn that keep-awake cannot survive a closed laptop lid
The "Keep awake while agents work" power-save blocker only prevents idle system sleep; closing a laptop lid forces sleep on macOS and Windows regardless. Detect a built-in battery (macOS ioreg, Windows Win32_Battery, Linux /sys/class/power_supply) and append a lid-close caveat to the setting description on laptops. Generated-By: PostHog Code Task-Id: 6b1cbbfe-903b-400d-a76f-45e614c6d0f8
1 parent 889554a commit 3c84ce2

8 files changed

Lines changed: 63 additions & 1 deletion

File tree

apps/code/src/main/platform-adapters/electron-power-manager.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
import { execFile } from "node:child_process";
2+
import { readdir } from "node:fs/promises";
3+
import { promisify } from "node:util";
14
import type { IPowerManager } from "@posthog/platform/power-manager";
25
import { powerMonitor, powerSaveBlocker } from "electron";
36
import { injectable } from "inversify";
47

8+
const execFileAsync = promisify(execFile);
9+
510
@injectable()
611
export class ElectronPowerManager implements IPowerManager {
12+
private hasBuiltInBatteryResult: Promise<boolean> | null = null;
13+
714
public onResume(handler: () => void): () => void {
815
powerMonitor.on("resume", handler);
916
return () => powerMonitor.off("resume", handler);
@@ -17,4 +24,38 @@ export class ElectronPowerManager implements IPowerManager {
1724
}
1825
};
1926
}
27+
28+
public hasBuiltInBattery(): Promise<boolean> {
29+
if (!this.hasBuiltInBatteryResult) {
30+
this.hasBuiltInBatteryResult = detectBuiltInBattery().catch(() => false);
31+
}
32+
return this.hasBuiltInBatteryResult;
33+
}
34+
}
35+
36+
async function detectBuiltInBattery(): Promise<boolean> {
37+
switch (process.platform) {
38+
case "darwin": {
39+
const { stdout } = await execFileAsync("ioreg", [
40+
"-rc",
41+
"AppleSmartBattery",
42+
]);
43+
return stdout.includes("AppleSmartBattery");
44+
}
45+
case "win32": {
46+
const { stdout } = await execFileAsync("powershell", [
47+
"-NoProfile",
48+
"-NonInteractive",
49+
"-Command",
50+
"[bool](Get-CimInstance -ClassName Win32_Battery)",
51+
]);
52+
return stdout.trim().toLowerCase() === "true";
53+
}
54+
case "linux": {
55+
const supplies = await readdir("/sys/class/power_supply");
56+
return supplies.some((name) => name.startsWith("BAT"));
57+
}
58+
default:
59+
return false;
60+
}
2061
}

packages/core/src/auth/auth.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ vi.mock("@posthog/shared", async (importOriginal) => {
2727
const mockPowerManager = vi.hoisted(() => ({
2828
onResume: vi.fn(() => () => {}),
2929
preventSleep: vi.fn(() => () => {}),
30+
hasBuiltInBattery: vi.fn(async () => false),
3031
}));
3132

3233
function createSessionPort(): IAuthSessionStore {

packages/core/src/sleep/sleep.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ function createDeps(preventSleepInitially = true) {
1818
const powerManager: IPowerManager = {
1919
onResume: vi.fn(() => () => {}),
2020
preventSleep: vi.fn(() => release),
21+
hasBuiltInBattery: vi.fn(async () => false),
2122
};
2223

2324
let stored = preventSleepInitially;

packages/core/src/sleep/sleep.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class SleepService {
4343
return this.enabled;
4444
}
4545

46+
hasBuiltInBattery(): Promise<boolean> {
47+
return this.powerManager.hasBuiltInBattery();
48+
}
49+
4650
acquire(activityId: string): void {
4751
this.activeActivities.add(activityId);
4852
this.updateBlocker();

packages/host-router/src/routers/sleep.router.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ export const sleepRouter = router({
1515
.mutation(({ ctx, input }) => {
1616
ctx.container.get<SleepService>(SLEEP_SERVICE).setEnabled(input.enabled);
1717
}),
18+
19+
hasBuiltInBattery: publicProcedure
20+
.output(z.boolean())
21+
.query(({ ctx }) =>
22+
ctx.container.get<SleepService>(SLEEP_SERVICE).hasBuiltInBattery(),
23+
),
1824
});

packages/platform/src/power-manager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface IPowerManager {
22
onResume(handler: () => void): () => void;
33
preventSleep(reason: string): () => void;
4+
hasBuiltInBattery(): Promise<boolean>;
45
}
56

67
export const POWER_MANAGER_SERVICE = Symbol.for(

packages/ui/src/features/settings/sections/GeneralSettings.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export function GeneralSettings() {
4040
const { data: serverPreventSleep } = useQuery(
4141
hostTRPC.sleep.getEnabled.queryOptions(),
4242
);
43+
const { data: hasBuiltInBattery } = useQuery(
44+
hostTRPC.sleep.hasBuiltInBattery.queryOptions(),
45+
);
4346
const preventSleepMutation = useMutation(
4447
hostTRPC.sleep.setEnabled.mutationOptions(),
4548
);
@@ -434,7 +437,11 @@ export function GeneralSettings() {
434437

435438
<SettingRow
436439
label="Keep awake while agents work"
437-
description="Prevent your computer from sleeping while the agent is running a task"
440+
description={
441+
hasBuiltInBattery
442+
? "Prevent your computer from going to sleep on its own while the agent is running a task. Closing the lid will still put it to sleep."
443+
: "Prevent your computer from going to sleep on its own while the agent is running a task"
444+
}
438445
noBorder
439446
>
440447
<Switch

packages/workspace-server/src/services/agent/agent.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ function createMockDependencies() {
157157
powerManager: {
158158
onResume: vi.fn(() => () => {}),
159159
preventSleep: vi.fn(() => () => {}),
160+
hasBuiltInBattery: vi.fn(async () => false),
160161
},
161162
bundledResources: {
162163
resolve: vi.fn((rel: string) => `/mock/appPath/${rel}`),

0 commit comments

Comments
 (0)