Skip to content

Commit 1c92f2a

Browse files
committed
fix(ui): treat started managed-service handoff as pending update instead of skipped
1 parent 19f7b72 commit 1c92f2a

2 files changed

Lines changed: 44 additions & 7 deletions

File tree

ui/src/app/overlays.test.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,22 +145,54 @@ describe("application approval overlays", () => {
145145
});
146146

147147
describe("application update overlays", () => {
148-
it("surfaces a coalesced restart while reconnect verification remains active", async () => {
148+
it("treats a started managed-service handoff as pending update instead of skipped", async () => {
149149
const request = vi.fn<RequestFn>().mockResolvedValue({
150150
ok: true,
151-
restart: { coalesced: true },
152-
result: { status: "ok", after: { version: "2.0.0" } },
151+
result: {
152+
status: "skipped",
153+
reason: "managed-service-handoff-started",
154+
before: { version: "1.0.0" },
155+
},
156+
handoff: { status: "started", command: "openclaw update --yes --timeout 1800" },
153157
});
154158
const harness = createGatewayHarness(client(request));
159+
harness.update({
160+
hello: {
161+
snapshot: {
162+
updateAvailable: {
163+
currentVersion: "1.0.0",
164+
latestVersion: "2.0.0",
165+
channel: "latest",
166+
},
167+
},
168+
},
169+
} as unknown as Partial<ApplicationGatewaySnapshot>);
155170
const overlays = createApplicationOverlays(harness.gateway);
156171

157172
await overlays.runUpdate();
158173

159174
expect(request).toHaveBeenCalledWith("update.run", {});
160-
expect(overlays.snapshot.updateStatusBanner).toEqual({
161-
tone: "info",
162-
text: "Update installed. A gateway restart is already in progress; status will refresh after it reconnects.",
175+
expect(overlays.snapshot.updateStatusBanner).toBeNull();
176+
expect(overlays.snapshot.updateRunning).toBe(false);
177+
overlays.dispose();
178+
});
179+
180+
it("falls back to null expected version when handoff has no after and no updateAvailable", async () => {
181+
const request = vi.fn<RequestFn>().mockResolvedValue({
182+
ok: true,
183+
result: {
184+
status: "skipped",
185+
reason: "managed-service-handoff-started",
186+
},
187+
handoff: { status: "started", command: "openclaw update --yes --timeout 1800" },
163188
});
189+
const harness = createGatewayHarness(client(request));
190+
const overlays = createApplicationOverlays(harness.gateway);
191+
192+
await overlays.runUpdate();
193+
194+
expect(request).toHaveBeenCalledWith("update.run", {});
195+
expect(overlays.snapshot.updateStatusBanner).toBeNull();
164196
expect(overlays.snapshot.updateRunning).toBe(false);
165197
overlays.dispose();
166198
});

ui/src/app/overlays.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ function resolveUpdateStatusBanner(params: {
126126
"restart-unhealthy":
127127
"The replacement process never became healthy. The previous process stayed up so you can recover.",
128128
"doctor-failed": "Doctor repair failed. Run `openclaw doctor --non-interactive` and retry.",
129+
"managed-service-handoff-started":
130+
"The update was handed off to the host service. The gateway will restart automatically when the update completes.",
129131
}[reason] ?? "See the gateway logs for the exact failure and retry once the cause is fixed.";
130132
return {
131133
tone: status === "skipped" ? "warn" : "danger",
@@ -508,7 +510,10 @@ export function createApplicationOverlays(gateway: ApplicationGateway): Applicat
508510
return;
509511
}
510512
const status = response.result?.status ?? (response.ok === true ? "ok" : "error");
511-
const expectedVersion = response.result?.after?.version?.trim() || null;
513+
const expectedVersion =
514+
response.result?.after?.version?.trim() ||
515+
snapshot.updateAvailable?.latestVersion?.trim() ||
516+
null;
512517
if (
513518
response.ok === true &&
514519
status === "skipped" &&

0 commit comments

Comments
 (0)