nterminal 1.2.54 → 1.2.55
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/README.md +4 -1
- package/bin/nterminal.js +4 -0
- package/dist/client/assets/MarkdownPreview-CCE_ITdh.js +1 -0
- package/dist/client/assets/{TranscriptMarkdownBody-DJbLRp61.js → TranscriptMarkdownBody-CZrc3zaR.js} +1 -1
- package/dist/client/assets/{index-BEv9DwQ7.css → index-EhJZer0I.css} +1 -1
- package/dist/client/assets/index-LXA2msdF.js +54 -0
- package/dist/client/assets/{index-Bj8e-m7A.js → index-WieImnbS.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/scripts/distcall.js +436 -0
- package/dist/scripts/distcall.js.map +1 -0
- package/dist/server/agent/agentRoutes.js +124 -2
- package/dist/server/agent/agentRoutes.js.map +1 -1
- package/dist/server/distcall/envelope.d.ts +6 -0
- package/dist/server/distcall/envelope.js +56 -0
- package/dist/server/distcall/envelope.js.map +1 -0
- package/dist/server/distcall/executor.d.ts +23 -0
- package/dist/server/distcall/executor.js +316 -0
- package/dist/server/distcall/executor.js.map +1 -0
- package/dist/server/files/rootToken.d.ts +4 -2
- package/dist/server/files/rootToken.js +9 -4
- package/dist/server/files/rootToken.js.map +1 -1
- package/dist/server/http.d.ts +2 -1
- package/dist/server/http.js +2 -0
- package/dist/server/http.js.map +1 -1
- package/dist/server/routes/agentManagementRoutes.js +14 -2
- package/dist/server/routes/agentManagementRoutes.js.map +1 -1
- package/dist/server/routes/distributedCallRoutes.d.ts +9 -0
- package/dist/server/routes/distributedCallRoutes.js +305 -0
- package/dist/server/routes/distributedCallRoutes.js.map +1 -0
- package/dist/server/routes/fileRoutes.js +32 -3
- package/dist/server/routes/fileRoutes.js.map +1 -1
- package/dist/server/routes/terminalLayoutRoutes.js +12 -8
- package/dist/server/routes/terminalLayoutRoutes.js.map +1 -1
- package/dist/server/storage/fileStore.d.ts +8 -1
- package/dist/server/storage/fileStore.js +49 -2
- package/dist/server/storage/fileStore.js.map +1 -1
- package/dist/shared/layoutState.d.ts +3 -0
- package/dist/shared/layoutState.js +49 -0
- package/dist/shared/layoutState.js.map +1 -1
- package/dist/shared/protocol.d.ts +74 -2
- package/dist/shared/protocol.js.map +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/docs/features.md +3 -2
- package/docs/operations.md +1 -1
- package/package.json +1 -1
- package/dist/client/assets/MarkdownPreview-DvW1iSJP.js +0 -1
- package/dist/client/assets/index-DaKyipYG.js +0 -54
- package/docs/assets/nterminal-workspace.png +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { once } from 'node:events';
|
|
5
|
+
const defaultPort = 3107;
|
|
6
|
+
const defaultChunkBytes = 64 * 1024 * 1024;
|
|
7
|
+
const maxChunkBytes = 128 * 1024 * 1024;
|
|
8
|
+
async function main() {
|
|
9
|
+
const [command, ...args] = process.argv.slice(2);
|
|
10
|
+
if (command === 'run') {
|
|
11
|
+
await runCommand(args);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
printHelp();
|
|
15
|
+
process.exit(command === 'help' || command === '-h' || command === '--help' || command === undefined ? 0 : 1);
|
|
16
|
+
}
|
|
17
|
+
async function runCommand(args) {
|
|
18
|
+
loadRuntimeEnv();
|
|
19
|
+
const options = parseRunArgs(args);
|
|
20
|
+
const auth = resolveAuth(options);
|
|
21
|
+
const output = options.outputPath ? createWriteStream(options.outputPath, { flags: 'w', mode: 0o600 }) : process.stdout;
|
|
22
|
+
let wroteAny = false;
|
|
23
|
+
try {
|
|
24
|
+
for await (const chunk of readTaskChunks(options.inputPath, options.chunkBytes)) {
|
|
25
|
+
const runOptions = buildRunOptions(options);
|
|
26
|
+
await postTaskChunk(auth, chunk, runOptions, async (line) => {
|
|
27
|
+
wroteAny = true;
|
|
28
|
+
await writeLine(output, line);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
if (output !== process.stdout) {
|
|
34
|
+
await new Promise((resolve, reject) => {
|
|
35
|
+
output.end((error) => (error ? reject(error) : resolve()));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!wroteAny) {
|
|
40
|
+
throw new Error('no tasks were read from input');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseRunArgs(args) {
|
|
44
|
+
const options = {
|
|
45
|
+
inputPath: null,
|
|
46
|
+
outputPath: null,
|
|
47
|
+
mainUrl: null,
|
|
48
|
+
token: null,
|
|
49
|
+
workers: null,
|
|
50
|
+
concurrencyPerWorker: null,
|
|
51
|
+
intervalMsPerWorker: null,
|
|
52
|
+
jitterMs: null,
|
|
53
|
+
timeoutMs: null,
|
|
54
|
+
maxRequestBytes: null,
|
|
55
|
+
maxResponseBytes: null,
|
|
56
|
+
chunkBytes: defaultChunkBytes,
|
|
57
|
+
allowPrivateTargets: false
|
|
58
|
+
};
|
|
59
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
60
|
+
const arg = args[index];
|
|
61
|
+
if (arg === '-h' || arg === '--help') {
|
|
62
|
+
printHelp();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
if (!arg.startsWith('-')) {
|
|
66
|
+
if (options.inputPath) {
|
|
67
|
+
throw new Error(`unexpected argument: ${arg}`);
|
|
68
|
+
}
|
|
69
|
+
options.inputPath = arg;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const next = () => {
|
|
73
|
+
const value = args[++index];
|
|
74
|
+
if (value === undefined || value.startsWith('-')) {
|
|
75
|
+
throw new Error(`${arg} requires a value`);
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
};
|
|
79
|
+
switch (arg) {
|
|
80
|
+
case '--out':
|
|
81
|
+
options.outputPath = next();
|
|
82
|
+
break;
|
|
83
|
+
case '--main-url':
|
|
84
|
+
options.mainUrl = normalizeMainUrl(next());
|
|
85
|
+
break;
|
|
86
|
+
case '--token':
|
|
87
|
+
options.token = next();
|
|
88
|
+
break;
|
|
89
|
+
case '--workers':
|
|
90
|
+
options.workers = parseWorkers(next());
|
|
91
|
+
break;
|
|
92
|
+
case '--concurrency-per-worker':
|
|
93
|
+
options.concurrencyPerWorker = parsePositiveInt(next(), arg);
|
|
94
|
+
break;
|
|
95
|
+
case '--interval-ms-per-worker':
|
|
96
|
+
options.intervalMsPerWorker = parseNonNegativeInt(next(), arg);
|
|
97
|
+
break;
|
|
98
|
+
case '--jitter-ms':
|
|
99
|
+
options.jitterMs = parseNonNegativeInt(next(), arg);
|
|
100
|
+
break;
|
|
101
|
+
case '--timeout-ms':
|
|
102
|
+
options.timeoutMs = parsePositiveInt(next(), arg);
|
|
103
|
+
break;
|
|
104
|
+
case '--max-request-bytes':
|
|
105
|
+
options.maxRequestBytes = parseByteSize(next(), arg);
|
|
106
|
+
break;
|
|
107
|
+
case '--max-response-bytes':
|
|
108
|
+
options.maxResponseBytes = parseByteSize(next(), arg);
|
|
109
|
+
break;
|
|
110
|
+
case '--chunk-bytes':
|
|
111
|
+
options.chunkBytes = Math.min(parseByteSize(next(), arg), maxChunkBytes);
|
|
112
|
+
break;
|
|
113
|
+
case '--allow-private':
|
|
114
|
+
options.allowPrivateTargets = true;
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`unknown option: ${arg}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
function resolveAuth(options) {
|
|
123
|
+
const role = process.env.NTERMINAL_ROLE;
|
|
124
|
+
const mainUrl = normalizeMainUrl(options.mainUrl ??
|
|
125
|
+
process.env.NTERMINAL_DISTCALL_MAIN_URL ??
|
|
126
|
+
(role === 'main' ? `http://127.0.0.1:${process.env.NTERMINAL_PORT || String(defaultPort)}` : ''));
|
|
127
|
+
if (!mainUrl) {
|
|
128
|
+
throw new Error('main URL is required on secondary servers; pass --main-url or set NTERMINAL_DISTCALL_MAIN_URL');
|
|
129
|
+
}
|
|
130
|
+
if (options.token) {
|
|
131
|
+
return { mainUrl, headers: { Authorization: `Bearer ${options.token}` } };
|
|
132
|
+
}
|
|
133
|
+
if (role === 'main') {
|
|
134
|
+
const secret = process.env.NTERMINAL_SESSION_SECRET;
|
|
135
|
+
if (!secret) {
|
|
136
|
+
throw new Error('NTERMINAL_SESSION_SECRET is required for local main distcall');
|
|
137
|
+
}
|
|
138
|
+
return { mainUrl, headers: { 'x-nterminal-local-secret': secret } };
|
|
139
|
+
}
|
|
140
|
+
const token = process.env.NTERMINAL_AGENT_TOKEN;
|
|
141
|
+
if (!token) {
|
|
142
|
+
throw new Error('NTERMINAL_AGENT_TOKEN is required for secondary distcall requests');
|
|
143
|
+
}
|
|
144
|
+
return { mainUrl, headers: { Authorization: `Bearer ${token}` } };
|
|
145
|
+
}
|
|
146
|
+
function buildRunOptions(options) {
|
|
147
|
+
const runOptions = {};
|
|
148
|
+
if (options.workers && options.workers.length > 0)
|
|
149
|
+
runOptions.workerIds = options.workers;
|
|
150
|
+
if (options.concurrencyPerWorker !== null)
|
|
151
|
+
runOptions.concurrencyPerWorker = options.concurrencyPerWorker;
|
|
152
|
+
if (options.intervalMsPerWorker !== null)
|
|
153
|
+
runOptions.intervalMsPerWorker = options.intervalMsPerWorker;
|
|
154
|
+
if (options.jitterMs !== null)
|
|
155
|
+
runOptions.jitterMs = options.jitterMs;
|
|
156
|
+
if (options.timeoutMs !== null)
|
|
157
|
+
runOptions.timeoutMs = options.timeoutMs;
|
|
158
|
+
if (options.maxRequestBytes !== null)
|
|
159
|
+
runOptions.maxRequestBytes = options.maxRequestBytes;
|
|
160
|
+
if (options.maxResponseBytes !== null)
|
|
161
|
+
runOptions.maxResponseBytes = options.maxResponseBytes;
|
|
162
|
+
if (options.allowPrivateTargets)
|
|
163
|
+
runOptions.allowPrivateTargets = true;
|
|
164
|
+
return runOptions;
|
|
165
|
+
}
|
|
166
|
+
async function postTaskChunk(auth, tasks, options, onLine) {
|
|
167
|
+
const response = await fetch(`${auth.mainUrl}/api/distcalls/run`, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: {
|
|
170
|
+
...auth.headers,
|
|
171
|
+
'content-type': 'application/json'
|
|
172
|
+
},
|
|
173
|
+
body: JSON.stringify({ tasks, options })
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok || !response.body) {
|
|
176
|
+
const message = await safeResponseText(response);
|
|
177
|
+
throw new Error(`distcall request failed: ${response.status}${message ? ` ${message}` : ''}`);
|
|
178
|
+
}
|
|
179
|
+
await readNdjson(response.body, onLine);
|
|
180
|
+
}
|
|
181
|
+
async function* readTaskChunks(inputPath, chunkBytes) {
|
|
182
|
+
const input = inputPath ? createReadStream(inputPath, { encoding: 'utf8' }) : process.stdin.setEncoding('utf8');
|
|
183
|
+
let buffered = '';
|
|
184
|
+
let tasks = [];
|
|
185
|
+
let currentBytes = 2;
|
|
186
|
+
let lineNumber = 0;
|
|
187
|
+
for await (const text of input) {
|
|
188
|
+
buffered += text;
|
|
189
|
+
let newline = buffered.indexOf('\n');
|
|
190
|
+
while (newline !== -1) {
|
|
191
|
+
const line = buffered.slice(0, newline);
|
|
192
|
+
buffered = buffered.slice(newline + 1);
|
|
193
|
+
lineNumber += 1;
|
|
194
|
+
const task = parseTaskLine(line, lineNumber);
|
|
195
|
+
if (task) {
|
|
196
|
+
const taskBytes = taskJsonBytes(task, chunkBytes);
|
|
197
|
+
if (tasks.length > 0 && currentBytes + taskBytes > chunkBytes) {
|
|
198
|
+
yield tasks;
|
|
199
|
+
tasks = [];
|
|
200
|
+
currentBytes = 2;
|
|
201
|
+
}
|
|
202
|
+
tasks.push(task);
|
|
203
|
+
currentBytes += taskBytes;
|
|
204
|
+
}
|
|
205
|
+
newline = buffered.indexOf('\n');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (buffered.trim()) {
|
|
209
|
+
lineNumber += 1;
|
|
210
|
+
const task = parseTaskLine(buffered, lineNumber);
|
|
211
|
+
if (task) {
|
|
212
|
+
const taskBytes = taskJsonBytes(task, chunkBytes);
|
|
213
|
+
if (tasks.length > 0 && currentBytes + taskBytes > chunkBytes) {
|
|
214
|
+
yield tasks;
|
|
215
|
+
tasks = [];
|
|
216
|
+
currentBytes = 2;
|
|
217
|
+
}
|
|
218
|
+
tasks.push(task);
|
|
219
|
+
currentBytes += taskBytes;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (tasks.length > 0) {
|
|
223
|
+
yield tasks;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function taskJsonBytes(task, chunkBytes) {
|
|
227
|
+
const taskBytes = Buffer.byteLength(JSON.stringify(task)) + 1;
|
|
228
|
+
if (taskBytes > chunkBytes) {
|
|
229
|
+
throw new Error(`task ${task.taskId} is larger than --chunk-bytes`);
|
|
230
|
+
}
|
|
231
|
+
return taskBytes;
|
|
232
|
+
}
|
|
233
|
+
function parseTaskLine(line, lineNumber) {
|
|
234
|
+
const trimmed = line.trim();
|
|
235
|
+
if (!trimmed) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(trimmed);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new Error(`invalid JSON on line ${lineNumber}: ${error instanceof Error ? error.message : 'parse failed'}`);
|
|
244
|
+
}
|
|
245
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
246
|
+
throw new Error(`line ${lineNumber} must be an object`);
|
|
247
|
+
}
|
|
248
|
+
const candidate = parsed;
|
|
249
|
+
const request = candidate.request;
|
|
250
|
+
const taskId = typeof candidate.taskId === 'string' ? candidate.taskId : typeof candidate.id === 'string' ? candidate.id : String(lineNumber);
|
|
251
|
+
if (request && typeof request === 'object') {
|
|
252
|
+
return normalizeTask(taskId, request, lineNumber);
|
|
253
|
+
}
|
|
254
|
+
return normalizeTask(taskId, candidate, lineNumber);
|
|
255
|
+
}
|
|
256
|
+
function normalizeTask(taskId, request, lineNumber) {
|
|
257
|
+
const method = String(request.method ?? 'GET').toUpperCase();
|
|
258
|
+
if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
|
|
259
|
+
throw new Error(`line ${lineNumber} has unsupported method: ${method}`);
|
|
260
|
+
}
|
|
261
|
+
if (typeof request.url !== 'string' || !request.url) {
|
|
262
|
+
throw new Error(`line ${lineNumber} is missing url`);
|
|
263
|
+
}
|
|
264
|
+
const headers = normalizeHeaders(request.headers, lineNumber);
|
|
265
|
+
const task = {
|
|
266
|
+
taskId,
|
|
267
|
+
request: {
|
|
268
|
+
method: method,
|
|
269
|
+
url: request.url
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
if (headers)
|
|
273
|
+
task.request.headers = headers;
|
|
274
|
+
if (typeof request.bodyBase64 === 'string') {
|
|
275
|
+
task.request.bodyBase64 = request.bodyBase64;
|
|
276
|
+
}
|
|
277
|
+
else if ('body' in request) {
|
|
278
|
+
task.request.body = request.body;
|
|
279
|
+
}
|
|
280
|
+
return task;
|
|
281
|
+
}
|
|
282
|
+
function normalizeHeaders(value, lineNumber) {
|
|
283
|
+
if (value === undefined || value === null) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
287
|
+
throw new Error(`line ${lineNumber} headers must be an object`);
|
|
288
|
+
}
|
|
289
|
+
const headers = {};
|
|
290
|
+
for (const [key, headerValue] of Object.entries(value)) {
|
|
291
|
+
if (typeof headerValue !== 'string') {
|
|
292
|
+
throw new Error(`line ${lineNumber} header ${key} must be a string`);
|
|
293
|
+
}
|
|
294
|
+
headers[key] = headerValue;
|
|
295
|
+
}
|
|
296
|
+
return headers;
|
|
297
|
+
}
|
|
298
|
+
async function readNdjson(body, onLine) {
|
|
299
|
+
const reader = body.getReader();
|
|
300
|
+
const decoder = new TextDecoder();
|
|
301
|
+
let buffered = '';
|
|
302
|
+
while (true) {
|
|
303
|
+
const { done, value } = await reader.read();
|
|
304
|
+
if (done) {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
buffered += decoder.decode(value, { stream: true });
|
|
308
|
+
let newline = buffered.indexOf('\n');
|
|
309
|
+
while (newline !== -1) {
|
|
310
|
+
const line = buffered.slice(0, newline).trim();
|
|
311
|
+
if (line) {
|
|
312
|
+
await onLine(line);
|
|
313
|
+
}
|
|
314
|
+
buffered = buffered.slice(newline + 1);
|
|
315
|
+
newline = buffered.indexOf('\n');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
buffered += decoder.decode();
|
|
319
|
+
const tail = buffered.trim();
|
|
320
|
+
if (tail) {
|
|
321
|
+
await onLine(tail);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function writeLine(output, line) {
|
|
325
|
+
if (output.write(`${line}\n`)) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
await once(output, 'drain');
|
|
329
|
+
}
|
|
330
|
+
async function safeResponseText(response) {
|
|
331
|
+
try {
|
|
332
|
+
return (await response.text()).slice(0, 1000);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return '';
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function loadRuntimeEnv() {
|
|
339
|
+
const envPath = process.env.NTERMINAL_ENV_FILE || path.join(os.homedir(), '.nterminal/.env');
|
|
340
|
+
if (!existsSync(envPath)) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const text = readFileSync(envPath, 'utf8');
|
|
344
|
+
for (const line of text.split(/\r?\n/)) {
|
|
345
|
+
const parsed = parseEnvLine(line);
|
|
346
|
+
if (!parsed || process.env[parsed.key] !== undefined) {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
process.env[parsed.key] = parsed.value;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function parseEnvLine(line) {
|
|
353
|
+
const trimmed = line.trim();
|
|
354
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
|
|
358
|
+
if (!match) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
let value = match[2] ?? '';
|
|
362
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
363
|
+
value = value.slice(1, -1);
|
|
364
|
+
}
|
|
365
|
+
return { key: match[1], value };
|
|
366
|
+
}
|
|
367
|
+
function normalizeMainUrl(value) {
|
|
368
|
+
const trimmed = value.trim();
|
|
369
|
+
if (!trimmed) {
|
|
370
|
+
return '';
|
|
371
|
+
}
|
|
372
|
+
return new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`).origin;
|
|
373
|
+
}
|
|
374
|
+
function parseWorkers(value) {
|
|
375
|
+
if (value.trim().toLowerCase() === 'all') {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return value
|
|
379
|
+
.split(',')
|
|
380
|
+
.map((entry) => entry.trim())
|
|
381
|
+
.filter(Boolean);
|
|
382
|
+
}
|
|
383
|
+
function parsePositiveInt(value, name) {
|
|
384
|
+
const parsed = Number(value);
|
|
385
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
386
|
+
throw new Error(`${name} must be a positive integer`);
|
|
387
|
+
}
|
|
388
|
+
return parsed;
|
|
389
|
+
}
|
|
390
|
+
function parseNonNegativeInt(value, name) {
|
|
391
|
+
const parsed = Number(value);
|
|
392
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
393
|
+
throw new Error(`${name} must be a non-negative integer`);
|
|
394
|
+
}
|
|
395
|
+
return parsed;
|
|
396
|
+
}
|
|
397
|
+
function parseByteSize(value, name) {
|
|
398
|
+
const match = /^(\d+)(b|kb|mb|gb)?$/i.exec(value.trim());
|
|
399
|
+
if (!match) {
|
|
400
|
+
throw new Error(`${name} must be a byte size such as 1048576, 64mb, or 1gb`);
|
|
401
|
+
}
|
|
402
|
+
const amount = Number(match[1]);
|
|
403
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
404
|
+
throw new Error(`${name} must be positive`);
|
|
405
|
+
}
|
|
406
|
+
const unit = (match[2] ?? 'b').toLowerCase();
|
|
407
|
+
const multiplier = unit === 'gb' ? 1024 ** 3 : unit === 'mb' ? 1024 ** 2 : unit === 'kb' ? 1024 : 1;
|
|
408
|
+
return amount * multiplier;
|
|
409
|
+
}
|
|
410
|
+
function printHelp() {
|
|
411
|
+
console.log(`Usage: nterminal distcall run [requests.ndjson] [options]
|
|
412
|
+
|
|
413
|
+
Each input line is JSON. Accepted shapes:
|
|
414
|
+
{"id":"task-1","method":"GET","url":"https://api.example.invalid/items"}
|
|
415
|
+
{"taskId":"task-2","request":{"method":"POST","url":"https://api.example.invalid","headers":{},"body":{}}}
|
|
416
|
+
|
|
417
|
+
Options:
|
|
418
|
+
--out <file> Write NDJSON results to a file.
|
|
419
|
+
--main-url <url> Main server URL. Required on secondary unless NTERMINAL_DISTCALL_MAIN_URL is set.
|
|
420
|
+
--token <token> Override auth token for the main request.
|
|
421
|
+
--workers <all|id,id> Restrict workers for this run. Default: enabled workers from Settings.
|
|
422
|
+
--concurrency-per-worker <n> Requests in flight per worker. Default: 1.
|
|
423
|
+
--interval-ms-per-worker <n> Delay between launches per worker.
|
|
424
|
+
--jitter-ms <n> Random extra launch delay per request.
|
|
425
|
+
--timeout-ms <n> Per-request timeout.
|
|
426
|
+
--max-request-bytes <size> Per-target request cap, bounded by Settings.
|
|
427
|
+
--max-response-bytes <size> Per-target response cap, bounded by Settings.
|
|
428
|
+
--chunk-bytes <size> Main request chunk size. Default: 64mb, max: 128mb.
|
|
429
|
+
--allow-private Permit private targets only if Settings also allows them.
|
|
430
|
+
`);
|
|
431
|
+
}
|
|
432
|
+
main().catch((error) => {
|
|
433
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
434
|
+
process.exit(1);
|
|
435
|
+
});
|
|
436
|
+
//# sourceMappingURL=distcall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distcall.js","sourceRoot":"","sources":["../../scripts/distcall.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { once } from 'node:events';
|
|
1
2
|
import WebSocket from 'ws';
|
|
2
3
|
import { requireAgentToken, extractBearerToken, verifyAgentToken } from './agentAuth.js';
|
|
3
4
|
import { signFileRootToken, verifyFileRootToken, FileRootTokenError } from '../files/rootToken.js';
|
|
@@ -8,9 +9,49 @@ import { clearManualUpdate, readManualUpdate, rememberManualUpdate } from '../up
|
|
|
8
9
|
import { checkpointStateBeforeUpdate } from '../update/stateCheckpoint.js';
|
|
9
10
|
import { runRuntimeUninstall } from '../update/runtimeUninstall.js';
|
|
10
11
|
import { collectSystemStats } from '../system/stats.js';
|
|
12
|
+
import { decryptDistributedCallPayload, distributedCallRequestAuthWindowMs, encryptDistributedCallPayload, verifyDistributedCallRequestSignature } from '../distcall/envelope.js';
|
|
13
|
+
import { normalizeDistributedCallOptions, runDistributedCallTasks } from '../distcall/executor.js';
|
|
14
|
+
import { defaultDistributedCallMaxRequestBytes, defaultDistributedCallMaxResponseBytes } from '../storage/fileStore.js';
|
|
15
|
+
const distCallAgentBodyLimitBytes = 192 * 1024 * 1024;
|
|
11
16
|
export async function registerAgentRoutes(app, config, services) {
|
|
12
17
|
const fileExplorerService = services.fileExplorerService ?? new (await import('../files/fileExplorerService.js')).FileExplorerService(config);
|
|
13
18
|
const authMiddleware = requireAgentToken(config);
|
|
19
|
+
app.post('/api/agent/distcalls/run', { bodyLimit: distCallAgentBodyLimitBytes, onRequest: buildDistCallAuthHook(config) }, async (request, reply) => {
|
|
20
|
+
if (!config.agentToken) {
|
|
21
|
+
return reply.code(403).send({ error: 'agent_token_required' });
|
|
22
|
+
}
|
|
23
|
+
const jobId = typeof request.body?.jobId === 'string' ? request.body.jobId : '';
|
|
24
|
+
const headerJobId = singleHeader(request.headers['x-nterminal-distcall-job']);
|
|
25
|
+
if (!jobId || !request.body?.envelope || jobId !== headerJobId) {
|
|
26
|
+
return reply.code(400).send({ error: 'invalid_distcall_request' });
|
|
27
|
+
}
|
|
28
|
+
let payload;
|
|
29
|
+
try {
|
|
30
|
+
payload = decryptDistributedCallPayload(config.agentToken, jobId, 'tasks', request.body.envelope);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return reply.code(400).send({ error: 'invalid_distcall_envelope' });
|
|
34
|
+
}
|
|
35
|
+
const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];
|
|
36
|
+
const workerId = typeof payload.workerId === 'string' && payload.workerId ? payload.workerId : 'agent';
|
|
37
|
+
const options = normalizeDistributedCallOptions(payload.options, {
|
|
38
|
+
maxRequestBytes: payload.options?.maxRequestBytes ?? defaultDistributedCallMaxRequestBytes,
|
|
39
|
+
maxResponseBytes: payload.options?.maxResponseBytes ?? defaultDistributedCallMaxResponseBytes,
|
|
40
|
+
allowPrivateTargets: payload.options?.allowPrivateTargets === true
|
|
41
|
+
});
|
|
42
|
+
const abortController = new AbortController();
|
|
43
|
+
reply.raw.on('close', () => abortController.abort());
|
|
44
|
+
reply.raw.writeHead(200, {
|
|
45
|
+
'Content-Type': 'application/x-ndjson; charset=utf-8',
|
|
46
|
+
'Cache-Control': 'no-store'
|
|
47
|
+
});
|
|
48
|
+
await runDistributedCallTasks(workerId, tasks, options, async (result) => {
|
|
49
|
+
const envelope = encryptDistributedCallPayload(config.agentToken, jobId, 'result', result);
|
|
50
|
+
await writeNdjsonLine(reply.raw, { envelope });
|
|
51
|
+
}, abortController.signal);
|
|
52
|
+
reply.raw.end();
|
|
53
|
+
return reply;
|
|
54
|
+
});
|
|
14
55
|
// --- Terminal routes ---
|
|
15
56
|
app.get('/api/agent/terminals', { preHandler: authMiddleware }, async () => ({
|
|
16
57
|
terminals: services.terminalManager.listTerminals()
|
|
@@ -195,6 +236,20 @@ export async function registerAgentRoutes(app, config, services) {
|
|
|
195
236
|
// --- File routes ---
|
|
196
237
|
app.post('/api/agent/files/root', { preHandler: authMiddleware }, async (request, reply) => {
|
|
197
238
|
const terminalId = request.body?.terminalId;
|
|
239
|
+
const requestedPath = request.body?.path;
|
|
240
|
+
if (typeof requestedPath === 'string' && requestedPath.trim()) {
|
|
241
|
+
try {
|
|
242
|
+
const rootPath = await fileExplorerService.resolveRootPath(requestedPath);
|
|
243
|
+
const signed = signFileRootToken(config, { source: 'workspace', rootPath });
|
|
244
|
+
return { rootToken: signed.rootToken, rootPath, issuedAt: signed.issuedAt };
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (error instanceof FileExplorerError && error.code === 'root_unavailable') {
|
|
248
|
+
return reply.code(409).send({ error: 'workspace_root_unavailable' });
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
198
253
|
if (typeof terminalId !== 'string' || !terminalId.trim()) {
|
|
199
254
|
return reply.code(400).send({ error: 'invalid_file_root_request' });
|
|
200
255
|
}
|
|
@@ -207,7 +262,7 @@ export async function registerAgentRoutes(app, config, services) {
|
|
|
207
262
|
}
|
|
208
263
|
try {
|
|
209
264
|
const rootPath = await fileExplorerService.resolveRootPath(liveCwd);
|
|
210
|
-
const signed = signFileRootToken(config, { terminalId, rootPath });
|
|
265
|
+
const signed = signFileRootToken(config, { source: 'terminal', terminalId, rootPath });
|
|
211
266
|
return { rootToken: signed.rootToken, terminalId, rootPath, issuedAt: signed.issuedAt };
|
|
212
267
|
}
|
|
213
268
|
catch (error) {
|
|
@@ -256,7 +311,14 @@ export async function registerAgentRoutes(app, config, services) {
|
|
|
256
311
|
app.post('/api/agent/files/open', { preHandler: authMiddleware }, async (request, reply) => withAgentFileRoot(config, request, reply, services.terminalManager, fileExplorerService, async (root, body) => {
|
|
257
312
|
const b = body;
|
|
258
313
|
const target = await fileExplorerService.resolveFileForTerminalOpen(root.rootPath, b.path);
|
|
259
|
-
const terminalId = b.terminalId === undefined
|
|
314
|
+
const terminalId = b.terminalId === undefined
|
|
315
|
+
? root.terminalId
|
|
316
|
+
: typeof b.terminalId === 'string' && b.terminalId.trim()
|
|
317
|
+
? b.terminalId
|
|
318
|
+
: (() => { throw new FileExplorerError(400, 'invalid_terminal_id'); })();
|
|
319
|
+
if (!terminalId) {
|
|
320
|
+
throw new FileExplorerError(400, 'invalid_terminal_id');
|
|
321
|
+
}
|
|
260
322
|
const opened = services.terminalManager.writeToTerminal(terminalId, terminalOpenCommand(target.absolutePath));
|
|
261
323
|
if (!opened)
|
|
262
324
|
return reply.code(404).send({ error: 'terminal_not_found' });
|
|
@@ -267,6 +329,59 @@ export async function registerAgentRoutes(app, config, services) {
|
|
|
267
329
|
return reply.type(preview.contentType).header('Content-Length', preview.size).send(preview.stream);
|
|
268
330
|
}));
|
|
269
331
|
}
|
|
332
|
+
function buildDistCallAuthHook(config) {
|
|
333
|
+
const replayCache = new Map();
|
|
334
|
+
return (request, reply, done) => {
|
|
335
|
+
if (!config.agentToken) {
|
|
336
|
+
reply.code(403).send({ error: 'agent_token_required' });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const jobId = singleHeader(request.headers['x-nterminal-distcall-job']);
|
|
340
|
+
const timestamp = singleHeader(request.headers['x-nterminal-distcall-ts']);
|
|
341
|
+
const signature = singleHeader(request.headers['x-nterminal-distcall-mac']);
|
|
342
|
+
if (!jobId ||
|
|
343
|
+
!timestamp ||
|
|
344
|
+
!signature ||
|
|
345
|
+
!verifyDistributedCallRequestSignature(config.agentToken, jobId, timestamp, signature)) {
|
|
346
|
+
reply.code(401).send({ error: 'invalid_distcall_auth' });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const now = Date.now();
|
|
350
|
+
pruneReplayCache(replayCache, now);
|
|
351
|
+
if (replayCache.has(jobId)) {
|
|
352
|
+
reply.code(409).send({ error: 'replayed_distcall_request' });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
replayCache.set(jobId, now + distributedCallRequestAuthWindowMs);
|
|
356
|
+
done();
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function pruneReplayCache(cache, now) {
|
|
360
|
+
for (const [jobId, expiresAt] of cache) {
|
|
361
|
+
if (expiresAt <= now) {
|
|
362
|
+
cache.delete(jobId);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async function writeNdjsonLine(stream, value) {
|
|
367
|
+
const writable = stream;
|
|
368
|
+
if (writable.destroyed || writable.writableEnded || writable.writableFinished) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (stream.write(`${JSON.stringify(value)}\n`)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
await Promise.race([
|
|
375
|
+
once(stream, 'drain'),
|
|
376
|
+
once(stream, 'close'),
|
|
377
|
+
once(stream, 'error').then(([error]) => {
|
|
378
|
+
throw error instanceof Error ? error : new Error('stream_error');
|
|
379
|
+
})
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
function singleHeader(value) {
|
|
383
|
+
return typeof value === 'string' ? value : null;
|
|
384
|
+
}
|
|
270
385
|
function stateCheckpointErrorMessage(error) {
|
|
271
386
|
const detail = error instanceof Error ? error.message : String(error);
|
|
272
387
|
return `State checkpoint failed; update was not started. ${detail}`;
|
|
@@ -288,6 +403,13 @@ async function verifyAgentFileRoot(config, body, reply, terminalManager, fileExp
|
|
|
288
403
|
}
|
|
289
404
|
try {
|
|
290
405
|
const root = verifyFileRootToken(config, body.rootToken);
|
|
406
|
+
if (root.source === 'workspace') {
|
|
407
|
+
return root;
|
|
408
|
+
}
|
|
409
|
+
if (!root.terminalId) {
|
|
410
|
+
reply.code(401).send({ error: 'invalid_root_token' });
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
291
413
|
const terminal = terminalManager.getTerminal(root.terminalId);
|
|
292
414
|
if (!terminal) {
|
|
293
415
|
reply.code(404).send({ error: 'terminal_not_found' });
|