From bfa33ebb5952dca97790bdc9bdb9883bd40e8775 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 18 Mar 2025 07:51:52 -0500 Subject: [PATCH 01/27] update deps to kyles branch and make helper for getting agent information --- package.json | 2 +- src/api.ts | 44 +++++++++++++++++++++++++++++++++++++++++++- yarn.lock | 4 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 7d7d3862..6d1df6b1 100644 --- a/package.json +++ b/package.json @@ -287,7 +287,7 @@ "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.21.1", "bufferutil": "^4.0.8", - "coder": "https://github.com/coder/coder#main", + "coder": "https://github.com/coder/coder#kyle/tasks", "dayjs": "^1.11.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", diff --git a/src/api.ts b/src/api.ts index 51e15416..b3c096a0 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,7 +1,7 @@ import { AxiosInstance } from "axios" import { spawn } from "child_process" import { Api } from "coder/site/src/api/api" -import { ProvisionerJobLog, Workspace } from "coder/site/src/api/typesGenerated" +import { ProvisionerJobLog, Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" import { FetchLikeInit } from "eventsource" import fs from "fs/promises" import { ProxyAgent } from "proxy-agent" @@ -276,3 +276,45 @@ export async function waitForBuild( writeEmitter.fire(`Workspace is now ${updatedWorkspace.latest_build.status}\r\n`) return updatedWorkspace } + +// 1. First, get a workspace by owner and name +export async function getAITasksForWorkspace( + restClient: Api, + writeEmitter: vscode.EventEmitter, + workspace: Workspace, +) { + + // 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!"); + } + } + } + } +} diff --git a/yarn.lock b/yarn.lock index 907f0855..e0f43fc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1592,9 +1592,9 @@ co@3.1.0: resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" integrity sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA== -"coder@https://github.com/coder/coder#main": +"coder@https://github.com/coder/coder#kyle/tasks": version "0.0.0" - resolved "https://github.com/coder/coder#975ea23d6f49a4043131f79036d1bf5166eb9140" + resolved "https://github.com/coder/coder#87e086298f19fec18b2ba18bf4ff39081d670570" collapse-white-space@^1.0.2: version "1.0.6" From 92c3bfd6a5cd881ed8065dba119ffb09e81ce7c0 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 18 Mar 2025 08:49:45 -0500 Subject: [PATCH 02/27] agents in sidebar --- package.json | 16 ++++++++++ src/api.ts | 33 ++++++-------------- src/commands.ts | 11 ++++++- src/extension.ts | 9 ++++++ src/workspacesProvider.ts | 65 +++++++++++++++++++++++++++++++++++---- 5 files changed, 104 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 6d1df6b1..6174e802 100644 --- a/package.json +++ b/package.json @@ -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": { @@ -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": [ @@ -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" } ] } diff --git a/src/api.ts b/src/api.ts index b3c096a0..2c976f00 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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" @@ -282,7 +282,9 @@ export async function getAITasksForWorkspace( restClient: Api, writeEmitter: vscode.EventEmitter, workspace: Workspace, -) { +): Promise { + // 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 @@ -290,31 +292,16 @@ export async function getAITasksForWorkspace( // 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; } diff --git a/src/commands.ts b/src/commands.ts index 3506d822..7df28c1f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -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" @@ -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 { + 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. diff --git a/src/extension.ts b/src/extension.ts index e5e2799a..831035ad 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -136,6 +136,15 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { 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 diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 0709487e..495dd0b5 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -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, @@ -146,9 +146,32 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { - 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() + 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 } /** @@ -207,7 +230,20 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 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) { @@ -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 { @@ -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, From c5f6dcbfcc68a72f2f74b585ffbe748aad2d0057 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 18 Mar 2025 09:27:28 -0500 Subject: [PATCH 03/27] janky working demo for meeting --- package.json | 18 ++++++++++++++---- src/commands.ts | 20 ++++++++++++-------- src/extension.ts | 10 +--------- src/workspacesProvider.ts | 8 ++++---- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 6174e802..61b5ea47 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,11 @@ "title": "Coder: Open Workspace", "icon": "$(play)" }, + { + "command": "coder.openFromSidebarAndOpenSession", + "title": "Coder: Open Workspace with Claude Code Session", + "icon": "$(terminal)" + }, { "command": "coder.createWorkspace", "title": "Create Workspace", @@ -206,8 +211,8 @@ "when": "coder.authenticated" }, { - "command": "coder.viewAITasks", - "title": "Coder: View AI Tasks", + "command": "coder.openAITask", + "title": "Coder: Open AI Task", "icon": "$(robot)", "when": "coder.authenticated" } @@ -239,7 +244,7 @@ "group": "navigation" }, { - "command": "coder.viewAITasks", + "command": "coder.openAITask", "when": "coder.authenticated && view == myWorkspaces", "group": "navigation" } @@ -250,6 +255,11 @@ "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderAgent", "group": "inline" }, + { + "command": "coder.openFromSidebarAndOpenSession", + "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderAgent", + "group": "inline" + }, { "command": "coder.navigateToWorkspace", "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderWorkspaceMultipleAgents", @@ -272,7 +282,7 @@ "when": "coder.authenticated" }, { - "command": "coder.viewAITasks", + "command": "coder.openAITask", "group": "remote_11_ssh_coder@3", "when": "coder.authenticated" } diff --git a/src/commands.ts b/src/commands.ts index 7df28c1f..4fcb58d3 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -296,14 +296,6 @@ export class Commands { await vscode.window.showTextDocument(doc) } - /** - * Open a view to show AI tasks across all workspaces - */ - public async viewAITasks(): Promise { - 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. @@ -416,6 +408,18 @@ export class Commands { } } + public async openAISession(): Promise { + + // Then launch an integrated terminal with screen session + const terminal = vscode.window.createTerminal({ + name: "Claude Code Session", + }) + + // Show the terminal and run the screen command + terminal.show(true) + terminal.sendText("screen -xRR claude-code") + } + /** * Open a workspace belonging to the currently logged-in deployment. * diff --git a/src/extension.ts b/src/extension.ts index 831035ad..f04743ed 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -124,6 +124,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) vscode.commands.registerCommand("coder.openFromSidebar", commands.openFromSidebar.bind(commands)) + vscode.commands.registerCommand("coder.openAITask", commands.openAISession.bind(commands)) vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands)) @@ -136,15 +137,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { 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 diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 495dd0b5..80c60597 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -238,12 +238,9 @@ export class WorkspaceProvider implements vscode.TreeDataProvider Date: Tue, 18 Mar 2025 15:55:40 -0500 Subject: [PATCH 04/27] clean up some horrible claude code --- src/api.ts | 29 ------------ src/commands.ts | 6 +-- src/workspacesProvider.ts | 92 ++++++++++++++++++++------------------- 3 files changed, 49 insertions(+), 78 deletions(-) diff --git a/src/api.ts b/src/api.ts index 2c976f00..b911f874 100644 --- a/src/api.ts +++ b/src/api.ts @@ -276,32 +276,3 @@ export async function waitForBuild( writeEmitter.fire(`Workspace is now ${updatedWorkspace.latest_build.status}\r\n`) return updatedWorkspace } - -// 1. First, get a workspace by owner and name -export async function getAITasksForWorkspace( - restClient: Api, - writeEmitter: vscode.EventEmitter, - workspace: Workspace, -): Promise { - // 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) { - if (!resource.agents) { - continue - } - - resource.agents.forEach((agent) => { - for (const task of agent.tasks) { - awaiting_tasks.push(task); - } - }) - } - - return awaiting_tasks; -} diff --git a/src/commands.ts b/src/commands.ts index 4fcb58d3..c33d1713 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -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 { getAITasksForWorkspace, makeCoderSdk, needToken } from "./api" +import { makeCoderSdk, needToken } from "./api" import { extractAgents } from "./api-helper" import { CertificateError } from "./error" import { Storage } from "./storage" @@ -295,7 +295,6 @@ export class Commands { const doc = await vscode.workspace.openTextDocument(uri) await vscode.window.showTextDocument(doc) } - /** * Log out from the currently logged-in deployment. @@ -409,12 +408,11 @@ export class Commands { } public async openAISession(): Promise { - // Then launch an integrated terminal with screen session const terminal = vscode.window.createTerminal({ name: "Claude Code Session", }) - + // Show the terminal and run the screen command terminal.show(true) terminal.sendText("screen -xRR claude-code") diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 80c60597..0294c648 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -3,7 +3,7 @@ import { Workspace, WorkspaceAgent, WorkspaceAgentTask } from "coder/site/src/ap import { EventSource } from "eventsource" import * as path from "path" import * as vscode from "vscode" -import { createStreamingFetchAdapter, getAITasksForWorkspace } from "./api" +import { createStreamingFetchAdapter } from "./api" import { AgentMetadataEvent, AgentMetadataEventSchemaArray, @@ -147,29 +147,28 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { - 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() - 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 - })) + 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() + } 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 } @@ -230,24 +229,25 @@ export class WorkspaceProvider implements vscode.TreeDataProvider new AgentTreeItem(agent, element.workspaceOwner, element.workspaceName, element.watchMetadata), ) - - // 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) - } - - // Return AI task items first, then a separator, then agent items - return Promise.resolve([...aiTaskItems, ...agentTreeItems]) + + return Promise.resolve(agentTreeItems) } else if (element instanceof AgentTreeItem) { const watcher = this.agentWatchers[element.agent.id] if (watcher?.error) { return Promise.resolve([new ErrorTreeItem(watcher.error)]) } + + const items: vscode.TreeItem[] = [] + + // Add AI tasks first, if the agent has any associated tasks + const agentTasks = element.agent.tasks.map((task) => new AITaskTreeItem(task)) + items.push(...agentTasks) + + // Add agent metadata const savedMetadata = watcher?.metadata || [] - return Promise.resolve(savedMetadata.map((metadata) => new AgentMetadataTreeItem(metadata))) + items.push(...savedMetadata.map((metadata) => new AgentMetadataTreeItem(metadata))) + + return Promise.resolve(items) } return Promise.resolve([]) @@ -320,18 +320,16 @@ class AgentMetadataTreeItem extends vscode.TreeItem { class AITaskTreeItem extends vscode.TreeItem { constructor(public readonly task: WorkspaceAgentTask) { - super(task.summary, vscode.TreeItemCollapsibleState.None) + // Add a hand raise emoji (✋) to indicate tasks awaiting user input + super(task.icon, vscode.TreeItemCollapsibleState.None) this.description = task.summary this.contextValue = "coderAITask" - - // Add an icon using VSCode's built-in Codicons - this.iconPath = new vscode.ThemeIcon("sparkle") - + // Add command to handle clicking on the task this.command = { - command: "coder.openAITask", + command: "coder.openAITask", title: "Open AI Task", - arguments: [task] + arguments: [task], } } } @@ -382,12 +380,16 @@ class AgentTreeItem extends OpenableTreeItem { agent.expanded_directory, "coderAgent", ) + + if (agent.task_waiting_for_user_input) { + this.label = "🙋 " + this.label; + } } } export class WorkspaceTreeItem extends OpenableTreeItem { - public aiTasks: WorkspaceAgentTask[] = [] - + public aiTasks: {waiting: boolean, tasks: WorkspaceAgentTask[]}[] = [] + constructor( public readonly workspace: Workspace, public readonly showOwner: boolean, From c7001d5f03b5d62805447988b659a7a054a7e7a3 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 18 Mar 2025 16:10:08 -0500 Subject: [PATCH 05/27] separate metadata and tasks --- src/api.ts | 2 +- src/workspacesProvider.ts | 52 ++++++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/api.ts b/src/api.ts index b911f874..51e15416 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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, WorkspaceAgentTask } from "coder/site/src/api/typesGenerated" +import { ProvisionerJobLog, Workspace } from "coder/site/src/api/typesGenerated" import { FetchLikeInit } from "eventsource" import fs from "fs/promises" import { ProxyAgent } from "proxy-agent" diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 0294c648..aa66a577 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -158,7 +158,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider() + const _emitter = new vscode.EventEmitter() } catch (error) { // Log the error but continue - we don't want to fail the whole tree if AI tasks fail this.storage.writeToCoderOutputChannel( @@ -236,18 +236,33 @@ export class WorkspaceProvider implements vscode.TreeDataProvider new AITaskTreeItem(task)) - items.push(...agentTasks) - - // Add agent metadata + + // Add AI tasks section with collapsible header + if (element.agent.tasks.length > 0) { + const aiTasksSection = new SectionTreeItem( + "AI Tasks", + element.agent.tasks.map((task) => new AITaskTreeItem(task)), + ) + items.push(aiTasksSection) + } + const savedMetadata = watcher?.metadata || [] - items.push(...savedMetadata.map((metadata) => new AgentMetadataTreeItem(metadata))) - + + // Add agent metadata section with collapsible header + if (savedMetadata.length > 0) { + const metadataSection = new SectionTreeItem( + "Agent Metadata", + savedMetadata.map((metadata) => new AgentMetadataTreeItem(metadata)), + ) + items.push(metadataSection) + } + return Promise.resolve(items) + } else if (element instanceof SectionTreeItem) { + // Return the children of the section + return Promise.resolve(element.children) } return Promise.resolve([]) @@ -298,6 +313,19 @@ function monitorMetadata(agentId: WorkspaceAgent["id"], restClient: Api): AgentW return watcher } +/** + * A tree item that represents a collapsible section with child items + */ +class SectionTreeItem extends vscode.TreeItem { + constructor( + label: string, + public readonly children: vscode.TreeItem[], + ) { + super(label, vscode.TreeItemCollapsibleState.Expanded) + this.contextValue = "coderSectionHeader" + } +} + class ErrorTreeItem extends vscode.TreeItem { constructor(error: unknown) { super("Failed to query metadata: " + errToStr(error, "no error provided"), vscode.TreeItemCollapsibleState.None) @@ -382,13 +410,13 @@ class AgentTreeItem extends OpenableTreeItem { ) if (agent.task_waiting_for_user_input) { - this.label = "🙋 " + this.label; + this.label = "🙋 " + this.label } } } export class WorkspaceTreeItem extends OpenableTreeItem { - public aiTasks: {waiting: boolean, tasks: WorkspaceAgentTask[]}[] = [] + public aiTasks: { waiting: boolean; tasks: WorkspaceAgentTask[] }[] = [] constructor( public readonly workspace: Workspace, From f39f458af0cde633980fedd2a0f1f09d30172164 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 19 Mar 2025 10:28:38 -0500 Subject: [PATCH 06/27] tweaks to terminal sizing --- src/commands.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/commands.ts b/src/commands.ts index c33d1713..cd29afe4 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -411,10 +411,16 @@ export class Commands { // Then launch an integrated terminal with screen session const terminal = vscode.window.createTerminal({ name: "Claude Code Session", + location: vscode.TerminalLocation.Panel }) // Show the terminal and run the screen command terminal.show(true) + + // Hide sidebar and maximize terminal panel + // await vscode.commands.executeCommand("workbench.action.toggleSidebarVisibility") + await vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") + terminal.sendText("screen -xRR claude-code") } From c31cb7c27b0004e1460ef9f722bb7966b888d03b Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 31 Mar 2025 10:33:49 -0500 Subject: [PATCH 07/27] put coder dep back on main post merge of ai work --- package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 61b5ea47..18653143 100644 --- a/package.json +++ b/package.json @@ -313,7 +313,7 @@ "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.21.1", "bufferutil": "^4.0.8", - "coder": "https://github.com/coder/coder#kyle/tasks", + "coder": "https://github.com/coder/coder#main", "dayjs": "^1.11.13", "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", diff --git a/yarn.lock b/yarn.lock index e0f43fc1..798f958a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1592,9 +1592,9 @@ co@3.1.0: resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" integrity sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA== -"coder@https://github.com/coder/coder#kyle/tasks": +"coder@https://github.com/coder/coder#main": version "0.0.0" - resolved "https://github.com/coder/coder#87e086298f19fec18b2ba18bf4ff39081d670570" + resolved "https://github.com/coder/coder#8ea956fc115c221f198dd2c54538c93fc03c91cf" collapse-white-space@^1.0.2: version "1.0.6" From 063b27e21f475942457bd88305a344f57bf12165 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 09:06:19 -0500 Subject: [PATCH 08/27] statuses updates --- src/commands.ts | 29 +++++++------- src/extension.ts | 2 +- src/workspacesProvider.ts | 83 +++++++++++++++++++++++++++------------ yarn.lock | 2 +- 4 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index cd29afe4..491b4fcd 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -407,21 +407,22 @@ export class Commands { } } - public async openAISession(): Promise { - // Then launch an integrated terminal with screen session - const terminal = vscode.window.createTerminal({ - name: "Claude Code Session", - location: vscode.TerminalLocation.Panel - }) + public async openAppStatus(app: { + name?: string + status?: string + url?: string + agent_name?: string + }): Promise { + // Check if app has a URL to open + if (app.url) { + await vscode.env.openExternal(vscode.Uri.parse(app.url)) + return + } - // Show the terminal and run the screen command - terminal.show(true) - - // Hide sidebar and maximize terminal panel - // await vscode.commands.executeCommand("workbench.action.toggleSidebarVisibility") - await vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") - - terminal.sendText("screen -xRR claude-code") + // If no URL, show information about the app status + vscode.window.showInformationMessage(`${app.name || "Application"}: ${app.status || "Running"}`, { + detail: `Agent: ${app.agent_name || "Unknown"}`, + }) } /** diff --git a/src/extension.ts b/src/extension.ts index f04743ed..1dbb3836 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -124,7 +124,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) vscode.commands.registerCommand("coder.openFromSidebar", commands.openFromSidebar.bind(commands)) - vscode.commands.registerCommand("coder.openAITask", commands.openAISession.bind(commands)) + vscode.commands.registerCommand("coder.openAppStatus", commands.openAppStatus.bind(commands)) vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands)) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index aa66a577..806c37f7 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -1,5 +1,5 @@ import { Api } from "coder/site/src/api/api" -import { Workspace, WorkspaceAgent, WorkspaceAgentTask } from "coder/site/src/api/typesGenerated" +import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" import { EventSource } from "eventsource" import * as path from "path" import * as vscode from "vscode" @@ -155,14 +155,26 @@ export class WorkspaceProvider implements vscode.TreeDataProvider() + const agents = extractAgents(workspace) + agents.forEach((agent) => { + // Check if agent has apps property with status reporting + if (agent.apps && Array.isArray(agent.apps)) { + workspaceTreeItem.appStatus = agent.apps.map((app) => ({ + name: app.display_name || app.name || "App", + status: app.status || "Running", + icon: app.icon || "$(pulse)", + url: app.url, + agent_id: agent.id, + agent_name: agent.name, + })) + } + }) } catch (error) { - // Log the error but continue - we don't want to fail the whole tree if AI tasks fail + // Log the error but continue - we don't want to fail the whole tree if app status fails this.storage.writeToCoderOutputChannel( - `Failed to fetch AI tasks for workspace ${workspace.name}: ${errToStr(error, "unknown error")}`, + `Failed to get app status for workspace ${workspace.name}: ${errToStr(error, "unknown error")}`, ) } @@ -239,13 +251,24 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { - const aiTasksSection = new SectionTreeItem( - "AI Tasks", - element.agent.tasks.map((task) => new AITaskTreeItem(task)), + // Add app status section with collapsible header + if (element.agent.apps && element.agent.apps.length > 0) { + let needsAttention = [] + for (const app of element.agent.apps) { + if (app.statuses && app.statuses.length > 0) { + for (const status of app.statuses) { + if (status.needs_user_attention) { + needsAttention.push(new AppStatusTreeItem(status)) + } + } + } + } + + const appStatusSection = new SectionTreeItem( + "Applications in need of attention", + needsAttention, ) - items.push(aiTasksSection) + items.push(appStatusSection) } const savedMetadata = watcher?.metadata || [] @@ -346,18 +369,27 @@ class AgentMetadataTreeItem extends vscode.TreeItem { } } -class AITaskTreeItem extends vscode.TreeItem { - constructor(public readonly task: WorkspaceAgentTask) { - // Add a hand raise emoji (✋) to indicate tasks awaiting user input - super(task.icon, vscode.TreeItemCollapsibleState.None) - this.description = task.summary - this.contextValue = "coderAITask" +class AppStatusTreeItem extends vscode.TreeItem { + constructor( + public readonly app: { + name?: string + display_name?: string + status?: string + icon?: string + url?: string + agent_id?: string + agent_name?: string + }, + ) { + super(app.icon || "$(pulse)", vscode.TreeItemCollapsibleState.None) + this.description = app.status || "Running" + this.contextValue = "coderAppStatus" - // Add command to handle clicking on the task + // Add command to handle clicking on the app this.command = { - command: "coder.openAITask", - title: "Open AI Task", - arguments: [task], + command: "coder.openAppStatus", + title: "Open App Status", + arguments: [app], } } } @@ -409,14 +441,15 @@ class AgentTreeItem extends OpenableTreeItem { "coderAgent", ) - if (agent.task_waiting_for_user_input) { - this.label = "🙋 " + this.label + if (agent.apps && agent.apps.length > 0) { + // Add an icon to indicate this agent has running apps + this.label = "🖐️ " + this.label } } } export class WorkspaceTreeItem extends OpenableTreeItem { - public aiTasks: { waiting: boolean; tasks: WorkspaceAgentTask[] }[] = [] + public appStatus: { name: string; status: string; icon?: string }[] = [] constructor( public readonly workspace: Workspace, diff --git a/yarn.lock b/yarn.lock index 798f958a..fb2ad02d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1594,7 +1594,7 @@ co@3.1.0: "coder@https://github.com/coder/coder#main": version "0.0.0" - resolved "https://github.com/coder/coder#8ea956fc115c221f198dd2c54538c93fc03c91cf" + resolved "https://github.com/coder/coder#3a243c111b9abb5c38328169ff70064025bbe2fe" collapse-white-space@^1.0.2: version "1.0.6" From be1e137c85147f198d13cbe8a66d25fce1571012 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 12:41:23 -0500 Subject: [PATCH 09/27] coder ssh strategy finally working --- package.json | 11 +++-------- src/commands.ts | 20 ++++++++++++++++++- src/workspacesProvider.ts | 41 ++++++++++++++++++++++++++------------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 18653143..71e355bd 100644 --- a/package.json +++ b/package.json @@ -211,8 +211,8 @@ "when": "coder.authenticated" }, { - "command": "coder.openAITask", - "title": "Coder: Open AI Task", + "command": "coder.openAppStatus", + "title": "Coder: Open App Status", "icon": "$(robot)", "when": "coder.authenticated" } @@ -244,7 +244,7 @@ "group": "navigation" }, { - "command": "coder.openAITask", + "command": "coder.openAppStatus", "when": "coder.authenticated && view == myWorkspaces", "group": "navigation" } @@ -280,11 +280,6 @@ "command": "coder.createWorkspace", "group": "remote_11_ssh_coder@2", "when": "coder.authenticated" - }, - { - "command": "coder.openAITask", - "group": "remote_11_ssh_coder@3", - "when": "coder.authenticated" } ] } diff --git a/src/commands.ts b/src/commands.ts index 491b4fcd..7635fbfc 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -412,14 +412,32 @@ export class Commands { status?: string url?: string agent_name?: string + command?: string + workspace_name?: string }): Promise { + // Launch and run command in terminal if command is provided + if (app.command) { + const terminal = vscode.window.createTerminal(`${app.name || "Application"} Status`) + terminal.show(false) + vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") + // If workspace_name is provided, run coder ssh before the command + if (app.workspace_name) { + terminal.sendText(`coder ssh ${app.workspace_name}`) + // Sleep for 5 seconds + await new Promise((resolve) => setTimeout(resolve, 5000)) + terminal.sendText(app.command) + } else { + terminal.sendText("need workspace name") + } + return + } // Check if app has a URL to open if (app.url) { await vscode.env.openExternal(vscode.Uri.parse(app.url)) return } - // If no URL, show information about the app status + // If no URL or command, show information about the app status vscode.window.showInformationMessage(`${app.name || "Application"}: ${app.status || "Running"}`, { detail: `Agent: ${app.agent_name || "Unknown"}`, }) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 806c37f7..f795b96d 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -168,6 +168,8 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { - let needsAttention = [] + const needsAttention = [] for (const app of element.agent.apps) { if (app.statuses && app.statuses.length > 0) { for (const status of app.statuses) { if (status.needs_user_attention) { - needsAttention.push(new AppStatusTreeItem(status)) + needsAttention.push( + new AppStatusTreeItem({ + name: status.message, + command: app.command, + status: status.state, + workspace_name: element.workspaceName, + }), + ) } } } } - const appStatusSection = new SectionTreeItem( - "Applications in need of attention", - needsAttention, - ) + const appStatusSection = new SectionTreeItem("Applications in need of attention", needsAttention) items.push(appStatusSection) } @@ -372,17 +378,15 @@ class AgentMetadataTreeItem extends vscode.TreeItem { class AppStatusTreeItem extends vscode.TreeItem { constructor( public readonly app: { - name?: string - display_name?: string + name: string status?: string - icon?: string url?: string - agent_id?: string - agent_name?: string + command?: string + workspace_name?: string }, ) { - super(app.icon || "$(pulse)", vscode.TreeItemCollapsibleState.None) - this.description = app.status || "Running" + super(app.name, vscode.TreeItemCollapsibleState.None) + this.description = app.status this.contextValue = "coderAppStatus" // Add command to handle clicking on the app @@ -449,7 +453,16 @@ class AgentTreeItem extends OpenableTreeItem { } export class WorkspaceTreeItem extends OpenableTreeItem { - public appStatus: { name: string; status: string; icon?: string }[] = [] + public appStatus: { + name: string + status: string + icon?: string + url?: string + agent_id?: string + agent_name?: string + command?: string + workspace_name?: string + }[] = [] constructor( public readonly workspace: Workspace, From 08c93ae6ca981180932c531f469c6d7a9c6670cc Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 13:17:32 -0500 Subject: [PATCH 10/27] show apps in need of attention only when there are some to show --- src/commands.ts | 2 +- src/workspacesProvider.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 7635fbfc..c6edff76 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -418,7 +418,7 @@ export class Commands { // Launch and run command in terminal if command is provided if (app.command) { const terminal = vscode.window.createTerminal(`${app.name || "Application"} Status`) - terminal.show(false) + terminal.show(true) vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") // If workspace_name is provided, run coder ssh before the command if (app.workspace_name) { diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index f795b96d..baff46ba 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -273,8 +273,11 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { + const appStatusSection = new SectionTreeItem("Applications in need of attention", needsAttention) + items.push(appStatusSection) + } } const savedMetadata = watcher?.metadata || [] From 99f3b1d8eea467bf1f9fd4ba61be28eb9b9ad100 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 17:55:14 -0500 Subject: [PATCH 11/27] working with goose and claude --- src/workspacesProvider.ts | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index baff46ba..3de35e42 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -255,27 +255,26 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { - const needsAttention = [] + const appStatuses = [] for (const app of element.agent.apps) { if (app.statuses && app.statuses.length > 0) { for (const status of app.statuses) { - if (status.needs_user_attention) { - needsAttention.push( - new AppStatusTreeItem({ - name: status.message, - command: app.command, - status: status.state, - workspace_name: element.workspaceName, - }), - ) - } + // Show all statuses, not just ones needing attention + appStatuses.push( + new AppStatusTreeItem({ + name: status.message, + command: app.command, + status: status.state, + workspace_name: element.workspaceName, + }), + ) } } } - // Only show the section if it has items that need attention - if (needsAttention.length > 0) { - const appStatusSection = new SectionTreeItem("Applications in need of attention", needsAttention) + // Show the section if it has any items + if (appStatuses.length > 0) { + const appStatusSection = new SectionTreeItem("Application Statuses", appStatuses) items.push(appStatusSection) } } @@ -353,7 +352,7 @@ class SectionTreeItem extends vscode.TreeItem { label: string, public readonly children: vscode.TreeItem[], ) { - super(label, vscode.TreeItemCollapsibleState.Expanded) + super(label, vscode.TreeItemCollapsibleState.Collapsed) this.contextValue = "coderSectionHeader" } } From cd7c68c3f33b6bfb8912925f2f1d160aed262fcc Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 18:27:29 -0500 Subject: [PATCH 12/27] switch up labels --- src/workspacesProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 3de35e42..91760fc8 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -262,9 +262,9 @@ export class WorkspaceProvider implements vscode.TreeDataProvider Date: Tue, 1 Apr 2025 18:31:23 -0500 Subject: [PATCH 13/27] remove hand raise emojis --- src/workspacesProvider.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 91760fc8..888f0b39 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -259,7 +259,9 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { for (const status of app.statuses) { - // Show all statuses, not just ones needing attention + // Show all statuses, not just ones needing attention. + // We need to do this for now because the reporting isn't super accurate + // yet. appStatuses.push( new AppStatusTreeItem({ name: status.icon, @@ -449,7 +451,7 @@ class AgentTreeItem extends OpenableTreeItem { if (agent.apps && agent.apps.length > 0) { // Add an icon to indicate this agent has running apps - this.label = "🖐️ " + this.label + this.label = this.label } } } From ac8d5fdca1c5c0baea61f4d12e57dfd65704fa26 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 18:31:53 -0500 Subject: [PATCH 14/27] app not application --- src/workspacesProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 888f0b39..3836fb4a 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -276,7 +276,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { - const appStatusSection = new SectionTreeItem("Application Statuses", appStatuses) + const appStatusSection = new SectionTreeItem("App Statuses", appStatuses) items.push(appStatusSection) } } From 69f9b97746345183ec5c0b88f9b9f05efc480092 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 18:43:01 -0500 Subject: [PATCH 15/27] remove unused commands --- package.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/package.json b/package.json index d9a9ed78..a8292e61 100644 --- a/package.json +++ b/package.json @@ -170,11 +170,6 @@ "title": "Coder: Open Workspace", "icon": "$(play)" }, - { - "command": "coder.openFromSidebarAndOpenSession", - "title": "Coder: Open Workspace with Claude Code Session", - "icon": "$(terminal)" - }, { "command": "coder.createWorkspace", "title": "Create Workspace", @@ -242,11 +237,6 @@ "command": "coder.refreshWorkspaces", "when": "coder.authenticated && view == myWorkspaces", "group": "navigation" - }, - { - "command": "coder.openAppStatus", - "when": "coder.authenticated && view == myWorkspaces", - "group": "navigation" } ], "view/item/context": [ @@ -255,11 +245,6 @@ "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderAgent", "group": "inline" }, - { - "command": "coder.openFromSidebarAndOpenSession", - "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderAgent", - "group": "inline" - }, { "command": "coder.navigateToWorkspace", "when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderWorkspaceMultipleAgents", From 68c2e04bf7ab380d951da357233739e2dd202b3e Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 18:48:15 -0500 Subject: [PATCH 16/27] terminal names --- src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.ts b/src/commands.ts index c6edff76..f75d4174 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -417,7 +417,7 @@ export class Commands { }): Promise { // Launch and run command in terminal if command is provided if (app.command) { - const terminal = vscode.window.createTerminal(`${app.name || "Application"} Status`) + const terminal = vscode.window.createTerminal(app.status) terminal.show(true) vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") // If workspace_name is provided, run coder ssh before the command From b1e281cc3e0f6da9b8e983a75f4c78a4651f315d Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 1 Apr 2025 23:09:56 -0500 Subject: [PATCH 17/27] use built in coder cli --- src/commands.ts | 12 ++++++++++-- yarn.lock | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index f75d4174..026ca286 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -8,6 +8,7 @@ import { CertificateError } from "./error" import { Storage } from "./storage" import { AuthorityPrefix, toSafeHost } from "./util" import { OpenableTreeItem } from "./workspacesProvider" +import path from "node:path" export class Commands { // These will only be populated when actively connected to a workspace and are @@ -422,8 +423,15 @@ export class Commands { vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") // If workspace_name is provided, run coder ssh before the command if (app.workspace_name) { - terminal.sendText(`coder ssh ${app.workspace_name}`) - // Sleep for 5 seconds + let url = this.storage.getUrl() + if (!url) { + throw new Error("No coder url found for sidebar"); + } + let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) + const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` + terminal.sendText(`${escape(binary)} ssh --global-config ${escape( + path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), + )} ${app.workspace_name}`) await new Promise((resolve) => setTimeout(resolve, 5000)) terminal.sendText(app.command) } else { diff --git a/yarn.lock b/yarn.lock index 92284aee..ea065f24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1594,7 +1594,7 @@ co@3.1.0: "coder@https://github.com/coder/coder#main": version "0.0.0" - resolved "https://github.com/coder/coder#3a243c111b9abb5c38328169ff70064025bbe2fe" + resolved "https://github.com/coder/coder#2efb8088f4d923d1884fe8947dc338f9d179693b" collapse-white-space@^1.0.2: version "1.0.6" From 22246b92f1ff09b5e40af2ff1f6e7dd5739e283d Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 15:53:42 -0500 Subject: [PATCH 18/27] reset back to working state pre tmux and reverse app statuses so most recent report is on top --- src/workspacesProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 3836fb4a..a4294b49 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -276,7 +276,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { - const appStatusSection = new SectionTreeItem("App Statuses", appStatuses) + const appStatusSection = new SectionTreeItem("App Statuses", appStatuses.reverse()) items.push(appStatusSection) } } From 80f74f9a64cda40202572b945553c551eb36926d Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 15:56:38 -0500 Subject: [PATCH 19/27] only show terminal after ssh command and app commands run --- src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.ts b/src/commands.ts index bce860ad..f827d6b3 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -419,7 +419,6 @@ export class Commands { // Launch and run command in terminal if command is provided if (app.command) { const terminal = vscode.window.createTerminal(app.status) - terminal.show(true) vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") // If workspace_name is provided, run coder ssh before the command if (app.workspace_name) { @@ -437,6 +436,7 @@ export class Commands { } else { terminal.sendText("need workspace name") } + terminal.show(false) return } // Check if app has a URL to open From 0f7dd65dc29f6d59c9ef5459a1e4a5c47958af16 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:06:10 -0500 Subject: [PATCH 20/27] loading indicator --- src/commands.ts | 55 ++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index f827d6b3..ca449059 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -418,31 +418,44 @@ export class Commands { }): Promise { // Launch and run command in terminal if command is provided if (app.command) { - const terminal = vscode.window.createTerminal(app.status) - vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") - // If workspace_name is provided, run coder ssh before the command - if (app.workspace_name) { - let url = this.storage.getUrl() - if (!url) { - throw new Error("No coder url found for sidebar"); + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Launching ${app.name || "application"}...`, + cancellable: false + }, async () => { + const terminal = vscode.window.createTerminal(app.status) + + // If workspace_name is provided, run coder ssh before the command + if (app.workspace_name) { + let url = this.storage.getUrl() + if (!url) { + throw new Error("No coder url found for sidebar"); + } + let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) + const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` + terminal.sendText(`${escape(binary)} ssh --global-config ${escape( + path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), + )} ${app.workspace_name}`) + await new Promise((resolve) => setTimeout(resolve, 5000)) + terminal.sendText(app.command ?? "") + } else { + terminal.sendText("need workspace name") } - let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) - const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` - terminal.sendText(`${escape(binary)} ssh --global-config ${escape( - path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), - )} ${app.workspace_name}`) - await new Promise((resolve) => setTimeout(resolve, 5000)) - terminal.sendText(app.command) - } else { - terminal.sendText("need workspace name") - } - terminal.show(false) - return + + // Maximise the terminal and switch focus to the launch terminal window. + vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") + terminal.show(false) + }); } // Check if app has a URL to open if (app.url) { - await vscode.env.openExternal(vscode.Uri.parse(app.url)) - return + return vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Opening ${app.name || "application"} in browser...`, + cancellable: false + }, async () => { + await vscode.env.openExternal(vscode.Uri.parse(app.url!)) + }); } // If no URL or command, show information about the app status From 8f996dd18f432c7a42611efdd5de7b9c56e2cc93 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:07:45 -0500 Subject: [PATCH 21/27] update loading msg --- src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.ts b/src/commands.ts index ca449059..27ed5b48 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -420,7 +420,7 @@ export class Commands { if (app.command) { return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: `Launching ${app.name || "application"}...`, + title: `Connecting to AI Agent...`, cancellable: false }, async () => { const terminal = vscode.window.createTerminal(app.status) From c24b675dd4b501d406d7fc8c4a3098b6fee85e6d Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:18:58 -0500 Subject: [PATCH 22/27] don't mess with terminal size --- src/commands.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 27ed5b48..9a4752e5 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -441,9 +441,6 @@ export class Commands { } else { terminal.sendText("need workspace name") } - - // Maximise the terminal and switch focus to the launch terminal window. - vscode.commands.executeCommand("workbench.action.toggleMaximizedPanel") terminal.show(false) }); } From 6f83606730f7f848cab99ca9fad449b45dea1084 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:24:30 -0500 Subject: [PATCH 23/27] workspace name isn't optional --- src/commands.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 9a4752e5..7a472f4b 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -414,7 +414,7 @@ export class Commands { url?: string agent_name?: string command?: string - workspace_name?: string + workspace_name: string }): Promise { // Launch and run command in terminal if command is provided if (app.command) { @@ -426,7 +426,7 @@ export class Commands { const terminal = vscode.window.createTerminal(app.status) // If workspace_name is provided, run coder ssh before the command - if (app.workspace_name) { + let url = this.storage.getUrl() if (!url) { throw new Error("No coder url found for sidebar"); @@ -438,9 +438,6 @@ export class Commands { )} ${app.workspace_name}`) await new Promise((resolve) => setTimeout(resolve, 5000)) terminal.sendText(app.command ?? "") - } else { - terminal.sendText("need workspace name") - } terminal.show(false) }); } From 579ad4ebbc0a0df8a02e085251cdc8f3530da3ce Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:28:18 -0500 Subject: [PATCH 24/27] changelog and format --- CHANGELOG.md | 4 ++++ src/commands.ts | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f3a583..6b1c4409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added +- Coder extension sidebar now displays available app statuses, and let's + the user click them to drop into a session with a running AI Agent. + ## [v1.7.1](https://github.com/coder/vscode-coder/releases/tag/v1.7.1) (2025-04-14) ### Fixed diff --git a/src/commands.ts b/src/commands.ts index 7a472f4b..bc14a00f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -26,7 +26,7 @@ export class Commands { private readonly vscodeProposed: typeof vscode, private readonly restClient: Api, private readonly storage: Storage, - ) {} + ) { } /** * Find the requested agent if specified, otherwise return the agent if there @@ -424,20 +424,20 @@ export class Commands { cancellable: false }, async () => { const terminal = vscode.window.createTerminal(app.status) - + // If workspace_name is provided, run coder ssh before the command - - let url = this.storage.getUrl() - if (!url) { - throw new Error("No coder url found for sidebar"); - } - let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) - const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` - terminal.sendText(`${escape(binary)} ssh --global-config ${escape( - path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), - )} ${app.workspace_name}`) - await new Promise((resolve) => setTimeout(resolve, 5000)) - terminal.sendText(app.command ?? "") + + let url = this.storage.getUrl() + if (!url) { + throw new Error("No coder url found for sidebar"); + } + let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) + const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` + terminal.sendText(`${escape(binary)} ssh --global-config ${escape( + path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), + )} ${app.workspace_name}`) + await new Promise((resolve) => setTimeout(resolve, 5000)) + terminal.sendText(app.command ?? "") terminal.show(false) }); } From 9340b7dff4919ba8525ff0c20bf23f91151f5476 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:41:50 -0500 Subject: [PATCH 25/27] remove unused icon code --- src/workspacesProvider.ts | 41 ++++++++++++++------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index a4294b49..68f69113 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -1,5 +1,5 @@ import { Api } from "coder/site/src/api/api" -import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" +import { Workspace, WorkspaceAgent, WorkspaceApp } from "coder/site/src/api/typesGenerated" import { EventSource } from "eventsource" import * as path from "path" import * as vscode from "vscode" @@ -156,29 +156,20 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { - // Check if agent has apps property with status reporting - if (agent.apps && Array.isArray(agent.apps)) { - workspaceTreeItem.appStatus = agent.apps.map((app) => ({ - name: app.display_name || app.name || "App", - status: app.status || "Running", - icon: app.icon || "$(pulse)", - url: app.url, - agent_id: agent.id, - agent_name: agent.name, - command: app.command, - workspace_name: workspace.name, - })) - } - }) - } catch (error) { - // Log the error but continue - we don't want to fail the whole tree if app status fails - this.storage.writeToCoderOutputChannel( - `Failed to get app status for workspace ${workspace.name}: ${errToStr(error, "unknown error")}`, - ) - } + const agents = extractAgents(workspace) + agents.forEach((agent) => { + // Check if agent has apps property with status reporting + if (agent.apps && Array.isArray(agent.apps)) { + workspaceTreeItem.appStatus = agent.apps.map((app: WorkspaceApp) => ({ + name: app.display_name, + url: app.url, + agent_id: agent.id, + agent_name: agent.name, + command: app.command, + workspace_name: workspace.name, + })) + } + }) return workspaceTreeItem }), @@ -459,8 +450,6 @@ class AgentTreeItem extends OpenableTreeItem { export class WorkspaceTreeItem extends OpenableTreeItem { public appStatus: { name: string - status: string - icon?: string url?: string agent_id?: string agent_name?: string From 8dac372f0ebc44ba2b404c2d1429a484267d4c9f Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 21 Apr 2025 16:49:03 -0500 Subject: [PATCH 26/27] cleanup --- src/commands.ts | 5 ++--- src/workspacesProvider.ts | 8 +++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index bc14a00f..63be87d2 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -410,7 +410,6 @@ export class Commands { public async openAppStatus(app: { name?: string - status?: string url?: string agent_name?: string command?: string @@ -423,7 +422,7 @@ export class Commands { title: `Connecting to AI Agent...`, cancellable: false }, async () => { - const terminal = vscode.window.createTerminal(app.status) + const terminal = vscode.window.createTerminal(app.name) // If workspace_name is provided, run coder ssh before the command @@ -453,7 +452,7 @@ export class Commands { } // If no URL or command, show information about the app status - vscode.window.showInformationMessage(`${app.name || "Application"}: ${app.status || "Running"}`, { + vscode.window.showInformationMessage(`${app.name}`, { detail: `Agent: ${app.agent_name || "Unknown"}`, }) } diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 68f69113..dff3bbd7 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -255,9 +255,8 @@ export class WorkspaceProvider implements vscode.TreeDataProvider Date: Mon, 21 Apr 2025 17:07:17 -0500 Subject: [PATCH 27/27] remove unnecessary label assignment (I think there used to be an icon there) --- CHANGELOG.md | 1 + src/commands.ts | 70 ++++++++++++++++++++++----------------- src/workspacesProvider.ts | 7 +--- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b1c4409..aeba3f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added + - Coder extension sidebar now displays available app statuses, and let's the user click them to drop into a session with a running AI Agent. diff --git a/src/commands.ts b/src/commands.ts index 63be87d2..830347e0 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,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 path from "node:path" import * as vscode from "vscode" import { makeCoderSdk, needToken } from "./api" import { extractAgents } from "./api-helper" @@ -8,7 +9,6 @@ import { CertificateError } from "./error" import { Storage } from "./storage" import { toRemoteAuthority, toSafeHost } from "./util" import { OpenableTreeItem } from "./workspacesProvider" -import path from "node:path" export class Commands { // These will only be populated when actively connected to a workspace and are @@ -26,7 +26,7 @@ export class Commands { private readonly vscodeProposed: typeof vscode, private readonly restClient: Api, private readonly storage: Storage, - ) { } + ) {} /** * Find the requested agent if specified, otherwise return the agent if there @@ -417,38 +417,46 @@ export class Commands { }): Promise { // Launch and run command in terminal if command is provided if (app.command) { - return vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: `Connecting to AI Agent...`, - cancellable: false - }, async () => { - const terminal = vscode.window.createTerminal(app.name) - - // If workspace_name is provided, run coder ssh before the command - - let url = this.storage.getUrl() - if (!url) { - throw new Error("No coder url found for sidebar"); - } - let binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) - const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` - terminal.sendText(`${escape(binary)} ssh --global-config ${escape( - path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), - )} ${app.workspace_name}`) - await new Promise((resolve) => setTimeout(resolve, 5000)) - terminal.sendText(app.command ?? "") - terminal.show(false) - }); + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Connecting to AI Agent...`, + cancellable: false, + }, + async () => { + const terminal = vscode.window.createTerminal(app.name) + + // If workspace_name is provided, run coder ssh before the command + + const url = this.storage.getUrl() + if (!url) { + throw new Error("No coder url found for sidebar") + } + const binary = await this.storage.fetchBinary(this.restClient, toSafeHost(url)) + const escape = (str: string): string => `"${str.replace(/"/g, '\\"')}"` + terminal.sendText( + `${escape(binary)} ssh --global-config ${escape( + path.dirname(this.storage.getSessionTokenPath(toSafeHost(url))), + )} ${app.workspace_name}`, + ) + await new Promise((resolve) => setTimeout(resolve, 5000)) + terminal.sendText(app.command ?? "") + terminal.show(false) + }, + ) } // Check if app has a URL to open if (app.url) { - return vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: `Opening ${app.name || "application"} in browser...`, - cancellable: false - }, async () => { - await vscode.env.openExternal(vscode.Uri.parse(app.url!)) - }); + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Opening ${app.name || "application"} in browser...`, + cancellable: false, + }, + async () => { + await vscode.env.openExternal(vscode.Uri.parse(app.url!)) + }, + ) } // If no URL or command, show information about the app status diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index dff3bbd7..0f821a2f 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -251,7 +251,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider 0) { for (const status of app.statuses) { // Show all statuses, not just ones needing attention. - // We need to do this for now because the reporting isn't super accurate + // We need to do this for now because the reporting isn't super accurate // yet. appStatuses.push( new AppStatusTreeItem({ @@ -437,11 +437,6 @@ class AgentTreeItem extends OpenableTreeItem { agent.expanded_directory, "coderAgent", ) - - if (agent.apps && agent.apps.length > 0) { - // Add an icon to indicate this agent has running apps - this.label = this.label - } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy