Просмотр исходного кода

fix(opencode): restore effect error logging (#31551)

Dax 2 месяцев назад
Родитель
Сommit
600e405ba0

+ 9 - 5
packages/opencode/src/control-plane/workspace.ts

@@ -586,7 +586,13 @@ export const layer = Layer.effect(
 
             if (target.type === "remote") {
               yield* syncHistory(previous, target.url, target.headers).pipe(
-                Effect.catch((error) => Effect.sync(() => {})),
+                Effect.catch((error) =>
+                  Effect.logWarning("session warp final source sync failed", {
+                    workspaceID: previous.id,
+                    sessionID: input.sessionID,
+                    error: errorData(error),
+                  }),
+                ),
               )
             } else {
               yield* prompt.cancel(input.sessionID)
@@ -739,9 +745,7 @@ export const layer = Layer.effect(
         ([type, adapter]) =>
           WorkspaceAdapterRuntime.list(adapter).pipe(
             Effect.catchCause((error) =>
-              Effect.sync(() => {
-                return []
-              }),
+              Effect.logWarning("workspace adapter list failed", { type, error }).pipe(Effect.as([])),
             ),
           ),
         { concurrency: "unbounded" },
@@ -817,7 +821,7 @@ export const layer = Layer.effect(
         Effect.gen(function* () {
           yield* WorkspaceAdapterRuntime.remove(info)
         }),
-        () => Effect.sync(() => {}),
+        () => Effect.logError("adapter not available when removing workspace", { type: row.type }),
       )
 
       yield* db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run().pipe(Effect.orDie)

+ 19 - 1
packages/opencode/src/mcp/index.ts

@@ -215,6 +215,12 @@ function fetchFromClient<T extends { name: string }>(
       return e
     },
   }).pipe(
+    Effect.tapError((error) =>
+      Effect.logWarning(`failed to get ${label}`, {
+        clientName,
+        error: error instanceof Error ? error.message : String(error),
+      }),
+    ),
     Effect.map((items) => {
       const out: Record<string, T & { client: string }> = {}
       const sanitizedClient = sanitize(clientName)
@@ -529,6 +535,7 @@ export const layer = Layer.effect(
           ([key, mcp]) =>
             Effect.gen(function* () {
               if (!isMcpConfigured(mcp)) {
+                yield* Effect.logError("Ignoring MCP config entry without type", { key })
                 return
               }
 
@@ -679,6 +686,7 @@ export const layer = Layer.effect(
 
             const listed = s.defs[clientName]
             if (!listed) {
+              yield* Effect.logWarning("missing cached tools for connected server", { clientName })
               return
             }
 
@@ -744,6 +752,7 @@ export const layer = Layer.effect(
       const s = yield* InstanceState.get(state)
       const client = s.clients[clientName]
       if (!client) {
+        yield* Effect.logWarning(`client not found for ${label}`, { clientName })
         return undefined
       }
       return yield* Effect.tryPromise({
@@ -751,7 +760,16 @@ export const layer = Layer.effect(
         catch: (e: any) => {
           return e
         },
-      }).pipe(Effect.orElseSucceed(() => undefined))
+      }).pipe(
+        Effect.tapError((error) =>
+          Effect.logError(`failed to ${label}`, {
+            clientName,
+            ...meta,
+            error: error instanceof Error ? error.message : String(error),
+          }),
+        ),
+        Effect.orElseSucceed(() => undefined),
+      )
     })
 
     const getPrompt = Effect.fn("MCP.getPrompt")(function* (

+ 13 - 4
packages/opencode/src/plugin/index.ts

@@ -162,8 +162,13 @@ export const layer = Layer.effect(
         for (const plugin of flags.disableDefaultPlugins ? [] : internalPlugins(flags)) {
           const init = yield* Effect.tryPromise({
             try: () => plugin(input),
-            catch: (err) => {},
-          }).pipe(Effect.option)
+            catch: errorMessage,
+          }).pipe(
+            Effect.tapError((error) =>
+              Effect.logError("failed to load internal plugin", { name: plugin.name, error }),
+            ),
+            Effect.option,
+          )
           if (init._tag === "Some") hooks.push(init.value)
         }
 
@@ -217,6 +222,7 @@ export const layer = Layer.effect(
               return message
             },
           }).pipe(
+            Effect.tapError((error) => Effect.logError("failed to load plugin", { path: load.spec, error })),
             Effect.catch(() => {
               // TODO: make proper events for this
               // events.publish(Session.Event.Error, {
@@ -256,8 +262,11 @@ export const layer = Layer.effect(
             (hook) =>
               Effect.tryPromise({
                 try: () => Promise.resolve(hook.dispose?.()),
-                catch: (error) => {},
-              }).pipe(Effect.ignore),
+                catch: errorMessage,
+              }).pipe(
+                Effect.tapError((error) => Effect.logError("plugin dispose hook failed", { error })),
+                Effect.ignore,
+              ),
             { discard: true },
           ),
         )

+ 14 - 1
packages/opencode/src/session/llm.ts

@@ -113,6 +113,7 @@ const live: Layer.Layer<
       // Wire up toolExecutor for DWS workflow models so that tool calls
       // from the workflow service are executed via opencode's tool system
       // and results sent back over the WebSocket.
+      const bridge = yield* EffectBridge.make()
       if (language instanceof GitLabWorkflowLanguageModel) {
         const workflowModel = language as GitLabWorkflowLanguageModel & {
           sessionID?: string
@@ -149,7 +150,6 @@ const live: Layer.Layer<
           return !match || match.action !== "ask"
         })
 
-        const bridge = yield* EffectBridge.make()
         const approvedToolsForSession = new Set<string>()
         workflowModel.approvalHandler = bridge.bind(async (approvalTools) => {
           const uniqueNames = [...new Set(approvalTools.map((t: { name: string }) => t.name))] as string[]
@@ -276,6 +276,19 @@ const live: Layer.Layer<
       return {
         type: "ai-sdk" as const,
         result: streamText({
+          onError(error) {
+            bridge.fork(
+              Effect.logError("stream error", {
+                providerID: input.model.providerID,
+                modelID: input.model.id,
+                "session.id": input.sessionID,
+                small: (input.small ?? false).toString(),
+                agent: input.agent.name,
+                mode: input.agent.mode,
+                error,
+              }),
+            )
+          },
           // Copilot returns the authoritative billed amount only in provider-specific response fields.
           includeRawChunks: input.model.providerID.includes("github-copilot"),
           async experimental_repairToolCall(failed) {