Skip to content

ADFA-4584 | Extract AI features into plugins and expand the plugin API#1489

Open
jatezzz wants to merge 9 commits into
stagefrom
refactor/ADFA-4584-ai-hackathon-plugin
Open

ADFA-4584 | Extract AI features into plugins and expand the plugin API#1489
jatezzz wants to merge 9 commits into
stagefrom
refactor/ADFA-4584-ai-hackathon-plugin

Conversation

@jatezzz

@jatezzz jatezzz commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Description

Extracts the AI/agent functionality (chat assistant, LLM inference, native llama libraries, agent UI/actions) out of the main app and into standalone, optional plugins. To support that, the plugin API is expanded so plugins can do
everything the built-in features used to do — access project/file/build services, listen to editor content changes, render inline (ghost text) suggestions, contribute dynamic toolbar icons, and request mic input.

All API additions are additive with default no-op implementations, so binary compatibility with existing plugins is preserved (no interface renames, no removed signatures).

Details

  • App slimming: removed the agent/ module (~50 classes), llama.cpp native libs, and the bundled llama.aar; cleaned up all dangling agent references (fragments, commands, actions, layout, bootstrap assets) and CI cache to fix the resulting build/D8 failures.
  • Plugin API surface: PluginContext now supports cross-plugin service registration/resolution and lifecycle listeners; IdeEditorService gains content-change listeners + showInlineSuggestion/dismissInlineSuggestion; ToolbarAction gains an iconProvider and IdeUIService.refreshToolbarActions for state-driven animated icons; RECORD_AUDIO declared for voice plugins.
  • Editor wiring: editor content changes are bridged to plugin callbacks and ghost text is rendered inline via GhostTextRenderer.
  • Merge fix: GhostTextRenderer now extends TracingEditorRenderer so the single editor renderer keeps the ADFA-2468 block-line data-race guards while also drawing ghost text (both onCreateRenderer overrides collapsed into one).
  • Regenerated plugin-api/api/plugin-api.api (additive only) so apiCheck passes.

Ticket

ADFA-4584

Observation

  • API changes are strictly additive with default implementations — plugins compiled against the previous API continue to load and run unchanged.
  • The editor supports only one renderer, hence merging tracing + ghost text into a single class rather than composing two renderers.
  • The extracted AI code now lives in the plugin-examples repo as ai-core-plugin (LLM backend) and ai-assistant-plugin (chat UI).

jatezzz added 4 commits July 7, 2026 16:04
Remove the embedded agent module, llama.cpp native libraries, bundled
llama.aar, and all AI UI/actions/commands from the main app. The AI
functionality now lives in the plugin-examples repo as ai-core-plugin
(LLM backend) and ai-assistant-plugin (chat UI). Clean up all dangling
agent references (fragments, layout, bootstrap assets) that broke the
build after extraction.
Broaden the plugin service surface (project manipulation, file listing, build tracking, IDEApiFacade) so extracted plugins can do what the built-in features used to. All changes are additive: keep original interface names, add default implementations, and drop no-op stubs so plugins compiled against the previous API keep loading unchanged.
Add EditorContentChangeListener plus showInlineSuggestion/dismissInlineSuggestion to IdeEditorService (default no-ops for compatibility), bridge editor content changes to plugin callbacks, and render accepted completions as inline ghost text in content coordinates. Also enable cross-plugin service resolution in PluginContext.
Add ToolbarAction.iconProvider and IdeUIService.refreshToolbarActions so plugins can drive state-based animated toolbar icons; declare RECORD_AUDIO for voice plugins. Collapse the duplicate editor renderer overrides into a single GhostTextRenderer that extends TracingEditorRenderer (keeps the ADFA-2468 block-line data-race guard), and regenerate the plugin-api golden file so apiCheck passes.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough
  • Extracted AI/agent functionality into optional standalone plugins and expanded the plugin API to support cross-plugin service registration/lookup, plugin lifecycle listeners, editor content-change callbacks, inline suggestions, and dynamic toolbar icon providers.
  • Removed embedded AI/agent wiring from the main app (native llama integration, bundled llama.aar, and related UI/actions/bootstrap assets), addressing resulting integration/build issues.
  • Added android.permission.RECORD_AUDIO to support voice/speech-enabled plugins (audio recording occurs under the app UID).
  • Implemented inline “ghost text” editor suggestions via GhostTextRenderer, now supporting multi-line previews with per-plugin ownership; added plugin-scoped clearing (clearSuggestionFor(pluginId)), and added Tab-to-commit behavior.
  • Bridged editor content changes to plugins: EditorProviderImpl now emits (content, cursorLine, cursorColumn, language) to registered editor content-change callbacks, and IdeEditorService exposes corresponding listener APIs.
  • Routed inline suggestion show/dismiss through IdeEditorService/EditorProviderImpl, tagged by pluginId, so multiple plugins can independently manage suggestions.
  • Added toolbar refresh plumbing so plugins can trigger editor toolbar rebuilds, including dynamic toolbar icon resolution via ToolbarAction.iconProvider (re-resolved on each rebuild) and IdeUIService.refreshToolbarActions().
  • Added/updated plugin manager wiring to buffer/replay editor content-change callbacks when the active editor provider isn’t ready, and to integrate lifecycle listener dispatch and shared service registry hosting.
  • Regenerated plugin-api.api to reflect the new public plugin API surface (including lifecycle/service APIs, editor content-change listener, inline suggestion controls, toolbar refresh, and icon provider).
  • Added PluginContext unit tests covering service/lifecycle behavior and registry/resource edge cases.
  • Crash resilience: updated PluginFragmentFactory fallback to catch instantiation/class-lookup failures and return a safe placeholder fragment.
  • Risk: expanded plugin API increases integration surface area; regressions are possible if any implementation misses new default/callback wiring or lifecycle expectations.
  • Risk: several plugin/renderer callback paths intentionally swallow exceptions (content-change listener exceptions, dynamic icon provider failures, ghost text rendering), which can mask underlying issues—monitor logs/telemetry and validate plugin behavior in practice.
  • Best practice: treat RECORD_AUDIO as sensitive—ensure voice plugin behavior and permission prompts are user-transparent and clearly documented.

Walkthrough

The PR expands plugin/editor integration with toolbar refresh hooks, editor content-change callbacks, inline ghost-text suggestions, and shared plugin lifecycle/service APIs. It also adds a manifest audio permission, starts animated toolbar icons, and hardens fragment fallback instantiation.

Changes

Plugin editor and toolbar plumbing

Layer / File(s) Summary
Toolbar icon provider and animation
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt, app/src/main/java/com/itsaky/androidide/actions/PluginToolbarActionItem.kt, common/src/main/java/com/itsaky/androidide/ui/ProjectActionsToolbar.kt
ToolbarAction adds iconProvider, toolbar items re-resolve provider icons during prepare, and animated drawables are started after assignment.
Toolbar refresh host wiring
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginToolbarHost.kt, app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt, plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeUIServiceImpl.kt
Adds a toolbar host interface, implements it in the editor activity, and routes refreshToolbarActions() through the active UI host.
Editor content-change contracts
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt, plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginContext.kt, plugin-api/api/plugin-api.api
Adds EditorContentChangeListener, content-change listener registration, and inline suggestion methods to the public plugin/editor APIs.
Content-change delivery and editor delegation
app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
Registers document-change delivery, forwards content callbacks, buffers them across provider swaps, and delegates inline suggestion show/dismiss calls through the editor service.
Inline ghost-text rendering
editor/src/main/java/com/itsaky/androidide/editor/ui/GhostTextRenderer.kt, editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt, editor/src/main/java/com/itsaky/androidide/editor/ui/TracingEditorRenderer.kt
Adds owned multi-line ghost-text rendering, wires editor show/dismiss/commit behavior, and keeps the base renderer subclassable.
Plugin lifecycle and shared services
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginContext.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt, plugin-api/api/plugin-api.api, plugin-api/src/test/kotlin/com/itsaky/androidide/plugins/PluginContextTest.kt
Expands plugin context/service/lifecycle APIs, adds shared-service and lifecycle dispatchers, wires them through plugin manager, and adds coverage.
Audio permission manifest entry
app/src/main/AndroidManifest.xml
Adds android.permission.RECORD_AUDIO with an explanatory comment.
Fragment fallback handling
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt
Catches fragment instantiation failures in the fallback path and returns a placeholder fragment.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • appdevforall/CodeOnTheGo#1150: Shares the EditorHandlerActivity.prepareOptionsMenu() path that this PR now uses to rebuild plugin toolbar actions.
  • appdevforall/CodeOnTheGo#1194: Extends the same editor-provider and plugin-manager plumbing that this PR updates for content callbacks and inline suggestions.

Sequence Diagram(s)

sequenceDiagram
  participant PluginManager
  participant IdeUIServiceImpl
  participant EditorHandlerActivity
  participant PluginToolbarHost
  PluginManager->>IdeUIServiceImpl: refreshToolbarActions()
  IdeUIServiceImpl->>EditorHandlerActivity: getCurrentActivity()
  EditorHandlerActivity-->>IdeUIServiceImpl: PluginToolbarHost
  IdeUIServiceImpl->>PluginToolbarHost: refreshPluginToolbarActions()
  PluginToolbarHost->>EditorHandlerActivity: prepareOptionsMenu()
Loading

Suggested reviewers: dara-abijo-adfa, itsaky-adfa

Poem

A bunny tapped the toolbar bright,
Then ghost-text hopped in through the night.
With twitches, tabs, and plugin cheer,
The editor now whispers clear.
Thump thump — the rabbits built it right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.20% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: extracting AI features into plugins and expanding the plugin API.
Description check ✅ Passed The description matches the changeset and accurately describes the plugin extraction, API expansion, and editor wiring.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ADFA-4584-ai-hackathon-plugin

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.

@jatezzz jatezzz changed the title ADFA-4584 | Extract AI features into plugins and expand the plugin API #1486 ADFA-4584 | Extract AI features into plugins and expand the plugin API Jul 7, 2026
@jatezzz

jatezzz commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt (1)

61-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a narrower exception type than blanket Exception.

Based on learnings, this codebase's convention is to catch the specific exception type observed rather than a broad catch-all, to keep fail-fast behavior during development.

♻️ Suggested narrowing
     override fun getAppSharedPreferences(prefsName: String): SharedPreferences? {
         return try {
             androidContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
-        } catch (e: Exception) {
+        } catch (e: IllegalStateException) {
             logger.error("Failed to access app SharedPreferences: $prefsName", e)
             null
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt`
around lines 61 - 68, The getAppSharedPreferences method currently catches
blanket Exception, which is too broad for this codebase’s fail-fast convention.
Update the try/catch around androidContext.getSharedPreferences to handle only
the specific exception type actually expected from this call path, and keep the
existing logger.error and null fallback behavior unchanged.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/AndroidManifest.xml`:
- Around line 29-32: `PermissionsHelper.getRequiredPermissions()` is missing
`Manifest.permission.RECORD_AUDIO`, so mic access is not requested during
onboarding even though the manifest declares it. Update the required-permissions
list in `PermissionsHelper` to include `RECORD_AUDIO`, and ensure the onboarding
flow uses that updated list so voice/speech plugins receive the runtime
permission prompt.

In `@app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt`:
- Around line 75-97: The inline suggestion flow in EditorProviderImpl still
resolves the visible editor via inspectableEditor()/current(), so content
changes from non-active files can anchor ghost text on the wrong tab. Update
showInlineSuggestion/dismissInlineSuggestion to accept or derive the target file
from the document-change path, or guard on the current file before invoking
them, using the same file context handled by onDocumentChange and the
contentCallbacks callback.

In `@editor/src/main/java/com/itsaky/androidide/editor/ui/GhostTextRenderer.kt`:
- Around line 85-95: GhostTextRenderer is calling a non-existent Sora API via
editor.getCharOffsetX in the ghost text drawing path, so replace that offset
calculation with the editor’s supported public layout/offset method in
GhostTextRenderer.render/drawText handling. Use the existing editor/text
positioning symbols in this class to compute x consistently with Sora 0.23.6,
while keeping the baseline and single-line substring behavior unchanged.

In `@editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt`:
- Around line 1002-1013: The Tab acceptance flow in IDEEditor.onKeyDown is
committing a different payload than what GhostTextRenderer previews: the
renderer shows only the first line, but takeSuggestion() and
commitText(suggestion) use the full multi-line string. Update the
GhostTextRenderer/takeSuggestion path so the accepted text matches the visible
preview, or change GhostTextRenderer to render and clearly indicate the full
multi-line suggestion before committing it.

In
`@plugin-api/src/test/kotlin/com/itsaky/androidide/plugins/PluginContextTest.kt`:
- Around line 48-54: The PluginContext test stub is not exercising the actual
registerService/unregisterService contract because TestPluginContext methods are
no-op and the tests call context.services directly. Update PluginContextTest so
the assertions go through PluginContext.registerService and
PluginContext.unregisterService on the TestPluginContext/PluginContextImpl path,
and verify the effect on the service registry and per-plugin scoping rather than
testing ServiceRegistry in isolation.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt`:
- Around line 21-47: Scope service access in PluginContextImpl so shared
services are keyed by the provider plugin, not just by service class. Update
getPluginService, registerService, and unregisterService to use the passed
pluginId together with the serviceClass, and adjust ServiceRegistryImpl
accordingly so one plugin cannot read or remove another plugin’s service of the
same type. Also implement getProvidedServices() to return the services actually
registered by this PluginContextImpl rather than an empty list.
- Around line 74-80: `PluginContextImpl` still leaves
`addPluginLifecycleListener` and `removePluginLifecycleListener` as no-ops, so
lifecycle listeners are never retained or dispatched. Wire these methods into
the existing listener storage used by `PluginContextImpl` so
`addPluginLifecycleListener` registers the listener and
`removePluginLifecycleListener` unregisters it, and make sure the lifecycle
dispatch path actually iterates over that stored set/list when plugin events
occur.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt`:
- Around line 85-90: The fallback in PluginFragmentFactory.instantiate is
swallowing ClassNotFoundException by returning a plain Fragment(), which
bypasses PluginScreenActivity.loadPluginFragment()’s runCatching failure path
and can leave a blank screen. Remove or narrow this placeholder behavior so
instantiate() still surfaces the failure to callers, and if a placeholder is
needed, restrict it to the state-restoration path only in
FragmentFactory/PluginFragmentFactory.
- Around line 85-90: The fallback in PluginFragmentFactory.instantiate is
catching the wrong exception, so the placeholder Fragment() path never runs when
defaultFactory.instantiate(classLoader, className) fails at runtime. Update the
catch to handle Fragment.InstantiationException (and keep the log/fallback
there) so the actual AndroidX failure is intercepted; if this flow should remain
fatal like PluginScreenActivity, remove the placeholder and let the exception
propagate instead.

---

Nitpick comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt`:
- Around line 61-68: The getAppSharedPreferences method currently catches
blanket Exception, which is too broad for this codebase’s fail-fast convention.
Update the try/catch around androidContext.getSharedPreferences to handle only
the specific exception type actually expected from this call path, and keep the
existing logger.error and null fallback behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a1481ef-bd43-4ce5-b0ae-75e6684344f7

📥 Commits

Reviewing files that changed from the base of the PR and between db0137a and 2bf361c.

📒 Files selected for processing (19)
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/itsaky/androidide/actions/PluginToolbarActionItem.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
  • common/src/main/java/com/itsaky/androidide/ui/ProjectActionsToolbar.kt
  • editor/src/main/java/com/itsaky/androidide/editor/ui/GhostTextRenderer.kt
  • editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt
  • editor/src/main/java/com/itsaky/androidide/editor/ui/TracingEditorRenderer.kt
  • plugin-api/api/plugin-api.api
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginContext.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt
  • plugin-api/src/test/kotlin/com/itsaky/androidide/plugins/PluginContextTest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/context/PluginContextImpl.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeUIServiceImpl.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginToolbarHost.kt

Comment thread app/src/main/AndroidManifest.xml
Comment thread app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
Comment thread editor/src/main/java/com/itsaky/androidide/editor/ui/GhostTextRenderer.kt Outdated
Comment thread editor/src/main/java/com/itsaky/androidide/editor/ui/IDEEditor.kt
…ess death

When a plugin dialog fragment (e.g. the AI settings dialog) is restored from saved state after process death — before the plugin has reloaded and registered its classloader — the factory's fallback was meant to substitute a placeholder Fragment. It caught ClassNotFoundException, but FragmentFactory.loadFragmentClass wraps that in a Fragment.InstantiationException, so the catch never matched and the exception propagated into a fatal crash loop.

Also catch Fragment.InstantiationException so the placeholder substitution actually runs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt (1)

85-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix correctly addresses the crash.

Catching Fragment.InstantiationException resolves the issue flagged in the previous review — defaultFactory.instantiate() wraps ClassNotFoundException in Fragment.InstantiationException, so the old ClassNotFoundException-only catch was dead code on the standard path. The placeholder fallback now fires as intended for process-death restoration.

The ClassNotFoundException catch (lines 90-93) is now redundant for the standard AndroidX FragmentFactory since it wraps ClassNotFoundException before throwing. It's only useful if a custom defaultFactory implementation throws ClassNotFoundException directly. Consider adding a brief comment explaining why both catches are kept, or removing the ClassNotFoundException catch if defaultFactory is always the standard factory.

Based on learnings, narrow exception handling that catches specific exception types is preferred over broad catch-all in this project. The changed code correctly uses narrow catches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt`
around lines 85 - 93, The fallback in PluginFragmentFactory.instantiate
currently handles both Fragment.InstantiationException and
ClassNotFoundException, but the ClassNotFoundException branch is redundant for
the standard AndroidX defaultFactory path because instantiate() already wraps
it; either remove the dead catch if defaultFactory is always the standard
factory, or add a short comment in instantiate() explaining that both catches
are kept only for custom defaultFactory implementations that may throw
ClassNotFoundException directly.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt`:
- Around line 85-93: The fallback in PluginFragmentFactory.instantiate currently
handles both Fragment.InstantiationException and ClassNotFoundException, but the
ClassNotFoundException branch is redundant for the standard AndroidX
defaultFactory path because instantiate() already wraps it; either remove the
dead catch if defaultFactory is always the standard factory, or add a short
comment in instantiate() explaining that both catches are kept only for custom
defaultFactory implementations that may throw ClassNotFoundException directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f3e0d06-674d-47d9-b98f-93c2ba5a2af1

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf361c and 0b6d95f.

📒 Files selected for processing (1)
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/fragment/PluginFragmentFactory.kt

@jatezzz jatezzz requested review from a team, Daniel-ADFA and itsaky-adfa July 8, 2026 16:56
jatezzz and others added 4 commits July 9, 2026 12:33
Introduce a thread-safe SharedServices registry so plugins can share service instances when PluginContext cross-plugin service access is unavailable. Backed by a ConcurrentHashMap with register/get/unregister/ clear plus reified extension helpers.

Update the API baseline (plugin-api.api) for the new public API.
…plugin' into refactor/ADFA-4584-ai-hackathon-plugin
@jatezzz jatezzz requested a review from hal-eisen-adfa July 9, 2026 20:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/SharedServices.kt (2)

9-27: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Ensure clear() is invoked on plugin unload to avoid memory leaks.

SharedServices is a global singleton holding strong references to service implementations. If those implementations retain plugin classloaders, activity contexts, or other heavyweight objects, failing to call clear() at the right lifecycle point will leak them across plugin reload cycles.

Verify that PluginManager or the plugin lifecycle shutdown path calls SharedServices.clear() (or selectively unregister) when plugins are unloaded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/SharedServices.kt`
around lines 9 - 27, `SharedServices` keeps strong references in a global
singleton, so make sure its cleanup path is wired into plugin shutdown. Update
the plugin unload/lifecycle teardown in `PluginManager` (or the relevant unload
hook) to call `SharedServices.clear()` when a plugin is unloaded, or use
`unregister()` for targeted cleanup if only specific services should be removed.
Verify the unload path runs reliably so service instances don’t survive reload
cycles.

12-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrain T to Any to prevent null registration at compile time.

T has no upper bound, so callers could pass null as implementation. The as Any cast on line 13 would throw a NullPointerException at runtime. Adding T : Any to both the method and the reified extension shifts this to a compile-time error.

♻️ Proposed fix
-    fun <T> register(serviceClass: Class<T>, implementation: T) {
+    fun <T : Any> register(serviceClass: Class<T>, implementation: T) {
         services[serviceClass] = implementation as Any
     }

And the reified extension:

-inline fun <reified T> SharedServices.register(implementation: T) {
+inline fun <reified T : Any> SharedServices.register(implementation: T) {
     register(T::class.java, implementation)
 }

Also applies to: 31-33

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/SharedServices.kt`
around lines 12 - 14, Constrain the generic type in SharedServices.register to
non-nullable by adding an Any upper bound so null implementations are rejected
at compile time instead of failing at the cast. Update both register overloads
in SharedServices, including the reified extension mentioned in the diff, so
their T declarations use T : Any and the existing services map assignment no
longer relies on a null-unsafe cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/SharedServices.kt`:
- Around line 9-27: `SharedServices` keeps strong references in a global
singleton, so make sure its cleanup path is wired into plugin shutdown. Update
the plugin unload/lifecycle teardown in `PluginManager` (or the relevant unload
hook) to call `SharedServices.clear()` when a plugin is unloaded, or use
`unregister()` for targeted cleanup if only specific services should be removed.
Verify the unload path runs reliably so service instances don’t survive reload
cycles.
- Around line 12-14: Constrain the generic type in SharedServices.register to
non-nullable by adding an Any upper bound so null implementations are rejected
at compile time instead of failing at the cast. Update both register overloads
in SharedServices, including the reified extension mentioned in the diff, so
their T declarations use T : Any and the existing services map assignment no
longer relies on a null-unsafe cast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c79b2be0-5fb3-40f5-826f-98709a2892a8

📥 Commits

Reviewing files that changed from the base of the PR and between 9c9e771 and 2cc6c28.

📒 Files selected for processing (2)
  • plugin-api/api/plugin-api.api
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/SharedServices.kt

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.

2 participants