Skip to content

feat: add Capacitor Android build with MobilePlatform and native MCP transport#3650

Open
3aKHP wants to merge 5 commits intochatboxai:mainfrom
3aKHP:feat/android-capacitor
Open

feat: add Capacitor Android build with MobilePlatform and native MCP transport#3650
3aKHP wants to merge 5 commits intochatboxai:mainfrom
3aKHP:feat/android-capacitor

Conversation

@3aKHP
Copy link
Copy Markdown

@3aKHP 3aKHP commented Apr 11, 2026

Closes #3639

Summary

This PR adds a production-ready Android/Capacitor build target to the open-source edition of Chatbox.

What's included

  • Capacitor Android project (android/, capacitor.config.ts): generated by npx cap add android, configured with appId: app.chatboxai.chatbox. Build with pnpm mobile:sync:android && cd android && ./gradlew assembleDebug.
  • MobilePlatform (src/renderer/platform/mobile_platform.ts): extends WebPlatform with type = 'mobile', activating all existing mobile UI branches (sidebar hide, SplashScreen, mobile routing history, etc.). Device metadata resolved via @capacitor/app and @capacitor/device.
  • Native MCP HTTP transport (src/renderer/packages/mcp/mobile-http-transport.ts): on Capacitor the WebView enforces CORS, blocking standard fetch-based MCP connections. MobileStreamableHTTPTransport routes POST/DELETE through CapacitorHttp (OS networking, CORS-exempt) and GET/SSE through createNativeReadableStream with a line-by-line SSE decoder.
  • MCP UI gating: stdio transport option hidden on non-desktop platforms; featureFlags.mcp enabled for 'mobile' and 'web' platform types.
  • MUI typography.fontSize fix: the previous formula (userFontSize * 14) / 16 produced 12.25 px at default settings — ~12.5% smaller than the MUI default. Analysis of the official chatbox-1.20.1.apk bundle confirms the correct value is a fixed 14, matching MUI's default. The user's message font preference continues to be applied via --chatbox-msg-font-size.
  • OSS build stubs: translation.ts and github-copilot.ts stubs so the open-source build compiles without the private-edition implementations (both files are already imported by the existing codebase but were missing from the OSS tree).

Commits

SHA Message
a9bc6bb chore: add OSS build stubs and tooling
c718a87 feat: add Capacitor Android project and MobilePlatform
b1bc68e feat: add native HTTP MCP transport for Capacitor and enable MCP on mobile
9198479 fix: align MUI typography fontSize with official mobile build

Build instructions

# 1. Build the renderer for Android
pnpm mobile:sync:android   # sets CHATBOX_BUILD_TARGET=mobile_app CHATBOX_BUILD_PLATFORM=android

# 2. Assemble APK (requires Android SDK, JAVA_HOME set)
cd android && ./gradlew assembleDebug

# 3. Install on device / emulator
adb install app/build/outputs/apk/debug/app-debug.apk

android/local.properties (SDK path) is excluded by android/.gitignore and must be created locally.

Test plan

  • tsc --noEmit --project tsconfig.web.json passes with no errors
  • Desktop Electron build unaffected (pnpm build)
  • Android APK builds and installs (gradlew assembleDebug)
  • MobilePlatform is selected at runtime on device (platform.type === 'mobile')
  • Sidebar is hidden by default on mobile
  • MCP HTTP server reachable from device (CORS-exempt via CapacitorHttp)
  • stdio MCP option absent from settings UI on mobile
  • MUI font sizes visually match the official chatbox-1.20.1.apk

Summary by CodeRabbit

Release Notes

  • New Features

    • Added full Android platform support with Capacitor integration.
    • Enabled MCP (Model Context Protocol) for mobile and web platforms.
    • Added mobile platform detection and environment-aware configuration.
    • Introduced OAuth authentication infrastructure.
  • Chores

    • Added Android build system and Gradle configuration.
    • Added GitHub Copilot provider support.
    • Updated package manager toolchain versions.
  • Tests

    • Added Android instrumented and unit tests.

3aKHP added 4 commits April 11, 2026 12:42
- Add translation.ts stub (no-op) so the OSS build compiles;
  the full implementation lives in the private edition only.
- Add github-copilot provider stub so providers/index.ts side-effect
  import resolves without error in the open-source build.
- Extend oauth/index.ts with OAuthCredentials / OAuthResult interfaces
  and OAuthIpcChannels constants required by the OAuth flow plumbing.
- Add .erb/scripts/delete-source-maps-runner.js build helper.
- Pin Node / pnpm versions via Volta in package.json.
- Add capacitor.config.ts (appId: app.chatboxai.chatbox, webDir points to
  the renderer dist output used by the mobile:sync:android script).
- Add the generated android/ project (via `npx cap add android`).
  android/local.properties is excluded by android/.gitignore.
- Add MobilePlatform class that extends WebPlatform with type = 'mobile',
  enabling all mobile UI branches (sidebar, SplashScreen, routing, etc.).
  Device metadata is resolved via @capacitor/app and @capacitor/device.
- Update platform/index.ts to return MobilePlatform when
  Capacitor.isNativePlatform() is true (Android / iOS native runtime).
…obile

On Android/iOS the WebView enforces CORS, so the standard browser fetch
used by StreamableHTTPClientTransport is blocked for cross-origin MCP
servers. MobileStreamableHTTPTransport routes all requests through
@capacitor/http (OS-level networking, CORS-exempt):
- POST and DELETE via CapacitorHttp.request
- GET (SSE stream) via createNativeReadableStream, parsed with a
  line-by-line SSE decoder that handles chunked native responses

Additional changes:
- controller.ts: branch on Capacitor.isNativePlatform() to use the new
  transport; guard stdio path with a window.electronAPI check so an
  actionable error is thrown on mobile instead of a silent hang.
- feature-flags.ts: enable MCP for 'mobile' and 'web' platforms, not
  just 'desktop'.
- ConfigModal.tsx: hide the stdio radio button on non-desktop platforms
  to prevent users from configuring an unsupported transport type.
The previous code computed MUI's base font size as
  (userFontSize * 14) / 16
which yields 12.25px at the default setting of 14px — ~12.5% smaller
than the MUI default of 14.

Analysis of the official chatbox-1.20.1.apk bundle confirms it uses a
fixed `fontSize: 14`, independent of the user-configurable message font
size (which is correctly applied via the --chatbox-msg-font-size CSS
variable).

Changes:
- getThemeDesign: remove fontSize parameter, hard-code typography.fontSize
  to 14 with an explanatory comment.
- useAppTheme: drop the fontSize subscription from useSettingsStore and
  remove it from the useMemo dependency array.
- Settings.tsx (navigateToSettings): update call site to the new 2-arg
  getThemeDesign signature.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

Important

Review skipped

Auto reviews are disabled on this repository. 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0da40ffa-c8b9-4310-8404-12477231e314

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

This PR adds comprehensive Android/mobile platform support to a desktop Electron application using Capacitor. It includes Android build configuration, Gradle setup with dependency management, mobile platform detection, mobile-specific MCP HTTP transport via Capacitor, OAuth infrastructure, and adjusts platform-aware feature flags and theming logic.

Changes

Cohort / File(s) Summary
Android Build Configuration
android/build.gradle, android/gradle.properties, android/gradle/wrapper/gradle-wrapper.properties, android/variables.gradle
Establishes Android project-level Gradle configuration with classpath dependencies (Android Gradle Plugin 8.7.2, Google Services 4.4.2), JVM memory settings, AndroidX support, and pinned SDK/library versions for consistent builds.
Android App Module
android/app/build.gradle, android/app/capacitor.build.gradle, android/app/proguard-rules.pro
Configures app-specific build settings including namespace, SDK versions, release build type, Capacitor plugin dependencies, Java 21 compatibility, and ProGuard template.
Android Gradle Wrapper & Settings
android/gradlew, android/gradlew.bat, android/settings.gradle, android/capacitor.settings.gradle
Provides POSIX and Windows Gradle wrapper scripts for automated Gradle invocation, and maps Capacitor modules from node_modules into the Gradle build.
Android Project Structure
android/.gitignore, android/app/.gitignore
Excludes build artifacts, Gradle outputs, IDE metadata, and local configuration from version control while preserving placeholder markers.
Android Manifest & Resources
android/app/src/main/AndroidManifest.xml, android/app/src/main/res/layout/activity_main.xml, android/app/src/main/res/xml/file_paths.xml
Declares MainActivity as exported launcher with extensive configChanges, configures FileProvider for file URI exposure, and defines layout with full-screen WebView.
Android App Icons & Styles
android/app/src/main/res/drawable/ic_launcher_background.xml, android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml, android/app/src/main/res/mipmap-anydpi-v26/*.xml, android/app/src/main/res/values/ic_launcher_background.xml, android/app/src/main/res/values/styles.xml
Defines adaptive launcher icon resources with background/foreground layers, app themes (AppTheme, AppTheme.NoActionBar, AppTheme.NoActionBarLaunch), and color resources.
Android String Resources
android/app/src/main/res/values/strings.xml
Declares localized app metadata including app_name (chatbox), package_name (app.chatboxai.chatbox), and custom_url_scheme.
Android Java Implementation
android/app/src/main/java/app/chatboxai/chatbox/MainActivity.java, android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java, android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
Provides MainActivity as Capacitor bridge entry point, plus boilerplate unit and instrumented test examples.
Mobile Platform Support
src/renderer/platform/mobile_platform.ts, src/renderer/platform/index.ts
Introduces MobilePlatform class extending WebPlatform with Capacitor-backed device info (version, platform, device name), and updates platform detection to use runtime Capacitor.isNativePlatform() instead of build-time flags.
MCP Mobile HTTP Transport
src/renderer/packages/mcp/mobile-http-transport.ts
Adds MobileStreamableHTTPTransport for routing MCP traffic through Capacitor HTTP layer to bypass CORS; manages session IDs, handles inline SSE payloads, and establishes long-lived native GET streams for server-sent events.
MCP Controller Updates
src/renderer/packages/mcp/controller.ts
Routes HTTP transport creation to MobileStreamableHTTPTransport when on Capacitor native platform, adds stdio transport validation to error on missing Electron API, and preserves fallback to SSE for non-native HTTP.
Feature Flags & Theme Configuration
src/renderer/utils/feature-flags.ts, src/renderer/hooks/useAppTheme.ts, src/renderer/modals/Settings.tsx, src/renderer/components/settings/mcp/ConfigModal.tsx
Enables MCP feature flag on mobile/web in addition to desktop, removes user-configurable fontSize parameter from theme design function (now constant), and conditionally hides stdio option in MCP settings when not on desktop.
OAuth & Provider Infrastructure
src/shared/oauth/index.ts, src/shared/providers/definitions/github-copilot.ts
Introduces OAuth interfaces (OAuthCredentials, OAuthResult, OAuthStartResult, DeviceFlowStartResult), IPC channel constants for OAuth flows, and GitHub Copilot provider definition mapping to OpenAI-compatible model factory.
Capacitor Configuration & Toolchain
capacitor.config.ts, package.json
Adds Capacitor config specifying appId, appName, webDir, and pins Node.js and pnpm versions via Volta in package.json.
Build Utilities
.erb/scripts/delete-source-maps-runner.js
Adds script to synchronously delete *.js.map source map files from Webpack output directories using rimrafSync with glob patterns.
Translation Utilities
src/renderer/packages/translation.ts
Introduces translateTexts async stub returning array of null values; placeholder for future translation API integration.

Sequence Diagram

sequenceDiagram
    participant Client as Renderer<br/>(MCP Client)
    participant CapHttp as CapacitorHttp<br/>(Mobile Layer)
    participant Native as Native<br/>iOS/Android
    participant Server as MCP<br/>Server

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Server: Mobile MCP HTTP Transport Initialization
    Client->>CapHttp: POST /initialize (mcp-session-id)
    CapHttp->>Native: HTTP POST request
    Native->>Server: Forward HTTP request
    Server->>Native: HTTP 202 Accepted + session-id header
    Native->>CapHttp: Response with headers
    CapHttp->>Client: Extract & store session-id
    end

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Server: Subsequent RPC Request
    Client->>CapHttp: POST with mcp-session-id header
    CapHttp->>Native: HTTP POST (auto-attach session-id)
    Native->>Server: Forward with session context
    Server->>Native: HTTP response
    Native->>CapHttp: Response body
    CapHttp->>Client: Parse JSON-RPC message
    end

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Server: SSE Streaming (notifications/initialized)
    Client->>CapHttp: notifications/initialized POST
    CapHttp->>Native: HTTP POST
    Server->>Native: HTTP 202 + initiate SSE
    Native->>CapHttp: Long-lived GET for SSE
    loop SSE Event Stream
        Server->>Native: data: {JSON-RPC}<br/>data: {JSON-RPC}
        Native->>CapHttp: Buffer & parse chunks
        CapHttp->>Client: Deliver JSON-RPC message
    end
    Client->>CapHttp: close()
    CapHttp->>Native: DELETE session cleanup
    Native->>Server: Terminate session
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hops with joy through Android lands,
Gradle scripts and Capacitor's hands,
Mobile platforms now take flight,
MCP flows through HTTP light,
Desktop and mobile united—
ChatBox's future brightly sighted! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.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 title clearly and concisely describes the main change: adding Capacitor Android build with MobilePlatform and native MCP transport support.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@3aKHP
Copy link
Copy Markdown
Author

3aKHP commented Apr 11, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

✅ Actions performed

Review triggered.

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.

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: 10

🧹 Nitpick comments (10)
.erb/scripts/delete-source-maps-runner.js (1)

5-7: Optional: deduplicate repeated cleanup calls.

A small array + loop keeps this easier to extend if more output folders are added later.

Refactor sketch
-rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { glob: true })
-rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { glob: true })
-rimrafSync(path.join(webpackPaths.distPath, 'preload', '*.js.map'), { glob: true })
+const sourceMapPatterns = [
+  path.join(webpackPaths.distMainPath, '*.js.map'),
+  path.join(webpackPaths.distRendererPath, '*.js.map'),
+  path.join(webpackPaths.distPath, 'preload', '*.js.map'),
+]
+
+for (const pattern of sourceMapPatterns) {
+  rimrafSync(pattern, { glob: true })
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.erb/scripts/delete-source-maps-runner.js around lines 5 - 7, The three
repeated calls to rimrafSync cleaning map files should be consolidated: build an
array of the target directories (e.g., webpackPaths.distMainPath,
webpackPaths.distRendererPath, path.join(webpackPaths.distPath, 'preload')) and
iterate over it to call rimrafSync(path.join(dir, '*.js.map'), { glob: true });
this deduplicates the cleanup logic and makes adding new output folders easier
while keeping the existing rimrafSync usage and glob option.
src/renderer/hooks/useAppTheme.ts (1)

59-93: Consider extracting the base MUI font size into a shared constant.

fontSize: 14 is now a product-level default; centralizing it helps prevent drift between theme logic and related settings/migration logic.

♻️ Minimal refactor
+const BASE_MUI_FONT_SIZE = 14

 export function getThemeDesign(realTheme: 'light' | 'dark', language: Language): ThemeOptions {
   return {
@@
-      fontSize: 14,
+      fontSize: BASE_MUI_FONT_SIZE,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/useAppTheme.ts` around lines 59 - 93, The theme hardcodes
fontSize: 14 inside getThemeDesign; extract this into a shared constant (e.g.,
BASE_MUI_FONT_SIZE) and replace the literal in getThemeDesign with that
constant, export it so other modules/migrations/settings can import the
canonical value, and update any related places that reference the magic number
to use the new constant to avoid drift.
src/renderer/packages/translation.ts (1)

7-13: LGTM! Stub implementation is correct.

The function correctly maintains the API contract by returning an array of nulls with the same length as the input. The async keyword automatically wraps the return value in a Promise, and the caller in MessageErrTips.tsx already handles null returns gracefully using the null-coalescing operator.

💡 Optional: Consider adding a dev-mode warning

For improved developer experience, you could optionally add a console warning in development mode to alert developers that translation is unavailable in the OSS build:

 export async function translateTexts(
   texts: string[],
   _targetLang: string,
   _options?: { sourceLang?: string }
 ): Promise<(string | null)[]> {
+  if (process.env.NODE_ENV === 'development') {
+    console.warn('Translation is not available in the OSS edition')
+  }
   return texts.map(() => null)
 }

However, since the stub is well-documented and the feature is intentionally unavailable in OSS, this is purely optional.

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

In `@src/renderer/packages/translation.ts` around lines 7 - 13, The translateTexts
stub currently returns the correct Promise of nulls but optionally add a
dev-only warning to make the lack of translation obvious during development:
inside translateTexts, detect development mode (e.g., process.env.NODE_ENV ===
'development' or an existing isDev flag) and call console.warn once (or for each
call) with a clear message that translations are unavailable in the OSS build
and translateTexts is a stub; keep the existing return behavior and do not alter
the function signature or returned array length.
src/shared/providers/definitions/github-copilot.ts (1)

9-19: Consider hiding this stub provider from OSS runtime selection.

Registering this provider into the global registry with models: [] may expose a selectable but non-functional path to users. Consider gating registration or marking it hidden in OSS builds to avoid dead-end setup UX.

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

In `@src/shared/providers/definitions/github-copilot.ts` around lines 9 - 19, The
githubCopilotProvider entry currently registers a stub with
defaultSettings.models = [] which can show a selectable but non-functional
option; update the registration so the provider is not exposed in OSS runtimes
by either adding a hidden flag or gating its registration behind a build/runtime
check. Modify the call to defineProvider for githubCopilotProvider to include a
property like hidden: true (or an isInternal/isExperimental flag) or wrap the
export/registration logic with a condition (e.g., process.env.RUNTIME !== 'oss'
or a feature-flag) so the provider is omitted from the global registry in OSS
builds; ensure you adjust any consumer code that lists providers to respect the
hidden/flag field and reference githubCopilotProvider, defineProvider, and
defaultSettings.models while making the change.
src/shared/oauth/index.ts (2)

21-41: Prefer discriminated unions for result contracts.

Current shapes allow impossible states (success: true without required payload, success: false without error). A discriminated union tightens correctness and simplifies consumers.

Proposed refactor
-export interface OAuthResult {
-  success: boolean
-  error?: string
-  credentials?: OAuthCredentials
-}
+export type OAuthResult =
+  | { success: true; credentials: OAuthCredentials }
+  | { success: false; error: string }

-export interface OAuthStartResult {
-  success: boolean
-  error?: string
-  authUrl?: string
-}
+export type OAuthStartResult =
+  | { success: true; authUrl: string }
+  | { success: false; error: string }

-export interface DeviceFlowStartResult {
-  success: boolean
-  error?: string
-  deviceCode?: string
-  userCode?: string
-  verificationUri?: string
-  expiresIn?: number
-  interval?: number
-}
+export type DeviceFlowStartResult =
+  | {
+      success: true
+      deviceCode: string
+      userCode: string
+      verificationUri: string
+      expiresIn: number
+      interval?: number
+    }
+  | { success: false; error: string }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/oauth/index.ts` around lines 21 - 41, Replace the loose interfaces
OAuthResult, OAuthStartResult, and DeviceFlowStartResult with discriminated
union types so consumers cannot get impossible states; e.g., define each as a
union of { success: true, /* required payload props */ } | { success: false,
error: string }, ensuring for OAuthResult the success arm requires credentials:
OAuthCredentials, for OAuthStartResult the success arm requires authUrl: string,
and for DeviceFlowStartResult the success arm requires
deviceCode/userCode/verificationUri/expiresIn/interval; update any call
sites/types accordingly to use the new discriminated unions (refer to the
OAuthResult, OAuthStartResult, and DeviceFlowStartResult symbols).

