diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx
index c725c85e..9309665d 100644
--- a/frontend/src/app/components/Layout/AppShell.tsx
+++ b/frontend/src/app/components/Layout/AppShell.tsx
@@ -26,6 +26,7 @@ import { ackRun, runWorkflowNow } from '@/shared/state/workflowsSlice';
import { setPendingBrowserUrl } from '@/shared/state/tempStateSlice';
import { fetchOutputs } from '@/shared/state/outputsSlice';
import UpdateReadyPill from '@/app/components/Layout/UpdateReadyPill';
+import ShareRequestHost from '@/app/components/share/ShareRequestHost';
import { findBrowserByWebContentsId } from '@/shared/browserRegistry';
import { byPreviewRecency } from '@/shared/previewOrder';
import { useClaudeTokens, useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext';
@@ -574,6 +575,8 @@ const AppShell: React.FC = () => {
+
+
);
};
diff --git a/frontend/src/app/components/share/ShareRequestHost.tsx b/frontend/src/app/components/share/ShareRequestHost.tsx
new file mode 100644
index 00000000..f80705ab
--- /dev/null
+++ b/frontend/src/app/components/share/ShareRequestHost.tsx
@@ -0,0 +1,27 @@
+import React, { useEffect, useState } from 'react';
+import ShareModal from './ShareModal';
+import type { ShareTarget } from './shareTypes';
+
+export const SHARE_REQUEST_EVENT = 'openswarm:share-entity';
+
+export function requestShare(target: ShareTarget): void {
+ window.dispatchEvent(new CustomEvent(SHARE_REQUEST_EVENT, { detail: target }));
+}
+
+/** One global mount that turns share requests from stateless menu rows (card context menu, dock
+ * tiles) into the ShareModal; the row can't own modal state because the menu unmounts on click. */
+const ShareRequestHost: React.FC = () => {
+ const [target, setTarget] = useState(null);
+ useEffect(() => {
+ const onShare = (e: Event): void => {
+ const detail = (e as CustomEvent).detail as ShareTarget | undefined;
+ if (detail && detail.kind && detail.id) setTarget(detail);
+ };
+ window.addEventListener(SHARE_REQUEST_EVENT, onShare);
+ return () => window.removeEventListener(SHARE_REQUEST_EVENT, onShare);
+ }, []);
+ if (!target) return null;
+ return setTarget(null)} />;
+};
+
+export default ShareRequestHost;
diff --git a/frontend/src/app/components/share/shareTypes.ts b/frontend/src/app/components/share/shareTypes.ts
index 737efcf8..dff1bb45 100644
--- a/frontend/src/app/components/share/shareTypes.ts
+++ b/frontend/src/app/components/share/shareTypes.ts
@@ -1,6 +1,6 @@
// Shared types for the .swarm share/import UI. The *Response shapes mirror the backend pydantic models in backend/apps/swarm/models.py; keep them in sync.
-export type ShareKind = 'skill' | 'app' | 'workflow' | 'dashboard';
+export type ShareKind = 'skill' | 'app' | 'workflow' | 'dashboard' | 'session';
export interface ShareTarget {
kind: ShareKind;
diff --git a/frontend/src/app/pages/Dashboard/cards/agentCardMenuRows.ts b/frontend/src/app/pages/Dashboard/cards/agentCardMenuRows.ts
index c34e591a..4c2f05d3 100644
--- a/frontend/src/app/pages/Dashboard/cards/agentCardMenuRows.ts
+++ b/frontend/src/app/pages/Dashboard/cards/agentCardMenuRows.ts
@@ -1,6 +1,9 @@
import { clearSessionMessages, deleteSession, duplicateSession, expandSession, collapseSession, stopAgent, fetchSession, type AgentSession } from '@/shared/state/agentsSlice';
import { placeCard, bringToFront } from '@/shared/state/dashboardLayoutSlice';
import { setClipboardCards } from '@/shared/dashboardClipboard';
+import { copySessionResponse } from '@/shared/copySessionResponse';
+import { requestShare } from '@/app/components/share/ShareRequestHost';
+import { displaySessionName } from '@/shared/state/sessionDisplay';
import { handleSlashCommand } from '@/app/pages/AgentChat/ChatInput/hooks/slashCommands';
import type { AppDispatch } from '@/shared/state/store';
import type { CardMenuRow } from '../desktop/openCardContextMenu';
@@ -46,6 +49,8 @@ export function agentCardMenuRows({ session, dispatch, expanded, tileZone, expan
x: card.x, y: card.y, width: card.width, height: card.height, expanded,
}]),
},
+ { label: 'Copy response', onClick: () => { copySessionResponse(session.id); } },
+ { label: 'Share as .swarm…', onClick: () => requestShare({ kind: 'session', id: session.id, name: displaySessionName(session.name) }) },
{ kind: 'separator' },
{ kind: 'header', label: 'Session' },
{ label: 'Stop turn', disabled: !running, onClick: () => { void dispatch(stopAgent({ sessionId: session.id })); } },
diff --git a/frontend/src/app/pages/Dashboard/desktop/dockTileMenuRows.ts b/frontend/src/app/pages/Dashboard/desktop/dockTileMenuRows.ts
index b81bf079..d77d5079 100644
--- a/frontend/src/app/pages/Dashboard/desktop/dockTileMenuRows.ts
+++ b/frontend/src/app/pages/Dashboard/desktop/dockTileMenuRows.ts
@@ -1,5 +1,7 @@
import { bringToFront, recordClosedCard, removeCard, removeWorkflowCard } from '@/shared/state/dashboardLayoutSlice';
import { closeSession } from '@/shared/state/agentsSlice';
+import { copySessionResponse } from '@/shared/copySessionResponse';
+import { requestShare } from '@/app/components/share/ShareRequestHost';
import { removeBrowserCardCleanly } from '@/shared/browserTeardown';
import { removeViewCardCleanly } from '@/shared/viewTeardown';
import type { AppDispatch } from '@/shared/state/store';
@@ -10,6 +12,10 @@ export function dockTileMenuRows(entry: DockEntry, dispatch: AppDispatch, onFocu
return [
{ label: 'Show on canvas', onClick: onFocus },
{ label: 'Bring to front', onClick: () => dispatch(bringToFront({ id: entry.id, type: entry.kind })) },
+ ...(entry.kind === 'agent' ? [
+ { label: 'Copy response', onClick: () => { copySessionResponse(entry.id); } } as CardMenuRow,
+ { label: 'Share as .swarm…', onClick: () => requestShare({ kind: 'session', id: entry.id, name: entry.label }) } as CardMenuRow,
+ ] : []),
{ kind: 'separator' },
{
label: 'Close',
diff --git a/frontend/src/shared/copySessionResponse.ts b/frontend/src/shared/copySessionResponse.ts
new file mode 100644
index 00000000..54b6e6cb
--- /dev/null
+++ b/frontend/src/shared/copySessionResponse.ts
@@ -0,0 +1,13 @@
+import { store } from '@/shared/state/store';
+
+/** Copy the chat's latest assistant answer to the clipboard; true when something was copied.
+ * Menu rows (card context menu, dock tiles) call this statelessly. */
+export function copySessionResponse(sessionId: string): boolean {
+ const session = store.getState().agents.sessions[sessionId];
+ const last = [...(session?.messages ?? [])]
+ .reverse()
+ .find((m) => m.role === 'assistant' && typeof m.content === 'string' && m.content.trim());
+ if (!last) return false;
+ void navigator.clipboard.writeText(last.content as string);
+ return true;
+}