Răsfoiți Sursa

Fix shutdown handling, error management, and process lifecycle issues

🤖 Generated with [OpenCode](https://opencode.ai)

Co-Authored-By: OpenCode <[email protected]>
Dax Raad 8 luni în urmă
părinte
comite
28f5cbbfe9

+ 4 - 3
packages/opencode/src/app/app.ts

@@ -97,7 +97,7 @@ export namespace App {
         log.info("registering service", { name: key })
         log.info("registering service", { name: key })
         services.set(key, {
         services.set(key, {
           state: init(app.info),
           state: init(app.info),
-          shutdown: shutdown,
+          shutdown,
         })
         })
       }
       }
       return services.get(key)?.state as State
       return services.get(key)?.state as State
@@ -108,14 +108,15 @@ export namespace App {
     return ctx.use().info
     return ctx.use().info
   }
   }
 
 
-  export async function provide<T extends (app: Info) => any>(
+  export async function provide<T>(
     input: { cwd: string; version: string },
     input: { cwd: string; version: string },
-    cb: T,
+    cb: (app: Info) => Promise<T>,
   ) {
   ) {
     const app = await create(input)
     const app = await create(input)
     return ctx.provide(app, async () => {
     return ctx.provide(app, async () => {
       const result = await cb(app.info)
       const result = await cb(app.info)
       for (const [key, entry] of app.services.entries()) {
       for (const [key, entry] of app.services.entries()) {
+        if (!entry.shutdown) continue
         log.info("shutdown", { name: key })
         log.info("shutdown", { name: key })
         await entry.shutdown?.(await entry.state)
         await entry.shutdown?.(await entry.state)
       }
       }

+ 6 - 4
packages/opencode/src/cli/cmd/provider.ts

@@ -6,6 +6,7 @@ import * as prompts from "@clack/prompts"
 import open from "open"
 import open from "open"
 import { VERSION } from "../version"
 import { VERSION } from "../version"
 import { Provider } from "../../provider/provider"
 import { Provider } from "../../provider/provider"
+import { UI } from "../ui"
 
 
 export const ProviderCommand = cmd({
 export const ProviderCommand = cmd({
   command: "provider",
   command: "provider",
@@ -62,7 +63,7 @@ export const ProviderAddCommand = cmd({
           },
           },
         ],
         ],
       })
       })
-      if (prompts.isCancel(provider)) return
+      if (prompts.isCancel(provider)) throw new UI.CancelledError({})
 
 
       if (provider === "anthropic") {
       if (provider === "anthropic") {
         const method = await prompts.select({
         const method = await prompts.select({
@@ -78,7 +79,7 @@ export const ProviderAddCommand = cmd({
             },
             },
           ],
           ],
         })
         })
-        if (prompts.isCancel(method)) return
+        if (prompts.isCancel(method)) throw new UI.CancelledError({})
 
 
         if (method === "oauth") {
         if (method === "oauth") {
           // some weird bug where program exits without this
           // some weird bug where program exits without this
@@ -92,7 +93,8 @@ export const ProviderAddCommand = cmd({
             message: "Paste the authorization code here: ",
             message: "Paste the authorization code here: ",
             validate: (x) => (x.length > 0 ? undefined : "Required"),
             validate: (x) => (x.length > 0 ? undefined : "Required"),
           })
           })
-          if (prompts.isCancel(code)) return
+          if (prompts.isCancel(code)) throw new UI.CancelledError({})
+
           await AuthAnthropic.exchange(code, verifier)
           await AuthAnthropic.exchange(code, verifier)
             .then(() => {
             .then(() => {
               prompts.log.success("Login successful")
               prompts.log.success("Login successful")
@@ -109,7 +111,7 @@ export const ProviderAddCommand = cmd({
         message: "Enter your API key",
         message: "Enter your API key",
         validate: (x) => (x.length > 0 ? undefined : "Required"),
         validate: (x) => (x.length > 0 ? undefined : "Required"),
       })
       })
-      if (prompts.isCancel(key)) return
+      if (prompts.isCancel(key)) throw new UI.CancelledError({})
       await AuthKeys.set(provider, key)
       await AuthKeys.set(provider, key)
 
 
       prompts.outro("Done")
       prompts.outro("Done")

+ 7 - 1
packages/opencode/src/cli/ui.ts

@@ -1,4 +1,5 @@
-import { VERSION } from "./version"
+import { z } from "zod"
+import { NamedError } from "../util/error"
 
 
 export namespace UI {
 export namespace UI {
   const LOGO = [
   const LOGO = [
@@ -7,6 +8,11 @@ export namespace UI {
     `▀▀▀▀ █▀▀▀ ▀▀▀ ▀  ▀ ▀▀▀ ▀▀▀▀ ▀▀▀  ▀▀▀`,
     `▀▀▀▀ █▀▀▀ ▀▀▀ ▀  ▀ ▀▀▀ ▀▀▀▀ ▀▀▀  ▀▀▀`,
   ]
   ]
 
 
+  export const CancelledError = NamedError.create(
+    "UICancelledError",
+    z.object({}),
+  )
+
   export const Style = {
   export const Style = {
     TEXT_HIGHLIGHT: "\x1b[96m",
     TEXT_HIGHLIGHT: "\x1b[96m",
     TEXT_HIGHLIGHT_BOLD: "\x1b[96m\x1b[1m",
     TEXT_HIGHLIGHT_BOLD: "\x1b[96m\x1b[1m",

+ 69 - 55
packages/opencode/src/index.ts

@@ -21,62 +21,76 @@ import { UI } from "./cli/ui"
 
 
 await Log.init({ print: process.argv.includes("--print-logs") })
 await Log.init({ print: process.argv.includes("--print-logs") })
 
 
-yargs(hideBin(process.argv))
-  .scriptName("opencode")
-  .version(VERSION)
-  .command({
-    command: "$0",
-    describe: "Start OpenCode TUI",
-    builder: (yargs) =>
-      yargs.option("print-logs", {
-        type: "boolean",
-      }),
-    handler: async (args) => {
-      UI.logo()
-      await App.provide({ cwd: process.cwd(), version: VERSION }, async () => {
-        const providers = await Provider.list()
-        if (Object.keys(providers).length === 0) {
-          await ProviderAddCommand.handler(args)
-          return
-        }
+try {
+  await yargs(hideBin(process.argv))
+    .scriptName("opencode")
+    .version(VERSION)
+    .command({
+      command: "$0",
+      describe: "Start OpenCode TUI",
+      handler: async (args) => {
+        while (true) {
+          const result = await App.provide(
+            { cwd: process.cwd(), version: VERSION },
+            async () => {
+              const providers = await Provider.list()
+              if (Object.keys(providers).length === 0) {
+                return "needs_provider"
+              }
+
+              await Share.init()
+              const server = Server.listen()
 
 
-        await Share.init()
-        const server = Server.listen()
+              let cmd = ["go", "run", "./main.go"]
+              let cwd = new URL("../../tui/cmd/opencode", import.meta.url)
+                .pathname
+              if (Bun.embeddedFiles.length > 0) {
+                const blob = Bun.embeddedFiles[0] as File
+                const binary = path.join(Global.Path.cache, "tui", blob.name)
+                const file = Bun.file(binary)
+                if (!(await file.exists())) {
+                  await Bun.write(file, blob, { mode: 0o755 })
+                  await fs.chmod(binary, 0o755)
+                }
+                cwd = process.cwd()
+                cmd = [binary]
+              }
+              const proc = Bun.spawn({
+                cmd,
+                cwd,
+                stdout: "inherit",
+                stderr: "inherit",
+                stdin: "inherit",
+                env: {
+                  ...process.env,
+                  OPENCODE_SERVER: server.url.toString(),
+                },
+                onExit: () => {
+                  server.stop()
+                },
+              })
+              await proc.exited
+              await server.stop()
 
 
-        let cmd = ["go", "run", "./main.go"]
-        let cwd = new URL("../../tui/cmd/opencode", import.meta.url).pathname
-        if (Bun.embeddedFiles.length > 0) {
-          const blob = Bun.embeddedFiles[0] as File
-          const binary = path.join(Global.Path.cache, "tui", blob.name)
-          const file = Bun.file(binary)
-          if (!(await file.exists())) {
-            await Bun.write(file, blob, { mode: 0o755 })
-            await fs.chmod(binary, 0o755)
+              return "done"
+            },
+          )
+          if (result === "done") break
+          if (result === "needs_provider") {
+            UI.logo()
+            await ProviderAddCommand.handler(args)
           }
           }
-          cwd = process.cwd()
-          cmd = [binary]
         }
         }
-        const proc = Bun.spawn({
-          cmd,
-          cwd,
-          stdout: "inherit",
-          stderr: "inherit",
-          stdin: "inherit",
-          env: {
-            ...process.env,
-            OPENCODE_SERVER: server.url.toString(),
-          },
-          onExit: () => {
-            server.stop()
-          },
-        })
-        await proc.exited
-        await server.stop()
-      })
-    },
-  })
-  .command(RunCommand)
-  .command(GenerateCommand)
-  .command(ScrapCommand)
-  .command(ProviderCommand)
-  .parse()
+      },
+    })
+    .command(RunCommand)
+    .command(GenerateCommand)
+    .command(ScrapCommand)
+    .command(ProviderCommand)
+    .fail((msg, err) => {
+      Log.Default.error(msg)
+    })
+    .parse()
+} catch (e) {
+  Log.Default.error(e)
+}

+ 1 - 1
packages/opencode/src/lsp/client.ts

@@ -169,7 +169,7 @@ export namespace LSPClient {
         log.info("shutting down")
         log.info("shutting down")
         connection.end()
         connection.end()
         connection.dispose()
         connection.dispose()
-        server.process.kill()
+        server.process.kill("SIGKILL")
       },
       },
     }
     }
 
 

+ 1 - 1
packages/opencode/src/lsp/server.ts

@@ -32,7 +32,7 @@ export namespace LSPServer {
         ".cts",
         ".cts",
       ],
       ],
       async spawn(app) {
       async spawn(app) {
-        const tsserver = Bun.resolve(
+        const tsserver = await Bun.resolve(
           "typescript/lib/tsserver.js",
           "typescript/lib/tsserver.js",
           app.path.cwd,
           app.path.cwd,
         ).catch(() => {})
         ).catch(() => {})

+ 1 - 1
packages/opencode/src/util/log.ts

@@ -2,7 +2,7 @@ import path from "path"
 import fs from "fs/promises"
 import fs from "fs/promises"
 import { Global } from "../global"
 import { Global } from "../global"
 export namespace Log {
 export namespace Log {
-  const Default = create()
+  export const Default = create()
 
 
   export interface Options {
   export interface Options {
     print: boolean
     print: boolean