@dexto/tools-filesystem 1.5.8 → 1.6.0

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 (83) hide show
  1. package/dist/directory-approval.cjs +94 -0
  2. package/dist/directory-approval.d.cts +22 -0
  3. package/dist/directory-approval.d.ts +20 -0
  4. package/dist/directory-approval.d.ts.map +1 -0
  5. package/dist/directory-approval.integration.test.cjs +303 -269
  6. package/dist/directory-approval.integration.test.d.ts +14 -2
  7. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  8. package/dist/directory-approval.integration.test.js +309 -270
  9. package/dist/directory-approval.js +59 -0
  10. package/dist/edit-file-tool.cjs +57 -90
  11. package/dist/edit-file-tool.d.cts +20 -3
  12. package/dist/edit-file-tool.d.ts +22 -9
  13. package/dist/edit-file-tool.d.ts.map +1 -0
  14. package/dist/edit-file-tool.js +53 -76
  15. package/dist/edit-file-tool.test.cjs +66 -29
  16. package/dist/edit-file-tool.test.d.ts +7 -2
  17. package/dist/edit-file-tool.test.d.ts.map +1 -0
  18. package/dist/edit-file-tool.test.js +66 -29
  19. package/dist/error-codes.d.ts +2 -3
  20. package/dist/error-codes.d.ts.map +1 -0
  21. package/dist/errors.d.ts +4 -7
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/file-tool-types.d.cts +7 -35
  24. package/dist/file-tool-types.d.ts +8 -40
  25. package/dist/file-tool-types.d.ts.map +1 -0
  26. package/dist/filesystem-service.cjs +18 -1
  27. package/dist/filesystem-service.d.cts +11 -6
  28. package/dist/filesystem-service.d.ts +14 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +18 -1
  31. package/dist/filesystem-service.test.cjs +10 -2
  32. package/dist/filesystem-service.test.d.ts +7 -2
  33. package/dist/filesystem-service.test.d.ts.map +1 -0
  34. package/dist/filesystem-service.test.js +10 -2
  35. package/dist/glob-files-tool.cjs +22 -47
  36. package/dist/glob-files-tool.d.cts +17 -3
  37. package/dist/glob-files-tool.d.ts +19 -9
  38. package/dist/glob-files-tool.d.ts.map +1 -0
  39. package/dist/glob-files-tool.js +23 -48
  40. package/dist/grep-content-tool.cjs +29 -46
  41. package/dist/grep-content-tool.d.cts +26 -3
  42. package/dist/grep-content-tool.d.ts +28 -9
  43. package/dist/grep-content-tool.d.ts.map +1 -0
  44. package/dist/grep-content-tool.js +30 -47
  45. package/dist/index.cjs +3 -3
  46. package/dist/index.d.cts +4 -2
  47. package/dist/index.d.ts +10 -5
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +2 -2
  50. package/dist/path-validator.d.cts +2 -2
  51. package/dist/path-validator.d.ts +6 -9
  52. package/dist/path-validator.d.ts.map +1 -0
  53. package/dist/path-validator.test.d.ts +7 -2
  54. package/dist/path-validator.test.d.ts.map +1 -0
  55. package/dist/read-file-tool.cjs +21 -60
  56. package/dist/read-file-tool.d.cts +17 -3
  57. package/dist/read-file-tool.d.ts +19 -9
  58. package/dist/read-file-tool.d.ts.map +1 -0
  59. package/dist/read-file-tool.js +22 -51
  60. package/dist/tool-factory-config.cjs +61 -0
  61. package/dist/{tool-provider.d.ts → tool-factory-config.d.cts} +9 -23
  62. package/dist/{tool-provider.d.cts → tool-factory-config.d.ts} +13 -30
  63. package/dist/tool-factory-config.d.ts.map +1 -0
  64. package/dist/tool-factory-config.js +36 -0
  65. package/dist/tool-factory.cjs +102 -0
  66. package/dist/tool-factory.d.cts +7 -0
  67. package/dist/tool-factory.d.ts +4 -0
  68. package/dist/tool-factory.d.ts.map +1 -0
  69. package/dist/tool-factory.js +81 -0
  70. package/dist/types.d.ts +17 -18
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/write-file-tool.cjs +45 -73
  73. package/dist/write-file-tool.d.cts +20 -3
  74. package/dist/write-file-tool.d.ts +22 -9
  75. package/dist/write-file-tool.d.ts.map +1 -0
  76. package/dist/write-file-tool.js +46 -68
  77. package/dist/write-file-tool.test.cjs +76 -32
  78. package/dist/write-file-tool.test.d.ts +7 -2
  79. package/dist/write-file-tool.test.d.ts.map +1 -0
  80. package/dist/write-file-tool.test.js +76 -32
  81. package/package.json +4 -3
  82. package/dist/tool-provider.cjs +0 -123
  83. package/dist/tool-provider.js +0 -99
