Переглянути джерело

fix(cli): restore plugin list diagnostics (#37540)

Co-authored-by: Aiden Cline <rekram1-node@users.noreply.github.com>
opencode-agent[bot] 4 тижнів тому
батько
коміт
f2a4011371

+ 4 - 0
packages/cli/src/commands/commands.ts

@@ -114,6 +114,10 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
         }),
       ],
     }),
+    Spec.make("plugin", {
+      description: "Manage plugins",
+      commands: [Spec.make("list", { description: "List active plugins" })],
+    }),
     Spec.make("migrate", { description: "Migrate v1 data to v2" }),
     Spec.make("mini", {
       description: "Start the minimal interactive interface",

+ 24 - 0
packages/cli/src/commands/handlers/plugin/list.ts

@@ -0,0 +1,24 @@
+import { EOL } from "node:os"
+import { Effect } from "effect"
+import { OpenCode } from "@opencode-ai/client"
+import { Service } from "@opencode-ai/client/effect/service"
+import { Commands } from "../../commands"
+import { Runtime } from "../../../framework/runtime"
+import { ServiceConfig } from "../../../services/service-config"
+
+export default Runtime.handler(
+  Commands.commands.plugin.commands.list,
+  Effect.fn("cli.plugin.list")(function* () {
+    const options = yield* ServiceConfig.options()
+    const found = yield* Service.discover(options)
+    const endpoint = found ?? (yield* Service.ensure(options))
+    const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
+    const response = yield* Effect.promise(() => client.plugin.list({ location: { directory: process.cwd() } }))
+    const plugins = response.data.toSorted((a, b) => a.id.localeCompare(b.id))
+    if (plugins.length === 0) {
+      process.stdout.write("No plugins loaded" + EOL)
+      return
+    }
+    process.stdout.write(plugins.map((plugin) => plugin.id).join(EOL) + EOL)
+  }),
+)

+ 3 - 0
packages/cli/src/index.ts

@@ -32,6 +32,9 @@ const Handlers = Runtime.handlers(Commands, {
     auth: () => import("./commands/handlers/mcp/auth"),
     logout: () => import("./commands/handlers/mcp/logout"),
   },
+  plugin: {
+    list: () => import("./commands/handlers/plugin/list"),
+  },
   migrate: () => import("./commands/handlers/migrate"),
   mini: () => import("./commands/handlers/mini"),
   run: () => import("./commands/handlers/run"),

+ 5 - 1
packages/core/src/plugin/supervisor.ts

@@ -142,7 +142,11 @@ const resolve = Effect.fn("PluginSupervisor.resolve")(function* (
       continue
     }
 
-    const plugin = yield* load(operation).pipe(Effect.catchCause(() => Effect.succeed(undefined)))
+    const plugin = yield* load(operation).pipe(
+      Effect.catchCause((cause) =>
+        Effect.logWarning("failed to load plugin", { target: operation.target, cause }).pipe(Effect.as(undefined)),
+      ),
+    )
     if (!plugin) continue
     const previous = packages.get(operation.target)
     if (previous) enabled.delete(previous.id)

+ 16 - 5
packages/core/test/config/plugin.test.ts

@@ -18,7 +18,7 @@ import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { AbsolutePath } from "@opencode-ai/core/schema"
-import { Effect } from "effect"
+import { Effect, Logger } from "effect"
 import { Database } from "../../src/database/database"
 import { tmpdir } from "../fixture/tmpdir"
 import { testEffect } from "../lib/effect"
@@ -111,8 +111,15 @@ describe("PluginSupervisor config", () => {
     ),
   )
 
-  it.live("ignores invalid packages and continues loading", () =>
-    withLocation(
+  it.live("logs invalid packages and continues loading", () => {
+    const output: string[] = []
+    const logger = Logger.map(Logger.formatStructured, (entry) => {
+      if (!Array.isArray(entry.message) || entry.message[0] !== "failed to load plugin") return
+      const details = entry.message[1]
+      if (typeof details !== "object" || details === null || !("target" in details)) return
+      if (typeof details.target === "string") output.push(details.target)
+    })
+    return withLocation(
       {
         plugins: [
           "-*",
@@ -130,9 +137,13 @@ describe("PluginSupervisor config", () => {
         expect(yield* agents.get(AgentV2.ID.make("configured"))).toMatchObject({
           description: "Loaded after invalid plugins",
         })
+        expect(output).toEqual([
+          path.join(import.meta.dir, "../plugin/fixtures/missing-plugin.ts"),
+          path.join(import.meta.dir, "../plugin/fixtures/invalid-plugin.ts"),
+        ])
       }),
-    ),
-  )
+    ).pipe(Effect.provide(Logger.layer([logger])))
+  })
 
   it.live("loads auto-discovered plugin files", () =>
     withLocation(