mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
refactor(stage-tamagotchi): reload tools when extension reloads
This commit is contained in:
@@ -28,11 +28,15 @@ import {
|
||||
import {
|
||||
electronPluginInspect,
|
||||
electronPluginList,
|
||||
electronPluginLoad,
|
||||
electronPluginLoadEnabled,
|
||||
electronPluginSetAutoReload,
|
||||
electronPluginSetEnabled,
|
||||
electronPluginUnload,
|
||||
} from '../../../../shared/eventa/plugin/host'
|
||||
import {
|
||||
electronPluginToolsChanged,
|
||||
} from '../../../../shared/eventa/plugin/tools'
|
||||
import { setupExtensionHostServiceInternal } from './host'
|
||||
import { loadManifestsFrom } from './host/registry'
|
||||
import { setupExtensionHost as setupExtensionHostService } from './index'
|
||||
@@ -544,6 +548,43 @@ describe('setupExtensionHost', () => {
|
||||
expect(error).toEqual(expect.objectContaining({ enabled: true, loaded: false }))
|
||||
})
|
||||
|
||||
it('emits a plugin tools changed event after loading an extension through IPC', async () => {
|
||||
const pluginDir = join(pluginsDir, 'test-tools-changed')
|
||||
await mkdir(pluginDir, { recursive: true })
|
||||
await writeEntrypoint({
|
||||
dir: pluginDir,
|
||||
name: 'test-tools-changed.ts',
|
||||
contents: createEmptyExtensionEntrypoint('test-tools-changed'),
|
||||
})
|
||||
await writeManifest({
|
||||
dir: pluginDir,
|
||||
name: 'test-tools-changed',
|
||||
entrypoint: './test-tools-changed.ts',
|
||||
})
|
||||
|
||||
await setupExtensionHost()
|
||||
|
||||
expect(contextState.lastContext).toBeDefined()
|
||||
const toolsChangedEvents: Array<{ reason: string, name?: string }> = []
|
||||
contextState.lastContext!.on(electronPluginToolsChanged, (event) => {
|
||||
if (!event.body) {
|
||||
throw new Error('Expected plugin tools changed event body.')
|
||||
}
|
||||
toolsChangedEvents.push(event.body)
|
||||
})
|
||||
|
||||
const invokeLoad = defineInvoke(contextState.lastContext!, electronPluginLoad)
|
||||
|
||||
await invokeLoad({ name: 'test-tools-changed' })
|
||||
|
||||
expect(toolsChangedEvents).toEqual([
|
||||
{
|
||||
reason: 'loaded',
|
||||
name: 'test-tools-changed',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('loads the first matching manifest when duplicate plugin names exist', async () => {
|
||||
const errorEntrypoint = join(testDataRoot, 'test-error-plugin.ts')
|
||||
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import type {
|
||||
ExtensionHostService,
|
||||
SetupExtensionHostOptions,
|
||||
} from './types'
|
||||
import type { ExtensionHostService, SetupExtensionHostOptions } from './types'
|
||||
|
||||
import {
|
||||
defineInvoke,
|
||||
defineInvokeHandler,
|
||||
} from '@moeru/eventa'
|
||||
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
|
||||
import { createContext } from '@moeru/eventa/adapters/electron/main'
|
||||
import {
|
||||
app,
|
||||
ipcMain,
|
||||
} from 'electron'
|
||||
import { app, ipcMain } from 'electron'
|
||||
|
||||
import {
|
||||
electronPluginGetAssetBaseUrl,
|
||||
@@ -34,6 +25,7 @@ import {
|
||||
electronPluginInvokeTool,
|
||||
electronPluginListAgentTools,
|
||||
electronPluginListXsaiTools,
|
||||
electronPluginToolsChanged,
|
||||
} from '../../../../shared/eventa/plugin/tools'
|
||||
import { setupExtensionHostServiceInternal } from './host'
|
||||
|
||||
@@ -64,7 +56,12 @@ export async function setupExtensionHost(options: SetupExtensionHostOptions): Pr
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginSetEnabled, async (payload) => {
|
||||
return await hostService.setEnabled(payload)
|
||||
const result = await hostService.setEnabled(payload)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'enabled-state-changed',
|
||||
name: payload.name,
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginSetAutoReload, async (payload) => {
|
||||
@@ -72,15 +69,29 @@ export async function setupExtensionHost(options: SetupExtensionHostOptions): Pr
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginLoadEnabled, async () => {
|
||||
return await hostService.loadEnabled()
|
||||
const result = await hostService.loadEnabled()
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'load-enabled',
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginLoad, async (payload) => {
|
||||
return await hostService.load(payload.name)
|
||||
const result = await hostService.load(payload.name)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'loaded',
|
||||
name: payload.name,
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginUnload, async (payload) => {
|
||||
return await hostService.unload(payload.name)
|
||||
const result = await hostService.unload(payload.name)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'unloaded',
|
||||
name: payload.name,
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginInspect, async () => {
|
||||
|
||||
@@ -48,13 +48,11 @@ import {
|
||||
electronPluginSetEnabled,
|
||||
electronPluginUnload,
|
||||
} from '../shared/eventa/plugin/host'
|
||||
import { electronPluginToolsChanged } from '../shared/eventa/plugin/tools'
|
||||
import { initializeElectronAuthCallbackBridge } from './bridges/electron-auth-callback'
|
||||
import { initializeStageThreeRuntimeTraceBridge } from './bridges/stage-three-runtime-trace'
|
||||
import { useLanguage } from './composables/use-language'
|
||||
import {
|
||||
createChatSyncWindowLifecycle,
|
||||
resolveInitialChatSyncRoutePath,
|
||||
} from './stores/chat-sync-lifecycle'
|
||||
import { createChatSyncWindowLifecycle, resolveInitialChatSyncRoutePath } from './stores/chat-sync-lifecycle'
|
||||
import { useTamagotchiMcpToolsStore } from './stores/mcp-tools'
|
||||
import { useTamagotchiPluginToolsStore } from './stores/plugin-tools'
|
||||
import { useServerChannelSettingsStore } from './stores/settings/server-channel'
|
||||
@@ -188,6 +186,10 @@ function createFullStageRuntime() {
|
||||
syncGodotStageRenderer(event.body)
|
||||
})
|
||||
|
||||
context.value.on(electronPluginToolsChanged, () => {
|
||||
void refreshPluginRuntimeTools()
|
||||
})
|
||||
|
||||
return {
|
||||
async initialize() {
|
||||
analyticsStore.initialize()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineInvokeEventa } from '@moeru/eventa'
|
||||
import { defineEventa, defineInvokeEventa } from '@moeru/eventa'
|
||||
|
||||
/**
|
||||
* Renderer-facing plugin tool descriptor used by agent tooling UIs.
|
||||
@@ -80,6 +80,23 @@ export interface ElectronPluginXsaiToolsetDefinition {
|
||||
prompts: ElectronPluginToolsetPromptDefinition[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes why plugin-backed runtime tools should be refreshed.
|
||||
*
|
||||
* Use when:
|
||||
* - The main process notifies renderers after plugin lifecycle changes
|
||||
*
|
||||
* Expects:
|
||||
* - `name` is present when the change is scoped to one plugin
|
||||
*
|
||||
* Returns:
|
||||
* - N/A
|
||||
*/
|
||||
export interface ElectronPluginToolsChangedPayload {
|
||||
reason: 'loaded' | 'load-enabled' | 'unloaded' | 'enabled-state-changed'
|
||||
name?: string
|
||||
}
|
||||
|
||||
export const electronPluginListAgentTools = defineInvokeEventa<ElectronPluginToolDescriptor[]>('eventa:invoke:electron:plugins:tools:list')
|
||||
export const electronPluginListXsaiTools = defineInvokeEventa<ElectronPluginXsaiToolsetDefinition>('eventa:invoke:electron:plugins:tools:list-xsai')
|
||||
export const electronPluginInvokeTool = defineInvokeEventa<unknown, {
|
||||
@@ -87,3 +104,4 @@ export const electronPluginInvokeTool = defineInvokeEventa<unknown, {
|
||||
name: string
|
||||
input: unknown
|
||||
}>('eventa:invoke:electron:plugins:tools:invoke')
|
||||
export const electronPluginToolsChanged = defineEventa<ElectronPluginToolsChangedPayload>('eventa:event:electron:plugins:tools:changed')
|
||||
|
||||
Reference in New Issue
Block a user