From 135667417bc3a5b90d68020ac99a7392dbacf42f Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 27 Jun 2026 18:31:17 +0900 Subject: [PATCH] ui: remove legacy local worker projection --- Cargo.lock | 2 - crates/workspace-server/Cargo.toml | 2 - crates/workspace-server/src/companion.rs | 8 +- crates/workspace-server/src/hosts.rs | 652 +----------------- crates/workspace-server/src/server.rs | 57 +- package.nix | 2 +- web/workspace/src/app.css | 159 ++--- .../lib/workspace-pages/WorkspacePage.svelte | 72 -- .../src/lib/workspace-sidebar/types.ts | 1 - 9 files changed, 108 insertions(+), 847 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f651f90f..7d240f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6037,8 +6037,6 @@ dependencies = [ "axum", "chrono", "futures", - "manifest", - "pod-store", "project-record", "protocol", "reqwest", diff --git a/crates/workspace-server/Cargo.toml b/crates/workspace-server/Cargo.toml index 99558ec2..9e6c20cd 100644 --- a/crates/workspace-server/Cargo.toml +++ b/crates/workspace-server/Cargo.toml @@ -9,9 +9,7 @@ publish = false async-trait.workspace = true axum = { workspace = true, features = ["ws"] } chrono = { version = "0.4", default-features = false, features = ["clock"] } -manifest = { workspace = true } futures.workspace = true -pod-store = { workspace = true } protocol = { workspace = true } project-record.workspace = true reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "native-tls"] } diff --git a/crates/workspace-server/src/companion.rs b/crates/workspace-server/src/companion.rs index 24f32811..a1b23d8e 100644 --- a/crates/workspace-server/src/companion.rs +++ b/crates/workspace-server/src/companion.rs @@ -564,14 +564,12 @@ fn diagnostic( #[cfg(test)] mod tests { use super::*; - use crate::hosts::{EmbeddedWorkerRuntime, LocalWorkerRuntime, RuntimeRegistry}; + use crate::hosts::{EmbeddedWorkerRuntime, RuntimeRegistry}; #[test] fn companion_spawns_visible_worker_and_records_providerless_turn() { - let registry = RuntimeRegistry::for_workspace( - LocalWorkerRuntime::new("local:test", "/workspace/project", None), - EmbeddedWorkerRuntime::new_memory("local:test"), - ); + let registry = + RuntimeRegistry::for_workspace(EmbeddedWorkerRuntime::new_memory("local:test")); let registry = Arc::new(registry); let companion = CompanionConsole::new(registry.clone()); diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index bfa68575..eecb7b33 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -1,20 +1,12 @@ use crate::Error; use chrono::Utc; -use pod_store::WorkerMetadata; use reqwest::StatusCode; use reqwest::blocking::{Client as BlockingHttpClient, RequestBuilder}; use reqwest::header::{AUTHORIZATION, CONTENT_TYPE}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use serde_json::Value; use sha2::{Digest, Sha256}; -use std::{ - collections::BTreeSet, - fs, - path::{Path, PathBuf}, - sync::Arc, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use worker_runtime::catalog::{ CapabilityRequest, ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail, WorkerIntent, WorkerStatus as EmbeddedWorkerStatus, @@ -38,9 +30,7 @@ use worker_runtime::observation::{ TranscriptProjection as EmbeddedTranscriptProjection, TranscriptQuery, TranscriptRole, }; -const LOCAL_RUNTIME_ID: &str = "local-worker-runtime"; const EMBEDDED_RUNTIME_ID: &str = "embedded-worker-runtime"; -const LOCAL_HOST_KIND: &str = "local-worker-host"; const EMBEDDED_HOST_KIND: &str = "embedded-worker-runtime-host"; const REMOTE_HOST_KIND: &str = "remote-worker-runtime-host"; const MAX_DIAGNOSTICS: usize = 16; @@ -77,11 +67,7 @@ pub enum DiagnosticSeverity { #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum RuntimeSourceKind { - /// Compatibility projection over the existing local Pod metadata store. - LocalCompatibility, - /// Reserved boundary for the future in-process worker-runtime Runtime adapter. EmbeddedWorkerRuntime, - /// Reserved boundary for a future remote Workspace Runtime adapter. RemoteHttp, } @@ -96,7 +82,7 @@ pub enum RuntimeSourceStatus { #[serde(rename_all = "snake_case")] pub enum RuntimeIdentityAuthority { /// Public Runtime/Host/Worker ids are registry projections, never raw - /// compatibility-store names, socket addresses, session ids, or paths. + /// socket addresses, session ids, credentials, or paths. RuntimeRegistryProjection, } @@ -109,15 +95,6 @@ pub struct RuntimeSourceSummary { } impl RuntimeSourceSummary { - pub fn local_compatibility() -> Self { - Self { - kind: RuntimeSourceKind::LocalCompatibility, - status: RuntimeSourceStatus::Active, - identity_authority: RuntimeIdentityAuthority::RuntimeRegistryProjection, - note: "read-only compatibility projection over local Worker metadata; no socket, session, or path authority is exposed".to_string(), - } - } - pub fn embedded_worker_runtime() -> Self { Self { kind: RuntimeSourceKind::EmbeddedWorkerRuntime, @@ -170,7 +147,6 @@ pub struct RuntimeCapabilitySummary { pub has_git: bool, pub supports_worktrees: bool, pub supports_backend_internal_tools: bool, - pub local_pod_inspection: String, pub workspace_scope: String, pub max_workers: usize, pub os: String, @@ -674,15 +650,8 @@ impl RuntimeRegistry { Self { runtimes } } - pub fn for_local_pods(runtime: LocalWorkerRuntime) -> Self { - Self::new(vec![Arc::new(runtime)]) - } - - pub fn for_workspace( - local_runtime: LocalWorkerRuntime, - embedded_runtime: EmbeddedWorkerRuntime, - ) -> Self { - Self::new(vec![Arc::new(local_runtime), Arc::new(embedded_runtime)]) + pub fn for_workspace(embedded_runtime: EmbeddedWorkerRuntime) -> Self { + Self::new(vec![Arc::new(embedded_runtime)]) } pub fn register(&mut self, runtime: R) @@ -1894,300 +1863,6 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime { } } -#[derive(Clone)] -pub struct LocalWorkerRuntime { - runtime_id: String, - host_id: String, - workspace_root: PathBuf, - data_dir: Option, -} - -pub type LocalRuntimeBridge = LocalWorkerRuntime; - -impl LocalWorkerRuntime { - pub fn new( - workspace_id: impl AsRef, - workspace_root: impl Into, - data_dir: Option, - ) -> Self { - Self { - runtime_id: LOCAL_RUNTIME_ID.to_string(), - host_id: host_id_for_workspace(workspace_id.as_ref()), - workspace_root: workspace_root.into(), - data_dir, - } - } - - fn pod_root(&self) -> Option { - self.data_dir.as_ref().map(|dir| dir.join("pods")) - } - - fn worker_names(&self, pod_root: &Path) -> Result, RuntimeDiagnostic> { - let entries = fs::read_dir(pod_root).map_err(|err| { - diagnostic( - "local_pod_registry_unreadable", - DiagnosticSeverity::Warning, - format!("local Worker registry could not be read: {err}"), - ) - })?; - - let mut worker_names = BTreeSet::new(); - for entry in entries.flatten() { - let Ok(file_type) = entry.file_type() else { - continue; - }; - if !file_type.is_dir() { - continue; - } - let Some(name) = entry.file_name().to_str().map(|name| name.to_string()) else { - continue; - }; - worker_names.insert(name); - } - Ok(worker_names) - } - - fn read_worker(&self, pod_root: &Path, worker_name: &str) -> WorkerReadOutcome { - let metadata_path = pod_root.join(worker_name).join("metadata.json"); - let data = match fs::read_to_string(metadata_path) { - Ok(data) => data, - Err(err) => { - return WorkerReadOutcome::Diagnostic(diagnostic( - "local_pod_metadata_unreadable", - DiagnosticSeverity::Warning, - format!("local Worker metadata could not be read: {err}"), - )); - } - }; - let metadata: WorkerMetadata = match serde_json::from_str(&data) { - Ok(metadata) => metadata, - Err(err) => { - return WorkerReadOutcome::Diagnostic(diagnostic( - "local_pod_metadata_invalid", - DiagnosticSeverity::Warning, - format!("local Worker metadata could not be parsed: {err}"), - )); - } - }; - - let Some(metadata_workspace_root) = metadata.workspace_root.as_ref() else { - return WorkerReadOutcome::Diagnostic(diagnostic( - "local_pod_workspace_root_missing", - DiagnosticSeverity::Warning, - "local Worker metadata did not include a workspace identity and was not included" - .to_string(), - )); - }; - if !same_workspace_root(metadata_workspace_root, &self.workspace_root) { - return WorkerReadOutcome::Diagnostic(diagnostic( - "local_pod_workspace_not_visible", - DiagnosticSeverity::Info, - "local Worker metadata belongs to another workspace and was not included" - .to_string(), - )); - } - - let label = safe_display_hint(&metadata.worker_name); - let role = manifest_hint_string(&metadata.resolved_manifest_snapshot, "role"); - let profile = - manifest_hint_string(&metadata.resolved_manifest_snapshot, "profile_selector"); - let worker_id = worker_id_for_pod(worker_name); - let status = match metadata - .active - .as_ref() - .and_then(|active| active.segment_id.as_ref()) - { - Some(_) => "active:segment".to_string(), - None if metadata.active.is_some() => "active:pending_segment".to_string(), - None => "metadata_only".to_string(), - }; - let state = if metadata.active.is_some() { - "active".to_string() - } else { - "metadata_only".to_string() - }; - let last_seen_at = None; - WorkerReadOutcome::Worker(WorkerSummary { - runtime_id: self.runtime_id.clone(), - worker_id, - host_id: self.host_id.clone(), - label, - role, - profile, - workspace: WorkerWorkspaceSummary { - visibility: "current_workspace".to_string(), - identity: "runtime_workspace".to_string(), - }, - state, - status, - last_seen_at, - implementation: WorkerImplementationSummary { - kind: "local_pod".to_string(), - display_hint: safe_display_hint(worker_name), - }, - capabilities: WorkerCapabilitySummary { - can_accept_input: true, - can_stream_events: false, - can_stop: false, - can_spawn_followup: false, - can_read_bounded_transcript: false, - }, - diagnostics: vec![diagnostic( - "worker_actions_not_implemented", - DiagnosticSeverity::Info, - "runtime overview is available; worker control and stream/proxy operations are not yet implemented" - .to_string(), - )], - }) - } -} - -impl WorkspaceWorkerRuntime for LocalWorkerRuntime { - fn runtime_id(&self) -> &str { - &self.runtime_id - } - - fn runtime_summary(&self, limit: usize) -> RuntimeSummary { - let host_list = self.list_hosts(1); - RuntimeSummary { - runtime_id: self.runtime_id.clone(), - label: "Local Worker runtime".to_string(), - kind: "local_pod".to_string(), - status: "available".to_string(), - source: RuntimeSourceSummary::local_compatibility(), - host_ids: host_list - .items - .iter() - .take(limit) - .map(|host| host.host_id.clone()) - .collect(), - capabilities: local_runtime_capabilities(limit, self.pod_root().is_some()), - diagnostics: host_list.diagnostics, - } - } - - fn list_hosts(&self, limit: usize) -> RuntimeList { - if limit == 0 { - return RuntimeList::new(Vec::new(), Vec::new()); - } - - let mut diagnostics = Vec::new(); - let inspection_available = self.pod_root().is_some(); - if !inspection_available { - diagnostics.push(diagnostic( - "local_pod_registry_unavailable", - DiagnosticSeverity::Warning, - "local Worker data directory is not configured; worker discovery is unavailable" - .to_string(), - )); - } - - let item = HostSummary { - runtime_id: self.runtime_id.clone(), - host_id: self.host_id.clone(), - label: label_for_workspace(&self.workspace_root), - kind: LOCAL_HOST_KIND.to_string(), - status: if inspection_available { - "available" - } else { - "degraded" - } - .to_string(), - observed_at: Utc::now().to_rfc3339(), - last_seen_at: None, - capabilities: local_runtime_capabilities(limit, inspection_available), - diagnostics: diagnostics.clone(), - }; - RuntimeList::new(vec![item], diagnostics) - } - - fn list_workers(&self, limit: usize) -> RuntimeList { - let Some(pod_root) = self.pod_root() else { - return RuntimeList::new( - Vec::new(), - vec![diagnostic( - "local_pod_registry_unavailable", - DiagnosticSeverity::Warning, - "local Worker data directory is not configured; worker discovery is unavailable" - .to_string(), - )], - ); - }; - if limit == 0 { - return RuntimeList::new(Vec::new(), Vec::new()); - } - - let mut workers = Vec::new(); - let mut diagnostics = Vec::new(); - let worker_names = match self.worker_names(&pod_root) { - Ok(worker_names) => worker_names, - Err(diag) => return RuntimeList::new(Vec::new(), vec![diag]), - }; - - for worker_name in worker_names { - if workers.len() >= limit { - break; - } - match self.read_worker(&pod_root, &worker_name) { - WorkerReadOutcome::Worker(worker) => workers.push(worker), - WorkerReadOutcome::Diagnostic(diag) => { - if diagnostics.len() < MAX_DIAGNOSTICS { - diagnostics.push(diag); - } - } - } - } - RuntimeList::new(workers, diagnostics) - } - - fn worker(&self, worker_id: &str) -> WorkerLookupResult { - let Some(pod_root) = self.pod_root() else { - return WorkerLookupResult { - worker: None, - diagnostics: vec![diagnostic( - "local_pod_registry_unavailable", - DiagnosticSeverity::Warning, - "local Worker data directory is not configured; worker discovery is unavailable" - .to_string(), - )], - }; - }; - let worker_names = match self.worker_names(&pod_root) { - Ok(worker_names) => worker_names, - Err(diag) => { - return WorkerLookupResult { - worker: None, - diagnostics: vec![diag], - }; - } - }; - for worker_name in worker_names { - if worker_id_for_pod(&worker_name) != worker_id { - continue; - } - return match self.read_worker(&pod_root, &worker_name) { - WorkerReadOutcome::Worker(worker) => WorkerLookupResult { - worker: Some(worker), - diagnostics: Vec::new(), - }, - WorkerReadOutcome::Diagnostic(diag) => WorkerLookupResult { - worker: None, - diagnostics: vec![diag], - }, - }; - } - WorkerLookupResult { - worker: None, - diagnostics: Vec::new(), - } - } -} - -enum WorkerReadOutcome { - Worker(WorkerSummary), - Diagnostic(RuntimeDiagnostic), -} - fn embedded_runtime_capabilities(limit: usize, available: bool) -> RuntimeCapabilitySummary { RuntimeCapabilitySummary { can_list_hosts: true, @@ -2203,7 +1878,6 @@ fn embedded_runtime_capabilities(limit: usize, available: bool) -> RuntimeCapabi has_git: false, supports_worktrees: false, supports_backend_internal_tools: true, - local_pod_inspection: "not_applicable".to_string(), workspace_scope: "backend_internal".to_string(), max_workers: limit, os: std::env::consts::OS.to_string(), @@ -2472,7 +2146,6 @@ fn remote_runtime_capabilities(limit: usize, available: bool) -> RuntimeCapabili has_git: false, supports_worktrees: false, supports_backend_internal_tools: false, - local_pod_inspection: "unavailable".to_string(), workspace_scope: "remote_runtime_backend_private".to_string(), max_workers: limit, os: "remote".to_string(), @@ -2532,37 +2205,6 @@ fn remote_http_status_diagnostic( ) } -fn local_runtime_capabilities( - limit: usize, - inspection_available: bool, -) -> RuntimeCapabilitySummary { - RuntimeCapabilitySummary { - can_list_hosts: true, - can_list_workers: inspection_available, - can_get_worker: inspection_available, - can_spawn_worker: false, - can_stop_worker: false, - can_accept_input: false, - can_stream_events: false, - can_read_bounded_transcript: false, - has_workspace_fs: false, - has_shell: false, - has_git: false, - supports_worktrees: false, - supports_backend_internal_tools: false, - local_pod_inspection: if inspection_available { - "available" - } else { - "unavailable" - } - .to_string(), - workspace_scope: "current_workspace".to_string(), - max_workers: limit, - os: std::env::consts::OS.to_string(), - arch: std::env::consts::ARCH.to_string(), - } -} - fn diagnostic( code: impl Into, severity: DiagnosticSeverity, @@ -2594,14 +2236,6 @@ fn operation_failed_or_unknown_worker( }) } -fn host_id_for_workspace(workspace_id: &str) -> String { - bounded_backend_identifier("local-", workspace_id) -} - -fn worker_id_for_pod(worker_name: &str) -> String { - bounded_backend_identifier("local-worker-", worker_name) -} - fn bounded_backend_identifier(prefix: &str, value: &str) -> String { let digest = digest_hex(value.as_bytes(), ID_DIGEST_HEX_LEN); let mut body = sanitize_identifier_body(value); @@ -2680,27 +2314,6 @@ fn validate_backend_identifier( Ok(()) } -fn label_for_workspace(workspace_root: &Path) -> String { - workspace_root - .file_name() - .and_then(|name| name.to_str()) - .filter(|name| !name.is_empty()) - .unwrap_or("workspace") - .to_string() -} - -fn same_workspace_root(left: &Path, right: &Path) -> bool { - lexical_path_string(left) == lexical_path_string(right) -} - -fn lexical_path_string(path: &Path) -> String { - let mut value = path.to_string_lossy().replace('\\', "/"); - while value.len() > 1 && value.ends_with('/') { - value.pop(); - } - value -} - fn safe_display_hint(value: &str) -> String { value .chars() @@ -2722,27 +2335,6 @@ fn worker_spawn_intent_label(intent: &WorkerSpawnIntent) -> &'static str { } } -fn manifest_hint_string(snapshot: &Option, key: &str) -> Option { - let value = snapshot.as_ref()?; - let candidate = match key { - "role" => value - .get("role") - .or_else(|| value.get("role_claim").and_then(|role| role.get("role"))), - "profile_selector" => value - .get("profile_selector") - .or_else(|| value.get("profile")), - _ => value.get(key), - }?; - let text = candidate.as_str().map(ToOwned::to_owned).or_else(|| { - candidate - .get("name") - .and_then(|name| name.as_str()) - .map(ToOwned::to_owned) - })?; - let safe = safe_display_hint(&text); - (!safe.is_empty()).then_some(safe) -} - pub fn placeholder_worker(host_id: impl Into) -> WorkerSummary { let host_id = host_id.into(); WorkerSummary { @@ -2795,32 +2387,10 @@ pub fn placeholder_spawn_response(host_id: impl Into) -> WorkerSpawnResu mod tests { use super::*; use serde_json::json; - use std::fs; use std::io::{Read as _, Write as _}; use std::net::TcpListener; use std::sync::Arc; use std::thread; - use tempfile::TempDir; - - fn write_metadata(dir: &Path, worker_name: &str, metadata: &WorkerMetadata) { - let pod_dir = dir.join("pods").join(worker_name); - fs::create_dir_all(&pod_dir).unwrap(); - fs::write( - pod_dir.join("metadata.json"), - serde_json::to_vec(metadata).unwrap(), - ) - .unwrap(); - } - - fn metadata(workspace_root: Option<&str>) -> WorkerMetadata { - let mut metadata = WorkerMetadata::new("coder", None); - metadata.workspace_root = workspace_root.map(PathBuf::from); - metadata.resolved_manifest_snapshot = Some(json!({ - "role": "coder", - "profile_selector": "builtin:coder" - })); - metadata - } fn test_config_bundle() -> ConfigBundle { ConfigBundle { @@ -2848,15 +2418,6 @@ mod tests { .with_computed_digest() } - fn assert_valid_generated_id(id: &str) { - assert!(id.len() <= MAX_IDENTIFIER_LEN, "id too long: {id}"); - validate_backend_identifier("test_id", id).unwrap(); - } - - fn host_id() -> String { - host_id_for_workspace("local:test") - } - #[derive(Clone)] struct FixtureRuntime { runtime_id: String, @@ -2927,7 +2488,6 @@ mod tests { has_git: false, supports_worktrees: false, supports_backend_internal_tools: false, - local_pod_inspection: "none".to_string(), workspace_scope: "none".to_string(), max_workers: self.workers.len(), os: "test".to_string(), @@ -3035,140 +2595,10 @@ mod tests { )); } - #[test] - fn local_runtime_reports_host_without_private_paths() { - let bridge = LocalWorkerRuntime::new("local:test", "/workspace/project", None); - let hosts = bridge.list_hosts(10); - assert_eq!(hosts.items.len(), 1); - let host = &hosts.items[0]; - assert_eq!(host.runtime_id, LOCAL_RUNTIME_ID); - assert_eq!(host.host_id, host_id()); - assert_valid_generated_id(&host.host_id); - assert_eq!(host.capabilities.local_pod_inspection, "unavailable"); - assert_eq!(host.capabilities.workspace_scope, "current_workspace"); - let json = serde_json::to_string(host).unwrap(); - assert!(!json.contains("/workspace/project")); - assert!(!json.contains("metadata.json")); - } - - #[test] - fn registry_lists_runtimes_hosts_and_workers() { - let temp = TempDir::new().unwrap(); - write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project"))); - let registry = RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - )); - - let runtimes = registry.list_runtimes(10); - assert_eq!(runtimes.items[0].runtime_id, LOCAL_RUNTIME_ID); - assert_eq!( - runtimes.items[0].source.kind, - RuntimeSourceKind::LocalCompatibility - ); - assert_eq!( - runtimes.items[0].source.identity_authority, - RuntimeIdentityAuthority::RuntimeRegistryProjection - ); - assert_eq!(runtimes.items[0].host_ids, vec![host_id()]); - - let hosts = registry.list_hosts(10); - assert_eq!(hosts.items[0].host_id, host_id()); - assert_valid_generated_id(&hosts.items[0].host_id); - - let workers = registry.list_workers(10); - assert_eq!(workers.items.len(), 1); - let worker = &workers.items[0]; - assert_eq!(worker.runtime_id, LOCAL_RUNTIME_ID); - assert_eq!(worker.worker_id, worker_id_for_pod("coder")); - assert_valid_generated_id(&worker.worker_id); - assert_eq!(worker.host_id, host_id()); - assert_eq!(worker.workspace.visibility, "current_workspace"); - assert_eq!(worker.implementation.display_hint, "coder"); - let json = serde_json::to_string(worker).unwrap(); - assert!(!json.contains("/workspace/project")); - assert!(!json.contains("metadata.json")); - } - - #[test] - fn registry_resolves_backend_validated_host_ids() { - let temp = TempDir::new().unwrap(); - write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project"))); - let registry = RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - )); - - let workers = registry.list_workers_for_host(&host_id(), 10).unwrap(); - assert_eq!(workers.items.len(), 1); - assert!(matches!( - registry.list_workers_for_host("../secret", 10), - Err(RuntimeRegistryError::InvalidIdentifier { .. }) - )); - assert!(matches!( - registry.list_workers_for_host("local-missing", 10), - Err(RuntimeRegistryError::UnknownHost(_)) - )); - } - - #[test] - fn local_runtime_excludes_other_workspace_metadata() { - let temp = TempDir::new().unwrap(); - write_metadata(temp.path(), "other", &metadata(Some("/workspace/other"))); - write_metadata(temp.path(), "missing", &metadata(None)); - let bridge = LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - ); - let workers = bridge.list_workers(10); - assert!(workers.items.is_empty()); - assert!( - workers - .diagnostics - .iter() - .any(|diag| diag.code == "local_pod_workspace_not_visible") - ); - assert!( - workers - .diagnostics - .iter() - .any(|diag| diag.code == "local_pod_workspace_root_missing") - ); - } - - #[test] - fn local_runtime_worker_detail_is_safe_and_bounded() { - let temp = TempDir::new().unwrap(); - write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project"))); - let bridge = LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - ); - let worker_id = worker_id_for_pod("coder"); - let worker = bridge.worker(&worker_id).worker.unwrap(); - assert_eq!(worker.label, "coder"); - assert_eq!(worker.workspace.identity, "runtime_workspace"); - assert!( - bridge - .worker(&worker_id_for_pod("missing")) - .worker - .is_none() - ); - } - #[test] fn embedded_runtime_registers_routes_input_and_transcript_without_internal_leaks() { - let temp = TempDir::new().unwrap(); - let mut registry = RuntimeRegistry::for_local_pods(LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - )); - registry.register(EmbeddedWorkerRuntime::new_memory("local:test")); + let registry = + RuntimeRegistry::for_workspace(EmbeddedWorkerRuntime::new_memory("local:test")); let runtimes = registry.list_runtimes(10); let embedded_summary = runtimes @@ -3242,19 +2672,6 @@ mod tests { assert_eq!(transcript.items[0].role, "user"); assert_eq!(transcript.items[0].content, "hello embedded runtime"); - assert!(matches!( - registry.send_input( - LOCAL_RUNTIME_ID, - &worker.worker_id, - WorkerInputRequest { - kind: WorkerInputKind::User, - content: "wrong runtime".to_string(), - }, - ), - Err(RuntimeRegistryError::UnknownWorker { runtime_id, worker_id }) - if runtime_id == LOCAL_RUNTIME_ID && worker_id == worker.worker_id - )); - let json = serde_json::to_string(&(embedded_summary, worker, transcript)).unwrap(); for forbidden in [ "/workspace/project", @@ -3627,61 +3044,4 @@ mod tests { "last_event_id": 0 }) } - - #[test] - fn generated_worker_ids_are_opaque_bounded_unique_and_resolvable() { - let temp = TempDir::new().unwrap(); - let long_a = format!("{}-A", "TicketWorkerWithVeryLongName".repeat(8)); - let long_b = format!("{}-B", "TicketWorkerWithVeryLongName".repeat(8)); - let worker_names = vec![ - "00001KVWECEQG-Coder.Worker".to_string(), - "foo.bar".to_string(), - "foo-bar".to_string(), - "Ticket#Worker@Reviewer".to_string(), - long_a, - long_b, - ]; - for worker_name in &worker_names { - write_metadata( - temp.path(), - worker_name, - &metadata(Some("/workspace/project")), - ); - } - let bridge = LocalWorkerRuntime::new( - "local:test", - "/workspace/project", - Some(temp.path().to_path_buf()), - ); - let registry = RuntimeRegistry::for_local_pods(bridge.clone()); - - let listed = registry.list_workers(100); - assert_eq!(listed.items.len(), worker_names.len()); - let mut ids = BTreeSet::new(); - for worker in listed.items { - assert_valid_generated_id(&worker.worker_id); - assert!( - ids.insert(worker.worker_id.clone()), - "duplicate id: {}", - worker.worker_id - ); - assert!(!worker.worker_id.contains('.')); - assert!(!worker.worker_id.contains('@')); - assert!(!worker.worker_id.contains('#')); - - let from_registry = registry - .worker(&worker.runtime_id, &worker.worker_id) - .unwrap(); - assert_eq!(from_registry.worker_id, worker.worker_id); - let from_runtime = bridge.worker(&worker.worker_id).worker.unwrap(); - assert_eq!(from_runtime.worker_id, worker.worker_id); - } - - let dotted = worker_id_for_pod("foo.bar"); - let dashed = worker_id_for_pod("foo-bar"); - assert_ne!(dotted, dashed); - assert!(ids.contains(&dotted)); - assert!(ids.contains(&dashed)); - assert!(ids.iter().any(|id| id.len() == MAX_IDENTIFIER_LEN)); - } } diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 22feb94f..ceae3612 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -18,8 +18,8 @@ use crate::companion::{ }; use crate::hosts::{ ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EmbeddedWorkerRuntime, - HostSummary, LocalWorkerRuntime, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, - RuntimeRegistry, RuntimeSummary, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, + HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry, + RuntimeSummary, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, WorkerLifecycleResult, WorkerSpawnRequest, WorkerSpawnResult, WorkerSummary, WorkerTranscriptProjection, }; @@ -53,7 +53,6 @@ pub struct ServerConfig { pub static_assets_dir: Option, pub auth: AuthConfig, pub max_records: usize, - pub local_runtime_data_dir: Option, pub runtime_event_sources: Vec, pub remote_runtime_sources: Vec, } @@ -71,7 +70,6 @@ impl ServerConfig { token_configured: false, }, max_records: 200, - local_runtime_data_dir: manifest::paths::data_dir(), runtime_event_sources: Vec::new(), remote_runtime_sources: Vec::new(), } @@ -99,14 +97,9 @@ impl WorkspaceApi { updated_at: config.workspace_created_at.clone(), }) .await?; - let mut runtime = RuntimeRegistry::for_workspace( - LocalWorkerRuntime::new( - config.workspace_id.clone(), - config.workspace_root.clone(), - config.local_runtime_data_dir.clone(), - ), - EmbeddedWorkerRuntime::new_memory(config.workspace_id.clone()), - ); + let mut runtime = RuntimeRegistry::for_workspace(EmbeddedWorkerRuntime::new_memory( + config.workspace_id.clone(), + )); for remote_config in config.remote_runtime_sources.iter().cloned() { runtime .register(RemoteWorkerRuntime::new(remote_config).map_err(|err| err.into_error())?); @@ -1049,7 +1042,6 @@ mod tests { let store = SqliteWorkspaceStore::in_memory().unwrap(); let mut config = ServerConfig::local_dev(dir.path(), test_identity()); config.static_assets_dir = Some(static_dir); - config.local_runtime_data_dir = Some(dir.path().join("data")); let api = WorkspaceApi::new(config, Arc::new(store)).await.unwrap(); let app = build_router(api); @@ -1109,28 +1101,24 @@ mod tests { let hosts = get_json(app.clone(), "/api/hosts").await; assert_eq!(hosts["source"], "worker_runtime_registry"); - assert_eq!(hosts["items"][0]["runtime_id"], "local-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(); - assert!(host_id.starts_with("local-")); - assert!(host_id.len() <= 120); - assert_ne!(host_id, TEST_REPOSITORY_ID); - assert_eq!(hosts["items"][0]["kind"], "local-worker-host"); - assert_eq!( - hosts["items"][0]["capabilities"]["local_pod_inspection"], - "available" - ); + assert_eq!(hosts["items"][0]["kind"], "embedded-worker-runtime-host"); assert_eq!( hosts["items"][0]["capabilities"]["workspace_scope"], - "current_workspace" + "backend_internal" ); assert!(!hosts.to_string().contains("metadata.json")); let runtimes = get_json(app.clone(), "/api/runtimes").await; assert_eq!(runtimes["source"], "worker_runtime_registry"); - assert_eq!(runtimes["items"][0]["runtime_id"], "local-worker-runtime"); + assert_eq!( + runtimes["items"][0]["runtime_id"], + "embedded-worker-runtime" + ); assert_eq!( runtimes["items"][0]["source"]["kind"], - "local_compatibility" + "embedded_worker_runtime" ); assert_eq!( runtimes["items"][0]["source"]["identity_authority"], @@ -1147,10 +1135,6 @@ mod tests { .expect("companion worker is visible through runtime worker API"); assert_eq!(companion_worker["runtime_id"], "embedded-worker-runtime"); assert_eq!(companion_worker["capabilities"]["can_stop"], false); - assert_eq!( - workers["diagnostics"][0]["code"], - "local_pod_registry_unreadable" - ); let companion_status = get_json(app.clone(), "/api/companion/status").await; assert_eq!(companion_status["state"], "ready"); @@ -1186,7 +1170,13 @@ mod tests { assert_eq!(companion_transcript["items"][1]["role"], "assistant"); let host_workers = get_json(app.clone(), &format!("/api/hosts/{host_id}/workers")).await; - assert!(host_workers["items"].as_array().unwrap().is_empty()); + assert!( + host_workers["items"] + .as_array() + .unwrap() + .iter() + .any(|worker| worker["role"] == "workspace_companion") + ); let runs_response = app .clone() @@ -1270,8 +1260,7 @@ mod tests { async fn embedded_runtime_api_routes_by_runtime_and_worker_ids_without_leaking_internals() { let dir = tempfile::tempdir().unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); - let mut config = ServerConfig::local_dev(dir.path(), test_identity()); - config.local_runtime_data_dir = Some(dir.path().join("data")); + let config = ServerConfig::local_dev(dir.path(), test_identity()); let api = WorkspaceApi::new(config, Arc::new(store)).await.unwrap(); let app = build_router(api); @@ -1362,7 +1351,7 @@ mod tests { Request::builder() .method("POST") .uri(format!( - "/api/runtimes/local-worker-runtime/workers/{worker_id}/input" + "/api/runtimes/unknown-runtime/workers/{worker_id}/input" )) .header(CONTENT_TYPE, "application/json") .body(Body::from( @@ -1418,7 +1407,6 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); let mut config = ServerConfig::local_dev(dir.path(), test_identity()); - config.local_runtime_data_dir = Some(dir.path().join("data")); config .runtime_event_sources .push(RuntimeObservationSourceConfig { @@ -1630,7 +1618,6 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let store = SqliteWorkspaceStore::in_memory().unwrap(); let mut config = ServerConfig::local_dev(dir.path(), test_identity()); - config.local_runtime_data_dir = Some(dir.path().join("data")); let runtime_id = source.runtime_id.clone(); let worker_id = source.worker_id.clone(); config.runtime_event_sources.push(source); diff --git a/package.nix b/package.nix index ffab8968..799b2b55 100644 --- a/package.nix +++ b/package.nix @@ -43,7 +43,7 @@ rustPlatform.buildRustPackage rec { filter = sourceFilter; }; - cargoHash = "sha256-fdmGo/HE80wRSLE/u20YXS63G/vvHx43uoc9BivZUxQ="; + cargoHash = "sha256-TPXVkDfy61HCWTfSr0boLKlFbvc6zdpRKQRUDXuPppU="; depsExtraArgs = { # Older fetchCargoVendor utilities used crates.io's API download endpoint, diff --git a/web/workspace/src/app.css b/web/workspace/src/app.css index 203d868d..e5e6f1d2 100644 --- a/web/workspace/src/app.css +++ b/web/workspace/src/app.css @@ -52,8 +52,8 @@ --radius-soft: 8px; --radius-panel: 12px; - --interactive-hover: color-mix(in oklch, var(--bg-subtle) 88%, white 12%); - --interactive-selected: color-mix(in oklch, var(--bg-subtle) 88%, var(--accent) 12%); + --interactive-hover: oklch(94.5% 0 0); + --interactive-selected: oklch(93% 0.02 230); --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; @@ -81,6 +81,8 @@ --success: oklch(77% 0.12 145); --warning: oklch(82% 0.13 85); --danger: oklch(76% 0.14 25); + --interactive-hover: oklch(29% 0 0); + --interactive-selected: oklch(30% 0.025 230); } } } @@ -598,9 +600,9 @@ min-width: 10rem; padding: 0.75rem 0.9rem; border-radius: 16px; - background: #ecfeff; - border: 1px solid #a5f3fc; - color: #0f766e; + background: var(--bg-raised); + border: 1px solid var(--line); + color: var(--text-muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; @@ -610,28 +612,28 @@ .console-status small { text-transform: none; letter-spacing: 0; - color: #475569; + color: var(--text-muted); font-weight: 600; } .console-status[data-state='busy'] { - background: #fff7ed; - border-color: #fed7aa; - color: #c2410c; + background: var(--bg-raised); + border-color: var(--line); + color: var(--warning); } .console-status[data-state='error'], .console-status[data-state='timeout'] { - background: #fef2f2; - border-color: #fecaca; - color: #b91c1c; + background: var(--bg-raised); + border-color: var(--line); + color: var(--danger); } .console-status[data-state='cancelled'], .console-status[data-state='rejected'] { - background: #f8fafc; - border-color: #cbd5e1; - color: #475569; + background: var(--bg-raised); + border-color: var(--line); + color: var(--text-muted); } .console-transport { @@ -647,7 +649,7 @@ .console-transport dt { margin: 0; - color: #64748b; + color: var(--text-muted); font-size: 0.75rem; font-weight: 700; letter-spacing: 0.05em; @@ -656,14 +658,14 @@ .console-transport dd { margin: 0; - color: #0f172a; + color: var(--text-strong); font-weight: 700; } .console-transport p { grid-column: 1 / -1; margin: 0; - color: #475569; + color: var(--text-muted); line-height: 1.5; } @@ -676,20 +678,20 @@ margin: 0; padding: 0.55rem 0.7rem; border-radius: 10px; - background: #eff6ff; - color: #1d4ed8; + background: var(--bg-raised); + color: var(--accent); font-size: 0.86rem; } .diagnostic.warning, .diagnostic.warn { - background: #fff7ed; - color: #c2410c; + background: var(--bg-raised); + color: var(--warning); } .diagnostic.error { - background: #fef2f2; - color: #b91c1c; + background: var(--bg-raised); + color: var(--danger); } .transcript-card, @@ -711,20 +713,20 @@ gap: 0.45rem; padding: 0.85rem 1rem; border-radius: 16px; - border: 1px solid #dbeafe; - background: #f8fafc; + border: 1px solid var(--line); + background: var(--bg-raised); } .transcript-item.user { margin-left: min(8vw, 4rem); - background: #eff6ff; - border-color: #bfdbfe; + background: var(--bg-raised); + border-color: var(--line-strong); } .transcript-item.assistant { margin-right: min(8vw, 4rem); - background: #f0fdf4; - border-color: #bbf7d0; + background: var(--bg-raised); + border-color: var(--line-strong); } .message-meta { @@ -732,12 +734,12 @@ flex-wrap: wrap; align-items: center; gap: 0.55rem; - color: #64748b; + color: var(--text-muted); font-size: 0.78rem; } .message-meta strong { - color: #0f172a; + color: var(--text-strong); text-transform: uppercase; letter-spacing: 0.05em; } @@ -750,37 +752,37 @@ margin: 0; white-space: pre-wrap; line-height: 1.55; - color: #1f2937; + color: var(--code); } .empty-state { margin: 0; padding: 1rem; border-radius: 14px; - background: #f8fafc; - color: #64748b; + background: var(--bg-raised); + color: var(--text-muted); } .composer-card label { font-weight: 800; - color: #0f172a; + color: var(--text-strong); } .composer-card textarea { width: 100%; min-height: 8rem; resize: vertical; - border: 1px solid #cbd5e1; + border: 1px solid var(--line); border-radius: 14px; padding: 0.85rem 1rem; font: inherit; - color: #0f172a; - background: #ffffff; + color: var(--text-strong); + background: var(--bg); } .composer-card textarea:disabled { - background: #f8fafc; - color: #64748b; + background: var(--bg-raised); + color: var(--text-muted); } .composer-actions { @@ -793,7 +795,7 @@ .composer-actions span { margin-right: auto; - color: #64748b; + color: var(--text-muted); font-size: 0.85rem; } @@ -801,15 +803,15 @@ border: 0; border-radius: 999px; padding: 0.65rem 1rem; - background: #2563eb; - color: #ffffff; + background: var(--accent); + color: var(--bg); font-weight: 800; cursor: pointer; } .composer-actions button.secondary { - background: #e2e8f0; - color: #334155; + background: var(--bg-raised); + color: var(--text-strong); } .composer-actions button:disabled { @@ -824,10 +826,10 @@ justify-content: center; gap: var(--space-1); border-radius: 999px; - border: 1px solid var(--border); + border: 1px solid var(--line); padding: 0.4rem 0.75rem; - background: #ffffff; - color: #1d4ed8; + background: var(--bg); + color: var(--accent); font-size: 0.85rem; font-weight: 800; text-decoration: none; @@ -836,8 +838,8 @@ .secondary-button:hover, .inline-link:hover { - border-color: #bfdbfe; - background: #eff6ff; + border-color: var(--line-strong); + background: var(--bg-raised); } .worker-console-shell { @@ -847,10 +849,10 @@ .console-status-pill { min-width: 14rem; padding: 0.75rem 0.9rem; + border: 1px solid var(--line); border-radius: 16px; - background: #ecfeff; - border: 1px solid #a5f3fc; - color: #0f766e; + background: var(--bg-raised); + color: var(--text-muted); font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; @@ -859,9 +861,7 @@ } .console-status-pill.warn { - background: #fff7ed; - border-color: #fed7aa; - color: #c2410c; + color: var(--warning); } .console-grid { @@ -891,26 +891,18 @@ .worker-transcript li { display: grid; gap: var(--space-2); - border: 1px solid #e2e8f0; - border-left: 4px solid #cbd5e1; + border: 1px solid var(--line); border-radius: 14px; padding: 0.75rem 0.85rem; - background: #ffffff; + background: var(--bg); } -.worker-transcript li.user { - border-left-color: #2563eb; - background: #eff6ff; -} -.worker-transcript li.assistant { - border-left-color: #10b981; - background: #f0fdf4; -} + + .worker-transcript li.error-line { - border-left-color: #dc2626; - background: #fef2f2; + border-color: var(--danger); } .message-heading { @@ -918,13 +910,13 @@ align-items: center; justify-content: space-between; gap: var(--space-2); - color: #0f172a; + color: var(--text-strong); font-weight: 800; } .message-heading small { margin: 0; - color: #64748b; + color: var(--text-muted); font-size: 0.74rem; font-weight: 700; text-transform: uppercase; @@ -935,12 +927,12 @@ margin: 0; overflow-x: auto; white-space: pre-wrap; - color: #1f2937; + color: var(--code); } .message-detail, .metadata-details { - color: #475569; + color: var(--text-muted); font-size: 0.84rem; } @@ -967,7 +959,7 @@ } .console-side-card dt { - color: #64748b; + color: var(--text-muted); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.05em; @@ -976,15 +968,16 @@ .console-side-card dd { margin: 0; - color: #0f172a; + color: var(--text-strong); font-weight: 700; } .degrade-note { - border: 1px solid #fde68a; + border: 1px solid var(--line); border-radius: 12px; padding: 0.65rem 0.8rem; - background: #fffbeb; + background: var(--bg-raised); + color: var(--warning); } .console-composer { @@ -993,7 +986,7 @@ } .console-composer label { - color: #0f172a; + color: var(--text-strong); font-weight: 800; } @@ -1001,16 +994,16 @@ width: 100%; min-height: 7rem; resize: vertical; - border: 1px solid #cbd5e1; + border: 1px solid var(--line); border-radius: 14px; padding: 0.85rem 1rem; font: inherit; - color: #0f172a; + color: var(--text-strong); } .console-composer textarea:disabled { - background: #f8fafc; - color: #64748b; + background: var(--bg-raised); + color: var(--text-muted); } @media (max-width: 960px) { diff --git a/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte b/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte index c68398f5..90cbc418 100644 --- a/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte +++ b/web/workspace/src/lib/workspace-pages/WorkspacePage.svelte @@ -3,7 +3,6 @@ import { workerConsoleHref } from '$lib/workspace-console/model'; import WorkspaceSidebar from '$lib/workspace-sidebar/WorkspaceSidebar.svelte'; import type { - Diagnostic, Host, ListResponse, ObjectiveDetail, @@ -28,17 +27,6 @@ objectiveId = null }: { view?: WorkspaceView; repositoryId?: string; objectiveId?: string | null } = $props(); - const endpoints = [ - { label: 'Workspace', path: '/api/workspace' }, - { label: 'Tickets', path: '/api/tickets' }, - { label: 'Objectives', path: '/api/objectives' }, - { label: 'Repositories', path: '/api/repositories' }, - { label: 'Repository tickets', path: '/api/repositories/local/tickets' }, - { label: 'Runs', path: '/api/runs' }, - { label: 'Hosts', path: '/api/hosts' }, - { label: 'Workers', path: '/api/workers' } - ]; - let workspace = $state(null); let hosts = $state | null>(null); let workers = $state | null>(null); @@ -149,10 +137,6 @@ } } - function diagnosticsFor(...groups: Array): Diagnostic[] { - return groups.flatMap((group) => group ?? []); - } - function routeFromView(view: WorkspaceView, objectiveId: string | null): RouteState { if (view === 'repository') { return { page: 'repository' }; @@ -265,21 +249,6 @@ {/if} - {@const repositoryDiagnostics = diagnosticsFor(repository?.git.diagnostics, repositoryTickets?.diagnostics)} - {#if repositoryDiagnostics.length > 0} -
-

Repository diagnostics

-
    - {#each repositoryDiagnostics as diagnostic} -
  • - {diagnostic.severity} - {diagnostic.code} - {diagnostic.message} -
  • - {/each} -
-
- {/if} {:else if route.page === 'objectives' || route.page === 'objective'}

Objectives

@@ -391,26 +360,6 @@ {/if}
-
-
-

Read API surface

-
    - {#each endpoints as endpoint} -
  • {endpoint.path} — {endpoint.label}
  • - {/each} -
-
- -
-

Reserved seams

-

- Event streams remain represented as extension-point state in the backend - response. Hosts and Workers are read-only local observations; no - scheduler, lifecycle control, or hosted multi-tenant behavior is - implemented in this slice. -

-
-
@@ -435,10 +384,6 @@
Kind
{host.kind}
-
-
Local inspection
-
{host.capabilities.local_pod_inspection}
-
Runtime
{host.runtime_id}
@@ -509,23 +454,6 @@
- {#if hosts || workers} - {@const diagnostics = diagnosticsFor(hosts?.diagnostics, workers?.diagnostics)} - {#if diagnostics.length > 0} -
-

Diagnostics

-
    - {#each diagnostics as diagnostic} -
  • - {diagnostic.severity} - {diagnostic.code} - {diagnostic.message} -
  • - {/each} -
-
- {/if} - {/if} {/if} diff --git a/web/workspace/src/lib/workspace-sidebar/types.ts b/web/workspace/src/lib/workspace-sidebar/types.ts index 923b709d..8e039a44 100644 --- a/web/workspace/src/lib/workspace-sidebar/types.ts +++ b/web/workspace/src/lib/workspace-sidebar/types.ts @@ -42,7 +42,6 @@ export type RuntimeCapabilities = { has_git: boolean; supports_worktrees: boolean; supports_backend_internal_tools: boolean; - local_pod_inspection: string; workspace_scope: string; os: string; arch: string;