feat: propagate workspace prompt revisions

This commit is contained in:
2026-08-19 06:01:03 +09:00
parent d24d50cac9
commit 17d6789b41
7 changed files with 351 additions and 7 deletions
+102
View File
@@ -45,6 +45,7 @@ use worker_runtime::http_server::{
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse,
RuntimeHttpWorkerWorkspaceApiRequest, RuntimeHttpWorkersResponse,
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
RuntimeHttpWorkspacePromptProjectionRequest, RuntimeHttpWorkspacePromptProjectionResponse,
};
use worker_runtime::identity::{
RuntimeWorkerRef, WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef,
@@ -778,6 +779,13 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
}
}
fn observe_workspace_prompt_projection(
&self,
_projection: worker::WorkspacePromptProjection,
) -> Result<(), String> {
Ok(())
}
fn sync_config_bundle(&self, _bundle: ConfigBundle) -> ConfigBundleSyncResult {
ConfigBundleSyncResult {
state: WorkerOperationState::Unsupported,
@@ -1185,6 +1193,36 @@ impl RuntimeRegistry {
Ok(runtime.replace_worker_workspace_api(worker_id, workspace_api))
}
pub fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,
) -> Vec<RuntimeDiagnostic> {
let runtimes = self
.runtimes
.read()
.map(|runtimes| runtimes.clone())
.unwrap_or_default();
runtimes
.into_iter()
.filter_map(|runtime| {
runtime
.observe_workspace_prompt_projection(projection.clone())
.err()
.map(|message| {
diagnostic(
"workspace_prompt_projection_notification_failed",
DiagnosticSeverity::Warning,
format!(
"runtime '{}' rejected Workspace Prompt projection revision {}: {message}",
runtime.runtime_id(), projection.config_revision
),
)
})
})
.take(MAX_DIAGNOSTICS)
.collect()
}
pub fn spawn_worker(
&self,
runtime_id: &str,
@@ -2040,6 +2078,15 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
}
}
fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,
) -> Result<(), String> {
self.runtime
.observe_workspace_prompt_projection(projection)
.map_err(|error| error.to_string())
}
fn sync_config_bundle(&self, bundle: ConfigBundle) -> ConfigBundleSyncResult {
match self.runtime.store_config_bundle(bundle) {
Ok(availability) => ConfigBundleSyncResult {
@@ -3155,6 +3202,18 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
}
}
fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,
) -> Result<(), String> {
self.post_json::<_, RuntimeHttpWorkspacePromptProjectionResponse>(
"/v1/workspace-prompt-projections",
&RuntimeHttpWorkspacePromptProjectionRequest { projection },
)
.map(|_| ())
.map_err(|error| error.message)
}
fn sync_config_bundle(&self, bundle: ConfigBundle) -> ConfigBundleSyncResult {
let request = RuntimeHttpConfigBundleSyncRequest { bundle };
match self.post_json::<_, RuntimeHttpConfigBundleAvailabilityResponse>(
@@ -4507,6 +4566,7 @@ mod tests {
runtime_id: String,
host_id: String,
workers: Vec<WorkerSummary>,
observed_prompt_revisions: Arc<Mutex<Vec<u64>>>,
}
impl FixtureRuntime {
@@ -4542,6 +4602,7 @@ mod tests {
working_directory: None,
diagnostics: Vec::new(),
}],
observed_prompt_revisions: Arc::new(Mutex::new(Vec::new())),
}
}
}
@@ -4551,6 +4612,17 @@ mod tests {
&self.runtime_id
}
fn observe_workspace_prompt_projection(
&self,
projection: worker::WorkspacePromptProjection,
) -> Result<(), String> {
self.observed_prompt_revisions
.lock()
.map_err(|_| "prompt projection observations poisoned".to_string())?
.push(projection.config_revision);
Ok(())
}
fn runtime_summary(&self, _limit: usize) -> RuntimeSummary {
RuntimeSummary {
runtime_id: self.runtime_id.clone(),
@@ -4647,6 +4719,36 @@ mod tests {
assert_eq!(from_runtime_a.label, "worker from runtime a");
}
#[test]
fn registry_broadcasts_workspace_prompt_projection_revisions() {
let runtime =
FixtureRuntime::with_worker("runtime-a", "host-a", "worker-a", "worker from runtime a");
let observed = runtime.observed_prompt_revisions.clone();
let registry = RuntimeRegistry::new(vec![Arc::new(runtime)]);
let catalog = worker::EffectivePromptCatalog::new(
std::collections::BTreeMap::from([(
"default".to_string(),
"workspace prompt".to_string(),
)]),
12,
"schema",
"toolchain",
)
.unwrap();
let projection = worker::WorkspacePromptProjection::new(
"workspace-a",
"source-12",
catalog.catalog_digest.clone(),
catalog,
)
.unwrap();
let diagnostics = registry.observe_workspace_prompt_projection(projection);
assert!(diagnostics.is_empty());
assert_eq!(*observed.lock().unwrap(), vec![12]);
}
#[test]
fn registry_worker_list_can_be_scoped_by_runtime_id() {
let registry = RuntimeRegistry::new(vec![
+8
View File
@@ -2673,6 +2673,14 @@ async fn scoped_commit_workspace_config_tree(
let state = api
.config_store
.commit_evaluated_workspace_config(&path.workspace_id, &candidate)?;
if let Ok(projection) = api
.prompt_projection_cache
.resolve(&path.workspace_id, &state)
{
let _diagnostics = api
.runtime
.observe_workspace_prompt_projection((*projection).clone());
}
Ok((
StatusCode::CREATED,
Json(WorkspaceConfigTreeResponse {