From 706cb09e918e5ee2b29aa9e1690810e463c3007c Mon Sep 17 00:00:00 2001 From: evanlowe <62918515+evanlowe@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:12:01 +0800 Subject: [PATCH] fix(studio): honor runtime visibility by role --- frontend/src/App.tsx | 7 +- frontend/src/styles.css | 2 +- frontend/src/ui/AgentSelector.tsx | 2 +- frontend/src/ui/MyAgents.css | 8 + frontend/src/ui/MyAgents.tsx | 65 ++- frontend/tests/myAgents.test.mjs | 21 +- frontend/tests/studioAccess.test.mjs | 9 + tests/cli/test_studio_rbac.py | 15 +- ...tor-CDm5YAyp.js => CodeEditor-_pW8mJDp.js} | 2 +- ...IY.js => MarkdownPromptEditor-D1hnC8YP.js} | 2 +- veadk/webui/assets/index--lpW_agw.css | 10 - .../{index-CgdykAXF.js => index-CcI9ozT0.js} | 430 +++++++++--------- veadk/webui/assets/index-Cd_o9xgW.css | 10 + veadk/webui/index.html | 4 +- 14 files changed, 330 insertions(+), 257 deletions(-) rename veadk/webui/assets/{CodeEditor-CDm5YAyp.js => CodeEditor-_pW8mJDp.js} (99%) rename veadk/webui/assets/{MarkdownPromptEditor-CWHxYwIY.js => MarkdownPromptEditor-D1hnC8YP.js} (99%) delete mode 100644 veadk/webui/assets/index--lpW_agw.css rename veadk/webui/assets/{index-CgdykAXF.js => index-CcI9ozT0.js} (56%) create mode 100644 veadk/webui/assets/index-Cd_o9xgW.css diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6706f3d5..5cbd7994 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -825,6 +825,7 @@ export default function App() { // Null while the server-derived role is unresolved. Privileged UI remains // hidden until this has loaded; failures fall back to the ordinary user. const [access, setAccess] = useState(null); + const grantedRuntimeScope = access?.capabilities.runtimeScope ?? "mine"; // Per-module feature gates (studio mode disables chat-centric modules). // Defaults to all-enabled until /web/ui-config resolves. const [features, setFeatures] = useState({ @@ -1312,7 +1313,7 @@ export default function App() { let nextToken = ""; do { const page = await getRuntimes({ - scope: "mine", + scope: grantedRuntimeScope, region: "all", pageSize: 100, nextToken, @@ -1335,7 +1336,7 @@ export default function App() { } finally { setAgentLibraryLoading(false); } - }, []); + }, [grantedRuntimeScope]); // Placeholder: persisting/registering the created agent is a follow-up. function onCreate(draft: AgentDraft) { @@ -3506,6 +3507,8 @@ export default function App() { {myAgents ? ( {rt.isMine && ( - 我创建的 + 我创建的 )} diff --git a/frontend/src/ui/MyAgents.css b/frontend/src/ui/MyAgents.css index fcc99ae0..a2bab7c4 100644 --- a/frontend/src/ui/MyAgents.css +++ b/frontend/src/ui/MyAgents.css @@ -331,7 +331,15 @@ display: block; } +.my-agent-card-title { + min-width: 0; + display: flex; + align-items: center; + gap: 6px; +} + .my-agent-card h3 { + min-width: 0; margin: 0; overflow: hidden; color: hsl(var(--foreground)); diff --git a/frontend/src/ui/MyAgents.tsx b/frontend/src/ui/MyAgents.tsx index 887f5917..a276acc2 100644 --- a/frontend/src/ui/MyAgents.tsx +++ b/frontend/src/ui/MyAgents.tsx @@ -1,7 +1,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { SVGProps } from "react"; -import { getRuntimeAgentInfo, getRuntimes, type CloudRuntime } from "../adk/client"; +import { + getRuntimeAgentInfo, + getRuntimes, + type CloudRuntime, + type RuntimeScope, +} from "../adk/client"; import "./MyAgents.css"; export interface MyAgentCardData { @@ -10,6 +15,7 @@ export interface MyAgentCardData { name: string; description: string; createdAt: string; + isMine?: boolean; runtime?: { runtimeId: string; region: string; @@ -117,6 +123,7 @@ function runtimeToAgent(runtime: CloudRuntime): MyAgentCardData { name: runtime.name, description: runtime.name, createdAt: formatCreatedAt(runtime.createdAt ?? ""), + isMine: runtime.isMine, runtime: { runtimeId: runtime.runtimeId, region: runtime.region, @@ -127,11 +134,12 @@ function runtimeToAgent(runtime: CloudRuntime): MyAgentCardData { } async function loadRuntimeAgents( + runtimeScope: RuntimeScope, region: RuntimeRegion, nextToken: string, onList: (agents: MyAgentCardData[]) => void, ): Promise { - const requestKey = `${region}:${nextToken}`; + const requestKey = `${runtimeScope}:${region}:${nextToken}`; const cached = runtimePageCache.get(requestKey); if (cached && cached.expiresAt > Date.now()) { onList(cached.page.runtimes.map(runtimeToAgent)); @@ -141,7 +149,7 @@ async function loadRuntimeAgents( let request = runtimePageRequests.get(requestKey); if (!request) { request = getRuntimes({ - scope: "mine", + scope: runtimeScope, region, pageSize: RUNTIME_PAGE_SIZE, nextToken, @@ -168,6 +176,7 @@ async function loadRuntimeAgents( name: info.name || runtime.name, description: info.description || runtime.name, createdAt: formatCreatedAt(runtime.createdAt ?? ""), + isMine: runtime.isMine, runtime: { runtimeId: runtime.runtimeId, region: runtime.region, @@ -190,12 +199,14 @@ function AgentCard({ onViewDetails, connecting, connected, + showOwnership, }: { agent: MyAgentCardData; onUse?: (agent: MyAgentCardData) => Promise; onViewDetails?: (agent: MyAgentCardData) => void; connecting?: boolean; connected?: boolean; + showOwnership?: boolean; }) { return (
@@ -207,7 +218,12 @@ function AgentCard({ aria-label={`查看 ${agent.name} 详情`} > -

{agent.name}

+ +

{agent.name}

+ {showOwnership && agent.isMine && ( + 我创建的 + )} +
创建时间
@@ -232,6 +248,8 @@ function AgentCard({ } export interface MyAgentsProps { + canCreate: boolean; + runtimeScope: RuntimeScope; onCreateAgent: (region: RuntimeRegion) => void; onCreateCodexAgent: () => void; onUseAgent: (agent: MyAgentCardData) => Promise; @@ -241,6 +259,8 @@ export interface MyAgentsProps { } export function MyAgents({ + canCreate, + runtimeScope, onCreateAgent, onCreateCodexAgent, onUseAgent, @@ -265,7 +285,7 @@ export function MyAgents({ const requestId = ++runtimeRequestRef.current; setLoadingRuntimes(true); setRuntimeError(""); - return loadRuntimeAgents(region, token, (agents) => { + return loadRuntimeAgents(runtimeScope, region, token, (agents) => { if (runtimeRequestRef.current !== requestId) return; setRuntimeAgents((current) => reset ? agents : [...current, ...agents]); }) @@ -279,7 +299,7 @@ export function MyAgents({ .finally(() => { if (runtimeRequestRef.current === requestId) setLoadingRuntimes(false); }); - }, [region]); + }, [region, runtimeScope]); useEffect(() => { setRuntimeAgents([]); @@ -349,9 +369,9 @@ export function MyAgents({ const emptyMessage = activeType === "openclaw" || activeType === "hermes" ? "暂未开放" : query.trim() ? "没有匹配的智能体" : `${activeLabel}暂无内容`; - const createAgent = activeType === "general" + const createAgent = canCreate && activeType === "general" ? () => onCreateAgent(region) - : activeType === "codex" ? onCreateCodexAgent : undefined; + : canCreate && activeType === "codex" ? onCreateCodexAgent : undefined; return (
@@ -409,7 +429,11 @@ export function MyAgents({ )}
-

在此处浏览您的所有智能体

+

+ {runtimeScope === "all" + ? "在此处浏览所有智能体" + : "在此处浏览您的所有智能体"} +