Remove AI config approval flow

This commit is contained in:
npc0-hue
2026-09-22 15:33:32 +08:00
parent 20008b4043
commit a8b5d483a3
54 changed files with 364 additions and 875 deletions
+38 -49
View File
@@ -165,7 +165,7 @@ async function main() {
{
name: "AI 提供商管理",
hash: "#/aiProviders",
markers: ["AI 提供商管理", "平台 API", aiProvider.name, "密钥状态", "已配置", "AI 配置审查", pluginSeed.diff.diffSummary, server.id]
markers: ["AI 提供商管理", "平台 API", aiProvider.name, "密钥状态", "已配置"]
},
{
name: "系统维护",
@@ -204,7 +204,7 @@ async function main() {
evidence.pluginInteractions = {
pluginLifecycle: await verifyPluginLifecycleInteraction(chrome, authHeaders, plugin, server),
aiDiffApproval: await verifyAIConfigDiffInteraction(chrome, authHeaders, pluginSeed.diff)
aiDirectExecution: await verifyAIDirectExecution(chrome, authHeaders, server)
};
evidence.walkthroughs = await verifyResponsiveThemeWalkthroughs(chrome, routeChecks, server);
@@ -579,29 +579,29 @@ async function preparePluginOperations(headers, server, plugin) {
requestId: `browser-acceptance-ai-config-${stamp}`,
serverInstanceId: server.id,
purpose: "config.suggest",
prompt: "Keep existing settings and add a reviewed max players recommendation."
prompt: "Keep existing settings and add a max players recommendation."
},
headers
);
if (aiInvocation.status !== "ok" || !aiInvocation.configRecommendation?.diffId) {
throw new Error(`AI invocation did not persist a reviewable diff: ${JSON.stringify(aiInvocation)}`);
if (aiInvocation.status !== "ok" || !aiInvocation.configRecommendation?.key || !aiInvocation.configExecution?.job?.id || aiInvocation.configExecution.job.capability !== "config.write") {
throw new Error(`AI invocation did not dispatch a config.write job: ${JSON.stringify(aiInvocation)}`);
}
const [lifecycles, diffs] = await Promise.all([
getJson(`/plugin-lifecycles?pluginId=${encodeURIComponent(plugin.id)}&serverInstanceId=${encodeURIComponent(server.id)}`, headers),
getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(server.id)}`, headers)
]);
const jobs = await getJson(`/jobs?serverInstanceId=${encodeURIComponent(server.id)}`, headers);
const lifecycles = await getJson(`/plugin-lifecycles?pluginId=${encodeURIComponent(plugin.id)}&serverInstanceId=${encodeURIComponent(server.id)}`, headers);
const installation = findRequired(lifecycles.items, (item) => item.id === lifecycle.installation.id && item.jobId === lifecycle.job.id, "durable plugin lifecycle installation");
const diff = findRequired(diffs.items, (item) => item.id === aiInvocation.configRecommendation.diffId && item.state === "pending", "pending AI config diff");
const configJob = findRequired(jobs.items, (item) => item.id === aiInvocation.configExecution.job.id && item.capability === "config.write", "AI config write job");
for (const [label, value] of Object.entries({ installation, diff, aiInvocation })) {
for (const [label, value] of Object.entries({ installation, configJob, aiInvocation })) {
assertNoForbiddenProjection(value, `plugin operations seed ${label}`);
}
return {
diff,
apiProof: {
pluginLifecycle: pick(installation, ["id", "pluginId", "serverInstanceId", "currentVersion", "targetVersion", "desiredState", "currentState", "lastOperation", "compatibility", "dependencyState", "jobId"]),
aiConfigDiff: pick(diff, ["id", "requestId", "serverInstanceId", "pluginId", "providerId", "model", "key", "configVersion", "diffSummary", "state", "expiresAt"])
aiConfigExecution: {
recommendation: pick(aiInvocation.configRecommendation, ["key", "diffSummary"]),
job: pick(configJob, ["id", "serverInstanceId", "runEndpointId", "capability", "targetKey", "state", "resultRef"])
}
}
};
}
@@ -649,46 +649,35 @@ async function verifyPluginLifecycleInteraction(chrome, headers, plugin, server)
};
}
async function verifyAIConfigDiffInteraction(chrome, headers, seededDiff) {
await chrome.navigate(`${webUrl}/#/aiProviders`);
await chrome.waitForText(["AI 配置审查", seededDiff.serverInstanceId, seededDiff.diffSummary, "审查并批准"], "AI config diff review");
async function verifyAIDirectExecution(chrome, headers, server) {
await chrome.navigate(`${webUrl}/#/servers/${encodeURIComponent(server.id)}`);
await chrome.waitForText([server.name, "AI 助手"], "server AI assistant");
await chrome.evaluate(() => {
const button = Array.from(document.querySelectorAll(".ai-diff-review-panel button")).find((item) => item.textContent?.includes("审查并批准"));
if (!(button instanceof HTMLButtonElement)) throw new Error("AI diff review button not found");
const tab = Array.from(document.querySelectorAll(".section-tab")).find((item) => item.textContent?.trim() === "AI 助手");
if (!(tab instanceof HTMLButtonElement)) throw new Error("AI assistant tab not found");
tab.click();
});
await chrome.waitForText(["AI 配置助手", "生成建议"], "AI direct execution panel");
await chrome.evaluate(() => {
const textarea = document.querySelector("textarea");
if (!(textarea instanceof HTMLTextAreaElement)) throw new Error("AI prompt textarea not found");
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
setter.call(textarea, "Keep existing settings and adjust max players.");
textarea.dispatchEvent(new Event("input", { bubbles: true }));
textarea.dispatchEvent(new Event("change", { bubbles: true }));
});
await chrome.evaluate(() => {
const button = Array.from(document.querySelectorAll("button")).find((item) => item.textContent?.trim() === "生成建议");
if (!(button instanceof HTMLButtonElement)) throw new Error("AI suggestion button not found");
button.click();
});
await chrome.waitForText(["批准 AI 配置差异", seededDiff.id, "取消"], "AI diff approval confirmation");
await chrome.evaluate(() => {
const cancel = document.querySelector(".confirm-panel .confirm-actions button");
if (!(cancel instanceof HTMLButtonElement)) throw new Error("AI diff approval cancel button not found");
cancel.click();
});
const pendingResponse = await getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(seededDiff.serverInstanceId)}`, headers);
const pending = findRequired(pendingResponse.items, (item) => item.id === seededDiff.id, "AI diff after approval cancel");
assertEqual(pending.state, "pending", "cancel keeps AI diff pending");
await chrome.evaluate(() => {
const button = Array.from(document.querySelectorAll(".ai-diff-review-panel button")).find((item) => item.textContent?.includes("审查并批准"));
if (!(button instanceof HTMLButtonElement)) throw new Error("AI diff review button not found after cancel");
button.click();
});
await chrome.waitForText(["批准 AI 配置差异", seededDiff.id], "AI diff approval confirmation reopen");
await chrome.evaluate(() => {
const confirm = document.querySelector(".confirm-panel .confirm-primary");
if (!(confirm instanceof HTMLButtonElement)) throw new Error("AI diff approval submit button not found");
confirm.click();
});
await chrome.waitForText(["已审批", "写入任务"], "AI diff durable approval");
const approvedResponse = await getJson(`/ai/config-diffs?serverInstanceId=${encodeURIComponent(seededDiff.serverInstanceId)}`, headers);
const approved = findRequired(approvedResponse.items, (item) => item.id === seededDiff.id, "approved AI config diff");
assertEqual(approved.state, "approved", "browser AI diff approval persisted");
if (!approved.jobId || !approved.approvedBy || !approved.approvedAt) {
throw new Error(`approved AI diff missed durable approval linkage: ${JSON.stringify(approved)}`);
}
assertNoForbiddenProjection(approved, "approved AI diff response");
await chrome.waitForText(["AI 配置写入已派发", "config.write", "AI 写入任务"], "AI direct config job");
const jobs = await getJson(`/jobs?serverInstanceId=${encodeURIComponent(server.id)}`, headers);
const configJob = findRequired(jobs.items, (item) => item.capability === "config.write", "browser AI config write job");
const terminalSSEPath = `/api/v1/server-instances/${encodeURIComponent(server.id)}/logs/events?jobId=${encodeURIComponent(configJob.id)}`;
return {
cancelPreservedState: pending.state,
persisted: pick(approved, ["id", "serverInstanceId", "state", "approvedBy", "approvedAt", "jobId", "configVersion", "currentConfigChecksum"]),
persisted: pick(configJob, ["id", "serverInstanceId", "runEndpointId", "capability", "targetKey", "state", "resultRef"]),
terminalSSEPath,
forbiddenFragmentScan: "passed",
textSample: (await chrome.visibleText()).slice(0, 1200)
};