Skip to content

Improve local voice readiness and add Composio trigger history#516

Merged
senamakel merged 7 commits intotinyhumansai:mainfrom
senamakel:fix/sunday
Apr 12, 2026
Merged

Improve local voice readiness and add Composio trigger history#516
senamakel merged 7 commits intotinyhumansai:mainfrom
senamakel:fix/sunday

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 12, 2026

Summary

  • Add Composio trigger history support in the core, including controller schema wiring, archived trigger history storage, and UI retrieval commands.
  • Surface Composio trigger history in the Webhooks screen with a dedicated history component and hook.
  • Improve local voice readiness handling by tightening STT status checks in the settings panel and overlay polling flow.
  • Refine local AI asset and prompt handling so on-demand model states and no-think prompting behave more predictably.
  • Update the about-app capability catalog to reflect the new Composio behavior.

Problem

  • Webhook-triggered Composio activity did not have a user-visible history view, which made debugging trigger intake and delivery difficult.
  • Local voice and local AI flows had readiness and state-handling gaps that could leave the UI out of sync with the underlying runtime.

Solution

  • Added trigger history persistence and a composio_list_trigger_history path in the Rust core, then exposed it through the existing controller/JSON-RPC surface.
  • Added Tauri command bindings plus a React hook/component pair to fetch and render recent Composio trigger history in the desktop UI.
  • Tightened voice-status checks and overlay polling so the UI responds more reliably to STT server state changes.
  • Adjusted local AI asset-state and prompt handling logic to better reflect on-demand availability and final-answer prompting behavior.

Submission Checklist

  • Unit tests — Vitest (app/) and/or cargo test (core) for logic you add or change
  • E2E / integration — Where behavior is user-visible or crosses UI → Tauri → sidecar → JSON-RPC; use existing harnesses (app/test/e2e, mock backend, tests/json_rpc_e2e.rs as appropriate)
  • N/A — Follow-up push step only; this branch was already committed before PR creation, and I did not run new unit or E2E suites as part of opening the PR
  • Doc comments/// / //! (Rust), JSDoc or brief file/module headers (TS) on public APIs and non-obvious modules
  • Inline comments — Where logic, invariants, or edge cases aren’t clear from names alone (keep them grep-friendly; avoid restating the code)

Impact

  • Affects the desktop app and Rust core sidecar behavior.
  • No migration required.
  • Pre-push checks passed for formatting, ESLint, TypeScript compile, and Tauri Rust check before push.

Related

  • Issue(s):
  • Follow-up PR(s)/TODOs:

Summary by CodeRabbit

  • New Features

    • ComposeIO trigger history: persistent daily archives, a history view with payload preview, and a refreshable ComposeIO Triggers page.
  • New Features

    • Live voice overlay syncing with core voice server state for real-time STT/transcribing UI updates.
  • Improvements

    • Broadened STT readiness to include on-demand model states.
    • Improved asset detection for STT/TTS (more accurate ready/ondemand/missing and warnings).
    • Refined inference handling to better honor “no-think” responses.

