From 537666149b5682f6f0d39d2d9f4059b3d339cc07 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Mon, 8 Jun 2026 19:43:41 +0000 Subject: [PATCH] chore: generate --- packages/core/src/models-dev.ts | 4 +- packages/core/src/observability/logging.ts | 6 +- packages/core/src/project-reference.ts | 6 +- packages/opencode/src/acp/usage.ts | 16 +- .../src/cli/cmd/run/runtime.lifecycle.ts | 442 +++---- packages/opencode/src/cli/cmd/run/runtime.ts | 1146 ++++++++--------- .../opencode/src/control-plane/workspace.ts | 16 +- packages/opencode/src/format/index.ts | 12 +- packages/opencode/src/index.ts | 1 - packages/opencode/src/lsp/server.ts | 9 - packages/opencode/src/mcp/index.ts | 2 - packages/opencode/src/mcp/oauth-callback.ts | 2 - packages/opencode/src/mcp/oauth-provider.ts | 1 - packages/opencode/src/plugin/digitalocean.ts | 1 - .../src/plugin/github-copilot/copilot.ts | 1 - packages/opencode/src/plugin/index.ts | 13 +- packages/opencode/src/plugin/openai/codex.ts | 4 +- .../opencode/src/plugin/openai/ws-pool.ts | 1 - packages/opencode/src/plugin/xai.ts | 1 - packages/opencode/src/provider/provider.ts | 5 +- packages/opencode/src/share/share-next.ts | 14 +- packages/opencode/src/skill/discovery.ts | 7 +- packages/opencode/src/skill/index.ts | 10 +- packages/opencode/src/snapshot/index.ts | 4 +- 24 files changed, 848 insertions(+), 876 deletions(-) diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index 32f8b22d8..5968f58aa 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -227,9 +227,7 @@ export const layer = Layer.effect( yield* events.publish(Event.Refreshed, {}) }), ).pipe( - Effect.tapCause((cause) => - Effect.logError("Failed to fetch models.dev", { cause: cause }), - ), + Effect.tapCause((cause) => Effect.logError("Failed to fetch models.dev", { cause: cause })), Effect.ignore, ) }) diff --git a/packages/core/src/observability/logging.ts b/packages/core/src/observability/logging.ts index 4a9553467..9593af31e 100644 --- a/packages/core/src/observability/logging.ts +++ b/packages/core/src/observability/logging.ts @@ -20,7 +20,11 @@ function formatter(id: string = runID) { }) } -function flatten(input: Record, prefix = "", seen = new WeakSet()): Array { +function flatten( + input: Record, + prefix = "", + seen = new WeakSet(), +): Array { if (seen.has(input)) return [[prefix, "[Circular]"]] seen.add(input) const entries = Object.entries(input) diff --git a/packages/core/src/project-reference.ts b/packages/core/src/project-reference.ts index 21d3003a4..1d1001e43 100644 --- a/packages/core/src/project-reference.ts +++ b/packages/core/src/project-reference.ts @@ -103,7 +103,11 @@ export const layer = Layer.effect( (materializer) => materializer.run.pipe( Effect.catchCause((cause) => - Effect.logWarning("failed to materialize project reference", { name: materializer.name, repository: materializer.repository, cause }), + Effect.logWarning("failed to materialize project reference", { + name: materializer.name, + repository: materializer.repository, + cause, + }), ), ), { concurrency: 4, discard: true }, diff --git a/packages/opencode/src/acp/usage.ts b/packages/opencode/src/acp/usage.ts index 59be7f5d3..be64114bb 100644 --- a/packages/opencode/src/acp/usage.ts +++ b/packages/opencode/src/acp/usage.ts @@ -154,7 +154,9 @@ export const layer = Layer.effect( contextLimitLoader.providers(input.directory).pipe( Effect.map((providers) => findContextLimit(providers, input.providerID, input.modelID)), Effect.catch((error) => - Effect.logError("failed to get providers for usage context limit", { error: error }).pipe(Effect.as(undefined)), + Effect.logError("failed to get providers for usage context limit", { error: error }).pipe( + Effect.as(undefined), + ), ), ), ) @@ -176,11 +178,13 @@ export const layer = Layer.effect( readonly sessionID: string readonly directory: string }) { - const messages = yield* messageLoader.messages({ sessionID: input.sessionID, directory: input.directory }).pipe( - Effect.catch((error) => - Effect.logError("failed to fetch messages for usage update", { error: error }).pipe(Effect.as(undefined)), - ), - ) + const messages = yield* messageLoader + .messages({ sessionID: input.sessionID, directory: input.directory }) + .pipe( + Effect.catch((error) => + Effect.logError("failed to fetch messages for usage update", { error: error }).pipe(Effect.as(undefined)), + ), + ) if (!messages) return const message = latestAssistantMessage(messages) diff --git a/packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts b/packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts index 457029d15..4644d3d03 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts @@ -174,233 +174,233 @@ function queueSplash( // scrollback commits and footer repaints happen in the same frame. After // the entry splash, RunFooter takes over the footer region. export async function createRuntimeLifecycle(input: LifecycleInput): Promise { - const source = resolveInteractiveStdin() - let unregisterKeymap: (() => void) | undefined + const source = resolveInteractiveStdin() + let unregisterKeymap: (() => void) | undefined + + try { + const renderer = await createCliRenderer({ + stdin: source.stdin, + targetFps: 30, + maxFps: 60, + useMouse: false, + autoFocus: false, + openConsoleOnError: false, + exitOnCtrlC: false, + useKittyKeyboard: { events: process.platform === "win32" }, + screenMode: "split-footer", + footerHeight: FOOTER_HEIGHT, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + clearOnShutdown: false, + }) + const theme = await resolveRunTheme(renderer) + renderer.setBackgroundColor(theme.background) + const keymap = createDefaultOpenTuiKeymap(renderer) + unregisterKeymap = registerOpencodeKeymap(keymap, renderer, input.tuiConfig) + const state: SplashState = { + entry: false, + exit: false, + } + const splash = splashInfo(input.sessionTitle, input.history) + const meta = splashMeta({ + title: splash.title, + session_id: input.sessionID, + }) + const labels = footerLabels({ + agent: input.agent, + model: input.model, + variant: input.variant, + }) + const footerTask = import("./footer") + const wrote = queueSplash( + renderer, + state, + "entry", + entrySplash({ + ...meta, + theme: theme.splash, + showSession: splash.showSession, + detail: directoryLabel(input.directory), + }), + ) + await renderer.idle().catch(() => {}) + + const { RunFooter } = await footerTask + let closed = false + let sigintRegistered = false + + const footer = new RunFooter(renderer, { + directory: input.directory, + findFiles: input.findFiles, + agents: input.agents, + resources: input.resources, + sessionID: input.getSessionID ?? (() => input.sessionID), + ...labels, + model: input.model, + variant: input.variant, + first: input.first, + history: input.history, + theme, + wrote, + keymap, + tuiConfig: input.tuiConfig, + backgroundSubagents: input.backgroundSubagents, + diffStyle: input.tuiConfig.diff_style ?? "auto", + onPermissionReply: input.onPermissionReply, + onQuestionReply: input.onQuestionReply, + onQuestionReject: input.onQuestionReject, + onCycleVariant: input.onCycleVariant, + onModelSelect: input.onModelSelect, + onVariantSelect: input.onVariantSelect, + onInterrupt: input.onInterrupt, + onBackground: input.onBackground, + onEditorOpen: async ({ value }) => { + if (closed || renderer.isDestroyed) { + return + } + + await renderer.idle().catch(() => {}) + const ignore = () => {} + detachSigint() + process.on("SIGINT", ignore) + try { + return await openEditor({ + value, + cwd: input.directory, + renderer, + stdin: source.stdin, + }) + } finally { + process.off("SIGINT", ignore) + attachSigint() + } + }, + onSubagentSelect: input.onSubagentSelect, + }) + + const sigint = () => { + footer.requestExit() + } + + const attachSigint = () => { + if (closed || sigintRegistered) { + return + } + + process.on("SIGINT", sigint) + sigintRegistered = true + } + + const detachSigint = () => { + if (!sigintRegistered) { + return + } + + process.off("SIGINT", sigint) + sigintRegistered = false + } + + attachSigint() + + const close = async (next: { + showExit: boolean + sessionTitle?: string + sessionID?: string + history?: RunPrompt[] + }) => { + if (closed) { + return + } + + closed = true + detachSigint() + let wroteExit = false try { - const renderer = await createCliRenderer({ - stdin: source.stdin, - targetFps: 30, - maxFps: 60, - useMouse: false, - autoFocus: false, - openConsoleOnError: false, - exitOnCtrlC: false, - useKittyKeyboard: { events: process.platform === "win32" }, - screenMode: "split-footer", - footerHeight: FOOTER_HEIGHT, - externalOutputMode: "capture-stdout", - consoleMode: "disabled", - clearOnShutdown: false, - }) - const theme = await resolveRunTheme(renderer) - renderer.setBackgroundColor(theme.background) - const keymap = createDefaultOpenTuiKeymap(renderer) - unregisterKeymap = registerOpencodeKeymap(keymap, renderer, input.tuiConfig) - const state: SplashState = { - entry: false, - exit: false, + await footer.idle().catch(() => {}) + + const show = renderer.isDestroyed ? false : next.showExit + if (!renderer.isDestroyed && show) { + const sessionID = next.sessionID || input.getSessionID?.() || input.sessionID + const splash = splashInfo(next.sessionTitle ?? input.sessionTitle, next.history ?? input.history) + wroteExit = queueSplash( + renderer, + state, + "exit", + exitSplash({ + ...splashMeta({ + title: splash.title, + session_id: sessionID, + }), + theme: footer.currentTheme().splash, + }), + ) + await renderer.idle().catch(() => {}) } - const splash = splashInfo(input.sessionTitle, input.history) - const meta = splashMeta({ - title: splash.title, - session_id: input.sessionID, - }) - const labels = footerLabels({ - agent: input.agent, - model: input.model, - variant: input.variant, - }) - const footerTask = import("./footer") - const wrote = queueSplash( - renderer, - state, - "entry", + } finally { + footer.close() + await footer.idle().catch(() => {}) + footer.destroy() + unregisterKeymap?.() + shutdown(renderer) + if (!wroteExit) { + process.stdout.write("\n") + } + source.cleanup?.() + } + } + + return { + footer, + refreshTheme() { + footer.refreshTheme() + }, + onResize(fn) { + let width = renderer.terminalWidth + let height = renderer.terminalHeight + const resize = () => { + if (width === renderer.terminalWidth && height === renderer.terminalHeight) { + return + } + + width = renderer.terminalWidth + height = renderer.terminalHeight + fn() + } + renderer.on(CliRenderEvents.RESIZE, resize) + return () => renderer.off(CliRenderEvents.RESIZE, resize) + }, + async resetForReplay(next) { + if (closed || renderer.isDestroyed || footer.isClosed) { + throw new Error("runtime closed") + } + + await footer.idle() + if (closed || renderer.isDestroyed || footer.isClosed) { + throw new Error("runtime closed") + } + + footer.resetForReplay(true) + renderer.resetSplitFooterForReplay({ clearSavedLines: true }) + const splash = splashInfo(next.sessionTitle ?? input.sessionTitle, next.history) + renderer.writeToScrollback( entrySplash({ - ...meta, - theme: theme.splash, + ...splashMeta({ + title: splash.title, + session_id: next.sessionID ?? input.getSessionID?.() ?? input.sessionID, + }), + theme: footer.currentTheme().splash, showSession: splash.showSession, detail: directoryLabel(input.directory), }), ) - await renderer.idle().catch(() => {}) - - const { RunFooter } = await footerTask - let closed = false - let sigintRegistered = false - - const footer = new RunFooter(renderer, { - directory: input.directory, - findFiles: input.findFiles, - agents: input.agents, - resources: input.resources, - sessionID: input.getSessionID ?? (() => input.sessionID), - ...labels, - model: input.model, - variant: input.variant, - first: input.first, - history: input.history, - theme, - wrote, - keymap, - tuiConfig: input.tuiConfig, - backgroundSubagents: input.backgroundSubagents, - diffStyle: input.tuiConfig.diff_style ?? "auto", - onPermissionReply: input.onPermissionReply, - onQuestionReply: input.onQuestionReply, - onQuestionReject: input.onQuestionReject, - onCycleVariant: input.onCycleVariant, - onModelSelect: input.onModelSelect, - onVariantSelect: input.onVariantSelect, - onInterrupt: input.onInterrupt, - onBackground: input.onBackground, - onEditorOpen: async ({ value }) => { - if (closed || renderer.isDestroyed) { - return - } - - await renderer.idle().catch(() => {}) - const ignore = () => {} - detachSigint() - process.on("SIGINT", ignore) - try { - return await openEditor({ - value, - cwd: input.directory, - renderer, - stdin: source.stdin, - }) - } finally { - process.off("SIGINT", ignore) - attachSigint() - } - }, - onSubagentSelect: input.onSubagentSelect, - }) - - const sigint = () => { - footer.requestExit() - } - - const attachSigint = () => { - if (closed || sigintRegistered) { - return - } - - process.on("SIGINT", sigint) - sigintRegistered = true - } - - const detachSigint = () => { - if (!sigintRegistered) { - return - } - - process.off("SIGINT", sigint) - sigintRegistered = false - } - - attachSigint() - - const close = async (next: { - showExit: boolean - sessionTitle?: string - sessionID?: string - history?: RunPrompt[] - }) => { - if (closed) { - return - } - - closed = true - detachSigint() - let wroteExit = false - - try { - await footer.idle().catch(() => {}) - - const show = renderer.isDestroyed ? false : next.showExit - if (!renderer.isDestroyed && show) { - const sessionID = next.sessionID || input.getSessionID?.() || input.sessionID - const splash = splashInfo(next.sessionTitle ?? input.sessionTitle, next.history ?? input.history) - wroteExit = queueSplash( - renderer, - state, - "exit", - exitSplash({ - ...splashMeta({ - title: splash.title, - session_id: sessionID, - }), - theme: footer.currentTheme().splash, - }), - ) - await renderer.idle().catch(() => {}) - } - } finally { - footer.close() - await footer.idle().catch(() => {}) - footer.destroy() - unregisterKeymap?.() - shutdown(renderer) - if (!wroteExit) { - process.stdout.write("\n") - } - source.cleanup?.() - } - } - - return { - footer, - refreshTheme() { - footer.refreshTheme() - }, - onResize(fn) { - let width = renderer.terminalWidth - let height = renderer.terminalHeight - const resize = () => { - if (width === renderer.terminalWidth && height === renderer.terminalHeight) { - return - } - - width = renderer.terminalWidth - height = renderer.terminalHeight - fn() - } - renderer.on(CliRenderEvents.RESIZE, resize) - return () => renderer.off(CliRenderEvents.RESIZE, resize) - }, - async resetForReplay(next) { - if (closed || renderer.isDestroyed || footer.isClosed) { - throw new Error("runtime closed") - } - - await footer.idle() - if (closed || renderer.isDestroyed || footer.isClosed) { - throw new Error("runtime closed") - } - - footer.resetForReplay(true) - renderer.resetSplitFooterForReplay({ clearSavedLines: true }) - const splash = splashInfo(next.sessionTitle ?? input.sessionTitle, next.history) - renderer.writeToScrollback( - entrySplash({ - ...splashMeta({ - title: splash.title, - session_id: next.sessionID ?? input.getSessionID?.() ?? input.sessionID, - }), - theme: footer.currentTheme().splash, - showSession: splash.showSession, - detail: directoryLabel(input.directory), - }), - ) - renderer.requestRender() - }, - close, - } - } catch (error) { - unregisterKeymap?.() - source.cleanup?.() - throw error - } + renderer.requestRender() + }, + close, + } + } catch (error) { + unregisterKeymap?.() + source.cleanup?.() + throw error + } } diff --git a/packages/opencode/src/cli/cmd/run/runtime.ts b/packages/opencode/src/cli/cmd/run/runtime.ts index b22b153fc..65cd15f1a 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.ts @@ -179,608 +179,608 @@ async function resolveExitTitle( // Files only attach on the first prompt turn -- after that, includeFiles // flips to false so subsequent turns don't re-send attachments. async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDeps = {}): Promise { - const start = performance.now() - const log = trace() - const tuiConfigTask = resolveRunTuiConfig() - const ctx = await input.boot() - const modelTask = resolveModelInfo(ctx.sdk, ctx.directory, ctx.model) - const sessionTask = - ctx.resume === true - ? resolveSessionInfo(ctx.sdk, ctx.sessionID, ctx.model) - : Promise.resolve({ - first: true, - history: [], - variant: undefined, - }) - const savedTask = resolveSavedVariant(ctx.model) - const [tuiConfig, session, savedVariant] = await Promise.all([tuiConfigTask, sessionTask, savedTask]) - const state: RuntimeState = { - shown: !session.first, - aborting: false, - model: ctx.model, - providers: [], - variants: [], - limits: {}, - activeVariant: resolveVariant(ctx.variant, session.variant, savedVariant, []), - sessionID: ctx.sessionID, - history: [...session.history], - localRows: [], - sessionTitle: ctx.sessionTitle, - agent: ctx.agent, - } - const ensureSession = () => { - if (!input.resolveSession || state.sessionID) { - return Promise.resolve() - } - - if (state.session) { - return state.session - } - - state.session = input.resolveSession(ctx).then((next) => { - state.sessionID = next.sessionID - state.sessionTitle = next.sessionTitle ?? state.sessionTitle - state.agent = next.agent + const start = performance.now() + const log = trace() + const tuiConfigTask = resolveRunTuiConfig() + const ctx = await input.boot() + const modelTask = resolveModelInfo(ctx.sdk, ctx.directory, ctx.model) + const sessionTask = + ctx.resume === true + ? resolveSessionInfo(ctx.sdk, ctx.sessionID, ctx.model) + : Promise.resolve({ + first: true, + history: [], + variant: undefined, }) - return state.session + const savedTask = resolveSavedVariant(ctx.model) + const [tuiConfig, session, savedVariant] = await Promise.all([tuiConfigTask, sessionTask, savedTask]) + const state: RuntimeState = { + shown: !session.first, + aborting: false, + model: ctx.model, + providers: [], + variants: [], + limits: {}, + activeVariant: resolveVariant(ctx.variant, session.variant, savedVariant, []), + sessionID: ctx.sessionID, + history: [...session.history], + localRows: [], + sessionTitle: ctx.sessionTitle, + agent: ctx.agent, + } + const ensureSession = () => { + if (!input.resolveSession || state.sessionID) { + return Promise.resolve() + } + + if (state.session) { + return state.session + } + + state.session = input.resolveSession(ctx).then((next) => { + state.sessionID = next.sessionID + state.sessionTitle = next.sessionTitle ?? state.sessionTitle + state.agent = next.agent + }) + return state.session + } + + const shell = await (deps.createRuntimeLifecycle ?? createRuntimeLifecycle)({ + directory: ctx.directory, + findFiles: (query) => + ctx.sdk.find + .files({ query, directory: ctx.directory }) + .then((x) => x.data ?? []) + .catch(() => []), + agents: [], + resources: [], + sessionID: state.sessionID, + sessionTitle: state.sessionTitle, + getSessionID: () => state.sessionID, + first: session.first, + history: session.history, + agent: state.agent, + model: state.model, + variant: state.activeVariant, + tuiConfig, + backgroundSubagents: input.backgroundSubagents, + onPermissionReply: async (next) => { + if (state.demo?.permission(next)) { + return } - const shell = await (deps.createRuntimeLifecycle ?? createRuntimeLifecycle)({ - directory: ctx.directory, - findFiles: (query) => - ctx.sdk.find - .files({ query, directory: ctx.directory }) - .then((x) => x.data ?? []) - .catch(() => []), - agents: [], - resources: [], - sessionID: state.sessionID, - sessionTitle: state.sessionTitle, - getSessionID: () => state.sessionID, - first: session.first, - history: session.history, - agent: state.agent, - model: state.model, + log?.write("send.permission.reply", next) + await ctx.sdk.permission.reply(next) + }, + onQuestionReply: async (next) => { + if (state.demo?.questionReply(next)) { + return + } + + await ctx.sdk.question.reply(next) + }, + onQuestionReject: async (next) => { + if (state.demo?.questionReject(next)) { + return + } + + await ctx.sdk.question.reject(next) + }, + onCycleVariant: () => { + if (!state.model || state.variants.length === 0) { + return { + status: "no variants available", + } + } + + state.activeVariant = cycleVariant(state.activeVariant, state.variants) + saveVariant(state.model, state.activeVariant) + return { + status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", + modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers), variant: state.activeVariant, - tuiConfig, - backgroundSubagents: input.backgroundSubagents, - onPermissionReply: async (next) => { - if (state.demo?.permission(next)) { - return - } + } + }, + onModelSelect: async (model) => { + if (state.model?.providerID === model.providerID && state.model.modelID === model.modelID) { + return + } - log?.write("send.permission.reply", next) - await ctx.sdk.permission.reply(next) - }, - onQuestionReply: async (next) => { - if (state.demo?.questionReply(next)) { - return - } + state.model = model + state.activeVariant = undefined + state.variants = variantsFor(state.providers, model) + const switching = resolveSavedVariant(model).then((saved) => { + const current = state.model + if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { + return + } - await ctx.sdk.question.reply(next) - }, - onQuestionReject: async (next) => { - if (state.demo?.questionReject(next)) { - return - } - - await ctx.sdk.question.reject(next) - }, - onCycleVariant: () => { - if (!state.model || state.variants.length === 0) { - return { - status: "no variants available", - } - } - - state.activeVariant = cycleVariant(state.activeVariant, state.variants) - saveVariant(state.model, state.activeVariant) - return { - status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", - modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers), - variant: state.activeVariant, - } - }, - onModelSelect: async (model) => { - if (state.model?.providerID === model.providerID && state.model.modelID === model.modelID) { - return - } - - state.model = model - state.activeVariant = undefined - state.variants = variantsFor(state.providers, model) - const switching = resolveSavedVariant(model).then((saved) => { - const current = state.model - if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { - return - } - - state.activeVariant = resolveVariant(ctx.variant, undefined, saved, state.variants) - }) - state.switching = switching - await switching - if (state.switching === switching) { - state.switching = undefined - } - - const current = state.model - if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { - return - } - - return { - modelLabel: formatModelLabel(model, state.activeVariant, state.providers), - status: `model ${model.modelID}`, - variant: state.activeVariant, - variants: state.variants, - } - }, - onVariantSelect: async (variant) => { - if (!state.model || state.variants.length === 0) { - return { - status: "no variants available", - } - } - - if (variant && !state.variants.includes(variant)) { - return { - status: `variant ${variant} unavailable`, - } - } - - state.activeVariant = variant - saveVariant(state.model, state.activeVariant) - return { - status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", - modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers), - variant: state.activeVariant, - variants: state.variants, - } - }, - onInterrupt: () => { - if (!hasSession(input, state) || state.aborting) { - return - } - - state.aborting = true - void ctx.sdk.session - .abort({ - sessionID: state.sessionID, - }) - .catch(() => {}) - .finally(() => { - state.aborting = false - }) - }, - onBackground: () => { - if (!hasSession(input, state)) return - void ctx.sdk.experimental.session.background({ sessionID: state.sessionID }).catch(() => {}) - }, - onSubagentSelect: (sessionID) => { - state.selectSubagent?.(sessionID) - log?.write("subagent.select", { - sessionID, - }) - }, + state.activeVariant = resolveVariant(ctx.variant, undefined, saved, state.variants) }) - const footer = shell.footer - const rememberLocal = (commit: StreamCommit, after?: LocalReplayAnchor) => { - state.localRows = [...state.localRows, { commit, after }].slice(-LOCAL_REPLAY_ROW_LIMIT) + state.switching = switching + await switching + if (state.switching === switching) { + state.switching = undefined } - const loadCatalog = async (): Promise => { - if (footer.isClosed) { - return + const current = state.model + if (!current || current.providerID !== model.providerID || current.modelID !== model.modelID) { + return + } + + return { + modelLabel: formatModelLabel(model, state.activeVariant, state.providers), + status: `model ${model.modelID}`, + variant: state.activeVariant, + variants: state.variants, + } + }, + onVariantSelect: async (variant) => { + if (!state.model || state.variants.length === 0) { + return { + status: "no variants available", } + } - const [agents, resources, commands] = await Promise.all([ - ctx.sdk.app - .agents({ directory: ctx.directory }) - .then((x) => x.data ?? []) - .catch(() => []), - ctx.sdk.experimental.resource - .list({ directory: ctx.directory }) - .then((x) => Object.values(x.data ?? {})) - .catch(() => []), - ctx.sdk.command - .list({ directory: ctx.directory }) - .then((x) => x.data ?? []) - .catch(() => []), - ]) - if (footer.isClosed) { - return + if (variant && !state.variants.includes(variant)) { + return { + status: `variant ${variant} unavailable`, } - - footer.event({ - type: "catalog", - agents, - resources, - commands, - }) } - void footer - .idle() - .then(loadCatalog) - .catch(() => {}) - - if (Flag.OPENCODE_SHOW_TTFD) { - footer.append({ - kind: "system", - text: `startup ${Math.max(0, Math.round(performance.now() - start))}ms`, - phase: "final", - source: "system", - }) + state.activeVariant = variant + saveVariant(state.model, state.activeVariant) + return { + status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default", + modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers), + variant: state.activeVariant, + variants: state.variants, + } + }, + onInterrupt: () => { + if (!hasSession(input, state) || state.aborting) { + return } - if (input.demo) { - await ensureSession() - state.demo = createRunDemo({ - footer, + state.aborting = true + void ctx.sdk.session + .abort({ sessionID: state.sessionID, - thinking: input.thinking, - limits: () => state.limits, }) - } - - if (input.afterPaint) { - void Promise.resolve(input.afterPaint(ctx)).catch(() => {}) - } - - void modelTask.then((info) => { - state.providers = info.providers - state.variants = variantsFor(state.providers, state.model) - state.limits = info.limits - - const next = resolveVariant(ctx.variant, session.variant, savedVariant, state.variants) - if (next !== state.activeVariant) { - state.activeVariant = next - } - - if (footer.isClosed) { - return - } - - footer.event({ type: "models", providers: info.providers }) - footer.event({ type: "variants", variants: state.variants, current: state.activeVariant }) - if (!state.model) { - return - } - - footer.event({ - type: "model", - model: formatModelLabel(state.model, state.activeVariant, state.providers), + .catch(() => {}) + .finally(() => { + state.aborting = false }) + }, + onBackground: () => { + if (!hasSession(input, state)) return + void ctx.sdk.experimental.session.background({ sessionID: state.sessionID }).catch(() => {}) + }, + onSubagentSelect: (sessionID) => { + state.selectSubagent?.(sessionID) + log?.write("subagent.select", { + sessionID, }) + }, + }) + const footer = shell.footer + const rememberLocal = (commit: StreamCommit, after?: LocalReplayAnchor) => { + state.localRows = [...state.localRows, { commit, after }].slice(-LOCAL_REPLAY_ROW_LIMIT) + } - const streamTask = deps.streamTransport ?? import("./stream.transport") - const ensureStream = () => { - if (state.stream) { - return state.stream - } + const loadCatalog = async (): Promise => { + if (footer.isClosed) { + return + } - // Share eager prewarm and first-turn boot through one in-flight promise, - // but clear it if transport creation fails so a later prompt can retry. - const next = (async () => { - await ensureSession() - if (footer.isClosed) { - throw new Error("runtime closed") - } + const [agents, resources, commands] = await Promise.all([ + ctx.sdk.app + .agents({ directory: ctx.directory }) + .then((x) => x.data ?? []) + .catch(() => []), + ctx.sdk.experimental.resource + .list({ directory: ctx.directory }) + .then((x) => Object.values(x.data ?? {})) + .catch(() => []), + ctx.sdk.command + .list({ directory: ctx.directory }) + .then((x) => x.data ?? []) + .catch(() => []), + ]) + if (footer.isClosed) { + return + } - const mod = await streamTask - if (footer.isClosed) { - throw new Error("runtime closed") - } + footer.event({ + type: "catalog", + agents, + resources, + commands, + }) + } - const handle = await mod.createSessionTransport({ - sdk: ctx.sdk, - directory: ctx.directory, - sessionID: state.sessionID, - thinking: input.thinking, - replay: input.replay, - replayLimit: input.replayLimit, - limits: () => state.limits, - providers: () => state.providers, - footer, - trace: log, - }) - if (footer.isClosed) { - await handle.close() - throw new Error("runtime closed") - } + void footer + .idle() + .then(loadCatalog) + .catch(() => {}) - state.selectSubagent = (sessionID) => handle.selectSubagent(sessionID) - return { mod, handle } - })() - state.stream = next - void next.catch(() => { - if (state.stream === next) { - state.stream = undefined - } - }) - return next + if (Flag.OPENCODE_SHOW_TTFD) { + footer.append({ + kind: "system", + text: `startup ${Math.max(0, Math.round(performance.now() - start))}ms`, + phase: "final", + source: "system", + }) + } + + if (input.demo) { + await ensureSession() + state.demo = createRunDemo({ + footer, + sessionID: state.sessionID, + thinking: input.thinking, + limits: () => state.limits, + }) + } + + if (input.afterPaint) { + void Promise.resolve(input.afterPaint(ctx)).catch(() => {}) + } + + void modelTask.then((info) => { + state.providers = info.providers + state.variants = variantsFor(state.providers, state.model) + state.limits = info.limits + + const next = resolveVariant(ctx.variant, session.variant, savedVariant, state.variants) + if (next !== state.activeVariant) { + state.activeVariant = next + } + + if (footer.isClosed) { + return + } + + footer.event({ type: "models", providers: info.providers }) + footer.event({ type: "variants", variants: state.variants, current: state.activeVariant }) + if (!state.model) { + return + } + + footer.event({ + type: "model", + model: formatModelLabel(state.model, state.activeVariant, state.providers), + }) + }) + + const streamTask = deps.streamTransport ?? import("./stream.transport") + const ensureStream = () => { + if (state.stream) { + return state.stream + } + + // Share eager prewarm and first-turn boot through one in-flight promise, + // but clear it if transport creation fails so a later prompt can retry. + const next = (async () => { + await ensureSession() + if (footer.isClosed) { + throw new Error("runtime closed") } - let resizeTimer: ReturnType | undefined - const offResize = shell.onResize(() => { - if (resizeTimer) { - clearTimeout(resizeTimer) - } + const mod = await streamTask + if (footer.isClosed) { + throw new Error("runtime closed") + } - resizeTimer = setTimeout(() => { - resizeTimer = undefined - if (footer.isClosed) { - return - } + const handle = await mod.createSessionTransport({ + sdk: ctx.sdk, + directory: ctx.directory, + sessionID: state.sessionID, + thinking: input.thinking, + replay: input.replay, + replayLimit: input.replayLimit, + limits: () => state.limits, + providers: () => state.providers, + footer, + trace: log, + }) + if (footer.isClosed) { + await handle.close() + throw new Error("runtime closed") + } - shell.refreshTheme() - if (!input.replay || !state.stream) { - return - } + state.selectSubagent = (sessionID) => handle.selectSubagent(sessionID) + return { mod, handle } + })() + state.stream = next + void next.catch(() => { + if (state.stream === next) { + state.stream = undefined + } + }) + return next + } - void state.stream - .then((item) => - item.handle.replayOnResize({ - localRows: () => state.localRows, - reset: () => - shell.resetForReplay({ - sessionTitle: state.sessionTitle, - sessionID: state.sessionID, - history: state.history, - }), + let resizeTimer: ReturnType | undefined + const offResize = shell.onResize(() => { + if (resizeTimer) { + clearTimeout(resizeTimer) + } + + resizeTimer = setTimeout(() => { + resizeTimer = undefined + if (footer.isClosed) { + return + } + + shell.refreshTheme() + if (!input.replay || !state.stream) { + return + } + + void state.stream + .then((item) => + item.handle.replayOnResize({ + localRows: () => state.localRows, + reset: () => + shell.resetForReplay({ + sessionTitle: state.sessionTitle, + sessionID: state.sessionID, + history: state.history, }), - ) - .catch(() => {}) - }, RESIZE_DELAY) - }) + }), + ) + .catch(() => {}) + }, RESIZE_DELAY) + }) - const runQueue = async () => { - let includeFiles = true - if (state.demo) { - await state.demo.start() + const runQueue = async () => { + let includeFiles = true + if (state.demo) { + await state.demo.start() + } + + const mod = await import("./runtime.queue") + const createSession = input.createSession + await mod.runPromptQueue({ + footer, + initialInput: input.initialInput, + trace: log, + onSend: (prompt) => { + state.shown = true + state.history.push(prompt) + if (prompt.mode !== "shell") { + rememberLocal({ + kind: "user", + text: prompt.text, + phase: "start", + source: "system", + messageID: prompt.messageID, + }) } - - const mod = await import("./runtime.queue") - const createSession = input.createSession - await mod.runPromptQueue({ - footer, - initialInput: input.initialInput, - trace: log, - onSend: (prompt) => { - state.shown = true - state.history.push(prompt) - if (prompt.mode !== "shell") { - rememberLocal({ - kind: "user", - text: prompt.text, + }, + onNewSession: createSession + ? async () => { + try { + await state.switching?.catch(() => {}) + const created = await createSession(ctx, { + agent: state.agent, + model: state.model, + variant: state.activeVariant, + }) + await footer.idle().catch(() => {}) + await state.stream?.then((item) => item.handle.close()).catch(() => {}) + state.stream = undefined + state.session = undefined + state.selectSubagent = undefined + state.shown = false + state.sessionID = created.sessionID + state.sessionTitle = created.sessionTitle + state.agent = created.agent ?? state.agent + state.history = [] + state.localRows = [] + includeFiles = true + state.demo = input.demo + ? createRunDemo({ + footer, + sessionID: state.sessionID, + thinking: input.thinking, + limits: () => state.limits, + }) + : undefined + log?.write("session.new", { + sessionID: state.sessionID, + }) + footer.event({ + type: "stream.subagent", + state: { + tabs: [], + details: {}, + permissions: [], + questions: [], + }, + }) + footer.event({ type: "stream.view", view: { type: "prompt" } }) + footer.event({ + type: "stream.patch", + patch: { + phase: "idle", + duration: "", + usage: "", + first: true, + }, + }) + footer.append({ + kind: "system", + text: `new session ${state.sessionID}`, + phase: "final", + source: "system", + }) + await state.demo?.start() + } catch (error) { + footer.event({ + type: "stream.patch", + patch: { + phase: "idle", + status: "failed to start new session", + }, + }) + const commit = { + kind: "error", + text: error instanceof Error ? error.message : String(error), phase: "start", source: "system", - messageID: prompt.messageID, - }) + messageID: MessageID.ascending(), + } as const + rememberLocal(commit) + footer.append(commit) } - }, - onNewSession: createSession - ? async () => { - try { - await state.switching?.catch(() => {}) - const created = await createSession(ctx, { - agent: state.agent, - model: state.model, - variant: state.activeVariant, - }) - await footer.idle().catch(() => {}) - await state.stream?.then((item) => item.handle.close()).catch(() => {}) - state.stream = undefined - state.session = undefined - state.selectSubagent = undefined - state.shown = false - state.sessionID = created.sessionID - state.sessionTitle = created.sessionTitle - state.agent = created.agent ?? state.agent - state.history = [] - state.localRows = [] - includeFiles = true - state.demo = input.demo - ? createRunDemo({ - footer, - sessionID: state.sessionID, - thinking: input.thinking, - limits: () => state.limits, - }) - : undefined - log?.write("session.new", { - sessionID: state.sessionID, - }) - footer.event({ - type: "stream.subagent", - state: { - tabs: [], - details: {}, - permissions: [], - questions: [], - }, - }) - footer.event({ type: "stream.view", view: { type: "prompt" } }) - footer.event({ - type: "stream.patch", - patch: { - phase: "idle", - duration: "", - usage: "", - first: true, - }, - }) - footer.append({ - kind: "system", - text: `new session ${state.sessionID}`, - phase: "final", - source: "system", - }) - await state.demo?.start() - } catch (error) { - footer.event({ - type: "stream.patch", - patch: { - phase: "idle", - status: "failed to start new session", - }, - }) - const commit = { - kind: "error", - text: error instanceof Error ? error.message : String(error), - phase: "start", - source: "system", - messageID: MessageID.ascending(), - } as const - rememberLocal(commit) - footer.append(commit) - } - } - : undefined, - run: async (prompt, signal) => { - if (state.demo && (await state.demo.prompt(prompt, signal))) { - return - } - - await state.switching?.catch(() => {}) - - let outputAnchor: LocalReplayAnchor | undefined - try { - const next = await ensureStream() - await next.handle.runPromptTurn({ - agent: state.agent, - model: state.model, - variant: state.activeVariant, - prompt, - files: input.files, - includeFiles, - onVisibleOutput: (anchor) => { - outputAnchor = anchor - }, - signal, - }) - if (prompt.messageID) { - state.localRows = state.localRows.filter( - (row) => row.commit.kind !== "user" || row.commit.messageID !== prompt.messageID, - ) - } - includeFiles = false - } catch (error) { - if (signal.aborted || footer.isClosed) { - return - } - - const text = - (await state.stream?.then((item) => item.mod).catch(() => undefined))?.formatUnknownError(error) ?? - (error instanceof Error ? error.message : String(error)) - const commit = { - kind: "error", - text, - phase: "start", - source: "system", - messageID: prompt.messageID, - } as const - rememberLocal(commit, outputAnchor) - footer.append(commit) - } - }, - }) - } - - try { - const eager = eagerStream(input, ctx) - if (eager) { - if (input.replay && state.shown) { - // Replay commits immutable scrollback rows, so wait for provider names - // before bootstrapping existing session history. - await modelTask } - - await ensureStream() + : undefined, + run: async (prompt, signal) => { + if (state.demo && (await state.demo.prompt(prompt, signal))) { + return } - if (!eager && input.resolveSession) { - queueMicrotask(() => { - if (footer.isClosed) { - return - } - - void ensureStream().catch(() => {}) - }) - } + await state.switching?.catch(() => {}) + let outputAnchor: LocalReplayAnchor | undefined try { - await runQueue() - } finally { - if (resizeTimer) { - clearTimeout(resizeTimer) + const next = await ensureStream() + await next.handle.runPromptTurn({ + agent: state.agent, + model: state.model, + variant: state.activeVariant, + prompt, + files: input.files, + includeFiles, + onVisibleOutput: (anchor) => { + outputAnchor = anchor + }, + signal, + }) + if (prompt.messageID) { + state.localRows = state.localRows.filter( + (row) => row.commit.kind !== "user" || row.commit.messageID !== prompt.messageID, + ) + } + includeFiles = false + } catch (error) { + if (signal.aborted || footer.isClosed) { + return } - offResize() - await state.stream?.then((item) => item.handle.close()).catch(() => {}) - } - } finally { - const title = await resolveExitTitle(ctx, input, state) - await shell.close({ - showExit: state.shown && hasSession(input, state), - sessionTitle: title, - sessionID: state.sessionID, - history: state.history, - }) + const text = + (await state.stream?.then((item) => item.mod).catch(() => undefined))?.formatUnknownError(error) ?? + (error instanceof Error ? error.message : String(error)) + const commit = { + kind: "error", + text, + phase: "start", + source: "system", + messageID: prompt.messageID, + } as const + rememberLocal(commit, outputAnchor) + footer.append(commit) + } + }, + }) + } + + try { + const eager = eagerStream(input, ctx) + if (eager) { + if (input.replay && state.shown) { + // Replay commits immutable scrollback rows, so wait for provider names + // before bootstrapping existing session history. + await modelTask } + + await ensureStream() + } + + if (!eager && input.resolveSession) { + queueMicrotask(() => { + if (footer.isClosed) { + return + } + + void ensureStream().catch(() => {}) + }) + } + + try { + await runQueue() + } finally { + if (resizeTimer) { + clearTimeout(resizeTimer) + } + offResize() + await state.stream?.then((item) => item.handle.close()).catch(() => {}) + } + } finally { + const title = await resolveExitTitle(ctx, input, state) + + await shell.close({ + showExit: state.shown && hasSession(input, state), + sessionTitle: title, + sessionID: state.sessionID, + history: state.history, + }) + } } // Local in-process mode. Creates an SDK client backed by a direct fetch to // the in-process server, so no external HTTP server is needed. export async function runInteractiveLocalMode(input: RunLocalInput): Promise { - const sdk = createOpencodeClient({ - baseUrl: "http://opencode.internal", - fetch: input.fetch, + const sdk = createOpencodeClient({ + baseUrl: "http://opencode.internal", + fetch: input.fetch, + directory: input.directory, + }) + let session: Promise | undefined + + return runInteractiveRuntime({ + files: input.files, + initialInput: input.initialInput, + thinking: input.thinking, + backgroundSubagents: input.backgroundSubagents, + replay: input.replay, + replayLimit: input.replayLimit, + demo: input.demo, + resolveSession: () => { + if (session) { + return session + } + + session = Promise.all([input.resolveAgent(), input.session(sdk)]).then(([agent, next]) => { + if (!next?.id) { + throw new Error("Session not found") + } + + void input.share(sdk, next.id).catch(() => {}) + return { + sessionID: next.id, + sessionTitle: next.title, + agent, + } + }) + return session + }, + createSession: createSessionResolver(input.createSession), + boot: async () => { + return { + sdk, directory: input.directory, - }) - let session: Promise | undefined - - return runInteractiveRuntime({ - files: input.files, - initialInput: input.initialInput, - thinking: input.thinking, - backgroundSubagents: input.backgroundSubagents, - replay: input.replay, - replayLimit: input.replayLimit, - demo: input.demo, - resolveSession: () => { - if (session) { - return session - } - - session = Promise.all([input.resolveAgent(), input.session(sdk)]).then(([agent, next]) => { - if (!next?.id) { - throw new Error("Session not found") - } - - void input.share(sdk, next.id).catch(() => {}) - return { - sessionID: next.id, - sessionTitle: next.title, - agent, - } - }) - return session - }, - createSession: createSessionResolver(input.createSession), - boot: async () => { - return { - sdk, - directory: input.directory, - sessionID: "", - sessionTitle: undefined, - resume: false, - agent: input.agent, - model: input.model, - variant: input.variant, - } - }, - }) + sessionID: "", + sessionTitle: undefined, + resume: false, + agent: input.agent, + model: input.model, + variant: input.variant, + } + }, + }) } // Attach mode. Uses the caller-provided SDK client directly. @@ -789,26 +789,26 @@ export async function runInteractiveMode( deps?: RunRuntimeDeps, ): Promise { return runInteractiveRuntime( - { - files: input.files, - initialInput: input.initialInput, - thinking: input.thinking, - backgroundSubagents: input.backgroundSubagents, - replay: input.replay, - replayLimit: input.replayLimit, - demo: input.demo, - boot: async () => ({ - sdk: input.sdk, - directory: input.directory, - sessionID: input.sessionID, - sessionTitle: input.sessionTitle, - resume: input.resume, - agent: input.agent, - model: input.model, - variant: input.variant, - }), - createSession: createSessionResolver(input.createSession), - }, - deps, + { + files: input.files, + initialInput: input.initialInput, + thinking: input.thinking, + backgroundSubagents: input.backgroundSubagents, + replay: input.replay, + replayLimit: input.replayLimit, + demo: input.demo, + boot: async () => ({ + sdk: input.sdk, + directory: input.directory, + sessionID: input.sessionID, + sessionTitle: input.sessionTitle, + resume: input.resume, + agent: input.agent, + model: input.model, + variant: input.variant, + }), + createSession: createSessionResolver(input.createSession), + }, + deps, ) } diff --git a/packages/opencode/src/control-plane/workspace.ts b/packages/opencode/src/control-plane/workspace.ts index b689aa888..ed7b16353 100644 --- a/packages/opencode/src/control-plane/workspace.ts +++ b/packages/opencode/src/control-plane/workspace.ts @@ -73,7 +73,6 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { } } - export const CreateInput = Schema.Struct({ id: Schema.optional(WorkspaceV2.ID), type: Info.fields.type, @@ -341,7 +340,6 @@ export const layer = Layer.effect( ) : {} - const response = yield* http.execute( HttpClientRequest.post(route(url, "/sync/history"), { headers: new Headers(headers), @@ -360,7 +358,6 @@ export const layer = Layer.effect( const history = (yield* response.json) as HistoryEvent[] - yield* Effect.forEach( history, (event) => @@ -575,7 +572,6 @@ export const layer = Layer.effect( const sessionWarp = Effect.fn("Workspace.sessionWarp")(function* (input: SessionWarpInput) { return yield* Effect.gen(function* () { - const current = yield* db .select({ workspaceID: SessionTable.workspace_id }) .from(SessionTable) @@ -590,10 +586,7 @@ export const layer = Layer.effect( if (target.type === "remote") { yield* syncHistory(previous, target.url, target.headers).pipe( - Effect.catch((error) => - Effect.sync(() => { - }), - ), + Effect.catch((error) => Effect.sync(() => {})), ) } else { yield* prompt.cancel(input.sessionID) @@ -679,7 +672,6 @@ export const layer = Layer.effect( const batches = Iterable.chunksOf(rows, 10) const total = Iterable.size(batches) - yield* Effect.forEach( batches, (events, i) => @@ -704,7 +696,6 @@ export const layer = Layer.effect( body, }) } - }), { discard: true }, ) @@ -727,7 +718,6 @@ export const layer = Layer.effect( } yield* session.setWorkspace({ sessionID: input.sessionID, workspaceID: input.workspaceID }) - }) }) @@ -827,9 +817,7 @@ export const layer = Layer.effect( Effect.gen(function* () { yield* WorkspaceAdapterRuntime.remove(info) }), - () => - Effect.sync(() => { - }), + () => Effect.sync(() => {}), ) yield* db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run().pipe(Effect.orDie) diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 747991cd3..0b6c0a8dd 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -94,12 +94,12 @@ export const layer = Layer.effect( .pipe( Effect.catch((error) => Effect.logError("failed to format file", { - error: "spawn failed", - command: cmd, - ...item.environment, - file: filepath, - cause: errorMessage(error.cause ?? error), - }).pipe(Effect.as(undefined)), + error: "spawn failed", + command: cmd, + ...item.environment, + file: filepath, + cause: errorMessage(error.cause ?? error), + }).pipe(Effect.as(undefined)), ), ) if (result && result.exitCode !== 0) { diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 3c570227b..13540a73a 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -75,7 +75,6 @@ const cli = yargs(args) process.env.AGENT = "1" process.env.OPENCODE = "1" process.env.OPENCODE_PID = String(process.pid) - }) .usage("") .completion("completion", "generate shell completion script") diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 84564f02f..2be68aa5a 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -206,7 +206,6 @@ export const ESLint: Info = { const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" await Process.run([npmCmd, "install"], { cwd: finalPath }) await Process.run([npmCmd, "run", "compile"], { cwd: finalPath }) - } const proc = spawn("node", [serverPath, "--stdio"], { @@ -572,7 +571,6 @@ export const ElixirLS: Info = { await Process.run(["mix", "deps.get"], { cwd, env }) await Process.run(["mix", "compile"], { cwd, env }) await Process.run(["mix", "elixir_ls.release2", "-o", "release"], { cwd, env }) - } } @@ -676,7 +674,6 @@ export const Zls: Info = { if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - } return { @@ -775,7 +772,6 @@ async function installRoslynLanguageServer(disableLspDownload: boolean) { if (global) { return global } - } async function roslynLanguageServerGlobalPath() { @@ -1062,7 +1058,6 @@ export const Clangd: Info = { await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {}) await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {}) - return { process: spawn(bin, args, { cwd: root, @@ -1507,7 +1502,6 @@ export const LuaLS: Info = { }) if (!ok) return } - } return { @@ -1682,7 +1676,6 @@ export const TerraformLS: Info = { if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - } return { @@ -1768,7 +1761,6 @@ export const TexLab: Info = { if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - } return { @@ -1948,7 +1940,6 @@ export const Tinymist: Info = { if (platform !== "win32") { await fs.chmod(bin, 0o755).catch(() => {}) } - } return { diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index bd3defcd1..3a348abb8 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -439,7 +439,6 @@ export const layer = Layer.effect( return DISABLED_RESULT } - const { client: mcpClient, status } = mcp.type === "remote" ? yield* connectRemote(key, mcp as ConfigMCPV1.Info & { type: "remote" }) @@ -844,7 +843,6 @@ export const layer = Layer.effect( return yield* storeClient(s, mcpName, client, listed, mcpConfig.timeout) } - const callbackPromise = McpOAuthCallback.waitForCallback(result.oauthState, mcpName) yield* Effect.tryPromise(() => open(result.authorizationUrl)).pipe( diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts index 135cd0613..8d3161849 100644 --- a/packages/opencode/src/mcp/oauth-callback.ts +++ b/packages/opencode/src/mcp/oauth-callback.ts @@ -2,7 +2,6 @@ import { createConnection } from "net" import { createServer } from "http" import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider" - // Current callback server configuration (may differ from defaults if custom redirectUri is used) let currentPort = OAUTH_CALLBACK_PORT let currentPath = OAUTH_CALLBACK_PATH @@ -85,7 +84,6 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http"). const error = url.searchParams.get("error") const errorDescription = url.searchParams.get("error_description") - // Enforce state parameter presence if (!state) { const errorMsg = "Missing required state parameter - potential CSRF attack" diff --git a/packages/opencode/src/mcp/oauth-provider.ts b/packages/opencode/src/mcp/oauth-provider.ts index 7eec1971c..aa29777f5 100644 --- a/packages/opencode/src/mcp/oauth-provider.ts +++ b/packages/opencode/src/mcp/oauth-provider.ts @@ -8,7 +8,6 @@ import type { import { Effect } from "effect" import { McpAuth } from "./auth" - const OAUTH_CALLBACK_PORT = 19876 const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback" diff --git a/packages/opencode/src/plugin/digitalocean.ts b/packages/opencode/src/plugin/digitalocean.ts index f77f03f01..9c3557a76 100644 --- a/packages/opencode/src/plugin/digitalocean.ts +++ b/packages/opencode/src/plugin/digitalocean.ts @@ -4,7 +4,6 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version" import { createServer } from "http" import open from "open" - const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82" const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize" const DO_API_BASE = "https://api.digitalocean.com" diff --git a/packages/opencode/src/plugin/github-copilot/copilot.ts b/packages/opencode/src/plugin/github-copilot/copilot.ts index 16e746081..ff7f867ab 100644 --- a/packages/opencode/src/plugin/github-copilot/copilot.ts +++ b/packages/opencode/src/plugin/github-copilot/copilot.ts @@ -6,7 +6,6 @@ import { setTimeout as sleep } from "node:timers/promises" import { CopilotModels } from "./models" import { MessageV2 } from "@/session/message-v2" - const CLIENT_ID = "Ov23li8tweQw6odWQebz" const API_VERSION = "2026-06-01" const UTILITY_MODELS = ["gpt-5.4-nano", "gpt-4.1", "gpt-4o", "gpt-4o-mini"] diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 01b76baec..326b37a36 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -30,7 +30,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags" import { EventV2Bridge } from "@/event-v2-bridge" import { InstallationChannel } from "@opencode-ai/core/installation/version" - type State = { hooks: Hooks[] } @@ -163,8 +162,7 @@ export const layer = Layer.effect( for (const plugin of flags.disableDefaultPlugins ? [] : internalPlugins(flags)) { const init = yield* Effect.tryPromise({ try: () => plugin(input), - catch: (err) => { - }, + catch: (err) => {}, }).pipe(Effect.option) if (init._tag === "Some") hooks.push(init.value) } @@ -179,10 +177,8 @@ export const layer = Layer.effect( items: plugins, kind: "server", report: { - start(candidate) { - }, - missing(candidate, _retry, message) { - }, + start(candidate) {}, + missing(candidate, _retry, message) {}, error(candidate, _retry, stage, error, resolved) { const spec = candidate.plan.spec const cause = error instanceof Error ? (error.cause ?? error) : error @@ -260,8 +256,7 @@ export const layer = Layer.effect( (hook) => Effect.tryPromise({ try: () => Promise.resolve(hook.dispose?.()), - catch: (error) => { - }, + catch: (error) => {}, }).pipe(Effect.ignore), { discard: true }, ), diff --git a/packages/opencode/src/plugin/openai/codex.ts b/packages/opencode/src/plugin/openai/codex.ts index 9431c3a15..b0d20b328 100644 --- a/packages/opencode/src/plugin/openai/codex.ts +++ b/packages/opencode/src/plugin/openai/codex.ts @@ -6,7 +6,6 @@ import { setTimeout as sleep } from "node:timers/promises" import { createServer } from "http" import { OpenAIWebSocketPool } from "./ws-pool" - const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" const ISSUER = "https://auth.openai.com" const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses" @@ -314,8 +313,7 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string } function stopOAuthServer() { if (oauthServer) { - oauthServer.close(() => { - }) + oauthServer.close(() => {}) oauthServer = undefined } } diff --git a/packages/opencode/src/plugin/openai/ws-pool.ts b/packages/opencode/src/plugin/openai/ws-pool.ts index 767b603e7..3cbb29a30 100644 --- a/packages/opencode/src/plugin/openai/ws-pool.ts +++ b/packages/opencode/src/plugin/openai/ws-pool.ts @@ -5,7 +5,6 @@ import { OpenAIWebSocket } from "./ws" export const TITLE_HEADER = "x-opencode-title" - export interface CreateWebSocketFetchOptions { httpFetch?: typeof globalThis.fetch url?: string diff --git a/packages/opencode/src/plugin/xai.ts b/packages/opencode/src/plugin/xai.ts index f64a6fb59..e932396a1 100644 --- a/packages/opencode/src/plugin/xai.ts +++ b/packages/opencode/src/plugin/xai.ts @@ -3,7 +3,6 @@ import { OAUTH_DUMMY_KEY } from "../auth" import { createServer } from "http" import { InstallationVersion } from "@opencode-ai/core/installation/version" - // Public Grok-CLI OAuth client. xAI's auth server rejects loopback OAuth from // non-allowlisted clients, so we reuse the Grok-CLI client_id that xAI ships // for desktop OAuth flows. Source of truth: hermes-agent PR #26534. diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 16900ca11..a02491385 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1306,7 +1306,6 @@ export const layer = Layer.effect( get: (key: string) => env.get(key), } - function mergeProvider(providerID: ProviderV2.ID, provider: Partial) { const existing = providers[providerID] if (existing) { @@ -1540,8 +1539,7 @@ export const layer = Layer.effect( providers[gitlab].models[modelID] = model } } - } catch (e) { - } + } catch (e) {} }) } @@ -1592,7 +1590,6 @@ export const layer = Layer.effect( delete providers[providerID] continue } - } return { diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index 6427e07de..259e0d717 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -138,9 +138,7 @@ export const layer = Layer.effect( s.queue.set(sessionID, next) yield* flush(sessionID).pipe( Effect.delay(1000), - Effect.catchCause((cause) => - Effect.logError("share flush failed", { sessionID: sessionID, cause: cause }), - ), + Effect.catchCause((cause) => Effect.logError("share flush failed", { sessionID: sessionID, cause: cause })), Effect.forkIn(s.scope), ) }) @@ -263,7 +261,11 @@ export const layer = Layer.effect( ) if (res.status >= 400) { - yield* Effect.logWarning("failed to sync share", { sessionID: sessionID, shareID: share.id, status: res.status }) + yield* Effect.logWarning("failed to sync share", { + sessionID: sessionID, + shareID: share.id, + status: res.status, + }) } }) @@ -325,9 +327,7 @@ export const layer = Layer.effect( const s = yield* InstanceState.get(state) s.shared.set(sessionID, result) yield* full(sessionID).pipe( - Effect.catchCause((cause) => - Effect.logError("share full sync failed", { sessionID: sessionID, cause: cause }), - ), + Effect.catchCause((cause) => Effect.logError("share full sync failed", { sessionID: sessionID, cause: cause })), Effect.forkIn(s.scope), ) return result diff --git a/packages/opencode/src/skill/discovery.ts b/packages/opencode/src/skill/discovery.ts index 3e06a7c52..8537d3867 100644 --- a/packages/opencode/src/skill/discovery.ts +++ b/packages/opencode/src/skill/discovery.ts @@ -39,9 +39,7 @@ export const layer: Layer.Layer res.arrayBuffer), Effect.flatMap((body) => fs.writeWithDirs(dest, new Uint8Array(body))), Effect.as(true), - Effect.catch((err) => - Effect.logError("failed to download", { url: url, error: err }).pipe(Effect.as(false)), - ), + Effect.catch((err) => Effect.logError("failed to download", { url: url, error: err }).pipe(Effect.as(false))), ) }) @@ -66,8 +64,7 @@ export const layer: Layer.Layer !skill.files.includes("SKILL.md")) yield* Effect.forEach( missing, - (skill) => - Effect.logWarning("skill entry missing SKILL.md", { url: index, skill: skill.name }), + (skill) => Effect.logWarning("skill entry missing SKILL.md", { url: index, skill: skill.name }), { discard: true }, ) const list = data.skills.filter((skill) => skill.files.includes("SKILL.md")) diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 77ea768e9..14bdf1289 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -122,7 +122,11 @@ const add = Effect.fnUntraced(function* (state: State, match: string, events: Ev if (!isSkillFrontmatter(md.data)) return if (state.skills[md.data.name]) { - yield* Effect.logWarning("duplicate skill name", { name: md.data.name, existing: state.skills[md.data.name].location, duplicate: match }) + yield* Effect.logWarning("duplicate skill name", { + name: md.data.name, + existing: state.skills[md.data.name].location, + duplicate: match, + }) } state.dirs.add(path.dirname(match)) @@ -153,7 +157,9 @@ const scan = Effect.fnUntraced(function* ( }).pipe( Effect.catch((error) => { if (!opts?.scope) return Effect.die(error) - return Effect.logError(`failed to scan ${opts.scope} skills`, { dir: root, error: error }).pipe(Effect.as([] as string[])) + return Effect.logError(`failed to scan ${opts.scope} skills`, { dir: root, error: error }).pipe( + Effect.as([] as string[]), + ) }), ) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 9677e5262..fdb4abf6c 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -564,8 +564,8 @@ export const layer: Layer.Layer