mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(ui): surface agent config errors and hydrated identity avatars (#129868)
This commit is contained in:
committed by
GitHub
parent
0dabafe9f5
commit
43fb810d89
@@ -18,6 +18,79 @@ const proofDir = path.join(process.cwd(), ".artifacts", "control-ui-e2e", "agent
|
||||
const requireRecord = createRequireRecord("record", "expected-object-value");
|
||||
|
||||
suite.define(() => {
|
||||
it("shows rejected initial configuration loads and recovers when reloaded", async () => {
|
||||
await suite.withPage(
|
||||
{ locale: "en-US", serviceWorkers: "block", viewport: { height: 900, width: 1280 } },
|
||||
async ({ page }) => {
|
||||
const config = { agents: { entries: { main: { default: true } } } };
|
||||
const gateway = await installMockGateway(page, {
|
||||
assistantName: "Main agent",
|
||||
defaultAgentId: "main",
|
||||
methodResponses: {
|
||||
"agents.list": {
|
||||
agents: [{ id: "main", name: "Main agent" }],
|
||||
defaultId: "main",
|
||||
mainKey: "main",
|
||||
scope: "agent",
|
||||
},
|
||||
"config.get": {
|
||||
__mockError: {
|
||||
code: "INTERNAL_ERROR",
|
||||
message: "Agent configuration unavailable; retry Reload Config",
|
||||
retryable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
(await page.goto(`${suite.server.baseUrl}settings/agents/main/overview`))?.status(),
|
||||
).toBe(200);
|
||||
await gateway.waitForRequest("agents.list");
|
||||
await gateway.waitForRequest("config.get");
|
||||
const agentsPage = page.locator("openclaw-agents-page");
|
||||
const reload = agentsPage.getByRole("button", { name: "Reload Config" });
|
||||
await reload.waitFor();
|
||||
if (captureUiProof) {
|
||||
await mkdir(proofDir, { recursive: true });
|
||||
await page.screenshot({
|
||||
animations: "disabled",
|
||||
fullPage: true,
|
||||
path: path.join(
|
||||
proofDir,
|
||||
`agent-config-load-${process.env.OPENCLAW_UI_PROOF_LABEL ?? "failed"}.png`,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const error = agentsPage
|
||||
.getByRole("alert")
|
||||
.filter({ hasText: "Agent configuration unavailable" });
|
||||
await expect.poll(() => error.isVisible()).toBe(true);
|
||||
await expect
|
||||
.poll(() => agentsPage.locator(".model-picker__select").getAttribute("disabled"))
|
||||
.not.toBeNull();
|
||||
|
||||
await gateway.setMethodResponse("config.get", {
|
||||
config,
|
||||
sourceConfig: config,
|
||||
runtimeConfig: config,
|
||||
hash: "recovered-agent-config",
|
||||
issues: [],
|
||||
raw: JSON.stringify(config),
|
||||
valid: true,
|
||||
});
|
||||
const readsBefore = (await gateway.getRequests("config.get")).length;
|
||||
await reload.click();
|
||||
await gateway.waitForRequest("config.get", { after: readsBefore });
|
||||
await expect.poll(() => error.count()).toBe(0);
|
||||
await expect
|
||||
.poll(() => agentsPage.locator(".model-picker__select").getAttribute("disabled"))
|
||||
.toBeNull();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("submits keyed entries and surfaces Gateway validation failures", async () => {
|
||||
await suite.withPage(
|
||||
{
|
||||
|
||||
@@ -164,11 +164,26 @@ suite.define(() => {
|
||||
},
|
||||
async ({ page }) => {
|
||||
const config = { agents: { list: [{ id: "main" }, { id: "emoji" }] } };
|
||||
const hydratedEmojiAgent = { id: "emoji", identity: { name: "Rocket" }, name: "Rocket" };
|
||||
const gateway = await installMockGateway(page, {
|
||||
defaultAgentId: "main",
|
||||
methodResponses: {
|
||||
"agent.identity.get": agentIdentities,
|
||||
"agents.list": agentsList,
|
||||
"agent.identity.get": {
|
||||
cases: [
|
||||
{
|
||||
match: { agentId: "emoji" },
|
||||
response: {
|
||||
agentId: "emoji",
|
||||
avatar: "",
|
||||
avatarStatus: "none",
|
||||
emoji: emojiGrapheme,
|
||||
name: "Rocket",
|
||||
},
|
||||
},
|
||||
agentIdentities.cases[1],
|
||||
],
|
||||
},
|
||||
"agents.list": { ...agentsList, agents: [asciiAgent, hydratedEmojiAgent] },
|
||||
"config.get": {
|
||||
config,
|
||||
sourceConfig: config,
|
||||
@@ -186,16 +201,22 @@ suite.define(() => {
|
||||
await gateway.waitForRequest("config.get");
|
||||
const agentSelect = page.locator("openclaw-agents-page openclaw-agent-select");
|
||||
await agentSelect.locator(".agent-select__trigger").click();
|
||||
await agentSelect.getByRole("menuitemradio", { name: "🚀Rocket", exact: true }).click();
|
||||
await agentSelect.getByRole("menuitemradio", { name: "Rocket", exact: true }).click();
|
||||
await expect.poll(() => new URL(page.url()).pathname).toBe("/settings/agents/emoji/tools");
|
||||
await page.getByRole("tab", { name: "Overview", exact: true }).click();
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname)
|
||||
.toBe("/settings/agents/emoji/overview");
|
||||
await expect
|
||||
.poll(() => page.locator(".agent-identity-editor__emoji input").inputValue())
|
||||
.toBe(emojiGrapheme);
|
||||
await screenshot(
|
||||
page,
|
||||
`03-agents-overview-${process.env.OPENCLAW_UI_PROOF_LABEL ?? "emoji"}.png`,
|
||||
);
|
||||
await expect
|
||||
.poll(() => page.locator(".agent-identity-editor__avatar-text").textContent())
|
||||
.toBe(emojiGrapheme);
|
||||
await screenshot(page, "03-agents-overview-emoji.png");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -952,11 +952,7 @@ class AgentsPage
|
||||
loading: configState.configLoading,
|
||||
saving: configState.configSaving,
|
||||
dirty: configState.configFormDirty,
|
||||
error:
|
||||
configState.configAutoSaveStatus === "error" ||
|
||||
configState.configAutoSaveStatus === "conflict"
|
||||
? configState.lastError
|
||||
: null,
|
||||
error: configState.lastError,
|
||||
},
|
||||
channels: {
|
||||
snapshot: this.context.channels.state.channelsSnapshot,
|
||||
|
||||
@@ -107,7 +107,8 @@ export function renderAgentOverview(params: {
|
||||
const identityAvatarUrl =
|
||||
identityDraft.avatar ?? resolveAgentAvatarUrl(agent, params.agentIdentity);
|
||||
const identityAvatarText =
|
||||
resolveAgentTextAvatar(agent) ?? (deriveAvatarInitial(identityName || agent.id) || "?");
|
||||
resolveAgentTextAvatar(agent, params.agentIdentity) ??
|
||||
(deriveAvatarInitial(identityName || agent.id) || "?");
|
||||
const identityDirty =
|
||||
identityDraft.name !== null || identityDraft.emoji !== null || identityDraft.avatar !== null;
|
||||
const identityInvalid =
|
||||
|
||||
@@ -109,7 +109,7 @@ describe("renderAgents", () => {
|
||||
renderAgents(
|
||||
createProps({
|
||||
agentIdentityById: {
|
||||
beta: { agentId: "beta", name: "Fetched Beta", avatar: "" },
|
||||
beta: { agentId: "beta", name: "Fetched Beta", avatar: "", emoji: "🦊" },
|
||||
},
|
||||
}),
|
||||
),
|
||||
@@ -119,6 +119,7 @@ describe("renderAgents", () => {
|
||||
expect(
|
||||
container.querySelector<HTMLInputElement>(".agent-identity-editor__fields input")?.value,
|
||||
).toBe("Fetched Beta");
|
||||
expect(container.querySelector(".agent-identity-editor__avatar-text")?.textContent).toBe("🦊");
|
||||
});
|
||||
|
||||
it("shows a model-catalog failure and lets the operator retry", () => {
|
||||
|
||||
Reference in New Issue
Block a user