Add Copy file path to file preview tab menus (#1473)

* feat(app): add Copy file path to file preview tab menus

* fix(app): restore copyAgentId translations accidentally replaced by placeholder
This commit is contained in:
Mohamed Boudra
2026-06-12 11:09:22 +08:00
committed by GitHub
parent 75aa626df5
commit f242519623
11 changed files with 125 additions and 0 deletions
@@ -86,6 +86,7 @@ interface SplitContainerProps {
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void; onCloseTabsToLeft: (tabId: string, paneTabs: WorkspaceTabDescriptor[]) => Promise<void> | void;
@@ -364,6 +365,7 @@ export function SplitContainer({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTabsToLeft, onCloseTabsToLeft,
@@ -580,6 +582,7 @@ export function SplitContainer({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
@@ -722,6 +725,7 @@ function SplitNodeView({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTabsToLeft, onCloseTabsToLeft,
@@ -774,6 +778,7 @@ function SplitNodeView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
@@ -819,6 +824,7 @@ function SplitNodeView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
@@ -870,6 +876,7 @@ function SplitPaneView({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTabsToLeft, onCloseTabsToLeft,
@@ -1011,6 +1018,7 @@ function SplitPaneView({
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeft} onCloseTabsToLeft={handleCloseTabsToLeft}
+2
View File
@@ -433,6 +433,7 @@ export const ar: TranslationResources = {
openFor: "فتح القائمة لـ{{label}}", openFor: "فتح القائمة لـ{{label}}",
copyResumeCommand: "نسخ أمر السيرة الذاتية", copyResumeCommand: "نسخ أمر السيرة الذاتية",
copyAgentId: "نسخ معرف الوكيل", copyAgentId: "نسخ معرف الوكيل",
copyFilePath: "Copy file path",
rename: "إعادة تسمية", rename: "إعادة تسمية",
closeAbove: "إغلاق علامات التبويب أعلاه", closeAbove: "إغلاق علامات التبويب أعلاه",
closeBelow: "إغلاق علامات التبويب أدناه", closeBelow: "إغلاق علامات التبويب أدناه",
@@ -465,6 +466,7 @@ export const ar: TranslationResources = {
copyFailed: "فشل النسخ", copyFailed: "فشل النسخ",
agentIdCopiedLabel: "AgentID", agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "أمر الاستئناف", resumeCommandCopiedLabel: "أمر الاستئناف",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "السيرة الذاتية ID غير متوفرة", resumeIdUnavailable: "السيرة الذاتية ID غير متوفرة",
resumeCommandUnavailable: "أمر الاستئناف غير متوفر", resumeCommandUnavailable: "أمر الاستئناف غير متوفر",
reloadingAgent: "وكيل إعادة التحميل...", reloadingAgent: "وكيل إعادة التحميل...",
+2
View File
@@ -432,6 +432,7 @@ export const en = {
openFor: "Open menu for {{label}}", openFor: "Open menu for {{label}}",
copyResumeCommand: "Copy resume command", copyResumeCommand: "Copy resume command",
copyAgentId: "Copy agent id", copyAgentId: "Copy agent id",
copyFilePath: "Copy file path",
rename: "Rename", rename: "Rename",
closeAbove: "Close tabs above", closeAbove: "Close tabs above",
closeBelow: "Close tabs below", closeBelow: "Close tabs below",
@@ -464,6 +465,7 @@ export const en = {
copyFailed: "Copy failed", copyFailed: "Copy failed",
agentIdCopiedLabel: "Agent ID", agentIdCopiedLabel: "Agent ID",
resumeCommandCopiedLabel: "resume command", resumeCommandCopiedLabel: "resume command",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "Resume ID not available", resumeIdUnavailable: "Resume ID not available",
resumeCommandUnavailable: "Resume command not available", resumeCommandUnavailable: "Resume command not available",
reloadingAgent: "Reloading agent...", reloadingAgent: "Reloading agent...",
+2
View File
@@ -436,6 +436,7 @@ export const es: TranslationResources = {
openFor: "Menú abierto para{{label}}", openFor: "Menú abierto para{{label}}",
copyResumeCommand: "Copiar comando de reanudación", copyResumeCommand: "Copiar comando de reanudación",
copyAgentId: "Copiar ID del agente", copyAgentId: "Copiar ID del agente",
copyFilePath: "Copy file path",
rename: "Rebautizar", rename: "Rebautizar",
closeAbove: "Cerrar pestañas arriba", closeAbove: "Cerrar pestañas arriba",
closeBelow: "Cerrar pestañas a continuación", closeBelow: "Cerrar pestañas a continuación",
@@ -469,6 +470,7 @@ export const es: TranslationResources = {
copyFailed: "Copia fallida", copyFailed: "Copia fallida",
agentIdCopiedLabel: "AgentID", agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "reanudar el comando", resumeCommandCopiedLabel: "reanudar el comando",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "ReanudarIDno disponible", resumeIdUnavailable: "ReanudarIDno disponible",
resumeCommandUnavailable: "Comando de reanudación no disponible", resumeCommandUnavailable: "Comando de reanudación no disponible",
reloadingAgent: "Agente de recarga...", reloadingAgent: "Agente de recarga...",
+2
View File
@@ -436,6 +436,7 @@ export const fr: TranslationResources = {
openFor: "Ouvrir le menu pour{{label}}", openFor: "Ouvrir le menu pour{{label}}",
copyResumeCommand: "Copier la commande de reprise", copyResumeCommand: "Copier la commande de reprise",
copyAgentId: "Copier l'identifiant de l'agent", copyAgentId: "Copier l'identifiant de l'agent",
copyFilePath: "Copy file path",
rename: "Rebaptiser", rename: "Rebaptiser",
closeAbove: "Fermer les onglets ci-dessus", closeAbove: "Fermer les onglets ci-dessus",
closeBelow: "Fermer les onglets ci-dessous", closeBelow: "Fermer les onglets ci-dessous",
@@ -469,6 +470,7 @@ export const fr: TranslationResources = {
copyFailed: "Échec de la copie", copyFailed: "Échec de la copie",
agentIdCopiedLabel: "AgentID", agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "reprendre la commande", resumeCommandCopiedLabel: "reprendre la commande",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "ReprendreIDnon disponible", resumeIdUnavailable: "ReprendreIDnon disponible",
resumeCommandUnavailable: "Commande de reprise non disponible", resumeCommandUnavailable: "Commande de reprise non disponible",
reloadingAgent: "Agent de rechargement...", reloadingAgent: "Agent de rechargement...",
+2
View File
@@ -436,6 +436,7 @@ export const ru: TranslationResources = {
openFor: "Открыть меню для{{label}}", openFor: "Открыть меню для{{label}}",
copyResumeCommand: "Копировать команду возобновления", copyResumeCommand: "Копировать команду возобновления",
copyAgentId: "Скопировать идентификатор агента", copyAgentId: "Скопировать идентификатор агента",
copyFilePath: "Copy file path",
rename: "Переименовать", rename: "Переименовать",
closeAbove: "Закрыть вкладки выше", closeAbove: "Закрыть вкладки выше",
closeBelow: "Закройте вкладки ниже", closeBelow: "Закройте вкладки ниже",
@@ -468,6 +469,7 @@ export const ru: TranslationResources = {
copyFailed: "Не удалось скопировать", copyFailed: "Не удалось скопировать",
agentIdCopiedLabel: "AgentID", agentIdCopiedLabel: "AgentID",
resumeCommandCopiedLabel: "команда возобновления", resumeCommandCopiedLabel: "команда возобновления",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "Резюме ID недоступно", resumeIdUnavailable: "Резюме ID недоступно",
resumeCommandUnavailable: "Команда возобновления недоступна", resumeCommandUnavailable: "Команда возобновления недоступна",
reloadingAgent: "Перезагрузка агента...", reloadingAgent: "Перезагрузка агента...",
+2
View File
@@ -433,6 +433,7 @@ export const zhCN: TranslationResources = {
openFor: "打开 {{label}} 的菜单", openFor: "打开 {{label}} 的菜单",
copyResumeCommand: "复制恢复命令", copyResumeCommand: "复制恢复命令",
copyAgentId: "复制 Agent ID", copyAgentId: "复制 Agent ID",
copyFilePath: "Copy file path",
rename: "重命名", rename: "重命名",
closeAbove: "关闭上方标签", closeAbove: "关闭上方标签",
closeBelow: "关闭下方标签", closeBelow: "关闭下方标签",
@@ -465,6 +466,7 @@ export const zhCN: TranslationResources = {
copyFailed: "复制失败", copyFailed: "复制失败",
agentIdCopiedLabel: "Agent ID", agentIdCopiedLabel: "Agent ID",
resumeCommandCopiedLabel: "恢复命令", resumeCommandCopiedLabel: "恢复命令",
filePathCopiedLabel: "File path",
resumeIdUnavailable: "恢复 ID 不可用", resumeIdUnavailable: "恢复 ID 不可用",
resumeCommandUnavailable: "恢复命令不可用", resumeCommandUnavailable: "恢复命令不可用",
reloadingAgent: "正在重新加载 Agent...", reloadingAgent: "正在重新加载 Agent...",
@@ -205,6 +205,7 @@ interface WorkspaceDesktopTabsRowProps {
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void; onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -519,6 +520,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTab, onCloseTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTabsToLeft, onCloseTabsToLeft,
@@ -590,6 +592,7 @@ export function WorkspaceDesktopTabsRow({
() => ({ () => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"), copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"), copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"), rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"), closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"), closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -684,6 +687,7 @@ export function WorkspaceDesktopTabsRow({
normalizedWorkspaceId={normalizedWorkspaceId} normalizedWorkspaceId={normalizedWorkspaceId}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft} onCloseTabsToLeft={onCloseTabsToLeft}
@@ -714,6 +718,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand, onCopyResumeCommand,
onNavigateTab, onNavigateTab,
onReloadAgent, onReloadAgent,
@@ -894,6 +899,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId, normalizedWorkspaceId,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTabsToLeft, onCloseTabsToLeft,
@@ -919,6 +925,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId: string; normalizedWorkspaceId: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void; onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -944,6 +951,7 @@ function ResolvedDesktopTabChip({
tabCount, tabCount,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -960,6 +968,7 @@ function ResolvedDesktopTabChip({
onCloseTabsToLeft, onCloseTabsToLeft,
onCloseTabsToRight, onCloseTabsToRight,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand, onCopyResumeCommand,
labels, labels,
onReloadAgent, onReloadAgent,
@@ -365,6 +365,7 @@ interface MobileWorkspaceTabSwitcherProps {
onSelectSwitcherTab: (key: string) => void; onSelectSwitcherTab: (key: string) => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
@@ -552,6 +553,7 @@ function MobileWorkspaceTabOption({
onPress, onPress,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -569,6 +571,7 @@ function MobileWorkspaceTabOption({
onPress: () => void; onPress: () => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
@@ -581,6 +584,7 @@ function MobileWorkspaceTabOption({
() => ({ () => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"), copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"), copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"), rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"), closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"), closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -602,6 +606,7 @@ function MobileWorkspaceTabOption({
menuTestIDBase, menuTestIDBase,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -668,6 +673,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onSelectSwitcherTab, onSelectSwitcherTab,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -723,6 +729,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onPress={onPress} onPress={onPress}
onCopyResumeCommand={onCopyResumeCommand} onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId} onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent} onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab} onRenameTab={onRenameTab}
onCloseTab={onCloseTab} onCloseTab={onCloseTab}
@@ -740,6 +747,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
normalizedWorkspaceId, normalizedWorkspaceId,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -2600,6 +2608,19 @@ function WorkspaceScreenContent({
[toast, t], [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( const handleCopyResumeCommand = useCallback(
async (agentId: string) => { async (agentId: string) => {
if (!agentId) return; if (!agentId) return;
@@ -3422,6 +3443,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent} onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab} onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeftInPane} onCloseTabsToLeft={handleCloseTabsToLeftInPane}
@@ -3456,6 +3478,7 @@ function WorkspaceScreenContent({
handleCloseTabById, handleCloseTabById,
handleCopyResumeCommand, handleCopyResumeCommand,
handleCopyAgentId, handleCopyAgentId,
handleCopyFilePath,
handleReloadAgent, handleReloadAgent,
handleRenameTab, handleRenameTab,
handleCloseTabsToLeftInPane, handleCloseTabsToLeftInPane,
@@ -3535,6 +3558,7 @@ function WorkspaceScreenContent({
onSelectSwitcherTab={handleSelectSwitcherTab} onSelectSwitcherTab={handleSelectSwitcherTab}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent} onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab} onRenameTab={handleRenameTab}
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
@@ -3556,6 +3580,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById} onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand} onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId} onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent} onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab} onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeft} onCloseTabsToLeft={handleCloseTabsToLeft}
@@ -15,6 +15,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
it("uses desktop tab ordering labels for desktop menus", () => { it("uses desktop tab ordering labels for desktop menus", () => {
const onCopyResumeCommand = vi.fn(); const onCopyResumeCommand = vi.fn();
const onCopyAgentId = vi.fn(); const onCopyAgentId = vi.fn();
const onCopyFilePath = vi.fn();
const onReloadAgent = vi.fn(); const onReloadAgent = vi.fn();
const onRenameTab = vi.fn(); const onRenameTab = vi.fn();
const onCloseTab = vi.fn(); const onCloseTab = vi.fn();
@@ -30,6 +31,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123", menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, onCloseTab,
@@ -59,6 +61,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-agent_123", menuTestIDBase: "workspace-tab-menu-agent_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab: vi.fn(), onRenameTab: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -93,6 +96,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-draft_123", menuTestIDBase: "workspace-tab-menu-draft_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab: vi.fn(), onRenameTab: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -120,6 +124,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123", menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab: vi.fn(), onRenameTab: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -148,6 +153,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123", menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab, onRenameTab,
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -181,6 +187,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-terminal_abc", menuTestIDBase: "workspace-tab-context-terminal_abc",
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab, onRenameTab,
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -193,6 +200,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(labels[0]).toBe("Rename"); expect(labels[0]).toBe("Rename");
expect(labels).not.toContain("Copy resume command"); expect(labels).not.toContain("Copy resume command");
expect(labels).not.toContain("Copy agent id"); expect(labels).not.toContain("Copy agent id");
expect(labels).not.toContain("Copy file path");
expect(labels).not.toContain("Reload agent"); expect(labels).not.toContain("Reload agent");
const renameEntry = entries.find((entry) => entry.kind === "item" && entry.label === "Rename"); const renameEntry = entries.find((entry) => entry.kind === "item" && entry.label === "Rename");
@@ -203,6 +211,48 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(onRenameTab).toHaveBeenCalledWith(terminalTab); 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", () => { it("uses the same rename entry shape for agent and terminal tabs", () => {
const terminalTab: WorkspaceTabDescriptor = { const terminalTab: WorkspaceTabDescriptor = {
key: "terminal_abc", key: "terminal_abc",
@@ -218,6 +268,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase, menuTestIDBase,
onCopyResumeCommand: vi.fn(), onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(), onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(), onReloadAgent: vi.fn(),
onRenameTab: vi.fn(), onRenameTab: vi.fn(),
onCloseTab: vi.fn(), onCloseTab: vi.fn(),
@@ -8,6 +8,7 @@ export type WorkspaceTabMenuSurface = "desktop" | "mobile";
export interface WorkspaceTabMenuLabels { export interface WorkspaceTabMenuLabels {
copyResumeCommand: string; copyResumeCommand: string;
copyAgentId: string; copyAgentId: string;
copyFilePath: string;
rename: string; rename: string;
closeAbove: string; closeAbove: string;
closeBelow: string; closeBelow: string;
@@ -22,6 +23,7 @@ export interface WorkspaceTabMenuLabels {
export const DEFAULT_WORKSPACE_TAB_MENU_LABELS: WorkspaceTabMenuLabels = { export const DEFAULT_WORKSPACE_TAB_MENU_LABELS: WorkspaceTabMenuLabels = {
copyResumeCommand: i18n.t("workspace.tabs.menu.copyResumeCommand"), copyResumeCommand: i18n.t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: i18n.t("workspace.tabs.menu.copyAgentId"), copyAgentId: i18n.t("workspace.tabs.menu.copyAgentId"),
copyFilePath: i18n.t("workspace.tabs.menu.copyFilePath"),
rename: i18n.t("workspace.tabs.menu.rename"), rename: i18n.t("workspace.tabs.menu.rename"),
closeAbove: i18n.t("workspace.tabs.menu.closeAbove"), closeAbove: i18n.t("workspace.tabs.menu.closeAbove"),
closeBelow: i18n.t("workspace.tabs.menu.closeBelow"), closeBelow: i18n.t("workspace.tabs.menu.closeBelow"),
@@ -66,6 +68,7 @@ interface BuildWorkspaceTabMenuEntriesInput {
menuTestIDBase: string; menuTestIDBase: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
@@ -81,6 +84,7 @@ interface BuildWorkspaceDesktopTabActionsInput {
tabCount: number; tabCount: number;
onCopyResumeCommand: (agentId: string) => Promise<void> | void; onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void; onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void; onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void; onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void; onCloseTab: (tabId: string) => Promise<void> | void;
@@ -148,6 +152,7 @@ export function buildWorkspaceTabMenuEntries(
menuTestIDBase, menuTestIDBase,
onCopyResumeCommand, onCopyResumeCommand,
onCopyAgentId, onCopyAgentId,
onCopyFilePath,
onReloadAgent, onReloadAgent,
onRenameTab, onRenameTab,
onCloseTab, 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") { if (tab.target.kind === "agent" || tab.target.kind === "terminal") {
entries.push({ entries.push({
kind: "item", kind: "item",
@@ -278,6 +297,7 @@ export function buildWorkspaceDesktopTabActions(
menuTestIDBase: contextMenuTestId, menuTestIDBase: contextMenuTestId,
onCopyResumeCommand: input.onCopyResumeCommand, onCopyResumeCommand: input.onCopyResumeCommand,
onCopyAgentId: input.onCopyAgentId, onCopyAgentId: input.onCopyAgentId,
onCopyFilePath: input.onCopyFilePath,
onReloadAgent: input.onReloadAgent, onReloadAgent: input.onReloadAgent,
onRenameTab: input.onRenameTab, onRenameTab: input.onRenameTab,
onCloseTab: input.onCloseTab, onCloseTab: input.onCloseTab,