Преглед на файлове

feat(server): wrap remaining route handlers in request spans (#23169)

Kit Langton преди 1 ден
родител
ревизия
999d8651aa

+ 7 - 6
packages/opencode/src/server/routes/instance/config.ts

@@ -5,7 +5,6 @@ import { Config } from "@/config"
 import { Provider } from "@/provider"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
-import { AppRuntime } from "@/effect/app-runtime"
 import { jsonRequest } from "./trace"
 
 export const ConfigRoutes = lazy(() =>
@@ -52,11 +51,13 @@ export const ConfigRoutes = lazy(() =>
         },
       }),
       validator("json", Config.Info),
-      async (c) => {
-        const config = c.req.valid("json")
-        await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.update(config)))
-        return c.json(config)
-      },
+      async (c) =>
+        jsonRequest("ConfigRoutes.update", c, function* () {
+          const config = c.req.valid("json")
+          const cfg = yield* Config.Service
+          yield* cfg.update(config)
+          return config
+        }),
     )
     .get(
       "/providers",

+ 79 - 91
packages/opencode/src/server/routes/instance/experimental.ts

@@ -12,11 +12,11 @@ import { Config } from "@/config"
 import { ConsoleState } from "@/config/console-state"
 import { Account } from "@/account/account"
 import { AccountID, OrgID } from "@/account/schema"
-import { AppRuntime } from "@/effect/app-runtime"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
 import { Effect, Option } from "effect"
 import { Agent } from "@/agent/agent"
+import { jsonRequest, runRequest } from "./trace"
 
 const ConsoleOrgOption = z.object({
   accountID: z.string(),
@@ -55,22 +55,18 @@ export const ExperimentalRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const result = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const config = yield* Config.Service
-            const account = yield* Account.Service
-            const [state, groups] = yield* Effect.all([config.getConsoleState(), account.orgsByAccount()], {
-              concurrency: "unbounded",
-            })
-            return {
-              ...state,
-              switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0),
-            }
-          }),
-        )
-        return c.json(result)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.console.get", c, function* () {
+          const config = yield* Config.Service
+          const account = yield* Account.Service
+          const [state, groups] = yield* Effect.all([config.getConsoleState(), account.orgsByAccount()], {
+            concurrency: "unbounded",
+          })
+          return {
+            ...state,
+            switchableOrgCount: groups.reduce((count, group) => count + group.orgs.length, 0),
+          }
+        }),
     )
     .get(
       "/console/orgs",
@@ -89,28 +85,25 @@ export const ExperimentalRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const orgs = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const account = yield* Account.Service
-            const [groups, active] = yield* Effect.all([account.orgsByAccount(), account.active()], {
-              concurrency: "unbounded",
-            })
-            const info = Option.getOrUndefined(active)
-            return groups.flatMap((group) =>
-              group.orgs.map((org) => ({
-                accountID: group.account.id,
-                accountEmail: group.account.email,
-                accountUrl: group.account.url,
-                orgID: org.id,
-                orgName: org.name,
-                active: !!info && info.id === group.account.id && info.active_org_id === org.id,
-              })),
-            )
-          }),
-        )
-        return c.json({ orgs })
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.console.listOrgs", c, function* () {
+          const account = yield* Account.Service
+          const [groups, active] = yield* Effect.all([account.orgsByAccount(), account.active()], {
+            concurrency: "unbounded",
+          })
+          const info = Option.getOrUndefined(active)
+          const orgs = groups.flatMap((group) =>
+            group.orgs.map((org) => ({
+              accountID: group.account.id,
+              accountEmail: group.account.email,
+              accountUrl: group.account.url,
+              orgID: org.id,
+              orgName: org.name,
+              active: !!info && info.id === group.account.id && info.active_org_id === org.id,
+            })),
+          )
+          return { orgs }
+        }),
     )
     .post(
       "/console/switch",
@@ -130,16 +123,13 @@ export const ExperimentalRoutes = lazy(() =>
         },
       }),
       validator("json", ConsoleSwitchBody),
-      async (c) => {
-        const body = c.req.valid("json")
-        await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const account = yield* Account.Service
-            yield* account.use(AccountID.make(body.accountID), Option.some(OrgID.make(body.orgID)))
-          }),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.console.switchOrg", c, function* () {
+          const body = c.req.valid("json")
+          const account = yield* Account.Service
+          yield* account.use(AccountID.make(body.accountID), Option.some(OrgID.make(body.orgID)))
+          return true
+        }),
     )
     .get(
       "/tool/ids",
@@ -160,15 +150,11 @@ export const ExperimentalRoutes = lazy(() =>
           ...errors(400),
         },
       }),
-      async (c) => {
-        const ids = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const registry = yield* ToolRegistry.Service
-            return yield* registry.ids()
-          }),
-        )
-        return c.json(ids)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.tool.ids", c, function* () {
+          const registry = yield* ToolRegistry.Service
+          return yield* registry.ids()
+        }),
     )
     .get(
       "/tool",
@@ -210,7 +196,9 @@ export const ExperimentalRoutes = lazy(() =>
       ),
       async (c) => {
         const { provider, model } = c.req.valid("query")
-        const tools = await AppRuntime.runPromise(
+        const tools = await runRequest(
+          "ExperimentalRoutes.tool.list",
+          c,
           Effect.gen(function* () {
             const agents = yield* Agent.Service
             const registry = yield* ToolRegistry.Service
@@ -249,11 +237,12 @@ export const ExperimentalRoutes = lazy(() =>
         },
       }),
       validator("json", Worktree.CreateInput.optional()),
-      async (c) => {
-        const body = c.req.valid("json")
-        const worktree = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.create(body)))
-        return c.json(worktree)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.worktree.create", c, function* () {
+          const body = c.req.valid("json")
+          const svc = yield* Worktree.Service
+          return yield* svc.create(body)
+        }),
     )
     .get(
       "/worktree",
@@ -272,10 +261,11 @@ export const ExperimentalRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const sandboxes = await AppRuntime.runPromise(Project.Service.use((svc) => svc.sandboxes(Instance.project.id)))
-        return c.json(sandboxes)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.worktree.list", c, function* () {
+          const svc = yield* Project.Service
+          return yield* svc.sandboxes(Instance.project.id)
+        }),
     )
     .delete(
       "/worktree",
@@ -296,14 +286,15 @@ export const ExperimentalRoutes = lazy(() =>
         },
       }),
       validator("json", Worktree.RemoveInput),
