fix(stage-pages,stage-ui): plugin host update

This commit is contained in:
Neko Ayaka
2026-04-22 14:23:45 +08:00
parent c7cf0c6a47
commit ca4c99f074
4 changed files with 110 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@
"DOM.Iterable",
"DOM.AsyncIterable"
],
"useDefineForClassFields": true,
"paths": {
"@proj-airi/stage-ui/*": [
"../../packages/stage-ui/src/*"
@@ -106,6 +106,18 @@ async function loadEnabled() {
}
}
async function setAutoReload(plugin: PluginManifestSummary, enabled: boolean) {
try {
await store.setAutoReload({
name: plugin.name,
enabled,
})
}
catch (error) {
toast.error(error instanceof Error ? error.message : `Failed to update auto-reload state for ${plugin.name}.`)
}
}
async function setEnabled(plugin: PluginManifestSummary, enabled: boolean) {
try {
await store.setEnabled({
@@ -173,7 +185,7 @@ onMounted(async () => {
:description="store.error"
/>
<div :class="['grid', 'gap-2', 'sm:grid-cols-2', 'xl:grid-cols-4']">
<div :class="['grid', 'gap-2', 'sm:grid-cols-2', 'xl:grid-cols-6']">
<div :class="['rounded-xl', 'bg-neutral-100', 'p-3', 'dark:bg-neutral-900/70']">
<div :class="['text-xs', 'uppercase', 'opacity-70']">
Discovered
@@ -206,6 +218,14 @@ onMounted(async () => {
{{ readyCapabilitiesCount }} / {{ store.capabilities.length }}
</div>
</div>
<div :class="['rounded-xl', 'bg-neutral-100', 'p-3', 'dark:bg-neutral-900/70']">
<div :class="['text-xs', 'uppercase', 'opacity-70']">
Kits
</div>
<div :class="['text-2xl', 'font-semibold']">
{{ store.kits.length }}
</div>
</div>
</div>
<div :class="['flex', 'flex-wrap', 'items-center', 'gap-2']">
@@ -272,6 +292,9 @@ onMounted(async () => {
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(plugin.enabled ? 'emerald' : 'neutral')]">
{{ plugin.enabled ? 'enabled' : 'disabled' }}
</span>
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(plugin.autoReload ? 'amber' : 'neutral')]">
{{ plugin.autoReload ? 'auto reload on' : 'auto reload off' }}
</span>
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(plugin.loaded ? 'emerald' : 'neutral')]">
{{ plugin.loaded ? 'loaded' : 'not loaded' }}
</span>
@@ -280,6 +303,14 @@ onMounted(async () => {
</span>
</div>
<div :class="['flex', 'flex-wrap', 'items-center', 'gap-2']">
<Button
size="sm"
variant="secondary"
:label="plugin.autoReload ? 'Auto Reload Off' : 'Auto Reload On'"
:icon="plugin.autoReload ? 'i-solar:refresh-circle-bold' : 'i-solar:refresh-bold-duotone'"
:loading="store.loading"
@click="setAutoReload(plugin, !plugin.autoReload)"
/>
<Button
size="sm"
variant="secondary"
@@ -372,6 +403,37 @@ onMounted(async () => {
</div>
</Section>
<Section
title="Kits"
icon="i-solar:box-bold-duotone"
inner-class="gap-2"
>
<div
v-if="store.kits.length === 0"
:class="['text-sm', 'opacity-70']"
>
No kits registered.
</div>
<div v-else :class="['grid', 'gap-2']">
<div
v-for="kit in store.kits"
:key="kit.kitId"
:class="['rounded-lg', 'border', 'border-neutral-300', 'bg-white/60', 'p-3', 'dark:border-neutral-800', 'dark:bg-neutral-950/60']"
>
<div :class="['flex', 'flex-wrap', 'items-center', 'justify-between', 'gap-2']">
<span :class="['font-mono', 'text-xs', 'sm:text-sm']">{{ kit.kitId }}</span>
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses('neutral')]">
v{{ kit.version }}
</span>
</div>
<div :class="['mt-2', 'text-xs', 'opacity-70']">
runtimes: {{ kit.runtimes.join(', ') || '-' }}
</div>
<pre :class="['mt-2', 'overflow-auto', 'rounded-lg', 'bg-neutral-100', 'p-2', 'text-xs', 'dark:bg-neutral-900/70']">{{ JSON.stringify(kit.capabilities, null, 2) }}</pre>
</div>
</div>
</Section>
<Section
title="Capabilities"
icon="i-solar:widget-2-bold-duotone"
@@ -6,6 +6,7 @@ export interface PluginManifestSummary {
entrypoints: Record<string, string | undefined>
path: string
enabled: boolean
autoReload: boolean
loaded: boolean
isNew: boolean
}
@@ -32,9 +33,36 @@ export interface PluginHostSessionSummary {
moduleId: string
}
export interface PluginHostKitCapabilitySummary {
key: string
actions: string[]
}
export interface PluginHostKitSummary {
kitId: string
version: string
capabilities: PluginHostKitCapabilitySummary[]
runtimes: Array<'electron' | 'node' | 'web'>
}
export interface PluginHostModuleSummary {
moduleId: string
ownerSessionId: string
ownerPluginId: string
kitId: string
kitModuleType: string
state: 'announced' | 'active' | 'degraded' | 'withdrawn'
runtime: 'electron' | 'node' | 'web'
revision: number
updatedAt: number
config: Record<string, unknown>
}
export interface PluginHostDebugSnapshot {
registry: PluginRegistrySnapshot
sessions: PluginHostSessionSummary[]
kits: PluginHostKitSummary[]
modules: PluginHostModuleSummary[]
capabilities: PluginCapabilityState[]
refreshedAt: number
}
@@ -42,6 +70,7 @@ export interface PluginHostDebugSnapshot {
interface PluginHostDebugBridge {
list: () => Promise<PluginRegistrySnapshot>
setEnabled: (payload: { name: string, enabled: boolean, path?: string }) => Promise<PluginRegistrySnapshot>
setAutoReload: (payload: { name: string, enabled: boolean }) => Promise<PluginRegistrySnapshot>
loadEnabled: () => Promise<PluginRegistrySnapshot>
load: (payload: { name: string }) => Promise<PluginRegistrySnapshot>
unload: (payload: { name: string }) => Promise<PluginRegistrySnapshot>
@@ -60,6 +89,7 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
const bridge = ref<PluginHostDebugBridge>()
const registry = ref<PluginRegistrySnapshot>()
const sessions = ref<PluginHostSessionSummary[]>([])
const kits = ref<PluginHostKitSummary[]>([])
const capabilities = ref<PluginCapabilityState[]>([])
const refreshedAt = ref<number>()
const error = ref<string>()
@@ -87,6 +117,7 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
function assignInspection(snapshot: PluginHostDebugSnapshot) {
assignRegistry(snapshot.registry)
sessions.value = snapshot.sessions
kits.value = snapshot.kits
capabilities.value = snapshot.capabilities
refreshedAt.value = snapshot.refreshedAt
}
@@ -146,6 +177,13 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
return nextRegistry
}
async function setAutoReload(payload: { name: string, enabled: boolean }) {
const nextRegistry = await withBridge(activeBridge => activeBridge.setAutoReload(payload))
assignRegistry(nextRegistry)
await refreshInspection()
return nextRegistry
}
async function loadEnabled() {
const nextRegistry = await withBridge(activeBridge => activeBridge.loadEnabled())
assignRegistry(nextRegistry)
@@ -170,6 +208,7 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
return {
registry,
sessions,
kits,
capabilities,
refreshedAt,
loading,
@@ -185,6 +224,7 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
refreshInspection,
refreshAll,
setEnabled,
setAutoReload,
loadEnabled,
load,
unload,
@@ -55,11 +55,16 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () =
const { post: broadcastContext, data: incomingContext } = useBroadcastChannel<ContextMessage, ContextMessage>({ name: CONTEXT_CHANNEL_NAME })
const { post: broadcastStreamEvent, data: incomingStreamEvent } = useBroadcastChannel<ChatStreamEvent, ChatStreamEvent>({ name: CHAT_STREAM_CHANNEL_NAME })
const sparkNotifyHostRole = ref<'main' | 'client'>('client')
const disposeHookFns = ref<Array<() => void>>([])
let remoteStreamGuard: { sessionId: string, generation: number } | null = null
let initialized = false
function setSparkNotifyHostRole(role: 'main' | 'client') {
sparkNotifyHostRole.value = role
}
async function initialize() {
await mutex.acquire()
@@ -525,5 +530,6 @@ export const useContextBridgeStore = defineStore('mods:api:context-bridge', () =
return {
initialize,
dispose,
setSparkNotifyHostRole,
}
})