package/dist/types.d.ts CHANGED
@@ -4,11 +4,11 @@
4
4
  * Types and interfaces for file system operations including reading, writing,
5
5
  * searching, and validation.
6
6
  */
7
- type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
7
+ export type BufferEncoding = 'ascii' | 'utf8' | 'utf-8' | 'utf16le' | 'ucs2' | 'ucs-2' | 'base64' | 'base64url' | 'latin1' | 'binary' | 'hex';
8
8
  /**
9
9
  * File content with metadata
10
10
  */
11
- interface FileContent {
11
+ export interface FileContent {
12
12
  content: string;
13
13
  lines: number;
14
14
  encoding: string;
@@ -19,7 +19,7 @@ interface FileContent {
19
19
  /**
20
20
  * Options for reading files
21
21
  */
22
- interface ReadFileOptions {
22
+ export interface ReadFileOptions {
23
23
  /** Maximum number of lines to read */
24
24
  limit?: number | undefined;
25
25
  /** Starting line number (1-based) */
@@ -30,7 +30,7 @@ interface ReadFileOptions {
30
30
  /**
31
31
  * File metadata for glob results
32
32
  */
33
- interface FileMetadata {
33
+ export interface FileMetadata {
34
34
  path: string;
35
35
  size: number;
36
36
  modified: Date;
@@ -39,7 +39,7 @@ interface FileMetadata {
39
39
  /**
40
40
  * Options for glob operations
41
41
  */
42
- interface GlobOptions {
42
+ export interface GlobOptions {
43
43
  /** Base directory to search from */
44
44
  cwd?: string | undefined;
45
45
  /** Maximum number of results */
@@ -50,7 +50,7 @@ interface GlobOptions {
50
50
  /**
51
51
  * Glob result
52
52
  */
53
- interface GlobResult {
53
+ export interface GlobResult {
54
54
  files: FileMetadata[];
55
55
  truncated: boolean;
56
56
  totalFound: number;
@@ -58,7 +58,7 @@ interface GlobResult {
58
58
  /**
59
59
  * Search match with context
60
60
  */
61
- interface SearchMatch {
61
+ export interface SearchMatch {
62
62
  file: string;
63
63
  lineNumber: number;
64
64
  line: string;
@@ -70,7 +70,7 @@ interface SearchMatch {
70
70
  /**
71
71
  * Options for content search (grep)
72
72
  */
73
- interface GrepOptions {
73
+ export interface GrepOptions {
74
74
  /** Base directory to search */
75
75
  path?: string | undefined;
76
76
  /** Glob pattern to filter files */
@@ -87,7 +87,7 @@ interface GrepOptions {
87
87
  /**
88
88
  * Search result
89
89
  */
90
- interface SearchResult {
90
+ export interface SearchResult {
91
91
  matches: SearchMatch[];
92
92
  totalMatches: number;
93
93
  truncated: boolean;
@@ -96,7 +96,7 @@ interface SearchResult {
96
96
  /**
97
97
  * Options for writing files
98
98
  */
99
- interface WriteFileOptions {
99
+ export interface WriteFileOptions {
100
100
  /** Create parent directories if they don't exist */
101
101
  createDirs?: boolean | undefined;
102
102
  /** File encoding (default: utf-8) */
@@ -107,7 +107,7 @@ interface WriteFileOptions {
107
107
  /**
108
108
  * Write result
109
109
  */
110
- interface WriteResult {
110
+ export interface WriteResult {
111
111
  success: boolean;
112
112
  path: string;
113
113
  bytesWritten: number;
@@ -118,7 +118,7 @@ interface WriteResult {
118
118
  /**
119
119
  * Edit operation
120
120
  */
121
- interface EditOperation {
121
+ export interface EditOperation {
122
122
  oldString: string;
123
123
  newString: string;
124
124
  replaceAll?: boolean | undefined;
@@ -126,7 +126,7 @@ interface EditOperation {
126
126
  /**
127
127
  * Options for editing files
128
128
  */
129
- interface EditFileOptions {
129
+ export interface EditFileOptions {
130
130
  /** Create backup before editing */
131
131
  backup?: boolean;
132
132
  /** File encoding */
@@ -135,7 +135,7 @@ interface EditFileOptions {
135
135
  /**
136
136
  * Edit result
137
137
  */
138
- interface EditResult {
138
+ export interface EditResult {
139
139
  success: boolean;
140
140
  path: string;
141
141
  changesCount: number;
@@ -148,7 +148,7 @@ interface EditResult {
148
148
  /**
149
149
  * Path validation result
150
150
  */
151
- interface PathValidation {
151
+ export interface PathValidation {
152
152
  isValid: boolean;
153
153
  error?: string;
154
154
  normalizedPath?: string;
@@ -156,7 +156,7 @@ interface PathValidation {
156
156
  /**
157
157
  * File system configuration
158
158
  */
159
- interface FileSystemConfig {
159
+ export interface FileSystemConfig {
160
160
  /** Allowed base paths */
161
161
  allowedPaths: string[];
162
162
  /** Blocked paths (relative to allowed paths) */
@@ -174,5 +174,4 @@ interface FileSystemConfig {
174
174
  /** Working directory for glob/grep operations (defaults to process.cwd()) */
175
175
  workingDirectory?: string | undefined;
176
176
  }
177
-
178
- export type { BufferEncoding, EditFileOptions, EditOperation, EditResult, FileContent, FileMetadata, FileSystemConfig, GlobOptions, GlobResult, GrepOptions, PathValidation, ReadFileOptions, SearchMatch, SearchResult, WriteFileOptions, WriteResult };
177
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,cAAc,GACpB,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,MAAM,GACN,OAAO,GACP,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,QAAQ,GACR,KAAK,CAAC;AAEZ;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,IAAI,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,4BAA4B;IAC5B,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,KAAK,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACL;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,8BAA8B;IAC9B,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,oDAAoD;IACpD,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,mCAAmC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,yDAAyD;IACzD,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,yBAAyB;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gDAAgD;IAChD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,wGAAwG;IACxG,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,mDAAmD;IACnD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC"}
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,33 +15,25 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var write_file_tool_exports = {};
30
20
  __export(write_file_tool_exports, {
31
21
  createWriteFileTool: () => createWriteFileTool
32
22
  });
33
23
  module.exports = __toCommonJS(write_file_tool_exports);
34
- var path = __toESM(require("node:path"), 1);
35
24
  var import_node_crypto = require("node:crypto");
36
25
  var import_zod = require("zod");
37
26
  var import_diff = require("diff");
38
27
  var import_core = require("@dexto/core");
39
28
  var import_error_codes = require("./error-codes.js");
29
+ var import_directory_approval = require("./directory-approval.js");
40
30
  const previewContentHashCache = /* @__PURE__ */ new Map();
41
31
  const FILE_NOT_EXISTS_MARKER = null;
42
32
  function computeContentHash(content) {
43
33
  return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
44
34
  }
45
35
  const WriteFileInputSchema = import_zod.z.object({
46
- file_path: import_zod.z.string().describe("Absolute path where the file should be written"),
36
+ file_path: import_zod.z.string().min(1).describe("Absolute path where the file should be written"),
47
37
  content: import_zod.z.string().describe("Content to write to the file"),
48
38
  create_dirs: import_zod.z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
49
39
  encoding: import_zod.z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
@@ -62,80 +52,51 @@ function generateDiffPreview(filePath, originalContent, newContent) {
62
52
  deletions
63
53
  };
64
54
  }
65
- function createWriteFileTool(options) {
66
- const { fileSystemService, directoryApproval } = options;
67
- let pendingApprovalParentDir;
68
- return {
55
+ function createWriteFileTool(getFileSystemService) {
56
+ return (0, import_core.defineTool)({
69
57
  id: "write_file",
58
+ displayName: "Write",
59
+ aliases: ["write"],
70
60
  description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
71
61
  inputSchema: WriteFileInputSchema,
72
- /**
73
- * Check if this write operation needs directory access approval.
74
- * Returns custom approval request if the file is outside allowed paths.
75
- */
76
- getApprovalOverride: async (args) => {
77
- const { file_path } = args;
78
- if (!file_path) return null;
79
- const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
80
- if (isAllowed) {
81
- return null;
82
- }
83
- if (directoryApproval?.isSessionApproved(file_path)) {
84
- return null;
85
- }
86
- const absolutePath = path.resolve(file_path);
87
- const parentDir = path.dirname(absolutePath);
88
- pendingApprovalParentDir = parentDir;
89
- return {
90
- type: import_core.ApprovalType.DIRECTORY_ACCESS,
91
- metadata: {
92
- path: absolutePath,
93
- parentDir,
94
- operation: "write",
95
- toolName: "write_file"
96
- }
97
- };
98
- },
99
- /**
100
- * Handle approved directory access - remember the directory for session
101
- */
102
- onApprovalGranted: (response) => {
103
- if (!directoryApproval || !pendingApprovalParentDir) return;
104
- const data = response.data;
105
- const rememberDirectory = data?.rememberDirectory ?? false;
106
- directoryApproval.addApproved(
107
- pendingApprovalParentDir,
108
- rememberDirectory ? "session" : "once"
109
- );
110
- pendingApprovalParentDir = void 0;
111
- },
62
+ ...(0, import_directory_approval.createDirectoryAccessApprovalHandlers)({
63
+ toolName: "write_file",
64
+ operation: "write",
65
+ getFileSystemService,
66
+ resolvePaths: (input, fileSystemService) => (0, import_directory_approval.resolveFilePath)(fileSystemService.getWorkingDirectory(), input.file_path)
67
+ }),
112
68
  /**
113
69
  * Generate preview for approval UI - shows diff or file creation info
114
70
  * Stores content hash for change detection in execute phase.
115
71
  */
116
- generatePreview: async (input, context) => {
72
+ async generatePreview(input, context) {
117
73
  const { file_path, content } = input;
74
+ const resolvedFileSystemService = await getFileSystemService(context);
75
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
76
+ resolvedFileSystemService.getWorkingDirectory(),
77
+ file_path
78
+ );
118
79
  try {
119
- const originalFile = await fileSystemService.readFile(file_path);
80
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
120
81
  const originalContent = originalFile.content;
121
- if (context?.toolCallId) {
82
+ if (context.toolCallId) {
122
83
  previewContentHashCache.set(
123
84
  context.toolCallId,
124
85
  computeContentHash(originalContent)
125
86
  );
126
87
  }
127
- return generateDiffPreview(file_path, originalContent, content);
88
+ return generateDiffPreview(resolvedPath, originalContent, content);
128
89
  } catch (error) {
129
90
  if (error instanceof import_core.DextoRuntimeError && error.code === import_error_codes.FileSystemErrorCode.FILE_NOT_FOUND) {
130
- if (context?.toolCallId) {
91
+ if (context.toolCallId) {
131
92
  previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
132
93
  }
133
94
  const lineCount = content.split("\n").length;
134
95
  const preview = {
135
96
  type: "file",
136
- path: file_path,
97
+ path: resolvedPath,
137
98
  operation: "create",
138
- size: content.length,
99
+ size: Buffer.byteLength(content, "utf8"),
139
100
  lineCount,
140
101
  content
141
102
  // Include content for approval preview
@@ -145,12 +106,17 @@ function createWriteFileTool(options) {
145
106
  throw error;
146
107
  }
147
108
  },
148
- execute: async (input, context) => {
109
+ async execute(input, context) {
110
+ const resolvedFileSystemService = await getFileSystemService(context);
149
111
  const { file_path, content, create_dirs, encoding } = input;
112
+ const { path: resolvedPath } = (0, import_directory_approval.resolveFilePath)(
113
+ resolvedFileSystemService.getWorkingDirectory(),
114
+ file_path
115
+ );
150
116
  let originalContent = null;
151
117
  let fileExistsNow = false;
152
118
  try {
153
- const originalFile = await fileSystemService.readFile(file_path);
119
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
154
120
  originalContent = originalFile.content;
155
121
  fileExistsNow = true;
156
122
  } catch (error) {
@@ -161,24 +127,30 @@ function createWriteFileTool(options) {
161
127
  throw error;
162
128
  }
163
129
  }
164
- if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
130
+ if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
165
131
  const expectedHash = previewContentHashCache.get(context.toolCallId);
166
132
  previewContentHashCache.delete(context.toolCallId);
167
133
  if (expectedHash === FILE_NOT_EXISTS_MARKER) {
168
134
  if (fileExistsNow) {
169
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
135
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
170
136
  }
171
137
  } else if (expectedHash !== null) {
172
138
  if (!fileExistsNow) {
173
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
139
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
140
+ }
141
+ if (originalContent === null) {
142
+ throw import_core.ToolError.executionFailed(
143
+ "write_file",
144
+ "Expected original file content when fileExistsNow is true"
145
+ );
174
146
  }
175
147
  const currentHash = computeContentHash(originalContent);
176
148
  if (expectedHash !== currentHash) {
177
- throw import_core.ToolError.fileModifiedSincePreview("write_file", file_path);
149
+ throw import_core.ToolError.fileModifiedSincePreview("write_file", resolvedPath);
178
150
  }
179
151
  }
180
152
  }
181
- const result = await fileSystemService.writeFile(file_path, content, {
153
+ const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
182
154
  createDirs: create_dirs,
183
155
  encoding
184
156
  });
@@ -187,13 +159,13 @@ function createWriteFileTool(options) {
187
159
  const lineCount = content.split("\n").length;
188
160
  _display = {
189
161
  type: "file",
190
- path: file_path,
162
+ path: resolvedPath,
191
163
  operation: "create",
192
164
  size: result.bytesWritten,
193
165
  lineCount
194
166
  };
195
167
  } else {
196
- _display = generateDiffPreview(file_path, originalContent, content);
168
+ _display = generateDiffPreview(resolvedPath, originalContent, content);
197
169
  }
198
170
  return {
199
171
  success: result.success,
@@ -203,7 +175,7 @@ function createWriteFileTool(options) {
203
175
  _display
204
176
  };
205
177
  }
206
- };
178
+ });
207
179
  }
208
180
  // Annotate the CommonJS export names for ESM import in node:
209
181
  0 && (module.exports = {
@@ -1,5 +1,6 @@
1
- import { InternalTool } from '@dexto/core';
2
- import { FileToolOptions } from './file-tool-types.cjs';
1
+ import { z } from 'zod';
2
+ import { Tool } from '@dexto/core';
3
+ import { FileSystemServiceGetter } from './file-tool-types.cjs';
3
4
  import './filesystem-service.cjs';
4
5
  import './types.cjs';
5
6
 
@@ -9,9 +10,25 @@ import './types.cjs';
9
10
  * Internal tool for writing content to files (requires approval)
10
11
  */
11
12
 
13
+ declare const WriteFileInputSchema: z.ZodObject<{
14
+ file_path: z.ZodString;
15
+ content: z.ZodString;
16
+ create_dirs: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
17
+ encoding: z.ZodDefault<z.ZodOptional<z.ZodEnum<["utf-8", "ascii", "latin1", "utf16le"]>>>;
18
+ }, "strict", z.ZodTypeAny, {
19
+ encoding: "ascii" | "utf-8" | "utf16le" | "latin1";
20
+ content: string;
21
+ file_path: string;
22
+ create_dirs: boolean;
23
+ }, {
24
+ content: string;
25
+ file_path: string;
26
+ encoding?: "ascii" | "utf-8" | "utf16le" | "latin1" | undefined;
27
+ create_dirs?: boolean | undefined;
28
+ }>;
12
29
  /**
13
30
  * Create the write_file internal tool with directory approval support
14
31
  */
15
- declare function createWriteFileTool(options: FileToolOptions): InternalTool;
32
+ declare function createWriteFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof WriteFileInputSchema>;
16
33
 
17
34
  export { createWriteFileTool };
@@ -1,17 +1,30 @@
1
- import { InternalTool } from '@dexto/core';
2
- import { FileToolOptions } from './file-tool-types.js';
3
- import './filesystem-service.js';
4
- import './types.js';
5
-
6
1
  /**
7
2
  * Write File Tool
8
3
  *
9
4
  * Internal tool for writing content to files (requires approval)
10
5
  */
11
-
6
+ import { z } from 'zod';
7
+ import type { Tool } from '@dexto/core';
8
+ import type { FileSystemServiceGetter } from './file-tool-types.js';
9
+ declare const WriteFileInputSchema: z.ZodObject<{
10
+ file_path: z.ZodString;
11
+ content: z.ZodString;
12
+ create_dirs: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
13
+ encoding: z.ZodDefault<z.ZodOptional<z.ZodEnum<["utf-8", "ascii", "latin1", "utf16le"]>>>;
14
+ }, "strict", z.ZodTypeAny, {
15
+ encoding: "ascii" | "utf-8" | "utf16le" | "latin1";
16
+ content: string;
17
+ file_path: string;
18
+ create_dirs: boolean;
19
+ }, {
20
+ content: string;
21
+ file_path: string;
22
+ encoding?: "ascii" | "utf-8" | "utf16le" | "latin1" | undefined;
23
+ create_dirs?: boolean | undefined;
24
+ }>;
12
25
  /**
13
26
  * Create the write_file internal tool with directory approval support
14
27
  */
15
- declare function createWriteFileTool(options: FileToolOptions): InternalTool;
16
-
17
- export { createWriteFileTool };
28
+ export declare function createWriteFileTool(getFileSystemService: FileSystemServiceGetter): Tool<typeof WriteFileInputSchema>;
29
+ export {};
30
+ //# sourceMappingURL=write-file-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file-tool.d.ts","sourceRoot":"","sources":["../src/write-file-tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAG9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAuBpE,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;EAeb,CAAC;AAyBd;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,oBAAoB,EAAE,uBAAuB,GAC9C,IAAI,CAAC,OAAO,oBAAoB,CAAC,CA0KnC"}
@@ -1,20 +1,16 @@
1
- import * as path from "node:path";
2
1
  import { createHash } from "node:crypto";
3
2
  import { z } from "zod";
4
3
  import { createPatch } from "diff";
5
- import {
6
- DextoRuntimeError,
7
- ApprovalType,
8
- ToolError
9
- } from "@dexto/core";
4
+ import { DextoRuntimeError, ToolError, defineTool } from "@dexto/core";
10
5
  import { FileSystemErrorCode } from "./error-codes.js";
6
+ import { createDirectoryAccessApprovalHandlers, resolveFilePath } from "./directory-approval.js";
11
7
  const previewContentHashCache = /* @__PURE__ */ new Map();
12
8
  const FILE_NOT_EXISTS_MARKER = null;
13
9
  function computeContentHash(content) {
14
10
  return createHash("sha256").update(content, "utf8").digest("hex");
15
11
  }
16
12
  const WriteFileInputSchema = z.object({
17
- file_path: z.string().describe("Absolute path where the file should be written"),
13
+ file_path: z.string().min(1).describe("Absolute path where the file should be written"),
18
14
  content: z.string().describe("Content to write to the file"),
19
15
  create_dirs: z.boolean().optional().default(false).describe("Create parent directories if they don't exist (default: false)"),
20
16
  encoding: z.enum(["utf-8", "ascii", "latin1", "utf16le"]).optional().default("utf-8").describe("File encoding (default: utf-8)")
@@ -33,80 +29,51 @@ function generateDiffPreview(filePath, originalContent, newContent) {
33
29
  deletions
34
30
  };
35
31
  }
36
- function createWriteFileTool(options) {
37
- const { fileSystemService, directoryApproval } = options;
38
- let pendingApprovalParentDir;
39
- return {
32
+ function createWriteFileTool(getFileSystemService) {
33
+ return defineTool({
40
34
  id: "write_file",
35
+ displayName: "Write",
36
+ aliases: ["write"],
41
37
  description: "Write content to a file. Creates a new file or overwrites existing file. Automatically creates backup of existing files before overwriting. Use create_dirs to create parent directories. Requires approval for all write operations. Returns success status, path, bytes written, and backup path if applicable.",
42
38
  inputSchema: WriteFileInputSchema,
43
- /**
44
- * Check if this write operation needs directory access approval.
45
- * Returns custom approval request if the file is outside allowed paths.
46
- */
47
- getApprovalOverride: async (args) => {
48
- const { file_path } = args;
49
- if (!file_path) return null;
50
- const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
51
- if (isAllowed) {
52
- return null;
53
- }
54
- if (directoryApproval?.isSessionApproved(file_path)) {
55
- return null;
56
- }
57
- const absolutePath = path.resolve(file_path);
58
- const parentDir = path.dirname(absolutePath);
59
- pendingApprovalParentDir = parentDir;
60
- return {
61
- type: ApprovalType.DIRECTORY_ACCESS,
62
- metadata: {
63
- path: absolutePath,
64
- parentDir,
65
- operation: "write",
66
- toolName: "write_file"
67
- }
68
- };
69
- },
70
- /**
71
- * Handle approved directory access - remember the directory for session
72
- */
73
- onApprovalGranted: (response) => {
74
- if (!directoryApproval || !pendingApprovalParentDir) return;
75
- const data = response.data;
76
- const rememberDirectory = data?.rememberDirectory ?? false;
77
- directoryApproval.addApproved(
78
- pendingApprovalParentDir,
79
- rememberDirectory ? "session" : "once"
80
- );
81
- pendingApprovalParentDir = void 0;
82
- },
39
+ ...createDirectoryAccessApprovalHandlers({
40
+ toolName: "write_file",
41
+ operation: "write",
42
+ getFileSystemService,
43
+ resolvePaths: (input, fileSystemService) => resolveFilePath(fileSystemService.getWorkingDirectory(), input.file_path)
44
+ }),
83
45
  /**
84
46
  * Generate preview for approval UI - shows diff or file creation info
85
47
  * Stores content hash for change detection in execute phase.
86
48
  */
87
- generatePreview: async (input, context) => {
49
+ async generatePreview(input, context) {
88
50
  const { file_path, content } = input;
51
+ const resolvedFileSystemService = await getFileSystemService(context);
52
+ const { path: resolvedPath } = resolveFilePath(
53
+ resolvedFileSystemService.getWorkingDirectory(),
54
+ file_path
55
+ );
89
56
  try {
90
- const originalFile = await fileSystemService.readFile(file_path);
57
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
91
58
  const originalContent = originalFile.content;
92
- if (context?.toolCallId) {
59
+ if (context.toolCallId) {
93
60
  previewContentHashCache.set(
94
61
  context.toolCallId,
95
62
  computeContentHash(originalContent)
96
63
  );
97
64
  }
98
- return generateDiffPreview(file_path, originalContent, content);
65
+ return generateDiffPreview(resolvedPath, originalContent, content);
99
66
  } catch (error) {
100
67
  if (error instanceof DextoRuntimeError && error.code === FileSystemErrorCode.FILE_NOT_FOUND) {
101
- if (context?.toolCallId) {
68
+ if (context.toolCallId) {
102
69
  previewContentHashCache.set(context.toolCallId, FILE_NOT_EXISTS_MARKER);
103
70
  }
104
71
  const lineCount = content.split("\n").length;
105
72
  const preview = {
106
73
  type: "file",
107
- path: file_path,
74
+ path: resolvedPath,
108
75
  operation: "create",
109
- size: content.length,
76
+ size: Buffer.byteLength(content, "utf8"),
110
77
  lineCount,
111
78
  content
112
79
  // Include content for approval preview
@@ -116,12 +83,17 @@ function createWriteFileTool(options) {
116
83
  throw error;
117
84
  }
118
85
  },
119
- execute: async (input, context) => {
86
+ async execute(input, context) {
87
+ const resolvedFileSystemService = await getFileSystemService(context);
120
88
  const { file_path, content, create_dirs, encoding } = input;
89
+ const { path: resolvedPath } = resolveFilePath(
90
+ resolvedFileSystemService.getWorkingDirectory(),
91
+ file_path
92
+ );
121
93
  let originalContent = null;
122
94
  let fileExistsNow = false;
123
95
  try {
124
- const originalFile = await fileSystemService.readFile(file_path);
96
+ const originalFile = await resolvedFileSystemService.readFile(resolvedPath);
125
97
  originalContent = originalFile.content;
126
98
  fileExistsNow = true;
127
99
  } catch (error) {
@@ -132,24 +104,30 @@ function createWriteFileTool(options) {
132
104
  throw error;
133
105
  }
134
106
  }
135
- if (context?.toolCallId && previewContentHashCache.has(context.toolCallId)) {
107
+ if (context.toolCallId && previewContentHashCache.has(context.toolCallId)) {
136
108
  const expectedHash = previewContentHashCache.get(context.toolCallId);
137
109
  previewContentHashCache.delete(context.toolCallId);
138
110
  if (expectedHash === FILE_NOT_EXISTS_MARKER) {
139
111
  if (fileExistsNow) {
140
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
112
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
141
113
  }
142
114
  } else if (expectedHash !== null) {
143
115
  if (!fileExistsNow) {
144
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
116
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
117
+ }
118
+ if (originalContent === null) {
119
+ throw ToolError.executionFailed(
120
+ "write_file",
121
+ "Expected original file content when fileExistsNow is true"
122
+ );
145
123
  }
146
124
  const currentHash = computeContentHash(originalContent);
147
125
  if (expectedHash !== currentHash) {
148
- throw ToolError.fileModifiedSincePreview("write_file", file_path);
126
+ throw ToolError.fileModifiedSincePreview("write_file", resolvedPath);
149
127
  }
150
128
  }
151
129
  }
152
- const result = await fileSystemService.writeFile(file_path, content, {
130
+ const result = await resolvedFileSystemService.writeFile(resolvedPath, content, {
153
131
  createDirs: create_dirs,
154
132
  encoding
155
133
  });
@@ -158,13 +136,13 @@ function createWriteFileTool(options) {
158
136
  const lineCount = content.split("\n").length;
159
137
  _display = {
160
138
  type: "file",
161
- path: file_path,
139
+ path: resolvedPath,
162
140
  operation: "create",
163
141
  size: result.bytesWritten,
164
142
  lineCount
165
143
  };
166
144
  } else {
167
- _display = generateDiffPreview(file_path, originalContent, content);
145
+ _display = generateDiffPreview(resolvedPath, originalContent, content);
168
146
  }
169
147
  return {
170
148
  success: result.success,
@@ -174,7 +152,7 @@ function createWriteFileTool(options) {
174
152
  _display
175
153
  };
176
154
  }
177
- };
155
+ });
178
156
  }
179
157
  export {
180
158
  createWriteFileTool