Skip to content

feat(agent_meetings): wire mascotId through full stack + fix tests#3363

Open
YellowSnnowmann wants to merge 16 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/recall-agent-customization-sync
Open

feat(agent_meetings): wire mascotId through full stack + fix tests#3363
YellowSnnowmann wants to merge 16 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/recall-agent-customization-sync

Conversation

@YellowSnnowmann
Copy link
Copy Markdown
Contributor

@YellowSnnowmann YellowSnnowmann commented Jun 4, 2026

Summary

Closes #3280

  • mascotId wiring (TS → Rust → Socket.IO): Added mascotId?: string to BackendMeetJoinInput, passes mascot_id RPC param through joinMeetViaBackendBot → Rust BackendMeetJoinRequestbot:join Socket.IO payload → backend createRecallBotSession → render URL ?id=<mascotId>.
  • Rust schema sync: Added missing agent_name, system_prompt, mascot_id, and rive_colors fields to schema_join() in schemas.rs so the controller schema contract matches ops.rs.
  • Redux dispatch on join: MeetingBotsCard now dispatches setBackendMeetJoining before calling joinMeetViaBackendBot, so the UI reflects the joining state immediately.
  • Test fixes: backendMeetService.test.ts strict assertion updated to include all 7 params; added 7 new tests covering mascotId, agentName, systemPrompt, riveColors, whitespace trimming, and all-fields-together case.
  • CodeRabbit fixes: leaving guard on Leave button; rive_colors skipped when both colors empty; transcript_turns_to_chat_batch overflow fixed with safe arithmetic.
  • Test coverage: Added ActiveMeetingView tests (7 cases), MascotFrameProducer render tests, backendMeet reducer in test store, matchMedia + listen polyfills in setup.

What's covered from #3280

  • mascotId threads end-to-end: TS service → Rust RPC → Socket.IO → backend
  • agentName and systemPrompt wired
  • riveColors wired
  • ✅ Schema contract updated
  • ✅ Test coverage for all new fields

Test plan

  • Run pnpm test — all backendMeetService + MeetingBotsCard + MascotFrameProducer tests pass
  • Run cargo test — Rust unit tests for agent_meetings pass

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced Meeting Bots UI with live meeting status display and leave functionality.
    • Added bot customization options for agent names, system prompts, mascot IDs, and appearance colors.
    • Improved canvas rendering diagnostics and monitoring capabilities.
    • Meeting transcripts now integrated into memory pipeline.
  • Bug Fixes

    • Fixed usage limit warning to suppress when chat workloads are fully routed away.
  • Localization

    • Added translations for onboarding errors and Meeting Bots UI across all supported languages.

