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
8 changes: 2 additions & 6 deletions apps/daemon/internal/agent/claudesdk/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,12 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, d
return fail(fmt.Errorf("Claude SDK requires an explicit state root"))
}
config = Config{Node: node, Entrypoint: entrypoint, StateDir: filepath.Join(options.StateRoot, "daemon", options.Profile, "runtime", "claude-sdk")}
binding, err := localworkspace.Load()
if err != nil {
return fail(err)
}
if binding != nil {
if binding := localworkspace.Current(); binding != nil {
config.Node, err = filepath.EvalSymlinks(node)
if err != nil {
return fail(err)
}
config, err = ConfigureLocal(config, options.StateRoot, options.RuntimeRoot, os.Getenv("OAC_RUNTIME_WORKSPACE"))
config, err = ConfigureLocal(config, options.StateRoot, options.RuntimeRoot, binding.Workspace())
if err != nil {
return fail(err)
}
Expand Down
3 changes: 1 addition & 2 deletions apps/daemon/internal/agent/codex/environment_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ func SupportsLocalEnvironment(version string) bool {
if !SupportsNativeSessionRecovery(version) || (runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "windows") {
return false
}
binding, err := localworkspace.Load()
return err == nil && binding != nil
return localworkspace.Current() != nil
}
8 changes: 1 addition & 7 deletions apps/daemon/internal/agent/codex/permission_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,11 @@ func TestRuntimeUsesHostPermissions(t *testing.T) {
func TestSelfHostedToolEnvironmentCannotRedirectNativeHistory(t *testing.T) {
root := t.TempDir()
t.Setenv("OAC_RUNTIME_HOME", root)
workspace := filepath.Join(root, "workspace")
if err := os.Mkdir(workspace, 0700); err != nil {
t.Fatal(err)
}
config := filepath.Join(root, "tool-env.json")
if err := os.WriteFile(config, []byte(`{"CODEX_HOME":"wrong","HOME":"wrong","USERPROFILE":"wrong","USER_VALUE":"ready"}`), 0600); err != nil {
t.Fatal(err)
}
for key, value := range map[string]string{"OAC_RUNTIME_ENVIRONMENT_ID": "b3d154b8-543b-4248-97b1-665f9f418d52", "OAC_RUNTIME_SESSION_ID": "33e02e0d-6fc8-4904-9d7a-4b61b9094ae0", "OAC_RUNTIME_WORKSPACE": workspace, "OAC_RUNTIME_CAPABILITY_DIRECTORY": filepath.Join(root, "capabilities"), "OAC_RUNTIME_TOOL_ENV_FILE": config} {
t.Setenv(key, value)
}
t.Setenv("OAC_RUNTIME_TOOL_ENV_FILE", config)
req := proto.PromptRequestPayload{AgentStateKey: "session", DisableSubagents: true, LocalEnvironment: &proto.LocalEnvironment{}}
plan, _, err := prepareSessionPlan(t.Context(), req, sessionConfig{stateRoot: testStateRoot(t)})
if err != nil {
Expand Down
10 changes: 1 addition & 9 deletions apps/daemon/internal/agent/codex/preparation_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,12 @@ func TestPreparationRouterRetainsActualNativeChild(t *testing.T) {
if err := os.Chmod(root, 0700); err != nil {
t.Fatal(err)
}
t.Setenv("OAC_RUNTIME_CAPABILITY_DIRECTORY", filepath.Join(t.TempDir(), "capabilities"))
t.Setenv("OAC_TEST_EXECUTOR_MODE", "complete")
environment, session, workspace := uuid.NewString(), uuid.NewString(), filepath.Join(root, "harness")
if err := os.MkdirAll(workspace, 0700); err != nil {
t.Fatal(err)
}
for key, value := range map[string]string{
"OAC_RUNTIME_ENVIRONMENT_ID": environment,
"OAC_RUNTIME_SESSION_ID": session,
"OAC_RUNTIME_WORKSPACE": workspace,
} {
t.Setenv(key, value)
}
binding, err := localworkspace.Load()
binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, workspace, filepath.Join(t.TempDir(), "capabilities"))
if err != nil {
t.Fatal(err)
}
Expand Down
12 changes: 4 additions & 8 deletions apps/daemon/internal/agent/mcode/discovery_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r
*runtime = *agent.Unavailable(runtime.Info)
fmt.Fprintf(options.Stderr, "oac-daemon: mcode workspace unavailable: %v\n", err)
}
binding, err := localworkspace.Load()
if err != nil {
fail(err)
return nil
}
binding := localworkspace.Current()
if binding == nil {
narrowWorkspace(&runtime.Info.Capabilities, false)
return nil
}
node := os.Getenv("OAC_RUNTIME_MCODE_NODE")
node := runtimeNode()
if node == "" {
node = "node"
}
node, err = exec.LookPath(node)
node, err := exec.LookPath(node)
if err != nil {
fail(err)
return nil
Expand All @@ -50,7 +46,7 @@ func discoverWorkspace(parent context.Context, options agent.DiscoveryOptions, r
fail(err)
return nil
}
c, err := ConfigureLocal(binary, node, os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), os.Getenv("OAC_RUNTIME_WORKSPACE"))
c, err := ConfigureLocal(binary, node, os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), binding.Workspace())
if err == nil {
err = CheckWorkspace(parent, c)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/mcode/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
func Installation() agent.Installation {
return agent.Installation{AgentKind: "mcode", Version: SupportedVersion, Supported: func() bool { return runtime.GOOS == "linux" || runtime.GOOS == "darwin" },
Environment: func(dir, node string) map[string]string {
return map[string]string{binaryEnv: filepath.Join(dir, "native", "cli.js"), "OAC_RUNTIME_MCODE_NODE": node, "OAC_RUNTIME_MCODE_COMPANION": dir}
return map[string]string{binaryEnv: filepath.Join(dir, "native", "cli.js"), nodeEnv: node, "OAC_RUNTIME_MCODE_COMPANION": dir}
},
Check: func(ctx context.Context, dir, node string, env []string) error {
got, err := installroot.Probe(ctx, node, []string{filepath.Join(dir, "native", "cli.js"), "--version"}, env, dir)
Expand Down
7 changes: 6 additions & 1 deletion apps/daemon/internal/agent/mcode/subagents.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,13 @@ type nativeSubagentTask struct {
Metadata struct{ ChildSessionID, ParentSessionID, ParentTurnID, SubTurnID, ExecutionMode string }
}

const nodeEnv = "OAC_RUNTIME_MCODE_NODE"

// runtimeNode is the Node executable the installation or image provides for the companion scripts.
func runtimeNode() string { return os.Getenv(nodeEnv) }

func subagentReader() (string, string, error) {
node := os.Getenv("OAC_RUNTIME_MCODE_NODE")
node := runtimeNode()
reader := filepath.Join(os.Getenv("OAC_RUNTIME_MCODE_COMPANION"), "subagent-snapshot.mjs")
for _, path := range []string{node, reader} {
resolved, err := filepath.EvalSymlinks(path)
Expand Down
9 changes: 3 additions & 6 deletions apps/daemon/internal/agent/mcode/tool_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ func TestWorkspaceCredentialsRemainInRuntimeSnapshotAcrossReconnect(t *testing.T
write(filepath.Join(plugin, ".codex-plugin", "plugin.json"), `{"name":"remote","description":"Credential fixture","mcpServers":"./.mcp.json"}`)
write(filepath.Join(plugin, ".mcp.json"), `{"mcpServers":{"remote":{"type":"http","url":"https://example.invalid/mcp","bearer_token_env_var":"MCP_TEST_TOKEN"}}}`)
environment, session := uuid.NewString(), uuid.NewString()
t.Setenv("OAC_RUNTIME_ENVIRONMENT_ID", environment)
t.Setenv("OAC_RUNTIME_SESSION_ID", session)
t.Setenv("OAC_RUNTIME_WORKSPACE", config.Directory)
t.Setenv("OAC_RUNTIME_CAPABILITY_DIRECTORY", t.TempDir())
capabilityDirectory := t.TempDir()
req.AgentStateKey = session
req.LocalEnvironment.ID, req.LocalEnvironment.WorkspaceDirectory = environment, config.Directory
req.LocalEnvironment.Capabilities = true
Expand All @@ -47,7 +44,7 @@ func TestWorkspaceCredentialsRemainInRuntimeSnapshotAcrossReconnect(t *testing.T
req.AgentSessionID = "native-1"
write(source, `{"MCP_TEST_TOKEN":"changed","TOOL_SECRET":"changed"}`)
}
binding, err := localworkspace.Load()
binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, config.Directory, capabilityDirectory)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -100,7 +97,7 @@ func TestWorkspaceCredentialsRemainInRuntimeSnapshotAcrossReconnect(t *testing.T
if err := os.Remove(filepath.Join(initialization, "tool-env.json")); err != nil {
t.Fatal(err)
}
binding, err := localworkspace.Load()
binding, err := localworkspace.NewWithCapabilityDirectory(environment, session, config.Directory, capabilityDirectory)
if err != nil {
t.Fatal(err)
}
Expand Down
14 changes: 6 additions & 8 deletions apps/daemon/internal/cli/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
//
// The daemon credential comes from a Provider bootstrap file
// (--bootstrap-file), self-hosted Environment enrollment
// (--remote/--environment-id/--credential-file) or the saved profile
// (--remote/--environment-id/--credential-file/--workspace) or the saved profile
// written by oac-core-device.
//
// -b re-execs the binary in the background with stdio redirected to
Expand All @@ -50,6 +50,7 @@ func runConnect(ctx *runContext, args []string) error {
remote = fs.String("remote", "", "self-hosted Environment remote_url, unchanged")
environment = fs.String("environment-id", "", "self-hosted Environment ID")
bootstrapFile = fs.String("bootstrap-file", "", "absolute path to Provider-to-Runtime connection JSON")
workspace = fs.String("workspace", "", "existing absolute workspace directory of a self-hosted Environment")
credentialFile = fs.String("credential-file", "", "absolute path to protected executor credential JSON")
)
if err := fs.Parse(args); err != nil {
Expand All @@ -69,21 +70,21 @@ func runConnect(ctx *runContext, args []string) error {
}
var bootstrapped *auth.Profile
if *bootstrapFile != "" {
if *remote != "" || *environment != "" || *credentialFile != "" || fs.NArg() != 0 {
if *remote != "" || *environment != "" || *credentialFile != "" || *workspace != "" || fs.NArg() != 0 {
return errors.New("connect: bootstrap input cannot be combined with enrollment options")
}
bootstrapped, err = bootstrapProfile(*bootstrapFile)
if err != nil {
return err
}
}
if *remote != "" || *environment != "" || *credentialFile != "" {
if *remote != "" || *environment != "" || *credentialFile != "" || *workspace != "" {
if fs.NArg() != 0 {
return errors.New("connect: Environment enrollment cannot use positional arguments")
}
connectCtx, stop := daemonize.NotifyContext(context.Background())
defer stop()
return runEnvironmentConnect(connectCtx, ctx, *profile, *background, false, *remote, *environment, *credentialFile)
return runEnvironmentConnect(connectCtx, ctx, *profile, *background, false, *remote, *environment, *credentialFile, *workspace, localworkspace.CapabilityDirectory)
}

// -b mode: parent forks, child re-enters with sentinel env set
Expand Down Expand Up @@ -326,10 +327,7 @@ func advertisedKinds(registry *agent.Registry, canExport bool) []proto.Supported
}

func pumpConn(parentCtx context.Context, conn *transport.Conn, registry *agent.Registry, boot *machineapi.BootstrapResponse, agentCLIs agentCLIDiscovery) error {
local, err := localworkspace.Load()
if err != nil {
return err
}
local := localworkspace.Current()
router, err := dispatch.New(dispatch.Config{
Registry: registry,
Sender: conn,
Expand Down
4 changes: 4 additions & 0 deletions apps/daemon/internal/cli/connect_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"errors"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/auth"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimebootstrap"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs"
)
Expand All @@ -18,5 +19,8 @@ func bootstrapProfile(path string) (*auth.Profile, error) {
if err != nil {
return nil, err
}
if err = localworkspace.Bind(input.EnvironmentID, input.SessionID, input.Workspace, localworkspace.CapabilityDirectory); err != nil {
return nil, errors.New("connect: Runtime workspace binding unavailable")
}
return &auth.Profile{ServerURL: input.CoreURL, RuntimeID: input.DeviceID, RunnerCredential: input.Credential}, nil
}
11 changes: 9 additions & 2 deletions apps/daemon/internal/cli/connect_bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"testing"

"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/auth"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/paths"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimebootstrap"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs"
"github.com/google/uuid"
)

func TestBootstrapConnectionDoesNotReadOrOverwritePrivateProfile(t *testing.T) {
Expand All @@ -31,7 +33,8 @@ func TestBootstrapConnectionDoesNotReadOrOverwritePrivateProfile(t *testing.T) {
if err = os.WriteFile(filepath.Join(profileDir, "auth.json"), priorRaw, 0600); err != nil {
t.Fatal(err)
}
input := runtimebootstrap.Connection{Version: runtimebootstrap.Version, CoreURL: "https://core.example/api/v1", DeviceID: "da912024-1543-4242-a2c1-5f4f7ebbc6c7", Credential: "bootstrap-secret"}
input := runtimebootstrap.Connection{Version: runtimebootstrap.Version, CoreURL: "https://core.example/api/v1", DeviceID: "da912024-1543-4242-a2c1-5f4f7ebbc6c7", Credential: "bootstrap-secret",
EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Workspace: t.TempDir()}
raw, err := input.Marshal()
if err != nil {
t.Fatal(err)
Expand All @@ -40,11 +43,15 @@ func TestBootstrapConnectionDoesNotReadOrOverwritePrivateProfile(t *testing.T) {
if err = os.WriteFile(path, raw, 0600); err != nil {
t.Fatal(err)
}
t.Cleanup(localworkspace.Unbind)
for range 2 {
p, err := bootstrapProfile(path)
if err != nil || p.ServerURL != input.CoreURL || p.RuntimeID != input.DeviceID || p.RunnerCredential != input.Credential {
t.Fatal("failed bootstrap/restart", err)
}
if bound := localworkspace.Current(); bound == nil || bound.Workspace() != input.Workspace || bound.Environment() != input.EnvironmentID {
t.Fatal("bootstrap did not bind the workspace")
}
}
got, err := auth.Load("default")
if err != nil || got != prior {
Expand All @@ -70,7 +77,7 @@ func TestConnectBootstrapRejectsOtherCredentialSourcesBeforeSideEffects(t *testi
for _, args := range [][]string{
{"--remote", "wss://core.example/api/v1/agent-daemon/ws"},
{"--credential-file", "/credential.json"},
{"--environment-id", "foreign"}, {"unexpected"},
{"--environment-id", "foreign"}, {"--workspace", "/workspace"}, {"unexpected"},
} {
err := runConnect(ctx, append([]string{"--bootstrap-file", "/not-present"}, args...))
if err == nil || !strings.Contains(err.Error(), "cannot be combined") {
Expand Down
1 change: 0 additions & 1 deletion apps/daemon/internal/cli/connect_cleanup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ func TestDisconnectedPumpRetainsExactExecutorUntilCleanup(t *testing.T) {
}

func testDisconnectedPumpCleanup(t *testing.T, suspend bool) {
t.Setenv("OAC_RUNTIME_WORKSPACE", "")
peers := make(chan *websocket.Conn, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
peer, err := (&websocket.Upgrader{}).Upgrade(w, r, nil)
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/cli/connect_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func environmentBootstrap(ctx context.Context, prof auth.Profile, remote string)
// exitOnRejection selects the native `start` behaviour: a permanent rejection
// ends the process with the message as its error. Otherwise the process that
// owns the connection parks (see parkEnvironment).
func runEnvironmentConnect(parent context.Context, rc *runContext, profile string, background, exitOnRejection bool, remote, environment, credentialFile string) error {
func runEnvironmentConnect(parent context.Context, rc *runContext, profile string, background, exitOnRejection bool, remote, environment, credentialFile, workspace, capabilityDirectory string) error {
base, err := environmentBase(remote)
if err != nil {
return err
Expand Down Expand Up @@ -154,7 +154,7 @@ func runEnvironmentConnect(parent context.Context, rc *runContext, profile strin
if err = parent.Err(); err != nil {
return err
}
if err = bindEnvironmentRuntime(remote, bound, credentialFile); err != nil {
if err = bindEnvironmentRuntime(remote, bound, workspace, capabilityDirectory); err != nil {
return err
}
if background && !daemonize.IsBackgroundChild() {
Expand Down
33 changes: 5 additions & 28 deletions apps/daemon/internal/cli/connect_environment_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace"
"github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/paths"
"github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimebootstrap"
"github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs"
)

Expand Down Expand Up @@ -43,52 +44,28 @@ func readEnvironmentPrivateFile(path string) ([]byte, error) {
return runtimefs.ReadPrivatePath(path, 16*1024)
}

func bindEnvironmentRuntime(remote string, bound machineapi.Enrollment, credentialFile string) error {
func bindEnvironmentRuntime(remote string, bound machineapi.Enrollment, workspace, capabilityDirectory string) error {
root, err := paths.Root()
if err != nil || !filepath.IsAbs(root) {
return errors.New("connect: absolute Runtime state directory required")
}
if err = runtimefs.EnsurePrivateDir(root); err != nil {
return errors.New("connect: Runtime state directory unavailable")
}
workspace := os.Getenv("OAC_RUNTIME_WORKSPACE")
if workspace == "/" || agentcapabilities.ValidateLocalDirectories([]string{workspace}) != nil {
return errors.New("connect: clean absolute Runtime workspace required")
}
info, statErr := os.Stat(workspace)
if statErr != nil || !info.IsDir() {
return errors.New("connect: existing Runtime workspace required")
}
if bound.WorkspaceDirectory != "/workspace" && bound.WorkspaceDirectory != workspace {
if bound.WorkspaceDirectory != runtimebootstrap.PublicWorkspace && bound.WorkspaceDirectory != workspace {
return errors.New("connect: enrollment does not match the Runtime workspace")
}
identity := map[string]string{"OAC_RUNTIME_ENVIRONMENT_ID": bound.EnvironmentID, "OAC_RUNTIME_SESSION_ID": bound.SessionID}
for key, value := range identity {
if previous := os.Getenv(key); previous != "" && previous != value {
return errors.New("connect: conflicting Runtime identity")
}
}
capabilityDirectory := os.Getenv("OAC_RUNTIME_CAPABILITY_DIRECTORY")
if capabilityDirectory == "" {
capabilityDirectory = localworkspace.CapabilityDirectory
}
if _, err := localworkspace.NewWithCapabilityDirectory(bound.EnvironmentID, bound.SessionID, workspace, capabilityDirectory); err != nil {
if err := localworkspace.Bind(bound.EnvironmentID, bound.SessionID, workspace, capabilityDirectory); err != nil {
return errors.New("connect: local Runtime layout unavailable")
}
want := environmentBinding{RemoteURL: remote, Enrollment: bound, LocalWorkspace: workspace, CapabilityDirectory: capabilityDirectory}
if err = saveEnvironmentBinding(root, want); err != nil {
return err
}
for key, value := range identity {
if err = os.Setenv(key, value); err != nil {
return errors.New("connect: Runtime identity configuration failed")
}
}
local, err := localworkspace.Load()
if err != nil || local == nil {
return errors.New("connect: Runtime binding unavailable")
}
return nil
return saveEnvironmentBinding(root, environmentBinding{RemoteURL: remote, Enrollment: bound, LocalWorkspace: workspace, CapabilityDirectory: capabilityDirectory})
}

func saveEnvironmentBinding(root string, want environmentBinding) error {
Expand Down
Loading
Loading