diff --git a/frontend/src/components/agents/AgentsHub.tsx b/frontend/src/components/agents/AgentsHub.tsx index e0c1c27e3..ccf1654e7 100644 --- a/frontend/src/components/agents/AgentsHub.tsx +++ b/frontend/src/components/agents/AgentsHub.tsx @@ -1333,7 +1333,10 @@ const AgentsHub = ({ currentPodId: propPodId = null }) => { if (!dmPodId) { throw new Error('DM pod not returned'); } - navigate(`/pods/agent-admin/${dmPodId}`); + // Stay in the shell that launched this — a v2 card must not eject the + // user into the legacy ChatRoom. Mirrors the install handoff above. + const isV2Context = typeof window !== 'undefined' && window.location.pathname.startsWith('/v2'); + navigate(isV2Context ? `/v2/pods/${dmPodId}` : `/pods/agent-admin/${dmPodId}`); } catch (err) { setError(err.response?.data?.message || 'Failed to open agent DM'); } @@ -1352,7 +1355,10 @@ const AgentsHub = ({ currentPodId: propPodId = null }) => { if (!roomPodId) { throw new Error('Agent room not returned'); } - navigate(`/pods/agent-room/${roomPodId}`); + // Stay in the shell that launched this — a v2 card must not eject the + // user into the legacy ChatRoom. Mirrors the install handoff above. + const isV2Context = typeof window !== 'undefined' && window.location.pathname.startsWith('/v2'); + navigate(isV2Context ? `/v2/pods/${roomPodId}` : `/pods/agent-room/${roomPodId}`); } catch (err) { setError(err.response?.data?.message || 'Failed to open agent room'); } diff --git a/frontend/src/v2/components/V2YourTeamPage.tsx b/frontend/src/v2/components/V2YourTeamPage.tsx index 0e61621e1..1e6053987 100644 --- a/frontend/src/v2/components/V2YourTeamPage.tsx +++ b/frontend/src/v2/components/V2YourTeamPage.tsx @@ -76,6 +76,9 @@ const V2YourTeamPage: React.FC = () => { // from the union of categories present on loaded agents so a sparse team // doesn't render empty filters. const [filter, setFilter] = useState('all'); + // Key (`name:instanceId`) of the agent whose 1:1 room is currently opening, + // so its "Talk to" button can show progress and block a double-submit. + const [opening, setOpening] = useState(null); useEffect(() => { let cancelled = false; @@ -144,6 +147,33 @@ const V2YourTeamPage: React.FC = () => { : sortedAgents.filter((a) => (a.category || 'Uncategorized') === filter) ), [sortedAgents, filter]); + // Open the coached 1:1 (agent-room) for this agent — the same surface the + // post-install handoff lands on. Without this, "talk to your agent" is only + // reachable from the project pod (a group), so the 1:1 relationship a user + // forms at install has no entry point on their primary team surface. + const handleTalkTo = async (a: AgentInstallationSummary, e: React.MouseEvent) => { + e.stopPropagation(); + const key = `${a.name}:${a.instanceId}`; + setOpening(key); + setError(null); + try { + const token = localStorage.getItem('token'); + const headers = token ? { Authorization: `Bearer ${token}` } : undefined; + const res = await axios.post<{ room?: { _id?: string } }>( + '/api/agents/runtime/room', + { agentName: a.name, instanceId: a.instanceId || 'default', podId: a.podId || undefined }, + { headers }, + ); + const roomId = res.data?.room?._id; + if (!roomId) throw new Error('Agent room not returned'); + navigate(`/v2/pods/${roomId}`); + } catch (err: unknown) { + const resp = (err as { response?: { data?: { message?: string } } })?.response; + setError(resp?.data?.message || 'Could not open a 1:1 with this agent — try again.'); + setOpening(null); + } + }; + return (
@@ -221,12 +251,14 @@ const V2YourTeamPage: React.FC = () => { const podLabel = a.podName || 'Untitled project'; const runtimeLabel = formatRuntime(a); const lastSeen = formatRelative(a.lastHeartbeatAt); + const cardKey = `${a.name}:${a.instanceId}`; + const isOpening = opening === cardKey; const onCardClick = () => { if (a.podId) navigate(`/v2/pods/${a.podId}`); }; return (
{ {lastSeen}
+ ); })} diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 24a059d7c..cfd9bdf14 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -4359,6 +4359,34 @@ background: #25c281; } +/* Specificity-matched against the global .v2-root button:not(.MuiButtonBase-root) + reset (0,0,2,1) — prefix with .v2-root button.X so source order wins. Same + pattern as .v2-chat__send / .v2-pods__item--active. */ +.v2-root button.v2-team-card__talk { + align-self: center; + flex-shrink: 0; + display: inline-flex; + align-items: center; + height: 32px; + padding: 0 14px; + font-size: 13px; + font-weight: 600; + color: var(--v2-accent); + background: transparent; + border: 1px solid var(--v2-accent); + border-radius: var(--v2-radius-sm); + white-space: nowrap; + transition: background 80ms ease, color 80ms ease; +} +.v2-root button.v2-team-card__talk:hover { + background: var(--v2-accent); + color: #fff; +} +.v2-root button.v2-team-card__talk:disabled { + opacity: 0.6; + cursor: default; +} + @media (max-width: 760px) { .v2-team { padding: 20px 16px 48px; } .v2-team__header { flex-direction: column; }