@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.196",
3
+ "version": "0.2.198",
4
4
  "description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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
- const sections = [];
148
- for (const entry of args.paths) {
149
- throwIfCancelled(extra.signal);
150
- let absolute;
151
- try {
152
- absolute = await resolveSafePath(entry, 'paths[]');
153
- } catch (error) {
154
- sections.push(`${String(entry)}: error - ${describeFilesystemFailure(error, { path: error?.path })}`);
155
- continue;
156
- }
157
- try {
158
- const { content } = await readTextFile(absolute);
159
- const lines = splitLines(content);
160
- const limit = liveConfig('maxReadLines');
161
- const slice = lines.slice(0, limit);
162
- const suffix = lines.length > limit ? `\n… ${lines.length - limit} more lines truncated` : '';
163
- sections.push(`${displayPath(absolute)}:\n${slice.join('\n')}${suffix}`);
164
- } catch (error) {
165
- sections.push(`${displayPath(absolute)}: error - ${describeFilesystemFailure(error, { path: error?.path })}`);
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
- const canTryFile = directoryError?.code === 'ENOTDIR'
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);