Skip to content

Commit e76d22e

Browse files
committed
First pass of cloud agents in extension, with mock data
1 parent 9f41ee0 commit e76d22e

File tree

15 files changed

+406
-88
lines changed

15 files changed

+406
-88
lines changed

packages/cloud/src/CloudAPI.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { z } from "zod"
22

3-
import { type AuthService, type ShareVisibility, type ShareResponse, shareResponseSchema } from "@roo-code/types"
3+
import {
4+
type AuthService,
5+
type ShareVisibility,
6+
type ShareResponse,
7+
shareResponseSchema,
8+
type CloudAgent,
9+
} from "@roo-code/types"
410

511
import { getRooCodeApiUrl } from "./config.js"
612
import { getUserAgent } from "./utils.js"
@@ -134,4 +140,27 @@ export class CloudAPI {
134140
.parse(data),
135141
})
136142
}
143+
144+
async getCloudAgents(): Promise<CloudAgent[]> {
145+
try {
146+
this.log("[CloudAPI] Fetching cloud agents")
147+
148+
const response = await this.request<{ agents: CloudAgent[] }>("/api/cloud_agents", {
149+
method: "GET",
150+
})
151+
152+
this.log("[CloudAPI] Cloud agents response:", response)
153+
return response.agents || []
154+
} catch (error) {
155+
this.log("[CloudAPI] Failed to fetch cloud agents, returning mock data:", error)
156+
157+
// Return mock data when API fails as requested
158+
return [
159+
{ id: "1", name: "Code Assistant", type: "code", icon: "💻" },
160+
{ id: "2", name: "Test Generator", type: "test", icon: "🧪" },
161+
{ id: "3", name: "Code Reviewer", type: "review", icon: "👁️" },
162+
{ id: "4", name: "Documentation Writer", type: "docs", icon: "📝" },
163+
]
164+
}
165+
}
137166
}

packages/types/src/cloud.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,3 +721,30 @@ export type LeaveResponse = {
721721
taskId?: string
722722
timestamp?: string
723723
}
724+
725+
/**
726+
* CloudAgent
727+
*/
728+
729+
export interface CloudAgent {
730+
id: string
731+
name: string
732+
type: string // e.g., "code", "review", "test", "docs"
733+
}
734+
735+
/**
736+
* CloudAgents API Response
737+
*/
738+
739+
export const cloudAgentsResponseSchema = z.object({
740+
agents: z.array(
741+
z.object({
742+
id: z.string(),
743+
name: z.string(),
744+
type: z.string(),
745+
icon: z.string(),
746+
}),
747+
),
748+
})
749+
750+
export type CloudAgentsResponse = z.infer<typeof cloudAgentsResponseSchema>

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
979979
"flex-col-reverse",
980980
"min-h-0",
981981
"overflow-hidden",
982-
"rounded",
982+
"rounded-lg",
983983
)}>
984984
<div
985985
ref={highlightLayerRef}
@@ -1005,7 +1005,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
10051005
isEditMode ? "pr-20" : "pr-9",
10061006
"z-10",
10071007
"forced-color-adjust-none",
1008-
"rounded",
1008+
"rounded-lg",
10091009
)}
10101010
style={{
10111011
color: "transparent",

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 31 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
4444

4545
import TelemetryBanner from "../common/TelemetryBanner"
4646
import VersionIndicator from "../common/VersionIndicator"
47-
import { useTaskSearch } from "../history/useTaskSearch"
4847
import HistoryPreview from "../history/HistoryPreview"
4948
import Announcement from "./Announcement"
5049
import BrowserSessionRow from "./BrowserSessionRow"
@@ -58,6 +57,7 @@ import { QueuedMessages } from "./QueuedMessages"
5857
import DismissibleUpsell from "../common/DismissibleUpsell"
5958
import { useCloudUpsell } from "@src/hooks/useCloudUpsell"
6059
import { Cloud } from "lucide-react"
60+
import CloudAgents from "../cloud/CloudAgents"
6161

6262
export interface ChatViewProps {
6363
isHidden: boolean
@@ -118,10 +118,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
118118
customModes,
119119
telemetrySetting,
120120
hasSystemPromptOverride,
121-
historyPreviewCollapsed, // Added historyPreviewCollapsed
122121
soundEnabled,
123122
soundVolume,
124123
cloudIsAuthenticated,
124+
cloudApiUrl,
125125
messageQueue = [],
126126
} = useExtensionState()
127127

@@ -131,20 +131,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
131131
messagesRef.current = messages
132132
}, [messages])
133133