-      async (c) => {
-        const body = c.req.valid("json")
-        await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove(body)))
-        await AppRuntime.runPromise(
-          Project.Service.use((svc) => svc.removeSandbox(Instance.project.id, body.directory)),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.worktree.remove", c, function* () {
+          const body = c.req.valid("json")
+          const worktree = yield* Worktree.Service
+          const project = yield* Project.Service
+          yield* worktree.remove(body)
+          yield* project.removeSandbox(Instance.project.id, body.directory)
+          return true
+        }),
     )
     .post(
       "/worktree/reset",
@@ -324,11 +315,13 @@ export const ExperimentalRoutes = lazy(() =>
         },
       }),
       validator("json", Worktree.ResetInput),
-      async (c) => {
-        const body = c.req.valid("json")
-        await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.reset(body)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.worktree.reset", c, function* () {
+          const body = c.req.valid("json")
+          const svc = yield* Worktree.Service
+          yield* svc.reset(body)
+          return true
+        }),
     )
     .get(
       "/session",
@@ -406,15 +399,10 @@ export const ExperimentalRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        return c.json(
-          await AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const mcp = yield* MCP.Service
-              return yield* mcp.resources()
-            }),
-          ),
-        )
-      },
+      async (c) =>
+        jsonRequest("ExperimentalRoutes.resource.list", c, function* () {
+          const mcp = yield* MCP.Service
+          return yield* mcp.resources()
+        }),
     ),
 )

+ 34 - 54
packages/opencode/src/server/routes/instance/file.ts

@@ -1,13 +1,12 @@
 import { Hono } from "hono"
 import { describeRoute, validator, resolver } from "hono-openapi"
-import { Effect } from "effect"
 import z from "zod"
-import { AppRuntime } from "@/effect/app-runtime"
 import { File } from "@/file"
 import { Ripgrep } from "@/file/ripgrep"
 import { LSP } from "@/lsp"
 import { Instance } from "@/project/instance"
 import { lazy } from "@/util/lazy"
+import { jsonRequest } from "./trace"
 
 export const FileRoutes = lazy(() =>
   new Hono()
@@ -34,13 +33,13 @@ export const FileRoutes = lazy(() =>
           pattern: z.string(),
         }),
       ),
-      async (c) => {
-        const pattern = c.req.valid("query").pattern
-        const result = await AppRuntime.runPromise(
-          Ripgrep.Service.use((svc) => svc.search({ cwd: Instance.directory, pattern, limit: 10 })),
-        )
-        return c.json(result.items)
-      },
+      async (c) =>
+        jsonRequest("FileRoutes.findText", c, function* () {
+          const pattern = c.req.valid("query").pattern
+          const svc = yield* Ripgrep.Service
+          const result = yield* svc.search({ cwd: Instance.directory, pattern, limit: 10 })
+          return result.items
+        }),
     )
     .get(
       "/find/file",
@@ -68,25 +67,17 @@ export const FileRoutes = lazy(() =>
           limit: z.coerce.number().int().min(1).max(200).optional(),
         }),
       ),
-      async (c) => {
-        const query = c.req.valid("query").query
-        const dirs = c.req.valid("query").dirs
-        const type = c.req.valid("query").type
-        const limit = c.req.valid("query").limit
-        const results = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            return yield* File.Service.use((svc) =>
-              svc.search({
-                query,
-                limit: limit ?? 10,
-                dirs: dirs !== "false",
-                type,
-              }),
-            )
-          }),
-        )
-        return c.json(results)
-      },
+      async (c) =>
+        jsonRequest("FileRoutes.findFile", c, function* () {
+          const query = c.req.valid("query")
+          const svc = yield* File.Service
+          return yield* svc.search({
+            query: query.query,
+            limit: query.limit ?? 10,
+            dirs: query.dirs !== "false",
+            type: query.type,
+          })
+        }),
     )
     .get(
       "/find/symbol",
@@ -138,15 +129,11 @@ export const FileRoutes = lazy(() =>
           path: z.string(),
         }),
       ),
-      async (c) => {
-        const path = c.req.valid("query").path
-        const content = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            return yield* File.Service.use((svc) => svc.list(path))
-          }),
-        )
-        return c.json(content)
-      },
+      async (c) =>
+        jsonRequest("FileRoutes.list", c, function* () {
+          const svc = yield* File.Service
+          return yield* svc.list(c.req.valid("query").path)
+        }),
     )
     .get(
       "/file/content",
@@ -171,15 +158,11 @@ export const FileRoutes = lazy(() =>
           path: z.string(),
         }),
       ),
-      async (c) => {
-        const path = c.req.valid("query").path
-        const content = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            return yield* File.Service.use((svc) => svc.read(path))
-          }),
-        )
-        return c.json(content)
-      },
+      async (c) =>
+        jsonRequest("FileRoutes.read", c, function* () {
+          const svc = yield* File.Service
+          return yield* svc.read(c.req.valid("query").path)
+        }),
     )
     .get(
       "/file/status",
@@ -198,13 +181,10 @@ export const FileRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const content = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            return yield* File.Service.use((svc) => svc.status())
-          }),
-        )
-        return c.json(content)
-      },
+      async (c) =>
+        jsonRequest("FileRoutes.status", c, function* () {
+          const svc = yield* File.Service
+          return yield* svc.status()
+        }),
     ),
 )

+ 39 - 48
packages/opencode/src/server/routes/instance/index.ts

@@ -26,8 +26,8 @@ import { ExperimentalRoutes } from "./experimental"
 import { ProviderRoutes } from "./provider"
 import { EventRoutes } from "./event"
 import { SyncRoutes } from "./sync"
-import { AppRuntime } from "@/effect/app-runtime"
 import { InstanceMiddleware } from "./middleware"
+import { jsonRequest } from "./trace"
 
 export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
   const app = new Hono()
@@ -141,19 +141,14 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        return c.json(
-          await AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const vcs = yield* Vcs.Service
-              const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], {
-                concurrency: 2,
-              })
-              return { branch, default_branch }
-            }),
-          ),
-        )
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.vcs.get", c, function* () {
+          const vcs = yield* Vcs.Service
+          const [branch, default_branch] = yield* Effect.all([vcs.branch(), vcs.defaultBranch()], {
+            concurrency: 2,
+          })
+          return { branch, default_branch }
+        }),
     )
     .get(
       "/vcs/diff",
@@ -178,16 +173,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           mode: Vcs.Mode,
         }),
       ),
