@remcp/runtime 0.2.210 → 0.2.211
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/package.json
CHANGED
package/src/error-classes.mjs
CHANGED
|
@@ -5,6 +5,7 @@ const PERMISSION_CODES = new Set(['EACCES', 'EPERM']);
|
|
|
5
5
|
const MISSING_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
6
6
|
const TIMEOUT_CODES = new Set(['ETIMEDOUT', 'ETIME']);
|
|
7
7
|
const CONNECTION_CODES = new Set(['ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH']);
|
|
8
|
+
const STORAGE_CODES = new Set(['ENOSPC', 'EDQUOT']);
|
|
8
9
|
|
|
9
10
|
export function classifyRuntimeToolError(error, toolName = '') {
|
|
10
11
|
const code = typeof error?.code === 'string' ? error.code.toUpperCase() : '';
|
|
@@ -12,6 +13,8 @@ export function classifyRuntimeToolError(error, toolName = '') {
|
|
|
12
13
|
const browserTool = typeof toolName === 'string' && toolName.startsWith('browser_');
|
|
13
14
|
if (PERMISSION_CODES.has(code)) return 'permission';
|
|
14
15
|
if (MISSING_CODES.has(code)) return 'not_found';
|
|
16
|
+
if (STORAGE_CODES.has(code)) return 'resource_exhausted';
|
|
17
|
+
if (code === 'EBUSY') return 'busy';
|
|
15
18
|
if (TIMEOUT_CODES.has(code)) return 'timeout';
|
|
16
19
|
if (browserTool && CONNECTION_CODES.has(code)) return 'browser_unavailable';
|
|
17
20
|
if (browserTool && code === 'BROWSER_TARGET_DISAPPEARED') return 'browser_target_stale';
|
|
@@ -37,6 +40,7 @@ export function classifyRuntimeToolError(error, toolName = '') {
|
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
if (/permission denied|access is denied|not authorized|not authorised|accessibility permission/.test(message)) return 'permission';
|
|
43
|
+
if (/\b(?:file|path|source) not found\b|\bis not a directory\b/.test(message)) return 'not_found';
|
|
40
44
|
if (/timed out|timeout after|time limit exceeded/.test(message)) return 'timeout';
|
|
41
45
|
if (/element not found|no element matches|no matching ui window|target not found|not found in the current/.test(message)) return 'not_found';
|
|
42
46
|
if (/requires .*|must be (?:a |an |positive |between )|invalid (?:argument|selector|field|parameter)/.test(message)) return 'invalid_input';
|
package/src/permissions.mjs
CHANGED
|
@@ -19,15 +19,17 @@ export function macosProtectedLocation(target, { platform = process.platform, ho
|
|
|
19
19
|
if (platform !== 'darwin') return null;
|
|
20
20
|
const value = typeof target === 'string' ? target.trim() : '';
|
|
21
21
|
if (!value) return null;
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Evaluate macOS paths with POSIX semantics even when diagnostics/tests run on
|
|
23
|
+
// Windows. Using the host path module turns /Users/... into a Windows path.
|
|
24
|
+
const resolved = path.posix.resolve(value);
|
|
25
|
+
if (resolved === '/Volumes' || resolved.startsWith('/Volumes/')) return 'an external or network volume';
|
|
24
26
|
// The kernel reports the absolute path, so a folder can be recognised even when it belongs to
|
|
25
27
|
// another account: every /Users/<name>/Desktop is somebody's Desktop, and it is protected for the
|
|
26
28
|
// process that is asking, whatever this runtime's own home directory is.
|
|
27
|
-
const segments = resolved.split(path.sep);
|
|
28
|
-
const relative = home ? path.relative(path.resolve(home), resolved) : '';
|
|
29
|
-
const insideHome = relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
30
|
-
const candidate = insideHome ? relative.split(path.sep) : segments[0] === '' && segments[1] === 'Users' ? segments.slice(3) : null;
|
|
29
|
+
const segments = resolved.split(path.posix.sep);
|
|
30
|
+
const relative = home ? path.posix.relative(path.posix.resolve(home), resolved) : '';
|
|
31
|
+
const insideHome = relative && !relative.startsWith('..') && !path.posix.isAbsolute(relative);
|
|
32
|
+
const candidate = insideHome ? relative.split(path.posix.sep) : segments[0] === '' && segments[1] === 'Users' ? segments.slice(3) : null;
|
|
31
33
|
if (!candidate?.length) return null;
|
|
32
34
|
const [first, second] = candidate;
|
|
33
35
|
if (MACOS_HOME_FOLDERS.includes(first)) return `~/${first}`;
|
|
@@ -63,6 +65,9 @@ export function filesystemErrorExplanation(error, options = {}) {
|
|
|
63
65
|
const code = typeof error?.code === 'string' ? error.code : '';
|
|
64
66
|
const where = typeof target === 'string' && target.trim() ? target.trim() : 'that path';
|
|
65
67
|
if (code === 'ENOSPC') return 'The disk is full (ENOSPC). Free some space or write to another volume.';
|
|
68
|
+
if (code === 'EDQUOT') return 'The disk or temporary-file quota is exhausted (EDQUOT). Free quota space or select another writable volume.';
|
|
69
|
+
if (code === 'ENOENT') return `The file or folder does not exist (ENOENT): ${where}. Check its name and drive letter.`;
|
|
70
|
+
if (code === 'ENOTDIR') return `A component of ${where} is a file instead of a directory (ENOTDIR). Check the parent path.`;
|
|
66
71
|
if (code === 'EROFS') return `${where} is on a read-only file system (EROFS). Write somewhere else, or remount it read-write.`;
|
|
67
72
|
if (code === 'EBUSY') return `${where} is in use by another program (EBUSY). Close whatever holds it and try again.`;
|
|
68
73
|
if (!PERMISSION_CODES.has(code)) return null;
|
|
@@ -399,7 +399,11 @@ export async function moveFileTool(args) {
|
|
|
399
399
|
export async function copyFileTool(args) {
|
|
400
400
|
const source = await resolveSafePath(args.source, 'source');
|
|
401
401
|
const destination = await resolveSafePath(args.destination, 'destination');
|
|
402
|
-
|
|
402
|
+
// Windows paths normally refer to the same file irrespective of case.
|
|
403
|
+
// Refuse to overwrite the still-open source via a differently cased name.
|
|
404
|
+
if (source === destination || (process.platform === 'win32' && source.toLowerCase() === destination.toLowerCase())) {
|
|
405
|
+
fail('source and destination are the same path');
|
|
406
|
+
}
|
|
403
407
|
const overwrite = args.overwrite !== false;
|
|
404
408
|
let info;
|
|
405
409
|
await withExistingParent(source, async anchoredSource => {
|
|
@@ -309,6 +309,14 @@ export async function openPathForInfo(absolute) {
|
|
|
309
309
|
}
|
|
310
310
|
if (!canTryFile) throw directoryError;
|
|
311
311
|
}
|
|
312
|
+
// Metadata on Windows must not require opening a file for reading. Another
|
|
313
|
+
// process may deny FILE_SHARE_READ while lstat still succeeds (e.g. Office).
|
|
314
|
+
// lstat also does not traverse a final symbolic link or junction.
|
|
315
|
+
if (process.platform === 'win32') {
|
|
316
|
+
const info = await lstat(absolute);
|
|
317
|
+
if (info.isSymbolicLink()) fail('Path is a symbolic link');
|
|
318
|
+
if (info.isFile()) return { info, close: async () => {} };
|
|
319
|
+
}
|
|
312
320
|
const opened = await openEntryAtPath(absolute, READ_NOFOLLOW_NONBLOCK);
|
|
313
321
|
try {
|
|
314
322
|
return { info: await opened.handle.stat(), close: opened.close };
|