Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions agent-computer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TAKE_CONTROL_FIRST,
} from "./control";
import { identity } from "./identity";
import { createProfiles, VIEWPORT } from "./profiles";
import { createProfiles, numberFromEnv, VIEWPORT } from "./profiles";
import {
type InputMessage,
type Screencast,
Expand Down Expand Up @@ -70,11 +70,8 @@ if (!COMPUTER_TOKEN) {
process.exit(1);
}

const PORT = Number.parseInt(process.env.PORT ?? "4100", 10);
const NAVIGATION_TIMEOUT_MS = Number.parseInt(
process.env.NAVIGATION_TIMEOUT_MS ?? "30000",
10,
);
const PORT = numberFromEnv("PORT", 4100);
const NAVIGATION_TIMEOUT_MS = numberFromEnv("NAVIGATION_TIMEOUT_MS", 30000);

/**
* How long one action waits for its element.
Expand All @@ -83,10 +80,7 @@ const NAVIGATION_TIMEOUT_MS = Number.parseInt(
* behaviour we want, but a ref that no longer resolves would otherwise hang for the full navigation
* timeout before saying so, and the person is sitting watching a screen that is not changing.
*/
const ACTION_TIMEOUT_MS = Number.parseInt(
process.env.ACTION_TIMEOUT_MS ?? "10000",
10,
);
const ACTION_TIMEOUT_MS = numberFromEnv("ACTION_TIMEOUT_MS", 10000);

/**
* How much page text a navigation hands back.
Expand Down
9 changes: 8 additions & 1 deletion agent-computer/src/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,14 @@ async function closeAndWait(context: BrowserContext): Promise<void> {
* and every browser would be closed the moment it opened. Anything that is not a positive number
* falls back, because "I typed this wrong" and "I did not set it" both mean the default.
*/
function numberFromEnv(name: string, fallback: number): number {
/**
* A positive number from the environment, or the fallback.
*
* `Number.parseInt(process.env.X ?? "default")` is not enough: an unset variable declared in a
* compose file arrives as an empty string rather than as absent, so `??` never fires and the parse
* yields `NaN`. Empty, absent, non-numeric and non-positive all mean "not set" and take the fallback.
*/
export function numberFromEnv(name: string, fallback: number): number {
const raw = process.env[name]?.trim();
if (!raw) return fallback;
const value = Number(raw);
Expand Down
38 changes: 38 additions & 0 deletions agent-computer/tests/number-from-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { afterEach, describe, expect, test } from "bun:test";
import { numberFromEnv } from "../src/profiles";

const NAME = "OPENBOT_TEST_NUMBER_FROM_ENV";

afterEach(() => {
delete process.env[NAME];
});

describe("numberFromEnv", () => {
test("takes a positive number, trimming surrounding whitespace", () => {
process.env[NAME] = " 5000 ";
expect(numberFromEnv(NAME, 10000)).toBe(5000);
});

test("falls back when the variable is unset", () => {
expect(numberFromEnv(NAME, 10000)).toBe(10000);
});

test("falls back on the empty string a compose file passes for an unset variable", () => {
// The bug this guards: `Number.parseInt(process.env.X ?? "default")` sees "" here, not undefined,
// so `??` never fires and the parse is NaN. An empty value means "not set" and takes the fallback.
process.env[NAME] = "";
expect(numberFromEnv(NAME, 10000)).toBe(10000);
});

test("falls back on a non-numeric value", () => {
process.env[NAME] = "soon";
expect(numberFromEnv(NAME, 10000)).toBe(10000);
});

test("falls back on zero and negatives, so a bad timeout is never enforced", () => {
process.env[NAME] = "0";
expect(numberFromEnv(NAME, 10000)).toBe(10000);
process.env[NAME] = "-5";
expect(numberFromEnv(NAME, 10000)).toBe(10000);
});
});
Loading