- Updated the Home component to improve readiness checks for local AI assets, ensuring a more robust UI experience.
- Refactored the LocalAiService to provide clearer state management for STT and TTS models, including handling on-demand downloads and error logging.
- Enhanced the voice status function to accurately reflect the availability of transcription backends, improving overall system reliability.
- Introduced warnings for on-demand model downloads to inform users about potential delays in functionality.
- Added a polling mechanism to periodically check the voice server status every 2 seconds, ensuring the overlay remains in sync with the server state.
- Introduced logic to activate or dismiss the speech-to-text (STT) mode based on the server's recording or transcribing state, enhancing user experience and responsiveness.
- Utilized the existing `callCoreRpc` function to fetch server status, improving reliability in state management during brief disconnects or reconnections.
- Updated the prompt construction logic in `LocalAiService` to enhance clarity and functionality.
- When `no_think` is set, the system prompt now includes a directive for the model to respond with only the final answer, improving response accuracy.
- Refactored the prompt and system parameters to ensure they are correctly formatted and passed to the `OllamaGenerateRequest`, enhancing overall request handling.
- Updated the STT readiness logic in `VoicePanel` to account for both 'ready' and 'ondemand' states, improving the accuracy of readiness assessments.
- Refined the `useVoiceSkillStatus` hook to ensure it only blocks when the local AI's STT state is explicitly 'missing', enhancing overall system reliability and user experience.
- Added `ComposeioTriggerHistory` component to display a list of ComposeIO trigger events with formatted timestamps and payloads.
- Introduced `useComposeioTriggerHistory` hook to manage fetching and state of trigger history entries, including error handling and loading states.
- Updated `Webhooks` page to integrate the new component and hook, replacing previous webhook activity display with ComposeIO trigger history.
- Created utility functions for formatting timestamps and payloads for better readability in the UI.
- Established backend support for fetching trigger history through new Tauri commands, ensuring robust data handling and storage.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

This PR adds a persistent ComposeIO trigger history (backend storage, RPC, and frontend UI with polling), relaxes STT readiness gating to accept 'ondemand' alongside 'ready', introduces overlay RPC polling to sync voice state, and refactors local-AI asset and inference prompt handling.

Changes

Cohort / File(s) Summary
STT Readiness & Voice Status
app/src/components/settings/panels/VoicePanel.tsx, app/src/features/voice/useVoiceSkillStatus.ts, src/openhuman/voice/ops.rs
Relaxed STT readiness: treat 'ondemand' as acceptable; sttReady now combines asset state and voiceStatus.stt_available. Broadened stt_available logic to include multiple whisper-in-process / binary+model conditions.
ComposeIO Trigger History — Frontend
app/src/components/webhooks/ComposeioTriggerHistory.tsx, app/src/hooks/useComposeioTriggerHistory.ts, app/src/utils/tauriCommands/composio.ts, app/src/utils/tauriCommands/index.ts
Added UI component, hook with 5s polling, and Tauri RPC wrapper to fetch and render ComposeIO trigger history and archive metadata.
ComposeIO Trigger History — Backend
src/openhuman/composio/trigger_history.rs, src/openhuman/composio/types.rs, src/openhuman/composio/ops.rs, src/openhuman/composio/schemas.rs, src/openhuman/composio/bus.rs, src/openhuman/composio/mod.rs
Implemented global trigger-history store writing daily JSONL files, added RPC op to list recent entries, integrated recording on trigger receipt, and exported types/controllers for the feature.
Webhooks Page Migration
app/src/pages/Webhooks.tsx
Replaced previous webhooks/tunnel UI with ComposeIO trigger history view, archive info, and a Refresh button wired to the new hook.
Overlay Voice Polling
app/src/overlay/OverlayApp.tsx
Added 2s RPC polling fallback (openhuman.voice_server_status), modeRef sync to avoid stale closures, and UI resynchronization logic for recording/transcribing ↔ stt transitions.
Local AI Assets & Inference
src/openhuman/local_ai/service/assets.rs, src/openhuman/local_ai/service/public_infer.rs
Assets: preserve resolve results, compute ready/ondemand/missing states with warnings for ondemand. Inference: move no_think directive into system (effective_system) rather than prefixing prompt.
Core Initialization & Catalog
src/core/jsonrpc.rs, src/openhuman/about_app/catalog.rs
Registered Composio trigger-history initialization during domain subscriber setup (cloning workspace_dir) and updated capability description to reference ComposeIO trigger archives.
Misc / Styling / Dependencies
app/src/pages/Home.tsx, Cargo.toml
Minor whitespace formatting in Home; added fs2 = "0.4" dependency for file locking used by trigger history storage.

Sequence Diagram(s)

