Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9697f6f
feat(human-relay): add clipboard monitoring for AI responses
NyxJae Mar 8, 2025
d1b34c7
Fixed: Remove unnecessary variables
NyxJae Mar 8, 2025
b1553bb
Merge branch 'RooVetGit:main' into human-relay
NyxJae Mar 10, 2025
717477a
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 11, 2025
a9b50ad
feat: Optimize Human-relay UI prompts and add format validation for c…
NyxJae Mar 11, 2025
d7256fc
feat: Add toggle options for clipboard monitoring of human relay prov…
NyxJae Mar 11, 2025
75d1477
feat: Optimize clipboard monitoring logic
NyxJae Mar 11, 2025
004f8e3
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 12, 2025
c90d4d0
Merge branch 'RooVetGit:main' into human-relay
NyxJae Mar 13, 2025
1d1cf54
feat: Add human relay monitoring related configuration keys
NyxJae Mar 13, 2025
2ed74f9
fix: Add VSCode progress ring component to HumanRelayDialog instead o…
NyxJae Mar 13, 2025
fb1550c
Merge branch 'RooVetGit:main' into human-relay
NyxJae Mar 14, 2025
161480b
feat: Add commands and shortcut keys to send clipboard content to hum…
NyxJae Mar 14, 2025
6dbaf15
Doc:Fix spelling errors in comments
NyxJae Mar 14, 2025
dd5e65d
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 15, 2025
17a97a7
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 17, 2025
a1a2380
fix: Update human-computer interaction warning information to use req…
NyxJae Mar 17, 2025
c1d68a2
feat(i8n): Add multilingual support for manual relay dialog text
NyxJae Mar 17, 2025
9daca17
fix(i18n): Correct the expression of "web page AI" in Chinese prompt …
NyxJae Mar 17, 2025
0987f63
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 18, 2025
fee34c4
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 18, 2025
23c2154
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 18, 2025
ab7847f
Add Vietnamese (vi) translation for humanRelay.json
NyxJae Mar 18, 2025
4c3100f
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 19, 2025
d8f612d
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 20, 2025
7eb42d0
fix:Added human relay monitoring clipboard and monitoring interval co…
NyxJae Mar 20, 2025
bb7ac3e
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 23, 2025
5f51d80
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Mar 31, 2025
385ebf7
fix:Supplement Human Relay related configurations
NyxJae Mar 31, 2025
51f63a0
fix: Fix Human-relay international translation
NyxJae Mar 31, 2025
107d16e
fix: Optimize the display of Human-relay Dialog
NyxJae Mar 31, 2025
5d19c97
fix(test): add missing CodeActionKind mock in vscode.js
NyxJae Mar 31, 2025
7b3bfca
Merge branch 'main' of github.com:NyxJae/Roo-Code into human-relay
NyxJae Apr 1, 2025
e60355f
Merge branch 'main' into human-relay
NyxJae Apr 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@
"command": "roo-cline.setCustomStoragePath",
"title": "Set Custom Storage Path",
"category": "Roo Code"
},
{
"command": "roo-cline.sendClipboardToHumanRelay",
"title": "Roo Code: Send Clipboard Content to Human Relay",
"category": "Roo Code"
}
],
"keybindings": [
{
"command": "roo-cline.sendClipboardToHumanRelay",
"key": "ctrl+alt+v",
"mac": "cmd+alt+v"
}
],
"menus": {
Expand Down
4 changes: 4 additions & 0 deletions src/__mocks__/vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ const vscode = {
Directory: 2,
SymbolicLink: 64,
},
CodeActionKind: {
QuickFix: { value: "quickfix" }, // Match the structure in ClineProvider.test.ts
RefactorRewrite: { value: "refactor.rewrite" }, // Match the structure in ClineProvider.test.ts
},
TabInputText: class {
constructor(uri) {
this.uri = uri
Expand Down
36 changes: 36 additions & 0 deletions src/activate/humanRelay.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as vscode from "vscode"
import { getPanel } from "./registerCommands"

// Callback mapping of human relay response.
const humanRelayCallbacks = new Map<string, (response: string | undefined) => void>()

Expand All @@ -24,3 +27,36 @@ export const handleHumanRelayResponse = (response: { requestId: string; text?: s
humanRelayCallbacks.delete(response.requestId)
}
}

/**
* Validate if content contains any tags in <xxx> format
*/
export function containsValidTags(content: string): boolean {
const tagPattern = /<[^>]+>/
return tagPattern.test(content)
}

export const sendClipboardToHumanRelay = async () => {
const panel = getPanel()
if (!panel) {
return
}

try {
const clipboardText = await vscode.env.clipboard.readText()
if (!clipboardText) {
return
}

const requestId = "SendAIResponse"

panel?.webview.postMessage({ type: "closeHumanRelayDialog" })

vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
requestId,
text: clipboardText,
})
} catch (error) {
console.error("Failed to process clipboard content:", error)
}
}
13 changes: 10 additions & 3 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): Cl
return visibleProvider
}

