mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.
typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.
oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.
Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom
electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
211 lines
7.3 KiB
TypeScript
211 lines
7.3 KiB
TypeScript
import { join } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import {
|
|
pluginWorkerParentMessageSchema,
|
|
type PluginWorkerChildMessage
|
|
} from '../../shared/plugins/plugin-host-protocol'
|
|
import type { PluginEventName } from '../../shared/plugins/plugin-manifest'
|
|
|
|
/**
|
|
* Message-loop core of the out-of-process plugin worker. Electron-free and
|
|
* side-effect-free (send/import/exit are injected) so it unit-tests without
|
|
* forking a real child process; `plugin-host-entry.ts` wires it to the fork
|
|
* IPC channel.
|
|
*/
|
|
|
|
export type PluginHostCallError = Error & { code?: string }
|
|
|
|
/** API surface handed to a plugin's `activate(orca)` export. Everything is
|
|
* EXPERIMENTAL until pluginApi v1 freezes. */
|
|
export type PluginWorkerOrcaApi = {
|
|
/** Register the handler for a command declared in the manifest. */
|
|
commands: {
|
|
register(commandId: string, handler: (args: unknown) => unknown): void
|
|
}
|
|
/** Handle an event the manifest subscribed to (`contributes.events`). */
|
|
events: {
|
|
on(event: PluginEventName, handler: (payload: unknown) => void | Promise<void>): void
|
|
}
|
|
/** Call a host API method (capability-gated host-side). */
|
|
host: {
|
|
call(method: string, params?: unknown): Promise<unknown>
|
|
}
|
|
/** Consented capability kinds (informational — the host re-gates). */
|
|
grantedCapabilities: readonly string[]
|
|
log(message: string): void
|
|
}
|
|
|
|
export type PluginWorkerRuntimeOptions = {
|
|
send: (message: PluginWorkerChildMessage) => void
|
|
importModule?: (specifier: string) => Promise<unknown>
|
|
exit?: (code: number) => void
|
|
}
|
|
|
|
export type PluginWorkerRuntime = {
|
|
handleMessage(raw: unknown): Promise<void>
|
|
}
|
|
|
|
function toErrorMessage(error: unknown): string {
|
|
return error instanceof Error ? (error.stack ?? error.message) : String(error)
|
|
}
|
|
|
|
export function createPluginWorkerRuntime(
|
|
options: PluginWorkerRuntimeOptions
|
|
): PluginWorkerRuntime {
|
|
const send = options.send
|
|
const importModule = options.importModule ?? ((specifier: string) => import(specifier))
|
|
const exit = options.exit ?? ((code: number) => process.exit(code))
|
|
const commandHandlers = new Map<string, (args: unknown) => unknown>()
|
|
const eventHandlers = new Map<string, ((payload: unknown) => void | Promise<void>)[]>()
|
|
const pendingHostCalls = new Map<
|
|
number,
|
|
{ resolve: (value: unknown) => void; reject: (error: PluginHostCallError) => void }
|
|
>()
|
|
let nextHostCallId = 0
|
|
let initialized = false
|
|
let shuttingDown = false
|
|
let deactivate: (() => unknown) | null = null
|
|
|
|
async function handleInit(input: {
|
|
pluginRoot: string
|
|
mainEntry: string
|
|
grantedCapabilities: string[]
|
|
}): Promise<void> {
|
|
if (initialized) {
|
|
send({ type: 'log', level: 'warn', message: 'ignoring duplicate init message' })
|
|
return
|
|
}
|
|
initialized = true
|
|
// Why: file URL import keeps ESM plugin entries working on Windows paths.
|
|
// Why: manifest paths accept either portable separator; split explicitly
|
|
// so a Windows-authored plugin also imports on macOS/Linux and vice versa.
|
|
const entryUrl = pathToFileURL(join(input.pluginRoot, ...input.mainEntry.split(/[\\/]/))).href
|
|
const module = (await importModule(entryUrl)) as { default?: unknown; deactivate?: unknown }
|
|
const activate = module?.default
|
|
if (typeof activate !== 'function') {
|
|
throw new Error(`plugin entry ${input.mainEntry} has no default-exported activate function`)
|
|
}
|
|
if (module.deactivate !== undefined && typeof module.deactivate !== 'function') {
|
|
throw new Error(`plugin entry ${input.mainEntry} has a non-function deactivate export`)
|
|
}
|
|
deactivate = (module.deactivate as (() => unknown) | undefined) ?? null
|
|
const orca: PluginWorkerOrcaApi = {
|
|
commands: {
|
|
register(commandId, handler) {
|
|
commandHandlers.set(commandId, handler)
|
|
}
|
|
},
|
|
events: {
|
|
on(event, handler) {
|
|
const handlers = eventHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
eventHandlers.set(event, handlers)
|
|
}
|
|
},
|
|
host: {
|
|
call(method, params) {
|
|
const callId = nextHostCallId++
|
|
return new Promise<unknown>((resolve, reject) => {
|
|
pendingHostCalls.set(callId, { resolve, reject })
|
|
send({ type: 'hostCall', callId, method, params })
|
|
})
|
|
}
|
|
},
|
|
grantedCapabilities: input.grantedCapabilities,
|
|
log(message) {
|
|
send({ type: 'log', level: 'info', message: String(message).slice(0, 8192) })
|
|
}
|
|
}
|
|
await activate(orca)
|
|
send({ type: 'ready', commands: [...commandHandlers.keys()] })
|
|
}
|
|
|
|
return {
|
|
async handleMessage(raw) {
|
|
const parsed = pluginWorkerParentMessageSchema.safeParse(raw)
|
|
if (!parsed.success) {
|
|
send({ type: 'log', level: 'warn', message: 'ignoring malformed parent message' })
|
|
return
|
|
}
|
|
const message = parsed.data
|
|
try {
|
|
switch (message.type) {
|
|
case 'init': {
|
|
await handleInit(message)
|
|
return
|
|
}
|
|
case 'invokeCommand': {
|
|
const handler = commandHandlers.get(message.commandId)
|
|
if (!handler) {
|
|
send({
|
|
type: 'commandResult',
|
|
callId: message.callId,
|
|
ok: false,
|
|
error: `no handler registered for command ${message.commandId}`
|
|
})
|
|
return
|
|
}
|
|
try {
|
|
const value = await handler(message.args)
|
|
send({ type: 'commandResult', callId: message.callId, ok: true, value })
|
|
} catch (error) {
|
|
send({
|
|
type: 'commandResult',
|
|
callId: message.callId,
|
|
ok: false,
|
|
error: toErrorMessage(error)
|
|
})
|
|
}
|
|
return
|
|
}
|
|
case 'deliverEvent': {
|
|
const handlers = eventHandlers.get(message.event) ?? []
|
|
for (const handler of handlers) {
|
|
try {
|
|
await handler(message.payload)
|
|
} catch (error) {
|
|
send({ type: 'log', level: 'error', message: toErrorMessage(error) })
|
|
}
|
|
}
|
|
send({ type: 'eventAck', eventId: message.eventId })
|
|
return
|
|
}
|
|
case 'hostResult': {
|
|
const pending = pendingHostCalls.get(message.callId)
|
|
if (!pending) {
|
|
return
|
|
}
|
|
pendingHostCalls.delete(message.callId)
|
|
if (message.ok) {
|
|
pending.resolve(message.value)
|
|
} else {
|
|
const error: PluginHostCallError = new Error(message.error ?? 'host call failed')
|
|
error.code = message.errorCode
|
|
pending.reject(error)
|
|
}
|
|
return
|
|
}
|
|
case 'shutdown': {
|
|
if (shuttingDown) {
|
|
return
|
|
}
|
|
shuttingDown = true
|
|
try {
|
|
await deactivate?.()
|
|
} catch (error) {
|
|
send({ type: 'log', level: 'error', message: toErrorMessage(error).slice(0, 8192) })
|
|
}
|
|
exit(0)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Why: an init/activation failure leaves the worker useless; report
|
|
// and die so the parent surfaces the error instead of hanging on
|
|
// the ready timeout.
|
|
send({ type: 'fatal', error: toErrorMessage(error) })
|
|
exit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|