Files
elizaos--eliza/plugins/plugin-task-coordinator/test/coding-agent-codex-artifact.live.e2e.test.ts
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

194 lines
5.9 KiB
TypeScript

// Live end-to-end test that drives the real `codex` CLI to generate an artifact
// (game.js/index.html/styles.css) and asserts the files land on disk. Gated on
// the `codex` binary being in PATH and `~/.codex/auth.json` existing, so it
// skips cleanly otherwise — run via `test:e2e:manual`.
import { spawn, spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
const EXPECTED_FILES = ["game.js", "index.html", "styles.css"];
const REAL_HOME_DIR = os.userInfo().homedir;
const CODEX_AUTH_PATH = path.join(REAL_HOME_DIR, ".codex", "auth.json");
function isCodexCliAvailable(): boolean {
const probe = spawnSync("codex", ["--version"], {
encoding: "utf8",
stdio: "pipe",
timeout: 5_000,
});
return probe.status === 0;
}
const CODEX_AVAILABLE = isCodexCliAvailable();
const CODEX_AUTH_AVAILABLE = fs.existsSync(CODEX_AUTH_PATH);
const describeIf =
CODEX_AVAILABLE && CODEX_AUTH_AVAILABLE ? describe : describe.skip;
function createIsolatedCodexHome(): string {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "eliza-codex-home-"));
const codexDir = path.join(homeDir, ".codex");
fs.mkdirSync(codexDir, { recursive: true });
fs.copyFileSync(CODEX_AUTH_PATH, path.join(codexDir, "auth.json"));
fs.writeFileSync(
path.join(codexDir, "config.toml"),
'model = "gpt-5.4"\n',
"utf8",
);
return homeDir;
}
function runCodexExec(
workingDirectory: string,
homeDir: string,
prompt: string,
timeoutMs: number,
): Promise<{ exitCode: number | null; stdout: string; stderr: string }> {
return new Promise((resolve, reject) => {
const child = spawn(
"codex",
[
"exec",
"--model",
"gpt-5.4",
"--full-auto",
"--skip-git-repo-check",
"--color",
"never",
prompt,
],
{
cwd: workingDirectory,
env: {
...process.env,
HOME: homeDir,
NO_COLOR: "1",
TERM: "dumb",
},
stdio: ["ignore", "pipe", "pipe"],
},
);
let stdout = "";
let stderr = "";
const timer = setTimeout(() => {
child.kill("SIGKILL");
reject(
new Error(
[
`codex exec timed out after ${timeoutMs}ms`,
stdout && `stdout:\n${stdout}`,
stderr && `stderr:\n${stderr}`,
]
.filter(Boolean)
.join("\n\n"),
),
);
}, timeoutMs);
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdout += chunk;
});
child.stderr.on("data", (chunk: string) => {
stderr += chunk;
});
child.on("error", (error) => {
clearTimeout(timer);
reject(error);
});
child.on("close", (exitCode) => {
clearTimeout(timer);
resolve({ exitCode, stdout, stderr });
});
});
}
describeIf("Coding agent Codex artifact generation", () => {
const cleanupDirs: string[] = [];
afterEach(() => {
for (const dir of cleanupDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it("creates a browser Tetris game in the expected files", async () => {
const homeDir = createIsolatedCodexHome();
const workingDirectory = fs.mkdtempSync(
path.join(os.tmpdir(), "eliza-codex-tetris-"),
);
cleanupDirs.push(homeDir, workingDirectory);
const prompt = [
"Create a browser Tetris game in the current directory.",
"Write exactly three files at the workspace root: index.html, styles.css, and game.js.",
"Do not create any other files or folders.",
"index.html must include elements with ids board, score, and next-piece, and load styles.css and game.js via relative paths.",
"game.js must implement keyboard controls for ArrowLeft, ArrowRight, ArrowDown, ArrowUp, and Space.",
"Render the score into #score and the next-piece preview into #next-piece.",
"Use plain browser JavaScript only.",
].join(" ");
const result = await runCodexExec(
workingDirectory,
homeDir,
prompt,
300_000,
);
const resultOutput = [result.stdout, result.stderr]
.filter(Boolean)
.join("\n\n");
expect(result.exitCode, resultOutput).toBe(0);
const entries = fs
.readdirSync(workingDirectory)
.filter((entry) => !entry.startsWith("."))
.sort();
expect(entries).toEqual(EXPECTED_FILES);
const indexHtml = fs.readFileSync(
path.join(workingDirectory, "index.html"),
"utf8",
);
const stylesCss = fs.readFileSync(
path.join(workingDirectory, "styles.css"),
"utf8",
);
const gameJs = fs.readFileSync(
path.join(workingDirectory, "game.js"),
"utf8",
);
expect(indexHtml).toMatch(/id=["']board["']/i);
expect(indexHtml).toMatch(/id=["']score["']/i);
expect(indexHtml).toMatch(/id=["']next-piece["']/i);
expect(indexHtml).toMatch(/href=["']\.?\/?styles\.css["']/i);
expect(indexHtml).toMatch(/src=["']\.?\/?game\.js["']/i);
expect(stylesCss).toMatch(/[#.]board\b/i);
// Codex sometimes styles the preview container class instead of the exact id.
expect(stylesCss).toMatch(/(?:[#.]next-piece\b|[.]preview\b)/i);
expect(gameJs).toMatch(/ArrowLeft/);
expect(gameJs).toMatch(/ArrowRight/);
expect(gameJs).toMatch(/ArrowDown/);
expect(gameJs).toMatch(/ArrowUp/);
expect(gameJs).toMatch(/Space|["'] ["']/);
expect(gameJs).toMatch(/getElementById\(["']score["']\)/);
expect(gameJs).toMatch(/getElementById\(["']next-piece["']\)/);
const syntaxCheck = spawnSync(
process.execPath,
["--check", path.join(workingDirectory, "game.js")],
{ encoding: "utf8" },
);
expect(
syntaxCheck.status,
[syntaxCheck.stdout, syntaxCheck.stderr].filter(Boolean).join("\n\n"),
).toBe(0);
}, 360_000);
});