fix(manager): allow session provider switches (#240)

This commit is contained in:
Lyon
2026-06-24 10:17:18 +08:00
committed by GitHub
parent 0f9e742ed1
commit 685a0504f5
2 changed files with 9 additions and 10 deletions
+2 -3
View File
@@ -685,9 +685,8 @@ export function assertSessionBoundary(existing: SessionRecord, input: CreateRunI
if (existing.tenantId !== input.tenantId || existing.projectId !== input.projectId) { if (existing.tenantId !== input.tenantId || existing.projectId !== input.projectId) {
throw new AgentRunError("tenant-policy-denied", "sessionRef cannot be reused across tenant or project boundary", { httpStatus: 403, details: { sessionId: existing.sessionId, valuesPrinted: false } }); throw new AgentRunError("tenant-policy-denied", "sessionRef cannot be reused across tenant or project boundary", { httpStatus: 403, details: { sessionId: existing.sessionId, valuesPrinted: false } });
} }
if (existing.backendProfile !== input.backendProfile) { // backendProfile is run-scoped, not session/PVC-scoped. A HWLAB session must be
throw new AgentRunError("schema-invalid", "sessionRef cannot be reused across backendProfile boundary", { httpStatus: 400, details: { sessionId: existing.sessionId, existingBackendProfile: existing.backendProfile, requestedBackendProfile: input.backendProfile, valuesPrinted: false } }); // able to switch providers without losing its session storage or workspace.
}
} }
export function statusFromTerminal(terminalStatus: TerminalStatus): RunRecord["status"] { export function statusFromTerminal(terminalStatus: TerminalStatus): RunRecord["status"] {
@@ -45,9 +45,9 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
await assertEventContractAndCompletedSemantics(client, context, server.baseUrl); await assertEventContractAndCompletedSemantics(client, context, server.baseUrl);
await assertRunnerJobStatus(client, context); await assertRunnerJobStatus(client, context);
assertThreadResumeStandard(context); assertThreadResumeStandard(context);
await assertSessionProfileIsolation(client, context); await assertSessionProfileSwitchAllowed(client, context);
await assertResourceBundleFailure(client, context, server.baseUrl); await assertResourceBundleFailure(client, context, server.baseUrl);
return { name: "hwlab-baseline-contract", tests: ["event-contract", "result-completed-terminal-only", "bounded-output-summary", "runner-job-status", "thread-resume-standard", "backend-preflight-redacted", "session-profile-isolation", "resource-bundle-failure-kind"] }; return { name: "hwlab-baseline-contract", tests: ["event-contract", "result-completed-terminal-only", "bounded-output-summary", "runner-job-status", "thread-resume-standard", "backend-preflight-redacted", "session-profile-switch", "resource-bundle-failure-kind"] };
} finally { } finally {
await new Promise<void>((resolve) => server.server.close(() => resolve())); await new Promise<void>((resolve) => server.server.close(() => resolve()));
} }
@@ -166,13 +166,13 @@ function assertThreadResumeStandard(context: SelfTestContext): void {
assert.equal(backendTurnOptions({ ...run, sessionRef: { sessionId: "selftest-thread-standard-session" } }, command).threadId, undefined); assert.equal(backendTurnOptions({ ...run, sessionRef: { sessionId: "selftest-thread-standard-session" } }, command).threadId, undefined);
} }
async function assertSessionProfileIsolation(client: ManagerClient, context: SelfTestContext): Promise<void> { async function assertSessionProfileSwitchAllowed(client: ManagerClient, context: SelfTestContext): Promise<void> {
const first = await client.post("/api/v1/runs", runPayload(context, "codex", "selftest-profile-boundary-session")) as { id: string }; const first = await client.post("/api/v1/runs", runPayload(context, "codex", "selftest-profile-boundary-session")) as { id: string };
await client.patch(`/api/v1/runs/${first.id}/status`, { terminalStatus: "completed", failureKind: null, failureMessage: null, threadId: "thread_codex_profile_boundary", turnId: "turn_profile_boundary" }); await client.patch(`/api/v1/runs/${first.id}/status`, { terminalStatus: "completed", failureKind: null, failureMessage: null, threadId: "thread_codex_profile_boundary", turnId: "turn_profile_boundary" });
await assert.rejects( const second = await client.post("/api/v1/runs", runPayload(context, "deepseek", "selftest-profile-boundary-session")) as { id: string; backendProfile?: string; sessionRef?: { sessionId?: string } };
() => client.post("/api/v1/runs", runPayload(context, "deepseek", "selftest-profile-boundary-session")), assert.ok(second.id);
(error) => error instanceof Error && error.message.includes("backendProfile boundary"), assert.equal(second.backendProfile, "deepseek");
); assert.equal(second.sessionRef?.sessionId, "selftest-profile-boundary-session");
} }
async function assertResourceBundleFailure(client: ManagerClient, context: SelfTestContext, managerUrl: string): Promise<void> { async function assertResourceBundleFailure(client: ManagerClient, context: SelfTestContext, managerUrl: string): Promise<void> {