Compare commits

...

1 Commits

Author SHA1 Message Date
Aiden Cline f81d771e47 fix(core): treat project paths as internal 2026-08-15 12:00:30 +00:00
20 changed files with 137 additions and 93 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ import { ConfigAgentV1 } from "../../v1/config/agent.js"
import { ConfigMigrateV1 } from "../../v1/config/migrate.js" import { ConfigMigrateV1 } from "../../v1/config/migrate.js"
import { Global } from "@opencode-ai/util/global" import { Global } from "@opencode-ai/util/global"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import type { LocationMutation } from "../../location-mutation.js" import type { LocationPath } from "../../location-path.js"
import type { ReadTool } from "../../tool/plugin/read.js" import type { ReadTool } from "../../tool/plugin/read.js"
import type { EditTool } from "../../tool/plugin/edit.js" import type { EditTool } from "../../tool/plugin/edit.js"
import { AbsolutePath } from "../../schema.js" import { AbsolutePath } from "../../schema.js"
@@ -28,7 +28,7 @@ const decodeAgent = Schema.decodeUnknownOption(ConfigAgent.Info)
const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info) const decodeLegacyAgent = Schema.decodeUnknownOption(ConfigAgentV1.Info)
const decodeConfig = Schema.decodeUnknownOption(Info) const decodeConfig = Schema.decodeUnknownOption(Info)
type PathAction = type PathAction =
| LocationMutation.ExternalDirectoryAuthorization["action"] | LocationPath.ExternalDirectoryAuthorization["action"]
| typeof ReadTool.name | typeof ReadTool.name
| typeof EditTool.name | typeof EditTool.name
const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[] const pathActions = ["external_directory", "read", "edit"] as const satisfies readonly PathAction[]
@@ -1,4 +1,4 @@
export * as LocationMutation from "./location-mutation.js" export * as LocationPath from "./location-path.js"
import { makeLocationNode } from "@opencode-ai/util/effect/app-node" import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
import path from "path" import path from "path"
@@ -12,8 +12,8 @@ export const Kind = Schema.Literals(["file", "directory"])
export type Kind = typeof Kind.Type export type Kind = typeof Kind.Type
/** /**
* Mutation paths do not accept project references. Relative paths resolve * Tool paths do not accept project references. Relative paths resolve from
* from the active Location. Paths outside it require separate * the active Location. Paths outside its project require separate
* `external_directory` approval. * `external_directory` approval.
*/ */
export const ResolveInput = Schema.Struct({ export const ResolveInput = Schema.Struct({
@@ -49,13 +49,13 @@ export interface Target {
export interface Interface { export interface Interface {
/** /**
* Resolve a path and derive its permission resources. Relative paths resolve * Resolve a path and derive its permission resources. Relative paths resolve
* from the Location. Paths outside it require separate `external_directory` * from the Location. Paths outside its project require separate
* approval. This does not approve the mutation. * `external_directory` approval. This does not approve access.
*/ */
readonly resolve: (input: ResolveInput) => Effect.Effect<Target, FSUtil.Error> readonly resolve: (input: ResolveInput) => Effect.Effect<Target, FSUtil.Error>
} }
export class Service extends Context.Service<Service, Interface>()("@opencode/LocationMutation") {} export class Service extends Context.Service<Service, Interface>()("@opencode/LocationPath") {}
const slash = (value: string) => value.replaceAll("\\", "/") const slash = (value: string) => value.replaceAll("\\", "/")
@@ -65,9 +65,13 @@ const layer = Layer.effect(
const fs = yield* FSUtil.Service const fs = yield* FSUtil.Service
const location = yield* Location.Service const location = yield* Location.Service
const resolve = Effect.fn("LocationMutation.resolve")(function* (input: ResolveInput) { const resolve = Effect.fn("LocationPath.resolve")(function* (input: ResolveInput) {
const absolute = path.resolve(location.directory, input.path) const absolute = path.resolve(location.directory, input.path)
if (FSUtil.contains(location.directory, absolute)) { const projectRoot = path.parse(location.project.directory).root
if (
FSUtil.contains(location.directory, absolute) ||
(location.project.directory !== projectRoot && FSUtil.contains(location.project.directory, absolute))
) {
return { return {
absolute, absolute,
resource: slash(path.relative(location.directory, absolute) || "."), resource: slash(path.relative(location.directory, absolute) || "."),
+2 -2
View File
@@ -19,7 +19,7 @@ import { Image } from "./image.js"
import { LocationWatcher } from "./filesystem/location-watcher.js" import { LocationWatcher } from "./filesystem/location-watcher.js"
import { Integration } from "./integration.js" import { Integration } from "./integration.js"
import { Location } from "./location.js" import { Location } from "./location.js"
import { LocationMutation } from "./location-mutation.js" import { LocationPath } from "./location-path.js"
import { LocationServiceMap } from "./location-service-map.js" import { LocationServiceMap } from "./location-service-map.js"
import { ModelResolver } from "./model-resolver.js" import { ModelResolver } from "./model-resolver.js"
import { MCP } from "./mcp/index.js" import { MCP } from "./mcp/index.js"
@@ -76,7 +76,7 @@ const locationServiceNodes = [
Skill.node, Skill.node,
InstructionBuiltIns.node, InstructionBuiltIns.node,
InstructionDiscovery.node, InstructionDiscovery.node,
LocationMutation.node, LocationPath.node,
FileMutation.node, FileMutation.node,
Formatter.node, Formatter.node,
MCP.node, MCP.node,
+4 -4
View File
@@ -32,7 +32,7 @@ import { InstructionDiscovery } from "../instruction-discovery.js"
import { Integration } from "../integration.js" import { Integration } from "../integration.js"
import { KV } from "../kv.js" import { KV } from "../kv.js"
import { Location } from "../location.js" import { Location } from "../location.js"
import { LocationMutation } from "../location-mutation.js" import { LocationPath } from "../location-path.js"
import { ModelsDev } from "../models-dev.js" import { ModelsDev } from "../models-dev.js"
import { Npm } from "@opencode-ai/util/npm" import { Npm } from "@opencode-ai/util/npm"
import { Permission } from "../permission.js" import { Permission } from "../permission.js"
@@ -92,7 +92,7 @@ const services = Effect.fn("PluginInternal.services")(function* () {
const integration = yield* Integration.Service const integration = yield* Integration.Service
const kv = yield* KV.Service const kv = yield* KV.Service
const location = yield* Location.Service const location = yield* Location.Service
const locationMutation = yield* LocationMutation.Service const locationMutation = yield* LocationPath.Service
const models = yield* ModelsDev.Service const models = yield* ModelsDev.Service
const npm = yield* Npm.Service const npm = yield* Npm.Service
const permission = yield* Permission.Service const permission = yield* Permission.Service
@@ -129,7 +129,7 @@ const services = Effect.fn("PluginInternal.services")(function* () {
Context.make(Integration.Service, integration), Context.make(Integration.Service, integration),
Context.make(KV.Service, kv), Context.make(KV.Service, kv),
Context.make(Location.Service, location), Context.make(Location.Service, location),
Context.make(LocationMutation.Service, locationMutation), Context.make(LocationPath.Service, locationMutation),
Context.make(ModelsDev.Service, models), Context.make(ModelsDev.Service, models),
Context.make(Npm.Service, npm), Context.make(Npm.Service, npm),
Context.make(Permission.Service, permission), Context.make(Permission.Service, permission),
@@ -173,7 +173,7 @@ export const requirements = LayerNode.group([
Integration.node, Integration.node,
KV.node, KV.node,
Location.node, Location.node,
LocationMutation.node, LocationPath.node,
ModelsDev.node, ModelsDev.node,
Npm.node, Npm.node,
Permission.node, Permission.node,
+3 -3
View File
@@ -16,7 +16,7 @@ import { Environment } from "../../environment/index.js"
import { FileMutation } from "../../file-mutation.js" import { FileMutation } from "../../file-mutation.js"
import { Formatter } from "../../formatter.js" import { Formatter } from "../../formatter.js"
import { Location } from "../../location.js" import { Location } from "../../location.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import { fileDiff } from "./file-diff.js" import { fileDiff } from "./file-diff.js"
@@ -110,7 +110,7 @@ const findLineOccurrences = (content: string, search: string) => {
export const Plugin = { export const Plugin = {
id: "opencode.tool.edit", id: "opencode.tool.edit",
effect: Effect.fn("EditTool.Plugin")(function* (ctx: PluginContext) { effect: Effect.fn("EditTool.Plugin")(function* (ctx: PluginContext) {
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const fileMutation = yield* FileMutation.Service const fileMutation = yield* FileMutation.Service
const environment = yield* Environment.Service const environment = yield* Environment.Service
const formatter = yield* Formatter.Service const formatter = yield* Formatter.Service
@@ -148,7 +148,7 @@ export const Plugin = {
const external = target.externalDirectory const external = target.externalDirectory
if (external) { if (external) {
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(external), ...LocationPath.externalDirectoryPermission(external),
sessionID: context.sessionID, sessionID: context.sessionID,
agent: context.agent, agent: context.agent,
source: permissionSource, source: permissionSource,
+3 -3
View File
@@ -7,7 +7,7 @@ import path from "path"
import { Environment } from "../../environment/index.js" import { Environment } from "../../environment/index.js"
import { FileSystem } from "../../filesystem.js" import { FileSystem } from "../../filesystem.js"
import { Location } from "../../location.js" import { Location } from "../../location.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Ripgrep } from "../../ripgrep.js" import { Ripgrep } from "../../ripgrep.js"
import { RelativePath } from "../../schema.js" import { RelativePath } from "../../schema.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
@@ -45,7 +45,7 @@ export const Plugin = {
const environment = yield* Environment.Service const environment = yield* Environment.Service
const ripgrep = yield* Ripgrep.Service const ripgrep = yield* Ripgrep.Service
const location = yield* Location.Service const location = yield* Location.Service
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const permission = yield* Permission.Service const permission = yield* Permission.Service
yield* ctx.tool yield* ctx.tool
@@ -64,7 +64,7 @@ export const Plugin = {
const external = target.externalDirectory const external = target.externalDirectory
if (external) if (external)
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(external), ...LocationPath.externalDirectoryPermission(external),
sessionID: context.sessionID, sessionID: context.sessionID,
agent: context.agent, agent: context.agent,
source, source,
+3 -3
View File
@@ -7,7 +7,7 @@ import path from "path"
import { Environment } from "../../environment/index.js" import { Environment } from "../../environment/index.js"
import { FileSystem } from "../../filesystem.js" import { FileSystem } from "../../filesystem.js"
import { Location } from "../../location.js" import { Location } from "../../location.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import { Ripgrep } from "../../ripgrep.js" import { Ripgrep } from "../../ripgrep.js"
import { RelativePath } from "../../schema.js" import { RelativePath } from "../../schema.js"
@@ -61,7 +61,7 @@ export const Plugin = {
const environment = yield* Environment.Service const environment = yield* Environment.Service
const ripgrep = yield* Ripgrep.Service const ripgrep = yield* Ripgrep.Service
const location = yield* Location.Service const location = yield* Location.Service
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const permission = yield* Permission.Service const permission = yield* Permission.Service
yield* ctx.tool yield* ctx.tool
@@ -79,7 +79,7 @@ export const Plugin = {
const target = yield* mutation.resolve({ path: input.path ?? "." }) const target = yield* mutation.resolve({ path: input.path ?? "." })
if (target.externalDirectory) if (target.externalDirectory)
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(target.externalDirectory), ...LocationPath.externalDirectoryPermission(target.externalDirectory),
sessionID: context.sessionID, sessionID: context.sessionID,
agent: context.agent, agent: context.agent,
source, source,
+7 -7
View File
@@ -10,7 +10,7 @@ import { Environment } from "../../environment/index.js"
import { Formatter } from "../../formatter.js" import { Formatter } from "../../formatter.js"
import { FileMutation } from "../../file-mutation.js" import { FileMutation } from "../../file-mutation.js"
import { Location } from "../../location.js" import { Location } from "../../location.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Patch } from "@opencode-ai/util/patch" import { Patch } from "@opencode-ai/util/patch"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import DESCRIPTION from "../patch.txt" import DESCRIPTION from "../patch.txt"
@@ -46,29 +46,29 @@ export const toModelOutput = (output: Output) =>
type Prepared = type Prepared =
| (Extract<Patch.Hunk, { readonly type: "add" }> & { | (Extract<Patch.Hunk, { readonly type: "add" }> & {
readonly target: LocationMutation.Target readonly target: LocationPath.Target
readonly content: string readonly content: string
readonly before: string readonly before: string
readonly after: string readonly after: string
}) })
| (Extract<Patch.Hunk, { readonly type: "delete" }> & { | (Extract<Patch.Hunk, { readonly type: "delete" }> & {
readonly target: LocationMutation.Target readonly target: LocationPath.Target
readonly before: string readonly before: string
readonly after: string readonly after: string
}) })
| (Extract<Patch.Hunk, { readonly type: "update" }> & { | (Extract<Patch.Hunk, { readonly type: "update" }> & {
readonly target: LocationMutation.Target readonly target: LocationPath.Target
readonly content: string readonly content: string
readonly before: string readonly before: string
readonly after: string readonly after: string
readonly moveTarget?: LocationMutation.Target readonly moveTarget?: LocationPath.Target
}) })
export const Plugin = { export const Plugin = {
id: "opencode.tool.patch", id: "opencode.tool.patch",
effect: Effect.fn("PatchTool.Plugin")(function* (ctx: PluginContext) { effect: Effect.fn("PatchTool.Plugin")(function* (ctx: PluginContext) {
const environment = yield* Environment.Service const environment = yield* Environment.Service
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const fileMutation = yield* FileMutation.Service const fileMutation = yield* FileMutation.Service
const formatter = yield* Formatter.Service const formatter = yield* Formatter.Service
const location = yield* Location.Service const location = yield* Location.Service
@@ -116,7 +116,7 @@ export const Plugin = {
const target = yield* mutation.resolve({ path: value, kind: "file" }) const target = yield* mutation.resolve({ path: value, kind: "file" })
if (!target.externalDirectory) return target if (!target.externalDirectory) return target
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(target.externalDirectory), ...LocationPath.externalDirectoryPermission(target.externalDirectory),
metadata: { metadata: {
filepath: target.absolute, filepath: target.absolute,
parentDir: target.externalDirectory.directory, parentDir: target.externalDirectory.directory,
+3 -3
View File
@@ -6,7 +6,7 @@ import { ToolFailure } from "@opencode-ai/ai"
import { Effect, Schema } from "effect" import { Effect, Schema } from "effect"
import { FSUtil } from "@opencode-ai/util/fs-util" import { FSUtil } from "@opencode-ai/util/fs-util"
import { Location } from "../../location.js" import { Location } from "../../location.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import { SessionInstructions } from "../../session/instructions.js" import { SessionInstructions } from "../../session/instructions.js"
import { AbsolutePath } from "../../schema.js" import { AbsolutePath } from "../../schema.js"
@@ -32,7 +32,7 @@ export const Plugin = {
id: "opencode.tool.read", id: "opencode.tool.read",
effect: Effect.fn("ReadTool.Plugin")(function* (ctx: PluginContext) { effect: Effect.fn("ReadTool.Plugin")(function* (ctx: PluginContext) {
const reader = yield* ReadToolFileSystem.Service const reader = yield* ReadToolFileSystem.Service
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const permission = yield* Permission.Service const permission = yield* Permission.Service
const sessionInstructions = yield* SessionInstructions.Service const sessionInstructions = yield* SessionInstructions.Service
const fs = yield* FSUtil.Service const fs = yield* FSUtil.Service
@@ -58,7 +58,7 @@ export const Plugin = {
const external = target.externalDirectory const external = target.externalDirectory
if (external) if (external)
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(external), ...LocationPath.externalDirectoryPermission(external),
sessionID: context.sessionID, sessionID: context.sessionID,
agent: context.agent, agent: context.agent,
source, source,
+2 -2
View File
@@ -7,7 +7,7 @@ import type { Context as PluginContext } from "@opencode-ai/plugin/effect/plugin
import { Deferred, Effect, Schema, Scope } from "effect" import { Deferred, Effect, Schema, Scope } from "effect"
import { Config } from "../../config.js" import { Config } from "../../config.js"
import { Environment } from "../../environment/index.js" import { Environment } from "../../environment/index.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import { PluginRuntime } from "../../plugin/runtime.js" import { PluginRuntime } from "../../plugin/runtime.js"
import { NonNegativeInt } from "../../schema.js" import { NonNegativeInt } from "../../schema.js"
@@ -84,7 +84,7 @@ export const Plugin = {
const runtime = yield* PluginRuntime.Service const runtime = yield* PluginRuntime.Service
const scope = yield* Scope.Scope const scope = yield* Scope.Scope
const environment = yield* Environment.Service const environment = yield* Environment.Service
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const shell = yield* Shell.Service const shell = yield* Shell.Service
const permission = yield* Permission.Service const permission = yield* Permission.Service
const config = yield* Config.Service const config = yield* Config.Service
+3 -3
View File
@@ -13,7 +13,7 @@ import { Bom } from "@opencode-ai/util/bom"
import { Environment } from "../../environment/index.js" import { Environment } from "../../environment/index.js"
import { FileMutation } from "../../file-mutation.js" import { FileMutation } from "../../file-mutation.js"
import { Formatter } from "../../formatter.js" import { Formatter } from "../../formatter.js"
import { LocationMutation } from "../../location-mutation.js" import { LocationPath } from "../../location-path.js"
import { Permission } from "../../permission.js" import { Permission } from "../../permission.js"
import { fileDiff } from "./file-diff.js" import { fileDiff } from "./file-diff.js"
@@ -46,7 +46,7 @@ export const toModelOutput = (output: Output) =>
export const Plugin = { export const Plugin = {
id: "opencode.tool.write", id: "opencode.tool.write",
effect: Effect.fn("WriteTool.Plugin")(function* (ctx: PluginContext) { effect: Effect.fn("WriteTool.Plugin")(function* (ctx: PluginContext) {
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const fileMutation = yield* FileMutation.Service const fileMutation = yield* FileMutation.Service
const environment = yield* Environment.Service const environment = yield* Environment.Service
const formatter = yield* Formatter.Service const formatter = yield* Formatter.Service
@@ -72,7 +72,7 @@ export const Plugin = {
const external = target.externalDirectory const external = target.externalDirectory
if (external) if (external)
yield* permission.assert({ yield* permission.assert({
...LocationMutation.externalDirectoryPermission(external), ...LocationPath.externalDirectoryPermission(external),
sessionID: context.sessionID, sessionID: context.sessionID,
agent: context.agent, agent: context.agent,
source, source,
+9 -9
View File
@@ -7,7 +7,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { FileMutation } from "@opencode-ai/core/file-mutation" import { FileMutation } from "@opencode-ai/core/file-mutation"
import { Environment } from "@opencode-ai/core/environment/index" import { Environment } from "@opencode-ai/core/environment/index"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { type EnvironmentFilesTransform, transformEnvironmentFiles } from "./fixture/environment" import { type EnvironmentFilesTransform, transformEnvironmentFiles } from "./fixture/environment"
import { location } from "./fixture/location" import { location } from "./fixture/location"
@@ -20,7 +20,7 @@ function provide(directory: string, transformFiles: EnvironmentFilesTransform =
Location.Service.of(location({ directory: AbsolutePath.make(directory) })), Location.Service.of(location({ directory: AbsolutePath.make(directory) })),
) )
return Effect.provide( return Effect.provide(
AppNodeBuilder.build(LayerNode.group([LocationMutation.node, FileMutation.node]), [ AppNodeBuilder.build(LayerNode.group([LocationPath.node, FileMutation.node]), [
[Location.node, activeLocation], [Location.node, activeLocation],
[Environment.node, transformEnvironmentFiles(activeLocation, transformFiles)], [Environment.node, transformEnvironmentFiles(activeLocation, transformFiles)],
]), ]),
@@ -40,7 +40,7 @@ describe("FileMutation", () => {
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(directory, "hello.txt") const targetPath = path.join(directory, "hello.txt")
yield* Effect.promise(() => fs.writeFile(targetPath, "before")) yield* Effect.promise(() => fs.writeFile(targetPath, "before"))
const target = yield* (yield* LocationMutation.Service).resolve({ path: "hello.txt" }) const target = yield* (yield* LocationPath.Service).resolve({ path: "hello.txt" })
expect(yield* (yield* FileMutation.Service).write({ target, content: "after" })).toEqual({ expect(yield* (yield* FileMutation.Service).write({ target, content: "after" })).toEqual({
operation: "write", operation: "write",
@@ -56,7 +56,7 @@ describe("FileMutation", () => {
it.live("writes a prospective internal file and creates parent directories", () => it.live("writes a prospective internal file and creates parent directories", () =>
withTmp((directory) => withTmp((directory) =>
Effect.gen(function* () { Effect.gen(function* () {
const target = yield* (yield* LocationMutation.Service).resolve({ const target = yield* (yield* LocationPath.Service).resolve({
path: path.join("src", "nested", "hello.txt"), path: path.join("src", "nested", "hello.txt"),
}) })
const result = yield* (yield* FileMutation.Service).write({ target, content: "hello" }) const result = yield* (yield* FileMutation.Service).write({ target, content: "hello" })
@@ -77,8 +77,8 @@ describe("FileMutation", () => {
Effect.gen(function* () { Effect.gen(function* () {
const preservedPath = path.join(directory, "preserved.txt") const preservedPath = path.join(directory, "preserved.txt")
yield* Effect.promise(() => fs.writeFile(preservedPath, "\uFEFFbefore")) yield* Effect.promise(() => fs.writeFile(preservedPath, "\uFEFFbefore"))
const preserved = yield* (yield* LocationMutation.Service).resolve({ path: "preserved.txt" }) const preserved = yield* (yield* LocationPath.Service).resolve({ path: "preserved.txt" })
const created = yield* (yield* LocationMutation.Service).resolve({ path: "created.txt" }) const created = yield* (yield* LocationPath.Service).resolve({ path: "created.txt" })
const files = yield* FileMutation.Service const files = yield* FileMutation.Service
yield* files.writeTextPreservingBom({ target: preserved, content: "\uFEFFafter" }) yield* files.writeTextPreservingBom({ target: preserved, content: "\uFEFFafter" })
@@ -95,7 +95,7 @@ describe("FileMutation", () => {
withTmp((outside) => withTmp((outside) =>
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(outside, "external.txt") const targetPath = path.join(outside, "external.txt")
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) const target = yield* (yield* LocationPath.Service).resolve({ path: targetPath })
const result = yield* (yield* FileMutation.Service).write({ target, content: "external" }) const result = yield* (yield* FileMutation.Service).write({ target, content: "external" })
expect(result).toEqual({ expect(result).toEqual({
@@ -133,7 +133,7 @@ describe("FileMutation", () => {
) )
yield* Effect.gen(function* () { yield* Effect.gen(function* () {
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const files = yield* FileMutation.Service const files = yield* FileMutation.Service
const firstPlan = yield* mutation.resolve({ path: "shared.txt" }) const firstPlan = yield* mutation.resolve({ path: "shared.txt" })
const secondPlan = yield* mutation.resolve({ path: "shared.txt" }) const secondPlan = yield* mutation.resolve({ path: "shared.txt" })
@@ -222,7 +222,7 @@ describe("FileMutation", () => {
) )
yield* Effect.gen(function* () { yield* Effect.gen(function* () {
const mutation = yield* LocationMutation.Service const mutation = yield* LocationPath.Service
const files = yield* FileMutation.Service const files = yield* FileMutation.Service
const firstPlan = yield* mutation.resolve({ path: "first.txt" }) const firstPlan = yield* mutation.resolve({ path: "first.txt" })
const secondPlan = yield* mutation.resolve({ path: "second.txt" }) const secondPlan = yield* mutation.resolve({ path: "second.txt" })
@@ -4,18 +4,26 @@ import { describe, expect, test } from "bun:test"
import { Effect, Layer, Schema } from "effect" import { Effect, Layer, Schema } from "effect"
import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { tmpdir } from "./fixture/tmpdir" import { tmpdir } from "./fixture/tmpdir"
import { location } from "./fixture/location" import { location } from "./fixture/location"
import { it } from "./lib/effect" import { it } from "./lib/effect"
function provide(directory: string) { function provide(directory: string, projectDirectory = directory) {
return Effect.provide( return Effect.provide(
LayerNode.compile(LocationMutation.node, [ LayerNode.compile(LocationPath.node, [
[ [
Location.node, Location.node,
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))), Layer.succeed(
Location.Service,
Location.Service.of(
location(
{ directory: AbsolutePath.make(directory) },
{ projectDirectory: AbsolutePath.make(projectDirectory) },
),
),
),
], ],
]), ]),
) )
@@ -28,13 +36,13 @@ function withTmp<A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) {
).pipe(Effect.flatMap((tmp) => f(tmp.path))) ).pipe(Effect.flatMap((tmp) => f(tmp.path)))
} }
describe("LocationMutation", () => { describe("LocationPath", () => {
it.live("resolves an active relative existing file target", () => it.live("resolves an active relative existing file target", () =>
withTmp((directory) => withTmp((directory) =>
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(directory, "hello.txt") const targetPath = path.join(directory, "hello.txt")
yield* Effect.promise(() => fs.writeFile(targetPath, "hello")) yield* Effect.promise(() => fs.writeFile(targetPath, "hello"))
const target = yield* (yield* LocationMutation.Service).resolve({ path: "hello.txt" }) const target = yield* (yield* LocationPath.Service).resolve({ path: "hello.txt" })
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: targetPath, absolute: targetPath,
@@ -49,7 +57,7 @@ describe("LocationMutation", () => {
withTmp((directory) => withTmp((directory) =>
Effect.gen(function* () { Effect.gen(function* () {
yield* Effect.promise(() => fs.mkdir(path.join(directory, "src"))) yield* Effect.promise(() => fs.mkdir(path.join(directory, "src")))
const target = yield* (yield* LocationMutation.Service).resolve({ path: path.join("src", "new.txt") }) const target = yield* (yield* LocationPath.Service).resolve({ path: path.join("src", "new.txt") })
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: path.join(directory, "src", "new.txt"), absolute: path.join(directory, "src", "new.txt"),
resource: "src/new.txt", resource: "src/new.txt",
@@ -58,10 +66,43 @@ describe("LocationMutation", () => {
), ),
) )
it.live("does not require external authorization inside the project but outside the active directory", () =>
withTmp((project) =>
Effect.gen(function* () {
const directory = path.join(project, "packages", "app")
const targetPath = path.join(project, "README.md")
yield* Effect.promise(() => fs.mkdir(directory, { recursive: true }))
yield* Effect.promise(() => fs.writeFile(targetPath, "hello"))
const locationPath = yield* LocationPath.Service
const target = yield* locationPath.resolve({ path: targetPath })
expect(target).toMatchObject({
absolute: targetPath,
resource: "../../README.md",
})
expect(target.externalDirectory).toBeUndefined()
}).pipe(provide(path.join(project, "packages", "app"), project)),
),
)
it.live("does not treat a filesystem-root project fallback as internal", () =>
withTmp((directory) =>
withTmp((outside) =>
Effect.gen(function* () {
const locationPath = yield* LocationPath.Service
const target = yield* locationPath.resolve({ path: path.join(outside, "target.txt") })
expect(target.externalDirectory).toBeDefined()
}).pipe(provide(directory, path.parse(directory).root)),
),
),
)
it.live("requires external-directory authorization for a relative lexical escape", () => it.live("requires external-directory authorization for a relative lexical escape", () =>
withTmp((directory) => withTmp((directory) =>
Effect.gen(function* () { Effect.gen(function* () {
const target = yield* (yield* LocationMutation.Service).resolve({ path: "../outside.txt" }) const target = yield* (yield* LocationPath.Service).resolve({ path: "../outside.txt" })
const root = path.dirname(directory) const root = path.dirname(directory)
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: path.join(root, "outside.txt"), absolute: path.join(root, "outside.txt"),
@@ -84,7 +125,7 @@ describe("LocationMutation", () => {
await fs.mkdir(outside) await fs.mkdir(outside)
await fs.symlink(outside, path.join(directory, "escape")) await fs.symlink(outside, path.join(directory, "escape"))
}) })
const target = yield* (yield* LocationMutation.Service).resolve({ path: path.join("escape", "new.txt") }) const target = yield* (yield* LocationPath.Service).resolve({ path: path.join("escape", "new.txt") })
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: path.join(directory, "escape", "new.txt"), absolute: path.join(directory, "escape", "new.txt"),
resource: "escape/new.txt", resource: "escape/new.txt",
@@ -104,7 +145,7 @@ describe("LocationMutation", () => {
await fs.symlink(path.join(directory, "actual"), path.join(directory, "linked")) await fs.symlink(path.join(directory, "actual"), path.join(directory, "linked"))
}) })
expect(yield* (yield* LocationMutation.Service).resolve({ path: "linked/new.txt" })).toMatchObject({ expect(yield* (yield* LocationPath.Service).resolve({ path: "linked/new.txt" })).toMatchObject({
absolute: path.join(directory, "linked", "new.txt"), absolute: path.join(directory, "linked", "new.txt"),
resource: "linked/new.txt", resource: "linked/new.txt",
}) })
@@ -116,7 +157,7 @@ describe("LocationMutation", () => {
withTmp((directory) => withTmp((directory) =>
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(directory, "new.txt") const targetPath = path.join(directory, "new.txt")
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) const target = yield* (yield* LocationPath.Service).resolve({ path: targetPath })
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: targetPath, absolute: targetPath,
resource: "new.txt", resource: "new.txt",
@@ -131,7 +172,7 @@ describe("LocationMutation", () => {
withTmp((outside) => withTmp((outside) =>
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(outside, "new.txt") const targetPath = path.join(outside, "new.txt")
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) const target = yield* (yield* LocationPath.Service).resolve({ path: targetPath })
const root = outside const root = outside
expect(target).toMatchObject({ expect(target).toMatchObject({
absolute: path.join(root, "new.txt"), absolute: path.join(root, "new.txt"),
@@ -152,7 +193,7 @@ describe("LocationMutation", () => {
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(outside, "existing.txt") const targetPath = path.join(outside, "existing.txt")
yield* Effect.promise(() => fs.writeFile(targetPath, "existing")) yield* Effect.promise(() => fs.writeFile(targetPath, "existing"))
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) const target = yield* (yield* LocationPath.Service).resolve({ path: targetPath })
expect(target).toMatchObject({ absolute: targetPath }) expect(target).toMatchObject({ absolute: targetPath })
expect(target.externalDirectory?.directory).toBe(outside) expect(target.externalDirectory?.directory).toBe(outside)
}).pipe(provide(directory)), }).pipe(provide(directory)),
@@ -164,7 +205,7 @@ describe("LocationMutation", () => {
withTmp((directory) => withTmp((directory) =>
withTmp((outside) => withTmp((outside) =>
Effect.gen(function* () { Effect.gen(function* () {
const target = yield* (yield* LocationMutation.Service).resolve({ path: outside, kind: "file" }) const target = yield* (yield* LocationPath.Service).resolve({ path: outside, kind: "file" })
expect(target.externalDirectory).toMatchObject({ expect(target.externalDirectory).toMatchObject({
directory: path.dirname(outside), directory: path.dirname(outside),
resource: path.join(path.dirname(outside), "*").replaceAll("\\", "/"), resource: path.join(path.dirname(outside), "*").replaceAll("\\", "/"),
@@ -179,7 +220,7 @@ describe("LocationMutation", () => {
withTmp((outside) => withTmp((outside) =>
Effect.gen(function* () { Effect.gen(function* () {
const targetPath = path.join(outside, "new", "nested", "file.txt") const targetPath = path.join(outside, "new", "nested", "file.txt")
const target = yield* (yield* LocationMutation.Service).resolve({ path: targetPath }) const target = yield* (yield* LocationPath.Service).resolve({ path: targetPath })
const parent = path.dirname(targetPath) const parent = path.dirname(targetPath)
expect(target.externalDirectory).toMatchObject({ expect(target.externalDirectory).toMatchObject({
directory: parent, directory: parent,
@@ -190,9 +231,9 @@ describe("LocationMutation", () => {
), ),
) )
test("ignores unknown mutation input fields", () => { test("ignores unknown path input fields", () => {
expect(Object.keys(LocationMutation.ResolveInput.fields)).toEqual(["path", "kind"]) expect(Object.keys(LocationPath.ResolveInput.fields)).toEqual(["path", "kind"])
expect(Schema.decodeUnknownSync(LocationMutation.ResolveInput)({ path: "README.md", reference: "docs" })).toEqual({ expect(Schema.decodeUnknownSync(LocationPath.ResolveInput)({ path: "README.md", reference: "docs" })).toEqual({
path: "README.md", path: "README.md",
}) })
}) })
@@ -13,7 +13,7 @@ import { FSUtil } from "@opencode-ai/util/fs-util"
import { Global } from "@opencode-ai/util/global" import { Global } from "@opencode-ai/util/global"
import { Image } from "@opencode-ai/core/image" import { Image } from "@opencode-ai/core/image"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { Model } from "@opencode-ai/core/model" import { Model } from "@opencode-ai/core/model"
import { Permission } from "@opencode-ai/core/permission" import { Permission } from "@opencode-ai/core/permission"
import { Project } from "@opencode-ai/core/project" import { Project } from "@opencode-ai/core/project"
@@ -43,7 +43,7 @@ const readToolNode = makeLocationNode({
deps: [ deps: [
Tool.node, Tool.node,
ReadToolFileSystem.node, ReadToolFileSystem.node,
LocationMutation.node, LocationPath.node,
Image.node, Image.node,
Permission.node, Permission.node,
SessionInstructions.node, SessionInstructions.node,
@@ -65,7 +65,7 @@ const testLayer = AppNodeBuilder.build(
Session.node, Session.node,
Location.node, Location.node,
FSUtil.node, FSUtil.node,
LocationMutation.node, LocationPath.node,
ReadToolFileSystem.node, ReadToolFileSystem.node,
readToolNode, readToolNode,
Tool.node, Tool.node,
+3 -3
View File
@@ -8,7 +8,7 @@ import { Environment } from "@opencode-ai/core/environment/index"
import { FileMutation } from "@opencode-ai/core/file-mutation" import { FileMutation } from "@opencode-ai/core/file-mutation"
import { Formatter } from "@opencode-ai/core/formatter" import { Formatter } from "@opencode-ai/core/formatter"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { Permission } from "@opencode-ai/core/permission" import { Permission } from "@opencode-ai/core/permission"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { Session } from "@opencode-ai/core/session" import { Session } from "@opencode-ai/core/session"
@@ -27,7 +27,7 @@ const editToolNode = makeLocationNode({
layer: Layer.effectDiscard(registerToolPlugin(EditTool.Plugin)), layer: Layer.effectDiscard(registerToolPlugin(EditTool.Plugin)),
deps: [ deps: [
Tool.node, Tool.node,
LocationMutation.node, LocationPath.node,
FileMutation.node, FileMutation.node,
Environment.node, Environment.node,
Formatter.node, Formatter.node,
@@ -84,7 +84,7 @@ const withTool = <A, E, R>(directory: string, body: (registry: Tool.Interface) =
}).pipe( }).pipe(
Effect.provide( Effect.provide(
AppNodeBuilder.build( AppNodeBuilder.build(
LayerNode.group([Tool.node, Tool.node, LocationMutation.node, FileMutation.node, editToolNode]), LayerNode.group([Tool.node, Tool.node, LocationPath.node, FileMutation.node, editToolNode]),
[ [
[ [
Environment.node, Environment.node,
+6 -7
View File
@@ -8,7 +8,7 @@ import { Environment } from "@opencode-ai/core/environment/index"
import { Formatter } from "@opencode-ai/core/formatter" import { Formatter } from "@opencode-ai/core/formatter"
import { FileMutation } from "@opencode-ai/core/file-mutation" import { FileMutation } from "@opencode-ai/core/file-mutation"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { Permission } from "@opencode-ai/core/permission" import { Permission } from "@opencode-ai/core/permission"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { Session } from "@opencode-ai/core/session" import { Session } from "@opencode-ai/core/session"
@@ -27,7 +27,7 @@ const patchToolNode = makeLocationNode({
layer: Layer.effectDiscard(registerToolPlugin(PatchTool.Plugin)), layer: Layer.effectDiscard(registerToolPlugin(PatchTool.Plugin)),
deps: [ deps: [
Tool.node, Tool.node,
LocationMutation.node, LocationPath.node,
FileMutation.node, FileMutation.node,
Environment.node, Environment.node,
Formatter.node, Formatter.node,
@@ -99,7 +99,7 @@ const withTool = <A, E, R>(
return yield* body(yield* Tool.Service) return yield* body(yield* Tool.Service)
}).pipe( }).pipe(
Effect.provide( Effect.provide(
AppNodeBuilder.build(LayerNode.group([Tool.node, LocationMutation.node, FileMutation.node, patchToolNode]), [ AppNodeBuilder.build(LayerNode.group([Tool.node, LocationPath.node, FileMutation.node, patchToolNode]), [
[ [
Environment.node, Environment.node,
transformEnvironmentFiles(activeLocation, (files) => ({ transformEnvironmentFiles(activeLocation, (files) => ({
@@ -920,7 +920,7 @@ describe("PatchTool", () => {
), ),
) )
it.live("treats a sibling path inside the project worktree as external to the Location", () => it.live("treats a sibling path inside the project worktree as internal", () =>
Effect.acquireUseRelease( Effect.acquireUseRelease(
Effect.promise(() => tmpdir()), Effect.promise(() => tmpdir()),
(tmp) => { (tmp) => {
@@ -939,9 +939,8 @@ describe("PatchTool", () => {
call("*** Begin Patch\n*** Update File: ../sibling.txt\n@@\n-before\n+after\n*** End Patch"), call("*** Begin Patch\n*** Update File: ../sibling.txt\n@@\n-before\n+after\n*** End Patch"),
), ),
).toMatchObject({ status: "completed" }) ).toMatchObject({ status: "completed" })
expect(assertions.map((input) => input.action)).toEqual(["external_directory", "edit"]) expect(assertions.map((input) => input.action)).toEqual(["edit"])
expect(assertions[0]?.resources).toEqual([path.join(tmp.path, "*").replaceAll("\\", "/")]) expect(assertions[0]?.resources).toEqual(["../sibling.txt"])
expect(assertions[1]?.resources).toEqual([target.replaceAll("\\", "/")])
expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("after\n") expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("after\n")
}), }),
tmp.path, tmp.path,
+5 -5
View File
@@ -14,7 +14,7 @@ import { Permission } from "@opencode-ai/core/permission"
import { Session } from "@opencode-ai/core/session" import { Session } from "@opencode-ai/core/session"
import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema" import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
import { Global } from "@opencode-ai/util/global" import { Global } from "@opencode-ai/util/global"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { location } from "./fixture/location" import { location } from "./fixture/location"
import { Tool } from "@opencode-ai/core/tool" import { Tool } from "@opencode-ai/core/tool"
import { ReadTool } from "@opencode-ai/core/tool/plugin/read" import { ReadTool } from "@opencode-ai/core/tool/plugin/read"
@@ -32,7 +32,7 @@ const readToolNode = makeLocationNode({
deps: [ deps: [
Tool.node, Tool.node,
ReadToolFileSystem.node, ReadToolFileSystem.node,
LocationMutation.node, LocationPath.node,
Image.node, Image.node,
Permission.node, Permission.node,
SessionInstructions.node, SessionInstructions.node,
@@ -107,8 +107,8 @@ const locationLayer = Layer.succeed(
Location.Service.of(location({ directory: AbsolutePath.make(process.cwd()) })), Location.Service.of(location({ directory: AbsolutePath.make(process.cwd()) })),
) )
const mutation = Layer.succeed( const mutation = Layer.succeed(
LocationMutation.Service, LocationPath.Service,
LocationMutation.Service.of({ LocationPath.Service.of({
resolve: (input) => { resolve: (input) => {
const absolute = path.resolve(process.cwd(), input.path) const absolute = path.resolve(process.cwd(), input.path)
const external = path.isAbsolute(input.path) && !FSUtil.contains(process.cwd(), absolute) const external = path.isAbsolute(input.path) && !FSUtil.contains(process.cwd(), absolute)
@@ -141,7 +141,7 @@ const readLayer = (imageLayer: Layer.Layer<Image.Service>) =>
[Permission.node, permission], [Permission.node, permission],
[Config.node, config], [Config.node, config],
[Image.node, imageLayer], [Image.node, imageLayer],
[LocationMutation.node, mutation], [LocationPath.node, mutation],
[FSUtil.node, testFileSystem], [FSUtil.node, testFileSystem],
[Location.node, locationLayer], [Location.node, locationLayer],
[Global.node, Global.layerWith({ data: Global.Path.data })], [Global.node, Global.layerWith({ data: Global.Path.data })],
+3 -3
View File
@@ -8,7 +8,7 @@ import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Environment } from "@opencode-ai/core/environment/index" import { Environment } from "@opencode-ai/core/environment/index"
import { FileSystem } from "@opencode-ai/core/filesystem" import { FileSystem } from "@opencode-ai/core/filesystem"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { Permission } from "@opencode-ai/core/permission" import { Permission } from "@opencode-ai/core/permission"
import { Ripgrep } from "@opencode-ai/core/ripgrep" import { Ripgrep } from "@opencode-ai/core/ripgrep"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
@@ -25,12 +25,12 @@ import { executeTool, registerToolPlugin, toolIdentity } from "./lib/tool"
const globToolNode = makeLocationNode({ const globToolNode = makeLocationNode({
name: "test/glob-tool-plugin", name: "test/glob-tool-plugin",
layer: Layer.effectDiscard(registerToolPlugin(GlobTool.Plugin)), layer: Layer.effectDiscard(registerToolPlugin(GlobTool.Plugin)),
deps: [Tool.node, Environment.node, Ripgrep.node, Location.node, LocationMutation.node, Permission.node], deps: [Tool.node, Environment.node, Ripgrep.node, Location.node, LocationPath.node, Permission.node],
}) })
const grepToolNode = makeLocationNode({ const grepToolNode = makeLocationNode({
name: "test/grep-tool-plugin", name: "test/grep-tool-plugin",
layer: Layer.effectDiscard(registerToolPlugin(GrepTool.Plugin)), layer: Layer.effectDiscard(registerToolPlugin(GrepTool.Plugin)),
deps: [Tool.node, Environment.node, Ripgrep.node, Location.node, LocationMutation.node, Permission.node], deps: [Tool.node, Environment.node, Ripgrep.node, Location.node, LocationPath.node, Permission.node],
}) })
const sessionID = Session.ID.make("ses_search_tool_test") const sessionID = Session.ID.make("ses_search_tool_test")
+2 -2
View File
@@ -16,7 +16,7 @@ import { Environment } from "@opencode-ai/core/environment/index"
import { FSUtil } from "@opencode-ai/util/fs-util" import { FSUtil } from "@opencode-ai/util/fs-util"
import { Global } from "@opencode-ai/util/global" import { Global } from "@opencode-ai/util/global"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { LocationServiceMap } from "@opencode-ai/core/location-service-map" import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
import { Model } from "@opencode-ai/core/model" import { Model } from "@opencode-ai/core/model"
import { Provider } from "@opencode-ai/core/provider" import { Provider } from "@opencode-ai/core/provider"
@@ -132,7 +132,7 @@ const shellPluginSupervisor = makeLocationNode({
deps: [ deps: [
Config.node, Config.node,
Environment.node, Environment.node,
LocationMutation.node, LocationPath.node,
Permission.node, Permission.node,
PluginRuntime.node, PluginRuntime.node,
Shell.node, Shell.node,
+3 -3
View File
@@ -8,7 +8,7 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Environment } from "@opencode-ai/core/environment/index" import { Environment } from "@opencode-ai/core/environment/index"
import { Location } from "@opencode-ai/core/location" import { Location } from "@opencode-ai/core/location"
import { LocationMutation } from "@opencode-ai/core/location-mutation" import { LocationPath } from "@opencode-ai/core/location-path"
import { Permission } from "@opencode-ai/core/permission" import { Permission } from "@opencode-ai/core/permission"
import { AbsolutePath } from "@opencode-ai/core/schema" import { AbsolutePath } from "@opencode-ai/core/schema"
import { Session } from "@opencode-ai/core/session" import { Session } from "@opencode-ai/core/session"
@@ -25,7 +25,7 @@ import { toolIdentity, executeTool, registerToolPlugin, toolDefinitions } from "
const writeToolNode = makeLocationNode({ const writeToolNode = makeLocationNode({
name: "test/write-tool-plugin", name: "test/write-tool-plugin",
layer: Layer.effectDiscard(registerToolPlugin(WriteTool.Plugin)), layer: Layer.effectDiscard(registerToolPlugin(WriteTool.Plugin)),
deps: [Tool.node, LocationMutation.node, FileMutation.node, Environment.node, Formatter.node, Permission.node], deps: [Tool.node, LocationPath.node, FileMutation.node, Environment.node, Formatter.node, Permission.node],
}) })
const sessionID = Session.ID.make("ses_write_tool_test") const sessionID = Session.ID.make("ses_write_tool_test")
@@ -72,7 +72,7 @@ const withTool = <A, E, R>(directory: string, body: (registry: Tool.Interface) =
}).pipe( }).pipe(
Effect.provide( Effect.provide(
AppNodeBuilder.build( AppNodeBuilder.build(
LayerNode.group([Tool.node, Tool.node, LocationMutation.node, FileMutation.node, writeToolNode]), LayerNode.group([Tool.node, Tool.node, LocationPath.node, FileMutation.node, writeToolNode]),
[ [
[ [
Environment.node, Environment.node,