@myagentroam/node 0.9.131 → 0.9.132

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/dist/connector.js CHANGED
@@ -1480,7 +1480,8 @@ export class NodeConnector {
1480
1480
  this.operationRouter.register('node.upgrade.request', (data) => this.upgradeCoordinator.request(data));
1481
1481
  this.operationRouter.register('node.upgrade.cancel', () => this.upgradeCoordinator.cancel());
1482
1482
  this.operationRouter.register('node.workbench.capabilities', async () => ({
1483
- manifest: await this.workbenchManifestService.read()
1483
+ manifest: await this.workbenchManifestService.read(),
1484
+ binaryTerminal: true
1484
1485
  }));
1485
1486
  }
1486
1487
  async initializeConversationHosting() {
package/dist/marmgr.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // Keep the CLI implementation in its own package; the Node install exposes the same entrypoint.
3
+ const entrypoint = '@myagentroam/marmgr/bin/marmgr.mjs';
4
+ await import(entrypoint);
5
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ const entrypoint = '@myagentroam/marmgr/bin/marnodes.mjs';
3
+ await import(entrypoint);
4
+ export {};
package/dist/marscp.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ const entrypoint = '@myagentroam/marmgr/bin/marscp.mjs';
3
+ await import(entrypoint);
4
+ export {};
package/dist/marssh.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ const entrypoint = '@myagentroam/marmgr/bin/marssh.mjs';
3
+ await import(entrypoint);
4
+ export {};
@@ -166,6 +166,15 @@ export class AbstractRunner {
166
166
  function resolvedMcpServers(raw, secrets) {
167
167
  if (!Array.isArray(raw))
168
168
  return [];
169
+ const marmgrEnvironment = {
170
+ ...(secrets['MAR_MGR_SERVER_URL'] === undefined
171
+ ? {}
172
+ : { MAR_MGR_SERVER_URL: secrets['MAR_MGR_SERVER_URL'] }),
173
+ ...(secrets['MAR_MGR_USER_TOKEN'] === undefined
174
+ ? {}
175
+ : { MAR_MGR_USER_TOKEN: secrets['MAR_MGR_USER_TOKEN'] }),
176
+ MAR_MGR_CONTEXT_REQUIRED: '1'
177
+ };
169
178
  const result = [];
170
179
  for (const value of raw) {
171
180
  if (value === null || typeof value !== 'object' || Array.isArray(value))
@@ -191,17 +200,20 @@ function resolvedMcpServers(raw, secrets) {
191
200
  environment: record['__marGoal'] === true
192
201
  ? {
193
202
  MAR_GOAL_MCP_ENDPOINT: record['endpoint'],
194
- MAR_GOAL_MCP_TOKEN: secrets[record['secretName']]
203
+ MAR_GOAL_MCP_TOKEN: secrets[record['secretName']],
204
+ ...marmgrEnvironment
195
205
  }
196
206
  : record['__marScheduled'] === true
197
207
  ? {
198
208
  MAR_SCHEDULE_MCP_ENDPOINT: record['endpoint'],
199
209
  MAR_SCHEDULE_MCP_TOKEN: secrets[record['secretName']],
200
- MAR_SCHEDULE_MCP_READ_ONLY: record['readOnly'] === true ? '1' : '0'
210
+ MAR_SCHEDULE_MCP_READ_ONLY: record['readOnly'] === true ? '1' : '0',
211
+ ...marmgrEnvironment
201
212
  }
202
213
  : {
203
214
  MAR_MAIL_MCP_ENDPOINT: record['endpoint'],
204
- MAR_MAIL_MCP_TOKEN: secrets[record['secretName']]
215
+ MAR_MAIL_MCP_TOKEN: secrets[record['secretName']],
216
+ ...marmgrEnvironment
205
217
  }
206
218
  });
207
219
  continue;
@@ -241,7 +253,8 @@ function resolvedMcpServers(raw, secrets) {
241
253
  ],
242
254
  environment: {
243
255
  ...runtimeEnvironment,
244
- ...configuredEnvironment
256
+ ...configuredEnvironment,
257
+ ...marmgrEnvironment
245
258
  }
246
259
  });
247
260
  }
