feat: 支持同 run runner 多轮 command

This commit is contained in:
Codex
2026-06-01 22:33:26 +08:00
parent 5fb008f5cf
commit 6fb8f7483a
14 changed files with 237 additions and 81 deletions
+16 -10
View File
@@ -14,28 +14,29 @@ const selfTest: SelfTestCase = async (context) => {
try {
const client = new ManagerClient(server.baseUrl);
const happy = await createRunWithCommand(client, context, "hello", "selftest-turn", 15_000);
const result = await runOnce({ managerUrl: server.baseUrl, runId: happy.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome } });
const result = await runOnce({ managerUrl: server.baseUrl, runId: happy.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome }, oneShot: true });
assert.equal(result.terminalStatus, "completed");
assert.equal(typeof (result.runner as { id?: unknown }).id, "string");
const events = await client.get(`/api/v1/runs/${happy.runId}/events?afterSeq=0&limit=100`) as { items?: Array<{ type: string; payload: unknown }> };
assert.ok(events.items?.some((event) => event.type === "assistant_message"));
assert.ok(events.items?.some((event) => event.type === "backend_status" && JSON.stringify(event.payload).includes("run-claimed")));
assertNoSecretLeak(events);
const finalRun = await client.get(`/api/v1/runs/${happy.runId}`) as { terminalStatus?: string };
assert.equal(finalRun.terminalStatus, "completed");
const finalRun = await client.get(`/api/v1/runs/${happy.runId}`) as { terminalStatus?: string | null; status?: string };
assert.equal(finalRun.terminalStatus, null);
assert.equal(finalRun.status, "claimed");
const finalCommand = await client.get(`/api/v1/runs/${happy.runId}/commands/${happy.commandId}`) as { state?: string };
assert.equal(finalCommand.state, "completed");
const projectedHome = path.join(context.tmp, "runtime-codex-home");
const projected = await createRunWithCommand(client, { workspace: context.workspace, codexHome: projectedHome }, "hello projected", "selftest-projected-codex-home", 15_000);
const projectedResult = await runOnce({ managerUrl: server.baseUrl, runId: projected.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: projectedHome, env: { CODEX_HOME: projectedHome, AGENTRUN_CODEX_SECRET_HOME: context.codexHome } });
const projectedResult = await runOnce({ managerUrl: server.baseUrl, runId: projected.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: projectedHome, env: { CODEX_HOME: projectedHome, AGENTRUN_CODEX_SECRET_HOME: context.codexHome }, oneShot: true });
assert.equal(projectedResult.terminalStatus, "completed");
await access(path.join(projectedHome, "auth.json"));
await access(path.join(projectedHome, "config.toml"));
const deepseekHome = path.join(context.tmp, "runtime-deepseek-home");
const deepseek = await createRunWithCommand(client, { ...context, backendProfile: "deepseek" }, "hello deepseek", "selftest-deepseek-turn", 15_000);
const deepseekResult = await runOnce({ managerUrl: server.baseUrl, runId: deepseek.runId, backendProfile: "deepseek", codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: deepseekHome, env: { CODEX_HOME: deepseekHome, AGENTRUN_CODEX_SECRET_HOME: context.deepseekHome } });
const deepseekResult = await runOnce({ managerUrl: server.baseUrl, runId: deepseek.runId, backendProfile: "deepseek", codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: deepseekHome, env: { CODEX_HOME: deepseekHome, AGENTRUN_CODEX_SECRET_HOME: context.deepseekHome }, oneShot: true });
assert.equal(deepseekResult.terminalStatus, "completed");
await access(path.join(deepseekHome, "auth.json"));
await access(path.join(deepseekHome, "config.toml"));
@@ -49,12 +50,12 @@ const selfTest: SelfTestCase = async (context) => {
);
const configModel = await createRunWithCommand(client, context, "hello config model", "selftest-config-model", 15_000);
const configModelResult = await runOnce({ managerUrl: server.baseUrl, runId: configModel.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "reject-unexpected-model" } });
const configModelResult = await runOnce({ managerUrl: server.baseUrl, runId: configModel.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "reject-unexpected-model" }, oneShot: true });
assert.equal(configModelResult.terminalStatus, "completed", "unspecified model should be omitted so Codex config.toml remains authoritative");
const explicitModel = await createRunWithCommand(client, context, "hello explicit model placeholder", "selftest-explicit-model-placeholder", 15_000);
const explicitCommand = await client.post(`/api/v1/runs/${explicitModel.runId}/commands`, { type: "turn", payload: { prompt: "hello explicit model", model: "gpt-5.5" }, idempotencyKey: "selftest-explicit-model-command" }) as { id: string };
const explicitModelResult = await runOnce({ managerUrl: server.baseUrl, runId: explicitModel.runId, commandId: explicitCommand.id, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "require-explicit-model" } });
const explicitModelResult = await runOnce({ managerUrl: server.baseUrl, runId: explicitModel.runId, commandId: explicitCommand.id, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_FAKE_CODEX_MODE: "require-explicit-model" }, oneShot: true });
assert.equal(explicitModelResult.terminalStatus, "completed", "explicit command payload model should still be forwarded");
await runFailureCase({ client, managerUrl: server.baseUrl, context, mode: "missing-turn-result", expectedStatus: "failed", expectedFailureKind: "backend-response-invalid" });
@@ -83,6 +84,7 @@ async function runFailureCase(options: { client: ManagerClient; managerUrl: stri
codexArgs: options.context.fakeCodexArgs,
codexHome: options.context.codexHome,
env: { CODEX_HOME: options.context.codexHome, AGENTRUN_FAKE_CODEX_MODE: options.mode },
oneShot: true,
}) as JsonRecord;
assert.equal(result.terminalStatus, options.expectedStatus, options.mode);
assert.equal(result.failureKind, options.expectedFailureKind, options.mode);
@@ -113,12 +115,15 @@ async function runSecretFailureCase(options: { client: ManagerClient; managerUrl
codexArgs: options.context.fakeCodexArgs,
codexHome: path.join(options.context.tmp, "missing-codex-home"),
env: { CODEX_HOME: path.join(options.context.tmp, "missing-codex-home") },
oneShot: true,
}) as JsonRecord;
assert.equal(result.terminalStatus, "blocked", "secret unavailable");
assert.equal(result.failureKind, "secret-unavailable", "secret unavailable");
const run = await options.client.get(`/api/v1/runs/${item.runId}`) as { status?: string; failureKind?: string };
assert.equal(run.status, "blocked", "secret unavailable");
assert.equal(run.failureKind, "secret-unavailable", "secret unavailable");
const command = await options.client.get(`/api/v1/runs/${item.runId}/commands/${item.commandId}`) as { state?: string };
assert.equal(command.state, "failed", "secret unavailable command state");
const envelope = await options.client.get(`/api/v1/runs/${item.runId}/commands/${item.commandId}/result`) as JsonRecord;
assert.equal(envelope.terminalStatus, "failed", "secret unavailable result terminal");
assert.equal(envelope.failureKind, "secret-unavailable", "secret unavailable result kind");
}
async function runSpawnFailureCase(options: { client: ManagerClient; managerUrl: string; context: SelfTestContext }): Promise<void> {
@@ -130,6 +135,7 @@ async function runSpawnFailureCase(options: { client: ManagerClient; managerUrl:
codexArgs: [],
codexHome: options.context.codexHome,
env: { CODEX_HOME: options.context.codexHome },
oneShot: true,
}) as JsonRecord;
assert.equal(result.terminalStatus, "failed", "spawn failure");
assert.equal(result.failureKind, "backend-spawn-failed", "spawn failure");