mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-26 16:06:16 +00:00
test(codex): share run-attempt app-server harness
This commit is contained in:
@@ -44,6 +44,73 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt
|
|||||||
} as EmbeddedRunAttemptParams;
|
} as EmbeddedRunAttemptParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAppServerHarness(
|
||||||
|
requestImpl: (method: string, params: unknown) => Promise<unknown>,
|
||||||
|
) {
|
||||||
|
const requests: Array<{ method: string; params: unknown }> = [];
|
||||||
|
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||||
|
const request = vi.fn(async (method: string, params?: unknown) => {
|
||||||
|
requests.push({ method, params });
|
||||||
|
return requestImpl(method, params);
|
||||||
|
});
|
||||||
|
|
||||||
|
__testing.setCodexAppServerClientFactoryForTests(
|
||||||
|
async () =>
|
||||||
|
({
|
||||||
|
request,
|
||||||
|
addNotificationHandler: (handler: typeof notify) => {
|
||||||
|
notify = handler;
|
||||||
|
return () => undefined;
|
||||||
|
},
|
||||||
|
addRequestHandler: () => () => undefined,
|
||||||
|
}) as never,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
request,
|
||||||
|
requests,
|
||||||
|
async waitForMethod(method: string) {
|
||||||
|
await vi.waitFor(() => expect(requests.some((entry) => entry.method === method)).toBe(true));
|
||||||
|
},
|
||||||
|
async completeTurn(params: { threadId: string; turnId: string }) {
|
||||||
|
await notify({
|
||||||
|
method: "turn/completed",
|
||||||
|
params: {
|
||||||
|
threadId: params.threadId,
|
||||||
|
turnId: params.turnId,
|
||||||
|
turn: { id: params.turnId, status: "completed" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectResumeRequest(
|
||||||
|
requests: Array<{ method: string; params: unknown }>,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
expect(requests).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
method: "thread/resume",
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createResumeHarness() {
|
||||||
|
return createAppServerHarness(async (method) => {
|
||||||
|
if (method === "thread/resume") {
|
||||||
|
return { thread: { id: "thread-existing" }, modelProvider: "openai" };
|
||||||
|
}
|
||||||
|
if (method === "turn/start") {
|
||||||
|
return { turn: { id: "turn-1", status: "inProgress" } };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe("runCodexAppServerAttempt", () => {
|
describe("runCodexAppServerAttempt", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-run-"));
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-run-"));
|
||||||
@@ -56,9 +123,7 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("forwards queued user input and aborts the active app-server turn", async () => {
|
it("forwards queued user input and aborts the active app-server turn", async () => {
|
||||||
const requests: Array<{ method: string; params: unknown }> = [];
|
const { requests, waitForMethod } = createAppServerHarness(async (method, _params) => {
|
||||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
|
||||||
requests.push({ method, params });
|
|
||||||
if (method === "thread/start") {
|
if (method === "thread/start") {
|
||||||
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
||||||
}
|
}
|
||||||
@@ -67,21 +132,11 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
__testing.setCodexAppServerClientFactoryForTests(
|
|
||||||
async () =>
|
|
||||||
({
|
|
||||||
request,
|
|
||||||
addNotificationHandler: () => () => undefined,
|
|
||||||
addRequestHandler: () => () => undefined,
|
|
||||||
}) as never,
|
|
||||||
);
|
|
||||||
|
|
||||||
const run = runCodexAppServerAttempt(
|
const run = runCodexAppServerAttempt(
|
||||||
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
|
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
|
||||||
);
|
);
|
||||||
await vi.waitFor(() =>
|
await waitForMethod("turn/start");
|
||||||
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queueAgentHarnessMessage("session-1", "more context")).toBe(true);
|
expect(queueAgentHarnessMessage("session-1", "more context")).toBe(true);
|
||||||
await vi.waitFor(() =>
|
await vi.waitFor(() =>
|
||||||
@@ -126,9 +181,7 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
};
|
};
|
||||||
process.on("unhandledRejection", onUnhandledRejection);
|
process.on("unhandledRejection", onUnhandledRejection);
|
||||||
try {
|
try {
|
||||||
const requests: Array<{ method: string; params: unknown }> = [];
|
const { waitForMethod } = createAppServerHarness(async (method, _params) => {
|
||||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
|
||||||
requests.push({ method, params });
|
|
||||||
if (method === "thread/start") {
|
if (method === "thread/start") {
|
||||||
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
||||||
}
|
}
|
||||||
@@ -140,14 +193,6 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
__testing.setCodexAppServerClientFactoryForTests(
|
|
||||||
async () =>
|
|
||||||
({
|
|
||||||
request,
|
|
||||||
addNotificationHandler: () => () => undefined,
|
|
||||||
addRequestHandler: () => () => undefined,
|
|
||||||
}) as never,
|
|
||||||
);
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const params = createParams(
|
const params = createParams(
|
||||||
path.join(tempDir, "session.jsonl"),
|
path.join(tempDir, "session.jsonl"),
|
||||||
@@ -156,9 +201,7 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
params.abortSignal = abortController.signal;
|
params.abortSignal = abortController.signal;
|
||||||
|
|
||||||
const run = runCodexAppServerAttempt(params);
|
const run = runCodexAppServerAttempt(params);
|
||||||
await vi.waitFor(() =>
|
await waitForMethod("turn/start");
|
||||||
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
|
|
||||||
);
|
|
||||||
abortController.abort("shutdown");
|
abortController.abort("shutdown");
|
||||||
|
|
||||||
await expect(run).resolves.toMatchObject({ aborted: true });
|
await expect(run).resolves.toMatchObject({ aborted: true });
|
||||||
@@ -170,10 +213,7 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("forwards image attachments to the app-server turn input", async () => {
|
it("forwards image attachments to the app-server turn input", async () => {
|
||||||
const requests: Array<{ method: string; params: unknown }> = [];
|
const { requests, waitForMethod, completeTurn } = createAppServerHarness(async (method) => {
|
||||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
|
||||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
|
||||||
requests.push({ method, params });
|
|
||||||
if (method === "thread/start") {
|
if (method === "thread/start") {
|
||||||
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
return { thread: { id: "thread-1" }, model: "gpt-5.4-codex", modelProvider: "openai" };
|
||||||
}
|
}
|
||||||
@@ -182,17 +222,6 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
__testing.setCodexAppServerClientFactoryForTests(
|
|
||||||
async () =>
|
|
||||||
({
|
|
||||||
request,
|
|
||||||
addNotificationHandler: (handler: typeof notify) => {
|
|
||||||
notify = handler;
|
|
||||||
return () => undefined;
|
|
||||||
},
|
|
||||||
addRequestHandler: () => () => undefined,
|
|
||||||
}) as never,
|
|
||||||
);
|
|
||||||
const params = createParams(
|
const params = createParams(
|
||||||
path.join(tempDir, "session.jsonl"),
|
path.join(tempDir, "session.jsonl"),
|
||||||
path.join(tempDir, "workspace"),
|
path.join(tempDir, "workspace"),
|
||||||
@@ -210,17 +239,8 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const run = runCodexAppServerAttempt(params);
|
const run = runCodexAppServerAttempt(params);
|
||||||
await vi.waitFor(() =>
|
await waitForMethod("turn/start");
|
||||||
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
|
await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||||
);
|
|
||||||
await notify({
|
|
||||||
method: "turn/completed",
|
|
||||||
params: {
|
|
||||||
threadId: "thread-1",
|
|
||||||
turnId: "turn-1",
|
|
||||||
turn: { id: "turn-1", status: "completed" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await run;
|
await run;
|
||||||
|
|
||||||
expect(requests).toEqual(
|
expect(requests).toEqual(
|
||||||
@@ -338,60 +358,22 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
modelProvider: "openai",
|
modelProvider: "openai",
|
||||||
dynamicToolsFingerprint: "[]",
|
dynamicToolsFingerprint: "[]",
|
||||||
});
|
});
|
||||||
const requests: Array<{ method: string; params: unknown }> = [];
|
const { requests, waitForMethod, completeTurn } = createResumeHarness();
|
||||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
|
||||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
|
||||||
requests.push({ method, params });
|
|
||||||
if (method === "thread/resume") {
|
|
||||||
return { thread: { id: "thread-existing" }, modelProvider: "openai" };
|
|
||||||
}
|
|
||||||
if (method === "turn/start") {
|
|
||||||
return { turn: { id: "turn-1", status: "inProgress" } };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
__testing.setCodexAppServerClientFactoryForTests(
|
|
||||||
async () =>
|
|
||||||
({
|
|
||||||
request,
|
|
||||||
addNotificationHandler: (handler: typeof notify) => {
|
|
||||||
notify = handler;
|
|
||||||
return () => undefined;
|
|
||||||
},
|
|
||||||
addRequestHandler: () => () => undefined,
|
|
||||||
}) as never,
|
|
||||||
);
|
|
||||||
|
|
||||||
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
|
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir));
|
||||||
await vi.waitFor(() =>
|
await waitForMethod("turn/start");
|
||||||
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
|
await completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
|
||||||
);
|
|
||||||
await notify({
|
|
||||||
method: "turn/completed",
|
|
||||||
params: {
|
|
||||||
threadId: "thread-existing",
|
|
||||||
turnId: "turn-1",
|
|
||||||
turn: { id: "turn-1", status: "completed" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await run;
|
await run;
|
||||||
|
|
||||||
expect(requests).toEqual(
|
expectResumeRequest(requests, {
|
||||||
expect.arrayContaining([
|
threadId: "thread-existing",
|
||||||
{
|
model: "gpt-5.4-codex",
|
||||||
method: "thread/resume",
|
modelProvider: "openai",
|
||||||
params: {
|
approvalPolicy: "never",
|
||||||
threadId: "thread-existing",
|
approvalsReviewer: "user",
|
||||||
model: "gpt-5.4-codex",
|
sandbox: "workspace-write",
|
||||||
modelProvider: "openai",
|
persistExtendedHistory: true,
|
||||||
approvalPolicy: "never",
|
});
|
||||||
approvalsReviewer: "user",
|
|
||||||
sandbox: "workspace-write",
|
|
||||||
persistExtendedHistory: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passes configured app-server policy, sandbox, service tier, and model on resume", async () => {
|
it("passes configured app-server policy, sandbox, service tier, and model on resume", async () => {
|
||||||
@@ -403,29 +385,7 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
model: "gpt-5.2",
|
model: "gpt-5.2",
|
||||||
modelProvider: "openai",
|
modelProvider: "openai",
|
||||||
});
|
});
|
||||||
const requests: Array<{ method: string; params: unknown }> = [];
|
const { requests, waitForMethod, completeTurn } = createResumeHarness();
|
||||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
|
||||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
|
||||||
requests.push({ method, params });
|
|
||||||
if (method === "thread/resume") {
|
|
||||||
return { thread: { id: "thread-existing" }, modelProvider: "openai" };
|
|
||||||
}
|
|
||||||
if (method === "turn/start") {
|
|
||||||
return { turn: { id: "turn-1", status: "inProgress" } };
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
__testing.setCodexAppServerClientFactoryForTests(
|
|
||||||
async () =>
|
|
||||||
({
|
|
||||||
request,
|
|
||||||
addNotificationHandler: (handler: typeof notify) => {
|
|
||||||
notify = handler;
|
|
||||||
return () => undefined;
|
|
||||||
},
|
|
||||||
addRequestHandler: () => () => undefined,
|
|
||||||
}) as never,
|
|
||||||
);
|
|
||||||
|
|
||||||
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
|
const run = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
|
||||||
pluginConfig: {
|
pluginConfig: {
|
||||||
@@ -437,34 +397,22 @@ describe("runCodexAppServerAttempt", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await vi.waitFor(() =>
|
await waitForMethod("turn/start");
|
||||||
expect(requests.some((entry) => entry.method === "turn/start")).toBe(true),
|
await completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
|
||||||
);
|
|
||||||
await notify({
|
|
||||||
method: "turn/completed",
|
|
||||||
params: {
|
|
||||||
threadId: "thread-existing",
|
|
||||||
turnId: "turn-1",
|
|
||||||
turn: { id: "turn-1", status: "completed" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await run;
|
await run;
|
||||||
|
|
||||||
|
expectResumeRequest(requests, {
|
||||||
|
threadId: "thread-existing",
|
||||||
|
model: "gpt-5.4-codex",
|
||||||
|
modelProvider: "openai",
|
||||||
|
approvalPolicy: "on-request",
|
||||||
|
approvalsReviewer: "guardian_subagent",
|
||||||
|
sandbox: "danger-full-access",
|
||||||
|
serviceTier: "priority",
|
||||||
|
persistExtendedHistory: true,
|
||||||
|
});
|
||||||
expect(requests).toEqual(
|
expect(requests).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
{
|
|
||||||
method: "thread/resume",
|
|
||||||
params: {
|
|
||||||
threadId: "thread-existing",
|
|
||||||
model: "gpt-5.4-codex",
|
|
||||||
modelProvider: "openai",
|
|
||||||
approvalPolicy: "on-request",
|
|
||||||
approvalsReviewer: "guardian_subagent",
|
|
||||||
sandbox: "danger-full-access",
|
|
||||||
serviceTier: "priority",
|
|
||||||
persistExtendedHistory: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
method: "turn/start",
|
method: "turn/start",
|
||||||
params: expect.objectContaining({
|
params: expect.objectContaining({
|
||||||
|
|||||||
Reference in New Issue
Block a user