Skip to content

Commit 4c74aa6

Browse files
fix: reload pending approvals after control ui reconnect
1 parent de50acd commit 4c74aa6

4 files changed

Lines changed: 51 additions & 0 deletions

File tree

ui/src/ui/app-gateway-chat-load.node.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ vi.mock("./controllers/exec-approval.ts", () => ({
144144
parseExecApprovalResolved: vi.fn(() => null),
145145
parsePluginApprovalRequested: vi.fn(() => null),
146146
pruneExecApprovalQueue: vi.fn((queue) => queue),
147+
refreshPendingApprovalQueue: vi.fn(async () => undefined),
147148
removeExecApproval: vi.fn((queue) => queue),
148149
}));
149150

ui/src/ui/app-gateway.node.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,53 @@ describe("connectGateway", () => {
964964
expect(host.execApprovalQueue[0]?.id).toBe("approval-1");
965965
});
966966

967+
it("loads pending approval requests after reconnect hello", async () => {
968+
const now = Date.now();
969+
const { host, client } = connectHostGateway();
970+
client.request.mockImplementation(async (method: string) => {
971+
if (method === "exec.approval.list") {
972+
return [
973+
{
974+
id: "approval-exec-reconnected",
975+
request: {
976+
command: "pnpm check:changed",
977+
host: "gateway",
978+
},
979+
createdAtMs: now,
980+
expiresAtMs: now + 60_000,
981+
},
982+
];
983+
}
984+
if (method === "plugin.approval.list") {
985+
return [
986+
{
987+
id: "approval-plugin-reconnected",
988+
request: {
989+
title: "Allow plugin action",
990+
description: "Plugin action requires confirmation.",
991+
pluginId: "calendar",
992+
},
993+
createdAtMs: now + 1,
994+
expiresAtMs: now + 60_000,
995+
},
996+
];
997+
}
998+
return {};
999+
});
1000+
1001+
client.emitHello();
1002+
1003+
await vi.waitFor(() => {
1004+
expect(host.execApprovalQueue.map((entry) => entry.id)).toEqual([
1005+
"approval-plugin-reconnected",
1006+
"approval-exec-reconnected",
1007+
]);
1008+
});
1009+
expect(client.request).toHaveBeenCalledWith("exec.approval.list", {});
1010+
expect(client.request).toHaveBeenCalledWith("plugin.approval.list", {});
1011+
expect(host.execApprovalQueue.map((entry) => entry.kind)).toEqual(["plugin", "exec"]);
1012+
});
1013+
9671014
it("maps generic fetch-failed auth errors to actionable token mismatch message", () => {
9681015
const host = createHost();
9691016

ui/src/ui/app-gateway.sessions.node.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ vi.mock("./controllers/exec-approval.ts", () => ({
7676
parseExecApprovalResolved: vi.fn(() => null),
7777
parsePluginApprovalRequested: vi.fn(() => null),
7878
pruneExecApprovalQueue: vi.fn((queue) => queue),
79+
refreshPendingApprovalQueue: vi.fn(async () => undefined),
7980
removeExecApproval: vi.fn(),
8081
}));
8182
vi.mock("./controllers/nodes.ts", () => ({

ui/src/ui/app-gateway.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
parseExecApprovalResolved,
6464
parsePluginApprovalRequested,
6565
pruneExecApprovalQueue,
66+
refreshPendingApprovalQueue,
6667
} from "./controllers/exec-approval.ts";
6768
import { loadHealthState, type HealthState } from "./controllers/health.ts";
6869
import {
@@ -848,6 +849,7 @@ export function connectGateway(host: GatewayHost, options?: ConnectGatewayOption
848849
console.warn("[openclaw] pending abort failed:", err);
849850
});
850851
}
852+
void refreshPendingApprovalQueue(host);
851853
// Reset orphaned chat run state from before disconnect.
852854
// Any in-flight run's final event was lost during the disconnect window.
853855
const orphanedRunId = host.chatRunId;

0 commit comments

Comments
 (0)