iosm-cli 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +7 -7
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-profiles.d.ts.map +1 -1
  7. package/dist/core/agent-profiles.js +3 -1
  8. package/dist/core/agent-profiles.js.map +1 -1
  9. package/dist/core/agent-session.d.ts.map +1 -1
  10. package/dist/core/agent-session.js +14 -1
  11. package/dist/core/agent-session.js.map +1 -1
  12. package/dist/core/sdk.d.ts +2 -2
  13. package/dist/core/sdk.d.ts.map +1 -1
  14. package/dist/core/sdk.js +3 -3
  15. package/dist/core/sdk.js.map +1 -1
  16. package/dist/core/shadow-guard.js +1 -1
  17. package/dist/core/shadow-guard.js.map +1 -1
  18. package/dist/core/system-prompt.d.ts.map +1 -1
  19. package/dist/core/system-prompt.js +17 -2
  20. package/dist/core/system-prompt.js.map +1 -1
  21. package/dist/core/tools/fetch.d.ts +56 -0
  22. package/dist/core/tools/fetch.d.ts.map +1 -0
  23. package/dist/core/tools/fetch.js +272 -0
  24. package/dist/core/tools/fetch.js.map +1 -0
  25. package/dist/core/tools/fs-ops.d.ts +54 -0
  26. package/dist/core/tools/fs-ops.d.ts.map +1 -0
  27. package/dist/core/tools/fs-ops.js +206 -0
  28. package/dist/core/tools/fs-ops.js.map +1 -0
  29. package/dist/core/tools/git-read.d.ts +60 -0
  30. package/dist/core/tools/git-read.d.ts.map +1 -0
  31. package/dist/core/tools/git-read.js +267 -0
  32. package/dist/core/tools/git-read.js.map +1 -0
  33. package/dist/core/tools/index.d.ts +44 -0
  34. package/dist/core/tools/index.d.ts.map +1 -1
  35. package/dist/core/tools/index.js +22 -0
  36. package/dist/core/tools/index.js.map +1 -1
  37. package/dist/core/tools/task.js +1 -1
  38. package/dist/core/tools/task.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  44. package/dist/modes/interactive/interactive-mode.js +56 -22
  45. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  46. package/docs/cli-reference.md +4 -1
  47. package/docs/configuration.md +2 -2
  48. package/docs/interactive-mode.md +2 -2
  49. package/docs/rpc-json-sdk.md +1 -1
  50. package/package.json +1 -1
