@wonderwhy-er/desktop-commander 0.2.17 → 0.2.18-alpha.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.
@@ -56,7 +56,7 @@ export async function startProcess(args) {
56
56
  }
57
57
  }
58
58
  }
59
- const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, shellUsed);
59
+ const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, shellUsed, parsed.data.verbose_timing || false);
60
60
  if (result.pid === -1) {
61
61
  return {
62
62
  content: [{ type: "text", text: result.output }],
@@ -75,13 +75,40 @@ export async function startProcess(args) {
75
75
  else if (result.isBlocked) {
76
76
  statusMessage = '\nā³ Process is running. Use read_process_output to get more output.';
77
77
  }
78
+ // Add timing information if requested
79
+ let timingMessage = '';
80
+ if (result.timingInfo) {
81
+ timingMessage = formatTimingInfo(result.timingInfo);
82
+ }
78
83
  return {
79
84
  content: [{
80
85
  type: "text",
81
- text: `Process started with PID ${result.pid} (shell: ${shellUsed})\nInitial output:\n${result.output}${statusMessage}`
86
+ text: `Process started with PID ${result.pid} (shell: ${shellUsed})\nInitial output:\n${result.output}${statusMessage}${timingMessage}`
82
87
  }],
83
88
  };
84
89
  }
90
+ function formatTimingInfo(timing) {
91
+ let msg = '\n\nšŸ“Š Timing Information:\n';
92
+ msg += ` Exit Reason: ${timing.exitReason}\n`;
93
+ msg += ` Total Duration: ${timing.totalDurationMs}ms\n`;
94
+ if (timing.timeToFirstOutputMs !== undefined) {
95
+ msg += ` Time to First Output: ${timing.timeToFirstOutputMs}ms\n`;
96
+ }
97
+ if (timing.firstOutputTime && timing.lastOutputTime) {
98
+ msg += ` Output Window: ${timing.lastOutputTime - timing.firstOutputTime}ms\n`;
99
+ }
100
+ if (timing.outputEvents && timing.outputEvents.length > 0) {
101
+ msg += `\n Output Events (${timing.outputEvents.length} total):\n`;
102
+ timing.outputEvents.forEach((event, idx) => {
103
+ msg += ` [${idx + 1}] +${event.deltaMs}ms | ${event.source} | ${event.length}b`;
104
+ if (event.matchedPattern) {
105
+ msg += ` | šŸŽÆ ${event.matchedPattern}`;
106
+ }
107
+ msg += `\n "${event.snippet}"\n`;
108
+ });
109
+ }
110
+ return msg;
111
+ }
85
112
  /**
86
113
  * Read output from a running process (renamed from read_output)
87
114
  * Includes early detection of process waiting for input
@@ -94,7 +121,7 @@ export async function readProcessOutput(args) {
94
121
  isError: true,
95
122
  };
96
123
  }
97
- const { pid, timeout_ms = 5000 } = parsed.data;
124
+ const { pid, timeout_ms = 5000, verbose_timing = false } = parsed.data;
98
125
  const session = terminalManager.getSession(pid);
99
126
  if (!session) {
100
127
  // Check if this is a completed session
@@ -117,15 +144,35 @@ export async function readProcessOutput(args) {
117
144
  let timeoutReached = false;
118
145
  let earlyExit = false;
119
146
  let processState;
147
+ // Timing telemetry
148
+ const startTime = Date.now();
149
+ let firstOutputTime;
150
+ let lastOutputTime;
151
+ const outputEvents = [];
152
+ let exitReason = 'timeout';
120
153
  try {
121
154
  const outputPromise = new Promise((resolve) => {
122
155
  const initialOutput = terminalManager.getNewOutput(pid);
123
156
  if (initialOutput && initialOutput.length > 0) {
157
+ const now = Date.now();
158
+ if (!firstOutputTime)
159
+ firstOutputTime = now;
160
+ lastOutputTime = now;
161
+ if (verbose_timing) {
162
+ outputEvents.push({
163
+ timestamp: now,
164
+ deltaMs: now - startTime,
165
+ source: 'initial_poll',
166
+ length: initialOutput.length,
167
+ snippet: initialOutput.slice(0, 50).replace(/\n/g, '\\n')
168
+ });
169
+ }
124
170
  // Immediate check on existing output
125
171
  const state = analyzeProcessState(initialOutput, pid);
126
172
  if (state.isWaitingForInput) {
127
173
  earlyExit = true;
128
174
  processState = state;
175
+ exitReason = 'early_exit_periodic_check';
129
176
  }
130
177
  resolve(initialOutput);
131
178
  return;
@@ -147,13 +194,28 @@ export async function readProcessOutput(args) {
147
194
  resolved = true;
148
195
  cleanup();
149
196
  timeoutReached = isTimeout;
197
+ if (isTimeout)
198
+ exitReason = 'timeout';
150
199
  resolve(value);
151
200
  };
152
201
  // Monitor for new output with immediate detection
153
202
  const session = terminalManager.getSession(pid);
154
203
  if (session && session.process && session.process.stdout && session.process.stderr) {
155
- const immediateDetector = (data) => {
204
+ const immediateDetector = (data, source) => {
156
205
  const text = data.toString();
206
+ const now = Date.now();
207
+ if (!firstOutputTime)
208
+ firstOutputTime = now;
209
+ lastOutputTime = now;
210
+ if (verbose_timing) {
211
+ outputEvents.push({
212
+ timestamp: now,
213
+ deltaMs: now - startTime,
214
+ source,
215
+ length: text.length,
216
+ snippet: text.slice(0, 50).replace(/\n/g, '\\n')
217
+ });
218
+ }
157
219
  // Immediate check for obvious prompts
158
220
  if (quickPromptPatterns.test(text)) {
159
221
  const newOutput = terminalManager.getNewOutput(pid) || text;
@@ -161,21 +223,27 @@ export async function readProcessOutput(args) {
161
223
  if (state.isWaitingForInput) {
162
224
  earlyExit = true;
163
225
  processState = state;
226
+ exitReason = 'early_exit_quick_pattern';
227
+ if (verbose_timing && outputEvents.length > 0) {
228
+ outputEvents[outputEvents.length - 1].matchedPattern = 'quick_pattern';
229
+ }
164
230
  resolveOnce(newOutput);
165
231
  return;
166
232
  }
167
233
  }
168
234
  };
169
- session.process.stdout.on('data', immediateDetector);
170
- session.process.stderr.on('data', immediateDetector);
235
+ const stdoutDetector = (data) => immediateDetector(data, 'stdout');
236
+ const stderrDetector = (data) => immediateDetector(data, 'stderr');
237
+ session.process.stdout.on('data', stdoutDetector);
238
+ session.process.stderr.on('data', stderrDetector);
171
239
  // Cleanup immediate detectors when done
172
240
  const originalResolveOnce = resolveOnce;
173
241
  const cleanupDetectors = () => {
174
242
  if (session.process.stdout) {
175
- session.process.stdout.removeListener('data', immediateDetector);
243
+ session.process.stdout.off('data', stdoutDetector);
176
244
  }
177
245
  if (session.process.stderr) {
178
- session.process.stderr.removeListener('data', immediateDetector);
246
+ session.process.stderr.off('data', stderrDetector);
179
247
  }
180
248
  };
181
249
  // Override resolveOnce to include cleanup
@@ -189,12 +257,29 @@ export async function readProcessOutput(args) {
189
257
  interval = setInterval(() => {
190
258
  const newOutput = terminalManager.getNewOutput(pid);
191
259
  if (newOutput && newOutput.length > 0) {
260
+ const now = Date.now();
261
+ if (!firstOutputTime)
262
+ firstOutputTime = now;
263
+ lastOutputTime = now;
264
+ if (verbose_timing) {
265
+ outputEvents.push({
266
+ timestamp: now,
267
+ deltaMs: now - startTime,
268
+ source: 'periodic_poll',
269
+ length: newOutput.length,
270
+ snippet: newOutput.slice(0, 50).replace(/\n/g, '\\n')
271
+ });
272
+ }
192
273
  const currentOutput = output + newOutput;
193
274
  const state = analyzeProcessState(currentOutput, pid);
194
275
  // Early exit if process is clearly waiting for input
195
276
  if (state.isWaitingForInput) {
196
277
  earlyExit = true;
197
278
  processState = state;
279
+ exitReason = 'early_exit_periodic_check';
280
+ if (verbose_timing && outputEvents.length > 0) {
281
+ outputEvents[outputEvents.length - 1].matchedPattern = 'periodic_check';
282
+ }
198
283
  resolveOnce(newOutput);
199
284
  return;
200
285
  }
@@ -205,9 +290,10 @@ export async function readProcessOutput(args) {
205
290
  }
206
291
  // Process finished
207
292
  processState = state;
293
+ exitReason = 'process_finished';
208
294
  resolveOnce(newOutput);
209
295
  }
210
- }, 200); // Check every 200ms
296
+ }, 50); // Check every 50ms for faster response
211
297
  timeout = setTimeout(() => {
212
298
  const finalOutput = terminalManager.getNewOutput(pid) || "";
213
299
  resolveOnce(finalOutput, true);
@@ -237,11 +323,27 @@ export async function readProcessOutput(args) {
237
323
  else if (timeoutReached) {
238
324
  statusMessage = '\nā±ļø Timeout reached - process may still be running';
239
325
  }
326
+ // Add timing information if requested
327
+ let timingMessage = '';
328
+ if (verbose_timing) {
329
+ const endTime = Date.now();
330
+ const timingInfo = {
331
+ startTime,
332
+ endTime,
333
+ totalDurationMs: endTime - startTime,
334
+ exitReason,
335
+ firstOutputTime,
336
+ lastOutputTime,
337
+ timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined,
338
+ outputEvents: outputEvents.length > 0 ? outputEvents : undefined
339
+ };
340
+ timingMessage = formatTimingInfo(timingInfo);
341
+ }
240
342
  const responseText = output || 'No new output available';
241
343
  return {
242
344
  content: [{
243
345
  type: "text",
244
- text: `${responseText}${statusMessage}`
346
+ text: `${responseText}${statusMessage}${timingMessage}`
245
347
  }],
246
348
  };
247
349
  }
@@ -260,7 +362,13 @@ export async function interactWithProcess(args) {
260
362
  isError: true,
261
363
  };
262
364
  }
263
- const { pid, input, timeout_ms = 8000, wait_for_prompt = true } = parsed.data;
365
+ const { pid, input, timeout_ms = 8000, wait_for_prompt = true, verbose_timing = false } = parsed.data;
366
+ // Timing telemetry
367
+ const startTime = Date.now();
368
+ let firstOutputTime;
369
+ let lastOutputTime;
370
+ const outputEvents = [];
371
+ let exitReason = 'timeout';
264
372
  try {
265
373
  capture('server_interact_with_process', {
266
374
  pid: pid,
@@ -275,10 +383,26 @@ export async function interactWithProcess(args) {
275
383
  }
276
384
  // If not waiting for response, return immediately
277
385
  if (!wait_for_prompt) {
386
+ exitReason = 'no_wait';
387
+ let timingMessage = '';
388
+ if (verbose_timing) {
389
+ const endTime = Date.now();
390
+ const timingInfo = {
391
+ startTime,
392
+ endTime,
393
+ totalDurationMs: endTime - startTime,
394
+ exitReason,
395
+ firstOutputTime,
396
+ lastOutputTime,
397
+ timeToFirstOutputMs: undefined,
398
+ outputEvents: undefined
399
+ };
400
+ timingMessage = formatTimingInfo(timingInfo);
401
+ }
278
402
  return {
279
403
  content: [{
280
404
  type: "text",
281
- text: `āœ… Input sent to process ${pid}. Use read_process_output to get the response.`
405
+ text: `āœ… Input sent to process ${pid}. Use read_process_output to get the response.${timingMessage}`
282
406
  }],
283
407
  };
284
408
  }
@@ -292,7 +416,8 @@ export async function interactWithProcess(args) {
292
416
  return new Promise((resolve) => {
293
417
  let resolved = false;
294
418
  let attempts = 0;
295
- const maxAttempts = Math.ceil(timeout_ms / 200);
419
+ const pollIntervalMs = 50; // Poll every 50ms for faster response
420
+ const maxAttempts = Math.ceil(timeout_ms / pollIntervalMs);
296
421
  let interval = null;
297
422
  let resolveOnce = () => {
298
423
  if (resolved)
@@ -302,74 +427,51 @@ export async function interactWithProcess(args) {
302
427
  clearInterval(interval);
303
428
  resolve();
304
429
  };
305
- // Set up immediate detection on the process streams
306
- const session = terminalManager.getSession(pid);
307
- if (session && session.process && session.process.stdout && session.process.stderr) {
308
- const immediateDetector = (data) => {
309
- const text = data.toString();
310
- // Immediate check for obvious prompts
311
- if (quickPromptPatterns.test(text)) {
312
- // Get the latest output and analyze
313
- setTimeout(() => {
314
- const newOutput = terminalManager.getNewOutput(pid);
315
- if (newOutput) {
316
- output += newOutput;
317
- const state = analyzeProcessState(output, pid);
318
- if (state.isWaitingForInput) {
319
- processState = state;
320
- earlyExit = true;
321
- resolveOnce();
322
- }
323
- }
324
- }, 50); // Small delay to ensure output is captured
325
- }
326
- };
327
- session.process.stdout.on('data', immediateDetector);
328
- session.process.stderr.on('data', immediateDetector);
329
- // Cleanup when done
330
- const cleanupDetectors = () => {
331
- if (session.process.stdout) {
332
- session.process.stdout.removeListener('data', immediateDetector);
333
- }
334
- if (session.process.stderr) {
335
- session.process.stderr.removeListener('data', immediateDetector);
336
- }
337
- };
338
- // Override resolveOnce to include cleanup
339
- const originalResolveOnce = resolveOnce;
340
- const resolveOnceWithCleanup = () => {
341
- cleanupDetectors();
342
- originalResolveOnce();
343
- };
344
- // Replace the local resolveOnce reference
345
- resolveOnce = resolveOnceWithCleanup;
346
- }
347
- // Periodic check as fallback
430
+ // Fast-polling check - check every 50ms for quick responses
348
431
  interval = setInterval(() => {
349
432
  if (resolved)
350
433
  return;
351
434
  const newOutput = terminalManager.getNewOutput(pid);
352
435
  if (newOutput && newOutput.length > 0) {
436
+ const now = Date.now();
437
+ if (!firstOutputTime)
438
+ firstOutputTime = now;
439
+ lastOutputTime = now;
440
+ if (verbose_timing) {
441
+ outputEvents.push({
442
+ timestamp: now,
443
+ deltaMs: now - startTime,
444
+ source: 'periodic_poll',
445
+ length: newOutput.length,
446
+ snippet: newOutput.slice(0, 50).replace(/\n/g, '\\n')
447
+ });
448
+ }
353
449
  output += newOutput;
354
450
  // Analyze current state
355
451
  processState = analyzeProcessState(output, pid);
356
452
  // Exit early if we detect the process is waiting for input
357
453
  if (processState.isWaitingForInput) {
358
454
  earlyExit = true;
455
+ exitReason = 'early_exit_periodic_check';
456
+ if (verbose_timing && outputEvents.length > 0) {
457
+ outputEvents[outputEvents.length - 1].matchedPattern = 'periodic_check';
458
+ }
359
459
  resolveOnce();
360
460
  return;
361
461
  }
362
462
  // Also exit if process finished
363
463
  if (processState.isFinished) {
464
+ exitReason = 'process_finished';
364
465
  resolveOnce();
365
466
  return;
366
467
  }
367
468
  }
368
469
  attempts++;
369
470
  if (attempts >= maxAttempts) {
471
+ exitReason = 'timeout';
370
472
  resolveOnce();
371
473
  }
372
- }, 200);
474
+ }, pollIntervalMs);
373
475
  });
374
476
  };
375
477
  await waitForResponse();
@@ -390,11 +492,27 @@ export async function interactWithProcess(args) {
390
492
  else if (timeoutReached) {
391
493
  statusMessage = '\nā±ļø Response may be incomplete (timeout reached)';
392
494
  }
495
+ // Add timing information if requested
496
+ let timingMessage = '';
497
+ if (verbose_timing) {
498
+ const endTime = Date.now();
499
+ const timingInfo = {
500
+ startTime,
501
+ endTime,
502
+ totalDurationMs: endTime - startTime,
503
+ exitReason,
504
+ firstOutputTime,
505
+ lastOutputTime,
506
+ timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined,
507
+ outputEvents: outputEvents.length > 0 ? outputEvents : undefined
508
+ };
509
+ timingMessage = formatTimingInfo(timingInfo);
510
+ }
393
511
  if (cleanOutput.trim().length === 0 && !timeoutReached) {
394
512
  return {
395
513
  content: [{
396
514
  type: "text",
397
- text: `āœ… Input executed in process ${pid}.\nšŸ“­ (No output produced)${statusMessage}`
515
+ text: `āœ… Input executed in process ${pid}.\nšŸ“­ (No output produced)${statusMessage}${timingMessage}`
398
516
  }],
399
517
  };
400
518
  }
@@ -409,6 +527,9 @@ export async function interactWithProcess(args) {
409
527
  if (statusMessage) {
410
528
  responseText += `\n\n${statusMessage}`;
411
529
  }
530
+ if (timingMessage) {
531
+ responseText += timingMessage;
532
+ }
412
533
  return {
413
534
  content: [{
414
535
  type: "text",
@@ -15,24 +15,30 @@ export declare const StartProcessArgsSchema: z.ZodObject<{
15
15
  command: z.ZodString;
16
16
  timeout_ms: z.ZodNumber;
17
17
  shell: z.ZodOptional<z.ZodString>;
18
+ verbose_timing: z.ZodOptional<z.ZodBoolean>;
18
19
  }, "strip", z.ZodTypeAny, {
19
20
  command: string;
20
21
  timeout_ms: number;
21
22
  shell?: string | undefined;
23
+ verbose_timing?: boolean | undefined;
22
24
  }, {
23
25
  command: string;
24
26
  timeout_ms: number;
25
27
  shell?: string | undefined;
28
+ verbose_timing?: boolean | undefined;
26
29
  }>;
27
30
  export declare const ReadProcessOutputArgsSchema: z.ZodObject<{
28
31
  pid: z.ZodNumber;
29
32
  timeout_ms: z.ZodOptional<z.ZodNumber>;
33
+ verbose_timing: z.ZodOptional<z.ZodBoolean>;
30
34
  }, "strip", z.ZodTypeAny, {
31
35
  pid: number;
32
36
  timeout_ms?: number | undefined;
37
+ verbose_timing?: boolean | undefined;
33
38
  }, {
34
39
  pid: number;
35
40
  timeout_ms?: number | undefined;
41
+ verbose_timing?: boolean | undefined;
36
42
  }>;
37
43
  export declare const ForceTerminateArgsSchema: z.ZodObject<{
38
44
  pid: z.ZodNumber;
@@ -140,15 +146,18 @@ export declare const InteractWithProcessArgsSchema: z.ZodObject<{
140
146
  input: z.ZodString;
141
147
  timeout_ms: z.ZodOptional<z.ZodNumber>;
142
148
  wait_for_prompt: z.ZodOptional<z.ZodBoolean>;
149
+ verbose_timing: z.ZodOptional<z.ZodBoolean>;
143
150
  }, "strip", z.ZodTypeAny, {
144
151
  pid: number;
145
152
  input: string;
146
153
  timeout_ms?: number | undefined;
154
+ verbose_timing?: boolean | undefined;
147
155
  wait_for_prompt?: boolean | undefined;
148
156
  }, {
149
157
  pid: number;
150
158
  input: string;
151
159
  timeout_ms?: number | undefined;
160
+ verbose_timing?: boolean | undefined;
152
161
  wait_for_prompt?: boolean | undefined;
153
162
  }>;
154
163
  export declare const GetUsageStatsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
@@ -18,10 +18,12 @@ export const StartProcessArgsSchema = z.object({
18
18
  command: z.string(),
19
19
  timeout_ms: z.number(),
20
20
  shell: z.string().optional(),
21
+ verbose_timing: z.boolean().optional(),
21
22
  });
22
23
  export const ReadProcessOutputArgsSchema = z.object({
23
24
  pid: z.number(),
24
25
  timeout_ms: z.number().optional(),
26
+ verbose_timing: z.boolean().optional(),
25
27
  });
26
28
  export const ForceTerminateArgsSchema = z.object({
27
29
  pid: z.number(),
@@ -72,6 +74,7 @@ export const InteractWithProcessArgsSchema = z.object({
72
74
  input: z.string(),
73
75
  timeout_ms: z.number().optional(),
74
76
  wait_for_prompt: z.boolean().optional(),
77
+ verbose_timing: z.boolean().optional(),
75
78
  });
76
79
  // Usage stats schema
77
80
  export const GetUsageStatsArgsSchema = z.object({});
package/dist/types.d.ts CHANGED
@@ -21,6 +21,25 @@ export interface CommandExecutionResult {
21
21
  pid: number;
22
22
  output: string;
23
23
  isBlocked: boolean;
24
+ timingInfo?: TimingInfo;
25
+ }
26
+ export interface TimingInfo {
27
+ startTime: number;
28
+ endTime: number;
29
+ totalDurationMs: number;
30
+ exitReason: 'early_exit_quick_pattern' | 'early_exit_periodic_check' | 'process_exit' | 'timeout';
31
+ firstOutputTime?: number;
32
+ lastOutputTime?: number;
33
+ timeToFirstOutputMs?: number;
34
+ outputEvents?: OutputEvent[];
35
+ }
36
+ export interface OutputEvent {
37
+ timestamp: number;
38
+ deltaMs: number;
39
+ source: 'stdout' | 'stderr';
40
+ length: number;
41
+ snippet: string;
42
+ matchedPattern?: string;
24
43
  }
25
44
  export interface ActiveSession {
26
45
  pid: number;
@@ -0,0 +1,43 @@
1
+ declare class FeatureFlagManager {
2
+ private flags;
3
+ private lastFetch;
4
+ private cachePath;
5
+ private cacheMaxAge;
6
+ private flagUrl;
7
+ private refreshInterval;
8
+ constructor();
9
+ /**
10
+ * Initialize - load from cache and start background refresh
11
+ */
12
+ initialize(): Promise<void>;
13
+ /**
14
+ * Get a flag value
15
+ */
16
+ get(flagName: string, defaultValue?: any): any;
17
+ /**
18
+ * Get all flags for debugging
19
+ */
20
+ getAll(): Record<string, any>;
21
+ /**
22
+ * Manually refresh flags immediately (for testing)
23
+ */
24
+ refresh(): Promise<boolean>;
25
+ /**
26
+ * Load flags from local cache
27
+ */
28
+ private loadFromCache;
29
+ /**
30
+ * Fetch flags from remote URL
31
+ */
32
+ private fetchFlags;
33
+ /**
34
+ * Save flags to local cache
35
+ */
36
+ private saveToCache;
37
+ /**
38
+ * Cleanup on shutdown
39
+ */
40
+ destroy(): void;
41
+ }
42
+ export declare const featureFlagManager: FeatureFlagManager;
43
+ export {};