merge: integrate ticket assignment notifications

# Conflicts:
#	crates/workspace-server/src/store.rs
This commit is contained in:
2026-07-31 23:59:09 +09:00
18 changed files with 4531 additions and 500 deletions
+86 -11
View File
@@ -308,6 +308,27 @@ pub struct WorkerSpawnWorkingDirectoryRequest {
pub selector: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkerTicketAssignmentRequest {
pub ticket_id: String,
pub operation_id: String,
}
pub(crate) fn worker_spawn_idempotency(
request: &WorkerSpawnRequest,
) -> Result<Option<(String, String)>, String> {
let Some(assignment) = request.ticket_assignment.as_ref() else {
return Ok(None);
};
let encoded = serde_json::to_vec(request)
.map_err(|error| format!("serialize Worker spawn idempotency input: {error}"))?;
Ok(Some((
assignment.operation_id.clone(),
format!("sha256:{}", digest_hex(&encoded, 64)),
)))
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct WorkerSpawnRequest {
@@ -317,6 +338,8 @@ pub struct WorkerSpawnRequest {
pub acceptance: WorkerSpawnAcceptanceRequirement,
pub profile: ProfileSelector,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ticket_assignment: Option<WorkerTicketAssignmentRequest>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_input: Option<EmbeddedWorkerInput>,
/// Optional safe working-directory creation request. The Workspace server resolves
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
@@ -329,6 +352,8 @@ pub struct WorkerSpawnRequest {
pub resolved_working_directory: Option<WorkingDirectoryClaim>,
#[serde(skip, default)]
pub resolved_config_bundle: Option<ConfigBundle>,
#[serde(skip, default)]
pub resolved_workspace_api: Option<WorkspaceApiRef>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -427,6 +452,8 @@ pub struct WorkerStopResult {
pub struct WorkerLifecycleRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ticket_assignment: Option<WorkerTicketAssignmentRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -1695,7 +1722,14 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
};
}
};
let (idempotency_key, idempotency_fingerprint) = worker_spawn_idempotency(&request)
.expect("WorkerSpawnRequest serialization is infallible")
.map_or((None, None), |(key, fingerprint)| {
(Some(key), Some(fingerprint))
});
let create_request = CreateWorkerRequest {
idempotency_key,
idempotency_fingerprint,
profile,
display_name: request.requested_worker_name.clone(),
config_bundle: None,
@@ -1703,13 +1737,16 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
initial_input: request.initial_input.clone(),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
workspace_api: self
.backend_base_url
.as_ref()
.map(|base_url| WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: base_url.clone(),
}),
workspace_api: request.resolved_workspace_api.clone().or_else(|| {
self.backend_base_url
.as_ref()
.map(|base_url| WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: base_url.clone(),
runtime_id: Some(self.runtime_id.clone()),
access_token: None,
})
}),
};
match self.runtime.create_worker(create_request) {
Ok(detail) => WorkerSpawnResult {
@@ -2669,7 +2706,14 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
};
}
};
let (idempotency_key, idempotency_fingerprint) = worker_spawn_idempotency(&request)
.expect("WorkerSpawnRequest serialization is infallible")
.map_or((None, None), |(key, fingerprint)| {
(Some(key), Some(fingerprint))
});
let create = CreateWorkerRequest {
idempotency_key,
idempotency_fingerprint,
profile,
display_name: request.requested_worker_name.clone(),
config_bundle: None,
@@ -2677,9 +2721,13 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
initial_input: request.initial_input.clone(),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
workspace_api: Some(WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: self.backend_base_url.clone(),
workspace_api: request.resolved_workspace_api.clone().or_else(|| {
Some(WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: self.backend_base_url.clone(),
runtime_id: Some(self.runtime_id.clone()),
access_token: None,
})
}),
};
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
@@ -3121,8 +3169,11 @@ fn embedded_profile_path(profile: &ProfileSelector) -> Result<String, String> {
fn embedded_profile_label(profile: &ProfileSelector) -> Option<String> {
Some(match profile {
ProfileSelector::Builtin(name) | ProfileSelector::Named(name) => {
if name.strip_prefix("builtin:").unwrap_or(name) == MEMORY_CONSOLIDATION_PROFILE {
let builtin_name = name.strip_prefix("builtin:").unwrap_or(name);
if builtin_name == MEMORY_CONSOLIDATION_PROFILE {
MEMORY_CONSOLIDATION_PROFILE.to_string()
} else if builtin_name == WORKSPACE_ORCHESTRATOR_PROFILE {
WORKSPACE_ORCHESTRATOR_PROFILE.to_string()
} else {
safe_display_hint(name)
}
@@ -3132,6 +3183,8 @@ fn embedded_profile_label(profile: &ProfileSelector) -> Option<String> {
const MEMORY_CONSOLIDATION_PROFILE: &str = "memory-consolidation";
const MEMORY_CONSOLIDATION_SINGLETON_KEY: &str = "workspace-memory-consolidation";
const WORKSPACE_ORCHESTRATOR_PROFILE: &str = "orchestrator";
pub(crate) const WORKSPACE_ORCHESTRATOR_SINGLETON_KEY: &str = "workspace-orchestrator";
struct WorkerDisplayMetadata {
display_name: String,
@@ -3160,6 +3213,20 @@ fn worker_display_metadata(
tags,
};
}
if profile_label == Some(WORKSPACE_ORCHESTRATOR_PROFILE) {
let mut tags = vec!["orchestrator".to_string(), "singleton".to_string()];
if internal {
tags.insert(0, "internal".to_string());
}
return WorkerDisplayMetadata {
display_name: requested_display_name
.filter(|value| !value.trim().is_empty())
.map(safe_display_hint)
.unwrap_or_else(|| "Workspace Orchestrator".to_string()),
singleton_key: Some(WORKSPACE_ORCHESTRATOR_SINGLETON_KEY.to_string()),
tags,
};
}
let display_name = requested_display_name
.filter(|value| !value.trim().is_empty())
.map(safe_display_hint)
@@ -4149,11 +4216,13 @@ mod tests {
expected_segments: 0,
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
resolved_config_bundle: None,
resolved_workspace_api: None,
}
}
@@ -4275,11 +4344,13 @@ mod tests {
expected_segments: 0,
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
resolved_config_bundle: None,
resolved_workspace_api: None,
},
)
.unwrap();
@@ -4371,11 +4442,13 @@ mod tests {
expected_segments: 0,
},
profile: ProfileSelector::Builtin("builtin:coder".to_string()),
ticket_assignment: None,
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
resolved_config_bundle: None,
resolved_workspace_api: None,
},
)
.unwrap();
@@ -4403,11 +4476,13 @@ mod tests {
requested_worker_name: None,
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
profile: ProfileSelector::Builtin("builtin:companion".to_string()),
ticket_assignment: None,
initial_input: None,
working_directory_request: None,
resolved_working_directory_request: None,
resolved_working_directory: None,
resolved_config_bundle: None,
resolved_workspace_api: None,
},
)
.unwrap();
+4
View File
@@ -85,6 +85,10 @@ pub enum Error {
UnknownRepository(String),
#[error("workspace id does not match this Workspace backend")]
WorkspaceIdMismatch,
#[error("Ticket assignment conflict: {0}")]
TicketAssignmentConflict(String),
#[error("Worker Workspace authentication failed: {0}")]
WorkerWorkspaceAuthentication(String),
#[error("workspace identity error: {0}")]
WorkspaceIdentity(String),
#[error("store error: {0}")]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff