Skip to content

Commit 75699dc

Browse files
oleg kusovoleg kusov
authored andcommitted
feat: add support of simple chat with minimal prompt
1 parent a3d8c0e commit 75699dc

File tree

9 files changed

+228
-22
lines changed

9 files changed

+228
-22
lines changed

src/core/Cline.ts

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,33 @@ export class Cline extends EventEmitter<ClineEvents> {
393393
throw new Error(`[Cline#ask] task ${this.taskId}.${this.instanceId} aborted`)
394394
}
395395

396+
// --- ADD LOGIC FOR chat_input_wait ---
397+
if (type === "chat_input_wait") {
398+
console.log("[Cline.ts ask] Waiting for chat input...")
399+
this.askResponse = undefined // Clear previous response state
400+
this.askResponseText = undefined
401+
this.askResponseImages = undefined
402+
// Don't add a visible message for this internal state
403+
// Signal UI to enable input
404+
const provider = this.providerRef.deref()
405+
if (!provider) {
406+
throw new Error("[Cline.ts ask] Cannot wait for input, provider reference lost.")
407+
}
408+
await provider.postMessageToWebview({ type: "acceptInput" })
409+
410+
// Wait for handleWebviewAskResponse to set this.askResponse
411+
await pWaitFor(() => this.askResponse !== undefined, { interval: 100 })
412+
413+
console.log("[Cline.ts ask] Resuming after chat input received.")
414+
const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages }
415+
this.askResponse = undefined // Clear state for next potential ask
416+
this.askResponseText = undefined
417+
this.askResponseImages = undefined
418+
this.emit("taskAskResponded") // Emit standard event
419+
return result
420+
}
421+
// --- END LOGIC FOR chat_input_wait ---
422+
396423
let askTs: number
397424

398425
if (partial !== undefined) {
@@ -852,8 +879,18 @@ export class Cline extends EventEmitter<ClineEvents> {
852879
}
853880

854881
private async initiateTaskLoop(userContent: UserContent): Promise<void> {
855-
// Kicks off the checkpoints initialization process in the background.
856-
this.getCheckpointService()
882+
// Only kickoff checkpoints for non-chat modes
883+
const provider = this.providerRef.deref()
884+
const { mode } = (await provider?.getState()) ?? {}
885+
886+
// Skip checkpoints for continuous chat modes
887+
if (mode !== "chat") {
888+
// Kicks off the checkpoints initialization process in the background.
889+
this.getCheckpointService()
890+
} else {
891+
// Disable checkpoints for chat modes
892+
this.enableCheckpoints = false
893+
}
857894

858895
let nextUserContent = userContent
859896
let includeFileDetails = true
@@ -876,10 +913,13 @@ export class Cline extends EventEmitter<ClineEvents> {
876913
// as he can.
877914

878915
if (didEndLoop) {
879-
// For now a task never 'completes'. This will only happen if
880-
// the user hits max requests and denies resetting the count.
916+
// If recursivelyMakeClineRequests returns true, it means the loop should end
917+
// This happens on error, abort, or when chat mode needs user input.
881918
break
882919
} else {
920+
// If recursivelyMakeClineRequests returns false, it means the model didn't end the loop
921+
// (e.g., it completed a turn without using a tool in non-chat mode).
922+
// We need to prepare the input for the next iteration of this loop.
883923
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed() }]
884924
this.consecutiveMistakeCount++
885925
}
@@ -1011,6 +1051,32 @@ export class Cline extends EventEmitter<ClineEvents> {
10111051
)
10121052
})()
10131053

