feat: update the goose module to support Tasks (#178)

Addresses https://github.com/coder/internal/issues/700. In addition to
the automated tests in the PR, I manually tested this module in
dev.coder.com.
This commit is contained in:
Hugo Dutka 2025-07-09 13:28:15 +02:00 committed by GitHub
parent d7fdc793c7
commit b74290051e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 449 additions and 287 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -10,6 +10,7 @@
"devDependencies": { "devDependencies": {
"@types/bun": "^1.2.18", "@types/bun": "^1.2.18",
"bun-types": "^1.2.18", "bun-types": "^1.2.18",
"dedent": "^1.6.0",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"marked": "^16.0.0", "marked": "^16.0.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",

View File

@ -4,7 +4,7 @@ description: Run Goose in your workspace
icon: ../../../../.icons/goose.svg icon: ../../../../.icons/goose.svg
maintainer_github: coder maintainer_github: coder
verified: true verified: true
tags: [agent, goose, ai] tags: [agent, goose, ai, tasks]
--- ---
# Goose # Goose
@ -13,36 +13,27 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener
```tf ```tf
module "goose" { module "goose" {
source = "registry.coder.com/coder/goose/coder" source = "registry.coder.com/coder/goose/coder"
version = "1.3.0" version = "2.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
folder = "/home/coder" folder = "/home/coder"
install_goose = true install_goose = true
goose_version = "v1.0.16" goose_version = "v1.0.31"
goose_provider = "anthropic"
goose_model = "claude-3-5-sonnet-latest"
agentapi_version = "latest"
} }
``` ```
## Prerequisites ## 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 - 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. The `codercom/oss-dogfood:latest` container image can be used for testing on container-based workspaces.
## Examples ## Examples
Your workspace must have `screen` or `tmux` installed to use the background session functionality. ### Run in the background and report tasks
### 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.
```tf ```tf
module "coder-login" { module "coder-login" {
@ -81,37 +72,23 @@ resource "coder_agent" "main" {
EOT EOT
GOOSE_TASK_PROMPT = data.coder_parameter.ai_prompt.value 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 # See https://block.github.io/goose/docs/getting-started/providers
ANTHROPIC_API_KEY = var.anthropic_api_key # or use a coder_parameter ANTHROPIC_API_KEY = var.anthropic_api_key # or use a coder_parameter
} }
} }
module "goose" { module "goose" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/goose/coder" source = "registry.coder.com/coder/goose/coder"
version = "1.3.0" version = "2.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
folder = "/home/coder" folder = "/home/coder"
install_goose = true install_goose = true
goose_version = "v1.0.16" goose_version = "v1.0.31"
agentapi_version = "latest"
# Enable experimental features goose_provider = "anthropic"
experiment_report_tasks = true goose_model = "claude-3-5-sonnet-latest"
# 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
# Required for experiment_auto_configure
experiment_goose_provider = "anthropic"
experiment_goose_model = "claude-3-5-sonnet-latest"
} }
``` ```
@ -123,11 +100,11 @@ You can extend Goose's capabilities by adding custom extensions. For example, to
module "goose" { module "goose" {
# ... other configuration ... # ... other configuration ...
experiment_pre_install_script = <<-EOT pre_install_script = <<-EOT
npm i -g @wonderwhy-er/desktop-commander@latest npm i -g @wonderwhy-er/desktop-commander@latest
EOT EOT
experiment_additional_extensions = <<-EOT additional_extensions = <<-EOT
desktop-commander: desktop-commander:
args: [] args: []
cmd: desktop-commander cmd: desktop-commander
@ -145,20 +122,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. 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. 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.
```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"
}
```

View File

@ -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<void>)[] = [];
const registerCleanup = (cleanup: () => Promise<void>) => {
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<string, string>;
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");
});
});

View File

