@wonderwhy-er/desktop-commander 0.2.42 → 0.2.43

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.
@@ -1,113 +1,74 @@
1
- import { distance } from 'fastest-levenshtein';
2
1
  import { capture } from '../utils/capture.js';
2
+ import { Worker } from 'worker_threads';
3
+ // Re-export so existing callers keep importing from this module.
4
+ export { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearchCore.js';
5
+ /** Abort fuzzy search in the worker after this many ms to avoid unbounded CPU burn. */
6
+ export const FUZZY_SEARCH_TIMEOUT_MS = 30000;
3
7
  /**
4
- * Recursively finds the closest match to a query string within text using fuzzy matching
5
- * @param text The text to search within
6
- * @param query The query string to find
7
- * @param start Start index in the text (default: 0)
8
- * @param end End index in the text (default: text.length)
9
- * @param parentDistance Best distance found so far (default: Infinity)
10
- * @returns Object with start and end indices, matched value, and Levenshtein distance
8
+ * Inline worker entry: imports the dependency-free core module (passed as
9
+ * moduleUrl) and runs the search off the main thread. The core module is
10
+ * deliberately a leaf importing this module (or anything app-level) from the
11
+ * worker would boot the whole server per search. Kept as an eval'd snippet so
12
+ * the worker needs no separate file to ship alongside the compiled output.
11
13
  */
12
- export function recursiveFuzzyIndexOf(text, query, start = 0, end = null, parentDistance = Infinity, depth = 0) {
13
- // For debugging and performance tracking purposes
14
- if (depth === 0) {
15
- const startTime = performance.now();
16
- const result = recursiveFuzzyIndexOf(text, query, start, end, parentDistance, depth + 1);
17
- const executionTime = performance.now() - startTime;
18
- // Capture detailed metrics for the recursive search for in-depth analysis
19
- capture('fuzzy_search_recursive_metrics', {
20
- execution_time_ms: executionTime,
21
- text_length: text.length,
22
- query_length: query.length,
23
- result_distance: result.distance
24
- });
25
- return result;
26
- }
27
- if (end === null)
28
- end = text.length;
29
- // For small text segments, use iterative approach
30
- if (end - start <= 2 * query.length) {
31
- return iterativeReduction(text, query, start, end, parentDistance);
32
- }
33
- let midPoint = start + Math.floor((end - start) / 2);
34
- let leftEnd = Math.min(end, midPoint + query.length); // Include query length to cover overlaps
35
- let rightStart = Math.max(start, midPoint - query.length); // Include query length to cover overlaps
36
- // Calculate distance for current segments
37
- let leftDistance = distance(text.substring(start, leftEnd), query);
38
- let rightDistance = distance(text.substring(rightStart, end), query);
39
- let bestDistance = Math.min(leftDistance, parentDistance, rightDistance);
40
- // If parent distance is already the best, use iterative approach
41
- if (parentDistance === bestDistance) {
42
- return iterativeReduction(text, query, start, end, parentDistance);
43
- }
44
- // Recursively search the better half
45
- if (leftDistance < rightDistance) {
46
- return recursiveFuzzyIndexOf(text, query, start, leftEnd, bestDistance, depth + 1);
47
- }
48
- else {
49
- return recursiveFuzzyIndexOf(text, query, rightStart, end, bestDistance, depth + 1);
50
- }
51
- }
14
+ const WORKER_CODE = `
15
+ const { workerData, parentPort } = require('worker_threads');
16
+ import(workerData.moduleUrl)
17
+ .then((m) => {
18
+ parentPort.postMessage({ ok: true, ...m.runFuzzySearch(workerData.text, workerData.query) });
19
+ })
20
+ .catch((err) => {
21
+ parentPort.postMessage({ ok: false, error: String(err && err.stack || err) });
22
+ });
23
+ `;
24
+ const CORE_MODULE_URL = new URL('./fuzzySearchCore.js', import.meta.url).href;
52
25
  /**
53
- * Iteratively refines the best match by reducing the search area
54
- * @param text The text to search within
55
- * @param query The query string to find
56
- * @param start Start index in the text
57
- * @param end End index in the text
58
- * @param parentDistance Best distance found so far
59
- * @returns Object with start and end indices, matched value, and Levenshtein distance
26
+ * Runs the fuzzy search in a Worker thread so the main MCP event loop stays
27
+ * responsive to pings and other tool calls during heavy scans. Rejects if the
28
+ * scan exceeds timeoutMs, terminating the worker so it doesn't linger in the
29
+ * background. Search metrics come back with the result and are captured here,
30
+ * on the main thread, where the client identity is initialized.
60
31
  */
61
- function iterativeReduction(text, query, start, end, parentDistance) {
62
- const startTime = performance.now();
63
- let iterations = 0;
64
- let bestDistance = parentDistance;
65
- let bestStart = start;
66
- let bestEnd = end;
67
- // Improve start position
68
- let nextDistance = distance(text.substring(bestStart + 1, bestEnd), query);
69
- while (nextDistance < bestDistance) {
70
- bestDistance = nextDistance;
71
- bestStart++;
72
- const smallerString = text.substring(bestStart + 1, bestEnd);
73
- nextDistance = distance(smallerString, query);
74
- iterations++;
75
- }
76
- // Improve end position
77
- nextDistance = distance(text.substring(bestStart, bestEnd - 1), query);
78
- while (nextDistance < bestDistance) {
79
- bestDistance = nextDistance;
80
- bestEnd--;
81
- const smallerString = text.substring(bestStart, bestEnd - 1);
82
- nextDistance = distance(smallerString, query);
83
- iterations++;
84
- }
85
- const executionTime = performance.now() - startTime;
86
- // Capture metrics for the iterative refinement phase
87
- capture('fuzzy_search_iterative_metrics', {
88
- execution_time_ms: executionTime,
89
- iterations: iterations,
90
- segment_length: end - start,
91
- query_length: query.length,
92
- final_distance: bestDistance
32
+ export function runFuzzySearchInWorker(text, query, timeoutMs = FUZZY_SEARCH_TIMEOUT_MS) {
33
+ return new Promise((resolve, reject) => {
34
+ const worker = new Worker(WORKER_CODE, { eval: true, workerData: { moduleUrl: CORE_MODULE_URL, text, query } });
35
+ // Never let a scan keep the server process alive during shutdown.
36
+ worker.unref();
37
+ const timer = setTimeout(() => {
38
+ worker.terminate();
39
+ reject(new Error(`Fuzzy search timed out after ${timeoutMs}ms`));
40
+ }, timeoutMs);
41
+ timer.unref();
42
+ worker.on('message', (msg) => {
43
+ clearTimeout(timer);
44
+ if (msg.ok) {
45
+ captureFuzzySearchMetrics(msg.metrics);
46
+ resolve(msg.result);
47
+ }
48
+ else {
49
+ reject(new Error(`Fuzzy search worker failed: ${msg.error}`));
50
+ }
51
+ // Don't let the worker wind down on its own; the answer is already
52
+ // here, and a lingering worker holds its copy of the file text.
53
+ // The promise is settled, so the exit-code rejection below is a no-op.
54
+ worker.terminate();
55
+ });
56
+ worker.on('error', (err) => {
57
+ clearTimeout(timer);
58
+ reject(err);
59
+ });
60
+ worker.on('exit', (code) => {
61
+ clearTimeout(timer);
62
+ if (code !== 0) {
63
+ reject(new Error(`Fuzzy search worker exited with code ${code}`));
64
+ }
65
+ });
93
66
  });
94
- return {
95
- start: bestStart,
96
- end: bestEnd,
97
- value: text.substring(bestStart, bestEnd),
98
- distance: bestDistance
99
- };
100
67
  }
101
- /**
102
- * Calculates the similarity ratio between two strings
103
- * @param a First string
104
- * @param b Second string
105
- * @returns Similarity ratio (0-1)
106
- */
107
- export function getSimilarityRatio(a, b) {
108
- const maxLength = Math.max(a.length, b.length);
109
- if (maxLength === 0)
110
- return 1; // Both strings are empty
111
- const levenshteinDistance = distance(a, b);
112
- return 1 - (levenshteinDistance / maxLength);
68
+ /** Same telemetry events the search used to emit inline, now sent from the main thread. */
69
+ function captureFuzzySearchMetrics(metrics) {
70
+ capture('fuzzy_search_recursive_metrics', metrics.recursive);
71
+ if (metrics.iterative) {
72
+ capture('fuzzy_search_iterative_metrics', metrics.iterative);
73
+ }
113
74
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pure fuzzy-search core, kept free of app imports on purpose: it runs inside
3
+ * the worker thread spawned by runFuzzySearchInWorker (fuzzySearch.ts), and
4
+ * anything imported here is loaded per worker. Telemetry is returned as data
5
+ * and captured on the main thread, which has the real client identity.
6
+ */
7
+ export interface FuzzyMatch {
8
+ start: number;
9
+ end: number;
10
+ value: string;
11
+ distance: number;
12
+ }
13
+ export interface FuzzySearchMetrics {
14
+ recursive: {
15
+ execution_time_ms: number;
16
+ text_length: number;
17
+ query_length: number;
18
+ result_distance: number;
19
+ };
20
+ iterative: {
21
+ execution_time_ms: number;
22
+ iterations: number;
23
+ segment_length: number;
24
+ query_length: number;
25
+ final_distance: number;
26
+ } | null;
27
+ }
28
+ /**
29
+ * Runs a full fuzzy search and returns the match together with the timing
30
+ * metrics that used to be captured inline.
31
+ */
32
+ export declare function runFuzzySearch(text: string, query: string): {
33
+ result: FuzzyMatch;
34
+ metrics: FuzzySearchMetrics;
35
+ };
36
+ /**
37
+ * Recursively finds the closest match to a query string within text using fuzzy matching
38
+ * @param text The text to search within
39
+ * @param query The query string to find
40
+ * @param start Start index in the text (default: 0)
41
+ * @param end End index in the text (default: text.length)
42
+ * @param parentDistance Best distance found so far (default: Infinity)
43
+ * @returns Object with start and end indices, matched value, and Levenshtein distance
44
+ */
45
+ export declare function recursiveFuzzyIndexOf(text: string, query: string, start?: number, end?: number | null, parentDistance?: number): FuzzyMatch;
46
+ /**
47
+ * Calculates the similarity ratio between two strings
48
+ * @param a First string
49
+ * @param b Second string
50
+ * @returns Similarity ratio (0-1)
51
+ */
52
+ export declare function getSimilarityRatio(a: string, b: string): number;
@@ -0,0 +1,125 @@
1
+ import { distance } from 'fastest-levenshtein';
2
+ // Set by iterativeReduction during a search (exactly one terminal call per
3
+ // search) and collected by runFuzzySearch. Single-threaded per context, so a
4
+ // module-level slot is safe.
5
+ let lastIterativeMetrics = null;
6
+ /**
7
+ * Runs a full fuzzy search and returns the match together with the timing
8
+ * metrics that used to be captured inline.
9
+ */
10
+ export function runFuzzySearch(text, query) {
11
+ const startTime = performance.now();
12
+ lastIterativeMetrics = null;
13
+ const result = recursiveFuzzyIndexOf(text, query);
14
+ return {
15
+ result,
16
+ metrics: {
17
+ recursive: {
18
+ execution_time_ms: performance.now() - startTime,
19
+ text_length: text.length,
20
+ query_length: query.length,
21
+ result_distance: result.distance
22
+ },
23
+ iterative: lastIterativeMetrics
24
+ }
25
+ };
26
+ }
27
+ /**
28
+ * Recursively finds the closest match to a query string within text using fuzzy matching
29
+ * @param text The text to search within
30
+ * @param query The query string to find
31
+ * @param start Start index in the text (default: 0)
32
+ * @param end End index in the text (default: text.length)
33
+ * @param parentDistance Best distance found so far (default: Infinity)
34
+ * @returns Object with start and end indices, matched value, and Levenshtein distance
35
+ */
36
+ export function recursiveFuzzyIndexOf(text, query, start = 0, end = null, parentDistance = Infinity) {
37
+ if (end === null)
38
+ end = text.length;
39
+ // For small text segments, use iterative approach
40
+ if (end - start <= 2 * query.length) {
41
+ return iterativeReduction(text, query, start, end, parentDistance);
42
+ }
43
+ let midPoint = start + Math.floor((end - start) / 2);
44
+ let leftEnd = Math.min(end, midPoint + query.length); // Include query length to cover overlaps
45
+ let rightStart = Math.max(start, midPoint - query.length); // Include query length to cover overlaps
46
+ // Calculate distance for current segments
47
+ let leftDistance = distance(text.substring(start, leftEnd), query);
48
+ let rightDistance = distance(text.substring(rightStart, end), query);
49
+ let bestDistance = Math.min(leftDistance, parentDistance, rightDistance);
50
+ // If parent distance is already the best, use iterative approach
51
+ if (parentDistance === bestDistance) {
52
+ return iterativeReduction(text, query, start, end, parentDistance);
53
+ }
54
+ // Recursively search the better half
55
+ if (leftDistance < rightDistance) {
56
+ return recursiveFuzzyIndexOf(text, query, start, leftEnd, bestDistance);
57
+ }
58
+ else {
59
+ return recursiveFuzzyIndexOf(text, query, rightStart, end, bestDistance);
60
+ }
61
+ }
62
+ /**
63
+ * Iteratively refines the best match by reducing the search area
64
+ * @param text The text to search within
65
+ * @param query The query string to find
66
+ * @param start Start index in the text
67
+ * @param end End index in the text
68
+ * @param parentDistance Best distance found so far
69
+ * @returns Object with start and end indices, matched value, and Levenshtein distance
70
+ */
71
+ function iterativeReduction(text, query, start, end, parentDistance) {
72
+ const startTime = performance.now();
73
+ let iterations = 0;
74
+ // Seed with the measured distance of this slice. For recursive callers
75
+ // this equals parentDistance (the parent measured exactly this slice), but
76
+ // a top-level call on text <= 2x query length arrives with Infinity, which
77
+ // made the first shrink unconditional and a position-0 match unreachable.
78
+ let bestDistance = distance(text.substring(start, end), query);
79
+ let bestStart = start;
80
+ let bestEnd = end;
81
+ // Improve start position
82
+ let nextDistance = distance(text.substring(bestStart + 1, bestEnd), query);
83
+ while (nextDistance < bestDistance) {
84
+ bestDistance = nextDistance;
85
+ bestStart++;
86
+ const smallerString = text.substring(bestStart + 1, bestEnd);
87
+ nextDistance = distance(smallerString, query);
88
+ iterations++;
89
+ }
90
+ // Improve end position
91
+ nextDistance = distance(text.substring(bestStart, bestEnd - 1), query);
92
+ while (nextDistance < bestDistance) {
93
+ bestDistance = nextDistance;
94
+ bestEnd--;
95
+ const smallerString = text.substring(bestStart, bestEnd - 1);
96
+ nextDistance = distance(smallerString, query);
97
+ iterations++;
98
+ }
99
+ lastIterativeMetrics = {
100
+ execution_time_ms: performance.now() - startTime,
101
+ iterations: iterations,
102
+ segment_length: end - start,
103
+ query_length: query.length,
104
+ final_distance: bestDistance
105
+ };
106
+ return {
107
+ start: bestStart,
108
+ end: bestEnd,
109
+ value: text.substring(bestStart, bestEnd),
110
+ distance: bestDistance
111
+ };
112
+ }
113
+ /**
114
+ * Calculates the similarity ratio between two strings
115
+ * @param a First string
116
+ * @param b Second string
117
+ * @returns Similarity ratio (0-1)
118
+ */
119
+ export function getSimilarityRatio(a, b) {
120
+ const maxLength = Math.max(a.length, b.length);
121
+ if (maxLength === 0)
122
+ return 1; // Both strings are empty
123
+ const levenshteinDistance = distance(a, b);
124
+ return 1 - (levenshteinDistance / maxLength);
125
+ }
@@ -1,4 +1,4 @@
1
- import { terminalManager } from '../terminal-manager.js';
1
+ import { terminalManager, MAX_BUFFERED_OUTPUT_CHARS } from '../terminal-manager.js';
2
2
  import { commandManager } from '../command-manager.js';
3
3
  import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
4
4
  import { capture } from "../utils/capture.js";
@@ -293,6 +293,13 @@ export async function readProcessOutput(args) {
293
293
  // Absolute position read
294
294
  statusMessage = `[Reading ${result.readCount} lines from line ${result.readFrom} (total: ${result.totalLines} lines, ${result.remaining} remaining)]`;
295
295
  }
296
+ // Surface buffer-cap eviction so the model knows the retained output is not
297
+ // the full output and that line numbers shifted (matches the truncation
298
+ // markers used by other tools).
299
+ if (result.evictedLines && result.evictedLines > 0) {
300
+ const capMB = Math.round(MAX_BUFFERED_OUTPUT_CHARS / 1024 / 1024);
301
+ statusMessage += `\n[WARNING: output exceeded the ${capMB}MB buffer cap; the ${result.evictedLines} earliest lines were evicted and cannot be read. Line numbers and totals refer to the retained buffer only]`;
302
+ }
296
303
  // Add process state info
297
304
  let processStateMessage = '';
298
305
  if (result.isComplete) {
@@ -72,18 +72,21 @@ export declare const ReadFileArgsSchema: z.ZodObject<{
72
72
  sheet: z.ZodOptional<z.ZodString>;
73
73
  range: z.ZodOptional<z.ZodString>;
74
74
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
75
+ origin: z.ZodOptional<z.ZodEnum<["ui", "llm"]>>;
75
76
  }, "strip", z.ZodTypeAny, {
76
77
  length: number;
77
78
  path: string;
78
79
  offset: number;
79
80
  isUrl: boolean;
80
81
  options?: Record<string, any> | undefined;
82
+ origin?: "ui" | "llm" | undefined;
81
83
  sheet?: string | undefined;
82
84
  range?: string | undefined;
83
85
  }, {
84
86
  path: string;
85
87
  length?: number | undefined;
86
88
  options?: Record<string, any> | undefined;
89
+ origin?: "ui" | "llm" | undefined;
87
90
  offset?: number | undefined;
88
91
  isUrl?: boolean | undefined;
89
92
  sheet?: string | undefined;
@@ -100,13 +103,16 @@ export declare const WriteFileArgsSchema: z.ZodObject<{
100
103
  path: z.ZodString;
101
104
  content: z.ZodString;
102
105
  mode: z.ZodDefault<z.ZodEnum<["rewrite", "append"]>>;
106
+ origin: z.ZodOptional<z.ZodEnum<["ui", "llm"]>>;
103
107
  }, "strip", z.ZodTypeAny, {
104
108
  path: string;
105
109
  content: string;
106
110
  mode: "rewrite" | "append";
111
+ origin?: "ui" | "llm" | undefined;
107
112
  }, {
108
113
  path: string;
109
114
  content: string;
115
+ origin?: "ui" | "llm" | undefined;
110
116
  mode?: "rewrite" | "append" | undefined;
111
117
  }>;
112
118
  export declare const PdfInsertOperationSchema: z.ZodObject<{
@@ -237,11 +243,14 @@ export declare const CreateDirectoryArgsSchema: z.ZodObject<{
237
243
  export declare const ListDirectoryArgsSchema: z.ZodObject<{
238
244
  path: z.ZodString;
239
245
  depth: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
246
+ origin: z.ZodOptional<z.ZodEnum<["ui", "llm"]>>;
240
247
  }, "strip", z.ZodTypeAny, {
241
248
  path: string;
242
249
  depth: number;
250
+ origin?: "ui" | "llm" | undefined;
243
251
  }, {
244
252
  path: string;
253
+ origin?: "ui" | "llm" | undefined;
245
254
  depth?: number | undefined;
246
255
  }>;
247
256
  export declare const MoveFileArgsSchema: z.ZodObject<{
@@ -269,10 +278,12 @@ export declare const EditBlockArgsSchema: z.ZodEffects<z.ZodObject<{
269
278
  range: z.ZodOptional<z.ZodString>;
270
279
  content: z.ZodOptional<z.ZodAny>;
271
280
  options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
281
+ origin: z.ZodOptional<z.ZodEnum<["ui", "llm"]>>;
272
282
  }, "strip", z.ZodTypeAny, {
273
283
  file_path: string;
274
284
  expected_replacements: number;
275
285
  options?: Record<string, any> | undefined;
286
+ origin?: "ui" | "llm" | undefined;
276
287
  range?: string | undefined;
277
288
  content?: any;
278
289
  old_string?: string | undefined;
@@ -280,6 +291,7 @@ export declare const EditBlockArgsSchema: z.ZodEffects<z.ZodObject<{
280
291
  }, {
281
292
  file_path: string;
282
293
  options?: Record<string, any> | undefined;
294
+ origin?: "ui" | "llm" | undefined;
283
295
  range?: string | undefined;
284
296
  content?: any;
285
297
  old_string?: string | undefined;
@@ -289,6 +301,7 @@ export declare const EditBlockArgsSchema: z.ZodEffects<z.ZodObject<{
289
301
  file_path: string;
290
302
  expected_replacements: number;
291
303
  options?: Record<string, any> | undefined;
304
+ origin?: "ui" | "llm" | undefined;
292
305
  range?: string | undefined;
293
306
  content?: any;
294
307
  old_string?: string | undefined;
@@ -296,6 +309,7 @@ export declare const EditBlockArgsSchema: z.ZodEffects<z.ZodObject<{
296
309
  }, {
297
310
  file_path: string;
298
311
  options?: Record<string, any> | undefined;
312
+ origin?: "ui" | "llm" | undefined;
299
313
  range?: string | undefined;
300
314
  content?: any;
301
315
  old_string?: string | undefined;
@@ -417,3 +431,9 @@ export declare const TrackUiEventArgsSchema: z.ZodObject<{
417
431
  params?: Record<string, string | number | boolean | null> | undefined;
418
432
  component?: string | undefined;
419
433
  }>;
434
+ /**
435
+ * Map of tool name -> argument schema, used by the dispatcher to detect and warn
436
+ * about parameters a caller sent that the tool does not support. Keep in sync
437
+ * with the tool definitions in server.ts.
438
+ */
439
+ export declare const toolArgSchemas: Record<string, z.ZodTypeAny>;
@@ -43,7 +43,10 @@ export const ReadFileArgsSchema = z.object({
43
43
  length: z.number().optional().default(1000),
44
44
  sheet: z.string().optional(), // String only for MCP client compatibility (Cursor doesn't support union types in JSON Schema)
45
45
  range: z.string().optional(),
46
- options: z.record(z.any()).optional()
46
+ options: z.record(z.any()).optional(),
47
+ // Whether the call came from the file-preview UI (refresh/navigation) or the
48
+ // LLM. Used only for telemetry attribution; see call_origin in server.ts.
49
+ origin: z.enum(['ui', 'llm']).optional(),
47
50
  });
48
51
  export const ReadMultipleFilesArgsSchema = z.object({
49
52
  paths: z.array(z.string()),
@@ -52,6 +55,8 @@ export const WriteFileArgsSchema = z.object({
52
55
  path: z.string(),
53
56
  content: z.string(),
54
57
  mode: z.enum(['rewrite', 'append']).default('rewrite'),
58
+ // Telemetry attribution: 'ui' when fired by the file-preview UI, else 'llm'.
59
+ origin: z.enum(['ui', 'llm']).optional(),
55
60
  });
56
61
  // PDF modification schemas - exported for reuse
57
62
  export const PdfInsertOperationSchema = z.object({
@@ -92,6 +97,8 @@ export const CreateDirectoryArgsSchema = z.object({
92
97
  export const ListDirectoryArgsSchema = z.object({
93
98
  path: z.string(),
94
99
  depth: z.number().optional().default(2),
100
+ // Telemetry attribution: 'ui' when fired by the file-preview UI, else 'llm'.
101
+ origin: z.enum(['ui', 'llm']).optional(),
95
102
  });
96
103
  export const MoveFileArgsSchema = z.object({
97
104
  source: z.string(),
@@ -114,7 +121,9 @@ export const EditBlockArgsSchema = z.object({
114
121
  // Structured file range rewrite (Excel, etc.)
115
122
  range: z.string().optional(),
116
123
  content: z.any().optional(),
117
- options: z.record(z.any()).optional()
124
+ options: z.record(z.any()).optional(),
125
+ // Telemetry attribution: 'ui' when fired by the file-preview UI, else 'llm'.
126
+ origin: z.enum(['ui', 'llm']).optional(),
118
127
  }).refine(data => {
119
128
  // Helper to check if value is actually provided (not undefined, not empty string)
120
129
  const hasValue = (v) => v !== undefined && v !== '';
@@ -181,3 +190,37 @@ export const TrackUiEventArgsSchema = z.object({
181
190
  component: z.string().optional().default('file_preview'),
182
191
  params: z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])).optional().default({}),
183
192
  });
193
+ /**
194
+ * Map of tool name -> argument schema, used by the dispatcher to detect and warn
195
+ * about parameters a caller sent that the tool does not support. Keep in sync
196
+ * with the tool definitions in server.ts.
197
+ */
198
+ export const toolArgSchemas = {
199
+ get_config: GetConfigArgsSchema,
200
+ set_config_value: SetConfigValueArgsSchema,
201
+ read_file: ReadFileArgsSchema,
202
+ read_multiple_files: ReadMultipleFilesArgsSchema,
203
+ write_file: WriteFileArgsSchema,
204
+ write_pdf: WritePdfArgsSchema,
205
+ create_directory: CreateDirectoryArgsSchema,
206
+ list_directory: ListDirectoryArgsSchema,
207
+ move_file: MoveFileArgsSchema,
208
+ start_search: StartSearchArgsSchema,
209
+ get_more_search_results: GetMoreSearchResultsArgsSchema,
210
+ stop_search: StopSearchArgsSchema,
211
+ list_searches: ListSearchesArgsSchema,
212
+ get_file_info: GetFileInfoArgsSchema,
213
+ edit_block: EditBlockArgsSchema,
214
+ start_process: StartProcessArgsSchema,
215
+ read_process_output: ReadProcessOutputArgsSchema,
216
+ interact_with_process: InteractWithProcessArgsSchema,
217
+ force_terminate: ForceTerminateArgsSchema,
218
+ list_sessions: ListSessionsArgsSchema,
219
+ list_processes: ListProcessesArgsSchema,
220
+ kill_process: KillProcessArgsSchema,
221
+ get_usage_stats: GetUsageStatsArgsSchema,
222
+ get_recent_tool_calls: GetRecentToolCallsArgsSchema,
223
+ give_feedback_to_desktop_commander: GiveFeedbackArgsSchema,
224
+ get_prompts: GetPromptsArgsSchema,
225
+ track_ui_event: TrackUiEventArgsSchema,
226
+ };
package/dist/types.d.ts CHANGED
@@ -18,6 +18,9 @@ export interface TerminalSession {
18
18
  lastReadIndex: number;
19
19
  isBlocked: boolean;
20
20
  startTime: Date;
21
+ bufferedChars: number;
22
+ evictedLines: number;
23
+ evictedChars: number;
21
24
  }
22
25
  export interface CommandExecutionResult {
23
26
  pid: number;
@@ -69,7 +72,6 @@ export interface FilePreviewStructuredContent {
69
72
  defaultEditorName?: string;
70
73
  defaultEditorPath?: string;
71
74
  content?: string;
72
- imageData?: string;
73
75
  mimeType?: string;
74
76
  }
75
77
  export interface ServerResult {