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();