openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
export * as ProjectV2 from "./project"export * as Project from "./project"import { Context, Effect, Layer, Schema } from "effect"import path from "path"import { AbsolutePath } from "./schema"import { FSUtil } from "./fs-util"import { Git } from "./git"import { makeGlobalNode } from "./effect/app-node"import { Hash } from "./util/hash"import { ProjectDirectories } from "./project/directories"import { ProjectSchema } from "./project/schema"export const ID = ProjectSchema.IDexport type ID = ProjectSchema.IDexport const Vcs = ProjectSchema.Vcsexport type Vcs = ProjectSchema.Vcsexport class Info extends Schema.Class<Info>("Project.Info")({id: ID,}) {}export const DirectoriesInput = ProjectDirectories.ListInputexport type DirectoriesInput = typeof DirectoriesInput.Typeexport const Directories = ProjectDirectories.ListOutputexport type Directories = typeof Directories.Typeexport interface Resolved {readonly previous?: IDreadonly id: IDreadonly directory: AbsolutePathreadonly vcs?: Vcs}export interface Interface {readonly directories: (input: DirectoriesInput) => Effect.Effect<Directories>readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>/*** Temporary bridge method for writing the resolved project ID to the repo-local cache.** This exists while the old opencode project service and this core project* service work together: core resolves the ID, while the old service still owns* database migration and persistence. The old service should call this after it* finishes migrating from `resolve().previous` to `resolve().id`; once project* persistence moves into core, this separate bridge method can go away.*/readonly commit: (input: { store: AbsolutePath; id: ID }) => Effect.Effect<void>}export class Service extends Context.Service<Service, Interface>()("@opencode/ProjectV2") {}const layer = Layer.effect(Service,Effect.gen(function* () {const fs = yield* FSUtil.Serviceconst git = yield* Git.Serviceconst projectDirectories = yield* ProjectDirectories.Serviceconst directories = Effect.fn("Project.directories")(function* (input: DirectoriesInput) {return yield* projectDirectories.list(input.projectID)})const cached = Effect.fnUntraced(function* (dir: string) {return yield* fs.readFileString(path.join(dir, "opencode")).pipe(Effect.map((value) => value.trim()),Effect.map((value) => (value ? ID.make(value) : undefined)),Effect.catch(() => Effect.succeed(undefined)),)})const remote = Effect.fnUntraced(function* (repo: Git.Repository) {const origin = yield* git.remote.get(repo)if (!origin) return undefinedconst normalized = url(origin)if (!normalized) return undefinedreturn ID.make(Hash.fast(`git-remote:${normalized}`))})function url(input: string) {const value = input.trim()if (!value) return undefinedtry {const parsed = new URL(value)if (parsed.protocol === "file:") return undefinedreturn parts(parsed.hostname, parsed.pathname)} catch {const scp = value.match(/^([^@/:]+@)?([^/:]+):(.+)$/)if (scp) return parts(scp[2], scp[3])return undefined}}function parts(host: string, name: string) {const pathname = name.replace(/^\/+/, "").replace(/\.git\/?$/, "").replace(/\/+$/, "")if (!host || !pathname) return undefinedreturn `${host.toLowerCase()}/${pathname}`}const root = Effect.fnUntraced(function* (repo: Git.Repository) {const root = (yield* git.history.rootCommits(repo))[0]return root ? ID.make(root) : undefined})const resolve = Effect.fn("Project.resolve")(function* (input: AbsolutePath) {const repo = yield* git.repo.discover(input)if (!repo) return { id: ID.global, directory: AbsolutePath.make(path.parse(input).root), vcs: undefined }const previous = yield* cached(repo.commonDirectory)const id = (yield* remote(repo)) ?? previous ?? (yield* root(repo))return {previous,id: id ?? ID.global,directory: repo.worktree,vcs: { type: "git" as const, store: repo.commonDirectory },}})const commit = Effect.fn("Project.commit")(function* (input: { store: AbsolutePath; id: ID }) {yield* fs.writeFileString(path.join(input.store, "opencode"), input.id).pipe(Effect.ignore)})return Service.of({ directories, resolve, commit })}),)export const node = makeGlobalNode({service: Service,layer: layer,deps: [FSUtil.node, Git.node, ProjectDirectories.node],})