refactor: remove worker capability summary flags
This commit is contained in:
@@ -5,7 +5,6 @@ pub use server_api::{
|
||||
CreateWorkspaceWorkerRequest as BackendCreateWorkerRequest, Diagnostic as BackendDiagnostic,
|
||||
DiagnosticSeverity as BackendDiagnosticSeverity, ListResponse as BackendRuntimeListResponse,
|
||||
RuntimeSummary as BackendRuntimeSummary,
|
||||
WorkerCapabilitySummary as BackendWorkerCapabilitySummary,
|
||||
WorkerImplementationSummary as BackendWorkerImplementationSummary,
|
||||
WorkerLaunchOptionsResponse as BackendWorkerLaunchOptions,
|
||||
WorkerLaunchProfileCandidate as BackendWorkerLaunchProfileCandidate,
|
||||
@@ -869,7 +868,6 @@ mod tests {
|
||||
"pinned": false,
|
||||
"retention_state": "resident",
|
||||
"implementation": {"kind": "embedded", "display_hint": "Embedded"},
|
||||
"capabilities": {"can_stop": true, "can_spawn_followup": false},
|
||||
"diagnostics": []
|
||||
},
|
||||
"diagnostics": []
|
||||
@@ -952,10 +950,11 @@ mod tests {
|
||||
"label": "Coder",
|
||||
"workspace": {"visibility": "workspace", "identity": "workspace"},
|
||||
"state": "idle",
|
||||
"availability": "observed",
|
||||
"implementation": {"kind": "worker", "display_hint": "Coder"},
|
||||
"capabilities": {"can_stop": true, "can_spawn_followup": false},
|
||||
"workdir_attachments": [{
|
||||
"alias": "checkout",
|
||||
"effective_permissions": {"read": true, "write": true, "command": true},
|
||||
"working_directory": {
|
||||
"working_directory_id": "wd-1",
|
||||
"source": {"kind": "repository", "repository_key": "main"},
|
||||
|
||||
@@ -24,7 +24,7 @@ pub use backend_runtime::{
|
||||
BackendCreateWorkerRequest, BackendCreateWorkerResponse, BackendDiagnostic,
|
||||
BackendDiagnosticSeverity, BackendRuntimeClientError, BackendRuntimeListResponse,
|
||||
BackendRuntimeListTarget, BackendRuntimeSummary, BackendRuntimeTarget,
|
||||
BackendWorkerCapabilitySummary, BackendWorkerImplementationSummary, BackendWorkerLaunchOptions,
|
||||
BackendWorkerImplementationSummary, BackendWorkerLaunchOptions,
|
||||
BackendWorkerLaunchProfileCandidate, BackendWorkerLaunchRuntimeOption,
|
||||
BackendWorkerLaunchTarget, BackendWorkerOperationState, BackendWorkerRestoreResponse,
|
||||
BackendWorkerRestoreResult, BackendWorkerRestoreState, BackendWorkerSummary,
|
||||
|
||||
@@ -1029,7 +1029,7 @@ mod tests {
|
||||
),
|
||||
(
|
||||
"200 OK",
|
||||
r#"{"workspace_id":"workspace-a","runtime_id":"embedded","worker_id":"worker-1","console_href":"/w/workspace-a/workers/worker-1","worker":{"runtime_id":"embedded","worker_id":"worker-1","host_id":"embedded","display_name":"Intake","label":"worker-1","profile":"builtin:intake","singleton_key":null,"tags":[],"workspace":{"visibility":"workspace","identity":"workspace-a","workspace_id":"workspace-a"},"state":"idle","last_seen_at":null,"pinned":false,"retention_state":"active","implementation":{"kind":"runtime","display_hint":"Runtime Worker"},"capabilities":{"can_stop":true,"can_spawn_followup":false},"diagnostics":[]},"diagnostics":[]}"#,
|
||||
r#"{"workspace_id":"workspace-a","runtime_id":"embedded","worker_id":"worker-1","console_href":"/w/workspace-a/workers/worker-1","worker":{"runtime_id":"embedded","worker_id":"worker-1","host_id":"embedded","display_name":"Intake","label":"worker-1","profile":"builtin:intake","singleton_key":null,"tags":[],"workspace":{"visibility":"workspace","identity":"workspace-a","workspace_id":"workspace-a"},"state":"idle","last_seen_at":null,"pinned":false,"retention_state":"active","implementation":{"kind":"runtime","display_hint":"Runtime Worker"},"diagnostics":[]},"diagnostics":[]}"#,
|
||||
),
|
||||
]);
|
||||
let client = BackendWorkspaceProductClient::new_with_access_token(
|
||||
@@ -1057,7 +1057,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn workspace_orchestrator_launch_uses_scoped_backend_route() {
|
||||
let body = r#"{"workspace_id":"workspace-a","online":true,"disposition":"created","worker":{"runtime_id":"embedded","worker_id":"worker-2","host_id":"embedded","display_name":"Orchestrator","label":"worker-2","profile":"builtin:orchestrator","singleton_key":"workspace-orchestrator","tags":[],"workspace":{"visibility":"workspace","identity":"workspace-a","workspace_id":"workspace-a"},"state":"idle","last_seen_at":null,"pinned":true,"retention_state":"active","implementation":{"kind":"runtime","display_hint":"Runtime Worker"},"capabilities":{"can_stop":true,"can_spawn_followup":false},"diagnostics":[]},"diagnostics":[]}"#;
|
||||
let body = r#"{"workspace_id":"workspace-a","online":true,"disposition":"created","worker":{"runtime_id":"embedded","worker_id":"worker-2","host_id":"embedded","display_name":"Orchestrator","label":"worker-2","profile":"builtin:orchestrator","singleton_key":"workspace-orchestrator","tags":[],"workspace":{"visibility":"workspace","identity":"workspace-a","workspace_id":"workspace-a"},"state":"idle","last_seen_at":null,"pinned":true,"retention_state":"active","implementation":{"kind":"runtime","display_hint":"Runtime Worker"},"diagnostics":[]},"diagnostics":[]}"#;
|
||||
let (base_url, request, handle) = one_response_server("200 OK", body);
|
||||
let client = BackendWorkspaceProductClient::new_with_access_token(
|
||||
base_url,
|
||||
|
||||
@@ -6892,7 +6892,6 @@ pub struct RuntimeWorkerSummary {
|
||||
#[serde(default)]
|
||||
pub retention_state: String,
|
||||
pub implementation: WorkerImplementationSummary,
|
||||
pub capabilities: WorkerCapabilitySummary,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workdir_attachments: Vec<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
@@ -7888,13 +7887,6 @@ pub struct WorkerImplementationSummary {
|
||||
pub display_hint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||
pub struct WorkerCapabilitySummary {
|
||||
pub can_stop: bool,
|
||||
pub can_spawn_followup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
|
||||
@@ -8120,7 +8112,6 @@ pub struct WorkerSummary {
|
||||
#[serde(default)]
|
||||
pub retention_state: String,
|
||||
pub implementation: WorkerImplementationSummary,
|
||||
pub capabilities: WorkerCapabilitySummary,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workdir_attachments: Vec<WorkerWorkdirAttachmentSummary>,
|
||||
#[serde(default)]
|
||||
@@ -8163,7 +8154,6 @@ pub struct WorkerLaunchWorkerSummary {
|
||||
pub pinned: bool,
|
||||
pub retention_state: String,
|
||||
pub implementation: WorkerImplementationSummary,
|
||||
pub capabilities: WorkerCapabilitySummary,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workdir_attachments: Vec<RuntimeWorkerWorkdirAttachmentSummary>,
|
||||
#[serde(default)]
|
||||
@@ -9339,7 +9329,6 @@ pub fn worker_launch_api_typescript() -> String {
|
||||
WorkingDirectorySummary::decl(&config),
|
||||
WorkerWorkspaceSummary::decl(&config),
|
||||
WorkerImplementationSummary::decl(&config),
|
||||
WorkerCapabilitySummary::decl(&config),
|
||||
RuntimeWorkerWorkdirAttachmentSummary::decl(&config),
|
||||
WorkerLaunchWorkerSummary::decl(&config),
|
||||
WorkerLaunchRuntimeOption::decl(&config),
|
||||
@@ -10308,10 +10297,6 @@ mod tests {
|
||||
kind: "runtime".to_string(),
|
||||
display_hint: "Runtime Worker".to_string(),
|
||||
},
|
||||
capabilities: WorkerCapabilitySummary {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
@@ -10791,7 +10776,6 @@ mod tests {
|
||||
},
|
||||
"state": "idle",
|
||||
"implementation": {"kind": "worker", "display_hint": "Coder"},
|
||||
"capabilities": {"can_stop": true, "can_spawn_followup": false}
|
||||
});
|
||||
|
||||
assert!(serde_json::from_value::<WorkerSummary>(payload).is_err());
|
||||
|
||||
@@ -552,9 +552,8 @@ fn working_directory_text(worker: &BackendWorkerSummary) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use client::{
|
||||
BackendDiagnostic, BackendDiagnosticSeverity, BackendWorkerCapabilitySummary,
|
||||
BackendWorkerImplementationSummary, BackendWorkerRestoreResult,
|
||||
BackendWorkerWorkspaceSummary,
|
||||
BackendDiagnostic, BackendDiagnosticSeverity, BackendWorkerImplementationSummary,
|
||||
BackendWorkerRestoreResult, BackendWorkerWorkspaceSummary,
|
||||
};
|
||||
|
||||
fn worker(runtime_id: &str, worker_id: &str, profile: Option<&str>) -> BackendWorkerSummary {
|
||||
@@ -588,10 +587,6 @@ mod tests {
|
||||
kind: "embedded".to_string(),
|
||||
display_hint: "embedded".to_string(),
|
||||
},
|
||||
capabilities: BackendWorkerCapabilitySummary {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
|
||||
@@ -1298,7 +1298,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runtime_owned_workspace_client_has_no_fixed_request_timeout() {
|
||||
let (base_url, server) = delayed_workspace_response(Duration::from_millis(75));
|
||||
// This must exceed reqwest::blocking::Client's default 30-second timeout.
|
||||
// A short delay only checks our Option field, not the effective HTTP deadline.
|
||||
let (base_url, server) = delayed_workspace_response(Duration::from_secs(31));
|
||||
let client =
|
||||
RuntimeOwnedWorkspaceClient::new("workspace-a", base_url, "runtime-a", "worker-a");
|
||||
assert_eq!(client.request_timeout, None);
|
||||
|
||||
@@ -240,12 +240,6 @@ pub struct InternalWorkerImplementationSummary {
|
||||
pub display_hint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InternalWorkerCapabilitySummary {
|
||||
pub can_stop: bool,
|
||||
pub can_spawn_followup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InternalWorkerSummary {
|
||||
#[serde(flatten)]
|
||||
@@ -273,7 +267,6 @@ pub struct InternalWorkerSummary {
|
||||
#[serde(default)]
|
||||
pub retention_state: String,
|
||||
pub implementation: InternalWorkerImplementationSummary,
|
||||
pub capabilities: InternalWorkerCapabilitySummary,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub workdir_attachments: Vec<WorkingDirectoryAttachmentStatus>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
@@ -384,10 +377,6 @@ pub(crate) fn workspace_worker_summary(
|
||||
kind: summary.implementation.kind,
|
||||
display_hint: summary.implementation.display_hint,
|
||||
},
|
||||
capabilities: server_api::WorkerCapabilitySummary {
|
||||
can_stop: summary.capabilities.can_stop,
|
||||
can_spawn_followup: summary.capabilities.can_spawn_followup,
|
||||
},
|
||||
workdir_attachments,
|
||||
diagnostics: summary.diagnostics.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
@@ -2231,10 +2220,6 @@ impl EmbeddedWorkerRuntime {
|
||||
Some(EmbeddedWorkerRef::new(EmbeddedWorkerId::parse(worker_id)?))
|
||||
}
|
||||
|
||||
fn can_stop_embedded_worker(&self, status: EmbeddedWorkerStatus) -> bool {
|
||||
runtime_worker_can_stop(self.execution_enabled, status)
|
||||
}
|
||||
|
||||
fn map_worker_summary(
|
||||
&self,
|
||||
summary: worker_runtime::catalog::WorkerSummary,
|
||||
@@ -2274,11 +2259,6 @@ impl EmbeddedWorkerRuntime {
|
||||
kind: "embedded_worker_runtime".to_string(),
|
||||
display_hint: "backend-internal worker-runtime Worker".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: summary.execution_metadata_available
|
||||
&& self.can_stop_embedded_worker(summary.status),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: summary.workdir_attachments,
|
||||
diagnostics: embedded_worker_projection_diagnostics(
|
||||
summary.execution_metadata_available,
|
||||
@@ -2319,11 +2299,6 @@ impl EmbeddedWorkerRuntime {
|
||||
kind: "embedded_worker_runtime".to_string(),
|
||||
display_hint: "backend-internal worker-runtime Worker".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: detail.execution_metadata_available
|
||||
&& self.can_stop_embedded_worker(detail.status),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: detail.workdir_attachments,
|
||||
diagnostics: embedded_worker_projection_diagnostics(
|
||||
detail.execution_metadata_available,
|
||||
@@ -4127,11 +4102,6 @@ impl RemoteWorkerRuntime {
|
||||
kind: "remote_worker_runtime".to_string(),
|
||||
display_hint: "Backend-proxied remote worker-runtime Worker".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: summary.execution_metadata_available
|
||||
&& runtime_worker_can_stop(true, summary.status),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: summary.workdir_attachments,
|
||||
diagnostics: remote_worker_projection_diagnostics(summary.execution_metadata_available),
|
||||
}
|
||||
@@ -4170,11 +4140,6 @@ impl RemoteWorkerRuntime {
|
||||
kind: "remote_worker_runtime".to_string(),
|
||||
display_hint: "Backend-proxied remote worker-runtime Worker".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: detail.execution_metadata_available
|
||||
&& runtime_worker_can_stop(true, detail.status),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: detail.workdir_attachments,
|
||||
diagnostics: remote_worker_projection_diagnostics(detail.execution_metadata_available),
|
||||
}
|
||||
@@ -5152,10 +5117,6 @@ fn embedded_runtime_status_label(status: RuntimeStatus) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn runtime_worker_can_stop(execution_enabled: bool, status: EmbeddedWorkerStatus) -> bool {
|
||||
execution_enabled && status.is_active()
|
||||
}
|
||||
|
||||
fn embedded_worker_status_label(status: EmbeddedWorkerStatus) -> &'static str {
|
||||
match status {
|
||||
EmbeddedWorkerStatus::Idle => "idle",
|
||||
@@ -5963,10 +5924,6 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> InternalWorkerSummary {
|
||||
kind: "placeholder".to_string(),
|
||||
display_hint: "unsupported".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: vec![diagnostic(
|
||||
"runtime_capability_unsupported",
|
||||
@@ -6621,10 +6578,6 @@ mod tests {
|
||||
kind: "fixture".to_string(),
|
||||
display_hint: "test fixture".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}],
|
||||
@@ -7123,7 +7076,12 @@ mod tests {
|
||||
let spawned = runtime.spawn_worker(test_create_binding(), embedded_spawn_request());
|
||||
assert_eq!(spawned.state, InternalWorkerOperationState::Accepted);
|
||||
let worker = spawned.worker.expect("created embedded worker");
|
||||
assert!(worker.capabilities.can_stop);
|
||||
assert!(
|
||||
serde_json::to_value(&worker)
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let input = runtime.send_input(
|
||||
&worker.worker.worker_id,
|
||||
@@ -7496,7 +7454,12 @@ mod tests {
|
||||
workers.items[0].workspace.identity,
|
||||
"runtime_registry_worker"
|
||||
);
|
||||
assert!(workers.items[0].capabilities.can_stop);
|
||||
assert!(
|
||||
serde_json::to_value(&workers.items[0])
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let input = registry
|
||||
.send_input(
|
||||
@@ -7581,11 +7544,14 @@ mod tests {
|
||||
|
||||
let workers = registry.list_workers(10);
|
||||
assert_eq!(workers.items.len(), 5);
|
||||
assert!(!workers.items[0].capabilities.can_stop);
|
||||
assert!(workers.items[1].capabilities.can_stop);
|
||||
assert!(workers.items[2].capabilities.can_stop);
|
||||
assert!(workers.items[3].capabilities.can_stop);
|
||||
assert!(!workers.items[4].capabilities.can_stop);
|
||||
for worker in &workers.items {
|
||||
assert!(
|
||||
serde_json::to_value(worker)
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
assert_eq!(workers.items[0].state, "stopped");
|
||||
assert_eq!(workers.items[1].state, "running");
|
||||
assert_eq!(workers.items[2].state, "paused");
|
||||
@@ -7601,7 +7567,12 @@ mod tests {
|
||||
let stopped_detail = registry
|
||||
.worker(&RuntimeWorkerRef::new("remote:primary", &worker_id))
|
||||
.unwrap();
|
||||
assert!(!stopped_detail.capabilities.can_stop);
|
||||
assert!(
|
||||
serde_json::to_value(&stopped_detail)
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(stopped_detail.state, "stopped");
|
||||
|
||||
server.join().expect("mock remote server finished");
|
||||
|
||||
@@ -152,17 +152,16 @@ use crate::companion::{
|
||||
use crate::config_source::ConfigCommitRequest;
|
||||
use crate::hosts::{
|
||||
EMBEDDED_RUNTIME_ID, EmbeddedWorkerRuntime, HostDiagnosticSeverity, InternalHostSummary,
|
||||
InternalWorkerCapabilitySummary, InternalWorkerImplementationSummary,
|
||||
InternalWorkerOperationState, InternalWorkerRestoreResult, InternalWorkerSummary,
|
||||
InternalWorkerWorkspaceSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic,
|
||||
RuntimePingFailureKind, RuntimeRegistry, RuntimeRegistryError, RuntimeRegistryUnregisterResult,
|
||||
TicketWorkerRole, WorkerCompletionsRequest, WorkerCompletionsResult, WorkerControlOperation,
|
||||
WorkerCreateBinding, WorkerInputKind, WorkerInputRequest, WorkerInputResult,
|
||||
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerSpawnAcceptanceRequirement,
|
||||
WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest,
|
||||
WorkerTicketAssignmentRequest, WorkspaceRuntimeAuthorization,
|
||||
is_disallowed_remote_runtime_address, is_loopback_runtime_origin,
|
||||
worker_spawn_create_fingerprint, workspace_worker_summary,
|
||||
InternalWorkerImplementationSummary, InternalWorkerOperationState, InternalWorkerRestoreResult,
|
||||
InternalWorkerSummary, InternalWorkerWorkspaceSummary, RemoteRuntimeConfig,
|
||||
RemoteWorkerRuntime, RuntimeDiagnostic, RuntimePingFailureKind, RuntimeRegistry,
|
||||
RuntimeRegistryError, RuntimeRegistryUnregisterResult, TicketWorkerRole,
|
||||
WorkerCompletionsRequest, WorkerCompletionsResult, WorkerControlOperation, WorkerCreateBinding,
|
||||
WorkerInputKind, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest,
|
||||
WorkerLifecycleResult, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest,
|
||||
WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest, WorkerTicketAssignmentRequest,
|
||||
WorkspaceRuntimeAuthorization, is_disallowed_remote_runtime_address,
|
||||
is_loopback_runtime_origin, worker_spawn_create_fingerprint, workspace_worker_summary,
|
||||
};
|
||||
use crate::memory_backend::execute_memory_backend_operation_with_authority;
|
||||
use crate::memory_staging::{
|
||||
@@ -5175,10 +5174,6 @@ fn runtime_worker_summary_to_api(
|
||||
kind: worker.implementation.kind,
|
||||
display_hint: worker.implementation.display_hint,
|
||||
},
|
||||
capabilities: server_api::WorkerCapabilitySummary {
|
||||
can_stop: worker.capabilities.can_stop,
|
||||
can_spawn_followup: worker.capabilities.can_spawn_followup,
|
||||
},
|
||||
workdir_attachments: worker
|
||||
.workdir_attachments
|
||||
.into_iter()
|
||||
@@ -16956,10 +16951,6 @@ fn worker_launch_worker_summary(worker: InternalWorkerSummary) -> WorkerLaunchWo
|
||||
kind: worker.implementation.kind,
|
||||
display_hint: worker.implementation.display_hint,
|
||||
},
|
||||
capabilities: server_api::WorkerCapabilitySummary {
|
||||
can_stop: worker.capabilities.can_stop,
|
||||
can_spawn_followup: worker.capabilities.can_spawn_followup,
|
||||
},
|
||||
workdir_attachments: worker
|
||||
.workdir_attachments
|
||||
.into_iter()
|
||||
@@ -24806,10 +24797,6 @@ fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> InternalWorker
|
||||
last_seen_at: Some(record.updated_at.clone()),
|
||||
pinned: record.retention_state == "pinned",
|
||||
retention_state: record.retention_state.clone(),
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workspace: InternalWorkerWorkspaceSummary {
|
||||
visibility: "backend_registry".to_string(),
|
||||
identity: record.workspace_id.clone(),
|
||||
@@ -24855,8 +24842,6 @@ fn worker_summary_from_projection(
|
||||
.then(|| observation.worker.worker_state.clone())
|
||||
.flatten();
|
||||
summary.last_seen_at = Some(observation.observed_at.clone());
|
||||
summary.capabilities.can_stop = observed
|
||||
&& observation.worker.state != protocol::subscription::SubscriptionWorkerState::Stopped;
|
||||
summary.implementation.display_hint = if observed {
|
||||
"Runtime-backed Worker".to_string()
|
||||
} else {
|
||||
@@ -26709,7 +26694,12 @@ mod tests {
|
||||
assert_eq!(unavailable.availability, availability);
|
||||
assert_eq!(unavailable.state, "unavailable");
|
||||
assert_eq!(unavailable.worker_state, None);
|
||||
assert!(!unavailable.capabilities.can_stop);
|
||||
assert!(
|
||||
serde_json::to_value(&unavailable)
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let mut observed_stopped = projection;
|
||||
let observation = observed_stopped.observation.as_mut().unwrap();
|
||||
@@ -28645,10 +28635,6 @@ mod tests {
|
||||
kind: "fixture".to_string(),
|
||||
display_hint: "Workdirless fixture".to_string(),
|
||||
},
|
||||
capabilities: InternalWorkerCapabilitySummary {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
@@ -40983,10 +40969,6 @@ mod tests {
|
||||
kind: "remote".to_string(),
|
||||
display_hint: "remote".to_string(),
|
||||
},
|
||||
capabilities: server_api::WorkerCapabilitySummary {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
@@ -41081,10 +41063,6 @@ mod tests {
|
||||
kind: "remote".to_string(),
|
||||
display_hint: "remote".to_string(),
|
||||
},
|
||||
capabilities: server_api::WorkerCapabilitySummary {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: Vec::new(),
|
||||
diagnostics: Vec::new(),
|
||||
}],
|
||||
@@ -43445,7 +43423,12 @@ mod tests {
|
||||
// catalog remains controllable while its failed execution is represented in
|
||||
// `worker_state` rather than by removing the catalog Worker.
|
||||
assert_eq!(restored_worker.state, "idle");
|
||||
assert!(restored_worker.capabilities.can_stop);
|
||||
assert!(
|
||||
serde_json::to_value(&restored_worker)
|
||||
.unwrap()
|
||||
.get("capabilities")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let bundles = restored
|
||||
.runtime
|
||||
|
||||
+4
-35
@@ -7448,9 +7448,6 @@
|
||||
"$ref": "#/components/schemas/SubscriptionWorkerAvailability",
|
||||
"description": "Freshness of the Runtime observation used to construct `worker_state`.\n`Unavailable` means no current controller snapshot is being asserted."
|
||||
},
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/WorkerCapabilitySummary"
|
||||
},
|
||||
"diagnostics": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -7550,7 +7547,6 @@
|
||||
"availability",
|
||||
"state",
|
||||
"implementation",
|
||||
"capabilities",
|
||||
"updated_at"
|
||||
],
|
||||
"type": "object"
|
||||
@@ -7791,9 +7787,6 @@
|
||||
"RuntimeWorkerSummary": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/WorkerCapabilitySummary"
|
||||
},
|
||||
"diagnostics": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -7872,8 +7865,7 @@
|
||||
"label",
|
||||
"workspace",
|
||||
"state",
|
||||
"implementation",
|
||||
"capabilities"
|
||||
"implementation"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -11254,21 +11246,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"WorkerCapabilitySummary": {
|
||||
"properties": {
|
||||
"can_spawn_followup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"can_stop": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"can_stop",
|
||||
"can_spawn_followup"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"WorkerFileDeleteResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -11447,9 +11424,6 @@
|
||||
"additionalProperties": false,
|
||||
"description": "Runtime-owned Worker summary embedded in Worker launch responses.\n\nThis preserves the existing launch wire shape. Workspace-owned Worker list\nand detail responses use [`WorkerSummary`] instead.",
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/WorkerCapabilitySummary"
|
||||
},
|
||||
"diagnostics": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -11529,8 +11503,7 @@
|
||||
"state",
|
||||
"pinned",
|
||||
"retention_state",
|
||||
"implementation",
|
||||
"capabilities"
|
||||
"implementation"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -11830,9 +11803,6 @@
|
||||
"$ref": "#/components/schemas/SubscriptionWorkerAvailability",
|
||||
"description": "Freshness of the Runtime observation used to construct `worker_state`.\n`Unavailable` means no current controller snapshot is being asserted."
|
||||
},
|
||||
"capabilities": {
|
||||
"$ref": "#/components/schemas/WorkerCapabilitySummary"
|
||||
},
|
||||
"diagnostics": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -11928,8 +11898,7 @@
|
||||
"workspace",
|
||||
"availability",
|
||||
"state",
|
||||
"implementation",
|
||||
"capabilities"
|
||||
"implementation"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -14020,7 +13989,7 @@
|
||||
"info": {
|
||||
"title": "Yoi Server API",
|
||||
"version": "0.1.0",
|
||||
"x-yoi-source-digest": "sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0"
|
||||
"x-yoi-source-digest": "sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
|
||||
@@ -31,7 +31,6 @@ const worker = {
|
||||
profile: "default",
|
||||
tags: [],
|
||||
diagnostics: [],
|
||||
capabilities: { can_stop: false, can_spawn_followup: false },
|
||||
workspace: {
|
||||
workspace_id: workspaceId,
|
||||
visibility: "workspace",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0)
|
||||
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
|
||||
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/companion-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0)
|
||||
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
|
||||
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/repository-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
|
||||
@@ -61,8 +61,6 @@ export type SubscriptionWorkerAvailability = "observed" | "unavailable";
|
||||
|
||||
export type WorkerBusyState = { kind: "run"; state: WorkerRunState; } | { kind: "maintenance"; state: WorkerMaintenanceState; };
|
||||
|
||||
export type WorkerCapabilitySummary = { can_spawn_followup: boolean; can_stop: boolean; };
|
||||
|
||||
export type WorkerImplementationSummary = { display_hint: string; kind: string; };
|
||||
|
||||
export type WorkerMaintenanceState = "compacting";
|
||||
@@ -73,7 +71,7 @@ export type WorkerState = { kind: "idle"; } | { kind: "busy"; state: WorkerBusyS
|
||||
|
||||
export type WorkerStateSnapshot = { last_command_id: number; state: WorkerState; };
|
||||
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type WorkerWorkdirAttachmentSummary = { alias: string; effective_permissions: ExternalWorkdirPermissions; working_directory: WorkingDirectorySummary; };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0)
|
||||
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
|
||||
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/runtime-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
|
||||
@@ -107,7 +107,7 @@ export type RuntimeWorkerLifecycleResult = { diagnostics?: Array<Diagnostic>; ru
|
||||
|
||||
export type RuntimeWorkerListResponse = { diagnostics?: Array<Diagnostic>; items: Array<WorkerSummary>; limit: number; source: string; workspace_id: string; };
|
||||
|
||||
export type RuntimeWorkerShowResponse = { availability: SubscriptionWorkerAvailability; capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; updated_at: string; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
export type RuntimeWorkerShowResponse = { availability: SubscriptionWorkerAvailability; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; updated_at: string; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type RuntimeWorkerSpawnAcceptanceEvidence = { detail: string; kind: string; };
|
||||
|
||||
@@ -123,7 +123,7 @@ export type RuntimeWorkerSpawnWorkingDirectoryAttachmentRequest = { alias: strin
|
||||
|
||||
export type RuntimeWorkerSpawnWorkingDirectoryRequest = { repository_key: string; selector?: string | null; };
|
||||
|
||||
export type RuntimeWorkerSummary = { capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<unknown>; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; };
|
||||
export type RuntimeWorkerSummary = { diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<unknown>; worker_id: string; worker_state?: unknown; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type RuntimeWorkerTicketAssignmentRequest = { operation_id: string; ticket_id: string; };
|
||||
|
||||
@@ -137,8 +137,6 @@ export type UploadedFileRef = { artifact_id: string; availability: UploadedFileA
|
||||
|
||||
export type WorkerBusyState = { kind: "run"; state: WorkerRunState; } | { kind: "maintenance"; state: WorkerMaintenanceState; };
|
||||
|
||||
export type WorkerCapabilitySummary = { can_spawn_followup: boolean; can_stop: boolean; };
|
||||
|
||||
export type WorkerFileDeleteResponse = { deleted: boolean; };
|
||||
|
||||
export type WorkerFileUploadResponse = { file: UploadedFileRef; };
|
||||
@@ -163,7 +161,7 @@ export type WorkerState = { kind: "idle"; } | { kind: "busy"; state: WorkerBusyS
|
||||
|
||||
export type WorkerStateSnapshot = { last_command_id: number; state: WorkerState; };
|
||||
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type WorkerWorkdirAttachmentSummary = { alias: string; effective_permissions: ExternalWorkdirPermissions; working_directory: WorkingDirectorySummary; };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0)
|
||||
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
|
||||
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/ticket-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
|
||||
// Input: openapi/server-api.json
|
||||
// Input contract: 0.1.0 (sha256:49856606e4a16d138cc04a62a6096fd40fff213587b1cbf7f0594f28994a45b0)
|
||||
// Input contract: 0.1.0 (sha256:ae559492220ec118f04c2c0c9593e8b3cb44c3cb857be3ae85715ace26da8043)
|
||||
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
|
||||
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
|
||||
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
|
||||
@@ -45,8 +45,6 @@ export type UploadedFileRef = { artifact_id: string; availability: UploadedFileA
|
||||
|
||||
export type WorkerBusyState = { kind: "run"; state: WorkerRunState; } | { kind: "maintenance"; state: WorkerMaintenanceState; };
|
||||
|
||||
export type WorkerCapabilitySummary = { can_spawn_followup: boolean; can_stop: boolean; };
|
||||
|
||||
export type WorkerImplementationSummary = { display_hint: string; kind: string; };
|
||||
|
||||
export type WorkerLaunchOptionsResponse = { default_profile?: string | null; diagnostics: Array<Diagnostic>; profiles: Array<WorkerLaunchProfileCandidate>; repositories: Array<WorkingDirectoryRepositoryOption>; runtimes: Array<WorkerLaunchRuntimeOption>; working_directories: Array<WorkingDirectorySummary>; workspace_id: string; };
|
||||
@@ -55,7 +53,7 @@ export type WorkerLaunchProfileCandidate = { description: string; id: string; la
|
||||
|
||||
export type WorkerLaunchRuntimeOption = { built_in: boolean; diagnostics: Array<Diagnostic>; display_name: string; runtime_id: string; status: string; worker_creation_available: boolean; working_directory_required: boolean; };
|
||||
|
||||
export type WorkerLaunchWorkerSummary = { capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned: boolean; profile?: string | null; retention_state: string; runtime_id: string; singleton_key?: string | null; state: string; tags: Array<string>; workdir_attachments?: Array<RuntimeWorkerWorkdirAttachmentSummary>; worker_id: string; workspace: WorkerWorkspaceSummary; };
|
||||
export type WorkerLaunchWorkerSummary = { diagnostics?: Array<Diagnostic>; display_name: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned: boolean; profile?: string | null; retention_state: string; runtime_id: string; singleton_key?: string | null; state: string; tags: Array<string>; workdir_attachments?: Array<RuntimeWorkerWorkdirAttachmentSummary>; worker_id: string; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type WorkerListResponse = { diagnostics?: Array<Diagnostic>; items: Array<WorkerSummary>; limit: number; source: string; workspace_id: string; };
|
||||
|
||||
@@ -67,7 +65,7 @@ export type WorkerState = { kind: "idle"; } | { kind: "busy"; state: WorkerBusyS
|
||||
|
||||
export type WorkerStateSnapshot = { last_command_id: number; state: WorkerState; };
|
||||
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; capabilities: WorkerCapabilitySummary; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
export type WorkerSummary = { availability: SubscriptionWorkerAvailability; diagnostics?: Array<Diagnostic>; display_name?: string; host_id: string; implementation: WorkerImplementationSummary; label: string; last_seen_at?: string | null; pinned?: boolean; profile?: string | null; resource_key: string; retention_state?: string; runtime_id: string; singleton_key?: string | null; state: string; tags?: Array<string>; workdir_attachments?: Array<WorkerWorkdirAttachmentSummary>; worker_id: string; worker_state?: WorkerStateSnapshot | null; workspace: WorkerWorkspaceSummary; };
|
||||
|
||||
export type WorkerWorkdirAttachmentSummary = { alias: string; effective_permissions: ExternalWorkdirPermissions; working_directory: WorkingDirectorySummary; };
|
||||
|
||||
|
||||
@@ -56,13 +56,28 @@ const worker = {
|
||||
kind: "runtime",
|
||||
display_hint: "Runtime Worker",
|
||||
},
|
||||
capabilities: {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
diagnostics: [],
|
||||
};
|
||||
|
||||
Deno.test("Worker summaries omit the removed management capabilities field", () => {
|
||||
const summary = { ...worker, resource_key: "W-1", availability: "observed" };
|
||||
assertEquals("capabilities" in parseWorkerSummary(summary), false);
|
||||
assertThrows(
|
||||
() => parseWorkerSummary({ ...summary, capabilities: {} }),
|
||||
Error,
|
||||
"unknown field capabilities",
|
||||
);
|
||||
const created = parseBrowserCreateWorkerResponse({
|
||||
workspace_id: "workspace-a",
|
||||
runtime_id: "runtime-a",
|
||||
worker_id: "worker-a",
|
||||
console_href: "/w/workspace-a/workers/W-1/console",
|
||||
worker,
|
||||
diagnostics: [],
|
||||
});
|
||||
assertEquals("capabilities" in created.worker, false);
|
||||
});
|
||||
|
||||
Deno.test("Worker summary parser enforces observation freshness", () => {
|
||||
const unavailable = {
|
||||
...worker,
|
||||
|
||||
@@ -10,7 +10,6 @@ import type {
|
||||
RuntimeWorkerWorkdirAttachmentSummary,
|
||||
RuntimeWorkingDirectoryCleanupTarget,
|
||||
RuntimeWorkingDirectorySummary,
|
||||
WorkerCapabilitySummary,
|
||||
WorkerImplementationSummary,
|
||||
WorkerLaunchOptionsResponse,
|
||||
WorkerLaunchProfileCandidate,
|
||||
@@ -251,21 +250,6 @@ function implementationSummary(
|
||||
};
|
||||
}
|
||||
|
||||
function capabilitySummary(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): WorkerCapabilitySummary {
|
||||
const item = record(value, label);
|
||||
exact(item, ["can_stop", "can_spawn_followup"], label);
|
||||
return {
|
||||
can_stop: boolean(item.can_stop, `${label}.can_stop`),
|
||||
can_spawn_followup: boolean(
|
||||
item.can_spawn_followup,
|
||||
`${label}.can_spawn_followup`,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function workerStateSnapshot(
|
||||
value: unknown,
|
||||
label: string,
|
||||
@@ -374,7 +358,6 @@ export function parseWorkerSummary(
|
||||
"pinned",
|
||||
"retention_state",
|
||||
"implementation",
|
||||
"capabilities",
|
||||
"workdir_attachments",
|
||||
"diagnostics",
|
||||
],
|
||||
@@ -423,7 +406,6 @@ export function parseWorkerSummary(
|
||||
item.implementation,
|
||||
`${label}.implementation`,
|
||||
),
|
||||
capabilities: capabilitySummary(item.capabilities, `${label}.capabilities`),
|
||||
workdir_attachments: item.workdir_attachments === undefined
|
||||
? undefined
|
||||
: array(
|
||||
@@ -617,7 +599,6 @@ function workerSummary(
|
||||
"pinned",
|
||||
"retention_state",
|
||||
"implementation",
|
||||
"capabilities",
|
||||
"workdir_attachments",
|
||||
"diagnostics",
|
||||
],
|
||||
@@ -641,7 +622,6 @@ function workerSummary(
|
||||
item.implementation,
|
||||
`${label}.implementation`,
|
||||
),
|
||||
capabilities: capabilitySummary(item.capabilities, `${label}.capabilities`),
|
||||
workdir_attachments: item.workdir_attachments === undefined
|
||||
? undefined
|
||||
: array(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { workerConsoleHref } from '$lib/workspace/resource-links';
|
||||
import { pushWorkspaceAlert } from '$lib/workspace/alerts/store';
|
||||
import {
|
||||
canStopSidebarWorker,
|
||||
canDeleteSidebarWorker,
|
||||
deleteSidebarWorker,
|
||||
stopSidebarWorker,
|
||||
@@ -91,14 +92,14 @@
|
||||
}
|
||||
|
||||
async function stopWorker(worker: SidebarWorker) {
|
||||
if (busyAction || !worker.capabilities.can_stop) return;
|
||||
if (busyAction || !canStopSidebarWorker(worker)) return;
|
||||
closeWorkerMenu();
|
||||
busyAction = { workerKey: workerKey(worker), kind: 'stop' };
|
||||
try {
|
||||
await stopSidebarWorker(workspaceId, worker);
|
||||
workers = workers.map((item) =>
|
||||
workerKey(item) === workerKey(worker)
|
||||
? { ...item, state: 'stopped', capabilities: { ...item.capabilities, can_stop: false } }
|
||||
? { ...item, state: 'stopped', lifecycleState: 'stopped' }
|
||||
: item
|
||||
);
|
||||
pushWorkspaceAlert('info', `${worker.display_name || worker.label} stopped`, {
|
||||
@@ -226,7 +227,7 @@
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
disabled={busyAction !== null || !worker.capabilities.can_stop}
|
||||
disabled={busyAction !== null || !canStopSidebarWorker(worker)}
|
||||
onclick={() => stopWorker(worker)}
|
||||
>
|
||||
{isBusy(worker, 'stop') ? 'Stopping…' : 'Stop'}
|
||||
|
||||
@@ -10,7 +10,6 @@ import type {
|
||||
BrowserWorkerWorkingDirectorySelection
|
||||
as SharedBrowserWorkerWorkingDirectorySelection,
|
||||
Diagnostic as SharedDiagnostic,
|
||||
WorkerCapabilitySummary as SharedWorkerCapabilitySummary,
|
||||
WorkerLaunchOptionsResponse as SharedWorkerLaunchOptionsResponse,
|
||||
WorkerLaunchProfileCandidate as SharedWorkerLaunchProfileCandidate,
|
||||
WorkerLaunchRuntimeOption as SharedWorkerLaunchRuntimeOption,
|
||||
@@ -78,8 +77,6 @@ export type Runtime = {
|
||||
};
|
||||
};
|
||||
|
||||
export type WorkerCapabilities = SharedWorkerCapabilitySummary;
|
||||
|
||||
export type WorkerWorkdirAttachment = SharedWorkerWorkdirAttachmentSummary;
|
||||
|
||||
export type Worker =
|
||||
|
||||
@@ -4,10 +4,7 @@ import {
|
||||
parseRuntimeCleanupPlan,
|
||||
parseRuntimeWorkerLifecycleResult,
|
||||
} from "$lib/workspace/api/runtime-workers";
|
||||
import type {
|
||||
Diagnostic,
|
||||
Worker,
|
||||
} from "./types";
|
||||
import type { Diagnostic, Worker } from "./types";
|
||||
|
||||
type FetchFn = typeof fetch;
|
||||
type WorkerActionTarget = Pick<Worker, "runtime_id" | "worker_id" | "state">;
|
||||
@@ -25,13 +22,17 @@ async function responseError(response: Response): Promise<string> {
|
||||
const fallback = `${response.status} ${response.statusText}`.trim();
|
||||
try {
|
||||
const payload: unknown = await response.json();
|
||||
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
||||
if (
|
||||
typeof payload !== "object" || payload === null || Array.isArray(payload)
|
||||
) {
|
||||
return fallback;
|
||||
}
|
||||
const record = payload as Record<string, unknown>;
|
||||
if (typeof record.message === "string") return record.message.slice(0, 512);
|
||||
const nested = record.error;
|
||||
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) {
|
||||
if (
|
||||
typeof nested === "object" && nested !== null && !Array.isArray(nested)
|
||||
) {
|
||||
const message = (nested as Record<string, unknown>).message;
|
||||
if (typeof message === "string") return message.slice(0, 512);
|
||||
}
|
||||
@@ -51,6 +52,14 @@ function diagnosticMessage(
|
||||
fallback;
|
||||
}
|
||||
|
||||
export function canStopSidebarWorker(worker: {
|
||||
availability: Worker["availability"];
|
||||
lifecycleState: string;
|
||||
}): boolean {
|
||||
return worker.availability === "observed" &&
|
||||
["idle", "running", "paused"].includes(worker.lifecycleState);
|
||||
}
|
||||
|
||||
export function canDeleteSidebarWorker(worker: WorkerActionTarget): boolean {
|
||||
return worker.state === "stopped";
|
||||
}
|
||||
@@ -112,7 +121,9 @@ export async function deleteSidebarWorker(
|
||||
throw new Error(await responseError(executionResponse));
|
||||
}
|
||||
|
||||
const execution = parseRuntimeCleanupExecution(await executionResponse.json());
|
||||
const execution = parseRuntimeCleanupExecution(
|
||||
await executionResponse.json(),
|
||||
);
|
||||
const outcome = execution.results.find((result) =>
|
||||
result.target_id === candidate.target_id
|
||||
);
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "@testing-library/svelte";
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
import type {
|
||||
SubscriptionFrame,
|
||||
SubscriptionWorker,
|
||||
} from "$lib/generated/protocol";
|
||||
import WorkersNavSection from "./WorkersNavSection.svelte";
|
||||
import { disposeWorkspaceWorkersStore } from "./worker-subscription";
|
||||
|
||||
const transport = vi.hoisted(() => ({
|
||||
listener: null as { onFrame(frame: SubscriptionFrame): void } | null,
|
||||
close: vi.fn(),
|
||||
}));
|
||||
vi.mock("$lib/workspace/multiplexer", () => ({
|
||||
workspaceMultiplexer: () => ({
|
||||
subscribe: (
|
||||
_selector: unknown,
|
||||
listener: NonNullable<typeof transport.listener>,
|
||||
) => {
|
||||
transport.listener = listener;
|
||||
return { close: transport.close };
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
disposeWorkspaceWorkersStore("stop-test");
|
||||
transport.listener = null;
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function snapshot(
|
||||
state: SubscriptionWorker["state"],
|
||||
availability: SubscriptionWorker["availability"],
|
||||
): SubscriptionFrame {
|
||||
return {
|
||||
protocol_version: 1,
|
||||
frame: "response",
|
||||
message: {
|
||||
result: "subscribed",
|
||||
payload: {
|
||||
request_id: "request-1",
|
||||
subscription_id: "subscription-1",
|
||||
selector: { topic: "workspace_workers" },
|
||||
snapshot_revision: 1,
|
||||
snapshot: {
|
||||
topic: "workers",
|
||||
data: {
|
||||
workers: [{
|
||||
worker_id: "worker-a",
|
||||
runtime_id: "runtime-a",
|
||||
resource_key: "W-1",
|
||||
availability,
|
||||
subject_revision: 1,
|
||||
state,
|
||||
// An absent foreground snapshot must not prevent stopping an active catalog Worker.
|
||||
worker_state: null,
|
||||
has_running_internal_workers: false,
|
||||
display_name: "Test worker",
|
||||
workspace_id: "stop-test",
|
||||
workdir_attachments: [],
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test.each(["idle", "running", "paused", "stopped"] as const)(
|
||||
"Stop uses observed catalog state without capabilities (%s)",
|
||||
async (state) => {
|
||||
const request = vi.fn(async (input: RequestInfo | URL) =>
|
||||
String(input).endsWith("/stop")
|
||||
? Response.json({
|
||||
state: "accepted",
|
||||
runtime_id: "runtime-a",
|
||||
worker_id: "worker-a",
|
||||
diagnostics: [],
|
||||
})
|
||||
: new Response(null, { status: 404 })
|
||||
);
|
||||
vi.stubGlobal("fetch", request);
|
||||
render(WorkersNavSection, { workspaceId: "stop-test" });
|
||||
await waitFor(() => expect(transport.listener).not.toBeNull());
|
||||
transport.listener!.onFrame(snapshot(state, "observed"));
|
||||
await fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Actions for Test worker" }),
|
||||
);
|
||||
const stop = screen.getByRole("menuitem", {
|
||||
name: "Stop",
|
||||
}) as HTMLButtonElement;
|
||||
expect(stop.disabled).toBe(state === "stopped");
|
||||
if (state === "stopped") return;
|
||||
await fireEvent.click(stop);
|
||||
await waitFor(() =>
|
||||
expect(request.mock.calls.some(([url]) => String(url).endsWith("/stop")))
|
||||
.toBe(true)
|
||||
);
|
||||
await fireEvent.click(
|
||||
screen.getByRole("button", { name: "Actions for Test worker" }),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
(screen.getByRole("menuitem", { name: "Stop" }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("Unavailable observation disables Stop even when catalog state is running", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response(null, { status: 404 })),
|
||||
);
|
||||
render(WorkersNavSection, { workspaceId: "stop-test" });
|
||||
await waitFor(() => expect(transport.listener).not.toBeNull());
|
||||
transport.listener!.onFrame(snapshot("running", "unavailable"));
|
||||
await fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Actions for Test worker" }),
|
||||
);
|
||||
expect(
|
||||
(screen.getByRole("menuitem", { name: "Stop" }) as HTMLButtonElement)
|
||||
.disabled,
|
||||
).toBe(true);
|
||||
});
|
||||
@@ -16,6 +16,8 @@ import type { Worker } from "./types";
|
||||
export type SidebarWorker = Omit<Worker, "workdir_attachments"> & {
|
||||
workdir_attachments: SidebarWorkdirAttachment[];
|
||||
has_running_internal_workers: boolean;
|
||||
/** Catalog lifecycle, independent of the possibly unknown foreground display state. */
|
||||
lifecycleState: SubscriptionWorker["state"];
|
||||
};
|
||||
|
||||
export type WorkspaceWorkersState = {
|
||||
@@ -148,6 +150,7 @@ function projectWorker(
|
||||
},
|
||||
availability: worker.availability,
|
||||
state: liveWorkerState(worker),
|
||||
lifecycleState: worker.state,
|
||||
worker_state: worker.worker_state,
|
||||
pinned: false,
|
||||
retention_state: "transient",
|
||||
@@ -155,11 +158,6 @@ function projectWorker(
|
||||
kind: "runtime_subscription_worker",
|
||||
display_hint: "Workspace-authorized Runtime Worker",
|
||||
},
|
||||
capabilities: {
|
||||
can_stop: worker.availability !== "unavailable" &&
|
||||
worker.state !== "stopped",
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: (worker.workdir_attachments ?? []).map((
|
||||
attachment,
|
||||
) => ({
|
||||
|
||||
@@ -38,10 +38,6 @@ function worker(overrides: Partial<Worker>): Worker {
|
||||
kind: "runtime_worker",
|
||||
display_hint: "Runtime Worker",
|
||||
},
|
||||
capabilities: {
|
||||
can_stop: true,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
workdir_attachments: [],
|
||||
diagnostics: [],
|
||||
...overrides,
|
||||
@@ -56,10 +52,6 @@ Deno.test("registry-only workers are not sidebar targets or console targets", ()
|
||||
kind: "backend_worker_registry",
|
||||
display_hint: "Missing Worker",
|
||||
},
|
||||
capabilities: {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
});
|
||||
assertEquals(canShowWorkerInSidebar(registryOnly), false);
|
||||
assertEquals(canOpenWorkerConsole(registryOnly), false);
|
||||
|
||||
@@ -1935,22 +1935,6 @@
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<details class="metadata-details">
|
||||
<summary>Capabilities</summary>
|
||||
<ul>
|
||||
<li>
|
||||
stop: {worker.capabilities.can_stop
|
||||
? "available"
|
||||
: "unsupported"}
|
||||
</li>
|
||||
<li>
|
||||
follow-up spawn: {worker.capabilities
|
||||
.can_spawn_followup
|
||||
? "available"
|
||||
: "unsupported"}
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
{:else if !workerError}
|
||||
<p>Loading Worker detail…</p>
|
||||
{/if}
|
||||
|
||||
-4
@@ -62,10 +62,6 @@ function worker(workerId = "worker-a", runtimeId = "runtime-a"): Worker {
|
||||
profile: "default",
|
||||
tags: [],
|
||||
diagnostics: [],
|
||||
capabilities: {
|
||||
can_stop: true,
|
||||
can_spawn_followup: true,
|
||||
},
|
||||
workspace: { visibility: "workspace", identity: "workspace-a" },
|
||||
implementation: { kind: "builtin", display_hint: "test" },
|
||||
workdir_attachments: [],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
canDeleteSidebarWorker,
|
||||
canStopSidebarWorker,
|
||||
deleteSidebarWorker,
|
||||
stopSidebarWorker,
|
||||
} from "../../src/lib/workspace/sidebar/worker-actions.ts";
|
||||
@@ -41,7 +42,6 @@ const worker = {
|
||||
runtime_id: "runtime /",
|
||||
worker_id: "worker /",
|
||||
state: "running",
|
||||
capabilities: { can_stop: true },
|
||||
} as Worker;
|
||||
|
||||
function jsonResponse(payload: unknown, status = 200): Response {
|
||||
@@ -51,6 +51,33 @@ function jsonResponse(payload: unknown, status = 200): Response {
|
||||
});
|
||||
}
|
||||
|
||||
Deno.test("sidebar Stop follows observed catalog lifecycle, not foreground display state", () => {
|
||||
for (const lifecycleState of ["idle", "running", "paused"]) {
|
||||
const target = {
|
||||
availability: "observed" as const,
|
||||
lifecycleState,
|
||||
state: "unknown",
|
||||
};
|
||||
assert(canStopSidebarWorker(target));
|
||||
assert(!canStopSidebarWorker({ ...target, availability: "unavailable" }));
|
||||
}
|
||||
for (
|
||||
const lifecycleState of [
|
||||
"stopped",
|
||||
"missing",
|
||||
"execution_unavailable",
|
||||
"unknown",
|
||||
]
|
||||
) {
|
||||
const target = {
|
||||
availability: "observed" as const,
|
||||
lifecycleState,
|
||||
state: "running",
|
||||
};
|
||||
assert(!canStopSidebarWorker(target));
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("sidebar Stop uses the workspace-scoped Worker lifecycle endpoint", async () => {
|
||||
const requests: Array<{ url: string; init?: RequestInit }> = [];
|
||||
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
|
||||
Reference in New Issue
Block a user