opencode-agent[bot] 2 месяцев назад
Родитель
Сommit
71c3a7c8f2

+ 6 - 17
packages/core/src/effect/layer-node/layer-node.ts

@@ -50,11 +50,7 @@ export function make<
   const T extends Tag | undefined = undefined,
 >(
   input: MakeInput<Implementation, Items, T>,
-): Node<
-  Layer.Success<Implementation>,
-  Layer.Error<Implementation> | Error<Items[number]>,
-  T
-> {
+): Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, T> {
   return {
     kind: "layer",
     name: input.service !== undefined ? input.service.key : input.name,
@@ -65,10 +61,7 @@ export function make<
   }
 }
 
-export function unbound<R, Shape, const T extends Tag>(
-  service: Context.Key<R, Shape>,
-  tag: T,
-): Node<R, never, T> {
+export function unbound<R, Shape, const T extends Tag>(service: Context.Key<R, Shape>, tag: T): Node<R, never, T> {
   return {
     kind: "unbound",
     name: service.key,
@@ -100,16 +93,12 @@ export interface Tags<Config extends TagConfig> {
   ) => <const Implementation extends Layer.Any, const Items extends NodeList>(
     input: DistributiveOmit<MakeInput<Implementation, Items, Tag<Name>>, "tag"> &
       CheckTags<Items, Name | Extract<Config[Name][number], string>>,
-  ) => Node<
-    Layer.Success<Implementation>,
-    Layer.Error<Implementation> | Error<Items[number]>,
-    Tag<Name>
-  >
+  ) => Node<Layer.Success<Implementation>, Layer.Error<Implementation> | Error<Items[number]>, Tag<Name>>
 }
 
-export function tags<
-  const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] },
->(config: Config): Tags<Config> {
+export function tags<const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] }>(
+  config: Config,
+): Tags<Config> {
   const names = Object.keys(config) as TagNames<Config>[]
   const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags<Config>["values"]
   return {

+ 256 - 259
packages/core/src/session.ts

@@ -184,271 +184,268 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/v2
 export const layer = Layer.effect(
   Service,
   Effect.gen(function* () {
-          const database = yield* Database.Service
-          const db = database.db
-          const events = yield* EventV2.Service
-          const projects = yield* ProjectV2.Service
-          const execution = yield* SessionExecution.Service
-          const store = yield* SessionStore.Service
-          const locations = yield* LocationServiceMap.Service
-          const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
-          const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
-          const decode = (row: typeof SessionMessageTable.$inferSelect) =>
-            decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(
-              Effect.mapError(
-                () =>
-                  new MessageDecodeError({
-                    sessionID: SessionSchema.ID.make(row.session_id),
-                    messageID: SessionMessage.ID.make(row.id),
-                  }),
-              ),
-            )
-
-          const result = Service.of({
-            create: Effect.fn("V2Session.create")(function* (input) {
-              const sessionID = input.id ?? SessionSchema.ID.create()
-              const recorded = yield* store.get(sessionID)
-              if (recorded) return recorded
-              const project = yield* projects.resolve(input.location.directory)
-              yield* db
-                .insert(ProjectTable)
-                .values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] })
-                .onConflictDoNothing()
-                .run()
-                .pipe(Effect.orDie)
-              const now = Date.now()
-              const info = SessionV1.SessionInfo.make({
-                id: sessionID,
-                slug: Slug.create(),
-                version: InstallationVersion,
-                projectID: project.id,
-                directory: input.location.directory,
-                path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"),
-                workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined,
-                title: `New session - ${new Date(now).toISOString()}`,
-                agent: input.agent,
-                model: input.model
-                  ? {
-                      id: ModelV2.ID.make(input.model.id),
-                      providerID: input.model.providerID,
-                      variant: input.model.variant,
-                    }
-                  : undefined,
-                cost: 0,
-                tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
-                time: { created: now, updated: now },
-              })
-              const projected = yield* events
-                .publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location })
-                .pipe(
-                  Effect.as({ type: "created" } as const),
-                  Effect.catchDefect((defect) => {
-                    if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) {
-                      return Effect.die(defect)
-                    }
-                    // Concurrent creation lost the projection race. The existing Session identity wins.
-                    return store
-                      .get(sessionID)
-                      .pipe(
-                        Effect.flatMap((session) =>
-                          session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect),
-                        ),
-                      )
-                  }),
-                )
-              if (projected.type === "existing") return projected.session
-              // TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice.
-              return yield* result.get(sessionID).pipe(Effect.orDie)
-            }),
-            get: Effect.fn("V2Session.get")(function* (sessionID) {
-              const session = yield* store.get(sessionID)
-              if (!session) return yield* new NotFoundError({ sessionID })
-              return session
+    const database = yield* Database.Service
+    const db = database.db
+    const events = yield* EventV2.Service
+    const projects = yield* ProjectV2.Service
+    const execution = yield* SessionExecution.Service
+    const store = yield* SessionStore.Service
+    const locations = yield* LocationServiceMap.Service
+    const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message)
+    const isDurableSessionEvent = Schema.is(SessionEvent.Durable)
+    const decode = (row: typeof SessionMessageTable.$inferSelect) =>
+      decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe(
+        Effect.mapError(
+          () =>
+            new MessageDecodeError({
+              sessionID: SessionSchema.ID.make(row.session_id),
+              messageID: SessionMessage.ID.make(row.id),
             }),
-            list: Effect.fn("V2Session.list")(function* (input = {}) {
-              const direction = input.anchor?.direction ?? "next"
-              const requestedOrder = input.order ?? "desc"
-              const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
-              const sortColumn = SessionTable.time_created
-              const conditions: SQL[] = []
-              if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
-              if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
-              if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
-              if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
-              if (input.anchor) {
-                conditions.push(
-                  order === "asc"
-                    ? or(
-                        gt(sortColumn, input.anchor.time),
-                        and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)),
-                      )!
-                    : or(
-                        lt(sortColumn, input.anchor.time),
-                        and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)),
-                      )!,
-                )
+        ),
+      )
+
+    const result = Service.of({
+      create: Effect.fn("V2Session.create")(function* (input) {
+        const sessionID = input.id ?? SessionSchema.ID.create()
+        const recorded = yield* store.get(sessionID)
+        if (recorded) return recorded
+        const project = yield* projects.resolve(input.location.directory)
+        yield* db
+          .insert(ProjectTable)
+          .values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] })
+          .onConflictDoNothing()
+          .run()
+          .pipe(Effect.orDie)
+        const now = Date.now()
+        const info = SessionV1.SessionInfo.make({
+          id: sessionID,
+          slug: Slug.create(),
+          version: InstallationVersion,
+          projectID: project.id,
+          directory: input.location.directory,
+          path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"),
+          workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined,
+          title: `New session - ${new Date(now).toISOString()}`,
+          agent: input.agent,
+          model: input.model
+            ? {
+                id: ModelV2.ID.make(input.model.id),
+                providerID: input.model.providerID,
+                variant: input.model.variant,
+              }
+            : undefined,
+          cost: 0,
+          tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
+          time: { created: now, updated: now },
+        })
+        const projected = yield* events
+          .publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location })
+          .pipe(
+            Effect.as({ type: "created" } as const),
+            Effect.catchDefect((defect) => {
+              if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) {
+                return Effect.die(defect)
               }
-              const query = db
-                .select()
-                .from(SessionTable)
-                .where(conditions.length > 0 ? and(...conditions) : undefined)
-                .orderBy(
-                  order === "asc" ? asc(sortColumn) : desc(sortColumn),
-                  order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
+              // Concurrent creation lost the projection race. The existing Session identity wins.
+              return store
+                .get(sessionID)
+                .pipe(
+                  Effect.flatMap((session) =>
+                    session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect),
+                  ),
                 )
-              const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
-                Effect.orDie,
-              )
-              return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
             }),