@ -4,7 +4,7 @@ terraform {
required_providers { required_providers {
coder = { coder = {
source = "coder/coder" source = "coder/coder"
version = ">= 2.5" version = ">= 2.7"
} }
} }
} }
@ -54,67 +54,48 @@ variable "goose_version" {
default = "stable" default = "stable"
} }
variable "experiment_use_screen" { variable "install_agentapi" {
type = bool type = bool
description = "Whether to use screen for running Goose in the background." description = "Whether to install AgentAPI."
default = false default = true
} }
variable "experiment_use_tmux" { variable "agentapi_version" {
type = bool
description = "Whether to use tmux instead of screen for running Goose in the background."
default = false
}
variable "session_name" {
type = string type = string
description = "Name for the persistent session (screen or tmux)" description = "The version of AgentAPI to install."
default = "goose" default = "v0.2.3"
} }
variable "experiment_report_tasks" { variable "goose_provider" {
type = bool
description = "Whether to enable task reporting."
default = false
}
variable "experiment_auto_configure" {
type = bool
description = "Whether to automatically configure Goose."
default = false
}
variable "experiment_goose_provider" {
type = string type = string
description = "The provider to use for Goose (e.g., anthropic)." description = "The provider to use for Goose (e.g., anthropic)."
default = ""
} }
variable "experiment_goose_model" { variable "goose_model" {
type = string type = string
description = "The model to use for Goose (e.g., claude-3-5-sonnet-latest)." 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 type = string
description = "Custom script to run before installing Goose." description = "Custom script to run before installing Goose."
default = null default = null
} }
variable "experiment_post_install_script" { variable "post_install_script" {
type = string type = string
description = "Custom script to run after installing Goose." description = "Custom script to run after installing Goose."
default = null default = null
} }
variable "experiment_additional_extensions" { variable "additional_extensions" {
type = string type = string
description = "Additional extensions configuration in YAML format to append to the config." description = "Additional extensions configuration in YAML format to append to the config."
default = null default = null
} }
locals { locals {
app_slug = "goose"
base_extensions = <<-EOT base_extensions = <<-EOT
coder: coder:
args: args:
@ -125,7 +106,8 @@ coder:
description: Report ALL tasks and statuses (in progress, done, failed) you are working on. description: Report ALL tasks and statuses (in progress, done, failed) you are working on.
enabled: true enabled: true
envs: 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 name: Coder
timeout: 3000 timeout: 3000
type: stdio type: stdio
@ -139,204 +121,47 @@ EOT
# Add two spaces to each line of extensions to match YAML structure # Add two spaces to each line of extensions to match YAML structure
formatted_base = " ${replace(trimspace(local.base_extensions), "\n", "\n ")}" 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 ")}" : "" additional_extensions = var.additional_extensions != null ? "\n ${replace(trimspace(var.additional_extensions), "\n", "\n ")}" : ""
combined_extensions = <<-EOT
combined_extensions = <<-EOT
extensions: extensions:
${local.formatted_base}${local.additional_extensions} ${local.formatted_base}${local.additional_extensions}
EOT EOT
install_script = file("${path.module}/scripts/install.sh")
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : "" start_script = file("${path.module}/scripts/start.sh")
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : "" module_dir_name = ".goose-module"
} }
# Install and Initialize Goose module "agentapi" {
resource "coder_script" "goose" { source = "registry.coder.com/coder/agentapi/coder"
agent_id = var.agent_id version = "1.0.0"
display_name = "Goose"
icon = var.icon agent_id = var.agent_id
script = <<-EOT 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
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = local.start_script
install_script = <<-EOT
#!/bin/bash #!/bin/bash
set -e set -o errexit
set -o pipefail
# Function to check if a command exists echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
command_exists() { chmod +x /tmp/install.sh
command -v "$1" >/dev/null 2>&1
}
# Run pre-install script if provided ARG_PROVIDER='${var.goose_provider}' \
if [ -n "${local.encoded_pre_install_script}" ]; then ARG_MODEL='${var.goose_model}' \
echo "Running pre-install script..." ARG_GOOSE_CONFIG="$(echo -n '${base64encode(local.combined_extensions)}' | base64 -d)" \
echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh ARG_INSTALL='${var.install_goose}' \
chmod +x /tmp/pre_install.sh ARG_GOOSE_VERSION='${var.goose_version}' \
/tmp/pre_install.sh /tmp/install.sh
fi EOT
# 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
} }

View File

@ -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

View File

@ -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[@]}")"

View File

@ -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);

View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
while true; do
echo "$(date) - goose-mock"
sleep 15
done