134-
const { tasks } = useTaskSearch()
135-
136-
// Initialize expanded state based on the persisted setting (default to expanded if undefined)
137-
const [isExpanded, setIsExpanded] = useState(
138-
historyPreviewCollapsed === undefined ? true : !historyPreviewCollapsed,
139-
)
140-
141-
const toggleExpanded = useCallback(() => {
142-
const newState = !isExpanded
143-
setIsExpanded(newState)
144-
// Send message to extension to persist the new collapsed state
145-
vscode.postMessage({ type: "setHistoryPreviewCollapsed", bool: !newState })
146-
}, [isExpanded])
147-
148134
// Leaving this less safe version here since if the first message is not a
149135
// task, then the extension is in a bad state and needs to be debugged (see
150136
// Cline.abort).
@@ -1817,53 +1803,41 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
18171803
)}
18181804
</>
18191805
) : (
1820-
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-4 relative">
1821-
{/* Moved Task Bar Header Here */}
1822-
{tasks.length !== 0 && (
1823-
<div className="flex text-vscode-descriptionForeground w-full mx-auto px-5 pt-3">
1824-
<div className="flex items-center gap-1 cursor-pointer" onClick={toggleExpanded}>
1825-
{tasks.length < 10 && (
1826-
<span className={`font-medium text-xs `}>{t("history:recentTasks")}</span>
1827-
)}
1828-
<span
1829-
className={`codicon ${isExpanded ? "codicon-eye" : "codicon-eye-closed"} scale-90`}
1830-
/>
1831-
</div>
1832-
</div>
1833-
)}
1834-
<div
1835-
className={` w-full flex flex-col gap-4 m-auto ${isExpanded && tasks.length > 0 ? "mt-0" : ""} px-3.5 min-[370px]:px-10 pt-5 transition-all duration-300`}>
1836-
{/* Version indicator in top-right corner - only on welcome screen */}
1806+
<div className="flex flex-col h-full justify-center p-6 min-h-0 overflow-y-auto gap-4 relative">
1807+
<div className="flex flex-col items-start gap-2 justify-center max-w-md h-full">
18371808
<VersionIndicator
18381809
onClick={() => setShowAnnouncementModal(true)}
18391810
className="absolute top-2 right-3 z-10"
18401811
/>
18411812

1842-
<RooHero />
1813+
<div className="flex flex-col gap-4">
1814+
<RooHero />
18431815

1844-
<div className="mb-2.5">
1845-
{cloudIsAuthenticated || taskHistory.length < 4 ? (
1846-
<RooTips />
1847-
) : (
1848-
<>
1849-
<DismissibleUpsell
1850-
upsellId="taskList"
1851-
icon={<Cloud className="size-4 mt-0.5 shrink-0" />}
1852-
onClick={() => openUpsell()}
1853-
dismissOnClick={false}
1854-
className="bg-vscode-editor-background p-4 !text-base">
1855-
<Trans
1856-
i18nKey="cloud:upsell.taskList"
1857-
components={{
1858-
learnMoreLink: <VSCodeLink href="#" />,
1859-
}}
1860-
/>
1861-
</DismissibleUpsell>
1862-
</>
1863-
)}
1816+
{taskHistory.length < 4 && <RooTips />}
1817+
1818+
{taskHistory.length > 0 && <HistoryPreview />}
18641819
</div>
1865-
{/* Show the task history preview if expanded and tasks exist */}
1866-
{taskHistory.length > 0 && isExpanded && <HistoryPreview />}
1820+
1821+
{!cloudIsAuthenticated ? (
1822+
<DismissibleUpsell
1823+
upsellId="taskList"
1824+
icon={<Cloud className="size-5 mt-0.5 shrink-0" />}
1825+
onClick={() => openUpsell()}
1826+
dismissOnClick={false}
1827+
className="!bg-vscode-editor-background mt-6 border-border rounded-xl pl-4 pr-3 py-3 !text-base">
1828+
<Trans
1829+
i18nKey="cloud:upsell.taskList"
1830+
components={{
1831+
learnMoreLink: <VSCodeLink href="#" />,
1832+
}}
1833+
/>
1834+
</DismissibleUpsell>
1835+
) : (
1836+
<CloudAgents
1837+
cloudApiUrl={cloudApiUrl}
1838+
sessionToken={undefined} // Will use mock data for now
1839+
/>
1840+
)}
18671841
</div>
18681842
</div>
18691843
)}
@@ -1989,6 +1963,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
19891963
}
19901964
}}
19911965
/>
1966+
19921967
<ChatTextArea
19931968
ref={textAreaRef}
19941969
inputValue={inputValue}

