feat: profile解決とmodel catalogを更新

This commit is contained in:
2026-07-30 22:12:25 +09:00
parent d94ef81b43
commit 3e217adc14
34 changed files with 421 additions and 347 deletions
+86 -92
View File
@@ -234,7 +234,6 @@ pub struct WorkerSummary {
pub display_name: String,
/// Backward-compatible display label. New UI should prefer `display_name`.
pub label: String,
pub role: Option<String>,
pub profile: Option<String>,
pub singleton_key: Option<String>,
#[serde(default)]
@@ -274,8 +273,7 @@ impl<T> RuntimeList<T> {
}
fn is_retired_companion_worker(worker: &WorkerSummary) -> bool {
worker.role.as_deref() == Some("builtin:companion")
|| worker.profile.as_deref() == Some("builtin:companion")
worker.profile.as_deref() == Some("builtin:companion")
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -321,8 +319,7 @@ pub struct WorkerSpawnRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub requested_worker_name: Option<String>,
pub acceptance: WorkerSpawnAcceptanceRequirement,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile: Option<ProfileSelector>,
pub profile: ProfileSelector,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_input: Option<EmbeddedWorkerInput>,
/// Optional safe working-directory creation request. The Workspace server resolves
@@ -1405,7 +1402,6 @@ impl EmbeddedWorkerRuntime {
host_id: self.host_id.clone(),
display_name: display.display_name.clone(),
label: display.display_name,
role: profile.clone(),
profile,
singleton_key: display.singleton_key,
tags: display.tags,
@@ -1445,7 +1441,6 @@ impl EmbeddedWorkerRuntime {
host_id: self.host_id.clone(),
display_name: display.display_name.clone(),
label: display.display_name,
role: profile.clone(),
profile,
singleton_key: display.singleton_key,
tags: display.tags,
@@ -1705,11 +1700,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
));
}
let profile = request
.profile
.clone()
.unwrap_or_else(|| embedded_profile_selector(&request.intent));
let profile_source = match default_profile_source_archive_source(&profile) {
let profile = request.profile.clone();
let profile_source = match profile_source_archive_source(&request, &profile) {
Ok(source) => source,
Err(error) => {
diagnostics.push(diagnostic(
@@ -2364,7 +2356,6 @@ impl RemoteWorkerRuntime {
host_id: self.host_id.clone(),
display_name: display.display_name.clone(),
label: display.display_name,
role: profile.clone(),
profile,
singleton_key: display.singleton_key,
tags: display.tags,
@@ -2408,7 +2399,6 @@ impl RemoteWorkerRuntime {
host_id: self.host_id.clone(),
display_name: display.display_name.clone(),
label: display.display_name,
role: profile.clone(),
profile,
singleton_key: display.singleton_key,
tags: display.tags,
@@ -2678,11 +2668,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
)],
};
}
let profile = request
.profile
.clone()
.unwrap_or_else(|| embedded_profile_selector(&request.intent));
let profile_source = match default_profile_source_archive_http_source(
let profile = request.profile.clone();
let profile_source = match profile_source_archive_http_source(
&request,
&profile,
&self.workspace_id,
Some(self.runtime_id.as_str()),
@@ -2953,22 +2941,38 @@ fn embedded_worker_projection_diagnostics() -> Vec<RuntimeDiagnostic> {
)]
}
fn default_profile_source_archive_source(
fn profile_source_archive_for_request(
request: &WorkerSpawnRequest,
profile: &ProfileSelector,
) -> Result<ProfileSourceArchive, String> {
if let Some(archive) = request
.resolved_config_bundle
.as_ref()
.and_then(|bundle| bundle.profile_source_archive.clone())
{
return Ok(archive);
}
builtin_profile_source_archive(profile)
}
fn profile_source_archive_source(
request: &WorkerSpawnRequest,
profile: &ProfileSelector,
) -> Result<ProfileSourceArchiveSource, String> {
Ok(ProfileSourceArchiveSource::Embedded {
archive: default_profile_source_archive(profile)?,
archive: profile_source_archive_for_request(request, profile)?,
})
}
fn default_profile_source_archive_http_source(
fn profile_source_archive_http_source(
request: &WorkerSpawnRequest,
profile: &ProfileSelector,
workspace_id: &str,
runtime_id: Option<&str>,
resource_broker: &BackendResourceBroker,
backend_base_url: &str,
) -> Result<ProfileSourceArchiveSource, String> {
let archive = default_profile_source_archive(profile)?;
let archive = profile_source_archive_for_request(request, profile)?;
let _handle = resource_broker.issue_profile_source_archive_handle(
workspace_id.to_string(),
runtime_id,
@@ -2999,7 +3003,7 @@ enum ProfileSourceArchiveTransport {
}
#[cfg(test)]
fn default_embedded_config_bundle(
fn builtin_profile_config_bundle(
profile: &ProfileSelector,
workspace_id: &str,
runtime_id: Option<&str>,
@@ -3012,7 +3016,7 @@ fn default_embedded_config_bundle(
.unwrap_or_else(|| "default".to_string())
.replace([':', '/', ' '], "-")
);
let archive = default_profile_source_archive(profile)?;
let archive = builtin_profile_source_archive(profile)?;
let (profile_source_archive, profile_source_archive_handle) = match archive_transport {
ProfileSourceArchiveTransport::Inline => (Some(archive), None),
ProfileSourceArchiveTransport::BackendResourceHandle => {
@@ -3048,17 +3052,13 @@ fn default_embedded_config_bundle(
.with_computed_digest())
}
fn default_profile_source_archive(
fn builtin_profile_source_archive(
profile: &ProfileSelector,
) -> Result<ProfileSourceArchive, String> {
let selected = embedded_profile_label(profile).unwrap_or_else(|| "default".to_string());
let selected = embedded_profile_label(profile)
.ok_or_else(|| "profile selector must identify a concrete profile".to_string())?;
let selected_path = embedded_profile_path(profile)?;
let mut entrypoints = BTreeMap::new();
entrypoints.insert("default".to_string(), "profiles/default.dcdl".to_string());
entrypoints.insert(
"builtin:default".to_string(),
"profiles/default.dcdl".to_string(),
);
for slug in [
"companion",
"intake",
@@ -3073,8 +3073,8 @@ fn default_profile_source_archive(
let mut sources = BTreeMap::new();
sources.insert(
"profiles/default.dcdl".to_string(),
include_str!("../../../resources/profiles/default.dcdl").to_string(),
"profiles/base.dcdl".to_string(),
include_str!("../../../resources/profiles/base.dcdl").to_string(),
);
sources.insert(
"profiles/companion.dcdl".to_string(),
@@ -3111,8 +3111,8 @@ fn default_profile_source_archive(
"memory-consolidation",
] {
imports.insert(
format!("profiles/{slug}.dcdl\0./default.dcdl"),
"profiles/default.dcdl".to_string(),
format!("profiles/{slug}.dcdl\0./base.dcdl"),
"profiles/base.dcdl".to_string(),
);
}
@@ -3127,9 +3127,7 @@ fn default_profile_source_archive(
fn embedded_profile_path(profile: &ProfileSelector) -> Result<String, String> {
match profile {
ProfileSelector::RuntimeDefault => Ok("profiles/default.dcdl".to_string()),
ProfileSelector::Builtin(name) => match name.strip_prefix("builtin:").unwrap_or(name) {
"default" => Ok("profiles/default.dcdl".to_string()),
"companion" => Ok("profiles/companion.dcdl".to_string()),
"intake" => Ok("profiles/intake.dcdl".to_string()),
"orchestrator" => Ok("profiles/orchestrator.dcdl".to_string()),
@@ -3142,31 +3140,8 @@ fn embedded_profile_path(profile: &ProfileSelector) -> Result<String, String> {
}
}
fn embedded_profile_selector(intent: &WorkerSpawnIntent) -> ProfileSelector {
match intent {
WorkerSpawnIntent::TicketRole { role, .. } => {
ProfileSelector::Builtin(format!("builtin:{}", ticket_role_profile_slug(role)))
}
WorkerSpawnIntent::WorkspaceCompanion => {
ProfileSelector::Builtin("builtin:companion".to_string())
}
WorkerSpawnIntent::WorkspaceOrchestrator => ProfileSelector::RuntimeDefault,
WorkerSpawnIntent::WorkspaceCoding => ProfileSelector::Builtin("builtin:coder".to_string()),
}
}
fn ticket_role_profile_slug(role: &TicketWorkerRole) -> &'static str {
match role {
TicketWorkerRole::Intake => "intake",
TicketWorkerRole::Orchestrator => "orchestrator",
TicketWorkerRole::Coder => "coder",
TicketWorkerRole::Reviewer => "reviewer",
}
}
fn embedded_profile_label(profile: &ProfileSelector) -> Option<String> {
Some(match profile {
ProfileSelector::RuntimeDefault => "runtime_default".to_string(),
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => {
if name.strip_prefix("builtin:").unwrap_or(name) == MEMORY_CONSOLIDATION_PROFILE {
MEMORY_CONSOLIDATION_PROFILE.to_string()
@@ -3227,21 +3202,18 @@ fn worker_display_metadata(
}
fn profile_display_name(profile_label: &str) -> String {
match profile_label {
"runtime_default" => "Default Worker".to_string(),
value => value
.split(['-', '_'])
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" "),
}
profile_label
.split(['-', '_'])
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
fn embedded_input_rejected(
@@ -3632,7 +3604,6 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
host_id,
display_name: "Worker runtime actions are not implemented".to_string(),
label: "Worker runtime actions are not implemented".to_string(),
role: None,
profile: None,
singleton_key: None,
tags: Vec::new(),
@@ -3690,7 +3661,6 @@ mod tests {
let broker = BackendResourceBroker::default();
let runtime_id = "runtime-test";
for selector in [
ProfileSelector::RuntimeDefault,
ProfileSelector::Builtin("builtin:companion".to_string()),
ProfileSelector::Builtin("builtin:intake".to_string()),
ProfileSelector::Builtin("builtin:orchestrator".to_string()),
@@ -3698,7 +3668,7 @@ mod tests {
ProfileSelector::Builtin("builtin:reviewer".to_string()),
ProfileSelector::Builtin("builtin:memory-consolidation".to_string()),
] {
let bundle = default_embedded_config_bundle(
let bundle = builtin_profile_config_bundle(
&selector,
"workspace-test",
Some(runtime_id),
@@ -3724,9 +3694,7 @@ mod tests {
.verify()
.unwrap();
let selector_key = match &selector {
ProfileSelector::RuntimeDefault => "default".to_string(),
ProfileSelector::Builtin(name) => name.clone(),
ProfileSelector::Named(name) => name.clone(),
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => name.clone(),
};
let manifest = archive
.resolve_profile(&selector_key, root.path(), "embedded-test-worker")
@@ -3745,7 +3713,7 @@ mod tests {
let root = tempfile::tempdir().unwrap();
let broker = BackendResourceBroker::default();
let runtime_id = "remote:test";
let bundle = default_embedded_config_bundle(
let bundle = builtin_profile_config_bundle(
&ProfileSelector::Builtin("builtin:coder".to_string()),
"workspace-test",
Some(runtime_id),
@@ -3757,7 +3725,7 @@ mod tests {
let archive = bundle
.profile_source_archive
.as_ref()
.expect("remote default bundle carries inline profile archive")
.expect("remote built-in bundle carries inline profile archive")
.verify()
.unwrap();
let manifest = archive
@@ -3766,11 +3734,38 @@ mod tests {
assert_eq!(manifest.worker.name, "remote-test-worker");
}
#[test]
fn resolved_project_profile_archive_is_used_for_runtime_delivery() {
let broker = BackendResourceBroker::default();
let builtin_selector = ProfileSelector::Builtin("builtin:coder".to_string());
let archive = builtin_profile_source_archive(&builtin_selector)
.expect("build stand-in project profile archive");
let mut bundle = builtin_profile_config_bundle(
&builtin_selector,
"workspace-test",
Some("runtime-test"),
&broker,
ProfileSourceArchiveTransport::Inline,
)
.expect("build project profile bundle");
bundle.profile_source_archive = Some(archive.clone());
let mut request = embedded_spawn_request();
request.profile = ProfileSelector::Named("project:custom".to_string());
request.resolved_config_bundle = Some(bundle);
let delivered = profile_source_archive_for_request(&request, &request.profile)
.expect("resolve project profile archive");
assert_eq!(delivered.reference, archive.reference);
assert_eq!(delivered.content, archive.content);
}
#[test]
fn remote_profile_source_archive_url_uses_workspace_id_not_host_id() {
let broker = BackendResourceBroker::default();
let runtime_id = "remote:test";
let source = default_profile_source_archive_http_source(
let request = embedded_spawn_request();
let source = profile_source_archive_http_source(
&request,
&ProfileSelector::Builtin("builtin:coder".to_string()),
"workspace-actual",
Some(runtime_id),
@@ -3796,7 +3791,7 @@ mod tests {
let broker = BackendResourceBroker::default();
let runtime_id = "runtime-test";
assert!(
default_embedded_config_bundle(
builtin_profile_config_bundle(
&ProfileSelector::Builtin("builtin:missing".to_string()),
"workspace-test",
Some(runtime_id),
@@ -3806,7 +3801,7 @@ mod tests {
.is_err()
);
assert!(
default_embedded_config_bundle(
builtin_profile_config_bundle(
&ProfileSelector::Named("custom".to_string()),
"workspace-test",
Some(runtime_id),
@@ -3966,7 +3961,6 @@ mod tests {
host_id: host_id.to_string(),
display_name: label.to_string(),
label: label.to_string(),
role: None,
profile: None,
singleton_key: None,
tags: Vec::new(),
@@ -4158,7 +4152,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 0,
},
profile: None,
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -4284,7 +4278,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 0,
},
profile: None,
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -4380,7 +4374,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 0,
},
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -4412,7 +4406,7 @@ mod tests {
intent: WorkerSpawnIntent::WorkspaceCompanion,
requested_worker_name: None,
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
profile: None,
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -19,21 +19,14 @@ const PROFILE_SOURCE_TREE_ID: &str = "project";
const PROFILE_SOURCE_TREE_DISPLAY_ROOT: &str = "profiles";
const MAX_PROFILE_SOURCE_BYTES: u64 = 256 * 1024;
const BUILTIN_PROFILE_IDS: &[&str] = &[
"builtin:default",
"builtin:companion",
"builtin:intake",
"builtin:orchestrator",
"builtin:coder",
"builtin:reviewer",
];
const BUILTIN_PROFILE_SLUGS: &[&str] = &[
"default",
"companion",
"intake",
"orchestrator",
"coder",
"reviewer",
];
const BUILTIN_PROFILE_SLUGS: &[&str] =
&["companion", "intake", "orchestrator", "coder", "reviewer"];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceMetadataSettingsResponse {
@@ -832,7 +825,6 @@ pub fn project_profile_candidates(workspace_root: &Path) -> Vec<WorkspaceProfile
pub fn is_profile_candidate(workspace_root: &Path, profile_id: &str) -> bool {
BUILTIN_PROFILE_IDS.contains(&profile_id)
|| profile_id == "runtime_default"
|| project_profile_candidates(workspace_root)
.into_iter()
.any(|profile| profile.profile_id == profile_id)
@@ -840,7 +832,6 @@ pub fn is_profile_candidate(workspace_root: &Path, profile_id: &str) -> bool {
fn builtin_profile_summaries(default_profile: Option<&str>) -> Vec<WorkspaceProfileSummary> {
let labels = [
("builtin:default", "Default", "Bundled default Yoi profile"),
(
"builtin:companion",
"Companion",
@@ -1191,15 +1182,6 @@ fn build_profile_archive_from_tree_sources(
entrypoints.insert(project_selector(&entry.name), path);
}
}
if let Some(default) = registry
.default
.as_deref()
.filter(|value| value.starts_with("project:"))
{
if let Some(path) = entrypoints.get(default).cloned() {
entrypoints.insert("default".to_string(), path);
}
}
build_profile_archive_from_source_set(entrypoints, sources)
}
@@ -1920,9 +1902,7 @@ pub fn selector_for_builtin_candidate(
id: &str,
) -> Option<worker_runtime::catalog::ProfileSelector> {
match id {
"runtime_default" => Some(worker_runtime::catalog::ProfileSelector::RuntimeDefault),
"builtin:default"
| "builtin:companion"
"builtin:companion"
| "builtin:intake"
| "builtin:orchestrator"
| "builtin:coder"
+190 -82
View File
@@ -1059,6 +1059,7 @@ pub struct RemoteRuntimeTestResponse {
pub struct WorkerLaunchOptionsResponse {
pub workspace_id: String,
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
pub default_profile: Option<String>,
pub profiles: Vec<WorkerLaunchProfileCandidate>,
pub repositories: Vec<WorkingDirectoryRepositoryOption>,
pub working_directories: Vec<WorkingDirectorySummary>,
@@ -1128,7 +1129,8 @@ pub struct BrowserWorkerWorkingDirectorySelection {
pub struct BrowserCreateWorkerRequest {
pub runtime_id: String,
pub display_name: String,
pub profile: String,
#[serde(default)]
pub profile: Option<String>,
pub initial_text: String,
#[serde(default)]
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
@@ -1679,7 +1681,7 @@ fn start_memory_staging_consolidation(
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 1,
},
profile: Some(profile_selector),
profile: profile_selector,
initial_input: Some(input),
working_directory_request: None,
resolved_working_directory_request: None,
@@ -1788,10 +1790,7 @@ fn try_reuse_memory_consolidation_worker(
fn is_memory_consolidation_worker(worker: &WorkerSummary) -> bool {
worker.singleton_key.as_deref() == Some(MEMORY_CONSOLIDATION_SINGLETON_KEY)
|| [worker.profile.as_deref(), worker.role.as_deref()]
.into_iter()
.flatten()
.any(|value| value == MEMORY_CONSOLIDATION_PROFILE)
|| worker.profile.as_deref() == Some(MEMORY_CONSOLIDATION_PROFILE)
}
fn memory_consolidation_input_content(candidate_count: usize, total_bytes: u64) -> String {
@@ -4261,20 +4260,40 @@ async fn create_workspace_worker(
State(api): State<WorkspaceApi>,
Json(request): Json<BrowserCreateWorkerRequest>,
) -> ApiResult<Json<BrowserCreateWorkerResponse>> {
let profile = request
.profile
.as_deref()
.map(str::trim)
.filter(|profile| !profile.is_empty())
.map(ToOwned::to_owned)
.or_else(|| {
crate::profile_settings::load_profile_settings(
&api.config.workspace_id,
&api.config.workspace_root,
)
.default_profile
})
.ok_or_else(|| {
settings_bad_request(
"workspace_default_profile_missing",
"profile is required because this Workspace has no default profile configured",
)
})?;
let profile_selector =
profile_selector_for_candidate_with_root(&api.config.workspace_root, &request.profile)
.ok_or_else(|| {
profile_selector_for_candidate_with_root(&api.config.workspace_root, &profile).ok_or_else(
|| {
settings_bad_request(
"unsupported_worker_profile",
"profile must be selected from Backend-published worker profile candidates",
)
})?;
let resolved_config_bundle = if request.profile.starts_with("project:") {
},
)?;
let resolved_config_bundle = if profile.starts_with("project:") {
crate::profile_settings::build_workspace_profile_config_bundle(
&api.config.workspace_root,
&api.config.workspace_id,
&api.config.workspace_created_at,
&request.profile,
&profile,
)?
} else {
None
@@ -4320,7 +4339,7 @@ async fn create_workspace_worker(
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: if initial_input.is_some() { 1 } else { 0 },
},
profile: Some(profile_selector),
profile: profile_selector,
initial_input,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -5773,10 +5792,32 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResp
}
})
.collect();
let profile_settings = crate::profile_settings::load_profile_settings(
&api.config.workspace_id,
&api.config.workspace_root,
);
let profiles = profile_settings
.profiles
.into_iter()
.filter(|profile| {
!profile
.diagnostics
.iter()
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error)
})
.map(|profile| WorkerLaunchProfileCandidate {
id: profile.profile_id,
label: profile.label,
description: profile
.description
.unwrap_or_else(|| "Workspace profile.".to_string()),
})
.collect();
WorkerLaunchOptionsResponse {
workspace_id: api.config.workspace_id.clone(),
runtimes,
profiles: worker_profile_candidates_for_root(&api.config.workspace_root),
default_profile: profile_settings.default_profile,
profiles,
repositories: working_directory_repository_options(api),
working_directories: available_working_directory_summaries(api).unwrap_or_default(),
diagnostics: Vec::new(),
@@ -5932,7 +5973,6 @@ fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary
worker_id: record.runtime_worker_id.to_string(),
runtime_id: record.runtime_id.clone(),
host_id: "backend-registry".to_string(),
role: None,
display_name: record.display_name.clone(),
label: record.display_name.clone(),
singleton_key: None,
@@ -6373,58 +6413,25 @@ fn working_directory_request_for_browser(
})
}
#[cfg(test)]
fn worker_profile_candidates() -> Vec<WorkerLaunchProfileCandidate> {
worker_profile_candidates_for_root(Path::new("."))
.into_iter()
.filter(|candidate| candidate.id == "builtin:coder" || candidate.id == "runtime_default")
.collect()
}
fn worker_profile_candidates_for_root(workspace_root: &Path) -> Vec<WorkerLaunchProfileCandidate> {
let mut candidates = vec![
WorkerLaunchProfileCandidate {
id: "builtin:coder".to_string(),
label: "Coding Worker".to_string(),
description: "Built-in coding role profile for implementation work.".to_string(),
},
WorkerLaunchProfileCandidate {
id: "runtime_default".to_string(),
label: "Runtime default".to_string(),
description: "Use the selected Runtime's default profile.".to_string(),
},
];
candidates.extend(
crate::profile_settings::project_profile_candidates(workspace_root)
.into_iter()
.filter(|profile| profile.diagnostics.is_empty())
.map(|profile| WorkerLaunchProfileCandidate {
id: profile.profile_id,
label: profile.label,
description: profile
.description
.unwrap_or_else(|| "Workspace Decodal profile source.".to_string()),
}),
);
candidates
}
fn profile_selector_for_candidate(profile: &str) -> Option<ProfileSelector> {
crate::profile_settings::selector_for_builtin_candidate(profile)
.filter(|_| matches!(profile, "builtin:coder" | "runtime_default"))
}
fn profile_selector_for_candidate_with_root(
workspace_root: &Path,
profile: &str,
) -> Option<ProfileSelector> {
if profile_selector_for_candidate(profile).is_some() {
profile_selector_for_candidate(profile)
} else if crate::profile_settings::is_profile_candidate(workspace_root, profile) {
if let Some(selector @ ProfileSelector::Builtin(_)) =
crate::profile_settings::selector_for_builtin_candidate(profile)
} else {
None
{
return Some(selector);
}
crate::profile_settings::project_profile_candidates(workspace_root)
.into_iter()
.find(|candidate| {
candidate.profile_id == profile
&& !candidate
.diagnostics
.iter()
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error)
})
.and_then(|_| crate::profile_settings::selector_for_builtin_candidate(profile))
}
fn parse_runtime_worker_id_for_registry(worker_id: &str) -> ApiResult<u64> {
@@ -6441,7 +6448,7 @@ fn sanitize_worker_display_name(value: &str) -> Option<String> {
if display_name.chars().any(char::is_control) {
None
} else if display_name.is_empty() {
Some("Coding Worker".to_string())
Some("Worker".to_string())
} else {
Some(display_name.chars().take(80).collect())
}
@@ -7065,17 +7072,89 @@ mod tests {
#[test]
fn worker_profile_candidates_are_backend_published_and_mapped() {
let candidates = worker_profile_candidates();
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join(".yoi")).unwrap();
fs::write(
dir.path().join(".yoi/profiles.toml"),
"default = \"builtin:companion\"\n",
)
.unwrap();
let settings = crate::profile_settings::load_profile_settings("workspace-test", dir.path());
for expected in [
"builtin:companion",
"builtin:intake",
"builtin:orchestrator",
"builtin:coder",
"builtin:reviewer",
] {
assert!(
settings
.profiles
.iter()
.any(|profile| profile.profile_id == expected),
"missing {expected}"
);
}
assert!(
candidates
settings
.profiles
.iter()
.any(|candidate| candidate.id == "builtin:coder")
.all(|profile| profile.profile_id != "builtin:default")
);
assert_eq!(
settings.default_profile.as_deref(),
Some("builtin:companion")
);
assert!(matches!(
profile_selector_for_candidate("builtin:coder"),
profile_selector_for_candidate_with_root(dir.path(), "builtin:coder"),
Some(ProfileSelector::Builtin(value)) if value == "builtin:coder"
));
assert!(profile_selector_for_candidate("free-text-profile").is_none());
assert!(
profile_selector_for_candidate_with_root(dir.path(), "free-text-profile").is_none()
);
}
#[tokio::test]
async fn worker_launch_options_publish_every_valid_workspace_profile_and_default() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join(".yoi/profiles")).unwrap();
fs::write(
dir.path().join(".yoi/profiles.toml"),
concat!(
"default = \"builtin:companion\"\n",
"[profile.custom]\n",
"path = \".yoi/profiles/custom.dcdl\"\n",
),
)
.unwrap();
fs::write(
dir.path().join(".yoi/profiles/custom.dcdl"),
"slug = \"custom\"; description = \"Custom profile\";\n",
)
.unwrap();
let api = test_app(dir.path()).await;
let response = get_json(api, "/api/workers/launch-options").await;
assert_eq!(response["default_profile"], "builtin:companion");
let profiles = response["profiles"].as_array().unwrap();
for expected in [
"builtin:companion",
"builtin:intake",
"builtin:orchestrator",
"builtin:coder",
"builtin:reviewer",
"project:custom",
] {
assert!(
profiles.iter().any(|profile| profile["id"] == expected),
"missing {expected}: {profiles:?}"
);
}
assert!(
profiles
.iter()
.all(|profile| profile["id"] != "builtin:default")
);
}
#[test]
@@ -7258,11 +7337,19 @@ mod tests {
)
.unwrap();
fs::write(dir.path().join(".yoi/profiles/bad.dcdl"), "not decodal").unwrap();
assert!(
worker_profile_candidates_for_root(dir.path())
.iter()
.all(|candidate| candidate.id != "project:bad")
);
let candidates =
crate::profile_settings::load_profile_settings("workspace-test", dir.path())
.profiles
.into_iter()
.filter(|profile| {
!profile
.diagnostics
.iter()
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error)
})
.map(|profile| profile.profile_id)
.collect::<Vec<_>>();
assert!(!candidates.iter().any(|profile| profile == "project:bad"));
assert!(profile_selector_for_candidate_with_root(dir.path(), "project:bad").is_none());
}
@@ -7583,9 +7670,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 0,
},
profile: Some(ProfileSelector::Builtin(
MEMORY_CONSOLIDATION_PROFILE.to_string(),
)),
profile: ProfileSelector::Builtin(MEMORY_CONSOLIDATION_PROFILE.to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -8141,7 +8226,9 @@ mod tests {
},
},
profiles: vec![worker_runtime::config_bundle::ConfigProfileDescriptor {
selector: worker_runtime::catalog::ProfileSelector::RuntimeDefault,
selector: worker_runtime::catalog::ProfileSelector::Builtin(
"builtin:companion".to_string(),
),
label: Some("server-test".to_string()),
}],
declarations: Vec::new(),
@@ -8154,7 +8241,9 @@ mod tests {
fn runtime_create_request() -> worker_runtime::catalog::CreateWorkerRequest {
let bundle = runtime_test_bundle();
worker_runtime::catalog::CreateWorkerRequest {
profile: worker_runtime::catalog::ProfileSelector::RuntimeDefault,
profile: worker_runtime::catalog::ProfileSelector::Builtin(
"builtin:companion".to_string(),
),
display_name: None,
profile_source: worker_runtime::catalog::ProfileSourceArchiveSource::Http {
location: worker_runtime::catalog::ProfileSourceArchiveHttpRef {
@@ -8534,8 +8623,14 @@ mod tests {
}
#[tokio::test]
async fn browser_worker_create_succeeds_and_preserves_unsupported_diagnostics() {
async fn browser_worker_create_uses_workspace_default_and_preserves_unsupported_diagnostics() {
let dir = tempfile::tempdir().unwrap();
fs::create_dir_all(dir.path().join(".yoi")).unwrap();
fs::write(
dir.path().join(".yoi/profiles.toml"),
"default = \"builtin:coder\"\n",
)
.unwrap();
let app = test_app(dir.path()).await;
let created = post_json(
app.clone(),
@@ -8543,7 +8638,6 @@ mod tests {
serde_json::json!({
"runtime_id": "embedded-worker-runtime",
"display_name": "",
"profile": "runtime_default",
"initial_text": ""
}),
)
@@ -8556,7 +8650,9 @@ mod tests {
.iter()
.find(|worker| worker["worker_id"] == created["worker_id"])
.expect("created Worker should be listed");
assert_eq!(worker["label"], "Coding Worker");
assert_eq!(worker["label"], "Worker");
assert_eq!(worker["profile"], "builtin:coder");
assert!(worker.get("role").is_none());
assert_eq!(worker["worker_id"], created["worker_id"]);
let detail_path = format!(
"/api/runtimes/{}/workers/{}",
@@ -8564,7 +8660,7 @@ mod tests {
created["worker_id"].as_str().unwrap()
);
let detail = get_json(app.clone(), detail_path.as_str()).await;
assert_eq!(detail["label"], "Coding Worker");
assert_eq!(detail["label"], "Worker");
assert_eq!(detail["worker_id"], created["worker_id"]);
assert!(
created["console_href"]
@@ -8607,7 +8703,7 @@ mod tests {
Some(serde_json::json!({
"runtime_id": "remote-runtime",
"display_name": "Remote Worker",
"profile": "runtime_default",
"profile": "builtin:companion",
"initial_text": ""
})),
StatusCode::BAD_REQUEST,
@@ -8646,6 +8742,10 @@ mod tests {
"kind": "run_accepted",
"expected_segments": 0
},
"profile": {
"kind": "builtin",
"value": "builtin:coder"
},
"working_directory_request": {
"repository_id": TEST_REPOSITORY_ID,
"local_path": dir.path().display().to_string()
@@ -8681,6 +8781,10 @@ mod tests {
"kind": "run_accepted",
"expected_segments": 0
},
"profile": {
"kind": "builtin",
"value": "builtin:coder"
},
"working_directory_request": {
"repository_id": TEST_REPOSITORY_ID,
"selector": "HEAD"
@@ -9259,7 +9363,7 @@ mod tests {
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
expected_segments: 0,
},
profile: None,
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
@@ -9501,6 +9605,10 @@ mod tests {
"acceptance": {
"kind": "run_accepted",
"expected_segments": 0
},
"profile": {
"kind": "builtin",
"value": "builtin:coder"
}
}),
)