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]()),
- ),
- )
})