webview-ui/src/components/chat/TaskHeader.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,10 @@ const TaskHeader = ({
112112
)}
113113
<div
114114
className={cn(
115-
"px-2.5 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
115+
"px-3 pt-2.5 pb-2 flex flex-col gap-1.5 relative z-1 cursor-pointer",
116116
"bg-vscode-input-background hover:bg-vscode-input-background/90",
117117
"text-vscode-foreground/80 hover:text-vscode-foreground",
118-
"shadow-sm shadow-black/30 rounded-md",
118+
"shadow-sm shadow-black/30 rounded-xl",
119119
hasTodos && "border-b-0",
120120
)}
121121
onClick={(e) => {
@@ -163,7 +163,9 @@ const TaskHeader = ({
163163
</div>
164164
</div>
165165
{!isTaskExpanded && contextWindow > 0 && (
166-
<div className="flex items-center gap-2 text-sm" onClick={(e) => e.stopPropagation()}>
166+
<div
167+
className="flex items-center gap-2 text-sm text-muted-foreground/70"
168+
onClick={(e) => e.stopPropagation()}>
167169
<StandardTooltip
168170
content={
169171
<div className="space-y-1">
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React, { useEffect, useState } from "react"
2+
import { Button } from "@/components/ui"
3+
import { Brain, Plus } from "lucide-react"
4+
import type { CloudAgent } from "@roo-code/types"
5+
6+
interface CloudAgentsProps {
7+
cloudApiUrl: string
8+
sessionToken?: string
9+
}
10+
11+
const CloudAgents: React.FC<CloudAgentsProps> = ({ cloudApiUrl, sessionToken }) => {
12+
const [agents, setAgents] = useState<CloudAgent[]>([])
13+
const [loading, setLoading] = useState(true)
14+
const [error, setError] = useState(false)
15+
16+
useEffect(() => {
17+
const fetchAgents = async () => {
18+
try {
19+
setLoading(true)
20+
setError(false)
21+
22+
if (!sessionToken) {
23+
// Use mock data if no session token
24+
const mockAgents: CloudAgent[] = [
25+
{ id: "1", name: "Rooviewer", type: "PR Review" },
26+
{ id: "2", name: "Gertroode", type: "Documentation Writer" },
27+
{ id: "3", name: "Polyglot", type: "String Translator" },
28+
]
29+
setAgents(mockAgents)
30+
return
31+
}
32+
33+
const response = await fetch(`${cloudApiUrl}/api/cloud_agents`, {
34+
headers: {
35+
"Content-Type": "application/json",
36+
Authorization: `Bearer ${sessionToken}`,
37+
},
38+
})
39+
40+
if (!response.ok) {
41+
throw new Error("Failed to fetch agents")
42+
}
43+
44+
const data = await response.json()
45+
setAgents(data.agents || [])
46+
} catch (err) {
47+
console.error("Failed to fetch cloud agents, using mock data:", err)
48+
// Use mock data on error
49+
const mockAgents: CloudAgent[] = [
50+
{ id: "1", name: "Code Assistant", type: "code", icon: "💻" },
51+
{ id: "2", name: "Test Generator", type: "test", icon: "🧪" },
52+
{ id: "3", name: "Code Reviewer", type: "review", icon: "👁️" },
53+
{ id: "4", name: "Documentation Writer", type: "docs", icon: "📝" },
54+
]
55+
setAgents(mockAgents)
56+
} finally {
57+
setLoading(false)
58+
}
59+
}
60+
61+
fetchAgents()
62+
}, [cloudApiUrl, sessionToken])
63+
64+
// If there's an error, show nothing as requested
65+
if (error) {
66+
return null
67+
}
68+
69+
// Don't show loading state, just render nothing until data is ready
70+
if (loading) {
71+
return null
72+
}
73+
74+
const handleAgentClick = (agentId: string) => {
75+
window.open(`${cloudApiUrl}/cloud-agents/${agentId}`, "_blank")
76+
}
77+
78+
const handleCreateClick = () => {
79+
window.open(`${cloudApiUrl}/cloud-agents/create`, "_blank")
80+
}
81+
82+
return (
83+
<div className="flex flex-col gap-3 mt-6 w-full">
84+
<div className="flex flex-wrap items-center justify-between mt-4 mb-2">
85+
<h2 className="font-semibold text-lg shrink-0 m-0">Cloud Agents</h2>
86+
<button
87+
onClick={handleCreateClick}
88+
className="text-base flex items-center gap-1 text-vscode-descriptionForeground hover:text-vscode-textLink-foreground transition-colors cursor-pointer"
89+
title="Create new agent">
90+
<Plus className="h-4 w-4" />
91+
Create
92+
</button>
93+
</div>
94+
95+
{agents.length === 0 ? (
96+
<div className="flex flex-col items-center justify-center py-8 text-center">
97+
<p className="text-sm text-vscode-descriptionForeground mb-4">Create your first cloud agent</p>
98+
<Button variant="outline" size="sm" onClick={handleCreateClick}>
99+
<Plus className="h-3 w-3 mr-1" />
100+
Create Agent
101+
</Button>
102+
</div>
103+
) : (
104+
<div className="flex flex-col gap-1">
105+
{agents.map((agent) => (
106+
<div
107+
key={agent.id}
108+
className="flex items-center gap-3 px-4 py-2 rounded-xl bg-vscode-editor-background hover:bg-vscode-list-hoverBackground cursor-pointer transition-colors"
109+
onClick={() => handleAgentClick(agent.id)}>
110+
<span className="text-xl" role="img" aria-label={agent.type}>
111+
<Brain className="size-6" />
112+
</span>
113+
<div className="flex-1 min-w-0">
114+
<div className="text-base font-medium text-vscode-foreground truncate">
115+
{agent.name}
116+
</div>
117+
<div className="text-sm font-light text-vscode-descriptionForeground">
118+
{agent.type} agents
119+
</div>
120+
</div>
121+
</div>
122+
))}
123+
</div>
124+
)}
125+
</div>
126+
)
127+
}
128+
129+
export default CloudAgents

0 commit comments

Comments
 (0)