mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
Adds Orca's experimental plugin system behind a settings flag: a supervised kernel, declarative content packs (VM recipes, commands and keybindings, language packs), sandboxed iframe panels, forked worker hosts, and a Git-backed marketplace v0 with consent, provenance and kill-list enforcement. Theme, icon-theme and terminal-theme contributions are deferred to a follow-up pass.
233 lines
8.3 KiB
TypeScript
233 lines
8.3 KiB
TypeScript
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import { PLUGIN_WORKSPACE_TERMINAL_LIMIT } from '../../shared/plugins/plugin-host-api'
|
|
import { bindPluginHostServices, type PluginRuntimeDelegate } from './plugin-host-service-bindings'
|
|
import { executePluginHostCall, type PluginHostServices } from './plugin-host-methods'
|
|
|
|
function createServices(storageSet: PluginHostServices['storage']['set']): PluginHostServices {
|
|
return {
|
|
resolveActiveWorktreeContext: vi.fn().mockResolvedValue(null),
|
|
listWorktreeTerminals: vi.fn().mockResolvedValue([]),
|
|
sendTerminalText: vi.fn().mockResolvedValue({ accepted: true }),
|
|
dispatchPluginNotification: vi.fn().mockResolvedValue({ delivered: true }),
|
|
storage: {
|
|
get: vi.fn(),
|
|
set: storageSet,
|
|
delete: vi.fn(),
|
|
keys: vi.fn().mockReturnValue([])
|
|
},
|
|
secrets: {
|
|
get: vi.fn().mockReturnValue({ ok: true, value: null }),
|
|
set: vi.fn().mockReturnValue({ ok: true }),
|
|
delete: vi.fn()
|
|
},
|
|
settings: {
|
|
getAll: vi.fn().mockReturnValue({}),
|
|
set: vi.fn().mockReturnValue({ ok: true })
|
|
},
|
|
subscribeEvents: vi.fn().mockReturnValue([])
|
|
}
|
|
}
|
|
|
|
describe('executePluginHostCall mutation auditing', () => {
|
|
it('rejects prototype-sensitive storage keys before any host service call', async () => {
|
|
const storageSet = vi.fn().mockReturnValue({ ok: true })
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'storage.set',
|
|
params: { key: '__proto__', value: 42 },
|
|
viaPanel: false,
|
|
grantedCapabilities: ['storage'],
|
|
services: createServices(storageSet),
|
|
audit: { record: vi.fn().mockResolvedValue(undefined) }
|
|
})
|
|
|
|
expect(outcome).toMatchObject({ ok: false, code: 'invalid_params' })
|
|
expect(storageSet).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('rejects non-JSON storage values before any host service call', async () => {
|
|
const storageSet = vi.fn().mockReturnValue({ ok: true })
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'storage.set',
|
|
params: { key: 'created', value: new Date() },
|
|
viaPanel: false,
|
|
grantedCapabilities: ['storage'],
|
|
services: createServices(storageSet),
|
|
audit: { record: vi.fn().mockResolvedValue(undefined) }
|
|
})
|
|
|
|
expect(outcome).toMatchObject({ ok: false, code: 'invalid_params' })
|
|
expect(storageSet).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('fails closed before a mutation when the audit intent cannot be recorded', async () => {
|
|
const storageSet = vi.fn().mockReturnValue({ ok: true })
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'storage.set',
|
|
params: { key: 'answer', value: 42 },
|
|
viaPanel: false,
|
|
grantedCapabilities: ['storage'],
|
|
services: createServices(storageSet),
|
|
audit: { record: vi.fn().mockRejectedValue(new Error('disk full')) }
|
|
})
|
|
|
|
expect(outcome).toMatchObject({ ok: false, code: 'action_failed' })
|
|
expect(storageSet).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('records an intent before the mutation and its outcome afterward', async () => {
|
|
const order: string[] = []
|
|
const storageSet = vi.fn(() => {
|
|
order.push('mutation')
|
|
return { ok: true as const }
|
|
})
|
|
const record = vi.fn(async (entry: { outcome: string }) => {
|
|
order.push(`audit:${entry.outcome}`)
|
|
})
|
|
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'storage.set',
|
|
params: { key: 'answer', value: 42 },
|
|
viaPanel: false,
|
|
grantedCapabilities: ['storage'],
|
|
services: createServices(storageSet),
|
|
audit: { record }
|
|
})
|
|
|
|
expect(outcome).toEqual({ ok: true, value: { ok: true } })
|
|
expect(order).toEqual(['audit:attempt', 'mutation', 'audit:ok'])
|
|
})
|
|
|
|
it('refuses mutations when no audit writer is configured', async () => {
|
|
const storageSet = vi.fn().mockReturnValue({ ok: true })
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'storage.set',
|
|
params: { key: 'answer', value: 42 },
|
|
viaPanel: false,
|
|
grantedCapabilities: ['storage'],
|
|
services: createServices(storageSet)
|
|
})
|
|
|
|
expect(outcome).toMatchObject({ ok: false, code: 'unavailable' })
|
|
expect(storageSet).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
function createTerminalHarness(terminalHandles: string[]): {
|
|
delegate: PluginRuntimeDelegate
|
|
services: PluginHostServices
|
|
} {
|
|
const delegate: PluginRuntimeDelegate = {
|
|
resolveActiveWorktreeContext: vi.fn().mockResolvedValue({
|
|
worktreeId: 'worktree-1',
|
|
path: '/Users/private/repo',
|
|
branch: 'main',
|
|
displayName: 'Repo'
|
|
}),
|
|
listTerminals: vi.fn().mockResolvedValue({
|
|
terminals: terminalHandles.map((handle) => ({ handle, title: null }))
|
|
}),
|
|
sendTerminal: vi.fn().mockResolvedValue({ accepted: true }),
|
|
dispatchPluginNotification: vi.fn().mockResolvedValue({ delivered: true })
|
|
}
|
|
return {
|
|
delegate,
|
|
services: bindPluginHostServices({
|
|
delegate,
|
|
pluginsDataDir: join(tmpdir(), 'plugin-host-methods-test'),
|
|
subscribeEvents: vi.fn().mockReturnValue([])
|
|
})
|
|
}
|
|
}
|
|
|
|
async function sendTerminalText(
|
|
services: PluginHostServices,
|
|
terminalId: string
|
|
): ReturnType<typeof executePluginHostCall> {
|
|
return executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'terminal.sendText',
|
|
params: { terminalId, text: 'echo hi', enter: true },
|
|
viaPanel: true,
|
|
grantedCapabilities: ['terminal:send'],
|
|
services,
|
|
audit: { record: vi.fn().mockResolvedValue(undefined) }
|
|
})
|
|
}
|
|
|
|
describe('terminal.sendText explicit worktree routing', () => {
|
|
it('performs one bounded list and zero sends when the terminal is outside the worktree', async () => {
|
|
const { delegate, services } = createTerminalHarness(['terminal:local:other'])
|
|
|
|
const outcome = await sendTerminalText(services, 'terminal:ssh:requested')
|
|
|
|
expect(outcome).toMatchObject({ ok: false, code: 'action_failed' })
|
|
expect(delegate.resolveActiveWorktreeContext).toHaveBeenCalledTimes(1)
|
|
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
|
|
expect(delegate.listTerminals).toHaveBeenCalledWith(
|
|
'id:worktree-1',
|
|
PLUGIN_WORKSPACE_TERMINAL_LIMIT
|
|
)
|
|
expect(delegate.sendTerminal).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it.each(['terminal:local:one', 'terminal:ssh:opaque-provider-id'])(
|
|
'performs one bounded list and one send for provider-agnostic id %s',
|
|
async (terminalId) => {
|
|
const { delegate, services } = createTerminalHarness([terminalId])
|
|
|
|
const outcome = await sendTerminalText(services, terminalId)
|
|
|
|
expect(outcome).toEqual({ ok: true, value: { accepted: true } })
|
|
expect(delegate.resolveActiveWorktreeContext).toHaveBeenCalledTimes(1)
|
|
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
|
|
expect(delegate.listTerminals).toHaveBeenCalledWith(
|
|
'id:worktree-1',
|
|
PLUGIN_WORKSPACE_TERMINAL_LIMIT
|
|
)
|
|
expect(delegate.sendTerminal).toHaveBeenCalledTimes(1)
|
|
expect(delegate.sendTerminal).toHaveBeenCalledWith(terminalId, {
|
|
text: 'echo hi',
|
|
enter: true
|
|
})
|
|
expect(vi.mocked(delegate.listTerminals).mock.invocationCallOrder[0]!).toBeLessThan(
|
|
vi.mocked(delegate.sendTerminal).mock.invocationCallOrder[0]!
|
|
)
|
|
}
|
|
)
|
|
|
|
it('bounds workspace.readContext and omits the provider path', async () => {
|
|
const handles = Array.from(
|
|
{ length: PLUGIN_WORKSPACE_TERMINAL_LIMIT + 10 },
|
|
(_, index) => `terminal:local:${index}`
|
|
)
|
|
const { delegate, services } = createTerminalHarness(handles)
|
|
|
|
const outcome = await executePluginHostCall({
|
|
pluginId: 'orca-samples.demo',
|
|
method: 'workspace.readContext',
|
|
params: {},
|
|
viaPanel: true,
|
|
grantedCapabilities: ['workspace:read'],
|
|
services
|
|
})
|
|
|
|
expect(outcome).toMatchObject({
|
|
ok: true,
|
|
value: { branch: 'main', displayName: 'Repo' }
|
|
})
|
|
expect(outcome).not.toHaveProperty('value.path')
|
|
expect(outcome).not.toHaveProperty('value.worktreeId')
|
|
expect(outcome.ok && (outcome.value as { terminals: unknown[] }).terminals).toHaveLength(
|
|
PLUGIN_WORKSPACE_TERMINAL_LIMIT
|
|
)
|
|
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|