@wonderwhy-er/desktop-commander 0.2.39 → 0.2.41
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.
- package/README.md +4 -2
- package/dist/handlers/filesystem-handlers.js +6 -0
- package/dist/server.js +2 -1
- package/dist/tools/filesystem.js +48 -14
- package/dist/types.d.ts +1 -0
- package/dist/ui/file-preview/preview-runtime.js +204 -153
- package/dist/ui/file-preview/src/{App.js → app.js} +0 -5
- package/dist/ui/file-preview/src/directory-controller.js +9 -2
- package/dist/ui/file-preview/src/file-type-handlers.js +20 -9
- package/dist/ui/file-preview/src/markdown/controller.d.ts +7 -1
- package/dist/ui/file-preview/src/markdown/controller.js +135 -16
- package/dist/ui/file-preview/src/markdown/editor.d.ts +97 -1
- package/dist/ui/file-preview/src/markdown/editor.js +814 -26
- package/dist/ui/file-preview/src/model.d.ts +2 -1
- package/dist/ui/file-preview/src/payload-utils.js +10 -1
- package/dist/utils/capture.js +1 -1
- package/dist/utils/feature-flags.d.ts +3 -0
- package/dist/utils/feature-flags.js +34 -5
- package/dist/utils/files/excel.js +26 -5
- package/dist/utils/toolHistory.d.ts +13 -0
- package/dist/utils/toolHistory.js +65 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/dist/ui/config-editor/app.js +0 -840
- package/dist/ui/config-editor/array-modal.d.ts +0 -19
- package/dist/ui/config-editor/array-modal.js +0 -185
- package/dist/ui/config-editor/main.d.ts +0 -1
- package/dist/ui/config-editor/main.js +0 -2
- package/dist/ui/config-editor/src/App.d.ts +0 -43
- package/dist/ui/config-editor/src/components/layout.d.ts +0 -4
- package/dist/ui/config-editor/src/components/layout.js +0 -83
- package/dist/ui/config-editor/src/components/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/components/toolbar.js +0 -21
- package/dist/ui/config-editor/src/config-values.d.ts +0 -6
- package/dist/ui/config-editor/src/config-values.js +0 -61
- package/dist/ui/config-editor/src/contracts.d.ts +0 -14
- package/dist/ui/config-editor/src/contracts.js +0 -3
- package/dist/ui/config-editor/src/directory-browser.d.ts +0 -6
- package/dist/ui/config-editor/src/directory-browser.js +0 -71
- package/dist/ui/config-editor/src/layout.d.ts +0 -5
- package/dist/ui/config-editor/src/layout.js +0 -90
- package/dist/ui/config-editor/src/parsing.d.ts +0 -5
- package/dist/ui/config-editor/src/parsing.js +0 -50
- package/dist/ui/config-editor/src/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/toolbar.js +0 -18
- package/dist/ui/config-editor/src/types.d.ts +0 -17
- package/dist/ui/config-editor/src/types.js +0 -3
- package/dist/ui/config-editor/src/utils/config-values.d.ts +0 -9
- package/dist/ui/config-editor/src/utils/config-values.js +0 -61
- package/dist/ui/config-editor/src/utils/directory-browser.d.ts +0 -31
- package/dist/ui/config-editor/src/utils/directory-browser.js +0 -201
- package/dist/ui/config-editor/src/utils/parsing.d.ts +0 -8
- package/dist/ui/config-editor/src/utils/parsing.js +0 -50
- package/dist/ui/file-preview/app.d.ts +0 -8
- package/dist/ui/file-preview/app.js +0 -2020
- package/dist/ui/file-preview/components/code-viewer.d.ts +0 -6
- package/dist/ui/file-preview/components/code-viewer.js +0 -73
- package/dist/ui/file-preview/components/highlighting.d.ts +0 -2
- package/dist/ui/file-preview/components/highlighting.js +0 -54
- package/dist/ui/file-preview/components/html-renderer.d.ts +0 -5
- package/dist/ui/file-preview/components/html-renderer.js +0 -47
- package/dist/ui/file-preview/components/markdown-renderer.d.ts +0 -1
- package/dist/ui/file-preview/components/markdown-renderer.js +0 -67
- package/dist/ui/file-preview/components/toolbar.d.ts +0 -6
- package/dist/ui/file-preview/components/toolbar.js +0 -75
- package/dist/ui/file-preview/image-preview.d.ts +0 -3
- package/dist/ui/file-preview/image-preview.js +0 -21
- package/dist/ui/file-preview/main.d.ts +0 -1
- package/dist/ui/file-preview/main.js +0 -5
- package/dist/ui/file-preview/markdown/editor.d.ts +0 -36
- package/dist/ui/file-preview/markdown/editor.js +0 -643
- package/dist/ui/file-preview/markdown/linking.d.ts +0 -9
- package/dist/ui/file-preview/markdown/linking.js +0 -210
- package/dist/ui/file-preview/markdown/outline.d.ts +0 -7
- package/dist/ui/file-preview/markdown/outline.js +0 -40
- package/dist/ui/file-preview/markdown/preview.d.ts +0 -8
- package/dist/ui/file-preview/markdown/preview.js +0 -33
- package/dist/ui/file-preview/markdown/slugify.d.ts +0 -3
- package/dist/ui/file-preview/markdown/slugify.js +0 -31
- package/dist/ui/file-preview/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/markdown/toc.js +0 -75
- package/dist/ui/file-preview/markdown/utils.d.ts +0 -1
- package/dist/ui/file-preview/markdown/utils.js +0 -15
- package/dist/ui/file-preview/markdown/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/markdown/workspace-controller.js +0 -40
- package/dist/ui/file-preview/src/components/CodeViewer.d.ts +0 -6
- package/dist/ui/file-preview/src/components/CodeViewer.js +0 -60
- package/dist/ui/file-preview/src/components/HtmlRenderer.d.ts +0 -8
- package/dist/ui/file-preview/src/components/HtmlRenderer.js +0 -45
- package/dist/ui/file-preview/src/components/MarkdownRenderer.d.ts +0 -1
- package/dist/ui/file-preview/src/components/MarkdownRenderer.js +0 -15
- package/dist/ui/file-preview/src/components/Toolbar.d.ts +0 -6
- package/dist/ui/file-preview/src/components/Toolbar.js +0 -75
- package/dist/ui/file-preview/src/components/editor-toolbar.d.ts +0 -15
- package/dist/ui/file-preview/src/components/editor-toolbar.js +0 -384
- package/dist/ui/file-preview/src/components/markdown-editor.d.ts +0 -29
- package/dist/ui/file-preview/src/components/markdown-editor.js +0 -535
- package/dist/ui/file-preview/src/markdown/block-merge.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown/block-merge.js +0 -86
- package/dist/ui/file-preview/src/markdown/link-modal.d.ts +0 -13
- package/dist/ui/file-preview/src/markdown/link-modal.js +0 -213
- package/dist/ui/file-preview/src/markdown/raw-editor.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown/raw-editor.js +0 -61
- package/dist/ui/file-preview/src/markdown/selection-toolbar.d.ts +0 -14
- package/dist/ui/file-preview/src/markdown/selection-toolbar.js +0 -128
- package/dist/ui/file-preview/src/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/editor.d.ts +0 -36
- package/dist/ui/file-preview/src/markdown-workspace/editor.js +0 -643
- package/dist/ui/file-preview/src/markdown-workspace/linking.d.ts +0 -9
- package/dist/ui/file-preview/src/markdown-workspace/linking.js +0 -210
- package/dist/ui/file-preview/src/markdown-workspace/outline.d.ts +0 -7
- package/dist/ui/file-preview/src/markdown-workspace/outline.js +0 -40
- package/dist/ui/file-preview/src/markdown-workspace/preview.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown-workspace/preview.js +0 -33
- package/dist/ui/file-preview/src/markdown-workspace/slugify.d.ts +0 -3
- package/dist/ui/file-preview/src/markdown-workspace/slugify.js +0 -31
- package/dist/ui/file-preview/src/markdown-workspace/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown-workspace/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/utils.d.ts +0 -1
- package/dist/ui/file-preview/src/markdown-workspace/utils.js +0 -15
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.js +0 -40
- package/dist/ui/file-preview/types.d.ts +0 -1
- package/dist/ui/file-preview/types.js +0 -1
- package/dist/ui/server-integration.d.ts +0 -13
- package/dist/ui/server-integration.js +0 -31
- package/dist/ui/shared/ToolHeader.d.ts +0 -9
- package/dist/ui/shared/ToolHeader.js +0 -29
- package/dist/ui/shared/app-bootstrap.d.ts +0 -9
- package/dist/ui/shared/app-bootstrap.js +0 -15
- package/dist/ui/shared/guards.d.ts +0 -1
- package/dist/ui/shared/guards.js +0 -3
- package/dist/ui/shared/host-lifecycle.d.ts +0 -17
- package/dist/ui/shared/host-lifecycle.js +0 -41
- package/dist/ui/shared/rpc-client.d.ts +0 -14
- package/dist/ui/shared/rpc-client.js +0 -72
- package/dist/ui/shared/theme-adaptation.d.ts +0 -10
- package/dist/ui/shared/theme-adaptation.js +0 -118
- package/dist/ui/shared/tool-header.d.ts +0 -9
- package/dist/ui/shared/tool-header.js +0 -25
- package/dist/utils/ui-call-context.d.ts +0 -8
- package/dist/utils/ui-call-context.js +0 -72
- /package/dist/ui/config-editor/{app.d.ts → src/app.d.ts} +0 -0
- /package/dist/ui/config-editor/src/{App.js → app.js} +0 -0
- /package/dist/ui/file-preview/src/{App.d.ts → app.d.ts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DocumentOutlineItem } from './document-outline.js';
|
|
2
2
|
import type { FilePreviewStructuredContent } from '../../../types.js';
|
|
3
|
-
import type { MarkdownEditorView } from './markdown/editor.js';
|
|
3
|
+
import type { MarkdownEditRange, MarkdownEditorView } from './markdown/editor.js';
|
|
4
4
|
export type RenderPayload = FilePreviewStructuredContent & {
|
|
5
5
|
content: string;
|
|
6
6
|
};
|
|
@@ -12,6 +12,7 @@ export interface MarkdownWorkspaceState {
|
|
|
12
12
|
outline: DocumentOutlineItem[];
|
|
13
13
|
mode: 'edit';
|
|
14
14
|
dirty: boolean;
|
|
15
|
+
dirtyLineRanges: MarkdownEditRange[];
|
|
15
16
|
activeHeadingId: string | null;
|
|
16
17
|
pendingAnchor: string | null;
|
|
17
18
|
notice: string | null;
|
|
@@ -41,6 +41,12 @@ export function extractToolText(value) {
|
|
|
41
41
|
}
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
|
+
function extractStructuredContentText(value) {
|
|
45
|
+
if (!isObjectRecord(value)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return typeof value.content === 'string' ? value.content : undefined;
|
|
49
|
+
}
|
|
44
50
|
export function extractRenderPayload(value) {
|
|
45
51
|
if (!isObjectRecord(value)) {
|
|
46
52
|
return undefined;
|
|
@@ -52,7 +58,10 @@ export function extractRenderPayload(value) {
|
|
|
52
58
|
: null;
|
|
53
59
|
if (!meta)
|
|
54
60
|
return undefined;
|
|
55
|
-
const text =
|
|
61
|
+
const text = extractStructuredContentText(value.structuredContent)
|
|
62
|
+
?? extractToolText(value)
|
|
63
|
+
?? extractToolText(value.structuredContent)
|
|
64
|
+
?? '';
|
|
56
65
|
return buildRenderPayload(meta, text);
|
|
57
66
|
}
|
|
58
67
|
export function assertSuccessfulEditBlockResult(result) {
|
package/dist/utils/capture.js
CHANGED
|
@@ -381,7 +381,7 @@ export const capture_call_tool = async (event, properties) => {
|
|
|
381
381
|
const GA_NEW_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_NEW_ID}&api_secret=${GA_NEW_SECRET}`;
|
|
382
382
|
// Route highest-volume tools to new property, rest to old
|
|
383
383
|
const HIGH_VOLUME_TOOLS = ['start_process', 'track_ui_event'];
|
|
384
|
-
const toolName = properties?.name;
|
|
384
|
+
const toolName = properties?.tool_name ?? properties?.name;
|
|
385
385
|
const gaUrl = HIGH_VOLUME_TOOLS.includes(toolName) ? GA_NEW_URL : GA_OLD_URL;
|
|
386
386
|
// Build properties once, send to GA4 + telemetry proxy in parallel
|
|
387
387
|
const eventProperties = await buildEventProperties(properties);
|
|
@@ -33,6 +33,9 @@ declare class FeatureFlagManager {
|
|
|
33
33
|
* Wait for fresh flags to be fetched from network.
|
|
34
34
|
* Use this when you need to ensure flags are loaded before making decisions
|
|
35
35
|
* (e.g., A/B test assignments for new users who don't have a cache yet)
|
|
36
|
+
*
|
|
37
|
+
* Has a hard timeout to prevent blocking MCP startup if the fetch hangs.
|
|
38
|
+
* See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
|
|
36
39
|
*/
|
|
37
40
|
waitForFreshFlags(): Promise<void>;
|
|
38
41
|
/**
|
|
@@ -93,10 +93,24 @@ class FeatureFlagManager {
|
|
|
93
93
|
* Wait for fresh flags to be fetched from network.
|
|
94
94
|
* Use this when you need to ensure flags are loaded before making decisions
|
|
95
95
|
* (e.g., A/B test assignments for new users who don't have a cache yet)
|
|
96
|
+
*
|
|
97
|
+
* Has a hard timeout to prevent blocking MCP startup if the fetch hangs.
|
|
98
|
+
* See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
|
|
96
99
|
*/
|
|
97
100
|
async waitForFreshFlags() {
|
|
98
101
|
if (this.freshFetchPromise) {
|
|
99
|
-
|
|
102
|
+
let safetyTimeoutHandle;
|
|
103
|
+
try {
|
|
104
|
+
const safetyTimeout = new Promise((resolve) => {
|
|
105
|
+
safetyTimeoutHandle = setTimeout(resolve, 5000);
|
|
106
|
+
});
|
|
107
|
+
await Promise.race([this.freshFetchPromise, safetyTimeout]);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (safetyTimeoutHandle) {
|
|
111
|
+
clearTimeout(safetyTimeoutHandle);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
/**
|
|
@@ -127,17 +141,26 @@ class FeatureFlagManager {
|
|
|
127
141
|
* Fetch flags from remote URL
|
|
128
142
|
*/
|
|
129
143
|
async fetchFlags() {
|
|
144
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
145
|
+
const controller = new AbortController();
|
|
146
|
+
const abortTimeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
147
|
+
let hardTimeoutHandle;
|
|
130
148
|
try {
|
|
131
149
|
// Don't log here - runs async and can interfere with MCP clients
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
// Use Promise.race as a hard timeout safety net.
|
|
151
|
+
// On some platforms (Windows + Node 24 / undici 7.x), AbortController.abort()
|
|
152
|
+
// fails to interrupt an in-progress TCP connect — the fetch hangs until the
|
|
153
|
+
// OS-level TCP timeout (~30s on Windows). Promise.race guarantees we reject
|
|
154
|
+
// at the JS level regardless of AbortController behavior.
|
|
155
|
+
// See: https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/465
|
|
156
|
+
const fetchPromise = fetch(this.flagUrl, {
|
|
135
157
|
signal: controller.signal,
|
|
136
158
|
headers: {
|
|
137
159
|
'Cache-Control': 'no-cache',
|
|
138
160
|
}
|
|
139
161
|
});
|
|
140
|
-
|
|
162
|
+
const hardTimeout = new Promise((_, reject) => hardTimeoutHandle = setTimeout(() => reject(new Error('Feature flags fetch timed out')), FETCH_TIMEOUT_MS));
|
|
163
|
+
const response = await Promise.race([fetchPromise, hardTimeout]);
|
|
141
164
|
if (!response.ok) {
|
|
142
165
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
143
166
|
}
|
|
@@ -155,6 +178,12 @@ class FeatureFlagManager {
|
|
|
155
178
|
logger.debug('Failed to fetch feature flags:', error.message);
|
|
156
179
|
// Continue with cached values
|
|
157
180
|
}
|
|
181
|
+
finally {
|
|
182
|
+
clearTimeout(abortTimeout);
|
|
183
|
+
if (hardTimeoutHandle) {
|
|
184
|
+
clearTimeout(hardTimeoutHandle);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
158
187
|
}
|
|
159
188
|
/**
|
|
160
189
|
* Save flags to local cache
|
|
@@ -25,8 +25,10 @@ export class ExcelFileHandler {
|
|
|
25
25
|
const paginationInfo = totalRows > returnedRows
|
|
26
26
|
? `\n[Showing rows ${(options?.offset || 0) + 1}-${(options?.offset || 0) + returnedRows} of ${totalRows} total. Use offset/length to paginate.]`
|
|
27
27
|
: '';
|
|
28
|
+
const sheetHasSpace = /\s/.test(sheetName);
|
|
29
|
+
const exampleSheet = sheetHasSpace ? sheetName : 'Sheet1';
|
|
28
30
|
const content = `[Sheet: '${sheetName}' from ${path}]${paginationInfo}
|
|
29
|
-
[To MODIFY cells: use edit_block with range param, e.g., edit_block(path, {range: "
|
|
31
|
+
[To MODIFY cells: use edit_block with range param, e.g., edit_block(path, {range: "${exampleSheet}!E5", content: [[newValue]]}). read_file accepts the same range form, or pass sheet + range separately.]
|
|
30
32
|
|
|
31
33
|
${JSON.stringify(data)}`;
|
|
32
34
|
return {
|
|
@@ -260,6 +262,17 @@ ${JSON.stringify(data)}`;
|
|
|
260
262
|
if (workbook.worksheets.length === 0) {
|
|
261
263
|
return { sheetName: '', data: [], totalRows: 0, returnedRows: 0 };
|
|
262
264
|
}
|
|
265
|
+
// Accept range with embedded sheet prefix (parity with edit_block).
|
|
266
|
+
// E.g. range:"Sheet1!A1:B2" or "'My Sheet'!A1" — strip the sheet
|
|
267
|
+
// prefix and, when the caller did not pass an explicit sheet, use it.
|
|
268
|
+
let cellRangeOnly = range;
|
|
269
|
+
if (range && range.includes('!')) {
|
|
270
|
+
const [sheetFromRange, cellsFromRange] = this.parseRange(range);
|
|
271
|
+
cellRangeOnly = cellsFromRange ?? undefined;
|
|
272
|
+
if (sheetRef === undefined && sheetFromRange) {
|
|
273
|
+
sheetRef = sheetFromRange;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
263
276
|
// Find target worksheet
|
|
264
277
|
let worksheet;
|
|
265
278
|
let sheetName;
|
|
@@ -287,8 +300,8 @@ ${JSON.stringify(data)}`;
|
|
|
287
300
|
let endRow = worksheet.actualRowCount || 1;
|
|
288
301
|
let startCol = 1;
|
|
289
302
|
let endCol = worksheet.actualColumnCount || 1;
|
|
290
|
-
if (
|
|
291
|
-
const parsed = this.parseCellRange(
|
|
303
|
+
if (cellRangeOnly) {
|
|
304
|
+
const parsed = this.parseCellRange(cellRangeOnly);
|
|
292
305
|
startRow = parsed.startRow;
|
|
293
306
|
startCol = parsed.startCol;
|
|
294
307
|
if (parsed.endRow)
|
|
@@ -386,7 +399,14 @@ ${JSON.stringify(data)}`;
|
|
|
386
399
|
}
|
|
387
400
|
parseRange(range) {
|
|
388
401
|
if (range.includes('!')) {
|
|
389
|
-
const
|
|
402
|
+
const idx = range.indexOf('!');
|
|
403
|
+
let sheetName = range.slice(0, idx);
|
|
404
|
+
const cellRange = range.slice(idx + 1);
|
|
405
|
+
// Strip Excel-native single quotes around sheet names with spaces:
|
|
406
|
+
// 'My Sheet'!A1 → My Sheet, A1
|
|
407
|
+
if (sheetName.length >= 2 && sheetName.startsWith("'") && sheetName.endsWith("'")) {
|
|
408
|
+
sheetName = sheetName.slice(1, -1).replace(/''/g, "'");
|
|
409
|
+
}
|
|
390
410
|
return [sheetName, cellRange];
|
|
391
411
|
}
|
|
392
412
|
return [range, null];
|
|
@@ -395,7 +415,8 @@ ${JSON.stringify(data)}`;
|
|
|
395
415
|
// Parse A1 or A1:C10 format
|
|
396
416
|
const match = range.match(/^([A-Z]+)(\d+)(?::([A-Z]+)(\d+))?$/i);
|
|
397
417
|
if (!match) {
|
|
398
|
-
throw new Error(`Invalid cell range: ${range}`
|
|
418
|
+
throw new Error(`Invalid cell range: "${range}". Expected forms: "A1", "A1:C10", or "SheetName!A1:C10" ` +
|
|
419
|
+
`(single-quote sheet names containing spaces: "'My Sheet'!A1:C10").`);
|
|
399
420
|
}
|
|
400
421
|
const startCol = this.columnToNumber(match[1]);
|
|
401
422
|
const startRow = parseInt(match[2], 10);
|
|
@@ -12,6 +12,8 @@ interface FormattedToolCallRecord extends Omit<ToolCallRecord, 'timestamp'> {
|
|
|
12
12
|
declare class ToolHistory {
|
|
13
13
|
private history;
|
|
14
14
|
private readonly MAX_ENTRIES;
|
|
15
|
+
private readonly MAX_HISTORY_FILE_SIZE_BYTES;
|
|
16
|
+
private readonly HISTORY_FILE_TRIM_TARGET_BYTES;
|
|
15
17
|
private readonly historyFile;
|
|
16
18
|
private writeQueue;
|
|
17
19
|
private isWriting;
|
|
@@ -21,6 +23,17 @@ declare class ToolHistory {
|
|
|
21
23
|
* Load history from disk (all instances share the same file)
|
|
22
24
|
*/
|
|
23
25
|
private loadFromDisk;
|
|
26
|
+
/**
|
|
27
|
+
* Trim the on-disk history file to stay under the size cap by dropping the
|
|
28
|
+
* oldest entries (lines) until the kept tail fits within the trim target.
|
|
29
|
+
* Returns true only when the file was actually rewritten with a smaller
|
|
30
|
+
* tail, so callers can fall through to their normal path on failure or
|
|
31
|
+
* no-op rather than mutating in-memory state.
|
|
32
|
+
*
|
|
33
|
+
* Always keeps at least the most recent entry, even if a single record
|
|
34
|
+
* exceeds the trim target — there is no useful state below that.
|
|
35
|
+
*/
|
|
36
|
+
private trimHistoryFileIfTooLarge;
|
|
24
37
|
/**
|
|
25
38
|
* Trim history file to prevent it from growing indefinitely
|
|
26
39
|
*/
|
|
@@ -18,6 +18,11 @@ class ToolHistory {
|
|
|
18
18
|
constructor() {
|
|
19
19
|
this.history = [];
|
|
20
20
|
this.MAX_ENTRIES = 1000;
|
|
21
|
+
this.MAX_HISTORY_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
22
|
+
// When the file exceeds the cap we trim it down to this target instead of
|
|
23
|
+
// all the way to zero, so a single overflow doesn't cause every subsequent
|
|
24
|
+
// flush to re-trim.
|
|
25
|
+
this.HISTORY_FILE_TRIM_TARGET_BYTES = 4 * 1024 * 1024;
|
|
21
26
|
this.writeQueue = [];
|
|
22
27
|
this.isWriting = false;
|
|
23
28
|
// Store history in same directory as config to keep everything together
|
|
@@ -41,6 +46,9 @@ class ToolHistory {
|
|
|
41
46
|
if (!fs.existsSync(this.historyFile)) {
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
49
|
+
// If the file is over the cap, trim it down before reading so we
|
|
50
|
+
// load a bounded amount.
|
|
51
|
+
this.trimHistoryFileIfTooLarge();
|
|
44
52
|
const content = fs.readFileSync(this.historyFile, 'utf-8');
|
|
45
53
|
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
46
54
|
// Parse each line as JSON
|
|
@@ -64,6 +72,58 @@ class ToolHistory {
|
|
|
64
72
|
// Silently fail
|
|
65
73
|
}
|
|
66
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Trim the on-disk history file to stay under the size cap by dropping the
|
|
77
|
+
* oldest entries (lines) until the kept tail fits within the trim target.
|
|
78
|
+
* Returns true only when the file was actually rewritten with a smaller
|
|
79
|
+
* tail, so callers can fall through to their normal path on failure or
|
|
80
|
+
* no-op rather than mutating in-memory state.
|
|
81
|
+
*
|
|
82
|
+
* Always keeps at least the most recent entry, even if a single record
|
|
83
|
+
* exceeds the trim target — there is no useful state below that.
|
|
84
|
+
*/
|
|
85
|
+
trimHistoryFileIfTooLarge() {
|
|
86
|
+
let stats;
|
|
87
|
+
try {
|
|
88
|
+
if (!fs.existsSync(this.historyFile)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
stats = fs.statSync(this.historyFile);
|
|
92
|
+
if (stats.size <= this.MAX_HISTORY_FILE_SIZE_BYTES) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const content = fs.readFileSync(this.historyFile, 'utf-8');
|
|
101
|
+
const lines = content.split('\n').filter(line => line.length > 0);
|
|
102
|
+
if (lines.length === 0) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Walk lines from newest to oldest, accumulating bytes (line + '\n'),
|
|
106
|
+
// and keep as many as fit within the trim target. Always keep at
|
|
107
|
+
// least the last line.
|
|
108
|
+
const kept = [];
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
111
|
+
const lineBytes = Buffer.byteLength(lines[i], 'utf-8') + 1; // +1 for '\n'
|
|
112
|
+
if (kept.length > 0 && bytes + lineBytes > this.HISTORY_FILE_TRIM_TARGET_BYTES) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
kept.push(lines[i]);
|
|
116
|
+
bytes += lineBytes;
|
|
117
|
+
}
|
|
118
|
+
kept.reverse();
|
|
119
|
+
fs.writeFileSync(this.historyFile, kept.join('\n') + '\n', 'utf-8');
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
// Trim failed; do not claim the file was changed.
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
67
127
|
/**
|
|
68
128
|
* Trim history file to prevent it from growing indefinitely
|
|
69
129
|
*/
|
|
@@ -101,6 +161,11 @@ class ToolHistory {
|
|
|
101
161
|
const toWrite = [...this.writeQueue];
|
|
102
162
|
this.writeQueue = [];
|
|
103
163
|
try {
|
|
164
|
+
// If the on-disk file has grown past the cap, trim it down to the
|
|
165
|
+
// target size (keeping the most recent entries) before appending.
|
|
166
|
+
// The in-memory cache is unaffected — it is already bounded by
|
|
167
|
+
// MAX_ENTRIES via addCall.
|
|
168
|
+
this.trimHistoryFileIfTooLarge();
|
|
104
169
|
// Append to file (atomic append operation)
|
|
105
170
|
const lines = toWrite.map(entry => JSON.stringify(entry)).join('\n') + '\n';
|
|
106
171
|
fs.appendFileSync(this.historyFile, lines, 'utf-8');
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.2.
|
|
1
|
+
export declare const VERSION = "0.2.41";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
1
|
+
export const VERSION = '0.2.41';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonderwhy-er/desktop-commander",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.41",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"mcpName": "io.github.wonderwhy-er/desktop-commander",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"release:dry": "node scripts/publish-release.cjs --dry-run",
|
|
54
54
|
"release:mcp": "node scripts/publish-release.cjs --mcp-only",
|
|
55
55
|
"release:alpha": "node scripts/publish-release.cjs --npm-only --alpha",
|
|
56
|
+
"release:skip-mcp": "node scripts/publish-release.cjs --skip-mcp",
|
|
56
57
|
"logs:view": "npm run build && node scripts/view-fuzzy-logs.js",
|
|
57
58
|
"logs:analyze": "npm run build && node scripts/analyze-fuzzy-logs.js",
|
|
58
59
|
"logs:clear": "npm run build && node scripts/clear-fuzzy-logs.js",
|
|
@@ -88,6 +89,10 @@
|
|
|
88
89
|
"@supabase/supabase-js": "^2.89.0",
|
|
89
90
|
"@tiptap/core": "^3.22.3",
|
|
90
91
|
"@tiptap/extension-image": "^3.22.3",
|
|
92
|
+
"@tiptap/extension-table": "^3.22.4",
|
|
93
|
+
"@tiptap/extension-table-cell": "^3.22.4",
|
|
94
|
+
"@tiptap/extension-table-header": "^3.22.4",
|
|
95
|
+
"@tiptap/extension-table-row": "^3.22.4",
|
|
91
96
|
"@tiptap/pm": "^3.22.3",
|
|
92
97
|
"@tiptap/starter-kit": "^3.22.3",
|
|
93
98
|
"@vscode/ripgrep": "^1.15.9",
|
|
@@ -123,6 +128,7 @@
|
|
|
123
128
|
"commander": "^13.1.0",
|
|
124
129
|
"esbuild": "^0.27.2",
|
|
125
130
|
"js-tiktoken": "^1.0.21",
|
|
131
|
+
"jsdom": "^24.1.3",
|
|
126
132
|
"nexe": "^5.0.0-beta.4",
|
|
127
133
|
"nodemon": "^3.0.2",
|
|
128
134
|
"shx": "^0.3.4",
|