diff --git a/registry/coder/modules/agent-relay-cursor/README.md b/registry/coder/modules/agent-relay-cursor/README.md index 5266ba931..08f2c9a43 100644 --- a/registry/coder/modules/agent-relay-cursor/README.md +++ b/registry/coder/modules/agent-relay-cursor/README.md @@ -16,7 +16,7 @@ parameters the relay stamps on each build and runs the Cursor CLI worker. ```tf module "cursor_worker" { source = "registry.coder.com/coder/agent-relay-cursor/coder" - version = "0.2.0" + version = "0.3.0" agent_id = coder_agent.main.id # Downloads the Cursor CLI at start when it is not in the image. Bake @@ -40,6 +40,16 @@ The `agent_relay_status` metadata block is required. It has to live on the `coder_agent`, which the module cannot declare; the relay reads it to decide when to reap the workspace. +When Agent Relay stamps `agent_relay_session_id`, the module adds two external +apps. Cursor's docs call the clients Cursor Web and Cursor Desktop: + +- **Open in Cursor Web** opens the cloud agent at `https://cursor.com/agents/`. +- **Open in Cursor Desktop** opens it with + `cursor://anysphere.cursor-deeplink/background-agent?bcId=`. + +A workspace a person creates by hand has no session id, so neither button is +created. + ## Requirements - The Cursor CLI (`agent`) must be in the workspace. `install_cli` (default diff --git a/registry/coder/modules/agent-relay-cursor/main.tf b/registry/coder/modules/agent-relay-cursor/main.tf index 2b5f7aae5..528f8589a 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tf +++ b/registry/coder/modules/agent-relay-cursor/main.tf @@ -313,3 +313,31 @@ output "dispatched" { description = "Whether this workspace was spawned by Agent Relay (credential set) or manually (empty)." value = data.coder_parameter.agent_relay_credential.value != "" } + +output "session_id" { + description = "Cursor cloud agent id stamped in agent_relay_session_id. Empty when a person created the workspace." + value = data.coder_parameter.agent_relay_session_id.value +} + +# Cursor's own names for the two clients that open a cloud agent: +# Cursor Web (cursor.com/agents) and Cursor Desktop. A hand-created +# workspace has no session id, so neither button exists. +resource "coder_app" "cursor_web" { + count = data.coder_parameter.agent_relay_session_id.value != "" ? 1 : 0 + agent_id = var.agent_id + slug = "cursor-web" + display_name = "Open in Cursor Web" + icon = "/icon/cursor.svg" + url = "https://cursor.com/agents/${data.coder_parameter.agent_relay_session_id.value}" + external = true +} + +resource "coder_app" "cursor_desktop" { + count = data.coder_parameter.agent_relay_session_id.value != "" ? 1 : 0 + agent_id = var.agent_id + slug = "cursor-desktop" + display_name = "Open in Cursor Desktop" + icon = "/icon/cursor.svg" + url = "cursor://anysphere.cursor-deeplink/background-agent?bcId=${data.coder_parameter.agent_relay_session_id.value}" + external = true +} diff --git a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl index 36340084d..354a86165 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl +++ b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl @@ -179,6 +179,16 @@ run "worker_wiring" { condition = output.dispatched == false error_message = "a build with no credential was not dispatched by Agent Relay" } + + assert { + condition = output.session_id == "" + error_message = "a manual build has no Cursor cloud session id" + } + + assert { + condition = length(coder_app.cursor_web) == 0 && length(coder_app.cursor_desktop) == 0 + error_message = "a manual build has no cloud agent, so neither Cursor client button is created" + } } run "computer_use_off_by_default" {