workspace: gate workdir assignment
This commit is contained in:
parent
21802d68f8
commit
59b1b3df79
|
|
@ -154,6 +154,8 @@ pub struct WorkingDirectorySummary {
|
||||||
pub status: WorkingDirectoryStatusKind,
|
pub status: WorkingDirectoryStatusKind,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub cleanliness: Option<String>,
|
pub cleanliness: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub primary_worker_id: Option<WorkerId>,
|
||||||
/// Backend projection metadata. Runtimes leave this absent; Workspace Browser
|
/// Backend projection metadata. Runtimes leave this absent; Workspace Browser
|
||||||
/// APIs fill it with `backend_managed` or `runtime_unmanaged`.
|
/// APIs fill it with `backend_managed` or `runtime_unmanaged`.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,8 @@ impl Runtime {
|
||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
Ok(backend.list_working_directories())
|
let statuses = backend.list_working_directories();
|
||||||
|
self.annotate_working_directory_statuses(statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a Runtime-owned working directory status.
|
/// Get a Runtime-owned working directory status.
|
||||||
|
|
@ -287,9 +288,10 @@ impl Runtime {
|
||||||
}
|
}
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
backend
|
let status = backend
|
||||||
.working_directory(working_directory_id)
|
.working_directory(working_directory_id)
|
||||||
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))?;
|
||||||
|
self.annotate_working_directory_status(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cleanup a Runtime-owned working directory.
|
/// Cleanup a Runtime-owned working directory.
|
||||||
|
|
@ -300,6 +302,11 @@ impl Runtime {
|
||||||
let backend = {
|
let backend = {
|
||||||
let state = self.lock()?;
|
let state = self.lock()?;
|
||||||
state.ensure_running()?;
|
state.ensure_running()?;
|
||||||
|
if let Some(worker_id) = state.primary_worker_id_for_workdir(working_directory_id) {
|
||||||
|
return Err(RuntimeError::InvalidRequest(format!(
|
||||||
|
"working directory {working_directory_id} is assigned to worker {worker_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
state.execution_backend.clone().ok_or_else(|| {
|
state.execution_backend.clone().ok_or_else(|| {
|
||||||
RuntimeError::ExecutionBackendUnavailable {
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
message: "working directory cleanup requires an execution backend".to_string(),
|
message: "working directory cleanup requires an execution backend".to_string(),
|
||||||
|
|
@ -311,6 +318,26 @@ impl Runtime {
|
||||||
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn annotate_working_directory_statuses(
|
||||||
|
&self,
|
||||||
|
statuses: Vec<CatalogWorkingDirectoryStatus>,
|
||||||
|
) -> Result<Vec<CatalogWorkingDirectoryStatus>, RuntimeError> {
|
||||||
|
statuses
|
||||||
|
.into_iter()
|
||||||
|
.map(|status| self.annotate_working_directory_status(status))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn annotate_working_directory_status(
|
||||||
|
&self,
|
||||||
|
mut status: CatalogWorkingDirectoryStatus,
|
||||||
|
) -> Result<CatalogWorkingDirectoryStatus, RuntimeError> {
|
||||||
|
let state = self.lock()?;
|
||||||
|
status.summary.primary_worker_id =
|
||||||
|
state.primary_worker_id_for_workdir(status.summary.working_directory_id.as_str());
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a Worker through the canonical profile-source + execution backend path.
|
/// Create a Worker through the canonical profile-source + execution backend path.
|
||||||
pub fn create_worker(
|
pub fn create_worker(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -321,6 +348,15 @@ impl Runtime {
|
||||||
state.ensure_running()?;
|
state.ensure_running()?;
|
||||||
validate_create_worker_request(&request)?;
|
validate_create_worker_request(&request)?;
|
||||||
state.validate_worker_config_boundary(&request)?;
|
state.validate_worker_config_boundary(&request)?;
|
||||||
|
if let Some(working_directory_id) = requested_primary_workdir_id(&request) {
|
||||||
|
if let Some(owner_worker_id) =
|
||||||
|
state.primary_worker_id_for_workdir(working_directory_id)
|
||||||
|
{
|
||||||
|
return Err(RuntimeError::InvalidRequest(format!(
|
||||||
|
"working directory {working_directory_id} is already assigned to worker {owner_worker_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
let backend = state.execution_backend.clone().ok_or_else(|| {
|
let backend = state.execution_backend.clone().ok_or_else(|| {
|
||||||
RuntimeError::ExecutionBackendUnavailable {
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
message: "worker creation requires an execution backend".to_string(),
|
message: "worker creation requires an execution backend".to_string(),
|
||||||
|
|
@ -1342,6 +1378,22 @@ impl RuntimeState {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn primary_worker_id_for_workdir(&self, working_directory_id: &str) -> Option<WorkerId> {
|
||||||
|
self.workers.values().find_map(|worker| {
|
||||||
|
if worker
|
||||||
|
.execution
|
||||||
|
.working_directory
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|binding| binding.summary.working_directory_id == working_directory_id)
|
||||||
|
|| requested_primary_workdir_id(&worker.request) == Some(working_directory_id)
|
||||||
|
{
|
||||||
|
Some(worker.worker_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn push_event(
|
fn push_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
worker_ref: Option<WorkerRef>,
|
worker_ref: Option<WorkerRef>,
|
||||||
|
|
@ -1510,6 +1562,19 @@ impl WorkerRecord {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn requested_primary_workdir_id(request: &CreateWorkerRequest) -> Option<&str> {
|
||||||
|
request
|
||||||
|
.working_directory
|
||||||
|
.as_ref()
|
||||||
|
.map(|claim| claim.working_directory_id.as_str())
|
||||||
|
.or_else(|| {
|
||||||
|
request
|
||||||
|
.working_directory_request
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|request| request.backend_workdir_id.as_deref())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_create_worker_request(request: &CreateWorkerRequest) -> Result<(), RuntimeError> {
|
fn validate_create_worker_request(request: &CreateWorkerRequest) -> Result<(), RuntimeError> {
|
||||||
match &request.profile_source {
|
match &request.profile_source {
|
||||||
crate::catalog::ProfileSourceArchiveSource::Embedded { archive } => {
|
crate::catalog::ProfileSourceArchiveSource::Embedded { archive } => {
|
||||||
|
|
|
||||||
|
|
@ -866,8 +866,8 @@ mod tests {
|
||||||
|
|
||||||
use crate::Runtime as EmbeddedRuntime;
|
use crate::Runtime as EmbeddedRuntime;
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, MaterializerKind, ProfileSelector, RepositorySelector,
|
ConfigBundleRef, CreateWorkerRequest, MaterializerKind, ProfileSelector,
|
||||||
WorkingDirectoryRepository, WorkingDirectoryRequest,
|
RepositorySelector, WorkingDirectoryRepository, WorkingDirectoryRequest,
|
||||||
};
|
};
|
||||||
use crate::execution::WorkerExecutionContext;
|
use crate::execution::WorkerExecutionContext;
|
||||||
use crate::identity::RuntimeId;
|
use crate::identity::RuntimeId;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
MaterializerKind, WorkingDirectoryCleanupTarget, WorkingDirectoryRequest, WorkingDirectoryStatus,
|
MaterializerKind, WorkingDirectoryCleanupTarget, WorkingDirectoryRequest,
|
||||||
WorkingDirectoryStatusKind, WorkingDirectorySummary,
|
WorkingDirectoryStatus, WorkingDirectoryStatusKind, WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use crate::identity::WorkerRef;
|
use crate::identity::WorkerRef;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -47,6 +47,7 @@ impl WorkingDirectory {
|
||||||
cleanup_target: Some(self.cleanup_target.clone()),
|
cleanup_target: Some(self.cleanup_target.clone()),
|
||||||
status: self.status.clone(),
|
status: self.status.clone(),
|
||||||
cleanliness: None,
|
cleanliness: None,
|
||||||
|
primary_worker_id: None,
|
||||||
management_kind: None,
|
management_kind: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +221,7 @@ impl LocalGitWorktreeMaterializer {
|
||||||
}),
|
}),
|
||||||
status: WorkingDirectoryStatusKind::Corrupted,
|
status: WorkingDirectoryStatusKind::Corrupted,
|
||||||
cleanliness: Some("unknown".to_string()),
|
cleanliness: Some("unknown".to_string()),
|
||||||
|
primary_worker_id: None,
|
||||||
management_kind: None,
|
management_kind: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1709,8 +1709,7 @@ fn build_runtime_cleanup_plan(
|
||||||
reason: if blocking_reason.is_some() {
|
reason: if blocking_reason.is_some() {
|
||||||
"Workdir cleanup is blocked until linked Worker state is safe".to_string()
|
"Workdir cleanup is blocked until linked Worker state is safe".to_string()
|
||||||
} else if matches!(file_status.as_str(), "missing" | "not_found") {
|
} else if matches!(file_status.as_str(), "missing" | "not_found") {
|
||||||
"Not-found Workdir record can be deleted from the Backend registry"
|
"Not-found Workdir record can be deleted from the Backend registry".to_string()
|
||||||
.to_string()
|
|
||||||
} else if file_status == "corrupted" {
|
} else if file_status == "corrupted" {
|
||||||
"Corrupted Workdir can be deleted from Runtime storage and Backend registry"
|
"Corrupted Workdir can be deleted from Runtime storage and Backend registry"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
@ -1848,9 +1847,10 @@ fn execute_runtime_cleanup(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?;
|
cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?;
|
||||||
let deleted = api
|
let deleted = api.store.delete_workdir_registry(
|
||||||
.store
|
&api.config.workspace_id,
|
||||||
.delete_workdir_registry(&api.config.workspace_id, candidate.workdir_id.as_str())?;
|
candidate.workdir_id.as_str(),
|
||||||
|
)?;
|
||||||
if !deleted {
|
if !deleted {
|
||||||
return Err(cleanup_api_error(
|
return Err(cleanup_api_error(
|
||||||
runtime_id,
|
runtime_id,
|
||||||
|
|
@ -1869,9 +1869,10 @@ fn execute_runtime_cleanup(
|
||||||
}
|
}
|
||||||
CleanupTargetKind::WorkdirCleanCleanup => {
|
CleanupTargetKind::WorkdirCleanCleanup => {
|
||||||
cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?;
|
cleanup_runtime_workdir_for_execution(api, runtime_id, candidate)?;
|
||||||
let deleted = api
|
let deleted = api.store.delete_workdir_registry(
|
||||||
.store
|
&api.config.workspace_id,
|
||||||
.delete_workdir_registry(&api.config.workspace_id, candidate.workdir_id.as_str())?;
|
candidate.workdir_id.as_str(),
|
||||||
|
)?;
|
||||||
if !deleted {
|
if !deleted {
|
||||||
return Err(cleanup_api_error(
|
return Err(cleanup_api_error(
|
||||||
runtime_id,
|
runtime_id,
|
||||||
|
|
@ -1883,7 +1884,8 @@ fn execute_runtime_cleanup(
|
||||||
target_id: candidate.target_id.clone(),
|
target_id: candidate.target_id.clone(),
|
||||||
action: candidate.action.clone(),
|
action: candidate.action.clone(),
|
||||||
status: "deleted".to_string(),
|
status: "deleted".to_string(),
|
||||||
message: "Workdir deleted from Runtime storage and Backend registry".to_string(),
|
message: "Workdir deleted from Runtime storage and Backend registry"
|
||||||
|
.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CleanupTargetKind::WorkdirRecordDelete => {
|
CleanupTargetKind::WorkdirRecordDelete => {
|
||||||
|
|
@ -3933,7 +3935,7 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResp
|
||||||
runtimes,
|
runtimes,
|
||||||
profiles: worker_profile_candidates_for_root(&api.config.workspace_root),
|
profiles: worker_profile_candidates_for_root(&api.config.workspace_root),
|
||||||
repositories: working_directory_repository_options(api),
|
repositories: working_directory_repository_options(api),
|
||||||
working_directories: working_directory_summaries(api).unwrap_or_default(),
|
working_directories: available_working_directory_summaries(api).unwrap_or_default(),
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3966,6 +3968,32 @@ fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirec
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn available_working_directory_summaries(
|
||||||
|
api: &WorkspaceApi,
|
||||||
|
) -> ApiResult<Vec<WorkingDirectorySummary>> {
|
||||||
|
let limit = api.config.max_records.min(200);
|
||||||
|
for worker in api.runtime.list_workers(limit).items {
|
||||||
|
let _ = sync_worker_observation(api, &worker);
|
||||||
|
}
|
||||||
|
let records = working_directory_summaries(api)?;
|
||||||
|
let mut available = Vec::new();
|
||||||
|
for summary in records {
|
||||||
|
if summary.status != WorkingDirectoryStatusKind::Active
|
||||||
|
|| summary.cleanliness.as_deref() != Some("clean")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let links = api.store.list_workdir_worker_links(
|
||||||
|
&api.config.workspace_id,
|
||||||
|
summary.working_directory_id.as_str(),
|
||||||
|
)?;
|
||||||
|
if links.is_empty() && summary.primary_worker_id.is_none() {
|
||||||
|
available.push(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(available)
|
||||||
|
}
|
||||||
|
|
||||||
fn runtime_working_directory_summaries(
|
fn runtime_working_directory_summaries(
|
||||||
api: &WorkspaceApi,
|
api: &WorkspaceApi,
|
||||||
runtime_id: &str,
|
runtime_id: &str,
|
||||||
|
|
@ -4235,7 +4263,10 @@ fn sync_runtime_workdir_observations(
|
||||||
&status.summary,
|
&status.summary,
|
||||||
management_kind.as_str(),
|
management_kind.as_str(),
|
||||||
);
|
);
|
||||||
preserve_workdir_identity_for_corrupted_summary(&mut updated, Some(&record));
|
preserve_workdir_identity_for_corrupted_summary(
|
||||||
|
&mut updated,
|
||||||
|
Some(&record),
|
||||||
|
);
|
||||||
api.store.upsert_workdir_registry(&updated)?;
|
api.store.upsert_workdir_registry(&updated)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4400,6 +4431,7 @@ fn workdir_summary_from_record(record: &WorkdirRegistryRecord) -> WorkingDirecto
|
||||||
}),
|
}),
|
||||||
status,
|
status,
|
||||||
cleanliness: Some(record.cleanliness.clone()),
|
cleanliness: Some(record.cleanliness.clone()),
|
||||||
|
primary_worker_id: None,
|
||||||
management_kind: Some(record.management_kind.clone()),
|
management_kind: Some(record.management_kind.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ export type WorkingDirectorySummary = {
|
||||||
resolved_tree?: string | null;
|
resolved_tree?: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
cleanliness?: string | null;
|
cleanliness?: string | null;
|
||||||
|
primary_worker_id?: number | null;
|
||||||
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
|
management_kind?: "backend_managed" | "runtime_unmanaged" | string | null;
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: string;
|
kind: string;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ const options: WorkerLaunchOptionsResponse = {
|
||||||
materializer_kind: "local_git_worktree",
|
materializer_kind: "local_git_worktree",
|
||||||
resolved_commit: "0123456789abcdef",
|
resolved_commit: "0123456789abcdef",
|
||||||
status: "active",
|
status: "active",
|
||||||
|
cleanliness: "clean",
|
||||||
|
primary_worker_id: null,
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: "git_worktree",
|
kind: "git_worktree",
|
||||||
working_directory_id: "wd-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,16 @@ export function defaultWorkerLaunchForm(
|
||||||
const preferredProfile =
|
const preferredProfile =
|
||||||
options?.profiles.find((candidate) => candidate.id === "builtin:coder") ??
|
options?.profiles.find((candidate) => candidate.id === "builtin:coder") ??
|
||||||
options?.profiles[0];
|
options?.profiles[0];
|
||||||
const preferredWorkingDirectory =
|
const availableWorkingDirectories = options?.working_directories.filter((directory) =>
|
||||||
options?.working_directories.find((directory) =>
|
directory.status === "active" &&
|
||||||
directory.status === "active"
|
directory.cleanliness === "clean" &&
|
||||||
) ??
|
directory.primary_worker_id == null
|
||||||
options?.working_directories[0];
|
) ?? [];
|
||||||
|
const selectedRuntime = current.runtime_id
|
||||||
|
? options?.runtimes.find((runtime) => runtime.runtime_id === current.runtime_id)
|
||||||
|
: preferredRuntime;
|
||||||
|
const workdirlessRuntime = selectedRuntime?.working_directory_required === false;
|
||||||
|
const preferredWorkingDirectory = workdirlessRuntime ? undefined : availableWorkingDirectories[0];
|
||||||
const preferredRepository =
|
const preferredRepository =
|
||||||
options?.repositories.find((repository) =>
|
options?.repositories.find((repository) =>
|
||||||
repository.id === current.working_directory_repository_id
|
repository.id === current.working_directory_repository_id
|
||||||
|
|
@ -54,7 +59,7 @@ export function defaultWorkerLaunchForm(
|
||||||
? current.profile
|
? current.profile
|
||||||
: preferredProfile?.id || "",
|
: preferredProfile?.id || "",
|
||||||
initial_text: current.initial_text,
|
initial_text: current.initial_text,
|
||||||
working_directory_id: options?.working_directories.some(
|
working_directory_id: !workdirlessRuntime && availableWorkingDirectories.some(
|
||||||
(directory) =>
|
(directory) =>
|
||||||
directory.working_directory_id === current.working_directory_id,
|
directory.working_directory_id === current.working_directory_id,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,16 @@
|
||||||
let isNewWorkingDirectorySelected = $derived(workingDirectoryId === NEW_WORKING_DIRECTORY_VALUE);
|
let isNewWorkingDirectorySelected = $derived(workingDirectoryId === NEW_WORKING_DIRECTORY_VALUE);
|
||||||
let selectedRuntime = $derived(options?.runtimes.find((runtime) => runtime.runtime_id === runtimeId));
|
let selectedRuntime = $derived(options?.runtimes.find((runtime) => runtime.runtime_id === runtimeId));
|
||||||
let selectedRuntimeAllowsNoWorkdir = $derived(selectedRuntime?.working_directory_required === false);
|
let selectedRuntimeAllowsNoWorkdir = $derived(selectedRuntime?.working_directory_required === false);
|
||||||
let hasSelectedExistingWorkdir = $derived(Boolean(workingDirectoryId && !isNewWorkingDirectorySelected));
|
let hasSelectedExistingWorkdir = $derived(Boolean(
|
||||||
|
workingDirectoryId && !isNewWorkingDirectorySelected && !selectedRuntimeAllowsNoWorkdir,
|
||||||
|
));
|
||||||
|
let availableWorkingDirectories = $derived(
|
||||||
|
selectedRuntimeAllowsNoWorkdir
|
||||||
|
? []
|
||||||
|
: (options?.working_directories ?? []).filter((directory) =>
|
||||||
|
directory.status === 'active' && directory.cleanliness === 'clean' && directory.primary_worker_id == null
|
||||||
|
),
|
||||||
|
);
|
||||||
let canStartWorker = $derived(Boolean(
|
let canStartWorker = $derived(Boolean(
|
||||||
runtimeId &&
|
runtimeId &&
|
||||||
profile &&
|
profile &&
|
||||||
|
|
@ -65,6 +74,13 @@
|
||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (selectedRuntimeAllowsNoWorkdir && workingDirectoryId) {
|
||||||
|
workingDirectoryId = '';
|
||||||
|
relativeCwd = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function loadLaunchOptions(signal?: AbortSignal) {
|
async function loadLaunchOptions(signal?: AbortSignal) {
|
||||||
loading = true;
|
loading = true;
|
||||||
optionsError = null;
|
optionsError = null;
|
||||||
|
|
@ -109,6 +125,10 @@
|
||||||
submitError = { message: 'select a runtime before creating a workdir', diagnostics: [] };
|
submitError = { message: 'select a runtime before creating a workdir', diagnostics: [] };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (selectedRuntimeAllowsNoWorkdir) {
|
||||||
|
submitError = { message: 'embedded Runtime does not create workdirs', diagnostics: [] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!workingDirectoryRepositoryId) {
|
if (!workingDirectoryRepositoryId) {
|
||||||
submitError = { message: 'select a repository before creating a workdir', diagnostics: [] };
|
submitError = { message: 'select a repository before creating a workdir', diagnostics: [] };
|
||||||
return;
|
return;
|
||||||
|
|
@ -124,7 +144,6 @@
|
||||||
runtime_id: runtimeId,
|
runtime_id: runtimeId,
|
||||||
repository_id: workingDirectoryRepositoryId,
|
repository_id: workingDirectoryRepositoryId,
|
||||||
selector: workingDirectorySelector || null,
|
selector: workingDirectorySelector || null,
|
||||||
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -245,16 +264,16 @@
|
||||||
<span>Run at</span>
|
<span>Run at</span>
|
||||||
<select class="worker-inline-select wd-select" bind:value={workingDirectoryId} aria-label="Workdir">
|
<select class="worker-inline-select wd-select" bind:value={workingDirectoryId} aria-label="Workdir">
|
||||||
{#if selectedRuntimeAllowsNoWorkdir}
|
{#if selectedRuntimeAllowsNoWorkdir}
|
||||||
<option value="">No workdir · embedded conversation only</option>
|
<option value="">No workdir</option>
|
||||||
{:else}
|
{:else}
|
||||||
<option value="" disabled>Select workdir</option>
|
<option value="" disabled>Select workdir</option>
|
||||||
|
{#each availableWorkingDirectories as directory}
|
||||||
|
<option value={directory.working_directory_id}>
|
||||||
|
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
<option value={NEW_WORKING_DIRECTORY_VALUE}>New workdir…</option>
|
||||||
{/if}
|
{/if}
|
||||||
{#each options?.working_directories ?? [] as directory}
|
|
||||||
<option value={directory.working_directory_id} disabled={directory.status !== 'active'}>
|
|
||||||
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'}
|
|
||||||
</option>
|
|
||||||
{/each}
|
|
||||||
<option value={NEW_WORKING_DIRECTORY_VALUE}>New workdir…</option>
|
|
||||||
</select>
|
</select>
|
||||||
<span>in</span>
|
<span>in</span>
|
||||||
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">
|
<select class="worker-inline-select runtime-select" bind:value={runtimeId} required aria-label="Runtime">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user