diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2e8ed9d5..b6ea24ef 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" + ? "DeepSeek Harness" : 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 e5c75b7a..065aaaa7 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 747a6694..d29dc066 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 d0a81837..7bf229f2 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 1ebe2d1f..8315bdb9 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 5ad7c6e2..72ea5c46 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: "DeepSeek Harness" }, { id: "openclaw", label: "OpenClaw 智能体" }, { id: "hermes", label: "Hermes 智能体" }, ]; diff --git a/frontend/src/ui/SandboxAgentDetails.tsx b/frontend/src/ui/SandboxAgentDetails.tsx index 6d6d398d..d60b6e32 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": "DeepSeek Harness", openclaw: "OpenClaw", hermes: "Hermes", } as const; diff --git a/frontend/src/ui/SandboxAgentWorkspace.tsx b/frontend/src/ui/SandboxAgentWorkspace.tsx index d62ec510..6ea1e03b 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" + ? "DeepSeek Harness" + : workspace.kind === "openclaw" ? "OpenClaw" : "Hermes"; useEffect(() => { setSurface("main"); diff --git a/frontend/src/ui/SandboxLaunchDialog.tsx b/frontend/src/ui/SandboxLaunchDialog.tsx index a99b6b19..15e99989 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" + ? "DeepSeek Harness" + : 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 c33b19d5..e7202e1c 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 70595b1f..683716a4 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: "DeepSeek Harness" }, { 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 3c8c9c1b..9fc986d5 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: "DeepSeek Harness", + kind: "deepseek-harness", + value: "deepseek-harness", + description: "打开 DeepSeek Harness 工作区", + }, +] 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 }) => (