diff --git a/packages/core/test/tool-write.test.ts b/packages/core/test/tool-write.test.ts index 989f49c645..f43c6b42ef 100644 --- a/packages/core/test/tool-write.test.ts +++ b/packages/core/test/tool-write.test.ts @@ -87,7 +87,11 @@ const reset = () => { denyAction = undefined } -const withTool = (directory: string, body: (registry: Tool.Interface) => Effect.Effect) => { +const withTool = ( + directory: string, + body: (registry: Tool.Interface) => Effect.Effect, + options?: { edit?: boolean }, +) => { const activeLocation = Layer.succeed( Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) })), @@ -97,7 +101,13 @@ const withTool = (directory: string, body: (registry: Tool.Interface) = }).pipe( Effect.provide( AppNodeBuilder.build( - LayerNode.group([Tool.node, Tool.node, LocationMutation.node, FileMutation.node, writeToolNode]), + LayerNode.group([ + Tool.node, + LocationMutation.node, + FileMutation.node, + writeToolNode, + ...(options?.edit ? [editToolNode] : []), + ]), [ [ Environment.node, @@ -115,28 +125,6 @@ const withTool = (directory: string, body: (registry: Tool.Interface) = ) } -const withMutationTools = (directory: string, body: (registry: Tool.Interface) => Effect.Effect) => { - const activeLocation = Layer.succeed( - Location.Service, - Location.Service.of(location({ directory: AbsolutePath.make(directory) })), - ) - return Effect.gen(function* () { - return yield* body(yield* Tool.Service) - }).pipe( - Effect.provide( - AppNodeBuilder.build( - LayerNode.group([Tool.node, Tool.node, LocationMutation.node, FileMutation.node, writeToolNode, editToolNode]), - [ - [Environment.node, environment], - [Location.node, activeLocation], - [Formatter.node, formatter], - [Permission.node, permission], - ], - ), - ), - ) -} - const call = (input: typeof WriteTool.Input.Type, id = "call-write") => ({ sessionID, ...toolIdentity, @@ -483,15 +471,21 @@ describe("WriteTool", () => { ? Deferred.succeed(editApproved, undefined).pipe(Effect.asVoid) : Effect.void - const write = yield* withMutationTools(tmp.path, (registry) => - executeTool(registry, call({ path: "shared.txt", content: "before" }, "call-serialized-write")), + const write = yield* withTool( + tmp.path, + (registry) => + executeTool(registry, call({ path: "shared.txt", content: "before" }, "call-serialized-write")), + { edit: true }, ).pipe(Effect.forkChild) yield* Deferred.await(formatting) - const edit = yield* withMutationTools(tmp.path, (registry) => - executeTool( - registry, - editCall({ path: "shared.txt", oldString: "before", newString: "after" }, "call-serialized-edit"), - ), + const edit = yield* withTool( + tmp.path, + (registry) => + executeTool( + registry, + editCall({ path: "shared.txt", oldString: "before", newString: "after" }, "call-serialized-edit"), + ), + { edit: true }, ).pipe(Effect.forkChild) yield* Effect.yieldNow expect(yield* Deferred.isDone(editApproved)).toBe(false) @@ -507,82 +501,4 @@ describe("WriteTool", () => { ), ) - it.live("serializes complete write transactions across Location service instances", () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => { - reset() - const target = path.join(tmp.path, "shared.txt") - return Effect.gen(function* () { - const formatting = yield* Deferred.make() - const releaseFormatting = yield* Deferred.make() - const secondApproved = yield* Deferred.make() - let formats = 0 - formatFile = () => - ++formats === 1 - ? Deferred.succeed(formatting, undefined).pipe( - Effect.andThen(Deferred.await(releaseFormatting)), - Effect.as(false), - ) - : Effect.succeed(false) - afterPermission = (input) => - input.source?.id === "call-second-write" && input.action === "edit" - ? Deferred.succeed(secondApproved, undefined).pipe(Effect.asVoid) - : Effect.void - - const first = yield* withTool(tmp.path, (registry) => - executeTool(registry, call({ path: "shared.txt", content: "first" }, "call-first-write")), - ).pipe(Effect.forkChild) - yield* Deferred.await(formatting) - const second = yield* withTool(tmp.path, (registry) => - executeTool(registry, call({ path: "shared.txt", content: "second" }, "call-second-write")), - ).pipe(Effect.forkChild) - yield* Effect.yieldNow - expect(yield* Deferred.isDone(secondApproved)).toBe(false) - - yield* Deferred.succeed(releaseFormatting, undefined) - expect((yield* Fiber.join(first)).status).toBe("completed") - expect((yield* Fiber.join(second)).status).toBe("completed") - expect(yield* Deferred.isDone(secondApproved)).toBe(true) - expect(yield* Effect.promise(() => fs.readFile(target, "utf8"))).toBe("second") - }) - }, - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), - ) - - it.live("allows complete write transactions for unrelated paths to run concurrently", () => - Effect.acquireUseRelease( - Effect.promise(() => tmpdir()), - (tmp) => { - reset() - return Effect.gen(function* () { - const formatting = yield* Deferred.make() - const releaseFormatting = yield* Deferred.make() - let formats = 0 - formatFile = () => - ++formats === 1 - ? Deferred.succeed(formatting, undefined).pipe( - Effect.andThen(Deferred.await(releaseFormatting)), - Effect.as(false), - ) - : Effect.succeed(false) - - const first = yield* withTool(tmp.path, (registry) => - executeTool(registry, call({ path: "first.txt", content: "first" }, "call-first-path")), - ).pipe(Effect.forkChild) - yield* Deferred.await(formatting) - const second = yield* withTool(tmp.path, (registry) => - executeTool(registry, call({ path: "second.txt", content: "second" }, "call-second-path")), - ).pipe(Effect.forkChild) - - expect((yield* Fiber.join(second)).status).toBe("completed") - expect(yield* Effect.promise(() => fs.readFile(path.join(tmp.path, "second.txt"), "utf8"))).toBe("second") - yield* Deferred.succeed(releaseFormatting, undefined) - expect((yield* Fiber.join(first)).status).toBe("completed") - }) - }, - (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), - ), - ) })