Finish async tool cleanup
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { constants } from "node:fs";
|
||||
import { access as fsAccess } from "node:fs/promises";
|
||||
import { createInterface } from "node:readline";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { Text } from "@earendil-works/pi-tui";
|
||||
@@ -9,7 +7,7 @@ import { type Static, Type } from "typebox";
|
||||
import { keyHint } from "../../modes/interactive/components/keybinding-hints.ts";
|
||||
import { ensureTool } from "../../utils/tools-manager.ts";
|
||||
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts";
|
||||
import { resolveToCwd } from "./path-utils.ts";
|
||||
import { pathExists, resolveToCwd } from "./path-utils.ts";
|
||||
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
||||
@@ -47,14 +45,7 @@ export interface FindOperations {
|
||||
}
|
||||
|
||||
const defaultFindOperations: FindOperations = {
|
||||
exists: async (absolutePath) => {
|
||||
try {
|
||||
await fsAccess(absolutePath, constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
exists: pathExists,
|
||||
// This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.
|
||||
glob: () => [],
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { constants } from "node:fs";
|
||||
import { access as fsAccess, readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
|
||||
import { readdir as fsReaddir, stat as fsStat } from "node:fs/promises";
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { Text } from "@earendil-works/pi-tui";
|
||||
import nodePath from "path";
|
||||
import { type Static, Type } from "typebox";
|
||||
import { keyHint } from "../../modes/interactive/components/keybinding-hints.ts";
|
||||
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.ts";
|
||||
import { resolveToCwd } from "./path-utils.ts";
|
||||
import { pathExists, resolveToCwd } from "./path-utils.ts";
|
||||
import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.ts";
|
||||
import { wrapToolDefinition } from "./tool-definition-wrapper.ts";
|
||||
import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.ts";
|
||||
@@ -39,14 +38,7 @@ export interface LsOperations {
|
||||
}
|
||||
|
||||
const defaultLsOperations: LsOperations = {
|
||||
exists: async (absolutePath) => {
|
||||
try {
|
||||
await fsAccess(absolutePath, constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
exists: pathExists,
|
||||
stat: fsStat,
|
||||
readdir: fsReaddir,
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ function fileExists(filePath: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
async function fileExistsAsync(filePath: string): Promise<boolean> {
|
||||
export async function pathExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await access(filePath, constants.F_OK);
|
||||
return true;
|
||||
@@ -86,31 +86,31 @@ export function resolveReadPath(filePath: string, cwd: string): string {
|
||||
export async function resolveReadPathAsync(filePath: string, cwd: string): Promise<string> {
|
||||
const resolved = resolveToCwd(filePath, cwd);
|
||||
|
||||
if (await fileExistsAsync(resolved)) {
|
||||
if (await pathExists(resolved)) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// Try macOS AM/PM variant (narrow no-break space before AM/PM)
|
||||
const amPmVariant = tryMacOSScreenshotPath(resolved);
|
||||
if (amPmVariant !== resolved && (await fileExistsAsync(amPmVariant))) {
|
||||
if (amPmVariant !== resolved && (await pathExists(amPmVariant))) {
|
||||
return amPmVariant;
|
||||
}
|
||||
|
||||
// Try NFD variant (macOS stores filenames in NFD form)
|
||||
const nfdVariant = tryNFDVariant(resolved);
|
||||
if (nfdVariant !== resolved && (await fileExistsAsync(nfdVariant))) {
|
||||
if (nfdVariant !== resolved && (await pathExists(nfdVariant))) {
|
||||
return nfdVariant;
|
||||
}
|
||||
|
||||
// Try curly quote variant (macOS uses U+2019 in screenshot names)
|
||||
const curlyVariant = tryCurlyQuoteVariant(resolved);
|
||||
if (curlyVariant !== resolved && (await fileExistsAsync(curlyVariant))) {
|
||||
if (curlyVariant !== resolved && (await pathExists(curlyVariant))) {
|
||||
return curlyVariant;
|
||||
}
|
||||
|
||||
// Try combined NFD + curly quote (for French macOS screenshots like "Capture d'écran")
|
||||
const nfdCurlyVariant = tryCurlyQuoteVariant(nfdVariant);
|
||||
if (nfdCurlyVariant !== resolved && (await fileExistsAsync(nfdCurlyVariant))) {
|
||||
if (nfdCurlyVariant !== resolved && (await pathExists(nfdCurlyVariant))) {
|
||||
return nfdCurlyVariant;
|
||||
}
|
||||
|
||||
|
||||
@@ -201,34 +201,27 @@ export function createWriteToolDefinition(
|
||||
const absolutePath = resolveToCwd(path, cwd);
|
||||
const dir = dirname(absolutePath);
|
||||
return withFileMutationQueue(absolutePath, async () => {
|
||||
let aborted = signal?.aborted ?? false;
|
||||
const onAbort = () => {
|
||||
aborted = true;
|
||||
};
|
||||
// Do not reject from an abort event listener here: that would release the
|
||||
// mutation queue while an in-flight filesystem operation may still finish.
|
||||
// Checking signal.aborted after each await observes the same aborts while
|
||||
// keeping the queue locked until the current operation has settled.
|
||||
const throwIfAborted = (): void => {
|
||||
if (aborted || signal?.aborted) {
|
||||
throw new Error("Operation aborted");
|
||||
}
|
||||
if (signal?.aborted) throw new Error("Operation aborted");
|
||||
};
|
||||
|
||||
signal?.addEventListener("abort", onAbort, { once: true });
|
||||
try {
|
||||
throwIfAborted();
|
||||
// Create parent directories if needed.
|
||||
await ops.mkdir(dir);
|
||||
throwIfAborted();
|
||||
throwIfAborted();
|
||||
// Create parent directories if needed.
|
||||
await ops.mkdir(dir);
|
||||
throwIfAborted();
|
||||
|
||||
// Write the file contents.
|
||||
await ops.writeFile(absolutePath, content);
|
||||
throwIfAborted();
|
||||
// Write the file contents.
|
||||
await ops.writeFile(absolutePath, content);
|
||||
throwIfAborted();
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
|
||||
details: undefined,
|
||||
};
|
||||
} finally {
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
|
||||
details: undefined,
|
||||
};
|
||||
});
|
||||
},
|
||||
renderCall(args, theme, context) {
|
||||
|
||||
Reference in New Issue
Block a user