Skip to content

Commit 6a18155

Browse files
allquixoticoz-agent
andcommitted
feat: add 'Masquerade as Roo Code' setting (3.53.3)
Adds a checkbox under Settings \u2192 UI that dynamically flips the visible name and runtime-swappable icons back to upstream Roo Code branding. Default remains CRC. Implementation - New globalSettingsSchema field masqueradeAsRooCode: boolean (default false) - Webview + extension-host i18next 'brand' post-processor rewrites \bCRC\b \u2192 'Roo Code' when enabled; TranslationContext emits languageChanged so useTranslation and <Trans> consumers re-render - UI subcategory gains a SearchableSetting bound to cachedState (not live state) per the SettingsView pattern; SettingsView passes masqueradeAsRooCode and includes it in the updateSettings payload - Non-i18n hardcoded CRC strings moved to translations (Roo.tsx serviceName, Bedrock discovery description, ImageGenerationSettings dropdown, RooHero alt text); ApiOptions PROVIDERS label transformed via new brand helper - Logo swap: new src/assets/images/roo-code-logo.svg (upstream mark) used when the flag is on; panel-{light,dark}-roo.svg variants applied to the editor-tab WebviewPanel.iconPath; tab title + HTML <title> pick CRC or Roo Code based on the setting, with live refresh via refreshTabPanelBrandAssets - Tests: new vitest specs for brand post-processor (webview + host) and an expanded UISettings.spec covering the new checkbox; existing TranslationContext + CloudUpsellDialog mocks updated Scope limitation VS Code reads the activity-bar icon/title, marketplace display name, sidebar view name, and command-palette category prefix from src/package.nls.json at activation and cannot swap them at runtime; this is documented in the setting's own description. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent a74c32e commit 6a18155

28 files changed

Lines changed: 438 additions & 34 deletions

