From e92087d729f064df806db055162beb834b4df40e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=86=A0=E8=BE=B0?= Date: Wed, 27 May 2026 08:08:40 +0800 Subject: [PATCH] fix(opencode): route agent list by directory --- .../httpapi/middleware/workspace-routing.ts | 10 ++++++- .../src/server/shared/workspace-routing.ts | 1 + .../test/server/httpapi-exercise/index.ts | 28 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts index 8bffe59640fb..2c48a54562f4 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/workspace-routing.ts @@ -164,17 +164,25 @@ function planRequest( return Effect.gen(function* () { const url = requestURL(request) const envWorkspaceID = configuredWorkspaceID() + const local = shouldStayOnControlPlane(request, url) const workspaceID = url.pathname.startsWith("/api/") ? selectedV2WorkspaceID(url, sessionWorkspaceID) : selectedWorkspaceID(url, sessionWorkspaceID) if (workspaceID === InvalidWorkspaceID) return RequestPlan.InvalidWorkspace() + // Control-plane reads can carry stale workspace params from clients; route + // them by directory instead of requiring the workspace to still exist. + if (local) + return RequestPlan.Local({ + directory: defaultDirectory(request, url), + workspaceID: envWorkspaceID ?? workspaceID, + }) const workspace = yield* resolveWorkspace(workspaceID, envWorkspaceID) if (workspaceID && workspace === undefined && !envWorkspaceID) { return RequestPlan.MissingWorkspace({ workspaceID }) } - if (workspace !== undefined && !envWorkspaceID && !shouldStayOnControlPlane(request, url)) { + if (workspace !== undefined && !envWorkspaceID) { return yield* planWorkspaceRequest(request, url, workspace) } diff --git a/packages/opencode/src/server/shared/workspace-routing.ts b/packages/opencode/src/server/shared/workspace-routing.ts index 366c455dd6bb..02e6bf5f84a9 100644 --- a/packages/opencode/src/server/shared/workspace-routing.ts +++ b/packages/opencode/src/server/shared/workspace-routing.ts @@ -4,6 +4,7 @@ type Rule = { method?: string; path: string; exact?: boolean; action: "local" | const RULES: Array = [ { path: "/experimental/workspace", action: "local" }, + { method: "GET", path: "/agent", exact: true, action: "local" }, { path: "/session/status", action: "forward" }, { method: "GET", path: "/session", action: "local" }, ] diff --git a/packages/opencode/test/server/httpapi-exercise/index.ts b/packages/opencode/test/server/httpapi-exercise/index.ts index f2b132cb7320..f6da2c7ae6b1 100644 --- a/packages/opencode/test/server/httpapi-exercise/index.ts +++ b/packages/opencode/test/server/httpapi-exercise/index.ts @@ -126,6 +126,34 @@ const scenarios: Scenario[] = [ .status(400, undefined, "status"), http.protected.get("/command", "command.list").json(200, array, "status"), http.protected.get("/agent", "app.agents").json(200, array, "status"), + http.protected + .get("/agent", "app.agents.custom") + .inProject({ + git: true, + config: { + agent: { + "custom-primary": { + mode: "primary", + description: "Custom primary agent", + }, + }, + }, + }) + .at((ctx) => ({ + path: `/agent?directory=${encodeURIComponent(ctx.directory ?? "")}&workspace=wrk_stale`, + headers: ctx.headers(), + })) + .json( + 200, + (body) => { + array(body) + check( + body.some((item) => isRecord(item) && item.name === "custom-primary" && item.mode === "primary"), + "agent list should include custom primary agents from the requested directory", + ) + }, + "status", + ), http.protected.get("/skill", "app.skills").json(200, array, "status"), http.protected.get("/lsp", "lsp.status").json(200, array), http.protected.get("/formatter", "formatter.status").json(200, array),