humanish 0.131.0 → 0.131.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,17 @@ The Unreleased section holds the full notes for the next version until it is tag
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.131.1: Hosted desktops keep their screens on while participants wait (2026-10-13)
12
+
13
+ humanish 0.131.1 keeps a hosted desktop's screen on while a participant waits without input.
14
+ About 1 in 8 stock E2B desktops started with a screen saver that turned the screen black after 5
15
+ minutes, and a participant then spent its turns keeping the screen awake: one live hour cost
16
+ $17.70 where its pair cost $2.03. humanish now turns the screen saver off once the participant's
17
+ browser or terminal has opened. The refusal of an out-of-range `actor.maxWaitMs` also now says the
18
+ field caps a turn of only waits.
19
+
20
+ [Release notes](https://github.com/danielgwilson/humanish/releases/tag/v0.131.1)
21
+
11
22
  ## 0.131.0: Participants that wait watch the screen and wake when it changes (2026-10-13)
12
23
 
13
24
  humanish 0.131.0 watches the screen while a computer-use or shared-world participant waits. A
@@ -68,7 +68,7 @@ function defaultMaxWaitMs(speechEnabled) {
68
68
  return speechEnabled ? CUA_WAIT_LIMITS.stepMs : CUA_WAIT_LIMITS.defaultMaxMs;
69
69
  }
70
70
  const FIELDS = [
71
- ["maxWaitMs", "the longest one wait action lasts", CUA_WAIT_LIMITS.mostMaxMs],
71
+ ["maxWaitMs", "the longest one wait or one turn of only waits lasts", CUA_WAIT_LIMITS.mostMaxMs],
72
72
  [
73
73
  "idleWaitMs",
74
74
  "how long a wait with no duration lasts while the participant only waits",
@@ -87,7 +87,7 @@ export function waitFieldsReason(fields, prefix, maxPrefix = prefix) {
87
87
  }
88
88
  const { maxWaitMs, idleWaitMs } = fields;
89
89
  if (typeof maxWaitMs === "number" && typeof idleWaitMs === "number" && idleWaitMs > maxWaitMs)
90
- return `${prefix}idleWaitMs (${idleWaitMs}) is longer than ${maxPrefix}maxWaitMs (${maxWaitMs}), the longest one wait action lasts. Lower ${prefix}idleWaitMs or raise ${maxPrefix}maxWaitMs.`;
90
+ return `${prefix}idleWaitMs (${idleWaitMs}) is longer than ${maxPrefix}maxWaitMs (${maxWaitMs}), the longest one wait or one turn of only waits lasts. Lower ${prefix}idleWaitMs or raise ${maxPrefix}maxWaitMs.`;
91
91
  return null;
92
92
  }
93
93
  /**
@@ -2,7 +2,7 @@ import { createE2BDesktopExecutor } from "../../../substrates/e2b/desktop-execut
2
2
  import { participantInbox, planParticipantComms } from "./comms.js";
3
3
  import { participantBrowserStateObserver } from "./fidelity.js";
4
4
  import { acquireParticipantDesktop, placeParticipantFiles, provisionParticipantSubject, verifyParticipantScreen, } from "./prepare.js";
5
- import { openParticipantSurface, startParticipantMedia, startParticipantStream } from "./start.js";
5
+ import { keepParticipantScreenOn, openParticipantSurface, startParticipantMedia, startParticipantStream, } from "./start.js";
6
6
  import { desktopEvidenceOf, newParticipantState } from "./state.js";
7
7
  import { finishE2BDesktop } from "./teardown.js";
8
8
  import { participantServeUrl } from "../types.js";
@@ -44,6 +44,7 @@ export function createE2BParticipantDesktop(spec, deps, warnings) {
44
44
  await provisionParticipantSubject(ctx, state, desktop);
45
45
  await startParticipantMedia(ctx, state, desktop);
46
46
  await openParticipantSurface(ctx, state, desktop);
47
+ await keepParticipantScreenOn(ctx, desktop);
47
48
  prepared = true;
48
49
  }
49
50
  async function openSession() {
@@ -8,6 +8,15 @@ export declare function startParticipantMedia(ctx: E2BParticipantContext, state:
8
8
  * desktop's panel clock in it; a clock that cannot be set is a warning, and it shows UTC.
9
9
  */
10
10
  export declare function openParticipantSurface(ctx: E2BParticipantContext, state: E2BParticipantState, desktop: E2BDesktopSandbox): Promise<void>;
11
+ /**
12
+ * Turn the desktop's screen saver off, so the screen stays on while the participant waits without
13
+ * input. The XFCE session sets the screen saver's timeout in its first seconds, and the last write
14
+ * wins. Nothing waits for those writes: this runs after the browser or terminal has opened and the
15
+ * desktop has waited DESKTOP_SETTLE_MS, and on 16 fresh desktops the last write came within 2.5 s
16
+ * of a command sent as soon as the desktop was created. A screen saver that cannot be turned off
17
+ * is a warning.
18
+ */
19
+ export declare function keepParticipantScreenOn(ctx: E2BParticipantContext, desktop: E2BDesktopSandbox): Promise<void>;
11
20
  /**
12
21
  * Measure the browser window and start the live stream. A stream that cannot start is a warning;
13
22
  * unusable browser geometry fails the participant before any action.
@@ -1,12 +1,13 @@
1
1
  // The second half of an E2B desktop participant's preparation, and the start of its session: speech and
2
- // recording, then the browser or terminal the participant sees, then the live stream. Each step
3
- // fills the participant's state.
2
+ // recording, then the browser or terminal the participant sees, then the screen saver turned off,
3
+ // then the live stream. Each step fills the participant's state.
4
4
  import { redactText, toErrorMessage } from "../../../evidence/redaction.js";
5
5
  import { CHROME_DEVTOOLS_PORT, DESKTOP_SETTLE_MS, openDesktopBrowserTarget, openDesktopTerminal, startDesktopStream, } from "../../../substrates/e2b/desktop-browser.js";
6
6
  import { setPanelClockZone } from "../../../substrates/e2b/desktop-clock.js";
7
7
  import { captureDesktopBrowserGeometry } from "../../../substrates/e2b/desktop-geometry.js";
8
8
  import { prepareDesktopMedia, startE2BDesktopMedia, } from "../../../substrates/e2b/desktop-media.js";
9
9
  import { startE2BDesktopRecording } from "../../../substrates/e2b/desktop-recording.js";
10
+ import { turnOffScreenSaver } from "../../../substrates/e2b/desktop-screen.js";
10
11
  import { applyParticipantMobileFidelity, mobileLaunchFlags } from "./fidelity.js";
11
12
  /** Start the declared speech worker and screen recording. A recording that cannot start is a warning. */
12
13
  export async function startParticipantMedia(ctx, state, desktop) {
@@ -101,6 +102,20 @@ export async function openParticipantSurface(ctx, state, desktop) {
101
102
  warnings,
102
103
  });
103
104
  }
105
+ /**
106
+ * Turn the desktop's screen saver off, so the screen stays on while the participant waits without
107
+ * input. The XFCE session sets the screen saver's timeout in its first seconds, and the last write
108
+ * wins. Nothing waits for those writes: this runs after the browser or terminal has opened and the
109
+ * desktop has waited DESKTOP_SETTLE_MS, and on 16 fresh desktops the last write came within 2.5 s
110
+ * of a command sent as soon as the desktop was created. A screen saver that cannot be turned off
111
+ * is a warning.
112
+ */
113
+ export async function keepParticipantScreenOn(ctx, desktop) {
114
+ const { spec, deps, warnings } = ctx;
115
+ await turnOffScreenSaver(desktop, deps.requestTimeoutMs).catch((error) => {
116
+ warnings.push(`The desktop's screen saver for participant ${spec.planned.id} could not be turned off, so its screen may go black after 5 minutes without input: ${redactText(deps.scrubKnownValues(toErrorMessage(error)))}`);
117
+ });
118
+ }
104
119
  /**
105
120
  * DevTools answering later than this after launch is recorded as a warning on the participant. Six live
106
121
  * launches answered in 4.8-7.7 s; without this wait the reads it guards give up at about 11 s.
@@ -37,7 +37,7 @@ export interface DesktopParticipant extends Participant {
37
37
  readonly dwell?: DwellWindow;
38
38
  readonly reasoningEffort?: ReasoningEffort;
39
39
  readonly maxOutputTokens?: number;
40
- /** actor.maxWaitMs: the longest one wait action lasts. */
40
+ /** actor.maxWaitMs: the longest one wait or one turn of only waits lasts. */
41
41
  readonly maxWaitMs?: number;
42
42
  /**
43
43
  * The participant's idleWaitMs, else actor.idleWaitMs: how long a wait with no duration lasts
@@ -0,0 +1,10 @@
1
+ import type { E2BDesktopSandbox } from "./sdk.js";
2
+ /**
3
+ * Turn the desktop's X screen saver off with `xset s off`. Nothing here waits for the three
4
+ * writers, so a call holds only when it comes after their last write: on 16 fresh desktops that
5
+ * write came 1.6 to 2.5 s after a command sent as soon as the desktop was created, and none came
6
+ * in the following 27 s. After that, xfce4-screensaver and xfce4-power-manager write the timeout
7
+ * again only when the xfconf settings of either one change, which setting the panel clock does not
8
+ * do. A desktop without xset is left as it is. Throws with xset's error output when xset fails.
9
+ */
10
+ export declare function turnOffScreenSaver(desktop: E2BDesktopSandbox, requestTimeoutMs: number): Promise<void>;
@@ -0,0 +1,25 @@
1
+ // The hosted desktop's X screen saver. The stock template's XFCE session has three writers of its
2
+ // timeout, each acting in the session's first seconds: xfce4-screensaver sets its idle delay
3
+ // (300 s), xfce4-power-manager sets its blank-on-ac time (600 s), and an autostart entry runs
4
+ // `xset s off`. The last write wins. A timeout left at 300 s turns the screen black after 5 minutes
5
+ // without input, so a participant waiting on a page sees a black screen. The desktop runs on Xvfb,
6
+ // which has no display power management extension, so the screen saver is the only thing that
7
+ // blanks it.
8
+ import { e2bShell } from "./shell.js";
9
+ /**
10
+ * Turn the desktop's X screen saver off with `xset s off`. Nothing here waits for the three
11
+ * writers, so a call holds only when it comes after their last write: on 16 fresh desktops that
12
+ * write came 1.6 to 2.5 s after a command sent as soon as the desktop was created, and none came
13
+ * in the following 27 s. After that, xfce4-screensaver and xfce4-power-manager write the timeout
14
+ * again only when the xfconf settings of either one change, which setting the panel clock does not
15
+ * do. A desktop without xset is left as it is. Throws with xset's error output when xset fails.
16
+ */
17
+ export async function turnOffScreenSaver(desktop, requestTimeoutMs) {
18
+ const result = await e2bShell(desktop).run([
19
+ 'export DISPLAY="${DISPLAY:-:0}"',
20
+ "command -v xset >/dev/null 2>&1 || exit 0",
21
+ "xset s off",
22
+ ].join("\n"), { requestTimeoutMs, timeoutMs: 15_000 });
23
+ if (result.exitCode !== 0)
24
+ throw new Error(result.stderr.trim() || `xset exited ${result.exitCode}`);
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.131.0",
3
+ "version": "0.131.1",
4
4
  "description": "Synthetic user research for apps, CLIs, and agent-facing product flows.",
5
5
  "keywords": [
6
6
  "cli",