From 4ca6cc18c4c5f6c5e4b8d03005c89051459b8426 Mon Sep 17 00:00:00 2001 From: evanlowe <62918515+evanlowe@users.noreply.github.com> Date: Sun, 9 Aug 2026 07:21:01 +0800 Subject: [PATCH] feat(studio): unify dev sandbox and add system info --- docs/content/docs/framework/frontend.en.mdx | 78 +- docs/content/docs/framework/frontend.mdx | 56 +- frontend/README.md | 58 +- frontend/SPEC.md | 2 +- frontend/server/skills/devenv.py | 6 +- frontend/src/App.tsx | 171 +-- frontend/src/adk/client.ts | 42 + frontend/src/adk/newChatCapabilities.ts | 4 - frontend/src/styles.css | 32 - frontend/src/ui/Composer.tsx | 96 +- frontend/src/ui/Sidebar.tsx | 66 +- frontend/src/ui/SystemInfo.css | 233 ++++ frontend/src/ui/SystemInfo.tsx | 178 +++ .../ui/new-chat-modes/NewChatModeSelector.tsx | 19 - .../src/ui/new-chat-modes/new-chat-modes.css | 11 - frontend/src/ui/new-chat-modes/types.ts | 2 +- .../ui/skill-create/SkillCandidatePane.tsx | 331 ----- .../ui/skill-create/SkillCreateWorkspace.tsx | 159 --- frontend/src/ui/skill-create/api.ts | 312 ----- frontend/src/ui/skill-create/skill-create.css | 245 ---- frontend/src/ui/skill-create/types.ts | 81 -- .../src/ui/skills/SkillConversationStream.css | 46 + .../SkillConversationStream.tsx | 7 +- .../ui/skills/SkillGenerationWorkspace.tsx | 2 +- frontend/tests/newChatComposerLayout.test.mjs | 23 +- .../tests/newChatModeCapabilities.test.mjs | 23 +- frontend/tests/skillCreateMode.test.mjs | 141 -- frontend/tests/systemInfo.test.mjs | 62 +- tests/cli/test_frontend_sandbox_options.py | 8 +- tests/cli/test_studio_deploy_target.py | 47 +- tests/cli/test_studio_rbac.py | 55 +- tests/cli/test_studio_update.py | 9 +- veadk/cli/cli_frontend.py | 93 +- ...By.js => MarkdownPromptEditor-CJ7ejKGL.js} | 2 +- veadk/webui/assets/index-CSryzStF.css | 10 + veadk/webui/assets/index-G_3SFXsd.js | 1140 ----------------- veadk/webui/assets/index-mHaPGnVw.css | 10 - veadk/webui/assets/index-v7mbHeUV.js | 1139 ++++++++++++++++ veadk/webui/index.html | 4 +- 39 files changed, 1942 insertions(+), 3061 deletions(-) create mode 100644 frontend/src/ui/SystemInfo.css create mode 100644 frontend/src/ui/SystemInfo.tsx delete mode 100644 frontend/src/ui/skill-create/SkillCandidatePane.tsx delete mode 100644 frontend/src/ui/skill-create/SkillCreateWorkspace.tsx delete mode 100644 frontend/src/ui/skill-create/api.ts delete mode 100644 frontend/src/ui/skill-create/skill-create.css delete mode 100644 frontend/src/ui/skill-create/types.ts create mode 100644 frontend/src/ui/skills/SkillConversationStream.css rename frontend/src/ui/{skill-create => skills}/SkillConversationStream.tsx (83%) delete mode 100644 frontend/tests/skillCreateMode.test.mjs rename veadk/webui/assets/{MarkdownPromptEditor-coAGiFBy.js => MarkdownPromptEditor-CJ7ejKGL.js} (99%) create mode 100644 veadk/webui/assets/index-CSryzStF.css delete mode 100644 veadk/webui/assets/index-G_3SFXsd.js delete mode 100644 veadk/webui/assets/index-mHaPGnVw.css create mode 100644 veadk/webui/assets/index-v7mbHeUV.js diff --git a/docs/content/docs/framework/frontend.en.mdx b/docs/content/docs/framework/frontend.en.mdx index 9e6ff824f..6571712ad 100644 --- a/docs/content/docs/framework/frontend.en.mdx +++ b/docs/content/docs/framework/frontend.en.mdx @@ -187,71 +187,27 @@ After selecting a sub-agent, the `/` menu shows skills mounted on that target. C The frontend sends structured `veadkInvocation` metadata instead of inferring invocation intent from the message string. The backend plugin directs ADK to call the skill tool or invoke `transfer_to_agent` one tree edge at a time until it reaches the target. The same metadata is written to the first Google GenAI `Part`, so reopening session history restores the `/skill` and `@agent` chips. -### Skill creation mode - -The new-chat **Skill creation** mode runs -`doubao-seed-2-0-pro-260215` and `deepseek-v4-flash-260425` in parallel through -Ark Responses. Each candidate gets an independent AgentKit CodeEnv session. -The server validates the generated directory name, `SKILL.md` frontmatter, -file count, size, and safe paths before packaging a ZIP. AgentKit credential -configuration resolves the Ark API key with the deployer's AK/SK and writes it -directly to each dedicated CodeEnv Tool. Per-job Session variables cannot -override the Tool's model credential. Model credentials are never returned to -the browser, and the Skill creation APIs require the Studio developer or admin -role. - -After submission, both candidate conversations appear immediately and -independently render public reasoning summaries, tool calls, and assistant -messages returned by the generator. Private chain-of-thought and credentials -never enter the page. Completed candidates still support preview, ZIP download, -and AgentKit publishing. Once the real job ID is available, the frontend resumes -polling directly from Sandbox state. An interrupted creation stream or a brief -AgentKit control-plane delay no longer turns an intermediate state into an -expired job. - -Before starting a local server, configure separate `Ready` AgentKit Tools for -Codex, OpenClaw, Hermes, and Skill creation: +### Skill Center + +Studio developers and admins create and optimize Skills from the Skill Center. +Each candidate runs in an isolated DevEnv session on the shared Dev Sandbox +Tool, streams public activity, and validates the generated files. Valid results +can be previewed, downloaded, or published to AgentKit. Model credentials remain +on the Tool and are never returned to the browser. + +Local Studio reads the DevEnv Tool ID from `SANDBOX_DEV`: ```bash -export SANDBOX_CHAT_CODEX= -export SANDBOX_CHAT_OPENCLAW= -export SANDBOX_CHAT_HERMES= -export SANDBOX_SKILL_CREATOR= -veadk frontend --agents-dir examples +export SANDBOX_DEV= +veadk studio --agents-dir examples ``` -For local Studio, configure the Ark model variables on both dedicated CodeEnv -Tools. Adding a candidate to AgentKit uploads its ZIP to TOS, then creates or -updates the Skill through the Skills API. It is published to a SkillSpace only -when the user provides SkillSpace IDs. +Each task has a one-hour DevEnv session. Leaving a running task stops and +releases that session; task state remains in Sandbox so polling can continue +across frontend instances. -| Environment variable | Default | Description | -| :-- | :-- | :-- | -| `SANDBOX_CHAT_CODEX` | — | AgentKit CodeEnv Tool ID dedicated to temporary chats. | -| `SANDBOX_CHAT_OPENCLAW` | — | AgentKit ArkClawEnv Tool ID used by OpenClaw agents. | -| `SANDBOX_CHAT_HERMES` | — | AgentKit HermesEnv Tool ID used by Hermes agents. | -| `SANDBOX_SKILL_CREATOR` | — | AgentKit CodeEnv Tool ID dedicated to Skill creation. | -| `VEADK_SKILL_CREATOR_TOS_BUCKET` | AgentKit account default | TOS bucket used for published artifacts. | -| `VEADK_SKILL_CREATOR_TOS_PREFIX` | `agentkit/skills` | TOS object-key prefix. | -| `VEADK_SKILL_CREATOR_PROJECT_NAME` | — | Project name used when creating a Skill. | - -Candidate sessions have a 30-minute TTL and are deleted immediately when the -user starts over or leaves the job. Job state is stored in Sandbox rather than -frontend-process memory, so polling and downloads continue to work when FaaS -requests reach different instances. - -On the first cloud deployment, the command creates two independent CodeEnv -Tools in parallel with the deployer's AK/SK when Tool IDs are omitted. Existing -Tools can be selected with `--sandbox-chat-codex-tool-id` and -`--sandbox-skill-creator-tool-id`. The Ark credential is resolved with the -deployer's AK/SK and written directly to both Tools as model environment -variables. Sandbox sessions inherit those Tool variables. The browser and -VeFaaS Function receive only Tool IDs, never model credentials. - -If Tool or model-credential provisioning fails, the CLI prints the underlying -error verbatim, including its original line breaks, after redacting credential -values. Use the service action, error code, request ID, and request details to -identify the missing permission or account resource. +A Volcengine cloud deployment creates the Dev Sandbox when no Tool ID is +provided, or uses an existing Tool selected with `--sandbox-dev-tool-id`. ```bash veadk studio deploy \ @@ -323,7 +279,7 @@ otherwise the current values are retained. Use `--path` to select a source checkout; it defaults to the current directory. Node.js and npm must be installed locally. `--sandbox-chat-codex-tool-id`, `--sandbox-chat-openclaw-tool-id`, -`--sandbox-chat-hermes-tool-id`, and `--sandbox-skill-creator-tool-id` likewise +`--sandbox-chat-hermes-tool-id`, and `--sandbox-dev-tool-id` likewise replace only the explicitly supplied environment value. Omitting an option keeps that Tool ID from the deployed Function instead of clearing it. diff --git a/docs/content/docs/framework/frontend.mdx b/docs/content/docs/framework/frontend.mdx index 86638a3a8..7752c156b 100644 --- a/docs/content/docs/framework/frontend.mdx +++ b/docs/content/docs/framework/frontend.mdx @@ -160,51 +160,25 @@ veadk frontend --agents-dir examples 前端不会从消息字符串中猜测调用意图,而是发送结构化的 `veadkInvocation` metadata。后端插件据此要求 ADK 调用技能工具,或沿 Agent 树逐级调用 `transfer_to_agent`,直到到达目标节点。同一份 metadata 也会写入首个 Google GenAI `Part`,因此重新加载历史会话后仍能恢复 `/skill` 与 `@agent` chip。 -### Skill 创建模式 +### 技能中心 -新会话输入框中的「Skill 创建」会固定使用 -`doubao-seed-2-0-pro-260215` 和 `deepseek-v4-flash-260425`,通过 Ark -Responses API 并行生成两个候选方案。每个候选使用独立的 AgentKit CodeEnv -Session,生成结果会校验目录名、`SKILL.md` frontmatter、文件数量、大小和路径安全, -再打包为 ZIP。真实 Ark API Key 由 AgentKit 凭据托管保存,Studio 和 Sandbox -只使用可撤销的网关票据;每个任务的 Session 环境变量不会覆盖托管票据,且服务端 -只接受 Volcengine HTTPS 凭据中继地址。浏览器不会收到模型凭据。Skill 创建接口仅允许 -Studio developer 和 admin 使用。 +Studio developer 和 admin 可在技能中心创建或优化 Skill。每个候选方案都会在共享 +Dev Sandbox Tool 上创建独立的 DevEnv Session,持续显示公开活动,并在完成后校验 +生成文件。通过校验的结果可预览、下载或发布到 AgentKit;模型凭据只保存在 Tool +中,不会返回浏览器。 -提交需求后会立即显示两个候选对话流,各自持续渲染生成器返回的公开思考摘要、工具 -调用和助手回复。私有思维链和凭据不会进入页面。候选完成后仍可预览、下载 ZIP 或 -添加到 AgentKit。前端拿到真实任务 ID 后会直接从 Sandbox 状态恢复轮询;创建流中断 -或 AgentKit 控制面短暂未发现新 Session 时,不会把中间状态误判为任务过期。 - -本地启动前需要分别配置临时会话和 Skill 创建使用的两个 `Ready` CodeEnv Tool: +本地 Studio 从 `SANDBOX_DEV` 读取 DevEnv Tool ID: ```bash -export SANDBOX_CHAT_CODEX= -export SANDBOX_SKILL_CREATOR= -veadk frontend --agents-dir examples +export SANDBOX_DEV= +veadk studio --agents-dir examples ``` -本地运行前使用 AgentKit 的 `credential-hosting` 命令,并将结果分别写入上述 CodeEnv -Tool。添加到 AgentKit 时,服务端会先将 -ZIP 上传到 TOS,再调用 Skills API 创建或更新 Skill;只有用户填写 SkillSpace ID -时才发布到对应空间。以下变量均为可选覆盖项: - -| 环境变量 | 默认值 | 说明 | -| :-- | :-- | :-- | -| `SANDBOX_CHAT_CODEX` | — | 临时会话专用 AgentKit CodeEnv Tool ID。 | -| `SANDBOX_SKILL_CREATOR` | — | Skill 创建专用 AgentKit CodeEnv Tool ID。 | -| `VEADK_SKILL_CREATOR_TOS_BUCKET` | AgentKit 账户默认 Bucket | 发布产物使用的 TOS Bucket。 | -| `VEADK_SKILL_CREATOR_TOS_PREFIX` | `agentkit/skills` | TOS 对象 Key 前缀。 | -| `VEADK_SKILL_CREATOR_PROJECT_NAME` | — | 创建 Skill 时使用的项目名称。 | - -候选 Session 的 TTL 为 30 分钟,用户重新创建或离开任务时会立即清理。任务状态保存 -在 Sandbox 而非 Frontend 进程内存,因此 FaaS 请求命中不同实例时仍可继续轮询和下载。 +每个任务的 DevEnv Session 最长保留 1 小时。离开仍在运行的任务时,Studio 会停止并 +释放对应 Session;任务状态保存在 Sandbox 中,因此前端实例切换后仍可继续轮询。 -云上首次部署时,若没有指定 Tool ID,部署命令会使用部署 AK/SK 并行创建两个独立 -CodeEnv Tool。也可以分别使用 `--sandbox-chat-codex-tool-id` 和 -`--sandbox-skill-creator-tool-id` 指定已有 Tool。部署命令会通过 AgentKit 凭据托管 -将 Ark 凭据保存到 KMS,并把代理地址和可撤销票据绑定到两个 Tool;VeFaaS Function -只接收 Tool ID,不接收模型凭据: +Volcengine 云上部署未指定 Tool ID 时会自动创建 Dev Sandbox,也可通过 +`--sandbox-dev-tool-id` 使用已有 Tool: ```bash veadk studio deploy \ @@ -263,8 +237,10 @@ Application。未指定 `--region` 和 `--project` 时,会在北京、上海 更新不会改变 Application/Function ID、访问 URL、SSO、IAM、网关或已有环境 变量。`--site-title` 和 `--site-logo` 仅在显式传入时覆盖,省略时保留云上品牌 设置。可用 `--path` 指定源码目录,默认为当前目录;本地需安装 Node.js 与 npm。 -`--sandbox-chat-codex-tool-id` 和 `--sandbox-skill-creator-tool-id` 也只在显式传入时 -更新对应环境变量;未传的 Tool ID 保留云上已有值,不会被清空。 +Sandbox Tool ID 参数也只在显式传入时更新对应环境变量,包括 +`--sandbox-chat-codex-tool-id`、`--sandbox-chat-openclaw-tool-id`、 +`--sandbox-chat-hermes-tool-id` 和 `--sandbox-dev-tool-id`;未传的 Tool ID 保留 +云上已有值,不会被清空。 ### Studio 一键更新 diff --git a/frontend/README.md b/frontend/README.md index 84e642ca5..b9b19a528 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -42,6 +42,10 @@ server that `veadk frontend` launches — no separate backend. the normal conversation renderer; leaving the conversation only disconnects it, so the Agent remains available until the user deletes it. OpenClaw and Hermes expose their main interface and Terminal through Studio. +- **System information**: open a full page from the account menu to inspect the + Studio version, configured Sandbox Tool IDs, and available Identity user + pools. Resource identifiers remain read-only and require Agent-management + access. - **AgentKit Skill center**: browse Skill Spaces and their skills with server-side pagination by region, then inspect the selected Skill content. - **Automation directory**: browse development and message-channel integrations @@ -381,50 +385,28 @@ tool or transfer one tree edge at a time until it reaches the selected agent. The same metadata is attached to the first Google GenAI `Part`, so session history restores the `/skill` and `@agent` chips after a reload. -### Skill creation mode +### Skill Center -Skill creation uses `doubao-seed-2-0-pro-260215` and -`deepseek-v4-flash-260425` through Ark Responses. The real Ark API key is kept -by AgentKit credential hosting; Studio and the two isolated Sandbox sessions -receive only its revocable gateway ticket. Credentials are never returned to -the browser or copied into per-job Session variables. Studio also rejects -non-HTTPS or non-Volcengine credential relay URLs. Skill creation is limited -to Studio developers and admins. +Studio developers and admins create and optimize Skills from the Skill Center. +Each candidate runs in an isolated session on the shared AgentKit Dev Sandbox, +streams its public activity, validates the generated files, and can then be +previewed, downloaded, or published to AgentKit. Model credentials remain on +the Tool and are never returned to the browser. -After submission, Studio opens both candidate conversations immediately. Each -conversation independently renders public reasoning summaries, tool calls, and -assistant messages returned by the generator. Private chain-of-thought and -credentials never enter the UI. Completed candidates still support preview, -ZIP download, and AgentKit publish. - -Configure separate ready AgentKit Tools for Codex, OpenClaw, Hermes, and Skill -creation before starting the server. The Tool IDs are intentionally server-only -and cannot be supplied by the browser: +Local Studio reads the DevEnv Tool ID from `SANDBOX_DEV`. A Volcengine cloud +deployment creates the Dev Sandbox automatically when the ID is omitted, or +uses the Tool supplied through `--sandbox-dev-tool-id`: ```bash -export SANDBOX_CHAT_CODEX= -export SANDBOX_CHAT_OPENCLAW= -export SANDBOX_CHAT_HERMES= -export SANDBOX_SKILL_CREATOR= -veadk frontend --agents-dir examples +export SANDBOX_DEV= +veadk studio --agents-dir examples ``` -Publishing a generated Skill uses TOS and the AgentKit Skills API. Set -`VEADK_SKILL_CREATOR_TOS_BUCKET`, `VEADK_SKILL_CREATOR_TOS_PREFIX`, and -`VEADK_SKILL_CREATOR_PROJECT_NAME` only when their defaults are unsuitable. -Each candidate session expires after 30 minutes and is deleted immediately when -a job is discarded. Job state lives in Sandbox rather than frontend-process -memory, so polling and downloads continue to work when FaaS requests reach a -different instance. - -For local Studio, run the AgentKit `credential-hosting` command and bind its -result to both CodeEnv Tools. A cloud deployment creates both Tools in parallel -when their IDs are omitted. Alternatively, select existing Tools with -`--sandbox-chat-codex-tool-id`, `--sandbox-chat-openclaw-tool-id`, -`--sandbox-chat-hermes-tool-id`, and `--sandbox-skill-creator-tool-id`. The -deploy command obtains the Ark key with the deployer's Volcengine credentials, -stores it through AgentKit credential hosting, and binds only the returned -ticket and relay URL to the Tools: +Each task has its own one-hour DevEnv session. Leaving a running task stops and +releases its session; task state remains in Sandbox so polling can continue +across frontend instances. + +Deploy Studio with: ```bash veadk studio deploy \ diff --git a/frontend/SPEC.md b/frontend/SPEC.md index 88ab56a61..92d353932 100644 --- a/frontend/SPEC.md +++ b/frontend/SPEC.md @@ -343,7 +343,7 @@ export function ExampleIcon(props: SVGProps) { | Agent 选择 | `AgentSelector`、`AgentIdentityIcon` | Sidebar 与输入框可以有独立作用域样式 | | 会话输入 | `Composer`、`NewChatModeSelector` | 保留 IME、键盘、附件和发送状态行为 | | 临时会话 | `SandboxLaunchDialog`、`SandboxSession` | 不得复用普通 ADK Session 流程 | -| Skill A/B 创建 | `SkillCreateWorkspace`、`SkillCandidatePane` | 保持双流输出、模型名和预览切换 | +| Skill 创建与优化 | `SkillCenter`、`SkillGenerationWorkspace` | 保持 Dev Sandbox 任务状态、候选切换和发布流程 | | 工具调用 | `BuiltinToolHeader` 和 `ui/builtin-tools/` | 新内置工具通过 Registry 扩展 | | 加载文字 | `TextShimmer` | 不得新增独立 Shimmer 实现 | | Markdown | `Markdown`、`MarkdownPromptEditor` | 外部 Markdown 禁止启用原始 HTML | diff --git a/frontend/server/skills/devenv.py b/frontend/server/skills/devenv.py index dda64096a..9c3cf55da 100644 --- a/frontend/server/skills/devenv.py +++ b/frontend/server/skills/devenv.py @@ -14,9 +14,9 @@ """DevEnv-backed Skill creation and optimization workbench for Studio. -This is intentionally additive to the legacy A/B Skill creator. A Skill task -creates its own Session on Studio's shared Dev Sandbox Tool, backed by the -provider-specific development image and kept type-isolated from CodeEnv Tools. +A Skill task creates its own Session on Studio's shared Dev Sandbox Tool, +backed by the provider-specific development image and kept type-isolated from +CodeEnv Tools. """ from __future__ import annotations diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dfbe85715..a3b4191c4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -87,6 +87,7 @@ import { type MyAgentCardData, } from "./ui/MyAgents"; import { Applications, type ApplicationId } from "./ui/Applications"; +import { SystemInfo } from "./ui/SystemInfo"; import { GitHubIntegration } from "./ui/GitHubIntegration"; import { FeishuBotIntegration } from "./automations/feishu/FeishuBotIntegration"; import { CodingAgentsIntegration } from "./automations/coding-agents/CodingAgentsIntegration"; @@ -121,9 +122,6 @@ import { type WorkspaceAgentDraft, } from "./create/agentDraftStorage"; import type { DeployResult, DeploymentTaskUpdate } from "./ui/ProjectPreview"; -import { createSkillJob, deleteSkillJob } from "./ui/skill-create/api"; -import { SkillCreateWorkspace } from "./ui/skill-create/SkillCreateWorkspace"; -import { SKILL_MODELS, type SkillCreationJob } from "./ui/skill-create/types"; import type { NewChatMode, NewChatTask } from "./ui/new-chat-modes/types"; import { NewChatFeatureCarousel } from "./ui/new-chat-modes/NewChatFeatureCarousel"; import { NewChatFeatureNotice } from "./ui/new-chat-modes/NewChatFeatureNotice"; @@ -142,10 +140,7 @@ import { type SandboxSkill, type SandboxToolLaunch, } from "./adk/sandbox"; -import { - getSandboxCapability, - getSkillCreatorCapability, -} from "./adk/newChatCapabilities"; +import { getSandboxCapability } from "./adk/newChatCapabilities"; import { SandboxLaunchDialog, type SandboxLaunchState, @@ -196,15 +191,13 @@ interface NewChatCapabilitiesState { harnessEnabled?: boolean; builtinTools?: string[]; temporaryEnabled?: boolean; - skillCreateEnabled?: boolean; } async function probeNewChatCapabilities( agentId: string, ): Promise { - const [sandboxResult, skillResult, harnessResult] = await Promise.allSettled([ + const [sandboxResult, harnessResult] = await Promise.allSettled([ getSandboxCapability(), - getSkillCreatorCapability(), listSessionBuiltinTools(agentId), ]); return { @@ -214,8 +207,6 @@ async function probeNewChatCapabilities( builtinTools: harnessResult.status === "fulfilled" ? harnessResult.value : [], temporaryEnabled: sandboxResult.status === "fulfilled" && sandboxResult.value.enabled, - skillCreateEnabled: - skillResult.status === "fulfilled" && skillResult.value.enabled, }; } @@ -839,9 +830,6 @@ export default function App() { const newChatCapabilitiesReady = !appName || (newChatCapabilities.ready === true && newChatCapabilities.agentId === appName); - const [skillJob, setSkillJob] = useState(null); - const [skillCreating, setSkillCreating] = useState(false); - const skillCreationRunRef = useRef(0); const [attachments, setAttachments] = useState([]); const [invocation, setInvocation] = useState(emptyInvocation); const [agentInfo, setAgentInfo] = useState(null); @@ -1042,18 +1030,6 @@ export default function App() { } } - function discardSkillCreation() { - skillCreationRunRef.current += 1; - const job = skillJob; - setSkillJob(null); - setSkillCreating(false); - if (job && !job.id.startsWith("pending-")) { - void deleteSkillJob(job.id).catch((cause) => { - setError(cause instanceof Error ? cause.message : String(cause)); - }); - } - } - async function abandonDraftSession(sid: string) { try { await deleteSessionMedia(appName, userId, sid); @@ -1173,6 +1149,7 @@ export default function App() { const [feedbackCasePreview, setFeedbackCasePreview] = useState(null); const [myAgents, setMyAgents] = useState(false); + const [systemInfo, setSystemInfo] = useState(false); const [applicationsView, setApplicationsView] = useState<"catalog" | ApplicationId | null>(null); // A search result may belong to a different agent; remember it so the @@ -2341,8 +2318,6 @@ export default function App() { setInput(""); setInvocation(emptyInvocation()); setNewChatMode("temporary"); - discardSkillCreation(); - setSkillCreating(false); discardDraftAttachments(attachments); setAttachments([]); releaseAllSandboxPreviews(); @@ -2983,8 +2958,6 @@ export default function App() { setGreeting(pickGreeting()); setNewChatMode("agent"); setNewChatTask(null); - discardSkillCreation(); - setSkillCreating(false); const abandonedSession = sessionId && persistentTurns.length === 0 && attachments.length > 0 ? sessionId : ""; @@ -3023,6 +2996,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); startNewChat(); } @@ -3063,7 +3037,6 @@ export default function App() { setPendingTurns([]); setNewChatMode("agent"); setNewChatTask(null); - discardSkillCreation(); setInvocation(emptyInvocation()); setSessionCapabilities(null); setSessionBuiltinTools([]); @@ -4108,6 +4081,7 @@ export default function App() { setFocusedDeploymentTaskId(""); setFocusedWorkspaceAgentId(""); setMyAgents(true); + setSystemInfo(false); setApplicationsView(null); setError(""); }; @@ -4127,6 +4101,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView("catalog"); setError(""); }; @@ -4188,15 +4163,17 @@ export default function App() { ? "feedback" : skillCenter ? "skills" - : applicationsView - ? "applications" - : searchView - ? "search" - : myAgents || manageAgents || sandboxAgentDetailTarget || sandboxAgentWorkspace - ? "agents" - : sessionId || createView || skillCenter || addAgent || addMenu - ? null - : "new-chat"; + : systemInfo + ? null + : applicationsView + ? "applications" + : searchView + ? "search" + : myAgents || manageAgents || sandboxAgentDetailTarget || sandboxAgentWorkspace + ? "agents" + : sessionId || createView || skillCenter || addAgent || addMenu + ? null + : "new-chat"; return (
@@ -4223,6 +4200,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); setSearchView(true); setError(""); @@ -4244,6 +4222,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); setCreateView(null); setImportedDraft(null); @@ -4262,6 +4241,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); setSkillCenter(true); setError(""); @@ -4281,6 +4261,7 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); setSessionId(""); setAddMenu(false); @@ -4289,8 +4270,28 @@ export default function App() { }} onMyAgents={openMyAgentsPage} onApplications={openApplicationsPage} + onSystemInfo={() => { + setPlatformFeedbackOrigin(null); + if (sandboxSession) exitSandboxSession(); + viewSidRef.current = ""; + setSessionId(""); + setCreateView(null); + setSkillCenter(false); + setAddAgent(false); + setAddMenu(false); + setSearchView(false); + setManageAgents(false); + setAgentDetailTarget(null); + setSandboxAgentDetailTarget(null); + setSandboxAgentWorkspace(null); + setMyAgents(false); + setApplicationsView(null); + setSystemInfo(true); + setError(""); + }} onIssueFeedback={() => { if (platformFeedbackOrigin !== null) return; + setSystemInfo(false); setPlatformFeedbackOrigin( sidebarActivePage ?? (sandboxSession @@ -4313,13 +4314,13 @@ export default function App() { setSandboxAgentDetailTarget(null); setSandboxAgentWorkspace(null); setMyAgents(false); + setSystemInfo(false); setApplicationsView(null); setError(""); pickSession(id); }} onDeleteSession={removeSession} userInfo={userInfo} - version={version} onLogout={onLogout} /> @@ -4387,57 +4388,6 @@ export default function App() { value={input} onChange={setInput} onSubmit={() => { - if (!sandboxSession && newChatMode === "skill-create") { - const prompt = input.trim(); - if (!prompt || skillCreating) return; - const provisionalJob: SkillCreationJob = { - id: `pending-${Date.now()}`, - prompt, - status: "provisioning", - candidates: SKILL_MODELS.map((model, index) => ({ - id: `pending-${index}`, - model, - modelLabel: model, - status: "queued", - stage: "provisioning", - files: [], - activities: [{ - id: "provisioning", - kind: "status", - text: "正在拉起 Sandbox", - status: "running", - }], - })), - }; - setSkillCreating(true); - const creationRun = ++skillCreationRunRef.current; - setError(""); - setSkillJob(provisionalJob); - setInput(""); - void createSkillJob(prompt, (job) => { - if (skillCreationRunRef.current === creationRun) { - setSkillJob(job); - } - }) - .then((job) => { - if (skillCreationRunRef.current === creationRun) { - setSkillJob(job); - } - }) - .catch((cause) => { - if (skillCreationRunRef.current === creationRun) { - setSkillJob(null); - setInput(prompt); - setError(cause instanceof Error ? cause.message : String(cause)); - } - }) - .finally(() => { - if (skillCreationRunRef.current === creationRun) { - setSkillCreating(false); - } - }); - return; - } const text = input; setInput(""); if (sandboxSession) { @@ -4461,9 +4411,7 @@ export default function App() { busy={ sandboxSession ? sandboxBusy - : newChatMode === "skill-create" - ? skillCreating - : conversationBusy + : conversationBusy } showMeta={turns.length > 0 && !sandboxSession} attachments={sandboxSession ? [] : attachments} @@ -4477,11 +4425,10 @@ export default function App() { onRemoveAttachment={removeDraftAttachment} newChatMode={sandboxSession ? "agent" : newChatMode} newChatTask={sandboxSession ? null : newChatTask} - newChatLayout={!sandboxSession && turns.length === 0 && skillJob === null} + newChatLayout={!sandboxSession && turns.length === 0} showAgentPicker={ !sandboxSession && turns.length === 0 && - skillJob === null && newChatMode === "agent" } agentPickerDisabled={!userId || conversationBusy} @@ -4515,16 +4462,12 @@ export default function App() { } showModeSelector={false} temporaryEnabled={newChatCapabilitiesReady && newChatCapabilities.temporaryEnabled} - skillCreateEnabled={newChatCapabilitiesReady && newChatCapabilities.skillCreateEnabled} harnessEnabled={newChatCapabilitiesReady && newChatCapabilities.harnessEnabled} builtinTools={ newChatCapabilitiesReady ? newChatCapabilities.builtinTools : [] } onModeChange={(mode) => { - if ( - (mode === "temporary" && !newChatCapabilities.temporaryEnabled) || - (mode === "skill-create" && !newChatCapabilities.skillCreateEnabled) - ) return; + if (mode === "temporary" && !newChatCapabilities.temporaryEnabled) return; if (mode === "temporary") { setNewChatTask(null); setNewChatMode(mode); @@ -4534,20 +4477,6 @@ export default function App() { setNewChatMode(mode); if (mode !== "agent") setNewChatTask(null); setError(""); - if (mode === "skill-create") { - setInvocation(emptyInvocation()); - const abandonedSession = - sessionId && persistentTurns.length === 0 && attachments.length > 0 - ? sessionId - : ""; - discardDraftAttachments(attachments); - setAttachments([]); - if (abandonedSession) { - viewSidRef.current = ""; - setSessionId(""); - void abandonDraftSession(abandonedSession); - } - } }} onTaskChange={setNewChatTask} /> @@ -4588,6 +4517,8 @@ export default function App() { initialModule={issueFeedbackModuleForPage(platformFeedbackOrigin)} onSubmit={submitPlatformIssueFeedback} /> + ) : systemInfo ? ( + ) : applicationsView === "coding-agents" ? ( setApplicationsView("catalog")} @@ -4959,8 +4890,6 @@ export default function App() { onDeploymentComplete={finishDeployment} initialDeployRegion={newRuntimeRegion} /> - ) : turns.length === 0 && skillJob ? ( - ) : turns.length === 0 && !newChatCapabilitiesReady ? (
正在检查 Agent 能力… @@ -4976,9 +4905,7 @@ export default function App() {

{sandboxSession ? "让灵感自由生长" - : newChatMode === "skill-create" - ? "想创建一个什么 Skill?" - : greeting} + : greeting}

{composer} diff --git a/frontend/src/adk/client.ts b/frontend/src/adk/client.ts index 733f313a3..9762876dd 100644 --- a/frontend/src/adk/client.ts +++ b/frontend/src/adk/client.ts @@ -1943,6 +1943,48 @@ export async function listDeploymentResources( }; } +export type SandboxToolKind = + | "codex" + | "openclaw" + | "hermes" + | "dev"; + +export interface SandboxToolInfo { + kind: SandboxToolKind; + label: string; + toolId: string; +} + +export interface SystemInfoResponse { + sandboxTools: SandboxToolInfo[]; +} + +export async function getSystemInfo( + signal?: AbortSignal, +): Promise { + const response = await apiFetch("/web/system-info", { signal }); + if (!response.ok) { + throw new Error(await httpErrorMessage(response, "加载系统信息失败")); + } + const payload = (await response.json()) as { sandboxTools?: unknown }; + if (!Array.isArray(payload.sandboxTools)) { + throw new Error("系统信息响应格式无效"); + } + const sandboxTools = payload.sandboxTools.map((item) => { + if ( + !item || + typeof item !== "object" || + typeof (item as SandboxToolInfo).kind !== "string" || + typeof (item as SandboxToolInfo).label !== "string" || + typeof (item as SandboxToolInfo).toolId !== "string" + ) { + throw new Error("系统信息响应格式无效"); + } + return item as SandboxToolInfo; + }); + return { sandboxTools }; +} + export interface IdentityUserPool { uid: string; name: string; diff --git a/frontend/src/adk/newChatCapabilities.ts b/frontend/src/adk/newChatCapabilities.ts index f7ca9e687..e5c75b7a4 100644 --- a/frontend/src/adk/newChatCapabilities.ts +++ b/frontend/src/adk/newChatCapabilities.ts @@ -30,7 +30,3 @@ async function getCapability(path: string): Promise { export async function getSandboxCapability(): Promise { return getCapability("/web/sandbox/capabilities"); } - -export async function getSkillCreatorCapability(): Promise { - return getCapability("/web/skill-creator/capabilities"); -} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 976361e98..108e0b882 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -2422,7 +2422,6 @@ body { .composer--new-chat.composer--has-task .new-chat-mode { left: 138px; } .composer--new-chat.composer--task-image .new-chat-mode, .composer--new-chat.composer--task-video .new-chat-mode { left: 176px; } -.composer--new-chat.composer--skill-mode .new-chat-mode { left: 10px; } .new-chat-task-chip { position: absolute; bottom: 10px; @@ -2448,10 +2447,6 @@ body { } .new-chat-task-chip--image, .new-chat-task-chip--video { width: 116px; } -.new-chat-task-chip--skill { - left: 10px; - width: 86px; -} .new-chat-task-chip > span:last-child { flex: 0 0 auto; white-space: nowrap; @@ -4062,33 +4057,6 @@ body { .account-action:hover { background: hsl(var(--foreground) / 0.05); } .account-action .icon { width: 16px; height: 16px; color: hsl(var(--muted-foreground)); } -.system-info-dialog { - width: 360px; - padding: 0; - overflow: hidden; -} -.system-info-head { - display: flex; - align-items: center; - justify-content: space-between; - margin: 0 20px; - padding: 20px 0 16px; - border-bottom: 1px solid hsl(var(--border)); -} -.system-info-head h2 { margin: 0; font-size: 17px; font-weight: 650; } -.system-info-meta { margin: 0; padding: 18px 20px 20px; } -.system-info-meta div { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; } -.system-info-meta dt { color: hsl(var(--muted-foreground)); font-size: 13px; } -.system-info-meta dd { - max-width: 100%; - margin: 0; - overflow-wrap: anywhere; - font-family: inherit; - font-size: 13px; - font-weight: 400; - font-variant-numeric: tabular-nums; -} - /* ---------- sidebar account footer ---------- */ .sidebar-footer { flex-shrink: 0; diff --git a/frontend/src/ui/Composer.tsx b/frontend/src/ui/Composer.tsx index a82890591..3c90c14b8 100644 --- a/frontend/src/ui/Composer.tsx +++ b/frontend/src/ui/Composer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import { useLayoutEffect, useRef, useState } from "react"; import type { ComponentType, SVGProps } from "react"; import { ArrowUp, @@ -32,28 +32,8 @@ import { NewChatModeSelector } from "./new-chat-modes/NewChatModeSelector"; import { NewChatAgentPicker } from "./new-chat-modes/NewChatAgentPicker"; import type { NewChatMode, NewChatTask } from "./new-chat-modes/types"; import { NEW_CHAT_TASK_TOOLS } from "./new-chat-modes/taskTools"; -import { SKILL_MODELS } from "./skill-create/types"; import { VideoGenerateIcon } from "./builtin-tools/icons"; -function SkillCreateIcon(props: SVGProps) { - return ( - - ); -} - interface CompletionTrigger { kind: "skill" | "agent"; query: string; @@ -133,7 +113,6 @@ export interface ComposerProps { onModeChange?: (value: NewChatMode) => void; onTaskChange?: (value: NewChatTask | null) => void; temporaryEnabled?: boolean; - skillCreateEnabled?: boolean; harnessEnabled?: boolean; builtinTools?: readonly string[]; showAgentPicker?: boolean; @@ -171,7 +150,6 @@ export function Composer({ onModeChange, onTaskChange, temporaryEnabled, - skillCreateEnabled, harnessEnabled = false, builtinTools = [], showAgentPicker = false, @@ -209,18 +187,10 @@ export function Composer({ el.style.height = `${Math.min(el.scrollHeight, 200)}px`; }, [value]); - const skillMode = newChatMode === "skill-create"; - useEffect(() => { - if (!skillMode) return; - setMenuOpen(false); - setTrigger(null); - }, [skillMode]); - const uploadPending = !skillMode && attachments.some((attachment) => attachment.status !== "ready"); + const uploadPending = attachments.some((attachment) => attachment.status !== "ready"); const canSend = !disabled && !busy && !uploadPending && - (value.trim().length > 0 || (!skillMode && attachments.length > 0)); - const placeholderText = skillMode - ? `描述你想创建的 Skill,将使用 ${SKILL_MODELS.join(" 和 ")} 并行创建…` - : disabled ? "请先选择智能体" : `向 ${agentName} 发消息…`; + (value.trim().length > 0 || attachments.length > 0); + const placeholderText = disabled ? "请先选择智能体" : `向 ${agentName} 发消息…`; const query = trigger?.query.toLocaleLowerCase() ?? ""; const suggestions: CompletionItem[] = trigger?.kind === "skill" @@ -343,18 +313,16 @@ export function Composer({ } return ( -
- {!skillMode ? ( - onInvocationChange({ - ...invocation, - skills: invocation.skills.filter((skill) => skill.name !== name), - })} - onRemoveAgent={() => onInvocationChange({ skills: [] })} - /> - ) : null} - {!skillMode && attachments.length > 0 && ( +
+ onInvocationChange({ + ...invocation, + skills: invocation.skills.filter((skill) => skill.name !== name), + })} + onRemoveAgent={() => onInvocationChange({ skills: [] })} + /> + {attachments.length > 0 && ( ) : null} - {!skillMode ?
+
: null} +
{showAgentPicker && onSelectRuntime && onSelectSandboxSession ? ( ) : null} @@ -490,22 +457,6 @@ export function Composer({ ) : null} - {newChatLayout && skillMode && onModeChange ? ( - - ) : null} -