Dax Raad пре 7 месеци
родитељ
комит
26dcb85de1

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

@@ -36,12 +36,15 @@ export namespace App {
     services: Map<any, { state: any; shutdown?: (input: any) => Promise<void> }>
   }>("app")
 
+  export const use = ctx.use
+
   const APP_JSON = "app.json"
 
   export type Input = {
     cwd: string
   }
 
+  export const provideExisting = ctx.provide
   export async function provide<T>(
     input: Input,
     cb: (app: App.Info) => Promise<T>,

+ 2 - 0
packages/opencode/src/cli/bootstrap.ts

@@ -1,5 +1,6 @@
 import { App } from "../app/app"
 import { ConfigHooks } from "../config/hooks"
+import { FileWatcher } from "../file/watch"
 import { Format } from "../format"
 import { LSP } from "../lsp"
 import { Share } from "../share/share"
@@ -13,6 +14,7 @@ export async function bootstrap<T>(
     Format.init()
     ConfigHooks.init()
     LSP.init()
+    FileWatcher.init()
 
     return cb(app)
   })

+ 11 - 0
packages/opencode/src/cli/cmd/debug/index.ts

@@ -1,3 +1,4 @@
+import { bootstrap } from "../../bootstrap"
 import { cmd } from "../cmd"
 import { FileCommand } from "./file"
 import { LSPCommand } from "./lsp"
@@ -12,6 +13,16 @@ export const DebugCommand = cmd({
       .command(RipgrepCommand)
       .command(FileCommand)
       .command(SnapshotCommand)
+      .command({
+        command: "wait",
+        async handler() {
+          await bootstrap({ cwd: process.cwd() }, async () => {
+            await new Promise((resolve) =>
+              setTimeout(resolve, 1_000 * 60 * 60 * 24),
+            )
+          })
+        },
+      })
       .demandCommand(),
   async handler() {},
 })

+ 2 - 2
packages/opencode/src/cli/cmd/serve.ts

@@ -1,7 +1,7 @@
-import { App } from "../../app/app"
 import { Provider } from "../../provider/provider"
 import { Server } from "../../server/server"
 import { Share } from "../../share/share"
+import { bootstrap } from "../bootstrap"
 import { cmd } from "./cmd"
 
 export const ServeCommand = cmd({
@@ -23,7 +23,7 @@ export const ServeCommand = cmd({
   describe: "starts a headless opencode server",
   handler: async (args) => {
     const cwd = process.cwd()
-    await App.provide({ cwd }, async () => {
+    await bootstrap({ cwd }, async () => {
       const providers = await Provider.list()
       if (Object.keys(providers).length === 0) {
         return "needs_provider"

+ 50 - 0
packages/opencode/src/file/watch.ts

@@ -0,0 +1,50 @@
+import { z } from "zod"
+import { Bus } from "../bus"
+import fs from "fs"
+import { App } from "../app/app"
+import { Log } from "../util/log"
+
+export namespace FileWatcher {
+  const log = Log.create({ service: "file.watcher" })
+
+  export const Event = {
+    Updated: Bus.event(
+      "file.watcher.updated",
+      z.object({
+        file: z.string(),
+        event: z.union([z.literal("rename"), z.literal("change")]),
+      }),
+    ),
+  }
+
+  export function init() {
+    App.state(
+      "file.watcher",
+      () => {
+        const app = App.use()
+        const watcher = fs.watch(
+          app.info.path.cwd,
+          { recursive: true },
+          (event, file) => {
+            log.info("change", { file, event })
+            if (!file) return
+            // for some reason async local storage is lost here
+            // https://github.com/oven-sh/bun/issues/20754
+            App.provideExisting(app, async () => {
+              Bus.publish(Event.Updated, {
+                file,
+                event,
+              })
+            })
+          },
+        )
+        return {
+          watcher,
+        }
+      },
+      async (state) => {
+        state.watcher.close()
+      },
+    )()
+  }
+}

+ 5 - 0
packages/opencode/src/session/message.ts

@@ -188,6 +188,11 @@ export namespace Message {
               }),
             })
             .optional(),
+          user: z
+            .object({
+              snapshot: z.string().optional(),
+            })
+            .optional(),
         })
         .openapi({ ref: "MessageMetadata" }),
     })