import type { ProblemMarkdownFrontMatter } from '../types/problem.js'; import type { TestCaseResult } from '../types/testCaseResult.js'; interface BaseGuiTestCase { id: string; input?: string; fileInputPath?: string; } export interface GuiScreenshotFile { path: string; data: string; encoding: 'base64'; } export interface GuiCommandRunResult { stdin: string; stdout: string; stderr: string; status: number | undefined; timeSeconds: number; memoryBytes: number; screenshots: GuiScreenshotFile[]; stopReason: 'process_exit' | 'stable_screenshot' | 'timeout'; } interface GuiJudgeContext { timeLimitSeconds: number; problemMarkdownFrontMatter: Pick; } type GuiJudgeCaseResult = Pick; export interface GuiCommandJudgePresetOptions { mainFilePath?: string; runTimeoutSeconds?: number; screenshotWaitSeconds?: number; stopDetectionThreshold?: number; readTestCases?: (problemDir: string) => Promise; prepare?: (context: { cwd: string; /** * Build the submission with it. Under `EXERCODE_SANDBOX_USER` delegation it already carries the * sandbox user's overrides, but the handler must still wrap whatever it spawns with * `wrapCommandWithSandboxUser` (both exported): a build runs the submission's own scripts * (`package.json` lifecycle scripts, `build.gradle`, `build.rs`), which as the trusted harness * user could read the problem's test cases and rewrite the harness. The preset terminates * leftover sandbox processes after the handler returns. */ env: NodeJS.ProcessEnv; mainFilePath: string; problemMarkdownFrontMatter: ProblemMarkdownFrontMatter; }) => Promise | undefined> | Partial | undefined; /** * Runs as the trusted harness user with the submission's `cwd`. Fixture files it creates there * must be written with `createDirectoryWithoutFollowingSymlinks`/`writeFileWithoutFollowingSymlinks` * (both exported): a submission can plant a symlink at a fixture path, and a plain `fs.writeFile` * would follow it into a file only the harness can write. */ resolveInput?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv; }) => Promise | string; command?: (context: { testCase: TTestCase; cwd: string; env: NodeJS.ProcessEnv; mainFilePath: string; }) => Promise | readonly [string, ...string[]]; runCommand?: (context: { testCase: TTestCase; /** * The command to run. Under `EXERCODE_SANDBOX_USER` delegation it is already wrapped so it * executes as the sandbox user; spawn it as given (with the supplied `env`) instead of * reconstructing it, or the submission runs as the trusted harness user. * * Its direct child is then a root-owned `sudo` whose descendants belong to the sandbox user, so * the handler cannot signal them: enforce `timeLimitSeconds` with `startSandboxTimeoutWatchdog` * and `killSandboxUserProcesses` (both exported) rather than `child.kill()` or an outer * `timeout`. The preset terminates leftover sandbox processes after the handler returns. */ command: readonly [string, ...string[]]; stdin: string; cwd: string; env: NodeJS.ProcessEnv; timeLimitSeconds: number; screenshotWaitSeconds: number; stopDetectionThreshold: number; }) => Promise | GuiCommandRunResult; test: (context: { testCase: TTestCase; runResult: GuiCommandRunResult; outputFiles: NonNullable; context: GuiJudgeContext; }) => Promise> | Partial; } /** * A preset function for judging GUI programs by collecting screenshots while the program runs. * * Keep problem-specific logic in `prepare`, `command`, and `test`. * * @example * Create `judge.ts`: * ```ts * import { DecisionCode } from '@exercode/problem-utils'; * import { guiCommandJudgePreset } from '@exercode/problem-utils/presets/guiCommand'; * * await guiCommandJudgePreset(import.meta.dirname, { * mainFilePath: 'Main.java', * readTestCases: async () => [{ id: 'default' }], * test: ({ runResult }) => { * return runResult.screenshots.length > 0 * ? { decisionCode: DecisionCode.ACCEPTED } * : { decisionCode: DecisionCode.WRONG_ANSWER }; * }, * }); * ``` */ export declare function guiCommandJudgePreset(problemDir: string, options: GuiCommandJudgePresetOptions): Promise; export {};