@dexto/tools-filesystem 1.5.8 → 1.6.1

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 (95) hide show
  1. package/dist/directory-approval.cjs +98 -0
  2. package/dist/directory-approval.d.ts +24 -0
  3. package/dist/directory-approval.d.ts.map +1 -0
  4. package/dist/directory-approval.integration.test.cjs +175 -390
  5. package/dist/directory-approval.integration.test.d.ts +14 -2
  6. package/dist/directory-approval.integration.test.d.ts.map +1 -0
  7. package/dist/directory-approval.integration.test.js +178 -390
  8. package/dist/directory-approval.js +63 -0
  9. package/dist/edit-file-tool.cjs +109 -120
  10. package/dist/edit-file-tool.d.ts +22 -9
  11. package/dist/edit-file-tool.d.ts.map +1 -0
  12. package/dist/edit-file-tool.js +116 -110
  13. package/dist/edit-file-tool.test.cjs +109 -29
  14. package/dist/edit-file-tool.test.d.ts +7 -2
  15. package/dist/edit-file-tool.test.d.ts.map +1 -0
  16. package/dist/edit-file-tool.test.js +109 -29
  17. package/dist/error-codes.cjs +4 -0
  18. package/dist/error-codes.d.ts +6 -3
  19. package/dist/error-codes.d.ts.map +1 -0
  20. package/dist/error-codes.js +4 -0
  21. package/dist/errors.cjs +48 -0
  22. package/dist/errors.d.ts +20 -7
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +48 -0
  25. package/dist/file-tool-types.d.ts +8 -40
  26. package/dist/file-tool-types.d.ts.map +1 -0
  27. package/dist/filesystem-service.cjs +325 -10
  28. package/dist/filesystem-service.d.ts +41 -12
  29. package/dist/filesystem-service.d.ts.map +1 -0
  30. package/dist/filesystem-service.js +326 -11
  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 +32 -46
  36. package/dist/glob-files-tool.d.ts +19 -9
  37. package/dist/glob-files-tool.d.ts.map +1 -0
  38. package/dist/glob-files-tool.js +33 -47
  39. package/dist/grep-content-tool.cjs +40 -45
  40. package/dist/grep-content-tool.d.ts +28 -9
  41. package/dist/grep-content-tool.d.ts.map +1 -0
  42. package/dist/grep-content-tool.js +41 -46
  43. package/dist/index.cjs +6 -3
  44. package/dist/index.d.cts +852 -14
  45. package/dist/index.d.ts +11 -5
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +4 -2
  48. package/dist/path-validator.cjs +28 -2
  49. package/dist/path-validator.d.ts +20 -9
  50. package/dist/path-validator.d.ts.map +1 -0
  51. package/dist/path-validator.js +28 -2
  52. package/dist/path-validator.test.d.ts +7 -2
  53. package/dist/path-validator.test.d.ts.map +1 -0
  54. package/dist/read-file-tool.cjs +26 -59
  55. package/dist/read-file-tool.d.ts +19 -9
  56. package/dist/read-file-tool.d.ts.map +1 -0
  57. package/dist/read-file-tool.js +27 -50
  58. package/dist/tool-factory-config.cjs +61 -0
  59. package/dist/{tool-provider.d.ts → tool-factory-config.d.ts} +13 -30
  60. package/dist/tool-factory-config.d.ts.map +1 -0
  61. package/dist/tool-factory-config.js +36 -0
  62. package/dist/tool-factory.cjs +123 -0
  63. package/dist/tool-factory.d.ts +4 -0
  64. package/dist/tool-factory.d.ts.map +1 -0
  65. package/dist/tool-factory.js +102 -0
  66. package/dist/types.d.ts +82 -18
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/write-file-tool.cjs +93 -99
  69. package/dist/write-file-tool.d.ts +22 -9
  70. package/dist/write-file-tool.d.ts.map +1 -0
  71. package/dist/write-file-tool.js +97 -91
  72. package/dist/write-file-tool.test.cjs +139 -33
  73. package/dist/write-file-tool.test.d.ts +7 -2
  74. package/dist/write-file-tool.test.d.ts.map +1 -0
  75. package/dist/write-file-tool.test.js +139 -33
  76. package/package.json +5 -4
  77. package/dist/directory-approval.integration.test.d.cts +0 -2
  78. package/dist/edit-file-tool.d.cts +0 -17
  79. package/dist/edit-file-tool.test.d.cts +0 -2
  80. package/dist/error-codes.d.cts +0 -32
  81. package/dist/errors.d.cts +0 -112
  82. package/dist/file-tool-types.d.cts +0 -46
  83. package/dist/filesystem-service.d.cts +0 -112
  84. package/dist/filesystem-service.test.d.cts +0 -2
  85. package/dist/glob-files-tool.d.cts +0 -17
  86. package/dist/grep-content-tool.d.cts +0 -17
  87. package/dist/path-validator.d.cts +0 -97
  88. package/dist/path-validator.test.d.cts +0 -2
  89. package/dist/read-file-tool.d.cts +0 -17
  90. package/dist/tool-provider.cjs +0 -123
  91. package/dist/tool-provider.d.cts +0 -77
  92. package/dist/tool-provider.js +0 -99
  93. package/dist/types.d.cts +0 -178
  94. package/dist/write-file-tool.d.cts +0 -17
  95. package/dist/write-file-tool.test.d.cts +0 -2
