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
13 changes: 12 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,18 @@ compatibility fallback is permitted. The synthetic third-harness acceptance unde
`services/agents-api/internal/store` exercises the actual gateway and daemon router;
its fixture under `apps/parsar-daemon/testdata` is never a production engine.
See [the integration guide](contracts/agents-api/harnesses.md) for contract and
operation-specific acceptance.
operation-specific acceptance and the [onboarding reference](contracts/agents-api/harness-onboarding.md)
for implementation and registration steps.

MiniMax Code's opt-in Agents API profile qualifies native ACP 0.4.12 for
`environment:none` text execution. It reuses the shared lifecycle without public
workspace, functions, MCP or native Subagents. Native configuration disables
file/shell authority and external
capability discovery; the child receives a private Session home and a restricted
environment. Active-input application requires a native ACP receipt, cancellation
settles the process and output, and continuation requires the exact owned native
history. Do not infer history IDs or qualify hosted execution from this text
profile. See [deployment and acceptance](services/agents-api/deploy/mcode/README.md).

Claude hosted functions compose the existing SDK function bridge with the native
workspace sandbox. Only declared function tools and the verified native tool
Expand Down
3 changes: 3 additions & 0 deletions apps/parsar-daemon/internal/agent/mcode/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func (s *Session) handle(frame rpcFrame) error {
if event.SessionID != s.sessionID {
return nil
}
s.mu.Lock()
s.steeringReady = true
s.mu.Unlock()
switch event.Update.Kind {
case "agent_message_chunk":
if event.Update.Content.Type != "text" {
Expand Down
62 changes: 62 additions & 0 deletions apps/parsar-daemon/internal/agent/mcode/execution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package mcode

import (
"fmt"
"os"

"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
)

// SupportsExecution is an operator opt-in, separate from ordinary product availability.
func SupportsExecution(version string) bool {
return os.Getenv("PARSAR_MCODE_AGENTS_API") == "1" && version == SupportedVersion
}

func validateExecutionRequest(req proto.PromptRequestPayload) error {
if !req.ReleaseOnCompletion || !req.DisableExecutionEnvironment || !req.DisableSubagents || req.WorkDir != "" || req.AgentStateKey == "" || req.RemoteEnvironment != nil || req.LocalEnvironment != nil || req.RequireExistingNativeSession || len(req.FunctionTools) != 0 || (req.MCPHTTPServers != nil && len(*req.MCPHTTPServers) != 0) {
return fmt.Errorf("mcode: unsupported execution configuration")
}
if req.ExecutionControls == nil || req.ExecutionControls.WebSearch != "disabled" || (req.ExecutionControls.TextVerbosity != "" && req.ExecutionControls.TextVerbosity != "medium") {
return fmt.Errorf("mcode: unsupported execution controls")
}
if mode := optionString(req.AgentOptions, "mode"); mode != "" {
return fmt.Errorf("mcode: text execution uses default native permissions")
}
if req.AgentOptions["plugins"] != nil {
return fmt.Errorf("mcode: execution cannot import plugins")
}
if req.AgentOptions["skills"] != nil || req.AgentOptions["mcp_servers"] != nil || req.AgentOptions["env"] != nil {
return fmt.Errorf("mcode: execution cannot import product capabilities or environment")
}
return nil
}

func configureTextExecution(config map[string]any) {
config["agents"] = map[string]any{"default": map[string]any{
"tools": []string{}, "builtinTools": []string{}, "skills": []string{},
"features": map[string]bool{"mavis": false, "delegation": false, "webSearch": false},
}}
config["askUser"] = map[string]bool{"enabled": false}
config["beta"] = map[string]bool{"browserUseTooling": false, "mcodeTools": false, "threadGoal": false}
}

// Only process and model-network essentials cross into the native child.
func executionEnvironment() []string {
var env []string
for _, key := range []string{"PATH", "LANG", "LC_ALL", "TMPDIR", "TMP", "TEMP", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "all_proxy", "no_proxy", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"} {
if value, ok := os.LookupEnv(key); ok {
env = append(env, key+"="+value)
}
}
return env
}

// ACP commands are only recognized for a single text block. Public input must
// remain user text; ordinary product Sessions retain their native command behavior.
func promptContent(text string, public bool) []map[string]string {
blocks := []map[string]string{{"type": "text", "text": text}}
if public {
blocks = append(blocks, map[string]string{"type": "text", "text": ""})
}
return blocks
}
77 changes: 77 additions & 0 deletions apps/parsar-daemon/internal/agent/mcode/execution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package mcode

import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"

"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
)

func executionRequest(t *testing.T) proto.PromptRequestPayload {
r := testRequest(t)
r.StrictResume, r.ReleaseOnCompletion, r.DisableExecutionEnvironment, r.DisableSubagents = true, true, true, true
r.ExecutionControls = &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}
return r
}

func TestExecutionOptionsExcludeAmbientAuthority(t *testing.T) {
r := executionRequest(t)
t.Setenv("AGENTS_API_SECRET_CANARY", "secret")
t.Setenv("NODE_OPTIONS", "--import=untrusted")
opts, err := prepareOptions(t.Context(), r)
if err != nil {
t.Fatal(err)
}
for _, e := range opts.Env {
if strings.HasPrefix(e, "AGENTS_API_SECRET_CANARY=") || strings.HasPrefix(e, "NODE_OPTIONS=") {
t.Fatal("ambient authority inherited")
}
}
data, err := os.ReadFile(filepath.Join(opts.DataDir, "config.yaml"))
if err != nil {
t.Fatal(err)
}
var config map[string]any
if json.Unmarshal(data, &config) != nil {
t.Fatal("bad config")
}
features := config["agents"].(map[string]any)["default"].(map[string]any)["features"].(map[string]any)
for _, k := range []string{"mavis", "delegation", "webSearch"} {
if features[k] != false {
t.Fatalf("%s remains enabled", k)
}
}
}

func TestExecutionRejectsUnqualifiedAuthority(t *testing.T) {
for _, change := range []func(*proto.PromptRequestPayload){
func(r *proto.PromptRequestPayload) { r.DisableExecutionEnvironment = false },
func(r *proto.PromptRequestPayload) { r.DisableSubagents = false },
func(r *proto.PromptRequestPayload) { r.RequireExistingNativeSession = true },
func(r *proto.PromptRequestPayload) { r.WorkDir = "/tmp" },
func(r *proto.PromptRequestPayload) { r.FunctionTools = []proto.FunctionTool{{Name: "f"}} },
func(r *proto.PromptRequestPayload) { r.ExecutionControls.WebSearch = "enabled" },
func(r *proto.PromptRequestPayload) { r.AgentOptions["env"] = map[string]any{"X": "Y"} },
} {
r := executionRequest(t)
change(&r)
if _, err := prepareOptions(t.Context(), r); err == nil {
t.Fatal("unsupported execution accepted")
}
}
}

func TestPublicTextDoesNotInvokeACPCommands(t *testing.T) {
for _, text := range []string{"/model", "/compact", "hello"} {
blocks := promptContent(text, true)
if len(blocks) != 2 || blocks[0]["text"] != text || blocks[1]["text"] != "" {
t.Fatal(blocks)
}
if blocks = promptContent(text, false); len(blocks) != 1 || blocks[0]["text"] != text {
t.Fatal("product prompt changed")
}
}
}
66 changes: 66 additions & 0 deletions apps/parsar-daemon/internal/agent/mcode/native_history_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package mcode

import (
"context"
"encoding/json"
"os"
"strings"
"testing"
"time"

"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
)

// A successful public real-model run supplies an actual foreign native ID.
// Neither that history nor a missing ID may silently become a new session.
func TestNativeMCodeHistoryIsolation(t *testing.T) {
binary, options, foreign := os.Getenv("PARSAR_MCODE_BIN"), os.Getenv("PARSAR_MCODE_REAL_OPTIONS"), os.Getenv("PARSAR_MCODE_FOREIGN_NATIVE_ID")
if binary == "" || options == "" || foreign == "" {
t.Skip("native executable, private provider options and foreign history ID required")
}
raw, err := os.ReadFile(options)
if err != nil {
t.Fatal(err)
}
for name, id := range map[string]string{"foreign": foreign, "missing": "00000000-0000-4000-8000-000000000000"} {
t.Run(name, func(t *testing.T) {
req := executionRequest(t)
if json.Unmarshal(raw, &req.AgentOptions) != nil {
t.Fatal("invalid private options")
}
req.AgentSessionID, req.Prompt = id, "This input must never execute."
ctx, cancel := context.WithTimeout(t.Context(), 90*time.Second)
defer cancel()
out := make(chan proto.Envelope, 64)
session, err := newSession(ctx, req, out, binary)
if err != nil {
t.Fatal(err)
}
defer func() {
cleanup, stop := context.WithTimeout(context.Background(), 10*time.Second)
defer stop()
if err := session.Cancel(cleanup); err != nil {
t.Error(err)
}
}()
rejected := false
for event := range out {
if event.Type == proto.TypeError {
var failure proto.ErrorPayload
_ = json.Unmarshal(event.Payload, &failure)
rejected = strings.Contains(failure.Error, "session/load:") && !strings.Contains(failure.Error, "deadline exceeded")
}
if event.Type == proto.TypeDone {
var done proto.DonePayload
_ = json.Unmarshal(event.Payload, &done)
if done.Content != "" || done.Metadata[proto.DoneMetaAgentSessionID] != nil {
t.Fatal("unowned history executed or silently replaced")
}
}
}
if !rejected {
t.Fatal("native history was not explicitly rejected")
}
})
}
}
35 changes: 25 additions & 10 deletions apps/parsar-daemon/internal/agent/mcode/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ import (
)

type launchOptions struct {
Dir, DataDir, Model, Mode string
Env []string
MCP []map[string]any
Dir, DataDir, Model string
Env []string
MCP []map[string]any
}

func prepareOptions(ctx context.Context, req proto.PromptRequestPayload) (launchOptions, error) {
var result launchOptions
if req.StrictResume {
if err := validateExecutionRequest(req); err != nil {
return result, err
}
}
if len(req.Attachments) > 0 {
return result, fmt.Errorf("mcode: ACP does not support attachments")
}
Expand Down Expand Up @@ -71,6 +76,17 @@ func prepareOptions(ctx context.Context, req proto.PromptRequestPayload) (launch
if result.Model == "" {
return result, fmt.Errorf("mcode: model is required")
}
if req.StrictResume {
configureTextExecution(config)
}
mode := optionString(opts, "mode")
if mode == "" {
mode = "auto"
}
if mode != "auto" && mode != "default" && mode != "bypassPermissions" {
return result, fmt.Errorf("mcode: unsupported permission mode")
}
config["permissionMode"] = mode
data, err := json.Marshal(config)
if err != nil {
return result, err
Expand All @@ -79,7 +95,10 @@ func prepareOptions(ctx context.Context, req proto.PromptRequestPayload) (launch
return result, err
}
result.Env = append([]string{}, os.Environ()...)
if raw := opts["env"]; raw != nil {
if req.StrictResume {
result.Env = executionEnvironment()
}
if raw := opts["env"]; raw != nil && !req.StrictResume {
env, ok := raw.(map[string]any)
if !ok {
return result, fmt.Errorf("mcode: env must be an object")
Expand All @@ -94,12 +113,8 @@ func prepareOptions(ctx context.Context, req proto.PromptRequestPayload) (launch
}
// The adapter owns the native state location, including after cold resume.
result.Env = append(result.Env, "MINIMAX_DATA_DIR="+result.DataDir)
result.Mode = optionString(opts, "mode")
if result.Mode == "" {
result.Mode = "auto"
}
if result.Mode != "auto" && result.Mode != "default" && result.Mode != "bypassPermissions" {
return result, fmt.Errorf("mcode: unsupported permission mode")
if req.StrictResume {
result.Env = append(result.Env, "HOME="+result.DataDir, "USERPROFILE="+result.DataDir)
}
result.MCP, err = mcpServers(opts["mcp_servers"])
return result, err
Expand Down
4 changes: 4 additions & 0 deletions apps/parsar-daemon/internal/agent/mcode/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestOptionsRefreshManagedState(t *testing.T) {
t.Fatal("state override did not win")
}
req.AgentOptions["system_prompt"] = ""
req.AgentOptions["mode"] = "default"
req.AgentSessionID = "native-1"
refreshed, err := prepareOptions(context.Background(), req)
if err != nil {
Expand All @@ -45,6 +46,9 @@ func TestOptionsRefreshManagedState(t *testing.T) {
if json.Unmarshal(data, &cfg) != nil {
t.Fatal("invalid config")
}
if cfg["permissionMode"] != "default" {
t.Fatal("requested native permission mode was not refreshed")
}
if cfg["skills"].(map[string]any)["external"].(map[string]any)["enabled"] != false {
t.Fatal("external discovery enabled")
}
Expand Down
Loading
Loading