@@ -170,7 +170,7 @@ export class ClaudeManagedRunController {
170
170
  ...(typeof payload.model === 'string' ? { model: payload.model } : {}),
171
171
  ...(typeof payload.effort === 'string' ? { effort: payload.effort } : {}),
172
172
  ...(typeof payload.access === 'string' ? { access: payload.access } : {}),
173
- environment: secretEnvironment,
173
+ environment: { ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' },
174
174
  ...(typeof payload.personalInstructions === 'string'
175
175
  ? { personalInstructions: payload.personalInstructions }
176
176
  : {}),
@@ -385,7 +385,7 @@ export class CodexManagedRunController {
385
385
  return;
386
386
  }
387
387
  try {
388
- this.runner.prepareEnvironment(secretEnvironment, runId);
388
+ this.runner.prepareEnvironment({ ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' }, runId);
389
389
  }
390
390
  catch (error) {
391
391
  this.host.emit(runId, 'run.rejected', { code: safeErrorCode(error, 'SECRET_ENVIRONMENT_INVALID') }, 'FAILED');
@@ -686,7 +686,7 @@ export function allowsUntrustedWebFetchTls(environment) {
686
686
  return environment['MAR_AGENT_WEB_FETCH_ALLOW_UNTRUSTED_TLS'] !== '0';
687
687
  }
688
688
  async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
689
- const environment = { ...process.env, ...secretEnvironment };
689
+ const environment = { ...process.env, ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' };
690
690
  const ripgrep = await availableBundledRipgrep();
691
691
  return {
692
692
  environment: ripgrep === undefined ? environment : prependBundledRipgrepToPath(environment, ripgrep),
@@ -108,7 +108,10 @@ export class OpenCodeManagedRunController {
108
108
  let mcp;
109
109
  let attachments;
110
110
  try {
111
- environment = parseSecretEnvironment(payload.secretEnvironment);
111
+ environment = {
112
+ ...parseSecretEnvironment(payload.secretEnvironment),
113
+ MAR_MGR_CONTEXT_REQUIRED: '1'
114
+ };
112
115
  mcpSecretEnvironment = parseSecretEnvironment(payload.mcpSecretEnvironment);
113
116
  mcp = this.runner.mcpConfiguration(payload.mcpInstallations, {
114
117
  ...environment,
@@ -38,8 +38,11 @@ export class TerminalService {
38
38
  if (commandMode &&
39
39
  (input.workspaceId !== undefined ||
40
40
  typeof input.command !== 'string' ||
41
- input.command.trim().length === 0))
41
+ input.command.trim().length === 0 ||
42
+ (input.binary !== undefined && typeof input.binary !== 'boolean')))
42
43
  throw new Error('TERMINAL_COMMAND_INVALID');
44
+ if (!commandMode && input.binary !== undefined)
45
+ throw new Error('TERMINAL_OPEN_INVALID');
43
46
  const secretEnvironment = parseSecretEnvironment(input.secretEnvironment);
44
47
  if ((commandMode || secretEnvironment.MAR_GIT_TOKEN !== undefined) &&
45
48
  (!Number.isSafeInteger(input.__requestUserId) || Number(input.__requestUserId) <= 0))
@@ -58,11 +61,11 @@ export class TerminalService {
58
61
  const options = {
59
62
  ...input,
60
63
  workspaceId: typeof input.workspaceId === 'string' ? input.workspaceId : null,
61
- environment: secretEnvironment,
64
+ environment: { ...secretEnvironment, MAR_MGR_CONTEXT_REQUIRED: '1' },
62
65
  ...(cwd === undefined ? {} : { cwd })
63
66
  };
64
67
  const terminal = commandMode
65
- ? this.terminalManager.openCommand(options, input.command)
68
+ ? this.terminalManager.openCommand(options, input.command, input.binary === true)
66
69
  : this.terminalManager.open(options);
67
70
  if (commandMode || secretEnvironment.MAR_GIT_TOKEN !== undefined)
68
71
  this.terminalOwners.set(terminal.id, {
package/dist/terminal.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { spawn as spawnProcess } from 'node:child_process';
2
3
  import { existsSync } from 'node:fs';
3
4
  import { homedir } from 'node:os';
4
5
  import { join } from 'node:path';
@@ -26,6 +27,7 @@ export class TerminalManager {
26
27
  idleTimeoutMs;
27
28
  now;
28
29
  ptyFactory;
30
+ pipeFactory;
29
31
  terminate;
30
32
  terminals = new Map();
31
33
  attachments = new Map();
@@ -61,6 +63,7 @@ export class TerminalManager {
61
63
  }
62
64
  this.now = options.now ?? Date.now;
63
65
  this.ptyFactory = options.ptyFactory ?? nodePtyFactory;
66
+ this.pipeFactory = options.pipeFactory ?? nodePipeFactory;
64
67
  this.terminate =
65
68
  options.terminate ?? ((child, platform) => terminateProcessTree(child, platform));
66
69
  this.idleTimer = setInterval(() => void this.closeExpired(), Math.min(this.idleTimeoutMs, 60_000));
@@ -81,13 +84,13 @@ export class TerminalManager {
81
84
  return this.openInternal(input);
82
85
  }
83
86
  /** A command starts only after a relay or direct Terminal attachment is active. */
84
- openCommand(input, command) {
87
+ openCommand(input, command, binary = false) {
85
88
  if (typeof command !== 'string' ||
86
89
  command.trim().length === 0 ||
87
90
  Buffer.byteLength(command, 'utf8') > TERMINAL_COMMAND_MAX_BYTES ||
88
91
  command.includes('\0'))
89
92
  throw new Error('TERMINAL_COMMAND_INVALID');
90
- return this.openInternal(input, command);
93
+ return this.openInternal(input, command, binary);
91
94
  }
92
95
  startCommand(terminalId) {
93
96
  const terminal = this.requireTerminal(terminalId);
@@ -96,17 +99,25 @@ export class TerminalManager {
96
99
  return;
97
100
  delete terminal.deferredCommand;
98
101
  try {
99
- const pty = this.ptyFactory(deferred.shell, deferred.args, deferred.options);
100
- terminal.pty = pty;
101
- pty.onData((data) => this.recordOutput(terminal, data));
102
- pty.onExit((event) => this.exited(terminal, event.exitCode));
102
+ if (terminal.binary) {
103
+ const pipe = this.pipeFactory(deferred.shell, deferred.args, deferred.options);
104
+ terminal.pipe = pipe;
105
+ pipe.onData((data) => this.recordBinaryOutput(terminal, data));
106
+ pipe.onExit((exitCode) => this.exited(terminal, exitCode));
107
+ }
108
+ else {
109
+ const pty = this.ptyFactory(deferred.shell, deferred.args, deferred.options);
110
+ terminal.pty = pty;
111
+ pty.onData((data) => this.recordOutput(terminal, data));
112
+ pty.onExit((event) => this.exited(terminal, event.exitCode));
113
+ }
103
114
  }
104
115
  catch {
105
116
  this.emit({ type: 'error', terminalId, code: 'TERMINAL_COMMAND_START_FAILED' });
106
117
  this.exited(terminal, 127);
107
118
  }
108
119
  }
109
- openInternal(input = {}, command) {
120
+ openInternal(input = {}, command, binary = false) {
110
121
  const profileId = input.profileId ?? this.profiles.find((profile) => profile.isDefault)?.id;
111
122
  if (typeof profileId !== 'string')
112
123
  throw new Error('TERMINAL_PROFILE_INVALID');
@@ -143,6 +154,8 @@ export class TerminalManager {
143
154
  workspaceId: input.workspaceId ?? null,
144
155
  profile,
145
156
  pty,
157
+ pipe: undefined,
158
+ binary,
146
159
  ...(commandArgs === undefined
147
160
  ? {}
148
161
  : { deferredCommand: { shell: profile.shell, args: commandArgs, options } }),
@@ -252,12 +265,23 @@ export class TerminalManager {
252
265
  terminal.updatedAt = this.now();
253
266
  terminal.lastUsedAt = terminal.updatedAt;
254
267
  if (parsed.data.type === 'input') {
268
+ if (terminal.binary) {
269
+ if (terminal.pipe === undefined)
270
+ throw new Error('TERMINAL_COMMAND_NOT_STARTED');
271
+ const bytes = Buffer.from(parsed.data.data, 'base64');
272
+ if (bytes.length > 32 * 1024 || bytes.toString('base64') !== parsed.data.data)
273
+ throw new Error('TERMINAL_FRAME_INVALID');
274
+ terminal.pipe.write(bytes);
275
+ return;
276
+ }
255
277
  if (terminal.pty === undefined)
256
278
  throw new Error('TERMINAL_COMMAND_NOT_STARTED');
257
279
  terminal.pty.write(parsed.data.data);
258
280
  return;
259
281
  }
260
282
  if (parsed.data.type === 'resize') {
283
+ if (terminal.binary)
284
+ throw new Error('TERMINAL_FRAME_INVALID');
261
285
  if (terminal.pty === undefined)
262
286
  throw new Error('TERMINAL_COMMAND_NOT_STARTED');
263
287
  terminal.pty.resize(parsed.data.cols, parsed.data.rows);
@@ -279,6 +303,8 @@ export class TerminalManager {
279
303
  try {
280
304
  if (terminal.pty !== undefined)
281
305
  await this.terminate(terminal.pty, this.platform);
306
+ if (terminal.pipe !== undefined)
307
+ await this.terminate(terminal.pipe, this.platform);
282
308
  }
283
309
  finally {
284
310
  this.emit({ type: 'exit', terminalId: terminal.id, exitCode: terminal.exitCode });
@@ -331,6 +357,31 @@ export class TerminalManager {
331
357
  });
332
358
  }
333
359
  }
360
+ recordBinaryOutput(terminal, data) {
361
+ if (terminal.state !== 'RUNNING')
362
+ return;
363
+ for (let offset = 0; offset < data.length; offset += 32 * 1024) {
364
+ const part = data.subarray(offset, offset + 32 * 1024);
365
+ const frame = {
366
+ sequence: ++terminal.sequence,
367
+ data: part.toString('base64'),
368
+ bytes: part.length
369
+ };
370
+ terminal.replay.push(frame);
371
+ terminal.replayBytes += frame.bytes;
372
+ while (terminal.replayBytes > this.maxReplayBytes && terminal.replay.length > 0) {
373
+ const removed = terminal.replay.shift();
374
+ if (removed !== undefined)
375
+ terminal.replayBytes -= removed.bytes;
376
+ }
377
+ this.emit({
378
+ type: 'output',
379
+ terminalId: terminal.id,
380
+ sequence: frame.sequence,
381
+ data: frame.data
382
+ });
383
+ }
384
+ }
334
385
  exited(terminal, exitCode) {
335
386
  if (terminal.state !== 'RUNNING')
336
387
  return;
@@ -433,6 +484,33 @@ function nodePtyFactory(shell, args, options) {
433
484
  });
434
485
  return pty;
435
486
  }
487
+ function nodePipeFactory(shell, args, options) {
488
+ const child = spawnProcess(shell, [...args], {
489
+ cwd: options.cwd,
490
+ env: options.env,
491
+ stdio: ['pipe', 'pipe', 'pipe'],
492
+ detached: process.platform !== 'win32',
493
+ windowsHide: true
494
+ });
495
+ child.stdin.on('error', () => undefined);
496
+ child.stderr.resume();
497
+ return {
498
+ pid: child.pid,
499
+ kill: (signal) => child.kill(signal),
500
+ write: (bytes) => {
501
+ if (child.stdin.destroyed)
502
+ throw new Error('TERMINAL_PIPE_CLOSED');
503
+ child.stdin.write(bytes);
504
+ },
505
+ onData: (listener) => {
506
+ child.stdout.on('data', listener);
507
+ },
508
+ onExit: (listener) => {
509
+ child.on('error', () => listener(127));
510
+ child.on('close', (exitCode) => listener(exitCode ?? 1));
511
+ }
512
+ };
513
+ }
436
514
  export function defaultTerminalProfiles(platform, findWindowsExecutable = resolveWindowsExecutable) {
437
515
  if (platform === 'win32') {
438
516
  const pwsh = findWindowsExecutable('pwsh.exe');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/node",
3
- "version": "0.9.131",
3
+ "version": "0.9.132",
4
4
  "description": "MyAgentRoam Node runtime CLI.",
5
5
  "license": "Apache-2.0 AND GPL-2.0-or-later",
6
6
  "type": "module",
@@ -12,7 +12,11 @@
12
12
  "NOTICE"
13
13
  ],
14
14
  "bin": {
15
- "mar-node": "./dist/main.js"
15
+ "mar-node": "./dist/main.js",
16
+ "marmgr": "./dist/marmgr.js",
17
+ "marnodes": "./dist/marnodes.js",
18
+ "marssh": "./dist/marssh.js",
19
+ "marscp": "./dist/marscp.js"
16
20
  },
17
21
  "engines": {
18
22
  "node": ">=22.13"
@@ -30,8 +34,9 @@
30
34
  "npm": "11.12.1",
31
35
  "ws": "^8.21.3",
32
36
  "zod": "4.4.3",
33
- "@myagentroam/agent": "0.9.131",
34
- "@myagentroam/protocol": "0.9.131",
37
+ "@myagentroam/marmgr": "0.9.115",
38
+ "@myagentroam/agent": "0.9.132",
39
+ "@myagentroam/protocol": "0.9.132",
35
40
  "@myagentroam/code-navigation": "0.9.129"
36
41
  },
37
42
  "devDependencies": {