From 77b9071b8a51bf22d53f7f47a22c204f3bee14e5 Mon Sep 17 00:00:00 2001 From: murashit Date: Wed, 17 Jun 2026 14:43:20 +0900 Subject: [PATCH] Route unhandled app-server request policies to short-lived clients --- src/plugin-runtime.ts | 3 +++ tests/main.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/plugin-runtime.ts b/src/plugin-runtime.ts index 1d804adf..ef171e55 100644 --- a/src/plugin-runtime.ts +++ b/src/plugin-runtime.ts @@ -229,6 +229,9 @@ export class CodexPanelRuntime implements AppServerClientAccess { if (!appServerQueryContextIsComplete(context)) { throw new Error("Codex app-server query context is incomplete."); } + if (options.unhandledServerRequestMessage !== undefined) { + return withShortLivedAppServerClient(context.codexPath, context.vaultPath, operation, options); + } const chatSurface = this.connectedClientSurface(); if (chatSurface) return chatSurface.runWithAppServerClient(operation); return withShortLivedAppServerClient(context.codexPath, context.vaultPath, operation, options); diff --git a/tests/main.test.ts b/tests/main.test.ts index 4f99b8eb..55ed7147 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -14,6 +14,14 @@ import { installObsidianDomShims } from "./support/dom"; installObsidianDomShims(); +const { withShortLivedAppServerClientMock } = vi.hoisted(() => ({ + withShortLivedAppServerClientMock: vi.fn(), +})); + +vi.mock("../src/app-server/connection/short-lived-client", () => ({ + withShortLivedAppServerClient: withShortLivedAppServerClientMock, +})); + function panels(plugin: CodexPanelPlugin) { return new WorkspacePanelCoordinator({ app: plugin.app, @@ -28,6 +36,7 @@ function threadCatalog(plugin: CodexPanelPlugin) { describe("CodexPanelPlugin boot restored panel loading", () => { beforeEach(() => { vi.useRealTimers(); + withShortLivedAppServerClientMock.mockReset(); }); it("loads restored Codex panel leaves after startup without blocking onload", async () => { @@ -505,6 +514,33 @@ describe("CodexPanelPlugin boot restored panel loading", () => { expect(threadCatalog(plugin).snapshot()).toEqual([thread("cached")]); }); + it("uses a short-lived client when the operation declares an unhandled server-request policy", async () => { + const { CodexChatView } = await import("../src/features/chat/host/view"); + const connectedLeaf = leaf(); + connectedLeaf.view = chatView(CodexChatView, connectedLeaf); + const connectedView = connectedLeaf.view as CodexChatView; + vi.spyOn(connectedView.surface, "openPanelSnapshot").mockReturnValue(panelSnapshot({ viewId: "connected", connected: true })); + const runWithAppServerClient = vi.spyOn(connectedView.surface, "runWithAppServerClient").mockResolvedValue("chat-result"); + const shortLivedClient = { readEffectiveConfig: vi.fn().mockResolvedValue({}) }; + withShortLivedAppServerClientMock.mockImplementation( + (_codexPath: string, _vaultPath: string, operation: (client: typeof shortLivedClient) => Promise) => + operation(shortLivedClient), + ); + const plugin = await pluginWithLeaves([connectedLeaf]); + plugin.settings.codexPath = "codex"; + + const result = await plugin.runtime.withClient((client) => client.readEffectiveConfig("/vault") as Promise, { + unhandledServerRequestMessage: "Settings refresh does not handle server requests.", + }); + + expect(result).toEqual({}); + expect(runWithAppServerClient).not.toHaveBeenCalled(); + expect(withShortLivedAppServerClientMock).toHaveBeenCalledWith("codex", "/vault", expect.any(Function), { + unhandledServerRequestMessage: "Settings refresh does not handle server requests.", + }); + expect(shortLivedClient.readEffectiveConfig).toHaveBeenCalledWith("/vault"); + }); + it("refreshes shared thread lists from a remaining connected panel after the archived panel is detached", async () => { const { CodexChatView } = await import("../src/features/chat/host/view"); const sourceLeaf = leaf();