@remcp/runtime 0.2.84 → 0.2.86

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.86 — 2026-09-21
4
+
5
+ - macOS `event_log` now streams unified-log output and terminates `/usr/bin/log show` after the requested line limit instead of buffering the whole selected time window.
6
+ - The shared bounded line reader preserves UTF-8 boundaries, caps memory/stderr, enforces timeouts, and force-kills a child that ignores graceful termination.
7
+ - Regression coverage proves a noisy process is stopped after the requested number of lines; the runtime catalog remains 83 tools.
8
+
9
+ ## 0.2.85 — 2026-09-21
10
+
11
+ - Runtime capability remains 83 granular tools; no device-side operation is removed or renamed in this patch.
12
+ - The hosted server now groups those operations into a compact typed discovery façade while dispatch still validates and executes the original per-operation runtime schemas.
13
+ - Lockstep patch release with the ReMCP client/server at `0.2.85`.
14
+
3
15
  ## 0.2.84 — 2026-09-21
4
16
 
5
17
  - `edit_document` can create a new DOCX with `create=true`, refusing to overwrite an existing destination.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.84",
3
+ "version": "0.2.86",
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",
@@ -4,6 +4,7 @@ import process from 'node:process';
4
4
  import { execFile, spawn } from 'node:child_process';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { mkdtemp, rm } from 'node:fs/promises';
7
+ import { StringDecoder } from 'node:string_decoder';
7
8
  import { promisify } from 'node:util';
8
9
  import { ToolError, fail, structured, text } from '../util.mjs';
9
10
 
@@ -101,6 +102,85 @@ export async function runWithInput(file, args = [], input = '', options = {}) {
101
102
  });
102
103
  }
103
104
 
105
+ export async function runFileHeadLines(file, args = [], lineLimit = 200, options = {}) {
106
+ const timeout = clamp(options.timeout, 15_000, 100, 120_000);
107
+ const limit = clamp(lineLimit, 200, 1, 5000);
108
+ const maxBuffer = clamp(options.maxBuffer, 1024 * 1024, 1024, 64 * 1024 * 1024);
109
+ return new Promise((resolve, reject) => {
110
+ const child = spawn(file, args.map(String), {
111
+ cwd: options.cwd,
112
+ env: options.env || process.env,
113
+ stdio: ['ignore', 'pipe', 'pipe'],
114
+ windowsHide: true,
115
+ });
116
+ const decoder = new StringDecoder('utf8');
117
+ const lines = [];
118
+ const err = [];
119
+ let pending = '';
120
+ let buffered = 0;
121
+ let settled = false;
122
+ let reachedLimit = false;
123
+ let hardKillTimer = null;
124
+ const finish = (fn, value) => {
125
+ if (settled) return;
126
+ settled = true;
127
+ clearTimeout(timer);
128
+ if (hardKillTimer) clearTimeout(hardKillTimer);
129
+ fn(value);
130
+ };
131
+ const stopAfterLimit = () => {
132
+ if (reachedLimit) return;
133
+ reachedLimit = true;
134
+ child.kill('SIGTERM');
135
+ hardKillTimer = setTimeout(() => child.kill('SIGKILL'), 250);
136
+ hardKillTimer.unref?.();
137
+ };
138
+ const timer = setTimeout(() => {
139
+ child.kill('SIGKILL');
140
+ finish(reject, new ToolError(`${options.label || file} timed out after ${timeout} ms`));
141
+ }, timeout);
142
+ child.stdout.on('data', chunk => {
143
+ if (settled || reachedLimit) return;
144
+ buffered += chunk.length;
145
+ if (buffered > maxBuffer) {
146
+ child.kill('SIGKILL');
147
+ finish(reject, new ToolError(`${options.label || file} exceeded the ${maxBuffer} byte output limit before producing ${limit} lines`));
148
+ return;
149
+ }
150
+ pending += decoder.write(chunk);
151
+ while (lines.length < limit) {
152
+ const newline = pending.indexOf('\n');
153
+ if (newline < 0) break;
154
+ lines.push(pending.slice(0, newline));
155
+ pending = pending.slice(newline + 1);
156
+ }
157
+ if (lines.length >= limit) stopAfterLimit();
158
+ });
159
+ child.stderr.on('data', chunk => {
160
+ if (settled || reachedLimit) return;
161
+ buffered += chunk.length;
162
+ if (buffered > maxBuffer) {
163
+ child.kill('SIGKILL');
164
+ finish(reject, new ToolError(`${options.label || file} exceeded the ${maxBuffer} byte output limit`));
165
+ return;
166
+ }
167
+ err.push(chunk);
168
+ });
169
+ child.on('error', error => finish(reject, new ToolError(`${options.label || file} failed: ${error.message}`)));
170
+ child.on('close', code => {
171
+ if (settled) return;
172
+ if (!reachedLimit) {
173
+ pending += decoder.end();
174
+ if (pending && lines.length < limit) lines.push(pending);
175
+ }
176
+ const stdout = lines.slice(0, limit).join('\n');
177
+ const stderr = Buffer.concat(err).toString('utf8');
178
+ if (code === 0 || reachedLimit || options.allowFailure) finish(resolve, { stdout, stderr, code, truncated: reachedLimit });
179
+ else finish(reject, new ToolError(`${options.label || file} failed: ${stderr.trim() || `exit ${code}`}`));
180
+ });
181
+ });
182
+ }
183
+
104
184
  export async function runPowerShell(script, options = {}) {
105
185
  const executable = process.env.SystemRoot
106
186
  ? path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
@@ -15,6 +15,7 @@ import {
15
15
  optionalString,
16
16
  requireEnum,
17
17
  runFile,
18
+ runFileHeadLines,
18
19
  runOsa,
19
20
  runPowerShell,
20
21
  safeEnvironment,
@@ -92,8 +93,8 @@ export async function eventLog(args) {
92
93
  if (process.platform === 'darwin') {
93
94
  const argv = ['show','--last',since,'--style','ndjson'];
94
95
  if (filter) argv.push('--predicate', `eventMessage CONTAINS[c] ${JSON.stringify(filter)}`);
95
- const { stdout } = await runFile('/usr/bin/log', argv, { label: 'macOS unified log', timeout: 30_000, maxBuffer: 32 * 1024 * 1024 });
96
- return text(stdout.split('\n').slice(0, limit).join('\n'));
96
+ const { stdout } = await runFileHeadLines('/usr/bin/log', argv, limit, { label: 'macOS unified log', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 });
97
+ return text(stdout);
97
98
  }
98
99
  if (!commandExists('journalctl')) unavailable('Event logs', 'journalctl is required on Linux');
99
100
  const n = Number.parseInt(since, 10), unit = since.at(-1);