Move service state into InstanceState, flatten service facades (#18483)

This commit is contained in:
Kit Langton
2026-03-21 00:51:35 -04:00
committed by GitHub
parent 40aeaa120d
commit 38e0dc9ccd
84 changed files with 4536 additions and 3742 deletions

View File

@@ -1,7 +1,11 @@
import { Plugin } from "../plugin"
import { Format } from "../format"
import { LSP } from "../lsp"
import { File } from "../file"
import { FileWatcher } from "../file/watcher"
import { Snapshot } from "../snapshot"
import { Project } from "./project"
import { Vcs } from "./vcs"
import { Bus } from "../bus"
import { Command } from "../command"
import { Instance } from "./instance"
@@ -12,8 +16,12 @@ export async function InstanceBootstrap() {
Log.Default.info("bootstrapping", { directory: Instance.directory })
await Plugin.init()
ShareNext.init()
Format.init()
await LSP.init()
File.init()
FileWatcher.init()
Vcs.init()
Snapshot.init()
Bus.subscribe(Command.Event.Executed, async (payload) => {
if (payload.properties.name === Command.Default.INIT) {

View File

@@ -7,13 +7,13 @@ import { Context } from "../util/context"
import { Project } from "./project"
import { State } from "./state"
interface Context {
export interface Shape {
directory: string
worktree: string
project: Project.Info
}
const context = Context.create<Context>("instance")
const cache = new Map<string, Promise<Context>>()
const context = Context.create<Shape>("instance")
const cache = new Map<string, Promise<Shape>>()
const disposal = {
all: undefined as Promise<void> | undefined,
@@ -52,7 +52,7 @@ function boot(input: { directory: string; init?: () => Promise<any>; project?: P
})
}
function track(directory: string, next: Promise<Context>) {
function track(directory: string, next: Promise<Shape>) {
const task = next.catch((error) => {
if (cache.get(directory) === task) cache.delete(directory)
throw error

View File

@@ -1,7 +1,8 @@
import { Effect, Layer, ServiceMap } from "effect"
import { Bus } from "@/bus"
import { BusEvent } from "@/bus/bus-event"
import { InstanceContext } from "@/effect/instance-context"
import { InstanceState } from "@/effect/instance-state"
import { makeRunPromise } from "@/effect/run-service"
import { FileWatcher } from "@/file/watcher"
import { Log } from "@/util/log"
import { git } from "@/util/git"
@@ -30,54 +31,81 @@ export namespace Vcs {
export type Info = z.infer<typeof Info>
export interface Interface {
readonly init: () => Effect.Effect<void>
readonly branch: () => Effect.Effect<string | undefined>
}
interface State {
current: string | undefined
}
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/Vcs") {}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const instance = yield* InstanceContext
let currentBranch: string | undefined
const state = yield* InstanceState.make<State>(
Effect.fn("Vcs.state")((ctx) =>
Effect.gen(function* () {
if (ctx.project.vcs !== "git") {
return { current: undefined }
}
if (instance.project.vcs === "git") {
const getCurrentBranch = async () => {
const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd: instance.project.worktree,
})
if (result.exitCode !== 0) return undefined
const text = result.text().trim()
return text || undefined
}
const getCurrentBranch = async () => {
const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd: ctx.worktree,
})
if (result.exitCode !== 0) return undefined
const text = result.text().trim()
return text || undefined
}
currentBranch = yield* Effect.promise(() => getCurrentBranch())
log.info("initialized", { branch: currentBranch })
const value = {
current: yield* Effect.promise(() => getCurrentBranch()),
}
log.info("initialized", { branch: value.current })
yield* Effect.acquireRelease(
Effect.sync(() =>
Bus.subscribe(
FileWatcher.Event.Updated,
Instance.bind(async (evt) => {
if (!evt.properties.file.endsWith("HEAD")) return
const next = await getCurrentBranch()
if (next !== currentBranch) {
log.info("branch changed", { from: currentBranch, to: next })
currentBranch = next
Bus.publish(Event.BranchUpdated, { branch: next })
}
}),
),
),
(unsubscribe) => Effect.sync(unsubscribe),
)
}
yield* Effect.acquireRelease(
Effect.sync(() =>
Bus.subscribe(
FileWatcher.Event.Updated,
Instance.bind(async (evt) => {
if (!evt.properties.file.endsWith("HEAD")) return
const next = await getCurrentBranch()
if (next !== value.current) {
log.info("branch changed", { from: value.current, to: next })
value.current = next
Bus.publish(Event.BranchUpdated, { branch: next })
}
}),
),
),
(unsubscribe) => Effect.sync(unsubscribe),
)
return value
}),
),
)
return Service.of({
init: Effect.fn("Vcs.init")(function* () {
yield* InstanceState.get(state)
}),
branch: Effect.fn("Vcs.branch")(function* () {
return currentBranch
return yield* InstanceState.use(state, (x) => x.current)
}),
})
}),
).pipe(Layer.fresh)
)
const runPromise = makeRunPromise(Service, layer)
export function init() {
return runPromise((svc) => svc.init())
}
export function branch() {
return runPromise((svc) => svc.branch())
}
}