humanish 0.96.0 → 0.96.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/dist/cua-actor-lab.d.ts +2 -0
- package/dist/cua-actor-lab.js +51 -35
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/docs/contracts/run-bundle.md +7 -4
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +3 -3
- package/docs/ramp/README.md +5 -1
- package/docs/release/0.96.1-browser-navigation.md +22 -0
- package/package.json +1 -1
package/dist/cua-actor-lab.d.ts
CHANGED
|
@@ -786,6 +786,8 @@ export declare function applyMobileEmulation(desktop: E2BDesktopSandbox, request
|
|
|
786
786
|
targetId?: string;
|
|
787
787
|
holderName: string;
|
|
788
788
|
}>;
|
|
789
|
+
/** Root-relative physical client bounds from xwininfo's C-locale stats. */
|
|
790
|
+
export declare function parseXwininfoGeometry(output: string): RunDesktopGeometry["browserWindow"] | undefined;
|
|
789
791
|
/** Shared hosted-browser geometry capture used by per-lane and sequential shared-world routes. */
|
|
790
792
|
export declare function captureDesktopBrowserGeometry(args: {
|
|
791
793
|
desktop: E2BDesktopSandbox;
|
package/dist/cua-actor-lab.js
CHANGED
|
@@ -1164,36 +1164,48 @@ function isMeasuredViewport(value) {
|
|
|
1164
1164
|
function isPositiveMeasurement(value) {
|
|
1165
1165
|
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
1166
1166
|
}
|
|
1167
|
-
async function
|
|
1167
|
+
async function measureBrowserWindowWithXwininfo(desktop, windowId, requestTimeoutMs) {
|
|
1168
1168
|
const result = await desktop.commands.run([
|
|
1169
1169
|
"set -euo pipefail",
|
|
1170
1170
|
`win=${shellSingleQuote(windowId)}`,
|
|
1171
|
-
|
|
1171
|
+
// Older xdotool builds translate parent-relative offsets twice. With window
|
|
1172
|
+
// decorations that falsely reports a visible client as clipped, triggering
|
|
1173
|
+
// fullscreen and hiding the participant's address bar. Read root-relative
|
|
1174
|
+
// client coordinates directly; never substitute emulated CDP outer bounds.
|
|
1175
|
+
"LC_ALL=C xwininfo -id \"$win\" -stats 2>/dev/null"
|
|
1172
1176
|
].join("\n"), { requestTimeoutMs, timeoutMs: 5_000 });
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
+
if (result.exitCode !== undefined && result.exitCode !== 0)
|
|
1178
|
+
return undefined;
|
|
1179
|
+
return parseXwininfoGeometry(result.stdout ?? "");
|
|
1180
|
+
}
|
|
1181
|
+
/** Root-relative physical client bounds from xwininfo's C-locale stats. */
|
|
1182
|
+
export function parseXwininfoGeometry(output) {
|
|
1183
|
+
const read = (label) => {
|
|
1184
|
+
const matches = [...output.matchAll(new RegExp(`^\\s*${label}:\\s*(-?\\d+)\\s*$`, "gm"))];
|
|
1185
|
+
if (matches.length !== 1)
|
|
1177
1186
|
return undefined;
|
|
1178
|
-
const value = Number(
|
|
1179
|
-
return Number.
|
|
1187
|
+
const value = Number(matches[0][1]);
|
|
1188
|
+
return Number.isSafeInteger(value) ? value : undefined;
|
|
1180
1189
|
};
|
|
1181
|
-
const x = read("X");
|
|
1182
|
-
const y = read("Y");
|
|
1183
|
-
const width = read("
|
|
1184
|
-
const height = read("
|
|
1190
|
+
const x = read("Absolute upper-left X");
|
|
1191
|
+
const y = read("Absolute upper-left Y");
|
|
1192
|
+
const width = read("Width");
|
|
1193
|
+
const height = read("Height");
|
|
1194
|
+
const mapStates = [...output.matchAll(/^\s*Map State:\s*(\S+)\s*$/gm)];
|
|
1195
|
+
if (mapStates.length !== 1 || mapStates[0][1] !== "IsViewable")
|
|
1196
|
+
return undefined;
|
|
1185
1197
|
if (x === undefined || y === undefined || width === undefined || height === undefined || width <= 0 || height <= 0) {
|
|
1186
1198
|
return undefined;
|
|
1187
1199
|
}
|
|
1188
|
-
return { x, y, width, height, source: "
|
|
1200
|
+
return { x, y, width, height, source: "xwininfo" };
|
|
1189
1201
|
}
|
|
1190
1202
|
/** Physical X client bounds, never the page's emulated window.outerWidth/Height. */
|
|
1191
1203
|
function isBrowserWindowContained(bounds, [width, height]) {
|
|
1192
1204
|
return bounds.x >= 0 && bounds.y >= 0
|
|
1193
1205
|
&& bounds.x + bounds.width <= width && bounds.y + bounds.height <= height;
|
|
1194
1206
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
*
|
|
1207
|
+
/** Bounded repair. Resizing can clear a window-manager maximize state and move the
|
|
1208
|
+
* client origin as decorations return, so remeasure before a second adjustment. */
|
|
1197
1209
|
async function fitBrowserWindowWithinDesktop(desktop, windowId, resolution, requestTimeoutMs) {
|
|
1198
1210
|
const run = (command) => desktop.commands.run([
|
|
1199
1211
|
"set -euo pipefail",
|
|
@@ -1202,18 +1214,22 @@ async function fitBrowserWindowWithinDesktop(desktop, windowId, resolution, requ
|
|
|
1202
1214
|
].join("\n"), { requestTimeoutMs, timeoutMs: 5_000 }).catch(() => undefined);
|
|
1203
1215
|
await run('xdotool windowmove "$win" 0 0');
|
|
1204
1216
|
await desktop.wait(250).catch(() => undefined);
|
|
1205
|
-
const moved = await
|
|
1217
|
+
const moved = await measureBrowserWindowWithXwininfo(desktop, windowId, requestTimeoutMs).catch(() => undefined);
|
|
1206
1218
|
if (moved === undefined)
|
|
1207
1219
|
return moved;
|
|
1208
1220
|
let resized = moved;
|
|
1209
|
-
const width = resolution[0] - moved.x;
|
|
1210
|
-
const height = resolution[1] - moved.y;
|
|
1211
1221
|
// Resizing alone cannot fix an offscreen client origin. The window manager
|
|
1212
1222
|
// can also center a minimum-width client at a negative x on a narrow screen.
|
|
1213
|
-
|
|
1223
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1224
|
+
if (isBrowserWindowContained(resized, resolution))
|
|
1225
|
+
return resized;
|
|
1226
|
+
const width = resolution[0] - resized.x;
|
|
1227
|
+
const height = resolution[1] - resized.y;
|
|
1228
|
+
if (resized.x < 0 || resized.y < 0 || width <= 0 || height <= 0)
|
|
1229
|
+
break;
|
|
1214
1230
|
await run(`xdotool windowsize "$win" ${width} ${height}`);
|
|
1215
1231
|
await desktop.wait(250).catch(() => undefined);
|
|
1216
|
-
const measured = await
|
|
1232
|
+
const measured = await measureBrowserWindowWithXwininfo(desktop, windowId, requestTimeoutMs).catch(() => undefined);
|
|
1217
1233
|
if (measured === undefined)
|
|
1218
1234
|
return measured;
|
|
1219
1235
|
resized = measured;
|
|
@@ -1236,7 +1252,7 @@ async function fitBrowserWindowWithinDesktop(desktop, windowId, resolution, requ
|
|
|
1236
1252
|
// Give the window manager a bounded settling window, keeping missing reads unverified.
|
|
1237
1253
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
1238
1254
|
await desktop.wait(250).catch(() => undefined);
|
|
1239
|
-
const measured = await
|
|
1255
|
+
const measured = await measureBrowserWindowWithXwininfo(desktop, windowId, requestTimeoutMs).catch(() => undefined);
|
|
1240
1256
|
if (measured === undefined || isBrowserWindowContained(measured, resolution))
|
|
1241
1257
|
return measured;
|
|
1242
1258
|
resized = measured;
|
|
@@ -1253,40 +1269,40 @@ export async function captureDesktopBrowserGeometry(args) {
|
|
|
1253
1269
|
return undefined;
|
|
1254
1270
|
});
|
|
1255
1271
|
}
|
|
1256
|
-
let
|
|
1272
|
+
let physicalWindow;
|
|
1257
1273
|
if (browserWindowId !== undefined) {
|
|
1258
1274
|
if (args.resize !== false) {
|
|
1259
1275
|
await fillDesktopBrowserWindow(args.desktop, browserWindowId, args.requestedScreen, args.requestTimeoutMs);
|
|
1260
1276
|
// Let the window manager apply the resize before querying both X and page layout geometry.
|
|
1261
1277
|
await args.desktop.wait(250).catch(() => undefined);
|
|
1262
1278
|
}
|
|
1263
|
-
|
|
1279
|
+
physicalWindow = await measureBrowserWindowWithXwininfo(args.desktop, browserWindowId, args.requestTimeoutMs)
|
|
1264
1280
|
.catch(() => undefined);
|
|
1265
1281
|
}
|
|
1266
1282
|
else {
|
|
1267
1283
|
warnings.push(`Browser window bounds could not be measured for lane ${args.laneId}; the live stream will use the full desktop.`);
|
|
1268
1284
|
}
|
|
1269
1285
|
let unusable;
|
|
1270
|
-
if (
|
|
1271
|
-
const before =
|
|
1286
|
+
if (physicalWindow !== undefined && !isBrowserWindowContained(physicalWindow, args.requestedScreen)) {
|
|
1287
|
+
const before = physicalWindow;
|
|
1272
1288
|
if (args.resize !== false && browserWindowId !== undefined) {
|
|
1273
|
-
|
|
1274
|
-
if (
|
|
1289
|
+
physicalWindow = await fitBrowserWindowWithinDesktop(args.desktop, browserWindowId, args.requestedScreen, args.requestTimeoutMs);
|
|
1290
|
+
if (physicalWindow === undefined) {
|
|
1275
1291
|
// Keep the last measured bad state; a missing observation cannot prove a successful fix.
|
|
1276
|
-
|
|
1292
|
+
physicalWindow = before;
|
|
1277
1293
|
unusable = `Physical browser containment could not be verified after correction for lane ${args.laneId}; the last measured window was clipped.`;
|
|
1278
1294
|
}
|
|
1279
|
-
else if (isBrowserWindowContained(
|
|
1280
|
-
warnings.push(`Browser window clipping corrected for lane ${args.laneId}; physical bounds are ${
|
|
1295
|
+
else if (isBrowserWindowContained(physicalWindow, args.requestedScreen)) {
|
|
1296
|
+
warnings.push(`Browser window clipping corrected for lane ${args.laneId}; physical bounds are ${physicalWindow.width}x${physicalWindow.height} at (${physicalWindow.x}, ${physicalWindow.y}).`);
|
|
1281
1297
|
}
|
|
1282
1298
|
}
|
|
1283
|
-
if (unusable === undefined && !isBrowserWindowContained(
|
|
1284
|
-
unusable = `Browser window is outside the captured ${args.requestedScreen[0]}x${args.requestedScreen[1]} desktop for lane ${args.laneId}: physical bounds ${
|
|
1299
|
+
if (unusable === undefined && !isBrowserWindowContained(physicalWindow, args.requestedScreen)) {
|
|
1300
|
+
unusable = `Browser window is outside the captured ${args.requestedScreen[0]}x${args.requestedScreen[1]} desktop for lane ${args.laneId}: physical bounds ${physicalWindow.width}x${physicalWindow.height} at (${physicalWindow.x}, ${physicalWindow.y}), right=${physicalWindow.x + physicalWindow.width}, bottom=${physicalWindow.y + physicalWindow.height}.`;
|
|
1285
1301
|
}
|
|
1286
1302
|
if (unusable !== undefined)
|
|
1287
1303
|
warnings.push(unusable);
|
|
1288
1304
|
}
|
|
1289
|
-
if (
|
|
1305
|
+
if (physicalWindow === undefined) {
|
|
1290
1306
|
warnings.push(`Physical browser containment is unverified for lane ${args.laneId}; X window bounds could not be measured. Page-reported outer dimensions can be emulated and do not prove physical visibility.`);
|
|
1291
1307
|
}
|
|
1292
1308
|
let cdpUnavailable;
|
|
@@ -1302,11 +1318,11 @@ export async function captureDesktopBrowserGeometry(args) {
|
|
|
1302
1318
|
return undefined;
|
|
1303
1319
|
})
|
|
1304
1320
|
: undefined;
|
|
1305
|
-
const browserWindow =
|
|
1321
|
+
const browserWindow = physicalWindow ?? chromeGeometry?.browserWindow;
|
|
1306
1322
|
const viewport = chromeGeometry?.viewport;
|
|
1307
1323
|
// The fill check reads the X window when it was measured: under mobile emulation (#221) the
|
|
1308
1324
|
// page's window.outerWidth reports the EMULATED screen (414), which is not a fill failure.
|
|
1309
|
-
const fillBounds =
|
|
1325
|
+
const fillBounds = physicalWindow;
|
|
1310
1326
|
if (!browserWindow) {
|
|
1311
1327
|
warnings.push(`Browser outer bounds could not be measured for lane ${args.laneId}.`);
|
|
1312
1328
|
}
|