@opengeni/runtime 0.3.0 → 0.3.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/{chunk-D5KU3QUC.js → chunk-HGQ252FL.js} +22 -3
- package/dist/chunk-HGQ252FL.js.map +1 -0
- package/dist/index-CSGkld-v.d.ts +1801 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +160 -37
- package/dist/index.js.map +1 -1
- package/dist/sandbox/index.d.ts +4 -1784
- package/dist/sandbox/index.js +1 -1
- package/package.json +3 -3
- package/src/history-sanitizer.ts +35 -38
- package/src/index.ts +67 -6
- package/src/metrics.ts +5 -0
- package/src/sandbox/display-stack.ts +25 -4
- package/src/sandbox/index.ts +28 -1
- package/src/sandbox-computer.ts +167 -36
- package/src/screenshot-error-card.ts +25 -0
- package/dist/chunk-D5KU3QUC.js.map +0 -1
package/src/sandbox-computer.ts
CHANGED
|
@@ -77,6 +77,19 @@ const SCROLL_MAX_CLICKS = 15;
|
|
|
77
77
|
// short to ride out a cold gVisor XFCE boot, so the turn failed loud on a transient.
|
|
78
78
|
const SCREENSHOT_WARMUP_BUDGET_MS = 30_000;
|
|
79
79
|
const SCREENSHOT_RETRY_DELAY_MS = 750;
|
|
80
|
+
// The screenshot PNG is read back off the box by base64-ing it over the provider's exec
|
|
81
|
+
// channel. Modal's exec collects stdout into a BOUNDED buffer that silently EMPTIES a
|
|
82
|
+
// single oversized read: a fully-painted 1280x800 desktop PNG (~222 KB) base64s to
|
|
83
|
+
// ~296 KB, which trips the cap and returns "" — the blank-frame incident. A partly-
|
|
84
|
+
// painted frame (~200 KB base64) slipped under it, which is why this only surfaced once
|
|
85
|
+
// the paint gate began delivering FULLY-painted frames. (Channel-A's file read already
|
|
86
|
+
// caps its own base64-over-exec output for the same reason; the screenshot read was the
|
|
87
|
+
// one large read that didn't bound itself.) We therefore read the file in 3-byte-aligned
|
|
88
|
+
// CHUNKS whose per-read base64 (~131 KB) stays well under the cap, then VALIDATE the
|
|
89
|
+
// reconstructed byte count against the on-box file size. 98304 = 96 KiB, divisible by 3
|
|
90
|
+
// so each chunk's base64 is self-contained (no cross-chunk padding) and concatenating the
|
|
91
|
+
// decoded chunks reconstructs the PNG byte-exactly.
|
|
92
|
+
const SCREENSHOT_READ_CHUNK_BYTES = 98_304;
|
|
80
93
|
|
|
81
94
|
export type SandboxComputerOptions = {
|
|
82
95
|
display?: string; // ":0"
|
|
@@ -116,7 +129,9 @@ const BUTTON_NUM: Record<ComputerButton, number> = { left: 1, wheel: 2, right: 3
|
|
|
116
129
|
// exec/execCommand (readFile path-validates against /workspace and rejects /tmp).
|
|
117
130
|
type ExecResultLike = { output?: string; stdout?: string; stderr?: string; exitCode?: number | null; sessionId?: number };
|
|
118
131
|
type ComputerSession = {
|
|
119
|
-
|
|
132
|
+
// A native provider exec returns a structured `ExecResultLike`; the routing proxy
|
|
133
|
+
// fronting a Modal box returns the execCommand banner STRING. Both are handled.
|
|
134
|
+
exec?: (args: { cmd: string; runAs?: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<ExecResultLike | string>;
|
|
120
135
|
execCommand?: (args: { cmd: string; runAs?: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<string>;
|
|
121
136
|
};
|
|
122
137
|
|
|
@@ -124,6 +139,13 @@ type ComputerSession = {
|
|
|
124
139
|
export class ComputerUnavailableError extends Error {
|
|
125
140
|
constructor(message: string) { super(message); this.name = "ComputerUnavailableError"; }
|
|
126
141
|
}
|
|
142
|
+
/** The screenshot file read back SHORT of its on-box byte size — a chunk was truncated
|
|
143
|
+
* by the provider's exec-output cap. This is deterministic, NOT a warm-up transient, so
|
|
144
|
+
* it fails LOUD immediately with the byte counts (never a silent blank the model would
|
|
145
|
+
* read as a real empty screen) and is not retried. */
|
|
146
|
+
export class ScreenshotReadError extends Error {
|
|
147
|
+
constructor(message: string) { super(message); this.name = "ScreenshotReadError"; }
|
|
148
|
+
}
|
|
127
149
|
/** A write action attempted while readOnly. */
|
|
128
150
|
export class ComputerReadOnlyError extends Error {
|
|
129
151
|
constructor() { super("computer-use is read-only — write actions are disabled"); this.name = "ComputerReadOnlyError"; }
|
|
@@ -268,6 +290,12 @@ export class SandboxComputer implements Computer {
|
|
|
268
290
|
return Buffer.from(bytes).toString("base64");
|
|
269
291
|
} catch (error) {
|
|
270
292
|
lastError = error;
|
|
293
|
+
// A short/truncated read is deterministic (the exec-output cap), not a warm-up
|
|
294
|
+
// transient — retrying only burns the budget and delays a legible failure. Fail
|
|
295
|
+
// loud NOW (the `finally` still cleans up the temp file).
|
|
296
|
+
if (error instanceof ScreenshotReadError) {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
271
299
|
} finally {
|
|
272
300
|
// Best-effort cleanup on every attempt (success OR failure); never mask the
|
|
273
301
|
// screenshot result.
|
|
@@ -288,37 +316,75 @@ export class SandboxComputer implements Computer {
|
|
|
288
316
|
throw new ComputerUnavailableError("scrot produced an empty screenshot (display not up?)");
|
|
289
317
|
}
|
|
290
318
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
319
|
+
// Run a read-only command over the SAME command primitive computer actions use
|
|
320
|
+
// (exec ?? execCommand) and return its RAW stdout body — NOT `session.readFile` (Modal
|
|
293
321
|
// path-validates against /workspace and rejects /tmp) and NOT `this.x()` (its
|
|
294
|
-
// `sandboxCommandOutput` parser drops the execCommand STRING body
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
|
|
322
|
+
// `sandboxCommandOutput` parser drops the execCommand STRING body). exec exposes a
|
|
323
|
+
// structured stdout; execCommand returns the formatted STRING, so we strip its banner
|
|
324
|
+
// ("…Output:\n<body>") to recover the body.
|
|
325
|
+
//
|
|
326
|
+
// ROUTING-PROXY SEAM: when the selfhosted feature is on, the turn's box is wrapped in a
|
|
327
|
+
// `RoutingSandboxSession`, which ALWAYS exposes an `exec` method — but for a Modal-backed
|
|
328
|
+
// box (no native `exec`) that method internally falls back to `execCommand` and returns
|
|
329
|
+
// the formatted STRING, not a `{output}` object. `sandboxCommandOutput` returns "" for a
|
|
330
|
+
// string, so a naive `sandboxCommandOutput(await session.exec())` silently dropped the
|
|
331
|
+
// whole screenshot body → empty read → "display not up" error card on EVERY Modal
|
|
332
|
+
// computer-use turn once routing was enabled. So a STRING exec result is banner-stripped
|
|
333
|
+
// exactly like the direct execCommand path; only a structured object goes to
|
|
334
|
+
// sandboxCommandOutput.
|
|
335
|
+
private async readCmdRaw(cmd: string): Promise<string> {
|
|
300
336
|
const args = {
|
|
301
|
-
cmd
|
|
337
|
+
cmd,
|
|
302
338
|
...(this.runAs ? { runAs: this.runAs } : {}),
|
|
303
339
|
yieldTimeMs: ACTION_YIELD_MS,
|
|
304
|
-
// null disables the provider's
|
|
305
|
-
//
|
|
340
|
+
// null disables the provider's TOKEN truncation; the byte cap this method chunks
|
|
341
|
+
// around is a separate, lower-level exec-stdout buffer limit.
|
|
306
342
|
maxOutputTokens: null as unknown as number,
|
|
307
343
|
};
|
|
308
|
-
let raw: string;
|
|
309
344
|
if (typeof this.session.exec === "function") {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
345
|
+
const result = await this.session.exec(args);
|
|
346
|
+
// A routing proxy fronting a Modal box returns the execCommand banner STRING;
|
|
347
|
+
// a native structured exec returns an object. Handle both so neither silently
|
|
348
|
+
// yields an empty body.
|
|
349
|
+
return typeof result === "string" ? stripExecBanner(result) : sandboxCommandOutput(result);
|
|
350
|
+
}
|
|
351
|
+
if (typeof this.session.execCommand === "function") {
|
|
352
|
+
return stripExecBanner(await this.session.execCommand(args));
|
|
353
|
+
}
|
|
354
|
+
throw new ComputerUnavailableError("session cannot run commands (no exec/execCommand) — screenshots unavailable");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Read the screenshot PNG bytes off the box. A SINGLE `base64 <file>` over Modal's
|
|
358
|
+
// exec silently returns "" once the base64 exceeds the exec-stdout buffer cap (a full
|
|
359
|
+
// desktop ~296 KB base64 trips it — the blank-frame incident). So we size the file
|
|
360
|
+
// first, then base64 it in SCREENSHOT_READ_CHUNK_BYTES-sized, 3-byte-aligned chunks
|
|
361
|
+
// (each read's base64 stays well under the cap) and reconstruct — validating the total
|
|
362
|
+
// against the on-box size and failing LOUD on any short read rather than handing back a
|
|
363
|
+
// truncated/blank frame. Binary-safe: base64 is plain ASCII over stdout.
|
|
364
|
+
private async readScreenshotBytes(path: string): Promise<Uint8Array> {
|
|
365
|
+
// On-box byte size (tiny output — always safe). A still-warming :0 can yield a
|
|
366
|
+
// zero-byte scrot file; report empty so the caller retries within its warm-up budget.
|
|
367
|
+
const sizeRaw = (await this.readCmdRaw(`wc -c < ${path} 2>/dev/null`)).replace(/[^0-9]/g, "");
|
|
368
|
+
const fileSize = Number.parseInt(sizeRaw, 10);
|
|
369
|
+
if (!Number.isFinite(fileSize) || fileSize <= 0) return new Uint8Array();
|
|
370
|
+
|
|
371
|
+
const chunks: Buffer[] = [];
|
|
372
|
+
const nChunks = Math.ceil(fileSize / SCREENSHOT_READ_CHUNK_BYTES);
|
|
373
|
+
for (let i = 0; i < nChunks; i++) {
|
|
374
|
+
// `dd bs=CHUNK skip=i count=1` reads bytes [i*CHUNK, (i+1)*CHUNK); CHUNK % 3 == 0
|
|
375
|
+
// so each chunk's base64 is self-contained and decoded chunks concatenate exactly.
|
|
376
|
+
const raw = await this.readCmdRaw(
|
|
377
|
+
`dd if=${path} bs=${SCREENSHOT_READ_CHUNK_BYTES} skip=${i} count=1 2>/dev/null | base64`,
|
|
378
|
+
);
|
|
379
|
+
chunks.push(Buffer.from(raw.replace(/\s+/g, ""), "base64"));
|
|
318
380
|
}
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
321
|
-
|
|
381
|
+
const bytes = Buffer.concat(chunks);
|
|
382
|
+
if (bytes.length !== fileSize) {
|
|
383
|
+
throw new ScreenshotReadError(
|
|
384
|
+
`screenshot read reconstructed ${bytes.length}B of ${fileSize}B (${nChunks} chunk(s), path=${path}) — a chunk was truncated by the exec-output cap; frame incomplete`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return new Uint8Array(bytes);
|
|
322
388
|
}
|
|
323
389
|
|
|
324
390
|
async click(xp: number, yp: number, button: ComputerButton) {
|
|
@@ -403,12 +469,35 @@ const POINTER_BUTTON: Record<ComputerButton, PointerButton> = {
|
|
|
403
469
|
forward: PointerButton.POINTER_BUTTON_UNSPECIFIED,
|
|
404
470
|
};
|
|
405
471
|
|
|
472
|
+
// Native capture (ScreenCaptureKit / x11) can hand back a null/empty FIRST frame in
|
|
473
|
+
// the moments right after the agent connects — the capture stream still warming — the
|
|
474
|
+
// same class of transient the scrot path retries around for a cold :0. Native capture
|
|
475
|
+
// is normally sub-second, so the warm-up budget is much shorter than the scrot path's
|
|
476
|
+
// 30 s cold-Modal-box budget; enough to ride a first-frame miss, not enough to hang a
|
|
477
|
+
// turn on a genuinely dead capture. A TERMINAL failure (permission denied) short-
|
|
478
|
+
// circuits the budget and fails immediately.
|
|
479
|
+
const NATIVE_SCREENSHOT_WARMUP_BUDGET_MS = 6_000;
|
|
480
|
+
const NATIVE_SCREENSHOT_RETRY_DELAY_MS = 400;
|
|
481
|
+
|
|
406
482
|
export type NativeDesktopComputerOptions = {
|
|
407
483
|
dimensions?: [number, number]; // the display geometry (must match the capture size)
|
|
408
484
|
environment?: NonNullable<Computer["environment"]>; // "ubuntu" (default) | "mac" | ...; model uses it for OS key conventions
|
|
409
485
|
readOnly?: boolean; // when true, every WRITE action throws ComputerReadOnlyError
|
|
486
|
+
screenshotWarmupBudgetMs?: number; // wall-clock budget to retry a warming/empty first frame
|
|
487
|
+
screenshotRetryDelayMs?: number; // delay between warm-up retries
|
|
410
488
|
};
|
|
411
489
|
|
|
490
|
+
/** A capture failure that RETRYING cannot cure — Screen Recording (TCC) has not been
|
|
491
|
+
* granted to the agent, so every capture will deny until the user grants it. We fail
|
|
492
|
+
* FAST on this rather than burn the warm-up budget, and surface the reason verbatim so
|
|
493
|
+
* the operator sees "grant Screen Recording", not a blank screen. Matches the agent's
|
|
494
|
+
* `capture_rgba` denial strings ("Screen Recording permission is not granted",
|
|
495
|
+
* "no shareable content (Screen Recording denied?)"). */
|
|
496
|
+
function isTerminalCaptureDenial(error: unknown): boolean {
|
|
497
|
+
const msg = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
|
|
498
|
+
return msg.includes("screen recording") || msg.includes("permission is not granted") || msg.includes("consent");
|
|
499
|
+
}
|
|
500
|
+
|
|
412
501
|
/**
|
|
413
502
|
* A `Computer` that drives a SELF-HOSTED machine's OWN desktop NATIVELY over the
|
|
414
503
|
* control plane (`desktopInput` inject + `screenshot` capture on the bound
|
|
@@ -428,6 +517,8 @@ export class NativeDesktopComputer implements Computer {
|
|
|
428
517
|
readonly dimensions: [number, number];
|
|
429
518
|
private session: NativeDesktopSession;
|
|
430
519
|
private readonly readOnly: boolean;
|
|
520
|
+
private readonly screenshotWarmupBudgetMs: number;
|
|
521
|
+
private readonly screenshotRetryDelayMs: number;
|
|
431
522
|
// The ENCODED vs NATIVE geometry of the MOST RECENT screenshot the model saw. The
|
|
432
523
|
// model computes click coordinates in the encoded-pixel space of that screenshot;
|
|
433
524
|
// when the agent downscaled the PNG to fit the transport budget, encoded < native,
|
|
@@ -445,6 +536,8 @@ export class NativeDesktopComputer implements Computer {
|
|
|
445
536
|
// should pass "mac" so the model uses ⌘-based shortcuts — see the coordinate TODO.
|
|
446
537
|
this.environment = opts.environment ?? "ubuntu";
|
|
447
538
|
this.readOnly = opts.readOnly ?? false;
|
|
539
|
+
this.screenshotWarmupBudgetMs = opts.screenshotWarmupBudgetMs ?? NATIVE_SCREENSHOT_WARMUP_BUDGET_MS;
|
|
540
|
+
this.screenshotRetryDelayMs = opts.screenshotRetryDelayMs ?? NATIVE_SCREENSHOT_RETRY_DELAY_MS;
|
|
448
541
|
}
|
|
449
542
|
|
|
450
543
|
/** Rebind to a freshly resumed-by-id session after a box rollover / re-establish. */
|
|
@@ -487,19 +580,57 @@ export class NativeDesktopComputer implements Computer {
|
|
|
487
580
|
async screenshot(): Promise<string> {
|
|
488
581
|
// CRITICAL CONTRACT (mirrors SandboxComputer.screenshot): NEVER return "". The
|
|
489
582
|
// Agents SDK builds the model image as `data:image/png;base64,${output}`; an
|
|
490
|
-
// empty output → `image_url: ''
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
583
|
+
// empty output → `image_url: ''`. On the hosted computer_use_preview path the SDK
|
|
584
|
+
// catches a throw here, sets output='', and the wire-seam sanitizer rewrites the
|
|
585
|
+
// empty image_url to a BLANK 1×1 placeholder to dodge a 400 — so a capture MISS
|
|
586
|
+
// silently reaches the model as a "blank desktop" it then confidently reports.
|
|
587
|
+
//
|
|
588
|
+
// Native capture (ScreenCaptureKit / x11) can hand back a null/empty FIRST frame
|
|
589
|
+
// in the moments right after the agent connects (the capture stream warming) — the
|
|
590
|
+
// same transient the scrot path retries around for a cold :0. This path previously
|
|
591
|
+
// did a SINGLE capture, so one warm-up miss became a blank. We now RETRY across a
|
|
592
|
+
// bounded budget and, on a terminal failure, FAIL LOUD with the agent's actual
|
|
593
|
+
// reason (logged) — never a silent blank. A permission (TCC) denial is terminal:
|
|
594
|
+
// retrying cannot grant Screen Recording, so we break immediately.
|
|
595
|
+
let lastError: unknown;
|
|
596
|
+
const deadline = Date.now() + this.screenshotWarmupBudgetMs;
|
|
597
|
+
let attempt = 0;
|
|
598
|
+
while (true) {
|
|
599
|
+
if (attempt > 0) {
|
|
600
|
+
await new Promise((r) => setTimeout(r, this.screenshotRetryDelayMs));
|
|
601
|
+
}
|
|
602
|
+
attempt++;
|
|
603
|
+
try {
|
|
604
|
+
const { png, width, height, nativeWidth, nativeHeight } = await this.session.screenshot();
|
|
605
|
+
if (png.length === 0) {
|
|
606
|
+
// A warming capture yields an empty frame — retry rather than hand the model
|
|
607
|
+
// a blank; throw once the budget is spent.
|
|
608
|
+
throw new ComputerUnavailableError("native desktop screenshot returned an empty frame (display not up?)");
|
|
609
|
+
}
|
|
610
|
+
// Record the encoded (what the model sees) vs native geometry of THIS frame so
|
|
611
|
+
// the next click/move/scroll/drag scales its coordinates back to native pixels.
|
|
612
|
+
this.lastEncoded = [width, height];
|
|
613
|
+
this.lastNative = [nativeWidth || width, nativeHeight || height];
|
|
614
|
+
return Buffer.from(png).toString("base64");
|
|
615
|
+
} catch (error) {
|
|
616
|
+
lastError = error;
|
|
617
|
+
// A permission denial will deny on every retry — fail fast with the reason.
|
|
618
|
+
if (isTerminalCaptureDenial(error)) break;
|
|
619
|
+
}
|
|
620
|
+
// Stop once the warm-up budget is spent — the NEXT sleep would push us past it.
|
|
621
|
+
if (Date.now() + this.screenshotRetryDelayMs >= deadline) {
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
// Exhausted the budget (or hit a terminal denial): FAIL LOUD. Log the specific
|
|
626
|
+
// reason so the failure is DIAGNOSABLE (not a silent blank the model misreads),
|
|
627
|
+
// then rethrow — never return "".
|
|
628
|
+
const reason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
629
|
+
console.warn(`[NativeDesktopComputer] screenshot failed after ${attempt} attempt(s): ${reason}`);
|
|
630
|
+
if (lastError instanceof Error) {
|
|
631
|
+
throw lastError;
|
|
497
632
|
}
|
|
498
|
-
|
|
499
|
-
// the next click/move/scroll/drag scales its coordinates back to native pixels.
|
|
500
|
-
this.lastEncoded = [width, height];
|
|
501
|
-
this.lastNative = [nativeWidth || width, nativeHeight || height];
|
|
502
|
-
return Buffer.from(png).toString("base64");
|
|
633
|
+
throw new ComputerUnavailableError("native desktop screenshot returned an empty frame (display not up?)");
|
|
503
634
|
}
|
|
504
635
|
|
|
505
636
|
async click(x: number, y: number, button: ComputerButton) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The legible "screen capture failed" error card the history-sanitizer wire seam
|
|
2
|
+
// substitutes for an EMPTY `computer_call_output` image_url (see
|
|
3
|
+
// `rewriteEmptyComputerCallOutputImageUrls`). The hosted `computer_use_preview`
|
|
4
|
+
// protocol has only an IMAGE channel back to the model, so a capture failure has to
|
|
5
|
+
// be a legible IMAGE — otherwise the SDK's empty-output placeholder (a 1×1
|
|
6
|
+
// transparent PNG) reaches the model as a plausible-but-wrong BLANK DESKTOP it then
|
|
7
|
+
// confidently reports (the exact failure mode of the 0.1.3 TCC-denied incident).
|
|
8
|
+
//
|
|
9
|
+
// This is a valid 8-bit RGBA PNG (verified: signature + per-chunk CRC + IDAT inflates
|
|
10
|
+
// to the exact scanline size) rendered from a fixed message, so the provider's image
|
|
11
|
+
// decoder accepts it (an invalid/empty image_url is what 400s the turn). The specific
|
|
12
|
+
// failure REASON (permission denied / null image / timeout / display down) is not in
|
|
13
|
+
// the card — it is logged worker-side by `NativeDesktopComputer.screenshot()`; the
|
|
14
|
+
// card's job is only to stop the model from hallucinating a real blank desktop.
|
|
15
|
+
//
|
|
16
|
+
// Regenerate with: `bun run scripts/gen-screenshot-error-card.mjs`
|
|
17
|
+
// Rendered text:
|
|
18
|
+
// SCREEN CAPTURE FAILED
|
|
19
|
+
// THE DESKTOP COULD NOT BE CAPTURED.
|
|
20
|
+
// THIS IS A PLACEHOLDER, NOT THE REAL SCREEN.
|
|
21
|
+
// DO NOT SAY THE SCREEN IS BLANK.
|
|
22
|
+
// TELL THE USER TO CHECK THE DISPLAY AND GRANT
|
|
23
|
+
// SCREEN RECORDING PERMISSION.
|
|
24
|
+
export const SCREENSHOT_FAILURE_CARD_IMAGE_URL =
|
|
25
|
+
"data:image/png;base64,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";
|