-      async (c) => {
-        return c.json(
-          await AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const vcs = yield* Vcs.Service
-              return yield* vcs.diff(c.req.valid("query").mode)
-            }),
-          ),
-        )
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.vcs.diff", c, function* () {
+          const vcs = yield* Vcs.Service
+          return yield* vcs.diff(c.req.valid("query").mode)
+        }),
     )
     .get(
       "/command",
@@ -206,10 +196,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        const commands = await AppRuntime.runPromise(Command.Service.use((svc) => svc.list()))
-        return c.json(commands)
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.command.list", c, function* () {
+          const svc = yield* Command.Service
+          return yield* svc.list()
+        }),
     )
     .get(
       "/agent",
@@ -228,10 +219,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        const modes = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list()))
-        return c.json(modes)
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.agent.list", c, function* () {
+          const svc = yield* Agent.Service
+          return yield* svc.list()
+        }),
     )
     .get(
       "/skill",
@@ -250,15 +242,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        const skills = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const skill = yield* Skill.Service
-            return yield* skill.all()
-          }),
-        )
-        return c.json(skills)
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.skill.list", c, function* () {
+          const skill = yield* Skill.Service
+          return yield* skill.all()
+        }),
     )
     .get(
       "/lsp",
@@ -277,10 +265,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        const items = await AppRuntime.runPromise(LSP.Service.use((lsp) => lsp.status()))
-        return c.json(items)
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.lsp.status", c, function* () {
+          const lsp = yield* LSP.Service
+          return yield* lsp.status()
+        }),
     )
     .get(
       "/formatter",
@@ -299,8 +288,10 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
           },
         },
       }),
-      async (c) => {
-        return c.json(await AppRuntime.runPromise(Format.Service.use((svc) => svc.status())))
-      },
+      async (c) =>
+        jsonRequest("InstanceRoutes.formatter.status", c, function* () {
+          const svc = yield* Format.Service
+          return yield* svc.status()
+        }),
     )
 }

+ 47 - 33
packages/opencode/src/server/routes/instance/mcp.ts

@@ -2,12 +2,11 @@ import { Hono } from "hono"
 import { describeRoute, validator, resolver } from "hono-openapi"
 import z from "zod"
 import { MCP } from "@/mcp"
-import { Config } from "@/config"
 import { ConfigMCP } from "@/config/mcp"
-import { AppRuntime } from "@/effect/app-runtime"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
 import { Effect } from "effect"
+import { jsonRequest, runRequest } from "./trace"
 
 export const McpRoutes = lazy(() =>
   new Hono()
@@ -28,9 +27,11 @@ export const McpRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        return c.json(await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.status())))
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.status", c, function* () {
+          const mcp = yield* MCP.Service
+          return yield* mcp.status()
+        }),
     )
     .post(
       "/",
@@ -57,11 +58,13 @@ export const McpRoutes = lazy(() =>
           config: ConfigMCP.Info.zod,
         }),
       ),
-      async (c) => {
-        const { name, config } = c.req.valid("json")
-        const result = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.add(name, config)))
-        return c.json(result.status)
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.add", c, function* () {
+          const { name, config } = c.req.valid("json")
+          const mcp = yield* MCP.Service
+          const result = yield* mcp.add(name, config)
+          return result.status
+        }),
     )
     .post(
       "/:name/auth",
@@ -87,7 +90,9 @@ export const McpRoutes = lazy(() =>
       }),
       async (c) => {
         const name = c.req.param("name")
-        const result = await AppRuntime.runPromise(
+        const result = await runRequest(
+          "McpRoutes.auth.start",
+          c,
           Effect.gen(function* () {
             const mcp = yield* MCP.Service
             const supports = yield* mcp.supportsOAuth(name)
@@ -129,12 +134,13 @@ export const McpRoutes = lazy(() =>
           code: z.string().describe("Authorization code from OAuth callback"),
         }),
       ),
-      async (c) => {
-        const name = c.req.param("name")
-        const { code } = c.req.valid("json")
-        const status = await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.finishAuth(name, code)))
-        return c.json(status)
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.auth.callback", c, function* () {
+          const name = c.req.param("name")
+          const { code } = c.req.valid("json")
+          const mcp = yield* MCP.Service
+          return yield* mcp.finishAuth(name, code)
+        }),
     )
     .post(
       "/:name/auth/authenticate",
@@ -156,7 +162,9 @@ export const McpRoutes = lazy(() =>
       }),
       async (c) => {
         const name = c.req.param("name")
-        const result = await AppRuntime.runPromise(
+        const result = await runRequest(
+          "McpRoutes.auth.authenticate",
+          c,
           Effect.gen(function* () {
             const mcp = yield* MCP.Service
             const supports = yield* mcp.supportsOAuth(name)
@@ -191,11 +199,13 @@ export const McpRoutes = lazy(() =>
           ...errors(404),
         },
       }),
-      async (c) => {
-        const name = c.req.param("name")
-        await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.removeAuth(name)))
-        return c.json({ success: true as const })
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.auth.remove", c, function* () {
+          const name = c.req.param("name")
+          const mcp = yield* MCP.Service
+          yield* mcp.removeAuth(name)
+          return { success: true as const }
+        }),
     )
     .post(
       "/:name/connect",
@@ -214,11 +224,13 @@ export const McpRoutes = lazy(() =>
         },
       }),
       validator("param", z.object({ name: z.string() })),
-      async (c) => {
-        const { name } = c.req.valid("param")
-        await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.connect(name)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.connect", c, function* () {
+          const { name } = c.req.valid("param")
+          const mcp = yield* MCP.Service
+          yield* mcp.connect(name)
+          return true
+        }),
     )
     .post(
       "/:name/disconnect",
@@ -237,10 +249,12 @@ export const McpRoutes = lazy(() =>
         },
       }),
       validator("param", z.object({ name: z.string() })),
-      async (c) => {
-        const { name } = c.req.valid("param")
-        await AppRuntime.runPromise(MCP.Service.use((mcp) => mcp.disconnect(name)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("McpRoutes.disconnect", c, function* () {
+          const { name } = c.req.valid("param")
+          const mcp = yield* MCP.Service
+          yield* mcp.disconnect(name)
+          return true
+        }),
     ),
 )

+ 18 - 19
packages/opencode/src/server/routes/instance/permission.ts

@@ -1,11 +1,11 @@
 import { Hono } from "hono"
 import { describeRoute, validator, resolver } from "hono-openapi"
 import z from "zod"
-import { AppRuntime } from "@/effect/app-runtime"
 import { Permission } from "@/permission"
 import { PermissionID } from "@/permission/schema"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
+import { jsonRequest } from "./trace"
 
 export const PermissionRoutes = lazy(() =>
   new Hono()
@@ -34,20 +34,18 @@ export const PermissionRoutes = lazy(() =>
         }),
       ),
       validator("json", z.object({ reply: Permission.Reply.zod, message: z.string().optional() })),
-      async (c) => {
-        const params = c.req.valid("param")
-        const json = c.req.valid("json")
-        await AppRuntime.runPromise(
-          Permission.Service.use((svc) =>
-            svc.reply({
-              requestID: params.requestID,
-              reply: json.reply,
-              message: json.message,
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("PermissionRoutes.reply", c, function* () {
+          const params = c.req.valid("param")
+          const json = c.req.valid("json")
+          const svc = yield* Permission.Service
+          yield* svc.reply({
+            requestID: params.requestID,
+            reply: json.reply,
+            message: json.message,
+          })
+          return true
+        }),
     )
     .get(
       "/",
@@ -66,9 +64,10 @@ export const PermissionRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const permissions = await AppRuntime.runPromise(Permission.Service.use((svc) => svc.list()))
-        return c.json(permissions)
-      },
+      async (c) =>
+        jsonRequest("PermissionRoutes.list", c, function* () {
+          const svc = yield* Permission.Service
+          return yield* svc.list()
+        }),
     ),
 )

+ 11 - 7
packages/opencode/src/server/routes/instance/project.ts

@@ -9,6 +9,7 @@ import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
 import { InstanceBootstrap } from "@/project/bootstrap"
 import { AppRuntime } from "@/effect/app-runtime"
+import { jsonRequest, runRequest } from "./trace"
 
 export const ProjectRoutes = lazy(() =>
   new Hono()
@@ -75,7 +76,9 @@ export const ProjectRoutes = lazy(() =>
       async (c) => {
         const dir = Instance.directory
         const prev = Instance.project
-        const next = await AppRuntime.runPromise(
+        const next = await runRequest(
+          "ProjectRoutes.initGit",
+          c,
           Project.Service.use((svc) => svc.initGit({ directory: dir, project: prev })),
         )
         if (next.id === prev.id && next.vcs === prev.vcs && next.worktree === prev.worktree) return c.json(next)
@@ -108,11 +111,12 @@ export const ProjectRoutes = lazy(() =>
       }),
       validator("param", z.object({ projectID: ProjectID.zod })),
       validator("json", Project.UpdateInput.omit({ projectID: true })),
-      async (c) => {
-        const projectID = c.req.valid("param").projectID
-        const body = c.req.valid("json")
-        const project = await AppRuntime.runPromise(Project.Service.use((svc) => svc.update({ ...body, projectID })))
-        return c.json(project)
-      },
+      async (c) =>
+        jsonRequest("ProjectRoutes.update", c, function* () {
+          const projectID = c.req.valid("param").projectID
+          const body = c.req.valid("json")
+          const svc = yield* Project.Service
+          return yield* svc.update({ ...body, projectID })
+        }),
     ),
 )

+ 53 - 64
packages/opencode/src/server/routes/instance/provider.ts

@@ -6,11 +6,11 @@ import { Provider } from "@/provider"
 import { ModelsDev } from "@/provider"
 import { ProviderAuth } from "@/provider"
 import { ProviderID } from "@/provider/schema"
-import { AppRuntime } from "@/effect/app-runtime"
 import { mapValues } from "remeda"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
 import { Effect } from "effect"
+import { jsonRequest } from "./trace"
 
 export const ProviderRoutes = lazy(() =>
   new Hono()
@@ -31,39 +31,31 @@ export const ProviderRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const result = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const svc = yield* Provider.Service
-            const cfg = yield* Config.Service
-            const config = yield* cfg.get()
-            const all = yield* Effect.promise(() => ModelsDev.get())
-            const disabled = new Set(config.disabled_providers ?? [])
-            const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
-            const filtered: Record<string, (typeof all)[string]> = {}
-            for (const [key, value] of Object.entries(all)) {
-              if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
-                filtered[key] = value
-              }
+      async (c) =>
+        jsonRequest("ProviderRoutes.list", c, function* () {
+          const svc = yield* Provider.Service
+          const cfg = yield* Config.Service
+          const config = yield* cfg.get()
+          const all = yield* Effect.promise(() => ModelsDev.get())
+          const disabled = new Set(config.disabled_providers ?? [])
+          const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
+          const filtered: Record<string, (typeof all)[string]> = {}
+          for (const [key, value] of Object.entries(all)) {
+            if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
+              filtered[key] = value
             }
-            const connected = yield* svc.list()
-            const providers = Object.assign(
-              mapValues(filtered, (x) => Provider.fromModelsDevProvider(x)),
-              connected,
-            )
-            return {
-              all: Object.values(providers),
-              default: Provider.defaultModelIDs(providers),
-              connected: Object.keys(connected),
-            }
-          }),
-        )
-        return c.json({
-          all: result.all,
-          default: result.default,
-          connected: result.connected,
-        })
-      },
+          }
+          const connected = yield* svc.list()
+          const providers = Object.assign(
+            mapValues(filtered, (x) => Provider.fromModelsDevProvider(x)),
+            connected,
+          )
+          return {
+            all: Object.values(providers),
+            default: Provider.defaultModelIDs(providers),
+            connected: Object.keys(connected),
+          }
+        }),
     )
     .get(
       "/auth",
@@ -82,9 +74,11 @@ export const ProviderRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        return c.json(await AppRuntime.runPromise(ProviderAuth.Service.use((svc) => svc.methods())))
-      },
+      async (c) =>
+        jsonRequest("ProviderRoutes.auth", c, function* () {
+          const svc = yield* ProviderAuth.Service
+          return yield* svc.methods()
+        }),
     )
     .post(
       "/:providerID/oauth/authorize",
@@ -111,20 +105,17 @@ export const ProviderRoutes = lazy(() =>
         }),
       ),
       validator("json", ProviderAuth.AuthorizeInput.zod),
-      async (c) => {
-        const providerID = c.req.valid("param").providerID
-        const { method, inputs } = c.req.valid("json")
-        const result = await AppRuntime.runPromise(
-          ProviderAuth.Service.use((svc) =>
-            svc.authorize({
-              providerID,
-              method,
-              inputs,
-            }),
-          ),
-        )
-        return c.json(result)
-      },
+      async (c) =>
+        jsonRequest("ProviderRoutes.oauth.authorize", c, function* () {
+          const providerID = c.req.valid("param").providerID
+          const { method, inputs } = c.req.valid("json")
+          const svc = yield* ProviderAuth.Service
+          return yield* svc.authorize({
+            providerID,
+            method,
+            inputs,
+          })
+        }),
     )
     .post(
       "/:providerID/oauth/callback",
@@ -151,19 +142,17 @@ export const ProviderRoutes = lazy(() =>
         }),
       ),
       validator("json", ProviderAuth.CallbackInput.zod),
-      async (c) => {
-        const providerID = c.req.valid("param").providerID
-        const { method, code } = c.req.valid("json")
-        await AppRuntime.runPromise(
-          ProviderAuth.Service.use((svc) =>
-            svc.callback({
-              providerID,
-              method,
-              code,
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("ProviderRoutes.oauth.callback", c, function* () {
+          const providerID = c.req.valid("param").providerID
+          const { method, code } = c.req.valid("json")
+          const svc = yield* ProviderAuth.Service
+          yield* svc.callback({
+            providerID,
+            method,
+            code,
+          })
+          return true
+        }),
     ),
 )

+ 29 - 40
packages/opencode/src/server/routes/instance/pty.ts

@@ -8,6 +8,7 @@ import { Pty } from "@/pty"
 import { PtyID } from "@/pty/schema"
 import { NotFoundError } from "@/storage"
 import { errors } from "../../error"
+import { jsonRequest, runRequest } from "./trace"
 
 export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
   return new Hono()
@@ -28,16 +29,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
           },
         },
       }),
-      async (c) => {
-        return c.json(
-          await AppRuntime.runPromise(
-            Effect.gen(function* () {
-              const pty = yield* Pty.Service
-              return yield* pty.list()
-            }),
-          ),
-        )
-      },
+      async (c) =>
+        jsonRequest("PtyRoutes.list", c, function* () {
+          const pty = yield* Pty.Service
+          return yield* pty.list()
+        }),
     )
     .post(
       "/",
@@ -58,15 +54,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
         },
       }),
       validator("json", Pty.CreateInput),
-      async (c) => {
-        const info = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const pty = yield* Pty.Service
-            return yield* pty.create(c.req.valid("json"))
-          }),
-        )
-        return c.json(info)
-      },
+      async (c) =>
+        jsonRequest("PtyRoutes.create", c, function* () {
+          const pty = yield* Pty.Service
+          return yield* pty.create(c.req.valid("json"))
+        }),
     )
     .get(
       "/:ptyID",
@@ -88,7 +80,9 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
       }),
       validator("param", z.object({ ptyID: PtyID.zod })),
       async (c) => {
-        const info = await AppRuntime.runPromise(
+        const info = await runRequest(
+          "PtyRoutes.get",
+          c,
           Effect.gen(function* () {
             const pty = yield* Pty.Service
             return yield* pty.get(c.req.valid("param").ptyID)
@@ -120,15 +114,11 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
       }),
       validator("param", z.object({ ptyID: PtyID.zod })),
       validator("json", Pty.UpdateInput),
-      async (c) => {
-        const info = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const pty = yield* Pty.Service
-            return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
-          }),
-        )
-        return c.json(info)
-      },
+      async (c) =>
+        jsonRequest("PtyRoutes.update", c, function* () {
+          const pty = yield* Pty.Service
+          return yield* pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
+        }),
     )
     .delete(
       "/:ptyID",
@@ -149,15 +139,12 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
         },
       }),
       validator("param", z.object({ ptyID: PtyID.zod })),
