From 948f595c1eac79529b1a5d9029c18eceeb972c79 Mon Sep 17 00:00:00 2001 From: James Arnott Date: Mon, 7 Apr 2025 23:14:24 +0100 Subject: [PATCH 01/11] WIP: creator mode shizz --- package-lock.json | 1 - src/activate/index.ts | 1 + src/extension.ts | 9 ++++++++- src/shared/api.ts | 1 + src/shared/creatorMode.ts | 10 ++++++++++ src/shared/modes.ts | 9 +++++++++ 6 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/shared/creatorMode.ts diff --git a/package-lock.json b/package-lock.json index 6b0efd9f23a..391ffa701f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12640,7 +12640,6 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, - "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", diff --git a/src/activate/index.ts b/src/activate/index.ts index 658bf467f7a..2fff46a5f04 100644 --- a/src/activate/index.ts +++ b/src/activate/index.ts @@ -2,3 +2,4 @@ export { handleUri } from "./handleUri" export { registerCommands } from "./registerCommands" export { registerCodeActions } from "./registerCodeActions" export { registerTerminalActions } from "./registerTerminalActions" +export { registerPearListener } from "./registerPearListener" diff --git a/src/extension.ts b/src/extension.ts index f90dcf8ab4e..99d1cab0e1b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,7 +23,13 @@ import { telemetryService } from "./services/telemetry/TelemetryService" import { TerminalRegistry } from "./integrations/terminal/TerminalRegistry" import { API } from "./exports/api" -import { handleUri, registerCommands, registerCodeActions, registerTerminalActions } from "./activate" +import { + handleUri, + registerCommands, + registerCodeActions, + registerTerminalActions, + registerPearListener, +} from "./activate" import { formatLanguage } from "./shared/language" /** @@ -246,6 +252,7 @@ export function activate(context: vscode.ExtensionContext) { registerCodeActions(context) registerTerminalActions(context) + registerPearListener() context.subscriptions.push( vscode.commands.registerCommand("roo-cline.focus", async (...args: any[]) => { diff --git a/src/shared/api.ts b/src/shared/api.ts index 362ea95a362..15211e6485d 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -88,6 +88,7 @@ export interface ApiHandlerOptions { export type ApiConfiguration = ApiHandlerOptions & { apiProvider?: ApiProvider id?: string // stable unique identifier + creatorMode?: boolean } // Import GlobalStateKey type from globalState.ts diff --git a/src/shared/creatorMode.ts b/src/shared/creatorMode.ts new file mode 100644 index 00000000000..350deea3f39 --- /dev/null +++ b/src/shared/creatorMode.ts @@ -0,0 +1,10 @@ +export const CREATOR_MODE_PLANNING_PROMPT = ` +Depending on the user's request, you may need to do some information gathering (for example using read_file or search_files) to get more context about the task. +You may also ask the user clarifying questions to get a better understanding of the task. +Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. +Focus on breaking down complex tasks into manageable steps, considering technical requirements, potential challenges, and best practices. +The plan should be clear enough that it can be directly implemented by switching to Code mode afterward. +(Directly write the plan to a markdown file instead of showing it as normal response.)\n\n +Once you create and write the plan, you mark the task as completed. +You only make plans and you should not ask or switch to any other mode. +Keep the plan brief, mainly feature based (include a feature list), and no steps, a product outline.` diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 374958f7434..9df98c36f34 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups" import { addCustomInstructions } from "../core/prompts/sections/custom-instructions" +import { CREATOR_MODE_PLANNING_PROMPT } from "./creatorMode" // Mode types export type Mode = string @@ -78,6 +79,14 @@ export function getToolsForMode(groups: readonly GroupEntry[]): string[] { // Main modes configuration as an ordered array export const modes: readonly ModeConfig[] = [ + { + slug: "creator", + name: "Creator", + roleDefinition: + "You are PearAI Agent (Powered by Roo Code / Cline), a creative and systematic software architect focused on turning high-level ideas into actionable plans. Your primary goal is to help users transform their ideas into structured action plans.", + groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], + customInstructions: CREATOR_MODE_PLANNING_PROMPT, + }, { slug: "code", name: "Code", From fd1b9ced5a147c0c7d69b22fad0fa3c553b0c7f3 Mon Sep 17 00:00:00 2001 From: James Arnott Date: Tue, 8 Apr 2025 12:18:29 +0100 Subject: [PATCH 02/11] WIP: registering pear listener for messages from submodule --- src/activate/registerPearListener.ts | 78 ++++++++++++++++++++++++++++ src/core/webview/ClineProvider.ts | 18 ++++++- src/utils/util.ts | 19 +++++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/activate/registerPearListener.ts create mode 100644 src/utils/util.ts diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts new file mode 100644 index 00000000000..4a5e5a66bd3 --- /dev/null +++ b/src/activate/registerPearListener.ts @@ -0,0 +1,78 @@ +import * as vscode from "vscode" +import { ClineProvider } from "../core/webview/ClineProvider" +import { assert } from "../utils/util" + +export const getPearaiExtension = async () => { + const pearAiExtension = vscode.extensions.getExtension("pearai.pearai") + + assert(!!pearAiExtension, "PearAI extension not found") + + if (!pearAiExtension.isActive) { + await pearAiExtension.activate() + } + + return pearAiExtension +} + +export const registerPearListener = async () => { + // Getting the pear ai extension instance + const pearAiExtension = await getPearaiExtension() + + // Access the API directly from exports + if (pearAiExtension.exports) { + pearAiExtension.exports.pearAPI.creatorMode.onDidRequestExecutePlan(async (msg: any) => { + console.dir(`onDidRequestNewTask triggered with: ${JSON.stringify(msg)}`) + + // Get the sidebar provider + const sidebarProvider = ClineProvider.getSidebarInstance() + + if (sidebarProvider) { + // Focus the sidebar first + await vscode.commands.executeCommand("pearai-roo-cline.SidebarProvider.focus") + + // Wait for the view to be ready using a helper function + await ensureViewIsReady(sidebarProvider) + + // Switch to creator mode + await sidebarProvider.handleModeSwitch("creator") + await sidebarProvider.postStateToWebview() + + // Navigate to chat view + await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) + + // Wait a brief moment for UI to update + await new Promise((resolve) => setTimeout(resolve, 300)) + + // Initialize with task + await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, true) + } + }) + } else { + console.error("⚠️⚠️ PearAI API not available in exports ⚠️⚠️") + } +} + +// TODO: decide if this is needed +// Helper function to ensure the webview is ready +async function ensureViewIsReady(provider: ClineProvider): Promise { + // If the view is already launched, we're good to go + if (provider.viewLaunched) { + return + } + + // Otherwise, we need to wait for it to initialize + return new Promise((resolve) => { + // Set up a one-time listener for when the view is ready + const disposable = provider.on("clineAdded", () => { + // Clean up the listener + disposable.dispose() + resolve() + }) + + // Set a timeout just in case + setTimeout(() => { + disposable.dispose() + resolve() + }, 5000) + }) +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index abb0cb073ef..ee666f3221e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -98,6 +98,7 @@ export class ClineProvider extends EventEmitter implements readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel, private readonly renderContext: "sidebar" | "editor" = "sidebar", + private readonly isCreatorView: boolean = false, ) { super() @@ -124,6 +125,16 @@ export class ClineProvider extends EventEmitter implements }) } + public static getSidebarInstance(): ClineProvider | undefined { + const sidebar = Array.from(this.activeInstances).find((instance) => !instance.isCreatorView) + + if (!sidebar?.view?.visible) { + vscode.commands.executeCommand("pearai-roo-cline.SidebarProvider.focus") + } + + return sidebar + } + // Adds a new Cline instance to clineStack, marking the start of a new task. // The instance is pushed to the top of the stack (LIFO order). // When the task is completed, the top instance is removed, reactivating the previous task. @@ -455,7 +466,7 @@ export class ClineProvider extends EventEmitter implements // when initializing a new task, (not from history but from a tool command new_task) there is no need to remove the previouse task // since the new task is a sub task of the previous one, and when it finishes it is removed from the stack and the caller is resumed // in this way we can have a chain of tasks, each one being a sub task of the previous one until the main task is finished - public async initClineWithTask(task?: string, images?: string[], parentTask?: Cline) { + public async initClineWithTask(task?: string, images?: string[], parentTask?: Cline, creatorMode?: boolean) { const { apiConfiguration, customModePrompts, @@ -473,7 +484,10 @@ export class ClineProvider extends EventEmitter implements const cline = new Cline({ provider: this, - apiConfiguration, + apiConfiguration: { + ...apiConfiguration, + creatorMode, + }, customInstructions: effectiveInstructions, enableDiff, enableCheckpoints, diff --git a/src/utils/util.ts b/src/utils/util.ts new file mode 100644 index 00000000000..54e5cbc8f04 --- /dev/null +++ b/src/utils/util.ts @@ -0,0 +1,19 @@ +class AssertionError extends Error { + constructor(message: string) { + super(message) + // Adding the stack info to error. + // Inspired by: https://blog.dennisokeeffe.com/blog/2020-08-07-error-tracing-with-sentry-and-es6-classes + if (Error.captureStackTrace) { + Error.captureStackTrace(this, AssertionError) + } else { + this.stack = new Error(message).stack + } + this.name = "AssertionError" + } +} + +export function assert(condition: boolean, message: string): asserts condition { + if (!condition) { + throw new AssertionError(message) + } +} From d75b11d40da4073ee2362f792a7f89f06b964faa Mon Sep 17 00:00:00 2001 From: James Arnott Date: Thu, 17 Apr 2025 13:50:40 +0100 Subject: [PATCH 03/11] fix: sending roo code when we're in creator mode --- src/core/Cline.ts | 5 ++++ src/core/webview/ClineProvider.ts | 29 +++++++++++++++++-- src/shared/api.ts | 2 +- .../src/components/settings/ApiOptions.tsx | 24 +++++++++++---- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 6cb26fbbc61..639bee3eefe 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -115,6 +115,7 @@ export type ClineOptions = { rootTask?: Cline parentTask?: Cline taskNumber?: number + creatorMode?: boolean } export class Cline extends EventEmitter { @@ -131,6 +132,7 @@ export class Cline extends EventEmitter { private pausedModeSlug: string = defaultModeSlug private pauseInterval: NodeJS.Timeout | undefined + public creatorMode: boolean readonly apiConfiguration: ApiConfiguration api: ApiHandler private urlContentFetcher: UrlContentFetcher @@ -192,6 +194,7 @@ export class Cline extends EventEmitter { rootTask, parentTask, taskNumber, + creatorMode, }: ClineOptions) { super() @@ -219,6 +222,8 @@ export class Cline extends EventEmitter { this.enableCheckpoints = enableCheckpoints this.checkpointStorage = checkpointStorage + this.creatorMode = creatorMode ?? false + this.rootTask = rootTask this.parentTask = parentTask this.taskNumber = taskNumber ?? -1 diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6eebc52ed2f..41f3400bea1 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -501,6 +501,15 @@ export class ClineProvider extends EventEmitter implements experiments, } = await this.getState() + // Update API configuration with creator mode + await this.updateApiConfiguration({ + ...apiConfiguration, + creatorMode, + }) + + // Post updated state to webview immediately + await this.postStateToWebview() + const modePrompt = customModePrompts?.[mode] as PromptComponent const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n") @@ -524,6 +533,7 @@ export class ClineProvider extends EventEmitter implements rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined, parentTask, taskNumber: this.clineStack.length + 1, + creatorMode, }) await this.addClineToStack(cline) @@ -2175,6 +2185,13 @@ export class ClineProvider extends EventEmitter implements private async updateApiConfiguration(apiConfiguration: ApiConfiguration) { // Update mode's default config. const { mode } = await this.getState() + const currentCline = this.getCurrentCline() + + // Preserve creator mode when updating configuration + const updatedConfig = { + ...apiConfiguration, + creatorMode: currentCline?.creatorMode, + } if (mode) { const currentApiConfigName = await this.getGlobalState("currentApiConfigName") @@ -2186,7 +2203,7 @@ export class ClineProvider extends EventEmitter implements } } - await this.contextProxy.setApiConfiguration(apiConfiguration) + await this.contextProxy.setApiConfiguration(updatedConfig) if (this.getCurrentCline()) { this.getCurrentCline()!.api = buildApiHandler(apiConfiguration) @@ -2511,8 +2528,10 @@ export class ClineProvider extends EventEmitter implements } async getStateToPostToWebview() { + const currentCline = this.getCurrentCline() + // Get base state const { - apiConfiguration, + apiConfiguration: baseApiConfiguration, lastShownAnnouncementId, customInstructions, alwaysAllowReadOnly, @@ -2560,6 +2579,12 @@ export class ClineProvider extends EventEmitter implements maxReadFileLine, } = await this.getState() + // Construct API configuration with creator mode + const apiConfiguration = { + ...baseApiConfiguration, + creatorMode: currentCline?.creatorMode, + } + const telemetryKey = process.env.POSTHOG_API_KEY const machineId = vscode.env.machineId const allowedCommands = vscode.workspace.getConfiguration("roo-cline").get("allowedCommands") || [] diff --git a/src/shared/api.ts b/src/shared/api.ts index 4c7b818b3d9..154cb54e529 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -85,12 +85,12 @@ export interface ApiHandlerOptions { pearaiAgentModels?: PearAIAgentModelsConfig modelMaxThinkingTokens?: number fakeAi?: unknown + creatorMode?: boolean } export type ApiConfiguration = ApiHandlerOptions & { apiProvider?: ApiProvider id?: string // stable unique identifier - creatorMode?: boolean } // Import GlobalStateKey type from globalState.ts diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index fa00a72f38e..ff5c92cfa34 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1607,8 +1607,8 @@ export function normalizeApiConfiguration( if (modelId === "pearai-model" && models[modelId].underlyingModelUpdated) { let modelInfo = models[modelId].underlyingModelUpdated selectedModelInfo = { - contextWindow: modelInfo.contextWindow || 4096, // provide default or actual value - supportsPromptCache: modelInfo.supportsPromptCaching || false, // provide default or actual value + contextWindow: modelInfo.contextWindow || 4096, + supportsPromptCache: modelInfo.supportsPromptCaching || false, ...modelInfo, } } else { @@ -1619,7 +1619,13 @@ export function normalizeApiConfiguration( selectedModelInfo = models[defaultId] } - return { selectedProvider: provider, selectedModelId, selectedModelInfo } + // Preserve all original configuration fields while updating model-specific ones + return { + selectedProvider: provider, + selectedModelId, + selectedModelInfo, + ...apiConfiguration, + } } switch (provider) { @@ -1705,8 +1711,16 @@ export function normalizeApiConfiguration( } case "pearai": { // Always use the models from the hook which are fetched when provider is selected - let query = pearAiModelsQuery - return getProviderData(pearAiModelsQuery || {}, pearAiDefaultModelId) + const { selectedProvider, selectedModelId, selectedModelInfo } = getProviderData( + pearAiModelsQuery || {}, + pearAiDefaultModelId, + ) + return { + selectedProvider, + selectedModelId, + selectedModelInfo, + creatorMode: apiConfiguration?.creatorMode, + } } default: return getProviderData(anthropicModels, anthropicDefaultModelId) From 818091e58da20fac83f93cdff744aaa778640bfe Mon Sep 17 00:00:00 2001 From: James Arnott Date: Thu, 17 Apr 2025 14:39:49 +0100 Subject: [PATCH 04/11] feat: added creator mode bar --- webview-ui/src/components/chat/ChatView.tsx | 4 + .../src/components/chat/CreatorModeBar.tsx | 64 ++++++++++ .../src/components/chat/button/index.tsx | 116 ++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 webview-ui/src/components/chat/CreatorModeBar.tsx create mode 100644 webview-ui/src/components/chat/button/index.tsx diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 9380a5807f8..15123ba0258 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -44,6 +44,7 @@ import { vscInputBorder, vscSidebarBorder, } from "../ui" +import { CreatorModeBar } from "./CreatorModeBar" interface ChatViewProps { isHidden: boolean @@ -1138,6 +1139,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flexDirection: "column", overflow: "hidden", }}> + {apiConfiguration?.creatorMode === true && ( + + )} {task ? ( <> void + nextCallback?: () => void + className?: string +} +// from: https://vscode.dev/github/trypear/pearai-submodule/blob/acorn/253-submodule-api-fixed/gui/src/pages/creator/ui/planningBar.tsx#L15-L50 +// TODO: UI LIBRARY COMPONENT SHARING SHIZZ HERE! + +export const CreatorModeBar: FC = ({ + isGenerating, + requestedPlan, + playCallback, + nextCallback, + className, +}) => { + return ( +
+ {isGenerating &&
} +
+
+
+
+
Planning
+
+
{requestedPlan}
+
+
+ +
+
+ + + +
+ + {/* */} +
+
+ ) +} diff --git a/webview-ui/src/components/chat/button/index.tsx b/webview-ui/src/components/chat/button/index.tsx new file mode 100644 index 00000000000..a5e043aa6e8 --- /dev/null +++ b/webview-ui/src/components/chat/button/index.tsx @@ -0,0 +1,116 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +// FROM: https://vscode.dev/github/trypear/pearai-submodule/blob/acorn/253-submodule-api-fixed/gui/src/pages/creator/ui/button/index.tsx#L1-L121 +// TODO: UI LIBRARY COMPONENT SHARING SHIZZ HERE! +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 border-none whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#a1a1aa] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-[#18181b] text-[#fafafa] shadow hover:bg-[#27272a]", + destructive: "bg-[#ef4444] text-[#fafafa] shadow-sm hover:bg-[#dc2626]", + outline: "border border-[#e4e4e7] bg-[#ffffff] shadow-sm hover:bg-[#f4f4f5] hover:text-[#18181b]", + secondary: "bg-[#f4f4f5] text-[#18181b] hover:bg-[#e4e4e7]", + ghost: "hover:bg-[#f4f4f5] hover:text-[#18181b]", + link: "text-[#18181b] underline-offset-4 hover:underline", + }, + size: { + // default: "h-9 px-4 py-2", + default: "h-7 rounded-md px-2 text-md", + sm: "h-6 rounded-md px-2 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + toggled: { + true: "", + }, + }, + compoundVariants: [ + { + variant: "default", + toggled: true, + className: " bg-[#3030ad] text-[#0B84FF] hover:bg-[#3a3ad2]", // bg-[#27272a] text-[#fafafa] + }, + { + variant: "destructive", + toggled: true, + className: "bg-[#dc2626] text-[#fafafa]", + }, + { + variant: "outline", + toggled: true, + className: "bg-[#f4f4f5] text-[#18181b] border-[#a1a1aa]", + }, + { + variant: "secondary", + toggled: true, + // className: "bg-[#e4e4e7] text-[#18181b]" + className: "bg-[#E3EFFF] text-[#4388F8] hover:bg-[#D1E3FF]", + }, + { + variant: "ghost", + toggled: true, + className: "bg-[#f4f4f5] text-[#18181b]", + }, + { + variant: "link", + toggled: true, + className: "text-[#18181b] underline", + }, + ], + defaultVariants: { + variant: "default", + size: "default", + toggled: false, + }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean + onToggle?: (toggled: boolean) => void +} + +const Button = React.forwardRef( + ( + { className, variant, size, toggled: initialToggled = false, asChild = false, onToggle, onClick, ...props }, + ref, + ) => { + const Comp = asChild ? Slot : "button" + const [toggled, setToggled] = React.useState(initialToggled) + + const handleClick = (event: React.MouseEvent) => { + if (onToggle) { + const newToggled = !toggled + setToggled(newToggled) + onToggle(newToggled) + } + + onClick?.(event) + } + + return ( + + ) + }, +) +Button.displayName = "Button" + +export { Button, buttonVariants } From 9836cd7c3be7f994febf248cab8edd9f72d61ae3 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Sat, 19 Apr 2025 17:05:33 -0400 Subject: [PATCH 05/11] Added newProject filtering --- src/activate/registerPearListener.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts index 4a5e5a66bd3..198276dae94 100644 --- a/src/activate/registerPearListener.ts +++ b/src/activate/registerPearListener.ts @@ -33,10 +33,11 @@ export const registerPearListener = async () => { // Wait for the view to be ready using a helper function await ensureViewIsReady(sidebarProvider) - // Switch to creator mode - await sidebarProvider.handleModeSwitch("creator") - await sidebarProvider.postStateToWebview() - + if (msg.newProject) { + // Switch to creator mode + await sidebarProvider.handleModeSwitch("creator") + await sidebarProvider.postStateToWebview() + } // Navigate to chat view await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) @@ -44,7 +45,7 @@ export const registerPearListener = async () => { await new Promise((resolve) => setTimeout(resolve, 300)) // Initialize with task - await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, true) + await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, msg.newProject) } }) } else { From ea2eca6c0c5052815cb13e9652a1a527400e4830 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 23 Apr 2025 21:42:53 -0400 Subject: [PATCH 06/11] Progress --- src/activate/registerPearListener.ts | 10 ++++++++-- src/api/providers/anthropic.ts | 9 ++++++++- src/core/Cline.ts | 4 ++++ src/core/webview/ClineProvider.ts | 14 ++++++++++++-- src/shared/api.ts | 1 + src/shared/creatorMode.ts | 10 ---------- src/shared/modes.ts | 2 -- src/shared/pearaiApi.ts | 4 ++-- 8 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts index 198276dae94..d557dd197d8 100644 --- a/src/activate/registerPearListener.ts +++ b/src/activate/registerPearListener.ts @@ -33,7 +33,7 @@ export const registerPearListener = async () => { // Wait for the view to be ready using a helper function await ensureViewIsReady(sidebarProvider) - if (msg.newProject) { + if (msg.creatorMode) { // Switch to creator mode await sidebarProvider.handleModeSwitch("creator") await sidebarProvider.postStateToWebview() @@ -45,7 +45,13 @@ export const registerPearListener = async () => { await new Promise((resolve) => setTimeout(resolve, 300)) // Initialize with task - await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, msg.newProject) + await sidebarProvider.initClineWithTask( + msg.plan, + undefined, + undefined, + msg.creatorMode, + msg.newProjectType, + ) } }) } else { diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index a1773d8c52b..6bf850646ba 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -98,8 +98,15 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa case "claude-3-opus-20240229": case "claude-3-haiku-20240307": betas.push("prompt-caching-2024-07-31") + + // Include prompt_key if newProjectType is set return { - headers: { "anthropic-beta": betas.join(",") }, + headers: { + "anthropic-beta": betas.join(","), + ...(String(this.options.newProjectType) + ? { prompt_key: String(this.options.newProjectType) } + : {}), + }, authorization: `Bearer ${this.options.apiKey}`, } default: diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 639bee3eefe..46d72ab4ea5 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -116,6 +116,7 @@ export type ClineOptions = { parentTask?: Cline taskNumber?: number creatorMode?: boolean + newProjectType?: string } export class Cline extends EventEmitter { @@ -133,6 +134,7 @@ export class Cline extends EventEmitter { private pauseInterval: NodeJS.Timeout | undefined public creatorMode: boolean + public newProjectType: string readonly apiConfiguration: ApiConfiguration api: ApiHandler private urlContentFetcher: UrlContentFetcher @@ -195,6 +197,7 @@ export class Cline extends EventEmitter { parentTask, taskNumber, creatorMode, + newProjectType, }: ClineOptions) { super() @@ -223,6 +226,7 @@ export class Cline extends EventEmitter { this.checkpointStorage = checkpointStorage this.creatorMode = creatorMode ?? false + this.newProjectType = newProjectType ?? "" this.rootTask = rootTask this.parentTask = parentTask diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 41f3400bea1..00ff852f009 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -488,7 +488,13 @@ export class ClineProvider extends EventEmitter implements // when initializing a new task, (not from history but from a tool command new_task) there is no need to remove the previouse task // since the new task is a sub task of the previous one, and when it finishes it is removed from the stack and the caller is resumed // in this way we can have a chain of tasks, each one being a sub task of the previous one until the main task is finished - public async initClineWithTask(task?: string, images?: string[], parentTask?: Cline, creatorMode?: boolean) { + public async initClineWithTask( + task?: string, + images?: string[], + parentTask?: Cline, + creatorMode?: boolean, + newProjectType?: string, + ) { const { apiConfiguration, customModePrompts, @@ -505,6 +511,7 @@ export class ClineProvider extends EventEmitter implements await this.updateApiConfiguration({ ...apiConfiguration, creatorMode, + newProjectType, }) // Post updated state to webview immediately @@ -519,7 +526,7 @@ export class ClineProvider extends EventEmitter implements provider: this, apiConfiguration: { ...apiConfiguration, - creatorMode, + newProjectType, pearaiAgentModels, }, customInstructions: effectiveInstructions, @@ -534,6 +541,7 @@ export class ClineProvider extends EventEmitter implements parentTask, taskNumber: this.clineStack.length + 1, creatorMode, + newProjectType, }) await this.addClineToStack(cline) @@ -2191,6 +2199,7 @@ export class ClineProvider extends EventEmitter implements const updatedConfig = { ...apiConfiguration, creatorMode: currentCline?.creatorMode, + newProjectType: currentCline?.newProjectType, } if (mode) { @@ -2583,6 +2592,7 @@ export class ClineProvider extends EventEmitter implements const apiConfiguration = { ...baseApiConfiguration, creatorMode: currentCline?.creatorMode, + newProjectType: currentCline?.newProjectType, } const telemetryKey = process.env.POSTHOG_API_KEY diff --git a/src/shared/api.ts b/src/shared/api.ts index 154cb54e529..b1a4da8837d 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -86,6 +86,7 @@ export interface ApiHandlerOptions { modelMaxThinkingTokens?: number fakeAi?: unknown creatorMode?: boolean + newProjectType?: string } export type ApiConfiguration = ApiHandlerOptions & { diff --git a/src/shared/creatorMode.ts b/src/shared/creatorMode.ts index 350deea3f39..e69de29bb2d 100644 --- a/src/shared/creatorMode.ts +++ b/src/shared/creatorMode.ts @@ -1,10 +0,0 @@ -export const CREATOR_MODE_PLANNING_PROMPT = ` -Depending on the user's request, you may need to do some information gathering (for example using read_file or search_files) to get more context about the task. -You may also ask the user clarifying questions to get a better understanding of the task. -Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. -Focus on breaking down complex tasks into manageable steps, considering technical requirements, potential challenges, and best practices. -The plan should be clear enough that it can be directly implemented by switching to Code mode afterward. -(Directly write the plan to a markdown file instead of showing it as normal response.)\n\n -Once you create and write the plan, you mark the task as completed. -You only make plans and you should not ask or switch to any other mode. -Keep the plan brief, mainly feature based (include a feature list), and no steps, a product outline.` diff --git a/src/shared/modes.ts b/src/shared/modes.ts index 9df98c36f34..51403fc07fa 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -1,7 +1,6 @@ import * as vscode from "vscode" import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups" import { addCustomInstructions } from "../core/prompts/sections/custom-instructions" -import { CREATOR_MODE_PLANNING_PROMPT } from "./creatorMode" // Mode types export type Mode = string @@ -85,7 +84,6 @@ export const modes: readonly ModeConfig[] = [ roleDefinition: "You are PearAI Agent (Powered by Roo Code / Cline), a creative and systematic software architect focused on turning high-level ideas into actionable plans. Your primary goal is to help users transform their ideas into structured action plans.", groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], - customInstructions: CREATOR_MODE_PLANNING_PROMPT, }, { slug: "code", diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts index 2ff45a276df..6f9ef07eaa8 100644 --- a/src/shared/pearaiApi.ts +++ b/src/shared/pearaiApi.ts @@ -1,8 +1,8 @@ // CHANGE AS NEEDED FOR DEVELOPMENT // PROD: -export const PEARAI_URL = "https://server.trypear.ai/pearai-server-api2/integrations/cline" +// export const PEARAI_URL = "https://server.trypear.ai/pearai-server-api2/integrations/cline" // DEV: -// export const PEARAI_URL = "http://localhost:8000/integrations/cline" +export const PEARAI_URL = "http://localhost:8000/integrations/cline" import { anthropicModels, From 088f3f4b7c19a578486dd172d7c852684d83f2d5 Mon Sep 17 00:00:00 2001 From: nang-dev Date: Wed, 23 Apr 2025 23:09:49 -0400 Subject: [PATCH 07/11] Added creatorModeConfig --- src/activate/registerPearListener.ts | 16 ++++++++-------- src/api/providers/anthropic.ts | 12 +++++++----- src/core/Cline.ts | 13 +++++-------- src/core/webview/ClineProvider.ts | 19 +++++++------------ src/shared/api.ts | 4 ++-- src/shared/pearaiApi.ts | 10 ++++++++-- webview-ui/src/components/chat/ChatView.tsx | 2 +- .../src/components/settings/ApiOptions.tsx | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts index d557dd197d8..e40a2494b89 100644 --- a/src/activate/registerPearListener.ts +++ b/src/activate/registerPearListener.ts @@ -33,7 +33,7 @@ export const registerPearListener = async () => { // Wait for the view to be ready using a helper function await ensureViewIsReady(sidebarProvider) - if (msg.creatorMode) { + if (msg.creatorModeConfig?.creatorMode) { // Switch to creator mode await sidebarProvider.handleModeSwitch("creator") await sidebarProvider.postStateToWebview() @@ -44,14 +44,14 @@ export const registerPearListener = async () => { // Wait a brief moment for UI to update await new Promise((resolve) => setTimeout(resolve, 300)) + let creatorModeConifig = { + creatorMode: msg.creatorMode, + newProjectType: msg.newProjectType, + newProjectPath: msg.newProjectPath, + } + // Initialize with task - await sidebarProvider.initClineWithTask( - msg.plan, - undefined, - undefined, - msg.creatorMode, - msg.newProjectType, - ) + await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, creatorModeConifig) } }) } else { diff --git a/src/api/providers/anthropic.ts b/src/api/providers/anthropic.ts index 6bf850646ba..de021d85f09 100644 --- a/src/api/providers/anthropic.ts +++ b/src/api/providers/anthropic.ts @@ -98,16 +98,18 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa case "claude-3-opus-20240229": case "claude-3-haiku-20240307": betas.push("prompt-caching-2024-07-31") - // Include prompt_key if newProjectType is set return { headers: { "anthropic-beta": betas.join(","), - ...(String(this.options.newProjectType) - ? { prompt_key: String(this.options.newProjectType) } - : {}), + prompt_key: this.options.creatorModeConfig?.newProjectType + ? String(this.options.creatorModeConfig.newProjectType) + : undefined, + project_path: this.options.creatorModeConfig?.newProjectPath + ? String(this.options.creatorModeConfig.newProjectPath) + : undefined, + authorization: `Bearer ${this.options.apiKey}`, }, - authorization: `Bearer ${this.options.apiKey}`, } default: return undefined diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 46d72ab4ea5..b3e22c8e27b 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -83,6 +83,7 @@ import { parseXml } from "../utils/xml" import { readLines } from "../integrations/misc/read-lines" import { getWorkspacePath } from "../utils/path" import { isBinaryFile } from "isbinaryfile" +import { creatorModeConfig } from "../shared/pearaiApi" type ToolResponse = string | Array type UserContent = Array @@ -115,8 +116,7 @@ export type ClineOptions = { rootTask?: Cline parentTask?: Cline taskNumber?: number - creatorMode?: boolean - newProjectType?: string + creatorModeConfig?: creatorModeConfig } export class Cline extends EventEmitter { @@ -133,8 +133,7 @@ export class Cline extends EventEmitter { private pausedModeSlug: string = defaultModeSlug private pauseInterval: NodeJS.Timeout | undefined - public creatorMode: boolean - public newProjectType: string + public creatorModeConfig: creatorModeConfig readonly apiConfiguration: ApiConfiguration api: ApiHandler private urlContentFetcher: UrlContentFetcher @@ -196,8 +195,7 @@ export class Cline extends EventEmitter { rootTask, parentTask, taskNumber, - creatorMode, - newProjectType, + creatorModeConfig, }: ClineOptions) { super() @@ -225,8 +223,7 @@ export class Cline extends EventEmitter { this.enableCheckpoints = enableCheckpoints this.checkpointStorage = checkpointStorage - this.creatorMode = creatorMode ?? false - this.newProjectType = newProjectType ?? "" + this.creatorModeConfig = creatorModeConfig ?? { creatorMode: false } this.rootTask = rootTask this.parentTask = parentTask diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 00ff852f009..4164ed6d658 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -78,7 +78,7 @@ import { getUri } from "./getUri" import { telemetryService } from "../../services/telemetry/TelemetryService" import { TelemetrySetting } from "../../shared/TelemetrySetting" import { getWorkspacePath } from "../../utils/path" -import { PEARAI_URL } from "../../shared/pearaiApi" +import { PEARAI_URL, creatorModeConfig } from "../../shared/pearaiApi" import { PearAIAgentModelsConfig } from "../../api/providers/pearai/pearai" /** @@ -492,8 +492,7 @@ export class ClineProvider extends EventEmitter implements task?: string, images?: string[], parentTask?: Cline, - creatorMode?: boolean, - newProjectType?: string, + creatorModeConfig?: creatorModeConfig, ) { const { apiConfiguration, @@ -510,8 +509,7 @@ export class ClineProvider extends EventEmitter implements // Update API configuration with creator mode await this.updateApiConfiguration({ ...apiConfiguration, - creatorMode, - newProjectType, + creatorModeConfig, }) // Post updated state to webview immediately @@ -526,7 +524,7 @@ export class ClineProvider extends EventEmitter implements provider: this, apiConfiguration: { ...apiConfiguration, - newProjectType, + creatorModeConfig, pearaiAgentModels, }, customInstructions: effectiveInstructions, @@ -540,8 +538,7 @@ export class ClineProvider extends EventEmitter implements rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined, parentTask, taskNumber: this.clineStack.length + 1, - creatorMode, - newProjectType, + creatorModeConfig, }) await this.addClineToStack(cline) @@ -2198,8 +2195,7 @@ export class ClineProvider extends EventEmitter implements // Preserve creator mode when updating configuration const updatedConfig = { ...apiConfiguration, - creatorMode: currentCline?.creatorMode, - newProjectType: currentCline?.newProjectType, + creatorModeConfig: currentCline?.creatorModeConfig, } if (mode) { @@ -2591,8 +2587,7 @@ export class ClineProvider extends EventEmitter implements // Construct API configuration with creator mode const apiConfiguration = { ...baseApiConfiguration, - creatorMode: currentCline?.creatorMode, - newProjectType: currentCline?.newProjectType, + creatorModeConfig: currentCline?.creatorModeConfig, } const telemetryKey = process.env.POSTHOG_API_KEY diff --git a/src/shared/api.ts b/src/shared/api.ts index b1a4da8837d..3a8b9127898 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -85,8 +85,7 @@ export interface ApiHandlerOptions { pearaiAgentModels?: PearAIAgentModelsConfig modelMaxThinkingTokens?: number fakeAi?: unknown - creatorMode?: boolean - newProjectType?: string + creatorModeConfig?: creatorModeConfig } export type ApiConfiguration = ApiHandlerOptions & { @@ -96,6 +95,7 @@ export type ApiConfiguration = ApiHandlerOptions & { // Import GlobalStateKey type from globalState.ts import { GlobalStateKey } from "./globalState" +import { creatorModeConfig } from "./pearaiApi" // Define API configuration keys for dynamic object building. // TODO: This needs actual type safety; a type error should be thrown if diff --git a/src/shared/pearaiApi.ts b/src/shared/pearaiApi.ts index 6f9ef07eaa8..6c0ddbde20e 100644 --- a/src/shared/pearaiApi.ts +++ b/src/shared/pearaiApi.ts @@ -1,8 +1,8 @@ // CHANGE AS NEEDED FOR DEVELOPMENT // PROD: -// export const PEARAI_URL = "https://server.trypear.ai/pearai-server-api2/integrations/cline" +export const PEARAI_URL = "https://server.trypear.ai/pearai-server-api2/integrations/cline" // DEV: -export const PEARAI_URL = "http://localhost:8000/integrations/cline" +// export const PEARAI_URL = "http://localhost:8000/integrations/cline" import { anthropicModels, @@ -130,3 +130,9 @@ export const allModels: { [key: string]: ModelInfo } = { // Unbound models (single default model) [`unbound/${unboundDefaultModelId}`]: unboundDefaultModelInfo, } as const satisfies Record + +export interface creatorModeConfig { + creatorMode?: boolean // Defaults to false when not set + newProjectType?: string + newProjectPath?: string +} diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 15123ba0258..bab4afde721 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1139,7 +1139,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie flexDirection: "column", overflow: "hidden", }}> - {apiConfiguration?.creatorMode === true && ( + {apiConfiguration?.creatorModeConfig?.creatorMode === true && ( )} {task ? ( diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index ff5c92cfa34..ad59df9516a 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -1719,7 +1719,7 @@ export function normalizeApiConfiguration( selectedProvider, selectedModelId, selectedModelInfo, - creatorMode: apiConfiguration?.creatorMode, + creatorMode: apiConfiguration?.creatorModeConfig?.creatorMode, } } default: From dcbadcd507690d6dd4fb8c84ed3d0370c082e7e9 Mon Sep 17 00:00:00 2001 From: James Arnott Date: Tue, 6 May 2025 19:16:29 +0100 Subject: [PATCH 08/11] schema update --- src/schemas/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/schemas/index.ts b/src/schemas/index.ts index db61b52a2e2..a658e552e93 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -339,6 +339,13 @@ export type Experiments = z.infer type _AssertExperiments = AssertEqual>> + +export const creatorModeConfig = z.object({ + creatorMode: z.boolean().optional(), + newProjectType: z.string().optional(), + newProjectPath: z.string().optional(), +}).optional() + /** * ProviderSettings */ @@ -449,6 +456,8 @@ export const providerSettingsSchema = z.object({ defaultModelId: z.string().optional(), }) .optional(), + creatorModeConfig: creatorModeConfig, + }) export type ProviderSettings = z.infer @@ -546,6 +555,7 @@ const providerSettingsRecord: ProviderSettingsRecord = { pearaiApiKey: undefined, pearaiModelInfo: undefined, pearaiAgentModels: undefined, + creatorModeConfig: undefined, // X.AI (Grok) xaiApiKey: undefined, } From e79d6db4b0be8a54f3fc3ece213ae524c6e1311c Mon Sep 17 00:00:00 2001 From: James Arnott Date: Tue, 6 May 2025 19:28:02 +0100 Subject: [PATCH 09/11] fix:build --- package-lock.json | 1 + src/activate/registerPearListener.ts | 4 ++-- src/core/webview/ClineProvider.ts | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e543fbb3c36..5e9bd85604e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17040,6 +17040,7 @@ "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "chalk": "^2.4.1", diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts index e40a2494b89..ba75e81ff50 100644 --- a/src/activate/registerPearListener.ts +++ b/src/activate/registerPearListener.ts @@ -51,7 +51,7 @@ export const registerPearListener = async () => { } // Initialize with task - await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, creatorModeConifig) + await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, undefined, creatorModeConifig) } }) } else { @@ -70,7 +70,7 @@ async function ensureViewIsReady(provider: ClineProvider): Promise { // Otherwise, we need to wait for it to initialize return new Promise((resolve) => { // Set up a one-time listener for when the view is ready - const disposable = provider.on("clineAdded", () => { + const disposable = provider.on("clineCreated", () => { // Clean up the listener disposable.dispose() resolve() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 3b4af0f7cda..72efe865b79 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -85,8 +85,8 @@ export class ClineProvider extends EventEmitter implements readonly context: vscode.ExtensionContext, private readonly outputChannel: vscode.OutputChannel, private readonly renderContext: "sidebar" | "editor" = "sidebar", - private readonly isCreatorView: boolean = false, public readonly contextProxy: ContextProxy, + private readonly isCreatorView: boolean = false, ) { super() @@ -474,7 +474,6 @@ export class ClineProvider extends EventEmitter implements task?: string, images?: string[], parentTask?: Cline, - creatorModeConfig?: creatorModeConfig, options: Partial< Pick< ClineOptions, @@ -486,6 +485,7 @@ export class ClineProvider extends EventEmitter implements | "experiments" > > = {}, + creatorModeConfig?: creatorModeConfig, ) { const { apiConfiguration, @@ -853,10 +853,11 @@ export class ClineProvider extends EventEmitter implements const currentCline = this.getCurrentCline() // Preserve creator mode when updating configuration - const updatedConfig = { - ...apiConfiguration, + const updatedConfig: ProviderSettings = { + ...providerSettings, creatorModeConfig: currentCline?.creatorModeConfig, } + if (mode) { const currentApiConfigName = this.getGlobalState("currentApiConfigName") From dc6167715b6a675b6c3c576835b15391b54154b6 Mon Sep 17 00:00:00 2001 From: James Arnott Date: Thu, 8 May 2025 18:11:20 +0100 Subject: [PATCH 10/11] chore: spelling + types --- src/activate/registerPearListener.ts | 4 ++-- src/exports/roo-code.d.ts | 7 +++++++ src/exports/types.ts | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/activate/registerPearListener.ts b/src/activate/registerPearListener.ts index ba75e81ff50..003bab9ceaf 100644 --- a/src/activate/registerPearListener.ts +++ b/src/activate/registerPearListener.ts @@ -44,14 +44,14 @@ export const registerPearListener = async () => { // Wait a brief moment for UI to update await new Promise((resolve) => setTimeout(resolve, 300)) - let creatorModeConifig = { + let creatorModeConfig = { creatorMode: msg.creatorMode, newProjectType: msg.newProjectType, newProjectPath: msg.newProjectPath, } // Initialize with task - await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, undefined, creatorModeConifig) + await sidebarProvider.initClineWithTask(msg.plan, undefined, undefined, undefined, creatorModeConfig) } }) } else { diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 4eef2426dab..c6069a5d225 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -202,6 +202,13 @@ type ProviderSettings = { defaultModelId?: string | undefined } | undefined + creatorModeConfig?: + | { + creatorMode?: boolean | undefined + newProjectType?: string | undefined + newProjectPath?: string | undefined + } + | undefined } type GlobalSettings = { diff --git a/src/exports/types.ts b/src/exports/types.ts index 0b557dd0baf..2795442a125 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -203,6 +203,13 @@ type ProviderSettings = { defaultModelId?: string | undefined } | undefined + creatorModeConfig?: + | { + creatorMode?: boolean | undefined + newProjectType?: string | undefined + newProjectPath?: string | undefined + } + | undefined } export type { ProviderSettings } From 9777a01326dae55368a9a9a251e4fe27e71af13c Mon Sep 17 00:00:00 2001 From: James Arnott Date: Fri, 9 May 2025 11:02:07 +0100 Subject: [PATCH 11/11] fix: not using deep compare effect --- webview-ui/src/components/chat/ChatView.tsx | 352 ++++++++++---------- 1 file changed, 176 insertions(+), 176 deletions(-) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 69795636d48..f2272f52d11 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -160,182 +160,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - // if last message is an ask, show user ask UI - // if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost. - // basically as long as a task is active, the conversation history will be persisted - if (lastMessage) { - switch (lastMessage.type) { - case "ask": - const isPartial = lastMessage.partial === true - switch (lastMessage.ask) { - case "api_req_failed": - playSound("progress_loop") - setTextAreaDisabled(true) - setClineAsk("api_req_failed") - setEnableButtons(true) - setPrimaryButtonText(t("chat:retry.title")) - setSecondaryButtonText(t("chat:startNewTask.title")) - break - case "mistake_limit_reached": - playSound("progress_loop") - setTextAreaDisabled(false) - setClineAsk("mistake_limit_reached") - setEnableButtons(true) - setPrimaryButtonText(t("chat:proceedAnyways.title")) - setSecondaryButtonText(t("chat:startNewTask.title")) - break - case "followup": - if (!isPartial) { - playSound("notification") - } - setTextAreaDisabled(isPartial) - setClineAsk("followup") - // setting enable buttons to `false` would trigger a focus grab when - // the text area is enabled which is undesirable. - // We have no buttons for this tool, so no problem having them "enabled" - // to workaround this issue. See #1358. - setEnableButtons(true) - setPrimaryButtonText(undefined) - setSecondaryButtonText(undefined) - break - case "tool": - if (!isAutoApproved(lastMessage) && !isPartial) { - playSound("notification") - } - setTextAreaDisabled(isPartial) - setClineAsk("tool") - setEnableButtons(!isPartial) - const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool - switch (tool.tool) { - case "editedExistingFile": - case "appliedDiff": - case "newFileCreated": - case "insertContent": - setPrimaryButtonText(t("chat:save.title")) - setSecondaryButtonText(t("chat:reject.title")) - break - case "finishTask": - setPrimaryButtonText(t("chat:completeSubtaskAndReturn")) - setSecondaryButtonText(undefined) - break - default: - setPrimaryButtonText(t("chat:approve.title")) - setSecondaryButtonText(t("chat:reject.title")) - break - } - break - case "browser_action_launch": - if (!isAutoApproved(lastMessage) && !isPartial) { - playSound("notification") - } - setTextAreaDisabled(isPartial) - setClineAsk("browser_action_launch") - setEnableButtons(!isPartial) - setPrimaryButtonText(t("chat:approve.title")) - setSecondaryButtonText(t("chat:reject.title")) - break - case "command": - if (!isAutoApproved(lastMessage) && !isPartial) { - playSound("notification") - } - setTextAreaDisabled(isPartial) - setClineAsk("command") - setEnableButtons(!isPartial) - setPrimaryButtonText(t("chat:runCommand.title")) - setSecondaryButtonText(t("chat:reject.title")) - break - case "command_output": - setTextAreaDisabled(false) - setClineAsk("command_output") - setEnableButtons(true) - setPrimaryButtonText(t("chat:proceedWhileRunning.title")) - setSecondaryButtonText(t("chat:killCommand.title")) - break - case "use_mcp_server": - if (!isAutoApproved(lastMessage) && !isPartial) { - playSound("notification") - } - setTextAreaDisabled(isPartial) - setClineAsk("use_mcp_server") - setEnableButtons(!isPartial) - setPrimaryButtonText(t("chat:approve.title")) - setSecondaryButtonText(t("chat:reject.title")) - break - case "completion_result": - // extension waiting for feedback. but we can just present a new task button - if (!isPartial) { - playSound("celebration") - } - setTextAreaDisabled(isPartial) - setClineAsk("completion_result") - setEnableButtons(!isPartial) - setPrimaryButtonText(t("chat:startNewTask.title")) - setSecondaryButtonText(undefined) - break - case "resume_task": - if (!isAutoApproved(lastMessage) && !isPartial) { - playSound("notification") - } - setTextAreaDisabled(false) - setClineAsk("resume_task") - setEnableButtons(true) - setPrimaryButtonText(t("chat:resumeTask.title")) - setSecondaryButtonText(t("chat:terminate.title")) - setDidClickCancel(false) // special case where we reset the cancel button state - break - case "resume_completed_task": - if (!isPartial) { - playSound("celebration") - } - setTextAreaDisabled(false) - setClineAsk("resume_completed_task") - setEnableButtons(true) - setPrimaryButtonText(t("chat:startNewTask.title")) - setSecondaryButtonText(undefined) - setDidClickCancel(false) - break - } - break - case "say": - // Don't want to reset since there could be a "say" after - // an "ask" while ask is waiting for response. - switch (lastMessage.say) { - case "api_req_retry_delayed": - setTextAreaDisabled(true) - break - case "api_req_started": - if (secondLastMessage?.ask === "command_output") { - // If the last ask is a command_output, and we - // receive an api_req_started, then that means - // the command has finished and we don't need - // input from the user anymore (in every other - // case, the user has to interact with input - // field or buttons to continue, which does the - // following automatically). - setInputValue("") - setTextAreaDisabled(true) - setSelectedImages([]) - setClineAsk(undefined) - setEnableButtons(false) - } - break - case "api_req_finished": - case "error": - case "text": - case "browser_action": - case "browser_action_result": - case "command_output": - case "mcp_server_request_started": - case "mcp_server_response": - case "completion_result": - break - } - break - } - } - }, [lastMessage, secondLastMessage]) - useEffect(() => { if (messages.length === 0) { setTextAreaDisabled(false) @@ -850,6 +674,182 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + // if last message is an ask, show user ask UI + // if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost. + // basically as long as a task is active, the conversation history will be persisted + if (lastMessage) { + switch (lastMessage.type) { + case "ask": + const isPartial = lastMessage.partial === true + switch (lastMessage.ask) { + case "api_req_failed": + playSound("progress_loop") + setTextAreaDisabled(true) + setClineAsk("api_req_failed") + setEnableButtons(true) + setPrimaryButtonText(t("chat:retry.title")) + setSecondaryButtonText(t("chat:startNewTask.title")) + break + case "mistake_limit_reached": + playSound("progress_loop") + setTextAreaDisabled(false) + setClineAsk("mistake_limit_reached") + setEnableButtons(true) + setPrimaryButtonText(t("chat:proceedAnyways.title")) + setSecondaryButtonText(t("chat:startNewTask.title")) + break + case "followup": + if (!isPartial) { + playSound("notification") + } + setTextAreaDisabled(isPartial) + setClineAsk("followup") + // setting enable buttons to `false` would trigger a focus grab when + // the text area is enabled which is undesirable. + // We have no buttons for this tool, so no problem having them "enabled" + // to workaround this issue. See #1358. + setEnableButtons(true) + setPrimaryButtonText(undefined) + setSecondaryButtonText(undefined) + break + case "tool": + if (!isAutoApproved(lastMessage) && !isPartial) { + playSound("notification") + } + setTextAreaDisabled(isPartial) + setClineAsk("tool") + setEnableButtons(!isPartial) + const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool + switch (tool.tool) { + case "editedExistingFile": + case "appliedDiff": + case "newFileCreated": + case "insertContent": + setPrimaryButtonText(t("chat:save.title")) + setSecondaryButtonText(t("chat:reject.title")) + break + case "finishTask": + setPrimaryButtonText(t("chat:completeSubtaskAndReturn")) + setSecondaryButtonText(undefined) + break + default: + setPrimaryButtonText(t("chat:approve.title")) + setSecondaryButtonText(t("chat:reject.title")) + break + } + break + case "browser_action_launch": + if (!isAutoApproved(lastMessage) && !isPartial) { + playSound("notification") + } + setTextAreaDisabled(isPartial) + setClineAsk("browser_action_launch") + setEnableButtons(!isPartial) + setPrimaryButtonText(t("chat:approve.title")) + setSecondaryButtonText(t("chat:reject.title")) + break + case "command": + if (!isAutoApproved(lastMessage) && !isPartial) { + playSound("notification") + } + setTextAreaDisabled(isPartial) + setClineAsk("command") + setEnableButtons(!isPartial) + setPrimaryButtonText(t("chat:runCommand.title")) + setSecondaryButtonText(t("chat:reject.title")) + break + case "command_output": + setTextAreaDisabled(false) + setClineAsk("command_output") + setEnableButtons(true) + setPrimaryButtonText(t("chat:proceedWhileRunning.title")) + setSecondaryButtonText(t("chat:killCommand.title")) + break + case "use_mcp_server": + if (!isAutoApproved(lastMessage) && !isPartial) { + playSound("notification") + } + setTextAreaDisabled(isPartial) + setClineAsk("use_mcp_server") + setEnableButtons(!isPartial) + setPrimaryButtonText(t("chat:approve.title")) + setSecondaryButtonText(t("chat:reject.title")) + break + case "completion_result": + // extension waiting for feedback. but we can just present a new task button + if (!isPartial) { + playSound("celebration") + } + setTextAreaDisabled(isPartial) + setClineAsk("completion_result") + setEnableButtons(!isPartial) + setPrimaryButtonText(t("chat:startNewTask.title")) + setSecondaryButtonText(undefined) + break + case "resume_task": + if (!isAutoApproved(lastMessage) && !isPartial) { + playSound("notification") + } + setTextAreaDisabled(false) + setClineAsk("resume_task") + setEnableButtons(true) + setPrimaryButtonText(t("chat:resumeTask.title")) + setSecondaryButtonText(t("chat:terminate.title")) + setDidClickCancel(false) // special case where we reset the cancel button state + break + case "resume_completed_task": + if (!isPartial) { + playSound("celebration") + } + setTextAreaDisabled(false) + setClineAsk("resume_completed_task") + setEnableButtons(true) + setPrimaryButtonText(t("chat:startNewTask.title")) + setSecondaryButtonText(undefined) + setDidClickCancel(false) + break + } + break + case "say": + // Don't want to reset since there could be a "say" after + // an "ask" while ask is waiting for response. + switch (lastMessage.say) { + case "api_req_retry_delayed": + setTextAreaDisabled(true) + break + case "api_req_started": + if (secondLastMessage?.ask === "command_output") { + // If the last ask is a command_output, and we + // receive an api_req_started, then that means + // the command has finished and we don't need + // input from the user anymore (in every other + // case, the user has to interact with input + // field or buttons to continue, which does the + // following automatically). + setInputValue("") + setTextAreaDisabled(true) + setSelectedImages([]) + setClineAsk(undefined) + setEnableButtons(false) + } + break + case "api_req_finished": + case "error": + case "text": + case "browser_action": + case "browser_action_result": + case "command_output": + case "mcp_server_request_started": + case "mcp_server_response": + case "completion_result": + break + } + break + } + } + }, [lastMessage, secondLastMessage, isAutoApproved, t]) + useEffect(() => { // This ensures the first message is not read, future user messages are // labeled as `user_feedback`.