mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
feat(plugins): expose marketplace lifecycle
This commit is contained in:
+20
-1
@@ -162,6 +162,8 @@ import { registerSystemResumeBroadcast } from './system-resume-broadcast'
|
||||
import { PluginService } from './plugins/plugin-service'
|
||||
import { PluginKillListService } from './plugins/plugin-kill-list-service'
|
||||
import { getPluginsDataDir } from './plugins/plugin-discovery'
|
||||
import { PluginMarketplaceService } from './plugins/plugin-marketplace-service'
|
||||
import { PluginMarketplaceInstaller } from './plugins/plugin-marketplace-installer'
|
||||
import { resolvePluginHostEntryPath } from './plugins/plugin-host-process'
|
||||
import { applyPluginConsent, applyPluginEnablement } from './plugins/plugin-enablement'
|
||||
import { setPluginServiceForRpc } from './runtime/rpc/methods/plugins'
|
||||
@@ -232,6 +234,8 @@ let watcherShutdownDone = false
|
||||
let automations: AutomationService | null = null
|
||||
let pluginService: PluginService | null = null
|
||||
let pluginKillListService: PluginKillListService | null = null
|
||||
let pluginMarketplaceService: PluginMarketplaceService | null = null
|
||||
let pluginMarketplaceInstaller: PluginMarketplaceInstaller | null = null
|
||||
let keybindings: KeybindingService | null = null
|
||||
|
||||
function emitPluginWorktreeLifecycle(event: RuntimeWorktreeLifecycleEvent): void {
|
||||
@@ -975,7 +979,10 @@ function openMainWindow(): BrowserWindow {
|
||||
await preserveAgentAuthBeforeRestart({ codexRuntimeHome, claudeRuntimeAuth, store })
|
||||
}
|
||||
},
|
||||
pluginService ?? undefined
|
||||
pluginService ?? undefined,
|
||||
pluginMarketplaceService && pluginMarketplaceInstaller
|
||||
? { marketplace: pluginMarketplaceService, installer: pluginMarketplaceInstaller }
|
||||
: undefined
|
||||
)
|
||||
automations.setWebContents(window.webContents)
|
||||
automations.start()
|
||||
@@ -1937,6 +1944,16 @@ app.whenReady().then(async () => {
|
||||
pluginsDataDir: getPluginsDataDir(app.getPath('userData'))
|
||||
})
|
||||
await pluginKillListService.initialize()
|
||||
pluginMarketplaceService = new PluginMarketplaceService({
|
||||
pluginsDataDir: getPluginsDataDir(app.getPath('userData')),
|
||||
getKillListEntry: (pluginKey) => pluginKillListService?.find(pluginKey) ?? null
|
||||
})
|
||||
pluginMarketplaceInstaller = new PluginMarketplaceInstaller({
|
||||
marketplace: pluginMarketplaceService,
|
||||
userDataPath: app.getPath('userData'),
|
||||
hostVersion: app.getVersion(),
|
||||
blockedPluginReason: (pluginKey) => pluginKillListService?.reason(pluginKey) ?? null
|
||||
})
|
||||
pluginService = new PluginService({
|
||||
userDataPath: app.getPath('userData'),
|
||||
hostVersion: app.getVersion(),
|
||||
@@ -2366,6 +2383,8 @@ app.on('will-quit', (e) => {
|
||||
// Electron exit first and orphan the hosts.
|
||||
setPluginServiceForRpc(null)
|
||||
pluginKillListService = null
|
||||
pluginMarketplaceService = null
|
||||
pluginMarketplaceInstaller = null
|
||||
const pluginHostShutdown = pluginService?.dispose() ?? Promise.resolve()
|
||||
pluginService = null
|
||||
setUnreadDockBadgeCount(0)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { PluginMarketplaceInstaller } from '../plugins/plugin-marketplace-installer'
|
||||
import type { PluginMarketplaceService } from '../plugins/plugin-marketplace-service'
|
||||
import type { PluginService } from '../plugins/plugin-service'
|
||||
|
||||
type IpcHandler = (event: unknown, args?: unknown) => unknown
|
||||
|
||||
const electronMocks = vi.hoisted(() => ({ handle: vi.fn() }))
|
||||
vi.mock('electron', () => ({ ipcMain: { handle: electronMocks.handle } }))
|
||||
|
||||
import {
|
||||
registerPluginMarketplaceHandlers,
|
||||
type PluginMarketplaceHandlerServices
|
||||
} from './plugin-marketplaces'
|
||||
|
||||
const SOURCE_ID = 'a'.repeat(32)
|
||||
const MARKETPLACE_COMMIT = 'b'.repeat(40)
|
||||
const PLUGIN_COMMIT = 'c'.repeat(40)
|
||||
const PLUGIN_KEY = 'orca-samples.demo'
|
||||
|
||||
let handlers: Map<string, IpcHandler>
|
||||
|
||||
function createServices(): PluginMarketplaceHandlerServices {
|
||||
return {
|
||||
marketplace: {
|
||||
listSources: vi.fn().mockResolvedValue([{ id: SOURCE_ID }]),
|
||||
addSource: vi.fn().mockResolvedValue({ id: SOURCE_ID }),
|
||||
removeSource: vi.fn().mockResolvedValue(true),
|
||||
refreshSource: vi.fn().mockResolvedValue({ id: SOURCE_ID }),
|
||||
refreshAll: vi.fn().mockResolvedValue([{ id: SOURCE_ID }]),
|
||||
listPlugins: vi.fn().mockResolvedValue([{ pluginKey: PLUGIN_KEY }])
|
||||
} as unknown as PluginMarketplaceService,
|
||||
installer: {
|
||||
preview: vi.fn().mockResolvedValue({ pluginKey: PLUGIN_KEY }),
|
||||
install: vi.fn().mockResolvedValue({ ok: true, pluginKey: PLUGIN_KEY }),
|
||||
previewInstalledUpdate: vi.fn().mockResolvedValue({ pluginKey: PLUGIN_KEY }),
|
||||
rollback: vi.fn().mockResolvedValue({ ok: true, pluginKey: PLUGIN_KEY })
|
||||
} as unknown as PluginMarketplaceInstaller
|
||||
}
|
||||
}
|
||||
|
||||
function createPluginService(): PluginService {
|
||||
return {
|
||||
deactivatePlugin: vi.fn().mockResolvedValue(undefined),
|
||||
refresh: vi.fn().mockResolvedValue(undefined)
|
||||
} as unknown as PluginService
|
||||
}
|
||||
|
||||
async function invoke(channel: string, args?: unknown): Promise<unknown> {
|
||||
const handler = handlers.get(channel)
|
||||
if (!handler) {
|
||||
throw new Error(`missing IPC handler: ${channel}`)
|
||||
}
|
||||
return handler({}, args)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
handlers = new Map()
|
||||
electronMocks.handle.mockReset()
|
||||
electronMocks.handle.mockImplementation((channel: string, handler: IpcHandler) => {
|
||||
handlers.set(channel, handler)
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin marketplace IPC authority', () => {
|
||||
it('validates every mutating or plugin-selecting request strictly', async () => {
|
||||
registerPluginMarketplaceHandlers(createPluginService(), createServices())
|
||||
|
||||
await expect(
|
||||
invoke('plugins:addMarketplace', {
|
||||
kind: 'git',
|
||||
url: 'https://example.com/marketplace.git',
|
||||
ref: 'main',
|
||||
unexpected: true
|
||||
})
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:removeMarketplace', { sourceId: SOURCE_ID, unexpected: true })
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:refreshMarketplaces', { sourceId: 'not-a-source' })
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:previewMarketplacePlugin', {
|
||||
marketplaceSourceId: SOURCE_ID,
|
||||
pluginKey: '__proto__.demo'
|
||||
})
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:installMarketplacePlugin', {
|
||||
marketplaceSourceId: SOURCE_ID,
|
||||
marketplaceCommit: 'moving-ref',
|
||||
pluginKey: PLUGIN_KEY,
|
||||
resolvedCommit: PLUGIN_COMMIT
|
||||
})
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:previewMarketplaceUpdate', {
|
||||
pluginKey: PLUGIN_KEY,
|
||||
unexpected: true
|
||||
})
|
||||
).rejects.toThrow()
|
||||
await expect(
|
||||
invoke('plugins:rollbackMarketplacePlugin', { pluginKey: 'bare-id' })
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('dispatches source listing, add, removal, and refresh operations', async () => {
|
||||
const services = createServices()
|
||||
registerPluginMarketplaceHandlers(createPluginService(), services)
|
||||
const source = {
|
||||
kind: 'git' as const,
|
||||
url: 'https://example.com/marketplace.git',
|
||||
ref: 'main'
|
||||
}
|
||||
|
||||
await invoke('plugins:listMarketplaces')
|
||||
await invoke('plugins:addMarketplace', source)
|
||||
await invoke('plugins:removeMarketplace', { sourceId: SOURCE_ID })
|
||||
await invoke('plugins:refreshMarketplaces', { sourceId: SOURCE_ID })
|
||||
await invoke('plugins:refreshMarketplaces', {})
|
||||
await invoke('plugins:listMarketplacePlugins')
|
||||
|
||||
expect(services.marketplace.listSources).toHaveBeenCalledTimes(2)
|
||||
expect(services.marketplace.addSource).toHaveBeenCalledWith(source)
|
||||
expect(services.marketplace.removeSource).toHaveBeenCalledWith(SOURCE_ID)
|
||||
expect(services.marketplace.refreshSource).toHaveBeenCalledWith(SOURCE_ID)
|
||||
expect(services.marketplace.refreshAll).toHaveBeenCalledOnce()
|
||||
expect(services.marketplace.listPlugins).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('refreshes discovery only after a successful install', async () => {
|
||||
const services = createServices()
|
||||
const pluginService = createPluginService()
|
||||
registerPluginMarketplaceHandlers(pluginService, services)
|
||||
const preview = {
|
||||
marketplaceSourceId: SOURCE_ID,
|
||||
marketplaceCommit: MARKETPLACE_COMMIT,
|
||||
pluginKey: PLUGIN_KEY,
|
||||
resolvedCommit: PLUGIN_COMMIT
|
||||
}
|
||||
|
||||
await invoke('plugins:installMarketplacePlugin', preview)
|
||||
expect(services.installer.install).toHaveBeenCalledWith(preview)
|
||||
expect(pluginService.refresh).toHaveBeenCalledOnce()
|
||||
|
||||
vi.mocked(pluginService.refresh).mockClear()
|
||||
vi.mocked(services.installer.install).mockResolvedValueOnce({ ok: false, error: 'failed' })
|
||||
await invoke('plugins:installMarketplacePlugin', preview)
|
||||
expect(pluginService.refresh).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('deactivates before rollback and refreshes discovery only on success', async () => {
|
||||
const services = createServices()
|
||||
const pluginService = createPluginService()
|
||||
registerPluginMarketplaceHandlers(pluginService, services)
|
||||
|
||||
await invoke('plugins:rollbackMarketplacePlugin', { pluginKey: PLUGIN_KEY })
|
||||
|
||||
expect(pluginService.deactivatePlugin).toHaveBeenCalledWith(PLUGIN_KEY)
|
||||
expect(services.installer.rollback).toHaveBeenCalledWith(PLUGIN_KEY)
|
||||
expect(vi.mocked(pluginService.deactivatePlugin).mock.invocationCallOrder[0]).toBeLessThan(
|
||||
vi.mocked(services.installer.rollback).mock.invocationCallOrder[0]
|
||||
)
|
||||
expect(pluginService.refresh).toHaveBeenCalledOnce()
|
||||
|
||||
vi.mocked(pluginService.refresh).mockClear()
|
||||
vi.mocked(services.installer.rollback).mockResolvedValueOnce({ ok: false, error: 'failed' })
|
||||
await invoke('plugins:rollbackMarketplacePlugin', { pluginKey: PLUGIN_KEY })
|
||||
expect(pluginService.refresh).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { z } from 'zod'
|
||||
import { isQualifiedPluginKey } from '../../shared/plugins/plugin-manifest'
|
||||
import { pluginMarketplaceGitSourceSchema } from '../../shared/plugins/plugin-marketplace'
|
||||
import type { PluginMarketplaceInstaller } from '../plugins/plugin-marketplace-installer'
|
||||
import type { PluginMarketplaceService } from '../plugins/plugin-marketplace-service'
|
||||
import type { PluginService } from '../plugins/plugin-service'
|
||||
|
||||
export type PluginMarketplaceHandlerServices = {
|
||||
marketplace: PluginMarketplaceService
|
||||
installer: PluginMarketplaceInstaller
|
||||
}
|
||||
|
||||
const sourceIdSchema = z.string().regex(/^[0-9a-f]{32}$/)
|
||||
const removeMarketplaceSchema = z.strictObject({ sourceId: sourceIdSchema })
|
||||
const refreshMarketplaceSchema = z.strictObject({ sourceId: sourceIdSchema.optional() })
|
||||
const marketplacePluginSchema = z.strictObject({
|
||||
marketplaceSourceId: sourceIdSchema,
|
||||
pluginKey: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key')
|
||||
})
|
||||
const installMarketplacePluginSchema = marketplacePluginSchema.extend({
|
||||
marketplaceCommit: z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/),
|
||||
resolvedCommit: z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/)
|
||||
})
|
||||
const installedPluginSchema = z.strictObject({
|
||||
pluginKey: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key')
|
||||
})
|
||||
|
||||
export function registerPluginMarketplaceHandlers(
|
||||
pluginService: PluginService,
|
||||
services: PluginMarketplaceHandlerServices
|
||||
): void {
|
||||
ipcMain.handle('plugins:listMarketplaces', () => services.marketplace.listSources())
|
||||
ipcMain.handle('plugins:addMarketplace', async (_event, args: unknown) => {
|
||||
const source = pluginMarketplaceGitSourceSchema.parse(args)
|
||||
return services.marketplace.addSource(source)
|
||||
})
|
||||
ipcMain.handle('plugins:removeMarketplace', async (_event, args: unknown) => {
|
||||
const { sourceId } = removeMarketplaceSchema.parse(args)
|
||||
await services.marketplace.removeSource(sourceId)
|
||||
return services.marketplace.listSources()
|
||||
})
|
||||
ipcMain.handle('plugins:refreshMarketplaces', async (_event, args: unknown) => {
|
||||
const { sourceId } = refreshMarketplaceSchema.parse(args ?? {})
|
||||
return sourceId
|
||||
? [await services.marketplace.refreshSource(sourceId)]
|
||||
: services.marketplace.refreshAll()
|
||||
})
|
||||
ipcMain.handle('plugins:listMarketplacePlugins', () => services.marketplace.listPlugins())
|
||||
ipcMain.handle('plugins:previewMarketplacePlugin', async (_event, args: unknown) => {
|
||||
const parsed = marketplacePluginSchema.parse(args)
|
||||
return services.installer.preview(parsed.marketplaceSourceId, parsed.pluginKey)
|
||||
})
|
||||
ipcMain.handle('plugins:installMarketplacePlugin', async (_event, args: unknown) => {
|
||||
const result = await services.installer.install(installMarketplacePluginSchema.parse(args))
|
||||
if (result.ok) {
|
||||
await pluginService.refresh()
|
||||
}
|
||||
return result
|
||||
})
|
||||
ipcMain.handle('plugins:previewMarketplaceUpdate', async (_event, args: unknown) => {
|
||||
const { pluginKey } = installedPluginSchema.parse(args)
|
||||
return services.installer.previewInstalledUpdate(pluginKey)
|
||||
})
|
||||
ipcMain.handle('plugins:rollbackMarketplacePlugin', async (_event, args: unknown) => {
|
||||
const { pluginKey } = installedPluginSchema.parse(args)
|
||||
await pluginService.deactivatePlugin(pluginKey)
|
||||
const result = await services.installer.rollback(pluginKey)
|
||||
if (result.ok) {
|
||||
await pluginService.refresh()
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
@@ -23,6 +23,10 @@ import { normalizePluginIdList } from '../../shared/plugins/plugin-consent-state
|
||||
import { isAllowedPluginGitUrl } from '../../shared/plugins/plugin-install-lockfile'
|
||||
import type { PluginSkillStoreSnapshot } from '../../shared/plugins/plugin-skill-store'
|
||||
import { authorizePluginSkillMapping } from '../plugins/plugin-skill-mapping-authority'
|
||||
import {
|
||||
registerPluginMarketplaceHandlers,
|
||||
type PluginMarketplaceHandlerServices
|
||||
} from './plugin-marketplaces'
|
||||
|
||||
export function parsePluginConsentArgs(args: unknown): z.infer<typeof pluginConsentRequestSchema> {
|
||||
return pluginConsentRequestSchema.parse(args)
|
||||
@@ -93,7 +97,8 @@ function rendererPanelOwner(webContentsId: number): string {
|
||||
export function registerPluginHandlers(
|
||||
store: Store,
|
||||
pluginService: PluginService,
|
||||
runtime: OrcaRuntimeService | null
|
||||
runtime: OrcaRuntimeService | null,
|
||||
marketplaceServices?: PluginMarketplaceHandlerServices
|
||||
): void {
|
||||
// The runtime IS the delegate: the structural PluginRuntimeDelegate type
|
||||
// keeps the facade electron-free while main binds the real service.
|
||||
@@ -281,4 +286,7 @@ export function registerPluginHandlers(
|
||||
await pluginService.refresh()
|
||||
return listPluginsForClients(pluginService)
|
||||
})
|
||||
if (marketplaceServices) {
|
||||
registerPluginMarketplaceHandlers(pluginService, marketplaceServices)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ import {
|
||||
scanRuntimeAiVaultSessions
|
||||
} from '../ai-vault/runtime-session-scanner'
|
||||
import type { PluginService } from '../plugins/plugin-service'
|
||||
import type { PluginMarketplaceHandlerServices } from './plugin-marketplaces'
|
||||
|
||||
let registered = false
|
||||
|
||||
@@ -103,7 +104,8 @@ export function registerCoreHandlers(
|
||||
crashReports?: CrashReportStore,
|
||||
keybindings?: KeybindingService,
|
||||
lifecycleOptions: CoreHandlerLifecycleOptions = {},
|
||||
pluginService?: PluginService
|
||||
pluginService?: PluginService,
|
||||
marketplaceServices?: PluginMarketplaceHandlerServices
|
||||
): void {
|
||||
// Why: on macOS the app can stay alive after all windows close, then
|
||||
// openMainWindow() is called again on 'activate'. ipcMain.handle() throws
|
||||
@@ -164,7 +166,7 @@ export function registerCoreHandlers(
|
||||
})
|
||||
}
|
||||
if (pluginService) {
|
||||
registerPluginHandlers(store, pluginService, runtime)
|
||||
registerPluginHandlers(store, pluginService, runtime, marketplaceServices)
|
||||
}
|
||||
registerTelemetryHandlers(store)
|
||||
registerOrcaProfileHandlers(store, {
|
||||
|
||||
@@ -66,6 +66,8 @@ import type {
|
||||
} from '../shared/plugins/plugin-icon-theme-artifact'
|
||||
import type { PluginTerminalThemeRegistration } from '../shared/plugins/plugin-terminal-theme-artifact'
|
||||
import type { PluginChangeEvent } from '../shared/plugins/plugin-change-event'
|
||||
import type { PluginManifest } from '../shared/plugins/plugin-manifest'
|
||||
import type { PluginMarketplaceGitSource } from '../shared/plugins/plugin-marketplace'
|
||||
import type {
|
||||
LocalhostWorktreeLabelResult,
|
||||
LocalhostWorktreeLabelRoute
|
||||
@@ -997,6 +999,50 @@ export type PluginHostInstallResult =
|
||||
}
|
||||
| { ok: false; error: string }
|
||||
|
||||
export type PluginMarketplaceHostSourceState = {
|
||||
id: string
|
||||
source: PluginMarketplaceGitSource
|
||||
addedAt: number
|
||||
marketplace: {
|
||||
name: string
|
||||
owner: string
|
||||
resolvedCommit: string
|
||||
fetchedAt: number
|
||||
} | null
|
||||
stale: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type PluginMarketplaceHostListing = {
|
||||
marketplaceSourceId: string
|
||||
marketplaceName: string
|
||||
marketplaceOwner: string
|
||||
marketplaceCommit: string
|
||||
pluginKey: string
|
||||
source: PluginMarketplaceGitSource
|
||||
description?: string
|
||||
categories: string[]
|
||||
official: boolean
|
||||
bundled: boolean
|
||||
blockedByKillList?: { reason: string; advisoryUrl?: string }
|
||||
}
|
||||
|
||||
export type PluginMarketplaceHostInstallPreview = {
|
||||
marketplaceSourceId: string
|
||||
marketplaceName: string
|
||||
marketplaceOwner: string
|
||||
marketplaceCommit: string
|
||||
pluginKey: string
|
||||
source: PluginMarketplaceGitSource
|
||||
resolvedCommit: string
|
||||
contentHash: string
|
||||
consentFingerprint: string
|
||||
manifest: PluginManifest
|
||||
official: boolean
|
||||
bundled: boolean
|
||||
blockedByKillList?: { reason: string; advisoryUrl?: string }
|
||||
}
|
||||
|
||||
export type PreloadApi = {
|
||||
app: AppApi
|
||||
orcaProfiles: {
|
||||
@@ -3234,6 +3280,29 @@ export type PreloadApi = {
|
||||
params?: unknown
|
||||
}) => Promise<PluginPanelActionOutcome>
|
||||
install: (source: PluginHostInstallSource) => Promise<PluginHostInstallResult>
|
||||
listMarketplaces: () => Promise<PluginMarketplaceHostSourceState[]>
|
||||
addMarketplace: (
|
||||
source: PluginMarketplaceGitSource
|
||||
) => Promise<PluginMarketplaceHostSourceState>
|
||||
removeMarketplace: (args: { sourceId: string }) => Promise<PluginMarketplaceHostSourceState[]>
|
||||
refreshMarketplaces: (args?: {
|
||||
sourceId?: string
|
||||
}) => Promise<PluginMarketplaceHostSourceState[]>
|
||||
listMarketplacePlugins: () => Promise<PluginMarketplaceHostListing[]>
|
||||
previewMarketplacePlugin: (args: {
|
||||
marketplaceSourceId: string
|
||||
pluginKey: string
|
||||
}) => Promise<PluginMarketplaceHostInstallPreview>
|
||||
installMarketplacePlugin: (
|
||||
preview: Pick<
|
||||
PluginMarketplaceHostInstallPreview,
|
||||
'marketplaceSourceId' | 'marketplaceCommit' | 'pluginKey' | 'resolvedCommit'
|
||||
>
|
||||
) => Promise<PluginHostInstallResult>
|
||||
previewMarketplaceUpdate: (args: {
|
||||
pluginKey: string
|
||||
}) => Promise<PluginMarketplaceHostInstallPreview>
|
||||
rollbackMarketplacePlugin: (args: { pluginKey: string }) => Promise<PluginHostInstallResult>
|
||||
remove: (args: { pluginKey: string }) => Promise<PluginHostListEntry[]>
|
||||
getLogs: (args: { pluginKey: string }) => Promise<PluginHostLogLine[]>
|
||||
/** Re-discovers after settings edits (feature flag, dev paths). */
|
||||
|
||||
@@ -587,6 +587,19 @@ const api = {
|
||||
}): Promise<PluginPanelActionOutcome> => ipcRenderer.invoke('plugins:panelAction', args),
|
||||
install: (source: PluginHostInstallSource): Promise<PluginHostInstallResult> =>
|
||||
ipcRenderer.invoke('plugins:install', source),
|
||||
listMarketplaces: () => ipcRenderer.invoke('plugins:listMarketplaces'),
|
||||
addMarketplace: (source) => ipcRenderer.invoke('plugins:addMarketplace', source),
|
||||
removeMarketplace: (args) => ipcRenderer.invoke('plugins:removeMarketplace', args),
|
||||
refreshMarketplaces: (args = {}) => ipcRenderer.invoke('plugins:refreshMarketplaces', args),
|
||||
listMarketplacePlugins: () => ipcRenderer.invoke('plugins:listMarketplacePlugins'),
|
||||
previewMarketplacePlugin: (args) =>
|
||||
ipcRenderer.invoke('plugins:previewMarketplacePlugin', args),
|
||||
installMarketplacePlugin: (preview) =>
|
||||
ipcRenderer.invoke('plugins:installMarketplacePlugin', preview),
|
||||
previewMarketplaceUpdate: (args) =>
|
||||
ipcRenderer.invoke('plugins:previewMarketplaceUpdate', args),
|
||||
rollbackMarketplacePlugin: (args) =>
|
||||
ipcRenderer.invoke('plugins:rollbackMarketplacePlugin', args),
|
||||
remove: (args: { pluginKey: string }): Promise<PluginHostListEntry[]> =>
|
||||
ipcRenderer.invoke('plugins:remove', args),
|
||||
getLogs: (args: { pluginKey: string }): Promise<PluginHostLogLine[]> =>
|
||||
|
||||
Reference in New Issue
Block a user