humanish 0.83.0 → 0.83.2

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.
@@ -46,6 +46,7 @@ import { appendSandboxReceipt } from "./sandbox-receipts.js";
46
46
  import { assertScreenshotEvidence } from "./image-evidence.js";
47
47
  import { buildObserverData } from "./observer-data.js";
48
48
  import { corepackCommandFor, needsNodeRuntime, nodeBootstrapCommand } from "./subject-runtime.js";
49
+ import { TERMINAL_NODE_BOOTSTRAP_COMMAND } from "./terminal-node-bootstrap.js";
49
50
  import { chromeCdpProbeCommand, parseChromeCdpProbeOutput } from "./chrome-cdp-probe.js";
50
51
  import { personaToDirectives, renderPersonaPromptSection } from "./persona.js";
51
52
  import { labPersonaIds, resolveCommittedPersonas } from "./persona-resolve.js";
@@ -1164,16 +1165,45 @@ async function fitBrowserWindowWithinDesktop(desktop, windowId, resolution, requ
1164
1165
  await run('xdotool windowmove "$win" 0 0');
1165
1166
  await desktop.wait(250).catch(() => undefined);
1166
1167
  const moved = await measureBrowserWindowWithXdotool(desktop, windowId, requestTimeoutMs).catch(() => undefined);
1167
- // A failed move cannot be repaired by resizing a window whose origin is offscreen.
1168
- if (moved === undefined || moved.x < 0 || moved.y < 0)
1168
+ if (moved === undefined)
1169
1169
  return moved;
1170
+ let resized = moved;
1170
1171
  const width = resolution[0] - moved.x;
1171
1172
  const height = resolution[1] - moved.y;
1172
- if (width <= 0 || height <= 0)
1173
- return moved;
1174
- await run(`xdotool windowsize "$win" ${width} ${height}`);
1175
- await desktop.wait(250).catch(() => undefined);
1176
- return measureBrowserWindowWithXdotool(desktop, windowId, requestTimeoutMs).catch(() => undefined);
1173
+ // Resizing alone cannot fix an offscreen client origin. The window manager
1174
+ // can also center a minimum-width client at a negative x on a narrow screen.
1175
+ if (moved.x >= 0 && moved.y >= 0 && width > 0 && height > 0) {
1176
+ await run(`xdotool windowsize "$win" ${width} ${height}`);
1177
+ await desktop.wait(250).catch(() => undefined);
1178
+ const measured = await measureBrowserWindowWithXdotool(desktop, windowId, requestTimeoutMs).catch(() => undefined);
1179
+ if (measured === undefined)
1180
+ return measured;
1181
+ resized = measured;
1182
+ }
1183
+ if (resized === undefined || isBrowserWindowContained(resized, resolution))
1184
+ return resized;
1185
+ // Chrome's minimum client width can equal the whole desktop. Window-manager
1186
+ // borders then make a decorated window impossible to contain, even after a
1187
+ // successful move/resize. Request fullscreen once and prove the physical result.
1188
+ // xprop/xdotool ship with the desktop template; wmctrl is not required.
1189
+ // Check state first so the fullscreen shortcut cannot toggle an existing state off.
1190
+ await run([
1191
+ 'state=$(xprop -id "$win" _NET_WM_STATE)',
1192
+ 'case "$state" in',
1193
+ ' *_NET_WM_STATE_FULLSCREEN*) ;;',
1194
+ ' *) xdotool windowactivate --sync "$win"; xdotool key --clearmodifiers F11 ;;',
1195
+ 'esac'
1196
+ ].join("\n"));
1197
+ // The fullscreen animation may report its new origin before its final width.
1198
+ // Give the window manager a bounded settling window, keeping missing reads unverified.
1199
+ for (let attempt = 0; attempt < 4; attempt += 1) {
1200
+ await desktop.wait(250).catch(() => undefined);
1201
+ const measured = await measureBrowserWindowWithXdotool(desktop, windowId, requestTimeoutMs).catch(() => undefined);
1202
+ if (measured === undefined || isBrowserWindowContained(measured, resolution))
1203
+ return measured;
1204
+ resized = measured;
1205
+ }
1206
+ return resized;
1177
1207
  }
1178
1208
  /** Shared hosted-browser geometry capture used by per-lane and sequential shared-world routes. */
1179
1209
  export async function captureDesktopBrowserGeometry(args) {
@@ -1266,23 +1296,22 @@ function shellSingleQuote(value) {
1266
1296
  return `'${value.replace(/'/g, "'\\''")}'`;
1267
1297
  }
1268
1298
  /**
1269
- * Put a CLI on the desktop before the participant arrives (#495).
1299
+ * Prepare a CLI study's runtime and, only when declared, its product (#495, #515).
1270
1300
  *
1271
1301
  * The install runs UNKEYED and before the session starts, for the same reason the clone route
1272
1302
  * provisions its subject first: what is being studied begins when the participant looks at the
1273
- * screen, and making them fight an install first would be a study of the install.
1303
+ * screen. Omitting install deliberately studies product installation; Node/npm remain a
1304
+ * harness prerequisite so the participant can follow the product's public npm instructions.
1274
1305
  */
1275
1306
  async function provisionDesktopCli(desktop, args) {
1276
1307
  const install = args.install;
1277
- if (install === undefined)
1278
- return;
1279
1308
  const now = () => Date.now();
1280
- if (needsNodeRuntime([install])) {
1309
+ if (install === undefined || needsNodeRuntime([install])) {
1281
1310
  const startedAt = now();
1282
- emitPhaseStarted(args.onPhase, now, "runtime", "providing the Node runtime the install needs");
1311
+ emitPhaseStarted(args.onPhase, now, "runtime", "providing Node/npm for the desktop CLI study");
1283
1312
  const bootstrap = await runDetachedStep(desktop, {
1284
1313
  name: "desktop-cli-runtime-node",
1285
- command: nodeBootstrapCommand(),
1314
+ command: TERMINAL_NODE_BOOTSTRAP_COMMAND,
1286
1315
  cwd: "/home/user",
1287
1316
  timeoutMs: INSTALL_TIMEOUT_MS,
1288
1317
  requestTimeoutMs: args.requestTimeoutMs
@@ -1294,6 +1323,8 @@ async function provisionDesktopCli(desktop, args) {
1294
1323
  throw new Error(`desktop-cli runtime bootstrap failed for "${args.product}"`);
1295
1324
  }
1296
1325
  }
1326
+ if (install === undefined)
1327
+ return;
1297
1328
  const startedAt = now();
1298
1329
  emitPhaseStarted(args.onPhase, now, "install", `installing ${args.product} on the desktop`);
1299
1330
  const result = await runDetachedStep(desktop, {
@@ -1831,9 +1862,8 @@ export async function runCuaLane(spec, deps) {
1831
1862
  };
1832
1863
  }
1833
1864
  if (desktopCliRoute) {
1834
- // Put the product on the desktop before the participant sees it. UNKEYED, like every other
1835
- // provisioning step: the participant's world is prepared by the harness, and what is being
1836
- // studied starts at the moment they look at the screen.
1865
+ // Prepare the runtime and any declared product install, UNKEYED. With install omitted,
1866
+ // the participant discovers and installs the product from its public surfaces.
1837
1867
  await provisionDesktopCli(desktop, {
1838
1868
  product: config.subject.product?.name ?? "",
1839
1869
  ...(config.subject.product?.install === undefined ? {} : { install: config.subject.product.install }),