refactor: convert edit tool to Tool.defineEffect (#21904)
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
|
import { Effect } from "effect"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { createTwoFilesPatch, diffLines } from "diff"
|
import { createTwoFilesPatch, diffLines } from "diff"
|
||||||
@@ -17,7 +18,7 @@ import { FileTime } from "../file/time"
|
|||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Snapshot } from "@/snapshot"
|
import { Snapshot } from "@/snapshot"
|
||||||
import { assertExternalDirectory } from "./external-directory"
|
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||||
|
|
||||||
const MAX_DIAGNOSTICS_PER_FILE = 20
|
const MAX_DIAGNOSTICS_PER_FILE = 20
|
||||||
|
|
||||||
@@ -34,15 +35,24 @@ function convertToLineEnding(text: string, ending: "\n" | "\r\n"): string {
|
|||||||
return text.replaceAll("\n", "\r\n")
|
return text.replaceAll("\n", "\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditTool = Tool.define("edit", {
|
const Parameters = z.object({
|
||||||
description: DESCRIPTION,
|
|
||||||
parameters: z.object({
|
|
||||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||||
oldString: z.string().describe("The text to replace"),
|
oldString: z.string().describe("The text to replace"),
|
||||||
newString: z.string().describe("The text to replace it with (must be different from oldString)"),
|
newString: z.string().describe("The text to replace it with (must be different from oldString)"),
|
||||||
replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
|
replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
|
||||||
}),
|
})
|
||||||
async execute(params, ctx) {
|
|
||||||
|
export const EditTool = Tool.defineEffect(
|
||||||
|
"edit",
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const lsp = yield* LSP.Service
|
||||||
|
const filetime = yield* FileTime.Service
|
||||||
|
|
||||||
|
return {
|
||||||
|
description: DESCRIPTION,
|
||||||
|
parameters: Parameters,
|
||||||
|
execute: (params: z.infer<typeof Parameters>, ctx: Tool.Context) =>
|
||||||
|
Effect.gen(function* () {
|
||||||
if (!params.filePath) {
|
if (!params.filePath) {
|
||||||
throw new Error("filePath is required")
|
throw new Error("filePath is required")
|
||||||
}
|
}
|
||||||
@@ -51,13 +61,15 @@ export const EditTool = Tool.define("edit", {
|
|||||||
throw new Error("No changes to apply: oldString and newString are identical.")
|
throw new Error("No changes to apply: oldString and newString are identical.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
const filePath = path.isAbsolute(params.filePath)
|
||||||
await assertExternalDirectory(ctx, filePath)
|
? params.filePath
|
||||||
|
: path.join(Instance.directory, params.filePath)
|
||||||
|
yield* assertExternalDirectoryEffect(ctx, filePath)
|
||||||
|
|
||||||
let diff = ""
|
let diff = ""
|
||||||
let contentOld = ""
|
let contentOld = ""
|
||||||
let contentNew = ""
|
let contentNew = ""
|
||||||
await FileTime.withLock(filePath, async () => {
|
yield* filetime.withLock(filePath, async () => {
|
||||||
if (params.oldString === "") {
|
if (params.oldString === "") {
|
||||||
const existed = await Filesystem.exists(filePath)
|
const existed = await Filesystem.exists(filePath)
|
||||||
contentNew = params.newString
|
contentNew = params.newString
|
||||||
@@ -95,7 +107,12 @@ export const EditTool = Tool.define("edit", {
|
|||||||
contentNew = replace(contentOld, old, next, params.replaceAll)
|
contentNew = replace(contentOld, old, next, params.replaceAll)
|
||||||
|
|
||||||
diff = trimDiff(
|
diff = trimDiff(
|
||||||
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
createTwoFilesPatch(
|
||||||
|
filePath,
|
||||||
|
filePath,
|
||||||
|
normalizeLineEndings(contentOld),
|
||||||
|
normalizeLineEndings(contentNew),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
await ctx.ask({
|
await ctx.ask({
|
||||||
permission: "edit",
|
permission: "edit",
|
||||||
@@ -116,7 +133,12 @@ export const EditTool = Tool.define("edit", {
|
|||||||
})
|
})
|
||||||
contentNew = await Filesystem.readText(filePath)
|
contentNew = await Filesystem.readText(filePath)
|
||||||
diff = trimDiff(
|
diff = trimDiff(
|
||||||
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
createTwoFilesPatch(
|
||||||
|
filePath,
|
||||||
|
filePath,
|
||||||
|
normalizeLineEndings(contentOld),
|
||||||
|
normalizeLineEndings(contentNew),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
await FileTime.read(ctx.sessionID, filePath)
|
await FileTime.read(ctx.sessionID, filePath)
|
||||||
})
|
})
|
||||||
@@ -141,15 +163,17 @@ export const EditTool = Tool.define("edit", {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let output = "Edit applied successfully."
|
let output = "Edit applied successfully."
|
||||||
await LSP.touchFile(filePath, true)
|
yield* lsp.touchFile(filePath, true)
|
||||||
const diagnostics = await LSP.diagnostics()
|
const diagnostics = yield* lsp.diagnostics()
|
||||||
const normalizedFilePath = Filesystem.normalizePath(filePath)
|
const normalizedFilePath = Filesystem.normalizePath(filePath)
|
||||||
const issues = diagnostics[normalizedFilePath] ?? []
|
const issues = diagnostics[normalizedFilePath] ?? []
|
||||||
const errors = issues.filter((item) => item.severity === 1)
|
const errors = issues.filter((item) => item.severity === 1)
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
const limited = errors.slice(0, MAX_DIAGNOSTICS_PER_FILE)
|
||||||
const suffix =
|
const suffix =
|
||||||
errors.length > MAX_DIAGNOSTICS_PER_FILE ? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more` : ""
|
errors.length > MAX_DIAGNOSTICS_PER_FILE
|
||||||
|
? `\n... and ${errors.length - MAX_DIAGNOSTICS_PER_FILE} more`
|
||||||
|
: ""
|
||||||
output += `\n\nLSP errors detected in this file, please fix:\n<diagnostics file="${filePath}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
|
output += `\n\nLSP errors detected in this file, please fix:\n<diagnostics file="${filePath}">\n${limited.map(LSP.Diagnostic.pretty).join("\n")}${suffix}\n</diagnostics>`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +186,10 @@ export const EditTool = Tool.define("edit", {
|
|||||||
title: `${path.relative(Instance.worktree, filePath)}`,
|
title: `${path.relative(Instance.worktree, filePath)}`,
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
},
|
}).pipe(Effect.orDie, Effect.runPromise),
|
||||||
})
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
|
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
import { Effect } from "effect"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { EditTool } from "./edit"
|
import { EditTool } from "./edit"
|
||||||
import DESCRIPTION from "./multiedit.txt"
|
import DESCRIPTION from "./multiedit.txt"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
|
|
||||||
export const MultiEditTool = Tool.define("multiedit", {
|
export const MultiEditTool = Tool.defineEffect(
|
||||||
|
"multiedit",
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const editInfo = yield* EditTool
|
||||||
|
const edit = yield* Effect.promise(() => editInfo.init())
|
||||||
|
|
||||||
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||||
@@ -20,18 +27,20 @@ export const MultiEditTool = Tool.define("multiedit", {
|
|||||||
)
|
)
|
||||||
.describe("Array of edit operations to perform sequentially on the file"),
|
.describe("Array of edit operations to perform sequentially on the file"),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
execute: (params: { filePath: string; edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }> }, ctx: Tool.Context) =>
|
||||||
const tool = await EditTool.init()
|
Effect.gen(function* () {
|
||||||
const results = []
|
const results = []
|
||||||
for (const [, edit] of params.edits.entries()) {
|
for (const [, entry] of params.edits.entries()) {
|
||||||
const result = await tool.execute(
|
const result = yield* Effect.promise(() =>
|
||||||
|
edit.execute(
|
||||||
{
|
{
|
||||||
filePath: params.filePath,
|
filePath: params.filePath,
|
||||||
oldString: edit.oldString,
|
oldString: entry.oldString,
|
||||||
newString: edit.newString,
|
newString: entry.newString,
|
||||||
replaceAll: edit.replaceAll,
|
replaceAll: entry.replaceAll,
|
||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
@@ -42,5 +51,7 @@ export const MultiEditTool = Tool.define("multiedit", {
|
|||||||
},
|
},
|
||||||
output: results.at(-1)!.output,
|
output: results.at(-1)!.output,
|
||||||
}
|
}
|
||||||
},
|
}).pipe(Effect.orDie, Effect.runPromise),
|
||||||
})
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ export namespace ToolRegistry {
|
|||||||
const codesearch = yield* CodeSearchTool
|
const codesearch = yield* CodeSearchTool
|
||||||
const globtool = yield* GlobTool
|
const globtool = yield* GlobTool
|
||||||
const writetool = yield* WriteTool
|
const writetool = yield* WriteTool
|
||||||
|
const edit = yield* EditTool
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
Effect.fn("ToolRegistry.state")(function* (ctx) {
|
Effect.fn("ToolRegistry.state")(function* (ctx) {
|
||||||
@@ -173,7 +174,7 @@ export namespace ToolRegistry {
|
|||||||
read: Tool.init(read),
|
read: Tool.init(read),
|
||||||
glob: Tool.init(globtool),
|
glob: Tool.init(globtool),
|
||||||
grep: Tool.init(GrepTool),
|
grep: Tool.init(GrepTool),
|
||||||
edit: Tool.init(EditTool),
|
edit: Tool.init(edit),
|
||||||
write: Tool.init(writetool),
|
write: Tool.init(writetool),
|
||||||
task: Tool.init(task),
|
task: Tool.init(task),
|
||||||
fetch: Tool.init(webfetch),
|
fetch: Tool.init(webfetch),
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { afterEach, describe, test, expect } from "bun:test"
|
import { afterAll, afterEach, describe, test, expect } from "bun:test"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
|
import { Effect, Layer, ManagedRuntime } from "effect"
|
||||||
import { EditTool } from "../../src/tool/edit"
|
import { EditTool } from "../../src/tool/edit"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
import { FileTime } from "../../src/file/time"
|
import { FileTime } from "../../src/file/time"
|
||||||
|
import { LSP } from "../../src/lsp"
|
||||||
import { SessionID, MessageID } from "../../src/session/schema"
|
import { SessionID, MessageID } from "../../src/session/schema"
|
||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
@@ -27,6 +29,22 @@ async function touch(file: string, time: number) {
|
|||||||
await fs.utimes(file, date, date)
|
await fs.utimes(file, date, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runtime = ManagedRuntime.make(
|
||||||
|
Layer.mergeAll(LSP.defaultLayer, FileTime.defaultLayer),
|
||||||
|
)
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await runtime.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolve = () =>
|
||||||
|
runtime.runPromise(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const info = yield* EditTool
|
||||||
|
return yield* Effect.promise(() => info.init())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
describe("tool.edit", () => {
|
describe("tool.edit", () => {
|
||||||
describe("creating new files", () => {
|
describe("creating new files", () => {
|
||||||
test("creates new file when oldString is empty", async () => {
|
test("creates new file when oldString is empty", async () => {
|
||||||
@@ -36,7 +54,7 @@ describe("tool.edit", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
const result = await edit.execute(
|
const result = await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -61,7 +79,7 @@ describe("tool.edit", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -91,7 +109,7 @@ describe("tool.edit", () => {
|
|||||||
const events: string[] = []
|
const events: string[] = []
|
||||||
const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated"))
|
const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated"))
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -119,7 +137,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
const result = await edit.execute(
|
const result = await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -146,7 +164,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -169,7 +187,7 @@ describe("tool.edit", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -194,7 +212,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -217,7 +235,7 @@ describe("tool.edit", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -249,7 +267,7 @@ describe("tool.edit", () => {
|
|||||||
await touch(filepath, 2_000)
|
await touch(filepath, 2_000)
|
||||||
|
|
||||||
// Try to edit with the new content
|
// Try to edit with the new content
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -274,7 +292,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -307,7 +325,7 @@ describe("tool.edit", () => {
|
|||||||
const events: string[] = []
|
const events: string[] = []
|
||||||
const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated"))
|
const unsubUpdated = Bus.subscribe(FileWatcher.Event.Updated, () => events.push("updated"))
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -335,7 +353,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -361,7 +379,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -385,7 +403,7 @@ describe("tool.edit", () => {
|
|||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -410,7 +428,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, dirpath)
|
await FileTime.read(ctx.sessionID, dirpath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
await expect(
|
await expect(
|
||||||
edit.execute(
|
edit.execute(
|
||||||
{
|
{
|
||||||
@@ -435,7 +453,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
const result = await edit.execute(
|
const result = await edit.execute(
|
||||||
{
|
{
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
@@ -502,7 +520,7 @@ describe("tool.edit", () => {
|
|||||||
return await Instance.provide({
|
return await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
const filePath = path.join(tmp.path, "test.txt")
|
const filePath = path.join(tmp.path, "test.txt")
|
||||||
await FileTime.read(ctx.sessionID, filePath)
|
await FileTime.read(ctx.sessionID, filePath)
|
||||||
await edit.execute(
|
await edit.execute(
|
||||||
@@ -647,7 +665,7 @@ describe("tool.edit", () => {
|
|||||||
fn: async () => {
|
fn: async () => {
|
||||||
await FileTime.read(ctx.sessionID, filepath)
|
await FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
const edit = await EditTool.init()
|
const edit = await resolve()
|
||||||
|
|
||||||
// Two concurrent edits
|
// Two concurrent edits
|
||||||
const promise1 = edit.execute(
|
const promise1 = edit.execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user