import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay"
import {
registerHumanRelayCallback,
unregisterHumanRelayCallback,
handleHumanRelayResponse,
sendClipboardToHumanRelay,
} from "./humanRelay"
import { handleNewTask } from "./handleTask"

// Store panel references in both modes
Expand All @@ -24,7 +29,7 @@ let tabPanel: vscode.WebviewPanel | undefined = undefined

/**
* Get the currently active panel
* @returns WebviewPanel或WebviewView
* @returns WebviewPanel or WebviewView
*/
export function getPanel(): vscode.WebviewPanel | vscode.WebviewView | undefined {
return tabPanel || sidebarPanel
Expand Down Expand Up @@ -109,8 +114,10 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
"roo-cline.registerHumanRelayCallback": registerHumanRelayCallback,
"roo-cline.unregisterHumanRelayCallback": unregisterHumanRelayCallback,
"roo-cline.handleHumanRelayResponse": handleHumanRelayResponse,
"roo-cline.newTask": handleNewTask,
"roo-cline.sendClipboardToHumanRelay": sendClipboardToHumanRelay, // Keep local change
"roo-cline.newTask": handleNewTask, // Keep remote change
"roo-cline.setCustomStoragePath": async () => {
// Keep remote change
const { promptForCustomStoragePath } = await import("../shared/storagePathManager")
await promptForCustomStoragePath()
},
Expand Down
159 changes: 131 additions & 28 deletions src/api/providers/human-relay.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
// filepath: e:\Project\Roo-Code\src\api\providers\human-relay.ts
import { Anthropic } from "@anthropic-ai/sdk"
import { ApiHandlerOptions, ModelInfo } from "../../shared/api"
import { ApiHandler, SingleCompletionHandler } from "../index"
import { ApiStream } from "../transform/stream"
import * as vscode from "vscode"
import { ExtensionMessage } from "../../shared/ExtensionMessage"
import { getPanel } from "../../activate/registerCommands" // Import the getPanel function
import { getPanel } from "../../activate/registerCommands"
import { containsValidTags } from "../../activate/humanRelay"

/**
* Human Relay API processor
* This processor does not directly call the API, but interacts with the model through human operations copy and paste.
* This processor does not directly call the API, but interacts with the model through human operations like copy and paste.
*/
export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
private options: ApiHandlerOptions

constructor(options: ApiHandlerOptions) {
this.options = options
}

countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number> {
return Promise.resolve(0)
}

/**
* Create a message processing flow, display a dialog box to request human assistance
* @param systemPrompt System prompt words
* @param messages Message list
*/
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
// Get the most recent user message
const latestMessage = messages[messages.length - 1]

if (!latestMessage) {
throw new Error("No message to relay")
}

// If it is the first message, splice the system prompt word with the user message
// Concatenate system prompt with user message if this is the first message
let promptText = ""
if (messages.length === 1) {
promptText = `${systemPrompt}\n\n${getMessageContent(latestMessage)}`
Expand All @@ -45,23 +42,20 @@ export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
// Copy to clipboard
await vscode.env.clipboard.writeText(promptText)

// A dialog box pops up to request user action
const response = await showHumanRelayDialog(promptText)
// Display a dialog box to request user action
const response = await showHumanRelayDialog(promptText, this.options)

if (!response) {
// The user canceled the operation
throw new Error("Human relay operation cancelled")
}

// Return to the user input reply
yield { type: "text", text: response }
}

/**
* Get model information
*/
getModel(): { id: string; info: ModelInfo } {
// Human relay does not depend on a specific model, here is a default configuration
return {
id: "human-relay",
info: {
Expand All @@ -78,15 +72,12 @@ export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
}

/**
* Implementation of a single prompt
* @param prompt Prompt content
* Implementation of a single prompt completion
*/
async completePrompt(prompt: string): Promise<string> {
// Copy to clipboard
await vscode.env.clipboard.writeText(prompt)

// A dialog box pops up to request user action
const response = await showHumanRelayDialog(prompt)
const response = await showHumanRelayDialog(prompt, this.options)

if (!response) {
throw new Error("Human relay operation cancelled")
Expand All @@ -97,8 +88,7 @@ export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
}

/**
* Extract text content from message object
* @param message
* Extract text content from a message object
*/
function getMessageContent(message: Anthropic.Messages.MessageParam): string {
if (typeof message.content === "string") {
Expand All @@ -111,29 +101,142 @@ function getMessageContent(message: Anthropic.Messages.MessageParam): string {
}
return ""
}

// Global variables
let normalizedPrompt: string | null = null
let currentPrompt: string | null = null
let normalizedLastResponse: string | null = null
let globalClipboardInterval: NodeJS.Timeout | null = null

/**
* Normalize text by removing extra spaces
*/
function normalizeText(text: string | null): string {
if (!text) return ""
return text.replace(/\s+/g, " ").trim()
}

/**
* Displays the human relay dialog and waits for user response.
* @param promptText The prompt text that needs to be copied.
* @returns The user's input response or undefined (if canceled).
* Compare if two strings are equal (ignoring whitespace differences)
*/
async function showHumanRelayDialog(promptText: string): Promise<string | undefined> {
function isTextEqual(str1: string | null, str2: string | null): boolean {
if (str1 === str2) return true
if (!str1 || !str2) return false
return normalizeText(str1) === normalizeText(str2)
}

/**
* Stop clipboard monitoring
*/
function stopClipboardMonitoring() {
if (globalClipboardInterval) {
clearInterval(globalClipboardInterval)
globalClipboardInterval = null
}
}

/**
* Start clipboard monitoring
*/
async function startClipboardMonitoring(requestId: string, options?: ApiHandlerOptions) {
// Stop any existing monitoring
stopClipboardMonitoring()
vscode.env.clipboard.writeText(currentPrompt ?? "")

// Start new monitoring
const monitorInterval = Math.min(Math.max(100, options?.humanRelayMonitorInterval ?? 500), 2000)

globalClipboardInterval = setInterval(async () => {
try {
const currentClipboardContent = await vscode.env.clipboard.readText()
if (!currentClipboardContent) {
return
}

const normalizedClipboard = normalizeText(currentClipboardContent)

const panel = getPanel()

// Check if response is duplicate
if (normalizedClipboard === normalizedLastResponse) {
panel?.webview.postMessage({
type: "showHumanRelayResponseAlert",
requestId: "lastInteraction",
})
return
}
if (!containsValidTags(currentClipboardContent)) {
panel?.webview.postMessage({
type: "showHumanRelayResponseAlert",
requestId: "invalidResponse",
})
return
}

// Process valid new response
if (normalizedClipboard !== normalizedPrompt) {
normalizedLastResponse = normalizedClipboard

// Clear timer
stopClipboardMonitoring()

// Close dialog and send response
panel?.webview.postMessage({ type: "closeHumanRelayDialog" })
vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
requestId,
text: currentClipboardContent,
})
}
} catch (error) {
console.error("Error monitoring clipboard:", error)
}
}, monitorInterval)
}

/**
* Display human relay dialog and wait for user response
*/
async function showHumanRelayDialog(promptText: string, options?: ApiHandlerOptions): Promise<string | undefined> {
currentPrompt = promptText
normalizedPrompt = normalizeText(promptText)

return new Promise<string | undefined>((resolve) => {
// Create a unique request ID
const requestId = Date.now().toString()
// Create unique request ID
const requestId = "SendAIResponse"

// Register a global callback function
// Register global callback function
vscode.commands.executeCommand(
"roo-cline.registerHumanRelayCallback",
requestId,
(response: string | undefined) => {
stopClipboardMonitoring()
resolve(response)
},
)

// Open the dialog box directly using the current panel
// Get panel and register message handler
const panel = getPanel()
if (panel) {
panel.webview.onDidReceiveMessage((message) => {
if (message.type === "toggleHumanRelayMonitor" && message.requestId === requestId) {
if (message.bool) {
startClipboardMonitoring(requestId, options)
} else {
stopClipboardMonitoring()
}
}
})
}

// Open dialog
vscode.commands.executeCommand("roo-cline.showHumanRelayDialog", {
requestId,
promptText,
})

// Start polling clipboard changes if enabled
if (options?.humanRelayMonitorClipboard) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure unit/integration tests cover the new clipboard monitoring functionality, including duplicate response detection.

startClipboardMonitoring(requestId, options)
}
})
}
4 changes: 4 additions & 0 deletions src/exports/roo-code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ type ProviderSettings = {
modelMaxThinkingTokens?: number | undefined
includeMaxTokens?: boolean | undefined
fakeAi?: unknown | undefined
humanRelayMonitorClipboard?: boolean | undefined
humanRelayMonitorInterval?: number | undefined
}

type GlobalSettings = {
Expand Down Expand Up @@ -335,6 +337,8 @@ type GlobalSettings = {
}
| undefined
enhancementApiConfigId?: string | undefined
humanRelayMonitorClipboard?: boolean | undefined
humanRelayMonitorInterval?: number | undefined
}

type ClineMessage = {
Expand Down
Loading