-            messages: Effect.fn("V2Session.messages")(function* (input) {
-              yield* result.get(input.sessionID)
-              const direction = input.cursor?.direction ?? "next"
-              const requestedOrder = input.order ?? "desc"
-              const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
-              const anchor = input.cursor
-                ? yield* db
-                    .select({ seq: SessionMessageTable.seq })
-                    .from(SessionMessageTable)
-                    .where(
-                      and(
-                        eq(SessionMessageTable.session_id, input.sessionID),
-                        eq(SessionMessageTable.id, input.cursor.id),
-                      ),
-                    )
-                    .get()
-                    .pipe(Effect.orDie)
-                : undefined
-              if (input.cursor && !anchor) return []
-              const boundary = anchor
-                ? order === "asc"
-                  ? gt(SessionMessageTable.seq, anchor.seq)
-                  : lt(SessionMessageTable.seq, anchor.seq)
-                : undefined
-              const where = boundary
-                ? and(eq(SessionMessageTable.session_id, input.sessionID), boundary)
-                : eq(SessionMessageTable.session_id, input.sessionID)
-              const query = db
-                .select()
-                .from(SessionMessageTable)
-                .where(where)
-                .orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq))
-              const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
-                Effect.orDie,
+          )
+        if (projected.type === "existing") return projected.session
+        // TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice.
+        return yield* result.get(sessionID).pipe(Effect.orDie)
+      }),
+      get: Effect.fn("V2Session.get")(function* (sessionID) {
+        const session = yield* store.get(sessionID)
+        if (!session) return yield* new NotFoundError({ sessionID })
+        return session
+      }),
+      list: Effect.fn("V2Session.list")(function* (input = {}) {
+        const direction = input.anchor?.direction ?? "next"
+        const requestedOrder = input.order ?? "desc"
+        const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
+        const sortColumn = SessionTable.time_created
+        const conditions: SQL[] = []
+        if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory))
+        if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
+        if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project))
+        if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
+        if (input.anchor) {
+          conditions.push(
+            order === "asc"
+              ? or(
+                  gt(sortColumn, input.anchor.time),
+                  and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)),
+                )!
+              : or(
+                  lt(sortColumn, input.anchor.time),
+                  and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)),
+                )!,
+          )
+        }
+        const query = db
+          .select()
+          .from(SessionTable)
+          .where(conditions.length > 0 ? and(...conditions) : undefined)
+          .orderBy(
+            order === "asc" ? asc(sortColumn) : desc(sortColumn),
+            order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
+          )
+        const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
+          Effect.orDie,
+        )
+        return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row))
+      }),
+      messages: Effect.fn("V2Session.messages")(function* (input) {
+        yield* result.get(input.sessionID)
+        const direction = input.cursor?.direction ?? "next"
+        const requestedOrder = input.order ?? "desc"
+        const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder
+        const anchor = input.cursor
+          ? yield* db
+              .select({ seq: SessionMessageTable.seq })
+              .from(SessionMessageTable)
+              .where(
+                and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.cursor.id)),
               )
