From 051e45109464e81b0f296817f2d72d8b3d0fbc28 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 12:48:54 +0500 Subject: [PATCH 01/16] feat(coder-labs): add Omnigent multi-agent server module --- .icons/omnigent.svg | 1 + .../coder-labs/modules/omnigent/README.md | 85 +++++++++ registry/coder-labs/modules/omnigent/main.tf | 102 +++++++++++ .../modules/omnigent/main.tftest.hcl | 144 +++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 38 ++++ .../modules/omnigent/scripts/start.sh.tftpl | 37 ++++ .../templates/omnigent-workspace/README.md | 44 +++++ .../templates/omnigent-workspace/main.tf | 170 ++++++++++++++++++ 8 files changed, 621 insertions(+) create mode 100644 .icons/omnigent.svg create mode 100644 registry/coder-labs/modules/omnigent/README.md create mode 100644 registry/coder-labs/modules/omnigent/main.tf create mode 100644 registry/coder-labs/modules/omnigent/main.tftest.hcl create mode 100644 registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl create mode 100644 registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl create mode 100644 registry/coder-labs/templates/omnigent-workspace/README.md create mode 100644 registry/coder-labs/templates/omnigent-workspace/main.tf diff --git a/.icons/omnigent.svg b/.icons/omnigent.svg new file mode 100644 index 000000000..c65fe35ff --- /dev/null +++ b/.icons/omnigent.svg @@ -0,0 +1 @@ +O diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md new file mode 100644 index 000000000..2aa70d224 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/README.md @@ -0,0 +1,85 @@ +--- +display_name: Omnigent +icon: ../../../../.icons/omnigent.svg +description: Run a private Omnigent multi-agent coding server in your workspace. +verified: false +tags: [agent, omnigent, ai, multi-agent] +--- + +# Omnigent + +Run a private [Omnigent](https://github.com/omnigent-dev) multi-agent coding orchestrator server inside your Coder workspace. Each workspace gets its own isolated Omnigent instance with a stable, derived admin password — no shared credentials, no manual password management. + +The module installs Omnigent via `uv tool install`, starts the server on a configurable port, waits for the health endpoint, and registers the local workspace as a host. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +## Examples + +### Standalone with default settings + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} +``` + +### With a custom port + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + port = 7878 +} +``` + +### With AI tools (Omnigent + Claude Code + Codex) + +Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace: + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} +``` + +## Troubleshooting + +Server logs are written to `~/.coder-modules/coder-labs/omnigent/logs/start.log`. If the Omnigent app shows as unhealthy or the server fails to start, check: + +```bash +cat ~/.coder-modules/coder-labs/omnigent/logs/start.log +cat ~/.coder-modules/coder-labs/omnigent/logs/install.log +``` + +The health endpoint is available at `http://localhost:/health`. You can check it directly: + +```bash +curl -sf http://localhost:6767/health && echo "healthy" || echo "not ready" +``` diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf new file mode 100644 index 000000000..c6c9794ca --- /dev/null +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -0,0 +1,102 @@ +terraform { + required_version = ">= 1.9" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.12" + } + } +} + +variable "agent_id" { + description = "The ID of a Coder agent." + type = string +} + +variable "icon" { + description = "Icon for Omnigent scripts and app." + type = string + default = "../../../../.icons/omnigent.svg" +} + +variable "port" { + description = "Port the Omnigent server listens on inside the workspace." + type = number + default = 6767 + validation { + condition = var.port > 1024 && var.port < 65536 + error_message = "port must be between 1025 and 65535." + } +} + +variable "omnigent_version" { + description = "Omnigent version to install. 'latest' installs the newest release." + type = string + default = "latest" +} + +variable "share" { + description = "Coder app share level." + type = string + default = "owner" + validation { + condition = contains(["owner", "authenticated", "public"], var.share) + error_message = "share must be one of: owner, authenticated, public." + } +} + +variable "order" { + description = "Order for the Omnigent app in the Coder UI." + type = number + default = null +} + +locals { + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_OMNIGENT_VERSION = var.omnigent_version + ARG_PORT = tostring(var.port) + }) + start_script = templatefile("${path.module}/scripts/start.sh.tftpl", { + ARG_PORT = tostring(var.port) + }) +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" + + agent_id = var.agent_id + module_directory = "$HOME/.coder-modules/coder-labs/omnigent" + display_name_prefix = "Omnigent" + icon = var.icon + install_script = local.install_script + start_script = local.start_script +} + +resource "coder_app" "omnigent" { + agent_id = var.agent_id + slug = "omnigent" + display_name = "Omnigent" + url = "http://localhost:${var.port}" + icon = var.icon + subdomain = true + share = var.share + order = var.order + + healthcheck { + url = "http://localhost:${var.port}/health" + interval = 15 + threshold = 3 + } +} + +output "scripts" { + description = "Ordered list of coder exp sync names produced by this module, in run order." + value = module.coder_utils.scripts +} + +output "port" { + description = "Port the Omnigent server is listening on." + value = var.port +} diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl new file mode 100644 index 000000000..1a4c9d421 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -0,0 +1,144 @@ +run "test_defaults" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = var.port == 6767 + error_message = "port should default to 6767" + } + + assert { + condition = var.share == "owner" + error_message = "share should default to owner" + } + + assert { + condition = var.omnigent_version == "latest" + error_message = "omnigent_version should default to latest" + } + + assert { + condition = coder_app.omnigent.url == "http://localhost:6767" + error_message = "coder_app url should use default port 6767" + } + + assert { + condition = coder_app.omnigent.share == "owner" + error_message = "coder_app share should default to owner" + } +} + +run "test_custom_port" { + command = plan + + variables { + agent_id = "test-agent" + port = 8080 + } + + assert { + condition = var.port == 8080 + error_message = "port should be set to 8080" + } + + assert { + condition = coder_app.omnigent.url == "http://localhost:8080" + error_message = "coder_app url should use custom port 8080" + } +} + +run "test_custom_share" { + command = plan + + variables { + agent_id = "test-agent" + share = "authenticated" + } + + assert { + condition = var.share == "authenticated" + error_message = "share should be set to authenticated" + } + + assert { + condition = coder_app.omnigent.share == "authenticated" + error_message = "coder_app share should be authenticated" + } +} + +run "test_custom_version" { + command = plan + + variables { + agent_id = "test-agent" + omnigent_version = "0.1.0" + } + + assert { + condition = var.omnigent_version == "0.1.0" + error_message = "omnigent_version should be set to 0.1.0" + } +} + +run "test_scripts_output" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = length(output.scripts) > 0 + error_message = "scripts output should be non-empty" + } +} + +run "test_port_output" { + command = plan + + variables { + agent_id = "test-agent" + port = 7777 + } + + assert { + condition = output.port == 7777 + error_message = "port output should match the configured port" + } +} + +run "test_invalid_port_low" { + command = plan + + variables { + agent_id = "test-agent" + port = 80 + } + + expect_failures = [var.port] +} + +run "test_invalid_port_high" { + command = plan + + variables { + agent_id = "test-agent" + port = 65536 + } + + expect_failures = [var.port] +} + +run "test_invalid_share" { + command = plan + + variables { + agent_id = "test-agent" + share = "invalid" + } + + expect_failures = [var.share] +} diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl new file mode 100644 index 000000000..f8e91c2c0 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -0,0 +1,38 @@ +#!/bin/bash +set -euo pipefail + +BOLD='\033[0;1m' + +ARG_OMNIGENT_VERSION='${ARG_OMNIGENT_VERSION}' +ARG_PORT='${ARG_PORT}' + +echo "--------------------------------" +printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION}" +printf "port: %s\n" "$${ARG_PORT}" +echo "--------------------------------" + +# Install uv if missing +if ! command -v uv &>/dev/null; then + printf "%s Installing uv\n" "$${BOLD}" + curl -LsSf https://astral.sh/uv/install.sh | sh +fi +export PATH="$${HOME}/.local/bin:$${PATH}" + +# Install or upgrade omnigent +if [ "$${ARG_OMNIGENT_VERSION}" = "latest" ]; then + if ! command -v omnigent &>/dev/null; then + printf "%s Installing omnigent (latest)\n" "$${BOLD}" + uv tool install omnigent + else + printf "%s Upgrading omnigent\n" "$${BOLD}" + uv tool upgrade omnigent 2>/dev/null || true + fi +else + printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" + uv tool install "omnigent==$${ARG_OMNIGENT_VERSION}" --force-reinstall +fi +export PATH="$${HOME}/.local/bin:$${PATH}" +printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)" + +# Configure client to point to the local server +omnigent config set server=http://localhost:$${ARG_PORT} diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl new file mode 100644 index 000000000..bca40a954 --- /dev/null +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" +LOG_FILE="$${MODULE_DIR}/logs/start.log" +ARG_PORT='${ARG_PORT}' + +mkdir -p "$${MODULE_DIR}/logs" + +# Derive a stable admin password from the workspace ID (first 16 hex chars) +OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1-16) + +if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then + echo "Starting Omnigent server on port $${ARG_PORT}..." + export OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD="$${OMNIGENT_ADMIN_PASSWORD}" + nohup omnigent server --host 127.0.0.1 --port "$${ARG_PORT}" --no-open \ + >> "$${LOG_FILE}" 2>&1 & +else + echo "Omnigent server already running on port $${ARG_PORT}, skipping start." +fi + +echo "Waiting for Omnigent server..." +for i in $(seq 1 90); do + if curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then + echo "Omnigent server is ready." + break + fi + if [ "$${i}" -eq 90 ]; then + echo "ERROR: Omnigent server did not start within 90 seconds." >&2 + cat "$${LOG_FILE}" >&2 || true + exit 1 + fi + sleep 1 +done + +# Register local workspace as a host +omnigent host "" diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md new file mode 100644 index 000000000..1d00c75ba --- /dev/null +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -0,0 +1,44 @@ +--- +display_name: Omnigent Workspace +icon: ../../../../.icons/omnigent.svg +description: Docker workspace with Omnigent, Claude Code, and Codex pre-installed. +verified: false +tags: [docker, omnigent, claude-code, codex, ai, multi-agent] +--- + +# Omnigent Workspace + +A Docker-based workspace that combines three AI agent modules: + +- **[Omnigent](https://registry.coder.com/modules/coder-labs/omnigent)** — private multi-agent coding orchestrator server +- **[Claude Code](https://registry.coder.com/modules/coder/claude-code)** — Anthropic's Claude in your terminal +- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI + +Each workspace runs its own isolated Omnigent server. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} +``` + +## Prerequisites + +- Docker with `sysbox-runc` runtime installed on the Coder host +- `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` set as Coder template variables diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf new file mode 100644 index 000000000..b72dfaf16 --- /dev/null +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -0,0 +1,170 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.13" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 4.0" + } + } +} + +provider "coder" {} +provider "docker" {} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} +data "coder_provisioner" "me" {} + +variable "anthropic_api_key" { + description = "Anthropic API key for Claude Code." + type = string + sensitive = true +} + +variable "openai_api_key" { + description = "OpenAI API key for Codex." + type = string + sensitive = true +} + +resource "coder_agent" "main" { + arch = data.coder_provisioner.me.arch + os = "linux" + startup_script = <<-EOT + #!/bin/bash + set -e + if [ ! -f ~/.init_done ]; then + cp -rT /etc/skel ~ 2>/dev/null || true + touch ~/.init_done + fi + EOT + + env = { + GIT_AUTHOR_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) + GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email + GIT_COMMITTER_NAME = coalesce(data.coder_workspace_owner.me.full_name, data.coder_workspace_owner.me.name) + GIT_COMMITTER_EMAIL = data.coder_workspace_owner.me.email + } + + metadata { + display_name = "CPU Usage" + key = "0_cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "1_ram_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Home Disk" + key = "2_home_disk" + script = "coder stat disk --path $${HOME}" + interval = 60 + timeout = 1 + } +} + +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id +} + +module "codex" { + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + agent_id = coder_agent.main.id + openai_api_key = var.openai_api_key +} + +module "claude_code" { + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id + anthropic_api_key = var.anthropic_api_key +} + +resource "docker_volume" "home_volume" { + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}-home" + lifecycle { + ignore_changes = all + } + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name_at_creation" + value = data.coder_workspace.me.name + } +} + +data "docker_registry_image" "workspace" { + name = "codercom/enterprise-base:ubuntu" +} + +resource "docker_image" "workspace" { + name = "codercom/enterprise-base@${data.docker_registry_image.workspace.sha256_digest}" + pull_triggers = [data.docker_registry_image.workspace.sha256_digest] + keep_locally = true +} + +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = docker_image.workspace.image_id + name = "coder-${data.coder_workspace_owner.me.name}-${lower(data.coder_workspace.me.name)}" + hostname = lower(data.coder_workspace.me.name) + runtime = "sysbox-runc" + + entrypoint = ["sh", "-c", replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")] + + env = [ + "CODER_AGENT_TOKEN=${coder_agent.main.token}", + ] + + host { + host = "host.docker.internal" + ip = "host-gateway" + } + + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home_volume.name + read_only = false + } + + labels { + label = "coder.owner" + value = data.coder_workspace_owner.me.name + } + labels { + label = "coder.owner_id" + value = data.coder_workspace_owner.me.id + } + labels { + label = "coder.workspace_id" + value = data.coder_workspace.me.id + } + labels { + label = "coder.workspace_name" + value = data.coder_workspace.me.name + } +} From 74b69f135f64ab13a2aa57b2ee899a93bf9ccd2c Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:07:50 +0500 Subject: [PATCH 02/16] fix(coder-labs/omnigent): blocking fixes + policies/agents inputs Blocking fixes: - Add PATH export at top of start.sh so omnigent is found after install - Separate server.log from start.log (nohup concurrent write conflict) - Bump coder provider constraint from >= 2.12 to >= 2.13 (matches coder-utils) - Base64-encode omnigent version in install.sh (injection hardening) - Surface upgrade failures as warnings instead of silencing them New inputs: - server_config: inline YAML written to module dir and passed as -c - server_config_path: path to existing config file, mutually exclusive with server_config - agents: list of {name, content} pre-registered at startup via --agent flags - pre_install_script / post_install_script: pass-through to coder-utils New output: server_config_path (effective config path or empty string) 13/13 tests pass. --- .../coder-labs/modules/omnigent/README.md | 77 ++++++++++++++++--- registry/coder-labs/modules/omnigent/main.tf | 74 ++++++++++++++++-- .../modules/omnigent/main.tftest.hcl | 59 ++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 38 +++++++-- .../modules/omnigent/scripts/start.sh.tftpl | 32 +++++++- 5 files changed, 255 insertions(+), 25 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 2aa70d224..356f4e4fd 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -22,16 +22,6 @@ module "omnigent" { ## Examples -### Standalone with default settings - -```tf -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} -``` - ### With a custom port ```tf @@ -69,11 +59,68 @@ module "claude_code" { } ``` +### Policies (server-wide) + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + server_config = <<-YAML + policies: + cap_tool_calls: + type: function + handler: omnigent.policies.builtins.safety.max_tool_calls_per_session + factory_params: + limit: 50 + require_approval: + type: function + handler: omnigent.policies.builtins.safety.ask_on_os_tools + YAML +} +``` + +### Custom agents + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + agents = [ + { + name = "reviewer" + content = <<-YAML + name: reviewer + instructions: You are an expert code reviewer. Focus on correctness, security, and clarity. + executor: + harness: claude-sdk + model: claude-sonnet-4-5 + YAML + } + ] +} +``` + +### Bring-your-own config file + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + server_config_path = "/home/coder/.omnigent/server_config.yaml" +} +``` + ## Troubleshooting -Server logs are written to `~/.coder-modules/coder-labs/omnigent/logs/start.log`. If the Omnigent app shows as unhealthy or the server fails to start, check: +Script logs are written to `~/.coder-modules/coder-labs/omnigent/logs/`. If the Omnigent app shows as unhealthy or the server fails to start, check: ```bash +cat ~/.coder-modules/coder-labs/omnigent/logs/server.log cat ~/.coder-modules/coder-labs/omnigent/logs/start.log cat ~/.coder-modules/coder-labs/omnigent/logs/install.log ``` @@ -83,3 +130,11 @@ The health endpoint is available at `http://localhost:/health`. You can ch ```bash curl -sf http://localhost:6767/health && echo "healthy" || echo "not ready" ``` + +### Finding the admin password + +The admin password is derived from the workspace ID at runtime. To retrieve it inside the workspace: + +```bash +echo -n "$CODER_WORKSPACE_ID" | tr -d '-' | cut -c1-16 +``` diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf index c6c9794ca..ab62bcf57 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = ">= 2.12" + version = ">= 2.13" } } } @@ -52,13 +52,70 @@ variable "order" { default = null } +variable "server_config" { + description = "Inline server_config.yaml content for the Omnigent server. Supports policies, policy_modules, admins, and allowed_domains keys. When set, written to the module directory and passed as -c to the server. Mutually exclusive with server_config_path." + type = string + default = null + validation { + condition = !(var.server_config != null && var.server_config_path != null) + error_message = "Only one of server_config or server_config_path may be set." + } +} + +variable "server_config_path" { + description = "Path to an existing server_config.yaml in the workspace. When set, passed directly as -c to the server; no config file is written by this module. Mutually exclusive with server_config." + type = string + default = null +} + +variable "agents" { + description = "Custom agent YAML definitions to pre-register at server startup. Each entry is written to the module directory and passed as --agent flags." + type = list(object({ + name = string + content = string + })) + default = [] +} + +variable "pre_install_script" { + description = "Custom script to run before installing Omnigent." + type = string + default = null +} + +variable "post_install_script" { + description = "Custom script to run after installing Omnigent." + type = string + default = null +} + locals { + module_dir = "$HOME/.coder-modules/coder-labs/omnigent" + server_config_file = "${local.module_dir}/config/server.yaml" + agents_dir = "${local.module_dir}/agents" + + effective_server_config_path = ( + var.server_config_path != null ? var.server_config_path : + var.server_config != null ? local.server_config_file : + null + ) + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { - ARG_OMNIGENT_VERSION = var.omnigent_version - ARG_PORT = tostring(var.port) + ARG_OMNIGENT_VERSION_IS_LATEST = tostring(var.omnigent_version == "latest") + ARG_OMNIGENT_VERSION_B64 = var.omnigent_version != "latest" ? base64encode(var.omnigent_version) : "" + ARG_PORT = tostring(var.port) + ARG_WRITE_SERVER_CONFIG = tostring(var.server_config != null) + ARG_SERVER_CONFIG_B64 = var.server_config != null ? base64encode(var.server_config) : "" + ARG_SERVER_CONFIG_FILE = local.server_config_file + ARG_SERVER_CONFIG_DIR = "${local.module_dir}/config" + ARG_AGENTS_B64 = length(var.agents) > 0 ? base64encode(join("\n", [for a in var.agents : "${a.name}\t${base64encode(a.content)}"])) : "" + ARG_AGENTS_DIR = local.agents_dir }) + start_script = templatefile("${path.module}/scripts/start.sh.tftpl", { - ARG_PORT = tostring(var.port) + ARG_PORT = tostring(var.port) + ARG_EFFECTIVE_SERVER_CONFIG_PATH = local.effective_server_config_path != null ? local.effective_server_config_path : "" + ARG_AGENTS_DIR = local.agents_dir }) } @@ -67,9 +124,11 @@ module "coder_utils" { version = "0.0.1" agent_id = var.agent_id - module_directory = "$HOME/.coder-modules/coder-labs/omnigent" + module_directory = local.module_dir display_name_prefix = "Omnigent" icon = var.icon + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script install_script = local.install_script start_script = local.start_script } @@ -100,3 +159,8 @@ output "port" { description = "Port the Omnigent server is listening on." value = var.port } + +output "server_config_path" { + description = "Effective path to the server config file, or empty string if no config is used." + value = local.effective_server_config_path != null ? local.effective_server_config_path : "" +} diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index 1a4c9d421..ccc2e48c3 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -142,3 +142,62 @@ run "test_invalid_share" { expect_failures = [var.share] } + +run "test_server_config" { + command = plan + + variables { + agent_id = "test-agent" + server_config = "policies: {}" + } + + assert { + condition = var.server_config == "policies: {}" + error_message = "server_config should be set" + } +} + +run "test_server_config_path" { + command = plan + + variables { + agent_id = "test-agent" + server_config_path = "/home/coder/.omnigent/server.yaml" + } + + assert { + condition = output.server_config_path == "/home/coder/.omnigent/server.yaml" + error_message = "server_config_path output should match the provided path" + } +} + +run "test_server_config_mutual_exclusion" { + command = plan + + variables { + agent_id = "test-agent" + server_config = "policies: {}" + server_config_path = "/home/coder/.omnigent/server.yaml" + } + + expect_failures = [var.server_config] +} + +run "test_agents" { + command = plan + + variables { + agent_id = "test-agent" + agents = [ + { + name = "reviewer" + content = "name: reviewer\ninstructions: You are a reviewer." + } + ] + } + + assert { + condition = length(var.agents) == 1 + error_message = "agents should have one entry" + } +} diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index f8e91c2c0..78af0118a 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -3,29 +3,39 @@ set -euo pipefail BOLD='\033[0;1m' -ARG_OMNIGENT_VERSION='${ARG_OMNIGENT_VERSION}' +ARG_OMNIGENT_VERSION_IS_LATEST='${ARG_OMNIGENT_VERSION_IS_LATEST}' +ARG_OMNIGENT_VERSION=$(echo -n '${ARG_OMNIGENT_VERSION_B64}' | base64 -d) ARG_PORT='${ARG_PORT}' +ARG_WRITE_SERVER_CONFIG='${ARG_WRITE_SERVER_CONFIG}' +ARG_SERVER_CONFIG=$(echo -n '${ARG_SERVER_CONFIG_B64}' | base64 -d) +ARG_SERVER_CONFIG_FILE='${ARG_SERVER_CONFIG_FILE}' +ARG_SERVER_CONFIG_DIR='${ARG_SERVER_CONFIG_DIR}' +ARG_AGENTS=$(echo -n '${ARG_AGENTS_B64}' | base64 -d) +ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' echo "--------------------------------" -printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION}" +printf "omnigent_version (latest=%s): %s\n" "$${ARG_OMNIGENT_VERSION_IS_LATEST}" "$${ARG_OMNIGENT_VERSION:-latest}" printf "port: %s\n" "$${ARG_PORT}" +printf "write_server_config: %s\n" "$${ARG_WRITE_SERVER_CONFIG}" echo "--------------------------------" +export PATH="$${HOME}/.local/bin:$${PATH}" + # Install uv if missing if ! command -v uv &>/dev/null; then printf "%s Installing uv\n" "$${BOLD}" curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$${HOME}/.local/bin:$${PATH}" fi -export PATH="$${HOME}/.local/bin:$${PATH}" # Install or upgrade omnigent -if [ "$${ARG_OMNIGENT_VERSION}" = "latest" ]; then +if [ "$${ARG_OMNIGENT_VERSION_IS_LATEST}" = "true" ]; then if ! command -v omnigent &>/dev/null; then printf "%s Installing omnigent (latest)\n" "$${BOLD}" uv tool install omnigent else printf "%s Upgrading omnigent\n" "$${BOLD}" - uv tool upgrade omnigent 2>/dev/null || true + uv tool upgrade omnigent || echo "Warning: upgrade failed, continuing with existing version" fi else printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" @@ -36,3 +46,21 @@ printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)" # Configure client to point to the local server omnigent config set server=http://localhost:$${ARG_PORT} + +# Write server config file if provided +if [ "$${ARG_WRITE_SERVER_CONFIG}" = "true" ]; then + mkdir -p "$${ARG_SERVER_CONFIG_DIR}" + printf "%s Writing server config to %s\n" "$${BOLD}" "$${ARG_SERVER_CONFIG_FILE}" + echo "$${ARG_SERVER_CONFIG}" > "$${ARG_SERVER_CONFIG_FILE}" +fi + +# Write agent YAML files +if [ -n "$${ARG_AGENTS}" ]; then + mkdir -p "$${ARG_AGENTS_DIR}" + while IFS=$'\t' read -r agent_name agent_content_b64; do + [ -z "$${agent_name}" ] && continue + agent_file="$${ARG_AGENTS_DIR}/$${agent_name}.yaml" + printf "%s Writing agent: %s -> %s\n" "$${BOLD}" "$${agent_name}" "$${agent_file}" + echo -n "$${agent_content_b64}" | base64 -d > "$${agent_file}" + done <<< "$${ARG_AGENTS}" +fi diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index bca40a954..f2aae552e 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -1,9 +1,14 @@ #!/bin/bash set -euo pipefail +export PATH="$${HOME}/.local/bin:$${PATH}" + MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" -LOG_FILE="$${MODULE_DIR}/logs/start.log" +START_LOG="$${MODULE_DIR}/logs/start.log" +SERVER_LOG="$${MODULE_DIR}/logs/server.log" ARG_PORT='${ARG_PORT}' +ARG_EFFECTIVE_SERVER_CONFIG_PATH='${ARG_EFFECTIVE_SERVER_CONFIG_PATH}' +ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' mkdir -p "$${MODULE_DIR}/logs" @@ -12,9 +17,28 @@ OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1 if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then echo "Starting Omnigent server on port $${ARG_PORT}..." + + # Build server flags + SERVER_FLAGS="--host 127.0.0.1 --port $${ARG_PORT} --no-open" + + # Add config file if set and present + if [ -n "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ] && [ -f "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ]; then + SERVER_FLAGS="$${SERVER_FLAGS} -c $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" + echo "Using server config: $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" + fi + + # Add pre-registered agent YAML files + if [ -d "$${ARG_AGENTS_DIR}" ]; then + for agent_file in "$${ARG_AGENTS_DIR}"/*.yaml; do + [ -f "$${agent_file}" ] || continue + SERVER_FLAGS="$${SERVER_FLAGS} --agent $${agent_file}" + echo "Registering agent: $${agent_file}" + done + fi + export OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD="$${OMNIGENT_ADMIN_PASSWORD}" - nohup omnigent server --host 127.0.0.1 --port "$${ARG_PORT}" --no-open \ - >> "$${LOG_FILE}" 2>&1 & + # shellcheck disable=SC2086 + nohup omnigent server $${SERVER_FLAGS} >> "$${SERVER_LOG}" 2>&1 & else echo "Omnigent server already running on port $${ARG_PORT}, skipping start." fi @@ -27,7 +51,7 @@ for i in $(seq 1 90); do fi if [ "$${i}" -eq 90 ]; then echo "ERROR: Omnigent server did not start within 90 seconds." >&2 - cat "$${LOG_FILE}" >&2 || true + cat "$${SERVER_LOG}" >&2 || true exit 1 fi sleep 1 From 8a3155e787f8c4bb9de760a040d65b33f8c0623a Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:09:43 +0500 Subject: [PATCH 03/16] refactor(coder-labs/omnigent): switch to official installer --- .../coder-labs/modules/omnigent/README.md | 2 +- registry/coder-labs/modules/omnigent/main.tf | 17 +++++----- .../modules/omnigent/scripts/install.sh.tftpl | 32 ++++++------------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 356f4e4fd..89e672a16 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -10,7 +10,7 @@ tags: [agent, omnigent, ai, multi-agent] Run a private [Omnigent](https://github.com/omnigent-dev) multi-agent coding orchestrator server inside your Coder workspace. Each workspace gets its own isolated Omnigent instance with a stable, derived admin password — no shared credentials, no manual password management. -The module installs Omnigent via `uv tool install`, starts the server on a configurable port, waits for the health endpoint, and registers the local workspace as a host. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. +The module installs Omnigent via the [official install script](https://omnigent.ai/install.sh), starts the server on a configurable port, waits for the health endpoint, and registers the local workspace as a host. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. ```tf module "omnigent" { diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf index ab62bcf57..7b758a3d1 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -101,15 +101,14 @@ locals { ) install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { - ARG_OMNIGENT_VERSION_IS_LATEST = tostring(var.omnigent_version == "latest") - ARG_OMNIGENT_VERSION_B64 = var.omnigent_version != "latest" ? base64encode(var.omnigent_version) : "" - ARG_PORT = tostring(var.port) - ARG_WRITE_SERVER_CONFIG = tostring(var.server_config != null) - ARG_SERVER_CONFIG_B64 = var.server_config != null ? base64encode(var.server_config) : "" - ARG_SERVER_CONFIG_FILE = local.server_config_file - ARG_SERVER_CONFIG_DIR = "${local.module_dir}/config" - ARG_AGENTS_B64 = length(var.agents) > 0 ? base64encode(join("\n", [for a in var.agents : "${a.name}\t${base64encode(a.content)}"])) : "" - ARG_AGENTS_DIR = local.agents_dir + ARG_OMNIGENT_VERSION_B64 = var.omnigent_version != "latest" ? base64encode(var.omnigent_version) : "" + ARG_PORT = tostring(var.port) + ARG_WRITE_SERVER_CONFIG = tostring(var.server_config != null) + ARG_SERVER_CONFIG_B64 = var.server_config != null ? base64encode(var.server_config) : "" + ARG_SERVER_CONFIG_FILE = local.server_config_file + ARG_SERVER_CONFIG_DIR = "${local.module_dir}/config" + ARG_AGENTS_B64 = length(var.agents) > 0 ? base64encode(join("\n", [for a in var.agents : "${a.name}\t${base64encode(a.content)}"])) : "" + ARG_AGENTS_DIR = local.agents_dir }) start_script = templatefile("${path.module}/scripts/start.sh.tftpl", { diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index 78af0118a..948707f12 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -3,8 +3,8 @@ set -euo pipefail BOLD='\033[0;1m' -ARG_OMNIGENT_VERSION_IS_LATEST='${ARG_OMNIGENT_VERSION_IS_LATEST}' ARG_OMNIGENT_VERSION=$(echo -n '${ARG_OMNIGENT_VERSION_B64}' | base64 -d) +# Empty = latest; non-empty = pinned version ARG_PORT='${ARG_PORT}' ARG_WRITE_SERVER_CONFIG='${ARG_WRITE_SERVER_CONFIG}' ARG_SERVER_CONFIG=$(echo -n '${ARG_SERVER_CONFIG_B64}' | base64 -d) @@ -14,34 +14,20 @@ ARG_AGENTS=$(echo -n '${ARG_AGENTS_B64}' | base64 -d) ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' echo "--------------------------------" -printf "omnigent_version (latest=%s): %s\n" "$${ARG_OMNIGENT_VERSION_IS_LATEST}" "$${ARG_OMNIGENT_VERSION:-latest}" +printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION:-latest}" printf "port: %s\n" "$${ARG_PORT}" printf "write_server_config: %s\n" "$${ARG_WRITE_SERVER_CONFIG}" echo "--------------------------------" -export PATH="$${HOME}/.local/bin:$${PATH}" - -# Install uv if missing -if ! command -v uv &>/dev/null; then - printf "%s Installing uv\n" "$${BOLD}" - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$${HOME}/.local/bin:$${PATH}" +# Install omnigent via official installer +INSTALL_ARGS="--non-interactive" +if [ -n "$${ARG_OMNIGENT_VERSION}" ]; then + INSTALL_ARGS="$${INSTALL_ARGS} --version $${ARG_OMNIGENT_VERSION}" fi +printf "%s Installing omnigent%s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION:+ $${ARG_OMNIGENT_VERSION}}" +# shellcheck disable=SC2086 +curl -fsSL https://omnigent.ai/install.sh | sh -s -- $${INSTALL_ARGS} -# Install or upgrade omnigent -if [ "$${ARG_OMNIGENT_VERSION_IS_LATEST}" = "true" ]; then - if ! command -v omnigent &>/dev/null; then - printf "%s Installing omnigent (latest)\n" "$${BOLD}" - uv tool install omnigent - else - printf "%s Upgrading omnigent\n" "$${BOLD}" - uv tool upgrade omnigent || echo "Warning: upgrade failed, continuing with existing version" - fi -else - printf "%s Installing omnigent %s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION}" - uv tool install "omnigent==$${ARG_OMNIGENT_VERSION}" --force-reinstall -fi -export PATH="$${HOME}/.local/bin:$${PATH}" printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)" # Configure client to point to the local server From bd1dbaeedcc8a66b343305bada26cb658b8bf567 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Jun 2026 13:16:26 +0500 Subject: [PATCH 04/16] chore: replace placeholder omnigent icon with official SVG logo --- .icons/omnigent.svg | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/.icons/omnigent.svg b/.icons/omnigent.svg index c65fe35ff..b70b60659 100644 --- a/.icons/omnigent.svg +++ b/.icons/omnigent.svg @@ -1 +1,44 @@ -O + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c337dcc9f5d470a7218049f03ca43b49d111534b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:01:11 +0500 Subject: [PATCH 05/16] fix(coder-labs): install uv before Omnigent setup --- .../modules/omnigent/main.tftest.hcl | 18 +++++++++++++++ .../modules/omnigent/scripts/install.sh.tftpl | 22 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index ccc2e48c3..e17cde796 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -96,6 +96,24 @@ run "test_scripts_output" { } } +run "test_install_script_installs_uv" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.install_script, "https://astral.sh/uv/install.sh") + error_message = "install script should install uv when it is missing" + } + + assert { + condition = strcontains(local.install_script, "command -v uv") + error_message = "install script should check whether uv is available" + } +} + run "test_port_output" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index 948707f12..b042b025b 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -13,12 +13,32 @@ ARG_SERVER_CONFIG_DIR='${ARG_SERVER_CONFIG_DIR}' ARG_AGENTS=$(echo -n '${ARG_AGENTS_B64}' | base64 -d) ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' +export PATH="$${HOME}/.local/bin:$${PATH}" + echo "--------------------------------" printf "omnigent_version: %s\n" "$${ARG_OMNIGENT_VERSION:-latest}" printf "port: %s\n" "$${ARG_PORT}" printf "write_server_config: %s\n" "$${ARG_WRITE_SERVER_CONFIG}" echo "--------------------------------" +if ! command -v curl >/dev/null 2>&1; then + echo "ERROR: curl is required to install uv and Omnigent." >&2 + exit 1 +fi + +if ! command -v uv >/dev/null 2>&1; then + printf "%s Installing uv\n" "$${BOLD}" + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$${HOME}/.local/bin:$${PATH}" +fi + +if ! command -v uv >/dev/null 2>&1; then + echo "ERROR: uv installation failed. Install from https://docs.astral.sh/uv/getting-started/installation/, then rerun." >&2 + exit 1 +fi + +printf "%s Found uv: %s\n" "$${BOLD}" "$(uv --version)" + # Install omnigent via official installer INSTALL_ARGS="--non-interactive" if [ -n "$${ARG_OMNIGENT_VERSION}" ]; then @@ -28,6 +48,8 @@ printf "%s Installing omnigent%s\n" "$${BOLD}" "$${ARG_OMNIGENT_VERSION:+ $${ARG # shellcheck disable=SC2086 curl -fsSL https://omnigent.ai/install.sh | sh -s -- $${INSTALL_ARGS} +export PATH="$${HOME}/.local/bin:$${PATH}" + printf "%s Installed omnigent: %s\n" "$${BOLD}" "$(omnigent --version)" # Configure client to point to the local server From 8293a8dc2745b41c7cbf7af1e703f5b816ad872e Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:09:48 +0500 Subject: [PATCH 06/16] fix(coder-labs): run Omnigent host in background --- registry/coder-labs/modules/omnigent/README.md | 1 + .../modules/omnigent/main.tftest.hcl | 18 ++++++++++++++++++ .../modules/omnigent/scripts/start.sh.tftpl | 11 +++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 89e672a16..64e679f4f 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -123,6 +123,7 @@ Script logs are written to `~/.coder-modules/coder-labs/omnigent/logs/`. If the cat ~/.coder-modules/coder-labs/omnigent/logs/server.log cat ~/.coder-modules/coder-labs/omnigent/logs/start.log cat ~/.coder-modules/coder-labs/omnigent/logs/install.log +cat ~/.coder-modules/coder-labs/omnigent/logs/host.log ``` The health endpoint is available at `http://localhost:/health`. You can check it directly: diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index e17cde796..341703165 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -114,6 +114,24 @@ run "test_install_script_installs_uv" { } } +run "test_start_script_backgrounds_host" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.start_script, "nohup omnigent host") + error_message = "start script should run the Omnigent host in the background" + } + + assert { + condition = strcontains(local.start_script, "host.log") + error_message = "start script should write Omnigent host logs to host.log" + } +} + run "test_port_output" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index f2aae552e..f569fbc6d 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -6,6 +6,7 @@ export PATH="$${HOME}/.local/bin:$${PATH}" MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" START_LOG="$${MODULE_DIR}/logs/start.log" SERVER_LOG="$${MODULE_DIR}/logs/server.log" +HOST_LOG="$${MODULE_DIR}/logs/host.log" ARG_PORT='${ARG_PORT}' ARG_EFFECTIVE_SERVER_CONFIG_PATH='${ARG_EFFECTIVE_SERVER_CONFIG_PATH}' ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' @@ -57,5 +58,11 @@ for i in $(seq 1 90); do sleep 1 done -# Register local workspace as a host -omnigent host "" +# Register local workspace as a host. `omnigent host` stays attached, so run it +# in the background to let the Coder startup script finish. +if ! pgrep -f "[o]mnigent host" >/dev/null 2>&1; then + echo "Starting Omnigent host..." + nohup omnigent host "" >> "$${HOST_LOG}" 2>&1 & +else + echo "Omnigent host already running, skipping start." +fi From 3221045ba52544c41aa144c85d470898ac45a7b8 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 19:22:34 +0500 Subject: [PATCH 07/16] fix(coder-labs): configure Omnigent template with AI Gateway --- .../coder-labs/modules/omnigent/README.md | 31 ++++++----- .../templates/omnigent-workspace/README.md | 35 +++++++------ .../templates/omnigent-workspace/main.tf | 52 ++++++++++--------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 64e679f4f..710189b90 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -35,27 +35,30 @@ module "omnigent" { ### With AI tools (Omnigent + Claude Code + Codex) -Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace: +Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace. This example authenticates Claude Code and Codex through Coder AI Gateway. ```tf -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + + agent_id = coder_agent.main.id + enable_ai_gateway = true } module "claude_code" { - source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + enable_ai_gateway = true +} + +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + + agent_id = coder_agent.main.id } ``` diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 1d00c75ba..21bfc0ab9 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -11,34 +11,37 @@ tags: [docker, omnigent, claude-code, codex, ai, multi-agent] A Docker-based workspace that combines three AI agent modules: - **[Omnigent](https://registry.coder.com/modules/coder-labs/omnigent)** — private multi-agent coding orchestrator server -- **[Claude Code](https://registry.coder.com/modules/coder/claude-code)** — Anthropic's Claude in your terminal -- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI +- **[Claude Code](https://registry.coder.com/modules/coder/claude-code)** — Anthropic's Claude in your terminal, authenticated through Coder AI Gateway +- **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI, authenticated through Coder AI Gateway Each workspace runs its own isolated Omnigent server. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. ```tf -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + + agent_id = coder_agent.main.id + enable_ai_gateway = true } module "claude_code" { - source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + enable_ai_gateway = true +} + +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + + agent_id = coder_agent.main.id } ``` ## Prerequisites - Docker with `sysbox-runc` runtime installed on the Coder host -- `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` set as Coder template variables +- Coder Premium with AI Gateway enabled diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index b72dfaf16..3908881ff 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -18,18 +18,6 @@ data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} data "coder_provisioner" "me" {} -variable "anthropic_api_key" { - description = "Anthropic API key for Claude Code." - type = string - sensitive = true -} - -variable "openai_api_key" { - description = "OpenAI API key for Codex." - type = string - sensitive = true -} - resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch os = "linux" @@ -74,24 +62,38 @@ resource "coder_agent" "main" { } } -module "omnigent" { - source = "registry.coder.com/coder-labs/omnigent/coder" - version = "1.0.0" - agent_id = coder_agent.main.id -} - module "codex" { - source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" - agent_id = coder_agent.main.id - openai_api_key = var.openai_api_key + source = "registry.coder.com/coder-labs/codex/coder" + version = "5.0.0" + + agent_id = coder_agent.main.id + enable_ai_gateway = true } module "claude_code" { - source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + source = "registry.coder.com/coder/claude-code/coder" + version = ">= 4.0.0" + agent_id = coder_agent.main.id - anthropic_api_key = var.anthropic_api_key + enable_ai_gateway = true +} + +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + + agent_id = coder_agent.main.id + + # Omnigent snapshots the host's available tools when the host starts. Wait for + # Claude Code and Codex to install and configure AI Gateway first, otherwise + # the Omnigent UI shows these harnesses as needing setup until the host restarts. + pre_install_script = <<-EOT + #!/bin/bash + set -euo pipefail + coder exp sync want coder-labs-omnigent-ai-tools ${join(" ", concat(module.claude_code.scripts, module.codex.scripts))} + coder exp sync start coder-labs-omnigent-ai-tools + coder exp sync complete coder-labs-omnigent-ai-tools + EOT } resource "docker_volume" "home_volume" { From 88156261b264c1c3cdaea4df5180483555896c0b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 20:47:13 +0500 Subject: [PATCH 08/16] fix(coder-labs): install Omnigent workspace tool dependencies --- .../templates/omnigent-workspace/README.md | 2 ++ .../templates/omnigent-workspace/main.tf | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 21bfc0ab9..ab937c976 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -45,3 +45,5 @@ module "omnigent" { - Docker with `sysbox-runc` runtime installed on the Coder host - Coder Premium with AI Gateway enabled + +The template installs `tmux` and `bubblewrap` before the AI tools start because Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index 3908881ff..d137af0e3 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -18,6 +18,21 @@ data "coder_workspace" "me" {} data "coder_workspace_owner" "me" {} data "coder_provisioner" "me" {} +locals { + ai_tools_pre_install_script = <<-EOT + #!/bin/bash + set -euo pipefail + + if command -v apt-get >/dev/null 2>&1; then + ( + flock 9 + sudo apt-get update + sudo apt-get install -y curl ca-certificates tmux bubblewrap + ) 9>/tmp/coder-ai-tools-apt.lock + fi + EOT +} + resource "coder_agent" "main" { arch = data.coder_provisioner.me.arch os = "linux" @@ -66,16 +81,18 @@ module "codex" { source = "registry.coder.com/coder-labs/codex/coder" version = "5.0.0" - agent_id = coder_agent.main.id - enable_ai_gateway = true + agent_id = coder_agent.main.id + enable_ai_gateway = true + pre_install_script = local.ai_tools_pre_install_script } module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" version = ">= 4.0.0" - agent_id = coder_agent.main.id - enable_ai_gateway = true + agent_id = coder_agent.main.id + enable_ai_gateway = true + pre_install_script = local.ai_tools_pre_install_script } module "omnigent" { From eafdb27035ed865cebf253e3fbe8233d7d31e76b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 20:49:42 +0500 Subject: [PATCH 09/16] fix(coder-labs): install jq for Claude Code setup --- registry/coder-labs/templates/omnigent-workspace/README.md | 2 +- registry/coder-labs/templates/omnigent-workspace/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index ab937c976..1e1355568 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -46,4 +46,4 @@ module "omnigent" { - Docker with `sysbox-runc` runtime installed on the Coder host - Coder Premium with AI Gateway enabled -The template installs `tmux` and `bubblewrap` before the AI tools start because Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. +The template installs `jq`, `tmux`, and `bubblewrap` before the AI tools start because the Claude Code module uses `jq` for setup and Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index d137af0e3..93cf5e111 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -27,7 +27,7 @@ locals { ( flock 9 sudo apt-get update - sudo apt-get install -y curl ca-certificates tmux bubblewrap + sudo apt-get install -y curl ca-certificates jq tmux bubblewrap ) 9>/tmp/coder-ai-tools-apt.lock fi EOT From 3e7972b286f383c75b2c94d8abd99827ccfa38f3 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 23 Jun 2026 22:27:43 +0500 Subject: [PATCH 10/16] fix(coder-labs): use latest AI module versions --- registry/coder-labs/templates/omnigent-workspace/README.md | 4 ++-- registry/coder-labs/templates/omnigent-workspace/main.tf | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 1e1355568..8cef9480b 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -19,7 +19,7 @@ Each workspace runs its own isolated Omnigent server. The admin password is deri ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true @@ -27,7 +27,7 @@ module "codex" { module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index 93cf5e111..a3727f068 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -79,7 +79,7 @@ resource "coder_agent" "main" { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true @@ -88,7 +88,7 @@ module "codex" { module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + version = "5.2.0" agent_id = coder_agent.main.id enable_ai_gateway = true From 77c722c51b71906af13d99e434ff76bea7846909 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 25 Jun 2026 22:25:59 +0500 Subject: [PATCH 11/16] fix(coder-labs): clone Coder repo in Omnigent template --- .../templates/omnigent-workspace/README.md | 16 +++- .../templates/omnigent-workspace/main.tf | 92 +++++++++++++++++-- 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 8cef9480b..5aab64742 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -14,14 +14,25 @@ A Docker-based workspace that combines three AI agent modules: - **[Claude Code](https://registry.coder.com/modules/coder/claude-code)** — Anthropic's Claude in your terminal, authenticated through Coder AI Gateway - **[Codex](https://registry.coder.com/modules/coder-labs/codex)** — OpenAI's Codex CLI, authenticated through Coder AI Gateway -Each workspace runs its own isolated Omnigent server. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. +The template clones `https://github.com/coder/coder` into `/home/coder/workspace/coder` with the [Git Clone](https://registry.coder.com/modules/coder/git-clone) module, then configures Claude Code and Codex to use that repo as their trusted workdir. Each workspace runs its own isolated Omnigent server. The admin password is derived from the workspace ID at runtime and never stored in Terraform state. ```tf +module "git_clone" { + source = "registry.coder.com/coder/git-clone/coder" + version = "2.0.1" + + agent_id = coder_agent.main.id + url = "https://github.com/coder/coder" + base_dir = "/home/coder/workspace" + folder_name = "coder" +} + module "codex" { source = "registry.coder.com/coder-labs/codex/coder" version = "5.2.0" agent_id = coder_agent.main.id + workdir = module.git_clone.repo_dir enable_ai_gateway = true } @@ -30,6 +41,7 @@ module "claude_code" { version = "5.2.0" agent_id = coder_agent.main.id + workdir = module.git_clone.repo_dir enable_ai_gateway = true } @@ -46,4 +58,4 @@ module "omnigent" { - Docker with `sysbox-runc` runtime installed on the Coder host - Coder Premium with AI Gateway enabled -The template installs `jq`, `tmux`, and `bubblewrap` before the AI tools start because the Claude Code module uses `jq` for setup and Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. +The template checks for existing dependencies before installing missing packages. It installs `jq`, `tmux`, `bubblewrap`, and Node.js 22 when needed because the Claude Code module uses `jq` for setup, Codex needs a recent Node.js runtime, and Omnigent launches the Claude Code and Codex harnesses through local terminal sessions. diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index a3727f068..53485db7c 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -19,17 +19,69 @@ data "coder_workspace_owner" "me" {} data "coder_provisioner" "me" {} locals { - ai_tools_pre_install_script = <<-EOT + repo_ready_sync_name = "coder-labs-omnigent-git-clone" + + ai_tools_pre_install_commands = <<-EOT + missing_packages=() + command -v curl >/dev/null 2>&1 || missing_packages+=(curl) + command -v jq >/dev/null 2>&1 || missing_packages+=(jq) + command -v tmux >/dev/null 2>&1 || missing_packages+=(tmux) + command -v bwrap >/dev/null 2>&1 || missing_packages+=(bubblewrap) + + need_node=false + if ! command -v node >/dev/null 2>&1; then + need_node=true + elif ! node -e 'process.exit(Number(process.versions.node.split(".")[0]) >= 22 ? 0 : 1)' >/dev/null 2>&1; then + need_node=true + fi + + if [ "$${#missing_packages[@]}" -eq 0 ] && [ "$${need_node}" = false ]; then + exit 0 + fi + + if ! command -v apt-get >/dev/null 2>&1; then + echo "ERROR: missing required tools and apt-get is not available to install them." >&2 + printf 'Missing packages: %s\n' "$${missing_packages[*]:-none}" >&2 + printf 'Need Node.js 22+: %s\n' "$${need_node}" >&2 + exit 1 + fi + + ( + flock 9 + sudo apt-get update + + if [ "$${need_node}" = true ]; then + if ! command -v curl >/dev/null 2>&1; then + sudo apt-get install -y curl ca-certificates + fi + curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + sudo apt-get install -y nodejs + fi + + if [ "$${#missing_packages[@]}" -gt 0 ]; then + sudo apt-get install -y ca-certificates "$${missing_packages[@]}" + fi + ) 9>/tmp/coder-ai-tools-apt.lock + EOT + + codex_pre_install_script = <<-EOT #!/bin/bash set -euo pipefail + coder exp sync want coder-labs-codex-repo-ready ${local.repo_ready_sync_name} + coder exp sync start coder-labs-codex-repo-ready + coder exp sync complete coder-labs-codex-repo-ready - if command -v apt-get >/dev/null 2>&1; then - ( - flock 9 - sudo apt-get update - sudo apt-get install -y curl ca-certificates jq tmux bubblewrap - ) 9>/tmp/coder-ai-tools-apt.lock - fi + ${local.ai_tools_pre_install_commands} + EOT + + claude_code_pre_install_script = <<-EOT + #!/bin/bash + set -euo pipefail + coder exp sync want coder-claude-code-repo-ready ${local.repo_ready_sync_name} + coder exp sync start coder-claude-code-repo-ready + coder exp sync complete coder-claude-code-repo-ready + + ${local.ai_tools_pre_install_commands} EOT } @@ -77,13 +129,32 @@ resource "coder_agent" "main" { } } +module "git_clone" { + source = "registry.coder.com/coder/git-clone/coder" + version = "2.0.1" + + agent_id = coder_agent.main.id + url = "https://github.com/coder/coder" + base_dir = "/home/coder/workspace" + folder_name = "coder" + extra_args = ["--depth=1"] + + post_clone_script = <<-EOT + #!/bin/bash + set -euo pipefail + coder exp sync start ${local.repo_ready_sync_name} + coder exp sync complete ${local.repo_ready_sync_name} + EOT +} + module "codex" { source = "registry.coder.com/coder-labs/codex/coder" version = "5.2.0" agent_id = coder_agent.main.id + workdir = module.git_clone.repo_dir enable_ai_gateway = true - pre_install_script = local.ai_tools_pre_install_script + pre_install_script = local.codex_pre_install_script } module "claude_code" { @@ -91,8 +162,9 @@ module "claude_code" { version = "5.2.0" agent_id = coder_agent.main.id + workdir = module.git_clone.repo_dir enable_ai_gateway = true - pre_install_script = local.ai_tools_pre_install_script + pre_install_script = local.claude_code_pre_install_script } module "omnigent" { From ae383d06823c8b0f8117a4d01754bfd155eb883e Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Sat, 27 Jun 2026 00:58:32 +0500 Subject: [PATCH 12/16] fix(coder-labs): connect Omnigent host to app server --- .../modules/omnigent/main.tftest.hcl | 18 ++++++++++++++++++ .../modules/omnigent/scripts/start.sh.tftpl | 9 +++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index 341703165..d6d6e4a98 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -132,6 +132,24 @@ run "test_start_script_backgrounds_host" { } } +run "test_start_script_connects_host_to_app_server" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.start_script, "omnigent host --server") + error_message = "start script should connect the host to the Coder app server" + } + + assert { + condition = strcontains(local.start_script, "http://localhost:$${ARG_PORT}") + error_message = "start script should pass the configured Omnigent server port to the host" + } +} + run "test_port_output" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index f569fbc6d..e336f50dc 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -58,11 +58,12 @@ for i in $(seq 1 90); do sleep 1 done -# Register local workspace as a host. `omnigent host` stays attached, so run it -# in the background to let the Coder startup script finish. -if ! pgrep -f "[o]mnigent host" >/dev/null 2>&1; then +# Register local workspace as a host against the Coder app server. Passing an +# empty server URL makes Omnigent spawn and connect to a separate local server, +# leaving the Coder app server with an offline host record for filesystem calls. +if ! pgrep -f "[o]mnigent host.*http://localhost:$${ARG_PORT}" >/dev/null 2>&1; then echo "Starting Omnigent host..." - nohup omnigent host "" >> "$${HOST_LOG}" 2>&1 & + nohup omnigent host --server "http://localhost:$${ARG_PORT}" >> "$${HOST_LOG}" 2>&1 & else echo "Omnigent host already running, skipping start." fi From 2b8a8f8b49e75fd0ea12b54b29f79dc8b1bbbc09 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 26 Jun 2026 20:25:46 +0000 Subject: [PATCH 13/16] fix(coder-labs): trust Coder app origins for Omnigent --- .../coder-labs/modules/omnigent/README.md | 14 ++++++ registry/coder-labs/modules/omnigent/main.tf | 7 +++ .../modules/omnigent/main.tftest.hcl | 39 +++++++++++++++ .../modules/omnigent/scripts/start.sh.tftpl | 47 +++++++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index 710189b90..c71ddc74f 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -33,6 +33,20 @@ module "omnigent" { } ``` +### With additional trusted origins + +The module automatically trusts Coder app origins derived from `CODER_AGENT_URL` and `VSCODE_PROXY_URI` when those environment variables are available. If you expose Omnigent through another reverse proxy, add that browser origin explicitly: + +```tf +module "omnigent" { + source = "registry.coder.com/coder-labs/omnigent/coder" + version = "1.0.0" + agent_id = coder_agent.main.id + + allowed_origins = ["https://omnigent.example.com"] +} +``` + ### With AI tools (Omnigent + Claude Code + Codex) Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace. This example authenticates Claude Code and Codex through Coder AI Gateway. diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf index 7b758a3d1..ebaad4a34 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -30,6 +30,12 @@ variable "port" { } } +variable "allowed_origins" { + description = "Additional trusted browser origins for Omnigent HTTP/WebSocket CSRF checks. Use this when exposing Omnigent through a reverse proxy not covered by the automatic Coder app origin detection." + type = list(string) + default = [] +} + variable "omnigent_version" { description = "Omnigent version to install. 'latest' installs the newest release." type = string @@ -115,6 +121,7 @@ locals { ARG_PORT = tostring(var.port) ARG_EFFECTIVE_SERVER_CONFIG_PATH = local.effective_server_config_path != null ? local.effective_server_config_path : "" ARG_AGENTS_DIR = local.agents_dir + ARG_ALLOWED_ORIGINS = join(",", var.allowed_origins) }) } diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index d6d6e4a98..ea0f4a370 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -50,6 +50,45 @@ run "test_custom_port" { } } +run "test_allowed_origins" { + command = plan + + variables { + agent_id = "test-agent" + allowed_origins = ["https://omnigent--workspace--owner--apps.example.com"] + } + + assert { + condition = contains(var.allowed_origins, "https://omnigent--workspace--owner--apps.example.com") + error_message = "allowed_origins should include the configured origin" + } + + assert { + condition = strcontains(local.start_script, "OMNIGENT_WS_ALLOWED_ORIGINS") + error_message = "start script should export Omnigent's trusted origin allowlist" + } + + assert { + condition = strcontains(local.start_script, "https://omnigent--workspace--owner--apps.example.com") + error_message = "start script should include the configured origin" + } + + assert { + condition = strcontains(local.start_script, "CODER_AGENT_URL") + error_message = "start script should allow the path-based Coder app origin" + } + + assert { + condition = strcontains(local.start_script, "VSCODE_PROXY_URI") + error_message = "start script should derive Coder app origins from VSCODE_PROXY_URI" + } + + assert { + condition = strcontains(local.start_script, "omnigent--") + error_message = "start script should allow the named Omnigent Coder app origin" + } +} + run "test_custom_share" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index e336f50dc..dfe0e81d9 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -10,9 +10,52 @@ HOST_LOG="$${MODULE_DIR}/logs/host.log" ARG_PORT='${ARG_PORT}' ARG_EFFECTIVE_SERVER_CONFIG_PATH='${ARG_EFFECTIVE_SERVER_CONFIG_PATH}' ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' +ARG_ALLOWED_ORIGINS='${ARG_ALLOWED_ORIGINS}' mkdir -p "$${MODULE_DIR}/logs" +append_allowed_origin() { + local origin="$${1:-}" + [ -n "$${origin}" ] || return 0 + case ",$${OMNIGENT_ALLOWED_ORIGINS}," in + *",$${origin},"*) ;; + *) + if [ -n "$${OMNIGENT_ALLOWED_ORIGINS}" ]; then + OMNIGENT_ALLOWED_ORIGINS="$${OMNIGENT_ALLOWED_ORIGINS},$${origin}" + else + OMNIGENT_ALLOWED_ORIGINS="$${origin}" + fi + ;; + esac +} + +origin_from_url() { + local url="$${1:-}" + case "$${url}" in + http://*|https://*) + printf '%s\n' "$${url}" | sed -E 's#^(https?://[^/]+).*#\1#' + ;; + esac +} + +OMNIGENT_ALLOWED_ORIGINS="$${ARG_ALLOWED_ORIGINS}" +append_allowed_origin "$(origin_from_url "$${CODER_AGENT_URL:-}")" + +# Coder app requests arrive with the browser Origin set to the app proxy URL. +# Trust both the named app URL and the direct port-forward URL when Coder exposes +# VSCODE_PROXY_URI in the workspace environment. +if [ -n "$${VSCODE_PROXY_URI:-}" ]; then + port_proxy_url="$${VSCODE_PROXY_URI//\{\{port\}\}/$${ARG_PORT}}" + append_allowed_origin "$(origin_from_url "$${port_proxy_url}")" + + if [ -n "$${CODER_WORKSPACE_AGENT_NAME:-}" ]; then + named_app_url="$${VSCODE_PROXY_URI//\{\{port\}\}--$${CODER_WORKSPACE_AGENT_NAME}--/omnigent--}" + if [ "$${named_app_url}" != "$${VSCODE_PROXY_URI}" ]; then + append_allowed_origin "$(origin_from_url "$${named_app_url}")" + fi + fi +fi + # Derive a stable admin password from the workspace ID (first 16 hex chars) OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1-16) @@ -38,6 +81,10 @@ if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then fi export OMNIGENT_ACCOUNTS_INIT_ADMIN_PASSWORD="$${OMNIGENT_ADMIN_PASSWORD}" + if [ -n "$${OMNIGENT_ALLOWED_ORIGINS}" ]; then + export OMNIGENT_WS_ALLOWED_ORIGINS="$${OMNIGENT_ALLOWED_ORIGINS}" + echo "Allowing Omnigent browser origins: $${OMNIGENT_WS_ALLOWED_ORIGINS}" + fi # shellcheck disable=SC2086 nohup omnigent server $${SERVER_FLAGS} >> "$${SERVER_LOG}" 2>&1 & else From f24aa6cfed90dd4d569bf27bd91cfec9cbf77216 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 26 Jun 2026 22:45:32 +0000 Subject: [PATCH 14/16] fix(coder-labs): harden Omnigent startup and agent config handling --- registry/coder-labs/modules/omnigent/main.tf | 19 +++++- .../modules/omnigent/main.tftest.hcl | 62 ++++++++++++++++++- .../modules/omnigent/scripts/install.sh.tftpl | 14 +++-- .../modules/omnigent/scripts/start.sh.tftpl | 12 ++-- 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/main.tf b/registry/coder-labs/modules/omnigent/main.tf index ebaad4a34..82880158c 100644 --- a/registry/coder-labs/modules/omnigent/main.tf +++ b/registry/coder-labs/modules/omnigent/main.tf @@ -34,6 +34,12 @@ variable "allowed_origins" { description = "Additional trusted browser origins for Omnigent HTTP/WebSocket CSRF checks. Use this when exposing Omnigent through a reverse proxy not covered by the automatic Coder app origin detection." type = list(string) default = [] + validation { + condition = alltrue([ + for origin in var.allowed_origins : trimspace(origin) == origin && can(regex("^https?://[^/?#,[:space:]]+$", origin)) + ]) + error_message = "allowed_origins entries must be origins like https://omnigent.example.com (scheme, host, optional port; no path)." + } } variable "omnigent_version" { @@ -81,6 +87,17 @@ variable "agents" { content = string })) default = [] + validation { + condition = alltrue([ + for agent in var.agents : ( + length(trimspace(agent.name)) > 0 && + !strcontains(agent.name, "\t") && + !strcontains(agent.name, "\n") && + !strcontains(agent.name, "\r") + ) + ]) + error_message = "agents entries must have a non-empty name without tab or newline characters." + } } variable "pre_install_script" { @@ -121,7 +138,7 @@ locals { ARG_PORT = tostring(var.port) ARG_EFFECTIVE_SERVER_CONFIG_PATH = local.effective_server_config_path != null ? local.effective_server_config_path : "" ARG_AGENTS_DIR = local.agents_dir - ARG_ALLOWED_ORIGINS = join(",", var.allowed_origins) + ARG_ALLOWED_ORIGINS_B64 = base64encode(join(",", var.allowed_origins)) }) } diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index ea0f4a370..705f725e4 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -69,8 +69,8 @@ run "test_allowed_origins" { } assert { - condition = strcontains(local.start_script, "https://omnigent--workspace--owner--apps.example.com") - error_message = "start script should include the configured origin" + condition = strcontains(local.start_script, base64encode("https://omnigent--workspace--owner--apps.example.com")) + error_message = "start script should include the encoded configured origin" } assert { @@ -89,6 +89,17 @@ run "test_allowed_origins" { } } +run "test_invalid_allowed_origin_path" { + command = plan + + variables { + agent_id = "test-agent" + allowed_origins = ["https://omnigent.example.com/path"] + } + + expect_failures = [var.allowed_origins] +} + run "test_custom_share" { command = plan @@ -153,6 +164,19 @@ run "test_install_script_installs_uv" { } } +run "test_install_script_clears_stale_agents" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.install_script, "rm -f \"$${ARG_AGENTS_DIR}\"/*.yaml") + error_message = "install script should clear stale generated agent YAML files" + } +} + run "test_start_script_backgrounds_host" { command = plan @@ -171,6 +195,24 @@ run "test_start_script_backgrounds_host" { } } +run "test_start_script_quotes_server_flags" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.start_script, "SERVER_FLAGS=(") + error_message = "start script should build server flags as a bash array" + } + + assert { + condition = strcontains(local.start_script, "omnigent server \"$${SERVER_FLAGS[@]}\"") + error_message = "start script should pass server flags without word splitting" + } +} + run "test_start_script_connects_host_to_app_server" { command = plan @@ -276,6 +318,22 @@ run "test_server_config_mutual_exclusion" { expect_failures = [var.server_config] } +run "test_invalid_agent_name" { + command = plan + + variables { + agent_id = "test-agent" + agents = [ + { + name = "bad\tname" + content = "name: reviewer\ninstructions: You are a reviewer." + } + ] + } + + expect_failures = [var.agents] +} + run "test_agents" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl index b042b025b..e0df64911 100644 --- a/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/install.sh.tftpl @@ -59,15 +59,19 @@ omnigent config set server=http://localhost:$${ARG_PORT} if [ "$${ARG_WRITE_SERVER_CONFIG}" = "true" ]; then mkdir -p "$${ARG_SERVER_CONFIG_DIR}" printf "%s Writing server config to %s\n" "$${BOLD}" "$${ARG_SERVER_CONFIG_FILE}" - echo "$${ARG_SERVER_CONFIG}" > "$${ARG_SERVER_CONFIG_FILE}" + printf '%s\n' "$${ARG_SERVER_CONFIG}" > "$${ARG_SERVER_CONFIG_FILE}" fi -# Write agent YAML files +# Write agent YAML files. Clear stale generated files first so removed agents +# are not re-registered on the next start. +mkdir -p "$${ARG_AGENTS_DIR}" +rm -f "$${ARG_AGENTS_DIR}"/*.yaml if [ -n "$${ARG_AGENTS}" ]; then - mkdir -p "$${ARG_AGENTS_DIR}" - while IFS=$'\t' read -r agent_name agent_content_b64; do + agent_index=0 + while IFS=$' ' read -r agent_name agent_content_b64; do [ -z "$${agent_name}" ] && continue - agent_file="$${ARG_AGENTS_DIR}/$${agent_name}.yaml" + agent_index=$((agent_index + 1)) + agent_file="$${ARG_AGENTS_DIR}/agent-$${agent_index}.yaml" printf "%s Writing agent: %s -> %s\n" "$${BOLD}" "$${agent_name}" "$${agent_file}" echo -n "$${agent_content_b64}" | base64 -d > "$${agent_file}" done <<< "$${ARG_AGENTS}" diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index dfe0e81d9..1a6627101 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -4,13 +4,12 @@ set -euo pipefail export PATH="$${HOME}/.local/bin:$${PATH}" MODULE_DIR="$${HOME}/.coder-modules/coder-labs/omnigent" -START_LOG="$${MODULE_DIR}/logs/start.log" SERVER_LOG="$${MODULE_DIR}/logs/server.log" HOST_LOG="$${MODULE_DIR}/logs/host.log" ARG_PORT='${ARG_PORT}' ARG_EFFECTIVE_SERVER_CONFIG_PATH='${ARG_EFFECTIVE_SERVER_CONFIG_PATH}' ARG_AGENTS_DIR='${ARG_AGENTS_DIR}' -ARG_ALLOWED_ORIGINS='${ARG_ALLOWED_ORIGINS}' +ARG_ALLOWED_ORIGINS=$(echo -n '${ARG_ALLOWED_ORIGINS_B64}' | base64 -d) mkdir -p "$${MODULE_DIR}/logs" @@ -63,11 +62,11 @@ if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then echo "Starting Omnigent server on port $${ARG_PORT}..." # Build server flags - SERVER_FLAGS="--host 127.0.0.1 --port $${ARG_PORT} --no-open" + SERVER_FLAGS=(--host 127.0.0.1 --port "$${ARG_PORT}" --no-open) # Add config file if set and present if [ -n "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ] && [ -f "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" ]; then - SERVER_FLAGS="$${SERVER_FLAGS} -c $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" + SERVER_FLAGS+=(-c "$${ARG_EFFECTIVE_SERVER_CONFIG_PATH}") echo "Using server config: $${ARG_EFFECTIVE_SERVER_CONFIG_PATH}" fi @@ -75,7 +74,7 @@ if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then if [ -d "$${ARG_AGENTS_DIR}" ]; then for agent_file in "$${ARG_AGENTS_DIR}"/*.yaml; do [ -f "$${agent_file}" ] || continue - SERVER_FLAGS="$${SERVER_FLAGS} --agent $${agent_file}" + SERVER_FLAGS+=(--agent "$${agent_file}") echo "Registering agent: $${agent_file}" done fi @@ -85,8 +84,7 @@ if ! curl -sf "http://localhost:$${ARG_PORT}/health" &>/dev/null; then export OMNIGENT_WS_ALLOWED_ORIGINS="$${OMNIGENT_ALLOWED_ORIGINS}" echo "Allowing Omnigent browser origins: $${OMNIGENT_WS_ALLOWED_ORIGINS}" fi - # shellcheck disable=SC2086 - nohup omnigent server $${SERVER_FLAGS} >> "$${SERVER_LOG}" 2>&1 & + nohup omnigent server "$${SERVER_FLAGS[@]}" >> "$${SERVER_LOG}" 2>&1 & else echo "Omnigent server already running on port $${ARG_PORT}, skipping start." fi From 49d3d25d558c075010553f7c9a8b4b06f59f372b Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 26 Jun 2026 22:45:32 +0000 Subject: [PATCH 15/16] fix(coder-labs): use Codex 5.2.1 in Omnigent template --- .../coder-labs/modules/omnigent/README.md | 19 +++++++++++++++++-- .../templates/omnigent-workspace/README.md | 2 +- .../templates/omnigent-workspace/main.tf | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/registry/coder-labs/modules/omnigent/README.md b/registry/coder-labs/modules/omnigent/README.md index c71ddc74f..5e640c75b 100644 --- a/registry/coder-labs/modules/omnigent/README.md +++ b/registry/coder-labs/modules/omnigent/README.md @@ -52,19 +52,25 @@ module "omnigent" { Compose Omnigent alongside other AI agent modules to create a full multi-agent workspace. This example authenticates Claude Code and Codex through Coder AI Gateway. ```tf +locals { + workdir = "/home/coder/project" +} + module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.2.1" agent_id = coder_agent.main.id + workdir = local.workdir enable_ai_gateway = true } module "claude_code" { source = "registry.coder.com/coder/claude-code/coder" - version = ">= 4.0.0" + version = "5.2.0" agent_id = coder_agent.main.id + workdir = local.workdir enable_ai_gateway = true } @@ -73,6 +79,15 @@ module "omnigent" { version = "1.0.0" agent_id = coder_agent.main.id + + # Wait for Claude Code and Codex setup before Omnigent snapshots host tools. + pre_install_script = <<-EOT + #!/bin/bash + set -euo pipefail + coder exp sync want coder-labs-omnigent-ai-tools ${join(" ", concat(module.claude_code.scripts, module.codex.scripts))} + coder exp sync start coder-labs-omnigent-ai-tools + coder exp sync complete coder-labs-omnigent-ai-tools + EOT } ``` diff --git a/registry/coder-labs/templates/omnigent-workspace/README.md b/registry/coder-labs/templates/omnigent-workspace/README.md index 5aab64742..82e5611e7 100644 --- a/registry/coder-labs/templates/omnigent-workspace/README.md +++ b/registry/coder-labs/templates/omnigent-workspace/README.md @@ -29,7 +29,7 @@ module "git_clone" { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.2.1" agent_id = coder_agent.main.id workdir = module.git_clone.repo_dir diff --git a/registry/coder-labs/templates/omnigent-workspace/main.tf b/registry/coder-labs/templates/omnigent-workspace/main.tf index 53485db7c..d1ba40a79 100644 --- a/registry/coder-labs/templates/omnigent-workspace/main.tf +++ b/registry/coder-labs/templates/omnigent-workspace/main.tf @@ -149,7 +149,7 @@ module "git_clone" { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.2.1" agent_id = coder_agent.main.id workdir = module.git_clone.repo_dir From 92a428eac53100145977814a0bb79cb1578273b4 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Fri, 26 Jun 2026 23:22:56 +0000 Subject: [PATCH 16/16] fix(coder-labs): pass AI Gateway token to Omnigent runners --- .../modules/omnigent/main.tftest.hcl | 28 +++++++++++++++++++ .../modules/omnigent/scripts/start.sh.tftpl | 22 +++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/registry/coder-labs/modules/omnigent/main.tftest.hcl b/registry/coder-labs/modules/omnigent/main.tftest.hcl index 705f725e4..9eb7364a9 100644 --- a/registry/coder-labs/modules/omnigent/main.tftest.hcl +++ b/registry/coder-labs/modules/omnigent/main.tftest.hcl @@ -231,6 +231,34 @@ run "test_start_script_connects_host_to_app_server" { } } +run "test_start_script_passes_ai_gateway_token_to_runners" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = strcontains(local.start_script, "append_runner_env_passthrough") + error_message = "start script should append runner env passthrough entries" + } + + assert { + condition = strcontains(local.start_script, "OPENAI_CODER_AIGATEWAY_SESSION_TOKEN") + error_message = "start script should pass the Coder AI Gateway OpenAI token to Omnigent runners" + } + + assert { + condition = strcontains(local.start_script, "OMNIGENT_RUNNER_ENV_PASSTHROUGH=\"$${OMNIGENT_RUNNER_ENV_PASSTHROUGH},$${name}\"") + error_message = "start script should preserve existing runner env passthrough values" + } + + assert { + condition = strcontains(local.start_script, "*\",$${name},\"*) ;;") + error_message = "start script should avoid duplicate runner env passthrough entries" + } +} + run "test_port_output" { command = plan diff --git a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl index 1a6627101..89ba7a335 100644 --- a/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl +++ b/registry/coder-labs/modules/omnigent/scripts/start.sh.tftpl @@ -37,6 +37,23 @@ origin_from_url() { esac } +append_runner_env_passthrough() { + local name="$${1:-}" + [ -n "$${name}" ] || return 0 + [ -n "$${!name:-}" ] || return 0 + case ",$${OMNIGENT_RUNNER_ENV_PASSTHROUGH:-}," in + *",$${name},"*) ;; + *) + if [ -n "$${OMNIGENT_RUNNER_ENV_PASSTHROUGH:-}" ]; then + OMNIGENT_RUNNER_ENV_PASSTHROUGH="$${OMNIGENT_RUNNER_ENV_PASSTHROUGH},$${name}" + else + OMNIGENT_RUNNER_ENV_PASSTHROUGH="$${name}" + fi + ;; + esac + export OMNIGENT_RUNNER_ENV_PASSTHROUGH +} + OMNIGENT_ALLOWED_ORIGINS="$${ARG_ALLOWED_ORIGINS}" append_allowed_origin "$(origin_from_url "$${CODER_AGENT_URL:-}")" @@ -55,6 +72,11 @@ if [ -n "$${VSCODE_PROXY_URI:-}" ]; then fi fi +# Omnigent host runners only receive a limited environment allowlist. Include +# Coder AI Gateway's OpenAI token when the workspace exposes it so Codex can +# authenticate from sessions launched through Omnigent. +append_runner_env_passthrough "OPENAI_CODER_AIGATEWAY_SESSION_TOKEN" + # Derive a stable admin password from the workspace ID (first 16 hex chars) OMNIGENT_ADMIN_PASSWORD=$(echo -n "$${CODER_WORKSPACE_ID}" | tr -d '-' | cut -c1-16)