nterminal 1.2.54 → 1.2.56

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.
Files changed (49) hide show
  1. package/README.md +4 -1
  2. package/bin/nterminal.js +4 -0
  3. package/dist/client/assets/MarkdownPreview-Bk02CJkU.js +1 -0
  4. package/dist/client/assets/{TranscriptMarkdownBody-DJbLRp61.js → TranscriptMarkdownBody-CDKYjgIZ.js} +1 -1
  5. package/dist/client/assets/index-BBVLYVi6.js +54 -0
  6. package/dist/client/assets/index-BIczcK4f.css +1 -0
  7. package/dist/client/assets/{index-Bj8e-m7A.js → index-fsgQPmdV.js} +1 -1
  8. package/dist/client/index.html +2 -2
  9. package/dist/scripts/distcall.js +436 -0
  10. package/dist/scripts/distcall.js.map +1 -0
  11. package/dist/server/agent/agentRoutes.js +124 -2
  12. package/dist/server/agent/agentRoutes.js.map +1 -1
  13. package/dist/server/distcall/envelope.d.ts +6 -0
  14. package/dist/server/distcall/envelope.js +56 -0
  15. package/dist/server/distcall/envelope.js.map +1 -0
  16. package/dist/server/distcall/executor.d.ts +23 -0
  17. package/dist/server/distcall/executor.js +316 -0
  18. package/dist/server/distcall/executor.js.map +1 -0
  19. package/dist/server/files/rootToken.d.ts +4 -2
  20. package/dist/server/files/rootToken.js +9 -4
  21. package/dist/server/files/rootToken.js.map +1 -1
  22. package/dist/server/http.d.ts +2 -1
  23. package/dist/server/http.js +2 -0
  24. package/dist/server/http.js.map +1 -1
  25. package/dist/server/routes/agentManagementRoutes.js +14 -2
  26. package/dist/server/routes/agentManagementRoutes.js.map +1 -1
  27. package/dist/server/routes/distributedCallRoutes.d.ts +9 -0
  28. package/dist/server/routes/distributedCallRoutes.js +305 -0
  29. package/dist/server/routes/distributedCallRoutes.js.map +1 -0
  30. package/dist/server/routes/fileRoutes.js +32 -3
  31. package/dist/server/routes/fileRoutes.js.map +1 -1
  32. package/dist/server/routes/terminalLayoutRoutes.js +12 -8
  33. package/dist/server/routes/terminalLayoutRoutes.js.map +1 -1
  34. package/dist/server/storage/fileStore.d.ts +8 -1
  35. package/dist/server/storage/fileStore.js +49 -2
  36. package/dist/server/storage/fileStore.js.map +1 -1
  37. package/dist/shared/layoutState.d.ts +3 -0
  38. package/dist/shared/layoutState.js +49 -0
  39. package/dist/shared/layoutState.js.map +1 -1
  40. package/dist/shared/protocol.d.ts +74 -2
  41. package/dist/shared/protocol.js.map +1 -1
  42. package/dist/shared/types.d.ts +2 -0
  43. package/docs/features.md +3 -2
  44. package/docs/operations.md +1 -1
  45. package/package.json +1 -1
  46. package/dist/client/assets/MarkdownPreview-DvW1iSJP.js +0 -1
  47. package/dist/client/assets/index-BEv9DwQ7.css +0 -1
  48. package/dist/client/assets/index-DaKyipYG.js +0 -54
  49. 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 ? root.terminalId : (typeof b.terminalId === 'string' && b.terminalId.trim() ? b.terminalId : (() => { throw new FileExplorerError(400, 'invalid_terminal_id'); })());
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' });