YellowSnnowmann and others added 7 commits June 3, 2026 12:53
…near-limit banner for custom providers (tinyhumansai#3097)

Issue 3 — Top-up banner shown despite custom provider:
`isNearLimit` was not guarded by `isFullyRoutedAway`, so users who
routed all CHAT_WORKLOADS away from OpenHuman still saw the near-limit
warning when background workloads kept billing data flowing.
Fix: add `!isFullyRoutedAway` to `isNearLimit` (mirrors the existing
guards on `isBudgetExhausted` / `isAtLimit`). Adds a regression test.

Issue 4 — Onboarding reset loop on Windows:
`RuntimeChoicePage` silently swallowed `completeAndExit()` failures
(only logging to console), leaving the user stuck on the runtime-choice
screen with no feedback or retry path. If they navigated back to the
welcome page, the loop repeated indefinitely.
Fix: adopt the `exitError` state pattern from `VaultSetupStep` — catch
the error, set `exitError`, and render a coral error banner with a
localised message so the user knows to retry. Adds i18n key to all 13
locale files.

Issues 1 & 2 (credit drain during setup / 9-connection limit) are
architectural: background workloads route through OpenHuman Cloud by
design even with a custom chat provider, and the connection limit is
enforced server-side by the upstream Composio API. Both are called out
in the PR description for product visibility.
…ough Rust core to backend bot:join

- Add RiveColors struct and agent_name/system_prompt/rive_colors fields to BackendMeetJoinRequest (types.rs)
- Thread optional fields into the bot:join socket emit payload in handle_join (ops.rs)
- Expose agentName/systemPrompt/riveColors in BackendMeetJoinInput and forward as snake_case RPC params in joinMeetViaBackendBot (meetCallService.ts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…backend bot:join

- Add RiveColors struct with optional primaryColor/secondaryColor
- Extend BackendMeetJoinRequest with agent_name, system_prompt, rive_colors
- Update ops.rs and event_handlers.rs to pass new params through Rust core
- Update meetCallService.ts and MeetingBotsCard to supply custom agent params
- Update tests for new payload shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…api client

ngrok free tier shows an interstitial browser warning page on first request.
This causes the preflight health check to fail, making the Google login button
appear unresponsive. Adding the header bypasses the warning page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ground

- Thread mascotId through full stack: BackendMeetJoinInput (TS) →
  agent_meetings_join RPC params → Rust BackendMeetJoinRequest →
  bot:join Socket.IO payload → backend recallBotService
- Add agent_name, system_prompt, mascot_id, rive_colors to schemas.rs
  join input schema (was missing from previous commit)
- Fix MeetingBotsCard: add missing leaveBackendMeetBot + Redux imports,
  wire dispatch(setBackendMeetJoining) before bot join call,
  pass agentName: displayName to joinMeetViaBackendBot
- Fix backendMeetService.test.ts: update strict assertion to include
  all new params; add mascotId, agentName, systemPrompt, riveColors
  coverage tests (14 tests total)

Closes tinyhumansai#3280

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YellowSnnowmann YellowSnnowmann requested a review from a team June 4, 2026 11:37
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds backend-orchestrated meeting-bot joins and transcript ingestion; rewrites MeetingBots UI to Redux-driven active view; instruments mascot camera and producer with 1280×720 capture, canvas-luma probes, WebRTC monkey-patching, outbound RTP stats, extended host diagnostics, and related tests/locales.

Changes

Backend Meeting Bot Integration with Mascot

Layer / File(s) Summary
Backend Meet Request Types & Schema
src/openhuman/agent_meetings/types.rs, src/openhuman/agent_meetings/schemas.rs
RiveColors type and BackendMeetJoinRequest extended with optional agent_name, system_prompt, mascot_id, rive_colors; controller schema accepts these fields.
Backend Meeting Ops & Transcript Ingestion
src/openhuman/agent_meetings/ops.rs, src/openhuman/socket/event_handlers.rs
Adds transcript_turns_to_chat_batch and ingest_backend_meeting_transcript() to convert turns into a ChatBatch and ingest; bot:join emit payload now conditionally includes customization fields and bot:transcript spawns ingestion.
Frontend Service & Redux Slice
app/src/services/meetCallService.ts, app/src/store/backendMeetSlice.ts
BackendMeetJoinInput gains optional bot customization (agentName, systemPrompt, mascotId, riveColors); joinMeetViaBackendBot forwards snake_cased params; BackendMeetState exported and selectors added; MeetingBotsCard mounted on Skills page.
Frontend Service Tests
app/src/services/__tests__/meetCallService.test.ts, app/src/services/backendMeetService.test.ts
Tests validate camelCase→snake_case mapping, URL trimming/validation, and forwarding of agentName/systemPrompt/mascotId/riveColors as expected.
Meeting Bot Card Component & UI
app/src/components/skills/MeetingBotsCard.tsx
Switches to Redux-driven ActiveMeetingView for joining/active states, computes mascot face/status from meet state, removes owner-name privacy input, and dispatches setBackendMeetJoining then joinMeetViaBackendBot.
Meeting Bot Card Tests
app/src/components/skills/__tests__/MeetingBotsCard.test.tsx
Mocks joinMeetViaBackendBot/leaveBackendMeetBot, removes owner-name form interactions, and adds UI coverage for platform labels and active-meeting flows.

Video Capture & Rendering Diagnostics

Layer / File(s) Summary
Camera Bridge Diagnostics Infrastructure
app/src-tauri/src/meet_video/camera_bridge.js
Increases capture canvas to 1280×720; adds diagnostics state: lastRemoteBitmapInfo, lastDrawSource, lastCanvasProbe, outboundVideoStats, and peer-connections tracking.
Render Path & Draw Source Tracking
app/src-tauri/src/meet_video/camera_bridge.js
Tracks active draw source (cold-start/remote/fallback), probes canvas pixels (luma avg/min/max and dark/bright counts) on remote and fallback paths, and sets fake track contentHint = 'motion'.
WebRTC Patching & Outbound Stats
app/src-tauri/src/meet_video/camera_bridge.js
Adds mascot-track creation, patches getUserMedia, RTCPeerConnection.addTrack/addTransceiver/getSenders, and RTCRtpSender.replaceTrack; sanitizes sendEncodings and periodically collects outbound RTP video stats and video-element luma probes.
Camera Bridge Info API Extension
app/src-tauri/src/meet_video/camera_bridge.js
window.__openhumanCameraBridgeInfo() now returns lastRemoteBitmapInfo, lastDrawSource, canvasProbe, outboundVideoStats, videoTrack details (or null), and videoElements probe results.
Diagnostic Log Extraction & Output
app/src-tauri/src/meet_video/inject.rs
spawn_diagnostics_poller parses extended bridge JSON to extract lastDrawSource, canvas luma stats, remote bitmap dims/bytes, outbound encoding stats, videoElements, and formats a mascot video summary for diagnostics logs.
Mascot Frame Producer Pixel Probing
app/src/features/meet/MascotFrameProducer.tsx
Adds sampleCanvasPixels helper, mirrors speaking state into a ref, and throttles producer-pixel-probe JSON messages (with requestId, sizes, JPEG bytes, isSpeaking, probe) sent before JPEG frames.

Onboarding Error Handling & Localization

Layer / File(s) Summary
Runtime Choice Page Error Handling & Tests
app/src/pages/onboarding/pages/RuntimeChoicePage.tsx, app/src/pages/onboarding/pages/RuntimeChoicePage.test.tsx
Adds exitError state, catches completeAndExit() failures and surfaces a translated error banner; tests cover redirect, custom/cloud flows, and error rendering.
Localization: Onboarding & Meeting Bots
app/src/lib/i18n/{ar,bn,de,en,es,fr,hi,id,it,ko,pl,pt,ru,zh-CN}.ts
Adds onboarding.runtimeChoice.exitError and skills.meetingBots.* keys across locale files.

Usage State Gating & Minor Cleanup

Layer / File(s) Summary
Usage State Near-Limit Suppression
app/src/hooks/useUsageState.ts, app/src/hooks/useUsageState.test.ts
isNearLimit now also requires !isFullyRoutedAway; adds a test verifying suppression when chat is fully routed away but background workloads remain.
Lint Cleanup, Headers, & Test Setup
app/src/components/intelligence/PixiGraph.tsx, app/src/components/skills/SkillsRunnerBody.tsx, app/src/services/apiClient.ts, app/src/services/backendHealth.ts, app/src/pages/Skills.tsx, app/src/test/setup.ts, app/src/test/test-utils.tsx
Removes react-hooks/exhaustive-deps suppressions, adds ngrok-skip-browser-warning: '1' header to ApiClient and health check, polyfills matchMedia for tests, wires backendMeet reducer into test utils, and re-enables MeetingBotsCard on Skills page with onToast wiring.

Estimated code review effort
🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • tinyhumansai/openhuman#3278: Shares onboarding/runtime-choice exitError and useUsageState near-limit gating changes referenced in this PR.
  • tinyhumansai/openhuman#3034: Related edits around MeetingBotsCard/MeetingBotsModal owner-name handling and persona prefill behavior.
  • tinyhumansai/openhuman#3182: Overlapping backend-meet Socket.IO/join and frontend backend-meet wiring used by joinMeetViaBackendBot.

Suggested reviewers

  • senamakel
  • sanil-23
  • oxoxDev

"🐰 I hopped in with a tiny cam,
I counted pixels, probed each frame,
Mascot joins and transcripts flow,
Tests pass by moonlight’s gentle glow,
Cheers from a jittery rabbit fam!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(agent_meetings): wire mascotId through full stack + fix tests' accurately summarizes the primary focus of wiring mascotId across the stack with supporting test improvements.
Linked Issues check ✅ Passed The changes comprehensively implement the primary objectives from #3280: Recall.ai bot backend integration, frontend join controls, mascot integration, backend/frontend state sync, transcript ingestion, error handling, and test coverage including MascotFrameProducer coverage gains.
Out of Scope Changes check ✅ Passed All changes directly support the linked objective of wiring mascotId through the full stack, with necessary supporting changes: i18n additions for new UI labels, Redux slice exports for state management, test infrastructure improvements, and diagnostic/probing enhancements for monitoring bot health.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. working A PR that is being worked on by the team. labels Jun 4, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
app/src/components/skills/MeetingBotsCard.tsx (3)

81-98: ⚡ Quick win

Emotion parsing is brittle.

Lines 91-96: The substring-matching logic for emotion keywords (e.g., e.includes('happy'), e.includes('celebrat')) is fragile. Partial matches could yield false positives (e.g., "unhappy" matches "happy"), and the order of checks determines precedence without clear rationale.

More robust emotion mapping

Use word boundaries or exact-match sets:

function faceFromMeetState(
  status: BackendMeetStatus,
  lastReply: BackendMeetReplyEvent | null,
  lastHarness: BackendMeetHarnessEvent | null,
): MascotFace {
  if (status === 'joining') return 'thinking';
  if (status === 'error') return 'concerned';
  if (status === 'ended') return 'happy';
  if (lastHarness) return 'thinking';
  if (lastReply) {
    const emotion = (lastReply.emotion ?? '').toLowerCase();
    const happyWords = ['happy', 'pleased', 'joy', 'excited'];
    const celebrateWords = ['celebrating', 'proud'];
    const concernedWords = ['concerned', 'worried', 'unsure'];
    const curiousWords = ['curious', 'interested'];
    
    if (happyWords.some(w => emotion.includes(w))) return 'happy';
    if (celebrateWords.some(w => emotion.includes(w))) return 'celebrating';
    if (concernedWords.some(w => emotion.includes(w))) return 'concerned';
    if (curiousWords.some(w => emotion.includes(w))) return 'curious';
  }
  return 'idle';
}

Alternatively, expect the backend to send a constrained set of emotion values and map directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/skills/MeetingBotsCard.tsx` around lines 81 - 98, The
emotion parsing in faceFromMeetState is brittle because it uses naive substring
checks on lastReply.emotion (e.g., e.includes('happy')) which causes false
positives like "unhappy" and unclear precedence; fix it by normalizing the
emotion string (toLowerCase), tokenizing or matching with word-boundary regex,
and mapping against explicit word lists or an exact-value map (e.g., happyWords
= ['happy','pleased','joy','excited'], celebrateWords = ['celebrating','proud'],
etc.) using token/word-boundary checks (or exact match) instead of raw includes,
and ensure the check order is deliberate and documented so each emotion branch
in faceFromMeetState returns the intended MascotFace.

116-122: ⚡ Quick win

Leave button not disabled during async call.

handleLeave is async (line 116) but the button's disabled state (line 143) only checks canLeave (derived from status). A user could click "Leave" multiple times before the async call resolves, potentially sending duplicate bot:leave events.

Track leaving state
+const [leaving, setLeaving] = useState(false);

 const handleLeave = async () => {
+  setLeaving(true);
   try {
     await leaveBackendMeetBot('user-requested');
   } catch (err) {
     onToast?.({ type: 'error', title: t('skills.meetingBots.couldNotStartTitle'), message: String(err) });
+  } finally {
+    setLeaving(false);
   }
 };

-{canLeave && (
+{canLeave && !leaving && (
   <button type="button" onClick={handleLeave}
-    className="...">
+    disabled={leaving}
+    className="... disabled:opacity-50 disabled:cursor-not-allowed">
     {t('skills.meetingBots.leaveButton')}
   </button>
 )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/skills/MeetingBotsCard.tsx` around lines 116 - 122, The
Leave button can be clicked multiple times because handleLeave is async but the
disabled state only depends on canLeave; add a local state flag (e.g., leaving)
in the MeetingBotsCard component, set leaving = true at the start of handleLeave
and reset it in a finally block, guard the handler to return early if leaving is
already true, and update the button's disabled prop to include the leaving flag
(disabled when !canLeave or leaving) so duplicate bot:leave events cannot be
emitted.

293-300: 💤 Low value

agentName duplicates displayName—clarify intent.

Line 300 passes agentName: displayName. This means the bot's display name and the agent name sent to the backend are always identical in this flow. If that's intentional (the bot represents the agent), it's fine. If they should diverge (e.g., separate mascot identity vs. UI label), consider making agentName its own state or deriving it from persona/mascot config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/skills/MeetingBotsCard.tsx` around lines 293 - 300, The
current call to joinMeetViaBackendBot passes agentName: displayName which
duplicates UI label and backend agent identity; decide intended behavior and
make agentName explicit: either keep it intentionally identical (leave as is) or
create a separate variable/state (e.g., agentNameFromPersona or mascotAgentName)
derived from the persona/mascot config or a new input, then pass that variable
to joinMeetViaBackendBot({ meetUrl, displayName, platform, agentName }) and
update any places that assume agentName === displayName (such as
setBackendMeetJoining or the backend payload) so the UI label (displayName) and
backend agent identity (agentName) can diverge if needed.
src/openhuman/agent_meetings/ops.rs (1)

166-190: 💤 Low value

Consider consolidating optional field insertion.

The repeated if let Some(map) = join_payload.as_object_mut() pattern (lines 171-189) is functional but verbose. Each optional field requires a separate nested block.

Optional refactor

Build all fields upfront with Option handling in the json! macro:

let join_payload = json!({
    "meetUrl": normalized_url.as_str(),
    "displayName": display_name,
    "platform": platform,
    "agentName": req.agent_name.as_deref(),
    "systemPrompt": req.system_prompt.as_deref(),
    "mascotId": req.mascot_id.as_deref(),
    "riveColors": req.rive_colors.as_ref().map(|c| json!({
        "primaryColor": c.primary_color,
        "secondaryColor": c.secondary_color,
    })),
});

Then use serde_json to strip null values before emitting (or accept that Socket.IO tolerates null for omitted fields).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/agent_meetings/ops.rs` around lines 166 - 190, The repeated
mutation of join_payload via join_payload.as_object_mut() is verbose; instead
construct join_payload in one json! call using Option-friendly values (e.g., set
"agentName": req.agent_name.as_deref(), "systemPrompt":
req.system_prompt.as_deref(), "mascotId": req.mascot_id.as_deref(), and
"riveColors": req.rive_colors.as_ref().map(|c| json!({ "primaryColor":
c.primary_color, "secondaryColor": c.secondary_color }))) so all optional fields
are included conditionally, and then either accept nulls or run a small
serde_json cleanup to remove nulls before sending; apply this change where
join_payload is created and remove the subsequent as_object_mut() insertion
blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src-tauri/src/meet_video/camera_bridge.js`:
- Around line 417-429: probeVideoElements() currently exposes track.label (via
the track object created in camera_bridge.js) which can leak PII; replace the
raw label field with a redacted flag (e.g., isMascotTrack) computed client-side
instead of sending full label, and update the Rust-side matcher to read
isMascotTrack rather than label. Specifically, stop including track.label in the
object created in probeVideoElements() (and the second occurrence around lines
574-585), add a boolean field isMascotTrack (derived from a safe heuristic or by
calling into the Rust matcher API) and ensure
window.__openhumanCameraBridgeInfo() payloads use that boolean; then update the
Rust matcher to match on isMascotTrack instead of expecting label text.
- Around line 500-514: The current patch in
NativeRTCPeerConnection.prototype.addTransceiver rewrites kind-only calls
addTransceiver('video', ...) even when the transceiver is receive-only; update
the guard so we only substitute a mascot when the call actually will create a
sender (i.e., the trackOrKind is a MediaStreamTrack that is video OR trackOrKind
=== 'video' AND the init.direction permits sending). Concretely, change the
condition that uses isVideoTransceiverInit/init to require init.direction !==
'recvonly' (and not 'inactive') or otherwise check that the effective direction
will include "send" before calling makeMascotTrack() and
sanitizeVideoSenderInit(); keep the rest of the flow (origAddTransceiver.call
with nextTrackOrKind/nextInit) unchanged and reference the functions
NativeRTCPeerConnection.prototype.addTransceiver, isVideoTransceiverInit,
makeMascotTrack, and sanitizeVideoSenderInit when making the update.

In `@app/src/pages/onboarding/pages/RuntimeChoicePage.tsx`:
- Line 45: The current console.error in the RuntimeChoicePage (the
"completeAndExit failed" log) prints the full err object and may leak PII;
update the error logging in the completeAndExit handler (inside
RuntimeChoicePage component) to only emit a redacted/safe summary — e.g., derive
a short message from err.message (or a sanitized helper like
redactError/safeLogError), strip or mask emails/URLs/IDs and omit stack traces,
then log that string with context instead of the raw err object. Ensure the new
log includes the same contextual prefix ("[onboarding:runtime-choice-page]
completeAndExit failed") but only the sanitized error summary.

In `@app/src/services/meetCallService.ts`:
- Around line 199-204: The rive_colors object is being forwarded even when
primaryColor/secondaryColor are empty or whitespace; in the block that builds
rive_colors (using input.riveColors and fields primaryColor/secondaryColor) trim
both strings, treat empty or whitespace-only results as undefined, and only
include rive_colors if at least one of the trimmed values is non-undefined;
update the logic around input.riveColors -> rive_colors (and use temporary
normalizedPrimary/normalizedSecondary or similar) so you don't send an
effectively empty rive_colors object.

In `@src/openhuman/agent_meetings/ops.rs`:
- Around line 28-52: The function transcript_turns_to_chat_batch currently
swallows duration overflow and uses 1ms per-index spacing; change its signature
to return Result<Option<ChatBatch>, String> and validate duration_ms with
checked conversion (return Err on overflow) instead of unwrap_or(i64::MAX);
compute a realistic per-turn spacing_ms as duration_ms.checked_div(turns.len()
as u64).unwrap_or(1) (handle turns.is_empty() -> 0) and use checked_mul(idx as
u64, then convert to i64 safely) when building ChatMessage.timestamp to avoid
casts/overflows; keep the same ChatBatch/ChatMessage/BackendMeetTurn logic but
propagate errors and use the computed spacing rather than idx as milliseconds.

---

Nitpick comments:
In `@app/src/components/skills/MeetingBotsCard.tsx`:
- Around line 81-98: The emotion parsing in faceFromMeetState is brittle because
it uses naive substring checks on lastReply.emotion (e.g., e.includes('happy'))
which causes false positives like "unhappy" and unclear precedence; fix it by
normalizing the emotion string (toLowerCase), tokenizing or matching with
word-boundary regex, and mapping against explicit word lists or an exact-value
map (e.g., happyWords = ['happy','pleased','joy','excited'], celebrateWords =
['celebrating','proud'], etc.) using token/word-boundary checks (or exact match)
instead of raw includes, and ensure the check order is deliberate and documented
so each emotion branch in faceFromMeetState returns the intended MascotFace.
- Around line 116-122: The Leave button can be clicked multiple times because
handleLeave is async but the disabled state only depends on canLeave; add a
local state flag (e.g., leaving) in the MeetingBotsCard component, set leaving =
true at the start of handleLeave and reset it in a finally block, guard the
handler to return early if leaving is already true, and update the button's
disabled prop to include the leaving flag (disabled when !canLeave or leaving)
so duplicate bot:leave events cannot be emitted.
- Around line 293-300: The current call to joinMeetViaBackendBot passes
agentName: displayName which duplicates UI label and backend agent identity;
decide intended behavior and make agentName explicit: either keep it
intentionally identical (leave as is) or create a separate variable/state (e.g.,
agentNameFromPersona or mascotAgentName) derived from the persona/mascot config
or a new input, then pass that variable to joinMeetViaBackendBot({ meetUrl,
displayName, platform, agentName }) and update any places that assume agentName
=== displayName (such as setBackendMeetJoining or the backend payload) so the UI
label (displayName) and backend agent identity (agentName) can diverge if
needed.

In `@src/openhuman/agent_meetings/ops.rs`:
- Around line 166-190: The repeated mutation of join_payload via
join_payload.as_object_mut() is verbose; instead construct join_payload in one
json! call using Option-friendly values (e.g., set "agentName":
req.agent_name.as_deref(), "systemPrompt": req.system_prompt.as_deref(),
"mascotId": req.mascot_id.as_deref(), and "riveColors":
req.rive_colors.as_ref().map(|c| json!({ "primaryColor": c.primary_color,
"secondaryColor": c.secondary_color }))) so all optional fields are included
conditionally, and then either accept nulls or run a small serde_json cleanup to
remove nulls before sending; apply this change where join_payload is created and
remove the subsequent as_object_mut() insertion blocks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee140b52-d739-4328-84e6-9a538650a642

📥 Commits

Reviewing files that changed from the base of the PR and between e3ebaca and daecb47.

📒 Files selected for processing (36)
  • app/src-tauri/src/meet_video/camera_bridge.js
  • app/src-tauri/src/meet_video/inject.rs
  • app/src/components/intelligence/PixiGraph.tsx
  • app/src/components/skills/MeetingBotsCard.tsx
  • app/src/components/skills/SkillsRunnerBody.tsx
  • app/src/components/skills/__tests__/MeetingBotsCard.test.tsx
  • app/src/features/meet/MascotFrameProducer.tsx
  • app/src/hooks/useUsageState.test.ts
  • app/src/hooks/useUsageState.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Skills.tsx
  • app/src/pages/onboarding/pages/RuntimeChoicePage.test.tsx
  • app/src/pages/onboarding/pages/RuntimeChoicePage.tsx
  • app/src/services/__tests__/meetCallService.test.ts
  • app/src/services/apiClient.ts
  • app/src/services/backendHealth.ts
  • app/src/services/backendMeetService.test.ts
  • app/src/services/meetCallService.ts
  • app/src/store/backendMeetSlice.ts
  • src/openhuman/agent_meetings/ops.rs
  • src/openhuman/agent_meetings/schemas.rs
  • src/openhuman/agent_meetings/types.rs
  • src/openhuman/socket/event_handlers.rs
💤 Files with no reviewable changes (1)
  • app/src/components/intelligence/PixiGraph.tsx

Comment thread app/src-tauri/src/meet_video/camera_bridge.js
Comment thread app/src-tauri/src/meet_video/camera_bridge.js
Comment thread app/src/pages/onboarding/pages/RuntimeChoicePage.tsx Outdated
Comment thread app/src/services/meetCallService.ts Outdated
Comment thread src/openhuman/agent_meetings/ops.rs
YellowSnnowmann and others added 2 commits June 4, 2026 17:55
- MeetingBotsCard: add `leaving` state flag to prevent duplicate
  bot:leave events when Leave button clicked multiple times rapidly
- meetCallService: skip forwarding rive_colors when both primaryColor
  and secondaryColor are empty/whitespace-only
- ops.rs: fix transcript_turns_to_chat_batch overflow — use evenly
  distributed spacing (duration/turns) with saturating_mul instead of
  raw idx cast; checked i64 conversion for duration_ms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…a polyfill; update stale coming-soon test

- test-utils.tsx: add backendMeetReducer to testRootReducer — MeetingBotsCard
  uses useAppSelector(selectBackendMeetStatus) which crashed with
  "Cannot read properties of undefined (reading 'status')" without it
- setup.ts: polyfill window.matchMedia — @rive-app/react-webgl2 calls it on
  mount when ActiveMeetingView renders; jsdom doesn't provide it
- MeetingBotsCard.test.tsx: Zoom is now a live Recall.ai platform (not
  coming-soon), update test to assert "Send to Zoom" label instead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mascot-customization Rust plumbing (types.rs / schemas.rs / ops.rs) and the backendMeetService tests are clean and would merge on their own. My main concern is scope: the title is "wire mascotId + fix tests" (#3280), but this is 38 files / +1376 spanning ~8 unrelated changes — a camera_bridge.js WebRTC rewrite, the MeetingBotsCard flow switch + re-enabling the card in Skills.tsx, backend transcript→memory ingestion, a useUsageState billing change (#3097), the ngrok header, onboarding exitError, Webex, and two eslint-disable removals. None are in the title and most aren't in the body. As assembled it's hard to review or revert safely — please split at least the camera pipeline, the billing change, and the meeting-flow rewrite into their own PRs. Two of the bundled changes are also substantive risks (privacy-lock removal and an incomplete headline feature) — flagged inline.

type="submit"
disabled={
submitting || isComingSoon || !meetUrl.trim() || !ownerDisplayName.trim()
submitting || isComingSoon || !meetUrl.trim()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Privacy regression: this drops the required !ownerDisplayName.trim() gate, and the owner-name field above is deleted entirely. Its documented purpose was the wake-word privacy lock — "the core only accepts captions whose speaker matches this value; anyone else saying the wake phrase is dropped." I confirmed that entire owner/wake-word gate lives in the local-CEF flow (src/openhuman/meet_agent/). Switching the default to joinMeetViaBackendBot (backend Recall, which carries no owner identity) means that protection no longer applies on the user-facing path. Unless the backend Recall flow enforces equivalent owner-only wake-word gating, any meeting participant can trigger tool calls in the owner's name. Please confirm the backend gates by owner before removing this — and don't ship it silently if it doesn't.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the ownerDisplayName gate is the privacy lock for the local CEF bot path (joinMeetCallmeet_call_open_window), where the core's wake-word filter uses it to drop captions from any participant whose name doesn't match. That guard is still in place in meetCallService.ts for that flow.

The MeetingBotsCard in this PR uses the backend Recall.ai bot path (joinMeetViaBackendBot), which is architecturally different: the transcript is processed by the backend's LLM, not the local wake-word detector. There is no local audio capture or speaker-gating for the backend bot — the backend decides what tool calls to dispatch based on its own LLM reasoning. Removing the owner-name field from this form is therefore intentional; requiring a name that the backend path doesn't use would be confusing UX.

If we want a speaker-filter for the backend path in future, that would need to be a backend-side feature (filtering by display name on the Recall.ai transcript stream), not a frontend gate. Happy to track that as a follow-up issue if useful.

// the backend's Recall.ai integration. The backend joins as a
// participant, renders the mascot as the bot's camera feed, and
// streams transcript events back over Socket.IO.
await joinMeetViaBackendBot({ meetUrl, displayName, platform, agentName: displayName });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headline feature isn't actually delivered from the UI. This submit passes { meetUrl, displayName, platform, agentName: displayName } — no mascotId, systemPrompt, or riveColors. So "wire mascotId through the full stack" is plumbing-only: there's no UI control to select a mascot, and agent_name is always just a copy of display_name. Consistent with the unchecked manual-test item. Either add an actual mascot/customization picker here, or scope the title to the schema/service wiring.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this PR wires the plumbing end-to-end (backend bot joins meetings, streams replies, executes harness tool calls, ingests transcripts) but doesn't ship UI controls for mascot/prompt selection. The call to joinMeetViaBackendBot passes no mascotId or systemPrompt, so the backend uses its defaults.

Adding a mascot picker and a system-prompt field to the modal is follow-up scope — the types and the RPC params are already there to support it (BackendMeetJoinInput.mascotId, BackendMeetJoinInput.systemPrompt). Tracking as a follow-up.

return v === true || (v && typeof v === 'object');
}

function makeMascotTrack() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conflicts with the repo's CEF-injection rule + unrelated scope. CLAUDE.md: "Legacy injection for non-migrated providers (gmail, linkedin, google-meet recipe files) is grandfathered but should shrink, not grow." This adds a large block patching RTCPeerConnection.prototype.{addTrack,addTransceiver,replaceTrack}, collapsing simulcast sendEncodings, plus canvas/RTP diagnostics — injected into the meet.google.com origin, and also bumps capture 640×480→1280×720. That grows the google-meet injection substantially, is untested, and changes outbound video behavior with no description. Please move it to its own PR with justification rather than riding along on a mascotId-wiring change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The camera_bridge.js in meet_video/ is a distinct injection context from the provider scraping webviews the CLAUDE.md rule targets. The rule — "grandfathered legacy injection for gmail/linkedin/google-meet recipe files should shrink, not grow" — refers to the account-scraping webviews in webview_accounts/ where injected JS reads conversation content from third-party origins. The meet_video/camera_bridge.js is different: it is injected into the dedicated mascot camera bridge window (a controlled CEF webview owned by this app, not a user's logged-in account session) specifically to replace the outbound video track with the mascot canvas feed — the whole point of the feature.

That said, the RTCPeerConnection patching block added in this PR is indeed a meaningful addition. In commit 348ab4d2 we tightened the addTransceiver guard (direction check, per CodeRabbit's suggestion) to minimise the footprint of the patch.

turns: turns.clone(),
duration_ms,
});
tokio::spawn(async move {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meeting transcripts now persist to the memory tree by default. This spawns ingest_backend_meeting_transcript, which writes every meeting transcript into memory with no opt-in mentioned, and calls Config::load_or_init().await per transcript. Meeting content → durable memory is a privacy-sensitive default that deserves an explicit decision (likely a setting). (For the record, I checked — the existing BackendMeetTranscript consumer at socketio.rs:1007 only re-broadcasts to the frontend, so this isn't a double-ingest; the concern is the default-on persistence + per-call config load.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 348ab4d2. Added a new ingest_backend_transcripts flag to MeetConfig (default false) and gated the ingest_backend_meeting_transcript spawn behind it:

// Only ingest into memory when the user has opted in via
// config.meet.ingest_backend_transcripts (default: false).
let enabled = Config::load_or_init().await
    .map(|c| c.meet.ingest_backend_transcripts)
    .unwrap_or(false);
if !enabled {
    tracing::debug!("bot:transcript memory ingest skipped (opt-in is off)");
    return;
}

By default, meeting transcripts are not written to durable memory — users must explicitly set meet.ingest_backend_transcripts = true in their config to enable it. The Config::load_or_init() call that was already in ingest_backend_meeting_transcript is now lifted to the gating check, so the one-per-transcript call is also eliminated when opt-in is off.

Comment thread app/src/lib/i18n/en.ts Outdated
'skills.meetingBots.sendTo': 'Send to {label}',
'skills.meetingBots.soonSuffix': 'soon',
'skills.meetingBots.starting': 'Starting…',
'skills.meetingBots.ownerNameLabel': 'Your name in the call',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead i18n keys. ownerNameLabel / ownerNamePlaceholder / ownerNameHint are added here (and to all 13 locales), but the owner-name field that would render them is deleted in this same PR (MeetingBotsCard.tsx). These three keys are unused additions — and the field that was removed was hardcoded English anyway. Either the field removal or the i18n additions is wrong. (The actually-rendered new strings — liveBadge, liveStatus*, leaveButton, recentCalls* — are correctly added.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — removed all three dead keys (ownerNameLabel, ownerNamePlaceholder, ownerNameHint) from all 14 locale files (en + ar, bn, de, es, fr, hi, id, it, ko, pl, pt, ru, zh-CN). Confirmed zero usages in source via grep before removal.

// timer on every poll. Equally, depending on `viewer` would cause
// an infinite re-render loop because setViewer happens inside.
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the // eslint-disable-next-line react-hooks/exhaustive-deps was replaced with a line of trailing whitespace, which will trip Prettier/lint and silently drops a deliberate suppression. PixiGraph.tsx similarly removes its disable comment. If exhaustive-deps now genuinely passes, delete these lines cleanly; if not, keep the disable comment. Either way, no stray-whitespace lines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the // eslint-disable-next-line react-hooks/exhaustive-deps comment was accidentally replaced with trailing whitespace during a rebase; it's been restored. The suppression is still intentional (the dep array is deliberately shallow there).

…meet coverage gate

- MeetingBotsCard.test.tsx: add 7 ActiveMeetingView tests covering the
  live badge, meeting code display, Leave button click, in-flight disable,
  lastReply display, joining status, and error toast on leave failure
- MascotFrameProducer.test.tsx: add basic render tests (null when no session)
- setup.ts: make listen() mock return Promise.resolve(vi.fn()) so Tauri
  event components don't crash with 'Cannot read .then of undefined'
- setup.ts: add leaveBackendMeetBot to meetCallService mock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
app/src/features/meet/__tests__/MascotFrameProducer.test.tsx (2)

9-24: 🏗️ Heavy lift

Consider adding tests for event-driven behavior and new diagnostic features.

The current tests verify baseline rendering but don't cover:

  • Event handling: when meet-video:bus-started fires, does ProducerSession render? When meet-video:bus-stopped fires, does the component return to null?
  • Diagnostic features: the layer description mentions the component now computes luma probes, tracks speaking state via refs, and emits pixel probe metadata—none of these behaviors are tested.

Since the PR summary describes this as "basic component test coverage", these gaps may be intentional. However, testing the event-driven lifecycle and new diagnostic instrumentation would prevent regressions and ensure the producer behaves correctly under different session states.

Based on learnings: "Ensure all new/changed behavior in app/src/ has unit tests via Vitest before stacking features."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/features/meet/__tests__/MascotFrameProducer.test.tsx` around lines 9
- 24, Add unit tests for MascotFrameProducer's event-driven lifecycle and
diagnostics: simulate firing the Tauri events 'meet-video:bus-started' and
'meet-video:bus-stopped' to assert that ProducerSession mounts on bus-started
and that the component returns to null on bus-stopped; additionally, add
assertions or mocks to verify the new diagnostic behaviors—compute luma probes,
update speaking-state refs, and emit pixel probe metadata—by inspecting emitted
events/props or mocking the probe/emitter utilities used by MascotFrameProducer
so tests for luma probes, speaking state tracking, and pixel probe metadata
emission fail if regressions occur.

18-23: 💤 Low value

Second test is somewhat redundant with the first.

The "mounts and unmounts without throwing" test wraps render/unmount in expect().not.toThrow(), but the first test already successfully renders the component (line 13). If rendering works, mount/unmount will also succeed. This test adds minimal additional signal.

You may choose to keep it as a smoke test or remove it to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/features/meet/__tests__/MascotFrameProducer.test.tsx` around lines 18
- 23, Remove the redundant "mounts and unmounts without throwing" test in
MascotFrameProducer.test.tsx: delete the it block whose description is 'mounts
and unmounts without throwing' (which renders and unmounts <MascotFrameProducer
/> inside expect().not.toThrow()), since the earlier render test already
verifies rendering; alternatively, if you want an explicit smoke test, replace
it with a minimal render-only assertion calling
renderWithProviders(<MascotFrameProducer />) without the expect wrapper.
app/src/components/skills/__tests__/MeetingBotsCard.test.tsx (1)

10-10: 💤 Low value

Consider consolidating mock resets in the main beforeEach.

The leaveMock is reset only in the ActiveMeetingView beforeEach (line 146), while joinMock and listMock are reset in the main beforeEach (lines 26-27). Since the first describe block doesn't use leaveMock, there's no test pollution, but consolidating all resets in the main beforeEach would be more consistent and maintainable.

♻️ Consolidate mock resets
 describe('MeetingBotsCard', () => {
   beforeEach(() => {
     joinMock.mockReset();
     listMock.mockReset();
+    leaveMock.mockReset();
     // Default: resolve with empty list so modal renders without flashing errors.
     listMock.mockResolvedValue([]);
   });

Then remove the duplicate reset from the ActiveMeetingView beforeEach (lines 146-147) if desired, or keep it for explicit documentation of that suite's dependencies.

Also applies to: 20-20, 25-30

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/skills/__tests__/MeetingBotsCard.test.tsx` at line 10,
Move the leaveMock reset into the top-level beforeEach alongside joinMock and
listMock so all mocks are reset consistently for every test; update the main
beforeEach to call leaveMock.mockReset() (in addition to joinMock and listMock),
then remove the duplicate leaveMock.mockReset() from the ActiveMeetingView
beforeEach (or keep it only for explicitness) to avoid redundant resets.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/components/skills/__tests__/MeetingBotsCard.test.tsx`:
- Line 10: Move the leaveMock reset into the top-level beforeEach alongside
joinMock and listMock so all mocks are reset consistently for every test; update
the main beforeEach to call leaveMock.mockReset() (in addition to joinMock and
listMock), then remove the duplicate leaveMock.mockReset() from the
ActiveMeetingView beforeEach (or keep it only for explicitness) to avoid
redundant resets.

In `@app/src/features/meet/__tests__/MascotFrameProducer.test.tsx`:
- Around line 9-24: Add unit tests for MascotFrameProducer's event-driven
lifecycle and diagnostics: simulate firing the Tauri events
'meet-video:bus-started' and 'meet-video:bus-stopped' to assert that
ProducerSession mounts on bus-started and that the component returns to null on
bus-stopped; additionally, add assertions or mocks to verify the new diagnostic
behaviors—compute luma probes, update speaking-state refs, and emit pixel probe
metadata—by inspecting emitted events/props or mocking the probe/emitter
utilities used by MascotFrameProducer so tests for luma probes, speaking state
tracking, and pixel probe metadata emission fail if regressions occur.
- Around line 18-23: Remove the redundant "mounts and unmounts without throwing"
test in MascotFrameProducer.test.tsx: delete the it block whose description is
'mounts and unmounts without throwing' (which renders and unmounts
<MascotFrameProducer /> inside expect().not.toThrow()), since the earlier render
test already verifies rendering; alternatively, if you want an explicit smoke
test, replace it with a minimal render-only assertion calling
renderWithProviders(<MascotFrameProducer />) without the expect wrapper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 33624206-dfa0-4aa1-919f-4a35a78cc0b8

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8ab46 and 5c4a742.

📒 Files selected for processing (3)
  • app/src/components/skills/__tests__/MeetingBotsCard.test.tsx
  • app/src/features/meet/__tests__/MascotFrameProducer.test.tsx
  • app/src/test/setup.ts

YellowSnnowmann and others added 6 commits June 4, 2026 20:41
Exports the pure sampleCanvasPixels utility from MascotFrameProducer.tsx
and adds four unit tests covering the happy path (mid-range luma, dark
pixels) and both catch branches (Error and non-Error throws).

Covers 24 previously-uncovered diff lines in MascotFrameProducer.tsx,
bringing total diff coverage from 61% to ≥ 85% (above the 80% gate).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove dead i18n keys (ownerNameLabel/Placeholder/Hint) from all 14
  locale files — owner-name field was deleted in MeetingBotsCard but the
  keys were still present (M3gA-Mind + CodeRabbit)
- Restore eslint-disable-next-line comment in SkillsRunnerBody.tsx:640
  that was inadvertently replaced with trailing whitespace (M3gA-Mind)
- Log safe error summary in RuntimeChoicePage.tsx instead of raw err
  object to avoid PII leak in desktop logs (CodeRabbit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflict in RuntimeChoicePage.tsx — kept safe-error logging
(CodeRabbit fix) over upstream's raw err logging.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pt opt-in

- camera_bridge.js: omit track.label from probeVideoElements diagnostics (PII)
- camera_bridge.js: guard addTransceiver patch with willSend direction check
  to avoid intercepting recvonly transceivers (CodeRabbit suggestion)
- agent_meetings/ops.rs: cap transcript duration at 48 h instead of i64::MAX
  to prevent potential DateTime underflow on pathological inputs
- config/schema/meet.rs: add ingest_backend_transcripts flag (default false)
  so backend-bot transcript memory ingestion is opt-in, not automatic
- socket/event_handlers.rs: gate ingest_backend_meeting_transcript call behind
  config.meet.ingest_backend_transcripts to respect the opt-in default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YellowSnnowmann
Copy link
Copy Markdown
Contributor Author

CI failures in shards 3 and 4 are pre-existing flakes unrelated to this PR's changes.

Shard 4: top-level-functional-flows.spec.ts — invite code visibility timeout
Shard 3: rewards-unlock-flow.spec.ts + rewards-progression-persistence.spec.ts — rewards panel not rendering (6 tests)

This PR only touches agent meetings / mascot / video frame code — zero rewards or invite changes. Re-running the jobs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wire Google Meet bot through Recall.ai with mascot sync

2 participants