Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion registry/coder/modules/agent-relay-cursor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.5.0"
version = "0.6.0"
agent_id = coder_agent.main.id

# Downloads the Cursor CLI at start when it is not in the image. Bake
Expand Down Expand Up @@ -86,6 +86,31 @@ for example after the idle-release timeout, or when the service account key
that minted it is deleted or expires. Its 7-day expiry is a backstop, not a
session limit.

## Additional worker arguments

Use `additional_args` to pass extra flags to `agent worker`, as a map of
flag to value. They are appended after the flags the module sets and before
the `start` subcommand. Each flag is passed exactly as written, so use `--name`
or `-n` as the CLI expects, followed by its value as the next argument; an
empty value passes the flag alone. Values are passed verbatim, so spaces and
quotes need no escaping. Terraform orders map keys alphabetically. Flags the module already sets
(`--pool`, `--idle-release-timeout`, `--auth-token-file`, `--auth-token`,
`--computer-use`, `--share-desktop`) are rejected. Give a flag that takes an optional value an explicit
one, or it consumes `start`.

```tf
module "cursor_worker" {
source = "registry.coder.com/coder/agent-relay-cursor/coder"
version = "0.6.0"
agent_id = coder_agent.main.id
# Passed as: --another-flag --some-flag value
additional_args = {
"--another-flag" = ""
"--some-flag" = "value"
}
}
```

## Parameters

Agent Relay verifies this contract against the template's active version at
Expand Down
22 changes: 22 additions & 0 deletions registry/coder/modules/agent-relay-cursor/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,28 @@ describe("agent-relay-cursor", () => {
expect(args).toContain("start");
});

it("appends additional_args before start, one argument per flag", async () => {
const { id, scripts } = await setup({
additional_args: JSON.stringify({
"--verbose": "",
"--name": "two words $HOME",
"-t": `it"s`,
}),
});
await stubAgent(id, 'printf "%s\\n" "$@" >/tmp/agent-args; sleep 30');
const { start } = await runDispatched(id, scripts);
expect(start.exitCode).toBe(0);
const args = (await readFileContainer(id, "/tmp/agent-args")).split("\n");
expect(args[0]).toBe("worker");
expect(args).toContain("--verbose");
// Values reach the CLI verbatim: no word splitting or expansion.
expect(args[args.indexOf("--name") + 1]).toBe("two words $HOME");
expect(args[args.indexOf("-t") + 1]).toBe('it"s');
// The subcommand stays last, after every flag.
expect(args.indexOf("start")).toBeGreaterThan(args.indexOf("-t") + 1);
expect(args[args.indexOf("--pool") + 1]).toBe(POOL_NAME);
});

