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) {
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) {
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 } });
}
// backendProfile is run-scoped, not session/PVC-scoped. A HWLAB session must be
// able to switch providers without losing its session storage or workspace.
}
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 assertRunnerJobStatus(client, context);
assertThreadResumeStandard(context);
await assertSessionProfileIsolation(client, context);
await assertSessionProfileSwitchAllowed(client, context);
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 {
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);
}
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 };
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(
() => client.post("/api/v1/runs", runPayload(context, "deepseek", "selftest-profile-boundary-session")),
(error) => error instanceof Error && error.message.includes("backendProfile boundary"),
);
const second = await client.post("/api/v1/runs", runPayload(context, "deepseek", "selftest-profile-boundary-session")) as { id: string; backendProfile?: string; sessionRef?: { sessionId?: string } };
assert.ok(second.id);
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> {