feat: profile解決とmodel catalogを更新
This commit is contained in:
parent
d94ef81b43
commit
3e217adc14
|
|
@ -145,8 +145,6 @@ pub struct BackendWorkerSummary {
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub role: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub profile: Option<String>,
|
pub profile: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub singleton_key: Option<String>,
|
pub singleton_key: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ pub enum TicketRoleLaunchError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
LaunchConfig(#[from] TicketRoleLaunchConfigError),
|
LaunchConfig(#[from] TicketRoleLaunchConfigError),
|
||||||
#[error(
|
#[error(
|
||||||
"Ticket role `{role}` profile selector `{selector}` is not resolvable before launch: {message}. Configure `[ticket.roles.{role}].profile` with an executable concrete profile selector such as `builtin:default` or a project/user profile"
|
"Ticket role `{role}` profile selector `{selector}` is not resolvable before launch: {message}. Configure `[ticket.roles.{role}].profile` with an executable concrete profile selector such as `builtin:companion` or a project/user profile"
|
||||||
)]
|
)]
|
||||||
ProfileResolution {
|
ProfileResolution {
|
||||||
role: TicketRole,
|
role: TicketRole,
|
||||||
|
|
@ -701,17 +701,20 @@ mod tests {
|
||||||
let mut config = String::from("[ticket]\n");
|
let mut config = String::from("[ticket]\n");
|
||||||
for role in roles {
|
for role in roles {
|
||||||
config.push_str(&format!(
|
config.push_str(&format!(
|
||||||
"\n[ticket.roles.{role}]\nprofile = \"builtin:default\"\n"
|
"\n[ticket.roles.{role}]\nprofile = \"builtin:companion\"\n"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
write_config(workspace, &config);
|
write_config(workspace, &config);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_segment(plan: &TicketRoleLaunchPlan) -> &str {
|
fn text_segment(plan: &TicketRoleLaunchPlan) -> &str {
|
||||||
match &plan.run_segments[1] {
|
plan.run_segments
|
||||||
Segment::Text { content } => content,
|
.iter()
|
||||||
other => panic!("expected text segment, got {other:?}"),
|
.find_map(|segment| match segment {
|
||||||
}
|
Segment::Text { content } => Some(content.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.expect("expected text segment")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_test_event<W>(writer: &mut W, event: Event)
|
async fn write_test_event<W>(writer: &mut W, event: Event)
|
||||||
|
|
@ -949,7 +952,7 @@ profile = "project:no-such-ticket-role-profile"
|
||||||
let plan = plan_ticket_role_launch(context).unwrap();
|
let plan = plan_ticket_role_launch(context).unwrap();
|
||||||
|
|
||||||
assert_eq!(plan.role, TicketRole::Intake);
|
assert_eq!(plan.role, TicketRole::Intake);
|
||||||
assert_eq!(plan.profile, "builtin:default");
|
assert_eq!(plan.profile, "builtin:companion");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -962,7 +965,7 @@ profile = "project:no-such-ticket-role-profile"
|
||||||
language = "Japanese"
|
language = "Japanese"
|
||||||
|
|
||||||
[ticket.roles.intake]
|
[ticket.roles.intake]
|
||||||
profile = "builtin:default"
|
profile = "builtin:companion"
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let context = TicketRoleLaunchContext::new(temp.path(), TicketRole::Intake);
|
let context = TicketRoleLaunchContext::new(temp.path(), TicketRole::Intake);
|
||||||
|
|
@ -1024,7 +1027,7 @@ profile = "builtin:default"
|
||||||
temp.path(),
|
temp.path(),
|
||||||
r#"
|
r#"
|
||||||
[ticket.roles.reviewer]
|
[ticket.roles.reviewer]
|
||||||
profile = "builtin:default"
|
profile = "builtin:companion"
|
||||||
launch_prompt = "$workspace/ticket/reviewer/launch"
|
launch_prompt = "$workspace/ticket/reviewer/launch"
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
@ -1037,7 +1040,7 @@ launch_prompt = "$workspace/ticket/reviewer/launch"
|
||||||
let text = text_segment(&plan);
|
let text = text_segment(&plan);
|
||||||
|
|
||||||
assert_eq!(plan.worker_name, "reviewer-fixed");
|
assert_eq!(plan.worker_name, "reviewer-fixed");
|
||||||
assert_eq!(plan.profile, "builtin:default");
|
assert_eq!(plan.profile, "builtin:companion");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
plan.launch_prompt_ref.as_deref(),
|
plan.launch_prompt_ref.as_deref(),
|
||||||
Some("$workspace/ticket/reviewer/launch")
|
Some("$workspace/ticket/reviewer/launch")
|
||||||
|
|
@ -1045,7 +1048,7 @@ launch_prompt = "$workspace/ticket/reviewer/launch"
|
||||||
assert!(matches!(&plan.run_segments[0], Segment::Text { .. }));
|
assert!(matches!(&plan.run_segments[0], Segment::Text { .. }));
|
||||||
assert!(!text.contains("Configured launch_prompt"));
|
assert!(!text.contains("Configured launch_prompt"));
|
||||||
assert!(!text.contains("$workspace/ticket/reviewer/launch"));
|
assert!(!text.contains("$workspace/ticket/reviewer/launch"));
|
||||||
assert!(!text.contains("Profile selector: builtin:default"));
|
assert!(!text.contains("Profile selector: builtin:companion"));
|
||||||
assert!(!text.contains("Role: reviewer"));
|
assert!(!text.contains("Role: reviewer"));
|
||||||
assert!(!text.contains("system_instruction"));
|
assert!(!text.contains("system_instruction"));
|
||||||
assert!(text.contains("Target Ticket:"));
|
assert!(text.contains("Target Ticket:"));
|
||||||
|
|
@ -1056,7 +1059,7 @@ launch_prompt = "$workspace/ticket/reviewer/launch"
|
||||||
.spawn_config(WorkerRuntimeCommand::for_executable("/bin/yoi"))
|
.spawn_config(WorkerRuntimeCommand::for_executable("/bin/yoi"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(spawn.worker_name, "reviewer-fixed");
|
assert_eq!(spawn.worker_name, "reviewer-fixed");
|
||||||
assert_eq!(spawn.profile.as_deref(), Some("builtin:default"));
|
assert_eq!(spawn.profile.as_deref(), Some("builtin:companion"));
|
||||||
assert_eq!(spawn.workspace_root, temp.path());
|
assert_eq!(spawn.workspace_root, temp.path());
|
||||||
assert!(spawn.cwd.is_none());
|
assert!(spawn.cwd.is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,23 @@ mod tests {
|
||||||
assert_eq!(cfg.context_window, 123_456);
|
assert_eq!(cfg.context_window, 123_456);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn codex_gpt56_sol_catalog_clamps_public_window_to_backend_limit() {
|
||||||
|
let providers = load_builtin_providers().unwrap();
|
||||||
|
let models = load_builtin_models().unwrap();
|
||||||
|
let manifest = ModelManifest {
|
||||||
|
ref_: Some("codex-oauth/gpt-5.6-sol".into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let cfg = resolve_with_catalogs(&manifest, &providers, &models).unwrap();
|
||||||
|
assert_eq!(cfg.model_id, "gpt-5.6-sol");
|
||||||
|
assert_eq!(cfg.context_window, 272_000);
|
||||||
|
assert_eq!(cfg.max_context_window, Some(272_000));
|
||||||
|
let capability = cfg.capability.expect("catalog capability");
|
||||||
|
assert!(capability.vision);
|
||||||
|
assert!(capability.reasoning.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn codex_gpt55_catalog_records_effective_context_window() {
|
fn codex_gpt55_catalog_records_effective_context_window() {
|
||||||
let providers = load_builtin_providers().unwrap();
|
let providers = load_builtin_providers().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROFILE_FORMAT_V1: &str = "yoi.profile.v1";
|
const PROFILE_FORMAT_V1: &str = "yoi.profile.v1";
|
||||||
const BUILTIN_DEFAULT_PROFILE_NAME: &str = "default";
|
|
||||||
const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml");
|
const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml");
|
||||||
const WORKSPACE_OVERRIDE_LOCAL_FILENAME: &str = "override.local.toml";
|
const WORKSPACE_OVERRIDE_LOCAL_FILENAME: &str = "override.local.toml";
|
||||||
|
|
||||||
|
|
@ -32,11 +31,6 @@ struct BuiltinProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
const BUILTIN_PROFILES: &[BuiltinProfile] = &[
|
const BUILTIN_PROFILES: &[BuiltinProfile] = &[
|
||||||
BuiltinProfile {
|
|
||||||
name: BUILTIN_DEFAULT_PROFILE_NAME,
|
|
||||||
label: "builtin:default",
|
|
||||||
description: "Bundled default Yoi coding profile",
|
|
||||||
},
|
|
||||||
BuiltinProfile {
|
BuiltinProfile {
|
||||||
name: "companion",
|
name: "companion",
|
||||||
label: "builtin:companion",
|
label: "builtin:companion",
|
||||||
|
|
@ -300,23 +294,6 @@ impl ProfileRegistry {
|
||||||
fn set_default(&mut self, default: ProfileDefault) {
|
fn set_default(&mut self, default: ProfileDefault) {
|
||||||
self.default = Some(default);
|
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) {
|
fn mark_default_flags(&mut self) {
|
||||||
let Some(default) = self.default.clone() else {
|
let Some(default) = self.default.clone() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -366,7 +343,6 @@ impl ProfileDiscovery {
|
||||||
if let Some(path) = &self.project_config {
|
if let Some(path) = &self.project_config {
|
||||||
load_profile_registry_file(&mut registry, ProfileRegistrySource::Project, path)?;
|
load_profile_registry_file(&mut registry, ProfileRegistrySource::Project, path)?;
|
||||||
}
|
}
|
||||||
registry.set_builtin_default_if_available();
|
|
||||||
registry.mark_default_flags();
|
registry.mark_default_flags();
|
||||||
Ok(registry)
|
Ok(registry)
|
||||||
}
|
}
|
||||||
|
|
@ -886,9 +862,8 @@ fn read_profile_artifact_file(path: &Path) -> Result<serde_json::Value, ProfileE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builtin_profile_artifact(label: &str) -> Option<serde_json::Value> {
|
fn builtin_profile_artifact(label: &str) -> Option<serde_json::Value> {
|
||||||
let mut value = builtin_default_profile_artifact();
|
let mut value = builtin_base_profile_artifact();
|
||||||
match label {
|
match label {
|
||||||
"builtin:default" | "default" => Some(value),
|
|
||||||
"builtin:companion" | "companion" => {
|
"builtin:companion" | "companion" => {
|
||||||
apply_role_profile(
|
apply_role_profile(
|
||||||
&mut value,
|
&mut value,
|
||||||
|
|
@ -970,7 +945,7 @@ fn builtin_profile_artifact(label: &str) -> Option<serde_json::Value> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builtin_default_profile_artifact() -> serde_json::Value {
|
fn builtin_base_profile_artifact() -> serde_json::Value {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"slug": "default",
|
"slug": "default",
|
||||||
"description": "Default Yoi coding profile.",
|
"description": "Default Yoi coding profile.",
|
||||||
|
|
@ -1395,16 +1370,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_default_profile_is_registered_as_default() {
|
fn builtin_profiles_do_not_define_an_implicit_default() {
|
||||||
let registry = ProfileDiscovery::with_sources(None, None)
|
let registry = ProfileDiscovery::with_sources(None, None)
|
||||||
.discover()
|
.discover()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let default = registry.default_entry().unwrap();
|
assert!(matches!(
|
||||||
assert_eq!(default.source, ProfileRegistrySource::Builtin);
|
registry.default_entry(),
|
||||||
assert_eq!(default.name, BUILTIN_DEFAULT_PROFILE_NAME);
|
Err(ProfileError::NoDefaultProfile)
|
||||||
assert!(default.is_default);
|
));
|
||||||
assert_eq!(default.path, None);
|
assert!(matches!(
|
||||||
assert_eq!(default.provenance, "builtin:default");
|
registry.select(&ProfileSelector::Default),
|
||||||
|
Err(ProfileError::NoDefaultProfile)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_role_profiles_are_registered_and_resolve() {
|
fn builtin_role_profiles_are_registered_and_resolve() {
|
||||||
|
|
@ -1808,12 +1785,12 @@ worker_context_max_tokens = 68000
|
||||||
assert!(err.to_string().contains("model.auth.file"));
|
assert!(err.to_string().contains("model.auth.file"));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn builtin_default_resolves_without_external_evaluator() {
|
fn builtin_companion_resolves_without_external_evaluator() {
|
||||||
let tmp = TempDir::new().unwrap();
|
let tmp = TempDir::new().unwrap();
|
||||||
let resolved = ProfileResolver::new()
|
let resolved = ProfileResolver::new()
|
||||||
.with_workspace_base(tmp.path())
|
.with_workspace_base(tmp.path())
|
||||||
.resolve(
|
.resolve(
|
||||||
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "default"),
|
&ProfileSelector::source_named(ProfileRegistrySource::Builtin, "companion"),
|
||||||
ProfileResolveOptions::with_worker_name("runtime-workspace"),
|
ProfileResolveOptions::with_worker_name("runtime-workspace"),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1829,15 +1806,15 @@ worker_context_max_tokens = 68000
|
||||||
assert!(!resolved.manifest.feature.ticket.orchestration_control);
|
assert!(!resolved.manifest.feature.ticket.orchestration_control);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolved.profile.as_ref().unwrap().name.as_deref(),
|
resolved.profile.as_ref().unwrap().name.as_deref(),
|
||||||
Some("default")
|
Some("companion")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolved.source,
|
resolved.source,
|
||||||
ProfileSource::Registry {
|
ProfileSource::Registry {
|
||||||
source: ProfileRegistrySource::Builtin,
|
source: ProfileRegistrySource::Builtin,
|
||||||
name: "default".into(),
|
name: "companion".into(),
|
||||||
path: None,
|
path: None,
|
||||||
provenance: Some("builtin:default".into()),
|
provenance: Some("builtin:companion".into()),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1192,7 +1192,7 @@ root = ".yoi/tickets"
|
||||||
temp.path(),
|
temp.path(),
|
||||||
r#"
|
r#"
|
||||||
[roles.intake]
|
[roles.intake]
|
||||||
profile = "builtin:default"
|
profile = "builtin:companion"
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1203,7 +1203,7 @@ profile = "builtin:default"
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.profile
|
.profile
|
||||||
.as_str(),
|
.as_str(),
|
||||||
"builtin:default"
|
"builtin:companion"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config
|
config
|
||||||
|
|
@ -1313,7 +1313,7 @@ profile = "inherit"
|
||||||
temp.path(),
|
temp.path(),
|
||||||
r#"
|
r#"
|
||||||
[roles.investigator]
|
[roles.investigator]
|
||||||
profile = "builtin:default"
|
profile = "builtin:companion"
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -363,7 +363,6 @@ mod tests {
|
||||||
display_name: "label".to_string(),
|
display_name: "label".to_string(),
|
||||||
singleton_key: None,
|
singleton_key: None,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
role: None,
|
|
||||||
profile: profile.map(str::to_string),
|
profile: profile.map(str::to_string),
|
||||||
workspace: BackendWorkerWorkspaceSummary {
|
workspace: BackendWorkerWorkspaceSummary {
|
||||||
visibility: "workspace".to_string(),
|
visibility: "workspace".to_string(),
|
||||||
|
|
|
||||||
|
|
@ -2782,7 +2782,7 @@ fn dashboard_ticket_intake_finish_success_clears_composer_and_reports_pod() {
|
||||||
implementation_worktree_root: PathBuf::from("/tmp/workspace/.worktree"),
|
implementation_worktree_root: PathBuf::from("/tmp/workspace/.worktree"),
|
||||||
role: TicketRole::Intake,
|
role: TicketRole::Intake,
|
||||||
worker_name: "intake-worker".to_string(),
|
worker_name: "intake-worker".to_string(),
|
||||||
profile: "builtin:default".to_string(),
|
profile: "builtin:companion".to_string(),
|
||||||
launch_prompt_ref: None,
|
launch_prompt_ref: None,
|
||||||
run_segments: vec![],
|
run_segments: vec![],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -699,10 +699,10 @@ description = "Project coder"
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (choices, default_index) = profile_choices_for_cwd(&project);
|
let (choices, default_index) = profile_choices_for_cwd(&project);
|
||||||
assert_eq!(choices[0].selector.as_deref(), Some("builtin:default"));
|
assert_eq!(choices[0].selector.as_deref(), Some("builtin:companion"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
choices[0].label,
|
choices[0].label,
|
||||||
"builtin:default — Bundled default Yoi coding profile"
|
"builtin:companion — Bundled Companion role profile"
|
||||||
);
|
);
|
||||||
let project_index = choices
|
let project_index = choices
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,10 @@ use std::path::PathBuf;
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
|
#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
|
||||||
pub enum ProfileSelector {
|
pub enum ProfileSelector {
|
||||||
RuntimeDefault,
|
|
||||||
Builtin(String),
|
Builtin(String),
|
||||||
Named(String),
|
Named(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProfileSelector {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::RuntimeDefault
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runtime fetch/caching metadata for a Backend-authored Decodal profile source archive.
|
/// Runtime fetch/caching metadata for a Backend-authored Decodal profile source archive.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ProfileSourceArchiveHttpRef {
|
pub struct ProfileSourceArchiveHttpRef {
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,6 @@ pub(crate) fn validate_profile_selector(
|
||||||
bundle_id: Option<&str>,
|
bundle_id: Option<&str>,
|
||||||
) -> Result<(), RuntimeError> {
|
) -> Result<(), RuntimeError> {
|
||||||
match selector {
|
match selector {
|
||||||
ProfileSelector::RuntimeDefault => Ok(()),
|
|
||||||
ProfileSelector::Builtin(value) | ProfileSelector::Named(value) => {
|
ProfileSelector::Builtin(value) | ProfileSelector::Named(value) => {
|
||||||
if value.trim().is_empty() {
|
if value.trim().is_empty() {
|
||||||
Err(RuntimeError::InvalidProfileSelector {
|
Err(RuntimeError::InvalidProfileSelector {
|
||||||
|
|
@ -544,7 +543,6 @@ fn declaration_sort_key(declaration: &ConfigDeclaration) -> String {
|
||||||
|
|
||||||
fn profile_sort_key(selector: &ProfileSelector) -> String {
|
fn profile_sort_key(selector: &ProfileSelector) -> String {
|
||||||
match selector {
|
match selector {
|
||||||
ProfileSelector::RuntimeDefault => "runtime_default".to_string(),
|
|
||||||
ProfileSelector::Builtin(value) => format!("builtin\0{value}"),
|
ProfileSelector::Builtin(value) => format!("builtin\0{value}"),
|
||||||
ProfileSelector::Named(value) => format!("named\0{value}"),
|
ProfileSelector::Named(value) => format!("named\0{value}"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1808,9 +1808,9 @@ mod ws_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ws_create_request() -> CreateWorkerRequest {
|
fn ws_create_request() -> CreateWorkerRequest {
|
||||||
let bundle = ws_test_bundle(ProfileSelector::RuntimeDefault);
|
let bundle = ws_test_bundle(ProfileSelector::Builtin("builtin:companion".to_string()));
|
||||||
CreateWorkerRequest {
|
CreateWorkerRequest {
|
||||||
profile: ProfileSelector::RuntimeDefault,
|
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
|
||||||
display_name: None,
|
display_name: None,
|
||||||
profile_source: crate::catalog::ProfileSourceArchiveSource::Http {
|
profile_source: crate::catalog::ProfileSourceArchiveSource::Http {
|
||||||
location: crate::catalog::ProfileSourceArchiveHttpRef {
|
location: crate::catalog::ProfileSourceArchiveHttpRef {
|
||||||
|
|
@ -1845,7 +1845,9 @@ mod ws_tests {
|
||||||
Runtime::with_execution_backend(RuntimeOptions::default(), Arc::new(WsBackend))
|
Runtime::with_execution_backend(RuntimeOptions::default(), Arc::new(WsBackend))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
runtime
|
runtime
|
||||||
.store_config_bundle(ws_test_bundle(ProfileSelector::RuntimeDefault))
|
.store_config_bundle(ws_test_bundle(ProfileSelector::Builtin(
|
||||||
|
"builtin:companion".to_string(),
|
||||||
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let worker = runtime
|
let worker = runtime
|
||||||
.create_worker_scoped(
|
.create_worker_scoped(
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,6 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(profile) = config.profile.clone() {
|
|
||||||
factory = factory.with_profile(profile);
|
|
||||||
}
|
|
||||||
let backend = Arc::new(
|
let backend = Arc::new(
|
||||||
WorkerRuntimeExecutionBackend::new(factory)
|
WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.map_err(ProcessError::WorkerAdapter)?
|
.map_err(ProcessError::WorkerAdapter)?
|
||||||
|
|
@ -171,9 +168,6 @@ where
|
||||||
"--backend-resource-token" => {
|
"--backend-resource-token" => {
|
||||||
config.backend_resource_token = Some(take_value(&flag, inline_value, &mut args)?);
|
config.backend_resource_token = Some(take_value(&flag, inline_value, &mut args)?);
|
||||||
}
|
}
|
||||||
"--profile" => {
|
|
||||||
config.profile = Some(take_value(&flag, inline_value, &mut args)?);
|
|
||||||
}
|
|
||||||
"--store" => {
|
"--store" => {
|
||||||
let value = take_value(&flag, inline_value, &mut args)?;
|
let value = take_value(&flag, inline_value, &mut args)?;
|
||||||
if !matches!(value.as_str(), "fs" | "fs-store") {
|
if !matches!(value.as_str(), "fs" | "fs-store") {
|
||||||
|
|
@ -286,7 +280,6 @@ struct ProcessConfig {
|
||||||
no_store: bool,
|
no_store: bool,
|
||||||
backend_resource_endpoint: Option<String>,
|
backend_resource_endpoint: Option<String>,
|
||||||
backend_resource_token: Option<String>,
|
backend_resource_token: Option<String>,
|
||||||
profile: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -308,7 +301,6 @@ impl ProcessConfig {
|
||||||
no_store: false,
|
no_store: false,
|
||||||
backend_resource_endpoint: None,
|
backend_resource_endpoint: None,
|
||||||
backend_resource_token: None,
|
backend_resource_token: None,
|
||||||
profile: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -804,7 +796,6 @@ Browsers must not connect to this Runtime process directly.
|
||||||
Options:
|
Options:
|
||||||
--bind <ADDR> Bind socket address (default: 127.0.0.1:38800)
|
--bind <ADDR> Bind socket address (default: 127.0.0.1:38800)
|
||||||
--display-name <NAME> Runtime display name
|
--display-name <NAME> Runtime display name
|
||||||
--profile <SELECTOR> Force spawned Workers to use a Profile selector
|
|
||||||
--fs-root <PATH> Filesystem-backed storage root (default: user data dir)
|
--fs-root <PATH> Filesystem-backed storage root (default: user data dir)
|
||||||
--fs-worker-dir <PATH> Worker storage directory (default: <fs-root>/worker)
|
--fs-worker-dir <PATH> Worker storage directory (default: <fs-root>/worker)
|
||||||
--fs-runtime-dir <PATH> Runtime catalog directory (default: <fs-root>/runtime)
|
--fs-runtime-dir <PATH> Runtime catalog directory (default: <fs-root>/runtime)
|
||||||
|
|
@ -852,6 +843,7 @@ mod tests {
|
||||||
assert!(parse_args(["--worker-store-dir", "/tmp/sessions"]).is_err());
|
assert!(parse_args(["--worker-store-dir", "/tmp/sessions"]).is_err());
|
||||||
assert!(parse_args(["--worker-metadata-dir", "/tmp/metadata"]).is_err());
|
assert!(parse_args(["--worker-metadata-dir", "/tmp/metadata"]).is_err());
|
||||||
assert!(parse_args(["--worker-runtime-base-dir", "/tmp/runtime"]).is_err());
|
assert!(parse_args(["--worker-runtime-base-dir", "/tmp/runtime"]).is_err());
|
||||||
|
assert!(parse_args(["--profile", "builtin:coder"]).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -866,8 +858,6 @@ mod tests {
|
||||||
"127.0.0.1:0",
|
"127.0.0.1:0",
|
||||||
"--display-name",
|
"--display-name",
|
||||||
"Runtime Alpha",
|
"Runtime Alpha",
|
||||||
"--profile",
|
|
||||||
"builtin:coder",
|
|
||||||
"--local-token-env",
|
"--local-token-env",
|
||||||
"TEST_RUNTIME_TOKEN",
|
"TEST_RUNTIME_TOKEN",
|
||||||
]);
|
]);
|
||||||
|
|
@ -881,8 +871,6 @@ mod tests {
|
||||||
"127.0.0.1:0",
|
"127.0.0.1:0",
|
||||||
"--display-name",
|
"--display-name",
|
||||||
"Runtime Alpha",
|
"Runtime Alpha",
|
||||||
"--profile",
|
|
||||||
"builtin:coder",
|
|
||||||
"--local-token-env",
|
"--local-token-env",
|
||||||
"TEST_RUNTIME_TOKEN",
|
"TEST_RUNTIME_TOKEN",
|
||||||
])
|
])
|
||||||
|
|
@ -894,7 +882,6 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(config.http.bind_addr, "127.0.0.1:0".parse().unwrap());
|
assert_eq!(config.http.bind_addr, "127.0.0.1:0".parse().unwrap());
|
||||||
assert_eq!(config.http.display_name.as_deref(), Some("Runtime Alpha"));
|
assert_eq!(config.http.display_name.as_deref(), Some("Runtime Alpha"));
|
||||||
assert_eq!(config.profile.as_deref(), Some("builtin:coder"));
|
|
||||||
assert_eq!(config.http.local_token.as_deref(), Some("secret-token"));
|
assert_eq!(config.http.local_token.as_deref(), Some("secret-token"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ pub struct ProfileRuntimeWorkerFactory {
|
||||||
profile_base_dir: PathBuf,
|
profile_base_dir: PathBuf,
|
||||||
store_dir: Option<PathBuf>,
|
store_dir: Option<PathBuf>,
|
||||||
worker_metadata_dir: Option<PathBuf>,
|
worker_metadata_dir: Option<PathBuf>,
|
||||||
profile: Option<String>,
|
|
||||||
runtime_base_dir: RuntimeArtifactRoot,
|
runtime_base_dir: RuntimeArtifactRoot,
|
||||||
resource_client: Option<Arc<dyn BackendResourceClient>>,
|
resource_client: Option<Arc<dyn BackendResourceClient>>,
|
||||||
profile_archive_cache: Arc<ProfileSourceArchiveCache>,
|
profile_archive_cache: Arc<ProfileSourceArchiveCache>,
|
||||||
|
|
@ -118,7 +117,6 @@ impl ProfileRuntimeWorkerFactory {
|
||||||
profile_base_dir,
|
profile_base_dir,
|
||||||
store_dir: None,
|
store_dir: None,
|
||||||
worker_metadata_dir: None,
|
worker_metadata_dir: None,
|
||||||
profile: None,
|
|
||||||
runtime_base_dir: RuntimeArtifactRoot::owned(),
|
runtime_base_dir: RuntimeArtifactRoot::owned(),
|
||||||
resource_client: None,
|
resource_client: None,
|
||||||
profile_archive_cache: Arc::new(ProfileSourceArchiveCache::default()),
|
profile_archive_cache: Arc::new(ProfileSourceArchiveCache::default()),
|
||||||
|
|
@ -135,13 +133,6 @@ impl ProfileRuntimeWorkerFactory {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the profile selector used for Runtime-created Workers. When unset,
|
|
||||||
/// normal default profile discovery is used.
|
|
||||||
pub fn with_profile(mut self, profile: impl Into<String>) -> Self {
|
|
||||||
self.profile = Some(profile.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_runtime_base_dir(mut self, runtime_base_dir: impl Into<PathBuf>) -> Self {
|
pub fn with_runtime_base_dir(mut self, runtime_base_dir: impl Into<PathBuf>) -> Self {
|
||||||
self.runtime_base_dir = RuntimeArtifactRoot::External(runtime_base_dir.into());
|
self.runtime_base_dir = RuntimeArtifactRoot::External(runtime_base_dir.into());
|
||||||
self
|
self
|
||||||
|
|
@ -184,37 +175,27 @@ impl ProfileRuntimeWorkerFactory {
|
||||||
|
|
||||||
fn runtime_profile_value(
|
fn runtime_profile_value(
|
||||||
profile: &crate::catalog::ProfileSelector,
|
profile: &crate::catalog::ProfileSelector,
|
||||||
) -> Option<std::borrow::Cow<'_, str>> {
|
) -> std::borrow::Cow<'_, str> {
|
||||||
match profile {
|
match profile {
|
||||||
crate::catalog::ProfileSelector::RuntimeDefault => None,
|
|
||||||
crate::catalog::ProfileSelector::Named(name) => {
|
crate::catalog::ProfileSelector::Named(name) => {
|
||||||
Some(std::borrow::Cow::Borrowed(name.as_str()))
|
std::borrow::Cow::Borrowed(name.as_str())
|
||||||
}
|
}
|
||||||
crate::catalog::ProfileSelector::Builtin(name) => {
|
crate::catalog::ProfileSelector::Builtin(name) => {
|
||||||
if name.starts_with("builtin:") {
|
if name.starts_with("builtin:") {
|
||||||
Some(std::borrow::Cow::Borrowed(name.as_str()))
|
std::borrow::Cow::Borrowed(name.as_str())
|
||||||
} else {
|
} else {
|
||||||
Some(std::borrow::Cow::Owned(format!("builtin:{name}")))
|
std::borrow::Cow::Owned(format!("builtin:{name}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_profile_for_request<'a>(
|
fn runtime_profile_for_request(request: &CreateWorkerRequest) -> std::borrow::Cow<'_, str> {
|
||||||
&'a self,
|
|
||||||
request: &'a CreateWorkerRequest,
|
|
||||||
) -> Option<std::borrow::Cow<'a, str>> {
|
|
||||||
if let Some(profile) = self.profile.as_deref() {
|
|
||||||
return Some(std::borrow::Cow::Borrowed(profile));
|
|
||||||
}
|
|
||||||
Self::runtime_profile_value(&request.profile)
|
Self::runtime_profile_value(&request.profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_profile<'a>(
|
fn runtime_profile(request: &WorkerExecutionSpawnRequest) -> std::borrow::Cow<'_, str> {
|
||||||
&'a self,
|
Self::runtime_profile_for_request(&request.request)
|
||||||
request: &'a WorkerExecutionSpawnRequest,
|
|
||||||
) -> Option<std::borrow::Cow<'a, str>> {
|
|
||||||
self.runtime_profile_for_request(&request.request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_fallback_manifest(
|
fn restore_fallback_manifest(
|
||||||
|
|
@ -368,7 +349,7 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||||
request: WorkerExecutionSpawnRequest,
|
request: WorkerExecutionSpawnRequest,
|
||||||
) -> Result<WorkerHandle, String> {
|
) -> Result<WorkerHandle, String> {
|
||||||
let worker_name = Self::runtime_worker_name(&request);
|
let worker_name = Self::runtime_worker_name(&request);
|
||||||
let profile = self.runtime_profile(&request);
|
let profile = Self::runtime_profile(&request);
|
||||||
let has_local_filesystem = request.working_directory.is_some();
|
let has_local_filesystem = request.working_directory.is_some();
|
||||||
let worker_root = request
|
let worker_root = request
|
||||||
.working_directory
|
.working_directory
|
||||||
|
|
@ -388,7 +369,7 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||||
let workspace_backend_ref =
|
let workspace_backend_ref =
|
||||||
RuntimeWorkspaceBackendRef::from_worker_request(&request.request);
|
RuntimeWorkspaceBackendRef::from_worker_request(&request.request);
|
||||||
let workspace_context = workspace_backend_ref.worker_context();
|
let workspace_context = workspace_backend_ref.worker_context();
|
||||||
let selector = profile.as_deref().unwrap_or("builtin:default");
|
let selector = profile.as_ref();
|
||||||
let archive = self
|
let archive = self
|
||||||
.resolve_profile_source_archive(&request.request.profile_source)
|
.resolve_profile_source_archive(&request.request.profile_source)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1421,7 +1402,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
profiles: vec![crate::config_bundle::ConfigProfileDescriptor {
|
profiles: vec![crate::config_bundle::ConfigProfileDescriptor {
|
||||||
selector: ProfileSelector::RuntimeDefault,
|
selector: ProfileSelector::Builtin("builtin:companion".to_string()),
|
||||||
label: Some("adapter-test".to_string()),
|
label: Some("adapter-test".to_string()),
|
||||||
}],
|
}],
|
||||||
declarations: Vec::new(),
|
declarations: Vec::new(),
|
||||||
|
|
@ -1457,7 +1438,7 @@ mod tests {
|
||||||
fn create_request(_name: &str) -> CreateWorkerRequest {
|
fn create_request(_name: &str) -> CreateWorkerRequest {
|
||||||
let bundle = test_bundle();
|
let bundle = test_bundle();
|
||||||
CreateWorkerRequest {
|
CreateWorkerRequest {
|
||||||
profile: ProfileSelector::RuntimeDefault,
|
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
|
||||||
display_name: None,
|
display_name: None,
|
||||||
profile_source: crate::catalog::ProfileSourceArchiveSource::Embedded {
|
profile_source: crate::catalog::ProfileSourceArchiveSource::Embedded {
|
||||||
archive: bundle.profile_source_archive.clone().unwrap(),
|
archive: bundle.profile_source_archive.clone().unwrap(),
|
||||||
|
|
@ -1654,15 +1635,15 @@ mod tests {
|
||||||
ProfileRuntimeWorkerFactory::runtime_profile_value(
|
ProfileRuntimeWorkerFactory::runtime_profile_value(
|
||||||
&crate::catalog::ProfileSelector::Builtin("coder".to_string())
|
&crate::catalog::ProfileSelector::Builtin("coder".to_string())
|
||||||
)
|
)
|
||||||
.as_deref(),
|
.as_ref(),
|
||||||
Some("builtin:coder")
|
"builtin:coder"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ProfileRuntimeWorkerFactory::runtime_profile_value(
|
ProfileRuntimeWorkerFactory::runtime_profile_value(
|
||||||
&crate::catalog::ProfileSelector::Builtin("builtin:coder".to_string())
|
&crate::catalog::ProfileSelector::Builtin("builtin:coder".to_string())
|
||||||
)
|
)
|
||||||
.as_deref(),
|
.as_ref(),
|
||||||
Some("builtin:coder")
|
"builtin:coder"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ struct SpawnWorkerInput {
|
||||||
/// Profile selector for child role configuration. Omit or use `default`
|
/// Profile selector for child role configuration. Omit or use `default`
|
||||||
/// for the effective child default profile, use `inherit` to derive
|
/// for the effective child default profile, use `inherit` to derive
|
||||||
/// reusable config from the spawner, or use a registry selector such as
|
/// reusable config from the spawner, or use a registry selector such as
|
||||||
/// `project:coder`, `project:reviewer`, `builtin:default`, or an
|
/// `project:coder`, `project:reviewer`, `builtin:companion`, or an
|
||||||
/// unambiguous profile slug. Raw/path selectors are rejected.
|
/// unambiguous profile slug. Raw/path selectors are rejected.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
profile: Option<String>,
|
profile: Option<String>,
|
||||||
|
|
@ -1516,7 +1516,7 @@ max_tokens = 3333
|
||||||
assert!(invalid.contains("Use `default`, `inherit`"));
|
assert!(invalid.contains("Use `default`, `inherit`"));
|
||||||
assert!(invalid.contains("`project:coder`"));
|
assert!(invalid.contains("`project:coder`"));
|
||||||
|
|
||||||
let default_config = build_spawn_config_json_for_profile(
|
let default_error = build_spawn_config_json_for_profile(
|
||||||
&parent,
|
&parent,
|
||||||
&available,
|
&available,
|
||||||
&project,
|
&project,
|
||||||
|
|
@ -1525,8 +1525,8 @@ max_tokens = 3333
|
||||||
&scope,
|
&scope,
|
||||||
SpawnProfileSelector::Default,
|
SpawnProfileSelector::Default,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_err();
|
||||||
assert!(default_config.contains("\"name\":\"child\""));
|
assert!(default_error.contains("no default profile is configured"));
|
||||||
|
|
||||||
let user_config = tmp.path().join("user-profiles.toml");
|
let user_config = tmp.path().join("user-profiles.toml");
|
||||||
std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.toml\"\n").unwrap();
|
std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.toml\"\n").unwrap();
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,6 @@ pub struct WorkerSummary {
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
/// Backward-compatible display label. New UI should prefer `display_name`.
|
/// Backward-compatible display label. New UI should prefer `display_name`.
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub role: Option<String>,
|
|
||||||
pub profile: Option<String>,
|
pub profile: Option<String>,
|
||||||
pub singleton_key: Option<String>,
|
pub singleton_key: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -274,8 +273,7 @@ impl<T> RuntimeList<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_retired_companion_worker(worker: &WorkerSummary) -> bool {
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -321,8 +319,7 @@ pub struct WorkerSpawnRequest {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub requested_worker_name: Option<String>,
|
pub requested_worker_name: Option<String>,
|
||||||
pub acceptance: WorkerSpawnAcceptanceRequirement,
|
pub acceptance: WorkerSpawnAcceptanceRequirement,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub profile: ProfileSelector,
|
||||||
pub profile: Option<ProfileSelector>,
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub initial_input: Option<EmbeddedWorkerInput>,
|
pub initial_input: Option<EmbeddedWorkerInput>,
|
||||||
/// Optional safe working-directory creation request. The Workspace server resolves
|
/// Optional safe working-directory creation request. The Workspace server resolves
|
||||||
|
|
@ -1405,7 +1402,6 @@ impl EmbeddedWorkerRuntime {
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
role: profile.clone(),
|
|
||||||
profile,
|
profile,
|
||||||
singleton_key: display.singleton_key,
|
singleton_key: display.singleton_key,
|
||||||
tags: display.tags,
|
tags: display.tags,
|
||||||
|
|
@ -1445,7 +1441,6 @@ impl EmbeddedWorkerRuntime {
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
role: profile.clone(),
|
|
||||||
profile,
|
profile,
|
||||||
singleton_key: display.singleton_key,
|
singleton_key: display.singleton_key,
|
||||||
tags: display.tags,
|
tags: display.tags,
|
||||||
|
|
@ -1705,11 +1700,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = request
|
let profile = request.profile.clone();
|
||||||
.profile
|
let profile_source = match profile_source_archive_source(&request, &profile) {
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| embedded_profile_selector(&request.intent));
|
|
||||||
let profile_source = match default_profile_source_archive_source(&profile) {
|
|
||||||
Ok(source) => source,
|
Ok(source) => source,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
diagnostics.push(diagnostic(
|
diagnostics.push(diagnostic(
|
||||||
|
|
@ -2364,7 +2356,6 @@ impl RemoteWorkerRuntime {
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
role: profile.clone(),
|
|
||||||
profile,
|
profile,
|
||||||
singleton_key: display.singleton_key,
|
singleton_key: display.singleton_key,
|
||||||
tags: display.tags,
|
tags: display.tags,
|
||||||
|
|
@ -2408,7 +2399,6 @@ impl RemoteWorkerRuntime {
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
role: profile.clone(),
|
|
||||||
profile,
|
profile,
|
||||||
singleton_key: display.singleton_key,
|
singleton_key: display.singleton_key,
|
||||||
tags: display.tags,
|
tags: display.tags,
|
||||||
|
|
@ -2678,11 +2668,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||||
)],
|
)],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let profile = request
|
let profile = request.profile.clone();
|
||||||
.profile
|
let profile_source = match profile_source_archive_http_source(
|
||||||
.clone()
|
&request,
|
||||||
.unwrap_or_else(|| embedded_profile_selector(&request.intent));
|
|
||||||
let profile_source = match default_profile_source_archive_http_source(
|
|
||||||
&profile,
|
&profile,
|
||||||
&self.workspace_id,
|
&self.workspace_id,
|
||||||
Some(self.runtime_id.as_str()),
|
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,
|
profile: &ProfileSelector,
|
||||||
) -> Result<ProfileSourceArchiveSource, String> {
|
) -> Result<ProfileSourceArchiveSource, String> {
|
||||||
Ok(ProfileSourceArchiveSource::Embedded {
|
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,
|
profile: &ProfileSelector,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
runtime_id: Option<&str>,
|
runtime_id: Option<&str>,
|
||||||
resource_broker: &BackendResourceBroker,
|
resource_broker: &BackendResourceBroker,
|
||||||
backend_base_url: &str,
|
backend_base_url: &str,
|
||||||
) -> Result<ProfileSourceArchiveSource, String> {
|
) -> 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(
|
let _handle = resource_broker.issue_profile_source_archive_handle(
|
||||||
workspace_id.to_string(),
|
workspace_id.to_string(),
|
||||||
runtime_id,
|
runtime_id,
|
||||||
|
|
@ -2999,7 +3003,7 @@ enum ProfileSourceArchiveTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn default_embedded_config_bundle(
|
fn builtin_profile_config_bundle(
|
||||||
profile: &ProfileSelector,
|
profile: &ProfileSelector,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
runtime_id: Option<&str>,
|
runtime_id: Option<&str>,
|
||||||
|
|
@ -3012,7 +3016,7 @@ fn default_embedded_config_bundle(
|
||||||
.unwrap_or_else(|| "default".to_string())
|
.unwrap_or_else(|| "default".to_string())
|
||||||
.replace([':', '/', ' '], "-")
|
.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 {
|
let (profile_source_archive, profile_source_archive_handle) = match archive_transport {
|
||||||
ProfileSourceArchiveTransport::Inline => (Some(archive), None),
|
ProfileSourceArchiveTransport::Inline => (Some(archive), None),
|
||||||
ProfileSourceArchiveTransport::BackendResourceHandle => {
|
ProfileSourceArchiveTransport::BackendResourceHandle => {
|
||||||
|
|
@ -3048,17 +3052,13 @@ fn default_embedded_config_bundle(
|
||||||
.with_computed_digest())
|
.with_computed_digest())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_profile_source_archive(
|
fn builtin_profile_source_archive(
|
||||||
profile: &ProfileSelector,
|
profile: &ProfileSelector,
|
||||||
) -> Result<ProfileSourceArchive, String> {
|
) -> 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 selected_path = embedded_profile_path(profile)?;
|
||||||
let mut entrypoints = BTreeMap::new();
|
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 [
|
for slug in [
|
||||||
"companion",
|
"companion",
|
||||||
"intake",
|
"intake",
|
||||||
|
|
@ -3073,8 +3073,8 @@ fn default_profile_source_archive(
|
||||||
|
|
||||||
let mut sources = BTreeMap::new();
|
let mut sources = BTreeMap::new();
|
||||||
sources.insert(
|
sources.insert(
|
||||||
"profiles/default.dcdl".to_string(),
|
"profiles/base.dcdl".to_string(),
|
||||||
include_str!("../../../resources/profiles/default.dcdl").to_string(),
|
include_str!("../../../resources/profiles/base.dcdl").to_string(),
|
||||||
);
|
);
|
||||||
sources.insert(
|
sources.insert(
|
||||||
"profiles/companion.dcdl".to_string(),
|
"profiles/companion.dcdl".to_string(),
|
||||||
|
|
@ -3111,8 +3111,8 @@ fn default_profile_source_archive(
|
||||||
"memory-consolidation",
|
"memory-consolidation",
|
||||||
] {
|
] {
|
||||||
imports.insert(
|
imports.insert(
|
||||||
format!("profiles/{slug}.dcdl\0./default.dcdl"),
|
format!("profiles/{slug}.dcdl\0./base.dcdl"),
|
||||||
"profiles/default.dcdl".to_string(),
|
"profiles/base.dcdl".to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3127,9 +3127,7 @@ fn default_profile_source_archive(
|
||||||
|
|
||||||
fn embedded_profile_path(profile: &ProfileSelector) -> Result<String, String> {
|
fn embedded_profile_path(profile: &ProfileSelector) -> Result<String, String> {
|
||||||
match profile {
|
match profile {
|
||||||
ProfileSelector::RuntimeDefault => Ok("profiles/default.dcdl".to_string()),
|
|
||||||
ProfileSelector::Builtin(name) => match name.strip_prefix("builtin:").unwrap_or(name) {
|
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()),
|
"companion" => Ok("profiles/companion.dcdl".to_string()),
|
||||||
"intake" => Ok("profiles/intake.dcdl".to_string()),
|
"intake" => Ok("profiles/intake.dcdl".to_string()),
|
||||||
"orchestrator" => Ok("profiles/orchestrator.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> {
|
fn embedded_profile_label(profile: &ProfileSelector) -> Option<String> {
|
||||||
Some(match profile {
|
Some(match profile {
|
||||||
ProfileSelector::RuntimeDefault => "runtime_default".to_string(),
|
|
||||||
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => {
|
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => {
|
||||||
if name.strip_prefix("builtin:").unwrap_or(name) == MEMORY_CONSOLIDATION_PROFILE {
|
if name.strip_prefix("builtin:").unwrap_or(name) == MEMORY_CONSOLIDATION_PROFILE {
|
||||||
MEMORY_CONSOLIDATION_PROFILE.to_string()
|
MEMORY_CONSOLIDATION_PROFILE.to_string()
|
||||||
|
|
@ -3227,21 +3202,18 @@ fn worker_display_metadata(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn profile_display_name(profile_label: &str) -> String {
|
fn profile_display_name(profile_label: &str) -> String {
|
||||||
match profile_label {
|
profile_label
|
||||||
"runtime_default" => "Default Worker".to_string(),
|
.split(['-', '_'])
|
||||||
value => value
|
.filter(|part| !part.is_empty())
|
||||||
.split(['-', '_'])
|
.map(|part| {
|
||||||
.filter(|part| !part.is_empty())
|
let mut chars = part.chars();
|
||||||
.map(|part| {
|
match chars.next() {
|
||||||
let mut chars = part.chars();
|
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
|
||||||
match chars.next() {
|
None => String::new(),
|
||||||
Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
|
}
|
||||||
None => String::new(),
|
})
|
||||||
}
|
.collect::<Vec<_>>()
|
||||||
})
|
.join(" ")
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" "),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn embedded_input_rejected(
|
fn embedded_input_rejected(
|
||||||
|
|
@ -3632,7 +3604,6 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
|
||||||
host_id,
|
host_id,
|
||||||
display_name: "Worker runtime actions are not implemented".to_string(),
|
display_name: "Worker runtime actions are not implemented".to_string(),
|
||||||
label: "Worker runtime actions are not implemented".to_string(),
|
label: "Worker runtime actions are not implemented".to_string(),
|
||||||
role: None,
|
|
||||||
profile: None,
|
profile: None,
|
||||||
singleton_key: None,
|
singleton_key: None,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
|
|
@ -3690,7 +3661,6 @@ mod tests {
|
||||||
let broker = BackendResourceBroker::default();
|
let broker = BackendResourceBroker::default();
|
||||||
let runtime_id = "runtime-test";
|
let runtime_id = "runtime-test";
|
||||||
for selector in [
|
for selector in [
|
||||||
ProfileSelector::RuntimeDefault,
|
|
||||||
ProfileSelector::Builtin("builtin:companion".to_string()),
|
ProfileSelector::Builtin("builtin:companion".to_string()),
|
||||||
ProfileSelector::Builtin("builtin:intake".to_string()),
|
ProfileSelector::Builtin("builtin:intake".to_string()),
|
||||||
ProfileSelector::Builtin("builtin:orchestrator".to_string()),
|
ProfileSelector::Builtin("builtin:orchestrator".to_string()),
|
||||||
|
|
@ -3698,7 +3668,7 @@ mod tests {
|
||||||
ProfileSelector::Builtin("builtin:reviewer".to_string()),
|
ProfileSelector::Builtin("builtin:reviewer".to_string()),
|
||||||
ProfileSelector::Builtin("builtin:memory-consolidation".to_string()),
|
ProfileSelector::Builtin("builtin:memory-consolidation".to_string()),
|
||||||
] {
|
] {
|
||||||
let bundle = default_embedded_config_bundle(
|
let bundle = builtin_profile_config_bundle(
|
||||||
&selector,
|
&selector,
|
||||||
"workspace-test",
|
"workspace-test",
|
||||||
Some(runtime_id),
|
Some(runtime_id),
|
||||||
|
|
@ -3724,9 +3694,7 @@ mod tests {
|
||||||
.verify()
|
.verify()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let selector_key = match &selector {
|
let selector_key = match &selector {
|
||||||
ProfileSelector::RuntimeDefault => "default".to_string(),
|
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => name.clone(),
|
||||||
ProfileSelector::Builtin(name) => name.clone(),
|
|
||||||
ProfileSelector::Named(name) => name.clone(),
|
|
||||||
};
|
};
|
||||||
let manifest = archive
|
let manifest = archive
|
||||||
.resolve_profile(&selector_key, root.path(), "embedded-test-worker")
|
.resolve_profile(&selector_key, root.path(), "embedded-test-worker")
|
||||||
|
|
@ -3745,7 +3713,7 @@ mod tests {
|
||||||
let root = tempfile::tempdir().unwrap();
|
let root = tempfile::tempdir().unwrap();
|
||||||
let broker = BackendResourceBroker::default();
|
let broker = BackendResourceBroker::default();
|
||||||
let runtime_id = "remote:test";
|
let runtime_id = "remote:test";
|
||||||
let bundle = default_embedded_config_bundle(
|
let bundle = builtin_profile_config_bundle(
|
||||||
&ProfileSelector::Builtin("builtin:coder".to_string()),
|
&ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
"workspace-test",
|
"workspace-test",
|
||||||
Some(runtime_id),
|
Some(runtime_id),
|
||||||
|
|
@ -3757,7 +3725,7 @@ mod tests {
|
||||||
let archive = bundle
|
let archive = bundle
|
||||||
.profile_source_archive
|
.profile_source_archive
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("remote default bundle carries inline profile archive")
|
.expect("remote built-in bundle carries inline profile archive")
|
||||||
.verify()
|
.verify()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let manifest = archive
|
let manifest = archive
|
||||||
|
|
@ -3766,11 +3734,38 @@ mod tests {
|
||||||
assert_eq!(manifest.worker.name, "remote-test-worker");
|
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]
|
#[test]
|
||||||
fn remote_profile_source_archive_url_uses_workspace_id_not_host_id() {
|
fn remote_profile_source_archive_url_uses_workspace_id_not_host_id() {
|
||||||
let broker = BackendResourceBroker::default();
|
let broker = BackendResourceBroker::default();
|
||||||
let runtime_id = "remote:test";
|
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()),
|
&ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
"workspace-actual",
|
"workspace-actual",
|
||||||
Some(runtime_id),
|
Some(runtime_id),
|
||||||
|
|
@ -3796,7 +3791,7 @@ mod tests {
|
||||||
let broker = BackendResourceBroker::default();
|
let broker = BackendResourceBroker::default();
|
||||||
let runtime_id = "runtime-test";
|
let runtime_id = "runtime-test";
|
||||||
assert!(
|
assert!(
|
||||||
default_embedded_config_bundle(
|
builtin_profile_config_bundle(
|
||||||
&ProfileSelector::Builtin("builtin:missing".to_string()),
|
&ProfileSelector::Builtin("builtin:missing".to_string()),
|
||||||
"workspace-test",
|
"workspace-test",
|
||||||
Some(runtime_id),
|
Some(runtime_id),
|
||||||
|
|
@ -3806,7 +3801,7 @@ mod tests {
|
||||||
.is_err()
|
.is_err()
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
default_embedded_config_bundle(
|
builtin_profile_config_bundle(
|
||||||
&ProfileSelector::Named("custom".to_string()),
|
&ProfileSelector::Named("custom".to_string()),
|
||||||
"workspace-test",
|
"workspace-test",
|
||||||
Some(runtime_id),
|
Some(runtime_id),
|
||||||
|
|
@ -3966,7 +3961,6 @@ mod tests {
|
||||||
host_id: host_id.to_string(),
|
host_id: host_id.to_string(),
|
||||||
display_name: label.to_string(),
|
display_name: label.to_string(),
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
role: None,
|
|
||||||
profile: None,
|
profile: None,
|
||||||
singleton_key: None,
|
singleton_key: None,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
|
|
@ -4158,7 +4152,7 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 0,
|
expected_segments: 0,
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -4284,7 +4278,7 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 0,
|
expected_segments: 0,
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -4380,7 +4374,7 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 0,
|
expected_segments: 0,
|
||||||
},
|
},
|
||||||
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -4412,7 +4406,7 @@ mod tests {
|
||||||
intent: WorkerSpawnIntent::WorkspaceCompanion,
|
intent: WorkerSpawnIntent::WorkspaceCompanion,
|
||||||
requested_worker_name: None,
|
requested_worker_name: None,
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
||||||
profile: None,
|
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_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 PROFILE_SOURCE_TREE_DISPLAY_ROOT: &str = "profiles";
|
||||||
const MAX_PROFILE_SOURCE_BYTES: u64 = 256 * 1024;
|
const MAX_PROFILE_SOURCE_BYTES: u64 = 256 * 1024;
|
||||||
const BUILTIN_PROFILE_IDS: &[&str] = &[
|
const BUILTIN_PROFILE_IDS: &[&str] = &[
|
||||||
"builtin:default",
|
|
||||||
"builtin:companion",
|
"builtin:companion",
|
||||||
"builtin:intake",
|
"builtin:intake",
|
||||||
"builtin:orchestrator",
|
"builtin:orchestrator",
|
||||||
"builtin:coder",
|
"builtin:coder",
|
||||||
"builtin:reviewer",
|
"builtin:reviewer",
|
||||||
];
|
];
|
||||||
const BUILTIN_PROFILE_SLUGS: &[&str] = &[
|
const BUILTIN_PROFILE_SLUGS: &[&str] =
|
||||||
"default",
|
&["companion", "intake", "orchestrator", "coder", "reviewer"];
|
||||||
"companion",
|
|
||||||
"intake",
|
|
||||||
"orchestrator",
|
|
||||||
"coder",
|
|
||||||
"reviewer",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct WorkspaceMetadataSettingsResponse {
|
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 {
|
pub fn is_profile_candidate(workspace_root: &Path, profile_id: &str) -> bool {
|
||||||
BUILTIN_PROFILE_IDS.contains(&profile_id)
|
BUILTIN_PROFILE_IDS.contains(&profile_id)
|
||||||
|| profile_id == "runtime_default"
|
|
||||||
|| project_profile_candidates(workspace_root)
|
|| project_profile_candidates(workspace_root)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|profile| profile.profile_id == profile_id)
|
.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> {
|
fn builtin_profile_summaries(default_profile: Option<&str>) -> Vec<WorkspaceProfileSummary> {
|
||||||
let labels = [
|
let labels = [
|
||||||
("builtin:default", "Default", "Bundled default Yoi profile"),
|
|
||||||
(
|
(
|
||||||
"builtin:companion",
|
"builtin:companion",
|
||||||
"Companion",
|
"Companion",
|
||||||
|
|
@ -1191,15 +1182,6 @@ fn build_profile_archive_from_tree_sources(
|
||||||
entrypoints.insert(project_selector(&entry.name), path);
|
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)
|
build_profile_archive_from_source_set(entrypoints, sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1920,9 +1902,7 @@ pub fn selector_for_builtin_candidate(
|
||||||
id: &str,
|
id: &str,
|
||||||
) -> Option<worker_runtime::catalog::ProfileSelector> {
|
) -> Option<worker_runtime::catalog::ProfileSelector> {
|
||||||
match id {
|
match id {
|
||||||
"runtime_default" => Some(worker_runtime::catalog::ProfileSelector::RuntimeDefault),
|
"builtin:companion"
|
||||||
"builtin:default"
|
|
||||||
| "builtin:companion"
|
|
||||||
| "builtin:intake"
|
| "builtin:intake"
|
||||||
| "builtin:orchestrator"
|
| "builtin:orchestrator"
|
||||||
| "builtin:coder"
|
| "builtin:coder"
|
||||||
|
|
|
||||||
|
|
@ -1059,6 +1059,7 @@ pub struct RemoteRuntimeTestResponse {
|
||||||
pub struct WorkerLaunchOptionsResponse {
|
pub struct WorkerLaunchOptionsResponse {
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
|
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
|
||||||
|
pub default_profile: Option<String>,
|
||||||
pub profiles: Vec<WorkerLaunchProfileCandidate>,
|
pub profiles: Vec<WorkerLaunchProfileCandidate>,
|
||||||
pub repositories: Vec<WorkingDirectoryRepositoryOption>,
|
pub repositories: Vec<WorkingDirectoryRepositoryOption>,
|
||||||
pub working_directories: Vec<WorkingDirectorySummary>,
|
pub working_directories: Vec<WorkingDirectorySummary>,
|
||||||
|
|
@ -1128,7 +1129,8 @@ pub struct BrowserWorkerWorkingDirectorySelection {
|
||||||
pub struct BrowserCreateWorkerRequest {
|
pub struct BrowserCreateWorkerRequest {
|
||||||
pub runtime_id: String,
|
pub runtime_id: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
pub profile: String,
|
#[serde(default)]
|
||||||
|
pub profile: Option<String>,
|
||||||
pub initial_text: String,
|
pub initial_text: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
|
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
|
||||||
|
|
@ -1679,7 +1681,7 @@ fn start_memory_staging_consolidation(
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 1,
|
expected_segments: 1,
|
||||||
},
|
},
|
||||||
profile: Some(profile_selector),
|
profile: profile_selector,
|
||||||
initial_input: Some(input),
|
initial_input: Some(input),
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_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 {
|
fn is_memory_consolidation_worker(worker: &WorkerSummary) -> bool {
|
||||||
worker.singleton_key.as_deref() == Some(MEMORY_CONSOLIDATION_SINGLETON_KEY)
|
worker.singleton_key.as_deref() == Some(MEMORY_CONSOLIDATION_SINGLETON_KEY)
|
||||||
|| [worker.profile.as_deref(), worker.role.as_deref()]
|
|| worker.profile.as_deref() == Some(MEMORY_CONSOLIDATION_PROFILE)
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.any(|value| value == MEMORY_CONSOLIDATION_PROFILE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn memory_consolidation_input_content(candidate_count: usize, total_bytes: u64) -> String {
|
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>,
|
State(api): State<WorkspaceApi>,
|
||||||
Json(request): Json<BrowserCreateWorkerRequest>,
|
Json(request): Json<BrowserCreateWorkerRequest>,
|
||||||
) -> ApiResult<Json<BrowserCreateWorkerResponse>> {
|
) -> 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 =
|
let profile_selector =
|
||||||
profile_selector_for_candidate_with_root(&api.config.workspace_root, &request.profile)
|
profile_selector_for_candidate_with_root(&api.config.workspace_root, &profile).ok_or_else(
|
||||||
.ok_or_else(|| {
|
|| {
|
||||||
settings_bad_request(
|
settings_bad_request(
|
||||||
"unsupported_worker_profile",
|
"unsupported_worker_profile",
|
||||||
"profile must be selected from Backend-published worker profile candidates",
|
"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(
|
crate::profile_settings::build_workspace_profile_config_bundle(
|
||||||
&api.config.workspace_root,
|
&api.config.workspace_root,
|
||||||
&api.config.workspace_id,
|
&api.config.workspace_id,
|
||||||
&api.config.workspace_created_at,
|
&api.config.workspace_created_at,
|
||||||
&request.profile,
|
&profile,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -4320,7 +4339,7 @@ async fn create_workspace_worker(
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: if initial_input.is_some() { 1 } else { 0 },
|
expected_segments: if initial_input.is_some() { 1 } else { 0 },
|
||||||
},
|
},
|
||||||
profile: Some(profile_selector),
|
profile: profile_selector,
|
||||||
initial_input,
|
initial_input,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -5773,10 +5792,32 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResp
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.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 {
|
WorkerLaunchOptionsResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
runtimes,
|
runtimes,
|
||||||
profiles: worker_profile_candidates_for_root(&api.config.workspace_root),
|
default_profile: profile_settings.default_profile,
|
||||||
|
profiles,
|
||||||
repositories: working_directory_repository_options(api),
|
repositories: working_directory_repository_options(api),
|
||||||
working_directories: available_working_directory_summaries(api).unwrap_or_default(),
|
working_directories: available_working_directory_summaries(api).unwrap_or_default(),
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
|
|
@ -5932,7 +5973,6 @@ fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary
|
||||||
worker_id: record.runtime_worker_id.to_string(),
|
worker_id: record.runtime_worker_id.to_string(),
|
||||||
runtime_id: record.runtime_id.clone(),
|
runtime_id: record.runtime_id.clone(),
|
||||||
host_id: "backend-registry".to_string(),
|
host_id: "backend-registry".to_string(),
|
||||||
role: None,
|
|
||||||
display_name: record.display_name.clone(),
|
display_name: record.display_name.clone(),
|
||||||
label: record.display_name.clone(),
|
label: record.display_name.clone(),
|
||||||
singleton_key: None,
|
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(
|
fn profile_selector_for_candidate_with_root(
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
profile: &str,
|
profile: &str,
|
||||||
) -> Option<ProfileSelector> {
|
) -> Option<ProfileSelector> {
|
||||||
if profile_selector_for_candidate(profile).is_some() {
|
if let Some(selector @ ProfileSelector::Builtin(_)) =
|
||||||
profile_selector_for_candidate(profile)
|
|
||||||
} else if crate::profile_settings::is_profile_candidate(workspace_root, profile) {
|
|
||||||
crate::profile_settings::selector_for_builtin_candidate(profile)
|
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> {
|
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) {
|
if display_name.chars().any(char::is_control) {
|
||||||
None
|
None
|
||||||
} else if display_name.is_empty() {
|
} else if display_name.is_empty() {
|
||||||
Some("Coding Worker".to_string())
|
Some("Worker".to_string())
|
||||||
} else {
|
} else {
|
||||||
Some(display_name.chars().take(80).collect())
|
Some(display_name.chars().take(80).collect())
|
||||||
}
|
}
|
||||||
|
|
@ -7065,17 +7072,89 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn worker_profile_candidates_are_backend_published_and_mapped() {
|
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!(
|
assert!(
|
||||||
candidates
|
settings
|
||||||
|
.profiles
|
||||||
.iter()
|
.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!(
|
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"
|
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]
|
#[test]
|
||||||
|
|
@ -7258,11 +7337,19 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
fs::write(dir.path().join(".yoi/profiles/bad.dcdl"), "not decodal").unwrap();
|
fs::write(dir.path().join(".yoi/profiles/bad.dcdl"), "not decodal").unwrap();
|
||||||
assert!(
|
let candidates =
|
||||||
worker_profile_candidates_for_root(dir.path())
|
crate::profile_settings::load_profile_settings("workspace-test", dir.path())
|
||||||
.iter()
|
.profiles
|
||||||
.all(|candidate| candidate.id != "project:bad")
|
.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());
|
assert!(profile_selector_for_candidate_with_root(dir.path(), "project:bad").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7583,9 +7670,7 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 0,
|
expected_segments: 0,
|
||||||
},
|
},
|
||||||
profile: Some(ProfileSelector::Builtin(
|
profile: ProfileSelector::Builtin(MEMORY_CONSOLIDATION_PROFILE.to_string()),
|
||||||
MEMORY_CONSOLIDATION_PROFILE.to_string(),
|
|
||||||
)),
|
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -8141,7 +8226,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
profiles: vec![worker_runtime::config_bundle::ConfigProfileDescriptor {
|
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()),
|
label: Some("server-test".to_string()),
|
||||||
}],
|
}],
|
||||||
declarations: Vec::new(),
|
declarations: Vec::new(),
|
||||||
|
|
@ -8154,7 +8241,9 @@ mod tests {
|
||||||
fn runtime_create_request() -> worker_runtime::catalog::CreateWorkerRequest {
|
fn runtime_create_request() -> worker_runtime::catalog::CreateWorkerRequest {
|
||||||
let bundle = runtime_test_bundle();
|
let bundle = runtime_test_bundle();
|
||||||
worker_runtime::catalog::CreateWorkerRequest {
|
worker_runtime::catalog::CreateWorkerRequest {
|
||||||
profile: worker_runtime::catalog::ProfileSelector::RuntimeDefault,
|
profile: worker_runtime::catalog::ProfileSelector::Builtin(
|
||||||
|
"builtin:companion".to_string(),
|
||||||
|
),
|
||||||
display_name: None,
|
display_name: None,
|
||||||
profile_source: worker_runtime::catalog::ProfileSourceArchiveSource::Http {
|
profile_source: worker_runtime::catalog::ProfileSourceArchiveSource::Http {
|
||||||
location: worker_runtime::catalog::ProfileSourceArchiveHttpRef {
|
location: worker_runtime::catalog::ProfileSourceArchiveHttpRef {
|
||||||
|
|
@ -8534,8 +8623,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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();
|
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 app = test_app(dir.path()).await;
|
||||||
let created = post_json(
|
let created = post_json(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
|
|
@ -8543,7 +8638,6 @@ mod tests {
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"runtime_id": "embedded-worker-runtime",
|
"runtime_id": "embedded-worker-runtime",
|
||||||
"display_name": "",
|
"display_name": "",
|
||||||
"profile": "runtime_default",
|
|
||||||
"initial_text": ""
|
"initial_text": ""
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -8556,7 +8650,9 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|worker| worker["worker_id"] == created["worker_id"])
|
.find(|worker| worker["worker_id"] == created["worker_id"])
|
||||||
.expect("created Worker should be listed");
|
.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"]);
|
assert_eq!(worker["worker_id"], created["worker_id"]);
|
||||||
let detail_path = format!(
|
let detail_path = format!(
|
||||||
"/api/runtimes/{}/workers/{}",
|
"/api/runtimes/{}/workers/{}",
|
||||||
|
|
@ -8564,7 +8660,7 @@ mod tests {
|
||||||
created["worker_id"].as_str().unwrap()
|
created["worker_id"].as_str().unwrap()
|
||||||
);
|
);
|
||||||
let detail = get_json(app.clone(), detail_path.as_str()).await;
|
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_eq!(detail["worker_id"], created["worker_id"]);
|
||||||
assert!(
|
assert!(
|
||||||
created["console_href"]
|
created["console_href"]
|
||||||
|
|
@ -8607,7 +8703,7 @@ mod tests {
|
||||||
Some(serde_json::json!({
|
Some(serde_json::json!({
|
||||||
"runtime_id": "remote-runtime",
|
"runtime_id": "remote-runtime",
|
||||||
"display_name": "Remote Worker",
|
"display_name": "Remote Worker",
|
||||||
"profile": "runtime_default",
|
"profile": "builtin:companion",
|
||||||
"initial_text": ""
|
"initial_text": ""
|
||||||
})),
|
})),
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
|
|
@ -8646,6 +8742,10 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
|
"profile": {
|
||||||
|
"kind": "builtin",
|
||||||
|
"value": "builtin:coder"
|
||||||
|
},
|
||||||
"working_directory_request": {
|
"working_directory_request": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"local_path": dir.path().display().to_string()
|
"local_path": dir.path().display().to_string()
|
||||||
|
|
@ -8681,6 +8781,10 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
|
"profile": {
|
||||||
|
"kind": "builtin",
|
||||||
|
"value": "builtin:coder"
|
||||||
|
},
|
||||||
"working_directory_request": {
|
"working_directory_request": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"selector": "HEAD"
|
"selector": "HEAD"
|
||||||
|
|
@ -9259,7 +9363,7 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||||
expected_segments: 0,
|
expected_segments: 0,
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory_request: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory_request: None,
|
resolved_working_directory_request: None,
|
||||||
|
|
@ -9501,6 +9605,10 @@ mod tests {
|
||||||
"acceptance": {
|
"acceptance": {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"kind": "builtin",
|
||||||
|
"value": "builtin:coder"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
29
docs/report/2026-07-30-spawn-worker-runtime-cli-drift.md
Normal file
29
docs/report/2026-07-30-spawn-worker-runtime-cli-drift.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# SpawnWorker failed because the active Runtime CLI rejected its launch protocol
|
||||||
|
|
||||||
|
Date: 2026-07-30
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
While implementing Ticket `00001KYS7YMN5`, the parent Worker delegated two read-only code-mapping tasks through `SpawnWorker`.
|
||||||
|
|
||||||
|
Both launches failed before a child socket appeared. The spawned process reported:
|
||||||
|
|
||||||
|
```text
|
||||||
|
yoi-runtime: unexpected positional argument `worker`
|
||||||
|
|
||||||
|
Usage: yoi-runtime [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
The active `yoi-runtime` binary accepts only its HTTP Runtime server options and auth subcommands, while the Worker-management layer attempted to start a child through a legacy `worker` positional command.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Delegated read-only investigation was unavailable.
|
||||||
|
- The parent Worker had to perform the code mapping directly.
|
||||||
|
- This is protocol/version drift between the Worker-management launch path and the active Runtime binary, not a failure in the delegated task or scope.
|
||||||
|
|
||||||
|
## Suggested improvement
|
||||||
|
|
||||||
|
Make the child-launch protocol explicit and versioned rather than invoking a user-facing Runtime CLI subcommand implicitly. At minimum, Runtime registration/health should advertise the supported spawn protocol, and `SpawnWorker` should reject an incompatible Runtime with a bounded compatibility diagnostic before attempting to create a child socket.
|
||||||
|
|
||||||
|
The active backend/runtime should also be rebuilt and restarted together after CLI/launch-protocol changes so long-running Worker-management processes do not retain an obsolete invocation contract.
|
||||||
|
|
@ -32,6 +32,17 @@ context_window = 256000
|
||||||
# Codex CLI's model catalog exposes these coding models with a 272k effective
|
# Codex CLI's model catalog exposes these coding models with a 272k effective
|
||||||
# context window on this route, even when public API docs advertise larger
|
# context window on this route, even when public API docs advertise larger
|
||||||
# model-family windows.
|
# model-family windows.
|
||||||
|
# Codex currently caps the ChatGPT-backed route at 272k even though the public
|
||||||
|
# GPT-5.6 Sol API model advertises a 1.05M context window. Keep both values so
|
||||||
|
# resolution and compaction use the effective backend limit without losing the
|
||||||
|
# underlying model capability.
|
||||||
|
[[model]]
|
||||||
|
id = "gpt-5.6-sol"
|
||||||
|
provider = "codex-oauth"
|
||||||
|
context_window = 1050000
|
||||||
|
max_context_window = 272000
|
||||||
|
capability = { tool_calling = "parallel", structured_output = "json_schema", reasoning = "effort", vision = true, prompt_caching = { kind = "auto" } }
|
||||||
|
|
||||||
[[model]]
|
[[model]]
|
||||||
id = "gpt-5.5"
|
id = "gpt-5.5"
|
||||||
provider = "codex-oauth"
|
provider = "codex-oauth"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
slug = "default";
|
slug = "base";
|
||||||
description = "Default Yoi coding profile.";
|
description = "Shared base configuration for Yoi Worker profiles.";
|
||||||
scope = "workspace_write";
|
scope = "workspace_write";
|
||||||
|
|
||||||
model = {
|
model = {
|
||||||
ref = "codex-oauth/gpt-5.5";
|
ref = "codex-oauth/gpt-5.6-sol";
|
||||||
};
|
};
|
||||||
|
|
||||||
session = {
|
session = {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "coder";
|
slug = "coder";
|
||||||
description = "Ticket implementation coder profile.";
|
description = "Ticket implementation coder profile.";
|
||||||
scope = "workspace_write";
|
scope = "workspace_write";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "companion";
|
slug = "companion";
|
||||||
description = "Workspace companion profile.";
|
description = "Workspace companion profile.";
|
||||||
scope = "workspace_write";
|
scope = "workspace_write";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "intake";
|
slug = "intake";
|
||||||
description = "Ticket intake profile.";
|
description = "Ticket intake profile.";
|
||||||
scope = "workspace_write";
|
scope = "workspace_write";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "memory-consolidation";
|
slug = "memory-consolidation";
|
||||||
description = "Memory staging consolidation profile.";
|
description = "Memory staging consolidation profile.";
|
||||||
scope = "workspace_read";
|
scope = "workspace_read";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "orchestrator";
|
slug = "orchestrator";
|
||||||
description = "Ticket orchestrator profile.";
|
description = "Ticket orchestrator profile.";
|
||||||
scope = "workspace_write";
|
scope = "workspace_write";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import "./default.dcdl" // {
|
import "./base.dcdl" // {
|
||||||
slug = "reviewer";
|
slug = "reviewer";
|
||||||
description = "Ticket review profile.";
|
description = "Ticket review profile.";
|
||||||
scope = "workspace_read";
|
scope = "workspace_read";
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@
|
||||||
<span class="worker-task-title">-</span>
|
<span class="worker-task-title">-</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="item-meta">
|
<span class="item-meta">
|
||||||
worker {worker.worker_id} · {worker.role ? `${worker.role} · ` : ''}{worker.state} · 🖥 {worker.host_id}
|
worker {worker.worker_id} · {worker.profile ? `${worker.profile} · ` : ''}{worker.state} · 🖥 {worker.host_id}
|
||||||
{worker.working_directory ? ` · wd:${worker.working_directory.repository_id}@${worker.working_directory.resolved_commit.slice(0, 8)}` : ''}
|
{worker.working_directory ? ` · wd:${worker.working_directory.repository_id}@${worker.working_directory.resolved_commit.slice(0, 8)}` : ''}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ export type Worker = {
|
||||||
host_id: string;
|
host_id: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
label: string;
|
label: string;
|
||||||
role?: string | null;
|
|
||||||
profile?: string | null;
|
profile?: string | null;
|
||||||
singleton_key?: string | null;
|
singleton_key?: string | null;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
|
@ -233,6 +232,7 @@ export type BrowserWorkingDirectoryCreateRequest = {
|
||||||
export type WorkerLaunchOptionsResponse = {
|
export type WorkerLaunchOptionsResponse = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
runtimes: WorkerLaunchRuntimeOption[];
|
runtimes: WorkerLaunchRuntimeOption[];
|
||||||
|
default_profile?: string | null;
|
||||||
profiles: WorkerLaunchProfileCandidate[];
|
profiles: WorkerLaunchProfileCandidate[];
|
||||||
repositories: WorkingDirectoryRepositoryOption[];
|
repositories: WorkingDirectoryRepositoryOption[];
|
||||||
working_directories: WorkingDirectorySummary[];
|
working_directories: WorkingDirectorySummary[];
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
default_profile: "builtin:coder",
|
||||||
profiles: [
|
profiles: [
|
||||||
{ id: "builtin:companion", label: "Companion", description: "chat" },
|
{ id: "builtin:companion", label: "Companion", description: "chat" },
|
||||||
{ id: "builtin:coder", label: "Coder", description: "code" },
|
{ id: "builtin:coder", label: "Coder", description: "code" },
|
||||||
|
|
@ -65,7 +66,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, repository, and working directory", () => {
|
Deno.test("defaultWorkerLaunchForm uses the Backend-published Workspace default profile", () => {
|
||||||
const form = defaultWorkerLaunchForm(options, {
|
const form = defaultWorkerLaunchForm(options, {
|
||||||
runtime_id: "",
|
runtime_id: "",
|
||||||
display_name: "",
|
display_name: "",
|
||||||
|
|
@ -78,7 +79,7 @@ Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, reposi
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(form.runtime_id, "remote");
|
assertEquals(form.runtime_id, "remote");
|
||||||
assertEquals(form.display_name, "Coding Worker");
|
assertEquals(form.display_name, "Worker");
|
||||||
assertEquals(form.profile, "builtin:coder");
|
assertEquals(form.profile, "builtin:coder");
|
||||||
assertEquals(form.initial_text, "hello");
|
assertEquals(form.initial_text, "hello");
|
||||||
assertEquals(form.working_directory_id, "wd-1-repo");
|
assertEquals(form.working_directory_id, "wd-1-repo");
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ export function defaultWorkerLaunchForm(
|
||||||
) ??
|
) ??
|
||||||
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ??
|
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ??
|
||||||
options?.runtimes[0];
|
options?.runtimes[0];
|
||||||
const preferredProfile =
|
const preferredProfile = options?.profiles.find((candidate) =>
|
||||||
options?.profiles.find((candidate) => candidate.id === "builtin:coder") ??
|
candidate.id === options.default_profile
|
||||||
options?.profiles[0];
|
);
|
||||||
const availableWorkingDirectories =
|
const availableWorkingDirectories =
|
||||||
options?.working_directories.filter((directory) =>
|
options?.working_directories.filter((directory) =>
|
||||||
directory.status === "active" &&
|
directory.status === "active" &&
|
||||||
|
|
@ -60,7 +60,7 @@ export function defaultWorkerLaunchForm(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || "",
|
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || "",
|
||||||
display_name: current.display_name || "Coding Worker",
|
display_name: current.display_name || "Worker",
|
||||||
profile:
|
profile:
|
||||||
options?.profiles.some((candidate) => candidate.id === current.profile)
|
options?.profiles.some((candidate) => candidate.id === current.profile)
|
||||||
? current.profile
|
? current.profile
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ function worker(overrides: Partial<Worker>): Worker {
|
||||||
host_id: "host",
|
host_id: "host",
|
||||||
display_name: "Worker 1",
|
display_name: "Worker 1",
|
||||||
label: "Worker 1",
|
label: "Worker 1",
|
||||||
role: null,
|
|
||||||
profile: null,
|
profile: null,
|
||||||
singleton_key: null,
|
singleton_key: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|
|
||||||
|
|
@ -1259,11 +1259,8 @@
|
||||||
<dd><code>{worker.host_id}</code></dd>
|
<dd><code>{worker.host_id}</code></dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt>Role / profile</dt>
|
<dt>Profile</dt>
|
||||||
<dd>
|
<dd>{worker.profile ?? "unknown"}</dd>
|
||||||
{worker.role ?? "unknown"} / {worker.profile ??
|
|
||||||
"unknown"}
|
|
||||||
</dd>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt>Workspace</dt>
|
<dt>Workspace</dt>
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function workerProfile(worker: Worker): string {
|
function workerProfile(worker: Worker): string {
|
||||||
return worker.profile ?? worker.role ?? 'unknown';
|
return worker.profile ?? 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
function workerDirectory(worker: Worker): string {
|
function workerDirectory(worker: Worker): string {
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@
|
||||||
let optionsError = $state<string | null>(null);
|
let optionsError = $state<string | null>(null);
|
||||||
let submitting = $state(false);
|
let submitting = $state(false);
|
||||||
let submitError = $state<DisplayError | null>(null);
|
let submitError = $state<DisplayError | null>(null);
|
||||||
let displayName = $state('Coding Worker');
|
let displayName = $state('Worker');
|
||||||
let runtimeId = $state('');
|
let runtimeId = $state('');
|
||||||
let profile = $state('builtin:coder');
|
let profile = $state('');
|
||||||
let initialText = $state('');
|
let initialText = $state('');
|
||||||
let workingDirectoryId = $state('');
|
let workingDirectoryId = $state('');
|
||||||
let workingDirectoryRepositoryId = $state('');
|
let workingDirectoryRepositoryId = $state('');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user