Skip to content

feat(composio): backend-proxied Composio integration end-to-end#501

Merged
senamakel merged 6 commits intotinyhumansai:mainfrom
senamakel:feat/composio
Apr 11, 2026
Merged

feat(composio): backend-proxied Composio integration end-to-end#501
senamakel merged 6 commits intotinyhumansai:mainfrom
senamakel:feat/composio

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 11, 2026

Summary

  • New openhuman::composio Rust domain module with RPC controllers (openhuman.composio_list_toolkits, _list_connections, _authorize, _delete_connection, _list_tools, _execute), backend-proxied through the shared IntegrationClient — no Composio API key on the client.
  • Five agent-callable tools (composio_list_toolkits, composio_list_connections, composio_authorize, composio_list_tools, composio_execute) registered in the tool registry when integrations.composio.enabled is set.
  • Socket.IO transport now routes composio:trigger events from the backend into the event bus as DomainEvent::ComposioTriggerReceived, with a ComposioTriggerSubscriber registered at startup in both channels::runtime::startup and core::jsonrpc bootstrap.
  • Frontend Skills page gains a parallel surface: composio toolkits (Gmail, Notion, GitHub, Slack, …) render as UnifiedSkillCards in the same grid as regular skills, grouped by category. Click → ComposioConnectModalopenhuman.composio_authorize → opens Composio OAuth URL via @tauri-apps/plugin-opener → polls composio_list_connections → flips to "Connected" state.
  • Three debug scripts: scripts/debug-composio-login.sh (curl + jq OAuth flow), scripts/debug-composio-trigger.mjs (Node socket.io listener with catchall + heartbeat + diagnostic checklist).

Problem

