From d8070f7f23f44ee986510532e5fa4cbcee7a6f07 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 15 Apr 2026 11:27:54 -0400 Subject: [PATCH 1/2] refactor(opencode): move tui state io to AppFileSystem --- .../cli/cmd/tui/component/prompt/frecency.tsx | 26 ++++++++++++---- .../cli/cmd/tui/component/prompt/history.tsx | 26 ++++++++++++---- .../cli/cmd/tui/component/prompt/stash.tsx | 30 ++++++++++++++----- .../opencode/src/cli/cmd/tui/context/kv.tsx | 24 ++++++++++++--- .../src/cli/cmd/tui/context/local.tsx | 22 ++++++++++++-- .../src/cli/cmd/tui/routes/session/index.tsx | 17 ++++++++--- .../src/cli/cmd/tui/util/clipboard.ts | 13 ++++++-- .../opencode/src/cli/cmd/tui/util/editor.ts | 22 ++++++++++++-- 8 files changed, 146 insertions(+), 34 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx index 3ea8826ef8b..afb7d5c92a6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx @@ -1,10 +1,12 @@ import path from "path" import { Global } from "@/global" -import { Filesystem } from "@/util/filesystem" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { onMount } from "solid-js" import { createStore } from "solid-js/store" import { createSimpleContext } from "../../context/helper" -import { appendFile, writeFile } from "fs/promises" +import { appendFile } from "fs/promises" function calculateFrecency(entry?: { frequency: number; lastOpen: number }): number { if (!entry) return 0 @@ -19,8 +21,22 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont name: "Frecency", init: () => { const frecencyPath = path.join(Global.Path.state, "frecency.jsonl") + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(frecencyPath) + }), + ) + const write = (content: string) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(frecencyPath, content) + }), + ) onMount(async () => { - const text = await Filesystem.readText(frecencyPath).catch(() => "") + const text = await read().catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -54,7 +70,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont if (sorted.length > 0) { const content = sorted.map((entry) => JSON.stringify(entry)).join("\n") + "\n" - writeFile(frecencyPath, content).catch(() => {}) + write(content).catch(() => {}) } }) @@ -77,7 +93,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont .slice(0, MAX_FRECENCY_ENTRIES) setStore("data", Object.fromEntries(sorted)) const content = sorted.map(([path, entry]) => JSON.stringify({ path, ...entry })).join("\n") + "\n" - writeFile(frecencyPath, content).catch(() => {}) + write(content).catch(() => {}) } } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index d49dd5c7b69..03c5fde5054 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -1,10 +1,12 @@ import path from "path" import { Global } from "@/global" -import { Filesystem } from "@/util/filesystem" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" import { createSimpleContext } from "../../context/helper" -import { appendFile, writeFile } from "fs/promises" +import { appendFile } from "fs/promises" import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2" export type PromptInfo = { @@ -31,8 +33,22 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create name: "PromptHistory", init: () => { const historyPath = path.join(Global.Path.state, "prompt-history.jsonl") + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(historyPath) + }), + ) + const write = (content: string) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(historyPath, content) + }), + ) onMount(async () => { - const text = await Filesystem.readText(historyPath).catch(() => "") + const text = await read().catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -51,7 +67,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create // Rewrite file with only valid entries to self-heal corruption if (lines.length > 0) { const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n" - writeFile(historyPath, content).catch(() => {}) + write(content).catch(() => {}) } }) @@ -97,7 +113,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create if (trimmed) { const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n" - writeFile(historyPath, content).catch(() => {}) + write(content).catch(() => {}) return } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx index ef3eb329a99..93f6e8e9d70 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx @@ -1,10 +1,12 @@ import path from "path" import { Global } from "@/global" -import { Filesystem } from "@/util/filesystem" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" import { createSimpleContext } from "../../context/helper" -import { appendFile, writeFile } from "fs/promises" +import { appendFile } from "fs/promises" import type { PromptInfo } from "./history" export type StashEntry = { @@ -19,8 +21,22 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp name: "PromptStash", init: () => { const stashPath = path.join(Global.Path.state, "prompt-stash.jsonl") + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(stashPath) + }), + ) + const write = (content: string) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(stashPath, content) + }), + ) onMount(async () => { - const text = await Filesystem.readText(stashPath).catch(() => "") + const text = await read().catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -39,7 +55,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp // Rewrite file with only valid entries to self-heal corruption if (lines.length > 0) { const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n" - writeFile(stashPath, content).catch(() => {}) + write(content).catch(() => {}) } }) @@ -66,7 +82,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp if (trimmed) { const content = store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" - writeFile(stashPath, content).catch(() => {}) + write(content).catch(() => {}) return } @@ -82,7 +98,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp ) const content = store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : "" - writeFile(stashPath, content).catch(() => {}) + write(content).catch(() => {}) return entry }, remove(index: number) { @@ -94,7 +110,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp ) const content = store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : "" - writeFile(stashPath, content).catch(() => {}) + write(content).catch(() => {}) }, } }, diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx index 7a52156f882..639da363787 100644 --- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx @@ -1,5 +1,7 @@ import { Global } from "@/global" -import { Filesystem } from "@/util/filesystem" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { createSignal, type Setter } from "solid-js" import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" @@ -11,10 +13,24 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ const [ready, setReady] = createSignal(false) const [store, setStore] = createStore>() const filePath = path.join(Global.Path.state, "kv.json") + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readJson(filePath) + }), + ) + const write = (data: unknown) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeJson(filePath, data) + }), + ) - Filesystem.readJson(filePath) + read() .then((x) => { - setStore(x) + if (typeof x === "object" && x !== null) setStore(x as Record) }) .catch(() => {}) .finally(() => { @@ -44,7 +60,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ }, set(key: string, value: any) { setStore(key, value) - Filesystem.writeJson(filePath, store) + write(store) }, } return result diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index ec3931b209a..e1a28745cb3 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,18 +1,20 @@ import { createStore } from "solid-js/store" import { batch, createEffect, createMemo } from "solid-js" +import { Effect } from "effect" import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" import { uniqueBy } from "remeda" import path from "path" import { Global } from "@/global" +import { AppRuntime } from "@/effect/app-runtime" import { iife } from "@/util/iife" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { createSimpleContext } from "./helper" import { useToast } from "../ui/toast" import { Provider } from "@/provider/provider" import { useArgs } from "./args" import { useSDK } from "./sdk" import { RGBA } from "@opentui/core" -import { Filesystem } from "@/util/filesystem" export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ name: "Local", @@ -124,6 +126,20 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const state = { pending: false, } + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readJson(filePath) + }), + ) + const write = (data: unknown) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeJson(filePath, data) + }), + ) function save() { if (!modelStore.ready) { @@ -131,14 +147,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return } state.pending = false - Filesystem.writeJson(filePath, { + write({ recent: modelStore.recent, favorite: modelStore.favorite, variant: modelStore.variant, }) } - Filesystem.readJson(filePath) + read() .then((x: any) => { if (Array.isArray(x.recent)) setModelStore("recent", x.recent) if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index c7790006f41..4d5d3adf317 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -74,8 +74,9 @@ import { Editor } from "../../util/editor" import stripAnsi from "strip-ansi" import { usePromptRef } from "../../context/prompt" import { useExit } from "../../context/exit" -import { Filesystem } from "@/util/filesystem" import { Global } from "@/global" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { PermissionPrompt } from "./permission" import { QuestionPrompt } from "./question" import { DialogExportOptions } from "../../ui/dialog-export-options" @@ -87,6 +88,7 @@ import { getScrollAcceleration } from "../../util/scroll" import { TuiPluginRuntime } from "../../plugin" import { DialogGoUpsell } from "../../component/dialog-go-upsell" import { SessionRetry } from "@/session/retry" +import { Effect } from "effect" addDefaultParsers(parsers.parsers) @@ -915,13 +917,20 @@ export function Session() { const exportDir = process.cwd() const filename = options.filename.trim() const filepath = path.join(exportDir, filename) + const write = (content: string) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(filepath, content) + }), + ) - await Filesystem.write(filepath, transcript) + await write(transcript) // Open with EDITOR if available const result = await Editor.open({ value: transcript, renderer }) if (result !== undefined) { - await Filesystem.write(filepath, result) + await write(result) } toast.show({ message: `Session exported to ${filename}`, variant: "success" }) @@ -2236,7 +2245,7 @@ function Skill(props: ToolProps) { function Diagnostics(props: { diagnostics?: Record[]>; filePath: string }) { const { theme } = useTheme() const errors = createMemo(() => { - const normalized = Filesystem.normalizePath(props.filePath) + const normalized = AppFileSystem.normalizePath(props.filePath) const arr = props.diagnostics?.[normalized] ?? [] return arr.filter((x) => x.severity === 1).slice(0, 3) }) diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 87c0a63abc8..df5786ed9ab 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -1,10 +1,12 @@ import { platform, release } from "os" import clipboardy from "clipboardy" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" import fs from "fs/promises" -import { Filesystem } from "../../../../util/filesystem" import { Process } from "../../../../util/process" import { which } from "../../../../util/which" @@ -58,8 +60,13 @@ export namespace Clipboard { ], { nothrow: true }, ) - const buffer = await Filesystem.readBytes(tmpfile) - return { data: buffer.toString("base64"), mime: "image/png" } + const buffer = await AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readFile(tmpfile) + }), + ) + return { data: Buffer.from(buffer).toString("base64"), mime: "image/png" } } catch { } finally { await fs.rm(tmpfile, { force: true }).catch(() => {}) diff --git a/packages/opencode/src/cli/cmd/tui/util/editor.ts b/packages/opencode/src/cli/cmd/tui/util/editor.ts index 9eaae99fce7..733d4c6a79a 100644 --- a/packages/opencode/src/cli/cmd/tui/util/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/util/editor.ts @@ -1,9 +1,11 @@ import { defer } from "@/util/defer" +import { AppRuntime } from "@/effect/app-runtime" +import { AppFileSystem } from "@opencode-ai/shared/filesystem" +import { Effect } from "effect" import { rm } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" import { CliRenderer } from "@opentui/core" -import { Filesystem } from "@/util/filesystem" import { Process } from "@/util/process" export namespace Editor { @@ -12,9 +14,23 @@ export namespace Editor { if (!editor) return const filepath = join(tmpdir(), `${Date.now()}.md`) + const write = (content: string) => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + yield* fs.writeWithDirs(filepath, content) + }), + ) + const read = () => + AppRuntime.runPromise( + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + return yield* fs.readFileString(filepath) + }), + ) await using _ = defer(async () => rm(filepath, { force: true })) - await Filesystem.write(filepath, opts.value) + await write(opts.value) opts.renderer.suspend() opts.renderer.currentRenderBuffer.clear() try { @@ -26,7 +42,7 @@ export namespace Editor { shell: process.platform === "win32", }) await proc.exited - const content = await Filesystem.readText(filepath) + const content = await read() return content || undefined } finally { opts.renderer.currentRenderBuffer.clear() From 58a8d4dbf5a9dd84dbe00cfa3675ec0dd8a2afdf Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 15 Apr 2026 12:40:11 -0400 Subject: [PATCH 2/2] refactor(opencode): remove AppRuntime from tui file io --- .../cli/cmd/tui/component/prompt/frecency.tsx | 25 +++---------- .../cli/cmd/tui/component/prompt/history.tsx | 25 +++---------- .../cli/cmd/tui/component/prompt/stash.tsx | 29 ++++----------- .../opencode/src/cli/cmd/tui/context/kv.tsx | 22 ++--------- .../src/cli/cmd/tui/context/local.tsx | 37 +++++++------------ .../src/cli/cmd/tui/routes/session/index.tsx | 13 +------ .../src/cli/cmd/tui/util/clipboard.ts | 11 +----- .../opencode/src/cli/cmd/tui/util/editor.ts | 21 +---------- 8 files changed, 39 insertions(+), 144 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx index afb7d5c92a6..052f33ca54d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/frecency.tsx @@ -1,8 +1,5 @@ import path from "path" import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { onMount } from "solid-js" import { createStore } from "solid-js/store" import { createSimpleContext } from "../../context/helper" @@ -21,22 +18,10 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont name: "Frecency", init: () => { const frecencyPath = path.join(Global.Path.state, "frecency.jsonl") - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readFileString(frecencyPath) - }), - ) - const write = (content: string) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeWithDirs(frecencyPath, content) - }), - ) onMount(async () => { - const text = await read().catch(() => "") + const text = await Bun.file(frecencyPath) + .text() + .catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -70,7 +55,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont if (sorted.length > 0) { const content = sorted.map((entry) => JSON.stringify(entry)).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(frecencyPath, content) } }) @@ -93,7 +78,7 @@ export const { use: useFrecency, provider: FrecencyProvider } = createSimpleCont .slice(0, MAX_FRECENCY_ENTRIES) setStore("data", Object.fromEntries(sorted)) const content = sorted.map(([path, entry]) => JSON.stringify({ path, ...entry })).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(frecencyPath, content) } } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx index 03c5fde5054..f9aa97f9e30 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/history.tsx @@ -1,8 +1,5 @@ import path from "path" import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" import { createSimpleContext } from "../../context/helper" @@ -33,22 +30,10 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create name: "PromptHistory", init: () => { const historyPath = path.join(Global.Path.state, "prompt-history.jsonl") - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readFileString(historyPath) - }), - ) - const write = (content: string) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeWithDirs(historyPath, content) - }), - ) onMount(async () => { - const text = await read().catch(() => "") + const text = await Bun.file(historyPath) + .text() + .catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -67,7 +52,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create // Rewrite file with only valid entries to self-heal corruption if (lines.length > 0) { const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(historyPath, content) } }) @@ -113,7 +98,7 @@ export const { use: usePromptHistory, provider: PromptHistoryProvider } = create if (trimmed) { const content = store.history.map((line) => JSON.stringify(line)).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(historyPath, content) return } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx index 93f6e8e9d70..8b155ecc120 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/stash.tsx @@ -1,8 +1,5 @@ import path from "path" import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { onMount } from "solid-js" import { createStore, produce, unwrap } from "solid-js/store" import { createSimpleContext } from "../../context/helper" @@ -21,22 +18,10 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp name: "PromptStash", init: () => { const stashPath = path.join(Global.Path.state, "prompt-stash.jsonl") - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readFileString(stashPath) - }), - ) - const write = (content: string) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeWithDirs(stashPath, content) - }), - ) onMount(async () => { - const text = await read().catch(() => "") + const text = await Bun.file(stashPath) + .text() + .catch(() => "") const lines = text .split("\n") .filter(Boolean) @@ -55,7 +40,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp // Rewrite file with only valid entries to self-heal corruption if (lines.length > 0) { const content = lines.map((line) => JSON.stringify(line)).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(stashPath, content) } }) @@ -82,7 +67,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp if (trimmed) { const content = store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" - write(content).catch(() => {}) + void Bun.write(stashPath, content) return } @@ -98,7 +83,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp ) const content = store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : "" - write(content).catch(() => {}) + void Bun.write(stashPath, content) return entry }, remove(index: number) { @@ -110,7 +95,7 @@ export const { use: usePromptStash, provider: PromptStashProvider } = createSimp ) const content = store.entries.length > 0 ? store.entries.map((line) => JSON.stringify(line)).join("\n") + "\n" : "" - write(content).catch(() => {}) + void Bun.write(stashPath, content) }, } }, diff --git a/packages/opencode/src/cli/cmd/tui/context/kv.tsx b/packages/opencode/src/cli/cmd/tui/context/kv.tsx index 639da363787..6016a3fee1b 100644 --- a/packages/opencode/src/cli/cmd/tui/context/kv.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/kv.tsx @@ -1,7 +1,4 @@ import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { createSignal, type Setter } from "solid-js" import { createStore } from "solid-js/store" import { createSimpleContext } from "./helper" @@ -13,22 +10,9 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ const [ready, setReady] = createSignal(false) const [store, setStore] = createStore>() const filePath = path.join(Global.Path.state, "kv.json") - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readJson(filePath) - }), - ) - const write = (data: unknown) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeJson(filePath, data) - }), - ) - read() + Bun.file(filePath) + .json() .then((x) => { if (typeof x === "object" && x !== null) setStore(x as Record) }) @@ -60,7 +44,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({ }, set(key: string, value: any) { setStore(key, value) - write(store) + void Bun.write(filePath, JSON.stringify(store, null, 2)) }, } return result diff --git a/packages/opencode/src/cli/cmd/tui/context/local.tsx b/packages/opencode/src/cli/cmd/tui/context/local.tsx index e1a28745cb3..7c58f195e04 100644 --- a/packages/opencode/src/cli/cmd/tui/context/local.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/local.tsx @@ -1,14 +1,11 @@ import { createStore } from "solid-js/store" import { batch, createEffect, createMemo } from "solid-js" -import { Effect } from "effect" import { useSync } from "@tui/context/sync" import { useTheme } from "@tui/context/theme" import { uniqueBy } from "remeda" import path from "path" import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" import { iife } from "@/util/iife" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { createSimpleContext } from "./helper" import { useToast } from "../ui/toast" import { Provider } from "@/provider/provider" @@ -126,20 +123,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const state = { pending: false, } - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readJson(filePath) - }), - ) - const write = (data: unknown) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeJson(filePath, data) - }), - ) function save() { if (!modelStore.ready) { @@ -147,14 +130,22 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ return } state.pending = false - write({ - recent: modelStore.recent, - favorite: modelStore.favorite, - variant: modelStore.variant, - }) + void Bun.write( + filePath, + JSON.stringify( + { + recent: modelStore.recent, + favorite: modelStore.favorite, + variant: modelStore.variant, + }, + null, + 2, + ), + ) } - read() + Bun.file(filePath) + .json() .then((x: any) => { if (Array.isArray(x.recent)) setModelStore("recent", x.recent) if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 4d5d3adf317..584d0edcc64 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -75,7 +75,6 @@ import stripAnsi from "strip-ansi" import { usePromptRef } from "../../context/prompt" import { useExit } from "../../context/exit" import { Global } from "@/global" -import { AppRuntime } from "@/effect/app-runtime" import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { PermissionPrompt } from "./permission" import { QuestionPrompt } from "./question" @@ -88,7 +87,6 @@ import { getScrollAcceleration } from "../../util/scroll" import { TuiPluginRuntime } from "../../plugin" import { DialogGoUpsell } from "../../component/dialog-go-upsell" import { SessionRetry } from "@/session/retry" -import { Effect } from "effect" addDefaultParsers(parsers.parsers) @@ -917,20 +915,13 @@ export function Session() { const exportDir = process.cwd() const filename = options.filename.trim() const filepath = path.join(exportDir, filename) - const write = (content: string) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeWithDirs(filepath, content) - }), - ) - await write(transcript) + await Bun.write(filepath, transcript) // Open with EDITOR if available const result = await Editor.open({ value: transcript, renderer }) if (result !== undefined) { - await write(result) + await Bun.write(filepath, result) } toast.show({ message: `Session exported to ${filename}`, variant: "success" }) diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index df5786ed9ab..4533ff91946 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -1,8 +1,5 @@ import { platform, release } from "os" import clipboardy from "clipboardy" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" @@ -60,13 +57,7 @@ export namespace Clipboard { ], { nothrow: true }, ) - const buffer = await AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readFile(tmpfile) - }), - ) - return { data: Buffer.from(buffer).toString("base64"), mime: "image/png" } + return { data: Buffer.from(await Bun.file(tmpfile).arrayBuffer()).toString("base64"), mime: "image/png" } } catch { } finally { await fs.rm(tmpfile, { force: true }).catch(() => {}) diff --git a/packages/opencode/src/cli/cmd/tui/util/editor.ts b/packages/opencode/src/cli/cmd/tui/util/editor.ts index 733d4c6a79a..15bb07c579a 100644 --- a/packages/opencode/src/cli/cmd/tui/util/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/util/editor.ts @@ -1,7 +1,4 @@ import { defer } from "@/util/defer" -import { AppRuntime } from "@/effect/app-runtime" -import { AppFileSystem } from "@opencode-ai/shared/filesystem" -import { Effect } from "effect" import { rm } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" @@ -14,23 +11,9 @@ export namespace Editor { if (!editor) return const filepath = join(tmpdir(), `${Date.now()}.md`) - const write = (content: string) => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - yield* fs.writeWithDirs(filepath, content) - }), - ) - const read = () => - AppRuntime.runPromise( - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - return yield* fs.readFileString(filepath) - }), - ) await using _ = defer(async () => rm(filepath, { force: true })) - await write(opts.value) + await Bun.write(filepath, opts.value) opts.renderer.suspend() opts.renderer.currentRenderBuffer.clear() try { @@ -42,7 +25,7 @@ export namespace Editor { shell: process.platform === "win32", }) await proc.exited - const content = await read() + const content = await Bun.file(filepath).text() return content || undefined } finally { opts.renderer.currentRenderBuffer.clear()