-              return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode)
-            }),
-            message: Effect.fn("V2Session.message")(function* (input) {
-              const stored = yield* store.message(input.messageID)
-              return stored?.sessionID === input.sessionID ? stored.message : undefined
-            }),
-            context: Effect.fn("V2Session.context")(function* (sessionID) {
-              yield* result.get(sessionID)
-              return yield* store.context(sessionID)
-            }),
-            events: (input) =>
-              Stream.unwrap(
-                result
-                  .get(input.sessionID)
-                  .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
-              ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
-            history: Effect.fn("V2Session.history")(function* (input) {
-              yield* result.get(input.sessionID)
-              return yield* EventV2.readAggregate(db, {
-                ...input,
-                aggregateID: input.sessionID,
-                manifest: SessionDurable,
-              })
-            }),
-            prompt: Effect.fn("V2Session.prompt")((input) =>
-              Effect.uninterruptible(
-                Effect.gen(function* () {
-                  yield* result.get(input.sessionID)
-                  const prompt = resolvePrompt(input.prompt)
-                  const messageID = input.id ?? SessionMessage.ID.create()
-                  const delivery = input.delivery ?? "steer"
-                  const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
-                  const admitted = yield* SessionInput.admit(db, events, {
-                    id: messageID,
-                    sessionID: input.sessionID,
-                    prompt,
-                    delivery,
-                  }).pipe(
-                    Effect.catchDefect((defect) =>
-                      defect instanceof SessionInput.LifecycleConflict
-                        ? new PromptConflictError({ sessionID: input.sessionID, messageID })
-                        : Effect.die(defect),
-                    ),
-                  )
-                  if (!SessionInput.equivalent(admitted, expected))
-                    return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
-                  if (input.resume !== false) yield* execution.wake(admitted.sessionID)
-                  return admitted
-                }),
+              .get()
+              .pipe(Effect.orDie)
+          : undefined
+        if (input.cursor && !anchor) return []
+        const boundary = anchor
+          ? order === "asc"
+            ? gt(SessionMessageTable.seq, anchor.seq)
+            : lt(SessionMessageTable.seq, anchor.seq)
+          : undefined
+        const where = boundary
+          ? and(eq(SessionMessageTable.session_id, input.sessionID), boundary)
+          : eq(SessionMessageTable.session_id, input.sessionID)
+        const query = db
+          .select()
+          .from(SessionMessageTable)
+          .where(where)
+          .orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq))
+        const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe(
+          Effect.orDie,
+        )
+        return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode)
+      }),
+      message: Effect.fn("V2Session.message")(function* (input) {
+        const stored = yield* store.message(input.messageID)
+        return stored?.sessionID === input.sessionID ? stored.message : undefined
+      }),
+      context: Effect.fn("V2Session.context")(function* (sessionID) {
+        yield* result.get(sessionID)
+        return yield* store.context(sessionID)
+      }),
+      events: (input) =>
+        Stream.unwrap(
+          result
+            .get(input.sessionID)
+            .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
+        ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
+      history: Effect.fn("V2Session.history")(function* (input) {
+        yield* result.get(input.sessionID)
+        return yield* EventV2.readAggregate(db, {
+          ...input,
+          aggregateID: input.sessionID,
+          manifest: SessionDurable,
+        })
+      }),
+      prompt: Effect.fn("V2Session.prompt")((input) =>
+        Effect.uninterruptible(
+          Effect.gen(function* () {
+            yield* result.get(input.sessionID)
+            const prompt = resolvePrompt(input.prompt)
+            const messageID = input.id ?? SessionMessage.ID.create()
+            const delivery = input.delivery ?? "steer"
+            const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
+            const admitted = yield* SessionInput.admit(db, events, {
+              id: messageID,
+              sessionID: input.sessionID,
+              prompt,
+              delivery,
+            }).pipe(
+              Effect.catchDefect((defect) =>
+                defect instanceof SessionInput.LifecycleConflict
+                  ? new PromptConflictError({ sessionID: input.sessionID, messageID })
+                  : Effect.die(defect),
               ),
-            ),
-            shell: Effect.fn("V2Session.shell")(function* () {
-              return yield* new OperationUnavailableError({ operation: "shell" })
-            }),
-            skill: Effect.fn("V2Session.skill")(function* () {
-              return yield* new OperationUnavailableError({ operation: "skill" })
-            }),
-            switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
-              yield* result.get(input.sessionID)
-              yield* events.publish(SessionEvent.AgentSwitched, {
-                sessionID: input.sessionID,
-                messageID: SessionMessage.ID.create(),
-                timestamp: yield* DateTime.now,
-                agent: input.agent,
-              })
-            }),
-            switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
-              yield* result.get(input.sessionID)
-              yield* events.publish(SessionEvent.ModelSwitched, {
-                sessionID: input.sessionID,
-                messageID: SessionMessage.ID.create(),
-                timestamp: yield* DateTime.now,
-                model: input.model,
-              })
-            }),
-            compact: Effect.fn("V2Session.compact")(function* (input) {
-              yield* result.get(input.sessionID)
-              return yield* new OperationUnavailableError({ operation: "compact" })
-            }),
-            wait: Effect.fn("V2Session.wait")(function* (sessionID) {
-              yield* result.get(sessionID)
-              return yield* new OperationUnavailableError({ operation: "wait" })
-            }),
-            active: execution.active,
-            resume: Effect.fn("V2Session.resume")(function* (sessionID) {
-              yield* result.get(sessionID)
-              yield* execution.resume(sessionID)
-            }),
-            interrupt: Effect.fn("V2Session.interrupt")((sessionID) =>
-              Effect.uninterruptible(execution.interrupt(sessionID)),
-            ),
-            revert: {
-              stage: Effect.fn("V2Session.revert.stage")(function* (input) {
-                const session = yield* result.get(input.sessionID)
-                return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe(
-                  Effect.provideService(Database.Service, database),
-                  Effect.provideService(EventV2.Service, events),
-                  Effect.provide(locations.get(session.location)),
-                )
-              }),
-              clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) {
-                const session = yield* result.get(sessionID)
-                yield* SessionRevert.clear(session).pipe(
-                  Effect.provideService(EventV2.Service, events),
-                  Effect.provide(locations.get(session.location)),
-                )
-              }),
-              commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) {
-                const session = yield* result.get(sessionID)
-                yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events))
-              }),
-            },
-          })
+            )
+            if (!SessionInput.equivalent(admitted, expected))
+              return yield* new PromptConflictError({ sessionID: input.sessionID, messageID })
+            if (input.resume !== false) yield* execution.wake(admitted.sessionID)
+            return admitted
+          }),
+        ),
+      ),
+      shell: Effect.fn("V2Session.shell")(function* () {
+        return yield* new OperationUnavailableError({ operation: "shell" })
+      }),
+      skill: Effect.fn("V2Session.skill")(function* () {
+        return yield* new OperationUnavailableError({ operation: "skill" })
+      }),
+      switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) {
+        yield* result.get(input.sessionID)
+        yield* events.publish(SessionEvent.AgentSwitched, {
+          sessionID: input.sessionID,
+          messageID: SessionMessage.ID.create(),
+          timestamp: yield* DateTime.now,
+          agent: input.agent,
+        })
+      }),
+      switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
+        yield* result.get(input.sessionID)
+        yield* events.publish(SessionEvent.ModelSwitched, {
+          sessionID: input.sessionID,
+          messageID: SessionMessage.ID.create(),
+          timestamp: yield* DateTime.now,
+          model: input.model,
+        })
+      }),
+      compact: Effect.fn("V2Session.compact")(function* (input) {
+        yield* result.get(input.sessionID)
+        return yield* new OperationUnavailableError({ operation: "compact" })
+      }),
+      wait: Effect.fn("V2Session.wait")(function* (sessionID) {
+        yield* result.get(sessionID)
+        return yield* new OperationUnavailableError({ operation: "wait" })
+      }),
+      active: execution.active,
+      resume: Effect.fn("V2Session.resume")(function* (sessionID) {
+        yield* result.get(sessionID)
+        yield* execution.resume(sessionID)
+      }),
+      interrupt: Effect.fn("V2Session.interrupt")((sessionID) =>
+        Effect.uninterruptible(execution.interrupt(sessionID)),
+      ),
+      revert: {
+        stage: Effect.fn("V2Session.revert.stage")(function* (input) {
+          const session = yield* result.get(input.sessionID)
+          return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe(
+            Effect.provideService(Database.Service, database),
+            Effect.provideService(EventV2.Service, events),
+            Effect.provide(locations.get(session.location)),
+          )
+        }),
+        clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) {
+          const session = yield* result.get(sessionID)
+          yield* SessionRevert.clear(session).pipe(
+            Effect.provideService(EventV2.Service, events),
+            Effect.provide(locations.get(session.location)),
+          )
+        }),
+        commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) {
+          const session = yield* result.get(sessionID)
+          yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events))
+        }),
+      },
+    })
 
     return result
   }),

