Skip to content

Commit 51c1c08

Browse files
authored
fix(ui): preserve desktop lobster run outcomes (#103501)
1 parent fc507c7 commit 51c1c08

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

ui/src/components/app-sidebar.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ type SidebarLifecycleState = HTMLElement & {
3737
sessionsAgentId: string | null;
3838
sessionsResult: SessionsListResult | null;
3939
updateComplete: Promise<boolean>;
40+
variant: "panel" | "drawer";
41+
};
42+
43+
type LobsterPetElement = HTMLElement & {
44+
runOutcome: "ok" | "error" | "aborted";
4045
};
4146

4247
function createGatewayHarness(client: GatewayBrowserClient) {
@@ -157,11 +162,16 @@ function createContext(
157162
} as unknown as ApplicationContext<RouteId>;
158163
}
159164

160-
async function mountSidebar(gateway: ApplicationGateway, sessions: SessionCapability) {
165+
async function mountSidebar(
166+
gateway: ApplicationGateway,
167+
sessions: SessionCapability,
168+
variant: SidebarLifecycleState["variant"] = "panel",
169+
) {
161170
const provider = document.createElement(PROVIDER_ELEMENT_NAME) as AppSidebarContextProvider;
162171
const sidebar = document.createElement(
163172
"openclaw-app-sidebar",
164173
) as unknown as SidebarLifecycleState;
174+
sidebar.variant = variant;
165175
provider.setContext(createContext(gateway, sessions));
166176
provider.append(sidebar);
167177
document.body.append(provider);
@@ -173,6 +183,50 @@ afterEach(() => {
173183
document.body.replaceChildren();
174184
});
175185

186+
describe("AppSidebar lobster outcome wiring", () => {
187+
it.each([
188+
["panel", "failed", "error"],
189+
["panel", "killed", "aborted"],
190+
["drawer", "failed", "error"],
191+
["drawer", "killed", "aborted"],
192+
] as const)(
193+
"passes the %s variant's latest %s session outcome",
194+
async (variant, status, expectedOutcome) => {
195+
const client = {} as GatewayBrowserClient;
196+
const gateway = createGateway(client);
197+
const sessions = createSessionsHarness("main", ["agent:main:main"]);
198+
const { sidebar } = await mountSidebar(gateway, sessions.sessions, variant);
199+
const terminalState = createSessionState("main", ["agent:main:main"]);
200+
const result = terminalState.result;
201+
if (!result) {
202+
throw new Error("expected terminal session result");
203+
}
204+
const row = result.sessions[0];
205+
if (!row) {
206+
throw new Error("expected terminal session row");
207+
}
208+
209+
sessions.publishList({
210+
result: {
211+
...result,
212+
sessions: [
213+
{
214+
...row,
215+
status,
216+
endedAt: 100,
217+
},
218+
],
219+
},
220+
agentId: terminalState.agentId,
221+
});
222+
await sidebar.updateComplete;
223+
224+
const pet = sidebar.querySelector<LobsterPetElement>("openclaw-lobster-pet");
225+
expect(pet?.runOutcome).toBe(expectedOutcome);
226+
},
227+
);
228+
});
229+
176230
describe("AppSidebar session source lifecycle", () => {
177231
it("resets cached rows and creation order when the sessions source changes", async () => {
178232
const client = {} as GatewayBrowserClient;

ui/src/components/app-sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,7 @@ class AppSidebar extends OpenClawLightDomContentsElement {
16451645
<openclaw-lobster-pet
16461646
.seed=${lobsterPetSeed(this.sessionKey)}
16471647
.mode=${resolveLobsterPetMode(this.connected, this.sessionsResult?.sessions)}
1648+
.runOutcome=${resolveLobsterRunOutcome(this.sessionsResult?.sessions)}
16481649
.visitsEnabled=${this.lobsterPetVisits}
16491650
></openclaw-lobster-pet>
16501651
</div>

ui/src/e2e/sidebar-customization.e2e.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,53 @@ describeControlUiE2e("Control UI sidebar customization mocked Gateway E2E", () =
397397
await context.close();
398398
}
399399
});
400+
401+
it("passes failed run outcomes to both desktop and drawer lobsters", async () => {
402+
const context = await browser.newContext({
403+
locale: "en-US",
404+
serviceWorkers: "block",
405+
viewport: { height: 900, width: 1440 },
406+
});
407+
const page = await context.newPage();
408+
await installMockGateway(page, {
409+
methodResponses: {
410+
"sessions.list": {
411+
count: 1,
412+
defaults: {
413+
contextTokens: null,
414+
model: "gpt-5.5",
415+
modelProvider: "openai",
416+
},
417+
path: "",
418+
sessions: [
419+
{
420+
endedAt: 100,
421+
key: "main",
422+
kind: "direct",
423+
status: "failed",
424+
updatedAt: 100,
425+
},
426+
],
427+
ts: 100,
428+
},
429+
},
430+
});
431+
432+
const outcome = (locator: Locator) =>
433+
locator.evaluate((element) => (element as HTMLElement & { runOutcome: string }).runOutcome);
434+
435+
try {
436+
await page.goto(`${server.baseUrl}overview`);
437+
const sidebar = page.locator("openclaw-app-sidebar");
438+
const desktopPet = sidebar.locator(".sidebar-panel openclaw-lobster-pet");
439+
await expect.poll(() => outcome(desktopPet)).toBe("error");
440+
441+
await page.setViewportSize({ height: 900, width: 900 });
442+
await page.locator(".topbar-nav-toggle").click();
443+
const drawerPet = sidebar.locator(".sidebar-shell openclaw-lobster-pet");
444+
await expect.poll(() => outcome(drawerPet)).toBe("error");
445+
} finally {
446+
await context.close();
447+
}
448+
});
400449
});

0 commit comments

Comments
 (0)