From 3b4bb9f71980731e8b4d494b60bb213274e0d818 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Mon, 19 Oct 2026 15:57:26 +0000 Subject: [PATCH 1/2] Fix model limit and MiniMax Code review findings --- .../internal/agent/mcode/environment_mcp_test.go | 9 +++++---- apps/daemon/internal/agent/mcode/executor.go | 10 ++++++---- .../daemon/internal/agent/mcode/model_provider_test.go | 1 - apps/daemon/internal/agent/mcode/options.go | 3 +-- .../features/environment-templates/TemplateDetail.tsx | 6 +++--- apps/web/src/features/system/ModelProviderDialog.tsx | 4 ++-- apps/web/src/i18n/locales/en/templates.ts | 4 +--- apps/web/src/i18n/locales/zh-CN/templates.ts | 4 +--- contracts/agents-api/core-errors.md | 2 +- contracts/agents-api/v1/model_execution.go | 2 +- contracts/agents-api/v1/model_provider_error_test.go | 7 ++++++- contracts/agents-api/zh/core-errors.md | 4 ++-- internal/modelprovider/config.go | 2 +- 13 files changed, 30 insertions(+), 28 deletions(-) diff --git a/apps/daemon/internal/agent/mcode/environment_mcp_test.go b/apps/daemon/internal/agent/mcode/environment_mcp_test.go index 81155ff34..ea8851c97 100644 --- a/apps/daemon/internal/agent/mcode/environment_mcp_test.go +++ b/apps/daemon/internal/agent/mcode/environment_mcp_test.go @@ -92,13 +92,14 @@ func TestEnvironmentMCPRejectsUnqualifiedAuthorityBeforePreparation(t *testing.T case "duplicate": req.LocalEnvironment.MCP = append(req.LocalEnvironment.MCP, environmentMCPFixture()) } - _, err := prepareWorkspaceOptions(testStateRoot(t), c, req) - if err == nil { - _, err = runtimeMCP(req) - } + root := t.TempDir() + _, err := NewExecutorFactory(root, &c)(context.Background(), req) if err == nil || strings.Contains(err.Error(), "confidential-http-token") { t.Fatal("unqualified declaration accepted or credential exposed") } + if entries, _ := os.ReadDir(root); len(entries) != 0 { + t.Fatalf("native files written before MCP was rejected: %v", entries) + } }) } } diff --git a/apps/daemon/internal/agent/mcode/executor.go b/apps/daemon/internal/agent/mcode/executor.go index fc0bb177b..47b5fc3f0 100644 --- a/apps/daemon/internal/agent/mcode/executor.go +++ b/apps/daemon/internal/agent/mcode/executor.go @@ -41,7 +41,11 @@ func NewExecutorFactory(runtimeRoot string, config *WorkspaceConfig) agent.Execu if req.RunID != "" || len(req.Input) != 0 { return nil, fmt.Errorf("mcode: Executor configuration cannot contain Turn input") } - var err error + // MCP is checked before any native file is written. + mcp, err := runtimeMCP(req) + if err != nil { + return nil, err + } var opts launchOptions binary := defaultBinary() if frozen == nil { @@ -50,12 +54,10 @@ func NewExecutorFactory(runtimeRoot string, config *WorkspaceConfig) agent.Execu binary = frozen.Binary opts, err = prepareWorkspaceOptions(runtimeRoot, *frozen, req) } - if err == nil { - opts.MCP, err = runtimeMCP(req) - } if err != nil { return nil, err } + opts.MCP = mcp resource, err := newExecutor(ctx, req, opts, binary) if resource == nil { return nil, err diff --git a/apps/daemon/internal/agent/mcode/model_provider_test.go b/apps/daemon/internal/agent/mcode/model_provider_test.go index f3e40b717..ca1a846bb 100644 --- a/apps/daemon/internal/agent/mcode/model_provider_test.go +++ b/apps/daemon/internal/agent/mcode/model_provider_test.go @@ -89,7 +89,6 @@ func TestOptionsRejectInvalidProvider(t *testing.T) { {"protocol alias", "protocol", "chat-completions"}, {"remote HTTP", "base_url", "http://provider.example/v1"}, {"empty key", "api_key", ""}, - {"output without context", "context_window", 0}, {"excess output", "max_output_tokens", 64001}, {"native options", "options", map[string]any{"apiKey": "fixture-key"}}, } { diff --git a/apps/daemon/internal/agent/mcode/options.go b/apps/daemon/internal/agent/mcode/options.go index fd1480df4..1f8fc7dd3 100644 --- a/apps/daemon/internal/agent/mcode/options.go +++ b/apps/daemon/internal/agent/mcode/options.go @@ -114,7 +114,7 @@ func agentCapabilities(req proto.PromptRequestPayload, workspace *workspaceProfi tools = append(tools, "web_fetch") } } - capabilities := map[string]any{ + return map[string]any{ "tools": tools, "features": map[string]bool{ // The mavis tool carries cron scheduling that runs beyond a Turn, plus session send, agent create and MCP create. @@ -124,7 +124,6 @@ func agentCapabilities(req proto.PromptRequestPayload, workspace *workspaceProfi "webSearch": false, }, } - return capabilities } func optionString(options map[string]any, key string) string { diff --git a/apps/web/src/features/environment-templates/TemplateDetail.tsx b/apps/web/src/features/environment-templates/TemplateDetail.tsx index b7bcaf7a7..0f0538e07 100644 --- a/apps/web/src/features/environment-templates/TemplateDetail.tsx +++ b/apps/web/src/features/environment-templates/TemplateDetail.tsx @@ -9,7 +9,7 @@ import type { EnvironmentTemplateSkill, } from "@oac/agents-client"; -import { PageBody, PageHeader, RefreshButton, Section, StatusDot } from "../../components/console-ui"; +import { HelpTip, PageBody, PageHeader, RefreshButton, Section, StatusDot } from "../../components/console-ui"; import { formatBytes, formatDateTime, MISSING } from "../../lib/format"; import { CopyableId, CopyIdButton } from "../../components/list-ui"; import { templateName } from "./template-name"; @@ -169,12 +169,12 @@ export function TemplateDetailPage({
{t("detail.updated")}
{formatDateTime(template.updated_at, locale)}
{t("detail.network")}
-
{network ? t(`access.${network.access}`) : }
+
{network ? <>{t(`access.${network.access}`)}{network.access !== "enabled" ? {t("accessHelp")} : null} : }
{network?.access === "restricted" ? ( -
{t("detail.allowedDomains")} } help={t("accessHelp.restricted")}> +
{t("detail.allowedDomains")} }>
) : null} diff --git a/apps/web/src/features/system/ModelProviderDialog.tsx b/apps/web/src/features/system/ModelProviderDialog.tsx index 2f97f6432..89439a50f 100644 --- a/apps/web/src/features/system/ModelProviderDialog.tsx +++ b/apps/web/src/features/system/ModelProviderDialog.tsx @@ -86,8 +86,8 @@ export function ModelProviderDialog({ harness, onClose, onSaved, onReread }: { const output = tokenLimit(maxOutputTokens); const limitProblem = (limit: ReturnType) => (limit.problem === "whole" ? t("models.form.wholeNumber") : limit.problem === "large" ? t("models.form.tooLarge") : null); const outputProblem = limitProblem(output); - // The missing or undersized window is the field the administrator must fix. - const contextProblem = limitProblem(context) ?? (!outputProblem && output.value !== undefined && output.value > (context.value ?? 0) ? t("models.form.needsContext") : null); + // An undersized window is the field the administrator must fix. + const contextProblem = limitProblem(context) ?? (!outputProblem && output.value !== undefined && context.value !== undefined && output.value > context.value ? t("models.form.needsContext") : null); const ready = harness !== null && !busy && url !== "" && !urlProblem && apiKey.trim() !== "" && model.trim() !== "" && nativeConfig !== null && !configTooLarge && !protocolProblem && !contextProblem && !outputProblem; function clearNativeConfig() { diff --git a/apps/web/src/i18n/locales/en/templates.ts b/apps/web/src/i18n/locales/en/templates.ts index 918de39df..929094790 100644 --- a/apps/web/src/i18n/locales/en/templates.ts +++ b/apps/web/src/i18n/locales/en/templates.ts @@ -18,9 +18,7 @@ export const templates = { plugins: "Plugins", updated: "Updated", actions: "Actions", }, access: { enabled: "Enabled", disabled: "Disabled", restricted: "Restricted" }, - accessHelp: { - restricted: "Sessions with this policy are rejected, because the Runtime does not restrict network access.", - }, + accessHelp: "Sessions with this policy are rejected, because the Runtime does not restrict network access.", edit: "Edit", delete: "Delete", deleteLabel: "Delete {{name}}", diff --git a/apps/web/src/i18n/locales/zh-CN/templates.ts b/apps/web/src/i18n/locales/zh-CN/templates.ts index d3de33998..a9cca78e0 100644 --- a/apps/web/src/i18n/locales/zh-CN/templates.ts +++ b/apps/web/src/i18n/locales/zh-CN/templates.ts @@ -10,9 +10,7 @@ export const templates: TranslationShape = { emptyTitle: "还没有 Environment 模板", noMatch: "没有匹配的模板", clearFilter: "清除筛选", listLabel: "Environment 模板", columns: { name: "名称", network: "网络", packages: "依赖包", files: "文件", skills: "Skill", plugins: "Plugin", updated: "更新时间", actions: "操作" }, access: { enabled: "已启用", disabled: "已禁用", restricted: "受限" }, - accessHelp: { - restricted: "使用此策略的 Session 会被拒绝,因为 Runtime 不限制网络访问。", - }, + accessHelp: "使用此策略的 Session 会被拒绝,因为 Runtime 不限制网络访问。", edit: "编辑", delete: "删除", deleteLabel: "删除 {{name}}", open: "打开 {{name}}", back: "返回", unrecognized: "含无法识别的配置", unrecognizedHelp: "Core 返回了控制台无法识别的配置。可识别的部分照常显示,其余部分不做推测。", diff --git a/contracts/agents-api/core-errors.md b/contracts/agents-api/core-errors.md index b97be3110..25a3e21a0 100644 --- a/contracts/agents-api/core-errors.md +++ b/contracts/agents-api/core-errors.md @@ -61,7 +61,7 @@ Each code returns HTTP 400 with `type: "invalid_request_error"`. A missing, malf | `model_provider_base_url_invalid` | `base_url` | omitted | Requires HTTPS without credentials, query or fragment | | `model_provider_protocol_unsupported` | `protocol` | `harness` and `allowed_protocols`, from the build's adapter catalog | The protocol is unknown or unsupported by the selected Harness | | `model_provider_api_key_invalid` | `api_key` | `max_length`: 16384 | The key is empty, too long or contains a prohibited character | -| `model_provider_token_limits_invalid` | `context_window` or `max_output_tokens` | omitted | A limit is negative, or `max_output_tokens` exceeds `context_window` (0 when omitted) | +| `model_provider_token_limits_invalid` | `context_window` or `max_output_tokens` | omitted | A limit is negative, or `max_output_tokens` exceeds a set `context_window` | | `model_configuration_model_invalid` | `model` | omitted | The deployment default's model is not a nonempty model identifier | | `harness_config_invalid` | `harness_config` | omitted | The deployment default's native parameters are unsupported or invalid | | `invalid_sandbox_configuration` | `resources.cpus` | `min`: 1, `max`: 255 | The CPU count is outside the supported bounds | diff --git a/contracts/agents-api/v1/model_execution.go b/contracts/agents-api/v1/model_execution.go index e135c855e..c66f516b8 100644 --- a/contracts/agents-api/v1/model_execution.go +++ b/contracts/agents-api/v1/model_execution.go @@ -51,7 +51,7 @@ func (p *ModelProviderInput) validate(registry harnessconfig.Registry) error { if strings.TrimSpace(p.APIKey) == "" || len(p.APIKey) > 16384 || strings.ContainsAny(p.APIKey, "\x00\r\n") { return &ModelProviderError{Code: "model_provider_api_key_invalid", Param: "api_key", message: "invalid model provider API key"} } - if p.ContextWindow < 0 || p.MaxOutputTokens < 0 || (p.MaxOutputTokens > p.ContextWindow) { + if p.ContextWindow < 0 || p.MaxOutputTokens < 0 || (p.ContextWindow > 0 && p.MaxOutputTokens > p.ContextWindow) { param := "max_output_tokens" if p.ContextWindow < 0 { param = "context_window" diff --git a/contracts/agents-api/v1/model_provider_error_test.go b/contracts/agents-api/v1/model_provider_error_test.go index e783a8775..b00738a33 100644 --- a/contracts/agents-api/v1/model_provider_error_test.go +++ b/contracts/agents-api/v1/model_provider_error_test.go @@ -19,7 +19,7 @@ func TestModelProviderErrorMessagesAndPrecedence(t *testing.T) { {"protocol before key", "codex", "model_provider_protocol_unsupported", "protocol", "unsupported model provider protocol", func(p *ModelProviderInput) { p.Protocol = "private"; p.APIKey = "" }}, {"key", "claude_sdk", "model_provider_api_key_invalid", "api_key", "invalid model provider API key", func(p *ModelProviderInput) { p.Protocol = "anthropic"; p.APIKey = "" }}, {"context before output", "codex", "model_provider_token_limits_invalid", "context_window", "invalid model token limits", func(p *ModelProviderInput) { p.ContextWindow = -1; p.MaxOutputTokens = -2 }}, - {"output", "codex", "model_provider_token_limits_invalid", "max_output_tokens", "invalid model token limits", func(p *ModelProviderInput) { p.MaxOutputTokens = 1 }}, + {"output", "codex", "model_provider_token_limits_invalid", "max_output_tokens", "invalid model token limits", func(p *ModelProviderInput) { p.ContextWindow = 1; p.MaxOutputTokens = 2 }}, } { t.Run(tc.name, func(t *testing.T) { p := valid @@ -36,6 +36,11 @@ func TestModelProviderErrorMessagesAndPrecedence(t *testing.T) { if err := missing.Validate(); !errors.As(err, &field) || field.Code != "invalid_model_provider" || field.Param != "" || err.Error() != "model_provider is required" { t.Fatal(err) } + outputOnly := valid + outputOnly.MaxOutputTokens = 32000 + if err := outputOnly.ValidateHarness("codex"); err != nil { + t.Fatal(err) // An omitted context window keeps the native default. + } if err := valid.ValidateHarness("codex"); err != nil { t.Fatal(err) } diff --git a/contracts/agents-api/zh/core-errors.md b/contracts/agents-api/zh/core-errors.md index d5461517c..a14595b16 100644 --- a/contracts/agents-api/zh/core-errors.md +++ b/contracts/agents-api/zh/core-errors.md @@ -1,7 +1,7 @@ --- title: "Core 管理错误" source: contracts/agents-api/core-errors.md -source_hash: 73b7865736df763df48d1b419594feec04ef1c05b57e3d8e0c176e54ab336b7c +source_hash: 8799a6f1ba80217f77e7adac3412e60482be731fcea665dfb9ec19fad92a8d54 --- `/core/v1` 上的错误使用此封装结构。`message` 是安全的英文文本;`code` 和 `param` 可以为 null。客户端依据稳定的 `code` 和可选的 `param` 进行处理,对未知代码显示 `message`,绝不解析消息,也绝不自动重试被拒绝的写操作。 @@ -63,7 +63,7 @@ Web 的控制台服务器在 `/core` 路径上发生自身故障时使用此封 | `model_provider_base_url_invalid` | `base_url` | 省略 | 必须使用 HTTPS,且不得包含凭据、查询或片段 | | `model_provider_protocol_unsupported` | `protocol` | `harness` 和 `allowed_protocols`,来自该构建的适配器目录 | 协议未知,或所选 Harness 不支持该协议 | | `model_provider_api_key_invalid` | `api_key` | `max_length`:16384 | 密钥为空、过长或包含禁止字符 | -| `model_provider_token_limits_invalid` | `context_window` 或 `max_output_tokens` | 省略 | 限制为负数,或 `max_output_tokens` 超过 `context_window`(省略时为 0) | +| `model_provider_token_limits_invalid` | `context_window` 或 `max_output_tokens` | 省略 | 限制为负数,或 `max_output_tokens` 超过已设置的 `context_window` | | `model_configuration_model_invalid` | `model` | 省略 | 部署默认配置的 model 不是非空模型标识符 | | `harness_config_invalid` | `harness_config` | 省略 | 部署默认配置的原生参数不受支持或无效 | | `invalid_sandbox_configuration` | `resources.cpus` | `min`:1,`max`:255 | CPU 数量超出支持范围 | diff --git a/internal/modelprovider/config.go b/internal/modelprovider/config.go index 595beb289..7a8e34e10 100644 --- a/internal/modelprovider/config.go +++ b/internal/modelprovider/config.go @@ -70,7 +70,7 @@ func (p Provider) Validate() error { if strings.TrimSpace(p.APIKey) == "" || len(p.APIKey) > 16384 || strings.ContainsAny(p.APIKey, "\x00\r\n") { return ErrConfiguration } - if p.ContextWindow < 0 || p.MaxOutputTokens < 0 || p.MaxOutputTokens > p.ContextWindow { + if p.ContextWindow < 0 || p.MaxOutputTokens < 0 || (p.ContextWindow > 0 && p.MaxOutputTokens > p.ContextWindow) { return ErrConfiguration } return nil From 0ae58108c854459a1125f01590e58e7350a560a1 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Mon, 19 Oct 2026 16:29:41 +0000 Subject: [PATCH 2/2] Expect max output alone to be valid in the provider dialog --- apps/web/e2e/system.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/e2e/system.spec.ts b/apps/web/e2e/system.spec.ts index 658d8be4e..5a79065d3 100644 --- a/apps/web/e2e/system.spec.ts +++ b/apps/web/e2e/system.spec.ts @@ -41,14 +41,14 @@ test("sets, replaces and clears a harness's default model configuration, and kee // Codex accepts no native Harness configuration. await set.getByText("Advanced settings", { exact: true }).click(); await expect(set.getByLabel("Harness configuration (JSON)")).toHaveCount(0); - // Limits are Core's 32-bit whole numbers, and max output needs a context window at least as large. + // Limits are Core's 32-bit whole numbers, and max output fits a set context window. await set.getByLabel("Max output tokens").fill("32000"); + await expect(set.getByLabel("Context window", { exact: true })).not.toHaveAttribute("aria-invalid", "true"); + await set.getByLabel("Context window", { exact: true }).fill("1000"); await expect(set.getByText("Set a context window at least this large.")).toBeVisible(); await expect(set.getByLabel("Context window", { exact: true })).toHaveAttribute("aria-invalid", "true"); await expect(set.getByLabel("Context window", { exact: true })).toHaveAccessibleDescription(/Set a context window at least this large/); await expect(set.getByLabel("Max output tokens", { exact: true })).not.toHaveAttribute("aria-invalid", "true"); - await set.getByLabel("Context window", { exact: true }).fill("1000"); - await expect(set.getByLabel("Context window", { exact: true })).toHaveAttribute("aria-invalid", "true"); await set.getByLabel("Context window").fill("2147483648"); await expect(set.getByText("Enter a whole number up to 2147483647.")).toBeVisible(); await expect(set.getByRole("button", { name: "Save" })).toBeDisabled();