packages/types/src/global-settings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ export const globalSettingsSchema = z.object({
210210
*/
211211
enterBehavior: z.enum(["send", "newline"]).optional(),
212212
defaultRenderContext: z.enum(["sidebar", "editor"]).optional(),
213+
/**
214+
* When enabled, the extension displays "Roo Code" branding in all runtime-rendered UI
215+
* surfaces (webview translations, logos, webview panel title/icon) instead of "CRC".
216+
* Static VS Code manifest surfaces (activity bar icon, marketplace name, command palette
217+
* category prefix) are unaffected because they are read from package.nls.json at activation.
218+
* @default false
219+
*/
220+
masqueradeAsRooCode: z.boolean().optional(),
213221
profileThresholds: z.record(z.string(), z.number()).optional(),
214222
hasOpenedModeSelector: z.boolean().optional(),
215223
lastModeExportPath: z.string().optional(),

packages/types/src/vscode-extension-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export type ExtensionState = Pick<
320320
| "maxGitStatusFiles"
321321
| "autoImportSettingsOnStartup"
322322
| "defaultRenderContext"
323+
| "masqueradeAsRooCode"
323324
| "requestDelaySeconds"
324325
| "showWorktreesInHomeScreen"
325326
| "disabledTools"

src/activate/registerCommands.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,49 @@ export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): Cl
3131
let sidebarPanel: vscode.WebviewView | undefined = undefined
3232
let tabPanel: vscode.WebviewPanel | undefined = undefined
3333

34+
/**
35+
* Returns the title string that should be used for the editor-tab webview
36+
* panel, honoring the "Masquerade as Roo Code" setting.
37+
*/
38+
function getTabPanelTitle(contextProxy: ContextProxy): string {
39+
const masquerade = contextProxy.getValue("masqueradeAsRooCode") ?? false
40+
return masquerade ? "Roo Code" : "CRC"
41+
}
42+
43+
/**
44+
* Returns the `WebviewPanel.iconPath` that should be used for the editor-tab
45+
* webview panel, honoring the "Masquerade as Roo Code" setting.
46+
*/
47+
function getTabPanelIconPath(
48+
extensionUri: vscode.Uri,
49+
contextProxy: ContextProxy,
50+
): { light: vscode.Uri; dark: vscode.Uri } {
51+
const masquerade = contextProxy.getValue("masqueradeAsRooCode") ?? false
52+
const lightIcon = masquerade ? "panel-light-roo.svg" : "panel-light.svg"
53+
const darkIcon = masquerade ? "panel-dark-roo.svg" : "panel-dark.svg"
54+
return {
55+
light: vscode.Uri.joinPath(extensionUri, "assets", "icons", lightIcon),
56+
dark: vscode.Uri.joinPath(extensionUri, "assets", "icons", darkIcon),
57+
}
58+
}
59+
60+
/**
61+
* Refresh the editor-tab panel title + icon to match the current value of the
62+
* "Masquerade as Roo Code" setting. Safe to call any number of times and a
63+
* no-op when no tab panel is currently open.
64+
*/
65+
export function refreshTabPanelBrandAssets(extensionUri: vscode.Uri, contextProxy: ContextProxy): void {
66+
if (!tabPanel) {
67+
return
68+
}
69+
try {
70+
tabPanel.title = getTabPanelTitle(contextProxy)
71+
tabPanel.iconPath = getTabPanelIconPath(extensionUri, contextProxy)
72+
} catch {
73+
// Panel may have just been disposed; ignore.
74+
}
75+
}
76+
3477
/**
3578
* Get the currently active panel
3679
* @returns WebviewPanel或WebviewView
@@ -218,19 +261,21 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
218261

219262
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
220263

221-
const newPanel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "CRC", targetCol, {
222-
enableScripts: true,
223-
retainContextWhenHidden: true,
224-
localResourceRoots: [context.extensionUri],
225-
})
264+
const newPanel = vscode.window.createWebviewPanel(
265+
ClineProvider.tabPanelId,
266+
getTabPanelTitle(contextProxy),
267+
targetCol,
268+
{
269+
enableScripts: true,
270+
retainContextWhenHidden: true,
271+
localResourceRoots: [context.extensionUri],
272+
},
273+
)
226274

227275
// Save as tab type panel.
228276
setPanel(newPanel, "tab")
229277

230-
newPanel.iconPath = {
231-
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel-light.svg"),
232-
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "panel-dark.svg"),
233-
}
278+
newPanel.iconPath = getTabPanelIconPath(context.extensionUri, contextProxy)
234279

235280
await tabProvider.resolveWebviewView(newPanel)
236281

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

src/core/webview/ClineProvider.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ import {
8282
shouldRedirectSidebarToEditor,
8383
} from "../../utils/renderContext"
8484

85-
import { setPanel } from "../../activate/registerCommands"
85+
import { setPanel, refreshTabPanelBrandAssets } from "../../activate/registerCommands"
8686

87-
import { t } from "../../i18n"
87+
import { t, setMasqueradeMode as setHostMasqueradeMode } from "../../i18n"
8888

8989
import { buildApiHandler } from "../../api"
9090
import { forceFullModelDetailsLoad, hasLoadedFullDetails } from "../../api/providers/fetchers/lmstudio"
@@ -1485,6 +1485,7 @@ export class ClineProvider
14851485

14861486
const file = "src/index.tsx"
14871487
const scriptUri = `http://${localServerUrl}/${file}`
1488+
const docTitle = (this.contextProxy.getValue("masqueradeAsRooCode") ?? false) ? "Roo Code" : "CRC"
14881489

14891490
const reactRefresh = /*html*/ `
14901491
<script nonce="${nonce}" type="module">
@@ -1520,7 +1521,7 @@ export class ClineProvider
15201521
window.AUDIO_BASE_URI = "${audioUri}"
15211522
window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
15221523
</script>
1523-
<title>CRC</title>
1524+
<title>${docTitle}</title>
15241525
</head>
15251526
<body>
15261527
<div id="root"></div>
@@ -1563,6 +1564,7 @@ export class ClineProvider
15631564
])
15641565
const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
15651566
const audioUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "audio"])
1567+
const docTitle = (this.contextProxy.getValue("masqueradeAsRooCode") ?? false) ? "Roo Code" : "CRC"
15661568

15671569
// Use a nonce to only allow a specific script to be run.
15681570
/*
@@ -1599,7 +1601,7 @@ export class ClineProvider
15991601
window.AUDIO_BASE_URI = "${audioUri}"
16001602
window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
16011603
</script>
1602-
<title>CRC</title>
1604+
<title>${docTitle}</title>
16031605
</head>
16041606
<body>
16051607
<noscript>You need to enable JavaScript to run this app.</noscript>
@@ -2305,6 +2307,12 @@ export class ClineProvider
23052307
const taskStateSeq = ++this.clineMessagesSeq
23062308
const state = await this.getStateToPostToWebview()
23072309
state.clineMessagesSeq = taskStateSeq
2310+
// Keep the extension-host i18n post-processor in sync with the user's
2311+
// "Masquerade as Roo Code" setting so that any t(...) calls executed in
2312+
// the host (VS Code notifications, error toasts, etc.) render with the
2313+
// user's chosen branding.
2314+
setHostMasqueradeMode(state.masqueradeAsRooCode ?? false)
2315+
refreshTabPanelBrandAssets(this.context.extensionUri, this.contextProxy)
23082316
this.postMessageToWebview({ type: "state", state })
23092317

23102318
// Check MDM compliance and send user to account tab if not compliant
@@ -2545,6 +2553,7 @@ export class ClineProvider
25452553
openRouterImageApiKey,
25462554
openRouterImageGenerationSelectedModel,
25472555
defaultRenderContext,
2556+
masqueradeAsRooCode,
25482557
lockApiConfigAcrossModes,
25492558
} = await this.getState()
25502559

@@ -2675,6 +2684,7 @@ export class ClineProvider
26752684
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
26762685
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
26772686
enterBehavior: enterBehavior ?? "send",
2687+
masqueradeAsRooCode: masqueradeAsRooCode ?? false,
26782688
cloudUserInfo,
26792689
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
26802690
cloudAuthSkipModel: this.context.globalState.get<boolean>("roo-auth-skip-model") ?? false,
@@ -2896,6 +2906,7 @@ export class ClineProvider
28962906
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
28972907
enterBehavior: stateValues.enterBehavior ?? "send",
28982908
defaultRenderContext: stateValues.defaultRenderContext ?? "editor",
2909+
masqueradeAsRooCode: stateValues.masqueradeAsRooCode ?? false,
28992910
cloudUserInfo,
29002911
cloudIsAuthenticated,
29012912
sharingEnabled,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, it, expect, beforeEach } from "vitest"
2+
3+
import i18next, { setMasqueradeMode, applyBrandMasquerade } from "../setup"
4+
5+
describe("extension-host brand masquerade", () => {
6+
beforeEach(() => {
7+
setMasqueradeMode(false)
8+
i18next.addResourceBundle(
9+
"en",
10+
"brandTest",
11+
{
12+
auth: "Sign in to CRC Cloud to continue",
13+
storage: "Default path: D:\\CRCStorage",
14+
},
15+
true,
16+
true,
17+
)
18+
})
19+
20+
it("pure transform leaves input unchanged when flag is off", () => {
21+
expect(applyBrandMasquerade("Welcome to CRC!", false)).toBe("Welcome to CRC!")
22+
})
23+
24+
it("pure transform swaps standalone CRC tokens", () => {
25+
expect(applyBrandMasquerade("Welcome to CRC!", true)).toBe("Welcome to Roo Code!")
26+
})
27+
28+
it("i18next output is branded when masquerade is on", () => {
29+
setMasqueradeMode(true)
30+
expect(i18next.t("brandTest:auth")).toBe("Sign in to Roo Code Cloud to continue")
31+
})
32+
33+
it("i18next output preserves embedded CRC occurrences", () => {
34+
setMasqueradeMode(true)
35+
expect(i18next.t("brandTest:storage")).toBe("Default path: D:\\CRCStorage")
36+
})
37+
38+
it("toggling off restores the original translation", () => {
39+
setMasqueradeMode(true)
40+
setMasqueradeMode(false)
41+
expect(i18next.t("brandTest:auth")).toBe("Sign in to CRC Cloud to continue")
42+
})
43+
})

src/i18n/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import i18next from "./setup"
22

3+
export { setMasqueradeMode, applyBrandMasquerade } from "./setup"
4+
35
/**
46
* Initialize i18next with the specified language
57
*

src/i18n/setup.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,35 @@
11
import i18next from "i18next"
22

3+
/**
4+
* Runtime flag for the "Masquerade as Roo Code" setting. The extension host
5+
* updates this flag whenever the setting is read from the context proxy so
6+
* that VS Code notifications / error messages emitted via `t(...)` reflect the
7+
* current branding without requiring a reload.
8+
*/
9+
let masqueradeMode = false
10+
11+
export function setMasqueradeMode(enabled: boolean): void {
12+
masqueradeMode = enabled
13+
}
14+
15+
export function applyBrandMasquerade(value: string, enabled: boolean = masqueradeMode): string {
16+
if (!enabled || typeof value !== "string" || value.length === 0) {
17+
return value
18+
}
19+
return value.replace(/\bCRC\b/g, "Roo Code")
20+
}
21+
22+
i18next.use({
23+
type: "postProcessor",
24+
name: "brand",
25+
process: (value: unknown) => {
26+
if (typeof value !== "string") {
27+
return value as any
28+
}
29+
return applyBrandMasquerade(value)
30+
},
31+
} as any)
32+
333
// Build translations object
434
const translations: Record<string, Record<string, any>> = {}
535

@@ -77,6 +107,7 @@ i18next.init({
77107
interpolation: {
78108
escapeValue: false,
79109
},
110+
postProcess: ["brand"],
80111
})
81112

82113
export default i18next

0 commit comments

Comments
 (0)