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
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/claudesdk/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestLocalWorkspaceBindingNetworkAndRequiredHistory(t *testing.T) {
config.Workspace.PublicDirectory = config.Workspace.Directory
config.Workspace.NetworkAccess = "enabled"
req := workspaceRequest()
req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "enabled"}
req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "enabled", WorkspaceRoot: config.Workspace.Directory}
req.RequireExistingNativeSession = true
start, _, err := prepare(config, req)
if err != nil || !start.RequireHistory || start.Workspace.NetworkAccess != "enabled" {
Expand Down Expand Up @@ -47,7 +47,7 @@ func TestRestrictedWorkspacePolicyUsesExactBoundAuthority(t *testing.T) {
config.Workspace.NetworkAccess = "restricted"
config.Workspace.AllowedDomains = []string{"Example.com", "api.example.com", "example.com"}
req := workspaceRequest()
req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "restricted", AllowedDomains: []string{"api.example.com", "EXAMPLE.COM"}}
req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "restricted", AllowedDomains: []string{"api.example.com", "EXAMPLE.COM"}, WorkspaceRoot: config.Workspace.Directory}
if _, _, err := prepare(config, req); err == nil {
t.Fatal("Runtime must not promise inner network isolation")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestEnvironmentMCPUsesInstalledLauncherAndSelectedCredential(t *testing.T)
req := workspaceRequest()
token := "selected-user-token"
t.Setenv("MCP_TOKEN", "unselected-native-token")
req.LocalEnvironment = &proto.LocalEnvironment{CapabilityRoot: "/private/runtime/capabilities", NetworkAccess: "enabled", MCP: []proto.EnvironmentMCP{
req.LocalEnvironment = &proto.LocalEnvironment{CapabilityRoot: "/private/runtime/capabilities", NetworkAccess: "enabled", WorkspaceRoot: config.Workspace.Directory, MCP: []proto.EnvironmentMCP{
{InstallationRoot: "/private/runtime/capabilities", WorkspaceRoot: "/private/runtime/workspace", PackageRoot: "plugins/local", Server: agentplugin.MCPServer{Name: "local", Type: "stdio", Command: "untrusted-package-command", Args: []string{"package-argument"}, EnvVars: []string{"MCP_TOKEN"}}},
{InstallationRoot: "/private/runtime/capabilities", WorkspaceRoot: "/private/runtime/workspace", PackageRoot: "plugins/remote", Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp", BearerTokenEnvVar: "MCP_TOKEN"}, BearerToken: &token},
}}
Expand Down
15 changes: 1 addition & 14 deletions apps/daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,7 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
if err != nil || !filepath.IsAbs(root) || !filepath.IsAbs(config.StateDir) || relative == "." || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return fail("SDK state must be in a managed runtime subdirectory")
}
start.Cwd = req.WorkDir
if start.Cwd == "" {
start.Cwd = filepath.Join(config.StateDir, "work")
}
if strings.HasPrefix(start.Cwd, "~/") {
homeDir, err := os.UserHomeDir()
if err != nil {
return startRequest{}, nil, err
}
start.Cwd = filepath.Join(homeDir, strings.TrimPrefix(start.Cwd, "~/"))
}
if !filepath.IsAbs(start.Cwd) {
return fail("work_dir must be absolute or start with ~/")
}
start.Cwd = filepath.Join(config.StateDir, "work")
for _, dir := range []string{config.StateDir, filepath.Join(config.StateDir, "tmp"), start.Cwd} {
if err := os.MkdirAll(dir, 0o700); err != nil {
return startRequest{}, nil, err
Expand Down
4 changes: 1 addition & 3 deletions apps/daemon/internal/agent/claudesdk/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestTextFactoryCompletionAndFailures(t *testing.T) {
}

func TestTextFactoryRejectsUnsupportedInput(t *testing.T) {
for _, kind := range []string{"execution-controls", "tool", "option", "relative", "outside"} {
for _, kind := range []string{"execution-controls", "tool", "option", "outside"} {
t.Run(kind, func(t *testing.T) {
root := t.TempDir()
t.Setenv("OAC_RUNTIME_HOME", root)
Expand All @@ -87,8 +87,6 @@ func TestTextFactoryRejectsUnsupportedInput(t *testing.T) {
request.FunctionTools = []proto.FunctionTool{{}}
case "option":
request.AgentOptions["allowed_tools"] = "anything"
case "relative":
request.WorkDir = "relative"
case "outside":
config.StateDir = filepath.Dir(root)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestSelfHostedToolEnvironment(t *testing.T) {
}
t.Setenv("OAC_RUNTIME_TOOL_ENV_FILE", file)
req := workspaceRequest()
req.LocalEnvironment = &proto.LocalEnvironment{NetworkAccess: "enabled"}
req.LocalEnvironment = &proto.LocalEnvironment{NetworkAccess: "enabled", WorkspaceRoot: config.Workspace.Directory}
profile, _, err := prepareWorkspace(config, req)
if err != nil {
t.Fatal(err)
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/claudesdk/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspace
if req.DisableExecutionEnvironment {
return nil, nil, fmt.Errorf("claudesdk: workspace profile does not support the requested execution combination")
}
if req.WorkDir != "" && req.WorkDir != config.Workspace.Directory {
return nil, nil, fmt.Errorf("claudesdk: work_dir conflicts with the trusted workspace binding")
if req.LocalEnvironment != nil && req.LocalEnvironment.WorkspaceRoot != config.Workspace.Directory {
return nil, nil, fmt.Errorf("claudesdk: workspace root conflicts with the trusted workspace binding")
}
if req.LocalEnvironment != nil && !(agentnetwork.Policy{Access: config.Workspace.NetworkAccess, AllowedDomains: config.Workspace.AllowedDomains}).Equal(agentnetwork.Policy{Access: req.LocalEnvironment.NetworkAccess, AllowedDomains: req.LocalEnvironment.AllowedDomains}) {
return nil, nil, fmt.Errorf("claudesdk: local Runtime network policy mismatch")
Expand Down
9 changes: 5 additions & 4 deletions apps/daemon/internal/agent/claudesdk/workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,16 @@ func TestWorkspaceTrustedBindingAndEnvironment(t *testing.T) {
}

func TestWorkspaceRejectsConflictsBeforeSideEffects(t *testing.T) {
for _, name := range []string{"none", "work-dir", "mcp", "caller-policy", "relative", "missing", "ambient-setting", "duplicate-env", "bad-env"} {
for _, name := range []string{"none", "workspace-root", "mcp", "caller-policy", "relative", "missing", "ambient-setting", "duplicate-env", "bad-env"} {
t.Run(name, func(t *testing.T) {
config := workspaceFixture(t)
req := workspaceRequest()
switch name {
case "none":
req.DisableExecutionEnvironment = true
case "work-dir":
req.WorkDir = config.Workspace.ScratchDir
case "workspace-root":
config.Workspace.NetworkAccess = "enabled"
req.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "enabled", WorkspaceRoot: config.Workspace.ScratchDir}
case "mcp":
req.MCPHTTPServers = &[]proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "remote", ServerURL: "https://example.test/mcp"}}
case "caller-policy":
Expand Down Expand Up @@ -142,7 +143,7 @@ func TestPublicMCPUsesWorkspaceProjectionWithoutCredentialCopy(t *testing.T) {
config := workspaceFixture(t)
config.Workspace.NetworkAccess = "enabled"
req := workspaceRequest()
req.LocalEnvironment = &proto.LocalEnvironment{NetworkAccess: "enabled"}
req.LocalEnvironment = &proto.LocalEnvironment{NetworkAccess: "enabled", WorkspaceRoot: config.Workspace.Directory}
token := "vault-selected-canary"
tools := []string{"prove"}
req.MCPHTTPServers = &[]proto.MCPHTTPServer{{ConnectionOrigin: "environment", ServerLabel: "remote", ServerURL: "https://example.test/mcp", AllowedTools: &tools, Required: true, BearerToken: &token}}
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/codex/execution_controls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestExecutionControlsOverrideWithoutMutatingNativeOptions(t *testing.T) {
if options["model"] != "test-model" || original["web_search"] != "live" || original["model_verbosity"] != "high" {
t.Fatal("operator options mutated")
}
plan, err := BuildSessionPlan("run", "state", "", options)
plan, err := BuildSessionPlan("run", "state", options)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -49,7 +49,7 @@ func TestExecutionControlsRejectIncompleteOrInvalidValues(t *testing.T) {
{WebSearch: "invalid", TextVerbosity: "medium"}, {WebSearch: "disabled", TextVerbosity: "invalid"},
} {
options := executionOptions(proto.PromptRequestPayload{ExecutionControls: &controls})
if plan, err := BuildSessionPlan("run", "state", "", options); err == nil {
if plan, err := BuildSessionPlan("run", "state", options); err == nil {
plan.Cleanup()
t.Fatal("invalid controls accepted", controls)
}
Expand Down
3 changes: 1 addition & 2 deletions apps/daemon/internal/agent/codex/executor_native_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -50,7 +49,7 @@ func TestExecutorNativeReuse(t *testing.T) {
cfg.codexBinary = binary
cfg.logger = obslog.Discard()
req := proto.PromptRequestPayload{
AgentKind: "codex", AgentStateKey: "executor-native", WorkDir: filepath.Join(isolated, "workspace"),
AgentKind: "codex", AgentStateKey: "executor-native",
StrictResume: true, DisableExecutionEnvironment: true, DisableSubagents: true, ObserveMessages: true,
AgentOptions: map[string]any{"model": model, "model_provider": map[string]any{"base_url": endpoint, "protocol": "responses", "api_key": strings.TrimSpace(string(key))}},
FunctionTools: []proto.FunctionTool{{Name: "hold", Description: "Wait until the host supplies a result.", Parameters: json.RawMessage("{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}")}},
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/codex/harness_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestHarnessConfigAppliedWithoutChangingProvider(t *testing.T) {
t.Setenv("OAC_RUNTIME_HOME", t.TempDir())
plan, err := BuildSessionPlan("run", "native-config", "", map[string]any{
plan, err := BuildSessionPlan("run", "native-config", map[string]any{
"model": "fixture", "harness_config": map[string]any{"model_reasoning_effort": "high"},
"model_provider": map[string]any{"base_url": "https://provider.invalid/v1", "protocol": "responses", "api_key": "test-key"},
})
Expand All @@ -27,7 +27,7 @@ func TestHarnessConfigAppliedWithoutChangingProvider(t *testing.T) {
}

func TestHarnessConfigConflictFailsBeforePreparation(t *testing.T) {
_, err := BuildSessionPlan("run", "", "", map[string]any{"harness_config": map[string]any{"model_provider": "bypass"}})
_, err := BuildSessionPlan("run", "", map[string]any{"harness_config": map[string]any{"model_provider": "bypass"}})
if err != harnessconfig.ErrHarnessConfig {
t.Fatalf("configuration must fail before filesystem preparation: %v", err)
}
Expand Down
6 changes: 5 additions & 1 deletion apps/daemon/internal/agent/codex/mcp_http_preflight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func TestPublicMCPHTTPPreparationChecksBeforeNewAndResumedThread(t *testing.T) {
}
defer p.Close()
assertPreparationOnly(t, root)
home, err := allocCodexHome(req.AgentStateKey)
if err != nil {
t.Fatal(err)
}
s, err := p.start(t.Context(), "actual-run", proto.TextInput("actual prompt"), make(chan proto.Envelope, 8))
if err != nil {
t.Fatal(err)
Expand All @@ -191,7 +195,7 @@ func TestPublicMCPHTTPPreparationChecksBeforeNewAndResumedThread(t *testing.T) {
if err := json.Unmarshal(frame.Params, &params); err != nil {
t.Fatal(err)
}
if !checked || params["cwd"] != req.WorkDir || (frame.Method == "thread/resume") != (mode == "resume") {
if !checked || params["cwd"] != home || (frame.Method == "thread/resume") != (mode == "resume") {
t.Fatal("thread started before the check or with another cwd")
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/codex/model_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func TestPlanRejectsNonNativeFrozenProvider(t *testing.T) {
for _, protocol := range []string{"anthropic", "chat_completions"} {
t.Run(protocol, func(t *testing.T) {
provider := map[string]any{"protocol": protocol, "base_url": "https://model.invalid/v1", "api_key": "private-sentinel"}
plan, err := BuildSessionPlan("recovered", "frozen-state", t.TempDir(), map[string]any{"model": "frozen-model", "model_provider": provider})
plan, err := BuildSessionPlan("recovered", "frozen-state", map[string]any{"model": "frozen-model", "model_provider": provider})
if plan.Cleanup != nil {
plan.Cleanup()
}
Expand All @@ -30,7 +30,7 @@ func TestPlanRejectsIncompleteExplicitProvider(t *testing.T) {
{"model": "chosen", "model_provider": nil},
{"model_provider": map[string]any{"protocol": "responses", "base_url": "https://model.example/v1", "api_key": "fixture"}},
} {
if _, err := BuildSessionPlan("frozen", "state", "", options); err == nil {
if _, err := BuildSessionPlan("frozen", "state", options); err == nil {
t.Fatal("explicit provider fell back to native defaults")
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/codex/model_verbosity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestPrepareModelVerbosity(t *testing.T) {
if err := os.WriteFile(binary, []byte("#!/bin/sh\nprintf '%s' '"+catalog+"'\n"), 0700); err != nil {
t.Fatal(err)
}
plan, err := BuildSessionPlan("run", "state", "", map[string]any{"model": "known-model", "model_verbosity": "high"})
plan, err := BuildSessionPlan("run", "state", map[string]any{"model": "known-model", "model_verbosity": "high"})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -80,7 +80,7 @@ func TestPrepareDefaultModelVerbosity(t *testing.T) {
for _, model := range []string{"supported", "unsupported", "unknown-provider-model"} {
for _, level := range []string{"low", "medium", "high"} {
t.Run(model+"/"+level, func(t *testing.T) {
plan, err := BuildSessionPlan("run", "state", "", executionOptions(proto.PromptRequestPayload{AgentOptions: map[string]any{"model": model}, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: level}}))
plan, err := BuildSessionPlan("run", "state", executionOptions(proto.PromptRequestPayload{AgentOptions: map[string]any{"model": model}, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: level}}))
if err != nil {
t.Fatal(err)
}
Expand Down
40 changes: 4 additions & 36 deletions apps/daemon/internal/agent/codex/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import (
// SessionPlan holds the resolved per-prompt launch plan derived from
// the daemon's PromptRequestPayload.
type SessionPlan struct {
// Cwd is the validated working directory passed to codex (and to
// the spawned app-server). Empty when the caller provided no work_dir.
// 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.
Cwd string

// Env is the full environment slice (KEY=value) to layer onto
Expand Down Expand Up @@ -93,7 +94,7 @@ type SessionPlan struct {
// codexBinary in sessionConfig) rather than per-call.
//
// Daemon-managed Codex sessions bypass approvals and the engine sandbox.
func BuildSessionPlan(runID, agentStateKey, workDir string, opts map[string]any) (SessionPlan, error) {
func BuildSessionPlan(runID, agentStateKey string, opts map[string]any) (SessionPlan, error) {
cleanup := func() {}
plan := SessionPlan{
CollaborationMode: CollaborationModeDefault,
Expand Down Expand Up @@ -127,12 +128,6 @@ func BuildSessionPlan(runID, agentStateKey, workDir string, opts map[string]any)
}
}

resolvedCwd, err := resolveWorkDirCodex(workDir)
if err != nil {
return plan, err
}
plan.Cwd = resolvedCwd

plan.Model = stringOpt(opts, "model")
plan.SystemPrompt = stringOpt(opts, "system_prompt")
if override := stringOpt(opts, "override_system_prompt"); override != "" {
Expand Down Expand Up @@ -212,33 +207,6 @@ func BuildSessionPlan(runID, agentStateKey, workDir string, opts map[string]any)
// helpers
// ---------------------------------------------------------------------------

func resolveWorkDirCodex(input string) (string, error) {
trimmed := strings.TrimSpace(input)
if trimmed == "" {
return "", nil
}
var abs string
switch {
case strings.HasPrefix(trimmed, "~/"):
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("codex: resolve home dir: %w", err)
}
abs = filepath.Join(home, strings.TrimPrefix(trimmed, "~/"))
case filepath.IsAbs(trimmed):
abs = trimmed
default:
return "", fmt.Errorf("codex: work_dir must be absolute or start with ~/, got %q", trimmed)
}
// Create the working directory so a user
// naming a fresh project root in the agent wizard works on first
// run instead of erroring with "does not exist".
if err := os.MkdirAll(abs, 0o755); err != nil {
return "", fmt.Errorf("codex: mkdir work_dir %s: %w", abs, err)
}
return abs, nil
}

func allocCodexHome(agentStateKey string) (string, error) {
if strings.TrimSpace(agentStateKey) == "" {
return "", fmt.Errorf("codex: agentStateKey required for CODEX_HOME allocation")
Expand Down
Loading
Loading