feat(host): experimental web-based plugin support - #180
Open
kitakkun wants to merge 1 commit into
Open
Conversation
Render host plugin UIs as web pages (React/TypeScript, etc.) embedded in the existing plugin Content() via a windowed Chromium browser (KCEF). Pure web plugins can ship as manifest + assets only, with no Kotlin, and install from a zip. - host-sdk: JetWhaleWebView (windowed KCEF via SwingPanel), JetWhaleWebSource, JetWhaleWebHostPluginUi marker, JetWhaleWebBridge, JDK-only loopback asset server, lazy one-time KCEF bootstrap - host: web plugins render in a real windowed composition (WebPluginScreen); window.jetwhale bridge wired to the existing messenger / PluginFrame protocol - protocol: additive onRawEvent/onRawRequest raw inbound fallbacks so a web UI receives all agent messages without a Kotlin type per message - manifest: `web` block + host-provided WebManifestHostPlugin so a pure web plugin needs no factoryClass (exactly one of factoryClass / web) - install: accept .zip archives (manifest + assets) alongside .jar - build: add JogAmp repository for KCEF's JOGL/GlueGen native deps - example: pure web plugin com.kitakkun.jetwhale.example.web + agent counterpart; developer-guide section and jetwhale.d.ts typings Experimental (`@ExperimentalJetWhaleApi`). CEF/Chromium is downloaded at runtime, not bundled. Not yet verified in a running GUI.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds experimental web-based host plugin UI support to JetWhale, enabling plugins whose UI is built with web tech (static assets or a dev server) to be rendered in the host via an embedded Chromium browser (KCEF), including support for installing asset-only web plugins from .zip archives.
Changes:
- Introduces host SDK webview primitives (
JetWhaleWebView,JetWhaleWebSource,JetWhaleWebBridge, loopback asset server, KCEF bootstrap) and a host-provided factory for manifest-declared pure web plugins. - Extends protocol messaging with raw inbound fallbacks (
onRawEvent/onRawRequest) to support generic forwarding to web UIs without typed Kotlin messages. - Updates host plugin loading, hot-reload, settings UI, examples, and docs to accept
.zipplugin archives and demonstrate a pure web plugin.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Adds JogAmp Maven repo (scoped) for KCEF native JOGL/GlueGen dependencies. |
| schemas/plugin-manifest.schema.json | Updates manifest schema to allow factoryClass or web block. |
| jetwhale-protocol/core/src/commonMain/kotlin/com/kitakkun/jetwhale/protocol/messaging/JetWhaleMessageHandlers.kt | Adds raw event/request handler registration APIs. |
| jetwhale-protocol/core/src/commonMain/kotlin/com/kitakkun/jetwhale/protocol/messaging/InboundFrameDispatcher.kt | Dispatches unknown inbound frames to raw handlers when present. |
| jetwhale-protocol/core/api/jvm/core.api | Updates JVM API dump for new raw handler methods. |
| jetwhale-protocol/core/api/core.klib.api | Updates KLIB API dump for new raw handler methods. |
| jetwhale-plugins/example/host/src/main/resources/web/example/jetwhale.d.ts | Adds TypeScript typings for window.jetwhale bridge. |
| jetwhale-plugins/example/host/src/main/resources/web/example/index.html | Adds a minimal example web UI using the injected bridge. |
| jetwhale-plugins/example/host/src/main/resources/META-INF/jetwhale/plugin-manifest.json | Adds an example pure web plugin manifest entry. |
| jetwhale-plugins/example/agent/src/commonMain/kotlin/com/kitakkun/jetwhale/plugins/example/agent/ExampleWebAgentPlugin.kt | Adds agent counterpart for the example web host plugin. |
| jetwhale-host/feature/settings/src/main/kotlin/com/kitakkun/jetwhale/host/settings/plugin/PluginSettingsScreenRoot.kt | Updates file picker title to reflect .jar + .zip installs. |
| jetwhale-host/feature/plugin/src/main/kotlin/com/kitakkun/jetwhale/host/plugin/WebPluginScreen.kt | Adds windowed composition path for web plugin UI rendering. |
| jetwhale-host/feature/plugin/src/main/kotlin/com/kitakkun/jetwhale/host/plugin/PluginScreenRoot.kt | Chooses web-plugin rendering path vs off-screen compose scene. |
| jetwhale-host/feature/plugin/src/main/kotlin/com/kitakkun/jetwhale/host/plugin/PluginScreenContext.kt | Exposes plugin instance + bridge provider to drive rendering selection. |
| jetwhale-host/core/data/src/test/kotlin/com/kitakkun/jetwhale/host/data/plugin/DefaultPluginTrustServiceTest.kt | Adds trust/load coverage for .zip plugin archives. |
| jetwhale-host/core/data/src/main/kotlin/com/kitakkun/jetwhale/host/data/plugin/DefaultPluginHotReloadService.kt | Hot-reload now watches both .jar and .zip plugin archives. |
| jetwhale-host/core/data/src/main/kotlin/com/kitakkun/jetwhale/host/data/plugin/DefaultPluginFactoryRepository.kt | Loads host-provided factory for web manifest plugins; keeps reflective path for Kotlin plugins. |
| jetwhale-host/core/data/src/main/kotlin/com/kitakkun/jetwhale/host/data/AppDataDirectoryProvider.kt | Expands plugin archive acceptance from .jar to .jar + .zip. |
| jetwhale-host/app/build.gradle.kts | Adds required --add-opens JVM flags for KCEF in packaged + dev runs. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/WebManifestHostPlugin.kt | Implements the host-synthesized pure web plugin + factory helper. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/PluginAssetServer.kt | Adds loopback HTTP server serving bundled web assets from plugin archives. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/KcefController.kt | Adds one-time, lazy KCEF init/download controller with progress state. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/JetWhaleWebView.kt | Adds embedded Chromium view + JS bridge wiring to messenger + inbound forwarding. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/JetWhaleWebSource.kt | Defines dev-server vs bundled-asset web sources. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/JetWhaleWebHostPluginUi.kt | Marker interface to select windowed rendering path for web plugins. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/web/JetWhaleWebBridge.kt | Adds Kotlin-side message bridge into web UI listeners. |
| jetwhale-host-sdk/src/main/kotlin/com/kitakkun/jetwhale/host/sdk/JetWhaleHostPluginManifest.kt | Makes factoryClass optional and adds web manifest block model. |
| jetwhale-host-sdk/build.gradle.kts | Adds Compose UI/Foundation + KCEF + coroutines-swing deps for webview feature. |
| jetwhale-host-sdk/api/jetwhale-host-sdk.api | Updates API dump for new manifest/webview SDK surface. |
| gradle/libs.versions.toml | Adds KCEF + coroutines-swing + Compose UI/Foundation library entries. |
| docs/guide/developing-plugins.md | Documents pure web plugins, install-from-zip, and the window.jetwhale bridge. |
| demo/shared/src/commonMain/kotlin/com/kitakkun/jetwhale/demo/shared/InitializeJetWhale.kt | Registers the example web agent plugin in demo initialization. |
| demo/shared/src/commonMain/kotlin/com/kitakkun/jetwhale/demo/shared/DIModule.kt | Adds DI entry for ExampleWebAgentPlugin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+53
to
+56
| // Web plugins render in this real windowed composition (so the embedded browser's | ||
| // heavyweight component can attach) instead of the off-screen compose scene. | ||
| val pluginInstance = remember(reset) { screenContext.resolvePluginInstance() } | ||
| if (pluginInstance is JetWhaleWebHostPluginUi) { |
Comment on lines
+99
to
+121
| DisposableEffect(browser) { | ||
| // window.cefQuery -> parse -> agent, via the plugin messenger. | ||
| val router = CefMessageRouter.create() | ||
| router.addHandler(BridgeQueryHandler(messenger, scope), true) | ||
| client.addMessageRouter(router) | ||
|
|
||
| // Install the window.jetwhale bridge as soon as each main-frame document finishes loading. | ||
| val loadHandler = object : CefLoadHandlerAdapter() { | ||
| override fun onLoadEnd(browser: CefBrowser?, frame: CefFrame?, httpStatusCode: Int) { | ||
| if (frame?.isMain == true) { | ||
| browser?.executeJavaScript(BRIDGE_SHIM_JS, browser.url ?: "", 0) | ||
| } | ||
| } | ||
| } | ||
| client.addLoadHandler(loadHandler) | ||
|
|
||
| onDispose { | ||
| client.removeMessageRouter(router) | ||
| router.dispose() | ||
| browser.close(true) | ||
| client.dispose() | ||
| } | ||
| } |
| } | ||
| } | ||
|
|
||
| private fun jetwhaleHome(): File = File(System.getProperty("user.home"), ".jetwhale") |
Comment on lines
199
to
202
| return manifests.map { manifest -> | ||
| val factory = try { | ||
| // getConstructor (not getDeclaredConstructor): the contract is a *public* no-arg | ||
| // constructor, so a non-public/missing one fails clearly with NoSuchMethodException. | ||
| classLoader.loadClass(manifest.factoryClass).getConstructor().newInstance() | ||
| } catch (e: ReflectiveOperationException) { | ||
| throw IllegalStateException( | ||
| "Could not load factory '${manifest.factoryClass}' for plugin '${manifest.pluginId}' " + | ||
| "in $pluginJarPath: ${e.message}", | ||
| e, | ||
| ) | ||
| } | ||
| require(factory is JetWhaleHostPluginFactory) { | ||
| "Factory '${manifest.factoryClass}' for plugin '${manifest.pluginId}' in $pluginJarPath " + | ||
| "is not a ${JetWhaleHostPluginFactory::class.java.simpleName}" | ||
| } | ||
| val factory = manifest.web?.let { web -> webFactoryFor(web, classLoader) } | ||
| ?: reflectFactory(manifest.factoryClass, manifest.pluginId, pluginJarPath, classLoader) | ||
| LoadedHostPlugin(manifest = manifest, factory = factory) |
Comment on lines
86
to
90
| fun isManagedPluginJarPath(jarPath: String): Boolean { | ||
| val file = File(jarPath) | ||
| if (file.extension != "jar") return false | ||
| if (!isPluginArchive(file)) return false | ||
| return try { | ||
| file.canonicalFile.parentFile == File(pluginDir).canonicalFile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Experimental support for building and rendering host plugin UIs with web tech (React/TypeScript, etc.), embedded in the existing plugin
Content()via a windowed Chromium browser (KCEF). Pure web plugins can ship as manifest + assets only (no Kotlin, no JAR) and install from a.zip.What's included
jetwhale-host-sdk):JetWhaleWebView(windowed KCEF viaSwingPanel),JetWhaleWebSource(dev-server / bundled),JetWhaleWebHostPluginUimarker,JetWhaleWebBridge, JDK-only loopback asset server, lazy one-time KCEF bootstrap.WebPluginScreen) instead of the off-screen scene;window.jetwhalebridge wired to the existingmessenger/PluginFrameprotocol (agent side unchanged).onRawEvent/onRawRequestraw inbound fallbacks so a web UI receives all agent messages without a Kotlin type per message.web { entry, resourceRoot, devServerUrlProperty }+ host-providedWebManifestHostPlugin;factoryClassnow optional (exactly one offactoryClass/web)..ziparchives (manifest + assets) accepted alongside.jar.com.kitakkun.jetwhale.example.web(pure web plugin + agent counterpart),jetwhale.d.tstypings, developer-guide section.Design notes
Licensing
Added deps are permissive (KCEF Apache-2.0, JCEF BSD, JOGL/GlueGen BSD); JetWhale is Apache-2.0. Chromium is runtime-downloaded, not redistributed.
Status
DefaultPluginTrustServiceTestincl. new.zipacceptance passes.@ExperimentalJetWhaleApi.