@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.
@@ -146,7 +146,7 @@ export function attachDirectoryHandlers(options) {
146
146
  loadMoreBtn.querySelector('.dir-warning-text').textContent = 'Loading…';
147
147
  loadMoreBtn.disabled = true;
148
148
  try {
149
- const result = await options.callTool?.('list_directory', { path: loadPath, depth: 1 });
149
+ const result = await options.callTool?.('list_directory', { path: loadPath, depth: 1, origin: 'ui' });
150
150
  const text = extractToolText(result) ?? '';
151
151
  if (text) {
152
152
  const parsed = parseDirectoryEntries(text);
@@ -194,7 +194,7 @@ export function attachDirectoryHandlers(options) {
194
194
  if (chevron)
195
195
  chevron.textContent = '⏳';
196
196
  try {
197
- const result = await options.callTool?.('list_directory', { path: fullPath, depth: 2 });
197
+ const result = await options.callTool?.('list_directory', { path: fullPath, depth: 2, origin: 'ui' });
198
198
  const text = extractToolText(result) ?? '';
199
199
  if (text) {
200
200
  target.dataset.loaded = 'true';
@@ -222,7 +222,7 @@ export function attachDirectoryHandlers(options) {
222
222
  if (target.classList.contains('dir-row-file')) {
223
223
  target.classList.add('dir-loading');
224
224
  try {
225
- const result = await options.callTool?.('read_file', { path: fullPath });
225
+ const result = await options.callTool?.('read_file', { path: fullPath, origin: 'ui' });
226
226
  if (!result || typeof result !== 'object' || result === null) {
227
227
  return;
228
228
  }
@@ -16,13 +16,13 @@ function renderImageBody(payload) {
16
16
  html: '<div class="panel-content source-content"></div>',
17
17
  };
18
18
  }
19
- if (!payload.imageData || payload.imageData.trim().length === 0) {
19
+ if (!payload.content || payload.content.trim().length === 0) {
20
20
  return {
21
21
  notice: 'Preview is unavailable because image data is missing.',
22
22
  html: '<div class="panel-content source-content"></div>',
23
23
  };
24
24
  }
25
- const src = `data:${mimeType};base64,${payload.imageData}`;
25
+ const src = `data:${mimeType};base64,${payload.content}`;
26
26
  return {
27
27
  html: `<div class="panel-content image-content"><div class="image-preview"><img src="${escapeHtml(src)}" alt="${escapeHtml(payload.fileName)}" loading="eager" decoding="async"></div></div>`,
28
28
  };
@@ -281,6 +281,7 @@ export function createMarkdownController(dependencies) {
281
281
  const rawResult = await dependencies.callTool?.('read_file', {
282
282
  path: filePath,
283
283
  ...(typeof length === 'number' ? { offset: offset ?? 0, length } : {}),
284
+ origin: 'ui',
284
285
  });
285
286
  return { rawResult, payload: extractRenderPayload(rawResult) ?? null };
286
287
  }
@@ -360,7 +361,7 @@ export function createMarkdownController(dependencies) {
360
361
  const markers = new Set(['[DIR] .git', '[DIR] .obsidian', '[FILE] package.json', '[FILE] pnpm-workspace.yaml', '[FILE] turbo.json']);
361
362
  for (const ancestor of ancestors) {
362
363
  try {
363
- const result = await dependencies.callTool?.('list_directory', { path: ancestor, depth: 1 });
364
+ const result = await dependencies.callTool?.('list_directory', { path: ancestor, depth: 1, origin: 'ui' });
364
365
  const text = extractToolText(result) ?? '';
365
366
  const entries = splitListingLines(text);
366
367
  if (entries.some((entry) => markers.has(entry))) {
@@ -681,6 +682,7 @@ export function createMarkdownController(dependencies) {
681
682
  old_string: block.old_string,
682
683
  new_string: block.new_string,
683
684
  expected_replacements: 1,
685
+ origin: 'ui',
684
686
  });
685
687
  assertSuccessfulEditBlockResult(editResult);
686
688
  appliedCount++;
@@ -144,8 +144,8 @@ export function attachPanelActions(options) {
144
144
  });
145
145
  try {
146
146
  const readArgs = direction === 'before'
147
- ? { path: options.payload.filePath, offset: 0, length: range.fromLine - 1 }
148
- : { path: options.payload.filePath, offset: range.toLine };
147
+ ? { path: options.payload.filePath, offset: 0, length: range.fromLine - 1, origin: 'ui' }
148
+ : { path: options.payload.filePath, offset: range.toLine, origin: 'ui' };
149
149
  const result = await options.callTool?.('read_file', readArgs);
150
150
  const newText = extractToolText(result);
151
151
  if (newText && typeof newText === 'string') {
@@ -1,7 +1,7 @@
1
1
  import { platform } from 'os';
2
2
  import * as https from 'https';
3
3
  import { configManager, isTelemetryDisabledValue } from '../config-manager.js';
4
- import { currentClient, currentCallIsRemote } from '../server.js';
4
+ import { currentClient, currentCallIsRemote, currentRemoteClient } from '../server.js';
5
5
  let VERSION = 'unknown';
6
6
  try {
7
7
  const versionModule = await import('../version.js');
@@ -89,11 +89,14 @@ export const captureBase = async (captureURL, event, properties) => {
89
89
  uniqueUserId = await configManager.getOrCreateClientId();
90
90
  }
91
91
  // Get current client information for all events
92
+ // For remote calls, attribute to the originating remote client (carried on
93
+ // the tool call) instead of the device's local currentClient.
94
+ const effectiveClient = currentCallIsRemote && currentRemoteClient ? currentRemoteClient : currentClient;
92
95
  let clientContext = {};
93
- if (currentClient) {
96
+ if (effectiveClient) {
94
97
  clientContext = {
95
- client_name: currentClient.name,
96
- client_version: currentClient.version,
98
+ client_name: effectiveClient.name,
99
+ client_version: effectiveClient.version,
97
100
  };
98
101
  }
99
102
  // Track if user saw onboarding page
@@ -265,11 +268,14 @@ const buildEventProperties = async (properties) => {
265
268
  if (uniqueUserId === 'unknown') {
266
269
  uniqueUserId = await configManager.getOrCreateClientId();
267
270
  }
271
+ // For remote calls, attribute to the originating remote client (carried on
272
+ // the tool call) instead of the device's local currentClient.
273
+ const effectiveClient = currentCallIsRemote && currentRemoteClient ? currentRemoteClient : currentClient;
268
274
  let clientContext = {};
269
- if (currentClient) {
275
+ if (effectiveClient) {
270
276
  clientContext = {
271
- client_name: currentClient.name,
272
- client_version: currentClient.version,
277
+ client_name: effectiveClient.name,
278
+ client_version: effectiveClient.version,
273
279
  };
274
280
  }
275
281
  const sawOnboardingPage = await configManager.getValue('sawOnboardingPage');
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Utility for detecting parameters a caller sent that a tool's Zod schema does
3
+ * not accept.
4
+ *
5
+ * Zod's default `z.object()` silently strips unknown keys, so a model that
6
+ * invents a parameter (e.g. `view_range`) gets a normal-looking response with no
7
+ * signal that its input was ignored. These helpers let the dispatcher surface a
8
+ * corrective warning instead.
9
+ *
10
+ * Detection is top-level only. That is sufficient here because none of the tool
11
+ * arg schemas use a top-level `.passthrough()`/catchall — free-form fields
12
+ * (`options`, `params`, `pdfOptions`) are named keys whose contents are
13
+ * intentionally unvalidated.
14
+ */
15
+ /** List the parameter names a tool schema accepts (empty array if it takes none). */
16
+ export declare function getSupportedParams(schema: unknown): string[];
17
+ /**
18
+ * Return the names of top-level keys in `args` that the schema does not accept.
19
+ * Returns [] when args is not a plain object or the schema's params can't be
20
+ * determined (so we never warn on something we can't reason about).
21
+ */
22
+ export declare function detectUnsupportedParams(args: unknown, schema: unknown): string[];
23
+ /** Build the corrective warning string shown to the model. */
24
+ export declare function buildUnsupportedParamsWarning(toolName: string, unsupported: string[], supported: string[]): string;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Utility for detecting parameters a caller sent that a tool's Zod schema does
3
+ * not accept.
4
+ *
5
+ * Zod's default `z.object()` silently strips unknown keys, so a model that
6
+ * invents a parameter (e.g. `view_range`) gets a normal-looking response with no
7
+ * signal that its input was ignored. These helpers let the dispatcher surface a
8
+ * corrective warning instead.
9
+ *
10
+ * Detection is top-level only. That is sufficient here because none of the tool
11
+ * arg schemas use a top-level `.passthrough()`/catchall — free-form fields
12
+ * (`options`, `params`, `pdfOptions`) are named keys whose contents are
13
+ * intentionally unvalidated.
14
+ */
15
+ /**
16
+ * Resolve a Zod schema down to the field map of its underlying object schema,
17
+ * unwrapping wrappers (effects/optional/default/nullable/branded) defensively.
18
+ * Returns null when the schema is not (and does not wrap) a plain object schema,
19
+ * in which case we cannot determine the supported parameters and must not warn.
20
+ */
21
+ function getObjectShape(schema) {
22
+ let current = schema;
23
+ for (let i = 0; current && current._def && i < 10; i++) {
24
+ const typeName = current._def.typeName;
25
+ if (typeName === 'ZodObject') {
26
+ const shape = typeof current.shape === 'function' ? current.shape() : current.shape;
27
+ return shape ?? null;
28
+ }
29
+ if (typeName === 'ZodEffects')
30
+ current = current._def.schema;
31
+ else if (typeName === 'ZodOptional' || typeName === 'ZodNullable' || typeName === 'ZodDefault') {
32
+ current = typeof current._def.innerType === 'function' ? current._def.innerType() : current._def.innerType;
33
+ }
34
+ else if (typeName === 'ZodBranded')
35
+ current = current._def.type;
36
+ else
37
+ return null;
38
+ }
39
+ return null;
40
+ }
41
+ /** List the parameter names a tool schema accepts (empty array if it takes none). */
42
+ export function getSupportedParams(schema) {
43
+ const shape = getObjectShape(schema);
44
+ return shape ? Object.keys(shape) : [];
45
+ }
46
+ /**
47
+ * Return the names of top-level keys in `args` that the schema does not accept.
48
+ * Returns [] when args is not a plain object or the schema's params can't be
49
+ * determined (so we never warn on something we can't reason about).
50
+ */
51
+ export function detectUnsupportedParams(args, schema) {
52
+ if (!args || typeof args !== 'object' || Array.isArray(args))
53
+ return [];
54
+ const shape = getObjectShape(schema);
55
+ if (shape === null)
56
+ return [];
57
+ const supported = new Set(Object.keys(shape));
58
+ return Object.keys(args).filter((k) => !supported.has(k));
59
+ }
60
+ /** Build the corrective warning string shown to the model. */
61
+ export function buildUnsupportedParamsWarning(toolName, unsupported, supported) {
62
+ const ignored = unsupported.join(', ');
63
+ const supportedList = supported.length > 0 ? supported.join(', ') : '(none)';
64
+ return `You sent parameters not supported by this tool, which were ignored: ${ignored}. `
65
+ + `Supported parameters for ${toolName}: ${supportedList}.`;
66
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.42";
1
+ export declare const VERSION = "0.2.43";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.42';
1
+ export const VERSION = '0.2.43';
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.2.42",
3
+ "version": "0.2.43",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "mcpName": "io.github.wonderwhy-er/desktop-commander",
6
6
  "license": "MIT",
7
7
  "author": "Eduards Ruzga",
8
8
  "homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/wonderwhy-er/DesktopCommanderMCP.git"
12
+ },
9
13
  "bugs": "https://github.com/wonderwhy-er/DesktopCommanderMCP/issues",
10
14
  "type": "module",
11
15
  "engines": {