mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-07 02:58:15 +00:00
Add config to control public sharing (#10105)
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
bf81fa7237
commit
3502f41e75
13 changed files with 134 additions and 11 deletions
|
|
@ -337,6 +337,11 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
|
|||
return this.shareService!.canShareTask()
|
||||
}
|
||||
|
||||
public async canSharePublicly(): Promise<boolean> {
|
||||
this.ensureInitialized()
|
||||
return this.shareService!.canSharePublicly()
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
|
||||
public dispose(): void {
|
||||
|
|
|
|||
|
|
@ -47,4 +47,15 @@ export class CloudShareService {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async canSharePublicly(): Promise<boolean> {
|
||||
try {
|
||||
const cloudSettings = this.settingsService.getSettings()?.cloudSettings
|
||||
// Public sharing requires both enableTaskSharing AND allowPublicTaskSharing to be true
|
||||
return !!cloudSettings?.enableTaskSharing && cloudSettings?.allowPublicTaskSharing !== false
|
||||
} catch (error) {
|
||||
this.log("[ShareService] Error checking if task can be shared publicly:", error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,82 @@ describe("organizationSettingsSchema with features", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("organizationCloudSettingsSchema with allowPublicTaskSharing", () => {
|
||||
it("should validate without allowPublicTaskSharing property", () => {
|
||||
const input = {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
}
|
||||
const result = organizationCloudSettingsSchema.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data?.allowPublicTaskSharing).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should validate with allowPublicTaskSharing as true", () => {
|
||||
const input = {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
allowPublicTaskSharing: true,
|
||||
}
|
||||
const result = organizationCloudSettingsSchema.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data?.allowPublicTaskSharing).toBe(true)
|
||||
})
|
||||
|
||||
it("should validate with allowPublicTaskSharing as false", () => {
|
||||
const input = {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
allowPublicTaskSharing: false,
|
||||
}
|
||||
const result = organizationCloudSettingsSchema.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data?.allowPublicTaskSharing).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject non-boolean allowPublicTaskSharing", () => {
|
||||
const input = {
|
||||
allowPublicTaskSharing: "true",
|
||||
}
|
||||
const result = organizationCloudSettingsSchema.safeParse(input)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("should have correct TypeScript type", () => {
|
||||
// Type-only test to ensure TypeScript compilation
|
||||
const settings: OrganizationCloudSettings = {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
allowPublicTaskSharing: true,
|
||||
}
|
||||
expect(settings.allowPublicTaskSharing).toBe(true)
|
||||
|
||||
const settingsWithoutPublicSharing: OrganizationCloudSettings = {
|
||||
recordTaskMessages: false,
|
||||
}
|
||||
expect(settingsWithoutPublicSharing.allowPublicTaskSharing).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should validate in organizationSettingsSchema with allowPublicTaskSharing", () => {
|
||||
const input = {
|
||||
version: 1,
|
||||
cloudSettings: {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
allowPublicTaskSharing: false,
|
||||
},
|
||||
defaultSettings: {},
|
||||
allowList: {
|
||||
allowAll: true,
|
||||
providers: {},
|
||||
},
|
||||
}
|
||||
const result = organizationSettingsSchema.safeParse(input)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.data?.cloudSettings?.allowPublicTaskSharing).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("organizationCloudSettingsSchema with workspaceTaskVisibility", () => {
|
||||
it("should validate without workspaceTaskVisibility property", () => {
|
||||
const input = {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export type WorkspaceTaskVisibility = z.infer<typeof workspaceTaskVisibilitySche
|
|||
export const organizationCloudSettingsSchema = z.object({
|
||||
recordTaskMessages: z.boolean().optional(),
|
||||
enableTaskSharing: z.boolean().optional(),
|
||||
allowPublicTaskSharing: z.boolean().optional(),
|
||||
taskShareExpirationDays: z.number().int().positive().optional(),
|
||||
allowMembersViewAllTasks: z.boolean().optional(),
|
||||
workspaceTaskVisibility: workspaceTaskVisibilitySchema.optional(),
|
||||
|
|
@ -209,6 +210,7 @@ export const ORGANIZATION_DEFAULT: OrganizationSettings = {
|
|||
cloudSettings: {
|
||||
recordTaskMessages: true,
|
||||
enableTaskSharing: true,
|
||||
allowPublicTaskSharing: true,
|
||||
taskShareExpirationDays: 30,
|
||||
allowMembersViewAllTasks: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1873,6 +1873,7 @@ export class ClineProvider
|
|||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
publicSharingEnabled,
|
||||
organizationAllowList,
|
||||
organizationSettingsVersion,
|
||||
maxConcurrentFileReads,
|
||||
|
|
@ -2025,6 +2026,7 @@ export class ClineProvider
|
|||
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
|
||||
cloudOrganizations,
|
||||
sharingEnabled: sharingEnabled ?? false,
|
||||
publicSharingEnabled: publicSharingEnabled ?? false,
|
||||
organizationAllowList,
|
||||
organizationSettingsVersion,
|
||||
condensingApiConfigId,
|
||||
|
|
@ -2140,6 +2142,16 @@ export class ClineProvider
|
|||
)
|
||||
}
|
||||
|
||||
let publicSharingEnabled: boolean = false
|
||||
|
||||
try {
|
||||
publicSharingEnabled = await CloudService.instance.canSharePublicly()
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[getState] failed to get public sharing enabled state: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
let organizationSettingsVersion: number = -1
|
||||
|
||||
try {
|
||||
|
|
@ -2251,6 +2263,7 @@ export class ClineProvider
|
|||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
publicSharingEnabled,
|
||||
organizationAllowList,
|
||||
organizationSettingsVersion,
|
||||
condensingApiConfigId: stateValues.condensingApiConfigId,
|
||||
|
|
|
|||
|
|
@ -576,6 +576,7 @@ describe("ClineProvider", () => {
|
|||
autoCondenseContextPercent: 100,
|
||||
cloudIsAuthenticated: false,
|
||||
sharingEnabled: false,
|
||||
publicSharingEnabled: false,
|
||||
profileThresholds: {},
|
||||
hasOpenedModeSelector: false,
|
||||
diagnosticsEnabled: true,
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ export type ExtensionState = Pick<
|
|||
cloudApiUrl?: string
|
||||
cloudOrganizations?: CloudOrganizationMembership[]
|
||||
sharingEnabled: boolean
|
||||
publicSharingEnabled: boolean
|
||||
organizationAllowList: OrganizationAllowList
|
||||
organizationSettingsVersion?: number
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
|||
handleConnect,
|
||||
isAuthenticated: cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
publicSharingEnabled,
|
||||
} = useCloudUpsell({
|
||||
onAuthSuccess: () => {
|
||||
// Auto-open share dropdown after successful authentication
|
||||
|
|
@ -195,17 +196,21 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
|
|||
</div>
|
||||
</CommandItem>
|
||||
)}
|
||||
<CommandItem onSelect={() => handleShare("public")} className="cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="codicon codicon-globe text-sm"></span>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm">{t("chat:task.sharePublicly")}</span>
|
||||
<span className="text-xs text-vscode-descriptionForeground">
|
||||
{t("chat:task.sharePubliclyDescription")}
|
||||
</span>
|
||||
{publicSharingEnabled && (
|
||||
<CommandItem
|
||||
onSelect={() => handleShare("public")}
|
||||
className="cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="codicon codicon-globe text-sm"></span>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-sm">{t("chat:task.sharePublicly")}</span>
|
||||
<span className="text-xs text-vscode-descriptionForeground">
|
||||
{t("chat:task.sharePubliclyDescription")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandItem>
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ vi.mock("@/context/ExtensionStateContext", () => ({
|
|||
ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => children,
|
||||
useExtensionState: () => ({
|
||||
sharingEnabled: true,
|
||||
publicSharingEnabled: true,
|
||||
cloudIsAuthenticated: true,
|
||||
cloudUserInfo: {
|
||||
id: "test-user",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ describe("TaskActions", () => {
|
|||
vi.clearAllMocks()
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
sharingEnabled: true,
|
||||
publicSharingEnabled: true,
|
||||
cloudIsAuthenticated: true,
|
||||
cloudUserInfo: {
|
||||
organizationName: "Test Organization",
|
||||
|
|
@ -166,6 +167,7 @@ describe("TaskActions", () => {
|
|||
it("does not show organization option when user is not in an organization", () => {
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
sharingEnabled: true,
|
||||
publicSharingEnabled: true,
|
||||
cloudIsAuthenticated: true,
|
||||
cloudUserInfo: {
|
||||
// No organizationName property
|
||||
|
|
@ -264,6 +266,7 @@ describe("TaskActions", () => {
|
|||
// Simulate user becoming authenticated (e.g., from CloudView)
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
sharingEnabled: true,
|
||||
publicSharingEnabled: true,
|
||||
cloudIsAuthenticated: true,
|
||||
cloudUserInfo: {
|
||||
organizationName: "Test Organization",
|
||||
|
|
@ -302,6 +305,7 @@ describe("TaskActions", () => {
|
|||
// Simulate user becoming authenticated after clicking connect from share button
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
sharingEnabled: true,
|
||||
publicSharingEnabled: true,
|
||||
cloudIsAuthenticated: true,
|
||||
cloudUserInfo: {
|
||||
organizationName: "Test Organization",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
cloudIsAuthenticated: boolean
|
||||
cloudOrganizations?: CloudOrganizationMembership[]
|
||||
sharingEnabled: boolean
|
||||
publicSharingEnabled: boolean
|
||||
maxConcurrentFileReads?: number
|
||||
mdmCompliant?: boolean
|
||||
hasOpenedModeSelector: boolean // New property to track if user has opened mode selector
|
||||
|
|
@ -250,6 +251,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
cloudIsAuthenticated: false,
|
||||
cloudOrganizations: [],
|
||||
sharingEnabled: false,
|
||||
publicSharingEnabled: false,
|
||||
organizationAllowList: ORGANIZATION_ALLOW_ALL,
|
||||
organizationSettingsVersion: -1,
|
||||
autoCondenseContext: true,
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ describe("mergeExtensionState", () => {
|
|||
autoCondenseContextPercent: 100,
|
||||
cloudIsAuthenticated: false,
|
||||
sharingEnabled: false,
|
||||
publicSharingEnabled: false,
|
||||
profileThresholds: {},
|
||||
hasOpenedModeSelector: false, // Add the new required property
|
||||
maxImageFileSize: 5,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const useCloudUpsell = (options: UseCloudUpsellOptions = {}) => {
|
|||
const { onAuthSuccess, autoOpenOnAuth = false } = options
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [shouldOpenOnAuth, setShouldOpenOnAuth] = useState(false)
|
||||
const { cloudIsAuthenticated, sharingEnabled } = useExtensionState()
|
||||
const { cloudIsAuthenticated, sharingEnabled, publicSharingEnabled } = useExtensionState()
|
||||
const wasUnauthenticatedRef = useRef(false)
|
||||
const initiatedAuthRef = useRef(false)
|
||||
|
||||
|
|
@ -67,5 +67,6 @@ export const useCloudUpsell = (options: UseCloudUpsellOptions = {}) => {
|
|||
handleConnect,
|
||||
isAuthenticated: cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
publicSharingEnabled,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue