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: 2 additions & 11 deletions apps/daemon/internal/agent/codex/mcp_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"path/filepath"
"slices"
"strings"

"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace"
Expand Down Expand Up @@ -51,13 +50,8 @@ func runtimeMCPServers(req proto.PromptRequestPayload) (map[string]mcpServerConf
}

func configureMCP(plan *SessionPlan, servers map[string]mcpServerConfig) error {
var codexHome string
for _, entry := range plan.Env {
if value, ok := strings.CutPrefix(entry, "CODEX_HOME="); ok {
codexHome = value
}
}
if codexHome == "" || !filepath.IsAbs(codexHome) {
codexHome := nativeHomeFromPlan(*plan)
if !filepath.IsAbs(codexHome) {
return errors.New("codex: public MCP requires a private native home")
}
// Native OAuth defaults to the global keyring. File mode confines lookup to
Expand All @@ -68,9 +62,6 @@ func configureMCP(plan *SessionPlan, servers map[string]mcpServerConfig) error {
if err := writeCodexMCPConfig(codexHome, servers); err != nil {
return errors.New("codex: cannot write public MCP configuration")
}
if plan.Cwd == "" {
plan.Cwd = codexHome
}
for _, feature := range []string{"plugins", "apps"} {
plan.EnableFeatures = slices.DeleteFunc(plan.EnableFeatures, func(value string) bool { return value == feature })
if !slices.Contains(plan.DisableFeatures, feature) {
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/codex/mcp_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestPublicMCPHTTPPlanOwnsConfigurationAndPreservesHistory(t *testing.T) {
t.Fatal(err)
}
if plan.Cwd != home {
t.Fatal("empty cwd did not resolve to the private home")
t.Fatal("environment:none cwd is not the private home")
}
config, err := os.ReadFile(filepath.Join(home, "config.toml"))
if err != nil {
Expand Down
7 changes: 1 addition & 6 deletions apps/daemon/internal/agent/codex/model_verbosity.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@ func prepareModelVerbosity(ctx context.Context, binary string, plan *SessionPlan
// Protocol medium means the default text amount, which needs no native override.
plan.ExtraConfig = slices.DeleteFunc(plan.ExtraConfig, func(kv [2]string) bool { return kv[0] == "model_verbosity" })
}
codexHome := ""
for _, entry := range plan.Env {
if value, ok := strings.CutPrefix(entry, "CODEX_HOME="); ok {
codexHome = value
}
}
codexHome := nativeHomeFromPlan(*plan)
if !filepath.IsAbs(codexHome) {
return fmt.Errorf("codex: missing managed home for model catalog")
}
Expand Down
16 changes: 14 additions & 2 deletions apps/daemon/internal/agent/codex/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
// the daemon's PromptRequestPayload.
type SessionPlan struct {
// Cwd is the working directory passed to codex and the spawned
// app-server: the bound workspace root for an Environment request. For
// environment:none it is empty, or CODEX_HOME when MCP is configured.
// app-server: the bound workspace root for an Environment request and
// the Session's private CODEX_HOME for environment:none.
Cwd string

// Env is the full environment slice (KEY=value) to layer onto
Expand Down Expand Up @@ -233,6 +233,18 @@ func allocCodexHome(agentStateKey string) (string, error) {
return dir, nil
}

// nativeHomeFromPlan returns the CODEX_HOME codex receives: os/exec keeps the
// last duplicate entry, and BuildSessionPlan appends the allocated home last.
func nativeHomeFromPlan(plan SessionPlan) string {
var home string
for _, entry := range plan.Env {
if value, ok := strings.CutPrefix(entry, "CODEX_HOME="); ok {
home = value
}
}
return home
}

func resetGeneratedConfig(codexHome string) error {
path := filepath.Join(codexHome, "config.toml")
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
Expand Down
7 changes: 1 addition & 6 deletions apps/daemon/internal/agent/codex/recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ func SupportsNativeSessionRecovery(version string) bool {
}

func (s *Session) recoverRoot(plan SessionPlan) (string, error) {
var home string
for _, value := range plan.Env {
if strings.HasPrefix(value, "CODEX_HOME=") {
home = strings.TrimPrefix(value, "CODEX_HOME=")
}
}
home := nativeHomeFromPlan(plan)
if !filepath.IsAbs(home) || !filepath.IsAbs(plan.Cwd) {
return "", errors.New("codex: recovery requires private native history")
}
Expand Down
88 changes: 51 additions & 37 deletions apps/daemon/internal/agent/codex/recovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,43 +142,57 @@ func TestRequiredHistoryResolution(t *testing.T) {
}

func TestPreparedRecoveryCannotStartWithoutExistingHistory(t *testing.T) {
req, cfg, root := preparationFixture(t)
req.RequireExistingNativeSession = true
// Recovery searches history for the bound workspace root.
workspace := filepath.Join(root, "workspace")
if err := os.Mkdir(workspace, 0o700); err != nil {
t.Fatal(err)
}
req.DisableExecutionEnvironment = false
req.LocalEnvironment = &proto.LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", NetworkAccess: "enabled", CapabilitySources: &agentcapabilities.Input{}, WorkspaceRoot: workspace}
p, err := newPreparation(t.Context(), req, cfg)
if err != nil {
t.Fatal(err)
}
defer p.Close()
assertPreparationOnly(t, root)
out := make(chan proto.Envelope, 16)
session, err := p.Start(t.Context(), "recovery-run", proto.TextInput("continue"), out)
if err != nil {
t.Fatal(err)
}
defer session.Cancel(context.Background())
select {
case <-p.session.waitDone:
case <-time.After(4 * time.Second):
t.Fatal("recovery did not terminate")
}
found := false
for _, frame := range preparationFrames(t, root) {
if frame.Method == "thread/list" {
found = true
}
if frame.Method == "thread/start" || frame.Method == "turn/start" {
t.Fatal("missing history started work", frame.Method)
}
}
if !found {
t.Fatal("prepared start lost recovery requirement")
// Recovery searches history for the Session's working directory: the private
// home for environment:none and the bound workspace root otherwise.
for _, environment := range []string{"none", "local"} {
t.Run(environment, func(t *testing.T) {
req, cfg, root := preparationFixture(t)
req.RequireExistingNativeSession = true
cwd, err := allocCodexHome(req.AgentStateKey)
if err != nil {
t.Fatal(err)
}
if environment == "local" {
cwd = filepath.Join(root, "workspace")
if err := os.Mkdir(cwd, 0o700); err != nil {
t.Fatal(err)
}
req.DisableExecutionEnvironment = false
req.LocalEnvironment = &proto.LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", NetworkAccess: "enabled", CapabilitySources: &agentcapabilities.Input{}, WorkspaceRoot: cwd}
}
p, err := newPreparation(t.Context(), req, cfg)
if err != nil {
t.Fatal(err)
}
defer p.Close()
if p.plan.Cwd != cwd {
t.Fatalf("cwd = %q, want %q", p.plan.Cwd, cwd)
}
assertPreparationOnly(t, root)
out := make(chan proto.Envelope, 16)
session, err := p.Start(t.Context(), "recovery-run", proto.TextInput("continue"), out)
if err != nil {
t.Fatal(err)
}
defer session.Cancel(context.Background())
select {
case <-p.session.waitDone:
case <-time.After(4 * time.Second):
t.Fatal("recovery did not terminate")
}
found := false
for _, frame := range preparationFrames(t, root) {
if frame.Method == "thread/list" {
found = true
}
if frame.Method == "thread/start" || frame.Method == "turn/start" {
t.Fatal("missing history started work", frame.Method)
}
}
if !found {
t.Fatal("prepared start lost recovery requirement")
}
})
}
}

Expand Down
3 changes: 3 additions & 0 deletions apps/daemon/internal/agent/codex/session_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg
plan.Sandbox = "danger-full-access"
plan.Permissions = ""
plan.ApprovalPolicy = AskForApproval{String: "never"}
} else if req.DisableExecutionEnvironment {
// environment:none has no workspace; the Session's private home is its cwd.
plan.Cwd = nativeHomeFromPlan(plan)
}

if req.LocalEnvironment != nil {
Expand Down
10 changes: 0 additions & 10 deletions apps/daemon/internal/agent/codex/subagent_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"slices"
stdstrconv "strconv"
"strings"

"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)
Expand Down Expand Up @@ -72,12 +71,3 @@ func verifySubagentObservationProfile(ctx context.Context, rpc *JSONRPCClient, c
}
return nil
}

func nativeHomeFromPlan(plan SessionPlan) string {
for _, value := range plan.Env {
if strings.HasPrefix(value, "CODEX_HOME=") {
return strings.TrimPrefix(value, "CODEX_HOME=")
}
}
return ""
}
Loading