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
+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)]
pub struct RuntimeSummary {
pub runtime_id: String,
@@ -205,7 +185,9 @@ pub struct RuntimeSummary {
pub status: String,
pub source: RuntimeSourceSummary,
pub host_ids: Vec<String>,
pub capabilities: RuntimeCapabilitySummary,
pub worker_creation_available: bool,
pub os: String,
pub arch: String,
pub diagnostics: Vec<RuntimeDiagnostic>,
}
@@ -218,7 +200,8 @@ pub struct HostSummary {
pub status: String,
pub observed_at: String,
pub last_seen_at: Option<String>,
pub capabilities: HostCapabilitySummary,
pub os: String,
pub arch: String,
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 {
fn from(runtime: RuntimeSummary) -> Self {
Self {
@@ -340,7 +302,9 @@ impl From<RuntimeSummary> for workspace_api::RuntimeSummary {
status: runtime.status,
source: runtime.source.into(),
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(),
}
}
@@ -1890,7 +1854,9 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
status: "unavailable".to_string(),
source: RuntimeSourceSummary::embedded_worker_runtime(),
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,
};
}
@@ -1910,7 +1876,9 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
} else {
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,
}
}
@@ -1928,7 +1896,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
status: "available".to_string(),
observed_at: Utc::now().to_rfc3339(),
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(
"embedded_runtime_host_boundary",
DiagnosticSeverity::Info,
@@ -2576,7 +2545,9 @@ pub struct RemoteRuntimeConfig {
pub base_url: String,
pub bearer_token: Option<String>,
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 timeout: Duration,
}
@@ -2598,7 +2569,12 @@ impl std::fmt::Debug for RemoteRuntimeConfig {
&self.bearer_token.as_ref().map(|_| "<redacted>"),
)
.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("timeout", &self.timeout)
.finish()
@@ -2619,9 +2595,9 @@ impl RemoteRuntimeConfig {
base_url: base_url.into(),
bearer_token,
auth: None,
cached_capabilities: remote_runtime_capabilities(
200, false, false, "unknown", "unknown",
),
cached_worker_creation_available: false,
cached_os: "unknown".to_string(),
cached_arch: "unknown".to_string(),
cached_status: "configured".to_string(),
timeout: Duration::from_secs(10),
}
@@ -2632,11 +2608,6 @@ impl RemoteRuntimeConfig {
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 {
self.auth = Some(auth);
self
@@ -2708,7 +2679,9 @@ pub struct RemoteWorkerRuntime {
workspace_id: String,
bearer_token: Option<String>,
auth: Option<RemoteRuntimeAuthConfig>,
cached_capabilities: RuntimeCapabilitySummary,
cached_worker_creation_available: bool,
cached_os: String,
cached_arch: String,
cached_status: String,
host_id: String,
resource_broker: BackendResourceBroker,
@@ -2768,7 +2741,9 @@ impl RemoteWorkerRuntime {
workspace_id,
bearer_token: config.bearer_token,
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,
resource_broker: BackendResourceBroker::default(),
http,
@@ -3045,13 +3020,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
} else {
vec![self.host_id.clone()]
},
capabilities: remote_runtime_capabilities(
limit,
true,
response.runtime.worker_creation_available,
response.runtime.os,
response.runtime.arch,
),
worker_creation_available: response.runtime.worker_creation_available,
os: response.runtime.os,
arch: response.runtime.arch,
diagnostics: Vec::new(),
},
Err(diagnostic) => RuntimeSummary {
@@ -3065,7 +3036,9 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
} else {
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],
},
}
@@ -3084,7 +3057,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
status: "configured".to_string(),
observed_at: Utc::now().to_rfc3339(),
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(),
}],
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 {
match status {
RuntimeStatus::Running => "running",
@@ -4122,31 +4073,6 @@ fn percent_encode(input: &str, keep: impl Fn(u8) -> bool) -> String {
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 {
if err.is_timeout() {
diagnostic(
@@ -4800,22 +4726,9 @@ mod tests {
status: "available".to_string(),
source: RuntimeSourceSummary::embedded_worker_runtime_reserved(),
host_ids: vec![self.host_id.clone()],
capabilities: RuntimeCapabilitySummary {
can_list_hosts: true,
can_list_workers: true,
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(),
},
worker_creation_available: false,
os: "test".to_string(),
arch: "test".to_string(),
diagnostics: Vec::new(),
}
}
@@ -4830,7 +4743,8 @@ mod tests {
status: "available".to_string(),
observed_at: "unknown".to_string(),
last_seen_at: None,
capabilities: self.runtime_summary(1).capabilities,
os: "test".to_string(),
arch: "test".to_string(),
diagnostics: Vec::new(),
}],
Vec::new(),
@@ -5234,7 +5148,7 @@ mod tests {
RuntimeSourceKind::EmbeddedWorkerRuntime
);
assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active);
assert!(embedded_summary.capabilities.can_spawn_worker);
assert!(embedded_summary.worker_creation_available);
let spawned = registry
.spawn_worker(
@@ -5534,12 +5448,6 @@ mod tests {
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]
fn remote_runtime_projection_uses_canonical_worker_status_for_stop_capability() {
let worker_ids = (1..=4)
+28 -85
View File
@@ -2728,7 +2728,7 @@ pub struct RuntimeConnectionSummary {
pub built_in: bool,
pub config_managed: bool,
pub active: bool,
pub can_spawn_worker: bool,
pub worker_creation_available: bool,
pub restart_required: bool,
pub status: String,
pub diagnostics: Vec<RuntimeDiagnostic>,
@@ -2788,7 +2788,7 @@ pub struct WorkerLaunchRuntimeOption {
pub runtime_id: String,
pub display_name: String,
pub built_in: bool,
pub can_spawn_worker: bool,
pub worker_creation_available: bool,
pub working_directory_required: bool,
pub status: String,
pub diagnostics: Vec<RuntimeDiagnostic>,
@@ -6985,14 +6985,13 @@ fn select_memory_consolidation_runtime(api: &WorkspaceApi) -> ApiResult<String>
.items
.iter()
.find(|runtime| {
runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID
&& runtime.capabilities.can_spawn_worker
runtime.runtime_id == EMBEDDED_WORKER_RUNTIME_ID && runtime.worker_creation_available
})
.or_else(|| {
runtimes
.items
.iter()
.find(|runtime| runtime.capabilities.can_spawn_worker)
.find(|runtime| runtime.worker_creation_available)
})
{
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());
let existing = match api.runtime.working_directory(
&reserved.resolved_runtime_id,
@@ -12477,7 +12453,7 @@ fn embedded_runtime_connection_summary(api: &WorkspaceApi) -> RuntimeConnectionS
built_in: true,
config_managed: false,
active: runtime.status == "active",
can_spawn_worker: runtime.capabilities.can_spawn_worker,
worker_creation_available: runtime.worker_creation_available,
restart_required: false,
status: runtime.status,
diagnostics: runtime.diagnostics,
@@ -12489,7 +12465,7 @@ fn embedded_runtime_connection_summary(api: &WorkspaceApi) -> RuntimeConnectionS
built_in: true,
config_managed: false,
active: false,
can_spawn_worker: false,
worker_creation_available: false,
restart_required: false,
status: "unavailable".to_string(),
diagnostics: vec![settings_diagnostic(
@@ -12518,12 +12494,12 @@ fn remote_runtime_connection_summaries(
let live = live_runtimes
.iter()
.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) => (
runtime.label.clone(),
runtime.kind.clone(),
runtime.status == "active",
runtime.capabilities.can_spawn_worker,
runtime.worker_creation_available,
runtime.status.clone(),
runtime.diagnostics.clone(),
),
@@ -12555,7 +12531,7 @@ fn remote_runtime_connection_summaries(
built_in: false,
config_managed: true,
active,
can_spawn_worker,
worker_creation_available,
restart_required,
status,
diagnostics,
@@ -13070,7 +13046,7 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> ApiResult<WorkerLaunchO
runtime_id: runtime.runtime_id,
display_name: runtime.label,
built_in,
can_spawn_worker: runtime.capabilities.can_spawn_worker,
worker_creation_available: runtime.worker_creation_available,
working_directory_required: !built_in,
status: runtime.status,
diagnostics: runtime.diagnostics,
@@ -13459,11 +13435,9 @@ fn sync_all_runtime_workdir_observations(api: &WorkspaceApi) -> Vec<RuntimeDiagn
let mut diagnostics = Vec::new();
let runtimes = api.runtime.list_runtimes(api.config.max_records.min(200));
for runtime in runtimes.items {
if runtime.capabilities.supports_worktrees {
match sync_runtime_workdir_observations(api, runtime.runtime_id.as_str()) {
Ok(mut runtime_diagnostics) => diagnostics.append(&mut runtime_diagnostics),
Err(err) => diagnostics.extend(err.diagnostics),
}
match sync_runtime_workdir_observations(api, runtime.runtime_id.as_str()) {
Ok(mut runtime_diagnostics) => diagnostics.append(&mut runtime_diagnostics),
Err(err) => diagnostics.extend(err.diagnostics),
}
}
diagnostics
@@ -14240,8 +14214,8 @@ mod tests {
use worker_runtime::working_directory::WorkingDirectoryMaterializer;
use crate::hosts::{
RemoteRuntimeAuthConfig, RuntimeCapabilitySummary, TicketWorkerRole, WorkerInputKind,
WorkerOperationState, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent,
RemoteRuntimeAuthConfig, TicketWorkerRole, WorkerInputKind, WorkerOperationState,
WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent,
};
use crate::store::{
AccountRecord, ApiTokenRecord, BrowserSessionRecord, MemoryDocumentRecord,
@@ -14303,22 +14277,9 @@ mod tests {
server_id: "server-test".to_owned(),
server_private_key: "unused".to_owned(),
}),
cached_capabilities: RuntimeCapabilitySummary {
can_list_hosts: true,
can_list_workers: true,
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_worker_creation_available: true,
cached_os: "test".to_owned(),
cached_arch: "test".to_owned(),
cached_status: "connected".to_owned(),
timeout: std::time::Duration::from_secs(1),
});
@@ -19590,22 +19551,9 @@ mod tests {
server_id: "server-main".to_string(),
server_private_key: identity.private_key.clone(),
}),
cached_capabilities: RuntimeCapabilitySummary {
can_list_hosts: true,
can_list_workers: true,
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_worker_creation_available: true,
cached_os: "test".to_string(),
cached_arch: "test".to_string(),
cached_status: "connected".to_string(),
timeout: std::time::Duration::from_secs(1),
});
@@ -20906,7 +20854,7 @@ mod tests {
}
#[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();
init_clean_git_workspace(dir.path());
let api = test_api(dir.path()).await;
@@ -20931,7 +20879,7 @@ mod tests {
assert_eq!(response["error"], "Bad Request");
assert_eq!(
response["diagnostics"][0]["code"],
"runtime_workdir_unsupported"
"embedded_worker_workdir_unsupported"
);
set_test_default_runtime(&api, "not-a-registered-runtime");
request_json_authenticated(
@@ -20957,7 +20905,7 @@ mod tests {
assert_eq!(operation.state, "failed");
assert_eq!(
operation.failure.as_deref(),
Some("runtime_workdir_unsupported")
Some("embedded_worker_workdir_unsupported")
);
assert_eq!(operation.config_revision, 2);
assert!(!operation.config_projection_digest.is_empty());
@@ -22005,10 +21953,8 @@ mod tests {
assert_eq!(hosts["items"][0]["runtime_id"], "embedded-worker-runtime");
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]["capabilities"]["workspace_scope"],
"backend_internal"
);
assert_eq!(hosts["items"][0]["os"], std::env::consts::OS);
assert!(hosts["items"][0].get("capabilities").is_none());
assert!(!hosts.to_string().contains("metadata.json"));
let runtimes = get_json(app.clone(), "/api/runtimes").await;
@@ -22481,11 +22427,8 @@ mod tests {
"embedded_worker_runtime"
);
assert_eq!(embedded_summary["source"]["status"], "active");
assert_eq!(
embedded_summary["capabilities"]["workspace_scope"],
"backend_internal"
);
assert_eq!(embedded_summary["capabilities"]["has_workspace_fs"], false);
assert_eq!(embedded_summary["worker_creation_available"], true);
assert!(embedded_summary.get("capabilities").is_none());
let spawned = post_json(
app.clone(),