1054+
// =================================
1055+
// DEBUG: Log API request details
1056+
// =================================
1057+
console.log(`\n\n========== ROO CODE API REQUEST (${new Date().toISOString()}) ==========`)
1058+
console.log(`Mode: ${mode || "default"}`)
1059+
console.log(`System prompt length: ${systemPrompt.length} characters`)
1060+
console.log(`Conversation history: ${this.apiConversationHistory.length} messages`)
1061+
1062+
// Count total tokens in system prompt + conversation history
1063+
const modelInfo = this.api.getModel().info
1064+
console.log(`Model: ${this.api.getModel().id}, Context window: ${modelInfo.contextWindow} tokens`)
1065+
1066+
// Detailed debug info on each message
1067+
if (mode === "chat") {
1068+
console.log(`\n======= MINIMAL MODE DEBUG INFO =======`)
1069+
console.log(`System prompt: ${systemPrompt}...`)
1070+
console.log(
1071+
`\nLast user message content: ${
1072+
this.apiConversationHistory.length > 0 &&
1073+
this.apiConversationHistory[this.apiConversationHistory.length - 1].role === "user"
1074+
? JSON.stringify(this.apiConversationHistory[this.apiConversationHistory.length - 1].content)
1075+
: "No user message"
1076+
}...`,
1077+
)
1078+
}
1079+
10141080
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
10151081
if (previousApiReqIndex >= 0) {
10161082
const previousRequest = this.clineMessages[previousApiReqIndex]?.text
@@ -1199,7 +1265,7 @@ export class Cline extends EventEmitter<ClineEvents> {
11991265
// (have to do this for partial and complete since sending content in thinking tags to markdown renderer will automatically be removed)
12001266
// Remove end substrings of <thinking or </thinking (below xml parsing is only for opening tags)
12011267
// (this is done with the xml parsing below now, but keeping here for reference)
1202-
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?$/, "")
1268+
// content = content.replace(/<\/?t(?:h(?:i(?:n(?:k(?:i(?:n(?:g)?)?)?)?$/, "")
12031269
// Remove all instances of <thinking> (with optional line break after) and </thinking> (with optional line break before)
12041270
// - Needs to be separate since we dont want to remove the line break before the first tag
12051271
// - Needs to happen before the xml parsing below
@@ -1545,7 +1611,7 @@ export class Cline extends EventEmitter<ClineEvents> {
15451611
if (!block.partial || this.didRejectTool || this.didAlreadyUseTool) {
15461612
// block is finished streaming and executing
15471613
if (this.currentStreamingContentIndex === this.assistantMessageContent.length - 1) {
1548-
// its okay that we increment if !didCompleteReadingStream, it'll just return bc out of bounds and as streaming continues it will call presentAssitantMessage if a new block is ready. if streaming is finished then we set userMessageContentReady to true when out of bounds. This gracefully allows the stream to continue on and all potential content blocks be presented.
1614+
// its okay that we increment if !didCompleteReadingStream, it'll just return bc out of bounds and as streaming continues it will call presentAssistantMessage if a new block is ready. if streaming is finished then we set userMessageContentReady to true when out of bounds. This gracefully allows the stream to continue on and all potential content blocks be presented.
15491615
// last block is complete and it is finished executing
15501616
this.userMessageContentReady = true // will allow pwaitfor to continue
15511617
}
@@ -1892,15 +1958,52 @@ export class Cline extends EventEmitter<ClineEvents> {
18921958
// if the model did not tool use, then we need to tell it to either use a tool or attempt_completion
18931959
const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")
18941960
if (!didToolUse) {
1895-
this.userMessageContent.push({
1896-
type: "text",
1897-
text: formatResponse.noToolsUsed(),
1898-
})
1899-
this.consecutiveMistakeCount++
1900-
}
1961+
// --- Get provider and mode ---
1962+
const provider = this.providerRef.deref()
1963+
const { mode } = (await provider?.getState()) ?? {}
19011964

1902-
const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)
1903-
didEndLoop = recDidEndLoop
1965+
if (mode !== "chat") {
1966+
// Non-chat mode, no tool use: Add feedback and recurse internally
1967+
this.userMessageContent.push({
1968+
type: "text",
1969+
text: formatResponse.noToolsUsed(),
1970+
})
1971+
this.consecutiveMistakeCount++
1972+
// Continue the loop by recursing with the feedback message
1973+
// Return the boolean result of this recursive call
1974+
return await this.recursivelyMakeClineRequests(this.userMessageContent, false)
1975+
} else {
1976+
// Chat mode, no tool use: Use ask('chat_input_wait') to pause and get input
1977+
console.log("[Cline.ts] Chat mode, no tool use. Waiting for next user input via ask.")
1978+
const { response, text, images } = await this.ask("chat_input_wait")
1979+
1980+
if (response === "messageResponse") {
1981+
// User provided input
1982+
const nextUserContent: UserContent = []
1983+
if (text) nextUserContent.push({ type: "text", text })
1984+
if (images) nextUserContent.push(...formatResponse.imageBlocks(images))
1985+
1986+
if (nextUserContent.length > 0) {
1987+
// Add user input to UI (ask doesn't add messages for chat_input_wait)
1988+
await this.say("user_feedback", text, images)
1989+
// Recurse with the new user input
1990+
return await this.recursivelyMakeClineRequests(nextUserContent, false)
1991+
} else {
1992+
// Resumed but no content? End the loop.
1993+
console.warn("[Cline.ts] Resumed chat wait but received no content.")
1994+
return true
1995+
}
1996+
} else {
1997+
// User likely cancelled or something went wrong during the ask wait. End the loop.
1998+
console.log("[Cline.ts] Chat input wait did not receive messageResponse.")
1999+
return true
2000+
}
2001+
}
2002+
} else {
2003+
// Tool use detected: tool results are already prepared in this.userMessageContent
2004+
// by presentAssistantMessage. Continue the loop by recursing.
2005+
return await this.recursivelyMakeClineRequests(this.userMessageContent, false)
2006+
}
19042007
} else {
19052008
// if there's no assistant_responses, that means we got no text or tool_use content blocks from API which we should assume is an error
19062009
await this.say(
@@ -1996,10 +2099,13 @@ export class Cline extends EventEmitter<ClineEvents> {
19962099
}
19972100

19982101
async getEnvironmentDetails(includeFileDetails: boolean = false) {
2102+
const state = await this.providerRef.deref()?.getState()
2103+
2104+
if (state?.mode === "chat") return ""
2105+
19992106
let details = ""
20002107

2001-
const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } =
2002-
(await this.providerRef.deref()?.getState()) ?? {}
2108+
const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = state ?? {}
20032109

20042110
// It could be useful for cline to know if the user went from one or no file to another between messages, so we always include this context
20052111
details += "\n\n# VSCode Visible Files"
@@ -2196,6 +2302,7 @@ export class Cline extends EventEmitter<ClineEvents> {
21962302

21972303
details += `\n\n# Current Mode\n`
21982304
details += `<slug>${currentMode}</slug>\n`
2305+
21992306
details += `<name>${modeDetails.name}</name>\n`
22002307
details += `<model>${apiModelId}</model>\n`
22012308

src/core/prompts/system.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ async function generatePrompt(
4949
throw new Error("Extension context is required for generating system prompt")
5050
}
5151

52+
// Special handling for code-minimal mode
53+
if (mode === "chat") {
54+
// Get the full mode config to ensure we have the role definition
55+
const modeConfig = getModeBySlug(mode, customModeConfigs) || modes.find((m) => m.slug === mode) || modes[0]
56+
const roleDefinition = promptComponent?.roleDefinition || modeConfig.roleDefinition
57+
const customInstructionsText = promptComponent?.customInstructions || modeConfig.customInstructions || ""
58+
const globalCustomInstructionsText = globalCustomInstructions || ""
59+
60+
// Extremely minimal prompt for code-minimal mode
61+
return `${roleDefinition}
62+
63+
${customInstructionsText}
64+
${globalCustomInstructionsText}`
65+
}
66+
5267
// If diff is disabled, don't pass the diffStrategy
5368
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined
5469

src/exports/roo-code.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ type ClineMessage = {
308308
| "mistake_limit_reached"
309309
| "browser_action_launch"
310310
| "use_mcp_server"
311+
<<<<<<< HEAD
312+
=======
313+
| "finishTask"
314+
| "chat_input_wait"
315+
>>>>>>> 3305e297 (feat: add support of simple chat with minimal prompt)
311316
)
312317
| undefined
313318
say?:
@@ -383,6 +388,11 @@ type RooCodeEvents = {
383388
| "mistake_limit_reached"
384389
| "browser_action_launch"
385390
| "use_mcp_server"
391+
<<<<<<< HEAD
392+
=======
393+
| "finishTask"
394+
| "chat_input_wait"
395+
>>>>>>> 3305e297 (feat: add support of simple chat with minimal prompt)
386396
)
387397
| undefined
388398
say?:

src/exports/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,11 @@ type ClineMessage = {
313313
| "mistake_limit_reached"
314314
| "browser_action_launch"
315315
| "use_mcp_server"
316+
<<<<<<< HEAD
317+
=======
318+
| "finishTask"
319+
| "chat_input_wait"
320+
>>>>>>> 3305e297 (feat: add support of simple chat with minimal prompt)
316321
)
317322
| undefined
318323
say?:
@@ -392,6 +397,11 @@ type RooCodeEvents = {
392397
| "mistake_limit_reached"
393398
| "browser_action_launch"
394399
| "use_mcp_server"
400+
<<<<<<< HEAD
401+
=======
402+
| "finishTask"
403+
| "chat_input_wait"
404+
>>>>>>> 3305e297 (feat: add support of simple chat with minimal prompt)
395405
)
396406
| undefined
397407
say?:

src/schemas/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,11 @@ export const clineAsks = [
743743
"mistake_limit_reached",
744744
"browser_action_launch",
745745
"use_mcp_server",
746+
<<<<<<< HEAD
747+
=======
748+
"finishTask",
749+
"chat_input_wait",
750+
>>>>>>> 3305e297 (feat: add support of simple chat with minimal prompt)
746751
] as const
747752

748753
export const clineAskSchema = z.enum(clineAsks)

src/shared/modes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ export const modes: readonly ModeConfig[] = [
5959
"You are Roo, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
6060
groups: ["read", "edit", "browser", "command", "mcp"],
6161
},
62+
{
63+
slug: "chat",
64+
name: "💬 Chat (minimal token usage)",
65+
roleDefinition:
66+
"You are Roo, a simple chat assistant that only processes files manually attached with '@context' by the user.",
67+
groups: ["read", "edit"],
68+
customInstructions:
69+
"This is a direct chat mode. You will ONLY have access to files explicitly attached by the user using '@context'. No automatic context, no auto-loaded files, no workspace awareness. Treat this as a clean environment where only explicitly shared files exist.",
70+
},
6271
{
6372
slug: "architect",
6473
name: "🏗️ Architect",

webview-ui/src/App.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ const App = () => {
7878
}
7979

8080
if (message.type === "acceptInput") {
81-
chatViewRef.current?.acceptInput()
81+
console.log("[App.tsx] Received 'acceptInput' message from backend.")
82+
if (chatViewRef.current) {
83+
console.log("[App.tsx] Calling chatViewRef.current.enableChatInput().")
84+
chatViewRef.current?.enableChatInput()
85+
} else {
86+
console.error("[App.tsx] chatViewRef is null when trying to call enableChatInput.")
87+
}
8288
}
8389
},
8490
[switchTab],

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,17 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
363363
const isComposing = event.nativeEvent?.isComposing ?? false
364364
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
365365
event.preventDefault()
366-
onSend()
366+
367+
console.log(`[ChatTextArea.tsx] Enter pressed. textAreaDisabled: ${textAreaDisabled}`)
368+
369+
if (!textAreaDisabled) {
370+
console.log("[ChatTextArea.tsx] Calling onSend().")
371+
onSend()
372+
} else {
373+
console.warn(
374+
"[ChatTextArea.tsx] Enter pressed, but onSend() NOT called because textAreaDisabled is true.",
375+
)
376+
}
367377
}
368378

369379
if (event.key === "Backspace" && !isComposing) {
@@ -416,6 +426,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
416426
queryItems,
417427
customModes,
418428
fileSearchResults,
429+
textAreaDisabled,
419430
],
420431
)
421432

0 commit comments

Comments
 (0)