From 7211ddc880e02055cb6ee04eabe5521470ec83a8 Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Sun, 20 Sep 2026 18:00:36 +0800 Subject: [PATCH 1/6] feat: persist Agent execution defaults and select Core harnesses --- CONTRIBUTING.md | 39 +++++++- .../src/components/layout/SessionLauncher.tsx | 20 ++-- apps/web/src/i18n/locales/en-US/admin.json | 6 +- apps/web/src/i18n/locales/zh-CN/admin.json | 6 +- apps/web/src/lib/admin-router.ts | 3 + apps/web/src/lib/core-api.ts | 11 +++ apps/web/src/lib/use-new-conversation.ts | 83 ++++++++++++++++ .../web/src/pages/admin/ConversationsPage.tsx | 72 +------------- .../web/src/pages/admin/CreateAgentDialog.tsx | 7 +- .../pages/admin/agents/AgentConfigSummary.tsx | 6 +- .../src/pages/admin/agents/AgentConfigTab.tsx | 1 - .../admin/agents/AgentExecutionFields.tsx | 48 ++++++++++ .../pages/admin/core/StartSessionDialog.tsx | 62 ------------ contracts/agents-api/README.md | 7 +- contracts/agents-api/harness-selection.md | 88 +++++++++++++++++ contracts/agents-api/openapi.yaml | 31 ++++++ contracts/agents-api/v1/agents.go | 3 + contracts/agents-api/v1/core_extension.go | 20 ++++ contracts/agents-api/v1/sessions.go | 2 + docs/deploy/product-core.md | 24 ++++- .../verification/agent-defaults-2026-09-20.md | 81 ++++++++++++++++ .../connector/agentsapi/defaults_test.go | 44 +++++++++ .../internal/connector/agentsapi/request.go | 12 ++- server/internal/store/core_agent_config.go | 20 ++++ .../store/core_agent_defaults_test.go | 28 ++++++ server/internal/store/core_environment.go | 22 ++++- .../cmd/server/execution_options.go | 31 +++++- .../cmd/server/execution_options_test.go | 30 ++++++ services/agents-api/cmd/server/main.go | 8 +- .../agents-api/cmd/server/managed_runtimes.go | 22 ++++- services/agents-api/internal/api/agents.go | 2 +- .../agents-api/internal/api/agents_update.go | 5 +- services/agents-api/internal/api/handler.go | 12 ++- services/agents-api/internal/api/harness.go | 31 ++++++ .../agents-api/internal/api/harness_test.go | 95 +++++++++++++++++++ .../internal/api/saved_configuration.go | 5 +- .../agents-api/internal/api/session_agent.go | 5 +- .../internal/api/session_creation_identity.go | 2 +- .../internal/api/session_request.go | 2 +- .../internal/api/session_response.go | 3 + .../db/queries/runtime_allocations.sql | 2 +- .../db/sqlc/runtime_allocations.sql.go | 5 +- .../execution/environment_admission.go | 2 +- .../internal/execution/runtime_lifecycle.go | 9 +- .../internal/execution/runtime_pending.go | 8 +- .../execution/runtime_provider_selection.go | 8 ++ .../runtime_provider_selection_test.go | 13 +++ .../internal/store/runtime_allocations.go | 4 +- 48 files changed, 868 insertions(+), 182 deletions(-) create mode 100644 apps/web/src/lib/use-new-conversation.ts create mode 100644 apps/web/src/pages/admin/agents/AgentExecutionFields.tsx delete mode 100644 apps/web/src/pages/admin/core/StartSessionDialog.tsx create mode 100644 contracts/agents-api/harness-selection.md create mode 100644 contracts/agents-api/v1/core_extension.go create mode 100644 docs/verification/agent-defaults-2026-09-20.md create mode 100644 server/internal/connector/agentsapi/defaults_test.go create mode 100644 server/internal/store/core_agent_defaults_test.go create mode 100644 services/agents-api/internal/api/harness.go create mode 100644 services/agents-api/internal/api/harness_test.go create mode 100644 services/agents-api/internal/execution/runtime_provider_selection.go create mode 100644 services/agents-api/internal/execution/runtime_provider_selection_test.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9ea3757..8b976d38 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2360,7 +2360,8 @@ raw tool Items, general web-search control or text-verbosity levels. Router admi for `environment:none` uses the available engine capability, not an engine name. The independent API selects new Session engines through `AGENTS_API_ENGINE` (`codex` by default, `claude_sdk` or `mcode`); existing Sessions keep their stored engine. -This is operator configuration, not a public harness-selector field. API admission, +This remains the deployment default; the optional Core harness extension selects +an enabled engine for one saved or inline Agent configuration. API admission, device selection and the final preclaim check share the execution service's narrow engine policy without importing native adapters. Selected engines require the common durable execution capabilities. Codex retains its general search/verbosity checks; @@ -2617,12 +2618,42 @@ or filesystem isolation. Automatic installation remains separate. four platforms. Companion installation does not grant API authorization; task-scoped uploads keep the current run requester and workspace checks. +### Harness selection and Agent defaults + +Core accepts the optional `agent.x_agents_core.harness` extension through the +saved Agent and inline Session configuration paths. Define the extension once in +`contracts/agents-api/v1`; never use metadata or a competing top-level selector. +Resolve saved overrides before selecting the existing Session engine, and apply +that engine's execution policy before persistence. Omitted selection preserves +the deployment default; explicit unavailable selection fails without fallback. +Effective reads use the persisted engine. See the [extension contract](contracts/agents-api/harness-selection.md) +for null/retry behavior and operator configuration. + +Hosted engine-to-provider selection belongs to Core composition. Admission and +initial allocation share the mapping; retained allocations use their persisted +provider identity. Runtime images must satisfy their existing qualification rules. +Transient model options are partitioned by engine and must not expose another +engine's credentials. Do not infer an engine from a model name or template. + +Parsar Agents save default model, harness extension and a typed, non-confidential +`config.environment` selector. The environment selector is product configuration; +it becomes the official Session `environment`, never part of the protocol Agent. +Only a template reference or supported environment selector is stored, not live +container identity or initialization secrets. An explicitly chosen conversation +environment retains its existing override behavior. Otherwise the first message +uses the Agent defaults. Creating an Agent or opening an empty chat performs no +Core Session or container creation. One conversation/Agent binding freezes the +request on first execution; later messages and observer retries reuse it. Separate +conversations get independent Sessions and environments. Edits affect future +Sessions only. Keep the existing product navigation and direct empty-chat composer. + ### Product Core execution - Product Agent configuration uses the complete pinned inline Agent contract: model, instructions (mapped once from the product `system_prompt` field), tools, service_tier, reasoning, text and multi_agent. Do not narrow this schema to the - current Core MVP. Reject old engine/device/provider settings; Core validates + current Core MVP. The explicit Core harness extension and product environment + defaults follow the rules above. Reject old engine/device/provider settings; Core validates its current execution support. Business display/visibility stay in Parsar. A supplied product `config` replaces the execution configuration; omitted config preserves it. The separately supplied SP remains independent of that replacement. @@ -3290,8 +3321,8 @@ wrapped rows grow naturally. Keep this behavior in the shared header, with `actionClassName` reserved for page-specific action arrangements. Agent forms edit the model, instructions and pinned upstream Agent configuration. -Environment templates belong to Core; Session creation chooses a template or -an explicit environment type. Preserve drafts when an upstream operation fails +Environment templates belong to Core; Agent forms save a default template or +explicit environment type for new Sessions. Preserve drafts when an upstream operation fails and show unavailable features without inventing a successful local substitute. New and cloned Agents default invocation scope to workspace. Scope choices explain the Feishu gate without implying anonymous Web/API access. diff --git a/apps/web/src/components/layout/SessionLauncher.tsx b/apps/web/src/components/layout/SessionLauncher.tsx index 8f69d6e8..a4e2a401 100644 --- a/apps/web/src/components/layout/SessionLauncher.tsx +++ b/apps/web/src/components/layout/SessionLauncher.tsx @@ -1,19 +1,17 @@ -import { useEffect, useState } from "react" +import { useEffect } from "react" import { useWorkspaceId } from "../../lib/workspace" -import { useMyWorkspaces } from "../../lib/api-workspaces" -import { StartSessionDialog } from "../../pages/admin/core/StartSessionDialog" +import { useAdminView } from "../../lib/admin-router" export function SessionLauncher() { const workspaceID = useWorkspaceId() - const workspaces = useMyWorkspaces() - const role = workspaces.data?.workspaces.find(workspace => workspace.id === workspaceID)?.role - const [session, setSession] = useState<{ agentID?: string } | null>(null) + const { navigate } = useAdminView() useEffect(() => { - const start = (event: Event) => setSession({ agentID: (event as CustomEvent).detail }) + const start = (event: Event) => { + if (!workspaceID) return + navigate("conversations", { focus: "compose", agent: (event as CustomEvent).detail }) + } window.addEventListener("core:start-session", start) return () => window.removeEventListener("core:start-session", start) - }, []) - return session && workspaceID && role && role !== "viewer" - ? setSession(null)} /> - : null + }, [workspaceID, navigate]) + return null } diff --git a/apps/web/src/i18n/locales/en-US/admin.json b/apps/web/src/i18n/locales/en-US/admin.json index f2d891af..73c6a952 100644 --- a/apps/web/src/i18n/locales/en-US/admin.json +++ b/apps/web/src/i18n/locales/en-US/admin.json @@ -2921,6 +2921,10 @@ "skillsPending": "Import, edit and version Skill assets in the capability library. Loading them for an Agent requires Core environment initialization support, which is not connected yet. Saving an asset does not activate it for execution.", "memberEnvironment": "Choose an environment when starting a new conversation. Each conversation uses its own Core session and environment; an Agent does not own a permanent container or share a workspace across conversations.", "templateSupport": "Name and enabled/disabled network access are connected. Packages, setup commands, environment variables, restricted network rules and capability installation are waiting for Core support.", - "assetManagementHint": "Manage Parsar capability assets and MCP configuration here. Importing or authorizing an asset does not enable Agent execution. Runtime loading belongs to Core and is not connected yet." + "assetManagementHint": "Manage Parsar capability assets and MCP configuration here. Importing or authorizing an asset does not enable Agent execution. Runtime loading belongs to Core and is not connected yet.", + "executionDefaults": "Default execution configuration", + "defaultHarness": "Core default harness", + "harnessHint": "Uses a harness configured in Core. Unavailable harnesses and unsupported model or environment combinations fail explicitly.", + "executionDefaultsHint": "Saving does not start an environment. The first message creates an independent environment, reused by this chat. Changes apply to new chats only." } } diff --git a/apps/web/src/i18n/locales/zh-CN/admin.json b/apps/web/src/i18n/locales/zh-CN/admin.json index 96dde15e..2a9f8329 100644 --- a/apps/web/src/i18n/locales/zh-CN/admin.json +++ b/apps/web/src/i18n/locales/zh-CN/admin.json @@ -2921,6 +2921,10 @@ "skillsPending": "Skills 资产可在能力库中导入、编辑和管理版本。将它们加载给 Agent 运行,需要 Core 的环境初始化支持,目前等待实现。保存资产不表示已在 Agent 中启用。", "memberEnvironment": "发起新对话时选择运行环境。每段对话有独立的 Core 会话和环境;Agent 不永久占用容器,也不默认跨对话共享工作目录。", "templateSupport": "名称和启用/禁用网络已接通。软件包、初始化命令、环境变量、域名限制及能力安装等待 Core 实现。", - "assetManagementHint": "这里管理 Parsar 的能力资产和 MCP 配置。导入或授权成功不代表已可供 Agent 运行;运行时加载由 Core 承载,目前尚未接通。" + "assetManagementHint": "这里管理 Parsar 的能力资产和 MCP 配置。导入或授权成功不代表已可供 Agent 运行;运行时加载由 Core 承载,目前尚未接通。", + "executionDefaults": "默认执行配置", + "defaultHarness": "Core 默认 Harness", + "harnessHint": "使用 Core 已配置的 Harness。未启用或不支持的模型与环境组合会明确报错。", + "executionDefaultsHint": "只保存配置。第一条消息才创建独立环境;同一聊天持续复用。修改仅影响之后的新聊天。" } } diff --git a/apps/web/src/lib/admin-router.ts b/apps/web/src/lib/admin-router.ts index 7a154a80..90228399 100644 --- a/apps/web/src/lib/admin-router.ts +++ b/apps/web/src/lib/admin-router.ts @@ -110,6 +110,7 @@ export interface NavigateOptions { item?: string | null from?: string | null pendingCapability?: string | null + agent?: string | null focus?: string | null } @@ -172,6 +173,7 @@ export function useNavigateAdmin() { setOptionalParam(url, "item", opts?.item) setOptionalParam(url, "from", opts?.from) setOptionalParam(url, "pendingCapability", opts?.pendingCapability) + setOneShotParam(url, "agent", opts?.agent) setOneShotParam(url, "focus", opts?.focus) window.history.pushState({}, "", url.toString()) window.dispatchEvent(new Event("admin:navigate")) @@ -217,6 +219,7 @@ export function navigateAdmin(next: AdminView, opts?: NavigateOptions) { setOptionalParam(url, "item", opts?.item) setOptionalParam(url, "from", opts?.from) setOptionalParam(url, "pendingCapability", opts?.pendingCapability) + setOneShotParam(url, "agent", opts?.agent) setOneShotParam(url, "focus", opts?.focus) window.history.pushState({}, "", url.toString()) window.dispatchEvent(new Event("admin:navigate")) diff --git a/apps/web/src/lib/core-api.ts b/apps/web/src/lib/core-api.ts index 078bb89d..aef1f203 100644 --- a/apps/web/src/lib/core-api.ts +++ b/apps/web/src/lib/core-api.ts @@ -5,7 +5,10 @@ export type CoreEnvironment = | { type: "openai_hosted"; environment_template_id?: string } | { type: "self_hosted"; workspace_directory: string; capability_directories?: string[] } | { type: "none" } +export type CoreHarness = "codex" | "claude_sdk" | "mcode" export interface CoreAgentConfig { + x_agents_core?: { harness: CoreHarness } | null + environment?: CoreEnvironment model: string service_tier?: "auto" | "default" | "flex" | "priority" | "fast" | null tools?: Record[] | null @@ -54,3 +57,11 @@ export function jsonObject(text: string): Record { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Expected a JSON object") return value as Record } + +export function coreExecutionDefaults(config: Record | null | undefined): { harness: CoreHarness | ""; environment: CoreEnvironment } { + const extension = config?.x_agents_core as CoreAgentConfig["x_agents_core"] + return { harness: extension?.harness ?? "", environment: (config?.environment as CoreEnvironment | undefined) ?? { type: "openai_hosted" } } +} +export function coreHarnessLabel(harness: CoreHarness | ""): string { + return { codex: "Codex", claude_sdk: "Claude Code", mcode: "MiniMax Code", "": "" }[harness] +} diff --git a/apps/web/src/lib/use-new-conversation.ts b/apps/web/src/lib/use-new-conversation.ts new file mode 100644 index 00000000..ff755f8a --- /dev/null +++ b/apps/web/src/lib/use-new-conversation.ts @@ -0,0 +1,83 @@ +import { useEffect, useRef } from "react" +import { useQueryClient } from "@tanstack/react-query" +import { useAdminView } from "./admin-router" +import { createConversation, sendUserMessage } from "./api-conversations" +import { writeConversationViewState } from "./conversation-view-state" + +export function useNewConversation(wsId: string | null, selectedAgentId: string, entityId: string | null) { + const qc = useQueryClient() + const { navigate } = useAdminView() + const firstSendPending = useRef | null>(null) + const firstSend = useRef<{ + workspaceId: string + agentId: string + conversationId?: string + } | null>(null) + + useEffect(() => { + firstSendPending.current = null + firstSend.current = null + }, [wsId, selectedAgentId, entityId]) + + // "New conversation" navigates to an empty composer without pre-creating a + // conv — the conv is created on first send via handleSendFromEmpty, + // so the list only shows rows with a real first user turn. + const openCreate = () => { + firstSend.current = null + firstSendPending.current = null + navigate("conversations", { id: "", focus: "compose", agent: selectedAgentId }) + } + + // First-send creates the conv + posts the message + navigates in. + // Title derives from the first 30 chars so the list gets a + // meaningful name immediately (server defaults to "Untitled conversation"). + const handleSendFromEmpty = (content: string): Promise => { + if (firstSendPending.current) return firstSendPending.current + const pending = sendFromEmpty(content).finally(() => { if (firstSendPending.current === pending) firstSendPending.current = null }) + firstSendPending.current = pending + return pending + } + const sendFromEmpty = async (content: string): Promise => { + if (!wsId || !selectedAgentId) { + throw new Error("workspace_id and agent_id required for empty-state send") + } + let attempt = firstSend.current + if (!attempt || attempt.workspaceId !== wsId || attempt.agentId !== selectedAgentId) { + attempt = { workspaceId: wsId, agentId: selectedAgentId } + firstSend.current = attempt + } + let cid = attempt.conversationId + if (!cid) { + const conv = await createConversation(wsId, { + title: content.slice(0, 30), + surface: "web", + form: "thread", + agent_id: selectedAgentId, + }) + cid = conv.id + const createdAttempt = { ...attempt, conversationId: cid } + if (firstSend.current === attempt) firstSend.current = createdAttempt + attempt = createdAttempt + } + try { + await sendUserMessage(cid, { content }) + } finally { + // A failed first message still leaves a real conversation in the list. + qc.invalidateQueries({ + predicate: (q) => + q.queryKey[0] === "admin" && q.queryKey[1] === "conversations" && q.queryKey[2] === wsId, + }) + qc.invalidateQueries({ queryKey: ["admin", "conversationTimeline", cid] }) + } + if (firstSend.current !== attempt) return false + firstSend.current = null + writeConversationViewState(wsId, { + agentId: selectedAgentId, + conversationId: cid, + }) + navigate("conversations", { id: cid }) + return true + } + + return { openCreate, handleSendFromEmpty, resetFirstSend: () => { firstSend.current = null; firstSendPending.current = null } } +} diff --git a/apps/web/src/pages/admin/ConversationsPage.tsx b/apps/web/src/pages/admin/ConversationsPage.tsx index 5240052a..6ec208b1 100644 --- a/apps/web/src/pages/admin/ConversationsPage.tsx +++ b/apps/web/src/pages/admin/ConversationsPage.tsx @@ -1,3 +1,4 @@ +import { useNewConversation } from "../../lib/use-new-conversation" import { requestStartSession } from "../../lib/core-api" import { useQueryClient } from "@tanstack/react-query" import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from "react" @@ -37,8 +38,6 @@ import { useAdminView } from "../../lib/admin-router" import { ApiError } from "../../lib/api-client" import { useAgents } from "../../lib/api-agents" import { - createConversation, - sendUserMessage, useConversation, useDeleteConversation, useConversations, @@ -120,7 +119,7 @@ export function ConversationsPage() { pickedAgent.workspaceId === wsId && pickedAgent.agentId ? pickedAgent.agentId : savedViewState.agentId - const selectedAgentId = currentConv?.primary_agent_id || pickedAgentId || (allAgents[0]?.id ?? "") + const selectedAgentId = currentConv?.primary_agent_id || (!entityId && new URLSearchParams(window.location.search).get("agent")) || pickedAgentId || (allAgents[0]?.id ?? "") const selectedAgent = allAgents.find((a) => a.id === selectedAgentId) useEffect(() => { @@ -171,68 +170,7 @@ export function ConversationsPage() { } const qc = useQueryClient() - const firstSend = useRef<{ - workspaceId: string - agentId: string - conversationId?: string - } | null>(null) - - useEffect(() => { - firstSend.current = null - }, [wsId, selectedAgentId, entityId]) - - // "New conversation" navigates to an empty composer without pre-creating a - // conv — the conv is created on first send via handleSendFromEmpty, - // so the list only shows rows with a real first user turn. - const openCreate = () => { - firstSend.current = null - navigate("conversations", { id: "", focus: "compose" }) - } - - // First-send creates the conv + posts the message + navigates in. - // Title derives from the first 30 chars so the list gets a - // meaningful name immediately (server defaults to "Untitled conversation"). - const handleSendFromEmpty = async (content: string): Promise => { - if (!wsId || !selectedAgentId) { - throw new Error("workspace_id and agent_id required for empty-state send") - } - let attempt = firstSend.current - if (!attempt || attempt.workspaceId !== wsId || attempt.agentId !== selectedAgentId) { - attempt = { workspaceId: wsId, agentId: selectedAgentId } - firstSend.current = attempt - } - let cid = attempt.conversationId - if (!cid) { - const conv = await createConversation(wsId, { - title: content.slice(0, 30), - surface: "web", - form: "thread", - agent_id: selectedAgentId, - }) - cid = conv.id - const createdAttempt = { ...attempt, conversationId: cid } - if (firstSend.current === attempt) firstSend.current = createdAttempt - attempt = createdAttempt - } - try { - await sendUserMessage(cid, { content }) - } finally { - // A failed first message still leaves a real conversation in the list. - qc.invalidateQueries({ - predicate: (q) => - q.queryKey[0] === "admin" && q.queryKey[1] === "conversations" && q.queryKey[2] === wsId, - }) - qc.invalidateQueries({ queryKey: ["admin", "conversationTimeline", cid] }) - } - if (firstSend.current !== attempt) return false - firstSend.current = null - writeConversationViewState(wsId, { - agentId: selectedAgentId, - conversationId: cid, - }) - navigate("conversations", { id: cid }) - return true - } + const { openCreate, handleSendFromEmpty, resetFirstSend } = useNewConversation(wsId, selectedAgentId, entityId) const renameMutation = useUpdateConversationTitle(wsId) const deleteMutation = useDeleteConversation(wsId) @@ -261,7 +199,7 @@ export function ConversationsPage() { canDelete={workspaceRole === "owner" || workspaceRole === "admin"} selectedAgentId={selectedAgentId} onPickAgent={(id) => { - firstSend.current = null + resetFirstSend() setPickedAgent({ workspaceId: wsId, agentId: id }) writeConversationViewState(wsId, { agentId: id }) navigate("conversations", { id: "", focus: "compose" }) @@ -270,7 +208,7 @@ export function ConversationsPage() { conversations={conversations} selectedConversationId={entityId ?? ""} onPickConversation={(id) => { - firstSend.current = null + resetFirstSend() writeConversationViewState(wsId, { agentId: selectedAgentId, conversationId: id, diff --git a/apps/web/src/pages/admin/CreateAgentDialog.tsx b/apps/web/src/pages/admin/CreateAgentDialog.tsx index 3f985399..8889621d 100644 --- a/apps/web/src/pages/admin/CreateAgentDialog.tsx +++ b/apps/web/src/pages/admin/CreateAgentDialog.tsx @@ -1,11 +1,12 @@ import { Textarea } from "../../components/ui/textarea" -import { jsonObject, type CoreAgentConfig } from "../../lib/core-api" +import { jsonObject, coreExecutionDefaults, type CoreAgentConfig } from "../../lib/core-api" import { useId, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { Button } from "../../components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../components/ui/dialog" import { Input } from "../../components/ui/input" import { Label } from "../../components/ui/label" +import { AgentExecutionFields } from "./agents/AgentExecutionFields" import { AgentInstructionsField } from "./agents/AgentInstructionsField" import { AgentVisibilityField } from "./agents/AgentVisibilityField" import { AgentSaveErrorDialog } from "./agents/AgentSaveErrorDialog" @@ -36,6 +37,7 @@ export function CreateAgentDialog(props: CreateAgentDialogProps) { const id = useId() const submitRef = useRef(null) const scopeRef = useRef(null) + const [execution, setExecution] = useState(() => coreExecutionDefaults(props.agent?.config)) const [name, setName] = useState(props.agent?.name ?? "") const [description, setDescription] = useState(props.agent?.description ?? "") const [model, setModel] = useState(String(props.agent?.config?.model ?? "")) @@ -57,7 +59,7 @@ export function CreateAgentDialog(props: CreateAgentDialogProps) { } catch (error) { setConfigurationError(error instanceof Error ? error.message : t("core.failed")); return } props.onSubmit({ agentID: props.mode === "edit" ? props.agent?.id : undefined, - body: { name: name.trim(), description: description.trim(), connector_type: "agents_api", system_prompt: instructions, config: { ...extra, model: model.trim() } as CoreAgentConfig, ...(props.mode === "create" ? { visibility } : {}) }, + body: { name: name.trim(), description: description.trim(), connector_type: "agents_api", system_prompt: instructions, config: { ...extra, model: model.trim(), environment: execution.environment, ...(execution.harness ? { x_agents_core: { harness: execution.harness } } : {}) } as CoreAgentConfig, ...(props.mode === "create" ? { visibility } : {}) }, }) }}> @@ -68,6 +70,7 @@ export function CreateAgentDialog(props: CreateAgentDialogProps) {
setName(event.target.value)} disabled={props.pending} required />
setDescription(event.target.value)} disabled={props.pending} />
setModel(event.target.value)} disabled={props.pending} required aria-describedby={`${id}-hint`} />

{t("agents.core.modelHint")}

+ setExecution(value => ({ ...value, harness }))} onEnvironmentChange={environment => setExecution(value => ({ ...value, environment }))} disabled={props.pending} /> {props.mode === "create" && }
{t("core.advanced")}

{t("core.agentAdvancedHint")}