@@ -41,16 +41,19 @@ var import_errors = require("./errors.js");
41
41
  const DEFAULT_ENCODING = "utf-8";
42
42
  const DEFAULT_MAX_RESULTS = 1e3;
43
43
  const DEFAULT_MAX_SEARCH_RESULTS = 100;
44
+ const DEFAULT_MAX_LIST_RESULTS = 5e3;
45
+ const DEFAULT_LIST_CONCURRENCY = 16;
44
46
  class FileSystemService {
45
47
  config;
46
48
  pathValidator;
47
49
  initialized = false;
48
50
  initPromise = null;
49
51
  logger;
52
+ directoryApprovalChecker;
50
53
  /**
51
54
  * Create a new FileSystemService with validated configuration.
52
55
  *
53
- * @param config - Fully-validated configuration from provider schema.
56
+ * @param config - Fully-validated configuration from the factory schema.
54
57
  * All required fields have values, defaults already applied.
55
58
  * @param logger - Logger instance for this service
56
59
  */
@@ -124,8 +127,24 @@ class FileSystemService {
124
127
  * @param checker Function that returns true if path is in an approved directory
125
128
  */
126
129
  setDirectoryApprovalChecker(checker) {
130
+ this.directoryApprovalChecker = checker;
127
131
  this.pathValidator.setDirectoryApprovalChecker(checker);
128
132
  }
133
+ /**
134
+ * Update the working directory at runtime (e.g., when workspace changes).
135
+ * Rebuilds the PathValidator so allowed/blocked path roots are recalculated.
136
+ */
137
+ setWorkingDirectory(workingDirectory) {
138
+ const normalized = workingDirectory?.trim();
139
+ if (!normalized) return;
140
+ if (this.config.workingDirectory === normalized) return;
141
+ this.config = { ...this.config, workingDirectory: normalized };
142
+ this.pathValidator = new import_path_validator.PathValidator(this.config, this.logger);
143
+ if (this.directoryApprovalChecker) {
144
+ this.pathValidator.setDirectoryApprovalChecker(this.directoryApprovalChecker);
145
+ }
146
+ this.logger.info(`FileSystemService working directory set to ${normalized}`);
147
+ }
129
148
  /**
130
149
  * Check if a file path is within the configured allowed paths (config only).
131
150
  * This is used by file tools to determine if directory approval is needed.
@@ -136,16 +155,14 @@ class FileSystemService {
136
155
  async isPathWithinConfigAllowed(filePath) {
137
156
  return this.pathValidator.isPathWithinAllowed(filePath);
138
157
  }
139
- /**
140
- * Read a file with validation and size limits
141
- */
142
- async readFile(filePath, options = {}) {
143
- await this.ensureInitialized();
144
- const validation = await this.pathValidator.validatePath(filePath);
158
+ async validateReadPath(filePath, mode) {
159
+ const validation = mode === "toolPreview" ? await this.pathValidator.validatePathForPreview(filePath) : await this.pathValidator.validatePath(filePath);
145
160
  if (!validation.isValid || !validation.normalizedPath) {
146
161
  throw import_errors.FileSystemError.invalidPath(filePath, validation.error || "Unknown error");
147
162
  }
148
- const normalizedPath = validation.normalizedPath;
163
+ return validation.normalizedPath;
164
+ }
165
+ async readNormalizedFile(normalizedPath, options = {}) {
149
166
  try {
150
167
  const stats = await fs.stat(normalizedPath);
151
168
  if (!stats.isFile()) {
@@ -159,12 +176,18 @@ class FileSystemService {
159
176
  );
160
177
  }
161
178
  } catch (error) {
179
+ if (error instanceof import_core.DextoRuntimeError && error.scope === "filesystem") {
180
+ throw error;
181
+ }
162
182
  if (error.code === "ENOENT") {
163
183
  throw import_errors.FileSystemError.fileNotFound(normalizedPath);
164
184
  }
165
185
  if (error.code === "EACCES") {
166
186
  throw import_errors.FileSystemError.permissionDenied(normalizedPath, "read");
167
187
  }
188
+ if (error instanceof import_core.DextoRuntimeError) {
189
+ throw error;
190
+ }
168
191
  throw import_errors.FileSystemError.readFailed(
169
192
  normalizedPath,
170
193
  error instanceof Error ? error.message : String(error)
@@ -186,20 +209,44 @@ class FileSystemService {
186
209
  } else {
187
210
  selectedLines = lines;
188
211
  }
212
+ const returnedContent = selectedLines.join("\n");
189
213
  return {
190
- content: selectedLines.join("\n"),
214
+ content: returnedContent,
191
215
  lines: selectedLines.length,
192
216
  encoding,
193
217
  truncated,
194
- size: Buffer.byteLength(content, encoding)
218
+ size: Buffer.byteLength(returnedContent, encoding)
195
219
  };
196
220
  } catch (error) {
221
+ if (error instanceof import_core.DextoRuntimeError && error.scope === "filesystem") {
222
+ throw error;
223
+ }
197
224
  throw import_errors.FileSystemError.readFailed(
198
225
  normalizedPath,
199
226
  error instanceof Error ? error.message : String(error)
200
227
  );
201
228
  }
202
229
  }
230
+ /**
231
+ * Read a file with validation and size limits
232
+ */
233
+ async readFile(filePath, options = {}) {
234
+ await this.ensureInitialized();
235
+ const normalizedPath = await this.validateReadPath(filePath, "execute");
236
+ return await this.readNormalizedFile(normalizedPath, options);
237
+ }
238
+ /**
239
+ * Preview-only file read that bypasses config-allowed roots.
240
+ *
241
+ * This is intended for UI previews (diffs, create previews) shown BEFORE a user
242
+ * confirms directory access for the tool call. The returned content is UI-only
243
+ * and should not be forwarded to the LLM.
244
+ */
245
+ async readFileForToolPreview(filePath, options = {}) {
246
+ await this.ensureInitialized();
247
+ const normalizedPath = await this.validateReadPath(filePath, "toolPreview");
248
+ return await this.readNormalizedFile(normalizedPath, options);
249
+ }
203
250
  /**
204
251
  * Find files matching a glob pattern
205
252
  */
@@ -262,6 +309,274 @@ class FileSystemService {
262
309
  );
263
310
  }
264
311
  }
312
+ /**
313
+ * List contents of a directory (non-recursive)
314
+ */
315
+ async listDirectory(dirPath, options = {}) {
316
+ await this.ensureInitialized();
317
+ const validation = await this.pathValidator.validatePath(dirPath);
318
+ if (!validation.isValid || !validation.normalizedPath) {
319
+ throw import_errors.FileSystemError.invalidPath(dirPath, validation.error || "Unknown error");
320
+ }
321
+ const normalizedPath = validation.normalizedPath;
322
+ try {
323
+ const stats = await fs.stat(normalizedPath);
324
+ if (!stats.isDirectory()) {
325
+ throw import_errors.FileSystemError.invalidPath(normalizedPath, "Path is not a directory");
326
+ }
327
+ } catch (error) {
328
+ if (error instanceof import_core.DextoRuntimeError && error.scope === "filesystem") {
329
+ throw error;
330
+ }
331
+ if (error.code === "ENOENT") {
332
+ throw import_errors.FileSystemError.directoryNotFound(normalizedPath);
333
+ }
334
+ if (error.code === "EACCES") {
335
+ throw import_errors.FileSystemError.permissionDenied(normalizedPath, "read");
336
+ }
337
+ throw import_errors.FileSystemError.listFailed(
338
+ normalizedPath,
339
+ error instanceof Error ? error.message : String(error)
340
+ );
341
+ }
342
+ const includeHidden = options.includeHidden ?? true;
343
+ const includeMetadata = options.includeMetadata !== false;
344
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_LIST_RESULTS;
345
+ try {
346
+ const dirEntries = await fs.readdir(normalizedPath, { withFileTypes: true });
347
+ const candidates = dirEntries.filter(
348
+ (entry) => includeHidden || !entry.name.startsWith(".")
349
+ );
350
+ const concurrency = DEFAULT_LIST_CONCURRENCY;
351
+ const validatedEntries = await this.mapWithConcurrency(
352
+ candidates,
353
+ concurrency,
354
+ async (entry) => {
355
+ const entryPath = path.join(normalizedPath, entry.name);
356
+ const entryValidation = await this.pathValidator.validatePath(entryPath);
357
+ if (!entryValidation.isValid || !entryValidation.normalizedPath) {
358
+ return null;
359
+ }
360
+ return {
361
+ entry,
362
+ normalizedPath: entryValidation.normalizedPath
363
+ };
364
+ }
365
+ );
366
+ const validEntries = validatedEntries.filter(Boolean);
367
+ if (maxEntries <= 0) {
368
+ return {
369
+ path: normalizedPath,
370
+ entries: [],
371
+ truncated: validEntries.length > 0,
372
+ totalEntries: validEntries.length
373
+ };
374
+ }
375
+ if (!includeMetadata) {
376
+ const entries2 = validEntries.slice(0, maxEntries).map((entry) => ({
377
+ name: entry.entry.name,
378
+ path: entry.normalizedPath,
379
+ isDirectory: entry.entry.isDirectory(),
380
+ size: 0,
381
+ modified: /* @__PURE__ */ new Date()
382
+ }));
383
+ return {
384
+ path: normalizedPath,
385
+ entries: entries2,
386
+ truncated: validEntries.length > maxEntries,
387
+ totalEntries: validEntries.length
388
+ };
389
+ }
390
+ const metadataEntries = await this.mapWithConcurrency(
391
+ validEntries,
392
+ concurrency,
393
+ async (entry) => {
394
+ try {
395
+ const stat = await fs.stat(entry.normalizedPath);
396
+ return {
397
+ name: entry.entry.name,
398
+ path: entry.normalizedPath,
399
+ isDirectory: entry.entry.isDirectory(),
400
+ size: stat.size,
401
+ modified: stat.mtime
402
+ };
403
+ } catch {
404
+ return null;
405
+ }
406
+ }
407
+ );
408
+ const entries = [];
409
+ let successfulStats = 0;
410
+ let cutoffIndex = -1;
411
+ for (let index = 0; index < metadataEntries.length; index += 1) {
412
+ const entry = metadataEntries[index];
413
+ if (!entry) {
414
+ continue;
415
+ }
416
+ successfulStats += 1;
417
+ if (entries.length < maxEntries) {
418
+ entries.push(entry);
419
+ }
420
+ if (successfulStats === maxEntries) {
421
+ cutoffIndex = index;
422
+ }
423
+ }
424
+ const remainingSuccessful = cutoffIndex >= 0 ? metadataEntries.slice(cutoffIndex + 1).filter((entry) => entry !== null).length : 0;
425
+ const totalEntries = successfulStats < maxEntries ? successfulStats : maxEntries + remainingSuccessful;
426
+ return {
427
+ path: normalizedPath,
428
+ entries,
429
+ truncated: totalEntries > maxEntries,
430
+ totalEntries
431
+ };
432
+ } catch (error) {
433
+ throw import_errors.FileSystemError.listFailed(
434
+ normalizedPath,
435
+ error instanceof Error ? error.message : String(error)
436
+ );
437
+ }
438
+ }
439
+ async mapWithConcurrency(items, limit, mapper) {
440
+ if (items.length === 0) {
441
+ return [];
442
+ }
443
+ const results = new Array(items.length);
444
+ let nextIndex = 0;
445
+ const workerCount = Math.min(Math.max(1, limit), items.length);
446
+ const workers = Array.from({ length: workerCount }, async () => {
447
+ while (true) {
448
+ const current = nextIndex++;
449
+ if (current >= items.length) {
450
+ return;
451
+ }
452
+ const item = items[current];
453
+ if (item === void 0) {
454
+ continue;
455
+ }
456
+ results[current] = await mapper(item, current);
457
+ }
458
+ });
459
+ await Promise.all(workers);
460
+ return results;
461
+ }
462
+ /**
463
+ * Create a directory
464
+ */
465
+ async createDirectory(dirPath, options = {}) {
466
+ await this.ensureInitialized();
467
+ const validation = await this.pathValidator.validatePath(dirPath);
468
+ if (!validation.isValid || !validation.normalizedPath) {
469
+ throw import_errors.FileSystemError.invalidPath(dirPath, validation.error || "Unknown error");
470
+ }
471
+ const normalizedPath = validation.normalizedPath;
472
+ const recursive = options.recursive ?? false;
473
+ try {
474
+ const firstCreated = await fs.mkdir(normalizedPath, { recursive });
475
+ const created = recursive ? typeof firstCreated === "string" : true;
476
+ return { path: normalizedPath, created };
477
+ } catch (error) {
478
+ const code = error.code;
479
+ if (code === "EEXIST") {
480
+ try {
481
+ const stat = await fs.stat(normalizedPath);
482
+ if (stat.isDirectory()) {
483
+ return { path: normalizedPath, created: false };
484
+ }
485
+ } catch {
486
+ }
487
+ }
488
+ if (code === "EACCES" || code === "EPERM") {
489
+ throw import_errors.FileSystemError.permissionDenied(normalizedPath, "create directory");
490
+ }
491
+ throw import_errors.FileSystemError.createDirFailed(
492
+ normalizedPath,
493
+ error instanceof Error ? error.message : String(error)
494
+ );
495
+ }
496
+ }
497
+ /**
498
+ * Delete a file or directory
499
+ */
500
+ async deletePath(targetPath, options = {}) {
501
+ await this.ensureInitialized();
502
+ const validation = await this.pathValidator.validatePath(targetPath);
503
+ if (!validation.isValid || !validation.normalizedPath) {
504
+ throw import_errors.FileSystemError.invalidPath(targetPath, validation.error || "Unknown error");
505
+ }
506
+ const normalizedPath = validation.normalizedPath;
507
+ try {
508
+ await fs.rm(normalizedPath, { recursive: options.recursive ?? false, force: false });
509
+ return { path: normalizedPath, deleted: true };
510
+ } catch (error) {
511
+ const code = error.code;
512
+ if (code === "ENOENT") {
513
+ throw import_errors.FileSystemError.fileNotFound(normalizedPath);
514
+ }
515
+ if (code === "EACCES" || code === "EPERM") {
516
+ throw import_errors.FileSystemError.permissionDenied(normalizedPath, "delete");
517
+ }
518
+ throw import_errors.FileSystemError.deleteFailed(
519
+ normalizedPath,
520
+ error instanceof Error ? error.message : String(error)
521
+ );
522
+ }
523
+ }
524
+ /**
525
+ * Rename or move a file or directory
526
+ */
527
+ async renamePath(fromPath, toPath) {
528
+ await this.ensureInitialized();
529
+ const fromValidation = await this.pathValidator.validatePath(fromPath);
530
+ if (!fromValidation.isValid || !fromValidation.normalizedPath) {
531
+ throw import_errors.FileSystemError.invalidPath(fromPath, fromValidation.error || "Unknown error");
532
+ }
533
+ const toValidation = await this.pathValidator.validatePath(toPath);
534
+ if (!toValidation.isValid || !toValidation.normalizedPath) {
535
+ throw import_errors.FileSystemError.invalidPath(toPath, toValidation.error || "Unknown error");
536
+ }
537
+ const normalizedFrom = fromValidation.normalizedPath;
538
+ const normalizedTo = toValidation.normalizedPath;
539
+ if (normalizedFrom === normalizedTo) {
540
+ return { from: normalizedFrom, to: normalizedTo };
541
+ }
542
+ try {
543
+ await fs.access(normalizedTo);
544
+ throw import_errors.FileSystemError.renameFailed(
545
+ normalizedFrom,
546
+ `Target already exists: ${normalizedTo}`
547
+ );
548
+ } catch (error) {
549
+ const code = error.code;
550
+ if (!code) {
551
+ throw error;
552
+ }
553
+ if (code === "ENOENT") {
554
+ } else if (code === "EACCES" || code === "EPERM") {
555
+ throw import_errors.FileSystemError.permissionDenied(normalizedTo, "rename");
556
+ } else {
557
+ throw import_errors.FileSystemError.renameFailed(
558
+ normalizedFrom,
559
+ error instanceof Error ? error.message : String(error)
560
+ );
561
+ }
562
+ }
563
+ try {
564
+ await fs.rename(normalizedFrom, normalizedTo);
565
+ return { from: normalizedFrom, to: normalizedTo };
566
+ } catch (error) {
567
+ const code = error.code;
568
+ if (code === "ENOENT") {
569
+ throw import_errors.FileSystemError.fileNotFound(normalizedFrom);
570
+ }
571
+ if (code === "EACCES" || code === "EPERM") {
572
+ throw import_errors.FileSystemError.permissionDenied(normalizedFrom, "rename");
573
+ }
574
+ throw import_errors.FileSystemError.renameFailed(
575
+ normalizedFrom,
576
+ error instanceof Error ? error.message : String(error)
577
+ );
578
+ }
579
+ }
265
580
  /**
266
581
  * Search for content in files (grep-like functionality)
267
582
  */
@@ -1,36 +1,34 @@
1
- import { IDextoLogger } from '@dexto/core';
2
- import { FileSystemConfig, ReadFileOptions, FileContent, GlobOptions, GlobResult, GrepOptions, SearchResult, WriteFileOptions, WriteResult, EditOperation, EditFileOptions, EditResult } from './types.js';
3
-
4
1
  /**
5
2
  * FileSystem Service
6
3
  *
7
4
  * Secure file system operations for Dexto internal tools
8
5
  */
9
-
6
+ import { Logger } from '@dexto/core';
7
+ import { FileSystemConfig, FileContent, ReadFileOptions, GlobOptions, GlobResult, GrepOptions, SearchResult, WriteFileOptions, WriteResult, EditFileOptions, EditResult, EditOperation, ListDirectoryOptions, ListDirectoryResult, CreateDirectoryOptions, CreateDirectoryResult, DeletePathOptions, DeletePathResult, RenamePathResult } from './types.js';
10
8
  /**
11
9
  * FileSystemService - Handles all file system operations with security checks
12
10
  *
13
- * This service receives fully-validated configuration from the FileSystem Tools Provider.
14
- * All defaults have been applied by the provider's schema, so the service trusts the config
11
+ * This service receives fully-validated configuration from the FileSystem Tools Factory.
12
+ * All defaults have been applied by the factory's schema, so the service trusts the config
15
13
  * and uses it as-is without any fallback logic.
16
14
  *
17
- * TODO: Add tests for this class
18
15
  * TODO: instantiate only when internal file tools are enabled to avoid file dependencies which won't work in serverless
19
16
  */
20
- declare class FileSystemService {
17
+ export declare class FileSystemService {
21
18
  private config;
22
19
  private pathValidator;
23
20
  private initialized;
24
21
  private initPromise;
25
22
  private logger;
23
+ private directoryApprovalChecker?;
26
24
  /**
27
25
  * Create a new FileSystemService with validated configuration.
28
26
  *
29
- * @param config - Fully-validated configuration from provider schema.
27
+ * @param config - Fully-validated configuration from the factory schema.
30
28
  * All required fields have values, defaults already applied.
31
29
  * @param logger - Logger instance for this service
32
30
  */
33
- constructor(config: FileSystemConfig, logger: IDextoLogger);
31
+ constructor(config: FileSystemConfig, logger: Logger);
34
32
  /**
35
33
  * Get backup directory path (context-aware with optional override)
36
34
  * TODO: Migrate to explicit configuration via CLI enrichment layer (per-agent paths)
@@ -63,6 +61,11 @@ declare class FileSystemService {
63
61
  * @param checker Function that returns true if path is in an approved directory
64
62
  */
65
63
  setDirectoryApprovalChecker(checker: (filePath: string) => boolean): void;
64
+ /**
65
+ * Update the working directory at runtime (e.g., when workspace changes).
66
+ * Rebuilds the PathValidator so allowed/blocked path roots are recalculated.
67
+ */
68
+ setWorkingDirectory(workingDirectory: string): void;
66
69
  /**
67
70
  * Check if a file path is within the configured allowed paths (config only).
68
71
  * This is used by file tools to determine if directory approval is needed.
@@ -71,14 +74,41 @@ declare class FileSystemService {
71
74
  * @returns true if the path is within config-allowed paths, false otherwise
72
75
  */
73
76
  isPathWithinConfigAllowed(filePath: string): Promise<boolean>;
77
+ private validateReadPath;
78
+ private readNormalizedFile;
74
79
  /**
75
80
  * Read a file with validation and size limits
76
81
  */
77
82
  readFile(filePath: string, options?: ReadFileOptions): Promise<FileContent>;
83
+ /**
84
+ * Preview-only file read that bypasses config-allowed roots.
85
+ *
86
+ * This is intended for UI previews (diffs, create previews) shown BEFORE a user
87
+ * confirms directory access for the tool call. The returned content is UI-only
88
+ * and should not be forwarded to the LLM.
89
+ */
90
+ readFileForToolPreview(filePath: string, options?: ReadFileOptions): Promise<FileContent>;
78
91
  /**
79
92
  * Find files matching a glob pattern
80
93
  */
81
94
  globFiles(pattern: string, options?: GlobOptions): Promise<GlobResult>;
95
+ /**
96
+ * List contents of a directory (non-recursive)
97
+ */
98
+ listDirectory(dirPath: string, options?: ListDirectoryOptions): Promise<ListDirectoryResult>;
99
+ private mapWithConcurrency;
100
+ /**
101
+ * Create a directory
102
+ */
103
+ createDirectory(dirPath: string, options?: CreateDirectoryOptions): Promise<CreateDirectoryResult>;
104
+ /**
105
+ * Delete a file or directory
106
+ */
107
+ deletePath(targetPath: string, options?: DeletePathOptions): Promise<DeletePathResult>;
108
+ /**
109
+ * Rename or move a file or directory
110
+ */
111
+ renamePath(fromPath: string, toPath: string): Promise<RenamePathResult>;
82
112
  /**
83
113
  * Search for content in files (grep-like functionality)
84
114
  */
@@ -108,5 +138,4 @@ declare class FileSystemService {
108
138
  */
109
139
  isPathAllowed(filePath: string): Promise<boolean>;
110
140
  }
111
-
112
- export { FileSystemService };
141
+ //# sourceMappingURL=filesystem-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-service.d.ts","sourceRoot":"","sources":["../src/filesystem-service.ts"],"names":[],"mappings":"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"}