Skip to content

Commit ef61905

Browse files
authored
Merge branch 'RooCodeInc:main' into main
2 parents 57d3fbe + 7a0a6de commit ef61905

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1840
-2069
lines changed

.changeset/stale-rivers-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
roo-code: minor
3+
---
4+
5+
Add copy prompt button to task actions. Based on [@vultrnerd's feedback](https://github.com/Kilo-Org/kilocode/discussions/850).

.github/workflows/nightly-publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ on:
1111
jobs:
1212
publish-nightly:
1313
runs-on: ubuntu-latest
14-
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
1514

1615
permissions:
1716
contents: read # No tags pushed → read is enough.

src/core/config/ContextProxy.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,23 @@ export class ContextProxy {
147147
: this.originalContext.secrets.store(key, value)
148148
}
149149

150+
/**
151+
* Refresh secrets from storage and update cache
152+
* This is useful when you need to ensure the cache has the latest values
153+
*/
154+
async refreshSecrets(): Promise<void> {
155+
const promises = SECRET_STATE_KEYS.map(async (key) => {
156+
try {
157+
this.secretCache[key] = await this.originalContext.secrets.get(key)
158+
} catch (error) {
159+
logger.error(
160+
`Error refreshing secret ${key}: ${error instanceof Error ? error.message : String(error)}`,
161+
)
162+
}
163+
})
164+
await Promise.all(promises)
165+
}
166+
150167
private getAllSecretState(): SecretState {
151168
return Object.fromEntries(SECRET_STATE_KEYS.map((key) => [key, this.getSecret(key)]))
152169
}

src/core/tools/applyDiffTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ export async function applyDiffToolLegacy(
145145
cline.diffViewProvider.editType = "modify"
146146
await cline.diffViewProvider.open(relPath)
147147
await cline.diffViewProvider.update(diffResult.content, true)
148-
await cline.diffViewProvider.scrollToFirstDiff()
148+
cline.diffViewProvider.scrollToFirstDiff()
149149

150150
// Check if file is write-protected
151151
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false

src/core/tools/multiApplyDiffTool.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,12 @@ Expected structure:
159159
</args>
160160
161161
Original error: ${errorMessage}`
162-
throw new Error(detailedError)
162+
cline.consecutiveMistakeCount++
163+
cline.recordToolError("apply_diff")
164+
TelemetryService.instance.captureDiffApplicationError(cline.taskId, cline.consecutiveMistakeCount)
165+
await cline.say("diff_error", `Failed to parse apply_diff XML: ${errorMessage}`)
166+
pushToolResult(detailedError)
167+
return
163168
}
164169
} else if (legacyPath && typeof legacyDiffContent === "string") {
165170
// Handle legacy parameters (old way)
@@ -505,7 +510,7 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
505510
cline.diffViewProvider.editType = "modify"
506511
await cline.diffViewProvider.open(relPath)
507512
await cline.diffViewProvider.update(originalContent!, true)
508-
await cline.diffViewProvider.scrollToFirstDiff()
513+
cline.diffViewProvider.scrollToFirstDiff()
509514

510515
// For batch operations, we've already gotten approval
511516
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false

src/core/webview/ClineProvider.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -884,11 +884,6 @@ export class ClineProvider
884884
this.contextProxy.setProviderSettings(providerSettings),
885885
])
886886

887-
// Notify CodeIndexManager about the settings change
888-
if (this.codeIndexManager) {
889-
await this.codeIndexManager.handleExternalSettingsChange()
890-
}
891-
892887
// Change the provider for the current task.
893888
// TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
894889
const task = this.getCurrentCline()

src/core/webview/webviewMessageHandler.ts

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,21 @@ export const webviewMessageHandler = async (
11131113
break
11141114
case "browserToolEnabled":
11151115
await updateGlobalState("browserToolEnabled", message.bool ?? true)
1116+
await provider.postStateToWebview()
1117+
break
1118+
case "codebaseIndexEnabled":
1119+
// Update the codebaseIndexConfig with the new enabled state
1120+
const currentCodebaseConfig = getGlobalState("codebaseIndexConfig") || {}
1121+
await updateGlobalState("codebaseIndexConfig", {
1122+
...currentCodebaseConfig,
1123+
codebaseIndexEnabled: message.bool ?? false,
1124+
})
1125+
1126+
// Notify the code index manager about the change
1127+
if (provider.codeIndexManager) {
1128+
await provider.codeIndexManager.handleSettingsChange()
1129+
}
1130+
11161131
await provider.postStateToWebview()
11171132
break
11181133
case "language":
@@ -1806,38 +1821,86 @@ export const webviewMessageHandler = async (
18061821

18071822
break
18081823
}
1809-
case "codebaseIndexConfig": {
1810-
const codebaseIndexConfig = message.values ?? {
1811-
codebaseIndexEnabled: false,
1812-
codebaseIndexQdrantUrl: "http://localhost:6333",
1813-
codebaseIndexEmbedderProvider: "openai",
1814-
codebaseIndexEmbedderBaseUrl: "",
1815-
codebaseIndexEmbedderModelId: "",
1824+
1825+
case "saveCodeIndexSettingsAtomic": {
1826+
if (!message.codeIndexSettings) {
1827+
break
18161828
}
1817-
await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig)
1829+
1830+
const settings = message.codeIndexSettings
18181831

18191832
try {
1833+
// Save global state settings atomically (without codebaseIndexEnabled which is now in global settings)
1834+
const currentConfig = getGlobalState("codebaseIndexConfig") || {}
1835+
const globalStateConfig = {
1836+
...currentConfig,
1837+
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
1838+
codebaseIndexEmbedderProvider: settings.codebaseIndexEmbedderProvider,
1839+
codebaseIndexEmbedderBaseUrl: settings.codebaseIndexEmbedderBaseUrl,
1840+
codebaseIndexEmbedderModelId: settings.codebaseIndexEmbedderModelId,
1841+
codebaseIndexOpenAiCompatibleBaseUrl: settings.codebaseIndexOpenAiCompatibleBaseUrl,
1842+
codebaseIndexOpenAiCompatibleModelDimension: settings.codebaseIndexOpenAiCompatibleModelDimension,
1843+
}
1844+
1845+
// Save global state first
1846+
await updateGlobalState("codebaseIndexConfig", globalStateConfig)
1847+
1848+
// Save secrets directly using context proxy
1849+
if (settings.codeIndexOpenAiKey !== undefined) {
1850+
await provider.contextProxy.storeSecret("codeIndexOpenAiKey", settings.codeIndexOpenAiKey)
1851+
}
1852+
if (settings.codeIndexQdrantApiKey !== undefined) {
1853+
await provider.contextProxy.storeSecret("codeIndexQdrantApiKey", settings.codeIndexQdrantApiKey)
1854+
}
1855+
if (settings.codebaseIndexOpenAiCompatibleApiKey !== undefined) {
1856+
await provider.contextProxy.storeSecret(
1857+
"codebaseIndexOpenAiCompatibleApiKey",
1858+
settings.codebaseIndexOpenAiCompatibleApiKey,
1859+
)
1860+
}
1861+
if (settings.codebaseIndexGeminiApiKey !== undefined) {
1862+
await provider.contextProxy.storeSecret(
1863+
"codebaseIndexGeminiApiKey",
1864+
settings.codebaseIndexGeminiApiKey,
1865+
)
1866+
}
1867+
1868+
// Verify secrets are actually stored
1869+
const storedOpenAiKey = provider.contextProxy.getSecret("codeIndexOpenAiKey")
1870+
1871+
// Notify code index manager of changes
18201872
if (provider.codeIndexManager) {
1821-
await provider.codeIndexManager.handleExternalSettingsChange()
1873+
await provider.codeIndexManager.handleSettingsChange()
18221874

1823-
// If now configured and enabled, start indexing automatically
1875+
// Auto-start indexing if now enabled and configured
18241876
if (provider.codeIndexManager.isFeatureEnabled && provider.codeIndexManager.isFeatureConfigured) {
18251877
if (!provider.codeIndexManager.isInitialized) {
18261878
await provider.codeIndexManager.initialize(provider.contextProxy)
18271879
}
1828-
// Start indexing in background (no await)
18291880
provider.codeIndexManager.startIndexing()
18301881
}
18311882
}
1883+
1884+
// Send success response
1885+
await provider.postMessageToWebview({
1886+
type: "codeIndexSettingsSaved",
1887+
success: true,
1888+
settings: globalStateConfig,
1889+
})
1890+
1891+
// Update webview state
1892+
await provider.postStateToWebview()
18321893
} catch (error) {
1833-
provider.log(
1834-
`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`,
1835-
)
1894+
provider.log(`Error saving code index settings: ${error.message || error}`)
1895+
await provider.postMessageToWebview({
1896+
type: "codeIndexSettingsSaved",
1897+
success: false,
1898+
error: error.message || "Failed to save settings",
1899+
})
18361900
}
1837-
1838-
await provider.postStateToWebview()
18391901
break
18401902
}
1903+
18411904
case "requestIndexingStatus": {
18421905
const status = provider.codeIndexManager!.getCurrentStatus()
18431906
provider.postMessageToWebview({
@@ -1846,6 +1909,26 @@ export const webviewMessageHandler = async (
18461909
})
18471910
break
18481911
}
1912+
case "requestCodeIndexSecretStatus": {
1913+
// Check if secrets are set using the VSCode context directly for async access
1914+
const hasOpenAiKey = !!(await provider.context.secrets.get("codeIndexOpenAiKey"))
1915+
const hasQdrantApiKey = !!(await provider.context.secrets.get("codeIndexQdrantApiKey"))
1916+
const hasOpenAiCompatibleApiKey = !!(await provider.context.secrets.get(
1917+
"codebaseIndexOpenAiCompatibleApiKey",
1918+
))
1919+
const hasGeminiApiKey = !!(await provider.context.secrets.get("codebaseIndexGeminiApiKey"))
1920+
1921+
provider.postMessageToWebview({
1922+
type: "codeIndexSecretStatus",
1923+
values: {
1924+
hasOpenAiKey,
1925+
hasQdrantApiKey,
1926+
hasOpenAiCompatibleApiKey,
1927+
hasGeminiApiKey,
1928+
},
1929+
})
1930+
break
1931+
}
18491932
case "startIndexing": {
18501933
try {
18511934
const manager = provider.codeIndexManager!

src/integrations/editor/DiffViewProvider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class DiffViewProvider {
122122
}
123123

124124
// Place cursor at the beginning of the diff editor to keep it out of
125-
// the way of the stream animation.
125+
// the way of the stream animation, but do this without stealing focus
126126
const beginningOfDocument = new vscode.Position(0, 0)
127127
diffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
128128

@@ -137,7 +137,7 @@ export class DiffViewProvider {
137137
// Update decorations.
138138
this.activeLineController.setActiveLine(endLine)
139139
this.fadedOverlayController.updateOverlayAfterLine(endLine, document.lineCount)
140-
// Scroll to the current line.
140+
// Scroll to the current line without stealing focus.
141141
const ranges = this.activeDiffEditor?.visibleRanges
142142
if (ranges && ranges.length > 0 && ranges[0].start.line < endLine && ranges[0].end.line > endLine) {
143143
this.scrollEditorToLine(endLine)
@@ -504,7 +504,7 @@ export class DiffViewProvider {
504504
// Pre-open the file as a text document to ensure it doesn't open in preview mode
505505
// This fixes issues with files that have custom editor associations (like markdown preview)
506506
vscode.window
507-
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active })
507+
.showTextDocument(uri, { preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
508508
.then(() => {
509509
// Execute the diff command after ensuring the file is open as text
510510
return vscode.commands.executeCommand(
@@ -552,7 +552,7 @@ export class DiffViewProvider {
552552

553553
for (const part of diffs) {
554554
if (part.added || part.removed) {
555-
// Found the first diff, scroll to it.
555+
// Found the first diff, scroll to it without stealing focus.
556556
this.activeDiffEditor.revealRange(
557557
new vscode.Range(lineCount, 0, lineCount, 0),
558558
vscode.TextEditorRevealType.InCenter,

src/integrations/editor/__tests__/DiffViewProvider.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe("DiffViewProvider", () => {
176176
// Mock showTextDocument to track when it's called
177177
vi.mocked(vscode.window.showTextDocument).mockImplementation(async (uri, options) => {
178178
callOrder.push("showTextDocument")
179-
expect(options).toEqual({ preview: false, viewColumn: vscode.ViewColumn.Active })
179+
expect(options).toEqual({ preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true })
180180
return mockEditor as any
181181
})
182182

@@ -208,10 +208,10 @@ describe("DiffViewProvider", () => {
208208
// Verify that showTextDocument was called before executeCommand
209209
expect(callOrder).toEqual(["showTextDocument", "executeCommand"])
210210

211-
// Verify that showTextDocument was called with preview: false
211+
// Verify that showTextDocument was called with preview: false and preserveFocus: true
212212
expect(vscode.window.showTextDocument).toHaveBeenCalledWith(
213213
expect.objectContaining({ fsPath: `${mockCwd}/test.md` }),
214-
{ preview: false, viewColumn: vscode.ViewColumn.Active },
214+
{ preview: false, viewColumn: vscode.ViewColumn.Active, preserveFocus: true },
215215
)
216216

217217
// Verify that the diff command was executed

0 commit comments

Comments
 (0)