Skip to content

feat(flows): browser companion core wiring — Chrome automation via slug:"browser" (Part 1) - #5250

Closed
graycyrus wants to merge 5 commits into
tinyhumansai:mainfrom
graycyrus:feat/browser-companion
Closed

graycyrus wants to merge 5 commits into
tinyhumansai:mainfrom
graycyrus:feat/browser-companion

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Wires the tinyflows Chrome browser-extension companion into OpenHuman so a flow tool_call node with slug:"browser" drives the user's real, signed-in Chrome tab (16 CDP actions: open/click/fill/type/screenshot/find/…). This is Part 1 — core wiring of a staged feature (plan below); no user-facing surface yet (RPC + Settings pairing UI land in Part 2).
  • New desktop-only browser_companion domain owns the tinyflows CompanionServer lifecycle (loopback WS relay + pairing secret, 0600), exposed via a new opt-in ServiceSet::companion_relay service gated on config.browser_companion.enabled.
  • slug:"browser" is now a built-in flow tool_call: routed to Chrome via RoutingToolInvoker at run time, taught to the author-time curation gates (no Composio connection needed), with an author-advisory / run-hard readiness posture mirroring the existing inference-readiness (B45) pattern.
  • Everything rides the existing flows Cargo feature (no new compile gate); compiled out cleanly when flows is off.

Problem

Flows can orchestrate Composio/LLM/HTTP effects but cannot act on a live web page the user is signed into. tinyflows shipped a purpose-built Chrome companion (branch feat/chrome-extension) whose own doc states "OpenHuman host wiring is intentionally separate follow-up work." This PR is that wiring.

Solution

Data path: flow run → build_capabilities() → wrap OpenHumanTools in RoutingToolInvoker(ChromeToolInvoker(relay, run_id=thread_id, tab_id)) → a browser node executes over the loopback relay to the shared tab; every other slug/connection_ref flows through untouched.

  • Domain src/openhuman/browser_companion/ (mod/types/ops/store): start/stop/pair/unpair/rotate-secret/status over tinyflows::companion::CompanionServer; secret at {workspace}/browser_companion/relay.secret.
  • Service ServiceSet::companion_relay (desktop()=true, headless_api()/none()=false — never in cloud, matching the security model). spawn_companion_relay_service() no-ops unless enabled.
  • Routing at the real-execution build_capabilities site in run_flow_body (the dry-run/mock path does not route through it, so nothing to mis-wrap). Binds run→tab via a Stage-B CompanionServer::bind_run and releases it with an RAII drop guard.
  • Gates: validate_tool_contracts validates the browser action against the 16-action set instead of the Composio catalog; validate_connection_refs skips browser; a run-time hard gate validate_browser_readiness (running + extension connected + browser_tab_id) fails a real run cleanly before the engine executes; the author path stays advisory (no hard gate in run_builder_gates — see the B45 design note at flows/ops.rs). OpenHumanTools::invoke returns a clear "companion not running / no tab" error as a fallback.
  • Discovery: list_connectable_toolkits surfaces {toolkit:"browser", connected, type:"builtin"} for the workflow_builder agent.

tinyflows dependency (branch-tip, release-later — deliberate)

The vendored vendor/tinyflows submodule is bumped to the feat/chrome-extension tip + a small additive API (browser_relay/is_extension_connected/shared_tabs/bind_run/unbind_run on CompanionServer, needed by an embedding host) — see companion tinyflows PR. This OpenHuman PR is a DRAFT and must not merge until tinyflows cuts v0.6.0 and this submodule points at the release. Version-compat is clean (both sides on tinyagents 2.1).

Submission Checklist

  • Tests added — unit tests for the domain lifecycle (start→status→stop), secret perms, and the flows gates (browser action validation, connection-ref skip, run-time readiness failure modes, invoke fallback).
  • Diff coverage ≥ 80% — Rust-only change; the new domain (lifecycle/pairing/status/tab-mapping), the ServiceSet desktop-only selection, and the flows gates (browser action validation, connection-ref skip, run-time readiness, invoke fallback) are unit-tested. The few live socket-routing lines in run_flow_body aren't unit-coverable (they need a live extension); the JSON-RPC E2E lands with the RPC surface in Part 2.
  • Coverage matrix — N/A: new feature, matrix row added with the RPC surface in Part 2.
  • Affected feature IDs — see ## Related.
  • No new external network deps — the relay is loopback-only; no new crates beyond what feat/chrome-extension already vendors (axum 0.8 / tokio full, additive).
  • Manual smoke — N/A: no user-facing surface yet (Part 2).
  • Linked issue — N/A: net-new feature; staged plan tracked in my_docs/browser_companion_integration/PLAN.md (gitignored).

