@runuai/host 0.9.79 → 0.9.80
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/lib/browser-testing.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
} from "./task-container-cli";
|
|
51
51
|
import { MCP_CONFIG_LOCK_PATH } from "./mcp-config-lock";
|
|
52
52
|
import {
|
|
53
|
+
MACHINE_RUNTIME_AUTHORITY_ENV,
|
|
53
54
|
RUNTIME_AUTHORITY_ENV,
|
|
54
55
|
runtimeAuthorityDockerExecArgs,
|
|
55
56
|
} from "./runtime-authority";
|
|
@@ -994,8 +995,19 @@ export async function setupBrowserTesting(
|
|
|
994
995
|
containerName: string,
|
|
995
996
|
hasCodex: boolean,
|
|
996
997
|
codexHomes: readonly string[] = hasCodex ? [DEFAULT_CODEX_HOME] : [],
|
|
997
|
-
environment?: Pick<TaskEnvironmentHandle, "exec" | "launchDetachedSession"
|
|
998
|
+
environment?: Pick<TaskEnvironmentHandle, "exec" | "launchDetachedSession"> &
|
|
999
|
+
Partial<Pick<TaskEnvironmentHandle, "descriptor">>,
|
|
998
1000
|
): Promise<BrowserSetup> {
|
|
1001
|
+
// Machine sessions run under MACHINE_RUNTIME_AUTHORITY_ENV, so every setup
|
|
1002
|
+
// exec here must too — the prewarm in particular. Warming npx under the
|
|
1003
|
+
// CONTAINER authority (private NPM_CONFIG_CACHE) leaves the session-default
|
|
1004
|
+
// npm cache cold, and Codex's 10s MCP startup deadline then kills the
|
|
1005
|
+
// browser (and every npx-launched server) for the session's whole lifetime
|
|
1006
|
+
// while Claude's 30s survives — live 2026-08-28, first codex machine task.
|
|
1007
|
+
const authorityEnv =
|
|
1008
|
+
environment?.descriptor?.locator.provider === "machine"
|
|
1009
|
+
? MACHINE_RUNTIME_AUTHORITY_ENV
|
|
1010
|
+
: RUNTIME_AUTHORITY_ENV;
|
|
999
1011
|
let ready = false;
|
|
1000
1012
|
let configured = false;
|
|
1001
1013
|
let changed = false;
|
|
@@ -1029,7 +1041,7 @@ export async function setupBrowserTesting(
|
|
|
1029
1041
|
if (environment) {
|
|
1030
1042
|
await environment.launchDetachedSession({
|
|
1031
1043
|
argv: ["sh", "-c", step],
|
|
1032
|
-
env:
|
|
1044
|
+
env: authorityEnv,
|
|
1033
1045
|
maxOutputBytes: 64 * 1024,
|
|
1034
1046
|
launchTimeoutMs: 10_000,
|
|
1035
1047
|
});
|
|
@@ -1067,14 +1079,14 @@ export async function setupBrowserTesting(
|
|
|
1067
1079
|
await environment.exec({
|
|
1068
1080
|
argv: ["/usr/bin/chown", "node:node", "/opt/pw-browsers"],
|
|
1069
1081
|
user: "root",
|
|
1070
|
-
env:
|
|
1082
|
+
env: authorityEnv,
|
|
1071
1083
|
timeoutMs: 5_000,
|
|
1072
1084
|
maxOutputBytes: 64 * 1024,
|
|
1073
1085
|
});
|
|
1074
1086
|
return environment.exec({
|
|
1075
1087
|
argv: ["sh", "-lc", PREWARM_CMD],
|
|
1076
1088
|
cwd: "/workspace",
|
|
1077
|
-
env:
|
|
1089
|
+
env: authorityEnv,
|
|
1078
1090
|
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
1079
1091
|
maxOutputBytes: 256 * 1024,
|
|
1080
1092
|
});
|
|
@@ -1140,7 +1152,7 @@ export async function setupBrowserTesting(
|
|
|
1140
1152
|
(10 + Math.max(45, 15 + selectedCodexHomes.length * 12) + 5 + 10) *
|
|
1141
1153
|
1_000;
|
|
1142
1154
|
const writeEnv = {
|
|
1143
|
-
...
|
|
1155
|
+
...authorityEnv,
|
|
1144
1156
|
UAI_BROWSER_DEF: JSON.stringify(SERVER_DEF),
|
|
1145
1157
|
UAI_MCP_PATH: MCP_CONFIG_PATH,
|
|
1146
1158
|
UAI_CLAUDE_SETTINGS_PATH: CLAUDE_SETTINGS_PATH,
|
|
@@ -1276,7 +1288,7 @@ export async function setupBrowserTesting(
|
|
|
1276
1288
|
].join("; ");
|
|
1277
1289
|
|
|
1278
1290
|
const rootWorkerEnv = {
|
|
1279
|
-
...
|
|
1291
|
+
...authorityEnv,
|
|
1280
1292
|
NPM_CONFIG_CACHE: "/tmp/uai-root-npm-cache",
|
|
1281
1293
|
npm_config_cache: "/tmp/uai-root-npm-cache",
|
|
1282
1294
|
NPM_CONFIG_LOGS_DIR: "/tmp/uai-root-npm-cache/_logs",
|
|
@@ -42,8 +42,14 @@ export interface AwsMachineConfig {
|
|
|
42
42
|
/** Override the size ladder; the default picks the smallest Graviton
|
|
43
43
|
* type satisfying both cpu and memory. */
|
|
44
44
|
instanceType?: (spec: MachineSpec) => string;
|
|
45
|
+
/** Root EBS volume size. The AMI snapshot default (8 GiB Debian) starves
|
|
46
|
+
* real work — a monorepo install plus a browser left ~36 MB free live
|
|
47
|
+
* (2026-08-28). cloud-init's growpart expands the filesystem on boot. */
|
|
48
|
+
diskGiB?: number;
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
const DEFAULT_DISK_GIB = 50;
|
|
52
|
+
|
|
47
53
|
type Runner = (args: string[]) => Promise<DockerResult>;
|
|
48
54
|
|
|
49
55
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
@@ -224,6 +230,41 @@ export function createAwsMachineProvider(
|
|
|
224
230
|
return machineId.slice("uai-machine-".length);
|
|
225
231
|
};
|
|
226
232
|
|
|
233
|
+
/** Root device name per AMI, resolved once per process. A failure throws:
|
|
234
|
+
* guessing the device would silently attach a blank second volume while
|
|
235
|
+
* the root stays at its 8 GiB snapshot size. */
|
|
236
|
+
const rootDeviceByImage = new Map<string, string>();
|
|
237
|
+
async function rootDeviceName(image: string): Promise<string> {
|
|
238
|
+
const cached = rootDeviceByImage.get(image);
|
|
239
|
+
if (cached) return cached;
|
|
240
|
+
const res = await runner([
|
|
241
|
+
"ec2",
|
|
242
|
+
"describe-images",
|
|
243
|
+
"--image-ids",
|
|
244
|
+
image,
|
|
245
|
+
"--query",
|
|
246
|
+
"Images[0].RootDeviceName",
|
|
247
|
+
]);
|
|
248
|
+
if (res.status !== 0) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`could not resolve the root device of ${image}: ${
|
|
251
|
+
res.stderr.trim() || `exit ${res.status ?? "killed"}`
|
|
252
|
+
}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
let device: unknown;
|
|
256
|
+
try {
|
|
257
|
+
device = JSON.parse(res.stdout);
|
|
258
|
+
} catch {
|
|
259
|
+
device = null;
|
|
260
|
+
}
|
|
261
|
+
if (typeof device !== "string" || !device.startsWith("/dev/")) {
|
|
262
|
+
throw new Error(`image ${image} reports no root device name`);
|
|
263
|
+
}
|
|
264
|
+
rootDeviceByImage.set(image, device);
|
|
265
|
+
return device;
|
|
266
|
+
}
|
|
267
|
+
|
|
227
268
|
/** Resolve the logical machine id to its live instances via the tag. */
|
|
228
269
|
async function resolveLive(
|
|
229
270
|
machineId: string,
|
|
@@ -292,6 +333,12 @@ export function createAwsMachineProvider(
|
|
|
292
333
|
// identical retry still cannot mint a second instance; a CHANGED
|
|
293
334
|
// launch legitimately gets a fresh token, and the resume-aware
|
|
294
335
|
// provision's describe-first step remains the guard against doubling
|
|
336
|
+
// The AMI snapshot's root volume (8 GiB on the Debian payload) starves
|
|
337
|
+
// real work; request a task-sized gp3 root instead. The mapping must
|
|
338
|
+
// name the AMI's actual root device — a wrong name would ADD a blank
|
|
339
|
+
// volume while the root stays small — so resolve it per image.
|
|
340
|
+
const diskGiB = config.diskGiB ?? DEFAULT_DISK_GIB;
|
|
341
|
+
const rootDevice = await rootDeviceName(spec.image);
|
|
295
342
|
// a live machine.
|
|
296
343
|
const launchFingerprint = createHash("sha256")
|
|
297
344
|
.update(
|
|
@@ -302,6 +349,7 @@ export function createAwsMachineProvider(
|
|
|
302
349
|
config.subnetId ?? null,
|
|
303
350
|
config.securityGroupId ?? null,
|
|
304
351
|
config.iamInstanceProfileArn ?? null,
|
|
352
|
+
diskGiB,
|
|
305
353
|
]),
|
|
306
354
|
)
|
|
307
355
|
.digest("hex")
|
|
@@ -321,6 +369,17 @@ export function createAwsMachineProvider(
|
|
|
321
369
|
`${machineId}-${launchFingerprint}`.slice(0, 64),
|
|
322
370
|
"--tag-specifications",
|
|
323
371
|
`ResourceType=instance,Tags=[{Key=${AWS_MACHINE_TAG},Value=${spec.taskId}},{Key=Name,Value=${machineId}}]`,
|
|
372
|
+
"--block-device-mappings",
|
|
373
|
+
JSON.stringify([
|
|
374
|
+
{
|
|
375
|
+
DeviceName: rootDevice,
|
|
376
|
+
Ebs: {
|
|
377
|
+
VolumeSize: diskGiB,
|
|
378
|
+
VolumeType: "gp3",
|
|
379
|
+
DeleteOnTermination: true,
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
]),
|
|
324
383
|
...(spec.authorizedPublicKey
|
|
325
384
|
? ["--user-data", awsUserData(spec.authorizedPublicKey)]
|
|
326
385
|
: []),
|
|
@@ -138,11 +138,13 @@ function machineBackend() {
|
|
|
138
138
|
if (!region) {
|
|
139
139
|
throw new Error("UAI_MACHINE_PROVIDER=aws requires UAI_AWS_REGION");
|
|
140
140
|
}
|
|
141
|
+
const diskGiB = Number(process.env.UAI_MACHINE_DISK_GIB ?? "");
|
|
141
142
|
return createAwsMachineProvider({
|
|
142
143
|
region,
|
|
143
144
|
subnetId: process.env.UAI_AWS_SUBNET_ID,
|
|
144
145
|
securityGroupId: process.env.UAI_AWS_SECURITY_GROUP_ID,
|
|
145
146
|
iamInstanceProfileArn: process.env.UAI_AWS_INSTANCE_PROFILE_ARN,
|
|
147
|
+
...(Number.isInteger(diskGiB) && diskGiB > 0 ? { diskGiB } : {}),
|
|
146
148
|
});
|
|
147
149
|
}
|
|
148
150
|
return createLocalMachineProvider();
|