Merge remote-tracking branch 'origin/main'

This commit is contained in:
Mohamed Boudra
2026-06-12 12:12:46 +07:00
21 changed files with 492 additions and 13 deletions
@@ -87,6 +87,7 @@ interface SplitContainerProps {
onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
@@ -365,6 +366,7 @@ export function SplitContainer({
onCloseTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -581,6 +583,7 @@ export function SplitContainer({
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
@@ -723,6 +726,7 @@ function SplitNodeView({
onCloseTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -775,6 +779,7 @@ function SplitNodeView({
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
@@ -820,6 +825,7 @@ function SplitNodeView({
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
@@ -871,6 +877,7 @@ function SplitPaneView({
onCloseTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -1012,6 +1019,7 @@ function SplitPaneView({
onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeft}
@@ -14,7 +14,7 @@ const SNAP_POINTS: string[] = ["82%", "94%"];
export function PairDeviceModal({ visible, onClose, testID }: PairDeviceModalProps) {
const { t } = useTranslation();
const header = useMemo<SheetHeader>(
() => ({ title: t("settings.integrations.pairDevices.rowTitle") }),
() => ({ title: t("settings.host.pairDevices.rowTitle") }),
[t],
);
+11 -3
View File
@@ -433,6 +433,7 @@ export const ar: TranslationResources = {
openFor: "فتح القائمة لـ{{label}}",
copyResumeCommand: "نسخ أمر السيرة الذاتية",
copyAgentId: "نسخ معرف الوكيل",
copyFilePath: "Copy file path",
rename: "إعادة تسمية",
closeAbove: "إغلاق علامات التبويب أعلاه",
closeBelow: "إغلاق علامات التبويب أدناه",
@@ -467,6 +468,7 @@ export const ar: TranslationResources = {
copyFailed: "فشل النسخ",
agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "أمر الاستئناف",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "السيرة الذاتية ID غير متوفرة",
resumeCommandUnavailable: "أمر الاستئناف غير متوفر",
reloadingAgent: "وكيل إعادة التحميل...",
@@ -1294,7 +1296,7 @@ export const ar: TranslationResources = {
},
renameModal: {
rename: "إعادة تسمية",
saving: "توفير...",
saving: "جارٍ الحفظ...",
},
sidebarCallout: {
dismiss: "رفض",
@@ -1612,6 +1614,10 @@ export const ar: TranslationResources = {
sheetTitle: "إلحاق موجه النظام",
accessibilityLabel: "إلحاق موجه النظام",
placeholder: "اجعل الردود موجزة دائمًا.",
edit: "تحرير",
reset: "إعادة ضبط",
save: "حفظ",
saving: "جارٍ الحفظ...",
},
},
agents: {
@@ -1646,10 +1652,12 @@ export const ar: TranslationResources = {
editLabel: "تحرير التسمية",
title: "إعادة تسمية المضيف",
placeholder: "بلدي Host",
submit: "إعادة تسمية",
},
restart: {
title: "إعادة تشغيل البرنامج الخفي",
hint: "إعادة تشغيل عملية البرنامج الخفي. سيتم إعادة الاتصال بالتطبيق تلقائيًا",
confirm: "إعادة التشغيل",
confirmTitle: "أعد تشغيل{{name}}",
confirmMessage:
"سيؤدي هذا إلى إعادة تشغيل البرنامج الخفي. سيستمر العملاء الذين يعملون عليه؛ سيتم إعادة الاتصال بالتطبيق تلقائيًا.",
@@ -1815,9 +1823,9 @@ export const ar: TranslationResources = {
actions: {
reload: "إعادة تحميل",
tryAgain: "حاول ثانية",
save: "يحفظ",
save: "حفظ",
saved: "تم حفظ المشروع",
saving: "توفير...",
saving: "جارٍ الحفظ...",
cancel: "يلغي",
},
},
+8
View File
@@ -432,6 +432,7 @@ export const en = {
openFor: "Open menu for {{label}}",
copyResumeCommand: "Copy resume command",
copyAgentId: "Copy agent id",
copyFilePath: "Copy file path",
rename: "Rename",
closeAbove: "Close tabs above",
closeBelow: "Close tabs below",
@@ -466,6 +467,7 @@ export const en = {
copyFailed: "Copy failed",
agentIdCopiedLabel: "Agent ID",
resumeCommandCopiedLabel: "resume command",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "Resume ID not available",
resumeCommandUnavailable: "Resume command not available",
reloadingAgent: "Reloading agent...",
@@ -1619,6 +1621,10 @@ export const en = {
sheetTitle: "Append system prompt",
accessibilityLabel: "Append system prompt",
placeholder: "Always keep replies concise.",
edit: "Edit",
reset: "Reset",
save: "Save",
saving: "Saving...",
},
},
agents: {
@@ -1653,10 +1659,12 @@ export const en = {
editLabel: "Edit label",
title: "Rename host",
placeholder: "My Host",
submit: "Rename",
},
restart: {
title: "Restart daemon",
hint: "Restarts the daemon process. The app will reconnect automatically",
confirm: "Restart",
confirmTitle: "Restart {{name}}",
confirmMessage:
"This will restart the daemon. Agents running on it will keep going; the app will reconnect automatically.",
+11 -3
View File
@@ -436,6 +436,7 @@ export const es: TranslationResources = {
openFor: "Menú abierto para{{label}}",
copyResumeCommand: "Copiar comando de reanudación",
copyAgentId: "Copiar ID del agente",
copyFilePath: "Copy file path",
rename: "Rebautizar",
closeAbove: "Cerrar pestañas arriba",
closeBelow: "Cerrar pestañas a continuación",
@@ -471,6 +472,7 @@ export const es: TranslationResources = {
copyFailed: "Copia fallida",
agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "reanudar el comando",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "ReanudarIDno disponible",
resumeCommandUnavailable: "Comando de reanudación no disponible",
reloadingAgent: "Agente de recarga...",
@@ -1329,7 +1331,7 @@ export const es: TranslationResources = {
},
renameModal: {
rename: "Rebautizar",
saving: "Ahorro...",
saving: "Guardando...",
},
sidebarCallout: {
dismiss: "Despedir",
@@ -1649,6 +1651,10 @@ export const es: TranslationResources = {
sheetTitle: "Agregar mensaje del sistema",
accessibilityLabel: "Agregar mensaje del sistema",
placeholder: "Mantenga siempre respuestas concisas.",
edit: "Editar",
reset: "Restablecer",
save: "Guardar",
saving: "Guardando...",
},
},
agents: {
@@ -1683,10 +1689,12 @@ export const es: TranslationResources = {
editLabel: "Editar etiqueta",
title: "Cambiar nombre de host",
placeholder: "MiHost",
submit: "Cambiar nombre",
},
restart: {
title: "Reiniciar demonio",
hint: "Reinicia el proceso del demonio. La aplicación se volverá a conectar automáticamente",
confirm: "Reiniciar",
confirmTitle: "Reiniciar{{name}}",
confirmMessage:
"Esto reiniciará el demonio. Los agentes que se ejecutan en él seguirán funcionando; la aplicación se volverá a conectar automáticamente.",
@@ -1856,9 +1864,9 @@ export const es: TranslationResources = {
actions: {
reload: "Recargar",
tryAgain: "Intentar otra vez",
save: "Ahorrar",
save: "Guardar",
saved: "Proyecto guardado",
saving: "Ahorro...",
saving: "Guardando...",
cancel: "Cancelar",
},
},
+10 -2
View File
@@ -436,6 +436,7 @@ export const fr: TranslationResources = {
openFor: "Ouvrir le menu pour{{label}}",
copyResumeCommand: "Copier la commande de reprise",
copyAgentId: "Copier l'identifiant de l'agent",
copyFilePath: "Copy file path",
rename: "Rebaptiser",
closeAbove: "Fermer les onglets ci-dessus",
closeBelow: "Fermer les onglets ci-dessous",
@@ -471,6 +472,7 @@ export const fr: TranslationResources = {
copyFailed: "Échec de la copie",
agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "reprendre la commande",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "ReprendreIDnon disponible",
resumeCommandUnavailable: "Commande de reprise non disponible",
reloadingAgent: "Agent de rechargement...",
@@ -1332,7 +1334,7 @@ export const fr: TranslationResources = {
},
renameModal: {
rename: "Rebaptiser",
saving: "Économie...",
saving: "Sauvegarde...",
},
sidebarCallout: {
dismiss: "Rejeter",
@@ -1655,6 +1657,10 @@ export const fr: TranslationResources = {
sheetTitle: "Ajouter une invite système",
accessibilityLabel: "Ajouter une invite système",
placeholder: "Gardez toujours des réponses concises.",
edit: "Modifier",
reset: "Réinitialiser",
save: "Sauvegarder",
saving: "Sauvegarde...",
},
},
agents: {
@@ -1689,10 +1695,12 @@ export const fr: TranslationResources = {
editLabel: "Modifier l'étiquette",
title: "Renommer l'hôte",
placeholder: "MonHost",
submit: "Renommer",
},
restart: {
title: "Redémarrer le démon",
hint: "Redémarre le processus démon. L'application se reconnectera automatiquement",
confirm: "Redémarrer",
confirmTitle: "Redémarrer{{name}}",
confirmMessage:
"Cela redémarrera le démon. Les agents qui s'y exécutent continueront à fonctionner; l'application se reconnectera automatiquement.",
@@ -1866,7 +1874,7 @@ export const fr: TranslationResources = {
tryAgain: "Essayer à nouveau",
save: "Sauvegarder",
saved: "Projet enregistré",
saving: "Économie...",
saving: "Sauvegarde...",
cancel: "Annuler",
},
},
+8
View File
@@ -436,6 +436,7 @@ export const ru: TranslationResources = {
openFor: "Открыть меню для{{label}}",
copyResumeCommand: "Копировать команду возобновления",
copyAgentId: "Скопировать идентификатор агента",
copyFilePath: "Copy file path",
rename: "Переименовать",
closeAbove: "Закрыть вкладки выше",
closeBelow: "Закройте вкладки ниже",
@@ -470,6 +471,7 @@ export const ru: TranslationResources = {
copyFailed: "Не удалось скопировать",
agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "команда возобновления",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "Резюме ID недоступно",
resumeCommandUnavailable: "Команда возобновления недоступна",
reloadingAgent: "Перезагрузка агента...",
@@ -1643,6 +1645,10 @@ export const ru: TranslationResources = {
sheetTitle: "Добавить системное приглашение",
accessibilityLabel: "Добавить системное приглашение",
placeholder: "Всегда отвечайте кратко.",
edit: "Редактировать",
reset: "Сбросить",
save: "Сохранить",
saving: "Сохранение...",
},
},
agents: {
@@ -1677,10 +1683,12 @@ export const ru: TranslationResources = {
editLabel: "Изменить ярлык",
title: "Переименовать хост",
placeholder: "Мой Host",
submit: "Переименовать",
},
restart: {
title: "Перезапустить демон",
hint: "Перезапускает процесс демона. Приложение автоматически переподключится",
confirm: "Перезапустить",
confirmTitle: "Перезапустите{{name}}",
confirmMessage:
"Это перезапустит демон. Агенты, работающие на нем, будут продолжать работать; приложение автоматически повторно подключится.",
+8
View File
@@ -433,6 +433,7 @@ export const zhCN: TranslationResources = {
openFor: "打开 {{label}} 的菜单",
copyResumeCommand: "复制恢复命令",
copyAgentId: "复制 Agent ID",
copyFilePath: "Copy file path",
rename: "重命名",
closeAbove: "关闭上方标签",
closeBelow: "关闭下方标签",
@@ -467,6 +468,7 @@ export const zhCN: TranslationResources = {
copyFailed: "复制失败",
agentIdCopiedLabel: "Agent ID",
resumeCommandCopiedLabel: "恢复命令",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "恢复 ID 不可用",
resumeCommandUnavailable: "恢复命令不可用",
reloadingAgent: "正在重新加载 Agent...",
@@ -1594,6 +1596,10 @@ export const zhCN: TranslationResources = {
sheetTitle: "追加 system prompt",
accessibilityLabel: "追加 system prompt",
placeholder: "始终保持回复简洁。",
edit: "编辑",
reset: "重置",
save: "保存",
saving: "保存中...",
},
},
agents: {
@@ -1628,10 +1634,12 @@ export const zhCN: TranslationResources = {
editLabel: "编辑标签",
title: "重命名 Host",
placeholder: "我的 Host",
submit: "重命名",
},
restart: {
title: "重启 Daemon",
hint: "重启 Daemon 进程。应用会自动重新连接",
confirm: "重启",
confirmTitle: "重启 {{name}}",
confirmMessage: "这会重启 Daemon。其上运行的 Agent 会继续运行;应用会自动重新连接。",
restarting: "正在重启...",
@@ -399,6 +399,7 @@ interface WorkspaceDesktopTabsRowProps {
onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -713,6 +714,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -784,6 +786,7 @@ export function WorkspaceDesktopTabsRow({
() => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -881,6 +884,7 @@ export function WorkspaceDesktopTabsRow({
normalizedWorkspaceId={normalizedWorkspaceId}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
@@ -911,6 +915,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTabsToLeft,
onCloseTabsToRight,
onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand,
onNavigateTab,
onReloadAgent,
@@ -1003,6 +1008,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -1028,6 +1034,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -1053,6 +1060,7 @@ function ResolvedDesktopTabChip({
tabCount,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -1069,6 +1077,7 @@ function ResolvedDesktopTabChip({
onCloseTabsToLeft,
onCloseTabsToRight,
onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand,
labels,
onReloadAgent,
@@ -392,6 +392,7 @@ interface MobileWorkspaceTabSwitcherProps {
onSelectSwitcherTab: (key: string) => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -579,6 +580,7 @@ function MobileWorkspaceTabOption({
onPress,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -596,6 +598,7 @@ function MobileWorkspaceTabOption({
onPress: () => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -608,6 +611,7 @@ function MobileWorkspaceTabOption({
() => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -629,6 +633,7 @@ function MobileWorkspaceTabOption({
menuTestIDBase,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -695,6 +700,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onSelectSwitcherTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -750,6 +756,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onPress={onPress}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTab={onCloseTab}
@@ -767,6 +774,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
normalizedWorkspaceId,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -2712,6 +2720,19 @@ function WorkspaceScreenContent({
[toast, t],
);
const handleCopyFilePath = useCallback(
async (path: string) => {
if (!path) return;
try {
await Clipboard.setStringAsync(path);
toast.copied(t("workspace.tabs.toasts.filePathCopiedLabel"));
} catch {
toast.error(t("workspace.tabs.toasts.copyFailed"));
}
},
[toast, t],
);
const handleCopyResumeCommand = useCallback(
async (agentId: string) => {
if (!agentId) return;
@@ -3534,6 +3555,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeftInPane}
@@ -3568,6 +3590,7 @@ function WorkspaceScreenContent({
handleCloseTabById,
handleCopyResumeCommand,
handleCopyAgentId,
handleCopyFilePath,
handleReloadAgent,
handleRenameTab,
handleCloseTabsToLeftInPane,
@@ -3648,6 +3671,7 @@ function WorkspaceScreenContent({
onSelectSwitcherTab={handleSelectSwitcherTab}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTab={handleCloseTabById}
@@ -3669,6 +3693,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeft}
@@ -15,6 +15,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
it("uses desktop tab ordering labels for desktop menus", () => {
const onCopyResumeCommand = vi.fn();
const onCopyAgentId = vi.fn();
const onCopyFilePath = vi.fn();
const onReloadAgent = vi.fn();
const onRenameTab = vi.fn();
const onCloseTab = vi.fn();
@@ -30,6 +31,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -59,6 +61,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -93,6 +96,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-draft_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -120,6 +124,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -148,6 +153,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab,
onCloseTab: vi.fn(),
@@ -181,6 +187,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-terminal_abc",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab,
onCloseTab: vi.fn(),
@@ -193,6 +200,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(labels[0]).toBe("Rename");
expect(labels).not.toContain("Copy resume command");
expect(labels).not.toContain("Copy agent id");
expect(labels).not.toContain("Copy file path");
expect(labels).not.toContain("Reload agent");
const renameEntry = entries.find((entry) => entry.kind === "item" && entry.label === "Rename");
@@ -203,6 +211,48 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(onRenameTab).toHaveBeenCalledWith(terminalTab);
});
it("includes copy file path for file tabs", () => {
const onCopyFilePath = vi.fn();
const fileTab: WorkspaceTabDescriptor = {
key: "file_abc",
tabId: "file_abc",
kind: "file",
target: { kind: "file", path: "/some/path.ts", lineStart: 1, lineEnd: 10 },
};
const entries = buildWorkspaceTabMenuEntries({
surface: "desktop",
tab: fileTab,
index: 0,
tabCount: 1,
menuTestIDBase: "workspace-tab-context-file_abc",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath,
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
onCloseTabsBefore: vi.fn(),
onCloseTabsAfter: vi.fn(),
onCloseOtherTabs: vi.fn(),
});
const labels = entries.filter((entry) => entry.kind === "item").map((entry) => entry.label);
expect(labels[0]).toBe("Copy file path");
expect(labels).not.toContain("Copy resume command");
expect(labels).not.toContain("Copy agent id");
expect(labels).not.toContain("Rename");
expect(labels).not.toContain("Reload agent");
const copyFilePathEntry = entries.find(
(entry) => entry.kind === "item" && entry.key === "copy-file-path",
);
if (!copyFilePathEntry || copyFilePathEntry.kind !== "item") {
throw new Error("Copy file path entry missing");
}
copyFilePathEntry.onSelect();
expect(onCopyFilePath).toHaveBeenCalledWith("/some/path.ts");
});
it("uses the same rename entry shape for agent and terminal tabs", () => {
const terminalTab: WorkspaceTabDescriptor = {
key: "terminal_abc",
@@ -218,6 +268,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase,
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -8,6 +8,7 @@ export type WorkspaceTabMenuSurface = "desktop" | "mobile";
export interface WorkspaceTabMenuLabels {
copyResumeCommand: string;
copyAgentId: string;
copyFilePath: string;
rename: string;
closeAbove: string;
closeBelow: string;
@@ -22,6 +23,7 @@ export interface WorkspaceTabMenuLabels {
export const DEFAULT_WORKSPACE_TAB_MENU_LABELS: WorkspaceTabMenuLabels = {
copyResumeCommand: i18n.t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: i18n.t("workspace.tabs.menu.copyAgentId"),
copyFilePath: i18n.t("workspace.tabs.menu.copyFilePath"),
rename: i18n.t("workspace.tabs.menu.rename"),
closeAbove: i18n.t("workspace.tabs.menu.closeAbove"),
closeBelow: i18n.t("workspace.tabs.menu.closeBelow"),
@@ -66,6 +68,7 @@ interface BuildWorkspaceTabMenuEntriesInput {
menuTestIDBase: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -81,6 +84,7 @@ interface BuildWorkspaceDesktopTabActionsInput {
tabCount: number;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -148,6 +152,7 @@ export function buildWorkspaceTabMenuEntries(
menuTestIDBase,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -186,6 +191,20 @@ export function buildWorkspaceTabMenuEntries(
});
}
if (tab.target.kind === "file") {
const filePath = tab.target.path;
entries.push({
kind: "item",
key: "copy-file-path",
label: labels.copyFilePath,
icon: "copy",
testID: `${menuTestIDBase}-copy-file-path`,
onSelect: () => {
void onCopyFilePath(filePath);
},
});
}
if (tab.target.kind === "agent" || tab.target.kind === "terminal") {
entries.push({
kind: "item",
@@ -278,6 +297,7 @@ export function buildWorkspaceDesktopTabActions(
menuTestIDBase: contextMenuTestId,
onCopyResumeCommand: input.onCopyResumeCommand,
onCopyAgentId: input.onCopyAgentId,
onCopyFilePath: input.onCopyFilePath,
onReloadAgent: input.onReloadAgent,
onRenameTab: input.onRenameTab,
onCloseTab: input.onCloseTab,