sequenceDiagram
    participant ReactApp as React App<br/>(Webhooks)
    participant Hook as useComposeioTriggerHistory<br/>Hook
    participant RPC as Core RPC Client
    participant Core as Core Service
    participant FS as Workspace<br/>Filesystem

    Note over ReactApp,FS: Initial Load & Polling (5s interval)
    ReactApp->>Hook: mount component
    Hook->>Hook: initialize state (loading=true)
    Hook->>RPC: openhumanComposioListTriggerHistory(limit)
    RPC->>Core: openhuman.composio_list_trigger_history
    Core->>FS: scan triggers/*.jsonl
    Core->>FS: read & parse JSON lines
    FS-->>Core: entries (newest-first)
    Core-->>RPC: ComposioTriggerHistoryResult
    RPC-->>Hook: result.entries[]
    Hook->>Hook: setState(entries, archiveDir, currentDayFile)
    Hook-->>ReactApp: trigger history + archive info
    ReactApp->>ReactApp: render ComposeioTriggerHistory

    Note over Hook,FS: Polling continues every 5s
    Hook->>RPC: openhumanComposioListTriggerHistory()
    RPC->>Core: openhuman.composio_list_trigger_history
    Core->>FS: re-scan for new entries
    FS-->>Core: updated entries
    Core-->>Hook: latest history
    Hook->>Hook: setState(loading=false)
    Hook-->>ReactApp: re-render with new entries
Loading
sequenceDiagram
    participant Overlay as OverlayApp<br/>Component
    participant RPC as Core RPC Client
    participant Core as Core Voice<br/>Service
    participant UI as Overlay UI

    Note over Overlay,UI: Polling Loop (2s interval)
    Overlay->>Overlay: useEffect (on mount)
    Overlay->>Overlay: startInterval (2s)

    loop Every 2 seconds
        Overlay->>RPC: callCoreRpc<br/>voice_server_status
        RPC->>Core: openhuman.voice_server_status
        Core-->>RPC: {state: recording|transcribing|idle|stopped}
        RPC-->>Overlay: voice status

        alt state=recording/transcribing && mode!=stt
            Overlay->>Overlay: setMode('stt')
            Overlay->>Overlay: setBubble(listening|transcribing)
            Overlay->>UI: update overlay message
        else state=idle/stopped && mode=stt
            Overlay->>Overlay: goIdle()
            Overlay->>UI: dismiss overlay
        end
    end

    Note over Overlay: cleanup on unmount
    Overlay->>Overlay: clearInterval()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
Triggers hop into daily files with cheer,
Overlay listens, turning stt near,
'Ondemand' now welcomed in the race,
Prompts settle gently into their place,
Archives grow—a hopping, tidy trace.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.81% 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 accurately summarizes the two main changes: improving local voice readiness (tightened STT status checks, better STT availability logic) and adding Composio trigger history support (new persistence, RPC, UI components, and React hooks).

✏️ 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: 8

🧹 Nitpick comments (5)
src/openhuman/local_ai/service/public_infer.rs (1)

312-319: Add a debug checkpoint for the no_think branch decision.

This changed control path is important for inference behavior, but it currently has no branch-level diagnostic log (e.g., no_think, max_tokens, allow_empty, model id) with a stable prefix like [local_ai:infer].

♻️ Suggested patch
         let started = std::time::Instant::now();
 
+        tracing::debug!(
+            no_think,
+            allow_empty,
+            temperature,
+            max_tokens = ?max_tokens,
+            model = %model_ids::effective_chat_model_id(config),
+            "[local_ai:infer] building generate request"
+        );
+
         // When `no_think` is set, append the instruction to the system
         // prompt so the model treats it as a directive rather than content
         // it might parrot back.
         let effective_system = if no_think {

As per coding guidelines, src/**/*.rs: “Add substantial, development-oriented logs on new/changed flows; include logs at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths.”

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