+ 1 - 2
packages/core/test/effect/layer-node/layer-node-types.test.ts

@@ -16,8 +16,7 @@ class OtherError {
 
 const tags = LayerNode.tags({ app: [] })
 const make = tags.make("app")
-const build = <A, E>(root: LayerNode.Node<A, E, any>) =>
-  LayerNodeTree.compile(root) as Layer.Layer<A, E>
+const build = <A, E>(root: LayerNode.Node<A, E, any>) => LayerNodeTree.compile(root) as Layer.Layer<A, E>
 const aLayer = Layer.succeed(A, A.of({}))
 const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
 const cLayer = Layer.effect(

+ 4 - 4
packages/core/test/effect/layer-node/layer-node.test.ts

@@ -14,10 +14,10 @@ class App extends Context.Service<App, { readonly run: Effect.Effect<string[]> }
 const tags = LayerNode.tags({ app: [] })
 const make = tags.make("app")
 const build = <A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) =>
-  LayerNodeTree.compile(
-    root,
-    new Map(replacements?.map((item) => [item.source, item.replacement])),
-  ) as Layer.Layer<A, E>
+  LayerNodeTree.compile(root, new Map(replacements?.map((item) => [item.source, item.replacement]))) as Layer.Layer<
+    A,
+    E
+  >
 const valueLayer = Layer.succeed(Value, Value.of({ value: "production" }))
 const greetingLayer = Layer.effect(
   Greeting,

+ 3 - 1
packages/core/test/location-layer.test.ts

@@ -111,7 +111,9 @@ describe("LocationServiceMap", () => {
               }
             }).pipe(
               Effect.scoped,
-              Effect.provide(LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) }))),
+              Effect.provide(
+                LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) })),
+              ),
             )
 
           const blockedState = yield* update(blocked.path)

+ 5 - 1
packages/opencode/src/agent/agent.ts

@@ -447,7 +447,11 @@ export const defaultLayer = layer.pipe(
   Layer.provide(locationServiceMapLayer),
 )
 
-const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] })
+const locationServiceMapNode = LayerNode.make({
+  service: LocationServiceMap.Service,
+  layer: locationServiceMapLayer,
+  deps: [],
+})
 
 export const node = LayerNode.make({
   service: Service,

+ 5 - 1
packages/opencode/src/session/system.ts

@@ -134,7 +134,11 @@ export const defaultLayer = layer.pipe(
   Layer.provide(locationServiceMapLayer),
 )
 
-const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] })
+const locationServiceMapNode = LayerNode.make({
+  service: LocationServiceMap.Service,
+  layer: locationServiceMapLayer,
+  deps: [],
+})
 
 export const node = LayerNode.make({
   service: Service,

+ 1 - 4
packages/opencode/test/session/processor-effect.test.ts

@@ -232,10 +232,7 @@ const fragmentFailureLLM = Layer.succeed(
 const fragmentFailureEnv = LayerNodeTree.compile(
   root,
   new Map(
-    [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [
-      item.source,
-      item.replacement,
-    ]),
+    [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [item.source, item.replacement]),
   ),
 )
 const itFragmentFailure = testEffect(fragmentFailureEnv)

+ 3 - 3
packages/opencode/test/session/snapshot-tool-race.test.ts

@@ -92,9 +92,9 @@ const it = testEffect(
     root,
     new Map(
       [
-      LayerNode.replace(MCP.layer, mcp),
-      LayerNode.replace(LSP.layer, lsp),
-      LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
+        LayerNode.replace(MCP.layer, mcp),
+        LayerNode.replace(LSP.layer, lsp),
+        LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
       ].map((item) => [item.source, item.replacement]),
     ),
   ),

+ 8 - 1
packages/opencode/test/share/share-next.test.ts

@@ -45,7 +45,14 @@ function requestLayer(client: HttpClient.HttpClient) {
 function integrationLayer(client: HttpClient.HttpClient) {
   const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))
   return LayerNodeTree.compile(
-    LayerNode.group([ShareNext.node, EventV2Bridge.node, Session.node, SessionProjector.node, AccountRepo.node, Database.node]),
+    LayerNode.group([
+      ShareNext.node,
+      EventV2Bridge.node,
+      Session.node,
+      SessionProjector.node,
+      AccountRepo.node,
+      Database.node,
+    ]),
     new Map([[replacement.source, replacement.replacement]]),
   )
 }