fix: remove misleading runtime capability projections

This commit is contained in:
2026-08-26 06:19:33 +09:00
parent 8c075de147
commit f7852e8034
12 changed files with 97 additions and 278 deletions
+3 -3
View File
@@ -26,7 +26,7 @@ struct BackendWorkerLaunchOptions {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct BackendWorkerLaunchRuntime { struct BackendWorkerLaunchRuntime {
runtime_id: String, runtime_id: String,
can_spawn_worker: bool, worker_creation_available: bool,
working_directory_required: bool, working_directory_required: bool,
} }
@@ -261,7 +261,7 @@ impl BackendWorkspaceProductClient {
let runtime = options let runtime = options
.runtimes .runtimes
.iter() .iter()
.find(|runtime| runtime.can_spawn_worker && !runtime.working_directory_required) .find(|runtime| runtime.worker_creation_available && !runtime.working_directory_required)
.ok_or_else(|| { .ok_or_else(|| {
BackendWorkspaceClientError::InvalidTarget( BackendWorkspaceClientError::InvalidTarget(
"Backend has no spawn-capable Runtime that supports a Workdir-less Intake Worker" "Backend has no spawn-capable Runtime that supports a Workdir-less Intake Worker"
@@ -777,7 +777,7 @@ mod tests {
let (base_url, requests, handle) = response_sequence_server(vec![ let (base_url, requests, handle) = response_sequence_server(vec![
( (
"200 OK", "200 OK",
r#"{"runtimes":[{"runtime_id":"embedded","can_spawn_worker":true,"working_directory_required":false}]}"#, r#"{"runtimes":[{"runtime_id":"embedded","worker_creation_available":true,"working_directory_required":false}]}"#,
), ),
( (
"200 OK", "200 OK",
+3 -19
View File
@@ -247,24 +247,6 @@ pub struct RuntimeSourceSummary {
pub note: String, pub note: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeCapabilitySummary {
pub can_list_hosts: bool,
pub can_list_workers: bool,
pub can_get_worker: bool,
pub can_spawn_worker: bool,
pub can_stop_worker: bool,
pub has_workspace_fs: bool,
pub has_shell: bool,
pub has_git: bool,
pub supports_worktrees: bool,
pub supports_backend_internal_tools: bool,
pub workspace_scope: String,
pub max_workers: usize,
pub os: String,
pub arch: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeSummary { pub struct RuntimeSummary {
pub runtime_id: String, pub runtime_id: String,
@@ -274,7 +256,9 @@ pub struct RuntimeSummary {
pub source: RuntimeSourceSummary, pub source: RuntimeSourceSummary,
#[serde(default)] #[serde(default)]
pub host_ids: Vec<String>, pub host_ids: Vec<String>,
pub capabilities: RuntimeCapabilitySummary, pub worker_creation_available: bool,
pub os: String,
pub arch: String,
#[serde(default)] #[serde(default)]
pub diagnostics: Vec<Diagnostic>, pub diagnostics: Vec<Diagnostic>,
} }
+48 -140
View File
@@ -177,26 +177,6 @@ impl RuntimeSourceSummary {
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeCapabilitySummary {
pub can_list_hosts: bool,
pub can_list_workers: bool,
pub can_get_worker: bool,
pub can_spawn_worker: bool,
pub can_stop_worker: bool,
pub has_workspace_fs: bool,
pub has_shell: bool,
pub has_git: bool,
pub supports_worktrees: bool,
pub supports_backend_internal_tools: bool,
pub workspace_scope: String,
pub max_workers: usize,
pub os: String,
pub arch: String,
}
pub type HostCapabilitySummary = RuntimeCapabilitySummary;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeSummary { pub struct RuntimeSummary {
pub runtime_id: String, pub runtime_id: String,
@@ -205,7 +185,9 @@ pub struct RuntimeSummary {
pub status: String, pub status: String,
pub source: RuntimeSourceSummary, pub source: RuntimeSourceSummary,
pub host_ids: Vec<String>, pub host_ids: Vec<String>,
pub capabilities: RuntimeCapabilitySummary, pub worker_creation_available: bool,
pub os: String,
pub arch: String,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -218,7 +200,8 @@ pub struct HostSummary {
pub status: String, pub status: String,
pub observed_at: String, pub observed_at: String,
pub last_seen_at: Option<String>, pub last_seen_at: Option<String>,
pub capabilities: HostCapabilitySummary, pub os: String,
pub arch: String,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
} }
@@ -310,27 +293,6 @@ impl From<RuntimeSourceSummary> for workspace_api::RuntimeSourceSummary {
} }
} }
impl From<RuntimeCapabilitySummary> for workspace_api::RuntimeCapabilitySummary {
fn from(capabilities: RuntimeCapabilitySummary) -> Self {
Self {
can_list_hosts: capabilities.can_list_hosts,
can_list_workers: capabilities.can_list_workers,
can_get_worker: capabilities.can_get_worker,
can_spawn_worker: capabilities.can_spawn_worker,
can_stop_worker: capabilities.can_stop_worker,
has_workspace_fs: capabilities.has_workspace_fs,
has_shell: capabilities.has_shell,
has_git: capabilities.has_git,
supports_worktrees: capabilities.supports_worktrees,
supports_backend_internal_tools: capabilities.supports_backend_internal_tools,
workspace_scope: capabilities.workspace_scope,
max_workers: capabilities.max_workers,
os: capabilities.os,
arch: capabilities.arch,
}
}
}
impl From<RuntimeSummary> for workspace_api::RuntimeSummary { impl From<RuntimeSummary> for workspace_api::RuntimeSummary {
fn from(runtime: RuntimeSummary) -> Self { fn from(runtime: RuntimeSummary) -> Self {
Self { Self {
@@ -340,7 +302,9 @@ impl From<RuntimeSummary> for workspace_api::RuntimeSummary {
status: runtime.status, status: runtime.status,
source: runtime.source.into(), source: runtime.source.into(),
host_ids: runtime.host_ids, host_ids: runtime.host_ids,
capabilities: runtime.capabilities.into(), worker_creation_available: runtime.worker_creation_available,
os: runtime.os,
arch: runtime.arch,
diagnostics: runtime.diagnostics.into_iter().map(Into::into).collect(), diagnostics: runtime.diagnostics.into_iter().map(Into::into).collect(),
} }
} }
@@ -1890,7 +1854,9 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
status: "unavailable".to_string(), status: "unavailable".to_string(),
source: RuntimeSourceSummary::embedded_worker_runtime(), source: RuntimeSourceSummary::embedded_worker_runtime(),
host_ids: Vec::new(), host_ids: Vec::new(),
capabilities: embedded_runtime_capabilities(limit, false, false), worker_creation_available: false,
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
diagnostics, diagnostics,
}; };
} }
@@ -1910,7 +1876,9 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
} else { } else {
vec![self.host_id.clone()] vec![self.host_id.clone()]
}, },
capabilities: embedded_runtime_capabilities(limit, true, self.execution_enabled), worker_creation_available: true,
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
diagnostics, diagnostics,
} }
} }
@@ -1928,7 +1896,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
status: "available".to_string(), status: "available".to_string(),
observed_at: Utc::now().to_rfc3339(), observed_at: Utc::now().to_rfc3339(),
last_seen_at: None, last_seen_at: None,
capabilities: embedded_runtime_capabilities(limit, true, self.execution_enabled), os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
diagnostics: vec![diagnostic( diagnostics: vec![diagnostic(
"embedded_runtime_host_boundary", "embedded_runtime_host_boundary",
DiagnosticSeverity::Info, DiagnosticSeverity::Info,
@@ -2576,7 +2545,9 @@ pub struct RemoteRuntimeConfig {
pub base_url: String, pub base_url: String,
pub bearer_token: Option<String>, pub bearer_token: Option<String>,
pub auth: Option<RemoteRuntimeAuthConfig>, pub auth: Option<RemoteRuntimeAuthConfig>,
pub cached_capabilities: RuntimeCapabilitySummary, pub cached_worker_creation_available: bool,
pub cached_os: String,
pub cached_arch: String,
pub cached_status: String, pub cached_status: String,
pub timeout: Duration, pub timeout: Duration,
} }
@@ -2598,7 +2569,12 @@ impl std::fmt::Debug for RemoteRuntimeConfig {
&self.bearer_token.as_ref().map(|_| "<redacted>"), &self.bearer_token.as_ref().map(|_| "<redacted>"),
) )
.field("auth", &self.auth.as_ref().map(|_| "<capability-signer>")) .field("auth", &self.auth.as_ref().map(|_| "<capability-signer>"))
.field("cached_capabilities", &self.cached_capabilities) .field(
"cached_worker_creation_available",
&self.cached_worker_creation_available,
)
.field("cached_os", &self.cached_os)
.field("cached_arch", &self.cached_arch)
.field("cached_status", &self.cached_status) .field("cached_status", &self.cached_status)
.field("timeout", &self.timeout) .field("timeout", &self.timeout)
.finish() .finish()
@@ -2619,9 +2595,9 @@ impl RemoteRuntimeConfig {
base_url: base_url.into(), base_url: base_url.into(),
bearer_token, bearer_token,
auth: None, auth: None,
cached_capabilities: remote_runtime_capabilities( cached_worker_creation_available: false,
200, false, false, "unknown", "unknown", cached_os: "unknown".to_string(),
), cached_arch: "unknown".to_string(),
cached_status: "configured".to_string(), cached_status: "configured".to_string(),
timeout: Duration::from_secs(10), timeout: Duration::from_secs(10),
} }
@@ -2632,11 +2608,6 @@ impl RemoteRuntimeConfig {
self self
} }
pub fn with_cached_capabilities(mut self, capabilities: RuntimeCapabilitySummary) -> Self {
self.cached_capabilities = capabilities;
self
}
pub fn with_auth(mut self, auth: RemoteRuntimeAuthConfig) -> Self { pub fn with_auth(mut self, auth: RemoteRuntimeAuthConfig) -> Self {
self.auth = Some(auth); self.auth = Some(auth);
self self
@@ -2708,7 +2679,9 @@ pub struct RemoteWorkerRuntime {
workspace_id: String, workspace_id: String,
bearer_token: Option<String>, bearer_token: Option<String>,
auth: Option<RemoteRuntimeAuthConfig>, auth: Option<RemoteRuntimeAuthConfig>,
cached_capabilities: RuntimeCapabilitySummary, cached_worker_creation_available: bool,
cached_os: String,
cached_arch: String,
cached_status: String, cached_status: String,
host_id: String, host_id: String,
resource_broker: BackendResourceBroker, resource_broker: BackendResourceBroker,
@@ -2768,7 +2741,9 @@ impl RemoteWorkerRuntime {
workspace_id, workspace_id,
bearer_token: config.bearer_token, bearer_token: config.bearer_token,
auth: config.auth, auth: config.auth,
cached_capabilities: config.cached_capabilities, cached_worker_creation_available: config.cached_worker_creation_available,
cached_os: config.cached_os,
cached_arch: config.cached_arch,
cached_status: config.cached_status, cached_status: config.cached_status,
resource_broker: BackendResourceBroker::default(), resource_broker: BackendResourceBroker::default(),
http, http,
@@ -3045,13 +3020,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
} else { } else {
vec![self.host_id.clone()] vec![self.host_id.clone()]
}, },
capabilities: remote_runtime_capabilities( worker_creation_available: response.runtime.worker_creation_available,
limit, os: response.runtime.os,
true, arch: response.runtime.arch,
response.runtime.worker_creation_available,
response.runtime.os,
response.runtime.arch,
),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}, },
Err(diagnostic) => RuntimeSummary { Err(diagnostic) => RuntimeSummary {
@@ -3065,7 +3036,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
} else { } else {
vec![self.host_id.clone()] vec![self.host_id.clone()]
}, },
capabilities: self.cached_capabilities.clone(), worker_creation_available: self.cached_worker_creation_available,
os: self.cached_os.clone(),
arch: self.cached_arch.clone(),
diagnostics: vec![diagnostic], diagnostics: vec![diagnostic],
}, },
} }
@@ -3084,7 +3057,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
status: "configured".to_string(), status: "configured".to_string(),
observed_at: Utc::now().to_rfc3339(), observed_at: Utc::now().to_rfc3339(),
last_seen_at: None, last_seen_at: None,
capabilities: remote_runtime_capabilities(limit, true, false, "unknown", "unknown"), os: self.cached_os.clone(),
arch: self.cached_arch.clone(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}], }],
Vec::new(), Vec::new(),
@@ -3553,29 +3527,6 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
} }
} }
fn embedded_runtime_capabilities(
limit: usize,
available: bool,
execution_enabled: bool,
) -> RuntimeCapabilitySummary {
RuntimeCapabilitySummary {
can_list_hosts: true,
can_list_workers: available,
can_get_worker: available,
can_spawn_worker: available,
can_stop_worker: available && execution_enabled,
has_workspace_fs: false,
has_shell: false,
has_git: false,
supports_worktrees: false,
supports_backend_internal_tools: true,
workspace_scope: "backend_internal".to_string(),
max_workers: limit,
os: std::env::consts::OS.to_string(),
arch: std::env::consts::ARCH.to_string(),
}
}
fn embedded_runtime_status_label(status: RuntimeStatus) -> &'static str { fn embedded_runtime_status_label(status: RuntimeStatus) -> &'static str {
match status { match status {
RuntimeStatus::Running => "running", RuntimeStatus::Running => "running",
@@ -4122,31 +4073,6 @@ fn percent_encode(input: &str, keep: impl Fn(u8) -> bool) -> String {
encoded encoded
} }
fn remote_runtime_capabilities(
limit: usize,
available: bool,
worker_creation_available: bool,
os: impl Into<String>,
arch: impl Into<String>,
) -> RuntimeCapabilitySummary {
RuntimeCapabilitySummary {
can_list_hosts: true,
can_list_workers: available,
can_get_worker: available,
can_spawn_worker: available && worker_creation_available,
can_stop_worker: available,
has_workspace_fs: false,
has_shell: false,
has_git: false,
supports_worktrees: true,
supports_backend_internal_tools: false,
workspace_scope: "remote_runtime_backend_private".to_string(),
max_workers: limit,
os: os.into(),
arch: arch.into(),
}
}
fn remote_reqwest_diagnostic(runtime_id: &str, err: reqwest::Error) -> RuntimeDiagnostic { fn remote_reqwest_diagnostic(runtime_id: &str, err: reqwest::Error) -> RuntimeDiagnostic {
if err.is_timeout() { if err.is_timeout() {
diagnostic( diagnostic(
@@ -4800,22 +4726,9 @@ mod tests {
status: "available".to_string(), status: "available".to_string(),
source: RuntimeSourceSummary::embedded_worker_runtime_reserved(), source: RuntimeSourceSummary::embedded_worker_runtime_reserved(),
host_ids: vec![self.host_id.clone()], host_ids: vec![self.host_id.clone()],
capabilities: RuntimeCapabilitySummary { worker_creation_available: false,
can_list_hosts: true, os: "test".to_string(),
can_list_workers: true, arch: "test".to_string(),
can_get_worker: true,
can_spawn_worker: false,
can_stop_worker: false,
has_workspace_fs: false,
has_shell: false,
has_git: false,
supports_worktrees: false,
supports_backend_internal_tools: false,
workspace_scope: "none".to_string(),
max_workers: self.workers.len(),
os: "test".to_string(),
arch: "test".to_string(),
},
diagnostics: Vec::new(), diagnostics: Vec::new(),
} }
} }
@@ -4830,7 +4743,8 @@ mod tests {
status: "available".to_string(), status: "available".to_string(),
observed_at: "unknown".to_string(), observed_at: "unknown".to_string(),
last_seen_at: None, last_seen_at: None,
capabilities: self.runtime_summary(1).capabilities, os: "test".to_string(),
arch: "test".to_string(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
}], }],
Vec::new(), Vec::new(),
@@ -5234,7 +5148,7 @@ mod tests {
RuntimeSourceKind::EmbeddedWorkerRuntime RuntimeSourceKind::EmbeddedWorkerRuntime
); );
assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active); assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active);
assert!(embedded_summary.capabilities.can_spawn_worker); assert!(embedded_summary.worker_creation_available);
let spawned = registry let spawned = registry
.spawn_worker( .spawn_worker(
@@ -5534,12 +5448,6 @@ mod tests {
assert!(browser_payload.contains("worker_id")); assert!(browser_payload.contains("worker_id"));
} }
#[test]
fn remote_runtime_projection_allows_workdir_creation() {
let capabilities = remote_runtime_capabilities(8, true, true, "linux", "x86_64");
assert!(capabilities.supports_worktrees);
}
#[test] #[test]
fn remote_runtime_projection_uses_canonical_worker_status_for_stop_capability() { fn remote_runtime_projection_uses_canonical_worker_status_for_stop_capability() {
let worker_ids = (1..=4) let worker_ids = (1..=4)
+28 -85
View File
@@ -2728,7 +2728,7 @@ pub struct RuntimeConnectionSummary {
pub built_in: bool, pub built_in: bool,
pub config_managed: bool, pub config_managed: bool,
pub active: bool, pub active: bool,
pub can_spawn_worker: bool, pub worker_creation_available: bool,
pub restart_required: bool, pub restart_required: bool,
pub status: String, pub status: String,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
@@ -2788,7 +2788,7 @@ pub struct WorkerLaunchRuntimeOption {
pub runtime_id: String, pub runtime_id: String,
pub display_name: String, pub display_name: String,
pub built_in: bool, pub built_in: bool,
pub can_spawn_worker: bool, pub worker_creation_available: bool,
pub working_directory_required: bool, pub working_directory_required: bool,
pub status: String, pub status: String,
pub diagnostics: Vec<RuntimeDiagnostic>, pub diagnostics: Vec<RuntimeDiagnostic>,
@@ -6985,14 +6985,13 @@ fn select_memory_consolidation_runtime(api: &WorkspaceApi) -> ApiResult<String>
.items .items
.iter() .iter()
.find(|runtime| { .find(|runtime| {
runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID && runtime.worker_creation_available
&& runtime.capabilities.can_spawn_worker
}) })
.or_else(|| { .or_else(|| {
runtimes runtimes
.items .items
.iter() .iter()
.find(|runtime| runtime.capabilities.can_spawn_worker) .find(|runtime| runtime.worker_creation_available)
}) })
{ {
return Ok(runtime.runtime_id.clone()); return Ok(runtime.runtime_id.clone());
@@ -8331,29 +8330,6 @@ async fn create_workspace_working_directory(
)); ));
} }
if !runtime.capabilities.supports_worktrees {
api.config_store.finish_workdir_create_operation(
workspace_id,
&operation_id,
&request_fingerprint,
false,
Some("runtime_workdir_unsupported"),
&now_registry_timestamp(),
)?;
return Err(ApiError::with_diagnostics(
Error::RuntimeOperationFailed {
runtime_id: reserved.resolved_runtime_id,
code: "runtime_workdir_unsupported".to_string(),
message: "Selected Runtime does not support Workdir creation".to_string(),
},
vec![RuntimeDiagnostic {
code: "runtime_workdir_unsupported".to_string(),
severity: DiagnosticSeverity::Error,
message: "Selected Runtime does not support Workdir creation".to_string(),
}],
));
}
working_directory_request.backend_workdir_id = Some(reserved.working_directory_id.clone()); working_directory_request.backend_workdir_id = Some(reserved.working_directory_id.clone());
let existing = match api.runtime.working_directory( let existing = match api.runtime.working_directory(
&reserved.resolved_runtime_id, &reserved.resolved_runtime_id,
@@ -12477,7 +12453,7 @@ fn embedded_runtime_connection_summary(api: &WorkspaceApi) -> RuntimeConnectionS
built_in: true, built_in: true,
config_managed: false, config_managed: false,
active: runtime.status == "active", active: runtime.status == "active",
can_spawn_worker: runtime.capabilities.can_spawn_worker, worker_creation_available: runtime.worker_creation_available,
restart_required: false, restart_required: false,
status: runtime.status, status: runtime.status,
diagnostics: runtime.diagnostics, diagnostics: runtime.diagnostics,
@@ -12489,7 +12465,7 @@ fn embedded_runtime_connection_summary(api: &WorkspaceApi) -> RuntimeConnectionS
built_in: true, built_in: true,
config_managed: false, config_managed: false,
active: false, active: false,
can_spawn_worker: false, worker_creation_available: false,
restart_required: false, restart_required: false,
status: "unavailable".to_string(), status: "unavailable".to_string(),
diagnostics: vec![settings_diagnostic( diagnostics: vec![settings_diagnostic(
@@ -12518,12 +12494,12 @@ fn remote_runtime_connection_summaries(
let live = live_runtimes let live = live_runtimes
.iter() .iter()
.find(|runtime| runtime.runtime_id == remote.id); .find(|runtime| runtime.runtime_id == remote.id);
let (display_name, kind, active, can_spawn_worker, status, diagnostics) = match live { let (display_name, kind, active, worker_creation_available, status, diagnostics) = match live {
Some(runtime) => ( Some(runtime) => (
runtime.label.clone(), runtime.label.clone(),
runtime.kind.clone(), runtime.kind.clone(),
runtime.status == "active", runtime.status == "active",
runtime.capabilities.can_spawn_worker, runtime.worker_creation_available,
runtime.status.clone(), runtime.status.clone(),
runtime.diagnostics.clone(), runtime.diagnostics.clone(),
), ),
@@ -12555,7 +12531,7 @@ fn remote_runtime_connection_summaries(
built_in: false, built_in: false,
config_managed: true, config_managed: true,
active, active,
can_spawn_worker, worker_creation_available,
restart_required, restart_required,
status, status,
diagnostics, diagnostics,
@@ -13070,7 +13046,7 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> ApiResult<WorkerLaunchO
runtime_id: runtime.runtime_id, runtime_id: runtime.runtime_id,
display_name: runtime.label, display_name: runtime.label,
built_in, built_in,
can_spawn_worker: runtime.capabilities.can_spawn_worker, worker_creation_available: runtime.worker_creation_available,
working_directory_required: !built_in, working_directory_required: !built_in,
status: runtime.status, status: runtime.status,
diagnostics: runtime.diagnostics, diagnostics: runtime.diagnostics,
@@ -13459,11 +13435,9 @@ fn sync_all_runtime_workdir_observations(api: &WorkspaceApi) -> Vec<RuntimeDiagn
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();
let runtimes = api.runtime.list_runtimes(api.config.max_records.min(200)); let runtimes = api.runtime.list_runtimes(api.config.max_records.min(200));
for runtime in runtimes.items { for runtime in runtimes.items {
if runtime.capabilities.supports_worktrees { match sync_runtime_workdir_observations(api, runtime.runtime_id.as_str()) {
match sync_runtime_workdir_observations(api, runtime.runtime_id.as_str()) { Ok(mut runtime_diagnostics) => diagnostics.append(&mut runtime_diagnostics),
Ok(mut runtime_diagnostics) => diagnostics.append(&mut runtime_diagnostics), Err(err) => diagnostics.extend(err.diagnostics),
Err(err) => diagnostics.extend(err.diagnostics),
}
} }
} }
diagnostics diagnostics
@@ -14240,8 +14214,8 @@ mod tests {
use worker_runtime::working_directory::WorkingDirectoryMaterializer; use worker_runtime::working_directory::WorkingDirectoryMaterializer;
use crate::hosts::{ use crate::hosts::{
RemoteRuntimeAuthConfig, RuntimeCapabilitySummary, TicketWorkerRole, WorkerInputKind, RemoteRuntimeAuthConfig, TicketWorkerRole, WorkerInputKind, WorkerOperationState,
WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent,
}; };
use crate::store::{ use crate::store::{
AccountRecord, ApiTokenRecord, BrowserSessionRecord, MemoryDocumentRecord, AccountRecord, ApiTokenRecord, BrowserSessionRecord, MemoryDocumentRecord,
@@ -14303,22 +14277,9 @@ mod tests {
server_id: "server-test".to_owned(), server_id: "server-test".to_owned(),
server_private_key: "unused".to_owned(), server_private_key: "unused".to_owned(),
}), }),
cached_capabilities: RuntimeCapabilitySummary { cached_worker_creation_available: true,
can_list_hosts: true, cached_os: "test".to_owned(),
can_list_workers: true, cached_arch: "test".to_owned(),
can_get_worker: true,
can_spawn_worker: true,
can_stop_worker: true,
has_workspace_fs: false,
has_shell: false,
has_git: false,
supports_worktrees: false,
supports_backend_internal_tools: false,
workspace_scope: api.workspace_id().to_owned(),
max_workers: 1,
os: "test".to_owned(),
arch: "test".to_owned(),
},
cached_status: "connected".to_owned(), cached_status: "connected".to_owned(),
timeout: std::time::Duration::from_secs(1), timeout: std::time::Duration::from_secs(1),
}); });
@@ -19590,22 +19551,9 @@ mod tests {
server_id: "server-main".to_string(), server_id: "server-main".to_string(),
server_private_key: identity.private_key.clone(), server_private_key: identity.private_key.clone(),
}), }),
cached_capabilities: RuntimeCapabilitySummary { cached_worker_creation_available: true,
can_list_hosts: true, cached_os: "test".to_string(),
can_list_workers: true, cached_arch: "test".to_string(),
can_get_worker: true,
can_spawn_worker: true,
can_stop_worker: true,
has_workspace_fs: false,
has_shell: false,
has_git: false,
supports_worktrees: false,
supports_backend_internal_tools: false,
workspace_scope: TEST_WORKSPACE_ID.to_string(),
max_workers: 1,
os: "test".to_string(),
arch: "test".to_string(),
},
cached_status: "connected".to_string(), cached_status: "connected".to_string(),
timeout: std::time::Duration::from_secs(1), timeout: std::time::Duration::from_secs(1),
}); });
@@ -20906,7 +20854,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn browser_workspace_workdir_create_records_failed_default_resolution() { async fn browser_workspace_workdir_create_delegates_and_records_default_runtime_failure() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
init_clean_git_workspace(dir.path()); init_clean_git_workspace(dir.path());
let api = test_api(dir.path()).await; let api = test_api(dir.path()).await;
@@ -20931,7 +20879,7 @@ mod tests {
assert_eq!(response["error"], "Bad Request"); assert_eq!(response["error"], "Bad Request");
assert_eq!( assert_eq!(
response["diagnostics"][0]["code"], response["diagnostics"][0]["code"],
"runtime_workdir_unsupported" "embedded_worker_workdir_unsupported"
); );
set_test_default_runtime(&api, "not-a-registered-runtime"); set_test_default_runtime(&api, "not-a-registered-runtime");
request_json_authenticated( request_json_authenticated(
@@ -20957,7 +20905,7 @@ mod tests {
assert_eq!(operation.state, "failed"); assert_eq!(operation.state, "failed");
assert_eq!( assert_eq!(
operation.failure.as_deref(), operation.failure.as_deref(),
Some("runtime_workdir_unsupported") Some("embedded_worker_workdir_unsupported")
); );
assert_eq!(operation.config_revision, 2); assert_eq!(operation.config_revision, 2);
assert!(!operation.config_projection_digest.is_empty()); assert!(!operation.config_projection_digest.is_empty());
@@ -22005,10 +21953,8 @@ mod tests {
assert_eq!(hosts["items"][0]["runtime_id"], "embedded-worker-runtime"); assert_eq!(hosts["items"][0]["runtime_id"], "embedded-worker-runtime");
let host_id = hosts["items"][0]["host_id"].as_str().unwrap().to_string(); let host_id = hosts["items"][0]["host_id"].as_str().unwrap().to_string();
assert_eq!(hosts["items"][0]["kind"], "embedded-worker-runtime-host"); assert_eq!(hosts["items"][0]["kind"], "embedded-worker-runtime-host");
assert_eq!( assert_eq!(hosts["items"][0]["os"], std::env::consts::OS);
hosts["items"][0]["capabilities"]["workspace_scope"], assert!(hosts["items"][0].get("capabilities").is_none());
"backend_internal"
);
assert!(!hosts.to_string().contains("metadata.json")); assert!(!hosts.to_string().contains("metadata.json"));
let runtimes = get_json(app.clone(), "/api/runtimes").await; let runtimes = get_json(app.clone(), "/api/runtimes").await;
@@ -22481,11 +22427,8 @@ mod tests {
"embedded_worker_runtime" "embedded_worker_runtime"
); );
assert_eq!(embedded_summary["source"]["status"], "active"); assert_eq!(embedded_summary["source"]["status"], "active");
assert_eq!( assert_eq!(embedded_summary["worker_creation_available"], true);
embedded_summary["capabilities"]["workspace_scope"], assert!(embedded_summary.get("capabilities").is_none());
"backend_internal"
);
assert_eq!(embedded_summary["capabilities"]["has_workspace_fs"], false);
let spawned = post_json( let spawned = post_json(
app.clone(), app.clone(),
@@ -32,7 +32,7 @@ export type RuntimeConnectionSummary = {
built_in: boolean; built_in: boolean;
config_managed: boolean; config_managed: boolean;
active: boolean; active: boolean;
can_spawn_worker: boolean; worker_creation_available: boolean;
restart_required: boolean; restart_required: boolean;
status: string; status: string;
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];
@@ -29,30 +29,15 @@ export type Diagnostic = {
message: string; message: string;
}; };
export type RuntimeCapabilities = {
can_list_hosts: boolean;
can_list_workers: boolean;
can_get_worker: boolean;
can_spawn_worker: boolean;
can_stop_worker: boolean;
has_workspace_fs: boolean;
has_shell: boolean;
has_git: boolean;
supports_worktrees: boolean;
supports_backend_internal_tools: boolean;
workspace_scope: string;
os: string;
arch: string;
max_workers: number;
};
export type Runtime = { export type Runtime = {
runtime_id: string; runtime_id: string;
label: string; label: string;
kind: string; kind: string;
status: string; status: string;
host_ids: string[]; host_ids: string[];
capabilities: RuntimeCapabilities; worker_creation_available: boolean;
os: string;
arch: string;
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];
}; };
@@ -64,7 +49,8 @@ export type Host = {
status: string; status: string;
observed_at: string; observed_at: string;
last_seen_at: string | null; last_seen_at: string | null;
capabilities: RuntimeCapabilities; os: string;
arch: string;
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];
}; };
@@ -100,7 +86,7 @@ export type WorkerLaunchRuntimeOption = {
runtime_id: string; runtime_id: string;
display_name: string; display_name: string;
built_in: boolean; built_in: boolean;
can_spawn_worker: boolean; worker_creation_available: boolean;
working_directory_required: boolean; working_directory_required: boolean;
status: string; status: string;
diagnostics: Diagnostic[]; diagnostics: Diagnostic[];
@@ -23,7 +23,7 @@ const options: WorkerLaunchOptionsResponse = {
runtime_id: "remote", runtime_id: "remote",
display_name: "Remote", display_name: "Remote",
status: "active", status: "active",
can_spawn_worker: true, worker_creation_available: true,
built_in: false, built_in: false,
working_directory_required: true, working_directory_required: true,
diagnostics: [], diagnostics: [],
@@ -32,7 +32,7 @@ const options: WorkerLaunchOptionsResponse = {
runtime_id: "embedded", runtime_id: "embedded",
display_name: "Embedded", display_name: "Embedded",
status: "active", status: "active",
can_spawn_worker: true, worker_creation_available: true,
built_in: true, built_in: true,
working_directory_required: false, working_directory_required: false,
diagnostics: [], diagnostics: [],
@@ -30,9 +30,9 @@ export function defaultWorkerLaunchForm(
): WorkerLaunchFormState { ): WorkerLaunchFormState {
const preferredRuntime = const preferredRuntime =
options?.runtimes.find((runtime) => options?.runtimes.find((runtime) =>
runtime.can_spawn_worker && runtime.status === "active" runtime.worker_creation_available && runtime.status === "active"
) ?? ) ??
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ?? options?.runtimes.find((runtime) => runtime.worker_creation_available) ??
options?.runtimes[0]; options?.runtimes[0];
const preferredProfile = options?.profiles.find((candidate) => const preferredProfile = options?.profiles.find((candidate) =>
candidate.id === options.default_profile candidate.id === options.default_profile
@@ -88,7 +88,7 @@
</div> </div>
<div> <div>
<dt>Platform</dt> <dt>Platform</dt>
<dd>{host.capabilities.os} / {host.capabilities.arch}</dd> <dd>{host.os} / {host.arch}</dd>
</div> </div>
</dl> </dl>
</article> </article>
@@ -183,7 +183,7 @@
built_in: true, built_in: true,
config_managed: false, config_managed: false,
active: false, active: false,
can_spawn_worker: false, worker_creation_available: false,
restart_required: false, restart_required: false,
status: 'unknown', status: 'unknown',
diagnostics: [] diagnostics: []
@@ -5,7 +5,7 @@
let { data }: PageProps = $props(); let { data }: PageProps = $props();
function runtimePlatform(runtime: Runtime): string { function runtimePlatform(runtime: Runtime): string {
return `${runtime.capabilities.os} / ${runtime.capabilities.arch}`; return `${runtime.os} / ${runtime.arch}`;
} }
</script> </script>
@@ -37,7 +37,6 @@
<th>Kind</th> <th>Kind</th>
<th>Status</th> <th>Status</th>
<th>Platform</th> <th>Platform</th>
<th>Capacity</th>
<th>Workdirs</th> <th>Workdirs</th>
</tr> </tr>
</thead> </thead>
@@ -51,7 +50,6 @@
<td>{runtime.kind}</td> <td>{runtime.kind}</td>
<td>{runtime.status}</td> <td>{runtime.status}</td>
<td>{runtimePlatform(runtime)}</td> <td>{runtimePlatform(runtime)}</td>
<td>{runtime.capabilities.max_workers} workers</td>
<td> <td>
<a class="inline-link" href={`/w/${data.workspaceId}/settings/runtimes/${encodeURIComponent(runtime.runtime_id)}/workdirs`}> <a class="inline-link" href={`/w/${data.workspaceId}/settings/runtimes/${encodeURIComponent(runtime.runtime_id)}/workdirs`}>
Open workdirs Open workdirs
@@ -313,7 +313,7 @@
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime"> <select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">
{#if options?.runtimes.length} {#if options?.runtimes.length}
{#each options.runtimes as runtime} {#each options.runtimes as runtime}
<option value={runtime.runtime_id} disabled={!runtime.can_spawn_worker}> <option value={runtime.runtime_id} disabled={!runtime.worker_creation_available}>
{runtime.display_name} {runtime.display_name}
</option> </option>
{/each} {/each}