Skip to content

Commit 87e0862

Browse files
committed
Add deploy script
1 parent c053920 commit 87e0862

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1058
-303
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,8 @@
5757
"[css][html][markdown][yaml]": {
5858
"editor.defaultFormatter": "esbenp.prettier-vscode"
5959
},
60-
"typos.config": ".github/workflows/typos.toml"
60+
"typos.config": ".github/workflows/typos.toml",
61+
"[typescriptreact]": {
62+
"editor.defaultFormatter": "vscode.typescript-language-features"
63+
}
6164
}

agent/agentclaude/agentclaude.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"github.com/spf13/afero"
1616
)
1717

18-
func New(ctx context.Context, apiKey, systemPrompt, taskPrompt string) error {
18+
func New(ctx context.Context, apiKey, systemPrompt, taskPrompt string, onPause func()) error {
1919
claudePath, err := exec.LookPath("claude")
2020
if err != nil {
2121
return fmt.Errorf("claude not found: %w", err)
@@ -25,7 +25,13 @@ func New(ctx context.Context, apiKey, systemPrompt, taskPrompt string) error {
2525
2626
The user is running this task entirely autonomously.
2727
28-
You must use the coder-agent MCP server to periodically report your progress.
28+
Use the coder-agent MCP server to report your progress. You must report when you:
29+
- Start a new task.
30+
- Complete a task (e.g. push a commit, open a PR, etc.)
31+
- Make progress on a task.
32+
33+
You should report your progress frequently. Try to report as much as possible.
34+
2935
If you do not, the user will not be able to see your progress.
3036
`, systemPrompt, "")
3137
if err != nil {
@@ -42,34 +48,31 @@ If you do not, the user will not be able to see your progress.
4248
ProjectDirectory: wd,
4349
APIKey: apiKey,
4450
AllowedTools: []string{},
45-
MCPServers: map[string]ClaudeConfigMCP{
46-
// "coder-agent": {
47-
// Command: "coder",
48-
// Args: []string{"agent", "mcp"},
49-
// },
51+
MCPServers: map[string]ClaudeConfigMCP{
52+
"coder-agent": {
53+
Command: "coder",
54+
Args: []string{"agent", "mcp"},
55+
Env: map[string]string{
56+
"CODER_AGENT_TOKEN": os.Getenv("CODER_AGENT_TOKEN"),
57+
},
58+
},
5059
},
5160
})
5261
if err != nil {
5362
return fmt.Errorf("failed to configure claude: %w", err)
5463
}
5564

56-
cmd := exec.CommandContext(ctx, claudePath, taskPrompt)
57-
58-
handlePause := func() {
59-
// We need to notify the user that we've paused!
60-
fmt.Println("We would normally notify the user...")
61-
}
62-
65+
cmd := exec.CommandContext(ctx, claudePath, "--dangerously-skip-permissions", taskPrompt)
6366
// Create a simple wrapper that starts monitoring only after first write
6467
stdoutWriter := &delayedPauseWriter{
6568
writer: os.Stdout,
6669
pauseWindow: 2 * time.Second,
67-
onPause: handlePause,
70+
onPause: onPause,
6871
}
6972
stderrWriter := &delayedPauseWriter{
7073
writer: os.Stderr,
7174
pauseWindow: 2 * time.Second,
72-
onPause: handlePause,
75+
onPause: onPause,
7376
}
7477

7578
cmd.Stdout = stdoutWriter
@@ -197,6 +200,11 @@ func injectClaudeMD(fs afero.Fs, coderPrompt, systemPrompt string, configPath st
197200
newContent += cleanContent
198201
}
199202

203+
err = fs.MkdirAll(filepath.Dir(configPath), 0755)
204+
if err != nil {
205+
return fmt.Errorf("failed to create claude config directory: %w", err)
206+
}
207+
200208
// Write the updated content back to the file
201209
err = afero.WriteFile(fs, configPath, []byte(newContent), 0644)
202210
if err != nil {
@@ -226,9 +234,9 @@ type ClaudeConfig struct {
226234
}
227235

228236
type ClaudeConfigMCP struct {
229-
Command string
230-
Args []string
231-
Env map[string]string
237+
Command string `json:"command"`
238+
Args []string `json:"args"`
239+
Env map[string]string `json:"env"`
232240
}
233241

234242
func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
@@ -240,7 +248,6 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
240248
if err != nil {
241249
if os.IsNotExist(err) {
242250
config = make(map[string]any)
243-
err = nil
244251
} else {
245252
return fmt.Errorf("failed to stat claude config: %w", err)
246253
}
@@ -265,6 +272,7 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
265272
config["hasCompletedOnboarding"] = true
266273
// Stops Claude from asking for permissions.
267274
config["bypassPermissionsModeAccepted"] = true
275+
config["autoUpdaterStatus"] = "disabled"
268276

269277
projects, ok := config["projects"].(map[string]any)
270278
if !ok {
@@ -291,6 +299,8 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
291299
allowedTools = append(allowedTools, tool)
292300
}
293301
project["allowedTools"] = allowedTools
302+
project["hasTrustDialogAccepted"] = true
303+
project["hasCompletedProjectOnboarding"] = true
294304

295305
mcpServers, ok := project["mcpServers"].(map[string]any)
296306
if !ok {

agent/agentmcp/agentmcp.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,83 @@ package agentmcp
22

33
import (
44
"context"
5+
"errors"
56

67
"github.com/coder/coder/v2/buildinfo"
8+
"github.com/coder/coder/v2/codersdk/agentsdk"
79
"github.com/mark3labs/mcp-go/mcp"
810
"github.com/mark3labs/mcp-go/server"
911
)
1012

11-
func New(ctx context.Context) error {
13+
func New(ctx context.Context, sdk *agentsdk.Client) error {
1214
srv := server.NewMCPServer(
1315
"Coder Agent",
1416
buildinfo.Version(),
17+
server.WithInstructions(`Report your progress when starting, working on, or completing a task.
18+
19+
You MUST report tasks when starting something new, or when you've completed a task.
20+
21+
You MUST report intermediate progress on a task if you've been working on it for a while.
22+
23+
Examples of sending a task:
24+
- Working on a new part of the codebase.
25+
- Starting on an issue (you should include the issue URL as "link").
26+
- Opening a pull request (you should include the PR URL as "link").
27+
- Completing a task (you should set "done" to true).
28+
- Starting a new task (you should set "done" to false).
29+
`),
1530
)
1631

17-
tool := mcp.NewTool("summarize_task",
18-
mcp.WithDescription("Summarize your progress on a task."))
32+
tool := mcp.NewTool("report_task",
33+
mcp.WithDescription(`Report progress on a task.`),
34+
mcp.WithString("summary", mcp.Description(`A summary of your progress on a task.
35+
36+
Good Summaries:
37+
- "Taking a look at the login page..."
38+
- "Found a bug! Fixing it now..."
39+
- "Investigating the GitHub Issue..."`), mcp.Required()),
40+
mcp.WithString("link", mcp.Description(`A relevant link to your work. e.g. GitHub issue link, pull request link, etc.`), mcp.Required()),
41+
mcp.WithBoolean("done", mcp.Description(`Whether the task the user requested is complete.`), mcp.Required()),
42+
mcp.WithString("emoji", mcp.Description(`A relevant emoji to your work.`), mcp.Required()),
43+
)
1944

2045
srv.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
46+
args := request.Params.Arguments
47+
48+
summary, ok := args["summary"].(string)
49+
if !ok {
50+
return nil, errors.New("summary is required")
51+
}
52+
53+
link, ok := args["link"].(string)
54+
if !ok {
55+
return nil, errors.New("link is required")
56+
}
57+
58+
emoji, ok := args["emoji"].(string)
59+
if !ok {
60+
return nil, errors.New("emoji is required")
61+
}
62+
63+
done, ok := args["done"].(bool)
64+
if !ok {
65+
return nil, errors.New("done is required")
66+
}
67+
68+
err := sdk.PostTask(ctx, agentsdk.PostTaskRequest{
69+
Reporter: "claude",
70+
Summary: summary,
71+
URL: link,
72+
Completion: done,
73+
Icon: emoji,
74+
})
75+
if err != nil {
76+
return nil, err
77+
}
78+
2179
return &mcp.CallToolResult{
2280
Content: []mcp.Content{
23-
mcp.NewTextContent("Task summarized."),
81+
mcp.NewTextContent("Thanks for reporting!"),
2482
},
2583
}, nil
2684
})

cli/agent.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,23 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
377377
Use: "mcp",
378378
Short: "Start the MCP server",
379379
Handler: func(inv *serpent.Invocation) error {
380-
return agentmcp.New(inv.Context())
380+
client := agentsdk.New(r.agentURL)
381+
token, _ := inv.ParsedFlags().GetString(varAgentToken)
382+
if token == "" {
383+
tokenFile, _ := inv.ParsedFlags().GetString(varAgentTokenFile)
384+
if tokenFile != "" {
385+
tokenBytes, err := os.ReadFile(tokenFile)
386+
if err != nil {
387+
return xerrors.Errorf("read token file %q: %w", tokenFile, err)
388+
}
389+
token = strings.TrimSpace(string(tokenBytes))
390+
}
391+
}
392+
if token == "" {
393+
return xerrors.Errorf("CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE must be set for token auth")
394+
}
395+
client.SetSessionToken(token)
396+
return agentmcp.New(inv.Context(), client)
381397
},
382398
},
383399
{
@@ -401,7 +417,35 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
401417
},
402418
},
403419
Handler: func(inv *serpent.Invocation) error {
404-
return agentclaude.New(inv.Context(), claudeAPIKey, claudeSystemPrompt, claudeTaskPrompt)
420+
client := agentsdk.New(r.agentURL)
421+
token, _ := inv.ParsedFlags().GetString(varAgentToken)
422+
if token == "" {
423+
tokenFile, _ := inv.ParsedFlags().GetString(varAgentTokenFile)
424+
if tokenFile != "" {
425+
tokenBytes, err := os.ReadFile(tokenFile)
426+
if err != nil {
427+
return xerrors.Errorf("read token file %q: %w", tokenFile, err)
428+
}
429+
token = strings.TrimSpace(string(tokenBytes))
430+
}
431+
}
432+
if token == "" {
433+
return xerrors.Errorf("CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE must be set for token auth")
434+
}
435+
client.SetSessionToken(token)
436+
437+
client.PostTask(inv.Context(), agentsdk.PostTaskRequest{
438+
Reporter: "claude",
439+
Summary: "Starting on your task...",
440+
Icon: "🤖",
441+
Completion: false,
442+
})
443+
444+
return agentclaude.New(inv.Context(), claudeAPIKey, claudeSystemPrompt, claudeTaskPrompt, func() {
445+
_ = client.PatchTasks(inv.Context(), agentsdk.PatchTasksRequest{
446+
WaitingForUserInput: true,
447+
})
448+
})
405449
},
406450
},
407451
},

cli/server.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"sync/atomic"
3232
"time"
3333

34+
"github.com/SherClockHolmes/webpush-go"
3435
"github.com/charmbracelet/lipgloss"
3536
"github.com/coreos/go-oidc/v3/oidc"
3637
"github.com/coreos/go-systemd/daemon"
@@ -775,6 +776,39 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
775776
return xerrors.Errorf("set deployment id: %w", err)
776777
}
777778

779+
var vapidPublicKey string
780+
var vapidPrivateKey string
781+
err = options.Database.InTx(func(tx database.Store) error {
782+
vapidPublicKey, err = tx.GetVAPIDPublicKey(ctx)
783+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
784+
return xerrors.Errorf("get vapid public key: %w", err)
785+
}
786+
vapidPrivateKey, err = tx.GetVAPIDPrivateKey(ctx)
787+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
788+
return xerrors.Errorf("get vapid private key: %w", err)
789+
}
790+
if vapidPublicKey == "" || vapidPrivateKey == "" {
791+
vapidPrivateKey, vapidPublicKey, err = webpush.GenerateVAPIDKeys()
792+
if err != nil {
793+
return xerrors.Errorf("generate vapid keys: %w", err)
794+
}
795+
err = tx.InsertVAPIDPublicKey(ctx, vapidPublicKey)
796+
if err != nil {
797+
return xerrors.Errorf("insert vapid public key: %w", err)
798+
}
799+
err = tx.InsertVAPIDPrivateKey(ctx, vapidPrivateKey)
800+
if err != nil {
801+
return xerrors.Errorf("insert vapid private key: %w", err)
802+
}
803+
}
804+
return nil
805+
}, nil)
806+
if err != nil {
807+
return xerrors.Errorf("insert vapid keys: %w", err)
808+
}
809+
options.NotificationsVAPIDPrivateKey = vapidPrivateKey
810+
options.NotificationsVAPIDPublicKey = vapidPublicKey
811+
778812
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals)
779813
if err != nil {
780814
return xerrors.Errorf("get github oauth2 config params: %w", err)

coderd/apidoc/docs.go

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)