Impact

  • Desktop only. Off by default (browser_companion.enabled=false); the relay never binds in headless/cloud. Security model preserved end-to-end: 127.0.0.1-pinned listener, exact chrome-extension://<id> origin + token auth, only explicitly-shared tabs, all extension JS bundled locally.
  • No behaviour change when flows is off (domain compiled out) or when the companion is disabled (browser nodes fail closed with a clear message).

Related

  • Follow-up PR(s)/TODOs: Part 2 — browser_companion_* RPC namespace + Settings pairing UI + extension-initiated runs (inbound control channel → flows_run); Part 3 — extension resource shipping in the Tauri bundle + workflow_builder prompt.md; persist extension_id via config (TODO(stage-E) markers in ops.rs).
  • Depends on: tinyflows Stage B PR (branch oh/chrome-ext-relay) + tinyflows v0.6.0 release.

…ug:"browser" (Part 1)

New desktop-only `browser_companion` domain owning the tinyflows CompanionServer
lifecycle + pairing (loopback WS relay, 0600 secret), an opt-in
ServiceSet::companion_relay service, and first-class routing of `slug:"browser"`
flow tool_calls to the paired Chrome tab via RoutingToolInvoker. Author-advisory
/ run-hard readiness gate mirrors the inference-readiness (B45) posture. Rides
the flows Cargo feature; compiled out cleanly when flows is off.

Depends on tinyflows Stage B (CompanionServer relay/bind handles) + v0.6 release.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d11cd1a-9a44-4ff0-9577-72069025618f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a feature-gated Browser Companion relay with pairing persistence, browser toolkit contracts, readiness validation, and tab-scoped flow execution. Desktop services start the relay when enabled, while browser runs bind to shared tabs and clean up bindings afterward.

Changes

Browser Companion configuration and lifecycle

Layer / File(s) Summary
Configuration and companion data contracts
src/openhuman/config/schema/..., src/openhuman/browser_companion/types.rs, src/openhuman/browser_companion/store.rs
Adds Browser Companion configuration, status/pairing types, and workspace-scoped secret persistence with tests.
Relay lifecycle and runtime wiring
src/openhuman/browser_companion/..., src/core/runtime/..., src/openhuman/mod.rs
Adds singleton relay lifecycle, pairing operations, run binding, status accessors, and feature-gated runtime startup.

Browser flow integration

