Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions apps/daemon/internal/agent/mcode/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ func (s *Session) prepareNative() error {
if initialized.ProtocolVersion != 1 {
return fmt.Errorf("mcode: unsupported ACP protocol version %d", initialized.ProtocolVersion)
}
for _, binding := range s.opts.bindings {
if binding.Transport == "stdio" && initialized.Meta.MCPLifecycle.Version != 2 {
return fmt.Errorf("mcode: native MCP lifecycle is unavailable")
}
requiresMCP := !s.req.DisableExecutionEnvironment || slices.ContainsFunc(s.opts.bindings, func(binding agent.MCPBinding) bool {
return binding.Transport == "stdio"
})
if requiresMCP && initialized.Meta.MCPLifecycle.Version != 3 {
return fmt.Errorf("mcode: native MCP lifecycle is unavailable")
}
if !s.req.DisableSubagents && (initialized.Meta.Subagents.Version != 1 || initialized.Meta.Subagents.WorkspaceTools != "protected-mcp-v1" || s.req.MaxConcurrentSubagents == nil || initialized.Meta.Subagents.MaxConcurrent != *s.req.MaxConcurrentSubagents) {
return fmt.Errorf("mcode: native Subagent admission is unavailable")
Expand Down
8 changes: 6 additions & 2 deletions apps/daemon/internal/agent/mcode/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,14 +300,18 @@ func TestMCodeProcess(t *testing.T) {
if scenario == "unattended" && strings.Contains(string(frame.Params), "elicitation") {
os.Exit(7)
}
result = map[string]any{"protocolVersion": 1, "_meta": map[string]any{"oac/mcp-lifecycle": map[string]int{"version": 2}}}
result = map[string]any{"protocolVersion": 1, "_meta": map[string]any{"oac/mcp-lifecycle": map[string]int{"version": 3}}}
if scenario == "unpatched-mcp" {
result = map[string]any{"protocolVersion": 1}
}
if scenario == "old-mcp-lifecycle" {
result = map[string]any{"protocolVersion": 1, "_meta": map[string]any{"oac/mcp-lifecycle": map[string]int{"version": 1}}}
result = map[string]any{"protocolVersion": 1, "_meta": map[string]any{"oac/mcp-lifecycle": map[string]int{"version": 2}}}
}
case "session/new", "session/load":
if scenario == "workspace-not-ready" {
send(rpcFrame{JSONRPC: "2.0", ID: frame.ID, Error: &rpcError{Code: -32603, Message: "Required workspace MCP tools are unavailable."}})
continue
}
if strings.HasPrefix(scenario, "prepared-mcp-") {
if writeMCPRegistry(os.Getenv("MINIMAX_DATA_DIR"), mcpRegistryEntry("proof.server", "proof_server", "read.status", "read_status"), mcpRegistryEntry("late.server", "late_server", "read.status", "read_status"), mcpRegistryEntry("remote", "remote", "read.status", "read_status")) != nil {
os.Exit(12)
Expand Down
16 changes: 16 additions & 0 deletions apps/daemon/internal/agent/mcode/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ func TestViewLaunchesNodeWithGatewayOnly(t *testing.T) {
}
}

func TestWorkspacePreparationRequiresNativeReadiness(t *testing.T) {
for _, scenario := range []string{"old-mcp-lifecycle", "workspace-not-ready"} {
t.Run(scenario, func(t *testing.T) {
record := filepath.Join(t.TempDir(), "calls")
e, err := hostExecutor(t, t.Context(), helperInstall(t, scenario, record), workspaceRequest(t), hostSession(t))
if err == nil || e != nil {
t.Fatal("workspace preparation accepted an unready native owner", e, err)
}
raw, err := os.ReadFile(record)
if err != nil || strings.Contains(string(raw), "session/prompt") || strings.Contains(string(raw), "session/set_config_option") {
t.Fatal("failed readiness proceeded to model selection or prompt", string(raw), err)
}
})
}
}

// With environment none the CLI runs in the work directory without the
// workspace tools. In the workspace it runs each stdio binding's alias
// without arguments and loads each installed Skill from its sandbox path.
Expand Down
1 change: 1 addition & 0 deletions contracts/agents-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Each item is Core's deliberate or native behavior where the official service beh
- Native Item variants beyond those listed under [Turns and Items](./sessions-events.md#turns-and-items) are not projected, and Items cannot be modified.
- A function result that cancellation prevents from being applied never appears as an Item.
- Claude Code's native MCP error frames do not distinguish a local timeout or transport failure from a server's `isError` reply. Failed stdio calls remain eligible for cancellation within their current Turn, including calls for which the server may already have returned an error; successful native results remove their calls from that set. Cancelling such a service also ends its earlier background work under the [cancellation and settlement contract](./harness-onboarding.md#executor-and-turn-lifetimes). The next Turn does not inherit this set.
- MiniMax Code requires its workspace bridge to complete native MCP tool discovery before preparation succeeds. An unavailable or incomplete workspace tool inventory fails preparation; optional MCP retains native lazy discovery.
- MiniMax Code makes declared MCP available to its root ACP Session. Its native task children receive only `oac_workspace` from the generated shared MCP configuration; they do not inherit the root Session's declared MCP bindings.
- Codex cancellation is unqualified for MCP calls in an already active child Turn that a different root Turn steers, when the observer first sees that child Turn after it has settled. Native attribution remains with the original root Turn, and the steering receipt does not identify the child Turn; its locally failed calls cannot be reliably assigned to the cancelling root.
- Pinned Codex can lose command output emitted before its stream subscription.
Expand Down
3 changes: 2 additions & 1 deletion contracts/agents-api/zh/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Agents API 覆盖台账"
source: contracts/agents-api/index.md
source_hash: a99cbf206e87abf84c43ed803ac1a4b054dc26f4ba09bc9ed849af91af066a6f
source_hash: 53d85b1cabf4b8389dd288f86594d9e9605e928412beeba8d240936f5acad06c
---

Core 旨在以下方固定版本为准支持完整的 OpenAI Agents API([public API rule](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/AGENTS.md#public-api))。本台账记录 Core 对各项资源实现了哪些内容、哪些契约保存其详细信息,并列出相对于 OpenAI 服务的所有已知差异和所有未解决缺口。[API namespaces and credentials](../../../docs/zh/api/index.md) 说明谁调用哪些 API;[Agents API guide](../../../docs/zh/api/public-agent-api.md) 介绍使用方法。
Expand Down Expand Up @@ -124,6 +124,7 @@ Core 自身字段位于 `x_agents_core` 中([Core extensions](../../../docs/zh
- 除 [Turns and Items](sessions-events.md#turns-and-items) 中列出的变体外,其他原生 Item 变体不会被投影,而且 Items 无法修改。
- 如果取消导致函数结果无法应用,该结果将永远不会作为 Item 出现。
- Claude Code 的原生 MCP 错误帧无法区分本地超时或传输失败与服务端的 `isError` 回复。失败的 stdio 调用会在其当前 Turn 内保留为取消目标,包括服务端可能已经返回错误的调用;成功的原生结果会将其调用从该集合移除。按照[取消与结算契约](./harness-onboarding.md#executor-and-turn-lifetimes),取消此类服务也会终止该服务先前的后台工作。后继 Turn 不继承这个集合。
- MiniMax Code 要求工作区桥在准备成功前完成原生 MCP 工具发现。工作区工具清单不可用或不完整时,准备会失败;可选 MCP 保持原生惰性发现。
- MiniMax Code 仅向 root ACP Session 提供已声明的 MCP。其原生 task 子代理只从生成的共享 MCP 配置中获得 `oac_workspace`,不继承 root Session 已声明的 MCP 绑定。
- 当另一个 root Turn 向已活跃的子 Turn 追加引导,而观察器首次看到该子 Turn 时它已经结算,Codex 对其中 MCP 调用的取消尚未通过资格验证。原生归属仍指向最初的 root Turn,且引导回执不标识子 Turn;因此无法可靠地将其中本地失败的调用归属于正在取消的 root Turn。
- 固定版本的 Codex 可能会丢失在订阅其流之前发出的命令输出。
Expand Down
6 changes: 3 additions & 3 deletions packages/mcode-harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ One patch script (`patch-native.mjs`) patches the pinned native CLI source. The

## Workspace tools

The native process, its ACP Session and the workspace tools share the Session's workspace as their working directory; native configuration, Skills and history stay in the private Session data directory. Builtin file tools are disabled, so project files are read and written through the bridge's tools. Only the adapter registers this bridge; callers cannot supply its command, profile, working directory or environment. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the same bound workspace.
The native process, its ACP Session and the workspace tools share the Session's workspace as their working directory; native configuration, Skills and history stay in the private Session data directory. Builtin file tools are disabled, so project files are read and written through the bridge's tools. Native Session creation and loading connect the required workspace bridge and validate its tool inventory before preparation succeeds, using the existing 15-second stdio discovery limit and the same connection pool as Turn execution. An unavailable or incomplete bridge fails preparation; optional MCP servers retain native lazy discovery. The expected inventory is generated from the worker's `--describe` output. Only the adapter registers this bridge; callers cannot supply its command, profile, working directory or environment. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the same bound workspace.

For each tool call, the bridge starts `launch.mjs` with the Session's private profile (`workspace-profile.json`, written by the daemon). The launcher checks that the profile's `workspace` is a canonical absolute path, creates the `scratch` directory, and runs the worker in the workspace with the bridge's environment. An invalid profile rejects the call.

Expand All @@ -26,7 +26,7 @@ This standalone companion uses its own npm lock and is excluded from the root pn

The bridge owns each launcher until exit. MCP cancellation and transport shutdown stop all owned workers before releasing the bridge. The outer Runtime owns the native process group. Both boundaries require real Docker cancellation tests.

For declared stdio MCP calls, the adapter captures the affected server identities before sending cancellation and retains identities from callbacks received while cancellation drains. A local failure remains unconfirmed within its Turn even if native reports the tool as finished. The native tool wrapper sets the private `details.oac_response_received` field only for a result returned by the MCP SDK, including a server's `isError` reply; the adapter validates the result identity before releasing that call's owner. It waits for the Runtime to close every selected server's process scope, including background descendants, then calls the private `oac/session/mcp/disconnect` ACP extension with the Session ID and those server names. The native owner disconnects only the selected Session connections and waits for their old transports' actual close events. Configurations remain installed for lazy reconnection on a later call; other MCP servers and the workspace bridge keep their connections. HTTP servers, unknown names and `oac_workspace` are rejected by this control operation. ACP initialization advertises `oac/mcp-lifecycle` version 2, which the adapter requires for declared stdio MCP. The request uses the existing native Session, MCP service and connection pool; it adds no model or tool execution loop.
For declared stdio MCP calls, the adapter captures the affected server identities before sending cancellation and retains identities from callbacks received while cancellation drains. A local failure remains unconfirmed within its Turn even if native reports the tool as finished. The native tool wrapper sets the private `details.oac_response_received` field only for a result returned by the MCP SDK, including a server's `isError` reply; the adapter validates the result identity before releasing that call's owner. It waits for the Runtime to close every selected server's process scope, including background descendants, then calls the private `oac/session/mcp/disconnect` ACP extension with the Session ID and those server names. The native owner disconnects only the selected Session connections and waits for their old transports' actual close events. Configurations remain installed for lazy reconnection on a later call; other MCP servers and the workspace bridge keep their connections. HTTP servers, unknown names and `oac_workspace` are rejected by this control operation. ACP initialization advertises `oac/mcp-lifecycle` version 3, which the adapter requires for a workspace or declared stdio MCP. The request uses the existing native Session, MCP service and connection pool; it adds no model or tool execution loop.

The daemon sets the protected `protected-mcp-v1` tool policy independently of the concurrency limit. The native catalog applies it to root and child profiles, withholding direct native filesystem and process tools. The Session-private `oac_workspace` MCP server supplies the authorized workspace tools to workers. Other MCP servers keep their native selection rules. The same authored policy filters native prompt capabilities, so the ACP root and child templates name builtin workspace tools only when they are available; MCP tool names and schemas come from their declarations. Native Explore and Verifier profiles keep their stricter native capability ceiling. ACP initialization reports the applied policy and admission limit; enabled Subagents reject an unpatched CLI before accepting model input.

Expand All @@ -36,7 +36,7 @@ Native tool schemas are retained. Text and image results use standard MCP conten

`make check-mcode-harness` runs the package's Node tests and syntax checks. The native prompt regression requires `MCODE_SOURCE` pointing to the pinned, patched native source with upstream dependencies installed; the companion build always runs it. It checks ordinary native guidance, disabled local tools and protected root and child capabilities without a model. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harness-onboarding.md#qualify-the-adapter); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance.

With the same `MCODE_SOURCE`, `native-mcp-lifecycle.test.mjs` exercises the patched native Session configurations, connection pool and MCP SDK against controlled transports. It checks exact Session/server selection, delayed close events, connection attempts interrupted during initialization, and later lazy reconnection without closing unaffected services.
With the same `MCODE_SOURCE`, `native-mcp-lifecycle.test.mjs` exercises the patched native Session configurations, connection pool and MCP SDK against controlled transports. It checks exact Session/server selection, delayed close events, connection attempts interrupted during initialization, and later lazy reconnection without closing unaffected services. It also checks workspace readiness before preparation completes, rejection of incomplete or failed discovery, and reuse of the prepared connection.

For the packaged Linux regression, provide an operator-owned private profile and artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime:

Expand Down
Loading
Loading