fix: support Windows command wrappers for local adapters
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev-runner.mjs watch",
|
"dev": "node scripts/dev-runner.mjs watch",
|
||||||
"dev:watch": "PAPERCLIP_MIGRATION_PROMPT=never node scripts/dev-runner.mjs watch",
|
"dev:watch": "cross-env PAPERCLIP_MIGRATION_PROMPT=never node scripts/dev-runner.mjs watch",
|
||||||
"dev:once": "node scripts/dev-runner.mjs dev",
|
"dev:once": "node scripts/dev-runner.mjs dev",
|
||||||
"dev:server": "pnpm --filter @paperclipai/server dev",
|
"dev:server": "pnpm --filter @paperclipai/server dev",
|
||||||
"dev:ui": "pnpm --filter @paperclipai/ui dev",
|
"dev:ui": "pnpm --filter @paperclipai/ui dev",
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.30.0",
|
"@changesets/cli": "^2.30.0",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"esbuild": "^0.27.3",
|
"esbuild": "^0.27.3",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vitest": "^3.0.5"
|
"vitest": "^3.0.5"
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ interface RunningProcess {
|
|||||||
graceSec: number;
|
graceSec: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SpawnTarget {
|
||||||
|
command: string;
|
||||||
|
args: string[];
|
||||||
|
}
|
||||||
|
|
||||||
type ChildProcessWithEvents = ChildProcess & {
|
type ChildProcessWithEvents = ChildProcess & {
|
||||||
on(event: "error", listener: (err: Error) => void): ChildProcess;
|
on(event: "error", listener: (err: Error) => void): ChildProcess;
|
||||||
on(
|
on(
|
||||||
@@ -125,6 +130,78 @@ export function defaultPathForPlatform() {
|
|||||||
return "/usr/local/bin:/opt/homebrew/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin";
|
return "/usr/local/bin:/opt/homebrew/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function windowsPathExts(env: NodeJS.ProcessEnv): string[] {
|
||||||
|
return (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pathExists(candidate: string) {
|
||||||
|
try {
|
||||||
|
await fs.access(candidate, fsConstants.X_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveCommandPath(command: string, cwd: string, env: NodeJS.ProcessEnv): Promise<string | null> {
|
||||||
|
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
||||||
|
if (hasPathSeparator) {
|
||||||
|
const absolute = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
||||||
|
return (await pathExists(absolute)) ? absolute : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathValue = env.PATH ?? env.Path ?? "";
|
||||||
|
const delimiter = process.platform === "win32" ? ";" : ":";
|
||||||
|
const dirs = pathValue.split(delimiter).filter(Boolean);
|
||||||
|
const exts = process.platform === "win32" ? windowsPathExts(env) : [""];
|
||||||
|
const hasExtension = process.platform === "win32" && path.extname(command).length > 0;
|
||||||
|
|
||||||
|
for (const dir of dirs) {
|
||||||
|
const candidates =
|
||||||
|
process.platform === "win32"
|
||||||
|
? hasExtension
|
||||||
|
? [path.join(dir, command)]
|
||||||
|
: exts.map((ext) => path.join(dir, `${command}${ext}`))
|
||||||
|
: [path.join(dir, command)];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (await pathExists(candidate)) return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function quoteForCmd(arg: string) {
|
||||||
|
if (!arg.length) return '""';
|
||||||
|
const escaped = arg.replace(/"/g, '""').replace(/%/g, "%%");
|
||||||
|
return /[\s"&<>|^()]/.test(escaped) ? `"${escaped}"` : escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveSpawnTarget(
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
cwd: string,
|
||||||
|
env: NodeJS.ProcessEnv,
|
||||||
|
): Promise<SpawnTarget> {
|
||||||
|
const resolved = await resolveCommandPath(command, cwd, env);
|
||||||
|
const executable = resolved ?? command;
|
||||||
|
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
return { command: executable, args };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/\.(cmd|bat)$/i.test(executable)) {
|
||||||
|
const shell = env.ComSpec || process.env.ComSpec || "cmd.exe";
|
||||||
|
const commandLine = [quoteForCmd(executable), ...args.map(quoteForCmd)].join(" ");
|
||||||
|
return {
|
||||||
|
command: shell,
|
||||||
|
args: ["/d", "/s", "/c", commandLine],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { command: executable, args };
|
||||||
|
}
|
||||||
|
|
||||||
export function ensurePathInEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
export function ensurePathInEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
if (typeof env.PATH === "string" && env.PATH.length > 0) return env;
|
if (typeof env.PATH === "string" && env.PATH.length > 0) return env;
|
||||||
if (typeof env.Path === "string" && env.Path.length > 0) return env;
|
if (typeof env.Path === "string" && env.Path.length > 0) return env;
|
||||||
@@ -169,36 +246,12 @@ export async function ensureAbsoluteDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureCommandResolvable(command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
export async function ensureCommandResolvable(command: string, cwd: string, env: NodeJS.ProcessEnv) {
|
||||||
const hasPathSeparator = command.includes("/") || command.includes("\\");
|
const resolved = await resolveCommandPath(command, cwd, env);
|
||||||
if (hasPathSeparator) {
|
if (resolved) return;
|
||||||
|
if (command.includes("/") || command.includes("\\")) {
|
||||||
const absolute = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
const absolute = path.isAbsolute(command) ? command : path.resolve(cwd, command);
|
||||||
try {
|
|
||||||
await fs.access(absolute, fsConstants.X_OK);
|
|
||||||
} catch {
|
|
||||||
throw new Error(`Command is not executable: "${command}" (resolved: "${absolute}")`);
|
throw new Error(`Command is not executable: "${command}" (resolved: "${absolute}")`);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathValue = env.PATH ?? env.Path ?? "";
|
|
||||||
const delimiter = process.platform === "win32" ? ";" : ":";
|
|
||||||
const dirs = pathValue.split(delimiter).filter(Boolean);
|
|
||||||
const windowsExt = process.platform === "win32"
|
|
||||||
? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
|
|
||||||
: [""];
|
|
||||||
|
|
||||||
for (const dir of dirs) {
|
|
||||||
for (const ext of windowsExt) {
|
|
||||||
const candidate = path.join(dir, process.platform === "win32" ? `${command}${ext}` : command);
|
|
||||||
try {
|
|
||||||
await fs.access(candidate, fsConstants.X_OK);
|
|
||||||
return;
|
|
||||||
} catch {
|
|
||||||
// continue scanning PATH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Command not found in PATH: "${command}"`);
|
throw new Error(`Command not found in PATH: "${command}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +273,9 @@ export async function runChildProcess(
|
|||||||
|
|
||||||
return new Promise<RunProcessResult>((resolve, reject) => {
|
return new Promise<RunProcessResult>((resolve, reject) => {
|
||||||
const mergedEnv = ensurePathInEnv({ ...process.env, ...opts.env });
|
const mergedEnv = ensurePathInEnv({ ...process.env, ...opts.env });
|
||||||
const child = spawn(command, args, {
|
void resolveSpawnTarget(command, args, opts.cwd, mergedEnv)
|
||||||
|
.then((target) => {
|
||||||
|
const child = spawn(target.command, target.args, {
|
||||||
cwd: opts.cwd,
|
cwd: opts.cwd,
|
||||||
env: mergedEnv,
|
env: mergedEnv,
|
||||||
shell: false,
|
shell: false,
|
||||||
@@ -293,5 +348,7 @@ export async function runChildProcess(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
|||||||
'@changesets/cli':
|
'@changesets/cli':
|
||||||
specifier: ^2.30.0
|
specifier: ^2.30.0
|
||||||
version: 2.30.0(@types/node@25.2.3)
|
version: 2.30.0(@types/node@25.2.3)
|
||||||
|
cross-env:
|
||||||
|
specifier: ^10.1.0
|
||||||
|
version: 10.1.0
|
||||||
esbuild:
|
esbuild:
|
||||||
specifier: ^0.27.3
|
specifier: ^0.27.3
|
||||||
version: 0.27.3
|
version: 0.27.3
|
||||||
@@ -35,9 +38,6 @@ importers:
|
|||||||
'@paperclipai/adapter-cursor-local':
|
'@paperclipai/adapter-cursor-local':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/cursor-local
|
version: link:../packages/adapters/cursor-local
|
||||||
'@paperclipai/adapter-openclaw':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../packages/adapters/openclaw
|
|
||||||
'@paperclipai/adapter-openclaw-gateway':
|
'@paperclipai/adapter-openclaw-gateway':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/openclaw-gateway
|
version: link:../packages/adapters/openclaw-gateway
|
||||||
@@ -139,22 +139,6 @@ importers:
|
|||||||
specifier: ^5.7.3
|
specifier: ^5.7.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
packages/adapters/openclaw:
|
|
||||||
dependencies:
|
|
||||||
'@paperclipai/adapter-utils':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../../adapter-utils
|
|
||||||
picocolors:
|
|
||||||
specifier: ^1.1.1
|
|
||||||
version: 1.1.1
|
|
||||||
devDependencies:
|
|
||||||
'@types/node':
|
|
||||||
specifier: ^24.6.0
|
|
||||||
version: 24.12.0
|
|
||||||
typescript:
|
|
||||||
specifier: ^5.7.3
|
|
||||||
version: 5.9.3
|
|
||||||
|
|
||||||
packages/adapters/openclaw-gateway:
|
packages/adapters/openclaw-gateway:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@paperclipai/adapter-utils':
|
'@paperclipai/adapter-utils':
|
||||||
@@ -261,9 +245,6 @@ importers:
|
|||||||
'@paperclipai/adapter-cursor-local':
|
'@paperclipai/adapter-cursor-local':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/cursor-local
|
version: link:../packages/adapters/cursor-local
|
||||||
'@paperclipai/adapter-openclaw':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../packages/adapters/openclaw
|
|
||||||
'@paperclipai/adapter-openclaw-gateway':
|
'@paperclipai/adapter-openclaw-gateway':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/openclaw-gateway
|
version: link:../packages/adapters/openclaw-gateway
|
||||||
@@ -379,9 +360,6 @@ importers:
|
|||||||
'@paperclipai/adapter-cursor-local':
|
'@paperclipai/adapter-cursor-local':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/cursor-local
|
version: link:../packages/adapters/cursor-local
|
||||||
'@paperclipai/adapter-openclaw':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../packages/adapters/openclaw
|
|
||||||
'@paperclipai/adapter-openclaw-gateway':
|
'@paperclipai/adapter-openclaw-gateway':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/adapters/openclaw-gateway
|
version: link:../packages/adapters/openclaw-gateway
|
||||||
@@ -1011,6 +989,9 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@epic-web/invariant@1.0.0':
|
||||||
|
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||||
|
|
||||||
'@esbuild-kit/core-utils@3.3.2':
|
'@esbuild-kit/core-utils@3.3.2':
|
||||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||||
deprecated: 'Merged into tsx: https://tsx.is'
|
deprecated: 'Merged into tsx: https://tsx.is'
|
||||||
@@ -3441,6 +3422,11 @@ packages:
|
|||||||
crelt@1.0.6:
|
crelt@1.0.6:
|
||||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||||
|
|
||||||
|
cross-env@10.1.0:
|
||||||
|
resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -6743,6 +6729,8 @@ snapshots:
|
|||||||
'@embedded-postgres/windows-x64@18.1.0-beta.16':
|
'@embedded-postgres/windows-x64@18.1.0-beta.16':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@epic-web/invariant@1.0.0': {}
|
||||||
|
|
||||||
'@esbuild-kit/core-utils@3.3.2':
|
'@esbuild-kit/core-utils@3.3.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.18.20
|
esbuild: 0.18.20
|
||||||
@@ -8942,14 +8930,6 @@ snapshots:
|
|||||||
chai: 5.3.3
|
chai: 5.3.3
|
||||||
tinyrainbow: 2.0.0
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
|
|
||||||
dependencies:
|
|
||||||
'@vitest/spy': 3.2.4
|
|
||||||
estree-walker: 3.0.3
|
|
||||||
magic-string: 0.30.21
|
|
||||||
optionalDependencies:
|
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
|
|
||||||
|
|
||||||
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
|
'@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 3.2.4
|
'@vitest/spy': 3.2.4
|
||||||
@@ -9253,6 +9233,11 @@ snapshots:
|
|||||||
|
|
||||||
crelt@1.0.6: {}
|
crelt@1.0.6: {}
|
||||||
|
|
||||||
|
cross-env@10.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@epic-web/invariant': 1.0.0
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
|
||||||
cross-spawn@7.0.6:
|
cross-spawn@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 3.1.1
|
path-key: 3.1.1
|
||||||
@@ -11722,7 +11707,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
'@vitest/expect': 3.2.4
|
'@vitest/expect': 3.2.4
|
||||||
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
'@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
|
||||||
'@vitest/pretty-format': 3.2.4
|
'@vitest/pretty-format': 3.2.4
|
||||||
'@vitest/runner': 3.2.4
|
'@vitest/runner': 3.2.4
|
||||||
'@vitest/snapshot': 3.2.4
|
'@vitest/snapshot': 3.2.4
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"dev:watch": "PAPERCLIP_MIGRATION_PROMPT=never tsx watch --ignore ../ui/node_modules --ignore ../ui/.vite --ignore ../ui/dist src/index.ts",
|
"dev:watch": "cross-env PAPERCLIP_MIGRATION_PROMPT=never tsx watch --ignore ../ui/node_modules --ignore ../ui/.vite --ignore ../ui/dist src/index.ts",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
|
|||||||
@@ -29,4 +29,47 @@ describe("codex_local environment diagnostics", () => {
|
|||||||
expect(stats.isDirectory()).toBe(true);
|
expect(stats.isDirectory()).toBe(true);
|
||||||
await fs.rm(path.dirname(cwd), { recursive: true, force: true });
|
await fs.rm(path.dirname(cwd), { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("runs the hello probe when Codex is available via a Windows .cmd wrapper", async () => {
|
||||||
|
if (process.platform !== "win32") return;
|
||||||
|
|
||||||
|
const root = path.join(
|
||||||
|
os.tmpdir(),
|
||||||
|
`paperclip-codex-local-probe-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||||
|
);
|
||||||
|
const binDir = path.join(root, "bin");
|
||||||
|
const cwd = path.join(root, "workspace");
|
||||||
|
const fakeCodex = path.join(binDir, "codex.cmd");
|
||||||
|
const script = [
|
||||||
|
"@echo off",
|
||||||
|
"echo {\"type\":\"thread.started\",\"thread_id\":\"test-thread\"}",
|
||||||
|
"echo {\"type\":\"item.completed\",\"item\":{\"type\":\"agent_message\",\"text\":\"hello\"}}",
|
||||||
|
"echo {\"type\":\"turn.completed\",\"usage\":{\"input_tokens\":1,\"cached_input_tokens\":0,\"output_tokens\":1}}",
|
||||||
|
"exit /b 0",
|
||||||
|
"",
|
||||||
|
].join("\r\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(binDir, { recursive: true });
|
||||||
|
await fs.writeFile(fakeCodex, script, "utf8");
|
||||||
|
|
||||||
|
const result = await testEnvironment({
|
||||||
|
companyId: "company-1",
|
||||||
|
adapterType: "codex_local",
|
||||||
|
config: {
|
||||||
|
command: "codex",
|
||||||
|
cwd,
|
||||||
|
env: {
|
||||||
|
OPENAI_API_KEY: "test-key",
|
||||||
|
PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.status).toBe("pass");
|
||||||
|
expect(result.checks.some((check) => check.code === "codex_hello_probe_passed")).toBe(true);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user