In `@src/openhuman/local_ai/service/public_infer.rs` around lines 312 - 319, Add a
debug checkpoint when deciding the `no_think` branch: where `effective_system`
is set (using the `no_think` flag and the `effective_system` variable), emit a
development-level log with the stable prefix "[local_ai:infer]" that records the
branch decision and key inference parameters (at minimum `no_think`,
`max_tokens`, `allow_empty`, and the model id/identifier used). Place the log
right at the branch (both when the `no_think` true path is taken and the else
path) so entry into that control path is visible in logs; use the existing
logging/tracing facility used elsewhere in this module for consistency.
app/src/components/settings/panels/VoicePanel.tsx (1)

68-70: Add a namespaced debug checkpoint for STT readiness.

Line 68–70 changed the readiness gate, but there’s no trace for sttAssetState vs voiceResponse.stt_available. Add a dev-oriented, grep-friendly debug log at this decision point to simplify field diagnosis.

As per coding guidelines: "Add substantial, development-oriented logs on new/changed flows in TypeScript/React app code; use namespaced debug logs and dev-only detail as needed."

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

In `@app/src/components/settings/panels/VoicePanel.tsx` around lines 68 - 70, Add
a development-only, namespaced debug checkpoint at the STT readiness decision in
VoicePanel: capture and log sttAssetState, sttAssetOk, and
voiceResponse.stt_available before calling setSttReady so you can grep for the
log (e.g. "VoicePanel:stt"). Wrap the log in a dev-only guard
(process.env.NODE_ENV !== 'production') and use console.debug or the app logger
to emit a single, clear message showing the values used to compute readiness
(referencing sttAssetState, sttAssetOk, voiceResponse, assetsResponse, and
setSttReady).
app/src/utils/tauriCommands/composio.ts (1)

2-2: Split CommandResponse into a type-only import.

CommandResponse is only referenced in type positions here, so it should come from import type rather than the value import.

🛠️ Suggested change
-import { CommandResponse, isTauri } from './common';
+import type { CommandResponse } from './common';
+import { isTauri } from './common';

As per coding guidelines "Use import type for TypeScript type-only imports where appropriate".

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

In `@app/src/utils/tauriCommands/composio.ts` at line 2, The import currently
brings CommandResponse as a value; change it to a type-only import so TypeScript
knows it’s type-only: replace the single import of CommandResponse and isTauri
with a type import for CommandResponse and a normal import for isTauri (i.e.,
import type { CommandResponse } ... and import { isTauri } ...), updating the
import line in composio.ts where CommandResponse and isTauri are referenced.
app/src/overlay/OverlayApp.tsx (1)

378-380: Log polling failures at debug level instead of silently dropping them.

Right now the new fallback path has no trace when RPC polling stops working, which makes stuck-overlay reports much harder to diagnose. A throttled [overlay] console.debug here would be enough.

As per coding guidelines "Add substantial, development-oriented logs on new/changed flows in TypeScript/React app code; use namespaced debug logs and dev-only detail as needed".

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

In `@app/src/overlay/OverlayApp.tsx` around lines 378 - 380, The catch block in
OverlayApp.tsx that swallows polling errors should log them at debug level so
RPC polling failures are observable; update the catch in the OverlayApp
component's polling routine to call console.debug('[overlay] RPC poll failed',
err) (include the caught error variable) and guard it with a dev-only check
(e.g. if (process.env.NODE_ENV !== 'production')) and a simple throttle
(module-level lastDebugTs timestamp, only log if now - lastDebugTs > 5000ms) so
messages are emitted at most once every few seconds.
src/openhuman/composio/trigger_history.rs (1)

19-26: Handle OnceLock::set result explicitly instead of silently discarding it.

Ignoring GLOBAL_TRIGGER_HISTORY.set(store) hides initialization races and makes path mismatches hard to diagnose.

🔧 Proposed fix
-    let store = Arc::new(ComposioTriggerHistoryStore::new(&workspace_dir)?);
-    let _ = GLOBAL_TRIGGER_HISTORY.set(store);
+    let store = Arc::new(ComposioTriggerHistoryStore::new(&workspace_dir)?);
+    if GLOBAL_TRIGGER_HISTORY.set(store).is_err() {
+        tracing::debug!(
+            workspace_dir = %workspace_dir.display(),
+            "[composio][history] global store already initialized"
+        );
+    }
     Ok(())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/composio/trigger_history.rs` around lines 19 - 26, init_global
currently discards the result of GLOBAL_TRIGGER_HISTORY.set(store), hiding races
and path mismatches; update init_global to check the Result returned by
GLOBAL_TRIGGER_HISTORY.set(store) and handle the Err case explicitly (e.g.,
return Err with a message that includes the workspace_dir and/or the created
ComposioTriggerHistoryStore context) so callers see initialization failures;
reference the GLOBAL_TRIGGER_HISTORY OnceLock, the init_global function, and the
ComposioTriggerHistoryStore::new(workspace_dir) call when adding this explicit
error handling and propagation.
🤖 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/webhooks/ComposeioTriggerHistory.tsx`:
- Around line 18-23: The formatPayload function may return undefined because
JSON.stringify can return undefined for values like undefined, functions or
Symbols; update formatPayload to coalesce the JSON.stringify result to a string
(e.g., store the result of JSON.stringify(payload, null, 2) in a variable and
return that value if non-null/undefined, otherwise return String(payload)),
ensuring the function always returns a string and preserving the existing
try/catch behavior.

In `@app/src/hooks/useComposeioTriggerHistory.ts`:
- Around line 54-57: The hook early-returns when snapshot.sessionToken is falsy
but leaves previous cached state (entries, archiveDir, currentDayFile) intact;
update the useComposeioTriggerHistory hook so that whenever
snapshot.sessionToken is falsy you explicitly clear/reset entries, archiveDir,
and currentDayFile to their initial empty values (e.g., []/''/null) before
returning; apply the same clearing logic in the other effect that checks
snapshot.sessionToken so no stale/sensitive trigger history remains after logout
or token expiry.
- Around line 32-52: The refresh function in useComposeioTriggerHistory can run
concurrently because setInterval may call it while a prior call is still in
flight; add an in-flight guard (e.g., a useRef<boolean> isRefreshing or an
AbortController) around refresh so it immediately returns if already running,
set the guard true at start and false in finally, and ensure the interval
callback uses that guarded refresh (and clears the interval on unmount); update
references to refresh, setInterval usage, and any cleanup logic so overlapping
RPCs cannot occur.

In `@app/src/overlay/OverlayApp.tsx`:
- Around line 356-370: Change the recovery condition so the overlay switches to
STT whenever the server is recording/transcribing and the overlay is not already
in 'stt' (replace currentMode === 'idle' with currentMode !== 'stt'), and before
calling setMode('stt')/setBubble(...) clear any pending attention-dismiss timer
(e.g., clearTimeout(attentionDismissTimeout) or equivalent) so an existing
attention bubble/timer cannot prevent the UI from updating; apply the same
change to the other identical branch around line 389.

In `@src/openhuman/about_app/catalog.rs`:
- Around line 579-580: The capability description string assigned to description
in the catalog entry mistakenly uses "ComposeIO"; update that literal to use the
consistent product name "Composio" so it matches RPC/domain and other
docs—locate the description field in src/openhuman/about_app/catalog.rs (the
description variable/string) and replace "ComposeIO" with "Composio".

In `@src/openhuman/composio/bus.rs`:
- Around line 169-186: The call to store.record_trigger(...) inside the
EventHandler is performing synchronous file I/O (OpenOptions::open/writeln!) and
must be moved off the Tokio runtime; wrap the work in
tokio::task::spawn_blocking so the blocking record logic runs on a blocking
thread (e.g., call tokio::task::spawn_blocking(move ||
store.record_trigger(...)) and .await the JoinHandle), capture and log any
returned Err from the join or from record_trigger, and keep the surrounding
logic that checks trigger_history::global() and calls
tracing::warn/tracing::debug unchanged except now handling asynchronous spawn
results.

