refactor(effect): prune unused facades (#20748)

This commit is contained in:
Kit Langton
2026-04-02 20:15:09 -04:00
committed by GitHub
parent 7f45943a9e
commit 8942fc21aa
19 changed files with 131 additions and 163 deletions
-5
View File
@@ -417,11 +417,6 @@ export namespace Account {
return Option.getOrUndefined(await runPromise((service) => service.active())) return Option.getOrUndefined(await runPromise((service) => service.active()))
} }
export async function config(accountID: AccountID, orgID: OrgID): Promise<Record<string, unknown> | undefined> {
const cfg = await runPromise((service) => service.config(accountID, orgID))
return Option.getOrUndefined(cfg)
}
export async function token(accountID: AccountID): Promise<AccessToken | undefined> { export async function token(accountID: AccountID): Promise<AccessToken | undefined> {
const t = await runPromise((service) => service.token(accountID)) const t = await runPromise((service) => service.token(accountID))
return Option.getOrUndefined(t) return Option.getOrUndefined(t)
+18 -18
View File
@@ -124,20 +124,24 @@ export namespace Command {
source: "mcp", source: "mcp",
description: prompt.description, description: prompt.description,
get template() { get template() {
return new Promise<string>(async (resolve, reject) => { return Effect.runPromise(
const template = await MCP.getPrompt( mcp
prompt.client, .getPrompt(
prompt.name, prompt.client,
prompt.arguments prompt.name,
? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`])) prompt.arguments
: {}, ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`]))
).catch(reject) : {},
resolve( )
template?.messages .pipe(
.map((message) => (message.content.type === "text" ? message.content.text : "")) Effect.map(
.join("\n") || "", (template) =>
) template?.messages
}) .map((message) => (message.content.type === "text" ? message.content.text : ""))
.join("\n") || "",
),
),
)
}, },
hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [], hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
} }
@@ -185,10 +189,6 @@ export namespace Command {
const { runPromise } = makeRuntime(Service, defaultLayer) const { runPromise } = makeRuntime(Service, defaultLayer)
export async function get(name: string) {
return runPromise((svc) => svc.get(name))
}
export async function list() { export async function list() {
return runPromise((svc) => svc.list()) return runPromise((svc) => svc.list())
} }
@@ -341,10 +341,6 @@ export namespace Installation {
const { runPromise } = makeRuntime(Service, defaultLayer) const { runPromise } = makeRuntime(Service, defaultLayer)
export async function info(): Promise<Info> {
return runPromise((svc) => svc.info())
}
export async function method(): Promise<Method> { export async function method(): Promise<Method> {
return runPromise((svc) => svc.method()) return runPromise((svc) => svc.method())
} }
-8
View File
@@ -168,14 +168,6 @@ export namespace McpAuth {
export const updateCodeVerifier = async (mcpName: string, codeVerifier: string) => export const updateCodeVerifier = async (mcpName: string, codeVerifier: string) =>
runPromise((svc) => svc.updateCodeVerifier(mcpName, codeVerifier)) runPromise((svc) => svc.updateCodeVerifier(mcpName, codeVerifier))
export const clearCodeVerifier = async (mcpName: string) => runPromise((svc) => svc.clearCodeVerifier(mcpName))
export const updateOAuthState = async (mcpName: string, oauthState: string) => export const updateOAuthState = async (mcpName: string, oauthState: string) =>
runPromise((svc) => svc.updateOAuthState(mcpName, oauthState)) runPromise((svc) => svc.updateOAuthState(mcpName, oauthState))
export const getOAuthState = async (mcpName: string) => runPromise((svc) => svc.getOAuthState(mcpName))
export const clearOAuthState = async (mcpName: string) => runPromise((svc) => svc.clearOAuthState(mcpName))
export const isTokenExpired = async (mcpName: string) => runPromise((svc) => svc.isTokenExpired(mcpName))
} }
-5
View File
@@ -889,8 +889,6 @@ export namespace MCP {
export const status = async () => runPromise((svc) => svc.status()) export const status = async () => runPromise((svc) => svc.status())
export const clients = async () => runPromise((svc) => svc.clients())
export const tools = async () => runPromise((svc) => svc.tools()) export const tools = async () => runPromise((svc) => svc.tools())
export const prompts = async () => runPromise((svc) => svc.prompts()) export const prompts = async () => runPromise((svc) => svc.prompts())
@@ -906,9 +904,6 @@ export namespace MCP {
export const getPrompt = async (clientName: string, name: string, args?: Record<string, string>) => export const getPrompt = async (clientName: string, name: string, args?: Record<string, string>) =>
runPromise((svc) => svc.getPrompt(clientName, name, args)) runPromise((svc) => svc.getPrompt(clientName, name, args))
export const readResource = async (clientName: string, resourceUri: string) =>
runPromise((svc) => svc.readResource(clientName, resourceUri))
export const startAuth = async (mcpName: string) => runPromise((svc) => svc.startAuth(mcpName)) export const startAuth = async (mcpName: string) => runPromise((svc) => svc.startAuth(mcpName))
export const authenticate = async (mcpName: string) => runPromise((svc) => svc.authenticate(mcpName)) export const authenticate = async (mcpName: string) => runPromise((svc) => svc.authenticate(mcpName))
+8 -5
View File
@@ -140,6 +140,7 @@ export namespace Permission {
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service
const state = yield* InstanceState.make<State>( const state = yield* InstanceState.make<State>(
Effect.fn("Permission.state")(function* (ctx) { Effect.fn("Permission.state")(function* (ctx) {
const row = Database.use((db) => const row = Database.use((db) =>
@@ -191,7 +192,7 @@ export namespace Permission {
const deferred = yield* Deferred.make<void, RejectedError | CorrectedError>() const deferred = yield* Deferred.make<void, RejectedError | CorrectedError>()
pending.set(id, { info, deferred }) pending.set(id, { info, deferred })
void Bus.publish(Event.Asked, info) yield* bus.publish(Event.Asked, info)
return yield* Effect.ensuring( return yield* Effect.ensuring(
Deferred.await(deferred), Deferred.await(deferred),
Effect.sync(() => { Effect.sync(() => {
@@ -206,7 +207,7 @@ export namespace Permission {
if (!existing) return if (!existing) return
pending.delete(input.requestID) pending.delete(input.requestID)
void Bus.publish(Event.Replied, { yield* bus.publish(Event.Replied, {
sessionID: existing.info.sessionID, sessionID: existing.info.sessionID,
requestID: existing.info.id, requestID: existing.info.id,
reply: input.reply, reply: input.reply,
@@ -221,7 +222,7 @@ export namespace Permission {
for (const [id, item] of pending.entries()) { for (const [id, item] of pending.entries()) {
if (item.info.sessionID !== existing.info.sessionID) continue if (item.info.sessionID !== existing.info.sessionID) continue
pending.delete(id) pending.delete(id)
void Bus.publish(Event.Replied, { yield* bus.publish(Event.Replied, {
sessionID: item.info.sessionID, sessionID: item.info.sessionID,
requestID: item.info.id, requestID: item.info.id,
reply: "reject", reply: "reject",
@@ -249,7 +250,7 @@ export namespace Permission {
) )
if (!ok) continue if (!ok) continue
pending.delete(id) pending.delete(id)
void Bus.publish(Event.Replied, { yield* bus.publish(Event.Replied, {
sessionID: item.info.sessionID, sessionID: item.info.sessionID,
requestID: item.info.id, requestID: item.info.id,
reply: "always", reply: "always",
@@ -306,7 +307,9 @@ export namespace Permission {
return result return result
} }
export const { runPromise } = makeRuntime(Service, layer) export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
export const { runPromise } = makeRuntime(Service, defaultLayer)
export async function ask(input: z.infer<typeof AskInput>) { export async function ask(input: z.infer<typeof AskInput>) {
return runPromise((s) => s.ask(input)) return runPromise((s) => s.ask(input))
+6 -6
View File
@@ -74,8 +74,8 @@ export namespace Plugin {
return result return result
} }
function publishPluginError(message: string) { function publishPluginError(bus: Bus.Interface, message: string) {
Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) Effect.runFork(bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }))
} }
async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks: Hooks[]) { async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks: Hooks[]) {
@@ -161,24 +161,24 @@ export namespace Plugin {
if (stage === "install") { if (stage === "install") {
const parsed = parsePluginSpecifier(spec) const parsed = parsePluginSpecifier(spec)
log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message }) log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message })
publishPluginError(`Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`) publishPluginError(bus, `Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`)
return return
} }
if (stage === "compatibility") { if (stage === "compatibility") {
log.warn("plugin incompatible", { path: spec, error: message }) log.warn("plugin incompatible", { path: spec, error: message })
publishPluginError(`Plugin ${spec} skipped: ${message}`) publishPluginError(bus, `Plugin ${spec} skipped: ${message}`)
return return
} }
if (stage === "entry") { if (stage === "entry") {
log.error("failed to resolve plugin server entry", { path: spec, error: message }) log.error("failed to resolve plugin server entry", { path: spec, error: message })
publishPluginError(`Failed to load plugin ${spec}: ${message}`) publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`)
return return
} }
log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message }) log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message })
publishPluginError(`Failed to load plugin ${spec}: ${message}`) publishPluginError(bus, `Failed to load plugin ${spec}: ${message}`)
}, },
}, },
}), }),
+86 -82
View File
@@ -118,6 +118,8 @@ export namespace Pty {
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service
const plugin = yield* Plugin.Service
function teardown(session: Active) { function teardown(session: Active) {
try { try {
session.process.kill() session.process.kill()
@@ -157,7 +159,7 @@ export namespace Pty {
s.sessions.delete(id) s.sessions.delete(id)
log.info("removing session", { id }) log.info("removing session", { id })
teardown(session) teardown(session)
void Bus.publish(Event.Deleted, { id: session.info.id }) yield* bus.publish(Event.Deleted, { id: session.info.id })
}) })
const list = Effect.fn("Pty.list")(function* () { const list = Effect.fn("Pty.list")(function* () {
@@ -172,95 +174,95 @@ export namespace Pty {
const create = Effect.fn("Pty.create")(function* (input: CreateInput) { const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
const s = yield* InstanceState.get(state) const s = yield* InstanceState.get(state)
return yield* Effect.promise(async () => { const id = PtyID.ascending()
const id = PtyID.ascending() const command = input.command || Shell.preferred()
const command = input.command || Shell.preferred() const args = input.args || []
const args = input.args || [] if (Shell.login(command)) {
if (Shell.login(command)) { args.push("-l")
args.push("-l") }
}
const cwd = input.cwd || s.dir const cwd = input.cwd || s.dir
const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) const shell = yield* plugin.trigger("shell.env", { cwd }, { env: {} })
const env = { const env = {
...process.env, ...process.env,
...input.env, ...input.env,
...shellEnv.env, ...shell.env,
TERM: "xterm-256color", TERM: "xterm-256color",
OPENCODE_TERMINAL: "1", OPENCODE_TERMINAL: "1",
} as Record<string, string> } as Record<string, string>
if (process.platform === "win32") { if (process.platform === "win32") {
env.LC_ALL = "C.UTF-8" env.LC_ALL = "C.UTF-8"
env.LC_CTYPE = "C.UTF-8" env.LC_CTYPE = "C.UTF-8"
env.LANG = "C.UTF-8" env.LANG = "C.UTF-8"
} }
log.info("creating session", { id, cmd: command, args, cwd }) log.info("creating session", { id, cmd: command, args, cwd })
const spawn = await pty() const spawn = yield* Effect.promise(() => pty())
const proc = spawn(command, args, { const proc = yield* Effect.sync(() =>
spawn(command, args, {
name: "xterm-256color", name: "xterm-256color",
cwd, cwd,
env, env,
}) }),
)
const info = { const info = {
id, id,
title: input.title || `Terminal ${id.slice(-4)}`, title: input.title || `Terminal ${id.slice(-4)}`,
command, command,
args, args,
cwd, cwd,
status: "running", status: "running",
pid: proc.pid, pid: proc.pid,
} as const } as const
const session: Active = { const session: Active = {
info, info,
process: proc, process: proc,
buffer: "", buffer: "",
bufferCursor: 0, bufferCursor: 0,
cursor: 0, cursor: 0,
subscribers: new Map(), subscribers: new Map(),
} }
s.sessions.set(id, session) s.sessions.set(id, session)
proc.onData( proc.onData(
Instance.bind((chunk) => { Instance.bind((chunk) => {
session.cursor += chunk.length session.cursor += chunk.length
for (const [key, ws] of session.subscribers.entries()) { for (const [key, ws] of session.subscribers.entries()) {
if (ws.readyState !== 1) { if (ws.readyState !== 1) {
session.subscribers.delete(key) session.subscribers.delete(key)
continue continue
}
if (ws.data !== key) {
session.subscribers.delete(key)
continue
}
try {
ws.send(chunk)
} catch {
session.subscribers.delete(key)
}
} }
if (ws.data !== key) {
session.subscribers.delete(key)
continue
}
try {
ws.send(chunk)
} catch {
session.subscribers.delete(key)
}
}
session.buffer += chunk session.buffer += chunk
if (session.buffer.length <= BUFFER_LIMIT) return if (session.buffer.length <= BUFFER_LIMIT) return
const excess = session.buffer.length - BUFFER_LIMIT const excess = session.buffer.length - BUFFER_LIMIT
session.buffer = session.buffer.slice(excess) session.buffer = session.buffer.slice(excess)
session.bufferCursor += excess session.bufferCursor += excess
}), }),
) )
proc.onExit( proc.onExit(
Instance.bind(({ exitCode }) => { Instance.bind(({ exitCode }) => {
if (session.info.status === "exited") return if (session.info.status === "exited") return
log.info("session exited", { id, exitCode }) log.info("session exited", { id, exitCode })
session.info.status = "exited" session.info.status = "exited"
void Bus.publish(Event.Exited, { id, exitCode }) Effect.runFork(bus.publish(Event.Exited, { id, exitCode }))
Effect.runFork(remove(id)) Effect.runFork(remove(id))
}), }),
) )
await Bus.publish(Event.Created, { info }) yield* bus.publish(Event.Created, { info })
return info return info
})
}) })
const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) { const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {
@@ -273,7 +275,7 @@ export namespace Pty {
if (input.size) { if (input.size) {
session.process.resize(input.size.cols, input.size.rows) session.process.resize(input.size.cols, input.size.rows)
} }
void Bus.publish(Event.Updated, { info: session.info }) yield* bus.publish(Event.Updated, { info: session.info })
return session.info return session.info
}) })
@@ -361,7 +363,9 @@ export namespace Pty {
}), }),
) )
const { runPromise } = makeRuntime(Service, layer) const defaultLayer = layer.pipe(Layer.provide(Bus.layer), Layer.provide(Plugin.defaultLayer))
const { runPromise } = makeRuntime(Service, defaultLayer)
export async function list() { export async function list() {
return runPromise((svc) => svc.list()) return runPromise((svc) => svc.list())
+7 -4
View File
@@ -109,6 +109,7 @@ export namespace Question {
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const bus = yield* Bus.Service
const state = yield* InstanceState.make<State>( const state = yield* InstanceState.make<State>(
Effect.fn("Question.state")(function* () { Effect.fn("Question.state")(function* () {
const state = { const state = {
@@ -145,7 +146,7 @@ export namespace Question {
tool: input.tool, tool: input.tool,
} }
pending.set(id, { info, deferred }) pending.set(id, { info, deferred })
Bus.publish(Event.Asked, info) yield* bus.publish(Event.Asked, info)
return yield* Effect.ensuring( return yield* Effect.ensuring(
Deferred.await(deferred), Deferred.await(deferred),
@@ -164,7 +165,7 @@ export namespace Question {
} }
pending.delete(input.requestID) pending.delete(input.requestID)
log.info("replied", { requestID: input.requestID, answers: input.answers }) log.info("replied", { requestID: input.requestID, answers: input.answers })
Bus.publish(Event.Replied, { yield* bus.publish(Event.Replied, {
sessionID: existing.info.sessionID, sessionID: existing.info.sessionID,
requestID: existing.info.id, requestID: existing.info.id,
answers: input.answers, answers: input.answers,
@@ -181,7 +182,7 @@ export namespace Question {
} }
pending.delete(requestID) pending.delete(requestID)
log.info("rejected", { requestID }) log.info("rejected", { requestID })
Bus.publish(Event.Rejected, { yield* bus.publish(Event.Rejected, {
sessionID: existing.info.sessionID, sessionID: existing.info.sessionID,
requestID: existing.info.id, requestID: existing.info.id,
}) })
@@ -197,7 +198,9 @@ export namespace Question {
}), }),
) )
const { runPromise } = makeRuntime(Service, layer) const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
const { runPromise } = makeRuntime(Service, defaultLayer)
export async function ask(input: { export async function ask(input: {
sessionID: SessionID sessionID: SessionID
@@ -248,18 +248,10 @@ export namespace Instruction {
return runPromise((svc) => svc.systemPaths()) return runPromise((svc) => svc.systemPaths())
} }
export async function system() {
return runPromise((svc) => svc.system())
}
export function loaded(messages: MessageV2.WithParts[]) { export function loaded(messages: MessageV2.WithParts[]) {
return extract(messages) return extract(messages)
} }
export async function find(dir: string) {
return runPromise((svc) => svc.find(dir))
}
export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: MessageID) { export async function resolve(messages: MessageV2.WithParts[], filepath: string, messageID: MessageID) {
return runPromise((svc) => svc.resolve(messages, filepath, messageID)) return runPromise((svc) => svc.resolve(messages, filepath, messageID))
} }
+1 -1
View File
@@ -512,7 +512,7 @@ export namespace SessionProcessor {
Layer.provide(Snapshot.defaultLayer), Layer.provide(Snapshot.defaultLayer),
Layer.provide(Agent.defaultLayer), Layer.provide(Agent.defaultLayer),
Layer.provide(LLM.defaultLayer), Layer.provide(LLM.defaultLayer),
Layer.provide(Permission.layer), Layer.provide(Permission.defaultLayer),
Layer.provide(Plugin.defaultLayer), Layer.provide(Plugin.defaultLayer),
Layer.provide(SessionStatus.layer.pipe(Layer.provide(Bus.layer))), Layer.provide(SessionStatus.layer.pipe(Layer.provide(Bus.layer))),
Layer.provide(Bus.layer), Layer.provide(Bus.layer),
+1 -1
View File
@@ -1715,7 +1715,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
Layer.provide(SessionCompaction.defaultLayer), Layer.provide(SessionCompaction.defaultLayer),
Layer.provide(SessionProcessor.defaultLayer), Layer.provide(SessionProcessor.defaultLayer),
Layer.provide(Command.defaultLayer), Layer.provide(Command.defaultLayer),
Layer.provide(Permission.layer), Layer.provide(Permission.defaultLayer),
Layer.provide(MCP.defaultLayer), Layer.provide(MCP.defaultLayer),
Layer.provide(LSP.defaultLayer), Layer.provide(LSP.defaultLayer),
Layer.provide(FileTime.defaultLayer), Layer.provide(FileTime.defaultLayer),
-4
View File
@@ -174,8 +174,4 @@ export namespace SessionSummary {
export async function diff(input: z.infer<typeof DiffInput>) { export async function diff(input: z.infer<typeof DiffInput>) {
return runPromise((svc) => svc.diff(input)) return runPromise((svc) => svc.diff(input))
} }
export async function computeDiff(input: { messages: MessageV2.WithParts[] }) {
return runPromise((svc) => svc.computeDiff(input))
}
} }
-4
View File
@@ -545,10 +545,6 @@ export namespace Snapshot {
return runPromise((svc) => svc.init()) return runPromise((svc) => svc.init())
} }
export async function cleanup() {
return runPromise((svc) => svc.cleanup())
}
export async function track() { export async function track() {
return runPromise((svc) => svc.track()) return runPromise((svc) => svc.track())
} }
-4
View File
@@ -206,10 +206,6 @@ export namespace ToolRegistry {
const { runPromise } = makeRuntime(Service, defaultLayer) const { runPromise } = makeRuntime(Service, defaultLayer)
export async function register(tool: Tool.Info) {
return runPromise((svc) => svc.register(tool))
}
export async function ids() { export async function ids() {
return runPromise((svc) => svc.ids()) return runPromise((svc) => svc.ids())
} }
@@ -211,7 +211,7 @@ function liveRuntime(layer: Layer.Layer<LLM.Service>, provider = ProviderTest.fa
Layer.provide(Session.defaultLayer), Layer.provide(Session.defaultLayer),
Layer.provide(Snapshot.defaultLayer), Layer.provide(Snapshot.defaultLayer),
Layer.provide(layer), Layer.provide(layer),
Layer.provide(Permission.layer), Layer.provide(Permission.defaultLayer),
Layer.provide(Agent.defaultLayer), Layer.provide(Agent.defaultLayer),
Layer.provide(Plugin.defaultLayer), Layer.provide(Plugin.defaultLayer),
Layer.provide(status), Layer.provide(status),
@@ -149,7 +149,7 @@ const deps = Layer.mergeAll(
Session.defaultLayer, Session.defaultLayer,
Snapshot.defaultLayer, Snapshot.defaultLayer,
AgentSvc.defaultLayer, AgentSvc.defaultLayer,
Permission.layer, Permission.defaultLayer,
Plugin.defaultLayer, Plugin.defaultLayer,
Config.defaultLayer, Config.defaultLayer,
LLM.defaultLayer, LLM.defaultLayer,
@@ -150,7 +150,7 @@ function makeHttp() {
LLM.defaultLayer, LLM.defaultLayer,
AgentSvc.defaultLayer, AgentSvc.defaultLayer,
Command.defaultLayer, Command.defaultLayer,
Permission.layer, Permission.defaultLayer,
Plugin.defaultLayer, Plugin.defaultLayer,
Config.defaultLayer, Config.defaultLayer,
ProviderSvc.defaultLayer, ProviderSvc.defaultLayer,
@@ -114,7 +114,7 @@ function makeHttp() {
LLM.defaultLayer, LLM.defaultLayer,
AgentSvc.defaultLayer, AgentSvc.defaultLayer,
Command.defaultLayer, Command.defaultLayer,
Permission.layer, Permission.defaultLayer,
Plugin.defaultLayer, Plugin.defaultLayer,
Config.defaultLayer, Config.defaultLayer,
ProviderSvc.defaultLayer, ProviderSvc.defaultLayer,