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:
parent
d7fdc793c7
commit
b74290051e
@ -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",
|
||||||
|
|||||||
@ -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"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
254
registry/coder/modules/goose/main.test.ts
Normal file
254
registry/coder/modules/goose/main.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
57
registry/coder/modules/goose/scripts/install.sh
Normal file
57
registry/coder/modules/goose/scripts/install.sh
Normal 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
|
||||||
35
registry/coder/modules/goose/scripts/start.sh
Normal file
35
registry/coder/modules/goose/scripts/start.sh
Normal 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[@]}")"
|
||||||
19
registry/coder/modules/goose/testdata/agentapi-mock-print-args.js
vendored
Normal file
19
registry/coder/modules/goose/testdata/agentapi-mock-print-args.js
vendored
Normal 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);
|
||||||
8
registry/coder/modules/goose/testdata/goose-mock.sh
vendored
Normal file
8
registry/coder/modules/goose/testdata/goose-mock.sh
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo "$(date) - goose-mock"
|
||||||
|
sleep 15
|
||||||
|
done
|
||||||
Loading…
x
Reference in New Issue
Block a user