@@ -0,0 +1,272 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { isReadOnlyProfileName } from "../agent-profiles.js";
3
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, } from "./truncate.js";
4
+ const fetchSchema = Type.Object({
5
+ url: Type.String({ description: "Target URL (http/https)." }),
6
+ method: Type.Optional(Type.Union([
7
+ Type.Literal("GET"),
8
+ Type.Literal("POST"),
9
+ Type.Literal("PUT"),
10
+ Type.Literal("PATCH"),
11
+ Type.Literal("DELETE"),
12
+ Type.Literal("HEAD"),
13
+ Type.Literal("OPTIONS"),
14
+ ], { description: "HTTP method (default: GET)." })),
15
+ headers: Type.Optional(Type.Record(Type.String(), Type.String(), {
16
+ description: "Optional HTTP headers.",
17
+ })),
18
+ body: Type.Optional(Type.String({ description: "Optional request body (mostly for POST/PUT/PATCH/DELETE)." })),
19
+ timeout: Type.Optional(Type.Number({ description: "Request timeout in seconds (default: 30)." })),
20
+ max_bytes: Type.Optional(Type.Number({
21
+ description: "Maximum response bytes to capture from body (default: 262144).",
22
+ })),
23
+ response_format: Type.Optional(Type.Union([Type.Literal("auto"), Type.Literal("json"), Type.Literal("text")], {
24
+ description: "Body rendering: auto | json | text (default: auto).",
25
+ })),
26
+ max_redirects: Type.Optional(Type.Number({
27
+ description: "Maximum redirect hops to follow manually (default: 5).",
28
+ })),
29
+ });
30
+ export const DEFAULT_FETCH_TIMEOUT_SECONDS = 30;
31
+ export const DEFAULT_FETCH_MAX_BYTES = 256 * 1024;
32
+ export const DEFAULT_FETCH_MAX_REDIRECTS = 5;
33
+ const READ_ONLY_FETCH_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
34
+ const FULL_FETCH_METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
35
+ const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);
36
+ export function getAllowedFetchMethodsForProfile(profileName) {
37
+ return isReadOnlyProfileName(profileName) ? READ_ONLY_FETCH_METHODS : FULL_FETCH_METHODS;
38
+ }
39
+ function asSet(methods) {
40
+ return methods instanceof Set ? methods : new Set(methods);
41
+ }
42
+ function isJsonContentType(contentType) {
43
+ if (!contentType)
44
+ return false;
45
+ const normalized = contentType.toLowerCase();
46
+ return normalized.includes("application/json") || normalized.includes("+json");
47
+ }
48
+ function isRedirectStatus(status) {
49
+ return REDIRECT_STATUS_CODES.has(status);
50
+ }
51
+ function normalizeMethod(raw) {
52
+ return (raw ?? "GET").toUpperCase();
53
+ }
54
+ function normalizePositiveInt(raw, fallback, field) {
55
+ if (raw === undefined)
56
+ return fallback;
57
+ const value = Math.floor(raw);
58
+ if (!Number.isFinite(value) || value <= 0) {
59
+ throw new Error(`${field} must be a positive number.`);
60
+ }
61
+ return value;
62
+ }
63
+ function normalizeNonNegativeInt(raw, fallback, field) {
64
+ if (raw === undefined)
65
+ return fallback;
66
+ const value = Math.floor(raw);
67
+ if (!Number.isFinite(value) || value < 0) {
68
+ throw new Error(`${field} must be a non-negative number.`);
69
+ }
70
+ return value;
71
+ }
72
+ async function readResponseBodyWithLimit(response, maxBytes) {
73
+ if (!response.body) {
74
+ return { buffer: Buffer.alloc(0), truncated: false };
75
+ }
76
+ const reader = response.body.getReader();
77
+ const chunks = [];
78
+ let total = 0;
79
+ let truncated = false;
80
+ while (true) {
81
+ const { value, done } = await reader.read();
82
+ if (done)
83
+ break;
84
+ if (!value)
85
+ continue;
86
+ const chunk = Buffer.from(value);
87
+ if (total + chunk.length > maxBytes) {
88
+ const remaining = maxBytes - total;
89
+ if (remaining > 0) {
90
+ chunks.push(chunk.subarray(0, remaining));
91
+ total += remaining;
92
+ }
93
+ truncated = true;
94
+ await reader.cancel().catch(() => { });
95
+ break;
96
+ }
97
+ chunks.push(chunk);
98
+ total += chunk.length;
99
+ }
100
+ return { buffer: Buffer.concat(chunks, total), truncated };
101
+ }
102
+ function formatHttpSummary(input) {
103
+ const lines = [`HTTP ${input.status}${input.statusText ? ` ${input.statusText}` : ""}`, `${input.method} ${input.finalUrl}`];
104
+ if (input.redirectsFollowed > 0) {
105
+ lines.push(`redirects followed: ${input.redirectsFollowed}`);
106
+ }
107
+ if (input.body.length === 0) {
108
+ lines.push("(no response body)");
109
+ return lines.join("\n");
110
+ }
111
+ return `${lines.join("\n")}\n\n${input.body}`;
112
+ }
113
+ function nextMethodAfterRedirect(status, method) {
114
+ if (status === 303) {
115
+ return method === "HEAD" ? "HEAD" : "GET";
116
+ }
117
+ if ((status === 301 || status === 302) && method === "POST") {
118
+ return "GET";
119
+ }
120
+ return method;
121
+ }
122
+ export function createFetchTool(cwd, options) {
123
+ const fetchImpl = options?.fetchImpl ?? fetch;
124
+ const permissionGuard = options?.permissionGuard;
125
+ const defaultTimeoutSeconds = options?.defaultTimeoutSeconds ?? DEFAULT_FETCH_TIMEOUT_SECONDS;
126
+ const defaultMaxBytes = options?.defaultMaxBytes ?? DEFAULT_FETCH_MAX_BYTES;
127
+ const defaultMaxRedirects = options?.defaultMaxRedirects ?? DEFAULT_FETCH_MAX_REDIRECTS;
128
+ return {
129
+ name: "fetch",
130
+ label: "fetch",
131
+ description: "Make HTTP requests (manual redirect handling). Defaults: method=GET, timeout=30s, max_bytes=262144, response_format=auto, max_redirects=5.",
132
+ parameters: fetchSchema,
133
+ execute: async (_toolCallId, input, signal) => {
134
+ const method = normalizeMethod(input.method);
135
+ const allowedMethods = asSet(options?.resolveAllowedMethods?.() ?? FULL_FETCH_METHODS);
136
+ if (!allowedMethods.has(method)) {
137
+ throw new Error(`HTTP method "${method}" is not allowed in the current profile. Allowed methods: ${Array.from(allowedMethods).join(", ")}`);
138
+ }
139
+ if ((method === "GET" || method === "HEAD") && input.body !== undefined) {
140
+ throw new Error(`HTTP ${method} does not accept a request body in this tool.`);
141
+ }
142
+ let requestUrl;
143
+ try {
144
+ requestUrl = new URL(input.url).toString();
145
+ }
146
+ catch {
147
+ throw new Error(`Invalid URL: ${input.url}`);
148
+ }
149
+ const timeoutSeconds = normalizePositiveInt(input.timeout, defaultTimeoutSeconds, "timeout");
150
+ const maxBytes = normalizePositiveInt(input.max_bytes, defaultMaxBytes, "max_bytes");
151
+ const maxRedirects = normalizeNonNegativeInt(input.max_redirects, defaultMaxRedirects, "max_redirects");
152
+ const requestedFormat = input.response_format ?? "auto";
153
+ if (permissionGuard) {
154
+ const allowed = await permissionGuard({
155
+ toolName: "fetch",
156
+ cwd,
157
+ input: {
158
+ url: requestUrl,
159
+ method,
160
+ headers: input.headers,
161
+ hasBody: input.body !== undefined,
162
+ bodyLength: input.body?.length ?? 0,
163
+ timeoutSeconds,
164
+ maxBytes,
165
+ maxRedirects,
166
+ responseFormat: requestedFormat,
167
+ },
168
+ summary: `${method} ${requestUrl}`,
169
+ });
170
+ if (!allowed) {
171
+ throw new Error("Permission denied for fetch operation.");
172
+ }
173
+ }
174
+ const timeoutSignal = AbortSignal.timeout(timeoutSeconds * 1000);
175
+ const requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
176
+ let currentUrl = requestUrl;
177
+ let currentMethod = method;
178
+ let currentBody = input.body;
179
+ let redirectsFollowed = 0;
180
+ let response;
181
+ while (true) {
182
+ response = await fetchImpl(currentUrl, {
183
+ method: currentMethod,
184
+ headers: input.headers,
185
+ body: currentBody,
186
+ redirect: "manual",
187
+ signal: requestSignal,
188
+ });
189
+ if (!isRedirectStatus(response.status)) {
190
+ break;
191
+ }
192
+ const location = response.headers.get("location");
193
+ if (!location) {
194
+ break;
195
+ }
196
+ if (redirectsFollowed >= maxRedirects) {
197
+ throw new Error(`Redirect limit exceeded (${maxRedirects}).`);
198
+ }
199
+ currentUrl = new URL(location, currentUrl).toString();
200
+ const redirectedMethod = nextMethodAfterRedirect(response.status, currentMethod);
201
+ if (redirectedMethod !== currentMethod) {
202
+ currentBody = undefined;
203
+ }
204
+ currentMethod = redirectedMethod;
205
+ redirectsFollowed++;
206
+ }
207
+ if (!response) {
208
+ throw new Error("No response received.");
209
+ }
210
+ const bodyResult = await readResponseBodyWithLimit(response, maxBytes);
211
+ const contentType = response.headers.get("content-type");
212
+ const resolvedFormat = requestedFormat === "auto" ? (isJsonContentType(contentType) ? "json" : "text") : requestedFormat;
213
+ const rawText = bodyResult.buffer.toString("utf-8");
214
+ let formattedBody;
215
+ if (resolvedFormat === "json") {
216
+ try {
217
+ const parsed = JSON.parse(rawText);
218
+ formattedBody = JSON.stringify(parsed, null, 2);
219
+ }
220
+ catch (error) {
221
+ throw new Error(`Response body is not valid JSON: ${error?.message ?? "parse failed"}`);
222
+ }
223
+ }
224
+ else {
225
+ formattedBody = rawText;
226
+ }
227
+ const summary = formatHttpSummary({
228
+ method,
229
+ status: response.status,
230
+ statusText: response.statusText,
231
+ finalUrl: currentUrl,
232
+ redirectsFollowed,
233
+ body: formattedBody.trimEnd(),
234
+ });
235
+ const truncation = truncateHead(summary, {
236
+ maxBytes: Math.max(DEFAULT_MAX_BYTES, maxBytes + 1024),
237
+ maxLines: DEFAULT_MAX_LINES,
238
+ });
239
+ let output = truncation.content;
240
+ const notices = [];
241
+ if (bodyResult.truncated) {
242
+ notices.push(`response body truncated at ${formatSize(maxBytes)}`);
243
+ }
244
+ if (truncation.truncated) {
245
+ notices.push(`output truncated by ${truncation.truncatedBy === "lines" ? "line" : "byte"} limit (showing up to ${DEFAULT_MAX_LINES} lines)`);
246
+ }
247
+ if (notices.length > 0) {
248
+ output += `\n\n[${notices.join(". ")}]`;
249
+ }
250
+ const details = {
251
+ method,
252
+ requestUrl,
253
+ finalUrl: currentUrl,
254
+ status: response.status,
255
+ statusText: response.statusText,
256
+ redirectsFollowed,
257
+ responseFormat: resolvedFormat,
258
+ timeoutSeconds,
259
+ maxBytes,
260
+ bodyBytesCaptured: bodyResult.buffer.length,
261
+ bodyCaptureTruncated: bodyResult.truncated,
262
+ truncation: truncation.truncated ? truncation : undefined,
263
+ };
264
+ return {
265
+ content: [{ type: "text", text: output }],
266
+ details,
267
+ };
268
+ },
269
+ };
270
+ }
271
+ export const fetchTool = createFetchTool(process.cwd());
272
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../../src/core/tools/fetch.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { isReadOnlyProfileName } from \"../agent-profiles.js\";\nimport type { ToolPermissionGuard } from \"./permissions.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationResult,\n\ttruncateHead,\n} from \"./truncate.js\";\n\nconst fetchSchema = Type.Object({\n\turl: Type.String({ description: \"Target URL (http/https).\" }),\n\tmethod: Type.Optional(\n\t\tType.Union(\n\t\t\t[\n\t\t\t\tType.Literal(\"GET\"),\n\t\t\t\tType.Literal(\"POST\"),\n\t\t\t\tType.Literal(\"PUT\"),\n\t\t\t\tType.Literal(\"PATCH\"),\n\t\t\t\tType.Literal(\"DELETE\"),\n\t\t\t\tType.Literal(\"HEAD\"),\n\t\t\t\tType.Literal(\"OPTIONS\"),\n\t\t\t],\n\t\t\t{ description: \"HTTP method (default: GET).\" },\n\t\t),\n\t),\n\theaders: Type.Optional(\n\t\tType.Record(Type.String(), Type.String(), {\n\t\t\tdescription: \"Optional HTTP headers.\",\n\t\t}),\n\t),\n\tbody: Type.Optional(Type.String({ description: \"Optional request body (mostly for POST/PUT/PATCH/DELETE).\" })),\n\ttimeout: Type.Optional(Type.Number({ description: \"Request timeout in seconds (default: 30).\" })),\n\tmax_bytes: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: \"Maximum response bytes to capture from body (default: 262144).\",\n\t\t}),\n\t),\n\tresponse_format: Type.Optional(\n\t\tType.Union([Type.Literal(\"auto\"), Type.Literal(\"json\"), Type.Literal(\"text\")], {\n\t\t\tdescription: \"Body rendering: auto | json | text (default: auto).\",\n\t\t}),\n\t),\n\tmax_redirects: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription: \"Maximum redirect hops to follow manually (default: 5).\",\n\t\t}),\n\t),\n});\n\nexport type FetchToolInput = Static<typeof fetchSchema>;\nexport type FetchResponseFormat = \"auto\" | \"json\" | \"text\";\nexport type FetchMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\" | \"HEAD\" | \"OPTIONS\";\n\nexport const DEFAULT_FETCH_TIMEOUT_SECONDS = 30;\nexport const DEFAULT_FETCH_MAX_BYTES = 256 * 1024;\nexport const DEFAULT_FETCH_MAX_REDIRECTS = 5;\n\nconst READ_ONLY_FETCH_METHODS = new Set<FetchMethod>([\"GET\", \"HEAD\", \"OPTIONS\"]);\nconst FULL_FETCH_METHODS = new Set<FetchMethod>([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\", \"OPTIONS\"]);\n\nconst REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308]);\n\nexport interface FetchToolDetails {\n\tmethod: FetchMethod;\n\trequestUrl: string;\n\tfinalUrl: string;\n\tstatus: number;\n\tstatusText: string;\n\tredirectsFollowed: number;\n\tresponseFormat: \"json\" | \"text\";\n\ttimeoutSeconds: number;\n\tmaxBytes: number;\n\tbodyBytesCaptured: number;\n\tbodyCaptureTruncated: boolean;\n\ttruncation?: TruncationResult;\n}\n\nexport interface FetchToolOptions {\n\tfetchImpl?: typeof fetch;\n\tpermissionGuard?: ToolPermissionGuard;\n\tresolveAllowedMethods?: () => ReadonlySet<FetchMethod> | readonly FetchMethod[];\n\tdefaultTimeoutSeconds?: number;\n\tdefaultMaxBytes?: number;\n\tdefaultMaxRedirects?: number;\n}\n\nexport function getAllowedFetchMethodsForProfile(profileName: string | undefined): ReadonlySet<FetchMethod> {\n\treturn isReadOnlyProfileName(profileName) ? READ_ONLY_FETCH_METHODS : FULL_FETCH_METHODS;\n}\n\nfunction asSet(methods: ReadonlySet<FetchMethod> | readonly FetchMethod[]): ReadonlySet<FetchMethod> {\n\treturn methods instanceof Set ? methods : new Set(methods);\n}\n\nfunction isJsonContentType(contentType: string | null): boolean {\n\tif (!contentType) return false;\n\tconst normalized = contentType.toLowerCase();\n\treturn normalized.includes(\"application/json\") || normalized.includes(\"+json\");\n}\n\nfunction isRedirectStatus(status: number): boolean {\n\treturn REDIRECT_STATUS_CODES.has(status);\n}\n\nfunction normalizeMethod(raw: string | undefined): FetchMethod {\n\treturn ((raw ?? \"GET\").toUpperCase() as FetchMethod);\n}\n\nfunction normalizePositiveInt(raw: number | undefined, fallback: number, field: string): number {\n\tif (raw === undefined) return fallback;\n\tconst value = Math.floor(raw);\n\tif (!Number.isFinite(value) || value <= 0) {\n\t\tthrow new Error(`${field} must be a positive number.`);\n\t}\n\treturn value;\n}\n\nfunction normalizeNonNegativeInt(raw: number | undefined, fallback: number, field: string): number {\n\tif (raw === undefined) return fallback;\n\tconst value = Math.floor(raw);\n\tif (!Number.isFinite(value) || value < 0) {\n\t\tthrow new Error(`${field} must be a non-negative number.`);\n\t}\n\treturn value;\n}\n\nasync function readResponseBodyWithLimit(response: Response, maxBytes: number): Promise<{\n\tbuffer: Buffer;\n\ttruncated: boolean;\n}> {\n\tif (!response.body) {\n\t\treturn { buffer: Buffer.alloc(0), truncated: false };\n\t}\n\n\tconst reader = response.body.getReader();\n\tconst chunks: Buffer[] = [];\n\tlet total = 0;\n\tlet truncated = false;\n\n\twhile (true) {\n\t\tconst { value, done } = await reader.read();\n\t\tif (done) break;\n\t\tif (!value) continue;\n\n\t\tconst chunk = Buffer.from(value);\n\t\tif (total + chunk.length > maxBytes) {\n\t\t\tconst remaining = maxBytes - total;\n\t\t\tif (remaining > 0) {\n\t\t\t\tchunks.push(chunk.subarray(0, remaining));\n\t\t\t\ttotal += remaining;\n\t\t\t}\n\t\t\ttruncated = true;\n\t\t\tawait reader.cancel().catch(() => {});\n\t\t\tbreak;\n\t\t}\n\n\t\tchunks.push(chunk);\n\t\ttotal += chunk.length;\n\t}\n\n\treturn { buffer: Buffer.concat(chunks, total), truncated };\n}\n\nfunction formatHttpSummary(input: {\n\tmethod: FetchMethod;\n\tstatus: number;\n\tstatusText: string;\n\tfinalUrl: string;\n\tredirectsFollowed: number;\n\tbody: string;\n}): string {\n\tconst lines = [`HTTP ${input.status}${input.statusText ? ` ${input.statusText}` : \"\"}`, `${input.method} ${input.finalUrl}`];\n\tif (input.redirectsFollowed > 0) {\n\t\tlines.push(`redirects followed: ${input.redirectsFollowed}`);\n\t}\n\tif (input.body.length === 0) {\n\t\tlines.push(\"(no response body)\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\treturn `${lines.join(\"\\n\")}\\n\\n${input.body}`;\n}\n\nfunction nextMethodAfterRedirect(status: number, method: FetchMethod): FetchMethod {\n\tif (status === 303) {\n\t\treturn method === \"HEAD\" ? \"HEAD\" : \"GET\";\n\t}\n\tif ((status === 301 || status === 302) && method === \"POST\") {\n\t\treturn \"GET\";\n\t}\n\treturn method;\n}\n\nexport function createFetchTool(cwd: string, options?: FetchToolOptions): AgentTool<typeof fetchSchema> {\n\tconst fetchImpl = options?.fetchImpl ?? fetch;\n\tconst permissionGuard = options?.permissionGuard;\n\tconst defaultTimeoutSeconds = options?.defaultTimeoutSeconds ?? DEFAULT_FETCH_TIMEOUT_SECONDS;\n\tconst defaultMaxBytes = options?.defaultMaxBytes ?? DEFAULT_FETCH_MAX_BYTES;\n\tconst defaultMaxRedirects = options?.defaultMaxRedirects ?? DEFAULT_FETCH_MAX_REDIRECTS;\n\n\treturn {\n\t\tname: \"fetch\",\n\t\tlabel: \"fetch\",\n\t\tdescription:\n\t\t\t\"Make HTTP requests (manual redirect handling). Defaults: method=GET, timeout=30s, max_bytes=262144, response_format=auto, max_redirects=5.\",\n\t\tparameters: fetchSchema,\n\t\texecute: async (_toolCallId: string, input: FetchToolInput, signal?: AbortSignal) => {\n\t\t\tconst method = normalizeMethod(input.method);\n\t\t\tconst allowedMethods = asSet(options?.resolveAllowedMethods?.() ?? FULL_FETCH_METHODS);\n\t\t\tif (!allowedMethods.has(method)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`HTTP method \"${method}\" is not allowed in the current profile. Allowed methods: ${Array.from(allowedMethods).join(\", \")}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif ((method === \"GET\" || method === \"HEAD\") && input.body !== undefined) {\n\t\t\t\tthrow new Error(`HTTP ${method} does not accept a request body in this tool.`);\n\t\t\t}\n\n\t\t\tlet requestUrl: string;\n\t\t\ttry {\n\t\t\t\trequestUrl = new URL(input.url).toString();\n\t\t\t} catch {\n\t\t\t\tthrow new Error(`Invalid URL: ${input.url}`);\n\t\t\t}\n\n\t\t\tconst timeoutSeconds = normalizePositiveInt(input.timeout, defaultTimeoutSeconds, \"timeout\");\n\t\t\tconst maxBytes = normalizePositiveInt(input.max_bytes, defaultMaxBytes, \"max_bytes\");\n\t\t\tconst maxRedirects = normalizeNonNegativeInt(input.max_redirects, defaultMaxRedirects, \"max_redirects\");\n\t\t\tconst requestedFormat: FetchResponseFormat = input.response_format ?? \"auto\";\n\n\t\t\tif (permissionGuard) {\n\t\t\t\tconst allowed = await permissionGuard({\n\t\t\t\t\ttoolName: \"fetch\",\n\t\t\t\t\tcwd,\n\t\t\t\t\tinput: {\n\t\t\t\t\t\turl: requestUrl,\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t\theaders: input.headers,\n\t\t\t\t\t\thasBody: input.body !== undefined,\n\t\t\t\t\t\tbodyLength: input.body?.length ?? 0,\n\t\t\t\t\t\ttimeoutSeconds,\n\t\t\t\t\t\tmaxBytes,\n\t\t\t\t\t\tmaxRedirects,\n\t\t\t\t\t\tresponseFormat: requestedFormat,\n\t\t\t\t\t},\n\t\t\t\t\tsummary: `${method} ${requestUrl}`,\n\t\t\t\t});\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new Error(\"Permission denied for fetch operation.\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst timeoutSignal = AbortSignal.timeout(timeoutSeconds * 1000);\n\t\t\tconst requestSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;\n\n\t\t\tlet currentUrl = requestUrl;\n\t\t\tlet currentMethod: FetchMethod = method;\n\t\t\tlet currentBody = input.body;\n\t\t\tlet redirectsFollowed = 0;\n\t\t\tlet response: Response | undefined;\n\n\t\t\twhile (true) {\n\t\t\t\tresponse = await fetchImpl(currentUrl, {\n\t\t\t\t\tmethod: currentMethod,\n\t\t\t\t\theaders: input.headers,\n\t\t\t\t\tbody: currentBody,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t\tsignal: requestSignal,\n\t\t\t\t});\n\n\t\t\t\tif (!isRedirectStatus(response.status)) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst location = response.headers.get(\"location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (redirectsFollowed >= maxRedirects) {\n\t\t\t\t\tthrow new Error(`Redirect limit exceeded (${maxRedirects}).`);\n\t\t\t\t}\n\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).toString();\n\t\t\t\tconst redirectedMethod = nextMethodAfterRedirect(response.status, currentMethod);\n\t\t\t\tif (redirectedMethod !== currentMethod) {\n\t\t\t\t\tcurrentBody = undefined;\n\t\t\t\t}\n\t\t\t\tcurrentMethod = redirectedMethod;\n\t\t\t\tredirectsFollowed++;\n\t\t\t}\n\n\t\t\tif (!response) {\n\t\t\t\tthrow new Error(\"No response received.\");\n\t\t\t}\n\n\t\t\tconst bodyResult = await readResponseBodyWithLimit(response, maxBytes);\n\t\t\tconst contentType = response.headers.get(\"content-type\");\n\t\t\tconst resolvedFormat: \"json\" | \"text\" =\n\t\t\t\trequestedFormat === \"auto\" ? (isJsonContentType(contentType) ? \"json\" : \"text\") : requestedFormat;\n\t\t\tconst rawText = bodyResult.buffer.toString(\"utf-8\");\n\n\t\t\tlet formattedBody: string;\n\t\t\tif (resolvedFormat === \"json\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst parsed = JSON.parse(rawText);\n\t\t\t\t\tformattedBody = JSON.stringify(parsed, null, 2);\n\t\t\t\t} catch (error: any) {\n\t\t\t\t\tthrow new Error(`Response body is not valid JSON: ${error?.message ?? \"parse failed\"}`);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tformattedBody = rawText;\n\t\t\t}\n\n\t\t\tconst summary = formatHttpSummary({\n\t\t\t\tmethod,\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\tfinalUrl: currentUrl,\n\t\t\t\tredirectsFollowed,\n\t\t\t\tbody: formattedBody.trimEnd(),\n\t\t\t});\n\n\t\t\tconst truncation = truncateHead(summary, {\n\t\t\t\tmaxBytes: Math.max(DEFAULT_MAX_BYTES, maxBytes + 1024),\n\t\t\t\tmaxLines: DEFAULT_MAX_LINES,\n\t\t\t});\n\t\t\tlet output = truncation.content;\n\t\t\tconst notices: string[] = [];\n\n\t\t\tif (bodyResult.truncated) {\n\t\t\t\tnotices.push(`response body truncated at ${formatSize(maxBytes)}`);\n\t\t\t}\n\t\t\tif (truncation.truncated) {\n\t\t\t\tnotices.push(\n\t\t\t\t\t`output truncated by ${truncation.truncatedBy === \"lines\" ? \"line\" : \"byte\"} limit (showing up to ${DEFAULT_MAX_LINES} lines)`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (notices.length > 0) {\n\t\t\t\toutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t}\n\n\t\t\tconst details: FetchToolDetails = {\n\t\t\t\tmethod,\n\t\t\t\trequestUrl,\n\t\t\t\tfinalUrl: currentUrl,\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\tredirectsFollowed,\n\t\t\t\tresponseFormat: resolvedFormat,\n\t\t\t\ttimeoutSeconds,\n\t\t\t\tmaxBytes,\n\t\t\t\tbodyBytesCaptured: bodyResult.buffer.length,\n\t\t\t\tbodyCaptureTruncated: bodyResult.truncated,\n\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\tdetails,\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport const fetchTool = createFetchTool(process.cwd());\n"]}
@@ -0,0 +1,54 @@
1
+ import type { AgentTool } from "@mariozechner/pi-agent-core";
2
+ import { type Static } from "@sinclair/typebox";
3
+ import type { ToolPermissionGuard } from "./permissions.js";
4
+ declare const fsOpsSchema: import("@sinclair/typebox").TObject<{
5
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"mkdir">, import("@sinclair/typebox").TLiteral<"move">, import("@sinclair/typebox").TLiteral<"copy">, import("@sinclair/typebox").TLiteral<"delete">]>;
6
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
7
+ from: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
8
+ to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
9
+ recursive: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
10
+ force: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
11
+ }>;
12
+ export type FsOpsToolInput = Static<typeof fsOpsSchema>;
13
+ export interface FsOpsToolDetails {
14
+ action: "mkdir" | "move" | "copy" | "delete";
15
+ resolvedPath?: string;
16
+ resolvedFrom?: string;
17
+ resolvedTo?: string;
18
+ recursive?: boolean;
19
+ force?: boolean;
20
+ exdevFallback?: boolean;
21
+ noop?: boolean;
22
+ }
23
+ interface FsOpsOperations {
24
+ mkdir(path: string, options: {
25
+ recursive: boolean;
26
+ }): Promise<void>;
27
+ rename(from: string, to: string): Promise<void>;
28
+ copy(from: string, to: string, options: {
29
+ recursive: boolean;
30
+ force: boolean;
31
+ }): Promise<void>;
32
+ remove(path: string, options: {
33
+ recursive: boolean;
34
+ force: boolean;
35
+ }): Promise<void>;
36
+ stat(path: string): Promise<{
37
+ isDirectory: () => boolean;
38
+ }>;
39
+ }
40
+ export interface FsOpsToolOptions {
41
+ operations?: FsOpsOperations;
42
+ permissionGuard?: ToolPermissionGuard;
43
+ }
44
+ export declare function createFsOpsTool(cwd: string, options?: FsOpsToolOptions): AgentTool<typeof fsOpsSchema>;
45
+ export declare const fsOpsTool: AgentTool<import("@sinclair/typebox").TObject<{
46
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"mkdir">, import("@sinclair/typebox").TLiteral<"move">, import("@sinclair/typebox").TLiteral<"copy">, import("@sinclair/typebox").TLiteral<"delete">]>;
47
+ path: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
48
+ from: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
49
+ to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
50
+ recursive: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
51
+ force: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
52
+ }>, any>;
53
+ export {};
54
+ //# sourceMappingURL=fs-ops.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-ops.d.ts","sourceRoot":"","sources":["../../../src/core/tools/fs-ops.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,QAAA,MAAM,WAAW;;;;;;;EAmBf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,UAAU,eAAe;IACxB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,OAAO,CAAA;KAAE,CAAC,CAAC;CAC5D;AAUD,MAAM,WAAW,gBAAgB;IAChC,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,eAAe,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAoBD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CAiLtG;AAED,eAAO,MAAM,SAAS;;;;;;;QAAiC,CAAC"}
@@ -0,0 +1,206 @@
1
+ import { cp as fsCopy, mkdir as fsMkdir, rename as fsRename, rm as fsRm, stat as fsStat } from "node:fs/promises";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { resolveToCwd } from "./path-utils.js";
4
+ const fsOpsSchema = Type.Object({
5
+ action: Type.Union([Type.Literal("mkdir"), Type.Literal("move"), Type.Literal("copy"), Type.Literal("delete")], {
6
+ description: "Filesystem operation: mkdir | move | copy | delete.",
7
+ }),
8
+ path: Type.Optional(Type.String({ description: "Target path for mkdir/delete actions." })),
9
+ from: Type.Optional(Type.String({ description: "Source path for move/copy actions." })),
10
+ to: Type.Optional(Type.String({ description: "Destination path for move/copy actions." })),
11
+ recursive: Type.Optional(Type.Boolean({
12
+ description: "Enable recursive behavior (required for deleting/copying directories). mkdir defaults to recursive=true.",
13
+ })),
14
+ force: Type.Optional(Type.Boolean({
15
+ description: "Allow replacement/no-op safety escapes. Required to overwrite destinations or ignore missing delete target.",
16
+ })),
17
+ });
18
+ const defaultOps = {
19
+ mkdir: (path, options) => fsMkdir(path, options).then(() => { }),
20
+ rename: (from, to) => fsRename(from, to),
21
+ copy: (from, to, options) => fsCopy(from, to, options).then(() => { }),
22
+ remove: (path, options) => fsRm(path, options).then(() => { }),
23
+ stat: (path) => fsStat(path),
24
+ };
25
+ function ensureNotAborted(signal) {
26
+ if (signal?.aborted) {
27
+ throw new Error("Operation aborted");
28
+ }
29
+ }
30
+ async function pathExists(ops, path) {
31
+ try {
32
+ await ops.stat(path);
33
+ return true;
34
+ }
35
+ catch (error) {
36
+ if (error?.code === "ENOENT") {
37
+ return false;
38
+ }
39
+ throw error;
40
+ }
41
+ }
42
+ export function createFsOpsTool(cwd, options) {
43
+ const ops = options?.operations ?? defaultOps;
44
+ const permissionGuard = options?.permissionGuard;
45
+ return {
46
+ name: "fs_ops",
47
+ label: "fs_ops",
48
+ description: "Structured filesystem mutations: mkdir, move, copy, delete. Uses explicit recursive/force safety flags for destructive operations.",
49
+ parameters: fsOpsSchema,
50
+ execute: async (_toolCallId, input, signal) => {
51
+ ensureNotAborted(signal);
52
+ const action = input.action;
53
+ const recursive = input.recursive ?? (action === "mkdir");
54
+ const force = input.force ?? false;
55
+ const summary = action === "move" || action === "copy"
56
+ ? `${action} ${input.from ?? "(missing from)"} -> ${input.to ?? "(missing to)"}`
57
+ : `${action} ${input.path ?? "(missing path)"}`;
58
+ if (permissionGuard) {
59
+ const allowed = await permissionGuard({
60
+ toolName: "fs_ops",
61
+ cwd,
62
+ input: {
63
+ action,
64
+ path: input.path,
65
+ from: input.from,
66
+ to: input.to,
67
+ recursive,
68
+ force,
69
+ },
70
+ summary,
71
+ });
72
+ if (!allowed) {
73
+ throw new Error("Permission denied for fs_ops operation.");
74
+ }
75
+ }
76
+ if (action === "mkdir") {
77
+ if (!input.path) {
78
+ throw new Error("fs_ops mkdir requires path.");
79
+ }
80
+ const resolvedPath = resolveToCwd(input.path, cwd);
81
+ await ops.mkdir(resolvedPath, { recursive });
82
+ return {
83
+ content: [{ type: "text", text: `Created directory: ${input.path}` }],
84
+ details: {
85
+ action,
86
+ resolvedPath,
87
+ recursive,
88
+ force,
89
+ },
90
+ };
91
+ }
92
+ if (action === "delete") {
93
+ if (!input.path) {
94
+ throw new Error("fs_ops delete requires path.");
95
+ }
96
+ const resolvedPath = resolveToCwd(input.path, cwd);
97
+ const exists = await pathExists(ops, resolvedPath);
98
+ if (!exists) {
99
+ if (!force) {
100
+ throw new Error(`Path not found: ${input.path}`);
101
+ }
102
+ return {
103
+ content: [{ type: "text", text: `Skipped delete (path missing): ${input.path}` }],
104
+ details: {
105
+ action,
106
+ resolvedPath,
107
+ recursive,
108
+ force,
109
+ noop: true,
110
+ },
111
+ };
112
+ }
113
+ const sourceStat = await ops.stat(resolvedPath);
114
+ if (sourceStat.isDirectory() && !recursive) {
115
+ throw new Error("Deleting a directory requires recursive=true.");
116
+ }
117
+ ensureNotAborted(signal);
118
+ await ops.remove(resolvedPath, {
119
+ recursive: sourceStat.isDirectory(),
120
+ force: false,
121
+ });
122
+ return {
123
+ content: [{ type: "text", text: `Deleted: ${input.path}` }],
124
+ details: {
125
+ action,
126
+ resolvedPath,
127
+ recursive,
128
+ force,
129
+ },
130
+ };
131
+ }
132
+ if (!input.from || !input.to) {
133
+ throw new Error(`fs_ops ${action} requires both from and to.`);
134
+ }
135
+ const resolvedFrom = resolveToCwd(input.from, cwd);
136
+ const resolvedTo = resolveToCwd(input.to, cwd);
137
+ const sourceExists = await pathExists(ops, resolvedFrom);
138
+ if (!sourceExists) {
139
+ throw new Error(`Source path not found: ${input.from}`);
140
+ }
141
+ const destinationExists = await pathExists(ops, resolvedTo);
142
+ if (destinationExists && !force) {
143
+ throw new Error(`Destination already exists: ${input.to}. Pass force=true to replace.`);
144
+ }
145
+ if (destinationExists && force) {
146
+ ensureNotAborted(signal);
147
+ await ops.remove(resolvedTo, { recursive: true, force: true });
148
+ }
149
+ const sourceStat = await ops.stat(resolvedFrom);
150
+ const sourceIsDirectory = sourceStat.isDirectory();
151
+ if (action === "copy" && sourceIsDirectory && !recursive) {
152
+ throw new Error("Copying a directory requires recursive=true.");
153
+ }
154
+ if (action === "copy") {
155
+ ensureNotAborted(signal);
156
+ await ops.copy(resolvedFrom, resolvedTo, {
157
+ recursive: sourceIsDirectory,
158
+ force,
159
+ });
160
+ return {
161
+ content: [{ type: "text", text: `Copied: ${input.from} -> ${input.to}` }],
162
+ details: {
163
+ action,
164
+ resolvedFrom,
165
+ resolvedTo,
166
+ recursive,
167
+ force,
168
+ },
169
+ };
170
+ }
171
+ let exdevFallback = false;
172
+ try {
173
+ ensureNotAborted(signal);
174
+ await ops.rename(resolvedFrom, resolvedTo);
175
+ }
176
+ catch (error) {
177
+ if (error?.code !== "EXDEV") {
178
+ throw error;
179
+ }
180
+ exdevFallback = true;
181
+ ensureNotAborted(signal);
182
+ await ops.copy(resolvedFrom, resolvedTo, {
183
+ recursive: sourceIsDirectory,
184
+ force: true,
185
+ });
186
+ await ops.remove(resolvedFrom, {
187
+ recursive: sourceIsDirectory,
188
+ force: false,
189
+ });
190
+ }
191
+ return {
192
+ content: [{ type: "text", text: `Moved: ${input.from} -> ${input.to}` }],
193
+ details: {
194
+ action,
195
+ resolvedFrom,
196
+ resolvedTo,
197
+ recursive,
198
+ force,
199
+ exdevFallback: exdevFallback || undefined,
200
+ },
201
+ };
202
+ },
203
+ };
204
+ }
205
+ export const fsOpsTool = createFsOpsTool(process.cwd());
206
+ //# sourceMappingURL=fs-ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-ops.js","sourceRoot":"","sources":["../../../src/core/tools/fs-ops.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,KAAK,IAAI,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAElH,OAAO,EAAe,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE;QAC/G,WAAW,EAAE,qDAAqD;KAClE,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC,CAAC;IAC1F,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC,CAAC;IACvF,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC,CAAC;IAC1F,SAAS,EAAE,IAAI,CAAC,QAAQ,CACvB,IAAI,CAAC,OAAO,CAAC;QACZ,WAAW,EACV,0GAA0G;KAC3G,CAAC,CACF;IACD,KAAK,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,OAAO,CAAC;QACZ,WAAW,EACV,6GAA6G;KAC9G,CAAC,CACF;CACD,CAAC,CAAC;AAuBH,MAAM,UAAU,GAAoB;IACnC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IAC/D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IACrE,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IAC7D,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;CAC5B,CAAC;AAOF,SAAS,gBAAgB,CAAC,MAAoB;IAC7C,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACtC,CAAC;AACF,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAoB,EAAE,IAAY;IAC3D,IAAI,CAAC;QACJ,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACrB,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,OAA0B;IACtE,MAAM,GAAG,GAAG,OAAO,EAAE,UAAU,IAAI,UAAU,CAAC;IAC9C,MAAM,eAAe,GAAG,OAAO,EAAE,eAAe,CAAC;IAEjD,OAAO;QACN,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EACV,oIAAoI;QACrI,UAAU,EAAE,WAAW;QACvB,OAAO,EAAE,KAAK,EAAE,WAAmB,EAAE,KAAqB,EAAE,MAAoB,EAAE,EAAE;YACnF,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACzB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC;YAEnC,MAAM,OAAO,GACZ,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM;gBACrC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,gBAAgB,OAAO,KAAK,CAAC,EAAE,IAAI,cAAc,EAAE;gBAChF,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;YAElD,IAAI,eAAe,EAAE,CAAC;gBACrB,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC;oBACrC,QAAQ,EAAE,QAAQ;oBAClB,GAAG;oBACH,KAAK,EAAE;wBACN,MAAM;wBACN,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,EAAE,EAAE,KAAK,CAAC,EAAE;wBACZ,SAAS;wBACT,KAAK;qBACL;oBACD,OAAO;iBACP,CAAC,CAAC;gBACH,IAAI,CAAC,OAAO,EAAE,CAAC;oBACd,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;gBAC5D,CAAC;YACF,CAAC;YAED,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;oBACjB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;gBAChD,CAAC;gBACD,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACnD,MAAM,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC7C,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBACrE,OAAO,EAAE;wBACR,MAAM;wBACN,YAAY;wBACZ,SAAS;wBACT,KAAK;qBACe;iBACrB,CAAC;YACH,CAAC;YAED,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;oBACjB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;gBACjD,CAAC;gBACD,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACnD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBACnD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACb,IAAI,CAAC,KAAK,EAAE,CAAC;wBACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;oBAClD,CAAC;oBACD,OAAO;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kCAAkC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;wBACjF,OAAO,EAAE;4BACR,MAAM;4BACN,YAAY;4BACZ,SAAS;4BACT,KAAK;4BACL,IAAI,EAAE,IAAI;yBACU;qBACrB,CAAC;gBACH,CAAC;gBAED,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBAChD,IAAI,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;oBAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;gBAClE,CAAC;gBAED,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE;oBAC9B,SAAS,EAAE,UAAU,CAAC,WAAW,EAAE;oBACnC,KAAK,EAAE,KAAK;iBACZ,CAAC,CAAC;gBAEH,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC3D,OAAO,EAAE;wBACR,MAAM;wBACN,YAAY;wBACZ,SAAS;wBACT,KAAK;qBACe;iBACrB,CAAC;YACH,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,UAAU,MAAM,6BAA6B,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACnD,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YAC/C,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;YACzD,IAAI,CAAC,YAAY,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,iBAAiB,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAC5D,IAAI,iBAAiB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACjC,MAAM,IAAI,KAAK,CAAC,+BAA+B,KAAK,CAAC,EAAE,+BAA+B,CAAC,CAAC;YACzF,CAAC;YACD,IAAI,iBAAiB,IAAI,KAAK,EAAE,CAAC;gBAChC,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAChD,MAAM,iBAAiB,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;YACnD,IAAI,MAAM,KAAK,MAAM,IAAI,iBAAiB,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC1D,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;YACjE,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACvB,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE;oBACxC,SAAS,EAAE,iBAAiB;oBAC5B,KAAK;iBACL,CAAC,CAAC;gBACH,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;oBACzE,OAAO,EAAE;wBACR,MAAM;wBACN,YAAY;wBACZ,UAAU;wBACV,SAAS;wBACT,KAAK;qBACe;iBACrB,CAAC;YACH,CAAC;YAED,IAAI,aAAa,GAAG,KAAK,CAAC;YAC1B,IAAI,CAAC;gBACJ,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAC5C,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBACrB,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC7B,MAAM,KAAK,CAAC;gBACb,CAAC;gBACD,aAAa,GAAG,IAAI,CAAC;gBACrB,gBAAgB,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE;oBACxC,SAAS,EAAE,iBAAiB;oBAC5B,KAAK,EAAE,IAAI;iBACX,CAAC,CAAC;gBACH,MAAM,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE;oBAC9B,SAAS,EAAE,iBAAiB;oBAC5B,KAAK,EAAE,KAAK;iBACZ,CAAC,CAAC;YACJ,CAAC;YAED,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;gBACxE,OAAO,EAAE;oBACR,MAAM;oBACN,YAAY;oBACZ,UAAU;oBACV,SAAS;oBACT,KAAK;oBACL,aAAa,EAAE,aAAa,IAAI,SAAS;iBACrB;aACrB,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC","sourcesContent":["import { cp as fsCopy, mkdir as fsMkdir, rename as fsRename, rm as fsRm, stat as fsStat } from \"node:fs/promises\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { resolveToCwd } from \"./path-utils.js\";\nimport type { ToolPermissionGuard } from \"./permissions.js\";\n\nconst fsOpsSchema = Type.Object({\n\taction: Type.Union([Type.Literal(\"mkdir\"), Type.Literal(\"move\"), Type.Literal(\"copy\"), Type.Literal(\"delete\")], {\n\t\tdescription: \"Filesystem operation: mkdir | move | copy | delete.\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Target path for mkdir/delete actions.\" })),\n\tfrom: Type.Optional(Type.String({ description: \"Source path for move/copy actions.\" })),\n\tto: Type.Optional(Type.String({ description: \"Destination path for move/copy actions.\" })),\n\trecursive: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Enable recursive behavior (required for deleting/copying directories). mkdir defaults to recursive=true.\",\n\t\t}),\n\t),\n\tforce: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Allow replacement/no-op safety escapes. Required to overwrite destinations or ignore missing delete target.\",\n\t\t}),\n\t),\n});\n\nexport type FsOpsToolInput = Static<typeof fsOpsSchema>;\n\nexport interface FsOpsToolDetails {\n\taction: \"mkdir\" | \"move\" | \"copy\" | \"delete\";\n\tresolvedPath?: string;\n\tresolvedFrom?: string;\n\tresolvedTo?: string;\n\trecursive?: boolean;\n\tforce?: boolean;\n\texdevFallback?: boolean;\n\tnoop?: boolean;\n}\n\ninterface FsOpsOperations {\n\tmkdir(path: string, options: { recursive: boolean }): Promise<void>;\n\trename(from: string, to: string): Promise<void>;\n\tcopy(from: string, to: string, options: { recursive: boolean; force: boolean }): Promise<void>;\n\tremove(path: string, options: { recursive: boolean; force: boolean }): Promise<void>;\n\tstat(path: string): Promise<{ isDirectory: () => boolean }>;\n}\n\nconst defaultOps: FsOpsOperations = {\n\tmkdir: (path, options) => fsMkdir(path, options).then(() => {}),\n\trename: (from, to) => fsRename(from, to),\n\tcopy: (from, to, options) => fsCopy(from, to, options).then(() => {}),\n\tremove: (path, options) => fsRm(path, options).then(() => {}),\n\tstat: (path) => fsStat(path),\n};\n\nexport interface FsOpsToolOptions {\n\toperations?: FsOpsOperations;\n\tpermissionGuard?: ToolPermissionGuard;\n}\n\nfunction ensureNotAborted(signal?: AbortSignal): void {\n\tif (signal?.aborted) {\n\t\tthrow new Error(\"Operation aborted\");\n\t}\n}\n\nasync function pathExists(ops: FsOpsOperations, path: string): Promise<boolean> {\n\ttry {\n\t\tawait ops.stat(path);\n\t\treturn true;\n\t} catch (error: any) {\n\t\tif (error?.code === \"ENOENT\") {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nexport function createFsOpsTool(cwd: string, options?: FsOpsToolOptions): AgentTool<typeof fsOpsSchema> {\n\tconst ops = options?.operations ?? defaultOps;\n\tconst permissionGuard = options?.permissionGuard;\n\n\treturn {\n\t\tname: \"fs_ops\",\n\t\tlabel: \"fs_ops\",\n\t\tdescription:\n\t\t\t\"Structured filesystem mutations: mkdir, move, copy, delete. Uses explicit recursive/force safety flags for destructive operations.\",\n\t\tparameters: fsOpsSchema,\n\t\texecute: async (_toolCallId: string, input: FsOpsToolInput, signal?: AbortSignal) => {\n\t\t\tensureNotAborted(signal);\n\t\t\tconst action = input.action;\n\t\t\tconst recursive = input.recursive ?? (action === \"mkdir\");\n\t\t\tconst force = input.force ?? false;\n\n\t\t\tconst summary =\n\t\t\t\taction === \"move\" || action === \"copy\"\n\t\t\t\t\t? `${action} ${input.from ?? \"(missing from)\"} -> ${input.to ?? \"(missing to)\"}`\n\t\t\t\t\t: `${action} ${input.path ?? \"(missing path)\"}`;\n\n\t\t\tif (permissionGuard) {\n\t\t\t\tconst allowed = await permissionGuard({\n\t\t\t\t\ttoolName: \"fs_ops\",\n\t\t\t\t\tcwd,\n\t\t\t\t\tinput: {\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tpath: input.path,\n\t\t\t\t\t\tfrom: input.from,\n\t\t\t\t\t\tto: input.to,\n\t\t\t\t\t\trecursive,\n\t\t\t\t\t\tforce,\n\t\t\t\t\t},\n\t\t\t\t\tsummary,\n\t\t\t\t});\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new Error(\"Permission denied for fs_ops operation.\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (action === \"mkdir\") {\n\t\t\t\tif (!input.path) {\n\t\t\t\t\tthrow new Error(\"fs_ops mkdir requires path.\");\n\t\t\t\t}\n\t\t\t\tconst resolvedPath = resolveToCwd(input.path, cwd);\n\t\t\t\tawait ops.mkdir(resolvedPath, { recursive });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Created directory: ${input.path}` }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tresolvedPath,\n\t\t\t\t\t\trecursive,\n\t\t\t\t\t\tforce,\n\t\t\t\t\t} as FsOpsToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (action === \"delete\") {\n\t\t\t\tif (!input.path) {\n\t\t\t\t\tthrow new Error(\"fs_ops delete requires path.\");\n\t\t\t\t}\n\t\t\t\tconst resolvedPath = resolveToCwd(input.path, cwd);\n\t\t\t\tconst exists = await pathExists(ops, resolvedPath);\n\t\t\t\tif (!exists) {\n\t\t\t\t\tif (!force) {\n\t\t\t\t\t\tthrow new Error(`Path not found: ${input.path}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Skipped delete (path missing): ${input.path}` }],\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\taction,\n\t\t\t\t\t\t\tresolvedPath,\n\t\t\t\t\t\t\trecursive,\n\t\t\t\t\t\t\tforce,\n\t\t\t\t\t\t\tnoop: true,\n\t\t\t\t\t\t} as FsOpsToolDetails,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst sourceStat = await ops.stat(resolvedPath);\n\t\t\t\tif (sourceStat.isDirectory() && !recursive) {\n\t\t\t\t\tthrow new Error(\"Deleting a directory requires recursive=true.\");\n\t\t\t\t}\n\n\t\t\t\tensureNotAborted(signal);\n\t\t\t\tawait ops.remove(resolvedPath, {\n\t\t\t\t\trecursive: sourceStat.isDirectory(),\n\t\t\t\t\tforce: false,\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Deleted: ${input.path}` }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tresolvedPath,\n\t\t\t\t\t\trecursive,\n\t\t\t\t\t\tforce,\n\t\t\t\t\t} as FsOpsToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (!input.from || !input.to) {\n\t\t\t\tthrow new Error(`fs_ops ${action} requires both from and to.`);\n\t\t\t}\n\n\t\t\tconst resolvedFrom = resolveToCwd(input.from, cwd);\n\t\t\tconst resolvedTo = resolveToCwd(input.to, cwd);\n\t\t\tconst sourceExists = await pathExists(ops, resolvedFrom);\n\t\t\tif (!sourceExists) {\n\t\t\t\tthrow new Error(`Source path not found: ${input.from}`);\n\t\t\t}\n\t\t\tconst destinationExists = await pathExists(ops, resolvedTo);\n\t\t\tif (destinationExists && !force) {\n\t\t\t\tthrow new Error(`Destination already exists: ${input.to}. Pass force=true to replace.`);\n\t\t\t}\n\t\t\tif (destinationExists && force) {\n\t\t\t\tensureNotAborted(signal);\n\t\t\t\tawait ops.remove(resolvedTo, { recursive: true, force: true });\n\t\t\t}\n\n\t\t\tconst sourceStat = await ops.stat(resolvedFrom);\n\t\t\tconst sourceIsDirectory = sourceStat.isDirectory();\n\t\t\tif (action === \"copy\" && sourceIsDirectory && !recursive) {\n\t\t\t\tthrow new Error(\"Copying a directory requires recursive=true.\");\n\t\t\t}\n\n\t\t\tif (action === \"copy\") {\n\t\t\t\tensureNotAborted(signal);\n\t\t\t\tawait ops.copy(resolvedFrom, resolvedTo, {\n\t\t\t\t\trecursive: sourceIsDirectory,\n\t\t\t\t\tforce,\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Copied: ${input.from} -> ${input.to}` }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\taction,\n\t\t\t\t\t\tresolvedFrom,\n\t\t\t\t\t\tresolvedTo,\n\t\t\t\t\t\trecursive,\n\t\t\t\t\t\tforce,\n\t\t\t\t\t} as FsOpsToolDetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tlet exdevFallback = false;\n\t\t\ttry {\n\t\t\t\tensureNotAborted(signal);\n\t\t\t\tawait ops.rename(resolvedFrom, resolvedTo);\n\t\t\t} catch (error: any) {\n\t\t\t\tif (error?.code !== \"EXDEV\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\texdevFallback = true;\n\t\t\t\tensureNotAborted(signal);\n\t\t\t\tawait ops.copy(resolvedFrom, resolvedTo, {\n\t\t\t\t\trecursive: sourceIsDirectory,\n\t\t\t\t\tforce: true,\n\t\t\t\t});\n\t\t\t\tawait ops.remove(resolvedFrom, {\n\t\t\t\t\trecursive: sourceIsDirectory,\n\t\t\t\t\tforce: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Moved: ${input.from} -> ${input.to}` }],\n\t\t\t\tdetails: {\n\t\t\t\t\taction,\n\t\t\t\t\tresolvedFrom,\n\t\t\t\t\tresolvedTo,\n\t\t\t\t\trecursive,\n\t\t\t\t\tforce,\n\t\t\t\t\texdevFallback: exdevFallback || undefined,\n\t\t\t\t} as FsOpsToolDetails,\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport const fsOpsTool = createFsOpsTool(process.cwd());\n"]}