starmux 0.12.0 → 0.13.0
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/dist/src/cli/mac-mcp-gateway.js +4 -3
- package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
- package/dist/src/cli/starmux.js +1 -1
- package/dist/src/cli/starmux.js.map +1 -1
- package/dist/src/runtime-paths.js +3 -3
- package/dist/src/runtime-paths.js.map +1 -1
- package/dist/vendor/desktop-commander/LICENSE +21 -0
- package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
- package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
- package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
- package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
- package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
- package/dist/vendor/desktop-commander/dist/config.js +19 -0
- package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
- package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
- package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
- package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
- package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
- package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
- package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
- package/dist/vendor/desktop-commander/dist/index.js +88 -0
- package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
- package/dist/vendor/desktop-commander/dist/server.js +1414 -0
- package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
- package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
- package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
- package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
- package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
- package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
- package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
- package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
- package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
- package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
- package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
- package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
- package/dist/vendor/desktop-commander/dist/types.js +1 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
- package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
- package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
- package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
- package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
- package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
- package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
- package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
- package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
- package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
- package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
- package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
- package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
- package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
- package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
- package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
- package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
- package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
- package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
- package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
- package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
- package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
- package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
- package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
- package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
- package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
- package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
- package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
- package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
- package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
- package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
- package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
- package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
- package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
- package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
- package/dist/vendor/desktop-commander/dist/version.js +1 -0
- package/dist/vendor/desktop-commander/package.json +15 -0
- package/package.json +14 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side UI resource loading helpers for MCP responses. It resolves packaged UI assets, reads files safely, and exposes structured resource payloads to clients.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { buildUiContentSecurityPolicy, CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI, UI_FONT_ORIGINS, UI_RESOURCE_CSP } from './contracts.js';
|
|
8
|
+
const UI_RESOURCE_MIME_TYPE = 'text/html;profile=mcp-app';
|
|
9
|
+
export const FILE_PREVIEW_RESOURCE = {
|
|
10
|
+
uri: FILE_PREVIEW_RESOURCE_URI,
|
|
11
|
+
name: 'Desktop Commander File Preview',
|
|
12
|
+
description: 'Markdown-first preview surface for read_file structured content.',
|
|
13
|
+
mimeType: UI_RESOURCE_MIME_TYPE
|
|
14
|
+
};
|
|
15
|
+
export const CONFIG_EDITOR_RESOURCE = {
|
|
16
|
+
uri: CONFIG_EDITOR_RESOURCE_URI,
|
|
17
|
+
name: 'Desktop Commander Config Editor',
|
|
18
|
+
description: 'Interactive editor for Desktop Commander configuration values.',
|
|
19
|
+
mimeType: UI_RESOURCE_MIME_TYPE
|
|
20
|
+
};
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = path.dirname(__filename);
|
|
23
|
+
const DIST_FILE_PREVIEW_DIR = path.resolve(__dirname, 'file-preview');
|
|
24
|
+
const DIST_CONFIG_EDITOR_DIR = path.resolve(__dirname, 'config-editor');
|
|
25
|
+
function replaceOrThrow(source, pattern, replacement, context) {
|
|
26
|
+
if (!pattern.test(source)) {
|
|
27
|
+
throw new Error(`UI template is missing expected ${context}`);
|
|
28
|
+
}
|
|
29
|
+
return source.replace(pattern, () => replacement);
|
|
30
|
+
}
|
|
31
|
+
function inlineTemplateAssets(templateHtml, css, runtime) {
|
|
32
|
+
const safeCss = css.replace(/<\/style/gi, '<\\/style');
|
|
33
|
+
const safeRuntime = runtime.replace(/<\/script/gi, '<\\/script');
|
|
34
|
+
// First in <head>, so it covers everything the page loads, previews included
|
|
35
|
+
const policyAdded = replaceOrThrow(templateHtml, /<head>/i, `<head><meta http-equiv="Content-Security-Policy" content="${buildUiContentSecurityPolicy(UI_RESOURCE_CSP, UI_FONT_ORIGINS)}">`, '<head> tag');
|
|
36
|
+
const cssInlined = replaceOrThrow(policyAdded, /<link[^>]*href=["']\.\/styles\.css["'][^>]*>/i, `<style>${safeCss}</style>`, 'styles.css link tag');
|
|
37
|
+
const runtimeInlined = replaceOrThrow(cssInlined, /<script[^>]*src=["']\.\/[^"']+["'][^>]*>\s*<\/script>/i, `<script>${safeRuntime}</script>`, 'runtime script tag');
|
|
38
|
+
if (/href=["']\.\/styles\.css["']/i.test(runtimeInlined) ||
|
|
39
|
+
/<script[^>]*src=["']\.\/[^"']+["']/i.test(runtimeInlined)) {
|
|
40
|
+
throw new Error('UI template still contains external static asset references after inlining');
|
|
41
|
+
}
|
|
42
|
+
return runtimeInlined;
|
|
43
|
+
}
|
|
44
|
+
async function readInlinedResourceHtml(distDir, runtimeFileName) {
|
|
45
|
+
const [templateHtml, css, runtime] = await Promise.all([
|
|
46
|
+
fs.readFile(path.join(distDir, 'index.html'), 'utf8'),
|
|
47
|
+
fs.readFile(path.join(distDir, 'styles.css'), 'utf8'),
|
|
48
|
+
fs.readFile(path.join(distDir, runtimeFileName), 'utf8')
|
|
49
|
+
]);
|
|
50
|
+
return inlineTemplateAssets(templateHtml, css, runtime);
|
|
51
|
+
}
|
|
52
|
+
export async function getFilePreviewResourceText() {
|
|
53
|
+
return readInlinedResourceHtml(DIST_FILE_PREVIEW_DIR, 'preview-runtime.js');
|
|
54
|
+
}
|
|
55
|
+
export async function getConfigEditorResourceText() {
|
|
56
|
+
return readInlinedResourceHtml(DIST_CONFIG_EDITOR_DIR, 'config-editor-runtime.js');
|
|
57
|
+
}
|
|
58
|
+
function getUiResourceMeta() {
|
|
59
|
+
return { ui: { csp: UI_RESOURCE_CSP } };
|
|
60
|
+
}
|
|
61
|
+
const READABLE_UI_RESOURCES = {
|
|
62
|
+
[FILE_PREVIEW_RESOURCE_URI]: {
|
|
63
|
+
mimeType: FILE_PREVIEW_RESOURCE.mimeType,
|
|
64
|
+
getText: getFilePreviewResourceText,
|
|
65
|
+
getMeta: getUiResourceMeta
|
|
66
|
+
},
|
|
67
|
+
[CONFIG_EDITOR_RESOURCE_URI]: {
|
|
68
|
+
mimeType: CONFIG_EDITOR_RESOURCE.mimeType,
|
|
69
|
+
getText: getConfigEditorResourceText,
|
|
70
|
+
getMeta: getUiResourceMeta
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
export function listUiResources() {
|
|
74
|
+
return [FILE_PREVIEW_RESOURCE, CONFIG_EDITOR_RESOURCE];
|
|
75
|
+
}
|
|
76
|
+
export async function readUiResource(uri) {
|
|
77
|
+
const resource = READABLE_UI_RESOURCES[uri];
|
|
78
|
+
if (!resource) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const resourceText = await resource.getText();
|
|
82
|
+
const resourceMeta = resource.getMeta?.();
|
|
83
|
+
return {
|
|
84
|
+
contents: [
|
|
85
|
+
{
|
|
86
|
+
uri,
|
|
87
|
+
mimeType: resource.mimeType,
|
|
88
|
+
text: resourceText,
|
|
89
|
+
...(resourceMeta ? { _meta: resourceMeta } : {})
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { configManager } from '../config-manager.js';
|
|
2
|
+
import { featureFlagManager } from './feature-flags.js';
|
|
3
|
+
// Cache for variant assignments (loaded once per session). This map and the
|
|
4
|
+
// experiments map are keyed by experiment names from remote JSON, so they have
|
|
5
|
+
// no prototype: a name like __proto__ or constructor is an entry like any other.
|
|
6
|
+
const variantCache = Object.create(null);
|
|
7
|
+
/**
|
|
8
|
+
* Get experiments config from feature flags.
|
|
9
|
+
* The config is remote JSON, so it is validated here once: every experiment
|
|
10
|
+
* comes back as { variants: WeightedVariant[] }, keeping only variants with a
|
|
11
|
+
* string name (an invalid or negative weight counts as 0). One malformed entry
|
|
12
|
+
* must not break the lookups for all the others.
|
|
13
|
+
*/
|
|
14
|
+
function getExperiments() {
|
|
15
|
+
const raw = featureFlagManager.get('experiments', {});
|
|
16
|
+
const experiments = Object.create(null);
|
|
17
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
18
|
+
return experiments;
|
|
19
|
+
for (const [name, experiment] of Object.entries(raw)) {
|
|
20
|
+
const rawVariants = Array.isArray(experiment?.variants) ? experiment.variants : [];
|
|
21
|
+
const variants = [];
|
|
22
|
+
for (const v of rawVariants) {
|
|
23
|
+
if (typeof v?.name !== 'string' || !v.name)
|
|
24
|
+
continue;
|
|
25
|
+
const weight = typeof v.weight === 'number' && Number.isFinite(v.weight) && v.weight > 0 ? v.weight : 0;
|
|
26
|
+
variants.push({ name: v.name, weight });
|
|
27
|
+
}
|
|
28
|
+
experiments[name] = { variants };
|
|
29
|
+
}
|
|
30
|
+
return experiments;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get user's variant for an experiment (cached, deterministic)
|
|
34
|
+
* Supports weighted variants for unequal splits
|
|
35
|
+
*/
|
|
36
|
+
async function getVariant(experimentName) {
|
|
37
|
+
const experiments = getExperiments();
|
|
38
|
+
const experiment = experiments[experimentName];
|
|
39
|
+
if (!experiment?.variants.length)
|
|
40
|
+
return null;
|
|
41
|
+
// Check cache
|
|
42
|
+
if (variantCache[experimentName]) {
|
|
43
|
+
return variantCache[experimentName];
|
|
44
|
+
}
|
|
45
|
+
// Check persisted assignment
|
|
46
|
+
const configKey = `abTest_${experimentName}`;
|
|
47
|
+
const existing = await configManager.getValue(configKey);
|
|
48
|
+
// Validate existing assignment is still a valid variant
|
|
49
|
+
const variantNames = experiment.variants.map(v => v.name);
|
|
50
|
+
if (existing && variantNames.includes(existing)) {
|
|
51
|
+
variantCache[experimentName] = existing;
|
|
52
|
+
return existing;
|
|
53
|
+
}
|
|
54
|
+
// New assignment based on clientId with weighted selection
|
|
55
|
+
const clientId = await configManager.getOrCreateClientId();
|
|
56
|
+
const hash = hashCode(clientId + experimentName);
|
|
57
|
+
// Calculate total weight and select variant
|
|
58
|
+
const totalWeight = experiment.variants.reduce((sum, v) => sum + v.weight, 0);
|
|
59
|
+
let variant;
|
|
60
|
+
if (totalWeight > 0) {
|
|
61
|
+
const roll = hash % totalWeight;
|
|
62
|
+
let cumulative = 0;
|
|
63
|
+
variant = experiment.variants[0].name; // fallback
|
|
64
|
+
for (const v of experiment.variants) {
|
|
65
|
+
cumulative += v.weight;
|
|
66
|
+
if (roll < cumulative) {
|
|
67
|
+
variant = v.name;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Fallback to equal split when weights are misconfigured (all zero)
|
|
74
|
+
const index = hash % experiment.variants.length;
|
|
75
|
+
variant = experiment.variants[index].name;
|
|
76
|
+
}
|
|
77
|
+
await configManager.setValue(configKey, variant);
|
|
78
|
+
variantCache[experimentName] = variant;
|
|
79
|
+
return variant;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the exact assigned variant for a named experiment.
|
|
83
|
+
*/
|
|
84
|
+
export async function getABTestVariant(experimentName) {
|
|
85
|
+
return getVariant(experimentName);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a feature (variant name) is enabled for current user
|
|
89
|
+
*/
|
|
90
|
+
export async function hasFeature(featureName) {
|
|
91
|
+
const experiments = getExperiments();
|
|
92
|
+
for (const [expName, experiment] of Object.entries(experiments)) {
|
|
93
|
+
if (experiment.variants.some(v => v.name === featureName)) {
|
|
94
|
+
const variant = await getVariant(expName);
|
|
95
|
+
return variant === featureName;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get all A/B test assignments for analytics (reads from config)
|
|
102
|
+
*/
|
|
103
|
+
export async function getABTestAssignments() {
|
|
104
|
+
const experiments = getExperiments();
|
|
105
|
+
const assignments = {};
|
|
106
|
+
for (const expName of Object.keys(experiments)) {
|
|
107
|
+
const configKey = `abTest_${expName}`;
|
|
108
|
+
const variant = await configManager.getValue(configKey);
|
|
109
|
+
if (variant) {
|
|
110
|
+
assignments[`ab_${expName}`] = variant;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return assignments;
|
|
114
|
+
}
|
|
115
|
+
function hashCode(str) {
|
|
116
|
+
let hash = 0;
|
|
117
|
+
for (let i = 0; i < str.length; i++) {
|
|
118
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
119
|
+
hash |= 0;
|
|
120
|
+
}
|
|
121
|
+
return Math.abs(hash);
|
|
122
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { renameWithRetry } from './rename.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Single place for crash-safe file writes. Data goes to a temp file next to
|
|
7
|
+
* the target and a rename swaps it in, so readers only ever see the previous
|
|
8
|
+
* or the new complete file — never a truncated one.
|
|
9
|
+
*
|
|
10
|
+
* - The temp file's data is flushed to disk before the rename: otherwise the
|
|
11
|
+
* new name can reach the disk before the data, and a crash or power loss
|
|
12
|
+
* leaves the file empty or zero-filled (#697, #692). On macOS/Linux the
|
|
13
|
+
* folder is flushed after the rename, so the rename itself survives.
|
|
14
|
+
* - Writes to the same file from this process run one after another, so the
|
|
15
|
+
* per-process temp name (`<file>.<pid>.tmp`) can't collide; the pid keeps
|
|
16
|
+
* separate processes off each other's temp file.
|
|
17
|
+
* - The swap goes through renameWithRetry, which rides out Windows' brief
|
|
18
|
+
* locks on files other processes have open.
|
|
19
|
+
*/
|
|
20
|
+
const pendingWrites = new Map();
|
|
21
|
+
async function writeThroughTempFile(filePath, data, options) {
|
|
22
|
+
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
23
|
+
try {
|
|
24
|
+
const handle = await fs.open(tempPath, 'w', options.mode);
|
|
25
|
+
try {
|
|
26
|
+
await handle.writeFile(data, { encoding: options.encoding ?? 'utf8' });
|
|
27
|
+
await handle.sync();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await handle.close();
|
|
31
|
+
}
|
|
32
|
+
await options.beforeCommit?.();
|
|
33
|
+
await renameWithRetry(tempPath, filePath);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
// After a successful rename the temp file is gone (ENOENT); after a failed
|
|
37
|
+
// write, removing it is best effort and the write's own error is what's reported
|
|
38
|
+
await fs.unlink(tempPath).catch(() => { });
|
|
39
|
+
}
|
|
40
|
+
await syncFolder(path.dirname(filePath));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* macOS/Linux: flushes the folder, so a rename in it reaches the disk. Windows
|
|
44
|
+
* can't open a folder for flushing, and NTFS journals the rename itself. The
|
|
45
|
+
* new content is already committed when this runs, so a failure is logged,
|
|
46
|
+
* not reported as a failed write.
|
|
47
|
+
*/
|
|
48
|
+
async function syncFolder(folder) {
|
|
49
|
+
if (process.platform === 'win32')
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const handle = await fs.open(folder, 'r');
|
|
53
|
+
try {
|
|
54
|
+
await handle.sync();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await handle.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.error(`Could not flush ${folder} after replacing a file in it: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Atomically replaces `filePath` with `data`. Resolves once the new content is
|
|
66
|
+
* committed; rejects (leaving the previous file intact) if it can't be.
|
|
67
|
+
*/
|
|
68
|
+
export function writeFileAtomic(filePath, data, options = {}) {
|
|
69
|
+
const key = path.resolve(filePath);
|
|
70
|
+
const previous = pendingWrites.get(key) ?? Promise.resolve();
|
|
71
|
+
// The previous write's failure was already reported to its own caller; this one runs regardless
|
|
72
|
+
const write = previous.catch(() => { }).then(() => writeThroughTempFile(key, data, options));
|
|
73
|
+
pendingWrites.set(key, write);
|
|
74
|
+
// Bookkeeping only: the caller gets `write` itself, with its rejection
|
|
75
|
+
write.finally(() => {
|
|
76
|
+
if (pendingWrites.get(key) === write)
|
|
77
|
+
pendingWrites.delete(key);
|
|
78
|
+
}).catch(() => { });
|
|
79
|
+
return write;
|
|
80
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
|
|
3
|
+
const uiOriginCallContext = new AsyncLocalStorage();
|
|
4
|
+
export function runInUiOriginCallContext(fn) {
|
|
5
|
+
return uiOriginCallContext.run(true, fn);
|
|
6
|
+
}
|
|
7
|
+
export function isInsideUiOriginCall() {
|
|
8
|
+
return uiOriginCallContext.getStore() === true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const toolCallPaths = new AsyncLocalStorage();
|
|
12
|
+
export function runWithToolCallPaths(fn) {
|
|
13
|
+
return toolCallPaths.run(new Set(), fn);
|
|
14
|
+
}
|
|
15
|
+
export function addToolCallPaths() {}
|
|
16
|
+
|
|
17
|
+
export function isTelemetryDisabledByEnv() {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function sanitizeError(error) {
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return {
|
|
24
|
+
message: `${error.name}: ${error.message}`,
|
|
25
|
+
code: 'code' in error ? error.code : undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
message: typeof error === 'string' ? error : 'Unknown error',
|
|
30
|
+
code: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const captureBase = async () => {};
|
|
35
|
+
export const buildEventProperties = async () => ({});
|
|
36
|
+
export const capture = async () => {};
|
|
37
|
+
export const captureTransport = async () => {};
|
|
38
|
+
export const capture_call_tool = async () => {};
|
|
39
|
+
export const capture_ui_event = async () => {};
|
|
40
|
+
export const captureRemote = async () => {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single place for ending the process on purpose. Use this instead of process.exit().
|
|
3
|
+
*
|
|
4
|
+
* process.exit() tears Node down at once, without waiting for work V8 still
|
|
5
|
+
* runs on background threads. On Windows that aborts the process with
|
|
6
|
+
* "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c"
|
|
7
|
+
* and exit code 0xC0000409 instead of the one asked for, whenever V8 is still
|
|
8
|
+
* compiling WebAssembly, e.g. the HTTP parser fetch() compiles right after a
|
|
9
|
+
* download (nodejs/node#56645). An exit that happens because nothing is left
|
|
10
|
+
* to do waits for that work.
|
|
11
|
+
*
|
|
12
|
+
* So exitProcess() sets the exit code and lets Node exit on its own. If
|
|
13
|
+
* something keeps the process alive (stdin, a socket, a timer), process.exit()
|
|
14
|
+
* ends it after EXIT_GRACE_MS, by which time that background work is done.
|
|
15
|
+
* Either way 'exit' listeners run and pending stdout/stderr writes get the
|
|
16
|
+
* grace period to flush.
|
|
17
|
+
*
|
|
18
|
+
* Unlike process.exit() this returns, and the process keeps running for up to
|
|
19
|
+
* EXIT_GRACE_MS: callers return right after calling it. Only the first call
|
|
20
|
+
* counts; later calls (a second Ctrl+C, a timeout firing) keep its exit code.
|
|
21
|
+
*/
|
|
22
|
+
/** Longest the process keeps running after exitProcess() */
|
|
23
|
+
export const EXIT_GRACE_MS = 1000;
|
|
24
|
+
let exitCode;
|
|
25
|
+
export function exitProcess(code) {
|
|
26
|
+
if (exitCode !== undefined)
|
|
27
|
+
return;
|
|
28
|
+
exitCode = code;
|
|
29
|
+
process.exitCode = code;
|
|
30
|
+
setTimeout(() => process.exit(code), EXIT_GRACE_MS).unref();
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class FeatureFlagManager {
|
|
2
|
+
async initialize() {}
|
|
3
|
+
get(_flagName, defaultValue = false) {
|
|
4
|
+
return defaultValue;
|
|
5
|
+
}
|
|
6
|
+
getAll() {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
async refresh() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
wasLoadedFromCache() {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
async waitForFreshFlags() {}
|
|
16
|
+
destroy() {}
|
|
17
|
+
}
|
|
18
|
+
export const featureFlagManager = new FeatureFlagManager();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary file handler
|
|
3
|
+
* Handles binary files that aren't supported by other handlers (Excel, Image)
|
|
4
|
+
* Uses isBinaryFile for content-based detection
|
|
5
|
+
* Returns instructions to use start_process with appropriate tools
|
|
6
|
+
*/
|
|
7
|
+
import fs from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { isBinaryFile } from 'isbinaryfile';
|
|
10
|
+
/**
|
|
11
|
+
* Binary file handler implementation
|
|
12
|
+
* Uses content-based detection via isBinaryFile
|
|
13
|
+
*/
|
|
14
|
+
export class BinaryFileHandler {
|
|
15
|
+
async canHandle(filePath) {
|
|
16
|
+
// Content-based binary detection using isBinaryFile
|
|
17
|
+
try {
|
|
18
|
+
return await isBinaryFile(filePath);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
// If we can't check (file doesn't exist, etc.), don't handle it
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async read(filePath, options) {
|
|
26
|
+
const instructions = this.getBinaryInstructions(filePath);
|
|
27
|
+
return {
|
|
28
|
+
content: instructions,
|
|
29
|
+
mimeType: 'text/plain',
|
|
30
|
+
metadata: {
|
|
31
|
+
isBinary: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async write(path, content) {
|
|
36
|
+
throw new Error('Cannot write binary files directly. Use start_process with appropriate tools (Python, Node.js libraries, command-line utilities).');
|
|
37
|
+
}
|
|
38
|
+
async getInfo(path) {
|
|
39
|
+
const stats = await fs.stat(path);
|
|
40
|
+
return {
|
|
41
|
+
size: stats.size,
|
|
42
|
+
created: stats.birthtime,
|
|
43
|
+
modified: stats.mtime,
|
|
44
|
+
accessed: stats.atime,
|
|
45
|
+
isDirectory: stats.isDirectory(),
|
|
46
|
+
isFile: stats.isFile(),
|
|
47
|
+
permissions: stats.mode.toString(8).slice(-3),
|
|
48
|
+
fileType: 'binary',
|
|
49
|
+
metadata: {
|
|
50
|
+
isBinary: true
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate instructions for handling binary files
|
|
56
|
+
*/
|
|
57
|
+
getBinaryInstructions(filePath) {
|
|
58
|
+
const fileName = path.basename(filePath);
|
|
59
|
+
return `Cannot read binary file as text: ${fileName}
|
|
60
|
+
|
|
61
|
+
Use start_process + interact_with_process to analyze binary files with appropriate tools (Node.js or Python libraries, command-line utilities, etc.).
|
|
62
|
+
|
|
63
|
+
The read_file tool only handles text files, images, and Excel files.`;
|
|
64
|
+
}
|
|
65
|
+
}
|