Compare commits

..

1 Commits

Author SHA1 Message Date
Kit Langton
0f618408fa feat: unwrap LocalContext namespace to flat exports + barrel 2026-04-15 22:46:10 -04:00
276 changed files with 1542 additions and 1645 deletions

View File

@@ -1,8 +1,5 @@
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
"categories": {
"suspicious": "warn"
},
"rules": {
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off",
@@ -13,30 +10,7 @@
// Intentional control char matching (ANSI escapes, null byte sanitization)
"no-control-regex": "off",
// SST and plugin tools require triple-slash references
"triple-slash-reference": "off",
// Suspicious category: suppress noisy rules
// Effect's nested function* closures inherently shadow outer scope
"no-shadow": "off",
// Namespace-heavy codebase makes this too noisy
"unicorn/consistent-function-scoping": "off",
// Opinionated — .sort()/.reverse() mutation is fine in this codebase
"unicorn/no-array-sort": "off",
"unicorn/no-array-reverse": "off",
// Not relevant — this isn't a DOM event handler codebase
"unicorn/prefer-add-event-listener": "off",
// Bundler handles module resolution
"unicorn/require-module-specifiers": "off",
// postMessage target origin not relevant for this codebase
"unicorn/require-post-message-target-origin": "off",
// Side-effectful constructors are intentional in some places
"no-new": "off",
// Type-aware: catch unhandled promises
"typescript/no-floating-promises": "warn"
},
"options": {
"typeAware": true
"triple-slash-reference": "off"
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts"]
}

View File

@@ -20,7 +20,6 @@
"glob": "13.0.5",
"husky": "9.1.7",
"oxlint": "1.60.0",
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",
@@ -1681,18 +1680,6 @@
"@oxc-transform/binding-win32-x64-msvc": ["@oxc-transform/binding-win32-x64-msvc@0.96.0", "", { "os": "win32", "cpu": "x64" }, "sha512-0fI0P0W7bSO/GCP/N5dkmtB9vBqCA4ggo1WmXTnxNJVmFFOtcA1vYm1I9jl8fxo+sucW2WnlpnI4fjKdo3JKxA=="],
"@oxlint-tsgolint/darwin-arm64": ["@oxlint-tsgolint/darwin-arm64@0.21.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-P20j3MLqfwIT+94qGU3htC7dWp4pXGZW1p1p7FRUzu1aopq7c9nPCgf0W/WjktqQ57+iuTq9mbSlwWinl6+H1A=="],
"@oxlint-tsgolint/darwin-x64": ["@oxlint-tsgolint/darwin-x64@0.21.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-81TmmuBcPedEA0MwRmObuQuXnCprS1UiHQWGe7pseqNAJzUWXeAPrayqKTACX92VpruJI+yvY0XJrFp11PpcTA=="],
"@oxlint-tsgolint/linux-arm64": ["@oxlint-tsgolint/linux-arm64@0.21.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-sbjBr6zDduX8rNO0PTjhf7VYLCPWqdijWiMPp8e10qu6Tam1GdaVLaLlX8QrNupTgglO1GvqqgY/jcacWL8a6g=="],
"@oxlint-tsgolint/linux-x64": ["@oxlint-tsgolint/linux-x64@0.21.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jNrOcy53R5TJQfrK444Cm60bW9437xDoxPbm3AdvFSo/fhdFMllawc7uZC2Wzr+EAjTkW13K8R4QHzsUdBG9fQ=="],
"@oxlint-tsgolint/win32-arm64": ["@oxlint-tsgolint/win32-arm64@0.21.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xWeRxJJILDE4b9UqHEWGBxcBc1TUS6zWHhxcyxTZMwf4q3wdKeu0OHYAcwLGJzoSjEIf6FTjyfPiRNil2oqsdg=="],
"@oxlint-tsgolint/win32-x64": ["@oxlint-tsgolint/win32-x64@0.21.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Ob9AA9teI8ckPo1whV1smLr5NrqwgBv/8boDbK0YZG+fKgNGRwr1hBj1ORgFWOQaUBv+5njp5A0RAfJJjQ95QQ=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-YdeJKaZckDQL1qa62a1aKq/goyq48aX3yOxaaWqWb4sau4Ee4IiLbamftNLU3zbePky6QsDj6thnSSzHRBjDfA=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-7ANS7PpXCfq84xZQ8E5WPs14gwcuPcl+/8TFNXfpSu0CQBXz3cUo2fDpHT8v8HJN+Ut02eacvMAzTnc9s6X4tw=="],
@@ -4113,8 +4100,6 @@
"oxlint": ["oxlint@1.60.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.60.0", "@oxlint/binding-android-arm64": "1.60.0", "@oxlint/binding-darwin-arm64": "1.60.0", "@oxlint/binding-darwin-x64": "1.60.0", "@oxlint/binding-freebsd-x64": "1.60.0", "@oxlint/binding-linux-arm-gnueabihf": "1.60.0", "@oxlint/binding-linux-arm-musleabihf": "1.60.0", "@oxlint/binding-linux-arm64-gnu": "1.60.0", "@oxlint/binding-linux-arm64-musl": "1.60.0", "@oxlint/binding-linux-ppc64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-gnu": "1.60.0", "@oxlint/binding-linux-riscv64-musl": "1.60.0", "@oxlint/binding-linux-s390x-gnu": "1.60.0", "@oxlint/binding-linux-x64-gnu": "1.60.0", "@oxlint/binding-linux-x64-musl": "1.60.0", "@oxlint/binding-openharmony-arm64": "1.60.0", "@oxlint/binding-win32-arm64-msvc": "1.60.0", "@oxlint/binding-win32-ia32-msvc": "1.60.0", "@oxlint/binding-win32-x64-msvc": "1.60.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-tnRzTWiWJ9pg3ftRWnD0+Oqh78L6ZSwcEudvCZaER0PIqiAnNyXj5N1dPwjmNpDalkKS9m/WMLN1CTPUBPmsgw=="],
"oxlint-tsgolint": ["oxlint-tsgolint@0.21.0", "", { "optionalDependencies": { "@oxlint-tsgolint/darwin-arm64": "0.21.0", "@oxlint-tsgolint/darwin-x64": "0.21.0", "@oxlint-tsgolint/linux-arm64": "0.21.0", "@oxlint-tsgolint/linux-x64": "0.21.0", "@oxlint-tsgolint/win32-arm64": "0.21.0", "@oxlint-tsgolint/win32-x64": "0.21.0" }, "bin": { "tsgolint": "bin/tsgolint.js" } }, "sha512-HiWPhANwRnN1pZJQ2SgNB3WRR+1etLJHmRzQ/MJhyINsEIaOUCjxhlXJKbEaVUwdnyXwRWqo/P9Fx21lz0/mSg=="],
"p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="],
"p-defer": ["p-defer@3.0.0", "", {}, "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw=="],

View File

