update at 2026-03-03 21:19:52
This commit is contained in:
44
pxterm/packages/shared/src/codex/orchestrator.test.ts
Normal file
44
pxterm/packages/shared/src/codex/orchestrator.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildCdCommand, buildCodexPlan } from "./orchestrator";
|
||||
|
||||
describe("buildCdCommand", () => {
|
||||
it("`~` 应展开为 HOME", () => {
|
||||
expect(buildCdCommand("~")).toBe('cd "$HOME"');
|
||||
});
|
||||
|
||||
it("`~/...` 应保留 HOME 前缀并安全引用余下路径", () => {
|
||||
expect(buildCdCommand("~/workspace/remoteconn")).toBe('cd "$HOME"/\'workspace/remoteconn\'');
|
||||
});
|
||||
|
||||
it("普通绝对路径应保持单引号安全转义", () => {
|
||||
expect(buildCdCommand("/var/www/my app")).toBe("cd '/var/www/my app'");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildCodexPlan", () => {
|
||||
it("首条命令应使用 cd 计划且包含 sandbox 启动命令", () => {
|
||||
const plan = buildCodexPlan({
|
||||
projectPath: "~/workspace/remoteconn",
|
||||
sandbox: "workspace-write"
|
||||
});
|
||||
|
||||
expect(plan).toHaveLength(3);
|
||||
const cdStep = plan[0];
|
||||
const checkStep = plan[1];
|
||||
const runStep = plan[2];
|
||||
expect(cdStep).toBeDefined();
|
||||
expect(checkStep).toBeDefined();
|
||||
expect(runStep).toBeDefined();
|
||||
if (!cdStep || !checkStep || !runStep) {
|
||||
throw new Error("Codex 计划步骤缺失");
|
||||
}
|
||||
|
||||
expect(cdStep).toEqual({
|
||||
step: "cd",
|
||||
command: 'cd "$HOME"/\'workspace/remoteconn\'',
|
||||
markerType: "cd"
|
||||
});
|
||||
expect(checkStep.command).toBe("command -v codex");
|
||||
expect(runStep.command).toBe("codex --sandbox workspace-write");
|
||||
});
|
||||
});
|
||||
63
pxterm/packages/shared/src/codex/orchestrator.ts
Normal file
63
pxterm/packages/shared/src/codex/orchestrator.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Codex 模式编排命令。
|
||||
*/
|
||||
export interface CodexCommandPlan {
|
||||
step: "cd" | "check" | "run";
|
||||
command: string;
|
||||
markerType: "cd" | "check" | "run";
|
||||
}
|
||||
|
||||
export interface CodexRunOptions {
|
||||
projectPath: string;
|
||||
sandbox: "read-only" | "workspace-write" | "danger-full-access";
|
||||
}
|
||||
|
||||
/**
|
||||
* 对路径做最小安全转义,防止单引号截断。
|
||||
*/
|
||||
export function shellQuote(value: string): string {
|
||||
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 `cd` 命令:
|
||||
* - `~` 与 `~/...` 需要保留 HOME 展开语义,不能整体单引号包裹;
|
||||
* - 其他路径走单引号最小转义,避免空格/特殊字符破坏命令。
|
||||
*/
|
||||
export function buildCdCommand(projectPath: string): string {
|
||||
const normalized = String(projectPath || "~").trim() || "~";
|
||||
|
||||
if (normalized === "~") {
|
||||
return 'cd "$HOME"';
|
||||
}
|
||||
|
||||
if (normalized.startsWith("~/")) {
|
||||
const relative = normalized.slice(2);
|
||||
return relative ? `cd "$HOME"/${shellQuote(relative)}` : 'cd "$HOME"';
|
||||
}
|
||||
|
||||
return `cd ${shellQuote(normalized)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Codex 模式三步命令。
|
||||
*/
|
||||
export function buildCodexPlan(options: CodexRunOptions): CodexCommandPlan[] {
|
||||
return [
|
||||
{
|
||||
step: "cd",
|
||||
command: buildCdCommand(options.projectPath),
|
||||
markerType: "cd"
|
||||
},
|
||||
{
|
||||
step: "check",
|
||||
command: "command -v codex",
|
||||
markerType: "check"
|
||||
},
|
||||
{
|
||||
step: "run",
|
||||
command: `codex --sandbox ${options.sandbox}`,
|
||||
markerType: "run"
|
||||
}
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user