From 4a90267ebcfe7e088dc149c7848174858214b74d Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 12 Jul 2026 23:43:44 +0900 Subject: [PATCH] workspace: remove input readiness capability --- crates/workspace-server/src/hosts.rs | 44 ------------------- crates/workspace-server/src/server.rs | 2 - .../src/lib/workspace-sidebar/types.ts | 2 - .../src/lib/workspace-sidebar/workers.test.ts | 2 - .../workers/[workerId]/console/+page.svelte | 38 +++++++++++++--- 5 files changed, 31 insertions(+), 57 deletions(-) diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index 3583b53f..5896cb78 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -170,7 +170,6 @@ pub struct RuntimeCapabilitySummary { pub can_get_worker: bool, pub can_spawn_worker: bool, pub can_stop_worker: bool, - pub can_accept_input: bool, pub has_workspace_fs: bool, pub has_shell: bool, pub has_git: bool, @@ -223,7 +222,6 @@ pub struct WorkerImplementationSummary { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct WorkerCapabilitySummary { - pub can_accept_input: bool, pub can_stop: bool, pub can_spawn_followup: bool, } @@ -1204,14 +1202,6 @@ impl EmbeddedWorkerRuntime { )) } - fn can_accept_embedded_input( - &self, - status: EmbeddedWorkerStatus, - execution: &worker_runtime::execution::WorkerExecutionStatus, - ) -> bool { - runtime_worker_can_accept_input(self.execution_enabled, status, execution) - } - fn can_stop_embedded_worker( &self, status: EmbeddedWorkerStatus, @@ -1242,8 +1232,6 @@ impl EmbeddedWorkerRuntime { display_hint: "backend-internal worker-runtime Worker".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: self - .can_accept_embedded_input(summary.status, &summary.execution), can_stop: self.can_stop_embedded_worker(summary.status, &summary.execution), can_spawn_followup: false, }, @@ -1278,7 +1266,6 @@ impl EmbeddedWorkerRuntime { display_hint: "backend-internal worker-runtime Worker".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: self.can_accept_embedded_input(detail.status, &detail.execution), can_stop: self.can_stop_embedded_worker(detail.status, &detail.execution), can_spawn_followup: false, }, @@ -2014,7 +2001,6 @@ impl RemoteWorkerRuntime { display_hint: "Backend-proxied remote worker-runtime Worker".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: runtime_worker_can_accept_input(true, summary.status, &summary.execution), can_stop: runtime_worker_can_stop(true, summary.status, &summary.execution), can_spawn_followup: false, }, @@ -2049,7 +2035,6 @@ impl RemoteWorkerRuntime { display_hint: "Backend-proxied remote worker-runtime Worker".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: runtime_worker_can_accept_input(true, detail.status, &detail.execution), can_stop: runtime_worker_can_stop(true, detail.status, &detail.execution), can_spawn_followup: false, }, @@ -2459,7 +2444,6 @@ fn embedded_runtime_capabilities( can_get_worker: available, can_spawn_worker: available, can_stop_worker: available && execution_enabled, - can_accept_input: available && execution_enabled, has_workspace_fs: false, has_shell: false, has_git: false, @@ -2508,18 +2492,6 @@ fn embedded_spawn_execution_failure_diagnostic( )) } -fn runtime_worker_can_accept_input( - execution_enabled: bool, - status: EmbeddedWorkerStatus, - execution: &WorkerExecutionStatus, -) -> bool { - execution_enabled - && status == EmbeddedWorkerStatus::Running - && execution.backend == worker_runtime::execution::WorkerExecutionBackendKind::Connected - && execution.run_state == WorkerExecutionRunState::Idle - && !execution_last_result_blocks_control(execution) -} - fn runtime_worker_can_stop( execution_enabled: bool, status: EmbeddedWorkerStatus, @@ -2980,7 +2952,6 @@ fn remote_runtime_capabilities( can_get_worker: available, can_spawn_worker: available && worker_creation_available, can_stop_worker: available, - can_accept_input: available && worker_creation_available, has_workspace_fs: false, has_shell: false, has_git: false, @@ -3208,7 +3179,6 @@ pub fn placeholder_worker(host_id: impl Into) -> WorkerSummary { display_hint: "unsupported".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: false, can_stop: false, can_spawn_followup: false, }, @@ -3535,7 +3505,6 @@ mod tests { display_hint: "test fixture".to_string(), }, capabilities: WorkerCapabilitySummary { - can_accept_input: false, can_stop: false, can_spawn_followup: false, }, @@ -3565,7 +3534,6 @@ mod tests { can_get_worker: true, can_spawn_worker: false, can_stop_worker: false, - can_accept_input: false, has_workspace_fs: false, has_shell: false, has_git: false, @@ -3749,7 +3717,6 @@ mod tests { let spawned = runtime.spawn_worker(embedded_spawn_request()); assert_eq!(spawned.state, WorkerOperationState::Accepted); let worker = spawned.worker.expect("created embedded worker"); - assert!(worker.capabilities.can_accept_input); assert!(worker.capabilities.can_stop); let input = runtime.send_input( @@ -3768,7 +3735,6 @@ mod tests { .worker .expect("worker detail"); if detail.state == "idle" { - assert!(detail.capabilities.can_accept_input); break; } assert!( @@ -3801,7 +3767,6 @@ mod tests { ); assert_eq!(embedded_summary.source.status, RuntimeSourceStatus::Active); assert!(embedded_summary.capabilities.can_spawn_worker); - assert!(embedded_summary.capabilities.can_accept_input); let spawned = registry .spawn_worker( @@ -3837,8 +3802,6 @@ mod tests { assert_eq!(worker.workspace.identity, "runtime_registry_worker"); assert_eq!(worker.implementation.kind, "embedded_worker_runtime"); assert_eq!(worker.profile.as_deref(), Some("builtin:coder")); - assert!(worker.capabilities.can_accept_input); - let input = registry .send_input( EMBEDDED_RUNTIME_ID, @@ -4052,7 +4015,6 @@ mod tests { workers.items[0].workspace.identity, "runtime_registry_worker" ); - assert!(workers.items[0].capabilities.can_accept_input); assert!(workers.items[0].capabilities.can_stop); let input = registry @@ -4157,11 +4119,6 @@ mod tests { let workers = registry.list_workers(10); assert_eq!(workers.items.len(), 4); for worker in &workers.items { - assert!( - !worker.capabilities.can_accept_input, - "{} should not be input-capable", - worker.worker_id - ); assert!( !worker.capabilities.can_stop, "{} should not be stoppable", @@ -4174,7 +4131,6 @@ mod tests { assert_eq!(workers.items[3].state, "errored"); let stale_detail = registry.worker("remote:primary", "1").unwrap(); - assert!(!stale_detail.capabilities.can_accept_input); assert!(!stale_detail.capabilities.can_stop); assert_eq!(stale_detail.state, "stale"); diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 43d98d51..32361f06 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -4098,7 +4098,6 @@ fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary pinned: record.retention_state == "pinned", retention_state: record.retention_state.clone(), capabilities: WorkerCapabilitySummary { - can_accept_input: false, can_stop: false, can_spawn_followup: false, }, @@ -7142,7 +7141,6 @@ mod tests { .worker("embedded-worker-runtime", &worker_id) .expect("restored worker"); assert_eq!(restored_worker.state, "stale"); - assert!(!restored_worker.capabilities.can_accept_input); assert!(!restored_worker.capabilities.can_stop); assert!( restored_worker diff --git a/web/workspace/src/lib/workspace-sidebar/types.ts b/web/workspace/src/lib/workspace-sidebar/types.ts index 8240d29c..ec5a84e5 100644 --- a/web/workspace/src/lib/workspace-sidebar/types.ts +++ b/web/workspace/src/lib/workspace-sidebar/types.ts @@ -35,7 +35,6 @@ export type RuntimeCapabilities = { can_get_worker: boolean; can_spawn_worker: boolean; can_stop_worker: boolean; - can_accept_input: boolean; has_workspace_fs: boolean; has_shell: boolean; has_git: boolean; @@ -70,7 +69,6 @@ export type Host = { }; export type WorkerCapabilities = { - can_accept_input: boolean; can_stop: boolean; can_spawn_followup: boolean; }; diff --git a/web/workspace/src/lib/workspace-sidebar/workers.test.ts b/web/workspace/src/lib/workspace-sidebar/workers.test.ts index 3f12120e..97e48627 100644 --- a/web/workspace/src/lib/workspace-sidebar/workers.test.ts +++ b/web/workspace/src/lib/workspace-sidebar/workers.test.ts @@ -29,7 +29,6 @@ function worker(overrides: Partial): Worker { display_hint: "Runtime Worker", }, capabilities: { - can_accept_input: true, can_stop: true, can_spawn_followup: false, }, @@ -47,7 +46,6 @@ Deno.test("registry-only workers are not sidebar targets or console targets", () display_hint: "Missing Worker", }, capabilities: { - can_accept_input: false, can_stop: false, can_spawn_followup: false, }, diff --git a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte index 6397465e..e48a0b28 100644 --- a/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte @@ -10,7 +10,8 @@ ClientWorkerEventWsFrame, Diagnostic, Worker, - WorkerInputResult + WorkerInputResult, + PodProtocolEvent } from '$lib/workspace-sidebar/types'; type Props = { @@ -32,6 +33,7 @@ } let worker = $state(null); + let liveWorkerState = $state(null); let workerError = $state(null); let draft = $state(''); let sending = $state(false); @@ -59,7 +61,9 @@ ); const lines = $derived(projection.lines); const diagnostics = $derived(mergeDiagnostics(worker?.diagnostics ?? [], streamDiagnostics)); - const canSend = $derived(Boolean(worker?.capabilities.can_accept_input) && draft.trim().length > 0 && !sending); + const workerState = $derived(liveWorkerState ?? worker?.state ?? 'loading'); + const inputReady = $derived(workerState === 'idle'); + const canSend = $derived(inputReady && draft.trim().length > 0 && !sending); async function getJson(path: string): Promise { const response = await fetch(path); @@ -97,12 +101,15 @@ async function loadWorker(target: ConsoleTarget) { workerError = null; try { - worker = await getJson( + const payload = await getJson( workerApiPath(`/runtimes/${encodeURIComponent(target.runtimeId)}/workers/${encodeURIComponent(target.workerId)}`) ); + worker = payload; + liveWorkerState = payload.state; } catch (error) { workerError = error instanceof Error ? error.message : String(error); worker = null; + liveWorkerState = null; } } @@ -132,7 +139,7 @@ async function sendMessage(event: SubmitEvent) { event.preventDefault(); const content = draft.trim(); - if (!content || sending || !worker?.capabilities.can_accept_input) { + if (!content || sending || !inputReady) { return; } @@ -145,6 +152,7 @@ ); if (result.state === 'accepted') { draft = ''; + liveWorkerState = 'running'; } else { sendError = diagnosticsToText(result.diagnostics) || `Input was ${result.state}.`; } @@ -155,6 +163,18 @@ } } + function workerStateFromProtocolEvent(event: PodProtocolEvent): string | null { + switch (event.event) { + case 'snapshot': + case 'status': + return event.data.status; + case 'shutdown': + return 'shutdown'; + default: + return null; + } + } + function connectObservation(targetWorker: Worker | null, token: number, target: ConsoleTarget) { if (!targetWorker) { streamState = 'closed'; @@ -182,6 +202,10 @@ if (!rememberObservationEvent(frame.envelope.event_id)) { return; } + const observedState = workerStateFromProtocolEvent(frame.envelope.payload); + if (observedState) { + liveWorkerState = observedState; + } observedEvents = [ ...observedEvents, { @@ -304,6 +328,7 @@ $effect(() => { const target = consoleTarget; resetObservedEvents(); + liveWorkerState = null; streamDiagnostics = []; advanceReloadToken(); void loadConsoleData(target); @@ -324,7 +349,7 @@
- {worker?.state ?? 'loading'} · stream {streamState} + {workerState} · stream {streamState}