Skip to content

Bcpeinhardt/ai agent session in vscode #488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bfa33eb
update deps to kyles branch and make helper for getting agent informa…
bcpeinhardt Mar 18, 2025
92c3bfd
agents in sidebar
bcpeinhardt Mar 18, 2025
c5f6dcb
janky working demo for meeting
bcpeinhardt Mar 18, 2025
b2f6bb8
clean up some horrible claude code
bcpeinhardt Mar 18, 2025
c7001d5
separate metadata and tasks
bcpeinhardt Mar 18, 2025
f39f458
tweaks to terminal sizing
bcpeinhardt Mar 19, 2025
c31cb7c
put coder dep back on main post merge of ai work
bcpeinhardt Mar 31, 2025
063b27e
statuses updates
bcpeinhardt Apr 1, 2025
be1e137
coder ssh strategy finally working
bcpeinhardt Apr 1, 2025
08c93ae
show apps in need of attention only when there are some to show
bcpeinhardt Apr 1, 2025
99f3b1d
working with goose and claude
bcpeinhardt Apr 1, 2025
cd7c68c
switch up labels
bcpeinhardt Apr 1, 2025
26f740d
remove hand raise emojis
bcpeinhardt Apr 1, 2025
ac8d5fd
app not application
bcpeinhardt Apr 1, 2025
d0386d0
resolve conflict
bcpeinhardt Apr 1, 2025
69f9b97
remove unused commands
bcpeinhardt Apr 1, 2025
68c2e04
terminal names
bcpeinhardt Apr 1, 2025
b1e281c
use built in coder cli
bcpeinhardt Apr 2, 2025
5c66430
Merge branch 'main' into bcpeinhardt/ai-agent-session-in-vscode
bcpeinhardt Apr 14, 2025
83fafb0
Merge branch 'main' into bcpeinhardt/ai-agent-session-in-vscode
bcpeinhardt Apr 15, 2025
22246b9
reset back to working state pre tmux and reverse app statuses so most…
bcpeinhardt Apr 21, 2025
80f74f9
only show terminal after ssh command and app commands run
bcpeinhardt Apr 21, 2025
0f7dd65
loading indicator
bcpeinhardt Apr 21, 2025
8f996dd
update loading msg
bcpeinhardt Apr 21, 2025
c24b675
don't mess with terminal size
bcpeinhardt Apr 21, 2025
6f83606
workspace name isn't optional
bcpeinhardt Apr 21, 2025
579ad4e
changelog and format
bcpeinhardt Apr 21, 2025
9340b7d
remove unused icon code
bcpeinhardt Apr 21, 2025
8dac372
cleanup
bcpeinhardt Apr 21, 2025
866956a
remove unnecessary label assignment (I think there used to be an icon…
bcpeinhardt Apr 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
agents in sidebar
  • Loading branch information
bcpeinhardt committed Mar 18, 2025
commit 92c3bfd6a5cd881ed8065dba119ffb09e81ce7c0
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@
"title": "Coder: View Logs",
"icon": "$(list-unordered)",
"when": "coder.authenticated"
},
{
"command": "coder.viewAITasks",
"title": "Coder: View AI Tasks",
"icon": "$(robot)",
"when": "coder.authenticated"
}
],
"menus": {
Expand Down Expand Up @@ -231,6 +237,11 @@
"command": "coder.refreshWorkspaces",
"when": "coder.authenticated && view == myWorkspaces",
"group": "navigation"
},
{
"command": "coder.viewAITasks",
"when": "coder.authenticated && view == myWorkspaces",
"group": "navigation"
}
],
"view/item/context": [
Expand Down Expand Up @@ -259,6 +270,11 @@
"command": "coder.createWorkspace",
"group": "remote_11_ssh_coder@2",
"when": "coder.authenticated"
},
{
"command": "coder.viewAITasks",
"group": "remote_11_ssh_coder@3",
"when": "coder.authenticated"
}
]
}
Expand Down
33 changes: 10 additions & 23 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AxiosInstance } from "axios"
import { spawn } from "child_process"
import { Api } from "coder/site/src/api/api"
import { ProvisionerJobLog, Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
import { ProvisionerJobLog, Workspace, WorkspaceAgent, WorkspaceAgentTask } from "coder/site/src/api/typesGenerated"
import { FetchLikeInit } from "eventsource"
import fs from "fs/promises"
import { ProxyAgent } from "proxy-agent"
Expand Down Expand Up @@ -282,39 +282,26 @@ export async function getAITasksForWorkspace(
restClient: Api,
writeEmitter: vscode.EventEmitter<string>,
workspace: Workspace,
) {
): Promise<WorkspaceAgentTask[]> {
// We need to build up tasks
let awaiting_tasks: WorkspaceAgentTask[] = [];

// The workspace will include agents, and within each agent you can find tasks
// You can access the agents from the workspace resource
const resources = workspace.latest_build.resources;

// Go through each resource
for (const resource of resources) {
// Each resource can have multiple agents
if (!resource.agents) {
continue
}

for (const agent of resource.agents) {
// Check if this agent has any AI tasks
if (agent.tasks && agent.tasks.length > 0) {
// This agent has AI tasks!
console.log(`Agent ${agent.name} (${agent.id}) has ${agent.tasks.length} tasks`);

// Examine task details
for (const task of agent.tasks) {
console.log(`Task: ${task.summary}`);
console.log(`Reporter: ${task.reporter}`);
console.log(`Status: ${task.completion ? 'Completed' : 'In Progress'}`);
console.log(`URL: ${task.url}`);
console.log(`Icon: ${task.icon}`);
}

// Check if the agent is waiting for user input
if (agent.task_waiting_for_user_input) {
console.log("This agent is waiting for user input!");
}
resource.agents.forEach((agent) => {
for (const task of agent.tasks) {
awaiting_tasks.push(task);
}
}
})
}

return awaiting_tasks;
}
11 changes: 10 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Api } from "coder/site/src/api/api"
import { getErrorMessage } from "coder/site/src/api/errors"
import { User, Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
import * as vscode from "vscode"
import { makeCoderSdk, needToken } from "./api"
import { getAITasksForWorkspace, makeCoderSdk, needToken } from "./api"
import { extractAgents } from "./api-helper"
import { CertificateError } from "./error"
import { Storage } from "./storage"
Expand Down Expand Up @@ -295,6 +295,15 @@ export class Commands {
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc)
}

/**
* Open a view to show AI tasks across all workspaces
*/
public async viewAITasks(): Promise<void> {
vscode.window.showInformationMessage("Viewing AI tasks across workspaces")
// Refresh workspaces to ensure we have the latest tasks
vscode.commands.executeCommand("coder.refreshWorkspaces")
}

/**
* Log out from the currently logged-in deployment.
Expand Down
9 changes: 9 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
allWorkspacesProvider.fetchAndRefresh()
})
vscode.commands.registerCommand("coder.viewLogs", commands.viewLogs.bind(commands))
vscode.commands.registerCommand("coder.viewAITasks", commands.viewAITasks.bind(commands))
vscode.commands.registerCommand("coder.openAITask", (task) => {
// Open the task URL if available
if (task && task.url) {
vscode.env.openExternal(vscode.Uri.parse(task.url))
} else {
vscode.window.showInformationMessage("This AI task has no associated URL")
}
})

// Since the "onResolveRemoteAuthority:ssh-remote" activation event exists
// in package.json we're able to perform actions before the authority is
Expand Down
65 changes: 59 additions & 6 deletions src/workspacesProvider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Api } from "coder/site/src/api/api"
import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
import { Workspace, WorkspaceAgent, WorkspaceAgentTask } from "coder/site/src/api/typesGenerated"
import { EventSource } from "eventsource"
import * as path from "path"
import * as vscode from "vscode"
import { createStreamingFetchAdapter } from "./api"
import { createStreamingFetchAdapter, getAITasksForWorkspace } from "./api"
import {
AgentMetadataEvent,
AgentMetadataEventSchemaArray,
Expand Down Expand Up @@ -146,9 +146,32 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
}
})

return resp.workspaces.map((workspace) => {
return new WorkspaceTreeItem(workspace, this.getWorkspacesQuery === WorkspaceQuery.All, showMetadata)
})
// Create tree items for each workspace
const workspaceTreeItems = await Promise.all(resp.workspaces.map(async (workspace) => {
const workspaceTreeItem = new WorkspaceTreeItem(
workspace,
this.getWorkspacesQuery === WorkspaceQuery.All,
showMetadata
)

// Fetch AI tasks for the workspace
try {
// Create a dummy emitter for logs
const emitter = new vscode.EventEmitter<string>()
const aiTasks = await getAITasksForWorkspace(restClient, emitter, workspace)
workspaceTreeItem.aiTasks = aiTasks
this.storage.writeToCoderOutputChannel(aiTasks.length.toString())
console.log(aiTasks.length.toString())
} catch (error) {
// Log the error but continue - we don't want to fail the whole tree if AI tasks fail
this.storage.writeToCoderOutputChannel(`Failed to fetch AI tasks for workspace ${workspace.name}: ${errToStr(error, "unknown error")}`)

}

return workspaceTreeItem
}))

return workspaceTreeItems
}

/**
Expand Down Expand Up @@ -207,7 +230,20 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
const agentTreeItems = agents.map(
(agent) => new AgentTreeItem(agent, element.workspaceOwner, element.workspaceName, element.watchMetadata),
)
return Promise.resolve(agentTreeItems)

// Add AI task items to the workspace children if there are any
const aiTaskItems = element.aiTasks.map(task => new AITaskTreeItem(task))

// If there are AI tasks, add them at the beginning of the list
if (aiTaskItems.length == 0) {
return Promise.resolve(agentTreeItems)
}
// Create a separator item
const separator = new vscode.TreeItem("AI Tasks", vscode.TreeItemCollapsibleState.None)
separator.contextValue = "coderAITaskHeader"

// Return AI task items first, then a separator, then agent items
return Promise.resolve([...aiTaskItems, separator, ...agentTreeItems])
} else if (element instanceof AgentTreeItem) {
const watcher = this.agentWatchers[element.agent.id]
if (watcher?.error) {
Expand Down Expand Up @@ -285,6 +321,21 @@ class AgentMetadataTreeItem extends vscode.TreeItem {
}
}

class AITaskTreeItem extends vscode.TreeItem {
constructor(public readonly task: WorkspaceAgentTask) {
super(task.summary, vscode.TreeItemCollapsibleState.None)
this.description = task.summary
this.contextValue = "coderAITask"

// Add command to handle clicking on the task
this.command = {
command: "coder.openAITask",
title: "Open AI Task",
arguments: [task]
}
}
}

type CoderOpenableTreeItemType = "coderWorkspaceSingleAgent" | "coderWorkspaceMultipleAgents" | "coderAgent"

export class OpenableTreeItem extends vscode.TreeItem {
Expand Down Expand Up @@ -335,6 +386,8 @@ class AgentTreeItem extends OpenableTreeItem {
}

export class WorkspaceTreeItem extends OpenableTreeItem {
public aiTasks: WorkspaceAgentTask[] = []

constructor(
public readonly workspace: Workspace,
public readonly showOwner: boolean,
Expand Down