feat(meet): join Google Meet calls with mascot virtual camera#1350
feat(meet): join Google Meet calls with mascot virtual camera#1350senamakel merged 7 commits intotinyhumansai:mainfrom
Conversation
Adds an end-to-end "Join a Google Meet call" flow: - New `openhuman.meet_join_call` RPC validates the URL + display name and mints a request_id (Rust core, `src/openhuman/meet/`). - New Tauri commands `meet_call_open_window` / `meet_call_close_window` open a dedicated top-level CEF webview at the Meet URL with an isolated per-call data directory so the join is anonymous (shell, `app/src-tauri/src/meet_call/`). - New "Calls" tab on the Intelligence page lets the user paste a link, pick a display name, and tracks active calls. - about_app capability catalog entry under Channels (Beta). Tests: 6 Rust unit + 1 JSON-RPC e2e + 5 shell unit + 6 Vitest cases.
Two bugs that surfaced on first manual launch: 1. Tauri v2 ACL gate. Custom commands registered in invoke_handler! still need explicit entries under permissions/allow-core-process.toml, otherwise IPC rejects with "Command not found" before reaching Rust. meet_call_open_window and meet_call_close_window were missing — adding them lets the join click reach the shell. 2. The service swallowed Tauri's string-typed errors. Tauri v2 rejects with a String (the Err side of Result<_, String>), not a JS Error, so the component's `instanceof Error` catch fell back to the generic "Failed to start Meet call." message and the real reason was lost. Wrap the invoke and rethrow as a real Error with the original string, plus a console.error for the dev console.
Adds app/src-tauri/src/meet_scanner/ which, after the dedicated Meet
webview opens, attaches a CDP session to the new target and walks the
join page in three phases:
1. Click "Continue without microphone and camera" on the device-check.
2. Insert the guest display name into the "Your name" input via
Input.insertText so Meet's React-controlled <input> picks it up.
3. Click "Ask to join".
All steps run from the scanner side via Runtime.evaluate / Input.* — no
init-script JS is injected into the third-party webview, per the
project's CEF rule. Each phase has its own poll budget; failures log
and bail without crashing the window so the user can finish manually.
Verified end-to-end against a real Meet link — full sequence completes
in ~2s after the page paints. Capability catalog description updated
to reflect the automation.
Adds app/src-tauri/src/fake_camera/ which rasterizes the OpenHuman
mascot SVG (remotion/public/mascot.svg) into a 640x480 YUV420 Y4M
frame at first launch and caches it under <data_dir>/cache/fake_camera/.
At browser startup, lib.rs passes the cached path to CEF via:
--use-fake-device-for-media-stream
--use-fake-ui-for-media-stream
--use-file-for-fake-video-capture=<path>
Result: every CEF webview that calls getUserMedia({video:true}) now
sees the mascot as the agent's webcam. Verified end-to-end against a
real Meet link — the participant tile shows the mascot before "Ask to
join" is clicked.
Process-level flag (affects every CEF webview), which is fine today:
only the Meet call window deliberately requests a camera. No JS is
injected into third-party origins.
Also drops the meet_scanner device-check phase budget from 45s -> 6s,
since with a real "device" present Meet skips the "Continue without
microphone and camera" screen entirely; the scanner now logs the
absence as expected, not as a failure.
Pure-Rust pipeline using resvg + tiny-skia. 4 unit tests cover Y4M
header / payload layout, SVG -> RGBA buffer sizing, and the cache
hash determinism.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThis PR implements Google Meet join support: core validators and RPC, controller schemas, a frontend service and "Calls" UI tab, Tauri shell open/close commands and per-call external webviews, CDP-driven join automation, and a mascot SVG→Y4M fake-camera pipeline with caching and tests. ChangesGoogle Meet Integration Feature
Sequence DiagramsequenceDiagram
participant UI as Frontend UI
participant Service as meetCallService
participant Core as Core RPC
participant Tauri as Tauri Shell
participant WebView as External CEF WebView
UI->>Service: joinMeetCall({meetUrl, displayName})
Service->>Core: callCoreRpc openhuman.meet_join_call
Core-->>Service: {ok, request_id, meet_url, display_name}
Service->>Tauri: invoke meet_call_open_window
Tauri->>WebView: create external webview (isolated data dir)
Tauri->>meet_scanner: spawn automation (fire-and-forget)
WebView->>Tauri: on-destroy -> emit meet-call:closed
Tauri-->>UI: emit meet-call:closed event
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
tests/json_rpc_e2e.rs (1)
3886-3890: ⚡ Quick winAssert
request_idis a UUID, not just non-empty.This gives stronger regression protection for the RPC contract.
✅ Proposed test tightening
- assert!(!request_id.is_empty(), "request_id must not be empty"); + assert!( + uuid::Uuid::parse_str(request_id).is_ok(), + "request_id must be a UUID, got: {request_id}" + );🤖 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 `@tests/json_rpc_e2e.rs` around lines 3886 - 3890, Replace the current non-empty assertion for the extracted request_id with a strict UUID validation: instead of assert!(!request_id.is_empty()), attempt to parse the string as a UUID (e.g. using Uuid::parse_str or Uuid::try_parse) and assert that parsing succeeds (or call expect with a clear message). Update tests/json_rpc_e2e.rs to validate request_id using the uuid crate (add it to dev-dependencies if missing) so the test fails if request_id is not a valid UUID.src/openhuman/meet/rpc.rs (1)
16-17: ⚡ Quick winPrefer emitting the typed response struct instead of hand-crafted JSON.
Using
MeetJoinCallResponsedirectly reduces schema drift risk betweentypes.rs, schema metadata, and runtime output.♻️ Proposed refactor
-use super::types::MeetJoinCallRequest; +use super::types::{MeetJoinCallRequest, MeetJoinCallResponse}; @@ - let outcome = RpcOutcome::new( - json!({ - "ok": true, - "request_id": request_id, - "meet_url": normalized_url.as_str(), - "display_name": display_name, - }), - vec![], - ); + let response = MeetJoinCallResponse { + ok: true, + request_id, + meet_url: normalized_url.to_string(), + display_name, + }; + let outcome = RpcOutcome::new(json!(response), vec![]);Also applies to: 37-43
🤖 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/meet/rpc.rs` around lines 16 - 17, The handler currently constructs a raw JSON response; instead import and instantiate the typed MeetJoinCallResponse (alongside MeetJoinCallRequest) and populate its fields rather than building JSON by hand, then serialize or return that struct (e.g., via serde_json::to_value or by returning the typed response from the RPC handler). Update the use/import to include MeetJoinCallResponse, replace the hand-crafted JSON construction in the MeetJoinCallRequest handling code with creating MeetJoinCallResponse, and ensure you derive/serialize the struct to the expected RPC output format wherever the manual JSON was used (also replace similar manual JSON at the other occurrence currently around the code that mirrors lines 37-43).
🤖 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/fake_camera/mod.rs`:
- Around line 49-71: The current check-then-act flow in the function that
rasterizes and writes the mascot Y4M (calls rasterize_svg and
encode_single_frame_y4m, writes to tmp_path then fs::rename to y4m_path) can
race: another process may create y4m_path between exists() and rename, causing
rename to fail and abort even though the file is valid; change the write path to
tolerate "target already exists" by using a unique per-writer temp name (e.g.,
include a random/suid suffix when creating tmp_path) or by treating a failed
fs::rename error that indicates "already exists" (EEXIST) as a success—i.e.,
after rename failure, if y4m_path now exists and is readable, return
Ok(y4m_path) instead of an error; reference tmp_path, y4m_path, rasterize_svg,
encode_single_frame_y4m and the fs::rename call to locate where to implement
this.
In `@app/src-tauri/src/lib.rs`:
- Around line 1261-1295: The current code pushes process-global flags
(--use-fake-device-for-media-stream and --use-fake-ui-for-media-stream) when a
mascot Y4M is available, which enables fake devices for every CEF webview; stop
adding these two flags to the global args vector (see fake_camera_arg,
fake_camera::ensure_mascot_y4m and the args.push calls) and instead either (A)
only apply fake-device/ui flags when constructing the Meet call webview's
startup args (scope them to that webview), or (B) remove those two flags
entirely and only pass --use-file-for-fake-video-capture to the process while
implementing a per-webview fake-camera approach for the Meet window; update the
branch that currently pushes args.push(("--use-fake-device-for-media-stream",
None)) and args.push(("--use-fake-ui-for-media-stream", None)) accordingly.
In `@app/src-tauri/src/meet_call/mod.rs`:
- Around line 83-103: The per-call CEF profile directories created by
data_directory_for(&app, &request_id) and passed to
WebviewWindowBuilder::data_directory are never removed, causing buildup; update
the window teardown/Destroyed handler (where you create the WebviewWindow via
WebviewWindowBuilder and any close handlers) to asynchronously remove the
directory returned by data_directory_for(&app, &request_id) when the window is
closed or destroyed, and/or add a startup garbage-collection routine that scans
the meet_call/* subdirectories and deletes stale request_id profiles; refer to
data_directory_for, WebviewWindowBuilder::data_directory, and the window
Destroyed/close handler when adding the cleanup logic.
In `@app/src-tauri/src/meet_scanner/mod.rs`:
- Around line 108-123: wait_for_meet_target currently finds a target by URL
which is racy when multiple Meet windows exist; change it to accept and use a
concrete target id (e.g., add a parameter target_id: String or &str to
wait_for_meet_target) and call the CDP attachment helper that attaches by target
id instead of cdp::connect_and_attach_matching (or add/use a new
cdp::connect_and_attach_by_id helper); keep the existing deadline/retry logic
and last_err behavior, and update call sites that create the webview to store
and pass the created target id into wait_for_meet_target so attachment becomes
request-scoped.
In `@app/src/components/intelligence/IntelligenceCallsTab.tsx`:
- Around line 85-93: The current handleClose removes the call row in the finally
block even if closeMeetCall fails; change it so the active call is only removed
after a successful close: call closeMeetCall(requestId) and on success (either
the function resolves true or you receive the meet-call:closed event) call
setActiveCalls(prev => prev.filter(call => call.requestId !== requestId)); keep
the error handling (onToast) in the catch branch and do not remove the row from
the finally block—only remove in the success path or when the meet-call:closed
event handler runs.
In `@app/src/services/meetCallService.ts`:
- Around line 34-50: Move the isTauri() guard to run before invoking callCoreRpc
so non-desktop runs are side-effect free: in the meet join flow (the block that
calls callCoreRpc<CoreJoinResponse> with method 'openhuman.meet_join_call' and
params { meet_url, display_name }), check if isTauri() first and throw the same
"Joining a Meet call requires the desktop app..." Error when false; only call
callCoreRpc and perform the subsequent rpcResult checks if isTauri() is true.
In `@src/openhuman/meet/ops.rs`:
- Around line 35-37: The current check in the allowed_path logic (using
path.starts_with("lookup/")) permits nested lookup paths; update the validation
so lookup is exactly one segment after the prefix: when path begins with
"lookup/" ensure splitting path on '/' yields exactly two segments and the
second segment is non-empty (no additional slashes or empty id). Change the
calculation of allowed_path (which uses the path local and is_meet_code(path))
to include this stricter check instead of the loose starts_with, so only
"lookup/<id>" (single non-empty segment) is accepted.
In `@src/openhuman/meet/rpc.rs`:
- Around line 29-35: The tracing::info! call currently logs
normalized_url.path() (in the meet_join_call path) which can expose meeting
identifiers; remove or redact that field instead of logging the full path.
Update the tracing::info! invocation (the call that includes request_id, host,
path, display_name_chars and message "[meet] meet_join_call accepted; awaiting
shell handoff") to either omit the path key entirely or replace it with a
constant like "<redacted>" so only non-sensitive fields (request_id, host,
display_name_chars) are logged.
---
Nitpick comments:
In `@src/openhuman/meet/rpc.rs`:
- Around line 16-17: The handler currently constructs a raw JSON response;
instead import and instantiate the typed MeetJoinCallResponse (alongside
MeetJoinCallRequest) and populate its fields rather than building JSON by hand,
then serialize or return that struct (e.g., via serde_json::to_value or by
returning the typed response from the RPC handler). Update the use/import to
include MeetJoinCallResponse, replace the hand-crafted JSON construction in the
MeetJoinCallRequest handling code with creating MeetJoinCallResponse, and ensure
you derive/serialize the struct to the expected RPC output format wherever the
manual JSON was used (also replace similar manual JSON at the other occurrence
currently around the code that mirrors lines 37-43).
In `@tests/json_rpc_e2e.rs`:
- Around line 3886-3890: Replace the current non-empty assertion for the
extracted request_id with a strict UUID validation: instead of
assert!(!request_id.is_empty()), attempt to parse the string as a UUID (e.g.
using Uuid::parse_str or Uuid::try_parse) and assert that parsing succeeds (or
call expect with a clear message). Update tests/json_rpc_e2e.rs to validate
request_id using the uuid crate (add it to dev-dependencies if missing) so the
test fails if request_id is not a valid UUID.
🪄 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: 79a1c69d-5db9-4df9-bf75-0d5fade1f6bd
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapp/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
app/src-tauri/Cargo.tomlapp/src-tauri/permissions/allow-core-process.tomlapp/src-tauri/src/fake_camera/mod.rsapp/src-tauri/src/file_logging.rsapp/src-tauri/src/lib.rsapp/src-tauri/src/meet_call/mod.rsapp/src-tauri/src/meet_scanner/mod.rsapp/src/components/intelligence/IntelligenceCallsTab.tsxapp/src/pages/Intelligence.tsxapp/src/services/__tests__/meetCallService.test.tsapp/src/services/meetCallService.tssrc/core/all.rssrc/openhuman/about_app/catalog.rssrc/openhuman/about_app/catalog_tests.rssrc/openhuman/meet/mod.rssrc/openhuman/meet/ops.rssrc/openhuman/meet/rpc.rssrc/openhuman/meet/schemas.rssrc/openhuman/meet/types.rssrc/openhuman/mod.rstests/json_rpc_e2e.rs
Adds Vitest cases for the Calls tab covering: form render with disabled join button, successful submit -> active call list + success toast, rejection-with-Error -> error alert + error toast, rejection-with- non-Error fallback message, Leave button -> closeMeetCall + list removal. Lifts diff coverage on app/src/components/intelligence/ IntelligenceCallsTab.tsx from 0% past the >=80% gate.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/src/components/intelligence/IntelligenceCallsTab.test.tsx (1)
7-7: ⚡ Quick winAdd a test that exercises the
meet-call:closedevent-listener path.The
listenmock is wired correctly, but no test ever captures the registered callback and invokes it. The component's advertised behaviour — removing an active call from the list when the backend emitsmeet-call:closed— is therefore entirely untested.A minimal addition would look like:
import { listen } from '@tauri-apps/api/event'; it('removes a call when meet-call:closed fires for its requestId', async () => { // … join a call first … // Capture the callback registered by the component const listenMock = vi.mocked(listen); const [[, handler]] = listenMock.mock.calls; // Simulate the backend event await handler({ payload: { requestId: 'req-1' } } as any); await waitFor(() => expect(screen.queryByRole('button', { name: /Leave/i })).not.toBeInTheDocument() ); });🤖 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/intelligence/IntelligenceCallsTab.test.tsx` at line 7, Add a test in IntelligenceCallsTab.test.tsx that captures the mocked listen call (vi.mocked(listen)) after joining/creating an active call, extract the registered callback from listenMock.mock.calls, invoke it with an event object whose payload.requestId matches the active call (simulating the meet-call:closed event), and then await/assert that the call's UI (e.g., the "Leave" button) is removed; ensure you reference the listen mock and the meet-call:closed handler invocation so the registered callback path is exercised.
🤖 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/intelligence/IntelligenceCallsTab.test.tsx`:
- Line 7: Add a test in IntelligenceCallsTab.test.tsx that captures the mocked
listen call (vi.mocked(listen)) after joining/creating an active call, extract
the registered callback from listenMock.mock.calls, invoke it with an event
object whose payload.requestId matches the active call (simulating the
meet-call:closed event), and then await/assert that the call's UI (e.g., the
"Leave" button) is removed; ensure you reference the listen mock and the
meet-call:closed handler invocation so the registered callback path is
exercised.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 41c4f0c7-32b4-48df-98fd-d4b38b9e1ca7
📒 Files selected for processing (1)
app/src/components/intelligence/IntelligenceCallsTab.test.tsx
Seven targeted fixes from PR tinyhumansai#1350 review: - fake_camera: tolerate concurrent cache writers — if rename fails but the target file already exists, treat as success and discard our temp copy instead of erroring on the race. - meet_call: clean up the per-call CEF data directory when the window is destroyed. Each request_id was leaking an isolated profile dir to disk; we now rmdir it off the UI thread on the Destroyed event so cookies/cache don't accumulate across calls. - meet_scanner: scope target attachment to the per-call meet_url prefix instead of the meet.google.com host, so two concurrent Meet windows don't cross-control each other's join page. spawn() now takes the meet_url alongside request_id + display_name. - IntelligenceCallsTab: only remove the active-call row after closeMeetCall() resolves true. A failed close used to leave the Meet window open while wiping the only Leave/retry affordance from the UI. - meetCallService: move the isTauri() guard above callCoreRpc so the browser dev surface (`pnpm dev`) doesn't mint a stray request_id on the core for a join attempt that has no chance of opening a window. - meet/ops: tighten the lookup-path validator to exactly `lookup/<id>` (single non-empty segment). Nested lookup paths are no longer accepted. - meet/rpc: drop the URL path from the meet_join_call info log — the meeting code is the access credential and shouldn't appear in logs. The eighth comment (process-level fake-camera flags affecting all CEF webviews) was dismissed in-thread: the global scope is intentional — the agent presents as the mascot in every CEF webview by design, and the proper long-term fix is the CoreMediaIO Extension path already tracked under PR follow-ups.
Summary
--use-fake-device-for-media-stream+--use-file-for-fake-video-capture) feed Chromium a one-frame Y4M rasterized fromremotion/public/mascot.svg, so Meet sees the OpenHuman mascot as the agent's webcam.openhuman.meet_join_callcore RPC validates the URL/display name and mints arequest_id; newmeet_call_open_window/meet_call_close_windowTauri commands open/close the per-call webview keyed by that id.meet.join_call(Channels / Beta).Problem
The agent had no way to attend live conversations the user is asked to. We wanted a desktop path to drop the agent into a Meet call as a named guest, with a recognisable mascot face on camera, mic muted, and the entire join flow automated so the user doesn't have to hand-hold each step.
Solution
Five layers, each with a single concern:
src/openhuman/meet/(Rust core) — pure validation domain.openhuman.meet_join_call { meet_url, display_name }enforceshttps://meet.google.com/<code>or/lookup/<id>, trims/length-checks the display name, mints a UUIDrequest_id, returns the normalised echo. Wired into the controller registry the same way every other domain is.app/src-tauri/src/meet_call/(shell) —meet_call_open_window/meet_call_close_windowTauri commands. Top-levelWebviewWindowBuilder(separate window, not a child of the main window) with<app_local_data_dir>/meet_call/<request_id>/as a fresh data directory per call, so cookies stay isolated and Meet sees a brand-new anonymous user every time. Window-destroyed event emitsmeet-call:closedso the React tab keeps its in-flight list accurate.app/src-tauri/src/meet_scanner/(shell) — fire-and-forget tokio task spawned right after the window builds. Connects to CEF's browser-level WebSocket (already exposed by the existing scanner infrastructure on127.0.0.1:19222), attaches to the new Meet target, then drives a three-phase state machine viaRuntime.evaluate+Input.insertText:No init-script JS is injected — all driving runs from the scanner side per the project's CEF rule.
app/src-tauri/src/fake_camera/(shell) — at app startup, rasterises the OpenHuman mascot SVG into a 640×480 RGBA bitmap (viaresvg+tiny-skia), converts it to YUV420 with BT.601 coefficients, and writes a one-frame YUV4MPEG2 file under<data_dir>/cache/fake_camera/. The path is passed to CEF via three launch flags so any webview that callsgetUserMedia({video:true})reads the mascot frame in a loop. Cached across launches, keyed by SVG hash. Pure Rust, no system codecs.app/src/components/intelligence/IntelligenceCallsTab.tsx+app/src/services/meetCallService.ts(frontend) — new "Calls" tab on the Intelligence page collects URL + display name, calls the core RPC, then invokes the Tauri command with the returnedrequest_id. Listens tomeet-call:closedso manual window-close clears the active-calls list.End-to-end smoke run against a real Meet link: click → window opens → mascot in self-view → name typed → "Ask to join" pressed in ~6 seconds, all autonomously.
Two debugging fixes that surfaced during the rollout (separate commits in this branch):
permissions/allow-core-process.toml— Tauri v2 ACL gate. Custom commands registered ininvoke_handler!still need explicit allow entries; without them the IPC layer rejects with"Command not found"before the Rust handler runs.meetCallService.ts— Tauri rejects with aString(theErrside ofResult<_, String>), not a JSError. The component'sinstanceof Errorcatch was dropping the real reason and falling back to a generic message; wrap+rethrow as a realErrorplusconsole.errorfor the dev console.Submission Checklist
docs/TESTING-STRATEGY.mddiff-cover) meet the gate enforced by.github/workflows/coverage.yml. Runpnpm test:coverageandpnpm test:rustlocally; PRs below 80% on changed lines will not merge.docs/TEST-COVERAGE-MATRIX.md.docs/TEST-COVERAGE-MATRIX.md.docs/TESTING-STRATEGY.md)docs/RELEASE-MANUAL-SMOKE.mddoes not need a new row.Impact
compile_error!for non-desktop targets; the React surface refuses to open a window outsideisTauri()with an explicit user-facing message.--use-fake-device-for-media-streamand--use-file-for-fake-video-captureapply to every CEF webview in the process. Today only the Meet call window deliberately requests a camera; existing provider webviews (WhatsApp, Telegram, Slack, Discord, Gmail, LinkedIn) don't callgetUserMedia({video:true}), so this is a no-op for them. If a future feature needs a real camera in any webview, that surface will need to opt out of the fake stream.meet.google.com. The Tauri command sanitisesrequest_idagainst path traversal before joining it into the data-directory path.meet_call_open_window,meet_call_close_windowtopermissions/allow-core-process.toml— required for Tauri v2 ACL.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:checkpnpm typecheckcargo test --lib openhuman::meet,cargo test --test json_rpc_e2e -- meet_join_call,cargo test --manifest-path app/src-tauri/Cargo.toml --lib meet_call,cargo test --manifest-path app/src-tauri/Cargo.toml --lib fake_camera,pnpm --filter openhuman-app test:unit run app/src/services/__tests__/meetCallService.test.tscargo check --manifest-path Cargo.tomlcargo check --manifest-path app/src-tauri/Cargo.tomlValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
meet.google.com; scanner phases each fail-soft so the user can always finish manually.Duplicate / Superseded PR Handling
Summary by CodeRabbit