mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-22 11:55:46 +00:00
feat: add local server transcript and terminal action
This commit is contained in:
@@ -255,6 +255,7 @@ registerIpcHandlers({
|
||||
setLocalServerConfig: (config) => localServer.setConfig(config),
|
||||
runLocalServerStep: (step) => localServer.runStep(step),
|
||||
cancelLocalServerJob: () => localServer.cancelJob(),
|
||||
openLocalServerTerminal: () => localServer.openTerminal(),
|
||||
onLocalServerEvent: (listener) => localServer.subscribe(listener),
|
||||
getDefaultServerUrl: () => getDefaultServerUrl(),
|
||||
setDefaultServerUrl: (url) => setDefaultServerUrl(url),
|
||||
|
||||
@@ -27,6 +27,7 @@ type Deps = {
|
||||
setLocalServerConfig: (config: LocalServerConfig) => Promise<void> | void
|
||||
runLocalServerStep: (step: LocalServerStep) => Promise<void> | void
|
||||
cancelLocalServerJob: () => Promise<void> | void
|
||||
openLocalServerTerminal: () => Promise<void> | void
|
||||
onLocalServerEvent: (listener: (event: LocalServerEvent) => void) => () => void
|
||||
getDefaultServerUrl: () => Promise<string | null> | string | null
|
||||
setDefaultServerUrl: (url: string | null) => Promise<void> | void
|
||||
@@ -61,6 +62,11 @@ export function registerIpcHandlers(deps: Deps) {
|
||||
ipcMain.handle("local-server-set-config", (_event: IpcMainInvokeEvent, config: LocalServerConfig) =>
|
||||
deps.setLocalServerConfig(config),
|
||||
)
|
||||
ipcMain.handle("local-server-run-step", (_event: IpcMainInvokeEvent, step: LocalServerStep) =>
|
||||
deps.runLocalServerStep(step),
|
||||
)
|
||||
ipcMain.handle("local-server-cancel-job", () => deps.cancelLocalServerJob())
|
||||
ipcMain.handle("local-server-open-terminal", () => deps.openLocalServerTerminal())
|
||||
ipcMain.handle("get-default-server-url", () => deps.getDefaultServerUrl())
|
||||
ipcMain.handle("set-default-server-url", (_event: IpcMainInvokeEvent, url: string | null) =>
|
||||
deps.setDefaultServerUrl(url),
|
||||
|
||||
@@ -4,10 +4,11 @@ import type {
|
||||
LocalServerEvent,
|
||||
LocalServerState,
|
||||
LocalServerStep,
|
||||
LocalServerTranscriptLine,
|
||||
} from "../preload/types"
|
||||
import { LOCAL_SERVER_KEY } from "./constants"
|
||||
import { store } from "./store"
|
||||
import { listInstalledWslDistros, listOnlineWslDistros, probeWslDistro, probeWslRuntime } from "./wsl"
|
||||
import { listInstalledWslDistros, listOnlineWslDistros, openWslTerminal, probeWslDistro, probeWslRuntime } from "./wsl"
|
||||
|
||||
export function defaultLocalServerConfig(): LocalServerConfig {
|
||||
return {
|
||||
@@ -39,6 +40,20 @@ export function createLocalServerController() {
|
||||
emit({ type: "state", state })
|
||||
}
|
||||
|
||||
const appendTranscript = (line: Omit<LocalServerTranscriptLine, "at">) => {
|
||||
update({
|
||||
...state,
|
||||
transcript: [...state.transcript, { ...line, at: Date.now() }],
|
||||
})
|
||||
}
|
||||
|
||||
const clearTranscript = () => {
|
||||
update({
|
||||
...state,
|
||||
transcript: [],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
getState() {
|
||||
return state
|
||||
@@ -59,6 +74,8 @@ export function createLocalServerController() {
|
||||
jobAbort?.abort()
|
||||
const abort = new AbortController()
|
||||
jobAbort = abort
|
||||
clearTranscript()
|
||||
appendTranscript({ stream: "system", text: `Running local server step: ${step}` })
|
||||
update({
|
||||
...state,
|
||||
job: { step, startedAt: Date.now() },
|
||||
@@ -67,7 +84,10 @@ export function createLocalServerController() {
|
||||
|
||||
try {
|
||||
if (step === "wsl") {
|
||||
const wsl = await probeWslRuntime({ signal: abort.signal })
|
||||
const wsl = await probeWslRuntime({
|
||||
signal: abort.signal,
|
||||
onLine: (line) => appendTranscript(line),
|
||||
})
|
||||
if (jobAbort !== abort) return
|
||||
update({
|
||||
...state,
|
||||
@@ -85,15 +105,24 @@ export function createLocalServerController() {
|
||||
|
||||
if (step === "distro") {
|
||||
const [installedResult, onlineResult] = await Promise.allSettled([
|
||||
listInstalledWslDistros({ signal: abort.signal }),
|
||||
listOnlineWslDistros({ signal: abort.signal }),
|
||||
listInstalledWslDistros({
|
||||
signal: abort.signal,
|
||||
onLine: (line) => appendTranscript(line),
|
||||
}),
|
||||
listOnlineWslDistros({
|
||||
signal: abort.signal,
|
||||
onLine: (line) => appendTranscript(line),
|
||||
}),
|
||||
])
|
||||
if (jobAbort !== abort) return
|
||||
|
||||
const installed = installedResult.status === "fulfilled" ? installedResult.value : []
|
||||
const online = onlineResult.status === "fulfilled" ? onlineResult.value : []
|
||||
const selected = state.config.distro
|
||||
? await probeWslDistro(state.config.distro, { signal: abort.signal })
|
||||
? await probeWslDistro(state.config.distro, {
|
||||
signal: abort.signal,
|
||||
onLine: (line) => appendTranscript(line),
|
||||
})
|
||||
: null
|
||||
if (jobAbort !== abort) return
|
||||
|
||||
@@ -144,12 +173,17 @@ export function createLocalServerController() {
|
||||
cancelJob() {
|
||||
jobAbort?.abort()
|
||||
jobAbort = undefined
|
||||
appendTranscript({ stream: "system", text: "Canceled local server job" })
|
||||
update({
|
||||
...state,
|
||||
job: null,
|
||||
status: { kind: "idle" },
|
||||
})
|
||||
},
|
||||
async openTerminal() {
|
||||
if (!state.config.distro) throw new Error("No WSL distro selected")
|
||||
await openWslTerminal(state.config.distro)
|
||||
},
|
||||
setRuntime(runtime: LocalServerState["runtime"]) {
|
||||
update({
|
||||
...state,
|
||||
@@ -176,6 +210,7 @@ function toState(config: LocalServerConfig, current?: LocalServerState): LocalSe
|
||||
status: current?.status ?? { kind: "idle" },
|
||||
job: current?.job ?? null,
|
||||
checks: current?.checks ?? { wsl: null, distro: null },
|
||||
transcript: current?.transcript ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +191,21 @@ export async function upgradeWslOpencode(target: string, distro: string, opts?:
|
||||
return runWslBash(`opencode upgrade ${shellEscape(target)}`, distro, opts)
|
||||
}
|
||||
|
||||
export function openWslTerminal(distro?: string | null) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const child = spawn("cmd.exe", ["/c", "start", "", "wsl", ...(distro ? ["-d", distro] : [])], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
})
|
||||
child.once("error", reject)
|
||||
child.once("spawn", () => {
|
||||
child.unref()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function parseInstalledDistros(output: string) {
|
||||
return output.split(/\r?\n/g).flatMap((line) => {
|
||||
const trimmed = line.trim()
|
||||
|
||||
@@ -16,6 +16,7 @@ const api: ElectronAPI = {
|
||||
setConfig: (config) => ipcRenderer.invoke("local-server-set-config", config),
|
||||
runStep: (step) => ipcRenderer.invoke("local-server-run-step", step),
|
||||
cancelJob: () => ipcRenderer.invoke("local-server-cancel-job"),
|
||||
openTerminal: () => ipcRenderer.invoke("local-server-open-terminal"),
|
||||
subscribe: (cb) => {
|
||||
const handler = (_: unknown, event: LocalServerEvent) => cb(event)
|
||||
ipcRenderer.on("local-server-event", handler)
|
||||
|
||||
@@ -45,6 +45,11 @@ export type LocalServerDistroCheck = {
|
||||
selected: LocalServerDistroProbe | null
|
||||
error: string | null
|
||||
}
|
||||
export type LocalServerTranscriptLine = {
|
||||
stream: "stdout" | "stderr" | "system"
|
||||
text: string
|
||||
at: number
|
||||
}
|
||||
export type LocalServerConfig = {
|
||||
mode: LocalServerMode
|
||||
distro: string | null
|
||||
@@ -76,6 +81,7 @@ export type LocalServerState = {
|
||||
wsl: LocalServerWslCheck | null
|
||||
distro: LocalServerDistroCheck | null
|
||||
}
|
||||
transcript: LocalServerTranscriptLine[]
|
||||
}
|
||||
export type LocalServerEvent = {
|
||||
type: "state"
|
||||
@@ -86,6 +92,7 @@ export type LocalServerAPI = {
|
||||
setConfig: (config: LocalServerConfig) => Promise<void>
|
||||
runStep: (step: LocalServerStep) => Promise<void>
|
||||
cancelJob: () => Promise<void>
|
||||
openTerminal: () => Promise<void>
|
||||
subscribe: (cb: (event: LocalServerEvent) => void) => () => void
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user