Layer / File(s) Summary
Browser toolkit contracts and readiness
src/openhuman/flows/builder_tools.rs, src/openhuman/flows/node_contracts.rs, src/openhuman/flows/ops.rs, src/openhuman/flows/*tests.rs
Adds the built-in browser toolkit, browser action validation, contract notes, connection-ref exclusions, and readiness checks.
Tab-scoped flow execution and failure handling
src/openhuman/flows/schemas.rs, src/openhuman/flows/ops.rs, src/openhuman/tinyflows/caps.rs, vendor/tinyflows
Threads browser_tab_id through flow execution, routes browser calls through the relay, cleans up bindings, and reports actionable unavailable-companion errors.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FlowSchema
  participant FlowOps
  participant CompanionRelay
  participant ChromeExtension
  Client->>FlowSchema: Submit flow with browser_tab_id
  FlowSchema->>FlowOps: Start tab-scoped flow
  FlowOps->>CompanionRelay: Bind run to shared tab
  FlowOps->>ChromeExtension: Route browser actions
  CompanionRelay-->>FlowOps: Unbind run
  FlowOps-->>Client: Return result or readiness error
Loading

Possibly related PRs

Suggested labels: feature, rust-core, bug

Suggested reviewers: m3ga-mind, senamakel

Poem

A rabbit hops where browser tabs align,
A relay hums along the WebSocket line.
Secrets rest snug in a workspace burrow,
Runs bind to tabs, then leave no furrow.
“Hop-hop!” says Bun—the companion is online! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: browser companion core wiring for flows using the browser slug.

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

@graycyrus

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added bug feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 28, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🤖 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 `@src/core/runtime/builder.rs`:
- Line 89: Add regression tests for companion-relay selection: in
src/core/runtime/builder.rs at lines 89, 110, 131, and 162, assert that
ServiceSet::desktop() enables companion_relay while headless_api(), none(), and
embedded() disable it; at lines 733-736, verify the selected flag reaches relay
startup only when flows is enabled; in src/core/runtime/services.rs lines
230-255, test both enabled and disabled configuration gates without starting a
real relay.

In `@src/openhuman/browser_companion/ops.rs`:
- Around line 125-140: Update start_with_extension_id and stop_companion_server
to synchronize runtime state with the spawned listener task: report bind
failures before returning success, clear server/task state on unexpected task
exit, and await the aborted task during shutdown so immediate restarts occur
only after teardown completes.
- Around line 287-307: Update pair and the related CompanionRuntime state to
retain the supplied extension_id as the active ID after start_with_extension_id
succeeds. Make companion_status report this active runtime ID and make
rotate_secret use it when restarting, including when the configured ID is
disabled or default, while preserving the existing pairing flow.

In `@src/openhuman/browser_companion/types.rs`:
- Around line 27-36: Add tests for the SharedTab to SharedTabView conversion
implemented by From<tinyflows::companion::SharedTab> for SharedTabView, using an
inline #[cfg(test)] module or sibling Rust test file. Construct a representative
SharedTab and assert id, window_id, url, and title are preserved while
relay-internal state is not exposed in the resulting view.

In `@vendor/tinyflows`:
- Line 1: Update the vendor/tinyflows gitlink to the exact commit referenced by
the v0.6.0 release tag, ensuring the submodule resolves to that tagged revision
and not another commit.
🪄 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: 3c12f436-72de-4539-9806-927c188dad5f

📥 Commits

Reviewing files that changed from the base of the PR and between 4ce2a8c and 66654f1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • src/core/runtime/builder.rs
  • src/core/runtime/services.rs
  • src/openhuman/browser_companion/mod.rs
  • src/openhuman/browser_companion/ops.rs
  • src/openhuman/browser_companion/store.rs
  • src/openhuman/browser_companion/types.rs
  • src/openhuman/config/schema/browser_companion.rs
  • src/openhuman/config/schema/mod.rs
  • src/openhuman/config/schema/types.rs
  • src/openhuman/flows/builder_tools.rs
  • src/openhuman/flows/builder_tools_tests.rs
  • src/openhuman/flows/node_contracts.rs
  • src/openhuman/flows/ops.rs
  • src/openhuman/flows/ops_tests.rs
  • src/openhuman/flows/schemas.rs
  • src/openhuman/mod.rs
  • src/openhuman/tinyflows/caps.rs
  • vendor/tinyflows

Comment thread src/core/runtime/builder.rs
Comment thread src/openhuman/browser_companion/ops.rs
Comment thread src/openhuman/browser_companion/ops.rs
Comment thread src/openhuman/browser_companion/types.rs
Comment thread vendor/tinyflows
…id retention, coverage, CI gates

- ops.rs: reap dead listener state + is_running() (bind failure no longer reports
  running / blocks restart); stop awaits the aborted task so the loopback port is
  freed before an immediate pair/rotate restart; retain the live extension id in
  CompanionRuntime so status + rotate_secret use it (not stale Config) — fixes
  pair-then-rotate leaving the relay down.
- Tests: ServiceSet companion_relay desktop-only; SharedTab->SharedTabView mapping;
  lifecycle extended to cover pair(new id) + rotate_secret restart.
- CI: add core/runtime/builder.rs to the feature-gate-smoke allowlist; regenerate
  app/src-tauri/Cargo.lock for the new tinyflows transitive deps (--locked).
Route browser_relay/is_extension_connected/bind_run/unbind_run and
companion_status's server observations through a with_live_server() helper that
reaps a dead listener task and returns None unless is_running(). Closes the
CodeRabbit follow-up: these APIs no longer read or operate on a stale server
handle whose serve() has already exited.
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 28, 2026
…ipping the feature-gate-smoke allowlist

The explanatory note in types.rs contained a literal `#[cfg(feature = "flows")]`,
which the rust-feature-gate-smoke lane greps for when building its gated-test
allowlist — so adding a test to this file falsely flagged it as a new gated-test
module. Reworded to not embed the attribute; the file gates no test.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Maintainer pass (merge-readiness sweep). I picked this up to rebase and get it green, but stopped before touching the branch: the blocker you named in the description has hardened rather than cleared, and resolving the conflicts would mean guessing. Findings below, with the relocation map so the eventual rebase is cheap.

The vendored dependency is the hard blocker

The PR body says this "must not merge until tinyflows cuts v0.6.0 and this submodule points at the release." That release never happened, and the branch it was cut from is gone:

tinyflows tags today v0.8.2, v0.8.1, v0.8.0, v0.3.0, v0.2.0, v0.1.1no v0.6.0; the line went v0.3.0v0.8.0
vendor/tinyflows on main 99f27535 (v0.8.2) — git ls-tree -r shows no src/companion/, no src/browser/, no CompanionServer
vendor/tinyflows on this PR bef4df64 ("feat(companion): expose browser_relay/is_extension_connected/shared_tabs for embedding hosts", 2026-07-28) — has the full src/companion/ + src/browser/ stack and all five host-facing fns
relationship divergent, not behind: main's pin is 229 commits ahead on its line, this pin 149 on its own; neither is an ancestor of the other
reachability git branch -r --contains bef4df64 → nothing. feat/chrome-extension no longer exists; the commit survives only as an unreferenced object (still fetchable by explicit SHA)

So there is no pin that both satisfies this PR and is legal for main:

  • Keeping bef4df64 moves the submodule onto a divergent, older line — it reverts the 229 tinyflows commits main currently builds against, and trips the Module Pin Gate / check-submodule-monotonic.mjs job that has been added since this PR was opened.
  • Taking main's 99f27535 deletes tinyflows::companion::CompanionServer and the browser_relay / is_extension_connected / shared_tabs / bind_run / unbind_run methods this PR is built on, so the host wiring cannot compile.

This needs the tinyflows companion work merged to the tinyflows mainline and released before the OpenHuman side can move at all. That is a decision for you and whoever owns tinyflows, not something a rebase can resolve — which is why I have left the branch untouched.

The source-side conflicts are mechanical, but only after the above

Merging current main gives 10 content conflicts, one modify/delete and the submodule conflict. All of it is the kernelization + file-split wave, not anyone's logic changing underneath you. The relocation map:

This PR touches Now lives at
src/openhuman/tinyflows/caps.rs (OpenHumanTools, build_capabilities) src/openhuman/flows/tinyflows/caps/ops.rs — the whole src/openhuman/tinyflows/ family was deleted by #5314 (a52a599ec, "kernelize openhuman"), which is the modify/delete conflict
src/openhuman/flows/ops.rs (run_flow_body) src/openhuman/flows/ops_part_07.rsops.rs is now a 20-line include! shell
validate_tool_contracts src/openhuman/flows/ops_part_03.rs
validate_connection_refs src/openhuman/flows/ops_part_04.rs
src/openhuman/flows/builder_tools.rs split across builder_tools_part_01..07.rs
flows/ops_tests.rs, flows/builder_tools_tests.rs split into *_tests_part_NN_tests.rs siblings

src/core/runtime/{builder,services}.rs, config/schema/{mod,types}.rs, flows/{node_contracts,schemas}.rs and src/openhuman/mod.rs still exist and conflict only on context.

Two things worth knowing before you rebase: app/src-tauri/Cargo.lock also conflicts, and the two Cargo worlds have drifted (the shell manifest still patches tinyagents at a path that is now a virtual manifest), so the desktop lockfile may need regenerating separately from the root one.

Status

Marking this blocked on an upstream decision in the maintainer sweep rather than stale. Nothing here is a criticism of the change — the wiring reads well and the staged plan is clear; it is waiting on a tinyflows release that has not been cut. If the companion work is not going to land upstream, that is worth saying out loud so this draft can be closed rather than carried.

No commits, no pushes, no force-push to this branch.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

@graycyrus — I picked this up to rebase it onto current main (it was 6,682 commits behind). I attempted the rebase, hit something a rebase cannot fix, and stopped without pushing. Your branch is untouched at e3c9366e. Here is what I found and what I think you need to decide.

The blocker: tinyflows deleted the companion

This PR is wired to tinyflows::companion::CompanionServer and tinyflows::browser::{RoutingToolInvoker, ChromeToolInvoker, BrowserRelay, BrowserAction} — 13 symbols in total across browser_companion/ops.rs, flows/ops.rs and tinyflows/caps.rs. None of them exists in tinyflows any more:

  • tinyflows#79, "Remove Chrome workflow companion" (−11,789, merged 2026-08-30 by @senamakel) deleted the Rust modules, the companion CLI, the browser relay, the MV3 extension, the protocol fixtures and the Chrome CI job.
  • tinyflows#24 (oh/chrome-ext-relay) — the PR this one declares a dependency on — was closed. Its embedding-host API was salvaged into fix: remove standalone bins from Cargo.toml to fix Tauri bundler failure #39, which then went out again with Add SkillProvider and integrate skills runtime #79.
  • The feat/chrome-extension branch no longer exists on the repo.
  • I verified against the tree rather than the branch list: git ls-tree -r 99f27535 | grep -iE 'browser|companion|chrome|extension' returns nothing.

So this is not a merge conflict. The upstream capability the PR wires in was removed a month ago.

Why the submodule pointer has no correct resolution

vendor/tinyflows is one of the eleven conflicts, and both sides are wrong:

Resolution What breaks
Take main's 99f27535 (v0.8.2) No companion/browser modules → all 13 symbols unresolved → browser_companion cannot compile
Keep your bef4df648 That tree is a single-crate repo with no crates/ directory. main's Cargo.toml:158,159,165 declares path deps on vendor/tinyflows/crates/{tinyflows-catalog,tinyflows-sqlite,tinyflows-copilot} — cargo fails at resolution, before compiling anything

Between your merge base and now, tinyflows went from tinyflows = { path = "vendor/tinyflows" } (v0.5, single crate) to a five-crate workspace at "0.8" plus three path deps. There is no pointer that satisfies both your code and main's, and inventing one would mean re-implementing a subsystem someone deliberately removed — so I stopped here rather than guess.

#79's own summary points at the alternative: "Browser automation remains available as a host-managed capability through the existing tool invocation interface." That reads like the upstream position is that the relay belongs in the host, not in tinyflows — which would be a real redesign of Part 1, not a rebase. Your call, and possibly one to take with @senamakel first.

Everything else is mechanical — here is the map, if you revive it

I ran the rebase far enough to enumerate the conflicts. Eleven files conflict; ten are pure relocation from the kernelization (#5328), and I confirmed every anchor function you patch still exists on main:

src/openhuman/flows/ops.rs is now a 20-line include! shim — your 14 hunks re-home into six part-files:

Your anchor New home
build_builder_proposal ops_part_01.rs
validate_inference_readiness, validate_tool_contracts ops_part_03.rs
validate_connection_refs_against ops_part_04.rs
flows_run, flows_run_detached, RunRowFinalizer ops_part_06.rs
run_flow_body ops_part_07.rs
flows_resume ops_part_08.rs

Other moves:

  • src/openhuman/tinyflows/caps.rssrc/openhuman/flows/tinyflows/caps/ops.rs; OpenHumanTools is at :400 and its ToolInvoker::invoke at :568, so your slug == "browser" guard drops in unchanged. (This one comes through as a modify/delete conflict — the file is gone, not moved in git's eyes.)
  • flows/ops_tests.rs → your three test hunks go to ops_tests_part_08_tests.rs; the builder_tools_tests.rs one to builder_tools_tests_part_01_tests.rs.
  • src/core/runtime/builder.rs — 5 small blocks. ServiceSet is structurally unchanged, so companion_relay + the desktop()/headless_api()/none() presets apply as written.
  • config/schema/types.rs, flows/{builder_tools,node_contracts,schemas}.rs, src/openhuman/mod.rs — 1 block each, whole-file restructures where your actual delta is a few lines.
  • Applied clean: ci-lite.yml, core/runtime/services.rs, config/schema/mod.rs, and all five new files (browser_companion/{mod,ops,store,types}.rs, config/schema/browser_companion.rs).

One placement decision is yours, not mine. src/openhuman/mod.rs is now exactly the 31 post-kernelization families, and CLAUDE.md's rule is one directory = one feature gate. A new top-level browser_companion/ would be family #32; since it is gated on flows and consumed only by flows, src/openhuman/flows/browser_companion/ looks more consistent — but that is your structure to choose, so I did not pick for you.

What I did and did not do

  • gh pr update-branch --rebase"Cannot update PR branch due to conflicts", as expected.
  • Manual git rebase --onto upstream/main 4ce2a8c7c in a throwaway worktree → 11 conflicts on the first commit, aborted.
  • Nothing pushed. No force-push. Your branch is still e3c9366e and your five commits are intact. I did not approve or merge.

Happy to do the ten mechanical relocations the moment the tinyflows question has an answer — it is maybe an hour of careful work, but it is wasted until there is a CompanionServer to compile against.

@senamakel senamakel closed this Sep 12, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants