diff --git a/bun.lock b/bun.lock index 767cb6da20..8d1144fa25 100644 --- a/bun.lock +++ b/bun.lock @@ -612,7 +612,7 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/platform-node": "4.0.0-beta.42", + "@effect/platform-node": "4.0.0-beta.43", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@octokit/rest": "22.0.0", @@ -636,7 +636,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.42", + "effect": "4.0.0-beta.43", "fuzzysort": "3.1.0", "hono": "4.10.7", "hono-openapi": "1.1.2", @@ -995,9 +995,9 @@ "@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.42", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.42", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.42", "ioredis": "^5.7.0" } }, "sha512-kbdRML2FBa4q8U8rZQcnmLKZ5zN/z1bAA7t5D1/UsBHZqJgnfRgu1CP6kaEfb1Nie6YyaWshxTktZQryjvW/Yg=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.43", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.43", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.43", "ioredis": "^5.7.0" } }, "sha512-Uq6E1rjaIpjHauzjwoB2HzAg3battYt2Boy8XO50GoHiWCXKE6WapYZ0/AnaBx5v5qg2sOfqpuiLsUf9ZgxOkA=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.42", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.42" } }, "sha512-PC+lxLsrwob3+nBChAPrQq32olCeyApgXBvs1NrRsoArLViNT76T/68CttuCAksCZj5e1bZ1ZibLPel3vUmx2g=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.43", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.43" } }, "sha512-A9q0GEb61pYcQ06Dr6gXj1nKlDI3KHsar1sk3qb1ZY+kVSR64tBAylI8zGon23KY+NPtTUj/sEIToB7jc3Qt5w=="], "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], @@ -2771,7 +2771,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.42", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-c1UrRP+tLzyHb4Fepl8XBDJlLQLkrcMXrRBba441GQRxMbeQ/aIOSFcBwSda1iMJ5l9F0lYc3Bhe33/whrmavQ=="], + "effect": ["effect@4.0.0-beta.43", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-AJYyDimIwJOn87uUz/JzmgDc5GfjxJbXvEbTvNzMa+M3Uer344bLo/O5mMRkqc1vBleA+Ygs4+dbE3QsqOkKTQ=="], "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], diff --git a/package.json b/package.json index 2bb1a95391..cc2d3f4c21 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "packages/slack" ], "catalog": { - "@effect/platform-node": "4.0.0-beta.42", + "@effect/platform-node": "4.0.0-beta.43", "@types/bun": "1.3.11", "@octokit/rest": "22.0.0", "@hono/zod-validator": "0.4.2", @@ -45,7 +45,7 @@ "dompurify": "3.3.1", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.42", + "effect": "4.0.0-beta.43", "ai": "6.0.138", "hono": "4.10.7", "hono-openapi": "1.1.2", diff --git a/packages/opencode/src/effect/instance-state.ts b/packages/opencode/src/effect/instance-state.ts index b073cf0a4b..cc5901fb5e 100644 --- a/packages/opencode/src/effect/instance-state.ts +++ b/packages/opencode/src/effect/instance-state.ts @@ -24,9 +24,9 @@ export namespace InstanceState { return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F } - export const context = Effect.fnUntraced(function* () { + export const context = Effect.gen(function* () { return (yield* InstanceRef) ?? Instance.current - })() + }) export const directory = Effect.map(context, (ctx) => ctx.directory) @@ -37,9 +37,9 @@ export namespace InstanceState { const cache = yield* ScopedCache.make({ capacity: Number.POSITIVE_INFINITY, lookup: () => - Effect.fnUntraced(function* () { + Effect.gen(function* () { return yield* init(yield* context) - })(), + }), }) const off = registerDisposer((directory) => Effect.runPromise(ScopedCache.invalidate(cache, directory))) diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 08b2faf6b1..353f02c312 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -5,7 +5,6 @@ import { AppFileSystem } from "@/filesystem" import { git } from "@/util/git" import { Effect, Layer, ServiceMap } from "effect" import { formatPatch, structuredPatch } from "diff" -import fs from "fs" import fuzzysort from "fuzzysort" import ignore from "ignore" import path from "path" @@ -359,49 +358,46 @@ export namespace File { const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global" const next: Entry = { files: [], dirs: [] } - yield* Effect.promise(async () => { - if (isGlobalHome) { - const dirs = new Set() - const protectedNames = Protected.names() - const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"]) - const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name) - const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name) - const top = await fs.promises - .readdir(Instance.directory, { withFileTypes: true }) - .catch(() => [] as fs.Dirent[]) + if (isGlobalHome) { + const dirs = new Set() + const protectedNames = Protected.names() + const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"]) + const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name) + const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name) + const top = yield* appFs.readDirectoryEntries(Instance.directory).pipe(Effect.orElseSucceed(() => [])) - for (const entry of top) { - if (!entry.isDirectory()) continue - if (shouldIgnoreName(entry.name)) continue - dirs.add(entry.name + "/") + for (const entry of top) { + if (entry.type !== "directory") continue + if (shouldIgnoreName(entry.name)) continue + dirs.add(entry.name + "/") - const base = path.join(Instance.directory, entry.name) - const children = await fs.promises.readdir(base, { withFileTypes: true }).catch(() => [] as fs.Dirent[]) - for (const child of children) { - if (!child.isDirectory()) continue - if (shouldIgnoreNested(child.name)) continue - dirs.add(entry.name + "/" + child.name + "/") - } - } - - next.dirs = Array.from(dirs).toSorted() - } else { - const seen = new Set() - for await (const file of Ripgrep.files({ cwd: Instance.directory })) { - next.files.push(file) - let current = file - while (true) { - const dir = path.dirname(current) - if (dir === ".") break - if (dir === current) break - current = dir - if (seen.has(dir)) continue - seen.add(dir) - next.dirs.push(dir + "/") - } + const base = path.join(Instance.directory, entry.name) + const children = yield* appFs.readDirectoryEntries(base).pipe(Effect.orElseSucceed(() => [])) + for (const child of children) { + if (child.type !== "directory") continue + if (shouldIgnoreNested(child.name)) continue + dirs.add(entry.name + "/" + child.name + "/") } } - }) + + next.dirs = Array.from(dirs).toSorted() + } else { + const files = yield* Effect.promise(() => Array.fromAsync(Ripgrep.files({ cwd: Instance.directory }))) + const seen = new Set() + for (const file of files) { + next.files.push(file) + let current = file + while (true) { + const dir = path.dirname(current) + if (dir === ".") break + if (dir === current) break + current = dir + if (seen.has(dir)) continue + seen.add(dir) + next.dirs.push(dir + "/") + } + } + } const s = yield* InstanceState.get(state) s.cache = next @@ -636,30 +632,27 @@ export namespace File { yield* ensure() const { cache } = yield* InstanceState.get(state) - return yield* Effect.promise(async () => { - const query = input.query.trim() - const limit = input.limit ?? 100 - const kind = input.type ?? (input.dirs === false ? "file" : "all") - log.info("search", { query, kind }) + const query = input.query.trim() + const limit = input.limit ?? 100 + const kind = input.type ?? (input.dirs === false ? "file" : "all") + log.info("search", { query, kind }) - const result = cache - const preferHidden = query.startsWith(".") || query.includes("/.") + const preferHidden = query.startsWith(".") || query.includes("/.") - if (!query) { - if (kind === "file") return result.files.slice(0, limit) - return sortHiddenLast(result.dirs.toSorted(), preferHidden).slice(0, limit) - } + if (!query) { + if (kind === "file") return cache.files.slice(0, limit) + return sortHiddenLast(cache.dirs.toSorted(), preferHidden).slice(0, limit) + } - const items = - kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs] + const items = + kind === "file" ? cache.files : kind === "directory" ? cache.dirs : [...cache.files, ...cache.dirs] - const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit - const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target) - const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted + const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit + const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target) + const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted - log.info("search", { query, kind, results: output.length }) - return output - }) + log.info("search", { query, kind, results: output.length }) + return output }) log.info("init") diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index fbfab6c3b9..38ef4b11f4 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -111,26 +111,25 @@ export namespace ProviderAuth { export class Service extends ServiceMap.Service()("@opencode/ProviderAuth") {} - export const layer = Layer.effect( + export const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { const auth = yield* Auth.Service + const plugin = yield* Plugin.Service const state = yield* InstanceState.make( - Effect.fn("ProviderAuth.state")(() => - Effect.promise(async () => { - const plugins = await Plugin.list() - return { - hooks: Record.fromEntries( - Arr.filterMap(plugins, (x) => - x.auth?.provider !== undefined - ? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const) - : Result.failVoid, - ), + Effect.fn("ProviderAuth.state")(function* () { + const plugins = yield* plugin.list() + return { + hooks: Record.fromEntries( + Arr.filterMap(plugins, (x) => + x.auth?.provider !== undefined + ? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const) + : Result.failVoid, ), - pending: new Map(), - } - }), - ), + ), + pending: new Map(), + } + }), ) const methods = Effect.fn("ProviderAuth.methods")(function* () { @@ -230,7 +229,9 @@ export namespace ProviderAuth { }), ) - export const defaultLayer = layer.pipe(Layer.provide(Auth.defaultLayer)) + export const defaultLayer = Layer.suspend(() => + layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)), + ) const { runPromise } = makeRuntime(Service, defaultLayer) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 40ab69e0f3..861b34a7ad 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -961,11 +961,12 @@ export namespace Provider { } } - const layer: Layer.Layer = Layer.effect( + const layer: Layer.Layer = Layer.effect( Service, Effect.gen(function* () { const config = yield* Config.Service const auth = yield* Auth.Service + const plugin = yield* Plugin.Service const state = yield* InstanceState.make(() => Effect.gen(function* () { @@ -1128,7 +1129,7 @@ export namespace Provider { } } - const plugins = yield* Effect.promise(() => Plugin.list()) + const plugins = yield* plugin.list() for (const plugin of plugins) { if (!plugin.auth) continue const providerID = ProviderID.make(plugin.auth.provider) @@ -1541,7 +1542,9 @@ export namespace Provider { }), ) - export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer)) + export const defaultLayer = Layer.suspend(() => + layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)), + ) const { runPromise } = makeRuntime(Service, defaultLayer)