Add a share button when logged into cloud (#4448)

* Add a share button when logged into cloud

* Only show share button if enabled

* PR feedback
This commit is contained in:
Matt Rubens 2025-06-08 07:08:41 -07:00 committed by GitHub
parent 73e4418c12
commit 540d4fb94f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 513 additions and 50 deletions

View file

@ -9,6 +9,7 @@ import type { CloudUserInfo } from "@roo-code/types"
import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config"
import { RefreshTimer } from "./RefreshTimer"
import { getUserAgent } from "./utils"
export interface AuthServiceEvents {
"inactive-session": [data: { previousState: AuthState }]
@ -435,7 +436,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
}
private userAgent(): string {
return `Roo-Code ${this.context.extension?.packageJSON?.version}`
return getUserAgent(this.context)
}
private static _instance: AuthService | null = null

View file

@ -7,6 +7,7 @@ import { CloudServiceCallbacks } from "./types"
import { AuthService } from "./AuthService"
import { SettingsService } from "./SettingsService"
import { TelemetryClient } from "./TelemetryClient"
import { ShareService } from "./ShareService"
export class CloudService {
private static _instance: CloudService | null = null
@ -17,6 +18,7 @@ export class CloudService {
private authService: AuthService | null = null
private settingsService: SettingsService | null = null
private telemetryClient: TelemetryClient | null = null
private shareService: ShareService | null = null
private isInitialized = false
private log: (...args: unknown[]) => void
@ -48,6 +50,8 @@ export class CloudService {
this.telemetryClient = new TelemetryClient(this.authService, this.settingsService)
this.shareService = new ShareService(this.authService, this.settingsService, this.log)
try {
TelemetryService.instance.register(this.telemetryClient)
} catch (error) {
@ -112,6 +116,18 @@ export class CloudService {
this.telemetryClient!.capture(event)
}
// ShareService
public async shareTask(taskId: string): Promise<boolean> {
this.ensureInitialized()
return this.shareService!.shareTask(taskId)
}
public async canShareTask(): Promise<boolean> {
this.ensureInitialized()
return this.shareService!.canShareTask()
}
// Lifecycle
public dispose(): void {
@ -128,7 +144,13 @@ export class CloudService {
}
private ensureInitialized(): void {
if (!this.isInitialized || !this.authService || !this.settingsService || !this.telemetryClient) {
if (
!this.isInitialized ||
!this.authService ||
!this.settingsService ||
!this.telemetryClient ||
!this.shareService
) {
throw new Error("CloudService not initialized.")
}
}

View file

@ -0,0 +1,76 @@
import axios from "axios"
import * as vscode from "vscode"
import { shareResponseSchema } from "@roo-code/types"
import { getRooCodeApiUrl } from "./Config"
import type { AuthService } from "./AuthService"
import type { SettingsService } from "./SettingsService"
import { getUserAgent } from "./utils"
export class ShareService {
private authService: AuthService
private settingsService: SettingsService
private log: (...args: unknown[]) => void
constructor(authService: AuthService, settingsService: SettingsService, log?: (...args: unknown[]) => void) {
this.authService = authService
this.settingsService = settingsService
this.log = log || console.log
}
/**
* Share a task: Create link and copy to clipboard
* Returns true if successful, false if failed
*/
async shareTask(taskId: string): Promise<boolean> {
try {
const sessionToken = this.authService.getSessionToken()
if (!sessionToken) {
return false
}
const response = await axios.post(
`${getRooCodeApiUrl()}/api/extension/share`,
{ taskId },
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${sessionToken}`,
"User-Agent": getUserAgent(),
},
},
)
const data = shareResponseSchema.parse(response.data)
this.log("[share] Share link created successfully:", data)
if (data.success && data.shareUrl) {
// Copy to clipboard
await vscode.env.clipboard.writeText(data.shareUrl)
return true
} else {
this.log("[share] Share failed:", data.error)
return false
}
} catch (error) {
this.log("[share] Error sharing task:", error)
return false
}
}
/**
* Check if sharing is available
*/
async canShareTask(): Promise<boolean> {
try {
if (!this.authService.isAuthenticated()) {
return false
}
return !!this.settingsService.getSettings()?.cloudSettings?.enableTaskSharing
} catch (error) {
this.log("[share] Error checking if task can be shared:", error)
return false
}
}
}

View file

@ -0,0 +1,224 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, beforeEach, vi, type MockedFunction } from "vitest"
import axios from "axios"
import * as vscode from "vscode"
import { ShareService } from "../ShareService"
import type { AuthService } from "../AuthService"
import type { SettingsService } from "../SettingsService"
// Mock axios
vi.mock("axios")
const mockedAxios = axios as any
// Mock vscode
vi.mock("vscode", () => ({
window: {
showInformationMessage: vi.fn(),
showErrorMessage: vi.fn(),
},
env: {
clipboard: {
writeText: vi.fn(),
},
openExternal: vi.fn(),
},
Uri: {
parse: vi.fn(),
},
extensions: {
getExtension: vi.fn(() => ({
packageJSON: { version: "1.0.0" },
})),
},
}))
// Mock config
vi.mock("../Config", () => ({
getRooCodeApiUrl: () => "https://app.roocode.com",
}))
// Mock utils
vi.mock("../utils", () => ({
getUserAgent: () => "Roo-Code 1.0.0",
}))
describe("ShareService", () => {
let shareService: ShareService
let mockAuthService: AuthService
let mockSettingsService: SettingsService
let mockLog: MockedFunction<(...args: unknown[]) => void>
beforeEach(() => {
vi.clearAllMocks()
mockLog = vi.fn()
mockAuthService = {
hasActiveSession: vi.fn(),
getSessionToken: vi.fn(),
isAuthenticated: vi.fn(),
} as any
mockSettingsService = {
getSettings: vi.fn(),
} as any
shareService = new ShareService(mockAuthService, mockSettingsService, mockLog)
})
describe("shareTask", () => {
it("should share task and copy to clipboard", async () => {
const mockResponse = {
data: {
success: true,
shareUrl: "https://app.roocode.com/share/abc123",
},
}
;(mockAuthService.hasActiveSession as any).mockReturnValue(true)
;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
mockedAxios.post.mockResolvedValue(mockResponse)
const result = await shareService.shareTask("task-123")
expect(result).toBe(true)
expect(mockedAxios.post).toHaveBeenCalledWith(
"https://app.roocode.com/api/extension/share",
{ taskId: "task-123" },
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer session-token",
"User-Agent": "Roo-Code 1.0.0",
},
},
)
expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith("https://app.roocode.com/share/abc123")
})
it("should handle API error response", async () => {
const mockResponse = {
data: {
success: false,
error: "Task not found",
},
}
;(mockAuthService.hasActiveSession as any).mockReturnValue(true)
;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
mockedAxios.post.mockResolvedValue(mockResponse)
const result = await shareService.shareTask("task-123")
expect(result).toBe(false)
})
it("should handle authentication errors", async () => {
;(mockAuthService.hasActiveSession as any).mockReturnValue(false)
const result = await shareService.shareTask("task-123")
expect(result).toBe(false)
expect(mockedAxios.post).not.toHaveBeenCalled()
})
it("should handle 403 error for disabled sharing", async () => {
;(mockAuthService.hasActiveSession as any).mockReturnValue(true)
;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
const error = {
isAxiosError: true,
response: {
status: 403,
data: {
error: "Task sharing is not enabled for this organization",
},
},
}
mockedAxios.isAxiosError.mockReturnValue(true)
mockedAxios.post.mockRejectedValue(error)
const result = await shareService.shareTask("task-123")
expect(result).toBe(false)
})
it("should handle unexpected errors", async () => {
;(mockAuthService.hasActiveSession as any).mockReturnValue(true)
;(mockAuthService.getSessionToken as any).mockReturnValue("session-token")
mockedAxios.post.mockRejectedValue(new Error("Network error"))
const result = await shareService.shareTask("task-123")
expect(result).toBe(false)
})
})
describe("canShareTask", () => {
it("should return true when authenticated and sharing is enabled", async () => {
;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
;(mockSettingsService.getSettings as any).mockReturnValue({
cloudSettings: {
enableTaskSharing: true,
},
})
const result = await shareService.canShareTask()
expect(result).toBe(true)
})
it("should return false when authenticated but sharing is disabled", async () => {
;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
;(mockSettingsService.getSettings as any).mockReturnValue({
cloudSettings: {
enableTaskSharing: false,
},
})
const result = await shareService.canShareTask()
expect(result).toBe(false)
})
it("should return false when authenticated and sharing setting is undefined (default)", async () => {
;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
;(mockSettingsService.getSettings as any).mockReturnValue({
cloudSettings: {},
})
const result = await shareService.canShareTask()
expect(result).toBe(false)
})
it("should return false when authenticated and no settings available (default)", async () => {
;(mockAuthService.isAuthenticated as any).mockReturnValue(true)
;(mockSettingsService.getSettings as any).mockReturnValue(undefined)
const result = await shareService.canShareTask()
expect(result).toBe(false)
})
it("should return false when not authenticated", async () => {
;(mockAuthService.isAuthenticated as any).mockReturnValue(false)
const result = await shareService.canShareTask()
expect(result).toBe(false)
})
it("should handle errors gracefully", async () => {
;(mockAuthService.isAuthenticated as any).mockImplementation(() => {
throw new Error("Auth error")
})
const result = await shareService.canShareTask()
expect(result).toBe(false)
})
})
})

View file

@ -0,0 +1,10 @@
import * as vscode from "vscode"
/**
* Get the User-Agent string for API requests
* @param context Optional extension context for more accurate version detection
* @returns User-Agent string in format "Roo-Code {version}"
*/
export function getUserAgent(context?: vscode.ExtensionContext): string {
return `Roo-Code ${context?.extension?.packageJSON?.version || "unknown"}`
}

View file

@ -104,3 +104,15 @@ export const ORGANIZATION_DEFAULT: OrganizationSettings = {
defaultSettings: {},
allowList: ORGANIZATION_ALLOW_ALL,
} as const
/**
* Share Types
*/
export const shareResponseSchema = z.object({
success: z.boolean(),
shareUrl: z.string().optional(),
error: z.string().optional(),
})
export type ShareResponse = z.infer<typeof shareResponseSchema>

View file

@ -1306,6 +1306,7 @@ export class ClineProvider
historyPreviewCollapsed,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
organizationAllowList,
maxConcurrentFileReads,
condensingApiConfigId,
@ -1405,6 +1406,7 @@ export class ClineProvider
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
sharingEnabled: sharingEnabled ?? false,
organizationAllowList,
condensingApiConfigId,
customCondensingPrompt,
@ -1470,6 +1472,16 @@ export class ClineProvider
)
}
let sharingEnabled: boolean = false
try {
sharingEnabled = await CloudService.instance.canShareTask()
} catch (error) {
console.error(
`[getState] failed to get sharing enabled state: ${error instanceof Error ? error.message : String(error)}`,
)
}
// Return the same structure as before
return {
apiConfiguration: providerSettings,
@ -1546,6 +1558,7 @@ export class ClineProvider
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
organizationAllowList,
// Explicitly add condensing settings
condensingApiConfigId: stateValues.condensingApiConfigId,

View file

@ -428,6 +428,7 @@ describe("ClineProvider", () => {
autoCondenseContext: true,
autoCondenseContextPercent: 100,
cloudIsAuthenticated: false,
sharingEnabled: false,
}
const message: ExtensionMessage = {

View file

@ -207,6 +207,27 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
provider.exportTaskWithId(currentTaskId)
}
break
case "shareCurrentTask":
const shareTaskId = provider.getCurrentCline()?.taskId
if (!shareTaskId) {
vscode.window.showErrorMessage(t("common:errors.share_no_active_task"))
break
}
try {
const success = await CloudService.instance.shareTask(shareTaskId)
if (success) {
// Show success message
vscode.window.showInformationMessage(t("common:info.share_link_copied"))
} else {
// Show generic failure message
vscode.window.showErrorMessage(t("common:errors.share_task_failed"))
}
} catch (error) {
// Show generic failure message
vscode.window.showErrorMessage(t("common:errors.share_task_failed"))
}
break
case "showTaskWithId":
provider.showTaskWithId(message.text!)
break

View file

@ -63,7 +63,9 @@
"condense_not_enough_messages": "No hi ha prou missatges per condensar el context",
"condensed_recently": "El context s'ha condensat recentment; s'omet aquest intent",
"condense_handler_invalid": "El gestor de l'API per condensar el context no és vàlid",
"condense_context_grew": "La mida del context ha augmentat durant la condensació; s'omet aquest intent"
"condense_context_grew": "La mida del context ha augmentat durant la condensació; s'omet aquest intent",
"share_task_failed": "Ha fallat compartir la tasca. Si us plau, torna-ho a provar.",
"share_no_active_task": "No hi ha cap tasca activa per compartir"
},
"warnings": {
"no_terminal_content": "No s'ha seleccionat contingut de terminal",
@ -79,7 +81,8 @@
"mcp_server_not_found": "Servidor \"{{serverName}}\" no trobat a la configuració",
"custom_storage_path_set": "Ruta d'emmagatzematge personalitzada establerta: {{path}}",
"default_storage_path": "S'ha reprès l'ús de la ruta d'emmagatzematge predeterminada",
"settings_imported": "Configuració importada correctament."
"settings_imported": "Configuració importada correctament.",
"share_link_copied": "Enllaç de compartició copiat al portapapers"
},
"answers": {
"yes": "Sí",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Nicht genügend Nachrichten zum Verdichten des Kontexts",
"condensed_recently": "Kontext wurde kürzlich verdichtet; dieser Versuch wird übersprungen",
"condense_handler_invalid": "API-Handler zum Verdichten des Kontexts ist ungültig",
"condense_context_grew": "Kontextgröße ist während der Verdichtung gewachsen; dieser Versuch wird übersprungen"
"condense_context_grew": "Kontextgröße ist während der Verdichtung gewachsen; dieser Versuch wird übersprungen",
"share_task_failed": "Teilen der Aufgabe fehlgeschlagen. Bitte versuche es erneut.",
"share_no_active_task": "Keine aktive Aufgabe zum Teilen"
},
"warnings": {
"no_terminal_content": "Kein Terminal-Inhalt ausgewählt",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Server \"{{serverName}}\" nicht in der Konfiguration gefunden",
"custom_storage_path_set": "Benutzerdefinierter Speicherpfad festgelegt: {{path}}",
"default_storage_path": "Auf Standardspeicherpfad zurückgesetzt",
"settings_imported": "Einstellungen erfolgreich importiert."
"settings_imported": "Einstellungen erfolgreich importiert.",
"share_link_copied": "Share-Link in die Zwischenablage kopiert"
},
"answers": {
"yes": "Ja",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Not enough messages to condense context",
"condensed_recently": "Context was condensed recently; skipping this attempt",
"condense_handler_invalid": "API handler for condensing context is invalid",
"condense_context_grew": "Context size increased during condensing; skipping this attempt"
"condense_context_grew": "Context size increased during condensing; skipping this attempt",
"share_task_failed": "Failed to share task. Please try again.",
"share_no_active_task": "No active task to share"
},
"warnings": {
"no_terminal_content": "No terminal content selected",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Server \"{{serverName}}\" not found in configuration",
"custom_storage_path_set": "Custom storage path set: {{path}}",
"default_storage_path": "Reverted to using default storage path",
"settings_imported": "Settings imported successfully."
"settings_imported": "Settings imported successfully.",
"share_link_copied": "Share link copied to clipboard"
},
"answers": {
"yes": "Yes",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "No hay suficientes mensajes para condensar el contexto",
"condensed_recently": "El contexto se condensó recientemente; se omite este intento",
"condense_handler_invalid": "El manejador de API para condensar el contexto no es válido",
"condense_context_grew": "El tamaño del contexto aumentó durante la condensación; se omite este intento"
"condense_context_grew": "El tamaño del contexto aumentó durante la condensación; se omite este intento",
"share_task_failed": "Error al compartir la tarea. Por favor, inténtalo de nuevo.",
"share_no_active_task": "No hay tarea activa para compartir"
},
"warnings": {
"no_terminal_content": "No hay contenido de terminal seleccionado",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Servidor \"{{serverName}}\" no encontrado en la configuración",
"custom_storage_path_set": "Ruta de almacenamiento personalizada establecida: {{path}}",
"default_storage_path": "Se ha vuelto a usar la ruta de almacenamiento predeterminada",
"settings_imported": "Configuración importada correctamente."
"settings_imported": "Configuración importada correctamente.",
"share_link_copied": "Enlace de compartir copiado al portapapeles"
},
"answers": {
"yes": "Sí",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Pas assez de messages pour condenser le contexte",
"condensed_recently": "Le contexte a été condensé récemment ; cette tentative est ignorée",
"condense_handler_invalid": "Le gestionnaire d'API pour condenser le contexte est invalide",
"condense_context_grew": "La taille du contexte a augmenté pendant la condensation ; cette tentative est ignorée"
"condense_context_grew": "La taille du contexte a augmenté pendant la condensation ; cette tentative est ignorée",
"share_task_failed": "Échec du partage de la tâche. Veuillez réessayer.",
"share_no_active_task": "Aucune tâche active à partager"
},
"warnings": {
"no_terminal_content": "Aucun contenu de terminal sélectionné",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Serveur \"{{serverName}}\" introuvable dans la configuration",
"custom_storage_path_set": "Chemin de stockage personnalisé défini : {{path}}",
"default_storage_path": "Retour au chemin de stockage par défaut",
"settings_imported": "Paramètres importés avec succès."
"settings_imported": "Paramètres importés avec succès.",
"share_link_copied": "Lien de partage copié dans le presse-papiers"
},
"answers": {
"yes": "Oui",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "संदर्भ को संक्षिप्त करने के लिए पर्याप्त संदेश नहीं हैं",
"condensed_recently": "संदर्भ हाल ही में संक्षिप्त किया गया था; इस प्रयास को छोड़ा जा रहा है",
"condense_handler_invalid": "संदर्भ को संक्षिप्त करने के लिए API हैंडलर अमान्य है",
"condense_context_grew": "संक्षिप्तीकरण के दौरान संदर्भ का आकार बढ़ गया; इस प्रयास को छोड़ा जा रहा है"
"condense_context_grew": "संक्षिप्तीकरण के दौरान संदर्भ का आकार बढ़ गया; इस प्रयास को छोड़ा जा रहा है",
"share_task_failed": "कार्य साझा करने में विफल। कृपया पुनः प्रयास करें।",
"share_no_active_task": "साझा करने के लिए कोई सक्रिय कार्य नहीं"
},
"warnings": {
"no_terminal_content": "कोई टर्मिनल सामग्री चयनित नहीं",
@ -75,7 +77,8 @@
"mcp_server_not_found": "सर्वर \"{{serverName}}\" कॉन्फ़िगरेशन में नहीं मिला",
"custom_storage_path_set": "कस्टम स्टोरेज पाथ सेट किया गया: {{path}}",
"default_storage_path": "डिफ़ॉल्ट स्टोरेज पाथ का उपयोग पुनः शुरू किया गया",
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।"
"settings_imported": "सेटिंग्स सफलतापूर्वक इम्पोर्ट की गईं।",
"share_link_copied": "साझा लिंक क्लिपबोर्ड पर कॉपी किया गया"
},
"answers": {
"yes": "हां",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Non ci sono abbastanza messaggi per condensare il contesto",
"condensed_recently": "Il contesto è stato condensato di recente; questo tentativo viene saltato",
"condense_handler_invalid": "Il gestore API per condensare il contesto non è valido",
"condense_context_grew": "La dimensione del contesto è aumentata durante la condensazione; questo tentativo viene saltato"
"condense_context_grew": "La dimensione del contesto è aumentata durante la condensazione; questo tentativo viene saltato",
"share_task_failed": "Condivisione dell'attività fallita. Riprova.",
"share_no_active_task": "Nessuna attività attiva da condividere"
},
"warnings": {
"no_terminal_content": "Nessun contenuto del terminale selezionato",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Server \"{{serverName}}\" non trovato nella configurazione",
"custom_storage_path_set": "Percorso di archiviazione personalizzato impostato: {{path}}",
"default_storage_path": "Tornato al percorso di archiviazione predefinito",
"settings_imported": "Impostazioni importate con successo."
"settings_imported": "Impostazioni importate con successo.",
"share_link_copied": "Link di condivisione copiato negli appunti"
},
"answers": {
"yes": "Sì",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "コンテキストを圧縮するのに十分なメッセージがありません",
"condensed_recently": "コンテキストは最近圧縮されました;この試行をスキップします",
"condense_handler_invalid": "コンテキストを圧縮するためのAPIハンドラーが無効です",
"condense_context_grew": "圧縮中にコンテキストサイズが増加しました;この試行をスキップします"
"condense_context_grew": "圧縮中にコンテキストサイズが増加しました;この試行をスキップします",
"share_task_failed": "タスクの共有に失敗しました",
"share_no_active_task": "共有するアクティブなタスクがありません"
},
"warnings": {
"no_terminal_content": "選択されたターミナルコンテンツがありません",
@ -75,7 +77,8 @@
"mcp_server_not_found": "サーバー\"{{serverName}}\"が設定内に見つかりません",
"custom_storage_path_set": "カスタムストレージパスが設定されました:{{path}}",
"default_storage_path": "デフォルトのストレージパスに戻りました",
"settings_imported": "設定が正常にインポートされました。"
"settings_imported": "設定が正常にインポートされました。",
"share_link_copied": "共有リンクがクリップボードにコピーされました"
},
"answers": {
"yes": "はい",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "컨텍스트를 압축할 메시지가 충분하지 않습니다",
"condensed_recently": "컨텍스트가 최근 압축되었습니다; 이 시도를 건너뜁니다",
"condense_handler_invalid": "컨텍스트 압축을 위한 API 핸들러가 유효하지 않습니다",
"condense_context_grew": "압축 중 컨텍스트 크기가 증가했습니다; 이 시도를 건너뜁니다"
"condense_context_grew": "압축 중 컨텍스트 크기가 증가했습니다; 이 시도를 건너뜁니다",
"share_task_failed": "작업 공유에 실패했습니다",
"share_no_active_task": "공유할 활성 작업이 없습니다"
},
"warnings": {
"no_terminal_content": "선택된 터미널 내용이 없습니다",
@ -75,7 +77,8 @@
"mcp_server_not_found": "구성에서 서버 \"{{serverName}}\"을(를) 찾을 수 없습니다",
"custom_storage_path_set": "사용자 지정 저장 경로 설정됨: {{path}}",
"default_storage_path": "기본 저장 경로로 되돌아갔습니다",
"settings_imported": "설정이 성공적으로 가져와졌습니다."
"settings_imported": "설정이 성공적으로 가져와졌습니다.",
"share_link_copied": "공유 링크가 클립보드에 복사되었습니다"
},
"answers": {
"yes": "예",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Niet genoeg berichten om context te comprimeren",
"condensed_recently": "Context is recent gecomprimeerd; deze poging wordt overgeslagen",
"condense_handler_invalid": "API-handler voor het comprimeren van context is ongeldig",
"condense_context_grew": "Contextgrootte nam toe tijdens comprimeren; deze poging wordt overgeslagen"
"condense_context_grew": "Contextgrootte nam toe tijdens comprimeren; deze poging wordt overgeslagen",
"share_task_failed": "Delen van taak mislukt",
"share_no_active_task": "Geen actieve taak om te delen"
},
"warnings": {
"no_terminal_content": "Geen terminalinhoud geselecteerd",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Server \"{{serverName}}\" niet gevonden in configuratie",
"custom_storage_path_set": "Aangepast opslagpad ingesteld: {{path}}",
"default_storage_path": "Terug naar standaard opslagpad",
"settings_imported": "Instellingen succesvol geïmporteerd."
"settings_imported": "Instellingen succesvol geïmporteerd.",
"share_link_copied": "Deellink gekopieerd naar klembord"
},
"answers": {
"yes": "Ja",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Za mało wiadomości do skondensowania kontekstu",
"condensed_recently": "Kontekst został niedawno skondensowany; pomijanie tej próby",
"condense_handler_invalid": "Nieprawidłowy handler API do kondensowania kontekstu",
"condense_context_grew": "Rozmiar kontekstu wzrósł podczas kondensacji; pomijanie tej próby"
"condense_context_grew": "Rozmiar kontekstu wzrósł podczas kondensacji; pomijanie tej próby",
"share_task_failed": "Nie udało się udostępnić zadania",
"share_no_active_task": "Brak aktywnego zadania do udostępnienia"
},
"warnings": {
"no_terminal_content": "Nie wybrano zawartości terminala",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Serwer \"{{serverName}}\" nie znaleziony w konfiguracji",
"custom_storage_path_set": "Ustawiono niestandardową ścieżkę przechowywania: {{path}}",
"default_storage_path": "Wznowiono używanie domyślnej ścieżki przechowywania",
"settings_imported": "Ustawienia zaimportowane pomyślnie."
"settings_imported": "Ustawienia zaimportowane pomyślnie.",
"share_link_copied": "Link udostępniania skopiowany do schowka"
},
"answers": {
"yes": "Tak",

View file

@ -63,7 +63,9 @@
"condense_not_enough_messages": "Não há mensagens suficientes para condensar o contexto",
"condensed_recently": "O contexto foi condensado recentemente; pulando esta tentativa",
"condense_handler_invalid": "O manipulador de API para condensar o contexto é inválido",
"condense_context_grew": "O tamanho do contexto aumentou durante a condensação; pulando esta tentativa"
"condense_context_grew": "O tamanho do contexto aumentou durante a condensação; pulando esta tentativa",
"share_task_failed": "Falha ao compartilhar tarefa",
"share_no_active_task": "Nenhuma tarefa ativa para compartilhar"
},
"warnings": {
"no_terminal_content": "Nenhum conteúdo do terminal selecionado",
@ -79,7 +81,8 @@
"mcp_server_not_found": "Servidor \"{{serverName}}\" não encontrado na configuração",
"custom_storage_path_set": "Caminho de armazenamento personalizado definido: {{path}}",
"default_storage_path": "Retornado ao caminho de armazenamento padrão",
"settings_imported": "Configurações importadas com sucesso."
"settings_imported": "Configurações importadas com sucesso.",
"share_link_copied": "Link de compartilhamento copiado para a área de transferência"
},
"answers": {
"yes": "Sim",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Недостаточно сообщений для сжатия контекста",
"condensed_recently": "Контекст был недавно сжат; пропускаем эту попытку",
"condense_handler_invalid": "Обработчик API для сжатия контекста недействителен",
"condense_context_grew": "Размер контекста увеличился во время сжатия; пропускаем эту попытку"
"condense_context_grew": "Размер контекста увеличился во время сжатия; пропускаем эту попытку",
"share_task_failed": "Не удалось поделиться задачей",
"share_no_active_task": "Нет активной задачи для совместного использования"
},
"warnings": {
"no_terminal_content": "Не выбрано содержимое терминала",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Сервер \"{{serverName}}\" не найден в конфигурации",
"custom_storage_path_set": "Установлен пользовательский путь хранения: {{path}}",
"default_storage_path": "Возвращено использование пути хранения по умолчанию",
"settings_imported": "Настройки успешно импортированы."
"settings_imported": "Настройки успешно импортированы.",
"share_link_copied": "Ссылка для совместного использования скопирована в буфер обмена"
},
"answers": {
"yes": "Да",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Bağlamı sıkıştırmak için yeterli mesaj yok",
"condensed_recently": "Bağlam yakın zamanda sıkıştırıldı; bu deneme atlanıyor",
"condense_handler_invalid": "Bağlamı sıkıştırmak için API işleyicisi geçersiz",
"condense_context_grew": "Sıkıştırma sırasında bağlam boyutu arttı; bu deneme atlanıyor"
"condense_context_grew": "Sıkıştırma sırasında bağlam boyutu arttı; bu deneme atlanıyor",
"share_task_failed": "Görev paylaşılamadı",
"share_no_active_task": "Paylaşılacak aktif görev yok"
},
"warnings": {
"no_terminal_content": "Seçili terminal içeriği yok",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Yapılandırmada \"{{serverName}}\" sunucusu bulunamadı",
"custom_storage_path_set": "Özel depolama yolu ayarlandı: {{path}}",
"default_storage_path": "Varsayılan depolama yoluna geri dönüldü",
"settings_imported": "Ayarlar başarıyla içe aktarıldı."
"settings_imported": "Ayarlar başarıyla içe aktarıldı.",
"share_link_copied": "Paylaşım bağlantısı panoya kopyalandı"
},
"answers": {
"yes": "Evet",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "Không đủ tin nhắn để nén ngữ cảnh",
"condensed_recently": "Ngữ cảnh đã được nén gần đây; bỏ qua lần thử này",
"condense_handler_invalid": "Trình xử lý API để nén ngữ cảnh không hợp lệ",
"condense_context_grew": "Kích thước ngữ cảnh tăng lên trong quá trình nén; bỏ qua lần thử này"
"condense_context_grew": "Kích thước ngữ cảnh tăng lên trong quá trình nén; bỏ qua lần thử này",
"share_task_failed": "Không thể chia sẻ nhiệm vụ",
"share_no_active_task": "Không có nhiệm vụ hoạt động để chia sẻ"
},
"warnings": {
"no_terminal_content": "Không có nội dung terminal được chọn",
@ -75,7 +77,8 @@
"mcp_server_not_found": "Không tìm thấy máy chủ \"{{serverName}}\" trong cấu hình",
"custom_storage_path_set": "Đã thiết lập đường dẫn lưu trữ tùy chỉnh: {{path}}",
"default_storage_path": "Đã quay lại sử dụng đường dẫn lưu trữ mặc định",
"settings_imported": "Cài đặt đã được nhập thành công."
"settings_imported": "Cài đặt đã được nhập thành công.",
"share_link_copied": "Liên kết chia sẻ đã được sao chép vào clipboard"
},
"answers": {
"yes": "Có",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "没有足够的对话来压缩上下文",
"condensed_recently": "上下文最近已压缩;跳过此次尝试",
"condense_handler_invalid": "压缩上下文的API处理程序无效",
"condense_context_grew": "压缩过程中上下文大小增加;跳过此次尝试"
"condense_context_grew": "压缩过程中上下文大小增加;跳过此次尝试",
"share_task_failed": "分享任务失败。请重试。",
"share_no_active_task": "没有活跃任务可分享"
},
"warnings": {
"no_terminal_content": "没有选择终端内容",
@ -75,7 +77,8 @@
"mcp_server_not_found": "在配置中未找到服务器\"{{serverName}}\"",
"custom_storage_path_set": "自定义存储路径已设置:{{path}}",
"default_storage_path": "已恢复使用默认存储路径",
"settings_imported": "设置已成功导入。"
"settings_imported": "设置已成功导入。",
"share_link_copied": "分享链接已复制到剪贴板"
},
"answers": {
"yes": "是",

View file

@ -59,7 +59,9 @@
"condense_not_enough_messages": "沒有足夠的訊息來壓縮上下文",
"condensed_recently": "上下文最近已壓縮;跳過此次嘗試",
"condense_handler_invalid": "壓縮上下文的 API 處理程式無效",
"condense_context_grew": "壓縮過程中上下文大小增加;跳過此次嘗試"
"condense_context_grew": "壓縮過程中上下文大小增加;跳過此次嘗試",
"share_task_failed": "分享工作失敗。請重試。",
"share_no_active_task": "沒有活躍的工作可分享"
},
"warnings": {
"no_terminal_content": "沒有選擇終端機內容",
@ -75,7 +77,8 @@
"mcp_server_not_found": "在設定中沒有找到伺服器\"{{serverName}}\"",
"custom_storage_path_set": "自訂儲存路徑已設定:{{path}}",
"default_storage_path": "已恢復使用預設儲存路徑",
"settings_imported": "設定已成功匯入。"
"settings_imported": "設定已成功匯入。",
"share_link_copied": "分享連結已複製到剪貼簿"
},
"answers": {
"yes": "是",

View file

@ -220,6 +220,7 @@ export type ExtensionState = Pick<
cloudUserInfo: CloudUserInfo | null
cloudIsAuthenticated: boolean
sharingEnabled: boolean
organizationAllowList: OrganizationAllowList
autoCondenseContext: boolean

View file

@ -36,6 +36,7 @@ export interface WebviewMessage {
| "didShowAnnouncement"
| "selectImages"
| "exportCurrentTask"
| "shareCurrentTask"
| "showTaskWithId"
| "deleteTaskWithId"
| "exportTaskWithId"

View file

@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"
import type { HistoryItem } from "@roo-code/types"
import { vscode } from "@/utils/vscode"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { DeleteTaskDialog } from "../history/DeleteTaskDialog"
import { IconButton } from "./IconButton"
@ -17,9 +18,18 @@ interface TaskActionsProps {
export const TaskActions = ({ item, buttonsDisabled }: TaskActionsProps) => {
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null)
const { t } = useTranslation()
const { sharingEnabled } = useExtensionState()
return (
<div className="flex flex-row gap-1">
{item?.id && sharingEnabled && (
<IconButton
iconClass="codicon-link"
title={t("chat:task.share")}
disabled={buttonsDisabled}
onClick={() => vscode.postMessage({ type: "shareCurrentTask" })}
/>
)}
<IconButton
iconClass="codicon-desktop-download"
title={t("chat:task.export")}

View file

@ -36,6 +36,7 @@ export interface ExtensionStateContextType extends ExtensionState {
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
organizationAllowList: OrganizationAllowList
cloudIsAuthenticated: boolean
sharingEnabled: boolean
maxConcurrentFileReads?: number
condensingApiConfigId?: string
setCondensingApiConfigId: (value: string) => void
@ -201,6 +202,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
cloudUserInfo: null,
cloudIsAuthenticated: false,
sharingEnabled: false,
organizationAllowList: ORGANIZATION_ALLOW_ALL,
autoCondenseContext: true,
autoCondenseContextPercent: 100,

View file

@ -208,6 +208,7 @@ describe("mergeExtensionState", () => {
autoCondenseContext: true,
autoCondenseContextPercent: 100,
cloudIsAuthenticated: false,
sharingEnabled: false,
}
const prevState: ExtensionState = {

View file

@ -11,7 +11,8 @@
"closeAndStart": "Tancar tasca i iniciar-ne una de nova",
"export": "Exportar historial de tasques",
"delete": "Eliminar tasca (Shift + Clic per ometre confirmació)",
"condenseContext": "Condensar context de forma intel·ligent"
"condenseContext": "Condensar context de forma intel·ligent",
"share": "Compartir tasca"
},
"unpin": "Desfixar",
"pin": "Fixar",

View file

@ -11,6 +11,7 @@
"closeAndStart": "Aufgabe schließen und neue starten",
"export": "Aufgabenverlauf exportieren",
"delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)",
"share": "Aufgabe teilen",
"condenseContext": "Kontext intelligent komprimieren"
},
"unpin": "Lösen von oben",

View file

@ -11,6 +11,7 @@
"contextWindow": "Context Length:",
"closeAndStart": "Close task and start a new one",
"export": "Export task history",
"share": "Share task",
"delete": "Delete Task (Shift + Click to skip confirmation)"
},
"unpin": "Unpin",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Cerrar tarea e iniciar una nueva",
"export": "Exportar historial de tareas",
"delete": "Eliminar tarea (Shift + Clic para omitir confirmación)",
"condenseContext": "Condensar contexto de forma inteligente"
"condenseContext": "Condensar contexto de forma inteligente",
"share": "Compartir tarea"
},
"unpin": "Desfijar",
"pin": "Fijar",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Fermer la tâche et en commencer une nouvelle",
"export": "Exporter l'historique des tâches",
"delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)",
"condenseContext": "Condenser intelligemment le contexte"
"condenseContext": "Condenser intelligemment le contexte",
"share": "Partager la tâche"
},
"unpin": "Désépingler",
"pin": "Épingler",

View file

@ -11,7 +11,8 @@
"closeAndStart": "कार्य बंद करें और नया शुरू करें",
"export": "कार्य इतिहास निर्यात करें",
"delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)",
"condenseContext": "संदर्भ को बुद्धिमानी से संघनित करें"
"condenseContext": "संदर्भ को बुद्धिमानी से संघनित करें",
"share": "कार्य साझा करें"
},
"unpin": "पिन करें",
"pin": "अवपिन करें",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Chiudi attività e iniziane una nuova",
"export": "Esporta cronologia attività",
"delete": "Elimina attività (Shift + Clic per saltare la conferma)",
"condenseContext": "Condensa contesto in modo intelligente"
"condenseContext": "Condensa contesto in modo intelligente",
"share": "Condividi attività"
},
"unpin": "Rilascia",
"pin": "Fissa",

View file

@ -11,7 +11,8 @@
"closeAndStart": "タスクを閉じて新しいタスクを開始",
"export": "タスク履歴をエクスポート",
"delete": "タスクを削除Shift + クリックで確認をスキップ)",
"condenseContext": "コンテキストをインテリジェントに圧縮"
"condenseContext": "コンテキストをインテリジェントに圧縮",
"share": "タスクを共有"
},
"unpin": "ピン留めを解除",
"pin": "ピン留め",

View file

@ -11,7 +11,8 @@
"closeAndStart": "작업 닫고 새 작업 시작",
"export": "작업 기록 내보내기",
"delete": "작업 삭제 (Shift + 클릭으로 확인 생략)",
"condenseContext": "컨텍스트 지능적으로 압축"
"condenseContext": "컨텍스트 지능적으로 압축",
"share": "작업 공유"
},
"unpin": "고정 해제하기",
"pin": "고정하기",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Taak sluiten en een nieuwe starten",
"export": "Taakgeschiedenis exporteren",
"delete": "Taak verwijderen (Shift + Klik om bevestiging over te slaan)",
"condenseContext": "Context intelligent samenvatten"
"condenseContext": "Context intelligent samenvatten",
"share": "Taak delen"
},
"unpin": "Losmaken",
"pin": "Vastmaken",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Zamknij zadanie i rozpocznij nowe",
"export": "Eksportuj historię zadań",
"delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)",
"condenseContext": "Inteligentnie skondensuj kontekst"
"condenseContext": "Inteligentnie skondensuj kontekst",
"share": "Udostępnij zadanie"
},
"unpin": "Odepnij",
"pin": "Przypnij",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Fechar tarefa e iniciar nova",
"export": "Exportar histórico de tarefas",
"delete": "Excluir tarefa (Shift + Clique para pular confirmação)",
"condenseContext": "Condensar contexto de forma inteligente"
"condenseContext": "Condensar contexto de forma inteligente",
"share": "Compartilhar tarefa"
},
"unpin": "Desfixar",
"pin": "Fixar",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Закрыть задачу и начать новую",
"export": "Экспортировать историю задач",
"delete": "Удалить задачу (Shift + клик для пропуска подтверждения)",
"condenseContext": "Интеллектуально сжать контекст"
"condenseContext": "Интеллектуально сжать контекст",
"share": "Поделиться задачей"
},
"unpin": "Открепить",
"pin": "Закрепить",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Görevi kapat ve yeni bir görev başlat",
"export": "Görev geçmişini dışa aktar",
"delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)",
"condenseContext": "Bağlamı akıllıca yoğunlaştır"
"condenseContext": "Bağlamı akıllıca yoğunlaştır",
"share": "Görevi paylaş"
},
"unpin": "Sabitlemeyi iptal et",
"pin": "Sabitle",

View file

@ -11,7 +11,8 @@
"closeAndStart": "Đóng nhiệm vụ và bắt đầu nhiệm vụ mới",
"export": "Xuất lịch sử nhiệm vụ",
"delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)",
"condenseContext": "Cô đọng ngữ cảnh thông minh"
"condenseContext": "Cô đọng ngữ cảnh thông minh",
"share": "Chia sẻ nhiệm vụ"
},
"unpin": "Bỏ ghim khỏi đầu",
"pin": "Ghim lên đầu",

View file

@ -11,6 +11,7 @@
"closeAndStart": "关闭任务并开始新任务",
"export": "导出任务历史",
"delete": "删除任务Shift + 点击跳过确认)",
"share": "分享任务",
"condenseContext": "智能压缩上下文"
},
"unpin": "取消置顶",

View file

@ -11,7 +11,8 @@
"closeAndStart": "關閉現有工作並開始一項新的工作",
"export": "匯出工作紀錄",
"delete": "刪除工作(按住 Shift 並點選可跳過確認)",
"condenseContext": "智慧壓縮上下文"
"condenseContext": "智慧壓縮上下文",
"share": "分享工作"
},
"unpin": "取消置頂",
"pin": "置頂",