@remcp/runtime 0.2.188 → 0.2.190
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 +5 -0
- package/package.json +1 -1
- package/src/core-catalog-part2.mjs +4 -4
- package/src/core-catalog-shared.mjs +2 -1
- package/src/sessions.mjs +69 -11
- package/src/tools/terminal.mjs +22 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.190 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
- Publish the runtime alongside the compatible ReMCP agent; the device HTTPS network probe is agent-owned and does not add runtime MCP tools.
|
|
6
|
+
- Preserve the existing 84-tool runtime contract while verifying public package source and release parity.
|
|
7
|
+
|
|
3
8
|
## 0.2.186 — 2026-10-26
|
|
4
9
|
|
|
5
10
|
- Return from same-document CDP navigation promptly when Page.navigate provides no new loaderId instead of awaiting a full load event that never fires.
|
package/package.json
CHANGED
|
@@ -228,8 +228,8 @@ export const coreToolDefinitionsPart2 = [
|
|
|
228
228
|
properties: {
|
|
229
229
|
pid: { type: 'number', description: 'Session pid returned by start_process.' },
|
|
230
230
|
offset: { type: 'number', description: 'Zero-based line number to start from, counted across everything the session has produced; a negative value reads the last N lines. Omit for new output. Lines evicted by the buffer cap are no longer available.' },
|
|
231
|
-
length: { type: 'number', description: 'Maximum number of lines to return.' },
|
|
232
|
-
timeout_ms: { type: 'number', description: '
|
|
231
|
+
length: { type: 'number', description: 'Maximum number of lines to return per call (default 200). Without offset, unreturned lines remain available; use hasMore to fetch the next page.' },
|
|
232
|
+
timeout_ms: { type: 'number', description: 'With no offset, wait up to this many milliseconds for output/exit; 0 returns immediately (default). Use 1000–2000 to avoid repeated remote polls when output is expected.' },
|
|
233
233
|
},
|
|
234
234
|
required: ['pid'],
|
|
235
235
|
additionalProperties: false,
|
|
@@ -288,7 +288,7 @@ export const coreToolDefinitionsPart2 = [
|
|
|
288
288
|
required: ['pid'],
|
|
289
289
|
additionalProperties: false,
|
|
290
290
|
},
|
|
291
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint:
|
|
291
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
292
292
|
outputSchema: TERMINATE_PROCESS_OUTPUT_SCHEMA,
|
|
293
293
|
handler: terminalToolHandlers.force_terminate,
|
|
294
294
|
},
|
|
@@ -335,7 +335,7 @@ export const coreToolDefinitionsPart2 = [
|
|
|
335
335
|
required: ['pid'],
|
|
336
336
|
additionalProperties: false,
|
|
337
337
|
},
|
|
338
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint:
|
|
338
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
339
339
|
handler: systemToolHandlers.kill_process,
|
|
340
340
|
},
|
|
341
341
|
{
|
|
@@ -45,7 +45,8 @@ export const READ_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
|
45
45
|
range:{ type:'string' },
|
|
46
46
|
output:{ type:'string' },
|
|
47
47
|
explicitOffset:{ type:'boolean' },
|
|
48
|
-
|
|
48
|
+
hasMore:{ type:'boolean' },
|
|
49
|
+
}, ['range','output','explicitOffset','hasMore']);
|
|
49
50
|
export const WAIT_PROCESS_OUTPUT_SCHEMA = terminalOutputSchema({
|
|
50
51
|
pattern:{ type:'string' },
|
|
51
52
|
matched:{ type:'boolean' },
|
package/src/sessions.mjs
CHANGED
|
@@ -37,13 +37,15 @@ function notify(session) {
|
|
|
37
37
|
|
|
38
38
|
// A waiter whose timer fires first must remove itself, otherwise finished sessions keep
|
|
39
39
|
// holding closures until the next append (which may never come).
|
|
40
|
-
function waiter(session, timeoutMs) {
|
|
40
|
+
function waiter(session, timeoutMs, signal = null) {
|
|
41
|
+
if (signal?.aborted) return Promise.resolve();
|
|
41
42
|
return new Promise(resolve => {
|
|
42
43
|
let settled = false;
|
|
43
44
|
const finish = () => {
|
|
44
45
|
if (settled) return;
|
|
45
46
|
settled = true;
|
|
46
47
|
clearTimeout(timer);
|
|
48
|
+
signal?.removeEventListener('abort', finish);
|
|
47
49
|
const index = session.waiters.indexOf(finish);
|
|
48
50
|
if (index !== -1) session.waiters.splice(index, 1);
|
|
49
51
|
resolve();
|
|
@@ -51,6 +53,9 @@ function waiter(session, timeoutMs) {
|
|
|
51
53
|
const timer = setTimeout(finish, Math.max(0, timeoutMs));
|
|
52
54
|
timer.unref?.();
|
|
53
55
|
session.waiters.push(finish);
|
|
56
|
+
signal?.addEventListener('abort', finish, { once:true });
|
|
57
|
+
// The signal can abort between the initial check and listener registration.
|
|
58
|
+
if (signal?.aborted) finish();
|
|
54
59
|
});
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -65,6 +70,8 @@ export function createProcessSession({ pid, child, command, shell }) {
|
|
|
65
70
|
lines: [],
|
|
66
71
|
partial: '',
|
|
67
72
|
lastPartialRead: null,
|
|
73
|
+
lastPartialLineIndex: null,
|
|
74
|
+
cursorColumn: 0,
|
|
68
75
|
droppedLines: 0,
|
|
69
76
|
bufferedChars: 0,
|
|
70
77
|
cursor: 0,
|
|
@@ -131,13 +138,64 @@ export function totalLines(session) {
|
|
|
131
138
|
return session.droppedLines + session.lines.length + (session.partial ? 1 : 0);
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
export function readNewOutput(session) {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
session.
|
|
141
|
+
export function readNewOutput(session, maxLines = Infinity, maxChars = Infinity) {
|
|
142
|
+
const parts = [];
|
|
143
|
+
const lineLimit = Math.max(1, maxLines);
|
|
144
|
+
let remaining = Math.max(1, maxChars);
|
|
145
|
+
// If the buffer evicted a partially read line, its character offset does
|
|
146
|
+
// not belong to the first remaining line; never skip that fresh output.
|
|
147
|
+
if (session.cursor < session.droppedLines) {
|
|
148
|
+
session.cursor = session.droppedLines;
|
|
149
|
+
session.cursorColumn = 0;
|
|
150
|
+
session.lastPartialRead = null;
|
|
151
|
+
session.lastPartialLineIndex = null;
|
|
152
|
+
}
|
|
153
|
+
let from = session.cursor - session.droppedLines;
|
|
154
|
+
while (from < session.lines.length && parts.length < lineLimit && remaining > 0) {
|
|
155
|
+
const absolute = session.droppedLines + from;
|
|
156
|
+
const line = session.lines[from];
|
|
157
|
+
let skip = session.cursorColumn || 0;
|
|
158
|
+
if (session.lastPartialLineIndex === absolute && session.lastPartialRead
|
|
159
|
+
&& line.startsWith(session.lastPartialRead)) {
|
|
160
|
+
skip = Math.max(skip, session.lastPartialRead.length);
|
|
161
|
+
}
|
|
162
|
+
const suffix = line.slice(skip);
|
|
163
|
+
const room = remaining - (parts.length ? 1 : 0);
|
|
164
|
+
if (room <= 0) break;
|
|
165
|
+
if (suffix.length > room) {
|
|
166
|
+
// Preserve the undispatched tail rather than consuming then truncating it.
|
|
167
|
+
parts.push(suffix.slice(0, room));
|
|
168
|
+
session.cursorColumn = skip + room;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
parts.push(suffix);
|
|
172
|
+
remaining -= suffix.length + (parts.length > 1 ? 1 : 0);
|
|
173
|
+
from++;
|
|
174
|
+
session.cursor = session.droppedLines + from;
|
|
175
|
+
session.cursorColumn = 0;
|
|
176
|
+
if (session.lastPartialLineIndex === absolute) {
|
|
177
|
+
session.lastPartialRead = null;
|
|
178
|
+
session.lastPartialLineIndex = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Partial lines may be updated without a newline. Send only newly appended
|
|
182
|
+
// characters, and track even a partial page of that unfinished line.
|
|
183
|
+
if (session.cursor === session.droppedLines + session.lines.length
|
|
184
|
+
&& parts.length < lineLimit && remaining > 0 && session.partial
|
|
185
|
+
&& session.partial !== session.lastPartialRead) {
|
|
186
|
+
const sent = session.lastPartialLineIndex === session.cursor
|
|
187
|
+
&& session.partial.startsWith(session.lastPartialRead || '')
|
|
188
|
+
? session.lastPartialRead || '' : '';
|
|
189
|
+
const room = remaining - (parts.length ? 1 : 0);
|
|
190
|
+
if (room > 0) {
|
|
191
|
+
const delta = session.partial.slice(sent.length, sent.length + room);
|
|
192
|
+
if (delta) {
|
|
193
|
+
parts.push(delta);
|
|
194
|
+
session.lastPartialRead = sent + delta;
|
|
195
|
+
session.lastPartialLineIndex = session.cursor;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
141
199
|
return parts;
|
|
142
200
|
}
|
|
143
201
|
|
|
@@ -164,9 +222,9 @@ export function readOutputRange(session, offset, length) {
|
|
|
164
222
|
return { slice: snapshot.slice(start, end), start: session.droppedLines + start + 1, end: session.droppedLines + end, total };
|
|
165
223
|
}
|
|
166
224
|
|
|
167
|
-
export async function waitForProcessActivity(session, timeoutMs) {
|
|
168
|
-
if (session.exited) return;
|
|
169
|
-
await waiter(session, timeoutMs);
|
|
225
|
+
export async function waitForProcessActivity(session, timeoutMs, signal = null) {
|
|
226
|
+
if (session.exited || signal?.aborted) return;
|
|
227
|
+
await waiter(session, timeoutMs, signal);
|
|
170
228
|
}
|
|
171
229
|
|
|
172
230
|
export async function waitForProcessExit(session, timeoutMs) {
|
package/src/tools/terminal.mjs
CHANGED
|
@@ -129,7 +129,9 @@ export async function startProcessTool(args, extra = {}) {
|
|
|
129
129
|
const output = session.lines.slice(-200).join('\n');
|
|
130
130
|
const partial = session.partial;
|
|
131
131
|
session.cursor = session.droppedLines + session.lines.length;
|
|
132
|
+
session.cursorColumn = 0;
|
|
132
133
|
session.lastPartialRead = session.partial || null;
|
|
134
|
+
session.lastPartialLineIndex = session.partial ? session.cursor : null;
|
|
133
135
|
const warning = verdict.note ? String(verdict.note) : '';
|
|
134
136
|
const rendered = [warning, headline, output, partial].filter(Boolean).join('\n');
|
|
135
137
|
const status = describeSession(session);
|
|
@@ -147,6 +149,7 @@ export async function readProcessOutputTool(args, extra = {}) {
|
|
|
147
149
|
const session = getProcessSession(pid);
|
|
148
150
|
if (!session) fail(`No ReMCP session with pid ${pid}`);
|
|
149
151
|
const timeoutMs = clampInteger(args.timeout_ms, 0, 0, 120000);
|
|
152
|
+
const maxLines = clampInteger(args.length, 200, 1, 5000);
|
|
150
153
|
const hasOffset = args.offset !== undefined && args.offset !== null;
|
|
151
154
|
let slice;
|
|
152
155
|
let range;
|
|
@@ -154,22 +157,28 @@ export async function readProcessOutputTool(args, extra = {}) {
|
|
|
154
157
|
// An explicit offset is a peek at a line range: it does not consume the new-output
|
|
155
158
|
// cursor, so a caller can look at the tail and still read everything afterwards.
|
|
156
159
|
// Zero-based from the first line the session produced; negative reads the last N.
|
|
157
|
-
const page = readOutputRange(session, Number(args.offset),
|
|
160
|
+
const page = readOutputRange(session, Number(args.offset), maxLines);
|
|
158
161
|
slice = page.slice;
|
|
159
162
|
range = `${page.start}-${page.end} of ${page.total}`;
|
|
160
163
|
} else {
|
|
161
|
-
//
|
|
164
|
+
// Wait for a process event, exit, cancellation or deadline. Repeated
|
|
165
|
+
// 200ms wakeups multiplied unnecessarily across many concurrent sessions.
|
|
162
166
|
if (timeoutMs && !hasNewOutput(session)) {
|
|
163
|
-
const deadline =
|
|
164
|
-
while (!hasNewOutput(session) && !session.exited &&
|
|
167
|
+
const deadline = performance.now() + timeoutMs;
|
|
168
|
+
while (!hasNewOutput(session) && !session.exited && performance.now() < deadline) {
|
|
165
169
|
throwIfAborted(extra.signal);
|
|
166
|
-
await waitForProcessActivity(session, Math.
|
|
170
|
+
await waitForProcessActivity(session, Math.max(0, deadline - performance.now()), extra.signal);
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
throwIfAborted(extra.signal);
|
|
174
|
+
const before = Math.max(session.cursor, session.droppedLines);
|
|
175
|
+
// 32K code units keep each structured + text mirror below the 128K
|
|
176
|
+
// truncation boundary, even for multi-byte Unicode lines.
|
|
177
|
+
slice = readNewOutput(session, maxLines, 32 * 1024);
|
|
170
178
|
session.waitCursor = session.cursor;
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
range = slice.length
|
|
180
|
+
? `${before + 1}-${before + slice.length} of ${totalLines(session)}`
|
|
181
|
+
: `no new output (${totalLines(session)} lines total)`;
|
|
173
182
|
}
|
|
174
183
|
const status = describeSession(session);
|
|
175
184
|
const header = `pid ${pid} ${status.status} · lines ${range}`;
|
|
@@ -179,6 +188,7 @@ export async function readProcessOutputTool(args, extra = {}) {
|
|
|
179
188
|
range,
|
|
180
189
|
output,
|
|
181
190
|
explicitOffset:hasOffset,
|
|
191
|
+
hasMore:!hasOffset && hasNewOutput(session),
|
|
182
192
|
}, `${header}\n${output}`);
|
|
183
193
|
}
|
|
184
194
|
|
|
@@ -216,8 +226,10 @@ export async function waitForProcessOutputTool(args, extra = {}) {
|
|
|
216
226
|
}
|
|
217
227
|
const matched = waiterMatches(slice, matcher);
|
|
218
228
|
session.cursor = session.droppedLines + session.lines.length;
|
|
229
|
+
session.cursorColumn = 0;
|
|
219
230
|
session.waitCursor = session.cursor;
|
|
220
231
|
session.lastPartialRead = session.partial || null;
|
|
232
|
+
session.lastPartialLineIndex = session.partial ? session.cursor : null;
|
|
221
233
|
const status = describeSession(session);
|
|
222
234
|
const headline = matched
|
|
223
235
|
? `pid ${pid} ${status.status} · pattern matched${bufferedMatch ? ' (already buffered)' : ''}`
|
|
@@ -243,8 +255,10 @@ export async function interactWithProcessTool(args, extra = {}) {
|
|
|
243
255
|
const timeoutMs = clampInteger(args.timeout_ms, 1000, 0, 120000);
|
|
244
256
|
|
|
245
257
|
session.cursor = session.droppedLines + session.lines.length;
|
|
258
|
+
session.cursorColumn = 0;
|
|
246
259
|
session.waitCursor = session.cursor;
|
|
247
260
|
session.lastPartialRead = null;
|
|
261
|
+
session.lastPartialLineIndex = null;
|
|
248
262
|
throwIfAborted(extra.signal);
|
|
249
263
|
const stdin = session.child?.stdin;
|
|
250
264
|
if (!stdin || stdin.destroyed || session.stdinClosed) {
|