diff --git a/bun.lockb b/bun.lockb index 7f379c9d..6ec83ca9 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 733230db..8ea769ca 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "devDependencies": { "@types/bun": "^1.2.9", "bun-types": "^1.1.23", + "dedent": "^1.6.0", "gray-matter": "^4.0.3", "marked": "^12.0.2", "prettier": "^3.3.3", diff --git a/registry/coder/modules/goose/README.md b/registry/coder/modules/goose/README.md index 268b1d00..26fbba8b 100644 --- a/registry/coder/modules/goose/README.md +++ b/registry/coder/modules/goose/README.md @@ -4,7 +4,7 @@ description: Run Goose in your workspace icon: ../../../../.icons/goose.svg maintainer_github: coder verified: true -tags: [agent, goose, ai] +tags: [agent, goose, ai, tasks] --- # Goose @@ -13,36 +13,26 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener ```tf module "goose" { - source = "registry.coder.com/coder/goose/coder" - version = "1.3.0" - agent_id = coder_agent.example.id - folder = "/home/coder" - install_goose = true - goose_version = "v1.0.16" + source = "registry.coder.com/coder/goose/coder" + version = "2.0.0" + agent_id = coder_agent.example.id + folder = "/home/coder" + install_goose = true + goose_version = "v1.0.31" + goose_provider = "anthropic" + goose_model = "claude-3-5-sonnet-latest" } ``` ## Prerequisites -- `screen` or `tmux` must be installed in your workspace to run Goose in the background - You must add the [Coder Login](https://registry.coder.com/modules/coder-login) module to your template The `codercom/oss-dogfood:latest` container image can be used for testing on container-based workspaces. ## Examples -Your workspace must have `screen` or `tmux` installed to use the background session functionality. - -### Run in the background and report tasks (Experimental) - -> This functionality is in early access as of Coder v2.21 and is still evolving. -> For now, we recommend testing it in a demo or staging environment, -> rather than deploying to production -> -> Learn more in [the Coder documentation](https://coder.com/docs/tutorials/ai-agents) -> -> Join our [Discord channel](https://discord.gg/coder) or -> [contact us](https://coder.com/contact) to get help or share feedback. +### Run in the background and report tasks ```tf module "coder-login" { @@ -81,7 +71,6 @@ resource "coder_agent" "main" { EOT GOOSE_TASK_PROMPT = data.coder_parameter.ai_prompt.value - # An API key is required for experiment_auto_configure # See https://block.github.io/goose/docs/getting-started/providers ANTHROPIC_API_KEY = var.anthropic_api_key # or use a coder_parameter } @@ -90,28 +79,14 @@ resource "coder_agent" "main" { module "goose" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/goose/coder" - version = "1.3.0" + version = "2.0.0" agent_id = coder_agent.example.id folder = "/home/coder" install_goose = true - goose_version = "v1.0.16" - - # Enable experimental features - experiment_report_tasks = true - - # Run Goose in the background with screen (pick one: screen or tmux) - experiment_use_screen = true - # experiment_use_tmux = true # Alternative: use tmux instead of screen - - # Optional: customize the session name (defaults to "goose") - # session_name = "goose-session" - - # Avoid configuring Goose manually - experiment_auto_configure = true + goose_version = "v1.0.31" - # Required for experiment_auto_configure - experiment_goose_provider = "anthropic" - experiment_goose_model = "claude-3-5-sonnet-latest" + goose_provider = "anthropic" + goose_model = "claude-3-5-sonnet-latest" } ``` @@ -123,11 +98,11 @@ You can extend Goose's capabilities by adding custom extensions. For example, to module "goose" { # ... other configuration ... - experiment_pre_install_script = <<-EOT + pre_install_script = <<-EOT npm i -g @wonderwhy-er/desktop-commander@latest EOT - experiment_additional_extensions = <<-EOT + additional_extensions = <<-EOT desktop-commander: args: [] cmd: desktop-commander @@ -145,20 +120,6 @@ This will add the desktop-commander extension to Goose, allowing it to run comma Note: The indentation in the heredoc is preserved, so you can write the YAML naturally. -## Run standalone +## Troubleshooting -Run Goose as a standalone app in your workspace. This will install Goose and run it directly without using screen or tmux, and without any task reporting to the Coder UI. - -```tf -module "goose" { - source = "registry.coder.com/coder/goose/coder" - version = "1.3.0" - agent_id = coder_agent.example.id - folder = "/home/coder" - install_goose = true - goose_version = "v1.0.16" - - # Icon is not available in Coder v2.20 and below, so we'll use a custom icon URL - icon = "https://raw.githubusercontent.com/block/goose/refs/heads/main/ui/desktop/src/images/icon.svg" -} -``` +The module will create log files in the workspace's `~/.goose-module` directory. If you run into any issues, look at them for more information. diff --git a/registry/coder/modules/goose/main.test.ts b/registry/coder/modules/goose/main.test.ts new file mode 100644 index 00000000..bbf8b262 --- /dev/null +++ b/registry/coder/modules/goose/main.test.ts @@ -0,0 +1,254 @@ +import { + test, + afterEach, + describe, + setDefaultTimeout, + beforeAll, + expect, +} from "bun:test"; +import { execContainer, readFileContainer, runTerraformInit } from "~test"; +import { + loadTestFile, + writeExecutable, + setup as setupUtil, + execModuleScript, + expectAgentAPIStarted, +} from "../agentapi/test-util"; +import dedent from "dedent"; + +let cleanupFunctions: (() => Promise)[] = []; + +const registerCleanup = (cleanup: () => Promise) => { + cleanupFunctions.push(cleanup); +}; + +// Cleanup logic depends on the fact that bun's built-in test runner +// runs tests sequentially. +// https://bun.sh/docs/test/discovery#execution-order +// Weird things would happen if tried to run tests in parallel. +// One test could clean up resources that another test was still using. +afterEach(async () => { + // reverse the cleanup functions so that they are run in the correct order + const cleanupFnsCopy = cleanupFunctions.slice().reverse(); + cleanupFunctions = []; + for (const cleanup of cleanupFnsCopy) { + try { + await cleanup(); + } catch (error) { + console.error("Error during cleanup:", error); + } + } +}); + +interface SetupProps { + skipAgentAPIMock?: boolean; + skipGooseMock?: boolean; + moduleVariables?: Record; + agentapiMockScript?: string; +} + +const setup = async (props?: SetupProps): Promise<{ id: string }> => { + const projectDir = "/home/coder/project"; + const { id } = await setupUtil({ + moduleDir: import.meta.dir, + moduleVariables: { + install_goose: props?.skipGooseMock ? "true" : "false", + install_agentapi: props?.skipAgentAPIMock ? "true" : "false", + goose_provider: "test-provider", + goose_model: "test-model", + ...props?.moduleVariables, + }, + registerCleanup, + projectDir, + skipAgentAPIMock: props?.skipAgentAPIMock, + agentapiMockScript: props?.agentapiMockScript, + }); + if (!props?.skipGooseMock) { + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/goose", + content: await loadTestFile(import.meta.dir, "goose-mock.sh"), + }); + } + return { id }; +}; + +// increase the default timeout to 60 seconds +setDefaultTimeout(60 * 1000); + +describe("goose", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + test("happy-path", async () => { + const { id } = await setup(); + + await execModuleScript(id); + + await expectAgentAPIStarted(id); + }); + + test("install-version", async () => { + const { id } = await setup({ + skipGooseMock: true, + moduleVariables: { + install_goose: "true", + goose_version: "v1.0.24", + }, + }); + + await execModuleScript(id); + + const resp = await execContainer(id, [ + "bash", + "-c", + `"$HOME/.local/bin/goose" --version`, + ]); + if (resp.exitCode !== 0) { + console.log(resp.stdout); + console.log(resp.stderr); + } + expect(resp.exitCode).toBe(0); + expect(resp.stdout).toContain("1.0.24"); + }); + + test("install-stable", async () => { + const { id } = await setup({ + skipGooseMock: true, + moduleVariables: { + install_goose: "true", + goose_version: "stable", + }, + }); + + await execModuleScript(id); + + const resp = await execContainer(id, [ + "bash", + "-c", + `"$HOME/.local/bin/goose" --version`, + ]); + if (resp.exitCode !== 0) { + console.log(resp.stdout); + console.log(resp.stderr); + } + expect(resp.exitCode).toBe(0); + }); + + test("config", async () => { + const expected = + dedent` + GOOSE_PROVIDER: anthropic + GOOSE_MODEL: claude-3-5-sonnet-latest + extensions: + coder: + args: + - exp + - mcp + - server + cmd: coder + description: Report ALL tasks and statuses (in progress, done, failed) you are working on. + enabled: true + envs: + CODER_MCP_APP_STATUS_SLUG: goose + CODER_MCP_AI_AGENTAPI_URL: http://localhost:3284 + name: Coder + timeout: 3000 + type: stdio + developer: + display_name: Developer + enabled: true + name: developer + timeout: 300 + type: builtin + custom-stuff: + enabled: true + name: custom-stuff + timeout: 300 + type: builtin + `.trim() + "\n"; + + const { id } = await setup({ + moduleVariables: { + goose_provider: "anthropic", + goose_model: "claude-3-5-sonnet-latest", + additional_extensions: dedent` + custom-stuff: + enabled: true + name: custom-stuff + timeout: 300 + type: builtin + `.trim(), + }, + }); + await execModuleScript(id); + const resp = await readFileContainer( + id, + "/home/coder/.config/goose/config.yaml", + ); + expect(resp).toEqual(expected); + }); + + test("pre-post-install-scripts", async () => { + const { id } = await setup({ + moduleVariables: { + pre_install_script: "#!/bin/bash\necho 'pre-install-script'", + post_install_script: "#!/bin/bash\necho 'post-install-script'", + }, + }); + + await execModuleScript(id); + + const preInstallLog = await readFileContainer( + id, + "/home/coder/.goose-module/pre_install.log", + ); + expect(preInstallLog).toContain("pre-install-script"); + + const postInstallLog = await readFileContainer( + id, + "/home/coder/.goose-module/post_install.log", + ); + expect(postInstallLog).toContain("post-install-script"); + }); + + const promptFile = "/home/coder/.goose-module/prompt.txt"; + const agentapiStartLog = "/home/coder/.goose-module/agentapi-start.log"; + + test("start-with-prompt", async () => { + const { id } = await setup({ + agentapiMockScript: await loadTestFile( + import.meta.dir, + "agentapi-mock-print-args.js", + ), + }); + await execModuleScript(id, { + GOOSE_TASK_PROMPT: "custom-test-prompt", + }); + const prompt = await readFileContainer(id, promptFile); + expect(prompt).toContain("custom-test-prompt"); + + const agentapiMockOutput = await readFileContainer(id, agentapiStartLog); + expect(agentapiMockOutput).toContain( + "'goose run --interactive --instructions /home/coder/.goose-module/prompt.txt '", + ); + }); + + test("start-without-prompt", async () => { + const { id } = await setup({ + agentapiMockScript: await loadTestFile( + import.meta.dir, + "agentapi-mock-print-args.js", + ), + }); + await execModuleScript(id); + + const agentapiMockOutput = await readFileContainer(id, agentapiStartLog); + expect(agentapiMockOutput).toContain("'goose '"); + + const prompt = await execContainer(id, ["ls", "-l", promptFile]); + expect(prompt.exitCode).not.toBe(0); + expect(prompt.stderr).toContain("No such file or directory"); + }); +}); diff --git a/registry/coder/modules/goose/main.tf b/registry/coder/modules/goose/main.tf index a159ca7b..6d2d873a 100644 --- a/registry/coder/modules/goose/main.tf +++ b/registry/coder/modules/goose/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = ">= 2.5" + version = ">= 2.7" } } } @@ -54,67 +54,42 @@ variable "goose_version" { default = "stable" } -variable "experiment_use_screen" { +variable "install_agentapi" { type = bool - description = "Whether to use screen for running Goose in the background." - default = false -} - -variable "experiment_use_tmux" { - type = bool - description = "Whether to use tmux instead of screen for running Goose in the background." - default = false -} - -variable "session_name" { - type = string - description = "Name for the persistent session (screen or tmux)" - default = "goose" -} - -variable "experiment_report_tasks" { - type = bool - description = "Whether to enable task reporting." - default = false -} - -variable "experiment_auto_configure" { - type = bool - description = "Whether to automatically configure Goose." - default = false + description = "Whether to install AgentAPI." + default = true } -variable "experiment_goose_provider" { +variable "goose_provider" { type = string description = "The provider to use for Goose (e.g., anthropic)." - default = "" } -variable "experiment_goose_model" { +variable "goose_model" { type = string description = "The model to use for Goose (e.g., claude-3-5-sonnet-latest)." - default = "" } -variable "experiment_pre_install_script" { +variable "pre_install_script" { type = string description = "Custom script to run before installing Goose." default = null } -variable "experiment_post_install_script" { +variable "post_install_script" { type = string description = "Custom script to run after installing Goose." default = null } -variable "experiment_additional_extensions" { +variable "additional_extensions" { type = string description = "Additional extensions configuration in YAML format to append to the config." default = null } locals { + app_slug = "goose" base_extensions = <<-EOT coder: args: @@ -125,7 +100,8 @@ coder: description: Report ALL tasks and statuses (in progress, done, failed) you are working on. enabled: true envs: - CODER_MCP_APP_STATUS_SLUG: goose + CODER_MCP_APP_STATUS_SLUG: ${local.app_slug} + CODER_MCP_AI_AGENTAPI_URL: http://localhost:3284 name: Coder timeout: 3000 type: stdio @@ -139,204 +115,49 @@ EOT # Add two spaces to each line of extensions to match YAML structure formatted_base = " ${replace(trimspace(local.base_extensions), "\n", "\n ")}" - additional_extensions = var.experiment_additional_extensions != null ? "\n ${replace(trimspace(var.experiment_additional_extensions), "\n", "\n ")}" : "" - - combined_extensions = <<-EOT + additional_extensions = var.additional_extensions != null ? "\n ${replace(trimspace(var.additional_extensions), "\n", "\n ")}" : "" + combined_extensions = <<-EOT extensions: ${local.formatted_base}${local.additional_extensions} EOT - - encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" - encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : "" -} - -# Install and Initialize Goose -resource "coder_script" "goose" { - agent_id = var.agent_id - display_name = "Goose" - icon = var.icon - script = <<-EOT + install_script = file("${path.module}/scripts/install.sh") + start_script = file("${path.module}/scripts/start.sh") + module_dir_name = ".goose-module" +} + +module "agentapi" { + # TODO: reference the agentapi module when it's published in the registry + # THIS MUST BE DONE BEFORE THE PR IS MERGED + # while the PR is in review, you can reference the module like this: + # source = "git::https://github.com/coder/registry.git//registry/coder/modules/agentapi?ref=hugodutka/agentapi-module" + source = "../agentapi" + + agent_id = var.agent_id + 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 = "Goose" + cli_app_slug = "${local.app_slug}-cli" + cli_app_display_name = "Goose CLI" + module_dir_name = local.module_dir_name + install_agentapi = var.install_agentapi + pre_install_script = var.pre_install_script + post_install_script = var.post_install_script + start_script = local.start_script + install_script = <<-EOT #!/bin/bash - set -e - - # Function to check if a command exists - command_exists() { - command -v "$1" >/dev/null 2>&1 - } - - # Run pre-install script if provided - if [ -n "${local.encoded_pre_install_script}" ]; then - echo "Running pre-install script..." - echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh - chmod +x /tmp/pre_install.sh - /tmp/pre_install.sh - fi - - # Install Goose if enabled - if [ "${var.install_goose}" = "true" ]; then - if ! command_exists npm; then - echo "Error: npm is not installed. Please install Node.js and npm first." - exit 1 - fi - echo "Installing Goose..." - RELEASE_TAG=v${var.goose_version} curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash - fi - - # Run post-install script if provided - if [ -n "${local.encoded_post_install_script}" ]; then - echo "Running post-install script..." - echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh - chmod +x /tmp/post_install.sh - /tmp/post_install.sh - fi - - # Configure Goose if auto-configure is enabled - if [ "${var.experiment_auto_configure}" = "true" ]; then - echo "Configuring Goose..." - mkdir -p "$HOME/.config/goose" - cat > "$HOME/.config/goose/config.yaml" << EOL -GOOSE_PROVIDER: ${var.experiment_goose_provider} -GOOSE_MODEL: ${var.experiment_goose_model} -${trimspace(local.combined_extensions)} -EOL - fi - - # Write system prompt to config - mkdir -p "$HOME/.config/goose" - echo "$GOOSE_SYSTEM_PROMPT" > "$HOME/.config/goose/.goosehints" - - # Handle terminal multiplexer selection (tmux or screen) - if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then - echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously." - echo "Please set only one of them to true." - exit 1 - fi - - # Determine goose command - if command_exists goose; then - GOOSE_CMD=goose - elif [ -f "$HOME/.local/bin/goose" ]; then - GOOSE_CMD="$HOME/.local/bin/goose" - else - echo "Error: Goose is not installed. Please enable install_goose or install it manually." - exit 1 - fi - - # Run with tmux if enabled - if [ "${var.experiment_use_tmux}" = "true" ]; then - echo "Running Goose in the background with tmux..." - - # Check if tmux is installed - if ! command_exists tmux; then - echo "Error: tmux is not installed. Please install tmux manually." - exit 1 - fi - - touch "$HOME/.goose.log" - - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - - # Configure tmux for shared sessions - if [ ! -f "$HOME/.tmux.conf" ]; then - echo "Creating ~/.tmux.conf with shared session settings..." - echo "set -g mouse on" > "$HOME/.tmux.conf" - fi - - if ! grep -q "^set -g mouse on$" "$HOME/.tmux.conf"; then - echo "Adding 'set -g mouse on' to ~/.tmux.conf..." - echo "set -g mouse on" >> "$HOME/.tmux.conf" - fi - - # Create a new tmux session in detached mode - tmux new-session -d -s ${var.session_name} -c ${var.folder} "\"$GOOSE_CMD\" run --text \"Review your goosehints. Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GOOSE_TASK_PROMPT\" --interactive | tee -a \"$HOME/.goose.log\"; exec bash" - elif [ "${var.experiment_use_screen}" = "true" ]; then - echo "Running Goose in the background..." - - # Check if screen is installed - if ! command_exists screen; then - echo "Error: screen is not installed. Please install screen manually." - exit 1 - fi - - touch "$HOME/.goose.log" - - # Ensure the screenrc exists - if [ ! -f "$HOME/.screenrc" ]; then - echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.goose.log" - echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc" - fi - - if ! grep -q "^multiuser on$" "$HOME/.screenrc"; then - echo "Adding 'multiuser on' to ~/.screenrc..." | tee -a "$HOME/.goose.log" - echo "multiuser on" >> "$HOME/.screenrc" - fi - - if ! grep -q "^acladd $(whoami)$" "$HOME/.screenrc"; then - echo "Adding 'acladd $(whoami)' to ~/.screenrc..." | tee -a "$HOME/.goose.log" - echo "acladd $(whoami)" >> "$HOME/.screenrc" - fi - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - - screen -U -dmS ${var.session_name} bash -c " - cd ${var.folder} - \"$GOOSE_CMD\" run --text \"Review your goosehints. Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GOOSE_TASK_PROMPT\" --interactive | tee -a \"$HOME/.goose.log\" - /bin/bash - " - fi - EOT - run_on_start = true -} - -resource "coder_app" "goose" { - slug = "goose" - display_name = "Goose" - agent_id = var.agent_id - command = <<-EOT - #!/bin/bash - set -e - - # Function to check if a command exists - command_exists() { - command -v "$1" >/dev/null 2>&1 - } - - # Determine goose command - if command_exists goose; then - GOOSE_CMD=goose - elif [ -f "$HOME/.local/bin/goose" ]; then - GOOSE_CMD="$HOME/.local/bin/goose" - else - echo "Error: Goose is not installed. Please enable install_goose or install it manually." - exit 1 - fi - - export LANG=en_US.UTF-8 - export LC_ALL=en_US.UTF-8 - - if [ "${var.experiment_use_tmux}" = "true" ]; then - if tmux has-session -t ${var.session_name} 2>/dev/null; then - echo "Attaching to existing Goose tmux session." | tee -a "$HOME/.goose.log" - tmux attach-session -t ${var.session_name} - else - echo "Starting a new Goose tmux session." | tee -a "$HOME/.goose.log" - tmux new-session -s ${var.session_name} -c ${var.folder} "\"$GOOSE_CMD\" run --text \"Review your goosehints. Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GOOSE_TASK_PROMPT\" --interactive | tee -a \"$HOME/.goose.log\"; exec bash" - fi - elif [ "${var.experiment_use_screen}" = "true" ]; then - # Check if session exists first - if ! screen -list | grep -q "${var.session_name}"; then - echo "Error: No existing Goose session found. Please wait for the script to start it." - exit 1 - fi - # Only attach to existing session - screen -xRR ${var.session_name} - else - cd ${var.folder} - "$GOOSE_CMD" run --text "Review goosehints. Your task: $GOOSE_TASK_PROMPT" --interactive - fi - EOT - icon = var.icon - order = var.order - group = var.group + set -o errexit + set -o pipefail + + echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh + chmod +x /tmp/install.sh + + ARG_PROVIDER='${var.goose_provider}' \ + ARG_MODEL='${var.goose_model}' \ + ARG_GOOSE_CONFIG="$(echo -n '${base64encode(local.combined_extensions)}' | base64 -d)" \ + ARG_INSTALL='${var.install_goose}' \ + ARG_GOOSE_VERSION='${var.goose_version}' \ + /tmp/install.sh + EOT } diff --git a/registry/coder/modules/goose/scripts/install.sh b/registry/coder/modules/goose/scripts/install.sh new file mode 100644 index 00000000..28fc923a --- /dev/null +++ b/registry/coder/modules/goose/scripts/install.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +set -o nounset + +echo "--------------------------------" +echo "provider: $ARG_PROVIDER" +echo "model: $ARG_MODEL" +echo "goose_config: $ARG_GOOSE_CONFIG" +echo "install: $ARG_INSTALL" +echo "goose_version: $ARG_GOOSE_VERSION" +echo "--------------------------------" + +set +o nounset + +if [ "${ARG_INSTALL}" = "true" ]; then + echo "Installing Goose..." + parsed_version="${ARG_GOOSE_VERSION}" + if [ "${ARG_GOOSE_VERSION}" = "stable" ]; then + parsed_version="" + fi + curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | GOOSE_VERSION="${parsed_version}" CONFIGURE=false bash + echo "Goose installed" +else + echo "Skipping Goose installation" +fi + +if [ "${ARG_GOOSE_CONFIG}" != "" ]; then + echo "Configuring Goose..." + mkdir -p "$HOME/.config/goose" + echo "GOOSE_PROVIDER: $ARG_PROVIDER" >"$HOME/.config/goose/config.yaml" + echo "GOOSE_MODEL: $ARG_MODEL" >>"$HOME/.config/goose/config.yaml" + echo "$ARG_GOOSE_CONFIG" >>"$HOME/.config/goose/config.yaml" +else + echo "Skipping Goose configuration" +fi + +if [ "${GOOSE_SYSTEM_PROMPT}" != "" ]; then + echo "Setting Goose system prompt..." + mkdir -p "$HOME/.config/goose" + echo "$GOOSE_SYSTEM_PROMPT" >"$HOME/.config/goose/.goosehints" +else + echo "Goose system prompt not set. use the GOOSE_SYSTEM_PROMPT environment variable to set it." +fi + +if command_exists goose; then + GOOSE_CMD=goose +elif [ -f "$HOME/.local/bin/goose" ]; then + GOOSE_CMD="$HOME/.local/bin/goose" +else + echo "Error: Goose is not installed. Please enable install_goose or install it manually." + exit 1 +fi diff --git a/registry/coder/modules/goose/scripts/start.sh b/registry/coder/modules/goose/scripts/start.sh new file mode 100644 index 00000000..314a41d0 --- /dev/null +++ b/registry/coder/modules/goose/scripts/start.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +if command_exists goose; then + GOOSE_CMD=goose +elif [ -f "$HOME/.local/bin/goose" ]; then + GOOSE_CMD="$HOME/.local/bin/goose" +else + echo "Error: Goose is not installed. Please enable install_goose or install it manually." + exit 1 +fi + +# this must be kept up to date with main.tf +MODULE_DIR="$HOME/.goose-module" +mkdir -p "$MODULE_DIR" + +if [ ! -z "$GOOSE_TASK_PROMPT" ]; then + echo "Starting with a prompt" + PROMPT="Review your goosehints. Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GOOSE_TASK_PROMPT" + PROMPT_FILE="$MODULE_DIR/prompt.txt" + echo -n "$PROMPT" >"$PROMPT_FILE" + GOOSE_ARGS=(run --interactive --instructions "$PROMPT_FILE") +else + echo "Starting without a prompt" + GOOSE_ARGS=() +fi + +agentapi server --term-width 67 --term-height 1190 -- \ + bash -c "$(printf '%q ' "$GOOSE_CMD" "${GOOSE_ARGS[@]}")" diff --git a/registry/coder/modules/goose/testdata/agentapi-mock-print-args.js b/registry/coder/modules/goose/testdata/agentapi-mock-print-args.js new file mode 100644 index 00000000..fd859c81 --- /dev/null +++ b/registry/coder/modules/goose/testdata/agentapi-mock-print-args.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +const http = require("http"); +const args = process.argv.slice(2); +console.log(args); +const port = 3284; + +console.log(`starting server on port ${port}`); + +http + .createServer(function (_request, response) { + response.writeHead(200); + response.end( + JSON.stringify({ + status: "stable", + }), + ); + }) + .listen(port); diff --git a/registry/coder/modules/goose/testdata/goose-mock.sh b/registry/coder/modules/goose/testdata/goose-mock.sh new file mode 100644 index 00000000..4d7d3931 --- /dev/null +++ b/registry/coder/modules/goose/testdata/goose-mock.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +while true; do + echo "$(date) - goose-mock" + sleep 15 +done