In `@src/openhuman/composio/ops.rs`:
- Around line 205-225: The logs currently include absolute filesystem paths
(config.workspace_dir via tracing::debug! and archive_dir included in the
RpcOutcome message), which may leak PII; update the code that calls
tracing::debug! and RpcOutcome::new in this flow to omit or redact full paths:
remove printing config.workspace_dir.display() from the tracing::debug! call (or
replace with a non-sensitive identifier such as workspace name or basename), and
replace archive_dir in the RpcOutcome message with a redacted or non-path
summary (e.g., "archive present" or a basename/UUID), referencing the
tracing::debug! invocation, the config.workspace_dir usage, the archive_dir
variable and the RpcOutcome::new call so you edit the exact spots.

In `@src/openhuman/composio/trigger_history.rs`:
- Around line 76-92: The record_trigger function currently opens the archive
file and calls writeln! directly, which can interleave under concurrent
processes; wrap the append with an advisory file lock using fs2::FileExt to
serialize writes: after opening the file (the file variable in record_trigger)
call file.lock_exclusive().map_err(...) before writeln!(file, "{line}") and call
file.unlock().map_err(...) (or ensure unlock in a drop guard) after the write
and flush; add the fs2 dependency and necessary use statement and propagate
errors similarly to the existing map_err messages referencing path.display().

---

Nitpick comments:
In `@app/src/components/settings/panels/VoicePanel.tsx`:
- Around line 68-70: Add a development-only, namespaced debug checkpoint at the
STT readiness decision in VoicePanel: capture and log sttAssetState, sttAssetOk,
and voiceResponse.stt_available before calling setSttReady so you can grep for
the log (e.g. "VoicePanel:stt"). Wrap the log in a dev-only guard
(process.env.NODE_ENV !== 'production') and use console.debug or the app logger
to emit a single, clear message showing the values used to compute readiness
(referencing sttAssetState, sttAssetOk, voiceResponse, assetsResponse, and
setSttReady).

In `@app/src/overlay/OverlayApp.tsx`:
- Around line 378-380: The catch block in OverlayApp.tsx that swallows polling
errors should log them at debug level so RPC polling failures are observable;
update the catch in the OverlayApp component's polling routine to call
console.debug('[overlay] RPC poll failed', err) (include the caught error
variable) and guard it with a dev-only check (e.g. if (process.env.NODE_ENV !==
'production')) and a simple throttle (module-level lastDebugTs timestamp, only
log if now - lastDebugTs > 5000ms) so messages are emitted at most once every
few seconds.

In `@app/src/utils/tauriCommands/composio.ts`:
- Line 2: The import currently brings CommandResponse as a value; change it to a
type-only import so TypeScript knows it’s type-only: replace the single import
of CommandResponse and isTauri with a type import for CommandResponse and a
normal import for isTauri (i.e., import type { CommandResponse } ... and import
{ isTauri } ...), updating the import line in composio.ts where CommandResponse
and isTauri are referenced.

In `@src/openhuman/composio/trigger_history.rs`:
- Around line 19-26: init_global currently discards the result of
GLOBAL_TRIGGER_HISTORY.set(store), hiding races and path mismatches; update
init_global to check the Result returned by GLOBAL_TRIGGER_HISTORY.set(store)
and handle the Err case explicitly (e.g., return Err with a message that
includes the workspace_dir and/or the created ComposioTriggerHistoryStore
context) so callers see initialization failures; reference the
GLOBAL_TRIGGER_HISTORY OnceLock, the init_global function, and the
ComposioTriggerHistoryStore::new(workspace_dir) call when adding this explicit
error handling and propagation.

In `@src/openhuman/local_ai/service/public_infer.rs`:
- Around line 312-319: Add a debug checkpoint when deciding the `no_think`
branch: where `effective_system` is set (using the `no_think` flag and the
`effective_system` variable), emit a development-level log with the stable
prefix "[local_ai:infer]" that records the branch decision and key inference
parameters (at minimum `no_think`, `max_tokens`, `allow_empty`, and the model
id/identifier used). Place the log right at the branch (both when the `no_think`
true path is taken and the else path) so entry into that control path is visible
in logs; use the existing logging/tracing facility used elsewhere in this module
for consistency.
🪄 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: 175e7fd0-dd45-4a85-825e-db2bd747f8fd