The core had a monolithic tools/composio.rs that called Composio's v2/v3 API directly with x-api-key, bypassing the backend's allowlist, billing/margin, HMAC webhook pipeline, and trigger fan-out. The backend team shipped /agent-integrations/composio/* routes (PR https://github.com/tinyhumansai/backend/pull/625) but nothing in the core or UI consumed them, so users couldn't authorize toolkits, receive trigger events, or see connection state.

Solution

Rust core

  • New src/openhuman/composio/ domain module following the cron module template (mod.rs light, types.rs, client.rs, ops.rs, schemas.rs, bus.rs, tools.rs).
  • ComposioClient wraps IntegrationClient for GET/POST and adds a bespoke raw_delete helper because the shared client doesn't expose DELETE.
  • DomainEvent enum extended with ComposioTriggerReceived, ComposioConnectionCreated, ComposioActionExecuted; domain() match arm returns "composio".
  • socket::event_handlers::handle_sio_event parses composio:trigger payloads (toolkit, trigger, metadata.id/uuid, payload) and publishes to the global bus.
  • IntegrationsConfig gains a composio: IntegrationToggle field; everything downstream keys off it.
  • Controllers registered in core::all::build_registered_controllers and build_declared_controller_schemas; namespace description added.

Frontend

  • app/src/lib/composio/{types,composioApi,hooks}.ts mirror lib/skills/skillsApi.ts shape.
  • useComposioIntegrations() fetches toolkits + connections on mount, polls connections every 5s, derives a connectionByToolkit Map, detects the "composio disabled" error and short-circuits.
  • ComposioConnectModal portal-based, state machine idle → authorizing → waiting → connected / error, reuses openUrl() for Tauri-aware OAuth handoff, polls with 5-minute timeout, supports Disconnect.
  • Skills.tsx appends composio items with kind: 'composio' to the unified grid; toolkit metadata (display name, description, category, emoji) lives in components/composio/toolkitMeta.ts with a title-cased fallback for unknown slugs so new backend entries render automatically.

Debug tooling

  • scripts/debug-composio-login.sh — pure bash + curl + jq. Walks the OAuth flow: /toolkits/connections/authorize → poll → /tools → optional /execute.
  • scripts/debug-composio-trigger.mjs — Node script using the socket.io-client from the app workspace. Resolves JWT from env or openhuman-core auth get_session_token, verifies the gmail connection is ACTIVE, opens a socket.io client against the backend with the same options the Rust SocketManager uses, attaches an always-on onAny catchall, prints a heartbeat idle log every 15s when no traffic has arrived in 30s+, and on zero events exits with a diagnostic checklist that pinpoints userId mismatch vs silent socket drop vs backend-side drop.

Submission Checklist

  • Unit testscargo test --lib openhuman::composio (2 subscriber tests), core::all registry validation (schemas ↔ handlers paired), event_bus domain match arms.
  • E2E / integration — Real end-to-end verification via scripts/debug-composio-login.sh against staging (Gmail OAuth succeeded, connection flipped to ACTIVE) and scripts/debug-composio-trigger.mjs (socket listener confirmed working once a separate backend bug in handleWebhook.ts v2/v3 payload destructure was fixed — see "Impact" below).
  • Doc comments — Module-level //! headers on every new Rust file, JSDoc on every exported TS symbol and every script.
  • Inline comments — Event-bus routing, connection-status derivation priority, catchall rationale, and the three-way diagnostic checklist in the trigger script all carry inline explanations.

Impact

  • Runtime: Desktop. Composio integration is gated behind integrations.enabled && integrations.composio.enabled; zero impact if unset. The legacy tools/composio.rs direct-API path is left in place for backwards compatibility and is only wired when config.composio.api_key is set, so existing users don't regress.
  • Security: Composio API key stays on the backend; the client never sees it. All HTTP calls flow through the existing IntegrationClient with Bearer JWT auth.
  • Billing: composio_execute charges through the backend's existing pricing pipeline; cost is returned in the response and logged via ComposioActionExecuted events.
  • Debugging a backend drop we hit: while validating the trigger flow end-to-end we found that backend-1's src/controllers/agentIntegrations/composio/handleWebhook.ts destructures verified.payload.userId assuming the Composio v2 shape, but v3 webhooks (which Composio is now delivering) put the user identifier under a different key. The emit silently early-returns with delivered: false. The diagnostic script's catchall + checklist now guides future debuggers straight to this class of issue. A follow-up PR on backend-1 is needed to shape-tolerate the destructure — details in the trigger script's exit message and the session history.

Related

  • Backend PR: tinyhumansai/backend#625
  • Follow-ups:
    • Backend: v2/v3 payload shape compatibility in handleWebhook.ts + permanent matched_sockets=N log line.
    • Frontend: per-tool playground in ComposioConnectModal (the listTools/execute RPCs are already wired in composioApi.ts).
    • Toolkit catalog: replace emoji icons in toolkitMeta.ts with SVGs and expand beyond the initial 7 entries.
    • Delete the legacy src/openhuman/tools/composio.rs once all users have migrated off the direct-API path.

Summary by CodeRabbit

  • New Features
    • Composio integration: connect OAuth accounts to hundreds of third‑party apps.
    • Manage connections via a new in‑app modal from the Skills page (authorize, view status, disconnect).
    • Toolkits appear in Skills with connection status indicators and CTA to open the connect modal.
    • Agents can list/execute Composio tools and receive realtime trigger events from connected services.

- Introduced a new Composio module for backend-proxied access to various OAuth integrations.
- Implemented ComposioClient for handling API requests related to toolkits, connections, and actions.
- Added RPC operations for listing toolkits, managing connections, and executing actions.
- Registered Composio controllers and schemas for integration with the existing system.
- Created a debug script for testing Composio OAuth flow against the live backend.
- Updated Cargo.lock to version 0.52.3 to reflect the new changes.
- Added ComposioConnectModal for managing Composio toolkit connections, mirroring the user experience of existing modals.
- Introduced composioApi for backend communication, including functions for listing toolkits, managing connections, and handling OAuth authorization.
- Created toolkitMeta for displaying metadata of Composio toolkits in the Skills grid.
- Developed hooks for fetching and managing Composio integrations, ensuring real-time updates on connection status.
- Updated Skills page to integrate Composio toolkits, providing a seamless user experience for connecting and managing integrations.
…ger script

- Bumped OpenHuman package version in Cargo.lock to 0.52.3.
- Introduced a new script, debug-composio-trigger.mjs, for Socket.IO live listening of Composio trigger events, facilitating testing and debugging of webhook interactions.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

Warning

Rate limit exceeded

@senamakel has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 10 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 10 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40ea691f-4f69-492c-bd71-4510c5c6139b

📥 Commits

Reviewing files that changed from the base of the PR and between 36cbe2d and 393947a.

📒 Files selected for processing (3)
  • scripts/debug-composio-login.sh
  • scripts/debug-composio-trigger.mjs
  • src/openhuman/composio/ops.rs
📝 Walkthrough

Walkthrough

Adds end-to-end Composio integration: frontend modal + hooks and RPC client for OAuth/connect/disconnect/polling; backend Composio client, RPC ops, agent tools, types, event-bus trigger handling and Socket.IO wiring; CLI debug scripts and Skills page UX to manage connections.

Changes

Cohort / File(s) Summary
Frontend modal & metadata
app/src/components/composio/ComposioConnectModal.tsx, app/src/components/composio/toolkitMeta.ts
New modal component implementing OAuth connect/disconnect flow with polling and phase state machine; toolkit metadata resolver with catalog and fallback naming.
Frontend API, types & hooks
app/src/lib/composio/composioApi.ts, app/src/lib/composio/types.ts, app/src/lib/composio/hooks.ts
Typed callCoreRpc wrappers for Composio RPCs, TS types for responses/states, and a hook managing toolkits, connections, derived lookup, initial parallel fetch and polling.
Skills page integration
app/src/pages/Skills.tsx
Renders composio toolkits as skill items, shows status indicators, and opens ComposioConnectModal; wires modal lifecycle and refresh on change.
Frontend debug tools
scripts/debug-composio-login.sh, scripts/debug-composio-trigger.mjs
New end-to-end debug scripts: Bash for OAuth/login/execute; Node ESM for Socket.IO listener and composio:trigger event inspection.
Backend types & client
src/openhuman/composio/types.rs, src/openhuman/composio/client.rs
Serde domain types for Composio payloads and a backend HTTP client wrapper (validated requests, envelope parsing, delete via reqwest).
Backend RPC ops & schemas
src/openhuman/composio/ops.rs, src/openhuman/composio/schemas.rs
RPC-facing ops mapping to ComposioClient methods with event publishing; controller schemas and registered handlers exposing RPC endpoints.
Backend agent tools
src/openhuman/composio/tools.rs
Agent-facing Tool implementations to list toolkits/connections, authorize, list tools, and execute actions; publish DomainEvents and return serialized results.
Backend eventbus & module exports
src/openhuman/composio/bus.rs, src/openhuman/composio/mod.rs
Event-bus subscriber for composio triggers with idempotent registration; module re-exports client, ops, schemas, tools, and types.
Backend event types & Socket.IO
src/openhuman/event_bus/events.rs, src/openhuman/socket/event_handlers.rs
New DomainEvent variants for Composio and Socket.IO handler for composio:trigger that validates and publishes events.
Core wiring & config
src/openhuman/config/schema/tools.rs, src/openhuman/mod.rs, src/core/all.rs, src/core/jsonrpc.rs, src/openhuman/channels/runtime/startup.rs, src/openhuman/tools/ops.rs
Added Composio toggle to integrations config; registered composio controllers, subscriber, and agent tools in core startup and tool registry.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as User (browser)
  participant Modal as ComposioConnectModal
  participant FrontApi as composioApi (frontend RPC)
  participant Core as Core RPC / Sidecar
  participant Backend as Backend ComposioClient

  User->>Modal: Click "Connect"
  Modal->>FrontApi: authorize(toolkit)
  FrontApi->>Core: callCoreRpc openhuman.composio_authorize
  Core->>Backend: ComposioClient.authorize -> obtain connectUrl
  Backend-->>Core: {connectUrl, connectionId}
  Core-->>FrontApi: authorize response
  FrontApi-->>Modal: connectUrl
  Modal-->>User: open connectUrl (external window)
  Modal->>Core: periodically listConnections (poll)
  Core->>Backend: ComposioClient.list_connections
  Backend-->>Core: connection list
  Core-->>Modal: connections
  alt connection becomes connected
    Modal->>Modal: transition to connected, show details
  else timeout / error
    Modal->>Modal: transition to error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #375: Changes integration config and tool registration framework that Composio wiring builds upon.
  • PR #492: Modifies Skills page modal flows and component logic similar to the ComposioConnectModal integration.

Poem

🐰 I hopped in code with a cheeky grin,

Auth URLs swirling, tokens spin,
Polls that wait while users roam,
Connections found, then safely home,
Hooray — more tools to nibble on! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(composio): backend-proxied Composio integration end-to-end' clearly summarizes the main change—a complete end-to-end Composio integration system across the backend, frontend, and tooling.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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 (7)
app/src/components/composio/toolkitMeta.ts (1)

73-86: Use a const arrow for composioToolkitMeta.

This helper lives under app/src, so it should follow the app TS convention here.

♻️ Suggested change
-export function composioToolkitMeta(slug: string): ComposioToolkitMeta {
+export const composioToolkitMeta = (slug: string): ComposioToolkitMeta => {
   const key = slug.toLowerCase();
   const hit = CATALOG[key];
   if (hit) return { slug: key, ...hit };
   // Fallback: title-case the slug and bucket it under "Other".
   const name = key.charAt(0).toUpperCase() + key.slice(1);
   return {
     slug: key,
     name,
     description: `Composio integration for ${name}.`,
     category: 'Other',
     icon: '\uD83D\uDD0C',
   };
-}
+};

As per coding guidelines "Prefer arrow functions over function declarations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/composio/toolkitMeta.ts` around lines 73 - 86, Replace the
function declaration composioToolkitMeta with an exported const arrow function
while preserving its behavior and types: keep the parameter name slug: string,
use the same local variables (key, hit, name), reference CATALOG and return the
same ComposioToolkitMeta shape (including fallback object with slug, name,
description, category, icon). Ensure the exported identifier remains
composioToolkitMeta and the function remains typed to return
ComposioToolkitMeta.
src/openhuman/event_bus/events.rs (1)

292-294: Add Composio cases to all_variants_have_correct_domain.

The new "composio" arm is not covered by the regression test below, so a future remap of these variants would slip through unnoticed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/event_bus/events.rs` around lines 292 - 294, The test
all_variants_have_correct_domain is missing the new Composio variants, so update
that test to include ComposioTriggerReceived, ComposioConnectionCreated, and
ComposioActionExecuted mapped to the "composio" domain; locate the
match/expected mapping inside the test (all_variants_have_correct_domain in
events.rs) and add these three variants to the expected set or match arm so the
regression test verifies they resolve to "composio".
src/openhuman/socket/event_handlers.rs (1)

115-158: Parse composio:trigger through the shared DTO.

This branch redefines the webhook payload shape by hand even though src/openhuman/composio/types.rs already has ComposioTriggerEvent. Deserializing once here would keep the transport parser and domain type in sync and make backend shape drift fail loudly instead of publishing partially-empty events.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/socket/event_handlers.rs` around lines 115 - 158, Replace the
manual field extraction in the "composio:trigger" branch with deserialization
into the shared DTO ComposioTriggerEvent from src/openhuman/composio/types.rs:
attempt serde_json::from_value(data.clone()) (or from a specific nested value)
to produce a ComposioTriggerEvent, handle the Err by logging a warning and
dropping the event, and on Ok map the DTO fields into
DomainEvent::ComposioTriggerReceived (preserving
payload/metadata_id/metadata_uuid/toolkit/trigger) before calling
publish_global; this ensures shape mismatches fail at deserialize time and
reuses the canonical type rather than hand-parsing fields.
src/openhuman/composio/ops.rs (1)

186-188: Consider using #[allow(unused_imports)] on the re-export instead.

The _unused_marker function is a workaround for dead_code warnings. A cleaner approach is to allow unused imports directly on the re-export line.

♻️ Cleaner unused warning suppression
 // ── Helpers re-exported so callers can pull connection/tool types without
 // reaching into the nested types module.
+#[allow(unused_imports)]
 pub use super::types::{ComposioConnection as Connection, ComposioToolSchema as ToolSchemaType};
-
-// Suppress unused import warnings when only some ops are referenced.
-#[allow(dead_code)]
-fn _unused_marker(_c: ComposioConnection, _t: ComposioToolSchema) {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/composio/ops.rs` around lines 186 - 188, The current
_unused_marker function (_unused_marker(_c: ComposioConnection, _t:
ComposioToolSchema)) is used to silence dead_code warnings; instead remove that
function and apply #[allow(unused_imports)] directly to the re-export(s) that
bring ComposioConnection and ComposioToolSchema into scope (the pub use /
re-export line), so unused import warnings are suppressed at the import site
rather than by a dummy function.
scripts/debug-composio-login.sh (1)

94-112: Consider adding a trap for temp file cleanup on script abort.

If the script is killed between mktemp and rm -f, the temp file may be left behind. This is minor since the file contains only HTTP response data and is in /tmp.

♻️ Optional: Add trap for cleanup
 set -euo pipefail
+
+CLEANUP_FILES=()
+cleanup() { rm -f "${CLEANUP_FILES[@]}" 2>/dev/null || true; }
+trap cleanup EXIT
 
 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

Then in the call function:

     local tmp
     tmp="$(mktemp)"
+    CLEANUP_FILES+=("$tmp")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug-composio-login.sh` around lines 94 - 112, The call function
creates a temporary file with mktemp (tmp) but only removes it at the end, so if
the script is aborted the temp file can be left behind; update the script to
ensure cleanup by installing a trap that removes the temporary file on
EXIT/INT/TERM (or within call use a trap on RETURN) so tmp is always removed,
referencing the mktemp invocation and the tmp variable used alongside the
existing rm -f "$tmp" cleanup in call.
scripts/debug-composio-trigger.mjs (1)

154-169: Consider logging the error in debug mode for easier troubleshooting.

The empty catch block silently returns null, which is the intended fallback behavior. However, logging the actual error in debug mode would help diagnose why the binary fallback failed.

♻️ Optional: Add debug logging for core binary errors
-  } catch {
+  } catch (err) {
+    dbg('core binary failed:', err.message);
     return null;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug-composio-trigger.mjs` around lines 154 - 169, In
getSessionTokenFromCore, the catch block swallows errors—capture the thrown
error (catch (err)) and, when a debug flag is set (e.g. process.env.DEBUG or a
new process.env.COMPOSIO_DEBUG), log a concise diagnostic including coreBin and
the error message (use processLogger or console.debug) before returning null;
keep the fallback behavior unchanged and only emit the log in debug mode so
execSync failures (from coreBin / auth get_session_token) are visible during
troubleshooting.
src/openhuman/composio/tools.rs (1)

339-358: Unnecessary Arc wrapping followed by immediate clone.

The code wraps client in Arc, then immediately dereferences and clones it ((*client).clone()), which defeats the purpose of Arc. Since ComposioClient is already Clone (it wraps Arc<IntegrationClient> internally), just clone it directly.

♻️ Simplify by removing unnecessary Arc
 pub fn all_composio_agent_tools(
     config: &crate::openhuman::config::IntegrationsConfig,
 ) -> Vec<Box<dyn Tool>> {
-    let Some(client) = super::client::build_composio_client(config) else {
+    let Some(c) = super::client::build_composio_client(config) else {
         tracing::debug!("[composio] agent tools not registered — disabled or missing credentials");
         return Vec::new();
     };
-    let client = Arc::new(client);
-    // Each tool gets its own cheap clone of the client handle.
-    let c = (*client).clone();
     let tools: Vec<Box<dyn Tool>> = vec![
         Box::new(ComposioListToolkitsTool::new(c.clone())),
         Box::new(ComposioListConnectionsTool::new(c.clone())),
         Box::new(ComposioAuthorizeTool::new(c.clone())),
         Box::new(ComposioListToolsTool::new(c.clone())),
         Box::new(ComposioExecuteTool::new(c)),
     ];
     tracing::debug!(count = tools.len(), "[composio] agent tools registered");
     tools
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/composio/tools.rs` around lines 339 - 358, In
all_composio_agent_tools: avoid wrapping the returned client in Arc then
immediately dereferencing and cloning; instead use the Clone impl on the
ComposioClient returned by super::client::build_composio_client(config) directly
(e.g., let client = match build_composio_client(...) { Some(c) => c, ... }),
then create cheap clones with client.clone() and pass those clones into
ComposioListToolkitsTool::new, ComposioListConnectionsTool::new,
ComposioAuthorizeTool::new, ComposioListToolsTool::new and
ComposioExecuteTool::new; remove the Arc::new(...) and the (*client).clone()
temporary to simplify the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/components/composio/ComposioConnectModal.tsx`:
- Around line 84-125: startPolling currently uses setInterval which can trigger
overlapping async tick executions (calling listConnections) and race updates
like setPhase and onChanged; change tick handling to prevent overlap by adding
an in-flight guard (e.g., a boolean or ref like isPollingRef used by tick) or
replace the setInterval with a recursive setTimeout that awaits tick before
scheduling the next call; update references to pollTimerRef and stopPolling to
clear the timeout when stopping, keep using pollDeadlineRef, POLL_INTERVAL_MS,
listConnections, deriveComposioState, setPhase and onChanged as before, and
ensure tick returns early when the in-flight flag is set or the deadline has
passed to avoid concurrent polls and races.
- Around line 48-54: The modal currently only treats an incoming connection as
'connected' vs 'idle'; change initialization and resume logic to derive the true
composio state from the incoming connection and resume pending flows instead of
resetting to 'idle': use deriveComposioState(connection) to map the incoming
connection into the Phase enum (e.g., map any pending/authorizing state to
'authorizing' or the appropriate Phase) when setting the initial phase and when
setting activeConnection/connectUrl so the modal will start polling for an
in-flight OAuth handoff rather than calling authorize() anew; update the
initialization of phase, activeConnection, and connectUrl (and any code paths
that currently call authorize() on mount) to prefer resuming the existing
connection when deriveComposioState(connection) indicates a pending/authorizing
state.

In `@src/openhuman/channels/runtime/startup.rs`:
- Around line 47-49: The Composio trigger subscriber is being registered
unconditionally; move the feature-gate check for integrations.enabled &&
integrations.composio.enabled into the register_composio_trigger_subscriber()
function so the function itself becomes a no-op when the feature is disabled,
and remove any duplicate gating at the call sites (e.g., startup.rs and
src/core/jsonrpc.rs) so both entry points call the same guarded function; update
register_composio_trigger_subscriber() to read the integrations config (or
accept it as an argument) and return early when composio is disabled, ensuring
subscriber registration only happens when the feature is enabled.

In `@src/openhuman/composio/client.rs`:
- Around line 161-167: The comment above the reqwest client builder is
misleading: instead of saying it "reuses the shared client's reqwest pool,"
update the comment to state that this code builds a fresh lightweight Client
(creating a new connection pool) for the DELETE call; optionally note that to
actually reuse the shared pool you should clone or use the existing shared
reqwest::Client rather than calling Client::builder().build(). Refer to the
http_client local and the Client::builder() block (the
timeout/connect_timeout/build() sequence) when making this change.

In `@src/openhuman/composio/schemas.rs`:
- Around line 208-259: Handlers handle_authorize, handle_delete_connection and
handle_execute currently trim but allow all-whitespace strings to pass to the
backend; create a helper (e.g., read_required_non_empty for String) that calls
read_required::<String>, trims the result and returns Err when the trimmed
string is empty, then replace calls to read_required::<String>(&params,
"toolkit"/"connection_id"/"tool") in those handlers with the new
read_required_non_empty so blank/whitespace-only inputs are rejected locally
before invoking super::ops::* methods; keep read_required and read_optional
unchanged for other types.

---

Nitpick comments:
In `@app/src/components/composio/toolkitMeta.ts`:
- Around line 73-86: Replace the function declaration composioToolkitMeta with
an exported const arrow function while preserving its behavior and types: keep
the parameter name slug: string, use the same local variables (key, hit, name),
reference CATALOG and return the same ComposioToolkitMeta shape (including
fallback object with slug, name, description, category, icon). Ensure the
exported identifier remains composioToolkitMeta and the function remains typed
to return ComposioToolkitMeta.

In `@scripts/debug-composio-login.sh`:
- Around line 94-112: The call function creates a temporary file with mktemp
(tmp) but only removes it at the end, so if the script is aborted the temp file
can be left behind; update the script to ensure cleanup by installing a trap
that removes the temporary file on EXIT/INT/TERM (or within call use a trap on
RETURN) so tmp is always removed, referencing the mktemp invocation and the tmp
variable used alongside the existing rm -f "$tmp" cleanup in call.

In `@scripts/debug-composio-trigger.mjs`:
- Around line 154-169: In getSessionTokenFromCore, the catch block swallows
errors—capture the thrown error (catch (err)) and, when a debug flag is set
(e.g. process.env.DEBUG or a new process.env.COMPOSIO_DEBUG), log a concise
diagnostic including coreBin and the error message (use processLogger or
console.debug) before returning null; keep the fallback behavior unchanged and
only emit the log in debug mode so execSync failures (from coreBin / auth
get_session_token) are visible during troubleshooting.

In `@src/openhuman/composio/ops.rs`:
- Around line 186-188: The current _unused_marker function (_unused_marker(_c:
ComposioConnection, _t: ComposioToolSchema)) is used to silence dead_code
warnings; instead remove that function and apply #[allow(unused_imports)]
directly to the re-export(s) that bring ComposioConnection and
ComposioToolSchema into scope (the pub use / re-export line), so unused import
warnings are suppressed at the import site rather than by a dummy function.

In `@src/openhuman/composio/tools.rs`:
- Around line 339-358: In all_composio_agent_tools: avoid wrapping the returned
client in Arc then immediately dereferencing and cloning; instead use the Clone
impl on the ComposioClient returned by
super::client::build_composio_client(config) directly (e.g., let client = match
build_composio_client(...) { Some(c) => c, ... }), then create cheap clones with
client.clone() and pass those clones into ComposioListToolkitsTool::new,
ComposioListConnectionsTool::new, ComposioAuthorizeTool::new,
ComposioListToolsTool::new and ComposioExecuteTool::new; remove the
Arc::new(...) and the (*client).clone() temporary to simplify the code.

In `@src/openhuman/event_bus/events.rs`:
- Around line 292-294: The test all_variants_have_correct_domain is missing the
new Composio variants, so update that test to include ComposioTriggerReceived,
ComposioConnectionCreated, and ComposioActionExecuted mapped to the "composio"
domain; locate the match/expected mapping inside the test
(all_variants_have_correct_domain in events.rs) and add these three variants to
the expected set or match arm so the regression test verifies they resolve to
"composio".

In `@src/openhuman/socket/event_handlers.rs`:
- Around line 115-158: Replace the manual field extraction in the
"composio:trigger" branch with deserialization into the shared DTO
ComposioTriggerEvent from src/openhuman/composio/types.rs: attempt
serde_json::from_value(data.clone()) (or from a specific nested value) to
produce a ComposioTriggerEvent, handle the Err by logging a warning and dropping
the event, and on Ok map the DTO fields into
DomainEvent::ComposioTriggerReceived (preserving
payload/metadata_id/metadata_uuid/toolkit/trigger) before calling
publish_global; this ensures shape mismatches fail at deserialize time and
reuses the canonical type rather than hand-parsing fields.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d90e544-39f7-478c-9a9c-1c6fd1d86eff

📥 Commits

Reviewing files that changed from the base of the PR and between f181ae1 and 2a69e28.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • app/src/components/composio/ComposioConnectModal.tsx
  • app/src/components/composio/toolkitMeta.ts
  • app/src/lib/composio/composioApi.ts
  • app/src/lib/composio/hooks.ts
  • app/src/lib/composio/types.ts
  • app/src/pages/Skills.tsx
  • scripts/debug-composio-login.sh
  • scripts/debug-composio-trigger.mjs
  • src/core/all.rs
  • src/core/jsonrpc.rs
  • src/openhuman/channels/runtime/startup.rs
  • src/openhuman/composio/bus.rs
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/mod.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/schemas.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/composio/types.rs
  • src/openhuman/config/schema/tools.rs
  • src/openhuman/event_bus/events.rs
  • src/openhuman/mod.rs
  • src/openhuman/socket/event_handlers.rs
  • src/openhuman/tools/ops.rs

Comment thread app/src/components/composio/ComposioConnectModal.tsx Outdated
Comment thread app/src/components/composio/ComposioConnectModal.tsx
Comment on lines 47 to +49
crate::openhuman::health::bus::register_health_subscriber();
crate::openhuman::skills::bus::register_skill_cleanup_subscriber();
crate::openhuman::composio::register_composio_trigger_subscriber();
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.

⚠️ Potential issue | 🟠 Major

Honor the Composio feature gate before subscribing.

This path registers the trigger subscriber even when integrations.enabled or integrations.composio.enabled is false, so Composio trigger handling can still come alive with the feature turned off. src/core/jsonrpc.rs now does the same, so the safest fix is to move the gate into register_composio_trigger_subscriber() and share it across both entry points.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/channels/runtime/startup.rs` around lines 47 - 49, The Composio
trigger subscriber is being registered unconditionally; move the feature-gate
check for integrations.enabled && integrations.composio.enabled into the
register_composio_trigger_subscriber() function so the function itself becomes a
no-op when the feature is disabled, and remove any duplicate gating at the call
sites (e.g., startup.rs and src/core/jsonrpc.rs) so both entry points call the
same guarded function; update register_composio_trigger_subscriber() to read the
integrations config (or accept it as an argument) and return early when composio
is disabled, ensuring subscriber registration only happens when the feature is
enabled.

Comment thread src/openhuman/composio/client.rs Outdated
Comment thread src/openhuman/composio/schemas.rs
- Updated ComposioConnectModal to handle various connection phases more effectively, including 'waiting' for pending connections.
- Introduced new refs for managing polling state and in-flight requests to prevent overlapping executions.
- Improved error handling during polling, providing clearer feedback on OAuth timeouts.
- Added logic to resume polling if the modal opens while an OAuth handoff is in progress.
- Refactored connection handling in the modal for better clarity and maintainability.
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: 4

🧹 Nitpick comments (3)
scripts/debug-composio-trigger.mjs (1)

65-76: Load .env before resolving TRIGGER_SLUG.

Line 76 reads process.env.TRIGGER_SLUG before Lines 98-99 call loadEnv(), so the documented .env/app/.env.local override never applies unless the caller exported it in the parent shell.

Proposed fix
-const args = process.argv.slice(2);
-const flag = (name) => args.includes(name);
-const valueOf = (name, fallback) => {
-  const idx = args.indexOf(name);
-  if (idx === -1 || idx === args.length - 1) return fallback;
-  return args[idx + 1];
-};
-
-const DEBUG = flag('--debug');
-const TIMEOUT_SECS = parseInt(valueOf('--timeout', '0'), 10); // 0 = forever
-const MAX_EVENTS = parseInt(valueOf('--max-events', '0'), 10); // 0 = unlimited
-const TRIGGER_SLUG = (valueOf('--trigger', process.env.TRIGGER_SLUG || 'GMAIL_NEW_GMAIL_MESSAGE')).trim();
-
 function dbg(...a) {
   if (DEBUG) console.log('  [debug]', ...a);
 }
@@
 loadEnv(path.join(ROOT, '.env'));
 loadEnv(path.join(ROOT, 'app', '.env.local'));
+
+const args = process.argv.slice(2);
+const flag = (name) => args.includes(name);
+const valueOf = (name, fallback) => {
+  const idx = args.indexOf(name);
+  if (idx === -1 || idx === args.length - 1) return fallback;
+  return args[idx + 1];
+};
+
+const DEBUG = flag('--debug');
+const TIMEOUT_SECS = parseInt(valueOf('--timeout', '0'), 10); // 0 = forever
+const MAX_EVENTS = parseInt(valueOf('--max-events', '0'), 10); // 0 = unlimited
+const TRIGGER_SLUG = (valueOf('--trigger', process.env.TRIGGER_SLUG || 'GMAIL_NEW_GMAIL_MESSAGE')).trim();

Also applies to: 84-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug-composio-trigger.mjs` around lines 65 - 76, TRIGGER_SLUG (and
other environment-dependent constants resolved via valueOf) is being computed
before the project's .env is loaded, so process.env overrides from loadEnv()
never apply; move the call to loadEnv() (or ensure loadEnv() is invoked) before
you compute TRIGGER_SLUG, TIMEOUT_SECS, MAX_EVENTS, DEBUG, and any other
variables that read process.env so that valueOf('--trigger',
process.env.TRIGGER_SLUG || ...) picks up .env values; update the script to call
loadEnv() (or import/execute its initializer) prior to the lines that define
flag(), valueOf(), and the constants that depend on process.env.
app/src/components/composio/ComposioConnectModal.tsx (1)

38-43: Prefer an arrow component declaration for consistency with repo TS style.

This default-exported function can be converted to a const arrow component to align with project conventions.

♻️ Suggested refactor
-export default function ComposioConnectModal({
+const ComposioConnectModal = ({
   toolkit,
   connection,
   onChanged,
   onClose,
 }: ComposioConnectModalProps) {
@@
-}
+};
+
+export default ComposioConnectModal;

As per coding guidelines, **/*.{js,jsx,ts,tsx}: “Prefer arrow functions over function declarations”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/composio/ComposioConnectModal.tsx` around lines 38 - 43,
Convert the default-exported function component ComposioConnectModal to a const
arrow component to match repo style: replace the function declaration "export
default function ComposioConnectModal({ toolkit, connection, onChanged, onClose,
}: ComposioConnectModalProps)" with a const arrow like "const
ComposioConnectModal: React.FC<ComposioConnectModalProps> = ({ toolkit,
connection, onChanged, onClose }) => { ... }" and keep the default export
(either "export default ComposioConnectModal" at the bottom or export inline);
ensure TypeScript typing still references ComposioConnectModalProps and that all
references to the component remain unchanged.
scripts/debug-composio-login.sh (1)

188-188: Build JSON payloads with jq -n instead of string interpolation.

Direct interpolation can produce invalid JSON when values include quotes/backslashes.

♻️ Suggested refactor
-    call POST "/agent-integrations/composio/authorize" "{\"toolkit\":\"$TOOLKIT\"}"
+    AUTH_PAYLOAD="$(jq -cn --arg toolkit "$TOOLKIT" '{toolkit: $toolkit}')"
+    call POST "/agent-integrations/composio/authorize" "$AUTH_PAYLOAD"
@@
-    call POST "/agent-integrations/composio/execute" \
-        "{\"tool\":\"$EXECUTE_TOOL\",\"arguments\":{}}"
+    EXEC_PAYLOAD="$(jq -cn --arg tool "$EXECUTE_TOOL" '{tool: $tool, arguments: {}}')"
+    call POST "/agent-integrations/composio/execute" "$EXEC_PAYLOAD"

Also applies to: 263-264

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/debug-composio-login.sh` at line 188, The script builds JSON payloads
via string interpolation which can break when values contain quotes or
backslashes; update the two places that call the helper 'call' (e.g., the POST
to "/agent-integrations/composio/authorize" and the occurrences around lines
263-264) to generate the JSON payload with jq -n (or an equivalent JSON-safe
builder) and pass the resulting string to the call command, ensuring values like
$TOOLKIT (and the other variables used later) are safely encoded as JSON strings
rather than interpolated into raw JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/debug-composio-login.sh`:
- Around line 223-225: The current jq selection that sets STATUS from
CONNECTIONS_JSON uses "id == $cid OR toolkit == $tk" and then head -n1, which
can return a stale toolkit-only record; change the logic to prefer an exact id
match first and only fall back to a toolkit match if no id match exists: when
computing STATUS (the STATUS assignment that reads CONNECTIONS_JSON with jq
using $TOOLKIT and $CONNECTION_ID), implement a two-step selection (try
select(.id == $cid) first, and if that yields nothing then select where .toolkit
equals $tk case-insensitively) or use a jq expression that returns .status from
an id match before considering toolkit matches so head -n1 cannot pick a stale
toolkit-only entry.

In `@scripts/debug-composio-trigger.mjs`:
- Around line 233-261: The Gmail precheck currently hard-exits if no Gmail
connection is found, blocking other toolkit triggers; change it to determine the
target toolkit from the trigger flag/argument (e.g. parse --trigger or a
variable like desiredToolkit), then replace the hard-coded gmail lookup (the
connections.find(...) that assigns gmail) with a lookup for the desiredToolkit
(compare c.toolkit to desiredToolkit case-insensitively), and only call
process.exit(1) when the desiredToolkit is 'gmail' and no connection exists; for
other toolkits, log a warning (use fail/console.warn/info) but do not exit so
the socket listener can still run.
- Around line 58-60: The dynamic import currently passes a filesystem path
(socketIoPath) directly to import(), which breaks on Windows; import
pathToFileURL from 'url' and convert socketIoPath to a file:// URL (e.g.,
pathToFileURL(socketIoPath).href or toString()) before calling await
import(...), update the top-of-file imports alongside fileURLToPath and ensure
any other dynamic imports in this module use pathToFileURL so Node ESM receives
a valid URL.

In `@src/openhuman/composio/ops.rs`:
- Line 11: Remove the erroneous import `use anyhow::Result;` and add a concrete
alias for the module's error result type, e.g. `type OpResult<T> =
std::result::Result<T, String>;`, then update the seven functions that currently
declare return types as `Result<T, String>` to use `OpResult<T>` instead (i.e.,
replace each `Result<..., String>` with `OpResult<...>` for the functions that
return the module-specific string-error Result).

---

Nitpick comments:
In `@app/src/components/composio/ComposioConnectModal.tsx`:
- Around line 38-43: Convert the default-exported function component
ComposioConnectModal to a const arrow component to match repo style: replace the
function declaration "export default function ComposioConnectModal({ toolkit,
connection, onChanged, onClose, }: ComposioConnectModalProps)" with a const
arrow like "const ComposioConnectModal: React.FC<ComposioConnectModalProps> = ({
toolkit, connection, onChanged, onClose }) => { ... }" and keep the default
export (either "export default ComposioConnectModal" at the bottom or export
inline); ensure TypeScript typing still references ComposioConnectModalProps and
that all references to the component remain unchanged.

In `@scripts/debug-composio-login.sh`:
- Line 188: The script builds JSON payloads via string interpolation which can
break when values contain quotes or backslashes; update the two places that call
the helper 'call' (e.g., the POST to "/agent-integrations/composio/authorize"
and the occurrences around lines 263-264) to generate the JSON payload with jq
-n (or an equivalent JSON-safe builder) and pass the resulting string to the
call command, ensuring values like $TOOLKIT (and the other variables used later)
are safely encoded as JSON strings rather than interpolated into raw JSON.

In `@scripts/debug-composio-trigger.mjs`:
- Around line 65-76: TRIGGER_SLUG (and other environment-dependent constants
resolved via valueOf) is being computed before the project's .env is loaded, so
process.env overrides from loadEnv() never apply; move the call to loadEnv() (or
ensure loadEnv() is invoked) before you compute TRIGGER_SLUG, TIMEOUT_SECS,
MAX_EVENTS, DEBUG, and any other variables that read process.env so that
valueOf('--trigger', process.env.TRIGGER_SLUG || ...) picks up .env values;
update the script to call loadEnv() (or import/execute its initializer) prior to
the lines that define flag(), valueOf(), and the constants that depend on
process.env.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0e60ba8-7b02-4754-959e-ecbf7a677018

📥 Commits

Reviewing files that changed from the base of the PR and between 2a69e28 and 36cbe2d.

📒 Files selected for processing (9)
  • app/src/components/composio/ComposioConnectModal.tsx
  • scripts/debug-composio-login.sh
  • scripts/debug-composio-trigger.mjs
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/schemas.rs
  • src/openhuman/composio/tools.rs
  • src/openhuman/event_bus/events.rs
  • src/openhuman/socket/event_handlers.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/openhuman/socket/event_handlers.rs
  • src/openhuman/event_bus/events.rs
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/tools.rs

Comment thread scripts/debug-composio-login.sh Outdated
Comment thread scripts/debug-composio-trigger.mjs Outdated
Comment thread scripts/debug-composio-trigger.mjs Outdated
Comment thread src/openhuman/composio/ops.rs Outdated
…fication

- Updated debug-composio-login.sh to build JSON payloads using jq for safer handling of toolkit data.
- Enhanced connection verification logic in debug-composio-trigger.mjs to prioritize newly created connections and provide warnings for missing toolkits.
- Refactored composio operations in ops.rs to return a more explicit error type, improving clarity in error handling across RPC operations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant