Skip to content

feat(host): experimental web-based plugin support - #180

Open
kitakkun wants to merge 1 commit into
mainfrom
feature/web_based_plugin_support
Open

feat(host): experimental web-based plugin support#180
kitakkun wants to merge 1 commit into
mainfrom
feature/web_based_plugin_support

Conversation

@kitakkun

Copy link
Copy Markdown
Owner

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

  • SDK (jetwhale-host-sdk): JetWhaleWebView (windowed KCEF via SwingPanel), JetWhaleWebSource (dev-server / bundled), JetWhaleWebHostPluginUi marker, JetWhaleWebBridge, JDK-only loopback asset server, lazy one-time KCEF bootstrap.
  • Host: web plugins render in a real windowed composition (WebPluginScreen) instead of the off-screen scene; window.jetwhale bridge wired to the existing messenger / PluginFrame protocol (agent side unchanged).
  • Protocol: additive onRawEvent / onRawRequest raw inbound fallbacks so a web UI receives all agent messages without a Kotlin type per message.
  • Pure web plugins: manifest web { entry, resourceRoot, devServerUrlProperty } + host-provided WebManifestHostPlugin; factoryClass now optional (exactly one of factoryClass / web).
  • Install: .zip archives (manifest + assets) accepted alongside .jar.
  • Build: JogAmp repository added for KCEF's JOGL/GlueGen native deps.
  • Example + docs: com.kitakkun.jetwhale.example.web (pure web plugin + agent counterpart), jetwhale.d.ts typings, developer-guide section.

Design notes

  • Windowed AWT WebView (not OSR): the plugin scene is off-screen, so a heavyweight browser can only attach in a real window. Trade-offs: web plugins can't be screenshotted, and Compose overlays may be occluded.
  • KCEF over compose-webview-multiplatform: the latter's latest release targets Compose 1.8, too stale for this repo's 1.11.
  • CEF/Chromium is downloaded at runtime, not bundled.

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

  • All modules compile (post-rebase verified); DefaultPluginTrustServiceTest incl. new .zip acceptance passes.
  • ⚠️ Not yet verified in a running GUI (CEF download + windowed rendering + bridge round-trip untested). All new APIs are marked @ExperimentalJetWhaleApi.

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.
Copilot AI review requested due to automatic review settings July 27, 2026 23:51

Copilot AI 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.

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 .zip plugin 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
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