📥 Commits

Reviewing files that changed from the base of the PR and between 0a09d72 and 078485c.

📒 Files selected for processing (20)
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/webhooks/ComposeioTriggerHistory.tsx
  • app/src/features/voice/useVoiceSkillStatus.ts
  • app/src/hooks/useComposeioTriggerHistory.ts
  • app/src/overlay/OverlayApp.tsx
  • app/src/pages/Home.tsx
  • app/src/pages/Webhooks.tsx
  • app/src/utils/tauriCommands/composio.ts
  • app/src/utils/tauriCommands/index.ts
  • src/core/jsonrpc.rs
  • src/openhuman/about_app/catalog.rs
  • src/openhuman/composio/bus.rs
  • src/openhuman/composio/mod.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/schemas.rs
  • src/openhuman/composio/trigger_history.rs
  • src/openhuman/composio/types.rs
  • src/openhuman/local_ai/service/assets.rs
  • src/openhuman/local_ai/service/public_infer.rs
  • src/openhuman/voice/ops.rs

- Introduced the `fs2` crate to manage file locking, improving the reliability of file operations in the ComposeIO trigger history.
- Updated `ComposioTriggerHistoryStore` to utilize exclusive file locks during archive writing, ensuring data integrity.
- Enhanced error handling for file operations, providing clearer logging for failures related to file access and locking.
- Refactored the initialization logic for global trigger history to prevent duplicate setups, improving overall stability.
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: 1

🧹 Nitpick comments (1)
app/src/overlay/OverlayApp.tsx (1)

339-396: Serialize voice_server_status polling.

setInterval(() => void poll(), 2000) can queue another RPC before the previous one settles. When the core is slow or unavailable, this fallback will stack overlapping voice_server_status calls and duplicate state corrections/logs. Gate the loop with an inFlight flag or switch to recursive setTimeout so only one poll runs at a time.

