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

refactor(flags): route session workspaces through runtime flags (#27335)

Shoubhit Dash 3 месяцев назад
Родитель
Сommit
eed0eddc63

+ 1 - 0
packages/opencode/src/effect/runtime-flags.ts

@@ -23,6 +23,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
   experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
   experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
   experimentalEventSystem: enabledByExperimental("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
+  experimentalWorkspaces: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
   client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")),
 }) {}
 

+ 8 - 5
packages/opencode/src/session/session.ts

@@ -4,7 +4,6 @@ import { BusEvent } from "@/bus/bus-event"
 import { Bus } from "@/bus"
 import { Decimal } from "decimal.js"
 import { type ProviderMetadata, type LanguageModelUsage } from "ai"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 
 import { Database } from "@/storage/db"
@@ -38,6 +37,7 @@ import { Permission } from "@/permission"
 import { Global } from "@opencode-ai/core/global"
 import { Effect, Layer, Option, Context, Schema, Types } from "effect"
 import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 
 const log = Log.create({ service: "session" })
 
@@ -507,12 +507,13 @@ export type Patch = Types.DeepMutable<SyncEvent.Event<typeof Event.Updated>["dat
 const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
   Effect.sync(() => Database.use(fn))
 
-export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service | SyncEvent.Service> = Layer.effect(
+export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service | SyncEvent.Service | RuntimeFlags.Service> = Layer.effect(
   Service,
   Effect.gen(function* () {
     const bus = yield* Bus.Service
     const storage = yield* Storage.Service
     const sync = yield* SyncEvent.Service
+    const flags = yield* RuntimeFlags.Service
 
     const createNext = Effect.fn("Session.createNext")(function* (input: {
       id?: SessionID
@@ -550,7 +551,7 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
 
       yield* sync.run(Event.Created, { sessionID: result.id, info: result })
 
-      if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
+      if (!flags.experimentalWorkspaces) {
         // This only exist for backwards compatibility. We should not be
         // manually publishing this event; it is a sync event now
         yield* bus.publish(Event.Updated, {
@@ -570,7 +571,7 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
 
     const list = Effect.fn("Session.list")(function* (input?: ListInput) {
       const ctx = yield* InstanceState.context
-      return Array.from(listByProject({ projectID: ctx.project.id, ...input }))
+      return Array.from(listByProject({ projectID: ctx.project.id, experimentalWorkspaces: flags.experimentalWorkspaces, ...input }))
     })
 
     const children = Effect.fn("Session.children")(function* (parentID: SessionID) {
@@ -860,11 +861,13 @@ export const defaultLayer = layer.pipe(
   Layer.provide(Bus.layer),
   Layer.provide(Storage.defaultLayer),
   Layer.provide(SyncEvent.defaultLayer),
+  Layer.provide(RuntimeFlags.defaultLayer),
 )
 
 function* listByProject(
   input: ListInput & {
     projectID: ProjectID
+    experimentalWorkspaces: boolean
   },
 ) {
   const conditions = [eq(SessionTable.project_id, input.projectID)]
@@ -882,7 +885,7 @@ function* listByProject(
           : or(...conds)!,
       )
     }
-  } else if (input.scope !== "project" && !Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
+  } else if (input.scope !== "project" && !input.experimentalWorkspaces) {
     if (input.directory) {
       conditions.push(eq(SessionTable.directory, input.directory))
     }

+ 1 - 0
packages/opencode/test/effect/runtime-flags.test.ts

@@ -34,6 +34,7 @@ describe("RuntimeFlags", () => {
       expect(flags.experimentalLspTool).toBe(true)
       expect(flags.experimentalPlanMode).toBe(true)
       expect(flags.experimentalEventSystem).toBe(true)
+      expect(flags.experimentalWorkspaces).toBe(true)
       expect(flags.client).toBe("desktop")
     }),
   )

+ 1 - 0
packages/opencode/test/preload.ts

@@ -35,6 +35,7 @@ process.env["XDG_CONFIG_HOME"] = path.join(dir, "config")
 process.env["XDG_STATE_HOME"] = path.join(dir, "state")
 process.env["OPENCODE_MODELS_PATH"] = path.join(import.meta.dir, "tool", "fixtures", "models-api.json")
 process.env["OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"] = "true"
+process.env["OPENCODE_EXPERIMENTAL_WORKSPACES"] = "true"
 
 // Set test home directory to isolate tests from user's actual home directory
 // This prevents tests from picking up real user configs/skills from ~/.claude/skills

+ 13 - 9
packages/opencode/test/server/session-list.test.ts

@@ -1,19 +1,28 @@
 import { afterEach, describe, expect } from "bun:test"
-import { Effect } from "effect"
+import { Effect, Layer } from "effect"
 import { Session as SessionNs } from "@/session/session"
 import * as Log from "@opencode-ai/core/util/log"
 import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { mkdir } from "fs/promises"
 import path from "path"
 import { Database } from "@/storage/db"
 import { SessionTable } from "@/session/session.sql"
 import { eq } from "drizzle-orm"
 import { testEffect } from "../lib/effect"
+import { Bus } from "@/bus"
+import { Storage } from "@/storage/storage"
+import { SyncEvent } from "@/sync"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 
 void Log.init({ print: false })
-const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
-const it = testEffect(SessionNs.defaultLayer)
+const it = testEffect(
+  SessionNs.layer.pipe(
+    Layer.provide(Bus.layer),
+    Layer.provide(Storage.defaultLayer),
+    Layer.provide(SyncEvent.defaultLayer),
+    Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: false })),
+  ),
+)
 
 const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
   Effect.acquireRelease(
@@ -22,7 +31,6 @@ const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
   )
 
 afterEach(async () => {
-  Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces
   await disposeAllInstances()
 })
 
@@ -31,7 +39,6 @@ describe("session.list", () => {
     "does not filter by directory when directory is omitted",
     () =>
       Effect.gen(function* () {
-        Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false
         const test = yield* TestInstance
         yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true }))
         yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true }))
@@ -60,7 +67,6 @@ describe("session.list", () => {
     "filters by directory when directory is provided",
     () =>
       Effect.gen(function* () {
-        Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false
         const test = yield* TestInstance
         yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "opencode"), { recursive: true }))
         yield* Effect.promise(() => mkdir(path.join(test.directory, "packages", "app"), { recursive: true }))
@@ -91,7 +97,6 @@ describe("session.list", () => {
     "filters by path and ignores directory when path is provided",
     () =>
       Effect.gen(function* () {
-        Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false
         const test = yield* TestInstance
         yield* Effect.promise(() =>
           mkdir(path.join(test.directory, "packages", "opencode", "src", "deep"), { recursive: true }),
@@ -129,7 +134,6 @@ describe("session.list", () => {
     "falls back to directory when filtering legacy sessions without path",
     () =>
       Effect.gen(function* () {
-        Flag.OPENCODE_EXPERIMENTAL_WORKSPACES = false
         const test = yield* TestInstance
         yield* Effect.promise(() =>
           mkdir(path.join(test.directory, "packages", "opencode", "src"), { recursive: true }),

+ 15 - 4
packages/opencode/test/session/session.test.ts

@@ -3,16 +3,29 @@ import { Deferred, Effect, Exit, Layer } from "effect"
 import { Session as SessionNs } from "@/session/session"
 import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
 import * as Log from "@opencode-ai/core/util/log"
-import { Flag } from "@opencode-ai/core/flag/flag"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageID, PartID, type SessionID } from "../../src/session/schema"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 import { provideInstance, tmpdirScoped } from "../fixture/fixture"
 import { testEffect } from "../lib/effect"
+import { Bus } from "@/bus"
+import { Storage } from "@/storage/storage"
+import { SyncEvent } from "@/sync"
+import { RuntimeFlags } from "@/effect/runtime-flags"
 
 void Log.init({ print: false })
 
-const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, CrossSpawnSpawner.defaultLayer))
+const it = testEffect(
+  Layer.mergeAll(
+    SessionNs.layer.pipe(
+      Layer.provide(Bus.layer),
+      Layer.provide(Storage.defaultLayer),
+      Layer.provide(SyncEvent.defaultLayer),
+      Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces: false })),
+    ),
+    CrossSpawnSpawner.defaultLayer,
+  ),
+)
 
 const awaitDeferred = <T>(deferred: Deferred.Deferred<T>, message: string) =>
   Effect.race(
@@ -56,8 +69,6 @@ describe("session.created event", () => {
 
   it.instance("session.created event should be emitted before session.updated", () =>
     Effect.gen(function* () {
-      if (Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return
-
       const session = yield* SessionNs.Service
       const events: string[] = []
       const received = yield* Deferred.make<string[]>()