@@ -513,7 +513,7 @@ async function subscribeSessionEvents() {
const decoder = new TextDecoder()
let text = ""
void (async () => {
;(async () => {
while (true) {
try {
const { done, value } = await reader.read()
@@ -542,7 +542,7 @@ async function subscribeSessionEvents() {
? JSON.stringify(part.state.input)
: "Unknown"
console.log()
console.log(`${color}|`, `\x1b[0m\x1b[2m ${tool.padEnd(7, " ")}`, "", `\x1b[0m${title}`)
console.log(color + `|`, "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, "", "\x1b[0m" + title)
}
if (part.type === "text") {
@@ -776,7 +776,7 @@ async function assertPermissions() {
console.log(` permission: ${permission}`)
} catch (error) {
console.error(`Failed to check permissions: ${error}`)
throw new Error(`Failed to check permissions for user ${actor}: ${error}`, { cause: error })
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
}
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)

View File

@@ -87,7 +87,6 @@
"glob": "13.0.5",
"husky": "9.1.7",
"oxlint": "1.60.0",
"oxlint-tsgolint": "0.21.0",
"prettier": "3.6.2",
"semver": "^7.6.0",
"sst": "3.18.10",

View File

@@ -197,12 +197,12 @@ function ConnectionGate(props: ParentProps<{ disableHealthCheck?: boolean }>) {
fallback={
<ConnectionError
onRetry={() => {
if (checkMode() === "background") void healthCheckActions.refetch()
if (checkMode() === "background") healthCheckActions.refetch()
}}
onServerSelected={(key) => {
setCheckMode("blocking")
server.setActive(key)
void healthCheckActions.refetch()
healthCheckActions.refetch()
}}
/>
}

View File

@@ -327,7 +327,7 @@ export function DialogConnectProvider(props: { provider: string }) {
if (loading()) return
if (methods().length === 1) {
auto = true
void selectMethod(0)
selectMethod(0)
}
})
@@ -373,7 +373,7 @@ export function DialogConnectProvider(props: { provider: string }) {
key={(m) => m?.label}
onSelect={async (selected, index) => {
if (!selected) return
void selectMethod(index)
selectMethod(index)
}}
>
{(i) => (

View File

@@ -348,8 +348,8 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
const open = (path: string) => {
const value = file.tab(path)
void tabs().open(value)
void file.load(path)
tabs().open(value)
file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
props.onOpenFile?.(path)

View File

@@ -344,7 +344,7 @@ export function DialogSelectServer() {
createEffect(() => {
items()
void refreshHealth()
refreshHealth()
const interval = setInterval(refreshHealth, 10_000)
onCleanup(() => clearInterval(interval))
})
@@ -498,7 +498,7 @@ export function DialogSelectServer() {
async function handleRemove(url: ServerConnection.Key) {
server.remove(url)
if ((await platform.getDefaultServer?.()) === url) {
void platform.setDefaultServer?.(null)
platform.setDefaultServer?.(null)
}
}
@@ -536,7 +536,7 @@ export function DialogSelectServer() {
items={sortedItems}
key={(x) => x.http.url}
onSelect={(x) => {
if (x) void select(x)
if (x) select(x)
}}
divider={true}
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"

View File

@@ -212,9 +212,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
const tab = files.tab(item.path)
void tabs().open(tab)
tabs().open(tab)
tabs().setActive(tab)
void Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
}
const recent = createMemo(() => {
@@ -1139,7 +1139,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
if (working()) {
void abort()
abort()
event.preventDefault()
event.stopPropagation()
return
@@ -1205,7 +1205,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}
if (working()) {
void abort()
abort()
event.preventDefault()
}
return
@@ -1245,7 +1245,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
) {
return
}
void handleSubmit(event)
handleSubmit(event)
}
}

View File

@@ -295,7 +295,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const mode = input.mode()
if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) {
if (input.working()) void abort()
if (input.working()) abort()
return
}

View File

@@ -24,7 +24,7 @@ function openSessionContext(args: {
}) {
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
void args.tabs.open("context")
args.tabs.open("context")
args.tabs.setActive("context")
}

View File

@@ -44,7 +44,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
const close = () => {
const count = terminal.all().length
void terminal.close(props.terminal.id)
terminal.close(props.terminal.id)
if (count === 1) {
props.onClose?.()
}

View File

@@ -415,7 +415,7 @@ export const Terminal = (props: TerminalProps) => {
if (local.autoFocus !== false) focusTerminal()
if (typeof document !== "undefined" && document.fonts) {
void document.fonts.ready.then(scheduleFit)
document.fonts.ready.then(scheduleFit)
}
const onResize = t.onResize((size) => {

View File

@@ -128,7 +128,6 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
if (started) return run
started = true
run = (async () => {
// oxlint-disable-next-line no-unmodified-loop-condition -- `started` is set to false by stop() which also aborts; both flags are checked to allow graceful exit
while (!abort.signal.aborted && started) {
attempt = new AbortController()
lastEventAt = Date.now()

View File

@@ -237,7 +237,7 @@ function createGlobalSync() {
})
sessionLoads.set(directory, promise)
void promise.finally(() => {
promise.finally(() => {
sessionLoads.delete(directory)
children.unpin(directory)
})
@@ -273,7 +273,7 @@ function createGlobalSync() {
})()
booting.set(directory, promise)
void promise.finally(() => {
promise.finally(() => {
booting.delete(directory)
children.unpin(directory)
})
@@ -317,7 +317,7 @@ function createGlobalSync() {
setSessionTodo,
vcsCache: children.vcsCache.get(directory),
loadLsp: () => {
void sdkFor(directory)
sdkFor(directory)
.lsp.status()
.then((x) => {
setStore("lsp", x.data ?? [])
@@ -359,13 +359,13 @@ function createGlobalSync() {
eventFrame = undefined
eventTimer = setTimeout(() => {
eventTimer = undefined
void globalSDK.event.start()
globalSDK.event.start()
}, 0)
})
} else {
eventTimer = setTimeout(() => {
eventTimer = undefined
void globalSDK.event.start()
globalSDK.event.start()
}, 0)
}
void bootstrap()

View File

@@ -582,7 +582,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
open(directory: string) {
const root = rootFor(directory)
if (server.projects.list().find((x) => x.worktree === root)) return
void globalSync.project.loadSessions(root)
globalSync.project.loadSessions(root)
server.projects.open(root)
},
close(directory: string) {

View File

@@ -117,7 +117,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
entry?.value.clear()
}
void removePersisted(Persist.workspace(dir, "terminal"), platform)
removePersisted(Persist.workspace(dir, "terminal"), platform)
const legacy = new Set(getLegacyTerminalStorageKeys(dir))
for (const id of sessionIDs ?? []) {
@@ -126,7 +126,7 @@ export function clearWorkspaceTerminals(dir: string, sessionIDs?: string[], plat
}
}
for (const key of legacy) {
void removePersisted({ key }, platform)
removePersisted({ key }, platform)
}
}

View File

@@ -956,7 +956,7 @@ export default function Layout(props: ParentProps) {
// warm up child store to prevent flicker
globalSync.child(target.worktree)
void openProject(target.worktree)
openProject(target.worktree)
}
function navigateSessionByUnseen(offset: number) {
@@ -1094,7 +1094,7 @@ export default function Layout(props: ParentProps) {
disabled: !params.dir || !params.id,
onSelect: () => {
const session = currentSessions().find((s) => s.id === params.id)
if (session) void archiveSession(session)
if (session) archiveSession(session)
},
},
{
@@ -1360,11 +1360,11 @@ export default function Layout(props: ParentProps) {
if (!server.isLocal()) return
for (const directory of collectOpenProjectDeepLinks(urls)) {
void openProject(directory)
openProject(directory)
}
for (const link of collectNewSessionDeepLinks(urls)) {
void openProject(link.directory, false)
openProject(link.directory, false)
const slug = base64Encode(link.directory)
if (link.prompt) {
setSessionHandoff(slug, { prompt: link.prompt })
@@ -1453,11 +1453,11 @@ export default function Layout(props: ParentProps) {
function resolve(result: string | string[] | null) {
if (Array.isArray(result)) {
for (const directory of result) {
void openProject(directory, false)
openProject(directory, false)
}
void navigateToProject(result[0])
navigateToProject(result[0])
} else if (result) {
void openProject(result)
openProject(result)
}
}
@@ -1825,7 +1825,7 @@ export default function Layout(props: ParentProps) {
const next = new Set(dirs)
for (const directory of next) {
if (loadedSessionDirs.has(directory)) continue
void globalSync.project.loadSessions(directory)
globalSync.project.loadSessions(directory)
}
loadedSessionDirs.clear()
@@ -2110,7 +2110,7 @@ export default function Layout(props: ParentProps) {
onSave={(next) => {
const item = project()
if (!item) return
void renameProject(item, next)
renameProject(item, next)
}}
class="text-14-medium text-text-strong truncate"
displayClass="text-14-medium text-text-strong truncate"
@@ -2242,7 +2242,7 @@ export default function Layout(props: ParentProps) {
onClick={() => {
const item = project()
if (!item) return
void createWorkspace(item)
createWorkspace(item)
}}
>
{language.t("workspace.new")}

View File

@@ -277,7 +277,7 @@ const WorkspaceSessionList = (props: {
class="flex w-full text-left justify-start text-14-regular text-text-weak pl-2 pr-10"
size="large"
onClick={(e: MouseEvent) => {
void props.loadMore()
props.loadMore()
;(e.currentTarget as HTMLButtonElement).blur()
}}
>

View File

@@ -484,7 +484,7 @@ export default function Page() {
if (!tab) return
const path = file.pathFromTab(tab)
if (path) void file.load(path)
if (path) file.load(path)
})
createEffect(

View File

@@ -117,7 +117,7 @@ export const createOpenReviewFile = (input: {
input.openTab(tab)
input.setActive(tab)
}
if (maybePromise instanceof Promise) void maybePromise.then(open)
if (maybePromise instanceof Promise) maybePromise.then(open)
else open()
})
}

View File

@@ -46,9 +46,7 @@ describe("runtime adapters", () => {
})
test("resolves speech recognition constructor with webkit precedence", () => {
// oxlint-disable-next-line no-extraneous-class
class SpeechCtor {}
// oxlint-disable-next-line no-extraneous-class
class WebkitCtor {}
const ctor = getSpeechRecognitionCtor({
SpeechRecognition: SpeechCtor,

View File

@@ -105,4 +105,4 @@ async function main() {
console.log(`✓ Sitemap generated at ${outputPath}`)
}
void main()
main()

View File

@@ -766,7 +766,7 @@ export default function Spotlight(props: SpotlightProps) {
}
}
void initializeWebGPU()
initializeWebGPU()
onCleanup(() => {
if (cleanupFunctionRef) {

View File

@@ -298,7 +298,7 @@ export default function BlackSubscribe() {
// Resolve stripe promise once
createEffect(() => {
void stripePromise.then((s) => {
stripePromise.then((s) => {
if (s) setStripe(s)
})
})

View File

@@ -77,7 +77,7 @@ export default function Download() {
const handleCopyClick = (command: string) => (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
void navigator.clipboard.writeText(command)
navigator.clipboard.writeText(command)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")

View File

@@ -35,7 +35,7 @@ export default function Home() {
const button = event.currentTarget as HTMLButtonElement
const text = button.textContent
if (text) {
void navigator.clipboard.writeText(text)
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")

View File

@@ -27,7 +27,7 @@ export default function Home() {
const callback = () => {
const text = button.textContent
if (text) {
void navigator.clipboard.writeText(text)
navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")

View File

@@ -90,7 +90,7 @@ export function ReloadSection() {
}
const info = billingInfo()!
setStore("show", true)
setStore("reload", true)
setStore("reload", info.reload ? true : true)
setStore("reloadAmount", info.reloadAmount.toString())
setStore("reloadTrigger", info.reloadTrigger.toString())
}

View File

@@ -26,14 +26,14 @@ export function createDataDumper(sessionId: string, requestId: string, projectId
const minute = timestamp.substring(10, 12)
const second = timestamp.substring(12, 14)
void waitUntil(
waitUntil(
Resource.ZenDataNew.put(
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
JSON.stringify({ timestamp, ...data }),
),
)
void waitUntil(
waitUntil(
Resource.ZenDataNew.put(
`meta/${data.modelName}/${sessionId}/${requestId}.json`,
JSON.stringify({ timestamp, ...metadata }),

View File

@@ -28,7 +28,7 @@ export function wslPath(path: string, mode: "windows" | "linux" | null): string
const output = execFileSync("wsl", ["-e", "wslpath", flag, path])
return output.toString().trim()
} catch (error) {
throw new Error(`Failed to run wslpath: ${String(error)}`, { cause: error })
throw new Error(`Failed to run wslpath: ${String(error)}`)
}
}

View File

@@ -1,5 +1,5 @@
if (location.pathname === "/loading") {
void import("./loading")
import("./loading")
} else {
void import("./")
import("./")
}

View File

@@ -410,7 +410,7 @@ const createPlatform = (): Platform => {
}
let menuTrigger = null as null | ((id: string) => void)
void createMenu((id) => {
createMenu((id) => {
menuTrigger?.(id)
})
void listenForDeepLinks()

View File

@@ -48,7 +48,7 @@ render(() => {
})
onCleanup(() => {
void listener.then((cb) => cb())
listener.then((cb) => cb())
timers.forEach(clearTimeout)
})
})

View File

@@ -186,5 +186,5 @@ export async function createMenu(trigger: (id: string) => void) {
}),
],
})
void menu.setAsAppMenu()
menu.setAsAppMenu()
}

View File

@@ -17,7 +17,7 @@ const clamp = (value: number) => Math.min(Math.max(value, MIN_ZOOM_LEVEL), MAX_Z
const applyZoom = (next: number) => {
setWebviewZoom(next)
void invoke("plugin:webview|set_webview_zoom", {
invoke("plugin:webview|set_webview_zoom", {
value: next,
})
}

View File

@@ -37,4 +37,4 @@ async function test() {
await Share.remove({ id: shareInfo.id, secret: shareInfo.secret })
}
void test()
test()

View File

@@ -13,7 +13,6 @@ type Env = {
}
export class SyncServer extends DurableObject<Env> {
// oxlint-disable-next-line no-useless-constructor
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env)
}

View File

@@ -64,7 +64,7 @@ function findBinary() {
return { binaryPath, binaryName }
} catch (error) {
throw new Error(`Could not find package ${packageName}: ${error.message}`, { cause: error })
throw new Error(`Could not find package ${packageName}: ${error.message}`)
}
}
@@ -112,7 +112,7 @@ async function main() {
}
try {
void main()
main()
} catch (error) {
console.error("Postinstall script error:", error.message)
process.exit(0)

View File

@@ -121,46 +121,17 @@ Why `question` first:
Do not re-architect business logic during the HTTP migration. `HttpApi` handlers should call the same Effect services already used by the Hono handlers.
### 4. Bridge into Hono behind a feature flag
### 4. Build in parallel, do not bridge into Hono
The `HttpApi` routes are bridged into the Hono server via `HttpRouter.toWebHandler` with a shared `memoMap`. This means:
The `HttpApi` implementation lives under `src/server/instance/httpapi/` as a standalone Effect HTTP server. It is **not mounted into the Hono app**. There is no `toWebHandler` bridge, no Hono `Handler` export, and no `.route()` call wiring it into `experimental.ts`.
- one process, one port — no separate server
- the Effect handler shares layer instances with `AppRuntime` (same `Question.Service`, etc.)
- Effect middleware handles auth and instance lookup independently from Hono middleware
- Hono's `.all()` catch-all intercepts matching paths before the Hono route handlers
The standalone server (`httpapi/server.ts`) can be started independently and proves the routes work. Tests exercise it via `HttpRouter.serve` with `NodeHttpServer.layerTest`.
The bridge is gated behind `OPENCODE_EXPERIMENTAL_HTTPAPI` (or `OPENCODE_EXPERIMENTAL`). When the flag is off (default), all requests go through the original Hono handlers unchanged.
The goal is to build enough route coverage in the Effect server that the Hono server can eventually be replaced entirely. Until then, the two implementations exist side by side but are completely separate processes.
```ts
// in instance/index.ts
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
const handler = ExperimentalHttpApiServer.webHandler().handler
app.all("/question", (c) => handler(c.req.raw)).all("/question/*", (c) => handler(c.req.raw))
}
```
### 5. Migrate JSON route groups gradually
The Hono route handlers are always registered (after the bridge) so `hono-openapi` generates the OpenAPI spec entries that feed SDK codegen. When the flag is on, these handlers are dead code — the `.all()` bridge matches first.
### 5. Observability
The `webHandler` provides `Observability.layer` via `Layer.provideMerge`. Since the `memoMap` is shared with `AppRuntime`, the tracing provider is deduplicated — no extra initialization cost.
This gives:
- **spans**: `Effect.fn("QuestionHttpApi.list")` etc. appear in traces alongside service-layer spans
- **HTTP logs**: `HttpMiddleware.logger` emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` annotations, flowing to motel via `OtlpLogger`
### 6. Migrate JSON route groups gradually
As each route group is ported to `HttpApi`:
1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
4. verify SDK output is unchanged
Leave streaming-style endpoints on Hono until there is a clear reason to move them.
If the parallel slice works well, migrate additional JSON route groups one at a time. Leave streaming-style endpoints on Hono until there is a clear reason to move them.
## Schema rule for HttpApi work
@@ -331,43 +302,36 @@ The first slice is successful if:
- OpenAPI is generated from the `HttpApi` contract
- the tests are straightforward enough that the next slice feels mechanical
## Learnings
## Learnings from the question slice
### Schema
The first parallel `question` spike gave us a concrete pattern to reuse.
- `Schema.Class` works well for route DTOs such as `Question.Request`, `Question.Info`, and `Question.Reply`.
- scalar or collection schemas such as `Question.Answer` should stay as schemas and use helpers like `withStatics(...)` instead of being forced into classes.
- if an `HttpApi` success schema uses `Schema.Class`, the handler or underlying service needs to return real schema instances rather than plain objects.
- internal event payloads can stay anonymous when we want to avoid adding extra named OpenAPI component churn for non-route shapes.
- `Schema.Class` emits named `$ref` in OpenAPI — only use it for types that already had `.meta({ ref })` in the old Zod schema. Inner/nested types should stay as `Schema.Struct` to avoid SDK shape changes.
### Integration
- `HttpRouter.toWebHandler` with the shared `memoMap` from `run-service.ts` cleanly bridges Effect routes into Hono — one process, one port, shared layer instances.
- `Observability.layer` must be explicitly provided via `Layer.provideMerge` in the routes layer for OTEL spans and HTTP logs to flow. The `memoMap` deduplicates it with `AppRuntime` — no extra cost.
- `HttpMiddleware.logger` (enabled by default when `disableLogger` is not set) emits structured `Effect.log` entries with `http.method`, `http.url`, `http.status` — these flow through `OtlpLogger` to motel.
- Hono OpenAPI stubs must remain registered for SDK codegen until the SDK pipeline reads from the Effect OpenAPI spec instead.
- the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag gates the bridge at the Hono router level — default off, no behavior change unless opted in.
- the experimental slice should stay as a standalone Effect server and keep calling the existing service layer unchanged.
- compare generated OpenAPI semantically at the route and schema level.
## Route inventory
Status legend:
- `bridged` - Effect HttpApi slice exists and is bridged into Hono behind the flag
- `done` - Effect HttpApi slice exists but not yet bridged
- `done` - parallel `HttpApi` slice exists
- `next` - good near-term candidate
- `later` - possible, but not first wave
- `defer` - not a good early `HttpApi` target
Current instance route inventory:
- `question` - `bridged`
endpoints: `GET /question`, `POST /question/:requestID/reply`, `POST /question/:requestID/reject`
- `permission` - `bridged`
endpoints: `GET /permission`, `POST /permission/:requestID/reply`
- `provider` - `bridged` (partial)
bridged endpoint: `GET /provider/auth`
not yet ported: `GET /provider`, OAuth mutations
- `question` - `done`
endpoints in slice: `GET /question`, `POST /question/:requestID/reply`
- `permission` - `done`
endpoints in slice: `GET /permission`, `POST /permission/:requestID/reply`
- `provider` - `next`
best next endpoint: `GET /provider/auth`
later endpoint: `GET /provider`
defer first-wave OAuth mutations
- `config` - `next`
best next endpoint: `GET /config/providers`
later endpoint: `GET /config`
@@ -407,13 +371,7 @@ Recommended near-term sequence after the first spike:
- [x] keep the underlying service calls identical to the current handlers
- [x] compare generated OpenAPI against the current Hono/OpenAPI setup
- [x] document how auth, instance lookup, and error mapping would compose in the new stack
- [x] bridge Effect routes into Hono via `toWebHandler` with shared `memoMap`
- [x] gate behind `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
- [x] verify OTEL spans and HTTP logs flow to motel
- [x] bridge question, permission, and provider auth routes
- [ ] port remaining provider endpoints (`GET /provider`, OAuth mutations)
- [ ] port `config` read endpoints
- [ ] decide when to remove the flag and make Effect routes the default
- [ ] decide after the spike whether `HttpApi` should stay parallel, replace only some groups, or become the long-term default
## Rule of thumb

View File

@@ -31,9 +31,9 @@ import {
type Usage,
} from "@agentclientprotocol/sdk"
import { Log } from "../util"
import { Log } from "../util/log"
import { pathToFileURL } from "url"
import { Filesystem } from "../util"
import { Filesystem } from "../util/filesystem"
import { Hash } from "@opencode-ai/shared/util/hash"
import { ACPSessionManager } from "./session"
import type { ACPConfig } from "./types"
@@ -242,7 +242,7 @@ export namespace ACP {
const newContent = getNewContent(content, diff)
if (newContent) {
void this.connection.writeTextFile({
this.connection.writeTextFile({
sessionId: session.id,
path: filepath,
content: newContent,
@@ -1253,7 +1253,7 @@ export namespace ACP {
)
setTimeout(() => {
void this.connection.sessionUpdate({
this.connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: "available_commands_update",

View File

@@ -1,6 +1,6 @@
import { RequestError, type McpServer } from "@agentclientprotocol/sdk"
import type { ACPSessionState } from "./types"
import { Log } from "@/util"
import { Log } from "@/util/log"
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
const log = Log.create({ service: "acp-session-manager" })

View File

@@ -20,7 +20,7 @@ import path from "path"
import { Plugin } from "@/plugin"
import { Skill } from "../skill"
import { Effect, Context, Layer } from "effect"
import { InstanceState } from "@/effect"
import { InstanceState } from "@/effect/instance-state"
import * as Option from "effect/Option"
import * as OtelTracer from "@effect/opentelemetry/Tracer"

View File

@@ -25,7 +25,7 @@ export namespace BusEvent {
properties: def.properties,
})
.meta({
ref: `Event.${def.type}`,
ref: "Event" + "." + def.type,
})
})
.toArray()

View File

@@ -1,10 +1,10 @@
import z from "zod"
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect"
import { EffectBridge } from "@/effect"
import { Log } from "../util"
import { EffectBridge } from "@/effect/bridge"
import { Log } from "../util/log"
import { BusEvent } from "./bus-event"
import { GlobalBus } from "./global"
import { InstanceState } from "@/effect"
import { InstanceState } from "@/effect/instance-state"
import { makeRuntime } from "@/effect/run-service"
const log = Log.create({ service: "bus" })

View File

@@ -1,4 +1,4 @@
import { Log } from "@/util"
import { Log } from "@/util/log"
import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"

View File

@@ -7,7 +7,7 @@ import { Agent } from "../../agent/agent"
import { Provider } from "../../provider"
import path from "path"
import fs from "fs/promises"
import { Filesystem } from "../../util"
import { Filesystem } from "../../util/filesystem"
import matter from "gray-matter"
import { Instance } from "../../project/instance"
import { EOL } from "os"

View File

@@ -111,7 +111,6 @@ function parseToolParams(input?: string) {
} catch (evalError) {
throw new Error(
`Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`,
{ cause: evalError },
)
}
}

View File

@@ -3,7 +3,7 @@ import { AppRuntime } from "../../../effect/app-runtime"
import { Effect } from "effect"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
import { Log } from "../../../util"
import { Log } from "../../../util/log"
import { EOL } from "os"
export const LSPCommand = cmd({

View File

@@ -1,6 +1,6 @@
import { EOL } from "os"
import { Project } from "../../../project/project"
import { Log } from "../../../util"
import { Log } from "../../../util/log"
import { cmd } from "../cmd"
export const ScrapCommand = cmd({

View File

@@ -1,6 +1,6 @@
import path from "path"
import { exec } from "child_process"
import { Filesystem } from "../../util"
import { Filesystem } from "../../util/filesystem"
import * as prompts from "@clack/prompts"
import { map, pipe, sortBy, values } from "remeda"
import { Octokit } from "@octokit/rest"
@@ -32,7 +32,7 @@ import { SessionPrompt } from "@/session/prompt"
import { AppRuntime } from "@/effect/app-runtime"
import { Git } from "@/git"
import { setTimeout as sleep } from "node:timers/promises"
import { Process } from "@/util"
import { Process } from "@/util/process"
import { Effect } from "effect"
type GitHubAuthor = {
@@ -1031,7 +1031,6 @@ export const GithubRunCommand = cmd({
console.error("Failed to get OIDC token:", error instanceof Error ? error.message : error)
throw new Error(
"Could not fetch an OIDC token. Make sure to add `id-token: write` to your workflow permissions.",
{ cause: error },
)
}
}
@@ -1222,7 +1221,7 @@ export const GithubRunCommand = cmd({
console.log(` permission: ${permission}`)
} catch (error) {
console.error(`Failed to check permissions: ${error}`)
throw new Error(`Failed to check permissions for user ${actor}: ${error}`, { cause: error })
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
}
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)

View File

@@ -9,7 +9,7 @@ import { SessionTable, MessageTable, PartTable } from "../../session/session.sql
import { Instance } from "../../project/instance"
import { ShareNext } from "../../share/share-next"
import { EOL } from "os"
import { Filesystem } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { AppRuntime } from "@/effect/app-runtime"
/** Discriminated union returned by the ShareNext API (GET /api/shares/:id/data) */

View File

@@ -13,7 +13,7 @@ import { Installation } from "../../installation"
import path from "path"
import { Global } from "../../global"
import { modify, applyEdits } from "jsonc-parser"
import { Filesystem } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { Bus } from "../../bus"
import { AppRuntime } from "../../effect/app-runtime"
import { Effect } from "effect"

View File

@@ -7,8 +7,8 @@ import { installPlugin, patchPluginConfig, readPluginManifest } from "../../plug
import { resolvePluginTarget } from "../../plugin/shared"
import { Instance } from "../../project/instance"
import { errorMessage } from "../../util/error"
import { Filesystem } from "../../util"
import { Process } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { Process } from "../../util/process"
import { UI } from "../ui"
import { cmd } from "./cmd"

View File

@@ -3,7 +3,7 @@ import { cmd } from "./cmd"
import { AppRuntime } from "@/effect/app-runtime"
import { Git } from "@/git"
import { Instance } from "@/project/instance"
import { Process } from "@/util"
import { Process } from "@/util/process"
export const PrCommand = cmd({
command: "pr <number>",

View File

@@ -12,7 +12,7 @@ import { Global } from "../../global"
import { Plugin } from "../../plugin"
import { Instance } from "../../project/instance"
import type { Hooks } from "@opencode-ai/plugin"
import { Process } from "../../util"
import { Process } from "../../util/process"
import { text } from "node:stream/consumers"
import { Effect } from "effect"

View File

@@ -6,7 +6,7 @@ import { cmd } from "./cmd"
import { Flag } from "../../flag/flag"
import { bootstrap } from "../bootstrap"
import { EOL } from "os"
import { Filesystem } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
import { Server } from "../../server/server"
import { Provider } from "../../provider"
@@ -25,7 +25,7 @@ import { TaskTool } from "../../tool/task"
import { SkillTool } from "../../tool/skill"
import { BashTool } from "../../tool/bash"
import { TodoWriteTool } from "../../tool/todo"
import { Locale } from "../../util"
import { Locale } from "../../util/locale"
import { AppRuntime } from "@/effect/app-runtime"
type ToolProps<T> = {

View File

@@ -4,10 +4,10 @@ import { Session } from "../../session"
import { SessionID } from "../../session/schema"
import { bootstrap } from "../bootstrap"
import { UI } from "../ui"
import { Locale } from "../../util"
import { Locale } from "../../util/locale"
import { Flag } from "../../flag/flag"
import { Filesystem } from "../../util"
import { Process } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { Process } from "../../util/process"
import { EOL } from "os"
import path from "path"
import { which } from "../../util/which"

View File

@@ -350,7 +350,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
if (match) {
continued = true
if (args.fork) {
void sdk.client.session.fork({ sessionID: match }).then((result) => {
sdk.client.session.fork({ sessionID: match }).then((result) => {
if (result.data?.id) {
route.navigate({ type: "session", sessionID: result.data.id })
} else {
@@ -370,7 +370,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
createEffect(() => {
if (forked || sync.status !== "complete" || !args.sessionID || !args.fork) return
forked = true
void sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => {
sdk.client.session.fork({ sessionID: args.sessionID }).then((result) => {
if (result.data?.id) {
route.navigate({ type: "session", sessionID: result.data.id })
} else {
@@ -818,7 +818,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
)
void exit()
exit()
})
const plugin = createMemo(() => {

View File

@@ -4,7 +4,7 @@ import { useSync } from "@tui/context/sync"
import { map, pipe, entries, sortBy } from "remeda"
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
import { useTheme } from "../context/theme"
import { Keybind } from "@/util"
import { Keybind } from "@/util/keybind"
import { TextAttributes } from "@opentui/core"
import { useSDK } from "@tui/context/sdk"

View File

@@ -3,14 +3,14 @@ import { DialogSelect } from "@tui/ui/dialog-select"
import { useRoute } from "@tui/context/route"
import { useSync } from "@tui/context/sync"
import { createMemo, createResource, createSignal, onMount } from "solid-js"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { useProject } from "@tui/context/project"
import { useKeybind } from "../context/keybind"
import { useTheme } from "../context/theme"
import { useSDK } from "../context/sdk"
import { Flag } from "@/flag/flag"
import { DialogSessionRename } from "./dialog-session-rename"
import { Keybind } from "@/util"
import { Keybind } from "@/util/keybind"
import { createDebouncedSignal } from "../util/signal"
import { useToast } from "../ui/toast"
import { DialogWorkspaceCreate, openWorkspaceSession } from "./dialog-workspace-create"
@@ -145,7 +145,7 @@ export function DialogSessionList() {
title: "delete",
onTrigger: async (option) => {
if (toDelete() === option.value) {
void sdk.client.session.delete({
sdk.client.session.delete({
sessionID: option.value,
})
setToDelete(undefined)

View File

@@ -19,7 +19,7 @@ export function DialogSessionRename(props: DialogSessionRenameProps) {
title="Rename Session"
value={session()?.title}
onConfirm={(value) => {
void sdk.client.session.update({
sdk.client.session.update({
sessionID: props.session,
title: value,
})

View File

@@ -1,7 +1,7 @@
import { useDialog } from "@tui/ui/dialog"
import { DialogSelect } from "@tui/ui/dialog-select"
import { createMemo, createSignal } from "solid-js"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { useTheme } from "../context/theme"
import { useKeybind } from "../context/keybind"
import { usePromptStash, type StashEntry } from "./prompt/stash"

View File

@@ -26,7 +26,7 @@ export function ErrorComponent(props: {
useKeyboard((evt) => {
if (evt.ctrl && evt.name === "c") {
void handleExit()
handleExit()
}
})
const [copied, setCopied] = createSignal(false)
@@ -56,7 +56,7 @@ export function ErrorComponent(props: {
issueURL.searchParams.set("opencode-version", Installation.VERSION)
const copyIssueURL = () => {
void Clipboard.copy(issueURL.toString()).then(() => {
Clipboard.copy(issueURL.toString()).then(() => {
setCopied(true)
})
}

View File

@@ -12,7 +12,7 @@ import { useTheme, selectedForeground } from "@tui/context/theme"
import { SplitBorder } from "@tui/component/border"
import { useCommandDialog } from "@tui/component/dialog-command"
import { useTerminalDimensions } from "@opentui/solid"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import type { PromptInfo } from "./history"
import { useFrecency } from "./frecency"

View File

@@ -1,6 +1,6 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "../../context/helper"

View File

@@ -1,6 +1,6 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore, produce, unwrap } from "solid-js/store"
import { createSimpleContext } from "../../context/helper"

View File

@@ -3,7 +3,7 @@ import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, S
import "opentui-spinner/solid"
import path from "path"
import { fileURLToPath } from "url"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { useLocal } from "@tui/context/local"
import { useTheme } from "@tui/context/theme"
import { EmptyBorder, SplitBorder } from "@tui/component/border"
@@ -27,7 +27,7 @@ import { Clipboard } from "../../util/clipboard"
import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2"
import { TuiEvent } from "../../event"
import { iife } from "@/util/iife"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { formatDuration } from "@/util/format"
import { createColors, createFrames } from "../../ui/spinner.ts"
import { useDialog } from "@tui/ui/dialog"
@@ -235,7 +235,7 @@ export function Prompt(props: PromptProps) {
hidden: true,
onSelect: (dialog) => {
if (!input.focused) return
void submit()
submit()
dialog.clear()
},
},
@@ -280,7 +280,7 @@ export function Prompt(props: PromptProps) {
}, 5000)
if (store.interrupt >= 2) {
void sdk.client.session.abort({
sdk.client.session.abort({
sessionID: props.sessionID,
})
setStore("interrupt", 0)
@@ -429,7 +429,7 @@ export function Prompt(props: PromptProps) {
setStore("extmarkToPartIndex", new Map())
},
submit() {
void submit()
submit()
},
}
@@ -604,12 +604,12 @@ export function Prompt(props: PromptProps) {
if (!store.prompt.input) return
const trimmed = store.prompt.input.trim()
if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
void exit()
exit()
return
}
const selectedModel = local.model.current()
if (!selectedModel) {
void promptModelWarning()
promptModelWarning()
return
}
@@ -660,7 +660,7 @@ export function Prompt(props: PromptProps) {
const variant = local.model.variant.current()
if (store.mode === "shell") {
void sdk.client.session.shell({
sdk.client.session.shell({
sessionID,
agent: local.agent.current().name,
model: {
@@ -685,7 +685,7 @@ export function Prompt(props: PromptProps) {
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
void sdk.client.session.command({
sdk.client.session.command({
sessionID,
command: command.slice(1),
arguments: args,
@@ -1208,7 +1208,7 @@ export function Prompt(props: PromptProps) {
const r = retry()
if (!r) return
if (isTruncated()) {
void DialogAlert.show(dialog, "Retry Error", r.message)
DialogAlert.show(dialog, "Retry Error", r.message)
}
}

View File

@@ -1,6 +1,6 @@
import path from "path"
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { onMount } from "solid-js"
import { createStore, produce, unwrap } from "solid-js/store"
import { createSimpleContext } from "../../context/helper"

View File

@@ -1,7 +1,7 @@
import { createMemo } from "solid-js"
import type { KeyBinding } from "@opentui/core"
import { useKeybind } from "../context/keybind"
import { Keybind } from "@/util"
import { Keybind } from "@/util/keybind"
const TEXTAREA_ACTIONS = [
"submit",

View File

@@ -1,5 +1,5 @@
import { createMemo } from "solid-js"
import { Keybind } from "@/util"
import { Keybind } from "@/util/keybind"
import { pipe, mapValues } from "remeda"
import type { TuiConfig } from "@/config/tui"
import type { ParsedKey, Renderable } from "@opentui/core"

View File

@@ -1,5 +1,5 @@
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { createSignal, type Setter } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "./helper"
@@ -44,7 +44,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
},
set(key: string, value: any) {
setStore(key, value)
void Filesystem.writeJson(filePath, store)
Filesystem.writeJson(filePath, store)
},
}
return result

View File

@@ -12,7 +12,7 @@ import { Provider } from "@/provider"
import { useArgs } from "./args"
import { useSDK } from "./sdk"
import { RGBA } from "@opentui/core"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
@@ -131,7 +131,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return
}
state.pending = false
void Filesystem.writeJson(filePath, {
Filesystem.writeJson(filePath, {
recent: modelStore.recent,
favorite: modelStore.favorite,
variant: modelStore.variant,

View File

@@ -28,7 +28,7 @@ import type { Snapshot } from "@/snapshot"
import { useExit } from "./exit"
import { useArgs } from "./args"
import { batch, createEffect, on } from "solid-js"
import { Log } from "@/util"
import { Log } from "@/util/log"
import { ConsoleState, emptyConsoleState, type ConsoleState as ConsoleStateType } from "@/config/console-state"
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
@@ -111,7 +111,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
event.subscribe((event) => {
switch (event.type) {
case "server.instance.disposed":
void bootstrap()
bootstrap()
break
case "permission.replied": {
const requests = store.permission[event.properties.sessionID]
@@ -336,7 +336,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
case "lsp.updated": {
const workspace = project.workspace.current()
void sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", x.data ?? []))
sdk.client.lsp.status({ workspace }).then((x) => setStore("lsp", x.data ?? []))
break
}
@@ -415,7 +415,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
.then(() => {
if (store.status !== "complete") setStore("status", "partial")
// non-blocking
void Promise.all([
Promise.all([
...(args.continue ? [] : [sessionListPromise.then((sessions) => setStore("session", reconcile(sessions)))]),
consoleStatePromise.then((consoleState) => setStore("console_state", reconcile(consoleState))),
sdk.client.command.list({ workspace }).then((x) => setStore("command", reconcile(x.data ?? []))),

View File

@@ -40,7 +40,7 @@ import { useKV } from "./kv"
import { useRenderer } from "@opentui/solid"
import { createStore, produce } from "solid-js/store"
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { useTuiConfig } from "./tui-config"
import { isRecord } from "@/util/record"
import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui"
@@ -329,7 +329,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
})
function init() {
void Promise.allSettled([
Promise.allSettled([
resolveSystemTheme(store.mode),
getCustomThemes()
.then((custom) => {
@@ -377,7 +377,7 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
if (store.mode === mode) return
setStore("mode", mode)
renderer.clearPaletteCache()
void resolveSystemTheme(mode)
resolveSystemTheme(mode)
}
function pin(mode: "dark" | "light" = store.mode) {

View File

@@ -1,4 +1,4 @@
import { Keybind } from "@/util"
import { Keybind } from "@/util/keybind"
import type { TuiPlugin, TuiPluginApi, TuiPluginModule, TuiPluginStatus } from "@opencode-ai/plugin/tui"
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
import { fileURLToPath } from "url"
@@ -78,7 +78,7 @@ function Install(props: { api: TuiPluginApi }) {
}
setBusy(true)
void props.api.plugins
props.api.plugins
.install(mod, { global: global() })
.then((out) => {
if (!out.ok) {
@@ -188,7 +188,7 @@ function View(props: { api: TuiPluginApi }) {
if (!item) return
setLock(true)
const task = item.active ? props.api.plugins.deactivate(x) : props.api.plugins.activate(x)
void task
task
.then((ok) => {
if (!ok) {
props.api.ui.toast({

View File

@@ -15,7 +15,7 @@ import { fileURLToPath } from "url"
import { Config } from "@/config"
import { TuiConfig } from "@/config/tui"
import { Log } from "@/util"
import { Log } from "@/util/log"
import { errorData, errorMessage } from "@/util/error"
import { isRecord } from "@/util/record"
import { Instance } from "@/project/instance"
@@ -32,8 +32,8 @@ import { PluginMeta } from "@/plugin/meta"
import { installPlugin as installModulePlugin, patchPluginConfig, readPluginManifest } from "@/plugin/install"
import { hasTheme, upsertTheme } from "../context/theme"
import { Global } from "@/global"
import { Filesystem } from "@/util"
import { Process } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { Process } from "@/util/process"
import { Flock } from "@opencode-ai/shared/util/flock"
import { Flag } from "@/flag/flag"
import { INTERNAL_TUI_PLUGINS, type InternalTuiPlugin } from "./internal"

View File

@@ -2,7 +2,7 @@ import { createMemo, onMount } from "solid-js"
import { useSync } from "@tui/context/sync"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import type { TextPart } from "@opencode-ai/sdk/v2"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { useSDK } from "@tui/context/sdk"
import { useRoute } from "@tui/context/route"
import { useDialog } from "../../ui/dialog"

View File

@@ -29,7 +29,7 @@ export function DialogMessage(props: {
const msg = message()
if (!msg) return
void sdk.client.session.revert({
sdk.client.session.revert({
sessionID: props.sessionID,
messageID: msg.id,
})

View File

@@ -2,7 +2,7 @@ import { createMemo, onMount } from "solid-js"
import { useSync } from "@tui/context/sync"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import type { TextPart } from "@opencode-ai/sdk/v2"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { DialogMessage } from "./dialog-message"
import { useDialog } from "../../ui/dialog"
import type { PromptInfo } from "../../component/prompt/history"

View File

@@ -33,7 +33,7 @@ import type {
ReasoningPart,
} from "@opencode-ai/sdk/v2"
import { useLocal } from "@tui/context/local"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import type { Tool } from "@/tool/tool"
import type { ReadTool } from "@/tool/read"
import type { WriteTool } from "@/tool/write"
@@ -73,7 +73,7 @@ import { Editor } from "../../util/editor"
import stripAnsi from "strip-ansi"
import { usePromptRef } from "../../context/prompt"
import { useExit } from "../../context/exit"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { Global } from "@/global"
import { PermissionPrompt } from "./permission"
import { QuestionPrompt } from "./question"
@@ -241,7 +241,7 @@ export function Session() {
if (kv.get(GO_UPSELL_DONT_SHOW)) return
void DialogGoUpsell.show(dialog).then((dontShowAgain) => {
DialogGoUpsell.show(dialog).then((dontShowAgain) => {
if (dontShowAgain) kv.set(GO_UPSELL_DONT_SHOW, true)
kv.set(GO_UPSELL_LAST_SEEN_AT, Date.now())
})
@@ -272,7 +272,7 @@ export function Session() {
useKeyboard((evt) => {
if (!session()?.parentID) return
if (keybind.match("app_exit", evt)) {
void exit()
exit()
}
})
@@ -483,7 +483,7 @@ export function Session() {
})
return
}
void sdk.client.session.summarize({
sdk.client.session.summarize({
sessionID: route.sessionID,
modelID: selectedModel.modelID,
providerID: selectedModel.providerID,
@@ -529,7 +529,7 @@ export function Session() {
const revert = session()?.revert?.messageID
const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
if (!message) return
void sdk.client.session
sdk.client.session
.revert({
sessionID: route.sessionID,
messageID: message.id,
@@ -568,13 +568,13 @@ export function Session() {
if (!messageID) return
const message = messages().find((x) => x.role === "user" && x.id > messageID)
if (!message) {
void sdk.client.session.unrevert({
sdk.client.session.unrevert({
sessionID: route.sessionID,
})
prompt?.set({ input: "", parts: [] })
return
}
void sdk.client.session.revert({
sdk.client.session.revert({
sessionID: route.sessionID,
messageID: message.id,
})
@@ -1966,7 +1966,7 @@ function Task(props: ToolProps<typeof TaskTool>) {
onMount(() => {
if (props.metadata.sessionId && !sync.data.message[props.metadata.sessionId]?.length)
void sync.session.sync(props.metadata.sessionId)
sync.session.sync(props.metadata.sessionId)
})
const messages = createMemo(() => sync.data.message[props.metadata.sessionId ?? ""] ?? [])

View File

@@ -11,8 +11,8 @@ import { useSync } from "../../context/sync"
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
import path from "path"
import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
import { Keybind } from "@/util"
import { Locale } from "@/util"
import { Keybind } from "@/util/keybind"
import { Locale } from "@/util/locale"
import { Global } from "@/global"
import { useDialog } from "../../ui/dialog"
import { getScrollAcceleration } from "../../util/scroll"
@@ -184,7 +184,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
onSelect={(option) => {
setStore("stage", "permission")
if (option === "cancel") return
void sdk.client.permission.reply({
sdk.client.permission.reply({
reply: "always",
requestID: props.request.id,
})
@@ -194,7 +194,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
<Match when={store.stage === "reject"}>
<RejectPrompt
onConfirm={(message) => {
void sdk.client.permission.reply({
sdk.client.permission.reply({
reply: "reject",
requestID: props.request.id,
message: message || undefined,
@@ -447,13 +447,13 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
setStore("stage", "reject")
return
}
void sdk.client.permission.reply({
sdk.client.permission.reply({
reply: "reject",
requestID: props.request.id,
})
return
}
void sdk.client.permission.reply({
sdk.client.permission.reply({
reply: "once",
requestID: props.request.id,
})

View File

@@ -45,14 +45,14 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
function submit() {
const answers = questions().map((_, i) => store.answers[i] ?? [])
void sdk.client.question.reply({
sdk.client.question.reply({
requestID: props.request.id,
answers,
})
}
function reject() {
void sdk.client.question.reject({
sdk.client.question.reject({
requestID: props.request.id,
})
}
@@ -67,7 +67,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
setStore("custom", inputs)
}
if (single()) {
void sdk.client.question.reply({
sdk.client.question.reply({
requestID: props.request.id,
answers: [[answer]],
})

View File

@@ -6,7 +6,7 @@ import { SplitBorder } from "@tui/component/border"
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
import { useCommandDialog } from "@tui/component/dialog-command"
import { useKeybind } from "../../context/keybind"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import { useTerminalDimensions } from "@opentui/solid"
export function SubagentFooter() {

View File

@@ -1,15 +1,15 @@
import { cmd } from "@/cli/cmd/cmd"
import { tui } from "./app"
import { Rpc } from "@/util"
import { Rpc } from "@/util/rpc"
import { type rpc } from "./worker"
import path from "path"
import { fileURLToPath } from "url"
import { UI } from "@/cli/ui"
import { Log } from "@/util"
import { Log } from "@/util/log"
import { errorMessage } from "@/util/error"
import { withTimeout } from "@/util/timeout"
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
import type { EventSource } from "./context/sdk"
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"

View File

@@ -4,7 +4,7 @@ import { useDialog, type DialogContext } from "./dialog"
import { createStore } from "solid-js/store"
import { For } from "solid-js"
import { useKeyboard } from "@opentui/solid"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
export type DialogConfirmProps = {
title: string

View File

@@ -8,8 +8,8 @@ import * as fuzzysort from "fuzzysort"
import { isDeepEqual } from "remeda"
import { useDialog, type DialogContext } from "@tui/ui/dialog"
import { useKeybind } from "@tui/context/keybind"
import { Keybind } from "@/util"
import { Locale } from "@/util"
import { Keybind } from "@/util/keybind"
import { Locale } from "@/util/locale"
import { getScrollAcceleration } from "../util/scroll"
import { useTuiConfig } from "../context/tui-config"

View File

@@ -4,8 +4,8 @@ import { lazy } from "../../../../util/lazy.js"
import { tmpdir } from "os"
import path from "path"
import fs from "fs/promises"
import { Filesystem } from "../../../../util"
import { Process } from "../../../../util"
import { Filesystem } from "../../../../util/filesystem"
import { Process } from "../../../../util/process"
import { which } from "../../../../util/which"
/**

View File

@@ -3,8 +3,8 @@ 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"
import { Process } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { Process } from "@/util/process"
export namespace Editor {
export async function open(opts: { value: string; renderer: CliRenderer }): Promise<string | undefined> {

View File

@@ -2,7 +2,7 @@ import { Player } from "cli-sound"
import { mkdirSync } from "node:fs"
import { tmpdir } from "node:os"
import { basename, join } from "node:path"
import { Process } from "@/util"
import { Process } from "@/util/process"
import { which } from "@/util/which"
import pulseA from "../asset/pulse-a.wav" with { type: "file" }
import pulseB from "../asset/pulse-b.wav" with { type: "file" }

View File

@@ -1,5 +1,5 @@
import type { AssistantMessage, Part, Provider, UserMessage } from "@opencode-ai/sdk/v2"
import { Locale } from "@/util"
import { Locale } from "@/util/locale"
import * as Model from "./model"
export type TranscriptOptions = {

View File

@@ -1,9 +1,9 @@
import { Installation } from "@/installation"
import { Server } from "@/server/server"
import { Log } from "@/util"
import { Log } from "@/util/log"
import { Instance } from "@/project/instance"
import { InstanceBootstrap } from "@/project/bootstrap"
import { Rpc } from "@/util"
import { Rpc } from "@/util/rpc"
import { upgrade } from "@/cli/upgrade"
import { Config } from "@/config"
import { GlobalBus } from "@/bus/global"

View File

@@ -7,8 +7,8 @@ import { Global } from "../../global"
import fs from "fs/promises"
import path from "path"
import os from "os"
import { Filesystem } from "../../util"
import { Process } from "../../util"
import { Filesystem } from "../../util/filesystem"
import { Process } from "../../util/process"
interface UninstallArgs {
keepConfig: boolean

View File

@@ -34,7 +34,7 @@ export const WebCommand = cmd({
describe: "start opencode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = await Server.listen(opts)

View File

@@ -2,7 +2,7 @@ import path from "path"
import { writeHeapSnapshot } from "node:v8"
import { Flag } from "@/flag/flag"
import { Global } from "@/global"
import { Log } from "@/util"
import { Log } from "@/util/log"
const log = Log.create({ service: "heap" })
const MINUTE = 60_000

View File

@@ -1,6 +1,6 @@
import { BusEvent } from "@/bus/bus-event"
import { InstanceState } from "@/effect"
import { EffectBridge } from "@/effect"
import { InstanceState } from "@/effect/instance-state"
import { EffectBridge } from "@/effect/bridge"
import type { InstanceContext } from "@/project/instance"
import { SessionID, MessageID } from "@/session/schema"
import { Effect, Layer, Context } from "effect"

View File

@@ -1,8 +1,8 @@
import { Log } from "../util"
import { Log } from "../util/log"
import path from "path"
import { pathToFileURL } from "url"
import os from "os"
import { Process } from "../util"
import { Process } from "../util/process"
import z from "zod"
import { mergeDeep, pipe, unique } from "remeda"
import { Global } from "../global"
@@ -32,7 +32,7 @@ import { isRecord } from "@/util/record"
import { ConfigPaths } from "./paths"
import type { ConsoleState } from "./console-state"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { InstanceState } from "@/effect"
import { InstanceState } from "@/effect/instance-state"
import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect"
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
@@ -171,7 +171,7 @@ async function loadCommand(dir: string) {
? err.data.message
: `Failed to parse command ${item}`
const { Session } = await import("@/session")
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
log.error("failed to load command", { command: item, err })
return undefined
})
@@ -210,7 +210,7 @@ async function loadAgent(dir: string) {
? err.data.message
: `Failed to parse agent ${item}`
const { Session } = await import("@/session")
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
log.error("failed to load agent", { agent: item, err })
return undefined
})
@@ -248,7 +248,7 @@ async function loadMode(dir: string) {
? err.data.message
: `Failed to parse mode ${item}`
const { Session } = await import("@/session")
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
log.error("failed to load mode", { mode: item, err })
return undefined
})

View File

@@ -1,7 +1,7 @@
import { NamedError } from "@opencode-ai/shared/util/error"
import matter from "gray-matter"
import { z } from "zod"
import { Filesystem } from "../util"
import { Filesystem } from "../util/filesystem"
export namespace ConfigMarkdown {
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g

View File

@@ -3,7 +3,7 @@ import os from "os"
import z from "zod"
import { type ParseError as JsoncParseError, parse as parseJsonc, printParseErrorCode } from "jsonc-parser"
import { NamedError } from "@opencode-ai/shared/util/error"
import { Filesystem } from "@/util"
import { Filesystem } from "@/util/filesystem"
import { Flag } from "@/flag/flag"
import { Global } from "@/global"

View File

@@ -6,8 +6,8 @@ import { ConfigPaths } from "./paths"
import { TuiInfo, TuiOptions } from "./tui-schema"
import { Instance } from "@/project/instance"
import { Flag } from "@/flag/flag"
import { Log } from "@/util"
import { Filesystem } from "@/util"
import { Log } from "@/util/log"
import { Filesystem } from "@/util/filesystem"
import { Global } from "@/global"
const log = Log.create({ service: "tui.migrate" })

Some files were not shown because too many files have changed in this diff Show More