Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/opencode/script/seed-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ const seed = async () => {
}
await Session.updateMessage(message)
await Session.updatePart(part)
await Project.update({ projectID: Instance.project.id, name: "E2E Project" })
await AppRuntime.runPromise(
Project.Service.use((svc) => svc.update({ projectID: Instance.project.id, name: "E2E Project" })),
)
},
})
} finally {
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/cli/cmd/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ export const GithubRunCommand = cmd({
shareId = await (async () => {
if (share === false) return
if (!share && repoData.data.private) return
await SessionShare.share(session.id)
await AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.share(session.id)))
return session.id.slice(-8)
})()
console.log("opencode session", session.id)
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/src/project/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { disposeInstance } from "@/effect/instance-registry"
import { Filesystem } from "@/util/filesystem"
import { iife } from "@/util/iife"
import { Log } from "@/util/log"
import { Effect } from "effect"
import { LocalContext } from "../util/local-context"
import { Project } from "./project"
import { WorkspaceContext } from "@/control-plane/workspace-context"
Expand All @@ -29,7 +30,9 @@ function boot(input: { directory: string; init?: () => Promise<any>; worktree?:
worktree: input.worktree,
project: input.project,
}
: await Project.fromDirectory(input.directory).then(({ project, sandbox }) => ({
: await Effect.runPromise(
Project.Service.use((svc) => svc.fromDirectory(input.directory)).pipe(Effect.provide(Project.defaultLayer)),
).then(({ project, sandbox }) => ({
directory: input.directory,
worktree: sandbox,
project,
Expand Down
36 changes: 1 addition & 35 deletions packages/opencode/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import { which } from "../util/which"
import { ProjectID } from "./schema"
import { Effect, Layer, Path, Scope, Context, Stream } from "effect"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { NodeFileSystem, NodePath } from "@effect/platform-node"
import { makeRuntime } from "@/effect/run-service"
import { NodePath } from "@effect/platform-node"
import { AppFileSystem } from "@/filesystem"
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"

Expand Down Expand Up @@ -463,19 +462,6 @@ export namespace Project {
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(NodePath.layer),
)
const { runPromise } = makeRuntime(Service, defaultLayer)

// ---------------------------------------------------------------------------
// Promise-based API (delegates to Effect service via runPromise)
// ---------------------------------------------------------------------------

export function fromDirectory(directory: string) {
return runPromise((svc) => svc.fromDirectory(directory))
}

export function discover(input: Info) {
return runPromise((svc) => svc.discover(input))
}

export function list() {
return Database.use((db) =>
Expand All @@ -498,24 +484,4 @@ export namespace Project {
db.update(ProjectTable).set({ time_initialized: Date.now() }).where(eq(ProjectTable.id, id)).run(),
)
}

export function initGit(input: { directory: string; project: Info }) {
return runPromise((svc) => svc.initGit(input))
}

export function update(input: UpdateInput) {
return runPromise((svc) => svc.update(input))
}

export function sandboxes(id: ProjectID) {
return runPromise((svc) => svc.sandboxes(id))
}

export function addSandbox(id: ProjectID, directory: string) {
return runPromise((svc) => svc.addSandbox(id, directory))
}

export function removeSandbox(id: ProjectID, directory: string) {
return runPromise((svc) => svc.removeSandbox(id, directory))
}
}
6 changes: 4 additions & 2 deletions packages/opencode/src/server/instance/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export const ExperimentalRoutes = lazy(() =>
},
}),
async (c) => {
const sandboxes = await Project.sandboxes(Instance.project.id)
const sandboxes = await AppRuntime.runPromise(Project.Service.use((svc) => svc.sandboxes(Instance.project.id)))
return c.json(sandboxes)
},
)
Expand All @@ -302,7 +302,9 @@ export const ExperimentalRoutes = lazy(() =>
async (c) => {
const body = c.req.valid("json")
await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.remove(body)))
await Project.removeSandbox(Instance.project.id, body.directory)
await AppRuntime.runPromise(
Project.Service.use((svc) => svc.removeSandbox(Instance.project.id, body.directory)),
)
return c.json(true)
},
)
Expand Down
9 changes: 4 additions & 5 deletions packages/opencode/src/server/instance/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@ export const ProjectRoutes = lazy(() =>
async (c) => {
const dir = Instance.directory
const prev = Instance.project
const next = await Project.initGit({
directory: dir,
project: prev,
})
const next = await AppRuntime.runPromise(
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)
await Instance.reload({
directory: dir,
Expand Down Expand Up @@ -112,7 +111,7 @@ export const ProjectRoutes = lazy(() =>
async (c) => {
const projectID = c.req.valid("param").projectID
const body = c.req.valid("json")
const project = await Project.update({ ...body, projectID })
const project = await AppRuntime.runPromise(Project.Service.use((svc) => svc.update({ ...body, projectID })))
return c.json(project)
},
),
Expand Down
22 changes: 17 additions & 5 deletions packages/opencode/src/server/instance/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export const SessionRoutes = lazy(() =>
validator("json", Session.create.schema),
async (c) => {
const body = c.req.valid("json") ?? {}
const session = await SessionShare.create(body)
const session = await AppRuntime.runPromise(SessionShare.Service.use((svc) => svc.create(body)))
return c.json(session)
},
)
Expand Down Expand Up @@ -437,8 +437,14 @@ export const SessionRoutes = lazy(() =>
),
async (c) => {
const sessionID = c.req.valid("param").sessionID
await SessionShare.share(sessionID)
const session = await Session.get(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)
},
)
Expand Down Expand Up @@ -511,8 +517,14 @@ export const SessionRoutes = lazy(() =>
),
async (c) => {
const sessionID = c.req.valid("param").sessionID
await SessionShare.unshare(sessionID)
const session = await Session.get(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)
},
)
Expand Down
8 changes: 0 additions & 8 deletions packages/opencode/src/share/session.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { makeRuntime } from "@/effect/run-service"
import { Session } from "@/session"
import { SessionID } from "@/session/schema"
import { SyncEvent } from "@/sync"
import { fn } from "@/util/fn"
import { Effect, Layer, Scope, Context } from "effect"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
Expand Down Expand Up @@ -58,10 +56,4 @@ export namespace SessionShare {
Layer.provide(Session.defaultLayer),
Layer.provide(Config.defaultLayer),
)

const { runPromise } = makeRuntime(Service, defaultLayer)

export const create = fn(Session.create.schema, (input) => runPromise((svc) => svc.create(input)))
export const share = fn(SessionID.zod, (sessionID) => runPromise((svc) => svc.share(sessionID)))
export const unshare = fn(SessionID.zod, (sessionID) => runPromise((svc) => svc.unshare(sessionID)))
}
26 changes: 18 additions & 8 deletions packages/opencode/test/project/migrate-global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ import { SessionID } from "../../src/session/schema"
import { Log } from "../../src/util/log"
import { $ } from "bun"
import { tmpdir } from "../fixture/fixture"
import { Effect } from "effect"

Log.init({ print: false })

function run<A>(fn: (svc: Project.Interface) => Effect.Effect<A>) {
return Effect.runPromise(
Effect.gen(function* () {
const svc = yield* Project.Service
return yield* fn(svc)
}).pipe(Effect.provide(Project.defaultLayer)),
)
}

function uid() {
return SessionID.make(crypto.randomUUID())
}
Expand Down Expand Up @@ -58,7 +68,7 @@ describe("migrateFromGlobal", () => {
await $`git config user.name "Test"`.cwd(tmp.path).quiet()
await $`git config user.email "test@opencode.test"`.cwd(tmp.path).quiet()
await $`git config commit.gpgsign false`.cwd(tmp.path).quiet()
const { project: pre } = await Project.fromDirectory(tmp.path)
const { project: pre } = await run((svc) => svc.fromDirectory(tmp.path))
expect(pre.id).toBe(ProjectID.global)

// 2. Seed a session under "global" with matching directory
Expand All @@ -68,7 +78,7 @@ describe("migrateFromGlobal", () => {
// 3. Make a commit so the project gets a real ID
await $`git commit --allow-empty -m "root"`.cwd(tmp.path).quiet()

const { project: real } = await Project.fromDirectory(tmp.path)
const { project: real } = await run((svc) => svc.fromDirectory(tmp.path))
expect(real.id).not.toBe(ProjectID.global)

// 4. The session should have been migrated to the real project ID
Expand All @@ -80,7 +90,7 @@ describe("migrateFromGlobal", () => {
test("migrates global sessions even when project row already exists", async () => {
// 1. Create a repo with a commit — real project ID created immediately
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
expect(project.id).not.toBe(ProjectID.global)

// 2. Ensure "global" project row exists (as it would from a prior no-git session)
Expand All @@ -94,7 +104,7 @@ describe("migrateFromGlobal", () => {

// 4. Call fromDirectory again — project row already exists,
// so the current code skips migration entirely. This is the bug.
await Project.fromDirectory(tmp.path)
await run((svc) => svc.fromDirectory(tmp.path))

const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
expect(row).toBeDefined()
Expand All @@ -103,7 +113,7 @@ describe("migrateFromGlobal", () => {

test("does not claim sessions with empty directory", async () => {
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
expect(project.id).not.toBe(ProjectID.global)

ensureGlobal()
Expand All @@ -113,7 +123,7 @@ describe("migrateFromGlobal", () => {
const id = uid()
seed({ id, dir: "", project: ProjectID.global })

await Project.fromDirectory(tmp.path)
await run((svc) => svc.fromDirectory(tmp.path))

const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
expect(row).toBeDefined()
Expand All @@ -122,7 +132,7 @@ describe("migrateFromGlobal", () => {

test("does not steal sessions from unrelated directories", async () => {
await using tmp = await tmpdir({ git: true })
const { project } = await Project.fromDirectory(tmp.path)
const { project } = await run((svc) => svc.fromDirectory(tmp.path))
expect(project.id).not.toBe(ProjectID.global)

ensureGlobal()
Expand All @@ -131,7 +141,7 @@ describe("migrateFromGlobal", () => {
const id = uid()
seed({ id, dir: "/some/other/dir", project: ProjectID.global })

await Project.fromDirectory(tmp.path)
await run((svc) => svc.fromDirectory(tmp.path))

const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
expect(row).toBeDefined()
Expand Down
Loading
Loading