diff --git a/apps/daemon/internal/agent/claudesdk/mcp_environment_test.go b/apps/daemon/internal/agent/claudesdk/mcp_environment_test.go index 60f09c530..d614578ea 100644 --- a/apps/daemon/internal/agent/claudesdk/mcp_environment_test.go +++ b/apps/daemon/internal/agent/claudesdk/mcp_environment_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func TestEnvironmentMCPUsesInstalledLauncherAndSelectedCredential(t *testing.T) { @@ -17,8 +16,8 @@ func TestEnvironmentMCPUsesInstalledLauncherAndSelectedCredential(t *testing.T) token := "selected-user-token" t.Setenv("MCP_TOKEN", "unselected-native-token") req.LocalEnvironment = &proto.LocalEnvironment{CapabilityRoot: "/private/runtime/capabilities", WorkspaceRoot: config.Workspace.Directory, MCP: []proto.EnvironmentMCP{ - {InstallationRoot: "/private/runtime/capabilities", 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", PackageRoot: "plugins/remote", Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp", BearerTokenEnvVar: "MCP_TOKEN"}, BearerToken: &token}, + {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/local", Server: proto.PluginMCPServer{Name: "local", Type: "stdio", Command: "untrusted-package-command", Args: []string{"package-argument"}, EnvVars: []string{"MCP_TOKEN"}}}, + {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/remote", Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp", BearerTokenEnvVar: "MCP_TOKEN"}, BearerToken: &token}, }} start, env, err := prepareConfiguration(config, req) if err != nil { @@ -63,7 +62,7 @@ func TestEnvironmentMCPRejectsUnqualifiedCombinations(t *testing.T) { e.MCP[0].Server.URL = "http://example.invalid/mcp" }, } { - environment := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/remote", Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"}}}} + environment := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/remote", Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"}}}} mutate(environment) if _, _, err := prepareRuntimeMCP(proto.PromptRequestPayload{LocalEnvironment: environment}); err == nil { t.Fatal("unsupported declaration accepted") diff --git a/apps/daemon/internal/agent/claudesdk/workspace.go b/apps/daemon/internal/agent/claudesdk/workspace.go index 6c6c4aa86..e60dd9d66 100644 --- a/apps/daemon/internal/agent/claudesdk/workspace.go +++ b/apps/daemon/internal/agent/claudesdk/workspace.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) @@ -23,14 +22,14 @@ type WorkspaceConfig struct { } type workspaceProfile struct { - ToolEnv map[string]string `json:"tool_env,omitempty"` - CapabilityRoot string `json:"capability_root,omitempty"` - MCP []environmentMCPServer `json:"mcp,omitempty"` - Skills []agentcapabilities.InstalledSkill `json:"skills,omitempty"` - Home string `json:"home"` - State string `json:"state"` - Scratch string `json:"scratch"` - EnvNames []string `json:"env_names"` + ToolEnv map[string]string `json:"tool_env,omitempty"` + CapabilityRoot string `json:"capability_root,omitempty"` + MCP []environmentMCPServer `json:"mcp,omitempty"` + Skills []proto.InstalledSkill `json:"skills,omitempty"` + Home string `json:"home"` + State string `json:"state"` + Scratch string `json:"scratch"` + EnvNames []string `json:"env_names"` } func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspaceProfile, []string, error) { diff --git a/apps/daemon/internal/agent/codex/mcp_environment_test.go b/apps/daemon/internal/agent/codex/mcp_environment_test.go index e2674b1ce..1ab899d43 100644 --- a/apps/daemon/internal/agent/codex/mcp_environment_test.go +++ b/apps/daemon/internal/agent/codex/mcp_environment_test.go @@ -8,14 +8,13 @@ import ( "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func TestEnvironmentMCPProjectsIsolatedStdioAndPrivateHTTPReferences(t *testing.T) { token := "user-token" local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{ - {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/0", Server: agentplugin.MCPServer{Name: "local", Type: "stdio", Command: "must-not-be-native-command", Args: []string{"private-argument"}}}, - {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/1", BearerToken: &token, Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.com/mcp", HTTPHeaders: map[string]string{"X-Key": "literal-${DO_NOT_EXPAND}"}}}, + {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/0", Server: proto.PluginMCPServer{Name: "local", Type: "stdio", Command: "must-not-be-native-command", Args: []string{"private-argument"}}}, + {InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/1", BearerToken: &token, Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.com/mcp", HTTPHeaders: map[string]string{"X-Key": "literal-${DO_NOT_EXPAND}"}}}, }} servers, env, err := runtimeMCPServers(proto.PromptRequestPayload{LocalEnvironment: local}) if err != nil || len(servers) != 2 || len(env) != 1 { @@ -58,7 +57,7 @@ func TestSessionMCPHeadersReachNativeConfig(t *testing.T) { func TestEnvironmentMCPRejectsUnqualifiedCredentialChanges(t *testing.T) { token := "user-token" - local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{BearerToken: &token, Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "http://example.com/mcp"}}}} + local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{BearerToken: &token, Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "http://example.com/mcp"}}}} if _, _, err := runtimeMCPServers(proto.PromptRequestPayload{LocalEnvironment: local}); err == nil { t.Fatal("plaintext bearer accepted") } diff --git a/apps/daemon/internal/agent/codex/preparation_router_test.go b/apps/daemon/internal/agent/codex/preparation_router_test.go index f1a3086d3..db3b7480c 100644 --- a/apps/daemon/internal/agent/codex/preparation_router_test.go +++ b/apps/daemon/internal/agent/codex/preparation_router_test.go @@ -1,12 +1,7 @@ package codex -import "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" - import ( "context" - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/google/uuid" "os" "path/filepath" "testing" @@ -14,8 +9,11 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/dispatch" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" + "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" + "github.com/google/uuid" ) type preparationWireSender chan proto.Envelope @@ -55,7 +53,7 @@ func TestPreparationRouterRetainsActualNativeChild(t *testing.T) { } req.AgentStateKey = session req.DisableExecutionEnvironment = false - req.LocalEnvironment = &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}} + req.LocalEnvironment = &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &proto.CapabilitySources{}} registry := agent.NewRegistry() registry.RegisterKind(proto.SupportedAgentKind{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{LocalEnvironment: proto.CapabilitySupported, FunctionTools: proto.CapabilitySupported})}, harnessconfig.Configuration{}) prepared := make(chan *Prepared, 1) diff --git a/apps/daemon/internal/agent/codex/recovery_test.go b/apps/daemon/internal/agent/codex/recovery_test.go index f859c93e4..82ced82cf 100644 --- a/apps/daemon/internal/agent/codex/recovery_test.go +++ b/apps/daemon/internal/agent/codex/recovery_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/obs/log" "github.com/google/uuid" @@ -158,7 +157,7 @@ func TestPreparedRecoveryCannotStartWithoutExistingHistory(t *testing.T) { t.Fatal(err) } req.DisableExecutionEnvironment = false - req.LocalEnvironment = &proto.LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}, WorkspaceRoot: cwd} + req.LocalEnvironment = &proto.LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", CapabilitySources: &proto.CapabilitySources{}, WorkspaceRoot: cwd} } e, err := testExecutor(t, "complete", req, cfg) if err != nil { diff --git a/apps/daemon/internal/agent/mcode/environment_mcp_test.go b/apps/daemon/internal/agent/mcode/environment_mcp_test.go index 06f2d58b1..eb26734af 100644 --- a/apps/daemon/internal/agent/mcode/environment_mcp_test.go +++ b/apps/daemon/internal/agent/mcode/environment_mcp_test.go @@ -11,11 +11,10 @@ import ( "time" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func environmentMCPFixture() proto.EnvironmentMCP { - return proto.EnvironmentMCP{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/fixture", Server: agentplugin.MCPServer{ + return proto.EnvironmentMCP{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/fixture", Server: proto.PluginMCPServer{ Name: "proof.server", Type: "stdio", Command: "never-exec-before-sandbox", Args: []string{"private-argument"}, EnvVars: []string{"USER_SELECTED"}, CWD: "resources", }} @@ -77,7 +76,7 @@ func TestEnvironmentMCPRejectsUnqualifiedAuthorityBeforePreparation(t *testing.T req.LocalEnvironment.MCP = []proto.EnvironmentMCP{environmentMCPFixture()} switch name { case "http-bearer-insecure", "http-bearer-missing": - req.LocalEnvironment.MCP[0].Server = agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"} + req.LocalEnvironment.MCP[0].Server = proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"} if name == "http-bearer-missing" { req.LocalEnvironment.MCP[0].Server.BearerTokenEnvVar = "SELECTED_TOKEN" } @@ -177,7 +176,7 @@ func mcpRegistryEntry(server, segment, tool, toolSegment string) map[string]any func TestEnvironmentHTTPMCPUsesEphemeralACPConfiguration(t *testing.T) { for _, authenticated := range []bool{false, true} { c, req, record := workspaceFixture(t) - item := proto.EnvironmentMCP{Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"}} + item := proto.EnvironmentMCP{Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"}} const token = "private-mcp-canary" if authenticated { value := token diff --git a/apps/daemon/internal/agent/mcode/tool_environment_test.go b/apps/daemon/internal/agent/mcode/tool_environment_test.go index 66ffa720e..047445396 100644 --- a/apps/daemon/internal/agent/mcode/tool_environment_test.go +++ b/apps/daemon/internal/agent/mcode/tool_environment_test.go @@ -8,8 +8,9 @@ import ( "slices" "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/google/uuid" ) @@ -40,7 +41,7 @@ func TestWorkspaceCredentialsRemainInRuntimeSnapshotAcrossReconnect(t *testing.T req.AgentStateKey = session req.LocalEnvironment.ID, req.LocalEnvironment.WorkspaceDirectory = environment, config.Directory req.LocalEnvironment.Capabilities = true - req.LocalEnvironment.CapabilitySources = &agentcapabilities.Input{Directories: []string{plugin}} + req.LocalEnvironment.CapabilitySources = &proto.CapabilitySources{Directories: []string{plugin}} for _, resume := range []bool{false, true} { if resume { req.AgentSessionID = "native-1" diff --git a/apps/daemon/internal/agent/mcode/workspace_skills_test.go b/apps/daemon/internal/agent/mcode/workspace_skills_test.go index c063c6553..da9ff12eb 100644 --- a/apps/daemon/internal/agent/mcode/workspace_skills_test.go +++ b/apps/daemon/internal/agent/mcode/workspace_skills_test.go @@ -7,8 +7,7 @@ import ( "slices" "testing" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestWorkspaceSkillsUseSelectedSnapshotAndNativeLoader(t *testing.T) { @@ -23,9 +22,9 @@ func TestWorkspaceSkillsUseSelectedSnapshotAndNativeLoader(t *testing.T) { t.Fatal(err) } req.LocalEnvironment.CapabilityRoot = root - req.LocalEnvironment.Skills = []agentcapabilities.InstalledSkill{{ + req.LocalEnvironment.Skills = []proto.InstalledSkill{{ InstallationRoot: root, RelativeRoot: "plugin/skills/proof", PackageRoot: "plugin", - Metadata: agentskill.Metadata{Name: "proof", Description: "Read a marker"}, + Metadata: proto.SkillMetadata{Name: "proof", Description: "Read a marker"}, }} for range 2 { opts, err := prepareWorkspaceOptions(testStateRoot(t), c, req) diff --git a/apps/daemon/internal/agent/mcp_binding_test.go b/apps/daemon/internal/agent/mcp_binding_test.go index 1b97f2962..f4483b443 100644 --- a/apps/daemon/internal/agent/mcp_binding_test.go +++ b/apps/daemon/internal/agent/mcp_binding_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func TestMCPBindingsPreserveOriginAuthorityAndPolicy(t *testing.T) { @@ -27,8 +26,8 @@ func TestMCPBindingsPreserveOriginAuthorityAndPolicy(t *testing.T) { } } local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{ - {Server: agentplugin.MCPServer{Name: "stdio", Type: "stdio", Command: "node", Args: []string{"tool.js"}}, PackageRoot: "plugins/proof", InstallationRoot: "/private/installed"}, - {Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp", HTTPHeaders: map[string]string{"X-Selected": "literal"}}, BearerToken: &token}, + {Server: proto.PluginMCPServer{Name: "stdio", Type: "stdio", Command: "node", Args: []string{"tool.js"}}, PackageRoot: "plugins/proof", InstallationRoot: "/private/installed"}, + {Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp", HTTPHeaders: map[string]string{"X-Selected": "literal"}}, BearerToken: &token}, }} got, err := ResolveMCPBindings(proto.PromptRequestPayload{LocalEnvironment: local}) if err != nil || len(got) != 2 { @@ -58,11 +57,11 @@ func TestMCPBindingsDistinguishAbsentAndEmptyProfile(t *testing.T) { func TestMCPBindingsRejectRelocationAndAmbiguousInstallation(t *testing.T) { public := []proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "remote", ServerURL: "https://example.test/mcp"}} - local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp"}}}} + local := &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp"}}}} for _, req := range []proto.PromptRequestPayload{ {MCPHTTPServers: &public, LocalEnvironment: local}, {LocalEnvironment: &proto.LocalEnvironment{MCP: append(local.MCP, local.MCP[0])}}, - {LocalEnvironment: &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp", BearerTokenEnvVar: "MISSING"}}}}}, + {LocalEnvironment: &proto.LocalEnvironment{MCP: []proto.EnvironmentMCP{{Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp", BearerTokenEnvVar: "MISSING"}}}}}, } { if _, err := ResolveMCPBindings(req); err == nil { t.Fatal("unsupported authority accepted") @@ -94,7 +93,7 @@ func TestPublicEnvironmentMCPRetainsVaultAuthority(t *testing.T) { } } public[0].ConnectionOrigin = "environment" - req.LocalEnvironment.MCP = []proto.EnvironmentMCP{{Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp"}}} + req.LocalEnvironment.MCP = []proto.EnvironmentMCP{{Server: proto.PluginMCPServer{Name: "remote", Type: "http", URL: "https://example.test/mcp"}}} if _, err := ResolveMCPBindings(req); err == nil { t.Fatal("public/Plugin identity collision accepted") } diff --git a/apps/daemon/internal/cli/connect_suspend_test.go b/apps/daemon/internal/cli/connect_suspend_test.go index 34cf456c8..51e231388 100644 --- a/apps/daemon/internal/cli/connect_suspend_test.go +++ b/apps/daemon/internal/cli/connect_suspend_test.go @@ -17,7 +17,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/transport" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" @@ -383,7 +382,7 @@ func TestQuiesceRetainsExecutorAcrossReconnectAndShutdownConfirmsCleanup(t *test t.Fatal("no connection") } defer peer.Close() - preparation, err := proto.NewEnvelope(proto.TypeExecutionPrepare, "prepare", proto.ExecutionPreparePayload{SessionID: session, Configuration: proto.PromptRequestPayload{AgentKind: "cleanup", AgentStateKey: session, LocalEnvironment: &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}}}}) + preparation, err := proto.NewEnvelope(proto.TypeExecutionPrepare, "prepare", proto.ExecutionPreparePayload{SessionID: session, Configuration: proto.PromptRequestPayload{AgentKind: "cleanup", AgentStateKey: session, LocalEnvironment: &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &proto.CapabilitySources{}}}}) if err != nil { t.Fatal(err) } diff --git a/apps/daemon/internal/cli/runtime_mcp_test.go b/apps/daemon/internal/cli/runtime_mcp_test.go index 9e592d142..20116ebbe 100644 --- a/apps/daemon/internal/cli/runtime_mcp_test.go +++ b/apps/daemon/internal/cli/runtime_mcp_test.go @@ -4,12 +4,13 @@ import ( "slices" "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func TestRuntimeMCPOverlaysExplicitVariablesOnUserEnvironment(t *testing.T) { - manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: agentplugin.MCPServer{ + manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: proto.PluginMCPServer{ Name: "local", Type: "stdio", Command: "python3", Args: []string{"proof.py", "${LITERAL}"}, EnvVars: []string{"SELECTED"}, CWD: "./server", }}}} t.Setenv("SELECTED", "private-native-value") @@ -29,7 +30,7 @@ func TestRuntimeMCPOverlaysExplicitVariablesOnUserEnvironment(t *testing.T) { } func TestRuntimeMCPKeepsAbsoluteCWDAndRejectsHTTP(t *testing.T) { - manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: agentplugin.MCPServer{ + manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: proto.PluginMCPServer{ Name: "local", Type: "stdio", Command: "python3", CWD: "/workspace", }}}} got, err := resolveMCPInvocation(manifest, "/private/runtime/capabilities", "plugins/0", "local", nil) diff --git a/apps/daemon/internal/cli/runtime_mcp_windows_test.go b/apps/daemon/internal/cli/runtime_mcp_windows_test.go index 8b17a1a66..193413aa7 100644 --- a/apps/daemon/internal/cli/runtime_mcp_windows_test.go +++ b/apps/daemon/internal/cli/runtime_mcp_windows_test.go @@ -4,14 +4,15 @@ package cli import ( "encoding/json" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "os" "os/exec" "path/filepath" "slices" "strings" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestWindowsRuntimeMCPPackageManagers(t *testing.T) { @@ -137,7 +138,7 @@ func TestWindowsRuntimeMCPExplicitPath(t *testing.T) { t.Fatal(err) } t.Setenv("Path", os.Getenv("PATH")) - manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: agentplugin.MCPServer{Name: "test", Type: "stdio", Command: "selected-node", Args: []string{fixture}, CWD: directory, EnvVars: []string{"PATH", "OAC_MCP_FIXTURE_VALUE"}}}}} + manifest := agentcapabilities.Manifest{MCP: []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: proto.PluginMCPServer{Name: "test", Type: "stdio", Command: "selected-node", Args: []string{fixture}, CWD: directory, EnvVars: []string{"PATH", "OAC_MCP_FIXTURE_VALUE"}}}}} invocation, err := resolveMCPInvocation(manifest, directory, "plugins/0", "test", map[string]string{"PATH": directory, "OAC_MCP_FIXTURE_VALUE": "selected"}) if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/dispatch/preparation_test.go b/apps/daemon/internal/dispatch/preparation_test.go index a3ebfe2ef..7badf5c21 100644 --- a/apps/daemon/internal/dispatch/preparation_test.go +++ b/apps/daemon/internal/dispatch/preparation_test.go @@ -1,7 +1,5 @@ package dispatch_test -import "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" - import ( "context" "errors" @@ -14,9 +12,9 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/agent" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/dispatch" "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto/prototest" + "github.com/MiniMax-AI/OpenAgentCore/internal/harnessconfig" ) type controlledPreparation struct { @@ -111,7 +109,7 @@ func localPreparationHarness(t *testing.T) *harness { } func preparationRequest() proto.ExecutionPreparePayload { - return proto.ExecutionPreparePayload{SessionID: preparationSessionID, Configuration: proto.PromptRequestPayload{AgentKind: "prepared", AgentStateKey: preparationSessionID, LocalEnvironment: &proto.LocalEnvironment{ID: preparationEnvironmentID, WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}}}} + return proto.ExecutionPreparePayload{SessionID: preparationSessionID, Configuration: proto.PromptRequestPayload{AgentKind: "prepared", AgentStateKey: preparationSessionID, LocalEnvironment: &proto.LocalEnvironment{ID: preparationEnvironmentID, WorkspaceDirectory: "/workspace", CapabilitySources: &proto.CapabilitySources{}}}} } func preparationRouter(t *testing.T, sender dispatch.Sender, timeout time.Duration, factory preparationFactory) *dispatch.Router { diff --git a/apps/daemon/internal/dispatch/runtime_preparation_execution_test.go b/apps/daemon/internal/dispatch/runtime_preparation_execution_test.go index f6bb85f2a..98dabf227 100644 --- a/apps/daemon/internal/dispatch/runtime_preparation_execution_test.go +++ b/apps/daemon/internal/dispatch/runtime_preparation_execution_test.go @@ -39,7 +39,7 @@ func TestRuntimePreparationUnavailablePreventsNativeExecutor(t *testing.T) { } request := preparationRequest() request.Configuration.LocalEnvironment.Capabilities = true - request.Configuration.LocalEnvironment.CapabilitySources = &agentcapabilities.Input{Directories: []string{source}} + request.Configuration.LocalEnvironment.CapabilitySources = &proto.CapabilitySources{Directories: []string{source}} if err := r.Handle(t.Context(), mustEnv(t, proto.TypeExecutionPrepare, "missing-capability", request)); err != nil { t.Fatalf("valid frozen selection rejected before preparation: %v", err) } diff --git a/apps/daemon/internal/dispatch/runtime_preparation_test.go b/apps/daemon/internal/dispatch/runtime_preparation_test.go index ba8631a86..cdd948ae6 100644 --- a/apps/daemon/internal/dispatch/runtime_preparation_test.go +++ b/apps/daemon/internal/dispatch/runtime_preparation_test.go @@ -16,7 +16,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/localworkspace" "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" "github.com/google/uuid" ) @@ -59,7 +58,7 @@ func capabilityBegin(environment, session string, body []byte) proto.RuntimePrep digest := sha256.Sum256(body) return proto.RuntimePreparePayload{ BudgetMS: 300000, Step: "begin", Action: "skill", EnvironmentID: environment, SessionID: session, - Skill: &agentskill.Metadata{Type: "inline", Name: "proof", Description: "A proof."}, + Skill: &proto.SkillMetadata{Type: "inline", Name: "proof", Description: "A proof."}, SizeBytes: len(body), SHA256: hex.EncodeToString(digest[:]), } } @@ -249,7 +248,7 @@ func TestRuntimePreparationCancellationKeepsOwnershipUntilApplyStops(t *testing. sender.frames = make(chan proto.Envelope) ctx, cancel := context.WithCancel(context.Background()) id := uuid.NewString() - request := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", Action: "finalize", EnvironmentID: environment, SessionID: session, Sources: &agentcapabilities.Input{}} + request := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", Action: "finalize", EnvironmentID: environment, SessionID: session, Sources: &proto.CapabilitySources{}} owner := &runtimePreparationTransfer{envelope: capabilityEnvelope(t, id, request), request: request, ready: make(chan struct{}), cancel: cancel, finished: true, apply: true} close(owner.ready) r.runtimePreparation = owner diff --git a/apps/daemon/internal/localworkspace/binding.go b/apps/daemon/internal/localworkspace/binding.go index 1dfdd84e5..5dff64d7c 100644 --- a/apps/daemon/internal/localworkspace/binding.go +++ b/apps/daemon/internal/localworkspace/binding.go @@ -2,11 +2,12 @@ package localworkspace import ( "errors" - "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/paths" "os" "strings" "sync" + "github.com/MiniMax-AI/OpenAgentCore/apps/daemon/internal/paths" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" @@ -67,7 +68,7 @@ func (b *Binding) Configure(r proto.PromptRequestPayload) (proto.PromptRequestPa if local.WorkspaceDirectory != "/workspace" && local.WorkspaceDirectory != b.workspace { return r, errors.New("request does not match the local workspace selection") } - if local.CapabilitySources == nil || agentcapabilities.ValidateInput(*local.CapabilitySources) != nil { + if local.CapabilitySources == nil || proto.ValidateCapabilitySources(*local.CapabilitySources) != nil { return r, agentcapabilities.ErrInvalid } sources := *local.CapabilitySources diff --git a/apps/daemon/internal/localworkspace/binding_test.go b/apps/daemon/internal/localworkspace/binding_test.go index 321f092b3..0bb406ff7 100644 --- a/apps/daemon/internal/localworkspace/binding_test.go +++ b/apps/daemon/internal/localworkspace/binding_test.go @@ -24,7 +24,7 @@ func testBinding(t *testing.T) (*Binding, proto.PromptRequestPayload) { if err != nil { t.Fatal(err) } - return b, proto.PromptRequestPayload{LocalEnvironment: &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &agentcapabilities.Input{}}, AgentStateKey: session} + return b, proto.PromptRequestPayload{LocalEnvironment: &proto.LocalEnvironment{ID: environment, WorkspaceDirectory: "/workspace", CapabilitySources: &proto.CapabilitySources{}}, AgentStateKey: session} } func TestBindingRejectsScopeOverrides(t *testing.T) { diff --git a/apps/daemon/internal/localworkspace/capabilities.go b/apps/daemon/internal/localworkspace/capabilities.go index b94729542..739153e02 100644 --- a/apps/daemon/internal/localworkspace/capabilities.go +++ b/apps/daemon/internal/localworkspace/capabilities.go @@ -140,7 +140,7 @@ func (b *Binding) ApplyRuntimePreparation(ctx context.Context, input proto.Runti return ctx.Err() } -func (b *Binding) loadCapabilitySnapshot(root *os.Root, input agentcapabilities.Input, identity agentcapabilities.Identity, completed bool) (agentcapabilities.Manifest, error) { +func (b *Binding) loadCapabilitySnapshot(root *os.Root, input proto.CapabilitySources, identity agentcapabilities.Identity, completed bool) (agentcapabilities.Manifest, error) { if _, err := root.Lstat(agentcapabilities.ManifestName); errors.Is(err, os.ErrNotExist) { if completed || agentcapabilities.Finalize(root, input, identity, b.resolveCapabilityDirectory) != nil { return agentcapabilities.Manifest{}, agentcapabilities.ErrInvalid diff --git a/apps/daemon/internal/localworkspace/capabilities_test.go b/apps/daemon/internal/localworkspace/capabilities_test.go index 224213fca..66d74128c 100644 --- a/apps/daemon/internal/localworkspace/capabilities_test.go +++ b/apps/daemon/internal/localworkspace/capabilities_test.go @@ -5,14 +5,13 @@ import ( "encoding/json" "testing" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestCapabilityPathsStayRuntimeOwnedAndDoNotGateReads(t *testing.T) { binding, request := testBinding(t) request.LocalEnvironment.CapabilityRoot = "/caller/installation" - request.LocalEnvironment.Skills = []agentcapabilities.InstalledSkill{{RelativeRoot: "caller/private", PackageRoot: "caller", InstallationRoot: "/caller/installation"}} + request.LocalEnvironment.Skills = []proto.InstalledSkill{{RelativeRoot: "caller/private", PackageRoot: "caller", InstallationRoot: "/caller/installation"}} secret := "private-mcp-marker" request.LocalEnvironment.MCP = []proto.EnvironmentMCP{{PackageRoot: "caller/private", BearerToken: &secret}} raw, err := json.Marshal(request.LocalEnvironment) diff --git a/apps/daemon/internal/localworkspace/capability_preparation_test.go b/apps/daemon/internal/localworkspace/capability_preparation_test.go index 8bb807e19..06cd5f368 100644 --- a/apps/daemon/internal/localworkspace/capability_preparation_test.go +++ b/apps/daemon/internal/localworkspace/capability_preparation_test.go @@ -27,7 +27,7 @@ func TestPreparationFreezesLocalContentsAcrossReconnect(t *testing.T) { source := t.TempDir() writeSourceSkill(t, source, "first") req.LocalEnvironment.Capabilities = true - req.LocalEnvironment.CapabilitySources = &agentcapabilities.Input{Directories: []string{source}} + req.LocalEnvironment.CapabilitySources = &proto.CapabilitySources{Directories: []string{source}} configured, err := b.Configure(req) if err != nil { t.Fatal(err) @@ -69,7 +69,7 @@ func TestPreparationFreezesLocalContentsAcrossReconnect(t *testing.T) { // Reusing a snapshot under another selection cannot start native work, even // for another Session. local := *configured.LocalEnvironment - local.CapabilitySources = &agentcapabilities.Input{} + local.CapabilitySources = &proto.CapabilitySources{} changed := configured changed.LocalEnvironment = &local if _, err = b.Prepare(t.Context(), changed); err == nil { @@ -104,7 +104,7 @@ func TestPreparationUsesOperatorSourcesAndLeavesFailuresInert(t *testing.T) { } root.Close() req.LocalEnvironment.Capabilities = true - req.LocalEnvironment.CapabilitySources = &agentcapabilities.Input{Directories: []string{source, t.TempDir()}} + req.LocalEnvironment.CapabilitySources = &proto.CapabilitySources{Directories: []string{source, t.TempDir()}} configured, err := b.Configure(req) if err != nil { t.Fatal(err) diff --git a/apps/daemon/internal/localworkspace/capability_rebind_test.go b/apps/daemon/internal/localworkspace/capability_rebind_test.go index e1f4cbd3a..a0a10ebfd 100644 --- a/apps/daemon/internal/localworkspace/capability_rebind_test.go +++ b/apps/daemon/internal/localworkspace/capability_rebind_test.go @@ -3,6 +3,8 @@ package localworkspace import ( "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/google/uuid" ) @@ -11,7 +13,7 @@ func TestFinalizeRebindsIdenticalRestoredInstallation(t *testing.T) { first, _ := testBinding(t) source := t.TempDir() writeSourceSkill(t, source, "restored") - input := agentcapabilities.Input{Directories: []string{source}} + input := proto.CapabilitySources{Directories: []string{source}} root, unlock, err := first.openCapabilities(t.Context(), false) if err != nil { t.Fatal(err) @@ -25,7 +27,7 @@ func TestFinalizeRebindsIdenticalRestoredInstallation(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err = next.loadCapabilitySnapshot(root, agentcapabilities.Input{}, next.capabilityIdentity(), false); err == nil { + if _, err = next.loadCapabilitySnapshot(root, proto.CapabilitySources{}, next.capabilityIdentity(), false); err == nil { t.Fatal("different selection accepted") } if _, err = next.loadCapabilitySnapshot(root, input, next.capabilityIdentity(), false); err != nil { diff --git a/apps/daemon/internal/localworkspace/mcp_test.go b/apps/daemon/internal/localworkspace/mcp_test.go index 8bafbc604..130d58810 100644 --- a/apps/daemon/internal/localworkspace/mcp_test.go +++ b/apps/daemon/internal/localworkspace/mcp_test.go @@ -7,12 +7,11 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" ) func TestEnvironmentMCPCredentialsNeverFallBackToNativeEnv(t *testing.T) { t.Setenv("PLUGIN_TOKEN", "native-private-value") - installed := []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: agentplugin.MCPServer{ + installed := []agentcapabilities.InstalledMCP{{PackageRoot: "plugins/0", Server: proto.PluginMCPServer{ Name: "remote", Type: "http", URL: "https://example.com/mcp", BearerTokenEnvVar: "PLUGIN_TOKEN", }}} if _, err := resolveEnvironmentMCP(installed, nil); err == nil { @@ -37,7 +36,7 @@ func TestDeclaredMCPVariablesNeverFallBackToNativeEnv(t *testing.T) { } func TestMCPStdioLauncherContainsOnlyInstalledIdentity(t *testing.T) { - server := proto.EnvironmentMCP{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/0", Server: agentplugin.MCPServer{ + server := proto.EnvironmentMCP{InstallationRoot: "/private/runtime/capabilities", PackageRoot: "plugins/0", Server: proto.PluginMCPServer{ Name: "package_tool", Type: "stdio", Command: "untrusted-command", Args: []string{"private-argument"}, }} command, args := MCPStdioCommand(server) diff --git a/apps/daemon/internal/localworkspace/skills.go b/apps/daemon/internal/localworkspace/skills.go index f6bbf4d98..47a8682c7 100644 --- a/apps/daemon/internal/localworkspace/skills.go +++ b/apps/daemon/internal/localworkspace/skills.go @@ -3,11 +3,13 @@ package localworkspace import ( "path/filepath" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" ) const CapabilityDirectory = agentcapabilities.Directory -func SkillPath(skill agentcapabilities.InstalledSkill) string { +func SkillPath(skill proto.InstalledSkill) string { return filepath.Join(skill.InstallationRoot, skill.RelativeRoot) } diff --git a/apps/daemon/internal/localworkspace/skills_test.go b/apps/daemon/internal/localworkspace/skills_test.go index be37c0358..388524b0b 100644 --- a/apps/daemon/internal/localworkspace/skills_test.go +++ b/apps/daemon/internal/localworkspace/skills_test.go @@ -1,14 +1,15 @@ package localworkspace import ( - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "path/filepath" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestSkillPathUsesRuntimeInstallationRoot(t *testing.T) { root := t.TempDir() - skill := agentcapabilities.InstalledSkill{InstallationRoot: root, RelativeRoot: "plugins/0/skills/proof"} + skill := proto.InstalledSkill{InstallationRoot: root, RelativeRoot: "plugins/0/skills/proof"} if got := SkillPath(skill); got != filepath.Join(root, "plugins/0/skills/proof") { t.Fatal("Runtime root lost", got) } diff --git a/apps/daemon/internal/localworkspace/snapshot_marker_test.go b/apps/daemon/internal/localworkspace/snapshot_marker_test.go index 90132e1e5..5126ade53 100644 --- a/apps/daemon/internal/localworkspace/snapshot_marker_test.go +++ b/apps/daemon/internal/localworkspace/snapshot_marker_test.go @@ -10,7 +10,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" ) func markerBinding(t *testing.T) (*Binding, proto.PromptRequestPayload) { @@ -78,7 +77,7 @@ func TestCompletedSnapshotLossNeverRecapturesSources(t *testing.T) { if populated { writeSourceSkill(t, source, "first") request.LocalEnvironment.Capabilities = true - request.LocalEnvironment.CapabilitySources = &agentcapabilities.Input{Directories: []string{source}} + request.LocalEnvironment.CapabilitySources = &proto.CapabilitySources{Directories: []string{source}} } if _, err := b.Prepare(t.Context(), request); err != nil { t.Fatal(err) @@ -121,7 +120,7 @@ func TestSnapshotMarkerBackfillsOnlyVerifiedManifest(t *testing.T) { b, request := markerBinding(t) source := t.TempDir() writeSourceSkill(t, source, "frozen") - input := agentcapabilities.Input{Directories: []string{source}} + input := proto.CapabilitySources{Directories: []string{source}} root, err := os.OpenRoot(b.capabilityRoot) if err != nil { t.Fatal(err) @@ -186,7 +185,7 @@ func TestSnapshotMarkerRefusesDamagedState(t *testing.T) { func TestCapabilityFinalizeRecordsCompletionAndRejectsLaterImports(t *testing.T) { b, _ := markerBinding(t) identity := b.capabilityIdentity() - finalize := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: identity.EnvironmentID, SessionID: identity.SessionID, Action: "finalize", Sources: &agentcapabilities.Input{}} + finalize := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: identity.EnvironmentID, SessionID: identity.SessionID, Action: "finalize", Sources: &proto.CapabilitySources{}} if err := b.ApplyRuntimePreparation(t.Context(), finalize, nil); err != nil { t.Fatal(err) } @@ -197,7 +196,7 @@ func TestCapabilityFinalizeRecordsCompletionAndRejectsLaterImports(t *testing.T) if err := os.RemoveAll(b.capabilityRoot); err != nil { t.Fatal(err) } - skill := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: identity.EnvironmentID, SessionID: identity.SessionID, Action: "skill", Skill: &agentskill.Metadata{Type: "inline", Name: "example", Description: "Example"}, SizeBytes: 1, SHA256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} + skill := proto.RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: identity.EnvironmentID, SessionID: identity.SessionID, Action: "skill", Skill: &proto.SkillMetadata{Type: "inline", Name: "example", Description: "Example"}, SizeBytes: 1, SHA256: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} if err := b.ApplyRuntimePreparation(t.Context(), skill, []byte("x")); err == nil { t.Fatal("completed identity accepted import") } diff --git a/docs/runtime-protocol.md b/docs/runtime-protocol.md index e1ef8b873..1cfa33362 100644 --- a/docs/runtime-protocol.md +++ b/docs/runtime-protocol.md @@ -113,9 +113,12 @@ The linked source files define the required fields, validators, limits and finit | `function_result` | `function_call`, `function_call_withdrawn`, `interaction_decision_ack` | [Function calls](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/functions.go) | | `workspace_read`, `workspace_write`, `workspace_export` | Matching `*_result` | [Read](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_read.go), [write](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_write.go), [export](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_export.go) | | `environment_quiesce`, `environment_resume` | `environment_quiesced`, `environment_resumed` | [Suspension fencing](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/suspend.go) | +| none | `subagent_identity`, `subagent_lifecycle`, `subagent_turn`, `subagent_item`, `subagent_coordination` | [Subagent observations](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/subagents.go) | Initial, prepared and active input use the same [ordered MessageInput](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/message_input.go). Adapters keep message and content order and reject unsupported content explicitly; a text-only transport rejects image content rather than dropping it. The [message input contract](../contracts/agents-api/message-content.md) owns the public image profile, whitespace rules and each Harness's native conversion. +A Subagent is reported by the Run's adapter in this order: `subagent_identity` with the verified native identity, then `subagent_turn` for each child-owned Turn (active first, then its terminal snapshot), with `subagent_item` snapshots between them. `subagent_lifecycle` reports a successful native effect, such as a reopen, never task completion or a disconnect. `subagent_coordination` reports a native coordination operation in the common vocabulary, either directly for the root or as the payload of a `subagent_item`. Every payload is scoped by the authenticated Run envelope and carries native identifiers only; Core owns all public resource IDs. Only adapters that declare `subagent_observations` send these messages. + A `function_call` carries `native_id` and `turn_id` when a Subagent made the call, with the meaning they have in `subagent_turn`; both are empty for a call of the Run itself. The adapter sends the child's `subagent_identity` and `subagent_turn` before the call, and Core answers with `function_result` under the same `call_id`. A `function_call_withdrawn` carries the `call_id`, `native_id` and `turn_id` of a `function_call` and tells that the native Harness will never apply a result for it, for example because a Codex Subagent Turn ended and native dropped its request. Core stops requiring an answer for the call and rejects a later result for it. The adapter acknowledges a `function_result` already in flight with `not_pending`, and Core then waits for the withdrawal or the Turn's end instead of failing the Turn or resending the result. @@ -300,7 +303,7 @@ Core stores accepted values in the Turn outcome as `engine_error_code` and `engi A workspace read that needs no running Turn uses the read-only preparation profile: `execution_prepare` with `workspace_read_only`, which requires the `workspace_read_preparation` capability. It accepts only the bound Environment and resource identity; execution options, model and MCP credentials, native Session continuation and model or tool input are excluded, and the owner rejects `execution_start`. The Runtime serves it from its bound local workspace without starting a Harness process. The profile publishes `released` after the Runtime drops the preparation's ownership; a stale status snapshot never publishes success. A release request, HTTP disconnect or remote socket closure alone does not confirm the release. -`workspace_read` targets an existing preparation handle, or the Run it was transferred to, on the same authenticated device connection, with the exact frozen Environment identity; callers cannot supply sockets, credentials or workspace roots. `operation: directory` lists one workspace-relative directory (an empty path selects the root) with mutually exclusive byte and entry limits. A result carries a bounded number of single-component UTF-8 names, the entry kind, regular-file sizes and explicit truncation, and is returned only after directory access and handle cleanup settle. There is no snapshot, recursion or pagination at this layer. Byte and directory reads share target checks, correlation, capacity and retained operation waits. +`workspace_read` targets an existing preparation handle, or the Run it was transferred to, on the same authenticated device connection, with the exact frozen Environment identity; callers cannot supply sockets, credentials or workspace roots. `operation: directory` lists one workspace-relative directory (an empty path selects the root) with mutually exclusive byte and entry limits. A result carries a bounded number of single-component UTF-8 names, the entry kind, regular-file sizes and explicit truncation, and is returned only after directory access and handle cleanup settle. There is no snapshot, recursion or pagination at this layer. Byte and directory reads share target checks, correlation, capacity and retained operation waits. A rejected result carries one of `invalid_request`, `resource_unavailable`, `read_capacity`, `not_found` and `permission_denied`. Request payloads and correlation IDs are bounded before admission; oversized IDs are not echoed, and oversized trace metadata is omitted from replies. Raw read results are bounded within the transport frame. These are private transport limits, not public Files parameters. A successful read requires complete bytes or explicit truncation and an acknowledged native close. A safe native rejection carries no bytes; an interrupted or ambiguous read stays unknown and stops further reads on that owner. A dispatched read keeps its bounded waiter across observer cancellation and resource transfer or release, and resource closure stops new admission. The gateway bounds subscriptions and never retries or replays a read on reconnect; a duplicate pending operation ID cannot start another read. diff --git a/docs/zh/runtime-protocol.md b/docs/zh/runtime-protocol.md index 793533784..d726ddb37 100644 --- a/docs/zh/runtime-protocol.md +++ b/docs/zh/runtime-protocol.md @@ -1,7 +1,7 @@ --- title: "Core–Runtime 协议" source: docs/runtime-protocol.md -source_hash: e41baeed3c6b990b6da8823fc792d54393b2e247f0d96811f98b4bfccf1da5aa +source_hash: 56e2831e66c8638c833d1d296cce2d6597c6a808f34a60a29bde0a46d67b8c59 --- Core–Runtime 协议连接 Core 与 Runtime daemon,定义 daemon 连接上消息的含义和顺序。托管和自托管 Runtime 使用同一协议。 @@ -115,9 +115,12 @@ Core 对 `usage` 和 `resume` 没有准入规则。 | `function_result` | `function_call`, `function_call_withdrawn`, `interaction_decision_ack` | [Function 调用](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/functions.go) | | `workspace_read`, `workspace_write`, `workspace_export` | 对应的 `*_result` | [读取](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_read.go)、[写入](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_write.go)、[导出](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/workspace_export.go) | | `environment_quiesce`, `environment_resume` | `environment_quiesced`, `environment_resumed` | [暂停 fencing](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/suspend.go) | +| 无 | `subagent_identity`, `subagent_lifecycle`, `subagent_turn`, `subagent_item`, `subagent_coordination` | [Subagent 观察](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/subagents.go) | 初始、已准备和活动输入使用同一[有序 MessageInput](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/agentdaemon/proto/message_input.go)。Adapter 保留消息与内容顺序,并明确拒绝不支持的内容;仅文本 transport 拒绝图像内容而不丢弃它。[消息输入契约](../../contracts/agents-api/zh/message-content.md)负责公开图像 profile、空白规则和各 Harness 的原生转换。 +Run 的 adapter 按以下顺序报告 Subagent:先发送带已验证原生身份的 `subagent_identity`,再为每个 child 拥有的 Turn 发送 `subagent_turn`(先 active,再终态快照),其间发送 `subagent_item` 快照。`subagent_lifecycle` 报告成功的原生效果,例如重新打开,绝不表示任务完成或断连。`subagent_coordination` 以通用词汇报告原生协调操作,对 root 直接发送,或作为 `subagent_item` 的 payload 发送。每个 payload 的范围由已认证的 Run envelope 决定,只携带原生标识;所有公开资源 ID 由 Core 拥有。只有声明 `subagent_observations` 的 adapter 才发送这些消息。 + Subagent 发起调用时,`function_call` 携带 `native_id` 和 `turn_id`,含义与 `subagent_turn` 中相同;Run 自身发起的调用两者均为空。Adapter 在调用之前发送该子级的 `subagent_identity` 和 `subagent_turn`,Core 用同一个 `call_id` 的 `function_result` 作答。 `function_call_withdrawn` 携带某个 `function_call` 的 `call_id`、`native_id` 和 `turn_id`,表示原生 Harness 永远不会为它应用结果,例如 Codex Subagent 的 Turn 已结束且原生丢弃了其请求。Core 不再要求回答该调用,并拒绝之后为它提交的结果。adapter 以 `not_pending` 确认已在传输中的 `function_result`,Core 随后等待撤回或 Turn 结束,既不让 Turn 失败,也不重发该结果。 @@ -302,7 +305,7 @@ Core 在 Turn outcome 中将接受的值保存为 `engine_error_code` 和 `engin 无需运行 Turn 的工作区读取使用只读 preparation profile:带 `workspace_read_only` 的 `execution_prepare`,要求 `workspace_read_preparation` 能力。仅接受绑定的 Environment 和 resource 身份;不包含 execution option、model 与 MCP 凭据、原生 Session continuation、model 或 tool 输入,owner 拒绝 `execution_start`。Runtime 从绑定的本地工作区提供读取,不启动 Harness 进程。profile 在 Runtime 放弃该 preparation 的所有权后发布 `released`;旧 status snapshot 不发布成功。release 请求、HTTP 断连或远端 socket 关闭本身都不确认释放。 -`workspace_read` 在同一已认证设备连接上,针对现有 preparation handle 或它已转移给的 Run,使用精确冻结的 Environment 身份;调用方不能提供 socket、凭据或 workspace root。`operation: directory` 列出一个 workspace 相对目录(空路径选择根目录),字节与条目限制互斥。结果携带有限数量的单路径组件 UTF-8 名称,并包含 entry kind、普通文件大小和明确截断信息;仅在目录访问与 handle 清理结算后返回。此层没有快照、递归或分页。字节读取与目录读取共享目标检查、关联、容量和保留的操作等待。 +`workspace_read` 在同一已认证设备连接上,针对现有 preparation handle 或它已转移给的 Run,使用精确冻结的 Environment 身份;调用方不能提供 socket、凭据或 workspace root。`operation: directory` 列出一个 workspace 相对目录(空路径选择根目录),字节与条目限制互斥。结果携带有限数量的单路径组件 UTF-8 名称,并包含 entry kind、普通文件大小和明确截断信息;仅在目录访问与 handle 清理结算后返回。此层没有快照、递归或分页。字节读取与目录读取共享目标检查、关联、容量和保留的操作等待。被拒绝的结果携带 `invalid_request`、`resource_unavailable`、`read_capacity`、`not_found` 和 `permission_denied` 之一。 准入前,请求 payload 与 correlation ID 受限;过长 ID 不回显,过大的 trace metadata 不进入回复。原始 read result 受限于 transport frame 之内。这些是私有 transport 限制,不是公开 Files 参数。成功读取要求完整字节或明确截断,并确认原生 close。安全的原生拒绝不携带字节;中断或有歧义的读取保持未知,并停止该 owner 的后续读取。已 dispatch 的读取在 observer 取消以及资源转移或释放后仍保留有限时等待方,资源关闭阻止新准入。gateway 限制订阅,不在重连后重试或重放读取;重复的 pending operation ID 不能启动另一次读取。 diff --git a/internal/agentcapabilities/install.go b/internal/agentcapabilities/install.go index ac9585e44..f422022cb 100644 --- a/internal/agentcapabilities/install.go +++ b/internal/agentcapabilities/install.go @@ -7,12 +7,14 @@ import ( "strconv" "strings" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentbundle" "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" ) -func InstallSkill(root *os.Root, archive []byte, metadata agentskill.Metadata) error { +func InstallSkill(root *os.Root, archive []byte, metadata proto.SkillMetadata) error { if unfinished(root) != nil { return ErrInvalid } @@ -23,7 +25,7 @@ func InstallSkill(root *os.Root, archive []byte, metadata agentskill.Metadata) e return writeTree(root, "skills/"+metadata.Name, files) } -func InstallPlugin(root *os.Root, slot int, archive []byte, metadata agentplugin.Metadata) error { +func InstallPlugin(root *os.Root, slot int, archive []byte, metadata proto.PluginMetadata) error { if slot < 0 || slot >= 50 || unfinished(root) != nil { return ErrInvalid } @@ -36,7 +38,7 @@ func InstallPlugin(root *os.Root, slot int, archive []byte, metadata agentplugin // Finalize captures declared directories once after setup and binds the snapshot. // It emits an installation artifact, not another lifecycle or public catalog. -func Finalize(installed *os.Root, input Input, identity Identity, resolve DirectoryResolver) error { +func Finalize(installed *os.Root, input proto.CapabilitySources, identity Identity, resolve DirectoryResolver) error { digest, err := selectionHash(input) if err != nil || validateIdentity(identity) != nil || (len(input.Directories) > 0 && resolve == nil) { return ErrInvalid @@ -47,7 +49,7 @@ func Finalize(installed *os.Root, input Input, identity Identity, resolve Direct if validateStaged(installed, input) != nil { return ErrInvalid } - manifest := Manifest{Version: 1, Identity: identity, SelectionSHA256: digest, Skills: []InstalledSkill{}} + manifest := Manifest{Version: 1, Identity: identity, SelectionSHA256: digest, Skills: []proto.InstalledSkill{}} total := 0 count := func(files []agentbundle.File) error { for _, file := range files { @@ -109,7 +111,7 @@ func Finalize(installed *os.Root, input Input, identity Identity, resolve Direct // Rebind gives an installation restored from an earlier preparation the identity // of this Session. The earlier preparation must have had the identical selection. -func Rebind(installed *os.Root, manifest *Manifest, input Input, identity Identity) error { +func Rebind(installed *os.Root, manifest *Manifest, input proto.CapabilitySources, identity Identity) error { digest, err := selectionHash(input) if err != nil || validateIdentity(identity) != nil || manifest.Version != 1 || manifest.SelectionSHA256 != digest { return ErrInvalid @@ -226,7 +228,7 @@ func Load(root *os.Root) (Manifest, error) { // Only selected, fully imported bundles may precede finalization. A leftover // directory capture or temporary manifest is an incomplete installation, not a // reason to silently recapture mutable sources. -func validateStaged(root *os.Root, input Input) error { +func validateStaged(root *os.Root, input proto.CapabilitySources) error { expected := map[string]map[string]bool{"skills": {}, "plugins": {}} for _, skill := range input.Skills { expected["skills"][skill.Name] = true diff --git a/internal/agentcapabilities/install_test.go b/internal/agentcapabilities/install_test.go index 2db6f8e7f..9d6f43ae1 100644 --- a/internal/agentcapabilities/install_test.go +++ b/internal/agentcapabilities/install_test.go @@ -8,25 +8,25 @@ import ( "reflect" "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentbundle" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" ) func TestInstalledCapabilitiesPreserveSourcesWithoutRescanning(t *testing.T) { workspace := openTestRoot(t) installed := openTestRoot(t) - inline := agentskill.Metadata{Type: "inline", Name: "inline", Description: "Check proof."} + inline := proto.SkillMetadata{Type: "inline", Name: "inline", Description: "Check proof."} if err := writeTree(installed, "skills/inline", []agentbundle.File{{Path: "SKILL.md", Data: skillManifest("inline")}}); err != nil { t.Fatal(err) } - plugin := agentplugin.Metadata{Type: "inline", Name: "package", Description: "Package proof."} + plugin := proto.PluginMetadata{Type: "inline", Name: "package", Description: "Package proof."} if err := InstallPlugin(installed, 0, testPlugin(t, "bundled"), plugin); err != nil { t.Fatal(err) } writeWorkspace(t, workspace, "generated/nested/SKILL.md", skillManifest("generated")) writeWorkspace(t, workspace, "generated/shared/proof.txt", []byte("original-resource")) - input := Input{Skills: []agentskill.Metadata{inline}, Plugins: []agentplugin.Metadata{plugin}, Directories: []string{"/workspace/generated"}} + input := proto.CapabilitySources{Skills: []proto.SkillMetadata{inline}, Plugins: []proto.PluginMetadata{plugin}, Directories: []string{"/workspace/generated"}} if err := Finalize(installed, input, testIdentity, workspaceResolver(workspace)); err != nil { t.Fatal(err) } @@ -61,7 +61,7 @@ func TestDirectoryDiscoveryDoesNotActivateChildPluginMCP(t *testing.T) { writeWorkspace(t, workspace, "parent/child/.codex-plugin/plugin.json", []byte(`{"name":"remote","description":"Remote","skills":"./skills","mcpServers":"./.mcp.json"}`)) writeWorkspace(t, workspace, "parent/child/.mcp.json", []byte(`{"mcpServers":{"remote":{"type":"http","url":"https://example.com"}}}`)) writeWorkspace(t, workspace, "parent/child/skills/proof/SKILL.md", skillManifest("proof")) - if err := Finalize(installed, Input{Directories: []string{"/workspace/parent"}}, testIdentity, workspaceResolver(workspace)); err != nil { + if err := Finalize(installed, proto.CapabilitySources{Directories: []string{"/workspace/parent"}}, testIdentity, workspaceResolver(workspace)); err != nil { t.Fatal(err) } manifest, err := Load(installed) @@ -72,7 +72,7 @@ func TestDirectoryDiscoveryDoesNotActivateChildPluginMCP(t *testing.T) { t.Fatal("parent directory activated child Plugin MCP") } exact := openTestRoot(t) - if err := Finalize(exact, Input{Directories: []string{"/workspace/parent/child"}}, testIdentity, workspaceResolver(workspace)); err != nil { + if err := Finalize(exact, proto.CapabilitySources{Directories: []string{"/workspace/parent/child"}}, testIdentity, workspaceResolver(workspace)); err != nil { t.Fatal(err) } manifest, err = Load(exact) @@ -86,7 +86,7 @@ func TestInvalidDirectorySnapshotsNeverPublish(t *testing.T) { t.Run(scenario, func(t *testing.T) { workspace, installed := openTestRoot(t), openTestRoot(t) writeWorkspace(t, workspace, "first/SKILL.md", skillManifest("proof")) - input := Input{Directories: []string{"/workspace/first"}} + input := proto.CapabilitySources{Directories: []string{"/workspace/first"}} switch scenario { case "missing": input.Directories = []string{"/workspace/missing"} diff --git a/internal/agentcapabilities/local_directories_test.go b/internal/agentcapabilities/local_directories_test.go new file mode 100644 index 000000000..fca696622 --- /dev/null +++ b/internal/agentcapabilities/local_directories_test.go @@ -0,0 +1,9 @@ +package agentcapabilities + +import "testing" + +func TestPortableSourcesAreNotLocalAuthorization(t *testing.T) { + if ValidateDirectories([]string{`C:\skills`}) == nil || ValidateLocalDirectories([]string{`C:\skills`, `\\host\share\skills`}) == nil { + t.Fatal("portable source weakened local/managed authorization") + } +} diff --git a/internal/agentcapabilities/manifest.go b/internal/agentcapabilities/manifest.go index 555f93afb..186144b5a 100644 --- a/internal/agentcapabilities/manifest.go +++ b/internal/agentcapabilities/manifest.go @@ -6,16 +6,15 @@ import ( "encoding/hex" "encoding/json" "errors" - "github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs" "io/fs" "os" "strings" "unicode/utf8" - "github.com/google/uuid" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/runtimefs" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" + "github.com/google/uuid" ) const Directory = "/environment/initialization/capabilities" @@ -25,15 +24,6 @@ const MaxSnapshotBytes = 50 << 20 var ErrInvalid = errors.New("capability installation unavailable or unsupported") -// InstalledSkill contains Runtime-owned paths, never public resource metadata. -type InstalledSkill struct { - // InstallationRoot is resolved by Runtime and never persisted or accepted over wire. - InstallationRoot string `json:"-"` - Metadata agentskill.Metadata `json:"metadata"` - RelativeRoot string `json:"relative_root"` - PackageRoot string `json:"package_root"` -} - // Identity binds an installation to one immutable Environment and Session. type Identity struct { EnvironmentID string `json:"environment_id"` @@ -45,26 +35,19 @@ type Identity struct { type DirectoryResolver func(string) (*os.Root, error) type Manifest struct { - Identity Identity `json:"identity"` - SelectionSHA256 string `json:"selection_sha256"` - Version int `json:"version"` - Skills []InstalledSkill `json:"skills"` - Plugins []string `json:"plugins,omitempty"` - MCP []InstalledMCP `json:"-"` + Identity Identity `json:"identity"` + SelectionSHA256 string `json:"selection_sha256"` + Version int `json:"version"` + Skills []proto.InstalledSkill `json:"skills"` + Plugins []string `json:"plugins,omitempty"` + MCP []InstalledMCP `json:"-"` } // InstalledMCP is resolved from a frozen package at load time. The manifest // stores the package root, not a second copy of configuration or credentials. type InstalledMCP struct { PackageRoot string - Server agentplugin.MCPServer -} - -// Input describes frozen sources; directory contents are observed after setup. -type Input struct { - Skills []agentskill.Metadata `json:"skills,omitempty"` - Plugins []agentplugin.Metadata `json:"plugins,omitempty"` - Directories []string `json:"directories,omitempty"` + Server proto.PluginMCPServer } // ValidateDirectories preserves the managed public workspace-path contract. @@ -101,7 +84,7 @@ func validRelative(value string) bool { return value != "." && fs.ValidPath(value) && !strings.ContainsAny(value, "\\\x00\r\n") } -func (m *Manifest) add(metadata agentskill.Metadata, root, pkg string) error { +func (m *Manifest) add(metadata proto.SkillMetadata, root, pkg string) error { if !validRelative(root) || !validRelative(pkg) || (root != pkg && !strings.HasPrefix(root, pkg+"/")) || len(m.Skills) >= MaxSkills { return ErrInvalid } @@ -110,7 +93,7 @@ func (m *Manifest) add(metadata agentskill.Metadata, root, pkg string) error { return ErrInvalid } } - m.Skills = append(m.Skills, InstalledSkill{Metadata: metadata, RelativeRoot: root, PackageRoot: pkg}) + m.Skills = append(m.Skills, proto.InstalledSkill{Metadata: metadata, RelativeRoot: root, PackageRoot: pkg}) return nil } @@ -161,30 +144,8 @@ func validSelectionHash(value string) bool { return err == nil && len(digest) == sha256.Size && hex.EncodeToString(digest) == value } -// ValidateInput bounds source selections without reading archives or directories. -// Portable manifest grammar is checked by the shared Skill and Plugin parsers. -func ValidateInput(input Input) error { - if len(input.Skills) > MaxSkills || len(input.Plugins) > 50 || ValidateSourceDirectories(input.Directories) != nil { - return ErrInvalid - } - validMetadata := func(kind, name, description string) bool { - return kind == "inline" && name != "" && description != "" && utf8.ValidString(name) && utf8.ValidString(description) - } - for _, skill := range input.Skills { - if !validMetadata(skill.Type, skill.Name, skill.Description) { - return ErrInvalid - } - } - for _, plugin := range input.Plugins { - if !validMetadata(plugin.Type, plugin.Name, plugin.Description) { - return ErrInvalid - } - } - return nil -} - -func selectionHash(input Input) (string, error) { - if ValidateInput(input) != nil { +func selectionHash(input proto.CapabilitySources) (string, error) { + if proto.ValidateCapabilitySources(input) != nil { return "", ErrInvalid } // omitempty gives nil and empty selections the same canonical representation; @@ -199,7 +160,7 @@ func selectionHash(input Input) (string, error) { // ValidateSelection checks reconnect configuration without revisiting sources. // Use it only with a manifest returned by Load. -func ValidateSelection(manifest Manifest, input Input, identity Identity) error { +func ValidateSelection(manifest Manifest, input proto.CapabilitySources, identity Identity) error { digest, err := selectionHash(input) if err != nil || validateIdentity(identity) != nil || manifest.Version != 1 || manifest.Identity != identity || manifest.SelectionSHA256 != digest { return ErrInvalid diff --git a/internal/agentcapabilities/mcp_test.go b/internal/agentcapabilities/mcp_test.go index 0b110117c..5ce5711fb 100644 --- a/internal/agentcapabilities/mcp_test.go +++ b/internal/agentcapabilities/mcp_test.go @@ -5,6 +5,8 @@ import ( "reflect" "strings" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestMCPOnlyInstalledPackageSurvivesSourceDeletion(t *testing.T) { @@ -12,7 +14,7 @@ func TestMCPOnlyInstalledPackageSurvivesSourceDeletion(t *testing.T) { writeWorkspace(t, workspace, "plugin/.codex-plugin/plugin.json", []byte(`{"name":"native-tools","description":"Package tools.","mcpServers":"./.mcp.json"}`)) writeWorkspace(t, workspace, "plugin/.mcp.json", []byte(`{"mcpServers":{"proof":{"command":"python3","args":["proof.py"],"env_vars":["PLUGIN_TOKEN"]}}}`)) writeWorkspace(t, workspace, "plugin/proof.py", []byte("# Preserved server resource.\n")) - if err := Finalize(installed, Input{Directories: []string{"/workspace/plugin"}}, testIdentity, workspaceResolver(workspace)); err != nil { + if err := Finalize(installed, proto.CapabilitySources{Directories: []string{"/workspace/plugin"}}, testIdentity, workspaceResolver(workspace)); err != nil { t.Fatal(err) } before, err := Load(installed) diff --git a/internal/agentcapabilities/selection_test.go b/internal/agentcapabilities/selection_test.go index 633c5d250..fc669fb5c 100644 --- a/internal/agentcapabilities/selection_test.go +++ b/internal/agentcapabilities/selection_test.go @@ -10,9 +10,9 @@ import ( "strings" "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentbundle" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" ) var testIdentity = Identity{ @@ -66,8 +66,8 @@ func TestBundleAndLocalDirectoryInventoriesAgree(t *testing.T) { t.Run(name, func(t *testing.T) { bundled, local, source := openTestRoot(t), openTestRoot(t), openTestRoot(t) files := map[string][]byte{"SKILL.md": skillManifest("proof"), "resource.txt": []byte("preserved")} - skill := agentskill.Metadata{Type: "inline", Name: "proof", Description: "Check proof."} - input := Input{Skills: []agentskill.Metadata{skill}} + skill := proto.SkillMetadata{Type: "inline", Name: "proof", Description: "Check proof."} + input := proto.CapabilitySources{Skills: []proto.SkillMetadata{skill}} if plugin { files = map[string][]byte{ ".codex-plugin/plugin.json": []byte(`{"name":"package","description":"Package proof.","skills":"./skills","mcpServers":"./.mcp.json"}`), @@ -75,8 +75,8 @@ func TestBundleAndLocalDirectoryInventoriesAgree(t *testing.T) { "skills/proof/SKILL.md": skillManifest("proof"), "proof.py": []byte("# Preserved server.\n"), } - metadata := agentplugin.Metadata{Type: "inline", Name: "package", Description: "Package proof."} - input = Input{Plugins: []agentplugin.Metadata{metadata}} + metadata := proto.PluginMetadata{Type: "inline", Name: "package", Description: "Package proof."} + input = proto.CapabilitySources{Plugins: []proto.PluginMetadata{metadata}} if err := InstallPlugin(bundled, 0, testArchive(t, files), metadata); err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestBundleAndLocalDirectoryInventoriesAgree(t *testing.T) { for path, content := range files { writeWorkspace(t, source, path, content) } - localInput := Input{Directories: []string{"/caller/capabilities"}} + localInput := proto.CapabilitySources{Directories: []string{"/caller/capabilities"}} resolve := func(path string) (*os.Root, error) { if path != "/caller/capabilities" { t.Fatalf("unexpected source: %s", path) @@ -129,7 +129,7 @@ func TestSnapshotIdentityAndSourceLifetime(t *testing.T) { source, first, second := openTestRoot(t), openTestRoot(t), openTestRoot(t) writeWorkspace(t, source, "SKILL.md", skillManifest("proof")) writeWorkspace(t, source, "resource.txt", []byte("first")) - input := Input{Directories: []string{"/caller/source"}} + input := proto.CapabilitySources{Directories: []string{"/caller/source"}} resolve := func(string) (*os.Root, error) { return source.OpenRoot(".") } if err := Finalize(first, input, testIdentity, resolve); err != nil { t.Fatal(err) @@ -168,14 +168,14 @@ func TestSnapshotIdentityAndSourceLifetime(t *testing.T) { func TestEmptySnapshotAndRequiredBinding(t *testing.T) { installed := openTestRoot(t) - if err := Finalize(installed, Input{}, testIdentity, nil); err != nil { + if err := Finalize(installed, proto.CapabilitySources{}, testIdentity, nil); err != nil { t.Fatal(err) } manifest, err := Load(installed) if err != nil || len(manifest.Skills) != 0 || len(manifest.MCP) != 0 { t.Fatalf("empty snapshot: %+v %v", manifest, err) } - if ValidateSelection(manifest, Input{Skills: []agentskill.Metadata{}, Plugins: []agentplugin.Metadata{}, Directories: []string{}}, testIdentity) != nil { + if ValidateSelection(manifest, proto.CapabilitySources{Skills: []proto.SkillMetadata{}, Plugins: []proto.PluginMetadata{}, Directories: []string{}}, testIdentity) != nil { t.Fatal("nil/empty selections differ") } for _, scenario := range []string{"identity", "hash", "hash-uppercase", "old-manifest"} { @@ -204,7 +204,7 @@ func TestEmptySnapshotAndRequiredBinding(t *testing.T) { } }) } - skill := agentskill.Metadata{Type: "inline", Name: "proof", Description: "Check proof."} + skill := proto.SkillMetadata{Type: "inline", Name: "proof", Description: "Check proof."} if InstallSkill(installed, testArchive(t, map[string][]byte{"SKILL.md": skillManifest("proof")}), skill) == nil { t.Fatal("completed snapshot accepted a late import") } @@ -215,7 +215,7 @@ func TestPartialAndOversizedSnapshotsFailClosed(t *testing.T) { t.Run(scenario, func(t *testing.T) { source, installed := openTestRoot(t), openTestRoot(t) writeWorkspace(t, source, "SKILL.md", skillManifest("proof")) - input := Input{Directories: []string{"/caller/source"}} + input := proto.CapabilitySources{Directories: []string{"/caller/source"}} switch scenario { case "temporary-manifest": writeWorkspace(t, installed, ManifestName+".tmp", []byte("partial")) @@ -224,7 +224,7 @@ func TestPartialAndOversizedSnapshotsFailClosed(t *testing.T) { case "unexpected-bundle": writeWorkspace(t, installed, "skills/other/SKILL.md", skillManifest("other")) case "missing-bundle": - input.Skills = []agentskill.Metadata{{Type: "inline", Name: "missing", Description: "Check proof."}} + input.Skills = []proto.SkillMetadata{{Type: "inline", Name: "missing", Description: "Check proof."}} case "oversized": file, err := source.OpenFile("large", os.O_CREATE|os.O_WRONLY, 0600) if err != nil { @@ -272,10 +272,10 @@ func TestInstallerRejectsSourceRootSymlinksAndInvalidSkills(t *testing.T) { if err := source.Symlink("real", "alias"); err != nil { t.Fatal(err) } - if Finalize(installed, Input{Directories: []string{"/workspace/alias"}}, testIdentity, workspaceResolver(source)) == nil { + if Finalize(installed, proto.CapabilitySources{Directories: []string{"/workspace/alias"}}, testIdentity, workspaceResolver(source)) == nil { t.Fatal("source alias accepted") } - metadata := agentskill.Metadata{Type: "inline", Name: "proof", Description: "Check proof."} + metadata := proto.SkillMetadata{Type: "inline", Name: "proof", Description: "Check proof."} if InstallSkill(installed, testArchive(t, map[string][]byte{"SKILL.md": skillManifest("different")}), metadata) == nil { t.Fatal("mismatched Skill metadata installed") } @@ -292,7 +292,7 @@ func TestInstallRejectsAliasedParent(t *testing.T) { if err := root.Symlink("other", "skills"); err != nil { t.Fatal(err) } - metadata := agentskill.Metadata{Type: "inline", Name: "proof", Description: "Check proof."} + metadata := proto.SkillMetadata{Type: "inline", Name: "proof", Description: "Check proof."} if InstallSkill(root, testArchive(t, map[string][]byte{"SKILL.md": skillManifest("proof")}), metadata) == nil { t.Fatal("aliased installation parent accepted") } @@ -304,7 +304,7 @@ func TestInstallRejectsAliasedParent(t *testing.T) { func TestFailedDirectoryCaptureCannotBeReplayed(t *testing.T) { source, installed := openTestRoot(t), openTestRoot(t) writeWorkspace(t, source, "first/SKILL.md", skillManifest("first")) - input := Input{Directories: []string{"/workspace/first", "/workspace/second"}} + input := proto.CapabilitySources{Directories: []string{"/workspace/first", "/workspace/second"}} if Finalize(installed, input, testIdentity, workspaceResolver(source)) == nil { t.Fatal("missing second source accepted") } @@ -320,7 +320,7 @@ func TestFailedDirectoryCaptureCannotBeReplayed(t *testing.T) { } func TestSelectionDigestKeepsSourceOrderAndMetadata(t *testing.T) { - input := Input{Directories: []string{"/one", "/two"}, Skills: []agentskill.Metadata{{Type: "inline", Name: "proof", Description: "Check proof."}}} + input := proto.CapabilitySources{Directories: []string{"/one", "/two"}, Skills: []proto.SkillMetadata{{Type: "inline", Name: "proof", Description: "Check proof."}}} digest, err := selectionHash(input) if err != nil { t.Fatal(err) @@ -329,8 +329,8 @@ func TestSelectionDigestKeepsSourceOrderAndMetadata(t *testing.T) { swapped := input swapped.Directories = []string{"/two", "/one"} changed := input - changed.Skills = []agentskill.Metadata{{Type: "inline", Name: "proof", Description: "Changed."}} - for _, selection := range []Input{swapped, changed} { + changed.Skills = []proto.SkillMetadata{{Type: "inline", Name: "proof", Description: "Changed."}} + for _, selection := range []proto.CapabilitySources{swapped, changed} { if ValidateSelection(manifest, selection, testIdentity) == nil { t.Fatal("changed selection accepted") } diff --git a/internal/agentcapabilities/source_directories.go b/internal/agentcapabilities/source_directories.go deleted file mode 100644 index 9c8adac61..000000000 --- a/internal/agentcapabilities/source_directories.go +++ /dev/null @@ -1,50 +0,0 @@ -package agentcapabilities - -import ( - "path" - "strings" - "unicode/utf8" -) - -// ValidateSourceDirectories checks portable absolute source addresses without -// interpreting them on the Core host. Runtime authorizes and opens local paths. -func ValidateSourceDirectories(directories []string) error { - if len(directories) > 50 { - return ErrInvalid - } - seen := make(map[string]bool, len(directories)) - for _, directory := range directories { - if len(directory) == 0 || len(directory) > 4096 || !utf8.ValidString(directory) || seen[directory] || !portableAbsoluteDirectory(directory) { - return ErrInvalid - } - for _, b := range []byte(directory) { - if b < 0x20 || b == 0x7f { - return ErrInvalid - } - } - seen[directory] = true - } - return nil -} - -func portableAbsoluteDirectory(value string) bool { - // Drive letters and UNC shares are wire syntax, not a Windows implementation. - if len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' && (value[2] == '/' || value[2] == '\\') { - rest := strings.ReplaceAll(value[2:], "\\", "/") - return path.Clean(rest) == rest && !strings.ContainsAny(rest, ":*?\"<>|") - } - if strings.HasPrefix(value, "\\\\") || strings.HasPrefix(value, "//") { - rest := strings.ReplaceAll(value[2:], "\\", "/") - parts := strings.Split(rest, "/") - if len(parts) < 2 || strings.ContainsAny(rest, ":*?\"<>|") { - return false - } - for _, part := range parts { - if part == "" || part == "." || part == ".." { - return false - } - } - return true - } - return strings.HasPrefix(value, "/") && !strings.Contains(value, "\\") && path.Clean(value) == value -} diff --git a/internal/agentdaemon/proto/capability_sources.go b/internal/agentdaemon/proto/capability_sources.go new file mode 100644 index 000000000..9fc775268 --- /dev/null +++ b/internal/agentdaemon/proto/capability_sources.go @@ -0,0 +1,125 @@ +package proto + +import ( + "errors" + "path" + "strings" + "unicode/utf8" +) + +const ( + MaxCapabilitySkills = 50 + maxCapabilityPlugins = 50 + maxSourceDirectories = 50 +) + +var errInvalidCapabilitySources = errors.New("invalid capability sources") + +// SkillMetadata identifies one inline Skill selection. +type SkillMetadata struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` +} + +// PluginMetadata identifies one inline Plugin selection. +type PluginMetadata struct { + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` +} + +// CapabilitySources describes frozen sources; directory contents are observed after setup. +type CapabilitySources struct { + Skills []SkillMetadata `json:"skills,omitempty"` + Plugins []PluginMetadata `json:"plugins,omitempty"` + Directories []string `json:"directories,omitempty"` +} + +// InstalledSkill contains Runtime-owned paths, never public resource metadata. +type InstalledSkill struct { + // InstallationRoot is resolved by Runtime and never persisted or accepted over wire. + InstallationRoot string `json:"-"` + Metadata SkillMetadata `json:"metadata"` + RelativeRoot string `json:"relative_root"` + PackageRoot string `json:"package_root"` +} + +// PluginMCPServer is an inert Plugin package declaration. Environment variables are resolved +// from installed user configuration by Runtime, never from the parser's process. +type PluginMCPServer struct { + Name string `json:"-"` + Type string `json:"type,omitempty"` + URL string `json:"url,omitempty"` + BearerTokenEnvVar string `json:"bearer_token_env_var,omitempty"` + HTTPHeaders map[string]string `json:"http_headers,omitempty"` + Command string `json:"command,omitempty"` + Args []string `json:"args,omitempty"` + EnvVars []string `json:"env_vars,omitempty"` + CWD string `json:"cwd,omitempty"` +} + +// ValidateCapabilitySources bounds source selections without reading archives or directories. +// Portable manifest grammar is checked by the shared Skill and Plugin parsers. +func ValidateCapabilitySources(sources CapabilitySources) error { + if len(sources.Skills) > MaxCapabilitySkills || len(sources.Plugins) > maxCapabilityPlugins || ValidateSourceDirectories(sources.Directories) != nil { + return errInvalidCapabilitySources + } + validMetadata := func(kind, name, description string) bool { + return kind == "inline" && name != "" && description != "" && utf8.ValidString(name) && utf8.ValidString(description) + } + for _, skill := range sources.Skills { + if !validMetadata(skill.Type, skill.Name, skill.Description) { + return errInvalidCapabilitySources + } + } + for _, plugin := range sources.Plugins { + if !validMetadata(plugin.Type, plugin.Name, plugin.Description) { + return errInvalidCapabilitySources + } + } + return nil +} + +// ValidateSourceDirectories checks portable absolute source addresses without +// interpreting them on the Core host. Runtime authorizes and opens local paths. +func ValidateSourceDirectories(directories []string) error { + if len(directories) > maxSourceDirectories { + return errInvalidCapabilitySources + } + seen := make(map[string]bool, len(directories)) + for _, directory := range directories { + if len(directory) == 0 || len(directory) > 4096 || !utf8.ValidString(directory) || seen[directory] || !portableAbsoluteDirectory(directory) { + return errInvalidCapabilitySources + } + for _, b := range []byte(directory) { + if b < 0x20 || b == 0x7f { + return errInvalidCapabilitySources + } + } + seen[directory] = true + } + return nil +} + +func portableAbsoluteDirectory(value string) bool { + // Drive letters and UNC shares are wire syntax, not a Windows implementation. + if len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' && (value[2] == '/' || value[2] == '\\') { + rest := strings.ReplaceAll(value[2:], "\\", "/") + return path.Clean(rest) == rest && !strings.ContainsAny(rest, ":*?\"<>|") + } + if strings.HasPrefix(value, "\\\\") || strings.HasPrefix(value, "//") { + rest := strings.ReplaceAll(value[2:], "\\", "/") + parts := strings.Split(rest, "/") + if len(parts) < 2 || strings.ContainsAny(rest, ":*?\"<>|") { + return false + } + for _, part := range parts { + if part == "" || part == "." || part == ".." { + return false + } + } + return true + } + return strings.HasPrefix(value, "/") && !strings.Contains(value, "\\") && path.Clean(value) == value +} diff --git a/internal/agentcapabilities/source_directories_test.go b/internal/agentdaemon/proto/capability_sources_test.go similarity index 80% rename from internal/agentcapabilities/source_directories_test.go rename to internal/agentdaemon/proto/capability_sources_test.go index 2d1908177..0afbdd022 100644 --- a/internal/agentcapabilities/source_directories_test.go +++ b/internal/agentdaemon/proto/capability_sources_test.go @@ -1,4 +1,4 @@ -package agentcapabilities +package proto import ( "strings" @@ -19,7 +19,4 @@ func TestPortableSourceDirectories(t *testing.T) { if ValidateSourceDirectories([]string{"/a", "/a"}) == nil || ValidateSourceDirectories(make([]string, 51)) == nil { t.Fatal("source bounds not enforced") } - if ValidateDirectories([]string{`C:\skills`}) == nil || ValidateLocalDirectories([]string{`C:\skills`}) == nil { - t.Fatal("portable source weakened local/managed authorization") - } } diff --git a/internal/agentdaemon/proto/environment.go b/internal/agentdaemon/proto/environment.go index e28cf1bb6..eaf972cf7 100644 --- a/internal/agentdaemon/proto/environment.go +++ b/internal/agentdaemon/proto/environment.go @@ -1,17 +1,12 @@ package proto -import ( - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" -) - // LocalEnvironment names a frozen workspace selection. Runtime must verify it // against the bound local root before resolving capabilities or native execution. type LocalEnvironment struct { - CapabilityRoot string `json:"-"` - ID string `json:"id"` - WorkspaceDirectory string `json:"workspace_directory"` - CapabilitySources *agentcapabilities.Input `json:"capability_sources"` + CapabilityRoot string `json:"-"` + ID string `json:"id"` + WorkspaceDirectory string `json:"workspace_directory"` + CapabilitySources *CapabilitySources `json:"capability_sources"` // WorkspaceRoot is the bound local root the daemon supplies for execution; // wire input cannot supply it. Read-only preparation leaves it empty. WorkspaceRoot string `json:"-"` @@ -19,7 +14,7 @@ type LocalEnvironment struct { // Runtime still ensures and loads the protected installation before execution. Capabilities bool `json:"capabilities,omitempty"` // Skills is resolved by the bound daemon; wire input cannot supply paths. - Skills []agentcapabilities.InstalledSkill `json:"-"` + Skills []InstalledSkill `json:"-"` // MCP is resolved from the same protected installation, never from wire input. MCP []EnvironmentMCP `json:"-"` // ToolEnvironment consumes Core-completed confidential initialization. @@ -31,7 +26,7 @@ type LocalEnvironment struct { type EnvironmentMCP struct { InstallationRoot string PackageRoot string - Server agentplugin.MCPServer + Server PluginMCPServer BearerToken *string } diff --git a/internal/agentdaemon/proto/mcp.go b/internal/agentdaemon/proto/mcp.go index 4bbf4f9a5..ed3505ae1 100644 --- a/internal/agentdaemon/proto/mcp.go +++ b/internal/agentdaemon/proto/mcp.go @@ -4,8 +4,6 @@ import ( "errors" "regexp" "strings" - - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" ) // MCPHTTPServer declares public MCP tools with an explicit outbound connection origin. It is an HTTP @@ -40,7 +38,7 @@ var mcpEnvironmentVariable = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) // variable names in EnvVars and no NUL in the command or its arguments. func (s MCPStdio) Valid() bool { if strings.TrimSpace(s.Command) == "" || strings.ContainsRune(s.Command+strings.Join(s.Args, ""), 0) || - agentcapabilities.ValidateSourceDirectories([]string{s.Cwd}) != nil { + ValidateSourceDirectories([]string{s.Cwd}) != nil { return false } for _, name := range s.EnvVars { diff --git a/internal/agentdaemon/proto/runtime_prepare.go b/internal/agentdaemon/proto/runtime_prepare.go index 576cd8be4..56a430a7f 100644 --- a/internal/agentdaemon/proto/runtime_prepare.go +++ b/internal/agentdaemon/proto/runtime_prepare.go @@ -7,9 +7,6 @@ import ( "strings" "unicode/utf8" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" "github.com/google/uuid" ) @@ -40,21 +37,21 @@ type RuntimeInitialization struct { // Runtime resolves logical paths and owns installation destinations. Envelope.ID // identifies one connection-local transfer. type RuntimePreparePayload struct { - BudgetMS int64 `json:"budget_ms,omitempty"` - Step string `json:"step"` - EnvironmentID string `json:"environment_id,omitempty"` - SessionID string `json:"session_id,omitempty"` - Action string `json:"action,omitempty"` - Slot int `json:"slot,omitempty"` - Skill *agentskill.Metadata `json:"skill,omitempty"` - Plugin *agentplugin.Metadata `json:"plugin,omitempty"` - File *RuntimeInitialFile `json:"file,omitempty"` - Initialization *RuntimeInitialization `json:"initialization,omitempty"` - Sources *agentcapabilities.Input `json:"sources,omitempty"` - SizeBytes int `json:"size_bytes,omitempty"` - SHA256 string `json:"sha256,omitempty"` - Offset int `json:"offset,omitempty"` - Data []byte `json:"data,omitempty"` + BudgetMS int64 `json:"budget_ms,omitempty"` + Step string `json:"step"` + EnvironmentID string `json:"environment_id,omitempty"` + SessionID string `json:"session_id,omitempty"` + Action string `json:"action,omitempty"` + Slot int `json:"slot,omitempty"` + Skill *SkillMetadata `json:"skill,omitempty"` + Plugin *PluginMetadata `json:"plugin,omitempty"` + File *RuntimeInitialFile `json:"file,omitempty"` + Initialization *RuntimeInitialization `json:"initialization,omitempty"` + Sources *CapabilitySources `json:"sources,omitempty"` + SizeBytes int `json:"size_bytes,omitempty"` + SHA256 string `json:"sha256,omitempty"` + Offset int `json:"offset,omitempty"` + Data []byte `json:"data,omitempty"` } type RuntimePrepareResultPayload struct { @@ -91,12 +88,12 @@ func ValidRuntimePrepareRequest(p RuntimePreparePayload) bool { switch p.Action { case "skill": if p.Skill == nil || p.Plugin != nil || p.Sources != nil || p.Slot != 0 || - agentcapabilities.ValidateInput(agentcapabilities.Input{Skills: []agentskill.Metadata{*p.Skill}}) != nil { + ValidateCapabilitySources(CapabilitySources{Skills: []SkillMetadata{*p.Skill}}) != nil { return false } case "plugin": if p.Plugin == nil || p.Skill != nil || p.Sources != nil || p.Slot < 0 || p.Slot >= 50 || - agentcapabilities.ValidateInput(agentcapabilities.Input{Plugins: []agentplugin.Metadata{*p.Plugin}}) != nil { + ValidateCapabilitySources(CapabilitySources{Plugins: []PluginMetadata{*p.Plugin}}) != nil { return false } case "file": @@ -109,7 +106,7 @@ func ValidRuntimePrepareRequest(p RuntimePreparePayload) bool { } case "finalize": if p.Skill != nil || p.Plugin != nil || p.Sources == nil || p.Slot != 0 || - p.SizeBytes != 0 || p.SHA256 != "" || agentcapabilities.ValidateInput(*p.Sources) != nil { + p.SizeBytes != 0 || p.SHA256 != "" || ValidateCapabilitySources(*p.Sources) != nil { return false } default: diff --git a/internal/agentdaemon/proto/runtime_prepare_test.go b/internal/agentdaemon/proto/runtime_prepare_test.go index 8b63322e9..aa0f37b84 100644 --- a/internal/agentdaemon/proto/runtime_prepare_test.go +++ b/internal/agentdaemon/proto/runtime_prepare_test.go @@ -6,15 +6,12 @@ import ( "strings" "testing" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" "github.com/google/uuid" ) func capabilityBegin() RuntimePreparePayload { return RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), - Action: "skill", Skill: &agentskill.Metadata{Type: "inline", Name: "example", Description: "Example"}, SizeBytes: 10, SHA256: strings.Repeat("a", 64)} + Action: "skill", Skill: &SkillMetadata{Type: "inline", Name: "example", Description: "Example"}, SizeBytes: 10, SHA256: strings.Repeat("a", 64)} } func TestCapabilitiesRequestValidation(t *testing.T) { @@ -31,16 +28,16 @@ func TestCapabilitiesRequestValidation(t *testing.T) { "begin data": func(p *RuntimePreparePayload) { p.Data = []byte("x") }, "begin offset": func(p *RuntimePreparePayload) { p.Offset = 1 }, "mixed metadata": func(p *RuntimePreparePayload) { - p.Plugin = &agentplugin.Metadata{Type: "inline", Name: "example", Description: "Example"} + p.Plugin = &PluginMetadata{Type: "inline", Name: "example", Description: "Example"} }, - "mixed sources": func(p *RuntimePreparePayload) { p.Sources = &agentcapabilities.Input{} }, + "mixed sources": func(p *RuntimePreparePayload) { p.Sources = &CapabilitySources{} }, "missing metadata": func(p *RuntimePreparePayload) { p.Skill = nil }, "wrong metadata type": func(p *RuntimePreparePayload) { - p.Skill = &agentskill.Metadata{Type: "reference", Name: "example", Description: "Example"} + p.Skill = &SkillMetadata{Type: "reference", Name: "example", Description: "Example"} }, - "missing description": func(p *RuntimePreparePayload) { p.Skill = &agentskill.Metadata{Type: "inline", Name: "example"} }, + "missing description": func(p *RuntimePreparePayload) { p.Skill = &SkillMetadata{Type: "inline", Name: "example"} }, "oversized header": func(p *RuntimePreparePayload) { - p.Skill = &agentskill.Metadata{Type: "inline", Name: "example", Description: strings.Repeat("x", RuntimePrepareMaxFrameBytes)} + p.Skill = &SkillMetadata{Type: "inline", Name: "example", Description: strings.Repeat("x", RuntimePrepareMaxFrameBytes)} }, "skill slot": func(p *RuntimePreparePayload) { p.Slot = 1 }, "no digest": func(p *RuntimePreparePayload) { p.SHA256 = "" }, @@ -58,7 +55,7 @@ func TestCapabilitiesRequestValidation(t *testing.T) { }) } plugin := valid - plugin.Action, plugin.Skill, plugin.Plugin, plugin.Slot = "plugin", nil, &agentplugin.Metadata{Type: "inline", Name: "example", Description: "Example"}, 49 + plugin.Action, plugin.Skill, plugin.Plugin, plugin.Slot = "plugin", nil, &PluginMetadata{Type: "inline", Name: "example", Description: "Example"}, 49 if !ValidRuntimePrepareRequest(plugin) { t.Fatal("valid plugin refused") } @@ -85,7 +82,7 @@ func TestCapabilitiesRequestValidation(t *testing.T) { {Step: "chunk"}, {Step: "commit", Offset: 1}, {Step: "commit", Data: []byte("x")}, - {Step: "commit", Sources: &agentcapabilities.Input{}}, + {Step: "commit", Sources: &CapabilitySources{}}, } { if ValidRuntimePrepareRequest(p) { t.Fatal("invalid continuation accepted", p.Step) @@ -94,7 +91,7 @@ func TestCapabilitiesRequestValidation(t *testing.T) { } func TestCapabilitiesFinalizeRequiresExplicitBoundedSelection(t *testing.T) { - p := RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &agentcapabilities.Input{}} + p := RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &CapabilitySources{}} if !ValidRuntimePrepareRequest(p) { t.Fatal("explicit empty finalization refused") } @@ -103,13 +100,13 @@ func TestCapabilitiesFinalizeRequiresExplicitBoundedSelection(t *testing.T) { func(p *RuntimePreparePayload) { p.SHA256 = strings.Repeat("a", 64) }, func(p *RuntimePreparePayload) { p.SizeBytes = 1 }, func(p *RuntimePreparePayload) { - p.Sources = &agentcapabilities.Input{Directories: make([]string, 51)} + p.Sources = &CapabilitySources{Directories: make([]string, 51)} }, func(p *RuntimePreparePayload) { - p.Sources = &agentcapabilities.Input{Directories: []string{"/workspace/../secret"}} + p.Sources = &CapabilitySources{Directories: []string{"/workspace/../secret"}} }, func(p *RuntimePreparePayload) { - p.Sources = &agentcapabilities.Input{Directories: []string{"/workspace/a", "/workspace/a"}} + p.Sources = &CapabilitySources{Directories: []string{"/workspace/a", "/workspace/a"}} }, } { bad := p @@ -155,9 +152,9 @@ func TestCapabilitiesResultCannotMisrepresentUncertainty(t *testing.T) { } func TestLocalEnvironmentCarriesSelectionButNeverInstalledRoots(t *testing.T) { - input := &agentcapabilities.Input{Directories: []string{"/workspace/capabilities"}} + input := &CapabilitySources{Directories: []string{"/workspace/capabilities"}} original := LocalEnvironment{ID: uuid.NewString(), WorkspaceDirectory: "/workspace", CapabilitySources: input, Capabilities: true, - Skills: []agentcapabilities.InstalledSkill{{RelativeRoot: "private-installed-root"}}, + Skills: []InstalledSkill{{RelativeRoot: "private-installed-root"}}, MCP: []EnvironmentMCP{{PackageRoot: "private-mcp-root"}}} raw, err := json.Marshal(original) if err != nil { @@ -266,13 +263,10 @@ func TestRuntimePreparationExitCodes(t *testing.T) { } func TestRuntimePreparationPortableSources(t *testing.T) { - p := RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &agentcapabilities.Input{Directories: []string{`C:\Users\operator\skills`, `\\host\share\skills`}}} + p := RuntimePreparePayload{BudgetMS: 300000, Step: "begin", EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &CapabilitySources{Directories: []string{`C:\Users\operator\skills`, `\\host\share\skills`}}} if !ValidRuntimePrepareRequest(p) { t.Fatal("portable sources rejected by wire") } - if agentcapabilities.ValidateLocalDirectories(p.Sources.Directories) == nil { - t.Fatal("Windows sources accepted by Linux resolver") - } } func TestRuntimePreparationBudgetOnlyOnBegin(t *testing.T) { diff --git a/internal/agentplugin/bundle.go b/internal/agentplugin/bundle.go index 22cb371fe..d3d1db5f6 100644 --- a/internal/agentplugin/bundle.go +++ b/internal/agentplugin/bundle.go @@ -10,33 +10,29 @@ import ( "sort" "strings" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentbundle" "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" ) var ErrInvalid = errors.New("invalid or unsupported Plugin bundle") -type Metadata struct { - Type string `json:"type"` - Name string `json:"name"` - Description string `json:"description"` -} - // Skill identifies a validated root inside its preserved Plugin package. type Skill struct { - Metadata agentskill.Metadata `json:"metadata"` + Metadata proto.SkillMetadata `json:"metadata"` RelativeRoot string `json:"relative_root"` } type Bundle struct { - Metadata Metadata + Metadata proto.PluginMetadata Files []agentbundle.File Skills []Skill - MCP []MCPServer + MCP []proto.PluginMCPServer } // Read validates the archive and its declared identity without native loading. -func Read(archive []byte, expected Metadata) (Bundle, error) { +func Read(archive []byte, expected proto.PluginMetadata) (Bundle, error) { files, err := agentbundle.Read(archive) if err != nil { return Bundle{}, ErrInvalid @@ -82,7 +78,7 @@ func Inspect(files []agentbundle.File) (Bundle, error) { if err != nil { return Bundle{}, ErrInvalid } - result := Bundle{Metadata: Metadata{Type: "inline", Name: manifest.Name, Description: manifest.Description}, Files: files, MCP: servers} + result := Bundle{Metadata: proto.PluginMetadata{Type: "inline", Name: manifest.Name, Description: manifest.Description}, Files: files, MCP: servers} seen := map[string]bool{} for _, file := range files { if path.Base(file.Path) != "SKILL.md" { diff --git a/internal/agentplugin/bundle_test.go b/internal/agentplugin/bundle_test.go index 01c5002a2..4227489c5 100644 --- a/internal/agentplugin/bundle_test.go +++ b/internal/agentplugin/bundle_test.go @@ -5,12 +5,14 @@ import ( "bytes" "io/fs" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestPluginPreservesMultipleSkillRootsAndSharedResources(t *testing.T) { files := pluginFixture() files[".mcp.json"] = []byte(`{"mcpServers":{}}`) - bundle, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) + bundle, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) if err != nil || len(bundle.Skills) != 2 { t.Fatalf("plugin rejected: %v", err) } @@ -26,7 +28,7 @@ func TestPluginPreservesMultipleSkillRootsAndSharedResources(t *testing.T) { if !found { t.Fatal("package-relative resource was dropped") } - if _, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "other", Description: "Use shared resources."}); err == nil { + if _, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "other", Description: "Use shared resources."}); err == nil { t.Fatal("mismatched public identity accepted") } } @@ -50,12 +52,12 @@ func TestPluginRejectsUnqualifiedActivationAndUnsafeLayout(t *testing.T) { t.Run(tc.name, func(t *testing.T) { files := pluginFixture() files[tc.path] = []byte(tc.body) - if _, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { + if _, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { t.Fatal("invalid Plugin accepted") } }) } - if _, err := Read(pluginArchive(t, pluginFixture(), fs.ModeSymlink|0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { + if _, err := Read(pluginArchive(t, pluginFixture(), fs.ModeSymlink|0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { t.Fatal("symlink members accepted") } } diff --git a/internal/agentplugin/mcp.go b/internal/agentplugin/mcp.go index a9bfdfe6f..74465ab2e 100644 --- a/internal/agentplugin/mcp.go +++ b/internal/agentplugin/mcp.go @@ -8,26 +8,14 @@ import ( "sort" "strings" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "golang.org/x/net/http/httpguts" ) -// MCPServer is an inert package declaration. Environment variables are resolved -// from installed user configuration by Runtime, never from the parser's process. -type MCPServer struct { - Name string `json:"-"` - Type string `json:"type,omitempty"` - URL string `json:"url,omitempty"` - BearerTokenEnvVar string `json:"bearer_token_env_var,omitempty"` - HTTPHeaders map[string]string `json:"http_headers,omitempty"` - Command string `json:"command,omitempty"` - Args []string `json:"args,omitempty"` - EnvVars []string `json:"env_vars,omitempty"` - CWD string `json:"cwd,omitempty"` -} - var environmentVariable = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) -func readMCP(raw json.RawMessage, files map[string][]byte) ([]MCPServer, error) { +func readMCP(raw json.RawMessage, files map[string][]byte) ([]proto.PluginMCPServer, error) { config := ".mcp.json" declared := len(raw) != 0 if declared { @@ -55,14 +43,14 @@ func readMCP(raw json.RawMessage, files map[string][]byte) ([]MCPServer, error) names = append(names, name) } sort.Strings(names) - servers := make([]MCPServer, 0, len(names)) + servers := make([]proto.PluginMCPServer, 0, len(names)) for _, name := range names { - var server MCPServer + var server proto.PluginMCPServer if name == "" || len(name) > 128 || strings.ContainsAny(name, "\x00\r\n") || decodeObject(input.Servers[name], &server) != nil { return nil, ErrInvalid } server.Name = name - if err := server.validate(); err != nil { + if err := validateMCPServer(&server); err != nil { return nil, err } servers = append(servers, server) @@ -70,7 +58,7 @@ func readMCP(raw json.RawMessage, files map[string][]byte) ([]MCPServer, error) return servers, nil } -func (s *MCPServer) validate() error { +func validateMCPServer(s *proto.PluginMCPServer) error { if s.Type == "" && s.Command != "" { s.Type = "stdio" } diff --git a/internal/agentplugin/mcp_test.go b/internal/agentplugin/mcp_test.go index 91aed8d9e..5279f81e5 100644 --- a/internal/agentplugin/mcp_test.go +++ b/internal/agentplugin/mcp_test.go @@ -3,6 +3,8 @@ package agentplugin import ( "reflect" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestMCPOnlyPluginPreservesEnvironmentDeclarations(t *testing.T) { @@ -16,7 +18,7 @@ func TestMCPOnlyPluginPreservesEnvironmentDeclarations(t *testing.T) { } t.Setenv("DO_NOT_EXPAND", "private-native-value") t.Setenv("PLUGIN_TOKEN", "must-not-resolve-here") - bundle, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) + bundle, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) if err != nil || len(bundle.Skills) != 0 || len(bundle.MCP) != 2 { t.Fatalf("MCP-only bundle: %+v %v", bundle, err) } @@ -32,7 +34,7 @@ func TestPluginCombinesSkillAndMCPWithoutActivatingOtherFiles(t *testing.T) { files := pluginFixture() files[".mcp.json"] = []byte(`{"mcpServers":{"remote":{"type":"http","url":"https://example.com/mcp"}}}`) files["hooks/hooks.json"] = []byte(`{"must_remain_inert":true}`) - bundle, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) + bundle, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}) if err != nil || len(bundle.Skills) != 2 || len(bundle.MCP) != 1 { t.Fatalf("combined package: %+v %v", bundle, err) } @@ -54,7 +56,7 @@ func TestMCPRejectsUnsupportedAuthorityAndMalformedTransport(t *testing.T) { } { files := pluginFixture() files[".mcp.json"] = []byte(`{"mcpServers":{"test":` + input + `}}`) - if _, err := Read(pluginArchive(t, files, 0600), Metadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { + if _, err := Read(pluginArchive(t, files, 0600), proto.PluginMetadata{Type: "inline", Name: "proof", Description: "Use shared resources."}); err == nil { t.Errorf("unsupported declaration accepted: %s", input) } } diff --git a/internal/agentskill/bundle.go b/internal/agentskill/bundle.go index 4ebb70ae2..b9ad4a30f 100644 --- a/internal/agentskill/bundle.go +++ b/internal/agentskill/bundle.go @@ -8,6 +8,8 @@ import ( "strings" "unicode/utf8" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "gopkg.in/yaml.v3" "github.com/MiniMax-AI/OpenAgentCore/internal/agentbundle" @@ -22,14 +24,8 @@ const ( var ErrInvalid = errors.New("invalid or unsupported Skill bundle") var namePattern = regexp.MustCompile(`^[a-z0-9]+(?:[-_][a-z0-9]+)*$`) -type Metadata struct { - Type string `json:"type"` - Name string `json:"name"` - Description string `json:"description"` -} - // Read validates the full archive before exposing any files for installation. -func Read(archive []byte, expected Metadata) ([]agentbundle.File, error) { +func Read(archive []byte, expected proto.SkillMetadata) ([]agentbundle.File, error) { if expected.Type != "inline" || !namePattern.MatchString(expected.Name) || len(expected.Name) > 64 || expected.Description == "" || !utf8.ValidString(expected.Description) { return nil, ErrInvalid } @@ -57,7 +53,7 @@ func Read(archive []byte, expected Metadata) ([]agentbundle.File, error) { } // ValidateManifest accepts portable descriptive metadata, not native activation controls. -func ValidateManifest(body []byte, expected Metadata) error { +func ValidateManifest(body []byte, expected proto.SkillMetadata) error { actual, err := InspectManifest(body) if err != nil || actual != expected { return ErrInvalid @@ -66,67 +62,67 @@ func ValidateManifest(body []byte, expected Metadata) error { } // InspectManifest shares the portable parser with Runtime directory discovery. -func InspectManifest(body []byte) (Metadata, error) { +func InspectManifest(body []byte) (proto.SkillMetadata, error) { metadata, err := manifestMetadata(body) if err != nil || !namePattern.MatchString(metadata.Name) || len(metadata.Name) > 64 || metadata.Description == "" { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } return metadata, nil } -func manifestMetadata(body []byte) (Metadata, error) { +func manifestMetadata(body []byte) (proto.SkillMetadata, error) { if len(body) > 256<<10 || !utf8.Valid(body) { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } text := strings.ReplaceAll(string(body), "\r\n", "\n") if !strings.HasPrefix(text, "---\n") { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } end := strings.Index(text[4:], "\n---") if end < 0 { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } end += 4 tail := text[end+4:] if tail != "" && !strings.HasPrefix(tail, "\n") { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } decoder := yaml.NewDecoder(strings.NewReader(text[4:end])) var document yaml.Node if decoder.Decode(&document) != nil || len(document.Content) != 1 { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } var extra yaml.Node if decoder.Decode(&extra) != io.EOF { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } node := document.Content[0] if node.Kind != yaml.MappingNode { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } fields := map[string]*yaml.Node{} for i := 0; i < len(node.Content); i += 2 { key, value := node.Content[i], node.Content[i+1] if key.Kind != yaml.ScalarNode || key.Tag != "!!str" || fields[key.Value] != nil { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } fields[key.Value] = value switch key.Value { case "name", "description", "license", "compatibility": if value.Kind != yaml.ScalarNode || value.Tag != "!!str" { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } case "metadata": var metadata map[string]string if value.Kind != yaml.MappingNode || value.Decode(&metadata) != nil { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } default: - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } } if fields["name"] == nil || fields["description"] == nil { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } - return Metadata{Type: "inline", Name: fields["name"].Value, Description: fields["description"].Value}, nil + return proto.SkillMetadata{Type: "inline", Name: fields["name"].Value, Description: fields["description"].Value}, nil } diff --git a/internal/agentskill/bundle_test.go b/internal/agentskill/bundle_test.go index b725e7710..70029887e 100644 --- a/internal/agentskill/bundle_test.go +++ b/internal/agentskill/bundle_test.go @@ -5,10 +5,12 @@ import ( "bytes" "io/fs" "testing" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) func TestBundle(t *testing.T) { - metadata := Metadata{Type: "inline", Name: "proof-skill", Description: "Run the proof."} + metadata := proto.SkillMetadata{Type: "inline", Name: "proof-skill", Description: "Run the proof."} manifest := []byte("---\nname: proof-skill\ndescription: Run the proof.\n---\nRun scripts/check.py.\n") makeArchive := func(paths []string, bodies [][]byte, mode fs.FileMode) []byte { var b bytes.Buffer diff --git a/internal/agentskill/upload.go b/internal/agentskill/upload.go index e3efd1a00..c98da60e6 100644 --- a/internal/agentskill/upload.go +++ b/internal/agentskill/upload.go @@ -5,26 +5,28 @@ import ( "bytes" "io" "strings" + + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" ) // Inspect obtains portable metadata from an uploaded bundle, then applies the // same complete archive validation used for environment installation. -func Inspect(archive []byte) (Metadata, error) { +func Inspect(archive []byte) (proto.SkillMetadata, error) { if len(archive) > MaxArchiveBytes { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } reader, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive))) if err != nil || len(reader.File) > MaxFiles { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } - var metadata Metadata + var metadata proto.SkillMetadata found := false files := 0 for _, entry := range reader.File { if !entry.FileInfo().IsDir() { files++ if files > 500 { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } } parts := strings.Split(entry.Name, "/") @@ -32,28 +34,28 @@ func Inspect(archive []byte) (Metadata, error) { continue } if found || entry.UncompressedSize64 > 256<<10 { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } stream, err := entry.Open() if err != nil { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } body, readErr := io.ReadAll(io.LimitReader(stream, (256<<10)+1)) closeErr := stream.Close() if readErr != nil || closeErr != nil { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } metadata, err = manifestMetadata(body) if err != nil { - return Metadata{}, err + return proto.SkillMetadata{}, err } found = true } if !found { - return Metadata{}, ErrInvalid + return proto.SkillMetadata{}, ErrInvalid } if _, err = Read(archive, metadata); err != nil { - return Metadata{}, err + return proto.SkillMetadata{}, err } return metadata, nil } diff --git a/services/core/internal/api/environment_plugins.go b/services/core/internal/api/environment_plugins.go index 134668071..084b349fe 100644 --- a/services/core/internal/api/environment_plugins.go +++ b/services/core/internal/api/environment_plugins.go @@ -3,7 +3,8 @@ package api import ( "encoding/json" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions" ) @@ -31,12 +32,12 @@ func decodeEnvironmentPlugins(raw json.RawMessage) ([]environmentconfig.Plugin, if err != nil { return nil, err } - result = append(result, environmentconfig.Plugin{Metadata: agentplugin.Metadata{Type: input.Type, Name: input.Name, Description: input.Description}, Archive: body}) + result = append(result, environmentconfig.Plugin{Metadata: proto.PluginMetadata{Type: input.Type, Name: input.Name, Description: input.Description}, Archive: body}) } return result, environmentconfig.ValidatePlugins(result) } -func pluginResponse(plugins []agentplugin.Metadata) []json.RawMessage { +func pluginResponse(plugins []proto.PluginMetadata) []json.RawMessage { result := make([]json.RawMessage, 0, len(plugins)) for _, plugin := range plugins { raw, _ := json.Marshal(plugin) @@ -55,7 +56,7 @@ func storedPlugins(raw json.RawMessage) ([]json.RawMessage, error) { } seen := map[string]bool{} for _, entry := range entries { - var metadata agentplugin.Metadata + var metadata proto.PluginMetadata if decodeInputObject(entry, &metadata, "type", "name", "description") != nil || metadata.Type != "inline" || metadata.Name == "" || metadata.Description == "" || seen[metadata.Name] { return nil, sessions.ErrInvalidInput } diff --git a/services/core/internal/api/environment_request.go b/services/core/internal/api/environment_request.go index b3c045621..ad8600025 100644 --- a/services/core/internal/api/environment_request.go +++ b/services/core/internal/api/environment_request.go @@ -3,8 +3,9 @@ package api import ( "encoding/json" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions" ) @@ -27,7 +28,7 @@ func decodeSessionEnvironment(raw json.RawMessage) (*v1.Environment, error) { return decodeHostedEnvironment(raw) case "self_hosted": fields = append(fields, "workspace_directory", "capability_directories") - if agentcapabilities.ValidateSourceDirectories([]string{environment.WorkspaceDirectory}) != nil || agentcapabilities.ValidateSourceDirectories(environment.CapabilityDirectories) != nil { + if proto.ValidateSourceDirectories([]string{environment.WorkspaceDirectory}) != nil || proto.ValidateSourceDirectories(environment.CapabilityDirectories) != nil { return nil, sessions.ErrInvalidInput } default: diff --git a/services/core/internal/api/hosted_environment.go b/services/core/internal/api/hosted_environment.go index ded3037a5..e88f452ad 100644 --- a/services/core/internal/api/hosted_environment.go +++ b/services/core/internal/api/hosted_environment.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/agentnetwork" @@ -78,7 +80,7 @@ func decodePreparedEnvironment(raw json.RawMessage) (*v1.Environment, error) { if kind == "openai_hosted" && agentcapabilities.ValidateDirectories(env.CapabilityDirectories) != nil { return nil, sessions.ErrInvalidInput } - if kind == "self_hosted" && agentcapabilities.ValidateSourceDirectories([]string{env.WorkspaceDirectory}) != nil { + if kind == "self_hosted" && proto.ValidateSourceDirectories([]string{env.WorkspaceDirectory}) != nil { return nil, sessions.ErrInvalidInput } return env, nil diff --git a/services/core/internal/environmentconfig/plugins.go b/services/core/internal/environmentconfig/plugins.go index df6e823e2..80df3518b 100644 --- a/services/core/internal/environmentconfig/plugins.go +++ b/services/core/internal/environmentconfig/plugins.go @@ -1,12 +1,15 @@ package environmentconfig -import "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" +import ( + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" +) const maxPluginsArchiveBytes = 10 << 20 // Plugin separates safe identity from confidential immutable input. type Plugin struct { - Metadata agentplugin.Metadata `json:"metadata"` + Metadata proto.PluginMetadata `json:"metadata"` Archive []byte `json:"archive"` } @@ -37,8 +40,8 @@ func ValidatePlugins(plugins []Plugin) error { } // PluginMetadata returns the public metadata of every Plugin in order. -func (s Setup) PluginMetadata() []agentplugin.Metadata { - result := make([]agentplugin.Metadata, 0, len(s.Plugins)) +func (s Setup) PluginMetadata() []proto.PluginMetadata { + result := make([]proto.PluginMetadata, 0, len(s.Plugins)) for _, plugin := range s.Plugins { result = append(result, plugin.Metadata) } diff --git a/services/core/internal/environmentconfig/setup.go b/services/core/internal/environmentconfig/setup.go index 7e18e86db..a7b1442b8 100644 --- a/services/core/internal/environmentconfig/setup.go +++ b/services/core/internal/environmentconfig/setup.go @@ -6,8 +6,9 @@ import ( "regexp" "strings" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" ) // Setup is confidential input, never ordinary resource metadata. @@ -45,7 +46,7 @@ func (s Setup) ValidateInstalled() error { } func (s Setup) validate(installed bool) error { - if validateSkills(s.Skills, installed) != nil || ValidatePlugins(s.Plugins) != nil || agentcapabilities.ValidateSourceDirectories(s.CapabilityDirectories) != nil { + if validateSkills(s.Skills, installed) != nil || ValidatePlugins(s.Plugins) != nil || proto.ValidateSourceDirectories(s.CapabilityDirectories) != nil { return ErrInvalid } ordinary := s diff --git a/services/core/internal/environmentconfig/skills.go b/services/core/internal/environmentconfig/skills.go index 4e4c9d6de..ac4b9d264 100644 --- a/services/core/internal/environmentconfig/skills.go +++ b/services/core/internal/environmentconfig/skills.go @@ -1,6 +1,7 @@ package environmentconfig import ( + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/skills" ) @@ -23,8 +24,8 @@ type Skill struct { } // InstallationMetadata removes public reference identity at the installer boundary. -func (s Skill) InstallationMetadata() agentskill.Metadata { - return agentskill.Metadata{Type: "inline", Name: s.Metadata.Name, Description: s.Metadata.Description} +func (s Skill) InstallationMetadata() proto.SkillMetadata { + return proto.SkillMetadata{Type: "inline", Name: s.Metadata.Name, Description: s.Metadata.Description} } const maxSkillsArchiveBytes = 10 << 20 diff --git a/services/core/internal/environmenttemplates/template.go b/services/core/internal/environmenttemplates/template.go index cd3305329..f5ad42127 100644 --- a/services/core/internal/environmenttemplates/template.go +++ b/services/core/internal/environmenttemplates/template.go @@ -4,9 +4,10 @@ import ( "time" "unicode/utf8" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/agentnetwork" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" ) @@ -20,7 +21,7 @@ type Template struct { Packages v1.EnvironmentPackages Files []environmentconfig.InitialFileMetadata Skills []environmentconfig.SkillMetadata - Plugins []agentplugin.Metadata + Plugins []proto.PluginMetadata CapabilityDirectories []string CreatedAt time.Time UpdatedAt time.Time diff --git a/services/core/internal/execution/environment_placement.go b/services/core/internal/execution/environment_placement.go index 148e26fc3..b94b76df6 100644 --- a/services/core/internal/execution/environment_placement.go +++ b/services/core/internal/execution/environment_placement.go @@ -7,7 +7,6 @@ import ( v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions" ) @@ -15,7 +14,7 @@ import ( const hostedWorkspaceDirectory = "/workspace" type environmentPlacement struct { - Plugins []agentplugin.Metadata `json:"plugins,omitempty"` + Plugins []proto.PluginMetadata `json:"plugins,omitempty"` Skills []environmentconfig.SkillMetadata `json:"skills,omitempty"` Type string `json:"type"` ToolEnvironment bool `json:"initialization,omitempty"` @@ -36,7 +35,7 @@ func parseEnvironmentPlacement(configuration json.RawMessage) (environmentPlacem return placement, sessions.ErrInvalidInput } var local struct { - Plugins []agentplugin.Metadata `json:"plugins,omitempty"` + Plugins []proto.PluginMetadata `json:"plugins,omitempty"` Skills []environmentconfig.SkillMetadata `json:"skills,omitempty"` Files []environmentconfig.InitialFileMetadata `json:"files"` Packages *v1.EnvironmentPackages `json:"packages,omitempty"` @@ -48,7 +47,7 @@ func parseEnvironmentPlacement(configuration json.RawMessage) (environmentPlacem } decoder := json.NewDecoder(bytes.NewReader(configuration)) decoder.DisallowUnknownFields() - if decoder.Decode(&local) != nil || agentcapabilities.ValidateSourceDirectories(local.CapabilityDirectories) != nil { + if decoder.Decode(&local) != nil || proto.ValidateSourceDirectories(local.CapabilityDirectories) != nil { return placement, sessions.ErrInvalidInput } // Placement selects a workspace; all preparation fields are shared. @@ -80,7 +79,7 @@ func (d *Dispatcher) configurePreparedEnvironment(session sessions.Session, envi if err != nil || !environmentDeviceMatches(session, environment, bound) { return sessions.ErrInvalidInput } - sources := &agentcapabilities.Input{Plugins: append([]agentplugin.Metadata(nil), placement.Plugins...), Directories: append([]string(nil), placement.CapabilityDirectories...)} + sources := &proto.CapabilitySources{Plugins: append([]proto.PluginMetadata(nil), placement.Plugins...), Directories: append([]string(nil), placement.CapabilityDirectories...)} for _, metadata := range placement.Skills { if metadata.ValidateInstalled() != nil { return sessions.ErrInvalidInput @@ -97,6 +96,6 @@ func (d *Dispatcher) configurePreparedEnvironment(session sessions.Session, envi } func validSelfHostedPlacement(placement environmentPlacement) bool { - return agentcapabilities.ValidateSourceDirectories([]string{placement.WorkspaceDirectory}) == nil && - agentcapabilities.ValidateSourceDirectories(placement.CapabilityDirectories) == nil + return proto.ValidateSourceDirectories([]string{placement.WorkspaceDirectory}) == nil && + proto.ValidateSourceDirectories(placement.CapabilityDirectories) == nil } diff --git a/services/core/internal/execution/runtime_capabilities_test.go b/services/core/internal/execution/runtime_capabilities_test.go index 63319df84..52d30fbd6 100644 --- a/services/core/internal/execution/runtime_capabilities_test.go +++ b/services/core/internal/execution/runtime_capabilities_test.go @@ -8,7 +8,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/google/uuid" ) @@ -29,7 +28,7 @@ func TestRuntimeCapabilitiesPreserveRawBundlesAndSetupOrdering(t *testing.T) { archive := []byte("opaque archive bytes must be expanded only by Runtime") setup := environmentconfig.Setup{ Skills: []environmentconfig.Skill{{Metadata: environmentconfig.SkillMetadata{Type: "skill_reference", SkillID: "private-reference", Version: "1", Name: "example", Description: "Safe description"}, Archive: archive}}, - Plugins: []environmentconfig.Plugin{{Metadata: agentplugin.Metadata{Type: "inline", Name: "plugin", Description: "Safe plugin"}, Archive: archive}}, + Plugins: []environmentconfig.Plugin{{Metadata: proto.PluginMetadata{Type: "inline", Name: "plugin", Description: "Safe plugin"}, Archive: archive}}, Commands: []environmentconfig.SetupCommand{{Command: "prepare directory"}}, CapabilityDirectories: []string{"/workspace/generated"}, } diff --git a/services/core/internal/execution/runtime_setup.go b/services/core/internal/execution/runtime_setup.go index 1f1822177..bff49c3f7 100644 --- a/services/core/internal/execution/runtime_setup.go +++ b/services/core/internal/execution/runtime_setup.go @@ -69,7 +69,7 @@ func setupOperations(setup environmentconfig.Setup) []runtimeSetupOperation { result = append(result, operation) } if len(setup.Skills)+len(setup.Plugins)+len(setup.CapabilityDirectories) > 0 { - sources := agentcapabilities.Input{Plugins: setup.PluginMetadata(), Directories: setup.CapabilityDirectories} + sources := proto.CapabilitySources{Plugins: setup.PluginMetadata(), Directories: setup.CapabilityDirectories} for _, skill := range setup.Skills { sources.Skills = append(sources.Skills, skill.InstallationMetadata()) } diff --git a/services/core/internal/persistence/postgres/templatepg/store_test.go b/services/core/internal/persistence/postgres/templatepg/store_test.go index 589462c3f..ab95c2ebd 100644 --- a/services/core/internal/persistence/postgres/templatepg/store_test.go +++ b/services/core/internal/persistence/postgres/templatepg/store_test.go @@ -9,8 +9,9 @@ import ( "sync" "testing" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/credentialcrypto" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmenttemplates" @@ -289,7 +290,7 @@ func TestSkillsAndPluginsSealedAndPreserved(t *testing.T) { ctx := t.Context() setup := environmentconfig.Setup{ Skills: []environmentconfig.Skill{{Metadata: environmentconfig.SkillMetadata{Type: "inline", Name: "proof", Description: "A proof."}, Archive: archive(t, map[string]string{"proof/SKILL.md": "---\nname: proof\ndescription: A proof.\n---\nskill-private-canary"})}}, - Plugins: []environmentconfig.Plugin{{Metadata: agentplugin.Metadata{Type: "inline", Name: "plugin-proof", Description: "A proof."}, Archive: archive(t, map[string]string{"proof/.codex-plugin/plugin.json": `{"name":"plugin-proof","description":"A proof.","skills":"./skills"}`, "proof/skills/proof/SKILL.md": "---\nname: proof\ndescription: A proof.\n---\nplugin-private-canary"})}}, + Plugins: []environmentconfig.Plugin{{Metadata: proto.PluginMetadata{Type: "inline", Name: "plugin-proof", Description: "A proof."}, Archive: archive(t, map[string]string{"proof/.codex-plugin/plugin.json": `{"name":"plugin-proof","description":"A proof.","skills":"./skills"}`, "proof/skills/proof/SKILL.md": "---\nname: proof\ndescription: A proof.\n---\nplugin-private-canary"})}}, CapabilityDirectories: []string{"/workspace/generated"}, } tenant, foreign := uuid.NewString(), uuid.NewString() diff --git a/services/core/internal/runtimegateway/runtime_prepare_test.go b/services/core/internal/runtimegateway/runtime_prepare_test.go index d19cf2917..3cf39ae80 100644 --- a/services/core/internal/runtimegateway/runtime_prepare_test.go +++ b/services/core/internal/runtimegateway/runtime_prepare_test.go @@ -12,15 +12,13 @@ import ( "testing/synctest" "time" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentcapabilities" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" "github.com/google/uuid" ) func skillPreparation() proto.RuntimePreparePayload { return proto.RuntimePreparePayload{BudgetMS: 300000, EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "skill", - Skill: &agentskill.Metadata{Type: "inline", Name: "example", Description: "Example"}} + Skill: &proto.SkillMetadata{Type: "inline", Name: "example", Description: "Example"}} } type capabilityOutcome struct { @@ -117,7 +115,7 @@ func TestCapabilitiesTransfersMoreThanFrameLimitAndCorrelates(t *testing.T) { func TestCapabilitiesFinalizeTransfersNoArchive(t *testing.T) { s := newTestSession(newFakeConn(), "device", "test", nil, nil) defer s.Close("test") - request := proto.RuntimePreparePayload{BudgetMS: 300000, EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &agentcapabilities.Input{}} + request := proto.RuntimePreparePayload{BudgetMS: 300000, EnvironmentID: uuid.NewString(), SessionID: uuid.NewString(), Action: "finalize", Sources: &proto.CapabilitySources{}} id := uuid.NewString() done := beginCapabilities(s, t.Context(), id, request, nil) env := nextCapabilityFrame(t, s) @@ -142,7 +140,7 @@ func TestCapabilitiesRefusesConflictingArchiveBeforeSending(t *testing.T) { for _, mutate := range []func(*proto.RuntimePreparePayload){ func(p *proto.RuntimePreparePayload) { p.SHA256 = "incorrect" }, func(p *proto.RuntimePreparePayload) { p.SizeBytes = 999 }, - func(p *proto.RuntimePreparePayload) { p.Sources = &agentcapabilities.Input{} }, + func(p *proto.RuntimePreparePayload) { p.Sources = &proto.CapabilitySources{} }, } { request := skillPreparation() mutate(&request) diff --git a/services/core/internal/runtimegateway/workspace_read.go b/services/core/internal/runtimegateway/workspace_read.go index ca7159086..2456794ac 100644 --- a/services/core/internal/runtimegateway/workspace_read.go +++ b/services/core/internal/runtimegateway/workspace_read.go @@ -98,7 +98,7 @@ func validWorkspaceReadResult(result proto.WorkspaceReadResultPayload, limit int return false } switch result.ErrorCode { - case "invalid_request", "resource_unavailable", "read_capacity", "read_unsupported", "not_found", "permission_denied": + case "invalid_request", "resource_unavailable", "read_capacity", "not_found", "permission_denied": return true default: return false diff --git a/services/core/internal/skills/content.go b/services/core/internal/skills/content.go index 0010bea40..d0ade2236 100644 --- a/services/core/internal/skills/content.go +++ b/services/core/internal/skills/content.go @@ -1,11 +1,14 @@ package skills -import "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" +import ( + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentskill" +) // VerifyContent checks that a decrypted archive is still the complete bundle // its version records, before anything reads or installs it. func VerifyContent(content Content) error { - metadata := agentskill.Metadata{Type: "inline", Name: content.Version.Name, Description: content.Version.Description} + metadata := proto.SkillMetadata{Type: "inline", Name: content.Version.Name, Description: content.Version.Description} if _, err := agentskill.Read(content.Archive, metadata); err != nil { return ErrInvalidInput } diff --git a/services/core/tests/integration/environment_plugins_test.go b/services/core/tests/integration/environment_plugins_test.go index f3a5fb5ce..1e13c745b 100644 --- a/services/core/tests/integration/environment_plugins_test.go +++ b/services/core/tests/integration/environment_plugins_test.go @@ -8,7 +8,8 @@ import ( "reflect" "testing" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions" "github.com/google/uuid" @@ -34,7 +35,7 @@ func TestPluginsFrozenInSession(t *testing.T) { if err := writer.Close(); err != nil { t.Fatal(err) } - setup := environmentconfig.Setup{Plugins: []environmentconfig.Plugin{{Metadata: agentplugin.Metadata{Type: "inline", Name: "plugin-proof", Description: "A proof."}, Archive: archive.Bytes()}}, CapabilityDirectories: []string{"/workspace/generated"}} + setup := environmentconfig.Setup{Plugins: []environmentconfig.Plugin{{Metadata: proto.PluginMetadata{Type: "inline", Name: "plugin-proof", Description: "A proof."}, Archive: archive.Bytes()}}, CapabilityDirectories: []string{"/workspace/generated"}} tenant, foreign := uuid.NewString(), uuid.NewString() request := sessions.CreateSession{Creator: FixtureCreator(), Engine: "codex", IdempotencyKey: uuid.NewString(), Configuration: json.RawMessage(`{"environment":{"type":"openai_hosted"}}`), Initialization: setup} session, err := s.CreateSession(t.Context(), tenant, request) @@ -43,7 +44,7 @@ func TestPluginsFrozenInSession(t *testing.T) { } var cfg struct { Environment struct { - Plugins []agentplugin.Metadata `json:"plugins"` + Plugins []proto.PluginMetadata `json:"plugins"` Directories []string `json:"capability_directories"` } `json:"environment"` } diff --git a/services/core/tests/integration/runtime_initialization_test.go b/services/core/tests/integration/runtime_initialization_test.go index b50a949c5..3095a02c5 100644 --- a/services/core/tests/integration/runtime_initialization_test.go +++ b/services/core/tests/integration/runtime_initialization_test.go @@ -12,8 +12,6 @@ import ( "testing" "time" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" - v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/deployment" @@ -168,7 +166,7 @@ func TestManagedRuntimePreparationAllOperationsUsePeer(t *testing.T) { fileBody := bytes.Repeat([]byte("bounded bytes"), 12000) session, environment := hostedFailureSession(t, s, tenant, sessions.CreateSession{ InitialFiles: []environmentconfig.InitialFile{{Type: "inline", Path: "/workspace/first", Data: fileBody}}, - Initialization: environmentconfig.Setup{Skills: []environmentconfig.Skill{hostedFailureSkill(t)}, Plugins: []environmentconfig.Plugin{{Metadata: agentplugin.Metadata{Type: "inline", Name: "plugin", Description: "A plugin."}, Archive: archive.Bytes()}}, Packages: v1.EnvironmentPackages{NPM: []string{"is-number@7.0.0"}, Python: []string{"packaging==24.2"}}, Commands: []environmentconfig.SetupCommand{{Command: "read installed bundles and create directory"}}, CapabilityDirectories: []string{"/workspace/generated"}}, + Initialization: environmentconfig.Setup{Skills: []environmentconfig.Skill{hostedFailureSkill(t)}, Plugins: []environmentconfig.Plugin{{Metadata: proto.PluginMetadata{Type: "inline", Name: "plugin", Description: "A plugin."}, Archive: archive.Bytes()}}, Packages: v1.EnvironmentPackages{NPM: []string{"is-number@7.0.0"}, Python: []string{"packaging==24.2"}}, Commands: []environmentconfig.SetupCommand{{Command: "read installed bundles and create directory"}}, CapabilityDirectories: []string{"/workspace/generated"}}, }) provider := &initializingProvider{lifecycleProvider: lifecycleProvider{resources: map[string]sandbox.Info{}}} var actions []string diff --git a/services/core/tests/integration/template_null_selection_public_test.go b/services/core/tests/integration/template_null_selection_public_test.go index d0a5b247a..44d021084 100644 --- a/services/core/tests/integration/template_null_selection_public_test.go +++ b/services/core/tests/integration/template_null_selection_public_test.go @@ -14,7 +14,8 @@ import ( "testing" "time" - "github.com/MiniMax-AI/OpenAgentCore/internal/agentplugin" + "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" + "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/environmentconfig" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sessions" @@ -63,7 +64,7 @@ func TestTemplateNullSelectionOfficialClientPostgres(t *testing.T) { RejectedKeys []string `json:"rejected_keys"` Expected map[string]struct { Skills []environmentconfig.SkillMetadata `json:"skills"` - Plugins []agentplugin.Metadata `json:"plugins"` + Plugins []proto.PluginMetadata `json:"plugins"` CapabilityDirectories []string `json:"capability_directories"` SkillDigests []string `json:"skill_digests"` PluginDigests []string `json:"plugin_digests"`