it("records the exit code when the worker exits", async () => {
const { id, scripts } = await setup();
await stubAgent(id, "exit 3");
Expand Down
25 changes: 25 additions & 0 deletions registry/coder/modules/agent-relay-cursor/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ variable "serving_log_pattern" {
}
}

variable "additional_args" {
description = "Additional flags to pass to `agent worker`, as a map of flag to value. Each flag is passed as written (e.g. `--name` or `-n`) followed by its value as the next argument; an empty value passes the flag alone. Appended after the flags the module sets and before `start`."
type = map(string)
default = {}

validation {
condition = alltrue([for k, v in var.additional_args : can(regex("^--?[A-Za-z0-9][A-Za-z0-9._-]*$", k)) && !can(regex("\n", v))])
error_message = "additional_args keys must be flags such as --name or -n (letters, digits, '.', '_', '-'), and values must not contain newlines."
}

# The module already sets these: the pool and idle timeout come from Agent
# Relay's parameters, the token is passed by file so it stays off the
# command line, and desktop flags have their own inputs. Keep this list in
# sync with the `worker` flags in start.sh.tftpl.
validation {
condition = length(setintersection(keys(var.additional_args), ["--pool", "--idle-release-timeout", "--auth-token-file", "--auth-token", "--computer-use", "--share-desktop"])) == 0
error_message = "additional_args must not set --pool, --idle-release-timeout, --auth-token-file, --auth-token, --computer-use, or --share-desktop; the module sets them (use computer_use and share_desktop for the desktop flags)."
}
}

data "coder_parameter" "agent_relay_session_id" {
name = "agent_relay_session_id"
display_name = "Agent Relay session"
Expand Down Expand Up @@ -242,6 +262,10 @@ locals {
# tree so one directory holds everything a debugger needs.
module_directory = "$HOME/.coder-modules/coder/agent-relay-cursor"

# Render additional_args as argv: each key as written, followed by its
# value as a separate argument unless the value is empty.
additional_args = flatten([for k, v in var.additional_args : v == "" ? [k] : [k, v]])

install_script = templatefile("${path.module}/install.sh.tftpl", {
cli_binary = var.cli_binary
install_cli = var.install_cli
Expand All @@ -255,6 +279,7 @@ locals {
share_desktop = var.share_desktop
state_file = var.state_file
log_file = var.log_file
additional_args = base64encode(join("\n", local.additional_args))
})
}

Expand Down
53 changes: 53 additions & 0 deletions registry/coder/modules/agent-relay-cursor/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,59 @@ run "share_desktop_requires_computer_use" {
expect_failures = [var.share_desktop]
}

run "additional_args_default_empty" {
command = plan

assert {
condition = local.additional_args == [] && strcontains(local.start_script, "done < <(echo -n '' | base64 -d)")
error_message = "additional_args must default to empty"
}
}

run "additional_args_encoded" {
command = plan

variables {
additional_args = {
"--verbose" = ""
"--name" = "it's here"
"-t" = "x"
}
}

# Map keys sort byte-wise, so --flags come before -f flags.
assert {
condition = local.additional_args == ["--name", "it's here", "--verbose", "-t", "x"]
error_message = "each flag must be passed as written, followed by its value unless the value is empty"
}

# Base64 keeps quotes in a value from breaking the rendered script.
assert {
condition = strcontains(local.start_script, base64encode(join("\n", local.additional_args))) && !strcontains(local.start_script, "it's here")
error_message = "additional_args must be base64-encoded into the start script"
}
}

run "additional_args_rejects_bad_flag" {
command = plan

variables {
additional_args = { "verbose; rm -rf /" = "" }
}

expect_failures = [var.additional_args]
}

run "additional_args_rejects_module_flags" {
command = plan

variables {
additional_args = { "--auth-token" = "secret" }
}

expect_failures = [var.additional_args]
}

run "install_cli_enabled_by_default" {
command = plan

Expand Down
13 changes: 13 additions & 0 deletions registry/coder/modules/agent-relay-cursor/start.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ token_file="${module_directory}/worker-token"
scripts_dir="${module_directory}/scripts"
mkdir -p "$scripts_dir" "$(dirname "$state_file")" "$(dirname "$log_file")"

# additional_args, one argument per line. Shell-quoted here because the
# string is expanded into the supervisor below, which must see each
# argument as one word and never evaluate it.
additional_args=""
while IFS= read -r arg || [ -n "$arg" ]; do
if [ -n "$arg" ]; then
additional_args+="$(printf '%q' "$arg") "
fi
done < <(echo -n '${additional_args}' | base64 -d)

write_state() {
# Write via a temp file so a reader never sees a partial line.
printf '%s\n' "$1" >"$state_file.tmp"
Expand Down Expand Up @@ -126,6 +136,8 @@ write_state() {

export PATH="\$HOME/.local/bin:\$PATH"
unset AGENT_RELAY_CURSOR_TOKEN
# Keep these flags in sync with the reserved-flag list in additional_args'
# validation in main.tf, so additional_args cannot set them a second time.
${cli_binary} worker \\
--pool "\$AGENT_RELAY_CURSOR_POOL_NAME" \\
--idle-release-timeout "\$AGENT_RELAY_CURSOR_IDLE_RELEASE_TIMEOUT" \\
Expand All @@ -136,6 +148,7 @@ ${cli_binary} worker \\
%{ if share_desktop ~}
--share-desktop=view_and_control \\
%{ endif ~}
$additional_args \\
start >"$log_file" 2>&1 &
worker_pid=\$!
write_state "working \$worker_pid"
Expand Down
Loading