mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
refactor(stage-tamagotchi,plugin-*): unified to extension id
This commit is contained in:
+3
-3
@@ -37,7 +37,7 @@ describe('createStaticAssetService', () => {
|
||||
const sessionStore = createStaticAssetSessionStore()
|
||||
const validateInputs: string[] = []
|
||||
const server = createStaticAssetService({
|
||||
getManifestEntryByName: () => new Map([
|
||||
getManifestEntryByExtensionId: () => new Map([
|
||||
[extensionId, { rootDir, version }],
|
||||
]),
|
||||
sessionStore: {
|
||||
@@ -206,7 +206,7 @@ describe('createStaticAssetService', () => {
|
||||
]
|
||||
let manifestReadCount = 0
|
||||
const server = createStaticAssetService({
|
||||
getManifestEntryByName: () => manifestEntries[Math.min(manifestReadCount++, manifestEntries.length - 1)],
|
||||
getManifestEntryByExtensionId: () => manifestEntries[Math.min(manifestReadCount++, manifestEntries.length - 1)],
|
||||
})
|
||||
servers.push(server)
|
||||
await server.start()
|
||||
@@ -242,7 +242,7 @@ describe('createStaticAssetService', () => {
|
||||
const version = '1.0.0'
|
||||
const sessionStore = createStaticAssetSessionStore()
|
||||
const server = createStaticAssetService({
|
||||
getManifestEntryByName: () => new Map([
|
||||
getManifestEntryByExtensionId: () => new Map([
|
||||
[extensionId, { rootDir, version }],
|
||||
]),
|
||||
sessionStore: {
|
||||
|
||||
@@ -39,13 +39,13 @@ export interface StaticAssetService extends ServerManager {
|
||||
* - A higher-level plugin asset service needs an HTTP transport adapter
|
||||
*
|
||||
* Expects:
|
||||
* - `getManifestEntryByName` returns up-to-date plugin root/version map
|
||||
* - `getManifestEntryByExtensionId` returns up-to-date extension root/version map
|
||||
*
|
||||
* Returns:
|
||||
* - Lifecycle service with session create/revoke APIs and local base URL getter
|
||||
*/
|
||||
export function createStaticAssetService(options: {
|
||||
getManifestEntryByName: () => Map<string, StaticAssetManifestEntry>
|
||||
getManifestEntryByExtensionId: () => Map<string, StaticAssetManifestEntry>
|
||||
host?: string
|
||||
sessionStore?: StaticAssetSessionStore
|
||||
getType?: (ext: string) => string | undefined
|
||||
@@ -60,11 +60,11 @@ export function createStaticAssetService(options: {
|
||||
const getManifestEntryForRequest = (extensionId: string) => {
|
||||
const cache = manifestEntryRequestCache.getStore()
|
||||
if (!cache) {
|
||||
return options.getManifestEntryByName().get(extensionId)
|
||||
return options.getManifestEntryByExtensionId().get(extensionId)
|
||||
}
|
||||
|
||||
if (!cache.has(extensionId)) {
|
||||
cache.set(extensionId, options.getManifestEntryByName().get(extensionId))
|
||||
cache.set(extensionId, options.getManifestEntryByExtensionId().get(extensionId))
|
||||
}
|
||||
|
||||
return cache.get(extensionId)
|
||||
|
||||
+42
-42
@@ -27,9 +27,9 @@ export interface ExtensionAutoReloadFeatureOptions {
|
||||
log: ReturnType<typeof useLogg>
|
||||
getConfig: () => ExtensionConfig
|
||||
listEntries: () => ManifestEntry[]
|
||||
isLoaded: (name: string) => boolean
|
||||
resolveWatchPaths: (name: string) => string[]
|
||||
reload: (name: string, changedPath: string) => Promise<void>
|
||||
isLoaded: (extensionId: string) => boolean
|
||||
resolveWatchPaths: (extensionId: string) => string[]
|
||||
reload: (extensionId: string, changedPath: string) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ export interface ExtensionAutoReloadFeatureOptions {
|
||||
*
|
||||
* Expects:
|
||||
* - Call `sync()` after registry/config/load-state changes
|
||||
* - Call `clearExtension(name)` before unloading or disabling a plugin
|
||||
* - Call `clearExtension(extensionId)` before unloading or disabling an extension
|
||||
* - Call `dispose()` during host shutdown
|
||||
*
|
||||
* Returns:
|
||||
@@ -52,18 +52,18 @@ export function createExtensionAutoReloadFeature(options: ExtensionAutoReloadFea
|
||||
const autoReloadTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const autoReloadWatchers = new Map<string, FSWatcher[]>()
|
||||
|
||||
const clearTimer = (name: string) => {
|
||||
const timer = autoReloadTimers.get(name)
|
||||
const clearTimer = (extensionId: string) => {
|
||||
const timer = autoReloadTimers.get(extensionId)
|
||||
if (!timer) {
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(timer)
|
||||
autoReloadTimers.delete(name)
|
||||
autoReloadTimers.delete(extensionId)
|
||||
}
|
||||
|
||||
const closeWatchers = (name: string) => {
|
||||
const watchers = autoReloadWatchers.get(name)
|
||||
const closeWatchers = (extensionId: string) => {
|
||||
const watchers = autoReloadWatchers.get(extensionId)
|
||||
if (!watchers) {
|
||||
return
|
||||
}
|
||||
@@ -72,55 +72,55 @@ export function createExtensionAutoReloadFeature(options: ExtensionAutoReloadFea
|
||||
watcher.close()
|
||||
}
|
||||
|
||||
autoReloadWatchers.delete(name)
|
||||
autoReloadWatchers.delete(extensionId)
|
||||
}
|
||||
|
||||
const reloadExtensionById = async (name: string, changedPath: string) => {
|
||||
if (autoReloadInFlight.has(name)) {
|
||||
const reloadExtensionById = async (extensionId: string, changedPath: string) => {
|
||||
if (autoReloadInFlight.has(extensionId)) {
|
||||
return
|
||||
}
|
||||
|
||||
autoReloadInFlight.add(name)
|
||||
autoReloadInFlight.add(extensionId)
|
||||
try {
|
||||
await options.reload(name, changedPath)
|
||||
options.log.log('extension auto-reloaded after file change', { extension: name, path: changedPath })
|
||||
await options.reload(extensionId, changedPath)
|
||||
options.log.log('extension auto-reloaded after file change', { extensionId, path: changedPath })
|
||||
}
|
||||
catch (error) {
|
||||
options.log.withError(error).withFields({ extension: name, path: changedPath }).error('extension auto-reload failed')
|
||||
options.log.withError(error).withFields({ extensionId, path: changedPath }).error('extension auto-reload failed')
|
||||
}
|
||||
finally {
|
||||
autoReloadInFlight.delete(name)
|
||||
autoReloadInFlight.delete(extensionId)
|
||||
}
|
||||
}
|
||||
|
||||
const scheduleReload = (name: string, changedPath: string) => {
|
||||
clearTimer(name)
|
||||
autoReloadTimers.set(name, setTimeout(() => {
|
||||
autoReloadTimers.delete(name)
|
||||
void reloadExtensionById(name, changedPath)
|
||||
const scheduleReload = (extensionId: string, changedPath: string) => {
|
||||
clearTimer(extensionId)
|
||||
autoReloadTimers.set(extensionId, setTimeout(() => {
|
||||
autoReloadTimers.delete(extensionId)
|
||||
void reloadExtensionById(extensionId, changedPath)
|
||||
}, 180))
|
||||
}
|
||||
|
||||
return {
|
||||
sync() {
|
||||
const enabledNames = new Set(options.getConfig().autoReload)
|
||||
const desiredNames = new Set(options.listEntries()
|
||||
const enabledExtensionIds = new Set(options.getConfig().autoReload)
|
||||
const desiredExtensionIds = new Set(options.listEntries()
|
||||
.map(entry => manifestIdOf(entry.manifest))
|
||||
.filter(name => enabledNames.has(name) && options.isLoaded(name)))
|
||||
.filter(extensionId => enabledExtensionIds.has(extensionId) && options.isLoaded(extensionId)))
|
||||
|
||||
for (const name of autoReloadWatchers.keys()) {
|
||||
if (!desiredNames.has(name)) {
|
||||
clearTimer(name)
|
||||
closeWatchers(name)
|
||||
for (const extensionId of autoReloadWatchers.keys()) {
|
||||
if (!desiredExtensionIds.has(extensionId)) {
|
||||
clearTimer(extensionId)
|
||||
closeWatchers(extensionId)
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of desiredNames) {
|
||||
if (autoReloadWatchers.has(name)) {
|
||||
for (const extensionId of desiredExtensionIds) {
|
||||
if (autoReloadWatchers.has(extensionId)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const watchPaths = options.resolveWatchPaths(name)
|
||||
const watchPaths = options.resolveWatchPaths(extensionId)
|
||||
if (watchPaths.length === 0) {
|
||||
continue
|
||||
}
|
||||
@@ -128,25 +128,25 @@ export function createExtensionAutoReloadFeature(options: ExtensionAutoReloadFea
|
||||
const watchers: FSWatcher[] = []
|
||||
for (const watchPath of watchPaths) {
|
||||
try {
|
||||
const watcher = watchFile(watchPath, { persistent: false }, () => scheduleReload(name, watchPath))
|
||||
const watcher = watchFile(watchPath, { persistent: false }, () => scheduleReload(extensionId, watchPath))
|
||||
watcher.on('error', (error) => {
|
||||
options.log.withError(error).withFields({ extension: name, path: watchPath }).warn('extension auto-reload watcher error')
|
||||
options.log.withError(error).withFields({ extensionId, path: watchPath }).warn('extension auto-reload watcher error')
|
||||
})
|
||||
watchers.push(watcher)
|
||||
}
|
||||
catch (error) {
|
||||
options.log.withError(error).withFields({ extension: name, path: watchPath }).warn('failed to watch extension file for auto-reload')
|
||||
options.log.withError(error).withFields({ extensionId, path: watchPath }).warn('failed to watch extension file for auto-reload')
|
||||
}
|
||||
}
|
||||
|
||||
if (watchers.length > 0) {
|
||||
autoReloadWatchers.set(name, watchers)
|
||||
autoReloadWatchers.set(extensionId, watchers)
|
||||
}
|
||||
}
|
||||
},
|
||||
clearExtension(name: string) {
|
||||
clearTimer(name)
|
||||
closeWatchers(name)
|
||||
clearExtension(extensionId: string) {
|
||||
clearTimer(extensionId)
|
||||
closeWatchers(extensionId)
|
||||
},
|
||||
dispose() {
|
||||
const managedNames = new Set([
|
||||
@@ -154,9 +154,9 @@ export function createExtensionAutoReloadFeature(options: ExtensionAutoReloadFea
|
||||
...autoReloadWatchers.keys(),
|
||||
])
|
||||
|
||||
for (const name of managedNames) {
|
||||
clearTimer(name)
|
||||
closeWatchers(name)
|
||||
for (const extensionId of managedNames) {
|
||||
clearTimer(extensionId)
|
||||
closeWatchers(extensionId)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
+9
-9
@@ -76,12 +76,12 @@ describe('createExtensionAssetService', () => {
|
||||
mockState.createStaticAssetService.mockReturnValue(server)
|
||||
|
||||
const service = createExtensionAssetService({
|
||||
getManifestEntryByName: () => new Map(),
|
||||
getManifestEntryByExtensionId: () => new Map(),
|
||||
cookieAdapter: adapter,
|
||||
})
|
||||
|
||||
const result = await service.createAssetSession({
|
||||
pluginId: 'airi-plugin-game-chess',
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '1.0.0',
|
||||
ownerSessionId: 'owner-session-1',
|
||||
routeAssetPath: 'assets/app.js',
|
||||
@@ -123,12 +123,12 @@ describe('createExtensionAssetService', () => {
|
||||
mockState.createStaticAssetService.mockReturnValue(server)
|
||||
|
||||
const service = createExtensionAssetService({
|
||||
getManifestEntryByName: () => new Map(),
|
||||
getManifestEntryByExtensionId: () => new Map(),
|
||||
cookieAdapter: adapter,
|
||||
})
|
||||
|
||||
await expect(service.createAssetSession({
|
||||
pluginId: 'airi-plugin-game-chess',
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '1.0.0',
|
||||
ownerSessionId: 'owner-session-1',
|
||||
routeAssetPath: 'assets/app.js',
|
||||
@@ -149,12 +149,12 @@ describe('createExtensionAssetService', () => {
|
||||
const { adapter } = createFakeCookieAdapter()
|
||||
mockState.createStaticAssetService.mockReturnValue(server)
|
||||
const service = createExtensionAssetService({
|
||||
getManifestEntryByName: () => new Map(),
|
||||
getManifestEntryByExtensionId: () => new Map(),
|
||||
cookieAdapter: adapter,
|
||||
})
|
||||
|
||||
await expect(service.createAssetSession({
|
||||
pluginId: 'airi-plugin-game-chess',
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '1.0.0',
|
||||
ownerSessionId: 'owner-session-1',
|
||||
routeAssetPath: '../secret.txt',
|
||||
@@ -169,7 +169,7 @@ describe('createExtensionAssetService', () => {
|
||||
adapter.setCookie.mockRejectedValueOnce(new Error('cookie jar unavailable'))
|
||||
|
||||
await expect(service.createAssetSession({
|
||||
pluginId: 'airi-plugin-game-chess',
|
||||
extensionId: 'airi-plugin-game-chess',
|
||||
version: '1.0.0',
|
||||
ownerSessionId: 'owner-session-1',
|
||||
routeAssetPath: 'assets/app.js',
|
||||
@@ -196,7 +196,7 @@ describe('createExtensionAssetService', () => {
|
||||
mockState.createStaticAssetService.mockReturnValue(server)
|
||||
|
||||
const service = createExtensionAssetService({
|
||||
getManifestEntryByName: () => new Map(),
|
||||
getManifestEntryByExtensionId: () => new Map(),
|
||||
cookieAdapter: adapter,
|
||||
})
|
||||
|
||||
@@ -252,7 +252,7 @@ describe('createExtensionAssetService', () => {
|
||||
mockState.createStaticAssetService.mockReturnValue(server)
|
||||
|
||||
const service = createExtensionAssetService({
|
||||
getManifestEntryByName: () => new Map(),
|
||||
getManifestEntryByExtensionId: () => new Map(),
|
||||
cookieAdapter: adapter,
|
||||
})
|
||||
|
||||
|
||||
+8
-8
@@ -13,7 +13,7 @@ import { buildMountedStaticAssetPath } from '../../../http-server/static-assets/
|
||||
* - Snapshot builders need a transport-agnostic way to authorize one extension asset route before iframe load
|
||||
*
|
||||
* Expects:
|
||||
* - `pluginId` matches a manifest entry registered in the asset host
|
||||
* - `extensionId` matches a manifest entry registered in the asset host
|
||||
* - `routeAssetPath` identifies the iframe entry asset relative to the mounted `/ui` route
|
||||
* - `pathPrefix` is scoped to the mounted route prefix accepted by the session store
|
||||
*
|
||||
@@ -21,8 +21,8 @@ import { buildMountedStaticAssetPath } from '../../../http-server/static-assets/
|
||||
* - N/A
|
||||
*/
|
||||
export interface ExtensionAssetSessionInput {
|
||||
/** Extension/plugin manifest id that owns the static asset root. */
|
||||
pluginId: string
|
||||
/** Extension manifest id that owns the static asset root. */
|
||||
extensionId: string
|
||||
/** Extension/plugin version expected by the server-side session validator. */
|
||||
version: string
|
||||
/** Parent extension session id used for owner-scoped revocation. */
|
||||
@@ -164,17 +164,17 @@ function createExtensionAssetCookie(baseUrl: string, session: StaticAssetSession
|
||||
* - Asset session lifecycle should stay inside the extension domain instead of the HTTP server layer
|
||||
*
|
||||
* Expects:
|
||||
* - `getManifestEntryByName` returns the latest extension root/version map
|
||||
* - `getManifestEntryByExtensionId` returns the latest extension root/version map
|
||||
* - `cookieAdapter` writes and removes cookies in the Electron host session used by plugin iframes
|
||||
*
|
||||
* Returns:
|
||||
* - An extension-facing asset host service with generic extension asset methods
|
||||
*/
|
||||
export function createExtensionAssetService(options: {
|
||||
getManifestEntryByName: () => Map<string, StaticAssetManifestEntry>
|
||||
getManifestEntryByExtensionId: () => Map<string, StaticAssetManifestEntry>
|
||||
cookieAdapter: ExtensionAssetCookieAdapter
|
||||
}): ExtensionAssetService {
|
||||
const server = createStaticAssetService({ getManifestEntryByName: options.getManifestEntryByName })
|
||||
const server = createStaticAssetService({ getManifestEntryByExtensionId: options.getManifestEntryByExtensionId })
|
||||
let lastBaseUrl: string | undefined
|
||||
|
||||
const readBaseUrl = () => {
|
||||
@@ -208,7 +208,7 @@ export function createExtensionAssetService(options: {
|
||||
},
|
||||
async createAssetSession(input) {
|
||||
const session = server.createSession({
|
||||
extensionId: input.pluginId,
|
||||
extensionId: input.extensionId,
|
||||
version: input.version,
|
||||
ownerSessionId: input.ownerSessionId,
|
||||
pathPrefix: input.pathPrefix,
|
||||
@@ -222,7 +222,7 @@ export function createExtensionAssetService(options: {
|
||||
}
|
||||
|
||||
const mountedPath = buildMountedStaticAssetPath({
|
||||
extensionId: input.pluginId,
|
||||
extensionId: input.extensionId,
|
||||
assetSessionId: session.assetSessionId,
|
||||
assetPath: input.routeAssetPath,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ExtensionHost } from '@proj-airi/plugin-sdk/plugin-host'
|
||||
|
||||
import type {
|
||||
PluginHostDebugSnapshot,
|
||||
PluginHostModuleSummary,
|
||||
} from '../../../../../shared/eventa/plugin/host'
|
||||
import type { ExtensionAssetSnapshotService } from '../features/static-assets'
|
||||
import type { ExtensionConfig, ManifestEntry } from '../types'
|
||||
@@ -19,7 +18,7 @@ import { buildPluginRegistrySnapshot } from './registry'
|
||||
*
|
||||
* Expects:
|
||||
* - `host` is the initialized extension host instance
|
||||
* - `manifestEntryByName` contains entries for any extension-owned modules being inspected
|
||||
* - `manifestEntryByExtensionId` contains entries for any extension-owned modules being inspected
|
||||
* - `extensionAssetService` owns extension asset URL/session lifecycle when mounted asset URLs are needed
|
||||
*
|
||||
* Returns:
|
||||
@@ -31,7 +30,7 @@ export function buildPluginHostDebugSnapshot(options: {
|
||||
entries: ManifestEntry[]
|
||||
config: ExtensionConfig
|
||||
loaded: Set<string>
|
||||
manifestEntryByName: Map<string, ManifestEntry>
|
||||
manifestEntryByExtensionId: Map<string, ManifestEntry>
|
||||
extensionAssetService?: ExtensionAssetSnapshotService
|
||||
}): Promise<PluginHostDebugSnapshot> {
|
||||
const extensionAssetService = options.extensionAssetService
|
||||
@@ -39,10 +38,10 @@ export function buildPluginHostDebugSnapshot(options: {
|
||||
.listBindings()
|
||||
.map(module =>
|
||||
rewriteWidgetModuleAssetUrl(
|
||||
module as PluginHostModuleSummary,
|
||||
options.manifestEntryByName,
|
||||
module,
|
||||
options.manifestEntryByExtensionId,
|
||||
{
|
||||
pluginAssetBaseUrl: extensionAssetService?.getBaseUrl(),
|
||||
extensionAssetBaseUrl: extensionAssetService?.getBaseUrl(),
|
||||
...(extensionAssetService
|
||||
? {
|
||||
createAssetSession: ({ extensionId, version, sessionId, routeAssetPath, sessionPathPrefix }: {
|
||||
@@ -52,7 +51,7 @@ export function buildPluginHostDebugSnapshot(options: {
|
||||
routeAssetPath: string
|
||||
sessionPathPrefix: string
|
||||
}) => extensionAssetService.createAssetSession({
|
||||
pluginId: extensionId,
|
||||
extensionId,
|
||||
version,
|
||||
ownerSessionId: sessionId,
|
||||
routeAssetPath,
|
||||
@@ -62,7 +61,7 @@ export function buildPluginHostDebugSnapshot(options: {
|
||||
: {}),
|
||||
},
|
||||
),
|
||||
) as Array<PluginHostModuleSummary | Promise<PluginHostModuleSummary>>)
|
||||
))
|
||||
|
||||
return modules.then(resolvedModules => ({
|
||||
registry: buildPluginRegistrySnapshot({
|
||||
@@ -73,13 +72,13 @@ export function buildPluginHostDebugSnapshot(options: {
|
||||
}),
|
||||
sessions: options.host.listSessions().map(session => ({
|
||||
id: session.id,
|
||||
manifestName: session.manifest.id,
|
||||
extensionId: session.manifest.id,
|
||||
phase: session.phase,
|
||||
runtime: session.runtime ?? 'electron',
|
||||
moduleId: session.extension.id,
|
||||
})),
|
||||
kits: options.host.listKits(),
|
||||
modules: resolvedModules as PluginHostDebugSnapshot['modules'],
|
||||
modules: resolvedModules,
|
||||
capabilities: options.host.listCapabilities(),
|
||||
refreshedAt: Date.now(),
|
||||
}))
|
||||
|
||||
@@ -93,13 +93,13 @@ export interface ExtensionHostServiceInternal extends ExtensionHostService {
|
||||
* - Host state must remember a known manifest path for a plugin name
|
||||
*
|
||||
* Expects:
|
||||
* - `payload.name` matches a discovered or previously known plugin
|
||||
* - `payload.extensionId` matches a discovered or previously known extension
|
||||
* - `payload.path` is only needed when the manifest is not currently discoverable
|
||||
*
|
||||
* Returns:
|
||||
* - The updated extension registry snapshot after persistence
|
||||
*/
|
||||
setEnabled: (payload: { name: string, enabled: boolean, path?: string }) => Promise<PluginRegistrySnapshot>
|
||||
setEnabled: (payload: { extensionId: string, enabled: boolean, path?: string }) => Promise<PluginRegistrySnapshot>
|
||||
|
||||
/**
|
||||
* Persists whether one loaded plugin should use auto-reload.
|
||||
@@ -109,12 +109,12 @@ export interface ExtensionHostServiceInternal extends ExtensionHostService {
|
||||
* - Host features need to resync optional watcher state after config changes
|
||||
*
|
||||
* Expects:
|
||||
* - `payload.name` matches one plugin entry in config or discovery state
|
||||
* - `payload.extensionId` matches one extension entry in config or discovery state
|
||||
*
|
||||
* Returns:
|
||||
* - The updated extension registry snapshot after persistence
|
||||
*/
|
||||
setAutoReload: (payload: { name: string, enabled: boolean }) => Promise<PluginRegistrySnapshot>
|
||||
setAutoReload: (payload: { extensionId: string, enabled: boolean }) => Promise<PluginRegistrySnapshot>
|
||||
|
||||
/**
|
||||
* Loads every plugin currently marked as enabled.
|
||||
@@ -132,34 +132,34 @@ export interface ExtensionHostServiceInternal extends ExtensionHostService {
|
||||
loadEnabled: () => Promise<PluginRegistrySnapshot>
|
||||
|
||||
/**
|
||||
* Loads one plugin by manifest name.
|
||||
* Loads one extension by manifest id.
|
||||
*
|
||||
* Use when:
|
||||
* - Renderer explicitly requests one plugin to start
|
||||
* - Host features need to restart a plugin after manifest or entrypoint changes
|
||||
*
|
||||
* Expects:
|
||||
* - `name` resolves to a manifest entry in the current registry
|
||||
* - `extensionId` resolves to a manifest entry in the current registry
|
||||
*
|
||||
* Returns:
|
||||
* - The extension registry snapshot after the load completes
|
||||
*/
|
||||
load: (name: string) => Promise<PluginRegistrySnapshot>
|
||||
load: (extensionId: string) => Promise<PluginRegistrySnapshot>
|
||||
|
||||
/**
|
||||
* Stops one loaded plugin by manifest name.
|
||||
* Stops one loaded extension by manifest id.
|
||||
*
|
||||
* Use when:
|
||||
* - Renderer explicitly requests one plugin to stop
|
||||
* - Host features need to stop a plugin before reload or disposal
|
||||
*
|
||||
* Expects:
|
||||
* - `name` identifies a plugin that may or may not currently be loaded
|
||||
* - `extensionId` identifies an extension that may or may not currently be loaded
|
||||
*
|
||||
* Returns:
|
||||
* - The extension registry snapshot after unload bookkeeping completes
|
||||
*/
|
||||
unload: (name: string) => Promise<PluginRegistrySnapshot>
|
||||
unload: (extensionId: string) => Promise<PluginRegistrySnapshot>
|
||||
|
||||
/**
|
||||
* Builds the full extension host debug snapshot.
|
||||
@@ -248,7 +248,7 @@ export async function setupExtensionHostServiceInternal(
|
||||
|
||||
// Extension feature: Static Assets serving
|
||||
const extensionAssetService = createExtensionAssetService({
|
||||
getManifestEntryByName: () => extensionRegistry.getManifestEntryByName(),
|
||||
getManifestEntryByExtensionId: () => extensionRegistry.getManifestEntryByExtensionId(),
|
||||
cookieAdapter: createElectronExtensionAssetCookieAdapter(),
|
||||
})
|
||||
await extensionAssetService.start()
|
||||
@@ -290,21 +290,21 @@ export async function setupExtensionHostServiceInternal(
|
||||
}
|
||||
|
||||
const createModuleAssetSession = async (input: {
|
||||
pluginId: string
|
||||
extensionId: string
|
||||
version: string
|
||||
ownerSessionId: string
|
||||
routeAssetPath: string
|
||||
pathPrefix: string
|
||||
}) => {
|
||||
const { pluginId, version, ownerSessionId, routeAssetPath, pathPrefix } = input
|
||||
const cacheKey = `${pluginId}:${version}:${ownerSessionId}:${routeAssetPath}:${pathPrefix}`
|
||||
const { extensionId, version, ownerSessionId, routeAssetPath, pathPrefix } = input
|
||||
const cacheKey = `${extensionId}:${version}:${ownerSessionId}:${routeAssetPath}:${pathPrefix}`
|
||||
const cachedSession = moduleAssetSessionCache.get(cacheKey)
|
||||
if (cachedSession) {
|
||||
return cachedSession
|
||||
}
|
||||
|
||||
const session = await extensionAssetService.createAssetSession({
|
||||
pluginId,
|
||||
extensionId,
|
||||
version,
|
||||
ownerSessionId,
|
||||
routeAssetPath,
|
||||
@@ -317,9 +317,9 @@ export async function setupExtensionHostServiceInternal(
|
||||
|
||||
const extensionAssetSnapshotService: ExtensionAssetSnapshotService = {
|
||||
getBaseUrl: extensionAssetService.getBaseUrl,
|
||||
createAssetSession: ({ pluginId, version, ownerSessionId, routeAssetPath, pathPrefix }) => {
|
||||
createAssetSession: ({ extensionId, version, ownerSessionId, routeAssetPath, pathPrefix }) => {
|
||||
return createModuleAssetSession({
|
||||
pluginId,
|
||||
extensionId,
|
||||
version,
|
||||
ownerSessionId,
|
||||
routeAssetPath,
|
||||
@@ -335,50 +335,50 @@ export async function setupExtensionHostServiceInternal(
|
||||
entries: extensionRegistry.listEntries(),
|
||||
config: getConfig(),
|
||||
loaded,
|
||||
manifestEntryByName: extensionRegistry.getManifestEntryByName(),
|
||||
manifestEntryByExtensionId: extensionRegistry.getManifestEntryByExtensionId(),
|
||||
extensionAssetService: extensionAssetSnapshotService,
|
||||
})
|
||||
}
|
||||
|
||||
const loadExtensionById = async (
|
||||
name: string,
|
||||
extensionId: string,
|
||||
loadOptions: { cacheBustKey?: string } = {},
|
||||
) => {
|
||||
if (loaded.has(name)) {
|
||||
if (loaded.has(extensionId)) {
|
||||
return
|
||||
}
|
||||
|
||||
const entry = extensionRegistry.findManifestEntry(name)
|
||||
const entry = extensionRegistry.findManifestEntry(extensionId)
|
||||
if (!entry) {
|
||||
throw new Error(`Extension manifest not found: ${name}`)
|
||||
throw new Error(`Extension manifest not found: ${extensionId}`)
|
||||
}
|
||||
|
||||
const manifestForLoad = createManifestForLoad(entry, loadOptions)
|
||||
const session = await host.start(manifestForLoad, { cwd: dirname(entry.path) })
|
||||
loaded.add(name)
|
||||
loadedSessionIds.set(name, session.id)
|
||||
log.log('extension loaded', { extension: name, sessionId: session.id })
|
||||
loaded.add(extensionId)
|
||||
loadedSessionIds.set(extensionId, session.id)
|
||||
log.withFields({ extensionId, sessionId: session.id }).log('extension loaded')
|
||||
}
|
||||
|
||||
const stopLoadedExtensionById = async (name: string) => {
|
||||
const sessionId = loadedSessionIds.get(name)
|
||||
const stopLoadedExtensionById = async (extensionId: string) => {
|
||||
const sessionId = loadedSessionIds.get(extensionId)
|
||||
if (!sessionId) {
|
||||
loaded.delete(name)
|
||||
loaded.delete(extensionId)
|
||||
return
|
||||
}
|
||||
|
||||
await host.stop(sessionId)
|
||||
loadedSessionIds.delete(name)
|
||||
loaded.delete(name)
|
||||
loadedSessionIds.delete(extensionId)
|
||||
loaded.delete(extensionId)
|
||||
|
||||
clearModuleAssetSessionCacheByOwnerSessionId(sessionId)
|
||||
await extensionAssetService.revokeByOwnerSessionId(sessionId)
|
||||
|
||||
log.log('extension unloaded', { extension: name, sessionId })
|
||||
log.withFields({ extensionId, sessionId }).log('extension unloaded')
|
||||
}
|
||||
|
||||
const resolveAutoReloadWatchPaths = (name: string) => {
|
||||
const entry = extensionRegistry.findManifestEntry(name)
|
||||
const resolveAutoReloadWatchPaths = (extensionId: string) => {
|
||||
const entry = extensionRegistry.findManifestEntry(extensionId)
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
@@ -392,36 +392,36 @@ export async function setupExtensionHostServiceInternal(
|
||||
log,
|
||||
getConfig,
|
||||
listEntries: () => extensionRegistry.listEntries(),
|
||||
isLoaded: name => loaded.has(name),
|
||||
isLoaded: extensionId => loaded.has(extensionId),
|
||||
resolveWatchPaths: resolveAutoReloadWatchPaths,
|
||||
reload: async (name) => {
|
||||
await stopLoadedExtensionById(name)
|
||||
reload: async (extensionId) => {
|
||||
await stopLoadedExtensionById(extensionId)
|
||||
await refreshManifests()
|
||||
await loadExtensionById(name, { cacheBustKey: `auto-reload-${Date.now()}` })
|
||||
await loadExtensionById(extensionId, { cacheBustKey: `auto-reload-${Date.now()}` })
|
||||
},
|
||||
})
|
||||
|
||||
const unloadExtensionById = async (name: string) => {
|
||||
autoReloadFeature.clearExtension(name)
|
||||
await stopLoadedExtensionById(name)
|
||||
const unloadExtensionById = async (extensionId: string) => {
|
||||
autoReloadFeature.clearExtension(extensionId)
|
||||
await stopLoadedExtensionById(extensionId)
|
||||
}
|
||||
|
||||
const loadEnabledExtensions = async () => {
|
||||
const config = getConfig()
|
||||
for (const entry of extensionRegistry.listEntries()) {
|
||||
const name = manifestIdOf(entry.manifest)
|
||||
if (!config.enabled.includes(name)) {
|
||||
const extensionId = manifestIdOf(entry.manifest)
|
||||
if (!config.enabled.includes(extensionId)) {
|
||||
continue
|
||||
}
|
||||
if (loaded.has(name)) {
|
||||
if (loaded.has(extensionId)) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
await loadExtensionById(name)
|
||||
await loadExtensionById(extensionId)
|
||||
}
|
||||
catch (error) {
|
||||
log.withError(error).withFields({ extension: name }).error('extension failed to start')
|
||||
log.withError(error).withFields({ extensionId }).error('extension failed to start')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,22 +450,22 @@ export async function setupExtensionHostServiceInternal(
|
||||
const config = getConfig()
|
||||
const enabled = new Set(config.enabled)
|
||||
if (payload.enabled) {
|
||||
enabled.add(payload.name)
|
||||
enabled.add(payload.extensionId)
|
||||
}
|
||||
else {
|
||||
enabled.delete(payload.name)
|
||||
clearModuleAssetSessionCacheByExtensionId(payload.name)
|
||||
await extensionAssetService.revokeByExtensionId(payload.name)
|
||||
enabled.delete(payload.extensionId)
|
||||
clearModuleAssetSessionCacheByExtensionId(payload.extensionId)
|
||||
await extensionAssetService.revokeByExtensionId(payload.extensionId)
|
||||
}
|
||||
|
||||
const entry = extensionRegistry.findManifestEntry(payload.name)
|
||||
const entry = extensionRegistry.findManifestEntry(payload.extensionId)
|
||||
const manifestPath = entry?.path ?? payload.path ?? ''
|
||||
extensionConfig.update({
|
||||
enabled: [...enabled],
|
||||
autoReload: config.autoReload,
|
||||
known: {
|
||||
...config.known,
|
||||
[payload.name]: { path: manifestPath },
|
||||
[payload.extensionId]: { path: manifestPath },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -478,10 +478,10 @@ export async function setupExtensionHostServiceInternal(
|
||||
const config = getConfig()
|
||||
const autoReload = new Set(config.autoReload)
|
||||
if (payload.enabled) {
|
||||
autoReload.add(payload.name)
|
||||
autoReload.add(payload.extensionId)
|
||||
}
|
||||
else {
|
||||
autoReload.delete(payload.name)
|
||||
autoReload.delete(payload.extensionId)
|
||||
}
|
||||
|
||||
extensionConfig.update({
|
||||
@@ -498,14 +498,14 @@ export async function setupExtensionHostServiceInternal(
|
||||
autoReloadFeature.sync()
|
||||
return listSnapshot()
|
||||
},
|
||||
async load(name) {
|
||||
async load(extensionId) {
|
||||
await refreshManifests()
|
||||
await loadExtensionById(name)
|
||||
await loadExtensionById(extensionId)
|
||||
autoReloadFeature.sync()
|
||||
return listSnapshot()
|
||||
},
|
||||
async unload(name) {
|
||||
await unloadExtensionById(name)
|
||||
async unload(extensionId) {
|
||||
await unloadExtensionById(extensionId)
|
||||
autoReloadFeature.sync()
|
||||
return listSnapshot()
|
||||
},
|
||||
|
||||
@@ -164,7 +164,7 @@ export async function loadManifestsFrom(
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a renderer-facing plugin summary from manifest, config, and runtime state.
|
||||
* Builds a renderer-facing extension summary from manifest, config, and runtime state.
|
||||
*
|
||||
* Use when:
|
||||
* - Registry snapshots need one UI-friendly entry per discovered plugin
|
||||
@@ -182,15 +182,15 @@ export function createPluginSummary(
|
||||
config: ExtensionConfig,
|
||||
loaded: Set<string>,
|
||||
): PluginManifestSummary {
|
||||
const name = manifestIdOf(entry.manifest)
|
||||
const extensionId = manifestIdOf(entry.manifest)
|
||||
return {
|
||||
name,
|
||||
extensionId,
|
||||
entrypoints: entry.manifest.entrypoints,
|
||||
path: entry.path,
|
||||
enabled: config.enabled.includes(name),
|
||||
autoReload: config.autoReload.includes(name),
|
||||
loaded: loaded.has(name),
|
||||
isNew: !config.known[name],
|
||||
enabled: config.enabled.includes(extensionId),
|
||||
autoReload: config.autoReload.includes(extensionId),
|
||||
loaded: loaded.has(extensionId),
|
||||
isNew: !config.known[extensionId],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ export function createManifestForLoad(
|
||||
*
|
||||
* Use when:
|
||||
* - Refreshing extension manifests from disk
|
||||
* - Looking up manifests by plugin name during load or inspect operations
|
||||
* - Looking up manifests by extension id during load or inspect operations
|
||||
*
|
||||
* Expects:
|
||||
* - `refresh()` is called before consumers read entries or manifests
|
||||
@@ -299,8 +299,8 @@ export interface ExtensionHostRegistry {
|
||||
refresh: () => Promise<ManifestEntry[]>
|
||||
listEntries: () => ManifestEntry[]
|
||||
listManifests: () => ExtensionManifestV1[]
|
||||
findManifestEntry: (name: string) => ManifestEntry | undefined
|
||||
getManifestEntryByName: () => Map<string, ManifestEntry>
|
||||
findManifestEntry: (extensionId: string) => ManifestEntry | undefined
|
||||
getManifestEntryByExtensionId: () => Map<string, ManifestEntry>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +321,7 @@ export function createExtensionHostRegistry(options: {
|
||||
}): ExtensionHostRegistry {
|
||||
let entries: ManifestEntry[] = []
|
||||
let manifests: ExtensionManifestV1[] = []
|
||||
let manifestEntryByName = new Map<string, ManifestEntry>()
|
||||
let manifestEntryByExtensionId = new Map<string, ManifestEntry>()
|
||||
|
||||
return {
|
||||
getRoot() {
|
||||
@@ -329,11 +329,11 @@ export function createExtensionHostRegistry(options: {
|
||||
},
|
||||
async refresh() {
|
||||
entries = await loadManifestsFrom(options.extensionsRoot, options.log)
|
||||
manifestEntryByName = new Map()
|
||||
manifestEntryByExtensionId = new Map()
|
||||
for (const entry of entries) {
|
||||
const id = manifestIdOf(entry.manifest)
|
||||
if (!manifestEntryByName.has(id)) {
|
||||
manifestEntryByName.set(id, entry)
|
||||
if (!manifestEntryByExtensionId.has(id)) {
|
||||
manifestEntryByExtensionId.set(id, entry)
|
||||
}
|
||||
}
|
||||
manifests = entries.map(entry => entry.manifest)
|
||||
@@ -345,11 +345,11 @@ export function createExtensionHostRegistry(options: {
|
||||
listManifests() {
|
||||
return manifests
|
||||
},
|
||||
findManifestEntry(name) {
|
||||
return manifestEntryByName.get(name)
|
||||
findManifestEntry(extensionId) {
|
||||
return manifestEntryByExtensionId.get(extensionId)
|
||||
},
|
||||
getManifestEntryByName() {
|
||||
return manifestEntryByName
|
||||
getManifestEntryByExtensionId() {
|
||||
return manifestEntryByExtensionId
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,8 +426,8 @@ describe('setupExtensionHost', () => {
|
||||
expect(snapshot.root).toBe(pluginsDir)
|
||||
expect(snapshot.plugins).toHaveLength(2)
|
||||
expect(snapshot.plugins).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'test-normal', path: normalPath, enabled: false, loaded: false, isNew: true }),
|
||||
expect.objectContaining({ name: 'test-error', path: errorPath, enabled: false, loaded: false, isNew: true }),
|
||||
expect.objectContaining({ extensionId: 'test-normal', path: normalPath, enabled: false, loaded: false, isNew: true }),
|
||||
expect.objectContaining({ extensionId: 'test-error', path: errorPath, enabled: false, loaded: false, isNew: true }),
|
||||
]))
|
||||
})
|
||||
|
||||
@@ -491,7 +491,7 @@ describe('setupExtensionHost', () => {
|
||||
|
||||
expect(snapshot.plugins).toEqual([
|
||||
expect.objectContaining({
|
||||
name: 'devtools-sample-plugin',
|
||||
extensionId: 'devtools-sample-plugin',
|
||||
path: manifestPath,
|
||||
enabled: false,
|
||||
loaded: false,
|
||||
@@ -528,13 +528,13 @@ describe('setupExtensionHost', () => {
|
||||
const invokeSetEnabled = defineInvoke(contextState.lastContext!, electronPluginSetEnabled)
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
|
||||
await invokeSetEnabled({ name: 'test-normal', enabled: true })
|
||||
await invokeSetEnabled({ name: 'test-error', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'test-normal', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'test-error', enabled: true })
|
||||
|
||||
const snapshot = await invokeLoadEnabled()
|
||||
|
||||
const normal = snapshot.plugins.find(plugin => plugin.name === 'test-normal')
|
||||
const error = snapshot.plugins.find(plugin => plugin.name === 'test-error')
|
||||
const normal = snapshot.plugins.find(plugin => plugin.extensionId === 'test-normal')
|
||||
const error = snapshot.plugins.find(plugin => plugin.extensionId === 'test-error')
|
||||
|
||||
expect(normal).toEqual(expect.objectContaining({ enabled: true, loaded: true }))
|
||||
expect(error).toEqual(expect.objectContaining({ enabled: true, loaded: false }))
|
||||
@@ -557,7 +557,7 @@ describe('setupExtensionHost', () => {
|
||||
await setupExtensionHost()
|
||||
|
||||
expect(contextState.lastContext).toBeDefined()
|
||||
const toolsChangedEvents: Array<{ reason: string, name?: string }> = []
|
||||
const toolsChangedEvents: Array<{ reason: string, extensionId?: string }> = []
|
||||
contextState.lastContext!.on(electronPluginToolsChanged, (event) => {
|
||||
if (!event.body) {
|
||||
throw new Error('Expected plugin tools changed event body.')
|
||||
@@ -567,12 +567,12 @@ describe('setupExtensionHost', () => {
|
||||
|
||||
const invokeLoad = defineInvoke(contextState.lastContext!, electronPluginLoad)
|
||||
|
||||
await invokeLoad({ name: 'test-tools-changed' })
|
||||
await invokeLoad({ extensionId: 'test-tools-changed' })
|
||||
|
||||
expect(toolsChangedEvents).toEqual([
|
||||
{
|
||||
reason: 'loaded',
|
||||
name: 'test-tools-changed',
|
||||
extensionId: 'test-tools-changed',
|
||||
},
|
||||
])
|
||||
})
|
||||
@@ -605,7 +605,7 @@ describe('setupExtensionHost', () => {
|
||||
const invokeSetEnabled = defineInvoke(contextState.lastContext!, electronPluginSetEnabled)
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
|
||||
await invokeSetEnabled({ name: 'duplicate-plugin', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'duplicate-plugin', enabled: true })
|
||||
await invokeLoadEnabled()
|
||||
|
||||
const duplicateSession = service.host
|
||||
@@ -631,16 +631,16 @@ describe('setupExtensionHost', () => {
|
||||
const invokeSetAutoReload = defineInvoke(contextState.lastContext!, electronPluginSetAutoReload)
|
||||
const invokeList = defineInvoke(contextState.lastContext!, electronPluginList)
|
||||
|
||||
await invokeSetAutoReload({ name: 'test-auto-reload', enabled: true })
|
||||
await invokeSetAutoReload({ extensionId: 'test-auto-reload', enabled: true })
|
||||
let snapshot = await invokeList()
|
||||
expect(snapshot.plugins).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'test-auto-reload', autoReload: true }),
|
||||
expect.objectContaining({ extensionId: 'test-auto-reload', autoReload: true }),
|
||||
]))
|
||||
|
||||
await invokeSetAutoReload({ name: 'test-auto-reload', enabled: false })
|
||||
await invokeSetAutoReload({ extensionId: 'test-auto-reload', enabled: false })
|
||||
snapshot = await invokeList()
|
||||
expect(snapshot.plugins).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'test-auto-reload', autoReload: false }),
|
||||
expect.objectContaining({ extensionId: 'test-auto-reload', autoReload: false }),
|
||||
]))
|
||||
})
|
||||
|
||||
@@ -667,12 +667,12 @@ describe('setupExtensionHost', () => {
|
||||
const invokeInspect = defineInvoke(contextState.lastContext!, electronPluginInspect)
|
||||
const invokeUnload = defineInvoke(contextState.lastContext!, electronPluginUnload)
|
||||
|
||||
await invokeSetEnabled({ name: 'test-auto-reload-reload', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'test-auto-reload-reload', enabled: true })
|
||||
await invokeLoadEnabled()
|
||||
await invokeSetAutoReload({ name: 'test-auto-reload-reload', enabled: true })
|
||||
await invokeSetAutoReload({ extensionId: 'test-auto-reload-reload', enabled: true })
|
||||
|
||||
const before = await invokeInspect()
|
||||
const beforeSession = before.sessions.find(session => session.manifestName === 'test-auto-reload-reload')
|
||||
const beforeSession = before.sessions.find(session => session.extensionId === 'test-auto-reload-reload')
|
||||
expect(beforeSession).toBeDefined()
|
||||
|
||||
const pluginSdkUrl = pathToFileURL(resolve(repoRoot, 'packages/plugin-sdk/src/index.ts')).href
|
||||
@@ -692,14 +692,14 @@ describe('setupExtensionHost', () => {
|
||||
while (Date.now() < deadline && afterSessionId === beforeSession?.id) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
const snapshot = await invokeInspect()
|
||||
afterSessionId = snapshot.sessions.find(session => session.manifestName === 'test-auto-reload-reload')?.id
|
||||
afterSessionId = snapshot.sessions.find(session => session.extensionId === 'test-auto-reload-reload')?.id
|
||||
}
|
||||
|
||||
expect(afterSessionId).toBeDefined()
|
||||
expect(afterSessionId).not.toEqual(beforeSession?.id)
|
||||
|
||||
await invokeSetAutoReload({ name: 'test-auto-reload-reload', enabled: false })
|
||||
await invokeUnload({ name: 'test-auto-reload-reload' })
|
||||
await invokeSetAutoReload({ extensionId: 'test-auto-reload-reload', enabled: false })
|
||||
await invokeUnload({ extensionId: 'test-auto-reload-reload' })
|
||||
})
|
||||
|
||||
it('loads enabled plugins with absolute manifest entrypoints outside the plugin directory', async () => {
|
||||
@@ -725,10 +725,10 @@ describe('setupExtensionHost', () => {
|
||||
const invokeSetEnabled = defineInvoke(contextState.lastContext!, electronPluginSetEnabled)
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
|
||||
await invokeSetEnabled({ name: 'test-absolute-entrypoint', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'test-absolute-entrypoint', enabled: true })
|
||||
|
||||
const snapshot = await invokeLoadEnabled()
|
||||
const plugin = snapshot.plugins.find(item => item.name === 'test-absolute-entrypoint')
|
||||
const plugin = snapshot.plugins.find(item => item.extensionId === 'test-absolute-entrypoint')
|
||||
|
||||
expect(plugin).toEqual(expect.objectContaining({ enabled: true, loaded: true }))
|
||||
}
|
||||
@@ -759,10 +759,10 @@ describe('setupExtensionHost', () => {
|
||||
const invokeSetEnabled = defineInvoke(contextState.lastContext!, electronPluginSetEnabled)
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
|
||||
await invokeSetEnabled({ name: 'devtools-sample-plugin', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'devtools-sample-plugin', enabled: true })
|
||||
|
||||
const snapshot = await invokeLoadEnabled()
|
||||
const plugin = snapshot.plugins.find(item => item.name === 'devtools-sample-plugin')
|
||||
const plugin = snapshot.plugins.find(item => item.extensionId === 'devtools-sample-plugin')
|
||||
|
||||
expect(plugin).toEqual(expect.objectContaining({ enabled: true, loaded: true }))
|
||||
})
|
||||
@@ -825,10 +825,10 @@ describe('setupExtensionHost', () => {
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
const invokeInspect = defineInvoke(contextState.lastContext!, electronPluginInspect)
|
||||
|
||||
await invokeSetEnabled({ name: 'airi-plugin-game-chess', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'airi-plugin-game-chess', enabled: true })
|
||||
|
||||
const registry = await invokeLoadEnabled()
|
||||
const plugin = registry.plugins.find(item => item.name === 'airi-plugin-game-chess')
|
||||
const plugin = registry.plugins.find(item => item.extensionId === 'airi-plugin-game-chess')
|
||||
expect(plugin).toEqual(expect.objectContaining({ enabled: true, loaded: true }))
|
||||
|
||||
const snapshot = await invokeInspect()
|
||||
@@ -837,7 +837,7 @@ describe('setupExtensionHost', () => {
|
||||
expect(snapshot.modules).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
moduleId: 'chess-like-main:gamelet',
|
||||
ownerPluginId: 'airi-plugin-game-chess',
|
||||
ownerExtensionId: 'airi-plugin-game-chess',
|
||||
kitId: 'kit.gamelet',
|
||||
kitModuleType: 'gamelet',
|
||||
runtime: 'electron',
|
||||
@@ -912,7 +912,7 @@ describe('setupExtensionHost', () => {
|
||||
const invokeLoadEnabled = defineInvoke(contextState.lastContext!, electronPluginLoadEnabled)
|
||||
const invokeInspect = defineInvoke(contextState.lastContext!, electronPluginInspect)
|
||||
|
||||
await invokeSetEnabled({ name: 'test-plugin-widget-asset-url', enabled: true })
|
||||
await invokeSetEnabled({ extensionId: 'test-plugin-widget-asset-url', enabled: true })
|
||||
await invokeLoadEnabled()
|
||||
const session = service.host
|
||||
.listSessions()
|
||||
@@ -947,7 +947,7 @@ describe('setupExtensionHost', () => {
|
||||
expect(snapshot.modules).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
moduleId: 'widget-shell-under-test',
|
||||
ownerPluginId: 'test-plugin-widget-asset-url',
|
||||
ownerExtensionId: 'test-plugin-widget-asset-url',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'window',
|
||||
runtime: 'electron',
|
||||
@@ -1084,7 +1084,7 @@ describe('setupExtensionHost', () => {
|
||||
expect.objectContaining({
|
||||
moduleId: 'widget-shell',
|
||||
ownerSessionId: session.id,
|
||||
ownerPluginId: 'test-dynamic-module',
|
||||
ownerExtensionId: 'test-dynamic-module',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'window',
|
||||
runtime: 'electron',
|
||||
@@ -1183,7 +1183,7 @@ describe('setupExtensionHost', () => {
|
||||
|
||||
expect(binding).toEqual(expect.objectContaining({
|
||||
moduleId: 'kit-module:gamelet',
|
||||
ownerPluginId: 'test-extension-gamelet-kit',
|
||||
ownerExtensionId: 'test-extension-gamelet-kit',
|
||||
ownerSessionId: session.id,
|
||||
kitId: 'kit.gamelet',
|
||||
kitModuleType: 'gamelet',
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function setupExtensionHost(options: SetupExtensionHostOptions): Pr
|
||||
const result = await hostService.setEnabled(payload)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'enabled-state-changed',
|
||||
name: payload.name,
|
||||
extensionId: payload.extensionId,
|
||||
})
|
||||
return result
|
||||
})
|
||||
@@ -75,19 +75,19 @@ export async function setupExtensionHost(options: SetupExtensionHostOptions): Pr
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginLoad, async (payload) => {
|
||||
const result = await hostService.load(payload.name)
|
||||
const result = await hostService.load(payload.extensionId)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'loaded',
|
||||
name: payload.name,
|
||||
extensionId: payload.extensionId,
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginUnload, async (payload) => {
|
||||
const result = await hostService.unload(payload.name)
|
||||
const result = await hostService.unload(payload.extensionId)
|
||||
context.emit(electronPluginToolsChanged, {
|
||||
reason: 'unloaded',
|
||||
name: payload.name,
|
||||
extensionId: payload.extensionId,
|
||||
})
|
||||
return result
|
||||
})
|
||||
@@ -109,7 +109,7 @@ export async function setupExtensionHost(options: SetupExtensionHostOptions): Pr
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginInvokeTool, async (payload) => {
|
||||
return await hostService.tools.invoke(payload.ownerPluginId, payload.name, payload.input)
|
||||
return await hostService.tools.invoke(payload.ownerExtensionId, payload.name, payload.input)
|
||||
})
|
||||
|
||||
defineInvokeHandler(context, electronPluginUpdateCapability, async (payload) => {
|
||||
|
||||
@@ -57,7 +57,7 @@ function createHostToolKit(options: { tools: TamagotchiToolRegistry }): KitRef<T
|
||||
ensureCleanup()
|
||||
options.tools.register({
|
||||
ownerSessionId: runtime.sessionId,
|
||||
ownerPluginId: runtime.extensionId,
|
||||
ownerExtensionId: runtime.extensionId,
|
||||
ownerModuleId: runtime.moduleId,
|
||||
...input,
|
||||
})
|
||||
@@ -66,7 +66,7 @@ function createHostToolKit(options: { tools: TamagotchiToolRegistry }): KitRef<T
|
||||
ensureCleanup()
|
||||
options.tools.registerToolsetPrompt({
|
||||
ownerSessionId: runtime.sessionId,
|
||||
ownerPluginId: runtime.extensionId,
|
||||
ownerExtensionId: runtime.extensionId,
|
||||
ownerModuleId: runtime.moduleId,
|
||||
toolset: input,
|
||||
})
|
||||
|
||||
@@ -87,7 +87,7 @@ export function resolveWidgetAssetRoute(assetPath: string): WidgetAssetRoute | u
|
||||
*
|
||||
* Expects:
|
||||
* - Module config may contain widget iframe `src` or `assetPath` fields
|
||||
* - Mapping includes a manifest entry for `module.ownerPluginId`
|
||||
* - Mapping includes a manifest entry for `module.ownerExtensionId`
|
||||
*
|
||||
* Returns:
|
||||
* - Original module when rewrite is not applicable
|
||||
@@ -95,9 +95,9 @@ export function resolveWidgetAssetRoute(assetPath: string): WidgetAssetRoute | u
|
||||
*/
|
||||
export function rewriteWidgetModuleAssetUrl(
|
||||
module: PluginHostModuleSummary,
|
||||
manifestEntryByName: Map<string, ManifestEntry>,
|
||||
manifestEntryByExtensionId: Map<string, ManifestEntry>,
|
||||
options?: {
|
||||
pluginAssetBaseUrl?: string
|
||||
extensionAssetBaseUrl?: string
|
||||
createAssetSession?: (input: {
|
||||
extensionId: string
|
||||
version: string
|
||||
@@ -107,7 +107,7 @@ export function rewriteWidgetModuleAssetUrl(
|
||||
}) => Promise<{ assetSessionId: string, url?: string }>
|
||||
},
|
||||
): Promise<PluginHostModuleSummary> | PluginHostModuleSummary {
|
||||
const entry = manifestEntryByName.get(module.ownerPluginId)
|
||||
const entry = manifestEntryByExtensionId.get(module.ownerExtensionId)
|
||||
if (!entry) {
|
||||
return module
|
||||
}
|
||||
@@ -138,23 +138,23 @@ export function rewriteWidgetModuleAssetUrl(
|
||||
return module
|
||||
}
|
||||
|
||||
if (!options?.pluginAssetBaseUrl || !options.createAssetSession) {
|
||||
if (!options?.extensionAssetBaseUrl || !options.createAssetSession) {
|
||||
return module
|
||||
}
|
||||
|
||||
return options.createAssetSession({
|
||||
extensionId: module.ownerPluginId,
|
||||
extensionId: module.ownerExtensionId,
|
||||
version: entry.version,
|
||||
sessionId: module.ownerSessionId,
|
||||
routeAssetPath: widgetAssetRoute.routeAssetPath,
|
||||
sessionPathPrefix: widgetAssetRoute.sessionPathPrefix,
|
||||
}).then((session) => {
|
||||
const mountedPath = buildMountedStaticAssetPath({
|
||||
extensionId: module.ownerPluginId,
|
||||
extensionId: module.ownerExtensionId,
|
||||
assetSessionId: session.assetSessionId,
|
||||
assetPath: widgetAssetRoute.routeAssetPath,
|
||||
})
|
||||
const iframeUrl = session.url ?? (mountedPath ? new URL(mountedPath, options.pluginAssetBaseUrl).toString() : '')
|
||||
const iframeUrl = session.url ?? (mountedPath ? new URL(mountedPath, options.extensionAssetBaseUrl).toString() : '')
|
||||
if (!iframeUrl) {
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import type {
|
||||
WidgetsUpdatePayload,
|
||||
} from '../../../../shared/eventa'
|
||||
|
||||
/**
|
||||
* Stable manifest id used as the runtime identity for one extension.
|
||||
*/
|
||||
export type ExtensionId = string
|
||||
|
||||
/**
|
||||
* Runtime-facing extension host service bundle returned by setup.
|
||||
*
|
||||
@@ -108,20 +113,20 @@ export interface ExtensionHostBindingListOptions {
|
||||
* Persisted extension configuration snapshot.
|
||||
*
|
||||
* Use when:
|
||||
* - Reading/writing enabled and auto-reload plugin state
|
||||
* - Reading/writing enabled and auto-reload extension state
|
||||
* - Keeping known extension manifest path metadata
|
||||
*
|
||||
* Expects:
|
||||
* - Arrays contain extension manifest names
|
||||
* - `known` maps plugin names to canonical manifest paths
|
||||
* - Arrays contain extension manifest ids
|
||||
* - `known` maps extension manifest ids to canonical manifest paths
|
||||
*
|
||||
* Returns:
|
||||
* - N/A
|
||||
*/
|
||||
export interface ExtensionConfig {
|
||||
enabled: string[]
|
||||
autoReload: string[]
|
||||
known: Record<string, { path: string }>
|
||||
enabled: ExtensionId[]
|
||||
autoReload: ExtensionId[]
|
||||
known: Record<ExtensionId, { path: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ const invokeMocks = vi.hoisted(() => ({
|
||||
listPluginXsaiTools: vi.fn(async () => ({
|
||||
tools: [
|
||||
{
|
||||
ownerPluginId: 'plugin-chess',
|
||||
ownerExtensionId: 'plugin-chess',
|
||||
name: 'play_chess',
|
||||
description: 'Play a chess move.',
|
||||
parameters: {
|
||||
@@ -21,7 +21,7 @@ const invokeMocks = vi.hoisted(() => ({
|
||||
],
|
||||
prompts: [
|
||||
{
|
||||
ownerPluginId: 'plugin-chess',
|
||||
ownerExtensionId: 'plugin-chess',
|
||||
id: 'chess-tools',
|
||||
prompt: {
|
||||
id: 'airi-plugin-game-chess.prompt',
|
||||
@@ -84,14 +84,14 @@ describe('useTamagotchiPluginToolsStore', async () => {
|
||||
}, toolOptions)
|
||||
|
||||
expect(invokeMocks.invokePluginTool).toHaveBeenCalledWith({
|
||||
ownerPluginId: 'plugin-chess',
|
||||
ownerExtensionId: 'plugin-chess',
|
||||
name: 'play_chess',
|
||||
input: {
|
||||
move: 'e2e4',
|
||||
},
|
||||
})
|
||||
expect(executionResult).toEqual({
|
||||
ownerPluginId: 'plugin-chess',
|
||||
ownerExtensionId: 'plugin-chess',
|
||||
name: 'play_chess',
|
||||
input: {
|
||||
move: 'e2e4',
|
||||
|
||||
@@ -43,7 +43,7 @@ export const useTamagotchiPluginToolsStore = defineStore('tamagotchi-plugin-tool
|
||||
llmToolsetPromptsStore.registerToolsetPrompts(
|
||||
'plugin-tools',
|
||||
definitions.prompts.map(definition => ({
|
||||
id: `${definition.ownerPluginId}:${definition.id}`,
|
||||
id: `${definition.ownerExtensionId}:${definition.id}`,
|
||||
title: definition.prompt.title,
|
||||
content: definition.prompt.content,
|
||||
})),
|
||||
@@ -55,7 +55,7 @@ export const useTamagotchiPluginToolsStore = defineStore('tamagotchi-plugin-tool
|
||||
description: definition.description,
|
||||
parameters: definition.parameters,
|
||||
execute: async input => invokePluginTool({
|
||||
ownerPluginId: definition.ownerPluginId,
|
||||
ownerExtensionId: definition.ownerExtensionId,
|
||||
name: definition.name,
|
||||
input,
|
||||
}),
|
||||
|
||||
@@ -571,7 +571,7 @@ describe('widgets tool helpers', () => {
|
||||
moduleSnapshot: {
|
||||
moduleId: 'module-1',
|
||||
ownerSessionId: 'session-1',
|
||||
ownerPluginId: 'plugin-1',
|
||||
ownerExtensionId: 'plugin-1',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'window',
|
||||
state: 'active',
|
||||
|
||||
@@ -142,7 +142,7 @@ export interface WidgetSnapshot {
|
||||
}
|
||||
|
||||
export interface PluginManifestSummary {
|
||||
name: string
|
||||
extensionId: string
|
||||
entrypoints: Record<string, string | undefined>
|
||||
path: string
|
||||
enabled: boolean
|
||||
@@ -173,7 +173,7 @@ export interface PluginCapabilityState {
|
||||
|
||||
export interface PluginHostSessionSummary {
|
||||
id: string
|
||||
manifestName: string
|
||||
extensionId: string
|
||||
phase: string
|
||||
runtime: 'electron' | 'node' | 'web'
|
||||
moduleId: string
|
||||
|
||||
@@ -58,7 +58,7 @@ export interface PluginModuleWidgetPayload {
|
||||
* - N/A
|
||||
*/
|
||||
export interface PluginManifestSummary {
|
||||
name: string
|
||||
extensionId: string
|
||||
entrypoints: Record<string, string | undefined>
|
||||
path: string
|
||||
enabled: boolean
|
||||
@@ -98,7 +98,7 @@ export interface PluginRegistrySnapshot {
|
||||
*/
|
||||
export interface PluginHostSessionSummary {
|
||||
id: string
|
||||
manifestName: string
|
||||
extensionId: string
|
||||
phase: string
|
||||
runtime: 'electron' | 'node' | 'web'
|
||||
moduleId: string
|
||||
@@ -155,7 +155,7 @@ export interface PluginHostKitSummary {
|
||||
export interface PluginHostModuleSummary {
|
||||
moduleId: string
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
kitId: string
|
||||
kitModuleType: string
|
||||
state: 'announced' | 'active' | 'degraded' | 'withdrawn'
|
||||
@@ -187,9 +187,9 @@ export interface PluginHostDebugSnapshot {
|
||||
}
|
||||
|
||||
export const electronPluginList = defineInvokeEventa<PluginRegistrySnapshot>('eventa:invoke:electron:plugins:list')
|
||||
export const electronPluginSetEnabled = defineInvokeEventa<PluginRegistrySnapshot, { name: string, enabled: boolean, path?: string }>('eventa:invoke:electron:plugins:set-enabled')
|
||||
export const electronPluginSetAutoReload = defineInvokeEventa<PluginRegistrySnapshot, { name: string, enabled: boolean }>('eventa:invoke:electron:plugins:set-auto-reload')
|
||||
export const electronPluginSetEnabled = defineInvokeEventa<PluginRegistrySnapshot, { extensionId: string, enabled: boolean, path?: string }>('eventa:invoke:electron:plugins:set-enabled')
|
||||
export const electronPluginSetAutoReload = defineInvokeEventa<PluginRegistrySnapshot, { extensionId: string, enabled: boolean }>('eventa:invoke:electron:plugins:set-auto-reload')
|
||||
export const electronPluginLoadEnabled = defineInvokeEventa<PluginRegistrySnapshot>('eventa:invoke:electron:plugins:load-enabled')
|
||||
export const electronPluginLoad = defineInvokeEventa<PluginRegistrySnapshot, { name: string }>('eventa:invoke:electron:plugins:load')
|
||||
export const electronPluginUnload = defineInvokeEventa<PluginRegistrySnapshot, { name: string }>('eventa:invoke:electron:plugins:unload')
|
||||
export const electronPluginLoad = defineInvokeEventa<PluginRegistrySnapshot, { extensionId: string }>('eventa:invoke:electron:plugins:load')
|
||||
export const electronPluginUnload = defineInvokeEventa<PluginRegistrySnapshot, { extensionId: string }>('eventa:invoke:electron:plugins:unload')
|
||||
export const electronPluginInspect = defineInvokeEventa<PluginHostDebugSnapshot>('eventa:invoke:electron:plugins:inspect')
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface ElectronPluginToolDescriptor {
|
||||
* - N/A
|
||||
*/
|
||||
export interface ElectronPluginXsaiToolDefinition {
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
name: string
|
||||
description: string
|
||||
parameters: Record<string, unknown>
|
||||
@@ -54,7 +54,7 @@ export interface ElectronPluginXsaiToolDefinition {
|
||||
* - N/A
|
||||
*/
|
||||
export interface ElectronPluginToolsetPromptDefinition {
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
id: string
|
||||
prompt: {
|
||||
id: string
|
||||
@@ -87,20 +87,20 @@ export interface ElectronPluginXsaiToolsetDefinition {
|
||||
* - The main process notifies renderers after plugin lifecycle changes
|
||||
*
|
||||
* Expects:
|
||||
* - `name` is present when the change is scoped to one plugin
|
||||
* - `extensionId` is present when the change is scoped to one extension
|
||||
*
|
||||
* Returns:
|
||||
* - N/A
|
||||
*/
|
||||
export interface ElectronPluginToolsChangedPayload {
|
||||
reason: 'loaded' | 'load-enabled' | 'unloaded' | 'enabled-state-changed'
|
||||
name?: string
|
||||
extensionId?: 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, {
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
name: string
|
||||
input: unknown
|
||||
}>('eventa:invoke:electron:plugins:tools:invoke')
|
||||
|
||||
@@ -467,7 +467,7 @@ describe('plugin-sdk-tamagotchi', () => {
|
||||
|
||||
registry.register({
|
||||
ownerSessionId: 'session-1',
|
||||
ownerPluginId: 'airi-extension-chess',
|
||||
ownerExtensionId: 'airi-extension-chess',
|
||||
ownerModuleId: 'chess',
|
||||
tool: {
|
||||
id: 'play_chess',
|
||||
@@ -486,7 +486,7 @@ describe('plugin-sdk-tamagotchi', () => {
|
||||
})
|
||||
registry.registerToolsetPrompt({
|
||||
ownerSessionId: 'session-1',
|
||||
ownerPluginId: 'airi-extension-chess',
|
||||
ownerExtensionId: 'airi-extension-chess',
|
||||
ownerModuleId: 'chess',
|
||||
toolset: {
|
||||
id: 'chess-tools',
|
||||
@@ -508,7 +508,7 @@ describe('plugin-sdk-tamagotchi', () => {
|
||||
}])
|
||||
await expect(registry.listSerializedXsaiTools()).resolves.toEqual({
|
||||
prompts: [{
|
||||
ownerPluginId: 'airi-extension-chess',
|
||||
ownerExtensionId: 'airi-extension-chess',
|
||||
id: 'chess-tools',
|
||||
prompt: {
|
||||
id: 'airi-plugin-game-chess.prompt',
|
||||
@@ -516,7 +516,7 @@ describe('plugin-sdk-tamagotchi', () => {
|
||||
},
|
||||
}],
|
||||
tools: [{
|
||||
ownerPluginId: 'airi-extension-chess',
|
||||
ownerExtensionId: 'airi-extension-chess',
|
||||
name: 'play_chess',
|
||||
description: 'Open chess.',
|
||||
parameters: {
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface RegisteredPluginToolDescriptor {
|
||||
* Describes the JSON-schema side of an xsai-compatible Tamagotchi extension tool.
|
||||
*/
|
||||
export interface SerializedXsaiToolDefinition {
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
name: string
|
||||
description: string
|
||||
parameters: HostDataRecord
|
||||
@@ -36,7 +36,7 @@ export interface ToolsetPromptManifest {
|
||||
* Captures one registered toolset prompt with extension ownership metadata.
|
||||
*/
|
||||
export interface SerializedToolsetPromptDefinition {
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
id: string
|
||||
prompt: ToolsetPromptManifest
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export interface PluginToolsetPromptDefinitionRecord {
|
||||
*/
|
||||
export interface ToolRegistryRecord {
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
ownerModuleId?: string
|
||||
tool: PluginToolDefinitionRecord
|
||||
availability?: () => Promise<boolean> | boolean
|
||||
@@ -88,7 +88,7 @@ export interface ToolRegistryRecord {
|
||||
*/
|
||||
export interface ToolsetPromptRegistryRecord {
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
ownerModuleId?: string
|
||||
toolset: PluginToolsetPromptDefinitionRecord
|
||||
availability?: () => Promise<boolean> | boolean
|
||||
@@ -112,23 +112,23 @@ export class TamagotchiToolRegistry {
|
||||
private readonly toolsetPrompts = new Map<string, ToolsetPromptRegistryRecord>()
|
||||
|
||||
register(record: ToolRegistryRecord) {
|
||||
const key = `${record.ownerPluginId}:${record.tool.id}`
|
||||
const key = `${record.ownerExtensionId}:${record.tool.id}`
|
||||
this.tools.set(key, record)
|
||||
return record
|
||||
}
|
||||
|
||||
registerToolsetPrompt(record: ToolsetPromptRegistryRecord) {
|
||||
const key = `${record.ownerPluginId}:${record.toolset.id}`
|
||||
const key = `${record.ownerExtensionId}:${record.toolset.id}`
|
||||
this.toolsetPrompts.set(key, record)
|
||||
return record
|
||||
}
|
||||
|
||||
unregister(ownerPluginId: string, toolId: string) {
|
||||
return this.tools.delete(`${ownerPluginId}:${toolId}`)
|
||||
unregister(ownerExtensionId: string, toolId: string) {
|
||||
return this.tools.delete(`${ownerExtensionId}:${toolId}`)
|
||||
}
|
||||
|
||||
unregisterToolsetPrompt(ownerPluginId: string, toolsetId: string) {
|
||||
return this.toolsetPrompts.delete(`${ownerPluginId}:${toolsetId}`)
|
||||
unregisterToolsetPrompt(ownerExtensionId: string, toolsetId: string) {
|
||||
return this.toolsetPrompts.delete(`${ownerExtensionId}:${toolsetId}`)
|
||||
}
|
||||
|
||||
unregisterOwnerSession(ownerSessionId: string) {
|
||||
@@ -195,7 +195,7 @@ export class TamagotchiToolRegistry {
|
||||
}
|
||||
|
||||
prompts.push({
|
||||
ownerPluginId: record.ownerPluginId,
|
||||
ownerExtensionId: record.ownerExtensionId,
|
||||
id: record.toolset.id,
|
||||
prompt: structuredClone(record.toolset.prompt),
|
||||
})
|
||||
@@ -213,7 +213,7 @@ export class TamagotchiToolRegistry {
|
||||
}
|
||||
|
||||
items.push({
|
||||
ownerPluginId: record.ownerPluginId,
|
||||
ownerExtensionId: record.ownerExtensionId,
|
||||
name: record.tool.id,
|
||||
description: record.tool.description,
|
||||
parameters: structuredClone(record.tool.parameters),
|
||||
@@ -226,8 +226,8 @@ export class TamagotchiToolRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
async invoke(ownerPluginId: string, toolId: string, input: unknown) {
|
||||
const key = `${ownerPluginId}:${toolId}`
|
||||
async invoke(ownerExtensionId: string, toolId: string, input: unknown) {
|
||||
const key = `${ownerExtensionId}:${toolId}`
|
||||
const record = this.tools.get(key)
|
||||
if (!record) {
|
||||
throw new Error(`Tamagotchi extension tool not found: ${key}`)
|
||||
|
||||
@@ -665,7 +665,7 @@ export class ExtensionHost {
|
||||
return cloneBindingRecord(this.modules.bind({
|
||||
...input,
|
||||
ownerSessionId: session.id,
|
||||
ownerPluginId: session.extension.id,
|
||||
ownerExtensionId: session.extension.id,
|
||||
runtime: session.runtime ?? this.runtime,
|
||||
}) as BindingRecord<C>)
|
||||
}
|
||||
@@ -763,7 +763,7 @@ export class ExtensionHost {
|
||||
const binding = cloneBindingRecord(this.modules.bind({
|
||||
...input,
|
||||
ownerSessionId: session.id,
|
||||
ownerPluginId: session.extension.id,
|
||||
ownerExtensionId: session.extension.id,
|
||||
runtime: this.runtime,
|
||||
}) as BindingRecord<C>)
|
||||
|
||||
|
||||
+11
-11
@@ -9,7 +9,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
const binding = service.bind({
|
||||
moduleId: 'chess-gamelet',
|
||||
ownerSessionId: 'session-1',
|
||||
ownerPluginId: 'airi-extension-chess',
|
||||
ownerExtensionId: 'airi-extension-chess',
|
||||
kitId: 'kit.gamelet',
|
||||
kitModuleType: 'gamelet',
|
||||
config: { title: 'Chess' },
|
||||
@@ -26,7 +26,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm1',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -42,7 +42,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
const announced = service.bind({
|
||||
moduleId: 'm2',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: { mountPoint: 'widgets' },
|
||||
@@ -67,7 +67,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm3',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -85,7 +85,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm4',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -96,7 +96,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm4',
|
||||
ownerSessionId: 'session-b',
|
||||
ownerPluginId: 'plugin-b',
|
||||
ownerExtensionId: 'plugin-b',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -111,7 +111,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
const original = service.bind({
|
||||
moduleId: 'm5',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: { mountPoint: 'widgets' },
|
||||
@@ -121,7 +121,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
const duplicate = service.bind({
|
||||
moduleId: 'm5',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'dialog',
|
||||
config: { mountPoint: 'mutated', width: 480 },
|
||||
@@ -140,7 +140,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm6',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -151,7 +151,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm6',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-b',
|
||||
ownerExtensionId: 'plugin-b',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
@@ -166,7 +166,7 @@ describe('kitApiBindingRegistryService', () => {
|
||||
service.bind({
|
||||
moduleId: 'm7',
|
||||
ownerSessionId: 'session-a',
|
||||
ownerPluginId: 'plugin-a',
|
||||
ownerExtensionId: 'plugin-a',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
config: {},
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { HostDataRecord, PluginRuntime } from '../../../shared/types'
|
||||
export interface BindingInput<C extends HostDataRecord = HostDataRecord> {
|
||||
moduleId: string
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
kitId: string
|
||||
kitModuleType: string
|
||||
runtime: PluginRuntime
|
||||
@@ -55,14 +55,14 @@ export interface BindingUpdatePatch<C extends HostDataRecord = HostDataRecord> {
|
||||
*
|
||||
* Expects:
|
||||
* - `ownerSessionId` is the ephemeral runtime session id
|
||||
* - `ownerPluginId` is the stable plugin identity across sessions
|
||||
* - `ownerExtensionId` is the stable extension identity across sessions
|
||||
*
|
||||
* Returns:
|
||||
* - A compact identity tuple used in collision and ownership checks
|
||||
*/
|
||||
export interface BindingOwnerIdentity {
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
}
|
||||
|
||||
const allowedBindingTransitions: Record<BindingState, readonly BindingState[]> = {
|
||||
@@ -78,7 +78,7 @@ function createOwnershipError(
|
||||
actual: BindingOwnerIdentity,
|
||||
) {
|
||||
return new Error(
|
||||
`Ownership violation for module \`${moduleId}\`: owned by \`${expected.ownerSessionId}/${expected.ownerPluginId}\`, not \`${actual.ownerSessionId}/${actual.ownerPluginId}\`.`,
|
||||
`Ownership violation for module \`${moduleId}\`: owned by \`${expected.ownerSessionId}/${expected.ownerExtensionId}\`, not \`${actual.ownerSessionId}/${actual.ownerExtensionId}\`.`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ function createModuleCollisionError(
|
||||
actual: BindingOwnerIdentity,
|
||||
) {
|
||||
return new Error(
|
||||
`Module id collision for \`${moduleId}\`: owned by \`${expected.ownerSessionId}/${expected.ownerPluginId}\`, not \`${actual.ownerSessionId}/${actual.ownerPluginId}\`.`,
|
||||
`Module id collision for \`${moduleId}\`: owned by \`${expected.ownerSessionId}/${expected.ownerExtensionId}\`, not \`${actual.ownerSessionId}/${actual.ownerExtensionId}\`.`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,17 +192,17 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
if (current) {
|
||||
if (
|
||||
current.ownerSessionId !== input.ownerSessionId
|
||||
|| current.ownerPluginId !== input.ownerPluginId
|
||||
|| current.ownerExtensionId !== input.ownerExtensionId
|
||||
) {
|
||||
throw createModuleCollisionError(
|
||||
input.moduleId,
|
||||
{
|
||||
ownerSessionId: current.ownerSessionId,
|
||||
ownerPluginId: current.ownerPluginId,
|
||||
ownerExtensionId: current.ownerExtensionId,
|
||||
},
|
||||
{
|
||||
ownerSessionId: input.ownerSessionId,
|
||||
ownerPluginId: input.ownerPluginId,
|
||||
ownerExtensionId: input.ownerExtensionId,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -213,7 +213,7 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
const record: BindingRecord<C> = {
|
||||
moduleId: input.moduleId,
|
||||
ownerSessionId: input.ownerSessionId,
|
||||
ownerPluginId: input.ownerPluginId,
|
||||
ownerExtensionId: input.ownerExtensionId,
|
||||
kitId: input.kitId,
|
||||
kitModuleType: input.kitModuleType,
|
||||
state: 'announced',
|
||||
@@ -343,8 +343,8 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
* Returns:
|
||||
* - The updated binding record with incremented revision and timestamp
|
||||
*/
|
||||
update(ownerSessionId: string, ownerPluginId: string, moduleId: string, patch: BindingUpdatePatch<C>) {
|
||||
return this.transition({ ownerSessionId, ownerPluginId }, moduleId, patch.state, patch)
|
||||
update(ownerSessionId: string, ownerExtensionId: string, moduleId: string, patch: BindingUpdatePatch<C>) {
|
||||
return this.transition({ ownerSessionId, ownerExtensionId }, moduleId, patch.state, patch)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -359,8 +359,8 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
* Returns:
|
||||
* - The updated active binding record
|
||||
*/
|
||||
activate(ownerSessionId: string, ownerPluginId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerPluginId }, moduleId, 'active')
|
||||
activate(ownerSessionId: string, ownerExtensionId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerExtensionId }, moduleId, 'active')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -375,8 +375,8 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
* Returns:
|
||||
* - The updated degraded binding record
|
||||
*/
|
||||
degrade(ownerSessionId: string, ownerPluginId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerPluginId }, moduleId, 'degraded')
|
||||
degrade(ownerSessionId: string, ownerExtensionId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerExtensionId }, moduleId, 'degraded')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,8 +391,8 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
* Returns:
|
||||
* - The updated withdrawn binding record
|
||||
*/
|
||||
withdraw(ownerSessionId: string, ownerPluginId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerPluginId }, moduleId, 'withdrawn')
|
||||
withdraw(ownerSessionId: string, ownerExtensionId: string, moduleId: string) {
|
||||
return this.transition({ ownerSessionId, ownerExtensionId }, moduleId, 'withdrawn')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,13 +421,13 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
|
||||
if (
|
||||
current.ownerSessionId !== owner.ownerSessionId
|
||||
|| current.ownerPluginId !== owner.ownerPluginId
|
||||
|| current.ownerExtensionId !== owner.ownerExtensionId
|
||||
) {
|
||||
throw createOwnershipError(
|
||||
moduleId,
|
||||
{
|
||||
ownerSessionId: current.ownerSessionId,
|
||||
ownerPluginId: current.ownerPluginId,
|
||||
ownerExtensionId: current.ownerExtensionId,
|
||||
},
|
||||
owner,
|
||||
)
|
||||
@@ -464,7 +464,7 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
* Returns:
|
||||
* - The removed binding record, or `undefined` when nothing existed
|
||||
*/
|
||||
unbind(ownerSessionId: string, ownerPluginId: string, moduleId: string) {
|
||||
unbind(ownerSessionId: string, ownerExtensionId: string, moduleId: string) {
|
||||
const current = this.bindings.get(moduleId)
|
||||
if (!current) {
|
||||
return undefined
|
||||
@@ -472,17 +472,17 @@ export class KitApiBindingRegistryService<C extends HostDataRecord = HostDataRec
|
||||
|
||||
if (
|
||||
current.ownerSessionId !== ownerSessionId
|
||||
|| current.ownerPluginId !== ownerPluginId
|
||||
|| current.ownerExtensionId !== ownerExtensionId
|
||||
) {
|
||||
throw createOwnershipError(
|
||||
moduleId,
|
||||
{
|
||||
ownerSessionId: current.ownerSessionId,
|
||||
ownerPluginId: current.ownerPluginId,
|
||||
ownerExtensionId: current.ownerExtensionId,
|
||||
},
|
||||
{
|
||||
ownerSessionId,
|
||||
ownerPluginId,
|
||||
ownerExtensionId,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ export class PermissionService {
|
||||
}
|
||||
|
||||
initialize(
|
||||
pluginId: string,
|
||||
extensionId: string,
|
||||
requestedDeclaration: ModulePermissionDeclaration,
|
||||
options?: {
|
||||
grant?: ModulePermissionGrant
|
||||
@@ -305,21 +305,21 @@ export class PermissionService {
|
||||
const explicitGrant = options?.grant ?? requested
|
||||
const mergedGrant = mergePermissions(persisted, explicitGrant)
|
||||
const granted = intersectPermissions(requested, mergedGrant)
|
||||
const previousRevision = this.store.get(pluginId)?.revision ?? 0
|
||||
const previousRevision = this.store.get(extensionId)?.revision ?? 0
|
||||
const snapshot: PermissionSnapshot = {
|
||||
requested,
|
||||
granted,
|
||||
revision: previousRevision + 1,
|
||||
}
|
||||
|
||||
this.store.set(pluginId, snapshot)
|
||||
this.store.set(extensionId, snapshot)
|
||||
return snapshot
|
||||
}
|
||||
|
||||
declare(pluginId: string, requestedDeclaration: ModulePermissionDeclaration) {
|
||||
const existing = this.store.get(pluginId)
|
||||
declare(extensionId: string, requestedDeclaration: ModulePermissionDeclaration) {
|
||||
const existing = this.store.get(extensionId)
|
||||
if (!existing) {
|
||||
throw new Error(`Cannot declare permissions for unknown plugin "${pluginId}".`)
|
||||
throw new Error(`Cannot declare permissions for unknown plugin "${extensionId}".`)
|
||||
}
|
||||
|
||||
const requested = normalizeDeclaration(requestedDeclaration)
|
||||
@@ -329,14 +329,14 @@ export class PermissionService {
|
||||
revision: existing.revision + 1,
|
||||
}
|
||||
|
||||
this.store.set(pluginId, snapshot)
|
||||
this.store.set(extensionId, snapshot)
|
||||
return snapshot
|
||||
}
|
||||
|
||||
grant(pluginId: string, grant: ModulePermissionGrant) {
|
||||
const existing = this.store.get(pluginId)
|
||||
grant(extensionId: string, grant: ModulePermissionGrant) {
|
||||
const existing = this.store.get(extensionId)
|
||||
if (!existing) {
|
||||
throw new Error(`Cannot grant permissions to unknown plugin "${pluginId}".`)
|
||||
throw new Error(`Cannot grant permissions to unknown plugin "${extensionId}".`)
|
||||
}
|
||||
|
||||
const mergedGranted = mergePermissions(existing.granted, grant)
|
||||
@@ -345,16 +345,16 @@ export class PermissionService {
|
||||
granted: intersectPermissions(existing.requested, mergedGranted),
|
||||
revision: existing.revision + 1,
|
||||
}
|
||||
this.store.set(pluginId, snapshot)
|
||||
this.store.set(extensionId, snapshot)
|
||||
return snapshot
|
||||
}
|
||||
|
||||
get(pluginId: string) {
|
||||
return this.store.get(pluginId)
|
||||
get(extensionId: string) {
|
||||
return this.store.get(extensionId)
|
||||
}
|
||||
|
||||
isAllowed(pluginId: string, area: ModulePermissionArea, action: string, key: string) {
|
||||
const snapshot = this.store.get(pluginId)
|
||||
isAllowed(extensionId: string, area: ModulePermissionArea, action: string, key: string) {
|
||||
const snapshot = this.store.get(extensionId)
|
||||
if (!snapshot) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('bindingRecordSchema', () => {
|
||||
const parsed = parse(bindingRecordSchema, {
|
||||
moduleId: 'board-main',
|
||||
ownerSessionId: 'extension-session-1',
|
||||
ownerPluginId: 'demo-plugin',
|
||||
ownerExtensionId: 'demo-plugin',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
state: 'announced',
|
||||
@@ -27,7 +27,7 @@ describe('bindingRecordSchema', () => {
|
||||
parse(bindingRecordSchema, {
|
||||
moduleId: 'board-main',
|
||||
ownerSessionId: 'extension-session-1',
|
||||
ownerPluginId: 'demo-plugin',
|
||||
ownerExtensionId: 'demo-plugin',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
state: 'booting',
|
||||
@@ -44,7 +44,7 @@ describe('bindingRecordSchema', () => {
|
||||
parse(bindingRecordSchema, {
|
||||
moduleId: 'board-main',
|
||||
ownerSessionId: 'extension-session-1',
|
||||
ownerPluginId: 'demo-plugin',
|
||||
ownerExtensionId: 'demo-plugin',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
state: 'announced',
|
||||
@@ -65,7 +65,7 @@ describe('bindingRecordSchema', () => {
|
||||
parse(bindingRecordSchema, {
|
||||
moduleId: 'board-main',
|
||||
ownerSessionId: 'extension-session-1',
|
||||
ownerPluginId: 'demo-plugin',
|
||||
ownerExtensionId: 'demo-plugin',
|
||||
kitId: 'kit.widget',
|
||||
kitModuleType: 'panel',
|
||||
state: 'announced',
|
||||
|
||||
@@ -36,7 +36,7 @@ export const bindingStateValues = ['announced', 'active', 'degraded', 'withdrawn
|
||||
export const bindingRecordSchema = object({
|
||||
moduleId: string(),
|
||||
ownerSessionId: string(),
|
||||
ownerPluginId: string(),
|
||||
ownerExtensionId: string(),
|
||||
kitId: string(),
|
||||
kitModuleType: string(),
|
||||
state: picklist(bindingStateValues),
|
||||
@@ -76,7 +76,7 @@ export type BindingState = typeof bindingStateValues[number]
|
||||
export interface BindingRecord<C extends HostDataRecord = HostDataRecord> {
|
||||
moduleId: string
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
kitId: string
|
||||
kitModuleType: string
|
||||
state: BindingState
|
||||
|
||||
@@ -13,15 +13,15 @@ import { toast } from 'vue-sonner'
|
||||
|
||||
const store = usePluginHostInspectorStore()
|
||||
const filter = ref('')
|
||||
const selectedPluginName = ref('')
|
||||
const selectedExtensionId = ref('')
|
||||
|
||||
const discoveredPlugins = computed(() => {
|
||||
const query = filter.value.trim().toLowerCase()
|
||||
const plugins = store.discoveredPlugins.slice().sort((left, right) => left.name.localeCompare(right.name))
|
||||
const plugins = store.discoveredPlugins.slice().sort((left, right) => left.extensionId.localeCompare(right.extensionId))
|
||||
if (!query)
|
||||
return plugins
|
||||
return plugins.filter(plugin =>
|
||||
plugin.name.toLowerCase().includes(query)
|
||||
plugin.extensionId.toLowerCase().includes(query)
|
||||
|| plugin.path.toLowerCase().includes(query),
|
||||
)
|
||||
})
|
||||
@@ -34,10 +34,10 @@ const loadedPlugins = computed(() => {
|
||||
return discoveredPlugins.value.filter(plugin => plugin.loaded)
|
||||
})
|
||||
|
||||
const sessionByPluginName = computed(() => {
|
||||
const sessionByExtensionId = computed(() => {
|
||||
const map = new Map<string, PluginHostSessionSummary>()
|
||||
for (const session of store.sessions) {
|
||||
map.set(session.manifestName, session)
|
||||
map.set(session.extensionId, session)
|
||||
}
|
||||
return map
|
||||
})
|
||||
@@ -110,58 +110,58 @@ async function loadEnabled() {
|
||||
async function setAutoReload(plugin: PluginManifestSummary, enabled: boolean) {
|
||||
try {
|
||||
await store.setAutoReload({
|
||||
name: plugin.name,
|
||||
extensionId: plugin.extensionId,
|
||||
enabled,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to update auto-reload state for ${plugin.name}.`)
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to update auto-reload state for ${plugin.extensionId}.`)
|
||||
}
|
||||
}
|
||||
|
||||
async function setEnabled(plugin: PluginManifestSummary, enabled: boolean) {
|
||||
try {
|
||||
await store.setEnabled({
|
||||
name: plugin.name,
|
||||
extensionId: plugin.extensionId,
|
||||
enabled,
|
||||
path: plugin.path,
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to update enabled state for ${plugin.name}.`)
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to update enabled state for ${plugin.extensionId}.`)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlugin(plugin: PluginManifestSummary) {
|
||||
try {
|
||||
await store.load({ name: plugin.name })
|
||||
await store.load({ extensionId: plugin.extensionId })
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to load plugin ${plugin.name}.`)
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to load plugin ${plugin.extensionId}.`)
|
||||
}
|
||||
}
|
||||
|
||||
async function unloadPlugin(plugin: PluginManifestSummary) {
|
||||
try {
|
||||
await store.unload({ name: plugin.name })
|
||||
await store.unload({ extensionId: plugin.extensionId })
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to unload plugin ${plugin.name}.`)
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to unload plugin ${plugin.extensionId}.`)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSelectedPlugin() {
|
||||
const name = selectedPluginName.value.trim()
|
||||
if (!name) {
|
||||
toast.error('Enter a plugin name to load.')
|
||||
const extensionId = selectedExtensionId.value.trim()
|
||||
if (!extensionId) {
|
||||
toast.error('Enter an extension id to load.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await store.load({ name })
|
||||
await store.load({ extensionId })
|
||||
}
|
||||
catch (error) {
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to load plugin ${name}.`)
|
||||
toast.error(errorMessageFrom(error) ?? `Failed to load plugin ${extensionId}.`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,15 +253,15 @@ onMounted(async () => {
|
||||
|
||||
<div :class="['flex', 'flex-wrap', 'items-center', 'gap-2']">
|
||||
<Input
|
||||
v-model="selectedPluginName"
|
||||
placeholder="Load discovered plugin by exact name..."
|
||||
v-model="selectedExtensionId"
|
||||
placeholder="Load discovered extension by exact id..."
|
||||
class="max-w-[520px] min-w-[320px]"
|
||||
/>
|
||||
<Button
|
||||
label="Load Plugin"
|
||||
icon="i-solar:download-minimalistic-bold-duotone"
|
||||
size="sm"
|
||||
:disabled="!selectedPluginName.trim()"
|
||||
:disabled="!selectedExtensionId.trim()"
|
||||
:loading="store.loading"
|
||||
@click="loadSelectedPlugin"
|
||||
/>
|
||||
@@ -288,7 +288,7 @@ onMounted(async () => {
|
||||
<div :class="['flex', 'flex-wrap', 'items-center', 'justify-between', 'gap-2']">
|
||||
<div :class="['flex', 'flex-wrap', 'items-center', 'gap-2']">
|
||||
<div :class="['font-semibold']">
|
||||
{{ plugin.name }}
|
||||
{{ plugin.extensionId }}
|
||||
</div>
|
||||
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(plugin.enabled ? 'emerald' : 'neutral')]">
|
||||
{{ plugin.enabled ? 'enabled' : 'disabled' }}
|
||||
@@ -348,14 +348,14 @@ onMounted(async () => {
|
||||
entrypoints: {{ JSON.stringify(plugin.entrypoints) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="sessionByPluginName.get(plugin.name)"
|
||||
v-if="sessionByExtensionId.get(plugin.extensionId)"
|
||||
:class="['mt-2', 'flex', 'items-center', 'gap-2', 'text-sm']"
|
||||
>
|
||||
<span>phase:</span>
|
||||
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(phaseChipTheme(sessionByPluginName.get(plugin.name)!.phase))]">
|
||||
{{ sessionByPluginName.get(plugin.name)!.phase }}
|
||||
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(phaseChipTheme(sessionByExtensionId.get(plugin.extensionId)!.phase))]">
|
||||
{{ sessionByExtensionId.get(plugin.extensionId)!.phase }}
|
||||
</span>
|
||||
<span :class="['opacity-70', 'font-mono']">{{ sessionByPluginName.get(plugin.name)!.moduleId }}</span>
|
||||
<span :class="['opacity-70', 'font-mono']">{{ sessionByExtensionId.get(plugin.extensionId)!.moduleId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -375,7 +375,7 @@ onMounted(async () => {
|
||||
:key="`enabled-${plugin.path}`"
|
||||
:class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses('emerald')]"
|
||||
>
|
||||
{{ plugin.name }}
|
||||
{{ plugin.extensionId }}
|
||||
</span>
|
||||
</div>
|
||||
</Section>
|
||||
@@ -395,9 +395,9 @@ onMounted(async () => {
|
||||
:class="['rounded-lg', 'bg-neutral-100', 'p-2', 'dark:bg-neutral-900/70']"
|
||||
>
|
||||
<div :class="['flex', 'items-center', 'justify-between', 'gap-2']">
|
||||
<span :class="['font-semibold']">{{ plugin.name }}</span>
|
||||
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(phaseChipTheme(sessionByPluginName.get(plugin.name)?.phase ?? 'unknown'))]">
|
||||
{{ sessionByPluginName.get(plugin.name)?.phase ?? 'unknown' }}
|
||||
<span :class="['font-semibold']">{{ plugin.extensionId }}</span>
|
||||
<span :class="['rounded-full', 'border', 'px-2', 'py-0.5', 'text-xs', ...chipClasses(phaseChipTheme(sessionByExtensionId.get(plugin.extensionId)?.phase ?? 'unknown'))]">
|
||||
{{ sessionByExtensionId.get(plugin.extensionId)?.phase ?? 'unknown' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export interface PluginManifestSummary {
|
||||
name: string
|
||||
extensionId: string
|
||||
entrypoints: Record<string, string | undefined>
|
||||
path: string
|
||||
enabled: boolean
|
||||
@@ -28,7 +28,7 @@ export interface PluginCapabilityState {
|
||||
|
||||
export interface PluginHostSessionSummary {
|
||||
id: string
|
||||
manifestName: string
|
||||
extensionId: string
|
||||
phase: string
|
||||
runtime: 'electron' | 'node' | 'web'
|
||||
moduleId: string
|
||||
@@ -49,7 +49,7 @@ export interface PluginHostKitSummary {
|
||||
export interface PluginHostModuleSummary {
|
||||
moduleId: string
|
||||
ownerSessionId: string
|
||||
ownerPluginId: string
|
||||
ownerExtensionId: string
|
||||
kitId: string
|
||||
kitModuleType: string
|
||||
state: 'announced' | 'active' | 'degraded' | 'withdrawn'
|
||||
@@ -70,11 +70,11 @@ 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>
|
||||
setEnabled: (payload: { extensionId: string, enabled: boolean, path?: string }) => Promise<PluginRegistrySnapshot>
|
||||
setAutoReload: (payload: { extensionId: string, enabled: boolean }) => Promise<PluginRegistrySnapshot>
|
||||
loadEnabled: () => Promise<PluginRegistrySnapshot>
|
||||
load: (payload: { name: string }) => Promise<PluginRegistrySnapshot>
|
||||
unload: (payload: { name: string }) => Promise<PluginRegistrySnapshot>
|
||||
load: (payload: { extensionId: string }) => Promise<PluginRegistrySnapshot>
|
||||
unload: (payload: { extensionId: string }) => Promise<PluginRegistrySnapshot>
|
||||
inspect: () => Promise<PluginHostDebugSnapshot>
|
||||
}
|
||||
|
||||
@@ -171,14 +171,14 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
|
||||
return refreshInspection()
|
||||
}
|
||||
|
||||
async function setEnabled(payload: { name: string, enabled: boolean, path?: string }) {
|
||||
async function setEnabled(payload: { extensionId: string, enabled: boolean, path?: string }) {
|
||||
const nextRegistry = await withBridge(activeBridge => activeBridge.setEnabled(payload))
|
||||
assignRegistry(nextRegistry)
|
||||
await refreshInspection()
|
||||
return nextRegistry
|
||||
}
|
||||
|
||||
async function setAutoReload(payload: { name: string, enabled: boolean }) {
|
||||
async function setAutoReload(payload: { extensionId: string, enabled: boolean }) {
|
||||
const nextRegistry = await withBridge(activeBridge => activeBridge.setAutoReload(payload))
|
||||
assignRegistry(nextRegistry)
|
||||
await refreshInspection()
|
||||
@@ -192,14 +192,14 @@ export const usePluginHostInspectorStore = defineStore('devtools:plugin-host-deb
|
||||
return nextRegistry
|
||||
}
|
||||
|
||||
async function load(payload: { name: string }) {
|
||||
async function load(payload: { extensionId: string }) {
|
||||
const nextRegistry = await withBridge(activeBridge => activeBridge.load(payload))
|
||||
assignRegistry(nextRegistry)
|
||||
await refreshInspection()
|
||||
return nextRegistry
|
||||
}
|
||||
|
||||
async function unload(payload: { name: string }) {
|
||||
async function unload(payload: { extensionId: string }) {
|
||||
const nextRegistry = await withBridge(activeBridge => activeBridge.unload(payload))
|
||||
assignRegistry(nextRegistry)
|
||||
await refreshInspection()
|
||||
|
||||
Reference in New Issue
Block a user