fix: gate direct session creation by workspace policy
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { apiFetch } from "./client";
|
||||
import { getSettings } from "./settings";
|
||||
import { workspacePolicyGate, workspacePreferences, type WorkspacePreference } from "../workspaces/drafts";
|
||||
import { getWorkspace, listWorkspaces } from "./workspaces";
|
||||
import {
|
||||
WORKSPACE_POLICY_ERROR, WORKSPACE_SUMMARY_ERROR, WorkspaceSelectionError, workspacePolicyGate,
|
||||
workspacePreferences, type WorkspacePreference,
|
||||
} from "../workspaces/drafts";
|
||||
import type {
|
||||
Principal, ResumeSessionResult, SessionScope, SessionSummary, SessionDocument, UiResponse,
|
||||
} from "./types";
|
||||
@@ -22,9 +26,83 @@ async function selectedPreferences(): Promise<WorkspacePreference> {
|
||||
});
|
||||
}
|
||||
|
||||
function reconcileWorkspacePolicy(preferences: WorkspacePreference, allowed: readonly string[], defaultModel?: string) {
|
||||
if (allowed.length === 0) throw new WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
|
||||
const selected = preferences.provider && preferences.model
|
||||
? `${preferences.provider}/${preferences.model}`
|
||||
: undefined;
|
||||
if (selected && allowed.includes(selected)) return preferences;
|
||||
const replacement = defaultModel && allowed.includes(defaultModel) ? defaultModel : allowed[0];
|
||||
const separator = replacement.indexOf("/");
|
||||
if (separator <= 0 || separator === replacement.length - 1) {
|
||||
throw new WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
|
||||
}
|
||||
return workspacePreferences.save({
|
||||
...preferences,
|
||||
provider: replacement.slice(0, separator),
|
||||
model: replacement.slice(separator + 1),
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureWorkspaceSelectionPolicy(): Promise<WorkspacePreference> {
|
||||
while (true) {
|
||||
const preferences = workspacePreferences.load();
|
||||
const workspaceId = preferences.workspaceId;
|
||||
if (!workspaceId) return preferences;
|
||||
workspacePolicyGate.beginSummary(workspaceId);
|
||||
let workspace;
|
||||
try {
|
||||
workspace = (await listWorkspaces()).find((candidate) => candidate.id === workspaceId);
|
||||
} catch {
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
workspacePolicyGate.rejectSummary(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_SUMMARY_ERROR);
|
||||
}
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
if (!workspace?.revision) {
|
||||
workspacePolicyGate.allowLegacy(workspaceId);
|
||||
return workspacePreferences.load();
|
||||
}
|
||||
workspacePolicyGate.select(workspaceId);
|
||||
const outcome = await Promise.race([
|
||||
getWorkspace(workspaceId).then(
|
||||
(record) => ({ kind: "record" as const, record }),
|
||||
() => ({ kind: "error" as const }),
|
||||
),
|
||||
workspacePolicyGate.waitForCurrent(() => workspacePreferences.load()).then(
|
||||
(selection) => ({ kind: "selection" as const, selection }),
|
||||
),
|
||||
]);
|
||||
if (outcome.kind === "selection") {
|
||||
if (outcome.selection.workspaceId !== workspaceId) continue;
|
||||
return outcome.selection;
|
||||
}
|
||||
if (outcome.kind === "error") {
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
workspacePolicyGate.reject(workspaceId);
|
||||
throw new WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
|
||||
}
|
||||
const { record } = outcome;
|
||||
if (workspacePreferences.load().workspaceId !== workspaceId) continue;
|
||||
let selection: WorkspacePreference;
|
||||
try {
|
||||
selection = reconcileWorkspacePolicy(
|
||||
preferences,
|
||||
record.workspace.llm_policy.allowed,
|
||||
record.workspace.llm_policy.default,
|
||||
);
|
||||
} catch (error) {
|
||||
workspacePolicyGate.reject(workspaceId);
|
||||
throw error;
|
||||
}
|
||||
workspacePolicyGate.resolve(workspaceId);
|
||||
if (workspacePreferences.load().workspaceId === workspaceId) return selection;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSession(i: NewSessionInput) {
|
||||
await selectedPreferences();
|
||||
const selection = await workspacePolicyGate.waitForCurrent(() => workspacePreferences.load());
|
||||
const selection = await ensureWorkspaceSelectionPolicy();
|
||||
return apiFetch<{ id: string }>("/sessions", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...i, ...selection }),
|
||||
|
||||
Reference in New Issue
Block a user