From e8a31b07662b016d350f95936dc8c573259ccc23 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 16:32:15 -0400 Subject: [PATCH 1/2] refactor(effect): prune unused facades --- packages/opencode/src/account/index.ts | 5 --- packages/opencode/src/command/index.ts | 36 +++++++++---------- packages/opencode/src/installation/index.ts | 4 --- packages/opencode/src/mcp/auth.ts | 8 ----- packages/opencode/src/mcp/index.ts | 5 --- packages/opencode/src/permission/index.ts | 13 ++++--- packages/opencode/src/plugin/index.ts | 12 +++---- packages/opencode/src/pty/index.ts | 16 +++++---- packages/opencode/src/question/index.ts | 11 +++--- packages/opencode/src/session/instruction.ts | 8 ----- packages/opencode/src/session/processor.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/session/summary.ts | 4 --- packages/opencode/src/snapshot/index.ts | 4 --- packages/opencode/src/tool/registry.ts | 4 --- .../opencode/test/session/compaction.test.ts | 2 +- .../test/session/processor-effect.test.ts | 2 +- .../test/session/prompt-effect.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- 19 files changed, 55 insertions(+), 87 deletions(-) diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index bcc90b7b1d5c..2a8d35bfa8de 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -417,11 +417,6 @@ export namespace Account { return Option.getOrUndefined(await runPromise((service) => service.active())) } - export async function config(accountID: AccountID, orgID: OrgID): Promise | undefined> { - const cfg = await runPromise((service) => service.config(accountID, orgID)) - return Option.getOrUndefined(cfg) - } - export async function token(accountID: AccountID): Promise { const t = await runPromise((service) => service.token(accountID)) return Option.getOrUndefined(t) diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index f9bd594c1602..088d7c565975 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -124,20 +124,24 @@ export namespace Command { source: "mcp", description: prompt.description, get template() { - return new Promise(async (resolve, reject) => { - const template = await MCP.getPrompt( - prompt.client, - prompt.name, - prompt.arguments - ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`])) - : {}, - ).catch(reject) - resolve( - template?.messages - .map((message) => (message.content.type === "text" ? message.content.text : "")) - .join("\n") || "", - ) - }) + return Effect.runPromise( + mcp + .getPrompt( + prompt.client, + prompt.name, + prompt.arguments + ? Object.fromEntries(prompt.arguments.map((argument, i) => [argument.name, `$${i + 1}`])) + : {}, + ) + .pipe( + Effect.map( + (template) => + template?.messages + .map((message) => (message.content.type === "text" ? message.content.text : "")) + .join("\n") || "", + ), + ), + ) }, hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [], } @@ -185,10 +189,6 @@ export namespace Command { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function get(name: string) { - return runPromise((svc) => svc.get(name)) - } - export async function list() { return runPromise((svc) => svc.list()) } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 232fa14f542c..f4cd4d09fa5f 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -341,10 +341,6 @@ export namespace Installation { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function info(): Promise { - return runPromise((svc) => svc.info()) - } - export async function method(): Promise { return runPromise((svc) => svc.method()) } diff --git a/packages/opencode/src/mcp/auth.ts b/packages/opencode/src/mcp/auth.ts index 54f2ce495772..773ca0a6f523 100644 --- a/packages/opencode/src/mcp/auth.ts +++ b/packages/opencode/src/mcp/auth.ts @@ -168,14 +168,6 @@ export namespace McpAuth { export const updateCodeVerifier = async (mcpName: string, codeVerifier: string) => 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) => 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)) } diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 47c39aad5646..8c92bb6b2e6d 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -889,8 +889,6 @@ export namespace MCP { 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 prompts = async () => runPromise((svc) => svc.prompts()) @@ -906,9 +904,6 @@ export namespace MCP { export const getPrompt = async (clientName: string, name: string, args?: Record) => 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 authenticate = async (mcpName: string) => runPromise((svc) => svc.authenticate(mcpName)) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 1a7bd2c610a5..b2cc0f9bbc07 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -140,6 +140,7 @@ export namespace Permission { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service const state = yield* InstanceState.make( Effect.fn("Permission.state")(function* (ctx) { const row = Database.use((db) => @@ -191,7 +192,7 @@ export namespace Permission { const deferred = yield* Deferred.make() pending.set(id, { info, deferred }) - void Bus.publish(Event.Asked, info) + yield* bus.publish(Event.Asked, info) return yield* Effect.ensuring( Deferred.await(deferred), Effect.sync(() => { @@ -206,7 +207,7 @@ export namespace Permission { if (!existing) return pending.delete(input.requestID) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, reply: input.reply, @@ -221,7 +222,7 @@ export namespace Permission { for (const [id, item] of pending.entries()) { if (item.info.sessionID !== existing.info.sessionID) continue pending.delete(id) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: item.info.sessionID, requestID: item.info.id, reply: "reject", @@ -249,7 +250,7 @@ export namespace Permission { ) if (!ok) continue pending.delete(id) - void Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: item.info.sessionID, requestID: item.info.id, reply: "always", @@ -306,7 +307,9 @@ export namespace Permission { 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) { return runPromise((s) => s.ask(input)) diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 53a8741eac84..fb60fa096e88 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -74,8 +74,8 @@ export namespace Plugin { return result } - function publishPluginError(message: string) { - Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() }) + function publishPluginError(bus: Bus.Interface, message: string) { + Effect.runFork(bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })) } async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks: Hooks[]) { @@ -161,24 +161,24 @@ export namespace Plugin { if (stage === "install") { const parsed = parsePluginSpecifier(spec) 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 } if (stage === "compatibility") { log.warn("plugin incompatible", { path: spec, error: message }) - publishPluginError(`Plugin ${spec} skipped: ${message}`) + publishPluginError(bus, `Plugin ${spec} skipped: ${message}`) return } if (stage === "entry") { 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 } 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}`) }, }, }), diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 72089d8441ea..230989355c94 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -118,6 +118,8 @@ export namespace Pty { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service + const plugin = yield* Plugin.Service function teardown(session: Active) { try { session.process.kill() @@ -157,7 +159,7 @@ export namespace Pty { s.sessions.delete(id) log.info("removing session", { id }) 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* () { @@ -181,7 +183,7 @@ export namespace Pty { } const cwd = input.cwd || s.dir - const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) + const shellEnv = await Effect.runPromise(plugin.trigger("shell.env", { cwd }, { env: {} })) const env = { ...process.env, ...input.env, @@ -254,11 +256,11 @@ export namespace Pty { if (session.info.status === "exited") return log.info("session exited", { id, exitCode }) session.info.status = "exited" - void Bus.publish(Event.Exited, { id, exitCode }) + Effect.runFork(bus.publish(Event.Exited, { id, exitCode })) Effect.runFork(remove(id)) }), ) - await Bus.publish(Event.Created, { info }) + await Effect.runPromise(bus.publish(Event.Created, { info })) return info }) }) @@ -273,7 +275,7 @@ export namespace Pty { if (input.size) { 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 }) @@ -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() { return runPromise((svc) => svc.list()) diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index f46cdd1081f0..13470f0a0231 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -109,6 +109,7 @@ export namespace Question { export const layer = Layer.effect( Service, Effect.gen(function* () { + const bus = yield* Bus.Service const state = yield* InstanceState.make( Effect.fn("Question.state")(function* () { const state = { @@ -145,7 +146,7 @@ export namespace Question { tool: input.tool, } pending.set(id, { info, deferred }) - Bus.publish(Event.Asked, info) + yield* bus.publish(Event.Asked, info) return yield* Effect.ensuring( Deferred.await(deferred), @@ -164,7 +165,7 @@ export namespace Question { } pending.delete(input.requestID) log.info("replied", { requestID: input.requestID, answers: input.answers }) - Bus.publish(Event.Replied, { + yield* bus.publish(Event.Replied, { sessionID: existing.info.sessionID, requestID: existing.info.id, answers: input.answers, @@ -181,7 +182,7 @@ export namespace Question { } pending.delete(requestID) log.info("rejected", { requestID }) - Bus.publish(Event.Rejected, { + yield* bus.publish(Event.Rejected, { sessionID: existing.info.sessionID, 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: { sessionID: SessionID diff --git a/packages/opencode/src/session/instruction.ts b/packages/opencode/src/session/instruction.ts index 02a536edd864..fc90093e99fe 100644 --- a/packages/opencode/src/session/instruction.ts +++ b/packages/opencode/src/session/instruction.ts @@ -248,18 +248,10 @@ export namespace Instruction { return runPromise((svc) => svc.systemPaths()) } - export async function system() { - return runPromise((svc) => svc.system()) - } - export function loaded(messages: MessageV2.WithParts[]) { 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) { return runPromise((svc) => svc.resolve(messages, filepath, messageID)) } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index b1a1b8dbd3d1..146c73f27712 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -512,7 +512,7 @@ export namespace SessionProcessor { Layer.provide(Snapshot.defaultLayer), Layer.provide(Agent.defaultLayer), Layer.provide(LLM.defaultLayer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(SessionStatus.layer.pipe(Layer.provide(Bus.layer))), Layer.provide(Bus.layer), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 5121f2452759..8facf7aba7ee 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -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(SessionProcessor.defaultLayer), Layer.provide(Command.defaultLayer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(MCP.defaultLayer), Layer.provide(LSP.defaultLayer), Layer.provide(FileTime.defaultLayer), diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index d26a00d49339..f2b53f3baf58 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -174,8 +174,4 @@ export namespace SessionSummary { export async function diff(input: z.infer) { return runPromise((svc) => svc.diff(input)) } - - export async function computeDiff(input: { messages: MessageV2.WithParts[] }) { - return runPromise((svc) => svc.computeDiff(input)) - } } diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index aa62c027150a..7c952bc54db9 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -545,10 +545,6 @@ export namespace Snapshot { return runPromise((svc) => svc.init()) } - export async function cleanup() { - return runPromise((svc) => svc.cleanup()) - } - export async function track() { return runPromise((svc) => svc.track()) } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 2620405144ac..1bb270716cb9 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -206,10 +206,6 @@ export namespace ToolRegistry { const { runPromise } = makeRuntime(Service, defaultLayer) - export async function register(tool: Tool.Info) { - return runPromise((svc) => svc.register(tool)) - } - export async function ids() { return runPromise((svc) => svc.ids()) } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index f1d61babfaf5..0dac255b10f2 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -211,7 +211,7 @@ function liveRuntime(layer: Layer.Layer, provider = ProviderTest.fa Layer.provide(Session.defaultLayer), Layer.provide(Snapshot.defaultLayer), Layer.provide(layer), - Layer.provide(Permission.layer), + Layer.provide(Permission.defaultLayer), Layer.provide(Agent.defaultLayer), Layer.provide(Plugin.defaultLayer), Layer.provide(status), diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index a79e6967af44..0fc25c1a6b41 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -149,7 +149,7 @@ const deps = Layer.mergeAll( Session.defaultLayer, Snapshot.defaultLayer, AgentSvc.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, LLM.defaultLayer, diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index d077f26d6b7b..6f81ffca39f7 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -150,7 +150,7 @@ function makeHttp() { LLM.defaultLayer, AgentSvc.defaultLayer, Command.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, ProviderSvc.defaultLayer, diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 8e7f3c8c45dc..4f4376e341ca 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -114,7 +114,7 @@ function makeHttp() { LLM.defaultLayer, AgentSvc.defaultLayer, Command.defaultLayer, - Permission.layer, + Permission.defaultLayer, Plugin.defaultLayer, Config.defaultLayer, ProviderSvc.defaultLayer, From 6d6f99ae83fa9b73d2aadd0ca77199fab8fb6e1e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Apr 2026 16:50:37 -0400 Subject: [PATCH 2/2] refactor(pty): shrink create promise boundary --- packages/opencode/src/pty/index.ts | 164 ++++++++++++++--------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index 230989355c94..a97f3373d1fd 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -174,95 +174,95 @@ export namespace Pty { const create = Effect.fn("Pty.create")(function* (input: CreateInput) { const s = yield* InstanceState.get(state) - return yield* Effect.promise(async () => { - const id = PtyID.ascending() - const command = input.command || Shell.preferred() - const args = input.args || [] - if (Shell.login(command)) { - args.push("-l") - } + const id = PtyID.ascending() + const command = input.command || Shell.preferred() + const args = input.args || [] + if (Shell.login(command)) { + args.push("-l") + } - const cwd = input.cwd || s.dir - const shellEnv = await Effect.runPromise(plugin.trigger("shell.env", { cwd }, { env: {} })) - const env = { - ...process.env, - ...input.env, - ...shellEnv.env, - TERM: "xterm-256color", - OPENCODE_TERMINAL: "1", - } as Record - - if (process.platform === "win32") { - env.LC_ALL = "C.UTF-8" - env.LC_CTYPE = "C.UTF-8" - env.LANG = "C.UTF-8" - } - log.info("creating session", { id, cmd: command, args, cwd }) + const cwd = input.cwd || s.dir + const shell = yield* plugin.trigger("shell.env", { cwd }, { env: {} }) + const env = { + ...process.env, + ...input.env, + ...shell.env, + TERM: "xterm-256color", + OPENCODE_TERMINAL: "1", + } as Record + + if (process.platform === "win32") { + env.LC_ALL = "C.UTF-8" + env.LC_CTYPE = "C.UTF-8" + env.LANG = "C.UTF-8" + } + log.info("creating session", { id, cmd: command, args, cwd }) - const spawn = await pty() - const proc = spawn(command, args, { + const spawn = yield* Effect.promise(() => pty()) + const proc = yield* Effect.sync(() => + spawn(command, args, { name: "xterm-256color", cwd, env, - }) - - const info = { - id, - title: input.title || `Terminal ${id.slice(-4)}`, - command, - args, - cwd, - status: "running", - pid: proc.pid, - } as const - const session: Active = { - info, - process: proc, - buffer: "", - bufferCursor: 0, - cursor: 0, - subscribers: new Map(), - } - s.sessions.set(id, session) - proc.onData( - Instance.bind((chunk) => { - session.cursor += chunk.length - - for (const [key, ws] of session.subscribers.entries()) { - if (ws.readyState !== 1) { - session.subscribers.delete(key) - continue - } - if (ws.data !== key) { - session.subscribers.delete(key) - continue - } - try { - ws.send(chunk) - } catch { - session.subscribers.delete(key) - } + }), + ) + + const info = { + id, + title: input.title || `Terminal ${id.slice(-4)}`, + command, + args, + cwd, + status: "running", + pid: proc.pid, + } as const + const session: Active = { + info, + process: proc, + buffer: "", + bufferCursor: 0, + cursor: 0, + subscribers: new Map(), + } + s.sessions.set(id, session) + proc.onData( + Instance.bind((chunk) => { + session.cursor += chunk.length + + for (const [key, ws] of session.subscribers.entries()) { + if (ws.readyState !== 1) { + session.subscribers.delete(key) + continue } + if (ws.data !== key) { + session.subscribers.delete(key) + continue + } + try { + ws.send(chunk) + } catch { + session.subscribers.delete(key) + } + } - session.buffer += chunk - if (session.buffer.length <= BUFFER_LIMIT) return - const excess = session.buffer.length - BUFFER_LIMIT - session.buffer = session.buffer.slice(excess) - session.bufferCursor += excess - }), - ) - proc.onExit( - Instance.bind(({ exitCode }) => { - if (session.info.status === "exited") return - log.info("session exited", { id, exitCode }) - session.info.status = "exited" - Effect.runFork(bus.publish(Event.Exited, { id, exitCode })) - Effect.runFork(remove(id)) - }), - ) - await Effect.runPromise(bus.publish(Event.Created, { info })) - return info - }) + session.buffer += chunk + if (session.buffer.length <= BUFFER_LIMIT) return + const excess = session.buffer.length - BUFFER_LIMIT + session.buffer = session.buffer.slice(excess) + session.bufferCursor += excess + }), + ) + proc.onExit( + Instance.bind(({ exitCode }) => { + if (session.info.status === "exited") return + log.info("session exited", { id, exitCode }) + session.info.status = "exited" + Effect.runFork(bus.publish(Event.Exited, { id, exitCode })) + Effect.runFork(remove(id)) + }), + ) + yield* bus.publish(Event.Created, { info }) + return info }) const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {