fix(agentrun): 继承 Aipod 会话持久身份

This commit is contained in:
Codex
2026-07-13 10:36:04 +02:00
parent 7340302ae4
commit eb63823b66
2 changed files with 233 additions and 1 deletions
+172
View File
@@ -1332,6 +1332,178 @@ describe("AgentRun default transport contract", () => {
rmSync(tempConfigPath, { force: true });
}
});
test("Aipod follow-up inherits cancelled durable session identity and keeps boundary conflicts fail-closed", async () => {
const sessionId = "sess_artificer_cancelled_selfmedia";
const durableSession = {
sessionId,
tenantId: "unidesk",
projectId: "pikainc/selfmedia",
providerId: "NC01",
backendProfile: "codex",
conversationId: "conv_selfmedia",
threadId: "thread_selfmedia",
metadata: { lastTurnId: "turn_cancelled", durable: true },
executionState: "terminal",
terminalStatus: "cancelled",
failureKind: "cancelled",
};
const renderBodies: Record<string, any>[] = [];
const sendBodies: Record<string, any>[] = [];
let sessionReads = 0;
const server = Bun.serve({
port: 0,
async fetch(request) {
const url = new URL(request.url);
expect(request.headers.get("authorization")).toBe("Bearer secret-value");
if (request.method === "GET" && url.pathname === `/api/v1/sessions/${sessionId}`) {
sessionReads += 1;
return Response.json({ ok: true, data: durableSession });
}
if (request.method === "POST" && url.pathname === "/api/v1/aipod-specs/Artificer/render") {
const body = await request.json() as Record<string, any>;
renderBodies.push(body);
return Response.json({
ok: true,
data: {
queueTask: {
tenantId: body.tenantId ?? "unidesk",
projectId: body.projectId ?? "default",
queue: "commander",
lane: "v0.2",
title: "Artificer cancelled continuation",
backendProfile: "codex",
providerId: "NC01",
workspaceRef: { kind: "opaque", path: "." },
sessionRef: body.sessionRef ?? { sessionId },
executionPolicy: {
sandbox: "workspace-write",
approval: "never",
timeoutMs: 1_800_000,
network: "enabled",
secretScope: {
allowCredentialEcho: false,
providerCredentials: [{
profile: "codex",
secretRef: { name: "agentrun-v01-provider-codex", keys: ["auth.json", "config.toml"] },
}],
},
},
resourceBundleRef: null,
payload: { prompt: body.prompt },
},
dispatchDefaults: { runnerJob: {} },
},
});
}
if (request.method === "POST" && url.pathname === `/api/v1/sessions/${sessionId}/send`) {
const body = await request.json() as Record<string, any>;
sendBodies.push(body);
if (body.run?.tenantId !== durableSession.tenantId || body.run?.projectId !== durableSession.projectId) {
return Response.json({
ok: false,
failureKind: "tenant-policy-denied",
message: "sessionRef cannot be reused across tenant or project boundary",
details: { sessionId, valuesPrinted: false },
}, { status: 403 });
}
return Response.json({
ok: true,
data: {
action: "session-send-plan",
dryRun: true,
mutation: false,
sessionId,
decision: "turn",
internalCommandType: "turn",
activeBefore: null,
request: { method: "POST", path: `/api/v1/sessions/${sessionId}/send`, commandType: "turn", createRunnerJob: true, valuesPrinted: false },
next: { confirm: { action: "send-session", operation: "send", resourceKind: "session", resourceName: sessionId } },
valuesPrinted: false,
},
});
}
return new Response("not found", { status: 404 });
},
});
const previousConfig = process.env.AGENTRUN_CLIENT_CONFIG;
const previousKey = process.env.HWLAB_API_KEY;
process.env.HWLAB_API_KEY = "secret-value";
const tempConfigPath = `/tmp/unidesk-agentrun-cancelled-session-test-${process.pid}-${Date.now()}.yaml`;
try {
process.env.AGENTRUN_CLIENT_CONFIG = tempConfigPath;
await Bun.write(tempConfigPath, agentRunMinimalClientYaml.replace("http://agentrun.example.local:8080", server.url.href.replace(/\/$/u, "")));
const continued = await runAgentRunCommand(null, [
"send",
`session/${sessionId}`,
"--aipod",
"Artificer",
"--prompt",
"continue cancelled selfmedia task",
"--dry-run",
"-o",
"json",
]);
expect(continued.ok).toBe(true);
expect(JSON.parse(renderedTextOf(continued))).toMatchObject({
kind: "SessionSendPlan",
decision: "turn",
dryRun: true,
mutation: false,
});
expect(sessionReads).toBe(1);
expect(renderBodies).toHaveLength(1);
expect(renderBodies[0]).toMatchObject({
tenantId: durableSession.tenantId,
projectId: durableSession.projectId,
sessionId,
sessionRef: {
sessionId,
conversationId: durableSession.conversationId,
threadId: durableSession.threadId,
metadata: durableSession.metadata,
},
});
expect(sendBodies).toHaveLength(1);
expect(sendBodies[0]?.run).toMatchObject({
tenantId: durableSession.tenantId,
projectId: durableSession.projectId,
sessionRef: {
sessionId,
conversationId: durableSession.conversationId,
threadId: durableSession.threadId,
metadata: durableSession.metadata,
},
});
const conflict = await runAgentRunCommand(null, [
"send",
`session/${sessionId}`,
"--aipod",
"Artificer",
"--project-id",
"pikasTech/unidesk",
"--prompt",
"must stay fail closed",
"--dry-run",
"-o",
"json",
]);
expect(conflict.ok).toBe(false);
expect(renderedTextOf(conflict)).toContain("tenant-policy-denied");
expect(sessionReads).toBe(2);
expect(renderBodies).toHaveLength(1);
expect(sendBodies).toHaveLength(1);
} finally {
server.stop(true);
if (previousConfig === undefined) delete process.env.AGENTRUN_CLIENT_CONFIG;
else process.env.AGENTRUN_CLIENT_CONFIG = previousConfig;
if (previousKey === undefined) delete process.env.HWLAB_API_KEY;
else process.env.HWLAB_API_KEY = previousKey;
rmSync(tempConfigPath, { force: true });
}
});
});
describe("AgentRun YAML tool credential binding", () => {