Skip to content

Commit 81eabcd

Browse files
committed
Add config to control public sharing
1 parent 5929e26 commit 81eabcd

File tree

12 files changed

+130
-11
lines changed

12 files changed

+130
-11
lines changed

packages/cloud/src/CloudService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,11 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
337337
return this.shareService!.canShareTask()
338338
}
339339

340+
public async canSharePublicly(): Promise<boolean> {
341+
this.ensureInitialized()
342+
return this.shareService!.canSharePublicly()
343+
}
344+
340345
// Lifecycle
341346

342347
public dispose(): void {

packages/cloud/src/CloudShareService.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,15 @@ export class CloudShareService {
4747
return false
4848
}
4949
}
50+
51+
async canSharePublicly(): Promise<boolean> {
52+
try {
53+
const cloudSettings = this.settingsService.getSettings()?.cloudSettings
54+
// Public sharing requires both enableTaskSharing AND allowPublicTaskSharing to be true
55+
return !!cloudSettings?.enableTaskSharing && cloudSettings?.allowPublicTaskSharing !== false
56+
} catch (error) {
57+
this.log("[ShareService] Error checking if task can be shared publicly:", error)
58+
return false
59+
}
60+
}
5061
}

packages/types/src/__tests__/cloud.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,82 @@ describe("organizationSettingsSchema with features", () => {
175175
})
176176
})
177177

