mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-07 02:58:15 +00:00
Refactor workflow detection logic and add comprehensive test coverage
- Extract duplicated automated workflow detection into shared utility - Add 23 comprehensive unit tests covering all edge cases and scenarios - Add detailed JSDoc documentation with examples and issue references - Improve code maintainability and eliminate duplication Addresses PR review feedback for issue #4574. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
10967936f9
commit
a6982abee0
4 changed files with 448 additions and 28 deletions
|
|
@ -10,7 +10,7 @@ import { getCommitInfo, getWorkingState } from "../../utils/git"
|
|||
import { getWorkspacePath } from "../../utils/path"
|
||||
|
||||
import { openFile } from "../../integrations/misc/open-file"
|
||||
import { ClineProvider } from "../webview/ClineProvider"
|
||||
import { isInAutomatedWorkflowFromVisibleProvider } from "../../utils/workflow-detection"
|
||||
import { extractTextFromFile } from "../../integrations/misc/extract-text"
|
||||
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
|
||||
|
||||
|
|
@ -37,21 +37,10 @@ export async function openMention(mention?: string): Promise<void> {
|
|||
if (mention.endsWith("/")) {
|
||||
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
|
||||
} else {
|
||||
// Check if we're in an automated workflow when opening file mentions
|
||||
const visibleProvider = ClineProvider.getVisibleInstance()
|
||||
const currentCline = visibleProvider?.getCurrentCline()
|
||||
const autoApprovalEnabled = visibleProvider?.contextProxy.getValue("autoApprovalEnabled")
|
||||
// Check if we're in an automated workflow to preserve chat focus during AI processing
|
||||
const shouldPreserveFocus = isInAutomatedWorkflowFromVisibleProvider()
|
||||
|
||||
// Detect automated workflow: AI is actively processing, streaming, or auto-approval is enabled
|
||||
// and there's an active task that hasn't completed reading/processing
|
||||
const isInAutomatedWorkflow =
|
||||
currentCline &&
|
||||
(currentCline.isStreaming ||
|
||||
currentCline.isWaitingForFirstChunk ||
|
||||
(autoApprovalEnabled && !currentCline.didCompleteReadingStream) ||
|
||||
(autoApprovalEnabled && currentCline.presentAssistantMessageLocked))
|
||||
|
||||
openFile(absPath, { preserveFocus: !!isInAutomatedWorkflow })
|
||||
openFile(absPath, { preserveFocus: shouldPreserveFocus })
|
||||
}
|
||||
} else if (mention === "problems") {
|
||||
vscode.commands.executeCommand("workbench.actions.view.problems")
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import { getModels, flushModels } from "../../api/providers/fetchers/modelCache"
|
|||
import { GetModelsOptions } from "../../shared/api"
|
||||
import { generateSystemPrompt } from "./generateSystemPrompt"
|
||||
import { getCommand } from "../../utils/commands"
|
||||
import { isInAutomatedWorkflowFromProvider } from "../../utils/workflow-detection"
|
||||
|
||||
const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
|
||||
|
||||
|
|
@ -426,22 +427,12 @@ export const webviewMessageHandler = async (
|
|||
openImage(message.text!)
|
||||
break
|
||||
case "openFile":
|
||||
// Check if we're in an automated workflow (AI is streaming/processing)
|
||||
const currentCline = provider.getCurrentCline()
|
||||
const autoApprovalEnabled = getGlobalState("autoApprovalEnabled")
|
||||
|
||||
// Detect automated workflow: AI is actively processing, streaming, or auto-approval is enabled
|
||||
// and there's an active task that hasn't completed reading/processing
|
||||
const isInAutomatedWorkflow =
|
||||
currentCline &&
|
||||
(currentCline.isStreaming ||
|
||||
currentCline.isWaitingForFirstChunk ||
|
||||
(autoApprovalEnabled && !currentCline.didCompleteReadingStream) ||
|
||||
(autoApprovalEnabled && currentCline.presentAssistantMessageLocked))
|
||||
// Check if we're in an automated workflow to preserve chat focus during AI processing
|
||||
const shouldPreserveFocus = isInAutomatedWorkflowFromProvider(provider)
|
||||
|
||||
openFile(message.text!, {
|
||||
...(message.values as { create?: boolean; content?: string; line?: number }),
|
||||
preserveFocus: !!isInAutomatedWorkflow,
|
||||
preserveFocus: shouldPreserveFocus,
|
||||
})
|
||||
break
|
||||
case "openMention":
|
||||
|
|
|
|||
322
src/utils/__tests__/workflow-detection.test.ts
Normal file
322
src/utils/__tests__/workflow-detection.test.ts
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
// Mock ClineProvider first
|
||||
const mockGetVisibleInstance = jest.fn()
|
||||
jest.mock("../../core/webview/ClineProvider", () => ({
|
||||
ClineProvider: {
|
||||
getVisibleInstance: mockGetVisibleInstance,
|
||||
},
|
||||
}))
|
||||
|
||||
import {
|
||||
isInAutomatedWorkflow,
|
||||
isInAutomatedWorkflowFromProvider,
|
||||
isInAutomatedWorkflowFromVisibleProvider,
|
||||
} from "../workflow-detection"
|
||||
import type { Task } from "../../core/task/Task"
|
||||
import type { ClineProvider } from "../../core/webview/ClineProvider"
|
||||
|
||||
// Mock ClineProvider
|
||||
const mockProvider = {
|
||||
getCurrentCline: jest.fn(),
|
||||
contextProxy: {
|
||||
getValue: jest.fn(),
|
||||
},
|
||||
} as unknown as ClineProvider
|
||||
|
||||
const mockVisibleProvider = {
|
||||
getCurrentCline: jest.fn(),
|
||||
contextProxy: {
|
||||
getValue: jest.fn(),
|
||||
},
|
||||
} as unknown as ClineProvider
|
||||
|
||||
describe("workflow-detection", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("isInAutomatedWorkflow", () => {
|
||||
it("should return false when currentTask is null", () => {
|
||||
expect(isInAutomatedWorkflow(null, false)).toBe(false)
|
||||
expect(isInAutomatedWorkflow(null, true)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when currentTask is undefined", () => {
|
||||
expect(isInAutomatedWorkflow(undefined, false)).toBe(false)
|
||||
expect(isInAutomatedWorkflow(undefined, true)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when task is streaming", () => {
|
||||
const streamingTask = {
|
||||
isStreaming: true,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(streamingTask, false)).toBe(true)
|
||||
expect(isInAutomatedWorkflow(streamingTask, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when task is waiting for first chunk", () => {
|
||||
const waitingTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: true,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(waitingTask, false)).toBe(true)
|
||||
expect(isInAutomatedWorkflow(waitingTask, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when auto-approval is enabled and stream not completed", () => {
|
||||
const incompleteStreamTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: false,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(incompleteStreamTask, false)).toBe(false)
|
||||
expect(isInAutomatedWorkflow(incompleteStreamTask, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when auto-approval is enabled and assistant message is locked", () => {
|
||||
const lockedMessageTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: true,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(lockedMessageTask, false)).toBe(false)
|
||||
expect(isInAutomatedWorkflow(lockedMessageTask, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when task is idle and auto-approval is disabled", () => {
|
||||
const idleTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(idleTask, false)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when task is idle even with auto-approval enabled", () => {
|
||||
const idleTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(idleTask, true)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true for multiple simultaneous conditions", () => {
|
||||
const busyTask = {
|
||||
isStreaming: true,
|
||||
isWaitingForFirstChunk: true,
|
||||
didCompleteReadingStream: false,
|
||||
presentAssistantMessageLocked: true,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(busyTask, false)).toBe(true)
|
||||
expect(isInAutomatedWorkflow(busyTask, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle tasks with missing properties gracefully", () => {
|
||||
const partialTask = {} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(partialTask, false)).toBe(false)
|
||||
expect(isInAutomatedWorkflow(partialTask, true)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInAutomatedWorkflowFromProvider", () => {
|
||||
it("should return false when no current task", () => {
|
||||
mockProvider.getCurrentCline = jest.fn().mockReturnValue(null)
|
||||
mockProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(isInAutomatedWorkflowFromProvider(mockProvider)).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when task is streaming", () => {
|
||||
const streamingTask = {
|
||||
isStreaming: true,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockProvider.getCurrentCline = jest.fn().mockReturnValue(streamingTask)
|
||||
mockProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(isInAutomatedWorkflowFromProvider(mockProvider)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when auto-approval is enabled and conditions are met", () => {
|
||||
const incompleteTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: false,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockProvider.getCurrentCline = jest.fn().mockReturnValue(incompleteTask)
|
||||
mockProvider.contextProxy.getValue = jest.fn().mockReturnValue(true)
|
||||
|
||||
expect(isInAutomatedWorkflowFromProvider(mockProvider)).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when task is idle and auto-approval is disabled", () => {
|
||||
const idleTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockProvider.getCurrentCline = jest.fn().mockReturnValue(idleTask)
|
||||
mockProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
|
||||
expect(isInAutomatedWorkflowFromProvider(mockProvider)).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle undefined values from contextProxy", () => {
|
||||
const streamingTask = {
|
||||
isStreaming: true,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockProvider.getCurrentCline = jest.fn().mockReturnValue(streamingTask)
|
||||
mockProvider.contextProxy.getValue = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
// Should still return true because isStreaming is true, regardless of autoApprovalEnabled value
|
||||
expect(isInAutomatedWorkflowFromProvider(mockProvider)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("isInAutomatedWorkflowFromVisibleProvider", () => {
|
||||
it("should return false when no visible provider", () => {
|
||||
mockGetVisibleInstance.mockReturnValue(null)
|
||||
|
||||
expect(isInAutomatedWorkflowFromVisibleProvider()).toBe(false)
|
||||
})
|
||||
|
||||
it("should return false when visible provider has no current task", () => {
|
||||
mockVisibleProvider.getCurrentCline = jest.fn().mockReturnValue(null)
|
||||
mockVisibleProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
|
||||
|
||||
expect(isInAutomatedWorkflowFromVisibleProvider()).toBe(false)
|
||||
})
|
||||
|
||||
it("should return true when visible provider has streaming task", () => {
|
||||
const streamingTask = {
|
||||
isStreaming: true,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockVisibleProvider.getCurrentCline = jest.fn().mockReturnValue(streamingTask)
|
||||
mockVisibleProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
|
||||
|
||||
expect(isInAutomatedWorkflowFromVisibleProvider()).toBe(true)
|
||||
})
|
||||
|
||||
it("should return true when visible provider has auto-approval enabled with incomplete task", () => {
|
||||
const incompleteTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: false,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockVisibleProvider.getCurrentCline = jest.fn().mockReturnValue(incompleteTask)
|
||||
mockVisibleProvider.contextProxy.getValue = jest.fn().mockReturnValue(true)
|
||||
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
|
||||
|
||||
expect(isInAutomatedWorkflowFromVisibleProvider()).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when visible provider has idle task and no auto-approval", () => {
|
||||
const idleTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
mockVisibleProvider.getCurrentCline = jest.fn().mockReturnValue(idleTask)
|
||||
mockVisibleProvider.contextProxy.getValue = jest.fn().mockReturnValue(false)
|
||||
mockGetVisibleInstance.mockReturnValue(mockVisibleProvider)
|
||||
|
||||
expect(isInAutomatedWorkflowFromVisibleProvider()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("edge cases and type safety", () => {
|
||||
it("should handle boolean coercion correctly", () => {
|
||||
// Test that the double negation (!!) in the original function works as expected
|
||||
const truthyTask = {
|
||||
isStreaming: true,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(truthyTask, false)).toBe(true)
|
||||
|
||||
// Test falsy values
|
||||
const falsyTask = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: true,
|
||||
presentAssistantMessageLocked: false,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(falsyTask, false)).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle provider with undefined getCurrentCline", () => {
|
||||
const providerWithUndefinedTask = {
|
||||
getCurrentCline: jest.fn().mockReturnValue(undefined),
|
||||
contextProxy: {
|
||||
getValue: jest.fn().mockReturnValue(false),
|
||||
},
|
||||
} as unknown as ClineProvider
|
||||
|
||||
expect(isInAutomatedWorkflowFromProvider(providerWithUndefinedTask)).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle all combinations of auto-approval conditions", () => {
|
||||
// Test matrix of auto-approval conditions
|
||||
const testCases = [
|
||||
// [didCompleteReadingStream, presentAssistantMessageLocked, autoApprovalEnabled, expected]
|
||||
[true, false, false, false],
|
||||
[true, false, true, false],
|
||||
[false, false, false, false],
|
||||
[false, false, true, true], // incomplete stream with auto-approval
|
||||
[true, true, false, false],
|
||||
[true, true, true, true], // locked message with auto-approval
|
||||
[false, true, false, false],
|
||||
[false, true, true, true], // both incomplete stream and locked message
|
||||
]
|
||||
|
||||
testCases.forEach(([didComplete, isLocked, autoApproval, expected]) => {
|
||||
const task = {
|
||||
isStreaming: false,
|
||||
isWaitingForFirstChunk: false,
|
||||
didCompleteReadingStream: didComplete,
|
||||
presentAssistantMessageLocked: isLocked,
|
||||
} as Task
|
||||
|
||||
expect(isInAutomatedWorkflow(task, autoApproval as boolean)).toBe(expected as boolean)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
118
src/utils/workflow-detection.ts
Normal file
118
src/utils/workflow-detection.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* @fileoverview Automated workflow detection utilities for preserving chat focus during AI operations.
|
||||
*
|
||||
* This module provides utilities to detect when the AI is actively processing tasks, which is used
|
||||
* to determine whether file operations should preserve chat focus to prevent interruption of user
|
||||
* input or accidental exposure of sensitive information like API keys.
|
||||
*
|
||||
* Created as part of the fix for GitHub issue #4574: "chatbox loses focus during automated workflow"
|
||||
*
|
||||
* @author Roo Code Team
|
||||
* @since v3.19.7
|
||||
*/
|
||||
|
||||
import type { Task } from "../core/task/Task"
|
||||
import type { ClineProvider } from "../core/webview/ClineProvider"
|
||||
import { ClineProvider as ClineProviderClass } from "../core/webview/ClineProvider"
|
||||
|
||||
/**
|
||||
* Detects if we are currently in an automated workflow where the AI is actively processing.
|
||||
*
|
||||
* This utility function addresses GitHub issue #4574 - preventing chatbox focus loss during
|
||||
* automated file editing workflows. When the AI is actively working (streaming, processing,
|
||||
* or auto-approving), opening files should preserve chat focus to prevent accidental
|
||||
* interruption of user input or API key exposure.
|
||||
*
|
||||
* An automated workflow is detected when:
|
||||
* - AI is actively streaming responses (`isStreaming === true`)
|
||||
* - AI is waiting for the first chunk of a response (`isWaitingForFirstChunk === true`)
|
||||
* - Auto-approval is enabled and the task hasn't completed reading/processing
|
||||
* (`autoApprovalEnabled && didCompleteReadingStream === false`)
|
||||
* - Auto-approval is enabled and the assistant message is locked for processing
|
||||
* (`autoApprovalEnabled && presentAssistantMessageLocked === true`)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Basic usage with explicit task and settings
|
||||
* const shouldPreserveFocus = isInAutomatedWorkflow(currentTask, autoApprovalEnabled);
|
||||
* openFile(filePath, { preserveFocus: shouldPreserveFocus });
|
||||
*
|
||||
* // Used in webview message handlers
|
||||
* if (isInAutomatedWorkflow(task, autoApproval)) {
|
||||
* // Preserve focus during automated operations
|
||||
* openFile(path, { preserveFocus: true });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param currentTask - The current active task, if any. Can be null/undefined when no task is active.
|
||||
* @param autoApprovalEnabled - Whether auto-approval mode is currently enabled in the UI settings.
|
||||
* @returns true if we're in an automated workflow and should preserve focus to prevent interruption
|
||||
* @since v3.19.7 - Added as part of focus preservation fix for issue #4574
|
||||
*/
|
||||
export function isInAutomatedWorkflow(currentTask: Task | null | undefined, autoApprovalEnabled: boolean): boolean {
|
||||
return !!(
|
||||
currentTask &&
|
||||
(currentTask.isStreaming ||
|
||||
currentTask.isWaitingForFirstChunk ||
|
||||
(autoApprovalEnabled && currentTask.didCompleteReadingStream === false) ||
|
||||
(autoApprovalEnabled && currentTask.presentAssistantMessageLocked === true))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for detecting automated workflows using a ClineProvider instance.
|
||||
*
|
||||
* This function extracts the current task and auto-approval state directly from the
|
||||
* provider's context, making it easier to use in components that have access to a
|
||||
* ClineProvider instance.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In webview message handlers where provider is available
|
||||
* const shouldPreserveFocus = isInAutomatedWorkflowFromProvider(provider);
|
||||
* openFile(message.text!, {
|
||||
* ...(message.values as OpenFileOptions),
|
||||
* preserveFocus: shouldPreserveFocus,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param provider - The ClineProvider instance to get current task and settings from
|
||||
* @returns true if we're in an automated workflow and should preserve focus
|
||||
* @since v3.19.7 - Added as part of focus preservation fix for issue #4574
|
||||
*/
|
||||
export function isInAutomatedWorkflowFromProvider(provider: ClineProvider): boolean {
|
||||
const currentTask = provider.getCurrentCline()
|
||||
const autoApprovalEnabled = provider.contextProxy.getValue("autoApprovalEnabled") as boolean
|
||||
return isInAutomatedWorkflow(currentTask, autoApprovalEnabled)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for detecting automated workflows from the currently visible provider.
|
||||
*
|
||||
* This function is particularly useful for components like file mention handlers that
|
||||
* don't have direct access to a ClineProvider instance but need to determine if they're
|
||||
* in an automated workflow context. It uses the static `getVisibleInstance()` method
|
||||
* to access the currently active provider.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In mention handlers or other components without provider access
|
||||
* export async function openMention(mention?: string): Promise<void> {
|
||||
* if (mention?.startsWith('/')) {
|
||||
* const shouldPreserveFocus = isInAutomatedWorkflowFromVisibleProvider();
|
||||
* openFile(absPath, { preserveFocus: shouldPreserveFocus });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns true if we're in an automated workflow and should preserve focus,
|
||||
* false if no visible provider exists or not in automated workflow
|
||||
* @since v3.19.7 - Added as part of focus preservation fix for issue #4574
|
||||
*/
|
||||
export function isInAutomatedWorkflowFromVisibleProvider(): boolean {
|
||||
const visibleProvider = ClineProviderClass.getVisibleInstance()
|
||||
if (!visibleProvider) {
|
||||
return false
|
||||
}
|
||||
return isInAutomatedWorkflowFromProvider(visibleProvider)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue