openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
import { describe, expect } from "bun:test"import { $ } from "bun"import fs from "fs/promises"import path from "path"import { eq } from "drizzle-orm"import { Effect } from "effect"import { MoveSession } from "@opencode-ai/core/control-plane/move-session"import { Database } from "@opencode-ai/core/database/database"import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"import { LayerNode } from "@opencode-ai/core/effect/layer-node"import { EventV2 } from "@opencode-ai/core/event"import { Project } from "@opencode-ai/core/project"import { ProjectTable } from "@opencode-ai/core/project/sql"import { ProjectDirectories } from "@opencode-ai/core/project/directories"import { AbsolutePath } from "@opencode-ai/core/schema"import { SessionV2 } from "@opencode-ai/core/session"import { SessionProjector } from "@opencode-ai/core/session/projector"import { SessionTable } from "@opencode-ai/core/session/sql"import { SessionStore } from "@opencode-ai/core/session/store"import { tmpdir } from "./fixture/tmpdir"import { testEffect } from "./lib/effect"const it = testEffect(AppNodeBuilder.build(LayerNode.group([MoveSession.node,Database.node,EventV2.node,ProjectDirectories.node,Project.node,SessionProjector.node,SessionStore.node,]),),)function abs(input: string) {return AbsolutePath.make(input)}async function initRepo(directory: string) {await $`git init`.cwd(directory).quiet()await $`git config core.autocrlf false`.cwd(directory).quiet()await $`git config core.fsmonitor false`.cwd(directory).quiet()await $`git config commit.gpgsign false`.cwd(directory).quiet()await $`git config user.email test@opencode.test`.cwd(directory).quiet()await $`git config user.name Test`.cwd(directory).quiet()await fs.writeFile(path.join(directory, "tracked.txt"), "initial\n")await $`git add tracked.txt`.cwd(directory).quiet()await $`git commit -m root`.cwd(directory).quiet()}describe("MoveSession", () => {it.live("moves session changes to another project directory", () =>Effect.gen(function* () {const root = yield* Effect.acquireRelease(Effect.promise(() => tmpdir()),(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),)yield* Effect.promise(() => initRepo(root.path))const source = abs(yield* Effect.promise(() => fs.realpath(root.path)))const destination = abs(`${root.path}-move-destination`)yield* Effect.addFinalizer(() =>Effect.promise(() => fs.rm(destination, { recursive: true, force: true })).pipe(Effect.ignore),)yield* Effect.promise(() => $`git worktree add --detach ${destination} HEAD`.cwd(root.path).quiet())const moved = abs(yield* Effect.promise(() => fs.realpath(destination)))yield* Effect.promise(() => fs.writeFile(path.join(source, "tracked.txt"), "changed\n"))yield* Effect.promise(() => fs.writeFile(path.join(source, "untracked.txt"), "new\n"))const projectID = (yield* Project.Service.use((service) => service.resolve(source))).idconst sessionID = SessionV2.ID.make("ses_move")const { db } = yield* Database.Serviceyield* db.insert(ProjectTable).values({ id: projectID, worktree: source, sandboxes: [], time_created: 1, time_updated: 1 }).run().pipe(Effect.orDie)yield* db.insert(SessionTable).values({id: sessionID,project_id: projectID,slug: "move",directory: source,title: "move",version: "test",time_created: 1,time_updated: 1,}).run().pipe(Effect.orDie)yield* MoveSession.Service.use((service) =>service.moveSession({ sessionID, destination: { directory: moved }, moveChanges: true }),)expect(yield* Effect.promise(() => fs.readFile(path.join(moved, "tracked.txt"), "utf8"))).toBe("changed\n")expect(yield* Effect.promise(() => fs.readFile(path.join(moved, "untracked.txt"), "utf8"))).toBe("new\n")expect(yield* Effect.promise(() => fs.readFile(path.join(source, "tracked.txt"), "utf8"))).toBe("initial\n")expect(yield* Effect.promise(() => Bun.file(path.join(source, "untracked.txt")).exists())).toBe(false)expect(yield* db.select({ directory: SessionTable.directory, path: SessionTable.path }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get(),).toEqual({ directory: moved, path: "" })}),)it.live("moves within a checkout without transferring existing changes", () =>Effect.gen(function* () {const root = yield* Effect.acquireRelease(Effect.promise(() => tmpdir()),(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),)yield* Effect.promise(() => initRepo(root.path))const source = abs(yield* Effect.promise(() => fs.realpath(root.path)))const destination = abs(path.join(source, "packages"))yield* Effect.promise(() => fs.mkdir(destination))yield* Effect.promise(() => fs.writeFile(path.join(source, "tracked.txt"), "changed\n"))yield* Effect.promise(() => fs.writeFile(path.join(source, "untracked.txt"), "new\n"))const projectID = (yield* Project.Service.use((service) => service.resolve(source))).idconst sessionID = SessionV2.ID.make("ses_move_nested")const { db } = yield* Database.Serviceyield* db.insert(ProjectTable).values({ id: projectID, worktree: source, sandboxes: [], time_created: 1, time_updated: 1 }).run().pipe(Effect.orDie)yield* db.insert(SessionTable).values({id: sessionID,project_id: projectID,slug: "move-nested",directory: source,title: "move nested",version: "test",time_created: 1,time_updated: 1,}).run().pipe(Effect.orDie)yield* MoveSession.Service.use((service) =>service.moveSession({ sessionID, destination: { directory: destination }, moveChanges: true }),)expect(yield* Effect.promise(() => fs.readFile(path.join(source, "tracked.txt"), "utf8"))).toBe("changed\n")expect(yield* Effect.promise(() => fs.readFile(path.join(source, "untracked.txt"), "utf8"))).toBe("new\n")expect(yield* db.select({ directory: SessionTable.directory, path: SessionTable.path }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get(),).toEqual({ directory: destination, path: "packages" })}),)it.live("moves nested session changes without cleaning unrelated files", () =>Effect.gen(function* () {const root = yield* Effect.acquireRelease(Effect.promise(() => tmpdir()),(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),)yield* Effect.promise(() => initRepo(root.path))const source = abs(yield* Effect.promise(() => fs.realpath(root.path)))const sourceDirectory = abs(path.join(source, "packages"))yield* Effect.promise(() => fs.mkdir(sourceDirectory))yield* Effect.promise(() => fs.writeFile(path.join(sourceDirectory, "tracked.txt"), "initial\n"))yield* Effect.promise(() => fs.writeFile(path.join(sourceDirectory, "staged.txt"), "initial\n"))yield* Effect.promise(() => $`git add packages/tracked.txt packages/staged.txt`.cwd(source).quiet())yield* Effect.promise(() => $`git commit -m packages`.cwd(source).quiet())const destination = abs(`${root.path}-move-nested-destination`)yield* Effect.addFinalizer(() =>Effect.promise(() => fs.rm(destination, { recursive: true, force: true })).pipe(Effect.ignore),)yield* Effect.promise(() => $`git worktree add --detach ${destination} HEAD`.cwd(source).quiet())const moved = abs(path.join(yield* Effect.promise(() => fs.realpath(destination)), "packages"))yield* Effect.promise(() => fs.writeFile(path.join(sourceDirectory, "tracked.txt"), "changed\n"))yield* Effect.promise(() => fs.writeFile(path.join(sourceDirectory, "staged.txt"), "staged\n"))yield* Effect.promise(() => $`git add packages/staged.txt`.cwd(source).quiet())yield* Effect.promise(() => fs.writeFile(path.join(sourceDirectory, "untracked.txt"), "new\n"))yield* Effect.promise(() => fs.writeFile(path.join(source, "tracked.txt"), "unrelated\n"))yield* Effect.promise(() => fs.writeFile(path.join(source, "untracked.txt"), "unrelated\n"))const projectID = (yield* Project.Service.use((service) => service.resolve(source))).idconst sessionID = SessionV2.ID.make("ses_move_nested_checkout")const { db } = yield* Database.Serviceyield* db.insert(ProjectTable).values({ id: projectID, worktree: source, sandboxes: [], time_created: 1, time_updated: 1 }).run().pipe(Effect.orDie)yield* db.insert(SessionTable).values({id: sessionID,project_id: projectID,slug: "move-nested-checkout",directory: sourceDirectory,title: "move nested checkout",version: "test",time_created: 1,time_updated: 1,}).run().pipe(Effect.orDie)yield* MoveSession.Service.use((service) =>service.moveSession({ sessionID, destination: { directory: moved }, moveChanges: true }),)expect(yield* Effect.promise(() => fs.readFile(path.join(moved, "tracked.txt"), "utf8"))).toBe("changed\n")expect(yield* Effect.promise(() => fs.readFile(path.join(moved, "staged.txt"), "utf8"))).toBe("staged\n")expect(yield* Effect.promise(() => fs.readFile(path.join(moved, "untracked.txt"), "utf8"))).toBe("new\n")expect(yield* Effect.promise(() => fs.readFile(path.join(sourceDirectory, "tracked.txt"), "utf8"))).toBe("initial\n",)expect(yield* Effect.promise(() => Bun.file(path.join(sourceDirectory, "untracked.txt")).exists())).toBe(false)expect(yield* Effect.promise(() => fs.readFile(path.join(sourceDirectory, "staged.txt"), "utf8"))).toBe("staged\n",)expect(yield* Effect.promise(() => $`git status --porcelain -- packages/staged.txt`.cwd(source).text())).toBe("M packages/staged.txt\n",)expect(yield* Effect.promise(() => fs.readFile(path.join(source, "tracked.txt"), "utf8"))).toBe("unrelated\n")expect(yield* Effect.promise(() => fs.readFile(path.join(source, "untracked.txt"), "utf8"))).toBe("unrelated\n")}),)})