@remcp/runtime 0.2.85 → 0.2.87
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 +12 -0
- package/package.json +1 -1
- package/src/extended/common.mjs +80 -0
- package/src/extended/diagnostics.mjs +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.87 — 2026-09-21
|
|
4
|
+
|
|
5
|
+
- macOS `event_log` now streams only the requested head lines from `log show` and terminates the child once the limit is reached.
|
|
6
|
+
- Added a bounded child-process line reader with UTF-8-safe decoding, timeout and byte caps, plus regression coverage for noisy output.
|
|
7
|
+
- Runtime tool schemas and the 83-tool release catalog are unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.2.86 — 2026-09-21
|
|
10
|
+
|
|
11
|
+
- Runtime capability remains the same 83 granular operations; this patch changes hosted tool metadata only.
|
|
12
|
+
- No runtime schema, permission, handler, or operation name changed.
|
|
13
|
+
- Lockstep patch release with the ReMCP client/server at `0.2.86`.
|
|
14
|
+
|
|
3
15
|
## 0.2.85 — 2026-09-21
|
|
4
16
|
|
|
5
17
|
- Runtime capability remains 83 granular tools; no device-side operation is removed or renamed in this patch.
|
package/package.json
CHANGED
package/src/extended/common.mjs
CHANGED
|
@@ -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
|
|
96
|
-
return text(stdout
|
|
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);
|