diff --git a/registry/coder-labs/modules/copilot/README.md b/registry/coder-labs/modules/copilot/README.md index 7c0e56935..7005e1ca3 100644 --- a/registry/coder-labs/modules/copilot/README.md +++ b/registry/coder-labs/modules/copilot/README.md @@ -13,12 +13,15 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.4.0" + version = "0.5.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" } ``` +> [!WARNING] +> **Security Notice**: This module runs Copilot with `--allow-all` by default, which enables all permissions (equivalent to `--allow-all-tools --allow-all-paths --allow-all-urls`). This bypasses permission prompts and allows Copilot unrestricted access to tools, file paths, and URLs. Use this module _only_ in trusted environments. + > [!IMPORTANT] > This example assumes you have [Coder external authentication](https://coder.com/docs/admin/external-auth) configured with `id = "github"`. If not, you can provide a direct token using the `github_token` variable or provide the correct external authentication id for GitHub by setting `external_auth_id = "my-github"`. @@ -51,7 +54,7 @@ data "coder_parameter" "ai_prompt" { module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.4.0" + version = "0.5.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -71,7 +74,7 @@ Customize tool permissions, MCP servers, and Copilot settings: ```tf module "copilot" { source = "registry.coder.com/coder-labs/copilot/coder" - version = "0.4.0" + version = "0.5.0" agent_id = coder_agent.example.id workdir = "/home/coder/projects" @@ -215,6 +218,19 @@ By default, the module resumes the latest Copilot session when the workspace res > [!NOTE] > Session resumption requires persistent storage for the home directory or workspace volume. Without persistent storage, sessions will not resume across workspace restarts. +## State Persistence + +AgentAPI can save and restore its conversation state to disk across workspace restarts. This complements `resume_session` (which resumes the Copilot CLI session) by also preserving the AgentAPI-level context. Enabled by default, requires agentapi >= v0.12.0 (older versions skip it with a warning). + +To disable: + +```tf +module "copilot" { + # ... other config + enable_state_persistence = false +} +``` + ## Troubleshooting If you encounter any issues, check the log files in the `~/.copilot-module` directory within your workspace for detailed information. diff --git a/registry/coder-labs/modules/copilot/copilot.tftest.hcl b/registry/coder-labs/modules/copilot/copilot.tftest.hcl index 0ff2379a0..9fdf93708 100644 --- a/registry/coder-labs/modules/copilot/copilot.tftest.hcl +++ b/registry/coder-labs/modules/copilot/copilot.tftest.hcl @@ -347,3 +347,32 @@ run "aibridge_proxy_with_copilot_config" { error_message = "copilot_model environment variable should be set alongside proxy" } } + +run "enable_state_persistence_default" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder" + } + + assert { + condition = var.enable_state_persistence == true + error_message = "enable_state_persistence should default to true" + } +} + +run "disable_state_persistence" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder" + enable_state_persistence = false + } + + assert { + condition = var.enable_state_persistence == false + error_message = "enable_state_persistence should be false when explicitly disabled" + } +} diff --git a/registry/coder-labs/modules/copilot/main.tf b/registry/coder-labs/modules/copilot/main.tf index 2837961f5..9194fa993 100644 --- a/registry/coder-labs/modules/copilot/main.tf +++ b/registry/coder-labs/modules/copilot/main.tf @@ -119,6 +119,12 @@ variable "subdomain" { default = false } +variable "enable_state_persistence" { + type = bool + description = "Enable AgentAPI conversation state persistence across restarts." + default = true +} + variable "order" { type = number description = "The order determines the position of app in the UI presentation." @@ -155,6 +161,12 @@ variable "cli_app_display_name" { default = "Copilot" } +variable "allow_all" { + type = bool + description = "Allow all tools without prompting (equivalent to --allow-all)." + default = true +} + variable "resume_session" { type = bool description = "Whether to automatically resume the latest Copilot session on workspace restart." @@ -271,25 +283,26 @@ resource "coder_env" "github_token" { module "agentapi" { source = "registry.coder.com/coder/agentapi/coder" - version = "2.0.0" - - agent_id = var.agent_id - folder = local.workdir - web_app_slug = local.app_slug - web_app_order = var.order - web_app_group = var.group - web_app_icon = var.icon - web_app_display_name = var.web_app_display_name - cli_app = var.cli_app - cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null - cli_app_icon = var.cli_app ? var.icon : null - cli_app_display_name = var.cli_app ? var.cli_app_display_name : null - agentapi_subdomain = var.subdomain - module_dir_name = local.module_dir_name - install_agentapi = var.install_agentapi - agentapi_version = var.agentapi_version - pre_install_script = var.pre_install_script - post_install_script = var.post_install_script + version = "2.2.0" + + agent_id = var.agent_id + folder = local.workdir + web_app_slug = local.app_slug + web_app_order = var.order + web_app_group = var.group + web_app_icon = var.icon + web_app_display_name = var.web_app_display_name + cli_app = var.cli_app + cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null + cli_app_icon = var.cli_app ? var.icon : null + cli_app_display_name = var.cli_app ? var.cli_app_display_name : null + agentapi_subdomain = var.subdomain + module_dir_name = local.module_dir_name + install_agentapi = var.install_agentapi + agentapi_version = var.agentapi_version + enable_state_persistence = var.enable_state_persistence + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script start_script = <<-EOT #!/bin/bash @@ -299,6 +312,7 @@ module "agentapi" { chmod +x /tmp/start.sh ARG_WORKDIR='${local.workdir}' \ + ARG_ALLOW_ALL='${var.allow_all}' \ ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ ARG_SYSTEM_PROMPT='${base64encode(local.final_system_prompt)}' \ ARG_COPILOT_MODEL='${var.copilot_model}' \ diff --git a/registry/coder-labs/modules/copilot/scripts/install.sh b/registry/coder-labs/modules/copilot/scripts/install.sh index 44c480e3a..4609376c3 100644 --- a/registry/coder-labs/modules/copilot/scripts/install.sh +++ b/registry/coder-labs/modules/copilot/scripts/install.sh @@ -1,11 +1,9 @@ #!/bin/bash -if [ -f "$HOME/.bashrc" ]; then - source "$HOME"/.bashrc -fi - set -euo pipefail +export PATH="$HOME/.local/bin:$PATH" + command_exists() { command -v "$1" > /dev/null 2>&1 } @@ -19,34 +17,13 @@ ARG_EXTERNAL_AUTH_ID=${ARG_EXTERNAL_AUTH_ID:-github} ARG_COPILOT_VERSION=${ARG_COPILOT_VERSION:-0.0.334} ARG_COPILOT_MODEL=${ARG_COPILOT_MODEL:-claude-sonnet-4.5} -validate_prerequisites() { - if ! command_exists node; then - echo "ERROR: Node.js not found. Copilot requires Node.js v22+." - echo "Install with: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs" - exit 1 - fi - - if ! command_exists npm; then - echo "ERROR: npm not found. Copilot requires npm v10+." - exit 1 - fi - - node_version=$(node --version | sed 's/v//' | cut -d. -f1) - if [ "$node_version" -lt 22 ]; then - echo "WARNING: Node.js v$node_version detected. Copilot requires v22+." - fi -} - install_copilot() { if ! command_exists copilot; then echo "Installing GitHub Copilot CLI (version: ${ARG_COPILOT_VERSION})..." - if [ "$ARG_COPILOT_VERSION" = "latest" ]; then - npm install -g @github/copilot - else - npm install -g "@github/copilot@${ARG_COPILOT_VERSION}" - fi + curl -fsSL https://gh.io/copilot-install | VERSION="${ARG_COPILOT_VERSION}" bash if ! command_exists copilot; then + echo "PATH after installation: $PATH" echo "ERROR: Failed to install Copilot" exit 1 fi @@ -95,7 +72,7 @@ setup_copilot_configurations() { } setup_copilot_config() { - export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" + export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME}" local copilot_config_dir="$XDG_CONFIG_HOME/.copilot" local copilot_config_file="$copilot_config_dir/config.json" local mcp_config_file="$copilot_config_dir/mcp-config.json" @@ -190,27 +167,15 @@ add_custom_mcp_servers() { local updated_config updated_config=$(jq --argjson custom "$custom_servers" '.mcpServers += $custom' "$mcp_config_file") echo "$updated_config" > "$mcp_config_file" - elif command_exists node; then - node -e " - const fs = require('fs'); - const existing = JSON.parse(fs.readFileSync('$mcp_config_file', 'utf8')); - const input = JSON.parse(\`$ARG_MCP_CONFIG\`); - const custom = input.mcpServers || {}; - existing.mcpServers = {...existing.mcpServers, ...custom}; - fs.writeFileSync('$mcp_config_file', JSON.stringify(existing, null, 2)); - " else - echo "WARNING: jq and node not available, cannot merge custom MCP servers" + echo "WARNING: jq not available, cannot merge custom MCP servers" fi } configure_copilot_model() { - if [ -n "$ARG_COPILOT_MODEL" ] && [ "$ARG_COPILOT_MODEL" != "claude-sonnet-4.5" ]; then - echo "Setting Copilot model to: $ARG_COPILOT_MODEL" - copilot config model "$ARG_COPILOT_MODEL" || { - echo "WARNING: Failed to set model via copilot config, will use environment variable fallback" - export COPILOT_MODEL="$ARG_COPILOT_MODEL" - } + if [[ -n "${ARG_COPILOT_MODEL}" ]]; then + echo "Setting Copilot model to: ${ARG_COPILOT_MODEL}" + export COPILOT_MODEL="${ARG_COPILOT_MODEL}" fi } @@ -227,7 +192,6 @@ configure_coder_integration() { fi } -validate_prerequisites install_copilot check_github_authentication setup_copilot_configurations diff --git a/registry/coder-labs/modules/copilot/scripts/start.sh b/registry/coder-labs/modules/copilot/scripts/start.sh index 0aecb1feb..7c5d21c5b 100644 --- a/registry/coder-labs/modules/copilot/scripts/start.sh +++ b/registry/coder-labs/modules/copilot/scripts/start.sh @@ -1,9 +1,5 @@ #!/bin/bash -if [ -f "$HOME/.bashrc" ]; then - source "$HOME"/.bashrc -fi - set -euo pipefail export PATH="$HOME/.local/bin:$PATH" @@ -13,6 +9,7 @@ command_exists() { } ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"} +ARG_ALLOW_ALL=${ARG_ALLOW_ALL:-true} ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d 2> /dev/null || echo "") ARG_SYSTEM_PROMPT=$(echo -n "${ARG_SYSTEM_PROMPT:-}" | base64 -d 2> /dev/null || echo "") ARG_COPILOT_MODEL=${ARG_COPILOT_MODEL:-} @@ -28,7 +25,7 @@ ARG_AIBRIDGE_PROXY_CERT_PATH=${ARG_AIBRIDGE_PROXY_CERT_PATH:-} validate_copilot_installation() { if ! command_exists copilot; then - echo "ERROR: Copilot not installed. Run: npm install -g @github/copilot" + echo "ERROR: Copilot not installed or not in PATH. Please ensure Copilot CLI is installed and accessible." exit 1 fi } @@ -73,6 +70,20 @@ build_copilot_args() { fi done fi + + if [ "$ARG_ALLOW_ALL" = "true" ]; then + COPILOT_ARGS+=(--allow-all) + fi + + if check_existing_session; then + COPILOT_ARGS+=(--continue) + else + local initial_prompt + initial_prompt=$(build_initial_prompt) + if [[ -n "${initial_prompt}" ]]; then + COPILOT_ARGS+=(-i "${initial_prompt}") + fi + fi } check_existing_session() { @@ -169,35 +180,11 @@ start_agentapi() { build_copilot_args - if check_existing_session; then - echo "Continuing latest Copilot session..." - if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then - echo "Copilot arguments: ${COPILOT_ARGS[*]}" - agentapi server --type copilot --term-width 120 --term-height 40 -- copilot --continue "${COPILOT_ARGS[@]}" - else - agentapi server --type copilot --term-width 120 --term-height 40 -- copilot --continue - fi + if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then + echo "Copilot arguments: ${COPILOT_ARGS[*]}" + agentapi server --type copilot --term-width 67 --term-height 1190 -- copilot "${COPILOT_ARGS[@]}" else - echo "Starting new Copilot session..." - local initial_prompt - initial_prompt=$(build_initial_prompt) - - if [ -n "$initial_prompt" ]; then - echo "Using initial prompt with system context" - if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then - echo "Copilot arguments: ${COPILOT_ARGS[*]}" - agentapi server -I="$initial_prompt" --type copilot --term-width 120 --term-height 40 -- copilot "${COPILOT_ARGS[@]}" - else - agentapi server -I="$initial_prompt" --type copilot --term-width 120 --term-height 40 -- copilot - fi - else - if [ ${#COPILOT_ARGS[@]} -gt 0 ]; then - echo "Copilot arguments: ${COPILOT_ARGS[*]}" - agentapi server --type copilot --term-width 120 --term-height 40 -- copilot "${COPILOT_ARGS[@]}" - else - agentapi server --type copilot --term-width 120 --term-height 40 -- copilot - fi - fi + agentapi server --type copilot --term-width 67 --term-height 1190 -- copilot fi }