-      async (c) => {
-        await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const pty = yield* Pty.Service
-            yield* pty.remove(c.req.valid("param").ptyID)
-          }),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("PtyRoutes.remove", c, function* () {
+          const pty = yield* Pty.Service
+          yield* pty.remove(c.req.valid("param").ptyID)
+          return true
+        }),
     )
     .get(
       "/:ptyID/connect",
@@ -194,7 +181,9 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
         })()
         let handler: Handler | undefined
         if (
-          !(await AppRuntime.runPromise(
+          !(await runRequest(
+            "PtyRoutes.connect",
+            c,
             Effect.gen(function* () {
               const pty = yield* Pty.Service
               return yield* pty.get(id)
@@ -232,7 +221,7 @@ export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
               Effect.gen(function* () {
                 const pty = yield* Pty.Service
                 return yield* pty.connect(id, socket, cursor)
-              }),
+              }).pipe(Effect.withSpan("PtyRoutes.connect.open")),
             )
             ready = true
             for (const msg of pending) handler?.onMessage(msg)

+ 24 - 23
packages/opencode/src/server/routes/instance/question.ts

@@ -3,10 +3,10 @@ import { describeRoute, validator } from "hono-openapi"
 import { resolver } from "hono-openapi"
 import { QuestionID } from "@/question/schema"
 import { Question } from "@/question"
-import { AppRuntime } from "@/effect/app-runtime"
 import z from "zod"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
+import { jsonRequest } from "./trace"
 
 const Reply = z.object({
   answers: Question.Answer.zod
@@ -33,10 +33,11 @@ export const QuestionRoutes = lazy(() =>
           },
         },
       }),
-      async (c) => {
-        const questions = await AppRuntime.runPromise(Question.Service.use((svc) => svc.list()))
-        return c.json(questions)
-      },
+      async (c) =>
+        jsonRequest("QuestionRoutes.list", c, function* () {
+          const svc = yield* Question.Service
+          return yield* svc.list()
+        }),
     )
     .post(
       "/:requestID/reply",
@@ -63,19 +64,17 @@ export const QuestionRoutes = lazy(() =>
         }),
       ),
       validator("json", Reply),
-      async (c) => {
-        const params = c.req.valid("param")
-        const json = c.req.valid("json")
-        await AppRuntime.runPromise(
-          Question.Service.use((svc) =>
-            svc.reply({
-              requestID: params.requestID,
-              answers: json.answers,
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("QuestionRoutes.reply", c, function* () {
+          const params = c.req.valid("param")
+          const json = c.req.valid("json")
+          const svc = yield* Question.Service
+          yield* svc.reply({
+            requestID: params.requestID,
+            answers: json.answers,
+          })
+          return true
+        }),
     )
     .post(
       "/:requestID/reject",
@@ -101,10 +100,12 @@ export const QuestionRoutes = lazy(() =>
           requestID: QuestionID.zod,
         }),
       ),
-      async (c) => {
-        const params = c.req.valid("param")
-        await AppRuntime.runPromise(Question.Service.use((svc) => svc.reject(params.requestID)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("QuestionRoutes.reject", c, function* () {
+          const params = c.req.valid("param")
+          const svc = yield* Question.Service
+          yield* svc.reject(params.requestID)
+          return true
+        }),
     ),
 )

+ 199 - 212
packages/opencode/src/server/routes/instance/session.ts

@@ -14,7 +14,6 @@ import { SessionStatus } from "@/session/status"
 import { SessionSummary } from "@/session/summary"
 import { Todo } from "@/session/todo"
 import { Effect } from "effect"
-import { AppRuntime } from "@/effect/app-runtime"
 import { Agent } from "@/agent/agent"
 import { Snapshot } from "@/snapshot"
 import { Command } from "@/command"
@@ -26,7 +25,7 @@ import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
 import { Bus } from "@/bus"
 import { NamedError } from "@opencode-ai/shared/util/error"
-import { jsonRequest } from "./trace"
+import { jsonRequest, runRequest } from "./trace"
 
 const log = Log.create({ service: "server" })
 
@@ -218,11 +217,12 @@ export const SessionRoutes = lazy(() =>
         },
       }),
       validator("json", Session.CreateInput),
-      async (c) => {
-        const body = c.req.valid("json") ?? {}
-        const session = await AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.create(body)))
-        return c.json(session)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.create", c, function* () {
+          const body = c.req.valid("json") ?? {}
+          const svc = yield* SessionShare.Service
+          return yield* svc.create(body)
+        }),
     )
     .delete(
       "/:sessionID",
@@ -248,11 +248,13 @@ export const SessionRoutes = lazy(() =>
           sessionID: Session.RemoveInput,
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        await AppRuntime.runPromise(Session.Service.use((svc) => svc.remove(sessionID)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.delete", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const svc = yield* Session.Service
+          yield* svc.remove(sessionID)
+          return true
+        }),
     )
     .patch(
       "/:sessionID",
@@ -290,32 +292,28 @@ export const SessionRoutes = lazy(() =>
             .optional(),
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const updates = c.req.valid("json")
-        const session = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const session = yield* Session.Service
-            const current = yield* session.get(sessionID)
+      async (c) =>
+        jsonRequest("SessionRoutes.update", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const updates = c.req.valid("json")
+          const session = yield* Session.Service
+          const current = yield* session.get(sessionID)
 
-            if (updates.title !== undefined) {
-              yield* session.setTitle({ sessionID, title: updates.title })
-            }
-            if (updates.permission !== undefined) {
-              yield* session.setPermission({
-                sessionID,
-                permission: Permission.merge(current.permission ?? [], updates.permission),
-              })
-            }
-            if (updates.time?.archived !== undefined) {
-              yield* session.setArchived({ sessionID, time: updates.time.archived })
-            }
+          if (updates.title !== undefined) {
+            yield* session.setTitle({ sessionID, title: updates.title })
+          }
+          if (updates.permission !== undefined) {
+            yield* session.setPermission({
+              sessionID,
+              permission: Permission.merge(current.permission ?? [], updates.permission),
+            })
+          }
+          if (updates.time?.archived !== undefined) {
+            yield* session.setArchived({ sessionID, time: updates.time.archived })
+          }
 
-            return yield* session.get(sessionID)
-          }),
-        )
-        return c.json(session)
-      },
+          return yield* session.get(sessionID)
+        }),
     )
     // TODO(v2): remove this dedicated route and rely on the normal `/init` command flow.
     .post(
@@ -351,22 +349,20 @@ export const SessionRoutes = lazy(() =>
           messageID: MessageID.zod,
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const body = c.req.valid("json")
-        await AppRuntime.runPromise(
-          SessionPrompt.Service.use((svc) =>
-            svc.command({
-              sessionID,
-              messageID: body.messageID,
-              model: body.providerID + "/" + body.modelID,
-              command: Command.Default.INIT,
-              arguments: "",
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.init", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const body = c.req.valid("json")
+          const svc = yield* SessionPrompt.Service
+          yield* svc.command({
+            sessionID,
+            messageID: body.messageID,
+            model: body.providerID + "/" + body.modelID,
+            command: Command.Default.INIT,
+            arguments: "",
+          })
+          return true
+        }),
     )
     .post(
       "/:sessionID/fork",
@@ -392,12 +388,13 @@ export const SessionRoutes = lazy(() =>
         }),
       ),
       validator("json", Session.ForkInput.omit({ sessionID: true })),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const body = c.req.valid("json")
-        const result = await AppRuntime.runPromise(Session.Service.use((svc) => svc.fork({ ...body, sessionID })))
-        return c.json(result)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.fork", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const body = c.req.valid("json")
+          const svc = yield* Session.Service
+          return yield* svc.fork({ ...body, sessionID })
+        }),
     )
     .post(
       "/:sessionID/abort",
@@ -423,10 +420,12 @@ export const SessionRoutes = lazy(() =>
           sessionID: SessionID.zod,
         }),
       ),
-      async (c) => {
-        await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.cancel(c.req.valid("param").sessionID)))
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.abort", c, function* () {
+          const svc = yield* SessionPrompt.Service
+          yield* svc.cancel(c.req.valid("param").sessionID)
+          return true
+        }),
     )
     .post(
       "/:sessionID/share",
@@ -452,18 +451,14 @@ export const SessionRoutes = lazy(() =>
           sessionID: SessionID.zod,
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const session = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const share = yield* SessionShare.Service
-            const session = yield* Session.Service
-            yield* share.share(sessionID)
-            return yield* session.get(sessionID)
-          }),
-        )
-        return c.json(session)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.share", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const share = yield* SessionShare.Service
+          const session = yield* Session.Service
+          yield* share.share(sessionID)
+          return yield* session.get(sessionID)
+        }),
     )
     .get(
       "/:sessionID/diff",
@@ -494,19 +489,16 @@ export const SessionRoutes = lazy(() =>
           messageID: SessionSummary.DiffInput.shape.messageID,
         }),
       ),