14-19: Clarify time-unit semantics on OAuth fields.

expiresAt and expiresIn use different time models (absolute vs duration). Add explicit unit docs to prevent seconds-vs-milliseconds bugs at call sites.

Proposed tweak
 export interface OAuthCredentials {
   accessToken: string
   refreshToken?: string
+  /** Unix epoch in milliseconds */
   expiresAt?: number
   tokenType?: string
 }
@@
 export interface DeviceFlowStartResult {
   success: boolean
   error?: string
   deviceCode?: string
   userCode?: string
   verificationUri?: string
+  /** Duration in seconds from RFC 8628 */
   expiresIn?: number
+  /** Poll interval in seconds */
   interval?: number
 }

Also applies to: 33-41

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

In `@src/shared/oauth/index.ts` around lines 14 - 19, Update the OAuth types to
explicitly document time-unit semantics: add JSDoc comments to
OAuthCredentials.expiresAt and any expiresIn fields (e.g., other interfaces
around lines 33–41) stating the exact unit used (for example: "expiresAt —
milliseconds since UNIX epoch" and "expiresIn — lifetime in seconds"), or
alternatively standardize both to the same unit and document that choice; ensure
the comments are adjacent to the OAuthCredentials interface and the named
expiresIn fields so callers know whether to use seconds or milliseconds.
android/gradle/wrapper/gradle-wrapper.properties (1)

4-4: Consider increasing Gradle download timeout to reduce CI flakiness.

networkTimeout=10000 is often too aggressive for wrapper downloads in shared CI runners.

Proposed tweak
-networkTimeout=10000
+networkTimeout=60000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/gradle/wrapper/gradle-wrapper.properties` at line 4, The Gradle
wrapper timeout is set too low via the networkTimeout property; update the
gradle-wrapper.properties entry networkTimeout from 10000 to a much larger value
(e.g., 600000) to reduce CI download failures, save the file, and verify wrapper
downloads succeed in CI; the key to change is the networkTimeout property in
gradle-wrapper.properties.
android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java (1)

14-17: Replace template arithmetic test with a project-relevant unit test.

addition_isCorrect is scaffold boilerplate and doesn’t validate app behavior.

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

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java` around
lines 14 - 17, The current ExampleUnitTest.addition_isCorrect scaffold test
should be replaced with a project-relevant unit test: open the ExampleUnitTest
class and remove or replace the addition_isCorrect method with one or more `@Test`
methods that exercise real app logic (for example, a pure function in AppUtils,
a ViewModel method like MainViewModel.getX, or a utility such as
DateFormatter.formatDate), import and instantiate the target class, provide any
necessary test inputs or mocks, then use JUnit assertions
(assertEquals/assertTrue/assertThrows) to verify the expected outputs/behavior;
keep the test deterministic, name the new test to reflect the behavior being
validated, and ensure it compiles in the current test configuration.
android/app/build.gradle (1)

20-23: Consider enabling shrink/obfuscation for release builds.

For a production-targeted Android build, Line 21 (minifyEnabled false) is a weak default. Consider turning it on for size/security hardening before release distribution.

Suggested hardening
 release {
-    minifyEnabled false
-    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+    minifyEnabled true
+    shrinkResources true
+    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/build.gradle` around lines 20 - 23, Change the Android release
block to enable code shrinking/obfuscation by setting minifyEnabled true for the
release build (the release {} block and its minifyEnabled setting), enable
shrinkResources true if desired, and ensure proguardFiles (the existing
getDefaultProguardFile('proguard-android.txt') and 'proguard-rules.pro') contain
the necessary keep rules for your libraries/Reflection/serialization so the app
still functions; run a release build and smoke-test features to validate and
iterate on proguard-rules.pro to prevent runtime breakage.
src/renderer/packages/mcp/controller.ts (1)

15-19: Reject unsupported stdio configs before runtime.

This guard is safe, but it fires only after the server is instantiated and started. Because persisted MCP configs still allow type: 'stdio', mobile/web clients can keep reloading an enabled server that is guaranteed to fail on every launch. Validate or migrate unsupported transports when configs are loaded/saved instead of only throwing here.

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

In `@src/renderer/packages/mcp/controller.ts` around lines 15 - 19, Persisted MCP
configs may still contain transportConfig.type === 'stdio' which will always
fail on non-desktop clients; instead of only throwing inside the runtime guard
that checks window.electronAPI, add validation/migration when configs are loaded
or saved. Implement a validateOrMigrateMcpConfig function (called from the
existing config load/save paths) that inspects transportConfig.type, removes or
replaces 'stdio' entries for non-desktop platforms, and persists the updated
config; keep the runtime guard in the start/instantiate code (the block that
checks transportConfig and window.electronAPI) as a last-resort safety net but
ensure configs are fixed earlier to prevent repeated failing restarts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/app/build.gradle`:
- Around line 47-54: Replace the broad try/catch that swallows all errors around
reading google-services.json with an explicit existence/size check: test
file('google-services.json').exists() and .length() (or .size()) before
attempting to apply plugin 'com.google.gms.google-services', and only skip
applying the plugin when the file is genuinely absent/empty; do not catch
Exception around the read/apply logic—if an unexpected error occurs while
evaluating or applying the plugin (related to servicesJSON or apply plugin:
'com.google.gms.google-services'), let it surface or log it as an error instead
of silently logging info so real setup failures aren’t hidden.

In `@android/app/capacitor.build.gradle`:
- Around line 5-6: The build currently sets Java bytecode level via
sourceCompatibility and targetCompatibility to JavaVersion.VERSION_21, which is
undocumented and will break developers without JDK 21; either document the Java
21 requirement and set org.gradle.java.home in gradle.properties (and update
README/CONTRIBUTING with installation/path guidance) or change
sourceCompatibility/targetCompatibility from JavaVersion.VERSION_21 to
JavaVersion.VERSION_17 in capacitor.build.gradle if Java 21 language/bytecode
features are not required; update the project docs accordingly so contributors
know which JDK to install.

In
`@android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java`:
- Line 24: Update the expected package name in the instrumented test: in
ExampleInstrumentedTest (the method using appContext.getPackageName() and
assertEquals), change the expected string from "com.getcapacitor.app" to the
actual app id "app.chatboxai.chatbox" so the assertEquals compares
appContext.getPackageName() against the correct package name.

In `@android/app/src/main/AndroidManifest.xml`:
- Line 5: Change the Android manifest backup settings to disable automatic
backups: locate the android:allowBackup attribute in the AndroidManifest
(attribute android:allowBackup currently set to "true") and set it to "false";
additionally, explicitly disable full-data backups by adding
android:fullBackupContent="false" (or point it to an empty backup rules XML) on
the same <application> element to ensure no user/chat data is included in system
backups.

In `@android/app/src/main/res/xml/file_paths.xml`:
- Around line 3-4: The FileProvider config in file_paths.xml currently uses
external-path and cache-path entries with path="." which exposes entire storage;
narrow these to explicit subdirectories by changing the external-path
name="my_images" path="." and cache-path name="my_cache_images" path="." entries
to point at dedicated folders (for example a fixed app images dir under external
storage and a specific cache subfolder) so only those subfolders are exposed;
update the external-path and cache-path entries (names: my_images,
my_cache_images) to use concrete subdirectory paths like an app-specific images
folder and cache/images folder and document the chosen folder names in the
manifest or README.

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java`:
- Line 1: Update the test package declaration to match the app namespace and
move the test file into the corresponding directory: change the package line in
ExampleUnitTest (class ExampleUnitTest) from com.getcapacitor.myapp to
app.chatboxai.chatbox, then relocate the file to
android/app/src/test/java/app/chatboxai/chatbox/ and adjust any imports or
references if necessary so the class compiles under the new package.

In `@android/gradlew.bat`:
- Around line 1-94: The batch script (contains labels like :execute, :fail, and
header lines starting with `@rem` and `@if` "%DEBUG%") currently has LF-only
endings; convert the file to use CRLF line endings and commit the change so
cmd.exe parses it reliably. Also add or update a .gitattributes entry to enforce
CRLF for .bat files (e.g., ensure *.bat text eol=crlf) or set core.autocrlf
appropriately, then normalize the repository (re-save the gradlew.bat with CRLF
and recommit) to prevent future LF-only commits.

In `@src/renderer/packages/mcp/mobile-http-transport.ts`:
- Around line 151-165: The _extractSseData method currently only matches "data:
" (with a space) and concatenates lines without newlines, breaking JSON when
payloads span multiple data lines; update _extractSseData to accept the full
wire format (match "data:" with or without a following space and also bare
"data") and when accumulating multiple data lines insert the SSE-preserved
newline between them (i.e., join successive data line values with "\n" rather
than concatenating directly), ensuring the final assembled string follows the
SSE spec for data fields.
- Around line 190-196: The native stream creation and reader acquisition
(createNativeReadableStream and stream.getReader) must be moved inside the
existing guarded try/catch in _startGetSse so synchronous throws are caught and
will trigger the existing error handling (onerror or the catch block) instead of
being lost; update _startGetSse to perform const stream =
createNativeReadableStream({...}) and const reader = stream.getReader() inside
the try, use this._buildHeaders as before, and ensure any cleanup or
reader.release/reader.cancel remains in the finally or catch as appropriate so
errors during setup propagate to onerror.

In `@src/shared/oauth/index.ts`:
- Around line 46-54: Add ipcMain.handle registrations in the main process for
each channel defined on OAuthIpcChannels (LOGIN, START_LOGIN, EXCHANGE_CODE,
START_DEVICE_FLOW, WAIT_DEVICE_TOKEN, REFRESH, CANCEL). For each channel call
ipcMain.handle('oauth:xxx', ...) and delegate to the corresponding main-process
OAuth implementation (e.g., functions like handleLogin, startLogin,
exchangeCode, startDeviceFlow, waitDeviceToken, refreshToken, cancelOAuth)
ensuring you validate args and return results/errors to the renderer; register
these handlers during your app's main-process initialization so the renderer
invocations in useOAuth.ts have corresponding handlers.

---

Nitpick comments:
In @.erb/scripts/delete-source-maps-runner.js:
- Around line 5-7: The three repeated calls to rimrafSync cleaning map files
should be consolidated: build an array of the target directories (e.g.,
webpackPaths.distMainPath, webpackPaths.distRendererPath,
path.join(webpackPaths.distPath, 'preload')) and iterate over it to call
rimrafSync(path.join(dir, '*.js.map'), { glob: true }); this deduplicates the
cleanup logic and makes adding new output folders easier while keeping the
existing rimrafSync usage and glob option.

In `@android/app/build.gradle`:
- Around line 20-23: Change the Android release block to enable code
shrinking/obfuscation by setting minifyEnabled true for the release build (the
release {} block and its minifyEnabled setting), enable shrinkResources true if
desired, and ensure proguardFiles (the existing
getDefaultProguardFile('proguard-android.txt') and 'proguard-rules.pro') contain
the necessary keep rules for your libraries/Reflection/serialization so the app
still functions; run a release build and smoke-test features to validate and
iterate on proguard-rules.pro to prevent runtime breakage.

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java`:
- Around line 14-17: The current ExampleUnitTest.addition_isCorrect scaffold
test should be replaced with a project-relevant unit test: open the
ExampleUnitTest class and remove or replace the addition_isCorrect method with
one or more `@Test` methods that exercise real app logic (for example, a pure
function in AppUtils, a ViewModel method like MainViewModel.getX, or a utility
such as DateFormatter.formatDate), import and instantiate the target class,
provide any necessary test inputs or mocks, then use JUnit assertions
(assertEquals/assertTrue/assertThrows) to verify the expected outputs/behavior;
keep the test deterministic, name the new test to reflect the behavior being
validated, and ensure it compiles in the current test configuration.

In `@android/gradle/wrapper/gradle-wrapper.properties`:
- Line 4: The Gradle wrapper timeout is set too low via the networkTimeout
property; update the gradle-wrapper.properties entry networkTimeout from 10000
to a much larger value (e.g., 600000) to reduce CI download failures, save the
file, and verify wrapper downloads succeed in CI; the key to change is the
networkTimeout property in gradle-wrapper.properties.

In `@src/renderer/hooks/useAppTheme.ts`:
- Around line 59-93: The theme hardcodes fontSize: 14 inside getThemeDesign;
extract this into a shared constant (e.g., BASE_MUI_FONT_SIZE) and replace the
literal in getThemeDesign with that constant, export it so other
modules/migrations/settings can import the canonical value, and update any
related places that reference the magic number to use the new constant to avoid
drift.

In `@src/renderer/packages/mcp/controller.ts`:
- Around line 15-19: Persisted MCP configs may still contain
transportConfig.type === 'stdio' which will always fail on non-desktop clients;
instead of only throwing inside the runtime guard that checks
window.electronAPI, add validation/migration when configs are loaded or saved.
Implement a validateOrMigrateMcpConfig function (called from the existing config
load/save paths) that inspects transportConfig.type, removes or replaces 'stdio'
entries for non-desktop platforms, and persists the updated config; keep the
runtime guard in the start/instantiate code (the block that checks
transportConfig and window.electronAPI) as a last-resort safety net but ensure
configs are fixed earlier to prevent repeated failing restarts.

In `@src/renderer/packages/translation.ts`:
- Around line 7-13: The translateTexts stub currently returns the correct
Promise of nulls but optionally add a dev-only warning to make the lack of
translation obvious during development: inside translateTexts, detect
development mode (e.g., process.env.NODE_ENV === 'development' or an existing
isDev flag) and call console.warn once (or for each call) with a clear message
that translations are unavailable in the OSS build and translateTexts is a stub;
keep the existing return behavior and do not alter the function signature or
returned array length.

In `@src/shared/oauth/index.ts`:
- Around line 21-41: Replace the loose interfaces OAuthResult, OAuthStartResult,
and DeviceFlowStartResult with discriminated union types so consumers cannot get
impossible states; e.g., define each as a union of { success: true, /* required
payload props */ } | { success: false, error: string }, ensuring for OAuthResult
the success arm requires credentials: OAuthCredentials, for OAuthStartResult the
success arm requires authUrl: string, and for DeviceFlowStartResult the success
arm requires deviceCode/userCode/verificationUri/expiresIn/interval; update any
call sites/types accordingly to use the new discriminated unions (refer to the
OAuthResult, OAuthStartResult, and DeviceFlowStartResult symbols).
- Around line 14-19: Update the OAuth types to explicitly document time-unit
semantics: add JSDoc comments to OAuthCredentials.expiresAt and any expiresIn
fields (e.g., other interfaces around lines 33–41) stating the exact unit used
(for example: "expiresAt — milliseconds since UNIX epoch" and "expiresIn —
lifetime in seconds"), or alternatively standardize both to the same unit and
document that choice; ensure the comments are adjacent to the OAuthCredentials
interface and the named expiresIn fields so callers know whether to use seconds
or milliseconds.

In `@src/shared/providers/definitions/github-copilot.ts`:
- Around line 9-19: The githubCopilotProvider entry currently registers a stub
with defaultSettings.models = [] which can show a selectable but non-functional
option; update the registration so the provider is not exposed in OSS runtimes
by either adding a hidden flag or gating its registration behind a build/runtime
check. Modify the call to defineProvider for githubCopilotProvider to include a
property like hidden: true (or an isInternal/isExperimental flag) or wrap the
export/registration logic with a condition (e.g., process.env.RUNTIME !== 'oss'
or a feature-flag) so the provider is omitted from the global registry in OSS
builds; ensure you adjust any consumer code that lists providers to respect the
hidden/flag field and reference githubCopilotProvider, defineProvider, and
defaultSettings.models while making the change.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f59ec05b-ffac-4827-b513-a65675dd83e0

📥 Commits

Reviewing files that changed from the base of the PR and between b45fc52 and 9198479.

⛔ Files ignored due to path filters (27)
  • android/app/src/main/res/drawable-land-hdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-mdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-land-xxxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-hdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-mdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable-port-xxxhdpi/splash.png is excluded by !**/*.png
  • android/app/src/main/res/drawable/splash.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png is excluded by !**/*.png
  • android/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (40)
  • .erb/scripts/delete-source-maps-runner.js
  • android/.gitignore
  • android/app/.gitignore
  • android/app/build.gradle
  • android/app/capacitor.build.gradle
  • android/app/proguard-rules.pro
  • android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/app/chatboxai/chatbox/MainActivity.java
  • android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  • android/app/src/main/res/drawable/ic_launcher_background.xml
  • android/app/src/main/res/layout/activity_main.xml
  • android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  • android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  • android/app/src/main/res/values/ic_launcher_background.xml
  • android/app/src/main/res/values/strings.xml
  • android/app/src/main/res/values/styles.xml
  • android/app/src/main/res/xml/file_paths.xml
  • android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java
  • android/build.gradle
  • android/capacitor.settings.gradle
  • android/gradle.properties
  • android/gradle/wrapper/gradle-wrapper.properties
  • android/gradlew
  • android/gradlew.bat
  • android/settings.gradle
  • android/variables.gradle
  • capacitor.config.ts
  • package.json
  • src/renderer/components/settings/mcp/ConfigModal.tsx
  • src/renderer/hooks/useAppTheme.ts
  • src/renderer/modals/Settings.tsx
  • src/renderer/packages/mcp/controller.ts
  • src/renderer/packages/mcp/mobile-http-transport.ts
  • src/renderer/packages/translation.ts
  • src/renderer/platform/index.ts
  • src/renderer/platform/mobile_platform.ts
  • src/renderer/utils/feature-flags.ts
  • src/shared/oauth/index.ts
  • src/shared/providers/definitions/github-copilot.ts

Comment thread android/app/build.gradle Outdated
Comment on lines +47 to +54
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
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 swallow all Google Services configuration errors.

Line 52 catches all exceptions, which can hide real setup failures and silently disable push support. Prefer explicit existence/size checks instead of reading servicesJSON.text as control flow.

Suggested fix
-try {
-    def servicesJSON = file('google-services.json')
-    if (servicesJSON.text) {
-        apply plugin: 'com.google.gms.google-services'
-    }
-} catch(Exception e) {
-    logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
-}
+def servicesJSON = file('google-services.json')
+if (servicesJSON.exists() && servicesJSON.length() > 0) {
+    apply plugin: 'com.google.gms.google-services'
+} else {
+    logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
def servicesJSON = file('google-services.json')
if (servicesJSON.exists() && servicesJSON.length() > 0) {
apply plugin: 'com.google.gms.google-services'
} else {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/build.gradle` around lines 47 - 54, Replace the broad try/catch
that swallows all errors around reading google-services.json with an explicit
existence/size check: test file('google-services.json').exists() and .length()
(or .size()) before attempting to apply plugin 'com.google.gms.google-services',
and only skip applying the plugin when the file is genuinely absent/empty; do
not catch Exception around the read/apply logic—if an unexpected error occurs
while evaluating or applying the plugin (related to servicesJSON or apply
plugin: 'com.google.gms.google-services'), let it surface or log it as an error
instead of silently logging info so real setup failures aren’t hidden.

Comment thread android/app/capacitor.build.gradle Outdated
Comment on lines +5 to +6
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Java/Gradle compatibility signals =="
rg -n --iglob '**/*.{gradle,gradle.kts,properties,md,yml,yaml}' \
  'JavaVersion\.VERSION_21|sourceCompatibility|targetCompatibility|actions/setup-java|java-version|org\.gradle\.java\.home|com\.android\.tools\.build:gradle'

echo
echo "Expected: CI/workflow and contributor docs explicitly require Java 21 if VERSION_21 is kept."

Repository: chatboxai/chatbox

Length of output: 447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking CI Workflow Files ==="
find . -type f \( -name "*.yml" -o -name "*.yaml" \) -path "*/.github/workflows/*" -o -path "*/.gitlab-ci*" -o -path "*/bitbucket-pipelines*" -o -path "*/circleci*" 2>/dev/null | head -20

echo
echo "=== Looking for java-version in CI files ==="
find . -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | xargs grep -l "java" 2>/dev/null | head -10

echo
echo "=== Checking gradle.properties ==="
find . -name "gradle.properties" -exec cat -n {} +

echo
echo "=== Checking README and docs for Java version mentions ==="
find . -type f \( -name "README*" -o -name "*.md" \) -exec grep -l -i "java\|jdk\|version" {} \; 2>/dev/null | head -5

echo
echo "=== Checking .java-version file (if exists) ==="
if [ -f .java-version ]; then cat .java-version; else echo "No .java-version file"; fi

echo
echo "=== Checking .jvmrc file (if exists) ==="
if [ -f .jvmrc ]; then cat .jvmrc; else echo "No .jvmrc file"; fi

Repository: chatboxai/chatbox

Length of output: 1630


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== README.md Java version mentions ==="
grep -i "java\|jdk\|version" ./README.md | head -20

echo
echo "=== Checking docs/testing.md ==="
grep -i "java\|jdk\|setup\|environment" ./docs/testing.md | head -20

echo
echo "=== Checking docs/storage.md ==="
grep -i "java\|jdk" ./docs/storage.md || echo "No Java mentions found"

echo
echo "=== Checking .github/ISSUE_TEMPLATE/bug_report.md ==="
head -20 ./.github/ISSUE_TEMPLATE/bug_report.md

echo
echo "=== Checking ERROR_HANDLING.md ==="
grep -i "java\|jdk\|setup\|environment" ./ERROR_HANDLING.md || echo "No Java mentions found"

echo
echo "=== Checking for CONTRIBUTING.md or BUILD.md ==="
find . -type f \( -name "CONTRIBUTING*" -o -name "BUILD*" -o -name "INSTALL*" \) -exec echo "File: {}" \; -exec head -30 {} \;

Repository: chatboxai/chatbox

Length of output: 1214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking gradle wrapper properties ==="
find . -name "gradle-wrapper.properties" -exec cat -n {} +

echo
echo "=== Looking for any .github directory structure ==="
ls -la .github/ 2>/dev/null || echo "No .github directory"

echo
echo "=== Looking for any workflows ==="
find . -type d -name "workflows" 2>/dev/null

echo
echo "=== Checking for package.json to understand Android build process ==="
cat -n package.json | head -50

echo
echo "=== Checking for android/package.json or android build scripts ==="
find android -name "package.json" -o -name "build.sh" -o -name "Makefile" 2>/dev/null

Repository: chatboxai/chatbox

Length of output: 4156


🌐 Web query:

Android Gradle Plugin 8.7.2 Java version requirements compatibility

💡 Result:

Android Gradle Plugin (AGP) 8.7.2 requires JDK 17 (minimum and default). This is the Java runtime version needed to execute the AGP and Gradle daemon for builds. Key compatibility details: - Minimum Gradle version: 8.9 - Default Gradle version: 8.9 (Gradle 8.9 requires JVM 8+, but AGP specifies JDK 17) - To configure: Set Gradle JDK to 17 in Android Studio (File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JDK). Use GRADLE_LOCAL_JAVA_HOME or Android Studio's bundled JDK (JBR 17). - For project source compatibility: Set compileOptions { sourceCompatibility/targetCompatibility = JavaVersion.VERSION_17 } and kotlin { jvmToolchain(17) }. - Error if using lower: "Android Gradle plugin requires Java 17 to run. You are currently using Java 11." AGP 8.7.x patch releases (like 8.7.2) inherit the compatibility from 8.7.0 major release.

Citations:


Document Java 21 requirement in gradle.properties and contributor docs, or adjust source/target compatibility.

The project sets sourceCompatibility and targetCompatibility to JavaVersion.VERSION_21, but this requirement is not documented anywhere. Developers without Java 21 available will encounter invalid source release: 21 errors during Android builds. AGP 8.7.2 requires JDK 17 minimum, but the project also targets Java 21 bytecode. Either:

  1. Add to gradle.properties:

    org.gradle.java.home=[path to Java 21]
    

    and document Java 21 as a requirement in README/CONTRIBUTING, or

  2. Downgrade sourceCompatibility/targetCompatibility to JavaVersion.VERSION_17 if Java 21 features are not required.

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

In `@android/app/capacitor.build.gradle` around lines 5 - 6, The build currently
sets Java bytecode level via sourceCompatibility and targetCompatibility to
JavaVersion.VERSION_21, which is undocumented and will break developers without
JDK 21; either document the Java 21 requirement and set org.gradle.java.home in
gradle.properties (and update README/CONTRIBUTING with installation/path
guidance) or change sourceCompatibility/targetCompatibility from
JavaVersion.VERSION_21 to JavaVersion.VERSION_17 in capacitor.build.gradle if
Java 21 language/bytecode features are not required; update the project docs
accordingly so contributors know which JDK to install.

// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

assertEquals("com.getcapacitor.app", appContext.getPackageName());
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

Fix incorrect expected package in instrumented test.

At Line [24], the assertion expects com.getcapacitor.app, but this PR sets the app id to app.chatboxai.chatbox. This test will fail in CI/device runs.

✅ Proposed fix
-        assertEquals("com.getcapacitor.app", appContext.getPackageName());
+        assertEquals("app.chatboxai.chatbox", appContext.getPackageName());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assertEquals("com.getcapacitor.app", appContext.getPackageName());
assertEquals("app.chatboxai.chatbox", appContext.getPackageName());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java`
at line 24, Update the expected package name in the instrumented test: in
ExampleInstrumentedTest (the method using appContext.getPackageName() and
assertEquals), change the expected string from "com.getcapacitor.app" to the
actual app id "app.chatboxai.chatbox" so the assertEquals compares
appContext.getPackageName() against the correct package name.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
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

Harden backup settings for user data privacy.

At Line [5], android:allowBackup="true" enables backup/restore of app data, which is risky for a chat app handling potentially sensitive content.

🔐 Proposed fix
-        android:allowBackup="true"
+        android:allowBackup="false"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android:allowBackup="true"
android:allowBackup="false"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/AndroidManifest.xml` at line 5, Change the Android
manifest backup settings to disable automatic backups: locate the
android:allowBackup attribute in the AndroidManifest (attribute
android:allowBackup currently set to "true") and set it to "false";
additionally, explicitly disable full-data backups by adding
android:fullBackupContent="false" (or point it to an empty backup rules XML) on
the same <application> element to ensure no user/chat data is included in system
backups.

Comment on lines +3 to +4
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify FileProvider breadth + URI generation call sites (read-only)
fd -i "file_paths.xml" | xargs -r -I{} sh -c 'echo "== {} =="; sed -n "1,120p" "{}"'
fd -i "AndroidManifest.xml" | xargs -r -I{} sh -c 'echo "== {} =="; rg -n -C3 "FileProvider|FILE_PROVIDER_PATHS|grantUriPermissions|exported" "{}"'
rg -nP --type=java --type=kt -C3 '\bFileProvider\.getUriForFile\s*\('

Repository: chatboxai/chatbox

Length of output: 1214


🏁 Script executed:

# Search for FileProvider.getUriForFile usage without type restrictions
rg -i "getUriForFile\|FileProvider" --glob="*.java" --glob="*.kt" -C2

# Also search for share intent handlers or uri generation patterns
rg -i "ACTION_SEND|ACTION_VIEW|intent.*uri|shareUri" --glob="*.java" --glob="*.kt" -C2

Repository: chatboxai/chatbox

Length of output: 43


🏁 Script executed:

# Check if there are any calls to FileProvider in the entire codebase
find . -name "*.java" -o -name "*.kt" | head -20

Repository: chatboxai/chatbox

Length of output: 287


🏁 Script executed:

cat -n android/app/src/main/java/app/chatboxai/chatbox/MainActivity.java | head -50

Repository: chatboxai/chatbox

Length of output: 220


🏁 Script executed:

# Also check if there are any references to sharing or file URIs in the app
rg -i "file:|Uri\.parse|getUriForFile|shareFile|share.*intent" --glob="*.java" --glob="*.kt"

Repository: chatboxai/chatbox

Length of output: 43


Narrow FileProvider exposure paths to follow principle of least privilege.

Lines 3-4 use path="." which exposes the entire external storage and cache directories. Although FileProvider.getUriForFile() is not currently called in the codebase, this overly broad configuration should be restricted to explicit subdirectories as a hardening measure.

Suggested fix
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
-    <external-path name="my_images" path="." />
-    <cache-path name="my_cache_images" path="." />
+    <external-files-path name="shared_images" path="images/" />
+    <cache-path name="shared_cache_images" path="images/" />
 </paths>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
<external-files-path name="shared_images" path="images/" />
<cache-path name="shared_cache_images" path="images/" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/main/res/xml/file_paths.xml` around lines 3 - 4, The
FileProvider config in file_paths.xml currently uses external-path and
cache-path entries with path="." which exposes entire storage; narrow these to
explicit subdirectories by changing the external-path name="my_images" path="."
and cache-path name="my_cache_images" path="." entries to point at dedicated
folders (for example a fixed app images dir under external storage and a
specific cache subfolder) so only those subfolders are exposed; update the
external-path and cache-path entries (names: my_images, my_cache_images) to use
concrete subdirectory paths like an app-specific images folder and cache/images
folder and document the chosen folder names in the manifest or README.

@@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
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 | 🟡 Minor

Align test package with the app namespace.

The test package currently diverges from the app namespace (app.chatboxai.chatbox in android/app/build.gradle), which can cause confusion and harder future test integration.

Suggested package alignment
-package com.getcapacitor.myapp;
+package app.chatboxai.chatbox;
Also move the file to the matching directory path under `android/app/src/test/java/app/chatboxai/chatbox/`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
package com.getcapacitor.myapp;
package app.chatboxai.chatbox;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java` at
line 1, Update the test package declaration to match the app namespace and move
the test file into the corresponding directory: change the package line in
ExampleUnitTest (class ExampleUnitTest) from com.getcapacitor.myapp to
app.chatboxai.chatbox, then relocate the file to
android/app/src/test/java/app/chatboxai/chatbox/ and adjust any imports or
references if necessary so the class compiles under the new package.

Comment thread android/gradlew.bat
Comment on lines +1 to +94
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
from pathlib import Path
p = Path("android/gradlew.bat")
b = p.read_bytes()
crlf = b.count(b"\r\n")
lf = b.count(b"\n")
bare_lf = lf - crlf
print(f"file={p}")
print(f"CRLF={crlf}, LF={lf}, bare_LF={bare_lf}")
print("PASS" if bare_lf == 0 else "FAIL: contains LF-only line endings")
PY

Repository: chatboxai/chatbox

Length of output: 147


Convert line endings to CRLF for Windows batch file compatibility.

The file currently uses LF-only line endings. Windows batch files require CRLF line endings for reliable parsing in cmd.exe; LF-only endings can cause intermittent parsing and runtime errors.

🧰 Tools
🪛 Blinter (1.0.112)

[error] 1-1: Unix line endings detected. Explanation: Batch file uses Unix line endings (LF-only) which can cause GOTO/CALL label parsing failures and script malfunction due to Windows batch parser 512-byte boundary bugs. Recommendation: Convert file to Windows line endings (CRLF). Use tools like dos2unix, notepad++, or configure git with 'git config core.autocrlf true'. Context: File uses Unix line endings (LF-only) - 94 LF sequences found

(E018)


[error] 88-88: Complex quote escaping error. Explanation: Complex quote patterns may not be handled correctly by the command interpreter. Recommendation: Use triple-quote pattern """text""" for quotes within quoted strings, or escape properly with variables. Context: Complex quote escaping detected

(E028)


[error] 29-29: Unsafe SET command usage. Explanation: SET commands without proper validation or quoting can cause security issues. Recommendation: Always quote SET values and validate input: SET "var=safe value". Context: SET command value should be quoted for safety

(SEC002)


[error] 45-45: Command injection via variable substitution. Explanation: Variables containing user input used in commands may allow code injection. Recommendation: Validate and sanitize variables before use in command execution. Context: Variable used with shell operators may allow injection

(SEC013)

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

In `@android/gradlew.bat` around lines 1 - 94, The batch script (contains labels
like :execute, :fail, and header lines starting with `@rem` and `@if` "%DEBUG%")
currently has LF-only endings; convert the file to use CRLF line endings and
commit the change so cmd.exe parses it reliably. Also add or update a
.gitattributes entry to enforce CRLF for .bat files (e.g., ensure *.bat text
eol=crlf) or set core.autocrlf appropriately, then normalize the repository
(re-save the gradlew.bat with CRLF and recommit) to prevent future LF-only
commits.

Comment on lines +151 to +165
/**
* Collect and concatenate all `data:` lines from one SSE event block.
* Multiple data lines are concatenated without a separator (SSE spec §9.2.6).
*/
private _extractSseData(eventBlock: string): string {
let data = ''
for (const line of eventBlock.split('\n')) {
if (line.startsWith('data: ')) {
data += line.slice(6)
} else if (line === 'data') {
// bare "data" field with no value — counts as empty string
data += ''
}
}
return data
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

Fix SSE data: parsing to accept the full wire format.

Line 158 only matches data: with a space, and the current concatenation drops the newline that SSE preserves between multiple data: fields. Servers that emit data:{"jsonrpc":...} or split payloads across multiple data: lines will produce malformed JSON and break MCP message delivery.

💡 Proposed fix
   private _extractSseData(eventBlock: string): string {
-    let data = ''
+    const chunks: string[] = []
     for (const line of eventBlock.split('\n')) {
-      if (line.startsWith('data: ')) {
-        data += line.slice(6)
-      } else if (line === 'data') {
-        // bare "data" field with no value — counts as empty string
-        data += ''
+      if (line.startsWith('data:')) {
+        const value = line.slice(5)
+        chunks.push(value.startsWith(' ') ? value.slice(1) : value)
+      } else if (line === 'data') {
+        chunks.push('')
       }
     }
-    return data
+    return chunks.join('\n')
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/packages/mcp/mobile-http-transport.ts` around lines 151 - 165,
The _extractSseData method currently only matches "data: " (with a space) and
concatenates lines without newlines, breaking JSON when payloads span multiple
data lines; update _extractSseData to accept the full wire format (match "data:"
with or without a following space and also bare "data") and when accumulating
multiple data lines insert the SSE-preserved newline between them (i.e., join
successive data line values with "\n" rather than concatenating directly),
ensuring the final assembled string follows the SSE spec for data fields.

Comment on lines +190 to +196
const stream = createNativeReadableStream({
url: this.url,
method: 'GET',
headers: this._buildHeaders({ Accept: 'text/event-stream' }),
})

const reader = stream.getReader()
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

Move native stream setup inside the guarded error path.

createNativeReadableStream() and stream.getReader() run before the try. If either throws, _startGetSse() rejects and the void this._startGetSse() caller drops the rejection, so stream setup fails silently and onerror never fires.

💡 Proposed fix
-    const stream = createNativeReadableStream({
-      url: this.url,
-      method: 'GET',
-      headers: this._buildHeaders({ Accept: 'text/event-stream' }),
-    })
-
-    const reader = stream.getReader()
-    const decoder = new TextDecoder()
-    // Buffer for partial SSE events across chunk boundaries
-    let buffer = ''
+    let reader: ReadableStreamDefaultReader<Uint8Array> | undefined

     try {
+      const stream = createNativeReadableStream({
+        url: this.url,
+        method: 'GET',
+        headers: this._buildHeaders({ Accept: 'text/event-stream' }),
+      })
+
+      reader = stream.getReader()
+      const decoder = new TextDecoder()
+      // Buffer for partial SSE events across chunk boundaries
+      let buffer = ''
+
       while (!signal.aborted) {
         const { done, value } = await reader.read()
         if (done) break
@@
     } finally {
       try {
-        reader.releaseLock()
+        reader?.releaseLock()
       } catch {
         // Ignore errors from releasing an already-released lock
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/packages/mcp/mobile-http-transport.ts` around lines 190 - 196,
The native stream creation and reader acquisition (createNativeReadableStream
and stream.getReader) must be moved inside the existing guarded try/catch in
_startGetSse so synchronous throws are caught and will trigger the existing
error handling (onerror or the catch block) instead of being lost; update
_startGetSse to perform const stream = createNativeReadableStream({...}) and
const reader = stream.getReader() inside the try, use this._buildHeaders as
before, and ensure any cleanup or reader.release/reader.cancel remains in the
finally or catch as appropriate so errors during setup propagate to onerror.

Comment thread src/shared/oauth/index.ts
Comment on lines +46 to +54
export const OAuthIpcChannels = {
LOGIN: 'oauth:login',
START_LOGIN: 'oauth:start-login',
EXCHANGE_CODE: 'oauth:exchange-code',
START_DEVICE_FLOW: 'oauth:start-device-flow',
WAIT_DEVICE_TOKEN: 'oauth:wait-device-token',
REFRESH: 'oauth:refresh',
CANCEL: 'oauth:cancel',
} as const
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Declared OAuth channel constants:"
rg -n "^[[:space:]]+[A-Z_]+: 'oauth:" src/shared/oauth/index.ts

echo
echo "Renderer usage sites:"
rg -nP "OAuthIpcChannels\.[A-Z_]+" -C2 --type ts --type tsx

echo
echo "Main-process registrations:"
rg -nP "ipcMain\.(handle|on)\(\s*['\"]oauth:" -C2 --type ts --type tsx

echo
echo "Any direct oauth channel invocations without constants:"
rg -nP "['\"]oauth:(login|start-login|exchange-code|start-device-flow|wait-device-token|refresh|cancel)['\"]" -C2 --type ts --type tsx

Repository: chatboxai/chatbox

Length of output: 433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Declared OAuth channel constants ==="
rg -n "^[[:space:]]+[A-Z_]+: 'oauth:" src/shared/oauth/index.ts

echo
echo "=== Renderer usage of OAuthIpcChannels ==="
rg -n "OAuthIpcChannels\." --type ts --type js

echo
echo "=== Main-process ipcMain.handle/on registrations for oauth ==="
rg -n "ipcMain\.(handle|on)" --type ts --type js | grep -i oauth || echo "No matches found with grep"

echo
echo "=== All ipcMain.handle/on registrations ==="
rg -n "ipcMain\.(handle|on)" --type ts --type js -A1 | head -80

echo
echo "=== Direct oauth channel string literals (should ideally use constants) ==="
rg -n "'oauth:(login|start-login|exchange-code|start-device-flow|wait-device-token|refresh|cancel)'" --type ts --type js

Repository: chatboxai/chatbox

Length of output: 5783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Searching for 'oauth' in main directory ==="
rg -n "oauth" src/main/ --type ts --type js -i

echo
echo "=== Checking if OAuth constants are imported anywhere in main ==="
rg -n "OAuthIpcChannels" src/main/ --type ts --type js

echo
echo "=== List all files in src/main/ that might contain IPC ==="
find src/main -name "*.ts" -o -name "*.js" | grep -iE "(oauth|ipc|auth)" | head -20

echo
echo "=== Check preload file for oauth handling ==="
find src -name "*preload*" -o -name "*pre-load*" | while read file; do
  echo "File: $file"
  rg -n "oauth" "$file" -i || echo "No oauth found"
done

Repository: chatboxai/chatbox

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full useOAuth.ts file to check platform guards ==="
cat -n src/renderer/hooks/useOAuth.ts | head -150

echo
echo "=== Check for any oauth handler registration patterns ==="
rg -n "handle.*oauth|ipcMain.*oauth" --type ts --type js -i

echo
echo "=== Check if OAuth channels might be handled through different mechanism ==="
rg -n "oauth" --type ts --type js -i | grep -i "handler\|register\|setup" | head -20

Repository: chatboxai/chatbox

Length of output: 8612


OAuth IPC handlers are missing from the main process.

The renderer correctly gates all invocations with if (!isDesktop) checks, ensuring non-desktop platforms are safe. However, verification reveals that none of the seven declared OAuth channels have corresponding ipcMain.handle registrations:

  • LOGIN, START_LOGIN, EXCHANGE_CODE, START_DEVICE_FLOW, WAIT_DEVICE_TOKEN, REFRESH, CANCEL

All seven channels are invoked by src/renderer/hooks/useOAuth.ts but have no handlers in the main process. This would cause runtime failures if OAuth is invoked on desktop. The handlers must be implemented in the main process before these constants can be used.

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

In `@src/shared/oauth/index.ts` around lines 46 - 54, Add ipcMain.handle
registrations in the main process for each channel defined on OAuthIpcChannels
(LOGIN, START_LOGIN, EXCHANGE_CODE, START_DEVICE_FLOW, WAIT_DEVICE_TOKEN,
REFRESH, CANCEL). For each channel call ipcMain.handle('oauth:xxx', ...) and
delegate to the corresponding main-process OAuth implementation (e.g., functions
like handleLogin, startLogin, exchangeCode, startDeviceFlow, waitDeviceToken,
refreshToken, cancelOAuth) ensuring you validate args and return results/errors
to the renderer; register these handlers during your app's main-process
initialization so the renderer invocations in useOAuth.ts have corresponding
handlers.

Android / build:
- build.gradle: replace try/catch on google-services.json with explicit
  file.exists() && file.length() > 0 check to avoid swallowing real errors
- capacitor.build.gradle: downgrade Java source/target compatibility from
  VERSION_21 to VERSION_17 (AGP 8.7.2 requires JDK 17 minimum; VERSION_21
  was undocumented and would break contributors without JDK 21)
- AndroidManifest.xml: set android:allowBackup="false" to prevent system
  backup of potentially sensitive chat data and API keys
- file_paths.xml: switch from external-path (entire external storage) to
  external-files-path (app-specific dir only) for FileProvider least-privilege
- ExampleInstrumentedTest.java: fix package name and assertEquals expected
  value to match actual appId (app.chatboxai.chatbox)
- ExampleUnitTest.java: align package declaration with app namespace

MCP mobile transport:
- _extractSseData: match 'data:' with or without trailing space per SSE
  wire spec; join multiple data lines with '\n' (W3C EventSource §9.2.6)
- _startGetSse: move createNativeReadableStream / stream.getReader() inside
  the try block so synchronous setup errors surface via onerror instead of
  being silently swallowed by the void caller

OAuth / tooling:
- OAuthIpcChannels: expand comment to explain OSS design — ipcMain handlers
  exist only in the private edition; renderer already guards every invocation
  with isDesktop() checks so these channels are never invoked on web/mobile
- .gitattributes: add '*.bat text eol=crlf' override so gradlew.bat retains
  CRLF line endings on Windows checkout despite the global eol=lf setting
@3aKHP
Copy link
Copy Markdown
Author

3aKHP commented Apr 11, 2026

All review findings have been addressed in commit 81f965a:

Finding Resolution
build.gradle try/catch swallowing errors Replaced with file.exists() && file.length() > 0
capacitor.build.gradle Java 21 undocumented Downgraded to JavaVersion.VERSION_17 (AGP 8.7.2 requires JDK 17)
AndroidManifest.xml allowBackup="true" Set to false — chat app should not expose data to backup
file_paths.xml path="." over-exposure Switched from external-path to external-files-path (app-specific dir)
ExampleInstrumentedTest wrong package/assertEquals Fixed to app.chatboxai.chatbox
ExampleUnitTest mismatched package Fixed to app.chatboxai.chatbox
_extractSseData only matches data: with space Now matches data: with or without space; joins multiple data lines with \n per W3C EventSource spec §9.2.6
_startGetSse stream setup outside try block Moved createNativeReadableStream + getReader() inside try; reader?.releaseLock() in finally
OAuthIpcChannels — missing ipcMain handlers By design (OSS stub): the renderer gates all invocations with isDesktop() checks. Expanded the comment to make this explicit. The main-process handlers exist only in the private edition.
gradlew.bat LF line endings Added *.bat text eol=crlf to .gitattributes to override the global eol=lf setting

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.

[Feature] MCP support in android

1 participant