@remcp/runtime 0.2.196 → 0.2.198
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
|
@@ -144,27 +144,36 @@ ${page.slice.join('\n')}`);
|
|
|
144
144
|
export async function readMultipleFilesTool(args, extra = {}) {
|
|
145
145
|
if (!Array.isArray(args.paths) || !args.paths.length) fail('paths must be a non-empty array');
|
|
146
146
|
if (args.paths.length > 50) fail('paths accepts at most 50 entries per call');
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
147
|
+
// Four bounded I/O workers avoid serial head-of-line blocking while retaining
|
|
148
|
+
// deterministic input order and the existing per-file error behavior.
|
|
149
|
+
const sections = new Array(args.paths.length);
|
|
150
|
+
let cursor = 0;
|
|
151
|
+
async function worker() {
|
|
152
|
+
while (true) {
|
|
153
|
+
throwIfCancelled(extra.signal);
|
|
154
|
+
const index = cursor++;
|
|
155
|
+
if (index >= args.paths.length) return;
|
|
156
|
+
const entry = args.paths[index];
|
|
157
|
+
let absolute;
|
|
158
|
+
try {
|
|
159
|
+
absolute = await resolveSafePath(entry, 'paths[]');
|
|
160
|
+
} catch (error) {
|
|
161
|
+
sections[index] = `${String(entry)}: error - ${describeFilesystemFailure(error, { path: error?.path })}`;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const { content } = await readTextFile(absolute);
|
|
166
|
+
const lines = splitLines(content);
|
|
167
|
+
const limit = liveConfig('maxReadLines');
|
|
168
|
+
const slice = lines.slice(0, limit);
|
|
169
|
+
const suffix = lines.length > limit ? `\n… ${lines.length - limit} more lines truncated` : '';
|
|
170
|
+
sections[index] = `${displayPath(absolute)}:\n${slice.join('\n')}${suffix}`;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
sections[index] = `${displayPath(absolute)}: error - ${describeFilesystemFailure(error, { path: error?.path })}`;
|
|
173
|
+
}
|
|
166
174
|
}
|
|
167
175
|
}
|
|
176
|
+
await Promise.all(Array.from({ length: Math.min(4, args.paths.length) }, worker));
|
|
168
177
|
return text(sections.join('\n\n'));
|
|
169
178
|
}
|
|
170
179
|
|
|
@@ -288,9 +297,16 @@ export async function openPathForInfo(absolute) {
|
|
|
288
297
|
directory = await openDirectoryPath(absolute);
|
|
289
298
|
return { info: await directory.handle.stat(), close: directory.close };
|
|
290
299
|
} catch (directoryError) {
|
|
291
|
-
|
|
300
|
+
let canTryFile = directoryError?.code === 'ENOTDIR'
|
|
292
301
|
|| directoryError?.code === 'EISDIR'
|
|
293
302
|
|| (!directoryError?.code && /not a directory/i.test(directoryError?.message || ''));
|
|
303
|
+
// macOS/BSD lstat rejects a regular file when opening it as a directory.
|
|
304
|
+
// Retry only regular-file leaves; never bypass canonical allowed-root checks.
|
|
305
|
+
if (!canTryFile && process.platform !== 'linux'
|
|
306
|
+
&& directoryError?.message === 'Filesystem path component is not a safe directory') {
|
|
307
|
+
const leaf = await lstat(absolute).catch(() => null);
|
|
308
|
+
canTryFile = Boolean(leaf?.isFile() && !leaf?.isSymbolicLink());
|
|
309
|
+
}
|
|
294
310
|
if (!canTryFile) throw directoryError;
|
|
295
311
|
}
|
|
296
312
|
const opened = await openEntryAtPath(absolute, READ_NOFOLLOW_NONBLOCK);
|