feat(tui): add v2 plugin runtime

This commit is contained in:
Dax Raad 2026-07-14 12:43:53 -04:00
commit 4a93972a78
63 changed files with 1722 additions and 1701 deletions

View file

@ -1,7 +1,6 @@
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Global } from "@opencode-ai/core/global"
import { run } from "@opencode-ai/tui"
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Config } from "../../config"
@ -9,6 +8,7 @@ import { Effect, Option } from "effect"
import { Server } from "../../services/server"
import { Updater } from "../../services/updater"
import { UpdatePreflight } from "../../services/update-preflight"
import { Npm } from "@opencode-ai/core/npm"
export default Runtime.handler(Commands, (input) =>
Effect.gen(function* () {
@ -36,7 +36,7 @@ export default Runtime.handler(Commands, (input) =>
)
preflight.loading()
const config = yield* Config.Service
let disposeSlots: (() => void) | undefined
const npm = yield* Npm.Service
const context = yield* Effect.context()
const runFork = Effect.runForkWith(context)
const runPromise = Effect.runPromiseWith(context)
@ -44,9 +44,14 @@ export default Runtime.handler(Commands, (input) =>
server,
args: { continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
config: {
path: config.path,
get: () => runPromise(config.get()),
update: (update) => runPromise(config.update(update)),
},
packages: {
resolve: (spec) =>
runPromise(npm.add(spec, { subpaths: ["tui"] }).pipe(Effect.map((result) => result.entrypoint))),
},
terminalHandoff: () => preflight.finish(),
log: (level, message, tags) => {
const effect =
@ -59,14 +64,6 @@ export default Runtime.handler(Commands, (input) =>
: Effect.logInfo(message, tags)
runFork(effect)
},
pluginHost: {
async start(pluginInput) {
disposeSlots = await loadBuiltinPlugins(pluginInput.api, pluginInput.runtime)
},
async dispose() {
disposeSlots?.()
},
},
}).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
}),
)

View file

@ -4,6 +4,7 @@ import { Spec } from "./spec"
import { Global } from "@opencode-ai/core/global"
import { Updater } from "../services/updater"
import { Config } from "../config"
import { Npm } from "@opencode-ai/core/npm"
export type Input<Value> =
Value extends Spec.Node<infer _Name, infer Command, infer _Commands>
@ -17,7 +18,7 @@ type RuntimeHandler = (
) => Effect.Effect<
void,
unknown,
FileSystem.FileSystem | Global.Service | Updater.Service | Config.Service | Scope.Scope
FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope
>
type Loader<Node extends Spec.Any> = () => Promise<{
default: (
@ -25,7 +26,7 @@ type Loader<Node extends Spec.Any> = () => Promise<{
) => Effect.Effect<
void,
any,
FileSystem.FileSystem | Global.Service | Updater.Service | Config.Service | Scope.Scope
FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope
>
}>
type ProvidedCommand = Command.Command<
@ -33,7 +34,7 @@ type ProvidedCommand = Command.Command<
unknown,
unknown,
unknown,
FileSystem.FileSystem | Global.Service | Updater.Service | Config.Service | Scope.Scope
FileSystem.FileSystem | Global.Service | Npm.Service | Updater.Service | Config.Service | Scope.Scope
>
export type Handlers<Node extends Spec.Any> = keyof Node["commands"] extends never

View file

@ -12,6 +12,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { Global } from "@opencode-ai/core/global"
import { AppProcess } from "@opencode-ai/core/process"
import { Config } from "./config"
import { Npm } from "@opencode-ai/core/npm"
const Handlers = Runtime.handlers(Commands, {
$: () => import("./commands/handlers/default"),
@ -54,7 +55,7 @@ Effect.logInfo("cli starting", {
Effect.annotateLogs({ role: "cli" }),
Effect.provide(Config.layer),
Effect.provide(Updater.layer),
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node]))),
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node, Npm.node]))),
Effect.provide(Observability.layer),
Effect.provide(NodeServices.layer),
Effect.scoped,

View file

@ -1164,13 +1164,13 @@ export function createPromptState(input: PromptInput): PromptState {
},
},
],
bindings: input.tuiConfig.keybinds.gather("run.prompt.autocomplete", [
bindings: [
"prompt.autocomplete.prev",
"prompt.autocomplete.next",
"prompt.autocomplete.hide",
"prompt.autocomplete.select",
"prompt.autocomplete.complete",
]),
].flatMap((command) => input.tuiConfig.keybinds.get(command)),
}))
const onKeyDown = (event: KeyEvent) => {