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
73 changes: 73 additions & 0 deletions package/ego-browser/src/env.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";

import { agentWorkspace, REPO_ROOT, SRC_DIR } from "../dist/src/env.js";

const BUNDLED_SKILL = resolve(SRC_DIR, "ego-browser");
const REPO_SKILL = resolve(REPO_ROOT, "..", "..", "skills", "ego-browser");
const HOME = resolve("/home/agent");
const INSTALL_ROOT = resolve(HOME, ".local", "share", "ego", "ego-skills");

function existsIn(paths) {
return (path) => paths.includes(path);
}

test("agentWorkspace prefers the EGO_BROWSER_AGENT_WORKSPACE override", () => {
const override = resolve("/workspaces/custom");
const result = agentWorkspace({
env: { EGO_BROWSER_AGENT_WORKSPACE: override },
exists: () => true,
});
assert.equal(result, override);
});

test("agentWorkspace uses the skill bundled next to the build output", () => {
const result = agentWorkspace({ env: {}, exists: existsIn([BUNDLED_SKILL]) });
assert.equal(result, BUNDLED_SKILL);
});

test("agentWorkspace uses the repo-layout skill when it exists", () => {
const result = agentWorkspace({ env: {}, exists: existsIn([REPO_SKILL]) });
assert.equal(result, REPO_SKILL);
});

test("agentWorkspace falls back to the installed skill registered by onboarding", () => {
const result = agentWorkspace({
env: { HOME },
exists: existsIn([resolve(INSTALL_ROOT, "learnings")]),
});
assert.equal(result, INSTALL_ROOT);
});

test("agentWorkspace prefers an ego-browser subdirectory inside the install root", () => {
const result = agentWorkspace({
env: { HOME },
exists: existsIn([
resolve(INSTALL_ROOT, "ego-browser", "learnings"),
resolve(INSTALL_ROOT, "learnings"),
]),
});
assert.equal(result, resolve(INSTALL_ROOT, "ego-browser"));
});

test("agentWorkspace ignores an installed directory without learnings", () => {
const result = agentWorkspace({
env: { HOME },
exists: existsIn([INSTALL_ROOT]),
});
assert.equal(result, REPO_SKILL);
});

test("agentWorkspace resolves the home directory from USERPROFILE", () => {
const result = agentWorkspace({
env: { USERPROFILE: HOME },
exists: existsIn([resolve(INSTALL_ROOT, "learnings")]),
});
assert.equal(result, INSTALL_ROOT);
});

test("agentWorkspace keeps the repo-layout path when nothing exists", () => {
const result = agentWorkspace({ env: {}, exists: () => false });
assert.equal(result, REPO_SKILL);
});
52 changes: 45 additions & 7 deletions package/ego-browser/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,57 @@ import { fileURLToPath } from "node:url";
export const SRC_DIR = dirname(fileURLToPath(import.meta.url));
export const REPO_ROOT = resolve(SRC_DIR, "..");

export function agentWorkspace() {
if (process.env.EGO_BROWSER_AGENT_WORKSPACE) {
return resolvePath(process.env.EGO_BROWSER_AGENT_WORKSPACE);
export function agentWorkspace({
env = process.env,
exists = existsSync,
} = {}) {
if (env.EGO_BROWSER_AGENT_WORKSPACE) {
return resolvePath(env.EGO_BROWSER_AGENT_WORKSPACE);
}

const bundledSkill = resolve(SRC_DIR, "ego-browser");
if (existsSync(bundledSkill)) {
if (exists(bundledSkill)) {
return bundledSkill;
}

return resolve(REPO_ROOT, "..", "..", "skills", "ego-browser");
const repoSkill = resolve(REPO_ROOT, "..", "..", "skills", "ego-browser");
if (exists(repoSkill)) {
return repoSkill;
}

// In the app-bundled runtime neither module-relative candidate exists on
// disk (the skill payload ships elsewhere in the bundle), so the repo-layout
// path resolves to a directory that is not there. Onboarding registers the
// shipped skill at a user-level location; accept it only when it actually
// holds learnings, so a stale leftover directory cannot claim the workspace.
for (const installedSkill of installedSkillCandidates(env)) {
if (exists(resolve(installedSkill, "learnings"))) {
return installedSkill;
}
}

return repoSkill;
}

// Onboarding links the shipped skill payload to ~/.local/share/ego/ego-skills;
// depending on the link target that path is the skill directory itself or a
// directory holding an ego-browser/ subdirectory. Resolved against the home
// directory directly so behavior is identical on POSIX and Windows.
function installedSkillCandidates(env) {
const home = env.HOME || env.USERPROFILE;
if (!home) {
return [];
}
const installRoot = resolve(home, ".local", "share", "ego", "ego-skills");
return [resolve(installRoot, "ego-browser"), installRoot];
}

export function resolvePath(path) {
if (path.startsWith("~")) {
return resolve(process.env.HOME || process.env.USERPROFILE || ".", path.slice(1));
return resolve(
process.env.HOME || process.env.USERPROFILE || ".",
path.slice(1),
);
}
return resolve(path);
}
Expand All @@ -36,7 +71,10 @@ export function loadEnvFile(path) {
}
const index = line.indexOf("=");
const key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim().replace(/^['"]|['"]$/g, "");
const value = line
.slice(index + 1)
.trim()
.replace(/^['"]|['"]$/g, "");
if (key && process.env[key] === undefined) {
process.env[key] = value;
}
Expand Down