-      async (c) => {
-        const query = c.req.valid("query")
-        const params = c.req.valid("param")
-        const result = await AppRuntime.runPromise(
-          SessionSummary.Service.use((summary) =>
-            summary.diff({
-              sessionID: params.sessionID,
-              messageID: query.messageID,
-            }),
-          ),
-        )
-        return c.json(result)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.diff", c, function* () {
+          const query = c.req.valid("query")
+          const params = c.req.valid("param")
+          const summary = yield* SessionSummary.Service
+          return yield* summary.diff({
+            sessionID: params.sessionID,
+            messageID: query.messageID,
+          })
+        }),
     )
     .delete(
       "/:sessionID/share",
@@ -532,18 +524,14 @@ export const SessionRoutes = lazy(() =>
           sessionID: SessionID.zod,
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const session = await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const share = yield* SessionShare.Service
-            const session = yield* Session.Service
-            yield* share.unshare(sessionID)
-            return yield* session.get(sessionID)
-          }),
-        )
-        return c.json(session)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.unshare", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const share = yield* SessionShare.Service
+          const session = yield* Session.Service
+          yield* share.unshare(sessionID)
+          return yield* session.get(sessionID)
+        }),
     )
     .post(
       "/:sessionID/summarize",
@@ -577,43 +565,40 @@ export const SessionRoutes = lazy(() =>
           auto: z.boolean().optional().default(false),
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const body = c.req.valid("json")
-        await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const session = yield* Session.Service
-            const revert = yield* SessionRevert.Service
-            const compact = yield* SessionCompaction.Service
-            const prompt = yield* SessionPrompt.Service
-            const agent = yield* Agent.Service
+      async (c) =>
+        jsonRequest("SessionRoutes.summarize", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const body = c.req.valid("json")
+          const session = yield* Session.Service
+          const revert = yield* SessionRevert.Service
+          const compact = yield* SessionCompaction.Service
+          const prompt = yield* SessionPrompt.Service
+          const agent = yield* Agent.Service
 
-            yield* revert.cleanup(yield* session.get(sessionID))
-            const msgs = yield* session.messages({ sessionID })
-            const defaultAgent = yield* agent.defaultAgent()
-            let currentAgent = defaultAgent
-            for (let i = msgs.length - 1; i >= 0; i--) {
-              const info = msgs[i].info
-              if (info.role === "user") {
-                currentAgent = info.agent || defaultAgent
-                break
-              }
+          yield* revert.cleanup(yield* session.get(sessionID))
+          const msgs = yield* session.messages({ sessionID })
+          const defaultAgent = yield* agent.defaultAgent()
+          let currentAgent = defaultAgent
+          for (let i = msgs.length - 1; i >= 0; i--) {
+            const info = msgs[i].info
+            if (info.role === "user") {
+              currentAgent = info.agent || defaultAgent
+              break
             }
+          }
 
-            yield* compact.create({
-              sessionID,
-              agent: currentAgent,
-              model: {
-                providerID: body.providerID,
-                modelID: body.modelID,
-              },
-              auto: body.auto,
-            })
-            yield* prompt.loop({ sessionID })
-          }),
-        )
-        return c.json(true)
-      },
+          yield* compact.create({
+            sessionID,
+            agent: currentAgent,
+            model: {
+              providerID: body.providerID,
+              modelID: body.modelID,
+            },
+            auto: body.auto,
+          })
+          yield* prompt.loop({ sessionID })
+          return true
+        }),
     )
     .get(
       "/:sessionID/message",
@@ -675,7 +660,9 @@ export const SessionRoutes = lazy(() =>
         const query = c.req.valid("query")
         const sessionID = c.req.valid("param").sessionID
         if (query.limit === undefined || query.limit === 0) {
-          const messages = await AppRuntime.runPromise(
+          const messages = await runRequest(
+            "SessionRoutes.messages",
+            c,
             Effect.gen(function* () {
               const session = yield* Session.Service
               yield* session.get(sessionID)
@@ -766,21 +753,18 @@ export const SessionRoutes = lazy(() =>
           messageID: MessageID.zod,
         }),
       ),
-      async (c) => {
-        const params = c.req.valid("param")
-        await AppRuntime.runPromise(
-          Effect.gen(function* () {
-            const state = yield* SessionRunState.Service
-            const session = yield* Session.Service
-            yield* state.assertNotBusy(params.sessionID)
-            yield* session.removeMessage({
-              sessionID: params.sessionID,
-              messageID: params.messageID,
-            })
-          }),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.deleteMessage", c, function* () {
+          const params = c.req.valid("param")
+          const state = yield* SessionRunState.Service
+          const session = yield* Session.Service
+          yield* state.assertNotBusy(params.sessionID)
+          yield* session.removeMessage({
+            sessionID: params.sessionID,
+            messageID: params.messageID,
+          })
+          return true
+        }),
     )
     .delete(
       "/:sessionID/message/:messageID/part/:partID",
@@ -807,19 +791,17 @@ export const SessionRoutes = lazy(() =>
           partID: PartID.zod,
         }),
       ),
-      async (c) => {
-        const params = c.req.valid("param")
-        await AppRuntime.runPromise(
-          Session.Service.use((svc) =>
-            svc.removePart({
-              sessionID: params.sessionID,
-              messageID: params.messageID,
-              partID: params.partID,
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.deletePart", c, function* () {
+          const params = c.req.valid("param")
+          const svc = yield* Session.Service
+          yield* svc.removePart({
+            sessionID: params.sessionID,
+            messageID: params.messageID,
+            partID: params.partID,
+          })
+          return true
+        }),
     )
     .patch(
       "/:sessionID/message/:messageID/part/:partID",
@@ -855,8 +837,10 @@ export const SessionRoutes = lazy(() =>
             `Part mismatch: body.id='${body.id}' vs partID='${params.partID}', body.messageID='${body.messageID}' vs messageID='${params.messageID}', body.sessionID='${body.sessionID}' vs sessionID='${params.sessionID}'`,
           )
         }
-        const part = await AppRuntime.runPromise(Session.Service.use((svc) => svc.updatePart(body)))
-        return c.json(part)
+        return jsonRequest("SessionRoutes.updatePart", c, function* () {
+          const svc = yield* Session.Service
+          return yield* svc.updatePart(body)
+        })
       },
     )
     .post(
@@ -895,7 +879,9 @@ export const SessionRoutes = lazy(() =>
         return stream(c, async (stream) => {
           const sessionID = c.req.valid("param").sessionID
           const body = c.req.valid("json")
-          const msg = await AppRuntime.runPromise(
+          const msg = await runRequest(
+            "SessionRoutes.prompt",
+            c,
             SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })),
           )
           void stream.write(JSON.stringify(msg))
@@ -926,15 +912,17 @@ export const SessionRoutes = lazy(() =>
       async (c) => {
         const sessionID = c.req.valid("param").sessionID
         const body = c.req.valid("json")
-        void AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID }))).catch(
-          (err) => {
-            log.error("prompt_async failed", { sessionID, error: err })
-            void Bus.publish(Session.Event.Error, {
-              sessionID,
-              error: new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject(),
-            })
-          },
-        )
+        void runRequest(
+          "SessionRoutes.prompt_async",
+          c,
+          SessionPrompt.Service.use((svc) => svc.prompt({ ...body, sessionID })),
+        ).catch((err) => {
+          log.error("prompt_async failed", { sessionID, error: err })
+          void Bus.publish(Session.Event.Error, {
+            sessionID,
+            error: new NamedError.Unknown({ message: err instanceof Error ? err.message : String(err) }).toObject(),
+          })
+        })
 
         return c.body(null, 204)
       },
@@ -969,12 +957,13 @@ export const SessionRoutes = lazy(() =>
         }),
       ),
       validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const body = c.req.valid("json")
-        const msg = await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.command({ ...body, sessionID })))
-        return c.json(msg)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.command", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const body = c.req.valid("json")
+          const svc = yield* SessionPrompt.Service
+          return yield* svc.command({ ...body, sessionID })
+        }),
     )
     .post(
       "/:sessionID/shell",
@@ -1001,12 +990,13 @@ export const SessionRoutes = lazy(() =>
         }),
       ),
       validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const body = c.req.valid("json")
-        const msg = await AppRuntime.runPromise(SessionPrompt.Service.use((svc) => svc.shell({ ...body, sessionID })))
-        return c.json(msg)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.shell", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const body = c.req.valid("json")
+          const svc = yield* SessionPrompt.Service
+          return yield* svc.shell({ ...body, sessionID })
+        }),
     )
     .post(
       "/:sessionID/revert",
@@ -1036,15 +1026,13 @@ export const SessionRoutes = lazy(() =>
       async (c) => {
         const sessionID = c.req.valid("param").sessionID
         log.info("revert", c.req.valid("json"))
-        const session = await AppRuntime.runPromise(
-          SessionRevert.Service.use((svc) =>
-            svc.revert({
-              sessionID,
-              ...c.req.valid("json"),
-            }),
-          ),
-        )
-        return c.json(session)
+        return jsonRequest("SessionRoutes.revert", c, function* () {
+          const svc = yield* SessionRevert.Service
+          return yield* svc.revert({
+            sessionID,
+            ...c.req.valid("json"),
+          })
+        })
       },
     )
     .post(
@@ -1071,11 +1059,12 @@ export const SessionRoutes = lazy(() =>
           sessionID: SessionID.zod,
         }),
       ),
-      async (c) => {
-        const sessionID = c.req.valid("param").sessionID
-        const session = await AppRuntime.runPromise(SessionRevert.Service.use((svc) => svc.unrevert({ sessionID })))
-        return c.json(session)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.unrevert", c, function* () {
+          const sessionID = c.req.valid("param").sessionID
+          const svc = yield* SessionRevert.Service
+          return yield* svc.unrevert({ sessionID })
+        }),
     )
     .post(
       "/:sessionID/permissions/:permissionID",
@@ -1104,17 +1093,15 @@ export const SessionRoutes = lazy(() =>
         }),
       ),
       validator("json", z.object({ response: Permission.Reply.zod })),
-      async (c) => {
-        const params = c.req.valid("param")
-        await AppRuntime.runPromise(
-          Permission.Service.use((svc) =>
-            svc.reply({
-              requestID: params.permissionID,
-              reply: c.req.valid("json").response,
-            }),
-          ),
-        )
-        return c.json(true)
-      },
+      async (c) =>
+        jsonRequest("SessionRoutes.permissionRespond", c, function* () {
+          const params = c.req.valid("param")
+          const svc = yield* Permission.Service
+          yield* svc.reply({
+            requestID: params.permissionID,
+            reply: c.req.valid("json").response,
+          })
+          return true
+        }),
     ),
 )

+ 6 - 2
packages/opencode/src/server/routes/instance/tui.ts

@@ -4,10 +4,10 @@ import z from "zod"
 import { Bus } from "@/bus"
 import { Session } from "@/session"
 import { TuiEvent } from "@/cli/cmd/tui/event"
-import { AppRuntime } from "@/effect/app-runtime"
 import { AsyncQueue } from "@/util/queue"
 import { errors } from "../../error"
 import { lazy } from "@/util/lazy"
+import { runRequest } from "./trace"
 
 const TuiRequest = z.object({
   path: z.string(),
@@ -371,7 +371,11 @@ export const TuiRoutes = lazy(() =>
       validator("json", TuiEvent.SessionSelect.properties),
       async (c) => {
         const { sessionID } = c.req.valid("json")
-        await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(sessionID)))
+        await runRequest(
+          "TuiRoutes.sessionSelect",
+          c,
+          Session.Service.use((svc) => svc.get(sessionID)),
+        )
         await Bus.publish(TuiEvent.SessionSelect, { sessionID })
         return c.json(true)
       },

+ 4 - 1
packages/opencode/src/server/workspace.ts

@@ -10,6 +10,7 @@ import { Instance } from "@/project/instance"
 import { Session } from "@/session"
 import { SessionID } from "@/session/schema"
 import { AppRuntime } from "@/effect/app-runtime"
+import { Effect } from "effect"
 import { Log } from "@/util"
 import { ServerProxy } from "./proxy"
 
@@ -42,7 +43,9 @@ async function getSessionWorkspace(url: URL) {
   const id = getSessionID(url)
   if (!id) return null
 
-  const session = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(id))).catch(() => undefined)
+  const session = await AppRuntime.runPromise(
+    Session.Service.use((svc) => svc.get(id)).pipe(Effect.withSpan("WorkspaceRouter.lookup")),
+  ).catch(() => undefined)
   return session?.workspaceID
 }