//! Lua profile discovery and resolution. //! //! Profiles are reusable, human-authored recipes. They are intentionally not //! complete runtime manifests: runtime-bound and authority-bearing fields such //! as `pod.name` and concrete `scope.allow` rules are supplied by the resolver //! from launch context. use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::rc::Rc; use mlua::{Lua, LuaOptions, LuaSerdeExt, RegistryKey, StdLib, Table, Value as LuaValue}; use serde::{Deserialize, Serialize}; use crate::config::{CompactionConfigPartial, PermissionConfigPartial, SessionConfigPartial}; use crate::model::{AuthRef, ModelManifest}; use crate::{ MemoryConfig, Permission, PodManifest, PodManifestConfig, PodMetaConfig, ResolveError, ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths, }; const PROFILE_FORMAT_V1: &str = "yoi.lua-profile.v1"; const BUILTIN_DEFAULT_PROFILE_NAME: &str = "default"; const BUILTIN_DEFAULT_PROFILE: &str = include_str!("../../../resources/profiles/default.lua"); const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml"); const WORKSPACE_OVERRIDE_LOCAL_FILENAME: &str = "override.local.toml"; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ProfileRegistrySource { Builtin, User, Project, } impl ProfileRegistrySource { pub fn as_str(self) -> &'static str { match self { Self::Builtin => "builtin", Self::User => "user", Self::Project => "project", } } fn parse(raw: &str) -> Option { match raw { "builtin" => Some(Self::Builtin), "user" => Some(Self::User), "project" => Some(Self::Project), _ => None, } } } impl std::fmt::Display for ProfileRegistrySource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum ProfileSelector { Path { path: PathBuf, }, Named { #[serde(default, skip_serializing_if = "Option::is_none")] source: Option, name: String, }, Default, } impl ProfileSelector { pub fn path(path: impl Into) -> Self { Self::Path { path: path.into() } } pub fn named(name: impl Into) -> Self { Self::Named { source: None, name: name.into(), } } pub fn source_named(source: ProfileRegistrySource, name: impl Into) -> Self { Self::Named { source: Some(source), name: name.into(), } } pub fn parse_cli(raw: &str) -> Self { if raw == "default" { return Self::Default; } if let Some(path) = raw.strip_prefix("path:") { return Self::path(path); } if let Some((prefix, name)) = raw.split_once(':') && let Some(source) = ProfileRegistrySource::parse(prefix) { return Self::source_named(source, name); } if raw.contains('/') || raw.starts_with('.') || raw.ends_with(".lua") { Self::path(raw) } else { Self::named(raw) } } pub fn display_label(&self) -> String { match self { Self::Path { path } => path.display().to_string(), Self::Named { source, name } => match source { Some(source) => format!("{source}:{name}"), None => name.clone(), }, Self::Default => "default".to_string(), } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum ProfileSource { Path { path: PathBuf, }, Registry { source: ProfileRegistrySource, name: String, #[serde(default, skip_serializing_if = "Option::is_none")] path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] provenance: Option, }, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProfileRegistryEntry { pub source: ProfileRegistrySource, pub name: String, pub path: Option, pub provenance: String, pub description: Option, pub is_default: bool, artifact: ProfileRegistryArtifact, } impl ProfileRegistryEntry { pub fn qualified_name(&self) -> String { format!("{}:{}", self.source, self.name) } fn path( source: ProfileRegistrySource, name: String, path: PathBuf, description: Option, ) -> Self { let provenance = path.display().to_string(); Self { source, name, path: Some(path.clone()), provenance, description, is_default: false, artifact: ProfileRegistryArtifact::Path(path), } } fn embedded( source: ProfileRegistrySource, name: &'static str, label: &'static str, content: &'static str, description: Option, ) -> Self { Self { source, name: name.to_string(), path: None, provenance: label.to_string(), description, is_default: false, artifact: ProfileRegistryArtifact::Embedded { label, content }, } } } #[derive(Debug, Clone, PartialEq, Eq)] enum ProfileRegistryArtifact { Path(PathBuf), Embedded { label: &'static str, content: &'static str, }, } #[derive(Debug, Clone, Default)] pub struct ProfileRegistry { entries: Vec, default: Option, } impl ProfileRegistry { pub fn entries(&self) -> &[ProfileRegistryEntry] { &self.entries } pub fn default_entry(&self) -> Result<&ProfileRegistryEntry, ProfileError> { let default = self .default .as_ref() .ok_or(ProfileError::NoDefaultProfile)?; self.select_named(default.source, &default.name) } pub fn select( &self, selector: &ProfileSelector, ) -> Result<&ProfileRegistryEntry, ProfileError> { match selector { ProfileSelector::Path { .. } => Err(ProfileError::InvalidProfile( "path selectors are not registry entries".into(), )), ProfileSelector::Default => self.default_entry(), ProfileSelector::Named { source, name } => self.select_named(*source, name), } } fn select_named( &self, source: Option, name: &str, ) -> Result<&ProfileRegistryEntry, ProfileError> { let matches: Vec<_> = self .entries .iter() .filter(|entry| entry.name == name && source.is_none_or(|s| s == entry.source)) .collect(); match matches.as_slice() { [entry] => Ok(*entry), [] => Err(ProfileError::ProfileNotFound { selector: source.map_or_else(|| name.to_string(), |s| format!("{s}:{name}")), }), _ => Err(ProfileError::AmbiguousProfileName { name: name.to_string(), matches: matches.iter().map(|entry| entry.qualified_name()).collect(), }), } } fn push_entry(&mut self, entry: ProfileRegistryEntry) { self.entries.push(entry); } fn set_default(&mut self, default: ProfileDefault) { self.default = Some(default); } fn set_builtin_default_if_available(&mut self) { if self.default.is_some() { return; } if self .select_named( Some(ProfileRegistrySource::Builtin), BUILTIN_DEFAULT_PROFILE_NAME, ) .is_ok() { self.default = Some(ProfileDefault { source: Some(ProfileRegistrySource::Builtin), name: BUILTIN_DEFAULT_PROFILE_NAME.to_string(), }); } } fn mark_default_flags(&mut self) { let Some(default) = self.default.clone() else { return; }; let Ok(default_entry) = self.select_named(default.source, &default.name) else { return; }; let source = default_entry.source; let name = default_entry.name.clone(); for entry in &mut self.entries { entry.is_default = entry.source == source && entry.name == name; } } } #[derive(Debug, Clone, PartialEq, Eq)] struct ProfileDefault { source: Option, name: String, } #[derive(Debug, Clone)] pub struct ProfileDiscovery { user_config: Option, project_config: Option, } impl ProfileDiscovery { pub fn for_cwd(cwd: &Path) -> Self { Self { user_config: paths::user_profiles_path(), project_config: find_project_profiles_from(cwd), } } pub fn with_sources(user_config: Option, project_config: Option) -> Self { Self { user_config, project_config, } } pub fn discover(&self) -> Result { let mut registry = ProfileRegistry::default(); add_builtin_profiles(&mut registry); if let Some(path) = &self.user_config { load_profile_registry_file(&mut registry, ProfileRegistrySource::User, path)?; } if let Some(path) = &self.project_config { load_profile_registry_file(&mut registry, ProfileRegistrySource::Project, path)?; } registry.set_builtin_default_if_available(); registry.mark_default_flags(); Ok(registry) } } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileMetadata { #[serde(default, skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub format: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileManifestSnapshot { pub source: ProfileSource, #[serde(default, skip_serializing_if = "Option::is_none")] pub profile: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace_override: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WorkspaceOverrideSnapshot { pub path: PathBuf, } #[derive(Debug)] struct WorkspaceOverrideLayer { path: PathBuf, config: PodManifestConfig, } #[derive(Debug, Clone)] pub struct ResolvedProfile { pub source: ProfileSource, pub profile: Option, pub manifest: PodManifest, pub manifest_snapshot: serde_json::Value, pub raw_artifact: serde_json::Value, } #[derive(Debug, Clone, Default)] pub struct ProfileResolveOptions { pub pod_name: Option, } impl ProfileResolveOptions { pub fn with_pod_name(name: impl Into) -> Self { Self { pod_name: Some(name.into()), } } } #[derive(Debug, Clone, Default)] pub struct ProfileResolver { workspace_base: Option, } impl ProfileResolver { pub fn new() -> Self { Self::default() } pub fn with_workspace_base(mut self, workspace_base: impl Into) -> Self { self.workspace_base = Some(workspace_base.into()); self } pub fn resolve( &self, selector: &ProfileSelector, options: ProfileResolveOptions, ) -> Result { match selector { ProfileSelector::Path { path } => self.resolve_path( path, ProfileSource::Path { path: absolutize(path)?, }, options, ), ProfileSelector::Named { .. } | ProfileSelector::Default => { let cwd = std::env::current_dir().map_err(|source| ProfileError::CommandIo { path: PathBuf::from("."), source, })?; let registry = ProfileDiscovery::for_cwd(&cwd).discover()?; self.resolve_from_registry(selector, ®istry, options) } } } /// Resolve a registry/default selector against an already-discovered /// registry. Callers such as SpawnPod use this to bind discovery to the /// Pod's cwd instead of the process current directory. pub fn resolve_from_registry( &self, selector: &ProfileSelector, registry: &ProfileRegistry, options: ProfileResolveOptions, ) -> Result { match selector { ProfileSelector::Path { .. } => Err(ProfileError::InvalidProfile( "path selectors are not registry entries".into(), )), ProfileSelector::Named { .. } | ProfileSelector::Default => { let entry = registry.select(selector)?.clone(); let source = ProfileSource::Registry { source: entry.source, name: entry.name.clone(), path: entry.path.as_deref().map(absolutize).transpose()?, provenance: (entry.path.is_none()).then(|| entry.provenance.clone()), }; self.resolve_registry_entry(&entry, source, options) } } } fn resolve_registry_entry( &self, entry: &ProfileRegistryEntry, source: ProfileSource, options: ProfileResolveOptions, ) -> Result { match &entry.artifact { ProfileRegistryArtifact::Path(path) => self.resolve_path(path, source, options), ProfileRegistryArtifact::Embedded { label, content } => { self.resolve_embedded_profile(label, content, source, options) } } } fn resolve_path( &self, path: &Path, source: ProfileSource, options: ProfileResolveOptions, ) -> Result { let absolute_path = absolutize(path)?; let extension = absolute_path .extension() .and_then(|s| s.to_str()) .map(str::to_string); match extension.as_deref() { Some("lua") => {} other => { return Err(ProfileError::UnsupportedProfileType { path: absolute_path, message: format!( "unsupported profile extension {}; Lua profiles must end in .lua", other.map_or("".to_string(), |s| format!(".{s}")) ), }); } } let profile_dir = absolute_path .parent() .map(Path::to_path_buf) .ok_or_else(|| ProfileError::InvalidPath { path: absolute_path.clone(), message: "profile path has no parent directory".into(), })?; let profile_dir = canonicalize_existing_dir(&profile_dir)?; let workspace_base = absolutize( self.workspace_base .as_deref() .unwrap_or_else(|| Path::new(".")), )?; let workspace_override = load_workspace_override_from(&workspace_base)?; let lua_value = evaluate_lua_profile(&absolute_path, &profile_dir)?; let raw_artifact = lua_value.clone(); resolve_lua_profile_value( source, &profile_dir, &workspace_base, options, lua_value, raw_artifact, workspace_override, ) } fn resolve_embedded_profile( &self, label: &'static str, content: &'static str, source: ProfileSource, options: ProfileResolveOptions, ) -> Result { let workspace_base = absolutize( self.workspace_base .as_deref() .unwrap_or_else(|| Path::new(".")), )?; let workspace_override = load_workspace_override_from(&workspace_base)?; let lua_value = evaluate_embedded_lua_profile(label, content)?; let raw_artifact = lua_value.clone(); resolve_lua_profile_value( source, &workspace_base, &workspace_base, options, lua_value, raw_artifact, workspace_override, ) } } fn resolve_lua_profile_value( source: ProfileSource, profile_dir: &Path, workspace_base: &Path, options: ProfileResolveOptions, value: serde_json::Value, raw_artifact: serde_json::Value, workspace_override: Option, ) -> Result { if !workspace_base.is_absolute() { return Err(ProfileError::InvalidPath { path: workspace_base.to_path_buf(), message: "profile workspace base must be absolute".into(), }); } reject_manifest_shaped_profile(&value)?; let profile: ProfileConfig = serde_json::from_value(value.clone()) .map_err(|source| ProfileError::ProfileDeserialize { source })?; validate_profile_paths(&profile)?; let pod_name = options .pod_name .ok_or(ProfileError::MissingRuntimePodName)?; let profile_meta = Some(ProfileMetadata { name: profile.slug.clone().or_else(|| source_name(&source)), description: profile.description.clone(), format: Some(PROFILE_FORMAT_V1.to_string()), }); let compaction = profile_compaction_to_partial(profile.compaction, &profile.model)?; let config = PodManifestConfig { pod: PodMetaConfig { name: Some(pod_name), prompt_pack: None, }, model: profile.model.unwrap_or_default(), worker: profile.worker.unwrap_or_default(), scope: profile_scope_to_config(profile.scope, workspace_base), session: profile.session, permissions: profile.permissions, compaction, web: profile.web, memory: profile.memory, skills: profile.skills, }; let mut config = PodManifestConfig::builtin_defaults().merge(config.resolve_paths(profile_dir)); let workspace_override_snapshot = if let Some(override_layer) = workspace_override { let override_base = override_layer .path .parent() .ok_or_else(|| ProfileError::InvalidPath { path: override_layer.path.clone(), message: "workspace override path has no parent directory".into(), })?; config = config.merge(override_layer.config.resolve_paths(override_base)); Some(WorkspaceOverrideSnapshot { path: override_layer.path, }) } else { None }; let mut manifest = PodManifest::try_from(config).map_err(ProfileError::ManifestResolve)?; manifest.profile = Some(ProfileManifestSnapshot { source: source.clone(), profile: profile_meta.clone(), workspace_override: workspace_override_snapshot, }); let manifest_snapshot = serde_json::to_value(&manifest).map_err(ProfileError::SnapshotSerialize)?; Ok(ResolvedProfile { source, profile: profile_meta, manifest, manifest_snapshot, raw_artifact, }) } #[derive(Debug, Default, Deserialize)] #[serde(deny_unknown_fields)] struct ProfileConfig { #[serde(default)] slug: Option, #[serde(default)] description: Option, #[serde(default)] model: Option, #[serde(default)] worker: Option, #[serde(default)] scope: Option, #[serde(default)] session: Option, #[serde(default)] permissions: Option, #[serde(default)] compaction: Option, #[serde(default)] web: Option, #[serde(default)] memory: Option, #[serde(default)] skills: Option, } #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] enum ProfileScopeConfig { Intent { intent: ProfileScopeIntent }, String(ProfileScopeIntent), } #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "snake_case")] enum ProfileScopeIntent { WorkspaceRead, WorkspaceWrite, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct RatioCompaction { #[allow(dead_code)] kind: String, #[serde(default)] threshold: Option, #[serde(default, alias = "request")] request_threshold: Option, #[serde(default, alias = "worker")] worker_context_max_tokens: Option, } #[derive(Debug, Deserialize)] struct ProfileRegistryDocument { #[serde(default)] default: Option, #[serde(default, alias = "entries")] profile: BTreeMap, } #[derive(Debug, Deserialize)] #[serde(untagged)] enum ProfileEntryConfig { Path(String), Table { path: PathBuf, #[serde(default)] description: Option, }, } impl ProfileEntryConfig { fn into_parts(self) -> (PathBuf, Option) { match self { Self::Path(path) => (PathBuf::from(path), None), Self::Table { path, description } => (path, description), } } } fn load_profile_registry_file( registry: &mut ProfileRegistry, source: ProfileRegistrySource, path: &Path, ) -> Result<(), ProfileError> { if !path.exists() { return Ok(()); } let content = std::fs::read_to_string(path).map_err(|source| ProfileError::ConfigRead { path: path.to_path_buf(), source, })?; let config: ProfileRegistryDocument = toml::from_str(&content).map_err(|source| ProfileError::ConfigParse { path: path.to_path_buf(), source, })?; let base = path.parent().unwrap_or_else(|| Path::new(".")); for (name, entry_config) in config.profile { let (entry_path, description) = entry_config.into_parts(); registry.push_entry(ProfileRegistryEntry::path( source, name, join_if_relative(base, &entry_path), description, )); } if let Some(default) = config.default { let (default_source, default_name) = parse_profile_ref(&default); registry.set_default(ProfileDefault { source: default_source.or(Some(source)), name: default_name, }); } Ok(()) } fn load_workspace_override_from( workspace_base: &Path, ) -> Result, ProfileError> { find_workspace_override_from(workspace_base) .map(|path| load_workspace_override_file(&path)) .transpose() } fn load_workspace_override_file(path: &Path) -> Result { let content = std::fs::read_to_string(path).map_err(|source| ProfileError::WorkspaceOverrideRead { path: path.to_path_buf(), source, })?; let config = PodManifestConfig::from_toml(&content).map_err(|source| { ProfileError::WorkspaceOverrideParse { path: path.to_path_buf(), source, } })?; if config.pod.name.is_some() { return Err(ProfileError::InvalidWorkspaceOverride { path: path.to_path_buf(), message: "workspace-local manifest overrides cannot set pod.name; Pod identity is a runtime input".into(), }); } Ok(WorkspaceOverrideLayer { path: path.to_path_buf(), config, }) } fn find_workspace_override_from(start: &Path) -> Option { let start = start .canonicalize() .ok() .unwrap_or_else(|| start.to_path_buf()); let mut cur: Option<&Path> = Some(start.as_path()); while let Some(dir) = cur { let candidate = dir.join(".yoi").join(WORKSPACE_OVERRIDE_LOCAL_FILENAME); if candidate.is_file() { return Some(candidate); } cur = dir.parent(); } None } fn find_project_profiles_from(start: &Path) -> Option { let start = start .canonicalize() .ok() .unwrap_or_else(|| start.to_path_buf()); let mut cur: Option<&Path> = Some(start.as_path()); while let Some(dir) = cur { let candidate = dir.join(".yoi").join("profiles.toml"); if candidate.is_file() { return Some(candidate); } cur = dir.parent(); } None } fn add_builtin_profiles(registry: &mut ProfileRegistry) { registry.push_entry(ProfileRegistryEntry::embedded( ProfileRegistrySource::Builtin, BUILTIN_DEFAULT_PROFILE_NAME, "builtin:default", BUILTIN_DEFAULT_PROFILE, Some("Bundled default Yoi coding profile".into()), )); } fn parse_profile_ref(raw: &str) -> (Option, String) { if let Some((prefix, name)) = raw.split_once(':') && let Some(source) = ProfileRegistrySource::parse(prefix) { return (Some(source), name.to_string()); } (None, raw.to_string()) } fn evaluate_lua_profile( path: &Path, module_root: &Path, ) -> Result { let content = std::fs::read_to_string(path).map_err(|source| ProfileError::ConfigRead { path: path.to_path_buf(), source, })?; evaluate_lua_profile_source( &content, path.display().to_string(), LocalModuleRoot::Filesystem(module_root.to_path_buf()), ) } fn evaluate_embedded_lua_profile( label: &'static str, content: &'static str, ) -> Result { evaluate_lua_profile_source( content, label.to_string(), LocalModuleRoot::Disabled { label }, ) } fn evaluate_lua_profile_source( content: &str, chunk_name: String, module_root: LocalModuleRoot, ) -> Result { let lua = Lua::new_with( StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::UTF8, LuaOptions::default(), ) .map_err(ProfileError::Lua)?; install_lua_api(&lua, module_root)?; let value: LuaValue = lua .load(content) .set_name(chunk_name) .eval() .map_err(ProfileError::Lua)?; match value { LuaValue::Table(_) => lua.from_value(value).map_err(ProfileError::Lua), _ => Err(ProfileError::InvalidProfile( "Lua profile must return a table or profile { ... }".into(), )), } } fn install_lua_api(lua: &Lua, module_root: LocalModuleRoot) -> Result<(), ProfileError> { let loader = Rc::new(RefCell::new(LocalModuleLoader { root: module_root, cache: HashMap::new(), loading: HashSet::new(), })); let require_loader = Rc::clone(&loader); let require = lua .create_function(move |lua, name: String| require_module(lua, &require_loader, &name)) .map_err(ProfileError::Lua)?; let globals = lua.globals(); globals.set("require", require).map_err(ProfileError::Lua)?; globals .set( "profile", lua.create_function(|_, table: Table| Ok(table)) .map_err(ProfileError::Lua)?, ) .map_err(ProfileError::Lua)?; for denied in [ "os", "io", "debug", "package", "dofile", "loadfile", "load", "collectgarbage", ] { globals .set(denied, LuaValue::Nil) .map_err(ProfileError::Lua)?; } Ok(()) } struct LocalModuleLoader { root: LocalModuleRoot, cache: HashMap, loading: HashSet, } enum LocalModuleRoot { Filesystem(PathBuf), Disabled { label: &'static str }, } fn require_module( lua: &Lua, loader: &Rc>, name: &str, ) -> mlua::Result { if let Some(value) = host_module(lua, name)? { return Ok(value); } if name.starts_with("yoi.") || name == "yoi" { return Err(mlua::Error::RuntimeError(format!( "unknown host module `{name}`" ))); } validate_module_name(name).map_err(mlua::Error::RuntimeError)?; if let Some(key) = loader.borrow().cache.get(name) { return lua.registry_value(key); } { let mut state = loader.borrow_mut(); if !state.loading.insert(name.to_string()) { return Err(mlua::Error::RuntimeError(format!( "cyclic local require `{name}`" ))); } } let path = { let state = loader.borrow(); match &state.root { LocalModuleRoot::Filesystem(root) => { local_module_path(root, name).map_err(mlua::Error::RuntimeError)? } LocalModuleRoot::Disabled { label } => { return Err(mlua::Error::RuntimeError(format!( "local require `{name}` is not available for embedded profile `{label}`" ))); } } }; let content = std::fs::read_to_string(&path).map_err(|e| { mlua::Error::RuntimeError(format!( "failed to read local module `{name}` ({}): {e}", path.display() )) })?; let result: mlua::Result = lua .load(&content) .set_name(path.display().to_string()) .eval(); loader.borrow_mut().loading.remove(name); let value = result?; let key = lua.create_registry_value(value.clone())?; loader.borrow_mut().cache.insert(name.to_string(), key); Ok(value) } fn host_module(lua: &Lua, name: &str) -> mlua::Result> { match name { "yoi" => { let t = lua.create_table()?; t.set("profile", profile_function(lua)?)?; t.set("models", models_module(lua)?)?; t.set("compact", compact_module(lua)?)?; t.set("scope", scope_module(lua)?)?; Ok(Some(LuaValue::Table(t))) } "yoi.profile" => Ok(Some(LuaValue::Function(profile_function(lua)?))), "yoi.models" => Ok(Some(LuaValue::Table(models_module(lua)?))), "yoi.compact" => Ok(Some(LuaValue::Table(compact_module(lua)?))), "yoi.scope" => Ok(Some(LuaValue::Table(scope_module(lua)?))), _ => Ok(None), } } fn profile_function(lua: &Lua) -> mlua::Result { lua.create_function(|_, table: Table| Ok(table)) } fn models_module(lua: &Lua) -> mlua::Result { let t = lua.create_table()?; t.set( "catalog", lua.create_function(|lua, reference: String| { let model = lua.create_table()?; model.set("ref", reference)?; Ok(model) })?, )?; Ok(t) } fn compact_module(lua: &Lua) -> mlua::Result
{ let t = lua.create_table()?; t.set( "ratio", lua.create_function(|_, table: Table| { table.set("kind", "ratio")?; Ok(table) })?, )?; t.set( "tokens", lua.create_function(|_, table: Table| { table.set("kind", "tokens")?; Ok(table) })?, )?; Ok(t) } fn scope_module(lua: &Lua) -> mlua::Result
{ let t = lua.create_table()?; t.set( "workspace_write", lua.create_function(|lua, ()| { let v = lua.create_table()?; v.set("intent", "workspace_write")?; Ok(v) })?, )?; t.set( "workspace_read", lua.create_function(|lua, ()| { let v = lua.create_table()?; v.set("intent", "workspace_read")?; Ok(v) })?, )?; Ok(t) } fn validate_module_name(name: &str) -> Result<(), String> { if name.is_empty() { return Err("empty module name".into()); } for part in name.split('.') { let mut chars = part.chars(); let Some(first) = chars.next() else { return Err(format!("invalid local module name `{name}`")); }; if !(first == '_' || first.is_ascii_alphabetic()) || !chars.all(|c| c == '_' || c.is_ascii_alphanumeric()) { return Err(format!("invalid local module name `{name}`")); } } Ok(()) } fn local_module_path(root: &Path, name: &str) -> Result { let mut path = root.to_path_buf(); for part in name.split('.') { path.push(part); } path.set_extension("lua"); let canonical = path .canonicalize() .map_err(|e| format!("local module `{name}` not found: {e}"))?; if !canonical.starts_with(root) { return Err(format!("local module `{name}` escapes profile directory")); } Ok(canonical) } fn reject_manifest_shaped_profile(value: &serde_json::Value) -> Result<(), ProfileError> { let Some(map) = value.as_object() else { return Err(ProfileError::InvalidProfile( "Lua profile must return an object/table".into(), )); }; for key in ["manifest", "config"] { if map.contains_key(key) { return Err(ProfileError::InvalidProfile(format!( "field `{key}` is a complete Manifest artifact boundary and is not allowed in reusable Profiles; use --manifest for complete Manifests" ))); } } if map.contains_key("pod") { return Err(ProfileError::InvalidProfile("field `pod` is runtime-bound and is not allowed in reusable Profiles; pass the Pod name via CLI/TUI runtime inputs".into())); } if let Some(scope) = map.get("scope").and_then(|v| v.as_object()) { for key in ["allow", "deny"] { if scope.contains_key(key) { return Err(ProfileError::InvalidProfile(format!( "field `scope.{key}` grants concrete authority and is not allowed in reusable Profiles; use require(\"yoi.scope\") intent helpers" ))); } } } Ok(()) } fn validate_profile_paths(profile: &ProfileConfig) -> Result<(), ProfileError> { if let Some(model) = &profile.model { reject_absolute_auth_file(&model.auth, "model.auth.file")?; } if let Some(compaction) = &profile.compaction && let Some(model) = compaction.get("model") { let model: ModelManifest = serde_json::from_value(model.clone()) .map_err(|source| ProfileError::ProfileDeserialize { source })?; reject_absolute_auth_file(&model.auth, "compaction.model.auth.file")?; } if let Some(memory) = &profile.memory && let Some(root) = &memory.workspace_root && root.is_absolute() { return Err(ProfileError::InvalidProfile("field `memory.workspace_root` is a resolved path and is not allowed in reusable Profiles".into())); } if let Some(skills) = &profile.skills { for dir in &skills.directories { if dir.is_absolute() { return Err(ProfileError::InvalidProfile( "field `skills.directories` must be profile-relative in reusable Profiles" .into(), )); } } } Ok(()) } fn reject_absolute_auth_file( auth: &Option, field: &'static str, ) -> Result<(), ProfileError> { if let Some(AuthRef::ApiKey { file: Some(file) }) = auth && file.is_absolute() { return Err(ProfileError::InvalidProfile(format!( "field `{field}` is a resolved path and is not allowed in reusable Profiles" ))); } Ok(()) } fn profile_scope_to_config( scope: Option, workspace_base: &Path, ) -> ScopeConfig { let intent = match scope { Some(ProfileScopeConfig::Intent { intent }) | Some(ProfileScopeConfig::String(intent)) => { intent } None => ProfileScopeIntent::WorkspaceWrite, }; let permission = match intent { ProfileScopeIntent::WorkspaceRead => Permission::Read, ProfileScopeIntent::WorkspaceWrite => Permission::Write, }; ScopeConfig { allow: vec![ScopeRule { target: workspace_base.to_path_buf(), permission, recursive: true, }], deny: Vec::new(), } } fn profile_compaction_to_partial( value: Option, model: &Option, ) -> Result, ProfileError> { let Some(value) = value else { return Ok(None); }; let Some(kind) = value.get("kind").and_then(|v| v.as_str()) else { return serde_json::from_value(value) .map(Some) .map_err(|source| ProfileError::ProfileDeserialize { source }); }; match kind { "tokens" => { let mut obj = value.as_object().cloned().unwrap_or_default(); obj.remove("kind"); serde_json::from_value(serde_json::Value::Object(obj)) .map(Some) .map_err(|source| ProfileError::ProfileDeserialize { source }) } "ratio" => { let ratio: RatioCompaction = serde_json::from_value(value) .map_err(|source| ProfileError::ProfileDeserialize { source })?; let context = model_context_window(model.as_ref()).ok_or_else(|| ProfileError::InvalidProfile("compact.ratio requires model.context_window/max_context_window or a known model ref; use compact.tokens for explicit token values".into()))?; Ok(Some(CompactionConfigPartial { threshold: ratio.threshold.map(|r| ratio_tokens(context, r)), request_threshold: ratio.request_threshold.map(|r| ratio_tokens(context, r)), worker_context_max_tokens: ratio .worker_context_max_tokens .map(|r| ratio_tokens(context, r)), ..Default::default() })) } other => Err(ProfileError::InvalidProfile(format!( "unknown compaction helper kind `{other}`" ))), } } fn ratio_tokens(context: u64, ratio: f64) -> u64 { ((context as f64) * ratio).floor() as u64 } fn model_context_window(model: Option<&ModelManifest>) -> Option { let model = model?; if let Some(max) = model.max_context_window { return Some(model.context_window.map_or(max, |ctx| ctx.min(max))); } if let Some(ctx) = model.context_window { return Some(ctx); } builtin_model_context_window(model.ref_.as_deref()?) } fn builtin_model_context_window(reference: &str) -> Option { let (provider, model_id) = reference.split_once('/')?; let parsed: toml::Value = toml::from_str(BUILTIN_MODEL_CATALOG).ok()?; for entry in parsed.get("model")?.as_array()? { let table = entry.as_table()?; if table.get("provider")?.as_str()? == provider && table.get("id")?.as_str()? == model_id { let context = table.get("context_window")?.as_integer()? as u64; let max = table .get("max_context_window") .and_then(|v| v.as_integer()) .map(|v| v as u64); return Some(max.map_or(context, |max| context.min(max))); } } None } fn source_name(source: &ProfileSource) -> Option { match source { ProfileSource::Path { path } => path .file_stem() .and_then(|s| s.to_str()) .map(str::to_string), ProfileSource::Registry { name, .. } => Some(name.clone()), } } fn canonicalize_existing_dir(path: &Path) -> Result { path.canonicalize() .map_err(|source| ProfileError::CommandIo { path: path.to_path_buf(), source, }) } fn absolutize(path: &Path) -> Result { if path.is_absolute() { Ok(path.to_path_buf()) } else { Ok(std::env::current_dir() .map_err(|source| ProfileError::CommandIo { path: PathBuf::from("."), source, })? .join(path)) } } fn join_if_relative(base: &Path, path: &Path) -> PathBuf { if path.is_absolute() { path.to_path_buf() } else { base.join(path) } } pub fn resolve_profile_artifact( source: ProfileSource, base_dir: &Path, raw_artifact: serde_json::Value, ) -> Result { resolve_lua_profile_value( source, base_dir, base_dir, ProfileResolveOptions::with_pod_name("artifact-pod"), raw_artifact.clone(), raw_artifact, None, ) } #[derive(Debug, thiserror::Error)] pub enum ProfileError { #[error("invalid profile path {}: {message}", .path.display())] InvalidPath { path: PathBuf, message: String }, #[error("unsupported profile type {}: {message}", .path.display())] UnsupportedProfileType { path: PathBuf, message: String }, #[error("failed to access profile path {}: {source}", .path.display())] CommandIo { path: PathBuf, #[source] source: std::io::Error, }, #[error("failed to read profile registry config {}: {source}", .path.display())] ConfigRead { path: PathBuf, #[source] source: std::io::Error, }, #[error("failed to parse profile registry config {}: {source}", .path.display())] ConfigParse { path: PathBuf, #[source] source: toml::de::Error, }, #[error("failed to read workspace local manifest override {}: {source}", .path.display())] WorkspaceOverrideRead { path: PathBuf, #[source] source: std::io::Error, }, #[error("failed to parse workspace local manifest override {}: {source}", .path.display())] WorkspaceOverrideParse { path: PathBuf, #[source] source: toml::de::Error, }, #[error("invalid workspace local manifest override {}: {message}", .path.display())] InvalidWorkspaceOverride { path: PathBuf, message: String }, #[error("no default profile is configured")] NoDefaultProfile, #[error("profile resolution requires an explicit runtime Pod name")] MissingRuntimePodName, #[error("profile not found: {selector}")] ProfileNotFound { selector: String }, #[error("ambiguous profile name `{name}`; use a source-qualified selector such as {matches:?}")] AmbiguousProfileName { name: String, matches: Vec }, #[error("failed to evaluate Lua profile: {0}")] Lua(#[source] mlua::Error), #[error("invalid Lua profile: {0}")] InvalidProfile(String), #[error("failed to decode Profile: {source}")] ProfileDeserialize { #[source] source: serde_json::Error, }, #[error("failed to resolve Profile into Manifest: {0}")] ManifestResolve(#[source] ResolveError), #[error("failed to serialize resolved manifest snapshot: {0}")] SnapshotSerialize(#[source] serde_json::Error), } #[cfg(test)] mod tests { use super::*; use crate::{ReasoningControl, ReasoningEffort, SchemeKind}; use tempfile::TempDir; fn write_profile(dir: &Path, name: &str, body: &str) -> PathBuf { let path = dir.join(name); std::fs::write(&path, body).unwrap(); path } #[test] fn parse_cli_preserves_paths_and_source_qualified_names() { assert!(matches!( ProfileSelector::parse_cli("./coder.lua"), ProfileSelector::Path { .. } )); assert_eq!( ProfileSelector::parse_cli("project:coder"), ProfileSelector::source_named(ProfileRegistrySource::Project, "coder") ); assert_eq!( ProfileSelector::parse_cli("coder"), ProfileSelector::named("coder") ); assert_eq!( ProfileSelector::parse_cli("default"), ProfileSelector::Default ); } #[test] fn builtin_default_profile_is_registered_as_default() { let registry = ProfileDiscovery::with_sources(None, None) .discover() .unwrap(); let default = registry.default_entry().unwrap(); assert_eq!(default.source, ProfileRegistrySource::Builtin); assert_eq!(default.name, BUILTIN_DEFAULT_PROFILE_NAME); assert!(default.is_default); assert_eq!(default.path, None); assert_eq!(default.provenance, "builtin:default"); } #[test] fn profile_resolution_requires_runtime_pod_name() { let tmp = TempDir::new().unwrap(); let err = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve(&ProfileSelector::Default, ProfileResolveOptions::default()) .unwrap_err(); assert!(matches!(err, ProfileError::MissingRuntimePodName)); } #[test] fn resolves_plain_lua_profile_with_runtime_pod_name_and_scope_intent() { let tmp = TempDir::new().unwrap(); let profile = write_profile( tmp.path(), "coder.lua", r#" local profile = require("yoi.profile") local scope = require("yoi.scope") return profile { slug = "coder", model = { scheme = "anthropic", model_id = "claude-sonnet-4-20250514" }, worker = { reasoning = "high" }, scope = scope.workspace_read(), } "#, ); let workspace = tmp.path().join("workspace"); std::fs::create_dir(&workspace).unwrap(); let resolved = ProfileResolver::new() .with_workspace_base(&workspace) .resolve( &ProfileSelector::path(&profile), ProfileResolveOptions::with_pod_name("runtime-pod"), ) .unwrap(); assert_eq!(resolved.manifest.pod.name, "runtime-pod"); assert_eq!(resolved.manifest.model.scheme, Some(SchemeKind::Anthropic)); assert_eq!( resolved.manifest.worker.reasoning, Some(ReasoningControl::Effort(ReasoningEffort::High)) ); assert_eq!(resolved.manifest.scope.allow[0].target, workspace); assert_eq!( resolved.manifest.scope.allow[0].permission, Permission::Read ); assert_eq!( resolved.profile.as_ref().unwrap().name.as_deref(), Some("coder") ); } #[test] fn host_modules_and_local_require_work() { let tmp = TempDir::new().unwrap(); std::fs::write( tmp.path().join("shared.lua"), r#"return { model = require("yoi.models").catalog("codex-oauth/gpt-5.5") }"#, ) .unwrap(); let profile = write_profile( tmp.path(), "main.lua", r#" local yoi = require("yoi") local shared = require("shared") return yoi.profile { slug = "main", model = shared.model, scope = yoi.scope.workspace_write(), } "#, ); let resolved = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::path(profile), ProfileResolveOptions::with_pod_name("p"), ) .unwrap(); assert_eq!( resolved.manifest.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5") ); assert_eq!( resolved.manifest.scope.allow[0].permission, Permission::Write ); } #[test] fn sandbox_denies_unsafe_libraries() { let tmp = TempDir::new().unwrap(); for (name, body) in [ ("os.lua", "return os.getenv('HOME')"), ("io.lua", "return io.open('x')"), ("debug.lua", "return debug.getinfo(1)"), ("package.lua", "return package.path"), ] { let path = write_profile(tmp.path(), name, body); let err = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::path(path), ProfileResolveOptions::with_pod_name("p"), ) .unwrap_err(); assert!(matches!( err, ProfileError::Lua(_) | ProfileError::InvalidProfile(_) )); } } #[test] fn rejects_manifest_shaped_runtime_and_authority_fields() { for (value, needle) in [ (serde_json::json!({"manifest": {}}), "manifest"), (serde_json::json!({"config": {}}), "config"), (serde_json::json!({"pod": {"name": "bad"}}), "pod"), ( serde_json::json!({"model": {"ref": "codex-oauth/gpt-5.5"}, "scope": {"allow": []}}), "scope.allow", ), ( serde_json::json!({"model": {"ref": "codex-oauth/gpt-5.5"}, "scope": {"deny": []}}), "scope.deny", ), ] { let err = resolve_profile_artifact( ProfileSource::Path { path: PathBuf::from("/profiles/bad.lua"), }, Path::new("/workspace"), value, ) .unwrap_err(); assert!(err.to_string().contains(needle), "unexpected error: {err}"); } } #[test] fn rejects_absolute_profile_paths() { let err = resolve_profile_artifact(ProfileSource::Path { path: PathBuf::from("/profiles/bad.lua") }, Path::new("/workspace"), serde_json::json!({"model": { "scheme": "anthropic", "model_id": "m", "auth": {"kind":"api_key", "file":"/secret/key"} }, "scope": "workspace_write"})).unwrap_err(); assert!(err.to_string().contains("model.auth.file")); } #[test] fn compact_ratio_uses_known_model_context() { let tmp = TempDir::new().unwrap(); let profile = write_profile( tmp.path(), "ratio.lua", r#" local profile = require("yoi.profile") local models = require("yoi.models") local compact = require("yoi.compact") return profile { model = models.catalog("codex-oauth/gpt-5.5"), compaction = compact.ratio { threshold = 0.5, request = 0.75, worker = 0.25 }, } "#, ); let resolved = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::path(profile), ProfileResolveOptions::with_pod_name("p"), ) .unwrap(); let c = resolved.manifest.compaction.unwrap(); assert_eq!(c.threshold, Some(136000)); assert_eq!(c.request_threshold, Some(204000)); assert_eq!(c.worker_context_max_tokens, 68000); } #[test] fn builtin_default_resolves_without_external_evaluator() { let tmp = TempDir::new().unwrap(); let resolved = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::source_named(ProfileRegistrySource::Builtin, "default"), ProfileResolveOptions::with_pod_name("runtime-workspace"), ) .unwrap(); assert_eq!(resolved.manifest.pod.name, "runtime-workspace"); assert_eq!( resolved.manifest.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5") ); assert_eq!(resolved.manifest.scope.allow[0].target, tmp.path()); assert_eq!( resolved.manifest.scope.allow[0].permission, Permission::Write ); assert!(resolved.manifest.session.record_event_trace); assert_eq!( resolved.profile.as_ref().unwrap().name.as_deref(), Some("default") ); assert_eq!( resolved.source, ProfileSource::Registry { source: ProfileRegistrySource::Builtin, name: "default".into(), path: None, provenance: Some("builtin:default".into()), } ); } #[test] fn workspace_local_override_layers_over_profile_defaults() { let tmp = TempDir::new().unwrap(); let workspace = tmp.path().join("project"); let nested = workspace.join("nested"); let yoi_dir = workspace.join(".yoi"); std::fs::create_dir_all(&nested).unwrap(); std::fs::create_dir_all(&yoi_dir).unwrap(); let override_path = yoi_dir.join(WORKSPACE_OVERRIDE_LOCAL_FILENAME); std::fs::write( &override_path, r#" [pod] prompt_pack = "prompts.toml" [worker] language = "ja" [session] record_event_trace = false "#, ) .unwrap(); let resolved = ProfileResolver::new() .with_workspace_base(&nested) .resolve( &ProfileSelector::Default, ProfileResolveOptions::with_pod_name("runtime-pod"), ) .unwrap(); assert_eq!(resolved.manifest.pod.name, "runtime-pod"); assert_eq!(resolved.manifest.worker.language, "ja"); assert!(!resolved.manifest.session.record_event_trace); assert_eq!( resolved.manifest.pod.prompt_pack.as_deref(), Some(yoi_dir.join("prompts.toml").as_path()) ); assert_eq!(resolved.manifest.scope.allow[0].target, nested); assert_eq!( resolved .manifest .profile .as_ref() .and_then(|snapshot| snapshot.workspace_override.as_ref()) .map(|snapshot| snapshot.path.as_path()), Some(override_path.as_path()) ); } #[test] fn workspace_local_override_uses_nearest_ancestor() { let tmp = TempDir::new().unwrap(); let workspace = tmp.path().join("project"); let nested = workspace.join("nested"); let child = nested.join("child"); let parent_yoi = workspace.join(".yoi"); let nested_yoi = nested.join(".yoi"); std::fs::create_dir_all(&child).unwrap(); std::fs::create_dir_all(&parent_yoi).unwrap(); std::fs::create_dir_all(&nested_yoi).unwrap(); std::fs::write( parent_yoi.join(WORKSPACE_OVERRIDE_LOCAL_FILENAME), r#" [pod] prompt_pack = "parent-prompts.toml" [worker] language = "parent" "#, ) .unwrap(); let nested_override_path = nested_yoi.join(WORKSPACE_OVERRIDE_LOCAL_FILENAME); std::fs::write( &nested_override_path, r#" [pod] prompt_pack = "nested-prompts.toml" [worker] language = "nested" "#, ) .unwrap(); let resolved = ProfileResolver::new() .with_workspace_base(&child) .resolve( &ProfileSelector::Default, ProfileResolveOptions::with_pod_name("runtime-pod"), ) .unwrap(); assert_eq!(resolved.manifest.worker.language, "nested"); assert_eq!( resolved.manifest.pod.prompt_pack.as_deref(), Some(nested_yoi.join("nested-prompts.toml").as_path()) ); assert_eq!( resolved .manifest .profile .as_ref() .and_then(|snapshot| snapshot.workspace_override.as_ref()) .map(|snapshot| snapshot.path.as_path()), Some(nested_override_path.as_path()) ); } #[test] fn workspace_local_override_rejects_runtime_pod_name() { let tmp = TempDir::new().unwrap(); let yoi_dir = tmp.path().join(".yoi"); std::fs::create_dir_all(&yoi_dir).unwrap(); std::fs::write( yoi_dir.join(WORKSPACE_OVERRIDE_LOCAL_FILENAME), "[pod]\nname = \"not-local\"\n", ) .unwrap(); let err = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::Default, ProfileResolveOptions::with_pod_name("runtime-pod"), ) .unwrap_err(); assert!(matches!(err, ProfileError::InvalidWorkspaceOverride { .. })); assert!(err.to_string().contains("pod.name")); } #[test] fn unsupported_profile_extension_has_clear_diagnostic() { let tmp = TempDir::new().unwrap(); let path = write_profile(tmp.path(), "legacy.txt", "{}"); let err = ProfileResolver::new() .with_workspace_base(tmp.path()) .resolve( &ProfileSelector::path(path), ProfileResolveOptions::default(), ) .unwrap_err(); assert!(matches!(err, ProfileError::UnsupportedProfileType { .. })); assert!(err.to_string().contains("Lua profiles must end in .lua")); } #[test] fn discovery_reads_user_and_project_registry_and_project_default_wins() { let tmp = TempDir::new().unwrap(); let user_config = tmp.path().join("profiles.toml"); let project_dir = tmp.path().join("project/.yoi"); std::fs::create_dir_all(&project_dir).unwrap(); let project_config = project_dir.join("profiles.toml"); std::fs::write( &user_config, "default = \"coder\"\n[profile]\ncoder = \"profiles/user-coder.lua\"\n", ) .unwrap(); std::fs::write(&project_config, "default = \"project:coder\"\n[profile.coder]\npath = \"profiles/project-coder.lua\"\ndescription = \"Project coder\"\n").unwrap(); let registry = ProfileDiscovery::with_sources(Some(user_config), Some(project_config)) .discover() .unwrap(); let default = registry.default_entry().unwrap(); assert_eq!(default.source, ProfileRegistrySource::Project); assert_eq!(default.name, "coder"); assert!( default .path .as_ref() .unwrap() .ends_with("profiles/project-coder.lua") ); } #[test] fn default_marks_direct_profile_entry() { let tmp = TempDir::new().unwrap(); let project_dir = tmp.path().join("project/.yoi"); std::fs::create_dir_all(&project_dir).unwrap(); let project_config = project_dir.join("profiles.toml"); std::fs::write( &project_config, "default = \"coder\"\n[profile]\ncoder = \"profiles/coder.lua\"\n", ) .unwrap(); let registry = ProfileDiscovery::with_sources(None, Some(project_config)) .discover() .unwrap(); let default = registry.default_entry().unwrap(); assert_eq!(default.source, ProfileRegistrySource::Project); assert_eq!(default.name, "coder"); assert!(default.is_default); assert_eq!( registry .entries() .iter() .filter(|entry| entry.is_default) .count(), 1 ); } #[test] fn unqualified_ambiguous_names_fail_closed() { let mut registry = ProfileRegistry::default(); registry.push_entry(ProfileRegistryEntry::path( ProfileRegistrySource::User, "coder".to_string(), PathBuf::from("/user/coder.lua"), None, )); registry.push_entry(ProfileRegistryEntry::path( ProfileRegistrySource::Project, "coder".to_string(), PathBuf::from("/project/coder.lua"), None, )); let err = registry .select(&ProfileSelector::named("coder")) .unwrap_err(); assert!(matches!(err, ProfileError::AmbiguousProfileName { .. })); let selected = registry .select(&ProfileSelector::source_named( ProfileRegistrySource::Project, "coder", )) .unwrap(); assert_eq!( selected.path.as_deref(), Some(Path::new("/project/coder.lua")) ); } }