From cd827ad3edd38a7ffbdfb87fee620f661becc6d5 Mon Sep 17 00:00:00 2001 From: "hanzhi.421" Date: Fri, 14 Aug 2026 21:31:08 +0800 Subject: [PATCH 1/2] feat: add DeepSeekHarness studio agent --- frontend/src/App.tsx | 71 ++- frontend/src/adk/newChatCapabilities.ts | 7 + frontend/src/adk/sandbox.ts | 2 +- frontend/src/telemetry/schema.ts | 5 +- frontend/src/ui/Composer.tsx | 3 + frontend/src/ui/MyAgents.tsx | 8 +- frontend/src/ui/SandboxAgentDetails.tsx | 1 + frontend/src/ui/SandboxAgentWorkspace.tsx | 4 +- frontend/src/ui/SandboxLaunchDialog.tsx | 4 +- frontend/src/ui/icons/SandboxAgentIcons.tsx | 27 +- .../ui/new-chat-modes/NewChatAgentPicker.tsx | 8 +- .../ui/new-chat-modes/NewChatModeSelector.tsx | 94 +++- frontend/src/ui/new-chat-modes/types.ts | 2 +- frontend/tests/myAgents.test.mjs | 9 +- frontend/tests/newChatAgentPicker.test.mjs | 1 + frontend/tests/newChatComposerLayout.test.mjs | 9 +- .../tests/newChatModeCapabilities.test.mjs | 15 +- frontend/tests/sandboxAgentIcons.test.mjs | 15 +- .../tests/sandboxSessionPresentation.test.mjs | 4 + frontend/tests/sandboxThreadsClient.test.mjs | 9 + tests/cli/test_frontend_agent_proxy.py | 90 ++++ tests/cli/test_frontend_sandbox.py | 50 ++ tests/cli/test_frontend_sandbox_options.py | 10 + tests/cli/test_frontend_skill_creator.py | 4 + veadk/cli/agentkit_session_metadata.py | 66 ++- veadk/cli/cli_frontend.py | 8 + veadk/cli/frontend_agent_proxy.py | 29 +- veadk/cli/frontend_sandbox.py | 101 +++- veadk/cli/frontend_skill_creator.py | 17 +- ...pJ.js => MarkdownPromptEditor-Bdqf2Plr.js} | 2 +- .../{index-Ei6D2O0i.js => index-jAEFFA8R.js} | 482 +++++++++--------- veadk/webui/index.html | 2 +- 32 files changed, 816 insertions(+), 343 deletions(-) rename veadk/webui/assets/{MarkdownPromptEditor-CoCxG4pJ.js => MarkdownPromptEditor-Bdqf2Plr.js} (99%) rename veadk/webui/assets/{index-Ei6D2O0i.js => index-jAEFFA8R.js} (65%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2e8ed9d50..23c680817 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -159,7 +159,10 @@ import { type SandboxThreadSummary, type SandboxToolLaunch, } from "./adk/sandbox"; -import { getSandboxCapability } from "./adk/newChatCapabilities"; +import { + getSandboxAgentCapability, + getSandboxCapability, +} from "./adk/newChatCapabilities"; import { getSkillWorkbenchCapability } from "./ui/skill-workbench/api"; import { createVideoTask, @@ -250,14 +253,21 @@ interface NewChatCapabilitiesState { harnessEnabled?: boolean; builtinTools?: string[]; temporaryEnabled?: boolean; + deepseekHarnessEnabled?: boolean; skillCustomizationEnabled?: boolean; } async function probeNewChatCapabilities( agentId: string, ): Promise { - const [sandboxResult, skillResult, harnessResult] = await Promise.allSettled([ + const [ + sandboxResult, + deepseekHarnessResult, + skillResult, + harnessResult, + ] = await Promise.allSettled([ getSandboxCapability(), + getSandboxAgentCapability("deepseek-harness"), getSkillWorkbenchCapability(), agentId ? listSessionBuiltinTools(agentId) : Promise.resolve([]), ]); @@ -268,6 +278,9 @@ async function probeNewChatCapabilities( builtinTools: harnessResult.status === "fulfilled" ? harnessResult.value : [], temporaryEnabled: sandboxResult.status === "fulfilled" && sandboxResult.value.enabled, + deepseekHarnessEnabled: + deepseekHarnessResult.status === "fulfilled" && + deepseekHarnessResult.value.enabled, skillCustomizationEnabled: skillResult.status === "fulfilled" && skillResult.value.enabled, }; @@ -2849,7 +2862,7 @@ export default function App() { setSandboxLaunchError(""); if ( !sandboxSession && - newChatMode === "temporary" && + newChatMode !== "agent" && !sandboxLaunchFromAgents ) { setNewChatMode("agent"); @@ -2890,7 +2903,40 @@ export default function App() { setMyAgents(true); return; } - if (sandboxLaunchKind !== "codex") return; + if (sandboxLaunchKind !== "codex") { + const workspace = await sandboxClient.openAgentSession( + sandboxLaunchKind, + createdSession.id, + { signal: controller.signal }, + ); + if (sandboxLaunchAbortRef.current !== controller) return; + viewSidRef.current = ""; + setSessionId(""); + setPendingTurns([]); + setInput(""); + setInvocation(emptyInvocation()); + setNewChatMode( + sandboxLaunchKind === "deepseek-harness" ? "deepseek-harness" : "agent", + ); + discardDraftAttachments(attachments); + setAttachments([]); + releaseAllSandboxPreviews(); + setSandboxTurns([]); + setSandboxSession(null); + setCreateView(null); + setSkillCenter(false); + setAddAgent(false); + setAddMenu(false); + setSearchView(false); + setManageAgents(false); + setAgentDetailTarget(null); + setMyAgents(false); + setSandboxAgentDetailTarget(null); + setSandboxAgentWorkspace(workspace); + setSandboxLaunchOpen(false); + setSandboxLaunchState("confirm"); + return; + } const nextSession = await sandboxClient.connectSession(createdSession.id, { signal: controller.signal, }); @@ -4998,6 +5044,8 @@ export default function App() { agentName={ sandboxSession.toolName === "codex" ? "Codex" + : sandboxSession.toolName === "deepseek-harness" + ? "DeepSeekHarness" : sandboxSession.toolName === "openclaw" ? "OpenClaw" : "Hermes" @@ -5120,6 +5168,7 @@ export default function App() { ? false : !userId || newChatMode === "temporary" || + newChatMode === "deepseek-harness" || (newChatWorkspaceMode === "agent" && newChatMode === "agent" && !appName) || @@ -5200,6 +5249,10 @@ export default function App() { onSkillActionChange={setNewChatSkillAction} onSkillTargetChange={setNewChatSkillTarget} temporaryEnabled={newChatCapabilitiesReady && newChatCapabilities.temporaryEnabled} + deepseekHarnessEnabled={ + newChatCapabilitiesReady && + newChatCapabilities.deepseekHarnessEnabled + } harnessEnabled={newChatCapabilitiesReady && newChatCapabilities.harnessEnabled} builtinTools={ newChatCapabilitiesReady ? newChatCapabilities.builtinTools : [] @@ -5212,6 +5265,16 @@ export default function App() { openSandboxLaunch(); return; } + if ( + mode === "deepseek-harness" && + !newChatCapabilities.deepseekHarnessEnabled + ) return; + if (mode === "deepseek-harness") { + setNewChatTask(null); + setNewChatMode(mode); + openSandboxLaunch("deepseek-harness"); + return; + } setNewChatMode(mode); if (mode !== "agent") setNewChatTask(null); setError(""); diff --git a/frontend/src/adk/newChatCapabilities.ts b/frontend/src/adk/newChatCapabilities.ts index e5c75b7a4..065aaaa79 100644 --- a/frontend/src/adk/newChatCapabilities.ts +++ b/frontend/src/adk/newChatCapabilities.ts @@ -1,5 +1,6 @@ import { withAuth } from "./auth"; import { withLocalUser } from "./identity"; +import type { SandboxAgentKind } from "./sandbox"; import { requestSignal } from "./timeout"; const CAPABILITY_TIMEOUT_MS = 10_000; @@ -30,3 +31,9 @@ async function getCapability(path: string): Promise { export async function getSandboxCapability(): Promise { return getCapability("/web/sandbox/capabilities"); } + +export async function getSandboxAgentCapability( + kind: SandboxAgentKind, +): Promise { + return getCapability(`/web/${kind}/capabilities`); +} diff --git a/frontend/src/adk/sandbox.ts b/frontend/src/adk/sandbox.ts index 747a6694f..d29dc0667 100644 --- a/frontend/src/adk/sandbox.ts +++ b/frontend/src/adk/sandbox.ts @@ -13,7 +13,7 @@ const SETTINGS_TIMEOUT_MS = 60_000; const UPLOAD_TIMEOUT_MS = 330_000; export const SANDBOX_DISPLAY_NAME_MAX_LENGTH = 40; -export type SandboxAgentKind = "openclaw" | "hermes"; +export type SandboxAgentKind = "deepseek-harness" | "openclaw" | "hermes"; export function sandboxStatusLabel(status: string): string { switch (status.trim().toLowerCase()) { diff --git a/frontend/src/telemetry/schema.ts b/frontend/src/telemetry/schema.ts index d0a818373..7bf229f26 100644 --- a/frontend/src/telemetry/schema.ts +++ b/frontend/src/telemetry/schema.ts @@ -103,7 +103,7 @@ export interface AgentDeployFailedProps { errorCode?: string; } -export type SandboxKind = "codex" | "openclaw" | "hermes"; +export type SandboxKind = "codex" | "deepseek-harness" | "openclaw" | "hermes"; export interface SandboxCreateStartedProps { sandboxKind: SandboxKind; @@ -138,6 +138,7 @@ export type AgentConnectKind = | "runtime" | "local" | "codex" + | "deepseek-harness" | "openclaw" | "hermes"; export type AgentConnectSource = @@ -179,7 +180,7 @@ export interface AgentConnectFailedProps { export interface AgentMessageStartedProps { agentId: string; - agentKind: "runtime" | "codex" | "openclaw" | "hermes"; + agentKind: "runtime" | "codex" | "deepseek-harness" | "openclaw" | "hermes"; messageSource: "composer" | "a2ui_action"; sessionState: "new" | "existing"; sessionId?: string; diff --git a/frontend/src/ui/Composer.tsx b/frontend/src/ui/Composer.tsx index 1ebe2d1f0..8315bdb90 100644 --- a/frontend/src/ui/Composer.tsx +++ b/frontend/src/ui/Composer.tsx @@ -159,6 +159,7 @@ export interface ComposerProps { onModeChange?: (value: NewChatMode) => void; onTaskChange?: (value: NewChatTask | null) => void; temporaryEnabled?: boolean; + deepseekHarnessEnabled?: boolean; harnessEnabled?: boolean; builtinTools?: readonly string[]; showAgentPicker?: boolean; @@ -212,6 +213,7 @@ export function Composer({ onModeChange, onTaskChange, temporaryEnabled, + deepseekHarnessEnabled, harnessEnabled = false, builtinTools = [], showAgentPicker = false, @@ -753,6 +755,7 @@ export function Composer({ onChange={onModeChange} disabled={busy} temporaryEnabled={temporaryEnabled} + deepseekHarnessEnabled={deepseekHarnessEnabled} /> ) : null} diff --git a/frontend/src/ui/MyAgents.tsx b/frontend/src/ui/MyAgents.tsx index 5ad7c6e2a..773837e39 100644 --- a/frontend/src/ui/MyAgents.tsx +++ b/frontend/src/ui/MyAgents.tsx @@ -47,11 +47,17 @@ export interface MyAgentCardData { draft?: WorkspaceAgentDraft; } -export type AgentType = "general" | "codex" | "openclaw" | "hermes"; +export type AgentType = + | "general" + | "codex" + | "deepseek-harness" + | "openclaw" + | "hermes"; const AGENT_TYPES: Array<{ id: AgentType; label: string }> = [ { id: "general", label: "通用智能体" }, { id: "codex", label: "Codex 智能体" }, + { id: "deepseek-harness", label: "DeepSeekHarness 智能体" }, { id: "openclaw", label: "OpenClaw 智能体" }, { id: "hermes", label: "Hermes 智能体" }, ]; diff --git a/frontend/src/ui/SandboxAgentDetails.tsx b/frontend/src/ui/SandboxAgentDetails.tsx index 6d6d398de..1ea2aed50 100644 --- a/frontend/src/ui/SandboxAgentDetails.tsx +++ b/frontend/src/ui/SandboxAgentDetails.tsx @@ -4,6 +4,7 @@ import "./SandboxAgentDetails.css"; const AGENT_LABELS = { codex: "Codex", + "deepseek-harness": "DeepSeekHarness", openclaw: "OpenClaw", hermes: "Hermes", } as const; diff --git a/frontend/src/ui/SandboxAgentWorkspace.tsx b/frontend/src/ui/SandboxAgentWorkspace.tsx index d62ec5101..4f4f9b0a6 100644 --- a/frontend/src/ui/SandboxAgentWorkspace.tsx +++ b/frontend/src/ui/SandboxAgentWorkspace.tsx @@ -18,7 +18,9 @@ export function SandboxAgentWorkspace({ const [terminalUrl, setTerminalUrl] = useState(""); const [terminalLoading, setTerminalLoading] = useState(false); const [terminalError, setTerminalError] = useState(""); - const label = workspace.kind === "openclaw" ? "OpenClaw" : "Hermes"; + const label = workspace.kind === "deepseek-harness" + ? "DeepSeekHarness" + : workspace.kind === "openclaw" ? "OpenClaw" : "Hermes"; useEffect(() => { setSurface("main"); diff --git a/frontend/src/ui/SandboxLaunchDialog.tsx b/frontend/src/ui/SandboxLaunchDialog.tsx index a99b6b19f..c88f640df 100644 --- a/frontend/src/ui/SandboxLaunchDialog.tsx +++ b/frontend/src/ui/SandboxLaunchDialog.tsx @@ -29,7 +29,9 @@ export function SandboxLaunchDialog({ }: SandboxLaunchDialogProps) { const agentLabel = agentKind === "codex" ? "Codex" - : agentKind === "openclaw" ? "OpenClaw" : "Hermes"; + : agentKind === "deepseek-harness" + ? "DeepSeekHarness" + : agentKind === "openclaw" ? "OpenClaw" : "Hermes"; const defaultDisplayName = agentKind === "codex" ? DEFAULT_SANDBOX_DISPLAY_NAME : `我的 ${agentLabel}`; diff --git a/frontend/src/ui/icons/SandboxAgentIcons.tsx b/frontend/src/ui/icons/SandboxAgentIcons.tsx index c33b19d5c..e7202e1c8 100644 --- a/frontend/src/ui/icons/SandboxAgentIcons.tsx +++ b/frontend/src/ui/icons/SandboxAgentIcons.tsx @@ -1,6 +1,10 @@ import type { SVGProps } from "react"; -export type SandboxAgentIconKind = "codex" | "openclaw" | "hermes"; +export type SandboxAgentIconKind = + | "codex" + | "deepseek-harness" + | "openclaw" + | "hermes"; export function CodexAgentIcon(props: SVGProps) { return ( @@ -60,11 +64,32 @@ export function HermesAgentIcon(props: SVGProps) { ); } +export function DeepSeekHarnessAgentIcon(props: SVGProps) { + return ( + + ); +} + export function SandboxAgentIcon({ kind, ...props }: SVGProps & { kind: SandboxAgentIconKind }) { if (kind === "codex") return ; + if (kind === "deepseek-harness") return ; if (kind === "openclaw") return ; return ; } diff --git a/frontend/src/ui/new-chat-modes/NewChatAgentPicker.tsx b/frontend/src/ui/new-chat-modes/NewChatAgentPicker.tsx index 70595b1fc..376bde497 100644 --- a/frontend/src/ui/new-chat-modes/NewChatAgentPicker.tsx +++ b/frontend/src/ui/new-chat-modes/NewChatAgentPicker.tsx @@ -16,7 +16,12 @@ import { AgentFaceIcon } from "../AgentFaceIcon"; import { SandboxAgentIcon } from "../icons/SandboxAgentIcons"; import "./new-chat-agent-picker.css"; -type AgentType = "general" | "codex" | "openclaw" | "hermes"; +type AgentType = + | "general" + | "codex" + | "deepseek-harness" + | "openclaw" + | "hermes"; interface AgentTypeOption { id: AgentType; @@ -26,6 +31,7 @@ interface AgentTypeOption { const AGENT_TYPES: AgentTypeOption[] = [ { id: "general", label: "通用智能体" }, { id: "codex", label: "Codex 智能体" }, + { id: "deepseek-harness", label: "DeepSeekHarness 智能体" }, { id: "openclaw", label: "OpenClaw 智能体" }, { id: "hermes", label: "Hermes 智能体" }, ]; diff --git a/frontend/src/ui/new-chat-modes/NewChatModeSelector.tsx b/frontend/src/ui/new-chat-modes/NewChatModeSelector.tsx index 3c8c9c1bb..457cfc415 100644 --- a/frontend/src/ui/new-chat-modes/NewChatModeSelector.tsx +++ b/frontend/src/ui/new-chat-modes/NewChatModeSelector.tsx @@ -26,6 +26,26 @@ const MODES: ModeOption[] = [ }, ]; +const BUILTIN_AGENTS = [ + { + label: "Codex 智能体", + kind: "codex", + value: "temporary", + description: "在沙箱中执行任务", + }, + { + label: "DeepSeekHarness 智能体", + kind: "deepseek-harness", + value: "deepseek-harness", + description: "打开 DeepSeekHarness 工作区", + }, +] satisfies Array<{ + label: string; + kind: SandboxAgentIconKind; + value: NewChatMode; + description: string; +}>; + const UNAVAILABLE_BUILTIN_AGENTS = [ { label: "ArkClaw", kind: "openclaw" }, { label: "Hermes 智能体", kind: "hermes" }, @@ -39,6 +59,7 @@ export interface NewChatModeSelectorProps { onChange: (value: NewChatMode) => void; disabled?: boolean; temporaryEnabled?: boolean; + deepseekHarnessEnabled?: boolean; } function ModeIcon({ mode }: { mode: NewChatMode }) { @@ -61,27 +82,42 @@ function NestedChevron() { ); } +function modeIndexForValue(value: NewChatMode): number { + const index = MODES.findIndex((mode) => mode.value === value); + return index >= 0 ? index : MODES.findIndex((mode) => mode.value === "temporary"); +} + export function NewChatModeSelector({ value, onChange, disabled = false, temporaryEnabled, + deepseekHarnessEnabled, }: NewChatModeSelectorProps) { const [open, setOpen] = useState(false); const [builtinOpen, setBuiltinOpen] = useState(false); - const [activeIndex, setActiveIndex] = useState(() => - MODES.findIndex((mode) => mode.value === value), - ); + const [activeIndex, setActiveIndex] = useState(() => modeIndexForValue(value)); const rootRef = useRef(null); const triggerRef = useRef(null); - const current = MODES.find((mode) => mode.value === value) ?? MODES[0]; - const currentLabel = current.value === "temporary" ? "Codex 智能体" : current.label; + const current = value === "agent" ? MODES[0] : MODES[1]; + const currentBuiltin = BUILTIN_AGENTS.find((agent) => agent.value === value); + const currentLabel = currentBuiltin?.label ?? current.label; function modeEnabled(mode: ModeOption): boolean | undefined { - if (mode.value === "temporary") return temporaryEnabled; + if (mode.value === "temporary") { + if (temporaryEnabled === true || deepseekHarnessEnabled === true) return true; + if (temporaryEnabled === false && deepseekHarnessEnabled === false) return false; + return undefined; + } return true; } + function builtinEnabled(mode: NewChatMode): boolean | undefined { + if (mode === "temporary") return temporaryEnabled; + if (mode === "deepseek-harness") return deepseekHarnessEnabled; + return false; + } + function modeDisabled(mode: ModeOption): boolean { return modeEnabled(mode) !== true; } @@ -126,8 +162,9 @@ export function NewChatModeSelector({ triggerRef.current?.focus(); } - function chooseBuiltinAgent() { - onChange("temporary"); + function chooseBuiltinAgent(mode: NewChatMode) { + if (builtinEnabled(mode) !== true) return; + onChange(mode); setOpen(false); setBuiltinOpen(false); } @@ -143,7 +180,7 @@ export function NewChatModeSelector({ aria-expanded={open} disabled={disabled} onClick={() => { - setActiveIndex(MODES.findIndex((mode) => mode.value === value)); + setActiveIndex(modeIndexForValue(value)); setOpen((currentOpen) => { if (currentOpen) setBuiltinOpen(false); return !currentOpen; @@ -200,7 +237,7 @@ export function NewChatModeSelector({ key={mode.value} type="button" role="option" - aria-selected={value === mode.value} + aria-selected={current.value === mode.value} aria-haspopup={nested ? "menu" : undefined} aria-expanded={nested ? builtinOpen : undefined} aria-disabled={modeDisabled(mode)} @@ -231,18 +268,31 @@ export function NewChatModeSelector({ {builtinOpen ? (
- + {BUILTIN_AGENTS.map((agent) => { + const enabled = builtinEnabled(agent.value); + return ( + + ); + })} {UNAVAILABLE_BUILTIN_AGENTS.map(({ label, kind }) => (