From c5087d83daa0cbacb7a1feb096aa0ca217e7e39f Mon Sep 17 00:00:00 2001 From: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Date: Tue, 16 Jun 2026 00:04:01 +0530 Subject: [PATCH] feat(meet): in-call meeting agency (active replies, streaming, approvals) (#3677) Co-authored-by: Claude Fable 5 --- app/src/components/skills/MeetingBotsCard.tsx | 55 +- .../skills/__tests__/MeetingBotsCard.test.tsx | 37 +- app/src/lib/i18n/ar.ts | 3 + app/src/lib/i18n/bn.ts | 3 + app/src/lib/i18n/de.ts | 3 + app/src/lib/i18n/en.ts | 3 + app/src/lib/i18n/es.ts | 3 + app/src/lib/i18n/fr.ts | 3 + app/src/lib/i18n/hi.ts | 3 + app/src/lib/i18n/id.ts | 3 + app/src/lib/i18n/it.ts | 3 + app/src/lib/i18n/ko.ts | 3 + app/src/lib/i18n/pl.ts | 3 + app/src/lib/i18n/pt.ts | 3 + app/src/lib/i18n/ru.ts | 3 + app/src/lib/i18n/zh-CN.ts | 3 + src/core/event_bus/events.rs | 20 + src/openhuman/agent_meetings/bus.rs | 76 ++ src/openhuman/agent_meetings/calendar.rs | 72 +- src/openhuman/agent_meetings/in_call.rs | 1050 +++++++++++++++++ src/openhuman/agent_meetings/mod.rs | 2 + src/openhuman/agent_meetings/ops.rs | 162 ++- src/openhuman/agent_meetings/schemas.rs | 49 +- src/openhuman/approval/gate.rs | 221 +++- src/openhuman/approval/mod.rs | 5 +- src/openhuman/config/schema/meet.rs | 43 + src/openhuman/meet_agent/brain/mod.rs | 7 +- 27 files changed, 1813 insertions(+), 28 deletions(-) create mode 100644 src/openhuman/agent_meetings/in_call.rs diff --git a/app/src/components/skills/MeetingBotsCard.tsx b/app/src/components/skills/MeetingBotsCard.tsx index b357f0103..6d0d3fa47 100644 --- a/app/src/components/skills/MeetingBotsCard.tsx +++ b/app/src/components/skills/MeetingBotsCard.tsx @@ -202,7 +202,15 @@ function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) const { t } = useT(); const dispatch = useAppDispatch(); const [meetUrl, setMeetUrl] = useState(''); - const [respondTo] = useState(''); + // The participant the bot answers to (authorized speaker). Wired to the + // backend join payload as `respondToParticipant` → `respondTo`, which the + // meeting stream uses to gate in-call requests to this speaker only. + const [respondTo, setRespondTo] = useState(''); + // Active (respond when addressed) vs listen-only (transcribe only). Defaults + // to active; the bot still only replies after being addressed by the wake + // phrase. Forwarded to the backend as `listenOnly` and mirrored into the + // meet slice so the active view shows the right status. + const [listenOnly, setListenOnly] = useState(false); const personaDisplayName = useAppSelector(selectPersonaDisplayName); const personaDescription = useAppSelector(selectPersonaDescription); const selectedMascotId = useAppSelector(selectSelectedMascotId); @@ -234,7 +242,7 @@ function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) }, [refreshRecentCalls]); const selectedLabel = t('skills.meetingBots.platforms.gmeet'); - const agentName = personaDisplayName.trim() || 'OpenHuman'; + const agentName = personaDisplayName.trim() || 'Tiny'; const systemPrompt = personaDescription.trim() || undefined; const mascotId = selectedMascotId ?? (mascotColor === 'custom' ? undefined : mascotColor); const riveColors = @@ -264,7 +272,7 @@ function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) hasSubmittedRef.current = true; try { const meetingId = crypto.randomUUID(); - dispatch(setBackendMeetJoining({ meetUrl: meetUrl.trim(), meetingId })); + dispatch(setBackendMeetJoining({ meetUrl: meetUrl.trim(), meetingId, listenOnly })); await joinMeetViaBackendBot({ meetUrl, displayName: agentName, @@ -275,6 +283,7 @@ function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) riveColors, correlationId: meetingId, respondToParticipant: respondTo.trim() || undefined, + listenOnly, }); } catch (err) { const message = err instanceof Error ? err.message : t('skills.meetingBots.failedToStart'); @@ -315,6 +324,44 @@ function MeetingBotsInline({ onToast, hasSubmittedRef }: MeetingBotsInlineProps) /> + + + + {error && (