178+
describe("organizationCloudSettingsSchema with allowPublicTaskSharing", () => {
179+
it("should validate without allowPublicTaskSharing property", () => {
180+
const input = {
181+
recordTaskMessages: true,
182+
enableTaskSharing: true,
183+
}
184+
const result = organizationCloudSettingsSchema.safeParse(input)
185+
expect(result.success).toBe(true)
186+
expect(result.data?.allowPublicTaskSharing).toBeUndefined()
187+
})
188+
189+
it("should validate with allowPublicTaskSharing as true", () => {
190+
const input = {
191+
recordTaskMessages: true,
192+
enableTaskSharing: true,
193+
allowPublicTaskSharing: true,
194+
}
195+
const result = organizationCloudSettingsSchema.safeParse(input)
196+
expect(result.success).toBe(true)
197+
expect(result.data?.allowPublicTaskSharing).toBe(true)
198+
})
199+
200+
it("should validate with allowPublicTaskSharing as false", () => {
201+
const input = {
202+
recordTaskMessages: true,
203+
enableTaskSharing: true,
204+
allowPublicTaskSharing: false,
205+
}
206+
const result = organizationCloudSettingsSchema.safeParse(input)
207+
expect(result.success).toBe(true)
208+
expect(result.data?.allowPublicTaskSharing).toBe(false)
209+
})
210+
211+
it("should reject non-boolean allowPublicTaskSharing", () => {
212+
const input = {
213+
allowPublicTaskSharing: "true",
214+
}
215+
const result = organizationCloudSettingsSchema.safeParse(input)
216+
expect(result.success).toBe(false)
217+
})
218+
219+
it("should have correct TypeScript type", () => {
220+
// Type-only test to ensure TypeScript compilation
221+
const settings: OrganizationCloudSettings = {
222+
recordTaskMessages: true,
223+
enableTaskSharing: true,
224+
allowPublicTaskSharing: true,
225+
}
226+
expect(settings.allowPublicTaskSharing).toBe(true)
227+
228+
const settingsWithoutPublicSharing: OrganizationCloudSettings = {
229+
recordTaskMessages: false,
230+
}
231+
expect(settingsWithoutPublicSharing.allowPublicTaskSharing).toBeUndefined()
232+
})
233+
234+
it("should validate in organizationSettingsSchema with allowPublicTaskSharing", () => {
235+
const input = {
236+
version: 1,
237+
cloudSettings: {
238+
recordTaskMessages: true,
239+
enableTaskSharing: true,
240+
allowPublicTaskSharing: false,
241+
},
242+
defaultSettings: {},
243+
allowList: {
244+
allowAll: true,
245+
providers: {},
246+
},
247+
}
248+
const result = organizationSettingsSchema.safeParse(input)
249+
expect(result.success).toBe(true)
250+
expect(result.data?.cloudSettings?.allowPublicTaskSharing).toBe(false)
251+
})
252+
})
253+
178254
describe("organizationCloudSettingsSchema with workspaceTaskVisibility", () => {
179255
it("should validate without workspaceTaskVisibility property", () => {
180256
const input = {

packages/types/src/cloud.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export type WorkspaceTaskVisibility = z.infer<typeof workspaceTaskVisibilitySche
135135
export const organizationCloudSettingsSchema = z.object({
136136
recordTaskMessages: z.boolean().optional(),
137137
enableTaskSharing: z.boolean().optional(),
138+
allowPublicTaskSharing: z.boolean().optional(),
138139
taskShareExpirationDays: z.number().int().positive().optional(),
139140
allowMembersViewAllTasks: z.boolean().optional(),
140141
workspaceTaskVisibility: workspaceTaskVisibilitySchema.optional(),
@@ -209,6 +210,7 @@ export const ORGANIZATION_DEFAULT: OrganizationSettings = {
209210
cloudSettings: {
210211
recordTaskMessages: true,
211212
enableTaskSharing: true,
213+
allowPublicTaskSharing: true,
212214
taskShareExpirationDays: 30,
213215
allowMembersViewAllTasks: true,
214216
},

src/core/webview/ClineProvider.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,6 +1873,7 @@ export class ClineProvider
18731873
cloudUserInfo,
18741874
cloudIsAuthenticated,
18751875
sharingEnabled,
1876+
publicSharingEnabled,
18761877
organizationAllowList,
18771878
organizationSettingsVersion,
18781879
maxConcurrentFileReads,
@@ -2025,6 +2026,7 @@ export class ClineProvider
20252026
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
20262027
cloudOrganizations,
20272028
sharingEnabled: sharingEnabled ?? false,
2029+
publicSharingEnabled: publicSharingEnabled ?? false,
20282030
organizationAllowList,
20292031
organizationSettingsVersion,
20302032
condensingApiConfigId,
@@ -2140,6 +2142,16 @@ export class ClineProvider
21402142
)
21412143
}
21422144

2145+
let publicSharingEnabled: boolean = false
2146+
2147+
try {
2148+
publicSharingEnabled = await CloudService.instance.canSharePublicly()
2149+
} catch (error) {
2150+
console.error(
2151+
`[getState] failed to get public sharing enabled state: ${error instanceof Error ? error.message : String(error)}`,
2152+
)
2153+
}
2154+
21432155
let organizationSettingsVersion: number = -1
21442156

21452157
try {
@@ -2251,6 +2263,7 @@ export class ClineProvider
22512263
cloudUserInfo,
22522264
cloudIsAuthenticated,
22532265
sharingEnabled,
2266+
publicSharingEnabled,
22542267
organizationAllowList,
22552268
organizationSettingsVersion,
22562269
condensingApiConfigId: stateValues.condensingApiConfigId,

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ describe("ClineProvider", () => {
576576
autoCondenseContextPercent: 100,
577577
cloudIsAuthenticated: false,
578578
sharingEnabled: false,
579+
publicSharingEnabled: false,
579580
profileThresholds: {},
580581
hasOpenedModeSelector: false,
581582
diagnosticsEnabled: true,

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ export type ExtensionState = Pick<
334334
cloudApiUrl?: string
335335
cloudOrganizations?: CloudOrganizationMembership[]
336336
sharingEnabled: boolean
337+
publicSharingEnabled: boolean
337338
organizationAllowList: OrganizationAllowList
338339
organizationSettingsVersion?: number
339340

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
4141
handleConnect,
4242
isAuthenticated: cloudIsAuthenticated,
4343
sharingEnabled,
44+
publicSharingEnabled,
4445
} = useCloudUpsell({
4546
onAuthSuccess: () => {
4647
// Auto-open share dropdown after successful authentication
@@ -195,17 +196,21 @@ export const ShareButton = ({ item, disabled = false }: ShareButtonProps) => {
195196
</div>
196197
</CommandItem>
197198
)}
198-
<CommandItem onSelect={() => handleShare("public")} className="cursor-pointer">
199-
<div className="flex items-center gap-2">
200-
<span className="codicon codicon-globe text-sm"></span>
201-
<div className="flex flex-col">
202-
<span className="text-sm">{t("chat:task.sharePublicly")}</span>
203-
<span className="text-xs text-vscode-descriptionForeground">
204-
{t("chat:task.sharePubliclyDescription")}
205-
</span>
199+
{publicSharingEnabled && (
200+
<CommandItem
201+
onSelect={() => handleShare("public")}
202+
className="cursor-pointer">
203+
<div className="flex items-center gap-2">
204+
<span className="codicon codicon-globe text-sm"></span>
205+
<div className="flex flex-col">
206+
<span className="text-sm">{t("chat:task.sharePublicly")}</span>
207+
<span className="text-xs text-vscode-descriptionForeground">
208+
{t("chat:task.sharePubliclyDescription")}
209+
</span>
210+
</div>
206211
</div>
207-
</div>
208-
</CommandItem>
212+
</CommandItem>
213+
)}
209214
</CommandGroup>
210215
</CommandList>
211216
</Command>

webview-ui/src/components/chat/__tests__/ShareButton.spec.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ vi.mock("@/context/ExtensionStateContext", () => ({
2020
ExtensionStateContextProvider: ({ children }: { children: React.ReactNode }) => children,
2121
useExtensionState: () => ({
2222
sharingEnabled: true,
23+
publicSharingEnabled: true,
2324
cloudIsAuthenticated: true,
2425
cloudUserInfo: {
2526
id: "test-user",

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface ExtensionStateContextType extends ExtensionState {
4343
cloudIsAuthenticated: boolean
4444
cloudOrganizations?: CloudOrganizationMembership[]
4545
sharingEnabled: boolean
46+
publicSharingEnabled: boolean
4647
maxConcurrentFileReads?: number
4748
mdmCompliant?: boolean
4849
hasOpenedModeSelector: boolean // New property to track if user has opened mode selector
@@ -250,6 +251,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
250251
cloudIsAuthenticated: false,
251252
cloudOrganizations: [],
252253
sharingEnabled: false,
254+
publicSharingEnabled: false,
253255
organizationAllowList: ORGANIZATION_ALLOW_ALL,
254256
organizationSettingsVersion: -1,
255257
autoCondenseContext: true,

0 commit comments

Comments
 (0)