fix(agentrun): 继承 Aipod 会话持久身份
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user