Suggested fix
   useEffect(() => {
     let disposed = false;
+    let inFlight = false;

     const poll = async () => {
+      if (inFlight) return;
+      inFlight = true;
       try {
         const res = await callCoreRpc<{
           state: string;
           hotkey: string;
           activation_mode: string;
           transcription_count: number;
           last_error: string | null;
         }>({ method: 'openhuman.voice_server_status' });

         if (disposed) return;

         const serverState = res.state; // 'stopped' | 'idle' | 'recording' | 'transcribing'
         const currentMode = modeRef.current;
         ...
       } catch (err) {
         if (process.env.NODE_ENV !== 'production') {
           const now = Date.now();
           if (now - lastPollDebugTs > 5000) {
             lastPollDebugTs = now;
             console.debug('[overlay] RPC poll failed', err);
           }
         }
+      } finally {
+        inFlight = false;
       }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/overlay/OverlayApp.tsx` around lines 339 - 396, The poll loop in
useEffect can start overlapping RPCs because setInterval calls poll every 2s
regardless of previous completion; modify the poll logic in OverlayApp.tsx (the
poll async function invoked by setInterval) to serialize calls by adding an
inFlight boolean (or use a recursive setTimeout) so a new poll returns
immediately if inFlight is true; ensure you set inFlight = true at start and
false in finally, preserving early returns (e.g., disposed) and still performing
the same state updates via setMode, setBubble, clearDismissTimer, and goIdle
when appropriate.
🤖 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/overlay/OverlayApp.tsx`:
- Around line 375-379: The poll branch that forces goIdle() when serverState is
'idle'|'stopped' should not cancel an intentional STT linger; update the block
that checks (serverState === 'idle' || serverState === 'stopped') && currentMode
=== 'stt' to only call goIdle() when there is no pending STT dismiss timer/flag
(the timer set by dictation:released/final transcription using
STT_RELEASE_LINGER_MS), e.g. check the existing sttDismissTimerId or
sttDismissPending flag (or add one if missing) and skip the forced dismiss while
that timer/flag is active so the scheduled STT linger can complete.

---

Nitpick comments:
In `@app/src/overlay/OverlayApp.tsx`:
- Around line 339-396: The poll loop in useEffect can start overlapping RPCs
because setInterval calls poll every 2s regardless of previous completion;
modify the poll logic in OverlayApp.tsx (the poll async function invoked by
setInterval) to serialize calls by adding an inFlight boolean (or use a
recursive setTimeout) so a new poll returns immediately if inFlight is true;
ensure you set inFlight = true at start and false in finally, preserving early
returns (e.g., disposed) and still performing the same state updates via
setMode, setBubble, clearDismissTimer, and goIdle when appropriate.
🪄 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: e96ca329-8171-4f35-9be2-9705f15d65c7

📥 Commits

Reviewing files that changed from the base of the PR and between 078485c and aa8d9a9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Cargo.toml
  • app/src/components/settings/panels/VoicePanel.tsx
  • app/src/components/webhooks/ComposeioTriggerHistory.tsx
  • app/src/hooks/useComposeioTriggerHistory.ts
  • app/src/overlay/OverlayApp.tsx
  • app/src/utils/tauriCommands/composio.ts
  • src/openhuman/about_app/catalog.rs
  • src/openhuman/composio/bus.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/trigger_history.rs
  • src/openhuman/local_ai/service/public_infer.rs
✅ Files skipped from review due to trivial changes (5)
  • Cargo.toml
  • src/openhuman/about_app/catalog.rs
  • app/src/components/settings/panels/VoicePanel.tsx
  • src/openhuman/local_ai/service/public_infer.rs
  • app/src/utils/tauriCommands/composio.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/src/components/webhooks/ComposeioTriggerHistory.tsx
  • src/openhuman/composio/ops.rs
  • src/openhuman/composio/bus.rs
  • app/src/hooks/useComposeioTriggerHistory.ts
  • src/openhuman/composio/trigger_history.rs

Comment on lines +375 to +379
// Server is idle/stopped but overlay thinks it's in stt → dismiss
if ((serverState === 'idle' || serverState === 'stopped') && currentMode === 'stt') {
console.debug(`[overlay] poll sync: server=${serverState}, overlay=stt → dismissing`);
goIdle();
}
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

Don't let the fallback poll cancel the intentional STT linger.

dictation:released and final transcription both schedule STT_RELEASE_LINGER_MS, but this branch calls goIdle() immediately on the next idle/stopped poll. If the poll lands during that window, the final STT bubble disappears early and the linger becomes timing-dependent. Skip the forced dismiss while an STT dismiss timer is already pending.

Suggested fix
-        if ((serverState === 'idle' || serverState === 'stopped') && currentMode === 'stt') {
+        if (
+          (serverState === 'idle' || serverState === 'stopped') &&
+          currentMode === 'stt' &&
+          dismissTimerRef.current === null
+        ) {
           console.debug(`[overlay] poll sync: server=${serverState}, overlay=stt → dismissing`);
           goIdle();
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/overlay/OverlayApp.tsx` around lines 375 - 379, The poll branch that
forces goIdle() when serverState is 'idle'|'stopped' should not cancel an
intentional STT linger; update the block that checks (serverState === 'idle' ||
serverState === 'stopped') && currentMode === 'stt' to only call goIdle() when
there is no pending STT dismiss timer/flag (the timer set by
dictation:released/final transcription using STT_RELEASE_LINGER_MS), e.g. check
the existing sttDismissTimerId or sttDismissPending flag (or add one if missing)
and skip the forced dismiss while that timer/flag is active so the scheduled STT
linger can complete.

@senamakel senamakel merged commit a115758 into tinyhumansai:main Apr 12, 2026
8 of 9 checks passed
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