diff --git a/crates/db/src/models/workspace.rs b/crates/db/src/models/workspace.rs index cf53808d15..4b18c1630e 100644 --- a/crates/db/src/models/workspace.rs +++ b/crates/db/src/models/workspace.rs @@ -433,6 +433,28 @@ impl Workspace { }) } + /// Find workspace by path, also trying the parent directory. + /// Used by VSCode extension which may open a repo subfolder (single-repo case) + /// rather than the workspace root directory (multi-repo case). + pub async fn resolve_container_ref_by_prefix( + pool: &SqlitePool, + path: &str, + ) -> Result { + // First try exact match + if let Ok(info) = Self::resolve_container_ref(pool, path).await { + return Ok(info); + } + + if let Some(parent) = std::path::Path::new(path).parent() + && let Some(parent_str) = parent.to_str() + && let Ok(info) = Self::resolve_container_ref(pool, parent_str).await + { + return Ok(info); + } + + Err(sqlx::Error::RowNotFound) + } + pub async fn set_archived( pool: &SqlitePool, workspace_id: Uuid, diff --git a/crates/server/src/routes/containers.rs b/crates/server/src/routes/containers.rs index 88e843c843..b33d60b600 100644 --- a/crates/server/src/routes/containers.rs +++ b/crates/server/src/routes/containers.rs @@ -8,6 +8,7 @@ use db::models::workspace::{Workspace, WorkspaceContext}; use deployment::Deployment; use serde::{Deserialize, Serialize}; use utils::response::ApiResponse; +use uuid::Uuid; use crate::{DeploymentImpl, error::ApiError}; @@ -17,6 +18,29 @@ pub struct ContainerQuery { pub container_ref: String, } +#[derive(Debug, Serialize)] +pub struct ContainerInfo { + pub project_id: Uuid, + pub task_id: Uuid, + pub attempt_id: Uuid, +} + +pub async fn get_container_info( + Query(query): Query, + State(deployment): State, +) -> Result>, ApiError> { + let info = + Workspace::resolve_container_ref_by_prefix(&deployment.db().pool, &query.container_ref) + .await + .map_err(ApiError::Database)?; + + Ok(ResponseJson(ApiResponse::success(ContainerInfo { + project_id: info.project_id, + task_id: info.task_id, + attempt_id: info.workspace_id, + }))) +} + pub async fn get_context( State(deployment): State, Query(payload): Query, @@ -40,5 +64,10 @@ pub async fn get_context( } pub fn router(_deployment: &DeploymentImpl) -> Router { - Router::new().route("/containers/attempt-context", get(get_context)) + Router::new() + // NOTE: /containers/info is required by the VSCode extension (vibe-kanban-vscode) + // to auto-detect workspaces. It maps workspace_id to attempt_id for compatibility. + // Do not remove this endpoint without updating the extension. + .route("/containers/info", get(get_container_info)) + .route("/containers/attempt-context", get(get_context)) }